Add generate and verify commands.

Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
This commit is contained in:
Aramis Sennyey
2023-03-07 16:49:01 -05:00
committed by Fredrik Adelöw
parent 978b09f273
commit 06b016e9b8
8 changed files with 159 additions and 1 deletions
+3
View File
@@ -92,6 +92,9 @@ jobs:
- name: verify api reference
run: node scripts/verify-api-reference.js
- name: verify openapi yaml <> ts files
run: yarn backstage-cli schema:openapi:verify
- name: verify doc links
run: node scripts/verify-links.js
+1
View File
@@ -96,6 +96,7 @@
"jest-css-modules": "^2.1.0",
"jest-environment-jsdom": "^29.0.2",
"jest-runtime": "^29.0.2",
"js-yaml": "^4.1.0",
"json-schema": "^0.4.0",
"lodash": "^4.17.21",
"mini-css-extract-plugin": "^2.4.2",
+21
View File
@@ -80,6 +80,13 @@ export function registerRepoCommand(program: Command) {
lazy(() => import('./repo/list-deprecations').then(m => m.command)),
);
command
.command('schema:openapi:verify')
.description(
'Verify that all OpenAPI schemas are valid and have a matching `schemas/openapi.ts` file.',
)
.action(lazy(() => import('./openapi/verify').then(m => m.bulkCommand)));
command
.command('test')
.allowUnknownOption(true) // Allows the command to run, but we still need to parse raw args
@@ -179,6 +186,20 @@ export function registerScriptCommand(program: Command) {
.command('postpack')
.description('Restores the changes made by the prepack command')
.action(lazy(() => import('./pack').then(m => m.post)));
command
.command('schema:openapi:verify')
.description(
'Verifies that an OpenAPI spec is defined in both an `openapi.yaml` file and a `schema/openapi.ts` file.',
)
.action(lazy(() => import('./openapi/verify').then(m => m.command)));
command
.command('schema:openapi:generate')
.description(
'Generates a `schema/openapi.ts` file from an OpenAPI spec defined in `openapi.yaml`.',
)
.action(lazy(() => import('./openapi/generate').then(m => m.command)));
}
export function registerMigrateCommand(program: Command) {
@@ -0,0 +1,41 @@
/*
* Copyright 2023 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 fs from 'fs-extra';
import { paths } from '../../lib/paths';
import YAML from 'js-yaml';
export async function command() {
const openapiPath = paths.resolveTarget('openapi.yaml');
if (!(await fs.pathExists(openapiPath))) {
console.warn('Could not find openapi.yaml in root of directory.');
process.exit(1);
}
try {
const yaml = YAML.load(
await fs.readFile(paths.resolveTarget('openapi.yaml'), 'utf8'),
);
// For now, we're not adding a header or linting after pasting.
await fs.writeFile(
paths.resolveTarget('schema/openapi.ts'),
`export default ${JSON.stringify(yaml, null, 2)} as const`,
);
} catch (err) {
console.error(err);
process.exit(1);
}
}
@@ -0,0 +1,91 @@
/*
* Copyright 2023 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 fs from 'fs-extra';
import { paths } from '../../lib/paths';
import YAML from 'js-yaml';
import { isEqual } from 'lodash';
import { join } from 'path';
import chalk from 'chalk';
import { relative as relativePath } from 'path';
import { PackageGraph } from '../../lib/monorepo';
async function verify(directoryPath: string) {
const openapiPath = join(directoryPath, 'openapi.yaml');
if (!(await fs.pathExists(openapiPath))) {
return;
}
const yaml = YAML.load(await fs.readFile(openapiPath, 'utf8'));
const schemaPath = join(directoryPath, 'schema/openapi.ts');
if (!(await fs.pathExists(schemaPath))) {
throw new Error('No `schema/openapi.ts` file found.');
}
const schema = await import(join(directoryPath, 'schema/openapi'));
if (schema.default) {
if (!isEqual(schema.default, yaml)) {
throw new Error(
'`openapi.yaml` and `schema/openapi.ts` do not match. Please run `yarn build:openapi` to generate the `schema/openapi.ts` file from the `openapi.yaml` file.',
);
}
} else {
throw new Error('`schemas/openapi.ts` needs to have a default export.');
}
}
export async function command() {
try {
await verify(paths.resolveTarget('.'));
} catch (err) {
console.error(chalk.red(err.message));
process.exit(1);
}
}
export async function bulkCommand(): Promise<void> {
const packages = await PackageGraph.listTargetPackages();
const resultsList = await Promise.all(
packages.map(async pkg => {
let resultText = '';
try {
await verify(pkg.dir);
} catch (err) {
resultText = err.message;
}
return {
relativeDir: relativePath(paths.targetRoot, pkg.dir),
resultText,
};
}),
);
let failed = false;
for (const { relativeDir, resultText } of resultsList) {
if (resultText) {
console.log();
console.log(chalk.red(`Lint failed in ${relativeDir}:`));
console.log(resultText.trimStart());
failed = true;
}
}
if (failed) {
process.exit(1);
}
}
@@ -51,7 +51,7 @@ import {
validateRequestBody,
} from './util';
import { ApiRouter } from '@backstage/backend-openapi-utils';
import spec from './schema/openapi';
import spec from '../../schema/openapi';
/**
* Options used by {@link createRouter}.
+1
View File
@@ -4157,6 +4157,7 @@ __metadata:
jest-css-modules: ^2.1.0
jest-environment-jsdom: ^29.0.2
jest-runtime: ^29.0.2
js-yaml: ^4.1.0
json-schema: ^0.4.0
lodash: ^4.17.21
mini-css-extract-plugin: ^2.4.2