Merge pull request #22350 from backstage/openapi-tooling/fix-generate-client
feat(repo-tools): Rework command structure and standardize `openapi generate-client`
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
---
|
||||
'@backstage/repo-tools': minor
|
||||
---
|
||||
|
||||
**BREAKING**: The `schema openapi *` commands are now renamed into `package schema openapi *` and `repo schema openapi *`. The aim is to make it more clear what the command is operating on, the entire repo or just a single package.
|
||||
|
||||
The following commands now live under the `package` namespace,
|
||||
|
||||
- `schema openapi generate` is now `package schema openapi generate --server`
|
||||
- `schema openapi generate-client` is now `package schema openapi generate --client-package`
|
||||
- `schema openapi init` is now `package schema openapi init`
|
||||
|
||||
And these commands live under the new `repo` namespace,
|
||||
|
||||
- `schema openapi lint` is now `repo schema openapi lint`
|
||||
- `schema openapi test` is now `repo schema openapi test`
|
||||
- `schema openapi verify` is now `repo schema openapi verify`
|
||||
|
||||
The `package schema openapi generate` now supports defining both `--server` and `--client-package` to generate both at once.This update also reworks the `--client-package` flag to accept only an output directory as the input directory can now be inferred.
|
||||
@@ -113,10 +113,10 @@ jobs:
|
||||
run: yarn backstage-repo-tools generate-catalog-info --ci
|
||||
|
||||
- name: lint openapi yaml files
|
||||
run: yarn backstage-repo-tools schema openapi lint
|
||||
run: yarn backstage-repo-tools repo schema openapi lint
|
||||
|
||||
- name: verify openapi yaml file matches generated ts file
|
||||
run: yarn backstage-repo-tools schema openapi verify
|
||||
run: yarn backstage-repo-tools repo schema openapi verify
|
||||
|
||||
- name: verify doc links
|
||||
run: node scripts/verify-links.js
|
||||
@@ -225,7 +225,7 @@ jobs:
|
||||
|
||||
# We run the test cases before verifying the specs to prevent any failing tests from causing errors.
|
||||
- name: verify openapi specs against test cases
|
||||
run: yarn backstage-repo-tools schema openapi test
|
||||
run: yarn backstage-repo-tools repo schema openapi test
|
||||
|
||||
- name: ensure clean working directory
|
||||
run: |
|
||||
|
||||
@@ -45,7 +45,7 @@ You should create a new folder, `src/schema` in your backend plugin to store you
|
||||
|
||||
## Generating a typed express router from a spec
|
||||
|
||||
Run `yarn backstage-repo-tools schema openapi generate <plugin-directory>`. This will create an `openapi.generated.ts` file in the `src/schema` directory that contains the OpenAPI schema as well as a generated express router with types.
|
||||
Run `yarn backstage-repo-tools package schema openapi generate --server` from the directory with your plugin. This will create an `openapi.generated.ts` file in the `src/schema` directory that contains the OpenAPI schema as well as a generated express router with types. You should add this command to your `package.json` for future use and you can combine both the server generation and the client generation below like so, `yarn backstage-repo-tools package schema openapi generate --server --client-package <clientPackageDirectory>`
|
||||
|
||||
Use it like so, update your `router.ts` or `createRouter.ts` file with the following content,
|
||||
|
||||
@@ -63,7 +63,7 @@ export async function createRouter(
|
||||
|
||||
## Generating a typed client from a spec
|
||||
|
||||
Run `yarn backstage-repo-tools schema openapi generate-client --input-spec <plugin-directory>/src/schema/openapi.yaml --output-directory <plugin-client-directory>`. `<plugin-directory>` should match the same backend plugin we've been using so far. `<plugin-client-directory>` is a new directory and npm package that you should create. The general pattern is `plugins/<plugin-name>-client`.
|
||||
From your current backend plugin directory, run `yarn backstage-repo-tools package schema openapi generate --client-package <plugin-client-directory>`. `<plugin-client-directory>` is a new directory and npm package that you should create. The general pattern is `plugins/<plugin-name>-client` or if you want to co-locate this with your other shared types, use `plugins/<plugin-name>-common`. You should add this command to your `package.json` for future use.
|
||||
|
||||
The generated client will have a directory `src/generated` that exports a `DefaultApiClient` class and all generated types. You can use the client like so,
|
||||
|
||||
@@ -108,7 +108,7 @@ describe('createRouter', () => {
|
||||
+ app = wrapInOpenApiTestServer(express().use(router));
|
||||
```
|
||||
|
||||
This adds a wrapper around the express server that allows it to reroute traffic for `supertest`. Run `yarn backstage-repo-tools schema openapi init` to create some required config files. Now, when you run `yarn backstage-repo-tools schema openapi test` your schema will now be tested against your test data. Any errors will be reported.
|
||||
This adds a wrapper around the express server that allows it to reroute traffic for `supertest`. Run `yarn backstage-repo-tools package schema openapi init` to create some required files. Now, when you run `yarn backstage-repo-tools repo schema openapi test` your schema will now be tested against your test data. Any errors will be reported.
|
||||
|
||||
Our command is a small wrapper over [`Optic`](https://github.com/opticdev/optic) which does all of the heavy lifting.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ title: Generate a client from your OpenAPI spec
|
||||
description: Documentation on how to create a client for a given OpenAPI spec
|
||||
---
|
||||
|
||||
## How to generate a client with `repo-tools schema openapi generate-client`?
|
||||
## How to generate a client with `repo-tools package schema openapi generate client`?
|
||||
|
||||
### Prerequisites
|
||||
|
||||
@@ -20,7 +20,7 @@ info:
|
||||
|
||||
### Generating your client
|
||||
|
||||
1. Run `yarn backstage-repo-tools schema openapi generate-client --input-spec <file> --output-directory <directory>`. This will create a new folder in `<directory>/src/generated` to house the generated content.
|
||||
1. Run `yarn backstage-repo-tools schema openapi generate client --output-package <directory>`. This will create a new folder in `<directory>/src/generated` to house the generated content.
|
||||
2. You should use the generated files as follows,
|
||||
|
||||
- `apis/DefaultApi.client.ts` - this is the client that you should use. It has types for all of the various operations on your API.
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
---
|
||||
id: test-case-validation
|
||||
title: Validate your OpenAPI spec against test data
|
||||
description: Documentation on how to use the `schema openapi test` command.
|
||||
description: Documentation on how to use the `repo schema openapi test` command.
|
||||
---
|
||||
|
||||
## OpenAPI Validation using Test Cases
|
||||
|
||||
This is primarily performed by `backstage-repo-tools schema openapi test`. Any errors found in the generated specs can be either
|
||||
This is primarily performed by `backstage-repo-tools repo schema openapi test`. Any errors found in the generated specs can be either
|
||||
|
||||
1. Fixed manually, this is usually relevant for request body or response body changes.
|
||||
2. Fixed automatically with `backstage-repo-tools schema openapi test --update`.
|
||||
2. Fixed automatically with `backstage-repo-tools repo schema openapi test --update`.
|
||||
3. Fixing the test case. This can happen where a response is mocked as
|
||||
|
||||
```ts
|
||||
|
||||
@@ -15,7 +15,8 @@ Commands:
|
||||
api-reports [options] [paths...]
|
||||
type-deps
|
||||
generate-catalog-info [options]
|
||||
schema [command]
|
||||
package [command]
|
||||
repo [command]
|
||||
help [command]
|
||||
```
|
||||
|
||||
@@ -48,10 +49,23 @@ Options:
|
||||
-h, --help
|
||||
```
|
||||
|
||||
### `backstage-repo-tools schema`
|
||||
### `backstage-repo-tools package`
|
||||
|
||||
```
|
||||
Usage: backstage-repo-tools schema [options] [command] [command]
|
||||
Usage: backstage-repo-tools package [options] [command] [command]
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
|
||||
Commands:
|
||||
schema [command]
|
||||
help [command]
|
||||
```
|
||||
|
||||
### `backstage-repo-tools package schema`
|
||||
|
||||
```
|
||||
Usage: backstage-repo-tools package schema [options] [command] [command]
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
@@ -61,65 +75,104 @@ Commands:
|
||||
help [command]
|
||||
```
|
||||
|
||||
### `backstage-repo-tools schema openapi`
|
||||
### `backstage-repo-tools package schema openapi`
|
||||
|
||||
```
|
||||
Usage: backstage-repo-tools schema openapi [options] [command] [command]
|
||||
Usage: backstage-repo-tools package schema openapi [options] [command] [command]
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
|
||||
Commands:
|
||||
init
|
||||
generate [options]
|
||||
help [command]
|
||||
```
|
||||
|
||||
### `backstage-repo-tools package schema openapi generate`
|
||||
|
||||
```
|
||||
Usage: backstage-repo-tools package schema openapi generate [options]
|
||||
|
||||
Options:
|
||||
--client-package [package]
|
||||
-h, --help
|
||||
```
|
||||
|
||||
### `backstage-repo-tools package schema openapi init`
|
||||
|
||||
```
|
||||
Usage: backstage-repo-tools package schema openapi init [options]
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
```
|
||||
|
||||
### `backstage-repo-tools repo`
|
||||
|
||||
```
|
||||
Usage: backstage-repo-tools repo [options] [command] [command]
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
|
||||
Commands:
|
||||
schema [command]
|
||||
help [command]
|
||||
```
|
||||
|
||||
### `backstage-repo-tools repo schema`
|
||||
|
||||
```
|
||||
Usage: backstage-repo-tools repo schema [options] [command] [command]
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
|
||||
Commands:
|
||||
openapi [command]
|
||||
help [command]
|
||||
```
|
||||
|
||||
### `backstage-repo-tools repo schema openapi`
|
||||
|
||||
```
|
||||
Usage: backstage-repo-tools repo schema openapi [options] [command] [command]
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
|
||||
Commands:
|
||||
verify [paths...]
|
||||
generate [paths...]
|
||||
lint [options] [paths...]
|
||||
test [options] [paths...]
|
||||
init <paths...>
|
||||
help [command]
|
||||
```
|
||||
|
||||
### `backstage-repo-tools schema openapi generate`
|
||||
### `backstage-repo-tools repo schema openapi lint`
|
||||
|
||||
```
|
||||
Usage: backstage-repo-tools schema openapi generate [options] [paths...]
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
```
|
||||
|
||||
### `backstage-repo-tools schema openapi init`
|
||||
|
||||
```
|
||||
Usage: backstage-repo-tools schema openapi init [options] <paths...>
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
```
|
||||
|
||||
### `backstage-repo-tools schema openapi lint`
|
||||
|
||||
```
|
||||
Usage: backstage-repo-tools schema openapi lint [options] [paths...]
|
||||
Usage: backstage-repo-tools repo schema openapi lint [options] [paths...]
|
||||
|
||||
Options:
|
||||
--strict
|
||||
-h, --help
|
||||
```
|
||||
|
||||
### `backstage-repo-tools schema openapi test`
|
||||
### `backstage-repo-tools repo schema openapi test`
|
||||
|
||||
```
|
||||
Usage: backstage-repo-tools schema openapi test [options] [paths...]
|
||||
Usage: backstage-repo-tools repo schema openapi test [options] [paths...]
|
||||
|
||||
Options:
|
||||
--update
|
||||
-h, --help
|
||||
```
|
||||
|
||||
### `backstage-repo-tools schema openapi verify`
|
||||
### `backstage-repo-tools repo schema openapi verify`
|
||||
|
||||
```
|
||||
Usage: backstage-repo-tools schema openapi verify [options] [paths...]
|
||||
Usage: backstage-repo-tools repo schema openapi verify [options] [paths...]
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
|
||||
@@ -18,12 +18,59 @@ import { assertError } from '@backstage/errors';
|
||||
import { Command } from 'commander';
|
||||
import { exitWithError } from '../lib/errors';
|
||||
|
||||
function registerSchemaCommand(program: Command) {
|
||||
function registerPackageCommand(program: Command) {
|
||||
const command = program
|
||||
.command('package [command]')
|
||||
.description('Various tools for working with specific packages.');
|
||||
|
||||
const schemaCommand = command
|
||||
.command('schema [command]')
|
||||
.description(
|
||||
"Various tools for working with specific packages' API schema",
|
||||
);
|
||||
|
||||
const openApiCommand = schemaCommand
|
||||
.command('openapi [command]')
|
||||
.description('Tooling for OpenAPI schema');
|
||||
|
||||
openApiCommand
|
||||
.command('init')
|
||||
.description(
|
||||
'Initialize any required files to use the OpenAPI tooling for this package.',
|
||||
)
|
||||
.action(
|
||||
lazy(() =>
|
||||
import('./package/schema/openapi/init').then(m => m.singleCommand),
|
||||
),
|
||||
);
|
||||
|
||||
openApiCommand
|
||||
.command('generate')
|
||||
.option(
|
||||
'--client-package [package]',
|
||||
'Top-level path to where the client should be generated, ie packages/catalog-client.',
|
||||
)
|
||||
.option('--server')
|
||||
.description(
|
||||
'Command to generate a client and/or a server stub from an OpenAPI spec.',
|
||||
)
|
||||
.action(
|
||||
lazy(() =>
|
||||
import('./package/schema/openapi/generate').then(m => m.command),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function registerRepoCommand(program: Command) {
|
||||
const command = program
|
||||
.command('repo [command]')
|
||||
.description('Tools for working across your entire repository.');
|
||||
|
||||
const schemaCommand = command
|
||||
.command('schema [command]')
|
||||
.description('Various tools for working with API schema');
|
||||
|
||||
const openApiCommand = command
|
||||
const openApiCommand = schemaCommand
|
||||
.command('openapi [command]')
|
||||
.description('Tooling for OpenApi schema');
|
||||
|
||||
@@ -33,16 +80,9 @@ function registerSchemaCommand(program: Command) {
|
||||
'Verify that all OpenAPI schemas are valid and have a matching `schemas/openapi.generated.ts` file.',
|
||||
)
|
||||
.action(
|
||||
lazy(() => import('./openapi/schema/verify').then(m => m.bulkCommand)),
|
||||
);
|
||||
|
||||
openApiCommand
|
||||
.command('generate [paths...]')
|
||||
.description(
|
||||
'Generates a Typescript file from an OpenAPI yaml spec. For use with the `@backstage/backend-openapi-utils` ApiRouter type.',
|
||||
)
|
||||
.action(
|
||||
lazy(() => import('./openapi/schema/generate').then(m => m.bulkCommand)),
|
||||
lazy(() =>
|
||||
import('./repo/schema/openapi/verify').then(m => m.bulkCommand),
|
||||
),
|
||||
);
|
||||
|
||||
openApiCommand
|
||||
@@ -52,27 +92,16 @@ function registerSchemaCommand(program: Command) {
|
||||
'--strict',
|
||||
'Fail on any linting severity messages, not just errors.',
|
||||
)
|
||||
.action(lazy(() => import('./openapi/lint').then(m => m.bulkCommand)));
|
||||
.action(
|
||||
lazy(() => import('./repo/schema/openapi/lint').then(m => m.bulkCommand)),
|
||||
);
|
||||
|
||||
openApiCommand
|
||||
.command('test [paths...]')
|
||||
.description('Test OpenAPI schemas against written tests')
|
||||
.option('--update', 'Update the spec on failure.')
|
||||
.action(lazy(() => import('./openapi/test').then(m => m.bulkCommand)));
|
||||
|
||||
openApiCommand
|
||||
.command('init <paths...>')
|
||||
.description('Creates any config needed for the test command.')
|
||||
.action(lazy(() => import('./openapi/test/init').then(m => m.default)));
|
||||
|
||||
openApiCommand
|
||||
.command('generate-client')
|
||||
.requiredOption('--input-spec <file>')
|
||||
.requiredOption('--output-directory <directory>')
|
||||
.action(
|
||||
lazy(() =>
|
||||
import('./openapi/client/generate').then(m => m.singleCommand),
|
||||
),
|
||||
lazy(() => import('./repo/schema/openapi/test').then(m => m.bulkCommand)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -139,8 +168,8 @@ export function registerCommands(program: Command) {
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
registerSchemaCommand(program);
|
||||
registerPackageCommand(program);
|
||||
registerRepoCommand(program);
|
||||
}
|
||||
|
||||
// Wraps an action function so that it always exits and handles errors
|
||||
|
||||
+19
-16
@@ -16,16 +16,23 @@
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { resolve } from 'path';
|
||||
import { OPENAPI_IGNORE_FILES, OUTPUT_PATH } from '../constants';
|
||||
import { paths as cliPaths } from '../../../lib/paths';
|
||||
import {
|
||||
OPENAPI_IGNORE_FILES,
|
||||
OUTPUT_PATH,
|
||||
} from '../../../../../lib/openapi/constants';
|
||||
import { paths as cliPaths } from '../../../../../lib/paths';
|
||||
import { mkdirpSync } from 'fs-extra';
|
||||
import fs from 'fs-extra';
|
||||
import { exec } from '../../../lib/exec';
|
||||
import { exec } from '../../../../../lib/exec';
|
||||
import { resolvePackagePath } from '@backstage/backend-common';
|
||||
import { getPathToCurrentOpenApiSpec } from '../../../../../lib/openapi/helpers';
|
||||
|
||||
async function generate(spec: string, outputDirectory: string) {
|
||||
const resolvedOpenapiPath = resolve(spec);
|
||||
const resolvedOutputDirectory = resolve(outputDirectory, OUTPUT_PATH);
|
||||
async function generate(outputDirectory: string) {
|
||||
const resolvedOpenapiPath = await getPathToCurrentOpenApiSpec();
|
||||
const resolvedOutputDirectory = cliPaths.resolveTargetRoot(
|
||||
outputDirectory,
|
||||
OUTPUT_PATH,
|
||||
);
|
||||
mkdirpSync(resolvedOutputDirectory);
|
||||
|
||||
await fs.mkdirp(resolvedOutputDirectory);
|
||||
@@ -80,19 +87,15 @@ async function generate(spec: string, outputDirectory: string) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function singleCommand({
|
||||
inputSpec,
|
||||
outputDirectory,
|
||||
}: {
|
||||
inputSpec: string;
|
||||
outputDirectory: string;
|
||||
}): Promise<void> {
|
||||
export async function command(outputPackage: string): Promise<void> {
|
||||
try {
|
||||
await generate(inputSpec, outputDirectory);
|
||||
console.log(chalk.green(`Generated client for ${inputSpec}`));
|
||||
await generate(outputPackage);
|
||||
console.log(
|
||||
chalk.green(`Generated client in ${outputPackage}/${OUTPUT_PATH}`),
|
||||
);
|
||||
} catch (err) {
|
||||
console.log();
|
||||
console.log(chalk.red(`Client generation failed in ${outputDirectory}:`));
|
||||
console.log(chalk.red(`Client generation failed:`));
|
||||
console.log(err);
|
||||
|
||||
process.exit(1);
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import chalk from 'chalk';
|
||||
import { OptionValues } from 'commander';
|
||||
import { command as generateClient } from './client';
|
||||
import { command as generateServer } from './server';
|
||||
|
||||
export async function command(opts: OptionValues) {
|
||||
if (!opts.clientPackage && !opts.server) {
|
||||
console.log(
|
||||
chalk.red('Either --client-package or --server must be defined.'),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
if (opts.clientPackage) {
|
||||
await generateClient(opts.clientPackage);
|
||||
}
|
||||
if (opts.server) {
|
||||
await generateServer();
|
||||
}
|
||||
}
|
||||
+16
-41
@@ -17,30 +17,19 @@
|
||||
import fs from 'fs-extra';
|
||||
import YAML from 'js-yaml';
|
||||
import chalk from 'chalk';
|
||||
import { resolve } from 'path';
|
||||
import { paths as cliPaths } from '../../../lib/paths';
|
||||
import { runner } from '../runner';
|
||||
import { TS_SCHEMA_PATH, YAML_SCHEMA_PATH } from '../constants';
|
||||
import { paths as cliPaths } from '../../../../../lib/paths';
|
||||
import { TS_SCHEMA_PATH } from '../../../../../lib/openapi/constants';
|
||||
import { promisify } from 'util';
|
||||
import { exec as execCb } from 'child_process';
|
||||
import { getPathToCurrentOpenApiSpec } from '../../../../../lib/openapi/helpers';
|
||||
|
||||
const exec = promisify(execCb);
|
||||
|
||||
async function generate(
|
||||
directoryPath: string,
|
||||
config?: { skipMissingYamlFile: boolean },
|
||||
) {
|
||||
const { skipMissingYamlFile } = config ?? {};
|
||||
const openapiPath = resolve(directoryPath, YAML_SCHEMA_PATH);
|
||||
if (!(await fs.pathExists(openapiPath))) {
|
||||
if (skipMissingYamlFile) {
|
||||
return;
|
||||
}
|
||||
throw new Error(`Could not find ${YAML_SCHEMA_PATH} in root of directory.`);
|
||||
}
|
||||
async function generate() {
|
||||
const openapiPath = await getPathToCurrentOpenApiSpec();
|
||||
const yaml = YAML.load(await fs.readFile(openapiPath, 'utf8'));
|
||||
|
||||
const tsPath = resolve(directoryPath, TS_SCHEMA_PATH);
|
||||
const tsPath = cliPaths.resolveTarget(TS_SCHEMA_PATH);
|
||||
|
||||
// The first set of comment slashes allow for the eslint notice plugin to run
|
||||
// with onNonMatchingHeader: 'replace', as is the case in the open source
|
||||
@@ -63,33 +52,19 @@ export const createOpenApiRouter = async (
|
||||
|
||||
await exec(`yarn backstage-cli package lint --fix ${tsPath}`);
|
||||
if (await cliPaths.resolveTargetRoot('node_modules/.bin/prettier')) {
|
||||
await exec(`yarn prettier --write ${tsPath}`);
|
||||
await exec(`yarn prettier --write ${tsPath}`, {
|
||||
cwd: cliPaths.targetRoot,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function bulkCommand(paths: string[] = []): Promise<void> {
|
||||
const resultsList = await runner(paths, (dir: string) =>
|
||||
generate(dir, { skipMissingYamlFile: true }),
|
||||
);
|
||||
|
||||
let failed = false;
|
||||
for (const { relativeDir, resultText } of resultsList) {
|
||||
if (resultText) {
|
||||
console.log();
|
||||
console.log(
|
||||
chalk.red(
|
||||
`OpenAPI yaml to Typescript generation failed in ${relativeDir}:`,
|
||||
),
|
||||
);
|
||||
console.log(resultText.trimStart());
|
||||
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
process.exit(1);
|
||||
} else {
|
||||
export async function command(): Promise<void> {
|
||||
try {
|
||||
await generate();
|
||||
console.log(chalk.green('Generated all files.'));
|
||||
} catch (err) {
|
||||
console.log(chalk.red(`OpenAPI server stub generation failed.`));
|
||||
console.log(err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
+24
-33
@@ -14,27 +14,30 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import fs from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
import { YAML_SCHEMA_PATH } from './../constants';
|
||||
|
||||
import { paths as cliPaths } from '../../../lib/paths';
|
||||
import { runner } from '../runner';
|
||||
import { YAML_SCHEMA_PATH } from '../../../../lib/openapi/constants';
|
||||
import { paths as cliPaths } from '../../../../lib/paths';
|
||||
import chalk from 'chalk';
|
||||
import { exec } from '../../../lib/exec';
|
||||
import { exec } from '../../../../lib/exec';
|
||||
import {
|
||||
getPathToCurrentOpenApiSpec,
|
||||
getRelativePathToFile,
|
||||
} from '../../../../lib/openapi/helpers';
|
||||
|
||||
const ROUTER_TEST_PATHS = [
|
||||
'src/service/router.test.ts',
|
||||
'src/service/createRouter.test.ts',
|
||||
];
|
||||
|
||||
async function init(directoryPath: string) {
|
||||
const openapiPath = join(directoryPath, YAML_SCHEMA_PATH);
|
||||
if (!(await fs.pathExists(openapiPath))) {
|
||||
async function init() {
|
||||
try {
|
||||
await getPathToCurrentOpenApiSpec();
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`You do not have an OpenAPI YAML file at ${openapiPath}. Please create one and retry this command. If you already have existing test cases for your router, see 'backstage-repo-tools schema openapi test --update'`,
|
||||
`OpenAPI.yaml not found in ${YAML_SCHEMA_PATH}. Please create one and retry this command.`,
|
||||
);
|
||||
}
|
||||
const opticConfigFilePath = join(directoryPath, 'optic.yml');
|
||||
|
||||
const opticConfigFilePath = await getRelativePathToFile('optic.yml');
|
||||
if (await fs.pathExists(opticConfigFilePath)) {
|
||||
throw new Error(`This directory already has an optic.yml file. Exiting.`);
|
||||
}
|
||||
@@ -45,7 +48,9 @@ async function init(directoryPath: string) {
|
||||
capture:
|
||||
${YAML_SCHEMA_PATH}:
|
||||
# 🔧 Runnable example with simple get requests.
|
||||
# Run with "PORT=3000 optic capture ${YAML_SCHEMA_PATH} --update interactive" in '${directoryPath}'
|
||||
# Run with "PORT=3000 optic capture ${YAML_SCHEMA_PATH} --update interactive" in '${
|
||||
cliPaths.targetDir
|
||||
}'
|
||||
# You can change the server and the 'requests' section to experiment
|
||||
server:
|
||||
# This will not be used by 'backstage-repo-tools schema openapi test', but may be useful for interactive updates.
|
||||
@@ -64,27 +69,13 @@ capture:
|
||||
}
|
||||
}
|
||||
|
||||
export default async function initCommand(paths: string[] = []) {
|
||||
const resultsList = await runner(paths, dir => init(dir), {
|
||||
concurrencyLimit: 5,
|
||||
});
|
||||
|
||||
let failed = false;
|
||||
for (const { relativeDir, resultText } of resultsList) {
|
||||
if (resultText) {
|
||||
console.log();
|
||||
console.log(
|
||||
chalk.red(`Failed to initialize ${relativeDir} for OpenAPI commands.`),
|
||||
);
|
||||
console.log(resultText.trimStart());
|
||||
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
export async function singleCommand() {
|
||||
try {
|
||||
await init();
|
||||
console.log(chalk.green(`Successfully configured.`));
|
||||
} catch (err) {
|
||||
console.log(chalk.red(`OpenAPI tooling initialization failed.`));
|
||||
console.log(err.message);
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log(chalk.green(`All directories have already been configured.`));
|
||||
}
|
||||
}
|
||||
+10
-15
@@ -24,24 +24,19 @@ import { Yaml } from '@stoplight/spectral-parsers';
|
||||
import ruleset from '@apisyouwonthate/style-guide';
|
||||
import fs from 'fs-extra';
|
||||
import chalk from 'chalk';
|
||||
import { resolve } from 'path';
|
||||
import { runner } from './runner';
|
||||
import { YAML_SCHEMA_PATH } from './constants';
|
||||
import { runner } from '../../../../lib/runner';
|
||||
import { oas } from '@stoplight/spectral-rulesets';
|
||||
import { DiagnosticSeverity } from '@stoplight/types';
|
||||
import { pretty } from '@stoplight/spectral-formatters';
|
||||
import { getPathToOpenApiSpec } from '../../../../lib/openapi/helpers';
|
||||
|
||||
async function lint(
|
||||
directoryPath: string,
|
||||
config?: { skipMissingYamlFile: boolean; strict: boolean },
|
||||
) {
|
||||
const { skipMissingYamlFile, strict } = config ?? {};
|
||||
const openapiPath = resolve(directoryPath, YAML_SCHEMA_PATH);
|
||||
if (!(await fs.pathExists(openapiPath))) {
|
||||
if (skipMissingYamlFile) {
|
||||
return;
|
||||
}
|
||||
throw new Error(`Could not find a file at ${openapiPath}.`);
|
||||
async function lint(directoryPath: string, config?: { strict: boolean }) {
|
||||
const { strict } = config ?? {};
|
||||
let openapiPath = '';
|
||||
try {
|
||||
openapiPath = await getPathToOpenApiSpec(directoryPath);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
const openapiFileContent = await fs.readFile(openapiPath, 'utf8');
|
||||
|
||||
@@ -90,7 +85,7 @@ export async function bulkCommand(
|
||||
options: { strict?: boolean },
|
||||
): Promise<void> {
|
||||
const resultsList = await runner(paths, (dir: string) =>
|
||||
lint(dir, { skipMissingYamlFile: true, strict: !!options.strict }),
|
||||
lint(dir, { strict: !!options.strict }),
|
||||
);
|
||||
|
||||
let failed = false;
|
||||
+10
-6
@@ -17,18 +17,22 @@
|
||||
import fs from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
import chalk from 'chalk';
|
||||
import { runner } from '../runner';
|
||||
import { YAML_SCHEMA_PATH } from '../constants';
|
||||
import { paths as cliPaths } from '../../../lib/paths';
|
||||
import { exec } from '../../../lib/exec';
|
||||
import { runner } from '../../../../lib/runner';
|
||||
import { YAML_SCHEMA_PATH } from '../../../../lib/openapi/constants';
|
||||
import { paths as cliPaths } from '../../../../lib/paths';
|
||||
import { exec } from '../../../../lib/exec';
|
||||
import { getPathToOpenApiSpec } from '../../../../lib/openapi/helpers';
|
||||
|
||||
async function test(
|
||||
directoryPath: string,
|
||||
{ port }: { port: number },
|
||||
options?: { update?: boolean },
|
||||
) {
|
||||
const openapiPath = join(directoryPath, YAML_SCHEMA_PATH);
|
||||
if (!(await fs.pathExists(openapiPath))) {
|
||||
let openapiPath = join(directoryPath, YAML_SCHEMA_PATH);
|
||||
try {
|
||||
openapiPath = await getPathToOpenApiSpec(directoryPath);
|
||||
} catch {
|
||||
// OpenAPI schema doesn't exist.
|
||||
return;
|
||||
}
|
||||
const opticConfigFilePath = join(directoryPath, 'optic.yml');
|
||||
+14
-6
@@ -21,13 +21,21 @@ import { join } from 'path';
|
||||
import chalk from 'chalk';
|
||||
import { relative as relativePath, resolve as resolvePath } from 'path';
|
||||
import Parser from '@apidevtools/swagger-parser';
|
||||
import { runner } from '../runner';
|
||||
import { paths as cliPaths } from '../../../lib/paths';
|
||||
import { TS_MODULE, TS_SCHEMA_PATH, YAML_SCHEMA_PATH } from '../constants';
|
||||
import { runner } from '../../../../lib/runner';
|
||||
import { paths as cliPaths } from '../../../../lib/paths';
|
||||
import {
|
||||
TS_MODULE,
|
||||
TS_SCHEMA_PATH,
|
||||
YAML_SCHEMA_PATH,
|
||||
} from '../../../../lib/openapi/constants';
|
||||
import { getPathToOpenApiSpec } from '../../../../lib/openapi/helpers';
|
||||
|
||||
async function verify(directoryPath: string) {
|
||||
const openapiPath = join(directoryPath, YAML_SCHEMA_PATH);
|
||||
if (!(await fs.pathExists(openapiPath))) {
|
||||
let openapiPath = '';
|
||||
try {
|
||||
openapiPath = await getPathToOpenApiSpec(directoryPath);
|
||||
} catch {
|
||||
// Unable to find spec at path.
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -47,7 +55,7 @@ async function verify(directoryPath: string) {
|
||||
if (!isEqual(schema.spec, yaml)) {
|
||||
const path = relativePath(cliPaths.targetRoot, directoryPath);
|
||||
throw new Error(
|
||||
`\`${YAML_SCHEMA_PATH}\` and \`${TS_SCHEMA_PATH}\` do not match. Please run \`yarn backstage-repo-tools schema openapi generate ${path}\` to regenerate \`${TS_SCHEMA_PATH}\`.`,
|
||||
`\`${YAML_SCHEMA_PATH}\` and \`${TS_SCHEMA_PATH}\` do not match. Please run \`yarn backstage-repo-tools package schema openapi generate\` from '${path}' to regenerate \`${TS_SCHEMA_PATH}\`.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { pathExists } from 'fs-extra';
|
||||
import { paths } from '../paths';
|
||||
import { YAML_SCHEMA_PATH } from './constants';
|
||||
import { resolve } from 'path';
|
||||
|
||||
export const getPathToFile = async (directory: string, filename: string) => {
|
||||
return resolve(directory, filename);
|
||||
};
|
||||
|
||||
export const getRelativePathToFile = async (filename: string) => {
|
||||
return await getPathToFile(paths.targetDir, filename);
|
||||
};
|
||||
|
||||
export const assertExists = async (path: string) => {
|
||||
if (!(await pathExists(path))) {
|
||||
throw new Error(`Could not find ${path}.`);
|
||||
}
|
||||
return path;
|
||||
};
|
||||
|
||||
export const getPathToOpenApiSpec = async (directory: string) => {
|
||||
return await assertExists(await getPathToFile(directory, YAML_SCHEMA_PATH));
|
||||
};
|
||||
|
||||
export const getPathToCurrentOpenApiSpec = async () => {
|
||||
return await assertExists(await getRelativePathToFile(YAML_SCHEMA_PATH));
|
||||
};
|
||||
+2
-2
@@ -14,10 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { resolvePackagePaths } from '../../lib/paths';
|
||||
import { resolvePackagePaths } from './paths';
|
||||
import pLimit from 'p-limit';
|
||||
import { relative as relativePath } from 'path';
|
||||
import { paths as cliPaths } from '../../lib/paths';
|
||||
import { paths as cliPaths } from './paths';
|
||||
import portFinder from 'portfinder';
|
||||
|
||||
export async function runner(
|
||||
@@ -42,7 +42,8 @@
|
||||
"test": "backstage-cli package test",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack",
|
||||
"clean": "backstage-cli package clean"
|
||||
"clean": "backstage-cli package clean",
|
||||
"generate": "backstage-repo-tools package schema openapi generate --server --client-package packages/catalog-client"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "workspace:^",
|
||||
@@ -61,6 +62,7 @@
|
||||
"@backstage/plugin-permission-common": "workspace:^",
|
||||
"@backstage/plugin-permission-node": "workspace:^",
|
||||
"@backstage/plugin-search-backend-module-catalog": "workspace:^",
|
||||
"@backstage/repo-tools": "workspace:^",
|
||||
"@backstage/types": "workspace:^",
|
||||
"@opentelemetry/api": "^1.3.0",
|
||||
"@types/express": "^4.17.6",
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
"test": "backstage-cli package test",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack",
|
||||
"clean": "backstage-cli package clean"
|
||||
"clean": "backstage-cli package clean",
|
||||
"generate": "backstage-repo-tools package schema openapi generate --server"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "workspace:^",
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack",
|
||||
"clean": "backstage-cli package clean",
|
||||
"start": "backstage-cli package start"
|
||||
"start": "backstage-cli package start",
|
||||
"generate": "backstage-repo-tools package schema openapi generate --server"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "workspace:^",
|
||||
@@ -38,6 +39,7 @@
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@backstage/integration": "workspace:^",
|
||||
"@backstage/plugin-catalog-node": "workspace:^",
|
||||
"@backstage/repo-tools": "workspace:^",
|
||||
"@types/express": "^4.17.6",
|
||||
"express": "^4.17.1",
|
||||
"leasot": "^12.0.0",
|
||||
|
||||
@@ -5720,6 +5720,7 @@ __metadata:
|
||||
"@backstage/plugin-permission-common": "workspace:^"
|
||||
"@backstage/plugin-permission-node": "workspace:^"
|
||||
"@backstage/plugin-search-backend-module-catalog": "workspace:^"
|
||||
"@backstage/repo-tools": "workspace:^"
|
||||
"@backstage/types": "workspace:^"
|
||||
"@opentelemetry/api": ^1.3.0
|
||||
"@types/core-js": ^2.5.4
|
||||
@@ -9755,6 +9756,7 @@ __metadata:
|
||||
"@backstage/errors": "workspace:^"
|
||||
"@backstage/integration": "workspace:^"
|
||||
"@backstage/plugin-catalog-node": "workspace:^"
|
||||
"@backstage/repo-tools": "workspace:^"
|
||||
"@types/express": ^4.17.6
|
||||
"@types/supertest": ^2.0.8
|
||||
express: ^4.17.1
|
||||
@@ -9955,7 +9957,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/repo-tools@workspace:*, @backstage/repo-tools@workspace:packages/repo-tools":
|
||||
"@backstage/repo-tools@workspace:*, @backstage/repo-tools@workspace:^, @backstage/repo-tools@workspace:packages/repo-tools":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/repo-tools@workspace:packages/repo-tools"
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user