@sanity/workbench 0.1.0-alpha.7 → 0.1.0-alpha.8

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.
@@ -1,31 +1,30 @@
1
- import type {
2
- StudioApplication as StudioApplicationApiResponse,
3
- // eslint-disable-next-line no-restricted-imports
4
- StudioResource as ProtocolStudioResource,
5
- } from "@sanity/message-protocol";
1
+ // eslint-disable-next-line no-restricted-imports
2
+ import type { StudioResource as ProtocolStudioResource } from "@sanity/message-protocol";
6
3
  import { afterEach, describe, expect, expectTypeOf, vi, it } from "vitest";
7
4
 
8
5
  import {
9
6
  MULTIPLE_WORKSPACE_STUDIO_DATA,
10
7
  PROJECTS,
11
8
  SINGLE_WORKSPACE_STUDIO_DATA,
12
- } from "../__tests__/__fixtures__";
13
- import type { ProjectId } from "../projects";
14
- import { StudioApplication, StudioWorkspace } from "./studio";
15
- import { brandUserApplicationId } from "./user-application";
9
+ } from "../../__tests__/__fixtures__";
10
+ import { brandProjectId, type ProjectId } from "../../projects";
11
+ import { brandUserApplicationId } from "../user-application";
12
+ import type { StudioUserApplication } from "./schemas";
13
+ import { StudioApplication } from "./studio";
14
+ import { StudioWorkspace } from "./workspace";
16
15
 
