@roots/bud
Version:
Configurable, extensible build tools for modern single and multi-page web applications
132 lines (131 loc) • 4.8 kB
JavaScript
import { exit, stderr, stdin, stdout } from 'node:process';
import BudCommand from '@roots/bud/cli/commands';
import BudBuildCommand from '@roots/bud/cli/commands/build';
import BudBuildDevelopmentCommand from '@roots/bud/cli/commands/build/development';
import BudBuildProductionCommand from '@roots/bud/cli/commands/build/production';
import BudCleanCommand from '@roots/bud/cli/commands/clean';
import BudConfigCommand from '@roots/bud/cli/commands/config';
import BudDoctorCommand from '@roots/bud/cli/commands/doctor';
import BudEnvCommand from '@roots/bud/cli/commands/env';
import BudReplCommand from '@roots/bud/cli/commands/repl';
import BudUpgradeCommand from '@roots/bud/cli/commands/upgrade';
import BudViewCommand from '@roots/bud/cli/commands/view';
import BudWebpackCommand from '@roots/bud/cli/commands/webpack';
import { Finder } from '@roots/bud/cli/finder';
import getContext, {} from '@roots/bud/context';
import * as args from '@roots/bud-framework/bootstrap/args';
import { Builtins, Cli } from '@roots/bud-support/clipanion';
import { BudError } from '@roots/bud-support/errors';
import { render as renderError } from '@roots/bud-support/errors';
import isFunction from '@roots/bud-support/isFunction';
import isUndefined from '@roots/bud-support/isUndefined';
import logger from '@roots/bud-support/logger';
/**
* Error handler
* @param error
*/
const onError = (error) => {
renderError(error);
exit(1);
};
/**
* {@link Context}
*/
const context = await getContext({ stderr, stdin, stdout }).catch(onError);
context.stderr = stderr;
context.stdin = stdin;
context.stdout = stdout;
/**
* {@link Cli}
*/
const application = new Cli({
binaryLabel: `bud`,
binaryName: `bud`,
binaryVersion: context.bud.version,
enableColors: context.env?.color !== `false`,
});
/**
* {@link Finder}
*/
const finder = new Finder(context, application);
[
Builtins.HelpCommand,
Builtins.VersionCommand,
BudCommand,
BudBuildCommand,
BudBuildDevelopmentCommand,
BudBuildProductionCommand,
BudCleanCommand,
BudConfigCommand,
BudDoctorCommand,
BudEnvCommand,
BudReplCommand,
BudUpgradeCommand,
BudViewCommand,
BudWebpackCommand,
].map(command => application.register(command));
/**
* --force flag
* {@link Context.force}
*/
const forceFlag = !isUndefined(context.force) ? context.force : false;
/**
* Register command extensions
*
* @param force - force rebuilding of extension cache
*/
const registerFoundCommands = async (force = forceFlag) => {
if (!force) {
logger.scope(`cli`).log(`Loading commands from cache...`);
await finder.init();
}
else {
logger.scope(`cli`).log(`Searching for commands...`);
await finder.getModules();
if (finder.paths.length > 0) {
logger.scope(`cli`).log(`Found ${finder.paths.length} commands.`);
logger.scope(`cli`).log(`Updating command cache...`);
await finder.cacheWrite();
}
}
if (!finder.paths.length) {
logger.scope(`cli`).log(`No commands found.`);
return;
}
const extensions = await finder.importCommands();
return await Promise.all(extensions.map(async ([path, registerCommand]) => {
logger.scope(`cli`).log(`Registering ${path}...`);
if (!isFunction(registerCommand)) {
logger.scope(`cli`).error(`Error registering ${path}`);
throw BudError.normalize(`${path} does not export a module exporting a registration function.`, {
details: `Error registering ${path}`,
thrownBy: import.meta.url,
});
}
await registerCommand(application).catch(error => {
logger
.scope(`cli`)
.error(`Error registering ${path}: ${error.message ?? error}`);
throw BudError.normalize(`Error registering ${path}`, {
details: `Error registering ${path}`,
origin: error,
thrownBy: import.meta.url,
});
});
})).catch(onError);
};
// first round will attempt to register extensions from cache
// if cache does not exist, it will be created
await registerFoundCommands().catch(async (error) => {
// first round failed to load extensions for some reason
// maybe a cached extension no longer exists
// or maybe a cached extension has been updated and the old path is resolvable but no longer valid
// so we'll try searching again and force a rebuild of the cache
// if it still fails we'll throw and exit the process
await registerFoundCommands(true).catch(onError);
});
/**
* Run application and exit when process is complete
*/
await application.runExit(args.raw, context);
export { application, Builtins, Cli };