/ utils / bash / registry.ts
registry.ts
 1  import { memoizeWithLRU } from '../memoize.js'
 2  import specs from './specs/index.js'
 3  
 4  export type CommandSpec = {
 5    name: string
 6    description?: string
 7    subcommands?: CommandSpec[]
 8    args?: Argument | Argument[]
 9    options?: Option[]
10  }
11  
12  export type Argument = {
13    name?: string
14    description?: string
15    isDangerous?: boolean
16    isVariadic?: boolean // repeats infinitely e.g. echo hello world
17    isOptional?: boolean
18    isCommand?: boolean // wrapper commands e.g. timeout, sudo
19    isModule?: string | boolean // for python -m and similar module args
20    isScript?: boolean // script files e.g. node script.js
21  }
22  
23  export type Option = {
24    name: string | string[]
25    description?: string
26    args?: Argument | Argument[]
27    isRequired?: boolean
28  }
29  
30  export async function loadFigSpec(
31    command: string,
32  ): Promise<CommandSpec | null> {
33    if (!command || command.includes('/') || command.includes('\\')) return null
34    if (command.includes('..')) return null
35    if (command.startsWith('-') && command !== '-') return null
36  
37    try {
38      const module = await import(`@withfig/autocomplete/build/${command}.js`)
39      return module.default || module
40    } catch {
41      return null
42    }
43  }
44  export const getCommandSpec = memoizeWithLRU(
45    async (command: string): Promise<CommandSpec | null> => {
46      const spec =
47        specs.find(s => s.name === command) ||
48        (await loadFigSpec(command)) ||
49        null
50      return spec
51    },
52    (command: string) => command,
53  )