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:
Patrik Oldsberg
2024-02-06 13:41:16 +01:00
committed by GitHub
21 changed files with 355 additions and 194 deletions
+19
View File
@@ -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.
+3 -3
View File
@@ -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: |
+3 -3
View File
@@ -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.
+2 -2
View File
@@ -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.
+3 -3
View File
@@ -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
+84 -31
View File
@@ -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
+58 -29
View File
@@ -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
@@ -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();
}
}
@@ -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);
}
}
@@ -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.`));
}
}
@@ -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;
@@ -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');
@@ -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));
};
@@ -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(
+3 -1
View File
@@ -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",
+2 -1
View File
@@ -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:^",
+3 -1
View File
@@ -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",
+3 -1
View File
@@ -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: