diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index f337329537..77d0047e60 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -73,6 +73,7 @@ "minimatch": "^9.0.0", "p-limit": "^3.0.2", "portfinder": "^1.0.32", + "ts-morph": "^23.0.0", "yaml-diff-patch": "^2.0.0" }, "devDependencies": { diff --git a/packages/repo-tools/src/commands/backend-lint/backend-lint.ts b/packages/repo-tools/src/commands/backend-lint/backend-lint.ts new file mode 100644 index 0000000000..2f7aa3cc04 --- /dev/null +++ b/packages/repo-tools/src/commands/backend-lint/backend-lint.ts @@ -0,0 +1,75 @@ +/* + * 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 fs from 'fs-extra'; +import { Project } from 'ts-morph'; +import { resolvePackagePaths } from '../../lib/paths'; + +const project = new Project({ + tsConfigFilePath: 'tsconfig.json', +}); + +export async function lint(paths: string[]) { + const pkgs = (await resolvePackagePaths()).filter(pkg => { + const role = JSON.parse(fs.readFileSync(`${pkg}/package.json`, 'utf-8')) + .backstage?.role; + return role === 'backend-plugin' || role === 'backend-plugin-module'; + }); + + if (paths.length > 0) { + paths.forEach(verifyIndex); + return; + } + pkgs.forEach(verifyIndex); +} + +function verifyIndex(pkg: string) { + console.log(`Verifying ${pkg}`); + const sourceFile = project.getSourceFile(`${pkg}/src/index.ts`); + if (!sourceFile) { + console.log(`Could not find ${pkg}/src/index.ts`); + process.exit(1); + } + const symbols = sourceFile?.getExportSymbols(); + + const exportCount = symbols?.length || 0; + if (exportCount > 1) { + console.log( + ` ⚠️ Warning: ${exportCount} exports found, ${symbols + .map(symbol => symbol.getName()) + .join(', ')}`, + ); + } + + const createRouterExport = symbols?.find( + symbol => symbol.getName() === 'createRouter', + ); + + if (!sourceFile.getDefaultExportSymbol()) { + console.log(' ❌ Missing default export'); + } + let createRouterDeprecated = undefined; + if (createRouterExport) { + createRouterDeprecated = createRouterExport + .getJsDocTags() + .find(tag => tag.getName() === 'deprecated'); + } + + if (createRouterExport) { + console.log(' ❌ createRouter is exported'); + if (!createRouterDeprecated) + console.log(' ❌ createRouter is NOT deprecated'); + } +} diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index 24ec2af411..7527edc870 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -236,6 +236,15 @@ export function registerCommands(program: Command) { ), ); + program + .command('backend-lint [paths...]') + .description( + 'Lint backend plugin packages for errors related to the new backend system migration', + ) + .action( + lazy(() => import('./backend-lint/backend-lint').then(m => m.lint)), + ); + registerPackageCommand(program); registerRepoCommand(program); } diff --git a/yarn.lock b/yarn.lock index 6c6d884fec..b0e460ee11 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7889,6 +7889,7 @@ __metadata: minimatch: ^9.0.0 p-limit: ^3.0.2 portfinder: ^1.0.32 + ts-morph: ^23.0.0 yaml-diff-patch: ^2.0.0 peerDependencies: "@microsoft/api-extractor-model": "*" @@ -17092,6 +17093,18 @@ __metadata: languageName: node linkType: hard +"@ts-morph/common@npm:~0.24.0": + version: 0.24.0 + resolution: "@ts-morph/common@npm:0.24.0" + dependencies: + fast-glob: ^3.3.2 + minimatch: ^9.0.4 + mkdirp: ^3.0.1 + path-browserify: ^1.0.1 + checksum: 793bc8a47c93ab55c6c036f94480d3b0e948661aef4bb7dbc29279b1dda2fc4fce809a88e221537867a313541842e12d1ecbd32b4769688abe1303807ec09db6 + languageName: node + linkType: hard + "@tsconfig/node10@npm:^1.0.7": version: 1.0.9 resolution: "@tsconfig/node10@npm:1.0.9" @@ -34350,7 +34363,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^9.0.0, minimatch@npm:^9.0.1, minimatch@npm:^9.0.3": +"minimatch@npm:^9.0.0, minimatch@npm:^9.0.1, minimatch@npm:^9.0.3, minimatch@npm:^9.0.4": version: 9.0.5 resolution: "minimatch@npm:9.0.5" dependencies: @@ -42884,6 +42897,16 @@ __metadata: languageName: node linkType: hard +"ts-morph@npm:^23.0.0": + version: 23.0.0 + resolution: "ts-morph@npm:23.0.0" + dependencies: + "@ts-morph/common": ~0.24.0 + code-block-writer: ^13.0.1 + checksum: 3282eb0f8bd4577770874736c3259b97501da9a86137160b5d68f106b7848ea7b1fbccf9e198a3d930ec40c993e9951d4bfae31e2562dac8f3de0d7bb0e23615 + languageName: node + linkType: hard + "ts-node@npm:^10.9.1": version: 10.9.2 resolution: "ts-node@npm:10.9.2"