Merge pull request #15561 from zcason/update-scaffolder-action-to-zod
Update scaffolder action to zod
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': minor
|
||||
---
|
||||
|
||||
Added the ability to be able to define an actions `input` and `output` schema using `zod` instead of hand writing types and `jsonschema`
|
||||
@@ -24,8 +24,53 @@ passed as `input` to the function.
|
||||
In `packages/backend/src/plugins/scaffolder/actions/custom.ts` we can create a
|
||||
new action.
|
||||
|
||||
```ts
|
||||
import { createTemplateAction } from '@backstage/plugin-scaffolder-backend';
|
||||
```ts title="With Zod"
|
||||
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import fs from 'fs-extra';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const createNewFileAction = () => {
|
||||
return createTemplateAction({
|
||||
id: 'mycompany:create-file',
|
||||
schema: {
|
||||
input: z.object({
|
||||
contents: z.string().describe('The contents of the file'),
|
||||
filename: z
|
||||
.string()
|
||||
.describe('The filename of the file that will be created'),
|
||||
}),
|
||||
},
|
||||
|
||||
async handler(ctx) {
|
||||
await fs.outputFile(
|
||||
`${ctx.workspacePath}/${ctx.input.filename}`,
|
||||
ctx.input.contents,
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
So let's break this down. The `createNewFileAction` is a function that returns a
|
||||
`createTemplateAction`, and it's a good place to pass in dependencies which
|
||||
close over the `TemplateAction`. Take a look at our
|
||||
[built-in actions](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend/src/scaffolder/actions/builtin)
|
||||
for reference.
|
||||
|
||||
The `createTemplateAction` takes an object which specifies the following:
|
||||
|
||||
- `id` - a unique ID for your custom action. We encourage you to namespace these
|
||||
in some way so that they won't collide with future built-in actions that we
|
||||
may ship with the `scaffolder-backend` plugin.
|
||||
- `schema.input` - A `zod` or JSON schema object for input values to your function
|
||||
- `schema.output` - A `zod` or JSON schema object for values which are output from the
|
||||
function using `ctx.output`
|
||||
- `handler` - the actual code which is run part of the action, with a context
|
||||
|
||||
You can also choose to define your custom action using JSON schema instead of `zod`:
|
||||
|
||||
```ts title="With JSON Schema"
|
||||
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
export const createNewFileAction = () => {
|
||||
@@ -59,27 +104,6 @@ export const createNewFileAction = () => {
|
||||
};
|
||||
```
|
||||
|
||||
So let's break this down. The `createNewFileAction` is a function that returns a
|
||||
`createTemplateAction`, and it's a good place to pass in dependencies which
|
||||
close over the `TemplateAction`. Take a look at our
|
||||
[built-in actions](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend/src/scaffolder/actions/builtin)
|
||||
for reference.
|
||||
|
||||
We set the type generic to `{ contents: string, filename: string }` which is
|
||||
there to set the type on the handler `ctx` `inputs` property so we get good type
|
||||
checking. This could be generated from the next part of this guide, the `input`
|
||||
schema, but it's not supported right now. Feel free to contribute 🚀 👍.
|
||||
|
||||
The `createTemplateAction` takes an object which specifies the following:
|
||||
|
||||
- `id` - a unique ID for your custom action. We encourage you to namespace these
|
||||
in some way so that they won't collide with future built-in actions that we
|
||||
may ship with the `scaffolder-backend` plugin.
|
||||
- `schema.input` - A JSON schema for input values to your function
|
||||
- `schema.output` - A JSON schema for values which are outputted from the
|
||||
function using `ctx.output`
|
||||
- `handler` - the actual code which is run part of the action, with a context
|
||||
|
||||
### The context object
|
||||
|
||||
When the action `handler` is called, we provide you a `context` as the only
|
||||
@@ -89,10 +113,10 @@ argument. It looks like the following:
|
||||
- `ctx.logger` - a Winston logger for additional logging inside your action
|
||||
- `ctx.logStream` - a stream version of the logger if needed
|
||||
- `ctx.workspacePath` - a string of the working directory of the template run
|
||||
- `ctx.input` - an object which should match the JSON schema provided in the
|
||||
- `ctx.input` - an object which should match the `zod` or JSON schema provided in the
|
||||
`schema.input` part of the action definition
|
||||
- `ctx.output` - a function which you can call to set outputs that match the
|
||||
JSON schema in `schema.output` for ex. `ctx.output('downloadUrl', something)`
|
||||
JSON schema or `zod` in `schema.output` for ex. `ctx.output('downloadUrl', myDownloadUrl)`
|
||||
- `createTemporaryDirectory` a function to call to give you a temporary
|
||||
directory somewhere on the runner so you can store some files there rather
|
||||
than polluting the `workspacePath`
|
||||
|
||||
@@ -24,6 +24,7 @@ import { Observable } from '@backstage/types';
|
||||
import { Octokit } from 'octokit';
|
||||
import { PluginDatabaseManager } from '@backstage/backend-common';
|
||||
import { PluginTaskScheduler } from '@backstage/backend-tasks';
|
||||
import { Schema } from 'jsonschema';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { SpawnOptionsWithoutStdio } from 'child_process';
|
||||
@@ -31,8 +32,11 @@ import { TaskSecrets as TaskSecrets_2 } from '@backstage/plugin-scaffolder-node'
|
||||
import { TaskSpec } from '@backstage/plugin-scaffolder-common';
|
||||
import { TaskSpecV1beta3 } from '@backstage/plugin-scaffolder-common';
|
||||
import { TemplateAction as TemplateAction_2 } from '@backstage/plugin-scaffolder-node';
|
||||
import { TemplateActionOptions } from '@backstage/plugin-scaffolder-node';
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
import { Writable } from 'stream';
|
||||
import { ZodType } from 'zod';
|
||||
import { ZodTypeDef } from 'zod';
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export type ActionContext<TInput extends JsonObject> = ActionContext_2<TInput>;
|
||||
@@ -40,7 +44,7 @@ export type ActionContext<TInput extends JsonObject> = ActionContext_2<TInput>;
|
||||
// @public
|
||||
export const createBuiltinActions: (
|
||||
options: CreateBuiltInActionsOptions,
|
||||
) => TemplateAction_2<JsonObject>[];
|
||||
) => TemplateAction_2[];
|
||||
|
||||
// @public
|
||||
export interface CreateBuiltInActionsOptions {
|
||||
@@ -72,7 +76,7 @@ export function createCatalogRegisterAction(options: {
|
||||
// @public
|
||||
export function createCatalogWriteAction(): TemplateAction_2<{
|
||||
filePath?: string | undefined;
|
||||
entity: Entity;
|
||||
entity: {};
|
||||
}>;
|
||||
|
||||
// @public
|
||||
@@ -514,9 +518,16 @@ export const createPublishGitlabMergeRequestAction: (options: {
|
||||
export function createRouter(options: RouterOptions): Promise<express.Router>;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export const createTemplateAction: <TInput extends JsonObject>(
|
||||
templateAction: TemplateAction_2<TInput>,
|
||||
) => TemplateAction_2<TInput>;
|
||||
export const createTemplateAction: <
|
||||
TParams,
|
||||
TInputSchema extends ZodType<any, ZodTypeDef, any> | Schema = {},
|
||||
TOutputSchema extends ZodType<any, ZodTypeDef, any> | Schema = {},
|
||||
TActionInput = TInputSchema extends ZodType<any, any, infer IReturn>
|
||||
? IReturn
|
||||
: TParams,
|
||||
>(
|
||||
action: TemplateActionOptions<TActionInput, TInputSchema, TOutputSchema>,
|
||||
) => TemplateAction_2<TActionInput>;
|
||||
|
||||
// @public
|
||||
export type CreateWorkerOptions = {
|
||||
@@ -863,11 +874,11 @@ export type TemplateAction<TInput extends JsonObject> =
|
||||
// @public
|
||||
export class TemplateActionRegistry {
|
||||
// (undocumented)
|
||||
get(actionId: string): TemplateAction_2<JsonObject>;
|
||||
get(actionId: string): TemplateAction_2;
|
||||
// (undocumented)
|
||||
list(): TemplateAction_2<JsonObject>[];
|
||||
list(): TemplateAction_2[];
|
||||
// (undocumented)
|
||||
register<TInput extends JsonObject>(action: TemplateAction_2<TInput>): void;
|
||||
register(action: TemplateAction_2): void;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -14,27 +14,26 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { ConflictError, NotFoundError } from '@backstage/errors';
|
||||
import { TemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
|
||||
/**
|
||||
* Registry of all registered template actions.
|
||||
* @public
|
||||
*/
|
||||
export class TemplateActionRegistry {
|
||||
private readonly actions = new Map<string, TemplateAction<any>>();
|
||||
private readonly actions = new Map<string, TemplateAction>();
|
||||
|
||||
register<TInput extends JsonObject>(action: TemplateAction<TInput>) {
|
||||
register(action: TemplateAction) {
|
||||
if (this.actions.has(action.id)) {
|
||||
throw new ConflictError(
|
||||
`Template action with ID '${action.id}' has already been registered`,
|
||||
);
|
||||
}
|
||||
|
||||
this.actions.set(action.id, action);
|
||||
}
|
||||
|
||||
get(actionId: string): TemplateAction<JsonObject> {
|
||||
get(actionId: string): TemplateAction {
|
||||
const action = this.actions.get(actionId);
|
||||
if (!action) {
|
||||
throw new NotFoundError(
|
||||
@@ -44,7 +43,7 @@ export class TemplateActionRegistry {
|
||||
return action;
|
||||
}
|
||||
|
||||
list(): TemplateAction<JsonObject>[] {
|
||||
list(): TemplateAction[] {
|
||||
return [...this.actions.values()];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
import fs from 'fs-extra';
|
||||
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import * as yaml from 'yaml';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { resolveSafeChildPath } from '@backstage/backend-common';
|
||||
import { z } from 'zod';
|
||||
|
||||
const id = 'catalog:write';
|
||||
|
||||
@@ -56,29 +56,26 @@ const examples = [
|
||||
* Writes a catalog descriptor file containing the provided entity to a path in the workspace.
|
||||
* @public
|
||||
*/
|
||||
|
||||
export function createCatalogWriteAction() {
|
||||
return createTemplateAction<{ filePath?: string; entity: Entity }>({
|
||||
return createTemplateAction({
|
||||
id,
|
||||
description: 'Writes the catalog-info.yaml for your template',
|
||||
examples,
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
filePath: {
|
||||
title: 'Catalog file path',
|
||||
description: 'Defaults to catalog-info.yaml',
|
||||
type: 'string',
|
||||
},
|
||||
entity: {
|
||||
title: 'Entity info to write catalog-info.yaml',
|
||||
description:
|
||||
'You can provide the same values used in the Entity schema.',
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
},
|
||||
input: z.object({
|
||||
filePath: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Defaults to catalog-info.yaml'),
|
||||
// TODO: this should reference an zod entity validator if it existed.
|
||||
entity: z
|
||||
.object({})
|
||||
.describe(
|
||||
'You can provide the same values used in the Entity schema.',
|
||||
),
|
||||
}),
|
||||
},
|
||||
examples,
|
||||
supportsDryRun: true,
|
||||
async handler(ctx) {
|
||||
ctx.logStream.write(`Writing catalog-info.yaml`);
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
GithubCredentialsProvider,
|
||||
ScmIntegrations,
|
||||
} from '@backstage/integration';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { TemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import {
|
||||
createCatalogRegisterAction,
|
||||
@@ -95,7 +94,7 @@ export interface CreateBuiltInActionsOptions {
|
||||
*/
|
||||
export const createBuiltinActions = (
|
||||
options: CreateBuiltInActionsOptions,
|
||||
): TemplateAction<JsonObject>[] => {
|
||||
): TemplateAction[] => {
|
||||
const {
|
||||
reader,
|
||||
integrations,
|
||||
@@ -188,5 +187,5 @@ export const createBuiltinActions = (
|
||||
}),
|
||||
];
|
||||
|
||||
return actions as TemplateAction<JsonObject>[];
|
||||
return actions as TemplateAction[];
|
||||
};
|
||||
|
||||
@@ -15,14 +15,13 @@
|
||||
*/
|
||||
|
||||
import { TemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { TemplateActionRegistry } from '../actions';
|
||||
|
||||
/** @internal */
|
||||
export class DecoratedActionsRegistry extends TemplateActionRegistry {
|
||||
constructor(
|
||||
private readonly innerRegistry: TemplateActionRegistry,
|
||||
extraActions: Array<TemplateAction<JsonObject>>,
|
||||
extraActions: Array<TemplateAction>,
|
||||
) {
|
||||
super();
|
||||
for (const action of extraActions) {
|
||||
@@ -30,7 +29,7 @@ export class DecoratedActionsRegistry extends TemplateActionRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
get(actionId: string): TemplateAction<JsonObject> {
|
||||
get(actionId: string): TemplateAction {
|
||||
try {
|
||||
return super.get(actionId);
|
||||
} catch {
|
||||
|
||||
@@ -24,8 +24,13 @@ import { ScmIntegrations } from '@backstage/integration';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { TaskContext } from './types';
|
||||
import { TaskSpec } from '@backstage/plugin-scaffolder-common';
|
||||
import { TaskSecrets } from '@backstage/plugin-scaffolder-node';
|
||||
import {
|
||||
createTemplateAction,
|
||||
TaskSecrets,
|
||||
TemplateAction,
|
||||
} from '@backstage/plugin-scaffolder-node';
|
||||
import { UserEntity } from '@backstage/catalog-model';
|
||||
import { z } from 'zod';
|
||||
|
||||
// The Stream module is lazy loaded, so make sure it's in the module cache before mocking fs
|
||||
void winston.transports.Stream;
|
||||
@@ -103,6 +108,20 @@ describe('DefaultWorkflowRunner', () => {
|
||||
},
|
||||
});
|
||||
|
||||
actionRegistry.register(
|
||||
createTemplateAction({
|
||||
id: 'jest-zod-validated-action',
|
||||
description: 'Mock action for testing',
|
||||
handler: fakeActionHandler,
|
||||
supportsDryRun: true,
|
||||
schema: {
|
||||
input: z.object({
|
||||
foo: z.number(),
|
||||
}),
|
||||
},
|
||||
}) as TemplateAction<unknown>,
|
||||
);
|
||||
|
||||
actionRegistry.register({
|
||||
id: 'output-action',
|
||||
description: 'Mock action for testing',
|
||||
@@ -151,6 +170,41 @@ describe('DefaultWorkflowRunner', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if the action has a 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-zod-validated-action' },
|
||||
],
|
||||
});
|
||||
|
||||
await expect(runner.execute(task)).rejects.toThrow(
|
||||
/Invalid input passed to action jest-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',
|
||||
parameters: {},
|
||||
output: {},
|
||||
steps: [
|
||||
{
|
||||
id: 'test',
|
||||
name: 'name',
|
||||
action: 'jest-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',
|
||||
|
||||
@@ -380,10 +380,7 @@ function scaffoldingTracker() {
|
||||
template,
|
||||
});
|
||||
|
||||
async function skipDryRun(
|
||||
step: TaskStep,
|
||||
action: TemplateAction<JsonObject>,
|
||||
) {
|
||||
async function skipDryRun(step: TaskStep, action: TemplateAction) {
|
||||
task.emitLog(`Skipping because ${action.id} does not support dry-run`, {
|
||||
stepId: step.id,
|
||||
status: 'skipped',
|
||||
|
||||
@@ -66,7 +66,6 @@ export interface RouterOptions {
|
||||
database: PluginDatabaseManager;
|
||||
catalogClient: CatalogApi;
|
||||
scheduler?: PluginTaskScheduler;
|
||||
|
||||
actions?: TemplateAction<any>[];
|
||||
/**
|
||||
* @deprecated taskWorkers is deprecated in favor of concurrentTasksLimit option with a single TaskWorker
|
||||
|
||||
@@ -13,14 +13,15 @@ import { Schema } from 'jsonschema';
|
||||
import { TemplateInfo } from '@backstage/plugin-scaffolder-common';
|
||||
import { UserEntity } from '@backstage/catalog-model';
|
||||
import { Writable } from 'stream';
|
||||
import { z } from 'zod';
|
||||
|
||||
// @public
|
||||
export type ActionContext<TInput extends JsonObject> = {
|
||||
export type ActionContext<TActionInput extends JsonObject> = {
|
||||
logger: Logger;
|
||||
logStream: Writable;
|
||||
secrets?: TaskSecrets;
|
||||
workspacePath: string;
|
||||
input: TInput;
|
||||
input: TActionInput;
|
||||
output(name: string, value: JsonValue): void;
|
||||
createTemporaryDirectory(): Promise<string>;
|
||||
templateInfo?: TemplateInfo;
|
||||
@@ -32,9 +33,16 @@ export type ActionContext<TInput extends JsonObject> = {
|
||||
};
|
||||
|
||||
// @public
|
||||
export const createTemplateAction: <TInput extends JsonObject>(
|
||||
templateAction: TemplateAction<TInput>,
|
||||
) => TemplateAction<TInput>;
|
||||
export const createTemplateAction: <
|
||||
TParams,
|
||||
TInputSchema extends z.ZodType<any, z.ZodTypeDef, any> | Schema = {},
|
||||
TOutputSchema extends z.ZodType<any, z.ZodTypeDef, any> | Schema = {},
|
||||
TActionInput = TInputSchema extends z.ZodType<any, any, infer IReturn>
|
||||
? IReturn
|
||||
: TParams,
|
||||
>(
|
||||
action: TemplateActionOptions<TActionInput, TInputSchema, TOutputSchema>,
|
||||
) => TemplateAction<TActionInput>;
|
||||
|
||||
// @alpha
|
||||
export interface ScaffolderActionsExtensionPoint {
|
||||
@@ -51,7 +59,7 @@ export type TaskSecrets = Record<string, string> & {
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type TemplateAction<TInput extends JsonObject> = {
|
||||
export type TemplateAction<TActionInput = unknown> = {
|
||||
id: string;
|
||||
description?: string;
|
||||
examples?: {
|
||||
@@ -63,6 +71,26 @@ export type TemplateAction<TInput extends JsonObject> = {
|
||||
input?: Schema;
|
||||
output?: Schema;
|
||||
};
|
||||
handler: (ctx: ActionContext<TInput>) => Promise<void>;
|
||||
handler: (ctx: ActionContext<TActionInput>) => Promise<void>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type TemplateActionOptions<
|
||||
TActionInput = {},
|
||||
TInputSchema extends Schema | z.ZodType = {},
|
||||
TOutputSchema extends Schema | z.ZodType = {},
|
||||
> = {
|
||||
id: string;
|
||||
description?: string;
|
||||
examples?: {
|
||||
description: string;
|
||||
example: string;
|
||||
}[];
|
||||
supportsDryRun?: boolean;
|
||||
schema?: {
|
||||
input?: TInputSchema;
|
||||
output?: TOutputSchema;
|
||||
};
|
||||
handler: (ctx: ActionContext<TActionInput>) => Promise<void>;
|
||||
};
|
||||
```
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
"@backstage/plugin-scaffolder-common": "workspace:^",
|
||||
"@backstage/types": "workspace:^",
|
||||
"jsonschema": "^1.2.6",
|
||||
"winston": "^3.2.1"
|
||||
"winston": "^3.2.1",
|
||||
"zod": "~3.18.0",
|
||||
"zod-to-json-schema": "~3.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^"
|
||||
|
||||
@@ -14,17 +14,61 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { TemplateAction } from './types';
|
||||
import { ActionContext, TemplateAction } from './types';
|
||||
import { z } from 'zod';
|
||||
import { Schema } from 'jsonschema';
|
||||
import zodToJsonSchema from 'zod-to-json-schema';
|
||||
|
||||
/** @public */
|
||||
export type TemplateActionOptions<
|
||||
TActionInput = {},
|
||||
TInputSchema extends Schema | z.ZodType = {},
|
||||
TOutputSchema extends Schema | z.ZodType = {},
|
||||
> = {
|
||||
id: string;
|
||||
description?: string;
|
||||
examples?: { description: string; example: string }[];
|
||||
supportsDryRun?: boolean;
|
||||
schema?: {
|
||||
input?: TInputSchema;
|
||||
output?: TOutputSchema;
|
||||
};
|
||||
handler: (ctx: ActionContext<TActionInput>) => Promise<void>;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 = <TInput extends JsonObject>(
|
||||
templateAction: TemplateAction<TInput>,
|
||||
): TemplateAction<TInput> => {
|
||||
// TODO(blam): Can add some more validation here to validate the action later on
|
||||
export const createTemplateAction = <
|
||||
TParams,
|
||||
TInputSchema extends Schema | z.ZodType = {},
|
||||
TOutputSchema extends Schema | z.ZodType = {},
|
||||
TActionInput = TInputSchema extends z.ZodType<any, any, infer IReturn>
|
||||
? IReturn
|
||||
: TParams,
|
||||
>(
|
||||
action: TemplateActionOptions<TActionInput, TInputSchema, TOutputSchema>,
|
||||
): TemplateAction<TActionInput> => {
|
||||
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;
|
||||
|
||||
const templateAction = {
|
||||
...action,
|
||||
schema: {
|
||||
...action.schema,
|
||||
input: inputSchema,
|
||||
output: outputSchema,
|
||||
},
|
||||
};
|
||||
|
||||
return templateAction;
|
||||
};
|
||||
|
||||
@@ -14,5 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { createTemplateAction } from './createTemplateAction';
|
||||
export {
|
||||
createTemplateAction,
|
||||
type TemplateActionOptions,
|
||||
} from './createTemplateAction';
|
||||
export { type ActionContext, type TemplateAction } from './types';
|
||||
|
||||
@@ -16,22 +16,22 @@
|
||||
|
||||
import { Logger } from 'winston';
|
||||
import { Writable } from 'stream';
|
||||
import { JsonValue, JsonObject } from '@backstage/types';
|
||||
import { Schema } from 'jsonschema';
|
||||
import { JsonObject, JsonValue } from '@backstage/types';
|
||||
import { TaskSecrets } from '../tasks/types';
|
||||
import { TemplateInfo } from '@backstage/plugin-scaffolder-common';
|
||||
import { UserEntity } from '@backstage/catalog-model';
|
||||
import { Schema } from 'jsonschema';
|
||||
|
||||
/**
|
||||
* ActionContext is passed into scaffolder actions.
|
||||
* @public
|
||||
*/
|
||||
export type ActionContext<TInput extends JsonObject> = {
|
||||
export type ActionContext<TActionInput extends JsonObject> = {
|
||||
logger: Logger;
|
||||
logStream: Writable;
|
||||
secrets?: TaskSecrets;
|
||||
workspacePath: string;
|
||||
input: TInput;
|
||||
input: TActionInput;
|
||||
output(name: string, value: JsonValue): void;
|
||||
|
||||
/**
|
||||
@@ -63,7 +63,7 @@ export type ActionContext<TInput extends JsonObject> = {
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type TemplateAction<TInput extends JsonObject> = {
|
||||
export type TemplateAction<TActionInput = unknown> = {
|
||||
id: string;
|
||||
description?: string;
|
||||
examples?: { description: string; example: string }[];
|
||||
@@ -72,5 +72,5 @@ export type TemplateAction<TInput extends JsonObject> = {
|
||||
input?: Schema;
|
||||
output?: Schema;
|
||||
};
|
||||
handler: (ctx: ActionContext<TInput>) => Promise<void>;
|
||||
handler: (ctx: ActionContext<TActionInput>) => Promise<void>;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user