17
16
  describe("StudioApplication", () => {
18
17
  afterEach(() => {
19
18
  vi.unstubAllEnvs();
20
19
  });
21
20
 
22
- const setup = (application?: StudioApplicationApiResponse) =>
21
+ const setup = (overrides?: Record<string, unknown>) =>
23
22
  new StudioApplication(
24
23
  {
25
24
  ...MULTIPLE_WORKSPACE_STUDIO_DATA,
26
- ...application,
25
+ ...overrides,
27
26
  manifest: null,
28
- },
27
+ } as StudioUserApplication,
29
28
  PROJECTS,
30
29
  );
31
30
 
@@ -65,7 +64,7 @@ describe("StudioApplication", () => {
65
64
  ...MULTIPLE_WORKSPACE_STUDIO_DATA,
66
65
  manifestData: {
67
66
  value: {
68
- ...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData.value,
67
+ ...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData!.value,
69
68
  workspaces: [],
70
69
  },
71
70
  },
@@ -188,7 +187,7 @@ describe("StudioApplication", () => {
188
187
  new StudioApplication(
189
188
  {
190
189
  ...SINGLE_WORKSPACE_STUDIO_DATA,
191
- projectId: "non-existing-project",
190
+ projectId: brandProjectId("non-existing-project"),
192
191
  },
193
192
  PROJECTS,
194
193
  ),
@@ -204,7 +203,7 @@ describe("StudioApplication", () => {
204
203
  ...SINGLE_WORKSPACE_STUDIO_DATA,
205
204
  manifestData: {
206
205
  value: {
207
- ...SINGLE_WORKSPACE_STUDIO_DATA.manifestData.value,
206
+ ...SINGLE_WORKSPACE_STUDIO_DATA.manifestData!.value,
208
207
  workspaces: [],
209
208
  },
210
209
  },
@@ -251,11 +250,11 @@ describe("StudioApplication", () => {
251
250
  ...SINGLE_WORKSPACE_STUDIO_DATA,
252
251
  manifestData: {
253
252
  value: {
254
- ...SINGLE_WORKSPACE_STUDIO_DATA.manifestData.value,
253
+ ...SINGLE_WORKSPACE_STUDIO_DATA.manifestData!.value,
255
254
  workspaces: [
256
255
  {
257
- ...SINGLE_WORKSPACE_STUDIO_DATA.manifestData.value.workspaces[0],
258
- projectId: "non-existing-project",
256
+ ...SINGLE_WORKSPACE_STUDIO_DATA.manifestData!.value.workspaces[0],
257
+ projectId: brandProjectId("non-existing-project"),
259
258
  },
260
259
  ],
261
260
  },
@@ -270,7 +269,7 @@ describe("StudioApplication", () => {
270
269
  ...SINGLE_WORKSPACE_STUDIO_DATA,
271
270
  manifestData: {
272
271
  value: {
273
- ...SINGLE_WORKSPACE_STUDIO_DATA.manifestData.value,
272
+ ...SINGLE_WORKSPACE_STUDIO_DATA.manifestData!.value,
274
273
  workspaces: [],
275
274
  },
276
275
  },
@@ -301,7 +300,7 @@ describe("StudioApplication", () => {
301
300
  ...MULTIPLE_WORKSPACE_STUDIO_DATA,
302
301
  manifestData: {
303
302
  value: {
304
- ...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData.value,
303
+ ...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData!.value,
305
304
  version: 3,
306
305
  studioVersion,
307
306
  },
@@ -318,7 +317,7 @@ describe("StudioApplication", () => {
318
317
  ...SINGLE_WORKSPACE_STUDIO_DATA,
319
318
  manifestData: {
320
319
  value: {
321
- ...SINGLE_WORKSPACE_STUDIO_DATA.manifestData.value,
320
+ ...SINGLE_WORKSPACE_STUDIO_DATA.manifestData!.value,
322
321
  version: 2,
323
322
  },
324
323
  },
@@ -348,7 +347,7 @@ describe("StudioApplication", () => {
348
347
  },
349
348
  manifestData: {
350
349
  value: {
351
- ...SINGLE_WORKSPACE_STUDIO_DATA.manifestData.value,
350
+ ...SINGLE_WORKSPACE_STUDIO_DATA.manifestData!.value,
352
351
  version: 3,
353
352
  studioVersion,
354
353
  },
@@ -364,7 +363,7 @@ describe("StudioApplication", () => {
364
363
  ...MULTIPLE_WORKSPACE_STUDIO_DATA,
365
364
  manifestData: {
366
365
  value: {
367
- ...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData.value,
366
+ ...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData!.value,
368
367
  version: 3,
369
368
  studioVersion: "invalid",
370
369
  },
@@ -409,7 +408,7 @@ describe("StudioApplication", () => {
409
408
  ...SINGLE_WORKSPACE_STUDIO_DATA,
410
409
  manifestData: {
411
410
  value: {
412
- ...SINGLE_WORKSPACE_STUDIO_DATA.manifestData.value,
411
+ ...SINGLE_WORKSPACE_STUDIO_DATA.manifestData!.value,
413
412
  workspaces: [],
414
413
  },
415
414
  },
@@ -598,27 +597,27 @@ describe("StudioApplication", () => {
598
597
  ...MULTIPLE_WORKSPACE_STUDIO_DATA,
599
598
  manifestData: {
600
599
  value: {
601
- ...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData.value,
600
+ ...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData!.value,
602
601
  workspaces: [
603
602
  {
604
- ...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData.value
603
+ ...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData!.value
605
604
  .workspaces[0],
606
605
  projectId: "zvy7mysv",
607
606
  },
608
- ...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData.value.workspaces.slice(
607
+ ...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData!.value.workspaces.slice(
609
608
  1,
610
609
  ),
611
610
  ],
612
611
  },
613
612
  },
614
- } satisfies StudioApplicationApiResponse;
613
+ } as StudioUserApplication;
615
614
 
616
615
  const studio = setup(application);
617
616
 
618
617
  expect(studio.workspaces).toHaveLength(7);
619
618
  studio.workspaces.forEach((workspace) => {
620
619
  expect(workspace.title).not.toBe(
621
- MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData.value.workspaces[0].title,
620
+ MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData!.value.workspaces[0].title,
622
621
  );
623
622
  });
624
623
  });
@@ -633,16 +632,16 @@ describe("StudioApplication", () => {
633
632
  ...SINGLE_WORKSPACE_STUDIO_DATA,
634
633
  manifestData: {
635
634
  value: {
636
- ...SINGLE_WORKSPACE_STUDIO_DATA.manifestData.value,
635
+ ...SINGLE_WORKSPACE_STUDIO_DATA.manifestData!.value,
637
636
  workspaces: [
638
637
  {
639
- ...SINGLE_WORKSPACE_STUDIO_DATA.manifestData.value.workspaces[0],
638
+ ...SINGLE_WORKSPACE_STUDIO_DATA.manifestData!.value.workspaces[0],
640
639
  projectId: PROJECT_ID,
641
640
  },
642
641
  ],
643
642
  },
644
643
  },
645
- } satisfies StudioApplicationApiResponse;
644
+ } as StudioUserApplication;
646
645
 
647
646
  const studio = setup(application);
648
647
 
@@ -655,15 +654,85 @@ describe("StudioApplication", () => {
655
654
  expect(studio.workspaces[0].title).toBe(project?.displayName);
656
655
  expect(studio.title).toBe(project?.displayName);
657
656
  });
657
+
658
+ describe("projects", () => {
659
+ it("should only include the projects actually used by the studio, not every project passed in", () => {
660
+ // MULTIPLE_WORKSPACE_STUDIO_DATA is deployed in "hzao7xsp" and all of
661
+ // its workspaces reference the same project. The other project in
662
+ // PROJECTS ("c16r74n4") is not used by this studio and must not leak
663
+ // through.
664
+ const studio = setup();
665
+
666
+ expect(studio.projects).toHaveLength(1);
667
+ expect(studio.projects[0].id).toBe("hzao7xsp");
668
+ // The stored reference should be the same instance from the input list
669
+ expect(studio.projects[0]).toBe(
670
+ PROJECTS.find((p) => p.id === "hzao7xsp"),
671
+ );
672
+ });
673
+
674
+ it("should include projects referenced by workspaces even when they differ from the application's own project", () => {
675
+ const studio = setup({
676
+ ...MULTIPLE_WORKSPACE_STUDIO_DATA,
677
+ manifestData: {
678
+ value: {
679
+ ...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData!.value,
680
+ workspaces: [
681
+ {
682
+ ...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData!.value
683
+ .workspaces[0],
684
+ projectId: "c16r74n4",
685
+ },
686
+ ...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData!.value.workspaces.slice(
687
+ 1,
688
+ ),
689
+ ],
690
+ },
691
+ },
692
+ });
693
+
694
+ expect(studio.projects.map((p) => p.id).toSorted()).toEqual([
695
+ "c16r74n4",
696
+ "hzao7xsp",
697
+ ]);
698
+ });
699
+
700
+ it("should omit projects from workspaces that were dropped because the user has no access", () => {
701
+ vi.spyOn(console, "warn").mockImplementation(() => {});
702
+
703
+ const studio = setup({
704
+ ...MULTIPLE_WORKSPACE_STUDIO_DATA,
705
+ manifestData: {
706
+ value: {
707
+ ...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData!.value,
708
+ workspaces: [
709
+ {
710
+ ...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData!.value
711
+ .workspaces[0],
712
+ projectId: "zvy7mysv",
713
+ },
714
+ ...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData!.value.workspaces.slice(
715
+ 1,
716
+ ),
717
+ ],
718
+ },
719
+ },
720
+ });
721
+
722
+ // The workspace referencing the inaccessible project is dropped, so
723
+ // only the application's own project should remain in `projects`.
724
+ expect(studio.projects.map((p) => p.id)).toEqual(["hzao7xsp"]);
725
+ });
726
+ });
658
727
  });
659
728
 
660
729
  describe("StudioWorkspace", () => {
661
- const setup = (application?: StudioApplicationApiResponse) =>
730
+ const setup = (overrides?: Record<string, unknown>) =>
662
731
  new StudioApplication(
663
732
  {
664
733
  ...MULTIPLE_WORKSPACE_STUDIO_DATA,
665
- ...application,
666
- },
734
+ ...overrides,
735
+ } as StudioUserApplication,
667
736
  PROJECTS,
668
737
  );
669
738
 
@@ -725,10 +794,10 @@ describe("StudioWorkspace", () => {
725
794
  appHost: "https://my-studio.com/admin",
726
795
  manifestData: {
727
796
  value: {
728
- ...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData.value,
797
+ ...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData!.value,
729
798
  workspaces: [
730
799
  {
731
- ...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData.value
800
+ ...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData!.value
732
801
  .workspaces[0],
733
802
  basePath: "/admin",
734
803
  },
@@ -1,82 +1,52 @@
1
- import type {
2
- StudioApplication as StudioApplicationData,
3
- // eslint-disable-next-line no-restricted-imports
4
- StudioResource as ProtocolStudioResource,
5
- WorkspaceManifest,
6
- } from "@sanity/message-protocol";
7
1
  import type { SemVer } from "semver";
8
2
  import { coerce, gt, gte, lt, rsort, valid } from "semver";
9
- import { z } from "zod";
10
3
 
11
- import { AbstractApplication } from "../applications/application";
4
+ import { type Project } from "../../projects";
5
+ import { UserApplication } from "../user-application";
12
6
  import {
13
- brandProjectId,
14
- ProjectId as ProjectIdSchema,
15
- type Project,
16
- type ProjectId,
17
- } from "../projects";
18
- import { joinUrlPaths, normalizePath } from "../shared/urls";
19
- import { type UserApplicationId } from "./user-application";
20
- import { UserApplication } from "./user-application";
21
-
22
- const WORKSPACE_MANIFEST_SCHEMA = z.object({
23
- name: z.string(),
24
- title: z.string(),
25
- subtitle: z.string().nullable().optional(),
26
- basePath: z.string(),
27
- projectId: ProjectIdSchema,
28
- dataset: z.string(),
29
- icon: z.string().optional().nullable(),
30
- schema: z.string(),
31
- tools: z.string().optional(),
32
- });
33
-
34
- const STUDIO_MANIFEST_SCHEMA = z
35
- .object({
36
- // data from manifest
37
- version: z.number(),
38
- createdAt: z.string(),
39
- studioVersion: z.string().nullable().optional(),
40
- // data from workspace object
41
- workspaces: z.array(WORKSPACE_MANIFEST_SCHEMA),
42
- })
43
- .superRefine((data, ctx) => {
44
- if (!data.version) {
45
- ctx.addIssue({
46
- code: "invalid_type",
47
- message: "Manifest version is too old",
48
- expected: "number",
49
- });
50
- }
7
+ type Workspace,
8
+ type StudioUserApplication,
9
+ ClientManifest as ClientManifestSchema,
10
+ } from "./schemas";
11
+ import { StudioWorkspace } from "./workspace";
12
+
13
+ const ClientManifest = ClientManifestSchema.superRefine((data, ctx) => {
14
+ if (!data.version) {
15
+ ctx.addIssue({
16
+ code: "invalid_type",
17
+ message: "Manifest version is too old",
18
+ expected: "number",
19
+ });
20
+ }
51
21
 
52
- if (data.version < 2) {
53
- ctx.addIssue({
54
- code: "invalid_value",
55
- message: "Manifest version is too old",
56
- values: [2, 3],
57
- });
58
- }
22
+ if (data.version < 2) {
23
+ ctx.addIssue({
24
+ code: "invalid_value",
25
+ message: "Manifest version is too old",
26
+ values: [2, 3],
27
+ });
28
+ }
59
29
 
60
- if (data.version >= 3 && !data.studioVersion) {
61
- ctx.addIssue({
62
- code: "invalid_type",
63
- message: "Manifest version 3 or higher requires a `studioVersion`",
64
- expected: "string",
65
- });
66
- }
67
- });
30
+ if (data.version >= 3 && !data.studioVersion) {
31
+ ctx.addIssue({
32
+ code: "invalid_type",
33
+ message: "Manifest version 3 or higher requires a `studioVersion`",
34
+ expected: "string",
35
+ });
36
+ }
37
+ });
68
38
 
69
39
  const DEFAULT_WORKSPACE_DATA = {
70
40
  name: "default",
71
41
  title: "Default",
72
42
  basePath: "/",
73
- } as const satisfies Partial<Workspace>;
43
+ } as const satisfies Omit<Workspace, "projectId">;
74
44
 
75
45
  /**
76
46
  * @internal
77
47
  */
78
48
  export class StudioApplication extends UserApplication<
79
- StudioApplicationData,
49
+ StudioUserApplication,
80
50
  "studio",
81
51
  never
82
52
  > {
@@ -88,6 +58,14 @@ export class StudioApplication extends UserApplication<
88
58
 
89
59
  readonly project: Project<false, false>;
90
60
 
61
+ /**
62
+ * The projects actually referenced by this studio — the application's own
63
+ * project plus any project used by a workspace. Preserved so the instance
64
+ * can be re-constructed (e.g. by dock wrappers) without having to thread
65
+ * the full organization project list through again.
66
+ */
67
+ readonly projects: Project<false, false>[];
68
+
91
69
  /**
92
70
  * @param application - The studio application to create a list of workspaces for
93
71
  * @param projects - The projects available in the organization. It's not enough to just pass
@@ -95,7 +73,7 @@ export class StudioApplication extends UserApplication<
95
73
  * The workspaces may have different projects completely.
96
74
  */
97
75
  constructor(
98
- application: StudioApplicationData,
76
+ application: StudioUserApplication,
99
77
  projects: Project<false, false>[],
100
78
  ) {
101
79
  super(application, "studio");
@@ -113,7 +91,7 @@ export class StudioApplication extends UserApplication<
113
91
  * this could potentially crash the app. Instead we log a warning and return an empty array.
114
92
  */
115
93
  try {
116
- workspaces = STUDIO_MANIFEST_SCHEMA.parse(
94
+ workspaces = ClientManifest.parse(
117
95
  application.manifestData?.value,
118
96
  ).workspaces;
119
97
  } catch (error) {
@@ -148,10 +126,7 @@ export class StudioApplication extends UserApplication<
148
126
  Array.isArray(serverManifestWorkspaces) &&
149
127
  serverManifestWorkspaces.length
150
128
  ) {
151
- workspaces = serverManifestWorkspaces.map((w) => ({
152
- ...w,
153
- projectId: brandProjectId(w.projectId),
154
- }));
129
+ workspaces = serverManifestWorkspaces;
155
130
  }
156
131
  }
157
132
 
@@ -172,8 +147,7 @@ export class StudioApplication extends UserApplication<
172
147
  return acc;
173
148
  }, new Map<Workspace, Project<false, false>>());
174
149
 
175
- const brandedProjectId = brandProjectId(application.projectId);
176
- const project = projects.find((p) => p.id === brandedProjectId);
150
+ const project = projects.find((p) => p.id === application.projectId);
177
151
 
178
152
  if (!project) {
179
153
  throw new Error(`Project not found for application ${application.id}`);
@@ -186,7 +160,7 @@ export class StudioApplication extends UserApplication<
186
160
  workspacesWithProjectsMap.set(
187
161
  {
188
162
  ...DEFAULT_WORKSPACE_DATA,
189
- projectId: brandedProjectId,
163
+ projectId: application.projectId,
190
164
  } satisfies Workspace,
191
165
  project,
192
166
  );
@@ -210,6 +184,14 @@ export class StudioApplication extends UserApplication<
210
184
  );
211
185
 
212
186
  this.project = project;
187
+
188
+ // Collect only the projects actually referenced by this studio — the
189
+ // application's own project plus any project used by a workspace.
190
+ const usedProjects = new Set<Project<false, false>>([project]);
191
+ for (const workspace of this.workspaces) {
192
+ usedProjects.add(workspace.project);
193
+ }
194
+ this.projects = Array.from(usedProjects);
213
195
  }
214
196
 
215
197
  get href() {
@@ -236,9 +218,9 @@ export class StudioApplication extends UserApplication<
236
218
  return new URL(this.url).hostname;
237
219
  }
238
220
 
239
- get<TKey extends keyof StudioApplicationData>(
221
+ get<TKey extends keyof StudioUserApplication>(
240
222
  attr: TKey,
241
- ): StudioApplicationData[TKey] {
223
+ ): StudioUserApplication[TKey] {
242
224
  if (!(attr in this.application)) {
243
225
  throw new Error(
244
226
  `Attribute ${attr.toString()} does not exist on studio ${this.application.id}`,
@@ -514,143 +496,3 @@ type StudioIssues = Array<{
514
496
  id: StudioDashboardIssue;
515
497
  version?: string;
516
498
  }>;
517
-
518
- type Workspace = Omit<WorkspaceManifest, "dataset" | "schema" | "projectId"> & {
519
- projectId: ProjectId;
520
- dataset?: string;
521
- schema?: string;
522
- };
523
-
524
- /**
525
- * @internal
526
- */
527
- export class StudioWorkspace extends AbstractApplication<
528
- "workspace",
529
- ProtocolStudioResource
530
- > {
531
- /**
532
- * Workspaces always belong to a studio application.
533
- * They do not exist on their own & therefore can access
534
- * information about the studio they're in via this property.
535
- */
536
- private readonly studioApplication: StudioApplication;
537
- private readonly workspace: Workspace;
538
- readonly project: Project<false, false>;
539
- private readonly isDefaultWorkspace: boolean;
540
-
541
- constructor(
542
- studioApplication: StudioApplication,
543
- workspace: Workspace,
544
- project: Project<false, false>,
545
- isDefaultWorkspace: boolean,
546
- ) {
547
- super("workspace");
548
-
549
- this.studioApplication = studioApplication;
550
- this.workspace = workspace;
551
- this.project = project;
552
- this.isDefaultWorkspace = isDefaultWorkspace;
553
- }
554
-
555
- /**
556
- * The studio application that this workspace belongs to.
557
- */
558
- get studio() {
559
- return this.studioApplication;
560
- }
561
-
562
- get id() {
563
- return StudioWorkspace.makeId(this.studio.id, this.workspace.name);
564
- }
565
-
566
- get href() {
567
- return joinUrlPaths(this.studio.href, this.workspace.name);
568
- }
569
-
570
- get title() {
571
- /**
572
- * If there's no manifest we will have created a single workspace for the application.
573
- * In this circumstance we won't have a meaningful title, so instead we use the hostname of the appHost.
574
- */
575
- if (this.isDefaultWorkspace) {
576
- return this.project.displayName;
577
- }
578
-
579
- return this.workspace.title;
580
- }
581
-
582
- get subtitle() {
583
- if (this.isDefaultWorkspace) {
584
- const isValidAppHost = URL.canParse(this.studio.get("appHost"));
585
- const url = isValidAppHost
586
- ? new URL(this.studio.get("appHost"))
587
- : this.studio.url;
588
-
589
- return url.hostname;
590
- }
591
-
592
- return this.get("subtitle");
593
- }
594
-
595
- get<TKey extends Exclude<keyof Workspace, "id" | "icon" | "title">>(
596
- attr: TKey,
597
- ): Workspace[TKey] {
598
- return this.workspace[attr];
599
- }
600
-
601
- /**
602
- * With comlink, studio-applications were not considered applications at all, only workspaces. This is partially why
603
- * we create default workspaces when the application has no manifest or the manifest has no workspaces.
604
- *
605
- * Thereby, to create it we depend on a lot of information from the parent application even if it's duplicated across
606
- * different workspaces.
607
- */
608
- toProtocolResource(): ProtocolStudioResource {
609
- return {
610
- ...this.workspace,
611
- type: "studio",
612
- userApplicationId: this.studio.id,
613
- activeDeployment: this.studio.get("activeDeployment"),
614
- autoUpdatingVersion: this.studio.get("autoUpdatingVersion"),
615
- dashboardStatus: this.studio.get("dashboardStatus"),
616
- url: this.studio.url.toString(),
617
- href: this.href,
618
- id: this.id,
619
- hasManifest: true,
620
- hasSchema: Boolean(this.workspace.schema),
621
- manifest: this.studio.get("manifestData")?.value ?? null,
622
- updatedAt: this.studio.get("updatedAt"),
623
- version: this.studio.get("activeDeployment")?.version,
624
- urlType: this.studio.get("urlType"),
625
- config: this.studio.get("config"),
626
- } satisfies ProtocolStudioResource;
627
- }
628
-
629
- /**
630
- * @returns the URL to the workspace of a studio application.
631
- */
632
- get url(): URL {
633
- const studioUrl = new URL(this.studio.url);
634
- const normalizedUrlPath = normalizePath(studioUrl.pathname);
635
-
636
- let finalBasePath = normalizePath(this.get("basePath"));
637
-
638
- // the appHost may already contain the basepath for externally hosted studios
639
- // or embedded studios, so we deduplicate the segments
640
- if (finalBasePath.startsWith(normalizedUrlPath)) {
641
- finalBasePath = finalBasePath.slice(normalizedUrlPath.length);
642
- }
643
-
644
- studioUrl.pathname = joinUrlPaths(normalizedUrlPath, finalBasePath);
645
-
646
- return studioUrl;
647
- }
648
-
649
- static makeId(applicationId: UserApplicationId, workspaceName: string) {
650
- return `${applicationId}-${workspaceName}`;
651
- }
652
-
653
- static splitId(id: string): [string, string] {
654
- return id.split(new RegExp(/-(.*)/)).slice(0, 2) as [string, string];
655
- }
656
- }