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/.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/.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/.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 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. 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 diff --git a/packages/backend-defaults/src/entrypoints/auth/helpers.test.ts b/packages/backend-defaults/src/entrypoints/auth/helpers.test.ts index 8454aa3ecc..fdc6e26c3a 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,63 @@ describe('credentials', () => { ), ).not.toMatch(/my-token/); }); + + 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(JSON.stringify(simpleService)).toMatchInlineSnapshot( + `"{"$$type":"@backstage/BackstageCredentials","version":"v1","principal":{"type":"service","subject":"my-service"}}"`, + ); + + const serviceWithAccessRestrictions = createCredentialsWithServicePrincipal( + 'my-service', + undefined, + { + permissionNames: ['perm'], + permissionAttributes: { + action: ['read'], + }, + }, + ); + 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 58e2bcff74..02840112a9 100644 --- a/packages/backend-defaults/src/entrypoints/auth/helpers.ts +++ b/packages/backend-defaults/src/entrypoints/auth/helpers.ts @@ -22,29 +22,34 @@ 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 { - return Object.defineProperty( - { - $$type: '@backstage/BackstageCredentials', - version: 'v1', - principal: { - type: 'service', - subject: sub, - accessRestrictions, - }, - }, - 'token', - { + const principal = createServicePrincipal(sub, accessRestrictions); + const result = { + $$type: '@backstage/BackstageCredentials', + version: 'v1', + principal, + } as const; + 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 createCredentialsWithUserPrincipal( @@ -53,36 +58,49 @@ export function createCredentialsWithUserPrincipal( expiresAt?: Date, actor?: string, ): InternalBackstageCredentials { - return Object.defineProperty( - { - $$type: '@backstage/BackstageCredentials', - version: 'v1', - expiresAt, - principal: { - type: 'user', - userEntityRef: sub, - ...(actor && { - actor: { type: 'service', subject: actor }, - }), - }, - }, - 'token', - { + const principal = createUserPrincipal( + sub, + actor ? createServicePrincipal(actor) : undefined, + ); + const result = { + $$type: '@backstage/BackstageCredentials', + version: 'v1', + expiresAt, + principal, + } as const; + 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 { - return { + const principal = createNonePrincipal(); + const result = { $$type: '@backstage/BackstageCredentials', version: 'v1', - principal: { - type: 'none', + principal, + } as const; + Object.defineProperties(result, { + toString: { + enumerable: false, + configurable: true, + writable: true, + value: () => `backstageCredentials{${principal}}`, }, - }; + }); + return result; } export function toInternalBackstageCredentials( @@ -106,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 343d6d6d20..9248e3bde0 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( + `"mockCredentials{servicePrincipal{my-service}}"`, + ); + expect( + String(mockCredentials.user('user:default/mock')), + ).toMatchInlineSnapshot( + `"mockCredentials{userPrincipal{user:default/mock}}"`, + ); + expect( + String( + mockCredentials.user('user:default/mock', { + actor: { subject: 'my-actor' }, + }), + ), + ).toMatchInlineSnapshot( + `"mockCredentials{userPrincipal{user:default/mock,actor={my-actor}}}"`, + ); + expect(String(mockCredentials.none())).toMatchInlineSnapshot( + `"mockCredentials{nonePrincipal}"`, + ); + }); }); diff --git a/packages/backend-test-utils/src/services/mockCredentials.ts b/packages/backend-test-utils/src/services/mockCredentials.ts index 617ffdb240..d749f7edb1 100644 --- a/packages/backend-test-utils/src/services/mockCredentials.ts +++ b/packages/backend-test-utils/src/services/mockCredentials.ts @@ -76,10 +76,19 @@ 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.defineProperties(result, { + toString: { + enumerable: false, + configurable: true, + writable: true, + value: () => `mockCredentials{nonePrincipal}`, + }, + }); + return result; } /** @@ -111,24 +120,32 @@ export namespace mockCredentials { options?: { actor?: { subject: string } }, ): BackstageCredentials { validateUserEntityRef(userEntityRef); - return Object.defineProperty( - { - $$type: '@backstage/BackstageCredentials', - principal: { - type: 'user', - userEntityRef, - ...(options?.actor && { - actor: { type: 'service', subject: options.actor.subject }, - }), - }, + const result = { + $$type: '@backstage/BackstageCredentials', + principal: { + type: 'user', + userEntityRef, + ...(options?.actor && { + actor: { type: 'service', subject: options.actor.subject } as const, + }), }, - 'token', - { + } as const; + 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; } /** @@ -231,14 +248,27 @@ 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.defineProperties(result, { + toString: { + enumerable: false, + configurable: true, + value: () => + `mockCredentials{servicePrincipal{${subject}${ + accessRestrictions + ? `,accessRestrictions=${JSON.stringify(accessRestrictions)}` + : '' + }}}`, + }, + }); + return result; } /** diff --git a/packages/canon/src/index.ts b/packages/canon/src/index.ts index dc0b3c1cfc..d35883bbe6 100644 --- a/packages/canon/src/index.ts +++ b/packages/canon/src/index.ts @@ -28,30 +28,28 @@ export * from './components/Box'; export * from './components/Grid'; export * from './components/Flex'; export * from './components/Container'; +export * from './components/Text'; +export * from './components/Heading'; // UI components export * from './components/Avatar'; export * from './components/Button'; -export * from './components/ButtonIcon'; -export * from './components/ButtonLink'; -export * from './components/Checkbox'; export * from './components/Collapsible'; export * from './components/DataTable'; export * from './components/FieldLabel'; -export * from './components/FieldError'; -export * from './components/Heading'; export * from './components/Icon'; -export * from './components/Link'; -export * from './components/Menu'; -export * from './components/RadioGroup'; -export * from './components/ScrollArea'; -export * from './components/Select'; -export * from './components/Switch'; +export * from './components/ButtonIcon'; +export * from './components/ButtonLink'; +export * from './components/Checkbox'; export * from './components/Table'; export * from './components/Tabs'; -export * from './components/Text'; export * from './components/TextField'; export * from './components/Tooltip'; +export * from './components/Menu'; +export * from './components/ScrollArea'; +export * from './components/Link'; +export * from './components/Select'; +export * from './components/Switch'; // Types export * from './types'; 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/report.api.md b/plugins/scaffolder-backend/report.api.md index 1f2a17a384..7ece0f84ea 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 { UpdateTaskCheckpointOptions } from '@backstage/plugin-scaffolder-node/alpha'; 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: UpdateTaskCheckpointOptions): Promise; } // @public @deprecated 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/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 7802a7ab8d..1542db8438 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -60,6 +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, + CheckpointContext, +} from '@backstage/plugin-scaffolder-node/alpha'; type NunjucksWorkflowRunnerOptions = { workingDirectory: string; @@ -91,16 +95,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'; }; @@ -384,10 +378,9 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { secrets: task.secrets ?? {}, logger: taskLogger, workspacePath, - async checkpoint(opts: { - key?: string; - fn: () => Promise | T; - }) { + async checkpoint( + opts: CheckpointContext, + ) { const { key: checkpointKey, fn } = opts; const key = `v1.task.checkpoint.${step.id}.${checkpointKey}`; @@ -396,9 +389,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.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/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 86f9f7fcf7..c88a1f687c 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -32,30 +32,19 @@ import { TaskSecrets, TaskStatus, } from '@backstage/plugin-scaffolder-node'; -import { WorkspaceProvider } from '@backstage/plugin-scaffolder-node/alpha'; import { - JsonObject, - JsonValue, - Observable, - createDeferred, -} from '@backstage/types'; + 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'; import { readDuration } from './helper'; import { InternalTaskSecrets, TaskStore } from './types'; type TaskState = { - checkpoints: { - [key: string]: - | { - status: 'failed'; - reason: string; - } - | { - status: 'success'; - value: JsonValue; - }; - }; + checkpoints: CheckpointState; }; /** * TaskManager @@ -152,20 +141,9 @@ 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 { + async updateCheckpoint?(options: UpdateTaskCheckpointOptions): Promise { const { key, ...value } = options; + if (this.task.state) { (this.task.state as TaskState).checkpoints[key] = value; } else { 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/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts index a6703a12d1..e2f07f2320 100644 --- a/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts +++ b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts @@ -22,6 +22,7 @@ import { } from '@backstage/backend-test-utils'; import { JsonObject, JsonValue } from '@backstage/types'; import { ActionContext } from '@backstage/plugin-scaffolder-node'; +import { CheckpointContext } from '@backstage/plugin-scaffolder-node/alpha'; import { loggerToWinstonLogger } from './loggerToWinstonLogger'; /** @@ -44,10 +45,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: CheckpointContext, + ): Promise { return opts.fn(); }, getInitiatorCredentials: () => Promise.resolve(credentials), diff --git a/plugins/scaffolder-node/report-alpha.api.md b/plugins/scaffolder-node/report-alpha.api.md index a96f312d2d..09541fbc56 100644 --- a/plugins/scaffolder-node/report-alpha.api.md +++ b/plugins/scaffolder-node/report-alpha.api.md @@ -27,6 +27,31 @@ export type AutocompleteHandler = ({ }[]; }>; +// @alpha +export type CheckpointContext = { + key: string; + fn: () => Promise | T; +}; + +// @alpha +export type CheckpointState = { + [key: string]: CheckpointStateValue; +}; + +// @alpha +export type CheckpointStateValue = + | { + status: 'failed'; + reason: string; + } + | { + status: 'success'; + value: T; + }; + +// @alpha +export type CheckpointStatus = 'failed' | 'success'; + // @alpha (undocumented) export type CreatedTemplateFilter< TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], @@ -187,6 +212,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 f13d256332..0162d96d95 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 { CheckpointContext } from '@backstage/plugin-scaffolder-node/alpha'; + /** * 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: CheckpointContext, + ): Promise; output( name: keyof TActionOutput, value: TActionOutput[keyof TActionOutput], diff --git a/plugins/scaffolder-node/src/alpha/checkpoints/index.ts b/plugins/scaffolder-node/src/alpha/checkpoints/index.ts new file mode 100644 index 0000000000..16c936b235 --- /dev/null +++ b/plugins/scaffolder-node/src/alpha/checkpoints/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './types'; diff --git a/plugins/scaffolder-node/src/alpha/checkpoints/types.ts b/plugins/scaffolder-node/src/alpha/checkpoints/types.ts new file mode 100644 index 0000000000..5bd1eb5cd3 --- /dev/null +++ b/plugins/scaffolder-node/src/alpha/checkpoints/types.ts @@ -0,0 +1,57 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { JsonValue } from '@backstage/types'; + +/** + * The status of a checkpoint, indicating whether it succeeded or failed. + * + * @alpha + */ +export type CheckpointStatus = 'failed' | 'success'; + +/** + * Represents the union of all possible checkpoint state values. + * + * @alpha + */ +export type CheckpointStateValue = + | { status: 'failed'; reason: string } + | { status: 'success'; value: T }; + +/** + * A map of checkpoint keys to their states. + * + * @alpha + */ +export type CheckpointState = { + [key: string]: CheckpointStateValue; +}; + +/** + * Context for checkpoint function invocation. + * + * @alpha + */ +export type CheckpointContext = { + /** + * Unique key for the checkpoint + */ + key: string; + /** + * Function to execute for the checkpoint + */ + fn: () => Promise | T; +}; 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/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/types.ts b/plugins/scaffolder-node/src/tasks/types.ts index d8cfd24530..d2403f205a 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 { UpdateTaskCheckpointOptions } from '@backstage/plugin-scaffolder-node/alpha'; /** * 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: UpdateTaskCheckpointOptions): Promise; serializeWorkspace?(options: { path: string }): Promise; diff --git a/yarn.lock b/yarn.lock index da694cf51f..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:^" @@ -8757,9 +8756,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 +8766,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 +8780,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 +8813,7 @@ __metadata: term-size: "npm:^2.1.0" bin: changeset: bin.js - checksum: 10/fc325447b81a811464107e72a687f6c0414c5f928e518cb122d1efde1d71c205b1972464795ab97fb26087900ea55b99a551e9a010c9681def3d7561fd1c3f0b + checksum: 10/f401da29025d7bcc07b732bb09a9627f785bfc21c7c2005861d11ffea732bc14d33394fc2fcae50cc5f2b710f6080c5babe2fa90d432de5fdb47ae6afc147936 languageName: node linkType: hard @@ -8854,17 +8853,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 @@ -9105,11 +9104,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 @@ -14865,13 +14864,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 @@ -21782,9 +21781,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 @@ -42032,27 +42031,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