Merge remote-tracking branch 'origin/master' into package-workspaces
This commit is contained in:
@@ -77,8 +77,6 @@
|
||||
"@types/webpack-env": "^1.15.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.17.0",
|
||||
"@typescript-eslint/parser": "^8.16.0",
|
||||
"@yarnpkg/lockfile": "^1.1.0",
|
||||
"@yarnpkg/parsers": "^3.0.0",
|
||||
"bfj": "^9.0.2",
|
||||
"buffer": "^6.0.3",
|
||||
"chalk": "^4.0.0",
|
||||
@@ -182,7 +180,6 @@
|
||||
"@types/tar": "^6.1.1",
|
||||
"@types/terser-webpack-plugin": "^5.0.4",
|
||||
"@types/webpack-sources": "^3.2.3",
|
||||
"@types/yarnpkg__lockfile": "^1.1.4",
|
||||
"del": "^8.0.0",
|
||||
"esbuild-loader": "^4.0.0",
|
||||
"eslint-webpack-plugin": "^4.2.0",
|
||||
|
||||
-103
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 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 fs from 'fs-extra';
|
||||
import { resolve as resolvePath } from 'node:path';
|
||||
import { targetPaths } from '@backstage/cli-common';
|
||||
|
||||
const DEFAULT_CACHE_BASE_PATH = 'node_modules/.cache/backstage-cli';
|
||||
|
||||
const CACHE_MAX_AGE_MS = 7 * 24 * 3600_000;
|
||||
|
||||
export class SuccessCache {
|
||||
readonly #path: string;
|
||||
|
||||
/**
|
||||
* Trim any occurrences of the workspace root path from the input string. This
|
||||
* is useful to ensure stable hashes that don't vary based on the workspace
|
||||
* location.
|
||||
*/
|
||||
static trimPaths(input: string) {
|
||||
return input.replaceAll(targetPaths.rootDir, '');
|
||||
}
|
||||
|
||||
constructor(name: string, basePath?: string) {
|
||||
this.#path = resolvePath(basePath ?? DEFAULT_CACHE_BASE_PATH, name);
|
||||
}
|
||||
|
||||
async read(): Promise<Set<string>> {
|
||||
try {
|
||||
const stat = await fs.stat(this.#path);
|
||||
if (!stat.isDirectory()) {
|
||||
await fs.rm(this.#path);
|
||||
return new Set();
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
return new Set();
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
const items = await fs.readdir(this.#path);
|
||||
|
||||
const returned = new Set<string>();
|
||||
const removed = new Set<string>();
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
for (const item of items) {
|
||||
const split = item.split('_');
|
||||
if (split.length !== 2) {
|
||||
removed.add(item);
|
||||
continue;
|
||||
}
|
||||
const createdAt = parseInt(split[0], 10);
|
||||
if (Number.isNaN(createdAt) || now - createdAt > CACHE_MAX_AGE_MS) {
|
||||
removed.add(item);
|
||||
} else {
|
||||
returned.add(split[1]);
|
||||
}
|
||||
}
|
||||
|
||||
for (const item of removed) {
|
||||
await fs.unlink(resolvePath(this.#path, item));
|
||||
}
|
||||
|
||||
return returned;
|
||||
}
|
||||
|
||||
async write(newEntries: Iterable<string>): Promise<void> {
|
||||
const now = Date.now();
|
||||
|
||||
await fs.ensureDir(this.#path);
|
||||
|
||||
const existingItems = await fs.readdir(this.#path);
|
||||
|
||||
const empty = Buffer.alloc(0);
|
||||
for (const key of newEntries) {
|
||||
// Remove any existing items with the key we're about to add
|
||||
const trimmedItems = existingItems.filter(item =>
|
||||
item.endsWith(`_${key}`),
|
||||
);
|
||||
for (const trimmedItem of trimmedItems) {
|
||||
await fs.unlink(resolvePath(this.#path, trimmedItem));
|
||||
}
|
||||
|
||||
await fs.writeFile(resolvePath(this.#path, `${now}_${key}`), empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,102 +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 { Lockfile } from './Lockfile';
|
||||
import { createMockDirectory } from '@backstage/backend-test-utils';
|
||||
|
||||
const LEGACY_HEADER = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
`;
|
||||
|
||||
const MODERN_HEADER = `# This file is generated by running "yarn install" inside your project.
|
||||
# Manual changes might be lost - proceed with caution!
|
||||
|
||||
__metadata:
|
||||
version: 6
|
||||
cacheKey: 8
|
||||
`;
|
||||
|
||||
const mockA = `${LEGACY_HEADER}
|
||||
a@^1:
|
||||
version "1.0.1"
|
||||
resolved "https://my-registry/a-1.0.01.tgz#abc123"
|
||||
integrity sha512-xyz
|
||||
dependencies:
|
||||
b "^2"
|
||||
|
||||
b@2.0.x:
|
||||
version "2.0.1"
|
||||
|
||||
b@^2:
|
||||
version "2.0.0"
|
||||
`;
|
||||
|
||||
describe('Lockfile', () => {
|
||||
const mockDir = createMockDirectory();
|
||||
|
||||
it('should load and serialize mockA', async () => {
|
||||
mockDir.setContent({
|
||||
'yarn.lock': mockA,
|
||||
});
|
||||
|
||||
const lockfile = await Lockfile.load(mockDir.resolve('yarn.lock'));
|
||||
expect(lockfile.get('a')).toEqual([
|
||||
{ range: '^1', version: '1.0.1', dataKey: 'a@^1' },
|
||||
]);
|
||||
expect(lockfile.get('b')).toEqual([
|
||||
{ range: '2.0.x', version: '2.0.1', dataKey: 'b@2.0.x' },
|
||||
{ range: '^2', version: '2.0.0', dataKey: 'b@^2' },
|
||||
]);
|
||||
expect(lockfile.toString()).toBe(mockA);
|
||||
});
|
||||
});
|
||||
|
||||
const mockANew = `${MODERN_HEADER}
|
||||
a@^1:
|
||||
version: 1.0.1
|
||||
dependencies:
|
||||
b: ^2
|
||||
integrity: sha512-xyz
|
||||
resolved: "https://my-registry/a-1.0.01.tgz#abc123"
|
||||
|
||||
"b@2.0.x, b@^2.0.1":
|
||||
version: 2.0.1
|
||||
|
||||
b@^2:
|
||||
version: 2.0.0
|
||||
`;
|
||||
|
||||
describe('New Lockfile', () => {
|
||||
const mockDir = createMockDirectory();
|
||||
|
||||
it('should load and serialize mockANew', async () => {
|
||||
mockDir.setContent({
|
||||
'yarn.lock': mockANew,
|
||||
});
|
||||
|
||||
const lockfile = await Lockfile.load(mockDir.resolve('yarn.lock'));
|
||||
expect(lockfile.get('a')).toEqual([
|
||||
{ range: '^1', version: '1.0.1', dataKey: 'a@^1' },
|
||||
]);
|
||||
expect(lockfile.get('b')).toEqual([
|
||||
{ range: '2.0.x', version: '2.0.1', dataKey: 'b@2.0.x, b@^2.0.1' },
|
||||
{ range: '^2.0.1', version: '2.0.1', dataKey: 'b@2.0.x, b@^2.0.1' },
|
||||
{ range: '^2', version: '2.0.0', dataKey: 'b@^2' },
|
||||
]);
|
||||
expect(lockfile.toString()).toBe(mockANew);
|
||||
});
|
||||
});
|
||||
@@ -1,138 +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 fs from 'fs-extra';
|
||||
import { parseSyml, stringifySyml } from '@yarnpkg/parsers';
|
||||
import { stringify as legacyStringifyLockfile } from '@yarnpkg/lockfile';
|
||||
|
||||
const ENTRY_PATTERN = /^((?:@[^/]+\/)?[^@/]+)@(.+)$/;
|
||||
|
||||
type LockfileData = {
|
||||
[entry: string]: {
|
||||
version: string;
|
||||
resolved?: string;
|
||||
integrity?: string /* old */;
|
||||
checksum?: string /* new */;
|
||||
dependencies?: { [name: string]: string };
|
||||
peerDependencies?: { [name: string]: string };
|
||||
};
|
||||
};
|
||||
|
||||
type LockfileQueryEntry = {
|
||||
range: string;
|
||||
version: string;
|
||||
dataKey: string;
|
||||
};
|
||||
|
||||
// the new yarn header is handled out of band of the parsing
|
||||
// https://github.com/yarnpkg/berry/blob/0c5974f193a9397630e9aee2b3876cca62611149/packages/yarnpkg-core/sources/Project.ts#L1741-L1746
|
||||
const NEW_HEADER = `${[
|
||||
`# This file is generated by running "yarn install" inside your project.\n`,
|
||||
`# Manual changes might be lost - proceed with caution!\n`,
|
||||
].join(``)}\n`;
|
||||
|
||||
// taken from yarn parser package
|
||||
// https://github.com/yarnpkg/berry/blob/0c5974f193a9397630e9aee2b3876cca62611149/packages/yarnpkg-parsers/sources/syml.ts#L136
|
||||
const LEGACY_REGEX = /^(#.*(\r?\n))*?#\s+yarn\s+lockfile\s+v1\r?\n/i;
|
||||
|
||||
// these are special top level yarn keys.
|
||||
// https://github.com/yarnpkg/berry/blob/9bd61fbffb83d0b8166a9cc26bec3a58743aa453/packages/yarnpkg-parsers/sources/syml.ts#L9
|
||||
const SPECIAL_OBJECT_KEYS = [
|
||||
`__metadata`,
|
||||
`version`,
|
||||
`resolution`,
|
||||
`dependencies`,
|
||||
`peerDependencies`,
|
||||
`dependenciesMeta`,
|
||||
`peerDependenciesMeta`,
|
||||
`binaries`,
|
||||
];
|
||||
|
||||
export class Lockfile {
|
||||
static async load(path: string) {
|
||||
const lockfileContents = await fs.readFile(path, 'utf8');
|
||||
return Lockfile.parse(lockfileContents);
|
||||
}
|
||||
|
||||
static parse(content: string) {
|
||||
const legacy = LEGACY_REGEX.test(content);
|
||||
|
||||
let data: LockfileData;
|
||||
try {
|
||||
data = parseSyml(content);
|
||||
} catch (err) {
|
||||
throw new Error(`Failed yarn.lock parse, ${err}`);
|
||||
}
|
||||
|
||||
const packages = new Map<string, LockfileQueryEntry[]>();
|
||||
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
if (SPECIAL_OBJECT_KEYS.includes(key)) continue;
|
||||
|
||||
const [, name, ranges] = ENTRY_PATTERN.exec(key) ?? [];
|
||||
if (!name) {
|
||||
throw new Error(`Failed to parse yarn.lock entry '${key}'`);
|
||||
}
|
||||
|
||||
let queries = packages.get(name);
|
||||
if (!queries) {
|
||||
queries = [];
|
||||
packages.set(name, queries);
|
||||
}
|
||||
for (let range of ranges.split(/\s*,\s*/)) {
|
||||
if (range.startsWith(`${name}@`)) {
|
||||
range = range.slice(`${name}@`.length);
|
||||
}
|
||||
if (range.startsWith('npm:')) {
|
||||
range = range.slice('npm:'.length);
|
||||
}
|
||||
queries.push({ range, version: value.version, dataKey: key });
|
||||
}
|
||||
}
|
||||
|
||||
return new Lockfile(packages, data, legacy);
|
||||
}
|
||||
|
||||
private readonly packages: Map<string, LockfileQueryEntry[]>;
|
||||
private readonly data: LockfileData;
|
||||
private readonly legacy: boolean;
|
||||
|
||||
private constructor(
|
||||
packages: Map<string, LockfileQueryEntry[]>,
|
||||
data: LockfileData,
|
||||
legacy: boolean = false,
|
||||
) {
|
||||
this.packages = packages;
|
||||
this.data = data;
|
||||
this.legacy = legacy;
|
||||
}
|
||||
|
||||
/** Get the entries for a single package in the lockfile */
|
||||
get(name: string): LockfileQueryEntry[] | undefined {
|
||||
return this.packages.get(name);
|
||||
}
|
||||
|
||||
/** Returns the name of all packages available in the lockfile */
|
||||
keys(): IterableIterator<string> {
|
||||
return this.packages.keys();
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.legacy
|
||||
? legacyStringifyLockfile(this.data)
|
||||
: NEW_HEADER + stringifySyml(this.data);
|
||||
}
|
||||
}
|
||||
@@ -1,125 +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 { createMockDirectory } from '@backstage/backend-test-utils';
|
||||
import { overrideTargetPaths } from '@backstage/cli-common/testUtils';
|
||||
import { getHasYarnPlugin } from './yarnPlugin';
|
||||
|
||||
const mockDir = createMockDirectory();
|
||||
overrideTargetPaths(mockDir.path);
|
||||
|
||||
describe('getHasYarnPlugin', () => {
|
||||
beforeEach(() => {
|
||||
mockDir.clear();
|
||||
});
|
||||
|
||||
it('should return false when .yarnrc.yml does not exist', async () => {
|
||||
mockDir.setContent({});
|
||||
|
||||
const result = await getHasYarnPlugin();
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when .yarnrc.yml is empty', async () => {
|
||||
mockDir.setContent({
|
||||
'.yarnrc.yml': '',
|
||||
});
|
||||
|
||||
const result = await getHasYarnPlugin();
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when plugins array is empty', async () => {
|
||||
mockDir.setContent({
|
||||
'.yarnrc.yml': 'plugins: []',
|
||||
});
|
||||
|
||||
const result = await getHasYarnPlugin();
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when plugins array does not contain backstage plugin', async () => {
|
||||
mockDir.setContent({
|
||||
'.yarnrc.yml': `
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
|
||||
`,
|
||||
});
|
||||
|
||||
const result = await getHasYarnPlugin();
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when backstage plugin is present', async () => {
|
||||
mockDir.setContent({
|
||||
'.yarnrc.yml': `
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-backstage.cjs
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
|
||||
`,
|
||||
});
|
||||
|
||||
const result = await getHasYarnPlugin();
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true when backstage plugin is the only plugin', async () => {
|
||||
mockDir.setContent({
|
||||
'.yarnrc.yml': `
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-backstage.cjs
|
||||
`,
|
||||
});
|
||||
|
||||
const result = await getHasYarnPlugin();
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should throw error when .yarnrc.yml has invalid content', async () => {
|
||||
mockDir.setContent({
|
||||
'.yarnrc.yml': 'invalid: yaml: content: [',
|
||||
});
|
||||
|
||||
await expect(getHasYarnPlugin()).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should throw error when .yarnrc.yml has unexpected structure', async () => {
|
||||
mockDir.setContent({
|
||||
'.yarnrc.yml': `
|
||||
plugins: "not an array"
|
||||
`,
|
||||
});
|
||||
|
||||
await expect(getHasYarnPlugin()).rejects.toThrow(
|
||||
'Unexpected content in .yarnrc.yml',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle plugins with different structure', async () => {
|
||||
mockDir.setContent({
|
||||
'.yarnrc.yml': `
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-backstage.cjs
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
|
||||
`,
|
||||
});
|
||||
|
||||
const result = await getHasYarnPlugin();
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,66 +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 fs from 'fs-extra';
|
||||
import yaml from 'yaml';
|
||||
import z from 'zod';
|
||||
import { targetPaths } from '@backstage/cli-common';
|
||||
|
||||
const yarnRcSchema = z.object({
|
||||
plugins: z
|
||||
.array(
|
||||
z.object({
|
||||
path: z.string(),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Detects whether the Backstage Yarn plugin is installed in the target repository.
|
||||
*
|
||||
* @returns Promise<boolean> - true if the plugin is installed, false otherwise
|
||||
*/
|
||||
export async function getHasYarnPlugin(): Promise<boolean> {
|
||||
const yarnRcPath = targetPaths.resolveRoot('.yarnrc.yml');
|
||||
const yarnRcContent = await fs.readFile(yarnRcPath, 'utf-8').catch(e => {
|
||||
if (e.code === 'ENOENT') {
|
||||
// gracefully continue in case the file doesn't exist
|
||||
return '';
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
|
||||
if (!yarnRcContent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const parseResult = yarnRcSchema.safeParse(yaml.parse(yarnRcContent));
|
||||
|
||||
if (!parseResult.success) {
|
||||
throw new Error(
|
||||
`Unexpected content in .yarnrc.yml: ${parseResult.error.toString()}`,
|
||||
);
|
||||
}
|
||||
|
||||
const yarnRc = parseResult.data;
|
||||
|
||||
const backstagePlugin = yarnRc.plugins?.some(
|
||||
plugin => plugin.path === '.yarn/plugins/@yarnpkg/plugin-backstage.cjs',
|
||||
);
|
||||
|
||||
return Boolean(backstagePlugin);
|
||||
}
|
||||
+2
-2
@@ -17,12 +17,12 @@
|
||||
import {
|
||||
productionPack,
|
||||
revertProductionPack,
|
||||
} from '../../../../modules/build/lib/packager/productionPack';
|
||||
} from '../../lib/packager/productionPack';
|
||||
import { targetPaths } from '@backstage/cli-common';
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import { publishPreflightCheck } from '../../lib/publishing';
|
||||
import { createTypeDistProject } from '../../../../lib/typeDistProject';
|
||||
import { createTypeDistProject } from '../../lib/typeDistProject';
|
||||
|
||||
export const pre = async () => {
|
||||
publishPreflightCheck({
|
||||
@@ -19,11 +19,11 @@ import { resolve as resolvePath } from 'node:path';
|
||||
import {
|
||||
getModuleFederationRemoteOptions,
|
||||
serveBundle,
|
||||
} from '../../../../build/lib/bundler';
|
||||
} from '../../../lib/bundler';
|
||||
import { targetPaths } from '@backstage/cli-common';
|
||||
|
||||
import { BackstagePackageJson } from '@backstage/cli-node';
|
||||
import { hasReactDomClient } from '../../../../build/lib/bundler/hasReactDomClient';
|
||||
import { hasReactDomClient } from '../../../lib/bundler/hasReactDomClient';
|
||||
|
||||
interface StartAppOptions {
|
||||
verifyVersions?: boolean;
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
} from '@backstage/cli-node';
|
||||
import { buildFrontend } from '../../lib/buildFrontend';
|
||||
import { buildBackend } from '../../lib/buildBackend';
|
||||
import { createScriptOptionsParser } from '../../../../lib/optionsParser';
|
||||
import { createScriptOptionsParser } from '../../lib/optionsParser';
|
||||
|
||||
export async function command(opts: OptionValues, cmd: Command): Promise<void> {
|
||||
let packages = await PackageGraph.listTargetPackages();
|
||||
|
||||
@@ -16,8 +16,14 @@
|
||||
|
||||
import { Command, Option } from 'commander';
|
||||
import { createCliPlugin } from '../../wiring/factory';
|
||||
import { lazy } from '../../lib/lazy';
|
||||
import { configOption } from '../config';
|
||||
import { lazy } from '../../wiring/lazy';
|
||||
|
||||
const configOption = [
|
||||
'--config <path>',
|
||||
'Config files to load instead of app-config.yaml',
|
||||
(opt: string, opts: string[]) => (opts ? [...opts, opt] : [opt]),
|
||||
Array<string>(),
|
||||
] as const;
|
||||
|
||||
export function registerPackageCommands(command: Command) {
|
||||
command
|
||||
@@ -197,6 +203,58 @@ export const buildPlugin = createCliPlugin({
|
||||
},
|
||||
});
|
||||
|
||||
reg.addCommand({
|
||||
path: ['package', 'clean'],
|
||||
description: 'Delete cache directories',
|
||||
execute: async ({ args }) => {
|
||||
const command = new Command();
|
||||
const defaultCommand = command.action(
|
||||
lazy(() => import('./commands/package/clean'), 'default'),
|
||||
);
|
||||
|
||||
await defaultCommand.parseAsync(args, { from: 'user' });
|
||||
},
|
||||
});
|
||||
|
||||
reg.addCommand({
|
||||
path: ['package', 'prepack'],
|
||||
description: 'Prepares a package for packaging before publishing',
|
||||
execute: async ({ args }) => {
|
||||
const command = new Command();
|
||||
const defaultCommand = command.action(
|
||||
lazy(() => import('./commands/package/pack'), 'pre'),
|
||||
);
|
||||
|
||||
await defaultCommand.parseAsync(args, { from: 'user' });
|
||||
},
|
||||
});
|
||||
|
||||
reg.addCommand({
|
||||
path: ['package', 'postpack'],
|
||||
description: 'Restores the changes made by the prepack command',
|
||||
execute: async ({ args }) => {
|
||||
const command = new Command();
|
||||
const defaultCommand = command.action(
|
||||
lazy(() => import('./commands/package/pack'), 'post'),
|
||||
);
|
||||
|
||||
await defaultCommand.parseAsync(args, { from: 'user' });
|
||||
},
|
||||
});
|
||||
|
||||
reg.addCommand({
|
||||
path: ['repo', 'clean'],
|
||||
description: 'Delete cache and output directories',
|
||||
execute: async ({ args }) => {
|
||||
const command = new Command();
|
||||
const defaultCommand = command.action(
|
||||
lazy(() => import('./commands/repo/clean'), 'command'),
|
||||
);
|
||||
|
||||
await defaultCommand.parseAsync(args, { from: 'user' });
|
||||
},
|
||||
});
|
||||
|
||||
reg.addCommand({
|
||||
path: ['build-workspace'],
|
||||
description:
|
||||
|
||||
@@ -18,7 +18,7 @@ import fs from 'fs-extra';
|
||||
import { resolve as resolvePath } from 'node:path';
|
||||
import { buildBundle, getModuleFederationRemoteOptions } from './bundler';
|
||||
import { BackstagePackageJson } from '@backstage/cli-node';
|
||||
import { loadCliConfig } from '../../config/lib/config';
|
||||
import { loadCliConfig } from './config';
|
||||
|
||||
interface BuildAppOptions {
|
||||
targetDir: string;
|
||||
|
||||
@@ -32,7 +32,7 @@ import pickBy from 'lodash/pickBy';
|
||||
import { runOutput, targetPaths } from '@backstage/cli-common';
|
||||
|
||||
import { transforms } from './transforms';
|
||||
import { version } from '../../../../lib/version';
|
||||
import { version } from '../../../../wiring/version';
|
||||
import yn from 'yn';
|
||||
import { hasReactDomClient } from './hasReactDomClient';
|
||||
import { createWorkspaceLinkingPlugins } from './linkWorkspaces';
|
||||
|
||||
@@ -20,7 +20,7 @@ import { readEntryPoints } from '../entryPoints';
|
||||
import {
|
||||
createTypeDistProject,
|
||||
getEntryPointDefaultFeatureType,
|
||||
} from '../../../../lib/typeDistProject';
|
||||
} from '../typeDistProject';
|
||||
import {
|
||||
BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL,
|
||||
defaultRemoteSharedDependencies,
|
||||
|
||||
@@ -24,7 +24,7 @@ import { RspackDevServer } from '@rspack/dev-server';
|
||||
|
||||
import { targetPaths } from '@backstage/cli-common';
|
||||
|
||||
import { loadCliConfig } from '../../../config/lib/config';
|
||||
import { loadCliConfig } from '../config';
|
||||
import { createConfig, resolveBaseUrl, resolveEndpoint } from './config';
|
||||
import { createDetectedModulesEntryPoint } from './packageDetection';
|
||||
import { resolveBundlingPaths, resolveOptionalBundlingPaths } from './paths';
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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 { ConfigSources, loadConfigSchema } from '@backstage/config-loader';
|
||||
import { AppConfig, ConfigReader } from '@backstage/config';
|
||||
import { targetPaths } from '@backstage/cli-common';
|
||||
|
||||
import { getPackages } from '@manypkg/get-packages';
|
||||
import { PackageGraph } from '@backstage/cli-node';
|
||||
import { resolve as resolvePath } from 'node:path';
|
||||
|
||||
type Options = {
|
||||
args: string[];
|
||||
targetDir?: string;
|
||||
fromPackage?: string;
|
||||
withFilteredKeys?: boolean;
|
||||
watch?: (newFrontendAppConfigs: AppConfig[]) => void;
|
||||
};
|
||||
|
||||
export async function loadCliConfig(options: Options) {
|
||||
const targetDir = options.targetDir ?? targetPaths.dir;
|
||||
|
||||
const { packages } = await getPackages(targetDir);
|
||||
|
||||
let localPackageNames;
|
||||
if (options.fromPackage) {
|
||||
if (packages.length) {
|
||||
const graph = PackageGraph.fromPackages(packages);
|
||||
localPackageNames = Array.from(
|
||||
graph.collectPackageNames([options.fromPackage], node => {
|
||||
// Workaround for Backstage main repo only, since the CLI has some artificial devDependencies
|
||||
if (node.name === '@backstage/cli') {
|
||||
return undefined;
|
||||
}
|
||||
return node.localDependencies.keys();
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
localPackageNames = [options.fromPackage];
|
||||
}
|
||||
} else {
|
||||
localPackageNames = packages.map(p => p.packageJson.name);
|
||||
}
|
||||
|
||||
const schema = await loadConfigSchema({
|
||||
dependencies: localPackageNames,
|
||||
packagePaths: [targetPaths.resolveRoot('package.json')],
|
||||
});
|
||||
|
||||
const source = ConfigSources.default({
|
||||
allowMissingDefaultConfig: true,
|
||||
watch: Boolean(options.watch),
|
||||
rootDir: targetPaths.rootDir,
|
||||
argv: options.args.flatMap(t => ['--config', resolvePath(targetDir, t)]),
|
||||
});
|
||||
|
||||
const appConfigs = await new Promise<AppConfig[]>((resolve, reject) => {
|
||||
async function loadConfigReaderLoop() {
|
||||
let loaded = false;
|
||||
|
||||
try {
|
||||
const abortController = new AbortController();
|
||||
for await (const { configs } of source.readConfigData({
|
||||
signal: abortController.signal,
|
||||
})) {
|
||||
if (loaded) {
|
||||
const newFrontendAppConfigs = schema.process(configs, {
|
||||
visibility: ['frontend'],
|
||||
withFilteredKeys: options.withFilteredKeys,
|
||||
ignoreSchemaErrors: true,
|
||||
});
|
||||
options.watch?.(newFrontendAppConfigs);
|
||||
} else {
|
||||
resolve(configs);
|
||||
loaded = true;
|
||||
|
||||
if (!options.watch) {
|
||||
abortController.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (loaded) {
|
||||
console.error(`Failed to reload configuration, ${error}`);
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
loadConfigReaderLoop();
|
||||
});
|
||||
|
||||
const configurationLoadedMessage = appConfigs.length
|
||||
? `Loaded config from ${appConfigs.map(c => c.context).join(', ')}`
|
||||
: `No configuration files found, running without config`;
|
||||
|
||||
process.stderr.write(`${configurationLoadedMessage}\n`);
|
||||
|
||||
const frontendAppConfigs = schema.process(appConfigs, {
|
||||
visibility: ['frontend'],
|
||||
withFilteredKeys: options.withFilteredKeys,
|
||||
ignoreSchemaErrors: true,
|
||||
});
|
||||
const frontendConfig = ConfigReader.fromConfigs(frontendAppConfigs);
|
||||
|
||||
const fullConfig = ConfigReader.fromConfigs(appConfigs);
|
||||
|
||||
return {
|
||||
schema,
|
||||
appConfigs,
|
||||
frontendConfig,
|
||||
frontendAppConfigs,
|
||||
fullConfig,
|
||||
};
|
||||
}
|
||||
+2
-2
@@ -62,8 +62,8 @@ export function createScriptOptionsParser(
|
||||
// Triggers the writing of options to the result object
|
||||
cmd.parseOptions(argsStr.split(' '));
|
||||
|
||||
(cmd as any)._storeOptionsAsProperties = currentOpts;
|
||||
(cmd as any)._optionValues = currentStore;
|
||||
(cmd as any)._optionValues = currentOpts;
|
||||
(cmd as any)._storeOptionsAsProperties = currentStore;
|
||||
|
||||
return result;
|
||||
};
|
||||
@@ -44,7 +44,7 @@ import {
|
||||
PackageGraphNode,
|
||||
runConcurrentTasks,
|
||||
} from '@backstage/cli-node';
|
||||
import { createTypeDistProject } from '../../../../lib/typeDistProject';
|
||||
import { createTypeDistProject } from '../typeDistProject';
|
||||
|
||||
// These packages aren't safe to pack in parallel since the CLI depends on them
|
||||
const UNSAFE_PACKAGES = [
|
||||
|
||||
@@ -19,7 +19,7 @@ import npmPackList from 'npm-packlist';
|
||||
import { resolve as resolvePath, posix as posixPath } from 'node:path';
|
||||
import { BackstagePackageJson } from '@backstage/cli-node';
|
||||
import { readEntryPoints } from '../entryPoints';
|
||||
import { getEntryPointDefaultFeatureType } from '../../../../lib/typeDistProject';
|
||||
import { getEntryPointDefaultFeatureType } from '../typeDistProject';
|
||||
import { Project } from 'ts-morph';
|
||||
|
||||
const PKG_PATH = 'package.json';
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
import { createCliPlugin } from '../../wiring/factory';
|
||||
import yargs from 'yargs';
|
||||
import { Command } from 'commander';
|
||||
import { lazy } from '../../lib/lazy';
|
||||
import { lazy } from '../../wiring/lazy';
|
||||
|
||||
export const configOption = [
|
||||
'--config <path>',
|
||||
|
||||
@@ -27,11 +27,9 @@ type Options = {
|
||||
targetDir?: string;
|
||||
fromPackage?: string;
|
||||
mockEnv?: boolean;
|
||||
withFilteredKeys?: boolean;
|
||||
withDeprecatedKeys?: boolean;
|
||||
fullVisibility?: boolean;
|
||||
strict?: boolean;
|
||||
watch?: (newFrontendAppConfigs: AppConfig[]) => void;
|
||||
};
|
||||
|
||||
export async function loadCliConfig(options: Options) {
|
||||
@@ -73,48 +71,29 @@ export async function loadCliConfig(options: Options) {
|
||||
substitutionFunc: options.mockEnv
|
||||
? async name => process.env[name] || 'x'
|
||||
: undefined,
|
||||
watch: Boolean(options.watch),
|
||||
rootDir: targetPaths.rootDir,
|
||||
argv: options.args.flatMap(t => ['--config', resolvePath(targetDir, t)]),
|
||||
});
|
||||
|
||||
const appConfigs = await new Promise<AppConfig[]>((resolve, reject) => {
|
||||
async function loadConfigReaderLoop() {
|
||||
async function readConfig() {
|
||||
let loaded = false;
|
||||
|
||||
try {
|
||||
const abortController = new AbortController();
|
||||
for await (const { configs } of source.readConfigData({
|
||||
signal: abortController.signal,
|
||||
})) {
|
||||
if (loaded) {
|
||||
const newFrontendAppConfigs = schema.process(configs, {
|
||||
visibility: options.fullVisibility
|
||||
? ['frontend', 'backend', 'secret']
|
||||
: ['frontend'],
|
||||
withFilteredKeys: options.withFilteredKeys,
|
||||
withDeprecatedKeys: options.withDeprecatedKeys,
|
||||
ignoreSchemaErrors: !options.strict,
|
||||
});
|
||||
options.watch?.(newFrontendAppConfigs);
|
||||
} else {
|
||||
resolve(configs);
|
||||
loaded = true;
|
||||
|
||||
if (!options.watch) {
|
||||
abortController.abort();
|
||||
}
|
||||
}
|
||||
resolve(configs);
|
||||
loaded = true;
|
||||
abortController.abort();
|
||||
}
|
||||
} catch (error) {
|
||||
if (loaded) {
|
||||
console.error(`Failed to reload configuration, ${error}`);
|
||||
} else {
|
||||
if (!loaded) {
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
loadConfigReaderLoop();
|
||||
readConfig();
|
||||
});
|
||||
|
||||
const configurationLoadedMessage = appConfigs.length
|
||||
@@ -130,7 +109,6 @@ export async function loadCliConfig(options: Options) {
|
||||
visibility: options.fullVisibility
|
||||
? ['frontend', 'backend', 'secret']
|
||||
: ['frontend'],
|
||||
withFilteredKeys: options.withFilteredKeys,
|
||||
withDeprecatedKeys: options.withDeprecatedKeys,
|
||||
ignoreSchemaErrors: !options.strict,
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
import { createCliPlugin } from '../../wiring/factory';
|
||||
import { Command } from 'commander';
|
||||
import { lazy } from '../../lib/lazy';
|
||||
import { lazy } from '../../wiring/lazy';
|
||||
|
||||
export default createCliPlugin({
|
||||
pluginId: 'new',
|
||||
|
||||
@@ -17,8 +17,11 @@
|
||||
import { version as cliVersion } from '../../../../package.json';
|
||||
import os from 'node:os';
|
||||
import { runOutput, targetPaths, findOwnPaths } from '@backstage/cli-common';
|
||||
import { Lockfile } from '../../../lib/versioning';
|
||||
import { BackstagePackageJson, PackageGraph } from '@backstage/cli-node';
|
||||
import {
|
||||
BackstagePackageJson,
|
||||
Lockfile,
|
||||
PackageGraph,
|
||||
} from '@backstage/cli-node';
|
||||
import { minimatch } from 'minimatch';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
import yargs from 'yargs';
|
||||
import { createCliPlugin } from '../../wiring/factory';
|
||||
import { lazy } from '../../lib/lazy';
|
||||
import { lazy } from '../../wiring/lazy';
|
||||
|
||||
export default createCliPlugin({
|
||||
pluginId: 'info',
|
||||
|
||||
@@ -24,11 +24,11 @@ import {
|
||||
BackstagePackageJson,
|
||||
Lockfile,
|
||||
runWorkerQueueThreads,
|
||||
SuccessCache,
|
||||
} from '@backstage/cli-node';
|
||||
import { targetPaths } from '@backstage/cli-common';
|
||||
|
||||
import { createScriptOptionsParser } from '../../../../lib/optionsParser';
|
||||
import { SuccessCache } from '../../../../lib/cache/SuccessCache';
|
||||
import { createScriptOptionsParser } from '../../lib/optionsParser';
|
||||
|
||||
function depCount(pkg: BackstagePackageJson) {
|
||||
const deps = pkg.dependencies ? Object.keys(pkg.dependencies).length : 0;
|
||||
@@ -41,7 +41,10 @@ function depCount(pkg: BackstagePackageJson) {
|
||||
export async function command(opts: OptionValues, cmd: Command): Promise<void> {
|
||||
let packages = await PackageGraph.listTargetPackages();
|
||||
|
||||
const cache = new SuccessCache('lint', opts.successCacheDir);
|
||||
const cache = SuccessCache.create({
|
||||
name: 'lint',
|
||||
basePath: opts.successCacheDir,
|
||||
});
|
||||
const cacheContext = opts.successCache
|
||||
? {
|
||||
entries: await cache.read(),
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
import { createCliPlugin } from '../../wiring/factory';
|
||||
import { Command } from 'commander';
|
||||
import { lazy } from '../../lib/lazy';
|
||||
import { lazy } from '../../wiring/lazy';
|
||||
|
||||
export function registerPackageLintCommand(command: Command) {
|
||||
command.arguments('[directories...]');
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2024 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 { Command } from 'commander';
|
||||
|
||||
export function createScriptOptionsParser(
|
||||
anyCmd: Command,
|
||||
commandPath: string[],
|
||||
) {
|
||||
// Regardless of what command instance is passed in we want to find
|
||||
// the root command and resolve the path from there
|
||||
let rootCmd = anyCmd;
|
||||
while (rootCmd.parent) {
|
||||
rootCmd = rootCmd.parent;
|
||||
}
|
||||
|
||||
// Now find the command that was requested
|
||||
let targetCmd = rootCmd as Command | undefined;
|
||||
for (const name of commandPath) {
|
||||
targetCmd = targetCmd?.commands.find(c => c.name() === name) as
|
||||
| Command
|
||||
| undefined;
|
||||
}
|
||||
|
||||
if (!targetCmd) {
|
||||
throw new Error(
|
||||
`Could not find package command '${commandPath.join(' ')}'`,
|
||||
);
|
||||
}
|
||||
const cmd = targetCmd;
|
||||
|
||||
const expectedScript = `backstage-cli ${commandPath.join(' ')}`;
|
||||
|
||||
return (scriptStr?: string) => {
|
||||
if (!scriptStr || !scriptStr.startsWith(expectedScript)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const argsStr = scriptStr.slice(expectedScript.length).trim();
|
||||
|
||||
// Can't clone or copy or even use commands as prototype, so we mutate
|
||||
// the necessary members instead, and then reset them once we're done
|
||||
const currentOpts = (cmd as any)._optionValues;
|
||||
const currentStore = (cmd as any)._storeOptionsAsProperties;
|
||||
|
||||
const result: Record<string, any> = {};
|
||||
(cmd as any)._storeOptionsAsProperties = false;
|
||||
(cmd as any)._optionValues = result;
|
||||
|
||||
// Triggers the writing of options to the result object
|
||||
cmd.parseOptions(argsStr.split(' '));
|
||||
|
||||
(cmd as any)._optionValues = currentOpts;
|
||||
(cmd as any)._storeOptionsAsProperties = currentStore;
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
@@ -31,8 +31,6 @@ import {
|
||||
} from 'node:path';
|
||||
import { targetPaths } from '@backstage/cli-common';
|
||||
|
||||
import { publishPreflightCheck } from '../../lib/publishing';
|
||||
|
||||
const SCRIPT_EXTS = ['.js', '.jsx', '.ts', '.tsx', '.json'];
|
||||
|
||||
/**
|
||||
@@ -507,8 +505,6 @@ export async function command(opts: OptionValues): Promise<void> {
|
||||
fixPluginId,
|
||||
fixPluginPackages,
|
||||
fixPeerModules,
|
||||
// Run the publish preflight check too, to make sure we don't uncover errors during publishing
|
||||
publishPreflightCheck,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,50 +15,11 @@
|
||||
*/
|
||||
import { Command } from 'commander';
|
||||
import { createCliPlugin } from '../../wiring/factory';
|
||||
import { lazy } from '../../lib/lazy';
|
||||
import { lazy } from '../../wiring/lazy';
|
||||
|
||||
export default createCliPlugin({
|
||||
pluginId: 'maintenance',
|
||||
init: async reg => {
|
||||
reg.addCommand({
|
||||
path: ['package', 'clean'],
|
||||
description: 'Delete cache directories',
|
||||
execute: async ({ args }) => {
|
||||
const command = new Command();
|
||||
const defaultCommand = command.action(
|
||||
lazy(() => import('./commands/package/clean'), 'default'),
|
||||
);
|
||||
|
||||
await defaultCommand.parseAsync(args, { from: 'user' });
|
||||
},
|
||||
});
|
||||
|
||||
reg.addCommand({
|
||||
path: ['package', 'prepack'],
|
||||
description: 'Prepares a package for packaging before publishing',
|
||||
execute: async ({ args }) => {
|
||||
const command = new Command();
|
||||
const defaultCommand = command.action(
|
||||
lazy(() => import('./commands/package/pack'), 'pre'),
|
||||
);
|
||||
|
||||
await defaultCommand.parseAsync(args, { from: 'user' });
|
||||
},
|
||||
});
|
||||
|
||||
reg.addCommand({
|
||||
path: ['package', 'postpack'],
|
||||
description: 'Restores the changes made by the prepack command',
|
||||
execute: async ({ args }) => {
|
||||
const command = new Command();
|
||||
const defaultCommand = command.action(
|
||||
lazy(() => import('./commands/package/pack'), 'post'),
|
||||
);
|
||||
|
||||
await defaultCommand.parseAsync(args, { from: 'user' });
|
||||
},
|
||||
});
|
||||
|
||||
reg.addCommand({
|
||||
path: ['repo', 'fix'],
|
||||
description: 'Automatically fix packages in the project',
|
||||
@@ -79,19 +40,6 @@ export default createCliPlugin({
|
||||
},
|
||||
});
|
||||
|
||||
reg.addCommand({
|
||||
path: ['repo', 'clean'],
|
||||
description: 'Delete cache and output directories',
|
||||
execute: async ({ args }) => {
|
||||
const command = new Command();
|
||||
const defaultCommand = command.action(
|
||||
lazy(() => import('./commands/repo/clean'), 'command'),
|
||||
);
|
||||
|
||||
await defaultCommand.parseAsync(args, { from: 'user' });
|
||||
},
|
||||
});
|
||||
|
||||
reg.addCommand({
|
||||
path: ['repo', 'list-deprecations'],
|
||||
description: 'List deprecations',
|
||||
|
||||
@@ -14,21 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
fixPackageExports,
|
||||
readFixablePackages,
|
||||
writeFixedPackages,
|
||||
} from '../../maintenance/commands/repo/fix';
|
||||
|
||||
export async function command() {
|
||||
console.log(
|
||||
'The `migrate package-exports` command is deprecated, use `repo fix` instead.',
|
||||
throw new Error(
|
||||
'The `migrate package-exports` command has been removed, use `repo fix` instead.',
|
||||
);
|
||||
const packages = await readFixablePackages();
|
||||
|
||||
for (const pkg of packages) {
|
||||
fixPackageExports(pkg);
|
||||
}
|
||||
|
||||
await writeFixedPackages(packages);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import * as runObj from '@backstage/cli-common';
|
||||
import { overrideTargetPaths } from '@backstage/cli-common/testUtils';
|
||||
import bump, { bumpBackstageJsonVersion, createVersionFinder } from './bump';
|
||||
import { registerMswTestHooks, withLogCollector } from '@backstage/test-utils';
|
||||
import { YarnInfoInspectData } from '../../../../lib/versioning/packages';
|
||||
import { YarnInfoInspectData } from '../../lib/versioning/packages';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { rest } from 'msw';
|
||||
import { NotFoundError } from '@backstage/errors';
|
||||
@@ -69,8 +69,8 @@ jest.mock('@backstage/cli-common', () => {
|
||||
});
|
||||
|
||||
const mockFetchPackageInfo = jest.fn();
|
||||
jest.mock('../../../../lib/versioning/packages', () => {
|
||||
const actual = jest.requireActual('../../../../lib/versioning/packages');
|
||||
jest.mock('../../lib/versioning/packages', () => {
|
||||
const actual = jest.requireActual('../../lib/versioning/packages');
|
||||
return {
|
||||
...actual,
|
||||
fetchPackageInfo: (name: string) => mockFetchPackageInfo(name),
|
||||
|
||||
@@ -30,14 +30,16 @@ import { OptionValues } from 'commander';
|
||||
import { isError, NotFoundError } from '@backstage/errors';
|
||||
import { resolve as resolvePath } from 'node:path';
|
||||
|
||||
import { getHasYarnPlugin } from '../../../../lib/yarnPlugin';
|
||||
import {
|
||||
hasBackstageYarnPlugin,
|
||||
Lockfile,
|
||||
runConcurrentTasks,
|
||||
} from '@backstage/cli-node';
|
||||
import {
|
||||
fetchPackageInfo,
|
||||
Lockfile,
|
||||
mapDependencies,
|
||||
YarnInfoInspectData,
|
||||
} from '../../../../lib/versioning';
|
||||
import { runConcurrentTasks } from '@backstage/cli-node';
|
||||
} from '../../lib/versioning/packages';
|
||||
import {
|
||||
getManifestByReleaseLine,
|
||||
getManifestByVersion,
|
||||
@@ -74,7 +76,7 @@ function extendsDefaultPattern(pattern: string): boolean {
|
||||
export default async (opts: OptionValues) => {
|
||||
const lockfilePath = targetPaths.resolveRoot('yarn.lock');
|
||||
const lockfile = await Lockfile.load(lockfilePath);
|
||||
const hasYarnPlugin = await getHasYarnPlugin();
|
||||
const yarnPluginEnabled = await hasBackstageYarnPlugin();
|
||||
|
||||
let pattern = opts.pattern;
|
||||
|
||||
@@ -128,7 +130,7 @@ export default async (opts: OptionValues) => {
|
||||
});
|
||||
}
|
||||
|
||||
if (hasYarnPlugin) {
|
||||
if (yarnPluginEnabled) {
|
||||
console.log();
|
||||
console.log(
|
||||
`Updating yarn plugin to v${releaseManifest.releaseVersion}...`,
|
||||
@@ -212,7 +214,7 @@ export default async (opts: OptionValues) => {
|
||||
const oldLockfileRange = await asLockfileVersion(oldRange);
|
||||
|
||||
const useBackstageRange =
|
||||
hasYarnPlugin &&
|
||||
yarnPluginEnabled &&
|
||||
// Only use backstage:^ versions if the package is present in
|
||||
// the manifest for the release we're bumping to.
|
||||
releaseManifest.packages.find(
|
||||
@@ -252,7 +254,7 @@ export default async (opts: OptionValues) => {
|
||||
if (extendsDefaultPattern(pattern)) {
|
||||
await bumpBackstageJsonVersion(
|
||||
releaseManifest.releaseVersion,
|
||||
hasYarnPlugin,
|
||||
yarnPluginEnabled,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
@@ -317,7 +319,7 @@ export default async (opts: OptionValues) => {
|
||||
console.log();
|
||||
}
|
||||
|
||||
if (hasYarnPlugin) {
|
||||
if (yarnPluginEnabled) {
|
||||
console.log();
|
||||
console.log(
|
||||
chalk.blue(
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
import { createCliPlugin } from '../../wiring/factory';
|
||||
import { Command } from 'commander';
|
||||
import { lazy } from '../../lib/lazy';
|
||||
import { lazy } from '../../wiring/lazy';
|
||||
|
||||
export default createCliPlugin({
|
||||
pluginId: 'migrate',
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
import { createCliPlugin } from '../../wiring/factory';
|
||||
import { Command } from 'commander';
|
||||
import { lazy } from '../../lib/lazy';
|
||||
import { lazy } from '../../wiring/lazy';
|
||||
import { NotImplementedError } from '@backstage/errors';
|
||||
|
||||
export default createCliPlugin({
|
||||
|
||||
@@ -24,11 +24,11 @@ import startCase from 'lodash/startCase';
|
||||
import upperCase from 'lodash/upperCase';
|
||||
import upperFirst from 'lodash/upperFirst';
|
||||
import lowerFirst from 'lodash/lowerFirst';
|
||||
import { Lockfile } from '../../../../lib/versioning';
|
||||
import { Lockfile } from '@backstage/cli-node';
|
||||
import { targetPaths } from '@backstage/cli-common';
|
||||
|
||||
import { createPackageVersionProvider } from '../../../../lib/version';
|
||||
import { getHasYarnPlugin } from '../../../../lib/yarnPlugin';
|
||||
import { createPackageVersionProvider } from '../version';
|
||||
import { hasBackstageYarnPlugin } from '@backstage/cli-node';
|
||||
|
||||
const builtInHelpers = {
|
||||
camelCase,
|
||||
@@ -55,9 +55,9 @@ export class PortableTemplater {
|
||||
/* ignored */
|
||||
}
|
||||
|
||||
const hasYarnPlugin = await getHasYarnPlugin();
|
||||
const yarnPluginEnabled = await hasBackstageYarnPlugin();
|
||||
const versionProvider = createPackageVersionProvider(lockfile, {
|
||||
preferBackstageProtocol: hasYarnPlugin,
|
||||
preferBackstageProtocol: yarnPluginEnabled,
|
||||
});
|
||||
|
||||
const templater = new PortableTemplater(
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { packageVersions, createPackageVersionProvider } from './version';
|
||||
import { Lockfile } from './versioning';
|
||||
import { Lockfile } from '@backstage/cli-node';
|
||||
import corePluginApiPkg from '@backstage/core-plugin-api/package.json';
|
||||
import { createMockDirectory } from '@backstage/backend-test-utils';
|
||||
|
||||
@@ -14,13 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import semver from 'semver';
|
||||
import { findOwnPaths } from '@backstage/cli-common';
|
||||
import { Lockfile } from './versioning';
|
||||
|
||||
/* eslint-disable-next-line no-restricted-syntax */
|
||||
const ownPaths = findOwnPaths(__dirname);
|
||||
import { Lockfile } from '@backstage/cli-node';
|
||||
|
||||
/* eslint-disable @backstage/no-relative-monorepo-imports */
|
||||
/*
|
||||
@@ -35,28 +30,28 @@ This does not create an actual dependency on these packages and does not bring i
|
||||
Rollup will extract the value of the version field in each package at build time without
|
||||
leaving any imports in place.
|
||||
*/
|
||||
import { version as backendPluginApi } from '../../../../packages/backend-plugin-api/package.json';
|
||||
import { version as backendTestUtils } from '../../../../packages/backend-test-utils/package.json';
|
||||
import { version as catalogClient } from '../../../../packages/catalog-client/package.json';
|
||||
import { version as cli } from '../../../../packages/cli/package.json';
|
||||
import { version as config } from '../../../../packages/config/package.json';
|
||||
import { version as coreAppApi } from '../../../../packages/core-app-api/package.json';
|
||||
import { version as coreComponents } from '../../../../packages/core-components/package.json';
|
||||
import { version as corePluginApi } from '../../../../packages/core-plugin-api/package.json';
|
||||
import { version as devUtils } from '../../../../packages/dev-utils/package.json';
|
||||
import { version as errors } from '../../../../packages/errors/package.json';
|
||||
import { version as frontendDefaults } from '../../../../packages/frontend-defaults/package.json';
|
||||
import { version as frontendPluginApi } from '../../../../packages/frontend-plugin-api/package.json';
|
||||
import { version as frontendTestUtils } from '../../../../packages/frontend-test-utils/package.json';
|
||||
import { version as testUtils } from '../../../../packages/test-utils/package.json';
|
||||
import { version as scaffolderNode } from '../../../../plugins/scaffolder-node/package.json';
|
||||
import { version as scaffolderNodeTestUtils } from '../../../../plugins/scaffolder-node-test-utils/package.json';
|
||||
import { version as authBackend } from '../../../../plugins/auth-backend/package.json';
|
||||
import { version as authBackendModuleGuestProvider } from '../../../../plugins/auth-backend-module-guest-provider/package.json';
|
||||
import { version as catalogNode } from '../../../../plugins/catalog-node/package.json';
|
||||
import { version as theme } from '../../../../packages/theme/package.json';
|
||||
import { version as types } from '../../../../packages/types/package.json';
|
||||
import { version as backendDefaults } from '../../../../packages/backend-defaults/package.json';
|
||||
import { version as backendPluginApi } from '../../../../../../packages/backend-plugin-api/package.json';
|
||||
import { version as backendTestUtils } from '../../../../../../packages/backend-test-utils/package.json';
|
||||
import { version as catalogClient } from '../../../../../../packages/catalog-client/package.json';
|
||||
import { version as cli } from '../../../../../../packages/cli/package.json';
|
||||
import { version as config } from '../../../../../../packages/config/package.json';
|
||||
import { version as coreAppApi } from '../../../../../../packages/core-app-api/package.json';
|
||||
import { version as coreComponents } from '../../../../../../packages/core-components/package.json';
|
||||
import { version as corePluginApi } from '../../../../../../packages/core-plugin-api/package.json';
|
||||
import { version as devUtils } from '../../../../../../packages/dev-utils/package.json';
|
||||
import { version as errors } from '../../../../../../packages/errors/package.json';
|
||||
import { version as frontendDefaults } from '../../../../../../packages/frontend-defaults/package.json';
|
||||
import { version as frontendPluginApi } from '../../../../../../packages/frontend-plugin-api/package.json';
|
||||
import { version as frontendTestUtils } from '../../../../../../packages/frontend-test-utils/package.json';
|
||||
import { version as testUtils } from '../../../../../../packages/test-utils/package.json';
|
||||
import { version as scaffolderNode } from '../../../../../../plugins/scaffolder-node/package.json';
|
||||
import { version as scaffolderNodeTestUtils } from '../../../../../../plugins/scaffolder-node-test-utils/package.json';
|
||||
import { version as authBackend } from '../../../../../../plugins/auth-backend/package.json';
|
||||
import { version as authBackendModuleGuestProvider } from '../../../../../../plugins/auth-backend-module-guest-provider/package.json';
|
||||
import { version as catalogNode } from '../../../../../../plugins/catalog-node/package.json';
|
||||
import { version as theme } from '../../../../../../packages/theme/package.json';
|
||||
import { version as types } from '../../../../../../packages/types/package.json';
|
||||
import { version as backendDefaults } from '../../../../../../packages/backend-defaults/package.json';
|
||||
|
||||
export const packageVersions: Record<string, string> = {
|
||||
'@backstage/backend-defaults': backendDefaults,
|
||||
@@ -84,14 +79,6 @@ export const packageVersions: Record<string, string> = {
|
||||
'@backstage/plugin-catalog-node': catalogNode,
|
||||
};
|
||||
|
||||
export function findVersion() {
|
||||
const pkgContent = fs.readFileSync(ownPaths.resolve('package.json'), 'utf8');
|
||||
return JSON.parse(pkgContent).version;
|
||||
}
|
||||
|
||||
export const version = findVersion();
|
||||
export const isDev = fs.pathExistsSync(ownPaths.resolve('src'));
|
||||
|
||||
export function createPackageVersionProvider(
|
||||
lockfile?: Lockfile,
|
||||
options?: {
|
||||
@@ -22,7 +22,7 @@ import yargs from 'yargs';
|
||||
import { run as runJest, yargsOptions as jestYargsOptions } from 'jest-cli';
|
||||
import { relative as relativePath } from 'node:path';
|
||||
import { Command, OptionValues } from 'commander';
|
||||
import { Lockfile, PackageGraph } from '@backstage/cli-node';
|
||||
import { Lockfile, PackageGraph, SuccessCache } from '@backstage/cli-node';
|
||||
|
||||
import {
|
||||
runCheck,
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
findOwnPaths,
|
||||
isChildPath,
|
||||
} from '@backstage/cli-common';
|
||||
import { SuccessCache } from '../../../../lib/cache/SuccessCache';
|
||||
|
||||
type JestProject = {
|
||||
displayName: string;
|
||||
@@ -333,7 +332,10 @@ export async function command(opts: OptionValues, cmd: Command): Promise<void> {
|
||||
);
|
||||
}
|
||||
|
||||
const cache = new SuccessCache('test', opts.successCacheDir);
|
||||
const cache = SuccessCache.create({
|
||||
name: 'test',
|
||||
basePath: opts.successCacheDir,
|
||||
});
|
||||
const graph = await getPackageGraph();
|
||||
|
||||
// Shared state for the bridge
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
import { createCliPlugin } from '../../wiring/factory';
|
||||
import { Command } from 'commander';
|
||||
import { lazy } from '../../lib/lazy';
|
||||
import { lazy } from '../../wiring/lazy';
|
||||
|
||||
export default createCliPlugin({
|
||||
pluginId: 'test',
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
import yargs from 'yargs';
|
||||
import { createCliPlugin } from '../../wiring/factory';
|
||||
import { lazy } from '../../lib/lazy';
|
||||
import { lazy } from '../../wiring/lazy';
|
||||
import { DEFAULT_MESSAGE_PATTERN } from './lib/messageFilePath';
|
||||
|
||||
export default createCliPlugin({
|
||||
|
||||
@@ -18,9 +18,9 @@ import { CommandGraph } from './CommandGraph';
|
||||
import { CliFeature, OpaqueCliPlugin } from './types';
|
||||
import { CommandRegistry } from './CommandRegistry';
|
||||
import { Command } from 'commander';
|
||||
import { version } from '../lib/version';
|
||||
import { version } from './version';
|
||||
import chalk from 'chalk';
|
||||
import { exitWithError } from '../lib/errors';
|
||||
import { exitWithError } from './errors';
|
||||
import { ForwardedError } from '@backstage/errors';
|
||||
import { isPromise } from 'node:util/types';
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { assertError } from '@backstage/errors';
|
||||
import { exitWithError } from '../lib/errors';
|
||||
import { exitWithError } from './errors';
|
||||
|
||||
type ActionFunc = (...args: any[]) => Promise<void>;
|
||||
type ActionExports<TModule extends object> = {
|
||||
@@ -14,6 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { Lockfile } from './Lockfile';
|
||||
export { fetchPackageInfo, mapDependencies } from './packages';
|
||||
export type { YarnInfoInspectData } from './packages';
|
||||
import fs from 'fs-extra';
|
||||
import { findOwnPaths } from '@backstage/cli-common';
|
||||
|
||||
/* eslint-disable-next-line no-restricted-syntax */
|
||||
const ownPaths = findOwnPaths(__dirname);
|
||||
|
||||
export function findVersion() {
|
||||
const pkgContent = fs.readFileSync(ownPaths.resolve('package.json'), 'utf8');
|
||||
return JSON.parse(pkgContent).version;
|
||||
}
|
||||
|
||||
export const version = findVersion();
|
||||
export const isDev = fs.pathExistsSync(ownPaths.resolve('src'));
|
||||
Reference in New Issue
Block a user