diff --git a/.npmignore b/.npmignore index fee8b2e..47fad5c 100644 --- a/.npmignore +++ b/.npmignore @@ -14,6 +14,5 @@ /tslint.json /.eslintignore /.eslintrc.json -/.travis.yml /tsconfig.build.json /tsconfig.test.json diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0c886aa..0000000 --- a/.travis.yml +++ /dev/null @@ -1,5 +0,0 @@ -language: node_js -node_js: - - "10" -script: - - npm run test && npm run build diff --git a/README.md b/README.md index 6896215..ede8da4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -[![Build Status](https://travis-ci.org/devexperts/swagger-codegen-ts.svg?branch=master)](https://travis-ci.org/devexperts/swagger-codegen-ts) - # Typesafe OpenAPI generator for TypeScript +![](https://img.shields.io/npm/v/@devexperts/swagger-codegen-ts) + ## Features * Generates client code from **OpenAPI 3.0, 2.0** (aka Swagger) and **AsyncAPI** specs * **Pluggable HTTP clients:** can use `fetch`, `Axios` or any other library @@ -34,23 +34,25 @@ const createdPet: Promise = petController.addPet({ More usage scenarios are supported - check the [usage page](./docs/usage/generated-code.md) for more detail. -## Installation +## CLI Usage 1. Make sure the peer dependencies are installed, then install the codegen itself: - ``` + ```shell yarn add typescript fp-ts io-ts io-ts-types yarn add -D @devexperts/swagger-codegen-ts ``` -2. Create a console script that would invoke the `generate` function, passing the options such as path to the schema file and the output directory. -See the [Generators](docs/usage/api.md) page for the API reference, and [examples/generate](examples/generate) for sample scripts. +2. Use the CLI to generate the client code. Example: + ```shell + yarn swagger-codegen-ts -o src/generated petstore-api.yaml + ``` -3. In most cases, you might want to include the code generation step into the build and local launch scripts. Example: +3. In most cases, you might want to add this command into your `package.json` and include it into the build and local launch scripts. Example: ```diff /* package.json */ "scripts": { - + "generate:api": "ts-node scripts/generate-api.ts", + + "generate:api": "swagger-codegen-ts -o src/generated petstore-api.yaml", - "start": "react-scripts start", + "start": "yarn generate:api && react-scripts start", - "build": "react-scripts build" @@ -58,6 +60,13 @@ See the [Generators](docs/usage/api.md) page for the API reference, and [example } ``` +## API + +You can also create a custom script using the API. The main entry point of the API is the `generate` function, +which accepts options such as path to the schema file and the output directory. + +See the [Generators](docs/usage/api.md) page for the API reference, and [examples/generate](examples/generate) for sample scripts. + ## Contributing * Feel free to file bugs and feature requests in [GitHub issues](https://github.com/devexperts/swagger-codegen-ts/issues/new). diff --git a/docs/development/overview.md b/docs/development/overview.md index 1473736..a9016a3 100644 --- a/docs/development/overview.md +++ b/docs/development/overview.md @@ -4,8 +4,19 @@ TODO: describe the architecture and data flows ### FAQ 1. **Why don't spec codecs reuse common parts?** - - That's because different versions of specs refer to different versions of [JSON Schema](http://json-schema.org) and they are generally not the same. We would like to avoid maintaining JSON Schema composition in this project. (for now) + + That's because different versions of specs refer to different versions of [JSON Schema](http://json-schema.org) and they are generally not the same. We would like to avoid maintaining JSON Schema composition in this project. (for now) ### Publish -`npm version major|minor|patch` + +GitHub Actions are configured to build and publish a new version when a [new release](https://github.com/devexperts/swagger-codegen-ts/releases/new) is created in GitHub. + +If you need to run this process manually: +``` +# bumps the version in package.json, builds the dist files +# and commits the updated CHANGELOG.md +yarn version + +# uploads the package to the registry +yarn publish +``` diff --git a/package.json b/package.json index 05443a1..0d21734 100644 --- a/package.json +++ b/package.json @@ -8,15 +8,18 @@ "test:lint": "eslint \"./src/**/*.ts\" \"./test/**/*.ts\" --fix", "test:build": "tsc -p tsconfig.test.json", "test:jest": "jest", - "test": "yarn test:lint && yarn prettier && yarn test:jest && yarn test:build", + "test": "yarn test:lint && yarn test:jest && yarn test:build", "prettier": "prettier --list-different \"./src/**/*.ts\" \"./test/**/*.ts\"", "prettier:fix": "prettier --write \"./src/**/*.ts\" \"./test/**/*.ts\"", - "prepublishOnly": "yarn test && yarn build", "start": "nodemon --exec \"yarn test:run\"", "build": "tsc -p tsconfig.build.json", - "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0", + "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s", + "preversion": "yarn test && yarn build", "version": "yarn changelog && git add CHANGELOG.md" }, + "bin": { + "swagger-codegen-ts": "dist/cli/index.js" + }, "author": "devexperts", "license": "MPL-2.0", "repository": { @@ -41,7 +44,8 @@ "eslint-plugin-prettier": "^3.1.1", "fs-extra": "^8.1.0", "json-schema-ref-parser": "^7.1.1", - "prettier": "^1.19.1" + "prettier": "^1.19.1", + "yargs": "^17.7.1" }, "devDependencies": { "@devexperts/lint": "^1.0.0-alpha.10", diff --git a/src/cli/index.ts b/src/cli/index.ts new file mode 100644 index 0000000..800bf98 --- /dev/null +++ b/src/cli/index.ts @@ -0,0 +1,121 @@ +#!/usr/bin/env node +const yargs = require('yargs'); // eslint-disable-line +const { hideBin } = require('yargs/helpers'); // eslint-disable-line +import { array, either, taskEither } from 'fp-ts'; +import { serialize as serializeOpenAPI3 } from '../language/typescript/3.0'; +import { generate, Language } from '../index'; +import { Decoder } from 'io-ts'; +import { Reader } from 'fp-ts/lib/Reader'; +import { ResolveRefContext } from '../utils/ref'; +import { TaskEither } from 'fp-ts/lib/TaskEither'; +import * as $RefParser from 'json-schema-ref-parser'; +import * as path from 'path'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { OpenapiObject, OpenapiObjectCodec } from '../schema/3.0/openapi-object'; +import { SwaggerObject } from '../schema/2.0/swagger-object'; +import { serialize as serializeSwagger } from '../language/typescript/2.0'; +import { AsyncAPIObject, AsyncAPIObjectCodec } from '../schema/asyncapi-2.0.0/asyncapi-object'; +import { serialize as serializeAsyncApi } from '../language/typescript/asyncapi-2.0.0'; + +const options = yargs(hideBin(process.argv)) + .usage('swagger-codegen-ts -o [flags] ...') + .option('baseDir', { + alias: 'b', + describe: 'base directory for paths', + defaultDescription: 'current working dir', + }) + .option('outDir', { + alias: 'o', + describe: 'path to the output files, relative to the current working dir' + }) + .positional('', { + array: true, + type: 'string', + describe: 'list of OpenAPI or YAML specifications, paths relative to the ', + }) + .demandCommand(1, 'Please provide one or more spec files.') + .argv; + +const cwd = process.cwd(); +const specsDir = path.resolve(cwd, options.baseDir || '.'); +const outDir = path.resolve(cwd, options.outDir || '.'); + +const tasks = pipe( + options._, + array.map((spec: string) => path.resolve(specsDir, spec)), + array.map(spec => + pipe( + detectCodec(spec), + taskEither.chain(codec => + generate({ + ...(codec as any), + cwd: path.dirname(spec), + spec: path.basename(spec), + out: outDir, + }), + ), + ), + ), + array.array.sequence(taskEither.taskEither), +); + +tasks().then( + either.fold( + error => { + console.error(error); + process.exit(1); + }, + () => { + console.log('Generated successfully'); + }, + ), +); + +export interface DetectedCodec { + readonly decoder: Decoder; + readonly language: Reader>; +} + +const supportedCodecs = [ + { + decoder: OpenapiObjectCodec, + language: serializeOpenAPI3, + }, + { + decoder: SwaggerObject, + language: serializeSwagger, + }, + { + decoder: AsyncAPIObjectCodec, + language: serializeAsyncApi, + }, +]; + +function detectCodec( + spec: string, +): TaskEither | DetectedCodec | DetectedCodec> { + return pipe( + taskEither.tryCatch( + () => + $RefParser.resolve(spec, { + resolve: { + external: false, + http: false, + }, + }), + either.toError, + ), + taskEither.map(refs => Object.entries(refs.values())), + taskEither.filterOrElse( + i => i.length === 1, + () => new Error('Could not detect the schema type for ' + spec), + ), + taskEither.chain(([[, schema]]) => + pipe( + supportedCodecs, + array.findFirst(c => either.isRight(c.decoder.decode(schema))), + taskEither.fromOption(() => new Error('Could not detect the schema type for ' + spec)), + ), + ), + ); +} diff --git a/tsconfig.build.json b/tsconfig.build.json index 85fb6ef..baada0c 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -2,6 +2,7 @@ "extends": "./tsconfig.json", "include": [ "./src/index.ts", + "./src/cli/index.ts", "./src/language/typescript/2.0/index.ts", "./src/language/typescript/3.0/index.ts", "./src/language/typescript/asyncapi-2.0.0/index.ts", diff --git a/tsconfig.json b/tsconfig.json index 2869d9d..40a242f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,7 @@ "noEmitOnError": true }, "exclude": [ - "dist" + "dist", "node_modules" ], "include": [ "test/index.spec.ts" diff --git a/yarn.lock b/yarn.lock index f6cd1ea..16c14d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -651,7 +651,7 @@ ansi-regex@^4.0.0, ansi-regex@^4.1.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== -ansi-regex@^5.0.0: +ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== @@ -1082,6 +1082,15 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -4772,14 +4781,14 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string-width@^4.1.0, string-width@^4.2.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" - integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== dependencies: emoji-regex "^8.0.0" is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" + strip-ansi "^6.0.1" string.prototype.trimleft@^2.1.0: version "2.1.0" @@ -4832,12 +4841,12 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" - integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: - ansi-regex "^5.0.0" + ansi-regex "^5.0.1" strip-bom@^3.0.0: version "3.0.0" @@ -5409,6 +5418,11 @@ yargs-parser@^20.2.2, yargs-parser@^20.2.3: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + yargs@^13.3.0: version "13.3.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83" @@ -5438,6 +5452,19 @@ yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" +yargs@^17.7.1: + version "17.7.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.1.tgz#34a77645201d1a8fc5213ace787c220eabbd0967" + integrity sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + yn@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"