@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.
- package/dist/_chunks-es/index.js +6 -1
- package/dist/_chunks-es/index.js.map +1 -1
- package/dist/_internal.js +3 -8
- package/dist/_internal.js.map +1 -1
- package/dist/core.d.ts +582 -24
- package/dist/core.js +207 -105
- package/dist/core.js.map +1 -1
- package/package.json +3 -1
- package/src/_internal/render.test.ts +46 -14
- package/src/_internal/render.ts +4 -11
- package/src/core/__tests__/__fixtures__.ts +8 -11
- package/src/core/applications/application-list.test.ts +1 -1
- package/src/core/applications/application-list.ts +1 -1
- package/src/core/applications/local-application.test.ts +3 -3
- package/src/core/applications/local-application.ts +6 -2
- package/src/core/canvases.ts +1 -1
- package/src/core/index.ts +1 -1
- package/src/core/log/index.ts +12 -0
- package/src/core/media-libraries.ts +1 -1
- package/src/core/user-applications/core-app.test.ts +110 -25
- package/src/core/user-applications/core-app.ts +66 -10
- package/src/core/user-applications/studios/index.ts +3 -0
- package/src/core/user-applications/studios/schemas.test.ts +113 -0
- package/src/core/user-applications/studios/schemas.ts +106 -0
- package/src/core/user-applications/{studio.test.ts → studios/studio.test.ts} +106 -37
- package/src/core/user-applications/{studio.ts → studios/studio.ts} +56 -214
- package/src/core/user-applications/studios/workspace.ts +143 -0
- package/src/core/user-applications/user-application.test.ts +7 -8
- package/src/core/user-applications/user-application.ts +36 -5
|
@@ -1,31 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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 "
|
|
13
|
-
import type
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
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 = (
|
|
21
|
+
const setup = (overrides?: Record<string, unknown>) =>
|
|
23
22
|
new StudioApplication(
|
|
24
23
|
{
|
|
25
24
|
...MULTIPLE_WORKSPACE_STUDIO_DATA,
|
|
26
|
-
...
|
|
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
|
|
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
|
|
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
|
|
253
|
+
...SINGLE_WORKSPACE_STUDIO_DATA.manifestData!.value,
|
|
255
254
|
workspaces: [
|
|
256
255
|
{
|
|
257
|
-
...SINGLE_WORKSPACE_STUDIO_DATA.manifestData
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
600
|
+
...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData!.value,
|
|
602
601
|
workspaces: [
|
|
603
602
|
{
|
|
604
|
-
...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData
|
|
603
|
+
...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData!.value
|
|
605
604
|
.workspaces[0],
|
|
606
605
|
projectId: "zvy7mysv",
|
|
607
606
|
},
|
|
608
|
-
...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData
|
|
607
|
+
...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData!.value.workspaces.slice(
|
|
609
608
|
1,
|
|
610
609
|
),
|
|
611
610
|
],
|
|
612
611
|
},
|
|
613
612
|
},
|
|
614
|
-
}
|
|
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
|
|
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
|
|
635
|
+
...SINGLE_WORKSPACE_STUDIO_DATA.manifestData!.value,
|
|
637
636
|
workspaces: [
|
|
638
637
|
{
|
|
639
|
-
...SINGLE_WORKSPACE_STUDIO_DATA.manifestData
|
|
638
|
+
...SINGLE_WORKSPACE_STUDIO_DATA.manifestData!.value.workspaces[0],
|
|
640
639
|
projectId: PROJECT_ID,
|
|
641
640
|
},
|
|
642
641
|
],
|
|
643
642
|
},
|
|
644
643
|
},
|
|
645
|
-
}
|
|
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 = (
|
|
730
|
+
const setup = (overrides?: Record<string, unknown>) =>
|
|
662
731
|
new StudioApplication(
|
|
663
732
|
{
|
|
664
733
|
...MULTIPLE_WORKSPACE_STUDIO_DATA,
|
|
665
|
-
...
|
|
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
|
|
797
|
+
...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData!.value,
|
|
729
798
|
workspaces: [
|
|
730
799
|
{
|
|
731
|
-
...MULTIPLE_WORKSPACE_STUDIO_DATA.manifestData
|
|
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 {
|
|
4
|
+
import { type Project } from "../../projects";
|
|
5
|
+
import { UserApplication } from "../user-application";
|
|
12
6
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} from "
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
43
|
+
} as const satisfies Omit<Workspace, "projectId">;
|
|
74
44
|
|
|
75
45
|
/**
|
|
76
46
|
* @internal
|
|
77
47
|
*/
|
|
78
48
|
export class StudioApplication extends UserApplication<
|
|
79
|
-
|
|
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:
|
|
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 =
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
221
|
+
get<TKey extends keyof StudioUserApplication>(
|
|
240
222
|
attr: TKey,
|
|
241
|
-
):
|
|
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
|
-
}
|