@volter/sdk 0.0.0-stage → 0.5.204

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 (523) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +20 -0
  3. package/README.md +38 -3
  4. package/package.json +510 -4
  5. package/src/account.ts +210 -0
  6. package/src/chrome.ts +88 -0
  7. package/src/client.ts +1646 -0
  8. package/src/commands.ts +66 -0
  9. package/src/contributions.ts +619 -0
  10. package/src/css-numeric-style.ts +97 -0
  11. package/src/document-probe.ts +282 -0
  12. package/src/editor-view.ts +225 -0
  13. package/src/extension.ts +40 -0
  14. package/src/generations.ts +178 -0
  15. package/src/host.ts +1157 -0
  16. package/src/http-transport.browser.ts +14 -0
  17. package/src/http-transport.node.ts +19 -0
  18. package/src/index.ts +131 -0
  19. package/src/kit/CapabilityCoverageSection.tsx +185 -0
  20. package/src/kit/account-client.ts +333 -0
  21. package/src/kit/action-registry.ts +317 -0
  22. package/src/kit/active-product.ts +76 -0
  23. package/src/kit/active-project.ts +155 -0
  24. package/src/kit/adapter-editor-config.ts +25 -0
  25. package/src/kit/adapter-module.ts +7 -0
  26. package/src/kit/adapter-observation.ts +49 -0
  27. package/src/kit/animation/animation-clock.ts +479 -0
  28. package/src/kit/animation/stage-transport.ts +385 -0
  29. package/src/kit/api/assets.ts +365 -0
  30. package/src/kit/api/project-open.ts +355 -0
  31. package/src/kit/api/project-source.ts +180 -0
  32. package/src/kit/api/project-state.ts +110 -0
  33. package/src/kit/api/relay.ts +270 -0
  34. package/src/kit/api/themes.ts +45 -0
  35. package/src/kit/api-asset-library-wire.ts +45 -0
  36. package/src/kit/api-base.ts +10 -0
  37. package/src/kit/api-build.ts +99 -0
  38. package/src/kit/api-git-wire.ts +56 -0
  39. package/src/kit/api-logs.ts +92 -0
  40. package/src/kit/api-project-identity.ts +74 -0
  41. package/src/kit/api-settings.ts +36 -0
  42. package/src/kit/api-worktrees.ts +205 -0
  43. package/src/kit/asset-capabilities.ts +344 -0
  44. package/src/kit/asset-compare-core.ts +171 -0
  45. package/src/kit/asset-editor-context.tsx +101 -0
  46. package/src/kit/asset-events.ts +96 -0
  47. package/src/kit/asset-inspector-actions.ts +87 -0
  48. package/src/kit/asset-selection-viewer-registry.ts +113 -0
  49. package/src/kit/asset-selection.ts +146 -0
  50. package/src/kit/asset-thumbnails.ts +25 -0
  51. package/src/kit/asset-viewers.ts +115 -0
  52. package/src/kit/asset-workflow/asset-import-jobs.ts +106 -0
  53. package/src/kit/asset-workflow/asset-ledger-backend.ts +126 -0
  54. package/src/kit/asset-workflow/asset-ledger.ts +156 -0
  55. package/src/kit/asset-workflow/asset-materialization-report.ts +140 -0
  56. package/src/kit/asset-workflow/asset-pack-manifest.ts +320 -0
  57. package/src/kit/asset-workflow/asset-types.ts +142 -0
  58. package/src/kit/asset-workflow/audio-preview-player.ts +193 -0
  59. package/src/kit/asset-workflow/audio-waveform.ts +22 -0
  60. package/src/kit/asset-workflow/cloud-asset-client.ts +263 -0
  61. package/src/kit/asset-workflow/hosted-asset-materialization.ts +236 -0
  62. package/src/kit/asset-workflow/image-view-scale.ts +31 -0
  63. package/src/kit/asset-workflow/import-contract.ts +124 -0
  64. package/src/kit/asset-workflow/ledger-write-lock.ts +244 -0
  65. package/src/kit/asset-workflow/pixi-spritesheet.ts +197 -0
  66. package/src/kit/asset-workflow/preview-resource-lifetime.ts +44 -0
  67. package/src/kit/asset-workflow/project-asset-commands.ts +20 -0
  68. package/src/kit/asset-workflow/project-content.ts +288 -0
  69. package/src/kit/asset-workflow/project-source-index.ts +545 -0
  70. package/src/kit/asset-workflow/thumbnail-system.ts +256 -0
  71. package/src/kit/authoring/active-adapter.ts +200 -0
  72. package/src/kit/authoring/active-systems.ts +422 -0
  73. package/src/kit/authoring/adapter-key.ts +18 -0
  74. package/src/kit/authoring/authoring-asset-url.ts +27 -0
  75. package/src/kit/authoring/bootstrap-state.ts +49 -0
  76. package/src/kit/authoring/boundary-authoring-adapter.ts +189 -0
  77. package/src/kit/authoring/canvas-scene-guides.ts +84 -0
  78. package/src/kit/authoring/composite-authoring-adapter.ts +2109 -0
  79. package/src/kit/authoring/consumer-actions.ts +531 -0
  80. package/src/kit/authoring/design-time-layers.ts +852 -0
  81. package/src/kit/authoring/design-time-mount-registry.ts +244 -0
  82. package/src/kit/authoring/edit-mode-authoring.ts +637 -0
  83. package/src/kit/authoring/empty-project-authoring.ts +22 -0
  84. package/src/kit/authoring/instance-source-menu.ts +135 -0
  85. package/src/kit/authoring/layered-pick.ts +185 -0
  86. package/src/kit/authoring/mounted-root-subjects.ts +146 -0
  87. package/src/kit/authoring/no-authoring-adapter.ts +59 -0
  88. package/src/kit/authoring/object3d-document-persistence.ts +122 -0
  89. package/src/kit/authoring/panel-authoring.ts +121 -0
  90. package/src/kit/authoring/project-authoring-session.ts +105 -0
  91. package/src/kit/authoring/provenance.ts +99 -0
  92. package/src/kit/authoring/react-canvas-navigation.ts +259 -0
  93. package/src/kit/authoring/react-design-canvas-style.ts +20 -0
  94. package/src/kit/authoring/react-story-board.ts +917 -0
  95. package/src/kit/authoring/selection-scope.ts +195 -0
  96. package/src/kit/authoring/shell-document-ops.ts +169 -0
  97. package/src/kit/authoring/story-board-chrome-fit.ts +107 -0
  98. package/src/kit/authoring/story-board-presentation.ts +111 -0
  99. package/src/kit/authoring/three-root.ts +67 -0
  100. package/src/kit/authoring/viewport-tool-context.ts +73 -0
  101. package/src/kit/authoring/viewport-tool-owner.ts +38 -0
  102. package/src/kit/authoring/world-session-state.ts +101 -0
  103. package/src/kit/authoring-seam-evidence.ts +300 -0
  104. package/src/kit/availability-tick.ts +66 -0
  105. package/src/kit/bitmap-label.ts +120 -0
  106. package/src/kit/boot-routing.ts +392 -0
  107. package/src/kit/breakpoint-state.ts +43 -0
  108. package/src/kit/build-identity.ts +16 -0
  109. package/src/kit/bytes-codec.ts +62 -0
  110. package/src/kit/cancellation-reason.ts +58 -0
  111. package/src/kit/canvas-frames.ts +88 -0
  112. package/src/kit/capture-camera-pose.ts +77 -0
  113. package/src/kit/capture-size.ts +88 -0
  114. package/src/kit/chrome-registry.ts +159 -0
  115. package/src/kit/chrome-slot-registry.ts +91 -0
  116. package/src/kit/collaboration-client.ts +264 -0
  117. package/src/kit/collaboration-presence.ts +41 -0
  118. package/src/kit/command-dispatch.ts +19 -0
  119. package/src/kit/command-listener.ts +2182 -0
  120. package/src/kit/command-registry.ts +71 -0
  121. package/src/kit/component-board-registry.ts +205 -0
  122. package/src/kit/component-states-registry.ts +199 -0
  123. package/src/kit/components/AlignToolbar.tsx +204 -0
  124. package/src/kit/components/ApplicationMenus.tsx +372 -0
  125. package/src/kit/components/AssetEditorShell.tsx +216 -0
  126. package/src/kit/components/AssetInspectorToolSection.tsx +124 -0
  127. package/src/kit/components/BoardRulers.tsx +354 -0
  128. package/src/kit/components/CanvasAddNodeDialogs.tsx +529 -0
  129. package/src/kit/components/CanvasSceneViewport.tsx +1195 -0
  130. package/src/kit/components/ChromeSlot.tsx +20 -0
  131. package/src/kit/components/CodeView.tsx +470 -0
  132. package/src/kit/components/CompactInspectorShell.tsx +39 -0
  133. package/src/kit/components/ConsolePanel.tsx +273 -0
  134. package/src/kit/components/GameplaySessionTimeline.tsx +295 -0
  135. package/src/kit/components/InspectionProjection.tsx +932 -0
  136. package/src/kit/components/Inspector.tsx +270 -0
  137. package/src/kit/components/InspectorCanvasPreview.tsx +35 -0
  138. package/src/kit/components/InspectorFieldsSection.tsx +290 -0
  139. package/src/kit/components/InspectorStoriesSection.tsx +92 -0
  140. package/src/kit/components/InspectorToolSection.tsx +96 -0
  141. package/src/kit/components/InspectorTransformSection.tsx +245 -0
  142. package/src/kit/components/LightExplorerPanel.tsx +433 -0
  143. package/src/kit/components/MediaProperties.tsx +145 -0
  144. package/src/kit/components/ProjectHeader.tsx +328 -0
  145. package/src/kit/components/ReactCanvasControls.tsx +358 -0
  146. package/src/kit/components/RootSelectionOverlay.tsx +3688 -0
  147. package/src/kit/components/RootTextEditor.tsx +79 -0
  148. package/src/kit/components/SaveStatus.tsx +70 -0
  149. package/src/kit/components/SurfaceCrashBoundary.tsx +105 -0
  150. package/src/kit/components/SurfaceStateOverlay.tsx +24 -0
  151. package/src/kit/components/ToolContributionSurfaces.tsx +49 -0
  152. package/src/kit/components/ToolHost.tsx +380 -0
  153. package/src/kit/components/Toolbar.tsx +811 -0
  154. package/src/kit/components/TransientHint.tsx +44 -0
  155. package/src/kit/components/VersionControlSection.tsx +470 -0
  156. package/src/kit/components/ViewportOverlaysMenu.tsx +177 -0
  157. package/src/kit/components/VolterLogo.tsx +18 -0
  158. package/src/kit/components/WorktreeSwitcher.tsx +712 -0
  159. package/src/kit/components/account-documents.tsx +1162 -0
  160. package/src/kit/components/asset-documents.tsx +794 -0
  161. package/src/kit/components/asset-editor-persistence.ts +216 -0
  162. package/src/kit/components/asset-selection-section.tsx +545 -0
  163. package/src/kit/components/asset-thumbnails.tsx +307 -0
  164. package/src/kit/components/asset-viewers/AudioViewer.tsx +201 -0
  165. package/src/kit/components/asset-viewers/GenericJsonViewer.tsx +102 -0
  166. package/src/kit/components/asset-viewers/ImageViewer.tsx +300 -0
  167. package/src/kit/components/asset-viewers/JsonAssetDocument.tsx +98 -0
  168. package/src/kit/components/asset-viewers/OnlineAssetDetail.tsx +426 -0
  169. package/src/kit/components/asset-viewers/SourceAssetViewer.tsx +356 -0
  170. package/src/kit/components/asset-viewers/SpritesheetSpriteView.tsx +102 -0
  171. package/src/kit/components/asset-viewers/VideoViewer.tsx +101 -0
  172. package/src/kit/components/asset-viewers/shader-source.ts +144 -0
  173. package/src/kit/components/board-guides.ts +150 -0
  174. package/src/kit/components/canvas-scene-hotkeys.ts +37 -0
  175. package/src/kit/components/canvas-temporary-pivot.ts +34 -0
  176. package/src/kit/components/core-utilities.tsx +94 -0
  177. package/src/kit/components/inspector-preview-section.tsx +223 -0
  178. package/src/kit/components/inspector-revert-label.ts +20 -0
  179. package/src/kit/components/inspector-selection.ts +42 -0
  180. package/src/kit/components/inspector-stories-gating.ts +171 -0
  181. package/src/kit/components/inspector-transform-subject.ts +11 -0
  182. package/src/kit/components/inspector-transform.ts +88 -0
  183. package/src/kit/components/kind-documents.tsx +544 -0
  184. package/src/kit/components/primitives/DraftColorInput.tsx +74 -0
  185. package/src/kit/components/project-tool-documents.tsx +402 -0
  186. package/src/kit/components/scene-documents.tsx +221 -0
  187. package/src/kit/components/status-contributions.tsx +407 -0
  188. package/src/kit/components/tool-documents.tsx +302 -0
  189. package/src/kit/components/tool-schema-form.tsx +262 -0
  190. package/src/kit/components/use-after-paint.ts +41 -0
  191. package/src/kit/components/use-project-image-assets.ts +86 -0
  192. package/src/kit/components/workspace-history.ts +32 -0
  193. package/src/kit/components/world-documents.tsx +570 -0
  194. package/src/kit/components/world-overlay-gestures.ts +1939 -0
  195. package/src/kit/composite-screenshot.ts +2238 -0
  196. package/src/kit/content-entry-source-registry.ts +184 -0
  197. package/src/kit/contribution-surfaces.ts +48 -0
  198. package/src/kit/coverage/canvas-reveal.ts +192 -0
  199. package/src/kit/coverage/design-time-surfaces.ts +101 -0
  200. package/src/kit/coverage/ontology-invariants.ts +466 -0
  201. package/src/kit/coverage/session-vitals.ts +503 -0
  202. package/src/kit/crash-null-boundary.ts +36 -0
  203. package/src/kit/creation-site-edit.ts +1491 -0
  204. package/src/kit/creation-site-registry.ts +160 -0
  205. package/src/kit/delegate-harness-registry.ts +134 -0
  206. package/src/kit/document-areas.ts +70 -0
  207. package/src/kit/document-context-registry.ts +193 -0
  208. package/src/kit/document-open-registry.ts +200 -0
  209. package/src/kit/document-play-extension.ts +221 -0
  210. package/src/kit/document-preview-source.ts +20 -0
  211. package/src/kit/document-renderer-session.ts +138 -0
  212. package/src/kit/document-stage-sessions.ts +26 -0
  213. package/src/kit/document-viewports.ts +120 -0
  214. package/src/kit/editor-api.ts +46 -0
  215. package/src/kit/editor-chrome-capture.ts +136 -0
  216. package/src/kit/editor-commands.ts +176 -0
  217. package/src/kit/editor-console.ts +580 -0
  218. package/src/kit/editor-current-view.ts +56 -0
  219. package/src/kit/editor-document-probe.ts +1168 -0
  220. package/src/kit/editor-git-client.ts +115 -0
  221. package/src/kit/editor-hotkeys.ts +728 -0
  222. package/src/kit/editor-lease-view.ts +39 -0
  223. package/src/kit/editor-lease.ts +415 -0
  224. package/src/kit/editor-mode.ts +19 -0
  225. package/src/kit/editor-notifications.ts +140 -0
  226. package/src/kit/editor-presence.ts +563 -0
  227. package/src/kit/editor-presentation-activity.ts +58 -0
  228. package/src/kit/editor-presentation-notice.ts +42 -0
  229. package/src/kit/editor-runtime.tsx +147 -0
  230. package/src/kit/editor-server-response.ts +86 -0
  231. package/src/kit/editor-session-attribution.ts +85 -0
  232. package/src/kit/editor-session-mode.ts +54 -0
  233. package/src/kit/editor-state-facets.ts +74 -0
  234. package/src/kit/editor-view-presentation.ts +777 -0
  235. package/src/kit/environment-images.ts +58 -0
  236. package/src/kit/eyedropper-session.ts +60 -0
  237. package/src/kit/files/file-provider.ts +62 -0
  238. package/src/kit/files/project-files.ts +270 -0
  239. package/src/kit/finders/index.ts +137 -0
  240. package/src/kit/finders/scenes-from-entrypoint-selection.ts +387 -0
  241. package/src/kit/frame/frame-parts.ts +30 -0
  242. package/src/kit/framed-document-capture.ts +34 -0
  243. package/src/kit/game-globals-prelude.ts +143 -0
  244. package/src/kit/game-surface-defaults.ts +33 -0
  245. package/src/kit/gameplay-dom-recording.ts +318 -0
  246. package/src/kit/gameplay-export-state.ts +14 -0
  247. package/src/kit/gameplay-replay.ts +417 -0
  248. package/src/kit/gameplay-session-time.ts +9 -0
  249. package/src/kit/gameplay-sessions.ts +204 -0
  250. package/src/kit/hierarchy-component-marks.ts +298 -0
  251. package/src/kit/hierarchy-internals.ts +197 -0
  252. package/src/kit/hierarchy-kind-icon.ts +217 -0
  253. package/src/kit/hierarchy-menu-registry.ts +67 -0
  254. package/src/kit/hierarchy-node-rows.ts +307 -0
  255. package/src/kit/hierarchy-panel-view.ts +280 -0
  256. package/src/kit/hierarchy-projection.ts +76 -0
  257. package/src/kit/hierarchy-row-media.ts +45 -0
  258. package/src/kit/hierarchy-row-model.ts +308 -0
  259. package/src/kit/hierarchy-rows.ts +11 -0
  260. package/src/kit/hierarchy-walk.ts +86 -0
  261. package/src/kit/history/editor-session.ts +25 -0
  262. package/src/kit/history/history-commands.ts +147 -0
  263. package/src/kit/history/history-delegate.ts +187 -0
  264. package/src/kit/history/history-limit-notices.ts +43 -0
  265. package/src/kit/history/history-service.ts +1189 -0
  266. package/src/kit/history/persistence-coordinator.ts +35 -0
  267. package/src/kit/history/project-file-history.ts +386 -0
  268. package/src/kit/history/project-root-history-backends.ts +139 -0
  269. package/src/kit/history/resource-registry.ts +209 -0
  270. package/src/kit/history/snapshot-store.ts +103 -0
  271. package/src/kit/history/source-history-backend.ts +546 -0
  272. package/src/kit/history-types.ts +124 -0
  273. package/src/kit/hmr-registration-group.ts +67 -0
  274. package/src/kit/hmr-stable-react-context.ts +23 -0
  275. package/src/kit/hotkeys.ts +190 -0
  276. package/src/kit/inference-diagnostics.ts +69 -0
  277. package/src/kit/initial-project.ts +80 -0
  278. package/src/kit/inspection/active-subject.ts +571 -0
  279. package/src/kit/inspection/active-surface.ts +142 -0
  280. package/src/kit/inspection/compose-subject.ts +1055 -0
  281. package/src/kit/inspection/compose.ts +7 -0
  282. package/src/kit/inspection/display.ts +171 -0
  283. package/src/kit/inspection/document-subject.ts +109 -0
  284. package/src/kit/inspection/game-subject.ts +85 -0
  285. package/src/kit/inspection/null-subject.ts +119 -0
  286. package/src/kit/inspection/serialize.ts +357 -0
  287. package/src/kit/inspection/use-active-inspection.ts +180 -0
  288. package/src/kit/inspection-model.ts +542 -0
  289. package/src/kit/inspection-node-media.ts +58 -0
  290. package/src/kit/inspector-presentation.ts +203 -0
  291. package/src/kit/inspector-property-grouping.ts +64 -0
  292. package/src/kit/inspector-section-registry.ts +221 -0
  293. package/src/kit/instance-source-actions.ts +163 -0
  294. package/src/kit/js-heap.ts +71 -0
  295. package/src/kit/key-actions.ts +91 -0
  296. package/src/kit/keymap-presets.ts +428 -0
  297. package/src/kit/layout-policy.ts +31 -0
  298. package/src/kit/light-explorer-model.ts +134 -0
  299. package/src/kit/live-canvas-frame.ts +55 -0
  300. package/src/kit/live-document.ts +296 -0
  301. package/src/kit/live-gesture-lock.ts +50 -0
  302. package/src/kit/live-seam-evidence.ts +11 -0
  303. package/src/kit/live-session-registry.ts +220 -0
  304. package/src/kit/live-transition.ts +391 -0
  305. package/src/kit/manifest-project.ts +107 -0
  306. package/src/kit/module-fetch-diagnosis.ts +192 -0
  307. package/src/kit/mount-failure-report.ts +154 -0
  308. package/src/kit/native-selection-style.ts +497 -0
  309. package/src/kit/object3d-document-write-policy.ts +137 -0
  310. package/src/kit/packaged-runtime.ts +108 -0
  311. package/src/kit/palettes/maya.palette.json +57 -0
  312. package/src/kit/palettes/substance.palette.json +57 -0
  313. package/src/kit/performance-profiler.ts +367 -0
  314. package/src/kit/performance-sources.ts +69 -0
  315. package/src/kit/photograph-notice.ts +141 -0
  316. package/src/kit/play-boot-phase.ts +166 -0
  317. package/src/kit/play-camera-flight.ts +35 -0
  318. package/src/kit/png-encode.worker.ts +26 -0
  319. package/src/kit/presentation-surface.ts +248 -0
  320. package/src/kit/product-command.ts +90 -0
  321. package/src/kit/project-adapter.ts +1140 -0
  322. package/src/kit/project-asset-refresh.ts +23 -0
  323. package/src/kit/project-asset-roots.ts +68 -0
  324. package/src/kit/project-local-state.ts +151 -0
  325. package/src/kit/project-manager.ts +243 -0
  326. package/src/kit/project-module-changes.ts +201 -0
  327. package/src/kit/project-module-split.ts +270 -0
  328. package/src/kit/project-play-layers.ts +25 -0
  329. package/src/kit/project-provenance.ts +115 -0
  330. package/src/kit/project-ready.ts +42 -0
  331. package/src/kit/project-shape.ts +68 -0
  332. package/src/kit/project-tools.ts +107 -0
  333. package/src/kit/projection-types.ts +44 -0
  334. package/src/kit/readiness.ts +113 -0
  335. package/src/kit/renderer-resource-counts.ts +27 -0
  336. package/src/kit/reported-play-state.ts +90 -0
  337. package/src/kit/resolve-contributed-command.ts +14 -0
  338. package/src/kit/resolve-relative-specifier.ts +33 -0
  339. package/src/kit/retained-document-states.ts +91 -0
  340. package/src/kit/scene-document-plan.ts +320 -0
  341. package/src/kit/scene-live-open.ts +210 -0
  342. package/src/kit/scoped-game-css.ts +152 -0
  343. package/src/kit/served-url.ts +5 -0
  344. package/src/kit/session-close.ts +17 -0
  345. package/src/kit/session-tombstone.ts +127 -0
  346. package/src/kit/settings/settings-provider.ts +82 -0
  347. package/src/kit/settings-store.ts +348 -0
  348. package/src/kit/shell-document-state.ts +27 -0
  349. package/src/kit/shell-store-door.ts +45 -0
  350. package/src/kit/shell-store.ts +722 -0
  351. package/src/kit/source-conflict.ts +122 -0
  352. package/src/kit/stage-context.ts +377 -0
  353. package/src/kit/stage-invalidation.ts +25 -0
  354. package/src/kit/stage-store-registry.ts +69 -0
  355. package/src/kit/startup-failure.ts +80 -0
  356. package/src/kit/state-report-deferral.ts +73 -0
  357. package/src/kit/storage/host-files-storage.ts +97 -0
  358. package/src/kit/storage/http-storage.ts +174 -0
  359. package/src/kit/storage/index.ts +75 -0
  360. package/src/kit/storage/mem-storage.ts +158 -0
  361. package/src/kit/storage/path-lock.ts +44 -0
  362. package/src/kit/storage/paths.ts +26 -0
  363. package/src/kit/storage-types.ts +127 -0
  364. package/src/kit/stories/StoryPreviewMount.tsx +306 -0
  365. package/src/kit/stories/compose-project-stories.ts +255 -0
  366. package/src/kit/stories/prefabs-finder.ts +54 -0
  367. package/src/kit/stories/prefabs-from-stories.ts +182 -0
  368. package/src/kit/stories/project-story-regions.ts +24 -0
  369. package/src/kit/stories/story-capture.ts +579 -0
  370. package/src/kit/stories/story-declared-medium.ts +126 -0
  371. package/src/kit/stories/story-discovery.ts +176 -0
  372. package/src/kit/stories/story-dom-runtime.ts +78 -0
  373. package/src/kit/stories/story-grouping.ts +111 -0
  374. package/src/kit/stories/story-mount-turn.ts +27 -0
  375. package/src/kit/stories/story-presentation.ts +215 -0
  376. package/src/kit/stories/story-preview-component.ts +7 -0
  377. package/src/kit/stories/story-registry.ts +530 -0
  378. package/src/kit/stories-scope.ts +35 -0
  379. package/src/kit/story-document-openers.ts +36 -0
  380. package/src/kit/story-thumbnails.ts +47 -0
  381. package/src/kit/surface-keyboard.ts +101 -0
  382. package/src/kit/surface-state.ts +135 -0
  383. package/src/kit/system-seam-evidence.ts +72 -0
  384. package/src/kit/tab-census.ts +202 -0
  385. package/src/kit/tab-lifecycle-client.ts +227 -0
  386. package/src/kit/theme-library.ts +897 -0
  387. package/src/kit/theme-preference.ts +429 -0
  388. package/src/kit/three-viewport-presentation.ts +23 -0
  389. package/src/kit/tool-contribution-play.ts +74 -0
  390. package/src/kit/tool-loader.ts +1918 -0
  391. package/src/kit/transform-mode-request.ts +66 -0
  392. package/src/kit/transient-hint.ts +78 -0
  393. package/src/kit/transport-strip.tsx +174 -0
  394. package/src/kit/ui-source/adapter-region-includes.ts +238 -0
  395. package/src/kit/ui-source/file-region-resolver.ts +302 -0
  396. package/src/kit/ui-source/inspect.ts +775 -0
  397. package/src/kit/ui-source/source-write-backend.ts +605 -0
  398. package/src/kit/ui-source/tier-source-write-backend.ts +279 -0
  399. package/src/kit/user-local-state.ts +105 -0
  400. package/src/kit/viewport-activation-timings.ts +840 -0
  401. package/src/kit/viewport-editor-controls.ts +22 -0
  402. package/src/kit/viewport-presentation.ts +668 -0
  403. package/src/kit/viewport-surface-status.tsx +55 -0
  404. package/src/kit/wait-until.ts +37 -0
  405. package/src/kit/worker-call-metrics.ts +166 -0
  406. package/src/kit/workspace-areas.ts +191 -0
  407. package/src/kit/workspace-aux-commands.ts +11 -0
  408. package/src/kit/workspace-available-documents.ts +142 -0
  409. package/src/kit/workspace-core-utilities.ts +31 -0
  410. package/src/kit/workspace-document-ids.ts +59 -0
  411. package/src/kit/workspace-document-registry.ts +624 -0
  412. package/src/kit/workspace-document-restore.ts +146 -0
  413. package/src/kit/workspace-host-commands.ts +141 -0
  414. package/src/kit/workspace-persistence-gate.ts +40 -0
  415. package/src/kit/workspace-play-utilities.ts +44 -0
  416. package/src/kit/workspace-presets.ts +446 -0
  417. package/src/kit/workspace-regions.ts +276 -0
  418. package/src/kit/workspace-static-panels.ts +73 -0
  419. package/src/kit/workspace-status-registry.ts +121 -0
  420. package/src/kit/workspace-storage.ts +35 -0
  421. package/src/kit/workspace-style.ts +226 -0
  422. package/src/kit/workspace-utility-commands.ts +74 -0
  423. package/src/kit/workspace-utility-registry.ts +263 -0
  424. package/src/kit/world-adoption-event.ts +23 -0
  425. package/src/kit/world-adoption.ts +115 -0
  426. package/src/kit/world-canvas-viewport-state.ts +35 -0
  427. package/src/kit/world-document-routing.ts +104 -0
  428. package/src/kit/world-pan-state.ts +198 -0
  429. package/src/kit/write-pipe.ts +173 -0
  430. package/src/layout-arrangements.ts +5 -0
  431. package/src/layouts.tsx +108 -0
  432. package/src/looks.ts +16 -0
  433. package/src/project/output-roots.ts +73 -0
  434. package/src/project/tab-census.ts +155 -0
  435. package/src/project-tool-catalog.ts +104 -0
  436. package/src/selection.tsx +107 -0
  437. package/src/services.ts +18 -0
  438. package/src/session/build-report.ts +22 -0
  439. package/src/session/collaboration-types.ts +262 -0
  440. package/src/session/command-table.ts +327 -0
  441. package/src/session/discovery.ts +100 -0
  442. package/src/session/editor-brand.ts +48 -0
  443. package/src/session/editor-compatibility.ts +329 -0
  444. package/src/session/editor-control-lifecycle.ts +68 -0
  445. package/src/session/editor-control-protocol.ts +5 -0
  446. package/src/session/entrypoint-selection-readers.ts +66 -0
  447. package/src/session/entrypoint-selection-source.ts +120 -0
  448. package/src/session/game-css-scope.ts +30 -0
  449. package/src/session/hosted-attachment.ts +225 -0
  450. package/src/session/limited-view.ts +82 -0
  451. package/src/session/product-create.ts +24 -0
  452. package/src/session/product-locator.ts +478 -0
  453. package/src/session/project-module-url.ts +242 -0
  454. package/src/session/project-serving.ts +164 -0
  455. package/src/session/project-upgrade.ts +669 -0
  456. package/src/session/registry-format.ts +210 -0
  457. package/src/session/relative-path-guard.ts +56 -0
  458. package/src/session/scoped-game-css.ts +461 -0
  459. package/src/session/source-glob.ts +15 -0
  460. package/src/session/tool-contribution-convention.ts +123 -0
  461. package/src/session/workbench-locator.ts +712 -0
  462. package/src/session.ts +41 -0
  463. package/src/share.ts +160 -0
  464. package/src/source-analysis.ts +28 -0
  465. package/src/source-authoring.ts +439 -0
  466. package/src/tools/errors.ts +91 -0
  467. package/src/tools/provider-execution.ts +70 -0
  468. package/src/tools/registry.ts +341 -0
  469. package/src/tools/types.ts +159 -0
  470. package/src/transport.ts +100 -0
  471. package/src/types.ts +1693 -0
  472. package/src/views.ts +164 -0
  473. package/src/widgets/design-system.ts +93 -0
  474. package/src/widgets/editor-appearance.ts +151 -0
  475. package/src/widgets/editor-material.ts +83 -0
  476. package/src/widgets/icon-set-registry.ts +105 -0
  477. package/src/widgets/index.ts +71 -0
  478. package/src/widgets/inspector-widgets/AlignmentGrid.tsx +182 -0
  479. package/src/widgets/inspector-widgets/AssetSlotPicker.tsx +123 -0
  480. package/src/widgets/inspector-widgets/BorderEditor.tsx +309 -0
  481. package/src/widgets/inspector-widgets/ColorPicker.tsx +549 -0
  482. package/src/widgets/inspector-widgets/CurveEditor.tsx +359 -0
  483. package/src/widgets/inspector-widgets/FilterEditor.tsx +108 -0
  484. package/src/widgets/inspector-widgets/FontPicker.tsx +191 -0
  485. package/src/widgets/inspector-widgets/GradientEditor.tsx +623 -0
  486. package/src/widgets/inspector-widgets/ScrubbableInput.tsx +180 -0
  487. package/src/widgets/inspector-widgets/ShadowEditor.tsx +319 -0
  488. package/src/widgets/inspector-widgets/color-utils.ts +201 -0
  489. package/src/widgets/inspector-widgets/curve-utils.ts +212 -0
  490. package/src/widgets/inspector-widgets/index.ts +25 -0
  491. package/src/widgets/inspector-widgets/shared.tsx +140 -0
  492. package/src/widgets/interactive-edit-scope.ts +33 -0
  493. package/src/widgets/patterns/Dialog.tsx +140 -0
  494. package/src/widgets/patterns/Fields.tsx +44 -0
  495. package/src/widgets/patterns/List.tsx +25 -0
  496. package/src/widgets/patterns/StateSurface.tsx +40 -0
  497. package/src/widgets/patterns/Surfaces.tsx +122 -0
  498. package/src/widgets/patterns/Tabs.tsx +80 -0
  499. package/src/widgets/patterns/Toolbar.tsx +72 -0
  500. package/src/widgets/patterns/Tree.tsx +72 -0
  501. package/src/widgets/primitives/AnchoredMenu.tsx +260 -0
  502. package/src/widgets/primitives/Button.tsx +62 -0
  503. package/src/widgets/primitives/ColorInput.tsx +78 -0
  504. package/src/widgets/primitives/DraftTextInput.tsx +63 -0
  505. package/src/widgets/primitives/EditorIcon.tsx +157 -0
  506. package/src/widgets/primitives/FormControls.tsx +88 -0
  507. package/src/widgets/primitives/HoverPreview.tsx +96 -0
  508. package/src/widgets/primitives/JsonInput.tsx +113 -0
  509. package/src/widgets/primitives/Layout.tsx +100 -0
  510. package/src/widgets/primitives/Menu.tsx +161 -0
  511. package/src/widgets/primitives/NumberInput.tsx +169 -0
  512. package/src/widgets/primitives/Panel.tsx +80 -0
  513. package/src/widgets/primitives/SectionHeader.tsx +77 -0
  514. package/src/widgets/primitives/Text.tsx +54 -0
  515. package/src/widgets/primitives/ThemeRootPortal.tsx +52 -0
  516. package/src/widgets/primitives/Tooltip.tsx +204 -0
  517. package/src/widgets/primitives/Vec3Input.tsx +70 -0
  518. package/src/widgets/primitives/banner-tones.ts +32 -0
  519. package/src/widgets/primitives/clamp-to-viewport.ts +44 -0
  520. package/src/widgets/primitives/editor-icons.ts +254 -0
  521. package/src/widgets/primitives/panel-header-styles.ts +42 -0
  522. package/src/widgets/theme.ts +2841 -0
  523. package/src/widgets/z-index.ts +25 -0
