@travetto/pack
Version:
Code packing utilities
258 lines (219 loc) • 8.77 kB
text/typescript
import fs from 'node:fs/promises';
import path from 'node:path';
import { cliTpl } from '@travetto/cli';
import { Env, Runtime, RuntimeIndex } from '@travetto/runtime';
import { 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(cfg: CommonPackConfig, title: string): AsyncIterable<string[]> {
if (cfg.ejectFile) {
yield ActiveShellCommand.comment(title);
yield ActiveShellCommand.echo(title);
} else {
yield [title];
}
}
/**
* Clean out pack workspace, removing all content
*/
static async * clean(cfg: CommonPackConfig): AsyncIterable<string[]> {
if (!cfg.clean) {
return;
}
yield* PackOperation.title(cfg, cliTpl`${{ title: 'Cleaning Output' }} ${{ path: cfg.buildDir }}`);
if (cfg.ejectFile) {
yield ActiveShellCommand.rmRecursive(cfg.buildDir);
if (cfg.output) {
yield ActiveShellCommand.rmRecursive(cfg.output);
}
yield ActiveShellCommand.mkdir(cfg.buildDir);
} else {
await fs.rm(cfg.buildDir, { recursive: true, force: true });
if (cfg.output) {
await fs.rm(cfg.output, { recursive: true, force: true });
}
await fs.mkdir(cfg.buildDir, { recursive: true });
}
}
/**
* Invoke bundler (rollup) to produce output in workspace folder
*/
static async * bundle(cfg: CommonPackConfig): AsyncIterable<string[]> {
const cwd = RuntimeIndex.outputRoot;
const out = RuntimeIndex.manifest.build.outputFolder;
const bundleCommand = ['npx', 'rollup', '-c', RuntimeIndex.resolveFileImport(cfg.rollupConfiguration)];
const entryPointFile = RuntimeIndex.getFromImport(cfg.entryPoint)!.outputFile.split(`${out}/`)[1];
const env = {
...Object.fromEntries(([
['BUNDLE_ENTRY', entryPointFile],
['BUNDLE_MAIN_FILE', cfg.mainFile],
['BUNDLE_COMPRESS', cfg.minify],
['BUNDLE_SOURCEMAP', cfg.sourcemap],
['BUNDLE_SOURCES', cfg.includeSources],
['BUNDLE_OUTPUT', cfg.buildDir],
['BUNDLE_FORMAT', Runtime.workspace.type],
['BUNDLE_ENV_FILE', cfg.envFile],
['BUNDLE_EXTERNAL', cfg.externalDependencies.map(x => x.split(':')[0]).join(',')]
] as const)
.filter(x => x[1] === false || x[1])
.map(x => [x[0], `${x[1]}`])
),
...Env.TRV_MANIFEST.export(RuntimeIndex.getModule(cfg.module)!.outputPath),
};
const props = (['minify', 'sourcemap', 'entryPoint'] as const)
.map(k => cliTpl`${{ subtitle: k }}=${{ param: cfg[k] }}`).join(' ');
yield* PackOperation.title(cfg, cliTpl`${{ title: 'Bundling Output' }} ${props}`);
if (cfg.ejectFile) {
yield* Object.entries(env).filter(x => !!x[1]).map(x =>
ActiveShellCommand.export(x[0], x[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(cfg.buildDir, cfg.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(cfg: CommonPackConfig): AsyncIterable<string[]> {
const file = 'package.json';
const pkg = { type: Runtime.workspace.type, main: cfg.mainFile };
yield* PackOperation.title(cfg, cliTpl`${{ title: 'Writing' }} ${{ path: file }}`);
if (cfg.ejectFile) {
yield* ActiveShellCommand.createFile(
path.resolve(cfg.buildDir, file),
[JSON.stringify(pkg)]
);
} else {
await PackUtil.writeRawFile(
path.resolve(cfg.buildDir, file),
[JSON.stringify(pkg, null, 2)]
);
}
}
/**
* Define .env.js file to control manifest location
*/
static async * writeEnv(cfg: CommonPackConfig): AsyncIterable<string[]> {
const file = path.resolve(cfg.buildDir, cfg.envFile);
const env = {
...Env.NODE_ENV.export('production'),
...Env.TRV_MANIFEST.export(cfg.manifestFile),
...Env.TRV_MODULE.export(cfg.module),
...Env.TRV_CLI_IPC.export(undefined),
...Env.TRV_RESOURCE_OVERRIDES.export({
'@#resources': '@@#resources',
...(cfg.includeWorkspaceResources ? {
'@@#resources': `@@#${cfg.workspaceResourceFolder}`
} : {})
})
};
yield* PackOperation.title(cfg, cliTpl`${{ title: 'Writing' }} ${{ path: file }}`);
if (cfg.ejectFile) {
yield* ActiveShellCommand.createFile(
path.resolve(cfg.buildDir, file),
PackUtil.buildEnvFile(env)
);
} else {
await PackUtil.writeRawFile(
path.resolve(cfg.buildDir, file),
PackUtil.buildEnvFile(env)
);
}
}
/**
* Create launcher scripts (.sh, .cmd) to run output
*/
static async * writeEntryScript(cfg: CommonPackConfig): AsyncIterable<string[]> {
if (!cfg.mainScripts && !cfg.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', cfg.mainFile, ...cfg.entryArguments), true
);
const file = `${cfg.mainName}${ext}`;
const args = cfg.entryArguments.join(' ');
yield* PackOperation.title(cfg, cliTpl`${{ title }} ${{ path: file }} args=(${{ param: args }})`);
if (cfg.ejectFile) {
yield* ActiveShellCommand.createFile(path.resolve(cfg.buildDir, file), contents, '755');
} else {
await PackUtil.writeRawFile(path.resolve(cfg.buildDir, file), contents, '755');
}
}
}
/**
* Copy over repo /resources folder into workspace, will get packaged into final output
*/
static async * copyMonoRepoResources(cfg: CommonPackConfig): AsyncIterable<string[]> {
if (!cfg.includeWorkspaceResources) {
return;
}
yield* PackOperation.title(cfg, cliTpl`${{ title: 'Copying over workspace resources' }}`);
const dest = path.resolve(cfg.buildDir, cfg.workspaceResourceFolder);
const src = Runtime.workspaceRelative('resources');
if (cfg.ejectFile) {
yield ActiveShellCommand.copyRecursive(src, dest, true);
} else {
await PackUtil.copyRecursive(src, dest, true);
}
}
/**
* Copy over /resources folder into workspace, will get packaged into final output
*/
static async * copyResources(cfg: CommonPackConfig): AsyncIterable<string[]> {
const resources = {
count: RuntimeIndex.mainModule.files.resources?.length ?? 0,
src: path.resolve(Runtime.mainSourcePath, 'resources'),
dest: path.resolve(cfg.buildDir, 'resources')
};
yield* PackOperation.title(cfg, cliTpl`${{ title: 'Copying over module resources' }}`);
if (cfg.ejectFile) {
if (resources.count) {
yield ActiveShellCommand.copyRecursive(resources.src, path.resolve(cfg.buildDir, 'resources'), true);
}
} else {
if (resources.count) {
await PackUtil.copyRecursive(resources.src, resources.dest, true);
}
}
}
/**
* Produce the output manifest, only including prod dependencies
*/
static async * writeManifest(cfg: CommonPackConfig): AsyncIterable<string[]> {
const out = path.resolve(cfg.buildDir, cfg.manifestFile);
const cmd = ['npx', 'trvc', 'manifest', '--prod', out];
const env = { ...Env.TRV_MODULE.export(cfg.module) };
yield* PackOperation.title(cfg, cliTpl`${{ title: 'Writing Manifest' }} ${{ path: cfg.manifestFile }}`);
if (cfg.ejectFile) {
yield [...Object.entries(env).map(([k, v]) => `${k}=${v}`), ...cmd];
} else {
await PackUtil.runCommand(cmd, { env: { ...process.env, ...env } });
}
}
/**
* Generate ZIP file for workspace
*/
static async * compress(cfg: CommonPackConfig): AsyncIterable<string[]> {
yield* PackOperation.title(cfg, cliTpl`${{ title: 'Compressing' }} ${{ path: cfg.output }}`);
if (cfg.ejectFile) {
await ActiveShellCommand.mkdir(path.dirname(cfg.output));
yield ActiveShellCommand.chdir(cfg.buildDir);
yield ActiveShellCommand.zip(cfg.output);
yield ActiveShellCommand.chdir(path.resolve());
} else {
await fs.mkdir(path.dirname(cfg.output), { recursive: true });
await PackUtil.runCommand(ActiveShellCommand.zip(cfg.output), { cwd: cfg.buildDir });
}
}
}