diff --git a/src/core/search-list.test.ts b/src/core/search-list.test.ts index e0f5d81..c89989c 100644 --- a/src/core/search-list.test.ts +++ b/src/core/search-list.test.ts @@ -361,6 +361,44 @@ describe('registerSearchList', () => { expect(registered[0][1]).not.toBe(SearchList); }); + it('registers on the default export of an inquirer that has no top-level registerPrompt, as cli-utilities 2 ships', () => { + const registered: string[] = []; + const fakeLoad = (id: string): unknown => + id === 'inquirer' ? { default: { registerPrompt: (name: string) => registered.push(name) } } : SearchList; + + registerSearchList(fakeLoad); + + expect(registered).toEqual(['search-list', 'list', 'checkbox']); + }); + + it.each([ + ['its package exports hide lib/prompts, as inquirer 12 does', 'ERR_PACKAGE_PATH_NOT_EXPORTED'], + ['lib/prompts is missing from the package', 'MODULE_NOT_FOUND'], + ])('keeps the built-in list and checkbox when %s', (_reason, code) => { + const registered: string[] = []; + const fakeLoad = (id: string): unknown => { + if (id.startsWith('inquirer/lib/prompts/')) { + throw Object.assign(new Error(`Cannot load '${id}'`), { code }); + } + return id === 'inquirer' ? { default: { registerPrompt: (name: string) => registered.push(name) } } : SearchList; + }; + + expect(() => registerSearchList(fakeLoad)).not.toThrow(); + expect(registered).toEqual(['search-list']); + }); + + it('still fails when a list or checkbox prompt cannot load for any reason other than being absent', () => { + const broken = new Error('Unexpected token in inquirer/lib/prompts/list'); + const fakeLoad = (id: string): unknown => { + if (id === 'inquirer/lib/prompts/list') { + throw broken; + } + return id === 'inquirer' ? { registerPrompt: () => undefined } : SearchList; + }; + + expect(() => registerSearchList(fakeLoad)).toThrow(broken); + }); + it('registers on the real inquirer cli-utilities depends on when no loader is given', () => { const inquirer = load('inquirer') as { prompt: { prompts: Record } }; diff --git a/src/core/search-list.ts b/src/core/search-list.ts index b9b6d93..76070a1 100644 --- a/src/core/search-list.ts +++ b/src/core/search-list.ts @@ -153,12 +153,39 @@ export function utilitiesLoader(): ModuleLoader { return createRequire(require.resolve('@contentstack/cli-utilities')); } +// cli-utilities 1 ships inquirer 8 (registerPrompt on the module, prompts under lib/prompts). csdx 2 forces +// every plugin onto its own cli-utilities 2, whose inquirer 12 has registerPrompt only on the default export +// and no lib/prompts, so list and checkbox keep inquirer's own paging there. +function promptRegistry(inquirer: unknown): PromptRegistry { + const module = inquirer as Partial & { default?: PromptRegistry }; + + return typeof module.registerPrompt === 'function' ? (module as PromptRegistry) : (module.default as PromptRegistry); +} + +const ABSENT_MODULE_CODES = new Set(['MODULE_NOT_FOUND', 'ERR_PACKAGE_PATH_NOT_EXPORTED']); + +function loadIfPresent(load: ModuleLoader, id: string): unknown { + try { + return load(id); + } catch (error) { + if (ABSENT_MODULE_CODES.has(String((error as { code?: unknown }).code))) { + return undefined; + } + + throw error; + } +} + export function registerSearchList(load: ModuleLoader = utilitiesLoader()): void { - const inquirer = load('inquirer') as PromptRegistry; + const inquirer = promptRegistry(load('inquirer')); inquirer.registerPrompt('search-list', launchSearchList(load('inquirer-search-list') as SearchListClass)); for (const type of ['list', 'checkbox']) { - inquirer.registerPrompt(type, launchPaginated(load(`inquirer/lib/prompts/${type}`) as PaginatedClass)); + const Prompt = loadIfPresent(load, `inquirer/lib/prompts/${type}`); + + if (Prompt !== undefined) { + inquirer.registerPrompt(type, launchPaginated(Prompt as PaginatedClass)); + } } }