From 6ad0c456486a11769475c10648f157243093a8e2 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 11 Mar 2022 18:03:32 +0100 Subject: [PATCH 1/2] cli: add experimental add-deps command Signed-off-by: Patrik Oldsberg --- .changeset/happy-foxes-arrive.md | 5 + packages/cli/src/commands/add-deps.ts | 142 ++++++++++++++++++++++++++ packages/cli/src/commands/index.ts | 7 ++ 3 files changed, 154 insertions(+) create mode 100644 .changeset/happy-foxes-arrive.md create mode 100644 packages/cli/src/commands/add-deps.ts diff --git a/.changeset/happy-foxes-arrive.md b/.changeset/happy-foxes-arrive.md new file mode 100644 index 0000000000..03ddfede0d --- /dev/null +++ b/.changeset/happy-foxes-arrive.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Added an experimental `add-deps` command which adds missing monorepo dependencies to the target package.` diff --git a/packages/cli/src/commands/add-deps.ts b/packages/cli/src/commands/add-deps.ts new file mode 100644 index 0000000000..e4cdc4dc84 --- /dev/null +++ b/packages/cli/src/commands/add-deps.ts @@ -0,0 +1,142 @@ +/* + * Copyright 2020 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 { paths } from '../lib/paths'; +import { ESLint } from 'eslint'; +import { join as joinPath, basename } from 'path'; +import fs from 'fs-extra'; +import { isChildPath } from '@backstage/cli-common'; +import { PackageGraph } from '../lib/monorepo'; + +function isTestPath(filePath: string) { + if (!isChildPath(joinPath(paths.targetDir, 'src'), filePath)) { + return true; + } + const name = basename(filePath); + return ( + name.startsWith('setupTests.') || + name.includes('.test.') || + name.includes('.stories.') + ); +} + +export async function command() { + const pkgJsonPath = paths.resolveTarget('package.json'); + const pkg = await fs.readJson(pkgJsonPath); + if (pkg.workspaces) { + throw new Error( + 'Adding dependencies to the workspace root is not supported', + ); + } + + const packages = await PackageGraph.listTargetPackages(); + const localPackageVersions = new Map( + packages.map(p => [p.packageJson.name, p.packageJson.version]), + ); + + const eslint = new ESLint({ + cwd: paths.targetDir, + overrideConfig: { + plugins: ['monorepo'], + rules: { + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: [ + `!${joinPath(paths.targetDir, 'src/**')}`, + joinPath(paths.targetDir, 'src/**/*.test.*'), + joinPath(paths.targetDir, 'src/**/*.stories.*'), + joinPath(paths.targetDir, 'src/setupTests.*'), + ], + optionalDependencies: true, + peerDependencies: true, + bundledDependencies: true, + }, + ], + }, + }, + extensions: ['jsx', 'ts', 'tsx', 'mjs', 'cjs'], + }); + + const results = await eslint.lintFiles(['.']); + + const addedDeps = new Set(); + const addedDevDeps = new Set(); + const removedDevDeps = new Set(); + + for (const result of results) { + for (const message of result.messages) { + // Just in case + if (message.ruleId !== 'import/no-extraneous-dependencies') { + continue; + } + + const match = message.message.match(/^'([^']*)' should be listed/); + if (!match) { + continue; + } + const packageName = match[1]; + if (!localPackageVersions.has(packageName)) { + continue; + } + + if (message.message.endsWith('not devDependencies.')) { + addedDeps.add(packageName); + removedDevDeps.add(packageName); + } else if (isTestPath(result.filePath)) { + addedDevDeps.add(packageName); + } else { + addedDeps.add(packageName); + } + } + } + + if (addedDeps.size || addedDevDeps.size || removedDevDeps.size) { + for (const name of addedDeps) { + if (!pkg.dependencies) { + pkg.dependencies = {}; + } + pkg.dependencies[name] = `^${localPackageVersions.get(name)}`; + } + for (const name of addedDevDeps) { + if (!pkg.devDependencies) { + pkg.devDependencies = {}; + } + pkg.devDependencies[name] = `^${localPackageVersions.get(name)}`; + } + for (const name of removedDevDeps) { + delete pkg.devDependencies[name]; + } + if (Object.keys(pkg.devDependencies).length === 0) { + delete pkg.devDependencies; + } + + if (pkg.dependencies) { + pkg.dependencies = Object.fromEntries( + Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b)), + ); + } + if (pkg.devDependencies) { + pkg.devDependencies = Object.fromEntries( + Object.entries(pkg.devDependencies).sort(([a], [b]) => + a.localeCompare(b), + ), + ); + } + + await fs.writeJson(pkgJsonPath, pkg, { spaces: 2 }); + } +} diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index bc427bb19b..b50831f981 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -386,6 +386,13 @@ export function registerCommands(program: CommanderStatic) { .description('Check Backstage package versioning') .action(lazy(() => import('./versions/lint').then(m => m.default))); + program + .command('add-deps', { hidden: true }) + .description( + 'Add missing monorepo dependencies to package.json [EXPERIMENTAL]', + ) + .action(lazy(() => import('./add-deps').then(m => m.command))); + // TODO(Rugvip): Deprecate in favor of package variant program .command('prepack') From 9a86b65359cdaee7cf0d508b45c553228ca9cd61 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 19 Mar 2022 10:10:47 +0100 Subject: [PATCH 2/2] cli: rename add-deps to package fix Signed-off-by: Patrik Oldsberg --- .changeset/happy-foxes-arrive.md | 2 +- packages/cli/src/commands/{add-deps.ts => fix.ts} | 0 packages/cli/src/commands/index.ts | 13 ++++++------- 3 files changed, 7 insertions(+), 8 deletions(-) rename packages/cli/src/commands/{add-deps.ts => fix.ts} (100%) diff --git a/.changeset/happy-foxes-arrive.md b/.changeset/happy-foxes-arrive.md index 03ddfede0d..53e59c8d77 100644 --- a/.changeset/happy-foxes-arrive.md +++ b/.changeset/happy-foxes-arrive.md @@ -2,4 +2,4 @@ '@backstage/cli': patch --- -Added an experimental `add-deps` command which adds missing monorepo dependencies to the target package.` +Added an experimental `package fix` command which applies automated fixes to the target package. The initial fix that is available is to add missing monorepo dependencies to the target package. diff --git a/packages/cli/src/commands/add-deps.ts b/packages/cli/src/commands/fix.ts similarity index 100% rename from packages/cli/src/commands/add-deps.ts rename to packages/cli/src/commands/fix.ts diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index b50831f981..1d22ffb223 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -125,6 +125,12 @@ export function registerScriptCommand(program: CommanderStatic) { .description('Run tests, forwarding args to Jest, defaulting to watch mode') .action(lazy(() => import('./testCommand').then(m => m.default))); + command + .command('fix', { hidden: true }) + .description('Applies automated fixes to the package. [EXPERIMENTAL]') + .option('--deps', 'Only fix monorepo dependencies in package.json') + .action(lazy(() => import('./fix').then(m => m.command))); + command .command('clean') .description('Delete cache directories') @@ -386,13 +392,6 @@ export function registerCommands(program: CommanderStatic) { .description('Check Backstage package versioning') .action(lazy(() => import('./versions/lint').then(m => m.default))); - program - .command('add-deps', { hidden: true }) - .description( - 'Add missing monorepo dependencies to package.json [EXPERIMENTAL]', - ) - .action(lazy(() => import('./add-deps').then(m => m.command))); - // TODO(Rugvip): Deprecate in favor of package variant program .command('prepack')