Merge pull request #30142 from backstage/blam/actions-cleanup/14
`scaffolder-backend`: last of the actions migrated, and removal of the old `TemplateAction` types
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-node-test-utils': minor
|
||||
---
|
||||
|
||||
**BREAKING CHANGES**
|
||||
|
||||
Because of the removal of the `logStream` property to the `ActionsContext` this has been removed from the `createMockActionContext` method.
|
||||
|
||||
You can remove this as it's no longer supported in the scaffolder actions.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
---
|
||||
|
||||
Migrating to latest action format
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-node': minor
|
||||
---
|
||||
|
||||
**BREAKING CHANGES**
|
||||
|
||||
The legacy methods to define `createTemplateActions` have been replaced with the new native `zod` approaches for defining input and output schemas.
|
||||
|
||||
You can migrate actions that look like the following with the below examples:
|
||||
|
||||
```ts
|
||||
// really old legacy json schema
|
||||
createTemplateAction<{ repoUrl: string }, { repoOutput: string }>({
|
||||
id: 'test',
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object'
|
||||
required: ['repoUrl']
|
||||
properties: {
|
||||
repoUrl: {
|
||||
type: 'string',
|
||||
description: 'repository url description'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// old zod method
|
||||
createTemplateAction({
|
||||
id: 'test'
|
||||
schema: {
|
||||
input: {
|
||||
repoUrl: z.string({ description: 'repository url description' })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// new method:
|
||||
createTemplateAction({
|
||||
id: 'test',
|
||||
schema: {
|
||||
input: {
|
||||
repoUrl: z => z.string({ description: 'repository url description' })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// or for more complex zod types like unions
|
||||
createTemplateAction({
|
||||
id: 'test',
|
||||
schema: {
|
||||
input: z => z.object({
|
||||
repoUrl: z.string({ description: 'repository url description' })
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
This breaking change also means that `logStream` has been removed entirely from `ActionsContext`, and that the `logger` is now just a `LoggerService` implementation instead. There is no replacement for the `logStream`, if you wish to still keep using a `logStream` we recommend that you create your own stream that writes to `ctx.logger` instead.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Updating the scaffolder action boilerplate to use new `zod` schema
|
||||
@@ -12,22 +12,16 @@ import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
export function createExampleAction() {
|
||||
// For more information on how to define custom actions, see
|
||||
// https://backstage.io/docs/features/software-templates/writing-custom-actions
|
||||
return createTemplateAction<{
|
||||
myParameter: string;
|
||||
}>({
|
||||
return createTemplateAction({
|
||||
id: 'acme:example',
|
||||
description: 'Runs an example action',
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
required: ['myParameter'],
|
||||
properties: {
|
||||
myParameter: {
|
||||
title: 'An example parameter',
|
||||
description: "This is an example parameter, don't set it to foo",
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
myParameter: z =>
|
||||
z.string({
|
||||
description:
|
||||
"This is an example parameter, don't set it to foo",
|
||||
}),
|
||||
},
|
||||
},
|
||||
async handler(ctx) {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
```ts
|
||||
import { BackendFeature } from '@backstage/backend-plugin-api';
|
||||
import { Config } from '@backstage/config';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { TemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
|
||||
@@ -103,14 +102,16 @@ export function createPublishBitbucketCloudPullRequestAction(options: {
|
||||
{
|
||||
repoUrl: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
targetBranch?: string;
|
||||
sourceBranch: string;
|
||||
token?: string;
|
||||
gitAuthorName?: string;
|
||||
gitAuthorEmail?: string;
|
||||
description?: string | undefined;
|
||||
targetBranch?: string | undefined;
|
||||
token?: string | undefined;
|
||||
gitAuthorName?: string | undefined;
|
||||
gitAuthorEmail?: string | undefined;
|
||||
},
|
||||
JsonObject,
|
||||
'v1'
|
||||
{
|
||||
pullRequestUrl: string;
|
||||
},
|
||||
'v2'
|
||||
>;
|
||||
```
|
||||
|
||||
+48
-59
@@ -139,6 +139,7 @@ const findBranches = async (opts: {
|
||||
|
||||
return r.values[0];
|
||||
};
|
||||
|
||||
const createBranch = async (opts: {
|
||||
workspace: string;
|
||||
repo: string;
|
||||
@@ -190,6 +191,7 @@ const createBranch = async (opts: {
|
||||
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
const getDefaultBranch = async (opts: {
|
||||
workspace: string;
|
||||
repo: string;
|
||||
@@ -233,73 +235,60 @@ export function createPublishBitbucketCloudPullRequestAction(options: {
|
||||
}) {
|
||||
const { integrations, config } = options;
|
||||
|
||||
return createTemplateAction<{
|
||||
repoUrl: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
targetBranch?: string;
|
||||
sourceBranch: string;
|
||||
token?: string;
|
||||
gitAuthorName?: string;
|
||||
gitAuthorEmail?: string;
|
||||
}>({
|
||||
return createTemplateAction({
|
||||
id: 'publish:bitbucketCloud:pull-request',
|
||||
examples,
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
required: ['repoUrl', 'title', 'sourceBranch'],
|
||||
properties: {
|
||||
repoUrl: {
|
||||
title: 'Repository Location',
|
||||
type: 'string',
|
||||
},
|
||||
title: {
|
||||
title: 'Pull Request title',
|
||||
type: 'string',
|
||||
repoUrl: z =>
|
||||
z.string({
|
||||
description: 'Repository Location',
|
||||
}),
|
||||
title: z =>
|
||||
z.string({
|
||||
description: 'The title for the pull request',
|
||||
},
|
||||
description: {
|
||||
title: 'Pull Request Description',
|
||||
type: 'string',
|
||||
description: 'The description of the pull request',
|
||||
},
|
||||
targetBranch: {
|
||||
title: 'Target Branch',
|
||||
type: 'string',
|
||||
description: `Branch of repository to apply changes to. The default value is 'master'`,
|
||||
},
|
||||
sourceBranch: {
|
||||
title: 'Source Branch',
|
||||
type: 'string',
|
||||
}),
|
||||
description: z =>
|
||||
z
|
||||
.string({
|
||||
description: 'The description of the pull request',
|
||||
})
|
||||
.optional(),
|
||||
targetBranch: z =>
|
||||
z
|
||||
.string({
|
||||
description: `Branch of repository to apply changes to. The default value is 'master'`,
|
||||
})
|
||||
.optional(),
|
||||
sourceBranch: z =>
|
||||
z.string({
|
||||
description: 'Branch of repository to copy changes from',
|
||||
},
|
||||
token: {
|
||||
title: 'Authorization Token',
|
||||
type: 'string',
|
||||
description:
|
||||
'The token to use for authorization to BitBucket Cloud',
|
||||
},
|
||||
gitAuthorName: {
|
||||
title: 'Author Name',
|
||||
type: 'string',
|
||||
description: `Sets the author name for the commit. The default value is 'Scaffolder'`,
|
||||
},
|
||||
gitAuthorEmail: {
|
||||
title: 'Author Email',
|
||||
type: 'string',
|
||||
description: `Sets the author email for the commit.`,
|
||||
},
|
||||
},
|
||||
}),
|
||||
token: z =>
|
||||
z
|
||||
.string({
|
||||
description:
|
||||
'The token to use for authorization to BitBucket Cloud',
|
||||
})
|
||||
.optional(),
|
||||
gitAuthorName: z =>
|
||||
z
|
||||
.string({
|
||||
description: `Sets the author name for the commit. The default value is 'Scaffolder'`,
|
||||
})
|
||||
.optional(),
|
||||
gitAuthorEmail: z =>
|
||||
z
|
||||
.string({
|
||||
description: `Sets the author email for the commit.`,
|
||||
})
|
||||
.optional(),
|
||||
},
|
||||
output: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
pullRequestUrl: {
|
||||
title: 'A URL to the pull request with the provider',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
pullRequestUrl: z =>
|
||||
z.string({
|
||||
description: 'A URL to the pull request with the provider',
|
||||
}),
|
||||
},
|
||||
},
|
||||
async handler(ctx) {
|
||||
|
||||
@@ -97,7 +97,7 @@ describe('createPublishGithubPullRequestAction', () => {
|
||||
|
||||
describe('with targetBranchName', () => {
|
||||
let input: GithubPullRequestActionInput;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput>;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput, any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
fakeClient = {
|
||||
@@ -181,7 +181,7 @@ describe('createPublishGithubPullRequestAction', () => {
|
||||
|
||||
describe('with no sourcePath', () => {
|
||||
let input: GithubPullRequestActionInput;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput>;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput, any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
input = {
|
||||
@@ -250,7 +250,7 @@ describe('createPublishGithubPullRequestAction', () => {
|
||||
|
||||
describe('with sourcePath', () => {
|
||||
let input: GithubPullRequestActionInput;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput>;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput, any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
input = {
|
||||
@@ -306,7 +306,7 @@ describe('createPublishGithubPullRequestAction', () => {
|
||||
|
||||
describe('with repoUrl', () => {
|
||||
let input: GithubPullRequestActionInput;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput>;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput, any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
input = {
|
||||
@@ -359,7 +359,7 @@ describe('createPublishGithubPullRequestAction', () => {
|
||||
|
||||
describe('with reviewers and teamReviewers', () => {
|
||||
let input: GithubPullRequestActionInput;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput>;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput, any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
input = {
|
||||
@@ -406,7 +406,7 @@ describe('createPublishGithubPullRequestAction', () => {
|
||||
|
||||
describe('with no reviewers and teamReviewers', () => {
|
||||
let input: GithubPullRequestActionInput;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput>;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput, any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
input = {
|
||||
@@ -431,7 +431,7 @@ describe('createPublishGithubPullRequestAction', () => {
|
||||
|
||||
describe('with assignees', () => {
|
||||
let input: GithubPullRequestActionInput;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput>;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput, any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
input = {
|
||||
@@ -475,7 +475,7 @@ describe('createPublishGithubPullRequestAction', () => {
|
||||
|
||||
describe('with broken symlink', () => {
|
||||
let input: GithubPullRequestActionInput;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput>;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput, any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
input = {
|
||||
@@ -520,7 +520,7 @@ describe('createPublishGithubPullRequestAction', () => {
|
||||
|
||||
describe('with executable file mode 755', () => {
|
||||
let input: GithubPullRequestActionInput;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput>;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput, any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
input = {
|
||||
@@ -579,7 +579,7 @@ describe('createPublishGithubPullRequestAction', () => {
|
||||
|
||||
describe('with executable file mode 775', () => {
|
||||
let input: GithubPullRequestActionInput;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput>;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput, any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
input = {
|
||||
@@ -638,7 +638,7 @@ describe('createPublishGithubPullRequestAction', () => {
|
||||
|
||||
describe('with commit message', () => {
|
||||
let input: GithubPullRequestActionInput;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput>;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput, any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
input = {
|
||||
@@ -683,7 +683,7 @@ describe('createPublishGithubPullRequestAction', () => {
|
||||
|
||||
describe('with force fork', () => {
|
||||
let input: GithubPullRequestActionInput;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput>;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput, any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
input = {
|
||||
@@ -729,7 +729,7 @@ describe('createPublishGithubPullRequestAction', () => {
|
||||
|
||||
describe('with author name and email', () => {
|
||||
let input: GithubPullRequestActionInput;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput>;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput, any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
input = {
|
||||
@@ -779,7 +779,7 @@ describe('createPublishGithubPullRequestAction', () => {
|
||||
|
||||
describe('with author name', () => {
|
||||
let input: GithubPullRequestActionInput;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput>;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput, any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
input = {
|
||||
@@ -828,7 +828,7 @@ describe('createPublishGithubPullRequestAction', () => {
|
||||
|
||||
describe('with author email', () => {
|
||||
let input: GithubPullRequestActionInput;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput>;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput, any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
input = {
|
||||
@@ -877,7 +877,7 @@ describe('createPublishGithubPullRequestAction', () => {
|
||||
|
||||
describe('with author from config file', () => {
|
||||
let input: GithubPullRequestActionInput;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput>;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput, any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
input = {
|
||||
@@ -946,7 +946,7 @@ describe('createPublishGithubPullRequestAction', () => {
|
||||
|
||||
describe('with author attributes and config file', () => {
|
||||
let input: GithubPullRequestActionInput;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput>;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput, any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
input = {
|
||||
@@ -1017,7 +1017,7 @@ describe('createPublishGithubPullRequestAction', () => {
|
||||
|
||||
describe('with author fallback and no config', () => {
|
||||
let input: GithubPullRequestActionInput;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput>;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput, any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
input = {
|
||||
@@ -1112,7 +1112,7 @@ describe('createPublishGithubPullRequestAction', () => {
|
||||
|
||||
describe('with createWhenEmpty equals true', () => {
|
||||
let input: GithubPullRequestActionInput;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput>;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput, any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
input = {
|
||||
@@ -1175,7 +1175,7 @@ describe('createPublishGithubPullRequestAction', () => {
|
||||
|
||||
describe('with createWhenEmpty equals false', () => {
|
||||
let input: GithubPullRequestActionInput;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput>;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput, any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
fakeClient.createPullRequest.mockResolvedValueOnce(null);
|
||||
|
||||
@@ -76,15 +76,26 @@ export function createCatalogRegisterAction(options: {
|
||||
}): TemplateAction<
|
||||
| {
|
||||
catalogInfoUrl: string;
|
||||
optional?: boolean;
|
||||
optional?: boolean | undefined;
|
||||
}
|
||||
| {
|
||||
catalogInfoUrl: string;
|
||||
optional?: boolean | undefined;
|
||||
catalogInfoPath?: string | undefined;
|
||||
}
|
||||
| {
|
||||
repoContentsUrl: string;
|
||||
catalogInfoPath?: string;
|
||||
optional?: boolean;
|
||||
optional?: boolean | undefined;
|
||||
}
|
||||
| {
|
||||
repoContentsUrl: string;
|
||||
optional?: boolean | undefined;
|
||||
catalogInfoPath?: string | undefined;
|
||||
},
|
||||
JsonObject,
|
||||
'v1'
|
||||
{
|
||||
[x: string]: any;
|
||||
},
|
||||
'v2'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -93,12 +104,23 @@ export function createCatalogWriteAction(): TemplateAction<
|
||||
entity: Record<string, any>;
|
||||
filePath?: string | undefined;
|
||||
},
|
||||
any,
|
||||
'v1'
|
||||
{
|
||||
[x: string]: any;
|
||||
},
|
||||
'v2'
|
||||
>;
|
||||
|
||||
// @public
|
||||
export function createDebugLogAction(): TemplateAction<any, any, 'v1'>;
|
||||
export function createDebugLogAction(): TemplateAction<
|
||||
{
|
||||
message?: string | undefined;
|
||||
listWorkspace?: boolean | 'with-contents' | 'with-filenames' | undefined;
|
||||
},
|
||||
{
|
||||
[x: string]: any;
|
||||
},
|
||||
'v2'
|
||||
>;
|
||||
|
||||
// @public
|
||||
export function createFetchCatalogEntityAction(options: {
|
||||
@@ -126,11 +148,13 @@ export function createFetchPlainAction(options: {
|
||||
}): TemplateAction<
|
||||
{
|
||||
url: string;
|
||||
targetPath?: string;
|
||||
token?: string;
|
||||
targetPath?: string | undefined;
|
||||
token?: string | undefined;
|
||||
},
|
||||
JsonObject,
|
||||
'v1'
|
||||
{
|
||||
[x: string]: any;
|
||||
},
|
||||
'v2'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -141,10 +165,12 @@ export function createFetchPlainFileAction(options: {
|
||||
{
|
||||
url: string;
|
||||
targetPath: string;
|
||||
token?: string;
|
||||
token?: string | undefined;
|
||||
},
|
||||
JsonObject,
|
||||
'v1'
|
||||
{
|
||||
[x: string]: any;
|
||||
},
|
||||
'v2'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -156,18 +182,21 @@ export function createFetchTemplateAction(options: {
|
||||
}): TemplateAction<
|
||||
{
|
||||
url: string;
|
||||
targetPath?: string;
|
||||
values: any;
|
||||
templateFileExtension?: string | boolean;
|
||||
copyWithoutTemplating?: string[];
|
||||
cookiecutterCompat?: boolean;
|
||||
replace?: boolean;
|
||||
trimBlocks?: boolean;
|
||||
lstripBlocks?: boolean;
|
||||
token?: string;
|
||||
targetPath?: string | undefined;
|
||||
values?: Record<string, any> | undefined;
|
||||
copyWithoutRender?: string[] | undefined;
|
||||
copyWithoutTemplating?: string[] | undefined;
|
||||
cookiecutterCompat?: boolean | undefined;
|
||||
templateFileExtension?: string | boolean | undefined;
|
||||
replace?: boolean | undefined;
|
||||
trimBlocks?: boolean | undefined;
|
||||
lstripBlocks?: boolean | undefined;
|
||||
token?: string | undefined;
|
||||
},
|
||||
JsonObject,
|
||||
'v1'
|
||||
{
|
||||
[x: string]: any;
|
||||
},
|
||||
'v2'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -180,15 +209,17 @@ export function createFetchTemplateFileAction(options: {
|
||||
{
|
||||
url: string;
|
||||
targetPath: string;
|
||||
values: any;
|
||||
cookiecutterCompat?: boolean;
|
||||
replace?: boolean;
|
||||
trimBlocks?: boolean;
|
||||
lstripBlocks?: boolean;
|
||||
token?: string;
|
||||
values?: Record<string, any> | undefined;
|
||||
cookiecutterCompat?: boolean | undefined;
|
||||
replace?: boolean | undefined;
|
||||
trimBlocks?: boolean | undefined;
|
||||
lstripBlocks?: boolean | undefined;
|
||||
token?: string | undefined;
|
||||
},
|
||||
JsonObject,
|
||||
'v1'
|
||||
{
|
||||
[x: string]: any;
|
||||
},
|
||||
'v2'
|
||||
>;
|
||||
|
||||
// @public
|
||||
@@ -196,15 +227,17 @@ export const createFilesystemDeleteAction: () => TemplateAction<
|
||||
{
|
||||
files: string[];
|
||||
},
|
||||
JsonObject,
|
||||
'v1'
|
||||
{
|
||||
[x: string]: any;
|
||||
},
|
||||
'v2'
|
||||
>;
|
||||
|
||||
// @public
|
||||
export const createFilesystemReadDirAction: () => TemplateAction<
|
||||
{
|
||||
recursive: boolean;
|
||||
paths: string[];
|
||||
recursive: boolean;
|
||||
},
|
||||
{
|
||||
files: {
|
||||
@@ -218,26 +251,38 @@ export const createFilesystemReadDirAction: () => TemplateAction<
|
||||
fullPath: string;
|
||||
}[];
|
||||
},
|
||||
'v1'
|
||||
'v2'
|
||||
>;
|
||||
|
||||
// @public
|
||||
export const createFilesystemRenameAction: () => TemplateAction<
|
||||
{
|
||||
files: Array<{
|
||||
files: {
|
||||
from: string;
|
||||
to: string;
|
||||
overwrite?: boolean;
|
||||
}>;
|
||||
overwrite?: boolean | undefined;
|
||||
}[];
|
||||
},
|
||||
JsonObject,
|
||||
'v1'
|
||||
{
|
||||
[x: string]: any;
|
||||
},
|
||||
'v2'
|
||||
>;
|
||||
|
||||
// @public
|
||||
export function createWaitAction(options?: {
|
||||
maxWaitTime?: Duration | HumanDuration;
|
||||
}): TemplateAction<HumanDuration, JsonObject, 'v1'>;
|
||||
}): TemplateAction<
|
||||
{
|
||||
minutes?: number | undefined;
|
||||
seconds?: number | undefined;
|
||||
milliseconds?: number | undefined;
|
||||
},
|
||||
{
|
||||
[x: string]: any;
|
||||
},
|
||||
'v2'
|
||||
>;
|
||||
|
||||
// @public @deprecated
|
||||
export type CreateWorkerOptions = {
|
||||
|
||||
@@ -35,73 +35,45 @@ export function createCatalogRegisterAction(options: {
|
||||
}) {
|
||||
const { catalogClient, integrations, auth } = options;
|
||||
|
||||
return createTemplateAction<
|
||||
| { catalogInfoUrl: string; optional?: boolean }
|
||||
| { repoContentsUrl: string; catalogInfoPath?: string; optional?: boolean }
|
||||
>({
|
||||
return createTemplateAction({
|
||||
id,
|
||||
description:
|
||||
'Registers entities from a catalog descriptor file in the workspace into the software catalog.',
|
||||
examples,
|
||||
schema: {
|
||||
input: {
|
||||
oneOf: [
|
||||
{
|
||||
type: 'object',
|
||||
required: ['catalogInfoUrl'],
|
||||
properties: {
|
||||
catalogInfoUrl: {
|
||||
title: 'Catalog Info URL',
|
||||
description:
|
||||
'An absolute URL pointing to the catalog info file location',
|
||||
type: 'string',
|
||||
},
|
||||
optional: {
|
||||
title: 'Optional',
|
||||
input: z =>
|
||||
z.union([
|
||||
z.object({
|
||||
catalogInfoUrl: z.string({
|
||||
description:
|
||||
'An absolute URL pointing to the catalog info file location',
|
||||
}),
|
||||
optional: z
|
||||
.boolean({
|
||||
description:
|
||||
'Permit the registered location to optionally exist. Default: false',
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
required: ['repoContentsUrl'],
|
||||
properties: {
|
||||
repoContentsUrl: {
|
||||
title: 'Repository Contents URL',
|
||||
description:
|
||||
'An absolute URL pointing to the root of a repository directory tree',
|
||||
type: 'string',
|
||||
},
|
||||
catalogInfoPath: {
|
||||
title: 'Fetch URL',
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
z.object({
|
||||
repoContentsUrl: z.string({
|
||||
description:
|
||||
'An absolute URL pointing to the root of a repository directory tree',
|
||||
}),
|
||||
catalogInfoPath: z
|
||||
.string({
|
||||
description:
|
||||
'A relative path from the repo root pointing to the catalog info file, defaults to /catalog-info.yaml',
|
||||
type: 'string',
|
||||
},
|
||||
optional: {
|
||||
title: 'Optional',
|
||||
})
|
||||
.optional(),
|
||||
optional: z
|
||||
.boolean({
|
||||
description:
|
||||
'Permit the registered location to optionally exist. Default: false',
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
output: {
|
||||
type: 'object',
|
||||
required: ['catalogInfoUrl'],
|
||||
properties: {
|
||||
entityRef: {
|
||||
type: 'string',
|
||||
},
|
||||
catalogInfoUrl: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
]),
|
||||
},
|
||||
async handler(ctx) {
|
||||
const { input } = ctx;
|
||||
|
||||
@@ -18,7 +18,6 @@ import fs from 'fs-extra';
|
||||
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import * as yaml from 'yaml';
|
||||
import { resolveSafeChildPath } from '@backstage/backend-plugin-api';
|
||||
import { z } from 'zod';
|
||||
import { examples } from './write.examples';
|
||||
|
||||
const id = 'catalog:write';
|
||||
@@ -33,18 +32,17 @@ export function createCatalogWriteAction() {
|
||||
id,
|
||||
description: 'Writes the catalog-info.yaml for your template',
|
||||
schema: {
|
||||
input: z.object({
|
||||
filePath: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Defaults to catalog-info.yaml'),
|
||||
input: {
|
||||
filePath: z =>
|
||||
z.string().optional().describe('Defaults to catalog-info.yaml'),
|
||||
// TODO: this should reference an zod entity validator if it existed.
|
||||
entity: z
|
||||
.record(z.any())
|
||||
.describe(
|
||||
'You can provide the same values used in the Entity schema.',
|
||||
),
|
||||
}),
|
||||
entity: z =>
|
||||
z
|
||||
.record(z.any())
|
||||
.describe(
|
||||
'You can provide the same values used in the Entity schema.',
|
||||
),
|
||||
},
|
||||
},
|
||||
examples,
|
||||
supportsDryRun: true,
|
||||
|
||||
@@ -19,7 +19,6 @@ import { join, relative } from 'path';
|
||||
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import { examples } from './log.examples';
|
||||
import fs from 'fs';
|
||||
import { z } from 'zod';
|
||||
|
||||
const id = 'debug:log';
|
||||
|
||||
@@ -34,24 +33,23 @@ const id = 'debug:log';
|
||||
* @public
|
||||
*/
|
||||
export function createDebugLogAction() {
|
||||
return createTemplateAction<{
|
||||
message?: string;
|
||||
listWorkspace?: boolean | 'with-filenames' | 'with-contents';
|
||||
}>({
|
||||
return createTemplateAction({
|
||||
id,
|
||||
description:
|
||||
'Writes a message into the log and/or lists all files in the workspace.',
|
||||
examples,
|
||||
schema: {
|
||||
input: z.object({
|
||||
message: z.string({ description: 'Message to output.' }).optional(),
|
||||
listWorkspace: z
|
||||
.union([z.boolean(), z.enum(['with-filenames', 'with-contents'])], {
|
||||
description:
|
||||
'List all files in the workspace. If used with "with-contents", also the file contents are listed.',
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
input: {
|
||||
message: z =>
|
||||
z.string({ description: 'Message to output.' }).optional(),
|
||||
listWorkspace: z =>
|
||||
z
|
||||
.union([z.boolean(), z.enum(['with-filenames', 'with-contents'])], {
|
||||
description:
|
||||
'List all files in the workspace. If used with "with-contents", also the file contents are listed.',
|
||||
})
|
||||
.optional(),
|
||||
},
|
||||
},
|
||||
supportsDryRun: true,
|
||||
async handler(ctx) {
|
||||
|
||||
+1
-8
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
import { createWaitAction } from './wait';
|
||||
import { Writable } from 'stream';
|
||||
import { examples } from './wait.examples';
|
||||
import yaml from 'yaml';
|
||||
import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils';
|
||||
@@ -23,13 +22,7 @@ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-
|
||||
describe('debug:wait examples', () => {
|
||||
const action = createWaitAction();
|
||||
|
||||
const logStream = {
|
||||
write: jest.fn(),
|
||||
} as jest.Mocked<Partial<Writable>> as jest.Mocked<Writable>;
|
||||
|
||||
const mockContext = createMockActionContext({
|
||||
logStream,
|
||||
});
|
||||
const mockContext = createMockActionContext();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
|
||||
@@ -15,19 +15,12 @@
|
||||
*/
|
||||
|
||||
import { createWaitAction } from './wait';
|
||||
import { Writable } from 'stream';
|
||||
import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils';
|
||||
|
||||
describe('debug:wait', () => {
|
||||
const action = createWaitAction();
|
||||
|
||||
const logStream = {
|
||||
write: jest.fn(),
|
||||
} as jest.Mocked<Partial<Writable>> as jest.Mocked<Writable>;
|
||||
|
||||
const mockContext = createMockActionContext({
|
||||
logStream,
|
||||
});
|
||||
const mockContext = createMockActionContext();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
|
||||
@@ -48,27 +48,30 @@ export function createWaitAction(options?: {
|
||||
return Duration.fromISOTime(MAX_WAIT_TIME_IN_ISO);
|
||||
};
|
||||
|
||||
return createTemplateAction<HumanDuration>({
|
||||
return createTemplateAction({
|
||||
id,
|
||||
description: 'Waits for a certain period of time.',
|
||||
examples,
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
minutes: {
|
||||
title: 'Waiting period in minutes.',
|
||||
type: 'number',
|
||||
},
|
||||
seconds: {
|
||||
title: 'Waiting period in seconds.',
|
||||
type: 'number',
|
||||
},
|
||||
milliseconds: {
|
||||
title: 'Waiting period in milliseconds.',
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
minutes: z =>
|
||||
z
|
||||
.number({
|
||||
description: 'Waiting period in minutes.',
|
||||
})
|
||||
.optional(),
|
||||
seconds: z =>
|
||||
z
|
||||
.number({
|
||||
description: 'Waiting period in seconds.',
|
||||
})
|
||||
.optional(),
|
||||
milliseconds: z =>
|
||||
z
|
||||
.number({
|
||||
description: 'Waiting period in milliseconds.',
|
||||
})
|
||||
.optional(),
|
||||
},
|
||||
},
|
||||
async handler(ctx) {
|
||||
|
||||
@@ -39,39 +39,32 @@ export function createFetchPlainAction(options: {
|
||||
}) {
|
||||
const { reader, integrations } = options;
|
||||
|
||||
return createTemplateAction<{
|
||||
url: string;
|
||||
targetPath?: string;
|
||||
token?: string;
|
||||
}>({
|
||||
return createTemplateAction({
|
||||
id: ACTION_ID,
|
||||
examples,
|
||||
description:
|
||||
'Downloads content and places it in the workspace, or optionally in a subdirectory specified by the `targetPath` input option.',
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
required: ['url'],
|
||||
properties: {
|
||||
url: {
|
||||
title: 'Fetch URL',
|
||||
url: z =>
|
||||
z.string({
|
||||
description:
|
||||
'Relative path or absolute URL pointing to the directory tree to fetch',
|
||||
type: 'string',
|
||||
},
|
||||
targetPath: {
|
||||
title: 'Target Path',
|
||||
description:
|
||||
'Target path within the working directory to download the contents to.',
|
||||
type: 'string',
|
||||
},
|
||||
token: {
|
||||
title: 'Token',
|
||||
description:
|
||||
'An optional token to use for authentication when reading the resources.',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
}),
|
||||
targetPath: z =>
|
||||
z
|
||||
.string({
|
||||
description:
|
||||
'Target path within the working directory to download the contents to.',
|
||||
})
|
||||
.optional(),
|
||||
token: z =>
|
||||
z
|
||||
.string({
|
||||
description:
|
||||
'An optional token to use for authentication when reading the resources.',
|
||||
})
|
||||
.optional(),
|
||||
},
|
||||
},
|
||||
supportsDryRun: true,
|
||||
|
||||
@@ -14,18 +14,20 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { UrlReaderService } from '@backstage/backend-plugin-api';
|
||||
import { resolveSafeChildPath } from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
resolveSafeChildPath,
|
||||
UrlReaderService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { examples } from './plainFile.examples';
|
||||
|
||||
import {
|
||||
createTemplateAction,
|
||||
fetchFile,
|
||||
} from '@backstage/plugin-scaffolder-node';
|
||||
|
||||
/**
|
||||
* Downloads content and places it in the workspace, or optionally
|
||||
* in a subdirectory specified by the 'targetPath' input option.
|
||||
* Downloads a single file and places it in the workspace.
|
||||
* @public
|
||||
*/
|
||||
export function createFetchPlainFileAction(options: {
|
||||
@@ -34,38 +36,29 @@ export function createFetchPlainFileAction(options: {
|
||||
}) {
|
||||
const { reader, integrations } = options;
|
||||
|
||||
return createTemplateAction<{
|
||||
url: string;
|
||||
targetPath: string;
|
||||
token?: string;
|
||||
}>({
|
||||
return createTemplateAction({
|
||||
id: 'fetch:plain:file',
|
||||
description: 'Downloads single file and places it in the workspace.',
|
||||
examples,
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
required: ['url', 'targetPath'],
|
||||
properties: {
|
||||
url: {
|
||||
title: 'Fetch URL',
|
||||
url: z =>
|
||||
z.string({
|
||||
description:
|
||||
'Relative path or absolute URL pointing to the single file to fetch.',
|
||||
type: 'string',
|
||||
},
|
||||
targetPath: {
|
||||
title: 'Target Path',
|
||||
}),
|
||||
targetPath: z =>
|
||||
z.string({
|
||||
description:
|
||||
'Target path within the working directory to download the file as.',
|
||||
type: 'string',
|
||||
},
|
||||
token: {
|
||||
title: 'Token',
|
||||
description:
|
||||
'An optional token to use for authentication when reading the resources.',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
}),
|
||||
token: z =>
|
||||
z
|
||||
.string({
|
||||
description:
|
||||
'An optional token to use for authentication when reading the resources.',
|
||||
})
|
||||
.optional(),
|
||||
},
|
||||
},
|
||||
supportsDryRun: true,
|
||||
|
||||
@@ -41,89 +41,87 @@ export function createFetchTemplateAction(options: {
|
||||
additionalTemplateFilters?: Record<string, TemplateFilter>;
|
||||
additionalTemplateGlobals?: Record<string, TemplateGlobal>;
|
||||
}) {
|
||||
return createTemplateAction<{
|
||||
url: string;
|
||||
targetPath?: string;
|
||||
values: any;
|
||||
templateFileExtension?: string | boolean;
|
||||
|
||||
// Cookiecutter compat options
|
||||
copyWithoutTemplating?: string[];
|
||||
cookiecutterCompat?: boolean;
|
||||
replace?: boolean;
|
||||
trimBlocks?: boolean;
|
||||
lstripBlocks?: boolean;
|
||||
token?: string;
|
||||
}>({
|
||||
return createTemplateAction({
|
||||
id: 'fetch:template',
|
||||
description:
|
||||
'Downloads a skeleton, templates variables into file and directory names and content, and places the result in the workspace, or optionally in a subdirectory specified by the `targetPath` input option.',
|
||||
examples,
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
required: ['url'],
|
||||
properties: {
|
||||
url: {
|
||||
title: 'Fetch URL',
|
||||
url: z =>
|
||||
z.string({
|
||||
description:
|
||||
'Relative path or absolute URL pointing to the directory tree to fetch',
|
||||
type: 'string',
|
||||
},
|
||||
targetPath: {
|
||||
title: 'Target Path',
|
||||
description:
|
||||
'Target path within the working directory to download the contents to. Defaults to the working directory root.',
|
||||
type: 'string',
|
||||
},
|
||||
values: {
|
||||
title: 'Template Values',
|
||||
description: 'Values to pass on to the templating engine',
|
||||
type: 'object',
|
||||
},
|
||||
copyWithoutRender: {
|
||||
title: '[Deprecated] Copy Without Render',
|
||||
description:
|
||||
'An array of glob patterns. Any files or directories which match are copied without being processed as templates.',
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
copyWithoutTemplating: {
|
||||
title: 'Copy Without Templating',
|
||||
description:
|
||||
'An array of glob patterns. Contents of matched files or directories are copied without being processed, but paths are subject to rendering.',
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
cookiecutterCompat: {
|
||||
title: 'Cookiecutter compatibility mode',
|
||||
description:
|
||||
'Enable features to maximise compatibility with templates built for fetch:cookiecutter',
|
||||
type: 'boolean',
|
||||
},
|
||||
templateFileExtension: {
|
||||
title: 'Template File Extension',
|
||||
description:
|
||||
'If set, only files with the given extension will be templated. If set to `true`, the default extension `.njk` is used.',
|
||||
type: ['string', 'boolean'],
|
||||
},
|
||||
replace: {
|
||||
title: 'Replace files',
|
||||
description:
|
||||
'If set, replace files in targetPath instead of skipping existing ones.',
|
||||
type: 'boolean',
|
||||
},
|
||||
token: {
|
||||
title: 'Token',
|
||||
description:
|
||||
'An optional token to use for authentication when reading the resources.',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
}),
|
||||
targetPath: z =>
|
||||
z
|
||||
.string({
|
||||
description:
|
||||
'Target path within the working directory to download the contents to. Defaults to the working directory root.',
|
||||
})
|
||||
.optional(),
|
||||
values: z =>
|
||||
z
|
||||
.record(z.any(), {
|
||||
description: 'Values to pass on to the templating engine',
|
||||
})
|
||||
.optional(),
|
||||
copyWithoutRender: z =>
|
||||
z
|
||||
.array(z.string(), {
|
||||
description:
|
||||
'An array of glob patterns. Any files or directories which match are copied without being processed as templates.',
|
||||
})
|
||||
.optional(),
|
||||
copyWithoutTemplating: z =>
|
||||
z
|
||||
.array(z.string(), {
|
||||
description:
|
||||
'An array of glob patterns. Contents of matched files or directories are copied without being processed, but paths are subject to rendering.',
|
||||
})
|
||||
.optional(),
|
||||
cookiecutterCompat: z =>
|
||||
z
|
||||
.boolean({
|
||||
description:
|
||||
'Enable features to maximise compatibility with templates built for fetch:cookiecutter',
|
||||
})
|
||||
.optional(),
|
||||
templateFileExtension: z =>
|
||||
z
|
||||
.union([z.string(), z.boolean()], {
|
||||
description:
|
||||
'If set, only files with the given extension will be templated. If set to `true`, the default extension `.njk` is used.',
|
||||
})
|
||||
.optional(),
|
||||
replace: z =>
|
||||
z
|
||||
.boolean({
|
||||
description:
|
||||
'If set, replace files in targetPath instead of skipping existing ones.',
|
||||
})
|
||||
.optional(),
|
||||
trimBlocks: z =>
|
||||
z
|
||||
.boolean({
|
||||
description:
|
||||
'If set, the first newline after a block is removed (block, not variable tag).',
|
||||
})
|
||||
.optional(),
|
||||
lstripBlocks: z =>
|
||||
z
|
||||
.boolean({
|
||||
description:
|
||||
'If set, leading spaces and tabs are stripped from the start of a line to a block.',
|
||||
})
|
||||
.optional(),
|
||||
token: z =>
|
||||
z
|
||||
.string({
|
||||
description:
|
||||
'An optional token to use for authentication when reading the resources.',
|
||||
})
|
||||
.optional(),
|
||||
},
|
||||
},
|
||||
supportsDryRun: true,
|
||||
|
||||
@@ -38,60 +38,63 @@ export function createFetchTemplateFileAction(options: {
|
||||
additionalTemplateFilters?: Record<string, TemplateFilter>;
|
||||
additionalTemplateGlobals?: Record<string, TemplateGlobal>;
|
||||
}) {
|
||||
return createTemplateAction<{
|
||||
url: string;
|
||||
targetPath: string;
|
||||
values: any;
|
||||
cookiecutterCompat?: boolean;
|
||||
replace?: boolean;
|
||||
trimBlocks?: boolean;
|
||||
lstripBlocks?: boolean;
|
||||
token?: string;
|
||||
}>({
|
||||
return createTemplateAction({
|
||||
id: 'fetch:template:file',
|
||||
description: 'Downloads single file and places it in the workspace.',
|
||||
examples,
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
required: ['url', 'targetPath'],
|
||||
properties: {
|
||||
url: {
|
||||
title: 'Fetch URL',
|
||||
url: z =>
|
||||
z.string({
|
||||
description:
|
||||
'Relative path or absolute URL pointing to the single file to fetch.',
|
||||
type: 'string',
|
||||
},
|
||||
targetPath: {
|
||||
title: 'Target Path',
|
||||
}),
|
||||
targetPath: z =>
|
||||
z.string({
|
||||
description:
|
||||
'Target path within the working directory to download the file as.',
|
||||
type: 'string',
|
||||
},
|
||||
values: {
|
||||
title: 'Template Values',
|
||||
description: 'Values to pass on to the templating engine',
|
||||
type: 'object',
|
||||
},
|
||||
cookiecutterCompat: {
|
||||
title: 'Cookiecutter compatibility mode',
|
||||
description:
|
||||
'Enable features to maximise compatibility with templates built for fetch:cookiecutter',
|
||||
type: 'boolean',
|
||||
},
|
||||
replace: {
|
||||
title: 'Replace file',
|
||||
description:
|
||||
'If set, replace file in targetPath instead of overwriting existing one.',
|
||||
type: 'boolean',
|
||||
},
|
||||
token: {
|
||||
title: 'Token',
|
||||
description:
|
||||
'An optional token to use for authentication when reading the resources.',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
}),
|
||||
values: z =>
|
||||
z
|
||||
.record(z.any(), {
|
||||
description: 'Values to pass on to the templating engine',
|
||||
})
|
||||
.optional(),
|
||||
cookiecutterCompat: z =>
|
||||
z
|
||||
.boolean({
|
||||
description:
|
||||
'Enable features to maximise compatibility with templates built for fetch:cookiecutter',
|
||||
})
|
||||
.optional(),
|
||||
replace: z =>
|
||||
z
|
||||
.boolean({
|
||||
description:
|
||||
'If set, replace file in targetPath instead of overwriting existing one.',
|
||||
})
|
||||
.optional(),
|
||||
trimBlocks: z =>
|
||||
z
|
||||
.boolean({
|
||||
description:
|
||||
'If set, the first newline after a block is removed (block, not variable tag).',
|
||||
})
|
||||
.optional(),
|
||||
lstripBlocks: z =>
|
||||
z
|
||||
.boolean({
|
||||
description:
|
||||
'If set, leading spaces and tabs are stripped from the start of a line to a block.',
|
||||
})
|
||||
.optional(),
|
||||
token: z =>
|
||||
z
|
||||
.string({
|
||||
description:
|
||||
'An optional token to use for authentication when reading the resources.',
|
||||
})
|
||||
.optional(),
|
||||
},
|
||||
},
|
||||
supportsDryRun: true,
|
||||
|
||||
@@ -26,24 +26,16 @@ import { examples } from './delete.examples';
|
||||
* @public
|
||||
*/
|
||||
export const createFilesystemDeleteAction = () => {
|
||||
return createTemplateAction<{ files: string[] }>({
|
||||
return createTemplateAction({
|
||||
id: 'fs:delete',
|
||||
description: 'Deletes files and directories from the workspace',
|
||||
examples,
|
||||
schema: {
|
||||
input: {
|
||||
required: ['files'],
|
||||
type: 'object',
|
||||
properties: {
|
||||
files: {
|
||||
title: 'Files',
|
||||
files: z =>
|
||||
z.array(z.string(), {
|
||||
description: 'A list of files and directories that will be deleted',
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
supportsDryRun: true,
|
||||
|
||||
@@ -14,19 +14,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import z from 'zod';
|
||||
import { resolveSafeChildPath } from '@backstage/backend-plugin-api';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { z as zod } from 'zod';
|
||||
|
||||
const contentSchema = z.object({
|
||||
name: z.string().describe('Name of the file or directory'),
|
||||
path: z
|
||||
.string()
|
||||
.describe('path to the file or directory relative to the workspace'),
|
||||
fullPath: z.string().describe('full path to the file or directory'),
|
||||
});
|
||||
type Content = z.infer<typeof contentSchema>;
|
||||
const contentSchema = (z: typeof zod) =>
|
||||
z.object({
|
||||
name: z.string().describe('Name of the file or directory'),
|
||||
path: z
|
||||
.string()
|
||||
.describe('path to the file or directory relative to the workspace'),
|
||||
fullPath: z.string().describe('full path to the file or directory'),
|
||||
});
|
||||
|
||||
type Content = zod.infer<ReturnType<typeof contentSchema>>;
|
||||
|
||||
/**
|
||||
* Creates new action that enables reading directories in the workspace.
|
||||
@@ -38,14 +40,14 @@ export const createFilesystemReadDirAction = () => {
|
||||
description: 'Reads files and directories from the workspace',
|
||||
supportsDryRun: true,
|
||||
schema: {
|
||||
input: z.object({
|
||||
paths: z.array(z.string().min(1)),
|
||||
recursive: z.boolean().default(false),
|
||||
}),
|
||||
output: z.object({
|
||||
files: z.array(contentSchema),
|
||||
folders: z.array(contentSchema),
|
||||
}),
|
||||
input: {
|
||||
paths: z => z.array(z.string().min(1)),
|
||||
recursive: z => z.boolean().default(false),
|
||||
},
|
||||
output: {
|
||||
files: z => z.array(contentSchema(z)),
|
||||
folders: z => z.array(contentSchema(z)),
|
||||
},
|
||||
},
|
||||
async handler(ctx) {
|
||||
const files: Content[] = [];
|
||||
|
||||
@@ -16,56 +16,42 @@
|
||||
|
||||
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import { resolveSafeChildPath } from '@backstage/backend-plugin-api';
|
||||
import { InputError } from '@backstage/errors';
|
||||
import fs from 'fs-extra';
|
||||
import { examples } from './rename.examples';
|
||||
import { InputError } from '@backstage/errors';
|
||||
|
||||
/**
|
||||
* Creates a new action that allows renames of files and directories in the workspace.
|
||||
* @public
|
||||
*/
|
||||
export const createFilesystemRenameAction = () => {
|
||||
return createTemplateAction<{
|
||||
files: Array<{
|
||||
from: string;
|
||||
to: string;
|
||||
overwrite?: boolean;
|
||||
}>;
|
||||
}>({
|
||||
return createTemplateAction({
|
||||
id: 'fs:rename',
|
||||
description: 'Renames files and directories within the workspace',
|
||||
examples,
|
||||
schema: {
|
||||
input: {
|
||||
required: ['files'],
|
||||
type: 'object',
|
||||
properties: {
|
||||
files: {
|
||||
title: 'Files',
|
||||
description:
|
||||
'A list of file and directory names that will be renamed',
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
required: ['from', 'to'],
|
||||
properties: {
|
||||
from: {
|
||||
type: 'string',
|
||||
title: 'The source location of the file to be renamed',
|
||||
},
|
||||
to: {
|
||||
type: 'string',
|
||||
title: 'The destination of the new file',
|
||||
},
|
||||
overwrite: {
|
||||
type: 'boolean',
|
||||
title:
|
||||
files: z =>
|
||||
z.array(
|
||||
z.object({
|
||||
from: z.string({
|
||||
description: 'The source location of the file to be renamed',
|
||||
}),
|
||||
to: z.string({
|
||||
description: 'The destination of the new file',
|
||||
}),
|
||||
overwrite: z
|
||||
.boolean({
|
||||
description:
|
||||
'Overwrite existing file or directory, default is false',
|
||||
},
|
||||
},
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
{
|
||||
description:
|
||||
'A list of file and directory names that will be renamed',
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
supportsDryRun: true,
|
||||
@@ -78,7 +64,6 @@ export const createFilesystemRenameAction = () => {
|
||||
if (!file.from || !file.to) {
|
||||
throw new InputError('each file must have a from and to property');
|
||||
}
|
||||
|
||||
const sourceFilepath = resolveSafeChildPath(
|
||||
ctx.workspacePath,
|
||||
file.from,
|
||||
|
||||
@@ -23,11 +23,9 @@ import { TaskSpec } from '@backstage/plugin-scaffolder-common';
|
||||
import {
|
||||
createTemplateAction,
|
||||
TaskSecrets,
|
||||
TemplateAction,
|
||||
TaskContext,
|
||||
} from '@backstage/plugin-scaffolder-node';
|
||||
import { UserEntity } from '@backstage/catalog-model';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
AuthorizeResult,
|
||||
PermissionEvaluator,
|
||||
@@ -38,7 +36,6 @@ import {
|
||||
mockCredentials,
|
||||
mockServices,
|
||||
} from '@backstage/backend-test-utils';
|
||||
import { loggerToWinstonLogger } from '../../util/loggerToWinstonLogger';
|
||||
|
||||
describe('NunjucksWorkflowRunner', () => {
|
||||
let actionRegistry: TemplateActionRegistry;
|
||||
@@ -127,32 +124,12 @@ describe('NunjucksWorkflowRunner', () => {
|
||||
handler: fakeActionHandler,
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
required: ['foo'],
|
||||
properties: {
|
||||
foo: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
foo: z => z.number(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
actionRegistry.register(
|
||||
createTemplateAction({
|
||||
id: 'jest-legacy-zod-validated-action',
|
||||
description: 'Mock action for testing',
|
||||
handler: fakeActionHandler,
|
||||
supportsDryRun: true,
|
||||
schema: {
|
||||
input: z.object({
|
||||
foo: z.number(),
|
||||
}),
|
||||
},
|
||||
}) as TemplateAction,
|
||||
);
|
||||
|
||||
actionRegistry.register(
|
||||
createTemplateAction({
|
||||
id: 'jest-zod-validated-action',
|
||||
@@ -186,13 +163,14 @@ describe('NunjucksWorkflowRunner', () => {
|
||||
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(),
|
||||
}),
|
||||
output: z =>
|
||||
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({
|
||||
@@ -222,7 +200,9 @@ describe('NunjucksWorkflowRunner', () => {
|
||||
ctx.output('key2', key2);
|
||||
ctx.output('key3', key3);
|
||||
|
||||
// @ts-expect-error - not valid output type
|
||||
ctx.output('key4', key4);
|
||||
// @ts-expect-error - not valid output type
|
||||
ctx.output('key5', key5);
|
||||
},
|
||||
}),
|
||||
@@ -236,7 +216,7 @@ describe('NunjucksWorkflowRunner', () => {
|
||||
actionRegistry,
|
||||
integrations,
|
||||
workingDirectory: mockDir.path,
|
||||
logger: loggerToWinstonLogger(logger),
|
||||
logger,
|
||||
permissions: mockedPermissionApi,
|
||||
});
|
||||
});
|
||||
@@ -280,22 +260,6 @@ describe('NunjucksWorkflowRunner', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if the action has legacy zod schema and the input does not match', async () => {
|
||||
const task = createMockTaskWithSpec({
|
||||
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({
|
||||
steps: [
|
||||
@@ -313,23 +277,6 @@ describe('NunjucksWorkflowRunner', () => {
|
||||
expect(fakeActionHandler).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should run the action when the zod validation passes with legacy zod', async () => {
|
||||
const task = createMockTaskWithSpec({
|
||||
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({
|
||||
steps: [
|
||||
|
||||
@@ -28,7 +28,6 @@ import fs from 'fs-extra';
|
||||
import { validate as validateJsonSchema } from 'jsonschema';
|
||||
import nunjucks from 'nunjucks';
|
||||
import path from 'path';
|
||||
import { PassThrough } from 'stream';
|
||||
import * as winston from 'winston';
|
||||
import {
|
||||
SecureTemplater,
|
||||
@@ -40,6 +39,7 @@ import { TaskTrackType, WorkflowResponse, WorkflowRunner } from './types';
|
||||
|
||||
import type {
|
||||
AuditorService,
|
||||
LoggerService,
|
||||
PermissionsService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { UserEntity } from '@backstage/catalog-model';
|
||||
@@ -59,14 +59,13 @@ import { createDefaultFilters } from '../../lib/templating/filters/createDefault
|
||||
import { scaffolderActionRules } from '../../service/rules';
|
||||
import { createCounterMetric, createHistogramMetric } from '../../util/metrics';
|
||||
import { BackstageLoggerTransport, WinstonLogger } from './logger';
|
||||
import { loggerToWinstonLogger } from '../../util/loggerToWinstonLogger';
|
||||
import { convertFiltersToRecord } from '../../util/templating';
|
||||
|
||||
type NunjucksWorkflowRunnerOptions = {
|
||||
workingDirectory: string;
|
||||
actionRegistry: TemplateActionRegistry;
|
||||
integrations: ScmIntegrations;
|
||||
logger: winston.Logger;
|
||||
logger: LoggerService;
|
||||
auditor?: AuditorService;
|
||||
additionalTemplateFilters?: Record<string, TemplateFilter>;
|
||||
additionalTemplateGlobals?: Record<string, TemplateGlobal>;
|
||||
@@ -113,7 +112,7 @@ const createStepLogger = ({
|
||||
}: {
|
||||
task: TaskContext;
|
||||
step: TaskStep;
|
||||
rootLogger: winston.Logger;
|
||||
rootLogger: LoggerService;
|
||||
}) => {
|
||||
const taskLogger = WinstonLogger.create({
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
@@ -126,22 +125,7 @@ const createStepLogger = ({
|
||||
|
||||
taskLogger.addRedactions(Object.values(task.secrets ?? {}));
|
||||
|
||||
// This stream logger should be deprecated. We're going to replace it with
|
||||
// just using the logger directly, as all those logs get written to step logs
|
||||
// using the stepLogStream above.
|
||||
// Initially this stream used to be the only way to write to the client logs, but that
|
||||
// has changed over time, there's not really a need for this anymore.
|
||||
// You can just create a simple wrapper like the below in your action to write to the main logger.
|
||||
// This way we also get redactions for free.
|
||||
const streamLogger = new PassThrough();
|
||||
streamLogger.on('data', async data => {
|
||||
const message = data.toString().trim();
|
||||
if (message?.length > 1) {
|
||||
taskLogger.info(message);
|
||||
}
|
||||
});
|
||||
|
||||
return { taskLogger, streamLogger };
|
||||
return { taskLogger };
|
||||
};
|
||||
|
||||
const isActionAuthorized = createConditionAuthorizer(
|
||||
@@ -268,7 +252,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner {
|
||||
}
|
||||
const action: TemplateAction<JsonObject> =
|
||||
this.options.actionRegistry.get(step.action);
|
||||
const { taskLogger, streamLogger } = createStepLogger({
|
||||
const { taskLogger } = createStepLogger({
|
||||
task,
|
||||
step,
|
||||
rootLogger: this.options.logger,
|
||||
@@ -393,9 +377,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner {
|
||||
id: await task.getWorkspaceName(),
|
||||
},
|
||||
secrets: task.secrets ?? {},
|
||||
// TODO(blam): move to LoggerService and away from Winston
|
||||
logger: loggerToWinstonLogger(taskLogger),
|
||||
logStream: streamLogger,
|
||||
logger: taskLogger,
|
||||
workspacePath,
|
||||
async checkpoint<T extends JsonValue | void>(opts: {
|
||||
key?: string;
|
||||
|
||||
@@ -7,12 +7,12 @@ import { ActionContext } from '@backstage/plugin-scaffolder-node';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
|
||||
// @public
|
||||
export const createMockActionContext: <
|
||||
export function createMockActionContext<
|
||||
TActionInput extends JsonObject = JsonObject,
|
||||
TActionOutput extends JsonObject = JsonObject,
|
||||
TActionOutput extends JsonObject = any,
|
||||
>(
|
||||
options?: Partial<ActionContext<TActionInput, TActionOutput>>,
|
||||
) => ActionContext<TActionInput, TActionOutput>;
|
||||
): ActionContext<TActionInput, TActionOutput>;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
|
||||
@@ -30,13 +30,14 @@ import { loggerToWinstonLogger } from './loggerToWinstonLogger';
|
||||
* @public
|
||||
* @param options - optional parameters to override default mock context
|
||||
*/
|
||||
export const createMockActionContext = <
|
||||
export function createMockActionContext<
|
||||
TActionInput extends JsonObject = JsonObject,
|
||||
TActionOutput extends JsonObject = JsonObject,
|
||||
TActionOutput extends JsonObject = any,
|
||||
>(
|
||||
options?: Partial<ActionContext<TActionInput, TActionOutput>>,
|
||||
): ActionContext<TActionInput, TActionOutput> => {
|
||||
): ActionContext<TActionInput, TActionOutput> {
|
||||
const credentials = mockCredentials.user();
|
||||
|
||||
const defaultContext = {
|
||||
logger: loggerToWinstonLogger(mockServices.logger.mock()),
|
||||
logStream: new PassThrough(),
|
||||
@@ -66,16 +67,8 @@ export const createMockActionContext = <
|
||||
};
|
||||
}
|
||||
|
||||
const {
|
||||
input,
|
||||
logger,
|
||||
logStream,
|
||||
secrets,
|
||||
templateInfo,
|
||||
workspacePath,
|
||||
task,
|
||||
user,
|
||||
} = options;
|
||||
const { input, logger, secrets, templateInfo, workspacePath, task, user } =
|
||||
options;
|
||||
|
||||
return {
|
||||
...defaultContext,
|
||||
@@ -84,11 +77,10 @@ export const createMockActionContext = <
|
||||
createTemporaryDirectory: jest.fn().mockResolvedValue(workspacePath),
|
||||
}),
|
||||
...(logger && { logger }),
|
||||
...(logStream && { logStream }),
|
||||
...(input && { input }),
|
||||
...(secrets && { secrets }),
|
||||
...(task && { task }),
|
||||
...(user && { user }),
|
||||
templateInfo,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ 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';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { Observable } from '@backstage/types';
|
||||
import { Schema } from 'jsonschema';
|
||||
@@ -25,63 +24,34 @@ import { z } from 'zod';
|
||||
export type ActionContext<
|
||||
TActionInput extends JsonObject,
|
||||
TActionOutput extends JsonObject = 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;
|
||||
};
|
||||
_TSchemaType extends 'v2' = '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;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export function addFiles(options: {
|
||||
@@ -180,69 +150,67 @@ export function createBranch(options: {
|
||||
logger?: LoggerService | undefined;
|
||||
}): Promise<void>;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
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 (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;
|
||||
},
|
||||
TInputSchema extends
|
||||
| {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
}
|
||||
| ((zImpl: typeof z) => z.ZodType),
|
||||
TOutputSchema extends
|
||||
| {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
}
|
||||
| ((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 extends {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
}
|
||||
? {
|
||||
[key in keyof TInputSchema]: z.infer<ReturnType<TInputSchema[key]>>;
|
||||
}
|
||||
: TInputSchema extends (zImpl: typeof z) => z.ZodType
|
||||
? z.infer<ReturnType<TInputSchema>>
|
||||
: never,
|
||||
TOutputSchema extends {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
}
|
||||
? {
|
||||
[key in keyof TOutputSchema]: z.infer<ReturnType<TOutputSchema[key]>>;
|
||||
}
|
||||
: TOutputSchema extends (zImpl: typeof z) => z.ZodType
|
||||
? z.infer<ReturnType<TOutputSchema>>
|
||||
: never,
|
||||
TInputSchema,
|
||||
TOutputSchema,
|
||||
'v2'
|
||||
>,
|
||||
): TemplateAction<
|
||||
FlattenOptionalProperties<{
|
||||
[key in keyof TInputSchema]: z.output<ReturnType<TInputSchema[key]>>;
|
||||
}>,
|
||||
FlattenOptionalProperties<{
|
||||
[key in keyof TOutputSchema]: z.output<ReturnType<TOutputSchema[key]>>;
|
||||
}>,
|
||||
FlattenOptionalProperties<
|
||||
TInputSchema extends {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
}
|
||||
? {
|
||||
[key in keyof TInputSchema]: z.output<ReturnType<TInputSchema[key]>>;
|
||||
}
|
||||
: TInputSchema extends (zImpl: typeof z) => z.ZodType
|
||||
? z.output<ReturnType<TInputSchema>>
|
||||
: never
|
||||
>,
|
||||
FlattenOptionalProperties<
|
||||
TOutputSchema extends {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
}
|
||||
? {
|
||||
[key in keyof TOutputSchema]: z.output<
|
||||
ReturnType<TOutputSchema[key]>
|
||||
>;
|
||||
}
|
||||
: TOutputSchema extends (zImpl: typeof z) => z.ZodType
|
||||
? z.output<ReturnType<TOutputSchema>>
|
||||
: never
|
||||
>,
|
||||
'v2'
|
||||
>;
|
||||
|
||||
@@ -513,7 +481,7 @@ export type TaskStatus =
|
||||
export type TemplateAction<
|
||||
TActionInput extends JsonObject = JsonObject,
|
||||
TActionOutput extends JsonObject = JsonObject,
|
||||
TSchemaType extends 'v1' | 'v2' = 'v1',
|
||||
TSchemaType extends 'v2' = 'v2',
|
||||
> = {
|
||||
id: string;
|
||||
description?: string;
|
||||
@@ -536,18 +504,20 @@ export type TemplateActionOptions<
|
||||
TActionInput extends JsonObject = {},
|
||||
TActionOutput extends JsonObject = {},
|
||||
TInputSchema extends
|
||||
| JsonObject
|
||||
| z.ZodType
|
||||
| {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
} = JsonObject,
|
||||
}
|
||||
| ((zImpl: typeof z) => z.ZodType) = {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
},
|
||||
TOutputSchema extends
|
||||
| JsonObject
|
||||
| z.ZodType
|
||||
| {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
} = JsonObject,
|
||||
TSchemaType extends 'v1' | 'v2' = 'v1' | 'v2',
|
||||
}
|
||||
| ((zImpl: typeof z) => z.ZodType) = {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
},
|
||||
TSchemaType extends 'v2' = 'v2',
|
||||
> = {
|
||||
id: string;
|
||||
description?: string;
|
||||
|
||||
@@ -14,85 +14,8 @@
|
||||
* 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',
|
||||
@@ -107,14 +30,10 @@ describe('createTemplateAction', () => {
|
||||
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();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||
[a, b];
|
||||
|
||||
ctx.output('test', 'value');
|
||||
|
||||
@@ -128,4 +47,56 @@ describe('createTemplateAction', () => {
|
||||
|
||||
expect(action).toBeDefined();
|
||||
});
|
||||
|
||||
it('should allow creating with a function for input and output schema for more complex types', () => {
|
||||
const action = createTemplateAction({
|
||||
id: 'test',
|
||||
schema: {
|
||||
input: z =>
|
||||
z.union([
|
||||
z.object({
|
||||
repoUrl: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
numberThing: z.number(),
|
||||
}),
|
||||
]),
|
||||
output: z =>
|
||||
z.object({
|
||||
test: z.string(),
|
||||
}),
|
||||
},
|
||||
handler: async ctx => {
|
||||
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');
|
||||
|
||||
if ('repoUrl' in ctx.input) {
|
||||
// @ts-expect-error - not valid input type
|
||||
const a: number = ctx.input.repoUrl;
|
||||
|
||||
const b: string = ctx.input.repoUrl;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||
[a, b];
|
||||
}
|
||||
|
||||
if ('numberThing' in ctx.input) {
|
||||
const a: number = ctx.input.numberThing;
|
||||
|
||||
// @ts-expect-error - not valid input type
|
||||
const b: string = ctx.input.numberThing;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||
[a, b];
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
expect(action).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -30,14 +30,18 @@ export type TemplateActionOptions<
|
||||
TActionInput extends JsonObject = {},
|
||||
TActionOutput extends JsonObject = {},
|
||||
TInputSchema extends
|
||||
| JsonObject
|
||||
| z.ZodType
|
||||
| { [key in string]: (zImpl: typeof z) => z.ZodType } = JsonObject,
|
||||
| { [key in string]: (zImpl: typeof z) => z.ZodType }
|
||||
| ((zImpl: typeof z) => z.ZodType) = {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
},
|
||||
TOutputSchema extends
|
||||
| JsonObject
|
||||
| z.ZodType
|
||||
| { [key in string]: (zImpl: typeof z) => z.ZodType } = JsonObject,
|
||||
TSchemaType extends 'v1' | 'v2' = 'v1' | 'v2',
|
||||
| {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
}
|
||||
| ((zImpl: typeof z) => z.ZodType) = {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
},
|
||||
TSchemaType extends 'v2' = 'v2',
|
||||
> = {
|
||||
id: string;
|
||||
description?: string;
|
||||
@@ -62,109 +66,82 @@ type FlattenOptionalProperties<T extends { [key in string]: unknown }> = Expand<
|
||||
[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 function createTemplateAction<
|
||||
TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType },
|
||||
TOutputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType },
|
||||
TInputSchema extends
|
||||
| { [key in string]: (zImpl: typeof z) => z.ZodType }
|
||||
| ((zImpl: typeof z) => z.ZodType),
|
||||
TOutputSchema extends
|
||||
| { [key in string]: (zImpl: typeof z) => z.ZodType }
|
||||
| ((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 extends { [key in string]: (zImpl: typeof z) => z.ZodType }
|
||||
? {
|
||||
[key in keyof TInputSchema]: z.infer<ReturnType<TInputSchema[key]>>;
|
||||
}
|
||||
: TInputSchema extends (zImpl: typeof z) => z.ZodType
|
||||
? z.infer<ReturnType<TInputSchema>>
|
||||
: never,
|
||||
TOutputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }
|
||||
? {
|
||||
[key in keyof TOutputSchema]: z.infer<ReturnType<TOutputSchema[key]>>;
|
||||
}
|
||||
: TOutputSchema extends (zImpl: typeof z) => z.ZodType
|
||||
? z.infer<ReturnType<TOutputSchema>>
|
||||
: never,
|
||||
TInputSchema,
|
||||
TOutputSchema,
|
||||
'v2'
|
||||
>,
|
||||
): TemplateAction<
|
||||
FlattenOptionalProperties<{
|
||||
[key in keyof TInputSchema]: z.output<ReturnType<TInputSchema[key]>>;
|
||||
}>,
|
||||
FlattenOptionalProperties<{
|
||||
[key in keyof TOutputSchema]: z.output<ReturnType<TOutputSchema[key]>>;
|
||||
}>,
|
||||
FlattenOptionalProperties<
|
||||
TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }
|
||||
? {
|
||||
[key in keyof TInputSchema]: z.output<ReturnType<TInputSchema[key]>>;
|
||||
}
|
||||
: TInputSchema extends (zImpl: typeof z) => z.ZodType
|
||||
? z.output<ReturnType<TInputSchema>>
|
||||
: never
|
||||
>,
|
||||
FlattenOptionalProperties<
|
||||
TOutputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }
|
||||
? {
|
||||
[key in keyof TOutputSchema]: z.output<
|
||||
ReturnType<TOutputSchema[key]>
|
||||
>;
|
||||
}
|
||||
: TOutputSchema extends (zImpl: typeof z) => z.ZodType
|
||||
? z.output<ReturnType<TOutputSchema>>
|
||||
: never
|
||||
>,
|
||||
'v2'
|
||||
>;
|
||||
export function createTemplateAction<
|
||||
TInputParams extends JsonObject = JsonObject,
|
||||
TOutputParams extends JsonObject = JsonObject,
|
||||
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 }
|
||||
TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } = {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
},
|
||||
TOutputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } = {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
},
|
||||
TActionInput extends JsonObject = 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,
|
||||
any,
|
||||
infer IReturn
|
||||
>
|
||||
? IReturn
|
||||
: TOutputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }
|
||||
: never,
|
||||
TActionOutput extends JsonObject = TOutputSchema extends {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
}
|
||||
? Expand<{
|
||||
[key in keyof TOutputSchema]: z.infer<ReturnType<TOutputSchema[key]>>;
|
||||
}>
|
||||
: TOutputParams,
|
||||
: never,
|
||||
>(
|
||||
action: TemplateActionOptions<
|
||||
TActionInput,
|
||||
@@ -172,13 +149,7 @@ export function createTemplateAction<
|
||||
TInputSchema,
|
||||
TOutputSchema
|
||||
>,
|
||||
): TemplateAction<
|
||||
TActionInput,
|
||||
TActionOutput,
|
||||
TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }
|
||||
? 'v2'
|
||||
: 'v1'
|
||||
> {
|
||||
): TemplateAction<TActionInput, TActionOutput, 'v2'> {
|
||||
const { inputSchema, outputSchema } = parseSchemas(
|
||||
action as TemplateActionOptions<any, any, any>,
|
||||
);
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Logger } from 'winston';
|
||||
import { Writable } from 'stream';
|
||||
import { JsonObject, JsonValue } from '@backstage/types';
|
||||
import { TaskSecrets } from '../tasks';
|
||||
import { TemplateInfo } from '@backstage/plugin-scaffolder-common';
|
||||
@@ -32,143 +30,75 @@ import {
|
||||
export type ActionContext<
|
||||
TActionInput extends JsonObject,
|
||||
TActionOutput extends JsonObject = 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;
|
||||
/**
|
||||
* Creates a temporary directory for use by the action, which is then cleaned up automatically.
|
||||
*/
|
||||
createTemporaryDirectory(): Promise<string>;
|
||||
_TSchemaType extends 'v2' = '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>;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
/**
|
||||
* Optional value of each invocation
|
||||
*/
|
||||
each?: JsonObject;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type TemplateAction<
|
||||
TActionInput extends JsonObject = JsonObject,
|
||||
TActionOutput extends JsonObject = JsonObject,
|
||||
TSchemaType extends 'v1' | 'v2' = 'v1',
|
||||
TSchemaType extends 'v2' = 'v2',
|
||||
> = {
|
||||
id: string;
|
||||
description?: string;
|
||||
|
||||
@@ -130,11 +130,7 @@ function checkRequiredParams(repoUrl: URL, ...params: string[]) {
|
||||
}
|
||||
}
|
||||
|
||||
const isZodSchema = (schema: unknown): schema is z.ZodType => {
|
||||
return typeof schema === 'object' && !!schema && 'safeParseAsync' in schema;
|
||||
};
|
||||
|
||||
const isNativeZodSchema = (
|
||||
const isKeyValueZodCallback = (
|
||||
schema: unknown,
|
||||
): schema is { [key in string]: (zImpl: typeof z) => z.ZodType } => {
|
||||
return (
|
||||
@@ -144,23 +140,20 @@ const isNativeZodSchema = (
|
||||
);
|
||||
};
|
||||
|
||||
const isZodFunctionDefinition = (
|
||||
schema: unknown,
|
||||
): schema is (zImpl: typeof z) => z.ZodType => {
|
||||
return typeof schema === 'function';
|
||||
};
|
||||
|
||||
export const parseSchemas = (
|
||||
action: TemplateActionOptions,
|
||||
action: TemplateActionOptions<any, any, any>,
|
||||
): { 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)) {
|
||||
if (isKeyValueZodCallback(action.schema.input)) {
|
||||
const input = z.object(
|
||||
Object.fromEntries(
|
||||
Object.entries(action.schema.input).map(([k, v]) => [k, v(z)]),
|
||||
@@ -169,7 +162,7 @@ export const parseSchemas = (
|
||||
|
||||
return {
|
||||
inputSchema: zodToJsonSchema(input) as Schema,
|
||||
outputSchema: isNativeZodSchema(action.schema.output)
|
||||
outputSchema: isKeyValueZodCallback(action.schema.output)
|
||||
? (zodToJsonSchema(
|
||||
z.object(
|
||||
Object.fromEntries(
|
||||
@@ -181,9 +174,18 @@ export const parseSchemas = (
|
||||
};
|
||||
}
|
||||
|
||||
if (isZodFunctionDefinition(action.schema.input)) {
|
||||
return {
|
||||
inputSchema: zodToJsonSchema(action.schema.input(z)) as Schema,
|
||||
outputSchema: isZodFunctionDefinition(action.schema.output)
|
||||
? (zodToJsonSchema(action.schema.output(z)) as Schema)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
inputSchema: action.schema.input,
|
||||
outputSchema: action.schema.output,
|
||||
inputSchema: undefined,
|
||||
outputSchema: undefined,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user