@sanity/workbench 0.1.0-alpha.2 → 0.1.0-alpha.20

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.
Files changed (48) hide show
  1. package/README.md +24 -0
  2. package/dist/_chunks-es/index.js +39 -0
  3. package/dist/_chunks-es/index.js.map +1 -0
  4. package/dist/_chunks-es/studio.js +864 -0
  5. package/dist/_chunks-es/studio.js.map +1 -0
  6. package/dist/_internal.d.ts +16 -4
  7. package/dist/_internal.js +34 -26
  8. package/dist/_internal.js.map +1 -1
  9. package/dist/core.d.ts +2138 -0
  10. package/dist/core.js +75 -0
  11. package/dist/core.js.map +1 -0
  12. package/dist/system.d.ts +2120 -0
  13. package/dist/system.js +888 -0
  14. package/dist/system.js.map +1 -0
  15. package/package.json +33 -6
  16. package/src/_exports/core.ts +1 -0
  17. package/src/_exports/system.ts +1 -0
  18. package/src/_internal/index.ts +2 -1
  19. package/src/_internal/render.ts +72 -42
  20. package/src/core/applications/application-list.ts +104 -0
  21. package/src/core/applications/application.ts +174 -0
  22. package/src/core/canvases.ts +92 -0
  23. package/src/core/config.ts +34 -0
  24. package/src/core/env.ts +43 -0
  25. package/src/core/index.ts +13 -0
  26. package/src/core/log/index.ts +125 -0
  27. package/src/core/media-libraries.ts +93 -0
  28. package/src/core/organizations.ts +115 -0
  29. package/src/core/projects.ts +114 -0
  30. package/src/core/shared/urls.ts +129 -0
  31. package/src/core/user-applications/core-app.ts +148 -0
  32. package/src/core/user-applications/studios/index.ts +3 -0
  33. package/src/core/user-applications/studios/schemas.ts +128 -0
  34. package/src/core/user-applications/studios/studio.ts +533 -0
  35. package/src/core/user-applications/studios/workspace.ts +152 -0
  36. package/src/core/user-applications/user-application.ts +222 -0
  37. package/src/system/auth.machine.ts +223 -0
  38. package/src/system/index.ts +22 -0
  39. package/src/system/inspect.ts +40 -0
  40. package/src/system/load-federated-module.ts +54 -0
  41. package/src/system/remote.machine.ts +219 -0
  42. package/src/system/remotes.machine.ts +92 -0
  43. package/src/system/root.machine.ts +224 -0
  44. package/src/system/service.machine.ts +207 -0
  45. package/src/system/services.machine.ts +120 -0
  46. package/src/system/system-preferences.machine.ts +215 -0
  47. package/src/system/telemetry.machine.ts +179 -0
  48. package/src/_internal/render.test.ts +0 -18
