@sanity/workflow-studio 0.26.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +89 -0
- package/README.md +10 -4
- package/dist/index.cjs +10 -3
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +11 -4
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,94 @@
|
|
|
1
1
|
# @sanity/workflow-studio
|
|
2
2
|
|
|
3
|
+
## 0.28.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6584554: **BREAKING:** assignment surfaces now read the project's role catalog, so a role
|
|
8
|
+
with no current holders is assignable and carries its project title.
|
|
9
|
+
|
|
10
|
+
Previously the only role vocabulary was the roles loaded members happened to
|
|
11
|
+
hold. A role the project defines but nobody has yet could not be offered at all,
|
|
12
|
+
and a role named by a stored value rendered as its bare machine name because no
|
|
13
|
+
membership record carried a title for it. Both are fixed by reading
|
|
14
|
+
`GET /projects/{projectId}/roles`. That read is gated on
|
|
15
|
+
`sanity-project-roles: read`, which the default user-facing project roles carry
|
|
16
|
+
down to viewer, so it is not expected to need an administrator.
|
|
17
|
+
|
|
18
|
+
The catalog also becomes the bound on what a picker offers, which subtracts as
|
|
19
|
+
well as adds: a role a member holds that the catalog does not list stops being
|
|
20
|
+
offerable, though it still labels correctly wherever it is already assigned. On
|
|
21
|
+
a complete catalog nothing is lost, since every held role is in it.
|
|
22
|
+
|
|
23
|
+
Three API changes, all in `@sanity/workflow-components`:
|
|
24
|
+
- `ProjectMembersState` gains a required `roles` field, the project's assignable
|
|
25
|
+
roles. Anything constructing this state by hand must supply it; `roles: []`
|
|
26
|
+
preserves the previous behaviour, since resolution falls back to the roles
|
|
27
|
+
members hold. `MemberPicker` does not take it — it lists members only, and its
|
|
28
|
+
props were narrowed to the three fields it reads.
|
|
29
|
+
- `memberRoleFor` and `roleLabelFor` take a `RoleVocabulary` (`{members, roles}`)
|
|
30
|
+
in place of a bare member array. The state either adapter returns satisfies it,
|
|
31
|
+
so a caller holding one can pass it straight through.
|
|
32
|
+
- `AssigneeBadge` and `AssigneeBadges` take `vocabulary` in place of `members`,
|
|
33
|
+
for the same reason.
|
|
34
|
+
|
|
35
|
+
Not migrating leaves a type error at each call site rather than a silent
|
|
36
|
+
behaviour change.
|
|
37
|
+
|
|
38
|
+
Hosts using `@sanity/workflow-studio` or `@sanity/workflow-sdk` get the catalog
|
|
39
|
+
without changes: both adapters fetch it beside the member list, each behind a
|
|
40
|
+
per-client, per-project cache, so it costs one extra request per project per page
|
|
41
|
+
load however many surfaces read it. A failed roles read is deliberately
|
|
42
|
+
non-fatal — it degrades to the previous member-derived behaviour rather than
|
|
43
|
+
taking down the member list, and therefore every assignment control, with it. It
|
|
44
|
+
is reported once on the console with its cause, because nothing downstream can
|
|
45
|
+
tell an unreadable catalog from a project that defines no assignable roles. That
|
|
46
|
+
empty result is cached too, so a project whose catalog cannot be read stays on
|
|
47
|
+
the member-derived behaviour until the page reloads.
|
|
48
|
+
|
|
49
|
+
**Docs impact:** Update the assignment concepts and any Studio plugin guidance
|
|
50
|
+
that describes which roles can be assigned, to say the catalog is the source,
|
|
51
|
+
that a role with no holders is offered, and that a held role missing from the
|
|
52
|
+
catalog is not. The `@sanity/workflow-components` README covers the new
|
|
53
|
+
`RoleVocabulary` shape and the picker's behaviour.
|
|
54
|
+
|
|
55
|
+
### Patch Changes
|
|
56
|
+
|
|
57
|
+
- Updated dependencies [1e4a5da]
|
|
58
|
+
- Updated dependencies [6584554]
|
|
59
|
+
- Updated dependencies [f4405b4]
|
|
60
|
+
- Updated dependencies [5050b06]
|
|
61
|
+
- Updated dependencies [a044ba5]
|
|
62
|
+
- @sanity/workflow-engine@0.28.0
|
|
63
|
+
- @sanity/workflow-sdk@0.28.0
|
|
64
|
+
- @sanity/workflow-react@0.28.0
|
|
65
|
+
|
|
66
|
+
## 0.27.0
|
|
67
|
+
|
|
68
|
+
### Minor Changes
|
|
69
|
+
|
|
70
|
+
- 780cf93: **BREAKING:** `ProjectMember.roles` is now `readonly {name: string; title?: string}[]` instead of `readonly string[]`, and `AssigneeStack`'s `roles` prop takes the same records rather than name strings. This affects integrations that build or read `ProjectMember` rows directly, or render `AssigneeStack` themselves. Read `role.name` wherever a role name was read before, and pass role records rather than name strings into `projectMemberRow` and `AssigneeStack`. Until migrated, TypeScript fails at those sites. Integrations using `useProjectMembers` from `@sanity/workflow-sdk` or the Studio plugin's own equivalent get the new shape without changes, because the adapters own the projection.
|
|
71
|
+
|
|
72
|
+
A role now reads as its project title rather than its machine name — `Administrator`, not `administrator` — wherever one is shown: picker role rows, assignee badges, the instance-snapshot pills, and the hover hint naming a collapsed assignee cluster. A square role avatar takes its letters from the title too, so `Blueprints Deployer` reads as `BD` where `blueprints-deployer` could only ever give one letter; its colour still derives from the machine name, so retitling a project role keeps the role's colour. A role whose membership record carries no title — or a title that is only whitespace, which counts as none — and a role named by an assignee that no current member holds, both still read as the machine name. `roleLabel`, `roleLabelFor`, and `memberRoleFor` are exported for callers rendering their own role labels.
|
|
73
|
+
|
|
74
|
+
Both people-pickers now say who a person is, so choosing one no longer requires knowing the org chart. Search matches role names and titles as well as display name and email, so typing a role narrows the list to its holders. Members are also listed by display name rather than in the order the host supplied them, which was the sequence people joined the project — expect the list order to change, and on a large project to become usable. And hovering a row previews the account behind the name — a larger avatar badged with the person's identity provider, their email, and the roles they hold. Marks exist for Google, GitHub, and `saml-`-prefixed deployments; any other provider badges nothing rather than showing an empty circle. That is what tells two members sharing a display name apart, which no role label could. Rows share one tooltip delay group, so the wait to open is paid once and moving along the list swaps the preview rather than waiting again at each row.
|
|
75
|
+
|
|
76
|
+
`MemberAvatar` takes an optional `loginProvider` to badge the provider mark onto its corner. `ProjectMember` gains optional `loginProvider` and `isCurrentUser`; both are populated by the adapters, so integrations using `useProjectMembers` get them without changes.
|
|
77
|
+
|
|
78
|
+
Neither picker ranks or groups its member rows by the roles those members hold, and neither claims who is eligible for an activity. (The roles-and-members picker still lists selectable roles above the members, as it did before.) An activity has no role gate; only an action does, and a manually fired action's `roles` is folded into its `filter` at desugar, so the accepted set is not readable from a deployed definition. A picker has nothing to derive eligibility from, and a ranked list would assert something no engine or Content Lake check backs.
|
|
79
|
+
|
|
80
|
+
Stored values are unchanged. An assignee still persists the machine role name, and titles are display only.
|
|
81
|
+
|
|
82
|
+
**Docs impact:** Update the `@sanity/workflow-components` member-selection reference for the `ProjectMember.roles` and `AssigneeStack` `roles` shapes and the `roleLabel` / `roleLabelFor` / `memberRoleFor` exports (done in this package's README). Any concept or guide page showing a role in Studio UI should show titles rather than machine names, and the assignment guide should state that a picker never restricts or ranks who may be assigned. Add the `ProjectMember.roles` migration to the release notes.
|
|
83
|
+
|
|
84
|
+
### Patch Changes
|
|
85
|
+
|
|
86
|
+
- Updated dependencies [780cf93]
|
|
87
|
+
- Updated dependencies [b7c8bed]
|
|
88
|
+
- @sanity/workflow-engine@0.27.0
|
|
89
|
+
- @sanity/workflow-sdk@0.27.0
|
|
90
|
+
- @sanity/workflow-react@0.27.0
|
|
91
|
+
|
|
3
92
|
## 0.26.0
|
|
4
93
|
|
|
5
94
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -33,10 +33,16 @@ Studio source context (`useSource` / `useClient`).
|
|
|
33
33
|
errors surface reactively through `error`.
|
|
34
34
|
- `useStudioProjectUsers` — the shared Studio project-user directory used by
|
|
35
35
|
actor resolution and member pickers. It returns the upstream membership and
|
|
36
|
-
profile records without an Editorial Workflows projection,
|
|
37
|
-
`ProjectUserDirectory` integration.
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
profile records without an Editorial Workflows projection, the project's
|
|
37
|
+
assignable `roles`, and the engine's `ProjectUserDirectory` integration. The
|
|
38
|
+
role catalog is read alongside the memberships and is the only account of a
|
|
39
|
+
role nobody currently holds; it is filtered to roles a person can be assigned,
|
|
40
|
+
and it is empty when the read is unavailable, which leaves role labels to the
|
|
41
|
+
roles members hold and is reported once on the console with its cause.
|
|
42
|
+
Concurrent loads are coalesced, the settled successful directory is reused for
|
|
43
|
+
the client/project lifetime, and a failed membership load retries after a
|
|
44
|
+
30-second backoff. That backoff does not cover the catalog: an unavailable one
|
|
45
|
+
is held empty for the client's lifetime rather than re-read. A directory that has not
|
|
40
46
|
answered reports `loading`, including before its fetch starts (the fetch
|
|
41
47
|
begins in a consumer's effect), so an empty `users` with `loading: false`
|
|
42
48
|
and no `error` is a confirmed "no members" rather than a directory nobody
|
package/dist/index.cjs
CHANGED
|
@@ -194,11 +194,11 @@ function isRecord(value) {
|
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
function isRole(value) {
|
|
197
|
-
return isRecord(value) && typeof value.name == "string";
|
|
197
|
+
return isRecord(value) && typeof value.name == "string" && (value.title === void 0 || typeof value.title == "string");
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
function isMembership(value) {
|
|
201
|
-
return isRecord(value) && typeof value.id == "string" && (value.isRobot === void 0 || typeof value.isRobot == "boolean") && (value.roles === void 0 || Array.isArray(value.roles) && value.roles.every(isRole));
|
|
201
|
+
return isRecord(value) && typeof value.id == "string" && (value.isRobot === void 0 || typeof value.isRobot == "boolean") && (value.isCurrentUser === void 0 || typeof value.isCurrentUser == "boolean") && (value.roles === void 0 || Array.isArray(value.roles) && value.roles.every(isRole));
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
async function fetchProjectUsers(client, projectId) {
|
|
@@ -226,6 +226,7 @@ const projectUsersByClient = /* @__PURE__ */ new WeakMap;
|
|
|
226
226
|
function createProjectUsersStore(client, projectId) {
|
|
227
227
|
let snapshot = {
|
|
228
228
|
users: [],
|
|
229
|
+
roles: [],
|
|
229
230
|
loading: !0,
|
|
230
231
|
error: void 0
|
|
231
232
|
}, pending, retryAt = 0, usersById = /* @__PURE__ */ new Map, usersByEmail = /* @__PURE__ */ new Map;
|
|
@@ -238,18 +239,23 @@ function createProjectUsersStore(client, projectId) {
|
|
|
238
239
|
...snapshot,
|
|
239
240
|
loading: !0,
|
|
240
241
|
error: void 0
|
|
241
|
-
}), pending = fetchProjectUsers(client, projectId)
|
|
242
|
+
}), pending = Promise.all([ fetchProjectUsers(client, projectId), projectUsers.loadProjectRolesOrNone({
|
|
243
|
+
client: client,
|
|
244
|
+
projectId: projectId
|
|
245
|
+
}) ]).then(([users, roles]) => (usersById = new Map(users.flatMap(user => {
|
|
242
246
|
const entries = [ [ user.membership.id, user ] ], globalId = user.profile?.sanityUserId;
|
|
243
247
|
return globalId !== void 0 && globalId !== user.membership.id && entries.push([ globalId, user ]),
|
|
244
248
|
entries;
|
|
245
249
|
})), usersByEmail = new Map(users.flatMap(user => user.profile?.email ? [ [ user.profile.email.toLowerCase(), user ] ] : [])),
|
|
246
250
|
retryAt = 1 / 0, setSnapshot({
|
|
247
251
|
users: users,
|
|
252
|
+
roles: roles,
|
|
248
253
|
loading: !1,
|
|
249
254
|
error: void 0
|
|
250
255
|
}), pending = void 0, users), error => {
|
|
251
256
|
throw retryAt = Date.now() + PROJECT_USERS_RETRY_MS, setSnapshot({
|
|
252
257
|
users: [],
|
|
258
|
+
roles: [],
|
|
253
259
|
loading: !1,
|
|
254
260
|
error: error
|
|
255
261
|
}), pending = void 0, error;
|
|
@@ -316,6 +322,7 @@ function studioProjectUserDirectory(client, projectId) {
|
|
|
316
322
|
|
|
317
323
|
const missingProjectSnapshot = {
|
|
318
324
|
users: [],
|
|
325
|
+
roles: [],
|
|
319
326
|
loading: !1,
|
|
320
327
|
error: new Error("No projectId on the Studio client")
|
|
321
328
|
}, noopSubscribe = () => () => {};
|
package/dist/index.d.cts
CHANGED
|
@@ -6,6 +6,7 @@ import type { GdrUri } from "@sanity/workflow-engine";
|
|
|
6
6
|
import type { InstancesQueryFilter } from "@sanity/workflow-engine";
|
|
7
7
|
import { MissingHandlerPolicy } from "@sanity/workflow-engine";
|
|
8
8
|
import { MutationGuardDoc } from "@sanity/workflow-react";
|
|
9
|
+
import { ProjectRole } from "@sanity/workflow-sdk/project-users";
|
|
9
10
|
import { ProjectUserDirectory } from "@sanity/workflow-engine";
|
|
10
11
|
import { ProjectUserProfile } from "@sanity/workflow-sdk/project-users";
|
|
11
12
|
import { ResourceClientResolver } from "@sanity/workflow-engine";
|
|
@@ -39,8 +40,10 @@ export declare interface StudioObserverOptions {
|
|
|
39
40
|
export declare interface StudioProjectMembership {
|
|
40
41
|
readonly id: string;
|
|
41
42
|
readonly isRobot?: boolean;
|
|
43
|
+
readonly isCurrentUser?: boolean;
|
|
42
44
|
readonly roles?: readonly {
|
|
43
45
|
readonly name: string;
|
|
46
|
+
readonly title?: string;
|
|
44
47
|
}[];
|
|
45
48
|
readonly [key: string]: unknown;
|
|
46
49
|
}
|
|
@@ -59,6 +62,9 @@ export declare function studioProjectUserDirectory(
|
|
|
59
62
|
|
|
60
63
|
export declare interface StudioProjectUsersState {
|
|
61
64
|
readonly users: readonly StudioProjectUser[];
|
|
65
|
+
/** The project's assignable roles, whether or not anyone holds them. Empty
|
|
66
|
+
* when the read is unavailable — see {@link loadProjectRolesOrNone}. */
|
|
67
|
+
readonly roles: readonly ProjectRole[];
|
|
62
68
|
/** True while the directory has not answered, including before its load
|
|
63
69
|
* starts — the fetch begins in a consumer's effect or the first directory
|
|
64
70
|
* lookup, one commit after the first render reads this state. An empty
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { GdrUri } from "@sanity/workflow-engine";
|
|
|
6
6
|
import type { InstancesQueryFilter } from "@sanity/workflow-engine";
|
|
7
7
|
import { MissingHandlerPolicy } from "@sanity/workflow-engine";
|
|
8
8
|
import { MutationGuardDoc } from "@sanity/workflow-react";
|
|
9
|
+
import { ProjectRole } from "@sanity/workflow-sdk/project-users";
|
|
9
10
|
import { ProjectUserDirectory } from "@sanity/workflow-engine";
|
|
10
11
|
import { ProjectUserProfile } from "@sanity/workflow-sdk/project-users";
|
|
11
12
|
import { ResourceClientResolver } from "@sanity/workflow-engine";
|
|
@@ -39,8 +40,10 @@ export declare interface StudioObserverOptions {
|
|
|
39
40
|
export declare interface StudioProjectMembership {
|
|
40
41
|
readonly id: string;
|
|
41
42
|
readonly isRobot?: boolean;
|
|
43
|
+
readonly isCurrentUser?: boolean;
|
|
42
44
|
readonly roles?: readonly {
|
|
43
45
|
readonly name: string;
|
|
46
|
+
readonly title?: string;
|
|
44
47
|
}[];
|
|
45
48
|
readonly [key: string]: unknown;
|
|
46
49
|
}
|
|
@@ -59,6 +62,9 @@ export declare function studioProjectUserDirectory(
|
|
|
59
62
|
|
|
60
63
|
export declare interface StudioProjectUsersState {
|
|
61
64
|
readonly users: readonly StudioProjectUser[];
|
|
65
|
+
/** The project's assignable roles, whether or not anyone holds them. Empty
|
|
66
|
+
* when the read is unavailable — see {@link loadProjectRolesOrNone}. */
|
|
67
|
+
readonly roles: readonly ProjectRole[];
|
|
62
68
|
/** True while the directory has not answered, including before its load
|
|
63
69
|
* starts — the fetch begins in a consumer's effect or the first directory
|
|
64
70
|
* lookup, one commit after the first render reads this state. An empty
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import { useSource, useClient } from "sanity";
|
|
|
12
12
|
|
|
13
13
|
import { useWorkflowTelemetry, useWorkflowSession as useWorkflowSession$1, useDocumentWorkflows as useDocumentWorkflows$1, useWorkflowInstances as useWorkflowInstances$1 } from "@sanity/workflow-react";
|
|
14
14
|
|
|
15
|
-
import { loadProjectUserProfiles } from "@sanity/workflow-sdk/project-users";
|
|
15
|
+
import { loadProjectRolesOrNone, loadProjectUserProfiles } from "@sanity/workflow-sdk/project-users";
|
|
16
16
|
|
|
17
17
|
function makeStudioObserver(sdk, options) {
|
|
18
18
|
const {client: client, engineResource: engineResource} = options;
|
|
@@ -202,11 +202,11 @@ function isRecord(value) {
|
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
function isRole(value) {
|
|
205
|
-
return isRecord(value) && typeof value.name == "string";
|
|
205
|
+
return isRecord(value) && typeof value.name == "string" && (value.title === void 0 || typeof value.title == "string");
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
function isMembership(value) {
|
|
209
|
-
return isRecord(value) && typeof value.id == "string" && (value.isRobot === void 0 || typeof value.isRobot == "boolean") && (value.roles === void 0 || Array.isArray(value.roles) && value.roles.every(isRole));
|
|
209
|
+
return isRecord(value) && typeof value.id == "string" && (value.isRobot === void 0 || typeof value.isRobot == "boolean") && (value.isCurrentUser === void 0 || typeof value.isCurrentUser == "boolean") && (value.roles === void 0 || Array.isArray(value.roles) && value.roles.every(isRole));
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
async function fetchProjectUsers(client, projectId) {
|
|
@@ -234,6 +234,7 @@ const projectUsersByClient = /* @__PURE__ */ new WeakMap;
|
|
|
234
234
|
function createProjectUsersStore(client, projectId) {
|
|
235
235
|
let snapshot = {
|
|
236
236
|
users: [],
|
|
237
|
+
roles: [],
|
|
237
238
|
loading: !0,
|
|
238
239
|
error: void 0
|
|
239
240
|
}, pending, retryAt = 0, usersById = /* @__PURE__ */ new Map, usersByEmail = /* @__PURE__ */ new Map;
|
|
@@ -246,18 +247,23 @@ function createProjectUsersStore(client, projectId) {
|
|
|
246
247
|
...snapshot,
|
|
247
248
|
loading: !0,
|
|
248
249
|
error: void 0
|
|
249
|
-
}), pending = fetchProjectUsers(client, projectId)
|
|
250
|
+
}), pending = Promise.all([ fetchProjectUsers(client, projectId), loadProjectRolesOrNone({
|
|
251
|
+
client: client,
|
|
252
|
+
projectId: projectId
|
|
253
|
+
}) ]).then(([users, roles]) => (usersById = new Map(users.flatMap(user => {
|
|
250
254
|
const entries = [ [ user.membership.id, user ] ], globalId = user.profile?.sanityUserId;
|
|
251
255
|
return globalId !== void 0 && globalId !== user.membership.id && entries.push([ globalId, user ]),
|
|
252
256
|
entries;
|
|
253
257
|
})), usersByEmail = new Map(users.flatMap(user => user.profile?.email ? [ [ user.profile.email.toLowerCase(), user ] ] : [])),
|
|
254
258
|
retryAt = 1 / 0, setSnapshot({
|
|
255
259
|
users: users,
|
|
260
|
+
roles: roles,
|
|
256
261
|
loading: !1,
|
|
257
262
|
error: void 0
|
|
258
263
|
}), pending = void 0, users), error => {
|
|
259
264
|
throw retryAt = Date.now() + PROJECT_USERS_RETRY_MS, setSnapshot({
|
|
260
265
|
users: [],
|
|
266
|
+
roles: [],
|
|
261
267
|
loading: !1,
|
|
262
268
|
error: error
|
|
263
269
|
}), pending = void 0, error;
|
|
@@ -324,6 +330,7 @@ function studioProjectUserDirectory(client, projectId) {
|
|
|
324
330
|
|
|
325
331
|
const missingProjectSnapshot = {
|
|
326
332
|
users: [],
|
|
333
|
+
roles: [],
|
|
327
334
|
loading: !1,
|
|
328
335
|
error: new Error("No projectId on the Studio client")
|
|
329
336
|
}, noopSubscribe = () => () => {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/workflow-studio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "React adapter that drives the @sanity/workflow-engine reactive session in Sanity Studio through the App SDK store.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -50,17 +50,17 @@
|
|
|
50
50
|
"react-dom": "^19.2.7",
|
|
51
51
|
"sanity": "^6",
|
|
52
52
|
"vitest": "^4.1.8",
|
|
53
|
-
"@sanity/workflow-engine": "0.
|
|
54
|
-
"@sanity/workflow-
|
|
55
|
-
"@sanity/workflow-
|
|
53
|
+
"@sanity/workflow-engine": "0.28.0",
|
|
54
|
+
"@sanity/workflow-react": "0.28.0",
|
|
55
|
+
"@sanity/workflow-sdk": "0.28.0"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"@sanity/sdk": "^2.12.0",
|
|
59
59
|
"react": "^19.2.7",
|
|
60
60
|
"sanity": "^6",
|
|
61
|
-
"@sanity/workflow-
|
|
62
|
-
"@sanity/workflow-
|
|
63
|
-
"@sanity/workflow-sdk": "0.
|
|
61
|
+
"@sanity/workflow-engine": "0.28.0",
|
|
62
|
+
"@sanity/workflow-react": "0.28.0",
|
|
63
|
+
"@sanity/workflow-sdk": "0.28.0"
|
|
64
64
|
},
|
|
65
65
|
"engines": {
|
|
66
66
|
"node": ">=20"
|