diff --git a/packages/knip/src/plugins/moonrepo/index.ts b/packages/knip/src/plugins/moonrepo/index.ts index cdfaf1b34..6d2ae3987 100644 --- a/packages/knip/src/plugins/moonrepo/index.ts +++ b/packages/knip/src/plugins/moonrepo/index.ts @@ -1,5 +1,6 @@ import type { IsPluginEnabled, Plugin, ResolveConfig } from '../../types/config.js'; import { hasDependency } from '../../util/plugin.js'; +import { createCommandProcessor } from '../../util/string.js'; import type { MoonConfiguration } from './types.js'; // https://moonrepo.dev/docs @@ -15,16 +16,20 @@ const isRootOnly = true; const config = ['moon.yml', '.moon/tasks.yml', '.moon/tasks/*.yml']; const resolveConfig: ResolveConfig = async (config, options) => { + const commandProcessor = createCommandProcessor({ + '$projectRoot': options.cwd, + '$workspaceRoot': options.rootCwd, + }) const tasks = config.tasks ? Object.values(config.tasks) : []; const inputs = tasks .map(task => task.command) .filter(command => command) - .map(command => command.replace('$workspaceRoot', options.rootCwd)) - .map(command => command.replace('$projectRoot', options.cwd)) + .map(commandProcessor) .flatMap(command => options.getInputsFromScripts(command)); return [...inputs]; }; + export default { title, enablers, diff --git a/packages/knip/src/plugins/moonrepo/types.ts b/packages/knip/src/plugins/moonrepo/types.ts index b94f4f425..ef2b529fc 100644 --- a/packages/knip/src/plugins/moonrepo/types.ts +++ b/packages/knip/src/plugins/moonrepo/types.ts @@ -1,7 +1,7 @@ export interface MoonConfiguration { tasks?: { [taskName: string]: { - command: string; + command: string | string[]; }; }; } diff --git a/packages/knip/src/util/string.ts b/packages/knip/src/util/string.ts index df3886ef5..338d1515a 100644 --- a/packages/knip/src/util/string.ts +++ b/packages/knip/src/util/string.ts @@ -68,3 +68,45 @@ export const prettyMilliseconds = (ms: number): string => { if (minutes > 0) return `${minutes}m ${Math.floor(seconds % 60)}s`; return seconds % 1 ? `${seconds.toFixed(1)}s` : `${Math.floor(seconds)}s`; }; + +/** + * Template literal processor for commands that might be + * strings or arrays of strings. + * + * @example + * ```ts + * const processor = createCommandProcessor({ '$projectRoot': '/path/to/project' }); + * + * console.log(processor('echo $projectRoot')); + * // 'echo /path/to/project' + * + * console.log(processor(['npx', 'tsx', '$projectRoot/server/worker.js', '--', '--force'])); + * // ['npx', 'tsx', '/path/to/project/server/worker.js', '--', '--force'] + * ``` + */ +export const createCommandProcessor = (context: T) => { + + const templater = (template: string):string => { + return template.replace(/\$(\w+)/g, (_, key) => { + const value = context[key as keyof T]; + if (!value) { + return key; + } + return String(value); + }); + } + + const processor = (command: C): C => { + if (typeof command === 'string') { + return templater(command) as C; + } + + if (Array.isArray(command)) { + return command.map(cmd => processor(cmd)) as C; + } + + throw new TypeError('Command must be a string or an array of strings'); + } + + return processor +} \ No newline at end of file