@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.
- package/LICENSE +202 -0
- package/NOTICE +20 -0
- package/README.md +38 -3
- package/package.json +510 -4
- package/src/account.ts +210 -0
- package/src/chrome.ts +88 -0
- package/src/client.ts +1646 -0
- package/src/commands.ts +66 -0
- package/src/contributions.ts +619 -0
- package/src/css-numeric-style.ts +97 -0
- package/src/document-probe.ts +282 -0
- package/src/editor-view.ts +225 -0
- package/src/extension.ts +40 -0
- package/src/generations.ts +178 -0
- package/src/host.ts +1157 -0
- package/src/http-transport.browser.ts +14 -0
- package/src/http-transport.node.ts +19 -0
- package/src/index.ts +131 -0
- package/src/kit/CapabilityCoverageSection.tsx +185 -0
- package/src/kit/account-client.ts +333 -0
- package/src/kit/action-registry.ts +317 -0
- package/src/kit/active-product.ts +76 -0
- package/src/kit/active-project.ts +155 -0
- package/src/kit/adapter-editor-config.ts +25 -0
- package/src/kit/adapter-module.ts +7 -0
- package/src/kit/adapter-observation.ts +49 -0
- package/src/kit/animation/animation-clock.ts +479 -0
- package/src/kit/animation/stage-transport.ts +385 -0
- package/src/kit/api/assets.ts +365 -0
- package/src/kit/api/project-open.ts +355 -0
- package/src/kit/api/project-source.ts +180 -0
- package/src/kit/api/project-state.ts +110 -0
- package/src/kit/api/relay.ts +270 -0
- package/src/kit/api/themes.ts +45 -0
- package/src/kit/api-asset-library-wire.ts +45 -0
- package/src/kit/api-base.ts +10 -0
- package/src/kit/api-build.ts +99 -0
- package/src/kit/api-git-wire.ts +56 -0
- package/src/kit/api-logs.ts +92 -0
- package/src/kit/api-project-identity.ts +74 -0
- package/src/kit/api-settings.ts +36 -0
- package/src/kit/api-worktrees.ts +205 -0
- package/src/kit/asset-capabilities.ts +344 -0
- package/src/kit/asset-compare-core.ts +171 -0
- package/src/kit/asset-editor-context.tsx +101 -0
- package/src/kit/asset-events.ts +96 -0
- package/src/kit/asset-inspector-actions.ts +87 -0
- package/src/kit/asset-selection-viewer-registry.ts +113 -0
- package/src/kit/asset-selection.ts +146 -0
- package/src/kit/asset-thumbnails.ts +25 -0
- package/src/kit/asset-viewers.ts +115 -0
- package/src/kit/asset-workflow/asset-import-jobs.ts +106 -0
- package/src/kit/asset-workflow/asset-ledger-backend.ts +126 -0
- package/src/kit/asset-workflow/asset-ledger.ts +156 -0
- package/src/kit/asset-workflow/asset-materialization-report.ts +140 -0
- package/src/kit/asset-workflow/asset-pack-manifest.ts +320 -0
- package/src/kit/asset-workflow/asset-types.ts +142 -0
- package/src/kit/asset-workflow/audio-preview-player.ts +193 -0
- package/src/kit/asset-workflow/audio-waveform.ts +22 -0
- package/src/kit/asset-workflow/cloud-asset-client.ts +263 -0
- package/src/kit/asset-workflow/hosted-asset-materialization.ts +236 -0
- package/src/kit/asset-workflow/image-view-scale.ts +31 -0
- package/src/kit/asset-workflow/import-contract.ts +124 -0
- package/src/kit/asset-workflow/ledger-write-lock.ts +244 -0
- package/src/kit/asset-workflow/pixi-spritesheet.ts +197 -0
- package/src/kit/asset-workflow/preview-resource-lifetime.ts +44 -0
- package/src/kit/asset-workflow/project-asset-commands.ts +20 -0
- package/src/kit/asset-workflow/project-content.ts +288 -0
- package/src/kit/asset-workflow/project-source-index.ts +545 -0
- package/src/kit/asset-workflow/thumbnail-system.ts +256 -0
- package/src/kit/authoring/active-adapter.ts +200 -0
- package/src/kit/authoring/active-systems.ts +422 -0
- package/src/kit/authoring/adapter-key.ts +18 -0
- package/src/kit/authoring/authoring-asset-url.ts +27 -0
- package/src/kit/authoring/bootstrap-state.ts +49 -0
- package/src/kit/authoring/boundary-authoring-adapter.ts +189 -0
- package/src/kit/authoring/canvas-scene-guides.ts +84 -0
- package/src/kit/authoring/composite-authoring-adapter.ts +2109 -0
- package/src/kit/authoring/consumer-actions.ts +531 -0
- package/src/kit/authoring/design-time-layers.ts +852 -0
- package/src/kit/authoring/design-time-mount-registry.ts +244 -0
- package/src/kit/authoring/edit-mode-authoring.ts +637 -0
- package/src/kit/authoring/empty-project-authoring.ts +22 -0
- package/src/kit/authoring/instance-source-menu.ts +135 -0
- package/src/kit/authoring/layered-pick.ts +185 -0
- package/src/kit/authoring/mounted-root-subjects.ts +146 -0
- package/src/kit/authoring/no-authoring-adapter.ts +59 -0
- package/src/kit/authoring/object3d-document-persistence.ts +122 -0
- package/src/kit/authoring/panel-authoring.ts +121 -0
- package/src/kit/authoring/project-authoring-session.ts +105 -0
- package/src/kit/authoring/provenance.ts +99 -0
- package/src/kit/authoring/react-canvas-navigation.ts +259 -0
- package/src/kit/authoring/react-design-canvas-style.ts +20 -0
- package/src/kit/authoring/react-story-board.ts +917 -0
- package/src/kit/authoring/selection-scope.ts +195 -0
- package/src/kit/authoring/shell-document-ops.ts +169 -0
- package/src/kit/authoring/story-board-chrome-fit.ts +107 -0
- package/src/kit/authoring/story-board-presentation.ts +111 -0
- package/src/kit/authoring/three-root.ts +67 -0
- package/src/kit/authoring/viewport-tool-context.ts +73 -0
- package/src/kit/authoring/viewport-tool-owner.ts +38 -0
- package/src/kit/authoring/world-session-state.ts +101 -0
- package/src/kit/authoring-seam-evidence.ts +300 -0
- package/src/kit/availability-tick.ts +66 -0
- package/src/kit/bitmap-label.ts +120 -0
- package/src/kit/boot-routing.ts +392 -0
- package/src/kit/breakpoint-state.ts +43 -0
- package/src/kit/build-identity.ts +16 -0
- package/src/kit/bytes-codec.ts +62 -0
- package/src/kit/cancellation-reason.ts +58 -0
- package/src/kit/canvas-frames.ts +88 -0
- package/src/kit/capture-camera-pose.ts +77 -0
- package/src/kit/capture-size.ts +88 -0
- package/src/kit/chrome-registry.ts +159 -0
- package/src/kit/chrome-slot-registry.ts +91 -0
- package/src/kit/collaboration-client.ts +264 -0
- package/src/kit/collaboration-presence.ts +41 -0
- package/src/kit/command-dispatch.ts +19 -0
- package/src/kit/command-listener.ts +2182 -0
- package/src/kit/command-registry.ts +71 -0
- package/src/kit/component-board-registry.ts +205 -0
- package/src/kit/component-states-registry.ts +199 -0
- package/src/kit/components/AlignToolbar.tsx +204 -0
- package/src/kit/components/ApplicationMenus.tsx +372 -0
- package/src/kit/components/AssetEditorShell.tsx +216 -0
- package/src/kit/components/AssetInspectorToolSection.tsx +124 -0
- package/src/kit/components/BoardRulers.tsx +354 -0
- package/src/kit/components/CanvasAddNodeDialogs.tsx +529 -0
- package/src/kit/components/CanvasSceneViewport.tsx +1195 -0
- package/src/kit/components/ChromeSlot.tsx +20 -0
- package/src/kit/components/CodeView.tsx +470 -0
- package/src/kit/components/CompactInspectorShell.tsx +39 -0
- package/src/kit/components/ConsolePanel.tsx +273 -0
- package/src/kit/components/GameplaySessionTimeline.tsx +295 -0
- package/src/kit/components/InspectionProjection.tsx +932 -0
- package/src/kit/components/Inspector.tsx +270 -0
- package/src/kit/components/InspectorCanvasPreview.tsx +35 -0
- package/src/kit/components/InspectorFieldsSection.tsx +290 -0
- package/src/kit/components/InspectorStoriesSection.tsx +92 -0
- package/src/kit/components/InspectorToolSection.tsx +96 -0
- package/src/kit/components/InspectorTransformSection.tsx +245 -0
- package/src/kit/components/LightExplorerPanel.tsx +433 -0
- package/src/kit/components/MediaProperties.tsx +145 -0
- package/src/kit/components/ProjectHeader.tsx +328 -0
- package/src/kit/components/ReactCanvasControls.tsx +358 -0
- package/src/kit/components/RootSelectionOverlay.tsx +3688 -0
- package/src/kit/components/RootTextEditor.tsx +79 -0
- package/src/kit/components/SaveStatus.tsx +70 -0
- package/src/kit/components/SurfaceCrashBoundary.tsx +105 -0
- package/src/kit/components/SurfaceStateOverlay.tsx +24 -0
- package/src/kit/components/ToolContributionSurfaces.tsx +49 -0
- package/src/kit/components/ToolHost.tsx +380 -0
- package/src/kit/components/Toolbar.tsx +811 -0
- package/src/kit/components/TransientHint.tsx +44 -0
- package/src/kit/components/VersionControlSection.tsx +470 -0
- package/src/kit/components/ViewportOverlaysMenu.tsx +177 -0
- package/src/kit/components/VolterLogo.tsx +18 -0
- package/src/kit/components/WorktreeSwitcher.tsx +712 -0
- package/src/kit/components/account-documents.tsx +1162 -0
- package/src/kit/components/asset-documents.tsx +794 -0
- package/src/kit/components/asset-editor-persistence.ts +216 -0
- package/src/kit/components/asset-selection-section.tsx +545 -0
- package/src/kit/components/asset-thumbnails.tsx +307 -0
- package/src/kit/components/asset-viewers/AudioViewer.tsx +201 -0
- package/src/kit/components/asset-viewers/GenericJsonViewer.tsx +102 -0
- package/src/kit/components/asset-viewers/ImageViewer.tsx +300 -0
- package/src/kit/components/asset-viewers/JsonAssetDocument.tsx +98 -0
- package/src/kit/components/asset-viewers/OnlineAssetDetail.tsx +426 -0
- package/src/kit/components/asset-viewers/SourceAssetViewer.tsx +356 -0
- package/src/kit/components/asset-viewers/SpritesheetSpriteView.tsx +102 -0
- package/src/kit/components/asset-viewers/VideoViewer.tsx +101 -0
- package/src/kit/components/asset-viewers/shader-source.ts +144 -0
- package/src/kit/components/board-guides.ts +150 -0
- package/src/kit/components/canvas-scene-hotkeys.ts +37 -0
- package/src/kit/components/canvas-temporary-pivot.ts +34 -0
- package/src/kit/components/core-utilities.tsx +94 -0
- package/src/kit/components/inspector-preview-section.tsx +223 -0
- package/src/kit/components/inspector-revert-label.ts +20 -0
- package/src/kit/components/inspector-selection.ts +42 -0
- package/src/kit/components/inspector-stories-gating.ts +171 -0
- package/src/kit/components/inspector-transform-subject.ts +11 -0
- package/src/kit/components/inspector-transform.ts +88 -0
- package/src/kit/components/kind-documents.tsx +544 -0
- package/src/kit/components/primitives/DraftColorInput.tsx +74 -0
- package/src/kit/components/project-tool-documents.tsx +402 -0
- package/src/kit/components/scene-documents.tsx +221 -0
- package/src/kit/components/status-contributions.tsx +407 -0
- package/src/kit/components/tool-documents.tsx +302 -0
- package/src/kit/components/tool-schema-form.tsx +262 -0
- package/src/kit/components/use-after-paint.ts +41 -0
- package/src/kit/components/use-project-image-assets.ts +86 -0
- package/src/kit/components/workspace-history.ts +32 -0
- package/src/kit/components/world-documents.tsx +570 -0
- package/src/kit/components/world-overlay-gestures.ts +1939 -0
- package/src/kit/composite-screenshot.ts +2238 -0
- package/src/kit/content-entry-source-registry.ts +184 -0
- package/src/kit/contribution-surfaces.ts +48 -0
- package/src/kit/coverage/canvas-reveal.ts +192 -0
- package/src/kit/coverage/design-time-surfaces.ts +101 -0
- package/src/kit/coverage/ontology-invariants.ts +466 -0
- package/src/kit/coverage/session-vitals.ts +503 -0
- package/src/kit/crash-null-boundary.ts +36 -0
- package/src/kit/creation-site-edit.ts +1491 -0
- package/src/kit/creation-site-registry.ts +160 -0
- package/src/kit/delegate-harness-registry.ts +134 -0
- package/src/kit/document-areas.ts +70 -0
- package/src/kit/document-context-registry.ts +193 -0
- package/src/kit/document-open-registry.ts +200 -0
- package/src/kit/document-play-extension.ts +221 -0
- package/src/kit/document-preview-source.ts +20 -0
- package/src/kit/document-renderer-session.ts +138 -0
- package/src/kit/document-stage-sessions.ts +26 -0
- package/src/kit/document-viewports.ts +120 -0
- package/src/kit/editor-api.ts +46 -0
- package/src/kit/editor-chrome-capture.ts +136 -0
- package/src/kit/editor-commands.ts +176 -0
- package/src/kit/editor-console.ts +580 -0
- package/src/kit/editor-current-view.ts +56 -0
- package/src/kit/editor-document-probe.ts +1168 -0
- package/src/kit/editor-git-client.ts +115 -0
- package/src/kit/editor-hotkeys.ts +728 -0
- package/src/kit/editor-lease-view.ts +39 -0
- package/src/kit/editor-lease.ts +415 -0
- package/src/kit/editor-mode.ts +19 -0
- package/src/kit/editor-notifications.ts +140 -0
- package/src/kit/editor-presence.ts +563 -0
- package/src/kit/editor-presentation-activity.ts +58 -0
- package/src/kit/editor-presentation-notice.ts +42 -0
- package/src/kit/editor-runtime.tsx +147 -0
- package/src/kit/editor-server-response.ts +86 -0
- package/src/kit/editor-session-attribution.ts +85 -0
- package/src/kit/editor-session-mode.ts +54 -0
- package/src/kit/editor-state-facets.ts +74 -0
- package/src/kit/editor-view-presentation.ts +777 -0
- package/src/kit/environment-images.ts +58 -0
- package/src/kit/eyedropper-session.ts +60 -0
- package/src/kit/files/file-provider.ts +62 -0
- package/src/kit/files/project-files.ts +270 -0
- package/src/kit/finders/index.ts +137 -0
- package/src/kit/finders/scenes-from-entrypoint-selection.ts +387 -0
- package/src/kit/frame/frame-parts.ts +30 -0
- package/src/kit/framed-document-capture.ts +34 -0
- package/src/kit/game-globals-prelude.ts +143 -0
- package/src/kit/game-surface-defaults.ts +33 -0
- package/src/kit/gameplay-dom-recording.ts +318 -0
- package/src/kit/gameplay-export-state.ts +14 -0
- package/src/kit/gameplay-replay.ts +417 -0
- package/src/kit/gameplay-session-time.ts +9 -0
- package/src/kit/gameplay-sessions.ts +204 -0
- package/src/kit/hierarchy-component-marks.ts +298 -0
- package/src/kit/hierarchy-internals.ts +197 -0
- package/src/kit/hierarchy-kind-icon.ts +217 -0
- package/src/kit/hierarchy-menu-registry.ts +67 -0
- package/src/kit/hierarchy-node-rows.ts +307 -0
- package/src/kit/hierarchy-panel-view.ts +280 -0
- package/src/kit/hierarchy-projection.ts +76 -0
- package/src/kit/hierarchy-row-media.ts +45 -0
- package/src/kit/hierarchy-row-model.ts +308 -0
- package/src/kit/hierarchy-rows.ts +11 -0
- package/src/kit/hierarchy-walk.ts +86 -0
- package/src/kit/history/editor-session.ts +25 -0
- package/src/kit/history/history-commands.ts +147 -0
- package/src/kit/history/history-delegate.ts +187 -0
- package/src/kit/history/history-limit-notices.ts +43 -0
- package/src/kit/history/history-service.ts +1189 -0
- package/src/kit/history/persistence-coordinator.ts +35 -0
- package/src/kit/history/project-file-history.ts +386 -0
- package/src/kit/history/project-root-history-backends.ts +139 -0
- package/src/kit/history/resource-registry.ts +209 -0
- package/src/kit/history/snapshot-store.ts +103 -0
- package/src/kit/history/source-history-backend.ts +546 -0
- package/src/kit/history-types.ts +124 -0
- package/src/kit/hmr-registration-group.ts +67 -0
- package/src/kit/hmr-stable-react-context.ts +23 -0
- package/src/kit/hotkeys.ts +190 -0
- package/src/kit/inference-diagnostics.ts +69 -0
- package/src/kit/initial-project.ts +80 -0
- package/src/kit/inspection/active-subject.ts +571 -0
- package/src/kit/inspection/active-surface.ts +142 -0
- package/src/kit/inspection/compose-subject.ts +1055 -0
- package/src/kit/inspection/compose.ts +7 -0
- package/src/kit/inspection/display.ts +171 -0
- package/src/kit/inspection/document-subject.ts +109 -0
- package/src/kit/inspection/game-subject.ts +85 -0
- package/src/kit/inspection/null-subject.ts +119 -0
- package/src/kit/inspection/serialize.ts +357 -0
- package/src/kit/inspection/use-active-inspection.ts +180 -0
- package/src/kit/inspection-model.ts +542 -0
- package/src/kit/inspection-node-media.ts +58 -0
- package/src/kit/inspector-presentation.ts +203 -0
- package/src/kit/inspector-property-grouping.ts +64 -0
- package/src/kit/inspector-section-registry.ts +221 -0
- package/src/kit/instance-source-actions.ts +163 -0
- package/src/kit/js-heap.ts +71 -0
- package/src/kit/key-actions.ts +91 -0
- package/src/kit/keymap-presets.ts +428 -0
- package/src/kit/layout-policy.ts +31 -0
- package/src/kit/light-explorer-model.ts +134 -0
- package/src/kit/live-canvas-frame.ts +55 -0
- package/src/kit/live-document.ts +296 -0
- package/src/kit/live-gesture-lock.ts +50 -0
- package/src/kit/live-seam-evidence.ts +11 -0
- package/src/kit/live-session-registry.ts +220 -0
- package/src/kit/live-transition.ts +391 -0
- package/src/kit/manifest-project.ts +107 -0
- package/src/kit/module-fetch-diagnosis.ts +192 -0
- package/src/kit/mount-failure-report.ts +154 -0
- package/src/kit/native-selection-style.ts +497 -0
- package/src/kit/object3d-document-write-policy.ts +137 -0
- package/src/kit/packaged-runtime.ts +108 -0
- package/src/kit/palettes/maya.palette.json +57 -0
- package/src/kit/palettes/substance.palette.json +57 -0
- package/src/kit/performance-profiler.ts +367 -0
- package/src/kit/performance-sources.ts +69 -0
- package/src/kit/photograph-notice.ts +141 -0
- package/src/kit/play-boot-phase.ts +166 -0
- package/src/kit/play-camera-flight.ts +35 -0
- package/src/kit/png-encode.worker.ts +26 -0
- package/src/kit/presentation-surface.ts +248 -0
- package/src/kit/product-command.ts +90 -0
- package/src/kit/project-adapter.ts +1140 -0
- package/src/kit/project-asset-refresh.ts +23 -0
- package/src/kit/project-asset-roots.ts +68 -0
- package/src/kit/project-local-state.ts +151 -0
- package/src/kit/project-manager.ts +243 -0
- package/src/kit/project-module-changes.ts +201 -0
- package/src/kit/project-module-split.ts +270 -0
- package/src/kit/project-play-layers.ts +25 -0
- package/src/kit/project-provenance.ts +115 -0
- package/src/kit/project-ready.ts +42 -0
- package/src/kit/project-shape.ts +68 -0
- package/src/kit/project-tools.ts +107 -0
- package/src/kit/projection-types.ts +44 -0
- package/src/kit/readiness.ts +113 -0
- package/src/kit/renderer-resource-counts.ts +27 -0
- package/src/kit/reported-play-state.ts +90 -0
- package/src/kit/resolve-contributed-command.ts +14 -0
- package/src/kit/resolve-relative-specifier.ts +33 -0
- package/src/kit/retained-document-states.ts +91 -0
- package/src/kit/scene-document-plan.ts +320 -0
- package/src/kit/scene-live-open.ts +210 -0
- package/src/kit/scoped-game-css.ts +152 -0
- package/src/kit/served-url.ts +5 -0
- package/src/kit/session-close.ts +17 -0
- package/src/kit/session-tombstone.ts +127 -0
- package/src/kit/settings/settings-provider.ts +82 -0
- package/src/kit/settings-store.ts +348 -0
- package/src/kit/shell-document-state.ts +27 -0
- package/src/kit/shell-store-door.ts +45 -0
- package/src/kit/shell-store.ts +722 -0
- package/src/kit/source-conflict.ts +122 -0
- package/src/kit/stage-context.ts +377 -0
- package/src/kit/stage-invalidation.ts +25 -0
- package/src/kit/stage-store-registry.ts +69 -0
- package/src/kit/startup-failure.ts +80 -0
- package/src/kit/state-report-deferral.ts +73 -0
- package/src/kit/storage/host-files-storage.ts +97 -0
- package/src/kit/storage/http-storage.ts +174 -0
- package/src/kit/storage/index.ts +75 -0
- package/src/kit/storage/mem-storage.ts +158 -0
- package/src/kit/storage/path-lock.ts +44 -0
- package/src/kit/storage/paths.ts +26 -0
- package/src/kit/storage-types.ts +127 -0
- package/src/kit/stories/StoryPreviewMount.tsx +306 -0
- package/src/kit/stories/compose-project-stories.ts +255 -0
- package/src/kit/stories/prefabs-finder.ts +54 -0
- package/src/kit/stories/prefabs-from-stories.ts +182 -0
- package/src/kit/stories/project-story-regions.ts +24 -0
- package/src/kit/stories/story-capture.ts +579 -0
- package/src/kit/stories/story-declared-medium.ts +126 -0
- package/src/kit/stories/story-discovery.ts +176 -0
- package/src/kit/stories/story-dom-runtime.ts +78 -0
- package/src/kit/stories/story-grouping.ts +111 -0
- package/src/kit/stories/story-mount-turn.ts +27 -0
- package/src/kit/stories/story-presentation.ts +215 -0
- package/src/kit/stories/story-preview-component.ts +7 -0
- package/src/kit/stories/story-registry.ts +530 -0
- package/src/kit/stories-scope.ts +35 -0
- package/src/kit/story-document-openers.ts +36 -0
- package/src/kit/story-thumbnails.ts +47 -0
- package/src/kit/surface-keyboard.ts +101 -0
- package/src/kit/surface-state.ts +135 -0
- package/src/kit/system-seam-evidence.ts +72 -0
- package/src/kit/tab-census.ts +202 -0
- package/src/kit/tab-lifecycle-client.ts +227 -0
- package/src/kit/theme-library.ts +897 -0
- package/src/kit/theme-preference.ts +429 -0
- package/src/kit/three-viewport-presentation.ts +23 -0
- package/src/kit/tool-contribution-play.ts +74 -0
- package/src/kit/tool-loader.ts +1918 -0
- package/src/kit/transform-mode-request.ts +66 -0
- package/src/kit/transient-hint.ts +78 -0
- package/src/kit/transport-strip.tsx +174 -0
- package/src/kit/ui-source/adapter-region-includes.ts +238 -0
- package/src/kit/ui-source/file-region-resolver.ts +302 -0
- package/src/kit/ui-source/inspect.ts +775 -0
- package/src/kit/ui-source/source-write-backend.ts +605 -0
- package/src/kit/ui-source/tier-source-write-backend.ts +279 -0
- package/src/kit/user-local-state.ts +105 -0
- package/src/kit/viewport-activation-timings.ts +840 -0
- package/src/kit/viewport-editor-controls.ts +22 -0
- package/src/kit/viewport-presentation.ts +668 -0
- package/src/kit/viewport-surface-status.tsx +55 -0
- package/src/kit/wait-until.ts +37 -0
- package/src/kit/worker-call-metrics.ts +166 -0
- package/src/kit/workspace-areas.ts +191 -0
- package/src/kit/workspace-aux-commands.ts +11 -0
- package/src/kit/workspace-available-documents.ts +142 -0
- package/src/kit/workspace-core-utilities.ts +31 -0
- package/src/kit/workspace-document-ids.ts +59 -0
- package/src/kit/workspace-document-registry.ts +624 -0
- package/src/kit/workspace-document-restore.ts +146 -0
- package/src/kit/workspace-host-commands.ts +141 -0
- package/src/kit/workspace-persistence-gate.ts +40 -0
- package/src/kit/workspace-play-utilities.ts +44 -0
- package/src/kit/workspace-presets.ts +446 -0
- package/src/kit/workspace-regions.ts +276 -0
- package/src/kit/workspace-static-panels.ts +73 -0
- package/src/kit/workspace-status-registry.ts +121 -0
- package/src/kit/workspace-storage.ts +35 -0
- package/src/kit/workspace-style.ts +226 -0
- package/src/kit/workspace-utility-commands.ts +74 -0
- package/src/kit/workspace-utility-registry.ts +263 -0
- package/src/kit/world-adoption-event.ts +23 -0
- package/src/kit/world-adoption.ts +115 -0
- package/src/kit/world-canvas-viewport-state.ts +35 -0
- package/src/kit/world-document-routing.ts +104 -0
- package/src/kit/world-pan-state.ts +198 -0
- package/src/kit/write-pipe.ts +173 -0
- package/src/layout-arrangements.ts +5 -0
- package/src/layouts.tsx +108 -0
- package/src/looks.ts +16 -0
- package/src/project/output-roots.ts +73 -0
- package/src/project/tab-census.ts +155 -0
- package/src/project-tool-catalog.ts +104 -0
- package/src/selection.tsx +107 -0
- package/src/services.ts +18 -0
- package/src/session/build-report.ts +22 -0
- package/src/session/collaboration-types.ts +262 -0
- package/src/session/command-table.ts +327 -0
- package/src/session/discovery.ts +100 -0
- package/src/session/editor-brand.ts +48 -0
- package/src/session/editor-compatibility.ts +329 -0
- package/src/session/editor-control-lifecycle.ts +68 -0
- package/src/session/editor-control-protocol.ts +5 -0
- package/src/session/entrypoint-selection-readers.ts +66 -0
- package/src/session/entrypoint-selection-source.ts +120 -0
- package/src/session/game-css-scope.ts +30 -0
- package/src/session/hosted-attachment.ts +225 -0
- package/src/session/limited-view.ts +82 -0
- package/src/session/product-create.ts +24 -0
- package/src/session/product-locator.ts +478 -0
- package/src/session/project-module-url.ts +242 -0
- package/src/session/project-serving.ts +164 -0
- package/src/session/project-upgrade.ts +669 -0
- package/src/session/registry-format.ts +210 -0
- package/src/session/relative-path-guard.ts +56 -0
- package/src/session/scoped-game-css.ts +461 -0
- package/src/session/source-glob.ts +15 -0
- package/src/session/tool-contribution-convention.ts +123 -0
- package/src/session/workbench-locator.ts +712 -0
- package/src/session.ts +41 -0
- package/src/share.ts +160 -0
- package/src/source-analysis.ts +28 -0
- package/src/source-authoring.ts +439 -0
- package/src/tools/errors.ts +91 -0
- package/src/tools/provider-execution.ts +70 -0
- package/src/tools/registry.ts +341 -0
- package/src/tools/types.ts +159 -0
- package/src/transport.ts +100 -0
- package/src/types.ts +1693 -0
- package/src/views.ts +164 -0
- package/src/widgets/design-system.ts +93 -0
- package/src/widgets/editor-appearance.ts +151 -0
- package/src/widgets/editor-material.ts +83 -0
- package/src/widgets/icon-set-registry.ts +105 -0
- package/src/widgets/index.ts +71 -0
- package/src/widgets/inspector-widgets/AlignmentGrid.tsx +182 -0
- package/src/widgets/inspector-widgets/AssetSlotPicker.tsx +123 -0
- package/src/widgets/inspector-widgets/BorderEditor.tsx +309 -0
- package/src/widgets/inspector-widgets/ColorPicker.tsx +549 -0
- package/src/widgets/inspector-widgets/CurveEditor.tsx +359 -0
- package/src/widgets/inspector-widgets/FilterEditor.tsx +108 -0
- package/src/widgets/inspector-widgets/FontPicker.tsx +191 -0
- package/src/widgets/inspector-widgets/GradientEditor.tsx +623 -0
- package/src/widgets/inspector-widgets/ScrubbableInput.tsx +180 -0
- package/src/widgets/inspector-widgets/ShadowEditor.tsx +319 -0
- package/src/widgets/inspector-widgets/color-utils.ts +201 -0
- package/src/widgets/inspector-widgets/curve-utils.ts +212 -0
- package/src/widgets/inspector-widgets/index.ts +25 -0
- package/src/widgets/inspector-widgets/shared.tsx +140 -0
- package/src/widgets/interactive-edit-scope.ts +33 -0
- package/src/widgets/patterns/Dialog.tsx +140 -0
- package/src/widgets/patterns/Fields.tsx +44 -0
- package/src/widgets/patterns/List.tsx +25 -0
- package/src/widgets/patterns/StateSurface.tsx +40 -0
- package/src/widgets/patterns/Surfaces.tsx +122 -0
- package/src/widgets/patterns/Tabs.tsx +80 -0
- package/src/widgets/patterns/Toolbar.tsx +72 -0
- package/src/widgets/patterns/Tree.tsx +72 -0
- package/src/widgets/primitives/AnchoredMenu.tsx +260 -0
- package/src/widgets/primitives/Button.tsx +62 -0
- package/src/widgets/primitives/ColorInput.tsx +78 -0
- package/src/widgets/primitives/DraftTextInput.tsx +63 -0
- package/src/widgets/primitives/EditorIcon.tsx +157 -0
- package/src/widgets/primitives/FormControls.tsx +88 -0
- package/src/widgets/primitives/HoverPreview.tsx +96 -0
- package/src/widgets/primitives/JsonInput.tsx +113 -0
- package/src/widgets/primitives/Layout.tsx +100 -0
- package/src/widgets/primitives/Menu.tsx +161 -0
- package/src/widgets/primitives/NumberInput.tsx +169 -0
- package/src/widgets/primitives/Panel.tsx +80 -0
- package/src/widgets/primitives/SectionHeader.tsx +77 -0
- package/src/widgets/primitives/Text.tsx +54 -0
- package/src/widgets/primitives/ThemeRootPortal.tsx +52 -0
- package/src/widgets/primitives/Tooltip.tsx +204 -0
- package/src/widgets/primitives/Vec3Input.tsx +70 -0
- package/src/widgets/primitives/banner-tones.ts +32 -0
- package/src/widgets/primitives/clamp-to-viewport.ts +44 -0
- package/src/widgets/primitives/editor-icons.ts +254 -0
- package/src/widgets/primitives/panel-header-styles.ts +42 -0
- package/src/widgets/theme.ts +2841 -0
- 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
|
+
}
|