@sanity/workbench-cli 1.2.0 → 1.4.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 (61) hide show
  1. package/dist/_exports/build.d.ts +22 -17
  2. package/dist/_exports/deploy.d.ts +169 -23
  3. package/dist/_exports/deploy.js +4 -1
  4. package/dist/_exports/deploy.js.map +1 -1
  5. package/dist/_exports/dev.d.ts +9 -5
  6. package/dist/_exports/index.d.ts +19 -15
  7. package/dist/_exports/undeploy.d.ts +248 -0
  8. package/dist/_exports/undeploy.js +3 -0
  9. package/dist/_exports/undeploy.js.map +1 -0
  10. package/dist/actions/build/artifact.js +2 -2
  11. package/dist/actions/build/artifact.js.map +1 -1
  12. package/dist/actions/build/configs/artifact.js +5 -5
  13. package/dist/actions/build/configs/artifact.js.map +1 -1
  14. package/dist/actions/build/vite/plugin.js +5 -0
  15. package/dist/actions/build/vite/plugin.js.map +1 -1
  16. package/dist/actions/build/vite/plugins/plugin-sanity-app-id.js +5 -3
  17. package/dist/actions/build/vite/plugins/plugin-sanity-app-id.js.map +1 -1
  18. package/dist/actions/build/vite/plugins/plugin-sanity-environment.js +26 -2
  19. package/dist/actions/build/vite/plugins/plugin-sanity-environment.js.map +1 -1
  20. package/dist/actions/deploy/buildExposes.js +59 -0
  21. package/dist/actions/deploy/buildExposes.js.map +1 -0
  22. package/dist/actions/deploy/checkBuiltOutput.js +4 -3
  23. package/dist/actions/deploy/checkBuiltOutput.js.map +1 -1
  24. package/dist/actions/deploy/deployConfig.js +63 -0
  25. package/dist/actions/deploy/deployConfig.js.map +1 -0
  26. package/dist/actions/deploy/deployWorkbenchApp.js +108 -0
  27. package/dist/actions/deploy/deployWorkbenchApp.js.map +1 -0
  28. package/dist/actions/deploy/getWorkbench.js +4 -4
  29. package/dist/actions/deploy/getWorkbench.js.map +1 -1
  30. package/dist/actions/dev/deriveInterfaces.js +43 -32
  31. package/dist/actions/dev/deriveInterfaces.js.map +1 -1
  32. package/dist/actions/dev/exposesSetId.js +8 -8
  33. package/dist/actions/dev/exposesSetId.js.map +1 -1
  34. package/dist/actions/dev/registry.js +22 -12
  35. package/dist/actions/dev/registry.js.map +1 -1
  36. package/dist/actions/dev/startDevManifestWatcher.js +2 -2
  37. package/dist/actions/dev/startDevManifestWatcher.js.map +1 -1
  38. package/dist/actions/dev/startDevServerRegistration.js +7 -7
  39. package/dist/actions/dev/startDevServerRegistration.js.map +1 -1
  40. package/dist/actions/dev/startWorkbenchDevServer.js +9 -6
  41. package/dist/actions/dev/startWorkbenchDevServer.js.map +1 -1
  42. package/dist/actions/dev/writeWorkbenchRuntime.js +4 -1
  43. package/dist/actions/dev/writeWorkbenchRuntime.js.map +1 -1
  44. package/dist/actions/undeploy/workbenchUndeployAdapter.js +150 -0
  45. package/dist/actions/undeploy/workbenchUndeployAdapter.js.map +1 -0
  46. package/dist/contract.js +16 -8
  47. package/dist/contract.js.map +1 -1
  48. package/dist/defineApp.js +25 -20
  49. package/dist/defineApp.js.map +1 -1
  50. package/dist/defineView.js.map +1 -1
  51. package/dist/resolveWorkbenchApp.js +4 -3
  52. package/dist/resolveWorkbenchApp.js.map +1 -1
  53. package/dist/services/apiVersion.js +5 -0
  54. package/dist/services/apiVersion.js.map +1 -0
  55. package/dist/services/applications.js +100 -0
  56. package/dist/services/applications.js.map +1 -0
  57. package/dist/services/installations.js +63 -0
  58. package/dist/services/installations.js.map +1 -0
  59. package/package.json +7 -3
  60. package/dist/actions/deploy/deployInstallationConfig.js +0 -91
  61. package/dist/actions/deploy/deployInstallationConfig.js.map +0 -1
