cli/new: peek into local template definitions

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2025-02-10 20:04:44 +01:00
parent 5108226057
commit b18880af3d
3 changed files with 66 additions and 82 deletions
+9 -50
View File
@@ -15,54 +15,13 @@
*/
export const defaultTemplates = [
{
id: 'backend-module',
target: require.resolve(
'@backstage/cli/templates/default-backend-module.yaml',
),
},
{
id: 'backend-plugin',
target: require.resolve(
'@backstage/cli/templates/default-backend-plugin.yaml',
),
},
{
id: 'plugin-common',
target: require.resolve(
'@backstage/cli/templates/default-common-plugin-package.yaml',
),
},
{
id: 'plugin-node',
target: require.resolve(
'@backstage/cli/templates/default-node-plugin-package.yaml',
),
},
{
id: 'frontend-plugin',
target: require.resolve('@backstage/cli/templates/default-plugin.yaml'),
},
{
id: 'plugin-react',
target: require.resolve(
'@backstage/cli/templates/default-react-plugin-package.yaml',
),
},
{
id: 'node-library',
target: require.resolve(
'@backstage/cli/templates/node-library-package.yaml',
),
},
{
id: 'web-library',
target: require.resolve(
'@backstage/cli/templates/web-library-package.yaml',
),
},
{
id: 'scaffolder-module',
target: require.resolve('@backstage/cli/templates/scaffolder-module.yaml'),
},
'@backstage/cli/templates/frontend-plugin',
'@backstage/cli/templates/backend-plugin',
'@backstage/cli/templates/backend-plugin-module',
'@backstage/cli/templates/plugin-web-library',
'@backstage/cli/templates/plugin-node-library',
'@backstage/cli/templates/plugin-common-library',
'@backstage/cli/templates/web-library',
'@backstage/cli/templates/node-library',
'@backstage/cli/templates/scaffolder-backend-module',
];
@@ -15,9 +15,11 @@
*/
import fs from 'fs-extra';
import { resolve as resolvePath, dirname, extname } from 'node:path';
import { paths } from '../../paths';
import { defaultTemplates } from '../defaultTemplates';
import { PortableTemplateConfig } from '../types';
import { PortableTemplateConfig, PortableTemplatePointer } from '../types';
import { parse as parseYaml } from 'yaml';
import { z } from 'zod';
import { fromZodError } from 'zod-validation-error';
import { ForwardedError } from '@backstage/errors';
@@ -31,27 +33,9 @@ const defaults = {
packageNamePluginInfix: 'plugin-',
};
const builtInTemplateIds = defaultTemplates.map(t => `default-${t.id}`) as [
string,
...string[],
];
const newConfigSchema = z
.object({
templates: z
.array(
z.union([
z.enum(builtInTemplateIds),
z
.object({
id: z.string(),
description: z.string().optional(),
target: z.string(),
})
.strict(),
]),
)
.optional(),
templates: z.array(z.string()).optional(),
globals: z
.object({
license: z.string().optional(),
@@ -100,19 +84,21 @@ export async function loadPortableTemplateConfig(
const config = parsed.data.backstage?.cli?.new;
const templatePointers =
config?.templates?.map(t => {
if (typeof t === 'string') {
const defaultTemplate = defaultTemplates.find(
d => t === `default-${d.id}`,
const basePath = dirname(pkgPath);
const templatePointers = await Promise.all(
(config?.templates ?? defaultTemplates).map(pointer => {
try {
const templatePath = resolveLocalTemplatePath(pointer, basePath);
return peekLocalTemplateDefinition(templatePath);
} catch (error) {
throw new ForwardedError(
`Failed to load template definition '${pointer}'`,
error,
);
if (!defaultTemplate) {
throw new Error(`Built-in template '${t}' does not exist`);
}
return defaultTemplate;
}
return t;
}) ?? defaultTemplates;
}),
);
return {
isUsingDefaultTemplates: !config?.templates,
@@ -134,3 +120,42 @@ export async function loadPortableTemplateConfig(
defaults.packageNamePluginInfix,
};
}
function resolveLocalTemplatePath(pointer: string, basePath: string): string {
const isDirectoryPointer = extname(pointer) === '';
if (pointer.startsWith('.')) {
if (isDirectoryPointer) {
return resolvePath(basePath, pointer, 'template.yaml');
}
return resolvePath(basePath, pointer);
}
if (isDirectoryPointer) {
return require.resolve(`${pointer}/template.yaml`, { paths: [basePath] });
}
return require.resolve(pointer, { paths: [basePath] });
}
const partialTemplateDefinitionSchema = z.object({
name: z.string(),
description: z.string().optional(),
});
async function peekLocalTemplateDefinition(
target: string,
): Promise<PortableTemplatePointer> {
const content = await fs.readFile(target, 'utf8');
const rawTemplate = parseYaml(content);
const parsed = partialTemplateDefinitionSchema.safeParse(rawTemplate);
if (!parsed.success) {
throw fromZodError(parsed.error);
}
return {
name: parsed.data.name,
description: parsed.data.description,
target,
};
}
+1 -1
View File
@@ -39,7 +39,7 @@ export type PortableTemplateConfig = {
};
export type PortableTemplatePointer = {
id: string;
name: string;
description?: string;
target: string;
};