cli/new: more strict input separation and types

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2025-02-07 17:08:27 +01:00
parent 5c5eb056b7
commit 08bf562c98
6 changed files with 103 additions and 28 deletions
+4 -3
View File
@@ -15,7 +15,7 @@
*/
import {
collectPortableTemplateParams,
collectPortableTemplateInput,
loadPortableTemplate,
loadPortableTemplateConfig,
selectTemplateInteractively,
@@ -40,7 +40,8 @@ export async function createNewPackage(options: CreateNewPackageOptions) {
);
const template = await loadPortableTemplate(selectedTemplate);
const params = await collectPortableTemplateParams({
const input = await collectPortableTemplateInput({
config,
template,
prefilledParams: options.prefilledParams,
});
@@ -48,6 +49,6 @@ export async function createNewPackage(options: CreateNewPackageOptions) {
await executePortableTemplate({
config,
template,
params,
input,
});
}
@@ -27,19 +27,19 @@ import { createDirName, resolvePackageName } from './utils';
import { runAdditionalActions } from './additionalActions';
import { executePluginPackageTemplate } from './executePluginPackageTemplate';
import { TemporaryDirectoryManager } from './TemporaryDirectoryManager';
import { PortableTemplateConfig, PortableTemplateParams } from '../types';
import { PortableTemplateConfig, PortableTemplateInput } from '../types';
import { PortableTemplate } from '../types';
type ExecuteNewTemplateOptions = {
config: PortableTemplateConfig;
template: PortableTemplate;
params: PortableTemplateParams;
input: PortableTemplateInput;
};
export async function executePortableTemplate(
options: ExecuteNewTemplateOptions,
) {
const { template, params } = options;
const { config, template, input } = options;
const tmpDirManager = TemporaryDirectoryManager.create();
@@ -16,7 +16,7 @@
import inquirer from 'inquirer';
import { PortableTemplateConfig } from '../types';
import { collectPortableTemplateParams } from './collectPortableTemplateParams';
import { collectPortableTemplateInput } from './collectPortableTemplateInput';
describe('collectTemplateParams', () => {
const baseOptions = {
@@ -33,15 +33,24 @@ describe('collectTemplateParams', () => {
},
prefilledParams: {
pluginId: 'test',
owner: '',
owner: 'me',
},
};
it('should return default values if not provided', async () => {
await expect(collectPortableTemplateParams(baseOptions)).resolves.toEqual({
pluginId: 'test',
owner: '',
targetPath: '/example',
await expect(collectPortableTemplateInput(baseOptions)).resolves.toEqual({
roleParams: {
role: 'frontend-plugin',
pluginId: 'test',
},
builtInParams: {
owner: 'me',
},
params: {
pluginId: 'test',
owner: 'me',
},
globals: {},
});
});
@@ -49,13 +58,23 @@ describe('collectTemplateParams', () => {
jest.spyOn(inquirer, 'prompt').mockResolvedValueOnce({ pluginId: 'other' });
await expect(
collectPortableTemplateParams({
collectPortableTemplateInput({
...baseOptions,
prefilledParams: {},
}),
).resolves.toEqual({
pluginId: 'other',
targetPath: '/example',
roleParams: {
role: 'frontend-plugin',
pluginId: 'other',
},
builtInParams: {
owner: undefined,
},
params: {
pluginId: 'other',
owner: undefined,
},
globals: {},
});
});
});
@@ -18,32 +18,38 @@ import inquirer, { DistinctQuestion } from 'inquirer';
import { getCodeownersFilePath, parseOwnerIds } from '../../codeowners';
import { paths } from '../../paths';
import {
PortableTemplateConfig,
PortableTemplateInput,
PortableTemplateInputBuiltInParams,
PortableTemplateInputRoleParams,
PortableTemplateParams,
PortableTemplatePrompt,
PortableTemplateRole,
} from '../types';
import { PortableTemplate } from '../types';
const RESERVED_PROMPT_NAMES = ['name', 'pluginId', 'moduleId', 'owner'];
type CollectTemplateParamsOptions = {
config: PortableTemplateConfig;
template: PortableTemplate;
prefilledParams: PortableTemplateParams;
};
export async function collectPortableTemplateParams(
export async function collectPortableTemplateInput(
options: CollectTemplateParamsOptions,
): Promise<PortableTemplateParams> {
const { template, prefilledParams } = options;
): Promise<PortableTemplateInput> {
const { config, template, prefilledParams } = options;
const codeOwnersFilePath = await getCodeownersFilePath(paths.targetRoot);
const prompts = getPromptsForRole(template.role);
const rolePrompts = getPromptsForRole(template.role);
if (codeOwnersFilePath) {
prompts.push(ownerPrompt());
}
if (template.prompts) {
prompts.push(...template.prompts.map(customPrompt));
}
const buildInPrompts = codeOwnersFilePath ? [ownerPrompt()] : [];
const templatePrompts = template.prompts?.map(customPrompt) ?? [];
const prompts = [...rolePrompts, ...buildInPrompts, ...templatePrompts];
const needsAnswer = [];
const prefilledAnswers = {} as PortableTemplateParams;
@@ -59,10 +65,23 @@ export async function collectPortableTemplateParams(
needsAnswer,
);
return {
const answers = {
...prefilledAnswers,
...promptAnswers,
targetPath: template.targetPath,
};
return {
roleParams: {
role: template.role,
name: answers.name,
pluginId: answers.pluginId,
moduleId: answers.moduleId,
} as PortableTemplateInputRoleParams,
builtInParams: {
owner: answers.owner,
} as PortableTemplateInputBuiltInParams,
params: answers,
globals: config.globals,
};
}
@@ -157,6 +176,11 @@ export function ownerPrompt(): DistinctQuestion {
}
export function customPrompt(prompt: PortableTemplatePrompt): DistinctQuestion {
if (RESERVED_PROMPT_NAMES.includes(prompt.id)) {
throw new Error(
`Prompt ID '${prompt.id}' is reserved and cannot be used in a template`,
);
}
return {
type: 'input',
name: prompt.id,
@@ -14,7 +14,7 @@
* limitations under the License.
*/
export { collectPortableTemplateParams } from './collectPortableTemplateParams';
export { collectPortableTemplateInput } from './collectPortableTemplateInput';
export { loadPortableTemplateConfig } from './loadPortableTemplateConfig';
export { selectTemplateInteractively } from './selectTemplateInteractively';
export { loadPortableTemplate } from './loadPortableTemplate';
+31
View File
@@ -78,3 +78,34 @@ export type PortableTemplateGlobals = {
private?: boolean;
scope?: string;
};
export type PortableTemplateInputRoleParams =
| {
role: 'web-library' | 'node-library' | 'common-library';
name: string;
}
| {
role:
| 'plugin-web-library'
| 'plugin-node-library'
| 'plugin-common-library'
| 'frontend-plugin'
| 'backend-plugin';
pluginId: string;
}
| {
role: 'frontend-plugin-module' | 'backend-plugin-module';
pluginId: string;
moduleId: string;
};
export type PortableTemplateInputBuiltInParams = {
owner?: string;
};
export type PortableTemplateInput = {
roleParams: PortableTemplateInputRoleParams;
builtInParams: PortableTemplateInputBuiltInParams;
params: PortableTemplateParams;
globals: PortableTemplateParams;
};