package/dist/defineApp.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod/mini';
2
- import { InstallationConfigSchema, InterfaceDeclarationSchema, ServiceDeclarationSchema } from './contract.js';
2
+ import { ConfigSchema, 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;
@@ -27,6 +27,12 @@ import { InstallationConfigSchema, InterfaceDeclarationSchema, ServiceDeclaratio
27
27
  * from the public `DefineAppInput` type.
28
28
  * @internal
29
29
  */ applicationType: z.optional(ApplicationType),
30
+ /**
31
+ * Deployed as a versioned snapshot on the app's org installation, not the
32
+ * application service. Singletons only. Internal, so excluded from the public
33
+ * `DefineAppInput` and set via `@ts-expect-error` like `applicationType`.
34
+ * @internal
35
+ */ config: z.optional(ConfigSchema),
30
36
  /**
31
37
  * App entrypoint module. Defaults to `./src/App.tsx` when omitted. The build
32
38
  * derives the app's navigable `app` view from it. SDK apps only — setting it
@@ -34,12 +40,6 @@ import { InstallationConfigSchema, InterfaceDeclarationSchema, ServiceDeclaratio
34
40
  */ entry: z.optional(z.string()),
35
41
  /** Dock group to render in. Defaults to `dock.applications` when omitted. */ group: z.optional(DockGroupSchema),
36
42
  /** 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
43
  /**
44
44
  * Sanity-owned app deployed once, installed per org; excluded from the public `DefineAppInput`.
45
45
  * @internal
@@ -53,6 +53,11 @@ import { InstallationConfigSchema, InterfaceDeclarationSchema, ServiceDeclaratio
53
53
  * application service on deploy, not into the app manifest. Service `name`s
54
54
  * must be unique within the app.
55
55
  */ 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'))),
56
+ /**
57
+ * Hostname the application is created at on first deploy. Generated when
58
+ * omitted; redeploys target `deployment.appId` and ignore it. SDK apps
59
+ * only — studios use `studioHost` in sanity.cli.ts.
60
+ */ slug: z.optional(z.string()),
56
61
  /** User-facing app title. Wins over studio.config.ts title on merge. */ title: z.string(),
57
62
  /**
58
63
  * Views the app exposes (e.g. dock panels). Metadata only — built into
@@ -67,13 +72,13 @@ z.refine((input)=>!(input.applicationType === 'studio' && input.entry !== undefi
67
72
  path: [
68
73
  'entry'
69
74
  ]
70
- })).check(// An installation config belongs to a Sanity-owned singleton (the Media
75
+ })).check(// An config belongs to a Sanity-owned singleton (the Media
71
76
  // 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',
77
+ // {@link readConfig} for the runtime guard.
78
+ z.refine((input)=>!(input.config && !input.isSingleton), {
79
+ error: '`config` is only supported for singleton apps',
75
80
  path: [
76
- 'installationConfig'
81
+ 'config'
77
82
  ]
78
83
  }));
79
84
  /**
@@ -90,16 +95,16 @@ z.refine((input)=>!(input.installationConfig && !input.isSingleton), {
90
95
  return typeof app === 'object' && app !== null && WORKBENCH_APP in app;
91
96
  }
92
97
  /**
93
- * The app's installation config, or `undefined` when it declares none. Throws
98
+ * The app's config, or `undefined` when it declares none. Throws
94
99
  * when a non-singleton declares one — configs belong to Sanity-owned singletons,
95
100
  * so build/dev/deploy all read it through here to reject the combination
96
101
  * consistently.
97
102
  * @internal
98
- */ export function readInstallationConfig(app) {
99
- if (app.installationConfig && !app.isSingleton) {
100
- throw new Error('`installationConfig` is only supported for singleton apps');
103
+ */ export function readConfig(app) {
104
+ if (app.config && !app.isSingleton) {
105
+ throw new Error('`config` is only supported for singleton apps');
101
106
  }
102
- return app.installationConfig;
107
+ return app.config;
103
108
  }
104
109
  /**
105
110
  * Declare a Sanity Workbench application. Identity at runtime — returns the same
@@ -116,13 +121,13 @@ z.refine((input)=>!(input.installationConfig && !input.isSingleton), {
116
121
  });
117
122
  }
118
123
  /**
119
- * Declare the Sanity Media Library as a workbench app — a singleton whose `fields` become its installation config.
124
+ * Declare the Sanity Media Library as a workbench app — a singleton whose `fields` become its config.
120
125
  * @public
121
126
  */ export function unstable_defineMediaLibrary(input) {
122
127
  return unstable_defineApp({
123
- // @ts-expect-error -- `applicationType`/`isSingleton`/`installationConfig` are internal, excluded from `DefineAppInput`; Sanity-owned apps set them
128
+ // @ts-expect-error -- `applicationType`/`isSingleton`/`config` are internal, excluded from `DefineAppInput`; Sanity-owned apps set them
124
129
  applicationType: 'media-library',
125
- installationConfig: input.fields?.length ? {
130
+ config: input.fields?.length ? {
126
131
  appType: 'media-library',
127
132
  fields: input.fields
128
133
  } : undefined,
@@ -1 +1 @@
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":"AAAA,SAAQA,CAAC,QAAO,WAAU;AAE1B,SACEC,wBAAwB,EACxBC,0BAA0B,EAC1BC,wBAAwB,QACnB,gBAAe;AAEtB,0CAA0C,GAC1C,MAAMC,mBAAmB;AAEzB;;;CAGC,GACD,MAAMC,kBAAkBL,EAAEM,IAAI,CAAC;IAAC;IAAW;IAAU;IAAU;IAAa;CAAgB;AAE5F,8CAA8C,GAC9C,MAAMC,kBAAkBP,EAAEM,IAAI,CAAC;IAAC;IAAe;IAAqB;CAAY;AAUhF;;;;;CAKC,GACD,OAAO,MAAME,uBAAuBR,EACjCS,MAAM,CAAC;IACN;;;;KAIC,GACDC,iBAAiBV,EAAEW,QAAQ,CAACN;IAC5B;;;;KAIC,GACDO,OAAOZ,EAAEW,QAAQ,CAACX,EAAEa,MAAM;IAC1B,2EAA2E,GAC3EC,OAAOd,EAAEW,QAAQ,CAACJ;IAClB,6EAA6E,GAC7EQ,MAAMf,EAAEW,QAAQ,CAACX,EAAEa,MAAM;IACzB;;;;;KAKC,GACDG,oBAAoBhB,EAAEW,QAAQ,CAACV;IAC/B;;;KAGC,GACDgB,aAAajB,EAAEW,QAAQ,CAACX,EAAEkB,OAAO;IACjC,2DAA2D,GAC3DC,MAAMnB,EAAEa,MAAM,GAAGO,KAAK,CAACpB,EAAEqB,KAAK,CAACjB,kBAAkB;IACjD,gFAAgF,GAChFkB,gBAAgBtB,EAAEa,MAAM,CACtB;IAEF,+EAA+E,GAC/EU,UAAUvB,EAAEW,QAAQ,CAACX,EAAEwB,MAAM;IAC7B;;;;;KAKC,GACDC,UAAUzB,EAAEW,QAAQ,CAClBX,EACG0B,KAAK,CAACvB,0BACNiB,KAAK,CACJpB,EAAE2B,MAAM,CACN,CAACF,WAAa,IAAIG,IAAIH,SAASI,GAAG,CAAC,CAACC,UAAYA,QAAQX,IAAI,GAAGY,IAAI,KAAKN,SAASO,MAAM,EACvF;IAIR,sEAAsE,GACtEC,OAAOjC,EAAEa,MAAM;IACf;;;;KAIC,GACDqB,OAAOlC,EAAEW,QAAQ,CACfX,EACG0B,KAAK,CAACxB,4BACNkB,KAAK,CACJpB,EAAE2B,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;AAC1EpB,EAAE2B,MAAM,CAAC,CAACS,QAAU,CAAEA,CAAAA,MAAM1B,eAAe,KAAK,YAAY0B,MAAMxB,KAAK,KAAKyB,SAAQ,GAAI;IACtFC,OAAO;IACPC,MAAM;QAAC;KAAQ;AACjB,IAEDnB,KAAK,CACJ,wEAAwE;AACxE,4DAA4D;AAC5D,wDAAwD;AACxDpB,EAAE2B,MAAM,CAAC,CAACS,QAAU,CAAEA,CAAAA,MAAMpB,kBAAkB,IAAI,CAACoB,MAAMnB,WAAW,AAAD,GAAI;IACrEqB,OAAO;IACPC,MAAM;QAAC;KAAqB;AAC9B,IACD;AAcH;;;;;CAKC,GACD,MAAMC,gBAA+BC,OAAOC,GAAG,CAAC;AAmBhD;;;;CAIC,GACD,OAAO,SAASC,eAAeC,GAAY;IACzC,OAAO,OAAOA,QAAQ,YAAYA,QAAQ,QAAQJ,iBAAiBI;AACrE;AAEA;;;;;;CAMC,GACD,OAAO,SAASC,uBACdD,GAAiB;IAEjB,IAAIA,IAAI5B,kBAAkB,IAAI,CAAC4B,IAAI3B,WAAW,EAAE;QAC9C,MAAM,IAAI6B,MAAM;IAClB;IACA,OAAOF,IAAI5B,kBAAkB;AAC/B;AAEA;;;;;;CAMC,GACD,OAAO,SAAS+B,mBAAmBX,KAAqB;IACtD,OAAOY,OAAOC,cAAc,CAACb,OAAOI,eAAe;QACjDU,cAAc;QACdC,YAAY;QACZC,OAAO;QACPC,UAAU;IACZ;AACF;AA2BA;;;CAGC,GACD,OAAO,SAASC,4BAA4BlB,KAA8B;IACxE,OAAOW,mBAAmB;QACxB,oJAAoJ;QACpJrC,iBAAiB;QACjBM,oBAAoBoB,MAAMmB,MAAM,EAAEvB,SAC9B;YAACwB,SAAS;YAAiBD,QAAQnB,MAAMmB,MAAM;QAAA,IAC/ClB;QACJpB,aAAa;QACbE,MAAM;QACNG,gBAAgBc,MAAMd,cAAc;QACpCW,OAAO;IACT;AACF"}
1
+ {"version":3,"sources":["../src/defineApp.ts"],"sourcesContent":["import {z} from 'zod/mini'\n\nimport {ConfigSchema, 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 * 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 config: z.optional(ConfigSchema),\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 * 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 /**\n * Hostname the application is created at on first deploy. Generated when\n * omitted; redeploys target `deployment.appId` and ignore it. SDK apps\n * only — studios use `studioHost` in sanity.cli.ts.\n */\n slug: z.optional(z.string()),\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 config belongs to a Sanity-owned singleton (the Media\n // Library). A non-singleton declaring one is rejected — see\n // {@link readConfig} for the runtime guard.\n z.refine((input) => !(input.config && !input.isSingleton), {\n error: '`config` is only supported for singleton apps',\n path: ['config'],\n }),\n )\n\n/**\n * User-facing input for `unstable_defineApp`. Excludes the internal\n * `applicationType`, `isSingleton`, and `config` — 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' | 'config' | '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 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 readConfig(app: WorkbenchApp): WorkbenchApp['config'] | undefined {\n if (app.config && !app.isSingleton) {\n throw new Error('`config` is only supported for singleton apps')\n }\n return app.config\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 config.\n * @public\n */\nexport function unstable_defineMediaLibrary(input: DefineMediaLibraryInput): DefineAppResult {\n return unstable_defineApp({\n // @ts-expect-error -- `applicationType`/`isSingleton`/`config` are internal, excluded from `DefineAppInput`; Sanity-owned apps set them\n applicationType: 'media-library',\n config: input.fields?.length ? {appType: 'media-library', fields: input.fields} : undefined,\n isSingleton: true,\n name: 'media-library',\n organizationId: input.organizationId,\n title: 'Media Library',\n })\n}\n"],"names":["z","ConfigSchema","InterfaceDeclarationSchema","ServiceDeclarationSchema","APP_NAME_PATTERN","ApplicationType","enum","DockGroupSchema","DefineAppInputSchema","object","applicationType","optional","config","entry","string","group","icon","isSingleton","boolean","name","check","regex","organizationId","priority","number","services","array","refine","Set","map","service","size","length","slug","title","views","view","input","undefined","error","path","WORKBENCH_APP","Symbol","for","isWorkbenchApp","app","readConfig","Error","unstable_defineApp","Object","defineProperty","configurable","enumerable","value","writable","unstable_defineMediaLibrary","fields","appType"],"mappings":"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"}
@@ -1 +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"}
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 name: string\n src: string\n title: string\n type: 'panel'\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;AAoDtB;;;;;;;;;CASC,GACD,OAAO,SAASC,oBACdC,IAAW,EACXC,UAAuC;IAEvC,OAAO;QAACA;QAAYD;QAAME,SAASJ;IAAqB;AAC1D"}
@@ -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, readInstallationConfig } from './defineApp.js';
6
+ import { isWorkbenchApp, readConfig } from './defineApp.js';
7
7
  /**
8
8
  * Resolve the workbench app for a CLI config, or `null` for a plain project.
9
9
  * @public
@@ -12,11 +12,12 @@ import { isWorkbenchApp, readInstallationConfig } from './defineApp.js';
12
12
  if (!isWorkbenchApp(app)) return null;
13
13
  return {
14
14
  applicationType: app.applicationType,
15
+ config: readConfig(app),
15
16
  entry: app.entry,
16
- installationConfig: readInstallationConfig(app),
17
- isSingleton: app.isSingleton ?? false,
17
+ isSingleton: app.isSingleton,
18
18
  name: app.name,
19
19
  services: app.services ?? [],
20
+ slug: app.slug,
20
21
  views: app.views ?? []
21
22
  };
22
23
  }
@@ -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} 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"}
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 {type DefineAppInput, isWorkbenchApp, readConfig, type WorkbenchApp} 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 config?: WorkbenchApp['config']\n services?: DefineAppInput['services']\n views?: DefineAppInput['views']\n}\n\n/** @public */\nexport interface ResolvedWorkbenchApp {\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 /** Deploys on its own path, separate from the interfaces. */\n readonly config?: WorkbenchApp['config']\n /** SDK app-view entrypoint, when declared. */\n readonly entry?: string\n /** Explicit singleton flag (a Sanity-owned app); `undefined` when the app doesn't set it. */\n readonly isSingleton?: boolean\n /** Hostname the application is created at on first deploy. */\n readonly slug?: 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\n return {\n applicationType: app.applicationType,\n config: readConfig(app),\n entry: app.entry,\n isSingleton: app.isSingleton,\n name: app.name,\n services: app.services ?? [],\n slug: app.slug,\n views: app.views ?? [],\n }\n}\n"],"names":["isWorkbenchApp","readConfig","resolveWorkbenchApp","cliConfig","app","applicationType","config","entry","isSingleton","name","services","slug","views"],"mappings":"AAAA,gEAAgE;AAChE,+EAA+E;AAC/E,+EAA+E;AAC/E,qEAAqE;AACrE,gEAAgE;AAIhE,SAA6BA,cAAc,EAAEC,UAAU,QAA0B,iBAAgB;AAmCjG;;;CAGC,GACD,OAAO,SAASC,oBACdC,SAAuC;IAEvC,MAAMC,MAAMD,WAAWC;IACvB,IAAI,CAACJ,eAAeI,MAAM,OAAO;IAEjC,OAAO;QACLC,iBAAiBD,IAAIC,eAAe;QACpCC,QAAQL,WAAWG;QACnBG,OAAOH,IAAIG,KAAK;QAChBC,aAAaJ,IAAII,WAAW;QAC5BC,MAAML,IAAIK,IAAI;QACdC,UAAUN,IAAIM,QAAQ,IAAI,EAAE;QAC5BC,MAAMP,IAAIO,IAAI;QACdC,OAAOR,IAAIQ,KAAK,IAAI,EAAE;IACxB;AACF"}
@@ -0,0 +1,5 @@
1
+ // The app endpoints (applications + installations) live behind the
2
+ // experimental `vX` version; a dated version returns 501.
3
+ export const APP_WORKBENCH_API_VERSION = 'vX';
4
+
5
+ //# sourceMappingURL=apiVersion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/services/apiVersion.ts"],"sourcesContent":["// The app endpoints (applications + installations) live behind the\n// experimental `vX` version; a dated version returns 501.\nexport const APP_WORKBENCH_API_VERSION = 'vX'\n"],"names":["APP_WORKBENCH_API_VERSION"],"mappings":"AAAA,mEAAmE;AACnE,0DAA0D;AAC1D,OAAO,MAAMA,4BAA4B,KAAI"}
@@ -0,0 +1,100 @@
1
+ import { PassThrough } from 'node:stream';
2
+ import { getGlobalCliClient } from '@sanity/cli-core';
3
+ import { isStaging } from '@sanity/cli-core/util';
4
+ import FormData from 'form-data';
5
+ import { APP_WORKBENCH_API_VERSION } from './apiVersion.js';
6
+ export function getWorkbenchUrl(organizationId) {
7
+ return `https://${organizationId}.${isStaging() ? 'run.sanity.work' : 'sanity.run'}`;
8
+ }
9
+ /** Where a deployed application is served on its organization's workbench. */ export function getApplicationUrl(application) {
10
+ const segment = application.type === 'studio' ? 'studio' : 'application';
11
+ return `${getWorkbenchUrl(application.organizationId)}/${segment}/${application.id}`;
12
+ }
13
+ async function getClient() {
14
+ return getGlobalCliClient({
15
+ apiVersion: APP_WORKBENCH_API_VERSION,
16
+ requireUser: true
17
+ });
18
+ }
19
+ export async function getApplication(applicationId) {
20
+ const client = await getClient();
21
+ try {
22
+ return await client.request({
23
+ uri: `/applications/${applicationId}`
24
+ });
25
+ } catch (err) {
26
+ if (err?.statusCode === 404) return null;
27
+ throw err;
28
+ }
29
+ }
30
+ /** Create an application and its first deployment in one call. */ export async function createApplication(options) {
31
+ const { interfaces, isSingleton, organizationId, projectId, slug, tarball, title, type, version, workspaces } = options;
32
+ const formData = new FormData();
33
+ formData.append('type', type);
34
+ formData.append('title', title);
35
+ formData.append('organizationId', organizationId);
36
+ formData.append('slug', slug);
37
+ if (isSingleton !== undefined) formData.append('isSingleton', String(isSingleton));
38
+ // Studio config is set once, at create — it's immutable on redeploy.
39
+ if (projectId) appendJson(formData, 'config', {
40
+ studio: {
41
+ projectId
42
+ }
43
+ });
44
+ appendDeploymentParts(formData, {
45
+ interfaces,
46
+ tarball,
47
+ version,
48
+ workspaces
49
+ });
50
+ return request(`/applications`, formData);
51
+ }
52
+ /** Deploy a new active version to an existing application. */ export async function createDeployment(options) {
53
+ const { applicationId, interfaces, isAutoUpdating, tarball, version, workspaces } = options;
54
+ const formData = new FormData();
55
+ formData.append('isAutoUpdating', isAutoUpdating.toString());
56
+ appendDeploymentParts(formData, {
57
+ interfaces,
58
+ tarball,
59
+ version,
60
+ workspaces
61
+ });
62
+ return request(`/applications/${applicationId}/deployments`, formData);
63
+ }
64
+ /** Soft-deletes the application and all its deployments; already deleted counts as done. */ export async function deleteApplication(applicationId) {
65
+ const client = await getClient();
66
+ try {
67
+ await client.request({
68
+ method: 'DELETE',
69
+ uri: `/applications/${applicationId}`
70
+ });
71
+ } catch (err) {
72
+ if (err?.statusCode !== 404) throw err;
73
+ }
74
+ }
75
+ function appendDeploymentParts(formData, { interfaces, tarball, version, workspaces }) {
76
+ formData.append('version', version);
77
+ appendJson(formData, 'interfaces', interfaces);
78
+ // Studio-only — the server rejects a workspaces part on non-studio types.
79
+ if (workspaces?.length) appendJson(formData, 'workspaces', workspaces);
80
+ formData.append('tarball', tarball, {
81
+ contentType: 'application/gzip',
82
+ filename: 'app.tar.gz'
83
+ });
84
+ }
85
+ /** Structured parts must arrive as JSON so the server parses them. */ function appendJson(formData, name, value) {
86
+ formData.append(name, JSON.stringify(value), {
87
+ contentType: 'application/json'
88
+ });
89
+ }
90
+ async function request(uri, formData) {
91
+ const client = await getClient();
92
+ return client.request({
93
+ body: formData.pipe(new PassThrough()),
94
+ headers: formData.getHeaders(),
95
+ method: 'POST',
96
+ uri
97
+ });
98
+ }
99
+
100
+ //# sourceMappingURL=applications.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/services/applications.ts"],"sourcesContent":["import {PassThrough} from 'node:stream'\nimport {type Gzip} from 'node:zlib'\n\nimport {getGlobalCliClient} from '@sanity/cli-core'\nimport {isStaging} from '@sanity/cli-core/util'\nimport FormData from 'form-data'\n\nimport {APP_WORKBENCH_API_VERSION} from './apiVersion.js'\n\nexport type ApplicationType = 'coreApp' | 'studio'\n\nexport interface Application {\n id: string\n organizationId: string\n slug: string | null\n title: string\n type: ApplicationType\n}\n\n/**\n * An interface as Brett stores it: the declared `type` (validated server-side,\n * not here) and the remote-relative `moduleId` the workbench loads it by — the\n * host prepends the app's own id.\n * @internal\n */\nexport interface BrettInterface {\n moduleId: string\n name: string\n title: string\n type: string\n version: string\n}\n\n/** A studio workspace as Brett stores it. */\nexport interface BrettWorkspace {\n dataset: string\n projectId: string\n\n basePath?: string\n icon?: string\n name?: string\n subtitle?: string\n title?: string\n}\n\nexport function getWorkbenchUrl(organizationId: string): string {\n return `https://${organizationId}.${isStaging() ? 'run.sanity.work' : 'sanity.run'}`\n}\n\n/** Where a deployed application is served on its organization's workbench. */\nexport function getApplicationUrl(\n application: Pick<Application, 'id' | 'organizationId' | 'type'>,\n): string {\n const segment = application.type === 'studio' ? 'studio' : 'application'\n return `${getWorkbenchUrl(application.organizationId)}/${segment}/${application.id}`\n}\n\nasync function getClient() {\n return getGlobalCliClient({apiVersion: APP_WORKBENCH_API_VERSION, requireUser: true})\n}\n\nexport async function getApplication(applicationId: string): Promise<Application | null> {\n const client = await getClient()\n try {\n return await client.request({uri: `/applications/${applicationId}`})\n } catch (err) {\n if ((err as {statusCode?: number})?.statusCode === 404) return null\n throw err\n }\n}\n\n/** Create an application and its first deployment in one call. */\nexport async function createApplication(options: {\n interfaces: readonly BrettInterface[]\n isSingleton?: boolean\n organizationId: string\n projectId?: string\n slug: string\n tarball: Gzip\n title: string\n type: ApplicationType\n version: string\n workspaces?: readonly BrettWorkspace[]\n}): Promise<Application> {\n const {\n interfaces,\n isSingleton,\n organizationId,\n projectId,\n slug,\n tarball,\n title,\n type,\n version,\n workspaces,\n } = options\n const formData = new FormData()\n formData.append('type', type)\n formData.append('title', title)\n formData.append('organizationId', organizationId)\n formData.append('slug', slug)\n if (isSingleton !== undefined) formData.append('isSingleton', String(isSingleton))\n // Studio config is set once, at create — it's immutable on redeploy.\n if (projectId) appendJson(formData, 'config', {studio: {projectId}})\n appendDeploymentParts(formData, {interfaces, tarball, version, workspaces})\n return request(`/applications`, formData)\n}\n\n/** Deploy a new active version to an existing application. */\nexport async function createDeployment(options: {\n applicationId: string\n interfaces: readonly BrettInterface[]\n isAutoUpdating: boolean\n tarball: Gzip\n version: string\n workspaces?: readonly BrettWorkspace[]\n}): Promise<{id: string}> {\n const {applicationId, interfaces, isAutoUpdating, tarball, version, workspaces} = options\n const formData = new FormData()\n formData.append('isAutoUpdating', isAutoUpdating.toString())\n appendDeploymentParts(formData, {interfaces, tarball, version, workspaces})\n return request(`/applications/${applicationId}/deployments`, formData)\n}\n\n/** Soft-deletes the application and all its deployments; already deleted counts as done. */\nexport async function deleteApplication(applicationId: string): Promise<void> {\n const client = await getClient()\n try {\n await client.request({method: 'DELETE', uri: `/applications/${applicationId}`})\n } catch (err) {\n if ((err as {statusCode?: number})?.statusCode !== 404) throw err\n }\n}\n\nfunction appendDeploymentParts(\n formData: FormData,\n {\n interfaces,\n tarball,\n version,\n workspaces,\n }: {\n interfaces: readonly BrettInterface[]\n tarball: Gzip\n version: string\n workspaces?: readonly BrettWorkspace[]\n },\n): void {\n formData.append('version', version)\n appendJson(formData, 'interfaces', interfaces)\n // Studio-only — the server rejects a workspaces part on non-studio types.\n if (workspaces?.length) appendJson(formData, 'workspaces', workspaces)\n formData.append('tarball', tarball, {contentType: 'application/gzip', filename: 'app.tar.gz'})\n}\n\n/** Structured parts must arrive as JSON so the server parses them. */\nfunction appendJson(formData: FormData, name: string, value: unknown): void {\n formData.append(name, JSON.stringify(value), {contentType: 'application/json'})\n}\n\nasync function request<T>(uri: string, formData: FormData): Promise<T> {\n const client = await getClient()\n return client.request({\n body: formData.pipe(new PassThrough()),\n headers: formData.getHeaders(),\n method: 'POST',\n uri,\n })\n}\n"],"names":["PassThrough","getGlobalCliClient","isStaging","FormData","APP_WORKBENCH_API_VERSION","getWorkbenchUrl","organizationId","getApplicationUrl","application","segment","type","id","getClient","apiVersion","requireUser","getApplication","applicationId","client","request","uri","err","statusCode","createApplication","options","interfaces","isSingleton","projectId","slug","tarball","title","version","workspaces","formData","append","undefined","String","appendJson","studio","appendDeploymentParts","createDeployment","isAutoUpdating","toString","deleteApplication","method","length","contentType","filename","name","value","JSON","stringify","body","pipe","headers","getHeaders"],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import { PassThrough } from 'node:stream';
2
+ import { getGlobalCliClient } from '@sanity/cli-core';
3
+ import FormData from 'form-data';
4
+ import { APP_WORKBENCH_API_VERSION } from './apiVersion.js';
5
+ async function getClient() {
6
+ return getGlobalCliClient({
7
+ apiVersion: APP_WORKBENCH_API_VERSION,
8
+ requireUser: true
9
+ });
10
+ }
11
+ /** The org's active singleton installation, matched on its slug. */ export async function resolveSingletonInstallationId(organizationId, slug) {
12
+ const client = await getClient();
13
+ // `limit=none` returns every installation in one response, no pagination.
14
+ const { data } = await client.request({
15
+ query: {
16
+ limit: 'none',
17
+ organizationId
18
+ },
19
+ uri: '/installations'
20
+ });
21
+ return data.find((item)=>item.application?.slug === slug)?.id;
22
+ }
23
+ /** Upload a config snapshot to the installation as a multipart tarball. */ export async function createConfig(installationId, { tarball, version }) {
24
+ const formData = new FormData();
25
+ formData.append('version', version);
26
+ formData.append('tarball', tarball, {
27
+ contentType: 'application/gzip',
28
+ filename: 'installation-config.tar.gz'
29
+ });
30
+ const client = await getClient();
31
+ await client.request({
32
+ body: formData.pipe(new PassThrough()),
33
+ headers: formData.getHeaders(),
34
+ method: 'POST',
35
+ uri: `/installations/${installationId}/configs`
36
+ });
37
+ }
38
+ /** The installation's deployed config snapshots, newest first. */ export async function listConfigs(installationId) {
39
+ const client = await getClient();
40
+ const { data } = await client.request({
41
+ query: {
42
+ limit: 'none'
43
+ },
44
+ uri: `/installations/${installationId}/configs`
45
+ });
46
+ return data;
47
+ }
48
+ /**
49
+ * Soft-deletes one config snapshot; its content is purged once superseded.
50
+ * Already deleted counts as done, so a partially-failed undeploy can re-run.
51
+ */ export async function deleteConfig(installationId, configId) {
52
+ const client = await getClient();
53
+ try {
54
+ await client.request({
55
+ method: 'DELETE',
56
+ uri: `/installations/${installationId}/configs/${configId}`
57
+ });
58
+ } catch (err) {
59
+ if (err?.statusCode !== 404) throw err;
60
+ }
61
+ }
62
+
63
+ //# sourceMappingURL=installations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/services/installations.ts"],"sourcesContent":["import {PassThrough} from 'node:stream'\nimport {type Gzip} from 'node:zlib'\n\nimport {getGlobalCliClient} from '@sanity/cli-core'\nimport FormData from 'form-data'\n\nimport {APP_WORKBENCH_API_VERSION} from './apiVersion.js'\n\nexport interface ConfigSnapshot {\n id: string\n\n createdAt?: string\n deployedBy?: string\n /** Whether this snapshot is the one being served; at most one per installation. */\n isActive?: boolean\n}\n\ninterface InstallationListItem {\n id: string\n\n application?: {slug?: string}\n}\n\nasync function getClient() {\n return getGlobalCliClient({apiVersion: APP_WORKBENCH_API_VERSION, requireUser: true})\n}\n\n/** The org's active singleton installation, matched on its slug. */\nexport async function resolveSingletonInstallationId(\n organizationId: string,\n slug: string,\n): Promise<string | undefined> {\n const client = await getClient()\n // `limit=none` returns every installation in one response, no pagination.\n const {data}: {data: InstallationListItem[]} = await client.request({\n query: {limit: 'none', organizationId},\n uri: '/installations',\n })\n return data.find((item) => item.application?.slug === slug)?.id\n}\n\n/** Upload a config snapshot to the installation as a multipart tarball. */\nexport async function createConfig(\n installationId: string,\n {tarball, version}: {tarball: Gzip; version: string},\n): Promise<void> {\n const formData = new FormData()\n formData.append('version', version)\n formData.append('tarball', tarball, {\n contentType: 'application/gzip',\n filename: 'installation-config.tar.gz',\n })\n\n const client = await getClient()\n await client.request({\n body: formData.pipe(new PassThrough()),\n headers: formData.getHeaders(),\n method: 'POST',\n uri: `/installations/${installationId}/configs`,\n })\n}\n\n/** The installation's deployed config snapshots, newest first. */\nexport async function listConfigs(installationId: string): Promise<ConfigSnapshot[]> {\n const client = await getClient()\n const {data}: {data: ConfigSnapshot[]} = await client.request({\n query: {limit: 'none'},\n uri: `/installations/${installationId}/configs`,\n })\n return data\n}\n\n/**\n * Soft-deletes one config snapshot; its content is purged once superseded.\n * Already deleted counts as done, so a partially-failed undeploy can re-run.\n */\nexport async function deleteConfig(installationId: string, configId: string): Promise<void> {\n const client = await getClient()\n try {\n await client.request({\n method: 'DELETE',\n uri: `/installations/${installationId}/configs/${configId}`,\n })\n } catch (err) {\n if ((err as {statusCode?: number})?.statusCode !== 404) throw err\n }\n}\n"],"names":["PassThrough","getGlobalCliClient","FormData","APP_WORKBENCH_API_VERSION","getClient","apiVersion","requireUser","resolveSingletonInstallationId","organizationId","slug","client","data","request","query","limit","uri","find","item","application","id","createConfig","installationId","tarball","version","formData","append","contentType","filename","body","pipe","headers","getHeaders","method","listConfigs","deleteConfig","configId","err","statusCode"],"mappings":"AAAA,SAAQA,WAAW,QAAO,cAAa;AAGvC,SAAQC,kBAAkB,QAAO,mBAAkB;AACnD,OAAOC,cAAc,YAAW;AAEhC,SAAQC,yBAAyB,QAAO,kBAAiB;AAiBzD,eAAeC;IACb,OAAOH,mBAAmB;QAACI,YAAYF;QAA2BG,aAAa;IAAI;AACrF;AAEA,kEAAkE,GAClE,OAAO,eAAeC,+BACpBC,cAAsB,EACtBC,IAAY;IAEZ,MAAMC,SAAS,MAAMN;IACrB,0EAA0E;IAC1E,MAAM,EAACO,IAAI,EAAC,GAAmC,MAAMD,OAAOE,OAAO,CAAC;QAClEC,OAAO;YAACC,OAAO;YAAQN;QAAc;QACrCO,KAAK;IACP;IACA,OAAOJ,KAAKK,IAAI,CAAC,CAACC,OAASA,KAAKC,WAAW,EAAET,SAASA,OAAOU;AAC/D;AAEA,yEAAyE,GACzE,OAAO,eAAeC,aACpBC,cAAsB,EACtB,EAACC,OAAO,EAAEC,OAAO,EAAmC;IAEpD,MAAMC,WAAW,IAAItB;IACrBsB,SAASC,MAAM,CAAC,WAAWF;IAC3BC,SAASC,MAAM,CAAC,WAAWH,SAAS;QAClCI,aAAa;QACbC,UAAU;IACZ;IAEA,MAAMjB,SAAS,MAAMN;IACrB,MAAMM,OAAOE,OAAO,CAAC;QACnBgB,MAAMJ,SAASK,IAAI,CAAC,IAAI7B;QACxB8B,SAASN,SAASO,UAAU;QAC5BC,QAAQ;QACRjB,KAAK,CAAC,eAAe,EAAEM,eAAe,QAAQ,CAAC;IACjD;AACF;AAEA,gEAAgE,GAChE,OAAO,eAAeY,YAAYZ,cAAsB;IACtD,MAAMX,SAAS,MAAMN;IACrB,MAAM,EAACO,IAAI,EAAC,GAA6B,MAAMD,OAAOE,OAAO,CAAC;QAC5DC,OAAO;YAACC,OAAO;QAAM;QACrBC,KAAK,CAAC,eAAe,EAAEM,eAAe,QAAQ,CAAC;IACjD;IACA,OAAOV;AACT;AAEA;;;CAGC,GACD,OAAO,eAAeuB,aAAab,cAAsB,EAAEc,QAAgB;IACzE,MAAMzB,SAAS,MAAMN;IACrB,IAAI;QACF,MAAMM,OAAOE,OAAO,CAAC;YACnBoB,QAAQ;YACRjB,KAAK,CAAC,eAAe,EAAEM,eAAe,SAAS,EAAEc,UAAU;QAC7D;IACF,EAAE,OAAOC,KAAK;QACZ,IAAI,AAACA,KAA+BC,eAAe,KAAK,MAAMD;IAChE;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/workbench-cli",
3
- "version": "1.2.0",
3
+ "version": "1.4.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",
@@ -39,19 +39,23 @@
39
39
  "source": "./src/_exports/init.ts",
40
40
  "default": "./dist/_exports/init.js"
41
41
  },
42
+ "./undeploy": {
43
+ "source": "./src/_exports/undeploy.ts",
44
+ "default": "./dist/_exports/undeploy.js"
45
+ },
42
46
  "./package.json": "./package.json"
43
47
  },
44
48
  "publishConfig": {
45
49
  "access": "public"
46
50
  },
47
51
  "dependencies": {
48
- "@module-federation/vite": "1.16.11",
52
+ "@module-federation/vite": "1.16.14",
49
53
  "@vitejs/plugin-react": "^6.0.3",
50
54
  "form-data": "^4.0.5",
51
55
  "tar-fs": "^3.1.2",
52
56
  "vite": "^8.1.3",
53
57
  "zod": "^4.4.3",
54
- "@sanity/cli-core": "^2.2.0"
58
+ "@sanity/cli-core": "^2.4.0"
55
59
  },
56
60
  "devDependencies": {
57
61
  "@eslint/compat": "^2.1.0",
@@ -1,91 +0,0 @@
1
- import { basename, dirname } from 'node:path';
2
- import { PassThrough } from 'node:stream';
3
- import { styleText } from 'node:util';
4
- import { createGzip } from 'node:zlib';
5
- import { getGlobalCliClient, subdebug } from '@sanity/cli-core';
6
- import FormData from 'form-data';
7
- import { pack } from 'tar-fs';
8
- // Brett's app-registry endpoints live behind the experimental `vX` version.
9
- const INSTALLATIONS_API_VERSION = 'vX';
10
- const debug = subdebug('deploy');
11
- /**
12
- * The org's active installation for an app type, or `undefined` when none is
13
- * installed. Read-only, so `--dry-run` can report deployability.
14
- * @internal
15
- */ export async function resolveInstallationId(options) {
16
- switch(options.appType){
17
- case 'media-library':
18
- {
19
- return resolveSingletonInstallationId(options.organizationId, 'media-library');
20
- }
21
- default:
22
- {
23
- throw new Error(`Cannot create installation config for unknown app type: ${options.appType}`);
24
- }
25
- }
26
- }
27
- /**
28
- * A report heading and item list for a config; a media library's `fields` are
29
- * one of potentially many shapes.
30
- * @internal
31
- */ export function summarizeInstallationConfig(config) {
32
- switch(config.appType){
33
- case 'media-library':
34
- {
35
- const items = config.fields.map((field)=>` ${field.title} (${field.name})`).join('\n');
36
- return `Media library fields:\n${items}`;
37
- }
38
- default:
39
- {
40
- throw new Error(`Cannot create installation config for unknown app type: ${config.appType}`);
41
- }
42
- }
43
- }
44
- /**
45
- * Upload the built module-federation remote to the installation as its config
46
- * snapshot. `installationId` is resolved by the caller so `--dry-run` never
47
- * reaches this mutating step.
48
- * @internal
49
- */ export async function deployInstallationConfig(options) {
50
- const { appType, installationId, output, sourceDir, version } = options;
51
- const tarball = pack(dirname(sourceDir), {
52
- entries: [
53
- basename(sourceDir)
54
- ]
55
- }).pipe(createGzip());
56
- const formData = new FormData();
57
- formData.append('version', version);
58
- formData.append('tarball', tarball, {
59
- contentType: 'application/gzip',
60
- filename: 'installation-config.tar.gz'
61
- });
62
- const client = await getGlobalCliClient({
63
- apiVersion: INSTALLATIONS_API_VERSION,
64
- requireUser: true
65
- });
66
- await client.request({
67
- body: formData.pipe(new PassThrough()),
68
- headers: formData.getHeaders(),
69
- method: 'POST',
70
- uri: `/installations/${installationId}/configs`
71
- });
72
- debug('Deployed installation config for app type: %s', appType);
73
- output.log(`\nšŸš€ ${styleText('bold', 'Success!')} Installation config deployed`);
74
- }
75
- /** The org's active singleton installation, matched on its slug. */ async function resolveSingletonInstallationId(organizationId, slug) {
76
- const client = await getGlobalCliClient({
77
- apiVersion: INSTALLATIONS_API_VERSION,
78
- requireUser: true
79
- });
80
- // `limit=none` returns every installation in one response, no pagination.
81
- const { data } = await client.request({
82
- query: {
83
- limit: 'none',
84
- organizationId
85
- },
86
- uri: '/installations'
87
- });
88
- return data.find((item)=>item.application?.slug === slug)?.id;
89
- }
90
-
91
- //# sourceMappingURL=deployInstallationConfig.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/actions/deploy/deployInstallationConfig.ts"],"sourcesContent":["import {basename, dirname} from 'node:path'\nimport {PassThrough} from 'node:stream'\nimport {styleText} from 'node:util'\nimport {createGzip} from 'node:zlib'\n\nimport {getGlobalCliClient, type Output, subdebug} from '@sanity/cli-core'\nimport FormData from 'form-data'\nimport {pack} from 'tar-fs'\n\n// Brett's app-registry endpoints live behind the experimental `vX` version.\nconst INSTALLATIONS_API_VERSION = 'vX'\n\nconst debug = subdebug('deploy')\n\ninterface InstallationListItem {\n id: string\n\n application?: {slug?: string}\n}\n\n/**\n * The org's active installation for an app type, or `undefined` when none is\n * installed. Read-only, so `--dry-run` can report deployability.\n * @internal\n */\nexport async function resolveInstallationId(options: {\n appType: string\n organizationId: string\n}): Promise<string | undefined> {\n switch (options.appType) {\n case 'media-library': {\n return resolveSingletonInstallationId(options.organizationId, 'media-library')\n }\n default: {\n throw new Error(`Cannot create installation config for unknown app type: ${options.appType}`)\n }\n }\n}\n\n/**\n * A report heading and item list for a config; a media library's `fields` are\n * one of potentially many shapes.\n * @internal\n */\nexport function summarizeInstallationConfig(config: {\n appType: string\n fields: {name: string; title: string}[]\n}): string {\n switch (config.appType) {\n case 'media-library': {\n const items = config.fields.map((field) => ` ${field.title} (${field.name})`).join('\\n')\n return `Media library fields:\\n${items}`\n }\n default: {\n throw new Error(`Cannot create installation config for unknown app type: ${config.appType}`)\n }\n }\n}\n\n/**\n * Upload the built module-federation remote to the installation as its config\n * snapshot. `installationId` is resolved by the caller so `--dry-run` never\n * reaches this mutating step.\n * @internal\n */\nexport async function deployInstallationConfig(options: {\n appType: string\n installationId: string\n output: Output\n sourceDir: string\n version: string\n}): Promise<void> {\n const {appType, installationId, output, sourceDir, version} = options\n const tarball = pack(dirname(sourceDir), {entries: [basename(sourceDir)]}).pipe(createGzip())\n const formData = new FormData()\n formData.append('version', version)\n formData.append('tarball', tarball, {\n contentType: 'application/gzip',\n filename: 'installation-config.tar.gz',\n })\n\n const client = await getGlobalCliClient({\n apiVersion: INSTALLATIONS_API_VERSION,\n requireUser: true,\n })\n await client.request({\n body: formData.pipe(new PassThrough()),\n headers: formData.getHeaders(),\n method: 'POST',\n uri: `/installations/${installationId}/configs`,\n })\n\n debug('Deployed installation config for app type: %s', appType)\n output.log(`\\nšŸš€ ${styleText('bold', 'Success!')} Installation config deployed`)\n}\n\n/** The org's active singleton installation, matched on its slug. */\nasync function resolveSingletonInstallationId(\n organizationId: string,\n slug: string,\n): Promise<string | undefined> {\n const client = await getGlobalCliClient({\n apiVersion: INSTALLATIONS_API_VERSION,\n requireUser: true,\n })\n // `limit=none` returns every installation in one response, no pagination.\n const {data}: {data: InstallationListItem[]} = await client.request({\n query: {limit: 'none', organizationId},\n uri: '/installations',\n })\n return data.find((item) => item.application?.slug === slug)?.id\n}\n"],"names":["basename","dirname","PassThrough","styleText","createGzip","getGlobalCliClient","subdebug","FormData","pack","INSTALLATIONS_API_VERSION","debug","resolveInstallationId","options","appType","resolveSingletonInstallationId","organizationId","Error","summarizeInstallationConfig","config","items","fields","map","field","title","name","join","deployInstallationConfig","installationId","output","sourceDir","version","tarball","entries","pipe","formData","append","contentType","filename","client","apiVersion","requireUser","request","body","headers","getHeaders","method","uri","log","slug","data","query","limit","find","item","application","id"],"mappings":"AAAA,SAAQA,QAAQ,EAAEC,OAAO,QAAO,YAAW;AAC3C,SAAQC,WAAW,QAAO,cAAa;AACvC,SAAQC,SAAS,QAAO,YAAW;AACnC,SAAQC,UAAU,QAAO,YAAW;AAEpC,SAAQC,kBAAkB,EAAeC,QAAQ,QAAO,mBAAkB;AAC1E,OAAOC,cAAc,YAAW;AAChC,SAAQC,IAAI,QAAO,SAAQ;AAE3B,4EAA4E;AAC5E,MAAMC,4BAA4B;AAElC,MAAMC,QAAQJ,SAAS;AAQvB;;;;CAIC,GACD,OAAO,eAAeK,sBAAsBC,OAG3C;IACC,OAAQA,QAAQC,OAAO;QACrB,KAAK;YAAiB;gBACpB,OAAOC,+BAA+BF,QAAQG,cAAc,EAAE;YAChE;QACA;YAAS;gBACP,MAAM,IAAIC,MAAM,CAAC,wDAAwD,EAAEJ,QAAQC,OAAO,EAAE;YAC9F;IACF;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASI,4BAA4BC,MAG3C;IACC,OAAQA,OAAOL,OAAO;QACpB,KAAK;YAAiB;gBACpB,MAAMM,QAAQD,OAAOE,MAAM,CAACC,GAAG,CAAC,CAACC,QAAU,CAAC,EAAE,EAAEA,MAAMC,KAAK,CAAC,EAAE,EAAED,MAAME,IAAI,CAAC,CAAC,CAAC,EAAEC,IAAI,CAAC;gBACpF,OAAO,CAAC,uBAAuB,EAAEN,OAAO;YAC1C;QACA;YAAS;gBACP,MAAM,IAAIH,MAAM,CAAC,wDAAwD,EAAEE,OAAOL,OAAO,EAAE;YAC7F;IACF;AACF;AAEA;;;;;CAKC,GACD,OAAO,eAAea,yBAAyBd,OAM9C;IACC,MAAM,EAACC,OAAO,EAAEc,cAAc,EAAEC,MAAM,EAAEC,SAAS,EAAEC,OAAO,EAAC,GAAGlB;IAC9D,MAAMmB,UAAUvB,KAAKP,QAAQ4B,YAAY;QAACG,SAAS;YAAChC,SAAS6B;SAAW;IAAA,GAAGI,IAAI,CAAC7B;IAChF,MAAM8B,WAAW,IAAI3B;IACrB2B,SAASC,MAAM,CAAC,WAAWL;IAC3BI,SAASC,MAAM,CAAC,WAAWJ,SAAS;QAClCK,aAAa;QACbC,UAAU;IACZ;IAEA,MAAMC,SAAS,MAAMjC,mBAAmB;QACtCkC,YAAY9B;QACZ+B,aAAa;IACf;IACA,MAAMF,OAAOG,OAAO,CAAC;QACnBC,MAAMR,SAASD,IAAI,CAAC,IAAI/B;QACxByC,SAAST,SAASU,UAAU;QAC5BC,QAAQ;QACRC,KAAK,CAAC,eAAe,EAAEnB,eAAe,QAAQ,CAAC;IACjD;IAEAjB,MAAM,iDAAiDG;IACvDe,OAAOmB,GAAG,CAAC,CAAC,KAAK,EAAE5C,UAAU,QAAQ,YAAY,6BAA6B,CAAC;AACjF;AAEA,kEAAkE,GAClE,eAAeW,+BACbC,cAAsB,EACtBiC,IAAY;IAEZ,MAAMV,SAAS,MAAMjC,mBAAmB;QACtCkC,YAAY9B;QACZ+B,aAAa;IACf;IACA,0EAA0E;IAC1E,MAAM,EAACS,IAAI,EAAC,GAAmC,MAAMX,OAAOG,OAAO,CAAC;QAClES,OAAO;YAACC,OAAO;YAAQpC;QAAc;QACrC+B,KAAK;IACP;IACA,OAAOG,KAAKG,IAAI,CAAC,CAACC,OAASA,KAAKC,WAAW,EAAEN,SAASA,OAAOO;AAC/D"}