From ff3a604dae3157b26f2b618411d8d860554d583e Mon Sep 17 00:00:00 2001 From: Aramis Date: Thu, 18 Jan 2024 18:00:01 -0500 Subject: [PATCH 1/6] feat(repo-tools): Update OpenAPI command naming. fixes: https://github.com/backstage/backstage/issues/21790 Signed-off-by: Aramis --- docs/openapi/01-getting-started.md | 6 +- packages/repo-tools/src/commands/index.ts | 99 +++++++++++++------ .../schema/openapi/generate/client.ts} | 35 ++++--- .../schema/openapi/generate/server.ts} | 57 +++-------- .../test => package/schema/openapi}/init.ts | 10 +- .../{ => repo/schema}/openapi/lint.ts | 25 ++--- .../index.ts => repo/schema/openapi/test.ts} | 16 +-- .../schema => repo/schema/openapi}/verify.ts | 20 ++-- .../{commands => lib}/openapi/constants.ts | 0 .../repo-tools/src/lib/openapi/helpers.ts | 32 ++++++ .../src/{commands/openapi => lib}/runner.ts | 4 +- plugins/catalog-backend/package.json | 5 +- plugins/search-backend/package.json | 3 +- plugins/todo-backend/package.json | 4 +- yarn.lock | 4 +- 15 files changed, 195 insertions(+), 125 deletions(-) rename packages/repo-tools/src/commands/{openapi/client/generate.ts => package/schema/openapi/generate/client.ts} (73%) rename packages/repo-tools/src/commands/{openapi/schema/generate.ts => package/schema/openapi/generate/server.ts} (60%) rename packages/repo-tools/src/commands/{openapi/test => package/schema/openapi}/init.ts (90%) rename packages/repo-tools/src/commands/{ => repo/schema}/openapi/lint.ts (83%) rename packages/repo-tools/src/commands/{openapi/test/index.ts => repo/schema/openapi/test.ts} (86%) rename packages/repo-tools/src/commands/{openapi/schema => repo/schema/openapi}/verify.ts (79%) rename packages/repo-tools/src/{commands => lib}/openapi/constants.ts (100%) create mode 100644 packages/repo-tools/src/lib/openapi/helpers.ts rename packages/repo-tools/src/{commands/openapi => lib}/runner.ts (94%) diff --git a/docs/openapi/01-getting-started.md b/docs/openapi/01-getting-started.md index eb3b1c1209..ce04e75107 100644 --- a/docs/openapi/01-getting-started.md +++ b/docs/openapi/01-getting-started.md @@ -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 `. 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 `. 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. 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 /src/schema/openapi.yaml --output-directory `. `` should match the same backend plugin we've been using so far. `` is a new directory and npm package that you should create. The general pattern is `plugins/-client`. +From your current backend plugin directory, run `yarn backstage-repo-tools package schema openapi generate client --output-package `. `` is a new directory and npm package that you should create. The general pattern is `plugins/-client` or if you want to co-locate this with your other shared types, use `plugins/-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. diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index d9c6d892f5..06ea2fa95c 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -18,12 +18,71 @@ 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.') + .action( + lazy(() => import('./package/schema/openapi/init').then(m => m.default)), + ); + + const generateCommand = openApiCommand + .command('generate [command]') + .description( + 'Commands for generating various things from an OpenAPI spec.', + ); + + generateCommand + .command('server') + .description( + 'Generates an express server stub using the OpenAPI schema for typings.', + ) + .action( + lazy(() => + import('./package/schema/openapi/generate/server').then(m => m.command), + ), + ); + + generateCommand + .command('client') + .description( + 'Generates a client that can interact with your backend plugin using types from your OpenAPI schema.', + ) + .requiredOption( + '--output-package ', + 'Top-level path to where the client should be generated, ie packages/catalog-client.', + ) + .action( + lazy(() => + import('./package/schema/openapi/generate/client').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 +92,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 +104,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 ') - .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 ') - .requiredOption('--output-directory ') .action( - lazy(() => - import('./openapi/client/generate').then(m => m.singleCommand), - ), + lazy(() => import('./repo/schema/openapi/test').then(m => m.bulkCommand)), ); } @@ -139,8 +180,8 @@ export function registerCommands(program: Command) { ), ), ); - - registerSchemaCommand(program); + registerPackageCommand(program); + registerRepoCommand(program); } // Wraps an action function so that it always exits and handles errors diff --git a/packages/repo-tools/src/commands/openapi/client/generate.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts similarity index 73% rename from packages/repo-tools/src/commands/openapi/client/generate.ts rename to packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts index f827f1005f..101c2c918b 100644 --- a/packages/repo-tools/src/commands/openapi/client/generate.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts @@ -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,19 @@ async function generate(spec: string, outputDirectory: string) { }); } -export async function singleCommand({ - inputSpec, - outputDirectory, +export async function command({ + outputPackage, }: { - inputSpec: string; - outputDirectory: string; + outputPackage: string; }): Promise { 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); diff --git a/packages/repo-tools/src/commands/openapi/schema/generate.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts similarity index 60% rename from packages/repo-tools/src/commands/openapi/schema/generate.ts rename to packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts index cd9f572f2d..e6c56c3122 100644 --- a/packages/repo-tools/src/commands/openapi/schema/generate.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts @@ -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 { - 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 { + 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); } } diff --git a/packages/repo-tools/src/commands/openapi/test/init.ts b/packages/repo-tools/src/commands/package/schema/openapi/init.ts similarity index 90% rename from packages/repo-tools/src/commands/openapi/test/init.ts rename to packages/repo-tools/src/commands/package/schema/openapi/init.ts index a38416aabb..56136f8882 100644 --- a/packages/repo-tools/src/commands/openapi/test/init.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/init.ts @@ -15,12 +15,12 @@ */ import fs from 'fs-extra'; import { join } from 'path'; -import { YAML_SCHEMA_PATH } from './../constants'; +import { YAML_SCHEMA_PATH } from '../../../../lib/openapi/constants'; -import { paths as cliPaths } from '../../../lib/paths'; -import { runner } from '../runner'; +import { paths as cliPaths } from '../../../../lib/paths'; +import { runner } from '../../../../lib/runner'; import chalk from 'chalk'; -import { exec } from '../../../lib/exec'; +import { exec } from '../../../../lib/exec'; const ROUTER_TEST_PATHS = [ 'src/service/router.test.ts', @@ -31,7 +31,7 @@ async function init(directoryPath: string) { const openapiPath = join(directoryPath, YAML_SCHEMA_PATH); if (!(await fs.pathExists(openapiPath))) { 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'`, + `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 package schema openapi test --update'`, ); } const opticConfigFilePath = join(directoryPath, 'optic.yml'); diff --git a/packages/repo-tools/src/commands/openapi/lint.ts b/packages/repo-tools/src/commands/repo/schema/openapi/lint.ts similarity index 83% rename from packages/repo-tools/src/commands/openapi/lint.ts rename to packages/repo-tools/src/commands/repo/schema/openapi/lint.ts index 8aac0189b1..eacb3ac50d 100644 --- a/packages/repo-tools/src/commands/openapi/lint.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/lint.ts @@ -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 { const resultsList = await runner(paths, (dir: string) => - lint(dir, { skipMissingYamlFile: true, strict: !!options.strict }), + lint(dir, { strict: !!options.strict }), ); let failed = false; diff --git a/packages/repo-tools/src/commands/openapi/test/index.ts b/packages/repo-tools/src/commands/repo/schema/openapi/test.ts similarity index 86% rename from packages/repo-tools/src/commands/openapi/test/index.ts rename to packages/repo-tools/src/commands/repo/schema/openapi/test.ts index 47b553aaef..833de181e3 100644 --- a/packages/repo-tools/src/commands/openapi/test/index.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/test.ts @@ -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'); diff --git a/packages/repo-tools/src/commands/openapi/schema/verify.ts b/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts similarity index 79% rename from packages/repo-tools/src/commands/openapi/schema/verify.ts rename to packages/repo-tools/src/commands/repo/schema/openapi/verify.ts index 7120dd67ad..442494610c 100644 --- a/packages/repo-tools/src/commands/openapi/schema/verify.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts @@ -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}\`.`, ); } } diff --git a/packages/repo-tools/src/commands/openapi/constants.ts b/packages/repo-tools/src/lib/openapi/constants.ts similarity index 100% rename from packages/repo-tools/src/commands/openapi/constants.ts rename to packages/repo-tools/src/lib/openapi/constants.ts diff --git a/packages/repo-tools/src/lib/openapi/helpers.ts b/packages/repo-tools/src/lib/openapi/helpers.ts new file mode 100644 index 0000000000..5a5932c802 --- /dev/null +++ b/packages/repo-tools/src/lib/openapi/helpers.ts @@ -0,0 +1,32 @@ +/* + * 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 getPathToOpenApiSpec = async (directory: string) => { + const openapiPath = resolve(directory, YAML_SCHEMA_PATH); + if (!(await pathExists(openapiPath))) { + throw new Error(`Could not find ${YAML_SCHEMA_PATH}.`); + } + return openapiPath; +}; + +export const getPathToCurrentOpenApiSpec = async () => { + return await getPathToOpenApiSpec(paths.targetDir); +}; diff --git a/packages/repo-tools/src/commands/openapi/runner.ts b/packages/repo-tools/src/lib/runner.ts similarity index 94% rename from packages/repo-tools/src/commands/openapi/runner.ts rename to packages/repo-tools/src/lib/runner.ts index cc354daaaa..b9700cd09b 100644 --- a/packages/repo-tools/src/commands/openapi/runner.ts +++ b/packages/repo-tools/src/lib/runner.ts @@ -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( diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 42f40cccd2..eee268736a 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -42,7 +42,9 @@ "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-server": "backstage-repo-tools package schema openapi generate server", + "generate-client": "backstage-repo-tools package schema openapi generate client --output-package packages/catalog-client" }, "dependencies": { "@backstage/backend-common": "workspace:^", @@ -61,6 +63,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", diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index bea288f6b2..b328083246 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -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-server": "backstage-repo-tools package schema openapi generate server" }, "dependencies": { "@backstage/backend-common": "workspace:^", diff --git a/plugins/todo-backend/package.json b/plugins/todo-backend/package.json index ddf3baac3d..75a070e284 100644 --- a/plugins/todo-backend/package.json +++ b/plugins/todo-backend/package.json @@ -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-server": "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", diff --git a/yarn.lock b/yarn.lock index 33ac9d7aab..ef01464078 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5560,6 +5560,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 @@ -9350,6 +9351,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 @@ -9550,7 +9552,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: From 4c62935c8e6e17dd48d9d53a97e5c7da5473dd7a Mon Sep 17 00:00:00 2001 From: Aramis Date: Thu, 18 Jan 2024 18:09:49 -0500 Subject: [PATCH 2/6] add changeset Signed-off-by: Aramis --- .changeset/lovely-bugs-prove.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .changeset/lovely-bugs-prove.md diff --git a/.changeset/lovely-bugs-prove.md b/.changeset/lovely-bugs-prove.md new file mode 100644 index 0000000000..5b35402dfb --- /dev/null +++ b/.changeset/lovely-bugs-prove.md @@ -0,0 +1,19 @@ +--- +'@backstage/repo-tools': minor +--- + +Renames the `schema openapi *` commands 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` +- `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` + +This also reworks the `package schema openapi generate client` to accept only an output directory as the input directory can now be inferred. From e16864039b7653813baeff5f279dbd28c1390cc6 Mon Sep 17 00:00:00 2001 From: Aramis Date: Thu, 18 Jan 2024 18:18:47 -0500 Subject: [PATCH 3/6] fix more documentation and workflows Signed-off-by: Aramis --- .changeset/lovely-bugs-prove.md | 2 +- .github/workflows/ci.yml | 6 +- docs/openapi/generate-client.md | 4 +- docs/openapi/test-case-validation.md | 6 +- packages/repo-tools/cli-report.md | 138 +++++++++++++++++++++------ 5 files changed, 116 insertions(+), 40 deletions(-) diff --git a/.changeset/lovely-bugs-prove.md b/.changeset/lovely-bugs-prove.md index 5b35402dfb..b1cb762e73 100644 --- a/.changeset/lovely-bugs-prove.md +++ b/.changeset/lovely-bugs-prove.md @@ -2,7 +2,7 @@ '@backstage/repo-tools': minor --- -Renames the `schema openapi *` commands 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. +**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, diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd6e2edbc3..ddc1712698 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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: | diff --git a/docs/openapi/generate-client.md b/docs/openapi/generate-client.md index f2c7c64574..640036960e 100644 --- a/docs/openapi/generate-client.md +++ b/docs/openapi/generate-client.md @@ -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 --output-directory `. This will create a new folder in `/src/generated` to house the generated content. +1. Run `yarn backstage-repo-tools schema openapi generate client --output-package `. This will create a new folder in `/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. diff --git a/docs/openapi/test-case-validation.md b/docs/openapi/test-case-validation.md index 0d93b6d5b5..28d0eb5470 100644 --- a/docs/openapi/test-case-validation.md +++ b/docs/openapi/test-case-validation.md @@ -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 diff --git a/packages/repo-tools/cli-report.md b/packages/repo-tools/cli-report.md index be1e7ffceb..3a03e25352 100644 --- a/packages/repo-tools/cli-report.md +++ b/packages/repo-tools/cli-report.md @@ -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,127 @@ 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 [command] + help [command] +``` + +### `backstage-repo-tools package schema openapi generate` + +``` +Usage: backstage-repo-tools package schema openapi generate [options] [command] [command] + +Options: + -h, --help + +Commands: + server + client [options] + help [command] +``` + +### `backstage-repo-tools package schema openapi generate client` + +``` +Usage: backstage-repo-tools package schema openapi generate client [options] + +Options: + --output-package + -h, --help +``` + +### `backstage-repo-tools package schema openapi generate server` + +``` +Usage: backstage-repo-tools package schema openapi generate server [options] + +Options: + -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 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] - -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 From 9c1c15fe1b0d11b40eeef536d20c8d7c8ad2072f Mon Sep 17 00:00:00 2001 From: Aramis Date: Tue, 23 Jan 2024 10:09:09 -0500 Subject: [PATCH 4/6] work to update init to be package focused Signed-off-by: Aramis --- .../commands/package/schema/openapi/init.ts | 25 +++++++++++-------- .../repo-tools/src/lib/openapi/helpers.ts | 22 ++++++++++------ 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/packages/repo-tools/src/commands/package/schema/openapi/init.ts b/packages/repo-tools/src/commands/package/schema/openapi/init.ts index 56136f8882..ab91aabecc 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/init.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/init.ts @@ -21,20 +21,23 @@ import { paths as cliPaths } from '../../../../lib/paths'; import { runner } from '../../../../lib/runner'; import chalk from 'chalk'; import { exec } from '../../../../lib/exec'; +import { getPathToCurrentOpenApiSpec } 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 package 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 getPathTo; if (await fs.pathExists(opticConfigFilePath)) { throw new Error(`This directory already has an optic.yml file. Exiting.`); } @@ -65,6 +68,12 @@ capture: } export default async function initCommand(paths: string[] = []) { + try { + await init(); + } catch (err) { + console.log(chalk.red(`OpenAPI tooling initialization failed.`)); + console.log(err.message); + } const resultsList = await runner(paths, dir => init(dir), { concurrencyLimit: 5, }); @@ -72,12 +81,6 @@ export default async function initCommand(paths: string[] = []) { 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; } } diff --git a/packages/repo-tools/src/lib/openapi/helpers.ts b/packages/repo-tools/src/lib/openapi/helpers.ts index 5a5932c802..dda7f110a4 100644 --- a/packages/repo-tools/src/lib/openapi/helpers.ts +++ b/packages/repo-tools/src/lib/openapi/helpers.ts @@ -17,16 +17,24 @@ import { pathExists } from 'fs-extra'; import { paths } from '../paths'; import { YAML_SCHEMA_PATH } from './constants'; -import { resolve } from 'path'; +import { join, resolve } from 'path'; + +export const getPathToFile = async (directory: string, filename: string) => { + const path = resolve(directory, filename); + if (!(await pathExists(path))) { + throw new Error(`Could not find ${join(directory, filename)}.`); + } + return path; +}; + +export const getRelativePathToFile = async (filename: string) => { + return await getPathToFile(paths.targetDir, filename); +}; export const getPathToOpenApiSpec = async (directory: string) => { - const openapiPath = resolve(directory, YAML_SCHEMA_PATH); - if (!(await pathExists(openapiPath))) { - throw new Error(`Could not find ${YAML_SCHEMA_PATH}.`); - } - return openapiPath; + return await getPathToFile(directory, YAML_SCHEMA_PATH); }; export const getPathToCurrentOpenApiSpec = async () => { - return await getPathToOpenApiSpec(paths.targetDir); + return await getRelativePathToFile(YAML_SCHEMA_PATH); }; From 2a42f97175f2c04e7a4a60ea24150a8156a24ac3 Mon Sep 17 00:00:00 2001 From: Aramis Date: Tue, 23 Jan 2024 13:25:42 -0500 Subject: [PATCH 5/6] make init for a single package Signed-off-by: Aramis --- packages/repo-tools/src/commands/index.ts | 8 +++-- .../commands/package/schema/openapi/init.ts | 32 ++++++------------- .../repo-tools/src/lib/openapi/helpers.ts | 19 ++++++----- 3 files changed, 27 insertions(+), 32 deletions(-) diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index 06ea2fa95c..290999687c 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -35,9 +35,13 @@ function registerPackageCommand(program: Command) { openApiCommand .command('init') - .description('Initialize any required files to use the OpenAPI tooling.') + .description( + 'Initialize any required files to use the OpenAPI tooling for this package.', + ) .action( - lazy(() => import('./package/schema/openapi/init').then(m => m.default)), + lazy(() => + import('./package/schema/openapi/init').then(m => m.singleCommand), + ), ); const generateCommand = openApiCommand diff --git a/packages/repo-tools/src/commands/package/schema/openapi/init.ts b/packages/repo-tools/src/commands/package/schema/openapi/init.ts index ab91aabecc..00e82f65df 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/init.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/init.ts @@ -14,14 +14,14 @@ * limitations under the License. */ import fs from 'fs-extra'; -import { join } from 'path'; import { YAML_SCHEMA_PATH } from '../../../../lib/openapi/constants'; - import { paths as cliPaths } from '../../../../lib/paths'; -import { runner } from '../../../../lib/runner'; import chalk from 'chalk'; import { exec } from '../../../../lib/exec'; -import { getPathToCurrentOpenApiSpec } from '../../../../lib/openapi/helpers'; +import { + getPathToCurrentOpenApiSpec, + getRelativePathToFile, +} from '../../../../lib/openapi/helpers'; const ROUTER_TEST_PATHS = [ 'src/service/router.test.ts', @@ -37,7 +37,7 @@ async function init() { ); } - const opticConfigFilePath = await getPathTo; + const opticConfigFilePath = await getRelativePathToFile('optic.yml'); if (await fs.pathExists(opticConfigFilePath)) { throw new Error(`This directory already has an optic.yml file. Exiting.`); } @@ -48,7 +48,9 @@ async function init() { 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. @@ -67,27 +69,13 @@ capture: } } -export default async function initCommand(paths: string[] = []) { +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); - } - const resultsList = await runner(paths, dir => init(dir), { - concurrencyLimit: 5, - }); - - let failed = false; - for (const { relativeDir, resultText } of resultsList) { - if (resultText) { - failed = true; - } - } - - if (failed) { process.exit(1); - } else { - console.log(chalk.green(`All directories have already been configured.`)); } } diff --git a/packages/repo-tools/src/lib/openapi/helpers.ts b/packages/repo-tools/src/lib/openapi/helpers.ts index dda7f110a4..f7e684a407 100644 --- a/packages/repo-tools/src/lib/openapi/helpers.ts +++ b/packages/repo-tools/src/lib/openapi/helpers.ts @@ -17,24 +17,27 @@ import { pathExists } from 'fs-extra'; import { paths } from '../paths'; import { YAML_SCHEMA_PATH } from './constants'; -import { join, resolve } from 'path'; +import { resolve } from 'path'; export const getPathToFile = async (directory: string, filename: string) => { - const path = resolve(directory, filename); - if (!(await pathExists(path))) { - throw new Error(`Could not find ${join(directory, filename)}.`); - } - return path; + 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 getPathToFile(directory, YAML_SCHEMA_PATH); + return await assertExists(await getPathToFile(directory, YAML_SCHEMA_PATH)); }; export const getPathToCurrentOpenApiSpec = async () => { - return await getRelativePathToFile(YAML_SCHEMA_PATH); + return await assertExists(await getRelativePathToFile(YAML_SCHEMA_PATH)); }; From b107fdf6090352c2c1715891b0f86e46fec67d0c Mon Sep 17 00:00:00 2001 From: Aramis Date: Wed, 24 Jan 2024 10:40:04 -0500 Subject: [PATCH 6/6] rework `generate` to take flags instead of subcommands Signed-off-by: Aramis --- .changeset/lovely-bugs-prove.md | 6 ++-- docs/openapi/01-getting-started.md | 4 +-- packages/repo-tools/cli-report.md | 29 ++-------------- packages/repo-tools/src/commands/index.ts | 34 +++++-------------- .../package/schema/openapi/generate/client.ts | 6 +--- .../package/schema/openapi/generate/index.ts | 34 +++++++++++++++++++ plugins/catalog-backend/package.json | 3 +- plugins/search-backend/package.json | 2 +- plugins/todo-backend/package.json | 2 +- 9 files changed, 55 insertions(+), 65 deletions(-) create mode 100644 packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts diff --git a/.changeset/lovely-bugs-prove.md b/.changeset/lovely-bugs-prove.md index b1cb762e73..de55f329bb 100644 --- a/.changeset/lovely-bugs-prove.md +++ b/.changeset/lovely-bugs-prove.md @@ -6,8 +6,8 @@ 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` +- `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, @@ -16,4 +16,4 @@ And these commands live under the new `repo` namespace, - `schema openapi test` is now `repo schema openapi test` - `schema openapi verify` is now `repo schema openapi verify` -This also reworks the `package schema openapi generate client` to accept only an output directory as the input directory can now be inferred. +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. diff --git a/docs/openapi/01-getting-started.md b/docs/openapi/01-getting-started.md index ce04e75107..5b9908756f 100644 --- a/docs/openapi/01-getting-started.md +++ b/docs/openapi/01-getting-started.md @@ -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 package schema openapi generate server `. 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. +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 ` 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 -From your current backend plugin directory, run `yarn backstage-repo-tools package schema openapi generate client --output-package `. `` is a new directory and npm package that you should create. The general pattern is `plugins/-client` or if you want to co-locate this with your other shared types, use `plugins/-common`. You should add this command to your `package.json` for future use. +From your current backend plugin directory, run `yarn backstage-repo-tools package schema openapi generate --client-package `. `` is a new directory and npm package that you should create. The general pattern is `plugins/-client` or if you want to co-locate this with your other shared types, use `plugins/-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, diff --git a/packages/repo-tools/cli-report.md b/packages/repo-tools/cli-report.md index 3a03e25352..7988c479b0 100644 --- a/packages/repo-tools/cli-report.md +++ b/packages/repo-tools/cli-report.md @@ -85,40 +85,17 @@ Options: Commands: init - generate [command] + generate [options] help [command] ``` ### `backstage-repo-tools package schema openapi generate` ``` -Usage: backstage-repo-tools package schema openapi generate [options] [command] [command] - -Options: - -h, --help - -Commands: - server - client [options] - help [command] -``` - -### `backstage-repo-tools package schema openapi generate client` - -``` -Usage: backstage-repo-tools package schema openapi generate client [options] - -Options: - --output-package - -h, --help -``` - -### `backstage-repo-tools package schema openapi generate server` - -``` -Usage: backstage-repo-tools package schema openapi generate server [options] +Usage: backstage-repo-tools package schema openapi generate [options] Options: + --client-package [package] -h, --help ``` diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index 290999687c..1fa37402ce 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -44,35 +44,19 @@ function registerPackageCommand(program: Command) { ), ); - const generateCommand = openApiCommand - .command('generate [command]') - .description( - 'Commands for generating various things from an OpenAPI spec.', - ); - - generateCommand - .command('server') - .description( - 'Generates an express server stub using the OpenAPI schema for typings.', - ) - .action( - lazy(() => - import('./package/schema/openapi/generate/server').then(m => m.command), - ), - ); - - generateCommand - .command('client') - .description( - 'Generates a client that can interact with your backend plugin using types from your OpenAPI schema.', - ) - .requiredOption( - '--output-package ', + 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/client').then(m => m.command), + import('./package/schema/openapi/generate').then(m => m.command), ), ); } diff --git a/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts index 101c2c918b..5dfed05f93 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts @@ -87,11 +87,7 @@ async function generate(outputDirectory: string) { }); } -export async function command({ - outputPackage, -}: { - outputPackage: string; -}): Promise { +export async function command(outputPackage: string): Promise { try { await generate(outputPackage); console.log( diff --git a/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts new file mode 100644 index 0000000000..1e48fe3825 --- /dev/null +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts @@ -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(); + } +} diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index eee268736a..5e1d4f4e60 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -43,8 +43,7 @@ "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", "clean": "backstage-cli package clean", - "generate-server": "backstage-repo-tools package schema openapi generate server", - "generate-client": "backstage-repo-tools package schema openapi generate client --output-package packages/catalog-client" + "generate": "backstage-repo-tools package schema openapi generate --server --client-package packages/catalog-client" }, "dependencies": { "@backstage/backend-common": "workspace:^", diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index b328083246..c68f2b816e 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -40,7 +40,7 @@ "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", "clean": "backstage-cli package clean", - "generate-server": "backstage-repo-tools package schema openapi generate server" + "generate": "backstage-repo-tools package schema openapi generate --server" }, "dependencies": { "@backstage/backend-common": "workspace:^", diff --git a/plugins/todo-backend/package.json b/plugins/todo-backend/package.json index 75a070e284..2d9eb469d1 100644 --- a/plugins/todo-backend/package.json +++ b/plugins/todo-backend/package.json @@ -27,7 +27,7 @@ "postpack": "backstage-cli package postpack", "clean": "backstage-cli package clean", "start": "backstage-cli package start", - "generate-server": "backstage-repo-tools package schema openapi generate server" + "generate": "backstage-repo-tools package schema openapi generate --server" }, "dependencies": { "@backstage/backend-common": "workspace:^",