@roots/bud
Version:
Configurable, extensible build tools for modern single and multi-page web applications
279 lines (272 loc) • 10.4 kB
JavaScript
import { __decorate } from "tslib";
import { jsx as _jsx } from "@roots/bud-support/jsx-runtime";
import { join, parse } from 'node:path';
import { exit } from 'node:process';
import basedir from '@roots/bud/cli/flags/basedir';
import cache from '@roots/bud/cli/flags/cache';
import clean from '@roots/bud/cli/flags/clean';
import color from '@roots/bud/cli/flags/color';
import debug from '@roots/bud/cli/flags/debug';
import dry from '@roots/bud/cli/flags/dry';
import filter from '@roots/bud/cli/flags/filter';
import force from '@roots/bud/cli/flags/force';
import ignoreErrors from '@roots/bud/cli/flags/ignoreErrors';
import input from '@roots/bud/cli/flags/input';
import log from '@roots/bud/cli/flags/log';
import mode from '@roots/bud/cli/flags/mode';
import notify from '@roots/bud/cli/flags/notify';
import output from '@roots/bud/cli/flags/output';
import publicPath from '@roots/bud/cli/flags/publicPath';
import silent from '@roots/bud/cli/flags/silent';
import storage from '@roots/bud/cli/flags/storage';
import use from '@roots/bud/cli/flags/use';
import verbose from '@roots/bud/cli/flags/verbose';
import override, {} from '@roots/bud/cli/helpers/override';
import * as instance from '@roots/bud/instance';
import { Bud } from '@roots/bud-framework';
import { Command, Option } from '@roots/bud-support/clipanion';
import { bind } from '@roots/bud-support/decorators/bind';
import { BudError } from '@roots/bud-support/errors';
import { render as renderError } from '@roots/bud-support/errors';
import figures from '@roots/bud-support/figures';
import * as Ink from '@roots/bud-support/ink';
import { render } from '@roots/bud-support/ink/instance';
import isNumber from '@roots/bud-support/isNumber';
import noop from '@roots/bud-support/noop';
/**
* Base {@link Command}
*/
export default class BudCommand extends Command {
/**
* {@link Command.paths}
*/
static paths = [Command.Default];
/**
* {@link Command.usage}
*/
static usage = Command.Usage({
category: `build`,
description: `Configurable, extensible build tools for modern single and multi-page web applications`,
details: `\
Documentation for this command is available at https://bud.js.org/.
Any flags which accept a boolean can be negated with the \`--no-\` prefix. For example, \`--no-color\` will disable color output.
Any command can be exited with \`esc\` or \`ctrl+c\`.
Common tasks:
- \`bud build production\` compiles source assets in \`production\` mode.
- \`bud build development\` compiles source assets in \`development\` mode and updates modules in the browser.
- \`bud doctor\` checks your system and project for common configuration issues. Try this before making an issue in the bud.js repo.
- \`bud upgrade\` upgrades bud.js core packages and extensions to the latest version.
Helpful flags:
- \`--help\` can be appened to any command for usage information.
- \`--basedir\` sets the working directory for bud and will be treated as project root.
- \`--storage\` sets the storage directory. Defaults to the system tmp dir.
- \`--log\` enables logging. Use \`--log\` in tandem with \`--verbose\` for more detailed output.
- \`--debug\` enables debug mode. It is very noisy in the terminal but also produces useful output files in the storage directory.
`,
examples: [[`Interactive menu of available subcommands`, `$0`]],
});
basedir = basedir;
cache = cache;
clean = clean;
color = color;
debug = debug;
dry = dry(true);
filter = filter;
force = force;
ignoreErrors = ignoreErrors;
input = input;
log = log;
mode = mode;
output = output;
notify = notify;
publicPath = publicPath;
silent = silent(true);
storage = storage;
use = use;
verbose = verbose;
/**
* Render static cli output
*/
renderStatic(el) {
return render(_jsx(Ink.Static, { items: [0], children: (e, i) => _jsx(Ink.Fragment, { children: el }, i) }));
}
/**
* Execute arbitrary sh command with inherited stdio
*/
async $(bin, args, options = {}, bail = () => setTimeout(exit, 100)) {
const { execa } = await import(`@roots/bud-support/execa`);
const cwd = this.bud?.path() ?? process.cwd();
return execa(bin, args.filter(Boolean), {
cwd,
encoding: `utf8`,
env: { NODE_ENV: `development` },
extendEnv: true,
stdio: `inherit`,
...options,
})
.on(`data`, data => data && this.context.stdout.write(data))
.on(`error`, error => error && this.context.stderr.write(error.message))
.on(`exit`, bail)
.on(`disconnect`, bail)
.on(`close`, bail);
}
async override([argValue, envKey, manifestPath, callback]) {
await override(this.bud, argValue, envKey, manifestPath, callback);
}
/**
* Apply context from manifest to bud.js instance
*/
async applyUserOptions(bud) {
await Promise.all([
[
this.cache,
`BUD_CACHE`,
`cache`,
b => async (v) => b.persist(v),
],
[
this.input,
`BUD_PATH_INPUT`,
`paths.input`,
b => async (v) => b.setPath(`@src`, v),
],
[
this.output,
`BUD_PATH_OUTPUT`,
`paths.output`,
b => async (v) => b.setPath(`@dist`, v),
],
[
this.publicPath,
`BUD_PATH_PUBLIC`,
`paths.public`,
b => async (v) => b.setPublicPath(v),
],
[
this.storage,
`BUD_PATH_STORAGE`,
`paths.storage`,
b => async (v) => b.hooks.on(`location.@storage`, b.relPath(v)),
],
[
this.use,
`BUD_USE`,
undefined,
b => async (v) => await b.extensions.add(v.reduce((a, v) => [...a, ...v.split(`,`)], [])),
],
].map(this.override));
}
/**
* Handle errors
*/
async catch(error) {
if (!error.isBudError)
error = BudError.normalize(error);
if (this.bud?.notifier?.notify) {
this.bud.notifier.notify({
group: this.bud.label,
message: error?.message,
subtitle: error?.name ?? `Error`,
title: this.bud.label ?? `bud.js`,
});
}
renderError(error);
if ((!this.bud || this.bud.isProduction) && this.ignoreErrors !== true)
exit(1);
}
/**
* {@link Command.execute}
*/
async execute() {
const { Menu } = await import(`@roots/bud/cli/components/Menu`);
render(_jsx(Menu, { cli: this.cli }));
}
/**
* Make {@link Bud} instance
*/
async makeBud() {
this.context = {
...this.context,
dry: this.dry,
ignoreErrors: this.ignoreErrors,
mode: this.mode ?? this.context.mode ?? `production`,
silent: this.silent,
};
this.bud = await instance.get().initialize(this.context);
await this.applyUserOptions(this.bud);
this.bud.hooks.action(`config.after`, this.applyUserOptions);
return await this.bud.processConfigs();
}
/**
* Run a binary.
*/
async run(path, userArgs, defaultArgs = []) {
let [signifier, ...pathParts] = path;
const binaryPath = await this.bud.module
.getDirectory(signifier)
.catch(this.catch);
if (typeof binaryPath !== `string`) {
process.exitCode = 3;
throw new Error(`Could not find ${signifier} module`);
}
let binary = join(binaryPath, ...pathParts);
if (!(await this.bud.fs.exists(binary))) {
const checkedPaths = [];
const parsedParts = parse(join(...pathParts));
const extensions = [`.js`, `.mjs`, `.cjs`].filter(ext => ext !== parsedParts.ext);
pathParts = await extensions.reduce(async (promise, ext) => {
const result = await promise;
if (result)
return result;
const path = [parsedParts.dir, `${parsedParts.name}${ext}`];
checkedPaths.push(join(...path));
if (await this.bud.fs.exists(join(binaryPath, ...path)))
return path;
}, Promise.resolve(null));
if (!pathParts) {
process.exitCode = 2;
throw new Error([
`Could not find ${signifier} binary`,
``,
`Checked:`,
`- ${binary}`,
...checkedPaths
.map(path => join(binaryPath, path))
.map(path => `- ${path}`),
].join(`\n`));
}
binary = join(binaryPath, ...pathParts);
}
const binaryArguments = userArgs?.length ? userArgs : defaultArgs;
this.context.stdout.write(`${figures.pointerSmall} ${signifier} ${binaryArguments.join(` `)}\n`
.replace(this.bud.path(`@src`), `@src`)
.replace(this.bud.path(), ``));
const result = await this.$(binary, binaryArguments).catch(noop);
const exitCode = result && isNumber(result?.exitCode) ? result.exitCode : 1;
if (exitCode) {
this.context.stderr.write(`${figures.cross} exit code ${exitCode}\n`);
return exitCode;
}
this.context.stdout.write(`${figures.tick} success\n`);
return exitCode;
}
}
__decorate([
bind
], BudCommand.prototype, "$", null);
__decorate([
bind
], BudCommand.prototype, "override", null);
__decorate([
bind
], BudCommand.prototype, "applyUserOptions", null);
__decorate([
bind
], BudCommand.prototype, "catch", null);
__decorate([
bind
], BudCommand.prototype, "makeBud", null);
__decorate([
bind
], BudCommand.prototype, "run", null);
export { Option };