cli/new: hardcode template file name and always point to dir
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -64,14 +64,14 @@ Custom templates can be installed from local directories. To install a template
|
||||
"backstage": {
|
||||
"cli": {
|
||||
"new": {
|
||||
"templates": ["./templates/custom-plugin.yaml"]
|
||||
"templates": ["./templates/custom-plugin"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Each entry in the `templates` array should be relative path that points either directly to a template YAML file or a directory containing a `template.yaml` file. If the path starts with `./` it will be use as is, otherwise it will be resolved as a module within `node_modules`.
|
||||
Each entry in the `templates` array should be relative path that points to a directory containing a `template.yaml` file. If the path starts with `./` it will be use as is, otherwise it will be resolved as a module within `node_modules`.
|
||||
|
||||
When defining the `templates` array it will override the default set of templates. If you want to keep using and of the build-in templates in the Backstage CLI you can reference them directly within the CLI package. This following is the full list of built-in templates:
|
||||
|
||||
@@ -100,9 +100,11 @@ When defining the `templates` array it will override the default set of template
|
||||
|
||||
## Creating your own CLI templates
|
||||
|
||||
Your first step in creating your own CLI template is composing your yaml file:
|
||||
Each template lives in its own directory and must have a `template.yaml` file that describes the template. The template directory can also contain any files that should be templated or copied to the generated package.
|
||||
|
||||
```yaml title="in templates/custom-plugin.yaml"
|
||||
Start by creating `template.yaml` in a new directory somewhere in your project, in this example we're using `./templates/custom-plugin/template.yaml`:
|
||||
|
||||
```yaml title="in templates/custom-plugin/template.yaml"
|
||||
name: custom-plugin
|
||||
role: frontend-plugin
|
||||
description: Description of my CLI template # optional
|
||||
@@ -117,25 +119,18 @@ The following properties are supported:
|
||||
- `description` - A description of the type of package that this template produces.
|
||||
- `values` - A map of additional values that will be present during templating. The values are themselves templated and can reference other values. If the key matches any of the user prompts, such as `pluginId`, the value will be used directly instead of prompting the user.
|
||||
|
||||
Once you have your composed template yaml file, [add your new template](#installing-custom-templates) to the CLI config in your root `package.json`:
|
||||
Next, add any other files you want to be part of the template to the same directory. All files will be copied as is, except any files with a `.hbs` extension. They will be treated as [Handlebars](https://handlebarsjs.com/) templates and will be rendered with the values from the `template.yaml` file as well as additional prompts such as `pluginId`. For example, you could create a `src/index.ts` file with the following content:
|
||||
|
||||
```diff
|
||||
{
|
||||
// ...
|
||||
"backstage": {
|
||||
"cli": {
|
||||
"new": {
|
||||
+ "templates": [
|
||||
+ "./templates/custom-plugin.yaml"
|
||||
+ ]
|
||||
}
|
||||
}
|
||||
}
|
||||
```typescript title="in templates/custom-plugin/src/index.ts.hbs"
|
||||
export function getPluginId() {
|
||||
return '{{ pluginId }}';
|
||||
}
|
||||
```
|
||||
|
||||
If you'd like to see more examples, you can find all the default templates and its yaml files [here](https://github.com/backstage/backstage/tree/master/packages/cli/templates).
|
||||
|
||||
Once your template is ready, [add it to your config](#installing-custom-templates), and you should now be able to select it when running `yarn new`.
|
||||
|
||||
### Template Roles
|
||||
|
||||
The `role` property in the template yaml file is used to determine what input will be gathered for the template, as well as what actions will be taken after the new package has been created. The following roles are supported:
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import { createMockDirectory } from '@backstage/backend-test-utils';
|
||||
import { loadPortableTemplate } from './loadPortableTemplate';
|
||||
import { TEMPLATE_FILE_NAME } from '../types';
|
||||
|
||||
describe('loadTemplate', () => {
|
||||
const mockDir = createMockDirectory();
|
||||
@@ -26,19 +27,21 @@ describe('loadTemplate', () => {
|
||||
|
||||
it('should load a valid template', async () => {
|
||||
mockDir.setContent({
|
||||
'path/to/template.yaml': `
|
||||
'path/to': {
|
||||
[TEMPLATE_FILE_NAME]: `
|
||||
name: template1
|
||||
role: frontend-plugin
|
||||
values:
|
||||
foo: bar
|
||||
`,
|
||||
},
|
||||
'path/to/hello.txt': 'hello world',
|
||||
});
|
||||
|
||||
await expect(
|
||||
loadPortableTemplate({
|
||||
name: 'template1',
|
||||
target: mockDir.resolve('path/to/template.yaml'),
|
||||
target: mockDir.resolve('path/to', TEMPLATE_FILE_NAME),
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
name: 'template1',
|
||||
|
||||
@@ -18,6 +18,7 @@ import { realpathSync } from 'node:fs';
|
||||
import { loadPortableTemplateConfig } from './loadPortableTemplateConfig';
|
||||
import { defaultTemplates } from '../defaultTemplates';
|
||||
import { createMockDirectory } from '@backstage/backend-test-utils';
|
||||
import { TEMPLATE_FILE_NAME } from '../types';
|
||||
|
||||
describe('loadPortableTemplateConfig', () => {
|
||||
const mockDir = createMockDirectory();
|
||||
@@ -32,7 +33,7 @@ describe('loadPortableTemplateConfig', () => {
|
||||
backstage: {
|
||||
cli: {
|
||||
new: {
|
||||
templates: ['./path/to/template1', './path/to/template2.yaml'],
|
||||
templates: ['./path/to/template1'],
|
||||
globals: {
|
||||
license: 'MIT',
|
||||
private: true,
|
||||
@@ -43,9 +44,9 @@ describe('loadPortableTemplateConfig', () => {
|
||||
},
|
||||
},
|
||||
}),
|
||||
'path/to/template1/template.yaml':
|
||||
'name: template1\nrole: frontend-plugin\n',
|
||||
'path/to/template2.yaml': 'name: template2\nrole: frontend-plugin\n',
|
||||
'path/to/template1': {
|
||||
[TEMPLATE_FILE_NAME]: 'name: template1\nrole: frontend-plugin\n',
|
||||
},
|
||||
});
|
||||
|
||||
await expect(
|
||||
@@ -57,11 +58,7 @@ describe('loadPortableTemplateConfig', () => {
|
||||
templatePointers: [
|
||||
{
|
||||
name: 'template1',
|
||||
target: mockDir.resolve('path/to/template1/template.yaml'),
|
||||
},
|
||||
{
|
||||
name: 'template2',
|
||||
target: mockDir.resolve('path/to/template2.yaml'),
|
||||
target: mockDir.resolve('path/to/template1', TEMPLATE_FILE_NAME),
|
||||
},
|
||||
],
|
||||
license: 'MIT',
|
||||
@@ -84,11 +81,7 @@ describe('loadPortableTemplateConfig', () => {
|
||||
templatePointers: [
|
||||
{
|
||||
name: 'template1',
|
||||
target: mockDir.resolve('path/to/template1/template.yaml'),
|
||||
},
|
||||
{
|
||||
name: 'template2',
|
||||
target: mockDir.resolve('path/to/template2.yaml'),
|
||||
target: mockDir.resolve('path/to/template1', TEMPLATE_FILE_NAME),
|
||||
},
|
||||
],
|
||||
license: 'nope',
|
||||
@@ -121,14 +114,14 @@ describe('loadPortableTemplateConfig', () => {
|
||||
package: {
|
||||
templates: {
|
||||
plugin: {
|
||||
'template.yaml':
|
||||
[TEMPLATE_FILE_NAME]:
|
||||
'name: frontend-plugin\nrole: frontend-plugin\n',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'my-package': {
|
||||
'template.yaml': 'name: backend-plugin\nrole: backend-plugin\n',
|
||||
[TEMPLATE_FILE_NAME]: 'name: backend-plugin\nrole: backend-plugin\n',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -144,14 +137,15 @@ describe('loadPortableTemplateConfig', () => {
|
||||
name: 'frontend-plugin',
|
||||
target: realpathSync(
|
||||
mockDir.resolve(
|
||||
'node_modules/@my/package/templates/plugin/template.yaml',
|
||||
'node_modules/@my/package/templates/plugin',
|
||||
TEMPLATE_FILE_NAME,
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'backend-plugin',
|
||||
target: realpathSync(
|
||||
mockDir.resolve('node_modules/my-package/template.yaml'),
|
||||
mockDir.resolve('node_modules/my-package', TEMPLATE_FILE_NAME),
|
||||
),
|
||||
},
|
||||
],
|
||||
@@ -180,7 +174,7 @@ describe('loadPortableTemplateConfig', () => {
|
||||
node_modules: Object.fromEntries(
|
||||
defaultTemplates.map(t => [
|
||||
t,
|
||||
{ 'template.yaml': `name: x\nrole: web-library\n` },
|
||||
{ [TEMPLATE_FILE_NAME]: `name: x\nrole: web-library\n` },
|
||||
]),
|
||||
),
|
||||
});
|
||||
@@ -194,7 +188,7 @@ describe('loadPortableTemplateConfig', () => {
|
||||
templatePointers: defaultTemplates.map(t => ({
|
||||
name: 'x',
|
||||
target: realpathSync(
|
||||
mockDir.resolve(`node_modules/${t}/template.yaml`),
|
||||
mockDir.resolve(`node_modules/${t}`, TEMPLATE_FILE_NAME),
|
||||
),
|
||||
})),
|
||||
license: 'MIT',
|
||||
@@ -255,7 +249,7 @@ describe('loadPortableTemplateConfig', () => {
|
||||
node_modules: Object.fromEntries(
|
||||
defaultTemplates.map(t => [
|
||||
t,
|
||||
{ 'template.yaml': `name: x\nrole: web-library\n` },
|
||||
{ [TEMPLATE_FILE_NAME]: `name: x\nrole: web-library\n` },
|
||||
]),
|
||||
),
|
||||
});
|
||||
|
||||
@@ -15,10 +15,14 @@
|
||||
*/
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import { resolve as resolvePath, dirname, extname } from 'node:path';
|
||||
import { resolve as resolvePath, dirname } from 'node:path';
|
||||
import { paths } from '../../paths';
|
||||
import { defaultTemplates } from '../defaultTemplates';
|
||||
import { PortableTemplateConfig, PortableTemplatePointer } from '../types';
|
||||
import {
|
||||
PortableTemplateConfig,
|
||||
PortableTemplatePointer,
|
||||
TEMPLATE_FILE_NAME,
|
||||
} from '../types';
|
||||
import { parse as parseYaml } from 'yaml';
|
||||
import { z } from 'zod';
|
||||
import { fromZodError } from 'zod-validation-error';
|
||||
@@ -122,19 +126,13 @@ export async function loadPortableTemplateConfig(
|
||||
}
|
||||
|
||||
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);
|
||||
return resolvePath(basePath, pointer, TEMPLATE_FILE_NAME);
|
||||
}
|
||||
|
||||
if (isDirectoryPointer) {
|
||||
return require.resolve(`${pointer}/template.yaml`, { paths: [basePath] });
|
||||
}
|
||||
return require.resolve(pointer, { paths: [basePath] });
|
||||
return require.resolve(`${pointer}/${TEMPLATE_FILE_NAME}`, {
|
||||
paths: [basePath],
|
||||
});
|
||||
}
|
||||
|
||||
const partialTemplateDefinitionSchema = z.object({
|
||||
|
||||
@@ -38,6 +38,8 @@ export type PortableTemplateConfig = {
|
||||
packageNamePluginInfix: string;
|
||||
};
|
||||
|
||||
export const TEMPLATE_FILE_NAME = 'template.yaml';
|
||||
|
||||
export type PortableTemplatePointer = {
|
||||
name: string;
|
||||
description?: string;
|
||||
|
||||
Reference in New Issue
Block a user