cli/new: move built-in prompts to be derived from role instead
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -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: '',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<string, string | number | boolean>;
|
||||
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<string, string | number | boolean>
|
||||
>(needsAnswer);
|
||||
|
||||
return {
|
||||
...defaultParams,
|
||||
|
||||
@@ -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<TOptions extends Answers> = DistinctQuestion<TOptions> & {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export function pluginIdPrompt(): Prompt<{ id: string }> {
|
||||
export type Prompt<TOptions extends Answers> = DistinctQuestion<TOptions>;
|
||||
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<Record<string, string>> {
|
||||
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;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,14 +16,12 @@
|
||||
import { NewTemplate } from '../types';
|
||||
|
||||
export interface Options extends Record<string, string | boolean> {
|
||||
id: string;
|
||||
private: boolean;
|
||||
baseVersion: string;
|
||||
license: string;
|
||||
targetPath: string;
|
||||
owner: string;
|
||||
scope: string;
|
||||
moduleId: string;
|
||||
}
|
||||
|
||||
export const resolvePackageName = (options: {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user