cli: refactor out parts of versioning:bump + add tests
This commit is contained in:
@@ -17,11 +17,13 @@
|
||||
import fs from 'fs-extra';
|
||||
import semver from 'semver';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import { run, runPlain } from '../../lib/run';
|
||||
import { run } from '../../lib/run';
|
||||
import { paths } from '../../lib/paths';
|
||||
import { Lockfile } from '../../lib/versioning';
|
||||
|
||||
const PREFIX = '@backstage';
|
||||
import {
|
||||
mapDependencies,
|
||||
fetchPackageInfo,
|
||||
Lockfile,
|
||||
} from '../../lib/versioning';
|
||||
|
||||
const DEP_TYPES = [
|
||||
'dependencies',
|
||||
@@ -30,20 +32,6 @@ const DEP_TYPES = [
|
||||
'optionalDependencies',
|
||||
];
|
||||
|
||||
// Package data as returned by `yarn info`
|
||||
type YarnInfoInspectData = {
|
||||
name: string;
|
||||
'dist-tags': { latest: string };
|
||||
versions: string[];
|
||||
time: { [version: string]: string };
|
||||
};
|
||||
|
||||
// Possible `yarn info` output
|
||||
type YarnInfo = {
|
||||
type: 'inspect';
|
||||
data: YarnInfoInspectData | { type: string; data: unknown };
|
||||
};
|
||||
|
||||
type PkgVersionInfo = {
|
||||
range: string;
|
||||
name: string;
|
||||
@@ -51,43 +39,15 @@ type PkgVersionInfo = {
|
||||
};
|
||||
|
||||
export default async () => {
|
||||
const LernaProject = require('@lerna/project');
|
||||
const project = new LernaProject(paths.targetDir);
|
||||
const packages = await project.getPackages();
|
||||
|
||||
// First we discover all Backstage dependencies within our own repo
|
||||
const dependencyMap = new Map<string, PkgVersionInfo[]>();
|
||||
for (const pkg of packages) {
|
||||
const deps = DEP_TYPES.flatMap(
|
||||
t => Object.entries(pkg.get(t) ?? {}) as [string, string][],
|
||||
);
|
||||
|
||||
for (const [name, range] of deps) {
|
||||
if (name.startsWith(PREFIX)) {
|
||||
dependencyMap.set(
|
||||
name,
|
||||
(dependencyMap.get(name) ?? []).concat({
|
||||
range,
|
||||
name: pkg.name,
|
||||
location: pkg.location,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
const dependencyMap = await mapDependencies();
|
||||
|
||||
// Next check with the package registry what the latest version of all of those dependencies are
|
||||
const targetVersions = new Map<string, string>();
|
||||
await workerThreads(16, dependencyMap.keys(), async name => {
|
||||
console.log(`Checking for updates of ${name}`);
|
||||
const output = await runPlain('yarn', 'info', '--json', name);
|
||||
const info = JSON.parse(output) as YarnInfo;
|
||||
if (info.type !== 'inspect') {
|
||||
throw new Error(`Received unknown yarn info for ${name}, ${output}`);
|
||||
}
|
||||
|
||||
const data = info.data as YarnInfoInspectData;
|
||||
const latest = data['dist-tags'].latest;
|
||||
const info = await fetchPackageInfo(name);
|
||||
const latest = info['dist-tags'].latest;
|
||||
if (!latest) {
|
||||
throw new Error(`No latest version found for ${name}`);
|
||||
}
|
||||
|
||||
@@ -15,3 +15,4 @@
|
||||
*/
|
||||
|
||||
export { Lockfile } from './Lockfile';
|
||||
export { fetchPackageInfo, mapDependencies } from './packages';
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 * as runObj from '../run';
|
||||
import { paths } from '../paths';
|
||||
import { fetchPackageInfo, mapDependencies } from './packages';
|
||||
|
||||
describe('fetchPackageInfo', () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should forward info', async () => {
|
||||
jest
|
||||
.spyOn(runObj, 'runPlain')
|
||||
.mockResolvedValue(`{"type":"inspect","data":{"the":"data"}}`);
|
||||
|
||||
await expect(fetchPackageInfo('my-package')).resolves.toEqual({
|
||||
the: 'data',
|
||||
});
|
||||
expect(runObj.runPlain).toHaveBeenCalledWith(
|
||||
'yarn',
|
||||
'info',
|
||||
'--json',
|
||||
'my-package',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mapDependencies', () => {
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should derp', async () => {
|
||||
// Make sure all modules involved in package discovery are in the module cache before we mock fs
|
||||
const LernaProject = require('@lerna/project');
|
||||
const project = new LernaProject(paths.targetDir);
|
||||
await project.getPackages();
|
||||
|
||||
mockFs({
|
||||
'/lerna.json': JSON.stringify({
|
||||
packages: ['pkgs/*'],
|
||||
}),
|
||||
'/pkgs/a/package.json': JSON.stringify({
|
||||
name: 'a',
|
||||
dependencies: {
|
||||
'@backstage/core': '1 || 2',
|
||||
},
|
||||
}),
|
||||
'/pkgs/b/package.json': JSON.stringify({
|
||||
name: 'b',
|
||||
dependencies: {
|
||||
'@backstage/core': '3',
|
||||
'@backstage/cli': '^0',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const oldDir = paths.targetDir;
|
||||
paths.targetDir = '/';
|
||||
|
||||
const dependencyMap = await mapDependencies();
|
||||
expect(Array.from(dependencyMap)).toEqual([
|
||||
[
|
||||
'@backstage/core',
|
||||
[
|
||||
{
|
||||
name: 'a',
|
||||
range: '1 || 2',
|
||||
location: '/pkgs/a',
|
||||
},
|
||||
{
|
||||
name: 'b',
|
||||
range: '3',
|
||||
location: '/pkgs/b',
|
||||
},
|
||||
],
|
||||
],
|
||||
[
|
||||
'@backstage/cli',
|
||||
[
|
||||
{
|
||||
name: 'b',
|
||||
range: '^0',
|
||||
location: '/pkgs/b',
|
||||
},
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
paths.targetDir = oldDir;
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 { runPlain } from '../../lib/run';
|
||||
import { paths } from '../../lib/paths';
|
||||
|
||||
const PREFIX = '@backstage';
|
||||
|
||||
const DEP_TYPES = [
|
||||
'dependencies',
|
||||
'devDependencies',
|
||||
'peerDependencies',
|
||||
'optionalDependencies',
|
||||
];
|
||||
|
||||
// Package data as returned by `yarn info`
|
||||
type YarnInfoInspectData = {
|
||||
name: string;
|
||||
'dist-tags': { latest: string };
|
||||
versions: string[];
|
||||
time: { [version: string]: string };
|
||||
};
|
||||
|
||||
// Possible `yarn info` output
|
||||
type YarnInfo = {
|
||||
type: 'inspect';
|
||||
data: YarnInfoInspectData | { type: string; data: unknown };
|
||||
};
|
||||
|
||||
type PkgVersionInfo = {
|
||||
range: string;
|
||||
name: string;
|
||||
location: string;
|
||||
};
|
||||
|
||||
export async function fetchPackageInfo(
|
||||
name: string,
|
||||
): Promise<YarnInfoInspectData> {
|
||||
const output = await runPlain('yarn', 'info', '--json', name);
|
||||
const info = JSON.parse(output) as YarnInfo;
|
||||
if (info.type !== 'inspect') {
|
||||
throw new Error(`Received unknown yarn info for ${name}, ${output}`);
|
||||
}
|
||||
|
||||
return info.data as YarnInfoInspectData;
|
||||
}
|
||||
|
||||
/** Map all dependencies in the repo as dependency => dependents */
|
||||
export async function mapDependencies(): Promise<
|
||||
Map<string, PkgVersionInfo[]>
|
||||
> {
|
||||
const LernaProject = require('@lerna/project');
|
||||
const project = new LernaProject(paths.targetDir);
|
||||
const packages = await project.getPackages();
|
||||
|
||||
const dependencyMap = new Map<string, PkgVersionInfo[]>();
|
||||
for (const pkg of packages) {
|
||||
const deps = DEP_TYPES.flatMap(
|
||||
t => Object.entries(pkg.get(t) ?? {}) as [string, string][],
|
||||
);
|
||||
|
||||
for (const [name, range] of deps) {
|
||||
if (name.startsWith(PREFIX)) {
|
||||
dependencyMap.set(
|
||||
name,
|
||||
(dependencyMap.get(name) ?? []).concat({
|
||||
range,
|
||||
name: pkg.name,
|
||||
location: pkg.location,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dependencyMap;
|
||||
}
|
||||
Reference in New Issue
Block a user