Merge pull request #13519 from backstage/rugvip/lernadep
cli: remove last dependencies on Lerna
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Removed internal dependencies on Lerna. It is now no longer necessary to have Lerna installed in a project to use all features of the Backstage CLI.
|
||||
+1
-1
@@ -23,7 +23,7 @@
|
||||
"lint:all": "backstage-cli repo lint",
|
||||
"lint:type-deps": "node scripts/check-type-dependencies.js",
|
||||
"docker-build": "yarn tsc && yarn workspace example-backend build --build-dependencies && yarn workspace example-backend build-image",
|
||||
"new": "backstage-cli new --scope backstage --no-private",
|
||||
"new": "backstage-cli new --scope backstage --baseVersion 0.0.0 --no-private",
|
||||
"create-plugin": "echo \"use 'yarn new' instead\"",
|
||||
"release": "node scripts/prepare-release.js && changeset version && yarn diff --yes && yarn prettier --write '{packages,plugins}/*/{package.json,CHANGELOG.md}' '.changeset/*.json' && yarn install --no-immutable",
|
||||
"prettier:check": "prettier --check .",
|
||||
|
||||
@@ -179,6 +179,7 @@ Options:
|
||||
--option <name>=<value>
|
||||
--scope <scope>
|
||||
--npm-registry <URL>
|
||||
--baseVersion <version>
|
||||
--no-private
|
||||
-h, --help
|
||||
```
|
||||
|
||||
@@ -301,6 +301,7 @@ export default async (opts: OptionValues) => {
|
||||
npmRegistry,
|
||||
},
|
||||
createPackageVersionProvider(lockfile),
|
||||
isMonoRepo,
|
||||
);
|
||||
|
||||
Task.section('Moving to final location');
|
||||
|
||||
@@ -218,6 +218,10 @@ export function registerCommands(program: Command) {
|
||||
'--npm-registry <URL>',
|
||||
'The package registry to use for new packages',
|
||||
)
|
||||
.option(
|
||||
'--baseVersion <version>',
|
||||
'The version to use for any new packages (default: 0.1.0)',
|
||||
)
|
||||
.option('--no-private', 'Do not mark new packages as private')
|
||||
.action(lazy(() => import('./new/new').then(m => m.default)));
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import { FactoryRegistry } from '../../lib/new/FactoryRegistry';
|
||||
import { paths } from '../../lib/paths';
|
||||
import { assertError } from '@backstage/errors';
|
||||
import { Task } from '../../lib/tasks';
|
||||
import { isMonoRepo } from '../../lib/monorepo/isMonoRepo';
|
||||
|
||||
function parseOptions(optionStrings: string[]): Record<string, string> {
|
||||
const options: Record<string, string> = {};
|
||||
@@ -49,33 +50,16 @@ export default async (opts: OptionValues) => {
|
||||
providedOptions,
|
||||
);
|
||||
|
||||
let isMonoRepo = false;
|
||||
try {
|
||||
const rootPackageJson = await fs.readJson(
|
||||
paths.resolveTargetRoot('package.json'),
|
||||
);
|
||||
if (rootPackageJson.workspaces) {
|
||||
isMonoRepo = true;
|
||||
}
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
if (error.code !== 'ENOENT') {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
let defaultVersion = '0.1.0';
|
||||
try {
|
||||
const rootLernaJson = await fs.readJson(
|
||||
paths.resolveTargetRoot('lerna.json'),
|
||||
);
|
||||
if (rootLernaJson.version) {
|
||||
defaultVersion = rootLernaJson.version;
|
||||
}
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
if (error.code !== 'ENOENT') {
|
||||
throw error;
|
||||
if (opts.baseVersion) {
|
||||
defaultVersion = opts.baseVersion;
|
||||
} else {
|
||||
const lernaVersion = await fs
|
||||
.readJson(paths.resolveTargetRoot('lerna.json'))
|
||||
.then(pkg => pkg.version)
|
||||
.catch(() => undefined);
|
||||
if (lernaVersion) {
|
||||
defaultVersion = lernaVersion;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +73,7 @@ export default async (opts: OptionValues) => {
|
||||
let modified = false;
|
||||
try {
|
||||
await factory.create(options, {
|
||||
isMonoRepo,
|
||||
isMonoRepo: await isMonoRepo(),
|
||||
defaultVersion,
|
||||
scope: opts.scope?.replace(/^@/, ''),
|
||||
npmRegistry: opts.npmRegistry,
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2022 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 '../paths';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
export async function isMonoRepo(): Promise<boolean> {
|
||||
const rootPackageJsonPath = paths.resolveTargetRoot('package.json');
|
||||
try {
|
||||
const pkg = await fs.readJson(rootPackageJsonPath);
|
||||
return Boolean(pkg?.workspaces?.packages);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2022 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 { isMonoRepo } from './isMonoRepo';
|
||||
import mockFs from 'mock-fs';
|
||||
|
||||
describe('isMonoRepo', () => {
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it('should detect a monorepo', async () => {
|
||||
mockFs({
|
||||
'package.json': JSON.stringify({
|
||||
name: 'foo',
|
||||
workspaces: {
|
||||
packages: ['packages/*'],
|
||||
},
|
||||
}),
|
||||
});
|
||||
await expect(isMonoRepo()).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it('should detect a non- monorepo', async () => {
|
||||
mockFs({
|
||||
'package.json': JSON.stringify({
|
||||
name: 'foo',
|
||||
}),
|
||||
});
|
||||
await expect(isMonoRepo()).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if package.json is missing', async () => {
|
||||
mockFs({});
|
||||
await expect(isMonoRepo()).resolves.toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -62,6 +62,7 @@ export async function executePluginPackageTemplate(
|
||||
tempDir,
|
||||
options.values,
|
||||
createPackageVersionProvider(lockfile),
|
||||
ctx.isMonoRepo,
|
||||
);
|
||||
|
||||
// Format package.json if it exists
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
} from '../builder';
|
||||
import { copyPackageDist } from './copyPackageDist';
|
||||
import { getRoleInfo } from '../role';
|
||||
import { runParallelWorkers } from '../parallel';
|
||||
|
||||
// These packages aren't safe to pack in parallel since the CLI depends on them
|
||||
const UNSAFE_PACKAGES = [
|
||||
@@ -88,6 +89,14 @@ type Options = {
|
||||
skeleton?: 'skeleton.tar' | 'skeleton.tar.gz';
|
||||
};
|
||||
|
||||
function prefixLogFunc(prefix: string, out: 'stdout' | 'stderr') {
|
||||
return (data: Buffer) => {
|
||||
for (const line of data.toString('utf8').split(/\r?\n/)) {
|
||||
process[out].write(`${prefix} ${line}\n`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses `yarn pack` to package local packages and unpacks them into a dist workspace.
|
||||
* The target workspace will end up containing dist version of each package and
|
||||
@@ -124,7 +133,7 @@ export async function createDistWorkspace(
|
||||
);
|
||||
|
||||
const standardBuilds = new Array<BuildOptions>();
|
||||
const customBuild = new Array<string>();
|
||||
const customBuild = new Array<{ dir: string; name: string }>();
|
||||
|
||||
for (const pkg of packages) {
|
||||
if (!toBuild.has(pkg.packageJson.name)) {
|
||||
@@ -135,13 +144,13 @@ export async function createDistWorkspace(
|
||||
console.warn(
|
||||
`Building ${pkg.packageJson.name} separately because it has no role`,
|
||||
);
|
||||
customBuild.push(pkg.packageJson.name);
|
||||
customBuild.push({ dir: pkg.dir, name: pkg.packageJson.name });
|
||||
continue;
|
||||
}
|
||||
|
||||
const buildScript = pkg.packageJson.scripts?.build;
|
||||
if (!buildScript) {
|
||||
customBuild.push(pkg.packageJson.name);
|
||||
customBuild.push({ dir: pkg.dir, name: pkg.packageJson.name });
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -149,7 +158,7 @@ export async function createDistWorkspace(
|
||||
console.warn(
|
||||
`Building ${pkg.packageJson.name} separately because it has a custom build script, '${buildScript}'`,
|
||||
);
|
||||
customBuild.push(pkg.packageJson.name);
|
||||
customBuild.push({ dir: pkg.dir, name: pkg.packageJson.name });
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -157,7 +166,7 @@ export async function createDistWorkspace(
|
||||
console.warn(
|
||||
`Building ${pkg.packageJson.name} separately because it is a bundled package`,
|
||||
);
|
||||
customBuild.push(pkg.packageJson.name);
|
||||
customBuild.push({ dir: pkg.dir, name: pkg.packageJson.name });
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -181,14 +190,15 @@ export async function createDistWorkspace(
|
||||
await buildPackages(standardBuilds);
|
||||
|
||||
if (customBuild.length > 0) {
|
||||
const scopeArgs = customBuild.flatMap(name => ['--scope', name]);
|
||||
const lernaArgs =
|
||||
options.parallelism && Number.isInteger(options.parallelism)
|
||||
? ['--concurrency', options.parallelism.toString()]
|
||||
: [];
|
||||
|
||||
await run('yarn', ['lerna', ...lernaArgs, 'run', ...scopeArgs, 'build'], {
|
||||
cwd: paths.targetRoot,
|
||||
await runParallelWorkers({
|
||||
items: customBuild,
|
||||
worker: async ({ name, dir }) => {
|
||||
await run('yarn', ['run', 'build'], {
|
||||
cwd: dir,
|
||||
stdoutLogFunc: prefixLogFunc(`${name}: `, 'stdout'),
|
||||
stderrLogFunc: prefixLogFunc(`${name}: `, 'stderr'),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ describe('templatingTask', () => {
|
||||
pluginVersion: '0.0.0',
|
||||
},
|
||||
() => '^0.1.2',
|
||||
true,
|
||||
);
|
||||
|
||||
await expect(
|
||||
|
||||
@@ -22,7 +22,6 @@ import { promisify } from 'util';
|
||||
import { basename, dirname } from 'path';
|
||||
import recursive from 'recursive-readdir';
|
||||
import { exec as execCb } from 'child_process';
|
||||
import { paths } from './paths';
|
||||
import { assertError } from '@backstage/errors';
|
||||
|
||||
const exec = promisify(execCb);
|
||||
@@ -102,11 +101,11 @@ export async function templatingTask(
|
||||
destinationDir: string,
|
||||
context: any,
|
||||
versionProvider: (name: string, versionHint?: string) => string,
|
||||
isMonoRepo: boolean,
|
||||
) {
|
||||
const files = await recursive(templateDir).catch(error => {
|
||||
throw new Error(`Failed to read template directory: ${error.message}`);
|
||||
});
|
||||
const isMonoRepo = await fs.pathExists(paths.resolveTargetRoot('lerna.json'));
|
||||
|
||||
for (const file of files) {
|
||||
const destinationFile = file.replace(templateDir, destinationDir);
|
||||
|
||||
Reference in New Issue
Block a user