From 0382331be1f101d06a64875c1d486052f24f479d Mon Sep 17 00:00:00 2001 From: Eric Defore Date: Thu, 12 Feb 2026 15:43:06 -0500 Subject: [PATCH 1/4] ENG-219: Add check command infrastructure Adds the check command framework with support for command-type and module-type custom checks, compile-time built-in check discovery via tsup define, dynamic subcommand registration, and deferred version file validation in config parsing. --- src/checks/collection.ts | 95 +++++++++++ src/checks/types.ts | 12 ++ src/cli.ts | 2 + src/commands/check.ts | 298 +++++++++++++++++++++++++++++++++++ src/config.ts | 17 +- src/utils/output.ts | 2 +- tests/commands/check.test.ts | 141 +++++++++++++++++ tsup.config.ts | 11 ++ 8 files changed, 561 insertions(+), 17 deletions(-) create mode 100644 src/checks/collection.ts create mode 100644 src/checks/types.ts create mode 100644 src/commands/check.ts create mode 100644 tests/commands/check.test.ts diff --git a/src/checks/collection.ts b/src/checks/collection.ts new file mode 100644 index 0000000..b006ed0 --- /dev/null +++ b/src/checks/collection.ts @@ -0,0 +1,95 @@ +import type { CheckConfig } from '../types.js'; + +/** + * Manages a collection of registered checks. + * + * @since TBD + */ +export class CheckCollection { + private checks: Map = new Map(); + + /** + * Registers a check configuration. + * + * @since TBD + * + * @param {CheckConfig} config - The check configuration to add. + * + * @returns {void} + */ + add(config: CheckConfig): void { + this.checks.set(config.slug, config); + } + + /** + * Retrieves a check configuration by its slug. + * + * @since TBD + * + * @param {string} slug - The slug of the check to retrieve. + * + * @returns {CheckConfig | undefined} The check configuration, or undefined if not found. + */ + get(slug: string): CheckConfig | undefined { + return this.checks.get(slug); + } + + /** + * Checks whether a check with the given slug is registered. + * + * @since TBD + * + * @param {string} slug - The slug to check for. + * + * @returns {boolean} True if a check with the given slug is registered, false otherwise. + */ + has(slug: string): boolean { + return this.checks.has(slug); + } + + /** + * Removes a check by its slug. + * + * @since TBD + * + * @param {string} slug - The slug of the check to remove. + * + * @returns {void} + */ + remove(slug: string): void { + this.checks.delete(slug); + } + + /** + * Returns all registered checks as an array. + * + * @since TBD + * + * @returns {CheckConfig[]} An array of all registered check configurations. + */ + getAll(): CheckConfig[] { + return Array.from(this.checks.values()); + } + + /** + * Returns the number of registered checks. + * + * @since TBD + * + * @returns {number} The number of registered checks. + */ + get size(): number { + return this.checks.size; + } + + /** + * Allows iterating over all registered checks. + * + * @since TBD + * + * @returns {Iterator} An iterator over all registered check configurations. + */ + [Symbol.iterator](): Iterator { + return this.checks.values(); + } +} diff --git a/src/checks/types.ts b/src/checks/types.ts new file mode 100644 index 0000000..2d94576 --- /dev/null +++ b/src/checks/types.ts @@ -0,0 +1,12 @@ +import type { CheckConfig, CheckResult } from '../types.js'; + +export interface CheckContext { + config: CheckConfig; + workingDir: string; + isDev: boolean; +} + +export interface CheckModule { + configure?: (config: CheckConfig) => void; + execute: (context: CheckContext) => Promise; +} diff --git a/src/cli.ts b/src/cli.ts index 4a5c634..29f2dc1 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,8 +1,10 @@ import { createApp } from './app.js'; +import { registerCheckCommand } from './commands/check.js'; import { registerHelpCommand } from './commands/help.js'; const program = createApp(); +registerCheckCommand(program); registerHelpCommand(program); program.parseAsync(process.argv).catch((err) => { diff --git a/src/commands/check.ts b/src/commands/check.ts new file mode 100644 index 0000000..fd6818e --- /dev/null +++ b/src/commands/check.ts @@ -0,0 +1,298 @@ +import type { Command } from 'commander'; +import chalk from 'chalk'; +import { getConfig } from '../config.js'; + +declare const BUILTIN_CHECK_SLUGS: string[]; +import { runCommand } from '../utils/process.js'; +import * as output from '../utils/output.js'; +import type { CheckConfig, CheckResult } from '../types.js'; + +/** + * Runs all configured checks and returns an exit code. + * + * @since TBD + * + * @param {object} options - The options object. + * @param {boolean} [options.dev] - Whether to use dev failure methods. + * @param {string} [options.root] - The root directory for running commands. + * + * @returns {Promise} The exit code: 0 for success, 1 if any error-level check failed. + */ +export async function runChecks(options: { + dev?: boolean; + root?: string; +}): Promise { + const config = getConfig(options.root); + const checks = config.getChecks(); + const cwd = options.root ?? config.getWorkingDir(); + + if (checks.size === 0) { + output.writeln('📣 The .puprc does not have any checks configured.'); + output.writeln(`💡 If you would like to use the defaults, simply remove the ${chalk.yellow('"checks"')} property in ${chalk.yellow('.puprc')}.`); + output.writeln(''); + output.writeln(`If you would like to use one of the default checks, add one or more of the following to the ${chalk.yellow('"checks"')} property in your ${chalk.yellow('.puprc')}:`); + output.writeln(' "tbd": {}'); + output.writeln(' "version-conflict": {}'); + output.writeln(''); + output.writeln('If you would like to create your own check, take a look at the pup docs to learn how:'); + output.writeln(' https://github.com/stellarwp/pup'); + return 0; + } + + const failures: string[] = []; + + for (const [slug, checkConfig] of checks) { + const failMethod = options.dev + ? checkConfig.fail_method_dev + : checkConfig.fail_method; + const bailOnFailure = failMethod === 'error'; + + output.setPrefix(slug); + + let result: CheckResult; + + if (checkConfig.type === 'pup' || !checkConfig.type) { + result = await runBuiltinCheck(slug, checkConfig, cwd, config); + } else if (checkConfig.type === 'command' && checkConfig.command) { + result = await runShellCheck(checkConfig.command, cwd); + } else if ( + (checkConfig.type === 'simple' || checkConfig.type === 'class') && + checkConfig.file + ) { + result = await runModuleCheck(checkConfig.file, checkConfig, cwd); + } else { + output.warning(`Unknown check type: ${checkConfig.type}`); + output.setPrefix(''); + continue; + } + + if (result.output) { + for (const line of result.output.split('\n')) { + output.log(line); + } + } + + output.setPrefix(''); + + if (!result.success) { + failures.push(slug); + + if (bailOnFailure) { + output.writeln(chalk.yellow(`${slug}'s fail_method in ${chalk.cyan('.puprc')} is set to "${chalk.red('error')}". Exiting...`)); + return 1; + } + } + } + + if (failures.length > 0) { + output.error(`The following checks failed:\n* ${failures.join('\n* ')}`); + } + + return 0; +} + +/** + * Dispatches a built-in pup check by slug. + * + * @since TBD + * + * @param {string} slug - The identifier for the built-in check. + * @param {CheckConfig} _checkConfig - The configuration for this check. + * @param {string} _cwd - The current working directory. + * @param {ReturnType} _config - The resolved pup configuration. + * + * @returns {Promise} The result of the check. + */ +async function runBuiltinCheck( + slug: string, + _checkConfig: CheckConfig, + _cwd: string, + _config: ReturnType +): Promise { + switch (slug) { + default: + return { success: false, output: `Unknown built-in check: ${slug}` }; + } +} + +/** + * Runs a shell command check. + * + * @since TBD + * + * @param {string} command - The shell command to execute. + * @param {string} cwd - The current working directory. + * + * @returns {Promise} The result of the shell check. + */ +async function runShellCheck( + command: string, + cwd: string +): Promise { + const result = await runCommand(command, { cwd, silent: true }); + return { + success: result.exitCode === 0, + output: result.stdout || result.stderr || (result.exitCode === 0 ? 'Success!' : 'Failed.'), + }; +} + +/** + * Dynamically imports and executes a JS/TS module check. + * + * @since TBD + * + * @param {string} file - The path to the module file relative to the working directory. + * @param {CheckConfig} checkConfig - The configuration for this check. + * @param {string} cwd - The current working directory. + * + * @returns {Promise} The result of the module check. + */ +async function runModuleCheck( + file: string, + checkConfig: CheckConfig, + cwd: string +): Promise { + try { + const modulePath = new URL(`file://${cwd}/${file}`).href; + const mod = (await import(modulePath)) as { + configure?: (config: CheckConfig) => void; + execute: (context: { + config: CheckConfig; + workingDir: string; + }) => Promise; + }; + + if (mod.configure) { + mod.configure(checkConfig); + } + + return await mod.execute({ config: checkConfig, workingDir: cwd }); + } catch (err) { + return { + success: false, + output: `Failed to load check module ${file}: ${err}`, + }; + } +} + +/** + * Runs a single check by slug, handling prefix, output, and exit code. + * + * @since TBD + * + * @param {string} slug - The check slug to run. + * @param {object} options - The options object. + * @param {boolean} [options.dev] - Whether to use dev failure methods. + * @param {string} [options.root] - The root directory for running commands. + * + * @returns {Promise} The exit code: 0 for success, 1 if the check failed. + */ +async function runSingleCheck( + slug: string, + options: { dev?: boolean; root?: string } +): Promise { + const config = getConfig(options.root); + const checks = config.getChecks(); + const cwd = options.root ?? config.getWorkingDir(); + const isBuiltin = (BUILTIN_CHECK_SLUGS as string[]).includes(slug); + const checkConfig = checks.get(slug) ?? (isBuiltin ? {} as CheckConfig : undefined); + + if (!checkConfig) { + output.error(`Check "${slug}" is not configured.`); + return 1; + } + + let result: CheckResult; + + if (checkConfig.type === 'pup' || !checkConfig.type) { + result = await runBuiltinCheck(slug, checkConfig, cwd, config); + } else if (checkConfig.type === 'command' && checkConfig.command) { + result = await runShellCheck(checkConfig.command, cwd); + } else if ( + (checkConfig.type === 'simple' || checkConfig.type === 'class') && + checkConfig.file + ) { + result = await runModuleCheck(checkConfig.file, checkConfig, cwd); + } else { + output.warning(`Unknown check type: ${checkConfig.type}`); + return 1; + } + + if (result.output) { + for (const line of result.output.split('\n')) { + output.log(line); + } + } + + return result.success ? 0 : 1; +} + +/** + * Registers a single `check:{slug}` subcommand on the program. + * + * @since TBD + * + * @param {Command} program - The Commander.js program instance. + * @param {string} slug - The check slug to register. + * + * @returns {void} + */ +function registerCheckSubcommand(program: Command, slug: string): void { + program + .command(`check:${slug}`) + .description(`Run the ${slug} check.`) + .option('--dev', 'Run with dev failure methods.') + .option('--root ', 'Set the root directory for running commands.') + .action(async (options: { dev?: boolean; root?: string }) => { + const exitCode = await runSingleCheck(slug, options); + if (exitCode !== 0) { + process.exit(exitCode); + } + }); +} + +/** + * Registers the `check` command and individual `check:{slug}` subcommands + * for built-in checks and any custom checks configured in `.puprc`. + * + * @since TBD + * + * @param {Command} program - The Commander.js program instance. + * + * @returns {void} + */ +export function registerCheckCommand(program: Command): void { + program + .command('check') + .description('Run checks against the codebase.') + .option('--dev', 'Run with dev failure methods.') + .option('--root ', 'Set the root directory for running commands.') + .action(async (options: { dev?: boolean; root?: string }) => { + const exitCode = await runChecks(options); + if (exitCode !== 0) { + process.exit(exitCode); + } + }); + + const registered = new Set(); + + // Register built-in checks determined at compile time from src/commands/checks/. + for (const slug of BUILTIN_CHECK_SLUGS) { + registerCheckSubcommand(program, slug); + registered.add(slug); + } + + // Register any custom checks from .puprc that aren't already registered. + try { + const config = getConfig(); + for (const [slug] of config.getChecks()) { + if (registered.has(slug)) continue; + registerCheckSubcommand(program, slug); + registered.add(slug); + } + } catch { + // Config may not be loadable (e.g., no .puprc). Custom subcommands will + // not be registered, but built-in checks and the main `check` command + // still work. + } +} diff --git a/src/config.ts b/src/config.ts index efffbb7..dd49cf5 100644 --- a/src/config.ts +++ b/src/config.ts @@ -287,7 +287,7 @@ export class Config { * * @returns {void} * - * @throws {Error} If a version file entry is missing required properties or the file does not exist. + * @throws {Error} If a version file entry is missing required properties. */ private parseVersionFiles(): void { const versions = this.config.paths?.versions; @@ -300,21 +300,6 @@ export class Config { ); } - const filePath = path.resolve(this.workingDir, vf.file); - if (!fs.existsSync(filePath)) { - throw new Error(`Version file does not exist: ${vf.file}`); - } - - const contents = fs.readFileSync(filePath, 'utf-8'); - const regex = new RegExp(vf.regex); - const matches = contents.match(regex); - - if (!matches || !matches[1] || !matches[2]) { - throw new Error( - `Could not find version in file ${vf.file} using regex "/${vf.regex}/"` - ); - } - this._versionFiles.push({ file: vf.file, regex: vf.regex }); } } diff --git a/src/utils/output.ts b/src/utils/output.ts index 319083f..30fb78f 100644 --- a/src/utils/output.ts +++ b/src/utils/output.ts @@ -37,7 +37,7 @@ export function getPrefix(): string { */ function formatMessage(message: string): string { if (prefix) { - return `[${prefix}] ${message}`; + return `${chalk.blue(`[${prefix}]`)} ${message}`; } return message; } diff --git a/tests/commands/check.test.ts b/tests/commands/check.test.ts new file mode 100644 index 0000000..d28e7ac --- /dev/null +++ b/tests/commands/check.test.ts @@ -0,0 +1,141 @@ +import path from 'node:path'; +import fs from 'fs-extra'; +import { + runPup, + writePuprc, + getPuprc, + createTempProject, + cleanupTempProjects, +} from '../helpers/setup.js'; + +describe('check command', () => { + let projectDir: string; + + beforeEach(() => { + projectDir = createTempProject(); + writePuprc(getPuprc(), projectDir); + }); + + afterEach(() => { + cleanupTempProjects(); + }); + + it('should run default checks successfully', async () => { + const result = await runPup('check', { cwd: projectDir }); + expect(result.exitCode).toBe(0); + expect(result.output).toContain('[tbd]'); + expect(result.output).toContain('[version-conflict]'); + }); + + it('should show guidance when checks is an empty object', async () => { + writePuprc(getPuprc({ checks: {} }), projectDir); + + const result = await runPup('check', { cwd: projectDir }); + expect(result.exitCode).toBe(0); + expect(result.output).toContain('The .puprc does not have any checks configured.'); + expect(result.output).toContain('"tbd": {}'); + expect(result.output).toContain('"version-conflict": {}'); + }); +}); + +describe('custom checks', () => { + afterEach(() => { + cleanupTempProjects(); + }); + + it('should run a passing command-type check', async () => { + const projectDir = createTempProject(); + writePuprc(getPuprc({ + checks: { + 'my-check': { + type: 'command', + command: 'echo "custom check passed"', + }, + }, + }), projectDir); + + const result = await runPup('check', { cwd: projectDir }); + expect(result.exitCode).toBe(0); + expect(result.output).toContain('[my-check]'); + expect(result.output).toContain('custom check passed'); + }); + + it('should run a failing command-type check with warn', async () => { + const projectDir = createTempProject(); + writePuprc(getPuprc({ + checks: { + 'my-check': { + type: 'command', + command: 'exit 1', + fail_method: 'warn', + }, + }, + }), projectDir); + + const result = await runPup('check', { cwd: projectDir }); + expect(result.exitCode).toBe(0); + expect(result.output).toContain('[my-check]'); + expect(result.output).toContain('The following checks failed:'); + expect(result.output).toContain('my-check'); + }); + + it('should bail on a failing command-type check with error', async () => { + const projectDir = createTempProject(); + writePuprc(getPuprc({ + checks: { + 'my-check': { + type: 'command', + command: 'exit 1', + fail_method: 'error', + }, + }, + }), projectDir); + + const result = await runPup('check', { cwd: projectDir }); + expect(result.exitCode).not.toBe(0); + expect(result.output).toContain('[my-check]'); + expect(result.output).toContain("my-check's fail_method in .puprc is set to"); + }); + + it('should register custom check as subcommand', async () => { + const projectDir = createTempProject(); + writePuprc(getPuprc({ + checks: { + 'my-check': { + type: 'command', + command: 'echo "ran via subcommand"', + }, + }, + }), projectDir); + + const result = await runPup('check:my-check', { cwd: projectDir }); + expect(result.exitCode).toBe(0); + expect(result.output).toContain('ran via subcommand'); + expect(result.output).not.toContain('[my-check]'); + }); + + it('should run a simple module check', async () => { + const projectDir = createTempProject(); + + const checkScript = path.join(projectDir, 'my-simple-check.mjs'); + fs.writeFileSync(checkScript, ` +export async function execute({ config, workingDir }) { + return { success: true, output: 'simple module check passed' }; +} +`); + + writePuprc(getPuprc({ + checks: { + 'simple-check': { + type: 'simple', + file: 'my-simple-check.mjs', + }, + }, + }), projectDir); + + const result = await runPup('check', { cwd: projectDir }); + expect(result.exitCode).toBe(0); + expect(result.output).toContain('[simple-check]'); + expect(result.output).toContain('simple module check passed'); + }); +}); diff --git a/tsup.config.ts b/tsup.config.ts index efb31b5..0ccd1fc 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -1,4 +1,12 @@ import { defineConfig } from 'tsup'; +import fs from 'node:fs'; + +const builtinCheckSlugs = fs.existsSync('src/commands/checks') + ? fs + .readdirSync('src/commands/checks') + .filter((f) => f.endsWith('.ts')) + .map((f) => f.replace(/\.ts$/, '')) + : []; export default defineConfig({ entry: ['src/cli.ts'], @@ -11,4 +19,7 @@ export default defineConfig({ banner: { js: '#!/usr/bin/env node', }, + define: { + BUILTIN_CHECK_SLUGS: JSON.stringify(builtinCheckSlugs), + }, }); From 4989e3a23665faf9f9d0a8cf7383583485d83914 Mon Sep 17 00:00:00 2001 From: Eric Defore Date: Thu, 12 Feb 2026 19:08:35 -0500 Subject: [PATCH 2/4] ENG-219: Remove default checks test from check.test.ts Default checks are tested sufficiently in their own dedicated test files. --- tests/commands/check.test.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/commands/check.test.ts b/tests/commands/check.test.ts index d28e7ac..d2b7dd8 100644 --- a/tests/commands/check.test.ts +++ b/tests/commands/check.test.ts @@ -20,13 +20,6 @@ describe('check command', () => { cleanupTempProjects(); }); - it('should run default checks successfully', async () => { - const result = await runPup('check', { cwd: projectDir }); - expect(result.exitCode).toBe(0); - expect(result.output).toContain('[tbd]'); - expect(result.output).toContain('[version-conflict]'); - }); - it('should show guidance when checks is an empty object', async () => { writePuprc(getPuprc({ checks: {} }), projectDir); From a865037f0f0adbe976a5ff24e6420e9e29553308 Mon Sep 17 00:00:00 2001 From: Eric Defore Date: Thu, 12 Feb 2026 22:01:00 -0500 Subject: [PATCH 3/4] ENG-219: Pass through CLI args to check subcommands Allow unknown options on check subcommands so that args like --expected-version are parsed and merged into the check's args config. --- src/commands/check.ts | 44 +++++++++++++++++++++++++++++++++--- tests/commands/check.test.ts | 25 ++++++++++++++++++++ 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/commands/check.ts b/src/commands/check.ts index fd6818e..52ee8cf 100644 --- a/src/commands/check.ts +++ b/src/commands/check.ts @@ -175,6 +175,36 @@ async function runModuleCheck( } } +/** + * Parses unknown CLI arguments into a key-value map. + * + * @since TBD + * + * @param {string[]} args - The raw argument array from Commander (unknown options). + * + * @returns {Record} A map of argument names to values. + */ +function parseExtraArgs(args: string[]): Record { + const result: Record = {}; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (!arg.startsWith('--')) continue; + + const key = arg.replace(/^--/, ''); + const nextArg = args[i + 1]; + + if (nextArg && !nextArg.startsWith('--')) { + result[key] = nextArg; + i++; + } else { + result[key] = 'true'; + } + } + + return result; +} + /** * Runs a single check by slug, handling prefix, output, and exit code. * @@ -184,12 +214,14 @@ async function runModuleCheck( * @param {object} options - The options object. * @param {boolean} [options.dev] - Whether to use dev failure methods. * @param {string} [options.root] - The root directory for running commands. + * @param {Record} extraArgs - Additional CLI arguments to merge into check args. * * @returns {Promise} The exit code: 0 for success, 1 if the check failed. */ async function runSingleCheck( slug: string, - options: { dev?: boolean; root?: string } + options: { dev?: boolean; root?: string }, + extraArgs: Record = {} ): Promise { const config = getConfig(options.root); const checks = config.getChecks(); @@ -202,6 +234,10 @@ async function runSingleCheck( return 1; } + if (Object.keys(extraArgs).length > 0) { + checkConfig.args = { ...checkConfig.args, ...extraArgs }; + } + let result: CheckResult; if (checkConfig.type === 'pup' || !checkConfig.type) { @@ -243,8 +279,10 @@ function registerCheckSubcommand(program: Command, slug: string): void { .description(`Run the ${slug} check.`) .option('--dev', 'Run with dev failure methods.') .option('--root ', 'Set the root directory for running commands.') - .action(async (options: { dev?: boolean; root?: string }) => { - const exitCode = await runSingleCheck(slug, options); + .allowUnknownOption() + .action(async (options: { dev?: boolean; root?: string }, command: Command) => { + const extraArgs = parseExtraArgs(command.args); + const exitCode = await runSingleCheck(slug, options, extraArgs); if (exitCode !== 0) { process.exit(exitCode); } diff --git a/tests/commands/check.test.ts b/tests/commands/check.test.ts index d2b7dd8..89e2094 100644 --- a/tests/commands/check.test.ts +++ b/tests/commands/check.test.ts @@ -131,4 +131,29 @@ export async function execute({ config, workingDir }) { expect(result.output).toContain('[simple-check]'); expect(result.output).toContain('simple module check passed'); }); + + it('should pass CLI args through to a subcommand check', async () => { + const projectDir = createTempProject(); + + const checkScript = path.join(projectDir, 'args-check.mjs'); + fs.writeFileSync(checkScript, ` +export async function execute({ config }) { + const version = config.args['some-arg'] || 'none'; + return { success: true, output: 'some-arg=' + version }; +} +`); + + writePuprc(getPuprc({ + checks: { + 'args-check': { + type: 'simple', + file: 'args-check.mjs', + }, + }, + }), projectDir); + + const result = await runPup('check:args-check --some-arg 5.0.1', { cwd: projectDir }); + expect(result.exitCode).toBe(0); + expect(result.output).toContain('some-arg=5.0.1'); + }); }); From 0f915836a717958c893585968722e7e7787dde77 Mon Sep 17 00:00:00 2001 From: Eric Defore Date: Fri, 13 Feb 2026 16:05:50 -0500 Subject: [PATCH 4/4] ENG-219: Remove unused workingDir arg from getConfig() calls The getConfig() singleton is already initialized in createApp() before commands run, so passing options.root has no effect. The --root flag correctly controls the cwd for subprocess execution, not config loading. --- src/commands/check.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/check.ts b/src/commands/check.ts index c74f1b8..dd8ef88 100644 --- a/src/commands/check.ts +++ b/src/commands/check.ts @@ -22,7 +22,7 @@ export async function runChecks(options: { dev?: boolean; root?: string; }): Promise { - const config = getConfig(options.root); + const config = getConfig(); const checks = config.getChecks(); const cwd = options.root ?? config.getWorkingDir(); @@ -223,7 +223,7 @@ async function runSingleCheck( options: { dev?: boolean; root?: string }, extraArgs: Record = {} ): Promise { - const config = getConfig(options.root); + const config = getConfig(); const checks = config.getChecks(); const cwd = options.root ?? config.getWorkingDir(); const isBuiltin = (BUILTIN_CHECK_SLUGS as string[]).includes(slug);