@sanity/workbench 0.1.0-alpha.6 → 0.1.0-alpha.7
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/{log.js → _chunks-es/index.js} +1 -1
- package/dist/_chunks-es/index.js.map +1 -0
- package/dist/_internal.d.ts +13 -6
- package/dist/_internal.js +19 -3
- package/dist/_internal.js.map +1 -1
- package/dist/core.d.ts +906 -0
- package/dist/core.js +642 -0
- package/dist/core.js.map +1 -0
- package/package.json +10 -4
- package/src/_exports/core.ts +1 -0
- package/src/_internal/index.ts +2 -1
- package/src/_internal/render.test.ts +56 -1
- package/src/_internal/render.ts +51 -24
- package/src/core/__tests__/__fixtures__.ts +248 -0
- package/src/core/applications/application-list.test.ts +222 -0
- package/src/core/applications/application-list.ts +103 -0
- package/src/core/applications/application.ts +78 -0
- package/src/core/applications/local-application.test.ts +93 -0
- package/src/core/applications/local-application.ts +52 -0
- package/src/core/canvases.test.ts +38 -0
- package/src/core/canvases.ts +81 -0
- package/src/core/config.ts +34 -0
- package/src/core/index.ts +12 -0
- package/src/core/media-libraries.test.ts +38 -0
- package/src/core/media-libraries.ts +83 -0
- package/src/core/organizations.test.ts +134 -0
- package/src/core/organizations.ts +115 -0
- package/src/core/projects.test.ts +248 -0
- package/src/core/projects.ts +114 -0
- package/src/core/shared/urls.test.ts +182 -0
- package/src/core/shared/urls.ts +128 -0
- package/src/core/user-applications/core-app.test.ts +151 -0
- package/src/core/user-applications/core-app.ts +57 -0
- package/src/core/user-applications/studio.test.ts +928 -0
- package/src/core/user-applications/studio.ts +656 -0
- package/src/core/user-applications/user-application.test.ts +126 -0
- package/src/core/user-applications/user-application.ts +76 -0
- package/src/vite-env.d.ts +8 -0
- package/dist/log.d.ts +0 -48
- package/dist/log.js.map +0 -1
- package/src/_exports/log.ts +0 -1
- /package/src/{log → core/log}/index.test.ts +0 -0
- /package/src/{log → core/log}/index.ts +0 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
// eslint-disable-next-line no-restricted-imports
|
|
2
|
+
import type { Resource as ProtocolResource } from "@sanity/message-protocol";
|
|
3
|
+
import { describe, expect, expectTypeOf, it } from "vitest";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
APPLICATION_DATA,
|
|
7
|
+
CANVAS_DATA,
|
|
8
|
+
MEDIA_LIBRARY_DATA,
|
|
9
|
+
MULTIPLE_WORKSPACE_STUDIO_DATA,
|
|
10
|
+
PROJECTS,
|
|
11
|
+
SINGLE_WORKSPACE_STUDIO_DATA,
|
|
12
|
+
} from "../__tests__/__fixtures__";
|
|
13
|
+
import { CanvasApplication } from "../canvases";
|
|
14
|
+
import { MediaLibraryApplication } from "../media-libraries";
|
|
15
|
+
import { CoreAppApplication } from "../user-applications/core-app";
|
|
16
|
+
import {
|
|
17
|
+
StudioApplication,
|
|
18
|
+
StudioWorkspace,
|
|
19
|
+
} from "../user-applications/studio";
|
|
20
|
+
import { brandUserApplicationId } from "../user-applications/user-application";
|
|
21
|
+
import { ApplicationList } from "./application-list";
|
|
22
|
+
|
|
23
|
+
describe("ApplicationList", () => {
|
|
24
|
+
const create = () => {
|
|
25
|
+
const canvas = new CanvasApplication(CANVAS_DATA);
|
|
26
|
+
const mediaLibrary = new MediaLibraryApplication(MEDIA_LIBRARY_DATA);
|
|
27
|
+
const coreApp = new CoreAppApplication(APPLICATION_DATA);
|
|
28
|
+
const multiStudio = new StudioApplication(
|
|
29
|
+
MULTIPLE_WORKSPACE_STUDIO_DATA,
|
|
30
|
+
PROJECTS,
|
|
31
|
+
);
|
|
32
|
+
const singleStudio = new StudioApplication(
|
|
33
|
+
SINGLE_WORKSPACE_STUDIO_DATA,
|
|
34
|
+
PROJECTS,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return new ApplicationList([
|
|
38
|
+
canvas,
|
|
39
|
+
mediaLibrary,
|
|
40
|
+
coreApp,
|
|
41
|
+
multiStudio,
|
|
42
|
+
singleStudio,
|
|
43
|
+
...multiStudio.workspaces,
|
|
44
|
+
...singleStudio.workspaces,
|
|
45
|
+
]);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
describe("findApplication", () => {
|
|
49
|
+
it("should find an application by type and correctly infer the type", () => {
|
|
50
|
+
const applicationList = create();
|
|
51
|
+
|
|
52
|
+
const canvasResource = applicationList.findApplication("canvas");
|
|
53
|
+
expect(canvasResource).toBeInstanceOf(CanvasApplication);
|
|
54
|
+
expectTypeOf(canvasResource).toEqualTypeOf<
|
|
55
|
+
CanvasApplication | undefined
|
|
56
|
+
>();
|
|
57
|
+
|
|
58
|
+
const mediaResource = applicationList.findApplication("media-library");
|
|
59
|
+
expect(mediaResource).toBeInstanceOf(MediaLibraryApplication);
|
|
60
|
+
expectTypeOf(mediaResource).toEqualTypeOf<
|
|
61
|
+
MediaLibraryApplication | undefined
|
|
62
|
+
>();
|
|
63
|
+
|
|
64
|
+
const applicationId = brandUserApplicationId(APPLICATION_DATA.id);
|
|
65
|
+
const foundCoreApplication = applicationList.findApplication(
|
|
66
|
+
"coreApp",
|
|
67
|
+
applicationId,
|
|
68
|
+
);
|
|
69
|
+
expect(foundCoreApplication).toBeInstanceOf(CoreAppApplication);
|
|
70
|
+
expectTypeOf(foundCoreApplication).toEqualTypeOf<
|
|
71
|
+
CoreAppApplication | undefined
|
|
72
|
+
>();
|
|
73
|
+
expect(foundCoreApplication?.id).toBe(applicationId);
|
|
74
|
+
|
|
75
|
+
const foundStudioApplication = applicationList.findApplication(
|
|
76
|
+
"studio",
|
|
77
|
+
brandUserApplicationId(MULTIPLE_WORKSPACE_STUDIO_DATA.id),
|
|
78
|
+
);
|
|
79
|
+
expect(foundStudioApplication).toBeInstanceOf(StudioApplication);
|
|
80
|
+
expectTypeOf(foundStudioApplication).toEqualTypeOf<
|
|
81
|
+
StudioApplication | undefined
|
|
82
|
+
>();
|
|
83
|
+
expect(foundStudioApplication?.id).toBe(
|
|
84
|
+
brandUserApplicationId(MULTIPLE_WORKSPACE_STUDIO_DATA.id),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const workspaceId = StudioWorkspace.makeId(
|
|
88
|
+
brandUserApplicationId(SINGLE_WORKSPACE_STUDIO_DATA.id),
|
|
89
|
+
"default",
|
|
90
|
+
);
|
|
91
|
+
const foundWorkspace = applicationList.findApplication(
|
|
92
|
+
"workspace",
|
|
93
|
+
workspaceId,
|
|
94
|
+
);
|
|
95
|
+
expect(foundWorkspace).toBeInstanceOf(StudioWorkspace);
|
|
96
|
+
expectTypeOf(foundWorkspace).toEqualTypeOf<StudioWorkspace | undefined>();
|
|
97
|
+
expect(foundWorkspace?.id).toBe(workspaceId);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("should find all applications of a given type", () => {
|
|
101
|
+
const applicationList = create();
|
|
102
|
+
|
|
103
|
+
const canvasResources = applicationList.findApplicationsByType("canvas");
|
|
104
|
+
expect(canvasResources).toHaveLength(1);
|
|
105
|
+
expectTypeOf(canvasResources).toEqualTypeOf<CanvasApplication[]>();
|
|
106
|
+
|
|
107
|
+
const mediaResources =
|
|
108
|
+
applicationList.findApplicationsByType("media-library");
|
|
109
|
+
expect(mediaResources).toHaveLength(1);
|
|
110
|
+
expectTypeOf(mediaResources).toEqualTypeOf<MediaLibraryApplication[]>();
|
|
111
|
+
|
|
112
|
+
const coreApplications =
|
|
113
|
+
applicationList.findApplicationsByType("coreApp");
|
|
114
|
+
expect(coreApplications).toHaveLength(1);
|
|
115
|
+
expectTypeOf(coreApplications).toEqualTypeOf<CoreAppApplication[]>();
|
|
116
|
+
|
|
117
|
+
const studioApplications =
|
|
118
|
+
applicationList.findApplicationsByType("studio");
|
|
119
|
+
expect(studioApplications).toHaveLength(2);
|
|
120
|
+
expectTypeOf(studioApplications).toEqualTypeOf<StudioApplication[]>();
|
|
121
|
+
|
|
122
|
+
const workspaceApplications =
|
|
123
|
+
applicationList.findApplicationsByType("workspace");
|
|
124
|
+
expect(workspaceApplications).toHaveLength(9);
|
|
125
|
+
expectTypeOf(workspaceApplications).toEqualTypeOf<StudioWorkspace[]>();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("should allow passing multiple types to findApplicationsByType", () => {
|
|
129
|
+
const applicationList = create();
|
|
130
|
+
|
|
131
|
+
const applications = applicationList.findApplicationsByType([
|
|
132
|
+
"coreApp",
|
|
133
|
+
"studio",
|
|
134
|
+
]);
|
|
135
|
+
expect(applications).toHaveLength(3);
|
|
136
|
+
expect(applications[0]).toBeInstanceOf(CoreAppApplication);
|
|
137
|
+
expect(applications[1]).toBeInstanceOf(StudioApplication);
|
|
138
|
+
expect(applications[2]).toBeInstanceOf(StudioApplication);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("should correctly merge workspaces with other application types when specified", () => {
|
|
142
|
+
const applicationList = create();
|
|
143
|
+
|
|
144
|
+
expect(
|
|
145
|
+
applicationList.findApplicationsByType(["coreApp", "workspace"]),
|
|
146
|
+
).toHaveLength(10);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe("toProtocolResources", () => {
|
|
151
|
+
it("should return an array of protocol resources", () => {
|
|
152
|
+
const applicationList = create();
|
|
153
|
+
const protocolResources = applicationList.toProtocolResources();
|
|
154
|
+
expect(protocolResources).toHaveLength(12);
|
|
155
|
+
expectTypeOf(protocolResources).toEqualTypeOf<ProtocolResource[]>();
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe("static methods", () => {
|
|
160
|
+
describe("areApplicationsEqual", () => {
|
|
161
|
+
it("should return false if an application is undefined", () => {
|
|
162
|
+
const application = new CoreAppApplication(APPLICATION_DATA);
|
|
163
|
+
expect(
|
|
164
|
+
ApplicationList.areApplicationsEqual(undefined, application),
|
|
165
|
+
).toBe(false);
|
|
166
|
+
expect(
|
|
167
|
+
ApplicationList.areApplicationsEqual(application, undefined),
|
|
168
|
+
).toBe(false);
|
|
169
|
+
expect(ApplicationList.areApplicationsEqual(undefined, undefined)).toBe(
|
|
170
|
+
false,
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("should return true if the applications are the same", () => {
|
|
175
|
+
const application1 = new CoreAppApplication(APPLICATION_DATA);
|
|
176
|
+
const application2 = new CoreAppApplication(APPLICATION_DATA);
|
|
177
|
+
expect(
|
|
178
|
+
ApplicationList.areApplicationsEqual(application1, application2),
|
|
179
|
+
).toBe(true);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("should return false if the applications are not the same", () => {
|
|
183
|
+
const application1 = new CoreAppApplication(APPLICATION_DATA);
|
|
184
|
+
const application2 = new StudioApplication(
|
|
185
|
+
MULTIPLE_WORKSPACE_STUDIO_DATA,
|
|
186
|
+
PROJECTS,
|
|
187
|
+
);
|
|
188
|
+
expect(
|
|
189
|
+
ApplicationList.areApplicationsEqual(application1, application2),
|
|
190
|
+
).toBe(false);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("should return true if the applications are the same studio", () => {
|
|
194
|
+
const application1 = new StudioApplication(
|
|
195
|
+
MULTIPLE_WORKSPACE_STUDIO_DATA,
|
|
196
|
+
PROJECTS,
|
|
197
|
+
);
|
|
198
|
+
const application2 = new StudioApplication(
|
|
199
|
+
MULTIPLE_WORKSPACE_STUDIO_DATA,
|
|
200
|
+
PROJECTS,
|
|
201
|
+
);
|
|
202
|
+
expect(
|
|
203
|
+
ApplicationList.areApplicationsEqual(application1, application2),
|
|
204
|
+
).toBe(true);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("should return false if the applications are not the same studio", () => {
|
|
208
|
+
const application1 = new StudioApplication(
|
|
209
|
+
MULTIPLE_WORKSPACE_STUDIO_DATA,
|
|
210
|
+
PROJECTS,
|
|
211
|
+
);
|
|
212
|
+
const application2 = new StudioApplication(
|
|
213
|
+
SINGLE_WORKSPACE_STUDIO_DATA,
|
|
214
|
+
PROJECTS,
|
|
215
|
+
);
|
|
216
|
+
expect(
|
|
217
|
+
ApplicationList.areApplicationsEqual(application1, application2),
|
|
218
|
+
).toBe(false);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { Resource as ProtocolResource } from "@sanity/message-protocol";
|
|
2
|
+
|
|
3
|
+
import type { CanvasApplication } from "../canvases";
|
|
4
|
+
import type { MediaLibraryApplication } from "../media-libraries";
|
|
5
|
+
import type { CoreAppApplication } from "../user-applications/core-app";
|
|
6
|
+
import type {
|
|
7
|
+
StudioApplication,
|
|
8
|
+
StudioWorkspace,
|
|
9
|
+
} from "../user-applications/studio";
|
|
10
|
+
import type { UserApplicationId } from "../user-applications/user-application";
|
|
11
|
+
import { LocalApplication } from "./local-application";
|
|
12
|
+
|
|
13
|
+
type Application =
|
|
14
|
+
| CoreAppApplication
|
|
15
|
+
| StudioApplication
|
|
16
|
+
| StudioWorkspace
|
|
17
|
+
| CanvasApplication
|
|
18
|
+
| MediaLibraryApplication
|
|
19
|
+
| LocalApplication;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @public
|
|
23
|
+
*/
|
|
24
|
+
export class ApplicationList<TApplication extends Application = Application> {
|
|
25
|
+
readonly applications: TApplication[];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param applications - The applications to initialize the list with.
|
|
29
|
+
*/
|
|
30
|
+
constructor(applications: TApplication[]) {
|
|
31
|
+
this.applications = [...applications];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
findApplicationsByType<T extends TApplication["type"]>(
|
|
35
|
+
type: T,
|
|
36
|
+
): Extract<TApplication, { type: T }>[];
|
|
37
|
+
findApplicationsByType<T extends TApplication["type"][]>(
|
|
38
|
+
types: T,
|
|
39
|
+
): Extract<TApplication, { type: T[number] }>[];
|
|
40
|
+
findApplicationsByType(
|
|
41
|
+
_type: TApplication["type"] | TApplication["type"][],
|
|
42
|
+
): TApplication[] {
|
|
43
|
+
const types = Array.isArray(_type) ? _type : [_type];
|
|
44
|
+
|
|
45
|
+
return this.applications.filter((r) => types.includes(r.type));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
findApplication(
|
|
49
|
+
type: "media-library",
|
|
50
|
+
): Extract<TApplication, { type: "media-library" }> | undefined;
|
|
51
|
+
findApplication(
|
|
52
|
+
type: "canvas",
|
|
53
|
+
): Extract<TApplication, { type: "canvas" }> | undefined;
|
|
54
|
+
findApplication(
|
|
55
|
+
type: "coreApp",
|
|
56
|
+
id: UserApplicationId,
|
|
57
|
+
): Extract<TApplication, { type: "coreApp" }> | undefined;
|
|
58
|
+
findApplication(
|
|
59
|
+
type: "studio",
|
|
60
|
+
id: UserApplicationId,
|
|
61
|
+
): Extract<TApplication, { type: "studio" }> | undefined;
|
|
62
|
+
findApplication(
|
|
63
|
+
type: "workspace",
|
|
64
|
+
id: string,
|
|
65
|
+
): Extract<TApplication, { type: "workspace" }> | undefined;
|
|
66
|
+
findApplication(
|
|
67
|
+
type: TApplication["type"],
|
|
68
|
+
id?: UserApplicationId | string,
|
|
69
|
+
): TApplication | undefined {
|
|
70
|
+
switch (type) {
|
|
71
|
+
case "media-library":
|
|
72
|
+
case "canvas":
|
|
73
|
+
return this.applications.find((r) => r.type === type);
|
|
74
|
+
case "coreApp":
|
|
75
|
+
case "studio":
|
|
76
|
+
case "workspace": {
|
|
77
|
+
return this.applications.find((r) => r.type === type && r.id === id);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
toProtocolResources() {
|
|
83
|
+
return this.applications.reduce<ProtocolResource[]>((acc, r) => {
|
|
84
|
+
if (r.type === "studio" || r instanceof LocalApplication) return acc;
|
|
85
|
+
return [...acc, r.toProtocolResource()];
|
|
86
|
+
}, []);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
static areApplicationsEqual(
|
|
90
|
+
application1?: Application,
|
|
91
|
+
application2?: Application,
|
|
92
|
+
): boolean {
|
|
93
|
+
if (
|
|
94
|
+
!application1 ||
|
|
95
|
+
!application2 ||
|
|
96
|
+
application1.type !== application2.type
|
|
97
|
+
) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return application1.id === application2.id;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Resource as ProtocolResource } from "@sanity/message-protocol";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @public
|
|
5
|
+
*/
|
|
6
|
+
export type AbstractApplicationType =
|
|
7
|
+
| "studio"
|
|
8
|
+
| "coreApp"
|
|
9
|
+
| "canvas"
|
|
10
|
+
| "media-library"
|
|
11
|
+
| "workspace";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
export abstract class AbstractApplication<
|
|
17
|
+
TType extends AbstractApplicationType,
|
|
18
|
+
TProtocolResource extends ProtocolResource = Extract<
|
|
19
|
+
ProtocolResource,
|
|
20
|
+
{ type: TType }
|
|
21
|
+
>,
|
|
22
|
+
> {
|
|
23
|
+
readonly type: TType;
|
|
24
|
+
|
|
25
|
+
constructor(type: TType) {
|
|
26
|
+
this.type = type;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
abstract get href(): string;
|
|
30
|
+
|
|
31
|
+
abstract get title(): string;
|
|
32
|
+
|
|
33
|
+
abstract get id(): string;
|
|
34
|
+
|
|
35
|
+
get initials(): string {
|
|
36
|
+
const SYMBOLS = /[^\p{Alpha}\p{N}\p{White_Space}]/gu;
|
|
37
|
+
const WHITESPACE = /\p{White_Space}+/u;
|
|
38
|
+
const ALPHANUMERIC_SEGMENTS = /(\p{N}+|\p{Alpha}+)/gu;
|
|
39
|
+
const IS_NUMERIC = /^\p{N}+$/u;
|
|
40
|
+
|
|
41
|
+
if (!this.title) return "";
|
|
42
|
+
|
|
43
|
+
const namesArray = this.title
|
|
44
|
+
.replace(SYMBOLS, "")
|
|
45
|
+
.split(WHITESPACE)
|
|
46
|
+
.filter(Boolean);
|
|
47
|
+
|
|
48
|
+
if (namesArray.length === 0) return "";
|
|
49
|
+
|
|
50
|
+
if (namesArray.length === 1) {
|
|
51
|
+
const word = namesArray[0];
|
|
52
|
+
const segments = word.match(ALPHANUMERIC_SEGMENTS) || [];
|
|
53
|
+
|
|
54
|
+
if (segments.length === 0) return "";
|
|
55
|
+
if (segments.length === 1) {
|
|
56
|
+
if (word.length === 1) {
|
|
57
|
+
return word.toUpperCase();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (IS_NUMERIC.test(word)) {
|
|
61
|
+
return `${word.charAt(0)}${word.charAt(1)}`.toUpperCase();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return word.charAt(0).toUpperCase();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return `${segments[0]!.charAt(0)}${segments[1].charAt(0)}`.toUpperCase();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return `${namesArray[0].charAt(0)}${namesArray[namesArray.length - 1].charAt(0)}`.toUpperCase();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Converts the resource to a protocol resource that comlink expects
|
|
75
|
+
* for backwards compatibility with the old API format.
|
|
76
|
+
*/
|
|
77
|
+
abstract toProtocolResource(): TProtocolResource;
|
|
78
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, expect, expectTypeOf, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { AbstractApplication } from "./application";
|
|
4
|
+
import type { LocalApplicationData } from "./local-application";
|
|
5
|
+
import { LocalApplication } from "./local-application";
|
|
6
|
+
|
|
7
|
+
const STUDIO_APP: LocalApplicationData = {
|
|
8
|
+
host: "localhost",
|
|
9
|
+
port: 3333,
|
|
10
|
+
type: "studio",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const CORE_APP: LocalApplicationData = {
|
|
14
|
+
host: "127.0.0.1",
|
|
15
|
+
port: 4444,
|
|
16
|
+
type: "coreApp",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
describe("LocalApplication", () => {
|
|
20
|
+
it("is an instance of AbstractApplication", () => {
|
|
21
|
+
expect(new LocalApplication(STUDIO_APP)).toBeInstanceOf(
|
|
22
|
+
AbstractApplication,
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("type", () => {
|
|
27
|
+
it("inherits the type from LocalApplicationData", () => {
|
|
28
|
+
expect(new LocalApplication(STUDIO_APP).type).toBe("studio");
|
|
29
|
+
expect(new LocalApplication(CORE_APP).type).toBe("coreApp");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("has the correct TypeScript type", () => {
|
|
33
|
+
const app = new LocalApplication(STUDIO_APP);
|
|
34
|
+
expectTypeOf(app.type).toEqualTypeOf<"studio" | "coreApp">();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("id", () => {
|
|
39
|
+
it("generates a deterministic id from host and port", () => {
|
|
40
|
+
expect(new LocalApplication(STUDIO_APP).id).toBe("local-localhost-3333");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("is stable across instances with the same data", () => {
|
|
44
|
+
expect(new LocalApplication(STUDIO_APP).id).toBe(
|
|
45
|
+
new LocalApplication(STUDIO_APP).id,
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("differs when host or port differs", () => {
|
|
50
|
+
const a = new LocalApplication(STUDIO_APP);
|
|
51
|
+
const b = new LocalApplication(CORE_APP);
|
|
52
|
+
expect(a.id).not.toBe(b.id);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe("title", () => {
|
|
57
|
+
it("formats as host:port", () => {
|
|
58
|
+
expect(new LocalApplication(STUDIO_APP).title).toBe("localhost:3333");
|
|
59
|
+
expect(new LocalApplication(CORE_APP).title).toBe("127.0.0.1:4444");
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("href", () => {
|
|
64
|
+
it("generates a /local/ route", () => {
|
|
65
|
+
expect(new LocalApplication(STUDIO_APP).href).toBe(
|
|
66
|
+
"/local/localhost:3333",
|
|
67
|
+
);
|
|
68
|
+
expect(new LocalApplication(CORE_APP).href).toBe("/local/127.0.0.1:4444");
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe("initials", () => {
|
|
73
|
+
it("derives initials from the title", () => {
|
|
74
|
+
// "localhost:3333" → strip symbols → "localhost3333"
|
|
75
|
+
// → alpha segment "localhost" + numeric segment "3333" → "L" + "3" = "L3"
|
|
76
|
+
expect(new LocalApplication(STUDIO_APP).initials).toBe("L3");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("handles numeric-only titles (e.g. IP address)", () => {
|
|
80
|
+
// "127.0.0.1:4444" → strip symbols → "1270014444"
|
|
81
|
+
// → single numeric segment → first two chars → "12"
|
|
82
|
+
expect(new LocalApplication(CORE_APP).initials).toBe("12");
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("toProtocolResource", () => {
|
|
87
|
+
it("throws because local applications have no protocol representation", () => {
|
|
88
|
+
expect(() =>
|
|
89
|
+
new LocalApplication(STUDIO_APP).toProtocolResource(),
|
|
90
|
+
).toThrow();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { AbstractApplication } from "./application";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Raw data for a local application discovered by the CLI dev server.
|
|
5
|
+
*
|
|
6
|
+
* @experimental
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
export interface LocalApplicationData {
|
|
10
|
+
host: string;
|
|
11
|
+
port: number;
|
|
12
|
+
type: "studio" | "coreApp";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* An Application wrapping a locally-discovered CLI application.
|
|
17
|
+
* Provides a deterministic id, title and href suitable for use in navigation.
|
|
18
|
+
*
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
export class LocalApplication extends AbstractApplication<
|
|
22
|
+
"studio" | "coreApp",
|
|
23
|
+
never
|
|
24
|
+
> {
|
|
25
|
+
readonly localApplication: LocalApplicationData;
|
|
26
|
+
|
|
27
|
+
constructor(localApplication: LocalApplicationData) {
|
|
28
|
+
super(localApplication.type);
|
|
29
|
+
this.localApplication = localApplication;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get id(): string {
|
|
33
|
+
const { host, port } = this.localApplication;
|
|
34
|
+
return `local-${host}-${port}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get title(): string {
|
|
38
|
+
const { host, port } = this.localApplication;
|
|
39
|
+
return `${host}:${port}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get href(): string {
|
|
43
|
+
const { host, port } = this.localApplication;
|
|
44
|
+
return `/local/${host}:${port}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
toProtocolResource(): never {
|
|
48
|
+
throw new Error(
|
|
49
|
+
"LocalApplication cannot be serialized to a protocol resource",
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { CANVAS_DATA } from "./__tests__/__fixtures__";
|
|
4
|
+
import { CanvasApplication } from "./canvases";
|
|
5
|
+
|
|
6
|
+
const setup = () => new CanvasApplication(CANVAS_DATA);
|
|
7
|
+
|
|
8
|
+
describe("CanvasResource", () => {
|
|
9
|
+
it("should have a type of canvas", () => {
|
|
10
|
+
const resource = setup();
|
|
11
|
+
expect(resource.type).toBe("canvas");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("should have a to of /canvas", () => {
|
|
15
|
+
const resource = setup();
|
|
16
|
+
expect(resource.href).toBe("canvas");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("should have a name of Canvas", () => {
|
|
20
|
+
const resource = setup();
|
|
21
|
+
expect(resource.title).toBe("Canvas");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should have an id", () => {
|
|
25
|
+
const resource = setup();
|
|
26
|
+
expect(resource.id).toBe("cac1Na6lwtEI");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should implement the toProtocolResource method", () => {
|
|
30
|
+
const resource = setup();
|
|
31
|
+
expect(resource.toProtocolResource()).toMatchInlineSnapshot(`
|
|
32
|
+
{
|
|
33
|
+
"id": "cac1Na6lwtEI",
|
|
34
|
+
"type": "canvas",
|
|
35
|
+
}
|
|
36
|
+
`);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { CanvasResource as ProtocolCanvasResource } from "@sanity/message-protocol";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
import { AbstractApplication } from "./applications/application";
|
|
5
|
+
import { OrganizationId } from "./organizations";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Canvas ID schema, branded for type safety.
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
const CanvasId = z.string().nonempty().brand("CanvasId");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Canvas ID type, branded for type safety.
|
|
15
|
+
* @public
|
|
16
|
+
*/
|
|
17
|
+
export type CanvasId = z.output<typeof CanvasId>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Validates and brands a string as a CanvasId.
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
23
|
+
export function brandCanvasId(id: string): CanvasId {
|
|
24
|
+
return CanvasId.parse(id);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const Canvas = z.object({
|
|
28
|
+
id: CanvasId,
|
|
29
|
+
organizationId: OrganizationId,
|
|
30
|
+
status: z.enum(["active", "provisioning"]),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Represents a Canvas resource as returned from the API.
|
|
35
|
+
* @public
|
|
36
|
+
*/
|
|
37
|
+
export type Canvas = z.output<typeof Canvas>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Validates and parses a raw API response into a branded
|
|
41
|
+
* Canvas.
|
|
42
|
+
* @public
|
|
43
|
+
*/
|
|
44
|
+
export function parseCanvas(data: unknown): Canvas {
|
|
45
|
+
return Canvas.parse(data);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Whilst the constructor takes an organization's canvas resource the existance
|
|
50
|
+
* therefore implies the organization has access to the canvas application which
|
|
51
|
+
* is what workbench most importantly wants to know.
|
|
52
|
+
* @public
|
|
53
|
+
*/
|
|
54
|
+
export class CanvasApplication extends AbstractApplication<"canvas"> {
|
|
55
|
+
private readonly canvas: Canvas;
|
|
56
|
+
|
|
57
|
+
constructor(canvas: Canvas) {
|
|
58
|
+
super("canvas");
|
|
59
|
+
|
|
60
|
+
this.canvas = canvas;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get id(): CanvasId {
|
|
64
|
+
return this.canvas.id;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get href(): string {
|
|
68
|
+
return "canvas";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
get title(): string {
|
|
72
|
+
return "Canvas";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
toProtocolResource(): ProtocolCanvasResource {
|
|
76
|
+
return {
|
|
77
|
+
type: "canvas",
|
|
78
|
+
id: this.id,
|
|
79
|
+
} satisfies ProtocolCanvasResource;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { OrganizationId } from "./organizations";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Workbench configuration.
|
|
5
|
+
*
|
|
6
|
+
* @public
|
|
7
|
+
*/
|
|
8
|
+
interface Config {
|
|
9
|
+
/**
|
|
10
|
+
* The organization ID to use when rendering the workbench.
|
|
11
|
+
*/
|
|
12
|
+
organizationId?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The resolved configuration after processing
|
|
17
|
+
* and validating the provided config.
|
|
18
|
+
*
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
21
|
+
type ResolvedConfig = Omit<Config, "organizationId"> & {
|
|
22
|
+
organizationId: OrganizationId;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Options for rendering a remote module, such as a user application.
|
|
27
|
+
*
|
|
28
|
+
* @public
|
|
29
|
+
*/
|
|
30
|
+
interface RemoteModuleRenderOptions {
|
|
31
|
+
reactStrictMode?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type { Config, ResolvedConfig, RemoteModuleRenderOptions };
|