Merge pull request #7307 from backstage/rugvip/range

cli: make create-plugin prefer version ranges that are already in the lockfile
This commit is contained in:
Patrik Oldsberg
2021-09-24 17:20:28 +02:00
committed by GitHub
10 changed files with 181 additions and 50 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
The `create-plugin` command now prefers dependency versions ranges that are already in the lockfile.
@@ -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');
+7 -6
View File
@@ -24,7 +24,7 @@ import handlebars from 'handlebars';
import recursiveReadDir from 'recursive-readdir';
import { paths } from '../paths';
import { FileDiff } from './types';
import { packageVersions } from '../../lib/version';
import { createPackageVersionProvider } from '../../lib/version';
export type TemplatedFile = {
path: string;
@@ -40,14 +40,15 @@ async function readTemplateFile(
if (!templateFile.endsWith('.hbs')) {
return contents;
}
const packageVersionProvider = createPackageVersionProvider(undefined);
return handlebars.compile(contents)(templateVars, {
helpers: {
version(name: keyof typeof packageVersions) {
if (name in packageVersions) {
return packageVersions[name];
}
throw new Error(`No version available for package ${name}`);
versionQuery(name: string, hint: string | unknown) {
return packageVersionProvider(
name,
typeof hint === 'string' ? hint : undefined,
);
},
},
});
+3 -3
View File
@@ -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');
});
});
+6 -6
View File
@@ -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,
);
},
},
},
+72
View File
@@ -0,0 +1,72 @@
/*
* 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 mockFs from 'mock-fs';
import { createPackageVersionProvider } from './version';
import { Lockfile } from './versioning';
// eslint-disable-next-line monorepo/no-internal-import
import corePluginApiPkg from '@backstage/core-plugin-api/package.json';
describe('createPackageVersionProvider', () => {
afterEach(() => {
mockFs.restore();
});
it('should provide package versions', async () => {
mockFs({
'yarn.lock': `
"a@^0.1.0":
version "0.1.5"
"b@^0.2.0","b@*","b@^0.2.1":
version "0.2.5"
"c@^0.1.4":
version "0.1.8"
"c@^0.2.4":
version "0.2.8"
"c@^0.3.4":
version "0.3.8"
"@types/t@^1.1.0","@types/t@*","@types/t@^1.2.3":
version "1.4.5"
"@backstage/cli@*":
version "1.1.5"
`,
});
const lockfile = await Lockfile.load('yarn.lock');
const provider = createPackageVersionProvider(lockfile);
expect(provider('a', '0.1.5')).toBe('^0.1.0');
expect(provider('b', '1.0.0')).toBe('*');
expect(provider('c', '0.1.0')).toBe('^0.1.0');
expect(provider('c', '0.1.6')).toBe('^0.1.4');
expect(provider('c', '0.2.0')).toBe('^0.2.0');
expect(provider('c', '0.2.6')).toBe('^0.2.4');
expect(provider('c', '0.3.0-rc1')).toBe('0.3.0-rc1');
expect(provider('c', '0.3.0')).toBe('^0.3.0');
expect(provider('c', '0.3.6')).toBe('^0.3.4');
expect(provider('@backstage/cli')).toBe('*');
expect(provider('@backstage/core-plugin-api')).toBe(
`^${corePluginApiPkg.version}`,
);
expect(provider('@types/t', '1.4.2')).toBe('*');
});
});
+36 -1
View File
@@ -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<string, string> = {
'@backstage/backend-common': backendCommon,
'@backstage/cli': cli,
'@backstage/config': config,
@@ -60,3 +62,36 @@ 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 packageVersion = packageVersions[name];
const targetVersion = versionHint || packageVersion;
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];
if (highestRange?.range) {
return highestRange?.range;
}
if (packageVersion) {
return `^${packageVersion}`;
}
if (semver.parse(versionHint)?.prerelease.length) {
return versionHint!;
}
return `^${versionHint}`;
};
}
@@ -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"
@@ -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": "{{versionQuery '@material-ui/lab' '4.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"
+10
View File
@@ -127,6 +127,16 @@ async function buildDistWorkspace(workspaceName: string, rootDir: string) {
}
return pkge.version;
},
versionQuery(name: string, hint: string) {
const pkgData = require(`${name}/package.json`);
if (!pkgData) {
if (typeof hint !== 'string') {
throw new Error(`No version available for package ${name}`);
}
return `^${hint}`;
}
return `^${pkgData.version}`;
},
},
},
),