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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +24 -0
  2. package/dist/_chunks-es/index.js +39 -0
  3. package/dist/_chunks-es/index.js.map +1 -0
  4. package/dist/_chunks-es/module-federation.js +59 -0
  5. package/dist/_chunks-es/module-federation.js.map +1 -0
  6. package/dist/_chunks-es/studio.js +892 -0
  7. package/dist/_chunks-es/studio.js.map +1 -0
  8. package/dist/_internal.d.ts +16 -4
  9. package/dist/_internal.js +34 -27
  10. package/dist/_internal.js.map +1 -1
  11. package/dist/core.d.ts +2250 -0
  12. package/dist/core.js +74 -0
  13. package/dist/core.js.map +1 -0
  14. package/dist/system.d.ts +2135 -0
  15. package/dist/system.js +887 -0
  16. package/dist/system.js.map +1 -0
  17. package/package.json +34 -6
  18. package/src/_exports/core.ts +1 -0
  19. package/src/_exports/system.ts +1 -0
  20. package/src/_internal/index.ts +2 -1
  21. package/src/_internal/render.ts +72 -43
  22. package/src/core/applications/application-list.ts +104 -0
  23. package/src/core/applications/application.ts +177 -0
  24. package/src/core/applications/interface.ts +126 -0
  25. package/src/core/canvases.ts +92 -0
  26. package/src/core/config.ts +34 -0
  27. package/src/core/env.ts +43 -0
  28. package/src/core/index.ts +13 -0
  29. package/src/core/log/index.ts +125 -0
  30. package/src/core/media-libraries.ts +93 -0
  31. package/src/core/organizations.ts +115 -0
  32. package/src/core/projects.ts +114 -0
  33. package/src/core/shared/urls.ts +129 -0
  34. package/src/core/user-applications/core-app.ts +148 -0
  35. package/src/core/user-applications/studios/index.ts +3 -0
  36. package/src/core/user-applications/studios/schemas.ts +128 -0
  37. package/src/core/user-applications/studios/studio.ts +533 -0
  38. package/src/core/user-applications/studios/workspace.ts +156 -0
  39. package/src/core/user-applications/user-application.ts +222 -0
  40. package/src/system/auth.machine.ts +223 -0
  41. package/src/system/index.ts +22 -0
  42. package/src/system/inspect.ts +40 -0
  43. package/src/system/load-federated-module.ts +53 -0
  44. package/src/system/module-federation.ts +116 -0
  45. package/src/system/remote.machine.ts +219 -0
  46. package/src/system/remotes.machine.ts +92 -0
  47. package/src/system/root.machine.ts +224 -0
  48. package/src/system/service.machine.ts +207 -0
  49. package/src/system/services.machine.ts +120 -0
  50. package/src/system/system-preferences.machine.ts +215 -0
  51. package/src/system/telemetry.machine.ts +179 -0
  52. package/src/_internal/render.test.ts +0 -18
