From fbd7c921b9b9e0ab4e514c269af1a3ac5b354719 Mon Sep 17 00:00:00 2001 From: Kurt King Date: Fri, 21 Mar 2025 22:11:08 -0600 Subject: [PATCH 01/20] improve type safety with new type structure Signed-off-by: Kurt King --- .../tasks/NunjucksWorkflowRunner.ts | 15 +-- .../src/scaffolder/tasks/StorageTaskBroker.ts | 106 +++++++++++++----- 2 files changed, 78 insertions(+), 43 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 7802a7ab8d..444d6efe9b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -60,6 +60,7 @@ import { scaffolderActionRules } from '../../service/rules'; import { createCounterMetric, createHistogramMetric } from '../../util/metrics'; import { BackstageLoggerTransport, WinstonLogger } from './logger'; import { convertFiltersToRecord } from '../../util/templating'; +import { CheckpointState } from './StorageTaskBroker'; type NunjucksWorkflowRunnerOptions = { workingDirectory: string; @@ -91,16 +92,6 @@ type TemplateContext = { }; }; -type CheckpointState = - | { - status: 'failed'; - reason: string; - } - | { - status: 'success'; - value: JsonValue; - }; - const isValidTaskSpec = (taskSpec: TaskSpec): taskSpec is TaskSpecV1beta3 => { return taskSpec.apiVersion === 'scaffolder.backstage.io/v1beta3'; }; @@ -396,9 +387,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { if (prevTaskState) { const prevState = ( - prevTaskState.state?.checkpoints as { - [key: string]: CheckpointState; - } + prevTaskState.state?.checkpoints as CheckpointState )?.[key]; if (prevState && prevState.status === 'success') { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 86f9f7fcf7..a92d4134f5 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -44,19 +44,30 @@ import { DefaultWorkspaceService, WorkspaceService } from './WorkspaceService'; import { readDuration } from './helper'; import { InternalTaskSecrets, TaskStore } from './types'; -type TaskState = { - checkpoints: { - [key: string]: - | { - status: 'failed'; - reason: string; - } - | { - status: 'success'; - value: JsonValue; - }; - }; +type CheckpointStatus = 'failed' | 'success'; + +export type CheckpointSuccessState = { + status: Extract; + value: JsonValue; }; + +export type CheckpointFailedState = { + status: Extract; + reason: string; +}; + +export type CheckpointState = { + [key: string]: CheckpointSuccessState | CheckpointFailedState; +}; + +export type UpdateCheckpointOptions = { + key: string; +} & CheckpointState[keyof CheckpointState]; + +type TaskState = { + checkpoints: CheckpointState; +}; + /** * TaskManager * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. @@ -152,25 +163,57 @@ export class TaskManager implements TaskContext { return this.storage.getTaskState?.({ taskId: this.task.taskId }); } - async updateCheckpoint?( - options: - | { - key: string; - status: 'success'; - value: JsonValue; - } - | { - key: string; - status: 'failed'; - reason: string; - }, - ): Promise { - const { key, ...value } = options; - if (this.task.state) { - (this.task.state as TaskState).checkpoints[key] = value; - } else { - this.task.state = { checkpoints: { [key]: value } }; + /** + * Helper to safely access the checkpoints field from task state + * Ensures type safety when working with task state that might not match our structure + */ + private getCheckpointsFromState(state?: JsonObject): CheckpointState { + if ( + state && + 'checkpoints' in state && + typeof state.checkpoints === 'object' + ) { + return state.checkpoints as CheckpointState; } + return {}; + } + + async updateCheckpoint?(options: UpdateCheckpointOptions): Promise { + const { key, status } = options; + + // Extract appropriate state value based on status + let checkpointValue: CheckpointSuccessState | CheckpointFailedState; + + switch (status) { + case 'success': { + const { value } = options; + checkpointValue = { status, value }; + break; + } + case 'failed': { + const { reason } = options; + checkpointValue = { status, reason }; + break; + } + default: { + // Using status as 'never' gives compile-time guarantee we've handled all cases + const exhaustiveCheck: never = status; + throw new Error(`Unexpected status: ${exhaustiveCheck}`); + } + } + + if (this.task.state) { + const taskState: TaskState = { + checkpoints: this.getCheckpointsFromState(this.task.state), + }; + + // Update with the new checkpoint + taskState.checkpoints[key] = checkpointValue; + this.task.state = taskState; + } else { + this.task.state = { checkpoints: { [key]: checkpointValue } }; + } + await this.storage.saveTaskState?.({ taskId: this.task.taskId, state: this.task.state, @@ -254,6 +297,9 @@ export interface CurrentClaimedTask { secrets?: TaskSecrets; /** * The state of checkpoints of the task. + * This will be a JsonObject that may contain a `checkpoints` field + * with a structure matching the CheckpointState interface. + * @see CheckpointState */ state?: JsonObject; /** From 919a61667310f9d9d9100658742e758e65913fc1 Mon Sep 17 00:00:00 2001 From: Kurt King Date: Fri, 21 Mar 2025 22:22:42 -0600 Subject: [PATCH 02/20] move types to node package Signed-off-by: Kurt King --- .../tasks/NunjucksWorkflowRunner.ts | 2 +- .../src/scaffolder/tasks/StorageTaskBroker.ts | 33 ++------ plugins/scaffolder-node/src/actions/types.ts | 9 ++- .../scaffolder-node/src/checkpoints/index.ts | 17 ++++ .../scaffolder-node/src/checkpoints/types.ts | 78 +++++++++++++++++++ plugins/scaffolder-node/src/index.ts | 1 + plugins/scaffolder-node/src/tasks/types.ts | 17 +--- 7 files changed, 112 insertions(+), 45 deletions(-) create mode 100644 plugins/scaffolder-node/src/checkpoints/index.ts create mode 100644 plugins/scaffolder-node/src/checkpoints/types.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 444d6efe9b..d97bf10543 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -60,7 +60,7 @@ import { scaffolderActionRules } from '../../service/rules'; import { createCounterMetric, createHistogramMetric } from '../../util/metrics'; import { BackstageLoggerTransport, WinstonLogger } from './logger'; import { convertFiltersToRecord } from '../../util/templating'; -import { CheckpointState } from './StorageTaskBroker'; +import { CheckpointState } from '@backstage/plugin-scaffolder-node'; type NunjucksWorkflowRunnerOptions = { workingDirectory: string; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index a92d4134f5..9a872e7b3f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -33,36 +33,17 @@ import { TaskStatus, } from '@backstage/plugin-scaffolder-node'; import { WorkspaceProvider } from '@backstage/plugin-scaffolder-node/alpha'; -import { - JsonObject, - JsonValue, - Observable, - createDeferred, -} from '@backstage/types'; +import { JsonObject, Observable, createDeferred } from '@backstage/types'; import ObservableImpl from 'zen-observable'; import { DefaultWorkspaceService, WorkspaceService } from './WorkspaceService'; import { readDuration } from './helper'; import { InternalTaskSecrets, TaskStore } from './types'; - -type CheckpointStatus = 'failed' | 'success'; - -export type CheckpointSuccessState = { - status: Extract; - value: JsonValue; -}; - -export type CheckpointFailedState = { - status: Extract; - reason: string; -}; - -export type CheckpointState = { - [key: string]: CheckpointSuccessState | CheckpointFailedState; -}; - -export type UpdateCheckpointOptions = { - key: string; -} & CheckpointState[keyof CheckpointState]; +import { + CheckpointState, + CheckpointSuccessState, + CheckpointFailedState, + UpdateCheckpointOptions, +} from '@backstage/plugin-scaffolder-node'; type TaskState = { checkpoints: CheckpointState; diff --git a/plugins/scaffolder-node/src/actions/types.ts b/plugins/scaffolder-node/src/actions/types.ts index f13d256332..e7e678450d 100644 --- a/plugins/scaffolder-node/src/actions/types.ts +++ b/plugins/scaffolder-node/src/actions/types.ts @@ -23,6 +23,8 @@ import { BackstageCredentials, LoggerService, } from '@backstage/backend-plugin-api'; +import { CheckpointOptions } from '../checkpoints'; + /** * ActionContext is passed into scaffolder actions. * @public @@ -36,10 +38,9 @@ export type ActionContext< secrets?: TaskSecrets; workspacePath: string; input: TActionInput; - checkpoint(opts: { - key: string; - fn: () => Promise | T; - }): Promise; + checkpoint( + opts: CheckpointOptions, + ): Promise; output( name: keyof TActionOutput, value: TActionOutput[keyof TActionOutput], diff --git a/plugins/scaffolder-node/src/checkpoints/index.ts b/plugins/scaffolder-node/src/checkpoints/index.ts new file mode 100644 index 0000000000..5d542de408 --- /dev/null +++ b/plugins/scaffolder-node/src/checkpoints/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export * from './types'; diff --git a/plugins/scaffolder-node/src/checkpoints/types.ts b/plugins/scaffolder-node/src/checkpoints/types.ts new file mode 100644 index 0000000000..eac3213075 --- /dev/null +++ b/plugins/scaffolder-node/src/checkpoints/types.ts @@ -0,0 +1,78 @@ +/* + * 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. + */ + +import { JsonValue } from '@backstage/types'; + +/** + * The status of a checkpoint, indicating whether it succeeded or failed. + * + * @public + */ +export type CheckpointStatus = 'failed' | 'success'; + +/** + * Represents a successful checkpoint state with a value. + * + * @public + */ +export type CheckpointSuccessState = { + status: Extract; + value: JsonValue; +}; + +/** + * Represents a failed checkpoint state with a reason for failure. + * + * @public + */ +export type CheckpointFailedState = { + status: Extract; + reason: string; +}; + +/** + * A map of checkpoint keys to their states. + * + * @public + */ +export type CheckpointState = { + [key: string]: CheckpointSuccessState | CheckpointFailedState; +}; + +/** + * Options for updating a checkpoint in a task. + * + * @public + */ +export type UpdateCheckpointOptions = { + key: string; +} & CheckpointState[keyof CheckpointState]; + +/** + * Options for checkpoint function invocation. + * + * @public + */ +export type CheckpointOptions = { + /** + * Unique key for the checkpoint + */ + key: string; + /** + * Function to execute for the checkpoint + */ + fn: () => Promise | T; +}; diff --git a/plugins/scaffolder-node/src/index.ts b/plugins/scaffolder-node/src/index.ts index 5d49ee874c..d44f0e9c50 100644 --- a/plugins/scaffolder-node/src/index.ts +++ b/plugins/scaffolder-node/src/index.ts @@ -24,3 +24,4 @@ export * from './actions'; export * from './tasks'; export * from './files'; export * from './types'; +export * from './checkpoints'; diff --git a/plugins/scaffolder-node/src/tasks/types.ts b/plugins/scaffolder-node/src/tasks/types.ts index d8cfd24530..cc9023db8b 100644 --- a/plugins/scaffolder-node/src/tasks/types.ts +++ b/plugins/scaffolder-node/src/tasks/types.ts @@ -16,7 +16,8 @@ import { BackstageCredentials } from '@backstage/backend-plugin-api'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; -import { JsonObject, JsonValue, Observable } from '@backstage/types'; +import { JsonObject, Observable } from '@backstage/types'; +import { UpdateCheckpointOptions } from '../checkpoints'; /** * TaskSecrets @@ -129,19 +130,7 @@ export interface TaskContext { | undefined >; - updateCheckpoint?( - options: - | { - key: string; - status: 'success'; - value: JsonValue; - } - | { - key: string; - status: 'failed'; - reason: string; - }, - ): Promise; + updateCheckpoint?(options: UpdateCheckpointOptions): Promise; serializeWorkspace?(options: { path: string }): Promise; From 3fb5a07af291645adee0502b4d155784dd934703 Mon Sep 17 00:00:00 2001 From: Kurt King Date: Fri, 21 Mar 2025 22:42:27 -0600 Subject: [PATCH 03/20] use CheckpointOptions where possible Signed-off-by: Kurt King --- .../src/scaffolder/tasks/NunjucksWorkflowRunner.ts | 8 ++++---- .../src/actions/mockActionContext.ts | 12 +++++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index d97bf10543..d6a420e54a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -50,6 +50,7 @@ import { import { createConditionAuthorizer } from '@backstage/plugin-permission-node'; import { actionExecutePermission } from '@backstage/plugin-scaffolder-common/alpha'; import { + CheckpointOptions, TaskContext, TemplateAction, TemplateFilter, @@ -375,10 +376,9 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { secrets: task.secrets ?? {}, logger: taskLogger, workspacePath, - async checkpoint(opts: { - key?: string; - fn: () => Promise | T; - }) { + async checkpoint( + opts: CheckpointOptions, + ) { const { key: checkpointKey, fn } = opts; const key = `v1.task.checkpoint.${step.id}.${checkpointKey}`; diff --git a/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts index a6703a12d1..5f8844d13d 100644 --- a/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts +++ b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts @@ -21,7 +21,10 @@ import { mockServices, } from '@backstage/backend-test-utils'; import { JsonObject, JsonValue } from '@backstage/types'; -import { ActionContext } from '@backstage/plugin-scaffolder-node'; +import { + ActionContext, + CheckpointOptions, +} from '@backstage/plugin-scaffolder-node'; import { loggerToWinstonLogger } from './loggerToWinstonLogger'; /** @@ -44,10 +47,9 @@ export function createMockActionContext< output: jest.fn(), createTemporaryDirectory: jest.fn(), input: {} as TActionInput, - async checkpoint(opts: { - key: string; - fn: () => Promise | T; - }): Promise { + async checkpoint( + opts: CheckpointOptions, + ): Promise { return opts.fn(); }, getInitiatorCredentials: () => Promise.resolve(credentials), From 70eae7fa1a784950dce885580fc88c0f836c22fd Mon Sep 17 00:00:00 2001 From: Kurt King Date: Fri, 21 Mar 2025 22:47:29 -0600 Subject: [PATCH 04/20] update api reports Signed-off-by: Kurt King --- plugins/scaffolder-backend/report.api.md | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/plugins/scaffolder-backend/report.api.md b/plugins/scaffolder-backend/report.api.md index 1f2a17a384..99be4a27f1 100644 --- a/plugins/scaffolder-backend/report.api.md +++ b/plugins/scaffolder-backend/report.api.md @@ -14,7 +14,6 @@ import { Duration } from 'luxon'; import { EventsService } from '@backstage/plugin-events-node'; import { HumanDuration } from '@backstage/types'; import { JsonObject } from '@backstage/types'; -import { JsonValue } from '@backstage/types'; import { Knex } from 'knex'; import { LoggerService } from '@backstage/backend-plugin-api'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; @@ -38,6 +37,7 @@ import { TemplateEntityStepV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TemplateFilter } from '@backstage/plugin-scaffolder-node'; import { TemplateGlobal } from '@backstage/plugin-scaffolder-node'; import { TemplateParametersV1beta3 } from '@backstage/plugin-scaffolder-common'; +import { UpdateCheckpointOptions } from '@backstage/plugin-scaffolder-node'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { WorkspaceProvider } from '@backstage/plugin-scaffolder-node/alpha'; @@ -442,19 +442,7 @@ export class TaskManager implements TaskContext { // (undocumented) get spec(): TaskSpecV1beta3; // (undocumented) - updateCheckpoint?( - options: - | { - key: string; - status: 'success'; - value: JsonValue; - } - | { - key: string; - status: 'failed'; - reason: string; - }, - ): Promise; + updateCheckpoint?(options: UpdateCheckpointOptions): Promise; } // @public @deprecated From dbde1805b60f704955dbdd78ecd128a7c7f71e56 Mon Sep 17 00:00:00 2001 From: Kurt King Date: Mon, 24 Mar 2025 19:41:31 -0600 Subject: [PATCH 05/20] clean up types some Signed-off-by: Kurt King --- .changeset/petite-paths-remain.md | 7 +++ plugins/scaffolder-backend/report.api.md | 4 +- .../tasks/NunjucksWorkflowRunner.ts | 4 +- .../src/scaffolder/tasks/StorageTaskBroker.ts | 63 ++----------------- .../src/actions/mockActionContext.ts | 4 +- plugins/scaffolder-node/src/actions/types.ts | 4 +- .../scaffolder-node/src/checkpoints/types.ts | 35 +++++------ plugins/scaffolder-node/src/tasks/index.ts | 1 + plugins/scaffolder-node/src/tasks/types.ts | 13 +++- 9 files changed, 50 insertions(+), 85 deletions(-) create mode 100644 .changeset/petite-paths-remain.md diff --git a/.changeset/petite-paths-remain.md b/.changeset/petite-paths-remain.md new file mode 100644 index 0000000000..a2bef50c80 --- /dev/null +++ b/.changeset/petite-paths-remain.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-scaffolder-node-test-utils': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-scaffolder-node': patch +--- + +An internal refactor which adds additional types to experimental checkpoints diff --git a/plugins/scaffolder-backend/report.api.md b/plugins/scaffolder-backend/report.api.md index 99be4a27f1..3823db15d6 100644 --- a/plugins/scaffolder-backend/report.api.md +++ b/plugins/scaffolder-backend/report.api.md @@ -37,7 +37,7 @@ import { TemplateEntityStepV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TemplateFilter } from '@backstage/plugin-scaffolder-node'; import { TemplateGlobal } from '@backstage/plugin-scaffolder-node'; import { TemplateParametersV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { UpdateCheckpointOptions } from '@backstage/plugin-scaffolder-node'; +import { UpdateTaskCheckpointOptions } from '@backstage/plugin-scaffolder-node'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { WorkspaceProvider } from '@backstage/plugin-scaffolder-node/alpha'; @@ -442,7 +442,7 @@ export class TaskManager implements TaskContext { // (undocumented) get spec(): TaskSpecV1beta3; // (undocumented) - updateCheckpoint?(options: UpdateCheckpointOptions): Promise; + updateCheckpoint?(options: UpdateTaskCheckpointOptions): Promise; } // @public @deprecated diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index d6a420e54a..adc5152e4f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -50,7 +50,7 @@ import { import { createConditionAuthorizer } from '@backstage/plugin-permission-node'; import { actionExecutePermission } from '@backstage/plugin-scaffolder-common/alpha'; import { - CheckpointOptions, + CheckpointContext, TaskContext, TemplateAction, TemplateFilter, @@ -377,7 +377,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { logger: taskLogger, workspacePath, async checkpoint( - opts: CheckpointOptions, + opts: CheckpointContext, ) { const { key: checkpointKey, fn } = opts; const key = `v1.task.checkpoint.${step.id}.${checkpointKey}`; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 9a872e7b3f..5e90a11d83 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -23,6 +23,7 @@ import { import { Config } from '@backstage/config'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { + CheckpointState, SerializedTask, SerializedTaskEvent, TaskBroker, @@ -31,6 +32,7 @@ import { TaskContext, TaskSecrets, TaskStatus, + UpdateTaskCheckpointOptions, } from '@backstage/plugin-scaffolder-node'; import { WorkspaceProvider } from '@backstage/plugin-scaffolder-node/alpha'; import { JsonObject, Observable, createDeferred } from '@backstage/types'; @@ -38,17 +40,10 @@ import ObservableImpl from 'zen-observable'; import { DefaultWorkspaceService, WorkspaceService } from './WorkspaceService'; import { readDuration } from './helper'; import { InternalTaskSecrets, TaskStore } from './types'; -import { - CheckpointState, - CheckpointSuccessState, - CheckpointFailedState, - UpdateCheckpointOptions, -} from '@backstage/plugin-scaffolder-node'; type TaskState = { checkpoints: CheckpointState; }; - /** * TaskManager * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. @@ -144,57 +139,14 @@ export class TaskManager implements TaskContext { return this.storage.getTaskState?.({ taskId: this.task.taskId }); } - /** - * Helper to safely access the checkpoints field from task state - * Ensures type safety when working with task state that might not match our structure - */ - private getCheckpointsFromState(state?: JsonObject): CheckpointState { - if ( - state && - 'checkpoints' in state && - typeof state.checkpoints === 'object' - ) { - return state.checkpoints as CheckpointState; - } - return {}; - } - - async updateCheckpoint?(options: UpdateCheckpointOptions): Promise { - const { key, status } = options; - - // Extract appropriate state value based on status - let checkpointValue: CheckpointSuccessState | CheckpointFailedState; - - switch (status) { - case 'success': { - const { value } = options; - checkpointValue = { status, value }; - break; - } - case 'failed': { - const { reason } = options; - checkpointValue = { status, reason }; - break; - } - default: { - // Using status as 'never' gives compile-time guarantee we've handled all cases - const exhaustiveCheck: never = status; - throw new Error(`Unexpected status: ${exhaustiveCheck}`); - } - } + async updateCheckpoint?(options: UpdateTaskCheckpointOptions): Promise { + const { key, ...value } = options; if (this.task.state) { - const taskState: TaskState = { - checkpoints: this.getCheckpointsFromState(this.task.state), - }; - - // Update with the new checkpoint - taskState.checkpoints[key] = checkpointValue; - this.task.state = taskState; + (this.task.state as TaskState).checkpoints[key] = value; } else { - this.task.state = { checkpoints: { [key]: checkpointValue } }; + this.task.state = { checkpoints: { [key]: value } }; } - await this.storage.saveTaskState?.({ taskId: this.task.taskId, state: this.task.state, @@ -278,9 +230,6 @@ export interface CurrentClaimedTask { secrets?: TaskSecrets; /** * The state of checkpoints of the task. - * This will be a JsonObject that may contain a `checkpoints` field - * with a structure matching the CheckpointState interface. - * @see CheckpointState */ state?: JsonObject; /** diff --git a/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts index 5f8844d13d..d364d4dc1e 100644 --- a/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts +++ b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts @@ -23,7 +23,7 @@ import { import { JsonObject, JsonValue } from '@backstage/types'; import { ActionContext, - CheckpointOptions, + CheckpointContext, } from '@backstage/plugin-scaffolder-node'; import { loggerToWinstonLogger } from './loggerToWinstonLogger'; @@ -48,7 +48,7 @@ export function createMockActionContext< createTemporaryDirectory: jest.fn(), input: {} as TActionInput, async checkpoint( - opts: CheckpointOptions, + opts: CheckpointContext, ): Promise { return opts.fn(); }, diff --git a/plugins/scaffolder-node/src/actions/types.ts b/plugins/scaffolder-node/src/actions/types.ts index e7e678450d..8104470dd8 100644 --- a/plugins/scaffolder-node/src/actions/types.ts +++ b/plugins/scaffolder-node/src/actions/types.ts @@ -23,7 +23,7 @@ import { BackstageCredentials, LoggerService, } from '@backstage/backend-plugin-api'; -import { CheckpointOptions } from '../checkpoints'; +import { CheckpointContext } from '../checkpoints'; /** * ActionContext is passed into scaffolder actions. @@ -39,7 +39,7 @@ export type ActionContext< workspacePath: string; input: TActionInput; checkpoint( - opts: CheckpointOptions, + opts: CheckpointContext, ): Promise; output( name: keyof TActionOutput, diff --git a/plugins/scaffolder-node/src/checkpoints/types.ts b/plugins/scaffolder-node/src/checkpoints/types.ts index eac3213075..eecf769f5a 100644 --- a/plugins/scaffolder-node/src/checkpoints/types.ts +++ b/plugins/scaffolder-node/src/checkpoints/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import { JsonValue } from '@backstage/types'; /** @@ -28,9 +27,9 @@ export type CheckpointStatus = 'failed' | 'success'; * * @public */ -export type CheckpointSuccessState = { - status: Extract; - value: JsonValue; +export type CheckpointSuccessState = { + status: 'success'; + value: T; }; /** @@ -39,34 +38,34 @@ export type CheckpointSuccessState = { * @public */ export type CheckpointFailedState = { - status: Extract; + status: 'failed'; reason: string; }; +/** + * Represents the union of all possible checkpoint state values. + * + * @public + */ +export type CheckpointStateValue = + | CheckpointSuccessState + | CheckpointFailedState; + /** * A map of checkpoint keys to their states. * * @public */ export type CheckpointState = { - [key: string]: CheckpointSuccessState | CheckpointFailedState; + [key: string]: CheckpointStateValue; }; /** - * Options for updating a checkpoint in a task. + * Context for checkpoint function invocation. * * @public */ -export type UpdateCheckpointOptions = { - key: string; -} & CheckpointState[keyof CheckpointState]; - -/** - * Options for checkpoint function invocation. - * - * @public - */ -export type CheckpointOptions = { +export type CheckpointContext = { /** * Unique key for the checkpoint */ diff --git a/plugins/scaffolder-node/src/tasks/index.ts b/plugins/scaffolder-node/src/tasks/index.ts index 930de95237..e047131d2b 100644 --- a/plugins/scaffolder-node/src/tasks/index.ts +++ b/plugins/scaffolder-node/src/tasks/index.ts @@ -25,4 +25,5 @@ export type { TaskContext, TaskEventType, TaskStatus, + UpdateTaskCheckpointOptions, } from './types'; diff --git a/plugins/scaffolder-node/src/tasks/types.ts b/plugins/scaffolder-node/src/tasks/types.ts index cc9023db8b..e3b64fea15 100644 --- a/plugins/scaffolder-node/src/tasks/types.ts +++ b/plugins/scaffolder-node/src/tasks/types.ts @@ -17,7 +17,7 @@ import { BackstageCredentials } from '@backstage/backend-plugin-api'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { JsonObject, Observable } from '@backstage/types'; -import { UpdateCheckpointOptions } from '../checkpoints'; +import { CheckpointStateValue } from '../checkpoints'; /** * TaskSecrets @@ -105,6 +105,15 @@ export type TaskBrokerDispatchOptions = { createdBy?: string; }; +/** + * Options for updating a checkpoint in a task. + * + * @public + */ +export type UpdateTaskCheckpointOptions = { + key: string; +} & CheckpointStateValue; + /** * Task * @@ -130,7 +139,7 @@ export interface TaskContext { | undefined >; - updateCheckpoint?(options: UpdateCheckpointOptions): Promise; + updateCheckpoint?(options: UpdateTaskCheckpointOptions): Promise; serializeWorkspace?(options: { path: string }): Promise; From 04106ae76e554fe7f4bae047860c260a10ab1540 Mon Sep 17 00:00:00 2001 From: Kurt King Date: Sat, 10 May 2025 12:07:47 -0600 Subject: [PATCH 06/20] refactor to alpha exports Signed-off-by: Kurt King --- .../src/scaffolder/tasks/NunjucksWorkflowRunner.ts | 6 ++++-- .../src/scaffolder/tasks/StorageTaskBroker.ts | 8 +++++--- .../src/actions/mockActionContext.ts | 6 ++---- plugins/scaffolder-node/src/actions/types.ts | 2 +- .../src/{ => alpha}/checkpoints/index.ts | 3 +-- .../src/{ => alpha}/checkpoints/types.ts | 12 ++++++------ plugins/scaffolder-node/src/alpha/index.ts | 1 + plugins/scaffolder-node/src/index.ts | 1 - plugins/scaffolder-node/src/tasks/alpha.ts | 11 +++++++++++ plugins/scaffolder-node/src/tasks/index.ts | 2 +- plugins/scaffolder-node/src/tasks/types.ts | 11 +---------- 11 files changed, 33 insertions(+), 30 deletions(-) rename plugins/scaffolder-node/src/{ => alpha}/checkpoints/index.ts (93%) rename plugins/scaffolder-node/src/{ => alpha}/checkpoints/types.ts (96%) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index adc5152e4f..1542db8438 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -50,7 +50,6 @@ import { import { createConditionAuthorizer } from '@backstage/plugin-permission-node'; import { actionExecutePermission } from '@backstage/plugin-scaffolder-common/alpha'; import { - CheckpointContext, TaskContext, TemplateAction, TemplateFilter, @@ -61,7 +60,10 @@ import { scaffolderActionRules } from '../../service/rules'; import { createCounterMetric, createHistogramMetric } from '../../util/metrics'; import { BackstageLoggerTransport, WinstonLogger } from './logger'; import { convertFiltersToRecord } from '../../util/templating'; -import { CheckpointState } from '@backstage/plugin-scaffolder-node'; +import { + CheckpointState, + CheckpointContext, +} from '@backstage/plugin-scaffolder-node/alpha'; type NunjucksWorkflowRunnerOptions = { workingDirectory: string; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 5e90a11d83..c88a1f687c 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -23,7 +23,6 @@ import { import { Config } from '@backstage/config'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { - CheckpointState, SerializedTask, SerializedTaskEvent, TaskBroker, @@ -32,9 +31,12 @@ import { TaskContext, TaskSecrets, TaskStatus, - UpdateTaskCheckpointOptions, } from '@backstage/plugin-scaffolder-node'; -import { WorkspaceProvider } from '@backstage/plugin-scaffolder-node/alpha'; +import { + CheckpointState, + WorkspaceProvider, + UpdateTaskCheckpointOptions, +} from '@backstage/plugin-scaffolder-node/alpha'; import { JsonObject, Observable, createDeferred } from '@backstage/types'; import ObservableImpl from 'zen-observable'; import { DefaultWorkspaceService, WorkspaceService } from './WorkspaceService'; diff --git a/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts index d364d4dc1e..e2f07f2320 100644 --- a/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts +++ b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts @@ -21,10 +21,8 @@ import { mockServices, } from '@backstage/backend-test-utils'; import { JsonObject, JsonValue } from '@backstage/types'; -import { - ActionContext, - CheckpointContext, -} from '@backstage/plugin-scaffolder-node'; +import { ActionContext } from '@backstage/plugin-scaffolder-node'; +import { CheckpointContext } from '@backstage/plugin-scaffolder-node/alpha'; import { loggerToWinstonLogger } from './loggerToWinstonLogger'; /** diff --git a/plugins/scaffolder-node/src/actions/types.ts b/plugins/scaffolder-node/src/actions/types.ts index 8104470dd8..32ab12657c 100644 --- a/plugins/scaffolder-node/src/actions/types.ts +++ b/plugins/scaffolder-node/src/actions/types.ts @@ -23,7 +23,7 @@ import { BackstageCredentials, LoggerService, } from '@backstage/backend-plugin-api'; -import { CheckpointContext } from '../checkpoints'; +import { CheckpointContext } from '../alpha'; /** * ActionContext is passed into scaffolder actions. diff --git a/plugins/scaffolder-node/src/checkpoints/index.ts b/plugins/scaffolder-node/src/alpha/checkpoints/index.ts similarity index 93% rename from plugins/scaffolder-node/src/checkpoints/index.ts rename to plugins/scaffolder-node/src/alpha/checkpoints/index.ts index 5d542de408..16c936b235 100644 --- a/plugins/scaffolder-node/src/checkpoints/index.ts +++ b/plugins/scaffolder-node/src/alpha/checkpoints/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. @@ -13,5 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - export * from './types'; diff --git a/plugins/scaffolder-node/src/checkpoints/types.ts b/plugins/scaffolder-node/src/alpha/checkpoints/types.ts similarity index 96% rename from plugins/scaffolder-node/src/checkpoints/types.ts rename to plugins/scaffolder-node/src/alpha/checkpoints/types.ts index eecf769f5a..45f1c03430 100644 --- a/plugins/scaffolder-node/src/checkpoints/types.ts +++ b/plugins/scaffolder-node/src/alpha/checkpoints/types.ts @@ -18,14 +18,14 @@ import { JsonValue } from '@backstage/types'; /** * The status of a checkpoint, indicating whether it succeeded or failed. * - * @public + * @alpha */ export type CheckpointStatus = 'failed' | 'success'; /** * Represents a successful checkpoint state with a value. * - * @public + * @alpha */ export type CheckpointSuccessState = { status: 'success'; @@ -35,7 +35,7 @@ export type CheckpointSuccessState = { /** * Represents a failed checkpoint state with a reason for failure. * - * @public + * @alpha */ export type CheckpointFailedState = { status: 'failed'; @@ -45,7 +45,7 @@ export type CheckpointFailedState = { /** * Represents the union of all possible checkpoint state values. * - * @public + * @alpha */ export type CheckpointStateValue = | CheckpointSuccessState @@ -54,7 +54,7 @@ export type CheckpointStateValue = /** * A map of checkpoint keys to their states. * - * @public + * @alpha */ export type CheckpointState = { [key: string]: CheckpointStateValue; @@ -63,7 +63,7 @@ export type CheckpointState = { /** * Context for checkpoint function invocation. * - * @public + * @alpha */ export type CheckpointContext = { /** diff --git a/plugins/scaffolder-node/src/alpha/index.ts b/plugins/scaffolder-node/src/alpha/index.ts index 9df968a52d..30f95f116a 100644 --- a/plugins/scaffolder-node/src/alpha/index.ts +++ b/plugins/scaffolder-node/src/alpha/index.ts @@ -28,6 +28,7 @@ export * from '../tasks/alpha'; export * from './filters'; export * from './globals'; export * from './types'; +export * from './checkpoints'; /** * Extension point for managing scaffolder actions. diff --git a/plugins/scaffolder-node/src/index.ts b/plugins/scaffolder-node/src/index.ts index d44f0e9c50..5d49ee874c 100644 --- a/plugins/scaffolder-node/src/index.ts +++ b/plugins/scaffolder-node/src/index.ts @@ -24,4 +24,3 @@ export * from './actions'; export * from './tasks'; export * from './files'; export * from './types'; -export * from './checkpoints'; diff --git a/plugins/scaffolder-node/src/tasks/alpha.ts b/plugins/scaffolder-node/src/tasks/alpha.ts index fb6b09026c..4354a798cf 100644 --- a/plugins/scaffolder-node/src/tasks/alpha.ts +++ b/plugins/scaffolder-node/src/tasks/alpha.ts @@ -1,3 +1,5 @@ +import { CheckpointStateValue } from '../alpha'; + /* * Copyright 2024 The Backstage Authors * @@ -14,3 +16,12 @@ * limitations under the License. */ export * from './serializer'; + +/** + * Options for updating a checkpoint in a task. + * + * @alpha + */ +export type UpdateTaskCheckpointOptions = { + key: string; +} & CheckpointStateValue; diff --git a/plugins/scaffolder-node/src/tasks/index.ts b/plugins/scaffolder-node/src/tasks/index.ts index e047131d2b..2ff31857f5 100644 --- a/plugins/scaffolder-node/src/tasks/index.ts +++ b/plugins/scaffolder-node/src/tasks/index.ts @@ -25,5 +25,5 @@ export type { TaskContext, TaskEventType, TaskStatus, - UpdateTaskCheckpointOptions, + // UpdateTaskCheckpointOptions, } from './types'; diff --git a/plugins/scaffolder-node/src/tasks/types.ts b/plugins/scaffolder-node/src/tasks/types.ts index e3b64fea15..5621aa117b 100644 --- a/plugins/scaffolder-node/src/tasks/types.ts +++ b/plugins/scaffolder-node/src/tasks/types.ts @@ -17,7 +17,7 @@ import { BackstageCredentials } from '@backstage/backend-plugin-api'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { JsonObject, Observable } from '@backstage/types'; -import { CheckpointStateValue } from '../checkpoints'; +import { UpdateTaskCheckpointOptions } from '../alpha'; /** * TaskSecrets @@ -105,15 +105,6 @@ export type TaskBrokerDispatchOptions = { createdBy?: string; }; -/** - * Options for updating a checkpoint in a task. - * - * @public - */ -export type UpdateTaskCheckpointOptions = { - key: string; -} & CheckpointStateValue; - /** * Task * From 77f713137e4c70a0a208df1173f9bb366eaf89db Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Mon, 16 Jun 2025 11:41:51 +0200 Subject: [PATCH 07/20] chore: fixing issues with typescript Signed-off-by: benjdlambert --- plugins/scaffolder-backend/report.api.md | 2 +- plugins/scaffolder-node/report-alpha.api.md | 36 ++++++++++++++++++++ plugins/scaffolder-node/report.api.md | 23 ++++--------- plugins/scaffolder-node/src/actions/types.ts | 2 +- plugins/scaffolder-node/src/tasks/types.ts | 2 +- 5 files changed, 45 insertions(+), 20 deletions(-) diff --git a/plugins/scaffolder-backend/report.api.md b/plugins/scaffolder-backend/report.api.md index 3823db15d6..7ece0f84ea 100644 --- a/plugins/scaffolder-backend/report.api.md +++ b/plugins/scaffolder-backend/report.api.md @@ -37,7 +37,7 @@ import { TemplateEntityStepV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TemplateFilter } from '@backstage/plugin-scaffolder-node'; import { TemplateGlobal } from '@backstage/plugin-scaffolder-node'; import { TemplateParametersV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { UpdateTaskCheckpointOptions } from '@backstage/plugin-scaffolder-node'; +import { UpdateTaskCheckpointOptions } from '@backstage/plugin-scaffolder-node/alpha'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { WorkspaceProvider } from '@backstage/plugin-scaffolder-node/alpha'; diff --git a/plugins/scaffolder-node/report-alpha.api.md b/plugins/scaffolder-node/report-alpha.api.md index a96f312d2d..c3e8c7f71b 100644 --- a/plugins/scaffolder-node/report-alpha.api.md +++ b/plugins/scaffolder-node/report-alpha.api.md @@ -27,6 +27,37 @@ export type AutocompleteHandler = ({ }[]; }>; +// @alpha +export type CheckpointContext = { + key: string; + fn: () => Promise | T; +}; + +// @alpha +export type CheckpointFailedState = { + status: 'failed'; + reason: string; +}; + +// @alpha +export type CheckpointState = { + [key: string]: CheckpointStateValue; +}; + +// @alpha +export type CheckpointStateValue = + | CheckpointSuccessState + | CheckpointFailedState; + +// @alpha +export type CheckpointStatus = 'failed' | 'success'; + +// @alpha +export type CheckpointSuccessState = { + status: 'success'; + value: T; +}; + // @alpha (undocumented) export type CreatedTemplateFilter< TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], @@ -187,6 +218,11 @@ export type TemplateGlobalFunctionExample = { notes?: string; }; +// @alpha +export type UpdateTaskCheckpointOptions = { + key: string; +} & CheckpointStateValue; + // @alpha export interface WorkspaceProvider { // (undocumented) diff --git a/plugins/scaffolder-node/report.api.md b/plugins/scaffolder-node/report.api.md index a23a9137b1..bceb9980b6 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 { CheckpointContext } from '@backstage/plugin-scaffolder-node/alpha'; import { Expand } from '@backstage/types'; import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; @@ -15,6 +16,7 @@ import { ScmIntegrations } from '@backstage/integration'; import { SpawnOptionsWithoutStdio } from 'child_process'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { TemplateInfo } from '@backstage/plugin-scaffolder-common'; +import { UpdateTaskCheckpointOptions } from '@backstage/plugin-scaffolder-node/alpha'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { UserEntity } from '@backstage/catalog-model'; import { Writable } from 'stream'; @@ -30,10 +32,9 @@ export type ActionContext< secrets?: TaskSecrets; workspacePath: string; input: TActionInput; - checkpoint(opts: { - key: string; - fn: () => Promise | T; - }): Promise; + checkpoint( + opts: CheckpointContext, + ): Promise; output( name: keyof TActionOutput, value: TActionOutput[keyof TActionOutput], @@ -446,19 +447,7 @@ export interface TaskContext { // (undocumented) taskId?: string; // (undocumented) - updateCheckpoint?( - options: - | { - key: string; - status: 'success'; - value: JsonValue; - } - | { - key: string; - status: 'failed'; - reason: string; - }, - ): Promise; + updateCheckpoint?(options: UpdateTaskCheckpointOptions): Promise; } // @public diff --git a/plugins/scaffolder-node/src/actions/types.ts b/plugins/scaffolder-node/src/actions/types.ts index 32ab12657c..0162d96d95 100644 --- a/plugins/scaffolder-node/src/actions/types.ts +++ b/plugins/scaffolder-node/src/actions/types.ts @@ -23,7 +23,7 @@ import { BackstageCredentials, LoggerService, } from '@backstage/backend-plugin-api'; -import { CheckpointContext } from '../alpha'; +import { CheckpointContext } from '@backstage/plugin-scaffolder-node/alpha'; /** * ActionContext is passed into scaffolder actions. diff --git a/plugins/scaffolder-node/src/tasks/types.ts b/plugins/scaffolder-node/src/tasks/types.ts index 5621aa117b..d2403f205a 100644 --- a/plugins/scaffolder-node/src/tasks/types.ts +++ b/plugins/scaffolder-node/src/tasks/types.ts @@ -17,7 +17,7 @@ import { BackstageCredentials } from '@backstage/backend-plugin-api'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { JsonObject, Observable } from '@backstage/types'; -import { UpdateTaskCheckpointOptions } from '../alpha'; +import { UpdateTaskCheckpointOptions } from '@backstage/plugin-scaffolder-node/alpha'; /** * TaskSecrets From f63877526a6167acf4cff55f4f26f420720dbe1d Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Mon, 16 Jun 2025 13:35:55 +0200 Subject: [PATCH 08/20] chore: cleanup the types a little bit Signed-off-by: benjdlambert Signed-off-by: benjdlambert --- plugins/scaffolder-node/report-alpha.api.md | 24 +++++++---------- .../src/alpha/checkpoints/types.ts | 26 +++---------------- plugins/scaffolder-node/src/tasks/index.ts | 1 - 3 files changed, 12 insertions(+), 39 deletions(-) diff --git a/plugins/scaffolder-node/report-alpha.api.md b/plugins/scaffolder-node/report-alpha.api.md index c3e8c7f71b..09541fbc56 100644 --- a/plugins/scaffolder-node/report-alpha.api.md +++ b/plugins/scaffolder-node/report-alpha.api.md @@ -33,31 +33,25 @@ export type CheckpointContext = { fn: () => Promise | T; }; -// @alpha -export type CheckpointFailedState = { - status: 'failed'; - reason: string; -}; - // @alpha export type CheckpointState = { [key: string]: CheckpointStateValue; }; // @alpha -export type CheckpointStateValue = - | CheckpointSuccessState - | CheckpointFailedState; +export type CheckpointStateValue = + | { + status: 'failed'; + reason: string; + } + | { + status: 'success'; + value: T; + }; // @alpha export type CheckpointStatus = 'failed' | 'success'; -// @alpha -export type CheckpointSuccessState = { - status: 'success'; - value: T; -}; - // @alpha (undocumented) export type CreatedTemplateFilter< TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], diff --git a/plugins/scaffolder-node/src/alpha/checkpoints/types.ts b/plugins/scaffolder-node/src/alpha/checkpoints/types.ts index 45f1c03430..5bd1eb5cd3 100644 --- a/plugins/scaffolder-node/src/alpha/checkpoints/types.ts +++ b/plugins/scaffolder-node/src/alpha/checkpoints/types.ts @@ -22,34 +22,14 @@ import { JsonValue } from '@backstage/types'; */ export type CheckpointStatus = 'failed' | 'success'; -/** - * Represents a successful checkpoint state with a value. - * - * @alpha - */ -export type CheckpointSuccessState = { - status: 'success'; - value: T; -}; - -/** - * Represents a failed checkpoint state with a reason for failure. - * - * @alpha - */ -export type CheckpointFailedState = { - status: 'failed'; - reason: string; -}; - /** * Represents the union of all possible checkpoint state values. * * @alpha */ -export type CheckpointStateValue = - | CheckpointSuccessState - | CheckpointFailedState; +export type CheckpointStateValue = + | { status: 'failed'; reason: string } + | { status: 'success'; value: T }; /** * A map of checkpoint keys to their states. diff --git a/plugins/scaffolder-node/src/tasks/index.ts b/plugins/scaffolder-node/src/tasks/index.ts index 2ff31857f5..930de95237 100644 --- a/plugins/scaffolder-node/src/tasks/index.ts +++ b/plugins/scaffolder-node/src/tasks/index.ts @@ -25,5 +25,4 @@ export type { TaskContext, TaskEventType, TaskStatus, - // UpdateTaskCheckpointOptions, } from './types'; From 1a0fb41021e73bf2b4b0c6487e76887d12b15c59 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 14:03:17 +0000 Subject: [PATCH 09/20] fix(deps): update dependency docusaurus-pushfeedback to v1.0.5 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- microsite/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/microsite/yarn.lock b/microsite/yarn.lock index a200d0f9da..40a000df33 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -5942,11 +5942,11 @@ __metadata: linkType: hard "docusaurus-pushfeedback@npm:^1.0.0": - version: 1.0.3 - resolution: "docusaurus-pushfeedback@npm:1.0.3" + version: 1.0.5 + resolution: "docusaurus-pushfeedback@npm:1.0.5" peerDependencies: "@docusaurus/core": 3.x - checksum: 10/ee80ae0c1fc079b2c317cab86d83ba50cce18938a6ce0ac647aad25dc68fc0ad659e55c11e93d0e7c23270c503c3cb300111dfbdd04785ef7aaf6fb123b57eae + checksum: 10/5323af7f1c7b4590744ea9099cacb49087d98c48c11e2ed957ed744d6210a3d82fa0cc42ef0f011f25b0d08c2d0040065c8a2b641c7c9160532ceb51ad41475c languageName: node linkType: hard From ead925a8a2f6d2a1747487d35b1d37c2a65ac2f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 3 Jun 2025 17:51:37 +0200 Subject: [PATCH 10/20] Add a standard 'toString' on credentials objects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/gentle-llamas-press.md | 6 ++ .../src/entrypoints/auth/helpers.test.ts | 47 ++++++++++ .../src/entrypoints/auth/helpers.ts | 93 ++++++++++++------- .../src/services/mockCredentials.test.ts | 23 +++++ .../src/services/mockCredentials.ts | 66 +++++++++---- 5 files changed, 186 insertions(+), 49 deletions(-) create mode 100644 .changeset/gentle-llamas-press.md diff --git a/.changeset/gentle-llamas-press.md b/.changeset/gentle-llamas-press.md new file mode 100644 index 0000000000..9c922d396f --- /dev/null +++ b/.changeset/gentle-llamas-press.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-defaults': patch +'@backstage/backend-test-utils': minor +--- + +Add a standard `toString` on credentials objects diff --git a/packages/backend-defaults/src/entrypoints/auth/helpers.test.ts b/packages/backend-defaults/src/entrypoints/auth/helpers.test.ts index 8454aa3ecc..f54062efba 100644 --- a/packages/backend-defaults/src/entrypoints/auth/helpers.test.ts +++ b/packages/backend-defaults/src/entrypoints/auth/helpers.test.ts @@ -42,6 +42,23 @@ describe('credentials', () => { }, }); + expect( + createCredentialsWithUserPrincipal( + 'user:default/mock', + 'my-token', + undefined, + 'my-actor', + ), + ).toEqual({ + $$type: '@backstage/BackstageCredentials', + version: 'v1', + principal: { + type: 'user', + userEntityRef: 'user:default/mock', + actor: { type: 'service', subject: 'my-actor' }, + }, + }); + expect(createCredentialsWithNonePrincipal()).toEqual({ $$type: '@backstage/BackstageCredentials', version: 'v1', @@ -64,4 +81,34 @@ describe('credentials', () => { ), ).not.toMatch(/my-token/); }); + + it('should have a serializable form', () => { + expect( + String(createCredentialsWithServicePrincipal('my-service')), + ).toMatchInlineSnapshot( + `"{"$$type":"@backstage/BackstageCredentials","type":"service","subject":"my-service"}"`, + ); + expect( + String( + createCredentialsWithUserPrincipal('user:default/mock', 'my-token'), + ), + ).toMatchInlineSnapshot( + `"{"$$type":"@backstage/BackstageCredentials","type":"user","userEntityRef":"user:default/mock"}"`, + ); + expect( + String( + createCredentialsWithUserPrincipal( + 'user:default/mock', + 'my-token', + undefined, + 'my-actor', + ), + ), + ).toMatchInlineSnapshot( + `"{"$$type":"@backstage/BackstageCredentials","type":"user","userEntityRef":"user:default/mock","actor":{"type":"service","subject":"my-actor"}}"`, + ); + expect(String(createCredentialsWithNonePrincipal())).toMatchInlineSnapshot( + `"{"$$type":"@backstage/BackstageCredentials","type":"none"}"`, + ); + }); }); diff --git a/packages/backend-defaults/src/entrypoints/auth/helpers.ts b/packages/backend-defaults/src/entrypoints/auth/helpers.ts index 58e2bcff74..0490a1c5fc 100644 --- a/packages/backend-defaults/src/entrypoints/auth/helpers.ts +++ b/packages/backend-defaults/src/entrypoints/auth/helpers.ts @@ -28,23 +28,31 @@ export function createCredentialsWithServicePrincipal( token?: string, accessRestrictions?: BackstagePrincipalAccessRestrictions, ): InternalBackstageCredentials { - return Object.defineProperty( - { - $$type: '@backstage/BackstageCredentials', - version: 'v1', - principal: { + const result = { + $$type: '@backstage/BackstageCredentials', + version: 'v1', + principal: { + type: 'service', + subject: sub, + accessRestrictions, + }, + } as const; + Object.defineProperty(result, 'token', { + enumerable: false, + configurable: true, + value: token, + }); + Object.defineProperty(result, 'toString', { + enumerable: false, + configurable: true, + value: () => + JSON.stringify({ + $$type: '@backstage/BackstageCredentials', type: 'service', subject: sub, - accessRestrictions, - }, - }, - 'token', - { - enumerable: false, - configurable: true, - value: token, - }, - ); + }), + }); + return result; } export function createCredentialsWithUserPrincipal( @@ -53,36 +61,57 @@ export function createCredentialsWithUserPrincipal( expiresAt?: Date, actor?: string, ): InternalBackstageCredentials { - return Object.defineProperty( - { - $$type: '@backstage/BackstageCredentials', - version: 'v1', - expiresAt, - principal: { + const result = { + $$type: '@backstage/BackstageCredentials', + version: 'v1', + expiresAt, + principal: { + type: 'user', + userEntityRef: sub, + ...(actor && { + actor: { type: 'service', subject: actor } as const, + }), + }, + } as const; + Object.defineProperty(result, 'token', { + enumerable: false, + configurable: true, + value: token, + }); + Object.defineProperty(result, 'toString', { + enumerable: false, + configurable: true, + value: () => + JSON.stringify({ + $$type: '@backstage/BackstageCredentials', type: 'user', userEntityRef: sub, ...(actor && { actor: { type: 'service', subject: actor }, }), - }, - }, - 'token', - { - enumerable: false, - configurable: true, - value: token, - }, - ); + }), + }); + return result; } export function createCredentialsWithNonePrincipal(): InternalBackstageCredentials { - return { + const result = { $$type: '@backstage/BackstageCredentials', version: 'v1', principal: { type: 'none', }, - }; + } as const; + Object.defineProperty(result, 'toString', { + enumerable: false, + configurable: true, + value: () => + JSON.stringify({ + $$type: '@backstage/BackstageCredentials', + type: 'none', + }), + }); + return result; } export function toInternalBackstageCredentials( diff --git a/packages/backend-test-utils/src/services/mockCredentials.test.ts b/packages/backend-test-utils/src/services/mockCredentials.test.ts index 343d6d6d20..f81ec1909c 100644 --- a/packages/backend-test-utils/src/services/mockCredentials.test.ts +++ b/packages/backend-test-utils/src/services/mockCredentials.test.ts @@ -170,4 +170,27 @@ describe('mockCredentials', () => { "Invalid user entity reference 'wrong', expected :/", ); }); + + it('should have a serializable form', () => { + expect(String(mockCredentials.service('my-service'))).toMatchInlineSnapshot( + `"{"$$type":"@backstage/MockBackstageCredentials","type":"service","subject":"my-service"}"`, + ); + expect( + String(mockCredentials.user('user:default/mock')), + ).toMatchInlineSnapshot( + `"{"$$type":"@backstage/MockBackstageCredentials","type":"user","userEntityRef":"user:default/mock"}"`, + ); + expect( + String( + mockCredentials.user('user:default/mock', { + actor: { subject: 'my-actor' }, + }), + ), + ).toMatchInlineSnapshot( + `"{"$$type":"@backstage/MockBackstageCredentials","type":"user","userEntityRef":"user:default/mock","actor":{"type":"service","subject":"my-actor"}}"`, + ); + expect(String(mockCredentials.none())).toMatchInlineSnapshot( + `"{"$$type":"@backstage/MockBackstageCredentials","type":"none"}"`, + ); + }); }); diff --git a/packages/backend-test-utils/src/services/mockCredentials.ts b/packages/backend-test-utils/src/services/mockCredentials.ts index 617ffdb240..6ea57737d7 100644 --- a/packages/backend-test-utils/src/services/mockCredentials.ts +++ b/packages/backend-test-utils/src/services/mockCredentials.ts @@ -76,10 +76,20 @@ export namespace mockCredentials { * Creates a mocked credentials object for a unauthenticated principal. */ export function none(): BackstageCredentials { - return { + const result = { $$type: '@backstage/BackstageCredentials', principal: { type: 'none' }, - }; + } as const; + Object.defineProperty(result, 'toString', { + enumerable: false, + configurable: true, + value: () => + JSON.stringify({ + $$type: '@backstage/MockBackstageCredentials', + type: 'none', + }), + }); + return result; } /** @@ -111,24 +121,35 @@ export namespace mockCredentials { options?: { actor?: { subject: string } }, ): BackstageCredentials { validateUserEntityRef(userEntityRef); - return Object.defineProperty( - { - $$type: '@backstage/BackstageCredentials', - principal: { + const result = { + $$type: '@backstage/BackstageCredentials', + principal: { + type: 'user', + userEntityRef, + ...(options?.actor && { + actor: { type: 'service', subject: options.actor.subject } as const, + }), + }, + } as const; + Object.defineProperty(result, 'toString', { + enumerable: false, + configurable: true, + value: () => + JSON.stringify({ + $$type: '@backstage/MockBackstageCredentials', type: 'user', userEntityRef, ...(options?.actor && { actor: { type: 'service', subject: options.actor.subject }, }), - }, - }, - 'token', - { - enumerable: false, - configurable: true, - value: user.token(), - }, - ); + }), + }); + Object.defineProperty(result, 'token', { + enumerable: false, + configurable: true, + value: user.token(), + }); + return result; } /** @@ -231,14 +252,25 @@ export namespace mockCredentials { subject: string = DEFAULT_MOCK_SERVICE_SUBJECT, accessRestrictions?: BackstagePrincipalAccessRestrictions, ): BackstageCredentials { - return { + const result = { $$type: '@backstage/BackstageCredentials', principal: { type: 'service', subject, ...(accessRestrictions ? { accessRestrictions } : {}), }, - }; + } as const; + Object.defineProperty(result, 'toString', { + enumerable: false, + configurable: true, + value: () => + JSON.stringify({ + $$type: '@backstage/MockBackstageCredentials', + type: 'service', + subject, + }), + }); + return result; } /** From a098fa73dad11b332f7ebecc649e5dc732ac1f58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 12 Jun 2025 16:02:26 +0200 Subject: [PATCH 11/20] use defineProperties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../src/entrypoints/auth/helpers.test.ts | 77 +++++--- .../src/entrypoints/auth/helpers.ts | 168 ++++++++++++------ .../src/services/mockCredentials.test.ts | 8 +- .../src/services/mockCredentials.ts | 66 ++++--- 4 files changed, 203 insertions(+), 116 deletions(-) diff --git a/packages/backend-defaults/src/entrypoints/auth/helpers.test.ts b/packages/backend-defaults/src/entrypoints/auth/helpers.test.ts index f54062efba..fdc6e26c3a 100644 --- a/packages/backend-defaults/src/entrypoints/auth/helpers.test.ts +++ b/packages/backend-defaults/src/entrypoints/auth/helpers.test.ts @@ -82,33 +82,62 @@ describe('credentials', () => { ).not.toMatch(/my-token/); }); - it('should have a serializable form', () => { - expect( - String(createCredentialsWithServicePrincipal('my-service')), - ).toMatchInlineSnapshot( - `"{"$$type":"@backstage/BackstageCredentials","type":"service","subject":"my-service"}"`, + it('should have a serializable form both as strings and as JSON', () => { + const simpleService = createCredentialsWithServicePrincipal('my-service'); + expect(String(simpleService)).toMatchInlineSnapshot( + `"backstageCredentials{servicePrincipal{my-service}}"`, ); - expect( - String( - createCredentialsWithUserPrincipal('user:default/mock', 'my-token'), - ), - ).toMatchInlineSnapshot( - `"{"$$type":"@backstage/BackstageCredentials","type":"user","userEntityRef":"user:default/mock"}"`, + expect(JSON.stringify(simpleService)).toMatchInlineSnapshot( + `"{"$$type":"@backstage/BackstageCredentials","version":"v1","principal":{"type":"service","subject":"my-service"}}"`, ); - expect( - String( - createCredentialsWithUserPrincipal( - 'user:default/mock', - 'my-token', - undefined, - 'my-actor', - ), - ), - ).toMatchInlineSnapshot( - `"{"$$type":"@backstage/BackstageCredentials","type":"user","userEntityRef":"user:default/mock","actor":{"type":"service","subject":"my-actor"}}"`, + + const serviceWithAccessRestrictions = createCredentialsWithServicePrincipal( + 'my-service', + undefined, + { + permissionNames: ['perm'], + permissionAttributes: { + action: ['read'], + }, + }, ); - expect(String(createCredentialsWithNonePrincipal())).toMatchInlineSnapshot( - `"{"$$type":"@backstage/BackstageCredentials","type":"none"}"`, + expect(String(serviceWithAccessRestrictions)).toMatchInlineSnapshot( + `"backstageCredentials{servicePrincipal{my-service,accessRestrictions=cXWOJgUirHkHNZIowUi/YO5nwEwhTicC38iXi2XTYCk}}"`, + ); + expect(JSON.stringify(serviceWithAccessRestrictions)).toMatchInlineSnapshot( + `"{"$$type":"@backstage/BackstageCredentials","version":"v1","principal":{"type":"service","subject":"my-service","accessRestrictions":{"permissionNames":["perm"],"permissionAttributes":{"action":["read"]}}}}"`, + ); + + const simpleUser = createCredentialsWithUserPrincipal( + 'user:default/mock', + 'my-token', + ); + expect(String(simpleUser)).toMatchInlineSnapshot( + `"backstageCredentials{userPrincipal{user:default/mock}}"`, + ); + expect(JSON.stringify(simpleUser)).toMatchInlineSnapshot( + `"{"$$type":"@backstage/BackstageCredentials","version":"v1","principal":{"type":"user","userEntityRef":"user:default/mock"}}"`, + ); + + const userWithActor = createCredentialsWithUserPrincipal( + 'user:default/mock', + 'my-token', + undefined, + 'my-actor', + ); + expect(String(userWithActor)).toMatchInlineSnapshot( + `"backstageCredentials{userPrincipal{user:default/mock,actor={servicePrincipal{my-actor}}}}"`, + ); + expect(JSON.stringify(userWithActor)).toMatchInlineSnapshot( + `"{"$$type":"@backstage/BackstageCredentials","version":"v1","principal":{"type":"user","userEntityRef":"user:default/mock","actor":{"type":"service","subject":"my-actor"}}}"`, + ); + + const none = createCredentialsWithNonePrincipal(); + expect(String(none)).toMatchInlineSnapshot( + `"backstageCredentials{nonePrincipal}"`, + ); + expect(JSON.stringify(none)).toMatchInlineSnapshot( + `"{"$$type":"@backstage/BackstageCredentials","version":"v1","principal":{"type":"none"}}"`, ); }); }); diff --git a/packages/backend-defaults/src/entrypoints/auth/helpers.ts b/packages/backend-defaults/src/entrypoints/auth/helpers.ts index 0490a1c5fc..02840112a9 100644 --- a/packages/backend-defaults/src/entrypoints/auth/helpers.ts +++ b/packages/backend-defaults/src/entrypoints/auth/helpers.ts @@ -22,35 +22,32 @@ import { BackstageUserPrincipal, } from '@backstage/backend-plugin-api'; import { InternalBackstageCredentials } from './types'; +import { createHash } from 'crypto'; export function createCredentialsWithServicePrincipal( sub: string, token?: string, accessRestrictions?: BackstagePrincipalAccessRestrictions, ): InternalBackstageCredentials { + const principal = createServicePrincipal(sub, accessRestrictions); const result = { $$type: '@backstage/BackstageCredentials', version: 'v1', - principal: { - type: 'service', - subject: sub, - accessRestrictions, - }, + principal, } as const; - Object.defineProperty(result, 'token', { - enumerable: false, - configurable: true, - value: token, - }); - Object.defineProperty(result, 'toString', { - enumerable: false, - configurable: true, - value: () => - JSON.stringify({ - $$type: '@backstage/BackstageCredentials', - type: 'service', - subject: sub, - }), + Object.defineProperties(result, { + token: { + enumerable: false, + configurable: true, + writable: true, + value: token, + }, + toString: { + enumerable: false, + configurable: true, + writable: true, + value: () => `backstageCredentials{${principal}}`, + }, }); return result; } @@ -61,55 +58,47 @@ export function createCredentialsWithUserPrincipal( expiresAt?: Date, actor?: string, ): InternalBackstageCredentials { + const principal = createUserPrincipal( + sub, + actor ? createServicePrincipal(actor) : undefined, + ); const result = { $$type: '@backstage/BackstageCredentials', version: 'v1', expiresAt, - principal: { - type: 'user', - userEntityRef: sub, - ...(actor && { - actor: { type: 'service', subject: actor } as const, - }), - }, + principal, } as const; - Object.defineProperty(result, 'token', { - enumerable: false, - configurable: true, - value: token, - }); - Object.defineProperty(result, 'toString', { - enumerable: false, - configurable: true, - value: () => - JSON.stringify({ - $$type: '@backstage/BackstageCredentials', - type: 'user', - userEntityRef: sub, - ...(actor && { - actor: { type: 'service', subject: actor }, - }), - }), + Object.defineProperties(result, { + token: { + enumerable: false, + configurable: true, + writable: true, + value: token, + }, + toString: { + enumerable: false, + configurable: true, + writable: true, + value: () => `backstageCredentials{${principal}}`, + }, }); return result; } export function createCredentialsWithNonePrincipal(): InternalBackstageCredentials { + const principal = createNonePrincipal(); const result = { $$type: '@backstage/BackstageCredentials', version: 'v1', - principal: { - type: 'none', - }, + principal, } as const; - Object.defineProperty(result, 'toString', { - enumerable: false, - configurable: true, - value: () => - JSON.stringify({ - $$type: '@backstage/BackstageCredentials', - type: 'none', - }), + Object.defineProperties(result, { + toString: { + enumerable: false, + configurable: true, + writable: true, + value: () => `backstageCredentials{${principal}}`, + }, }); return result; } @@ -135,3 +124,74 @@ export function toInternalBackstageCredentials( return internalCredentials; } + +function createServicePrincipal( + sub: string, + accessRestrictions?: BackstagePrincipalAccessRestrictions, +): BackstageServicePrincipal { + const result = { + type: 'service', + subject: sub, + accessRestrictions, + } as const; + Object.defineProperties(result, { + toString: { + enumerable: false, + configurable: true, + writable: true, + value: () => { + let parts = sub; + if (accessRestrictions) { + const hash = createHash('sha256') + .update(JSON.stringify(accessRestrictions)) + .digest('base64') + .replace(/=+$/, ''); + parts += `,accessRestrictions=${hash}`; + } + return `servicePrincipal{${parts}}`; + }, + }, + }); + return result; +} + +function createUserPrincipal( + userEntityRef: string, + actor?: BackstageServicePrincipal, +): BackstageUserPrincipal { + const result = { + type: 'user', + userEntityRef, + actor, + } as const; + Object.defineProperties(result, { + toString: { + enumerable: false, + configurable: true, + writable: true, + value: () => { + let parts = userEntityRef; + if (actor) { + parts += `,actor={${actor}}`; + } + return `userPrincipal{${parts}}`; + }, + }, + }); + return result; +} + +function createNonePrincipal(): BackstageNonePrincipal { + const result = { + type: 'none', + } as const; + Object.defineProperties(result, { + toString: { + enumerable: false, + configurable: true, + writable: true, + value: () => 'nonePrincipal', + }, + }); + return result; +} diff --git a/packages/backend-test-utils/src/services/mockCredentials.test.ts b/packages/backend-test-utils/src/services/mockCredentials.test.ts index f81ec1909c..9248e3bde0 100644 --- a/packages/backend-test-utils/src/services/mockCredentials.test.ts +++ b/packages/backend-test-utils/src/services/mockCredentials.test.ts @@ -173,12 +173,12 @@ describe('mockCredentials', () => { it('should have a serializable form', () => { expect(String(mockCredentials.service('my-service'))).toMatchInlineSnapshot( - `"{"$$type":"@backstage/MockBackstageCredentials","type":"service","subject":"my-service"}"`, + `"mockCredentials{servicePrincipal{my-service}}"`, ); expect( String(mockCredentials.user('user:default/mock')), ).toMatchInlineSnapshot( - `"{"$$type":"@backstage/MockBackstageCredentials","type":"user","userEntityRef":"user:default/mock"}"`, + `"mockCredentials{userPrincipal{user:default/mock}}"`, ); expect( String( @@ -187,10 +187,10 @@ describe('mockCredentials', () => { }), ), ).toMatchInlineSnapshot( - `"{"$$type":"@backstage/MockBackstageCredentials","type":"user","userEntityRef":"user:default/mock","actor":{"type":"service","subject":"my-actor"}}"`, + `"mockCredentials{userPrincipal{user:default/mock,actor={my-actor}}}"`, ); expect(String(mockCredentials.none())).toMatchInlineSnapshot( - `"{"$$type":"@backstage/MockBackstageCredentials","type":"none"}"`, + `"mockCredentials{nonePrincipal}"`, ); }); }); diff --git a/packages/backend-test-utils/src/services/mockCredentials.ts b/packages/backend-test-utils/src/services/mockCredentials.ts index 6ea57737d7..d749f7edb1 100644 --- a/packages/backend-test-utils/src/services/mockCredentials.ts +++ b/packages/backend-test-utils/src/services/mockCredentials.ts @@ -80,14 +80,13 @@ export namespace mockCredentials { $$type: '@backstage/BackstageCredentials', principal: { type: 'none' }, } as const; - Object.defineProperty(result, 'toString', { - enumerable: false, - configurable: true, - value: () => - JSON.stringify({ - $$type: '@backstage/MockBackstageCredentials', - type: 'none', - }), + Object.defineProperties(result, { + toString: { + enumerable: false, + configurable: true, + writable: true, + value: () => `mockCredentials{nonePrincipal}`, + }, }); return result; } @@ -131,23 +130,20 @@ export namespace mockCredentials { }), }, } as const; - Object.defineProperty(result, 'toString', { - enumerable: false, - configurable: true, - value: () => - JSON.stringify({ - $$type: '@backstage/MockBackstageCredentials', - type: 'user', - userEntityRef, - ...(options?.actor && { - actor: { type: 'service', subject: options.actor.subject }, - }), - }), - }); - Object.defineProperty(result, 'token', { - enumerable: false, - configurable: true, - value: user.token(), + Object.defineProperties(result, { + toString: { + enumerable: false, + configurable: true, + value: () => + `mockCredentials{userPrincipal{${userEntityRef}${ + options?.actor ? `,actor={${options.actor.subject}}` : '' + }}}`, + }, + token: { + enumerable: false, + configurable: true, + value: user.token(), + }, }); return result; } @@ -260,15 +256,17 @@ export namespace mockCredentials { ...(accessRestrictions ? { accessRestrictions } : {}), }, } as const; - Object.defineProperty(result, 'toString', { - enumerable: false, - configurable: true, - value: () => - JSON.stringify({ - $$type: '@backstage/MockBackstageCredentials', - type: 'service', - subject, - }), + Object.defineProperties(result, { + toString: { + enumerable: false, + configurable: true, + value: () => + `mockCredentials{servicePrincipal{${subject}${ + accessRestrictions + ? `,accessRestrictions=${JSON.stringify(accessRestrictions)}` + : '' + }}}`, + }, }); return result; } From 9699967f657df80e99036dd41fbbd7d4457c5a4a Mon Sep 17 00:00:00 2001 From: Hermione Bird Date: Fri, 20 Jun 2025 14:37:39 +0100 Subject: [PATCH 12/20] adding export for Button Link in Canon Signed-off-by: Hermione Bird --- packages/canon/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/canon/src/index.ts b/packages/canon/src/index.ts index 69c302bdc5..d35883bbe6 100644 --- a/packages/canon/src/index.ts +++ b/packages/canon/src/index.ts @@ -39,6 +39,7 @@ export * from './components/DataTable'; export * from './components/FieldLabel'; export * from './components/Icon'; export * from './components/ButtonIcon'; +export * from './components/ButtonLink'; export * from './components/Checkbox'; export * from './components/Table'; export * from './components/Tabs'; From e71333aee1cc671dadbcfbdc4d0a91cb90a8e865 Mon Sep 17 00:00:00 2001 From: Hermione Bird Date: Fri, 20 Jun 2025 14:49:29 +0100 Subject: [PATCH 13/20] adding changeset Signed-off-by: Hermione Bird --- .changeset/three-shoes-behave.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/three-shoes-behave.md diff --git a/.changeset/three-shoes-behave.md b/.changeset/three-shoes-behave.md new file mode 100644 index 0000000000..51205ffa21 --- /dev/null +++ b/.changeset/three-shoes-behave.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +adding export for ButtonLink so it's importable From e34d2c814785dd94209fdbe602fcd1f3054db516 Mon Sep 17 00:00:00 2001 From: Hermione Bird Date: Fri, 20 Jun 2025 15:17:26 +0100 Subject: [PATCH 14/20] running api-report Signed-off-by: Hermione Bird --- packages/canon/report.api.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md index bd0b9cbee0..abe30ea93b 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -18,6 +18,7 @@ import { FocusEvent as FocusEvent_2 } from 'react'; import { ForwardRefExoticComponent } from 'react'; import { HTMLAttributes } from 'react'; import { JSX as JSX_2 } from 'react/jsx-runtime'; +import { LinkProps as LinkProps_2 } from 'react-aria-components'; import { Menu as Menu_2 } from '@base-ui-components/react/menu'; import { ReactElement } from 'react'; import { ReactNode } from 'react'; @@ -174,6 +175,28 @@ export interface ButtonIconProps extends ButtonProps_2 { | Partial>; } +// @public (undocumented) +export const ButtonLink: ForwardRefExoticComponent< + ButtonLinkProps & RefAttributes +>; + +// @public +export interface ButtonLinkProps extends LinkProps_2 { + // (undocumented) + children?: ReactNode; + // (undocumented) + iconEnd?: ReactElement; + // (undocumented) + iconStart?: ReactElement; + // (undocumented) + size?: 'small' | 'medium' | Partial>; + // (undocumented) + variant?: + | 'primary' + | 'secondary' + | Partial>; +} + // @public export interface ButtonProps extends ButtonProps_2 { // (undocumented) From dbf9161645be840c5dfe38ab098ab230642220e0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 20 Jun 2025 15:00:42 +0000 Subject: [PATCH 15/20] chore(deps): update dependency @changesets/cli to v2.29.5 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/yarn.lock b/yarn.lock index da694cf51f..f27b409a61 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8757,9 +8757,9 @@ __metadata: languageName: node linkType: hard -"@changesets/assemble-release-plan@npm:^6.0.8": - version: 6.0.8 - resolution: "@changesets/assemble-release-plan@npm:6.0.8" +"@changesets/assemble-release-plan@npm:^6.0.9": + version: 6.0.9 + resolution: "@changesets/assemble-release-plan@npm:6.0.9" dependencies: "@changesets/errors": "npm:^0.2.0" "@changesets/get-dependents-graph": "npm:^2.1.3" @@ -8767,7 +8767,7 @@ __metadata: "@changesets/types": "npm:^6.1.0" "@manypkg/get-packages": "npm:^1.1.3" semver: "npm:^7.5.3" - checksum: 10/5d01fc42c67229874cc70b93fbdc971e11909aa7a72f1909c585ecb3fdc69f3ac105d243e1341cd5b07c02dee133be461fa48138125f00d137e71f8b7e8f428e + checksum: 10/f84656eabb700ed77f97751b282e1701636ed45a44b443abd9af0291870495cc046fee301478010f39a1dc455799065ae007b9d7d2bb5ae8b793b65bbb8e052a languageName: node linkType: hard @@ -8781,16 +8781,16 @@ __metadata: linkType: hard "@changesets/cli@npm:^2.14.0": - version: 2.29.4 - resolution: "@changesets/cli@npm:2.29.4" + version: 2.29.5 + resolution: "@changesets/cli@npm:2.29.5" dependencies: "@changesets/apply-release-plan": "npm:^7.0.12" - "@changesets/assemble-release-plan": "npm:^6.0.8" + "@changesets/assemble-release-plan": "npm:^6.0.9" "@changesets/changelog-git": "npm:^0.2.1" "@changesets/config": "npm:^3.1.1" "@changesets/errors": "npm:^0.2.0" "@changesets/get-dependents-graph": "npm:^2.1.3" - "@changesets/get-release-plan": "npm:^4.0.12" + "@changesets/get-release-plan": "npm:^4.0.13" "@changesets/git": "npm:^3.0.4" "@changesets/logger": "npm:^0.1.1" "@changesets/pre": "npm:^2.0.2" @@ -8814,7 +8814,7 @@ __metadata: term-size: "npm:^2.1.0" bin: changeset: bin.js - checksum: 10/fc325447b81a811464107e72a687f6c0414c5f928e518cb122d1efde1d71c205b1972464795ab97fb26087900ea55b99a551e9a010c9681def3d7561fd1c3f0b + checksum: 10/f401da29025d7bcc07b732bb09a9627f785bfc21c7c2005861d11ffea732bc14d33394fc2fcae50cc5f2b710f6080c5babe2fa90d432de5fdb47ae6afc147936 languageName: node linkType: hard @@ -8854,17 +8854,17 @@ __metadata: languageName: node linkType: hard -"@changesets/get-release-plan@npm:^4.0.12": - version: 4.0.12 - resolution: "@changesets/get-release-plan@npm:4.0.12" +"@changesets/get-release-plan@npm:^4.0.13": + version: 4.0.13 + resolution: "@changesets/get-release-plan@npm:4.0.13" dependencies: - "@changesets/assemble-release-plan": "npm:^6.0.8" + "@changesets/assemble-release-plan": "npm:^6.0.9" "@changesets/config": "npm:^3.1.1" "@changesets/pre": "npm:^2.0.2" "@changesets/read": "npm:^0.6.5" "@changesets/types": "npm:^6.1.0" "@manypkg/get-packages": "npm:^1.1.3" - checksum: 10/d6482ecb6f1c2c47266493a36d05b484f0950d0a4472820649e953d073e3fdd612cdd8a4df9e3d7e00756d4e446dae639f9d6e0dab8a25f76bb6df77cd91c21c + checksum: 10/9983fae5a68012c4c418ddd62f2fb3d325363f21160252ff7b868503a1a2effb8fdd32e4a0289b72653afc3605ce19d163ff69205c942a0004efb571a5f78fd0 languageName: node linkType: hard From fb38753f5ddcef7d1f2557d41cdec70f63cec8a9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 20 Jun 2025 15:52:47 +0000 Subject: [PATCH 16/20] chore(deps): update dependency @playwright/test to v1.53.1 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/yarn.lock b/yarn.lock index f27b409a61..3912e0e7fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14865,13 +14865,13 @@ __metadata: linkType: hard "@playwright/test@npm:^1.32.3": - version: 1.53.0 - resolution: "@playwright/test@npm:1.53.0" + version: 1.53.1 + resolution: "@playwright/test@npm:1.53.1" dependencies: - playwright: "npm:1.53.0" + playwright: "npm:1.53.1" bin: playwright: cli.js - checksum: 10/968df4fba133dd18b8c65504c3cc5a3a6071e49f0706c6524711cdfab321a51debfeb506b9ff0a8f7dd8ce3015921d82fa51429d8f11d392cc68de1938703c33 + checksum: 10/98fb9b962710183d465b695daab2006296fd9a703ecb1b763a38cd12a39f7d6066f9539d1758e54313d393353fafb16b90fb31e4add1ca99ffec99b8b1b40fb9 languageName: node linkType: hard @@ -42032,27 +42032,27 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.53.0": - version: 1.53.0 - resolution: "playwright-core@npm:1.53.0" +"playwright-core@npm:1.53.1": + version: 1.53.1 + resolution: "playwright-core@npm:1.53.1" bin: playwright-core: cli.js - checksum: 10/881f27a9b7edd9954700489a5a4212cb91bcada226fd1d79a239b2eab0f333df1e2e41e275e6fa846d7f57c6a92afe14dca33ca7a2ce303dfb687d02511b7c69 + checksum: 10/d0ea8674c3abb76069255ca81bc0dfdef3f9548207f1404eec036bb8724135710f25ee791bfd7c043d5b9c2ccfa42288b0308d61dc5efc60a13b18811a15c4cd languageName: node linkType: hard -"playwright@npm:1.53.0": - version: 1.53.0 - resolution: "playwright@npm:1.53.0" +"playwright@npm:1.53.1": + version: 1.53.1 + resolution: "playwright@npm:1.53.1" dependencies: fsevents: "npm:2.3.2" - playwright-core: "npm:1.53.0" + playwright-core: "npm:1.53.1" dependenciesMeta: fsevents: optional: true bin: playwright: cli.js - checksum: 10/0b0258630f39b4d6ff1555d008ee4d591fe45cbe1e0f643a612397e3e6b1f7a99a2037a957eaa7351edd907ba10966ba105b2d244eafd1b247378910b660f086 + checksum: 10/74b3178d5ae3fde8de08fe6c221578530368f1abb8794fce234d06e0043178201eb3b7410354418517f23c105bd54ac1432da5f46c50353a5e6a80198a95f2cf languageName: node linkType: hard From 95f7e4e9bd29198497a23a0b5464a536359d9a8c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 20 Jun 2025 16:46:04 +0000 Subject: [PATCH 17/20] chore(deps): update dependency @types/lodash to v4.17.18 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3912e0e7fb..bde6730c01 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21782,9 +21782,9 @@ __metadata: linkType: hard "@types/lodash@npm:^4.14.151": - version: 4.17.17 - resolution: "@types/lodash@npm:4.17.17" - checksum: 10/496459a3cb1a0733bb60532de3899ad6297717af0b9b26ad6821154b2005fec86f29ccd47a2e6f4da4a8c7c818bb8ae73901144e8057ea86b7b02a3d7bb9d13f + version: 4.17.18 + resolution: "@types/lodash@npm:4.17.18" + checksum: 10/54ebb15b29925112dbe9da3abd99fb80d7202bc5ba20fc1b4fc8ea835d0012f00cbd9a3e7f367b70e7c3f2d5ee635964e3920a489625647b558f02994b3dd381 languageName: node linkType: hard From 6525f78e755a8aede427db03fff4a989711dcd6b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 20 Jun 2025 17:36:11 +0000 Subject: [PATCH 18/20] fix(deps): update dependency @dagrejs/dagre to v1.1.5 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index bde6730c01..9e13c331b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9105,11 +9105,11 @@ __metadata: linkType: hard "@dagrejs/dagre@npm:^1.1.4": - version: 1.1.4 - resolution: "@dagrejs/dagre@npm:1.1.4" + version: 1.1.5 + resolution: "@dagrejs/dagre@npm:1.1.5" dependencies: "@dagrejs/graphlib": "npm:2.2.4" - checksum: 10/0b3744b170c68ae0666e03aca19c3100d5131feafeb54b3ea096b749a9f0fe5385b8bd8889c11a49493cfab945b2486b9e30bc41b321755ed718e9f5cb4b74f1 + checksum: 10/c00abd1e04d19f90ad8dfa0a4e16365371bc4309affead3827a1b39f6b0b946643b8af0b1e5519011deca3fda4c7471b27e9ebb03423309a94f95ac0b881ac4f languageName: node linkType: hard From 3772e0c73527f1173f7c90ae062c62e9e747df43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Jerna=C5=9B?= Date: Sat, 21 Jun 2025 21:19:11 +0200 Subject: [PATCH 19/20] docs(scaffolder): Remove old JSON schema version from custom action MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Łukasz Jernaś --- .../writing-custom-actions.md | 78 +++++-------------- 1 file changed, 18 insertions(+), 60 deletions(-) diff --git a/docs/features/software-templates/writing-custom-actions.md b/docs/features/software-templates/writing-custom-actions.md index 26773c15c6..1a64406b04 100644 --- a/docs/features/software-templates/writing-custom-actions.md +++ b/docs/features/software-templates/writing-custom-actions.md @@ -12,7 +12,7 @@ by writing custom actions which can be used alongside our When adding custom actions, the actions array will **replace the built-in actions too**. Meaning, you will no longer be able to use them. -If you want to continue using the builtin actions, include them in the actions +If you want to continue using the builtin actions, include them in the `actions` array when registering your custom actions, as seen below. ::: @@ -52,19 +52,20 @@ its generated unit test. We will replace the existing placeholder code with our import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import fs from 'fs-extra'; -import { z } from 'zod'; +import { type z } from 'zod'; export const createNewFileAction = () => { return createTemplateAction({ id: 'acme:file:create', description: 'Create an Acme file.', schema: { - input: z.object({ - contents: z.string().describe('The contents of the file'), - filename: z - .string() - .describe('The filename of the file that will be created'), - }), + input: { + contents: z => z.string({ description: 'The contents of the file' }), + filename: z => + z.string({ + description: 'The filename of the file that will be created', + }), + }, }, async handler(ctx) { @@ -95,53 +96,11 @@ The `createTemplateAction` takes an object which specifies the following: function using `ctx.output` - `handler` - the actual code which is run as part of the action, with a context -You can also choose to define your custom action using JSON schema instead of `zod`: - -```ts title="With JSON Schema" -import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; -import { writeFile } from 'fs'; - -export const createNewFileAction = () => { - return createTemplateAction<{ contents: string; filename: string }>({ - id: 'acme:file:create', - description: 'Create an Acme file.', - schema: { - input: { - required: ['contents', 'filename'], - type: 'object', - properties: { - contents: { - type: 'string', - title: 'Contents', - description: 'The contents of the file', - }, - filename: { - type: 'string', - title: 'Filename', - description: 'The filename of the file that will be created', - }, - }, - }, - }, - async handler(ctx) { - const { signal } = ctx; - await writeFile( - resolveSafeChildPath(ctx.workspacePath, ctx.input.filename), - ctx.input.contents, - { signal }, - _ => {}, - ); - }, - }); -}; -``` - ### Naming Conventions Try to keep names consistent for both your own custom actions, and any actions contributed to open source. We've found that a separation of `:` and using a verb as the last part of the name works well. -We follow `provider:entity:verb` or as close to this as possible for our built in actions. For example, +We follow `provider:entity:verb` or as close to this as possible for our built-in actions. For example, `github:actions:create` or `github:repo:create`. Also feel free to use your company name to namespace them if you prefer too, for example `acme:file:create` like above. @@ -151,14 +110,14 @@ and writing of template entity definitions. ### Adding a TemplateExample -A TemplateExample is a way to document different ways that your custom action can be used. Once added it will be visible +A TemplateExample is a way to document different ways that your custom action can be used. Once added, it will be visible in your Backstage instance under the [/create/actions](https://demo.backstage.io/create/actions) path. You can have multiple examples for one action that can demonstrate different combinations of inputs and how to use them. #### Define TemplateExamples Below is a sample TemplateExample that is used for `publish:github`. The source code is available -on [github](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-github/src/actions/github.examples.ts) +on [GitHub](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-github/src/actions/github.examples.ts) and preview on [demo.backstage.io/create/actions](https://demo.backstage.io/create/actions#publish-github) ```ts title="With JSON Schema" @@ -222,7 +181,7 @@ return createTemplateAction({ #### Test TemplateAction examples It is also possible to test your example TemplateActions. You can see a sample test -on [github](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-github/src/actions/github.examples.test.ts) +on [GitHub](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-github/src/actions/github.examples.test.ts) ### The context object @@ -234,13 +193,12 @@ argument. It looks like the following: implement [idempotency of the actions](https://github.com/backstage/backstage/tree/master/beps/0004-scaffolder-task-idempotency) by not re-running the same function again if it was executed successfully on the previous run. -- `ctx.logger` - a Winston logger for additional logging inside your action -- `ctx.logStream` - a stream version of the logger if needed +- `ctx.logger` - a [LoggerService](../../backend-system/core-services/logger.md) instance for additional logging inside your action - `ctx.workspacePath` - a string of the working directory of the template run - `ctx.input` - an object which should match the `zod` or JSON schema provided in the `schema.input` part of the action definition - `ctx.output` - a function which you can call to set outputs that match the - JSON schema or `zod` in `schema.output` for ex. `ctx.output('downloadUrl', myDownloadUrl)` + `zod` schema in `schema.output` for ex. `ctx.output('downloadUrl', myDownloadUrl)` - `createTemporaryDirectory` a function to call to give you a temporary directory somewhere on the runner, so you can store some files there rather than polluting the `workspacePath` @@ -249,7 +207,7 @@ argument. It looks like the following: ## Registering Custom Actions -To register your new custom action in the Backend System you will need to create a backend module. Here is a very +To register your new custom action in the Backend System, you will need to create a backend module. Here is a very simplified example of how to do that: ```ts title="packages/backend/src/index.ts" @@ -327,8 +285,8 @@ const res = await ctx.checkpoint?.({ }); ``` -You have to define the unique key in scope of the scaffolder task for your checkpoint. During the execution task engine -will check if the checkpoint with such key was already executed or not, if yes, and the run was successful, the callback +You have to define the unique key in the scope of the scaffolder task for your checkpoint. During the execution task engine +will check if the checkpoint with such a key was already executed or not, if yes, and the run was successful, the callback will be skipped and instead the stored value will be returned. Whenever you change the return type of the checkpoint, we encourage you to change the ID. From fc70b43a368a8947477cb3042f0cd01221e88019 Mon Sep 17 00:00:00 2001 From: ShaoWei Teo Date: Sun, 22 Jun 2025 08:24:48 +0800 Subject: [PATCH 20/20] chore(deps): remove @backstage/backend-common Signed-off-by: ShaoWei Teo --- .changeset/sharp-stars-report.md | 5 +++++ plugins/scaffolder-backend/.eslintrc.js | 4 ++-- plugins/scaffolder-backend/package.json | 1 - .../src/scaffolder/tasks/DatabaseTaskStore.test.ts | 12 +++++++++--- .../src/scaffolder/tasks/StorageTaskBroker.test.ts | 7 +++++-- .../src/scaffolder/tasks/TaskWorker.test.ts | 7 +++++-- .../scaffolder-backend/src/service/router.test.ts | 7 +++++-- yarn.lock | 1 - 8 files changed, 31 insertions(+), 13 deletions(-) create mode 100644 .changeset/sharp-stars-report.md diff --git a/.changeset/sharp-stars-report.md b/.changeset/sharp-stars-report.md new file mode 100644 index 0000000000..c37e0e39ea --- /dev/null +++ b/.changeset/sharp-stars-report.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Replaced deprecated uses of `@backstage/backend-common` with the equivalents in `@backstage/backend-defaults` and `@backstage/backend-plugin-api`. diff --git a/plugins/scaffolder-backend/.eslintrc.js b/plugins/scaffolder-backend/.eslintrc.js index 953af54f90..3c5416068f 100644 --- a/plugins/scaffolder-backend/.eslintrc.js +++ b/plugins/scaffolder-backend/.eslintrc.js @@ -5,13 +5,13 @@ module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { name: 'path', importNames: ['resolve'], message: - 'Do not use path.resolve, use `resolveSafeChildPath` from `@backstage/backend-common` instead as it prevents security issues', + 'Do not use path.resolve, use `resolveSafeChildPath` from `@backstage/backend-plugin-api` instead as it prevents security issues', }, ], restrictedSrcSyntax: [ { message: - 'Do not use path.resolve, use `resolveSafeChildPath` from `@backstage/backend-common` instead as it prevents security issues', + 'Do not use path.resolve, use `resolveSafeChildPath` from `@backstage/backend-plugin-api` instead as it prevents security issues', selector: 'MemberExpression[object.name="path"][property.name="resolve"]', }, ], diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index d3fb0fa81b..91e669c4f5 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -61,7 +61,6 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-common": "^0.25.0", "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/catalog-model": "workspace:^", diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.test.ts index eb0418b5ed..f95381e0ed 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.test.ts @@ -14,12 +14,15 @@ * limitations under the License. */ -import { DatabaseManager } from '@backstage/backend-common'; +import { DatabaseManager } from '@backstage/backend-defaults/database'; import { ConfigReader } from '@backstage/config'; import { DatabaseTaskStore, RawDbTaskEventRow } from './DatabaseTaskStore'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { ConflictError } from '@backstage/errors'; -import { createMockDirectory } from '@backstage/backend-test-utils'; +import { + mockServices, + createMockDirectory, +} from '@backstage/backend-test-utils'; import fs from 'fs-extra'; import { EventsService } from '@backstage/plugin-events-node'; @@ -33,7 +36,10 @@ const createStore = async (events?: EventsService) => { }, }, }), - ).forPlugin('scaffolder'); + ).forPlugin('scaffolder', { + logger: mockServices.logger.mock(), + lifecycle: mockServices.lifecycle.mock(), + }); const store = await DatabaseTaskStore.create({ database: manager, events, diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts index dc89100b95..2b13130908 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { DatabaseManager } from '@backstage/backend-common'; +import { DatabaseManager } from '@backstage/backend-defaults/database'; import { ConfigReader } from '@backstage/config'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { @@ -36,7 +36,10 @@ async function createStore(): Promise { }, }, }), - ).forPlugin('scaffolder'); + ).forPlugin('scaffolder', { + logger: mockServices.logger.mock(), + lifecycle: mockServices.lifecycle.mock(), + }); return await DatabaseTaskStore.create({ database: manager, diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts index dd9233a5ee..6fedc4df40 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts @@ -15,7 +15,7 @@ */ import os from 'os'; -import { DatabaseManager } from '@backstage/backend-common'; +import { DatabaseManager } from '@backstage/backend-defaults/database'; import { ConfigReader } from '@backstage/config'; import { DatabaseTaskStore } from './DatabaseTaskStore'; import { StorageTaskBroker } from './StorageTaskBroker'; @@ -49,7 +49,10 @@ async function createStore(): Promise { }, }, }), - ).forPlugin('scaffolder'); + ).forPlugin('scaffolder', { + logger: mockServices.logger.mock(), + lifecycle: mockServices.lifecycle.mock(), + }); return await DatabaseTaskStore.create({ database: manager, }); diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index 679ac44640..2820c8b8dc 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { DatabaseManager } from '@backstage/backend-common'; +import { DatabaseManager } from '@backstage/backend-defaults/database'; import { ConfigReader } from '@backstage/config'; import request from 'supertest'; import ObservableImpl from 'zen-observable'; @@ -77,7 +77,10 @@ function createDatabase(): DatabaseService { }, }, }), - ).forPlugin('scaffolder'); + ).forPlugin('scaffolder', { + logger: mockServices.logger.mock(), + lifecycle: mockServices.lifecycle.mock(), + }); } const config = new ConfigReader({}); diff --git a/yarn.lock b/yarn.lock index 9e13c331b5..c6264bde1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7499,7 +7499,6 @@ __metadata: resolution: "@backstage/plugin-scaffolder-backend@workspace:plugins/scaffolder-backend" dependencies: "@backstage/backend-app-api": "workspace:^" - "@backstage/backend-common": "npm:^0.25.0" "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^"