@sanity/cli 7.4.2 → 7.6.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 (74) hide show
  1. package/dist/actions/build/buildApp.js +7 -183
  2. package/dist/actions/build/buildApp.js.map +1 -1
  3. package/dist/actions/build/buildStudio.js +6 -213
  4. package/dist/actions/build/buildStudio.js.map +1 -1
  5. package/dist/actions/build/shouldAutoUpdate.js +100 -22
  6. package/dist/actions/build/shouldAutoUpdate.js.map +1 -1
  7. package/dist/actions/deploy/{createStudioUserApplication.js → createUserApplication.js} +56 -5
  8. package/dist/actions/deploy/createUserApplication.js.map +1 -0
  9. package/dist/actions/deploy/deployApp.js +232 -185
  10. package/dist/actions/deploy/deployApp.js.map +1 -1
  11. package/dist/actions/deploy/deployChecks.js +263 -0
  12. package/dist/actions/deploy/deployChecks.js.map +1 -0
  13. package/dist/actions/deploy/deployRunner.js +57 -0
  14. package/dist/actions/deploy/deployRunner.js.map +1 -0
  15. package/dist/actions/deploy/deployStudio.js +189 -198
  16. package/dist/actions/deploy/deployStudio.js.map +1 -1
  17. package/dist/actions/deploy/deploymentPlan.js +89 -0
  18. package/dist/actions/deploy/deploymentPlan.js.map +1 -0
  19. package/dist/actions/deploy/findUserApplication.js +209 -0
  20. package/dist/actions/deploy/findUserApplication.js.map +1 -0
  21. package/dist/actions/deploy/resolveDeployTarget.js +185 -0
  22. package/dist/actions/deploy/resolveDeployTarget.js.map +1 -0
  23. package/dist/actions/dev/devAction.js +1 -1
  24. package/dist/actions/dev/devAction.js.map +1 -1
  25. package/dist/actions/dev/servers/getDevServerConfig.js +18 -6
  26. package/dist/actions/dev/servers/getDevServerConfig.js.map +1 -1
  27. package/dist/actions/dev/servers/startAppDevServer.js +1 -1
  28. package/dist/actions/dev/servers/startAppDevServer.js.map +1 -1
  29. package/dist/actions/dev/servers/startStudioDevServer.js +2 -1
  30. package/dist/actions/dev/servers/startStudioDevServer.js.map +1 -1
  31. package/dist/actions/manifest/extractCoreAppManifest.js +14 -0
  32. package/dist/actions/manifest/extractCoreAppManifest.js.map +1 -1
  33. package/dist/actions/mcp/editorConfigs.js +11 -5
  34. package/dist/actions/mcp/editorConfigs.js.map +1 -1
  35. package/dist/commands/deploy.js +27 -11
  36. package/dist/commands/deploy.js.map +1 -1
  37. package/dist/commands/dev.js +2 -1
  38. package/dist/commands/dev.js.map +1 -1
  39. package/dist/commands/init.js +2 -1
  40. package/dist/commands/init.js.map +1 -1
  41. package/dist/exports/index.d.ts +9 -0
  42. package/dist/exports/index.js +1 -8
  43. package/dist/exports/index.js.map +1 -1
  44. package/dist/server/devServer.js +27 -3
  45. package/dist/server/devServer.js.map +1 -1
  46. package/dist/util/appId.js +13 -5
  47. package/dist/util/appId.js.map +1 -1
  48. package/dist/util/compareDependencyVersions.js.map +1 -1
  49. package/dist/util/determineIsApp.js +1 -1
  50. package/dist/util/determineIsApp.js.map +1 -1
  51. package/dist/util/errorMessages.js +2 -0
  52. package/dist/util/errorMessages.js.map +1 -1
  53. package/oclif.manifest.json +14 -1
  54. package/package.json +8 -8
  55. package/templates/app-quickstart/.claude/skills/sanity-app-sdk/SKILL.md +56 -0
  56. package/templates/app-quickstart/AGENTS.md +40 -0
  57. package/templates/app-quickstart/README.md +22 -0
  58. package/templates/app-quickstart/src/ExampleComponent.css +6 -0
  59. package/templates/app-quickstart/src/ExampleComponent.tsx +30 -9
  60. package/templates/app-sanity-ui/.claude/skills/sanity-app-sdk/SKILL.md +60 -0
  61. package/templates/app-sanity-ui/AGENTS.md +44 -0
  62. package/templates/app-sanity-ui/README.md +23 -0
  63. package/templates/app-sanity-ui/src/ExampleComponent.tsx +8 -5
  64. package/dist/actions/build/handlePrereleaseVersions.js +0 -44
  65. package/dist/actions/build/handlePrereleaseVersions.js.map +0 -1
  66. package/dist/actions/deploy/createStudioUserApplication.js.map +0 -1
  67. package/dist/actions/deploy/createUserApplicationForApp.js +0 -56
  68. package/dist/actions/deploy/createUserApplicationForApp.js.map +0 -1
  69. package/dist/actions/deploy/findUserApplicationForApp.js +0 -111
  70. package/dist/actions/deploy/findUserApplicationForApp.js.map +0 -1
  71. package/dist/actions/deploy/findUserApplicationForStudio.js +0 -172
  72. package/dist/actions/deploy/findUserApplicationForStudio.js.map +0 -1
  73. package/dist/actions/deploy/viewDeployment.js +0 -32
  74. package/dist/actions/deploy/viewDeployment.js.map +0 -1
@@ -2,13 +2,6 @@ export { createCliConfig } from '../config/createCliConfig.js';
2
2
  export { defineCliConfig } from '../config/defineCliConfig.js';
3
3
  export { getCliClient } from '../util/cliClient.js';
4
4
  export { loadEnv } from '../util/loadEnv.js';
5
- // Module-federation application extension API (config-time). Canonical
6
- // implementation lives in `@sanity/workbench-cli`; re-exported here so
7
- // `sanity/cli` can surface it to app authors via
8
- // `import {unstable_defineApp} from 'sanity/cli'`. The runtime helpers
9
- // `unstable_defineView`/`unstable_defineService` are NOT here — they bundle to
10
- // the browser, so they live on the browser-safe `@sanity/cli/runtime` entry to
11
- // keep Node-only deps out of the frontend bundle.
12
- export { unstable_defineApp } from '@sanity/workbench-cli';
5
+ export { unstable_defineApp, unstable_defineMediaLibrary } from '@sanity/workbench-cli';
13
6
 
14
7
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/exports/index.ts"],"sourcesContent":["export type {GraphQLAPIConfig} from '../actions/graphql/types.js'\nexport {createCliConfig} from '../config/createCliConfig.js'\nexport {defineCliConfig} from '../config/defineCliConfig.js'\nexport type {CliApiConfig} from '../types.js'\nexport {type CliClientOptions, getCliClient} from '../util/cliClient.js'\nexport {loadEnv} from '../util/loadEnv.js'\nexport type {CliConfig, UserViteConfig} from '@sanity/cli-core'\n\n// Module-federation application extension API (config-time). Canonical\n// implementation lives in `@sanity/workbench-cli`; re-exported here so\n// `sanity/cli` can surface it to app authors via\n// `import {unstable_defineApp} from 'sanity/cli'`. The runtime helpers\n// `unstable_defineView`/`unstable_defineService` are NOT here — they bundle to\n// the browser, so they live on the browser-safe `@sanity/cli/runtime` entry to\n// keep Node-only deps out of the frontend bundle.\nexport {type DefineAppInput, unstable_defineApp} from '@sanity/workbench-cli'\n"],"names":["createCliConfig","defineCliConfig","getCliClient","loadEnv","unstable_defineApp"],"mappings":"AACA,SAAQA,eAAe,QAAO,+BAA8B;AAC5D,SAAQC,eAAe,QAAO,+BAA8B;AAE5D,SAA+BC,YAAY,QAAO,uBAAsB;AACxE,SAAQC,OAAO,QAAO,qBAAoB;AAG1C,uEAAuE;AACvE,uEAAuE;AACvE,iDAAiD;AACjD,uEAAuE;AACvE,+EAA+E;AAC/E,+EAA+E;AAC/E,kDAAkD;AAClD,SAA6BC,kBAAkB,QAAO,wBAAuB"}
1
+ {"version":3,"sources":["../../src/exports/index.ts"],"sourcesContent":["export type {GraphQLAPIConfig} from '../actions/graphql/types.js'\nexport {createCliConfig} from '../config/createCliConfig.js'\nexport {defineCliConfig} from '../config/defineCliConfig.js'\nexport type {CliApiConfig} from '../types.js'\nexport {type CliClientOptions, getCliClient} from '../util/cliClient.js'\nexport {loadEnv} from '../util/loadEnv.js'\nexport type {CliConfig, UserViteConfig} from '@sanity/cli-core'\n\nexport {\n type DefineAppInput,\n type DefineMediaLibraryInput,\n type MediaLibraryField,\n unstable_defineApp,\n unstable_defineMediaLibrary,\n} from '@sanity/workbench-cli'\n"],"names":["createCliConfig","defineCliConfig","getCliClient","loadEnv","unstable_defineApp","unstable_defineMediaLibrary"],"mappings":"AACA,SAAQA,eAAe,QAAO,+BAA8B;AAC5D,SAAQC,eAAe,QAAO,+BAA8B;AAE5D,SAA+BC,YAAY,QAAO,uBAAsB;AACxE,SAAQC,OAAO,QAAO,qBAAoB;AAG1C,SAIEC,kBAAkB,EAClBC,2BAA2B,QACtB,wBAAuB"}
@@ -1,3 +1,4 @@
1
+ import path from 'node:path';
1
2
  import { extendViteConfigWithUserConfig, getViteConfig, writeSanityRuntime } from '@sanity/cli-build/_internal/build';
