diff --git a/packages/cli/src/lib/new/defaultTemplates.ts b/packages/cli/src/lib/new/defaultTemplates.ts index 7784e046bd..1a2f7a89d4 100644 --- a/packages/cli/src/lib/new/defaultTemplates.ts +++ b/packages/cli/src/lib/new/defaultTemplates.ts @@ -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', ]; diff --git a/packages/cli/src/lib/new/preparation/loadPortableTemplateConfig.ts b/packages/cli/src/lib/new/preparation/loadPortableTemplateConfig.ts index 7f6a7636b9..a84956e121 100644 --- a/packages/cli/src/lib/new/preparation/loadPortableTemplateConfig.ts +++ b/packages/cli/src/lib/new/preparation/loadPortableTemplateConfig.ts @@ -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 { + 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, + }; +} diff --git a/packages/cli/src/lib/new/types.ts b/packages/cli/src/lib/new/types.ts index b6dc31569c..1b76d62474 100644 --- a/packages/cli/src/lib/new/types.ts +++ b/packages/cli/src/lib/new/types.ts @@ -39,7 +39,7 @@ export type PortableTemplateConfig = { }; export type PortableTemplatePointer = { - id: string; + name: string; description?: string; target: string; };