@sanity/workbench-cli 1.1.0-beta.0

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 (59) hide show
  1. package/README.md +10 -0
  2. package/dist/_exports/build.d.ts +129 -0
  3. package/dist/_exports/build.js +9 -0
  4. package/dist/_exports/build.js.map +1 -0
  5. package/dist/_exports/deploy.d.ts +115 -0
  6. package/dist/_exports/deploy.js +6 -0
  7. package/dist/_exports/deploy.js.map +1 -0
  8. package/dist/_exports/dev.d.ts +166 -0
  9. package/dist/_exports/dev.js +10 -0
  10. package/dist/_exports/dev.js.map +1 -0
  11. package/dist/_exports/index.d.ts +307 -0
  12. package/dist/_exports/index.js +15 -0
  13. package/dist/_exports/index.js.map +1 -0
  14. package/dist/_exports/init.d.ts +12 -0
  15. package/dist/_exports/init.js +5 -0
  16. package/dist/_exports/init.js.map +1 -0
  17. package/dist/actions/build/artifact.js +29 -0
  18. package/dist/actions/build/artifact.js.map +1 -0
  19. package/dist/actions/build/render-remote.js +69 -0
  20. package/dist/actions/build/render-remote.js.map +1 -0
  21. package/dist/actions/build/services/artifact.js +122 -0
  22. package/dist/actions/build/services/artifact.js.map +1 -0
  23. package/dist/actions/build/views/artifact.js +31 -0
  24. package/dist/actions/build/views/artifact.js.map +1 -0
  25. package/dist/actions/build/vite/constants.js +5 -0
  26. package/dist/actions/build/vite/constants.js.map +1 -0
  27. package/dist/actions/build/vite/plugin.js +74 -0
  28. package/dist/actions/build/vite/plugin.js.map +1 -0
  29. package/dist/actions/build/vite/plugins/plugin-module-federation.js +53 -0
  30. package/dist/actions/build/vite/plugins/plugin-module-federation.js.map +1 -0
  31. package/dist/actions/build/vite/plugins/plugin-sanity-environment.js +29 -0
  32. package/dist/actions/build/vite/plugins/plugin-sanity-environment.js.map +1 -0
  33. package/dist/actions/build/vite/plugins/plugin-sanity-extension-artifacts.js +33 -0
  34. package/dist/actions/build/vite/plugins/plugin-sanity-extension-artifacts.js.map +1 -0
  35. package/dist/actions/build/vite/plugins/plugin-sanity-federation-runtime.js +83 -0
  36. package/dist/actions/build/vite/plugins/plugin-sanity-federation-runtime.js.map +1 -0
  37. package/dist/actions/build/vite/workbench-vite-plugins.js +43 -0
  38. package/dist/actions/build/vite/workbench-vite-plugins.js.map +1 -0
  39. package/dist/actions/deploy/getWorkbench.js +40 -0
  40. package/dist/actions/deploy/getWorkbench.js.map +1 -0
  41. package/dist/actions/dev/canonicalizeWatchDir.js +23 -0
  42. package/dist/actions/dev/canonicalizeWatchDir.js.map +1 -0
  43. package/dist/actions/dev/processLiveness.js +109 -0
  44. package/dist/actions/dev/processLiveness.js.map +1 -0
  45. package/dist/actions/dev/registry.js +279 -0
  46. package/dist/actions/dev/registry.js.map +1 -0
  47. package/dist/actions/init/cliConfig.js +45 -0
  48. package/dist/actions/init/cliConfig.js.map +1 -0
  49. package/dist/contract.js +66 -0
  50. package/dist/contract.js.map +1 -0
  51. package/dist/defineApp.js +82 -0
  52. package/dist/defineApp.js.map +1 -0
  53. package/dist/defineService.js +19 -0
  54. package/dist/defineService.js.map +1 -0
  55. package/dist/defineView.js +19 -0
  56. package/dist/defineView.js.map +1 -0
  57. package/dist/resolveWorkbenchApp.js +21 -0
  58. package/dist/resolveWorkbenchApp.js.map +1 -0
  59. package/package.json +83 -0
