add enhanced/documented filter/global types and creator functions
Signed-off-by: Matt Benson <gudnabrsam@gmail.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': minor
|
||||
'@backstage/plugin-scaffolder-node': minor
|
||||
---
|
||||
|
||||
New api functions createTemplateFilter, createTemplateGlobalFunction, createTemplateGlobalValue;
|
||||
Core support for template extension documentation
|
||||
@@ -328,6 +328,7 @@ preconfigured
|
||||
prepack
|
||||
Preprarer
|
||||
productional
|
||||
projectSlug
|
||||
Protobuf
|
||||
proxied
|
||||
proxying
|
||||
@@ -425,6 +426,7 @@ subheaders
|
||||
subkey
|
||||
subpath
|
||||
subroutes
|
||||
substring
|
||||
subtree
|
||||
superfences
|
||||
Superfences
|
||||
|
||||
@@ -0,0 +1,502 @@
|
||||
---
|
||||
id: template-extensions
|
||||
title: Template Extensions
|
||||
description: Template extensions system
|
||||
---
|
||||
|
||||
Backstage templating is powered by [Nunjucks][]. The basics:
|
||||
|
||||
# Template Filters
|
||||
|
||||
The [filter][] is a critical mechanism for the rendering of Nunjucks templates,
|
||||
providing a means of transforming values in a familiar [piped][] fashion.
|
||||
Template filters are functions that help you transform data, extract specific
|
||||
information, and perform various operations in Scaffolder Templates.
|
||||
|
||||
## Built-in
|
||||
|
||||
Backstage provides out of the box the following set of "built-in" template
|
||||
filters (to create your own custom filters, look to the section [Custom Filter](#custom-filter) hereafter):
|
||||
|
||||
### parseRepoUrl
|
||||
|
||||
The `parseRepoUrl` filter parses a repository URL into its constituent parts:
|
||||
`owner`, repository name (`repo`), etc.
|
||||
|
||||
**Usage Example:**
|
||||
|
||||
```yaml
|
||||
- id: log
|
||||
name: Parse Repo URL
|
||||
action: debug:log
|
||||
input:
|
||||
message: ${{ parameters.repoUrl | parseRepoUrl }}
|
||||
```
|
||||
|
||||
- **Input**: `github.com?repo=backstage&owner=backstage`
|
||||
- **Output**: "RepoSpec" (see [parseRepoUrl][])
|
||||
|
||||
### parseEntityRef
|
||||
|
||||
The `parseEntityRef` filter allows you to extract different parts of
|
||||
an entity reference, such as the `kind`, `namespace`, and `name`.
|
||||
|
||||
**Usage example**
|
||||
|
||||
1. Without context
|
||||
|
||||
```yaml
|
||||
- id: log
|
||||
name: Parse Entity Reference
|
||||
action: debug:log
|
||||
input:
|
||||
message: ${{ parameters.owner | parseEntityRef }}
|
||||
```
|
||||
|
||||
- **Input**: `group:techdocs`
|
||||
- **Output**: [CompoundEntityRef][]
|
||||
|
||||
1. With context
|
||||
|
||||
```yaml
|
||||
- id: log
|
||||
name: Parse Entity Reference
|
||||
action: debug:log
|
||||
input:
|
||||
message: ${{ parameters.owner | parseEntityRef({ defaultKind:"group", defaultNamespace:"another-namespace" }) }}
|
||||
```
|
||||
|
||||
- **Input**: `techdocs`
|
||||
- **Output**: [CompoundEntityRef][]
|
||||
|
||||
### pick
|
||||
|
||||
The `pick` filter allows you to select a specific property (e.g. `kind`, `namespace`, `name`) from an object.
|
||||
|
||||
**Usage Example**
|
||||
|
||||
```yaml
|
||||
- id: log
|
||||
name: Pick
|
||||
action: debug:log
|
||||
input:
|
||||
message: ${{ parameters.owner | parseEntityRef | pick('name') }}
|
||||
```
|
||||
|
||||
- **Input**: `{ kind: 'Group', namespace: 'default', name: 'techdocs' }`
|
||||
- **Output**: `techdocs`
|
||||
|
||||
### projectSlug
|
||||
|
||||
The `projectSlug` filter generates a project slug from a repository URL.
|
||||
|
||||
**Usage Example**
|
||||
|
||||
```yaml
|
||||
- id: log
|
||||
name: Project Slug
|
||||
action: debug:log
|
||||
input:
|
||||
message: ${{ parameters.repoUrl | projectSlug }}
|
||||
```
|
||||
|
||||
- **Input**: `github.com?repo=backstage&owner=backstage`
|
||||
- **Output**: `backstage/backstage`
|
||||
|
||||
# Template Globals
|
||||
|
||||
In addition to its powerful filtering functionality, the Nunjucks engine allows
|
||||
access from the template expression context to specified globally-accessible
|
||||
references. Backstage propagates this capability via the scaffolder backend
|
||||
plugin, which we shall soon see in action.
|
||||
|
||||
# Customizing the templating environment
|
||||
|
||||
Custom plugins make it possible to install your own template extensions, which
|
||||
may be any combination of filters, global functions and global values. With the
|
||||
new backend you would use a scaffolder plugin module for this; later we will
|
||||
demonstrate the analogous approach with the old backend.
|
||||
|
||||
## Streamlining Template Extension Module Creation with the Backstage CLI
|
||||
|
||||
The creation of a "template environment customization" module in Backstage can
|
||||
be accelerated using the Backstage CLI.
|
||||
|
||||
Start by using the `yarn backstage-cli new` command to generate a scaffolder module. This command sets up the necessary boilerplate code, providing a smooth start:
|
||||
|
||||
```
|
||||
$ yarn backstage-cli new
|
||||
? What do you want to create?
|
||||
> backend-module - A new backend module that extends an existing backend plugin with additional features
|
||||
backend-plugin - A new backend plugin
|
||||
plugin - A new frontend plugin
|
||||
node-library - A new node-library package, exporting shared functionality for backend plugins and modules
|
||||
plugin-common - A new isomorphic common plugin package
|
||||
plugin-node - A new Node.js library plugin package
|
||||
plugin-react - A new web library plugin package
|
||||
scaffolder-module - An module exporting custom actions for @backstage/plugin-scaffolder-backend
|
||||
```
|
||||
|
||||
When prompted, select the option to generate a backend module.
|
||||
Since we want to extend the Scaffolder backend, enter `scaffolder` when prompted for the plugin to extend.
|
||||
Next, enter a name for your module (relative to the generated `scaffolder-backend-module-` prefix),
|
||||
and the CLI will generate the required files and directory structure.
|
||||
|
||||
## Writing your Module
|
||||
|
||||
Once the CLI has generated the essential structure for your new scaffolder
|
||||
module, it's time to implement our template extensions. Here we'll demonstrate
|
||||
how to create each of the supported extension types.
|
||||
|
||||
`src/module.ts` is where the magic happens. First we prepare to utilize the
|
||||
associated (_**alpha** phase_) API extension point by adding:
|
||||
|
||||
```ts
|
||||
import { scaffolderTemplatingExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha';
|
||||
```
|
||||
|
||||
Considering the generated code, you may observe that everything rests on the
|
||||
`createBackendModule` call, which after providing some minimal metadata to
|
||||
establish context, specifies a `register` callback whose sole responsibility
|
||||
here is to call, in turn, `registerInit` against the
|
||||
`BackendModuleRegistrationPoints` argument it receives. Modify this call to
|
||||
make the `scaffolderTemplatingExtensionPoint` available to the specified `init`
|
||||
function:
|
||||
|
||||
```ts
|
||||
register(reg) {
|
||||
reg.registerInit({
|
||||
deps: {
|
||||
...,
|
||||
templating: scaffolderTemplatingExtensionPoint,
|
||||
},
|
||||
async init({
|
||||
...,
|
||||
templating
|
||||
}) {
|
||||
...
|
||||
};
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
Now we're ready to extend the scaffolder templating engine. For our purposes
|
||||
here we'll drop everything in `module.ts`; use your own judgment as to the
|
||||
organization of your real-world plugin modules.
|
||||
|
||||
### Custom Filter
|
||||
|
||||
In this contrived example we add a filter to test whether the incoming string
|
||||
value contains (at least) a specified number of occurrences of a given
|
||||
substring. We can easily define this by adding code to our `init` callback:
|
||||
|
||||
```ts
|
||||
async init({
|
||||
...,
|
||||
templating,
|
||||
}) {
|
||||
...
|
||||
templating.addTemplateFilters({
|
||||
containsOccurrences: (arg: string, substring: string, times: number) => {
|
||||
let pos = 0;
|
||||
let count = 0;
|
||||
while (pos < arg.length) {
|
||||
pos = arg.indexOf(substring, pos);
|
||||
if (pos < 0) {
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
return count === times;
|
||||
},
|
||||
});
|
||||
},
|
||||
```
|
||||
|
||||
This demonstrates the bare minimum: a TypeScript `Record` of named template
|
||||
filter implementations to register. However, by adopting an alternate structure
|
||||
we can document our filter with additional metadata; to utilize this capability
|
||||
we begin by adding a new import:
|
||||
|
||||
```ts
|
||||
import { createTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha';
|
||||
```
|
||||
|
||||
Then, update your `init` implementation to specify an array rather than an
|
||||
object/record:
|
||||
|
||||
```ts
|
||||
async init({
|
||||
...,
|
||||
templating,
|
||||
}) {
|
||||
...
|
||||
templating.addTemplateFilters([
|
||||
createTemplateFilter({
|
||||
id: 'containsOccurrences',
|
||||
description: 'determine whether filter input contains a substring N times',
|
||||
filter: (arg: string, substring: string, times: number) => {
|
||||
let pos = 0;
|
||||
let count = 0;
|
||||
while (pos < arg.length) {
|
||||
pos = arg.indexOf(substring, pos);
|
||||
if (pos < 0) {
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
return count === times;
|
||||
},
|
||||
}),
|
||||
]);
|
||||
},
|
||||
```
|
||||
|
||||
With this we have added a `description` to our filter, which helps a template
|
||||
author to understand the filter's purpose.
|
||||
|
||||
#### Schema
|
||||
|
||||
To enhance our filter documentation further, we will specify its `schema`
|
||||
using a callback against the [Zod][] schema declaration library:
|
||||
|
||||
```ts
|
||||
createTemplateFilter({
|
||||
id: 'containsOccurrences',
|
||||
description: 'determine whether filter input contains a substring N times',
|
||||
schema: z =>
|
||||
z.function(
|
||||
z.tuple([
|
||||
z.string().describe('input'),
|
||||
z.string().describe('substring whose occurrences to find'),
|
||||
z.number().describe('number of occurrences to check for'),
|
||||
]),
|
||||
z.boolean(),
|
||||
),
|
||||
...,
|
||||
}),
|
||||
```
|
||||
|
||||
Because a filter is, in fact, a function, its schema is defined by generating a
|
||||
[Zod function schema][zod-fn] against the parameter supplied to our schema
|
||||
callback. A filter function is required to have at least one argument; in this
|
||||
example, we have two additional arguments. But what if we modify our filter's
|
||||
implementation function to make `times` optional? Code:
|
||||
|
||||
```ts
|
||||
createTemplateFilter({
|
||||
id: 'containsOccurrences',
|
||||
...,
|
||||
filter: (arg: string, substring: string, times?: number) => {
|
||||
if (times === undefined) {
|
||||
// note that, in real life, simply calling this function directly with Nunjucks would suffice rather than implementing a filter:
|
||||
return arg.includes(substring);
|
||||
}
|
||||
// original implementation follows
|
||||
...
|
||||
},
|
||||
}),
|
||||
```
|
||||
|
||||
In this case we should modify our `schema`:
|
||||
|
||||
```ts
|
||||
createTemplateFilter({
|
||||
...,
|
||||
schema: z =>
|
||||
z.function(
|
||||
z.tuple([
|
||||
z.string().describe('input'),
|
||||
z.string().describe('substring whose occurrences to find'),
|
||||
z
|
||||
.number()
|
||||
.describe('number of occurrences to check for')
|
||||
.optional(),
|
||||
]),
|
||||
z.boolean(),
|
||||
),
|
||||
...,
|
||||
}),
|
||||
```
|
||||
|
||||
#### Filter Example Documentation
|
||||
|
||||
Our filter documentation may benefit from examples which we specify thus:
|
||||
|
||||
```ts
|
||||
createTemplateFilter({
|
||||
...,
|
||||
examples: [
|
||||
{
|
||||
description: 'Basic Usage',
|
||||
example: `\
|
||||
- name: Contains Occurrences
|
||||
action: debug:log
|
||||
input:
|
||||
message: \${{ parameters.projectName | containsOccurrences('-', 2) }}
|
||||
`,
|
||||
notes: `\
|
||||
- **Input**: \`foo-bar-baz\`
|
||||
- **Output**: \`true\`
|
||||
`,
|
||||
},
|
||||
{
|
||||
description: 'Omitting Optional Parameter',
|
||||
example: `\
|
||||
- name: Contains baz
|
||||
action: debug:log
|
||||
input:
|
||||
message: \${{ parameters.projectName | containsOccurrences('baz') | dump }}
|
||||
`,
|
||||
notes: `\
|
||||
- **Input**: \`foo-bar\`
|
||||
- **Output**: \`false\`
|
||||
`,
|
||||
},
|
||||
],
|
||||
}),
|
||||
```
|
||||
|
||||
### Custom Global Function
|
||||
|
||||
In case your template needs access to a value generated from a function not
|
||||
appropriately modeled as a filter, Nunjucks supports the direct invocation of
|
||||
[global functions][global-fn]. We might, for example, add to `init`:
|
||||
|
||||
```ts
|
||||
async init({
|
||||
...,
|
||||
templating,
|
||||
}) {
|
||||
...
|
||||
templating.addTemplateGlobals({
|
||||
now: () => new Date().toISOString(),
|
||||
});
|
||||
},
|
||||
```
|
||||
|
||||
Here we have implemented a simple mechanism to obtain a timestamp (note that
|
||||
because we can only pass JSON-compatible--or `undefined`--values we have chosen
|
||||
to model a date/time as an ISO string) using a globally available function.
|
||||
|
||||
Again we have the option to make our global function self-documenting. Import:
|
||||
|
||||
```ts
|
||||
import {
|
||||
...,
|
||||
createTemplateGlobalFunction,
|
||||
} from '@backstage/plugin-scaffolder-node/alpha';
|
||||
```
|
||||
|
||||
Then modify:
|
||||
|
||||
```ts
|
||||
...
|
||||
templating.addTemplateGlobals([
|
||||
createTemplateGlobalFunction({
|
||||
id: 'now',
|
||||
description:
|
||||
'obtain an ISO representation of the current date and time',
|
||||
fn: () => new Date().toISOString(),
|
||||
}),
|
||||
]);
|
||||
```
|
||||
|
||||
#### Schema
|
||||
|
||||
Declaring a global function schema is quite like the schema declaration for a
|
||||
template filter:
|
||||
|
||||
```ts
|
||||
createTemplateGlobal({
|
||||
...,
|
||||
schema: z => z.function().args().returns(z.string()),
|
||||
...,
|
||||
}),
|
||||
```
|
||||
|
||||
#### Template Global Function Example Documentation
|
||||
|
||||
Again, this works in the same way as filter examples:
|
||||
|
||||
```ts
|
||||
createTemplateGlobal({
|
||||
...,
|
||||
examples: [
|
||||
{
|
||||
description: 'Obtain the current date/time',
|
||||
example: `\
|
||||
- name: Log Timestamp
|
||||
action: debug:log
|
||||
input:
|
||||
message: Current date/time: \${{ now() }}
|
||||
`,
|
||||
// optional `notes` omitted from this example
|
||||
},
|
||||
],
|
||||
...,
|
||||
}),
|
||||
|
||||
```
|
||||
|
||||
### Custom Global Value
|
||||
|
||||
Alternatively, your template may need access to a simple JSON value, which can
|
||||
be registered in this manner:
|
||||
|
||||
```ts
|
||||
async init({
|
||||
...,
|
||||
templating,
|
||||
}) {
|
||||
...
|
||||
templating.addTemplateGlobals({
|
||||
...,
|
||||
preferredMetasyntacticIdentifier: 'foo',
|
||||
});
|
||||
},
|
||||
```
|
||||
|
||||
Or the documenting form:
|
||||
|
||||
```ts
|
||||
async init({
|
||||
...,
|
||||
templating,
|
||||
}) {
|
||||
...
|
||||
templating.addTemplateGlobals([
|
||||
...,
|
||||
createTemplateGlobalValue({
|
||||
id: 'preferredMetasyntacticVariable',
|
||||
value: 'foo',
|
||||
description:
|
||||
'This description is as contrived as the global value it documents',
|
||||
}),
|
||||
]);
|
||||
},
|
||||
```
|
||||
|
||||
## Register Template Extensions with the Legacy Backend System
|
||||
|
||||
Users of the original Backstage backend can register template extensions by
|
||||
specifying options to the scaffolder backend plugin's `createRouter` function
|
||||
(customarily called in `packages/backend/src/plugins/scaffolder.ts`):
|
||||
|
||||
- `additionalTemplateFilters` - either of:
|
||||
- object mapping filter name to implementation function, or
|
||||
- array of documented template filters as returned by the
|
||||
utility function `createTemplateFilter`
|
||||
- `additionalTemplateGlobals` - either of:
|
||||
- object mapping global name to value or function, or
|
||||
- array of documented global functions and values as returned by the utility
|
||||
functions `createTemplateGlobalFunction` and `createTemplateGlobalValue`
|
||||
|
||||
[nunjucks]: https://mozilla.github.io/nunjucks
|
||||
[filter]: https://mozilla.github.io/nunjucks/templating.html#filters
|
||||
[global-fn]: https://mozilla.github.io/nunjucks/templating.html#global-functions
|
||||
[parseRepoUrl]: https://backstage.io/docs/reference/plugin-scaffolder-node.parserepourl
|
||||
[CompoundEntityRef]: https://backstage.io/docs/reference/catalog-model.compoundentityref
|
||||
[Zod]: https://zod.dev/
|
||||
[zod-fn]: https://zod.dev/?id=functions
|
||||
[piped]: https://en.wikipedia.org/wiki/Pipeline_(Unix)#Pipelines_in_command_line_interfaces
|
||||
@@ -631,7 +631,7 @@ output:
|
||||
|
||||
## The templating syntax
|
||||
|
||||
You might have noticed variables wrapped in `${{ }}` in the examples. These are
|
||||
You might have noticed expressions wrapped in `${{ }}` in the examples. These are
|
||||
template strings for linking and gluing the different parts of the template
|
||||
together. All the form inputs from the `parameters` section will be available by
|
||||
using this template syntax (for example, `${{ parameters.firstName }}` inserts
|
||||
@@ -704,219 +704,16 @@ You can read more about all the `inputs` and `outputs` defined in the actions in
|
||||
code part of the `JSONSchema`, or you can read more about our
|
||||
[built in actions](./builtin-actions.md).
|
||||
|
||||
## Built in Filters
|
||||
### More about expressions
|
||||
|
||||
Template filters are functions that help you transform data, extract specific information,
|
||||
and perform various operations in Scaffolder Templates.
|
||||
The `${{ }}` constructs in your template are evaluated using the
|
||||
powerful [Nunjucks templating engine](https://mozilla.github.io/nunjucks/).
|
||||
To learn more about basic Nunjucks templating please see
|
||||
[templating documentation](https://mozilla.github.io/nunjucks/templating.html).
|
||||
|
||||
This section introduces the built-in filters provided by Backstage and offers examples of
|
||||
how to use them in the Scaffolder templates. It's important to mention that Backstage also leverages the
|
||||
native filters from the Nunjucks library. For a complete list of these native filters and their usage,
|
||||
refer to the [Nunjucks documentation](https://mozilla.github.io/nunjucks/templating.html#builtin-filters).
|
||||
|
||||
To create your own custom filters, look to the section [Custom Filters and Globals](#custom-filters-and-globals) hereafter.
|
||||
|
||||
### parseRepoUrl
|
||||
|
||||
The `parseRepoUrl` filter parse a repository URL into
|
||||
its components, such as `owner`, repository `name`, and more.
|
||||
|
||||
**Usage Example:**
|
||||
|
||||
```yaml
|
||||
- id: log
|
||||
name: Parse Repo URL
|
||||
action: debug:log
|
||||
input:
|
||||
extra: ${{ parameters.repoUrl | parseRepoUrl }}
|
||||
```
|
||||
|
||||
- **Input**: `github.com?repo=backstage&org=backstage`
|
||||
- **Output**: [RepoSpec](https://github.com/backstage/backstage/blob/v1.17.2/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts#L39)
|
||||
|
||||
### parseEntityRef
|
||||
|
||||
The `parseEntityRef` filter allows you to extract different parts of
|
||||
an entity reference, such as the `kind`, `namespace`, and `name`.
|
||||
|
||||
**Usage example**
|
||||
|
||||
1. Without context
|
||||
|
||||
```yaml
|
||||
- id: log
|
||||
name: Parse Entity Reference
|
||||
action: debug:log
|
||||
input:
|
||||
extra: ${{ parameters.owner | parseEntityRef }}
|
||||
```
|
||||
|
||||
- **Input**: `group:techdocs`
|
||||
- **Output**: [CompoundEntityRef](https://github.com/backstage/backstage/blob/v1.17.2/packages/catalog-model/src/types.ts#L23)
|
||||
|
||||
2. With context
|
||||
|
||||
```yaml
|
||||
- id: log
|
||||
name: Parse Entity Reference
|
||||
action: debug:log
|
||||
input:
|
||||
extra: ${{ parameters.owner | parseEntityRef({ defaultKind:"group", defaultNamespace:"another-namespace" }) }}
|
||||
```
|
||||
|
||||
- **Input**: `techdocs`
|
||||
- **Output**: [CompoundEntityRef](https://github.com/backstage/backstage/blob/v1.17.2/packages/catalog-model/src/types.ts#L23)
|
||||
|
||||
### pick
|
||||
|
||||
This `pick` filter allows you to select specific properties (`kind`, `namespace`, `name`) from an object.
|
||||
|
||||
**Usage Example**
|
||||
|
||||
```yaml
|
||||
- id: log
|
||||
name: Pick
|
||||
action: debug:log
|
||||
input:
|
||||
extra: ${{ parameters.owner | parseEntityRef | pick('name') }}
|
||||
```
|
||||
|
||||
- **Input**: `{ kind: 'Group', namespace: 'default', name: 'techdocs' }`
|
||||
- **Output**: `techdocs`
|
||||
|
||||
### projectSlug
|
||||
|
||||
The `projectSlug` filter generates a project slug from a repository URL
|
||||
|
||||
**Usage Example**
|
||||
|
||||
```yaml
|
||||
- id: log
|
||||
name: Project Slug
|
||||
action: debug:log
|
||||
input:
|
||||
extra: ${{ parameters.repoUrl | projectSlug }}
|
||||
```
|
||||
|
||||
- **Input**: `github.com?repo=backstage&org=backstage`
|
||||
- **Output**: `backstage/backstage`
|
||||
|
||||
## Custom Filters and Globals
|
||||
|
||||
You may wish to extend the filters and globals with your own custom ones. For example `${{ myGlobal | myFilter | myOtherFilter }}` or `${{ myFunctionGlobal(1,2) | myFilter }}`.
|
||||
This can be achieved using the `additionalTemplateFilters` and `additionalTemplateGlobals` properties respectively.
|
||||
|
||||
These properties accept a `Record`
|
||||
|
||||
```ts title="plugins/scaffolder-backend/src/service/router.ts"
|
||||
additionalTemplateFilters?: Record<string, TemplateFilter>;
|
||||
additionalTemplateGlobals?: Record<string, TemplateGlobal>;
|
||||
```
|
||||
|
||||
where the first parameter is the identifier of the filter or global and the second is a `TemplateFilter` or a `TemplateGlobal` respectively.
|
||||
A `TemplateFilter` is a function which will be called using the previous `JsonValue` objects and may return a `JsonValue` object.
|
||||
A `TemplateGlobal` can either be a function which will be called using the passed `JsonValue` objects and may return a `JsonValue` object or it can be a `JsonValue` object itself.
|
||||
|
||||
```ts title="plugins/scaffolder-node/src/types.ts"
|
||||
export type TemplateFilter = (...args: JsonValue[]) => JsonValue | undefined;
|
||||
|
||||
export type TemplateGlobal =
|
||||
| ((...args: JsonValue[]) => JsonValue | undefined)
|
||||
| JsonValue;
|
||||
```
|
||||
|
||||
**Usage Example**
|
||||
|
||||
Given you want to have the following filters and globals available in you template:
|
||||
|
||||
```yaml
|
||||
apiVersion: scaffolder.backstage.io/v1beta3
|
||||
kind: Template
|
||||
metadata:
|
||||
name: test
|
||||
title: Test
|
||||
spec:
|
||||
owner: user:guest
|
||||
type: service
|
||||
|
||||
steps:
|
||||
- id: debug1
|
||||
name: debug1
|
||||
action: debug:log
|
||||
input:
|
||||
message: ${{ myGlobal | myFilter | myOtherFilter }}
|
||||
|
||||
- id: debug2
|
||||
name: debug2
|
||||
action: debug:log
|
||||
input:
|
||||
message: ${{ myFunctionGlobal(1,2) | myFilter }}
|
||||
```
|
||||
|
||||
You will have to create a new [`BackendModule`](../../backend-system/architecture/06-modules.md) using the `scaffolderTemplatingExtensionPoint`.
|
||||
|
||||
Here is a very simplified example of how to do that:
|
||||
|
||||
```ts title="packages/backend-next/src/index.ts"
|
||||
/* highlight-add-start */
|
||||
import { scaffolderTemplatingExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha';
|
||||
import { createBackendModule } from '@backstage/backend-plugin-api';
|
||||
/* highlight-add-end */
|
||||
|
||||
/* highlight-add-start */
|
||||
const scaffolderModuleCustomFilters = createBackendModule({
|
||||
pluginId: 'scaffolder', // name of the plugin that the module is targeting
|
||||
moduleId: 'custom-filters',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
scaffolder: scaffolderTemplatingExtensionPoint,
|
||||
// ... and other dependencies as needed
|
||||
},
|
||||
async init({ scaffolder /* ..., other dependencies */ }) {
|
||||
scaffolder.addTemplateGlobals({
|
||||
myGlobal: () => 'myGlobal',
|
||||
myFunctionGlobal: (...args: JsonValue[]) => args[0] + args[1],
|
||||
});
|
||||
scaffolder.addTemplateFilters({
|
||||
myFilter: () => 'the value is this now',
|
||||
myOtherFilter: (...args: JsonValue[]) => args.join(''),
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
/* highlight-add-end */
|
||||
|
||||
const backend = createBackend();
|
||||
backend.add(import('@backstage/plugin-scaffolder-backend'));
|
||||
/* highlight-add-next-line */
|
||||
backend.add(scaffolderModuleCustomFilters);
|
||||
```
|
||||
|
||||
If you still use the legacy backend system, then you will use the `createRouter()` function of the `Scaffolder plugin`
|
||||
|
||||
```ts title="packages/backend/src/plugins/scaffolder.ts"
|
||||
export default async function createPlugin({
|
||||
logger,
|
||||
config,
|
||||
}: PluginEnvironment): Promise<Router> {
|
||||
...
|
||||
return await createRouter({
|
||||
logger,
|
||||
config,
|
||||
|
||||
additionalTemplateFilters: {
|
||||
<YOUR_FILTERS>
|
||||
},
|
||||
additionalTemplateGlobals: {
|
||||
<YOUR_GLOBALS>
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Note that additional template global functions are currently not supported in `fetch:template` (see #25445).
|
||||
Information about Backstage's built-in Nunjucks extensions, as well as how to
|
||||
create your own customizations, may be found at
|
||||
[Template Extensions](./template-extensions.md).
|
||||
|
||||
## Template Editor
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ nav:
|
||||
- Builtin Actions: 'features/software-templates/builtin-actions.md'
|
||||
- Writing Custom Actions: 'features/software-templates/writing-custom-actions.md'
|
||||
- Writing Custom Step Layouts: 'features/software-templates/writing-custom-step-layouts.md'
|
||||
- Template Extensions: 'features/software-templates/template-extensions.md'
|
||||
- Migrating from v1beta2 to v1beta3 templates: 'features/software-templates/migrating-from-v1beta2-to-v1beta3.md'
|
||||
- Dry Run Testing: 'features/software-templates/dry-run-testing.md'
|
||||
- Backstage Search:
|
||||
|
||||
@@ -13,6 +13,8 @@ import { BackendFeature } from '@backstage/backend-plugin-api';
|
||||
import { BackstageCredentials } from '@backstage/backend-plugin-api';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { Config } from '@backstage/config';
|
||||
import { CreatedTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha';
|
||||
import { CreatedTemplateGlobal } from '@backstage/plugin-scaffolder-node/alpha';
|
||||
import { createGithubActionsDispatchAction as createGithubActionsDispatchAction_2 } from '@backstage/plugin-scaffolder-backend-module-github';
|
||||
import { createGithubDeployKeyAction as createGithubDeployKeyAction_2 } from '@backstage/plugin-scaffolder-backend-module-github';
|
||||
import { createGithubEnvironmentAction as createGithubEnvironmentAction_2 } from '@backstage/plugin-scaffolder-backend-module-github';
|
||||
@@ -549,9 +551,13 @@ export interface RouterOptions {
|
||||
// (undocumented)
|
||||
actions?: TemplateAction_2<any, any>[];
|
||||
// (undocumented)
|
||||
additionalTemplateFilters?: Record<string, TemplateFilter_2>;
|
||||
additionalTemplateFilters?:
|
||||
| Record<string, TemplateFilter_2>
|
||||
| CreatedTemplateFilter[];
|
||||
// (undocumented)
|
||||
additionalTemplateGlobals?: Record<string, TemplateGlobal_2>;
|
||||
additionalTemplateGlobals?:
|
||||
| Record<string, TemplateGlobal_2>
|
||||
| CreatedTemplateGlobal[];
|
||||
// (undocumented)
|
||||
additionalWorkspaceProviders?: Record<string, WorkspaceProvider>;
|
||||
// (undocumented)
|
||||
|
||||
@@ -82,10 +82,27 @@ export const scaffolderPlugin = createBackendPlugin({
|
||||
const additionalTemplateGlobals: Record<string, TemplateGlobal> = {};
|
||||
env.registerExtensionPoint(scaffolderTemplatingExtensionPoint, {
|
||||
addTemplateFilters(newFilters) {
|
||||
Object.assign(additionalTemplateFilters, newFilters);
|
||||
Object.assign(
|
||||
additionalTemplateFilters,
|
||||
Array.isArray(newFilters)
|
||||
? Object.fromEntries(
|
||||
newFilters.map(tf => [tf.id, tf.filter as TemplateFilter]),
|
||||
)
|
||||
: newFilters,
|
||||
);
|
||||
},
|
||||
addTemplateGlobals(newGlobals) {
|
||||
Object.assign(additionalTemplateGlobals, newGlobals);
|
||||
Object.assign(
|
||||
additionalTemplateGlobals,
|
||||
Array.isArray(newGlobals)
|
||||
? Object.fromEntries(
|
||||
newGlobals.map(g => [
|
||||
g.id,
|
||||
('value' in g ? g.value : g.fn) as TemplateGlobal,
|
||||
]),
|
||||
)
|
||||
: newGlobals,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -81,6 +81,8 @@ import {
|
||||
} from '@backstage/plugin-scaffolder-node';
|
||||
import {
|
||||
AutocompleteHandler,
|
||||
CreatedTemplateFilter,
|
||||
CreatedTemplateGlobal,
|
||||
WorkspaceProvider,
|
||||
} from '@backstage/plugin-scaffolder-node/alpha';
|
||||
import { HumanDuration, JsonObject, JsonValue } from '@backstage/types';
|
||||
@@ -173,8 +175,12 @@ export interface RouterOptions {
|
||||
*/
|
||||
concurrentTasksLimit?: number;
|
||||
taskBroker?: TaskBroker;
|
||||
additionalTemplateFilters?: Record<string, TemplateFilter>;
|
||||
additionalTemplateGlobals?: Record<string, TemplateGlobal>;
|
||||
additionalTemplateFilters?:
|
||||
| Record<string, TemplateFilter>
|
||||
| CreatedTemplateFilter[];
|
||||
additionalTemplateGlobals?:
|
||||
| Record<string, TemplateGlobal>
|
||||
| CreatedTemplateGlobal[];
|
||||
additionalWorkspaceProviders?: Record<string, WorkspaceProvider>;
|
||||
permissions?: PermissionsService;
|
||||
permissionRules?: Array<
|
||||
@@ -363,6 +369,24 @@ export async function createRouter(
|
||||
}
|
||||
|
||||
const actionRegistry = new TemplateActionRegistry();
|
||||
const templateExtensions = {
|
||||
additionalTemplateFilters: Array.isArray(additionalTemplateFilters)
|
||||
? Object.fromEntries(
|
||||
additionalTemplateFilters.map(f => [
|
||||
f.id,
|
||||
f.filter as TemplateFilter,
|
||||
]),
|
||||
)
|
||||
: additionalTemplateFilters,
|
||||
additionalTemplateGlobals: Array.isArray(additionalTemplateGlobals)
|
||||
? Object.fromEntries(
|
||||
additionalTemplateGlobals.map(g => [
|
||||
g.id,
|
||||
('value' in g ? g.value : g.fn) as TemplateGlobal,
|
||||
]),
|
||||
)
|
||||
: additionalTemplateGlobals,
|
||||
};
|
||||
|
||||
const workers: TaskWorker[] = [];
|
||||
if (concurrentTasksLimit !== 0) {
|
||||
@@ -378,11 +402,10 @@ export async function createRouter(
|
||||
logger,
|
||||
auditor,
|
||||
workingDirectory,
|
||||
additionalTemplateFilters,
|
||||
additionalTemplateGlobals,
|
||||
concurrentTasksLimit,
|
||||
permissions,
|
||||
gracefulShutdown,
|
||||
...templateExtensions,
|
||||
});
|
||||
workers.push(worker);
|
||||
}
|
||||
@@ -395,9 +418,8 @@ export async function createRouter(
|
||||
catalogClient,
|
||||
reader,
|
||||
config,
|
||||
additionalTemplateFilters,
|
||||
additionalTemplateGlobals,
|
||||
auth,
|
||||
...templateExtensions,
|
||||
});
|
||||
|
||||
actionsToRegister.forEach(action => actionRegistry.register(action));
|
||||
@@ -421,9 +443,8 @@ export async function createRouter(
|
||||
logger,
|
||||
auditor,
|
||||
workingDirectory,
|
||||
additionalTemplateFilters,
|
||||
additionalTemplateGlobals,
|
||||
permissions,
|
||||
...templateExtensions,
|
||||
});
|
||||
|
||||
const templateRules: TemplatePermissionRuleInput[] = Object.values(
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./alpha": "./src/alpha.ts",
|
||||
"./alpha": "./src/alpha/index.ts",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"main": "src/index.ts",
|
||||
@@ -34,7 +34,7 @@
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"alpha": [
|
||||
"src/alpha.ts"
|
||||
"src/alpha/index.ts"
|
||||
],
|
||||
"package.json": [
|
||||
"package.json"
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
/// <reference types="node" />
|
||||
|
||||
import { ExtensionPoint } from '@backstage/backend-plugin-api';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { TaskBroker } from '@backstage/plugin-scaffolder-node';
|
||||
import { TemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import { TemplateFilter } from '@backstage/plugin-scaffolder-node';
|
||||
import { TemplateGlobal } from '@backstage/plugin-scaffolder-node';
|
||||
import { TemplateFilter as TemplateFilter_2 } from '@backstage/plugin-scaffolder-node';
|
||||
import { TemplateGlobal as TemplateGlobal_2 } from '@backstage/plugin-scaffolder-node';
|
||||
import { z } from 'zod';
|
||||
|
||||
// @alpha
|
||||
export type AutocompleteHandler = ({
|
||||
@@ -27,6 +29,92 @@ export type AutocompleteHandler = ({
|
||||
}[];
|
||||
}>;
|
||||
|
||||
// @alpha (undocumented)
|
||||
export type CreatedTemplateFilter<
|
||||
TSchema extends
|
||||
| TemplateFilterSchema<any, any>
|
||||
| undefined
|
||||
| unknown = unknown,
|
||||
TFilterSchema extends TSchema extends TemplateFilterSchema<any, any>
|
||||
? z.infer<ReturnType<TSchema>>
|
||||
: TSchema extends unknown
|
||||
? unknown
|
||||
: TemplateFilter = TSchema extends TemplateFilterSchema<any, any>
|
||||
? z.infer<ReturnType<TSchema>>
|
||||
: TSchema extends unknown
|
||||
? unknown
|
||||
: TemplateFilter,
|
||||
> = {
|
||||
id: string;
|
||||
description?: string;
|
||||
examples?: TemplateFilterExample[];
|
||||
schema?: TSchema;
|
||||
filter: TFilterSchema;
|
||||
};
|
||||
|
||||
// @alpha (undocumented)
|
||||
export type CreatedTemplateGlobal =
|
||||
| CreatedTemplateGlobalValue
|
||||
| CreatedTemplateGlobalFunction<unknown, unknown>;
|
||||
|
||||
// @alpha (undocumented)
|
||||
export type CreatedTemplateGlobalFunction<
|
||||
TSchema extends
|
||||
| TemplateGlobalFunctionSchema<any, any>
|
||||
| undefined
|
||||
| unknown = unknown,
|
||||
TFilterSchema extends TSchema extends TemplateGlobalFunctionSchema<any, any>
|
||||
? z.infer<ReturnType<TSchema>>
|
||||
: TSchema extends unknown
|
||||
? unknown
|
||||
: Exclude<
|
||||
TemplateGlobal,
|
||||
JsonValue
|
||||
> = TSchema extends TemplateGlobalFunctionSchema<any, any>
|
||||
? z.infer<ReturnType<TSchema>>
|
||||
: TSchema extends unknown
|
||||
? unknown
|
||||
: Exclude<TemplateGlobal, JsonValue>,
|
||||
> = {
|
||||
id: string;
|
||||
description?: string;
|
||||
examples?: TemplateGlobalFunctionExample[];
|
||||
schema?: TSchema;
|
||||
fn: TFilterSchema;
|
||||
};
|
||||
|
||||
// @alpha (undocumented)
|
||||
export type CreatedTemplateGlobalValue<T extends JsonValue = JsonValue> = {
|
||||
id: string;
|
||||
value: T;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
// @alpha
|
||||
export const createTemplateFilter: <
|
||||
TSchema extends TemplateFilterSchema<any, any> | undefined,
|
||||
TFunctionSchema extends TSchema extends TemplateFilterSchema<any, any>
|
||||
? z.TypeOf<ReturnType<TSchema>>
|
||||
: (arg: JsonValue, ...rest: JsonValue[]) => JsonValue | undefined,
|
||||
>(
|
||||
filter: CreatedTemplateFilter<TSchema, TFunctionSchema>,
|
||||
) => CreatedTemplateFilter<unknown, unknown>;
|
||||
|
||||
// @alpha
|
||||
export const createTemplateGlobalFunction: <
|
||||
TSchema extends TemplateGlobalFunctionSchema<any, any> | undefined,
|
||||
TFilterSchema extends TSchema extends TemplateGlobalFunctionSchema<any, any>
|
||||
? z.TypeOf<ReturnType<TSchema>>
|
||||
: (...args: JsonValue[]) => JsonValue | undefined,
|
||||
>(
|
||||
fn: CreatedTemplateGlobalFunction<TSchema, TFilterSchema>,
|
||||
) => CreatedTemplateGlobalFunction<any, any>;
|
||||
|
||||
// @alpha
|
||||
export const createTemplateGlobalValue: (
|
||||
v: CreatedTemplateGlobalValue,
|
||||
) => CreatedTemplateGlobalValue;
|
||||
|
||||
// @alpha
|
||||
export const restoreWorkspace: (opts: {
|
||||
path: string;
|
||||
@@ -69,9 +157,13 @@ export const scaffolderTaskBrokerExtensionPoint: ExtensionPoint<ScaffolderTaskBr
|
||||
// @alpha
|
||||
export interface ScaffolderTemplatingExtensionPoint {
|
||||
// (undocumented)
|
||||
addTemplateFilters(filters: Record<string, TemplateFilter>): void;
|
||||
addTemplateFilters(
|
||||
filters: Record<string, TemplateFilter_2> | CreatedTemplateFilter[],
|
||||
): void;
|
||||
// (undocumented)
|
||||
addTemplateGlobals(filters: Record<string, TemplateGlobal>): void;
|
||||
addTemplateGlobals(
|
||||
globals: Record<string, TemplateGlobal_2> | CreatedTemplateGlobal[],
|
||||
): void;
|
||||
}
|
||||
|
||||
// @alpha
|
||||
@@ -91,6 +183,50 @@ export const serializeWorkspace: (opts: { path: string }) => Promise<{
|
||||
contents: Buffer;
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TemplateFilter = (
|
||||
arg: JsonValue,
|
||||
...rest: JsonValue[]
|
||||
) => JsonValue | undefined;
|
||||
|
||||
// @alpha (undocumented)
|
||||
export type TemplateFilterExample = {
|
||||
description?: string;
|
||||
example: string;
|
||||
notes?: string;
|
||||
};
|
||||
|
||||
// @alpha (undocumented)
|
||||
export type TemplateFilterSchema<
|
||||
Args extends z.ZodTuple<
|
||||
| [z.ZodType<JsonValue>]
|
||||
| [z.ZodType<JsonValue>, ...(z.ZodType<JsonValue> | z.ZodUnknown)[]],
|
||||
z.ZodType<JsonValue> | z.ZodUnknown | null
|
||||
>,
|
||||
Result extends z.ZodType<JsonValue> | z.ZodUndefined,
|
||||
> = (zod: typeof z) => z.ZodFunction<Args, Result>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TemplateGlobal =
|
||||
| ((...args: JsonValue[]) => JsonValue | undefined)
|
||||
| JsonValue;
|
||||
|
||||
// @alpha (undocumented)
|
||||
export type TemplateGlobalFunctionExample = {
|
||||
description?: string;
|
||||
example: string;
|
||||
notes?: string;
|
||||
};
|
||||
|
||||
// @alpha (undocumented)
|
||||
export type TemplateGlobalFunctionSchema<
|
||||
Args extends z.ZodTuple<
|
||||
[] | [z.ZodType<JsonValue>, ...(z.ZodType<JsonValue> | z.ZodUnknown)[]],
|
||||
z.ZodType<JsonValue> | z.ZodUnknown | null
|
||||
>,
|
||||
Result extends z.ZodType<JsonValue> | z.ZodUndefined,
|
||||
> = (zod: typeof z) => z.ZodFunction<Args, Result>;
|
||||
|
||||
// @alpha
|
||||
export interface WorkspaceProvider {
|
||||
// (undocumented)
|
||||
|
||||
@@ -482,7 +482,10 @@ export type TemplateExample = {
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type TemplateFilter = (...args: JsonValue[]) => JsonValue | undefined;
|
||||
export type TemplateFilter = (
|
||||
arg: JsonValue,
|
||||
...rest: JsonValue[]
|
||||
) => JsonValue | undefined;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TemplateGlobal =
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { CreatedTemplateFilter, TemplateFilterSchema } from './types';
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* This function is used to create new template filters in type-safe manner.
|
||||
* @alpha
|
||||
*/
|
||||
export const createTemplateFilter = <
|
||||
TSchema extends TemplateFilterSchema<any, any> | undefined,
|
||||
TFunctionSchema extends TSchema extends TemplateFilterSchema<any, any>
|
||||
? z.infer<ReturnType<TSchema>>
|
||||
: (arg: JsonValue, ...rest: JsonValue[]) => JsonValue | undefined,
|
||||
>(
|
||||
filter: CreatedTemplateFilter<TSchema, TFunctionSchema>,
|
||||
): CreatedTemplateFilter<unknown, unknown> => filter;
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export * from './types';
|
||||
export * from './createTemplateFilter';
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { z } from 'zod';
|
||||
import { TemplateFilter } from '../../types';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
|
||||
export type { TemplateFilter } from '../../types';
|
||||
|
||||
/** @alpha */
|
||||
export type TemplateFilterSchema<
|
||||
Args extends z.ZodTuple<
|
||||
| [z.ZodType<JsonValue>]
|
||||
| [z.ZodType<JsonValue>, ...(z.ZodType<JsonValue> | z.ZodUnknown)[]],
|
||||
z.ZodType<JsonValue> | z.ZodUnknown | null
|
||||
>,
|
||||
Result extends z.ZodType<JsonValue> | z.ZodUndefined,
|
||||
> = (zod: typeof z) => z.ZodFunction<Args, Result>;
|
||||
|
||||
/** @alpha */
|
||||
export type TemplateFilterExample = {
|
||||
description?: string;
|
||||
example: string;
|
||||
notes?: string;
|
||||
};
|
||||
|
||||
/** @alpha */
|
||||
export type CreatedTemplateFilter<
|
||||
TSchema extends
|
||||
| TemplateFilterSchema<any, any>
|
||||
| undefined
|
||||
| unknown = unknown,
|
||||
TFilterSchema extends TSchema extends TemplateFilterSchema<any, any>
|
||||
? z.infer<ReturnType<TSchema>>
|
||||
: TSchema extends unknown
|
||||
? unknown
|
||||
: TemplateFilter = TSchema extends TemplateFilterSchema<any, any>
|
||||
? z.infer<ReturnType<TSchema>>
|
||||
: TSchema extends unknown
|
||||
? unknown
|
||||
: TemplateFilter,
|
||||
> = {
|
||||
id: string;
|
||||
description?: string;
|
||||
examples?: TemplateFilterExample[];
|
||||
schema?: TSchema;
|
||||
filter: TFilterSchema;
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
CreatedTemplateGlobalFunction,
|
||||
CreatedTemplateGlobalValue,
|
||||
TemplateGlobalFunctionSchema,
|
||||
} from './types';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
|
||||
/**
|
||||
* This function is used to create new template global values in type-safe manner.
|
||||
* @param t - CreatedTemplateGlobalValue | CreatedTemplateGlobalFunction
|
||||
* @returns t
|
||||
* @alpha
|
||||
*/
|
||||
export const createTemplateGlobalValue = (
|
||||
v: CreatedTemplateGlobalValue,
|
||||
): CreatedTemplateGlobalValue => v;
|
||||
|
||||
/**
|
||||
* This function is used to create new template global functions in type-safe manner.
|
||||
* @param fn - CreatedTemplateGlobalFunction
|
||||
* @returns fn
|
||||
* @alpha
|
||||
*/
|
||||
export const createTemplateGlobalFunction = <
|
||||
TSchema extends TemplateGlobalFunctionSchema<any, any> | undefined,
|
||||
TFilterSchema extends TSchema extends TemplateGlobalFunctionSchema<any, any>
|
||||
? z.infer<ReturnType<TSchema>>
|
||||
: (...args: JsonValue[]) => JsonValue | undefined,
|
||||
>(
|
||||
fn: CreatedTemplateGlobalFunction<TSchema, TFilterSchema>,
|
||||
): CreatedTemplateGlobalFunction<any, any> => fn;
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export * from './types';
|
||||
export * from './createTemplateGlobal';
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { z } from 'zod';
|
||||
import { TemplateGlobal } from '../../types';
|
||||
|
||||
export type { TemplateGlobal } from '../../types';
|
||||
|
||||
/** @alpha */
|
||||
export type CreatedTemplateGlobalValue<T extends JsonValue = JsonValue> = {
|
||||
id: string;
|
||||
value: T;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
/** @alpha */
|
||||
export type TemplateGlobalFunctionSchema<
|
||||
Args extends z.ZodTuple<
|
||||
[] | [z.ZodType<JsonValue>, ...(z.ZodType<JsonValue> | z.ZodUnknown)[]],
|
||||
z.ZodType<JsonValue> | z.ZodUnknown | null
|
||||
>,
|
||||
Result extends z.ZodType<JsonValue> | z.ZodUndefined,
|
||||
> = (zod: typeof z) => z.ZodFunction<Args, Result>;
|
||||
|
||||
/** @alpha */
|
||||
export type TemplateGlobalFunctionExample = {
|
||||
description?: string;
|
||||
example: string;
|
||||
notes?: string;
|
||||
};
|
||||
|
||||
/** @alpha */
|
||||
export type CreatedTemplateGlobalFunction<
|
||||
TSchema extends
|
||||
| TemplateGlobalFunctionSchema<any, any>
|
||||
| undefined
|
||||
| unknown = unknown,
|
||||
TFilterSchema extends TSchema extends TemplateGlobalFunctionSchema<any, any>
|
||||
? z.infer<ReturnType<TSchema>>
|
||||
: TSchema extends unknown
|
||||
? unknown
|
||||
: Exclude<
|
||||
TemplateGlobal,
|
||||
JsonValue
|
||||
> = TSchema extends TemplateGlobalFunctionSchema<any, any>
|
||||
? z.infer<ReturnType<TSchema>>
|
||||
: TSchema extends unknown
|
||||
? unknown
|
||||
: Exclude<TemplateGlobal, JsonValue>,
|
||||
> = {
|
||||
id: string;
|
||||
description?: string;
|
||||
examples?: TemplateGlobalFunctionExample[];
|
||||
schema?: TSchema;
|
||||
fn: TFilterSchema;
|
||||
};
|
||||
|
||||
/** @alpha */
|
||||
export type CreatedTemplateGlobal =
|
||||
| CreatedTemplateGlobalValue
|
||||
| CreatedTemplateGlobalFunction<unknown, unknown>;
|
||||
@@ -21,8 +21,12 @@ import {
|
||||
TemplateFilter,
|
||||
TemplateGlobal,
|
||||
} from '@backstage/plugin-scaffolder-node';
|
||||
import { CreatedTemplateFilter } from './filters';
|
||||
import { CreatedTemplateGlobal } from './globals';
|
||||
|
||||
export * from './tasks/alpha';
|
||||
export * from '../tasks/alpha';
|
||||
export * from './filters';
|
||||
export * from './globals';
|
||||
|
||||
/**
|
||||
* Extension point for managing scaffolder actions.
|
||||
@@ -68,9 +72,13 @@ export const scaffolderTaskBrokerExtensionPoint =
|
||||
* @alpha
|
||||
*/
|
||||
export interface ScaffolderTemplatingExtensionPoint {
|
||||
addTemplateFilters(filters: Record<string, TemplateFilter>): void;
|
||||
addTemplateFilters(
|
||||
filters: Record<string, TemplateFilter> | CreatedTemplateFilter[],
|
||||
): void;
|
||||
|
||||
addTemplateGlobals(filters: Record<string, TemplateGlobal>): void;
|
||||
addTemplateGlobals(
|
||||
globals: Record<string, TemplateGlobal> | CreatedTemplateGlobal[],
|
||||
): void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,4 +23,4 @@
|
||||
export * from './actions';
|
||||
export * from './tasks';
|
||||
export * from './files';
|
||||
export type { TemplateFilter, TemplateGlobal } from './types';
|
||||
export * from './types';
|
||||
|
||||
Reference in New Issue
Block a user