From 1a588465e93fd26bdf7c76289f6922d2b896360c Mon Sep 17 00:00:00 2001 From: Paul Schultz Date: Thu, 3 Oct 2024 14:30:19 -0500 Subject: [PATCH 01/17] feat(scaffolder): add first class citizen support for zod Signed-off-by: Paul Schultz backport better typescript support Signed-off-by: Paul Schultz wip Signed-off-by: Paul Schultz wip Signed-off-by: Paul Schultz complete refactor Signed-off-by: Paul Schultz --- .changeset/curvy-numbers-grin.md | 7 + .changeset/dull-olives-itch.md | 7 + packages/types/report.api.md | 5 + packages/types/src/utility.ts | 38 ++++ .../src/actions/createTemplateAction.ts | 192 +++++++++++++++--- plugins/scaffolder-node/src/actions/index.ts | 24 ++- plugins/scaffolder-node/src/actions/types.ts | 171 ++++++++++++++-- 7 files changed, 391 insertions(+), 53 deletions(-) create mode 100644 .changeset/curvy-numbers-grin.md create mode 100644 .changeset/dull-olives-itch.md create mode 100644 packages/types/src/utility.ts diff --git a/.changeset/curvy-numbers-grin.md b/.changeset/curvy-numbers-grin.md new file mode 100644 index 0000000000..1f786335c0 --- /dev/null +++ b/.changeset/curvy-numbers-grin.md @@ -0,0 +1,7 @@ +--- +'@backstage/types': minor +--- + +feat: feat(scaffolder): add first class citizen support for zod + +Added `Prettify` utility type to enhance readability of hover overlays (type hints) for object types. This type simplifies complex object intersections, making them more legible in editor tooltips. diff --git a/.changeset/dull-olives-itch.md b/.changeset/dull-olives-itch.md new file mode 100644 index 0000000000..b886906849 --- /dev/null +++ b/.changeset/dull-olives-itch.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-scaffolder-node': minor +--- + +feat(scaffolder): add first class citizen support for zod + +This change introduces a new way to define template actions using Zod schemas for type safety. The existing `createTemplateAction` function is now renamed to `oldCreateTemplateAction` to maintain backwards compatibility. A new `createTemplateAction` function is introduced which acts as an overload, supporting both the old style (using JSON Schema or string schemas) via `oldCreateTemplateAction` and the new style (using Zod schemas) via `newCreateTemplateAction`. This new function, `newCreateTemplateAction`, provides direct support for Zod, simplifying action definition and enhancing type checking. diff --git a/packages/types/report.api.md b/packages/types/report.api.md index b6bfae0862..130a6ae504 100644 --- a/packages/types/report.api.md +++ b/packages/types/report.api.md @@ -79,6 +79,11 @@ export type Observer = { complete?(): void; }; +// @public +export type Prettify> = { + [K in keyof T]: T[K]; +} & {}; + // @public export type Subscription = { unsubscribe(): void; diff --git a/packages/types/src/utility.ts b/packages/types/src/utility.ts new file mode 100644 index 0000000000..ebcfedc360 --- /dev/null +++ b/packages/types/src/utility.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * A utility type that enhances the readability of hover overlays (type hints) for object types. + * + * @example Prettifying a basic object merge + * + * ### Basic object type + * ```ts + * type Example = { item1: string } & { item2: number } + * // ?^ { item1: string } & { item2: number } + * ``` + * + * ### Usage + * ```ts + * type Example = Prettify<{ item1: string } & { item2: number }> + * // ?^ { item1: string, item2: number } + * ``` + * + * @public + */ +export type Prettify> = { + [K in keyof T]: T[K]; +} & {}; diff --git a/plugins/scaffolder-node/src/actions/createTemplateAction.ts b/plugins/scaffolder-node/src/actions/createTemplateAction.ts index 4721df7de5..a083b2ab88 100644 --- a/plugins/scaffolder-node/src/actions/createTemplateAction.ts +++ b/plugins/scaffolder-node/src/actions/createTemplateAction.ts @@ -14,24 +14,42 @@ * limitations under the License. */ -import { ActionContext, TemplateAction } from './types'; +import type { JsonObject, Prettify } from '@backstage/types'; +import type { Schema } from 'jsonschema'; import { z } from 'zod'; -import { Schema } from 'jsonschema'; import zodToJsonSchema from 'zod-to-json-schema'; -import { JsonObject } from '@backstage/types'; +import type { + InferActionType, + NewActionContext, + NewTemplateAction, + OldActionContext, + OldTemplateAction, + TemplateExample, +} from './types'; -/** @public */ -export type TemplateExample = { - description: string; - example: string; -}; - -/** @public */ -export type TemplateActionOptions< - TActionInput extends JsonObject = {}, - TActionOutput extends JsonObject = {}, - TInputSchema extends Schema | z.ZodType = {}, - TOutputSchema extends Schema | z.ZodType = {}, +/** + * @deprecated migrate to {@link NewTemplateActionOptions} + * @public + */ +export type OldTemplateActionOptions< + TInputParams extends JsonObject = JsonObject, + TOutputParams extends JsonObject = JsonObject, + TInputSchema extends Schema | z.ZodType = Schema, + TOutputSchema extends Schema | z.ZodType = Schema, + TActionInput extends JsonObject = TInputSchema extends z.ZodType< + any, + any, + infer IReturn + > + ? IReturn + : TInputParams, + TActionOutput extends JsonObject = TOutputSchema extends z.ZodType< + any, + any, + infer IReturn + > + ? IReturn + : TOutputParams, > = { id: string; description?: string; @@ -41,15 +59,69 @@ export type TemplateActionOptions< input?: TInputSchema; output?: TOutputSchema; }; - handler: (ctx: ActionContext) => Promise; + handler: ( + ctx: OldActionContext, + ) => Promise; }; +/** + * @public + */ +export type NewTemplateActionOptions< + TInputParams extends Record< + PropertyKey, + (zod: typeof z) => z.ZodType + > = Record z.ZodType>, + TOutputParams extends Record< + PropertyKey, + (zod: typeof z) => z.ZodType + > = Record z.ZodType>, +> = { + id: string; + description?: string; + examples?: TemplateExample[]; + supportsDryRun?: boolean; + schema: { + input: TInputParams; + output: TOutputParams; + }; + handler: ( + ctx: NewActionContext< + InferActionType, + InferActionType + >, + ) => Promise; +}; + +/** + * @public + */ +export type TemplateActionOptions< + TInputParams extends Record z.ZodType>, + TOutputParams extends Record z.ZodType>, +> = + | OldTemplateActionOptions + | NewTemplateActionOptions; + +function isZod(schema?: Schema | z.ZodType): schema is z.ZodType { + return !!(schema && 'safeParseAsync' in schema); +} + +function transformZodRecordToObject( + record: Record z.ZodType>, +): z.ZodObject> { + return z.object( + Object.fromEntries(Object.entries(record).map(([k, v]) => [k, v(z)])), + ); +} + /** * 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. + * @deprecated migrate to {@link newCreateTemplateAction} * @public */ -export const createTemplateAction = < +export function oldCreateTemplateAction< TInputParams extends JsonObject = JsonObject, TOutputParams extends JsonObject = JsonObject, TInputSchema extends Schema | z.ZodType = {}, @@ -69,29 +141,91 @@ export const createTemplateAction = < ? IReturn : TOutputParams, >( - action: TemplateActionOptions< - TActionInput, - TActionOutput, + action: OldTemplateActionOptions< + TInputParams, + TOutputParams, TInputSchema, - TOutputSchema + TOutputSchema, + TActionInput, + TActionOutput >, -): TemplateAction => { +): OldTemplateAction { const inputSchema = - action.schema?.input && 'safeParseAsync' in action.schema.input - ? zodToJsonSchema(action.schema.input) + action.schema && action.schema.input && isZod(action.schema.input) + ? (zodToJsonSchema(action.schema.input) as Schema) : action.schema?.input; const outputSchema = - action.schema?.output && 'safeParseAsync' in action.schema.output - ? zodToJsonSchema(action.schema.output) + action.schema && action.schema.output && isZod(action.schema.output) + ? (zodToJsonSchema(action.schema.output) as Schema) : action.schema?.output; return { ...action, schema: { ...action.schema, - input: inputSchema as TInputSchema, - output: outputSchema as TOutputSchema, + input: inputSchema, + output: outputSchema, }, }; -}; +} + +/** + * 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 newCreateTemplateAction< + TInputParams extends Record z.ZodType>, + TOutputParams extends Record z.ZodType>, +>( + action: NewTemplateActionOptions, +): NewTemplateAction< + InferActionType, + InferActionType +> { + const input = transformZodRecordToObject(action.schema.input); + const output = transformZodRecordToObject(action.schema.output); + + return { + ...action, + schema: { + ...action.schema, + input: zodToJsonSchema(input) as Schema, + output: zodToJsonSchema(output) as Schema, + }, + }; +} + +function isOldAction( + action: OldTemplateActionOptions | NewTemplateActionOptions, +): action is OldTemplateActionOptions { + return ( + isZod(action.schema?.input) || + typeof action.schema?.input === 'string' || + isZod(action.schema?.output) || + typeof action.schema?.output === 'string' + ); +} + +/** + * 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< + TInputParams extends JsonObject = JsonObject, + TOutputParams extends JsonObject = JsonObject, + TAction extends + | OldTemplateActionOptions + | NewTemplateActionOptions = OldTemplateActionOptions, + TReturn = TAction extends OldTemplateActionOptions + ? Prettify> + : Prettify, +>(action: TAction): TReturn { + if (isOldAction(action)) { + return oldCreateTemplateAction(action) as TReturn; + } + + return newCreateTemplateAction(action) as TReturn; +} diff --git a/plugins/scaffolder-node/src/actions/index.ts b/plugins/scaffolder-node/src/actions/index.ts index c1c8551c53..9ac9d9a5a4 100644 --- a/plugins/scaffolder-node/src/actions/index.ts +++ b/plugins/scaffolder-node/src/actions/index.ts @@ -16,21 +16,31 @@ export { createTemplateAction, + type NewTemplateActionOptions, + type OldTemplateActionOptions, type TemplateActionOptions, - type TemplateExample, } from './createTemplateAction'; export { executeShellCommand, type ExecuteShellCommandOptions, } from './executeShellCommand'; export { fetchContents, fetchFile } from './fetch'; -export { type ActionContext, type TemplateAction } from './types'; export { - initRepoAndPush, - commitAndPushRepo, - commitAndPushBranch, addFiles, - createBranch, cloneRepo, + commitAndPushBranch, + commitAndPushRepo, + createBranch, + initRepoAndPush, } from './gitHelpers'; -export { parseRepoUrl, getRepoSourceDirectory } from './util'; +export type { + ActionContext, + InferActionType, + NewActionContext, + NewTemplateAction, + OldActionContext, + OldTemplateAction, + TemplateAction, + TemplateExample, +} from './types'; +export { getRepoSourceDirectory, parseRepoUrl } from './util'; diff --git a/plugins/scaffolder-node/src/actions/types.ts b/plugins/scaffolder-node/src/actions/types.ts index 171d3d82db..e554baf2f7 100644 --- a/plugins/scaffolder-node/src/actions/types.ts +++ b/plugins/scaffolder-node/src/actions/types.ts @@ -14,21 +14,45 @@ * 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'; -import { UserEntity } from '@backstage/catalog-model'; -import { Schema } from 'jsonschema'; -import { BackstageCredentials } from '@backstage/backend-plugin-api'; +import type { + BackstageCredentials, + LoggerService, +} from '@backstage/backend-plugin-api'; +import type { UserEntity } from '@backstage/catalog-model'; +import type { TemplateInfo } from '@backstage/plugin-scaffolder-common'; +import type { JsonObject, JsonValue, Prettify } from '@backstage/types'; +import type { Schema } from 'jsonschema'; +import type { Writable } from 'stream'; +import type { Logger } from 'winston'; +import { z } from 'zod'; +import type { TaskSecrets } from '../tasks'; /** - * ActionContext is passed into scaffolder actions. * @public */ -export type ActionContext< - TActionInput extends JsonObject, +export type TemplateExample = { + description: string; + example: string; +}; + +/** @public */ +export type InferActionType< + T extends Record z.ZodType>, +> = Prettify<{ + [K in keyof T]: T[K] extends ( + zod: typeof z, + ) => z.ZodType + ? Extract + : never; +}>; + +/** + * OldActionContext is passed into scaffolder actions. + * @deprecated migrate to {@link NewActionContext} + * @public + */ +export type OldActionContext< + TActionInput extends JsonObject = JsonObject, TActionOutput extends JsonObject = JsonObject, > = { // TODO(blam): move this to LoggerService @@ -43,8 +67,10 @@ export type ActionContext< fn: () => Promise | T; }): Promise; output( - name: keyof TActionOutput, - value: TActionOutput[keyof TActionOutput], + ...params: { + /* This maps the key to the value for type checking */ + [K in keyof TActionOutput]: [name: K, value: TActionOutput[K]]; + }[keyof TActionOutput] ): void; /** @@ -97,18 +123,129 @@ export type ActionContext< each?: JsonObject; }; -/** @public */ -export type TemplateAction< +/** + * NewActionContext is passed into scaffolder actions. + * @public + */ +export type NewActionContext< + TActionInput extends JsonObject = JsonObject, + TActionOutput extends JsonObject = JsonObject, +> = { + logger: LoggerService; + secrets?: TaskSecrets; + workspacePath: string; + input: TActionInput; + checkpoint( + key: string, + fn: () => Promise, + ): Promise; + output( + ...params: { + /* This maps the key to the value for type checking */ + [K in keyof TActionOutput]: [name: K, value: TActionOutput[K]]; + }[keyof TActionOutput] + ): void; + + /** + * Creates a temporary directory for use by the action, which is then cleaned up automatically. + */ + createTemporaryDirectory(): Promise; + + /** + * Get the credentials for the current request + */ + getInitiatorCredentials(): Promise; + + templateInfo?: TemplateInfo; + + /** + * Whether this action invocation is a dry-run or not. + * This will only ever be true if the actions as marked as supporting dry-runs. + */ + isDryRun?: boolean; + + /** + * The user which triggered the action. + */ + user?: { + /** + * The decorated entity from the Catalog + */ + entity?: UserEntity; + /** + * An entity ref for the author of the task + */ + ref?: string; + }; + + /** + * Implement the signal to make your custom step abortable https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal + */ + signal?: AbortSignal; + + /** + * Optional value of each invocation + */ + each?: JsonObject; +}; + +/** + * @public + */ +export type ActionContext< + TActionInput extends JsonObject = JsonObject, + TActionOutput extends JsonObject = JsonObject, +> = + | OldActionContext + | NewActionContext; + +/** + * @deprecated migrate to {@link NewTemplateAction} + * @public + */ +export type OldTemplateAction< TActionInput extends JsonObject = JsonObject, TActionOutput extends JsonObject = JsonObject, > = { id: string; description?: string; - examples?: { description: string; example: string }[]; + examples?: TemplateExample[]; supportsDryRun?: boolean; schema?: { input?: Schema; output?: Schema; }; - handler: (ctx: ActionContext) => Promise; + handler: ( + ctx: OldActionContext, + ) => Promise; }; + +/** + * @public + */ +export type NewTemplateAction< + TActionInput extends JsonObject = JsonObject, + TActionOutput extends JsonObject = JsonObject, +> = { + id: string; + description?: string; + examples?: TemplateExample[]; + supportsDryRun?: boolean; + schema: { + input: Schema; + output: Schema; + }; + handler: ( + ctx: NewActionContext, + ) => Promise; +}; + +/** + * @public + */ +export type TemplateAction< + TActionInput extends JsonObject = JsonObject, + TActionOutput extends JsonObject = JsonObject, +> = + | OldTemplateAction + | NewTemplateAction; From 01b228423ec36d9ebb29de91cc567912fc0b301d Mon Sep 17 00:00:00 2001 From: Paul Schultz Date: Tue, 17 Dec 2024 12:39:59 -0600 Subject: [PATCH 02/17] updated naming schema Signed-off-by: Paul Schultz --- .../src/actions/createTemplateAction.ts | 88 ++++++++++++------- plugins/scaffolder-node/src/actions/index.ts | 12 +-- plugins/scaffolder-node/src/actions/types.ts | 34 +++---- 3 files changed, 74 insertions(+), 60 deletions(-) diff --git a/plugins/scaffolder-node/src/actions/createTemplateAction.ts b/plugins/scaffolder-node/src/actions/createTemplateAction.ts index a083b2ab88..d33afdaca5 100644 --- a/plugins/scaffolder-node/src/actions/createTemplateAction.ts +++ b/plugins/scaffolder-node/src/actions/createTemplateAction.ts @@ -20,18 +20,18 @@ import { z } from 'zod'; import zodToJsonSchema from 'zod-to-json-schema'; import type { InferActionType, - NewActionContext, - NewTemplateAction, - OldActionContext, - OldTemplateAction, + ActionContextV2, + TemplateActionV2, + ActionContextV1, + TemplateActionV1, TemplateExample, } from './types'; /** - * @deprecated migrate to {@link NewTemplateActionOptions} + * @deprecated migrate to {@link TemplateActionOptionsV2} * @public */ -export type OldTemplateActionOptions< +export type TemplateActionOptionsV1< TInputParams extends JsonObject = JsonObject, TOutputParams extends JsonObject = JsonObject, TInputSchema extends Schema | z.ZodType = Schema, @@ -59,15 +59,13 @@ export type OldTemplateActionOptions< input?: TInputSchema; output?: TOutputSchema; }; - handler: ( - ctx: OldActionContext, - ) => Promise; + handler: (ctx: ActionContextV1) => Promise; }; /** * @public */ -export type NewTemplateActionOptions< +export type TemplateActionOptionsV2< TInputParams extends Record< PropertyKey, (zod: typeof z) => z.ZodType @@ -86,7 +84,7 @@ export type NewTemplateActionOptions< output: TOutputParams; }; handler: ( - ctx: NewActionContext< + ctx: ActionContextV2< InferActionType, InferActionType >, @@ -94,14 +92,36 @@ export type NewTemplateActionOptions< }; /** + * @deprecated migrate to {@link TemplateActionOptionsV2} * @public */ export type TemplateActionOptions< - TInputParams extends Record z.ZodType>, - TOutputParams extends Record z.ZodType>, -> = - | OldTemplateActionOptions - | NewTemplateActionOptions; + TInputParams extends JsonObject = JsonObject, + TOutputParams extends JsonObject = JsonObject, + TInputSchema extends Schema | z.ZodType = Schema, + TOutputSchema extends Schema | z.ZodType = Schema, + TActionInput extends JsonObject = TInputSchema extends z.ZodType< + any, + any, + infer IReturn + > + ? IReturn + : TInputParams, + TActionOutput extends JsonObject = TOutputSchema extends z.ZodType< + any, + any, + infer IReturn + > + ? IReturn + : TOutputParams, +> = TemplateActionOptionsV1< + TInputParams, + TOutputParams, + TInputSchema, + TOutputSchema, + TActionInput, + TActionOutput +>; function isZod(schema?: Schema | z.ZodType): schema is z.ZodType { return !!(schema && 'safeParseAsync' in schema); @@ -118,10 +138,10 @@ function transformZodRecordToObject( /** * 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. - * @deprecated migrate to {@link newCreateTemplateAction} + * @deprecated migrate to {@link createTemplateActionV2} * @public */ -export function oldCreateTemplateAction< +export function createTemplateActionV1< TInputParams extends JsonObject = JsonObject, TOutputParams extends JsonObject = JsonObject, TInputSchema extends Schema | z.ZodType = {}, @@ -141,7 +161,7 @@ export function oldCreateTemplateAction< ? IReturn : TOutputParams, >( - action: OldTemplateActionOptions< + action: TemplateActionOptionsV1< TInputParams, TOutputParams, TInputSchema, @@ -149,7 +169,7 @@ export function oldCreateTemplateAction< TActionInput, TActionOutput >, -): OldTemplateAction { +): TemplateActionV1 { const inputSchema = action.schema && action.schema.input && isZod(action.schema.input) ? (zodToJsonSchema(action.schema.input) as Schema) @@ -175,12 +195,12 @@ export function oldCreateTemplateAction< * Will convert zod schemas to json schemas for use throughout the system. * @public */ -export function newCreateTemplateAction< +export function createTemplateActionV2< TInputParams extends Record z.ZodType>, TOutputParams extends Record z.ZodType>, >( - action: NewTemplateActionOptions, -): NewTemplateAction< + action: TemplateActionOptionsV2, +): TemplateActionV2< InferActionType, InferActionType > { @@ -197,9 +217,9 @@ export function newCreateTemplateAction< }; } -function isOldAction( - action: OldTemplateActionOptions | NewTemplateActionOptions, -): action is OldTemplateActionOptions { +function isV1Action( + action: TemplateActionOptionsV1 | TemplateActionOptionsV2, +): action is TemplateActionOptionsV1 { return ( isZod(action.schema?.input) || typeof action.schema?.input === 'string' || @@ -217,15 +237,15 @@ export function createTemplateAction< TInputParams extends JsonObject = JsonObject, TOutputParams extends JsonObject = JsonObject, TAction extends - | OldTemplateActionOptions - | NewTemplateActionOptions = OldTemplateActionOptions, - TReturn = TAction extends OldTemplateActionOptions - ? Prettify> - : Prettify, + | TemplateActionOptionsV1 + | TemplateActionOptionsV2 = TemplateActionOptionsV1, + TReturn = TAction extends TemplateActionOptionsV1 + ? Prettify> + : Prettify, >(action: TAction): TReturn { - if (isOldAction(action)) { - return oldCreateTemplateAction(action) as TReturn; + if (isV1Action(action)) { + return createTemplateActionV1(action) as TReturn; } - return newCreateTemplateAction(action) as TReturn; + return createTemplateActionV2(action) as TReturn; } diff --git a/plugins/scaffolder-node/src/actions/index.ts b/plugins/scaffolder-node/src/actions/index.ts index 9ac9d9a5a4..c531977bf9 100644 --- a/plugins/scaffolder-node/src/actions/index.ts +++ b/plugins/scaffolder-node/src/actions/index.ts @@ -16,8 +16,8 @@ export { createTemplateAction, - type NewTemplateActionOptions, - type OldTemplateActionOptions, + type TemplateActionOptionsV2, + type TemplateActionOptionsV1, type TemplateActionOptions, } from './createTemplateAction'; export { @@ -35,12 +35,12 @@ export { } from './gitHelpers'; export type { ActionContext, + ActionContextV1, + ActionContextV2, InferActionType, - NewActionContext, - NewTemplateAction, - OldActionContext, - OldTemplateAction, TemplateAction, + TemplateActionV1, + TemplateActionV2, TemplateExample, } from './types'; export { getRepoSourceDirectory, parseRepoUrl } from './util'; diff --git a/plugins/scaffolder-node/src/actions/types.ts b/plugins/scaffolder-node/src/actions/types.ts index e554baf2f7..676f96e543 100644 --- a/plugins/scaffolder-node/src/actions/types.ts +++ b/plugins/scaffolder-node/src/actions/types.ts @@ -47,11 +47,11 @@ export type InferActionType< }>; /** - * OldActionContext is passed into scaffolder actions. - * @deprecated migrate to {@link NewActionContext} + * ActionContextV1 is passed into scaffolder actions. + * @deprecated migrate to {@link ActionContextV2} * @public */ -export type OldActionContext< +export type ActionContextV1< TActionInput extends JsonObject = JsonObject, TActionOutput extends JsonObject = JsonObject, > = { @@ -124,10 +124,10 @@ export type OldActionContext< }; /** - * NewActionContext is passed into scaffolder actions. + * ActionContextV2 is passed into scaffolder actions. * @public */ -export type NewActionContext< +export type ActionContextV2< TActionInput extends JsonObject = JsonObject, TActionOutput extends JsonObject = JsonObject, > = { @@ -190,20 +190,19 @@ export type NewActionContext< }; /** + * @deprecated migrate to {@link ActionContextV2} * @public */ export type ActionContext< TActionInput extends JsonObject = JsonObject, TActionOutput extends JsonObject = JsonObject, -> = - | OldActionContext - | NewActionContext; +> = ActionContextV2; /** - * @deprecated migrate to {@link NewTemplateAction} + * @deprecated migrate to {@link TemplateActionV2} * @public */ -export type OldTemplateAction< +export type TemplateActionV1< TActionInput extends JsonObject = JsonObject, TActionOutput extends JsonObject = JsonObject, > = { @@ -215,15 +214,13 @@ export type OldTemplateAction< input?: Schema; output?: Schema; }; - handler: ( - ctx: OldActionContext, - ) => Promise; + handler: (ctx: ActionContextV1) => Promise; }; /** * @public */ -export type NewTemplateAction< +export type TemplateActionV2< TActionInput extends JsonObject = JsonObject, TActionOutput extends JsonObject = JsonObject, > = { @@ -235,17 +232,14 @@ export type NewTemplateAction< input: Schema; output: Schema; }; - handler: ( - ctx: NewActionContext, - ) => Promise; + handler: (ctx: ActionContextV2) => Promise; }; /** + * @deprecated migrate to {@link ActionContextV2} * @public */ export type TemplateAction< TActionInput extends JsonObject = JsonObject, TActionOutput extends JsonObject = JsonObject, -> = - | OldTemplateAction - | NewTemplateAction; +> = TemplateActionV1; From 3ff5c07a702660798f372bde0f4728bd4ef84236 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 4 Feb 2025 13:36:31 +0100 Subject: [PATCH 03/17] chore: wip Signed-off-by: blam Signed-off-by: blam --- packages/types/src/utility.ts | 38 ---- .../src/actions/createTemplateAction.ts | 212 +++--------------- plugins/scaffolder-node/src/actions/index.ts | 26 +-- plugins/scaffolder-node/src/actions/types.ts | 165 ++------------ 4 files changed, 54 insertions(+), 387 deletions(-) delete mode 100644 packages/types/src/utility.ts diff --git a/packages/types/src/utility.ts b/packages/types/src/utility.ts deleted file mode 100644 index ebcfedc360..0000000000 --- a/packages/types/src/utility.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * A utility type that enhances the readability of hover overlays (type hints) for object types. - * - * @example Prettifying a basic object merge - * - * ### Basic object type - * ```ts - * type Example = { item1: string } & { item2: number } - * // ?^ { item1: string } & { item2: number } - * ``` - * - * ### Usage - * ```ts - * type Example = Prettify<{ item1: string } & { item2: number }> - * // ?^ { item1: string, item2: number } - * ``` - * - * @public - */ -export type Prettify> = { - [K in keyof T]: T[K]; -} & {}; diff --git a/plugins/scaffolder-node/src/actions/createTemplateAction.ts b/plugins/scaffolder-node/src/actions/createTemplateAction.ts index d33afdaca5..4721df7de5 100644 --- a/plugins/scaffolder-node/src/actions/createTemplateAction.ts +++ b/plugins/scaffolder-node/src/actions/createTemplateAction.ts @@ -14,42 +14,24 @@ * limitations under the License. */ -import type { JsonObject, Prettify } from '@backstage/types'; -import type { Schema } from 'jsonschema'; +import { ActionContext, TemplateAction } from './types'; import { z } from 'zod'; +import { Schema } from 'jsonschema'; import zodToJsonSchema from 'zod-to-json-schema'; -import type { - InferActionType, - ActionContextV2, - TemplateActionV2, - ActionContextV1, - TemplateActionV1, - TemplateExample, -} from './types'; +import { JsonObject } from '@backstage/types'; -/** - * @deprecated migrate to {@link TemplateActionOptionsV2} - * @public - */ -export type TemplateActionOptionsV1< - TInputParams extends JsonObject = JsonObject, - TOutputParams extends JsonObject = JsonObject, - TInputSchema extends Schema | z.ZodType = Schema, - TOutputSchema extends Schema | z.ZodType = Schema, - TActionInput extends JsonObject = TInputSchema extends z.ZodType< - any, - any, - infer IReturn - > - ? IReturn - : TInputParams, - TActionOutput extends JsonObject = TOutputSchema extends z.ZodType< - any, - any, - infer IReturn - > - ? IReturn - : TOutputParams, +/** @public */ +export type TemplateExample = { + description: string; + example: string; +}; + +/** @public */ +export type TemplateActionOptions< + TActionInput extends JsonObject = {}, + TActionOutput extends JsonObject = {}, + TInputSchema extends Schema | z.ZodType = {}, + TOutputSchema extends Schema | z.ZodType = {}, > = { id: string; description?: string; @@ -59,89 +41,15 @@ export type TemplateActionOptionsV1< input?: TInputSchema; output?: TOutputSchema; }; - handler: (ctx: ActionContextV1) => Promise; + handler: (ctx: ActionContext) => Promise; }; -/** - * @public - */ -export type TemplateActionOptionsV2< - TInputParams extends Record< - PropertyKey, - (zod: typeof z) => z.ZodType - > = Record z.ZodType>, - TOutputParams extends Record< - PropertyKey, - (zod: typeof z) => z.ZodType - > = Record z.ZodType>, -> = { - id: string; - description?: string; - examples?: TemplateExample[]; - supportsDryRun?: boolean; - schema: { - input: TInputParams; - output: TOutputParams; - }; - handler: ( - ctx: ActionContextV2< - InferActionType, - InferActionType - >, - ) => Promise; -}; - -/** - * @deprecated migrate to {@link TemplateActionOptionsV2} - * @public - */ -export type TemplateActionOptions< - TInputParams extends JsonObject = JsonObject, - TOutputParams extends JsonObject = JsonObject, - TInputSchema extends Schema | z.ZodType = Schema, - TOutputSchema extends Schema | z.ZodType = Schema, - TActionInput extends JsonObject = TInputSchema extends z.ZodType< - any, - any, - infer IReturn - > - ? IReturn - : TInputParams, - TActionOutput extends JsonObject = TOutputSchema extends z.ZodType< - any, - any, - infer IReturn - > - ? IReturn - : TOutputParams, -> = TemplateActionOptionsV1< - TInputParams, - TOutputParams, - TInputSchema, - TOutputSchema, - TActionInput, - TActionOutput ->; - -function isZod(schema?: Schema | z.ZodType): schema is z.ZodType { - return !!(schema && 'safeParseAsync' in schema); -} - -function transformZodRecordToObject( - record: Record z.ZodType>, -): z.ZodObject> { - return z.object( - Object.fromEntries(Object.entries(record).map(([k, v]) => [k, v(z)])), - ); -} - /** * 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. - * @deprecated migrate to {@link createTemplateActionV2} * @public */ -export function createTemplateActionV1< +export const createTemplateAction = < TInputParams extends JsonObject = JsonObject, TOutputParams extends JsonObject = JsonObject, TInputSchema extends Schema | z.ZodType = {}, @@ -161,91 +69,29 @@ export function createTemplateActionV1< ? IReturn : TOutputParams, >( - action: TemplateActionOptionsV1< - TInputParams, - TOutputParams, - TInputSchema, - TOutputSchema, + action: TemplateActionOptions< TActionInput, - TActionOutput + TActionOutput, + TInputSchema, + TOutputSchema >, -): TemplateActionV1 { +): TemplateAction => { const inputSchema = - action.schema && action.schema.input && isZod(action.schema.input) - ? (zodToJsonSchema(action.schema.input) as Schema) + action.schema?.input && 'safeParseAsync' in action.schema.input + ? zodToJsonSchema(action.schema.input) : action.schema?.input; const outputSchema = - action.schema && action.schema.output && isZod(action.schema.output) - ? (zodToJsonSchema(action.schema.output) as Schema) + action.schema?.output && 'safeParseAsync' in action.schema.output + ? zodToJsonSchema(action.schema.output) : action.schema?.output; return { ...action, schema: { ...action.schema, - input: inputSchema, - output: outputSchema, + input: inputSchema as TInputSchema, + output: outputSchema as TOutputSchema, }, }; -} - -/** - * 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 createTemplateActionV2< - TInputParams extends Record z.ZodType>, - TOutputParams extends Record z.ZodType>, ->( - action: TemplateActionOptionsV2, -): TemplateActionV2< - InferActionType, - InferActionType -> { - const input = transformZodRecordToObject(action.schema.input); - const output = transformZodRecordToObject(action.schema.output); - - return { - ...action, - schema: { - ...action.schema, - input: zodToJsonSchema(input) as Schema, - output: zodToJsonSchema(output) as Schema, - }, - }; -} - -function isV1Action( - action: TemplateActionOptionsV1 | TemplateActionOptionsV2, -): action is TemplateActionOptionsV1 { - return ( - isZod(action.schema?.input) || - typeof action.schema?.input === 'string' || - isZod(action.schema?.output) || - typeof action.schema?.output === 'string' - ); -} - -/** - * 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< - TInputParams extends JsonObject = JsonObject, - TOutputParams extends JsonObject = JsonObject, - TAction extends - | TemplateActionOptionsV1 - | TemplateActionOptionsV2 = TemplateActionOptionsV1, - TReturn = TAction extends TemplateActionOptionsV1 - ? Prettify> - : Prettify, ->(action: TAction): TReturn { - if (isV1Action(action)) { - return createTemplateActionV1(action) as TReturn; - } - - return createTemplateActionV2(action) as TReturn; -} +}; diff --git a/plugins/scaffolder-node/src/actions/index.ts b/plugins/scaffolder-node/src/actions/index.ts index c531977bf9..c1c8551c53 100644 --- a/plugins/scaffolder-node/src/actions/index.ts +++ b/plugins/scaffolder-node/src/actions/index.ts @@ -16,31 +16,21 @@ export { createTemplateAction, - type TemplateActionOptionsV2, - type TemplateActionOptionsV1, type TemplateActionOptions, + type TemplateExample, } from './createTemplateAction'; export { executeShellCommand, type ExecuteShellCommandOptions, } from './executeShellCommand'; export { fetchContents, fetchFile } from './fetch'; +export { type ActionContext, type TemplateAction } from './types'; export { - addFiles, - cloneRepo, - commitAndPushBranch, - commitAndPushRepo, - createBranch, initRepoAndPush, + commitAndPushRepo, + commitAndPushBranch, + addFiles, + createBranch, + cloneRepo, } from './gitHelpers'; -export type { - ActionContext, - ActionContextV1, - ActionContextV2, - InferActionType, - TemplateAction, - TemplateActionV1, - TemplateActionV2, - TemplateExample, -} from './types'; -export { getRepoSourceDirectory, parseRepoUrl } from './util'; +export { parseRepoUrl, getRepoSourceDirectory } from './util'; diff --git a/plugins/scaffolder-node/src/actions/types.ts b/plugins/scaffolder-node/src/actions/types.ts index 676f96e543..171d3d82db 100644 --- a/plugins/scaffolder-node/src/actions/types.ts +++ b/plugins/scaffolder-node/src/actions/types.ts @@ -14,45 +14,21 @@ * limitations under the License. */ -import type { - BackstageCredentials, - LoggerService, -} from '@backstage/backend-plugin-api'; -import type { UserEntity } from '@backstage/catalog-model'; -import type { TemplateInfo } from '@backstage/plugin-scaffolder-common'; -import type { JsonObject, JsonValue, Prettify } from '@backstage/types'; -import type { Schema } from 'jsonschema'; -import type { Writable } from 'stream'; -import type { Logger } from 'winston'; -import { z } from 'zod'; -import type { TaskSecrets } from '../tasks'; +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'; +import { UserEntity } from '@backstage/catalog-model'; +import { Schema } from 'jsonschema'; +import { BackstageCredentials } from '@backstage/backend-plugin-api'; /** + * ActionContext is passed into scaffolder actions. * @public */ -export type TemplateExample = { - description: string; - example: string; -}; - -/** @public */ -export type InferActionType< - T extends Record z.ZodType>, -> = Prettify<{ - [K in keyof T]: T[K] extends ( - zod: typeof z, - ) => z.ZodType - ? Extract - : never; -}>; - -/** - * ActionContextV1 is passed into scaffolder actions. - * @deprecated migrate to {@link ActionContextV2} - * @public - */ -export type ActionContextV1< - TActionInput extends JsonObject = JsonObject, +export type ActionContext< + TActionInput extends JsonObject, TActionOutput extends JsonObject = JsonObject, > = { // TODO(blam): move this to LoggerService @@ -67,10 +43,8 @@ export type ActionContextV1< fn: () => Promise | T; }): Promise; output( - ...params: { - /* This maps the key to the value for type checking */ - [K in keyof TActionOutput]: [name: K, value: TActionOutput[K]]; - }[keyof TActionOutput] + name: keyof TActionOutput, + value: TActionOutput[keyof TActionOutput], ): void; /** @@ -123,123 +97,18 @@ export type ActionContextV1< each?: JsonObject; }; -/** - * ActionContextV2 is passed into scaffolder actions. - * @public - */ -export type ActionContextV2< - TActionInput extends JsonObject = JsonObject, - TActionOutput extends JsonObject = JsonObject, -> = { - logger: LoggerService; - secrets?: TaskSecrets; - workspacePath: string; - input: TActionInput; - checkpoint( - key: string, - fn: () => Promise, - ): Promise; - output( - ...params: { - /* This maps the key to the value for type checking */ - [K in keyof TActionOutput]: [name: K, value: TActionOutput[K]]; - }[keyof TActionOutput] - ): void; - - /** - * Creates a temporary directory for use by the action, which is then cleaned up automatically. - */ - createTemporaryDirectory(): Promise; - - /** - * Get the credentials for the current request - */ - getInitiatorCredentials(): Promise; - - 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; -}; - -/** - * @deprecated migrate to {@link ActionContextV2} - * @public - */ -export type ActionContext< - TActionInput extends JsonObject = JsonObject, - TActionOutput extends JsonObject = JsonObject, -> = ActionContextV2; - -/** - * @deprecated migrate to {@link TemplateActionV2} - * @public - */ -export type TemplateActionV1< +/** @public */ +export type TemplateAction< TActionInput extends JsonObject = JsonObject, TActionOutput extends JsonObject = JsonObject, > = { id: string; description?: string; - examples?: TemplateExample[]; + examples?: { description: string; example: string }[]; supportsDryRun?: boolean; schema?: { input?: Schema; output?: Schema; }; - handler: (ctx: ActionContextV1) => Promise; + handler: (ctx: ActionContext) => Promise; }; - -/** - * @public - */ -export type TemplateActionV2< - TActionInput extends JsonObject = JsonObject, - TActionOutput extends JsonObject = JsonObject, -> = { - id: string; - description?: string; - examples?: TemplateExample[]; - supportsDryRun?: boolean; - schema: { - input: Schema; - output: Schema; - }; - handler: (ctx: ActionContextV2) => Promise; -}; - -/** - * @deprecated migrate to {@link ActionContextV2} - * @public - */ -export type TemplateAction< - TActionInput extends JsonObject = JsonObject, - TActionOutput extends JsonObject = JsonObject, -> = TemplateActionV1; From cf8b627f073d17b1df154507e0ced15f32e0d78e Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 5 Feb 2025 09:15:59 +0100 Subject: [PATCH 04/17] chore: refactoring things to work a little cleaner Signed-off-by: blam Signed-off-by: blam --- .../confluence/confluenceToMarkdown.ts | 1 - .../actions/builtin/catalog/fetch.ts | 81 +++---- .../scaffolder-backend/src/service/router.ts | 2 +- .../src/actions/mockActionContext.ts | 2 +- .../src/actions/createTemplateAction.test.ts | 125 +++++++++++ .../src/actions/createTemplateAction.ts | 111 ++++++++-- plugins/scaffolder-node/src/actions/types.ts | 198 ++++++++++++------ plugins/scaffolder-node/src/actions/util.ts | 52 +++++ 8 files changed, 451 insertions(+), 121 deletions(-) create mode 100644 plugins/scaffolder-node/src/actions/createTemplateAction.test.ts diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts index aa9f94988a..3d08259e98 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts @@ -66,7 +66,6 @@ export const createConfluenceToMarkdownAction = (options: { 'Paste your Confluence url. Ensure it follows this format: https://{confluence+base+url}/display/{spacekey}/{page+title} or https://{confluence+base+url}/spaces/{spacekey}/pages/1234567/{page+title} for Confluence Cloud', items: { type: 'string', - default: 'Confluence URL', }, }, repoUrl: { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.ts index 824ca4deb3..e21930ed78 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.ts @@ -16,7 +16,6 @@ import { CatalogApi } from '@backstage/catalog-client'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; -import { z } from 'zod'; import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model'; import { examples } from './fetch.examples'; import { AuthService } from '@backstage/backend-plugin-api'; @@ -41,48 +40,54 @@ export function createFetchCatalogEntityAction(options: { examples, supportsDryRun: true, schema: { - input: z.object({ - entityRef: z - .string({ - description: 'Entity reference of the entity to get', - }) - .optional(), - entityRefs: z - .array(z.string(), { - description: 'Entity references of the entities to get', - }) - .optional(), - optional: z - .boolean({ - description: - 'Allow the entity or entities to optionally exist. Default: false', - }) - .optional(), - defaultKind: z.string({ description: 'The default kind' }).optional(), - defaultNamespace: z - .string({ description: 'The default namespace' }) - .optional(), - }), - output: z.object({ - entity: z - .any({ - description: - 'Object containing same values used in the Entity schema. Only when used with `entityRef` parameter.', - }) - .optional(), - entities: z - .array( - z.any({ + input: { + entityRef: z => + z + .string({ + description: 'Entity reference of the entity to get', + }) + .optional(), + entityRefs: z => + z + .array(z.string(), { + description: 'Entity references of the entities to get', + }) + .optional(), + optional: z => + z + .boolean({ description: - 'Array containing objects with same values used in the Entity schema. Only when used with `entityRefs` parameter.', - }), - ) - .optional(), - }), + 'Allow the entity or entities to optionally exist. Default: false', + }) + .optional(), + defaultKind: z => + z.string({ description: 'The default kind' }).optional(), + defaultNamespace: z => + z.string({ description: 'The default namespace' }).optional(), + }, + output: { + entity: z => + z + .any({ + description: + 'Object containing same values used in the Entity schema. Only when used with `entityRef` parameter.', + }) + .optional(), + entities: z => + z + .array( + z.any({ + description: + 'Array containing objects with same values used in the Entity schema. Only when used with `entityRefs` parameter.', + }), + ) + .optional(), + }, }, async handler(ctx) { const { entityRef, entityRefs, optional, defaultKind, defaultNamespace } = ctx.input; + if (!entityRef && !entityRefs) { if (optional) { return; diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 35782ea441..1186e7d4f6 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -165,7 +165,7 @@ export interface RouterOptions { database: DatabaseService; catalogClient: CatalogApi; scheduler?: SchedulerService; - actions?: TemplateAction[]; + actions?: TemplateAction[]; /** * @deprecated taskWorkers is deprecated in favor of concurrentTasksLimit option with a single TaskWorker * @defaultValue 1 diff --git a/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts index eb03ac0a0f..f042416e36 100644 --- a/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts +++ b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts @@ -35,7 +35,7 @@ export const createMockActionContext = < TActionOutput extends JsonObject = JsonObject, >( options?: Partial>, -): ActionContext => { +): ActionContext => { const credentials = mockCredentials.user(); const defaultContext = { logger: loggerToWinstonLogger(mockServices.logger.mock()), diff --git a/plugins/scaffolder-node/src/actions/createTemplateAction.test.ts b/plugins/scaffolder-node/src/actions/createTemplateAction.test.ts new file mode 100644 index 0000000000..7dcb3f491c --- /dev/null +++ b/plugins/scaffolder-node/src/actions/createTemplateAction.test.ts @@ -0,0 +1,125 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createTemplateAction } from './createTemplateAction'; +import { z } from 'zod'; + +describe('createTemplateAction', () => { + it('should allow creating with jsonschema and use the old deprecated types', () => { + const action = createTemplateAction<{ repoUrl: string }, { test: string }>({ + id: 'test', + schema: { + input: { + type: 'object', + required: ['repoUrl'], + properties: { + repoUrl: { type: 'string' }, + }, + }, + output: { + type: 'object', + required: ['test'], + properties: { + test: { type: 'string' }, + }, + }, + }, + handler: async ctx => { + // @ts-expect-error - repoUrl is string + const a: number = ctx.input.repoUrl; + + const b: string = ctx.input.repoUrl; + expect(b).toBeDefined(); + + const stream = ctx.logStream; + expect(stream).toBeDefined(); + + ctx.output('test', 'value'); + + // @ts-expect-error - not valid output type + ctx.output('test', 4); + + // @ts-expect-error - not valid output name + ctx.output('test2', 'value'); + }, + }); + }); + + 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'); + }, + }); + }); + + it('should allow creating with new first class zod support', () => { + const action = createTemplateAction({ + id: 'test', + schema: { + input: { + repoUrl: d => d.string(), + }, + output: { + test: d => d.string(), + }, + }, + handler: async ctx => { + // @ts-expect-error - repoUrl is string + const a: number = ctx.input.repoUrl; + + const b: string = ctx.input.repoUrl; + expect(b).toBeDefined(); + + // @ts-expect-error - logStream is not available + const stream = ctx.logStream; + + expect(stream).toBeDefined(); + + ctx.output('test', 'value'); + + // @ts-expect-error - not valid output type + ctx.output('test', 4); + + // @ts-expect-error - not valid output name + ctx.output('test2', 'value'); + }, + }); + }); +}); diff --git a/plugins/scaffolder-node/src/actions/createTemplateAction.ts b/plugins/scaffolder-node/src/actions/createTemplateAction.ts index 4721df7de5..02cac0a4b5 100644 --- a/plugins/scaffolder-node/src/actions/createTemplateAction.ts +++ b/plugins/scaffolder-node/src/actions/createTemplateAction.ts @@ -17,8 +17,8 @@ import { ActionContext, TemplateAction } from './types'; import { z } from 'zod'; import { Schema } from 'jsonschema'; -import zodToJsonSchema from 'zod-to-json-schema'; -import { JsonObject } from '@backstage/types'; +import { Expand, JsonObject } from '@backstage/types'; +import { parseSchemas } from './util'; /** @public */ export type TemplateExample = { @@ -30,8 +30,14 @@ export type TemplateExample = { export type TemplateActionOptions< TActionInput extends JsonObject = {}, TActionOutput extends JsonObject = {}, - TInputSchema extends Schema | z.ZodType = {}, - TOutputSchema extends Schema | z.ZodType = {}, + TInputSchema extends + | Schema + | z.ZodType + | { [key in string]: (zImpl: typeof z) => z.ZodType } = Schema, + TOutputSchema extends + | Schema + | z.ZodType + | { [key in string]: (zImpl: typeof z) => z.ZodType } = Schema, > = { id: string; description?: string; @@ -41,7 +47,9 @@ export type TemplateActionOptions< input?: TInputSchema; output?: TOutputSchema; }; - handler: (ctx: ActionContext) => Promise; + handler: ( + ctx: ActionContext, + ) => Promise; }; /** @@ -49,17 +57,86 @@ export type TemplateActionOptions< * Will convert zod schemas to json schemas for use throughout the system. * @public */ -export const createTemplateAction = < +export function createTemplateAction< + TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }, + TOutputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }, +>( + action: TemplateActionOptions< + { + [key in keyof TInputSchema]: z.infer>; + }, + { + [key in keyof TOutputSchema]: z.infer>; + }, + TInputSchema, + TOutputSchema + >, +): TemplateAction< + Expand<{ + [key in keyof TInputSchema]: z.output>; + }>, + Expand<{ + [key in keyof TOutputSchema]: z.output>; + }>, + TInputSchema +>; +/** + * @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 Schema | z.ZodType = {}, - TOutputSchema extends Schema | z.ZodType = {}, + TInputSchema extends Schema = Schema, + TOutputSchema extends Schema = Schema, + TActionInput extends JsonObject = TInputParams, + TActionOutput extends JsonObject = TOutputParams, +>( + action: TemplateActionOptions< + TActionInput, + TActionOutput, + TInputSchema, + TOutputSchema + >, +): TemplateAction; +/** + * @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, + TActionOutput extends JsonObject = z.infer, +>( + action: TemplateActionOptions< + TActionInput, + TActionOutput, + TInputSchema, + TOutputSchema + >, +): TemplateAction; +export function createTemplateAction< + TInputParams extends JsonObject = JsonObject, + TOutputParams extends JsonObject = JsonObject, + TInputSchema extends + | Schema + | z.ZodType + | { [key in string]: (zImpl: typeof z) => z.ZodType } = {}, + TOutputSchema extends + | Schema + | z.ZodType + | { [key in string]: (zImpl: typeof z) => z.ZodType } = {}, TActionInput extends JsonObject = TInputSchema extends z.ZodType< any, any, infer IReturn > ? IReturn + : TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } + ? Expand<{ + [key in keyof TInputSchema]: z.infer>; + }> : TInputParams, TActionOutput extends JsonObject = TOutputSchema extends z.ZodType< any, @@ -67,6 +144,10 @@ export const createTemplateAction = < infer IReturn > ? IReturn + : TOutputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } + ? Expand<{ + [key in keyof TOutputSchema]: z.infer>; + }> : TOutputParams, >( action: TemplateActionOptions< @@ -75,16 +156,8 @@ export const createTemplateAction = < TInputSchema, TOutputSchema >, -): 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; +): TemplateAction { + const { inputSchema, outputSchema } = parseSchemas(action); return { ...action, @@ -94,4 +167,4 @@ export const createTemplateAction = < output: outputSchema as TOutputSchema, }, }; -}; +} diff --git a/plugins/scaffolder-node/src/actions/types.ts b/plugins/scaffolder-node/src/actions/types.ts index 171d3d82db..d89e61e9e7 100644 --- a/plugins/scaffolder-node/src/actions/types.ts +++ b/plugins/scaffolder-node/src/actions/types.ts @@ -21,8 +21,11 @@ import { TaskSecrets } from '../tasks'; import { TemplateInfo } from '@backstage/plugin-scaffolder-common'; import { UserEntity } from '@backstage/catalog-model'; import { Schema } from 'jsonschema'; -import { BackstageCredentials } from '@backstage/backend-plugin-api'; - +import { + BackstageCredentials, + LoggerService, +} from '@backstage/backend-plugin-api'; +import { z } from 'zod'; /** * ActionContext is passed into scaffolder actions. * @public @@ -30,77 +33,148 @@ import { BackstageCredentials } from '@backstage/backend-plugin-api'; export type ActionContext< TActionInput extends JsonObject, TActionOutput extends JsonObject = JsonObject, -> = { - // TODO(blam): move this to LoggerService - logger: Logger; - /** @deprecated - use `ctx.logger` instead */ - logStream: Writable; - secrets?: TaskSecrets; - workspacePath: string; - input: TActionInput; - checkpoint(opts: { - key: string; - fn: () => Promise | T; - }): Promise; - output( - name: keyof TActionOutput, - value: TActionOutput[keyof TActionOutput], - ): void; + TInputSchema extends + | { [key in string]: (zImpl: typeof z) => z.ZodType } + | Schema = Schema, +> = TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } + ? { + logger: LoggerService; + secrets?: TaskSecrets; + workspacePath: string; + input: TActionInput; + checkpoint( + key: string, + fn: () => Promise, + ): Promise; + output( + ...params: { + [K in keyof TActionOutput]: [name: K, value: TActionOutput[K]]; + }[keyof TActionOutput] + ): void; + /** + * Creates a temporary directory for use by the action, which is then cleaned up automatically. + */ + createTemporaryDirectory(): Promise; - /** - * Creates a temporary directory for use by the action, which is then cleaned up automatically. - */ - createTemporaryDirectory(): Promise; + /** + * Get the credentials for the current request + */ + getInitiatorCredentials(): Promise; - /** - * Get the credentials for the current request - */ - getInitiatorCredentials(): Promise; + /** + * 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(opts: { + key: string; + fn: () => Promise | T; + }): Promise; + output( + name: keyof TActionOutput, + value: TActionOutput[keyof TActionOutput], + ): void; - /** - * Optional value of each invocation - */ - each?: JsonObject; -}; + /** + * Creates a temporary directory for use by the action, which is then cleaned up automatically. + */ + createTemporaryDirectory(): Promise; + + /** + * Get the credentials for the current request + */ + getInitiatorCredentials(): Promise; + + /** + * Task information + */ + task: { + id: string; + }; + + templateInfo?: TemplateInfo; + + /** + * Whether this action invocation is a dry-run or not. + * This will only ever be true if the actions as marked as supporting dry-runs. + */ + isDryRun?: boolean; + + /** + * The user which triggered the action. + */ + user?: { + /** + * The decorated entity from the Catalog + */ + entity?: UserEntity; + /** + * An entity ref for the author of the task + */ + ref?: string; + }; + + /** + * Implement the signal to make your custom step abortable https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal + */ + signal?: AbortSignal; + + /** + * Optional value of each invocation + */ + each?: JsonObject; + }; /** @public */ export type TemplateAction< TActionInput extends JsonObject = JsonObject, TActionOutput extends JsonObject = JsonObject, + TInputSchema extends + | { [key in string]: (zImpl: typeof z) => z.ZodType } + | Schema = Schema, > = { id: string; description?: string; @@ -110,5 +184,7 @@ export type TemplateAction< input?: Schema; output?: Schema; }; - handler: (ctx: ActionContext) => Promise; + handler: ( + ctx: ActionContext, + ) => Promise; }; diff --git a/plugins/scaffolder-node/src/actions/util.ts b/plugins/scaffolder-node/src/actions/util.ts index 5e4ce60a1a..038d199d3b 100644 --- a/plugins/scaffolder-node/src/actions/util.ts +++ b/plugins/scaffolder-node/src/actions/util.ts @@ -18,6 +18,9 @@ import { InputError } from '@backstage/errors'; import { isChildPath } from '@backstage/backend-plugin-api'; import { join as joinPath, normalize as normalizePath } from 'path'; import { ScmIntegrationRegistry } from '@backstage/integration'; +import { TemplateActionOptions } from './createTemplateAction'; +import zodToJsonSchema from 'zod-to-json-schema'; +import { z } from 'zod'; /** * @public @@ -124,3 +127,52 @@ function checkRequiredParams(repoUrl: URL, ...params: string[]) { } } } + +const isZodSchema = (schema: unknown): schema is z.ZodType => { + return typeof schema === 'object' && !!schema && 'safeParseAsync' in schema; +}; + +const isNativeZodSchema = ( + schema: unknown, +): schema is { [key in string]: (zImpl: typeof z) => z.ZodType } => { + return ( + typeof schema === 'object' && + !!schema && + Object.values(schema).every(v => typeof v === 'function') + ); +}; + +export const parseSchemas = (action: TemplateActionOptions) => { + if (!action.schema) { + return { inputSchema: undefined, outputSchema: undefined }; + } + + if (isZodSchema(action.schema.input) && isZodSchema(action.schema.output)) { + return { + inputSchema: zodToJsonSchema(action.schema.input), + outputSchema: zodToJsonSchema(action.schema.output), + }; + } + + if ( + isNativeZodSchema(action.schema.input) && + isNativeZodSchema(action.schema.output) + ) { + const input = z.object( + Object.fromEntries( + Object.entries(action.schema.input).map(([k, v]) => [k, v(z)]), + ), + ); + const output = z.object( + Object.fromEntries( + Object.entries(action.schema.output).map(([k, v]) => [k, v(z)]), + ), + ); + return { + inputSchema: zodToJsonSchema(input), + outputSchema: zodToJsonSchema(output), + }; + } + + throw new Error('Invalid schema provided'); +}; From e2d82fc3d8e30f1bc16962b79cbaa108424d9ad0 Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 7 Feb 2025 13:23:17 +0100 Subject: [PATCH 05/17] chore: flatten the optional properties from zod Signed-off-by: blam --- .../src/actions/mockActionContext.ts | 2 +- .../src/actions/createTemplateAction.ts | 15 +++++++++++++-- plugins/scaffolder-node/src/actions/types.ts | 13 ++++++------- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts index f042416e36..eb03ac0a0f 100644 --- a/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts +++ b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts @@ -35,7 +35,7 @@ export const createMockActionContext = < TActionOutput extends JsonObject = JsonObject, >( options?: Partial>, -): ActionContext => { +): ActionContext => { const credentials = mockCredentials.user(); const defaultContext = { logger: loggerToWinstonLogger(mockServices.logger.mock()), diff --git a/plugins/scaffolder-node/src/actions/createTemplateAction.ts b/plugins/scaffolder-node/src/actions/createTemplateAction.ts index 02cac0a4b5..3622c05662 100644 --- a/plugins/scaffolder-node/src/actions/createTemplateAction.ts +++ b/plugins/scaffolder-node/src/actions/createTemplateAction.ts @@ -52,6 +52,17 @@ export type TemplateActionOptions< ) => Promise; }; +/** + * @ignore + */ +type FlattenOptionalProperties = Expand< + { + [K in keyof T as undefined extends T[K] ? never : K]: T[K]; + } & { + [K in keyof T as undefined extends T[K] ? K : never]?: T[K]; + } +>; + /** * 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. @@ -72,10 +83,10 @@ export function createTemplateAction< TOutputSchema >, ): TemplateAction< - Expand<{ + FlattenOptionalProperties<{ [key in keyof TInputSchema]: z.output>; }>, - Expand<{ + FlattenOptionalProperties<{ [key in keyof TOutputSchema]: z.output>; }>, TInputSchema diff --git a/plugins/scaffolder-node/src/actions/types.ts b/plugins/scaffolder-node/src/actions/types.ts index d89e61e9e7..9f90df6e08 100644 --- a/plugins/scaffolder-node/src/actions/types.ts +++ b/plugins/scaffolder-node/src/actions/types.ts @@ -42,14 +42,13 @@ export type ActionContext< secrets?: TaskSecrets; workspacePath: string; input: TActionInput; - checkpoint( - key: string, - fn: () => Promise, - ): Promise; + checkpoint(opts: { + key: string; + fn: () => Promise | T; + }): Promise; output( - ...params: { - [K in keyof TActionOutput]: [name: K, value: TActionOutput[K]]; - }[keyof TActionOutput] + name: keyof TActionOutput, + value: TActionOutput[keyof TActionOutput], ): void; /** * Creates a temporary directory for use by the action, which is then cleaned up automatically. From 4abd0747cb0885e0480e9f374ce968237e2090f1 Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 7 Feb 2025 13:45:48 +0100 Subject: [PATCH 06/17] chore: build api-reports Signed-off-by: blam --- packages/types/report.api.md | 5 ----- .../src/actions/createTemplateAction.test.ts | 6 ++++++ .../src/actions/createTemplateAction.ts | 4 +++- plugins/scaffolder-node/src/actions/types.ts | 21 ++++++++++++++++--- plugins/scaffolder-node/src/alpha/index.ts | 2 +- 5 files changed, 28 insertions(+), 10 deletions(-) diff --git a/packages/types/report.api.md b/packages/types/report.api.md index 130a6ae504..b6bfae0862 100644 --- a/packages/types/report.api.md +++ b/packages/types/report.api.md @@ -79,11 +79,6 @@ export type Observer = { complete?(): void; }; -// @public -export type Prettify> = { - [K in keyof T]: T[K]; -} & {}; - // @public export type Subscription = { unsubscribe(): void; diff --git a/plugins/scaffolder-node/src/actions/createTemplateAction.test.ts b/plugins/scaffolder-node/src/actions/createTemplateAction.test.ts index 7dcb3f491c..43d5d2f660 100644 --- a/plugins/scaffolder-node/src/actions/createTemplateAction.test.ts +++ b/plugins/scaffolder-node/src/actions/createTemplateAction.test.ts @@ -55,6 +55,8 @@ describe('createTemplateAction', () => { ctx.output('test2', 'value'); }, }); + + expect(action).toBeDefined(); }); it('should allow creating with zod and use the old deprecated types', () => { @@ -87,6 +89,8 @@ describe('createTemplateAction', () => { ctx.output('test2', 'value'); }, }); + + expect(action).toBeDefined(); }); it('should allow creating with new first class zod support', () => { @@ -121,5 +125,7 @@ describe('createTemplateAction', () => { ctx.output('test2', 'value'); }, }); + + expect(action).toBeDefined(); }); }); diff --git a/plugins/scaffolder-node/src/actions/createTemplateAction.ts b/plugins/scaffolder-node/src/actions/createTemplateAction.ts index 3622c05662..b2a525a5d5 100644 --- a/plugins/scaffolder-node/src/actions/createTemplateAction.ts +++ b/plugins/scaffolder-node/src/actions/createTemplateAction.ts @@ -92,6 +92,7 @@ export function createTemplateAction< TInputSchema >; /** + * @public * @deprecated migrate to using the new built in zod schema definitions for schemas */ export function createTemplateAction< @@ -110,6 +111,7 @@ export function createTemplateAction< >, ): TemplateAction; /** + * @public * @deprecated migrate to using the new built in zod schema definitions for schemas */ export function createTemplateAction< @@ -167,7 +169,7 @@ export function createTemplateAction< TInputSchema, TOutputSchema >, -): TemplateAction { +): TemplateAction { const { inputSchema, outputSchema } = parseSchemas(action); return { diff --git a/plugins/scaffolder-node/src/actions/types.ts b/plugins/scaffolder-node/src/actions/types.ts index 9f90df6e08..c5fa4052c4 100644 --- a/plugins/scaffolder-node/src/actions/types.ts +++ b/plugins/scaffolder-node/src/actions/types.ts @@ -35,7 +35,12 @@ export type ActionContext< TActionOutput extends JsonObject = JsonObject, TInputSchema extends | { [key in string]: (zImpl: typeof z) => z.ZodType } - | Schema = Schema, + | Schema + | unknown = unknown, + _TOutputSchema extends + | { [key in string]: (zImpl: typeof z) => z.ZodType } + | Schema + | unknown = unknown, > = TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } ? { logger: LoggerService; @@ -173,7 +178,12 @@ export type TemplateAction< TActionOutput extends JsonObject = JsonObject, TInputSchema extends | { [key in string]: (zImpl: typeof z) => z.ZodType } - | Schema = Schema, + | Schema + | unknown = unknown, + TOutputSchema extends + | { [key in string]: (zImpl: typeof z) => z.ZodType } + | Schema + | unknown = unknown, > = { id: string; description?: string; @@ -184,6 +194,11 @@ export type TemplateAction< output?: Schema; }; handler: ( - ctx: ActionContext, + ctx: ActionContext< + TActionInput, + TActionOutput, + TInputSchema, + TOutputSchema + >, ) => Promise; }; diff --git a/plugins/scaffolder-node/src/alpha/index.ts b/plugins/scaffolder-node/src/alpha/index.ts index 1b573d0650..d97aa54557 100644 --- a/plugins/scaffolder-node/src/alpha/index.ts +++ b/plugins/scaffolder-node/src/alpha/index.ts @@ -34,7 +34,7 @@ export * from './globals'; * @alpha */ export interface ScaffolderActionsExtensionPoint { - addActions(...actions: TemplateAction[]): void; + addActions(...actions: TemplateAction[]): void; } /** From cb6cee1e2f4c8eabc6e51f3b13f1121ed981f973 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 11 Feb 2025 13:34:38 +0100 Subject: [PATCH 07/17] chore: refactor the types a little bit Signed-off-by: blam --- .../src/actions/createTemplateAction.ts | 94 ++++++++++--------- plugins/scaffolder-node/src/actions/types.ts | 28 +----- 2 files changed, 56 insertions(+), 66 deletions(-) diff --git a/plugins/scaffolder-node/src/actions/createTemplateAction.ts b/plugins/scaffolder-node/src/actions/createTemplateAction.ts index b2a525a5d5..29c2d79db9 100644 --- a/plugins/scaffolder-node/src/actions/createTemplateAction.ts +++ b/plugins/scaffolder-node/src/actions/createTemplateAction.ts @@ -38,6 +38,7 @@ export type TemplateActionOptions< | Schema | z.ZodType | { [key in string]: (zImpl: typeof z) => z.ZodType } = Schema, + TSchemaType extends 'v1' | 'v2' = 'v1' | 'v2', > = { id: string; description?: string; @@ -48,7 +49,7 @@ export type TemplateActionOptions< output?: TOutputSchema; }; handler: ( - ctx: ActionContext, + ctx: ActionContext, ) => Promise; }; @@ -63,6 +64,46 @@ type FlattenOptionalProperties = Expand< } >; +/** + * @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 Schema = Schema, + TOutputSchema extends Schema = Schema, + TActionInput extends JsonObject = TInputParams, + TActionOutput extends JsonObject = TOutputParams, +>( + action: TemplateActionOptions< + TActionInput, + TActionOutput, + TInputSchema, + TOutputSchema, + 'v1' + >, +): TemplateAction; +/** + * @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, + TActionOutput extends JsonObject = z.infer, +>( + action: TemplateActionOptions< + TActionInput, + TActionOutput, + TInputSchema, + TOutputSchema, + 'v1' + >, +): TemplateAction; /** * 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. @@ -80,7 +121,8 @@ export function createTemplateAction< [key in keyof TOutputSchema]: z.infer>; }, TInputSchema, - TOutputSchema + TOutputSchema, + 'v2' >, ): TemplateAction< FlattenOptionalProperties<{ @@ -89,46 +131,8 @@ export function createTemplateAction< FlattenOptionalProperties<{ [key in keyof TOutputSchema]: z.output>; }>, - TInputSchema + 'v2' >; -/** - * @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 Schema = Schema, - TOutputSchema extends Schema = Schema, - TActionInput extends JsonObject = TInputParams, - TActionOutput extends JsonObject = TOutputParams, ->( - action: TemplateActionOptions< - TActionInput, - TActionOutput, - TInputSchema, - TOutputSchema - >, -): TemplateAction; -/** - * @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, - TActionOutput extends JsonObject = z.infer, ->( - action: TemplateActionOptions< - TActionInput, - TActionOutput, - TInputSchema, - TOutputSchema - >, -): TemplateAction; export function createTemplateAction< TInputParams extends JsonObject = JsonObject, TOutputParams extends JsonObject = JsonObject, @@ -169,7 +173,13 @@ export function createTemplateAction< TInputSchema, TOutputSchema >, -): TemplateAction { +): TemplateAction< + TActionInput, + TActionOutput, + TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } + ? 'v2' + : 'v1' +> { const { inputSchema, outputSchema } = parseSchemas(action); return { diff --git a/plugins/scaffolder-node/src/actions/types.ts b/plugins/scaffolder-node/src/actions/types.ts index c5fa4052c4..92a2c7464a 100644 --- a/plugins/scaffolder-node/src/actions/types.ts +++ b/plugins/scaffolder-node/src/actions/types.ts @@ -25,7 +25,6 @@ import { BackstageCredentials, LoggerService, } from '@backstage/backend-plugin-api'; -import { z } from 'zod'; /** * ActionContext is passed into scaffolder actions. * @public @@ -33,15 +32,8 @@ import { z } from 'zod'; export type ActionContext< TActionInput extends JsonObject, TActionOutput extends JsonObject = JsonObject, - TInputSchema extends - | { [key in string]: (zImpl: typeof z) => z.ZodType } - | Schema - | unknown = unknown, - _TOutputSchema extends - | { [key in string]: (zImpl: typeof z) => z.ZodType } - | Schema - | unknown = unknown, -> = TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } + TSchemaType extends 'v1' | 'v2' = 'v1', +> = TSchemaType extends 'v2' ? { logger: LoggerService; secrets?: TaskSecrets; @@ -176,14 +168,7 @@ export type ActionContext< export type TemplateAction< TActionInput extends JsonObject = JsonObject, TActionOutput extends JsonObject = JsonObject, - TInputSchema extends - | { [key in string]: (zImpl: typeof z) => z.ZodType } - | Schema - | unknown = unknown, - TOutputSchema extends - | { [key in string]: (zImpl: typeof z) => z.ZodType } - | Schema - | unknown = unknown, + TSchemaType extends 'v1' | 'v2' = 'v1', > = { id: string; description?: string; @@ -194,11 +179,6 @@ export type TemplateAction< output?: Schema; }; handler: ( - ctx: ActionContext< - TActionInput, - TActionOutput, - TInputSchema, - TOutputSchema - >, + ctx: ActionContext, ) => Promise; }; From 08efed9160a4cf7f60fb99d04d3c8512ad6de6ad Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 11 Feb 2025 13:42:38 +0100 Subject: [PATCH 08/17] chore: fix changesets Signed-off-by: blam t p --- .changeset/curvy-numbers-grin.md | 7 --- .changeset/dull-olives-itch.md | 69 ++++++++++++++++++++- plugins/scaffolder-node/src/actions/util.ts | 5 +- 3 files changed, 71 insertions(+), 10 deletions(-) delete mode 100644 .changeset/curvy-numbers-grin.md diff --git a/.changeset/curvy-numbers-grin.md b/.changeset/curvy-numbers-grin.md deleted file mode 100644 index 1f786335c0..0000000000 --- a/.changeset/curvy-numbers-grin.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/types': minor ---- - -feat: feat(scaffolder): add first class citizen support for zod - -Added `Prettify` utility type to enhance readability of hover overlays (type hints) for object types. This type simplifies complex object intersections, making them more legible in editor tooltips. diff --git a/.changeset/dull-olives-itch.md b/.changeset/dull-olives-itch.md index b886906849..679ed584d7 100644 --- a/.changeset/dull-olives-itch.md +++ b/.changeset/dull-olives-itch.md @@ -2,6 +2,71 @@ '@backstage/plugin-scaffolder-node': minor --- -feat(scaffolder): add first class citizen support for zod +**DEPRECATION**: We've deprecated the old way of defining actions using `createTemplateAction` with raw `JSONSchema` and type parameters, as well as using `zod` through an import. You can now use the new format to define `createTemplateActions` with `zod` provided by the framework. This change also removes support for `logStream` in the `context` as well as moving the `logger` to an instance of `LoggerService`. -This change introduces a new way to define template actions using Zod schemas for type safety. The existing `createTemplateAction` function is now renamed to `oldCreateTemplateAction` to maintain backwards compatibility. A new `createTemplateAction` function is introduced which acts as an overload, supporting both the old style (using JSON Schema or string schemas) via `oldCreateTemplateAction` and the new style (using Zod schemas) via `newCreateTemplateAction`. This new function, `newCreateTemplateAction`, provides direct support for Zod, simplifying action definition and enhancing type checking. +Before: + +```ts +createTemplateAction<{ repoUrl: string }, { test: string }>({ + id: 'test', + schema: { + input: { + type: 'object', + required: ['repoUrl'], + properties: { + repoUrl: { type: 'string' }, + }, + }, + output: { + type: 'object', + required: ['test'], + properties: { + test: { type: 'string' }, + }, + }, + }, + handler: async ctx => { + ctx.logStream.write('blob'); + }, +}); + +// or + +createTemplateAction({ + id: 'test', + schema: { + input: z.object({ + repoUrl: z.string(), + }), + output: z.object({ + test: z.string(), + }), + }, + handler: async ctx => { + ctx.logStream.write('something'); + }, +}); +``` + +After: + +```ts +createTemplateAction({ + id: 'test', + schema: { + input: { + repoUrl: d => d.string(), + }, + output: { + test: d => d.string(), + }, + }, + handler: async ctx => { + // you can just use ctx.logger.log('...'), or if you really need a log stream you can do this: + const logStream = new PassThrough(); + logStream.on('data', chunk => { + ctx.logger.info(chunk.toString()); + }); + }, +}); +``` diff --git a/plugins/scaffolder-node/src/actions/util.ts b/plugins/scaffolder-node/src/actions/util.ts index 038d199d3b..64728d9cd0 100644 --- a/plugins/scaffolder-node/src/actions/util.ts +++ b/plugins/scaffolder-node/src/actions/util.ts @@ -174,5 +174,8 @@ export const parseSchemas = (action: TemplateActionOptions) => { }; } - throw new Error('Invalid schema provided'); + return { + inputSchema: action.schema.input, + outputSchema: action.schema.output, + }; }; From c6ae5b2e332d96dd86af3e8ea16a101b39cbe877 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 13 Feb 2025 09:27:02 +0100 Subject: [PATCH 09/17] chore: refactor a little bit and fix tests Signed-off-by: blam --- .../tasks/NunjucksWorkflowRunner.test.ts | 203 ++++++++++++------ plugins/scaffolder-node/src/actions/util.ts | 27 +-- 2 files changed, 153 insertions(+), 77 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts index 8614f99388..26b784b512 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts @@ -104,33 +104,37 @@ describe('NunjucksWorkflowRunner', () => { fakeActionHandler = jest.fn(); fakeTaskLog = jest.fn(); - actionRegistry.register({ - id: 'jest-mock-action', - description: 'Mock action for testing', - handler: fakeActionHandler, - }); - - actionRegistry.register({ - id: 'jest-validated-action', - description: 'Mock action for testing', - supportsDryRun: true, - handler: fakeActionHandler, - schema: { - input: { - type: 'object', - required: ['foo'], - properties: { - foo: { - type: 'number', - }, - }, - }, - }, - }); + actionRegistry.register( + createTemplateAction({ + id: 'jest-mock-action', + description: 'Mock action for testing', + handler: fakeActionHandler, + }), + ); actionRegistry.register( createTemplateAction({ - id: 'jest-zod-validated-action', + id: 'jest-validated-action', + description: 'Mock action for testing', + supportsDryRun: true, + handler: fakeActionHandler, + schema: { + input: { + type: 'object', + required: ['foo'], + properties: { + foo: { + type: 'number', + }, + }, + }, + }, + }), + ); + + actionRegistry.register( + createTemplateAction({ + id: 'jest-legacy-zod-validated-action', description: 'Mock action for testing', handler: fakeActionHandler, supportsDryRun: true, @@ -142,52 +146,82 @@ describe('NunjucksWorkflowRunner', () => { }) as TemplateAction, ); - actionRegistry.register({ - id: 'output-action', - description: 'Mock action for testing', - handler: async ctx => { - ctx.output('mock', 'backstage'); - ctx.output('shouldRun', true); - }, - }); + actionRegistry.register( + createTemplateAction({ + id: 'jest-zod-validated-action', + description: 'Mock ac', + supportsDryRun: true, + schema: { + input: { + foo: zod => zod.number(), + }, + output: { + test: zod => zod.string(), + }, + }, + handler: fakeActionHandler, + }) as TemplateAction, + ); - actionRegistry.register({ - id: 'checkpoints-action', - description: 'Mock action with checkpoints', - handler: async ctx => { - const key1 = await ctx.checkpoint({ - key: 'key1', - fn: async () => 'updated', - }); - const key2 = await ctx.checkpoint({ - key: 'key2', - fn: async () => 'updated', - }); - const key3 = await ctx.checkpoint({ - key: 'key3', - fn: async () => 'updated', - }); + actionRegistry.register( + createTemplateAction({ + id: 'output-action', + description: 'Mock action for testing', + handler: async ctx => { + ctx.output('mock', 'backstage'); + ctx.output('shouldRun', true); + }, + }), + ); - const key4 = await ctx.checkpoint({ - key: 'key4', - fn: () => {}, - }); + actionRegistry.register( + createTemplateAction({ + id: 'checkpoints-action', + description: 'Mock action with checkpoints', + schema: { + output: z.object({ + key1: z.string(), + key2: z.string(), + key3: z.string(), + key4: z.string(), + key5: z.string(), + }), + }, + handler: async ctx => { + const key1 = await ctx.checkpoint({ + key: 'key1', + fn: async () => 'updated', + }); + const key2 = await ctx.checkpoint({ + key: 'key2', + fn: async () => 'updated', + }); + const key3 = await ctx.checkpoint({ + key: 'key3', + fn: async () => 'updated', + }); - const key5 = await ctx.checkpoint({ - key: 'key5', - fn: async () => {}, - }); + const key4 = await ctx.checkpoint({ + key: 'key4', + fn: () => {}, + }); - ctx.output('key1', key1); - ctx.output('key2', key2); - ctx.output('key3', key3); + const key5 = await ctx.checkpoint({ + key: 'key5', + fn: async () => {}, + }); - // @ts-expect-error - this is void return - ctx.output('key4', key4); - // @ts-expect-error - this is void return - ctx.output('key5', key5); - }, - }); + ctx.output('key1', key1); + ctx.output('key2', key2); + ctx.output('key3', key3); + + // @ts-expect-error - this is void return + ctx.output('key4', key4); + // @ts-expect-error - this is void return + ctx.output('key5', key5); + }, + }), + ); mockedPermissionApi.authorizeConditional.mockResolvedValue([ { result: AuthorizeResult.ALLOW }, @@ -244,6 +278,25 @@ describe('NunjucksWorkflowRunner', () => { ); }); + it('should throw an error if the action has legacy zod schema and the input does not match', async () => { + const task = createMockTaskWithSpec({ + apiVersion: 'scaffolder.backstage.io/v1beta3', + parameters: {}, + output: {}, + steps: [ + { + id: 'test', + name: 'name', + action: 'jest-legacy-zod-validated-action', + }, + ], + }); + + await expect(runner.execute(task)).rejects.toThrow( + /Invalid input passed to action jest-legacy-zod-validated-action, instance requires property \"foo\"/, + ); + }); + it('should run the action when the zod validation passes', async () => { const task = createMockTaskWithSpec({ apiVersion: 'scaffolder.backstage.io/v1beta3', @@ -264,6 +317,26 @@ describe('NunjucksWorkflowRunner', () => { expect(fakeActionHandler).toHaveBeenCalledTimes(1); }); + it('should run the action when the zod validation passes with legacy zod', async () => { + const task = createMockTaskWithSpec({ + apiVersion: 'scaffolder.backstage.io/v1beta3', + parameters: {}, + output: {}, + steps: [ + { + id: 'test', + name: 'name', + action: 'jest-legacy-zod-validated-action', + input: { foo: 1 }, + }, + ], + }); + + await runner.execute(task); + + expect(fakeActionHandler).toHaveBeenCalledTimes(1); + }); + it('should run the action when the validation passes', async () => { const task = createMockTaskWithSpec({ apiVersion: 'scaffolder.backstage.io/v1beta3', diff --git a/plugins/scaffolder-node/src/actions/util.ts b/plugins/scaffolder-node/src/actions/util.ts index 64728d9cd0..0d7bcd6772 100644 --- a/plugins/scaffolder-node/src/actions/util.ts +++ b/plugins/scaffolder-node/src/actions/util.ts @@ -147,30 +147,33 @@ export const parseSchemas = (action: TemplateActionOptions) => { return { inputSchema: undefined, outputSchema: undefined }; } - if (isZodSchema(action.schema.input) && isZodSchema(action.schema.output)) { + if (isZodSchema(action.schema.input)) { return { inputSchema: zodToJsonSchema(action.schema.input), - outputSchema: zodToJsonSchema(action.schema.output), + outputSchema: isZodSchema(action.schema.output) + ? zodToJsonSchema(action.schema.output) + : undefined, }; } - if ( - isNativeZodSchema(action.schema.input) && - isNativeZodSchema(action.schema.output) - ) { + if (isNativeZodSchema(action.schema.input)) { const input = z.object( Object.fromEntries( Object.entries(action.schema.input).map(([k, v]) => [k, v(z)]), ), ); - const output = z.object( - Object.fromEntries( - Object.entries(action.schema.output).map(([k, v]) => [k, v(z)]), - ), - ); + return { inputSchema: zodToJsonSchema(input), - outputSchema: zodToJsonSchema(output), + outputSchema: isNativeZodSchema(action.schema.output) + ? zodToJsonSchema( + z.object( + Object.fromEntries( + Object.entries(action.schema.output).map(([k, v]) => [k, v(z)]), + ), + ), + ) + : undefined, }; } From 3ece40f8c410bc10181646dcd916885b12311c47 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 18 Feb 2025 09:03:23 +0100 Subject: [PATCH 10/17] chore: wip Signed-off-by: blam Signed-off-by: benjdlambert --- .changeset/chatty-trainers-tease.md | 19 ++++++++++++++ .changeset/dull-olives-itch.md | 2 ++ plugins/scaffolder-backend/package.json | 2 +- .../src/scaffolder/tasks/helper.ts | 8 +++--- plugins/scaffolder-node/package.json | 2 +- .../src/actions/createTemplateAction.ts | 26 ++++++++++--------- plugins/scaffolder-node/src/actions/types.ts | 6 ++--- plugins/scaffolder-node/src/actions/util.ts | 15 ++++++----- yarn.lock | 4 +-- 9 files changed, 55 insertions(+), 29 deletions(-) create mode 100644 .changeset/chatty-trainers-tease.md diff --git a/.changeset/chatty-trainers-tease.md b/.changeset/chatty-trainers-tease.md new file mode 100644 index 0000000000..a247de7f7c --- /dev/null +++ b/.changeset/chatty-trainers-tease.md @@ -0,0 +1,19 @@ +--- +'@backstage/plugin-scaffolder-backend-module-confluence-to-markdown': patch +'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch +'@backstage/plugin-scaffolder-backend-module-notifications': patch +'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch +'@backstage/plugin-scaffolder-backend-module-bitbucket': patch +'@backstage/plugin-scaffolder-backend-module-gerrit': patch +'@backstage/plugin-scaffolder-backend-module-github': patch +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +'@backstage/plugin-scaffolder-backend-module-sentry': patch +'@backstage/plugin-scaffolder-backend-module-yeoman': patch +'@backstage/plugin-scaffolder-backend-module-azure': patch +'@backstage/plugin-scaffolder-backend-module-gitea': patch +'@backstage/plugin-scaffolder-backend-module-rails': patch +'@backstage/plugin-scaffolder-backend-module-gcp': patch +'@backstage/plugin-scaffolder-node-test-utils': patch +--- + +Re-export types diff --git a/.changeset/dull-olives-itch.md b/.changeset/dull-olives-itch.md index 679ed584d7..3a5ef71a21 100644 --- a/.changeset/dull-olives-itch.md +++ b/.changeset/dull-olives-itch.md @@ -1,5 +1,7 @@ --- '@backstage/plugin-scaffolder-node': minor +'@backstage/plugin-scaffolder-backend': minor +'@backstage/plugin-scaffolder-node-test-utils': minor --- **DEPRECATION**: We've deprecated the old way of defining actions using `createTemplateAction` with raw `JSONSchema` and type parameters, as well as using `zod` through an import. You can now use the new format to define `createTemplateActions` with `zod` provided by the framework. This change also removes support for `logStream` in the `context` as well as moving the `logger` to an instance of `LoggerService`. diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 9e5fae8e3c..64efe0275c 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -97,7 +97,7 @@ "globby": "^11.0.0", "isbinaryfile": "^5.0.0", "isolated-vm": "^5.0.1", - "jsonschema": "^1.2.6", + "json-schema": "^0.4.0", "knex": "^3.0.0", "lodash": "^4.17.21", "logform": "^2.3.2", diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/helper.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/helper.ts index 41425affc9..75168d022a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/helper.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/helper.ts @@ -18,7 +18,7 @@ import { Config, readDurationFromConfig } from '@backstage/config'; import { HumanDuration } from '@backstage/types'; import { isArray } from 'lodash'; -import { Schema } from 'jsonschema'; +import { JSONSchema7 } from 'json-schema'; /** * Returns true if the input is not `false`, `undefined`, `null`, `""`, `0`, or @@ -29,7 +29,7 @@ export function isTruthy(value: any): boolean { return isArray(value) ? value.length > 0 : !!value; } -export function generateExampleOutput(schema: Schema): unknown { +export function generateExampleOutput(schema: JSONSchema7): unknown { const { examples } = schema as { examples?: unknown }; if (examples && Array.isArray(examples)) { return examples[0]; @@ -38,13 +38,13 @@ export function generateExampleOutput(schema: Schema): unknown { return Object.fromEntries( Object.entries(schema.properties ?? {}).map(([key, value]) => [ key, - generateExampleOutput(value), + generateExampleOutput(value as JSONSchema7), ]), ); } else if (schema.type === 'array') { const [firstSchema] = [schema.items]?.flat(); if (firstSchema) { - return [generateExampleOutput(firstSchema)]; + return [generateExampleOutput(firstSchema as JSONSchema7)]; } return []; } else if (schema.type === 'string') { diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index d7f8fc042b..c239684ccf 100644 --- a/plugins/scaffolder-node/package.json +++ b/plugins/scaffolder-node/package.json @@ -64,7 +64,7 @@ "fs-extra": "^11.2.0", "globby": "^11.0.0", "isomorphic-git": "^1.23.0", - "jsonschema": "^1.2.6", + "json-schema": "^0.4.0", "p-limit": "^3.1.0", "tar": "^6.1.12", "winston": "^3.2.1", diff --git a/plugins/scaffolder-node/src/actions/createTemplateAction.ts b/plugins/scaffolder-node/src/actions/createTemplateAction.ts index 29c2d79db9..6f91f8a823 100644 --- a/plugins/scaffolder-node/src/actions/createTemplateAction.ts +++ b/plugins/scaffolder-node/src/actions/createTemplateAction.ts @@ -16,7 +16,7 @@ import { ActionContext, TemplateAction } from './types'; import { z } from 'zod'; -import { Schema } from 'jsonschema'; +import { JSONSchema7 } from 'json-schema'; import { Expand, JsonObject } from '@backstage/types'; import { parseSchemas } from './util'; @@ -31,13 +31,13 @@ export type TemplateActionOptions< TActionInput extends JsonObject = {}, TActionOutput extends JsonObject = {}, TInputSchema extends - | Schema + | JSONSchema7 | z.ZodType - | { [key in string]: (zImpl: typeof z) => z.ZodType } = Schema, + | { [key in string]: (zImpl: typeof z) => z.ZodType } = JSONSchema7, TOutputSchema extends - | Schema + | JSONSchema7 | z.ZodType - | { [key in string]: (zImpl: typeof z) => z.ZodType } = Schema, + | { [key in string]: (zImpl: typeof z) => z.ZodType } = JSONSchema7, TSchemaType extends 'v1' | 'v2' = 'v1' | 'v2', > = { id: string; @@ -71,8 +71,8 @@ type FlattenOptionalProperties = Expand< export function createTemplateAction< TInputParams extends JsonObject = JsonObject, TOutputParams extends JsonObject = JsonObject, - TInputSchema extends Schema = Schema, - TOutputSchema extends Schema = Schema, + TInputSchema extends JSONSchema7 = JSONSchema7, + TOutputSchema extends JSONSchema7 = JSONSchema7, TActionInput extends JsonObject = TInputParams, TActionOutput extends JsonObject = TOutputParams, >( @@ -137,11 +137,11 @@ export function createTemplateAction< TInputParams extends JsonObject = JsonObject, TOutputParams extends JsonObject = JsonObject, TInputSchema extends - | Schema + | JSONSchema7 | z.ZodType | { [key in string]: (zImpl: typeof z) => z.ZodType } = {}, TOutputSchema extends - | Schema + | JSONSchema7 | z.ZodType | { [key in string]: (zImpl: typeof z) => z.ZodType } = {}, TActionInput extends JsonObject = TInputSchema extends z.ZodType< @@ -180,14 +180,16 @@ export function createTemplateAction< ? 'v2' : 'v1' > { - const { inputSchema, outputSchema } = parseSchemas(action); + const { inputSchema, outputSchema } = parseSchemas( + action as TemplateActionOptions, + ); return { ...action, schema: { ...action.schema, - input: inputSchema as TInputSchema, - output: outputSchema as TOutputSchema, + input: inputSchema, + output: outputSchema, }, }; } diff --git a/plugins/scaffolder-node/src/actions/types.ts b/plugins/scaffolder-node/src/actions/types.ts index 92a2c7464a..c72fbb6d99 100644 --- a/plugins/scaffolder-node/src/actions/types.ts +++ b/plugins/scaffolder-node/src/actions/types.ts @@ -20,7 +20,7 @@ import { JsonObject, JsonValue } from '@backstage/types'; import { TaskSecrets } from '../tasks'; import { TemplateInfo } from '@backstage/plugin-scaffolder-common'; import { UserEntity } from '@backstage/catalog-model'; -import { Schema } from 'jsonschema'; +import { JSONSchema7 } from 'json-schema'; import { BackstageCredentials, LoggerService, @@ -175,8 +175,8 @@ export type TemplateAction< examples?: { description: string; example: string }[]; supportsDryRun?: boolean; schema?: { - input?: Schema; - output?: Schema; + input?: JSONSchema7; + output?: JSONSchema7; }; handler: ( ctx: ActionContext, diff --git a/plugins/scaffolder-node/src/actions/util.ts b/plugins/scaffolder-node/src/actions/util.ts index 0d7bcd6772..fe42909031 100644 --- a/plugins/scaffolder-node/src/actions/util.ts +++ b/plugins/scaffolder-node/src/actions/util.ts @@ -21,6 +21,7 @@ import { ScmIntegrationRegistry } from '@backstage/integration'; import { TemplateActionOptions } from './createTemplateAction'; import zodToJsonSchema from 'zod-to-json-schema'; import { z } from 'zod'; +import { JSONSchema7 } from 'json-schema'; /** * @public @@ -142,16 +143,18 @@ const isNativeZodSchema = ( ); }; -export const parseSchemas = (action: TemplateActionOptions) => { +export const parseSchemas = ( + action: TemplateActionOptions, +): { inputSchema?: JSONSchema7; outputSchema?: JSONSchema7 } => { if (!action.schema) { return { inputSchema: undefined, outputSchema: undefined }; } if (isZodSchema(action.schema.input)) { return { - inputSchema: zodToJsonSchema(action.schema.input), + inputSchema: zodToJsonSchema(action.schema.input) as JSONSchema7, outputSchema: isZodSchema(action.schema.output) - ? zodToJsonSchema(action.schema.output) + ? (zodToJsonSchema(action.schema.output) as JSONSchema7) : undefined, }; } @@ -164,15 +167,15 @@ export const parseSchemas = (action: TemplateActionOptions) => { ); return { - inputSchema: zodToJsonSchema(input), + inputSchema: zodToJsonSchema(input) as JSONSchema7, outputSchema: isNativeZodSchema(action.schema.output) - ? zodToJsonSchema( + ? (zodToJsonSchema( z.object( Object.fromEntries( Object.entries(action.schema.output).map(([k, v]) => [k, v(z)]), ), ), - ) + ) as JSONSchema7) : undefined, }; } diff --git a/yarn.lock b/yarn.lock index 0ea358c567..48b50e1e9e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7716,7 +7716,7 @@ __metadata: globby: ^11.0.0 isbinaryfile: ^5.0.0 isolated-vm: ^5.0.1 - jsonschema: ^1.2.6 + json-schema: ^0.4.0 knex: ^3.0.0 lodash: ^4.17.21 logform: ^2.3.2 @@ -7794,7 +7794,7 @@ __metadata: fs-extra: ^11.2.0 globby: ^11.0.0 isomorphic-git: ^1.23.0 - jsonschema: ^1.2.6 + json-schema: ^0.4.0 p-limit: ^3.1.0 tar: ^6.1.12 winston: ^3.2.1 From 6d0b7a9ce01caa125454fe42b1fa76a1bbf8c44e Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Fri, 28 Feb 2025 14:00:39 +0100 Subject: [PATCH 11/17] chore: nearly there, just not matching the overload Signed-off-by: benjdlambert --- .../bitbucketCloudPipelinesRun.test.ts | 3 ++ .../src/actions/bitbucketCloudPipelinesRun.ts | 21 ++++++--- .../src/actions/createTemplateAction.ts | 44 +++++++++---------- 3 files changed, 39 insertions(+), 29 deletions(-) diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.test.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.test.ts index edabe2a7ba..f910e35743 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.test.ts @@ -21,6 +21,7 @@ import { createBitbucketPipelinesRunAction } from './bitbucketCloudPipelinesRun' import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { TemplateAction } from '@backstage/plugin-scaffolder-node'; describe('bitbucket:pipelines:run', () => { const config = new ConfigReader({ @@ -65,9 +66,11 @@ describe('bitbucket:pipelines:run', () => { const actionNoCreds = createBitbucketPipelinesRunAction({ integrations: integrationsNoCreds, }); + const testContext = Object.assign({}, mockContext, { input: { workspace, repo_slug }, }); + await expect(actionNoCreds.handler(testContext)).rejects.toThrow( /Authorization has not been provided for Bitbucket Cloud/, ); diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.ts index 8525774692..11064f4743 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.ts @@ -30,12 +30,19 @@ export const createBitbucketPipelinesRunAction = (options: { integrations: ScmIntegrationRegistry; }) => { const { integrations } = options; - return createTemplateAction<{ - workspace: string; - repo_slug: string; - body?: object; - token?: string; - }>({ + return createTemplateAction< + { + workspace: string; + repo_slug: string; + body?: object; + token?: string; + }, + { + buildNumber: number; + repoUrl: string; + pipelinesUrl: string; + } + >({ id, description: 'Run a bitbucket cloud pipeline', examples, @@ -58,7 +65,7 @@ export const createBitbucketPipelinesRunAction = (options: { type: 'number', }, repoUrl: { - title: 'A URL to the pipeline repositry', + title: 'A URL to the pipeline repository', type: 'string', }, repoContentsUrl: { diff --git a/plugins/scaffolder-node/src/actions/createTemplateAction.ts b/plugins/scaffolder-node/src/actions/createTemplateAction.ts index 6f91f8a823..2abe696cb0 100644 --- a/plugins/scaffolder-node/src/actions/createTemplateAction.ts +++ b/plugins/scaffolder-node/src/actions/createTemplateAction.ts @@ -64,26 +64,6 @@ type FlattenOptionalProperties = Expand< } >; -/** - * @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 JSONSchema7 = JSONSchema7, - TOutputSchema extends JSONSchema7 = JSONSchema7, - TActionInput extends JsonObject = TInputParams, - TActionOutput extends JsonObject = TOutputParams, ->( - action: TemplateActionOptions< - TActionInput, - TActionOutput, - TInputSchema, - TOutputSchema, - 'v1' - >, -): TemplateAction; /** * @public * @deprecated migrate to using the new built in zod schema definitions for schemas @@ -133,17 +113,37 @@ export function createTemplateAction< }>, 'v2' >; +/** + * @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 JSONSchema7 = JSONSchema7, + TOutputSchema extends JSONSchema7 = JSONSchema7, + TActionInput extends JsonObject = TInputParams, + TActionOutput extends JsonObject = TOutputParams, +>( + action: TemplateActionOptions< + TActionInput, + TActionOutput, + TInputSchema, + TOutputSchema, + 'v1' + >, +): TemplateAction; export function createTemplateAction< TInputParams extends JsonObject = JsonObject, TOutputParams extends JsonObject = JsonObject, TInputSchema extends | JSONSchema7 | z.ZodType - | { [key in string]: (zImpl: typeof z) => z.ZodType } = {}, + | { [key in string]: (zImpl: typeof z) => z.ZodType } = JSONSchema7, TOutputSchema extends | JSONSchema7 | z.ZodType - | { [key in string]: (zImpl: typeof z) => z.ZodType } = {}, + | { [key in string]: (zImpl: typeof z) => z.ZodType } = JSONSchema7, TActionInput extends JsonObject = TInputSchema extends z.ZodType< any, any, From 965046fdd5ff48c6190d58c7b0ac1ad951f22904 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Tue, 4 Mar 2025 10:38:19 +0100 Subject: [PATCH 12/17] feat: some rework Signed-off-by: benjdlambert Signed-off-by: benjdlambert --- .../bitbucketCloudPipelinesRun.test.ts | 1 - .../confluence/confluenceToMarkdown.ts | 1 + .../src/actions/createTemplateAction.ts | 57 +++++++++---------- 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.test.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.test.ts index f910e35743..83d959e67f 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.test.ts @@ -21,7 +21,6 @@ import { createBitbucketPipelinesRunAction } from './bitbucketCloudPipelinesRun' import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { TemplateAction } from '@backstage/plugin-scaffolder-node'; describe('bitbucket:pipelines:run', () => { const config = new ConfigReader({ diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts index 3d08259e98..aa9f94988a 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts @@ -66,6 +66,7 @@ export const createConfluenceToMarkdownAction = (options: { 'Paste your Confluence url. Ensure it follows this format: https://{confluence+base+url}/display/{spacekey}/{page+title} or https://{confluence+base+url}/spaces/{spacekey}/pages/1234567/{page+title} for Confluence Cloud', items: { type: 'string', + default: 'Confluence URL', }, }, repoUrl: { diff --git a/plugins/scaffolder-node/src/actions/createTemplateAction.ts b/plugins/scaffolder-node/src/actions/createTemplateAction.ts index 2abe696cb0..ce38f3b4e1 100644 --- a/plugins/scaffolder-node/src/actions/createTemplateAction.ts +++ b/plugins/scaffolder-node/src/actions/createTemplateAction.ts @@ -16,7 +16,6 @@ import { ActionContext, TemplateAction } from './types'; import { z } from 'zod'; -import { JSONSchema7 } from 'json-schema'; import { Expand, JsonObject } from '@backstage/types'; import { parseSchemas } from './util'; @@ -31,13 +30,13 @@ export type TemplateActionOptions< TActionInput extends JsonObject = {}, TActionOutput extends JsonObject = {}, TInputSchema extends - | JSONSchema7 + | JsonObject | z.ZodType - | { [key in string]: (zImpl: typeof z) => z.ZodType } = JSONSchema7, + | { [key in string]: (zImpl: typeof z) => z.ZodType } = JsonObject, TOutputSchema extends - | JSONSchema7 + | JsonObject | z.ZodType - | { [key in string]: (zImpl: typeof z) => z.ZodType } = JSONSchema7, + | { [key in string]: (zImpl: typeof z) => z.ZodType } = JsonObject, TSchemaType extends 'v1' | 'v2' = 'v1' | 'v2', > = { id: string; @@ -64,6 +63,26 @@ type FlattenOptionalProperties = Expand< } >; +/** + * @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; /** * @public * @deprecated migrate to using the new built in zod schema definitions for schemas @@ -113,37 +132,17 @@ export function createTemplateAction< }>, 'v2' >; -/** - * @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 JSONSchema7 = JSONSchema7, - TOutputSchema extends JSONSchema7 = JSONSchema7, - TActionInput extends JsonObject = TInputParams, - TActionOutput extends JsonObject = TOutputParams, ->( - action: TemplateActionOptions< - TActionInput, - TActionOutput, - TInputSchema, - TOutputSchema, - 'v1' - >, -): TemplateAction; export function createTemplateAction< TInputParams extends JsonObject = JsonObject, TOutputParams extends JsonObject = JsonObject, TInputSchema extends - | JSONSchema7 + | JsonObject | z.ZodType - | { [key in string]: (zImpl: typeof z) => z.ZodType } = JSONSchema7, + | { [key in string]: (zImpl: typeof z) => z.ZodType } = JsonObject, TOutputSchema extends - | JSONSchema7 + | JsonObject | z.ZodType - | { [key in string]: (zImpl: typeof z) => z.ZodType } = JSONSchema7, + | { [key in string]: (zImpl: typeof z) => z.ZodType } = JsonObject, TActionInput extends JsonObject = TInputSchema extends z.ZodType< any, any, From a5ddf1d2cbc86fcce3a2a492dcd735b79401a631 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Tue, 4 Mar 2025 11:33:47 +0100 Subject: [PATCH 13/17] chore: use the original schema type intead Signed-off-by: benjdlambert --- plugins/scaffolder-backend/package.json | 2 +- .../src/scaffolder/tasks/helper.ts | 8 ++++---- plugins/scaffolder-node/package.json | 2 +- plugins/scaffolder-node/src/actions/types.ts | 6 +++--- plugins/scaffolder-node/src/actions/util.ts | 12 ++++++------ yarn.lock | 12 ++++++------ 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 64efe0275c..e1d7627326 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -97,7 +97,7 @@ "globby": "^11.0.0", "isbinaryfile": "^5.0.0", "isolated-vm": "^5.0.1", - "json-schema": "^0.4.0", + "jsonschema": "^1.5.0", "knex": "^3.0.0", "lodash": "^4.17.21", "logform": "^2.3.2", diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/helper.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/helper.ts index 75168d022a..da47bfd563 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/helper.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/helper.ts @@ -18,7 +18,7 @@ import { Config, readDurationFromConfig } from '@backstage/config'; import { HumanDuration } from '@backstage/types'; import { isArray } from 'lodash'; -import { JSONSchema7 } from 'json-schema'; +import { Schema } from 'jsonschema'; /** * Returns true if the input is not `false`, `undefined`, `null`, `""`, `0`, or @@ -29,7 +29,7 @@ export function isTruthy(value: any): boolean { return isArray(value) ? value.length > 0 : !!value; } -export function generateExampleOutput(schema: JSONSchema7): unknown { +export function generateExampleOutput(schema: Schema): unknown { const { examples } = schema as { examples?: unknown }; if (examples && Array.isArray(examples)) { return examples[0]; @@ -38,13 +38,13 @@ export function generateExampleOutput(schema: JSONSchema7): unknown { return Object.fromEntries( Object.entries(schema.properties ?? {}).map(([key, value]) => [ key, - generateExampleOutput(value as JSONSchema7), + generateExampleOutput(value as Schema), ]), ); } else if (schema.type === 'array') { const [firstSchema] = [schema.items]?.flat(); if (firstSchema) { - return [generateExampleOutput(firstSchema as JSONSchema7)]; + return [generateExampleOutput(firstSchema as Schema)]; } return []; } else if (schema.type === 'string') { diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index c239684ccf..d36da9306e 100644 --- a/plugins/scaffolder-node/package.json +++ b/plugins/scaffolder-node/package.json @@ -64,7 +64,7 @@ "fs-extra": "^11.2.0", "globby": "^11.0.0", "isomorphic-git": "^1.23.0", - "json-schema": "^0.4.0", + "jsonschema": "^1.5.0", "p-limit": "^3.1.0", "tar": "^6.1.12", "winston": "^3.2.1", diff --git a/plugins/scaffolder-node/src/actions/types.ts b/plugins/scaffolder-node/src/actions/types.ts index c72fbb6d99..92a2c7464a 100644 --- a/plugins/scaffolder-node/src/actions/types.ts +++ b/plugins/scaffolder-node/src/actions/types.ts @@ -20,7 +20,7 @@ import { JsonObject, JsonValue } from '@backstage/types'; import { TaskSecrets } from '../tasks'; import { TemplateInfo } from '@backstage/plugin-scaffolder-common'; import { UserEntity } from '@backstage/catalog-model'; -import { JSONSchema7 } from 'json-schema'; +import { Schema } from 'jsonschema'; import { BackstageCredentials, LoggerService, @@ -175,8 +175,8 @@ export type TemplateAction< examples?: { description: string; example: string }[]; supportsDryRun?: boolean; schema?: { - input?: JSONSchema7; - output?: JSONSchema7; + input?: Schema; + output?: Schema; }; handler: ( ctx: ActionContext, diff --git a/plugins/scaffolder-node/src/actions/util.ts b/plugins/scaffolder-node/src/actions/util.ts index fe42909031..f1775bb318 100644 --- a/plugins/scaffolder-node/src/actions/util.ts +++ b/plugins/scaffolder-node/src/actions/util.ts @@ -21,7 +21,7 @@ import { ScmIntegrationRegistry } from '@backstage/integration'; import { TemplateActionOptions } from './createTemplateAction'; import zodToJsonSchema from 'zod-to-json-schema'; import { z } from 'zod'; -import { JSONSchema7 } from 'json-schema'; +import { Schema } from 'jsonschema'; /** * @public @@ -145,16 +145,16 @@ const isNativeZodSchema = ( export const parseSchemas = ( action: TemplateActionOptions, -): { inputSchema?: JSONSchema7; outputSchema?: JSONSchema7 } => { +): { inputSchema?: Schema; outputSchema?: Schema } => { if (!action.schema) { return { inputSchema: undefined, outputSchema: undefined }; } if (isZodSchema(action.schema.input)) { return { - inputSchema: zodToJsonSchema(action.schema.input) as JSONSchema7, + inputSchema: zodToJsonSchema(action.schema.input) as Schema, outputSchema: isZodSchema(action.schema.output) - ? (zodToJsonSchema(action.schema.output) as JSONSchema7) + ? (zodToJsonSchema(action.schema.output) as Schema) : undefined, }; } @@ -167,7 +167,7 @@ export const parseSchemas = ( ); return { - inputSchema: zodToJsonSchema(input) as JSONSchema7, + inputSchema: zodToJsonSchema(input) as Schema, outputSchema: isNativeZodSchema(action.schema.output) ? (zodToJsonSchema( z.object( @@ -175,7 +175,7 @@ export const parseSchemas = ( Object.entries(action.schema.output).map(([k, v]) => [k, v(z)]), ), ), - ) as JSONSchema7) + ) as Schema) : undefined, }; } diff --git a/yarn.lock b/yarn.lock index 48b50e1e9e..a3a6be7dcd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7716,7 +7716,7 @@ __metadata: globby: ^11.0.0 isbinaryfile: ^5.0.0 isolated-vm: ^5.0.1 - json-schema: ^0.4.0 + jsonschema: ^1.5.0 knex: ^3.0.0 lodash: ^4.17.21 logform: ^2.3.2 @@ -7794,7 +7794,7 @@ __metadata: fs-extra: ^11.2.0 globby: ^11.0.0 isomorphic-git: ^1.23.0 - json-schema: ^0.4.0 + jsonschema: ^1.5.0 p-limit: ^3.1.0 tar: ^6.1.12 winston: ^3.2.1 @@ -34244,10 +34244,10 @@ __metadata: languageName: node linkType: hard -"jsonschema@npm:^1.2.6": - version: 1.4.1 - resolution: "jsonschema@npm:1.4.1" - checksum: 1ef02a6cd9bc32241ec86bbf1300bdbc3b5f2d8df6eb795517cf7d1cd9909e7beba1e54fdf73990fd66be98a182bda9add9607296b0cb00b1348212988e424b2 +"jsonschema@npm:^1.2.6, jsonschema@npm:^1.5.0": + version: 1.5.0 + resolution: "jsonschema@npm:1.5.0" + checksum: 170b9c375967bc135f4d029fedc31f5686f2c3bb07e7472cebddbb907b5369bf75a1a50287d6af9c31f61c76fe0b7786e78044c188aaddd329b77d856475e6db languageName: node linkType: hard From 2bd27c0fb94967dd542ca6980636c4b145a56d26 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Tue, 4 Mar 2025 14:54:19 +0100 Subject: [PATCH 14/17] chore: remove changeset Signed-off-by: benjdlambert --- .changeset/chatty-trainers-tease.md | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 .changeset/chatty-trainers-tease.md diff --git a/.changeset/chatty-trainers-tease.md b/.changeset/chatty-trainers-tease.md deleted file mode 100644 index a247de7f7c..0000000000 --- a/.changeset/chatty-trainers-tease.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-confluence-to-markdown': patch -'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch -'@backstage/plugin-scaffolder-backend-module-notifications': patch -'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch -'@backstage/plugin-scaffolder-backend-module-bitbucket': patch -'@backstage/plugin-scaffolder-backend-module-gerrit': patch -'@backstage/plugin-scaffolder-backend-module-github': patch -'@backstage/plugin-scaffolder-backend-module-gitlab': patch -'@backstage/plugin-scaffolder-backend-module-sentry': patch -'@backstage/plugin-scaffolder-backend-module-yeoman': patch -'@backstage/plugin-scaffolder-backend-module-azure': patch -'@backstage/plugin-scaffolder-backend-module-gitea': patch -'@backstage/plugin-scaffolder-backend-module-rails': patch -'@backstage/plugin-scaffolder-backend-module-gcp': patch -'@backstage/plugin-scaffolder-node-test-utils': patch ---- - -Re-export types From e279c30b7027928af5d4afbe910219af58f83a63 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Tue, 4 Mar 2025 14:59:30 +0100 Subject: [PATCH 15/17] chore: fixing changeset Signed-off-by: benjdlambert --- .changeset/slimy-rockets-jog.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/slimy-rockets-jog.md diff --git a/.changeset/slimy-rockets-jog.md b/.changeset/slimy-rockets-jog.md new file mode 100644 index 0000000000..09cdae53d0 --- /dev/null +++ b/.changeset/slimy-rockets-jog.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch +--- + +Fixing spelling mistake in `jsonschema` From 36677bb776e01a48e8dce8ade3c4c7f292286c96 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Tue, 4 Mar 2025 16:25:15 +0100 Subject: [PATCH 16/17] chore: split up changeset Signed-off-by: benjdlambert Signed-off-by: benjdlambert --- .changeset/dull-olives-itch.md | 2 -- .changeset/gorgeous-keys-shop.md | 5 +++++ .changeset/olive-dragons-guess.md | 5 +++++ 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 .changeset/gorgeous-keys-shop.md create mode 100644 .changeset/olive-dragons-guess.md diff --git a/.changeset/dull-olives-itch.md b/.changeset/dull-olives-itch.md index 3a5ef71a21..679ed584d7 100644 --- a/.changeset/dull-olives-itch.md +++ b/.changeset/dull-olives-itch.md @@ -1,7 +1,5 @@ --- '@backstage/plugin-scaffolder-node': minor -'@backstage/plugin-scaffolder-backend': minor -'@backstage/plugin-scaffolder-node-test-utils': minor --- **DEPRECATION**: We've deprecated the old way of defining actions using `createTemplateAction` with raw `JSONSchema` and type parameters, as well as using `zod` through an import. You can now use the new format to define `createTemplateActions` with `zod` provided by the framework. This change also removes support for `logStream` in the `context` as well as moving the `logger` to an instance of `LoggerService`. diff --git a/.changeset/gorgeous-keys-shop.md b/.changeset/gorgeous-keys-shop.md new file mode 100644 index 0000000000..f319f0bdbf --- /dev/null +++ b/.changeset/gorgeous-keys-shop.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-node-test-utils': minor +--- + +Use update `createTemplateAction` kinds diff --git a/.changeset/olive-dragons-guess.md b/.changeset/olive-dragons-guess.md new file mode 100644 index 0000000000..c1e3fa6286 --- /dev/null +++ b/.changeset/olive-dragons-guess.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +Support new `createTemplateAction` type, and convert `catalog:fetch` action to new way of defining actions. From 13f723650facf4b397b3d1ac638ba4322863c5d5 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Tue, 11 Mar 2025 11:12:29 +0100 Subject: [PATCH 17/17] chore: fixing api reports Signed-off-by: benjdlambert --- .../report.api.md | 3 +- .../report.api.md | 13 +- .../report.api.md | 6 +- .../report.api.md | 10 +- .../report.api.md | 3 +- .../report.api.md | 3 +- .../report.api.md | 6 +- .../report.api.md | 3 +- .../report.api.md | 36 ++-- .../report.api.md | 30 ++- .../report.api.md | 3 +- .../report.api.md | 3 +- .../report.api.md | 3 +- .../report.api.md | 3 +- plugins/scaffolder-backend/report.api.md | 94 ++++----- .../actions/TemplateActionRegistry.ts | 6 +- .../tasks/NunjucksWorkflowRunner.test.ts | 4 +- plugins/scaffolder-node/report-alpha.api.md | 2 +- plugins/scaffolder-node/report.api.md | 187 +++++++++++++----- 19 files changed, 262 insertions(+), 156 deletions(-) diff --git a/plugins/scaffolder-backend-module-azure/report.api.md b/plugins/scaffolder-backend-module-azure/report.api.md index e15aba0fa4..4276a33fa0 100644 --- a/plugins/scaffolder-backend-module-azure/report.api.md +++ b/plugins/scaffolder-backend-module-azure/report.api.md @@ -28,6 +28,7 @@ export function createPublishAzureAction(options: { gitAuthorName?: string; gitAuthorEmail?: string; }, - JsonObject + JsonObject, + 'v1' >; ``` diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md b/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md index ad74479b6e..3ce7528722 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md @@ -23,7 +23,12 @@ export const createBitbucketPipelinesRunAction: (options: { body?: object; token?: string; }, - JsonObject + { + buildNumber: number; + repoUrl: string; + pipelinesUrl: string; + }, + 'v1' >; // @public @@ -40,7 +45,8 @@ export function createPublishBitbucketCloudAction(options: { sourcePath?: string; token?: string; }, - JsonObject + JsonObject, + 'v1' >; // @public @@ -58,6 +64,7 @@ export function createPublishBitbucketCloudPullRequestAction(options: { gitAuthorName?: string; gitAuthorEmail?: string; }, - JsonObject + JsonObject, + 'v1' >; ``` diff --git a/plugins/scaffolder-backend-module-bitbucket-server/report.api.md b/plugins/scaffolder-backend-module-bitbucket-server/report.api.md index e92c85b284..ea62b5e90e 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/report.api.md +++ b/plugins/scaffolder-backend-module-bitbucket-server/report.api.md @@ -30,7 +30,8 @@ export function createPublishBitbucketServerAction(options: { gitAuthorName?: string; gitAuthorEmail?: string; }, - JsonObject + JsonObject, + 'v1' >; // @public @@ -49,6 +50,7 @@ export function createPublishBitbucketServerPullRequestAction(options: { gitAuthorName?: string; gitAuthorEmail?: string; }, - JsonObject + JsonObject, + 'v1' >; ``` diff --git a/plugins/scaffolder-backend-module-bitbucket/report.api.md b/plugins/scaffolder-backend-module-bitbucket/report.api.md index b90f2f8483..015a4ac0d6 100644 --- a/plugins/scaffolder-backend-module-bitbucket/report.api.md +++ b/plugins/scaffolder-backend-module-bitbucket/report.api.md @@ -25,7 +25,12 @@ export const createBitbucketPipelinesRunAction: (options: { body?: object; token?: string; }, - JsonObject + { + buildNumber: number; + repoUrl: string; + pipelinesUrl: string; + }, + 'v1' >; // @public @deprecated @@ -45,7 +50,8 @@ export function createPublishBitbucketAction(options: { gitAuthorName?: string; gitAuthorEmail?: string; }, - JsonObject + JsonObject, + 'v1' >; // @public @deprecated (undocumented) diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/report.api.md b/plugins/scaffolder-backend-module-confluence-to-markdown/report.api.md index 2dd5e244dd..64584ad4b5 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/report.api.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/report.api.md @@ -24,6 +24,7 @@ export const createConfluenceToMarkdownAction: (options: { confluenceUrls: string[]; repoUrl: string; }, - JsonObject + JsonObject, + 'v1' >; ``` diff --git a/plugins/scaffolder-backend-module-cookiecutter/report.api.md b/plugins/scaffolder-backend-module-cookiecutter/report.api.md index 36339ea722..4f963ce818 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/report.api.md +++ b/plugins/scaffolder-backend-module-cookiecutter/report.api.md @@ -54,6 +54,7 @@ export function createFetchCookiecutterAction(options: { extensions?: string[]; imageName?: string; }, - JsonObject + JsonObject, + 'v1' >; ``` diff --git a/plugins/scaffolder-backend-module-gerrit/report.api.md b/plugins/scaffolder-backend-module-gerrit/report.api.md index a3f23d7aa7..6cafa2b38a 100644 --- a/plugins/scaffolder-backend-module-gerrit/report.api.md +++ b/plugins/scaffolder-backend-module-gerrit/report.api.md @@ -23,7 +23,8 @@ export function createPublishGerritAction(options: { gitAuthorEmail?: string; sourcePath?: string; }, - JsonObject + JsonObject, + 'v1' >; // @public @@ -39,7 +40,8 @@ export function createPublishGerritReviewAction(options: { gitAuthorName?: string; gitAuthorEmail?: string; }, - JsonObject + JsonObject, + 'v1' >; // @public diff --git a/plugins/scaffolder-backend-module-gitea/report.api.md b/plugins/scaffolder-backend-module-gitea/report.api.md index 8456b7183c..3cb13339d2 100644 --- a/plugins/scaffolder-backend-module-gitea/report.api.md +++ b/plugins/scaffolder-backend-module-gitea/report.api.md @@ -24,7 +24,8 @@ export function createPublishGiteaAction(options: { gitAuthorEmail?: string; sourcePath?: string; }, - JsonObject + JsonObject, + 'v1' >; // @public diff --git a/plugins/scaffolder-backend-module-github/report.api.md b/plugins/scaffolder-backend-module-github/report.api.md index e2d124eb6f..f1b7f98a30 100644 --- a/plugins/scaffolder-backend-module-github/report.api.md +++ b/plugins/scaffolder-backend-module-github/report.api.md @@ -30,7 +30,8 @@ export function createGithubActionsDispatchAction(options: { }; token?: string; }, - JsonObject + JsonObject, + 'v1' >; // @public @@ -45,7 +46,8 @@ export function createGithubAutolinksAction(options: { isAlphanumeric?: boolean; token?: string; }, - JsonObject + JsonObject, + 'v1' >; // @public @@ -81,7 +83,8 @@ export function createGithubBranchProtectionAction(options: { requiredLinearHistory?: boolean; token?: string; }, - JsonObject + JsonObject, + 'v1' >; // @public @@ -96,7 +99,8 @@ export function createGithubDeployKeyAction(options: { privateKeySecretName?: string; token?: string; }, - JsonObject + JsonObject, + 'v1' >; // @public @@ -125,7 +129,8 @@ export function createGithubEnvironmentAction(options: { preventSelfReview?: boolean; reviewers?: string[]; }, - JsonObject + JsonObject, + 'v1' >; // @public @@ -139,7 +144,8 @@ export function createGithubIssuesLabelAction(options: { labels: string[]; token?: string; }, - JsonObject + JsonObject, + 'v1' >; // @public @@ -154,7 +160,8 @@ export function createGithubPagesEnableAction(options: { sourcePath?: '/' | '/docs'; token?: string; }, - JsonObject + JsonObject, + 'v1' >; // @public @@ -255,7 +262,8 @@ export function createGithubRepoCreateAction(options: { }; subscribe?: boolean; }, - JsonObject + JsonObject, + 'v1' >; // @public @@ -299,7 +307,8 @@ export function createGithubRepoPushAction(options: { requiredLinearHistory?: boolean; requireLastPushApproval?: boolean; }, - JsonObject + JsonObject, + 'v1' >; // @public @@ -318,7 +327,8 @@ export function createGithubWebhookAction(options: { insecureSsl?: boolean; token?: string; }, - JsonObject + JsonObject, + 'v1' >; // @public @@ -404,7 +414,8 @@ export function createPublishGithubAction(options: { }; subscribe?: boolean; }, - JsonObject + JsonObject, + 'v1' >; // @public @@ -431,7 +442,8 @@ export const createPublishGithubPullRequestAction: ( forceEmptyGitAuthor?: boolean; createWhenEmpty?: boolean; }, - JsonObject + JsonObject, + 'v1' >; // @public diff --git a/plugins/scaffolder-backend-module-gitlab/report.api.md b/plugins/scaffolder-backend-module-gitlab/report.api.md index d50104e98e..569efd58de 100644 --- a/plugins/scaffolder-backend-module-gitlab/report.api.md +++ b/plugins/scaffolder-backend-module-gitlab/report.api.md @@ -26,7 +26,8 @@ export const createGitlabGroupEnsureExistsAction: (options: { }, { groupId?: number | undefined; - } + }, + 'v1' >; // @public @@ -55,7 +56,8 @@ export const createGitlabIssueAction: (options: { issueUrl: string; issueId: number; issueIid: number; - } + }, + 'v1' >; // @public @@ -73,7 +75,8 @@ export const createGitlabProjectAccessTokenAction: (options: { }, { access_token: string; - } + }, + 'v1' >; // @public @@ -91,7 +94,8 @@ export const createGitlabProjectDeployTokenAction: (options: { { user: string; deploy_token: string; - } + }, + 'v1' >; // @public @@ -110,7 +114,8 @@ export const createGitlabProjectVariableAction: (options: { environmentScope?: string | undefined; variableProtected?: boolean | undefined; }, - JsonObject + any, + 'v1' >; // @public @@ -126,7 +131,8 @@ export const createGitlabRepoPushAction: (options: { token?: string; commitAction?: 'create' | 'delete' | 'update'; }, - JsonObject + JsonObject, + 'v1' >; // @public @@ -176,7 +182,8 @@ export function createPublishGitlabAction(options: { environment_scope?: string; }>; }, - JsonObject + JsonObject, + 'v1' >; // @public @@ -199,7 +206,8 @@ export const createPublishGitlabMergeRequestAction: (options: { reviewers?: string[]; assignReviewersFromApprovalRules?: boolean; }, - JsonObject + JsonObject, + 'v1' >; // @public @@ -216,7 +224,8 @@ export const createTriggerGitlabPipelineAction: (options: { }, { pipelineUrl: string; - } + }, + 'v1' >; // @public @@ -252,7 +261,8 @@ export const editGitlabIssueAction: (options: { issueUrl: string; issueId: number; issueIid: number; - } + }, + 'v1' >; // @public diff --git a/plugins/scaffolder-backend-module-notifications/report.api.md b/plugins/scaffolder-backend-module-notifications/report.api.md index 1c2bcf5f64..cb796e5033 100644 --- a/plugins/scaffolder-backend-module-notifications/report.api.md +++ b/plugins/scaffolder-backend-module-notifications/report.api.md @@ -23,7 +23,8 @@ export function createSendNotificationAction(options: { scope?: string; optional?: boolean; }, - JsonObject + JsonObject, + 'v1' >; // @public diff --git a/plugins/scaffolder-backend-module-rails/report.api.md b/plugins/scaffolder-backend-module-rails/report.api.md index 846e2fe77f..0d7618852d 100644 --- a/plugins/scaffolder-backend-module-rails/report.api.md +++ b/plugins/scaffolder-backend-module-rails/report.api.md @@ -49,7 +49,8 @@ export function createFetchRailsAction(options: { values: JsonObject; imageName?: string; }, - JsonObject + JsonObject, + 'v1' >; // @public diff --git a/plugins/scaffolder-backend-module-sentry/report.api.md b/plugins/scaffolder-backend-module-sentry/report.api.md index ff31bd62c3..973ef9844c 100644 --- a/plugins/scaffolder-backend-module-sentry/report.api.md +++ b/plugins/scaffolder-backend-module-sentry/report.api.md @@ -19,7 +19,8 @@ export function createSentryCreateProjectAction(options: { slug?: string; authToken?: string; }, - JsonObject + JsonObject, + 'v1' >; // @public diff --git a/plugins/scaffolder-backend-module-yeoman/report.api.md b/plugins/scaffolder-backend-module-yeoman/report.api.md index ac262abcf6..57498bbe3a 100644 --- a/plugins/scaffolder-backend-module-yeoman/report.api.md +++ b/plugins/scaffolder-backend-module-yeoman/report.api.md @@ -14,7 +14,8 @@ export function createRunYeomanAction(): TemplateAction< args?: string[]; options?: JsonObject; }, - JsonObject + JsonObject, + 'v1' >; // @public diff --git a/plugins/scaffolder-backend/report.api.md b/plugins/scaffolder-backend/report.api.md index 18813154f2..37afe780a9 100644 --- a/plugins/scaffolder-backend/report.api.md +++ b/plugins/scaffolder-backend/report.api.md @@ -30,6 +30,7 @@ import { createPublishGerritAction as createPublishGerritAction_2 } from '@backs import { createPublishGerritReviewAction as createPublishGerritReviewAction_2 } from '@backstage/plugin-scaffolder-backend-module-gerrit'; import { createPublishGithubAction as createPublishGithubAction_2 } from '@backstage/plugin-scaffolder-backend-module-github'; import { createPublishGitlabAction as createPublishGitlabAction_2 } from '@backstage/plugin-scaffolder-backend-module-gitlab'; +import { createTemplateAction as createTemplateAction_2 } from '@backstage/plugin-scaffolder-node'; import { DatabaseService } from '@backstage/backend-plugin-api'; import { DiscoveryService } from '@backstage/backend-plugin-api'; import { Duration } from 'luxon'; @@ -54,7 +55,6 @@ import { RESOURCE_TYPE_SCAFFOLDER_ACTION } from '@backstage/plugin-scaffolder-co import { RESOURCE_TYPE_SCAFFOLDER_TEMPLATE } from '@backstage/plugin-scaffolder-common/alpha'; import { ScaffolderEntitiesProcessor as ScaffolderEntitiesProcessor_2 } from '@backstage/plugin-catalog-backend-module-scaffolder-entity-model'; import { SchedulerService } from '@backstage/backend-plugin-api'; -import { Schema } from 'jsonschema'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { ScmIntegrations } from '@backstage/integration'; import { SerializedTask as SerializedTask_2 } from '@backstage/plugin-scaffolder-node'; @@ -71,14 +71,12 @@ import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { TaskSpecV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TaskStatus as TaskStatus_2 } from '@backstage/plugin-scaffolder-node'; import { TemplateAction as TemplateAction_2 } from '@backstage/plugin-scaffolder-node'; -import { TemplateActionOptions } from '@backstage/plugin-scaffolder-node'; import { TemplateEntityStepV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TemplateFilter as TemplateFilter_2 } from '@backstage/plugin-scaffolder-node'; import { TemplateGlobal as TemplateGlobal_2 } from '@backstage/plugin-scaffolder-node'; import { TemplateParametersV1beta3 } from '@backstage/plugin-scaffolder-common'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { WorkspaceProvider } from '@backstage/plugin-scaffolder-node/alpha'; -import { ZodType } from 'zod'; // @public @deprecated (undocumented) export type ActionContext = ActionContext_2; @@ -125,7 +123,8 @@ export function createCatalogRegisterAction(options: { catalogInfoPath?: string; optional?: boolean; }, - JsonObject + JsonObject, + 'v1' >; // @public @@ -134,17 +133,12 @@ export function createCatalogWriteAction(): TemplateAction_2< entity: Record; filePath?: string | undefined; }, - JsonObject + any, + 'v1' >; // @public -export function createDebugLogAction(): TemplateAction_2< - { - message?: string; - listWorkspace?: boolean | 'with-filenames' | 'with-contents'; - }, - JsonObject ->; +export function createDebugLogAction(): TemplateAction_2; // @public export function createFetchCatalogEntityAction(options: { @@ -152,16 +146,17 @@ export function createFetchCatalogEntityAction(options: { auth?: AuthService; }): TemplateAction_2< { + entityRef?: string | undefined; + entityRefs?: string[] | undefined; optional?: boolean | undefined; defaultKind?: string | undefined; defaultNamespace?: string | undefined; - entityRef?: string | undefined; - entityRefs?: string[] | undefined; }, { - entities?: any[] | undefined; entity?: any; - } + entities?: any[] | undefined; + }, + 'v2' >; // @public @@ -174,7 +169,8 @@ export function createFetchPlainAction(options: { targetPath?: string; token?: string; }, - JsonObject + JsonObject, + 'v1' >; // @public @@ -187,7 +183,8 @@ export function createFetchPlainFileAction(options: { targetPath: string; token?: string; }, - JsonObject + JsonObject, + 'v1' >; // @public @@ -210,7 +207,8 @@ export function createFetchTemplateAction(options: { lstripBlocks?: boolean; token?: string; }, - JsonObject + JsonObject, + 'v1' >; // @public @@ -230,7 +228,8 @@ export function createFetchTemplateFileAction(options: { lstripBlocks?: boolean; token?: string; }, - JsonObject + JsonObject, + 'v1' >; // @public @@ -238,14 +237,15 @@ export const createFilesystemDeleteAction: () => TemplateAction_2< { files: string[]; }, - JsonObject + JsonObject, + 'v1' >; // @public export const createFilesystemReadDirAction: () => TemplateAction_2< { + recursive: boolean; paths: string[]; - recursive?: boolean | undefined; }, { files: { @@ -258,7 +258,8 @@ export const createFilesystemReadDirAction: () => TemplateAction_2< path: string; fullPath: string; }[]; - } + }, + 'v1' >; // @public @@ -270,7 +271,8 @@ export const createFilesystemRenameAction: () => TemplateAction_2< overwrite?: boolean; }>; }, - JsonObject + JsonObject, + 'v1' >; // @public @deprecated (undocumented) @@ -346,7 +348,8 @@ export const createPublishGithubPullRequestAction: ( forceEmptyGitAuthor?: boolean; createWhenEmpty?: boolean; }, - JsonObject + JsonObject, + 'v1' >; // @public @deprecated (undocumented) @@ -372,45 +375,20 @@ export const createPublishGitlabMergeRequestAction: (options: { reviewers?: string[]; assignReviewersFromApprovalRules?: boolean; }, - JsonObject + JsonObject, + 'v1' >; // @public @deprecated export function createRouter(options: RouterOptions): Promise; // @public @deprecated (undocumented) -export const createTemplateAction: < - TInputParams extends JsonObject = JsonObject, - TOutputParams extends JsonObject = JsonObject, - TInputSchema extends Schema | ZodType = {}, - TOutputSchema extends Schema | ZodType = {}, - TActionInput extends JsonObject = TInputSchema extends ZodType< - any, - any, - infer IReturn - > - ? IReturn - : TInputParams, - TActionOutput extends JsonObject = TOutputSchema extends ZodType< - any, - any, - infer IReturn_1 - > - ? IReturn_1 - : TOutputParams, ->( - action: TemplateActionOptions< - TActionInput, - TActionOutput, - TInputSchema, - TOutputSchema - >, -) => TemplateAction_2; +export const createTemplateAction: typeof createTemplateAction_2; // @public export function createWaitAction(options?: { maxWaitTime?: Duration | HumanDuration; -}): TemplateAction_2; +}): TemplateAction_2; // @public export type CreateWorkerOptions = { @@ -547,7 +525,7 @@ export const fetchContents: typeof fetchContents_2; // @public @deprecated export interface RouterOptions { // (undocumented) - actions?: TemplateAction_2[]; + actions?: TemplateAction_2[]; // (undocumented) additionalTemplateFilters?: | Record @@ -866,11 +844,11 @@ export type TemplateAction = // @public export class TemplateActionRegistry { // (undocumented) - get(actionId: string): TemplateAction_2; + get(actionId: string): TemplateAction_2; // (undocumented) - list(): TemplateAction_2[]; + list(): TemplateAction_2[]; // (undocumented) - register(action: TemplateAction_2): void; + register(action: TemplateAction_2): void; } // @public @deprecated (undocumented) diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts b/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts index f00d7afad1..11d6234083 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts @@ -23,7 +23,7 @@ import { TemplateAction } from '@backstage/plugin-scaffolder-node'; export class TemplateActionRegistry { private readonly actions = new Map(); - register(action: TemplateAction) { + register(action: TemplateAction) { if (this.actions.has(action.id)) { throw new ConflictError( `Template action with ID '${action.id}' has already been registered`, @@ -33,7 +33,7 @@ export class TemplateActionRegistry { this.actions.set(action.id, action); } - get(actionId: string): TemplateAction { + get(actionId: string): TemplateAction { const action = this.actions.get(actionId); if (!action) { throw new NotFoundError( @@ -43,7 +43,7 @@ export class TemplateActionRegistry { return action; } - list(): TemplateAction[] { + list(): TemplateAction[] { return [...this.actions.values()]; } } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts index 26b784b512..2d6ed2d57c 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts @@ -160,7 +160,7 @@ describe('NunjucksWorkflowRunner', () => { }, }, handler: fakeActionHandler, - }) as TemplateAction, + }), ); actionRegistry.register( @@ -215,9 +215,7 @@ describe('NunjucksWorkflowRunner', () => { ctx.output('key2', key2); ctx.output('key3', key3); - // @ts-expect-error - this is void return ctx.output('key4', key4); - // @ts-expect-error - this is void return ctx.output('key5', key5); }, }), diff --git a/plugins/scaffolder-node/report-alpha.api.md b/plugins/scaffolder-node/report-alpha.api.md index 8ecc54f031..fa9e022bb4 100644 --- a/plugins/scaffolder-node/report-alpha.api.md +++ b/plugins/scaffolder-node/report-alpha.api.md @@ -122,7 +122,7 @@ export const restoreWorkspace: (opts: { // @alpha export interface ScaffolderActionsExtensionPoint { // (undocumented) - addActions(...actions: TemplateAction[]): void; + addActions(...actions: TemplateAction[]): void; } // @alpha diff --git a/plugins/scaffolder-node/report.api.md b/plugins/scaffolder-node/report.api.md index 333b47a6d1..494efc28f1 100644 --- a/plugins/scaffolder-node/report.api.md +++ b/plugins/scaffolder-node/report.api.md @@ -4,6 +4,7 @@ ```ts import { BackstageCredentials } from '@backstage/backend-plugin-api'; +import { Expand } from '@backstage/types'; import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; import { Logger } from 'winston'; @@ -24,34 +25,63 @@ import { z } from 'zod'; export type ActionContext< TActionInput extends JsonObject, TActionOutput extends JsonObject = JsonObject, -> = { - logger: Logger; - logStream: Writable; - secrets?: TaskSecrets; - workspacePath: string; - input: TActionInput; - checkpoint(opts: { - key: string; - fn: () => Promise | T; - }): Promise; - output( - name: keyof TActionOutput, - value: TActionOutput[keyof TActionOutput], - ): void; - createTemporaryDirectory(): Promise; - getInitiatorCredentials(): Promise; - task: { - id: string; - }; - templateInfo?: TemplateInfo; - isDryRun?: boolean; - user?: { - entity?: UserEntity; - ref?: string; - }; - signal?: AbortSignal; - each?: JsonObject; -}; + TSchemaType extends 'v1' | 'v2' = 'v1', +> = TSchemaType extends 'v2' + ? { + logger: LoggerService; + secrets?: TaskSecrets; + workspacePath: string; + input: TActionInput; + checkpoint(opts: { + key: string; + fn: () => Promise | T; + }): Promise; + output( + name: keyof TActionOutput, + value: TActionOutput[keyof TActionOutput], + ): void; + createTemporaryDirectory(): Promise; + getInitiatorCredentials(): Promise; + 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(opts: { + key: string; + fn: () => Promise | T; + }): Promise; + output( + name: keyof TActionOutput, + value: TActionOutput[keyof TActionOutput], + ): void; + createTemporaryDirectory(): Promise; + getInitiatorCredentials(): Promise; + task: { + id: string; + }; + templateInfo?: TemplateInfo; + isDryRun?: boolean; + user?: { + entity?: UserEntity; + ref?: string; + }; + signal?: AbortSignal; + each?: JsonObject; + }; // @public (undocumented) export function addFiles(options: { @@ -148,34 +178,71 @@ export function createBranch(options: { logger?: Logger | undefined; }): Promise; -// @public -export const createTemplateAction: < +// @public @deprecated (undocumented) +export function createTemplateAction< TInputParams extends JsonObject = JsonObject, TOutputParams extends JsonObject = JsonObject, - TInputSchema extends Schema | z.ZodType = {}, - TOutputSchema extends Schema | z.ZodType = {}, - TActionInput extends JsonObject = TInputSchema extends z.ZodType< - any, - any, - infer IReturn - > - ? IReturn - : TInputParams, - TActionOutput extends JsonObject = TOutputSchema extends z.ZodType< - any, - any, - infer IReturn_1 - > - ? IReturn_1 - : TOutputParams, + TInputSchema extends JsonObject = JsonObject, + TOutputSchema extends JsonObject = JsonObject, + TActionInput extends JsonObject = TInputParams, + TActionOutput extends JsonObject = TOutputParams, >( action: TemplateActionOptions< TActionInput, TActionOutput, TInputSchema, - TOutputSchema + TOutputSchema, + 'v1' >, -) => TemplateAction; +): TemplateAction; + +// @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, + TActionOutput extends JsonObject = z.infer, +>( + action: TemplateActionOptions< + TActionInput, + TActionOutput, + TInputSchema, + TOutputSchema, + 'v1' + >, +): TemplateAction; + +// @public +export function createTemplateAction< + TInputSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, + TOutputSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, +>( + action: TemplateActionOptions< + { + [key in keyof TInputSchema]: z.infer>; + }, + { + [key in keyof TOutputSchema]: z.infer>; + }, + TInputSchema, + TOutputSchema, + 'v2' + >, +): TemplateAction< + FlattenOptionalProperties<{ + [key in keyof TInputSchema]: z.output>; + }>, + FlattenOptionalProperties<{ + [key in keyof TOutputSchema]: z.output>; + }>, + 'v2' +>; // @public export function deserializeDirectoryContents( @@ -440,6 +507,7 @@ export type TaskStatus = export type TemplateAction< TActionInput extends JsonObject = JsonObject, TActionOutput extends JsonObject = JsonObject, + TSchemaType extends 'v1' | 'v2' = 'v1', > = { id: string; description?: string; @@ -452,15 +520,28 @@ export type TemplateAction< input?: Schema; output?: Schema; }; - handler: (ctx: ActionContext) => Promise; + handler: ( + ctx: ActionContext, + ) => Promise; }; // @public (undocumented) export type TemplateActionOptions< TActionInput extends JsonObject = {}, TActionOutput extends JsonObject = {}, - TInputSchema extends Schema | z.ZodType = {}, - TOutputSchema extends Schema | z.ZodType = {}, + TInputSchema extends + | JsonObject + | z.ZodType + | { + [key in string]: (zImpl: typeof z) => z.ZodType; + } = JsonObject, + TOutputSchema extends + | JsonObject + | z.ZodType + | { + [key in string]: (zImpl: typeof z) => z.ZodType; + } = JsonObject, + TSchemaType extends 'v1' | 'v2' = 'v1' | 'v2', > = { id: string; description?: string; @@ -470,7 +551,9 @@ export type TemplateActionOptions< input?: TInputSchema; output?: TOutputSchema; }; - handler: (ctx: ActionContext) => Promise; + handler: ( + ctx: ActionContext, + ) => Promise; }; // @public (undocumented)