diff --git a/.changeset/dull-dingos-dream.md b/.changeset/dull-dingos-dream.md new file mode 100644 index 0000000000..13a5271987 --- /dev/null +++ b/.changeset/dull-dingos-dream.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Update experimental backend bundle command to only output archives to `dist/` instead of a full workspace mirror in `dist-workspace/`. diff --git a/packages/cli/src/commands/backend/bundle.ts b/packages/cli/src/commands/backend/bundle.ts index e604ae9733..4d298f3735 100644 --- a/packages/cli/src/commands/backend/bundle.ts +++ b/packages/cli/src/commands/backend/bundle.ts @@ -14,26 +14,59 @@ * limitations under the License. */ -import { Command } from 'commander'; +import os from 'os'; import fs from 'fs-extra'; +import { resolve as resolvePath } from 'path'; +import tar, { CreateOptions } from 'tar'; +import { Command } from 'commander'; 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 PKG_PATH = 'package.json'; -const TARGET_DIR = 'dist-workspace'; +const BUNDLE_FILE = 'bundle.tar.gz'; +const SKELETON_FILE = 'skeleton.tar.gz'; export default async (cmd: Command) => { - const targetDir = paths.resolveTarget(TARGET_DIR); - const pkgPath = paths.resolveTarget(PKG_PATH); - const pkg = await fs.readJson(pkgPath); + const targetDir = paths.resolveTarget('dist'); + const pkg = await fs.readJson(paths.resolveTarget('package.json')); - await fs.remove(targetDir); - await fs.mkdir(targetDir); - await createDistWorkspace([pkg.name], { - targetDir: targetDir, - buildDependencies: Boolean(cmd.build), - parallel: parseParallel(process.env[PARALLEL_ENV_VAR]), - skeleton: 'skeleton.tar', - }); + // 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: Boolean(cmd.build), + 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/lib/packager/index.ts b/packages/cli/src/lib/packager/index.ts index 8653e66d37..e9388ad9bc 100644 --- a/packages/cli/src/lib/packager/index.ts +++ b/packages/cli/src/lib/packager/index.ts @@ -67,6 +67,11 @@ type Options = { */ buildDependencies?: boolean; + /** + * When `buildDependencies` is set, this list of packages will not be built even if they are dependencies. + */ + buildExcludes?: string[]; + /** * Enable (true/false) or control amount of (number) parallelism in some build steps. */ @@ -76,7 +81,7 @@ type Options = { * If set, creates a skeleton tarball that contains all package.json files * with the same structure as the workspace dir. */ - skeleton?: 'skeleton.tar'; + skeleton?: 'skeleton.tar' | 'skeleton.tar.gz'; }; /** @@ -98,7 +103,11 @@ export async function createDistWorkspace( const targets = await findTargetPackages(packageNames); if (options.buildDependencies) { - const scopeArgs = targets.flatMap(target => ['--scope', target.name]); + const exclude = options.buildExcludes ?? []; + const scopeArgs = targets + .filter(target => !exclude.includes(target.name)) + .flatMap(target => ['--scope', target.name]); + const lernaArgs = options.parallel && Number.isInteger(options.parallel) ? ['--concurrency', options.parallel.toString()] @@ -131,6 +140,7 @@ export async function createDistWorkspace( cwd: targetDir, portable: true, noMtime: true, + gzip: options.skeleton.endsWith('.gz'), } as CreateOptions & { noMtime: boolean }, skeletonFiles, );