From 29260100446370472eaa10cbf2af4e11b5ff8936 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 22 Jan 2022 15:36:06 +0100 Subject: [PATCH] cli: add new role-based bundle command Signed-off-by: Patrik Oldsberg --- packages/cli/src/commands/bundle/bundleApp.ts | 39 ++++++++++ .../cli/src/commands/bundle/bundleBackend.ts | 75 +++++++++++++++++++ packages/cli/src/commands/bundle/command.ts | 46 ++++++++++++ packages/cli/src/commands/bundle/index.ts | 17 +++++ packages/cli/src/commands/index.ts | 14 ++++ 5 files changed, 191 insertions(+) create mode 100644 packages/cli/src/commands/bundle/bundleApp.ts create mode 100644 packages/cli/src/commands/bundle/bundleBackend.ts create mode 100644 packages/cli/src/commands/bundle/command.ts create mode 100644 packages/cli/src/commands/bundle/index.ts diff --git a/packages/cli/src/commands/bundle/bundleApp.ts b/packages/cli/src/commands/bundle/bundleApp.ts new file mode 100644 index 0000000000..1ebd1f044e --- /dev/null +++ b/packages/cli/src/commands/bundle/bundleApp.ts @@ -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 BundleAppOptions { + writeStats: boolean; + configPaths: string[]; +} + +export async function bundleApp(options: BundleAppOptions) { + 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, + })), + }); +} diff --git a/packages/cli/src/commands/bundle/bundleBackend.ts b/packages/cli/src/commands/bundle/bundleBackend.ts new file mode 100644 index 0000000000..0f240d87dc --- /dev/null +++ b/packages/cli/src/commands/bundle/bundleBackend.ts @@ -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 BundleBackendOptions { + skipBuildDependencies: boolean; +} + +export async function bundleBackend(options: BundleBackendOptions) { + 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); + } +} diff --git a/packages/cli/src/commands/bundle/command.ts b/packages/cli/src/commands/bundle/command.ts new file mode 100644 index 0000000000..a13faeee62 --- /dev/null +++ b/packages/cli/src/commands/bundle/command.ts @@ -0,0 +1,46 @@ +/* + * 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 { Command } from 'commander'; +import { paths } from '../../lib/paths'; +import { readPackageRole } from '../../lib/role/packageRoles'; +import { bundleApp } from './bundleApp'; +import { bundleBackend } from './bundleBackend'; + +export async function command(cmd: Command): Promise { + const pkg = await fs.readJson(paths.resolveTarget('package.json')); + const roleInfo = readPackageRole(pkg); + if (!roleInfo) { + throw new Error(`Target package must have 'backstage.role' set`); + } + + const options = { + configPaths: cmd.config as string[], + writeStats: Boolean(cmd.stats), + skipBuildDependencies: Boolean(cmd.skipBuildDependencies), + }; + + if (roleInfo.role === 'app') { + return bundleApp(options); + } else if (roleInfo.role === 'backend') { + return bundleBackend(options); + } + + throw new Error( + `Bundle command is not supported for package role '${roleInfo.role}'`, + ); +} diff --git a/packages/cli/src/commands/bundle/index.ts b/packages/cli/src/commands/bundle/index.ts new file mode 100644 index 0000000000..680fe9e11d --- /dev/null +++ b/packages/cli/src/commands/bundle/index.ts @@ -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'; diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 362771f8f2..e834e0e494 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -133,6 +133,20 @@ export function registerCommands(program: CommanderStatic) { .option('--experimental-type-build', 'Enable experimental type build') .action(lazy(() => import('./build').then(m => m.default))); + program + .command('bundle') + .description('Bundle a package for deployment') + .option( + '--skip-build-dependencies', + 'Skip the automatic building of local dependencies', + ) + .option( + '--stats', + 'If bundle stats are available, write them to the output directory', + ) + .option(...configOption) + .action(lazy(() => import('./bundle').then(m => m.command))); + program .command('lint') .option(