Skip to content

Commit

Permalink
✨ improve(patch): heal cli commands cache (#2400)
Browse files Browse the repository at this point in the history
- prevent errors from being overwritten (in case of multiple node errors)
- rebuild command cache on type errors

## Type of change

**PATCH: backwards compatible change**
kellymears authored Aug 9, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 5c3f02f commit 7eb502f
Showing 36 changed files with 377 additions and 225 deletions.
4 changes: 1 addition & 3 deletions sources/@roots/bud-compiler/src/service/index.tsx
Original file line number Diff line number Diff line change
@@ -93,9 +93,7 @@ class Compiler extends Service implements BudCompiler {

this.instance.hooks.done.tap(bud.label, (stats: any) => {
this.onStats(stats)
bud.hooks.fire(`compiler.done`, bud, this.stats).catch(error => {
throw error
})
bud.hooks.fire(`compiler.done`, bud, this.stats).catch(this.onError)
})

return this.instance
217 changes: 121 additions & 96 deletions sources/@roots/bud-dashboard/src/components/error.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/* eslint-disable n/no-process-env */
import {BudError} from '@roots/bud-support/errors'
import figures from '@roots/bud-support/figures'
import {Box, Text} from '@roots/bud-support/ink'
import {Box, type ReactNode, Static, Text} from '@roots/bud-support/ink'
import isString from '@roots/bud-support/lodash/isString'

const basePath =
process.env.PROJECT_CWD ?? process.env.INIT_CWD ?? process.cwd()

export const Error = ({error}: {error: BudError | Error}) => {
export const Error = ({error}: {error: unknown}): ReactNode => {
let normalError: BudError

if (!error) {
@@ -35,110 +35,135 @@ export const Error = ({error}: {error: BudError | Error}) => {
error instanceof BudError ? error : BudError.normalize(error)

return (
<Box flexDirection="column" paddingTop={1}>
<Text backgroundColor="red" color="white">
{` `}
{normalError.name ?? `Error`}
{` `}
</Text>

{normalError.message && (
<Box marginTop={1}>
<Text>
<Text color="red">{figures.cross}</Text>
<Static items={[0]}>
{() => (
<Box flexDirection="column" paddingTop={1}>
<Text backgroundColor="red" color="white">
{` `}
{normalError.name ?? `Error`}
{` `}
{normalError.message
.replace(basePath, `.`)
.replace(`Error: `, ``)}
</Text>
</Box>
)}

{normalError.details &&
!normalError.details.startsWith(`resolve`) && (
<Box marginTop={1}>
<Text>
<Text color="blue">
{figures.ellipsis}
{` `}Details{` `}
{normalError.message && (
<Box marginTop={1}>
<Text>
<Text color="red">{figures.cross}</Text>
{` `}
{normalError.message
.replace(basePath, `.`)
.replace(`Error: `, ``)}
</Text>
</Box>
)}

{normalError.details &&
!normalError.details.startsWith(`resolve`) && (
<Box marginTop={1}>
<Text>
<Text color="blue">
{figures.ellipsis}
{` `}Details{` `}
</Text>

<Text>{normalError.details.replace(basePath, `.`)}</Text>
</Text>
</Box>
)}

{normalError.thrownBy && (
<Box marginTop={1}>
<Text>
<Text color="blue">
{figures.ellipsis}
{` `}Thrown by{` `}
</Text>

<Text>{normalError.thrownBy}</Text>
</Text>
</Box>
)}

{normalError.docs && (
<Box marginTop={1}>
<Text>
<Text color="blue">
{figures.arrowRight}
{` `}Documentation
</Text>
{` `}
<Text>{normalError.docs.href}</Text>
</Text>
</Box>
)}

{normalError.issues && (
<Box marginTop={1}>
<Text>
<Text color="blue">
{figures.arrowRight}
{` `}
Issues
</Text>
{` `}
<Text>{normalError.issues.href}</Text>
</Text>
</Box>
)}

<Text>{normalError.details.replace(basePath, `.`)}</Text>
</Text>
</Box>
)}

{normalError.thrownBy && (
<Box marginTop={1}>
<Text>
<Text color="blue">
{figures.ellipsis}
{` `}Thrown by{` `}
</Text>

<Text>{normalError.thrownBy}</Text>
</Text>
</Box>
)}

{normalError.docs && (
<Box marginTop={1}>
<Text>
<Text color="blue">
{figures.arrowRight}
{` `}Documentation
</Text>
{` `}
<Text>{normalError.docs.href}</Text>
</Text>
</Box>
)}

{normalError.issues && (
<Box marginTop={1}>
<Text>
<Text color="blue">
{figures.arrowRight}
{` `}
Issues
</Text>
{` `}
<Text>{normalError.issues.href}</Text>
</Text>
</Box>
)}
{normalError.file && (
<Box marginTop={1}>
<Text color="blue">
{figures.info}
{` `}See file{` `}
</Text>
<Text>{normalError.file.path}</Text>
</Box>
)}

{normalError.file && (
<Box marginTop={1}>
<Text color="blue">
{figures.info}
{` `}See file{` `}
</Text>
<Text>{normalError.file.path}</Text>
</Box>
)}
{normalError.origin && (
<Box flexDirection="column" marginTop={1}>
<Text color="blue">
{figures.home}
{` `}Originating error{` `}
</Text>

{normalError.origin && (
<Box flexDirection="column">
<Text color="blue">
{figures.home}
{` `}Originating error{` `}
</Text>
<Box
borderBottom={false}
borderColor="red"
borderLeft
borderRight={false}
borderStyle="single"
borderTop={false}
paddingLeft={1}
>
<Error error={normalError.origin} />
</Box>
</Box>
)}

{normalError.stack && (
<Box flexDirection="column" marginTop={1}>
<Text color="blue">
{figures.home}
{` `}Stack trace{` `}
</Text>

<Box
borderBottom={false}
borderColor="red"
borderLeft
borderRight={false}
borderStyle="single"
borderTop={false}
paddingLeft={1}
>
<Error error={normalError.origin} />
</Box>
<Box
borderBottom={false}
borderColor="red"
borderLeft
borderRight={false}
borderStyle="single"
borderTop={false}
paddingLeft={1}
>
<Text>{normalError.stack}</Text>
</Box>
</Box>
)}
</Box>
)}
</Box>
</Static>
)
}

1 change: 0 additions & 1 deletion sources/@roots/bud-dashboard/src/views/server.tsx
Original file line number Diff line number Diff line change
@@ -120,5 +120,4 @@ const Dev = ({
)
}


export {Server as default}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Option} from '@roots/bud-support/clipanion'
import BudCommand from '@roots/bud/cli/commands/bud'
import BudCommand from '@roots/bud/cli/commands'

/**
* Bud eslint command
2 changes: 1 addition & 1 deletion sources/@roots/bud-framework/src/bud.ts
Original file line number Diff line number Diff line change
@@ -324,7 +324,7 @@ export class Bud {

await this.children[context.label].promise()

this.get(context.label).hooks.on(
this.get(context.label)?.hooks.on(
`build.dependencies`,
typeof request !== `string` && request.dependsOn
? request.dependsOn
2 changes: 2 additions & 0 deletions sources/@roots/bud-framework/src/context.ts
Original file line number Diff line number Diff line change
@@ -82,6 +82,8 @@ export interface Context {
*/
clean?: boolean

colorDepth?: number

/**
* Render dashboard in CLI
*
2 changes: 1 addition & 1 deletion sources/@roots/bud-hooks/src/base/base.ts
Original file line number Diff line number Diff line change
@@ -32,7 +32,7 @@ export abstract class Hooks<Store> {

@bind
public catch(e: Error, id?: string, iteration?: number): void {
throw new BudError(`problem running hook ${id}`, {cause: e})
throw new BudError(`problem running hook ${id}`, {origin: e})
}

/**
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Command, Option} from '@roots/bud-support/clipanion'
import BudCommand from '@roots/bud/cli/commands/bud'
import BudCommand from '@roots/bud/cli/commands'

/**
* `bud prettier` command
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Command, Option} from '@roots/bud-support/clipanion'
import BudCommand from '@roots/bud/cli/commands/bud'
import BudCommand from '@roots/bud/cli/commands'

/**
* Bud stylelint command
20 changes: 18 additions & 2 deletions sources/@roots/bud-support/src/errors/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import cleanStack from 'clean-stack'

/**
* Props for Bud errors
*/
@@ -14,7 +16,7 @@ interface BudErrorProps extends Error {
isBudError: true
issue: URL
message: string
origin: BudError
origin: BudError | Error | string
thrownBy: string
}

@@ -64,7 +66,7 @@ class BudError extends Error {
/**
* Original error
*/
public declare origin: BudError | false
public declare origin: BudError | Error | string

/**
* Name of method that threw error
@@ -81,6 +83,20 @@ class BudError extends Error {

if (!this.instance) this.instance = `default`

if (this.stack) {
this.stack = cleanStack(this.stack, {
pathFilter: path =>
!path.includes(`react-reconciler`) &&
!path.includes(`bud-support/lib/errors`),
})
}
if (this.message) {
this.message = cleanStack(this.message, {
pathFilter: path =>
!path.includes(`react-reconciler`) &&
!path.includes(`bud-support/lib/errors`),
})
}
this.isBudError = true
}

1 change: 1 addition & 0 deletions sources/@roots/bud-support/src/ink/index.ts
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@ export {
memo,
type PropsWithChildren,
type ReactElement,
type ReactNode,
useCallback,
useContext,
useDebugValue,
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {relative} from 'node:path'

import {Option} from '@roots/bud-support/clipanion'
import BudCommand from '@roots/bud/cli/commands/bud'
import BudCommand from '@roots/bud/cli/commands'

export class BudTailwindCommand extends BudCommand {
/**
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {bind} from '@roots/bud-support/decorators/bind'
import BudCommand from '@roots/bud/cli/commands/bud'
import BudCommand from '@roots/bud/cli/commands'

/**
* Bud ts check command
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Command, Option} from '@roots/bud-support/clipanion'
import BudCommand from '@roots/bud/cli/commands/bud'
import BudCommand from '@roots/bud/cli/commands'

/**
* Bud tsc command
67 changes: 56 additions & 11 deletions sources/@roots/bud/package.json
Original file line number Diff line number Diff line change
@@ -57,16 +57,28 @@
"type": "module",
"exports": {
".": "./lib/index.js",
"./bud": "./lib/bud.js",
"./cli/commands/*": "./lib/cli/commands/*.js",
"./cli/decorators/*": "./lib/cli/decorators/*.js",
"./bud": "./lib/bud/index.js",
"./cli": "./lib/cli/index.js",
"./cli/app": "./lib/cli/app.js",
"./cli/commands": "./lib/cli/commands/index.js",
"./cli/commands/bud": "./lib/cli/commands/index.js",
"./cli/commands/build": "./lib/cli/commands/build/index.js",
"./cli/commands/build/development": "./lib/cli/commands/build/development/index.js",
"./cli/commands/build/production": "./lib/cli/commands/build/production/index.js",
"./cli/commands/clean": "./lib/cli/commands/clean/index.js",
"./cli/commands/doctor": "./lib/cli/commands/doctor/index.js",
"./cli/commands/repl": "./lib/cli/commands/repl/index.js",
"./cli/commands/upgrade": "./lib/cli/commands/upgrade/index.js",
"./cli/commands/view": "./lib/cli/commands/view/index.js",
"./cli/commands/webpack": "./lib/cli/commands/webpack/index.js",
"./cli/finder": "./lib/cli/finder.js",
"./cli/flags": "./lib/cli/flags/index.js",
"./cli/flags/*": "./lib/cli/flags/*.js",
"./cli/helpers/*": "./lib/cli/helpers/*.js",
"./cli/*": "./lib/cli/*.js",
"./context": "./lib/context/index.js",
"./context/*": "./lib/context/*.js",
"./factory": "./lib/factory.js",
"./instance": "./lib/instance.js",
"./factory": "./lib/factory/index.js",
"./instance": "./lib/instance/index.js",
"./config/jsconfig.json": "./config/jsconfig.json",
"./config/tsconfig.json": "./config/tsconfig.json"
},
@@ -76,16 +88,49 @@
"./lib/index.d.ts"
],
"bud": [
"./lib/bud.d.ts"
"./lib/bud/index.d.ts"
],
"cli": [
"./lib/cli/index.d.ts"
],
"cli/app": [
"./lib/cli/app.d.ts"
],
"cli/commands": [
"./lib/cli/commands/index.d.ts"
],
"cli/decorators/*": [
"./lib/cli/decorators/*.d.ts"
"cli/commands/bud": [
"./lib/cli/commands/index.d.ts"
],
"cli/commands/build": [
"./lib/cli/commands/build/index.d.ts"
],
"cli/commands/build/development": [
"./lib/cli/commands/build/development/index.d.ts"
],
"cli/commands/build/production": [
"./lib/cli/commands/build/production/index.d.ts"
],
"cli/commands/clean": [
"./lib/cli/commands/clean/index.d.ts"
],
"cli/commands/doctor": [
"./lib/cli/commands/doctor/index.d.ts"
],
"cli/commands/repl": [
"./lib/cli/commands/repl/index.d.ts"
],
"cli/commands/upgrade": [
"./lib/cli/commands/upgrade/index.d.ts"
],
"cli/commands/view": [
"./lib/cli/commands/view/index.d.ts"
],
"cli/commands/webpack": [
"./lib/cli/commands/webpack/index.d.ts"
],
"cli/finder": [
"./lib/cli/finder.d.ts"
],
"cli/flags": [
"./lib/cli/flags/index.d.ts"
@@ -103,10 +148,10 @@
"./lib/context/*.d.ts"
],
"factory": [
"./lib/factory.d.ts"
"./lib/factory/index.d.ts"
],
"instance": [
"./lib/instance.d.ts"
"./lib/instance/index.d.ts"
]
}
},
File renamed without changes.
154 changes: 112 additions & 42 deletions sources/@roots/bud/src/cli/app.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {CommandClass} from '@roots/bud-support/clipanion'
import type {BaseContext, CommandClass} from '@roots/bud-support/clipanion'

import {exit, stderr, stdin, stdout} from 'node:process'

@@ -7,27 +7,55 @@ 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 isUndefined from '@roots/bud-support/lodash/isUndefined'
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'
import BudBuildDevelopmentCommand from '@roots/bud/cli/commands/bud.build.development'
import BudBuildProductionCommand from '@roots/bud/cli/commands/bud.build.production'
import BudCleanCommand from '@roots/bud/cli/commands/bud.clean'
import BudUpgradeCommand from '@roots/bud/cli/commands/bud.upgrade'
import BudViewCommand from '@roots/bud/cli/commands/bud.view'
import BudWebpackCommand from '@roots/bud/cli/commands/bud.webpack'
import {Commands} from '@roots/bud/cli/finder'
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 BudDoctorCommand from '@roots/bud/cli/commands/doctor'
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, {type Context} from '@roots/bud/context'

import type {CLIContext} from './index.js'
/**
* Error handler
* @param error
*/
const onError = (error: Error) => {
render(<Error error={BudError.normalize(error)} />)
exit(1)
}

import BudDoctorCommand from './commands/doctor/index.js'
import BudReplCommand from './commands/repl/index.js'
/**
* {@link Context}
*/
const context: Context = {
...(await getContext({stderr, stdin, stdout}).catch(onError)),
stderr,
stdin,
stdout,
}

let application: Cli
let context: CLIContext | Context
/**
* {@link Cli}
*/
const application = new Cli<BaseContext & Context>({
binaryLabel: `bud`,
binaryName: `bud`,
binaryVersion: context.bud.version,
enableCapture: true,
enableColors: context.env?.color !== `false`,
})

const commands = [
/**
* Register built-ins
*/
;[
Builtins.HelpCommand,
Builtins.VersionCommand,
BudCommand,
@@ -40,40 +68,82 @@ const commands = [
BudUpgradeCommand,
BudViewCommand,
BudWebpackCommand,
]
].map(command => application.register(command))

const onError = (error: Error) => {
render(<Error error={BudError.normalize(error)} />)
exit(1)
}
/**
* --force flag
* {@link Context.force}
*/
const forceFlag = !isUndefined(context.force) ? context.force : false

const budContext = await getContext({stderr, stdin, stdout}).catch(onError)
/**
* Register command extensions
*
* @param force - force rebuilding of extension cache
*/
const registerFoundCommands = async (force: boolean = forceFlag) => {
try {
const finder = new Finder(context, application)
if (!force) await finder.init()
else {
await finder.findRegistrationModules()
await finder.cacheWrite()
}

context = {...budContext, stderr, stdin, stdout}
const extensions = await finder.importCommands()

application = new Cli({
binaryLabel: `bud`,
binaryName: `bud`,
binaryVersion: context.bud.version,
enableCapture: true,
enableColors: context.env?.color !== `false`,
})
return await Promise.all(
extensions.map(
async ([path, registerCommand]: [
string,
(application: Cli) => Promise<any>,
]) => {
if (!isFunction(registerCommand))
throw `${path} does not export a module exporting a registration function.`

commands.map(command => application.register(command))
await registerCommand(application).catch(error => {
throw new BudError(error, {
thrownBy: path,
})
})
},
),
)
} catch (error) {
throw error
}
}

try {
// first round will attempt to register extensions from cache
// if cache does not exist, it will be created
await registerFoundCommands()
} catch (error) {
try {
// 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
await registerFoundCommands(true)
} catch (innerError) {
// at this point we know that the error is not related to a suspect cache
// so we'll present it to the user
// and bail on the application
const initializeExtensionsError =
innerError instanceof BudError
? innerError
: BudError.normalize(innerError)

const finder = new Commands(context, application)
await finder.init()
const extensions = await finder.importCommands()
initializeExtensionsError.origin =
error instanceof BudError ? error : BudError.normalize(error)

await Promise.all(
extensions.map(
async (registerCommand: (application: Cli) => Promise<any>) => {
if (!isFunction(registerCommand)) return
await registerCommand(application).catch(onError)
},
),
)
onError(initializeExtensionsError)
}
}

/**
* Run application and exit when process is complete
*/
application.runExit(args.raw, context).catch(onError)

export {application, Builtins, Cli}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type mode from '@roots/bud/cli/flags/mode'

import BuildCommand from '@roots/bud/cli/commands/bud.build'
import BuildCommand from '@roots/bud/cli/commands/build'
import browser from '@roots/bud/cli/flags/browser'
import hot from '@roots/bud/cli/flags/hot'
import indicator from '@roots/bud/cli/flags/indicator'
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import isBoolean from '@roots/bud-support/lodash/isBoolean'
import BudCommand from '@roots/bud/cli/commands/bud'
import BudCommand from '@roots/bud/cli/commands'
import cache from '@roots/bud/cli/flags/cache'
import ci from '@roots/bud/cli/flags/ci'
import clean from '@roots/bud/cli/flags/clean'
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import type mode from '@roots/bud/cli/flags/mode'

import {Command} from '@roots/bud-support/clipanion'

import BuildCommand from './bud.build.js'
import BuildCommand from '@roots/bud/cli/commands/build'

/**
* `bud build production` command
Original file line number Diff line number Diff line change
@@ -3,13 +3,11 @@ import type {Bud} from '@roots/bud'
import {Command, Option} from '@roots/bud-support/clipanion'
import {bind} from '@roots/bud-support/decorators/bind'
import {Box, Text} from '@roots/bud-support/ink'
import BudCommand from '@roots/bud/cli/commands/bud'
import {dry} from '@roots/bud/cli/decorators/dry'
import BudCommand from '@roots/bud/cli/commands'

/**
* `bud clean`
*/
@dry
export default class BudCleanCommand extends BudCommand {
public static override paths = [[`clean`]]

2 changes: 1 addition & 1 deletion sources/@roots/bud/src/cli/commands/doctor/index.tsx
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ import figures from '@roots/bud-support/figures'
import {Box, Text} from '@roots/bud-support/ink'
import prettyFormat from '@roots/bud-support/pretty-format'
import webpack from '@roots/bud-support/webpack'
import BudCommand from '@roots/bud/cli/commands/bud'
import BudCommand from '@roots/bud/cli/commands'

import {WinError} from './WinError.js'

Original file line number Diff line number Diff line change
@@ -30,18 +30,16 @@ import verbose from '@roots/bud/cli/flags/verbose'
import {isset} from '@roots/bud/cli/helpers/isset'
import * as instance from '@roots/bud/instance'

import type {CLIContext} from '../index.js'

import * as Fallback from '../components/Error.js'
import {Menu} from '../components/Menu.js'

export type {BaseContext, Context}
export {Option}

/**
* {@link Command}
* Base {@link Command}
*/
export default class BudCommand extends Command<CLIContext> {
export default class BudCommand extends Command<BaseContext & Context> {
/**
* {@link Command.paths}
*/
@@ -86,8 +84,6 @@ export default class BudCommand extends Command<CLIContext> {

public color: typeof color = color

public declare context: CLIContext

public debug: typeof debug = debug

public dry = dry(true)
2 changes: 1 addition & 1 deletion sources/@roots/bud/src/cli/commands/repl/index.tsx
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import {bind} from '@roots/bud-framework/extension/decorators'
import {Command, Option} from '@roots/bud-support/clipanion'
import {render} from '@roots/bud-support/ink'
import logger from '@roots/bud-support/logger'
import BudCommand from '@roots/bud/cli/commands/bud'
import BudCommand from '@roots/bud/cli/commands'
import indent from '@roots/bud/cli/flags/indent'

/**
Original file line number Diff line number Diff line change
@@ -3,13 +3,11 @@ import {bind} from '@roots/bud-support/decorators/bind'
import {BudError} from '@roots/bud-support/errors'
import isString from '@roots/bud-support/lodash/isString'
import whichPm from '@roots/bud-support/which-pm'
import BudCommand from '@roots/bud/cli/commands/bud'
import {dry} from '@roots/bud/cli/decorators/dry'
import BudCommand from '@roots/bud/cli/commands'

/**
* `bud upgrade` command
*/
@dry
export default class BudUpgradeCommand extends BudCommand {
/**
* {@link Command.paths}
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ import {highlight} from '@roots/bud-support/highlight'
import {Box, Text} from '@roots/bud-support/ink'
import get from '@roots/bud-support/lodash/get'
import format from '@roots/bud-support/pretty-format'
import BudCommand from '@roots/bud/cli/commands/bud'
import BudCommand from '@roots/bud/cli/commands'
import indent from '@roots/bud/cli/flags/indent'

/**
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Command, Option} from '@roots/bud-support/clipanion'
import BudCommand from '@roots/bud/cli/commands/bud'
import BudCommand from '@roots/bud/cli/commands'

/**
* `bud webpack` command
4 changes: 2 additions & 2 deletions sources/@roots/bud/src/cli/components/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type BudCommand from '@roots/bud/cli/commands'

import {exit} from 'node:process'

import figures from '@roots/bud-support/figures'
@@ -9,8 +11,6 @@ import {
useState,
} from '@roots/bud-support/ink'

import type BudCommand from '../commands/bud.js'

export const Menu = ({cli}: {cli: BudCommand[`cli`]}) => {
const [defined] = useState(cli.definitions())
const [selected, setSelected] = useState(0)
14 changes: 0 additions & 14 deletions sources/@roots/bud/src/cli/decorators/dry.ts

This file was deleted.

43 changes: 33 additions & 10 deletions sources/@roots/bud/src/cli/finder.ts
Original file line number Diff line number Diff line change
@@ -14,8 +14,8 @@ import type {Cli} from './app.js'
/**
* Command finder class
*/
export class Commands {
public static instance: Commands
export class Finder {
public static instance: Finder
public fs: typeof filesystem = filesystem
public paths: Array<string>

@@ -31,13 +31,28 @@ export class Commands {
* Clear command cache
*/
@bind
public async clearCommandCache() {
const path = join(this.context.paths.storage, `bud.commands.yml`)
public async cacheClear() {
try {
if (await this.fs.exists(path)) await this.fs.remove(path)
if (await this.fs.exists(this.cachePath))
await this.fs.remove(this.cachePath)
} catch (error) {}
}

/**
* Command cache path
*/
public get cachePath() {
return join(this.context.paths.storage, `bud.commands.yml`)
}

/**
* Write command cache
*/
@bind
public async cacheWrite() {
if (this.paths) await this.fs.write(this.cachePath, this.paths)
}

/**
* Find commands shipped with a given extension
*/
@@ -81,12 +96,15 @@ export class Commands {
return await Promise.all(
this.paths.map(async path => {
try {
return await import(path).then(({default: register}) => register)
return [
path,
await import(path).then(({default: register}) => register),
]
} catch (error) {
await this.clearCommandCache()
await this.cacheClear()
}
}),
).catch(this.clearCommandCache)
).catch(this.cacheClear)
}

/**
@@ -102,13 +120,18 @@ export class Commands {
try {
if (await this.fs.exists(path)) {
this.paths = await this.fs.read(path)
if (Array.isArray(this.paths)) return this.paths
if (Array.isArray(this.paths)) return this
else throw new Error(`Invalid command cache.`)
}
} catch (error) {}

await this.findRegistrationModules()
await this.fs.write(path, this.paths)
return this.paths
return this
}

@bind public async readCache() {
return await this.fs.read(this.cachePath)
}

@bind
16 changes: 7 additions & 9 deletions sources/@roots/bud/src/cli/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type {Context} from '@roots/bud-framework/context'

export interface CLIContext extends Context {
colorDepth: number
}

export type {Builtins, Cli, CommandClass} from './app.js'

export {application} from './app.js'
export type {Context} from '@roots/bud-framework/context'
export {
application,
type Builtins,
Cli,
type CommandClass,
} from '@roots/bud/cli/app'
4 changes: 2 additions & 2 deletions sources/@roots/bud/src/context/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {Context} from '@roots/bud-framework'
import type {Context} from '@roots/bud-framework/context'

import {join} from 'node:path'
import {stderr, stdin, stdout} from 'node:process'
@@ -19,7 +19,7 @@ export type Options = Omit<Partial<Context>, `extensions`> & {
}

export default async (options: Options = {}): Promise<Context> => {
let basedir = options?.basedir ?? process.cwd()
const basedir = options?.basedir ?? process.cwd()
const paths = projectPaths.get(basedir)

const fs = filesystem.get(paths.basedir)
File renamed without changes.
1 change: 0 additions & 1 deletion sources/@roots/bud/src/index.ts
Original file line number Diff line number Diff line change
@@ -26,6 +26,5 @@ interface Config {
}

export {Bud, type Config}

export {factory} from '@roots/bud/factory'
export {get, instance as bud, set} from '@roots/bud/instance'
File renamed without changes.
7 changes: 3 additions & 4 deletions sources/@roots/bud/test/factory.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import {join} from 'node:path'

import {paths} from '@repo/constants'
import {path} from '@repo/constants'
import {factory} from '@roots/bud/factory'
import {describe, expect, test} from 'vitest'

import {factory} from '../src/factory.js'

describe(`@roots/bud/factory`, () => {
test(`should merge overrides`, async () => {
const bud = await factory({
basedir: join(paths.tests, `util`, `project`),
basedir: join(path(`tests`), `util`, `project`),
dry: true,
log: false,
})

0 comments on commit 7eb502f

Please sign in to comment.