@sanity/workbench 0.1.0-alpha.7 → 0.1.0-alpha.9
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 +9 -3
- package/src/_internal/render.ts +4 -11
- package/src/core/applications/application-list.ts +1 -1
- 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.ts +66 -10
- package/src/core/user-applications/studios/index.ts +3 -0
- package/src/core/user-applications/studios/schemas.ts +106 -0
- 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.ts +36 -5
- package/src/_internal/render.test.ts +0 -73
- package/src/core/__tests__/__fixtures__.ts +0 -248
- package/src/core/applications/application-list.test.ts +0 -222
- package/src/core/applications/local-application.test.ts +0 -93
- package/src/core/canvases.test.ts +0 -38
- package/src/core/log/index.test.ts +0 -133
- package/src/core/media-libraries.test.ts +0 -38
- package/src/core/organizations.test.ts +0 -134
- package/src/core/projects.test.ts +0 -248
- package/src/core/shared/urls.test.ts +0 -182
- package/src/core/user-applications/core-app.test.ts +0 -151
- package/src/core/user-applications/studio.test.ts +0 -928
- package/src/core/user-applications/user-application.test.ts +0 -126
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { ProjectId } from "../../projects";
|
|
4
|
+
import { ActiveDeployment, UserApplicationBase } from "../user-application";
|
|
5
|
+
|
|
6
|
+
const Workspace = z.object({
|
|
7
|
+
name: z.string(),
|
|
8
|
+
title: z.string(),
|
|
9
|
+
subtitle: z.string().optional(),
|
|
10
|
+
basePath: z.string(),
|
|
11
|
+
projectId: ProjectId,
|
|
12
|
+
dataset: z.string().optional(),
|
|
13
|
+
icon: z.string().nullable().optional(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
export type Workspace = z.output<typeof Workspace>;
|
|
20
|
+
|
|
21
|
+
const ServerManifest = z.object({
|
|
22
|
+
buildId: z.string().optional(),
|
|
23
|
+
bundleVersion: z.string().optional(),
|
|
24
|
+
version: z.string().optional(),
|
|
25
|
+
workspaces: z
|
|
26
|
+
.array(
|
|
27
|
+
Workspace.extend({
|
|
28
|
+
dataset: z.string(),
|
|
29
|
+
schemaDescriptorId: z.string(),
|
|
30
|
+
}),
|
|
31
|
+
)
|
|
32
|
+
.optional(),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @public
|
|
37
|
+
*/
|
|
38
|
+
export const ClientManifest = z.object({
|
|
39
|
+
version: z.number(),
|
|
40
|
+
createdAt: z.string(),
|
|
41
|
+
studioVersion: z.string().optional(),
|
|
42
|
+
workspaces: z.array(
|
|
43
|
+
Workspace.extend({
|
|
44
|
+
schema: z.string(),
|
|
45
|
+
tools: z.string().optional(),
|
|
46
|
+
}),
|
|
47
|
+
),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const StudioUserApplicationBase = UserApplicationBase.extend({
|
|
51
|
+
title: z.string().nullable(),
|
|
52
|
+
projectId: ProjectId,
|
|
53
|
+
type: z.literal("studio"),
|
|
54
|
+
manifest: ClientManifest.nullable(),
|
|
55
|
+
manifestData: z.object({ value: ClientManifest }).nullable(),
|
|
56
|
+
autoUpdatingVersion: z.string().nullable(),
|
|
57
|
+
config: z.object({
|
|
58
|
+
"live-manifest": z
|
|
59
|
+
.object({
|
|
60
|
+
createdAt: z.string(),
|
|
61
|
+
updatedAt: z.string(),
|
|
62
|
+
updatedBy: z.string(),
|
|
63
|
+
value: ServerManifest,
|
|
64
|
+
})
|
|
65
|
+
.optional(),
|
|
66
|
+
}),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const InternalStudioUserApplication = StudioUserApplicationBase.extend({
|
|
70
|
+
urlType: z.literal("internal"),
|
|
71
|
+
activeDeployment: ActiveDeployment.extend({
|
|
72
|
+
manifest: ServerManifest.nullable(),
|
|
73
|
+
}),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const ExternalStudioUserApplication = StudioUserApplicationBase.extend({
|
|
77
|
+
urlType: z.literal("external"),
|
|
78
|
+
activeDeployment: z.null(),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Studio user application schema — validates and brands API
|
|
83
|
+
* responses from the `/user-applications?appType=studio`
|
|
84
|
+
* endpoint. Uses a discriminated union on `urlType`.
|
|
85
|
+
* @public
|
|
86
|
+
*/
|
|
87
|
+
export const StudioUserApplication = z.discriminatedUnion("urlType", [
|
|
88
|
+
InternalStudioUserApplication,
|
|
89
|
+
ExternalStudioUserApplication,
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @public
|
|
94
|
+
*/
|
|
95
|
+
export type StudioUserApplication = z.output<typeof StudioUserApplication>;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Validates and parses a raw API response into a branded
|
|
99
|
+
* StudioUserApplication.
|
|
100
|
+
* @public
|
|
101
|
+
*/
|
|
102
|
+
export function parseStudioUserApplication(
|
|
103
|
+
data: unknown,
|
|
104
|
+
): StudioUserApplication {
|
|
105
|
+
return StudioUserApplication.parse(data);
|
|
106
|
+
}
|
|
@@ -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
|
-
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { StudioResource as ProtocolStudioResource } from "@sanity/message-protocol";
|
|
2
|
+
|
|
3
|
+
import { AbstractApplication } from "../../applications/application";
|
|
4
|
+
import type { Project } from "../../projects";
|
|
5
|
+
import { joinUrlPaths, normalizePath } from "../../shared/urls";
|
|
6
|
+
import type { UserApplicationId } from "../user-application";
|
|
7
|
+
import type { Workspace } from "./schemas";
|
|
8
|
+
import type { StudioApplication } from "./studio";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export class StudioWorkspace extends AbstractApplication<
|
|
14
|
+
"workspace",
|
|
15
|
+
ProtocolStudioResource
|
|
16
|
+
> {
|
|
17
|
+
/**
|
|
18
|
+
* Workspaces always belong to a studio application.
|
|
19
|
+
* They do not exist on their own & therefore can access
|
|
20
|
+
* information about the studio they're in via this property.
|
|
21
|
+
*/
|
|
22
|
+
private readonly studioApplication: StudioApplication;
|
|
23
|
+
private readonly workspace: Workspace;
|
|
24
|
+
readonly project: Project<false, false>;
|
|
25
|
+
private readonly isDefaultWorkspace: boolean;
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
28
|
+
studioApplication: StudioApplication,
|
|
29
|
+
workspace: Workspace,
|
|
30
|
+
project: Project<false, false>,
|
|
31
|
+
isDefaultWorkspace: boolean,
|
|
32
|
+
) {
|
|
33
|
+
super("workspace");
|
|
34
|
+
|
|
35
|
+
this.studioApplication = studioApplication;
|
|
36
|
+
this.workspace = workspace;
|
|
37
|
+
this.project = project;
|
|
38
|
+
this.isDefaultWorkspace = isDefaultWorkspace;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The studio application that this workspace belongs to.
|
|
43
|
+
*/
|
|
44
|
+
get studio() {
|
|
45
|
+
return this.studioApplication;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get id() {
|
|
49
|
+
return StudioWorkspace.makeId(this.studio.id, this.workspace.name);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get href() {
|
|
53
|
+
return joinUrlPaths(this.studio.href, this.workspace.name);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get title() {
|
|
57
|
+
/**
|
|
58
|
+
* If there's no manifest we will have created a single workspace for the application.
|
|
59
|
+
* In this circumstance we won't have a meaningful title, so instead we use the hostname of the appHost.
|
|
60
|
+
*/
|
|
61
|
+
if (this.isDefaultWorkspace) {
|
|
62
|
+
return this.project.displayName;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return this.workspace.title;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get subtitle() {
|
|
69
|
+
if (this.isDefaultWorkspace) {
|
|
70
|
+
const isValidAppHost = URL.canParse(this.studio.get("appHost"));
|
|
71
|
+
const url = isValidAppHost
|
|
72
|
+
? new URL(this.studio.get("appHost"))
|
|
73
|
+
: this.studio.url;
|
|
74
|
+
|
|
75
|
+
return url.hostname;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return this.get("subtitle");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
get<TKey extends Exclude<keyof Workspace, "id" | "icon" | "title">>(
|
|
82
|
+
attr: TKey,
|
|
83
|
+
): Workspace[TKey] {
|
|
84
|
+
return this.workspace[attr];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* With comlink, studio-applications were not considered applications at all, only workspaces. This is partially why
|
|
89
|
+
* we create default workspaces when the application has no manifest or the manifest has no workspaces.
|
|
90
|
+
*
|
|
91
|
+
* Thereby, to create it we depend on a lot of information from the parent application even if it's duplicated across
|
|
92
|
+
* different workspaces.
|
|
93
|
+
*/
|
|
94
|
+
toProtocolResource(): ProtocolStudioResource {
|
|
95
|
+
return {
|
|
96
|
+
...this.workspace,
|
|
97
|
+
type: "studio",
|
|
98
|
+
userApplicationId: this.studio.id,
|
|
99
|
+
activeDeployment: this.studio.get("activeDeployment"),
|
|
100
|
+
autoUpdatingVersion: this.studio.get("autoUpdatingVersion"),
|
|
101
|
+
dashboardStatus: this.studio.get("dashboardStatus"),
|
|
102
|
+
url: this.studio.url.toString(),
|
|
103
|
+
href: this.href,
|
|
104
|
+
id: this.id,
|
|
105
|
+
hasManifest: true,
|
|
106
|
+
hasSchema: Boolean("schema" in this.workspace && this.workspace.schema),
|
|
107
|
+
manifest: (this.studio.get("manifestData")?.value ??
|
|
108
|
+
null) as ProtocolStudioResource["manifest"],
|
|
109
|
+
updatedAt: this.studio.get("updatedAt"),
|
|
110
|
+
version: this.studio.get("activeDeployment")?.version,
|
|
111
|
+
urlType: this.studio.get("urlType"),
|
|
112
|
+
config: this.studio.get("config"),
|
|
113
|
+
} satisfies ProtocolStudioResource;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @returns the URL to the workspace of a studio application.
|
|
118
|
+
*/
|
|
119
|
+
get url(): URL {
|
|
120
|
+
const studioUrl = new URL(this.studio.url);
|
|
121
|
+
const normalizedUrlPath = normalizePath(studioUrl.pathname);
|
|
122
|
+
|
|
123
|
+
let finalBasePath = normalizePath(this.get("basePath"));
|
|
124
|
+
|
|
125
|
+
// the appHost may already contain the basepath for externally hosted studios
|
|
126
|
+
// or embedded studios, so we deduplicate the segments
|
|
127
|
+
if (finalBasePath.startsWith(normalizedUrlPath)) {
|
|
128
|
+
finalBasePath = finalBasePath.slice(normalizedUrlPath.length);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
studioUrl.pathname = joinUrlPaths(normalizedUrlPath, finalBasePath);
|
|
132
|
+
|
|
133
|
+
return studioUrl;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
static makeId(applicationId: UserApplicationId, workspaceName: string) {
|
|
137
|
+
return `${applicationId}-${workspaceName}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
static splitId(id: string): [string, string] {
|
|
141
|
+
return id.split(new RegExp(/-(.*)/)).slice(0, 2) as [string, string];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
UserApplication as UserApplicationData,
|
|
4
|
-
} from "@sanity/message-protocol";
|
|
1
|
+
// eslint-disable-next-line no-restricted-imports
|
|
2
|
+
import type { Resource as ProtocolResource } from "@sanity/message-protocol";
|
|
5
3
|
import { z } from "zod";
|
|
6
4
|
|
|
7
5
|
import {
|
|
@@ -32,11 +30,44 @@ export function brandUserApplicationId(id: string): UserApplicationId {
|
|
|
32
30
|
return UserApplicationId.parse(id);
|
|
33
31
|
}
|
|
34
32
|
|
|
33
|
+
/**
|
|
34
|
+
* @public
|
|
35
|
+
*/
|
|
36
|
+
export const UserApplicationBase = z.object({
|
|
37
|
+
id: UserApplicationId,
|
|
38
|
+
appHost: z.string(),
|
|
39
|
+
urlType: z.enum(["internal", "external"]),
|
|
40
|
+
createdAt: z.string(),
|
|
41
|
+
updatedAt: z.string(),
|
|
42
|
+
dashboardStatus: z.enum(["default", "disabled"]),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @public
|
|
47
|
+
*/
|
|
48
|
+
export type UserApplicationBase = z.output<typeof UserApplicationBase>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @public
|
|
52
|
+
*/
|
|
53
|
+
export const ActiveDeployment = z.object({
|
|
54
|
+
id: z.string(),
|
|
55
|
+
version: z.string(),
|
|
56
|
+
isActiveDeployment: z.boolean(),
|
|
57
|
+
userApplicationId: z.string(),
|
|
58
|
+
isAutoUpdating: z.boolean(),
|
|
59
|
+
size: z.number(),
|
|
60
|
+
deployedAt: z.string(),
|
|
61
|
+
deployedBy: z.string(),
|
|
62
|
+
createdAt: z.string(),
|
|
63
|
+
updatedAt: z.string(),
|
|
64
|
+
});
|
|
65
|
+
|
|
35
66
|
/**
|
|
36
67
|
* @internal
|
|
37
68
|
*/
|
|
38
69
|
export abstract class UserApplication<
|
|
39
|
-
TUserApplication extends
|
|
70
|
+
TUserApplication extends UserApplicationBase,
|
|
40
71
|
TType extends Extract<AbstractApplicationType, "coreApp" | "studio">,
|
|
41
72
|
TProtocolResource extends ProtocolResource = Extract<
|
|
42
73
|
ProtocolResource,
|