Add CLI command for full repo check.

Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
This commit is contained in:
Aramis Sennyey
2023-03-16 12:37:29 -04:00
committed by Fredrik Adelöw
parent abd4ff4cee
commit 6a985600a7
6 changed files with 110 additions and 38 deletions
+1
View File
@@ -106,6 +106,7 @@
"node-libs-browser": "^2.2.1",
"npm-packlist": "^5.0.0",
"ora": "^5.3.0",
"p-limit": "^3.0.2",
"postcss": "^8.1.0",
"process": "^0.11.10",
"react-dev-utils": "^12.0.0-next.60",
+7
View File
@@ -87,6 +87,13 @@ export function registerRepoCommand(program: Command) {
)
.action(lazy(() => import('./openapi/verify').then(m => m.bulkCommand)));
command
.command('schema:openapi:generate')
.description(
'Generates a Typescript file from an OpenAPI yaml spec. For use with the `@backstage/backend-openapi-utils` ApiRouter type.',
)
.action(lazy(() => import('./openapi/generate').then(m => m.bulkCommand)));
command
.command('test')
.allowUnknownOption(true) // Allows the command to run, but we still need to parse raw args
+71 -15
View File
@@ -17,25 +17,81 @@
import fs from 'fs-extra';
import { paths } from '../../lib/paths';
import YAML from 'js-yaml';
import chalk from 'chalk';
import { resolve } from 'path';
import { PackageGraph } from '../../lib/monorepo';
import pLimit from 'p-limit';
import { relative as relativePath } from 'path';
async function generate(
directoryPath: string,
config?: { skipMissingYamlFile: boolean },
) {
const { skipMissingYamlFile } = config ?? {};
const openapiPath = resolve(directoryPath, 'openapi.yaml');
if (!(await fs.pathExists(openapiPath))) {
if (skipMissingYamlFile) {
return;
}
throw new Error('Could not find openapi.yaml in root of directory.');
}
const yaml = YAML.load(await fs.readFile(openapiPath, 'utf8'));
// For now, we're not adding a header or linting after pasting.
await fs.writeFile(
resolve(directoryPath, 'schema/openapi.ts'),
`export default ${JSON.stringify(yaml, null, 2)} as const`,
);
}
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`,
);
await generate(paths.resolveTarget('.'));
console.log(chalk.green('OpenAPI files successfully generated.'));
} catch (err) {
console.error(err);
console.error(chalk.red(err.message));
process.exit(1);
}
}
export async function bulkCommand(): Promise<void> {
const packages = await PackageGraph.listTargetPackages();
const limit = pLimit(5);
const resultsList = await Promise.all(
packages.map(pkg =>
limit(async () => {
let resultText = '';
try {
await generate(pkg.dir, { skipMissingYamlFile: true });
} 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(
`OpenAPI yaml to Typescript generation failed in ${relativeDir}:`,
),
);
console.log(resultText.trimStart());
failed = true;
}
}
if (failed) {
process.exit(1);
}
}
+29 -22
View File
@@ -25,6 +25,7 @@ import { PackageGraph } from '../../lib/monorepo';
import { cloneDeep } from 'lodash';
import Parser from '@apidevtools/swagger-parser';
import { detectRoleFromPackage } from '../../lib/role';
import pLimit from 'p-limit';
const SUPPORTED_ROLES = [
'backend',
@@ -35,14 +36,15 @@ const SUPPORTED_ROLES = [
async function verify(
directoryPath: string,
{ checkRole }: { checkRole: boolean },
config: { checkRole: boolean } = { checkRole: false },
) {
const { checkRole } = config ?? {};
if (checkRole) {
const role = detectRoleFromPackage(
await fs.readJson(resolve(directoryPath, 'package.json')),
);
if (!SUPPORTED_ROLES.some(r => r === role)) {
if (!role || !SUPPORTED_ROLES.includes(role)) {
console.log(chalk.red(`Unsupported role ${role}`));
process.exit(1);
}
@@ -61,15 +63,17 @@ async function verify(
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 {
if (!schema.default) {
throw new Error('`schemas/openapi.ts` needs to have a default export.');
}
if (!isEqual(schema.default, yaml)) {
throw new Error(
`\`openapi.yaml\` and \`schema/openapi.ts\` do not match. Please run \`yarn --cwd ${relativePath(
paths.targetRoot,
directoryPath,
)} schema:openapi:generate\` to regenerate \`schema/openapi.ts\`.`,
);
}
}
export async function command() {
@@ -84,28 +88,31 @@ export async function command() {
export async function bulkCommand(): Promise<void> {
const packages = await PackageGraph.listTargetPackages();
const limit = pLimit(5);
const resultsList = await Promise.all(
packages.map(async pkg => {
let resultText = '';
try {
await verify(pkg.dir, { checkRole: false });
} catch (err) {
resultText = err.message;
}
packages.map(pkg =>
limit(async () => {
let resultText = '';
try {
await verify(pkg.dir);
} catch (err) {
resultText = err.message;
}
return {
relativeDir: relativePath(paths.targetRoot, pkg.dir),
resultText,
};
}),
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(chalk.red(`OpenAPI validation failed in ${relativeDir}:`));
console.log(resultText.trimStart());
failed = true;
+1 -1
View File
@@ -112,7 +112,7 @@ export default {
required: false,
schema: {
type: 'integer',
minimum: 1,
minimum: 0,
},
},
limit: {
+1
View File
@@ -4062,6 +4062,7 @@ __metadata:
nodemon: ^2.0.2
npm-packlist: ^5.0.0
ora: ^5.3.0
p-limit: ^3.0.2
postcss: ^8.1.0
process: ^0.11.10
react-dev-utils: ^12.0.0-next.60