Merge pull request #29089 from mbenson/scaffolder-template-extensions-stage3
document built-in template filters
This commit is contained in:
@@ -529,7 +529,7 @@ export interface RouterOptions {
|
||||
// (undocumented)
|
||||
additionalTemplateFilters?:
|
||||
| Record<string, TemplateFilter_2>
|
||||
| CreatedTemplateFilter[];
|
||||
| CreatedTemplateFilter<any, any>[];
|
||||
// (undocumented)
|
||||
additionalTemplateGlobals?:
|
||||
| Record<string, TemplateGlobal_2>
|
||||
|
||||
@@ -82,7 +82,7 @@ export const scaffolderPlugin = createBackendPlugin({
|
||||
},
|
||||
});
|
||||
|
||||
const additionalTemplateFilters: CreatedTemplateFilter[] = [];
|
||||
const additionalTemplateFilters: CreatedTemplateFilter<any, any>[] = [];
|
||||
const additionalTemplateGlobals: CreatedTemplateGlobal[] = [];
|
||||
|
||||
env.registerExtensionPoint(scaffolderTemplatingExtensionPoint, {
|
||||
|
||||
@@ -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<string, TemplateFilter> => {
|
||||
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}`;
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -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),
|
||||
];
|
||||
@@ -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 { TemplateFilterExample } from '@backstage/plugin-scaffolder-node/alpha';
|
||||
|
||||
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"}\`
|
||||
`,
|
||||
},
|
||||
];
|
||||
@@ -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,
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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 { parseEntityRef } from './filter';
|
||||
@@ -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"}\`
|
||||
`,
|
||||
},
|
||||
];
|
||||
@@ -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),
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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 { createParseRepoUrl } from './filter';
|
||||
@@ -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\`
|
||||
`,
|
||||
},
|
||||
];
|
||||
@@ -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,
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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 { pick } from './filter';
|
||||
@@ -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 { TemplateFilterExample } from '@backstage/plugin-scaffolder-node/alpha';
|
||||
|
||||
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
|
||||
`,
|
||||
},
|
||||
];
|
||||
@@ -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}`;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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 { createProjectSlug } from './filter';
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -179,7 +179,7 @@ export interface RouterOptions {
|
||||
taskBroker?: TaskBroker;
|
||||
additionalTemplateFilters?:
|
||||
| Record<string, TemplateFilter>
|
||||
| CreatedTemplateFilter[];
|
||||
| CreatedTemplateFilter<any, any>[];
|
||||
additionalTemplateGlobals?:
|
||||
| Record<string, TemplateGlobal>
|
||||
| CreatedTemplateGlobal[];
|
||||
|
||||
@@ -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/createDefaultFilters';
|
||||
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));
|
||||
@@ -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",
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -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<TemplateGlobalFunctionSchema<any, any>>,
|
||||
t: ReturnType<ZodFunctionSchema<any, any>>,
|
||||
): 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<any, any>,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -167,10 +173,7 @@ export function extractFilterMetadata(
|
||||
*/
|
||||
function isGlobalFunction(
|
||||
global: CreatedTemplateGlobal,
|
||||
): global is CreatedTemplateGlobalFunction<
|
||||
TemplateGlobalFunctionSchema<any, any> | undefined,
|
||||
any
|
||||
> {
|
||||
): global is CreatedTemplateGlobalFunction<any, any> {
|
||||
return 'fn' in global;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,56 +29,31 @@ 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,
|
||||
TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]],
|
||||
TReturnType extends z.ZodTypeAny,
|
||||
> = {
|
||||
id: string;
|
||||
description?: string;
|
||||
examples?: TemplateFilterExample[];
|
||||
schema?: TSchema;
|
||||
filter: TFilterSchema;
|
||||
schema?: ZodFunctionSchema<TFunctionArgs, TReturnType>;
|
||||
filter: (...args: z.infer<z.ZodTuple<TFunctionArgs>>) => z.infer<TReturnType>;
|
||||
};
|
||||
|
||||
// @alpha (undocumented)
|
||||
export type CreatedTemplateGlobal =
|
||||
| CreatedTemplateGlobalValue
|
||||
| CreatedTemplateGlobalFunction<unknown, unknown>;
|
||||
| CreatedTemplateGlobalFunction<any, any>;
|
||||
|
||||
// @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>,
|
||||
TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]],
|
||||
TReturnType extends z.ZodTypeAny,
|
||||
> = {
|
||||
id: string;
|
||||
description?: string;
|
||||
examples?: TemplateGlobalFunctionExample[];
|
||||
schema?: TSchema;
|
||||
fn: TFilterSchema;
|
||||
schema?: ZodFunctionSchema<TFunctionArgs, TReturnType>;
|
||||
fn: (...args: z.infer<z.ZodTuple<TFunctionArgs>>) => z.infer<TReturnType>;
|
||||
};
|
||||
|
||||
// @alpha (undocumented)
|
||||
@@ -90,23 +65,27 @@ export type CreatedTemplateGlobalValue<T extends JsonValue = JsonValue> = {
|
||||
|
||||
// @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>;
|
||||
TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]],
|
||||
TReturnType extends z.ZodTypeAny,
|
||||
>(options: {
|
||||
id: string;
|
||||
description?: string;
|
||||
examples?: TemplateFilterExample[];
|
||||
schema?: ZodFunctionSchema<TFunctionArgs, TReturnType>;
|
||||
filter: (...args: z.infer<z.ZodTuple<TFunctionArgs>>) => z.infer<TReturnType>;
|
||||
}) => CreatedTemplateFilter<TFunctionArgs, TReturnType>;
|
||||
|
||||
// @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>;
|
||||
TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]],
|
||||
TReturnType extends z.ZodTypeAny,
|
||||
>(options: {
|
||||
id: string;
|
||||
description?: string;
|
||||
examples?: TemplateGlobalFunctionExample[];
|
||||
schema?: ZodFunctionSchema<TFunctionArgs, TReturnType>;
|
||||
fn: (...args: z.infer<z.ZodTuple<TFunctionArgs>>) => z.infer<TReturnType>;
|
||||
}) => CreatedTemplateGlobalFunction<TFunctionArgs, TReturnType>;
|
||||
|
||||
// @alpha
|
||||
export const createTemplateGlobalValue: (
|
||||
@@ -156,7 +135,9 @@ export const scaffolderTaskBrokerExtensionPoint: ExtensionPoint<ScaffolderTaskBr
|
||||
export interface ScaffolderTemplatingExtensionPoint {
|
||||
// (undocumented)
|
||||
addTemplateFilters(
|
||||
filters: Record<string, TemplateFilter_2> | CreatedTemplateFilter[],
|
||||
filters:
|
||||
| Record<string, TemplateFilter_2>
|
||||
| CreatedTemplateFilter<any, any>[],
|
||||
): void;
|
||||
// (undocumented)
|
||||
addTemplateGlobals(
|
||||
@@ -194,16 +175,6 @@ export type TemplateFilterExample = {
|
||||
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)
|
||||
@@ -216,15 +187,6 @@ export type TemplateGlobalFunctionExample = {
|
||||
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)
|
||||
@@ -244,5 +206,17 @@ export interface WorkspaceProvider {
|
||||
}): Promise<void>;
|
||||
}
|
||||
|
||||
// @alpha (undocumented)
|
||||
export type ZodFunctionSchema<
|
||||
TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]],
|
||||
TReturnType extends z.ZodTypeAny,
|
||||
> = (
|
||||
zod: typeof z,
|
||||
) =>
|
||||
| z.ZodFunction<z.ZodTuple<TFunctionArgs, null>, TReturnType>
|
||||
| z.ZodType<
|
||||
(...args: z.infer<z.ZodTuple<TFunctionArgs>>) => z.infer<TReturnType>
|
||||
>;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { CreatedTemplateFilter, TemplateFilterSchema } from './types';
|
||||
import { ZodFunctionSchema } from '../types';
|
||||
import { CreatedTemplateFilter, TemplateFilterExample } from './types';
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
@@ -23,10 +23,12 @@ import { z } from 'zod';
|
||||
* @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;
|
||||
TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]],
|
||||
TReturnType extends z.ZodTypeAny,
|
||||
>(options: {
|
||||
id: string;
|
||||
description?: string;
|
||||
examples?: TemplateFilterExample[];
|
||||
schema?: ZodFunctionSchema<TFunctionArgs, TReturnType>;
|
||||
filter: (...args: z.infer<z.ZodTuple<TFunctionArgs>>) => z.infer<TReturnType>;
|
||||
}): CreatedTemplateFilter<TFunctionArgs, TReturnType> => options;
|
||||
|
||||
@@ -14,21 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { z } from 'zod';
|
||||
import { TemplateFilter } from '../../types';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { ZodFunctionSchema } from '../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;
|
||||
@@ -38,23 +27,12 @@ export type TemplateFilterExample = {
|
||||
|
||||
/** @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,
|
||||
TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]],
|
||||
TReturnType extends z.ZodTypeAny,
|
||||
> = {
|
||||
id: string;
|
||||
description?: string;
|
||||
examples?: TemplateFilterExample[];
|
||||
schema?: TSchema;
|
||||
filter: TFilterSchema;
|
||||
schema?: ZodFunctionSchema<TFunctionArgs, TReturnType>;
|
||||
filter: (...args: z.infer<z.ZodTuple<TFunctionArgs>>) => z.infer<TReturnType>;
|
||||
};
|
||||
|
||||
@@ -18,9 +18,9 @@ import { z } from 'zod';
|
||||
import {
|
||||
CreatedTemplateGlobalFunction,
|
||||
CreatedTemplateGlobalValue,
|
||||
TemplateGlobalFunctionSchema,
|
||||
TemplateGlobalFunctionExample,
|
||||
} from './types';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { ZodFunctionSchema } from '../types';
|
||||
|
||||
/**
|
||||
* 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<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;
|
||||
TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]],
|
||||
TReturnType extends z.ZodTypeAny,
|
||||
>(options: {
|
||||
id: string;
|
||||
description?: string;
|
||||
examples?: TemplateGlobalFunctionExample[];
|
||||
schema?: ZodFunctionSchema<TFunctionArgs, TReturnType>;
|
||||
fn: (...args: z.infer<z.ZodTuple<TFunctionArgs>>) => z.infer<TReturnType>;
|
||||
}): CreatedTemplateGlobalFunction<TFunctionArgs, TReturnType> => options;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { z } from 'zod';
|
||||
import { TemplateGlobal } from '../../types';
|
||||
import { ZodFunctionSchema } from '../types';
|
||||
|
||||
export type { TemplateGlobal } from '../../types';
|
||||
|
||||
@@ -26,15 +26,6 @@ export type CreatedTemplateGlobalValue<T extends JsonValue = JsonValue> = {
|
||||
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;
|
||||
@@ -44,31 +35,17 @@ export type TemplateGlobalFunctionExample = {
|
||||
|
||||
/** @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>,
|
||||
TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]],
|
||||
TReturnType extends z.ZodTypeAny,
|
||||
> = {
|
||||
id: string;
|
||||
description?: string;
|
||||
examples?: TemplateGlobalFunctionExample[];
|
||||
schema?: TSchema;
|
||||
fn: TFilterSchema;
|
||||
schema?: ZodFunctionSchema<TFunctionArgs, TReturnType>;
|
||||
fn: (...args: z.infer<z.ZodTuple<TFunctionArgs>>) => z.infer<TReturnType>;
|
||||
};
|
||||
|
||||
/** @alpha */
|
||||
export type CreatedTemplateGlobal =
|
||||
| CreatedTemplateGlobalValue
|
||||
| CreatedTemplateGlobalFunction<unknown, unknown>;
|
||||
| CreatedTemplateGlobalFunction<any, any>;
|
||||
|
||||
@@ -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.
|
||||
@@ -73,7 +74,7 @@ export const scaffolderTaskBrokerExtensionPoint =
|
||||
*/
|
||||
export interface ScaffolderTemplatingExtensionPoint {
|
||||
addTemplateFilters(
|
||||
filters: Record<string, TemplateFilter> | CreatedTemplateFilter[],
|
||||
filters: Record<string, TemplateFilter> | CreatedTemplateFilter<any, any>[],
|
||||
): void;
|
||||
|
||||
addTemplateGlobals(
|
||||
|
||||
@@ -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<z.ZodTuple<TFunctionArgs, null>, TReturnType>
|
||||
| z.ZodType<
|
||||
(...args: z.infer<z.ZodTuple<TFunctionArgs>>) => z.infer<TReturnType>
|
||||
>;
|
||||
Reference in New Issue
Block a user