@sanity/cli 6.3.1 → 6.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 (193) hide show
  1. package/README.md +311 -452
  2. package/dist/actions/build/decorateIndexWithStagingScript.js +16 -0
  3. package/dist/actions/build/decorateIndexWithStagingScript.js.map +1 -0
  4. package/dist/actions/build/writeSanityRuntime.js +3 -2
  5. package/dist/actions/build/writeSanityRuntime.js.map +1 -1
  6. package/dist/actions/dataset/create.js +4 -0
  7. package/dist/actions/dataset/create.js.map +1 -1
  8. package/dist/actions/deploy/findUserApplicationForApp.js +1 -0
  9. package/dist/actions/deploy/findUserApplicationForApp.js.map +1 -1
  10. package/dist/actions/deploy/types.js +1 -1
  11. package/dist/actions/deploy/types.js.map +1 -1
  12. package/dist/actions/init/bootstrapLocalTemplate.js +16 -1
  13. package/dist/actions/init/bootstrapLocalTemplate.js.map +1 -1
  14. package/dist/actions/init/initApp.js +72 -0
  15. package/dist/actions/init/initApp.js.map +1 -0
  16. package/dist/actions/init/initHelpers.js +37 -0
  17. package/dist/actions/init/initHelpers.js.map +1 -0
  18. package/dist/actions/init/initNextJs.js +246 -0
  19. package/dist/actions/init/initNextJs.js.map +1 -0
  20. package/dist/actions/init/initStudio.js +127 -0
  21. package/dist/actions/init/initStudio.js.map +1 -0
  22. package/dist/actions/init/scaffoldTemplate.js +114 -0
  23. package/dist/actions/init/scaffoldTemplate.js.map +1 -0
  24. package/dist/actions/init/templates/appQuickstart.js +2 -1
  25. package/dist/actions/init/templates/appQuickstart.js.map +1 -1
  26. package/dist/actions/init/templates/appSanityUi.js +2 -1
  27. package/dist/actions/init/templates/appSanityUi.js.map +1 -1
  28. package/dist/actions/init/templates/nextjs/index.js +1 -2
  29. package/dist/actions/init/templates/nextjs/index.js.map +1 -1
  30. package/dist/actions/init/templates/shopify.js +6 -6
  31. package/dist/actions/init/templates/shopify.js.map +1 -1
  32. package/dist/actions/init/templates/shopifyOnline.js +2 -2
  33. package/dist/actions/init/templates/shopifyOnline.js.map +1 -1
  34. package/dist/actions/manifest/types.js +1 -1
  35. package/dist/actions/manifest/types.js.map +1 -1
  36. package/dist/actions/mcp/detectAvailableEditors.js +16 -3
  37. package/dist/actions/mcp/detectAvailableEditors.js.map +1 -1
  38. package/dist/actions/mcp/editorConfigs.js +192 -132
  39. package/dist/actions/mcp/editorConfigs.js.map +1 -1
  40. package/dist/actions/mcp/setupMCP.js +4 -1
  41. package/dist/actions/mcp/setupMCP.js.map +1 -1
  42. package/dist/actions/mcp/writeMCPConfig.js +2 -2
  43. package/dist/actions/mcp/writeMCPConfig.js.map +1 -1
  44. package/dist/actions/schema/extractSchema.js +5 -7
  45. package/dist/actions/schema/extractSchema.js.map +1 -1
  46. package/dist/actions/schema/types.js +3 -3
  47. package/dist/actions/schema/types.js.map +1 -1
  48. package/dist/actions/users/validateEmail.js +2 -2
  49. package/dist/actions/users/validateEmail.js.map +1 -1
  50. package/dist/commands/backups/disable.js +1 -1
  51. package/dist/commands/backups/disable.js.map +1 -1
  52. package/dist/commands/backups/download.js +1 -1
  53. package/dist/commands/backups/download.js.map +1 -1
  54. package/dist/commands/backups/enable.js +1 -1
  55. package/dist/commands/backups/enable.js.map +1 -1
  56. package/dist/commands/backups/list.js +1 -1
  57. package/dist/commands/backups/list.js.map +1 -1
  58. package/dist/commands/build.js +1 -1
  59. package/dist/commands/build.js.map +1 -1
  60. package/dist/commands/cors/add.js +1 -1
  61. package/dist/commands/cors/add.js.map +1 -1
  62. package/dist/commands/cors/delete.js +1 -1
  63. package/dist/commands/cors/delete.js.map +1 -1
  64. package/dist/commands/cors/list.js +2 -2
  65. package/dist/commands/cors/list.js.map +1 -1
  66. package/dist/commands/datasets/alias/create.js +1 -1
  67. package/dist/commands/datasets/alias/create.js.map +1 -1
  68. package/dist/commands/datasets/alias/delete.js +1 -1
  69. package/dist/commands/datasets/alias/delete.js.map +1 -1
  70. package/dist/commands/datasets/alias/link.js +1 -1
  71. package/dist/commands/datasets/alias/link.js.map +1 -1
  72. package/dist/commands/datasets/alias/unlink.js +1 -1
  73. package/dist/commands/datasets/alias/unlink.js.map +1 -1
  74. package/dist/commands/datasets/copy.js +15 -1
  75. package/dist/commands/datasets/copy.js.map +1 -1
  76. package/dist/commands/datasets/create.js +1 -1
  77. package/dist/commands/datasets/create.js.map +1 -1
  78. package/dist/commands/datasets/delete.js +1 -1
  79. package/dist/commands/datasets/delete.js.map +1 -1
  80. package/dist/commands/datasets/embeddings/enable.js +11 -0
  81. package/dist/commands/datasets/embeddings/enable.js.map +1 -1
  82. package/dist/commands/datasets/export.js +2 -2
  83. package/dist/commands/datasets/export.js.map +1 -1
  84. package/dist/commands/datasets/list.js +2 -2
  85. package/dist/commands/datasets/list.js.map +1 -1
  86. package/dist/commands/debug.js +1 -1
  87. package/dist/commands/debug.js.map +1 -1
  88. package/dist/commands/deploy.js +3 -3
  89. package/dist/commands/deploy.js.map +1 -1
  90. package/dist/commands/dev.js +5 -5
  91. package/dist/commands/dev.js.map +1 -1
  92. package/dist/commands/docs/browse.js +1 -1
  93. package/dist/commands/docs/browse.js.map +1 -1
  94. package/dist/commands/documents/delete.js +1 -1
  95. package/dist/commands/documents/delete.js.map +1 -1
  96. package/dist/commands/exec.js +2 -2
  97. package/dist/commands/exec.js.map +1 -1
  98. package/dist/commands/graphql/deploy.js +2 -2
  99. package/dist/commands/graphql/deploy.js.map +1 -1
  100. package/dist/commands/graphql/list.js +2 -2
  101. package/dist/commands/graphql/list.js.map +1 -1
  102. package/dist/commands/hooks/create.js +2 -2
  103. package/dist/commands/hooks/create.js.map +1 -1
  104. package/dist/commands/hooks/delete.js +5 -5
  105. package/dist/commands/hooks/delete.js.map +1 -1
  106. package/dist/commands/hooks/list.js +3 -3
  107. package/dist/commands/hooks/list.js.map +1 -1
  108. package/dist/commands/hooks/logs.js +5 -5
  109. package/dist/commands/hooks/logs.js.map +1 -1
  110. package/dist/commands/init.js +175 -490
  111. package/dist/commands/init.js.map +1 -1
  112. package/dist/commands/install.js +1 -1
  113. package/dist/commands/install.js.map +1 -1
  114. package/dist/commands/learn.js +1 -1
  115. package/dist/commands/learn.js.map +1 -1
  116. package/dist/commands/login.js +1 -1
  117. package/dist/commands/login.js.map +1 -1
  118. package/dist/commands/logout.js +1 -1
  119. package/dist/commands/logout.js.map +1 -1
  120. package/dist/commands/manage.js +1 -1
  121. package/dist/commands/manage.js.map +1 -1
  122. package/dist/commands/manifest/extract.js +2 -2
  123. package/dist/commands/manifest/extract.js.map +1 -1
  124. package/dist/commands/mcp/configure.js +1 -1
  125. package/dist/commands/mcp/configure.js.map +1 -1
  126. package/dist/commands/media/delete-aspect.js +1 -1
  127. package/dist/commands/media/delete-aspect.js.map +1 -1
  128. package/dist/commands/media/export.js +1 -1
  129. package/dist/commands/media/export.js.map +1 -1
  130. package/dist/commands/preview.js +3 -3
  131. package/dist/commands/preview.js.map +1 -1
  132. package/dist/commands/projects/list.js +4 -2
  133. package/dist/commands/projects/list.js.map +1 -1
  134. package/dist/commands/schemas/deploy.js +3 -4
  135. package/dist/commands/schemas/deploy.js.map +1 -1
  136. package/dist/commands/schemas/extract.js +3 -3
  137. package/dist/commands/schemas/extract.js.map +1 -1
  138. package/dist/commands/schemas/list.js +4 -5
  139. package/dist/commands/schemas/list.js.map +1 -1
  140. package/dist/commands/telemetry/disable.js +2 -2
  141. package/dist/commands/telemetry/disable.js.map +1 -1
  142. package/dist/commands/telemetry/enable.js +2 -2
  143. package/dist/commands/telemetry/enable.js.map +1 -1
  144. package/dist/commands/telemetry/status.js +2 -2
  145. package/dist/commands/telemetry/status.js.map +1 -1
  146. package/dist/commands/tokens/add.js +1 -1
  147. package/dist/commands/tokens/add.js.map +1 -1
  148. package/dist/commands/tokens/delete.js +1 -1
  149. package/dist/commands/tokens/delete.js.map +1 -1
  150. package/dist/commands/tokens/list.js +2 -2
  151. package/dist/commands/tokens/list.js.map +1 -1
  152. package/dist/commands/users/list.js +1 -1
  153. package/dist/commands/users/list.js.map +1 -1
  154. package/dist/commands/versions.js +1 -1
  155. package/dist/commands/versions.js.map +1 -1
  156. package/dist/hooks/prerun/injectEnvVariables.js +3 -5
  157. package/dist/hooks/prerun/injectEnvVariables.js.map +1 -1
  158. package/dist/server/vite/plugin-sanity-build-entries.js +3 -2
  159. package/dist/server/vite/plugin-sanity-build-entries.js.map +1 -1
  160. package/dist/services/datasets.js +2 -1
  161. package/dist/services/datasets.js.map +1 -1
  162. package/dist/telemetry/init.telemetry.js.map +1 -1
  163. package/dist/util/packageManager/installationInfo/detectPackages.js +13 -7
  164. package/dist/util/packageManager/installationInfo/detectPackages.js.map +1 -1
  165. package/dist/util/telemetry/createTelemetryStore.js +27 -12
  166. package/dist/util/telemetry/createTelemetryStore.js.map +1 -1
  167. package/dist/util/update/fetchUpdateInfo.js +40 -0
  168. package/dist/util/update/fetchUpdateInfo.js.map +1 -0
  169. package/dist/util/update/fetchUpdateInfo.worker.js +19 -0
  170. package/dist/util/update/fetchUpdateInfo.worker.js.map +1 -0
  171. package/dist/util/update/getRunnerUpdateCommand.js +33 -0
  172. package/dist/util/update/getRunnerUpdateCommand.js.map +1 -0
  173. package/dist/util/update/getUpdateCommand.js +6 -7
  174. package/dist/util/update/getUpdateCommand.js.map +1 -1
  175. package/dist/util/update/packageRunner.js +10 -0
  176. package/dist/util/update/packageRunner.js.map +1 -0
  177. package/dist/util/update/resolveRunnerPackage.js +45 -0
  178. package/dist/util/update/resolveRunnerPackage.js.map +1 -0
  179. package/dist/util/update/resolveUpdateTarget.js +31 -0
  180. package/dist/util/update/resolveUpdateTarget.js.map +1 -0
  181. package/dist/util/update/showNotificationUpdate.js +8 -6
  182. package/dist/util/update/showNotificationUpdate.js.map +1 -1
  183. package/dist/util/update/updateChecker.js +73 -38
  184. package/dist/util/update/updateChecker.js.map +1 -1
  185. package/dist/util/validateProjection.js +121 -0
  186. package/dist/util/validateProjection.js.map +1 -0
  187. package/oclif.manifest.json +698 -681
  188. package/package.json +24 -23
  189. package/templates/app-quickstart/src/App.tsx +2 -2
  190. package/templates/app-sanity-ui/src/App.tsx +2 -2
  191. package/templates/shopify/schemaTypes/objects/hotspot/imageWithProductHotspotsType.ts +1 -1
  192. package/dist/util/update/fetchLatestVersion.js +0 -21
  193. package/dist/util/update/fetchLatestVersion.js.map +0 -1
