-
-
Notifications
You must be signed in to change notification settings - Fork 203
feat(nx-plugin): add NX Plugin with OpenAPI client generator and executor #1947
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
530c086
c1d59ec
ec4dd29
c5219db
6ec8fca
e79a8fe
0449a29
8372977
c3908ba
57dda7f
ad16a4e
4cc4da0
43baacf
c8c2236
2526549
80ba81d
013257e
733ff69
94a79a0
e3e4b7d
52a1e75
92f6777
4810908
a2fb1da
1314d5d
a5eb2fa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,7 @@ jobs: | |
strategy: | ||
matrix: | ||
os: [macos-latest, ubuntu-latest, windows-latest] | ||
node-version: ['18.20.5', '20.11.1', '22.11.0'] | ||
node-version: ['20.19.0', '22.12.0'] | ||
steps: | ||
- uses: actions/[email protected] | ||
with: | ||
|
@@ -38,7 +38,7 @@ jobs: | |
run: pnpm build --filter="@hey-api/**" | ||
|
||
- name: Build examples | ||
if: matrix.node-version == '22.11.0' && matrix.os == 'ubuntu-latest' | ||
if: matrix.node-version == '24.0.1' && matrix.os == 'ubuntu-latest' | ||
run: pnpm build --filter="@examples/**" | ||
|
||
- name: Run linter | ||
|
@@ -54,7 +54,7 @@ jobs: | |
run: pnpm test:e2e | ||
|
||
- name: Publish preview packages | ||
if: github.event_name == 'pull_request' && matrix.node-version == '22.11.0' && matrix.os == 'ubuntu-latest' | ||
if: github.event_name == 'pull_request' && matrix.node-version == '24.0.1' && matrix.os == 'ubuntu-latest' | ||
run: ./scripts/publish-preview-packages.sh | ||
env: | ||
TURBO_SCM_BASE: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
22.11.0 | ||
22.12.0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,5 +20,8 @@ | |
"typescript": "5.8.3", | ||
"vite": "6.2.7", | ||
"vitest": "3.1.1" | ||
}, | ||
"overrides": { | ||
"find-my-way": "9.2.0" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"$schema": "../../node_modules/turbo/schema.json", | ||
"extends": ["//"], | ||
"tasks": { | ||
"build": { | ||
"cache": true, | ||
"dependsOn": ["^build"], | ||
"outputs": [".nuxt/**"] | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,7 +39,7 @@ | |
"vitepress": "turbo run $1 --filter=\"@docs/openapi-ts\"" | ||
}, | ||
"engines": { | ||
"node": "^18.18.0 || ^20.9.0 || >=22.10.0" | ||
"node": "^20.19.0 || >=22.12.0" | ||
}, | ||
"devDependencies": { | ||
"@arethetypeswrong/cli": "0.17.4", | ||
|
@@ -57,6 +57,7 @@ | |
"eslint-plugin-sort-keys-fix": "1.1.2", | ||
"eslint-plugin-typescript-sort-keys": "3.3.0", | ||
"eslint-plugin-vue": "9.32.0", | ||
"find-my-way": "9.2.0", | ||
"globals": "15.14.0", | ||
"husky": "9.1.7", | ||
"lint-staged": "15.3.0", | ||
|
@@ -67,6 +68,7 @@ | |
"turbo": "2.5.5", | ||
"typescript": "5.8.3", | ||
"typescript-eslint": "8.29.1", | ||
"vite-tsconfig-paths": "5.1.4", | ||
"vitest": "3.1.1" | ||
}, | ||
"packageManager": "[email protected]+sha512.1acb565e6193efbebda772702950469150cf12bcc764262e7587e71d19dc98a423dff9536e57ea44c49bdf790ff694e83c27be5faa23d67e0c033b583be4bfcf" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,19 @@ | |
"scripts": { | ||
"build": "tsup" | ||
}, | ||
"exports": { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was facing issues with our new configs, adding the exports and adding cjs fixed the issues, |
||
".": { | ||
"import": { | ||
"types": "./dist/index.d.ts", | ||
"default": "./dist/index.js" | ||
}, | ||
"require": { | ||
"types": "./dist/index.d.cts", | ||
"default": "./dist/index.cjs" | ||
} | ||
}, | ||
"./setup": "./src/setup.ts" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exporting the setup file as a direct source file for other vitests to use as a setup file. |
||
}, | ||
"dependencies": { | ||
"vite": "^6.2.7", | ||
"vitest": "^3.1.1" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
// FAIL LOUDLY on unhandled promise rejections / errors | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding this file because vitest fails if an error is uncaught, but no trace is given to the logs. This change adds the trace. This will help future debugging. |
||
process.on('unhandledRejection', (reason) => { | ||
console.log(`******** FAILED TO HANDLE PROMISE REJECTION ********`); | ||
throw reason; | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,16 +15,17 @@ export function createVitestConfig( | |
provider: 'v8', | ||
}, | ||
exclude: [...configDefaults.exclude], | ||
pool: platform() === 'win32' ? 'threads' : 'forks', | ||
pool: 'forks', | ||
poolOptions: { | ||
forks: { | ||
singleFork: false, | ||
singleFork: true, | ||
}, | ||
threads: { | ||
singleThread: false, | ||
singleThread: true, | ||
}, | ||
}, | ||
root, | ||
setupFiles: ['@config/vite-base/setup'], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding our new vitest setup file with the new export path |
||
testTimeout: platform() === 'win32' ? 10000 : 5000, | ||
}, | ||
}); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) Hey API | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
# @hey-api/nx-plugin | ||
|
||
This plugin provides a generator and executor for generating and updating OpenAPI clients using the `@hey-api/openapi-ts` library. This can be tied in to automation and CI workflows to ensure your API clients are always up to date. | ||
|
||
## Installation | ||
|
||
```bash | ||
npm install -D @hey-api/plugin-nx | ||
``` | ||
|
||
## Usage | ||
|
||
### Generators | ||
|
||
#### openapi-client | ||
|
||
[Docs](src/generators/openapi-client/README.md) | ||
|
||
This plugin provides a generator for generating OpenAPI clients using the `@hey-api/openapi-ts` library. | ||
|
||
Run in interactive mode `nx g @hey-api/nx-plugin:openapi-client` | ||
|
||
##### Options | ||
|
||
- `name`: The name of the project. [ string ] (required) | ||
- `scope`: The scope of the project. [ string ] (required) | ||
- `spec`: The path to the OpenAPI spec file. [ URI or string ] (required) | ||
- `directory`: The directory to create the project in. [ string ] (optional) (default: `libs`) | ||
- `client`: The type of client to generate. [ string ] (optional) (default: `@hey-api/client-fetch`) | ||
To specify a specific version of the client you can use `@hey-api/[email protected]`. | ||
- `tags`: The tags to add to the project. [ string[] ] (optional) (default: `api,openapi`) | ||
The defaults tags will not be added to the project if you specify this option. | ||
- `plugins`: Additional plugins to provide to the client api. [ string[] ] (optional) | ||
- `test`: The type of tests to setup. [ 'none' | 'vitest' ] (optional) (default: `none`) | ||
- `baseTsConfigName`: The name of the base tsconfig file that contains the compiler paths used to resolve the imports. Use this if the base tsconfig file is in the workspace root. If provided with a baseTsConfigPath then the baseTsConfigName will be added to the path. Do not use this if the baseTsConfigPath is a file. [ string ] (optional) | ||
- `baseTsConfigPath`: The path to the base tsconfig file that contains the compiler paths used to resolve the imports. Use this if the base tsconfig file is not in the workspace root. This can be a file or a directory. If it is a directory and the baseTsConfigName is provided then the baseTsConfigName will be added to the path. If it is a file and the baseTsConfigName is provided then there will be an error. [ string ] (optional) | ||
|
||
##### Example | ||
|
||
```bash | ||
nx g @hey-api/nx-plugin:openapi-client --name=my-api --client=@hey-api/client-fetch --scope=@my-app --directory=libs --spec=./spec.yaml --tags=api,openapi | ||
``` | ||
|
||
### Executors | ||
|
||
#### update-api | ||
|
||
This executor updates the OpenAPI spec file and generates a new client. | ||
The options for the executor will be populated from the generator. | ||
|
||
No need to add them yourself, to modify the options manually edit the `project.json` of the generated project. | ||
|
||
Run `nx run @my-org/my-generated-package:updateApi` | ||
|
||
##### Options | ||
|
||
- `spec`: The path to the OpenAPI spec file. [ URI or string ] (required) | ||
- `name`: The name of the project. [ string ] (required) | ||
- `scope`: The scope of the project. [ string ] (required) | ||
- `client`: The type of client to generate. [ string ] (optional) (default: `@hey-api/client-fetch`) | ||
- `directory`: The directory to create the project in. [ string ] (optional) (default: `libs`) | ||
- `plugins`: Additional plugins to provide to the client api. [ string[] ] (optional) (default:[]) | ||
|
||
###### Spec File Notes | ||
|
||
If the spec file is a relative path and is located in the workspace then the containing project will be listed as an implicit dependency. | ||
The assumption is made that that project will generate the API spec file on build. | ||
|
||
If the spec file is a URL then we fetch the spec during cache checks to determine if we should rebuild the client code. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"executors": { | ||
"update-api": { | ||
"implementation": "./dist/updateApi.cjs", | ||
"schema": "./dist/executors/update-api/updateApi.schema.json", | ||
"description": "Updates the OpenAPI spec file and generates new client code if needed." | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"name": "openapi-generator", | ||
"version": "0.0.1", | ||
"generators": { | ||
"openapi-client": { | ||
"factory": "./dist/openapiClient.cjs", | ||
"schema": "./dist/generators/openapi-client/openapiClient.schema.json", | ||
"description": "Generate an OpenAPI client library from an OpenAPI spec file." | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
{ | ||
"name": "@hey-api/nx-plugin", | ||
"version": "0.0.1", | ||
"description": "🚀 Nx plugin for `@hey-api/openapi-ts` codegen.", | ||
"homepage": "https://heyapi.dev/", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/hey-api/openapi-ts.git", | ||
"directory": "packages/nx-plugin" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/hey-api/openapi-ts/issues" | ||
}, | ||
"license": "MIT", | ||
"author": { | ||
"email": "[email protected]", | ||
"name": "Hey API", | ||
"url": "https://heyapi.dev" | ||
}, | ||
"funding": "https://github.com/sponsors/hey-api", | ||
"keywords": [ | ||
"codegen", | ||
"openapi", | ||
"plugin", | ||
"nx", | ||
"swagger" | ||
], | ||
"type": "commonjs", | ||
"main": "./dist/index.cjs", | ||
"module": "./dist/index.cjs", | ||
"types": "./dist/index.d.ts", | ||
"scripts": { | ||
"build": "tsup", | ||
"test:watch": "vitest watch --config vitest.config.ts", | ||
"test": "vitest run --config vitest.config.ts", | ||
"typecheck": "tsc --noEmit", | ||
"prepublishOnly": "pnpm build" | ||
}, | ||
"exports": { | ||
".": { | ||
"require": { | ||
"types": "./dist/index.d.cts", | ||
"default": "./dist/index.cjs" | ||
} | ||
}, | ||
"./package.json": "./package.json" | ||
}, | ||
"executors": "./executors.json", | ||
"generators": "./generators.json", | ||
"dependencies": { | ||
"@hey-api/json-schema-ref-parser": "^1.0.6", | ||
"@hey-api/openapi-ts": "workspace:*", | ||
"@nx/devkit": "^21.3.5", | ||
"api-smart-diff": "^1.0.6", | ||
"latest-version": "^9.0.0", | ||
"nx": "^21.3.5", | ||
"prettier": "^3.6.2", | ||
"swagger2openapi": "^7.0.8", | ||
"tslib": "^2.8.1", | ||
"xcurl": "^2.1.2" | ||
}, | ||
"devDependencies": { | ||
"@config/vite-base": "workspace:*", | ||
"@types/swagger2openapi": "^7.0.4", | ||
"typescript": "^5.8.3", | ||
"vitest": "^3.2.4" | ||
}, | ||
"files": [ | ||
"executors.json", | ||
"generators.json", | ||
"package.json", | ||
"dist", | ||
"LICENSE.md" | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { | ||
copyFileSync, | ||
existsSync, | ||
mkdirSync, | ||
readdirSync, | ||
statSync, | ||
} from 'node:fs'; | ||
import { dirname, extname, join, resolve } from 'node:path'; | ||
import { fileURLToPath } from 'node:url'; | ||
|
||
const __filename = fileURLToPath(import.meta.url); | ||
const __dirname = dirname(__filename); | ||
|
||
/** | ||
* Recursively creates a directory if it does not exist. | ||
* @param {string} dirPath | ||
*/ | ||
function ensureDirectoryExists(dirPath) { | ||
if (!existsSync(dirPath)) { | ||
mkdirSync(dirPath, { recursive: true }); | ||
} | ||
} | ||
|
||
/** | ||
* Recursively copies JSON files from a source directory to a target directory. | ||
* @param {string} sourceDir | ||
* @param {string} targetDir | ||
*/ | ||
function copyJsonFiles(sourceDir, targetDir) { | ||
// Read all files and directories in the source directory | ||
const items = readdirSync(sourceDir); | ||
|
||
for (const item of items) { | ||
const sourcePath = join(sourceDir, item); | ||
const targetPath = join(targetDir, item); | ||
|
||
const stats = statSync(sourcePath); | ||
|
||
if (stats.isDirectory()) { | ||
// Recursively copy JSON files from subdirectories | ||
copyJsonFiles(sourcePath, targetPath); | ||
} else if (stats.isFile() && extname(item).toLowerCase() === '.json') { | ||
// Copy JSON files | ||
ensureDirectoryExists(dirname(targetPath)); | ||
copyFileSync(sourcePath, targetPath); | ||
console.log(`Copied: ${sourcePath} -> ${targetPath}`); | ||
} | ||
} | ||
} | ||
|
||
// Copy JSON files from src to dist | ||
const sourceDir = resolve(__dirname, '../src'); | ||
const targetDir = resolve(__dirname, '../dist'); | ||
|
||
console.log('Copying JSON files from src to dist...'); | ||
copyJsonFiles(sourceDir, targetDir); | ||
console.log('JSON files copying completed.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added this file to cache builds