Merge branch 'master' of https://github.com/backstage/backstage into feat/allow-inspect-override
Signed-off-by: jrwpatterson <jrwpatterson@gmail.com>
This commit is contained in:
@@ -1,142 +0,0 @@
|
||||
/*
|
||||
* 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 '@backstage/cli-node';
|
||||
|
||||
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<string>();
|
||||
const addedDevDeps = new Set<string>();
|
||||
const removedDevDeps = new Set<string>();
|
||||
|
||||
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 });
|
||||
}
|
||||
}
|
||||
@@ -168,12 +168,6 @@ export function registerScriptCommand(program: Command) {
|
||||
.description('Run tests, forwarding args to Jest, defaulting to watch mode')
|
||||
.action(lazy(() => import('./test').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')
|
||||
|
||||
@@ -29,6 +29,7 @@ import { YarnInfoInspectData } from '../../lib/versioning/packages';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { rest } from 'msw';
|
||||
import { NotFoundError } from '@backstage/errors';
|
||||
import { Lockfile } from '../../lib/versioning/Lockfile';
|
||||
|
||||
// Remove log coloring to simplify log matching
|
||||
jest.mock('chalk', () => ({
|
||||
@@ -224,7 +225,7 @@ describe('bump', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should bump backstage dependencies but not them installed', async () => {
|
||||
it('should bump backstage dependencies but not install them', async () => {
|
||||
mockFs({
|
||||
'/yarn.lock': lockfileMock,
|
||||
'/package.json': JSON.stringify({
|
||||
@@ -453,68 +454,36 @@ describe('bump', () => {
|
||||
jest.spyOn(runObj, 'run').mockResolvedValue(undefined);
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://versions.backstage.io/v1/releases/1.0.0/manifest.json',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
releaseVersion: '2.0.0',
|
||||
packages: [
|
||||
{ name: '@backstage/core', version: '5.0.0' },
|
||||
{ name: '@backstage/core-api', version: '5.0.0' },
|
||||
],
|
||||
}),
|
||||
),
|
||||
'https://versions.backstage.io/v1/releases/999.0.1/manifest.json',
|
||||
(_, res, ctx) => res(ctx.status(404), ctx.json({})),
|
||||
),
|
||||
);
|
||||
const { log: logs } = await withLogCollector(['log'], async () => {
|
||||
await expect(
|
||||
bump({ pattern: null, release: '1.0.0' } as unknown as Command),
|
||||
).rejects.toThrow('Duplicate versions present after package bump');
|
||||
bump({ pattern: null, release: '999.0.1' } as unknown as Command),
|
||||
).rejects.toThrow('No release found for 999.0.1 version');
|
||||
});
|
||||
expect(logs.filter(Boolean)).toEqual([
|
||||
'Using default pattern glob @backstage/*',
|
||||
'Checking for updates of @backstage/core',
|
||||
'Checking for updates of @backstage/theme',
|
||||
'Package info not found, ignoring package @backstage/theme',
|
||||
'Checking for updates of @backstage/core',
|
||||
'Checking for updates of @backstage/theme',
|
||||
'Checking for updates of @backstage/core-api',
|
||||
'Package info not found, ignoring package @backstage/theme',
|
||||
'Some packages are outdated, updating',
|
||||
'bumping @backstage/core in a to ^5.0.0',
|
||||
'bumping @backstage/core in b to ^5.0.0',
|
||||
'Your project is now at version 2.0.0, which has been written to backstage.json',
|
||||
'Running yarn install to install new versions',
|
||||
'⚠️ The following packages may have breaking changes:',
|
||||
' @backstage/core : 1.0.3 ~> 5.0.0',
|
||||
' https://github.com/backstage/backstage/blob/master/packages/core/CHANGELOG.md',
|
||||
'Version bump complete!',
|
||||
]);
|
||||
|
||||
expect(runObj.run).toHaveBeenCalledTimes(1);
|
||||
expect(runObj.run).toHaveBeenCalledWith(
|
||||
'yarn',
|
||||
['install'],
|
||||
expect.any(Object),
|
||||
);
|
||||
expect(runObj.run).toHaveBeenCalledTimes(0);
|
||||
|
||||
const packageA = await fs.readJson('/packages/a/package.json');
|
||||
expect(packageA).toEqual({
|
||||
name: 'a',
|
||||
dependencies: {
|
||||
'@backstage/core': '^5.0.0',
|
||||
'@backstage/core': '^1.0.5',
|
||||
},
|
||||
});
|
||||
const packageB = await fs.readJson('/packages/b/package.json');
|
||||
expect(packageB).toEqual({
|
||||
name: 'b',
|
||||
dependencies: {
|
||||
'@backstage/core': '^5.0.0',
|
||||
'@backstage/core': '^1.0.3',
|
||||
'@backstage/theme': '^1.0.0',
|
||||
},
|
||||
});
|
||||
expect(await fs.readJson('/backstage.json')).toEqual({ version: '2.0.0' });
|
||||
});
|
||||
|
||||
it('should prefer versions from the highest manifest version when main is not specified', async () => {
|
||||
@@ -825,6 +794,97 @@ describe('bump', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should log duplicates', async () => {
|
||||
jest.spyOn(Lockfile.prototype, 'analyze').mockReturnValue({
|
||||
invalidRanges: [],
|
||||
newVersions: [],
|
||||
newRanges: [
|
||||
{
|
||||
name: 'first-duplicate',
|
||||
oldRange: 'first-duplicate',
|
||||
newRange: 'first-duplicate',
|
||||
oldVersion: '1.0.0',
|
||||
newVersion: '2.0.0',
|
||||
},
|
||||
{
|
||||
name: 'second-duplicate',
|
||||
oldRange: 'second-duplicate',
|
||||
newRange: 'second-duplicate',
|
||||
oldVersion: '1.0.0',
|
||||
newVersion: '2.0.0',
|
||||
},
|
||||
{
|
||||
name: 'third-duplicate',
|
||||
oldRange: 'third-duplicate',
|
||||
newRange: 'third-duplicate',
|
||||
oldVersion: '1.0.0',
|
||||
newVersion: '2.0.0',
|
||||
},
|
||||
],
|
||||
});
|
||||
mockFs({
|
||||
'/yarn.lock': lockfileMock,
|
||||
'/package.json': JSON.stringify({
|
||||
workspaces: {
|
||||
packages: ['packages/*'],
|
||||
},
|
||||
}),
|
||||
'/packages/a/package.json': JSON.stringify({
|
||||
name: 'a',
|
||||
dependencies: {
|
||||
'@backstage/core': '^1.0.5',
|
||||
},
|
||||
}),
|
||||
'/packages/b/package.json': JSON.stringify({
|
||||
name: 'b',
|
||||
dependencies: {
|
||||
'@backstage/core': '^1.0.3',
|
||||
'@backstage/theme': '^1.0.0',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
jest
|
||||
.spyOn(paths, 'resolveTargetRoot')
|
||||
.mockImplementation((...path) => resolvePath('/', ...path));
|
||||
jest.spyOn(runObj, 'run').mockResolvedValue(undefined);
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://versions.backstage.io/v1/tags/main/manifest.json',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
packages: [],
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
const { log: logs } = await withLogCollector(['log'], async () => {
|
||||
await bump({ pattern: null, release: 'main' } as unknown as Command);
|
||||
});
|
||||
expect(logs.filter(Boolean)).toEqual([
|
||||
'Using default pattern glob @backstage/*',
|
||||
'Checking for updates of @backstage/core',
|
||||
'Checking for updates of @backstage/theme',
|
||||
'Checking for updates of @backstage/core-api',
|
||||
'Some packages are outdated, updating',
|
||||
'unlocking @backstage/core@^1.0.3 ~> 1.0.6',
|
||||
'unlocking @backstage/core-api@^1.0.6 ~> 1.0.7',
|
||||
'unlocking @backstage/core-api@^1.0.3 ~> 1.0.7',
|
||||
'bumping @backstage/core in a to ^1.0.6',
|
||||
'bumping @backstage/core in b to ^1.0.6',
|
||||
'bumping @backstage/theme in b to ^2.0.0',
|
||||
'Running yarn install to install new versions',
|
||||
'⚠️ The following packages may have breaking changes:',
|
||||
' @backstage/theme : 1.0.0 ~> 2.0.0',
|
||||
' https://github.com/backstage/backstage/blob/master/packages/theme/CHANGELOG.md',
|
||||
'Version bump complete!',
|
||||
'The following packages have duplicates but have been allowed:',
|
||||
'first-duplicate, second-duplicate, third-duplicate',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bumpBackstageJsonVersion', () => {
|
||||
|
||||
@@ -314,10 +314,6 @@ export default async (opts: OptionValues) => {
|
||||
),
|
||||
});
|
||||
|
||||
if (result.newVersions.length > 0) {
|
||||
throw new Error('Duplicate versions present after package bump');
|
||||
}
|
||||
|
||||
const forbiddenNewRanges = result.newRanges.filter(({ name }) =>
|
||||
forbiddenDuplicatesFilter(name),
|
||||
);
|
||||
@@ -328,6 +324,19 @@ export default async (opts: OptionValues) => {
|
||||
.join(', ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
const allowedDuplicates = result.newRanges.filter(
|
||||
({ name }) => !forbiddenDuplicatesFilter(name),
|
||||
);
|
||||
|
||||
if (allowedDuplicates.length > 0) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'The following packages have duplicates but have been allowed:',
|
||||
),
|
||||
);
|
||||
console.log(chalk.yellow(allowedDuplicates.map(i => i.name).join(', ')));
|
||||
}
|
||||
};
|
||||
|
||||
export function createStrictVersionFinder(options: {
|
||||
|
||||
Reference in New Issue
Block a user