@@ -21,6 +21,8 @@ import { telemetryStoreDebug } from './telemetryStoreDebug.js';
21
21
  telemetryStoreDebug('Creating telemetry store with sessionId: %s', sessionId);
22
22
  let cachedConsent = null;
23
23
  let filePath = null;
24
+ let initComplete = false;
25
+ const eventBuffer = [];
24
26
  const initializeConsent = async ()=>{
25
27
  if (cachedConsent) return;
26
28
  try {
@@ -48,34 +50,37 @@ import { telemetryStoreDebug } from './telemetryStoreDebug.js';
48
50
  filePath = null;
49
51
  }
50
52
  };
51
- const emit = (event)=>{
52
- if (!cachedConsent || cachedConsent.status !== 'granted') {
53
- if (cachedConsent) {
54
- telemetryStoreDebug('Cached consent not granted (%s), skipping event: %s', cachedConsent.status, event.type);
55
- } else {
56
- telemetryStoreDebug('Consent not resolved, skipping event: %s', event.type);
57
- }
58
- return;
59
- }
53
+ const writeEvent = (event)=>{
60
54
  if (!filePath) {
61
- telemetryStoreDebug('File path not initialized, skipping event: %s', event.type);
55
+ telemetryStoreDebug('No file path, skipping event: %s', event.type);
62
56
  return;
63
57
  }
64
58
  telemetryStoreDebug('Emitting event: %s', event.type);
65
59
  try {
66
- const eventLine = JSON.stringify(event) + '\n';
67
60
  // We use synchronous file writes to ensure telemetry events are captured even when
68
61
  // the process exits abruptly (process.exit, uncaught exceptions, SIGTERM, etc.).
69
62
  // The performance impact is probably negligible and is worth the trade-off
70
63
  // for 100% reliability. Async writes would be lost when the event loop
71
64
  // shuts down during process exit.
72
- appendFileSync(filePath, eventLine, 'utf8');
65
+ appendFileSync(filePath, JSON.stringify(event) + '\n', 'utf8');
73
66
  telemetryStoreDebug('Successfully wrote event to file: %s', filePath);
74
67
  } catch (error) {
75
68
  telemetryStoreDebug('Failed to write telemetry event: %o', error);
76
69
  // Silent failure - don't break CLI functionality
77
70
  }
78
71
  };
72
+ const emit = (event)=>{
73
+ if (!initComplete) {
74
+ telemetryStoreDebug('Init pending, buffering event: %s', event.type);
75
+ eventBuffer.push(event);
76
+ return;
77
+ }
78
+ if (!cachedConsent || cachedConsent.status !== 'granted') {
79
+ telemetryStoreDebug('Consent not granted (%s), skipping event: %s', cachedConsent?.status ?? 'unresolved', event.type);
80
+ return;
81
+ }
82
+ writeEvent(event);
83
+ };
79
84
  const logger = createLogger(sessionId, emit);
80
85
  // Initialize both consent and file path concurrently
81
86
  Promise.allSettled([
@@ -88,6 +93,16 @@ import { telemetryStoreDebug } from './telemetryStoreDebug.js';
88
93
  telemetryStoreDebug('Error initializing %s: %o', type, result.reason);
89
94
  }
90
95
  }
96
+ initComplete = true;
97
+ if (cachedConsent?.status === 'granted' && filePath) {
98
+ telemetryStoreDebug('Flushing %d buffered event(s)', eventBuffer.length);
99
+ for (const event of eventBuffer){
100
+ writeEvent(event);
101
+ }
102
+ } else {
103
+ telemetryStoreDebug('Discarding %d buffered event(s), consent: %s', eventBuffer.length, cachedConsent?.status ?? 'unresolved');
104
+ }
105
+ eventBuffer.length = 0;
91
106
  });
92
107
  return logger;
