@sanity/workbench-cli 1.1.2 → 1.2.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.
- package/dist/_exports/build.d.ts +76 -8
- package/dist/_exports/build.js.map +1 -1
- package/dist/_exports/deploy.d.ts +151 -12
- package/dist/_exports/deploy.js +2 -3
- package/dist/_exports/deploy.js.map +1 -1
- package/dist/_exports/dev.d.ts +22 -0
- package/dist/_exports/index.d.ts +82 -31
- package/dist/_exports/index.js +1 -11
- package/dist/_exports/index.js.map +1 -1
- package/dist/actions/build/artifact.js +8 -7
- package/dist/actions/build/artifact.js.map +1 -1
- package/dist/actions/build/configs/artifact.js +45 -0
- package/dist/actions/build/configs/artifact.js.map +1 -0
- package/dist/actions/build/vite/plugin.js +6 -14
- package/dist/actions/build/vite/plugin.js.map +1 -1
- package/dist/actions/build/vite/plugins/plugin-sanity-app-id.js +24 -0
- package/dist/actions/build/vite/plugins/plugin-sanity-app-id.js.map +1 -0
- package/dist/actions/build/vite/workbench-vite-plugins.js +8 -4
- package/dist/actions/build/vite/workbench-vite-plugins.js.map +1 -1
- package/dist/actions/deploy/checkBuiltOutput.js +25 -0
- package/dist/actions/deploy/checkBuiltOutput.js.map +1 -0
- package/dist/actions/deploy/deployInstallationConfig.js +91 -0
- package/dist/actions/deploy/deployInstallationConfig.js.map +1 -0
- package/dist/actions/deploy/getWorkbench.js +14 -25
- package/dist/actions/deploy/getWorkbench.js.map +1 -1
- package/dist/actions/deploy/viewDeployment.js +32 -0
- package/dist/actions/deploy/viewDeployment.js.map +1 -0
- package/dist/actions/dev/deriveInterfaces.js +47 -4
- package/dist/actions/dev/deriveInterfaces.js.map +1 -1
- package/dist/actions/dev/exposesSetId.js +69 -0
- package/dist/actions/dev/exposesSetId.js.map +1 -0
- package/dist/actions/dev/registry.js +17 -0
- package/dist/actions/dev/registry.js.map +1 -1
- package/dist/actions/dev/startDevManifestWatcher.js +2 -1
- package/dist/actions/dev/startDevManifestWatcher.js.map +1 -1
- package/dist/actions/dev/startDevServerRegistration.js +26 -11
- package/dist/actions/dev/startDevServerRegistration.js.map +1 -1
- package/dist/actions/dev/startWorkbenchDevServer.js +20 -5
- package/dist/actions/dev/startWorkbenchDevServer.js.map +1 -1
- package/dist/contract.js +33 -38
- package/dist/contract.js.map +1 -1
- package/dist/defineApp.js +55 -1
- package/dist/defineApp.js.map +1 -1
- package/dist/resolveWorkbenchApp.js +4 -1
- package/dist/resolveWorkbenchApp.js.map +1 -1
- package/package.json +7 -4
- package/dist/actions/dev/interfaceSetId.js +0 -51
- package/dist/actions/dev/interfaceSetId.js.map +0 -1
package/dist/contract.js
CHANGED
|
@@ -1,26 +1,13 @@
|
|
|
1
1
|
import { z } from 'zod/mini';
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
// throughout workbench-cli to keep bundles small.
|
|
2
|
+
// Shared module-federation extension contract: interface (view/service) and
|
|
3
|
+
// installation-config declaration schemas, plus the versions the build stamps.
|
|
4
|
+
// `zod/mini` keeps the bundle small.
|
|
5
|
+
/** @internal */ export const VIEW_CONTRACT_VERSION = 1;
|
|
6
|
+
/** @internal */ export const SERVICE_CONTRACT_VERSION = 1;
|
|
7
|
+
/** @internal */ export const MEDIA_LIBRARY_INSTALLATION_CONFIG_CONTRACT_VERSION = 1;
|
|
9
8
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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.
|
|
9
|
+
* Component slots each interface type exposes, in render order. Source of truth
|
|
10
|
+
* for {@link InterfaceType} and the build; add a type by registering it here.
|
|
24
11
|
* @internal
|
|
25
12
|
*/ export const VIEW_COMPONENTS = {
|
|
26
13
|
panel: [
|
|
@@ -28,39 +15,47 @@ import { z } from 'zod/mini';
|
|
|
28
15
|
'panel'
|
|
29
16
|
]
|
|
30
17
|
};
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
* identical (`name` + `src`); `kind` only tailors the validation message. Each
|
|
34
|
-
* declaration adds its `type` discriminator on top.
|
|
35
|
-
*/ function extensionDeclarationFields(kind) {
|
|
18
|
+
// Shared `name` + `src`; `kind` only tailors the validation message.
|
|
19
|
+
function extensionDeclarationFields(kind) {
|
|
36
20
|
const pattern = /^[a-zA-Z0-9_-]+$/;
|
|
37
21
|
return {
|
|
38
22
|
name: z.string().check(z.regex(pattern, `${kind} \`name\` must match ${pattern}`)),
|
|
39
23
|
src: z.string()
|
|
40
24
|
};
|
|
41
25
|
}
|
|
42
|
-
|
|
26
|
+
const PanelViewSchema = z.object({
|
|
43
27
|
type: z.literal('panel'),
|
|
44
28
|
...extensionDeclarationFields('View')
|
|
45
29
|
});
|
|
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', [
|
|
30
|
+
/** @internal */ export const InterfaceDeclarationSchema = z.discriminatedUnion('type', [
|
|
51
31
|
PanelViewSchema
|
|
52
32
|
]);
|
|
53
|
-
|
|
33
|
+
const WorkerServiceSchema = z.object({
|
|
54
34
|
type: z.literal('worker'),
|
|
55
35
|
...extensionDeclarationFields('Service')
|
|
56
36
|
});
|
|
37
|
+
/** @internal */ export const ServiceDeclarationSchema = z.discriminatedUnion('type', [
|
|
38
|
+
WorkerServiceSchema
|
|
39
|
+
]);
|
|
40
|
+
const MediaLibraryFieldSchema = z.object({
|
|
41
|
+
...extensionDeclarationFields('Field'),
|
|
42
|
+
public: z.optional(z.boolean()),
|
|
43
|
+
title: z.string()
|
|
44
|
+
});
|
|
57
45
|
/**
|
|
58
|
-
*
|
|
59
|
-
* into a worker artifact and persisted to the application service on deploy,
|
|
60
|
-
* never part of the app manifest.
|
|
46
|
+
* Stamped where the config crosses a boundary so the authoring model doesn't carry a constant discriminator.
|
|
61
47
|
* @internal
|
|
62
|
-
*/ export const
|
|
63
|
-
|
|
48
|
+
*/ export const INSTALLATION_CONFIG_TYPE = 'installation_config';
|
|
49
|
+
// `appType` is stamped by `unstable_defineMediaLibrary`, never authored.
|
|
50
|
+
const MediaLibraryInstallationConfigSchema = z.object({
|
|
51
|
+
appType: z.literal('media-library'),
|
|
52
|
+
fields: z.array(MediaLibraryFieldSchema).check(z.refine((fields)=>new Set(fields.map((field)=>field.name)).size === fields.length, 'Field `name` must be unique within a media library'))
|
|
53
|
+
});
|
|
54
|
+
/**
|
|
55
|
+
* An app's optional installation config, keyed by `appType`; deploys as a versioned snapshot, not an interface.
|
|
56
|
+
* @internal
|
|
57
|
+
*/ export const InstallationConfigSchema = z.discriminatedUnion('appType', [
|
|
58
|
+
MediaLibraryInstallationConfigSchema
|
|
64
59
|
]);
|
|
65
60
|
|
|
66
61
|
//# sourceMappingURL=contract.js.map
|
package/dist/contract.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/contract.ts"],"sourcesContent":["import {z} from 'zod/mini'\n\n//
|
|
1
|
+
{"version":3,"sources":["../src/contract.ts"],"sourcesContent":["import {z} from 'zod/mini'\n\n// Shared module-federation extension contract: interface (view/service) and\n// installation-config declaration schemas, plus the versions the build stamps.\n// `zod/mini` keeps the bundle small.\n\n/** @internal */\nexport const VIEW_CONTRACT_VERSION = 1\n\n/** @internal */\nexport const SERVICE_CONTRACT_VERSION = 1\n\n/** @internal */\nexport const MEDIA_LIBRARY_INSTALLATION_CONFIG_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/** @public */\nexport interface ViewComponentBaseProps<TView> {\n view: TView\n}\n\n/**\n * Component slots each interface type exposes, in render order. Source of truth\n * for {@link InterfaceType} and the build; 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/** @public */\nexport type InterfaceType = keyof typeof VIEW_COMPONENTS\n\n/** @public */\nexport type ServiceType = 'worker'\n\n// Shared `name` + `src`; `kind` only tailors the validation message.\nfunction extensionDeclarationFields(kind: 'Field' | '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\nconst PanelViewSchema = z.object({\n type: z.literal('panel'),\n ...extensionDeclarationFields('View'),\n})\n\n/** @internal */\nexport const InterfaceDeclarationSchema = z.discriminatedUnion('type', [PanelViewSchema])\n\nconst WorkerServiceSchema = z.object({\n type: z.literal('worker'),\n ...extensionDeclarationFields('Service'),\n})\n\n/** @internal */\nexport const ServiceDeclarationSchema = z.discriminatedUnion('type', [WorkerServiceSchema])\n\nconst MediaLibraryFieldSchema = z.object({\n ...extensionDeclarationFields('Field'),\n public: z.optional(z.boolean()),\n title: z.string(),\n})\n\n/**\n * Stamped where the config crosses a boundary so the authoring model doesn't carry a constant discriminator.\n * @internal\n */\nexport const INSTALLATION_CONFIG_TYPE = 'installation_config'\n\n// `appType` is stamped by `unstable_defineMediaLibrary`, never authored.\nconst MediaLibraryInstallationConfigSchema = z.object({\n appType: z.literal('media-library'),\n fields: z\n .array(MediaLibraryFieldSchema)\n .check(\n z.refine(\n (fields) => new Set(fields.map((field) => field.name)).size === fields.length,\n 'Field `name` must be unique within a media library',\n ),\n ),\n})\n\n/**\n * An app's optional installation config, keyed by `appType`; deploys as a versioned snapshot, not an interface.\n * @internal\n */\nexport const InstallationConfigSchema = z.discriminatedUnion('appType', [\n MediaLibraryInstallationConfigSchema,\n])\n"],"names":["z","VIEW_CONTRACT_VERSION","SERVICE_CONTRACT_VERSION","MEDIA_LIBRARY_INSTALLATION_CONFIG_CONTRACT_VERSION","VIEW_COMPONENTS","panel","extensionDeclarationFields","kind","pattern","name","string","check","regex","src","PanelViewSchema","object","type","literal","InterfaceDeclarationSchema","discriminatedUnion","WorkerServiceSchema","ServiceDeclarationSchema","MediaLibraryFieldSchema","public","optional","boolean","title","INSTALLATION_CONFIG_TYPE","MediaLibraryInstallationConfigSchema","appType","fields","array","refine","Set","map","field","size","length","InstallationConfigSchema"],"mappings":"AAAA,SAAQA,CAAC,QAAO,WAAU;AAE1B,4EAA4E;AAC5E,+EAA+E;AAC/E,qCAAqC;AAErC,cAAc,GACd,OAAO,MAAMC,wBAAwB,EAAC;AAEtC,cAAc,GACd,OAAO,MAAMC,2BAA2B,EAAC;AAEzC,cAAc,GACd,OAAO,MAAMC,qDAAqD,EAAC;AAcnE;;;;CAIC,GACD,OAAO,MAAMC,kBAAkB;IAC7BC,OAAO;QAAC;QAAS;KAAQ;AAC3B,EAAsD;AAQtD,qEAAqE;AACrE,SAASC,2BAA2BC,IAAkC;IACpE,MAAMC,UAAU;IAChB,OAAO;QACLC,MAAMT,EAAEU,MAAM,GAAGC,KAAK,CAACX,EAAEY,KAAK,CAACJ,SAAS,GAAGD,KAAK,qBAAqB,EAAEC,SAAS;QAChFK,KAAKb,EAAEU,MAAM;IACf;AACF;AAEA,MAAMI,kBAAkBd,EAAEe,MAAM,CAAC;IAC/BC,MAAMhB,EAAEiB,OAAO,CAAC;IAChB,GAAGX,2BAA2B,OAAO;AACvC;AAEA,cAAc,GACd,OAAO,MAAMY,6BAA6BlB,EAAEmB,kBAAkB,CAAC,QAAQ;IAACL;CAAgB,EAAC;AAEzF,MAAMM,sBAAsBpB,EAAEe,MAAM,CAAC;IACnCC,MAAMhB,EAAEiB,OAAO,CAAC;IAChB,GAAGX,2BAA2B,UAAU;AAC1C;AAEA,cAAc,GACd,OAAO,MAAMe,2BAA2BrB,EAAEmB,kBAAkB,CAAC,QAAQ;IAACC;CAAoB,EAAC;AAE3F,MAAME,0BAA0BtB,EAAEe,MAAM,CAAC;IACvC,GAAGT,2BAA2B,QAAQ;IACtCiB,QAAQvB,EAAEwB,QAAQ,CAACxB,EAAEyB,OAAO;IAC5BC,OAAO1B,EAAEU,MAAM;AACjB;AAEA;;;CAGC,GACD,OAAO,MAAMiB,2BAA2B,sBAAqB;AAE7D,yEAAyE;AACzE,MAAMC,uCAAuC5B,EAAEe,MAAM,CAAC;IACpDc,SAAS7B,EAAEiB,OAAO,CAAC;IACnBa,QAAQ9B,EACL+B,KAAK,CAACT,yBACNX,KAAK,CACJX,EAAEgC,MAAM,CACN,CAACF,SAAW,IAAIG,IAAIH,OAAOI,GAAG,CAAC,CAACC,QAAUA,MAAM1B,IAAI,GAAG2B,IAAI,KAAKN,OAAOO,MAAM,EAC7E;AAGR;AAEA;;;CAGC,GACD,OAAO,MAAMC,2BAA2BtC,EAAEmB,kBAAkB,CAAC,WAAW;IACtES;CACD,EAAC"}
|
package/dist/defineApp.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod/mini';
|
|
2
|
-
import { InterfaceDeclarationSchema, ServiceDeclarationSchema } from './contract.js';
|
|
2
|
+
import { InstallationConfigSchema, InterfaceDeclarationSchema, ServiceDeclarationSchema } from './contract.js';
|
|
3
3
|
/** Allowed characters for an app `name`. */ const APP_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
4
4
|
/**
|
|
5
5
|
* Internal application discriminator. Sanity-owned singleton apps only;
|
|
@@ -34,6 +34,16 @@ import { InterfaceDeclarationSchema, ServiceDeclarationSchema } from './contract
|
|
|
34
34
|
*/ entry: z.optional(z.string()),
|
|
35
35
|
/** Dock group to render in. Defaults to `dock.applications` when omitted. */ group: z.optional(DockGroupSchema),
|
|
36
36
|
/** Optional icon override (path to an SVG). Wins over manifest/studio icon. */ icon: z.optional(z.string()),
|
|
37
|
+
/**
|
|
38
|
+
* Deployed as a versioned snapshot on the app's org installation, not the
|
|
39
|
+
* application service. Singletons only. Internal, so excluded from the public
|
|
40
|
+
* `DefineAppInput` and set via `@ts-expect-error` like `applicationType`.
|
|
41
|
+
* @internal
|
|
42
|
+
*/ installationConfig: z.optional(InstallationConfigSchema),
|
|
43
|
+
/**
|
|
44
|
+
* Sanity-owned app deployed once, installed per org; excluded from the public `DefineAppInput`.
|
|
45
|
+
* @internal
|
|
46
|
+
*/ isSingleton: z.optional(z.boolean()),
|
|
37
47
|
/** 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
48
|
/** 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
49
|
/** Sort position within the group, ascending. Defaults to `100` when omitted. */ priority: z.optional(z.number()),
|
|
@@ -57,6 +67,14 @@ z.refine((input)=>!(input.applicationType === 'studio' && input.entry !== undefi
|
|
|
57
67
|
path: [
|
|
58
68
|
'entry'
|
|
59
69
|
]
|
|
70
|
+
})).check(// An installation config belongs to a Sanity-owned singleton (the Media
|
|
71
|
+
// Library). A non-singleton declaring one is rejected — see
|
|
72
|
+
// {@link readInstallationConfig} for the runtime guard.
|
|
73
|
+
z.refine((input)=>!(input.installationConfig && !input.isSingleton), {
|
|
74
|
+
error: '`installationConfig` is only supported for singleton apps',
|
|
75
|
+
path: [
|
|
76
|
+
'installationConfig'
|
|
77
|
+
]
|
|
60
78
|
}));
|
|
61
79
|
/**
|
|
62
80
|
* Nominal brand the CLI discriminates on to enable the workbench build/deploy
|
|
@@ -64,6 +82,25 @@ z.refine((input)=>!(input.applicationType === 'studio' && input.entry !== undefi
|
|
|
64
82
|
* boundaries — `@sanity/cli-core` re-derives the same global symbol with
|
|
65
83
|
* `Symbol.for` rather than importing it, so it stays internal to this module.
|
|
66
84
|
*/ const WORKBENCH_APP = Symbol.for('sanity.workbench.defineApp');
|
|
85
|
+
/**
|
|
86
|
+
* Whether `app` is a branded `unstable_defineApp(...)` result — the sole
|
|
87
|
+
* workbench opt-in.
|
|
88
|
+
* @public
|
|
89
|
+
*/ export function isWorkbenchApp(app) {
|
|
90
|
+
return typeof app === 'object' && app !== null && WORKBENCH_APP in app;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The app's installation config, or `undefined` when it declares none. Throws
|
|
94
|
+
* when a non-singleton declares one — configs belong to Sanity-owned singletons,
|
|
95
|
+
* so build/dev/deploy all read it through here to reject the combination
|
|
96
|
+
* consistently.
|
|
97
|
+
* @internal
|
|
98
|
+
*/ export function readInstallationConfig(app) {
|
|
99
|
+
if (app.installationConfig && !app.isSingleton) {
|
|
100
|
+
throw new Error('`installationConfig` is only supported for singleton apps');
|
|
101
|
+
}
|
|
102
|
+
return app.installationConfig;
|
|
103
|
+
}
|
|
67
104
|
/**
|
|
68
105
|
* Declare a Sanity Workbench application. Identity at runtime — returns the same
|
|
69
106
|
* object reference, tagged with the workbench brand. Field validation (the
|
|
@@ -78,5 +115,22 @@ z.refine((input)=>!(input.applicationType === 'studio' && input.entry !== undefi
|
|
|
78
115
|
writable: false
|
|
79
116
|
});
|
|
80
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Declare the Sanity Media Library as a workbench app — a singleton whose `fields` become its installation config.
|
|
120
|
+
* @public
|
|
121
|
+
*/ export function unstable_defineMediaLibrary(input) {
|
|
122
|
+
return unstable_defineApp({
|
|
123
|
+
// @ts-expect-error -- `applicationType`/`isSingleton`/`installationConfig` are internal, excluded from `DefineAppInput`; Sanity-owned apps set them
|
|
124
|
+
applicationType: 'media-library',
|
|
125
|
+
installationConfig: input.fields?.length ? {
|
|
126
|
+
appType: 'media-library',
|
|
127
|
+
fields: input.fields
|
|
128
|
+
} : undefined,
|
|
129
|
+
isSingleton: true,
|
|
130
|
+
name: 'media-library',
|
|
131
|
+
organizationId: input.organizationId,
|
|
132
|
+
title: 'Media Library'
|
|
133
|
+
});
|
|
134
|
+
}
|
|
81
135
|
|
|
82
136
|
//# sourceMappingURL=defineApp.js.map
|
package/dist/defineApp.js.map
CHANGED
|
@@ -1 +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"}
|
|
1
|
+
{"version":3,"sources":["../src/defineApp.ts"],"sourcesContent":["import {z} from 'zod/mini'\n\nimport {\n InstallationConfigSchema,\n InterfaceDeclarationSchema,\n ServiceDeclarationSchema,\n} 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 /**\n * Deployed as a versioned snapshot on the app's org installation, not the\n * application service. Singletons only. Internal, so excluded from the public\n * `DefineAppInput` and set via `@ts-expect-error` like `applicationType`.\n * @internal\n */\n installationConfig: z.optional(InstallationConfigSchema),\n /**\n * Sanity-owned app deployed once, installed per org; excluded from the public `DefineAppInput`.\n * @internal\n */\n isSingleton: z.optional(z.boolean()),\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 .check(\n // An installation config belongs to a Sanity-owned singleton (the Media\n // Library). A non-singleton declaring one is rejected — see\n // {@link readInstallationConfig} for the runtime guard.\n z.refine((input) => !(input.installationConfig && !input.isSingleton), {\n error: '`installationConfig` is only supported for singleton apps',\n path: ['installationConfig'],\n }),\n )\n\n/**\n * User-facing input for `unstable_defineApp`. Excludes the internal\n * `applicationType`, `isSingleton`, and `installationConfig` — validated by the\n * schema but not part of the public surface (Sanity-owned apps set them via\n * `@ts-expect-error`).\n * @public\n */\nexport type DefineAppInput = Omit<\n z.output<typeof DefineAppInputSchema>,\n 'applicationType' | 'installationConfig' | 'isSingleton'\n>\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 * A branded app as the CLI reads it — the full schema shape, including the\n * internal fields `DefineAppInput` omits. Schema-derived so the narrowing\n * can't drift from what the schema validates.\n * @public\n */\nexport type WorkbenchApp = DefineAppResult & z.output<typeof DefineAppInputSchema>\n\n/**\n * Whether `app` is a branded `unstable_defineApp(...)` result — the sole\n * workbench opt-in.\n * @public\n */\nexport function isWorkbenchApp(app: unknown): app is WorkbenchApp {\n return typeof app === 'object' && app !== null && WORKBENCH_APP in app\n}\n\n/**\n * The app's installation config, or `undefined` when it declares none. Throws\n * when a non-singleton declares one — configs belong to Sanity-owned singletons,\n * so build/dev/deploy all read it through here to reject the combination\n * consistently.\n * @internal\n */\nexport function readInstallationConfig(\n app: WorkbenchApp,\n): WorkbenchApp['installationConfig'] | undefined {\n if (app.installationConfig && !app.isSingleton) {\n throw new Error('`installationConfig` is only supported for singleton apps')\n }\n return app.installationConfig\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\n/**\n * One custom field a media library exposes. `src` default-exports a `defineField(...)` schema type.\n * @public\n */\nexport interface MediaLibraryField {\n /** Unique within the media library. */\n name: string\n src: string\n title: string\n\n /** Readable outside the owning organization. */\n public?: boolean\n}\n\n/**\n * Sanity-owned singleton, so authors don't name or title the app — only `organizationId` is required.\n * @public\n */\nexport interface DefineMediaLibraryInput {\n /** Organization that owns the media library — the CLI runs and deploys against it. */\n organizationId: string\n\n fields?: MediaLibraryField[]\n}\n\n/**\n * Declare the Sanity Media Library as a workbench app — a singleton whose `fields` become its installation config.\n * @public\n */\nexport function unstable_defineMediaLibrary(input: DefineMediaLibraryInput): DefineAppResult {\n return unstable_defineApp({\n // @ts-expect-error -- `applicationType`/`isSingleton`/`installationConfig` are internal, excluded from `DefineAppInput`; Sanity-owned apps set them\n applicationType: 'media-library',\n installationConfig: input.fields?.length\n ? {appType: 'media-library', fields: input.fields}\n : undefined,\n isSingleton: true,\n name: 'media-library',\n organizationId: input.organizationId,\n title: 'Media Library',\n })\n}\n"],"names":["z","InstallationConfigSchema","InterfaceDeclarationSchema","ServiceDeclarationSchema","APP_NAME_PATTERN","ApplicationType","enum","DockGroupSchema","DefineAppInputSchema","object","applicationType","optional","entry","string","group","icon","installationConfig","isSingleton","boolean","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","isWorkbenchApp","app","readInstallationConfig","Error","unstable_defineApp","Object","defineProperty","configurable","enumerable","value","writable","unstable_defineMediaLibrary","fields","appType"],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// project. The build and deploy accessors (actions/build, actions/deploy) each
|
|
4
4
|
// build their command-specific view on top of this one brand-check +
|
|
5
5
|
// extraction, so the discrimination lives in exactly one place.
|
|
6
|
-
import { isWorkbenchApp } from '
|
|
6
|
+
import { isWorkbenchApp, readInstallationConfig } from './defineApp.js';
|
|
7
7
|
/**
|
|
8
8
|
* Resolve the workbench app for a CLI config, or `null` for a plain project.
|
|
9
9
|
* @public
|
|
@@ -13,6 +13,9 @@ import { isWorkbenchApp } from '@sanity/cli-core';
|
|
|
13
13
|
return {
|
|
14
14
|
applicationType: app.applicationType,
|
|
15
15
|
entry: app.entry,
|
|
16
|
+
installationConfig: readInstallationConfig(app),
|
|
17
|
+
isSingleton: app.isSingleton ?? false,
|
|
18
|
+
name: app.name,
|
|
16
19
|
services: app.services ?? [],
|
|
17
20
|
views: app.views ?? []
|
|
18
21
|
};
|
|
@@ -1 +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
|
|
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} from '@sanity/cli-core'\n\nimport {\n type DefineAppInput,\n isWorkbenchApp,\n readInstallationConfig,\n type WorkbenchApp,\n} from './defineApp.js'\n\n/**\n * Bundled so adding a declaration family touches this type and the artifact\n * expanders, not every hop of build/dev plumbing in between.\n * @internal\n */\nexport interface WorkbenchExposes {\n installationConfig?: WorkbenchApp['installationConfig']\n services?: DefineAppInput['services']\n views?: DefineAppInput['views']\n}\n\n/** @public */\nexport interface ResolvedWorkbenchApp {\n /** A Sanity-owned singleton (e.g. the Media Library) — deploys its config, not an application. */\n readonly isSingleton: boolean\n /** The app's unique `name` from `unstable_defineApp`. */\n readonly name: string\n /** Background worker services the app declares. */\n readonly services: NonNullable<DefineAppInput['services']>\n\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 /** Deploys on its own path, separate from the interfaces. */\n readonly installationConfig?: WorkbenchApp['installationConfig']\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\n return {\n applicationType: app.applicationType,\n entry: app.entry,\n installationConfig: readInstallationConfig(app),\n isSingleton: app.isSingleton ?? false,\n name: app.name,\n services: app.services ?? [],\n views: app.views ?? [],\n }\n}\n"],"names":["isWorkbenchApp","readInstallationConfig","resolveWorkbenchApp","cliConfig","app","applicationType","entry","installationConfig","isSingleton","name","services","views"],"mappings":"AAAA,gEAAgE;AAChE,+EAA+E;AAC/E,+EAA+E;AAC/E,qEAAqE;AACrE,gEAAgE;AAIhE,SAEEA,cAAc,EACdC,sBAAsB,QAEjB,iBAAgB;AAiCvB;;;CAGC,GACD,OAAO,SAASC,oBACdC,SAAuC;IAEvC,MAAMC,MAAMD,WAAWC;IACvB,IAAI,CAACJ,eAAeI,MAAM,OAAO;IAEjC,OAAO;QACLC,iBAAiBD,IAAIC,eAAe;QACpCC,OAAOF,IAAIE,KAAK;QAChBC,oBAAoBN,uBAAuBG;QAC3CI,aAAaJ,IAAII,WAAW,IAAI;QAChCC,MAAML,IAAIK,IAAI;QACdC,UAAUN,IAAIM,QAAQ,IAAI,EAAE;QAC5BC,OAAOP,IAAIO,KAAK,IAAI,EAAE;IACxB;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/workbench-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Internal implementation detail of the Sanity CLI's unstable workbench support. Not intended for direct use.",
|
|
5
5
|
"homepage": "https://github.com/sanity-io/cli",
|
|
6
6
|
"bugs": "https://github.com/sanity-io/cli/issues",
|
|
@@ -47,16 +47,19 @@
|
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@module-federation/vite": "1.16.11",
|
|
49
49
|
"@vitejs/plugin-react": "^6.0.3",
|
|
50
|
-
"
|
|
50
|
+
"form-data": "^4.0.5",
|
|
51
|
+
"tar-fs": "^3.1.2",
|
|
52
|
+
"vite": "^8.1.3",
|
|
51
53
|
"zod": "^4.4.3",
|
|
52
|
-
"@sanity/cli-core": "^2.
|
|
54
|
+
"@sanity/cli-core": "^2.2.0"
|
|
53
55
|
},
|
|
54
56
|
"devDependencies": {
|
|
55
57
|
"@eslint/compat": "^2.1.0",
|
|
56
|
-
"@sanity/pkg-utils": "^10.
|
|
58
|
+
"@sanity/pkg-utils": "^10.8.1",
|
|
57
59
|
"@swc/cli": "^0.8.1",
|
|
58
60
|
"@swc/core": "^1.15.41",
|
|
59
61
|
"@types/node": "^22.20.0",
|
|
62
|
+
"@types/tar-fs": "^2.0.4",
|
|
60
63
|
"@vitest/coverage-istanbul": "^4.1.9",
|
|
61
64
|
"eslint": "^10.4.1",
|
|
62
65
|
"publint": "^0.3.21",
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Order-independent identity of an app's interface set. Reordering
|
|
3
|
-
* `views`/`services` keeps the same id (not a change); adding, removing,
|
|
4
|
-
* renaming, or repointing one changes it. `undefined` ids to the empty set.
|
|
5
|
-
*/ export function interfaceSetId(interfaces) {
|
|
6
|
-
if (!interfaces || interfaces.length === 0) return '';
|
|
7
|
-
return interfaces.map((iface)=>[
|
|
8
|
-
iface.interface_type,
|
|
9
|
-
iface.name,
|
|
10
|
-
iface.entry_point
|
|
11
|
-
].join('::')).toSorted().join('|');
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Tracks one app's interface set across config reloads. `changed` and `commit`
|
|
15
|
-
* are split so the caller commits only after the rebuild that depends on the new
|
|
16
|
-
* set succeeds — a thrown rebuild leaves it uncommitted, so the next save retries
|
|
17
|
-
* instead of skipping. Seed with the initially registered set.
|
|
18
|
-
*/ export function trackInterfaceSet(initial) {
|
|
19
|
-
let lastId = interfaceSetId(initial);
|
|
20
|
-
return {
|
|
21
|
-
changed: (interfaces)=>interfaceSetId(interfaces) !== lastId,
|
|
22
|
-
commit: (interfaces)=>{
|
|
23
|
-
lastId = interfaceSetId(interfaces);
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
const serverKey = (server)=>`${server.id ?? ''}@${server.host ?? ''}:${server.port}`;
|
|
28
|
-
/**
|
|
29
|
-
* Tracks every registered app's interface set across registry snapshots (the
|
|
30
|
-
* multi-app counterpart to {@link trackInterfaceSet}). `hasChanged` is true when
|
|
31
|
-
* a *known* app's set changed since the last call — its remote was rebuilt with
|
|
32
|
-
* new exposes, so the workbench must full-reload to drop the stale remote-entry.
|
|
33
|
-
* A new/removed app or manifest-only edit isn't a rebuild.
|
|
34
|
-
*/ export function createInterfacesTracker() {
|
|
35
|
-
let known = new Map();
|
|
36
|
-
return {
|
|
37
|
-
hasChanged (servers) {
|
|
38
|
-
const rebuilt = servers.some((server)=>{
|
|
39
|
-
const key = serverKey(server);
|
|
40
|
-
return known.has(key) && known.get(key) !== interfaceSetId(server.interfaces);
|
|
41
|
-
});
|
|
42
|
-
known = new Map(servers.map((server)=>[
|
|
43
|
-
serverKey(server),
|
|
44
|
-
interfaceSetId(server.interfaces)
|
|
45
|
-
]));
|
|
46
|
-
return rebuilt;
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
//# sourceMappingURL=interfaceSetId.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/dev/interfaceSetId.ts"],"sourcesContent":["import {type DevServerInterface} from './deriveInterfaces.js'\nimport {type DevServerManifest} from './registry.js'\n\n/**\n * Order-independent identity of an app's interface set. Reordering\n * `views`/`services` keeps the same id (not a change); adding, removing,\n * renaming, or repointing one changes it. `undefined` ids to the empty set.\n */\nexport function interfaceSetId(interfaces: readonly DevServerInterface[] | undefined): string {\n if (!interfaces || interfaces.length === 0) return ''\n return interfaces\n .map((iface) => [iface.interface_type, iface.name, iface.entry_point].join('::'))\n .toSorted()\n .join('|')\n}\n\n/**\n * Tracks one app's interface set across config reloads. `changed` and `commit`\n * are split so the caller commits only after the rebuild that depends on the new\n * set succeeds — a thrown rebuild leaves it uncommitted, so the next save retries\n * instead of skipping. Seed with the initially registered set.\n */\nexport function trackInterfaceSet(initial: readonly DevServerInterface[] | undefined): {\n changed: (interfaces: readonly DevServerInterface[] | undefined) => boolean\n commit: (interfaces: readonly DevServerInterface[] | undefined) => void\n} {\n let lastId = interfaceSetId(initial)\n return {\n changed: (interfaces) => interfaceSetId(interfaces) !== lastId,\n commit: (interfaces) => {\n lastId = interfaceSetId(interfaces)\n },\n }\n}\n\nconst serverKey = (server: DevServerManifest): string =>\n `${server.id ?? ''}@${server.host ?? ''}:${server.port}`\n\n/**\n * Tracks every registered app's interface set across registry snapshots (the\n * multi-app counterpart to {@link trackInterfaceSet}). `hasChanged` is true when\n * a *known* app's set changed since the last call — its remote was rebuilt with\n * new exposes, so the workbench must full-reload to drop the stale remote-entry.\n * A new/removed app or manifest-only edit isn't a rebuild.\n */\nexport function createInterfacesTracker(): {\n hasChanged: (servers: readonly DevServerManifest[]) => boolean\n} {\n let known = new Map<string, string>()\n return {\n hasChanged(servers) {\n const rebuilt = servers.some((server) => {\n const key = serverKey(server)\n return known.has(key) && known.get(key) !== interfaceSetId(server.interfaces)\n })\n known = new Map(\n servers.map((server) => [serverKey(server), interfaceSetId(server.interfaces)]),\n )\n return rebuilt\n },\n }\n}\n"],"names":["interfaceSetId","interfaces","length","map","iface","interface_type","name","entry_point","join","toSorted","trackInterfaceSet","initial","lastId","changed","commit","serverKey","server","id","host","port","createInterfacesTracker","known","Map","hasChanged","servers","rebuilt","some","key","has","get"],"mappings":"AAGA;;;;CAIC,GACD,OAAO,SAASA,eAAeC,UAAqD;IAClF,IAAI,CAACA,cAAcA,WAAWC,MAAM,KAAK,GAAG,OAAO;IACnD,OAAOD,WACJE,GAAG,CAAC,CAACC,QAAU;YAACA,MAAMC,cAAc;YAAED,MAAME,IAAI;YAAEF,MAAMG,WAAW;SAAC,CAACC,IAAI,CAAC,OAC1EC,QAAQ,GACRD,IAAI,CAAC;AACV;AAEA;;;;;CAKC,GACD,OAAO,SAASE,kBAAkBC,OAAkD;IAIlF,IAAIC,SAASZ,eAAeW;IAC5B,OAAO;QACLE,SAAS,CAACZ,aAAeD,eAAeC,gBAAgBW;QACxDE,QAAQ,CAACb;YACPW,SAASZ,eAAeC;QAC1B;IACF;AACF;AAEA,MAAMc,YAAY,CAACC,SACjB,GAAGA,OAAOC,EAAE,IAAI,GAAG,CAAC,EAAED,OAAOE,IAAI,IAAI,GAAG,CAAC,EAAEF,OAAOG,IAAI,EAAE;AAE1D;;;;;;CAMC,GACD,OAAO,SAASC;IAGd,IAAIC,QAAQ,IAAIC;IAChB,OAAO;QACLC,YAAWC,OAAO;YAChB,MAAMC,UAAUD,QAAQE,IAAI,CAAC,CAACV;gBAC5B,MAAMW,MAAMZ,UAAUC;gBACtB,OAAOK,MAAMO,GAAG,CAACD,QAAQN,MAAMQ,GAAG,CAACF,SAAS3B,eAAegB,OAAOf,UAAU;YAC9E;YACAoB,QAAQ,IAAIC,IACVE,QAAQrB,GAAG,CAAC,CAACa,SAAW;oBAACD,UAAUC;oBAAShB,eAAegB,OAAOf,UAAU;iBAAE;YAEhF,OAAOwB;QACT;IACF;AACF"}
|