From 64f90cbea039d472628e4e061cbabe2395d0e907 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 7 Feb 2025 15:56:49 +0100 Subject: [PATCH] cli/new: move built-in prompts to be derived from role instead Signed-off-by: Patrik Oldsberg --- .../collection/collectTemplateParams.test.ts | 45 ++---- .../new/collection/collectTemplateParams.ts | 44 +++--- .../cli/src/lib/new/collection/prompts.ts | 139 ++++++++---------- packages/cli/src/lib/new/execution/utils.ts | 2 - .../src/lib/new/preparation/loadTemplate.ts | 15 +- packages/cli/src/lib/new/types.ts | 14 +- .../cli/templates/default-backend-module.yaml | 4 - .../cli/templates/default-backend-plugin.yaml | 3 - .../default-common-plugin-package.yaml | 3 - .../default-node-plugin-package.yaml | 3 - packages/cli/templates/default-plugin.yaml | 3 - .../default-react-plugin-package.yaml | 3 - .../cli/templates/node-library-package.yaml | 3 - packages/cli/templates/scaffolder-module.yaml | 9 +- .../cli/templates/web-library-package.yaml | 3 - 15 files changed, 110 insertions(+), 183 deletions(-) diff --git a/packages/cli/src/lib/new/collection/collectTemplateParams.test.ts b/packages/cli/src/lib/new/collection/collectTemplateParams.test.ts index b92fe385c8..af1e6f4181 100644 --- a/packages/cli/src/lib/new/collection/collectTemplateParams.test.ts +++ b/packages/cli/src/lib/new/collection/collectTemplateParams.test.ts @@ -29,20 +29,23 @@ describe('collectTemplateParams', () => { id: 'test', templatePath: '/test', targetPath: '/example', + role: 'frontend-plugin' as const, + }, + prefilledParams: { + pluginId: 'test', + owner: '', }, - prefilledParams: {}, }; it('should return default values if not provided', async () => { await expect(collectTemplateParams(baseOptions)).resolves.toEqual({ - id: '', + pluginId: 'test', private: true, baseVersion: '0.1.0', owner: '', license: 'Apache-2.0', targetPath: '/example', scope: '', - moduleId: '', }); }); @@ -53,61 +56,33 @@ describe('collectTemplateParams', () => { config: { ...baseOptions.config, globals: { foo: 'bar' } }, }), ).resolves.toEqual({ - id: '', + pluginId: 'test', private: true, baseVersion: '0.1.0', owner: '', license: 'Apache-2.0', targetPath: '/example', scope: '', - moduleId: '', foo: 'bar', }); }); - it('should use prefilled parameters', async () => { - await expect( - collectTemplateParams({ - ...baseOptions, - template: { - ...baseOptions.template, - prompts: ['id'], - }, - prefilledParams: { id: 'test' }, - }), - ).resolves.toEqual({ - id: 'test', - private: true, - baseVersion: '0.1.0', - owner: '', - license: 'Apache-2.0', - targetPath: '/example', - scope: '', - moduleId: '', - }); - }); - - it('should prompt for parameters', async () => { - jest.spyOn(inquirer, 'prompt').mockResolvedValueOnce({ id: 'test' }); + it('should prompt for missing parameters', async () => { + jest.spyOn(inquirer, 'prompt').mockResolvedValueOnce({ pluginId: 'other' }); await expect( collectTemplateParams({ ...baseOptions, - template: { - ...baseOptions.template, - prompts: ['id'], - }, prefilledParams: {}, }), ).resolves.toEqual({ - id: 'test', + pluginId: 'other', private: true, baseVersion: '0.1.0', owner: '', license: 'Apache-2.0', targetPath: '/example', scope: '', - moduleId: '', }); }); }); diff --git a/packages/cli/src/lib/new/collection/collectTemplateParams.ts b/packages/cli/src/lib/new/collection/collectTemplateParams.ts index fc1879bd46..31d9837ef8 100644 --- a/packages/cli/src/lib/new/collection/collectTemplateParams.ts +++ b/packages/cli/src/lib/new/collection/collectTemplateParams.ts @@ -14,10 +14,11 @@ * limitations under the License. */ +import inquirer from 'inquirer'; import { getCodeownersFilePath } from '../../codeowners'; import { paths } from '../../paths'; import { NewConfig } from '../types'; -import { promptOptions } from './prompts'; +import { buildCustomPrompt, getPromptsForRole, ownerPrompt } from './prompts'; import { NewTemplate } from '../types'; import { Options } from '../execution/utils'; @@ -28,11 +29,9 @@ type CollectTemplateParamsOptions = { }; const defaultParams = { - id: '', owner: '', license: 'Apache-2.0', scope: '', - moduleId: '', baseVersion: '0.1.0', private: true, }; @@ -44,27 +43,28 @@ export async function collectTemplateParams( const codeOwnersFilePath = await getCodeownersFilePath(paths.targetRoot); - const prompts = template.prompts ?? []; + const prompts = getPromptsForRole(template.role); - const prefilledAnswers = Object.fromEntries( - prompts.flatMap(prompt => { - const id = typeof prompt === 'string' ? prompt : prompt.id; - const answer = prefilledParams[id]; - return answer ? [[id, answer]] : []; - }), - ); + if (codeOwnersFilePath) { + prompts.push(ownerPrompt()); + } + if (template.prompts) { + prompts.push(...template.prompts.map(buildCustomPrompt)); + } - const promptAnswers = await promptOptions({ - prompts: - prompts.filter( - prompt => - !Object.hasOwn( - prefilledAnswers, - typeof prompt === 'string' ? prompt : prompt.id, - ), - ) ?? [], - codeOwnersFilePath, - }); + const needsAnswer = []; + const prefilledAnswers = {} as Record; + for (const prompt of prompts) { + if (prefilledParams[prompt.name] !== undefined) { + prefilledAnswers[prompt.name] = prefilledParams[prompt.name]; + } else { + needsAnswer.push(prompt); + } + } + + const promptAnswers = await inquirer.prompt< + Record + >(needsAnswer); return { ...defaultParams, diff --git a/packages/cli/src/lib/new/collection/prompts.ts b/packages/cli/src/lib/new/collection/prompts.ts index fa2f88d8e1..6d3b479486 100644 --- a/packages/cli/src/lib/new/collection/prompts.ts +++ b/packages/cli/src/lib/new/collection/prompts.ts @@ -14,18 +14,31 @@ * limitations under the License. */ -import inquirer, { Answers, DistinctQuestion } from 'inquirer'; -import { NewTemplatePrompt } from '../types'; +import { Answers, DistinctQuestion } from 'inquirer'; +import { NewTemplatePrompt, TemplateRole } from '../types'; import { parseOwnerIds } from '../../codeowners'; -export type Prompt = DistinctQuestion & { - name: string; -}; - -export function pluginIdPrompt(): Prompt<{ id: string }> { +export type Prompt = DistinctQuestion; +export function namePrompt(): Prompt<{ name: string }> { return { type: 'input', - name: 'id', + name: 'name', + message: 'Enter the name of the package, without scope [required]', + validate: (value: string) => { + if (!value) { + return 'Please enter the name of the package'; + } else if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(value)) { + return 'Package names must be lowercase and contain only letters, digits, and dashes.'; + } + return true; + }, + }; +} + +export function pluginIdPrompt(): Prompt<{ pluginId: string }> { + return { + type: 'input', + name: 'pluginId', message: 'Enter the ID of the plugin [required]', validate: (value: string) => { if (!value) { @@ -54,30 +67,31 @@ export function moduleIdIdPrompt(): Prompt<{ moduleId: string }> { }; } -export function npmRegistryPrompt(): Prompt<{ npmRegistry: string }> { - return { - type: 'input', - name: 'npmRegistry', - message: 'Please specify your NPM registry [optional]', - validate: (value: string) => { - if (!value) { - return 'Please enter the URL of your NPM registry'; - } else if (!/^http*$/.test(value)) { - return 'Invalid URL.'; - } - return true; - }, - }; +export function getPromptsForRole(role: TemplateRole) { + switch (role) { + case 'web-library': + case 'node-library': + case 'common-library': + return [namePrompt()]; + case 'plugin-web-library': + case 'plugin-node-library': + case 'plugin-common-library': + case 'frontend-plugin': + case 'backend-plugin': + return [pluginIdPrompt()]; + case 'frontend-plugin-module': + case 'backend-plugin-module': + return [pluginIdPrompt(), moduleIdIdPrompt()]; + default: + return []; + } } -export function ownerPrompt( - codeOwnersPath: string | undefined, -): Prompt<{ owner?: string }> { +export function ownerPrompt(): Prompt<{ owner?: string }> { return { type: 'input', name: 'owner', message: 'Enter an owner to add to CODEOWNERS [optional]', - when: Boolean(codeOwnersPath), validate: (value: string) => { if (!value) { return true; @@ -93,58 +107,33 @@ export function ownerPrompt( }; } -export async function promptOptions({ - prompts, - codeOwnersFilePath, -}: { - prompts: NewTemplatePrompt[]; - codeOwnersFilePath: string | undefined; -}): Promise> { - const answers = await inquirer.prompt( - prompts.map((prompt: NewTemplatePrompt) => { - if (typeof prompt === 'string') { - switch (prompt) { - case 'id': - return pluginIdPrompt(); - case 'moduleId': - return moduleIdIdPrompt(); - case 'npmRegistry': - return npmRegistryPrompt(); - case 'owner': - return ownerPrompt(codeOwnersFilePath); +export function buildCustomPrompt( + prompt: NewTemplatePrompt, +): Prompt<{ [key: string]: string }> { + return { + type: 'input', + name: prompt.id, + message: prompt.prompt, + validate: (value: string) => { + if (!value) { + return `Please provide a value for ${prompt.id}`; + } else if (prompt.validate) { + let valid: boolean; + let message: string; + switch (prompt.validate) { + case 'backstage-id': + valid = /^[a-z0-9]+(-[a-z0-9]+)*$/.test(value); + message = + 'Value must be lowercase and contain only letters, digits, and dashes.'; + break; default: throw new Error( - `There is no built-in prompt with the following id: ${prompt}`, + `There is no built-in validator with the following id: ${prompt.validate}`, ); } + return valid || message; } - return { - type: 'input', - name: prompt.id, - message: prompt.prompt, - validate: (value: string) => { - if (!value) { - return `Please provide a value for ${prompt.id}`; - } else if (prompt.validate) { - let valid: boolean; - let message: string; - switch (prompt.validate) { - case 'backstage-id': - valid = /^[a-z0-9]+(-[a-z0-9]+)*$/.test(value); - message = - 'Value must be lowercase and contain only letters, digits, and dashes.'; - break; - default: - throw new Error( - `There is no built-in validator with the following id: ${prompt.validate}`, - ); - } - return valid || message; - } - return true; - }, - }; - }), - ); - return { ...answers }; + return true; + }, + }; } diff --git a/packages/cli/src/lib/new/execution/utils.ts b/packages/cli/src/lib/new/execution/utils.ts index 3fb2a97047..d9b7628b1c 100644 --- a/packages/cli/src/lib/new/execution/utils.ts +++ b/packages/cli/src/lib/new/execution/utils.ts @@ -16,14 +16,12 @@ import { NewTemplate } from '../types'; export interface Options extends Record { - id: string; private: boolean; baseVersion: string; license: string; targetPath: string; owner: string; scope: string; - moduleId: string; } export const resolvePackageName = (options: { diff --git a/packages/cli/src/lib/new/preparation/loadTemplate.ts b/packages/cli/src/lib/new/preparation/loadTemplate.ts index eaa73e1e83..9a0f50fc07 100644 --- a/packages/cli/src/lib/new/preparation/loadTemplate.ts +++ b/packages/cli/src/lib/new/preparation/loadTemplate.ts @@ -33,15 +33,12 @@ const templateDefinitionSchema = z role: z.enum(TEMPLATE_ROLES), prompts: z .array( - z.union([ - z.string(), - z.object({ - id: z.string(), - prompt: z.string(), - validate: z.string().optional(), - default: z.union([z.string(), z.boolean(), z.number()]).optional(), - }), - ]), + z.object({ + id: z.string(), + prompt: z.string(), + validate: z.string().optional(), + default: z.union([z.string(), z.boolean(), z.number()]).optional(), + }), ) .optional(), additionalActions: z.array(z.string()).optional(), diff --git a/packages/cli/src/lib/new/types.ts b/packages/cli/src/lib/new/types.ts index 4a91e708cd..ed3a2a6e7a 100644 --- a/packages/cli/src/lib/new/types.ts +++ b/packages/cli/src/lib/new/types.ts @@ -38,14 +38,12 @@ export type NewTemplatePointer = { target: string; }; -export type NewTemplatePrompt = - | { - id: string; - prompt: string; - validate?: string; - default?: string | boolean | number; - } - | string; +export type NewTemplatePrompt = { + id: string; + prompt: string; + validate?: string; + default?: string | boolean | number; +}; export const TEMPLATE_ROLES = [ 'web-library', diff --git a/packages/cli/templates/default-backend-module.yaml b/packages/cli/templates/default-backend-module.yaml index d89c9e546e..9973e17410 100644 --- a/packages/cli/templates/default-backend-module.yaml +++ b/packages/cli/templates/default-backend-module.yaml @@ -2,10 +2,6 @@ description: A new backend module that extends an existing backend plugin with a template: ./default-backend-module targetPath: plugins role: backend-plugin-module -prompts: - - id - - moduleId - - owner additionalActions: - install-backend - add-backend diff --git a/packages/cli/templates/default-backend-plugin.yaml b/packages/cli/templates/default-backend-plugin.yaml index 79dd690f64..cc085044d2 100644 --- a/packages/cli/templates/default-backend-plugin.yaml +++ b/packages/cli/templates/default-backend-plugin.yaml @@ -2,9 +2,6 @@ description: A new backend plugin template: ./default-backend-plugin targetPath: plugins role: backend-plugin -prompts: - - id - - owner additionalActions: - install-backend - add-backend diff --git a/packages/cli/templates/default-common-plugin-package.yaml b/packages/cli/templates/default-common-plugin-package.yaml index d8bb07b8fe..a84428ac67 100644 --- a/packages/cli/templates/default-common-plugin-package.yaml +++ b/packages/cli/templates/default-common-plugin-package.yaml @@ -2,6 +2,3 @@ description: A new isomorphic common plugin package template: ./default-common-plugin-package targetPath: plugins role: plugin-common-library -prompts: - - id - - owner diff --git a/packages/cli/templates/default-node-plugin-package.yaml b/packages/cli/templates/default-node-plugin-package.yaml index 0b2e41fbaf..fd52190983 100644 --- a/packages/cli/templates/default-node-plugin-package.yaml +++ b/packages/cli/templates/default-node-plugin-package.yaml @@ -2,6 +2,3 @@ description: A new Node.js library plugin package template: ./default-node-plugin-package targetPath: plugins role: plugin-node-library -prompts: - - id - - owner diff --git a/packages/cli/templates/default-plugin.yaml b/packages/cli/templates/default-plugin.yaml index 30fa908fb8..9dd71394d8 100644 --- a/packages/cli/templates/default-plugin.yaml +++ b/packages/cli/templates/default-plugin.yaml @@ -2,9 +2,6 @@ description: A new frontend plugin template: ./default-plugin targetPath: plugins role: frontend-plugin -prompts: - - id - - owner additionalActions: - install-frontend - add-frontend-legacy diff --git a/packages/cli/templates/default-react-plugin-package.yaml b/packages/cli/templates/default-react-plugin-package.yaml index f4ae4efe26..64290b6513 100644 --- a/packages/cli/templates/default-react-plugin-package.yaml +++ b/packages/cli/templates/default-react-plugin-package.yaml @@ -2,6 +2,3 @@ description: A new web library plugin package template: ./default-react-plugin-package targetPath: plugins role: plugin-web-library -prompts: - - id - - owner diff --git a/packages/cli/templates/node-library-package.yaml b/packages/cli/templates/node-library-package.yaml index fd17ffe40a..2a3c6df218 100644 --- a/packages/cli/templates/node-library-package.yaml +++ b/packages/cli/templates/node-library-package.yaml @@ -2,6 +2,3 @@ description: A new node-library package, exporting shared functionality for back template: ./node-library-package targetPath: packages role: node-library -prompts: - - id - - owner diff --git a/packages/cli/templates/scaffolder-module.yaml b/packages/cli/templates/scaffolder-module.yaml index 61285af75e..cf206bf51c 100644 --- a/packages/cli/templates/scaffolder-module.yaml +++ b/packages/cli/templates/scaffolder-module.yaml @@ -2,13 +2,8 @@ description: A module exporting custom actions for @backstage/plugin-scaffolder- template: ./scaffolder-module targetPath: plugins role: backend-plugin-module -prompts: - - id: id - prompt: Enter the ID of the plugin [required] - default: scaffolder - validate: backstage-id - - moduleId - - owner +params: + pluginId: scaffolder additionalActions: - install-backend - add-backend diff --git a/packages/cli/templates/web-library-package.yaml b/packages/cli/templates/web-library-package.yaml index 13d0cb6c96..e2150396ff 100644 --- a/packages/cli/templates/web-library-package.yaml +++ b/packages/cli/templates/web-library-package.yaml @@ -2,6 +2,3 @@ description: A new web-library package, exporting shared functionality for front template: ./web-library-package targetPath: packages role: web-library -prompts: - - id - - owner