93
108
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/util/telemetry/createTelemetryStore.ts"],"sourcesContent":["import {appendFileSync} from 'node:fs'\nimport {mkdir} from 'node:fs/promises'\nimport {dirname} from 'node:path'\n\nimport {\n type CLITelemetryStore,\n type ConsentInformation,\n type TelemetryUserProperties,\n} from '@sanity/cli-core'\nimport {type TelemetryEvent} from '@sanity/telemetry'\n\nimport {generateTelemetryFilePath} from './generateTelemetryFilePath.js'\nimport {createLogger} from './logger.js'\nimport {telemetryStoreDebug} from './telemetryStoreDebug.js'\n\n/**\n * FILE MANAGEMENT STRATEGY:\n *\n * The telemetry system uses a multi-file approach to handle concurrent CLI processes:\n *\n * 1. WRITING (per session):\n * - Each CLI session gets a unique file: telemetry-\\{hash\\}-\\{env\\}-\\{sessionId\\}.ndjson\n * - Prevents write conflicts when multiple CLI commands run simultaneously\n * - Events are written using an RxJS queue for ordered processing with retry logic\n *\n * 2. FLUSHING (aggregate all sessions):\n * - findTelemetryFiles() discovers ALL telemetry files for user/environment\n * - Events are collected from all session files and sent as a batch\n * - Files are deleted after successful transmission\n *\n * 3. CLEANUP (background maintenance):\n * - cleanupOldTelemetryFiles() removes stale files older than 7 days\n * - Prevents disk space accumulation from abandoned sessions\n */\n\ninterface CreateTelemetryStoreOptions {\n resolveConsent: () => Promise<ConsentInformation>\n}\n\n/**\n * Creates a file-based telemetry store with cached consent and reliable synchronous I/O.\n *\n * Key optimizations:\n * - Consent resolved once at creation and cached (vs checking on every emit)\n * - File path generated and directory created once during initialization\n * - Synchronous file writes to ensure events are captured even during process exit\n *\n * @param sessionId - Unique session identifier for file isolation\n * @param options - Configuration options\n * @returns TelemetryStore instance compatible with the telemetry interface\n *\n * @internal\n */\nexport function createTelemetryStore(\n sessionId: string,\n options: CreateTelemetryStoreOptions,\n): CLITelemetryStore {\n telemetryStoreDebug('Creating telemetry store with sessionId: %s', sessionId)\n\n let cachedConsent: ConsentInformation | null = null\n let filePath: string | null = null\n\n const initializeConsent = async () => {\n if (cachedConsent) return\n\n try {\n cachedConsent = await options.resolveConsent()\n telemetryStoreDebug('Cached consent status: %s', cachedConsent.status)\n } catch (error) {\n telemetryStoreDebug('Failed to initialize consent, treating as undetermined: %o', error)\n cachedConsent = {reason: 'fetchError', status: 'undetermined'}\n }\n }\n\n const initializeFilePath = async () => {\n if (filePath) return\n\n try {\n filePath = await generateTelemetryFilePath(sessionId)\n telemetryStoreDebug('Generated file path: %s', filePath)\n\n await mkdir(dirname(filePath), {recursive: true})\n telemetryStoreDebug('Created directory structure for: %s', filePath)\n } catch (error) {\n telemetryStoreDebug('Failed to initialize file path: %o', error)\n filePath = null\n }\n }\n\n const emit = (event: TelemetryEvent) => {\n if (!cachedConsent || cachedConsent.status !== 'granted') {\n if (cachedConsent) {\n telemetryStoreDebug(\n 'Cached consent not granted (%s), skipping event: %s',\n cachedConsent.status,\n event.type,\n )\n } else {\n telemetryStoreDebug('Consent not resolved, skipping event: %s', event.type)\n }\n return\n }\n\n if (!filePath) {\n telemetryStoreDebug('File path not initialized, skipping event: %s', event.type)\n return\n }\n\n telemetryStoreDebug('Emitting event: %s', event.type)\n\n try {\n const eventLine = JSON.stringify(event) + '\\n'\n\n // We use synchronous file writes to ensure telemetry events are captured even when\n // the process exits abruptly (process.exit, uncaught exceptions, SIGTERM, etc.).\n // The performance impact is probably negligible and is worth the trade-off\n // for 100% reliability. Async writes would be lost when the event loop\n // shuts down during process exit.\n appendFileSync(filePath, eventLine, 'utf8')\n telemetryStoreDebug('Successfully wrote event to file: %s', filePath)\n } catch (error) {\n telemetryStoreDebug('Failed to write telemetry event: %o', error)\n // Silent failure - don't break CLI functionality\n }\n }\n\n const logger = createLogger<TelemetryUserProperties>(sessionId, emit)\n\n // Initialize both consent and file path concurrently\n Promise.allSettled([initializeConsent(), initializeFilePath()]).then((results) => {\n for (const [index, result] of results.entries()) {\n if (result.status === 'rejected') {\n const type = index === 0 ? 'consent' : 'file path'\n telemetryStoreDebug('Error initializing %s: %o', type, result.reason)\n }\n }\n })\n\n return logger\n}\n"],"names":["appendFileSync","mkdir","dirname","generateTelemetryFilePath","createLogger","telemetryStoreDebug","createTelemetryStore","sessionId","options","cachedConsent","filePath","initializeConsent","resolveConsent","status","error","reason","initializeFilePath","recursive","emit","event","type","eventLine","JSON","stringify","logger","Promise","allSettled","then","results","index","result","entries"],"mappings":"AAAA,SAAQA,cAAc,QAAO,UAAS;AACtC,SAAQC,KAAK,QAAO,mBAAkB;AACtC,SAAQC,OAAO,QAAO,YAAW;AASjC,SAAQC,yBAAyB,QAAO,iCAAgC;AACxE,SAAQC,YAAY,QAAO,cAAa;AACxC,SAAQC,mBAAmB,QAAO,2BAA0B;AA0B5D;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASC,qBACdC,SAAiB,EACjBC,OAAoC;IAEpCH,oBAAoB,+CAA+CE;IAEnE,IAAIE,gBAA2C;IAC/C,IAAIC,WAA0B;IAE9B,MAAMC,oBAAoB;QACxB,IAAIF,eAAe;QAEnB,IAAI;YACFA,gBAAgB,MAAMD,QAAQI,cAAc;YAC5CP,oBAAoB,6BAA6BI,cAAcI,MAAM;QACvE,EAAE,OAAOC,OAAO;YACdT,oBAAoB,8DAA8DS;YAClFL,gBAAgB;gBAACM,QAAQ;gBAAcF,QAAQ;YAAc;QAC/D;IACF;IAEA,MAAMG,qBAAqB;QACzB,IAAIN,UAAU;QAEd,IAAI;YACFA,WAAW,MAAMP,0BAA0BI;YAC3CF,oBAAoB,2BAA2BK;YAE/C,MAAMT,MAAMC,QAAQQ,WAAW;gBAACO,WAAW;YAAI;YAC/CZ,oBAAoB,uCAAuCK;QAC7D,EAAE,OAAOI,OAAO;YACdT,oBAAoB,sCAAsCS;YAC1DJ,WAAW;QACb;IACF;IAEA,MAAMQ,OAAO,CAACC;QACZ,IAAI,CAACV,iBAAiBA,cAAcI,MAAM,KAAK,WAAW;YACxD,IAAIJ,eAAe;gBACjBJ,oBACE,uDACAI,cAAcI,MAAM,EACpBM,MAAMC,IAAI;YAEd,OAAO;gBACLf,oBAAoB,4CAA4Cc,MAAMC,IAAI;YAC5E;YACA;QACF;QAEA,IAAI,CAACV,UAAU;YACbL,oBAAoB,iDAAiDc,MAAMC,IAAI;YAC/E;QACF;QAEAf,oBAAoB,sBAAsBc,MAAMC,IAAI;QAEpD,IAAI;YACF,MAAMC,YAAYC,KAAKC,SAAS,CAACJ,SAAS;YAE1C,mFAAmF;YACnF,iFAAiF;YACjF,2EAA2E;YAC3E,uEAAuE;YACvE,kCAAkC;YAClCnB,eAAeU,UAAUW,WAAW;YACpChB,oBAAoB,wCAAwCK;QAC9D,EAAE,OAAOI,OAAO;YACdT,oBAAoB,uCAAuCS;QAC3D,iDAAiD;QACnD;IACF;IAEA,MAAMU,SAASpB,aAAsCG,WAAWW;IAEhE,qDAAqD;IACrDO,QAAQC,UAAU,CAAC;QAACf;QAAqBK;KAAqB,EAAEW,IAAI,CAAC,CAACC;QACpE,KAAK,MAAM,CAACC,OAAOC,OAAO,IAAIF,QAAQG,OAAO,GAAI;YAC/C,IAAID,OAAOjB,MAAM,KAAK,YAAY;gBAChC,MAAMO,OAAOS,UAAU,IAAI,YAAY;gBACvCxB,oBAAoB,6BAA6Be,MAAMU,OAAOf,MAAM;YACtE;QACF;IACF;IAEA,OAAOS;AACT"}
1
+ {"version":3,"sources":["../../../src/util/telemetry/createTelemetryStore.ts"],"sourcesContent":["import {appendFileSync} from 'node:fs'\nimport {mkdir} from 'node:fs/promises'\nimport {dirname} from 'node:path'\n\nimport {\n type CLITelemetryStore,\n type ConsentInformation,\n type TelemetryUserProperties,\n} from '@sanity/cli-core'\nimport {type TelemetryEvent} from '@sanity/telemetry'\n\nimport {generateTelemetryFilePath} from './generateTelemetryFilePath.js'\nimport {createLogger} from './logger.js'\nimport {telemetryStoreDebug} from './telemetryStoreDebug.js'\n\n/**\n * FILE MANAGEMENT STRATEGY:\n *\n * The telemetry system uses a multi-file approach to handle concurrent CLI processes:\n *\n * 1. WRITING (per session):\n * - Each CLI session gets a unique file: telemetry-\\{hash\\}-\\{env\\}-\\{sessionId\\}.ndjson\n * - Prevents write conflicts when multiple CLI commands run simultaneously\n * - Events are written using an RxJS queue for ordered processing with retry logic\n *\n * 2. FLUSHING (aggregate all sessions):\n * - findTelemetryFiles() discovers ALL telemetry files for user/environment\n * - Events are collected from all session files and sent as a batch\n * - Files are deleted after successful transmission\n *\n * 3. CLEANUP (background maintenance):\n * - cleanupOldTelemetryFiles() removes stale files older than 7 days\n * - Prevents disk space accumulation from abandoned sessions\n */\n\ninterface CreateTelemetryStoreOptions {\n resolveConsent: () => Promise<ConsentInformation>\n}\n\n/**\n * Creates a file-based telemetry store with cached consent and reliable synchronous I/O.\n *\n * Key optimizations:\n * - Consent resolved once at creation and cached (vs checking on every emit)\n * - File path generated and directory created once during initialization\n * - Synchronous file writes to ensure events are captured even during process exit\n *\n * @param sessionId - Unique session identifier for file isolation\n * @param options - Configuration options\n * @returns TelemetryStore instance compatible with the telemetry interface\n *\n * @internal\n */\nexport function createTelemetryStore(\n sessionId: string,\n options: CreateTelemetryStoreOptions,\n): CLITelemetryStore {\n telemetryStoreDebug('Creating telemetry store with sessionId: %s', sessionId)\n\n let cachedConsent: ConsentInformation | null = null\n let filePath: string | null = null\n let initComplete = false\n const eventBuffer: TelemetryEvent[] = []\n\n const initializeConsent = async () => {\n if (cachedConsent) return\n\n try {\n cachedConsent = await options.resolveConsent()\n telemetryStoreDebug('Cached consent status: %s', cachedConsent.status)\n } catch (error) {\n telemetryStoreDebug('Failed to initialize consent, treating as undetermined: %o', error)\n cachedConsent = {reason: 'fetchError', status: 'undetermined'}\n }\n }\n\n const initializeFilePath = async () => {\n if (filePath) return\n\n try {\n filePath = await generateTelemetryFilePath(sessionId)\n telemetryStoreDebug('Generated file path: %s', filePath)\n\n await mkdir(dirname(filePath), {recursive: true})\n telemetryStoreDebug('Created directory structure for: %s', filePath)\n } catch (error) {\n telemetryStoreDebug('Failed to initialize file path: %o', error)\n filePath = null\n }\n }\n\n const writeEvent = (event: TelemetryEvent) => {\n if (!filePath) {\n telemetryStoreDebug('No file path, skipping event: %s', event.type)\n return\n }\n\n telemetryStoreDebug('Emitting event: %s', event.type)\n\n try {\n // We use synchronous file writes to ensure telemetry events are captured even when\n // the process exits abruptly (process.exit, uncaught exceptions, SIGTERM, etc.).\n // The performance impact is probably negligible and is worth the trade-off\n // for 100% reliability. Async writes would be lost when the event loop\n // shuts down during process exit.\n appendFileSync(filePath, JSON.stringify(event) + '\\n', 'utf8')\n telemetryStoreDebug('Successfully wrote event to file: %s', filePath)\n } catch (error) {\n telemetryStoreDebug('Failed to write telemetry event: %o', error)\n // Silent failure - don't break CLI functionality\n }\n }\n\n const emit = (event: TelemetryEvent) => {\n if (!initComplete) {\n telemetryStoreDebug('Init pending, buffering event: %s', event.type)\n eventBuffer.push(event)\n return\n }\n\n if (!cachedConsent || cachedConsent.status !== 'granted') {\n telemetryStoreDebug(\n 'Consent not granted (%s), skipping event: %s',\n cachedConsent?.status ?? 'unresolved',\n event.type,\n )\n return\n }\n\n writeEvent(event)\n }\n\n const logger = createLogger<TelemetryUserProperties>(sessionId, emit)\n\n // Initialize both consent and file path concurrently\n Promise.allSettled([initializeConsent(), initializeFilePath()]).then((results) => {\n for (const [index, result] of results.entries()) {\n if (result.status === 'rejected') {\n const type = index === 0 ? 'consent' : 'file path'\n telemetryStoreDebug('Error initializing %s: %o', type, result.reason)\n }\n }\n\n initComplete = true\n\n if (cachedConsent?.status === 'granted' && filePath) {\n telemetryStoreDebug('Flushing %d buffered event(s)', eventBuffer.length)\n for (const event of eventBuffer) {\n writeEvent(event)\n }\n } else {\n telemetryStoreDebug(\n 'Discarding %d buffered event(s), consent: %s',\n eventBuffer.length,\n cachedConsent?.status ?? 'unresolved',\n )\n }\n eventBuffer.length = 0\n })\n\n return logger\n}\n"],"names":["appendFileSync","mkdir","dirname","generateTelemetryFilePath","createLogger","telemetryStoreDebug","createTelemetryStore","sessionId","options","cachedConsent","filePath","initComplete","eventBuffer","initializeConsent","resolveConsent","status","error","reason","initializeFilePath","recursive","writeEvent","event","type","JSON","stringify","emit","push","logger","Promise","allSettled","then","results","index","result","entries","length"],"mappings":"AAAA,SAAQA,cAAc,QAAO,UAAS;AACtC,SAAQC,KAAK,QAAO,mBAAkB;AACtC,SAAQC,OAAO,QAAO,YAAW;AASjC,SAAQC,yBAAyB,QAAO,iCAAgC;AACxE,SAAQC,YAAY,QAAO,cAAa;AACxC,SAAQC,mBAAmB,QAAO,2BAA0B;AA0B5D;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASC,qBACdC,SAAiB,EACjBC,OAAoC;IAEpCH,oBAAoB,+CAA+CE;IAEnE,IAAIE,gBAA2C;IAC/C,IAAIC,WAA0B;IAC9B,IAAIC,eAAe;IACnB,MAAMC,cAAgC,EAAE;IAExC,MAAMC,oBAAoB;QACxB,IAAIJ,eAAe;QAEnB,IAAI;YACFA,gBAAgB,MAAMD,QAAQM,cAAc;YAC5CT,oBAAoB,6BAA6BI,cAAcM,MAAM;QACvE,EAAE,OAAOC,OAAO;YACdX,oBAAoB,8DAA8DW;YAClFP,gBAAgB;gBAACQ,QAAQ;gBAAcF,QAAQ;YAAc;QAC/D;IACF;IAEA,MAAMG,qBAAqB;QACzB,IAAIR,UAAU;QAEd,IAAI;YACFA,WAAW,MAAMP,0BAA0BI;YAC3CF,oBAAoB,2BAA2BK;YAE/C,MAAMT,MAAMC,QAAQQ,WAAW;gBAACS,WAAW;YAAI;YAC/Cd,oBAAoB,uCAAuCK;QAC7D,EAAE,OAAOM,OAAO;YACdX,oBAAoB,sCAAsCW;YAC1DN,WAAW;QACb;IACF;IAEA,MAAMU,aAAa,CAACC;QAClB,IAAI,CAACX,UAAU;YACbL,oBAAoB,oCAAoCgB,MAAMC,IAAI;YAClE;QACF;QAEAjB,oBAAoB,sBAAsBgB,MAAMC,IAAI;QAEpD,IAAI;YACF,mFAAmF;YACnF,iFAAiF;YACjF,2EAA2E;YAC3E,uEAAuE;YACvE,kCAAkC;YAClCtB,eAAeU,UAAUa,KAAKC,SAAS,CAACH,SAAS,MAAM;YACvDhB,oBAAoB,wCAAwCK;QAC9D,EAAE,OAAOM,OAAO;YACdX,oBAAoB,uCAAuCW;QAC3D,iDAAiD;QACnD;IACF;IAEA,MAAMS,OAAO,CAACJ;QACZ,IAAI,CAACV,cAAc;YACjBN,oBAAoB,qCAAqCgB,MAAMC,IAAI;YACnEV,YAAYc,IAAI,CAACL;YACjB;QACF;QAEA,IAAI,CAACZ,iBAAiBA,cAAcM,MAAM,KAAK,WAAW;YACxDV,oBACE,gDACAI,eAAeM,UAAU,cACzBM,MAAMC,IAAI;YAEZ;QACF;QAEAF,WAAWC;IACb;IAEA,MAAMM,SAASvB,aAAsCG,WAAWkB;IAEhE,qDAAqD;IACrDG,QAAQC,UAAU,CAAC;QAAChB;QAAqBK;KAAqB,EAAEY,IAAI,CAAC,CAACC;QACpE,KAAK,MAAM,CAACC,OAAOC,OAAO,IAAIF,QAAQG,OAAO,GAAI;YAC/C,IAAID,OAAOlB,MAAM,KAAK,YAAY;gBAChC,MAAMO,OAAOU,UAAU,IAAI,YAAY;gBACvC3B,oBAAoB,6BAA6BiB,MAAMW,OAAOhB,MAAM;YACtE;QACF;QAEAN,eAAe;QAEf,IAAIF,eAAeM,WAAW,aAAaL,UAAU;YACnDL,oBAAoB,iCAAiCO,YAAYuB,MAAM;YACvE,KAAK,MAAMd,SAAST,YAAa;gBAC/BQ,WAAWC;YACb;QACF,OAAO;YACLhB,oBACE,gDACAO,YAAYuB,MAAM,EAClB1B,eAAeM,UAAU;QAE7B;QACAH,YAAYuB,MAAM,GAAG;IACvB;IAEA,OAAOR;AACT"}
@@ -0,0 +1,40 @@
1
+ import { getUserConfig, subdebug } from '@sanity/cli-core';
2
+ import { getLatestVersion } from 'get-latest-version';
3
+ import { promiseRaceWithTimeout } from '../promiseRaceWithTimeout.js';
4
+ import { resolveUpdateTarget } from './resolveUpdateTarget.js';
5
+ const debug = subdebug('updateChecker');
6
+ const FETCH_TIMEOUT = 15_000;
7
+ /**
8
+ * Fetch the latest version of the update target package and write it to the config cache.
9
+ * Designed to run in a detached child process so it never blocks the main CLI.
10
+ *
11
+ * When `packageOverride` is given, the cwd-based resolver is skipped — used by
12
+ * the main process to pin the worker to the same package it already resolved
13
+ * (e.g. via a runner's symlinked install) so cache reads and writes align.
14
+ */ export async function fetchUpdateInfo(cwd, cliVersion, packageOverride) {
15
+ const { packageName } = packageOverride ? {
16
+ packageName: packageOverride
17
+ } : await resolveUpdateTarget(cwd, cliVersion);
18
+ debug('Worker: fetching latest version of %s', packageName);
19
+ let latestVersion;
20
+ try {
21
+ latestVersion = await promiseRaceWithTimeout(getLatestVersion(packageName), FETCH_TIMEOUT);
22
+ } catch (err) {
23
+ debug('Worker: failed to fetch latest version of %s from npm: %s', packageName, err instanceof Error ? err.message : String(err));
24
+ throw err;
25
+ }
26
+ if (latestVersion === null) {
27
+ debug('Worker: fetch timed out after %dms', FETCH_TIMEOUT);
28
+ return;
29
+ }
30
+ debug('Worker: latest %s version is %s', packageName, latestVersion);
31
+ const store = getUserConfig();
32
+ const cacheKey = `latestVersion:${packageName}`;
33
+ store.set(cacheKey, {
34
+ updatedAt: Date.now(),
35
+ value: latestVersion
36
+ });
37
+ debug('Worker: cached result to %s', cacheKey);
38
+ }
39
+
40
+ //# sourceMappingURL=fetchUpdateInfo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/util/update/fetchUpdateInfo.ts"],"sourcesContent":["import {getUserConfig, subdebug} from '@sanity/cli-core'\nimport {getLatestVersion} from 'get-latest-version'\n\nimport {type SanityPackage} from '../packageManager/installationInfo/types.js'\nimport {promiseRaceWithTimeout} from '../promiseRaceWithTimeout.js'\nimport {resolveUpdateTarget} from './resolveUpdateTarget.js'\n\nconst debug = subdebug('updateChecker')\n\nconst FETCH_TIMEOUT = 15_000\n\n/**\n * Fetch the latest version of the update target package and write it to the config cache.\n * Designed to run in a detached child process so it never blocks the main CLI.\n *\n * When `packageOverride` is given, the cwd-based resolver is skipped — used by\n * the main process to pin the worker to the same package it already resolved\n * (e.g. via a runner's symlinked install) so cache reads and writes align.\n */\nexport async function fetchUpdateInfo(\n cwd: string,\n cliVersion: string,\n packageOverride?: SanityPackage,\n): Promise<void> {\n const {packageName} = packageOverride\n ? {packageName: packageOverride}\n : await resolveUpdateTarget(cwd, cliVersion)\n debug('Worker: fetching latest version of %s', packageName)\n\n let latestVersion: string | null | undefined\n try {\n latestVersion = await promiseRaceWithTimeout(getLatestVersion(packageName), FETCH_TIMEOUT)\n } catch (err) {\n debug(\n 'Worker: failed to fetch latest version of %s from npm: %s',\n packageName,\n err instanceof Error ? err.message : String(err),\n )\n throw err\n }\n\n if (latestVersion === null) {\n debug('Worker: fetch timed out after %dms', FETCH_TIMEOUT)\n return\n }\n\n debug('Worker: latest %s version is %s', packageName, latestVersion)\n\n const store = getUserConfig()\n const cacheKey = `latestVersion:${packageName}`\n\n store.set(cacheKey, {\n updatedAt: Date.now(),\n value: latestVersion,\n })\n\n debug('Worker: cached result to %s', cacheKey)\n}\n"],"names":["getUserConfig","subdebug","getLatestVersion","promiseRaceWithTimeout","resolveUpdateTarget","debug","FETCH_TIMEOUT","fetchUpdateInfo","cwd","cliVersion","packageOverride","packageName","latestVersion","err","Error","message","String","store","cacheKey","set","updatedAt","Date","now","value"],"mappings":"AAAA,SAAQA,aAAa,EAAEC,QAAQ,QAAO,mBAAkB;AACxD,SAAQC,gBAAgB,QAAO,qBAAoB;AAGnD,SAAQC,sBAAsB,QAAO,+BAA8B;AACnE,SAAQC,mBAAmB,QAAO,2BAA0B;AAE5D,MAAMC,QAAQJ,SAAS;AAEvB,MAAMK,gBAAgB;AAEtB;;;;;;;CAOC,GACD,OAAO,eAAeC,gBACpBC,GAAW,EACXC,UAAkB,EAClBC,eAA+B;IAE/B,MAAM,EAACC,WAAW,EAAC,GAAGD,kBAClB;QAACC,aAAaD;IAAe,IAC7B,MAAMN,oBAAoBI,KAAKC;IACnCJ,MAAM,yCAAyCM;IAE/C,IAAIC;IACJ,IAAI;QACFA,gBAAgB,MAAMT,uBAAuBD,iBAAiBS,cAAcL;IAC9E,EAAE,OAAOO,KAAK;QACZR,MACE,6DACAM,aACAE,eAAeC,QAAQD,IAAIE,OAAO,GAAGC,OAAOH;QAE9C,MAAMA;IACR;IAEA,IAAID,kBAAkB,MAAM;QAC1BP,MAAM,sCAAsCC;QAC5C;IACF;IAEAD,MAAM,mCAAmCM,aAAaC;IAEtD,MAAMK,QAAQjB;IACd,MAAMkB,WAAW,CAAC,cAAc,EAAEP,aAAa;IAE/CM,MAAME,GAAG,CAACD,UAAU;QAClBE,WAAWC,KAAKC,GAAG;QACnBC,OAAOX;IACT;IAEAP,MAAM,+BAA+Ba;AACvC"}
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ import { pathToFileURL } from 'node:url';
3
+ import { fetchUpdateInfo } from './fetchUpdateInfo.js';
4
+ // Only run if executed directly (not imported)
5
+ if (import.meta.url === pathToFileURL(process.argv[1]).href) {
6
+ const cwd = process.env.SANITY_UPDATE_CHECK_CWD || process.cwd();
7
+ const cliVersion = process.env.SANITY_UPDATE_CHECK_CLI_VERSION || '0.0.0';
8
+ const rawPackage = process.env.SANITY_UPDATE_CHECK_PACKAGE;
9
+ const packageOverride = rawPackage === 'sanity' || rawPackage === '@sanity/cli' ? rawPackage : undefined;
10
+ try {
11
+ await fetchUpdateInfo(cwd, cliVersion, packageOverride);
12
+ process.exit(0);
13
+ } catch {
14
+ // Silently exit - don't leave zombie processes
15
+ process.exit(1);
16
+ }
17
+ }
18
+
19
+ //# sourceMappingURL=fetchUpdateInfo.worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/util/update/fetchUpdateInfo.worker.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport {pathToFileURL} from 'node:url'\n\nimport {type SanityPackage} from '../packageManager/installationInfo/types.js'\nimport {fetchUpdateInfo} from './fetchUpdateInfo.js'\n\n// Only run if executed directly (not imported)\nif (import.meta.url === pathToFileURL(process.argv[1]).href) {\n const cwd = process.env.SANITY_UPDATE_CHECK_CWD || process.cwd()\n const cliVersion = process.env.SANITY_UPDATE_CHECK_CLI_VERSION || '0.0.0'\n const rawPackage = process.env.SANITY_UPDATE_CHECK_PACKAGE\n const packageOverride: SanityPackage | undefined =\n rawPackage === 'sanity' || rawPackage === '@sanity/cli' ? rawPackage : undefined\n\n try {\n await fetchUpdateInfo(cwd, cliVersion, packageOverride)\n process.exit(0)\n } catch {\n // Silently exit - don't leave zombie processes\n process.exit(1)\n }\n}\n"],"names":["pathToFileURL","fetchUpdateInfo","url","process","argv","href","cwd","env","SANITY_UPDATE_CHECK_CWD","cliVersion","SANITY_UPDATE_CHECK_CLI_VERSION","rawPackage","SANITY_UPDATE_CHECK_PACKAGE","packageOverride","undefined","exit"],"mappings":";AAEA,SAAQA,aAAa,QAAO,WAAU;AAGtC,SAAQC,eAAe,QAAO,uBAAsB;AAEpD,+CAA+C;AAC/C,IAAI,YAAYC,GAAG,KAAKF,cAAcG,QAAQC,IAAI,CAAC,EAAE,EAAEC,IAAI,EAAE;IAC3D,MAAMC,MAAMH,QAAQI,GAAG,CAACC,uBAAuB,IAAIL,QAAQG,GAAG;IAC9D,MAAMG,aAAaN,QAAQI,GAAG,CAACG,+BAA+B,IAAI;IAClE,MAAMC,aAAaR,QAAQI,GAAG,CAACK,2BAA2B;IAC1D,MAAMC,kBACJF,eAAe,YAAYA,eAAe,gBAAgBA,aAAaG;IAEzE,IAAI;QACF,MAAMb,gBAAgBK,KAAKG,YAAYI;QACvCV,QAAQY,IAAI,CAAC;IACf,EAAE,OAAM;QACN,+CAA+C;QAC/CZ,QAAQY,IAAI,CAAC;IACf;AACF"}
@@ -0,0 +1,33 @@
1
+ const BIN_NAMES = {
2
+ '@sanity/cli': 'sanity',
3
+ sanity: 'sanity'
4
+ };
5
+ export function getRunnerUpdateCommand(runner, packageName) {
6
+ const binName = BIN_NAMES[packageName];
7
+ switch(runner){
8
+ case 'bunx':
9
+ {
10
+ return `bunx ${packageName}@latest`;
11
+ }
12
+ case 'npx':
13
+ {
14
+ return `npx --yes ${packageName}@latest`;
15
+ }
16
+ case 'pnpm-dlx':
17
+ {
18
+ return `pnpm dlx ${packageName}@latest`;
19
+ }
20
+ case 'yarn-dlx':
21
+ {
22
+ // yarn dlx only needs `-p` when the package name differs from the bin name
23
+ return binName === packageName ? `yarn dlx ${packageName}@latest` : `yarn dlx -p ${packageName}@latest ${binName}`;
24
+ }
25
+ default:
26
+ {
27
+ const _exhaustive = runner;
28
+ throw new Error(`Unknown runner: ${_exhaustive}`);
29
+ }
30
+ }
31
+ }
32
+
33
+ //# sourceMappingURL=getRunnerUpdateCommand.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/util/update/getRunnerUpdateCommand.ts"],"sourcesContent":["import {type SanityPackage} from '../packageManager/installationInfo/types.js'\nimport {type PackageRunner} from './packageRunner.js'\n\nconst BIN_NAMES: Record<SanityPackage, string> = {\n '@sanity/cli': 'sanity',\n sanity: 'sanity',\n}\n\nexport function getRunnerUpdateCommand(runner: PackageRunner, packageName: SanityPackage): string {\n const binName = BIN_NAMES[packageName]\n\n switch (runner) {\n case 'bunx': {\n return `bunx ${packageName}@latest`\n }\n case 'npx': {\n return `npx --yes ${packageName}@latest`\n }\n case 'pnpm-dlx': {\n return `pnpm dlx ${packageName}@latest`\n }\n case 'yarn-dlx': {\n // yarn dlx only needs `-p` when the package name differs from the bin name\n return binName === packageName\n ? `yarn dlx ${packageName}@latest`\n : `yarn dlx -p ${packageName}@latest ${binName}`\n }\n default: {\n const _exhaustive: never = runner\n throw new Error(`Unknown runner: ${_exhaustive as string}`)\n }\n }\n}\n"],"names":["BIN_NAMES","sanity","getRunnerUpdateCommand","runner","packageName","binName","_exhaustive","Error"],"mappings":"AAGA,MAAMA,YAA2C;IAC/C,eAAe;IACfC,QAAQ;AACV;AAEA,OAAO,SAASC,uBAAuBC,MAAqB,EAAEC,WAA0B;IACtF,MAAMC,UAAUL,SAAS,CAACI,YAAY;IAEtC,OAAQD;QACN,KAAK;YAAQ;gBACX,OAAO,CAAC,KAAK,EAAEC,YAAY,OAAO,CAAC;YACrC;QACA,KAAK;YAAO;gBACV,OAAO,CAAC,UAAU,EAAEA,YAAY,OAAO,CAAC;YAC1C;QACA,KAAK;YAAY;gBACf,OAAO,CAAC,SAAS,EAAEA,YAAY,OAAO,CAAC;YACzC;QACA,KAAK;YAAY;gBACf,2EAA2E;gBAC3E,OAAOC,YAAYD,cACf,CAAC,SAAS,EAAEA,YAAY,OAAO,CAAC,GAChC,CAAC,YAAY,EAAEA,YAAY,QAAQ,EAAEC,SAAS;YACpD;QACA;YAAS;gBACP,MAAMC,cAAqBH;gBAC3B,MAAM,IAAII,MAAM,CAAC,gBAAgB,EAAED,aAAuB;YAC5D;IACF;AACF"}
@@ -1,18 +1,17 @@
1
1
  import { getYarnMajorVersion } from '@sanity/cli-core/package-manager';
2
- export const cliPkgName = 'sanity';
3
2
  /**
4
3
  * Get the appropriate update command for the package manager
5
- */ export function getUpdateCommand(pm) {
4
+ */ export function getUpdateCommand(pm, packageName) {
6
5
  if (pm === 'yarn') {
7
6
  const yarnMajor = getYarnMajorVersion();
8
7
  const cmd = yarnMajor !== undefined && yarnMajor >= 2 ? 'up' : 'upgrade';
9
- return `yarn ${cmd} ${cliPkgName}`;
8
+ return `yarn ${cmd} ${packageName}`;
10
9
  }
11
10
  const localCommands = {
12
- bun: `bun update ${cliPkgName}`,
13
- manual: `npm update ${cliPkgName}`,
14
- npm: `npm update ${cliPkgName}`,
15
- pnpm: `pnpm update ${cliPkgName}`
11
+ bun: `bun update ${packageName}`,
12
+ manual: `npm update ${packageName}`,
13
+ npm: `npm update ${packageName}`,
14
+ pnpm: `pnpm update ${packageName}`
16
15
  };
17
16
  return localCommands[pm];
18
17
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/util/update/getUpdateCommand.ts"],"sourcesContent":["import {getYarnMajorVersion} from '@sanity/cli-core/package-manager'\n\nimport {type PackageManager} from '../packageManager/packageManagerChoice.js'\n\nexport const cliPkgName = 'sanity'\n\n/**\n * Get the appropriate update command for the package manager\n */\nexport function getUpdateCommand(pm: PackageManager): string {\n if (pm === 'yarn') {\n const yarnMajor = getYarnMajorVersion()\n const cmd = yarnMajor !== undefined && yarnMajor >= 2 ? 'up' : 'upgrade'\n return `yarn ${cmd} ${cliPkgName}`\n }\n\n const localCommands: Record<Exclude<PackageManager, 'yarn'>, string> = {\n bun: `bun update ${cliPkgName}`,\n manual: `npm update ${cliPkgName}`,\n npm: `npm update ${cliPkgName}`,\n pnpm: `pnpm update ${cliPkgName}`,\n }\n return localCommands[pm]\n}\n"],"names":["getYarnMajorVersion","cliPkgName","getUpdateCommand","pm","yarnMajor","cmd","undefined","localCommands","bun","manual","npm","pnpm"],"mappings":"AAAA,SAAQA,mBAAmB,QAAO,mCAAkC;AAIpE,OAAO,MAAMC,aAAa,SAAQ;AAElC;;CAEC,GACD,OAAO,SAASC,iBAAiBC,EAAkB;IACjD,IAAIA,OAAO,QAAQ;QACjB,MAAMC,YAAYJ;QAClB,MAAMK,MAAMD,cAAcE,aAAaF,aAAa,IAAI,OAAO;QAC/D,OAAO,CAAC,KAAK,EAAEC,IAAI,CAAC,EAAEJ,YAAY;IACpC;IAEA,MAAMM,gBAAiE;QACrEC,KAAK,CAAC,WAAW,EAAEP,YAAY;QAC/BQ,QAAQ,CAAC,WAAW,EAAER,YAAY;QAClCS,KAAK,CAAC,WAAW,EAAET,YAAY;QAC/BU,MAAM,CAAC,YAAY,EAAEV,YAAY;IACnC;IACA,OAAOM,aAAa,CAACJ,GAAG;AAC1B"}
1
+ {"version":3,"sources":["../../../src/util/update/getUpdateCommand.ts"],"sourcesContent":["import {getYarnMajorVersion} from '@sanity/cli-core/package-manager'\n\nimport {type SanityPackage} from '../packageManager/installationInfo/types.js'\nimport {type PackageManager} from '../packageManager/packageManagerChoice.js'\n\n/**\n * Get the appropriate update command for the package manager\n */\nexport function getUpdateCommand(pm: PackageManager, packageName: SanityPackage): string {\n if (pm === 'yarn') {\n const yarnMajor = getYarnMajorVersion()\n const cmd = yarnMajor !== undefined && yarnMajor >= 2 ? 'up' : 'upgrade'\n return `yarn ${cmd} ${packageName}`\n }\n\n const localCommands: Record<Exclude<PackageManager, 'yarn'>, string> = {\n bun: `bun update ${packageName}`,\n manual: `npm update ${packageName}`,\n npm: `npm update ${packageName}`,\n pnpm: `pnpm update ${packageName}`,\n }\n return localCommands[pm]\n}\n"],"names":["getYarnMajorVersion","getUpdateCommand","pm","packageName","yarnMajor","cmd","undefined","localCommands","bun","manual","npm","pnpm"],"mappings":"AAAA,SAAQA,mBAAmB,QAAO,mCAAkC;AAKpE;;CAEC,GACD,OAAO,SAASC,iBAAiBC,EAAkB,EAAEC,WAA0B;IAC7E,IAAID,OAAO,QAAQ;QACjB,MAAME,YAAYJ;QAClB,MAAMK,MAAMD,cAAcE,aAAaF,aAAa,IAAI,OAAO;QAC/D,OAAO,CAAC,KAAK,EAAEC,IAAI,CAAC,EAAEF,aAAa;IACrC;IAEA,MAAMI,gBAAiE;QACrEC,KAAK,CAAC,WAAW,EAAEL,aAAa;QAChCM,QAAQ,CAAC,WAAW,EAAEN,aAAa;QACnCO,KAAK,CAAC,WAAW,EAAEP,aAAa;QAChCQ,MAAM,CAAC,YAAY,EAAER,aAAa;IACpC;IACA,OAAOI,aAAa,CAACL,GAAG;AAC1B"}
@@ -0,0 +1,10 @@
1
+ export function detectPackageRunner(binaryPath = process.argv[1] ?? '') {
2
+ const normalized = binaryPath.replaceAll('\\', '/');
3
+ if (normalized.includes('/_npx/')) return 'npx';
4
+ if (normalized.includes('/pnpm/dlx/')) return 'pnpm-dlx';
5
+ if (normalized.includes('/xfs-') && normalized.includes('/dlx-')) return 'yarn-dlx';
6
+ if (/\/bunx-\d+-/.test(normalized)) return 'bunx';
7
+ return null;
8
+ }
9
+
10
+ //# sourceMappingURL=packageRunner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/util/update/packageRunner.ts"],"sourcesContent":["export type PackageRunner = 'bunx' | 'npx' | 'pnpm-dlx' | 'yarn-dlx'\n\nexport function detectPackageRunner(\n binaryPath: string = process.argv[1] ?? '',\n): PackageRunner | null {\n const normalized = binaryPath.replaceAll('\\\\', '/')\n\n if (normalized.includes('/_npx/')) return 'npx'\n if (normalized.includes('/pnpm/dlx/')) return 'pnpm-dlx'\n if (normalized.includes('/xfs-') && normalized.includes('/dlx-')) return 'yarn-dlx'\n if (/\\/bunx-\\d+-/.test(normalized)) return 'bunx'\n\n return null\n}\n"],"names":["detectPackageRunner","binaryPath","process","argv","normalized","replaceAll","includes","test"],"mappings":"AAEA,OAAO,SAASA,oBACdC,aAAqBC,QAAQC,IAAI,CAAC,EAAE,IAAI,EAAE;IAE1C,MAAMC,aAAaH,WAAWI,UAAU,CAAC,MAAM;IAE/C,IAAID,WAAWE,QAAQ,CAAC,WAAW,OAAO;IAC1C,IAAIF,WAAWE,QAAQ,CAAC,eAAe,OAAO;IAC9C,IAAIF,WAAWE,QAAQ,CAAC,YAAYF,WAAWE,QAAQ,CAAC,UAAU,OAAO;IACzE,IAAI,cAAcC,IAAI,CAACH,aAAa,OAAO;IAE3C,OAAO;AACT"}
@@ -0,0 +1,45 @@
1
+ import { readFile, realpath } from 'node:fs/promises';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { subdebug } from '@sanity/cli-core';
4
+ const debug = subdebug('updateChecker');
5
+ const KNOWN_PACKAGES = new Set([
6
+ '@sanity/cli',
7
+ 'sanity'
8
+ ]);
9
+ const MAX_WALK_ITERATIONS = 25;
10
+ /**
11
+ * Resolve the Sanity package name + installed version from a runner install.
12
+ * Falls back to `sanity` + `fallbackVersion` when the walk can't determine them.
13
+ */ export async function resolveRunnerPackage(binaryPath = process.argv[1] ?? '', fallbackVersion = '') {
14
+ try {
15
+ // Follow the runner's .bin/sanity symlink to the real bin file, then walk
16
+ // up until we hit a package.json for a known Sanity package.
17
+ let dir = dirname(await realpath(binaryPath));
18
+ for(let i = 0; i < MAX_WALK_ITERATIONS && dir !== resolve(dir, '..'); i++){
19
+ try {
20
+ const pkg = JSON.parse(await readFile(resolve(dir, 'package.json'), 'utf8'));
21
+ if (typeof pkg.name === 'string' && typeof pkg.version === 'string' && isKnownSanityPackage(pkg.name)) {
22
+ return {
23
+ installedVersion: pkg.version,
24
+ packageName: pkg.name
25
+ };
26
+ }
27
+ } catch {
28
+ // ignore missing/malformed package.json and keep walking
29
+ }
30
+ dir = dirname(dir);
31
+ }
32
+ debug('resolveRunnerPackage: walk exhausted without finding a known Sanity package');
33
+ } catch (err) {
34
+ debug('resolveRunnerPackage: realpath failed for %s (%s)', binaryPath, err);
35
+ }
36
+ return {
37
+ installedVersion: fallbackVersion,
38
+ packageName: 'sanity'
39
+ };
40
+ }
41
+ function isKnownSanityPackage(name) {
42
+ return KNOWN_PACKAGES.has(name);
43
+ }
44
+
45
+ //# sourceMappingURL=resolveRunnerPackage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/util/update/resolveRunnerPackage.ts"],"sourcesContent":["import {readFile, realpath} from 'node:fs/promises'\nimport {dirname, resolve} from 'node:path'\n\nimport {subdebug} from '@sanity/cli-core'\n\nimport {type SanityPackage} from '../packageManager/installationInfo/types.js'\n\nconst debug = subdebug('updateChecker')\n\nconst KNOWN_PACKAGES = new Set<SanityPackage>(['@sanity/cli', 'sanity'])\nconst MAX_WALK_ITERATIONS = 25\n\ninterface RunnerPackage {\n installedVersion: string\n packageName: SanityPackage\n}\n\n/**\n * Resolve the Sanity package name + installed version from a runner install.\n * Falls back to `sanity` + `fallbackVersion` when the walk can't determine them.\n */\nexport async function resolveRunnerPackage(\n binaryPath: string = process.argv[1] ?? '',\n fallbackVersion = '',\n): Promise<RunnerPackage> {\n try {\n // Follow the runner's .bin/sanity symlink to the real bin file, then walk\n // up until we hit a package.json for a known Sanity package.\n let dir = dirname(await realpath(binaryPath))\n for (let i = 0; i < MAX_WALK_ITERATIONS && dir !== resolve(dir, '..'); i++) {\n try {\n const pkg = JSON.parse(await readFile(resolve(dir, 'package.json'), 'utf8'))\n if (\n typeof pkg.name === 'string' &&\n typeof pkg.version === 'string' &&\n isKnownSanityPackage(pkg.name)\n ) {\n return {installedVersion: pkg.version, packageName: pkg.name}\n }\n } catch {\n // ignore missing/malformed package.json and keep walking\n }\n dir = dirname(dir)\n }\n debug('resolveRunnerPackage: walk exhausted without finding a known Sanity package')\n } catch (err) {\n debug('resolveRunnerPackage: realpath failed for %s (%s)', binaryPath, err)\n }\n\n return {installedVersion: fallbackVersion, packageName: 'sanity'}\n}\n\nfunction isKnownSanityPackage(name: string): name is SanityPackage {\n return KNOWN_PACKAGES.has(name as SanityPackage)\n}\n"],"names":["readFile","realpath","dirname","resolve","subdebug","debug","KNOWN_PACKAGES","Set","MAX_WALK_ITERATIONS","resolveRunnerPackage","binaryPath","process","argv","fallbackVersion","dir","i","pkg","JSON","parse","name","version","isKnownSanityPackage","installedVersion","packageName","err","has"],"mappings":"AAAA,SAAQA,QAAQ,EAAEC,QAAQ,QAAO,mBAAkB;AACnD,SAAQC,OAAO,EAAEC,OAAO,QAAO,YAAW;AAE1C,SAAQC,QAAQ,QAAO,mBAAkB;AAIzC,MAAMC,QAAQD,SAAS;AAEvB,MAAME,iBAAiB,IAAIC,IAAmB;IAAC;IAAe;CAAS;AACvE,MAAMC,sBAAsB;AAO5B;;;CAGC,GACD,OAAO,eAAeC,qBACpBC,aAAqBC,QAAQC,IAAI,CAAC,EAAE,IAAI,EAAE,EAC1CC,kBAAkB,EAAE;IAEpB,IAAI;QACF,0EAA0E;QAC1E,6DAA6D;QAC7D,IAAIC,MAAMZ,QAAQ,MAAMD,SAASS;QACjC,IAAK,IAAIK,IAAI,GAAGA,IAAIP,uBAAuBM,QAAQX,QAAQW,KAAK,OAAOC,IAAK;YAC1E,IAAI;gBACF,MAAMC,MAAMC,KAAKC,KAAK,CAAC,MAAMlB,SAASG,QAAQW,KAAK,iBAAiB;gBACpE,IACE,OAAOE,IAAIG,IAAI,KAAK,YACpB,OAAOH,IAAII,OAAO,KAAK,YACvBC,qBAAqBL,IAAIG,IAAI,GAC7B;oBACA,OAAO;wBAACG,kBAAkBN,IAAII,OAAO;wBAAEG,aAAaP,IAAIG,IAAI;oBAAA;gBAC9D;YACF,EAAE,OAAM;YACN,yDAAyD;YAC3D;YACAL,MAAMZ,QAAQY;QAChB;QACAT,MAAM;IACR,EAAE,OAAOmB,KAAK;QACZnB,MAAM,qDAAqDK,YAAYc;IACzE;IAEA,OAAO;QAACF,kBAAkBT;QAAiBU,aAAa;IAAQ;AAClE;AAEA,SAASF,qBAAqBF,IAAY;IACxC,OAAOb,eAAemB,GAAG,CAACN;AAC5B"}
@@ -0,0 +1,31 @@
1
+ import { subdebug } from '@sanity/cli-core';
2
+ import { findInstalledPackage, findPackageDeclaration } from '../packageManager/installationInfo/detectPackages.js';
3
+ const debug = subdebug('updateChecker');
4
+ /**
5
+ * Determine which package to check for updates and what version is currently installed.
6
+ *
7
+ * If the user's project declares `sanity` as a dependency and it's installed,
8
+ * we check `sanity` (since that's what the user manages). Otherwise, we fall back
9
+ * to `@sanity/cli` (the currently running CLI binary).
10
+ */ export async function resolveUpdateTarget(cwd, cliVersion) {
11
+ // Check if `sanity` is a dependency in the local project
12
+ const sanityDeclaration = await findPackageDeclaration('sanity', cwd);
13
+ if (sanityDeclaration) {
14
+ debug('Project declares sanity as a dependency, checking installed version');
15
+ const sanityInstalled = await findInstalledPackage('sanity', cwd);
16
+ if (sanityInstalled) {
17
+ debug('Installed sanity version: %s', sanityInstalled.version);
18
+ return {
19
+ installedVersion: sanityInstalled.version,
20
+ packageName: 'sanity'
21
+ };
22
+ }
23
+ debug('sanity is declared but not installed, falling back to @sanity/cli');
24
+ }
25
+ return {
26
+ installedVersion: cliVersion,
27
+ packageName: '@sanity/cli'
28
+ };
29
+ }
30
+
31
+ //# sourceMappingURL=resolveUpdateTarget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/util/update/resolveUpdateTarget.ts"],"sourcesContent":["import {subdebug} from '@sanity/cli-core'\n\nimport {\n findInstalledPackage,\n findPackageDeclaration,\n} from '../packageManager/installationInfo/detectPackages.js'\nimport {type SanityPackage} from '../packageManager/installationInfo/types.js'\n\nconst debug = subdebug('updateChecker')\n\ninterface UpdateTarget {\n installedVersion: string\n packageName: SanityPackage\n}\n\n/**\n * Determine which package to check for updates and what version is currently installed.\n *\n * If the user's project declares `sanity` as a dependency and it's installed,\n * we check `sanity` (since that's what the user manages). Otherwise, we fall back\n * to `@sanity/cli` (the currently running CLI binary).\n */\nexport async function resolveUpdateTarget(cwd: string, cliVersion: string): Promise<UpdateTarget> {\n // Check if `sanity` is a dependency in the local project\n const sanityDeclaration = await findPackageDeclaration('sanity', cwd)\n\n if (sanityDeclaration) {\n debug('Project declares sanity as a dependency, checking installed version')\n const sanityInstalled = await findInstalledPackage('sanity', cwd)\n\n if (sanityInstalled) {\n debug('Installed sanity version: %s', sanityInstalled.version)\n return {installedVersion: sanityInstalled.version, packageName: 'sanity'}\n }\n\n debug('sanity is declared but not installed, falling back to @sanity/cli')\n }\n\n return {installedVersion: cliVersion, packageName: '@sanity/cli'}\n}\n"],"names":["subdebug","findInstalledPackage","findPackageDeclaration","debug","resolveUpdateTarget","cwd","cliVersion","sanityDeclaration","sanityInstalled","version","installedVersion","packageName"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,mBAAkB;AAEzC,SACEC,oBAAoB,EACpBC,sBAAsB,QACjB,uDAAsD;AAG7D,MAAMC,QAAQH,SAAS;AAOvB;;;;;;CAMC,GACD,OAAO,eAAeI,oBAAoBC,GAAW,EAAEC,UAAkB;IACvE,yDAAyD;IACzD,MAAMC,oBAAoB,MAAML,uBAAuB,UAAUG;IAEjE,IAAIE,mBAAmB;QACrBJ,MAAM;QACN,MAAMK,kBAAkB,MAAMP,qBAAqB,UAAUI;QAE7D,IAAIG,iBAAiB;YACnBL,MAAM,gCAAgCK,gBAAgBC,OAAO;YAC7D,OAAO;gBAACC,kBAAkBF,gBAAgBC,OAAO;gBAAEE,aAAa;YAAQ;QAC1E;QAEAR,MAAM;IACR;IAEA,OAAO;QAACO,kBAAkBJ;QAAYK,aAAa;IAAa;AAClE"}
@@ -3,20 +3,22 @@ import { ux } from '@oclif/core';
3
3
  import { boxen } from '@sanity/cli-core/ux';
4
4
  import isInstalledGlobally from 'is-installed-globally';
5
5
  import { getPackageManagerChoice } from '../packageManager/packageManagerChoice.js';
6
- import { cliPkgName, getUpdateCommand } from './getUpdateCommand.js';
6
+ import { getRunnerUpdateCommand } from './getRunnerUpdateCommand.js';
7
+ import { getUpdateCommand } from './getUpdateCommand.js';
7
8
  import { isInstalledUsingYarn } from './isInstalledUsingYarn.js';
8
9
  /**
9
10
  * Show a boxed notification about the available update
10
- */ export async function showUpdateNotification(currentVersion, latestVersion) {
11
+ */ export async function showUpdateNotification(currentVersion, latestVersion, packageName, runner = null) {
11
12
  let command;
12
- // Check if CLI is installed globally
13
- if (isInstalledGlobally) {
14
- command = isInstalledUsingYarn() ? `yarn global add ${cliPkgName}` : `npm install -g ${cliPkgName}`;
13
+ if (runner) {
14
+ command = getRunnerUpdateCommand(runner, packageName);
15
+ } else if (isInstalledGlobally) {
16
+ command = isInstalledUsingYarn() ? `yarn global add ${packageName}` : `npm install -g ${packageName}`;
15
17
  } else {
16
18
  const { chosen } = await getPackageManagerChoice(process.cwd(), {
17
19
  interactive: false
18
20
  });
19
- command = getUpdateCommand(chosen);
21
+ command = getUpdateCommand(chosen, packageName);
20
22
  }
21
23
  const message = `Update available: ${styleText('dim', currentVersion)} → ${styleText('green', latestVersion)}\n\nRun ${styleText('cyan', command)} to update`;
22
24
  const boxed = boxen(message, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/util/update/showNotificationUpdate.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {ux} from '@oclif/core'\nimport {boxen} from '@sanity/cli-core/ux'\nimport isInstalledGlobally from 'is-installed-globally'\n\nimport {getPackageManagerChoice} from '../packageManager/packageManagerChoice.js'\nimport {cliPkgName, getUpdateCommand} from './getUpdateCommand.js'\nimport {isInstalledUsingYarn} from './isInstalledUsingYarn.js'\n\n/**\n * Show a boxed notification about the available update\n */\nexport async function showUpdateNotification(\n currentVersion: string,\n latestVersion: string,\n): Promise<void> {\n let command\n\n // Check if CLI is installed globally\n if (isInstalledGlobally) {\n command = isInstalledUsingYarn()\n ? `yarn global add ${cliPkgName}`\n : `npm install -g ${cliPkgName}`\n } else {\n const {chosen} = await getPackageManagerChoice(process.cwd(), {interactive: false})\n command = getUpdateCommand(chosen)\n }\n\n const message = `Update available: ${styleText('dim', currentVersion)} → ${styleText('green', latestVersion)}\\n\\nRun ${styleText('cyan', command)} to update`\n\n const boxed = boxen(message, {\n borderColor: 'yellow',\n borderStyle: 'round',\n margin: 1,\n padding: 1,\n })\n\n ux.stderr('\\n' + boxed + '\\n')\n}\n"],"names":["styleText","ux","boxen","isInstalledGlobally","getPackageManagerChoice","cliPkgName","getUpdateCommand","isInstalledUsingYarn","showUpdateNotification","currentVersion","latestVersion","command","chosen","process","cwd","interactive","message","boxed","borderColor","borderStyle","margin","padding","stderr"],"mappings":"AAAA,SAAQA,SAAS,QAAO,YAAW;AAEnC,SAAQC,EAAE,QAAO,cAAa;AAC9B,SAAQC,KAAK,QAAO,sBAAqB;AACzC,OAAOC,yBAAyB,wBAAuB;AAEvD,SAAQC,uBAAuB,QAAO,4CAA2C;AACjF,SAAQC,UAAU,EAAEC,gBAAgB,QAAO,wBAAuB;AAClE,SAAQC,oBAAoB,QAAO,4BAA2B;AAE9D;;CAEC,GACD,OAAO,eAAeC,uBACpBC,cAAsB,EACtBC,aAAqB;IAErB,IAAIC;IAEJ,qCAAqC;IACrC,IAAIR,qBAAqB;QACvBQ,UAAUJ,yBACN,CAAC,gBAAgB,EAAEF,YAAY,GAC/B,CAAC,eAAe,EAAEA,YAAY;IACpC,OAAO;QACL,MAAM,EAACO,MAAM,EAAC,GAAG,MAAMR,wBAAwBS,QAAQC,GAAG,IAAI;YAACC,aAAa;QAAK;QACjFJ,UAAUL,iBAAiBM;IAC7B;IAEA,MAAMI,UAAU,CAAC,kBAAkB,EAAEhB,UAAU,OAAOS,gBAAgB,GAAG,EAAET,UAAU,SAASU,eAAe,QAAQ,EAAEV,UAAU,QAAQW,SAAS,UAAU,CAAC;IAE7J,MAAMM,QAAQf,MAAMc,SAAS;QAC3BE,aAAa;QACbC,aAAa;QACbC,QAAQ;QACRC,SAAS;IACX;IAEApB,GAAGqB,MAAM,CAAC,OAAOL,QAAQ;AAC3B"}
1
+ {"version":3,"sources":["../../../src/util/update/showNotificationUpdate.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {ux} from '@oclif/core'\nimport {boxen} from '@sanity/cli-core/ux'\nimport isInstalledGlobally from 'is-installed-globally'\n\nimport {type SanityPackage} from '../packageManager/installationInfo/types.js'\nimport {getPackageManagerChoice} from '../packageManager/packageManagerChoice.js'\nimport {getRunnerUpdateCommand} from './getRunnerUpdateCommand.js'\nimport {getUpdateCommand} from './getUpdateCommand.js'\nimport {isInstalledUsingYarn} from './isInstalledUsingYarn.js'\nimport {type PackageRunner} from './packageRunner.js'\n\n/**\n * Show a boxed notification about the available update\n */\nexport async function showUpdateNotification(\n currentVersion: string,\n latestVersion: string,\n packageName: SanityPackage,\n runner: PackageRunner | null = null,\n): Promise<void> {\n let command\n\n if (runner) {\n command = getRunnerUpdateCommand(runner, packageName)\n } else if (isInstalledGlobally) {\n command = isInstalledUsingYarn()\n ? `yarn global add ${packageName}`\n : `npm install -g ${packageName}`\n } else {\n const {chosen} = await getPackageManagerChoice(process.cwd(), {interactive: false})\n command = getUpdateCommand(chosen, packageName)\n }\n\n const message = `Update available: ${styleText('dim', currentVersion)} → ${styleText('green', latestVersion)}\\n\\nRun ${styleText('cyan', command)} to update`\n\n const boxed = boxen(message, {\n borderColor: 'yellow',\n borderStyle: 'round',\n margin: 1,\n padding: 1,\n })\n\n ux.stderr('\\n' + boxed + '\\n')\n}\n"],"names":["styleText","ux","boxen","isInstalledGlobally","getPackageManagerChoice","getRunnerUpdateCommand","getUpdateCommand","isInstalledUsingYarn","showUpdateNotification","currentVersion","latestVersion","packageName","runner","command","chosen","process","cwd","interactive","message","boxed","borderColor","borderStyle","margin","padding","stderr"],"mappings":"AAAA,SAAQA,SAAS,QAAO,YAAW;AAEnC,SAAQC,EAAE,QAAO,cAAa;AAC9B,SAAQC,KAAK,QAAO,sBAAqB;AACzC,OAAOC,yBAAyB,wBAAuB;AAGvD,SAAQC,uBAAuB,QAAO,4CAA2C;AACjF,SAAQC,sBAAsB,QAAO,8BAA6B;AAClE,SAAQC,gBAAgB,QAAO,wBAAuB;AACtD,SAAQC,oBAAoB,QAAO,4BAA2B;AAG9D;;CAEC,GACD,OAAO,eAAeC,uBACpBC,cAAsB,EACtBC,aAAqB,EACrBC,WAA0B,EAC1BC,SAA+B,IAAI;IAEnC,IAAIC;IAEJ,IAAID,QAAQ;QACVC,UAAUR,uBAAuBO,QAAQD;IAC3C,OAAO,IAAIR,qBAAqB;QAC9BU,UAAUN,yBACN,CAAC,gBAAgB,EAAEI,aAAa,GAChC,CAAC,eAAe,EAAEA,aAAa;IACrC,OAAO;QACL,MAAM,EAACG,MAAM,EAAC,GAAG,MAAMV,wBAAwBW,QAAQC,GAAG,IAAI;YAACC,aAAa;QAAK;QACjFJ,UAAUP,iBAAiBQ,QAAQH;IACrC;IAEA,MAAMO,UAAU,CAAC,kBAAkB,EAAElB,UAAU,OAAOS,gBAAgB,GAAG,EAAET,UAAU,SAASU,eAAe,QAAQ,EAAEV,UAAU,QAAQa,SAAS,UAAU,CAAC;IAE7J,MAAMM,QAAQjB,MAAMgB,SAAS;QAC3BE,aAAa;QACbC,aAAa;QACbC,QAAQ;QACRC,SAAS;IACX;IAEAtB,GAAGuB,MAAM,CAAC,OAAOL,QAAQ;AAC3B"}
@@ -1,17 +1,21 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { fileURLToPath } from 'node:url';
1
3
  import { getUserConfig, isCi, subdebug } from '@sanity/cli-core';
2
- import semver from 'semver';
3
- import { createExpiringConfig } from '../createExpiringConfig.js';
4
- import { fetchLatestVersion } from './fetchLatestVersion.js';
4
+ import { gt as semverGt } from 'semver';
5
+ import { detectPackageRunner } from './packageRunner.js';
6
+ import { resolveRunnerPackage } from './resolveRunnerPackage.js';
7
+ import { resolveUpdateTarget } from './resolveUpdateTarget.js';
5
8
  import { showUpdateNotification } from './showNotificationUpdate.js';
6
9
  const debug = subdebug('updateChecker');
7
- const TWELVE_HOURS = 12 * 60 * 60 * 1000 // 12 hours
8
- ;
9
- const CHECK_TIMEOUT = 300;
10
+ const TWELVE_HOURS = 12 * 60 * 60 * 1000;
10
11
  /**
11
12
  * Check for CLI updates and notify the user if a new version is available.
12
- * This is designed to be non-blocking and will silently fail if anything goes wrong.
13
13
  *
14
- * @param config - The CLI config containing version and name
14
+ * The main process resolves the local update target (which package and installed version),
15
+ * then reads the latest version from the config cache. It never makes network requests.
16
+ * If the cache is empty or expired, a detached worker process is spawned to fetch the
17
+ * latest version from npm and write it to the cache. The notification is shown on the
18
+ * next CLI invocation when the cached result is available instantly.
15
19
  */ export async function updateChecker(config) {
16
20
  debug(`Installed CLI version is ${config.version}`);
17
21
  // Skip in CI or if disabled
@@ -22,39 +26,70 @@ const CHECK_TIMEOUT = 300;
22
26
  if (!process.stdout.isTTY) {
23
27
  return;
24
28
  }
29
+ const runner = detectPackageRunner();
30
+ const { installedVersion, packageName } = runner ? await resolveRunnerPackage(process.argv[1] ?? '', config.version) : await resolveUpdateTarget(process.cwd(), config.version);
31
+ debug('Update target: %s@%s%s', packageName, installedVersion, runner ? ` via ${runner}` : '');
25
32
  const store = getUserConfig();
26
- let showNotificationUpdate = true;
27
- // Cache for latest version from npm
28
- const latestVersionCache = createExpiringConfig({
29
- fetchValue: async ()=>fetchLatestVersion(config.name, CHECK_TIMEOUT),
30
- key: 'cliLatestRemoteVersion',
31
- onCacheHit: ()=>{
32
- debug('Less than 12 hours since last check, skipping update check');
33
- showNotificationUpdate = false;
34
- },
35
- onFetch: ()=>debug('Checking for latest remote version'),
36
- store,
37
- ttl: TWELVE_HOURS,
38
- validateValue: (value)=>typeof value === 'string'
39
- });
40
- const latestVersion = await latestVersionCache.get();
41
- if (!latestVersion) {
42
- debug('No cached latest version result found');
43
- return;
44
- }
45
- const comparison = semver.compare(latestVersion, config.version);
46
- if (comparison < 0) {
47
- debug('Remote version older than local');
48
- return;
49
- }
50
- if (comparison === 0) {
51
- debug('No update found');
52
- return;
33
+ const cacheKey = `latestVersion:${packageName}`;
34
+ const cached = readCachedLatestVersion(store, cacheKey);
35
+ if (cached) {
36
+ const { expired, latestVersion, updatedAt } = cached;
37
+ debug('Cache %s for %s: installed=%s, latest=%s', expired ? 'expired' : 'hit', packageName, installedVersion, latestVersion);
38
+ if (semverGt(latestVersion, installedVersion)) {
39
+ const notifiedKey = `notifiedAt:${packageName}`;
40
+ if (store.get(notifiedKey) === updatedAt) {
41
+ debug('Update is available (%s), already notified for this cache cycle', latestVersion);
42
+ } else {
43
+ debug('Update is available (%s)', latestVersion);
44
+ await showUpdateNotification(installedVersion, latestVersion, packageName, runner);
45
+ store.set(notifiedKey, updatedAt);
46
+ }
47
+ } else {
48
+ debug('No update found');
49
+ }
50
+ if (expired) {
51
+ debug('Cache expired, spawning worker to refresh');
52
+ spawnFetchWorker(config.version, packageName);
53
+ }
54
+ } else {
55
+ debug('No cached update info, spawning worker to fetch');
56
+ spawnFetchWorker(config.version, packageName);
53
57
  }
54
- debug('Update is available (%s)', latestVersion);
55
- if (showNotificationUpdate) {
56
- await showUpdateNotification(config.version, latestVersion);
58
+ }
59
+ /**
60
+ * Read and validate the cached latest version for a specific package.
61
+ * The cache only stores the latest npm version (globally valid) - the installed
62
+ * version is always resolved locally to avoid cross-project confusion.
63
+ */ function readCachedLatestVersion(store, cacheKey) {
64
+ const stored = store.get(cacheKey);
65
+ if (!stored || typeof stored !== 'object' || !('updatedAt' in stored) || typeof stored.updatedAt !== 'number' || !('value' in stored) || typeof stored.value !== 'string') {
66
+ return null;
57
67
  }
68
+ const expired = Date.now() - stored.updatedAt > TWELVE_HOURS;
69
+ return {
70
+ expired,
71
+ latestVersion: stored.value,
72
+ updatedAt: stored.updatedAt
73
+ };
74
+ }
75
+ /**
76
+ * Spawn a detached worker process to fetch the latest version and update the cache.
77
+ * The worker is unref'd so the parent CLI can exit immediately.
78
+ */ function spawnFetchWorker(cliVersion, packageName) {
79
+ const workerPath = fileURLToPath(new URL('fetchUpdateInfo.worker.js', import.meta.url));
80
+ debug(`Spawning update check worker: ${process.execPath} ${workerPath}`);
81
+ spawn(process.execPath, [
82
+ workerPath
83
+ ], {
84
+ detached: true,
85
+ env: {
86
+ ...process.env,
87
+ SANITY_UPDATE_CHECK_CLI_VERSION: cliVersion,
88
+ SANITY_UPDATE_CHECK_CWD: process.cwd(),
89
+ SANITY_UPDATE_CHECK_PACKAGE: packageName
90
+ },
91
+ stdio: debug.enabled ? 'inherit' : 'ignore'
92
+ }).unref();
58
93
  }
59
94
 
60
95
  //# sourceMappingURL=updateChecker.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/util/update/updateChecker.ts"],"sourcesContent":["import {getUserConfig, isCi, subdebug} from '@sanity/cli-core'\nimport semver from 'semver'\n\nimport {createExpiringConfig} from '../createExpiringConfig.js'\nimport {fetchLatestVersion} from './fetchLatestVersion.js'\nimport {showUpdateNotification} from './showNotificationUpdate.js'\n\nconst debug = subdebug('updateChecker')\n\nconst TWELVE_HOURS = 12 * 60 * 60 * 1000 // 12 hours\nconst CHECK_TIMEOUT = 300\n\n/**\n * Check for CLI updates and notify the user if a new version is available.\n * This is designed to be non-blocking and will silently fail if anything goes wrong.\n *\n * @param config - The CLI config containing version and name\n */\nexport async function updateChecker(config: {\n name: string\n root: string\n version: string\n}): Promise<void> {\n debug(`Installed CLI version is ${config.version}`)\n // Skip in CI or if disabled\n if (isCi() || process.env.NO_UPDATE_NOTIFIER) {\n debug('Running on CI, or explicitly disabled, skipping update check')\n return\n }\n\n if (!process.stdout.isTTY) {\n return\n }\n\n const store = getUserConfig()\n\n let showNotificationUpdate = true\n\n // Cache for latest version from npm\n const latestVersionCache = createExpiringConfig({\n fetchValue: async () => fetchLatestVersion(config.name, CHECK_TIMEOUT),\n key: 'cliLatestRemoteVersion',\n onCacheHit: () => {\n debug('Less than 12 hours since last check, skipping update check')\n showNotificationUpdate = false\n },\n onFetch: () => debug('Checking for latest remote version'),\n store,\n ttl: TWELVE_HOURS,\n validateValue: (value): value is string => typeof value === 'string',\n })\n\n const latestVersion = await latestVersionCache.get()\n\n if (!latestVersion) {\n debug('No cached latest version result found')\n return\n }\n\n const comparison = semver.compare(latestVersion, config.version)\n\n if (comparison < 0) {\n debug('Remote version older than local')\n return\n }\n\n if (comparison === 0) {\n debug('No update found')\n return\n }\n\n debug('Update is available (%s)', latestVersion)\n\n if (showNotificationUpdate) {\n await showUpdateNotification(config.version, latestVersion)\n }\n}\n"],"names":["getUserConfig","isCi","subdebug","semver","createExpiringConfig","fetchLatestVersion","showUpdateNotification","debug","TWELVE_HOURS","CHECK_TIMEOUT","updateChecker","config","version","process","env","NO_UPDATE_NOTIFIER","stdout","isTTY","store","showNotificationUpdate","latestVersionCache","fetchValue","name","key","onCacheHit","onFetch","ttl","validateValue","value","latestVersion","get","comparison","compare"],"mappings":"AAAA,SAAQA,aAAa,EAAEC,IAAI,EAAEC,QAAQ,QAAO,mBAAkB;AAC9D,OAAOC,YAAY,SAAQ;AAE3B,SAAQC,oBAAoB,QAAO,6BAA4B;AAC/D,SAAQC,kBAAkB,QAAO,0BAAyB;AAC1D,SAAQC,sBAAsB,QAAO,8BAA6B;AAElE,MAAMC,QAAQL,SAAS;AAEvB,MAAMM,eAAe,KAAK,KAAK,KAAK,KAAK,WAAW;;AACpD,MAAMC,gBAAgB;AAEtB;;;;;CAKC,GACD,OAAO,eAAeC,cAAcC,MAInC;IACCJ,MAAM,CAAC,yBAAyB,EAAEI,OAAOC,OAAO,EAAE;IAClD,4BAA4B;IAC5B,IAAIX,UAAUY,QAAQC,GAAG,CAACC,kBAAkB,EAAE;QAC5CR,MAAM;QACN;IACF;IAEA,IAAI,CAACM,QAAQG,MAAM,CAACC,KAAK,EAAE;QACzB;IACF;IAEA,MAAMC,QAAQlB;IAEd,IAAImB,yBAAyB;IAE7B,oCAAoC;IACpC,MAAMC,qBAAqBhB,qBAAqB;QAC9CiB,YAAY,UAAYhB,mBAAmBM,OAAOW,IAAI,EAAEb;QACxDc,KAAK;QACLC,YAAY;YACVjB,MAAM;YACNY,yBAAyB;QAC3B;QACAM,SAAS,IAAMlB,MAAM;QACrBW;QACAQ,KAAKlB;QACLmB,eAAe,CAACC,QAA2B,OAAOA,UAAU;IAC9D;IAEA,MAAMC,gBAAgB,MAAMT,mBAAmBU,GAAG;IAElD,IAAI,CAACD,eAAe;QAClBtB,MAAM;QACN;IACF;IAEA,MAAMwB,aAAa5B,OAAO6B,OAAO,CAACH,eAAelB,OAAOC,OAAO;IAE/D,IAAImB,aAAa,GAAG;QAClBxB,MAAM;QACN;IACF;IAEA,IAAIwB,eAAe,GAAG;QACpBxB,MAAM;QACN;IACF;IAEAA,MAAM,4BAA4BsB;IAElC,IAAIV,wBAAwB;QAC1B,MAAMb,uBAAuBK,OAAOC,OAAO,EAAEiB;IAC/C;AACF"}
1
+ {"version":3,"sources":["../../../src/util/update/updateChecker.ts"],"sourcesContent":["import {spawn} from 'node:child_process'\nimport {fileURLToPath} from 'node:url'\n\nimport {getUserConfig, isCi, subdebug} from '@sanity/cli-core'\nimport {gt as semverGt} from 'semver'\n\nimport {type SanityPackage} from '../packageManager/installationInfo/types.js'\nimport {detectPackageRunner} from './packageRunner.js'\nimport {resolveRunnerPackage} from './resolveRunnerPackage.js'\nimport {resolveUpdateTarget} from './resolveUpdateTarget.js'\nimport {showUpdateNotification} from './showNotificationUpdate.js'\n\nconst debug = subdebug('updateChecker')\n\nconst TWELVE_HOURS = 12 * 60 * 60 * 1000\n\n/**\n * Check for CLI updates and notify the user if a new version is available.\n *\n * The main process resolves the local update target (which package and installed version),\n * then reads the latest version from the config cache. It never makes network requests.\n * If the cache is empty or expired, a detached worker process is spawned to fetch the\n * latest version from npm and write it to the cache. The notification is shown on the\n * next CLI invocation when the cached result is available instantly.\n */\nexport async function updateChecker(config: {version: string}): Promise<void> {\n debug(`Installed CLI version is ${config.version}`)\n\n // Skip in CI or if disabled\n if (isCi() || process.env.NO_UPDATE_NOTIFIER) {\n debug('Running on CI, or explicitly disabled, skipping update check')\n return\n }\n\n if (!process.stdout.isTTY) {\n return\n }\n\n const runner = detectPackageRunner()\n const {installedVersion, packageName} = runner\n ? await resolveRunnerPackage(process.argv[1] ?? '', config.version)\n : await resolveUpdateTarget(process.cwd(), config.version)\n debug('Update target: %s@%s%s', packageName, installedVersion, runner ? ` via ${runner}` : '')\n\n const store = getUserConfig()\n const cacheKey = `latestVersion:${packageName}`\n const cached = readCachedLatestVersion(store, cacheKey)\n\n if (cached) {\n const {expired, latestVersion, updatedAt} = cached\n\n debug(\n 'Cache %s for %s: installed=%s, latest=%s',\n expired ? 'expired' : 'hit',\n packageName,\n installedVersion,\n latestVersion,\n )\n\n if (semverGt(latestVersion, installedVersion)) {\n const notifiedKey = `notifiedAt:${packageName}`\n if (store.get(notifiedKey) === updatedAt) {\n debug('Update is available (%s), already notified for this cache cycle', latestVersion)\n } else {\n debug('Update is available (%s)', latestVersion)\n await showUpdateNotification(installedVersion, latestVersion, packageName, runner)\n store.set(notifiedKey, updatedAt)\n }\n } else {\n debug('No update found')\n }\n\n if (expired) {\n debug('Cache expired, spawning worker to refresh')\n spawnFetchWorker(config.version, packageName)\n }\n } else {\n debug('No cached update info, spawning worker to fetch')\n spawnFetchWorker(config.version, packageName)\n }\n}\n\n/**\n * Read and validate the cached latest version for a specific package.\n * The cache only stores the latest npm version (globally valid) - the installed\n * version is always resolved locally to avoid cross-project confusion.\n */\nfunction readCachedLatestVersion(\n store: ReturnType<typeof getUserConfig>,\n cacheKey: string,\n): {expired: boolean; latestVersion: string; updatedAt: number} | null {\n const stored: unknown = store.get(cacheKey)\n\n if (\n !stored ||\n typeof stored !== 'object' ||\n !('updatedAt' in stored) ||\n typeof stored.updatedAt !== 'number' ||\n !('value' in stored) ||\n typeof stored.value !== 'string'\n ) {\n return null\n }\n\n const expired = Date.now() - stored.updatedAt > TWELVE_HOURS\n return {expired, latestVersion: stored.value, updatedAt: stored.updatedAt}\n}\n\n/**\n * Spawn a detached worker process to fetch the latest version and update the cache.\n * The worker is unref'd so the parent CLI can exit immediately.\n */\nfunction spawnFetchWorker(cliVersion: string, packageName: SanityPackage): void {\n const workerPath = fileURLToPath(new URL('fetchUpdateInfo.worker.js', import.meta.url))\n debug(`Spawning update check worker: ${process.execPath} ${workerPath}`)\n\n spawn(process.execPath, [workerPath], {\n detached: true,\n env: {\n ...process.env,\n SANITY_UPDATE_CHECK_CLI_VERSION: cliVersion,\n SANITY_UPDATE_CHECK_CWD: process.cwd(),\n SANITY_UPDATE_CHECK_PACKAGE: packageName,\n },\n stdio: debug.enabled ? 'inherit' : 'ignore',\n }).unref()\n}\n"],"names":["spawn","fileURLToPath","getUserConfig","isCi","subdebug","gt","semverGt","detectPackageRunner","resolveRunnerPackage","resolveUpdateTarget","showUpdateNotification","debug","TWELVE_HOURS","updateChecker","config","version","process","env","NO_UPDATE_NOTIFIER","stdout","isTTY","runner","installedVersion","packageName","argv","cwd","store","cacheKey","cached","readCachedLatestVersion","expired","latestVersion","updatedAt","notifiedKey","get","set","spawnFetchWorker","stored","value","Date","now","cliVersion","workerPath","URL","url","execPath","detached","SANITY_UPDATE_CHECK_CLI_VERSION","SANITY_UPDATE_CHECK_CWD","SANITY_UPDATE_CHECK_PACKAGE","stdio","enabled","unref"],"mappings":"AAAA,SAAQA,KAAK,QAAO,qBAAoB;AACxC,SAAQC,aAAa,QAAO,WAAU;AAEtC,SAAQC,aAAa,EAAEC,IAAI,EAAEC,QAAQ,QAAO,mBAAkB;AAC9D,SAAQC,MAAMC,QAAQ,QAAO,SAAQ;AAGrC,SAAQC,mBAAmB,QAAO,qBAAoB;AACtD,SAAQC,oBAAoB,QAAO,4BAA2B;AAC9D,SAAQC,mBAAmB,QAAO,2BAA0B;AAC5D,SAAQC,sBAAsB,QAAO,8BAA6B;AAElE,MAAMC,QAAQP,SAAS;AAEvB,MAAMQ,eAAe,KAAK,KAAK,KAAK;AAEpC;;;;;;;;CAQC,GACD,OAAO,eAAeC,cAAcC,MAAyB;IAC3DH,MAAM,CAAC,yBAAyB,EAAEG,OAAOC,OAAO,EAAE;IAElD,4BAA4B;IAC5B,IAAIZ,UAAUa,QAAQC,GAAG,CAACC,kBAAkB,EAAE;QAC5CP,MAAM;QACN;IACF;IAEA,IAAI,CAACK,QAAQG,MAAM,CAACC,KAAK,EAAE;QACzB;IACF;IAEA,MAAMC,SAASd;IACf,MAAM,EAACe,gBAAgB,EAAEC,WAAW,EAAC,GAAGF,SACpC,MAAMb,qBAAqBQ,QAAQQ,IAAI,CAAC,EAAE,IAAI,IAAIV,OAAOC,OAAO,IAChE,MAAMN,oBAAoBO,QAAQS,GAAG,IAAIX,OAAOC,OAAO;IAC3DJ,MAAM,0BAA0BY,aAAaD,kBAAkBD,SAAS,CAAC,KAAK,EAAEA,QAAQ,GAAG;IAE3F,MAAMK,QAAQxB;IACd,MAAMyB,WAAW,CAAC,cAAc,EAAEJ,aAAa;IAC/C,MAAMK,SAASC,wBAAwBH,OAAOC;IAE9C,IAAIC,QAAQ;QACV,MAAM,EAACE,OAAO,EAAEC,aAAa,EAAEC,SAAS,EAAC,GAAGJ;QAE5CjB,MACE,4CACAmB,UAAU,YAAY,OACtBP,aACAD,kBACAS;QAGF,IAAIzB,SAASyB,eAAeT,mBAAmB;YAC7C,MAAMW,cAAc,CAAC,WAAW,EAAEV,aAAa;YAC/C,IAAIG,MAAMQ,GAAG,CAACD,iBAAiBD,WAAW;gBACxCrB,MAAM,mEAAmEoB;YAC3E,OAAO;gBACLpB,MAAM,4BAA4BoB;gBAClC,MAAMrB,uBAAuBY,kBAAkBS,eAAeR,aAAaF;gBAC3EK,MAAMS,GAAG,CAACF,aAAaD;YACzB;QACF,OAAO;YACLrB,MAAM;QACR;QAEA,IAAImB,SAAS;YACXnB,MAAM;YACNyB,iBAAiBtB,OAAOC,OAAO,EAAEQ;QACnC;IACF,OAAO;QACLZ,MAAM;QACNyB,iBAAiBtB,OAAOC,OAAO,EAAEQ;IACnC;AACF;AAEA;;;;CAIC,GACD,SAASM,wBACPH,KAAuC,EACvCC,QAAgB;IAEhB,MAAMU,SAAkBX,MAAMQ,GAAG,CAACP;IAElC,IACE,CAACU,UACD,OAAOA,WAAW,YAClB,CAAE,CAAA,eAAeA,MAAK,KACtB,OAAOA,OAAOL,SAAS,KAAK,YAC5B,CAAE,CAAA,WAAWK,MAAK,KAClB,OAAOA,OAAOC,KAAK,KAAK,UACxB;QACA,OAAO;IACT;IAEA,MAAMR,UAAUS,KAAKC,GAAG,KAAKH,OAAOL,SAAS,GAAGpB;IAChD,OAAO;QAACkB;QAASC,eAAeM,OAAOC,KAAK;QAAEN,WAAWK,OAAOL,SAAS;IAAA;AAC3E;AAEA;;;CAGC,GACD,SAASI,iBAAiBK,UAAkB,EAAElB,WAA0B;IACtE,MAAMmB,aAAazC,cAAc,IAAI0C,IAAI,6BAA6B,YAAYC,GAAG;IACrFjC,MAAM,CAAC,8BAA8B,EAAEK,QAAQ6B,QAAQ,CAAC,CAAC,EAAEH,YAAY;IAEvE1C,MAAMgB,QAAQ6B,QAAQ,EAAE;QAACH;KAAW,EAAE;QACpCI,UAAU;QACV7B,KAAK;YACH,GAAGD,QAAQC,GAAG;YACd8B,iCAAiCN;YACjCO,yBAAyBhC,QAAQS,GAAG;YACpCwB,6BAA6B1B;QAC/B;QACA2B,OAAOvC,MAAMwC,OAAO,GAAG,YAAY;IACrC,GAAGC,KAAK;AACV"}