@@ -0,0 +1,115 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Organization ID schema, branded for type safety.
5
+ * @public
6
+ */
7
+ export const OrganizationId = z.string().nonempty().brand("OrganizationId");
8
+
9
+ /**
10
+ * Organization ID type, branded for type safety.
11
+ * @public
12
+ */
13
+ export type OrganizationId = z.output<typeof OrganizationId>;
14
+
15
+ /**
16
+ * Validates and brands a string as an OrganizationId.
17
+ * @public
18
+ */
19
+ export function brandOrganizationId(id: string): OrganizationId {
20
+ return OrganizationId.parse(id);
21
+ }
22
+
23
+ const OrganizationMember = z.object({
24
+ sanityUserId: z.string(),
25
+ isCurrentUser: z.boolean(),
26
+ user: z.object({
27
+ id: z.string(),
28
+ displayName: z.string(),
29
+ familyName: z.string(),
30
+ givenName: z.string(),
31
+ middleName: z.string().nullable(),
32
+ imageUrl: z.string().nullable(),
33
+ email: z.string(),
34
+ loginProvider: z.string(),
35
+ }),
36
+ roles: z.array(
37
+ z.object({
38
+ name: z.string(),
39
+ title: z.string(),
40
+ description: z.string().optional(),
41
+ }),
42
+ ),
43
+ });
44
+
45
+ /**
46
+ * @public
47
+ */
48
+ export type OrganizationMember = z.output<typeof OrganizationMember>;
49
+
50
+ /**
51
+ * Organization schema — validates and brands API responses
52
+ * from the `/organizations/:id` endpoint.
53
+ * @public
54
+ */
55
+ export const Organization = z.object({
56
+ id: OrganizationId,
57
+ name: z.string(),
58
+ slug: z.string().nullable(),
59
+ createdAt: z.string(),
60
+ updatedAt: z.string(),
61
+ dashboardStatus: z.enum(["enabled", "disabled"]),
62
+ aiFeaturesStatus: z.enum(["enabled", "disabled"]),
63
+ defaultRoleName: z.string().nullable(),
64
+ });
65
+
66
+ /**
67
+ * Represents an organization with optional members and
68
+ * features arrays depending on the generic parameters.
69
+ * - `Organization` — base fields only (default)
70
+ * - `Organization<true>` — includes `members`
71
+ * - `Organization<true, true>` — includes both
72
+ * @public
73
+ */
74
+ export type Organization<
75
+ IncludeMembers extends boolean = true,
76
+ IncludeFeatures extends boolean = true,
77
+ > = z.output<typeof Organization> &
78
+ (IncludeMembers extends true ? { members: OrganizationMember[] } : unknown) &
79
+ (IncludeFeatures extends true ? { features: string[] } : unknown);
80
+
81
+ /**
82
+ * Validates and parses a raw API response into a branded
83
+ * Organization. The options control which schema is used —
84
+ * matching what the API returns based on query params.
85
+ * @public
86
+ */
87
+ export function parseOrganization<
88
+ IncludeMembers extends boolean = true,
89
+ IncludeFeatures extends boolean = true,
90
+ >(
91
+ data: unknown,
92
+ options?: {
93
+ includeMembers?: IncludeMembers;
94
+ includeFeatures?: IncludeFeatures;
95
+ },
96
+ ): Organization<IncludeMembers, IncludeFeatures> {
97
+ const includeMembers = options?.includeMembers ?? true;
98
+ const includeFeatures = options?.includeFeatures ?? true;
99
+
100
+ const extensions = {
101
+ ...(includeMembers && {
102
+ members: z.array(OrganizationMember),
103
+ }),
104
+ ...(includeFeatures && {
105
+ features: z.array(z.string()),
106
+ }),
107
+ };
108
+
109
+ const schema =
110
+ Object.keys(extensions).length > 0
111
+ ? Organization.extend(extensions)
112
+ : Organization;
113
+
114
+ return schema.parse(data) as Organization<IncludeMembers, IncludeFeatures>;
115
+ }
@@ -0,0 +1,114 @@
1
+ import { z } from "zod";
2
+
3
+ import { OrganizationId } from "./organizations";
4
+
5
+ /**
6
+ * Project ID schema, branded for type safety.
7
+ * @public
8
+ */
9
+ export const ProjectId = z.string().nonempty().brand("ProjectId");
10
+
11
+ /**
12
+ * Project ID type, branded for type safety.
13
+ * @public
14
+ */
15
+ export type ProjectId = z.output<typeof ProjectId>;
16
+
17
+ /**
18
+ * Validates and brands a string as a ProjectId.
19
+ * @public
20
+ */
21
+ export function brandProjectId(id: string): ProjectId {
22
+ return ProjectId.parse(id);
23
+ }
24
+
25
+ const ProjectMember = z.object({
26
+ id: z.string(),
27
+ createdAt: z.string(),
28
+ updatedAt: z.string(),
29
+ isCurrentUser: z.boolean(),
30
+ isRobot: z.boolean(),
31
+ roles: z.array(
32
+ z.object({
33
+ name: z.string(),
34
+ title: z.string(),
35
+ description: z.string(),
36
+ }),
37
+ ),
38
+ });
39
+
40
+ /**
41
+ * @public
42
+ */
43
+ export type ProjectMember = z.output<typeof ProjectMember>;
44
+
45
+ /**
46
+ * Project schema — validates and brands API responses
47
+ * from the `/projects/:id` endpoint.
48
+ * @public
49
+ */
50
+ export const Project = z.object({
51
+ id: ProjectId,
52
+ displayName: z.string(),
53
+ studioHost: z.string().nullable(),
54
+ organizationId: OrganizationId,
55
+ metadata: z.object({
56
+ color: z.string().optional(),
57
+ externalStudioHost: z.string().optional(),
58
+ initialTemplate: z.string().optional(),
59
+ cliInitializedAt: z.string().optional(),
60
+ integration: z.string().optional(),
61
+ }),
62
+ isBlocked: z.boolean(),
63
+ isDisabled: z.boolean(),
64
+ isDisabledByUser: z.boolean(),
65
+ activityFeedEnabled: z.boolean(),
66
+ createdAt: z.string(),
67
+ updatedAt: z.string(),
68
+ });
69
+
70
+ /**
71
+ * Represents a Sanity project with optional members and
72
+ * features arrays depending on the generic parameters.
73
+ * By default, neither members nor features are included.
74
+ * - `Project` — base fields only (default)
75
+ * - `Project<true>` — includes `members`
76
+ * - `Project<true, true>` — includes both
77
+ * @public
78
+ */
79
+ export type Project<
80
+ IncludeMembers extends boolean = true,
81
+ IncludeFeatures extends boolean = true,
82
+ > = z.output<typeof Project> &
83
+ (IncludeMembers extends true ? { members: ProjectMember[] } : unknown) &
84
+ (IncludeFeatures extends true ? { features: string[] } : unknown);
85
+
86
+ /**
87
+ * Validates and parses a raw API response into a branded
88
+ * Project. The options control which schema is used —
89
+ * matching what the API returns based on query params.
90
+ * @public
91
+ */
92
+ export function parseProject<
93
+ IncludeMembers extends boolean = true,
94
+ IncludeFeatures extends boolean = true,
95
+ >(
96
+ data: unknown,
97
+ options?: {
98
+ includeMembers?: IncludeMembers;
99
+ includeFeatures?: IncludeFeatures;
100
+ },
101
+ ): Project<IncludeMembers, IncludeFeatures> {
102
+ const includeMembers = options?.includeMembers ?? true;
103
+ const includeFeatures = options?.includeFeatures ?? true;
104
+
105
+ const extensions = {
106
+ ...(includeMembers && { members: z.array(ProjectMember) }),
107
+ ...(includeFeatures && { features: z.array(z.string()) }),
108
+ };
109
+
110
+ const schema =
111
+ Object.keys(extensions).length > 0 ? Project.extend(extensions) : Project;
112
+
113
+ return schema.parse(data) as Project<IncludeMembers, IncludeFeatures>;
114
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Joins multiple path segments into a single path string.
3
+ * Handles null, undefined, and URL objects gracefully.
4
+ *
5
+ * @public
6
+ * @param paths - An array of path segments to join.
7
+ * @returns A single joined path string.
8
+ */
9
+ export const joinUrlPaths = (
10
+ ...paths: Array<string | URL | null | undefined>
11
+ ): string => {
12
+ let nextPath = null;
13
+
14
+ const safeJoinSegments = (
15
+ segment1: string | null | undefined,
16
+ segment2: string | null | undefined,
17
+ ): string => {
18
+ if (!segment1) {
19
+ if (!segment2) {
20
+ return "";
21
+ }
22
+
23
+ return segment2;
24
+ }
25
+
26
+ if (!segment2) {
27
+ return segment1;
28
+ }
29
+
30
+ if (segment1.endsWith("/") && segment2.startsWith("/")) {
31
+ return segment1 + segment2.slice(1);
32
+ }
33
+
34
+ if (segment1.endsWith("/") || segment2.startsWith("/")) {
35
+ return segment1 + segment2;
36
+ }
37
+
38
+ return `${segment1}/${segment2}`;
39
+ };
40
+
41
+ const validPaths = paths.filter(
42
+ (path) => path !== null && path !== undefined,
43
+ );
44
+
45
+ for (const path of validPaths) {
46
+ nextPath = safeJoinSegments(
47
+ nextPath,
48
+ path instanceof URL ? path.pathname : path,
49
+ );
50
+ }
51
+
52
+ return nextPath ?? "";
53
+ };
54
+
55
+ /**
56
+ * Returns a normalized path by ensuring it starts with a single leading slash
57
+ * and does not end with a trailing slash (unless it's the root path).
58
+ */
59
+ export function normalizePath(pathname: string): string {
60
+ if (!pathname.startsWith("/")) {
61
+ pathname = `/${pathname}`;
62
+ }
63
+
64
+ if (pathname !== "/" && pathname.endsWith("/")) {
65
+ pathname = pathname.slice(0, -1);
66
+ }
67
+
68
+ return pathname;
69
+ }
70
+
71
+ /**
72
+ * The pathname, search, and hash values of a URL.
73
+ */
74
+ interface Path {
75
+ /**
76
+ * A URL pathname, beginning with a /.
77
+ */
78
+ pathname: string;
79
+ /**
80
+ * A URL search string, beginning with a ?.
81
+ */
82
+ search: string;
83
+ /**
84
+ * A URL fragment identifier, beginning with a #.
85
+ */
86
+ hash: string;
87
+ }
88
+
89
+ /**
90
+ * Parses a string URL path into its separate pathname, search, and hash components.
91
+ */
92
+ export function parsePath(path: string): Partial<Path> {
93
+ let parsedPath: Partial<Path> = {};
94
+
95
+ if (path) {
96
+ let hashIndex = path.indexOf("#");
97
+ if (hashIndex >= 0) {
98
+ parsedPath.hash = path.substring(hashIndex);
99
+ path = path.substring(0, hashIndex);
100
+ }
101
+
102
+ let searchIndex = path.indexOf("?");
103
+ if (searchIndex >= 0) {
104
+ parsedPath.search = path.substring(searchIndex);
105
+ path = path.substring(0, searchIndex);
106
+ }
107
+
108
+ if (path) {
109
+ parsedPath.pathname = path;
110
+ }
111
+ }
112
+
113
+ return parsedPath;
114
+ }
115
+
116
+ /**
117
+ * Creates a string URL path from the given pathname, search, and hash components.
118
+ */
119
+ export function createPath({
120
+ pathname = "/",
121
+ search = "",
122
+ hash = "",
123
+ }: Partial<Path>) {
124
+ if (search && search !== "?")
125
+ pathname += search.charAt(0) === "?" ? search : `?${search}`;
126
+ if (hash && hash !== "#")
127
+ pathname += hash.charAt(0) === "#" ? hash : `#${hash}`;
128
+ return pathname;
129
+ }
@@ -0,0 +1,148 @@
1
+ // eslint-disable-next-line no-restricted-imports
2
+ import type { ApplicationResource as ProtocolApplicationResource } from "@sanity/message-protocol";
3
+ import { z } from "zod";
4
+
5
+ import { OrganizationId } from "../organizations";
6
+ import {
7
+ UserApplication,
8
+ UserApplicationBase,
9
+ ActiveDeployment,
10
+ } from "./user-application";
11
+
12
+ /**
13
+ * @public
14
+ */
15
+ export const CoreAppUserApplicationManifest = z.object({
16
+ version: z.string(),
17
+ icon: z.string().optional(),
18
+ title: z.string().optional(),
19
+ group: z.string().optional(),
20
+ priority: z.number().optional(),
21
+ });
22
+
23
+ /**
24
+ * @public
25
+ */
26
+ export type CoreAppUserApplicationManifest = z.output<
27
+ typeof CoreAppUserApplicationManifest
28
+ >;
29
+
30
+ const CoreAppUserApplicationBase = UserApplicationBase.extend({
31
+ title: z.string(),
32
+ organizationId: OrganizationId,
33
+ type: z.literal("coreApp"),
34
+ });
35
+
36
+ // Core apps surface their manifest exclusively via `activeDeployment.manifest`
37
+ // — they have no top-level `manifest` field and no `manifestData`.
38
+ const CoreAppActiveDeployment = ActiveDeployment.extend({
39
+ manifest: CoreAppUserApplicationManifest.nullable(),
40
+ }).nullable();
41
+
42
+ const InternalCoreAppUserApplication = CoreAppUserApplicationBase.extend({
43
+ urlType: z.literal("internal"),
44
+ activeDeployment: CoreAppActiveDeployment,
45
+ });
46
+
47
+ const ExternalCoreAppUserApplication = CoreAppUserApplicationBase.extend({
48
+ urlType: z.literal("external"),
49
+ activeDeployment: CoreAppActiveDeployment,
50
+ });
51
+
52
+ /**
53
+ * Core application schema — validates and brands API
54
+ * responses from the `/user-applications?appType=coreApp`
55
+ * endpoint. Uses a discriminated union on `urlType`.
56
+ * @public
57
+ */
58
+ const CoreAppUserApplication = z.discriminatedUnion("urlType", [
59
+ InternalCoreAppUserApplication,
60
+ ExternalCoreAppUserApplication,
61
+ ]);
62
+
63
+ /**
64
+ * @public
65
+ */
66
+ export type CoreAppUserApplication = z.output<typeof CoreAppUserApplication>;
67
+
68
+ /**
69
+ * Validates and parses a raw API response into a branded
70
+ * CoreAppUserApplicationData.
71
+ * @public
72
+ */
73
+ export function parseCoreApplication(data: unknown): CoreAppUserApplication {
74
+ return CoreAppUserApplication.parse(data);
75
+ }
76
+
77
+ /**
78
+ * @public
79
+ */
80
+ export class CoreAppApplication extends UserApplication<
81
+ CoreAppUserApplication,
82
+ "coreApp",
83
+ ProtocolApplicationResource
84
+ > {
85
+ readonly activeDeployment: CoreAppUserApplication["activeDeployment"];
86
+
87
+ constructor(
88
+ application: CoreAppUserApplication,
89
+ options: {
90
+ isLocal?: boolean;
91
+ remoteApplication?: CoreAppApplication | null;
92
+ } = {},
93
+ ) {
94
+ super(application, "coreApp", options);
95
+
96
+ this.activeDeployment = application.activeDeployment;
97
+ }
98
+
99
+ // Typed `string | null` so the UI-aware remote subclass can override it to
100
+ // `null` for a non-navigable app (US5, spec 002-workbench-extension-api); the core route itself is always set.
101
+ get href(): string | null {
102
+ return this.isLocal
103
+ ? `/local/${this.id}`
104
+ : `/application/${this.application.id}`;
105
+ }
106
+
107
+ get title() {
108
+ return this.manifest?.title ?? this.application.title;
109
+ }
110
+
111
+ /**
112
+ * Resolves the core app's manifest from `activeDeployment.manifest`. This
113
+ * is the canonical (and only) slot for core app manifests — both for
114
+ * Sanity-deployed apps and for local CLI dev-server apps, which synthesise
115
+ * a deployment to surface the live manifest.
116
+ */
117
+ get manifest(): CoreAppUserApplicationManifest | null {
118
+ return this.activeDeployment?.manifest ?? null;
119
+ }
120
+
121
+ get subtitle() {
122
+ return undefined;
123
+ }
124
+
125
+ get updatedAt() {
126
+ return this.application.updatedAt;
127
+ }
128
+
129
+ get<TKey extends keyof CoreAppUserApplication>(
130
+ attr: TKey,
131
+ ): CoreAppUserApplication[TKey] {
132
+ if (!(attr in this.application)) {
133
+ throw new Error(
134
+ `Attribute ${attr.toString()} does not exist on application ${this.application.id}`,
135
+ );
136
+ }
137
+
138
+ return this.application[attr];
139
+ }
140
+
141
+ toProtocolResource(): ProtocolApplicationResource {
142
+ return {
143
+ ...this.application,
144
+ type: "application",
145
+ url: this.url.toString(),
146
+ } as ProtocolApplicationResource;
147
+ }
148
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./schemas";
2
+ export * from "./studio";
3
+ export * from "./workspace";
@@ -0,0 +1,128 @@
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
+ /**
22
+ * @public
23
+ */
24
+ export const ServerManifest = z.object({
25
+ buildId: z.string().optional(),
26
+ bundleVersion: z.string().optional(),
27
+ version: z.string().optional(),
28
+ group: z.string().optional(),
29
+ priority: z.number().optional(),
30
+ workspaces: z
31
+ .array(
32
+ Workspace.extend({
33
+ dataset: z.string(),
34
+ schemaDescriptorId: z.string(),
35
+ }),
36
+ )
37
+ .optional(),
38
+ });
39
+
40
+ /**
41
+ * @public
42
+ */
43
+ export type ServerManifest = z.output<typeof ServerManifest>;
44
+
45
+ /**
46
+ * @public
47
+ */
48
+ export const ClientManifest = z.object({
49
+ version: z.number(),
50
+ createdAt: z.string(),
51
+ studioVersion: z.string().optional(),
52
+ group: z.string().optional(),
53
+ priority: z.number().optional(),
54
+ workspaces: z.array(
55
+ Workspace.extend({
56
+ schema: z.string(),
57
+ tools: z.string().optional(),
58
+ }),
59
+ ),
60
+ });
61
+
62
+ /**
63
+ * @public
64
+ */
65
+ export type ClientManifest = z.output<typeof ClientManifest>;
66
+
67
+ const StudioUserApplicationBase = UserApplicationBase.extend({
68
+ title: z.string().nullable(),
69
+ projectId: ProjectId,
70
+ type: z.literal("studio"),
71
+ /**
72
+ * @deprecated Use `manifestData` instead.
73
+ */
74
+ manifest: ClientManifest.nullable(),
75
+ manifestData: z.object({ value: ClientManifest }).nullable(),
76
+ autoUpdatingVersion: z.string().nullable(),
77
+ config: z.object({
78
+ "live-manifest": z
79
+ .object({
80
+ createdAt: z.string(),
81
+ updatedAt: z.string(),
82
+ updatedBy: z.string(),
83
+ value: ServerManifest,
84
+ })
85
+ .optional(),
86
+ }),
87
+ });
88
+
89
+ const InternalStudioUserApplication = StudioUserApplicationBase.extend({
90
+ urlType: z.literal("internal"),
91
+ activeDeployment: ActiveDeployment.extend({
92
+ manifest: ServerManifest.nullable(),
93
+ }).nullable(),
94
+ });
95
+
96
+ const ExternalStudioUserApplication = StudioUserApplicationBase.extend({
97
+ urlType: z.literal("external"),
98
+ activeDeployment: ActiveDeployment.extend({
99
+ manifest: ServerManifest.nullable(),
100
+ }).nullable(),
101
+ });
102
+
103
+ /**
104
+ * Studio user application schema — validates and brands API
105
+ * responses from the `/user-applications?appType=studio`
106
+ * endpoint. Uses a discriminated union on `urlType`.
107
+ * @public
108
+ */
109
+ export const StudioUserApplication = z.discriminatedUnion("urlType", [
110
+ InternalStudioUserApplication,
111
+ ExternalStudioUserApplication,
112
+ ]);
113
+
114
+ /**
115
+ * @public
116
+ */
117
+ export type StudioUserApplication = z.output<typeof StudioUserApplication>;
118
+
119
+ /**
120
+ * Validates and parses a raw API response into a branded
121
+ * StudioUserApplication.
122
+ * @public
123
+ */
124
+ export function parseStudioUserApplication(
125
+ data: unknown,
126
+ ): StudioUserApplication {
127
+ return StudioUserApplication.parse(data);
128
+ }