2
3
  import { getAppEnvironmentVariables, getStudioEnvironmentVariables } from '@sanity/cli-build/_internal/env';
3
4
  import { getCliTelemetry } from '@sanity/cli-core';
@@ -6,7 +7,7 @@ import { serverDebug } from './serverDebug.js';
6
7
  import { sanityTypegenPlugin } from './vite/plugin-typegen.js';
7
8
  const debug = serverDebug.extend('dev');
8
9
  export async function startDevServer(options) {
9
- const { appTitle, basePath, cwd, entry, httpHost, httpPort, isApp, isWorkbenchApp, reactCompiler, reactStrictMode, schemaExtraction, services, typegen, views, vite: extendViteConfig } = options;
10
+ const { appTitle, basePath, bundledDev, cwd, entry, exposes, httpHost, httpPort, isApp, isWorkbenchApp, reactCompiler, reactStrictMode, schemaExtraction, typegen, vite: extendViteConfig, workbenchAppId } = options;
10
11
  debug('Writing Sanity runtime files');
11
12
  const { entries, watcher } = await writeSanityRuntime({
12
13
  appTitle,
@@ -43,6 +44,7 @@ export async function startDevServer(options) {
43
44
  basePath,
44
45
  cwd,
45
46
  entries,
47
+ exposes,
46
48
  getEnvironmentVariables,
47
49
  isApp,
48
50
  isWorkbenchApp,
@@ -53,9 +55,31 @@ export async function startDevServer(options) {
53
55
  host: httpHost,
54
56
  port: httpPort
55
57
  },
56
- services,
57
- views
58
+ workbenchAppId
58
59
  });
60
+ // Opt into Vite's experimental bundled dev mode. Set before the user-config
61
+ // extension below so a `vite` override in sanity.cli.ts still has final say.
62
+ //
63
+ // Bundled mode bundles the app up front from an HTML entry, defaulting to
64
+ // `<root>/index.html`. Sanity has no such file — it serves a virtual document
65
+ // rewritten to `.sanity/runtime/index.html` — so point the bundler at the real
66
+ // runtime HTML, otherwise the build fails with UNRESOLVED_ENTRY.
67
+ if (bundledDev) {
68
+ viteConfig = {
69
+ ...viteConfig,
70
+ build: {
71
+ ...viteConfig.build,
72
+ rolldownOptions: {
73
+ ...viteConfig.build?.rolldownOptions,
74
+ input: path.join(cwd, '.sanity', 'runtime', 'index.html')
75
+ }
76
+ },
77
+ experimental: {
78
+ ...viteConfig.experimental,
79
+ bundledDev: true
80
+ }
81
+ };
82
+ }
59
83
  // Extend Vite configuration with user-provided config
60
84
  if (extendViteConfig) {
61
85
  viteConfig = await extendViteConfigWithUserConfig({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server/devServer.ts"],"sourcesContent":["import {\n extendViteConfigWithUserConfig,\n getViteConfig,\n writeSanityRuntime,\n} from '@sanity/cli-build/_internal/build'\nimport {\n getAppEnvironmentVariables,\n getStudioEnvironmentVariables,\n} from '@sanity/cli-build/_internal/env'\nimport {CliConfig, getCliTelemetry, type UserViteConfig} from '@sanity/cli-core'\nimport {type DefineAppInput} from '@sanity/workbench-cli'\nimport {type PluginOptions as ReactCompilerConfig} from 'babel-plugin-react-compiler'\nimport {type FSWatcher} from 'chokidar'\nimport {createServer, type InlineConfig, type ViteDevServer} from 'vite'\n\nimport {serverDebug} from './serverDebug.js'\nimport {sanityTypegenPlugin} from './vite/plugin-typegen.js'\n\nconst debug = serverDebug.extend('dev')\n\nexport interface DevServerOptions {\n basePath: string\n cwd: string\n httpPort: number\n\n reactCompiler: ReactCompilerConfig | undefined\n reactStrictMode: boolean | undefined\n\n staticPath: string\n\n appTitle?: string\n entry?: string\n httpHost?: string\n isApp?: boolean\n isWorkbenchApp?: boolean\n projectName?: string\n schemaExtraction?: CliConfig['schemaExtraction']\n services?: DefineAppInput['services']\n typegen?: CliConfig['typegen']\n views?: DefineAppInput['views']\n vite?: UserViteConfig\n}\n\ninterface DevServer {\n close(): Promise<void>\n server: ViteDevServer\n\n watcher?: FSWatcher\n}\n\nexport async function startDevServer(options: DevServerOptions): Promise<DevServer> {\n const {\n appTitle,\n basePath,\n cwd,\n entry,\n httpHost,\n httpPort,\n isApp,\n isWorkbenchApp,\n reactCompiler,\n reactStrictMode,\n schemaExtraction,\n services,\n typegen,\n views,\n vite: extendViteConfig,\n } = options\n\n debug('Writing Sanity runtime files')\n const {entries, watcher} = await writeSanityRuntime({\n appTitle,\n basePath,\n cwd,\n entry,\n isApp,\n isWorkbenchApp,\n reactStrictMode,\n watch: true,\n })\n\n debug('Resolving vite config')\n const mode = 'development'\n\n function getEnvironmentVariables() {\n return isApp\n ? getAppEnvironmentVariables({jsonEncode: true, prefix: 'process.env.'})\n : getStudioEnvironmentVariables({jsonEncode: true, prefix: 'process.env.'})\n }\n\n let viteConfig: InlineConfig = await getViteConfig({\n additionalPlugins: [\n // Add typegen when enabled\n ...(typegen?.enabled\n ? [\n sanityTypegenPlugin({\n config: typegen,\n telemetryLogger: getCliTelemetry(),\n workDir: cwd,\n }),\n ]\n : []),\n ],\n basePath,\n cwd,\n entries,\n getEnvironmentVariables,\n isApp,\n isWorkbenchApp,\n mode: 'development',\n reactCompiler,\n schemaExtraction,\n server: {host: httpHost, port: httpPort},\n services,\n views,\n })\n\n // Extend Vite configuration with user-provided config\n if (extendViteConfig) {\n viteConfig = await extendViteConfigWithUserConfig(\n {command: 'serve', mode},\n viteConfig,\n extendViteConfig,\n )\n }\n\n debug('Creating vite server')\n const server = await createServer(viteConfig)\n\n debug('Listening on specified port')\n await server.listen()\n\n return {\n close: async () => {\n if (watcher) {\n await watcher.close()\n }\n await server.close()\n },\n server,\n watcher,\n }\n}\n"],"names":["extendViteConfigWithUserConfig","getViteConfig","writeSanityRuntime","getAppEnvironmentVariables","getStudioEnvironmentVariables","getCliTelemetry","createServer","serverDebug","sanityTypegenPlugin","debug","extend","startDevServer","options","appTitle","basePath","cwd","entry","httpHost","httpPort","isApp","isWorkbenchApp","reactCompiler","reactStrictMode","schemaExtraction","services","typegen","views","vite","extendViteConfig","entries","watcher","watch","mode","getEnvironmentVariables","jsonEncode","prefix","viteConfig","additionalPlugins","enabled","config","telemetryLogger","workDir","server","host","port","command","listen","close"],"mappings":"AAAA,SACEA,8BAA8B,EAC9BC,aAAa,EACbC,kBAAkB,QACb,oCAAmC;AAC1C,SACEC,0BAA0B,EAC1BC,6BAA6B,QACxB,kCAAiC;AACxC,SAAmBC,eAAe,QAA4B,mBAAkB;AAIhF,SAAQC,YAAY,QAA8C,OAAM;AAExE,SAAQC,WAAW,QAAO,mBAAkB;AAC5C,SAAQC,mBAAmB,QAAO,2BAA0B;AAE5D,MAAMC,QAAQF,YAAYG,MAAM,CAAC;AAgCjC,OAAO,eAAeC,eAAeC,OAAyB;IAC5D,MAAM,EACJC,QAAQ,EACRC,QAAQ,EACRC,GAAG,EACHC,KAAK,EACLC,QAAQ,EACRC,QAAQ,EACRC,KAAK,EACLC,cAAc,EACdC,aAAa,EACbC,eAAe,EACfC,gBAAgB,EAChBC,QAAQ,EACRC,OAAO,EACPC,KAAK,EACLC,MAAMC,gBAAgB,EACvB,GAAGhB;IAEJH,MAAM;IACN,MAAM,EAACoB,OAAO,EAAEC,OAAO,EAAC,GAAG,MAAM5B,mBAAmB;QAClDW;QACAC;QACAC;QACAC;QACAG;QACAC;QACAE;QACAS,OAAO;IACT;IAEAtB,MAAM;IACN,MAAMuB,OAAO;IAEb,SAASC;QACP,OAAOd,QACHhB,2BAA2B;YAAC+B,YAAY;YAAMC,QAAQ;QAAc,KACpE/B,8BAA8B;YAAC8B,YAAY;YAAMC,QAAQ;QAAc;IAC7E;IAEA,IAAIC,aAA2B,MAAMnC,cAAc;QACjDoC,mBAAmB;YACjB,2BAA2B;eACvBZ,SAASa,UACT;gBACE9B,oBAAoB;oBAClB+B,QAAQd;oBACRe,iBAAiBnC;oBACjBoC,SAAS1B;gBACX;aACD,GACD,EAAE;SACP;QACDD;QACAC;QACAc;QACAI;QACAd;QACAC;QACAY,MAAM;QACNX;QACAE;QACAmB,QAAQ;YAACC,MAAM1B;YAAU2B,MAAM1B;QAAQ;QACvCM;QACAE;IACF;IAEA,sDAAsD;IACtD,IAAIE,kBAAkB;QACpBQ,aAAa,MAAMpC,+BACjB;YAAC6C,SAAS;YAASb;QAAI,GACvBI,YACAR;IAEJ;IAEAnB,MAAM;IACN,MAAMiC,SAAS,MAAMpC,aAAa8B;IAElC3B,MAAM;IACN,MAAMiC,OAAOI,MAAM;IAEnB,OAAO;QACLC,OAAO;YACL,IAAIjB,SAAS;gBACX,MAAMA,QAAQiB,KAAK;YACrB;YACA,MAAML,OAAOK,KAAK;QACpB;QACAL;QACAZ;IACF;AACF"}
1
+ {"version":3,"sources":["../../src/server/devServer.ts"],"sourcesContent":["import path from 'node:path'\n\nimport {\n extendViteConfigWithUserConfig,\n getViteConfig,\n writeSanityRuntime,\n} from '@sanity/cli-build/_internal/build'\nimport {\n getAppEnvironmentVariables,\n getStudioEnvironmentVariables,\n} from '@sanity/cli-build/_internal/env'\nimport {CliConfig, getCliTelemetry, type UserViteConfig} from '@sanity/cli-core'\nimport {type WorkbenchExposes} from '@sanity/workbench-cli/build'\nimport {type PluginOptions as ReactCompilerConfig} from 'babel-plugin-react-compiler'\nimport {type FSWatcher} from 'chokidar'\nimport {createServer, type InlineConfig, type ViteDevServer} from 'vite'\n\nimport {serverDebug} from './serverDebug.js'\nimport {sanityTypegenPlugin} from './vite/plugin-typegen.js'\n\nconst debug = serverDebug.extend('dev')\n\nexport interface DevServerOptions {\n basePath: string\n cwd: string\n httpPort: number\n\n reactCompiler: ReactCompilerConfig | undefined\n reactStrictMode: boolean | undefined\n\n staticPath: string\n\n appTitle?: string\n /** Enable Vite's experimental bundled dev mode (`experimental.bundledDev`). */\n bundledDev?: boolean\n entry?: string\n exposes?: WorkbenchExposes\n httpHost?: string\n isApp?: boolean\n isWorkbenchApp?: boolean\n projectName?: string\n schemaExtraction?: CliConfig['schemaExtraction']\n typegen?: CliConfig['typegen']\n vite?: UserViteConfig\n /** The workbench app's bus identity (`__SANITY_APP_ID__`). */\n workbenchAppId?: string\n}\n\ninterface DevServer {\n close(): Promise<void>\n server: ViteDevServer\n\n watcher?: FSWatcher\n}\n\nexport async function startDevServer(options: DevServerOptions): Promise<DevServer> {\n const {\n appTitle,\n basePath,\n bundledDev,\n cwd,\n entry,\n exposes,\n httpHost,\n httpPort,\n isApp,\n isWorkbenchApp,\n reactCompiler,\n reactStrictMode,\n schemaExtraction,\n typegen,\n vite: extendViteConfig,\n workbenchAppId,\n } = options\n\n debug('Writing Sanity runtime files')\n const {entries, watcher} = await writeSanityRuntime({\n appTitle,\n basePath,\n cwd,\n entry,\n isApp,\n isWorkbenchApp,\n reactStrictMode,\n watch: true,\n })\n\n debug('Resolving vite config')\n const mode = 'development'\n\n function getEnvironmentVariables() {\n return isApp\n ? getAppEnvironmentVariables({jsonEncode: true, prefix: 'process.env.'})\n : getStudioEnvironmentVariables({jsonEncode: true, prefix: 'process.env.'})\n }\n\n let viteConfig: InlineConfig = await getViteConfig({\n additionalPlugins: [\n // Add typegen when enabled\n ...(typegen?.enabled\n ? [\n sanityTypegenPlugin({\n config: typegen,\n telemetryLogger: getCliTelemetry(),\n workDir: cwd,\n }),\n ]\n : []),\n ],\n basePath,\n cwd,\n entries,\n exposes,\n getEnvironmentVariables,\n isApp,\n isWorkbenchApp,\n mode: 'development',\n reactCompiler,\n schemaExtraction,\n server: {host: httpHost, port: httpPort},\n workbenchAppId,\n })\n\n // Opt into Vite's experimental bundled dev mode. Set before the user-config\n // extension below so a `vite` override in sanity.cli.ts still has final say.\n //\n // Bundled mode bundles the app up front from an HTML entry, defaulting to\n // `<root>/index.html`. Sanity has no such file — it serves a virtual document\n // rewritten to `.sanity/runtime/index.html` — so point the bundler at the real\n // runtime HTML, otherwise the build fails with UNRESOLVED_ENTRY.\n if (bundledDev) {\n viteConfig = {\n ...viteConfig,\n build: {\n ...viteConfig.build,\n rolldownOptions: {\n ...viteConfig.build?.rolldownOptions,\n input: path.join(cwd, '.sanity', 'runtime', 'index.html'),\n },\n },\n experimental: {...viteConfig.experimental, bundledDev: true},\n }\n }\n\n // Extend Vite configuration with user-provided config\n if (extendViteConfig) {\n viteConfig = await extendViteConfigWithUserConfig(\n {command: 'serve', mode},\n viteConfig,\n extendViteConfig,\n )\n }\n\n debug('Creating vite server')\n const server = await createServer(viteConfig)\n\n debug('Listening on specified port')\n await server.listen()\n\n return {\n close: async () => {\n if (watcher) {\n await watcher.close()\n }\n await server.close()\n },\n server,\n watcher,\n }\n}\n"],"names":["path","extendViteConfigWithUserConfig","getViteConfig","writeSanityRuntime","getAppEnvironmentVariables","getStudioEnvironmentVariables","getCliTelemetry","createServer","serverDebug","sanityTypegenPlugin","debug","extend","startDevServer","options","appTitle","basePath","bundledDev","cwd","entry","exposes","httpHost","httpPort","isApp","isWorkbenchApp","reactCompiler","reactStrictMode","schemaExtraction","typegen","vite","extendViteConfig","workbenchAppId","entries","watcher","watch","mode","getEnvironmentVariables","jsonEncode","prefix","viteConfig","additionalPlugins","enabled","config","telemetryLogger","workDir","server","host","port","build","rolldownOptions","input","join","experimental","command","listen","close"],"mappings":"AAAA,OAAOA,UAAU,YAAW;AAE5B,SACEC,8BAA8B,EAC9BC,aAAa,EACbC,kBAAkB,QACb,oCAAmC;AAC1C,SACEC,0BAA0B,EAC1BC,6BAA6B,QACxB,kCAAiC;AACxC,SAAmBC,eAAe,QAA4B,mBAAkB;AAIhF,SAAQC,YAAY,QAA8C,OAAM;AAExE,SAAQC,WAAW,QAAO,mBAAkB;AAC5C,SAAQC,mBAAmB,QAAO,2BAA0B;AAE5D,MAAMC,QAAQF,YAAYG,MAAM,CAAC;AAmCjC,OAAO,eAAeC,eAAeC,OAAyB;IAC5D,MAAM,EACJC,QAAQ,EACRC,QAAQ,EACRC,UAAU,EACVC,GAAG,EACHC,KAAK,EACLC,OAAO,EACPC,QAAQ,EACRC,QAAQ,EACRC,KAAK,EACLC,cAAc,EACdC,aAAa,EACbC,eAAe,EACfC,gBAAgB,EAChBC,OAAO,EACPC,MAAMC,gBAAgB,EACtBC,cAAc,EACf,GAAGjB;IAEJH,MAAM;IACN,MAAM,EAACqB,OAAO,EAAEC,OAAO,EAAC,GAAG,MAAM7B,mBAAmB;QAClDW;QACAC;QACAE;QACAC;QACAI;QACAC;QACAE;QACAQ,OAAO;IACT;IAEAvB,MAAM;IACN,MAAMwB,OAAO;IAEb,SAASC;QACP,OAAOb,QACHlB,2BAA2B;YAACgC,YAAY;YAAMC,QAAQ;QAAc,KACpEhC,8BAA8B;YAAC+B,YAAY;YAAMC,QAAQ;QAAc;IAC7E;IAEA,IAAIC,aAA2B,MAAMpC,cAAc;QACjDqC,mBAAmB;YACjB,2BAA2B;eACvBZ,SAASa,UACT;gBACE/B,oBAAoB;oBAClBgC,QAAQd;oBACRe,iBAAiBpC;oBACjBqC,SAAS1B;gBACX;aACD,GACD,EAAE;SACP;QACDF;QACAE;QACAc;QACAZ;QACAgB;QACAb;QACAC;QACAW,MAAM;QACNV;QACAE;QACAkB,QAAQ;YAACC,MAAMzB;YAAU0B,MAAMzB;QAAQ;QACvCS;IACF;IAEA,4EAA4E;IAC5E,6EAA6E;IAC7E,EAAE;IACF,0EAA0E;IAC1E,8EAA8E;IAC9E,+EAA+E;IAC/E,iEAAiE;IACjE,IAAId,YAAY;QACdsB,aAAa;YACX,GAAGA,UAAU;YACbS,OAAO;gBACL,GAAGT,WAAWS,KAAK;gBACnBC,iBAAiB;oBACf,GAAGV,WAAWS,KAAK,EAAEC,eAAe;oBACpCC,OAAOjD,KAAKkD,IAAI,CAACjC,KAAK,WAAW,WAAW;gBAC9C;YACF;YACAkC,cAAc;gBAAC,GAAGb,WAAWa,YAAY;gBAAEnC,YAAY;YAAI;QAC7D;IACF;IAEA,sDAAsD;IACtD,IAAIa,kBAAkB;QACpBS,aAAa,MAAMrC,+BACjB;YAACmD,SAAS;YAASlB;QAAI,GACvBI,YACAT;IAEJ;IAEAnB,MAAM;IACN,MAAMkC,SAAS,MAAMrC,aAAa+B;IAElC5B,MAAM;IACN,MAAMkC,OAAOS,MAAM;IAEnB,OAAO;QACLC,OAAO;YACL,IAAItB,SAAS;gBACX,MAAMA,QAAQsB,KAAK;YACrB;YACA,MAAMV,OAAOU,KAAK;QACpB;QACAV;QACAZ;IACF;AACF"}
@@ -13,15 +13,23 @@ function hasNewAppId(cliConfig) {
13
13
  function hasDeprecatedAppId(cliConfig) {
14
14
  return Boolean(getDeprecatedAppId(cliConfig));
15
15
  }
16
+ /**
17
+ * Decides which app-id problem a config has: both the deprecated `app.id` and
18
+ * `deployment.appId` (a conflict), only the deprecated one, or neither. Shared
19
+ * so the real deploy and the dry-run check reach the same verdict.
20
+ * @internal
21
+ */ export function resolveAppIdIssue(cliConfig) {
22
+ if (!hasDeprecatedAppId(cliConfig)) return null;
23
+ return hasNewAppId(cliConfig) ? 'conflicting-config' : 'deprecated-config';
24
+ }
16
25
  /**
17
26
  * Checks if an SDK app uses the deprecated app.id config & throws a warning if so.
18
27
  * @remarks Throws an error if an app uses both deployment.appId and app.id
19
28
  * @internal
20
29
  */ export function checkForDeprecatedAppId({ cliConfig, output }) {
21
- const hasNew = hasNewAppId(cliConfig);
22
- const hasOld = hasDeprecatedAppId(cliConfig);
23
- // Throw an error if both the old and new app ID configs are found
24
- if (hasOld && hasNew) {
30
+ const issue = resolveAppIdIssue(cliConfig);
31
+ // Both configs set: a real deploy can't pick one, so stop here
32
+ if (issue === 'conflicting-config') {
25
33
  output.error(`${styleText('bold', 'Found both app.id (deprecated) and deployment.appId in your application configuration.')}
26
34
 
27
35
  Please remove app.id from your sanity.cli.js or sanity.cli.ts file.`, {
@@ -29,7 +37,7 @@ Please remove app.id from your sanity.cli.js or sanity.cli.ts file.`, {
29
37
  });
30
38
  }
31
39
  // Just warn if only the old app ID config is found
32
- if (hasOld) {
40
+ if (issue === 'deprecated-config') {
33
41
  output.warn(`${styleText('bold', 'The `app.id` config has moved to `deployment.appId`.')}
34
42
 
35
43
  Please update \`sanity.cli.ts\` or \`sanity.cli.js\` and move:
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/util/appId.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {type CliConfig, type Output} from '@sanity/cli-core'\n\ninterface Options {\n cliConfig: CliConfig\n output: Output\n}\n\nfunction getDeploymentAppId(cliConfig: CliConfig): string | undefined {\n const id = cliConfig?.deployment?.appId\n return id\n}\n\nfunction getDeprecatedAppId(cliConfig: CliConfig): string | undefined {\n const id = cliConfig?.app?.id\n return id\n}\n\nfunction hasNewAppId(cliConfig: CliConfig) {\n return Boolean(getDeploymentAppId(cliConfig))\n}\n\nfunction hasDeprecatedAppId(cliConfig: CliConfig) {\n return Boolean(getDeprecatedAppId(cliConfig))\n}\n\n/**\n * Checks if an SDK app uses the deprecated app.id config & throws a warning if so.\n * @remarks Throws an error if an app uses both deployment.appId and app.id\n * @internal\n */\nexport function checkForDeprecatedAppId({cliConfig, output}: Options): void {\n const hasNew = hasNewAppId(cliConfig)\n const hasOld = hasDeprecatedAppId(cliConfig)\n\n // Throw an error if both the old and new app ID configs are found\n if (hasOld && hasNew) {\n output.error(\n `${styleText('bold', 'Found both app.id (deprecated) and deployment.appId in your application configuration.')}\n\nPlease remove app.id from your sanity.cli.js or sanity.cli.ts file.`,\n {\n exit: 1,\n },\n )\n }\n\n // Just warn if only the old app ID config is found\n if (hasOld) {\n output.warn(\n `${styleText('bold', 'The `app.id` config has moved to `deployment.appId`.')}\n\nPlease update \\`sanity.cli.ts\\` or \\`sanity.cli.js\\` and move:\n${styleText('red', `app: {id: \"${getDeprecatedAppId(cliConfig)}\", ... }`)}\nto\n${styleText('green', `deployment: {appId: \"${getDeprecatedAppId(cliConfig)}\", ... }`)}\n`,\n )\n }\n}\n\n/**\n * Get an application's ID\n * @remarks Favors the current implementation (deployment.appId) but will fall back to the deprecated app.id\n * @internal\n */\nexport function getAppId(cliConfig: CliConfig): string | undefined {\n const hasNew = hasNewAppId(cliConfig)\n const hasOld = hasDeprecatedAppId(cliConfig)\n\n if (hasNew) {\n return getDeploymentAppId(cliConfig)\n }\n\n if (hasOld) {\n return getDeprecatedAppId(cliConfig)\n }\n\n return undefined\n}\n"],"names":["styleText","getDeploymentAppId","cliConfig","id","deployment","appId","getDeprecatedAppId","app","hasNewAppId","Boolean","hasDeprecatedAppId","checkForDeprecatedAppId","output","hasNew","hasOld","error","exit","warn","getAppId","undefined"],"mappings":"AAAA,SAAQA,SAAS,QAAO,YAAW;AASnC,SAASC,mBAAmBC,SAAoB;IAC9C,MAAMC,KAAKD,WAAWE,YAAYC;IAClC,OAAOF;AACT;AAEA,SAASG,mBAAmBJ,SAAoB;IAC9C,MAAMC,KAAKD,WAAWK,KAAKJ;IAC3B,OAAOA;AACT;AAEA,SAASK,YAAYN,SAAoB;IACvC,OAAOO,QAAQR,mBAAmBC;AACpC;AAEA,SAASQ,mBAAmBR,SAAoB;IAC9C,OAAOO,QAAQH,mBAAmBJ;AACpC;AAEA;;;;CAIC,GACD,OAAO,SAASS,wBAAwB,EAACT,SAAS,EAAEU,MAAM,EAAU;IAClE,MAAMC,SAASL,YAAYN;IAC3B,MAAMY,SAASJ,mBAAmBR;IAElC,kEAAkE;IAClE,IAAIY,UAAUD,QAAQ;QACpBD,OAAOG,KAAK,CACV,GAAGf,UAAU,QAAQ,0FAA0F;;mEAElD,CAAC,EAC9D;YACEgB,MAAM;QACR;IAEJ;IAEA,mDAAmD;IACnD,IAAIF,QAAQ;QACVF,OAAOK,IAAI,CACT,GAAGjB,UAAU,QAAQ,wDAAwD;;;AAGnF,EAAEA,UAAU,OAAO,CAAC,WAAW,EAAEM,mBAAmBJ,WAAW,QAAQ,CAAC,EAAE;;AAE1E,EAAEF,UAAU,SAAS,CAAC,qBAAqB,EAAEM,mBAAmBJ,WAAW,QAAQ,CAAC,EAAE;AACtF,CAAC;IAEC;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASgB,SAAShB,SAAoB;IAC3C,MAAMW,SAASL,YAAYN;IAC3B,MAAMY,SAASJ,mBAAmBR;IAElC,IAAIW,QAAQ;QACV,OAAOZ,mBAAmBC;IAC5B;IAEA,IAAIY,QAAQ;QACV,OAAOR,mBAAmBJ;IAC5B;IAEA,OAAOiB;AACT"}
1
+ {"version":3,"sources":["../../src/util/appId.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {type CliConfig, type Output} from '@sanity/cli-core'\n\ninterface Options {\n cliConfig: CliConfig\n output: Output\n}\n\nfunction getDeploymentAppId(cliConfig: CliConfig): string | undefined {\n const id = cliConfig?.deployment?.appId\n return id\n}\n\nfunction getDeprecatedAppId(cliConfig: CliConfig): string | undefined {\n const id = cliConfig?.app?.id\n return id\n}\n\nfunction hasNewAppId(cliConfig: CliConfig) {\n return Boolean(getDeploymentAppId(cliConfig))\n}\n\nfunction hasDeprecatedAppId(cliConfig: CliConfig) {\n return Boolean(getDeprecatedAppId(cliConfig))\n}\n\n/** The one app-id configuration problem to surface, if any. */\nexport type AppIdIssue = 'conflicting-config' | 'deprecated-config'\n\n/**\n * Decides which app-id problem a config has: both the deprecated `app.id` and\n * `deployment.appId` (a conflict), only the deprecated one, or neither. Shared\n * so the real deploy and the dry-run check reach the same verdict.\n * @internal\n */\nexport function resolveAppIdIssue(cliConfig: CliConfig): AppIdIssue | null {\n if (!hasDeprecatedAppId(cliConfig)) return null\n return hasNewAppId(cliConfig) ? 'conflicting-config' : 'deprecated-config'\n}\n\n/**\n * Checks if an SDK app uses the deprecated app.id config & throws a warning if so.\n * @remarks Throws an error if an app uses both deployment.appId and app.id\n * @internal\n */\nexport function checkForDeprecatedAppId({cliConfig, output}: Options): void {\n const issue = resolveAppIdIssue(cliConfig)\n\n // Both configs set: a real deploy can't pick one, so stop here\n if (issue === 'conflicting-config') {\n output.error(\n `${styleText('bold', 'Found both app.id (deprecated) and deployment.appId in your application configuration.')}\n\nPlease remove app.id from your sanity.cli.js or sanity.cli.ts file.`,\n {\n exit: 1,\n },\n )\n }\n\n // Just warn if only the old app ID config is found\n if (issue === 'deprecated-config') {\n output.warn(\n `${styleText('bold', 'The `app.id` config has moved to `deployment.appId`.')}\n\nPlease update \\`sanity.cli.ts\\` or \\`sanity.cli.js\\` and move:\n${styleText('red', `app: {id: \"${getDeprecatedAppId(cliConfig)}\", ... }`)}\nto\n${styleText('green', `deployment: {appId: \"${getDeprecatedAppId(cliConfig)}\", ... }`)}\n`,\n )\n }\n}\n\n/**\n * Get an application's ID\n * @remarks Favors the current implementation (deployment.appId) but will fall back to the deprecated app.id\n * @internal\n */\nexport function getAppId(cliConfig: CliConfig): string | undefined {\n const hasNew = hasNewAppId(cliConfig)\n const hasOld = hasDeprecatedAppId(cliConfig)\n\n if (hasNew) {\n return getDeploymentAppId(cliConfig)\n }\n\n if (hasOld) {\n return getDeprecatedAppId(cliConfig)\n }\n\n return undefined\n}\n"],"names":["styleText","getDeploymentAppId","cliConfig","id","deployment","appId","getDeprecatedAppId","app","hasNewAppId","Boolean","hasDeprecatedAppId","resolveAppIdIssue","checkForDeprecatedAppId","output","issue","error","exit","warn","getAppId","hasNew","hasOld","undefined"],"mappings":"AAAA,SAAQA,SAAS,QAAO,YAAW;AASnC,SAASC,mBAAmBC,SAAoB;IAC9C,MAAMC,KAAKD,WAAWE,YAAYC;IAClC,OAAOF;AACT;AAEA,SAASG,mBAAmBJ,SAAoB;IAC9C,MAAMC,KAAKD,WAAWK,KAAKJ;IAC3B,OAAOA;AACT;AAEA,SAASK,YAAYN,SAAoB;IACvC,OAAOO,QAAQR,mBAAmBC;AACpC;AAEA,SAASQ,mBAAmBR,SAAoB;IAC9C,OAAOO,QAAQH,mBAAmBJ;AACpC;AAKA;;;;;CAKC,GACD,OAAO,SAASS,kBAAkBT,SAAoB;IACpD,IAAI,CAACQ,mBAAmBR,YAAY,OAAO;IAC3C,OAAOM,YAAYN,aAAa,uBAAuB;AACzD;AAEA;;;;CAIC,GACD,OAAO,SAASU,wBAAwB,EAACV,SAAS,EAAEW,MAAM,EAAU;IAClE,MAAMC,QAAQH,kBAAkBT;IAEhC,+DAA+D;IAC/D,IAAIY,UAAU,sBAAsB;QAClCD,OAAOE,KAAK,CACV,GAAGf,UAAU,QAAQ,0FAA0F;;mEAElD,CAAC,EAC9D;YACEgB,MAAM;QACR;IAEJ;IAEA,mDAAmD;IACnD,IAAIF,UAAU,qBAAqB;QACjCD,OAAOI,IAAI,CACT,GAAGjB,UAAU,QAAQ,wDAAwD;;;AAGnF,EAAEA,UAAU,OAAO,CAAC,WAAW,EAAEM,mBAAmBJ,WAAW,QAAQ,CAAC,EAAE;;AAE1E,EAAEF,UAAU,SAAS,CAAC,qBAAqB,EAAEM,mBAAmBJ,WAAW,QAAQ,CAAC,EAAE;AACtF,CAAC;IAEC;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASgB,SAAShB,SAAoB;IAC3C,MAAMiB,SAASX,YAAYN;IAC3B,MAAMkB,SAASV,mBAAmBR;IAElC,IAAIiB,QAAQ;QACV,OAAOlB,mBAAmBC;IAC5B;IAEA,IAAIkB,QAAQ;QACV,OAAOd,mBAAmBJ;IAC5B;IAEA,OAAOmB;AACT"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/util/compareDependencyVersions.ts"],"sourcesContent":["import path from 'node:path'\n\nimport {getModuleUrl} from '@sanity/cli-build/_internal/build'\nimport {getLocalPackageVersion, readPackageJson} from '@sanity/cli-core'\nimport {createRequester} from '@sanity/cli-core/request'\nimport {coerce, eq, prerelease, parse as semverParse} from 'semver'\n\nconst defaultRequester = createRequester({\n middleware: {httpErrors: false, promise: {onlyBody: false}},\n})\n\ninterface CompareDependencyVersions {\n installed: string\n pkg: string\n remote: string\n}\n\nexport interface UnresolvedPrerelease {\n pkg: string\n version: string\n}\n\nexport interface CompareDependencyVersionsResult {\n mismatched: Array<CompareDependencyVersions>\n unresolvedPrerelease: Array<UnresolvedPrerelease>\n}\n\ninterface CompareDependencyVersionsOptions {\n /** When provided, uses the app-specific module endpoint instead of the default endpoint. */\n appId?: string\n /** Optional requester for dependency injection (primarily for testing). */\n requester?: typeof defaultRequester\n}\n\n/**\n * @internal\n *\n * Compares the versions of dependencies in the studio or app with their remote versions.\n *\n * This function reads the package.json file in the provided working directory, and compares the versions of the dependencies\n * specified in the `autoUpdatesImports` parameter with their remote versions. If the versions do not match, the dependency is\n * added to a list of failed dependencies, which is returned by the function.\n *\n * The failed dependencies are anything that does not strictly match the remote version.\n * This means that if a version is lower or greater by even a patch it will be marked as failed.\n *\n * @param packages - An array of packages with their name and version to compare against remote.\n * @param workDir - The path to the working directory containing the package.json file.\n * @param options - Optional configuration for version comparison.\n *\n * @returns A promise that resolves to an object containing `mismatched` (packages whose local and remote versions differ)\n * and `unresolvedPrerelease` (packages with prerelease versions that could not be resolved remotely).\n *\n * @throws Throws an error if the remote version of a non-prerelease package cannot be fetched, or if the local version\n * of a package cannot be parsed.\n */\nexport async function compareDependencyVersions(\n packages: {name: string; version: string}[],\n workDir: string,\n {appId, requester = defaultRequester}: CompareDependencyVersionsOptions = {},\n): Promise<CompareDependencyVersionsResult> {\n const manifest = await readPackageJson(path.join(workDir, 'package.json'), {\n skipSchemaValidation: true,\n })\n const dependencies = {...manifest?.dependencies, ...manifest?.devDependencies}\n\n const mismatched: Array<CompareDependencyVersions> = []\n const unresolvedPrerelease: Array<UnresolvedPrerelease> = []\n\n for (const pkg of packages) {\n // Skip packages that are not declared in the local package.json\n if (!dependencies[pkg.name]) {\n continue\n }\n\n const resolvedVersion = await getRemoteResolvedVersion(getModuleUrl(pkg, {appId}), requester)\n\n if (resolvedVersion === null) {\n if (prerelease(pkg.version)) {\n unresolvedPrerelease.push({pkg: pkg.name, version: pkg.version})\n continue\n }\n throw new Error(\n `Failed to resolve remote version for ${pkg.name}@${pkg.version}: package not found`,\n )\n }\n\n const packageVersion = await getLocalPackageVersion(pkg.name, workDir)\n\n const manifestVersion = dependencies[pkg.name]\n\n const installed = coerce(packageVersion ? semverParse(packageVersion) : coerce(manifestVersion))\n\n if (!installed) {\n throw new Error(`Failed to parse installed version for ${pkg.name}`)\n }\n\n if (!eq(resolvedVersion, installed.version)) {\n mismatched.push({\n installed: installed.version,\n pkg: pkg.name,\n remote: resolvedVersion,\n })\n }\n }\n\n return {mismatched, unresolvedPrerelease}\n}\n\nasync function getRemoteResolvedVersion(\n url: string,\n request: typeof defaultRequester,\n): Promise<string | null> {\n let response\n try {\n response = await request({\n maxRedirects: 0,\n method: 'HEAD',\n url,\n })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n throw new Error(`Failed to fetch remote version for ${url}: ${message}`, {cause: err})\n }\n\n // 302 is expected, but lets also handle 2xx\n if (response.statusCode < 400) {\n const resolved = response.headers['x-resolved-version']\n if (!resolved) {\n throw new Error(`Missing 'x-resolved-version' header on response from HEAD ${url}`)\n }\n return resolved\n }\n\n if (response.statusCode === 404) {\n return null\n }\n\n throw new Error(`Unexpected HTTP response: ${response.statusCode} ${response.statusMessage}`)\n}\n"],"names":["path","getModuleUrl","getLocalPackageVersion","readPackageJson","createRequester","coerce","eq","prerelease","parse","semverParse","defaultRequester","middleware","httpErrors","promise","onlyBody","compareDependencyVersions","packages","workDir","appId","requester","manifest","join","skipSchemaValidation","dependencies","devDependencies","mismatched","unresolvedPrerelease","pkg","name","resolvedVersion","getRemoteResolvedVersion","version","push","Error","packageVersion","manifestVersion","installed","remote","url","request","response","maxRedirects","method","err","message","String","cause","statusCode","resolved","headers","statusMessage"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/util/compareDependencyVersions.ts"],"sourcesContent":["import path from 'node:path'\n\nimport {\n type CompareDependencyVersions,\n type CompareDependencyVersionsResult,\n getModuleUrl,\n type UnresolvedPrerelease,\n} from '@sanity/cli-build/_internal/build'\nimport {getLocalPackageVersion, readPackageJson} from '@sanity/cli-core'\nimport {createRequester} from '@sanity/cli-core/request'\nimport {coerce, eq, prerelease, parse as semverParse} from 'semver'\n\nconst defaultRequester = createRequester({\n middleware: {httpErrors: false, promise: {onlyBody: false}},\n})\n\ninterface CompareDependencyVersionsOptions {\n /** When provided, uses the app-specific module endpoint instead of the default endpoint. */\n appId?: string\n /** Optional requester for dependency injection (primarily for testing). */\n requester?: typeof defaultRequester\n}\n\n/**\n * @internal\n *\n * Compares the versions of dependencies in the studio or app with their remote versions.\n *\n * This function reads the package.json file in the provided working directory, and compares the versions of the dependencies\n * specified in the `autoUpdatesImports` parameter with their remote versions. If the versions do not match, the dependency is\n * added to a list of failed dependencies, which is returned by the function.\n *\n * The failed dependencies are anything that does not strictly match the remote version.\n * This means that if a version is lower or greater by even a patch it will be marked as failed.\n *\n * @param packages - An array of packages with their name and version to compare against remote.\n * @param workDir - The path to the working directory containing the package.json file.\n * @param options - Optional configuration for version comparison.\n *\n * @returns A promise that resolves to an object containing `mismatched` (packages whose local and remote versions differ)\n * and `unresolvedPrerelease` (packages with prerelease versions that could not be resolved remotely).\n *\n * @throws Throws an error if the remote version of a non-prerelease package cannot be fetched, or if the local version\n * of a package cannot be parsed.\n */\nexport async function compareDependencyVersions(\n packages: {name: string; version: string}[],\n workDir: string,\n {appId, requester = defaultRequester}: CompareDependencyVersionsOptions = {},\n): Promise<CompareDependencyVersionsResult> {\n const manifest = await readPackageJson(path.join(workDir, 'package.json'), {\n skipSchemaValidation: true,\n })\n const dependencies = {...manifest?.dependencies, ...manifest?.devDependencies}\n\n const mismatched: Array<CompareDependencyVersions> = []\n const unresolvedPrerelease: Array<UnresolvedPrerelease> = []\n\n for (const pkg of packages) {\n // Skip packages that are not declared in the local package.json\n if (!dependencies[pkg.name]) {\n continue\n }\n\n const resolvedVersion = await getRemoteResolvedVersion(getModuleUrl(pkg, {appId}), requester)\n\n if (resolvedVersion === null) {\n if (prerelease(pkg.version)) {\n unresolvedPrerelease.push({pkg: pkg.name, version: pkg.version})\n continue\n }\n throw new Error(\n `Failed to resolve remote version for ${pkg.name}@${pkg.version}: package not found`,\n )\n }\n\n const packageVersion = await getLocalPackageVersion(pkg.name, workDir)\n\n const manifestVersion = dependencies[pkg.name]\n\n const installed = coerce(packageVersion ? semverParse(packageVersion) : coerce(manifestVersion))\n\n if (!installed) {\n throw new Error(`Failed to parse installed version for ${pkg.name}`)\n }\n\n if (!eq(resolvedVersion, installed.version)) {\n mismatched.push({\n installed: installed.version,\n pkg: pkg.name,\n remote: resolvedVersion,\n })\n }\n }\n\n return {mismatched, unresolvedPrerelease}\n}\n\nasync function getRemoteResolvedVersion(\n url: string,\n request: typeof defaultRequester,\n): Promise<string | null> {\n let response\n try {\n response = await request({\n maxRedirects: 0,\n method: 'HEAD',\n url,\n })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n throw new Error(`Failed to fetch remote version for ${url}: ${message}`, {cause: err})\n }\n\n // 302 is expected, but lets also handle 2xx\n if (response.statusCode < 400) {\n const resolved = response.headers['x-resolved-version']\n if (!resolved) {\n throw new Error(`Missing 'x-resolved-version' header on response from HEAD ${url}`)\n }\n return resolved\n }\n\n if (response.statusCode === 404) {\n return null\n }\n\n throw new Error(`Unexpected HTTP response: ${response.statusCode} ${response.statusMessage}`)\n}\n"],"names":["path","getModuleUrl","getLocalPackageVersion","readPackageJson","createRequester","coerce","eq","prerelease","parse","semverParse","defaultRequester","middleware","httpErrors","promise","onlyBody","compareDependencyVersions","packages","workDir","appId","requester","manifest","join","skipSchemaValidation","dependencies","devDependencies","mismatched","unresolvedPrerelease","pkg","name","resolvedVersion","getRemoteResolvedVersion","version","push","Error","packageVersion","manifestVersion","installed","remote","url","request","response","maxRedirects","method","err","message","String","cause","statusCode","resolved","headers","statusMessage"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { isWorkbenchApp } from '@sanity/cli-core';
1
+ import { isWorkbenchApp } from '@sanity/workbench-cli';
2
2
  /**
3
3
  * Determine if the current project is an app (as opposed to a studio).
4
4
  *
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/util/determineIsApp.ts"],"sourcesContent":["import {type CliConfig, isWorkbenchApp} from '@sanity/cli-core'\n\n/**\n * Determine if the current project is an app (as opposed to a studio).\n *\n * For workbench apps (`unstable_defineApp`), this is driven by the resolved\n * `applicationType` — a studio is not an app, everything else is. The\n * `applicationType` is settled at config-load time (explicit, or inferred from\n * the presence of a `sanity.config.*`). Legacy configs fall back to the\n * presence of an `app` field.\n *\n * @returns `true` if the current project is an app, `false` otherwise.\n */\nexport function determineIsApp(cliConfig: CliConfig): boolean {\n const app = cliConfig?.app\n if (isWorkbenchApp(app)) {\n return app.applicationType !== 'studio'\n }\n return Boolean(cliConfig && 'app' in cliConfig)\n}\n"],"names":["isWorkbenchApp","determineIsApp","cliConfig","app","applicationType","Boolean"],"mappings":"AAAA,SAAwBA,cAAc,QAAO,mBAAkB;AAE/D;;;;;;;;;;CAUC,GACD,OAAO,SAASC,eAAeC,SAAoB;IACjD,MAAMC,MAAMD,WAAWC;IACvB,IAAIH,eAAeG,MAAM;QACvB,OAAOA,IAAIC,eAAe,KAAK;IACjC;IACA,OAAOC,QAAQH,aAAa,SAASA;AACvC"}
1
+ {"version":3,"sources":["../../src/util/determineIsApp.ts"],"sourcesContent":["import {type CliConfig} from '@sanity/cli-core'\nimport {isWorkbenchApp} from '@sanity/workbench-cli'\n\n/**\n * Determine if the current project is an app (as opposed to a studio).\n *\n * For workbench apps (`unstable_defineApp`), this is driven by the resolved\n * `applicationType` — a studio is not an app, everything else is. The\n * `applicationType` is settled at config-load time (explicit, or inferred from\n * the presence of a `sanity.config.*`). Legacy configs fall back to the\n * presence of an `app` field.\n *\n * @returns `true` if the current project is an app, `false` otherwise.\n */\nexport function determineIsApp(cliConfig: CliConfig): boolean {\n const app = cliConfig?.app\n if (isWorkbenchApp(app)) {\n return app.applicationType !== 'studio'\n }\n return Boolean(cliConfig && 'app' in cliConfig)\n}\n"],"names":["isWorkbenchApp","determineIsApp","cliConfig","app","applicationType","Boolean"],"mappings":"AACA,SAAQA,cAAc,QAAO,wBAAuB;AAEpD;;;;;;;;;;CAUC,GACD,OAAO,SAASC,eAAeC,SAAoB;IACjD,MAAMC,MAAMD,WAAWC;IACvB,IAAIH,eAAeG,MAAM;QACvB,OAAOA,IAAIC,eAAe,KAAK;IACjC;IACA,OAAOC,QAAQH,aAAa,SAASA;AACvC"}
@@ -1,5 +1,7 @@
1
1
  export const NO_PROJECT_ID = `sanity.cli.ts does not contain a project identifier ("api.projectId"), which is required for the Sanity CLI to communicate with the Sanity API`;
2
2
  export const NO_ORGANIZATION_ID = `sanity.cli.ts does not contain an organization identifier ("app.organizationId"), which is required for the Sanity CLI to communicate with the Sanity API`;
3
3
  export const NO_MEDIA_LIBRARY_ASPECTS_PATH = `sanity.cli.ts does not contain a media library aspects path ("mediaLibrary.aspectsPath"), which is required for the Sanity CLI to manage aspects.`;
4
+ export const EXTERNAL_APP_NOT_SUPPORTED = `Deploying an app to an external host is not supported.`;
5
+ export const APP_ID_NOT_FOUND_IN_ORGANIZATION = `The \`appId\` provided in your configuration’s \`deployment\` object cannot be found in your organization`;
4
6
 
5
7
  //# sourceMappingURL=errorMessages.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/util/errorMessages.ts"],"sourcesContent":["export const NO_PROJECT_ID = `sanity.cli.ts does not contain a project identifier (\"api.projectId\"), which is required for the Sanity CLI to communicate with the Sanity API`\nexport const NO_ORGANIZATION_ID = `sanity.cli.ts does not contain an organization identifier (\"app.organizationId\"), which is required for the Sanity CLI to communicate with the Sanity API`\nexport const NO_MEDIA_LIBRARY_ASPECTS_PATH = `sanity.cli.ts does not contain a media library aspects path (\"mediaLibrary.aspectsPath\"), which is required for the Sanity CLI to manage aspects.`\n"],"names":["NO_PROJECT_ID","NO_ORGANIZATION_ID","NO_MEDIA_LIBRARY_ASPECTS_PATH"],"mappings":"AAAA,OAAO,MAAMA,gBAAgB,CAAC,8IAA8I,CAAC,CAAA;AAC7K,OAAO,MAAMC,qBAAqB,CAAC,yJAAyJ,CAAC,CAAA;AAC7L,OAAO,MAAMC,gCAAgC,CAAC,iJAAiJ,CAAC,CAAA"}
1
+ {"version":3,"sources":["../../src/util/errorMessages.ts"],"sourcesContent":["export const NO_PROJECT_ID = `sanity.cli.ts does not contain a project identifier (\"api.projectId\"), which is required for the Sanity CLI to communicate with the Sanity API`\nexport const NO_ORGANIZATION_ID = `sanity.cli.ts does not contain an organization identifier (\"app.organizationId\"), which is required for the Sanity CLI to communicate with the Sanity API`\nexport const NO_MEDIA_LIBRARY_ASPECTS_PATH = `sanity.cli.ts does not contain a media library aspects path (\"mediaLibrary.aspectsPath\"), which is required for the Sanity CLI to manage aspects.`\nexport const EXTERNAL_APP_NOT_SUPPORTED = `Deploying an app to an external host is not supported.`\nexport const APP_ID_NOT_FOUND_IN_ORGANIZATION = `The \\`appId\\` provided in your configuration’s \\`deployment\\` object cannot be found in your organization`\n"],"names":["NO_PROJECT_ID","NO_ORGANIZATION_ID","NO_MEDIA_LIBRARY_ASPECTS_PATH","EXTERNAL_APP_NOT_SUPPORTED","APP_ID_NOT_FOUND_IN_ORGANIZATION"],"mappings":"AAAA,OAAO,MAAMA,gBAAgB,CAAC,8IAA8I,CAAC,CAAA;AAC7K,OAAO,MAAMC,qBAAqB,CAAC,yJAAyJ,CAAC,CAAA;AAC7L,OAAO,MAAMC,gCAAgC,CAAC,iJAAiJ,CAAC,CAAA;AAChM,OAAO,MAAMC,6BAA6B,CAAC,sDAAsD,CAAC,CAAA;AAClG,OAAO,MAAMC,mCAAmC,CAAC,yGAAyG,CAAC,CAAA"}
@@ -193,6 +193,12 @@
193
193
  "allowNo": true,
194
194
  "type": "boolean"
195
195
  },
196
+ "dry-run": {
197
+ "description": "Report what would be deployed without uploading or creating anything",
198
+ "name": "dry-run",
199
+ "allowNo": false,
200
+ "type": "boolean"
201
+ },
196
202
  "external": {
197
203
  "description": "Register an externally hosted studio",
198
204
  "exclusive": [
@@ -222,6 +228,13 @@
222
228
  "allowNo": false,
223
229
  "type": "boolean"
224
230
  },
231
+ "title": {
232
+ "description": "Title for a newly created application or studio. For apps it also skips the interactive title prompt, enabling unattended creation",
233
+ "name": "title",
234
+ "hasDynamicHelp": false,
235
+ "multiple": false,
236
+ "type": "option"
237
+ },
225
238
  "url": {
226
239
  "description": "Studio URL for deployment. For external studios, the full URL. For hosted studios, the hostname (e.g. \"my-studio\" or \"my-studio.sanity.studio\")",
227
240
  "name": "url",
@@ -5149,5 +5162,5 @@
5149
5162
  ]
5150
5163
  }
5151
5164
  },
5152
- "version": "7.4.2"
5165
+ "version": "7.6.0"
5153
5166
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/cli",
3
- "version": "7.4.2",
3
+ "version": "7.6.0",
4
4
  "description": "Sanity CLI tool for managing Sanity projects and organizations",
5
5
  "keywords": [
6
6
  "cli",
@@ -114,17 +114,17 @@
114
114
  "tinyglobby": "^0.2.17",
115
115
  "tsx": "^4.22.4",
116
116
  "typeid-js": "^1.2.0",
117
- "vite": "8.1.0",
117
+ "vite": "^8.1.3",
118
118
  "which": "^6.0.1",
119
119
  "yaml": "^2.9.0",
120
120
  "zod": "^4.4.3",
121
- "@sanity/cli-build": "^2.0.0",
122
- "@sanity/cli-core": "^2.1.2",
123
- "@sanity/workbench-cli": "^1.1.2"
121
+ "@sanity/cli-build": "^3.0.0",
122
+ "@sanity/cli-core": "^2.2.0",
123
+ "@sanity/workbench-cli": "^1.2.0"
124
124
  },
125
125
  "devDependencies": {
126
126
  "@eslint/compat": "^2.1.0",
127
- "@sanity/pkg-utils": "^10.5.8",
127
+ "@sanity/pkg-utils": "^10.8.1",
128
128
  "@sanity/ui": "^3.2.0",
129
129
  "@swc/cli": "^0.8.1",
130
130
  "@swc/core": "^1.15.41",
@@ -154,9 +154,9 @@
154
154
  "sanity": "^6.1.0",
155
155
  "typescript": "^5.9.3",
156
156
  "vitest": "^4.1.9",
157
- "@repo/package.config": "0.0.1",
158
157
  "@repo/tsconfig": "3.70.0",
159
- "@sanity/cli-test": "2.0.2",
158
+ "@repo/package.config": "0.0.1",
159
+ "@sanity/cli-test": "3.0.0",
160
160
  "@sanity/eslint-config-cli": "1.1.2"
161
161
  },
162
162
  "peerDependencies": {
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: sanity-app-sdk
3
+ description: Build features with the Sanity App SDK (@sanity/sdk-react). Use when adding components, fetching or editing Sanity content, or working with hooks like useDocuments, useDocument, useDocumentProjection, useEditDocument, or useQuery.
4
+ ---
5
+
6
+ # Sanity App SDK
7
+
8
+ ## Get the maintained guide first
9
+
10
+ If the Sanity MCP server is configured, call its `get_sanity_rules` tool with the `app-sdk` rule before writing SDK code. That rule is maintained by Sanity, is more detailed, and supersedes the notes below. The notes below are a fallback for when MCP is not available.
11
+
12
+ ## Picking a hook
13
+
14
+ - `useDocuments` / `usePaginatedDocuments`: lists of documents. Returns document handles, not full documents.
15
+ - `useDocumentProjection`: read specific fields from a handle, for display.
16
+ - `useDocument` plus `useEditDocument`: read and write a single document in real time.
17
+ - `useQuery`: raw GROQ. Use sparingly; prefer handles plus projections.
18
+
19
+ ## Document handles
20
+
21
+ Fetch handles first, then spread them into other hooks:
22
+
23
+ ```tsx
24
+ const {data} = useDocuments({documentType: 'article'})
25
+
26
+ // in a child component receiving one handle:
27
+ const {data: fields} = useDocumentProjection({...handle, projection: '{title}'})
28
+ ```
29
+
30
+ Use `documentId` as the React key when rendering lists, never the array index.
31
+
32
+ ## Suspense
33
+
34
+ Data hooks suspend while loading. Wrap every data-fetching component in `<Suspense>` with a fallback, keep one fetching hook per component, and always pass a `fallback` to `SanityApp`. All SDK hooks must be used inside `SanityApp`.
35
+
36
+ ## Editing
37
+
38
+ Write through `useEditDocument` on change so content stays in sync with the Content Lake:
39
+
40
+ ```tsx
41
+ const {data: title} = useDocument({...handle, path: 'title'})
42
+ const editTitle = useEditDocument({...handle, path: 'title'})
43
+ // <input value={title ?? ''} onChange={(e) => editTitle(e.currentTarget.value)} />
44
+ ```
45
+
46
+ Do not hold document field values in `useState` and save on submit. That pattern goes stale and loses concurrent edits.
47
+
48
+ ## Documentation
49
+
50
+ Fetch these for current detail rather than relying on the notes above:
51
+
52
+ - Best practices: https://www.sanity.io/docs/app-sdk/sdk-best-practices
53
+ - Editing documents: https://www.sanity.io/docs/app-sdk/editing-documents
54
+ - Configuration: https://www.sanity.io/docs/app-sdk/sdk-configuration
55
+ - Deployment: https://www.sanity.io/docs/app-sdk/sdk-deployment
56
+ - API reference with current signatures: https://reference.sanity.io/_sanity/sdk-react/
@@ -0,0 +1,40 @@
1
+ # AGENTS.md
2
+
3
+ Guidance for AI coding agents working in this repository.
4
+
5
+ ## What this is
6
+
7
+ A React application built with the Sanity App SDK (`@sanity/sdk-react`). It is not a Sanity Studio. The app reads and writes content in a Sanity project through SDK hooks, and runs inside the organization's Sanity Dashboard, in development and when deployed. The `sanity` CLI runs it with Vite under the hood.
8
+
9
+ ## Key files
10
+
11
+ - `src/App.tsx`: entry point. The `SanityApp` component takes a `config` array with `projectId` and `dataset`. All SDK hooks must be used inside `SanityApp`.
12
+ - `sanity.cli.ts`: CLI config with the organization ID and app entry path.
13
+
14
+ ## Commands
15
+
16
+ - `npm run dev`: starts the dev server on port 3333, but the app only renders inside the Sanity Dashboard. The CLI prints a Dashboard URL to open. Viewing it requires a signed-in Sanity account, so a human must complete authentication in the browser.
17
+ - `npm run build`: production build.
18
+ - `npm run deploy`: deploy to the Sanity Dashboard.
19
+
20
+ Environment variables prefixed with `SANITY_APP_` are bundled into the app.
21
+
22
+ ## Working with the App SDK
23
+
24
+ If the Sanity MCP server is available, call its `get_sanity_rules` tool with the `app-sdk` rule before writing SDK code. That rule is the maintained guide and supersedes the notes below.
25
+
26
+ Essentials:
27
+
28
+ - Data hooks suspend while loading. Wrap every data-fetching component in `<Suspense>`, keep one fetching hook per component, and always pass a `fallback` to `SanityApp`.
29
+ - Fetch lists with `useDocuments` (or `usePaginatedDocuments`). They return document handles, not full documents. Spread a handle into `useDocumentProjection` to display fields, or into `useDocument` and `useEditDocument` for real-time editing.
30
+ - Use `documentId` as the React key when rendering document lists, never the array index.
31
+ - Do not hold document field values in `useState` and save on submit. Write through `useEditDocument` on change so content stays in sync with the Content Lake.
32
+ - Prefer handles plus projections over raw GROQ. Reach for `useQuery` only when a complex query genuinely needs it.
33
+
34
+ ## Documentation
35
+
36
+ - App SDK docs: https://www.sanity.io/docs/app-sdk
37
+ - Best practices: https://www.sanity.io/docs/app-sdk/sdk-best-practices
38
+ - Editing documents: https://www.sanity.io/docs/app-sdk/editing-documents
39
+ - Configuration: https://www.sanity.io/docs/app-sdk/sdk-configuration
40
+ - API reference with current signatures: https://reference.sanity.io/_sanity/sdk-react/
@@ -0,0 +1,22 @@
1
+ # Sanity App
2
+
3
+ A custom application built with the [Sanity App SDK](https://www.sanity.io/docs/app-sdk?utm_source=readme). It is a React app that runs inside your organization's Sanity Dashboard, in development and when deployed.
4
+
5
+ ## Commands
6
+
7
+ - `npm run dev` starts the dev server and prints a Sanity Dashboard URL where your app runs. Open it and sign in with your Sanity account.
8
+ - `npm run build` builds the app for production.
9
+ - `npm run deploy` deploys the app to the Sanity Dashboard.
10
+
11
+ ## Configuration
12
+
13
+ - `src/App.tsx` is the app entry point. The `SanityApp` config sets which project and dataset the app reads content from.
14
+ - `sanity.cli.ts` holds your organization ID and the app entry path.
15
+
16
+ ## Learn more
17
+
18
+ - [App SDK Quickstart Guide](https://www.sanity.io/docs/app-sdk/sdk-quickstart?utm_source=readme)
19
+ - [App SDK documentation](https://www.sanity.io/docs/app-sdk?utm_source=readme)
20
+ - [API reference](https://reference.sanity.io/_sanity/sdk-react/)
21
+ - [Deploying your app](https://www.sanity.io/docs/app-sdk/sdk-deployment?utm_source=readme)
22
+ - [SDK Explorer with example apps](https://sdk-explorer.sanity.io)
@@ -88,3 +88,9 @@ p {
88
88
  border-left: 3px solid #e0e0e0;
89
89
  line-height: 1.5;
90
90
  }
91
+
92
+ .example-links {
93
+ margin-top: 1.5rem;
94
+ padding-inline-start: 1.25rem;
95
+ line-height: 1.8;
96
+ }
@@ -17,21 +17,42 @@ export function ExampleComponent() {
17
17
  Welcome to your Sanity App{user?.name ? `, ${user.name}` : ''}!
18
18
  </h1>
19
19
  <p className="example-text">
20
- This is an example component. You can replace this with your own content by creating a new
21
- component and importing it in App.tsx.
20
+ This is an example component, rendered with the <code>useCurrentUser</code> hook from the
21
+ App SDK. Replace it with your own components by importing them in App.tsx.
22
22
  </p>
23
23
  <div className="code-hint">
24
24
  <p>
25
- Quick tip: Create new components in separate files and import them like this in App.tsx /
26
- App.jsx:
25
+ A good next step is fetching content. Data hooks like <code>useDocuments</code> suspend
26
+ while loading, so render them inside a <code>{'<Suspense>'}</code> boundary:
27
27
  </p>
28
- <pre>{`import {YourComponent} from './YourComponent'
28
+ <pre>{`import {Suspense} from 'react'
29
+ import {useDocuments} from '@sanity/sdk-react'
29
30
 
30
- // Then use it in your JSX
31
- <SanityApp config={sanityConfigs}>
32
- <YourComponent />
33
- </SanityApp>`}</pre>
31
+ function DocumentList() {
32
+ // useDocuments returns handles you can pass to other hooks
33
+ const {data} = useDocuments({documentType: 'yourType'})
34
+ return <ul>{data.map((doc) => <li key={doc.documentId}>{doc.documentId}</li>)}</ul>
35
+ }
36
+
37
+ function App() {
38
+ return (
39
+ <Suspense fallback={<div>Loading...</div>}>
40
+ <DocumentList />
41
+ </Suspense>
42
+ )
43
+ }`}</pre>
34
44
  </div>
45
+ <ul className="example-links">
46
+ <li>
47
+ <a href="https://www.sanity.io/docs/app-sdk">App SDK documentation</a>
48
+ </li>
49
+ <li>
50
+ <a href="https://reference.sanity.io/_sanity/sdk-react/">API reference</a>
51
+ </li>
52
+ <li>
53
+ <a href="https://sdk-explorer.sanity.io">SDK Explorer with example apps</a>
54
+ </li>
55
+ </ul>
35
56
  </div>
36
57
  )
37
58
  }
@@ -0,0 +1,60 @@
1
+ ---
2
+ name: sanity-app-sdk
3
+ description: Build features with the Sanity App SDK (@sanity/sdk-react) and Sanity UI. Use when adding components, fetching or editing Sanity content, or working with hooks like useDocuments, useDocument, useDocumentProjection, useEditDocument, or useQuery.
4
+ ---
5
+
6
+ # Sanity App SDK
7
+
8
+ ## Get the maintained guide first
9
+
10
+ If the Sanity MCP server is configured, call its `get_sanity_rules` tool with the `app-sdk` rule before writing SDK code. That rule is maintained by Sanity, is more detailed, and supersedes the notes below. The notes below are a fallback for when MCP is not available.
11
+
12
+ ## Picking a hook
13
+
14
+ - `useDocuments` / `usePaginatedDocuments`: lists of documents. Returns document handles, not full documents.
15
+ - `useDocumentProjection`: read specific fields from a handle, for display.
16
+ - `useDocument` plus `useEditDocument`: read and write a single document in real time.
17
+ - `useQuery`: raw GROQ. Use sparingly; prefer handles plus projections.
18
+
19
+ ## Document handles
20
+
21
+ Fetch handles first, then spread them into other hooks:
22
+
23
+ ```tsx
24
+ const {data} = useDocuments({documentType: 'article'})
25
+
26
+ // in a child component receiving one handle:
27
+ const {data: fields} = useDocumentProjection({...handle, projection: '{title}'})
28
+ ```
29
+
30
+ Use `documentId` as the React key when rendering lists, never the array index.
31
+
32
+ ## Suspense
33
+
34
+ Data hooks suspend while loading. Wrap every data-fetching component in `<Suspense>` with a fallback, keep one fetching hook per component, and always pass a `fallback` to `SanityApp`. All SDK hooks must be used inside `SanityApp`.
35
+
36
+ ## Editing
37
+
38
+ Write through `useEditDocument` on change so content stays in sync with the Content Lake:
39
+
40
+ ```tsx
41
+ const {data: title} = useDocument({...handle, path: 'title'})
42
+ const editTitle = useEditDocument({...handle, path: 'title'})
43
+ // <input value={title ?? ''} onChange={(e) => editTitle(e.currentTarget.value)} />
44
+ ```
45
+
46
+ Do not hold document field values in `useState` and save on submit. That pattern goes stale and loses concurrent edits.
47
+
48
+ ## Sanity UI
49
+
50
+ This app wraps everything in Sanity UI's `ThemeProvider` (see `src/SanityUI.tsx`, built with `buildTheme()`). Build UI with Sanity UI primitives like `Card`, `Stack`, `Flex`, `Text`, and `Button`. See https://www.sanity.io/docs/app-sdk/sanity-ui-sdk and https://www.sanity.io/ui.
51
+
52
+ ## Documentation
53
+
54
+ Fetch these for current detail rather than relying on the notes above:
55
+
56
+ - Best practices: https://www.sanity.io/docs/app-sdk/sdk-best-practices
57
+ - Editing documents: https://www.sanity.io/docs/app-sdk/editing-documents
58
+ - Configuration: https://www.sanity.io/docs/app-sdk/sdk-configuration
59
+ - Deployment: https://www.sanity.io/docs/app-sdk/sdk-deployment
60
+ - API reference with current signatures: https://reference.sanity.io/_sanity/sdk-react/