Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions src/core/search-list.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown> } };

Expand Down
31 changes: 29 additions & 2 deletions src/core/search-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<PromptRegistry> & { 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));
}
}
}
Loading