From 7f56c442aae6ddd6e947e969b91ac0a09d03d6d7 Mon Sep 17 00:00:00 2001 From: Matt Benson Date: Wed, 12 Mar 2025 00:21:17 -0500 Subject: [PATCH 1/5] improve return types of createTemplateFilter() and createTemplateGlobalFunction(); accommodate in ScaffolderTemplatingExtensionPoint Signed-off-by: Matt Benson --- plugins/scaffolder-node/report-alpha.api.md | 12 +++++++----- .../src/alpha/filters/createTemplateFilter.ts | 6 +++--- .../src/alpha/globals/createTemplateGlobal.ts | 2 +- plugins/scaffolder-node/src/alpha/index.ts | 4 +++- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/plugins/scaffolder-node/report-alpha.api.md b/plugins/scaffolder-node/report-alpha.api.md index fa9e022bb4..151af9ea37 100644 --- a/plugins/scaffolder-node/report-alpha.api.md +++ b/plugins/scaffolder-node/report-alpha.api.md @@ -91,12 +91,12 @@ export type CreatedTemplateGlobalValue = { // @alpha export const createTemplateFilter: < TSchema extends TemplateFilterSchema | undefined, - TFunctionSchema extends TSchema extends TemplateFilterSchema + TFilterSchema extends TSchema extends TemplateFilterSchema ? z.infer> : (arg: JsonValue, ...rest: JsonValue[]) => JsonValue | undefined, >( - filter: CreatedTemplateFilter, -) => CreatedTemplateFilter; + filter: CreatedTemplateFilter, +) => CreatedTemplateFilter; // @alpha export const createTemplateGlobalFunction: < @@ -106,7 +106,7 @@ export const createTemplateGlobalFunction: < : (...args: JsonValue[]) => JsonValue | undefined, >( fn: CreatedTemplateGlobalFunction, -) => CreatedTemplateGlobalFunction; +) => CreatedTemplateGlobalFunction; // @alpha export const createTemplateGlobalValue: ( @@ -156,7 +156,9 @@ export const scaffolderTaskBrokerExtensionPoint: ExtensionPoint | CreatedTemplateFilter[], + filters: + | Record + | CreatedTemplateFilter[], ): void; // (undocumented) addTemplateGlobals( diff --git a/plugins/scaffolder-node/src/alpha/filters/createTemplateFilter.ts b/plugins/scaffolder-node/src/alpha/filters/createTemplateFilter.ts index 1f4bac4ab7..219884b704 100644 --- a/plugins/scaffolder-node/src/alpha/filters/createTemplateFilter.ts +++ b/plugins/scaffolder-node/src/alpha/filters/createTemplateFilter.ts @@ -24,9 +24,9 @@ import { z } from 'zod'; */ export const createTemplateFilter = < TSchema extends TemplateFilterSchema | undefined, - TFunctionSchema extends TSchema extends TemplateFilterSchema + TFilterSchema extends TSchema extends TemplateFilterSchema ? z.infer> : (arg: JsonValue, ...rest: JsonValue[]) => JsonValue | undefined, >( - filter: CreatedTemplateFilter, -): CreatedTemplateFilter => filter; + filter: CreatedTemplateFilter, +): CreatedTemplateFilter => filter; diff --git a/plugins/scaffolder-node/src/alpha/globals/createTemplateGlobal.ts b/plugins/scaffolder-node/src/alpha/globals/createTemplateGlobal.ts index 1f9e090d7f..2361a50964 100644 --- a/plugins/scaffolder-node/src/alpha/globals/createTemplateGlobal.ts +++ b/plugins/scaffolder-node/src/alpha/globals/createTemplateGlobal.ts @@ -45,4 +45,4 @@ export const createTemplateGlobalFunction = < : (...args: JsonValue[]) => JsonValue | undefined, >( fn: CreatedTemplateGlobalFunction, -): CreatedTemplateGlobalFunction => fn; +): CreatedTemplateGlobalFunction => fn; diff --git a/plugins/scaffolder-node/src/alpha/index.ts b/plugins/scaffolder-node/src/alpha/index.ts index d97aa54557..1fd5abee18 100644 --- a/plugins/scaffolder-node/src/alpha/index.ts +++ b/plugins/scaffolder-node/src/alpha/index.ts @@ -73,7 +73,9 @@ export const scaffolderTaskBrokerExtensionPoint = */ export interface ScaffolderTemplatingExtensionPoint { addTemplateFilters( - filters: Record | CreatedTemplateFilter[], + filters: + | Record + | CreatedTemplateFilter[], ): void; addTemplateGlobals( From 497d47aafa53a6c4de2aee24d9281785471329a6 Mon Sep 17 00:00:00 2001 From: Matt Benson Date: Fri, 7 Feb 2025 14:16:51 -0600 Subject: [PATCH 2/5] document built-in template filters Signed-off-by: Matt Benson --- .changeset/odd-bobcats-hang.md | 5 ++ .../src/lib/templating/filters.ts | 40 ------------ .../src/lib/templating/filters/index.ts | 29 +++++++++ .../filters/parseEntityRef/examples.ts | 48 +++++++++++++++ .../filters/parseEntityRef/index.ts | 61 +++++++++++++++++++ .../filters/parseRepoUrl/examples.ts | 31 ++++++++++ .../templating/filters/parseRepoUrl/index.ts | 50 +++++++++++++++ .../lib/templating/filters/pick/examples.ts | 31 ++++++++++ .../src/lib/templating/filters/pick/index.ts | 31 ++++++++++ .../filters/projectSlug/examples.ts | 32 ++++++++++ .../templating/filters/projectSlug/index.ts | 37 +++++++++++ .../actions/builtin/fetch/template.ts | 2 +- .../actions/builtin/fetch/templateFile.ts | 2 +- .../tasks/NunjucksWorkflowRunner.ts | 2 +- .../src/util/templating.test.ts | 4 +- 15 files changed, 360 insertions(+), 45 deletions(-) create mode 100644 .changeset/odd-bobcats-hang.md delete mode 100644 plugins/scaffolder-backend/src/lib/templating/filters.ts create mode 100644 plugins/scaffolder-backend/src/lib/templating/filters/index.ts create mode 100644 plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/examples.ts create mode 100644 plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/index.ts create mode 100644 plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/examples.ts create mode 100644 plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/index.ts create mode 100644 plugins/scaffolder-backend/src/lib/templating/filters/pick/examples.ts create mode 100644 plugins/scaffolder-backend/src/lib/templating/filters/pick/index.ts create mode 100644 plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/examples.ts create mode 100644 plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/index.ts diff --git a/.changeset/odd-bobcats-hang.md b/.changeset/odd-bobcats-hang.md new file mode 100644 index 0000000000..a7c0d59754 --- /dev/null +++ b/.changeset/odd-bobcats-hang.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +document built-in template filters diff --git a/plugins/scaffolder-backend/src/lib/templating/filters.ts b/plugins/scaffolder-backend/src/lib/templating/filters.ts deleted file mode 100644 index bc3f95b18c..0000000000 --- a/plugins/scaffolder-backend/src/lib/templating/filters.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2023 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 { parseEntityRef } from '@backstage/catalog-model'; -import { ScmIntegrations } from '@backstage/integration'; -import type { JsonObject, JsonValue } from '@backstage/types'; -import { - parseRepoUrl, - TemplateFilter, -} from '@backstage/plugin-scaffolder-node'; -import get from 'lodash/get'; - -export default ({ - integrations, -}: { - integrations: ScmIntegrations; -}): Record => { - return { - parseRepoUrl: url => parseRepoUrl(url as string, integrations), - parseEntityRef: (ref: JsonValue, context?: JsonValue) => - parseEntityRef(ref as string, context as JsonObject), - pick: (obj: JsonValue, key: JsonValue) => get(obj, key as string), - projectSlug: repoUrl => { - const { owner, repo } = parseRepoUrl(repoUrl as string, integrations); - return `${owner}/${repo}`; - }, - }; -}; diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/index.ts b/plugins/scaffolder-backend/src/lib/templating/filters/index.ts new file mode 100644 index 0000000000..5eb4de5ac3 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/index.ts @@ -0,0 +1,29 @@ +/* + * 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 { ScmIntegrations } from '@backstage/integration'; +import { createParseRepoUrl } from './parseRepoUrl'; +import { parseEntityRef } from './parseEntityRef'; +import { pick } from './pick'; +import { createProjectSlug } from './projectSlug'; + +export const createDefaultFilters = (options: { + integrations: ScmIntegrations; +}) => [ + createParseRepoUrl(options.integrations), + parseEntityRef, + pick, + createProjectSlug(options.integrations), +]; diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/examples.ts b/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/examples.ts new file mode 100644 index 0000000000..fd6219c998 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/examples.ts @@ -0,0 +1,48 @@ +import { TemplateFilterExample } from '@backstage/plugin-scaffolder-node/alpha'; + +/* + * 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 const examples: TemplateFilterExample[] = [ + { + description: 'Without context', + example: `\ +- id: log + name: Parse Entity Reference + action: debug:log + input: + message: \${{ parameters.owner | parseEntityRef }} +`, + notes: `\ +- **Input**: \`group:techdocs\` +- **Output**: \`{"kind": "group", "namespace": "default", "name": "techdocs"}\` +`, + }, + { + description: 'With context', + example: `\ +- id: log + name: Parse Entity Reference + action: debug:log + input: + message: \${{ parameters.owner | parseEntityRef({ defaultKind:"group", defaultNamespace:"another-namespace" }) }} +`, + notes: `\ +- **Input**: \`techdocs\` +- **Arguments:**: \`[{ "defaultKind": "group", "defaultNamespace": "another-namespace" }]\` +- **Output**: \`{"kind": "group", "namespace": "another-namespace", "name": "techdocs"}\` +`, + }, +]; diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/index.ts b/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/index.ts new file mode 100644 index 0000000000..221be44055 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/index.ts @@ -0,0 +1,61 @@ +/* + * 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 { parseEntityRef as filter } from '@backstage/catalog-model'; +import { createTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha'; +import { examples } from './examples'; + +export const parseEntityRef = createTemplateFilter({ + id: 'parseEntityRef', + description: + 'Extracts the parts of an entity reference, such as the kind, namespace, and name.', + schema: z => + z.function( + z.tuple([ + z.union([ + z.string().describe('compact entity reference'), + z + .object({ + kind: z.string().optional(), + namespace: z.string().optional(), + name: z.string(), + }) + .describe('`CompoundEntityRef`'), + ]), + z + .object({ + defaultKind: z + .string() + .describe('The default kind, if none is given in the reference'), + defaultNamespace: z + .string() + .describe( + 'The default namespace, if none is given in the reference', + ), + }) + .partial() + .optional(), + ]), + z + .object({ + kind: z.string(), + namespace: z.string(), + name: z.string(), + }) + .describe('`CompoundEntityRef`'), + ), + examples, + filter, +}); diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/examples.ts b/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/examples.ts new file mode 100644 index 0000000000..4348305737 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/examples.ts @@ -0,0 +1,31 @@ +/* + * 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 { TemplateFilterExample } from '@backstage/plugin-scaffolder-node/alpha'; + +export const examples: TemplateFilterExample[] = [ + { + example: `\ +- id: log + name: Parse Repo URL + action: debug:log + input: + message: \${{ parameters.repoUrl | parseRepoUrl }}`, + notes: ` - **Input**: \`github.com?repo=backstage&owner=backstage\` + - **Output**: \`{"host":"github.com","owner":"backstage","repo":"backstage"}\` +`, + }, +]; diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/index.ts b/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/index.ts new file mode 100644 index 0000000000..a726c140cf --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/index.ts @@ -0,0 +1,50 @@ +/* + * 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 { ScmIntegrations } from '@backstage/integration'; +import { parseRepoUrl } from '@backstage/plugin-scaffolder-node'; +import { createTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha'; +import { examples } from './examples'; + +export const createParseRepoUrl = (integrations: ScmIntegrations) => + createTemplateFilter({ + id: 'parseRepoUrl', + description: + 'Parses a repository URL into its constituent parts: owner, repository name, etc.', + schema: z => + z.function( + z.tuple([ + z.string().describe('repo URL as collected from repository picker'), + ]), + z + .object({ + repo: z.string(), + host: z.string(), + }) + .merge( + z + .object({ + owner: z.string(), + organization: z.string(), + workspace: z.string(), + project: z.string(), + }) + .partial(), + ) + .describe('`RepoSpec`'), + ), + examples, + filter: (url: string) => parseRepoUrl(url, integrations), + }); diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/pick/examples.ts b/plugins/scaffolder-backend/src/lib/templating/filters/pick/examples.ts new file mode 100644 index 0000000000..28593847ef --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/pick/examples.ts @@ -0,0 +1,31 @@ +import { TemplateFilterExample } from '@backstage/plugin-scaffolder-node/alpha'; + +/* + * 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 const examples: TemplateFilterExample[] = [ + { + example: `\ +- id: log + name: Pick + action: debug:log + input: + message: \${{ parameters.owner | parseEntityRef | pick('name') }}`, + notes: `\ +- **Input**: \`{ kind: 'Group', namespace: 'default', name: 'techdocs'\` } +- **Output**: \`techdocs\` +`, + }, +]; diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/pick/index.ts b/plugins/scaffolder-backend/src/lib/templating/filters/pick/index.ts new file mode 100644 index 0000000000..5d8a0d664a --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/pick/index.ts @@ -0,0 +1,31 @@ +/* + * 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 { createTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha'; +import { get as filter } from 'lodash'; +import { examples } from './examples'; + +export const pick = createTemplateFilter({ + id: 'pick', + description: + 'Selects a specific property (e.g. kind, namespace, name) from an object.', + schema: z => + z.function( + z.tuple([z.any(), z.string().describe('Property')]), + z.any().describe('Selected property'), + ), + examples, + filter, +}); diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/examples.ts b/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/examples.ts new file mode 100644 index 0000000000..dde36ea888 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/examples.ts @@ -0,0 +1,32 @@ +import { TemplateFilterExample } from '@backstage/plugin-scaffolder-node/alpha'; + +/* + * 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 const examples: TemplateFilterExample[] = [ + { + example: `\ +- id: log + name: Project Slug + action: debug:log + input: + message: \${{ parameters.repoUrl | projectSlug }} +`, + notes: `\ +- **Input**: \`github.com?repo=backstage&owner=backstage\` +- **Output**: backstage/backstage +`, + }, +]; diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/index.ts b/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/index.ts new file mode 100644 index 0000000000..a833121423 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/index.ts @@ -0,0 +1,37 @@ +/* + * 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 { ScmIntegrations } from '@backstage/integration'; +import { parseRepoUrl } from '@backstage/plugin-scaffolder-node'; +import { createTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha'; +import { examples } from './examples'; + +export const createProjectSlug = (integrations: ScmIntegrations) => + createTemplateFilter({ + id: 'projectSlug', + description: 'Generates a project slug from a repository URL.', + schema: z => + z.function( + z.tuple([ + z.string().describe('repo URL as collected from repository picker'), + ]), + z.string(), + ), + examples, + filter: (repoUrl: string) => { + const { owner, repo } = parseRepoUrl(repoUrl, integrations); + return `${owner}/${repo}`; + }, + }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts index 543c25ab3e..2e036abc19 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts @@ -29,7 +29,7 @@ import globby from 'globby'; import fs from 'fs-extra'; import { isBinaryFile } from 'isbinaryfile'; import { SecureTemplater } from '../../../../lib/templating/SecureTemplater'; -import createDefaultFilters from '../../../../lib/templating/filters'; +import { createDefaultFilters } from '../../../../lib/templating/filters'; import { examples } from './template.examples'; import { convertFiltersToRecord } from '../../../../util/templating'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts index f3b98d148a..9353f5e8d2 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts @@ -25,7 +25,7 @@ import { TemplateGlobal, } from '@backstage/plugin-scaffolder-node'; import { SecureTemplater } from '../../../../lib/templating/SecureTemplater'; -import createDefaultFilters from '../../../../lib/templating/filters'; +import { createDefaultFilters } from '../../../../lib/templating/filters'; import path from 'path'; import fs from 'fs-extra'; import { convertFiltersToRecord } from '../../../../util/templating'; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index b771c5ded1..e918511354 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -55,7 +55,7 @@ import { TemplateFilter, TemplateGlobal, } from '@backstage/plugin-scaffolder-node'; -import createDefaultFilters from '../../lib/templating/filters'; +import { createDefaultFilters } from '../../lib/templating/filters'; import { scaffolderActionRules } from '../../service/rules'; import { createCounterMetric, createHistogramMetric } from '../../util/metrics'; import { BackstageLoggerTransport, WinstonLogger } from './logger'; diff --git a/plugins/scaffolder-backend/src/util/templating.test.ts b/plugins/scaffolder-backend/src/util/templating.test.ts index 5ac6db601b..51493f08a1 100644 --- a/plugins/scaffolder-backend/src/util/templating.test.ts +++ b/plugins/scaffolder-backend/src/util/templating.test.ts @@ -28,14 +28,14 @@ import { extractGlobalValueMetadata, } from './templating'; import { JsonValue } from '@backstage/types'; -import builtInFilters from '../lib/templating/filters'; +import { createDefaultFilters } from '../lib/templating/filters'; import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; describe('templating utilities', () => { describe('built-in filters', () => { const integrations = ScmIntegrations.fromConfig(new ConfigReader({})); - const filters = builtInFilters({ integrations }); + const filters = createDefaultFilters({ integrations }); it('generates equivalent filter metadata', () => { const metadata = extractFilterMetadata(filters); expect(metadata).toMatchObject(extractFilterMetadata(filters)); From d48525c9f8bda08943c5e07909e9225116d275e7 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Wed, 19 Mar 2025 08:41:50 +0100 Subject: [PATCH 3/5] chore: fix type inference with different ways to define functions Signed-off-by: benjdlambert --- plugins/scaffolder-backend/report.api.md | 2 +- .../src/ScaffolderPlugin.ts | 2 +- .../templating/filters/parseRepoUrl/index.ts | 2 +- .../templating/filters/projectSlug/index.ts | 2 +- .../scaffolder-backend/src/service/router.ts | 2 +- .../src/util/templating.test.ts | 4 +- .../scaffolder-backend/src/util/templating.ts | 17 +-- plugins/scaffolder-node/report-alpha.api.md | 108 +++++++----------- .../src/alpha/filters/createTemplateFilter.ts | 23 ++-- .../src/alpha/filters/types.ts | 46 +++----- .../src/alpha/globals/createTemplateGlobal.ts | 20 ++-- .../src/alpha/globals/types.ts | 35 +----- plugins/scaffolder-node/src/alpha/index.ts | 4 +- 13 files changed, 108 insertions(+), 159 deletions(-) diff --git a/plugins/scaffolder-backend/report.api.md b/plugins/scaffolder-backend/report.api.md index 37afe780a9..c3100d8259 100644 --- a/plugins/scaffolder-backend/report.api.md +++ b/plugins/scaffolder-backend/report.api.md @@ -529,7 +529,7 @@ export interface RouterOptions { // (undocumented) additionalTemplateFilters?: | Record - | CreatedTemplateFilter[]; + | CreatedTemplateFilter[]; // (undocumented) additionalTemplateGlobals?: | Record diff --git a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts index 4882b3cb2e..9fe78c7119 100644 --- a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts +++ b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts @@ -82,7 +82,7 @@ export const scaffolderPlugin = createBackendPlugin({ }, }); - const additionalTemplateFilters: CreatedTemplateFilter[] = []; + const additionalTemplateFilters: CreatedTemplateFilter[] = []; const additionalTemplateGlobals: CreatedTemplateGlobal[] = []; env.registerExtensionPoint(scaffolderTemplatingExtensionPoint, { diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/index.ts b/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/index.ts index a726c140cf..1c65852bbb 100644 --- a/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/index.ts +++ b/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/index.ts @@ -46,5 +46,5 @@ export const createParseRepoUrl = (integrations: ScmIntegrations) => .describe('`RepoSpec`'), ), examples, - filter: (url: string) => parseRepoUrl(url, integrations), + filter: url => parseRepoUrl(url, integrations), }); diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/index.ts b/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/index.ts index a833121423..05bb6d61cd 100644 --- a/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/index.ts +++ b/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/index.ts @@ -30,7 +30,7 @@ export const createProjectSlug = (integrations: ScmIntegrations) => z.string(), ), examples, - filter: (repoUrl: string) => { + filter: repoUrl => { const { owner, repo } = parseRepoUrl(repoUrl, integrations); return `${owner}/${repo}`; }, diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 1186e7d4f6..8258d8c36f 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -179,7 +179,7 @@ export interface RouterOptions { taskBroker?: TaskBroker; additionalTemplateFilters?: | Record - | CreatedTemplateFilter[]; + | CreatedTemplateFilter[]; additionalTemplateGlobals?: | Record | CreatedTemplateGlobal[]; diff --git a/plugins/scaffolder-backend/src/util/templating.test.ts b/plugins/scaffolder-backend/src/util/templating.test.ts index 51493f08a1..f32e94f95c 100644 --- a/plugins/scaffolder-backend/src/util/templating.test.ts +++ b/plugins/scaffolder-backend/src/util/templating.test.ts @@ -64,7 +64,7 @@ describe('templating utilities', () => { z.string().describe('separator').optional(), ) .returns(z.string()), - filter: (input: string, times: number, separator?: string) => + filter: (input, times, separator) => Array(times) .fill(input) .join(separator ?? ''), @@ -149,7 +149,7 @@ describe('templating utilities', () => { id: 'respond', schema: z => z.function().args(z.string().describe('prompt')).returns(z.string()), - fn: (prompt: string) => + fn: prompt => prompt === 'knock knock' ? "who's there?" : "nobody's home", }), ]; diff --git a/plugins/scaffolder-backend/src/util/templating.ts b/plugins/scaffolder-backend/src/util/templating.ts index 1f97e98b83..2607d5bef6 100644 --- a/plugins/scaffolder-backend/src/util/templating.ts +++ b/plugins/scaffolder-backend/src/util/templating.ts @@ -22,7 +22,7 @@ import { CreatedTemplateGlobal, CreatedTemplateGlobalFunction, CreatedTemplateGlobalValue, - TemplateGlobalFunctionSchema, + ZodFunctionSchema, } from '@backstage/plugin-scaffolder-node/alpha'; import { JsonValue } from '@backstage/types'; import { Schema } from 'jsonschema'; @@ -63,8 +63,12 @@ type ExportFilterSchema = { * Converts a Zod function schema to JSON schema */ function convertZodFunctionToJsonSchema( - t: ReturnType>, + t: ReturnType>, ): ExportFunctionSchema { + if (!('parameters' in t) || !('returnType' in t)) { + throw new Error('Invalid Zod function schema'); + } + const args = (t.parameters().items as ZodType[]).map( zt => zodToJsonSchema(zt) as Schema, ); @@ -142,7 +146,9 @@ export function extractFilterMetadata( if (filter.schema) { metadata.schema = convertToFilterSchema( - convertZodFunctionToJsonSchema(filter.schema(z)), + convertZodFunctionToJsonSchema( + filter.schema(z) as z.ZodFunction, + ), ); } @@ -167,10 +173,7 @@ export function extractFilterMetadata( */ function isGlobalFunction( global: CreatedTemplateGlobal, -): global is CreatedTemplateGlobalFunction< - TemplateGlobalFunctionSchema | undefined, - any -> { +): global is CreatedTemplateGlobalFunction { return 'fn' in global; } diff --git a/plugins/scaffolder-node/report-alpha.api.md b/plugins/scaffolder-node/report-alpha.api.md index 151af9ea37..6ff02c7ad0 100644 --- a/plugins/scaffolder-node/report-alpha.api.md +++ b/plugins/scaffolder-node/report-alpha.api.md @@ -29,56 +29,31 @@ export type AutocompleteHandler = ({ // @alpha (undocumented) export type CreatedTemplateFilter< - TSchema extends - | TemplateFilterSchema - | undefined - | unknown = unknown, - TFilterSchema extends TSchema extends TemplateFilterSchema - ? z.infer> - : TSchema extends unknown - ? unknown - : TemplateFilter = TSchema extends TemplateFilterSchema - ? z.infer> - : TSchema extends unknown - ? unknown - : TemplateFilter, + TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], + TReturnType extends z.ZodTypeAny, > = { id: string; description?: string; examples?: TemplateFilterExample[]; - schema?: TSchema; - filter: TFilterSchema; + schema?: ZodFunctionSchema; + filter: (...args: z.infer>) => z.infer; }; // @alpha (undocumented) export type CreatedTemplateGlobal = | CreatedTemplateGlobalValue - | CreatedTemplateGlobalFunction; + | CreatedTemplateGlobalFunction; // @alpha (undocumented) export type CreatedTemplateGlobalFunction< - TSchema extends - | TemplateGlobalFunctionSchema - | undefined - | unknown = unknown, - TFilterSchema extends TSchema extends TemplateGlobalFunctionSchema - ? z.infer> - : TSchema extends unknown - ? unknown - : Exclude< - TemplateGlobal, - JsonValue - > = TSchema extends TemplateGlobalFunctionSchema - ? z.infer> - : TSchema extends unknown - ? unknown - : Exclude, + TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], + TReturnType extends z.ZodTypeAny, > = { id: string; description?: string; examples?: TemplateGlobalFunctionExample[]; - schema?: TSchema; - fn: TFilterSchema; + schema?: ZodFunctionSchema; + fn: (...args: z.infer>) => z.infer; }; // @alpha (undocumented) @@ -90,23 +65,27 @@ export type CreatedTemplateGlobalValue = { // @alpha export const createTemplateFilter: < - TSchema extends TemplateFilterSchema | undefined, - TFilterSchema extends TSchema extends TemplateFilterSchema - ? z.infer> - : (arg: JsonValue, ...rest: JsonValue[]) => JsonValue | undefined, ->( - filter: CreatedTemplateFilter, -) => CreatedTemplateFilter; + TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], + TReturnType extends z.ZodTypeAny, +>(options: { + id: string; + description?: string; + examples?: TemplateFilterExample[]; + schema?: ZodFunctionSchema; + filter: (...args: z.infer>) => z.infer; +}) => CreatedTemplateFilter; // @alpha export const createTemplateGlobalFunction: < - TSchema extends TemplateGlobalFunctionSchema | undefined, - TFilterSchema extends TSchema extends TemplateGlobalFunctionSchema - ? z.infer> - : (...args: JsonValue[]) => JsonValue | undefined, ->( - fn: CreatedTemplateGlobalFunction, -) => CreatedTemplateGlobalFunction; + TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], + TReturnType extends z.ZodTypeAny, +>(options: { + id: string; + description?: string; + examples?: TemplateGlobalFunctionExample[]; + schema?: ZodFunctionSchema; + fn: (...args: z.infer>) => z.infer; +}) => CreatedTemplateGlobalFunction; // @alpha export const createTemplateGlobalValue: ( @@ -158,7 +137,7 @@ export interface ScaffolderTemplatingExtensionPoint { addTemplateFilters( filters: | Record - | CreatedTemplateFilter[], + | CreatedTemplateFilter[], ): void; // (undocumented) addTemplateGlobals( @@ -196,16 +175,6 @@ export type TemplateFilterExample = { notes?: string; }; -// @alpha (undocumented) -export type TemplateFilterSchema< - Args extends z.ZodTuple< - | [z.ZodType] - | [z.ZodType, ...(z.ZodType | z.ZodUnknown)[]], - z.ZodType | z.ZodUnknown | null - >, - Result extends z.ZodType | z.ZodUndefined, -> = (zod: typeof z) => z.ZodFunction; - // @public (undocumented) export type TemplateGlobal = | ((...args: JsonValue[]) => JsonValue | undefined) @@ -218,15 +187,6 @@ export type TemplateGlobalFunctionExample = { notes?: string; }; -// @alpha (undocumented) -export type TemplateGlobalFunctionSchema< - Args extends z.ZodTuple< - [] | [z.ZodType, ...(z.ZodType | z.ZodUnknown)[]], - z.ZodType | z.ZodUnknown | null - >, - Result extends z.ZodType | z.ZodUndefined, -> = (zod: typeof z) => z.ZodFunction; - // @alpha export interface WorkspaceProvider { // (undocumented) @@ -246,5 +206,17 @@ export interface WorkspaceProvider { }): Promise; } +// @alpha +export type ZodFunctionSchema< + TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], + TReturnType extends z.ZodTypeAny, +> = ( + zod: typeof z, +) => + | z.ZodFunction, TReturnType> + | z.ZodType< + (...args: z.infer>) => z.infer + >; + // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/scaffolder-node/src/alpha/filters/createTemplateFilter.ts b/plugins/scaffolder-node/src/alpha/filters/createTemplateFilter.ts index 219884b704..03fb7fd18e 100644 --- a/plugins/scaffolder-node/src/alpha/filters/createTemplateFilter.ts +++ b/plugins/scaffolder-node/src/alpha/filters/createTemplateFilter.ts @@ -14,8 +14,11 @@ * limitations under the License. */ -import { JsonValue } from '@backstage/types'; -import { CreatedTemplateFilter, TemplateFilterSchema } from './types'; +import { + CreatedTemplateFilter, + TemplateFilterExample, + ZodFunctionSchema, +} from './types'; import { z } from 'zod'; /** @@ -23,10 +26,12 @@ import { z } from 'zod'; * @alpha */ export const createTemplateFilter = < - TSchema extends TemplateFilterSchema | undefined, - TFilterSchema extends TSchema extends TemplateFilterSchema - ? z.infer> - : (arg: JsonValue, ...rest: JsonValue[]) => JsonValue | undefined, ->( - filter: CreatedTemplateFilter, -): CreatedTemplateFilter => filter; + TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], + TReturnType extends z.ZodTypeAny, +>(options: { + id: string; + description?: string; + examples?: TemplateFilterExample[]; + schema?: ZodFunctionSchema; + filter: (...args: z.infer>) => z.infer; +}): CreatedTemplateFilter => options; diff --git a/plugins/scaffolder-node/src/alpha/filters/types.ts b/plugins/scaffolder-node/src/alpha/filters/types.ts index 2f65baaefc..741d4e2ca7 100644 --- a/plugins/scaffolder-node/src/alpha/filters/types.ts +++ b/plugins/scaffolder-node/src/alpha/filters/types.ts @@ -14,21 +14,9 @@ * 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] - | [z.ZodType, ...(z.ZodType | z.ZodUnknown)[]], - z.ZodType | z.ZodUnknown | null - >, - Result extends z.ZodType | z.ZodUndefined, -> = (zod: typeof z) => z.ZodFunction; - /** @alpha */ export type TemplateFilterExample = { description?: string; @@ -36,25 +24,29 @@ export type TemplateFilterExample = { notes?: string; }; +/** + * Type for template filter schema functions that supports both Zod function definition methods: + * @alpha + */ +export type ZodFunctionSchema< + TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], + TReturnType extends z.ZodTypeAny, +> = ( + zod: typeof z, +) => + | z.ZodFunction, TReturnType> + | z.ZodType< + (...args: z.infer>) => z.infer + >; + /** @alpha */ export type CreatedTemplateFilter< - TSchema extends - | TemplateFilterSchema - | undefined - | unknown = unknown, - TFilterSchema extends TSchema extends TemplateFilterSchema - ? z.infer> - : TSchema extends unknown - ? unknown - : TemplateFilter = TSchema extends TemplateFilterSchema - ? z.infer> - : TSchema extends unknown - ? unknown - : TemplateFilter, + TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], + TReturnType extends z.ZodTypeAny, > = { id: string; description?: string; examples?: TemplateFilterExample[]; - schema?: TSchema; - filter: TFilterSchema; + schema?: ZodFunctionSchema; + filter: (...args: z.infer>) => z.infer; }; diff --git a/plugins/scaffolder-node/src/alpha/globals/createTemplateGlobal.ts b/plugins/scaffolder-node/src/alpha/globals/createTemplateGlobal.ts index 2361a50964..a277363f3a 100644 --- a/plugins/scaffolder-node/src/alpha/globals/createTemplateGlobal.ts +++ b/plugins/scaffolder-node/src/alpha/globals/createTemplateGlobal.ts @@ -18,9 +18,9 @@ import { z } from 'zod'; import { CreatedTemplateGlobalFunction, CreatedTemplateGlobalValue, - TemplateGlobalFunctionSchema, + TemplateGlobalFunctionExample, } from './types'; -import { JsonValue } from '@backstage/types'; +import { ZodFunctionSchema } from '../filters'; /** * This function is used to create new template global values in type-safe manner. @@ -39,10 +39,12 @@ export const createTemplateGlobalValue = ( * @alpha */ export const createTemplateGlobalFunction = < - TSchema extends TemplateGlobalFunctionSchema | undefined, - TFilterSchema extends TSchema extends TemplateGlobalFunctionSchema - ? z.infer> - : (...args: JsonValue[]) => JsonValue | undefined, ->( - fn: CreatedTemplateGlobalFunction, -): CreatedTemplateGlobalFunction => fn; + TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], + TReturnType extends z.ZodTypeAny, +>(options: { + id: string; + description?: string; + examples?: TemplateGlobalFunctionExample[]; + schema?: ZodFunctionSchema; + fn: (...args: z.infer>) => z.infer; +}): CreatedTemplateGlobalFunction => options; diff --git a/plugins/scaffolder-node/src/alpha/globals/types.ts b/plugins/scaffolder-node/src/alpha/globals/types.ts index cbe74f1610..4754bcaa88 100644 --- a/plugins/scaffolder-node/src/alpha/globals/types.ts +++ b/plugins/scaffolder-node/src/alpha/globals/types.ts @@ -15,7 +15,7 @@ */ import { JsonValue } from '@backstage/types'; import { z } from 'zod'; -import { TemplateGlobal } from '../../types'; +import { ZodFunctionSchema } from '../filters/types'; export type { TemplateGlobal } from '../../types'; @@ -26,15 +26,6 @@ export type CreatedTemplateGlobalValue = { description?: string; }; -/** @alpha */ -export type TemplateGlobalFunctionSchema< - Args extends z.ZodTuple< - [] | [z.ZodType, ...(z.ZodType | z.ZodUnknown)[]], - z.ZodType | z.ZodUnknown | null - >, - Result extends z.ZodType | z.ZodUndefined, -> = (zod: typeof z) => z.ZodFunction; - /** @alpha */ export type TemplateGlobalFunctionExample = { description?: string; @@ -44,31 +35,17 @@ export type TemplateGlobalFunctionExample = { /** @alpha */ export type CreatedTemplateGlobalFunction< - TSchema extends - | TemplateGlobalFunctionSchema - | undefined - | unknown = unknown, - TFilterSchema extends TSchema extends TemplateGlobalFunctionSchema - ? z.infer> - : TSchema extends unknown - ? unknown - : Exclude< - TemplateGlobal, - JsonValue - > = TSchema extends TemplateGlobalFunctionSchema - ? z.infer> - : TSchema extends unknown - ? unknown - : Exclude, + TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], + TReturnType extends z.ZodTypeAny, > = { id: string; description?: string; examples?: TemplateGlobalFunctionExample[]; - schema?: TSchema; - fn: TFilterSchema; + schema?: ZodFunctionSchema; + fn: (...args: z.infer>) => z.infer; }; /** @alpha */ export type CreatedTemplateGlobal = | CreatedTemplateGlobalValue - | CreatedTemplateGlobalFunction; + | CreatedTemplateGlobalFunction; diff --git a/plugins/scaffolder-node/src/alpha/index.ts b/plugins/scaffolder-node/src/alpha/index.ts index 1fd5abee18..eb97447a5c 100644 --- a/plugins/scaffolder-node/src/alpha/index.ts +++ b/plugins/scaffolder-node/src/alpha/index.ts @@ -73,9 +73,7 @@ export const scaffolderTaskBrokerExtensionPoint = */ export interface ScaffolderTemplatingExtensionPoint { addTemplateFilters( - filters: - | Record - | CreatedTemplateFilter[], + filters: Record | CreatedTemplateFilter[], ): void; addTemplateGlobals( From 3cfc7f06d17f04e5d9916b3154857aa3a55a71c3 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Fri, 21 Mar 2025 11:07:00 +0100 Subject: [PATCH 4/5] feat: small refactor to move some logic out of the index files and move to using `.args()` and `.returns()` as it's cleaner Signed-off-by: benjdlambert --- .../{index.ts => createDefaultFilters.ts} | 0 .../filters/parseEntityRef/examples.ts | 4 +- .../filters/parseEntityRef/filter.ts | 63 +++++++++++++++++++ .../filters/parseEntityRef/index.ts | 47 +------------- .../templating/filters/parseRepoUrl/filter.ts | 52 +++++++++++++++ .../templating/filters/parseRepoUrl/index.ts | 36 +---------- .../src/lib/templating/filters/pick/filter.ts | 31 +++++++++ .../src/lib/templating/filters/pick/index.ts | 17 +---- .../templating/filters/projectSlug/filter.ts | 37 +++++++++++ .../templating/filters/projectSlug/index.ts | 23 +------ .../actions/builtin/fetch/template.ts | 2 +- .../actions/builtin/fetch/templateFile.ts | 2 +- .../tasks/NunjucksWorkflowRunner.ts | 2 +- .../src/util/templating.test.ts | 2 +- plugins/scaffolder-node/report-alpha.api.md | 2 +- .../src/alpha/filters/createTemplateFilter.ts | 7 +-- .../src/alpha/filters/types.ts | 16 +---- .../src/alpha/globals/createTemplateGlobal.ts | 2 +- .../src/alpha/globals/types.ts | 2 +- plugins/scaffolder-node/src/alpha/index.ts | 1 + plugins/scaffolder-node/src/alpha/types.ts | 30 +++++++++ 21 files changed, 230 insertions(+), 148 deletions(-) rename plugins/scaffolder-backend/src/lib/templating/filters/{index.ts => createDefaultFilters.ts} (100%) create mode 100644 plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/filter.ts create mode 100644 plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/filter.ts create mode 100644 plugins/scaffolder-backend/src/lib/templating/filters/pick/filter.ts create mode 100644 plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/filter.ts create mode 100644 plugins/scaffolder-node/src/alpha/types.ts diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/index.ts b/plugins/scaffolder-backend/src/lib/templating/filters/createDefaultFilters.ts similarity index 100% rename from plugins/scaffolder-backend/src/lib/templating/filters/index.ts rename to plugins/scaffolder-backend/src/lib/templating/filters/createDefaultFilters.ts diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/examples.ts b/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/examples.ts index fd6219c998..aef5b451bb 100644 --- a/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/examples.ts +++ b/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/examples.ts @@ -1,5 +1,3 @@ -import { TemplateFilterExample } from '@backstage/plugin-scaffolder-node/alpha'; - /* * Copyright 2025 The Backstage Authors * @@ -15,6 +13,8 @@ import { TemplateFilterExample } from '@backstage/plugin-scaffolder-node/alpha'; * See the License for the specific language governing permissions and * limitations under the License. */ +import { TemplateFilterExample } from '@backstage/plugin-scaffolder-node/alpha'; + export const examples: TemplateFilterExample[] = [ { description: 'Without context', diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/filter.ts b/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/filter.ts new file mode 100644 index 0000000000..23ce340370 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/filter.ts @@ -0,0 +1,63 @@ +/* + * 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 { parseEntityRef as filter } from '@backstage/catalog-model'; +import { createTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha'; +import { examples } from './examples'; + +export const parseEntityRef = createTemplateFilter({ + id: 'parseEntityRef', + description: + 'Extracts the parts of an entity reference, such as the kind, namespace, and name.', + schema: z => + z + .function() + .args( + z.union([ + z.string().describe('compact entity reference'), + z + .object({ + kind: z.string().optional(), + namespace: z.string().optional(), + name: z.string(), + }) + .describe('`CompoundEntityRef`'), + ]), + z + .object({ + defaultKind: z + .string() + .describe('The default kind, if none is given in the reference'), + defaultNamespace: z + .string() + .describe( + 'The default namespace, if none is given in the reference', + ), + }) + .partial() + .optional(), + ) + .returns( + z + .object({ + kind: z.string(), + namespace: z.string(), + name: z.string(), + }) + .describe('`CompoundEntityRef`'), + ), + examples, + filter, +}); diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/index.ts b/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/index.ts index 221be44055..b11d925161 100644 --- a/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/index.ts +++ b/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/index.ts @@ -13,49 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { parseEntityRef as filter } from '@backstage/catalog-model'; -import { createTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha'; -import { examples } from './examples'; - -export const parseEntityRef = createTemplateFilter({ - id: 'parseEntityRef', - description: - 'Extracts the parts of an entity reference, such as the kind, namespace, and name.', - schema: z => - z.function( - z.tuple([ - z.union([ - z.string().describe('compact entity reference'), - z - .object({ - kind: z.string().optional(), - namespace: z.string().optional(), - name: z.string(), - }) - .describe('`CompoundEntityRef`'), - ]), - z - .object({ - defaultKind: z - .string() - .describe('The default kind, if none is given in the reference'), - defaultNamespace: z - .string() - .describe( - 'The default namespace, if none is given in the reference', - ), - }) - .partial() - .optional(), - ]), - z - .object({ - kind: z.string(), - namespace: z.string(), - name: z.string(), - }) - .describe('`CompoundEntityRef`'), - ), - examples, - filter, -}); +export { parseEntityRef } from './filter'; diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/filter.ts b/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/filter.ts new file mode 100644 index 0000000000..8a7daea3bc --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/filter.ts @@ -0,0 +1,52 @@ +/* + * 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 { ScmIntegrations } from '@backstage/integration'; +import { parseRepoUrl } from '@backstage/plugin-scaffolder-node'; +import { createTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha'; +import { examples } from './examples'; + +export const createParseRepoUrl = (integrations: ScmIntegrations) => + createTemplateFilter({ + id: 'parseRepoUrl', + description: + 'Parses a repository URL into its constituent parts: owner, repository name, etc.', + schema: z => + z + .function() + .args( + z.string().describe('repo URL as collected from repository picker'), + ) + .returns( + z + .object({ + repo: z.string(), + host: z.string(), + }) + .merge( + z + .object({ + owner: z.string(), + organization: z.string(), + workspace: z.string(), + project: z.string(), + }) + .partial(), + ) + .describe('`RepoSpec`'), + ), + examples, + filter: url => parseRepoUrl(url, integrations), + }); diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/index.ts b/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/index.ts index 1c65852bbb..ed505315b5 100644 --- a/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/index.ts +++ b/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/index.ts @@ -13,38 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ScmIntegrations } from '@backstage/integration'; -import { parseRepoUrl } from '@backstage/plugin-scaffolder-node'; -import { createTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha'; -import { examples } from './examples'; - -export const createParseRepoUrl = (integrations: ScmIntegrations) => - createTemplateFilter({ - id: 'parseRepoUrl', - description: - 'Parses a repository URL into its constituent parts: owner, repository name, etc.', - schema: z => - z.function( - z.tuple([ - z.string().describe('repo URL as collected from repository picker'), - ]), - z - .object({ - repo: z.string(), - host: z.string(), - }) - .merge( - z - .object({ - owner: z.string(), - organization: z.string(), - workspace: z.string(), - project: z.string(), - }) - .partial(), - ) - .describe('`RepoSpec`'), - ), - examples, - filter: url => parseRepoUrl(url, integrations), - }); +export { createParseRepoUrl } from './filter'; diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/pick/filter.ts b/plugins/scaffolder-backend/src/lib/templating/filters/pick/filter.ts new file mode 100644 index 0000000000..a38855becc --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/pick/filter.ts @@ -0,0 +1,31 @@ +/* + * 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 { createTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha'; +import { get as filter } from 'lodash'; +import { examples } from './examples'; + +export const pick = createTemplateFilter({ + id: 'pick', + description: + 'Selects a specific property (e.g. kind, namespace, name) from an object.', + schema: z => + z + .function() + .args(z.any(), z.string().describe('Property')) + .returns(z.any().describe('Selected property')), + examples, + filter, +}); diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/pick/index.ts b/plugins/scaffolder-backend/src/lib/templating/filters/pick/index.ts index 5d8a0d664a..b3ca69bc38 100644 --- a/plugins/scaffolder-backend/src/lib/templating/filters/pick/index.ts +++ b/plugins/scaffolder-backend/src/lib/templating/filters/pick/index.ts @@ -13,19 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha'; -import { get as filter } from 'lodash'; -import { examples } from './examples'; - -export const pick = createTemplateFilter({ - id: 'pick', - description: - 'Selects a specific property (e.g. kind, namespace, name) from an object.', - schema: z => - z.function( - z.tuple([z.any(), z.string().describe('Property')]), - z.any().describe('Selected property'), - ), - examples, - filter, -}); +export { pick } from './filter'; diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/filter.ts b/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/filter.ts new file mode 100644 index 0000000000..f6d0eaf681 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/filter.ts @@ -0,0 +1,37 @@ +/* + * 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 { ScmIntegrations } from '@backstage/integration'; +import { parseRepoUrl } from '@backstage/plugin-scaffolder-node'; +import { createTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha'; +import { examples } from './examples'; + +export const createProjectSlug = (integrations: ScmIntegrations) => + createTemplateFilter({ + id: 'projectSlug', + description: 'Generates a project slug from a repository URL.', + schema: z => + z + .function() + .args( + z.string().describe('repo URL as collected from repository picker'), + ) + .returns(z.string()), + examples, + filter: repoUrl => { + const { owner, repo } = parseRepoUrl(repoUrl, integrations); + return `${owner}/${repo}`; + }, + }); diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/index.ts b/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/index.ts index 05bb6d61cd..71a092232c 100644 --- a/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/index.ts +++ b/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/index.ts @@ -13,25 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ScmIntegrations } from '@backstage/integration'; -import { parseRepoUrl } from '@backstage/plugin-scaffolder-node'; -import { createTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha'; -import { examples } from './examples'; - -export const createProjectSlug = (integrations: ScmIntegrations) => - createTemplateFilter({ - id: 'projectSlug', - description: 'Generates a project slug from a repository URL.', - schema: z => - z.function( - z.tuple([ - z.string().describe('repo URL as collected from repository picker'), - ]), - z.string(), - ), - examples, - filter: repoUrl => { - const { owner, repo } = parseRepoUrl(repoUrl, integrations); - return `${owner}/${repo}`; - }, - }); +export { createProjectSlug } from './filter'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts index 2e036abc19..8dfa26057b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts @@ -29,7 +29,7 @@ import globby from 'globby'; import fs from 'fs-extra'; import { isBinaryFile } from 'isbinaryfile'; import { SecureTemplater } from '../../../../lib/templating/SecureTemplater'; -import { createDefaultFilters } from '../../../../lib/templating/filters'; +import { createDefaultFilters } from '../../../../lib/templating/filters/createDefaultFilters'; import { examples } from './template.examples'; import { convertFiltersToRecord } from '../../../../util/templating'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts index 9353f5e8d2..2cd4ce883c 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts @@ -25,7 +25,7 @@ import { TemplateGlobal, } from '@backstage/plugin-scaffolder-node'; import { SecureTemplater } from '../../../../lib/templating/SecureTemplater'; -import { createDefaultFilters } from '../../../../lib/templating/filters'; +import { createDefaultFilters } from '../../../../lib/templating/filters/createDefaultFilters'; import path from 'path'; import fs from 'fs-extra'; import { convertFiltersToRecord } from '../../../../util/templating'; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index e918511354..c352714d9d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -55,7 +55,7 @@ import { TemplateFilter, TemplateGlobal, } from '@backstage/plugin-scaffolder-node'; -import { createDefaultFilters } from '../../lib/templating/filters'; +import { createDefaultFilters } from '../../lib/templating/filters/createDefaultFilters'; import { scaffolderActionRules } from '../../service/rules'; import { createCounterMetric, createHistogramMetric } from '../../util/metrics'; import { BackstageLoggerTransport, WinstonLogger } from './logger'; diff --git a/plugins/scaffolder-backend/src/util/templating.test.ts b/plugins/scaffolder-backend/src/util/templating.test.ts index f32e94f95c..61d8637016 100644 --- a/plugins/scaffolder-backend/src/util/templating.test.ts +++ b/plugins/scaffolder-backend/src/util/templating.test.ts @@ -28,7 +28,7 @@ import { extractGlobalValueMetadata, } from './templating'; import { JsonValue } from '@backstage/types'; -import { createDefaultFilters } from '../lib/templating/filters'; +import { createDefaultFilters } from '../lib/templating/filters/createDefaultFilters'; import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; diff --git a/plugins/scaffolder-node/report-alpha.api.md b/plugins/scaffolder-node/report-alpha.api.md index 6ff02c7ad0..a96f312d2d 100644 --- a/plugins/scaffolder-node/report-alpha.api.md +++ b/plugins/scaffolder-node/report-alpha.api.md @@ -206,7 +206,7 @@ export interface WorkspaceProvider { }): Promise; } -// @alpha +// @alpha (undocumented) export type ZodFunctionSchema< TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], TReturnType extends z.ZodTypeAny, diff --git a/plugins/scaffolder-node/src/alpha/filters/createTemplateFilter.ts b/plugins/scaffolder-node/src/alpha/filters/createTemplateFilter.ts index 03fb7fd18e..f648658588 100644 --- a/plugins/scaffolder-node/src/alpha/filters/createTemplateFilter.ts +++ b/plugins/scaffolder-node/src/alpha/filters/createTemplateFilter.ts @@ -14,11 +14,8 @@ * limitations under the License. */ -import { - CreatedTemplateFilter, - TemplateFilterExample, - ZodFunctionSchema, -} from './types'; +import { ZodFunctionSchema } from '../types'; +import { CreatedTemplateFilter, TemplateFilterExample } from './types'; import { z } from 'zod'; /** diff --git a/plugins/scaffolder-node/src/alpha/filters/types.ts b/plugins/scaffolder-node/src/alpha/filters/types.ts index 741d4e2ca7..1906ba1c9c 100644 --- a/plugins/scaffolder-node/src/alpha/filters/types.ts +++ b/plugins/scaffolder-node/src/alpha/filters/types.ts @@ -14,6 +14,7 @@ * limitations under the License. */ import { z } from 'zod'; +import { ZodFunctionSchema } from '../types'; export type { TemplateFilter } from '../../types'; @@ -24,21 +25,6 @@ export type TemplateFilterExample = { notes?: string; }; -/** - * Type for template filter schema functions that supports both Zod function definition methods: - * @alpha - */ -export type ZodFunctionSchema< - TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], - TReturnType extends z.ZodTypeAny, -> = ( - zod: typeof z, -) => - | z.ZodFunction, TReturnType> - | z.ZodType< - (...args: z.infer>) => z.infer - >; - /** @alpha */ export type CreatedTemplateFilter< TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], diff --git a/plugins/scaffolder-node/src/alpha/globals/createTemplateGlobal.ts b/plugins/scaffolder-node/src/alpha/globals/createTemplateGlobal.ts index a277363f3a..2687b83bb4 100644 --- a/plugins/scaffolder-node/src/alpha/globals/createTemplateGlobal.ts +++ b/plugins/scaffolder-node/src/alpha/globals/createTemplateGlobal.ts @@ -20,7 +20,7 @@ import { CreatedTemplateGlobalValue, TemplateGlobalFunctionExample, } from './types'; -import { ZodFunctionSchema } from '../filters'; +import { ZodFunctionSchema } from '../types'; /** * This function is used to create new template global values in type-safe manner. diff --git a/plugins/scaffolder-node/src/alpha/globals/types.ts b/plugins/scaffolder-node/src/alpha/globals/types.ts index 4754bcaa88..27a3a700e5 100644 --- a/plugins/scaffolder-node/src/alpha/globals/types.ts +++ b/plugins/scaffolder-node/src/alpha/globals/types.ts @@ -15,7 +15,7 @@ */ import { JsonValue } from '@backstage/types'; import { z } from 'zod'; -import { ZodFunctionSchema } from '../filters/types'; +import { ZodFunctionSchema } from '../types'; export type { TemplateGlobal } from '../../types'; diff --git a/plugins/scaffolder-node/src/alpha/index.ts b/plugins/scaffolder-node/src/alpha/index.ts index eb97447a5c..9df968a52d 100644 --- a/plugins/scaffolder-node/src/alpha/index.ts +++ b/plugins/scaffolder-node/src/alpha/index.ts @@ -27,6 +27,7 @@ import { CreatedTemplateGlobal } from './globals'; export * from '../tasks/alpha'; export * from './filters'; export * from './globals'; +export * from './types'; /** * Extension point for managing scaffolder actions. diff --git a/plugins/scaffolder-node/src/alpha/types.ts b/plugins/scaffolder-node/src/alpha/types.ts new file mode 100644 index 0000000000..ca52973775 --- /dev/null +++ b/plugins/scaffolder-node/src/alpha/types.ts @@ -0,0 +1,30 @@ +/* + * 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'; + +/** + * @alpha + */ +export type ZodFunctionSchema< + TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], + TReturnType extends z.ZodTypeAny, +> = ( + zod: typeof z, +) => + | z.ZodFunction, TReturnType> + | z.ZodType< + (...args: z.infer>) => z.infer + >; From 95b352ebaccf53883f3b4cb6625546787315ef0d Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Fri, 21 Mar 2025 11:08:53 +0100 Subject: [PATCH 5/5] chore: update changeset Signed-off-by: benjdlambert Signed-off-by: benjdlambert --- .changeset/odd-bobcats-hang.md | 3 ++- .../src/lib/templating/filters/projectSlug/examples.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.changeset/odd-bobcats-hang.md b/.changeset/odd-bobcats-hang.md index a7c0d59754..9b3aeccffc 100644 --- a/.changeset/odd-bobcats-hang.md +++ b/.changeset/odd-bobcats-hang.md @@ -1,5 +1,6 @@ --- '@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-scaffolder-node': patch --- -document built-in template filters +Document the internal built-in filters, and ensure that the types are validated when using `createTemplateFilter` and `createTemplateGlobalFunction` from the `zod` schema. diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/examples.ts b/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/examples.ts index dde36ea888..2d668baee4 100644 --- a/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/examples.ts +++ b/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/examples.ts @@ -1,5 +1,3 @@ -import { TemplateFilterExample } from '@backstage/plugin-scaffolder-node/alpha'; - /* * Copyright 2025 The Backstage Authors * @@ -15,6 +13,8 @@ import { TemplateFilterExample } from '@backstage/plugin-scaffolder-node/alpha'; * See the License for the specific language governing permissions and * limitations under the License. */ +import { TemplateFilterExample } from '@backstage/plugin-scaffolder-node/alpha'; + export const examples: TemplateFilterExample[] = [ { example: `\