diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 0169770649..a8133b0bd7 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -32,6 +32,7 @@ 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 { z } from 'zod'; @@ -44,7 +45,7 @@ export type ActionContext = ActionContext_2; // @public export const createBuiltinActions: ( options: CreateBuiltInActionsOptions, -) => TemplateAction_2[]; +) => TemplateAction_2[]; // @public export interface CreateBuiltInActionsOptions { @@ -626,7 +627,7 @@ export const createTemplateAction: < TInputSchema extends ZodType | Schema = {}, TOutputSchema extends ZodType | Schema = {}, >( - templateAction: TemplateAction_2, + action: TemplateActionOptions, ) => TemplateAction_2; // @public @@ -725,7 +726,7 @@ export type OctokitWithPullRequestPluginClient = Octokit & { // @public export interface RouterOptions { // (undocumented) - actions?: TemplateAction_2[]; + actions?: TemplateAction_2[]; // (undocumented) additionalTemplateFilters?: Record; // (undocumented) diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 8a61c7dfac..12ef31899c 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -92,8 +92,7 @@ "winston": "^3.2.1", "yaml": "^2.0.0", "zen-observable": "^0.10.0", - "zod": "~3.18.0", - "zod-to-json-schema": "~3.18.0" + "zod": "~3.18.0" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts b/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts index 3964c4b1f2..3868380919 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts @@ -16,7 +16,6 @@ import { ConflictError, NotFoundError } from '@backstage/errors'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; -import zodToJsonSchema from 'zod-to-json-schema'; /** * Registry of all registered template actions. * @public @@ -31,29 +30,7 @@ export class TemplateActionRegistry { ); } - // It's better to convert the zod here, and just deal with jsonschema everywhere - // rather than adding the zod check everywhere like the nunjucks engine, and the /actions/list - // endpoint to create jsonschema for the frontend. - 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, - }, - }; - - this.actions.set(action.id, templateAction); + this.actions.set(action.id, action); } get(actionId: string): TemplateAction { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts index d83d9c5b07..9ba4a27b1a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts @@ -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[] => { +): TemplateAction[] => { const { reader, integrations, @@ -188,5 +187,5 @@ export const createBuiltinActions = ( }), ]; - return actions as TemplateAction[]; + return actions as TemplateAction[]; }; diff --git a/plugins/scaffolder-backend/src/scaffolder/dryrun/DecoratedActionsRegistry.ts b/plugins/scaffolder-backend/src/scaffolder/dryrun/DecoratedActionsRegistry.ts index c02375554c..7109a6621e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/dryrun/DecoratedActionsRegistry.ts +++ b/plugins/scaffolder-backend/src/scaffolder/dryrun/DecoratedActionsRegistry.ts @@ -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>, + extraActions: Array, ) { super(); for (const action of extraActions) { @@ -30,7 +29,7 @@ export class DecoratedActionsRegistry extends TemplateActionRegistry { } } - get(actionId: string): TemplateAction { + get(actionId: string): TemplateAction { try { return super.get(actionId); } catch { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts index 0ab251208c..135ed4473e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts @@ -24,7 +24,10 @@ 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, +} from '@backstage/plugin-scaffolder-node'; import { UserEntity } from '@backstage/catalog-model'; import { z } from 'zod'; @@ -104,17 +107,19 @@ describe('DefaultWorkflowRunner', () => { }, }); - actionRegistry.register({ - id: 'jest-zod-validated-action', - description: 'Mock action for testing', - supportsDryRun: true, - handler: fakeActionHandler, - schema: { - input: z.object({ - foo: z.number(), - }), - }, - }); + actionRegistry.register( + createTemplateAction({ + id: 'jest-zod-validated-action', + description: 'Mock action for testing', + supportsDryRun: true, + handler: fakeActionHandler, + schema: { + input: z.object({ + foo: z.number(), + }), + }, + }), + ); actionRegistry.register({ id: 'output-action', diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 516abe79e4..31d3479026 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -24,7 +24,7 @@ import { JsonObject, JsonValue } from '@backstage/types'; import { InputError } from '@backstage/errors'; import { PassThrough } from 'stream'; import { generateExampleOutput, isTruthy } from './helper'; -import { validate as validateJsonSchema } from 'jsonschema'; +import { Schema, validate as validateJsonSchema } from 'jsonschema'; import { parseRepoUrl } from '../actions/builtin/publish/util'; import { TemplateActionRegistry } from '../actions'; import { @@ -380,10 +380,7 @@ function scaffoldingTracker() { template, }); - async function skipDryRun( - step: TaskStep, - action: TemplateAction, - ) { + async function skipDryRun(step: TaskStep, action: TemplateAction) { task.emitLog(`Skipping because ${action.id} does not support dry-run`, { stepId: step.id, status: 'skipped', diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 1dc7036450..b219c81219 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -67,7 +67,7 @@ export interface RouterOptions { catalogClient: CatalogApi; scheduler?: PluginTaskScheduler; - actions?: TemplateAction[]; + actions?: TemplateAction[]; /** * @deprecated taskWorkers is deprecated in favor of concurrentTasksLimit option with a single TaskWorker * @defaultValue 1 @@ -293,7 +293,7 @@ export async function createRouter( id: action.id, description: action.description, examples: action.examples, - schema: {}, + schema: action.schema, }; }); res.json(actionsList); diff --git a/plugins/scaffolder-node/api-report.md b/plugins/scaffolder-node/api-report.md index 7e949030b6..8acc32f9d3 100644 --- a/plugins/scaffolder-node/api-report.md +++ b/plugins/scaffolder-node/api-report.md @@ -6,7 +6,6 @@ /// import { ExtensionPoint } from '@backstage/backend-plugin-api'; -import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; import { Logger } from 'winston'; import { Schema } from 'jsonschema'; @@ -16,7 +15,7 @@ import { Writable } from 'stream'; import { z } from 'zod'; // @public -export type ActionContext = { +export type ActionContext = { logger: Logger; logStream: Writable; secrets?: TaskSecrets; @@ -38,7 +37,7 @@ export const createTemplateAction: < TInputSchema extends z.ZodType | Schema = {}, TOutputSchema extends z.ZodType | Schema = {}, >( - templateAction: TemplateAction, + action: TemplateActionOptions, ) => TemplateAction; // @alpha @@ -57,6 +56,26 @@ export type TaskSecrets = Record & { // @public (undocumented) export type TemplateAction< + TParams = unknown, + TInputSchema extends Schema | unknown = unknown, + TOutputSchema extends Schema | unknown = unknown, +> = { + id: string; + description?: string; + examples?: { + description: string; + example: string; + }[]; + supportsDryRun?: boolean; + schema?: { + input?: TInputSchema; + output?: TOutputSchema; + }; + handler: (ctx: ActionContext) => Promise; +}; + +// @public (undocumented) +export type TemplateActionOptions< TParams = {}, TInputSchema extends Schema | z.ZodType = {}, TOutputSchema extends Schema | z.ZodType = {}, diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index 791376f7d0..b7fd22a2bd 100644 --- a/plugins/scaffolder-node/package.json +++ b/plugins/scaffolder-node/package.json @@ -30,7 +30,8 @@ "@backstage/types": "workspace:^", "jsonschema": "^1.2.6", "winston": "^3.2.1", - "zod": "~3.18.0" + "zod": "~3.18.0", + "zod-to-json-schema": "~3.18.0" }, "devDependencies": { "@backstage/cli": "workspace:^" diff --git a/plugins/scaffolder-node/src/actions/createTemplateAction.ts b/plugins/scaffolder-node/src/actions/createTemplateAction.ts index 11c01a6086..9b7848c43b 100644 --- a/plugins/scaffolder-node/src/actions/createTemplateAction.ts +++ b/plugins/scaffolder-node/src/actions/createTemplateAction.ts @@ -14,12 +14,37 @@ * limitations under the License. */ -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< + TParams = {}, + 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< + TInputSchema extends z.ZodType + ? IReturn + : TParams + >, + ) => Promise; +}; + /** * 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 = < @@ -27,7 +52,26 @@ export const createTemplateAction = < TInputSchema extends Schema | z.ZodType = {}, TOutputSchema extends Schema | z.ZodType = {}, >( - templateAction: TemplateAction, -): TemplateAction => { - return templateAction; + action: TemplateActionOptions, +): TemplateAction => { + 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 as TemplateAction; }; diff --git a/plugins/scaffolder-node/src/actions/index.ts b/plugins/scaffolder-node/src/actions/index.ts index 7fdee6d692..e4af098356 100644 --- a/plugins/scaffolder-node/src/actions/index.ts +++ b/plugins/scaffolder-node/src/actions/index.ts @@ -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'; diff --git a/plugins/scaffolder-node/src/actions/types.ts b/plugins/scaffolder-node/src/actions/types.ts index 2adad00bba..a114574f85 100644 --- a/plugins/scaffolder-node/src/actions/types.ts +++ b/plugins/scaffolder-node/src/actions/types.ts @@ -16,17 +16,16 @@ import { Logger } from 'winston'; import { Writable } from 'stream'; -import { JsonValue, JsonObject } from '@backstage/types'; -import { Schema } from 'jsonschema'; +import { JsonValue } from '@backstage/types'; import { TaskSecrets } from '../tasks/types'; import { TemplateInfo } from '@backstage/plugin-scaffolder-common'; import { UserEntity } from '@backstage/catalog-model'; -import { z } from 'zod'; +import { Schema } from 'jsonschema'; /** * ActionContext is passed into scaffolder actions. * @public */ -export type ActionContext = { +export type ActionContext = { logger: Logger; logStream: Writable; secrets?: TaskSecrets; @@ -63,24 +62,14 @@ export type ActionContext = { }; /** @public */ -export type TemplateAction< - TParams = {}, - TInputSchema extends Schema | z.ZodType = {}, - TOutputSchema extends Schema | z.ZodType = {}, -> = { +export type TemplateAction = { id: string; description?: string; examples?: { description: string; example: string }[]; supportsDryRun?: boolean; schema?: { - input?: TInputSchema; - output?: TOutputSchema; + input?: Schema; + output?: Schema; }; - handler: ( - ctx: ActionContext< - TInputSchema extends z.ZodType - ? IReturn - : TParams - >, - ) => Promise; + handler: (ctx: ActionContext) => Promise; }; diff --git a/yarn.lock b/yarn.lock index bf4403c54f..a89f3c14fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7716,7 +7716,6 @@ __metadata: yaml: ^2.0.0 zen-observable: ^0.10.0 zod: ~3.18.0 - zod-to-json-schema: ~3.18.0 languageName: unknown linkType: soft @@ -7742,6 +7741,7 @@ __metadata: jsonschema: ^1.2.6 winston: ^3.2.1 zod: ~3.18.0 + zod-to-json-schema: ~3.18.0 languageName: unknown linkType: soft