Merge pull request #28798 from backstage/blam/zod-types
Deprecate `createTemplateAction` with old `JSONSchema` and `zod` declarations
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-node': minor
|
||||
---
|
||||
|
||||
**DEPRECATION**: We've deprecated the old way of defining actions using `createTemplateAction` with raw `JSONSchema` and type parameters, as well as using `zod` through an import. You can now use the new format to define `createTemplateActions` with `zod` provided by the framework. This change also removes support for `logStream` in the `context` as well as moving the `logger` to an instance of `LoggerService`.
|
||||
|
||||
Before:
|
||||
|
||||
```ts
|
||||
createTemplateAction<{ repoUrl: string }, { test: string }>({
|
||||
id: 'test',
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
required: ['repoUrl'],
|
||||
properties: {
|
||||
repoUrl: { type: 'string' },
|
||||
},
|
||||
},
|
||||
output: {
|
||||
type: 'object',
|
||||
required: ['test'],
|
||||
properties: {
|
||||
test: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
handler: async ctx => {
|
||||
ctx.logStream.write('blob');
|
||||
},
|
||||
});
|
||||
|
||||
// or
|
||||
|
||||
createTemplateAction({
|
||||
id: 'test',
|
||||
schema: {
|
||||
input: z.object({
|
||||
repoUrl: z.string(),
|
||||
}),
|
||||
output: z.object({
|
||||
test: z.string(),
|
||||
}),
|
||||
},
|
||||
handler: async ctx => {
|
||||
ctx.logStream.write('something');
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```ts
|
||||
createTemplateAction({
|
||||
id: 'test',
|
||||
schema: {
|
||||
input: {
|
||||
repoUrl: d => d.string(),
|
||||
},
|
||||
output: {
|
||||
test: d => d.string(),
|
||||
},
|
||||
},
|
||||
handler: async ctx => {
|
||||
// you can just use ctx.logger.log('...'), or if you really need a log stream you can do this:
|
||||
const logStream = new PassThrough();
|
||||
logStream.on('data', chunk => {
|
||||
ctx.logger.info(chunk.toString());
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-node-test-utils': minor
|
||||
---
|
||||
|
||||
Use update `createTemplateAction` kinds
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': minor
|
||||
---
|
||||
|
||||
Support new `createTemplateAction` type, and convert `catalog:fetch` action to new way of defining actions.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch
|
||||
---
|
||||
|
||||
Fixing spelling mistake in `jsonschema`
|
||||
@@ -29,6 +29,7 @@ export function createPublishAzureAction(options: {
|
||||
gitAuthorEmail?: string;
|
||||
signCommit?: boolean;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
```
|
||||
|
||||
@@ -23,7 +23,12 @@ export const createBitbucketPipelinesRunAction: (options: {
|
||||
body?: object;
|
||||
token?: string;
|
||||
},
|
||||
JsonObject
|
||||
{
|
||||
buildNumber: number;
|
||||
repoUrl: string;
|
||||
pipelinesUrl: string;
|
||||
},
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -41,7 +46,8 @@ export function createPublishBitbucketCloudAction(options: {
|
||||
token?: string;
|
||||
signCommit?: boolean;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -59,6 +65,7 @@ export function createPublishBitbucketCloudPullRequestAction(options: {
|
||||
gitAuthorName?: string;
|
||||
gitAuthorEmail?: string;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
```
|
||||
|
||||
+2
@@ -65,9 +65,11 @@ describe('bitbucket:pipelines:run', () => {
|
||||
const actionNoCreds = createBitbucketPipelinesRunAction({
|
||||
integrations: integrationsNoCreds,
|
||||
});
|
||||
|
||||
const testContext = Object.assign({}, mockContext, {
|
||||
input: { workspace, repo_slug },
|
||||
});
|
||||
|
||||
await expect(actionNoCreds.handler(testContext)).rejects.toThrow(
|
||||
/Authorization has not been provided for Bitbucket Cloud/,
|
||||
);
|
||||
|
||||
+14
-7
@@ -30,12 +30,19 @@ export const createBitbucketPipelinesRunAction = (options: {
|
||||
integrations: ScmIntegrationRegistry;
|
||||
}) => {
|
||||
const { integrations } = options;
|
||||
return createTemplateAction<{
|
||||
workspace: string;
|
||||
repo_slug: string;
|
||||
body?: object;
|
||||
token?: string;
|
||||
}>({
|
||||
return createTemplateAction<
|
||||
{
|
||||
workspace: string;
|
||||
repo_slug: string;
|
||||
body?: object;
|
||||
token?: string;
|
||||
},
|
||||
{
|
||||
buildNumber: number;
|
||||
repoUrl: string;
|
||||
pipelinesUrl: string;
|
||||
}
|
||||
>({
|
||||
id,
|
||||
description: 'Run a bitbucket cloud pipeline',
|
||||
examples,
|
||||
@@ -58,7 +65,7 @@ export const createBitbucketPipelinesRunAction = (options: {
|
||||
type: 'number',
|
||||
},
|
||||
repoUrl: {
|
||||
title: 'A URL to the pipeline repositry',
|
||||
title: 'A URL to the pipeline repository',
|
||||
type: 'string',
|
||||
},
|
||||
repoContentsUrl: {
|
||||
|
||||
@@ -31,7 +31,8 @@ export function createPublishBitbucketServerAction(options: {
|
||||
gitAuthorEmail?: string;
|
||||
signCommit?: boolean;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -50,6 +51,7 @@ export function createPublishBitbucketServerPullRequestAction(options: {
|
||||
gitAuthorName?: string;
|
||||
gitAuthorEmail?: string;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
```
|
||||
|
||||
@@ -25,7 +25,12 @@ export const createBitbucketPipelinesRunAction: (options: {
|
||||
body?: object;
|
||||
token?: string;
|
||||
},
|
||||
JsonObject
|
||||
{
|
||||
buildNumber: number;
|
||||
repoUrl: string;
|
||||
pipelinesUrl: string;
|
||||
},
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public @deprecated
|
||||
@@ -46,7 +51,8 @@ export function createPublishBitbucketAction(options: {
|
||||
gitAuthorEmail?: string;
|
||||
signCommit?: boolean;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
|
||||
@@ -24,6 +24,7 @@ export const createConfluenceToMarkdownAction: (options: {
|
||||
confluenceUrls: string[];
|
||||
repoUrl: string;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
```
|
||||
|
||||
@@ -54,6 +54,7 @@ export function createFetchCookiecutterAction(options: {
|
||||
extensions?: string[];
|
||||
imageName?: string;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
```
|
||||
|
||||
@@ -24,7 +24,8 @@ export function createPublishGerritAction(options: {
|
||||
sourcePath?: string;
|
||||
signCommit?: boolean;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -41,7 +42,8 @@ export function createPublishGerritReviewAction(options: {
|
||||
gitAuthorEmail?: string;
|
||||
signCommit?: boolean;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
|
||||
@@ -25,7 +25,8 @@ export function createPublishGiteaAction(options: {
|
||||
sourcePath?: string;
|
||||
signCommit?: boolean;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
|
||||
@@ -30,7 +30,8 @@ export function createGithubActionsDispatchAction(options: {
|
||||
};
|
||||
token?: string;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -45,7 +46,8 @@ export function createGithubAutolinksAction(options: {
|
||||
isAlphanumeric?: boolean;
|
||||
token?: string;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -81,7 +83,8 @@ export function createGithubBranchProtectionAction(options: {
|
||||
requiredLinearHistory?: boolean;
|
||||
token?: string;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -96,7 +99,8 @@ export function createGithubDeployKeyAction(options: {
|
||||
privateKeySecretName?: string;
|
||||
token?: string;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -125,7 +129,8 @@ export function createGithubEnvironmentAction(options: {
|
||||
preventSelfReview?: boolean;
|
||||
reviewers?: string[];
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -139,7 +144,8 @@ export function createGithubIssuesLabelAction(options: {
|
||||
labels: string[];
|
||||
token?: string;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -154,7 +160,8 @@ export function createGithubPagesEnableAction(options: {
|
||||
sourcePath?: '/' | '/docs';
|
||||
token?: string;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -255,7 +262,8 @@ export function createGithubRepoCreateAction(options: {
|
||||
};
|
||||
subscribe?: boolean;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -299,7 +307,8 @@ export function createGithubRepoPushAction(options: {
|
||||
requiredLinearHistory?: boolean;
|
||||
requireLastPushApproval?: boolean;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -318,7 +327,8 @@ export function createGithubWebhookAction(options: {
|
||||
insecureSsl?: boolean;
|
||||
token?: string;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -404,7 +414,8 @@ export function createPublishGithubAction(options: {
|
||||
};
|
||||
subscribe?: boolean;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -431,7 +442,8 @@ export const createPublishGithubPullRequestAction: (
|
||||
forceEmptyGitAuthor?: boolean;
|
||||
createWhenEmpty?: boolean;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
|
||||
@@ -26,7 +26,8 @@ export const createGitlabGroupEnsureExistsAction: (options: {
|
||||
},
|
||||
{
|
||||
groupId?: number | undefined;
|
||||
}
|
||||
},
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -55,7 +56,8 @@ export const createGitlabIssueAction: (options: {
|
||||
issueUrl: string;
|
||||
issueId: number;
|
||||
issueIid: number;
|
||||
}
|
||||
},
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -73,7 +75,8 @@ export const createGitlabProjectAccessTokenAction: (options: {
|
||||
},
|
||||
{
|
||||
access_token: string;
|
||||
}
|
||||
},
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -91,7 +94,8 @@ export const createGitlabProjectDeployTokenAction: (options: {
|
||||
{
|
||||
user: string;
|
||||
deploy_token: string;
|
||||
}
|
||||
},
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -110,7 +114,8 @@ export const createGitlabProjectVariableAction: (options: {
|
||||
environmentScope?: string | undefined;
|
||||
variableProtected?: boolean | undefined;
|
||||
},
|
||||
JsonObject
|
||||
any,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -126,7 +131,8 @@ export const createGitlabRepoPushAction: (options: {
|
||||
token?: string;
|
||||
commitAction?: 'create' | 'delete' | 'update';
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -177,7 +183,8 @@ export function createPublishGitlabAction(options: {
|
||||
environment_scope?: string;
|
||||
}>;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -200,7 +207,8 @@ export const createPublishGitlabMergeRequestAction: (options: {
|
||||
reviewers?: string[];
|
||||
assignReviewersFromApprovalRules?: boolean;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -217,7 +225,8 @@ export const createTriggerGitlabPipelineAction: (options: {
|
||||
},
|
||||
{
|
||||
pipelineUrl: string;
|
||||
}
|
||||
},
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -253,7 +262,8 @@ export const editGitlabIssueAction: (options: {
|
||||
issueUrl: string;
|
||||
issueId: number;
|
||||
issueIid: number;
|
||||
}
|
||||
},
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
|
||||
@@ -23,7 +23,8 @@ export function createSendNotificationAction(options: {
|
||||
scope?: string;
|
||||
optional?: boolean;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
|
||||
@@ -49,7 +49,8 @@ export function createFetchRailsAction(options: {
|
||||
values: JsonObject;
|
||||
imageName?: string;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
|
||||
@@ -19,7 +19,8 @@ export function createSentryCreateProjectAction(options: {
|
||||
slug?: string;
|
||||
authToken?: string;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
|
||||
@@ -14,7 +14,8 @@ export function createRunYeomanAction(): TemplateAction<
|
||||
args?: string[];
|
||||
options?: JsonObject;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
"globby": "^11.0.0",
|
||||
"isbinaryfile": "^5.0.0",
|
||||
"isolated-vm": "^5.0.1",
|
||||
"jsonschema": "^1.2.6",
|
||||
"jsonschema": "^1.5.0",
|
||||
"knex": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"logform": "^2.3.2",
|
||||
|
||||
@@ -30,6 +30,7 @@ import { createPublishGerritAction as createPublishGerritAction_2 } from '@backs
|
||||
import { createPublishGerritReviewAction as createPublishGerritReviewAction_2 } from '@backstage/plugin-scaffolder-backend-module-gerrit';
|
||||
import { createPublishGithubAction as createPublishGithubAction_2 } from '@backstage/plugin-scaffolder-backend-module-github';
|
||||
import { createPublishGitlabAction as createPublishGitlabAction_2 } from '@backstage/plugin-scaffolder-backend-module-gitlab';
|
||||
import { createTemplateAction as createTemplateAction_2 } from '@backstage/plugin-scaffolder-node';
|
||||
import { DatabaseService } from '@backstage/backend-plugin-api';
|
||||
import { DiscoveryService } from '@backstage/backend-plugin-api';
|
||||
import { Duration } from 'luxon';
|
||||
@@ -54,7 +55,6 @@ import { RESOURCE_TYPE_SCAFFOLDER_ACTION } from '@backstage/plugin-scaffolder-co
|
||||
import { RESOURCE_TYPE_SCAFFOLDER_TEMPLATE } from '@backstage/plugin-scaffolder-common/alpha';
|
||||
import { ScaffolderEntitiesProcessor as ScaffolderEntitiesProcessor_2 } from '@backstage/plugin-catalog-backend-module-scaffolder-entity-model';
|
||||
import { SchedulerService } from '@backstage/backend-plugin-api';
|
||||
import { Schema } from 'jsonschema';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { SerializedTask as SerializedTask_2 } from '@backstage/plugin-scaffolder-node';
|
||||
@@ -71,14 +71,12 @@ import { TaskSpec } from '@backstage/plugin-scaffolder-common';
|
||||
import { TaskSpecV1beta3 } from '@backstage/plugin-scaffolder-common';
|
||||
import { TaskStatus as TaskStatus_2 } from '@backstage/plugin-scaffolder-node';
|
||||
import { TemplateAction as TemplateAction_2 } from '@backstage/plugin-scaffolder-node';
|
||||
import { TemplateActionOptions } from '@backstage/plugin-scaffolder-node';
|
||||
import { TemplateEntityStepV1beta3 } from '@backstage/plugin-scaffolder-common';
|
||||
import { TemplateFilter as TemplateFilter_2 } from '@backstage/plugin-scaffolder-node';
|
||||
import { TemplateGlobal as TemplateGlobal_2 } from '@backstage/plugin-scaffolder-node';
|
||||
import { TemplateParametersV1beta3 } from '@backstage/plugin-scaffolder-common';
|
||||
import { UrlReaderService } from '@backstage/backend-plugin-api';
|
||||
import { WorkspaceProvider } from '@backstage/plugin-scaffolder-node/alpha';
|
||||
import { ZodType } from 'zod';
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export type ActionContext<TInput extends JsonObject> = ActionContext_2<TInput>;
|
||||
@@ -125,7 +123,8 @@ export function createCatalogRegisterAction(options: {
|
||||
catalogInfoPath?: string;
|
||||
optional?: boolean;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -134,17 +133,12 @@ export function createCatalogWriteAction(): TemplateAction_2<
|
||||
entity: Record<string, any>;
|
||||
filePath?: string | undefined;
|
||||
},
|
||||
JsonObject
|
||||
any,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
export function createDebugLogAction(): TemplateAction_2<
|
||||
{
|
||||
message?: string;
|
||||
listWorkspace?: boolean | 'with-filenames' | 'with-contents';
|
||||
},
|
||||
JsonObject
|
||||
>;
|
||||
export function createDebugLogAction(): TemplateAction_2<any, any, 'v1'>;
|
||||
|
||||
// @public
|
||||
export function createFetchCatalogEntityAction(options: {
|
||||
@@ -152,16 +146,17 @@ export function createFetchCatalogEntityAction(options: {
|
||||
auth?: AuthService;
|
||||
}): TemplateAction_2<
|
||||
{
|
||||
entityRef?: string | undefined;
|
||||
entityRefs?: string[] | undefined;
|
||||
optional?: boolean | undefined;
|
||||
defaultKind?: string | undefined;
|
||||
defaultNamespace?: string | undefined;
|
||||
entityRef?: string | undefined;
|
||||
entityRefs?: string[] | undefined;
|
||||
},
|
||||
{
|
||||
entities?: any[] | undefined;
|
||||
entity?: any;
|
||||
}
|
||||
entities?: any[] | undefined;
|
||||
},
|
||||
'v2'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -174,7 +169,8 @@ export function createFetchPlainAction(options: {
|
||||
targetPath?: string;
|
||||
token?: string;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -187,7 +183,8 @@ export function createFetchPlainFileAction(options: {
|
||||
targetPath: string;
|
||||
token?: string;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -210,7 +207,8 @@ export function createFetchTemplateAction(options: {
|
||||
lstripBlocks?: boolean;
|
||||
token?: string;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -230,7 +228,8 @@ export function createFetchTemplateFileAction(options: {
|
||||
lstripBlocks?: boolean;
|
||||
token?: string;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -238,14 +237,15 @@ export const createFilesystemDeleteAction: () => TemplateAction_2<
|
||||
{
|
||||
files: string[];
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
export const createFilesystemReadDirAction: () => TemplateAction_2<
|
||||
{
|
||||
recursive: boolean;
|
||||
paths: string[];
|
||||
recursive?: boolean | undefined;
|
||||
},
|
||||
{
|
||||
files: {
|
||||
@@ -258,7 +258,8 @@ export const createFilesystemReadDirAction: () => TemplateAction_2<
|
||||
path: string;
|
||||
fullPath: string;
|
||||
}[];
|
||||
}
|
||||
},
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -270,7 +271,8 @@ export const createFilesystemRenameAction: () => TemplateAction_2<
|
||||
overwrite?: boolean;
|
||||
}>;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
@@ -346,7 +348,8 @@ export const createPublishGithubPullRequestAction: (
|
||||
forceEmptyGitAuthor?: boolean;
|
||||
createWhenEmpty?: boolean;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
@@ -372,45 +375,20 @@ export const createPublishGitlabMergeRequestAction: (options: {
|
||||
reviewers?: string[];
|
||||
assignReviewersFromApprovalRules?: boolean;
|
||||
},
|
||||
JsonObject
|
||||
JsonObject,
|
||||
'v1'
|
||||
>;
|
||||
|
||||
// @public @deprecated
|
||||
export function createRouter(options: RouterOptions): Promise<express.Router>;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export const createTemplateAction: <
|
||||
TInputParams extends JsonObject = JsonObject,
|
||||
TOutputParams extends JsonObject = JsonObject,
|
||||
TInputSchema extends Schema | ZodType = {},
|
||||
TOutputSchema extends Schema | ZodType = {},
|
||||
TActionInput extends JsonObject = TInputSchema extends ZodType<
|
||||
any,
|
||||
any,
|
||||
infer IReturn
|
||||
>
|
||||
? IReturn
|
||||
: TInputParams,
|
||||
TActionOutput extends JsonObject = TOutputSchema extends ZodType<
|
||||
any,
|
||||
any,
|
||||
infer IReturn_1
|
||||
>
|
||||
? IReturn_1
|
||||
: TOutputParams,
|
||||
>(
|
||||
action: TemplateActionOptions<
|
||||
TActionInput,
|
||||
TActionOutput,
|
||||
TInputSchema,
|
||||
TOutputSchema
|
||||
>,
|
||||
) => TemplateAction_2<TActionInput, TActionOutput>;
|
||||
export const createTemplateAction: typeof createTemplateAction_2;
|
||||
|
||||
// @public
|
||||
export function createWaitAction(options?: {
|
||||
maxWaitTime?: Duration | HumanDuration;
|
||||
}): TemplateAction_2<HumanDuration, JsonObject>;
|
||||
}): TemplateAction_2<HumanDuration, JsonObject, 'v1'>;
|
||||
|
||||
// @public
|
||||
export type CreateWorkerOptions = {
|
||||
@@ -547,7 +525,7 @@ export const fetchContents: typeof fetchContents_2;
|
||||
// @public @deprecated
|
||||
export interface RouterOptions {
|
||||
// (undocumented)
|
||||
actions?: TemplateAction_2<any, any>[];
|
||||
actions?: TemplateAction_2<any, any, any>[];
|
||||
// (undocumented)
|
||||
additionalTemplateFilters?:
|
||||
| Record<string, TemplateFilter_2>
|
||||
@@ -866,11 +844,11 @@ export type TemplateAction<TInput extends JsonObject> =
|
||||
// @public
|
||||
export class TemplateActionRegistry {
|
||||
// (undocumented)
|
||||
get(actionId: string): TemplateAction_2;
|
||||
get(actionId: string): TemplateAction_2<any, any, any>;
|
||||
// (undocumented)
|
||||
list(): TemplateAction_2[];
|
||||
list(): TemplateAction_2<any, any, any>[];
|
||||
// (undocumented)
|
||||
register(action: TemplateAction_2): void;
|
||||
register(action: TemplateAction_2<any, any, any>): void;
|
||||
}
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
|
||||
@@ -23,7 +23,7 @@ import { TemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
export class TemplateActionRegistry {
|
||||
private readonly actions = new Map<string, TemplateAction>();
|
||||
|
||||
register(action: TemplateAction) {
|
||||
register(action: TemplateAction<any, any, any>) {
|
||||
if (this.actions.has(action.id)) {
|
||||
throw new ConflictError(
|
||||
`Template action with ID '${action.id}' has already been registered`,
|
||||
@@ -33,7 +33,7 @@ export class TemplateActionRegistry {
|
||||
this.actions.set(action.id, action);
|
||||
}
|
||||
|
||||
get(actionId: string): TemplateAction {
|
||||
get(actionId: string): TemplateAction<any, any, any> {
|
||||
const action = this.actions.get(actionId);
|
||||
if (!action) {
|
||||
throw new NotFoundError(
|
||||
@@ -43,7 +43,7 @@ export class TemplateActionRegistry {
|
||||
return action;
|
||||
}
|
||||
|
||||
list(): TemplateAction[] {
|
||||
list(): TemplateAction<any, any, any>[] {
|
||||
return [...this.actions.values()];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import { z } from 'zod';
|
||||
import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model';
|
||||
import { examples } from './fetch.examples';
|
||||
import { AuthService } from '@backstage/backend-plugin-api';
|
||||
@@ -41,48 +40,54 @@ export function createFetchCatalogEntityAction(options: {
|
||||
examples,
|
||||
supportsDryRun: true,
|
||||
schema: {
|
||||
input: z.object({
|
||||
entityRef: z
|
||||
.string({
|
||||
description: 'Entity reference of the entity to get',
|
||||
})
|
||||
.optional(),
|
||||
entityRefs: z
|
||||
.array(z.string(), {
|
||||
description: 'Entity references of the entities to get',
|
||||
})
|
||||
.optional(),
|
||||
optional: z
|
||||
.boolean({
|
||||
description:
|
||||
'Allow the entity or entities to optionally exist. Default: false',
|
||||
})
|
||||
.optional(),
|
||||
defaultKind: z.string({ description: 'The default kind' }).optional(),
|
||||
defaultNamespace: z
|
||||
.string({ description: 'The default namespace' })
|
||||
.optional(),
|
||||
}),
|
||||
output: z.object({
|
||||
entity: z
|
||||
.any({
|
||||
description:
|
||||
'Object containing same values used in the Entity schema. Only when used with `entityRef` parameter.',
|
||||
})
|
||||
.optional(),
|
||||
entities: z
|
||||
.array(
|
||||
z.any({
|
||||
input: {
|
||||
entityRef: z =>
|
||||
z
|
||||
.string({
|
||||
description: 'Entity reference of the entity to get',
|
||||
})
|
||||
.optional(),
|
||||
entityRefs: z =>
|
||||
z
|
||||
.array(z.string(), {
|
||||
description: 'Entity references of the entities to get',
|
||||
})
|
||||
.optional(),
|
||||
optional: z =>
|
||||
z
|
||||
.boolean({
|
||||
description:
|
||||
'Array containing objects with same values used in the Entity schema. Only when used with `entityRefs` parameter.',
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
}),
|
||||
'Allow the entity or entities to optionally exist. Default: false',
|
||||
})
|
||||
.optional(),
|
||||
defaultKind: z =>
|
||||
z.string({ description: 'The default kind' }).optional(),
|
||||
defaultNamespace: z =>
|
||||
z.string({ description: 'The default namespace' }).optional(),
|
||||
},
|
||||
output: {
|
||||
entity: z =>
|
||||
z
|
||||
.any({
|
||||
description:
|
||||
'Object containing same values used in the Entity schema. Only when used with `entityRef` parameter.',
|
||||
})
|
||||
.optional(),
|
||||
entities: z =>
|
||||
z
|
||||
.array(
|
||||
z.any({
|
||||
description:
|
||||
'Array containing objects with same values used in the Entity schema. Only when used with `entityRefs` parameter.',
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
},
|
||||
},
|
||||
async handler(ctx) {
|
||||
const { entityRef, entityRefs, optional, defaultKind, defaultNamespace } =
|
||||
ctx.input;
|
||||
|
||||
if (!entityRef && !entityRefs) {
|
||||
if (optional) {
|
||||
return;
|
||||
|
||||
@@ -104,33 +104,37 @@ describe('NunjucksWorkflowRunner', () => {
|
||||
fakeActionHandler = jest.fn();
|
||||
fakeTaskLog = jest.fn();
|
||||
|
||||
actionRegistry.register({
|
||||
id: 'jest-mock-action',
|
||||
description: 'Mock action for testing',
|
||||
handler: fakeActionHandler,
|
||||
});
|
||||
|
||||
actionRegistry.register({
|
||||
id: 'jest-validated-action',
|
||||
description: 'Mock action for testing',
|
||||
supportsDryRun: true,
|
||||
handler: fakeActionHandler,
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
required: ['foo'],
|
||||
properties: {
|
||||
foo: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
actionRegistry.register(
|
||||
createTemplateAction({
|
||||
id: 'jest-mock-action',
|
||||
description: 'Mock action for testing',
|
||||
handler: fakeActionHandler,
|
||||
}),
|
||||
);
|
||||
|
||||
actionRegistry.register(
|
||||
createTemplateAction({
|
||||
id: 'jest-zod-validated-action',
|
||||
id: 'jest-validated-action',
|
||||
description: 'Mock action for testing',
|
||||
supportsDryRun: true,
|
||||
handler: fakeActionHandler,
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
required: ['foo'],
|
||||
properties: {
|
||||
foo: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
actionRegistry.register(
|
||||
createTemplateAction({
|
||||
id: 'jest-legacy-zod-validated-action',
|
||||
description: 'Mock action for testing',
|
||||
handler: fakeActionHandler,
|
||||
supportsDryRun: true,
|
||||
@@ -142,52 +146,80 @@ describe('NunjucksWorkflowRunner', () => {
|
||||
}) as TemplateAction,
|
||||
);
|
||||
|
||||
actionRegistry.register({
|
||||
id: 'output-action',
|
||||
description: 'Mock action for testing',
|
||||
handler: async ctx => {
|
||||
ctx.output('mock', 'backstage');
|
||||
ctx.output('shouldRun', true);
|
||||
},
|
||||
});
|
||||
actionRegistry.register(
|
||||
createTemplateAction({
|
||||
id: 'jest-zod-validated-action',
|
||||
description: 'Mock ac',
|
||||
supportsDryRun: true,
|
||||
schema: {
|
||||
input: {
|
||||
foo: zod => zod.number(),
|
||||
},
|
||||
output: {
|
||||
test: zod => zod.string(),
|
||||
},
|
||||
},
|
||||
handler: fakeActionHandler,
|
||||
}),
|
||||
);
|
||||
|
||||
actionRegistry.register({
|
||||
id: 'checkpoints-action',
|
||||
description: 'Mock action with checkpoints',
|
||||
handler: async ctx => {
|
||||
const key1 = await ctx.checkpoint({
|
||||
key: 'key1',
|
||||
fn: async () => 'updated',
|
||||
});
|
||||
const key2 = await ctx.checkpoint({
|
||||
key: 'key2',
|
||||
fn: async () => 'updated',
|
||||
});
|
||||
const key3 = await ctx.checkpoint({
|
||||
key: 'key3',
|
||||
fn: async () => 'updated',
|
||||
});
|
||||
actionRegistry.register(
|
||||
createTemplateAction({
|
||||
id: 'output-action',
|
||||
description: 'Mock action for testing',
|
||||
handler: async ctx => {
|
||||
ctx.output('mock', 'backstage');
|
||||
ctx.output('shouldRun', true);
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const key4 = await ctx.checkpoint({
|
||||
key: 'key4',
|
||||
fn: () => {},
|
||||
});
|
||||
actionRegistry.register(
|
||||
createTemplateAction({
|
||||
id: 'checkpoints-action',
|
||||
description: 'Mock action with checkpoints',
|
||||
schema: {
|
||||
output: z.object({
|
||||
key1: z.string(),
|
||||
key2: z.string(),
|
||||
key3: z.string(),
|
||||
key4: z.string(),
|
||||
key5: z.string(),
|
||||
}),
|
||||
},
|
||||
handler: async ctx => {
|
||||
const key1 = await ctx.checkpoint({
|
||||
key: 'key1',
|
||||
fn: async () => 'updated',
|
||||
});
|
||||
const key2 = await ctx.checkpoint({
|
||||
key: 'key2',
|
||||
fn: async () => 'updated',
|
||||
});
|
||||
const key3 = await ctx.checkpoint({
|
||||
key: 'key3',
|
||||
fn: async () => 'updated',
|
||||
});
|
||||
|
||||
const key5 = await ctx.checkpoint({
|
||||
key: 'key5',
|
||||
fn: async () => {},
|
||||
});
|
||||
const key4 = await ctx.checkpoint({
|
||||
key: 'key4',
|
||||
fn: () => {},
|
||||
});
|
||||
|
||||
ctx.output('key1', key1);
|
||||
ctx.output('key2', key2);
|
||||
ctx.output('key3', key3);
|
||||
const key5 = await ctx.checkpoint({
|
||||
key: 'key5',
|
||||
fn: async () => {},
|
||||
});
|
||||
|
||||
// @ts-expect-error - this is void return
|
||||
ctx.output('key4', key4);
|
||||
// @ts-expect-error - this is void return
|
||||
ctx.output('key5', key5);
|
||||
},
|
||||
});
|
||||
ctx.output('key1', key1);
|
||||
ctx.output('key2', key2);
|
||||
ctx.output('key3', key3);
|
||||
|
||||
ctx.output('key4', key4);
|
||||
ctx.output('key5', key5);
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
mockedPermissionApi.authorizeConditional.mockResolvedValue([
|
||||
{ result: AuthorizeResult.ALLOW },
|
||||
@@ -244,6 +276,25 @@ describe('NunjucksWorkflowRunner', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if the action has legacy zod schema and the input does not match', async () => {
|
||||
const task = createMockTaskWithSpec({
|
||||
apiVersion: 'scaffolder.backstage.io/v1beta3',
|
||||
parameters: {},
|
||||
output: {},
|
||||
steps: [
|
||||
{
|
||||
id: 'test',
|
||||
name: 'name',
|
||||
action: 'jest-legacy-zod-validated-action',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await expect(runner.execute(task)).rejects.toThrow(
|
||||
/Invalid input passed to action jest-legacy-zod-validated-action, instance requires property \"foo\"/,
|
||||
);
|
||||
});
|
||||
|
||||
it('should run the action when the zod validation passes', async () => {
|
||||
const task = createMockTaskWithSpec({
|
||||
apiVersion: 'scaffolder.backstage.io/v1beta3',
|
||||
@@ -264,6 +315,26 @@ describe('NunjucksWorkflowRunner', () => {
|
||||
expect(fakeActionHandler).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should run the action when the zod validation passes with legacy zod', async () => {
|
||||
const task = createMockTaskWithSpec({
|
||||
apiVersion: 'scaffolder.backstage.io/v1beta3',
|
||||
parameters: {},
|
||||
output: {},
|
||||
steps: [
|
||||
{
|
||||
id: 'test',
|
||||
name: 'name',
|
||||
action: 'jest-legacy-zod-validated-action',
|
||||
input: { foo: 1 },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await runner.execute(task);
|
||||
|
||||
expect(fakeActionHandler).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should run the action when the validation passes', async () => {
|
||||
const task = createMockTaskWithSpec({
|
||||
apiVersion: 'scaffolder.backstage.io/v1beta3',
|
||||
|
||||
@@ -38,13 +38,13 @@ export function generateExampleOutput(schema: Schema): unknown {
|
||||
return Object.fromEntries(
|
||||
Object.entries(schema.properties ?? {}).map(([key, value]) => [
|
||||
key,
|
||||
generateExampleOutput(value),
|
||||
generateExampleOutput(value as Schema),
|
||||
]),
|
||||
);
|
||||
} else if (schema.type === 'array') {
|
||||
const [firstSchema] = [schema.items]?.flat();
|
||||
if (firstSchema) {
|
||||
return [generateExampleOutput(firstSchema)];
|
||||
return [generateExampleOutput(firstSchema as Schema)];
|
||||
}
|
||||
return [];
|
||||
} else if (schema.type === 'string') {
|
||||
|
||||
@@ -165,7 +165,7 @@ export interface RouterOptions {
|
||||
database: DatabaseService;
|
||||
catalogClient: CatalogApi;
|
||||
scheduler?: SchedulerService;
|
||||
actions?: TemplateAction<any, any>[];
|
||||
actions?: TemplateAction<any, any, any>[];
|
||||
/**
|
||||
* @deprecated taskWorkers is deprecated in favor of concurrentTasksLimit option with a single TaskWorker
|
||||
* @defaultValue 1
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
"fs-extra": "^11.2.0",
|
||||
"globby": "^11.0.0",
|
||||
"isomorphic-git": "^1.23.0",
|
||||
"jsonschema": "^1.2.6",
|
||||
"jsonschema": "^1.5.0",
|
||||
"p-limit": "^3.1.0",
|
||||
"tar": "^6.1.12",
|
||||
"winston": "^3.2.1",
|
||||
|
||||
@@ -122,7 +122,7 @@ export const restoreWorkspace: (opts: {
|
||||
// @alpha
|
||||
export interface ScaffolderActionsExtensionPoint {
|
||||
// (undocumented)
|
||||
addActions(...actions: TemplateAction<any, any>[]): void;
|
||||
addActions(...actions: TemplateAction<any, any, any>[]): void;
|
||||
}
|
||||
|
||||
// @alpha
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
```ts
|
||||
import { BackstageCredentials } from '@backstage/backend-plugin-api';
|
||||
import { Expand } from '@backstage/types';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { Logger } from 'winston';
|
||||
@@ -24,34 +25,63 @@ import { z } from 'zod';
|
||||
export type ActionContext<
|
||||
TActionInput extends JsonObject,
|
||||
TActionOutput extends JsonObject = JsonObject,
|
||||
> = {
|
||||
logger: Logger;
|
||||
logStream: Writable;
|
||||
secrets?: TaskSecrets;
|
||||
workspacePath: string;
|
||||
input: TActionInput;
|
||||
checkpoint<T extends JsonValue | void>(opts: {
|
||||
key: string;
|
||||
fn: () => Promise<T> | T;
|
||||
}): Promise<T>;
|
||||
output(
|
||||
name: keyof TActionOutput,
|
||||
value: TActionOutput[keyof TActionOutput],
|
||||
): void;
|
||||
createTemporaryDirectory(): Promise<string>;
|
||||
getInitiatorCredentials(): Promise<BackstageCredentials>;
|
||||
task: {
|
||||
id: string;
|
||||
};
|
||||
templateInfo?: TemplateInfo;
|
||||
isDryRun?: boolean;
|
||||
user?: {
|
||||
entity?: UserEntity;
|
||||
ref?: string;
|
||||
};
|
||||
signal?: AbortSignal;
|
||||
each?: JsonObject;
|
||||
};
|
||||
TSchemaType extends 'v1' | 'v2' = 'v1',
|
||||
> = TSchemaType extends 'v2'
|
||||
? {
|
||||
logger: LoggerService;
|
||||
secrets?: TaskSecrets;
|
||||
workspacePath: string;
|
||||
input: TActionInput;
|
||||
checkpoint<T extends JsonValue | void>(opts: {
|
||||
key: string;
|
||||
fn: () => Promise<T> | T;
|
||||
}): Promise<T>;
|
||||
output(
|
||||
name: keyof TActionOutput,
|
||||
value: TActionOutput[keyof TActionOutput],
|
||||
): void;
|
||||
createTemporaryDirectory(): Promise<string>;
|
||||
getInitiatorCredentials(): Promise<BackstageCredentials>;
|
||||
task: {
|
||||
id: string;
|
||||
};
|
||||
templateInfo?: TemplateInfo;
|
||||
isDryRun?: boolean;
|
||||
user?: {
|
||||
entity?: UserEntity;
|
||||
ref?: string;
|
||||
};
|
||||
signal?: AbortSignal;
|
||||
each?: JsonObject;
|
||||
}
|
||||
: {
|
||||
logger: Logger;
|
||||
logStream: Writable;
|
||||
secrets?: TaskSecrets;
|
||||
workspacePath: string;
|
||||
input: TActionInput;
|
||||
checkpoint<T extends JsonValue | void>(opts: {
|
||||
key: string;
|
||||
fn: () => Promise<T> | T;
|
||||
}): Promise<T>;
|
||||
output(
|
||||
name: keyof TActionOutput,
|
||||
value: TActionOutput[keyof TActionOutput],
|
||||
): void;
|
||||
createTemporaryDirectory(): Promise<string>;
|
||||
getInitiatorCredentials(): Promise<BackstageCredentials>;
|
||||
task: {
|
||||
id: string;
|
||||
};
|
||||
templateInfo?: TemplateInfo;
|
||||
isDryRun?: boolean;
|
||||
user?: {
|
||||
entity?: UserEntity;
|
||||
ref?: string;
|
||||
};
|
||||
signal?: AbortSignal;
|
||||
each?: JsonObject;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export function addFiles(options: {
|
||||
@@ -150,34 +180,71 @@ export function createBranch(options: {
|
||||
logger?: Logger | undefined;
|
||||
}): Promise<void>;
|
||||
|
||||
// @public
|
||||
export const createTemplateAction: <
|
||||
// @public @deprecated (undocumented)
|
||||
export function createTemplateAction<
|
||||
TInputParams extends JsonObject = JsonObject,
|
||||
TOutputParams extends JsonObject = JsonObject,
|
||||
TInputSchema extends Schema | z.ZodType = {},
|
||||
TOutputSchema extends Schema | z.ZodType = {},
|
||||
TActionInput extends JsonObject = TInputSchema extends z.ZodType<
|
||||
any,
|
||||
any,
|
||||
infer IReturn
|
||||
>
|
||||
? IReturn
|
||||
: TInputParams,
|
||||
TActionOutput extends JsonObject = TOutputSchema extends z.ZodType<
|
||||
any,
|
||||
any,
|
||||
infer IReturn_1
|
||||
>
|
||||
? IReturn_1
|
||||
: TOutputParams,
|
||||
TInputSchema extends JsonObject = JsonObject,
|
||||
TOutputSchema extends JsonObject = JsonObject,
|
||||
TActionInput extends JsonObject = TInputParams,
|
||||
TActionOutput extends JsonObject = TOutputParams,
|
||||
>(
|
||||
action: TemplateActionOptions<
|
||||
TActionInput,
|
||||
TActionOutput,
|
||||
TInputSchema,
|
||||
TOutputSchema
|
||||
TOutputSchema,
|
||||
'v1'
|
||||
>,
|
||||
) => TemplateAction<TActionInput, TActionOutput>;
|
||||
): TemplateAction<TActionInput, TActionOutput, 'v1'>;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export function createTemplateAction<
|
||||
TInputParams extends JsonObject = JsonObject,
|
||||
TOutputParams extends JsonObject = JsonObject,
|
||||
TInputSchema extends z.ZodType = z.ZodType,
|
||||
TOutputSchema extends z.ZodType = z.ZodType,
|
||||
TActionInput extends JsonObject = z.infer<TInputSchema>,
|
||||
TActionOutput extends JsonObject = z.infer<TOutputSchema>,
|
||||
>(
|
||||
action: TemplateActionOptions<
|
||||
TActionInput,
|
||||
TActionOutput,
|
||||
TInputSchema,
|
||||
TOutputSchema,
|
||||
'v1'
|
||||
>,
|
||||
): TemplateAction<TActionInput, TActionOutput, 'v1'>;
|
||||
|
||||
// @public
|
||||
export function createTemplateAction<
|
||||
TInputSchema extends {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
},
|
||||
TOutputSchema extends {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
},
|
||||
>(
|
||||
action: TemplateActionOptions<
|
||||
{
|
||||
[key in keyof TInputSchema]: z.infer<ReturnType<TInputSchema[key]>>;
|
||||
},
|
||||
{
|
||||
[key in keyof TOutputSchema]: z.infer<ReturnType<TOutputSchema[key]>>;
|
||||
},
|
||||
TInputSchema,
|
||||
TOutputSchema,
|
||||
'v2'
|
||||
>,
|
||||
): TemplateAction<
|
||||
FlattenOptionalProperties<{
|
||||
[key in keyof TInputSchema]: z.output<ReturnType<TInputSchema[key]>>;
|
||||
}>,
|
||||
FlattenOptionalProperties<{
|
||||
[key in keyof TOutputSchema]: z.output<ReturnType<TOutputSchema[key]>>;
|
||||
}>,
|
||||
'v2'
|
||||
>;
|
||||
|
||||
// @public
|
||||
export function deserializeDirectoryContents(
|
||||
@@ -443,6 +510,7 @@ export type TaskStatus =
|
||||
export type TemplateAction<
|
||||
TActionInput extends JsonObject = JsonObject,
|
||||
TActionOutput extends JsonObject = JsonObject,
|
||||
TSchemaType extends 'v1' | 'v2' = 'v1',
|
||||
> = {
|
||||
id: string;
|
||||
description?: string;
|
||||
@@ -455,15 +523,28 @@ export type TemplateAction<
|
||||
input?: Schema;
|
||||
output?: Schema;
|
||||
};
|
||||
handler: (ctx: ActionContext<TActionInput, TActionOutput>) => Promise<void>;
|
||||
handler: (
|
||||
ctx: ActionContext<TActionInput, TActionOutput, TSchemaType>,
|
||||
) => Promise<void>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type TemplateActionOptions<
|
||||
TActionInput extends JsonObject = {},
|
||||
TActionOutput extends JsonObject = {},
|
||||
TInputSchema extends Schema | z.ZodType = {},
|
||||
TOutputSchema extends Schema | z.ZodType = {},
|
||||
TInputSchema extends
|
||||
| JsonObject
|
||||
| z.ZodType
|
||||
| {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
} = JsonObject,
|
||||
TOutputSchema extends
|
||||
| JsonObject
|
||||
| z.ZodType
|
||||
| {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
} = JsonObject,
|
||||
TSchemaType extends 'v1' | 'v2' = 'v1' | 'v2',
|
||||
> = {
|
||||
id: string;
|
||||
description?: string;
|
||||
@@ -473,7 +554,9 @@ export type TemplateActionOptions<
|
||||
input?: TInputSchema;
|
||||
output?: TOutputSchema;
|
||||
};
|
||||
handler: (ctx: ActionContext<TActionInput, TActionOutput>) => Promise<void>;
|
||||
handler: (
|
||||
ctx: ActionContext<TActionInput, TActionOutput, TSchemaType>,
|
||||
) => Promise<void>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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 { createTemplateAction } from './createTemplateAction';
|
||||
import { z } from 'zod';
|
||||
|
||||
describe('createTemplateAction', () => {
|
||||
it('should allow creating with jsonschema and use the old deprecated types', () => {
|
||||
const action = createTemplateAction<{ repoUrl: string }, { test: string }>({
|
||||
id: 'test',
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
required: ['repoUrl'],
|
||||
properties: {
|
||||
repoUrl: { type: 'string' },
|
||||
},
|
||||
},
|
||||
output: {
|
||||
type: 'object',
|
||||
required: ['test'],
|
||||
properties: {
|
||||
test: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
handler: async ctx => {
|
||||
// @ts-expect-error - repoUrl is string
|
||||
const a: number = ctx.input.repoUrl;
|
||||
|
||||
const b: string = ctx.input.repoUrl;
|
||||
expect(b).toBeDefined();
|
||||
|
||||
const stream = ctx.logStream;
|
||||
expect(stream).toBeDefined();
|
||||
|
||||
ctx.output('test', 'value');
|
||||
|
||||
// @ts-expect-error - not valid output type
|
||||
ctx.output('test', 4);
|
||||
|
||||
// @ts-expect-error - not valid output name
|
||||
ctx.output('test2', 'value');
|
||||
},
|
||||
});
|
||||
|
||||
expect(action).toBeDefined();
|
||||
});
|
||||
|
||||
it('should allow creating with zod and use the old deprecated types', () => {
|
||||
const action = createTemplateAction({
|
||||
id: 'test',
|
||||
schema: {
|
||||
input: z.object({
|
||||
repoUrl: z.string(),
|
||||
}),
|
||||
output: z.object({
|
||||
test: z.string(),
|
||||
}),
|
||||
},
|
||||
handler: async ctx => {
|
||||
// @ts-expect-error - repoUrl is string
|
||||
const a: number = ctx.input.repoUrl;
|
||||
|
||||
const b: string = ctx.input.repoUrl;
|
||||
expect(b).toBeDefined();
|
||||
|
||||
const stream = ctx.logStream;
|
||||
expect(stream).toBeDefined();
|
||||
|
||||
ctx.output('test', 'value');
|
||||
|
||||
// @ts-expect-error - not valid output type
|
||||
ctx.output('test', 4);
|
||||
|
||||
// @ts-expect-error - not valid output name
|
||||
ctx.output('test2', 'value');
|
||||
},
|
||||
});
|
||||
|
||||
expect(action).toBeDefined();
|
||||
});
|
||||
|
||||
it('should allow creating with new first class zod support', () => {
|
||||
const action = createTemplateAction({
|
||||
id: 'test',
|
||||
schema: {
|
||||
input: {
|
||||
repoUrl: d => d.string(),
|
||||
},
|
||||
output: {
|
||||
test: d => d.string(),
|
||||
},
|
||||
},
|
||||
handler: async ctx => {
|
||||
// @ts-expect-error - repoUrl is string
|
||||
const a: number = ctx.input.repoUrl;
|
||||
|
||||
const b: string = ctx.input.repoUrl;
|
||||
expect(b).toBeDefined();
|
||||
|
||||
// @ts-expect-error - logStream is not available
|
||||
const stream = ctx.logStream;
|
||||
|
||||
expect(stream).toBeDefined();
|
||||
|
||||
ctx.output('test', 'value');
|
||||
|
||||
// @ts-expect-error - not valid output type
|
||||
ctx.output('test', 4);
|
||||
|
||||
// @ts-expect-error - not valid output name
|
||||
ctx.output('test2', 'value');
|
||||
},
|
||||
});
|
||||
|
||||
expect(action).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -16,9 +16,8 @@
|
||||
|
||||
import { ActionContext, TemplateAction } from './types';
|
||||
import { z } from 'zod';
|
||||
import { Schema } from 'jsonschema';
|
||||
import zodToJsonSchema from 'zod-to-json-schema';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { Expand, JsonObject } from '@backstage/types';
|
||||
import { parseSchemas } from './util';
|
||||
|
||||
/** @public */
|
||||
export type TemplateExample = {
|
||||
@@ -30,8 +29,15 @@ export type TemplateExample = {
|
||||
export type TemplateActionOptions<
|
||||
TActionInput extends JsonObject = {},
|
||||
TActionOutput extends JsonObject = {},
|
||||
TInputSchema extends Schema | z.ZodType = {},
|
||||
TOutputSchema extends Schema | z.ZodType = {},
|
||||
TInputSchema extends
|
||||
| JsonObject
|
||||
| z.ZodType
|
||||
| { [key in string]: (zImpl: typeof z) => z.ZodType } = JsonObject,
|
||||
TOutputSchema extends
|
||||
| JsonObject
|
||||
| z.ZodType
|
||||
| { [key in string]: (zImpl: typeof z) => z.ZodType } = JsonObject,
|
||||
TSchemaType extends 'v1' | 'v2' = 'v1' | 'v2',
|
||||
> = {
|
||||
id: string;
|
||||
description?: string;
|
||||
@@ -41,25 +47,112 @@ export type TemplateActionOptions<
|
||||
input?: TInputSchema;
|
||||
output?: TOutputSchema;
|
||||
};
|
||||
handler: (ctx: ActionContext<TActionInput, TActionOutput>) => Promise<void>;
|
||||
handler: (
|
||||
ctx: ActionContext<TActionInput, TActionOutput, TSchemaType>,
|
||||
) => Promise<void>;
|
||||
};
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
type FlattenOptionalProperties<T extends { [key in string]: unknown }> = Expand<
|
||||
{
|
||||
[K in keyof T as undefined extends T[K] ? never : K]: T[K];
|
||||
} & {
|
||||
[K in keyof T as undefined extends T[K] ? K : never]?: T[K];
|
||||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated migrate to using the new built in zod schema definitions for schemas
|
||||
*/
|
||||
export function createTemplateAction<
|
||||
TInputParams extends JsonObject = JsonObject,
|
||||
TOutputParams extends JsonObject = JsonObject,
|
||||
TInputSchema extends JsonObject = JsonObject,
|
||||
TOutputSchema extends JsonObject = JsonObject,
|
||||
TActionInput extends JsonObject = TInputParams,
|
||||
TActionOutput extends JsonObject = TOutputParams,
|
||||
>(
|
||||
action: TemplateActionOptions<
|
||||
TActionInput,
|
||||
TActionOutput,
|
||||
TInputSchema,
|
||||
TOutputSchema,
|
||||
'v1'
|
||||
>,
|
||||
): TemplateAction<TActionInput, TActionOutput, 'v1'>;
|
||||
/**
|
||||
* @public
|
||||
* @deprecated migrate to using the new built in zod schema definitions for schemas
|
||||
*/
|
||||
export function createTemplateAction<
|
||||
TInputParams extends JsonObject = JsonObject,
|
||||
TOutputParams extends JsonObject = JsonObject,
|
||||
TInputSchema extends z.ZodType = z.ZodType,
|
||||
TOutputSchema extends z.ZodType = z.ZodType,
|
||||
TActionInput extends JsonObject = z.infer<TInputSchema>,
|
||||
TActionOutput extends JsonObject = z.infer<TOutputSchema>,
|
||||
>(
|
||||
action: TemplateActionOptions<
|
||||
TActionInput,
|
||||
TActionOutput,
|
||||
TInputSchema,
|
||||
TOutputSchema,
|
||||
'v1'
|
||||
>,
|
||||
): TemplateAction<TActionInput, TActionOutput, 'v1'>;
|
||||
/**
|
||||
* This function is used to create new template actions to get type safety.
|
||||
* Will convert zod schemas to json schemas for use throughout the system.
|
||||
* @public
|
||||
*/
|
||||
export const createTemplateAction = <
|
||||
export function createTemplateAction<
|
||||
TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType },
|
||||
TOutputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType },
|
||||
>(
|
||||
action: TemplateActionOptions<
|
||||
{
|
||||
[key in keyof TInputSchema]: z.infer<ReturnType<TInputSchema[key]>>;
|
||||
},
|
||||
{
|
||||
[key in keyof TOutputSchema]: z.infer<ReturnType<TOutputSchema[key]>>;
|
||||
},
|
||||
TInputSchema,
|
||||
TOutputSchema,
|
||||
'v2'
|
||||
>,
|
||||
): TemplateAction<
|
||||
FlattenOptionalProperties<{
|
||||
[key in keyof TInputSchema]: z.output<ReturnType<TInputSchema[key]>>;
|
||||
}>,
|
||||
FlattenOptionalProperties<{
|
||||
[key in keyof TOutputSchema]: z.output<ReturnType<TOutputSchema[key]>>;
|
||||
}>,
|
||||
'v2'
|
||||
>;
|
||||
export function createTemplateAction<
|
||||
TInputParams extends JsonObject = JsonObject,
|
||||
TOutputParams extends JsonObject = JsonObject,
|
||||
TInputSchema extends Schema | z.ZodType = {},
|
||||
TOutputSchema extends Schema | z.ZodType = {},
|
||||
TInputSchema extends
|
||||
| JsonObject
|
||||
| z.ZodType
|
||||
| { [key in string]: (zImpl: typeof z) => z.ZodType } = JsonObject,
|
||||
TOutputSchema extends
|
||||
| JsonObject
|
||||
| z.ZodType
|
||||
| { [key in string]: (zImpl: typeof z) => z.ZodType } = JsonObject,
|
||||
TActionInput extends JsonObject = TInputSchema extends z.ZodType<
|
||||
any,
|
||||
any,
|
||||
infer IReturn
|
||||
>
|
||||
? IReturn
|
||||
: TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }
|
||||
? Expand<{
|
||||
[key in keyof TInputSchema]: z.infer<ReturnType<TInputSchema[key]>>;
|
||||
}>
|
||||
: TInputParams,
|
||||
TActionOutput extends JsonObject = TOutputSchema extends z.ZodType<
|
||||
any,
|
||||
@@ -67,6 +160,10 @@ export const createTemplateAction = <
|
||||
infer IReturn
|
||||
>
|
||||
? IReturn
|
||||
: TOutputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }
|
||||
? Expand<{
|
||||
[key in keyof TOutputSchema]: z.infer<ReturnType<TOutputSchema[key]>>;
|
||||
}>
|
||||
: TOutputParams,
|
||||
>(
|
||||
action: TemplateActionOptions<
|
||||
@@ -75,23 +172,23 @@ export const createTemplateAction = <
|
||||
TInputSchema,
|
||||
TOutputSchema
|
||||
>,
|
||||
): TemplateAction<TActionInput, TActionOutput> => {
|
||||
const inputSchema =
|
||||
action.schema?.input && 'safeParseAsync' in action.schema.input
|
||||
? zodToJsonSchema(action.schema.input)
|
||||
: action.schema?.input;
|
||||
|
||||
const outputSchema =
|
||||
action.schema?.output && 'safeParseAsync' in action.schema.output
|
||||
? zodToJsonSchema(action.schema.output)
|
||||
: action.schema?.output;
|
||||
): TemplateAction<
|
||||
TActionInput,
|
||||
TActionOutput,
|
||||
TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }
|
||||
? 'v2'
|
||||
: 'v1'
|
||||
> {
|
||||
const { inputSchema, outputSchema } = parseSchemas(
|
||||
action as TemplateActionOptions<any, any, any>,
|
||||
);
|
||||
|
||||
return {
|
||||
...action,
|
||||
schema: {
|
||||
...action.schema,
|
||||
input: inputSchema as TInputSchema,
|
||||
output: outputSchema as TOutputSchema,
|
||||
input: inputSchema,
|
||||
output: outputSchema,
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -21,8 +21,10 @@ import { TaskSecrets } from '../tasks';
|
||||
import { TemplateInfo } from '@backstage/plugin-scaffolder-common';
|
||||
import { UserEntity } from '@backstage/catalog-model';
|
||||
import { Schema } from 'jsonschema';
|
||||
import { BackstageCredentials } from '@backstage/backend-plugin-api';
|
||||
|
||||
import {
|
||||
BackstageCredentials,
|
||||
LoggerService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
/**
|
||||
* ActionContext is passed into scaffolder actions.
|
||||
* @public
|
||||
@@ -30,77 +32,143 @@ import { BackstageCredentials } from '@backstage/backend-plugin-api';
|
||||
export type ActionContext<
|
||||
TActionInput extends JsonObject,
|
||||
TActionOutput extends JsonObject = JsonObject,
|
||||
> = {
|
||||
// TODO(blam): move this to LoggerService
|
||||
logger: Logger;
|
||||
/** @deprecated - use `ctx.logger` instead */
|
||||
logStream: Writable;
|
||||
secrets?: TaskSecrets;
|
||||
workspacePath: string;
|
||||
input: TActionInput;
|
||||
checkpoint<T extends JsonValue | void>(opts: {
|
||||
key: string;
|
||||
fn: () => Promise<T> | T;
|
||||
}): Promise<T>;
|
||||
output(
|
||||
name: keyof TActionOutput,
|
||||
value: TActionOutput[keyof TActionOutput],
|
||||
): void;
|
||||
TSchemaType extends 'v1' | 'v2' = 'v1',
|
||||
> = TSchemaType extends 'v2'
|
||||
? {
|
||||
logger: LoggerService;
|
||||
secrets?: TaskSecrets;
|
||||
workspacePath: string;
|
||||
input: TActionInput;
|
||||
checkpoint<T extends JsonValue | void>(opts: {
|
||||
key: string;
|
||||
fn: () => Promise<T> | T;
|
||||
}): Promise<T>;
|
||||
output(
|
||||
name: keyof TActionOutput,
|
||||
value: TActionOutput[keyof TActionOutput],
|
||||
): void;
|
||||
/**
|
||||
* Creates a temporary directory for use by the action, which is then cleaned up automatically.
|
||||
*/
|
||||
createTemporaryDirectory(): Promise<string>;
|
||||
|
||||
/**
|
||||
* Creates a temporary directory for use by the action, which is then cleaned up automatically.
|
||||
*/
|
||||
createTemporaryDirectory(): Promise<string>;
|
||||
/**
|
||||
* Get the credentials for the current request
|
||||
*/
|
||||
getInitiatorCredentials(): Promise<BackstageCredentials>;
|
||||
|
||||
/**
|
||||
* Get the credentials for the current request
|
||||
*/
|
||||
getInitiatorCredentials(): Promise<BackstageCredentials>;
|
||||
/**
|
||||
* Task information
|
||||
*/
|
||||
task: {
|
||||
id: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Task information
|
||||
*/
|
||||
task: {
|
||||
id: string;
|
||||
};
|
||||
templateInfo?: TemplateInfo;
|
||||
|
||||
templateInfo?: TemplateInfo;
|
||||
/**
|
||||
* Whether this action invocation is a dry-run or not.
|
||||
* This will only ever be true if the actions as marked as supporting dry-runs.
|
||||
*/
|
||||
isDryRun?: boolean;
|
||||
|
||||
/**
|
||||
* Whether this action invocation is a dry-run or not.
|
||||
* This will only ever be true if the actions as marked as supporting dry-runs.
|
||||
*/
|
||||
isDryRun?: boolean;
|
||||
/**
|
||||
* The user which triggered the action.
|
||||
*/
|
||||
user?: {
|
||||
/**
|
||||
* The decorated entity from the Catalog
|
||||
*/
|
||||
entity?: UserEntity;
|
||||
/**
|
||||
* An entity ref for the author of the task
|
||||
*/
|
||||
ref?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* The user which triggered the action.
|
||||
*/
|
||||
user?: {
|
||||
/**
|
||||
* The decorated entity from the Catalog
|
||||
*/
|
||||
entity?: UserEntity;
|
||||
/**
|
||||
* An entity ref for the author of the task
|
||||
*/
|
||||
ref?: string;
|
||||
};
|
||||
/**
|
||||
* Implement the signal to make your custom step abortable https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal
|
||||
*/
|
||||
signal?: AbortSignal;
|
||||
|
||||
/**
|
||||
* Implement the signal to make your custom step abortable https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal
|
||||
*/
|
||||
signal?: AbortSignal;
|
||||
/**
|
||||
* Optional value of each invocation
|
||||
*/
|
||||
each?: JsonObject;
|
||||
}
|
||||
: /** @deprecated **/
|
||||
{
|
||||
// TODO(blam): move this to LoggerService
|
||||
logger: Logger;
|
||||
/** @deprecated - use `ctx.logger` instead */
|
||||
logStream: Writable;
|
||||
secrets?: TaskSecrets;
|
||||
workspacePath: string;
|
||||
input: TActionInput;
|
||||
checkpoint<T extends JsonValue | void>(opts: {
|
||||
key: string;
|
||||
fn: () => Promise<T> | T;
|
||||
}): Promise<T>;
|
||||
output(
|
||||
name: keyof TActionOutput,
|
||||
value: TActionOutput[keyof TActionOutput],
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Optional value of each invocation
|
||||
*/
|
||||
each?: JsonObject;
|
||||
};
|
||||
/**
|
||||
* Creates a temporary directory for use by the action, which is then cleaned up automatically.
|
||||
*/
|
||||
createTemporaryDirectory(): Promise<string>;
|
||||
|
||||
/**
|
||||
* Get the credentials for the current request
|
||||
*/
|
||||
getInitiatorCredentials(): Promise<BackstageCredentials>;
|
||||
|
||||
/**
|
||||
* Task information
|
||||
*/
|
||||
task: {
|
||||
id: string;
|
||||
};
|
||||
|
||||
templateInfo?: TemplateInfo;
|
||||
|
||||
/**
|
||||
* Whether this action invocation is a dry-run or not.
|
||||
* This will only ever be true if the actions as marked as supporting dry-runs.
|
||||
*/
|
||||
isDryRun?: boolean;
|
||||
|
||||
/**
|
||||
* The user which triggered the action.
|
||||
*/
|
||||
user?: {
|
||||
/**
|
||||
* The decorated entity from the Catalog
|
||||
*/
|
||||
entity?: UserEntity;
|
||||
/**
|
||||
* An entity ref for the author of the task
|
||||
*/
|
||||
ref?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Implement the signal to make your custom step abortable https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal
|
||||
*/
|
||||
signal?: AbortSignal;
|
||||
|
||||
/**
|
||||
* Optional value of each invocation
|
||||
*/
|
||||
each?: JsonObject;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type TemplateAction<
|
||||
TActionInput extends JsonObject = JsonObject,
|
||||
TActionOutput extends JsonObject = JsonObject,
|
||||
TSchemaType extends 'v1' | 'v2' = 'v1',
|
||||
> = {
|
||||
id: string;
|
||||
description?: string;
|
||||
@@ -110,5 +178,7 @@ export type TemplateAction<
|
||||
input?: Schema;
|
||||
output?: Schema;
|
||||
};
|
||||
handler: (ctx: ActionContext<TActionInput, TActionOutput>) => Promise<void>;
|
||||
handler: (
|
||||
ctx: ActionContext<TActionInput, TActionOutput, TSchemaType>,
|
||||
) => Promise<void>;
|
||||
};
|
||||
|
||||
@@ -18,6 +18,10 @@ import { InputError } from '@backstage/errors';
|
||||
import { isChildPath } from '@backstage/backend-plugin-api';
|
||||
import { join as joinPath, normalize as normalizePath } from 'path';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { TemplateActionOptions } from './createTemplateAction';
|
||||
import zodToJsonSchema from 'zod-to-json-schema';
|
||||
import { z } from 'zod';
|
||||
import { Schema } from 'jsonschema';
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -124,3 +128,60 @@ function checkRequiredParams(repoUrl: URL, ...params: string[]) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isZodSchema = (schema: unknown): schema is z.ZodType => {
|
||||
return typeof schema === 'object' && !!schema && 'safeParseAsync' in schema;
|
||||
};
|
||||
|
||||
const isNativeZodSchema = (
|
||||
schema: unknown,
|
||||
): schema is { [key in string]: (zImpl: typeof z) => z.ZodType } => {
|
||||
return (
|
||||
typeof schema === 'object' &&
|
||||
!!schema &&
|
||||
Object.values(schema).every(v => typeof v === 'function')
|
||||
);
|
||||
};
|
||||
|
||||
export const parseSchemas = (
|
||||
action: TemplateActionOptions,
|
||||
): { inputSchema?: Schema; outputSchema?: Schema } => {
|
||||
if (!action.schema) {
|
||||
return { inputSchema: undefined, outputSchema: undefined };
|
||||
}
|
||||
|
||||
if (isZodSchema(action.schema.input)) {
|
||||
return {
|
||||
inputSchema: zodToJsonSchema(action.schema.input) as Schema,
|
||||
outputSchema: isZodSchema(action.schema.output)
|
||||
? (zodToJsonSchema(action.schema.output) as Schema)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
if (isNativeZodSchema(action.schema.input)) {
|
||||
const input = z.object(
|
||||
Object.fromEntries(
|
||||
Object.entries(action.schema.input).map(([k, v]) => [k, v(z)]),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
inputSchema: zodToJsonSchema(input) as Schema,
|
||||
outputSchema: isNativeZodSchema(action.schema.output)
|
||||
? (zodToJsonSchema(
|
||||
z.object(
|
||||
Object.fromEntries(
|
||||
Object.entries(action.schema.output).map(([k, v]) => [k, v(z)]),
|
||||
),
|
||||
),
|
||||
) as Schema)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
inputSchema: action.schema.input,
|
||||
outputSchema: action.schema.output,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -34,7 +34,7 @@ export * from './globals';
|
||||
* @alpha
|
||||
*/
|
||||
export interface ScaffolderActionsExtensionPoint {
|
||||
addActions(...actions: TemplateAction<any, any>[]): void;
|
||||
addActions(...actions: TemplateAction<any, any, any>[]): void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7720,7 +7720,7 @@ __metadata:
|
||||
globby: ^11.0.0
|
||||
isbinaryfile: ^5.0.0
|
||||
isolated-vm: ^5.0.1
|
||||
jsonschema: ^1.2.6
|
||||
jsonschema: ^1.5.0
|
||||
knex: ^3.0.0
|
||||
lodash: ^4.17.21
|
||||
logform: ^2.3.2
|
||||
@@ -7799,7 +7799,7 @@ __metadata:
|
||||
fs-extra: ^11.2.0
|
||||
globby: ^11.0.0
|
||||
isomorphic-git: ^1.23.0
|
||||
jsonschema: ^1.2.6
|
||||
jsonschema: ^1.5.0
|
||||
p-limit: ^3.1.0
|
||||
tar: ^6.1.12
|
||||
winston: ^3.2.1
|
||||
@@ -34355,10 +34355,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jsonschema@npm:^1.2.6":
|
||||
version: 1.4.1
|
||||
resolution: "jsonschema@npm:1.4.1"
|
||||
checksum: 1ef02a6cd9bc32241ec86bbf1300bdbc3b5f2d8df6eb795517cf7d1cd9909e7beba1e54fdf73990fd66be98a182bda9add9607296b0cb00b1348212988e424b2
|
||||
"jsonschema@npm:^1.2.6, jsonschema@npm:^1.5.0":
|
||||
version: 1.5.0
|
||||
resolution: "jsonschema@npm:1.5.0"
|
||||
checksum: 170b9c375967bc135f4d029fedc31f5686f2c3bb07e7472cebddbb907b5369bf75a1a50287d6af9c31f61c76fe0b7786e78044c188aaddd329b77d856475e6db
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user