@@ -0,0 +1,864 @@
1
+ import { z } from "zod";
2
+ import { valid, coerce, gt, lt, gte, rsort } from "semver";
3
+ import { LocalInterfaceSchema } from "@sanity/federation";
4
+ class AbstractApplication {
5
+ type;
6
+ constructor(type) {
7
+ this.type = type;
8
+ }
9
+ /**
10
+ * Whether the application is served by a local CLI dev server rather than a
11
+ * deployed remote. Defaults to `false`; user applications flip this when
12
+ * constructed from a `LocalUserApplication` payload.
13
+ */
14
+ get isLocal() {
15
+ return !1;
16
+ }
17
+ /**
18
+ * Interfaces the application exposes (e.g. dock panels). Defaults to none;
19
+ * user applications surface what they declared via `unstable_defineApp`.
20
+ */
21
+ get interfaces() {
22
+ return [];
23
+ }
24
+ /**
25
+ * Whether the application has a navigable full-page `app` view (US5). Defaults
26
+ * to `true` — studios, Canvas, and Media Library are always navigable. An SDK
27
+ * app overrides this to derive it from whether it declares an `app` interface.
28
+ */
29
+ get hasAppView() {
30
+ return !0;
31
+ }
32
+ /**
33
+ * Federation module id of the app's full-page view (`${id}/App`), or `null`
34
+ * when it can't be federation-loaded: the app isn't federated (Canvas, Media
35
+ * Library, deployed apps shown in an iframe) or has no app view. Only a
36
+ * non-null `moduleId` is fetched and rendered by the remotes machine.
37
+ */
38
+ get moduleId() {
39
+ return this.isFederated && this.hasAppView ? `${this.id}/App` : null;
40
+ }
41
+ /**
42
+ * Federation module id of one of this app's panel view components, or `null`
43
+ * when the app isn't federated (a non-federated app's panels can't be loaded
44
+ * as remotes).
45
+ */
46
+ resolveViewModuleId(view, component) {
47
+ return this.isFederated ? `${this.id}/views/${view.name}/${component}` : null;
48
+ }
49
+ get initials() {
50
+ const SYMBOLS = /[^\p{Alpha}\p{N}\p{White_Space}]/gu, WHITESPACE = new RegExp("\\p{White_Space}+", "u"), ALPHANUMERIC_SEGMENTS = new RegExp("(\\p{N}+|\\p{Alpha}+)", "gu"), IS_NUMERIC = new RegExp("^\\p{N}+$", "u");
51
+ if (!this.title) return "";
52
+ const namesArray = this.title.replace(SYMBOLS, "").split(WHITESPACE).filter(Boolean);
53
+ if (namesArray.length === 0) return "";
54
+ if (namesArray.length === 1) {
55
+ const word = namesArray[0], segments = word.match(ALPHANUMERIC_SEGMENTS) || [];
56
+ return segments.length === 0 ? "" : segments.length === 1 ? word.length === 1 ? word.toUpperCase() : IS_NUMERIC.test(word) ? `${word.charAt(0)}${word.charAt(1)}`.toUpperCase() : word.charAt(0).toUpperCase() : `${segments[0].charAt(0)}${segments[1].charAt(0)}`.toUpperCase();
57
+ }
58
+ return `${namesArray[0].charAt(0)}${namesArray[namesArray.length - 1].charAt(0)}`.toUpperCase();
59
+ }
60
+ }
61
+ function getSanityDomain() {
62
+ return getSanityEnv() === "staging" ? "sanity.work" : "sanity.io";
63
+ }
64
+ function getApiHost() {
65
+ return `https://api.${getSanityDomain()}`;
66
+ }
67
+ function getSanityEnv() {
68
+ return typeof __SANITY_STAGING__ < "u" && __SANITY_STAGING__ === !0 ? "staging" : "production";
69
+ }
70
+ const OrganizationId = z.string().nonempty().brand("OrganizationId");
71
+ function brandOrganizationId(id) {
72
+ return OrganizationId.parse(id);
73
+ }
74
+ const OrganizationMember = z.object({
75
+ sanityUserId: z.string(),
76
+ isCurrentUser: z.boolean(),
77
+ user: z.object({
78
+ id: z.string(),
79
+ displayName: z.string(),
80
+ familyName: z.string(),
81
+ givenName: z.string(),
82
+ middleName: z.string().nullable(),
83
+ imageUrl: z.string().nullable(),
84
+ email: z.string(),
85
+ loginProvider: z.string()
86
+ }),
87
+ roles: z.array(
88
+ z.object({
89
+ name: z.string(),
90
+ title: z.string(),
91
+ description: z.string().optional()
92
+ })
93
+ )
94
+ }), Organization = z.object({
95
+ id: OrganizationId,
96
+ name: z.string(),
97
+ slug: z.string().nullable(),
98
+ createdAt: z.string(),
99
+ updatedAt: z.string(),
100
+ dashboardStatus: z.enum(["enabled", "disabled"]),
101
+ aiFeaturesStatus: z.enum(["enabled", "disabled"]),
102
+ defaultRoleName: z.string().nullable()
103
+ });
104
+ function parseOrganization(data, options) {
105
+ const includeMembers = options?.includeMembers ?? !0, includeFeatures = options?.includeFeatures ?? !0, extensions = {
106
+ ...includeMembers && {
107
+ members: z.array(OrganizationMember)
108
+ },
109
+ ...includeFeatures && {
110
+ features: z.array(z.string())
111
+ }
112
+ };
113
+ return (Object.keys(extensions).length > 0 ? Organization.extend(extensions) : Organization).parse(data);
114
+ }
115
+ const CanvasId = z.string().nonempty().brand("CanvasId");
116
+ function brandCanvasId(id) {
117
+ return CanvasId.parse(id);
118
+ }
119
+ const Canvas = z.object({
120
+ id: CanvasId,
121
+ organizationId: OrganizationId,
122
+ status: z.enum(["active", "provisioning"])
123
+ });
124
+ function parseCanvas(data) {
125
+ return Canvas.parse(data);
126
+ }
127
+ class CanvasApplication extends AbstractApplication {
128
+ canvas;
129
+ constructor(canvas) {
130
+ super("canvas"), this.canvas = canvas;
131
+ }
132
+ get id() {
133
+ return this.canvas.id;
134
+ }
135
+ get href() {
136
+ return "canvas";
137
+ }
138
+ get title() {
139
+ return "Canvas";
140
+ }
141
+ get url() {
142
+ return new URL(
143
+ `https://canvas.${getSanityDomain()}/o/${this.canvas.organizationId}`
144
+ );
145
+ }
146
+ get isFederated() {
147
+ return !1;
148
+ }
149
+ toProtocolResource() {
150
+ return {
151
+ type: "canvas",
152
+ id: this.id
153
+ };
154
+ }
155
+ }
156
+ const MediaLibraryId = z.string().nonempty().brand("MediaLibraryId");
157
+ function brandMediaLibraryId(id) {
158
+ return MediaLibraryId.parse(id);
159
+ }
160
+ const MediaLibrary = z.object({
161
+ id: MediaLibraryId,
162
+ organizationId: OrganizationId,
163
+ status: z.enum(["active", "provisioning"]),
164
+ aclMode: z.enum(["private", "public"])
165
+ });
166
+ function parseMediaLibrary(data) {
167
+ return MediaLibrary.parse(data);
168
+ }
169
+ class MediaLibraryApplication extends AbstractApplication {
170
+ library;
171
+ constructor(library) {
172
+ super("media-library"), this.library = library;
173
+ }
174
+ get id() {
175
+ return this.library.id;
176
+ }
177
+ get href() {
178
+ return "media";
179
+ }
180
+ get title() {
181
+ return "Media Library";
182
+ }
183
+ get isFederated() {
184
+ return !1;
185
+ }
186
+ get url() {
187
+ return new URL(
188
+ `https://media.${getSanityDomain()}/${this.library.organizationId}`
189
+ );
190
+ }
191
+ toProtocolResource() {
192
+ return {
193
+ type: "media-library",
194
+ id: this.id
195
+ };
196
+ }
197
+ }
198
+ const ProjectId = z.string().nonempty().brand("ProjectId");
199
+ function brandProjectId(id) {
200
+ return ProjectId.parse(id);
201
+ }
202
+ const ProjectMember = z.object({
203
+ id: z.string(),
204
+ createdAt: z.string(),
205
+ updatedAt: z.string(),
206
+ isCurrentUser: z.boolean(),
207
+ isRobot: z.boolean(),
208
+ roles: z.array(
209
+ z.object({
210
+ name: z.string(),
211
+ title: z.string(),
212
+ description: z.string()
213
+ })
214
+ )
215
+ }), Project = z.object({
216
+ id: ProjectId,
217
+ displayName: z.string(),
218
+ studioHost: z.string().nullable(),
219
+ organizationId: OrganizationId,
220
+ metadata: z.object({
221
+ color: z.string().optional(),
222
+ externalStudioHost: z.string().optional(),
223
+ initialTemplate: z.string().optional(),
224
+ cliInitializedAt: z.string().optional(),
225
+ integration: z.string().optional()
226
+ }),
227
+ isBlocked: z.boolean(),
228
+ isDisabled: z.boolean(),
229
+ isDisabledByUser: z.boolean(),
230
+ activityFeedEnabled: z.boolean(),
231
+ createdAt: z.string(),
232
+ updatedAt: z.string()
233
+ });
234
+ function parseProject(data, options) {
235
+ const includeMembers = options?.includeMembers ?? !0, includeFeatures = options?.includeFeatures ?? !0, extensions = {
236
+ ...includeMembers && { members: z.array(ProjectMember) },
237
+ ...includeFeatures && { features: z.array(z.string()) }
238
+ };
239
+ return (Object.keys(extensions).length > 0 ? Project.extend(extensions) : Project).parse(data);
240
+ }
241
+ const joinUrlPaths = (...paths) => {
242
+ let nextPath = null;
243
+ const safeJoinSegments = (segment1, segment2) => segment1 ? segment2 ? segment1.endsWith("/") && segment2.startsWith("/") ? segment1 + segment2.slice(1) : segment1.endsWith("/") || segment2.startsWith("/") ? segment1 + segment2 : `${segment1}/${segment2}` : segment1 : segment2 || "", validPaths = paths.filter(
244
+ (path) => path != null
245
+ );
246
+ for (const path of validPaths)
247
+ nextPath = safeJoinSegments(
248
+ nextPath,
249
+ path instanceof URL ? path.pathname : path
250
+ );
251
+ return nextPath ?? "";
252
+ };
253
+ function normalizePath(pathname) {
254
+ return pathname.startsWith("/") || (pathname = `/${pathname}`), pathname !== "/" && pathname.endsWith("/") && (pathname = pathname.slice(0, -1)), pathname;
255
+ }
256
+ const UserApplicationId = z.string().nonempty().brand("UserApplicationId");
257
+ function brandUserApplicationId(id) {
258
+ return UserApplicationId.parse(id);
259
+ }
260
+ const LocalUserApplicationBase = z.object({
261
+ host: z.string(),
262
+ port: z.number(),
263
+ /**
264
+ * Interfaces the app exposes — dock panels (`interface_type: "panel"`) and
265
+ * background workers (`interface_type: "worker"`), forwarded from the dev
266
+ * server in one list.
267
+ */
268
+ interfaces: z.array(LocalInterfaceSchema).optional(),
269
+ /** The `deployment.appId` from the application's `sanity.cli.ts`, when set. */
270
+ id: z.string().optional(),
271
+ /**
272
+ * The `api.projectId` from the application's `sanity.cli.ts`. Available
273
+ * synchronously at dev-server startup (no manifest extraction required), so
274
+ * the studio's primary project is resolvable from the very first local
275
+ * application event.
276
+ */
277
+ projectId: z.string().optional()
278
+ }), LocalUserApplication = z.discriminatedUnion("type", [
279
+ LocalUserApplicationBase.extend({
280
+ type: z.literal("studio"),
281
+ manifest: z.custom().optional()
282
+ }),
283
+ LocalUserApplicationBase.extend({
284
+ type: z.literal("coreApp"),
285
+ manifest: z.custom().optional()
286
+ })
287
+ ]), UserApplicationBase = z.object({
288
+ id: UserApplicationId,
289
+ appHost: z.string(),
290
+ urlType: z.enum(["internal", "external"]),
291
+ createdAt: z.string(),
292
+ updatedAt: z.string(),
293
+ dashboardStatus: z.enum(["default", "disabled"]),
294
+ /**
295
+ * Interfaces the app exposes (dock panels and background workers). Not part
296
+ * of the manifest.
297
+ */
298
+ interfaces: z.array(LocalInterfaceSchema).optional()
299
+ }), ActiveDeployment = z.object({
300
+ id: z.string(),
301
+ version: z.string(),
302
+ isActiveDeployment: z.boolean(),
303
+ userApplicationId: z.string(),
304
+ isAutoUpdating: z.boolean(),
305
+ size: z.number(),
306
+ deployedAt: z.string(),
307
+ deployedBy: z.string(),
308
+ createdAt: z.string(),
309
+ updatedAt: z.string()
310
+ });
311
+ class UserApplication extends AbstractApplication {
312
+ application;
313
+ id;
314
+ /**
315
+ * For local applications (`isLocal === true`), the deployed application
316
+ * that shares the same `id` — if one was passed at construction. `null`
317
+ * for deployed applications or when no remote twin was provided.
318
+ */
319
+ remoteApplication;
320
+ #isLocal;
321
+ constructor(application, type, options = {}) {
322
+ super(type), this.application = application, this.id = brandUserApplicationId(application.id), this.#isLocal = options.isLocal ?? !1, this.remoteApplication = options.remoteApplication ?? null;
323
+ }
324
+ /**
325
+ * Local dev servers are rendered as federated remotes; deployed applications
326
+ * are rendered in an iframe.
327
+ */
328
+ get isFederated() {
329
+ return this.isLocal;
330
+ }
331
+ get isLocal() {
332
+ return this.#isLocal;
333
+ }
334
+ get interfaces() {
335
+ return this.application.interfaces ?? [];
336
+ }
337
+ /**
338
+ * @returns A fully resolved URL instance for the application.
339
+ * For internal applications, constructs a URL using the Sanity studio domain
340
+ * pattern resolved from the environment at the consuming app's build time.
341
+ * For external applications (including local dev servers), returns the
342
+ * provided app host as a URL.
343
+ */
344
+ get url() {
345
+ return this.application.urlType === "internal" ? getSanityEnv() === "production" ? new URL(`https://${this.application.appHost}.sanity.studio`) : new URL(
346
+ `https://${this.application.appHost}.studio.${getSanityDomain()}`
347
+ ) : new URL(this.application.appHost);
348
+ }
349
+ }
350
+ const CoreAppUserApplicationManifest = z.object({
351
+ version: z.string(),
352
+ icon: z.string().optional(),
353
+ title: z.string().optional(),
354
+ group: z.string().optional(),
355
+ priority: z.number().optional()
356
+ }), CoreAppUserApplicationBase = UserApplicationBase.extend({
357
+ title: z.string(),
358
+ organizationId: OrganizationId,
359
+ type: z.literal("coreApp")
360
+ }), CoreAppActiveDeployment = ActiveDeployment.extend({
361
+ manifest: CoreAppUserApplicationManifest.nullable()
362
+ }).nullable(), InternalCoreAppUserApplication = CoreAppUserApplicationBase.extend({
363
+ urlType: z.literal("internal"),
364
+ activeDeployment: CoreAppActiveDeployment
365
+ }), ExternalCoreAppUserApplication = CoreAppUserApplicationBase.extend({
366
+ urlType: z.literal("external"),
367
+ activeDeployment: CoreAppActiveDeployment
368
+ }), CoreAppUserApplication = z.discriminatedUnion("urlType", [
369
+ InternalCoreAppUserApplication,
370
+ ExternalCoreAppUserApplication
371
+ ]);
372
+ function parseCoreApplication(data) {
373
+ return CoreAppUserApplication.parse(data);
374
+ }
375
+ class CoreAppApplication extends UserApplication {
376
+ activeDeployment;
377
+ constructor(application, options = {}) {
378
+ super(application, "coreApp", options), this.activeDeployment = application.activeDeployment;
379
+ }
380
+ // Typed `string | null` so the UI-aware remote subclass can override it to
381
+ // `null` for a non-navigable app (US5); the core route itself is always set.
382
+ get href() {
383
+ return this.isLocal ? `/local/${this.id}` : `/application/${this.application.id}`;
384
+ }
385
+ get title() {
386
+ return this.manifest?.title ?? this.application.title;
387
+ }
388
+ /**
389
+ * Resolves the core app's manifest from `activeDeployment.manifest`. This
390
+ * is the canonical (and only) slot for core app manifests — both for
391
+ * Sanity-deployed apps and for local CLI dev-server apps, which synthesise
392
+ * a deployment to surface the live manifest.
393
+ */
394
+ get manifest() {
395
+ return this.activeDeployment?.manifest ?? null;
396
+ }
397
+ get subtitle() {
398
+ }
399
+ get updatedAt() {
400
+ return this.application.updatedAt;
401
+ }
402
+ get(attr) {
403
+ if (!(attr in this.application))
404
+ throw new Error(
405
+ `Attribute ${attr.toString()} does not exist on application ${this.application.id}`
406
+ );
407
+ return this.application[attr];
408
+ }
409
+ toProtocolResource() {
410
+ return {
411
+ ...this.application,
412
+ type: "application",
413
+ url: this.url.toString()
414
+ };
415
+ }
416
+ }
417
+ const Workspace = z.object({
418
+ name: z.string(),
419
+ title: z.string(),
420
+ subtitle: z.string().optional(),
421
+ basePath: z.string(),
422
+ projectId: ProjectId,
423
+ dataset: z.string().optional(),
424
+ icon: z.string().nullable().optional()
425
+ }), ServerManifest = z.object({
426
+ buildId: z.string().optional(),
427
+ bundleVersion: z.string().optional(),
428
+ version: z.string().optional(),
429
+ group: z.string().optional(),
430
+ priority: z.number().optional(),
431
+ workspaces: z.array(
432
+ Workspace.extend({
433
+ dataset: z.string(),
434
+ schemaDescriptorId: z.string()
435
+ })
436
+ ).optional()
437
+ }), ClientManifest = z.object({
438
+ version: z.number(),
439
+ createdAt: z.string(),
440
+ studioVersion: z.string().optional(),
441
+ group: z.string().optional(),
442
+ priority: z.number().optional(),
443
+ workspaces: z.array(
444
+ Workspace.extend({
445
+ schema: z.string(),
446
+ tools: z.string().optional()
447
+ })
448
+ )
449
+ }), StudioUserApplicationBase = UserApplicationBase.extend({
450
+ title: z.string().nullable(),
451
+ projectId: ProjectId,
452
+ type: z.literal("studio"),
453
+ /**
454
+ * @deprecated Use `manifestData` instead.
455
+ */
456
+ manifest: ClientManifest.nullable(),
457
+ manifestData: z.object({ value: ClientManifest }).nullable(),
458
+ autoUpdatingVersion: z.string().nullable(),
459
+ config: z.object({
460
+ "live-manifest": z.object({
461
+ createdAt: z.string(),
462
+ updatedAt: z.string(),
463
+ updatedBy: z.string(),
464
+ value: ServerManifest
465
+ }).optional()
466
+ })
467
+ }), InternalStudioUserApplication = StudioUserApplicationBase.extend({
468
+ urlType: z.literal("internal"),
469
+ activeDeployment: ActiveDeployment.extend({
470
+ manifest: ServerManifest.nullable()
471
+ }).nullable()
472
+ }), ExternalStudioUserApplication = StudioUserApplicationBase.extend({
473
+ urlType: z.literal("external"),
474
+ activeDeployment: ActiveDeployment.extend({
475
+ manifest: ServerManifest.nullable()
476
+ }).nullable()
477
+ }), StudioUserApplication = z.discriminatedUnion("urlType", [
478
+ InternalStudioUserApplication,
479
+ ExternalStudioUserApplication
480
+ ]);
481
+ function parseStudioUserApplication(data) {
482
+ return StudioUserApplication.parse(data);
483
+ }
484
+ class StudioWorkspace extends AbstractApplication {
485
+ /**
486
+ * Workspaces always belong to a studio application.
487
+ * They do not exist on their own & therefore can access
488
+ * information about the studio they're in via this property.
489
+ */
490
+ studioApplication;
491
+ workspace;
492
+ project;
493
+ isDefaultWorkspace;
494
+ constructor(studioApplication, workspace, project, isDefaultWorkspace) {
495
+ super("workspace"), this.studioApplication = studioApplication, this.workspace = workspace, this.project = project, this.isDefaultWorkspace = isDefaultWorkspace;
496
+ }
497
+ /**
498
+ * The studio application that this workspace belongs to.
499
+ */
500
+ get studio() {
501
+ return this.studioApplication;
502
+ }
503
+ get id() {
504
+ return StudioWorkspace.makeId(this.studio.id, this.workspace.name);
505
+ }
506
+ get href() {
507
+ return joinUrlPaths(this.studio.href, this.workspace.name);
508
+ }
509
+ get title() {
510
+ return this.isDefaultWorkspace ? this.project.displayName : this.workspace.title;
511
+ }
512
+ get subtitle() {
513
+ return this.isDefaultWorkspace ? (URL.canParse(this.studio.get("appHost")) ? new URL(this.studio.get("appHost")) : this.studio.url).hostname : this.get("subtitle");
514
+ }
515
+ get(attr) {
516
+ return this.workspace[attr];
517
+ }
518
+ get isFederated() {
519
+ return this.studio.isFederated;
520
+ }
521
+ /**
522
+ * With comlink, studio-applications were not considered applications at all, only workspaces. This is partially why
523
+ * we create default workspaces when the application has no manifest or the manifest has no workspaces.
524
+ *
525
+ * Thereby, to create it we depend on a lot of information from the parent application even if it's duplicated across
526
+ * different workspaces.
527
+ */
528
+ toProtocolResource() {
529
+ return {
530
+ ...this.workspace,
531
+ type: "studio",
532
+ userApplicationId: this.studio.id,
533
+ activeDeployment: this.studio.get("activeDeployment"),
534
+ autoUpdatingVersion: this.studio.get("autoUpdatingVersion"),
535
+ dashboardStatus: this.studio.get("dashboardStatus"),
536
+ url: this.studio.url.toString(),
537
+ href: this.href,
538
+ id: this.id,
539
+ hasManifest: !0,
540
+ hasSchema: !!("schema" in this.workspace && this.workspace.schema),
541
+ // The protocol resource requires the client-shaped manifest (workspaces
542
+ // with `schema`). Read those sources directly rather than the unified
543
+ // `manifest` getter, which may return a `ServerManifest` for studios
544
+ // with a deployment manifest.
545
+ manifest: this.studio.get("manifestData")?.value ?? this.studio.get("manifest") ?? null,
546
+ updatedAt: this.studio.get("updatedAt"),
547
+ version: this.studio.get("activeDeployment")?.version,
548
+ urlType: this.studio.get("urlType"),
549
+ config: this.studio.get("config")
550
+ };
551
+ }
552
+ /**
553
+ * @returns the URL to the workspace of a studio application.
554
+ */
555
+ get url() {
556
+ const studioUrl = new URL(this.studio.url), normalizedUrlPath = normalizePath(studioUrl.pathname);
557
+ let finalBasePath = normalizePath(this.get("basePath"));
558
+ return finalBasePath.startsWith(normalizedUrlPath) && (finalBasePath = finalBasePath.slice(normalizedUrlPath.length)), studioUrl.pathname = joinUrlPaths(normalizedUrlPath, finalBasePath), studioUrl;
559
+ }
560
+ static makeId(applicationId, workspaceName) {
561
+ return `${applicationId}-${workspaceName}`;
562
+ }
563
+ static splitId(id) {
564
+ return id.split(new RegExp(/-(.*)/)).slice(0, 2);
565
+ }
566
+ }
567
+ const Manifest = z.union([
568
+ ClientManifest.superRefine((data, ctx) => {
569
+ data.version || ctx.addIssue({
570
+ code: "invalid_type",
571
+ message: "Manifest version is too old",
572
+ expected: "number"
573
+ }), data.version < 2 && ctx.addIssue({
574
+ code: "invalid_value",
575
+ message: "Manifest version is too old",
576
+ values: [2, 3]
577
+ }), data.version >= 3 && !data.studioVersion && ctx.addIssue({
578
+ code: "invalid_type",
579
+ message: "Manifest version 3 or higher requires a `studioVersion`",
580
+ expected: "string"
581
+ });
582
+ }),
583
+ ServerManifest
584
+ ]), DEFAULT_WORKSPACE_DATA = {
585
+ name: "default",
586
+ title: "Default",
587
+ basePath: "/"
588
+ };
589
+ class StudioApplication extends UserApplication {
590
+ /**
591
+ * Returns a list of studio workspaces based on the application manifest.
592
+ * If there is no manifest, or alternatively it is not valid, then we create a default workspace.
593
+ */
594
+ workspaces = [];
595
+ project;
596
+ /**
597
+ * The projects actually referenced by this studio — the application's own
598
+ * project plus any project used by a workspace. Preserved so the instance
599
+ * can be re-constructed (e.g. by dock wrappers) without having to thread
600
+ * the full organization project list through again.
601
+ */
602
+ projects;
603
+ /**
604
+ * @param application - The studio application to create a list of workspaces for
605
+ * @param projects - The projects available in the organization. It's not enough to just pass
606
+ * the project that associates with the application because that is the project the app is deployed in relation to.
607
+ * The workspaces may have different projects completely.
608
+ */
609
+ constructor(application, projects, options = {}) {
610
+ super(application, "studio", options);
611
+ let workspaces = [];
612
+ const manifest = this.manifest;
613
+ if (manifest)
614
+ try {
615
+ workspaces = Manifest.parse(manifest).workspaces ?? [];
616
+ } catch (error) {
617
+ console.warn(
618
+ `Failed to parse manifest for application ${application.id}`,
619
+ error
620
+ );
621
+ }
622
+ const workspacesWithProjectsMap = workspaces.reduce((acc, workspace) => {
623
+ const project2 = projects.find((p) => p.id === workspace.projectId);
624
+ return project2 ? acc.set(workspace, project2) : console.warn(
625
+ `Project not found for application ${application.id} and workspace ${workspace.name}. This workspace has been omitted.`
626
+ ), acc;
627
+ }, /* @__PURE__ */ new Map()), project = projects.find((p) => p.id === application.projectId);
628
+ if (!project)
629
+ throw new Error(`Project not found for application ${application.id}`);
630
+ workspacesWithProjectsMap.size === 0 && workspacesWithProjectsMap.set(
631
+ {
632
+ ...DEFAULT_WORKSPACE_DATA,
633
+ projectId: application.projectId
634
+ },
635
+ project
636
+ ), this.workspaces = Object.freeze(
637
+ Array.from(workspacesWithProjectsMap.entries()).map(([workspace, p]) => {
638
+ const isDefaultWorkspace = workspace.name === DEFAULT_WORKSPACE_DATA.name && workspace.basePath === DEFAULT_WORKSPACE_DATA.basePath && workspace.title === DEFAULT_WORKSPACE_DATA.title;
639
+ return this.createWorkspace(workspace, p, isDefaultWorkspace);
640
+ })
641
+ ), this.project = project;
642
+ const usedProjects = /* @__PURE__ */ new Set([project]);
643
+ for (const workspace of this.workspaces)
644
+ usedProjects.add(workspace.project);
645
+ this.projects = Array.from(usedProjects);
646
+ }
647
+ /**
648
+ * Factory hook called for each workspace during construction. Subclasses
649
+ * override this to substitute a `StudioWorkspace` subclass (e.g. a UI-aware
650
+ * variant in a downstream package) without re-implementing the constructor's
651
+ * workspace-derivation logic.
652
+ */
653
+ createWorkspace(workspace, project, isDefaultWorkspace) {
654
+ return new StudioWorkspace(this, workspace, project, isDefaultWorkspace);
655
+ }
656
+ get href() {
657
+ return this.isLocal ? `/local/${this.id}` : `/studio/${this.application.id}`;
658
+ }
659
+ get title() {
660
+ return this.get("title") || (this.workspaces.length > 1 && this.get("urlType") === "internal" ? this.project.displayName : this.workspaces[0].title);
661
+ }
662
+ get subtitle() {
663
+ return new URL(this.url).hostname;
664
+ }
665
+ get(attr) {
666
+ if (!(attr in this.application))
667
+ throw new Error(
668
+ `Attribute ${attr.toString()} does not exist on studio ${this.application.id}`
669
+ );
670
+ return this.application[attr];
671
+ }
672
+ /**
673
+ * Resolves the studio's most authoritative manifest. The lookup order is:
674
+ *
675
+ * 1. `activeDeployment.manifest` — deployment manifests are the new
676
+ * primitive within Sanity and are preferred whenever available.
677
+ * 2. `manifestData.value` — fallback to support older studios that
678
+ * haven't produced a deployment manifest yet.
679
+ * 3. `application.manifest` — last-resort legacy field.
680
+ *
681
+ * Returns `null` when no manifest is available. The return shape is a
682
+ * union because the deployment and client manifests are not
683
+ * interchangeable — consumers that depend on client-only fields (e.g.
684
+ * `studioVersion`, workspace `schema`) must narrow or read the raw
685
+ * field they need.
686
+ */
687
+ get manifest() {
688
+ return this.application.activeDeployment?.manifest ?? this.application.manifestData?.value ?? this.application.manifest ?? null;
689
+ }
690
+ get hasManifest() {
691
+ const manifest = this.manifest;
692
+ return manifest ? typeof manifest.version != "number" || manifest.version >= 2 : !1;
693
+ }
694
+ get hasSchema() {
695
+ if (this.application.activeDeployment?.manifest) return !0;
696
+ const workspaces = (this.application.manifestData?.value ?? this.application.manifest)?.workspaces ?? [];
697
+ return workspaces.length === 0 ? !1 : workspaces.every((w) => !!w.schema);
698
+ }
699
+ resolveVersion() {
700
+ let version;
701
+ if (this.get("urlType") === "internal")
702
+ version = this.get("activeDeployment")?.version;
703
+ else {
704
+ const clientManifest = this.get("manifestData")?.value ?? this.get("manifest");
705
+ version = clientManifest && "studioVersion" in clientManifest ? clientManifest.studioVersion : void 0;
706
+ }
707
+ const bundleVersion = this.get("activeDeployment")?.manifest?.bundleVersion;
708
+ return bundleVersion && valid(coerce(bundleVersion)) && (!version || version && gt(bundleVersion, version)) && (version = bundleVersion), !version || !valid(coerce(version)) ? null : coerce(version);
709
+ }
710
+ get version() {
711
+ const version = this.resolveVersion();
712
+ return version ? version.toString() : null;
713
+ }
714
+ get compatibilityStatus() {
715
+ return StudioApplication.resolveCompatibilityStatus(this);
716
+ }
717
+ /**
718
+ * Used to calculate the compatibility status of a given studio application.
719
+ * Optionally if you've resolved the version elsewhere provide that value to
720
+ * get the new compatibility status without mutating the application.
721
+ */
722
+ static resolveCompatibilityStatus(application, version = application.version) {
723
+ return version === null || lt(version, StudioApplication.MinimumStudioVersion) ? StudioApplication.CompatibilityStatuses.UNKNOWN : !application.hasSchema || !application.hasManifest || StudioApplication.resolveIssues(application, version).length > 0 ? StudioApplication.CompatibilityStatuses.PARTIALLY_COMPATIBLE : StudioApplication.CompatibilityStatuses.FULLY_COMPATIBLE;
724
+ }
725
+ get isAutoRedirecting() {
726
+ return StudioApplication.resolveIsAutoRedirecting(this);
727
+ }
728
+ /**
729
+ * Mirrors the `isRedirectable` function from Saison defined in
730
+ * https://github.com/sanity-io/saison/blob/83556405d23e07f6d3a71c76249c67e33fe1101f/src/utils/applications.ts
731
+ *
732
+ * Returns whether a studio application is auto-redirecting, meaning it can only be accessed in the context of
733
+ * Dashboard.
734
+ */
735
+ static resolveIsAutoRedirecting(application, versionArg = application.version) {
736
+ let version = versionArg;
737
+ if (application.get("urlType") === "external" || !version)
738
+ return !1;
739
+ if (!application.get("activeDeployment")?.isAutoUpdating)
740
+ return gt(version, "3.92.0");
741
+ const autoUpdatingVersion = application.get("autoUpdatingVersion");
742
+ if (autoUpdatingVersion) {
743
+ if (["next", "stable", "latest"].includes(autoUpdatingVersion))
744
+ return !0;
745
+ const autoUpdatingVersionPinnedVersion = coerce(autoUpdatingVersion);
746
+ autoUpdatingVersionPinnedVersion && (version = autoUpdatingVersionPinnedVersion);
747
+ }
748
+ return gt(version, StudioApplication.MinimumStudioVersion);
749
+ }
750
+ /**
751
+ * Returns a list of issues that prevent the studio from functioning properly in the Dashboard.
752
+ * This static value depends on the version of the studio that comes from the `activeDeployment` property.
753
+ * As such, if the studio is auto-updating, this list will be incorrect & instead you should use
754
+ * the static method `resolveIssues` to get the correct issues by passing the resolved version.
755
+ */
756
+ get issues() {
757
+ return StudioApplication.resolveIssues(this);
758
+ }
759
+ static resolveIssues(application, version = application.version) {
760
+ const issues = StudioApplication.Features.filter(
761
+ (feature) => !application.isFeatureSupported(feature.id, version)
762
+ );
763
+ return application.hasManifest || issues.push({
764
+ id: StudioApplication.StudioIssues.ISSUE_MANIFEST
765
+ }), issues;
766
+ }
767
+ isFeatureSupported(feature, version = this.version) {
768
+ const featureVersion = StudioApplication.Features.find(
769
+ (_) => _.id === feature
770
+ )?.version;
771
+ return !featureVersion || !version ? !1 : gte(version, featureVersion);
772
+ }
773
+ toProtocolResource() {
774
+ throw new Error(
775
+ "Studio application resources cannot be converted to protocol resources"
776
+ );
777
+ }
778
+ static CompatibilityStatuses = {
779
+ UNKNOWN: "unknown",
780
+ PARTIALLY_COMPATIBLE: "partially-compatible",
781
+ FULLY_COMPATIBLE: "fully-compatible"
782
+ };
783
+ static StudioIssues = {
784
+ ISSUE_ACTIVITY: "ACTIVITY",
785
+ ISSUE_AGENT: "AGENT",
786
+ ISSUE_FAVORITES: "FAVORITES",
787
+ ISSUE_URL_SYNCING: "URL_SYNCING",
788
+ ISSUE_UI_ADJUSTMENT: "UI_ADJUSTMENT",
789
+ ISSUE_CONTENT_MAPPING: "CONTENT_MAPPING",
790
+ ISSUE_LOGIN: "LOGIN",
791
+ ISSUE_MANIFEST: "MANIFEST"
792
+ };
793
+ static Features = [
794
+ {
795
+ id: StudioApplication.StudioIssues.ISSUE_AGENT,
796
+ version: "5.1.0"
797
+ },
798
+ {
799
+ id: StudioApplication.StudioIssues.ISSUE_FAVORITES,
800
+ version: "3.88.1"
801
+ },
802
+ {
803
+ id: StudioApplication.StudioIssues.ISSUE_ACTIVITY,
804
+ version: "3.88.1"
805
+ },
806
+ {
807
+ id: StudioApplication.StudioIssues.ISSUE_URL_SYNCING,
808
+ version: "3.75.0"
809
+ },
810
+ {
811
+ id: StudioApplication.StudioIssues.ISSUE_UI_ADJUSTMENT,
812
+ version: "3.78.1"
813
+ },
814
+ {
815
+ id: StudioApplication.StudioIssues.ISSUE_CONTENT_MAPPING,
816
+ version: "3.68.0"
817
+ },
818
+ {
819
+ id: StudioApplication.StudioIssues.ISSUE_LOGIN,
820
+ version: "2.28.0"
821
+ }
822
+ ];
823
+ static MinimumStudioVersion = "2.28.0";
824
+ static MinimumStudioVersionWithNoIssues = rsort(
825
+ StudioApplication.Features.map((feature) => feature.version)
826
+ ).at(0);
827
+ }
828
+ export {
829
+ AbstractApplication,
830
+ ActiveDeployment,
831
+ CanvasApplication,
832
+ ClientManifest,
833
+ CoreAppApplication,
834
+ CoreAppUserApplicationManifest,
835
+ LocalUserApplication,
836
+ MediaLibraryApplication,
837
+ Organization,
838
+ OrganizationId,
839
+ Project,
840
+ ProjectId,
841
+ ServerManifest,
842
+ StudioApplication,
843
+ StudioUserApplication,
844
+ StudioWorkspace,
845
+ UserApplication,
846
+ UserApplicationBase,
847
+ UserApplicationId,
848
+ brandCanvasId,
849
+ brandMediaLibraryId,
850
+ brandOrganizationId,
851
+ brandProjectId,
852
+ brandUserApplicationId,
853
+ getApiHost,
854
+ getSanityDomain,
855
+ getSanityEnv,
856
+ joinUrlPaths,
857
+ parseCanvas,
858
+ parseCoreApplication,
859
+ parseMediaLibrary,
860
+ parseOrganization,
861
+ parseProject,
862
+ parseStudioUserApplication
863
+ };
864
+ //# sourceMappingURL=studio.js.map