@@ -0,0 +1,279 @@
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, watch, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { coreAppManifestSchema, getSanityDataDir, studioManifestSchema, subdebug } from '@sanity/cli-core';
4
+ import { z } from 'zod/mini';
5
+ import { canonicalizeWatchDir } from './canonicalizeWatchDir.js';
6
+ import { getProcessStartTime, isOurProcess } from './processLiveness.js';
7
+ const devDebug = subdebug('dev');
8
+ /** Bump when the manifest/lock shape changes in a breaking way. */ const REGISTRY_VERSION = 1;
9
+ /**
10
+ * The current process's start time as reported by the OS, for the `startedAt`
11
+ * that `isOurProcess` checks on re-read. Falls back to now when the OS time is
12
+ * unavailable — `new Date()` alone records the write time, which drifts from
13
+ * process start by enough to look stale and get pruned right after writing.
14
+ */ function ownStartedAt() {
15
+ return (getProcessStartTime(process.pid) ?? new Date()).toISOString();
16
+ }
17
+ const devServerManifestSchema = z.object({
18
+ host: z.string(),
19
+ id: z.optional(z.string()),
20
+ /**
21
+ * Interfaces the app exposes, mapped from the declared `views` (dock panels,
22
+ * `interface_type: "panel"`) and `services` (background workers,
23
+ * `interface_type: "worker"`). A service is just an interface, so both live
24
+ * in this one list. Carried separately from the manifest — interfaces live in
25
+ * the application service, not the manifest — so the workbench can render
26
+ * local panels and run local workers without a deploy. `entry_point` is the
27
+ * declared `src`. Lenient by design; the workbench is the authority on the
28
+ * interface shape.
29
+ */ interfaces: z.optional(z.array(z.object({
30
+ entry_point: z.string(),
31
+ interface_type: z.string(),
32
+ name: z.string()
33
+ }))),
34
+ /**
35
+ * Inlined manifest — either a {@link StudioManifest} or {@link CoreAppManifest},
36
+ * validated against the shared cli-core schemas. The registry stores and
37
+ * rebroadcasts it; the CLI is what extracts and writes it.
38
+ */ manifest: z.optional(z.union([
39
+ studioManifestSchema,
40
+ coreAppManifestSchema
41
+ ])),
42
+ /**
43
+ * ISO timestamp of the most recent successful manifest extraction. Bumped
44
+ * on every regeneration so re-writing this registry entry triggers the
45
+ * workbench `watchRegistry` watcher and forces a rebroadcast to clients.
46
+ */ manifestUpdatedAt: z.optional(z.string()),
47
+ pid: z.number(),
48
+ port: z.number(),
49
+ projectId: z.optional(z.string()),
50
+ startedAt: z.string(),
51
+ type: z.enum([
52
+ 'coreApp',
53
+ 'studio'
54
+ ]),
55
+ version: z.literal(REGISTRY_VERSION),
56
+ workDir: z.string()
57
+ });
58
+ /**
59
+ * Path to the dev server registry directory. Lives under the shared Sanity
60
+ * config directory to stay consistent with other CLI paths.
61
+ */ function getRegistryDir() {
62
+ return join(getSanityDataDir(), 'dev-servers');
63
+ }
64
+ /**
65
+ * Write a manifest file for the current process and return a handle with a
66
+ * `release` function that removes it plus an `update` function for patching
67
+ * fields post-registration. Uses synchronous I/O so the file exists before
68
+ * any signal handler could fire.
69
+ */ export function registerDevServer(manifest) {
70
+ const registryDir = getRegistryDir();
71
+ mkdirSync(registryDir, {
72
+ recursive: true
73
+ });
74
+ let current = {
75
+ ...manifest,
76
+ pid: process.pid,
77
+ startedAt: ownStartedAt(),
78
+ version: REGISTRY_VERSION
79
+ };
80
+ const filePath = join(registryDir, `${process.pid}.json`);
81
+ writeFileSync(filePath, JSON.stringify(current, null, 2));
82
+ // Guard against late updates from background tasks (e.g. the initial
83
+ // manifest extraction) landing after `release()` has deleted the file —
84
+ // without this, the update would re-create the registry entry and leak.
85
+ let released = false;
86
+ return {
87
+ release () {
88
+ released = true;
89
+ try {
90
+ unlinkSync(filePath);
91
+ } catch {
92
+ // ENOENT is fine — already cleaned up
93
+ }
94
+ },
95
+ update (patch) {
96
+ if (released) return;
97
+ current = {
98
+ ...current,
99
+ ...patch
100
+ };
101
+ writeFileSync(filePath, JSON.stringify(current, null, 2));
102
+ }
103
+ };
104
+ }
105
+ /**
106
+ * Read all manifest files from the registry, prune stale entries (dead PIDs),
107
+ * and return the live ones.
108
+ */ export function getRegisteredServers() {
109
+ const registryDir = getRegistryDir();
110
+ if (!existsSync(registryDir)) {
111
+ return [];
112
+ }
113
+ const files = readdirSync(registryDir).filter((f)=>f.endsWith('.json'));
114
+ const servers = [];
115
+ for (const file of files){
116
+ const filePath = join(registryDir, file);
117
+ let raw;
118
+ try {
119
+ raw = JSON.parse(readFileSync(filePath, 'utf8'));
120
+ } catch {
121
+ continue;
122
+ }
123
+ const { data, success } = devServerManifestSchema.safeParse(raw);
124
+ if (!success) continue;
125
+ if (isOurProcess(data.pid, data.startedAt)) {
126
+ servers.push(data);
127
+ } else {
128
+ try {
129
+ unlinkSync(filePath);
130
+ } catch {
131
+ // Ignore — another process may have already cleaned it up
132
+ }
133
+ }
134
+ }
135
+ return servers;
136
+ }
137
+ /**
138
+ * Watch the registry directory for changes and invoke the callback with the
139
+ * current list of live servers whenever a change is detected.
140
+ *
141
+ * Uses `fs.watch` with a debounce to coalesce rapid file changes (e.g. a
142
+ * server starting and writing its manifest triggers multiple FS events).
143
+ */ export function watchRegistry(callback) {
144
+ const registryDir = getRegistryDir();
145
+ mkdirSync(registryDir, {
146
+ recursive: true
147
+ });
148
+ // Canonicalize to the real long path so `fs.watch` doesn't abort on Windows
149
+ // short-path dirs. See `canonicalizeWatchDir`.
150
+ const watchDir = canonicalizeWatchDir(registryDir);
151
+ let debounceTimer;
152
+ const notify = ()=>{
153
+ clearTimeout(debounceTimer);
154
+ debounceTimer = setTimeout(()=>{
155
+ callback(getRegisteredServers());
156
+ }, 50);
157
+ };
158
+ const watcher = watch(watchDir, notify);
159
+ return {
160
+ close () {
161
+ clearTimeout(debounceTimer);
162
+ watcher.close();
163
+ }
164
+ };
165
+ }
166
+ // The workbench singleton lock — "one workbench per machine". Lives in the same
167
+ // registry dir and shares the liveness/prune model: a stale lock left by a
168
+ // crashed process is pruned on read so the next acquire isn't blocked forever.
169
+ const workbenchLockSchema = z.object({
170
+ host: z.string(),
171
+ pid: z.number(),
172
+ port: z.number(),
173
+ startedAt: z.string(),
174
+ version: z.literal(REGISTRY_VERSION)
175
+ });
176
+ /**
177
+ * Read the workbench lock file and return its contents if the holding
178
+ * process is still alive. Prunes stale locks from crashed processes.
179
+ */ export function readWorkbenchLock() {
180
+ const lockPath = join(getRegistryDir(), 'workbench.lock');
181
+ let contents;
182
+ try {
183
+ contents = readFileSync(lockPath, 'utf8');
184
+ } catch {
185
+ // File doesn't exist — nothing to prune, nothing to return
186
+ return undefined;
187
+ }
188
+ // Past this point the file exists. Anything that isn't a live, valid lock
189
+ // (unparsable JSON, schema mismatch, dead/reused PID) is stale and must be
190
+ // pruned — otherwise the next `acquireWorkbenchLock` call is blocked by
191
+ // EEXIST forever and `sanity dev` silently no-ops the workbench server.
192
+ const data = parseLockContents(contents);
193
+ devDebug('Read workbench lock: %o', data);
194
+ if (data && isOurProcess(data.pid, data.startedAt)) {
195
+ devDebug('Workbench process is alive at pid %d on port %d', data.pid, data.port);
196
+ return data;
197
+ }
198
+ pruneWorkbenchLock(lockPath);
199
+ return undefined;
200
+ }
201
+ function parseLockContents(contents) {
202
+ try {
203
+ const { data, success } = workbenchLockSchema.safeParse(JSON.parse(contents));
204
+ return success ? data : undefined;
205
+ } catch {
206
+ return undefined;
207
+ }
208
+ }
209
+ function pruneWorkbenchLock(lockPath) {
210
+ try {
211
+ devDebug('Removing stale workbench lock');
212
+ unlinkSync(lockPath);
213
+ devDebug('Stale workbench lock removed');
214
+ } catch {
215
+ // Another process may have already cleaned it up
216
+ }
217
+ }
218
+ /**
219
+ * Attempt to acquire an exclusive lock for the workbench process.
220
+ * Uses `O_EXCL` (the `wx` flag) which is atomic at the OS level — only one
221
+ * process can create the file.
222
+ *
223
+ * The lock stores `{pid, host, port}` so other processes can find the
224
+ * running workbench. Call `updatePort` after the Vite server starts to
225
+ * write the actual port (Vite may pick a different one).
226
+ *
227
+ * @returns A {@link WorkbenchLock} if acquired, or `undefined` if another
228
+ * live process already holds it.
229
+ */ export function acquireWorkbenchLock(info, retries = 1) {
230
+ const registryDir = getRegistryDir();
231
+ mkdirSync(registryDir, {
232
+ recursive: true
233
+ });
234
+ const lockPath = join(registryDir, 'workbench.lock');
235
+ const startedAt = ownStartedAt();
236
+ const lockData = {
237
+ host: info.host,
238
+ pid: process.pid,
239
+ port: info.port,
240
+ startedAt,
241
+ version: REGISTRY_VERSION
242
+ };
243
+ devDebug('Acquiring workbench lock at %s', lockPath);
244
+ try {
245
+ writeFileSync(lockPath, JSON.stringify(lockData), {
246
+ flag: 'wx'
247
+ });
248
+ devDebug('Workbench lock acquired');
249
+ return {
250
+ release () {
251
+ try {
252
+ unlinkSync(lockPath);
253
+ } catch {
254
+ // Already cleaned up
255
+ }
256
+ },
257
+ updatePort (port) {
258
+ writeFileSync(lockPath, JSON.stringify({
259
+ ...lockData,
260
+ port
261
+ }));
262
+ }
263
+ };
264
+ } catch (err) {
265
+ devDebug('Failed to acquire workbench lock: %s', err instanceof Error ? err.message : String(err));
266
+ if (!isNodeError(err) || err.code !== 'EEXIST') return undefined;
267
+ // Lock exists — check if the holder is still alive
268
+ const existing = readWorkbenchLock();
269
+ if (existing) return undefined;
270
+ // Stale lock was pruned by readWorkbenchLock — retry (with guard against infinite recursion)
271
+ if (retries <= 0) return undefined;
272
+ return acquireWorkbenchLock(info, retries - 1);
273
+ }
274
+ }
275
+ function isNodeError(err) {
276
+ return err instanceof Error && 'code' in err;
277
+ }
278
+
279
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/actions/dev/registry.ts"],"sourcesContent":["import {\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n unlinkSync,\n watch,\n writeFileSync,\n} from 'node:fs'\nimport {join} from 'node:path'\n\nimport {\n coreAppManifestSchema,\n getSanityDataDir,\n studioManifestSchema,\n subdebug,\n} from '@sanity/cli-core'\nimport {z} from 'zod/mini'\n\nimport {canonicalizeWatchDir} from './canonicalizeWatchDir.js'\nimport {getProcessStartTime, isOurProcess} from './processLiveness.js'\n\nconst devDebug = subdebug('dev')\n\n/** Bump when the manifest/lock shape changes in a breaking way. */\nconst REGISTRY_VERSION = 1\n\n/**\n * The current process's start time as reported by the OS, for the `startedAt`\n * that `isOurProcess` checks on re-read. Falls back to now when the OS time is\n * unavailable — `new Date()` alone records the write time, which drifts from\n * process start by enough to look stale and get pruned right after writing.\n */\nfunction ownStartedAt(): string {\n return (getProcessStartTime(process.pid) ?? new Date()).toISOString()\n}\n\nconst devServerManifestSchema = z.object({\n host: z.string(),\n id: z.optional(z.string()),\n /**\n * Interfaces the app exposes, mapped from the declared `views` (dock panels,\n * `interface_type: \"panel\"`) and `services` (background workers,\n * `interface_type: \"worker\"`). A service is just an interface, so both live\n * in this one list. Carried separately from the manifest — interfaces live in\n * the application service, not the manifest — so the workbench can render\n * local panels and run local workers without a deploy. `entry_point` is the\n * declared `src`. Lenient by design; the workbench is the authority on the\n * interface shape.\n */\n interfaces: z.optional(\n z.array(z.object({entry_point: z.string(), interface_type: z.string(), name: z.string()})),\n ),\n /**\n * Inlined manifest — either a {@link StudioManifest} or {@link CoreAppManifest},\n * validated against the shared cli-core schemas. The registry stores and\n * rebroadcasts it; the CLI is what extracts and writes it.\n */\n manifest: z.optional(z.union([studioManifestSchema, coreAppManifestSchema])),\n /**\n * ISO timestamp of the most recent successful manifest extraction. Bumped\n * on every regeneration so re-writing this registry entry triggers the\n * workbench `watchRegistry` watcher and forces a rebroadcast to clients.\n */\n manifestUpdatedAt: z.optional(z.string()),\n pid: z.number(),\n port: z.number(),\n projectId: z.optional(z.string()),\n startedAt: z.string(),\n type: z.enum(['coreApp', 'studio']),\n version: z.literal(REGISTRY_VERSION),\n workDir: z.string(),\n})\n/**\n * A manifest describing a running dev server process (studio or app).\n * Stored as `~/.sanity/dev-servers/<pid>.json`.\n *\n * The workbench singleton is tracked separately via the lock file — see\n * `acquireWorkbenchLock` and `readWorkbenchLock` below.\n */\nexport type DevServerManifest = z.infer<typeof devServerManifestSchema>\n\n/**\n * Path to the dev server registry directory. Lives under the shared Sanity\n * config directory to stay consistent with other CLI paths.\n */\nfunction getRegistryDir(): string {\n return join(getSanityDataDir(), 'dev-servers')\n}\n\ninterface DevServerRegistration {\n /** Remove the registry entry. */\n release: () => void\n /**\n * Rewrite the registry entry with partial updates merged in. Also bumps the\n * file's mtime, which fires `watchRegistry` in any workbench process and\n * triggers a rebroadcast to connected clients.\n */\n update: (patch: Partial<Omit<DevServerManifest, 'pid' | 'startedAt' | 'version'>>) => void\n}\n\n/**\n * Write a manifest file for the current process and return a handle with a\n * `release` function that removes it plus an `update` function for patching\n * fields post-registration. Uses synchronous I/O so the file exists before\n * any signal handler could fire.\n */\nexport function registerDevServer(\n manifest: Omit<DevServerManifest, 'pid' | 'startedAt' | 'version'>,\n): DevServerRegistration {\n const registryDir = getRegistryDir()\n mkdirSync(registryDir, {recursive: true})\n\n let current: DevServerManifest = {\n ...manifest,\n pid: process.pid,\n startedAt: ownStartedAt(),\n version: REGISTRY_VERSION,\n }\n\n const filePath = join(registryDir, `${process.pid}.json`)\n writeFileSync(filePath, JSON.stringify(current, null, 2))\n\n // Guard against late updates from background tasks (e.g. the initial\n // manifest extraction) landing after `release()` has deleted the file —\n // without this, the update would re-create the registry entry and leak.\n let released = false\n\n return {\n release() {\n released = true\n try {\n unlinkSync(filePath)\n } catch {\n // ENOENT is fine — already cleaned up\n }\n },\n update(patch) {\n if (released) return\n current = {...current, ...patch}\n writeFileSync(filePath, JSON.stringify(current, null, 2))\n },\n }\n}\n\n/**\n * Read all manifest files from the registry, prune stale entries (dead PIDs),\n * and return the live ones.\n */\nexport function getRegisteredServers(): DevServerManifest[] {\n const registryDir = getRegistryDir()\n\n if (!existsSync(registryDir)) {\n return []\n }\n\n const files = readdirSync(registryDir).filter((f) => f.endsWith('.json'))\n const servers: DevServerManifest[] = []\n\n for (const file of files) {\n const filePath = join(registryDir, file)\n let raw: unknown\n try {\n raw = JSON.parse(readFileSync(filePath, 'utf8'))\n } catch {\n continue\n }\n\n const {data, success} = devServerManifestSchema.safeParse(raw)\n if (!success) continue\n\n if (isOurProcess(data.pid, data.startedAt)) {\n servers.push(data)\n } else {\n try {\n unlinkSync(filePath)\n } catch {\n // Ignore — another process may have already cleaned it up\n }\n }\n }\n\n return servers\n}\n\ninterface RegistryWatcher {\n close(): void\n}\n\n/**\n * Watch the registry directory for changes and invoke the callback with the\n * current list of live servers whenever a change is detected.\n *\n * Uses `fs.watch` with a debounce to coalesce rapid file changes (e.g. a\n * server starting and writing its manifest triggers multiple FS events).\n */\nexport function watchRegistry(callback: (servers: DevServerManifest[]) => void): RegistryWatcher {\n const registryDir = getRegistryDir()\n mkdirSync(registryDir, {recursive: true})\n\n // Canonicalize to the real long path so `fs.watch` doesn't abort on Windows\n // short-path dirs. See `canonicalizeWatchDir`.\n const watchDir = canonicalizeWatchDir(registryDir)\n\n let debounceTimer: ReturnType<typeof setTimeout> | undefined\n\n const notify = () => {\n clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n callback(getRegisteredServers())\n }, 50)\n }\n\n const watcher = watch(watchDir, notify)\n\n return {\n close() {\n clearTimeout(debounceTimer)\n watcher.close()\n },\n }\n}\n\n// The workbench singleton lock — \"one workbench per machine\". Lives in the same\n// registry dir and shares the liveness/prune model: a stale lock left by a\n// crashed process is pruned on read so the next acquire isn't blocked forever.\n\nconst workbenchLockSchema = z.object({\n host: z.string(),\n pid: z.number(),\n port: z.number(),\n startedAt: z.string(),\n version: z.literal(REGISTRY_VERSION),\n})\n\n/**\n * Read the workbench lock file and return its contents if the holding\n * process is still alive. Prunes stale locks from crashed processes.\n */\nexport function readWorkbenchLock(): z.infer<typeof workbenchLockSchema> | undefined {\n const lockPath = join(getRegistryDir(), 'workbench.lock')\n\n let contents: string\n try {\n contents = readFileSync(lockPath, 'utf8')\n } catch {\n // File doesn't exist — nothing to prune, nothing to return\n return undefined\n }\n\n // Past this point the file exists. Anything that isn't a live, valid lock\n // (unparsable JSON, schema mismatch, dead/reused PID) is stale and must be\n // pruned — otherwise the next `acquireWorkbenchLock` call is blocked by\n // EEXIST forever and `sanity dev` silently no-ops the workbench server.\n const data = parseLockContents(contents)\n devDebug('Read workbench lock: %o', data)\n if (data && isOurProcess(data.pid, data.startedAt)) {\n devDebug('Workbench process is alive at pid %d on port %d', data.pid, data.port)\n return data\n }\n\n pruneWorkbenchLock(lockPath)\n return undefined\n}\n\nfunction parseLockContents(contents: string): z.infer<typeof workbenchLockSchema> | undefined {\n try {\n const {data, success} = workbenchLockSchema.safeParse(JSON.parse(contents))\n return success ? data : undefined\n } catch {\n return undefined\n }\n}\n\nfunction pruneWorkbenchLock(lockPath: string): void {\n try {\n devDebug('Removing stale workbench lock')\n unlinkSync(lockPath)\n devDebug('Stale workbench lock removed')\n } catch {\n // Another process may have already cleaned it up\n }\n}\n\ninterface WorkbenchLock {\n /** Release the lock file. */\n release: () => void\n /** Update the lock with the actual port after the server starts listening. */\n updatePort: (port: number) => void\n}\n\n/**\n * Attempt to acquire an exclusive lock for the workbench process.\n * Uses `O_EXCL` (the `wx` flag) which is atomic at the OS level — only one\n * process can create the file.\n *\n * The lock stores `{pid, host, port}` so other processes can find the\n * running workbench. Call `updatePort` after the Vite server starts to\n * write the actual port (Vite may pick a different one).\n *\n * @returns A {@link WorkbenchLock} if acquired, or `undefined` if another\n * live process already holds it.\n */\nexport function acquireWorkbenchLock(\n info: {host: string; port: number},\n retries = 1,\n): WorkbenchLock | undefined {\n const registryDir = getRegistryDir()\n mkdirSync(registryDir, {recursive: true})\n\n const lockPath = join(registryDir, 'workbench.lock')\n const startedAt = ownStartedAt()\n const lockData = {\n host: info.host,\n pid: process.pid,\n port: info.port,\n startedAt,\n version: REGISTRY_VERSION,\n }\n\n devDebug('Acquiring workbench lock at %s', lockPath)\n\n try {\n writeFileSync(lockPath, JSON.stringify(lockData), {flag: 'wx'})\n devDebug('Workbench lock acquired')\n return {\n release() {\n try {\n unlinkSync(lockPath)\n } catch {\n // Already cleaned up\n }\n },\n updatePort(port: number) {\n writeFileSync(lockPath, JSON.stringify({...lockData, port}))\n },\n }\n } catch (err: unknown) {\n devDebug(\n 'Failed to acquire workbench lock: %s',\n err instanceof Error ? err.message : String(err),\n )\n if (!isNodeError(err) || err.code !== 'EEXIST') return undefined\n\n // Lock exists — check if the holder is still alive\n const existing = readWorkbenchLock()\n if (existing) return undefined\n\n // Stale lock was pruned by readWorkbenchLock — retry (with guard against infinite recursion)\n if (retries <= 0) return undefined\n return acquireWorkbenchLock(info, retries - 1)\n }\n}\n\nfunction isNodeError(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && 'code' in err\n}\n"],"names":["existsSync","mkdirSync","readdirSync","readFileSync","unlinkSync","watch","writeFileSync","join","coreAppManifestSchema","getSanityDataDir","studioManifestSchema","subdebug","z","canonicalizeWatchDir","getProcessStartTime","isOurProcess","devDebug","REGISTRY_VERSION","ownStartedAt","process","pid","Date","toISOString","devServerManifestSchema","object","host","string","id","optional","interfaces","array","entry_point","interface_type","name","manifest","union","manifestUpdatedAt","number","port","projectId","startedAt","type","enum","version","literal","workDir","getRegistryDir","registerDevServer","registryDir","recursive","current","filePath","JSON","stringify","released","release","update","patch","getRegisteredServers","files","filter","f","endsWith","servers","file","raw","parse","data","success","safeParse","push","watchRegistry","callback","watchDir","debounceTimer","notify","clearTimeout","setTimeout","watcher","close","workbenchLockSchema","readWorkbenchLock","lockPath","contents","undefined","parseLockContents","pruneWorkbenchLock","acquireWorkbenchLock","info","retries","lockData","flag","updatePort","err","Error","message","String","isNodeError","code","existing"],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ // `sanity.cli.ts` templates for workbench (`unstable_defineApp`) projects,
2
+ // consumed by the CLI's `init` scaffolding. The branded `unstable_defineApp`
3
+ // result is the sole workbench (module-federation) opt-in, so its config shape
4
+ // is workbench's to own; the CLI keeps the non-workbench templates and the
5
+ // `%placeholder%` substitution. `%name%`/`%title%`/etc. are filled in by the
6
+ // CLI's template processor.
7
+ /** App scaffold — `entry` auto-declares the navigable app view. */ export const workbenchAppConfigTemplate = `
8
+ import {defineCliConfig, unstable_defineApp} from 'sanity/cli'
9
+
10
+ export default defineCliConfig({
11
+ app: unstable_defineApp({
12
+ name: '%name%',
13
+ title: '%title%',
14
+ organizationId: '%organizationId%',
15
+ entry: '%entry%',
16
+ }),
17
+ })
18
+ `;
19
+ /**
20
+ * Studio scaffold — brands with name/title only, no `entry` (studio app views
21
+ * aren't implemented yet).
22
+ */ export const workbenchStudioConfigTemplate = `
23
+ import {defineCliConfig, unstable_defineApp} from 'sanity/cli'
24
+
25
+ export default defineCliConfig({
26
+ api: {
27
+ projectId: '%projectId%',
28
+ dataset: '%dataset%'
29
+ },
30
+ app: unstable_defineApp({
31
+ name: '%name%',
32
+ title: '%title%',
33
+ organizationId: '%organizationId%',
34
+ }),
35
+ deployment: {
36
+ /**
37
+ * Enable auto-updates for studios.
38
+ * Learn more at https://www.sanity.io/docs/studio/latest-version-of-sanity#k47faf43faf56
39
+ */
40
+ autoUpdates: __BOOL__autoUpdates__,
41
+ },
42
+ })
43
+ `;
44
+
45
+ //# sourceMappingURL=cliConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/actions/init/cliConfig.ts"],"sourcesContent":["// `sanity.cli.ts` templates for workbench (`unstable_defineApp`) projects,\n// consumed by the CLI's `init` scaffolding. The branded `unstable_defineApp`\n// result is the sole workbench (module-federation) opt-in, so its config shape\n// is workbench's to own; the CLI keeps the non-workbench templates and the\n// `%placeholder%` substitution. `%name%`/`%title%`/etc. are filled in by the\n// CLI's template processor.\n\n/** App scaffold — `entry` auto-declares the navigable app view. */\nexport const workbenchAppConfigTemplate = `\nimport {defineCliConfig, unstable_defineApp} from 'sanity/cli'\n\nexport default defineCliConfig({\n app: unstable_defineApp({\n name: '%name%',\n title: '%title%',\n organizationId: '%organizationId%',\n entry: '%entry%',\n }),\n})\n`\n\n/**\n * Studio scaffold — brands with name/title only, no `entry` (studio app views\n * aren't implemented yet).\n */\nexport const workbenchStudioConfigTemplate = `\nimport {defineCliConfig, unstable_defineApp} from 'sanity/cli'\n\nexport default defineCliConfig({\n api: {\n projectId: '%projectId%',\n dataset: '%dataset%'\n },\n app: unstable_defineApp({\n name: '%name%',\n title: '%title%',\n organizationId: '%organizationId%',\n }),\n deployment: {\n /**\n * Enable auto-updates for studios.\n * Learn more at https://www.sanity.io/docs/studio/latest-version-of-sanity#k47faf43faf56\n */\n autoUpdates: __BOOL__autoUpdates__,\n },\n})\n`\n"],"names":["workbenchAppConfigTemplate","workbenchStudioConfigTemplate"],"mappings":"AAAA,2EAA2E;AAC3E,6EAA6E;AAC7E,+EAA+E;AAC/E,2EAA2E;AAC3E,6EAA6E;AAC7E,4BAA4B;AAE5B,iEAAiE,GACjE,OAAO,MAAMA,6BAA6B,CAAC;;;;;;;;;;;AAW3C,CAAC,CAAA;AAED;;;CAGC,GACD,OAAO,MAAMC,gCAAgC,CAAC;;;;;;;;;;;;;;;;;;;;;AAqB9C,CAAC,CAAA"}
@@ -0,0 +1,66 @@
1
+ import { z } from 'zod/mini';
2
+ // The shared kernel of the module-federation extension contract: the supported
3
+ // view/service types, the contract versions the helpers stamp, and the
4
+ // declaration schemas an app author writes in `unstable_defineApp({views, services})`.
5
+ //
6
+ // Lives in `@sanity/workbench-cli` (alongside the vite plugin that consumes the
7
+ // same contract) so there is a single source of truth. `zod/mini` is used
8
+ // throughout workbench-cli to keep bundles small.
9
+ /**
10
+ * Contract version stamped on every defined view — lets the host and the
11
+ * generated artifact evolve the contract without breaking deployed views.
12
+ * @internal
13
+ */ export const VIEW_CONTRACT_VERSION = 1;
14
+ /**
15
+ * Contract version stamped on every defined service. Lets the workbench host
16
+ * and the generated worker artifact evolve the service contract without
17
+ * breaking already-deployed services; bumped only on a breaking change.
18
+ * @internal
19
+ */ export const SERVICE_CONTRACT_VERSION = 1;
20
+ /**
21
+ * Component slots each interface type exposes, in render order — the source of
22
+ * truth for {@link InterfaceType} and for the build (the vite plugin expands a
23
+ * view into one render artifact per component). Add a type by registering it here.
24
+ * @internal
25
+ */ export const VIEW_COMPONENTS = {
26
+ panel: [
27
+ 'title',
28
+ 'panel'
29
+ ]
30
+ };
31
+ /**
32
+ * Fields every extension declaration shares — a view or a service. The shape is
33
+ * identical (`name` + `src`); `kind` only tailors the validation message. Each
34
+ * declaration adds its `type` discriminator on top.
35
+ */ function extensionDeclarationFields(kind) {
36
+ const pattern = /^[a-zA-Z0-9_-]+$/;
37
+ return {
38
+ name: z.string().check(z.regex(pattern, `${kind} \`name\` must match ${pattern}`)),
39
+ src: z.string()
40
+ };
41
+ }
42
+ /** What an author writes for a `panel` in `unstable_defineApp({views})`. */ const PanelViewSchema = z.object({
43
+ type: z.literal('panel'),
44
+ ...extensionDeclarationFields('View')
45
+ });
46
+ /**
47
+ * The `{type, name, src}` an app declares for a view, discriminated by `type`.
48
+ * Persisted to the application service on deploy; never part of the app manifest.
49
+ * @internal
50
+ */ export const InterfaceDeclarationSchema = z.discriminatedUnion('type', [
51
+ PanelViewSchema
52
+ ]);
53
+ /** Declaration schema for a `worker` service — what a developer writes in `unstable_defineApp({services})`. */ const WorkerServiceSchema = z.object({
54
+ type: z.literal('worker'),
55
+ ...extensionDeclarationFields('Service')
56
+ });
57
+ /**
58
+ * A service declared on an app, discriminated by `type`. Metadata only; built
59
+ * into a worker artifact and persisted to the application service on deploy,
60
+ * never part of the app manifest.
61
+ * @internal
62
+ */ export const ServiceDeclarationSchema = z.discriminatedUnion('type', [
63
+ WorkerServiceSchema
64
+ ]);
65
+
66
+ //# sourceMappingURL=contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/contract.ts"],"sourcesContent":["import {z} from 'zod/mini'\n\n// The shared kernel of the module-federation extension contract: the supported\n// view/service types, the contract versions the helpers stamp, and the\n// declaration schemas an app author writes in `unstable_defineApp({views, services})`.\n//\n// Lives in `@sanity/workbench-cli` (alongside the vite plugin that consumes the\n// same contract) so there is a single source of truth. `zod/mini` is used\n// throughout workbench-cli to keep bundles small.\n\n/**\n * Contract version stamped on every defined view — lets the host and the\n * generated artifact evolve the contract without breaking deployed views.\n * @internal\n */\nexport const VIEW_CONTRACT_VERSION = 1\n\n/**\n * Contract version stamped on every defined service. Lets the workbench host\n * and the generated worker artifact evolve the service contract without\n * breaking already-deployed services; bumped only on a breaking change.\n * @internal\n */\nexport const SERVICE_CONTRACT_VERSION = 1\n\n/**\n * A view component. The return is opaque so the runtime helpers carry no React\n * dependency — the generated artifact renders it with the app's own React.\n * @public\n */\nexport type ViewComponent<TProps> = (props: TProps) => unknown\n\n/**\n * Props every view component receives, whatever its type. Per-type props\n * compose from this, so a prop added here reaches every view.\n * @public\n */\nexport interface ViewComponentBaseProps<TView> {\n view: TView\n}\n\n/**\n * Component slots each interface type exposes, in render order — the source of\n * truth for {@link InterfaceType} and for the build (the vite plugin expands a\n * view into one render artifact per component). Add a type by registering it here.\n * @internal\n */\nexport const VIEW_COMPONENTS = {\n panel: ['title', 'panel'],\n} as const satisfies Record<string, readonly string[]>\n\n/**\n * Every supported interface type — the first argument to `unstable_defineView`.\n * @public\n */\nexport type InterfaceType = keyof typeof VIEW_COMPONENTS\n\n/**\n * Every supported service type — the first argument to `unstable_defineService`.\n * Add a service type by adding its declaration schema below and registering it\n * here.\n * @public\n */\nexport type ServiceType = 'worker'\n\n/**\n * Fields every extension declaration shares — a view or a service. The shape is\n * identical (`name` + `src`); `kind` only tailors the validation message. Each\n * declaration adds its `type` discriminator on top.\n */\nfunction extensionDeclarationFields(kind: 'Service' | 'View') {\n const pattern = /^[a-zA-Z0-9_-]+$/\n return {\n name: z.string().check(z.regex(pattern, `${kind} \\`name\\` must match ${pattern}`)),\n src: z.string(),\n }\n}\n\n/** What an author writes for a `panel` in `unstable_defineApp({views})`. */\nconst PanelViewSchema = z.object({\n type: z.literal('panel'),\n ...extensionDeclarationFields('View'),\n})\n\n/**\n * The `{type, name, src}` an app declares for a view, discriminated by `type`.\n * Persisted to the application service on deploy; never part of the app manifest.\n * @internal\n */\nexport const InterfaceDeclarationSchema = z.discriminatedUnion('type', [PanelViewSchema])\n\n/** Declaration schema for a `worker` service — what a developer writes in `unstable_defineApp({services})`. */\nconst WorkerServiceSchema = z.object({\n type: z.literal('worker'),\n ...extensionDeclarationFields('Service'),\n})\n\n/**\n * A service declared on an app, discriminated by `type`. Metadata only; built\n * into a worker artifact and persisted to the application service on deploy,\n * never part of the app manifest.\n * @internal\n */\nexport const ServiceDeclarationSchema = z.discriminatedUnion('type', [WorkerServiceSchema])\n"],"names":["z","VIEW_CONTRACT_VERSION","SERVICE_CONTRACT_VERSION","VIEW_COMPONENTS","panel","extensionDeclarationFields","kind","pattern","name","string","check","regex","src","PanelViewSchema","object","type","literal","InterfaceDeclarationSchema","discriminatedUnion","WorkerServiceSchema","ServiceDeclarationSchema"],"mappings":"AAAA,SAAQA,CAAC,QAAO,WAAU;AAE1B,+EAA+E;AAC/E,uEAAuE;AACvE,uFAAuF;AACvF,EAAE;AACF,gFAAgF;AAChF,0EAA0E;AAC1E,kDAAkD;AAElD;;;;CAIC,GACD,OAAO,MAAMC,wBAAwB,EAAC;AAEtC;;;;;CAKC,GACD,OAAO,MAAMC,2BAA2B,EAAC;AAkBzC;;;;;CAKC,GACD,OAAO,MAAMC,kBAAkB;IAC7BC,OAAO;QAAC;QAAS;KAAQ;AAC3B,EAAsD;AAgBtD;;;;CAIC,GACD,SAASC,2BAA2BC,IAAwB;IAC1D,MAAMC,UAAU;IAChB,OAAO;QACLC,MAAMR,EAAES,MAAM,GAAGC,KAAK,CAACV,EAAEW,KAAK,CAACJ,SAAS,GAAGD,KAAK,qBAAqB,EAAEC,SAAS;QAChFK,KAAKZ,EAAES,MAAM;IACf;AACF;AAEA,0EAA0E,GAC1E,MAAMI,kBAAkBb,EAAEc,MAAM,CAAC;IAC/BC,MAAMf,EAAEgB,OAAO,CAAC;IAChB,GAAGX,2BAA2B,OAAO;AACvC;AAEA;;;;CAIC,GACD,OAAO,MAAMY,6BAA6BjB,EAAEkB,kBAAkB,CAAC,QAAQ;IAACL;CAAgB,EAAC;AAEzF,6GAA6G,GAC7G,MAAMM,sBAAsBnB,EAAEc,MAAM,CAAC;IACnCC,MAAMf,EAAEgB,OAAO,CAAC;IAChB,GAAGX,2BAA2B,UAAU;AAC1C;AAEA;;;;;CAKC,GACD,OAAO,MAAMe,2BAA2BpB,EAAEkB,kBAAkB,CAAC,QAAQ;IAACC;CAAoB,EAAC"}
@@ -0,0 +1,82 @@
1
+ import { z } from 'zod/mini';
2
+ import { InterfaceDeclarationSchema, ServiceDeclarationSchema } from './contract.js';
3
+ /** Allowed characters for an app `name`. */ const APP_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
4
+ /**
5
+ * Internal application discriminator. Sanity-owned singleton apps only;
6
+ * validated by the schema but excluded from the public `DefineAppInput` type.
7
+ */ const ApplicationType = z.enum([
8
+ 'coreApp',
9
+ 'studio',
10
+ 'canvas',
11
+ 'dashboard',
12
+ 'media-library'
13
+ ]);
14
+ /** Dock groups an app can place itself into. */ const DockGroupSchema = z.enum([
15
+ 'dock.system',
16
+ 'dock.applications',
17
+ 'dock.user'
18
+ ]);
19
+ /**
20
+ * Runtime-validation schema for `unstable_defineApp`. Validates the full shape
21
+ * including the internal `applicationType`; the user-facing `DefineAppInput`
22
+ * type below omits that field.
23
+ * @internal
24
+ */ export const DefineAppInputSchema = z.object({
25
+ /**
26
+ * Internal — Sanity-owned singleton apps only. Validated here but excluded
27
+ * from the public `DefineAppInput` type.
28
+ * @internal
29
+ */ applicationType: z.optional(ApplicationType),
30
+ /**
31
+ * App entrypoint module. Defaults to `./src/App.tsx` when omitted. The build
32
+ * derives the app's navigable `app` view from it. SDK apps only — setting it
33
+ * on a studio is rejected (studio app views are not yet implemented).
34
+ */ entry: z.optional(z.string()),
35
+ /** Dock group to render in. Defaults to `dock.applications` when omitted. */ group: z.optional(DockGroupSchema),
36
+ /** Optional icon override (path to an SVG). Wins over manifest/studio icon. */ icon: z.optional(z.string()),
37
+ /** Unique app identifier — must match `APP_NAME_PATTERN`. */ name: z.string().check(z.regex(APP_NAME_PATTERN, 'App `name` must match /^[a-zA-Z0-9_-]+$/')),
38
+ /** Organization that owns the app — the workbench runs and deploys against it. */ organizationId: z.string("App `organizationId` is required — pass the owning organization's ID to `unstable_defineApp`"),
39
+ /** Sort position within the group, ascending. Defaults to `100` when omitted. */ priority: z.optional(z.number()),
40
+ /**
41
+ * Background services the app runs (e.g. a `worker` emitting dock badges).
42
+ * Metadata only — built into worker artifacts and persisted to the
43
+ * application service on deploy, not into the app manifest. Service `name`s
44
+ * must be unique within the app.
45
+ */ services: z.optional(z.array(ServiceDeclarationSchema).check(z.refine((services)=>new Set(services.map((service)=>service.name)).size === services.length, 'Service `name` must be unique within an app'))),
46
+ /** User-facing app title. Wins over studio.config.ts title on merge. */ title: z.string(),
47
+ /**
48
+ * Views the app exposes (e.g. dock panels). Metadata only — built into
49
+ * render artifacts and persisted to the application service on deploy, not
50
+ * into the app manifest. View `name`s must be unique within the app.
51
+ */ views: z.optional(z.array(InterfaceDeclarationSchema).check(z.refine((views)=>new Set(views.map((view)=>view.name)).size === views.length, 'View `name` must be unique within an app')))
52
+ }).check(// Studio app views are not implemented yet. A studio that declares `entry`
53
+ // (the SDK app-view entrypoint) is rejected here rather than silently
54
+ // generating one; studios keep navigating via their existing render path.
55
+ z.refine((input)=>!(input.applicationType === 'studio' && input.entry !== undefined), {
56
+ error: 'App views for studios are not implemented yet',
57
+ path: [
58
+ 'entry'
59
+ ]
60
+ }));
61
+ /**
62
+ * Nominal brand the CLI discriminates on to enable the workbench build/deploy
63
+ * codepath. Registered via `Symbol.for` so the marker survives module-realm
64
+ * boundaries — `@sanity/cli-core` re-derives the same global symbol with
65
+ * `Symbol.for` rather than importing it, so it stays internal to this module.
66
+ */ const WORKBENCH_APP = Symbol.for('sanity.workbench.defineApp');
67
+ /**
68
+ * Declare a Sanity Workbench application. Identity at runtime — returns the same
69
+ * object reference, tagged with the workbench brand. Field validation (the
70
+ * `name` pattern etc.) runs at build time in the CLI via `DefineAppInputSchema`;
71
+ * this helper stays a thin, pure identity wrapper.
72
+ * @public
73
+ */ export function unstable_defineApp(input) {
74
+ return Object.defineProperty(input, WORKBENCH_APP, {
75
+ configurable: false,
76
+ enumerable: false,
77
+ value: true,
78
+ writable: false
79
+ });
80
+ }
81
+
82
+ //# sourceMappingURL=defineApp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/defineApp.ts"],"sourcesContent":["import {z} from 'zod/mini'\n\nimport {InterfaceDeclarationSchema, ServiceDeclarationSchema} from './contract.js'\n\n/** Allowed characters for an app `name`. */\nconst APP_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/\n\n/**\n * Internal application discriminator. Sanity-owned singleton apps only;\n * validated by the schema but excluded from the public `DefineAppInput` type.\n */\nconst ApplicationType = z.enum(['coreApp', 'studio', 'canvas', 'dashboard', 'media-library'])\n\n/** Dock groups an app can place itself into. */\nconst DockGroupSchema = z.enum(['dock.system', 'dock.applications', 'dock.user'])\n\n/**\n * Dock group identifier. The API does not block a user app from declaring a\n * reserved group (e.g. `dock.system`); priority conventions keep Sanity-owned\n * apps ahead.\n * @public\n */\nexport type DockGroup = z.output<typeof DockGroupSchema>\n\n/**\n * Runtime-validation schema for `unstable_defineApp`. Validates the full shape\n * including the internal `applicationType`; the user-facing `DefineAppInput`\n * type below omits that field.\n * @internal\n */\nexport const DefineAppInputSchema = z\n .object({\n /**\n * Internal — Sanity-owned singleton apps only. Validated here but excluded\n * from the public `DefineAppInput` type.\n * @internal\n */\n applicationType: z.optional(ApplicationType),\n /**\n * App entrypoint module. Defaults to `./src/App.tsx` when omitted. The build\n * derives the app's navigable `app` view from it. SDK apps only — setting it\n * on a studio is rejected (studio app views are not yet implemented).\n */\n entry: z.optional(z.string()),\n /** Dock group to render in. Defaults to `dock.applications` when omitted. */\n group: z.optional(DockGroupSchema),\n /** Optional icon override (path to an SVG). Wins over manifest/studio icon. */\n icon: z.optional(z.string()),\n /** Unique app identifier — must match `APP_NAME_PATTERN`. */\n name: z.string().check(z.regex(APP_NAME_PATTERN, 'App `name` must match /^[a-zA-Z0-9_-]+$/')),\n /** Organization that owns the app — the workbench runs and deploys against it. */\n organizationId: z.string(\n \"App `organizationId` is required — pass the owning organization's ID to `unstable_defineApp`\",\n ),\n /** Sort position within the group, ascending. Defaults to `100` when omitted. */\n priority: z.optional(z.number()),\n /**\n * Background services the app runs (e.g. a `worker` emitting dock badges).\n * Metadata only — built into worker artifacts and persisted to the\n * application service on deploy, not into the app manifest. Service `name`s\n * must be unique within the app.\n */\n services: z.optional(\n z\n .array(ServiceDeclarationSchema)\n .check(\n z.refine(\n (services) => new Set(services.map((service) => service.name)).size === services.length,\n 'Service `name` must be unique within an app',\n ),\n ),\n ),\n /** User-facing app title. Wins over studio.config.ts title on merge. */\n title: z.string(),\n /**\n * Views the app exposes (e.g. dock panels). Metadata only — built into\n * render artifacts and persisted to the application service on deploy, not\n * into the app manifest. View `name`s must be unique within the app.\n */\n views: z.optional(\n z\n .array(InterfaceDeclarationSchema)\n .check(\n z.refine(\n (views) => new Set(views.map((view) => view.name)).size === views.length,\n 'View `name` must be unique within an app',\n ),\n ),\n ),\n })\n .check(\n // Studio app views are not implemented yet. A studio that declares `entry`\n // (the SDK app-view entrypoint) is rejected here rather than silently\n // generating one; studios keep navigating via their existing render path.\n z.refine((input) => !(input.applicationType === 'studio' && input.entry !== undefined), {\n error: 'App views for studios are not implemented yet',\n path: ['entry'],\n }),\n )\n\n/**\n * User-facing input for `unstable_defineApp`. Excludes the internal\n * `applicationType` — that field is validated by the schema but is not part of\n * the public surface (Sanity-owned apps set it via `@ts-expect-error`).\n * @public\n */\nexport type DefineAppInput = Omit<z.output<typeof DefineAppInputSchema>, 'applicationType'>\n\n/**\n * Nominal brand the CLI discriminates on to enable the workbench build/deploy\n * codepath. Registered via `Symbol.for` so the marker survives module-realm\n * boundaries — `@sanity/cli-core` re-derives the same global symbol with\n * `Symbol.for` rather than importing it, so it stays internal to this module.\n */\nconst WORKBENCH_APP: unique symbol = Symbol.for('sanity.workbench.defineApp')\n\n/**\n * The branded result of `unstable_defineApp`. Carries the same fields as the\n * input plus the internal brand — users only ever see `DefineAppInput`.\n * @public\n */\nexport interface DefineAppResult extends DefineAppInput {\n readonly [WORKBENCH_APP]: true\n}\n\n/**\n * Declare a Sanity Workbench application. Identity at runtime — returns the same\n * object reference, tagged with the workbench brand. Field validation (the\n * `name` pattern etc.) runs at build time in the CLI via `DefineAppInputSchema`;\n * this helper stays a thin, pure identity wrapper.\n * @public\n */\nexport function unstable_defineApp(input: DefineAppInput): DefineAppResult {\n return Object.defineProperty(input, WORKBENCH_APP, {\n configurable: false,\n enumerable: false,\n value: true,\n writable: false,\n }) as DefineAppResult\n}\n"],"names":["z","InterfaceDeclarationSchema","ServiceDeclarationSchema","APP_NAME_PATTERN","ApplicationType","enum","DockGroupSchema","DefineAppInputSchema","object","applicationType","optional","entry","string","group","icon","name","check","regex","organizationId","priority","number","services","array","refine","Set","map","service","size","length","title","views","view","input","undefined","error","path","WORKBENCH_APP","Symbol","for","unstable_defineApp","Object","defineProperty","configurable","enumerable","value","writable"],"mappings":"AAAA,SAAQA,CAAC,QAAO,WAAU;AAE1B,SAAQC,0BAA0B,EAAEC,wBAAwB,QAAO,gBAAe;AAElF,0CAA0C,GAC1C,MAAMC,mBAAmB;AAEzB;;;CAGC,GACD,MAAMC,kBAAkBJ,EAAEK,IAAI,CAAC;IAAC;IAAW;IAAU;IAAU;IAAa;CAAgB;AAE5F,8CAA8C,GAC9C,MAAMC,kBAAkBN,EAAEK,IAAI,CAAC;IAAC;IAAe;IAAqB;CAAY;AAUhF;;;;;CAKC,GACD,OAAO,MAAME,uBAAuBP,EACjCQ,MAAM,CAAC;IACN;;;;KAIC,GACDC,iBAAiBT,EAAEU,QAAQ,CAACN;IAC5B;;;;KAIC,GACDO,OAAOX,EAAEU,QAAQ,CAACV,EAAEY,MAAM;IAC1B,2EAA2E,GAC3EC,OAAOb,EAAEU,QAAQ,CAACJ;IAClB,6EAA6E,GAC7EQ,MAAMd,EAAEU,QAAQ,CAACV,EAAEY,MAAM;IACzB,2DAA2D,GAC3DG,MAAMf,EAAEY,MAAM,GAAGI,KAAK,CAAChB,EAAEiB,KAAK,CAACd,kBAAkB;IACjD,gFAAgF,GAChFe,gBAAgBlB,EAAEY,MAAM,CACtB;IAEF,+EAA+E,GAC/EO,UAAUnB,EAAEU,QAAQ,CAACV,EAAEoB,MAAM;IAC7B;;;;;KAKC,GACDC,UAAUrB,EAAEU,QAAQ,CAClBV,EACGsB,KAAK,CAACpB,0BACNc,KAAK,CACJhB,EAAEuB,MAAM,CACN,CAACF,WAAa,IAAIG,IAAIH,SAASI,GAAG,CAAC,CAACC,UAAYA,QAAQX,IAAI,GAAGY,IAAI,KAAKN,SAASO,MAAM,EACvF;IAIR,sEAAsE,GACtEC,OAAO7B,EAAEY,MAAM;IACf;;;;KAIC,GACDkB,OAAO9B,EAAEU,QAAQ,CACfV,EACGsB,KAAK,CAACrB,4BACNe,KAAK,CACJhB,EAAEuB,MAAM,CACN,CAACO,QAAU,IAAIN,IAAIM,MAAML,GAAG,CAAC,CAACM,OAASA,KAAKhB,IAAI,GAAGY,IAAI,KAAKG,MAAMF,MAAM,EACxE;AAIV,GACCZ,KAAK,CACJ,2EAA2E;AAC3E,sEAAsE;AACtE,0EAA0E;AAC1EhB,EAAEuB,MAAM,CAAC,CAACS,QAAU,CAAEA,CAAAA,MAAMvB,eAAe,KAAK,YAAYuB,MAAMrB,KAAK,KAAKsB,SAAQ,GAAI;IACtFC,OAAO;IACPC,MAAM;QAAC;KAAQ;AACjB,IACD;AAUH;;;;;CAKC,GACD,MAAMC,gBAA+BC,OAAOC,GAAG,CAAC;AAWhD;;;;;;CAMC,GACD,OAAO,SAASC,mBAAmBP,KAAqB;IACtD,OAAOQ,OAAOC,cAAc,CAACT,OAAOI,eAAe;QACjDM,cAAc;QACdC,YAAY;QACZC,OAAO;QACPC,UAAU;IACZ;AACF"}
@@ -0,0 +1,19 @@
1
+ import { SERVICE_CONTRACT_VERSION } from './contract.js';
2
+ /**
3
+ * Define a Sanity Workbench background service. The first argument narrows the
4
+ * callback shape — `"worker"` runs the callback inside a Web Worker, where it
5
+ * can emit dock-badge updates and return a disposer.
6
+ *
7
+ * Identity at runtime: returns the callback tagged with its type and the contract
8
+ * version, for the CLI build to generate a worker artifact from. Used as the
9
+ * default export of a service's `src` file.
10
+ * @public
11
+ */ export function unstable_defineService(type, run) {
12
+ return {
13
+ run,
14
+ type,
15
+ version: SERVICE_CONTRACT_VERSION
16
+ };
17
+ }
18
+
19
+ //# sourceMappingURL=defineService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/defineService.ts"],"sourcesContent":["import {SERVICE_CONTRACT_VERSION, type ServiceType} from './contract.js'\n\n/**\n * The service's own declaration, surfaced to the callback.\n * @public\n */\nexport interface ServiceInfo {\n readonly name: string\n readonly type: string\n}\n\n/**\n * Context every service callback receives when its worker starts. Mirrors how a\n * view component receives its `view` — the service receives its own `service`.\n * @public\n */\nexport interface ServiceContext {\n readonly service: ServiceInfo\n}\n\n/**\n * A service callback. Runs once inside the worker on start; returns an optional\n * disposer the host calls before terminating the worker.\n * @public\n */\nexport type ServiceCallback = (context: ServiceContext) => (() => void) | void\n\n/** The callback shape each service type defines, keyed by type. */\ninterface ServiceCallbacksByType {\n worker: ServiceCallback\n}\n\n/**\n * The result of `unstable_defineService`: the author's callback, the service\n * type, and the internal contract version the worker artifact targets.\n * @public\n */\nexport interface DefinedService<TType extends ServiceType = ServiceType> {\n readonly run: ServiceCallbacksByType[TType]\n readonly type: TType\n /** @internal */\n readonly version: typeof SERVICE_CONTRACT_VERSION\n}\n\n/**\n * Define a Sanity Workbench background service. The first argument narrows the\n * callback shape — `\"worker\"` runs the callback inside a Web Worker, where it\n * can emit dock-badge updates and return a disposer.\n *\n * Identity at runtime: returns the callback tagged with its type and the contract\n * version, for the CLI build to generate a worker artifact from. Used as the\n * default export of a service's `src` file.\n * @public\n */\nexport function unstable_defineService<TType extends ServiceType>(\n type: TType,\n run: ServiceCallbacksByType[TType],\n): DefinedService<TType> {\n return {run, type, version: SERVICE_CONTRACT_VERSION}\n}\n"],"names":["SERVICE_CONTRACT_VERSION","unstable_defineService","type","run","version"],"mappings":"AAAA,SAAQA,wBAAwB,QAAyB,gBAAe;AA4CxE;;;;;;;;;CASC,GACD,OAAO,SAASC,uBACdC,IAAW,EACXC,GAAkC;IAElC,OAAO;QAACA;QAAKD;QAAME,SAASJ;IAAwB;AACtD"}
@@ -0,0 +1,19 @@
1
+ import { VIEW_CONTRACT_VERSION } from './contract.js';
2
+ /**
3
+ * Define a Sanity Workbench view. The first argument narrows the component shape
4
+ * and the props each component receives — `"panel"` yields a `{title, panel}`
5
+ * record whose components are typed with the panel props.
6
+ *
7
+ * Returns the component(s) tagged with their type and the contract version, for
8
+ * the CLI build to generate render artifacts from. Used as the default export of
9
+ * a view's `src` file.
10
+ * @public
11
+ */ export function unstable_defineView(type, components) {
12
+ return {
13
+ components,
14
+ type,
15
+ version: VIEW_CONTRACT_VERSION
16
+ };
17
+ }
18
+
19
+ //# sourceMappingURL=defineView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/defineView.ts"],"sourcesContent":["import {\n type InterfaceType,\n VIEW_CONTRACT_VERSION,\n type ViewComponent,\n type ViewComponentBaseProps,\n} from './contract.js'\n\n/**\n * Props a panel component receives: its interface record, minus the\n * service-assigned `id`/`deployment_id` a local dev server can't provide. Mirrors\n * the `panel` record the workbench host renders from (the wire format owned by\n * `@sanity/workbench`); drift is guarded by the stamped contract version.\n * @public\n */\nexport type PanelViewProps = ViewComponentBaseProps<{\n entry_point: string\n interface_type: 'panel'\n name: string\n}>\n\n/**\n * The component slots a `panel` view exposes — each its own module-federation\n * island, typed with the panel props.\n * @public\n */\nexport interface PanelViewComponents {\n panel: ViewComponent<PanelViewProps>\n title: ViewComponent<PanelViewProps>\n}\n\n/**\n * A panel's view-component slot — the module-federation expose for one island.\n * @public\n */\nexport type PanelComponent = keyof PanelViewComponents\n\n/**\n * The components each interface type exposes, keyed by type.\n * @public\n */\nexport interface ViewComponentsByType {\n panel: PanelViewComponents\n}\n\n/**\n * The result of `unstable_defineView`: the author's component(s), the view type,\n * and the internal contract version the build artifact targets.\n * @public\n */\nexport interface DefinedView<TType extends InterfaceType = InterfaceType> {\n readonly components: ViewComponentsByType[TType]\n readonly type: TType\n /** @internal */\n readonly version: typeof VIEW_CONTRACT_VERSION\n}\n\n/**\n * Define a Sanity Workbench view. The first argument narrows the component shape\n * and the props each component receives — `\"panel\"` yields a `{title, panel}`\n * record whose components are typed with the panel props.\n *\n * Returns the component(s) tagged with their type and the contract version, for\n * the CLI build to generate render artifacts from. Used as the default export of\n * a view's `src` file.\n * @public\n */\nexport function unstable_defineView<TType extends InterfaceType>(\n type: TType,\n components: ViewComponentsByType[TType],\n): DefinedView<TType> {\n return {components, type, version: VIEW_CONTRACT_VERSION}\n}\n"],"names":["VIEW_CONTRACT_VERSION","unstable_defineView","type","components","version"],"mappings":"AAAA,SAEEA,qBAAqB,QAGhB,gBAAe;AAmDtB;;;;;;;;;CASC,GACD,OAAO,SAASC,oBACdC,IAAW,EACXC,UAAuC;IAEvC,OAAO;QAACA;QAAYD;QAAME,SAASJ;IAAqB;AAC1D"}
@@ -0,0 +1,21 @@
1
+ // Package-internal shared resolver: turn a CLI config's branded
2
+ // `unstable_defineApp` app into its declared interfaces, or `null` for a plain
3
+ // project. The build and deploy accessors (actions/build, actions/deploy) each
4
+ // build their command-specific view on top of this one brand-check +
5
+ // extraction, so the discrimination lives in exactly one place.
6
+ import { isWorkbenchApp } from '@sanity/cli-core';
7
+ /**
8
+ * Resolve the workbench app for a CLI config, or `null` for a plain project.
9
+ * @public
10
+ */ export function resolveWorkbenchApp(cliConfig) {
11
+ const app = cliConfig?.app;
12
+ if (!isWorkbenchApp(app)) return null;
13
+ return {
14
+ applicationType: app.applicationType,
15
+ entry: app.entry,
16
+ services: app.services ?? [],
17
+ views: app.views ?? []
18
+ };
19
+ }
20
+
21
+ //# sourceMappingURL=resolveWorkbenchApp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/resolveWorkbenchApp.ts"],"sourcesContent":["// Package-internal shared resolver: turn a CLI config's branded\n// `unstable_defineApp` app into its declared interfaces, or `null` for a plain\n// project. The build and deploy accessors (actions/build, actions/deploy) each\n// build their command-specific view on top of this one brand-check +\n// extraction, so the discrimination lives in exactly one place.\n\nimport {type CliConfig, isWorkbenchApp} from '@sanity/cli-core'\n\nimport {type DefineAppInput} from './defineApp.js'\n\n/** @public */\nexport interface ResolvedWorkbenchApp {\n /** Background worker services the app declares. */\n readonly services: NonNullable<DefineAppInput['services']>\n /** Dock panel views the app declares. */\n readonly views: NonNullable<DefineAppInput['views']>\n\n /** Resolved app kind — `studio` or one of the SDK app types. */\n readonly applicationType?: string\n /** SDK app-view entrypoint, when declared. */\n readonly entry?: string\n}\n\n/**\n * Resolve the workbench app for a CLI config, or `null` for a plain project.\n * @public\n */\nexport function resolveWorkbenchApp(\n cliConfig: CliConfig | null | undefined,\n): ResolvedWorkbenchApp | null {\n const app = cliConfig?.app\n if (!isWorkbenchApp(app)) return null\n return {\n applicationType: app.applicationType,\n entry: app.entry,\n services: app.services ?? [],\n views: app.views ?? [],\n }\n}\n"],"names":["isWorkbenchApp","resolveWorkbenchApp","cliConfig","app","applicationType","entry","services","views"],"mappings":"AAAA,gEAAgE;AAChE,+EAA+E;AAC/E,+EAA+E;AAC/E,qEAAqE;AACrE,gEAAgE;AAEhE,SAAwBA,cAAc,QAAO,mBAAkB;AAiB/D;;;CAGC,GACD,OAAO,SAASC,oBACdC,SAAuC;IAEvC,MAAMC,MAAMD,WAAWC;IACvB,IAAI,CAACH,eAAeG,MAAM,OAAO;IACjC,OAAO;QACLC,iBAAiBD,IAAIC,eAAe;QACpCC,OAAOF,IAAIE,KAAK;QAChBC,UAAUH,IAAIG,QAAQ,IAAI,EAAE;QAC5BC,OAAOJ,IAAII,KAAK,IAAI,EAAE;IACxB;AACF"}