From 6e124e82c426598fd11fb7cbd3deb649542ee488 Mon Sep 17 00:00:00 2001 From: Kelly Mears Date: Wed, 12 Jul 2023 12:22:04 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=A9=B9=20fix:=20register=20command=20type?= =?UTF-8?q?=20error=20after=20extension=20uninstall=20(#2372)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes type error after uninstalling extension which registers commands. Without this change users would need to run `yarn bud clean storage` in order to clean out the stale entry. ## Type of change **PATCH: backwards compatible change** --- .../src/bud/commands/bud.eslint.command.tsx | 4 +- .../src/bud/commands/bud.prettier.command.tsx | 2 +- .../bud/commands/bud.stylelint.command.tsx | 3 +- .../src/bud/commands/tailwindcss/index.tsx | 2 +- .../src/bud/commands/bud.ts.check.command.tsx | 2 +- .../src/bud/commands/bud.tsc.command.tsx | 2 +- sources/@roots/bud/src/cli/app.tsx | 26 ++--- .../cli/commands/bud.build.development.tsx | 19 +--- .../@roots/bud/src/cli/commands/bud.build.tsx | 1 - .../@roots/bud/src/cli/commands/bud.clean.tsx | 11 ++- sources/@roots/bud/src/cli/commands/bud.tsx | 23 ++++- .../bud/src/cli/commands/bud.upgrade.tsx | 9 +- .../@roots/bud/src/cli/commands/bud.view.tsx | 8 +- .../bud/src/cli/commands/bud.webpack.tsx | 4 +- .../bud/src/cli/commands/doctor/index.tsx | 3 +- .../bud/src/cli/commands/repl/index.tsx | 1 + .../@roots/bud/src/cli/components/Menu.tsx | 91 ++++++++---------- sources/@roots/bud/src/cli/finder.ts | 95 ++++++++++--------- 18 files changed, 151 insertions(+), 155 deletions(-) diff --git a/sources/@roots/bud-eslint/src/bud/commands/bud.eslint.command.tsx b/sources/@roots/bud-eslint/src/bud/commands/bud.eslint.command.tsx index c708a0ede9..4699b74dcf 100644 --- a/sources/@roots/bud-eslint/src/bud/commands/bud.eslint.command.tsx +++ b/sources/@roots/bud-eslint/src/bud/commands/bud.eslint.command.tsx @@ -9,9 +9,9 @@ export class BudEslintCommand extends BudCommand { public static override paths = [[`lint`, `js`], [`eslint`]] public static override usage = BudCommand.Usage({ - category: `tools`, + category: `tool`, description: `eslint CLI passthrough`, - examples: [[`View eslint usage information`, `$0 eslint --help`]], + examples: [[`Run eslint on source files`, `$0 eslint`]], }) public options = Option.Proxy({name: `eslint passthrough options`}) diff --git a/sources/@roots/bud-prettier/src/bud/commands/bud.prettier.command.tsx b/sources/@roots/bud-prettier/src/bud/commands/bud.prettier.command.tsx index 957e840145..ee467c90c8 100644 --- a/sources/@roots/bud-prettier/src/bud/commands/bud.prettier.command.tsx +++ b/sources/@roots/bud-prettier/src/bud/commands/bud.prettier.command.tsx @@ -8,7 +8,7 @@ import {dry} from '@roots/bud/cli/decorators/dry' export class BudPrettierCommand extends BudCommand { public static override paths = [[`format`], [`prettier`]] public static override usage = Command.Usage({ - category: `tools`, + category: `tool`, description: `Prettier CLI`, examples: [[`View prettier usage information`, `$0 prettier --help`]], }) diff --git a/sources/@roots/bud-stylelint/src/bud/commands/bud.stylelint.command.tsx b/sources/@roots/bud-stylelint/src/bud/commands/bud.stylelint.command.tsx index c0069d3c9a..ca31f6d2bb 100644 --- a/sources/@roots/bud-stylelint/src/bud/commands/bud.stylelint.command.tsx +++ b/sources/@roots/bud-stylelint/src/bud/commands/bud.stylelint.command.tsx @@ -21,11 +21,10 @@ export class BudStylelintCommand extends BudCommand { * {@link BudCommand.usage} */ public static override usage = Command.Usage({ - category: `tools`, + category: `tool`, description: `stylelint CLI passthrough`, examples: [ [`Run stylelint on source files`, `$0 lint css`], - [`View stylelint usage information`, `$0 lint css --help`], ], }) diff --git a/sources/@roots/bud-tailwindcss/src/bud/commands/tailwindcss/index.tsx b/sources/@roots/bud-tailwindcss/src/bud/commands/tailwindcss/index.tsx index c330e3c567..8741d52692 100644 --- a/sources/@roots/bud-tailwindcss/src/bud/commands/tailwindcss/index.tsx +++ b/sources/@roots/bud-tailwindcss/src/bud/commands/tailwindcss/index.tsx @@ -9,7 +9,7 @@ export class BudTailwindCommand extends BudCommand { public static override paths = [[`tailwindcss`], [`tw`]] public static override usage = BudCommand.Usage({ - category: `tools`, + category: `tool`, description: `tailwindcss CLI passthrough`, examples: [ [`View tailwindcss usage information`, `$0 tailwindcss --help`], diff --git a/sources/@roots/bud-typescript/src/bud/commands/bud.ts.check.command.tsx b/sources/@roots/bud-typescript/src/bud/commands/bud.ts.check.command.tsx index df41610031..9c52df4e75 100644 --- a/sources/@roots/bud-typescript/src/bud/commands/bud.ts.check.command.tsx +++ b/sources/@roots/bud-typescript/src/bud/commands/bud.ts.check.command.tsx @@ -9,7 +9,7 @@ export class BudTSCheckCommand extends BudCommand { public static override paths = [[`ts`, `check`]] public static override usage = BudCommand.Usage({ - category: `tools`, + category: `tool`, description: `Typecheck source code`, details: ` This command runs the \`tsc\` command with the \`--noEmit\` flag. diff --git a/sources/@roots/bud-typescript/src/bud/commands/bud.tsc.command.tsx b/sources/@roots/bud-typescript/src/bud/commands/bud.tsc.command.tsx index 137eb1a4d1..1b1d89e32d 100644 --- a/sources/@roots/bud-typescript/src/bud/commands/bud.tsc.command.tsx +++ b/sources/@roots/bud-typescript/src/bud/commands/bud.tsc.command.tsx @@ -9,7 +9,7 @@ export class BudTSCCommand extends BudCommand { public static override paths = [[`tsc`]] public static override usage = Command.Usage({ - category: `tools`, + category: `tool`, description: `TypeScript CLI passthrough`, examples: [[`View tsc usage information`, `$0 tsc --help`]], }) diff --git a/sources/@roots/bud/src/cli/app.tsx b/sources/@roots/bud/src/cli/app.tsx index a42c7d03d0..1e88e08e95 100644 --- a/sources/@roots/bud/src/cli/app.tsx +++ b/sources/@roots/bud/src/cli/app.tsx @@ -6,6 +6,7 @@ import {Error} from '@roots/bud-dashboard/components/error' import {Builtins, Cli} from '@roots/bud-support/clipanion' import {BudError} from '@roots/bud-support/errors' import {render} from '@roots/bud-support/ink' +import isFunction from '@roots/bud-support/lodash/isFunction' import * as args from '@roots/bud-support/utilities/args' import BudCommand from '@roots/bud/cli/commands/bud' import BudBuildCommand from '@roots/bud/cli/commands/bud.build' @@ -60,19 +61,18 @@ application = new Cli({ commands.map(command => application.register(command)) -await Commands.get(application, context) - .getCommands() - .then(Commands.importCommandsFromPaths) - .then( - async registrar => - await Promise.all( - registrar.map( - async (registerCommand: (application: Cli) => Promise) => - await registerCommand(application).catch(onError), - ), - ), - ) - .catch(onError) +const finder = new Commands(context, application) +await finder.init() +const extensions = await finder.importCommands() + +await Promise.all( + extensions.map( + async (registerCommand: (application: Cli) => Promise) => { + if (!isFunction(registerCommand)) return + await registerCommand(application).catch(onError) + }, + ), +) application.runExit(args.raw, context).catch(onError) diff --git a/sources/@roots/bud/src/cli/commands/bud.build.development.tsx b/sources/@roots/bud/src/cli/commands/bud.build.development.tsx index 501aa652bd..d0370ca9e6 100644 --- a/sources/@roots/bud/src/cli/commands/bud.build.development.tsx +++ b/sources/@roots/bud/src/cli/commands/bud.build.development.tsx @@ -27,23 +27,12 @@ export default class BuildDevelopmentCommand extends BuildCommand { */ public static override usage = BuildCommand.Usage({ category: `build`, - description: `Compiles source assets in \`development\` mode.`, - details: `\ - \`bud build development\` compiles source assets in \`development\` mode. - `, + description: `Compile source assets in \`development\` mode.`, + details: `Compile source assets in \`development\` mode.`, examples: [ - [`compile source and serve`, `$0 build development`], [ - `open project in system default browser`, - `$0 build development --browser`, - ], - [ - `do not force reload in the browser when encountering a fatal HMR error`, - `$0 build development --no-reload`, - ], - [ - `do not display an error overlay when encountering errors in application code`, - `$0 build development --no-overlay`, + `Compile source assets in \`development\` mode.`, + `$0 build development`, ], ], }) diff --git a/sources/@roots/bud/src/cli/commands/bud.build.tsx b/sources/@roots/bud/src/cli/commands/bud.build.tsx index 7e0170cfe8..d8ee5233d9 100644 --- a/sources/@roots/bud/src/cli/commands/bud.build.tsx +++ b/sources/@roots/bud/src/cli/commands/bud.build.tsx @@ -52,7 +52,6 @@ export default class BudBuildCommand extends BudCommand { If you run this command without a configuration file \`bud\` will look for an entrypoint at \`@src/index.js\`. `, - examples: [[`compile source assets`, `$0 build`]], }) public [`cache`] = cache diff --git a/sources/@roots/bud/src/cli/commands/bud.clean.tsx b/sources/@roots/bud/src/cli/commands/bud.clean.tsx index 4aee41bc9b..db81d4ffd1 100644 --- a/sources/@roots/bud/src/cli/commands/bud.clean.tsx +++ b/sources/@roots/bud/src/cli/commands/bud.clean.tsx @@ -14,7 +14,7 @@ export default class BudCleanCommand extends BudCommand { public static override paths = [[`clean`]] public static override usage = Command.Usage({ - category: `tasks`, + category: `task`, description: `Clean project artifacts and caches`, details: ` \`bud clean\` empties the \`@dist\` and \`@storage\` directories. @@ -23,9 +23,9 @@ export default class BudCleanCommand extends BudCommand { \`bud clean cache\` empties the \`@storage/cache\` directory. `, examples: [ - [`Clean artifacts/caches`, `$0 clean`], - [`Clean dist`, `$0 clean @dist`], - [`Clean storage`, `$0 clean @storage`], + [`Clean all`, `$0 clean`], + [`Clean dist`, `$0 clean output`], + [`Clean storage`, `$0 clean storage`], ], }) @@ -41,6 +41,9 @@ export default class BudCleanCommand extends BudCommand { description: `empty @storage`, }) + @bind + public override async catch(error: Error) {} + @bind public async cleanCache() { if (this.bud.hasChildren) { diff --git a/sources/@roots/bud/src/cli/commands/bud.tsx b/sources/@roots/bud/src/cli/commands/bud.tsx index 97d004a0e4..e39cb89f0e 100644 --- a/sources/@roots/bud/src/cli/commands/bud.tsx +++ b/sources/@roots/bud/src/cli/commands/bud.tsx @@ -47,11 +47,28 @@ export default class BudCommand extends Command { public static override usage = Command.Usage({ description: `Run \`bud --help\` for usage information`, details: `\ - \`bud build production\` compiles source assets in \`production\` mode. Run \`bud build production --help\` for usage. + Documentation for this command is available at https://bud.js.org/. - \`bud build development\` compiles source assets in \`development\` mode and serves updated modules. Run \`bud build development --help\` for usage. + 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\`. + + Run this command with no arguments for an interactive menu of available subcommands. + + 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. + + 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: [[`compile source assets`, `$0 build`]], }) public basedir = basedir diff --git a/sources/@roots/bud/src/cli/commands/bud.upgrade.tsx b/sources/@roots/bud/src/cli/commands/bud.upgrade.tsx index 54172c15c5..9fd267937f 100644 --- a/sources/@roots/bud/src/cli/commands/bud.upgrade.tsx +++ b/sources/@roots/bud/src/cli/commands/bud.upgrade.tsx @@ -20,7 +20,7 @@ export default class BudUpgradeCommand extends BudCommand { * {@link Command.usage} */ public static override usage = Command.Usage({ - category: `tasks`, + category: `task`, description: `Set bud.js version`, details: ` This command will upgrade your bud.js installation to the latest stable version. @@ -34,12 +34,7 @@ export default class BudUpgradeCommand extends BudCommand { This command is a passthrough to the package manager you are using. `, examples: [ - [`Upgrade dependencies to latest`, `$0 upgrade`], - [`Upgrade dependencies to specific version`, `$0 upgrade 6.6.6`], - [ - `Upgrade through a private registry`, - `$0 upgrade --registry http://localhost:4873`, - ], + [`Upgrade all bud dependencies to latest version`, `$0 upgrade`], ], }) diff --git a/sources/@roots/bud/src/cli/commands/bud.view.tsx b/sources/@roots/bud/src/cli/commands/bud.view.tsx index daabd7b279..c551986b2a 100644 --- a/sources/@roots/bud/src/cli/commands/bud.view.tsx +++ b/sources/@roots/bud/src/cli/commands/bud.view.tsx @@ -13,11 +13,9 @@ export default class BudViewCommand extends BudCommand { public static override paths = [[`view`]] public static override usage = Command.Usage({ + category: `debug`, description: `Explore bud object`, - examples: [ - [`view compiled config`, `$0 view`], - [`view`, `$0 view env store`], - ], + examples: [[`view compiled config`, `$0 view build.config`]], }) public indent = indent @@ -47,7 +45,7 @@ export default class BudViewCommand extends BudCommand { if (this.color) value = highlight(value) BudViewCommand.renderStatic( - + {this.subject ?? `build.config`} {` `} {value} diff --git a/sources/@roots/bud/src/cli/commands/bud.webpack.tsx b/sources/@roots/bud/src/cli/commands/bud.webpack.tsx index 1385a8a03e..ed9e8ba36a 100644 --- a/sources/@roots/bud/src/cli/commands/bud.webpack.tsx +++ b/sources/@roots/bud/src/cli/commands/bud.webpack.tsx @@ -18,9 +18,9 @@ export default class BudWebpackCommand extends BudCommand { * {@link Command.usage} */ public static override usage = Command.Usage({ - category: `tools`, + category: `tool`, description: `Webpack CLI passthrough`, - examples: [[`View webpack usage information`, `$0 webpack --help`]], + examples: [[`Run webpack`, `$0 webpack`]], }) public options = Option.Proxy({name: `webpack passthrough options`}) diff --git a/sources/@roots/bud/src/cli/commands/doctor/index.tsx b/sources/@roots/bud/src/cli/commands/doctor/index.tsx index 3120313a61..2db7ecb93b 100644 --- a/sources/@roots/bud/src/cli/commands/doctor/index.tsx +++ b/sources/@roots/bud/src/cli/commands/doctor/index.tsx @@ -24,6 +24,7 @@ export default class DoctorCommand extends BudCommand { public static override paths = [[`doctor`]] public static override usage = Command.Usage({ + category: `debug`, description: `Check project for common errors`, details: `\ The \`bud doctor\` command will: @@ -39,7 +40,7 @@ In general, \`bud.js\` dependencies should be kept at the same version. This scr for a lot of edge cases so it might return a false positive. `, examples: [ - [`Check compiled configuration against webpack`, `$0 doctor`], + [`Check project for common configuration issues`, `$0 doctor`], ], }) diff --git a/sources/@roots/bud/src/cli/commands/repl/index.tsx b/sources/@roots/bud/src/cli/commands/repl/index.tsx index 09f22d3d04..4e3ede2967 100644 --- a/sources/@roots/bud/src/cli/commands/repl/index.tsx +++ b/sources/@roots/bud/src/cli/commands/repl/index.tsx @@ -18,6 +18,7 @@ export default class BudReplCommand extends BudCommand { * {@link BudCommand.usage} */ public static override usage = Command.Usage({ + category: `debug`, description: `Use bud in a repl`, examples: [[`repl`, `$0 repl`]], }) diff --git a/sources/@roots/bud/src/cli/components/Menu.tsx b/sources/@roots/bud/src/cli/components/Menu.tsx index 35bae945cd..3e8cbdaadb 100644 --- a/sources/@roots/bud/src/cli/components/Menu.tsx +++ b/sources/@roots/bud/src/cli/components/Menu.tsx @@ -1,3 +1,5 @@ +import {exit} from 'node:process' + import figures from '@roots/bud-support/figures' import { Box, @@ -9,75 +11,66 @@ import { import type BudCommand from '../commands/bud.js' -const options: Array<[string, string, Array]> = [ - [ - `build production`, - `build application for production`, - [`build`, `production`], - ], - [ - `build development`, - `start development server`, - [`build`, `development`], - ], - [ - `doctor`, - `check bud.js configuration for common errors and issues`, - [`doctor`], - ], - [ - `repl`, - `open a repl to explore bud just prior to compilation`, - [`repl`], - ], - [ - `upgrade`, - `upgrade bud.js and extensions to the latest stable version`, - [`upgrade`], - ], -] - export const Menu = ({cli}: {cli: BudCommand[`cli`]}) => { + const [defined] = useState(cli.definitions()) const [selected, setSelected] = useState(0) const [running, setRunning] = useState(false) + const [items, setItems] = useState([]) + + useEffect(() => { + setItems( + defined.reduce((acc, cmd, id) => { + return [ + ...acc, + ...(cmd.examples ?? []).map(([description, path]) => { + return {cmd, description, id, path} + }), + ] + }, []), + ) + }, [defined]) useInput((key, input) => { + if (input.escape) { + // eslint-disable-next-line n/no-process-exit + exit() + } + if (running) return input[`downArrow`] && setSelected(selected + 1) input[`upArrow`] && setSelected(selected - 1) - if (input.escape) { - // eslint-disable-next-line n/no-process-exit - process.exit(0) - } - if (input.return) { setRunning(true) - cli.run(options[selected][2]) + cli.run(items[selected].path.split(` `).slice(1)) } }) useEffect(() => { - if (selected > options.length - 1) setSelected(0) - if (selected < 0) setSelected(options.length - 1) - }, [selected]) + if (selected > items.length - 1) setSelected(0) + if (selected < 0) setSelected(items.length - 1) + }, [items, selected]) return ( - - {options.map(([option, description, command], index) => { - return ( - - {selected === index ? figures.radioOn : figures.radioOff} - {` `} - {option} - + + bud.js (v{cli.binaryVersion}) + + {items.map(({cmd, description, path}, id) => { + return ( + + {selected === id ? figures.radioOn : figures.radioOff} {` `} - {description} + {path.trim()} + + + {` `} + {description.trim()} + - - ) - })} + ) + })} + ) } diff --git a/sources/@roots/bud/src/cli/finder.ts b/sources/@roots/bud/src/cli/finder.ts index 594605f15f..b553231f38 100644 --- a/sources/@roots/bud/src/cli/finder.ts +++ b/sources/@roots/bud/src/cli/finder.ts @@ -20,37 +20,21 @@ export class Commands { public paths: Array /** + * Class constructor */ - private constructor( + public constructor( public context: Partial, public application: Cli, ) {} /** - * @public - * @static + * Clear command cache */ - public static get(application: Cli, context: Partial) { - if (Commands.instance) return Commands.instance - else { - Commands.instance = new Commands(context, application) - return Commands.instance - } - } - - public static async importCommandsFromPaths( - paths: Array, - ): Promise { + @bind + public async clearCommandCache() { + const path = join(this.context.paths.storage, `bud.commands.yml`) try { - return await Promise.all( - paths.map(async path => { - try { - return await import(path).then( - ({default: register}) => register, - ) - } catch (error) {} - }), - ) + if (await this.fs.exists(path)) await this.fs.remove(path) } catch (error) {} } @@ -65,27 +49,17 @@ export class Commands { } /** - * Get commands - * - * @remarks - * Returns cached commands if they exist, otherwise - * resolves and caches commands from project dependencies. + * Get registration module paths */ @bind - public async getCommands() { - const path = join(this.context.paths.storage, `bud.commands.yml`) - try { - if (await this.fs.exists(path)) { - const paths = await this.fs.read(path) - if (Array.isArray(paths)) return paths - } - } catch (error) {} - - const resolvedExtensionPaths = await this.getRegistrationModulePaths() - - await this.fs.write(path, resolvedExtensionPaths) + public async findRegistrationModules(): Promise> { + this.paths = await this.resolveExtensionCommandPaths( + this.getProjectDependencySignifiers(), + ) + .then(this.findExtensionCommandPaths) + .then(this.resolveExtensionCommandPaths) - return resolvedExtensionPaths + return this.paths } /** @@ -100,14 +74,41 @@ export class Commands { } /** + * Import commands */ @bind - public async getRegistrationModulePaths(): Promise> { - return await this.resolveExtensionCommandPaths( - this.getProjectDependencySignifiers(), - ) - .then(this.findExtensionCommandPaths) - .then(this.resolveExtensionCommandPaths) + public async importCommands(): Promise { + return await Promise.all( + this.paths.map(async path => { + try { + return await import(path).then(({default: register}) => register) + } catch (error) { + await this.clearCommandCache() + } + }), + ).catch(this.clearCommandCache) + } + + /** + * Get commands + * + * @remarks + * Returns cached commands if they exist, otherwise + * resolves and caches commands from project dependencies. + */ + @bind + public async init() { + const path = join(this.context.paths.storage, `bud.commands.yml`) + try { + if (await this.fs.exists(path)) { + this.paths = await this.fs.read(path) + if (Array.isArray(this.paths)) return this.paths + } + } catch (error) {} + + await this.findRegistrationModules() + await this.fs.write(path, this.paths) + return this.paths } @bind