cli/new: update config loading tests + fixes
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -58,7 +58,7 @@ export async function executePortableTemplate(
|
||||
});
|
||||
|
||||
Task.log();
|
||||
Task.log(`🎉 Successfully created ${template.id}`);
|
||||
Task.log(`🎉 Successfully created ${template.name}`);
|
||||
Task.log();
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
@@ -74,7 +74,7 @@ export async function executePortableTemplate(
|
||||
'continue manually, but you can also revert the changes and try again.',
|
||||
);
|
||||
|
||||
Task.error(`🔥 Failed to create ${template.id}!`);
|
||||
Task.error(`🔥 Failed to create ${template.name}!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ describe('writeTemplateContents', () => {
|
||||
it('should write an empty template', async () => {
|
||||
const { targetDir } = await writeTemplateContents(
|
||||
{
|
||||
id: 'test',
|
||||
name: 'test',
|
||||
files: [],
|
||||
role: 'frontend-plugin',
|
||||
parameters: {},
|
||||
@@ -60,7 +60,7 @@ describe('writeTemplateContents', () => {
|
||||
it('should write template with various files', async () => {
|
||||
await writeTemplateContents(
|
||||
{
|
||||
id: 'test',
|
||||
name: 'test',
|
||||
files: [
|
||||
{
|
||||
content: 'test',
|
||||
|
||||
@@ -30,8 +30,7 @@ describe('collectTemplateParams', () => {
|
||||
packageNamePluginInfix: 'plugin-',
|
||||
} satisfies PortableTemplateConfig,
|
||||
template: {
|
||||
id: 'test',
|
||||
templatePath: '/test',
|
||||
name: 'test',
|
||||
role: 'frontend-plugin' as const,
|
||||
files: [],
|
||||
parameters: {},
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { realpathSync } from 'node:fs';
|
||||
import { loadPortableTemplateConfig } from './loadPortableTemplateConfig';
|
||||
import { defaultTemplates } from '../defaultTemplates';
|
||||
import { createMockDirectory } from '@backstage/backend-test-utils';
|
||||
@@ -31,10 +32,7 @@ describe('loadPortableTemplateConfig', () => {
|
||||
backstage: {
|
||||
cli: {
|
||||
new: {
|
||||
templates: [
|
||||
{ id: 'template1', target: 'path/to/template1' },
|
||||
{ id: 'template2', target: 'path/to/template2' },
|
||||
],
|
||||
templates: ['./path/to/template1', './path/to/template2.yaml'],
|
||||
globals: {
|
||||
license: 'MIT',
|
||||
private: true,
|
||||
@@ -45,6 +43,9 @@ describe('loadPortableTemplateConfig', () => {
|
||||
},
|
||||
},
|
||||
}),
|
||||
'path/to/template1/template.yaml':
|
||||
'name: template1\nrole: frontend-plugin\n',
|
||||
'path/to/template2.yaml': 'name: template2\nrole: frontend-plugin\n',
|
||||
});
|
||||
|
||||
await expect(
|
||||
@@ -54,8 +55,14 @@ describe('loadPortableTemplateConfig', () => {
|
||||
).resolves.toEqual({
|
||||
isUsingDefaultTemplates: false,
|
||||
templatePointers: [
|
||||
{ id: 'template1', target: 'path/to/template1' },
|
||||
{ id: 'template2', target: 'path/to/template2' },
|
||||
{
|
||||
name: 'template1',
|
||||
target: mockDir.resolve('path/to/template1/template.yaml'),
|
||||
},
|
||||
{
|
||||
name: 'template2',
|
||||
target: mockDir.resolve('path/to/template2.yaml'),
|
||||
},
|
||||
],
|
||||
license: 'MIT',
|
||||
private: true,
|
||||
@@ -75,8 +82,14 @@ describe('loadPortableTemplateConfig', () => {
|
||||
).resolves.toEqual({
|
||||
isUsingDefaultTemplates: false,
|
||||
templatePointers: [
|
||||
{ id: 'template1', target: 'path/to/template1' },
|
||||
{ id: 'template2', target: 'path/to/template2' },
|
||||
{
|
||||
name: 'template1',
|
||||
target: mockDir.resolve('path/to/template1/template.yaml'),
|
||||
},
|
||||
{
|
||||
name: 'template2',
|
||||
target: mockDir.resolve('path/to/template2.yaml'),
|
||||
},
|
||||
],
|
||||
license: 'nope',
|
||||
version: '0.1.0',
|
||||
@@ -92,11 +105,7 @@ describe('loadPortableTemplateConfig', () => {
|
||||
backstage: {
|
||||
cli: {
|
||||
new: {
|
||||
templates: [
|
||||
'default-backend-plugin',
|
||||
{ id: 'template1', target: 'path/to/template1' },
|
||||
'default-frontend-plugin',
|
||||
],
|
||||
templates: ['@my/package/templates/plugin', 'my-package'],
|
||||
globals: {
|
||||
license: 'MIT',
|
||||
private: true,
|
||||
@@ -107,6 +116,21 @@ describe('loadPortableTemplateConfig', () => {
|
||||
},
|
||||
},
|
||||
}),
|
||||
node_modules: {
|
||||
'@my': {
|
||||
package: {
|
||||
templates: {
|
||||
plugin: {
|
||||
'template.yaml':
|
||||
'name: frontend-plugin\nrole: frontend-plugin\n',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'my-package': {
|
||||
'template.yaml': 'name: backend-plugin\nrole: backend-plugin\n',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await expect(
|
||||
@@ -117,15 +141,18 @@ describe('loadPortableTemplateConfig', () => {
|
||||
isUsingDefaultTemplates: false,
|
||||
templatePointers: [
|
||||
{
|
||||
id: 'backend-plugin',
|
||||
description: 'A new backend plugin',
|
||||
target: expect.stringMatching(/default-backend-plugin.yaml$/),
|
||||
name: 'frontend-plugin',
|
||||
target: realpathSync(
|
||||
mockDir.resolve(
|
||||
'node_modules/@my/package/templates/plugin/template.yaml',
|
||||
),
|
||||
),
|
||||
},
|
||||
{ id: 'template1', target: 'path/to/template1' },
|
||||
{
|
||||
id: 'frontend-plugin',
|
||||
description: 'A new frontend plugin',
|
||||
target: expect.stringMatching(/default-plugin.yaml$/),
|
||||
name: 'backend-plugin',
|
||||
target: realpathSync(
|
||||
mockDir.resolve('node_modules/my-package/template.yaml'),
|
||||
),
|
||||
},
|
||||
],
|
||||
license: 'MIT',
|
||||
@@ -150,6 +177,12 @@ describe('loadPortableTemplateConfig', () => {
|
||||
},
|
||||
},
|
||||
}),
|
||||
node_modules: Object.fromEntries(
|
||||
defaultTemplates.map(t => [
|
||||
t,
|
||||
{ 'template.yaml': `name: x\nrole: web-library\n` },
|
||||
]),
|
||||
),
|
||||
});
|
||||
|
||||
await expect(
|
||||
@@ -158,7 +191,12 @@ describe('loadPortableTemplateConfig', () => {
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
isUsingDefaultTemplates: true,
|
||||
templatePointers: defaultTemplates,
|
||||
templatePointers: defaultTemplates.map(t => ({
|
||||
name: 'x',
|
||||
target: realpathSync(
|
||||
mockDir.resolve(`node_modules/${t}/template.yaml`),
|
||||
),
|
||||
})),
|
||||
license: 'MIT',
|
||||
private: true,
|
||||
version: '0.1.0',
|
||||
@@ -195,7 +233,7 @@ describe('loadPortableTemplateConfig', () => {
|
||||
backstage: {
|
||||
cli: {
|
||||
new: {
|
||||
templates: ['invalid'],
|
||||
templates: ['./invalid'],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -207,13 +245,19 @@ describe('loadPortableTemplateConfig', () => {
|
||||
packagePath: mockDir.resolve('package.json'),
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
/^Failed to load templating configuration from '.*'; caused by Validation error: Invalid enum value/,
|
||||
`Failed to load template definition '.\/invalid'; caused by Error: ENOENT`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle missing backstage.new configuration', async () => {
|
||||
mockDir.setContent({
|
||||
'package.json': JSON.stringify({}),
|
||||
node_modules: Object.fromEntries(
|
||||
defaultTemplates.map(t => [
|
||||
t,
|
||||
{ 'template.yaml': `name: x\nrole: web-library\n` },
|
||||
]),
|
||||
),
|
||||
});
|
||||
|
||||
await expect(
|
||||
@@ -222,7 +266,7 @@ describe('loadPortableTemplateConfig', () => {
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
isUsingDefaultTemplates: true,
|
||||
templatePointers: defaultTemplates,
|
||||
templatePointers: expect.any(Array),
|
||||
license: 'Apache-2.0',
|
||||
version: '0.1.0',
|
||||
private: true,
|
||||
@@ -239,7 +283,7 @@ describe('loadPortableTemplateConfig', () => {
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
isUsingDefaultTemplates: true,
|
||||
templatePointers: defaultTemplates,
|
||||
templatePointers: expect.any(Array),
|
||||
license: 'nope',
|
||||
version: '0.1.0',
|
||||
private: true,
|
||||
|
||||
@@ -86,11 +86,11 @@ export async function loadPortableTemplateConfig(
|
||||
|
||||
const basePath = dirname(pkgPath);
|
||||
const templatePointers = await Promise.all(
|
||||
(config?.templates ?? defaultTemplates).map(pointer => {
|
||||
(config?.templates ?? defaultTemplates).map(async pointer => {
|
||||
try {
|
||||
const templatePath = resolveLocalTemplatePath(pointer, basePath);
|
||||
|
||||
return peekLocalTemplateDefinition(templatePath);
|
||||
return await peekLocalTemplateDefinition(templatePath);
|
||||
} catch (error) {
|
||||
throw new ForwardedError(
|
||||
`Failed to load template definition '${pointer}'`,
|
||||
|
||||
@@ -23,8 +23,8 @@ describe('selectTemplateInteractively', () => {
|
||||
const mockConfig = {
|
||||
isUsingDefaultTemplates: false,
|
||||
templatePointers: [
|
||||
{ id: 'template1', target: 'path/to/template1' },
|
||||
{ id: 'template2', target: 'path/to/template2' },
|
||||
{ name: 'template1', target: '/path/to/template1' },
|
||||
{ name: 'template2', target: '/path/to/template2' },
|
||||
],
|
||||
} as PortableTemplateConfig;
|
||||
|
||||
@@ -33,25 +33,27 @@ describe('selectTemplateInteractively', () => {
|
||||
});
|
||||
|
||||
it('should select a template interactively', async () => {
|
||||
jest.spyOn(inquirer, 'prompt').mockResolvedValueOnce({ id: 'template1' });
|
||||
jest.spyOn(inquirer, 'prompt').mockResolvedValueOnce({ name: 'template1' });
|
||||
|
||||
const result = await selectTemplateInteractively(mockConfig);
|
||||
|
||||
expect(result).toEqual({ id: 'template1', target: 'path/to/template1' });
|
||||
expect(result).toEqual({ name: 'template1', target: '/path/to/template1' });
|
||||
});
|
||||
|
||||
it('should error if interactive selections is not found', async () => {
|
||||
jest.spyOn(inquirer, 'prompt').mockResolvedValueOnce({ id: 'nonexistent' });
|
||||
jest
|
||||
.spyOn(inquirer, 'prompt')
|
||||
.mockResolvedValueOnce({ name: 'nonexistent' });
|
||||
|
||||
await expect(selectTemplateInteractively(mockConfig)).rejects.toThrow(
|
||||
"Template 'nonexistent' not found",
|
||||
);
|
||||
});
|
||||
|
||||
it('should use preselected template id', async () => {
|
||||
it('should use preselected template name', async () => {
|
||||
const result = await selectTemplateInteractively(mockConfig, 'template2');
|
||||
|
||||
expect(result).toEqual({ id: 'template2', target: 'path/to/template2' });
|
||||
expect(result).toEqual({ name: 'template2', target: '/path/to/template2' });
|
||||
});
|
||||
|
||||
it('should throw an error if template is not found', async () => {
|
||||
|
||||
@@ -19,22 +19,22 @@ import { PortableTemplateConfig, PortableTemplatePointer } from '../types';
|
||||
|
||||
export async function selectTemplateInteractively(
|
||||
config: PortableTemplateConfig,
|
||||
preselectedTemplateId?: string,
|
||||
preselectedTemplateName?: string,
|
||||
): Promise<PortableTemplatePointer> {
|
||||
let selectedId = preselectedTemplateId;
|
||||
let selectedName = preselectedTemplateName;
|
||||
|
||||
if (config.isUsingDefaultTemplates && selectedId === 'plugin') {
|
||||
if (config.isUsingDefaultTemplates && selectedName === 'plugin') {
|
||||
console.warn(
|
||||
`DEPRECATION WARNING: The 'plugin' template is deprecated, use 'frontend-plugin' instead`,
|
||||
);
|
||||
selectedId = 'frontend-plugin';
|
||||
selectedName = 'frontend-plugin';
|
||||
}
|
||||
|
||||
if (!selectedId) {
|
||||
const answers = await inquirer.prompt<{ id: string }>([
|
||||
if (!selectedName) {
|
||||
const answers = await inquirer.prompt<{ name: string }>([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'id',
|
||||
name: 'name',
|
||||
message: 'What do you want to create?',
|
||||
choices: config.templatePointers.map(t =>
|
||||
t.description
|
||||
@@ -43,12 +43,12 @@ export async function selectTemplateInteractively(
|
||||
),
|
||||
},
|
||||
]);
|
||||
selectedId = answers.id;
|
||||
selectedName = answers.name;
|
||||
}
|
||||
|
||||
const template = config.templatePointers.find(t => t.name === selectedId);
|
||||
const template = config.templatePointers.find(t => t.name === selectedName);
|
||||
if (!template) {
|
||||
throw new Error(`Template '${selectedId}' not found`);
|
||||
throw new Error(`Template '${selectedName}' not found`);
|
||||
}
|
||||
return template;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user