UNPKG

@travetto/pack

Version:

Code packing utilities

257 lines (219 loc) 9.42 kB
import fs from 'node:fs/promises'; import path from 'node:path'; import { cliTpl } from '@travetto/cli'; import { Env, Runtime, RuntimeIndex } from '@travetto/runtime'; import type { CommonPackConfig } from '../../src/types.ts'; import { PackUtil } from './util.ts'; import { ActiveShellCommand, ShellCommands } from './shell.ts'; /** * General pack operations */ export class PackOperation { static async * title(config: CommonPackConfig, title: string): AsyncIterable<string[]> { if (config.ejectFile) { yield ActiveShellCommand.comment(title); yield ActiveShellCommand.echo(title); } else { yield [title]; } } /** * Clean out pack workspace, removing all content */ static async * clean(config: CommonPackConfig): AsyncIterable<string[]> { if (!config.clean) { return; } yield* PackOperation.title(config, cliTpl`${{ title: 'Cleaning Output' }} ${{ path: config.buildDirectory }}`); if (config.ejectFile) { yield ActiveShellCommand.rmRecursive(config.buildDirectory); if (config.output) { yield ActiveShellCommand.rmRecursive(config.output); } yield ActiveShellCommand.mkdir(config.buildDirectory); } else { await fs.rm(config.buildDirectory, { recursive: true, force: true }); if (config.output) { await fs.rm(config.output, { recursive: true, force: true }); } await fs.mkdir(config.buildDirectory, { recursive: true }); } } /** * Invoke bundler (rollup) to produce output in workspace folder */ static async * bundle(config: CommonPackConfig): AsyncIterable<string[]> { const cwd = RuntimeIndex.outputRoot; const out = RuntimeIndex.manifest.build.outputFolder; const bundleCommand = [process.argv0, RuntimeIndex.resolvePackageCommand('rollup'), '-c', RuntimeIndex.resolveFileImport(config.rollupConfiguration)]; const entryPointFile = RuntimeIndex.getFromImport(config.entryPoint)!.outputFile.split(`${out}/`)[1]; const env = { ...Object.fromEntries(([ ['BUNDLE_ENTRY', entryPointFile], ['BUNDLE_MAIN_FILE', config.mainFile], ['BUNDLE_COMPRESS', config.minify], ['BUNDLE_SOURCEMAP', config.sourcemap], ['BUNDLE_SOURCES', config.includeSources], ['BUNDLE_OUTPUT', config.buildDirectory], ['BUNDLE_ENV_FILE', config.envFile], ['BUNDLE_EXTERNAL', config.externalDependencies.map(module => module.split(':')[0]).join(',')] ] as const) .filter(pair => pair[1] === false || pair[1]) .map(pair => [pair[0], `${pair[1]}`]) ), ...Env.TRV_MANIFEST.export(RuntimeIndex.getModule(config.module)!.outputPath), }; const properties = (['minify', 'sourcemap', 'entryPoint'] as const) .map(key => cliTpl`${{ subtitle: key }}=${{ param: config[key] }}`).join(' '); yield* PackOperation.title(config, cliTpl`${{ title: 'Bundling Output' }} ${properties}`); if (config.ejectFile) { yield* Object .entries(env) .filter(pair => !!pair[1]) .map(pair => ActiveShellCommand.export(pair[0], pair[1])); yield ActiveShellCommand.chdir(cwd); yield bundleCommand; yield ActiveShellCommand.chdir(path.resolve()); } else { await PackUtil.runCommand(bundleCommand, { cwd, env: { ...process.env, ...env }, stdio: [0, 'pipe', 2] }); const stat = await fs.stat(path.resolve(config.buildDirectory, config.mainFile)); yield [cliTpl`${{ title: 'Bundled Output ' }} ${{ identifier: 'sizeKb' }}=${{ param: Math.trunc(stat.size / 2 ** 10) }}`]; } } /** * Write out package.json, to help define how output .js file should be interpreted */ static async * writePackageJson(config: CommonPackConfig): AsyncIterable<string[]> { const file = 'package.json'; const pkg = { type: 'module', main: config.mainFile }; yield* PackOperation.title(config, cliTpl`${{ title: 'Writing' }} ${{ path: file }}`); if (config.ejectFile) { yield* ActiveShellCommand.createFile( path.resolve(config.buildDirectory, file), [JSON.stringify(pkg)] ); } else { await PackUtil.writeRawFile( path.resolve(config.buildDirectory, file), [JSON.stringify(pkg, null, 2)] ); } } /** * Define .env.js file to control manifest location */ static async * writeEnv(config: CommonPackConfig): AsyncIterable<string[]> { const file = path.resolve(config.buildDirectory, config.envFile); const env = { ...Env.NODE_ENV.export('production'), ...Env.TRV_MANIFEST.export(config.manifestFile), ...Env.TRV_MODULE.export(config.module), ...Env.TRV_CLI_IPC.export(undefined), ...Env.TRV_RESOURCE_OVERRIDES.export({ '@#resources': '@@#resources', ...(config.includeWorkspaceResources ? { '@@#resources': `@@#${config.workspaceResourceFolder}` } : {}) }) }; yield* PackOperation.title(config, cliTpl`${{ title: 'Writing' }} ${{ path: file }}`); if (config.ejectFile) { yield* ActiveShellCommand.createFile( path.resolve(config.buildDirectory, file), PackUtil.buildEnvFile(env) ); } else { await PackUtil.writeRawFile( path.resolve(config.buildDirectory, file), PackUtil.buildEnvFile(env) ); } } /** * Create launcher scripts (.sh, .cmd) to run output */ static async * writeEntryScript(config: CommonPackConfig): AsyncIterable<string[]> { if (!config.mainScripts && !config.entryPoint.includes('@travetto/cli')) { return; } const title = 'Writing entry scripts'; for (const sh of [ShellCommands.posix, ShellCommands.win32]) { const { ext, contents } = sh.script( sh.callCommandWithAllArgs('node', config.mainFile, ...config.entryArguments), true ); const file = `${config.mainName}${ext}`; const args = config.entryArguments.join(' '); yield* PackOperation.title(config, cliTpl`${{ title }} ${{ path: file }} args=(${{ param: args }})`); if (config.ejectFile) { yield* ActiveShellCommand.createFile(path.resolve(config.buildDirectory, file), contents, '755'); } else { await PackUtil.writeRawFile(path.resolve(config.buildDirectory, file), contents, '755'); } } } /** * Copy over repo /resources folder into workspace, will get packaged into final output */ static async * copyMonoRepoResources(config: CommonPackConfig): AsyncIterable<string[]> { if (!config.includeWorkspaceResources) { return; } yield* PackOperation.title(config, cliTpl`${{ title: 'Copying over workspace resources' }}`); const destinationDirectory = path.resolve(config.buildDirectory, config.workspaceResourceFolder); const sourceDirectory = Runtime.workspaceRelative('resources'); if (config.ejectFile) { yield ActiveShellCommand.copyRecursive(sourceDirectory, destinationDirectory, true); } else { await PackUtil.copyRecursive(sourceDirectory, destinationDirectory, true); } } /** * Copy over /resources folder into workspace, will get packaged into final output */ static async * copyResources(config: CommonPackConfig): AsyncIterable<string[]> { const resources = { count: RuntimeIndex.mainModule.files.resources?.length ?? 0, sourceDirectory: path.resolve(Runtime.mainSourcePath, 'resources'), destinationDirectory: path.resolve(config.buildDirectory, 'resources') }; yield* PackOperation.title(config, cliTpl`${{ title: 'Copying over module resources' }}`); if (config.ejectFile) { if (resources.count) { yield ActiveShellCommand.copyRecursive(resources.sourceDirectory, path.resolve(config.buildDirectory, 'resources'), true); } } else { if (resources.count) { await PackUtil.copyRecursive(resources.sourceDirectory, resources.destinationDirectory, true); } } } /** * Produce the output manifest, only including production dependencies */ static async * writeManifest(config: CommonPackConfig): AsyncIterable<string[]> { const out = path.resolve(config.buildDirectory, config.manifestFile); const cmd = [process.argv0, RuntimeIndex.resolvePackageCommand('trvc'), 'manifest:production', out]; const env = { ...Env.TRV_MODULE.export(config.module) }; yield* PackOperation.title(config, cliTpl`${{ title: 'Writing Manifest' }} ${{ path: config.manifestFile }}`); if (config.ejectFile) { yield [...Object.entries(env).map(([key, value]) => `${key}=${value}`), ...cmd]; } else { await PackUtil.runCommand(cmd, { env: { ...process.env, ...env } }); } } /** * Generate ZIP file for workspace */ static async * compress(config: CommonPackConfig): AsyncIterable<string[]> { yield* PackOperation.title(config, cliTpl`${{ title: 'Compressing' }} ${{ path: config.output }}`); if (config.ejectFile) { await ActiveShellCommand.mkdir(path.dirname(config.output)); yield ActiveShellCommand.chdir(config.buildDirectory); yield ActiveShellCommand.zip(config.output); yield ActiveShellCommand.chdir(path.resolve()); } else { await fs.mkdir(path.dirname(config.output), { recursive: true }); await PackUtil.runCommand(ActiveShellCommand.zip(config.output), { cwd: config.buildDirectory }); } } }