@@ -0,0 +1,669 @@
1
+ /**
2
+ * `upgrade [version]` — MOVE A PROJECT TO ONE RELEASE: its @volter packages AND its engine pin,
3
+ * together. Any product's CLI exposes it by passing who it is (`UpgradingProduct`) and declaring
4
+ * `volter.product.upgrade: true`, which is what lets the pinned-engine refusal name this verb
5
+ * (`./editor-compatibility`). It lives in the SDK's session half, beside the product and
6
+ * workbench locators, because a product CLI may import that and not editor-core's internals
7
+ * (`scripts/check-boundaries.mjs`).
8
+ *
9
+ * Why this exists (owner's stream, 2026-10-06): a project pinned to 0.5.185 had its packages
10
+ * upgraded to 0.5.189 and then would not open — the editor refuses a project whose
11
+ * `engine.version` differs from the engine it runs, and nothing that upgrades packages moves that
12
+ * pin. So every upgrade ended at the same refusal, and there was no command that did both.
13
+ *
14
+ * WHAT A RELEASE IS: the product package at `version`, and the versions it depends on. Its
15
+ * `@volter/editor-core` is the engine a project pins (`create.ts` stamps the same one), its
16
+ * `@volter/project` is the kit version the lockstep kit packages (`play`,
17
+ * `editor-ui`, …) install at. The running product answers for its own version with no network;
18
+ * any other version, and `latest`, is asked of the registry (`npm view`).
19
+ *
20
+ * IT EDITS TWO FILES AND NOTHING ELSE — `package.json` and `volter.project.json` — and in each
21
+ * only the rows it moves, in the file's own layout (add-play's rule, #133). It does not install:
22
+ * like `add-play`, it says `npm install` when a version changed, because the install is the
23
+ * person's and a checkout-linked project has nothing to install at all.
24
+ *
25
+ * A PRODUCT THAT WAS RENAMED (`UpgradingProduct.replaces`) is the one exception, because a
26
+ * project on the old name has no other way across: the old package is no longer published, and
27
+ * its own `upgrade` only knows its own name. So the NEW package's `upgrade` is the door — run as
28
+ * `npx <new package> upgrade` from inside the old project (`upgradeLine`) — and it moves the
29
+ * dependency to the new name, the `package.json` scripts that call the old command, and the
30
+ * files `create` wrote that name the old package or command (`.mcp.json`, `.codex/config.toml`,
31
+ * `AGENTS.md`, `CLAUDE.md`), so the project's agent servers and instructions keep working.
32
+ */
33
+ import { spawn } from 'node:child_process';
34
+ import { existsSync, lstatSync, readFileSync } from 'node:fs';
35
+ import { mkdir, readdir, readFile, rename, rm, rmdir, writeFile } from 'node:fs/promises';
36
+ import { dirname, join, relative, resolve, sep } from 'node:path';
37
+ import { MANIFEST_FILENAME } from '@volter/project/manifest/filename';
38
+ import { hasManifest } from '@volter/project/manifest/locate';
39
+ import { compareSemver } from './editor-compatibility';
40
+
41
+ export const UPGRADE_USAGE = "upgrade [version] # move this project's @volter packages and its engine pin to one release (default: latest); from a project made before 0.5.203, run the newest release's own: npx <package>@latest upgrade";
42
+
43
+ /** A product name that is no longer published, as a project still spells it. */
44
+ export interface RetiredProduct {
45
+ readonly packageName: string;
46
+ readonly command: string;
47
+ /** The names a project's own files use for the retired product, longest first, each with the
48
+ * name that replaces it. Each is replaced only as a whole word. */
49
+ readonly names: readonly (readonly [retired: string, current: string])[];
50
+ }
51
+
52
+ /** Who is upgrading: the product package a project declares, its command, and where it is installed. */
53
+ export interface UpgradingProduct {
54
+ readonly packageName: string;
55
+ readonly command: string;
56
+ /** The running product's package root — it answers for its own version without the registry. */
57
+ readonly dir: string;
58
+ /** The names this product replaced: a project declaring one is moved onto this product. */
59
+ readonly replaces?: readonly RetiredProduct[];
60
+ /** This project's `node_modules` is linked by the product after the upgrade (the game editor's runtime image),
61
+ * even where no link stands yet: it is never told to `npm install`, and no lockfile is rewritten. */
62
+ readonly linksNodeModules?: boolean;
63
+ }
64
+
65
+ /** The ONE line that moves a project onto `product`, from any version — including a project on a
66
+ * name `product` replaced, whose own installed command does not know `product` exists. */
67
+ export function upgradeLine(product: Pick<UpgradingProduct, 'packageName'>): string {
68
+ return `npx ${product.packageName}@latest upgrade`;
69
+ }
70
+
71
+ /** The retired product a project's `package.json` still declares, or null. */
72
+ export function declaredRetiredProduct(product: Pick<UpgradingProduct, 'replaces'>, project: string): RetiredProduct | null {
73
+ let pkg: PackageJson;
74
+ try {
75
+ pkg = JSON.parse(readFileSync(join(project, 'package.json'), 'utf8')) as PackageJson;
76
+ } catch {
77
+ return null;
78
+ }
79
+ return product.replaces?.find(retired =>
80
+ pkg.dependencies?.[retired.packageName] !== undefined || pkg.devDependencies?.[retired.packageName] !== undefined) ?? null;
81
+ }
82
+
83
+ /** The refusal every verb but `upgrade` gives a project still on a retired name. */
84
+ export function retiredProjectError(product: Pick<UpgradingProduct, 'packageName' | 'command'>, retired: RetiredProduct, project: string): Error {
85
+ return new Error(
86
+ `${project} is a ${retired.packageName} project, and ${retired.packageName} is now ${product.packageName} (command: ${product.command}). ` +
87
+ `Move the project onto it from the project folder, then run ${product.command} again:\n ${upgradeLine(product)}`,
88
+ );
89
+ }
90
+
91
+ /** The files `create` writes that name the product's package or command, project-relative. */
92
+ const RENAMED_PRODUCT_FILES = ['.mcp.json', '.codex/config.toml', 'AGENTS.md', 'CLAUDE.md'] as const;
93
+
94
+ /** The kit packages 0.5.203 renamed (#284), old name first. */
95
+ const RENAMED_KIT_PACKAGES = [
96
+ ['@volter/editor-project', '@volter/project'],
97
+ ['@volter/editor-sdk', '@volter/sdk'],
98
+ ['@volter/editor-live', '@volter/live'],
99
+ ['@volter/editor-model-play', '@volter/play'],
100
+ ] as const;
101
+
102
+ /**
103
+ * What 0.5.203 (#286) moved from where a project kept it to `editor/`: the adapter from the project's root, and the
104
+ * project's contributions and tools from `src/` (`tool-contribution-convention` read `src/contributions` and
105
+ * `src/tools` until then). Project-relative, from and to.
106
+ */
107
+ const MOVED_TO_EDITOR = [
108
+ ['volter.adapter.ts', 'editor/volter.adapter.ts'],
109
+ ['src/contributions', 'editor/contributions'],
110
+ ['src/tools', 'editor/tools'],
111
+ ] as const;
112
+
113
+ /** Every file under `path` (or `path` itself, a file); none when it is gone. */
114
+ async function allFiles(path: string): Promise<string[]> {
115
+ let entries: import('node:fs').Dirent[];
116
+ try { entries = await readdir(path, { withFileTypes: true }); } catch { return existsSync(path) ? [path] : []; }
117
+ const files: string[] = [];
118
+ for (const entry of entries) {
119
+ const child = join(path, entry.name);
120
+ if (entry.isDirectory()) files.push(...await allFiles(child));
121
+ else files.push(child);
122
+ }
123
+ return files;
124
+ }
125
+
126
+ /** Every script file under `dir`, skipping `node_modules` and dot-folders. */
127
+ async function scriptFiles(dir: string): Promise<string[]> {
128
+ const files: string[] = [];
129
+ let entries: import('node:fs').Dirent[];
130
+ try { entries = await readdir(dir, { withFileTypes: true }); } catch { return files; }
131
+ for (const entry of entries) {
132
+ if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue;
133
+ const path = join(dir, entry.name);
134
+ if (entry.isDirectory()) files.push(...await scriptFiles(path));
135
+ else if (/\.(?:[cm]?[jt]sx?)$/.test(entry.name)) files.push(path);
136
+ }
137
+ return files;
138
+ }
139
+
140
+ /** A project's own source that may import the kit: its root script files (the adapter, `vite.config.ts`),
141
+ * and everything under `editor/`, `src/` and `scripts/`. */
142
+ async function projectSourceFiles(project: string): Promise<string[]> {
143
+ const files: string[] = [];
144
+ try {
145
+ for (const entry of await readdir(project, { withFileTypes: true })) {
146
+ if (entry.isFile() && /\.(?:[cm]?[jt]sx?)$/.test(entry.name)) files.push(join(project, entry.name));
147
+ }
148
+ } catch { /* an unreadable project root is refused before this */ }
149
+ for (const folder of ['editor', 'src', 'scripts']) files.push(...await scriptFiles(join(project, folder)));
150
+ return files;
151
+ }
152
+
153
+ /**
154
+ * `text` naming what 0.5.203 moved where it now is: `src/contributions` and `src/tools` as path segments, and the
155
+ * root adapter (`volter.adapter.ts` not already under `editor/`). For the instructions and the catalog records a
156
+ * project carries, and the path strings in its own scripts (`check-idioms.ts` names both folders).
157
+ */
158
+ function rewriteMovedPaths(text: string): string {
159
+ return text
160
+ .replace(/(^|[^\w./-])(\.\/)?src\/(contributions|tools)(?=$|[/'"`\s),;:\]]|\.$)/gm, '$1$2editor/$3')
161
+ .replace(/(^|[^\w./-])(\.\/)?volter\.adapter\.ts\b/gm, '$1$2editor/volter.adapter.ts');
162
+ }
163
+
164
+ /** The text files that name a project's places in words or as data: the instructions `create` wrote (any root
165
+ * Markdown, and the agent skills under `.claude/`, `.agents/` and `.github/`), the root TypeScript configs (their
166
+ * `include`, `exclude` and `paths`) and the catalog's records (`.volter/`, its JSON). Never `node_modules`. */
167
+ async function projectTextFiles(project: string): Promise<string[]> {
168
+ const files: string[] = [];
169
+ try {
170
+ for (const entry of await readdir(project, { withFileTypes: true })) {
171
+ if (entry.isFile() && /(?:\.md|^tsconfig(?:\.[\w-]+)?\.json)$/i.test(entry.name)) files.push(join(project, entry.name));
172
+ }
173
+ } catch { /* an unreadable project root is refused before this */ }
174
+ const under = async (dir: string, pattern: RegExp): Promise<void> => {
175
+ for (const path of await allFiles(dir)) if (!path.includes(`${sep}node_modules${sep}`) && pattern.test(path)) files.push(path);
176
+ };
177
+ for (const folder of ['.claude', '.agents', '.github']) await under(join(project, folder), /\.md$/i);
178
+ await under(join(project, '.volter', 'catalog'), /\.json$/i);
179
+ if (existsSync(join(project, '.volter', 'scaffold-baseline.json'))) files.push(join(project, '.volter', 'scaffold-baseline.json'));
180
+ return files;
181
+ }
182
+
183
+ /** `text` with every import of a renamed kit package (the package itself or a subpath) under its current name, and
184
+ * every path that names its installed folder (`node_modules/@volter/editor-project/src/*`, a tsconfig's `paths`). */
185
+ function renameKitSpecifiers(text: string): string {
186
+ let out = text;
187
+ for (const [from, to] of RENAMED_KIT_PACKAGES) {
188
+ out = out.replace(new RegExp(`(['"\`]|node_modules\\/)${from.replace('/', '\\/')}(?=[/'"\`])`, 'g'), `$1${to}`);
189
+ }
190
+ return out;
191
+ }
192
+
193
+ /**
194
+ * `text`, a module moving from `fromDir` to `toDir` while the files in `moved` (old path to new) move too, with each
195
+ * relative specifier still naming the same file: `from`, `import`, `import()`, `require()`, `new URL()` and
196
+ * `import.meta.glob()`, and a bare `.` or `..`. A specifier whose module and target both stay is left as written.
197
+ */
198
+ function rebaseRelativeSpecifiers(text: string, fromDir: string, toDir: string, moved: (path: string) => string): string {
199
+ return text.replace(
200
+ /(\bfrom\s*|\bimport\s*\(\s*|\bimport\s+|\brequire\s*\(\s*|\bnew\s+URL\s*\(\s*|\bimport\.meta\.glob\s*\(\s*)(['"`])(\.{1,2}(?:\/[^'"`]*)?)\2/g,
201
+ (whole: string, lead: string, quote: string, spec: string) => {
202
+ const target = resolve(fromDir, spec);
203
+ if (fromDir === toDir && moved(target) === target) return whole;
204
+ let next = relative(toDir, moved(target)).split('\\').join('/');
205
+ if (next === '') next = '.';
206
+ if (!next.startsWith('.')) next = `./${next}`;
207
+ if (spec.endsWith('/') && !next.endsWith('/')) next = `${next}/`;
208
+ return `${lead}${quote}${next}${quote}`;
209
+ },
210
+ );
211
+ }
212
+
213
+ /** `text` as a whole word — not part of a longer command, package or name it is the start or the
214
+ * end of (`<command>-x`, a name's plural). A path after it (`<package>/package.json`) or
215
+ * punctuation (a name's `'s`) still ends the word. */
216
+ function wholeWord(text: string): RegExp {
217
+ return new RegExp(`(?<![\\w-])${text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![\\w-])`, 'g');
218
+ }
219
+
220
+ /** `entries` with the key `from` renamed to `to` where it stands, so a rewrite keeps its order. */
221
+ function renameKey<T>(entries: Record<string, T>, from: string, to: string, value: T): Record<string, T> {
222
+ return Object.fromEntries(Object.entries(entries).map(([key, current]) => key === from ? [to, value] : [key, current]));
223
+ }
224
+
225
+ interface Release {
226
+ readonly version: string;
227
+ readonly dependencies: Readonly<Record<string, string>>;
228
+ }
229
+
230
+ interface PackageJson {
231
+ dependencies?: Record<string, string>;
232
+ devDependencies?: Record<string, string>;
233
+ peerDependencies?: Record<string, string>;
234
+ optionalDependencies?: Record<string, string>;
235
+ workspaces?: unknown;
236
+ [key: string]: unknown;
237
+ }
238
+
239
+ /** The exact version a declared spec names (`0.5.185`, `^0.5.185`, `=0.5.185`), or null for a
240
+ * range, a tag, a path or a link — nothing a direction can be read from. */
241
+ function declaredVersion(spec: string | undefined): string | null {
242
+ const bare = spec?.replace(/^[\^~=v]+/, '');
243
+ return bare !== undefined && EXACT_VERSION.test(bare) ? bare : null;
244
+ }
245
+
246
+ /** The version a kit package is declared at, for telling which packages move together: an exact spec's, or a
247
+ * local tarball's (`file:…/volter-play-0.5.202.tgz`, how a project built from a checkout declares the kit). */
248
+ function kitVersion(spec: string | undefined): string | null {
249
+ return declaredVersion(spec) ?? spec?.match(/^file:.*-(\d+\.\d+\.\d+)\.tgz$/)?.[1] ?? null;
250
+ }
251
+
252
+ /**
253
+ * Write every planned file, or none: each to a temp file beside it first, then all renamed into
254
+ * place. A failure while writing leaves the project as it was; a failure between the renames
255
+ * puts back what was already moved. Until 2026-10-06 (#146 review) the two files were written
256
+ * one after the other, so a failure on the second left a project with new packages and the old
257
+ * pin — exactly the mismatch this verb exists to end.
258
+ */
259
+ async function writeAll(files: readonly { readonly path: string; readonly content: string; readonly original: string; readonly created?: boolean }[]): Promise<void> {
260
+ const temp = (path: string) => `${path}.upgrade-${process.pid}.tmp`;
261
+ try {
262
+ for (const file of files) await writeFile(temp(file.path), file.content);
263
+ } catch (error) {
264
+ await Promise.all(files.map(file => rm(temp(file.path), { force: true })));
265
+ throw new Error(`The upgrade was not written (nothing changed): ${error instanceof Error ? error.message : String(error)}`);
266
+ }
267
+ const moved: typeof files[number][] = [];
268
+ try {
269
+ for (const file of files) { await rename(temp(file.path), file.path); moved.push(file); }
270
+ } catch (error) {
271
+ // A file this upgrade made is removed again; any other gets its old content back.
272
+ for (const file of moved) await (file.created ? rm(file.path, { force: true }) : writeFile(file.path, file.original)).catch(() => {});
273
+ await Promise.all(files.map(file => rm(temp(file.path), { force: true })));
274
+ throw new Error(`The upgrade could not be put in place, and what had moved was put back: ${error instanceof Error ? error.message : String(error)}`);
275
+ }
276
+ }
277
+
278
+ /**
279
+ * THE LOCK FORGETS THE OLD @volter FAMILY (2026-10-08). Its packages pin one another exactly
280
+ * (`editor-blender` has a peer `@volter/sdk@<its release>`), and npm checks a new member's pins
281
+ * against the old members the lock still holds before it replaces them, so the `npm install`
282
+ * this verb prints refused every installed project with ERESOLVE. Dropping the `@volter/` rows
283
+ * from `package-lock.json` and npm's hidden `node_modules/.package-lock.json` lets npm resolve
284
+ * the family again from package.json; every other package keeps its locked version.
285
+ */
286
+ async function lockWithoutVolter(path: string): Promise<{ path: string; content: string; original: string } | null> {
287
+ let raw: string;
288
+ let lock: { packages?: Record<string, unknown>; dependencies?: Record<string, unknown> };
289
+ try { raw = await readFile(path, 'utf8'); lock = JSON.parse(raw); } catch { return null; }
290
+ let dropped = 0;
291
+ for (const key of Object.keys(lock.packages ?? {}))
292
+ if (/(^|\/)node_modules\/@volter\//.test(key)) { delete lock.packages![key]; dropped++; }
293
+ for (const key of Object.keys(lock.dependencies ?? {}))
294
+ if (key.startsWith('@volter/')) { delete lock.dependencies![key]; dropped++; }
295
+ return dropped > 0 ? { path, content: jsonLayout(raw)(lock), original: raw } : null;
296
+ }
297
+
298
+ /** An exact release — a range would leave the pin and the install free to disagree again. */
299
+ const EXACT_VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
300
+
301
+ /**
302
+ * A JSON file's own layout, so a rewrite changes the rows it moves and nothing else. The same rule
303
+ * as `add-play`'s `jsonLayout` (packages/cyclotron/node/add-play.ts, #133); kept beside the
304
+ * verb that shares it across products rather than reaching into one product's CLI.
305
+ */
306
+ function jsonLayout(raw: string): (value: unknown) => string {
307
+ const indent = /^[{[]\r?\n([ \t]+)\S/.exec(raw)?.[1] ?? ' ';
308
+ const eol = raw.includes('\r\n') ? '\r\n' : '\n';
309
+ const trailing = /\r?\n$/.test(raw) ? eol : '';
310
+ return value => JSON.stringify(value, null, indent).replace(/\n/g, eol) + trailing;
311
+ }
312
+
313
+ /** `npm view <spec> version dependencies --json`, parsed. */
314
+ function npmView(spec: string): Promise<Release> {
315
+ return new Promise((done, fail) => {
316
+ // npm is npm.cmd on Windows, and node refuses to spawn a .cmd without a shell (EINVAL). The shell gets one
317
+ // command line (arguments beside `shell` print Node's DEP0190 warning), so the spec, which carries the version
318
+ // the person typed, is a package name and a version or tag, nothing a shell reads.
319
+ if (!/^@?[\w.-]+(?:\/[\w.-]+)?@[\w.+-]+$/.test(spec)) {
320
+ fail(new Error(`${spec} is not a package and a version or tag.`));
321
+ return;
322
+ }
323
+ const child = process.platform === 'win32'
324
+ ? spawn(`npm view ${spec} version dependencies --json`, { windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'], shell: true })
325
+ : spawn('npm', ['view', spec, 'version', 'dependencies', '--json'], { stdio: ['ignore', 'pipe', 'pipe'] });
326
+ let out = '';
327
+ let err = '';
328
+ child.stdout.on('data', (chunk: Buffer) => { out += chunk.toString('utf8'); });
329
+ child.stderr.on('data', (chunk: Buffer) => { err += chunk.toString('utf8'); });
330
+ child.once('error', fail);
331
+ child.once('exit', code => {
332
+ try {
333
+ const answer = JSON.parse(out) as { version?: unknown; dependencies?: unknown; error?: { summary?: unknown } };
334
+ if (code === 0 && typeof answer.version === 'string') {
335
+ done({ version: answer.version, dependencies: (answer.dependencies ?? {}) as Record<string, string> });
336
+ return;
337
+ }
338
+ fail(new Error(typeof answer.error?.summary === 'string' ? answer.error.summary : `npm view exited ${code}`));
339
+ } catch {
340
+ fail(new Error(err.trim().split('\n').at(-1) || `npm view exited ${code}`));
341
+ }
342
+ });
343
+ });
344
+ }
345
+
346
+ async function readRelease(product: UpgradingProduct, requested: string | undefined): Promise<Release> {
347
+ const running = JSON.parse(await readFile(join(product.dir, 'package.json'), 'utf8')) as { version: string; dependencies?: Record<string, string> };
348
+ if (requested === running.version) return { version: running.version, dependencies: running.dependencies ?? {} };
349
+ const spec = `${product.packageName}@${requested ?? 'latest'}`;
350
+ try {
351
+ return await npmView(spec);
352
+ } catch (error) {
353
+ const reason = error instanceof Error ? error.message : String(error);
354
+ throw new Error(`Could not read ${spec} from the registry (${reason}). Name a published version: ${product.command} upgrade <version>.`);
355
+ }
356
+ }
357
+
358
+ export async function upgradeProject(product: UpgradingProduct, requested?: string, cwd = process.cwd()): Promise<void> {
359
+ if (requested !== undefined && !EXACT_VERSION.test(requested))
360
+ throw new Error(`${product.command} upgrade takes an exact version (e.g. 0.5.189), not "${requested}".`);
361
+ // The project around the working directory, as `edit` with no folder finds it.
362
+ let project = resolve(cwd);
363
+ while (!hasManifest(project)) {
364
+ const parent = dirname(project);
365
+ if (parent === project) throw new Error(`Run ${product.command} upgrade inside a project: there is no ${MANIFEST_FILENAME} here or above.`);
366
+ project = parent;
367
+ }
368
+ const packagePath = join(project, 'package.json');
369
+ if (!existsSync(packagePath)) throw new Error(`${project} has no package.json to upgrade.`);
370
+
371
+ const release = await readRelease(product, requested);
372
+ const engine = release.dependencies['@volter/editor-core'];
373
+ const kit = release.dependencies['@volter/project'];
374
+ if (!engine || !kit)
375
+ throw new Error(`${product.packageName}@${release.version} declares no @volter/editor-core and @volter/project, so there is no engine to pin.`);
376
+
377
+ // PLAN BOTH FILES, THEN WRITE: a refusal below leaves the project as it was.
378
+ const changed: string[] = [];
379
+ const kept: string[] = [];
380
+ const warnings: string[] = [];
381
+
382
+ // 1. THE PACKAGES. The product moves to the release; a package the release depends on moves
383
+ // to that version; a lockstep kit package (declared at the project's old kit version) moves to
384
+ // the new kit version. Any other @volter package is the author's choice and is named, not moved.
385
+ const packageRaw = await readFile(packagePath, 'utf8');
386
+ const pkg = JSON.parse(packageRaw) as PackageJson;
387
+ const previousKit = pkg.devDependencies?.['@volter/project'] ?? pkg.dependencies?.['@volter/project']
388
+ ?? pkg.devDependencies?.['@volter/editor-project'] ?? pkg.dependencies?.['@volter/editor-project'];
389
+ // Read before a retired name is moved below: a project on a retired name declares none of
390
+ // this product, and its direction is then read by its engine pin.
391
+ const currentProduct = declaredVersion(pkg.dependencies?.[product.packageName] ?? pkg.devDependencies?.[product.packageName]);
392
+
393
+ // NAMED, NOT MOVED: what this verb deliberately leaves to the author is listed, never skipped
394
+ // silently (#146 review) — peer and optional ranges are a package author's contract, and a
395
+ // workspace's members are projects of their own.
396
+ for (const section of ['peerDependencies', 'optionalDependencies'] as const) {
397
+ for (const [name, current] of Object.entries(pkg[section] ?? {})) {
398
+ if (name.startsWith('@volter/')) kept.push(`package.json ${section} keeps ${name}@${current}: ${section} are not moved by upgrade; edit them by hand if they should follow`);
399
+ }
400
+ }
401
+ if (pkg.workspaces !== undefined) {
402
+ const members = Array.isArray(pkg.workspaces) ? pkg.workspaces
403
+ : Array.isArray((pkg.workspaces as { packages?: unknown }).packages) ? (pkg.workspaces as { packages: unknown[] }).packages : [];
404
+ kept.push(`workspace members${members.length > 0 ? ` (${members.map(String).join(', ')})` : ''} are not upgraded: run ${product.command} upgrade in each member that is a project`);
405
+ }
406
+
407
+ let packagesChanged = false;
408
+ // 0. A RETIRED NAME becomes this product, where it stood and at the release — a project that
409
+ // already declares this product as well just loses the old row.
410
+ const retired = declaredRetiredProduct(product, project);
411
+ const textFiles: { path: string; content: string; original: string; created?: boolean }[] = [];
412
+ if (retired !== null) {
413
+ const declaresProduct = pkg.dependencies?.[product.packageName] !== undefined || pkg.devDependencies?.[product.packageName] !== undefined;
414
+ for (const section of ['dependencies', 'devDependencies'] as const) {
415
+ const declared = pkg[section];
416
+ const current = declared?.[retired.packageName];
417
+ if (declared === undefined || current === undefined) continue;
418
+ if (declaresProduct) {
419
+ delete declared[retired.packageName];
420
+ changed.push(`package.json ${section}: ${retired.packageName} ${current} removed (${product.packageName} is already declared)`);
421
+ } else {
422
+ pkg[section] = renameKey(declared, retired.packageName, product.packageName, release.version);
423
+ changed.push(`package.json ${section}: ${retired.packageName} ${current} -> ${product.packageName} ${release.version}`);
424
+ }
425
+ packagesChanged = true;
426
+ }
427
+ // The scripts that call the old command (`create` wrote `dev` and one named for the command).
428
+ const scripts = pkg['scripts'];
429
+ if (scripts !== null && typeof scripts === 'object' && !Array.isArray(scripts)) {
430
+ let rewritten = scripts as Record<string, unknown>;
431
+ for (const [name, body] of Object.entries(rewritten)) {
432
+ const nextBody = typeof body === 'string' ? body.replace(wholeWord(retired.command), product.command) : body;
433
+ const nextName = name === retired.command && !(product.command in rewritten) ? product.command : name;
434
+ if (nextBody === body && nextName === name) continue;
435
+ rewritten = renameKey(rewritten, name, nextName, nextBody);
436
+ changed.push(`package.json scripts: ${name === nextName ? name : `${name} -> ${nextName}`}${nextBody === body ? '' : ` runs "${String(nextBody)}"`}`);
437
+ }
438
+ pkg['scripts'] = rewritten;
439
+ }
440
+ // The files that name the package or the command: the agent servers resolve the product's
441
+ // `bin` by package name, and the instructions tell the agent which command to run.
442
+ for (const file of RENAMED_PRODUCT_FILES) {
443
+ const path = join(project, file);
444
+ if (!existsSync(path)) continue;
445
+ const original = await readFile(path, 'utf8');
446
+ // Only exact references to the product move — its package, its command and its names, each
447
+ // as a whole word; any other phrase that happens to contain them is the author's and stays.
448
+ let content = original.replace(wholeWord(retired.packageName), product.packageName).replace(wholeWord(retired.command), product.command);
449
+ for (const [from, to] of retired.names) content = content.replace(wholeWord(from), to);
450
+ if (content === original) continue;
451
+ textFiles.push({ path, content, original });
452
+ changed.push(`${file}: names ${product.packageName} and \`${product.command}\``);
453
+ }
454
+ }
455
+ // 0b. THE KIT'S OLD NAMES AND THE EDITOR FOLDER (0.5.203: #284 renamed the runtime packages, #286 moved a
456
+ // project's editor side into `editor/`). A project made before 0.5.203 declares the kit under its old names and
457
+ // keeps its editor side where #286 moved it from (the adapter at its root, contributions and tools in `src/`),
458
+ // and the release names neither; left alone, the upgraded project was refused on its next open, or opened
459
+ // without its tools. The names move where they stand, in package.json and in the project's own source, and what
460
+ // #286 moved moves into `editor/`, each file with its relative imports rebased.
461
+ for (const section of ['dependencies', 'devDependencies'] as const) {
462
+ const declared = pkg[section];
463
+ if (declared === undefined) continue;
464
+ for (const [from, to] of RENAMED_KIT_PACKAGES) {
465
+ const current = declared[from];
466
+ if (current === undefined) continue;
467
+ if (declared[to] !== undefined) {
468
+ delete declared[from];
469
+ changed.push(`package.json ${section}: ${from} removed (${to} is already declared)`);
470
+ } else {
471
+ pkg[section] = renameKey(pkg[section]!, from, to, current);
472
+ changed.push(`package.json ${section}: ${from} -> ${to}`);
473
+ }
474
+ packagesChanged = true;
475
+ }
476
+ }
477
+ const projectPath = (path: string) => relative(project, path).split('\\').join('/');
478
+ // FILE BY FILE, so a second run finishes what a first left: each file of a moved folder (or the adapter) moves
479
+ // unless a file is already at its destination, which is said and left. A destination folder that exists (empty,
480
+ // or holding what a first run moved) is merged into, not taken as a reason to skip the move.
481
+ const moves: { from: string; to: string }[] = [];
482
+ for (const [from, to] of MOVED_TO_EDITOR) {
483
+ const source = join(project, from);
484
+ if (existsSync(source)) moves.push({ from: source, to: join(project, to) });
485
+ }
486
+ const movedPath = (path: string): string => {
487
+ for (const move of moves) {
488
+ if (path === move.from) return move.to;
489
+ if (path.startsWith(move.from + sep)) return move.to + path.slice(move.from.length);
490
+ // The adapter imported without its extension, or as the `.js` a build would emit.
491
+ const bare = move.from.replace(/\.ts$/, '');
492
+ if (bare !== move.from && (path === bare || path === `${bare}.js`)) return move.to.replace(/\.ts$/, '') + path.slice(bare.length);
493
+ }
494
+ return path;
495
+ };
496
+ const collides = (path: string) => movedPath(path) !== path && existsSync(movedPath(path));
497
+ const stranded: string[] = [];
498
+ for (const move of moves) for (const path of await allFiles(move.from)) if (collides(path)) stranded.push(path);
499
+ for (const path of stranded)
500
+ kept.push(`${projectPath(path)} stays where it is and is not read: ${projectPath(movedPath(path))} already exists; keep one of the two by hand`);
501
+ const willMove = (path: string) => movedPath(path) !== path && !stranded.includes(path);
502
+ const leftBehind: string[] = [];
503
+ for (const path of await projectSourceFiles(project)) {
504
+ const original = await readFile(path, 'utf8');
505
+ const destination = willMove(path) ? movedPath(path) : path;
506
+ const content = rewriteMovedPaths(
507
+ rebaseRelativeSpecifiers(renameKitSpecifiers(original), dirname(path), dirname(destination), (p) => (willMove(p) ? movedPath(p) : p)),
508
+ );
509
+ if (destination !== path) {
510
+ textFiles.push({ path: destination, content, original: '', created: true });
511
+ leftBehind.push(path);
512
+ } else if (content !== original) {
513
+ textFiles.push({ path, content, original });
514
+ changed.push(`${projectPath(path)}: names the kit's current packages and the moved files where they now are`);
515
+ // 0.5.203 fails a game build that reaches into editor/ (src/ is the game): a game file that imported one of
516
+ // the moved files now does, and is named so the person moves what it needs out of editor/. A file left behind
517
+ // because its destination exists is already named, and is not read.
518
+ if (path.startsWith(join(project, 'src') + sep) && !stranded.includes(path) && /(['"`])(?:\.{1,2}\/)+[^'"`]*\beditor\//.test(content) && !/(['"`])(?:\.{1,2}\/)+[^'"`]*\beditor\//.test(original))
519
+ kept.push(`${projectPath(path)} now imports from editor/, which a game build refuses from 0.5.203: move what it uses out of editor/ (src/ is the game)`);
520
+ }
521
+ }
522
+ for (const move of moves) {
523
+ if ((await allFiles(move.from)).some((path) => !stranded.includes(path)))
524
+ changed.push(`${projectPath(move.from)} -> ${projectPath(move.to)} (a project's editor side lives in editor/ from 0.5.203)`);
525
+ }
526
+ // THE FILES THAT NAME THE OLD PLACES IN WORDS OR AS DATA: the instructions `create` wrote (AGENTS.md, IDIOMS.md,
527
+ // CLAUDE.md, the agent skills), the TypeScript configs (the tools' program includes `src/tools`; left, it found no
528
+ // files and `typecheck` failed) and the game editor's catalog records (`.volter/`), which name the files it wrote.
529
+ for (const path of await projectTextFiles(project)) {
530
+ const original = await readFile(path, 'utf8');
531
+ const content = rewriteMovedPaths(renameKitSpecifiers(original));
532
+ if (content === original) continue;
533
+ textFiles.push({ path, content, original });
534
+ changed.push(`${projectPath(path)}: names the moved files and the kit's packages where they now are`);
535
+ }
536
+ // A tool is registered in package.json by its path (`volter.tools`); a registration into a moved folder follows it.
537
+ const volter = pkg['volter'];
538
+ const tools = volter !== null && typeof volter === 'object' && !Array.isArray(volter) ? (volter as { tools?: unknown }).tools : undefined;
539
+ if (Array.isArray(tools)) {
540
+ const rewritten = tools.map((registration: unknown) => {
541
+ const entry = typeof registration === 'string' ? registration : (registration as { entry?: unknown } | null)?.entry;
542
+ if (typeof entry !== 'string' || !entry.startsWith('./')) return registration;
543
+ const next = `./${projectPath(movedPath(join(project, entry.slice(2))))}`;
544
+ if (next === entry) return registration;
545
+ changed.push(`package.json volter.tools: ${entry} -> ${next}`);
546
+ packagesChanged = true;
547
+ return typeof registration === 'string' ? next : { ...(registration as object), entry: next };
548
+ });
549
+ (volter as { tools: unknown[] }).tools = rewritten;
550
+ }
551
+
552
+ for (const section of ['dependencies', 'devDependencies'] as const) {
553
+ const declared = pkg[section];
554
+ if (!declared) continue;
555
+ for (const [name, current] of Object.entries(declared)) {
556
+ if (!name.startsWith('@volter/')) continue;
557
+ const target = name === product.packageName ? release.version : release.dependencies[name]
558
+ ?? (current === previousKit || (kitVersion(current) !== null && kitVersion(current) === kitVersion(previousKit)) ? kit : undefined);
559
+ if (target === undefined) {
560
+ kept.push(`package.json keeps ${name}@${current}: ${product.packageName}@${release.version} does not name it`);
561
+ continue;
562
+ }
563
+ if (current === target) continue;
564
+ declared[name] = target;
565
+ packagesChanged = true;
566
+ changed.push(current.startsWith('file:')
567
+ ? `package.json ${section}: ${name} was the local tarball ${current}; it is now ${target} from the registry`
568
+ : `package.json ${section}: ${name} ${current} -> ${target}`);
569
+ }
570
+ }
571
+
572
+ // 2. THE ENGINE PIN — the row the refusal names. Only `engine.version` moves.
573
+ const manifestPath = join(project, MANIFEST_FILENAME);
574
+ const manifestRaw = await readFile(manifestPath, 'utf8');
575
+ const manifest = JSON.parse(manifestRaw) as { engine?: Record<string, unknown>; [key: string]: unknown };
576
+ const pinned = manifest.engine?.['version'];
577
+ const pinChanged = pinned !== engine;
578
+
579
+ // NEVER BACKWARDS BY ACCIDENT (#146 review). `latest` can be older than what a project is on (a
580
+ // prerelease, a version published from a branch), and moving a project back silently downgrades
581
+ // its engine under it. So a backwards move happens only when the version was named, and is said.
582
+ // The product's own declared version decides; a project that declares it as a range or a link
583
+ // is read by its engine pin instead.
584
+ const backwards = currentProduct !== null
585
+ ? (compareSemver(release.version, currentProduct) === -1 ? `${product.packageName}@${currentProduct}` : null)
586
+ : typeof pinned === 'string' && compareSemver(engine, pinned) === -1 ? `engine ${pinned}` : null;
587
+ if (backwards !== null) {
588
+ if (requested === undefined)
589
+ throw new Error(
590
+ `${product.packageName}@latest is ${release.version} (engine ${engine}), older than the ${backwards} this project is on, so upgrading to it would move the project backwards. ` +
591
+ `Nothing was changed. To go back on purpose, name the version: ${product.command} upgrade ${release.version}.`,
592
+ );
593
+ warnings.push(`This moves the project BACKWARDS, from ${backwards} to ${product.packageName}@${release.version} (engine ${engine}), because that version was named.`);
594
+ }
595
+ if (pinChanged) {
596
+ manifest.engine = { ...manifest.engine, version: engine };
597
+ changed.push(`${MANIFEST_FILENAME} engine.version: ${typeof pinned === 'string' ? pinned : '(none)'} -> ${engine}`);
598
+ }
599
+
600
+ // A checkout's project links the checkout's own install, which already holds every kit
601
+ // package; `npm install` there would write into the checkout (`add-play`'s same rule).
602
+ const linked = product.linksNodeModules === true
603
+ || (() => { try { return lstatSync(join(project, 'node_modules')).isSymbolicLink(); } catch { return false; } })();
604
+ const locks = packagesChanged && !linked
605
+ ? (await Promise.all([join(project, 'package-lock.json'), join(project, 'node_modules', '.package-lock.json')]
606
+ .map(lockWithoutVolter))).filter((lock): lock is NonNullable<typeof lock> => lock !== null)
607
+ : [];
608
+ if (locks.length > 0) changed.push('package-lock.json: the old @volter rows dropped, so npm install resolves the new ones');
609
+
610
+ // The folders this run makes for the moved files are removed again if the write fails, so nothing of it is left.
611
+ const madeDirs: string[] = [];
612
+ for (const file of textFiles) {
613
+ if (!file.created) continue;
614
+ const chain: string[] = [];
615
+ for (let dir = dirname(file.path); !existsSync(dir) && dir !== dirname(dir); dir = dirname(dir)) chain.unshift(dir);
616
+ for (const dir of chain) { await mkdir(dir); madeDirs.push(dir); }
617
+ }
618
+ try {
619
+ await writeAll([
620
+ ...(packagesChanged ? [{ path: packagePath, content: jsonLayout(packageRaw)(pkg), original: packageRaw }] : []),
621
+ ...(pinChanged ? [{ path: manifestPath, content: jsonLayout(manifestRaw)(manifest), original: manifestRaw }] : []),
622
+ ...textFiles,
623
+ ...locks,
624
+ ]);
625
+ } catch (error) {
626
+ for (const dir of madeDirs.reverse()) await rmdir(dir).catch(() => undefined);
627
+ throw error;
628
+ }
629
+ // The moved scripts' new copies are in place; only then do the old ones go, and a moved folder's other files
630
+ // (data, a capability stamp) follow as they are. The project is upgraded by now, so a file that cannot be moved or
631
+ // removed (open in another program) is said, not thrown.
632
+ const failed: string[] = [];
633
+ const leftover = (path: string, error: unknown) => (failed.push(path),
634
+ kept.push(`${projectPath(path)} could not be removed (${error instanceof Error ? error.message : String(error)}); the editor does not read it, so delete it by hand`));
635
+ for (const path of leftBehind) await rm(path).catch((error: unknown) => leftover(path, error));
636
+ for (const move of moves) {
637
+ for (const path of await allFiles(move.from)) {
638
+ if (leftBehind.includes(path) || stranded.includes(path)) continue;
639
+ const to = movedPath(path);
640
+ await mkdir(dirname(to), { recursive: true });
641
+ await rename(path, to).catch((error: unknown) => leftover(path, error));
642
+ }
643
+ if (!move.from.endsWith('.ts') && ![...failed, ...stranded].some(path => path.startsWith(move.from + sep))) await rm(move.from, { recursive: true, force: true }).catch(() => undefined);
644
+ }
645
+ for (const line of warnings) console.warn(`! ${line}`);
646
+
647
+ console.log(changed.length > 0
648
+ ? `Upgraded ${project} to ${product.packageName}@${release.version} (engine ${engine}):`
649
+ : `${project} is already on ${product.packageName}@${release.version} (engine ${engine}).`);
650
+ for (const line of changed) console.log(` + ${line}`);
651
+ if (kept.length > 0) console.log('Left as it was:');
652
+ for (const line of kept) console.log(` = ${line}`);
653
+ if (changed.length === 0) {
654
+ // NOTHING TO MOVE, YET AN EDITOR REFUSED: then the editor that refused is not this project's
655
+ // own installation (an older global install, another checkout), and saying "already on"
656
+ // alone left the person with no next step (#146 review).
657
+ console.log(`If an editor still refuses this project, it is running from another installation: open it with this project's own, \`npx --no-install ${product.command} edit .\` in ${project}.`);
658
+ return;
659
+ }
660
+ if (retired !== null)
661
+ console.log(`This project now opens in ${product.packageName}. Its command is ${product.command}: from the project folder, \`npx --no-install ${product.command} <command>\`.`);
662
+ console.log('Next:');
663
+ if (packagesChanged && !linked) console.log(` npm install # in ${project}; installs the versions above`);
664
+ // A running session keeps the editor it started with: new packages need a new session, while a
665
+ // pin that moved alone is read again by the page's Retry.
666
+ console.log(packagesChanged
667
+ ? ` ${product.command} close && ${product.command} edit . # restart the session on the new install`
668
+ : ` Press Retry on the editor page that refused the project, or run ${product.command} edit .`);
669
+ }