diff --git a/.changeset/wild-horses-refuse.md b/.changeset/wild-horses-refuse.md new file mode 100644 index 0000000000..9dc0c5a15d --- /dev/null +++ b/.changeset/wild-horses-refuse.md @@ -0,0 +1,12 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +'@backstage/plugin-scaffolder-node': minor +--- + +BREAKING EXPERIMENTAL: The `checkpoint` method now takes an object instead of previous arguments. + +```ts +await ctx.checkpoint({ key: 'repo.create', fn: () => ockokit.repo.create({...})}) +``` + +You can also now return `void` from the checkpoint if the method returns `void` inside the `checkpoint` handler. diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts index 25caf7bad0..9bef42b26a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts @@ -152,19 +152,37 @@ describe('NunjucksWorkflowRunner', () => { id: 'checkpoints-action', description: 'Mock action with checkpoints', handler: async ctx => { - const key1 = await ctx.checkpoint('key1', async () => { - return 'updated'; + const key1 = await ctx.checkpoint({ + key: 'key1', + fn: async () => 'updated', }); - const key2 = await ctx.checkpoint('key2', async () => { - return 'updated'; + const key2 = await ctx.checkpoint({ + key: 'key2', + fn: async () => 'updated', }); - const key3 = await ctx.checkpoint('key3', async () => { - return 'updated'; + const key3 = await ctx.checkpoint({ + key: 'key3', + fn: async () => 'updated', + }); + + const key4 = await ctx.checkpoint({ + key: 'key4', + fn: () => {}, + }); + + const key5 = await ctx.checkpoint({ + key: 'key5', + fn: async () => {}, }); 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); }, }); @@ -632,6 +650,9 @@ describe('NunjucksWorkflowRunner', () => { key1: '${{steps.test.output.key1}}', key2: '${{steps.test.output.key2}}', key3: '${{steps.test.output.key3}}', + key4: '${{steps.test.output.key4}}', + key5: '${{steps.test.output.key5}}', + key6: '${{steps.test.output.key6}}', }, }), getTaskState: (): Promise< @@ -661,6 +682,8 @@ describe('NunjucksWorkflowRunner', () => { expect(result.output.key1).toEqual('initial'); expect(result.output.key2).toEqual('updated'); expect(result.output.key3).toEqual('updated'); + expect(result.output.key4).toEqual(undefined); + expect(result.output.key5).toEqual(undefined); }); it('should template the output from simple actions', async () => { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 6f7551568f..c5413a08e0 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -364,6 +364,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { )}`, ); } + await action.handler({ input: iteration.input, secrets: task.secrets ?? {}, @@ -371,21 +372,25 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { logger: loggerToWinstonLogger(taskLogger), logStream: streamLogger, workspacePath, - async checkpoint( - keySuffix: string, - fn: () => Promise, - ) { - const key = `v1.task.checkpoint.${step.id}.${keySuffix}`; + async checkpoint(opts: { + key?: string; + fn: () => Promise | T; + }) { + const { key: checkpointKey, fn } = opts; + const key = `v1.task.checkpoint.${step.id}.${checkpointKey}`; + try { - let prevValue: U | undefined; + let prevValue: T | undefined; + if (prevTaskState) { const prevState = ( prevTaskState.state?.checkpoints as { [key: string]: CheckpointState; } )?.[key]; + if (prevState && prevState.status === 'success') { - prevValue = prevState.value as U; + prevValue = prevState.value as T; } } @@ -395,7 +400,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { task.updateCheckpoint?.({ key, status: 'success', - value, + value: value ?? {}, }); } return value; diff --git a/plugins/scaffolder-node/report.api.md b/plugins/scaffolder-node/report.api.md index 6328f94bfd..c14a689d1a 100644 --- a/plugins/scaffolder-node/report.api.md +++ b/plugins/scaffolder-node/report.api.md @@ -31,10 +31,10 @@ export type ActionContext< secrets?: TaskSecrets; workspacePath: string; input: TActionInput; - checkpoint( - key: string, - fn: () => Promise, - ): Promise; + checkpoint(opts: { + key: string; + fn: () => Promise | T; + }): Promise; output( name: keyof TActionOutput, value: TActionOutput[keyof TActionOutput], diff --git a/plugins/scaffolder-node/src/actions/types.ts b/plugins/scaffolder-node/src/actions/types.ts index f0e4c12869..5dd9d5526f 100644 --- a/plugins/scaffolder-node/src/actions/types.ts +++ b/plugins/scaffolder-node/src/actions/types.ts @@ -38,10 +38,10 @@ export type ActionContext< secrets?: TaskSecrets; workspacePath: string; input: TActionInput; - checkpoint( - key: string, - fn: () => Promise, - ): Promise; + checkpoint(opts: { + key: string; + fn: () => Promise | T; + }): Promise; output( name: keyof TActionOutput, value: TActionOutput[keyof TActionOutput],