cli: add option to include bundled packages in repo build + parse options
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -15,24 +15,27 @@
|
||||
*/
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
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 {
|
||||
targetDir: string;
|
||||
writeStats: boolean;
|
||||
configPaths: string[];
|
||||
}
|
||||
|
||||
export async function buildApp(options: BuildAppOptions) {
|
||||
const { name } = await fs.readJson(paths.resolveTarget('package.json'));
|
||||
const { targetDir, writeStats, configPaths } = options;
|
||||
const { name } = await fs.readJson(resolvePath(targetDir, 'package.json'));
|
||||
await buildBundle({
|
||||
targetDir,
|
||||
entry: 'src/index',
|
||||
parallel: parseParallel(process.env[PARALLEL_ENV_VAR]),
|
||||
statsJsonEnabled: options.writeStats,
|
||||
statsJsonEnabled: writeStats,
|
||||
...(await loadCliConfig({
|
||||
args: options.configPaths,
|
||||
args: configPaths,
|
||||
fromPackage: name,
|
||||
})),
|
||||
});
|
||||
|
||||
@@ -19,7 +19,6 @@ 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';
|
||||
|
||||
@@ -27,12 +26,13 @@ const BUNDLE_FILE = 'bundle.tar.gz';
|
||||
const SKELETON_FILE = 'skeleton.tar.gz';
|
||||
|
||||
interface BuildBackendOptions {
|
||||
targetDir: string;
|
||||
skipBuildDependencies: boolean;
|
||||
}
|
||||
|
||||
export async function buildBackend(options: BuildBackendOptions) {
|
||||
const targetDir = paths.resolveTarget('dist');
|
||||
const pkg = await fs.readJson(paths.resolveTarget('package.json'));
|
||||
const { targetDir, skipBuildDependencies } = options;
|
||||
const pkg = await fs.readJson(resolvePath(targetDir, 'package.json'));
|
||||
|
||||
// We build the target package without generating type declarations.
|
||||
await buildPackage({ outputs: new Set([Output.cjs]) });
|
||||
@@ -41,7 +41,7 @@ export async function buildBackend(options: BuildBackendOptions) {
|
||||
try {
|
||||
await createDistWorkspace([pkg.name], {
|
||||
targetDir: tmpDir,
|
||||
buildDependencies: !options.skipBuildDependencies,
|
||||
buildDependencies: !skipBuildDependencies,
|
||||
buildExcludes: [pkg.name],
|
||||
parallel: parseParallel(process.env[PARALLEL_ENV_VAR]),
|
||||
skeleton: SKELETON_FILE,
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import { Command } from 'commander';
|
||||
import { buildPackage, Output } from '../../lib/builder';
|
||||
import { findRoleFromCommand, getRoleInfo } from '../../lib/role';
|
||||
import { paths } from '../../lib/paths';
|
||||
import { buildApp } from './buildApp';
|
||||
import { buildBackend } from './buildBackend';
|
||||
|
||||
@@ -25,12 +26,14 @@ export async function command(cmd: Command): Promise<void> {
|
||||
|
||||
if (role === 'app') {
|
||||
return buildApp({
|
||||
targetDir: paths.resolveTarget('dist'),
|
||||
configPaths: cmd.config as string[],
|
||||
writeStats: Boolean(cmd.stats),
|
||||
});
|
||||
}
|
||||
if (role === 'backend') {
|
||||
return buildBackend({
|
||||
targetDir: paths.resolveTarget('dist'),
|
||||
skipBuildDependencies: Boolean(cmd.skipBuildDependencies),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,7 +35,11 @@ export function registerRepoCommand(program: CommanderStatic) {
|
||||
command
|
||||
.command('build')
|
||||
.description(
|
||||
'Build all packages in the project that use the standard backstage build script',
|
||||
'Build packages in the project, excluding bundled app and backend packages.',
|
||||
)
|
||||
.option(
|
||||
'--all',
|
||||
'Build all packages, including bundled app and backend packages.',
|
||||
)
|
||||
.action(lazy(() => import('./repo/build').then(m => m.command)));
|
||||
}
|
||||
|
||||
@@ -15,13 +15,65 @@
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { Command } from 'commander';
|
||||
import { relative as relativePath } from 'path';
|
||||
import { buildPackages, getOutputsForRole } from '../../lib/builder';
|
||||
import { PackageGraph } from '../../lib/monorepo';
|
||||
import { ExtendedPackage } from '../../lib/monorepo/PackageGraph';
|
||||
import { paths } from '../../lib/paths';
|
||||
import { getRoleInfo } from '../../lib/role';
|
||||
import { buildApp } from '../build/buildApp';
|
||||
import { buildBackend } from '../build/buildBackend';
|
||||
|
||||
export async function command(): Promise<void> {
|
||||
function parseScriptOptions(
|
||||
cmd: Command,
|
||||
scriptCommandName: string,
|
||||
args: string[],
|
||||
) {
|
||||
let rootCommand = cmd;
|
||||
while (rootCommand.parent) {
|
||||
rootCommand = rootCommand.parent;
|
||||
}
|
||||
const scriptCommand = rootCommand.commands.find(c => c.name() === 'script')!;
|
||||
const targetCommand = scriptCommand.commands.find(
|
||||
c => c.name() === scriptCommandName,
|
||||
);
|
||||
if (!targetCommand) {
|
||||
throw new Error(`Could not find script command '${scriptCommandName}'`);
|
||||
}
|
||||
|
||||
const currentOpts = targetCommand._optionValues;
|
||||
const currentStore = targetCommand._storeOptionsAsProperties;
|
||||
|
||||
const result: Record<string, any> = {};
|
||||
targetCommand._storeOptionsAsProperties = false;
|
||||
targetCommand._optionValues = result;
|
||||
|
||||
targetCommand.parseOptions(args);
|
||||
|
||||
targetCommand._storeOptionsAsProperties = currentOpts;
|
||||
targetCommand._optionValues = currentStore;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseBackstageScript(
|
||||
cmd: Command,
|
||||
expectedScript: string,
|
||||
scriptStr?: string,
|
||||
) {
|
||||
const expectedPrefix = `backstage-cli script ${expectedScript}`;
|
||||
if (!scriptStr || !scriptStr.startsWith(expectedPrefix)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const argsStr = scriptStr.slice(expectedPrefix.length).trim();
|
||||
return parseScriptOptions(cmd, expectedScript, argsStr.split(' '));
|
||||
}
|
||||
|
||||
export async function command(cmd: Command): Promise<void> {
|
||||
const packages = await PackageGraph.listTargetPackages();
|
||||
const bundledPackages = new Array<ExtendedPackage>();
|
||||
|
||||
const options = packages.flatMap(pkg => {
|
||||
const role = pkg.packageJson.backstage?.role;
|
||||
@@ -32,7 +84,13 @@ export async function command(): Promise<void> {
|
||||
|
||||
const outputs = getOutputsForRole(role);
|
||||
if (outputs.size === 0) {
|
||||
console.warn(`Ignored ${pkg.packageJson.name} because it has no output`);
|
||||
if (getRoleInfo(role).output.includes('bundle')) {
|
||||
bundledPackages.push(pkg);
|
||||
} else {
|
||||
console.warn(
|
||||
`Ignored ${pkg.packageJson.name} because it has no output`,
|
||||
);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -43,7 +101,9 @@ export async function command(): Promise<void> {
|
||||
);
|
||||
return [];
|
||||
}
|
||||
if (!buildScript.startsWith('backstage-cli script build')) {
|
||||
|
||||
const buildOptions = parseBackstageScript(cmd, 'build', buildScript);
|
||||
if (!buildOptions) {
|
||||
console.warn(
|
||||
`Ignored ${pkg.packageJson.name} because it has a custom build script, '${buildScript}'`,
|
||||
);
|
||||
@@ -54,11 +114,46 @@ export async function command(): Promise<void> {
|
||||
targetDir: pkg.dir,
|
||||
outputs,
|
||||
logPrefix: `${chalk.cyan(relativePath(paths.targetRoot, pkg.dir))}: `,
|
||||
// TODO(Rugvip): Use commander to parse the script and grab these instead
|
||||
minify: buildScript.includes('--minify'),
|
||||
useApiExtractor: buildScript.includes('--experimental-type-build'),
|
||||
minify: buildOptions.minify,
|
||||
useApiExtractor: buildOptions.experimentalTypeBuild,
|
||||
};
|
||||
});
|
||||
|
||||
console.log('Building packages');
|
||||
await buildPackages(options);
|
||||
|
||||
if (cmd.all) {
|
||||
const apps = bundledPackages.filter(
|
||||
pkg => pkg.packageJson.backstage?.role === 'app',
|
||||
);
|
||||
|
||||
console.log('Building apps');
|
||||
await Promise.all(
|
||||
apps.map(async pkg => {
|
||||
const buildOptions = parseBackstageScript(
|
||||
cmd,
|
||||
'build',
|
||||
pkg.packageJson.scripts?.build,
|
||||
);
|
||||
await buildApp({
|
||||
targetDir: pkg.dir,
|
||||
configPaths: (buildOptions?.config as string[]) ?? [],
|
||||
writeStats: Boolean(buildOptions?.stats),
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
console.log('Building backends');
|
||||
const backends = bundledPackages.filter(
|
||||
pkg => pkg.packageJson.backstage?.role === 'backend',
|
||||
);
|
||||
await Promise.all(
|
||||
backends.map(async pkg => {
|
||||
await buildBackend({
|
||||
targetDir: pkg.dir,
|
||||
skipBuildDependencies: true,
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,55 +15,58 @@
|
||||
*/
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import { paths } from '../paths';
|
||||
|
||||
export type BundlingPathsOptions = {
|
||||
// bundle entrypoint, e.g. 'src/index'
|
||||
entry: string;
|
||||
// Target directory, defaulting to paths.targetDir
|
||||
targetDir?: string;
|
||||
};
|
||||
|
||||
export function resolveBundlingPaths(options: BundlingPathsOptions) {
|
||||
const { entry } = options;
|
||||
const { entry, targetDir = paths.targetDir } = options;
|
||||
|
||||
const resolveTargetModule = (pathString: string) => {
|
||||
for (const ext of ['mjs', 'js', 'ts', 'tsx', 'jsx']) {
|
||||
const filePath = paths.resolveTarget(`${pathString}.${ext}`);
|
||||
const filePath = resolvePath(targetDir, `${pathString}.${ext}`);
|
||||
if (fs.pathExistsSync(filePath)) {
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
return paths.resolveTarget(`${pathString}.js`);
|
||||
return resolvePath(targetDir, `${pathString}.js`);
|
||||
};
|
||||
|
||||
let targetPublic = undefined;
|
||||
let targetHtml = paths.resolveTarget('public/index.html');
|
||||
let targetHtml = resolvePath(targetDir, 'public/index.html');
|
||||
|
||||
// Prefer public folder
|
||||
if (fs.pathExistsSync(targetHtml)) {
|
||||
targetPublic = paths.resolveTarget('public');
|
||||
targetPublic = resolvePath(targetDir, 'public');
|
||||
} else {
|
||||
targetHtml = paths.resolveTarget(`${entry}.html`);
|
||||
targetHtml = resolvePath(targetDir, `${entry}.html`);
|
||||
if (!fs.pathExistsSync(targetHtml)) {
|
||||
targetHtml = paths.resolveOwn('templates/serve_index.html');
|
||||
}
|
||||
}
|
||||
|
||||
// Backend plugin dev run file
|
||||
const targetRunFile = paths.resolveTarget('src/run.ts');
|
||||
const targetRunFile = resolvePath(targetDir, 'src/run.ts');
|
||||
const runFileExists = fs.pathExistsSync(targetRunFile);
|
||||
|
||||
return {
|
||||
targetHtml,
|
||||
targetPublic,
|
||||
targetPath: paths.resolveTarget('.'),
|
||||
targetPath: resolvePath(targetDir, '.'),
|
||||
targetRunFile: runFileExists ? targetRunFile : undefined,
|
||||
targetDist: paths.resolveTarget('dist'),
|
||||
targetAssets: paths.resolveTarget('assets'),
|
||||
targetSrc: paths.resolveTarget('src'),
|
||||
targetDev: paths.resolveTarget('dev'),
|
||||
targetDist: resolvePath(targetDir, 'dist'),
|
||||
targetAssets: resolvePath(targetDir, 'assets'),
|
||||
targetSrc: resolvePath(targetDir, 'src'),
|
||||
targetDev: resolvePath(targetDir, 'dev'),
|
||||
targetEntry: resolveTargetModule(entry),
|
||||
targetTsConfig: paths.resolveTargetRoot('tsconfig.json'),
|
||||
targetPackageJson: paths.resolveTarget('package.json'),
|
||||
targetPackageJson: resolvePath(targetDir, 'package.json'),
|
||||
rootNodeModules: paths.resolveTargetRoot('node_modules'),
|
||||
root: paths.targetRoot,
|
||||
};
|
||||
|
||||
@@ -35,6 +35,8 @@ export type ServeOptions = BundlingPathsOptions & {
|
||||
};
|
||||
|
||||
export type BuildOptions = BundlingPathsOptions & {
|
||||
// Target directory, defaulting to paths.targetDir
|
||||
targetDir?: string;
|
||||
statsJsonEnabled: boolean;
|
||||
parallel?: ParallelOption;
|
||||
schema?: ConfigSchema;
|
||||
|
||||
Reference in New Issue
Block a user