Merge pull request #9133 from backstage/rugvip/role
cli: add support for package role + script & migration utils
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Introduced initial support for an experimental `backstage.role` field in package.json, as well as experimental and hidden `migrate` and `script` sub-commands. We do not recommend usage of any of these additions yet.
|
||||
@@ -112,7 +112,8 @@
|
||||
"webpack-node-externals": "^3.0.0",
|
||||
"yaml": "^1.10.0",
|
||||
"yml-loader": "^2.1.0",
|
||||
"yn": "^4.0.0"
|
||||
"yn": "^4.0.0",
|
||||
"zod": "^3.11.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-common": "^0.10.6",
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 { buildBundle } from '../../lib/bundler';
|
||||
import { parseParallel, PARALLEL_ENV_VAR } from '../../lib/parallel';
|
||||
import { loadCliConfig } from '../../lib/config';
|
||||
import { paths } from '../../lib/paths';
|
||||
|
||||
interface BuildAppOptions {
|
||||
writeStats: boolean;
|
||||
configPaths: string[];
|
||||
}
|
||||
|
||||
export async function buildApp(options: BuildAppOptions) {
|
||||
const { name } = await fs.readJson(paths.resolveTarget('package.json'));
|
||||
await buildBundle({
|
||||
entry: 'src/index',
|
||||
parallel: parseParallel(process.env[PARALLEL_ENV_VAR]),
|
||||
statsJsonEnabled: options.writeStats,
|
||||
...(await loadCliConfig({
|
||||
args: options.configPaths,
|
||||
fromPackage: name,
|
||||
})),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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 os from 'os';
|
||||
import fs from 'fs-extra';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import tar, { CreateOptions } from 'tar';
|
||||
import { createDistWorkspace } from '../../lib/packager';
|
||||
import { paths } from '../../lib/paths';
|
||||
import { parseParallel, PARALLEL_ENV_VAR } from '../../lib/parallel';
|
||||
import { buildPackage, Output } from '../../lib/builder';
|
||||
|
||||
const BUNDLE_FILE = 'bundle.tar.gz';
|
||||
const SKELETON_FILE = 'skeleton.tar.gz';
|
||||
|
||||
interface BuildBackendOptions {
|
||||
skipBuildDependencies: boolean;
|
||||
}
|
||||
|
||||
export async function buildBackend(options: BuildBackendOptions) {
|
||||
const targetDir = paths.resolveTarget('dist');
|
||||
const pkg = await fs.readJson(paths.resolveTarget('package.json'));
|
||||
|
||||
// We build the target package without generating type declarations.
|
||||
await buildPackage({ outputs: new Set([Output.cjs]) });
|
||||
|
||||
const tmpDir = await fs.mkdtemp(resolvePath(os.tmpdir(), 'backstage-bundle'));
|
||||
try {
|
||||
await createDistWorkspace([pkg.name], {
|
||||
targetDir: tmpDir,
|
||||
buildDependencies: !options.skipBuildDependencies,
|
||||
buildExcludes: [pkg.name],
|
||||
parallel: parseParallel(process.env[PARALLEL_ENV_VAR]),
|
||||
skeleton: SKELETON_FILE,
|
||||
});
|
||||
|
||||
// We built the target backend package using the regular build process, but the result of
|
||||
// that has now been packed into the dist workspace, so clean up the dist dir.
|
||||
await fs.remove(targetDir);
|
||||
await fs.mkdir(targetDir);
|
||||
|
||||
// Move out skeleton.tar.gz before we create the main bundle, no point having that included up twice.
|
||||
await fs.move(
|
||||
resolvePath(tmpDir, SKELETON_FILE),
|
||||
resolvePath(targetDir, SKELETON_FILE),
|
||||
);
|
||||
|
||||
// Create main bundle.tar.gz, with some tweaks to make it more likely hit Docker build cache.
|
||||
await tar.create(
|
||||
{
|
||||
file: resolvePath(targetDir, BUNDLE_FILE),
|
||||
cwd: tmpDir,
|
||||
portable: true,
|
||||
noMtime: true,
|
||||
gzip: true,
|
||||
} as CreateOptions & { noMtime: boolean },
|
||||
[''],
|
||||
);
|
||||
} finally {
|
||||
await fs.remove(tmpDir);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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 { Command } from 'commander';
|
||||
import { buildPackage, Output } from '../../lib/builder';
|
||||
import { findRoleFromCommand, getRoleInfo } from '../../lib/role';
|
||||
import { buildApp } from './buildApp';
|
||||
import { buildBackend } from './buildBackend';
|
||||
|
||||
export async function command(cmd: Command): Promise<void> {
|
||||
const role = await findRoleFromCommand(cmd);
|
||||
|
||||
if (role === 'app') {
|
||||
return buildApp({
|
||||
configPaths: cmd.config as string[],
|
||||
writeStats: Boolean(cmd.stats),
|
||||
});
|
||||
}
|
||||
if (role === 'backend') {
|
||||
return buildBackend({
|
||||
skipBuildDependencies: Boolean(cmd.skipBuildDependencies),
|
||||
});
|
||||
}
|
||||
|
||||
const roleInfo = getRoleInfo(role);
|
||||
|
||||
const outputs = new Set<Output>();
|
||||
|
||||
if (roleInfo.output.includes('cjs')) {
|
||||
outputs.add(Output.cjs);
|
||||
}
|
||||
if (roleInfo.output.includes('esm')) {
|
||||
outputs.add(Output.esm);
|
||||
}
|
||||
if (roleInfo.output.includes('types')) {
|
||||
outputs.add(Output.types);
|
||||
}
|
||||
|
||||
return buildPackage({
|
||||
outputs,
|
||||
minify: Boolean(cmd.minify),
|
||||
useApiExtractor: Boolean(cmd.experimentalTypeBuild),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { command } from './command';
|
||||
@@ -18,14 +18,112 @@ import { assertError } from '@backstage/errors';
|
||||
import { CommanderStatic } from 'commander';
|
||||
import { exitWithError } from '../lib/errors';
|
||||
|
||||
export function registerCommands(program: CommanderStatic) {
|
||||
const configOption = [
|
||||
'--config <path>',
|
||||
'Config files to load instead of app-config.yaml',
|
||||
(opt: string, opts: string[]) => [...opts, opt],
|
||||
Array<string>(),
|
||||
] as const;
|
||||
const configOption = [
|
||||
'--config <path>',
|
||||
'Config files to load instead of app-config.yaml',
|
||||
(opt: string, opts: string[]) => [...opts, opt],
|
||||
Array<string>(),
|
||||
] as const;
|
||||
|
||||
export function registerScriptCommand(program: CommanderStatic) {
|
||||
const command = program
|
||||
.command('script [command]', { hidden: true })
|
||||
.description('Lifecycle scripts for Backstage packages [EXPERIMENTAL]');
|
||||
|
||||
command
|
||||
.command('start')
|
||||
.description('Start a package for local development')
|
||||
.option(...configOption)
|
||||
.option('--role <name>', 'Run the command with an explicit package role')
|
||||
.option('--check', 'Enable type checking and linting if available')
|
||||
.option('--inspect', 'Enable debugger in Node.js environments')
|
||||
.option(
|
||||
'--inspect-brk',
|
||||
'Enable debugger in Node.js environments, breaking before code starts',
|
||||
)
|
||||
.action(lazy(() => import('./start').then(m => m.command)));
|
||||
|
||||
command
|
||||
.command('build')
|
||||
.description('Build a package for production deployment or publishing')
|
||||
.option(
|
||||
'--minify',
|
||||
'Minify the generated code. Does not apply to app or backend packages.',
|
||||
)
|
||||
.option(
|
||||
'--experimental-type-build',
|
||||
'Enable experimental type build. Does not apply to app or backend packages.',
|
||||
)
|
||||
.option(
|
||||
'--skip-build-dependencies',
|
||||
'Skip the automatic building of local dependencies. Applies to backend packages only.',
|
||||
)
|
||||
.option(
|
||||
'--stats',
|
||||
'If bundle stats are available, write them to the output directory. Applies to app packages only.',
|
||||
)
|
||||
.option(
|
||||
'--config <path>',
|
||||
'Config files to load instead of app-config.yaml. Applies to app packages only.',
|
||||
(opt: string, opts: string[]) => [...opts, opt],
|
||||
Array<string>(),
|
||||
)
|
||||
.action(lazy(() => import('./build').then(m => m.command)));
|
||||
|
||||
program
|
||||
.command('lint')
|
||||
.option(
|
||||
'--format <format>',
|
||||
'Lint report output format',
|
||||
'eslint-formatter-friendly',
|
||||
)
|
||||
.option('--fix', 'Attempt to automatically fix violations')
|
||||
.description('Lint a package')
|
||||
.action(lazy(() => import('./lint').then(m => m.default)));
|
||||
|
||||
program
|
||||
.command('test')
|
||||
.allowUnknownOption(true) // Allows the command to run, but we still need to parse raw args
|
||||
.helpOption(', --backstage-cli-help') // Let Jest handle help
|
||||
.description('Run tests, forwarding args to Jest, defaulting to watch mode')
|
||||
.action(lazy(() => import('./testCommand').then(m => m.default)));
|
||||
|
||||
command
|
||||
.command('clean')
|
||||
.description('Delete cache directories')
|
||||
.action(lazy(() => import('./clean/clean').then(m => m.default)));
|
||||
|
||||
command
|
||||
.command('prepack')
|
||||
.description('Prepares a package for packaging before publishing')
|
||||
.action(lazy(() => import('./pack').then(m => m.pre)));
|
||||
|
||||
command
|
||||
.command('postpack')
|
||||
.description('Restores the changes made by the prepack command')
|
||||
.action(lazy(() => import('./pack').then(m => m.post)));
|
||||
}
|
||||
|
||||
export function registerMigrateCommand(program: CommanderStatic) {
|
||||
const command = program
|
||||
.command('migrate [command]', { hidden: true })
|
||||
.description('Migration utilities [EXPERIMENTAL]');
|
||||
|
||||
command
|
||||
.command('package-role')
|
||||
.description(`Add package role field to packages that don't have it`)
|
||||
.action(lazy(() => import('./migrate/packageRole').then(m => m.default)));
|
||||
|
||||
command
|
||||
.command('package-scripts')
|
||||
.description('Set package scripts according to each package role')
|
||||
.action(
|
||||
lazy(() => import('./migrate/packageScripts').then(m => m.command)),
|
||||
);
|
||||
}
|
||||
|
||||
export function registerCommands(program: CommanderStatic) {
|
||||
// TODO(Rugvip): Deprecate in favor of script variant
|
||||
program
|
||||
.command('app:build')
|
||||
.description('Build an app for a production release')
|
||||
@@ -33,6 +131,7 @@ export function registerCommands(program: CommanderStatic) {
|
||||
.option(...configOption)
|
||||
.action(lazy(() => import('./app/build').then(m => m.default)));
|
||||
|
||||
// TODO(Rugvip): Deprecate in favor of script variant
|
||||
program
|
||||
.command('app:serve')
|
||||
.description('Serve an app for local development')
|
||||
@@ -40,6 +139,7 @@ export function registerCommands(program: CommanderStatic) {
|
||||
.option(...configOption)
|
||||
.action(lazy(() => import('./app/serve').then(m => m.default)));
|
||||
|
||||
// TODO(Rugvip): Deprecate in favor of script variant
|
||||
program
|
||||
.command('backend:build')
|
||||
.description('Build a backend plugin')
|
||||
@@ -47,6 +147,7 @@ export function registerCommands(program: CommanderStatic) {
|
||||
.option('--experimental-type-build', 'Enable experimental type build')
|
||||
.action(lazy(() => import('./backend/build').then(m => m.default)));
|
||||
|
||||
// TODO(Rugvip): Deprecate in favor of script variant
|
||||
program
|
||||
.command('backend:bundle')
|
||||
.description('Bundle the backend into a deployment archive')
|
||||
@@ -56,6 +157,7 @@ export function registerCommands(program: CommanderStatic) {
|
||||
)
|
||||
.action(lazy(() => import('./backend/bundle').then(m => m.default)));
|
||||
|
||||
// TODO(Rugvip): Deprecate in favor of script variant
|
||||
program
|
||||
.command('backend:dev')
|
||||
.description('Start local development server with HMR for the backend')
|
||||
@@ -104,6 +206,7 @@ export function registerCommands(program: CommanderStatic) {
|
||||
lazy(() => import('./create-plugin/createPlugin').then(m => m.default)),
|
||||
);
|
||||
|
||||
// TODO(Rugvip): Deprecate in favor of script variant
|
||||
program
|
||||
.command('plugin:build')
|
||||
.description('Build a plugin')
|
||||
@@ -111,6 +214,7 @@ export function registerCommands(program: CommanderStatic) {
|
||||
.option('--experimental-type-build', 'Enable experimental type build')
|
||||
.action(lazy(() => import('./plugin/build').then(m => m.default)));
|
||||
|
||||
// TODO(Rugvip): Deprecate in favor of script variant
|
||||
program
|
||||
.command('plugin:serve')
|
||||
.description('Serves the dev/ folder of a plugin')
|
||||
@@ -125,14 +229,16 @@ export function registerCommands(program: CommanderStatic) {
|
||||
.description('Diff an existing plugin with the creation template')
|
||||
.action(lazy(() => import('./plugin/diff').then(m => m.default)));
|
||||
|
||||
// TODO(Rugvip): Deprecate in favor of script variant
|
||||
program
|
||||
.command('build')
|
||||
.description('Build a package for publishing')
|
||||
.option('--outputs <formats>', 'List of formats to output [types,cjs,esm]')
|
||||
.option('--minify', 'Minify the generated code')
|
||||
.option('--experimental-type-build', 'Enable experimental type build')
|
||||
.action(lazy(() => import('./build').then(m => m.default)));
|
||||
.action(lazy(() => import('./oldBuild').then(m => m.default)));
|
||||
|
||||
// TODO(Rugvip): Deprecate in favor of script variant
|
||||
program
|
||||
.command('lint')
|
||||
.option(
|
||||
@@ -144,6 +250,7 @@ export function registerCommands(program: CommanderStatic) {
|
||||
.description('Lint a package')
|
||||
.action(lazy(() => import('./lint').then(m => m.default)));
|
||||
|
||||
// TODO(Rugvip): Deprecate in favor of script variant
|
||||
program
|
||||
.command('test')
|
||||
.allowUnknownOption(true) // Allows the command to run, but we still need to parse raw args
|
||||
@@ -205,6 +312,9 @@ export function registerCommands(program: CommanderStatic) {
|
||||
.description('Print configuration schema')
|
||||
.action(lazy(() => import('./config/schema').then(m => m.default)));
|
||||
|
||||
registerScriptCommand(program);
|
||||
registerMigrateCommand(program);
|
||||
|
||||
program
|
||||
.command('versions:bump')
|
||||
.option(
|
||||
@@ -220,16 +330,19 @@ export function registerCommands(program: CommanderStatic) {
|
||||
.description('Check Backstage package versioning')
|
||||
.action(lazy(() => import('./versions/lint').then(m => m.default)));
|
||||
|
||||
// TODO(Rugvip): Deprecate in favor of script variant
|
||||
program
|
||||
.command('prepack')
|
||||
.description('Prepares a package for packaging before publishing')
|
||||
.action(lazy(() => import('./pack').then(m => m.pre)));
|
||||
|
||||
// TODO(Rugvip): Deprecate in favor of script variant
|
||||
program
|
||||
.command('postpack')
|
||||
.description('Restores the changes made by the prepack command')
|
||||
.action(lazy(() => import('./pack').then(m => m.post)));
|
||||
|
||||
// TODO(Rugvip): Deprecate in favor of script variant
|
||||
program
|
||||
.command('clean')
|
||||
.description('Delete cache directories')
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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 { resolve as resolvePath } from 'path';
|
||||
import { getPackages } from '@manypkg/get-packages';
|
||||
import { paths } from '../../lib/paths';
|
||||
import { getRoleFromPackage, detectRoleFromPackage } from '../../lib/role';
|
||||
|
||||
export default async () => {
|
||||
const { packages } = await getPackages(paths.targetDir);
|
||||
|
||||
await Promise.all(
|
||||
packages.map(async ({ dir, packageJson: pkg }) => {
|
||||
const { name } = pkg;
|
||||
const existingRole = getRoleFromPackage(pkg);
|
||||
if (existingRole) {
|
||||
return;
|
||||
}
|
||||
|
||||
const detectedRole = detectRoleFromPackage(pkg);
|
||||
if (!detectedRole) {
|
||||
console.error(`No role detected for package ${name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Detected package role of ${name} as ${detectedRole}`);
|
||||
|
||||
let newPkg = pkg as any;
|
||||
|
||||
const pkgKeys = Object.keys(pkg);
|
||||
if (pkgKeys.includes('backstage')) {
|
||||
newPkg.backstage = {
|
||||
...newPkg.backstage,
|
||||
role: detectedRole,
|
||||
};
|
||||
} else {
|
||||
// We insert the backstage field after one of these fields, otherwise at the end
|
||||
const index =
|
||||
Math.max(
|
||||
pkgKeys.indexOf('version'),
|
||||
pkgKeys.indexOf('private'),
|
||||
pkgKeys.indexOf('publishConfig'),
|
||||
) + 1 || pkgKeys.length;
|
||||
|
||||
const pkgEntries = Object.entries(pkg);
|
||||
pkgEntries.splice(index, 0, ['backstage', { role: detectedRole }]);
|
||||
newPkg = Object.fromEntries(pkgEntries);
|
||||
}
|
||||
|
||||
await fs.writeJson(resolvePath(dir, 'package.json'), newPkg, {
|
||||
spaces: 2,
|
||||
});
|
||||
}),
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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 { resolve as resolvePath } from 'path';
|
||||
import { PackageGraph } from '../../lib/monorepo';
|
||||
import { getRoleFromPackage, getRoleInfo, PackageRole } from '../../lib/role';
|
||||
|
||||
const noStartRoles: PackageRole[] = ['cli', 'common-library'];
|
||||
|
||||
export async function command() {
|
||||
const packages = await PackageGraph.listTargetPackages();
|
||||
|
||||
await Promise.all(
|
||||
packages.map(async ({ dir, packageJson }) => {
|
||||
const role = getRoleFromPackage(packageJson);
|
||||
if (!role) {
|
||||
return;
|
||||
}
|
||||
|
||||
const roleInfo = getRoleInfo(role);
|
||||
const hasStart = !noStartRoles.includes(role);
|
||||
const isBundled = roleInfo.output.includes('bundle');
|
||||
|
||||
const expectedScripts = {
|
||||
...(hasStart && { start: 'backstage-cli script start' }),
|
||||
build: 'backstage-cli script build',
|
||||
lint: 'backstage-cli script lint',
|
||||
test: 'backstage-cli script test',
|
||||
clean: 'backstage-cli script clean',
|
||||
...(!isBundled && {
|
||||
postpack: 'backstage-cli script postpack',
|
||||
prepack: 'backstage-cli script prepack',
|
||||
}),
|
||||
};
|
||||
|
||||
let changed = false;
|
||||
const currentScripts: Record<string, string | undefined> =
|
||||
(packageJson.scripts = packageJson.scripts || {});
|
||||
|
||||
for (const [name, value] of Object.entries(expectedScripts)) {
|
||||
if (currentScripts[name] !== value) {
|
||||
changed = true;
|
||||
currentScripts[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
console.log(`Updating scripts for ${packageJson.name}`);
|
||||
await fs.writeJson(resolvePath(dir, 'package.json'), packageJson, {
|
||||
spaces: 2,
|
||||
});
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 { Command } from 'commander';
|
||||
import { startBackend } from './startBackend';
|
||||
import { startFrontend } from './startFrontend';
|
||||
import { findRoleFromCommand } from '../../lib/role';
|
||||
|
||||
export async function command(cmd: Command): Promise<void> {
|
||||
const role = await findRoleFromCommand(cmd);
|
||||
|
||||
const options = {
|
||||
configPaths: cmd.config as string[],
|
||||
checksEnabled: Boolean(cmd.check),
|
||||
inspectEnabled: Boolean(cmd.inspect),
|
||||
inspectBrkEnabled: Boolean(cmd.inspectBrk),
|
||||
};
|
||||
|
||||
switch (role) {
|
||||
case 'backend':
|
||||
case 'plugin-backend':
|
||||
case 'plugin-backend-module':
|
||||
case 'node-library':
|
||||
return startBackend(options);
|
||||
case 'app':
|
||||
return startFrontend({
|
||||
...options,
|
||||
entry: 'src/index',
|
||||
verifyVersions: true,
|
||||
});
|
||||
case 'web-library':
|
||||
case 'plugin-frontend':
|
||||
case 'plugin-frontend-module':
|
||||
return startFrontend({ entry: 'dev/index', ...options });
|
||||
default:
|
||||
throw new Error(
|
||||
`Start command is not supported for package role '${role}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { command } from './command';
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 { paths } from '../../lib/paths';
|
||||
import { serveBackend } from '../../lib/bundler';
|
||||
|
||||
interface StartBackendOptions {
|
||||
checksEnabled: boolean;
|
||||
inspectEnabled: boolean;
|
||||
inspectBrkEnabled: boolean;
|
||||
}
|
||||
|
||||
export async function startBackend(options: StartBackendOptions) {
|
||||
// Cleaning dist/ before we start the dev process helps work around an issue
|
||||
// where we end up with the entrypoint executing multiple times, causing
|
||||
// a port bind conflict among other things.
|
||||
await fs.remove(paths.resolveTarget('dist'));
|
||||
|
||||
const waitForExit = await serveBackend({
|
||||
entry: 'src/index',
|
||||
checksEnabled: options.checksEnabled,
|
||||
inspectEnabled: options.inspectEnabled,
|
||||
inspectBrkEnabled: options.inspectBrkEnabled,
|
||||
});
|
||||
|
||||
await waitForExit();
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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 chalk from 'chalk';
|
||||
import uniq from 'lodash/uniq';
|
||||
import { serveBundle } from '../../lib/bundler';
|
||||
import { loadCliConfig } from '../../lib/config';
|
||||
import { paths } from '../../lib/paths';
|
||||
import { Lockfile } from '../../lib/versioning';
|
||||
import { includedFilter } from '../versions/lint';
|
||||
|
||||
interface StartAppOptions {
|
||||
verifyVersions?: boolean;
|
||||
entry: string;
|
||||
|
||||
checksEnabled: boolean;
|
||||
configPaths: string[];
|
||||
}
|
||||
|
||||
export async function startFrontend(options: StartAppOptions) {
|
||||
if (options.verifyVersions) {
|
||||
const lockfile = await Lockfile.load(paths.resolveTargetRoot('yarn.lock'));
|
||||
const result = lockfile.analyze({
|
||||
filter: includedFilter,
|
||||
});
|
||||
const problemPackages = [...result.newVersions, ...result.newRanges].map(
|
||||
({ name }) => name,
|
||||
);
|
||||
|
||||
if (problemPackages.length > 1) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
`⚠️ Some of the following packages may be outdated or have duplicate installations:
|
||||
|
||||
${uniq(problemPackages).join(', ')}
|
||||
`,
|
||||
),
|
||||
);
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
`⚠️ This can be resolved using the following command:
|
||||
|
||||
yarn backstage-cli versions:check --fix
|
||||
`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const { name } = await fs.readJson(paths.resolveTarget('package.json'));
|
||||
const waitForExit = await serveBundle({
|
||||
entry: options.entry,
|
||||
checksEnabled: options.checksEnabled,
|
||||
...(await loadCliConfig({
|
||||
args: options.configPaths,
|
||||
fromPackage: name,
|
||||
withFilteredKeys: true,
|
||||
})),
|
||||
});
|
||||
|
||||
await waitForExit();
|
||||
}
|
||||
@@ -14,7 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Package } from '@manypkg/get-packages';
|
||||
import { getPackages, Package } from '@manypkg/get-packages';
|
||||
import { paths } from '../paths';
|
||||
import { PackageRole } from '../role';
|
||||
|
||||
type PackageJSON = Package['packageJson'];
|
||||
|
||||
@@ -25,8 +27,17 @@ export interface ExtendedPackageJSON extends PackageJSON {
|
||||
// The `bundled` field is a field known within Backstage, it means
|
||||
// that the package bundles all of its dependencies in its build output.
|
||||
bundled?: boolean;
|
||||
|
||||
backstage?: {
|
||||
role?: PackageRole;
|
||||
};
|
||||
}
|
||||
|
||||
export type ExtendedPackage = {
|
||||
dir: string;
|
||||
packageJson: ExtendedPackageJSON;
|
||||
};
|
||||
|
||||
export type PackageGraphNode = {
|
||||
/** The name of the package */
|
||||
name: string;
|
||||
@@ -47,6 +58,11 @@ export type PackageGraphNode = {
|
||||
};
|
||||
|
||||
export class PackageGraph extends Map<string, PackageGraphNode> {
|
||||
static async listTargetPackages(): Promise<ExtendedPackage[]> {
|
||||
const { packages } = await getPackages(paths.targetDir);
|
||||
return packages as ExtendedPackage[];
|
||||
}
|
||||
|
||||
static fromPackages(packages: Package[]): PackageGraph {
|
||||
const graph = new PackageGraph();
|
||||
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
export type {
|
||||
PackageRoleInfo,
|
||||
PackagePlatform,
|
||||
PackageOutputType,
|
||||
PackageRole,
|
||||
} from './types';
|
||||
export {
|
||||
getRoleInfo,
|
||||
getRoleFromPackage,
|
||||
findRoleFromCommand,
|
||||
detectRoleFromPackage,
|
||||
} from './packageRoles';
|
||||
@@ -0,0 +1,328 @@
|
||||
/*
|
||||
* 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 mockFs from 'mock-fs';
|
||||
import { Command } from 'commander';
|
||||
import {
|
||||
getRoleInfo,
|
||||
getRoleFromPackage,
|
||||
findRoleFromCommand,
|
||||
detectRoleFromPackage,
|
||||
} from './packageRoles';
|
||||
|
||||
describe('getRoleInfo', () => {
|
||||
it('provides role info by role', () => {
|
||||
expect(getRoleInfo('web-library')).toEqual({
|
||||
role: 'web-library',
|
||||
platform: 'web',
|
||||
output: ['types', 'esm'],
|
||||
});
|
||||
|
||||
expect(getRoleInfo('app')).toEqual({
|
||||
role: 'app',
|
||||
platform: 'web',
|
||||
output: ['bundle'],
|
||||
});
|
||||
|
||||
expect(() => getRoleInfo('invalid')).toThrow(
|
||||
`Unknown package role 'invalid'`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRoleFromPackage', () => {
|
||||
it('reads explicit package roles', () => {
|
||||
expect(
|
||||
getRoleFromPackage({
|
||||
backstage: {
|
||||
role: 'web-library',
|
||||
},
|
||||
}),
|
||||
).toEqual('web-library');
|
||||
|
||||
expect(
|
||||
getRoleFromPackage({
|
||||
backstage: {
|
||||
role: 'app',
|
||||
},
|
||||
}),
|
||||
).toEqual('app');
|
||||
|
||||
expect(() =>
|
||||
getRoleFromPackage({
|
||||
name: 'test',
|
||||
backstage: {},
|
||||
}),
|
||||
).toThrow('Package test must specify a role in the "backstage" field');
|
||||
|
||||
expect(() =>
|
||||
getRoleFromPackage({
|
||||
name: 'test',
|
||||
backstage: { role: 'invalid' },
|
||||
}),
|
||||
).toThrow(`Unknown package role 'invalid'`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findRoleFromCommand', () => {
|
||||
function mkCommand(args: string) {
|
||||
return new Command()
|
||||
.option('--role <role>', 'test role')
|
||||
.parse(['node', 'entry.js', ...args.split(' ')]) as Command;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
'package.json': JSON.stringify({
|
||||
name: 'test',
|
||||
backstage: {
|
||||
role: 'web-library',
|
||||
},
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it('provides role info by role', async () => {
|
||||
await expect(findRoleFromCommand(mkCommand(''))).resolves.toEqual(
|
||||
'web-library',
|
||||
);
|
||||
|
||||
await expect(
|
||||
findRoleFromCommand(mkCommand('--role node-library')),
|
||||
).resolves.toEqual('node-library');
|
||||
|
||||
await expect(
|
||||
findRoleFromCommand(mkCommand('--role invalid')),
|
||||
).rejects.toThrow(`Unknown package role 'invalid'`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('detectRoleFromPackage', () => {
|
||||
it('detects the role of example-app', () => {
|
||||
expect(
|
||||
detectRoleFromPackage({
|
||||
name: 'example-app',
|
||||
private: true,
|
||||
bundled: true,
|
||||
scripts: {
|
||||
start: 'backstage-cli app:serve',
|
||||
build: 'backstage-cli app:build',
|
||||
clean: 'backstage-cli clean',
|
||||
test: 'backstage-cli test',
|
||||
'test:e2e':
|
||||
'start-server-and-test start http://localhost:3000 cy:dev',
|
||||
'test:e2e:ci':
|
||||
'start-server-and-test start http://localhost:3000 cy:run',
|
||||
lint: 'backstage-cli lint',
|
||||
'cy:dev': 'cypress open',
|
||||
'cy:run': 'cypress run',
|
||||
},
|
||||
}),
|
||||
).toEqual('app');
|
||||
});
|
||||
|
||||
it('detects the role of example-backend', () => {
|
||||
expect(
|
||||
detectRoleFromPackage({
|
||||
name: 'example-backend',
|
||||
main: 'dist/index.cjs.js',
|
||||
types: 'src/index.ts',
|
||||
scripts: {
|
||||
build: 'backstage-cli backend:bundle',
|
||||
'build-image':
|
||||
'docker build ../.. -f Dockerfile --tag example-backend',
|
||||
start: 'backstage-cli backend:dev',
|
||||
lint: 'backstage-cli lint',
|
||||
test: 'backstage-cli test',
|
||||
clean: 'backstage-cli clean',
|
||||
'migrate:create': 'knex migrate:make -x ts',
|
||||
},
|
||||
}),
|
||||
).toEqual('backend');
|
||||
});
|
||||
|
||||
it('detects the role of @backstage/plugin-catalog', () => {
|
||||
expect(
|
||||
detectRoleFromPackage({
|
||||
name: '@backstage/plugin-catalog',
|
||||
main: 'src/index.ts',
|
||||
types: 'src/index.ts',
|
||||
publishConfig: {
|
||||
access: 'public',
|
||||
main: 'dist/index.esm.js',
|
||||
types: 'dist/index.d.ts',
|
||||
},
|
||||
scripts: {
|
||||
build: 'backstage-cli plugin:build',
|
||||
start: 'backstage-cli plugin:serve',
|
||||
lint: 'backstage-cli lint',
|
||||
test: 'backstage-cli test',
|
||||
diff: 'backstage-cli plugin:diff',
|
||||
prepack: 'backstage-cli prepack',
|
||||
postpack: 'backstage-cli postpack',
|
||||
clean: 'backstage-cli clean',
|
||||
},
|
||||
}),
|
||||
).toEqual('plugin-frontend');
|
||||
});
|
||||
|
||||
it('detects the role of @backstage/plugin-catalog-backend', () => {
|
||||
expect(
|
||||
detectRoleFromPackage({
|
||||
name: '@backstage/plugin-catalog-backend',
|
||||
main: 'src/index.ts',
|
||||
types: 'src/index.ts',
|
||||
publishConfig: {
|
||||
access: 'public',
|
||||
main: 'dist/index.cjs.js',
|
||||
types: 'dist/index.d.ts',
|
||||
},
|
||||
scripts: {
|
||||
start: 'backstage-cli backend:dev',
|
||||
build: 'backstage-cli backend:build',
|
||||
lint: 'backstage-cli lint',
|
||||
test: 'backstage-cli test',
|
||||
prepack: 'backstage-cli prepack',
|
||||
postpack: 'backstage-cli postpack',
|
||||
clean: 'backstage-cli clean',
|
||||
},
|
||||
}),
|
||||
).toEqual('plugin-backend');
|
||||
});
|
||||
|
||||
it('detects the role of @backstage/plugin-catalog-react', () => {
|
||||
expect(
|
||||
detectRoleFromPackage({
|
||||
name: '@backstage/plugin-catalog-react',
|
||||
main: 'src/index.ts',
|
||||
types: 'src/index.ts',
|
||||
publishConfig: {
|
||||
access: 'public',
|
||||
main: 'dist/index.esm.js',
|
||||
types: 'dist/index.d.ts',
|
||||
},
|
||||
scripts: {
|
||||
build: 'backstage-cli build',
|
||||
lint: 'backstage-cli lint',
|
||||
test: 'backstage-cli test',
|
||||
prepack: 'backstage-cli prepack',
|
||||
postpack: 'backstage-cli postpack',
|
||||
clean: 'backstage-cli clean',
|
||||
},
|
||||
}),
|
||||
).toEqual('web-library');
|
||||
});
|
||||
|
||||
it('detects the role of @backstage/plugin-catalog-common', () => {
|
||||
expect(
|
||||
detectRoleFromPackage({
|
||||
name: '@backstage/plugin-catalog-common',
|
||||
main: 'src/index.ts',
|
||||
types: 'src/index.ts',
|
||||
publishConfig: {
|
||||
access: 'public',
|
||||
main: 'dist/index.cjs.js',
|
||||
module: 'dist/index.esm.js',
|
||||
types: 'dist/index.d.ts',
|
||||
},
|
||||
scripts: {
|
||||
build: 'backstage-cli build',
|
||||
lint: 'backstage-cli lint',
|
||||
test: 'backstage-cli test --passWithNoTests',
|
||||
prepack: 'backstage-cli prepack',
|
||||
postpack: 'backstage-cli postpack',
|
||||
clean: 'backstage-cli clean',
|
||||
},
|
||||
}),
|
||||
).toEqual('common-library');
|
||||
});
|
||||
|
||||
it('detects the role of @backstage/plugin-catalog-backend-module-ldap', () => {
|
||||
expect(
|
||||
detectRoleFromPackage({
|
||||
name: '@backstage/plugin-catalog-backend-module-ldap',
|
||||
main: 'src/index.ts',
|
||||
types: 'src/index.ts',
|
||||
publishConfig: {
|
||||
access: 'public',
|
||||
main: 'dist/index.cjs.js',
|
||||
types: 'dist/index.d.ts',
|
||||
},
|
||||
scripts: {
|
||||
build: 'backstage-cli backend:build',
|
||||
lint: 'backstage-cli lint',
|
||||
test: 'backstage-cli test',
|
||||
prepack: 'backstage-cli prepack',
|
||||
postpack: 'backstage-cli postpack',
|
||||
clean: 'backstage-cli clean',
|
||||
},
|
||||
}),
|
||||
).toEqual('plugin-backend-module');
|
||||
});
|
||||
|
||||
it('detects the role of @backstage/plugin-permission-node', () => {
|
||||
expect(
|
||||
detectRoleFromPackage({
|
||||
name: '@backstage/plugin-permission-node',
|
||||
main: 'src/index.ts',
|
||||
types: 'src/index.ts',
|
||||
homepage: 'https://backstage.io',
|
||||
publishConfig: {
|
||||
access: 'public',
|
||||
main: 'dist/index.cjs.js',
|
||||
types: 'dist/index.d.ts',
|
||||
},
|
||||
scripts: {
|
||||
build: 'backstage-cli backend:build',
|
||||
lint: 'backstage-cli lint',
|
||||
test: 'backstage-cli test',
|
||||
prepack: 'backstage-cli prepack',
|
||||
postpack: 'backstage-cli postpack',
|
||||
clean: 'backstage-cli clean',
|
||||
},
|
||||
}),
|
||||
).toEqual('node-library');
|
||||
});
|
||||
|
||||
it('detects the role of @backstage/plugin-analytics-module-ga', () => {
|
||||
expect(
|
||||
detectRoleFromPackage({
|
||||
name: '@backstage/plugin-analytics-module-ga',
|
||||
main: 'src/index.ts',
|
||||
types: 'src/index.ts',
|
||||
publishConfig: {
|
||||
access: 'public',
|
||||
main: 'dist/index.esm.js',
|
||||
types: 'dist/index.d.ts',
|
||||
},
|
||||
scripts: {
|
||||
build: 'backstage-cli plugin:build',
|
||||
start: 'backstage-cli plugin:serve',
|
||||
lint: 'backstage-cli lint',
|
||||
test: 'backstage-cli test',
|
||||
diff: 'backstage-cli plugin:diff',
|
||||
prepack: 'backstage-cli prepack',
|
||||
postpack: 'backstage-cli postpack',
|
||||
clean: 'backstage-cli clean',
|
||||
},
|
||||
}),
|
||||
).toEqual('plugin-frontend-module');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* 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 { z } from 'zod';
|
||||
import fs from 'fs-extra';
|
||||
import { Command } from 'commander';
|
||||
import { paths } from '../paths';
|
||||
import { PackageRole, PackageRoleInfo } from './types';
|
||||
|
||||
const packageRoleInfos: PackageRoleInfo[] = [
|
||||
{
|
||||
role: 'app',
|
||||
platform: 'web',
|
||||
output: ['bundle'],
|
||||
},
|
||||
{
|
||||
role: 'backend',
|
||||
platform: 'node',
|
||||
output: ['bundle'],
|
||||
},
|
||||
{
|
||||
role: 'cli',
|
||||
platform: 'node',
|
||||
output: ['cjs'],
|
||||
},
|
||||
{
|
||||
role: 'web-library',
|
||||
platform: 'web',
|
||||
output: ['types', 'esm'],
|
||||
},
|
||||
{
|
||||
role: 'node-library',
|
||||
platform: 'node',
|
||||
output: ['types', 'cjs'],
|
||||
},
|
||||
{
|
||||
role: 'common-library',
|
||||
platform: 'common',
|
||||
output: ['types', 'esm', 'cjs'],
|
||||
},
|
||||
{
|
||||
role: 'plugin-frontend',
|
||||
platform: 'web',
|
||||
output: ['types', 'esm'],
|
||||
},
|
||||
{
|
||||
role: 'plugin-frontend-module',
|
||||
platform: 'web',
|
||||
output: ['types', 'esm'],
|
||||
},
|
||||
{
|
||||
role: 'plugin-backend',
|
||||
platform: 'node',
|
||||
output: ['types', 'cjs'],
|
||||
},
|
||||
{
|
||||
role: 'plugin-backend-module',
|
||||
platform: 'node',
|
||||
output: ['types', 'cjs'],
|
||||
},
|
||||
];
|
||||
|
||||
export function getRoleInfo(role: string): PackageRoleInfo {
|
||||
const roleInfo = packageRoleInfos.find(r => r.role === role);
|
||||
if (!roleInfo) {
|
||||
throw new Error(`Unknown package role '${role}'`);
|
||||
}
|
||||
return roleInfo;
|
||||
}
|
||||
|
||||
const readSchema = z.object({
|
||||
name: z.string().optional(),
|
||||
backstage: z
|
||||
.object({
|
||||
role: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export function getRoleFromPackage(pkgJson: unknown): PackageRole | undefined {
|
||||
const pkg = readSchema.parse(pkgJson);
|
||||
|
||||
// If there's an explicit role, use that.
|
||||
if (pkg.backstage) {
|
||||
const { role } = pkg.backstage;
|
||||
if (!role) {
|
||||
throw new Error(
|
||||
`Package ${pkg.name} must specify a role in the "backstage" field`,
|
||||
);
|
||||
}
|
||||
|
||||
return getRoleInfo(role).role;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function findRoleFromCommand(cmd: Command): Promise<PackageRole> {
|
||||
if (cmd.role) {
|
||||
return getRoleInfo(cmd.role)?.role;
|
||||
}
|
||||
|
||||
const pkg = await fs.readJson(paths.resolveTarget('package.json'));
|
||||
const info = getRoleFromPackage(pkg);
|
||||
if (!info) {
|
||||
throw new Error(`Target package must have 'backstage.role' set`);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
const detectionSchema = z.object({
|
||||
name: z.string().optional(),
|
||||
scripts: z
|
||||
.object({
|
||||
start: z.string().optional(),
|
||||
build: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
publishConfig: z
|
||||
.object({
|
||||
main: z.string().optional(),
|
||||
types: z.string().optional(),
|
||||
module: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
main: z.string().optional(),
|
||||
types: z.string().optional(),
|
||||
module: z.string().optional(),
|
||||
});
|
||||
|
||||
export function detectRoleFromPackage(
|
||||
pkgJson: unknown,
|
||||
): PackageRole | undefined {
|
||||
const pkg = detectionSchema.parse(pkgJson);
|
||||
|
||||
if (pkg.scripts?.start?.includes('app:serve')) {
|
||||
return 'app';
|
||||
}
|
||||
if (pkg.scripts?.build?.includes('backend:bundle')) {
|
||||
return 'backend';
|
||||
}
|
||||
if (pkg.name?.includes('plugin-') && pkg.name?.includes('-backend-module-')) {
|
||||
return 'plugin-backend-module';
|
||||
}
|
||||
if (pkg.name?.includes('plugin-') && pkg.name?.includes('-module-')) {
|
||||
return 'plugin-frontend-module';
|
||||
}
|
||||
if (pkg.scripts?.start?.includes('plugin:serve')) {
|
||||
return 'plugin-frontend';
|
||||
}
|
||||
if (pkg.scripts?.start?.includes('backend:dev')) {
|
||||
return 'plugin-backend';
|
||||
}
|
||||
|
||||
const mainEntry = pkg.publishConfig?.main || pkg.main;
|
||||
const moduleEntry = pkg.publishConfig?.module || pkg.module;
|
||||
const typesEntry = pkg.publishConfig?.types || pkg.types;
|
||||
if (typesEntry) {
|
||||
if (mainEntry && moduleEntry) {
|
||||
return 'common-library';
|
||||
}
|
||||
if (moduleEntry || mainEntry?.endsWith('.esm.js')) {
|
||||
return 'web-library';
|
||||
}
|
||||
if (mainEntry) {
|
||||
return 'node-library';
|
||||
}
|
||||
} else if (mainEntry) {
|
||||
return 'cli';
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export type PackageRole =
|
||||
| 'app'
|
||||
| 'backend'
|
||||
| 'cli'
|
||||
| 'web-library'
|
||||
| 'node-library'
|
||||
| 'common-library'
|
||||
| 'plugin-frontend'
|
||||
| 'plugin-frontend-module'
|
||||
| 'plugin-backend'
|
||||
| 'plugin-backend-module';
|
||||
|
||||
export type PackagePlatform = 'node' | 'web' | 'common';
|
||||
export type PackageOutputType = 'bundle' | 'types' | 'esm' | 'cjs';
|
||||
|
||||
export interface PackageRoleInfo {
|
||||
role: PackageRole;
|
||||
platform: PackagePlatform;
|
||||
output: PackageOutputType[];
|
||||
}
|
||||
Reference in New Issue
Block a user