diff --git a/.changeset/heavy-rabbits-melt.md b/.changeset/heavy-rabbits-melt.md new file mode 100644 index 0000000000..cb836588f6 --- /dev/null +++ b/.changeset/heavy-rabbits-melt.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +The `create-plugin` command now prefers dependency versions ranges that are already in the lockfile. diff --git a/packages/cli/src/commands/create-plugin/createPlugin.ts b/packages/cli/src/commands/create-plugin/createPlugin.ts index c9784969af..0d47df8c22 100644 --- a/packages/cli/src/commands/create-plugin/createPlugin.ts +++ b/packages/cli/src/commands/create-plugin/createPlugin.ts @@ -30,8 +30,9 @@ import { getCodeownersFilePath, } from '../../lib/codeowners'; import { paths } from '../../lib/paths'; -import { packageVersions } from '../../lib/version'; import { Task, templatingTask } from '../../lib/tasks'; +import { Lockfile } from '../../lib/versioning'; +import { createPackageVersionProvider } from '../../lib/version'; const exec = promisify(execCb); @@ -262,6 +263,13 @@ export default async (cmd: Command) => { ? await fs.readJson(paths.resolveTargetRoot('lerna.json')) : { version: '0.1.0' }; + let lockfile: Lockfile | undefined; + try { + lockfile = await Lockfile.load(paths.resolveTargetRoot('yarn.lock')); + } catch (error) { + console.warn(`No yarn.lock available, ${error}`); + } + Task.log(); Task.log('Creating the plugin...'); @@ -288,7 +296,7 @@ export default async (cmd: Command) => { privatePackage, npmRegistry, }, - packageVersions, + createPackageVersionProvider(lockfile), ); Task.section('Moving to final location'); diff --git a/packages/cli/src/lib/tasks.test.ts b/packages/cli/src/lib/tasks.test.ts index 9ee4e93336..4d9af05c4b 100644 --- a/packages/cli/src/lib/tasks.test.ts +++ b/packages/cli/src/lib/tasks.test.ts @@ -34,7 +34,7 @@ describe('templatingTask', () => { // Files content const testFileContent = 'testing'; const testVersionFileContent = - "version: {{pluginVersion}} {{version 'mock-pkg'}}"; + "version: {{pluginVersion}} {{versionQuery 'mock-pkg'}}"; mockFs({ [tmplDir]: { @@ -52,7 +52,7 @@ describe('templatingTask', () => { { pluginVersion: '0.0.0', }, - { 'mock-pkg': '0.1.2' }, + () => '^0.1.2', ); await expect( @@ -60,6 +60,6 @@ describe('templatingTask', () => { ).resolves.toBe(testFileContent); await expect( fs.readFile(resolvePath(destDir, 'sub/version.txt'), 'utf8'), - ).resolves.toBe('version: 0.0.0 0.1.2'); + ).resolves.toBe('version: 0.0.0 ^0.1.2'); }); }); diff --git a/packages/cli/src/lib/tasks.ts b/packages/cli/src/lib/tasks.ts index be6d052365..e17015d6cf 100644 --- a/packages/cli/src/lib/tasks.ts +++ b/packages/cli/src/lib/tasks.ts @@ -69,7 +69,7 @@ export async function templatingTask( templateDir: string, destinationDir: string, context: any, - versions: { [name: string]: string }, + versionProvider: (name: string, versionHint?: string) => string, ) { const files = await recursive(templateDir).catch(error => { throw new Error(`Failed to read template directory: ${error.message}`); @@ -90,11 +90,11 @@ export async function templatingTask( { name: basename(destination), ...context }, { helpers: { - version(name: string) { - if (versions[name]) { - return versions[name]; - } - throw new Error(`No version available for package ${name}`); + versionQuery(name: string, versionHint: string | unknown) { + return versionProvider( + name, + typeof versionHint === 'string' ? versionHint : undefined, + ); }, }, }, diff --git a/packages/cli/src/lib/version.ts b/packages/cli/src/lib/version.ts index 61ee4003fc..52ff594423 100644 --- a/packages/cli/src/lib/version.ts +++ b/packages/cli/src/lib/version.ts @@ -15,7 +15,9 @@ */ import fs from 'fs-extra'; +import semver from 'semver'; import { paths } from './paths'; +import { Lockfile } from './versioning'; /* eslint-disable import/no-extraneous-dependencies,monorepo/no-internal-import */ /* @@ -41,7 +43,7 @@ import { version as devUtils } from '@backstage/dev-utils/package.json'; import { version as testUtils } from '@backstage/test-utils/package.json'; import { version as theme } from '@backstage/theme/package.json'; -export const packageVersions = { +export const packageVersions: Record = { '@backstage/backend-common': backendCommon, '@backstage/cli': cli, '@backstage/config': config, @@ -60,3 +62,25 @@ export function findVersion() { export const version = findVersion(); export const isDev = fs.pathExistsSync(paths.resolveOwn('src')); + +export function createPackageVersionProvider(lockfile?: Lockfile) { + return (name: string, versionHint?: string) => { + const targetVersion = versionHint || packageVersions[name]; + if (!targetVersion) { + throw new Error(`No version available for package ${name}`); + } + + const lockfileEntries = lockfile?.get(name); + if ( + name.startsWith('@types/') && + lockfileEntries?.some(entry => entry.range === '*') + ) { + return '*'; + } + const validRanges = lockfileEntries?.filter(entry => + semver.satisfies(targetVersion, entry.range), + ); + const highestRange = validRanges?.slice(-1)[0]; + return highestRange?.range ?? `^${targetVersion}`; + }; +} diff --git a/packages/cli/templates/default-backend-plugin/package.json.hbs b/packages/cli/templates/default-backend-plugin/package.json.hbs index 8275223f49..43b9e83fe1 100644 --- a/packages/cli/templates/default-backend-plugin/package.json.hbs +++ b/packages/cli/templates/default-backend-plugin/package.json.hbs @@ -23,20 +23,20 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^{{version '@backstage/backend-common'}}", - "@backstage/config": "^{{version '@backstage/config'}}", - "@types/express": "^4.17.6", - "express": "^4.17.1", - "express-promise-router": "^4.1.0", - "winston": "^3.2.1", - "cross-fetch": "^3.0.6", - "yn": "^4.0.0" + "@backstage/backend-common": "{{versionQuery '@backstage/backend-common'}}", + "@backstage/config": "{{versionQuery '@backstage/config'}}", + "@types/express": "{{versionQuery '@types/express' '4.17.6'}}", + "express": "{{versionQuery 'express' '4.17.1'}}", + "express-promise-router": "{{versionQuery 'express-promise-router' '4.1.0'}}", + "winston": "{{versionQuery 'winston' '3.2.1'}}", + "cross-fetch": "{{versionQuery 'cross-fetch' '3.0.6'}}", + "yn": "{{versionQuery 'yn' '4.0.0'}}" }, "devDependencies": { - "@backstage/cli": "^{{version '@backstage/cli'}}", - "@types/supertest": "^2.0.8", - "supertest": "^4.0.2", - "msw": "^0.29.0" + "@backstage/cli": "{{versionQuery '@backstage/cli'}}", + "@types/supertest": "{{versionQuery '@types/supertest' '2.0.8'}}", + "supertest": "{{versionQuery 'supertest' '4.0.2'}}", + "msw": "{{versionQuery 'msw' '0.29.0'}}" }, "files": [ "dist" diff --git a/packages/cli/templates/default-plugin/package.json.hbs b/packages/cli/templates/default-plugin/package.json.hbs index 318830da39..77c3c84efb 100644 --- a/packages/cli/templates/default-plugin/package.json.hbs +++ b/packages/cli/templates/default-plugin/package.json.hbs @@ -24,28 +24,28 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^{{version '@backstage/core-components'}}", - "@backstage/core-plugin-api": "^{{version '@backstage/core-plugin-api'}}", - "@backstage/theme": "^{{version '@backstage/theme'}}", - "@material-ui/core": "^4.12.2", - "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.57", - "react": "^16.13.1", - "react-dom": "^16.13.1", - "react-use": "^17.2.4" + "@backstage/core-components": "{{versionQuery '@backstage/core-components'}}", + "@backstage/core-plugin-api": "{{versionQuery '@backstage/core-plugin-api'}}", + "@backstage/theme": "{{versionQuery '@backstage/theme'}}", + "@material-ui/core": "{{versionQuery '@material-ui/core' '4.12.2'}}", + "@material-ui/icons": "{{versionQuery '@material-ui/icons' '4.9.1'}}", + "@material-ui/lab": "4{{versionQuery '@material-ui/lab' '.0.0-alpha.57'}}", + "react": "{{versionQuery 'react' '16.13.1'}}", + "react-dom": "{{versionQuery 'react-dom' '16.13.1'}}", + "react-use": "{{versionQuery 'react-use' '17.2.4'}}" }, "devDependencies": { - "@backstage/cli": "^{{version '@backstage/cli'}}", - "@backstage/core-app-api": "^{{version '@backstage/core-app-api'}}", - "@backstage/dev-utils": "^{{version '@backstage/dev-utils'}}", - "@backstage/test-utils": "^{{version '@backstage/test-utils'}}", - "@testing-library/jest-dom": "^5.10.1", - "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^13.1.8", - "@types/jest": "^26.0.7", - "@types/node": "^14.14.32", - "msw": "^0.29.0", - "cross-fetch": "^3.0.6" + "@backstage/cli": "{{versionQuery '@backstage/cli'}}", + "@backstage/core-app-api": "{{versionQuery '@backstage/core-app-api'}}", + "@backstage/dev-utils": "{{versionQuery '@backstage/dev-utils'}}", + "@backstage/test-utils": "{{versionQuery '@backstage/test-utils'}}", + "@testing-library/jest-dom": "{{versionQuery '@testing-library/jest-dom' '5.10.1'}}", + "@testing-library/react": "{{versionQuery '@testing-library/react' '11.2.5'}}", + "@testing-library/user-event": "{{versionQuery '@testing-library/user-event' '13.1.8'}}", + "@types/jest": "{{versionQuery '@types/jest' '26.0.7'}}", + "@types/node": "{{versionQuery '@types/node' '14.14.32'}}", + "msw": "{{versionQuery 'msw' '0.29.0'}}", + "cross-fetch": "{{versionQuery 'cross-fetch' '3.0.6'}}" }, "files": [ "dist"