diff --git a/.npmignore b/.npmignore index ca1d4ee..c6de42a 100644 --- a/.npmignore +++ b/.npmignore @@ -2,4 +2,5 @@ node_modules/ .vscode/ src/ test/ -tsconfig.json \ No newline at end of file +tsconfig.json +Jenkinsfile \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..d2ace22 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,110 @@ +#!groovy + +SERVICE = "swaggerlicious" +SLACK_CHANNEL = "#aal-bambora-online-ci" + +def utils = new com.bambora.jenkins.pipeline.Utils() + +def getGitTag() { + def tag = sh script: "git describe --exact-match --tags \$(git log -n1 --pretty='%h')", returnStdout: true + return tag.trim() +} + +def getIntermediateGitTag() { + def tag = sh script: "git describe --tags", returnStdout: true + return tag.trim() +} + +def slack(String msg, String channel = "", String color = null) { + withCredentials([ + [ + $class: "UsernamePasswordMultiBinding", + credentialsId: "aal-bambora-online-ci", + usernameVariable: "USERNAME", + passwordVariable: "PASSWORD" + ] + ]) { + slackSend message: msg, color: color, token: "${env.PASSWORD}", channel: channel + } +} + +def notify_start(message) { + slack("*${SERVICE} (${env.BRANCH_NAME}):* ${message}", SLACK_CHANNEL, "info") +} + +def notify_success(message) { + slack("*${SERVICE} (${env.BRANCH_NAME}):* ${message}", SLACK_CHANNEL, "good") +} + +def notify_failure(message) { + slack("*${SERVICE} (${env.BRANCH_NAME}):* ${message}", SLACK_CHANNEL, "danger") +} + +node("docker-concurrent") { + checkout scm + + withCredentials([[ + $class: "UsernamePasswordMultiBinding", + credentialsId: "5811c22b-8ff3-4f49-9ba5-6d6ebbffc4a5", + usernameVariable: "GIT_USERNAME", + passwordVariable: "GIT_PASSWORD" + ]]) { + sh "git fetch --tags -q https://\${GIT_USERNAME}:\${GIT_PASSWORD}@github.com/bambora/swaggerlicious.git" + } + + try { + gitTag = getGitTag() + hasGitTag = true + } catch(error) { + gitTag = getIntermediateGitTag() + hasGitTag = false + } + + stage("Build") { + notify_start("Building of ${gitTag} has started...") + + try { + docker.image("node:7.0").inside("-u 0:0") { + sh "npm install" + env.NODE_ENV = "production" + sh "npm run build:production" + } + } catch (error) { + notify_failure("Building of ${gitTag} failed!") + throw error + } + + notify_success("Building of ${gitTag} was successful.") + } + + stage("Publish to public NPM") { + if (!(env.BRANCH_NAME == "master" && hasGitTag)) { + echo "Nothing to publish" + return + } + + notify_start("Publishing ${gitTag} to the public NPM repository...") + + try { + withCredentials([[ + $class: "StringBinding", + credentialsId: "public-npm-repository", + variable: "NPM_AUTH_TOKEN" + ]]) { + docker.image("node:7.0").inside("-u 0:0") { + sh "echo '//registry.npmjs.org/:_authToken=$NPM_AUTH_TOKEN' > .npmrc" + sh "npm publish --access public" + } + } + } catch (error) { + notify_failure("Publishing of ${gitTag} to the public NPM repository failed!") + throw error + } + + def version = gitTag.substring(1) + + notify_success("""Publishing of ${gitTag} to the public NPM repository was successful.\n +Direct link: https://registry.npmjs.org/@bambora/swaggerlicious/-/swaggerlicious-${version}.tgz +NPM install: `npm install @bambora/swaggerlicious@${version}`.""") + } +} \ No newline at end of file diff --git a/package.json b/package.json index 3f5941d..8fa3379 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "typescript": "^2.2.1" }, "dependencies": { - "json-refs": "^2.1.6", + "json-refs": "3.0.4", "@types/swagger-schema-official": "^2.0.1", "ramda": "^0.23.0" } diff --git a/src/index.ts b/src/index.ts index b08c23a..bbc35de 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,7 +16,7 @@ export default function swaggerlicious(jsonSpecification: OpenAPI.Spec) { } export function createReferenceLookupTable(specification: OpenAPI.Spec): Promise { - return jsonRefs.resolveRefs(specification).then(({ refs }) => { + return jsonRefs.resolveRefs(specification, { resolveCirculars: true }).then(({ refs }) => { const referenceLookupTable: ReferenceLookupTable = {}; R.mapObjIndexed( @@ -107,6 +107,10 @@ export type Properties = { [propertyName: string]: OpenAPI.Schema }; +export interface RecursiveNesting { + [rootNestingKey: number]: number; + } + export function getExampleRequest( operation : IOperation, referenceLookupTable : ReferenceLookupTable, @@ -148,7 +152,9 @@ export function getExampleResponse( export function traverseAndConstructExample( propertyOrDefinition : OpenAPI.Schema, onlyRequired : boolean, - nestingLevel : number + nestingLevel : number, + recursiveKey? : string, + recursiveNestingLevel? : RecursiveNesting ) { if (!propertyOrDefinition) return null; if (onlyRequired && !propertyOrDefinition.required) return null; @@ -168,7 +174,7 @@ export function traverseAndConstructExample( R.mapObjIndexed( (property, name) => { - exampleRequest[name] = getExampleValue(property, onlyRequired, nestingLevel); + exampleRequest[name] = getExampleValue(name, property, onlyRequired, nestingLevel, recursiveKey, recursiveNestingLevel); }, properties ); @@ -177,10 +183,13 @@ export function traverseAndConstructExample( } export function getExampleValue( + name : string, property : OpenAPI.Schema, onlyRequired : boolean, - nestingLevel : number -): any { + nestingLevel : number, + recursiveKey?: string, + recursiveNestingLevel?: RecursiveNesting +): any { if (property.example) return property.example; @@ -204,14 +213,30 @@ export function getExampleValue( if (property.type === "boolean") return false; - if (nestingLevel > 2) return; + //General max nesting + if (nestingLevel > 10) return; + + // Find current recursiveNestingLevel + if(recursiveNestingLevel === undefined) recursiveNestingLevel = {}; + + if (recursiveNestingLevel[recursiveKey] || 0 === 0) { + recursiveKey = name; + recursiveNestingLevel[recursiveKey] = (recursiveNestingLevel[recursiveKey] || 0) + 1; + } + + // Look for recursive nesting + if (recursiveNestingLevel[recursiveKey] > 2) { + return [null]; + } if (property.type === "array") { return [ traverseAndConstructExample( property.items as OpenAPI.Schema, onlyRequired, - (nestingLevel || 0) + 1 + (nestingLevel || 0) + 1, + recursiveKey, + recursiveNestingLevel ) ]; } @@ -220,6 +245,8 @@ export function getExampleValue( return traverseAndConstructExample( property, onlyRequired, - (nestingLevel || 0) + 1 + (nestingLevel || 0) + 1, + recursiveKey, + recursiveNestingLevel ); } \ No newline at end of file