diff --git a/packages/cli/src/lib/new/preparation/loadPortableTemplate.test.ts b/packages/cli/src/lib/new/preparation/loadPortableTemplate.test.ts index 74892ebceb..89a8e0c6aa 100644 --- a/packages/cli/src/lib/new/preparation/loadPortableTemplate.test.ts +++ b/packages/cli/src/lib/new/preparation/loadPortableTemplate.test.ts @@ -18,26 +18,56 @@ import { createMockDirectory } from '@backstage/backend-test-utils'; import { loadPortableTemplate } from './loadPortableTemplate'; describe('loadTemplate', () => { + const mockDir = createMockDirectory(); + + afterEach(() => { + mockDir.clear(); + }); + it('should load a valid template', async () => { - const mockDir = createMockDirectory({ - content: { - 'path/to/template1.yaml': ` - template: template1 + mockDir.setContent({ + 'path/to/template.yaml': ` + name: template1 role: frontend-plugin parameters: foo: bar `, - 'path/to/template1/hello.txt': 'hello world', - }, + 'path/to/hello.txt': 'hello world', }); - const result = await loadPortableTemplate({ - id: 'template1', - target: mockDir.resolve('path/to/template1.yaml'), + await expect( + loadPortableTemplate({ + name: 'template1', + target: mockDir.resolve('path/to/template.yaml'), + }), + ).resolves.toEqual({ + name: 'template1', + role: 'frontend-plugin', + files: [{ path: 'hello.txt', content: 'hello world' }], + parameters: { foo: 'bar' }, + templateValues: {}, + }); + }); + + it('should load a valid template with files in a separate dir', async () => { + mockDir.setContent({ + 'path/to/template.yaml': ` + name: template1 + role: frontend-plugin + files: content + parameters: + foo: bar + `, + 'path/to/content/hello.txt': 'hello world', }); - expect(result).toEqual({ - id: 'template1', + await expect( + loadPortableTemplate({ + name: 'template1', + target: mockDir.resolve('path/to/template.yaml'), + }), + ).resolves.toEqual({ + name: 'template1', role: 'frontend-plugin', files: [{ path: 'hello.txt', content: 'hello world' }], parameters: { foo: 'bar' }, @@ -46,11 +76,11 @@ describe('loadTemplate', () => { }); it('should throw an error if template file does not exist', async () => { - const mockDir = createMockDirectory(); + mockDir.setContent({}); await expect( loadPortableTemplate({ - id: 'template1', + name: 'template1', target: mockDir.resolve('path/to/template1.yaml'), }), ).rejects.toThrow( @@ -59,15 +89,13 @@ describe('loadTemplate', () => { }); it('should throw an error if template definition is invalid', async () => { - const mockDir = createMockDirectory({ - content: { - 'path/to/template1.yaml': `invalid: definition`, - }, + mockDir.setContent({ + 'path/to/template1.yaml': `invalid: definition`, }); await expect( loadPortableTemplate({ - id: 'template1', + name: 'template1', target: mockDir.resolve('path/to/template1.yaml'), }), ).rejects.toThrow( @@ -78,34 +106,24 @@ describe('loadTemplate', () => { it('should throw an error if target is a remote URL', async () => { await expect( loadPortableTemplate({ - id: 'template1', - target: 'http://example.com', - }), - ).rejects.toThrow('Remote templates are not supported yet'); - }); - - it('should throw an error if target directory does not exist', async () => { - await expect( - loadPortableTemplate({ - id: 'template1', + name: 'template1', target: 'http://example.com', }), ).rejects.toThrow('Remote templates are not supported yet'); }); it('should throw an error if the package role is invalid', async () => { - const mockDir = createMockDirectory({ - content: { - 'path/to/template1.yaml': ` - template: template1 - role: invalid-role - `, - }, + mockDir.setContent({ + 'path/to/template1.yaml': ` + name: x + role: invalid-role + files: template1 + `, }); await expect( loadPortableTemplate({ - id: 'template1', + name: 'template1', target: mockDir.resolve('path/to/template1.yaml'), }), ).rejects.toThrow( @@ -116,18 +134,17 @@ describe('loadTemplate', () => { }); it('should throw an error if template directory does not exist', async () => { - const mockDir = createMockDirectory({ - content: { - 'path/to/template1.yaml': ` - template: template1 - role: frontend-plugin - `, - }, + mockDir.setContent({ + 'path/to/template1.yaml': ` + name: x + role: frontend-plugin + files: template1 + `, }); await expect( loadPortableTemplate({ - id: 'template1', + name: 'template1', target: mockDir.resolve('path/to/template1.yaml'), }), ).rejects.toThrow( diff --git a/packages/cli/src/lib/new/preparation/loadPortableTemplate.ts b/packages/cli/src/lib/new/preparation/loadPortableTemplate.ts index 6d2c2ec624..60246351e6 100644 --- a/packages/cli/src/lib/new/preparation/loadPortableTemplate.ts +++ b/packages/cli/src/lib/new/preparation/loadPortableTemplate.ts @@ -32,8 +32,10 @@ import { fromZodError } from 'zod-validation-error'; const templateDefinitionSchema = z .object({ + name: z.string(), role: z.enum(TEMPLATE_ROLES), - template: z.string(), + description: z.string().optional(), + files: z.string().optional(), parameters: z.record(z.string()).optional(), templateValues: z.record(z.string()).optional(), }) @@ -63,9 +65,14 @@ export async function loadPortableTemplate( ); } - const { role, template, parameters = {}, templateValues = {} } = parsed.data; + const { + role, + files = '.', + parameters = {}, + templateValues = {}, + } = parsed.data; - const templatePath = resolvePath(dirname(pointer.target), template); + const templatePath = resolvePath(dirname(pointer.target), files); const filePaths = await recursiveReaddir(templatePath).catch(error => { throw new ForwardedError( `Failed to load template contents from '${templatePath}'`, @@ -73,10 +80,13 @@ export async function loadPortableTemplate( ); }); - const files = new Array(); + const loadedFiles = new Array(); for (const filePath of filePaths) { const path = relativePath(templatePath, filePath); + if (filePath === pointer.target) { + continue; + } const content = await fs.readFile(filePath, 'utf-8').catch(error => { throw new ForwardedError( @@ -86,11 +96,21 @@ export async function loadPortableTemplate( }); if (path.endsWith('.hbs')) { - files.push({ path: path.slice(0, -4), content, syntax: 'handlebars' }); + loadedFiles.push({ + path: path.slice(0, -4), + content, + syntax: 'handlebars', + }); } else { - files.push({ path, content }); + loadedFiles.push({ path, content }); } } - return { id: pointer.id, role, files, parameters, templateValues }; + return { + name: pointer.name, + role, + files: loadedFiles, + parameters, + templateValues, + }; } diff --git a/packages/cli/src/lib/new/preparation/selectTemplateInteractively.ts b/packages/cli/src/lib/new/preparation/selectTemplateInteractively.ts index 1b93743116..808efffc0a 100644 --- a/packages/cli/src/lib/new/preparation/selectTemplateInteractively.ts +++ b/packages/cli/src/lib/new/preparation/selectTemplateInteractively.ts @@ -38,15 +38,15 @@ export async function selectTemplateInteractively( message: 'What do you want to create?', choices: config.templatePointers.map(t => t.description - ? { name: `${t.id} - ${t.description}`, value: t.id } - : t.id, + ? { name: `${t.name} - ${t.description}`, value: t.name } + : t.name, ), }, ]); selectedId = answers.id; } - const template = config.templatePointers.find(t => t.id === selectedId); + const template = config.templatePointers.find(t => t.name === selectedId); if (!template) { throw new Error(`Template '${selectedId}' not found`); } diff --git a/packages/cli/src/lib/new/types.ts b/packages/cli/src/lib/new/types.ts index 289cd58262..009ab2a81b 100644 --- a/packages/cli/src/lib/new/types.ts +++ b/packages/cli/src/lib/new/types.ts @@ -66,7 +66,7 @@ export type PortableTemplateFile = { }; export type PortableTemplate = { - id: string; + name: string; role: PortableTemplateRole; files: PortableTemplateFile[]; parameters: Record;