@vgai/editor 0.2.0 → 0.4.0-canary.20260715.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/dist/assets/axe-C7YBtOuM.js +30 -0
- package/dist/assets/browserAll-DDtGGE48.js +1 -0
- package/dist/assets/create-runtime-DbTlBlKG.js +1 -0
- package/dist/assets/{game-BI111o5f.js → game-BE0r7DMI.js} +3 -3
- package/dist/assets/game-BdTHR2_X.js +1 -0
- package/dist/assets/{game-pt_1lQLv.js → game-C3iQGSqo.js} +1 -1
- package/dist/assets/game-CcHfYxjP.js +1 -0
- package/dist/assets/game-Cqzj_lpD.js +1 -0
- package/dist/assets/game-DSZdeCHu.js +1 -0
- package/dist/assets/game-Dch9sAPc.js +1 -0
- package/dist/assets/{game-xsOho_wg.js → game-SAFn90pA.js} +3 -3
- package/dist/assets/game-loop-DMIgSMV-.js +1 -0
- package/dist/assets/{game-source-CvidN7AB.js → game-source-Dk83w26t.js} +1 -1
- package/dist/assets/{index-Bp5cecD6.js → index-4idIsHHJ.js} +2 -2
- package/dist/assets/{index-DL7RcQ0n.js → index-C-LhOTBN.js} +1 -1
- package/dist/assets/{index-Cs8xwv3e.js → index-CDkxceqE.js} +1 -1
- package/dist/assets/index-CQEfi217.js +15412 -0
- package/dist/assets/index-Dvi-V0NE.css +1 -0
- package/dist/assets/ingest-siblings-s0P5I1Ub.js +1 -0
- package/dist/assets/mount-manifest-DXMOlF79.js +1 -0
- package/dist/assets/react-18-C9eHn8x1.js +1 -0
- package/dist/assets/webworkerAll-Fn0e-ouF.js +1 -0
- package/dist/assets/world-documents-GM0a9Kv7.js +1 -0
- package/dist/index.html +15 -13
- package/dist/ingest/racing-game/vgai.game.json +5 -4
- package/dist/ingest/simcity/vgai.game.json +5 -4
- package/dist/ingest/tanks/vgai.game.json +5 -4
- package/dist/project-scripts.bundle.js +3917 -0
- package/dist/scenes/untitled.vscn.json +2 -0
- package/dist-server/packaged.mjs +7697 -3435
- package/package.json +31 -4
- package/scripts/probe-reporter.ts +349 -0
- package/scripts/run-probe.ts +2260 -0
- package/server/asset-catalog-audit.ts +268 -0
- package/server/asset-catalog-v2.ts +146 -0
- package/server/asset-library-routes.ts +276 -58
- package/server/canonical-path.ts +1 -1
- package/server/dev.ts +148 -44
- package/server/editor-server.ts +888 -61
- package/server/editor-sse.ts +115 -4
- package/server/harness-chat-service.ts +796 -0
- package/server/local-asset-catalog.ts +157 -22
- package/server/packaged.ts +51 -21
- package/server/process-shutdown.ts +136 -0
- package/server/prod.ts +9 -6
- package/server/project-hmr-files.ts +41 -0
- package/server/project-operations.ts +343 -0
- package/server/project-optimize-deps-entries.ts +4 -4
- package/server/project-output-writer.ts +156 -0
- package/server/project-validation.ts +140 -0
- package/server/project-view.ts +8 -50
- package/server/server-utils.ts +75 -10
- package/server/session-registry.ts +12 -0
- package/src/EditorContext.tsx +107 -3
- package/src/action-registry.ts +115 -17
- package/src/adapter-resolver.ts +213 -478
- package/src/asset-events.ts +64 -1
- package/src/asset-preview.ts +906 -0
- package/src/asset-selection.ts +121 -0
- package/src/asset-workflow/asset-capabilities.ts +259 -0
- package/src/asset-workflow/asset-editor-errors.ts +46 -0
- package/src/asset-workflow/asset-import-pipeline.ts +176 -0
- package/src/asset-workflow/asset-library-query.ts +107 -0
- package/src/asset-workflow/asset-types.ts +192 -0
- package/src/asset-workflow/asset-workflow-quality.ts +65 -0
- package/src/asset-workflow/import-contract.ts +113 -0
- package/src/asset-workflow/model-conversion.ts +126 -0
- package/src/asset-workflow/model-inspection.ts +185 -0
- package/src/asset-workflow/project-asset-commands.ts +23 -0
- package/src/asset-workflow/project-asset-health.ts +153 -0
- package/src/asset-workflow/project-asset-operations.ts +119 -0
- package/src/asset-workflow/thumbnail-system.ts +172 -0
- package/src/authoring/active-systems.ts +15 -0
- package/src/authoring/boundary-authoring-adapter.ts +16 -5
- package/src/authoring/composite-authoring-adapter.ts +305 -124
- package/src/authoring/design-time-layers.ts +300 -117
- package/src/authoring/edit-mode-authoring.ts +278 -293
- package/src/authoring/embed-only-adapter.ts +4 -2
- package/src/authoring/first-party-hierarchy-menu.ts +7 -2
- package/src/authoring/first-party-inspector/AddSectionMenu.tsx +5 -12
- package/src/authoring/first-party-inspector/EnvironmentSection.tsx +53 -126
- package/src/authoring/first-party-inspector/HeaderSection.tsx +54 -65
- package/src/authoring/first-party-inspector/is-first-party-selection.ts +30 -11
- package/src/authoring/first-party-inspector/register.tsx +13 -2
- package/src/authoring/ingest-authoring-adapter.ts +142 -45
- package/src/authoring/mount-failure-report.ts +5 -5
- package/src/authoring/provenance.ts +41 -0
- package/src/authoring/react-dom-authoring-adapter.ts +92 -92
- package/src/authoring/react-inspector-section.tsx +489 -51
- package/src/authoring/react-world-authoring-adapter.ts +541 -306
- package/src/authoring/story-overlay.ts +1 -1
- package/src/authoring/story-types.ts +11 -0
- package/src/authoring/vgai-scene-authoring-adapter.ts +54 -25
- package/src/authoring/viewport-tool-context.ts +78 -0
- package/src/authoring/world-canvas-viewport-state.ts +35 -0
- package/src/authoring/world-hidden-viewport.ts +186 -0
- package/src/authoring/world-pan-state.ts +33 -17
- package/src/authoring/world-session-state.ts +1 -1
- package/src/authoring/world2d-authoring-adapter.ts +109 -52
- package/src/availability-tick.ts +56 -0
- package/src/browser-play-entry.ts +5 -5
- package/src/browser-project-scripts.ts +1 -0
- package/src/build-identity.ts +15 -0
- package/src/command-listener.ts +893 -19
- package/src/components/AlignToolbar.tsx +74 -63
- package/src/components/AppRoot.tsx +193 -26
- package/src/components/ApplicationMenus.tsx +272 -0
- package/src/components/AssetBrowser.tsx +1450 -293
- package/src/components/AssetEditorShell.tsx +141 -0
- package/src/components/AssetImportDetails.tsx +218 -0
- package/src/components/AssetLibraryPanel.tsx +14 -0
- package/src/components/CameraInfo.tsx +11 -20
- package/src/components/CenterDocuments.tsx +585 -0
- package/src/components/CommandPalette.tsx +75 -30
- package/src/components/ConsolePanel.css +55 -0
- package/src/components/ConsolePanel.tsx +57 -112
- package/src/components/DebugConsolePanel.tsx +461 -0
- package/src/components/DebuggerPanel.tsx +165 -0
- package/src/components/DefaultEditorLayout.tsx +167 -231
- package/src/components/GameHierarchy.tsx +873 -290
- package/src/components/GamePerformanceOverlay.tsx +46 -0
- package/src/components/HarnessChatPanel.tsx +2352 -0
- package/src/components/HistoryPanel.tsx +226 -0
- package/src/components/Inspector.tsx +153 -99
- package/src/components/InspectorToolSection.tsx +6 -4
- package/src/components/MountFailureBanner.tsx +31 -48
- package/src/components/NetworkSection.tsx +25 -14
- package/src/components/OnlineAssetBrowser.tsx +794 -343
- package/src/components/OverlayReportBanner.tsx +38 -64
- package/src/components/PerformancePanel.tsx +206 -0
- package/src/components/PlayBar.tsx +93 -263
- package/src/components/PrefabSaveDialog.tsx +55 -127
- package/src/components/ProjectHeader.css +60 -0
- package/src/components/ProjectHeader.tsx +170 -105
- package/src/components/ProjectScreen.tsx +246 -184
- package/src/components/ReactCanvasControls.tsx +185 -0
- package/src/components/ResolutionPicker.tsx +85 -0
- package/src/components/RootActivateDialog.tsx +39 -68
- package/src/components/SaveDialog.tsx +55 -127
- package/src/components/SaveStatus.tsx +11 -29
- package/src/components/StartupLoadingScreen.tsx +96 -0
- package/src/components/StateWatchPanel.tsx +302 -0
- package/src/components/StatsOverlay.tsx +12 -5
- package/src/components/StatusBar.tsx +64 -0
- package/src/components/ThemeManagerDialog.css +89 -0
- package/src/components/ThemeManagerDialog.tsx +371 -0
- package/src/components/ToolHost.tsx +26 -15
- package/src/components/Toolbar.tsx +117 -214
- package/src/components/VgaiVoxelLogo.tsx +89 -0
- package/src/components/ViewportOverlay.tsx +97 -172
- package/src/components/ViewportPanel.tsx +130 -31
- package/src/components/WorkspaceDock.tsx +891 -0
- package/src/components/WorldSelectionOverlay.tsx +51 -63
- package/src/components/WorldTextEditor.tsx +9 -5
- package/src/components/asset-documents.tsx +390 -0
- package/src/components/asset-editor-persistence.ts +44 -0
- package/src/components/asset-selection-section.tsx +338 -0
- package/src/components/asset-viewers/AudioViewer.tsx +73 -21
- package/src/components/asset-viewers/GenericJsonViewer.tsx +26 -11
- package/src/components/asset-viewers/ImageViewer.tsx +43 -11
- package/src/components/asset-viewers/InputMapViewer.tsx +512 -134
- package/src/components/asset-viewers/MaterialAssetEditor.tsx +540 -0
- package/src/components/asset-viewers/ModelViewer.tsx +805 -121
- package/src/components/asset-viewers/OnlineAssetDetail.tsx +177 -60
- package/src/components/asset-viewers/SourceAssetViewer.tsx +121 -0
- package/src/components/asset-workflow.css +506 -0
- package/src/components/build-documents.tsx +696 -0
- package/src/components/core-utilities.tsx +75 -0
- package/src/components/data-documents.tsx +364 -0
- package/src/components/{DataPanel.tsx → data-editor.tsx} +138 -288
- package/src/components/debugger-panel-state.ts +57 -0
- package/src/components/inspector-widgets/AlignmentGrid.tsx +8 -14
- package/src/components/inspector-widgets/BorderEditor.tsx +41 -33
- package/src/components/inspector-widgets/ColorPicker.tsx +39 -39
- package/src/components/inspector-widgets/FontPicker.tsx +13 -21
- package/src/components/inspector-widgets/GradientEditor.tsx +11 -19
- package/src/components/inspector-widgets/ScrubbableInput.tsx +6 -3
- package/src/components/inspector-widgets/ShadowEditor.tsx +20 -33
- package/src/components/inspector-widgets/shared.tsx +90 -49
- package/src/components/inspectors/AnimationSection.tsx +39 -40
- package/src/components/inspectors/AudioSection.tsx +28 -39
- package/src/components/inspectors/BoneAttachmentSection.tsx +19 -24
- package/src/components/inspectors/CameraSection.tsx +14 -23
- package/src/components/inspectors/ComponentsSection.tsx +39 -43
- package/src/components/inspectors/JointsSection.tsx +34 -43
- package/src/components/inspectors/LightSection.tsx +90 -35
- package/src/components/inspectors/MaterialSection.tsx +137 -174
- package/src/components/inspectors/MeshSection.tsx +9 -17
- package/src/components/inspectors/NavigationSection.tsx +8 -15
- package/src/components/inspectors/ParticleSection.tsx +91 -76
- package/src/components/inspectors/PhysicsSection.tsx +42 -46
- package/src/components/inspectors/PostProcessingSection.tsx +28 -36
- package/src/components/inspectors/RenderingSection.tsx +12 -20
- package/src/components/inspectors/ShadowSection.tsx +7 -8
- package/src/components/inspectors/SplineSection.tsx +31 -42
- package/src/components/inspectors/TransformSection.tsx +14 -22
- package/src/components/patterns/Dialog.tsx +95 -0
- package/src/components/patterns/Fields.tsx +44 -0
- package/src/components/patterns/List.tsx +25 -0
- package/src/components/patterns/StateSurface.tsx +40 -0
- package/src/components/patterns/Surfaces.tsx +115 -0
- package/src/components/patterns/Tabs.tsx +80 -0
- package/src/components/patterns/Toolbar.tsx +51 -0
- package/src/components/patterns/Tree.tsx +72 -0
- package/src/components/primitives/Button.tsx +46 -0
- package/src/components/primitives/ColorInput.tsx +11 -47
- package/src/components/primitives/EditorIcon.tsx +99 -0
- package/src/components/primitives/FormControls.tsx +71 -0
- package/src/components/primitives/JsonInput.tsx +15 -2
- package/src/components/primitives/Layout.tsx +100 -0
- package/src/components/primitives/Menu.tsx +105 -0
- package/src/components/primitives/NumberInput.tsx +8 -35
- package/src/components/primitives/Panel.tsx +49 -44
- package/src/components/primitives/SectionHeader.tsx +10 -28
- package/src/components/primitives/Text.tsx +46 -0
- package/src/components/primitives/Tooltip.tsx +20 -12
- package/src/components/primitives/Vec3Input.tsx +3 -0
- package/src/components/primitives/banner-tones.ts +33 -0
- package/src/components/primitives/editor-icons.ts +102 -0
- package/src/components/primitives/panel-header-styles.ts +30 -0
- package/src/components/project-operation-documents.tsx +226 -0
- package/src/components/status-contributions.tsx +175 -0
- package/src/components/story-documents-register.ts +15 -0
- package/src/components/story-documents.tsx +1185 -0
- package/src/components/tool-documents.tsx +110 -0
- package/src/components/workspace-dock.css +293 -0
- package/src/components/workspace-history.ts +33 -0
- package/src/components/world-documents.tsx +145 -0
- package/src/components/xstate-documents.tsx +209 -0
- package/src/composite-screenshot.ts +248 -0
- package/src/design-system/index.ts +70 -0
- package/src/design-system-stories/Foundation.stories.tsx +183 -0
- package/src/design-system-stories/Patterns.stories.tsx +251 -0
- package/src/design-system-stories/Workspace.stories.tsx +538 -0
- package/src/doctor/report.ts +57 -13
- package/src/editor-api.ts +295 -31
- package/src/editor-compatibility.ts +200 -0
- package/src/editor-console.ts +35 -17
- package/src/editor-hotkeys.ts +85 -33
- package/src/editor-store.ts +538 -233
- package/src/editor-viewport.ts +160 -34
- package/src/entity-factory.ts +14 -3
- package/src/gizmo-registry.ts +21 -5
- package/src/harness-chat-editor-context.ts +37 -0
- package/src/harness-chat-indicator.ts +68 -0
- package/src/harness-chat-layout.ts +53 -0
- package/src/harness-chat-lifecycle.ts +170 -0
- package/src/harness-chat-reconnect.ts +85 -0
- package/src/harness-chat-types.ts +409 -0
- package/src/hierarchy-drop.ts +50 -0
- package/src/hierarchy-expansion-state.ts +100 -0
- package/src/hierarchy-menu-registry.ts +1 -1
- package/src/hierarchy-projection.ts +76 -0
- package/src/history/editor-session.ts +21 -0
- package/src/history/history-commands.ts +147 -0
- package/src/history/history-service.ts +904 -0
- package/src/history/json-history-resource.ts +165 -0
- package/src/history/persistence-coordinator.ts +35 -0
- package/src/history/project-file-history.ts +266 -0
- package/src/history/project-root-history-backends.ts +53 -0
- package/src/history/resource-registry.ts +209 -0
- package/src/history/snapshot-store.ts +108 -0
- package/src/history/source-history-backend.ts +374 -0
- package/src/history/types.ts +100 -0
- package/src/hmr-registration-group.ts +67 -0
- package/src/hmr-stable-react-context.ts +23 -0
- package/src/hosted-example.ts +12 -2
- package/src/hotkeys.ts +89 -18
- package/src/ingest/discovery-react.ts +9 -10
- package/src/ingest/discovery.ts +4 -4
- package/src/ingest/discovery2d.ts +5 -5
- package/src/ingest/games/bloom-composer/vgai.game.json +3 -3
- package/src/ingest/games/embed-bundle/vgai.game.json +3 -3
- package/src/ingest/games/games-fps/vgai.game.json +3 -3
- package/src/ingest/games/iframe-reachable/vgai.game.json +3 -3
- package/src/ingest/games/interactive-cubes/vgai.game.json +3 -3
- package/src/ingest/games/mismatched-three/vgai.game.json +3 -3
- package/src/ingest/games/own-three-bundled/vgai.game.json +3 -3
- package/src/ingest/games/pointerlock-maze/vgai.game.json +7 -4
- package/src/ingest/games-2d/bubbo-bubbo/vgai.game.json +3 -3
- package/src/ingest/games-2d/bunnymark/vgai.game.json +3 -3
- package/src/ingest/games-2d/flappy/vgai.game.json +3 -3
- package/src/ingest/games-2d/flappy-pixi/vgai.game.json +3 -3
- package/src/ingest/games-2d/puzzling-potions/vgai.game.json +3 -3
- package/src/ingest/games-2d/tilemap-camera/vgai.game.json +3 -3
- package/src/ingest/games-react/react-rpg/vgai.game.json +3 -3
- package/src/ingest/types.ts +1 -1
- package/src/ingest-mode.ts +47 -52
- package/src/ingest-siblings.ts +23 -33
- package/src/initial-scene.ts +1 -2
- package/src/main.tsx +16 -8
- package/src/manifest-project.ts +10 -12
- package/src/model-thumbnail.ts +261 -26
- package/src/module-mode.ts +44 -50
- package/src/play-log-events.ts +26 -0
- package/src/play-mode.ts +495 -401
- package/src/play-scene-adoption.ts +23 -0
- package/src/playground/main.ts +68 -24
- package/src/playwright-shim.ts +473 -0
- package/src/project-manager.ts +90 -10
- package/src/project-operations.ts +112 -0
- package/src/project-story-discovery.ts +25 -0
- package/src/project-tool-discovery.ts +43 -0
- package/src/scene-serializer.ts +0 -6
- package/src/scene-sync.ts +7 -2
- package/src/script-loader.ts +3 -3
- package/src/skeleton-helper.ts +57 -0
- package/src/storage/seed.ts +68 -8
- package/src/stories/StoryPreviewMount.tsx +265 -0
- package/src/stories/action-stories.fixture.tsx +44 -0
- package/src/stories/button-stories.fixture.tsx +50 -0
- package/src/stories/compose-project-stories.ts +182 -0
- package/src/stories/loader-stories.fixture.tsx +53 -0
- package/src/stories/story-discovery.ts +130 -0
- package/src/stories/story-registry.ts +146 -0
- package/src/theme-css.ts +1049 -0
- package/src/theme-library.ts +238 -0
- package/src/theme-preference.ts +130 -0
- package/src/theme.ts +527 -0
- package/src/tool-loader.ts +80 -24
- package/src/ui-source/inspect.ts +27 -4
- package/src/ui-source/source-write-backend.ts +83 -22
- package/src/widgets/index.ts +9 -18
- package/src/workspace-aux-commands.ts +11 -0
- package/src/workspace-dock-controller.ts +64 -0
- package/src/workspace-document-ids.ts +5 -0
- package/src/workspace-document-registry.ts +367 -0
- package/src/workspace-state-persistence.ts +455 -0
- package/src/workspace-status-registry.ts +110 -0
- package/src/workspace-utility-commands.ts +49 -0
- package/src/workspace-utility-registry.ts +161 -0
- package/src/world-document-routing.ts +16 -0
- package/src/xstate-inspect/XStateMachineInspector.tsx +544 -0
- package/src/xstate-inspect/character-animation-machine.fixture.ts +74 -0
- package/src/xstate-inspect/use-live-actor-state.ts +39 -0
- package/src/xstate-inspect/xstate-graph.ts +265 -0
- package/src/xstate-inspect/xstate-layout.ts +76 -0
- package/src/z-index.ts +26 -0
- package/template/.claude/skills/editor/SKILL.md +129 -90
- package/template/AGENTS.md +1110 -0
- package/template/CLAUDE.md +1 -512
- package/template/IDIOMS.md +50 -0
- package/template/check-idioms.ts +706 -0
- package/template/manifest-entry-modules-plugin.ts +53 -0
- package/template/package.json +13 -4
- package/template/public/scenes/default.scn2d.json +6 -0
- package/template/public/scenes/default.vscn.json +7 -1
- package/template/public/scenes/untitled.vscn.json +10 -0
- package/template/scripts/playtest.ts +40 -0
- package/template/server/loopback.ts +75 -0
- package/template/server/rooms/arena-room.ts +69 -1
- package/template/server/rooms/game-room.ts +68 -1
- package/template/src/data/README.md +9 -4
- package/template/src/data/data-ref.ts +35 -0
- package/template/src/data/tuning.schema.ts +1 -1
- package/template/src/main.ts +41 -62
- package/template/src/manifest-entry-modules.d.ts +3 -0
- package/template/src/operations/README.md +88 -0
- package/template/src/runtime/pixi-adapter.ts +24 -0
- package/template/src/scripts/components/data-spinner.ts +43 -2
- package/template/src/scripts/components/spin-lap-math.ts +27 -0
- package/template/src/tools/README.md +5 -3
- package/template/src/tools/example.tool.tsx +13 -7
- package/template/src/ui/Button.stories.tsx +66 -0
- package/template/src/ui/RebindPanel.tsx +2 -6
- package/template/src/ui/game.tsx +17 -0
- package/template/tests/acceptance/example.spec.ts +64 -0
- package/template/tests/logic/example.test.ts +31 -0
- package/template/tests/logic/room.test.ts +100 -0
- package/template/tests/tsconfig.json +30 -0
- package/template/validate-manifest.ts +5 -5
- package/template/validate-scenes.ts +50 -18
- package/template/vgai.game.json +20 -13
- package/template/vite.config.ts +77 -16
- package/vite-plugin-ui-oid.ts +261 -17
- package/dist/assets/CCDIKSolver-Dz-ysxZF.js +0 -1
- package/dist/assets/SelectionBox-BXsv_6aH.js +0 -1
- package/dist/assets/browser-project-scripts-DOru4L1x.js +0 -2
- package/dist/assets/browserAll-CEkWYlFk.js +0 -1
- package/dist/assets/create-runtime-DYIWr3va.js +0 -1
- package/dist/assets/data-asset-Bb9sIM7i.js +0 -5
- package/dist/assets/game-8SDZPPgH.js +0 -1
- package/dist/assets/game-CevorxyV.js +0 -1
- package/dist/assets/game-Di7XlywD.js +0 -1
- package/dist/assets/game-DjnCaw8R.js +0 -1
- package/dist/assets/game-Q9A0aXSV.js +0 -1
- package/dist/assets/index-CPVV6pjw.js +0 -1
- package/dist/assets/index-CpOnYyeD.js +0 -213
- package/dist/assets/index-Dr8H_6Rl.js +0 -1
- package/dist/assets/index-_bCaipUk.js +0 -13650
- package/dist/assets/ingest-siblings-Dz8eEqrV.js +0 -1
- package/dist/assets/mount-manifest-Bhk0ohsl.js +0 -1
- package/dist/assets/scene-query-BivAHhJu.js +0 -1
- package/dist/assets/webworkerAll-BLrH0F6F.js +0 -1
- package/dist/assets/xstate-animation-binding-1NGLmw6y.js +0 -1
- package/dist/assets/xstate.esm-BgUD33nl.js +0 -3
- package/src/authoring/scene-ui-layer.ts +0 -485
- package/src/authoring/ui-authoring-adapter.ts +0 -325
- package/src/authoring/ui-hierarchy-menu.ts +0 -40
- package/src/authoring/ui-inspector-sections.tsx +0 -156
- package/src/authoring/undo-timeline.ts +0 -145
- package/src/components/BottomPanel.tsx +0 -326
- package/src/components/BuildPanel.tsx +0 -455
- package/src/components/FolderTree.tsx +0 -179
- package/src/components/RightPanel.tsx +0 -166
- package/src/components/WorldContextMenu.tsx +0 -257
- package/src/components/asset-viewers/AssetViewer.tsx +0 -38
- package/src/ingest/games-react/react-rpg/src/ui/game-state.tsx +0 -129
- package/src/scene-ui-mode/active-scene-ui.ts +0 -58
- package/src/scene-ui-mode/scene-ui-palette-panel.tsx +0 -152
- package/src/scene-ui-mode/scene-ui-timeline-tab.tsx +0 -29
- package/src/scene-ui-mode/ui-prefab-dialog-host.tsx +0 -74
- package/src/scene-ui-mode/ui-prefab-hierarchy-menu.ts +0 -49
- package/src/scene-ui-mode/ui-prefab-io.ts +0 -76
- package/src/ui-editor/inspector.tsx +0 -563
- package/src/ui-editor/node-ops.ts +0 -266
- package/src/ui-editor/overlay.tsx +0 -380
- package/src/ui-editor/palette.tsx +0 -114
- package/src/ui-editor/react-store.ts +0 -78
- package/src/ui-editor/timeline-panel.tsx +0 -443
- package/src/ui-editor/ui-edit-store.ts +0 -552
- package/src/ui-editor/ui-prefab.ts +0 -95
- package/template/project.json +0 -9
- package/template/src/scripts/main.ts +0 -94
- package/template/src/ui/DefaultHud.tsx +0 -40
- package/template/src/ui/game-state.tsx +0 -37
- package/template/src/ui/mount-default-hud.tsx +0 -19
- package/template/src/ui/mount-ui.ts +0 -15
- package/vite-plugin-react-world-provider.ts +0 -81
|
@@ -2,6 +2,15 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { readFile, stat } from 'node:fs/promises';
|
|
3
3
|
import { extname, resolve } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { assetCapabilities } from '../src/asset-workflow/asset-capabilities';
|
|
6
|
+
import {
|
|
7
|
+
type AssetCatalog,
|
|
8
|
+
type AssetFamily,
|
|
9
|
+
type AssetFile,
|
|
10
|
+
type AssetVariant,
|
|
11
|
+
parseAssetCatalog,
|
|
12
|
+
} from '../src/asset-workflow/asset-types';
|
|
13
|
+
import { migrateLegacyAssetCatalog } from './asset-catalog-v2';
|
|
5
14
|
import { isPathInside } from './server-utils';
|
|
6
15
|
|
|
7
16
|
export const DEFAULT_LOCAL_ASSET_LIBRARY_ROOT = '/Volumes/PeakSSD/vgai-asset-library';
|
|
@@ -10,17 +19,12 @@ export const REPOSITORY_ASSET_CATALOG_PATH = fileURLToPath(
|
|
|
10
19
|
);
|
|
11
20
|
|
|
12
21
|
export type LocalAssetType = 'model' | 'animation' | 'source';
|
|
13
|
-
|
|
14
|
-
export interface LocalAssetFile {
|
|
15
|
-
label: string;
|
|
16
|
-
path: string;
|
|
17
|
-
format: string;
|
|
18
|
-
sizeBytes: number;
|
|
19
|
-
dependencies?: string[];
|
|
20
|
-
}
|
|
22
|
+
export type LocalAssetFile = AssetFile;
|
|
21
23
|
|
|
22
24
|
export interface LocalCatalogAsset {
|
|
25
|
+
/** Variant identity. Family identity is explicit and separately exposed. */
|
|
23
26
|
id: string;
|
|
27
|
+
familyId: string;
|
|
24
28
|
source: string;
|
|
25
29
|
name: string;
|
|
26
30
|
type: LocalAssetType;
|
|
@@ -33,12 +37,12 @@ export interface LocalCatalogAsset {
|
|
|
33
37
|
files: LocalAssetFile[];
|
|
34
38
|
animationCount?: number;
|
|
35
39
|
clipNames?: string[];
|
|
40
|
+
variants: readonly AssetVariant[];
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
export interface LocalAssetCatalog {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
assets: LocalCatalogAsset[];
|
|
43
|
+
export interface LocalAssetCatalog extends AssetCatalog {
|
|
44
|
+
/** Valid variant projections, derived once at load for existing server consumers. */
|
|
45
|
+
readonly assets: readonly LocalCatalogAsset[];
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
export function getLocalAssetLibraryRoot(): string {
|
|
@@ -49,9 +53,7 @@ export function getLocalCatalogPath(root = getLocalAssetLibraryRoot()): string {
|
|
|
49
53
|
return resolve(root, 'catalog', 'catalog.json');
|
|
50
54
|
}
|
|
51
55
|
|
|
52
|
-
export async function resolveLocalCatalogPath(
|
|
53
|
-
root = getLocalAssetLibraryRoot(),
|
|
54
|
-
): Promise<string> {
|
|
56
|
+
export async function resolveLocalCatalogPath(root = getLocalAssetLibraryRoot()): Promise<string> {
|
|
55
57
|
const externalPath = getLocalCatalogPath(root);
|
|
56
58
|
try {
|
|
57
59
|
await stat(externalPath);
|
|
@@ -74,10 +76,17 @@ export async function loadLocalAssetCatalog(
|
|
|
74
76
|
return cachedCatalog;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
const input: unknown = JSON.parse(await readFile(catalogPath, 'utf8'));
|
|
80
|
+
const normalized =
|
|
81
|
+
input && typeof input === 'object' && 'version' in input && input.version === 1
|
|
82
|
+
? migrateLegacyAssetCatalog(input)
|
|
83
|
+
: parseAssetCatalog(input);
|
|
84
|
+
const parsed: LocalAssetCatalog = {
|
|
85
|
+
...normalized,
|
|
86
|
+
assets: normalized.families.flatMap((family) =>
|
|
87
|
+
family.variants.map((variant) => projectCatalogVariant(family, variant)),
|
|
88
|
+
),
|
|
89
|
+
};
|
|
81
90
|
cachedCatalog = parsed;
|
|
82
91
|
cachedPath = catalogPath;
|
|
83
92
|
cachedMtimeMs = catalogStat.mtimeMs;
|
|
@@ -89,7 +98,49 @@ export async function findLocalAsset(
|
|
|
89
98
|
root = getLocalAssetLibraryRoot(),
|
|
90
99
|
): Promise<LocalCatalogAsset | null> {
|
|
91
100
|
const catalog = await loadLocalAssetCatalog(root);
|
|
92
|
-
|
|
101
|
+
const exactVariant = catalog.assets.find((asset) => asset.id === id);
|
|
102
|
+
if (exactVariant) return exactVariant;
|
|
103
|
+
const family = catalog.families.find((candidate) => candidate.id === id);
|
|
104
|
+
return family ? preferredLocalAssetVariant(family) : null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function findLocalAssetFamily(
|
|
108
|
+
id: string,
|
|
109
|
+
root = getLocalAssetLibraryRoot(),
|
|
110
|
+
): Promise<AssetFamily | null> {
|
|
111
|
+
const catalog = await loadLocalAssetCatalog(root);
|
|
112
|
+
return (
|
|
113
|
+
catalog.families.find((family) => family.id === id) ??
|
|
114
|
+
catalog.families.find((family) => family.variants.some((variant) => variant.id === id)) ??
|
|
115
|
+
null
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function projectCatalogVariant(family: AssetFamily, variant: AssetVariant): LocalCatalogAsset {
|
|
120
|
+
return {
|
|
121
|
+
id: variant.id,
|
|
122
|
+
familyId: family.id,
|
|
123
|
+
source: family.source,
|
|
124
|
+
name: family.name,
|
|
125
|
+
type: variant.type,
|
|
126
|
+
categories: family.categories,
|
|
127
|
+
tags: family.tags,
|
|
128
|
+
license: family.license,
|
|
129
|
+
author: family.author,
|
|
130
|
+
sourceUrl: family.sourceUrl,
|
|
131
|
+
thumbnail: variant.thumbnail.path,
|
|
132
|
+
files: variant.files,
|
|
133
|
+
...(variant.animationCount !== undefined ? { animationCount: variant.animationCount } : {}),
|
|
134
|
+
...(variant.clipNames !== undefined ? { clipNames: variant.clipNames } : {}),
|
|
135
|
+
variants: family.variants,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function preferredLocalAssetVariant(family: AssetFamily): LocalCatalogAsset {
|
|
140
|
+
const ranked = rankLocalAssetPreviewVariants(
|
|
141
|
+
family.variants.map((variant) => projectCatalogVariant(family, variant)),
|
|
142
|
+
);
|
|
143
|
+
return ranked[0] ?? projectCatalogVariant(family, family.variants[0]!);
|
|
93
144
|
}
|
|
94
145
|
|
|
95
146
|
export function localAssetMatchesType(asset: LocalCatalogAsset, type?: string): boolean {
|
|
@@ -113,6 +164,76 @@ export function resolveCatalogFile(root: string, relativePath: string): string {
|
|
|
113
164
|
return absolutePath;
|
|
114
165
|
}
|
|
115
166
|
|
|
167
|
+
/** Stable SSD cache location for a catalog card's generated preview. */
|
|
168
|
+
export function localAssetThumbnailPath(asset: LocalCatalogAsset): string {
|
|
169
|
+
return asset.thumbnail ?? `thumbnails/${asset.id}.webp`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Resolve a generated/upstream thumbnail without allowing catalog path escapes. */
|
|
173
|
+
export function resolveLocalAssetThumbnail(root: string, asset: LocalCatalogAsset): string {
|
|
174
|
+
return resolveCatalogFile(root, localAssetThumbnailPath(asset));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const PREVIEW_FORMAT_PRIORITY: Record<string, number> = {
|
|
178
|
+
glb: 0,
|
|
179
|
+
gltf: 1,
|
|
180
|
+
obj: 2,
|
|
181
|
+
dae: 3,
|
|
182
|
+
fbx: 4,
|
|
183
|
+
stl: 5,
|
|
184
|
+
ply: 6,
|
|
185
|
+
'3ds': 7,
|
|
186
|
+
bvh: 8,
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Identify format variants of the same catalog item without relying on category order.
|
|
191
|
+
* Catalog sources consistently use `sources/<source-root>/<collection>/...`; collection plus
|
|
192
|
+
* normalized display name distinguishes same-named assets from different packs and GSO objects.
|
|
193
|
+
*/
|
|
194
|
+
export function localAssetVariantKey(asset: LocalCatalogAsset): string {
|
|
195
|
+
return asset.familyId;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Best-first preview candidates for every source-format variant of an asset. */
|
|
199
|
+
export function localAssetPreviewVariants(
|
|
200
|
+
asset: LocalCatalogAsset,
|
|
201
|
+
catalogAssets: readonly LocalCatalogAsset[],
|
|
202
|
+
): LocalCatalogAsset[] {
|
|
203
|
+
const key = localAssetVariantKey(asset);
|
|
204
|
+
return rankLocalAssetPreviewVariants(
|
|
205
|
+
catalogAssets.filter((candidate) => localAssetVariantKey(candidate) === key),
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Rank an already-associated set of semantic variants without regrouping it. */
|
|
210
|
+
export function rankLocalAssetPreviewVariants(
|
|
211
|
+
variants: readonly LocalCatalogAsset[],
|
|
212
|
+
): LocalCatalogAsset[] {
|
|
213
|
+
return [...variants].sort((left, right) => {
|
|
214
|
+
const leftFile = left.files[0];
|
|
215
|
+
const rightFile = right.files[0];
|
|
216
|
+
const formatDifference =
|
|
217
|
+
(PREVIEW_FORMAT_PRIORITY[leftFile?.format ?? ''] ?? 99) -
|
|
218
|
+
(PREVIEW_FORMAT_PRIORITY[rightFile?.format ?? ''] ?? 99);
|
|
219
|
+
if (formatDifference !== 0) return formatDifference;
|
|
220
|
+
const qualityDifference =
|
|
221
|
+
previewPathPenalty(leftFile?.path) - previewPathPenalty(rightFile?.path);
|
|
222
|
+
if (qualityDifference !== 0) return qualityDifference;
|
|
223
|
+
return (leftFile?.path ?? '').localeCompare(rightFile?.path ?? '');
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function previewPathPenalty(path: string | undefined): number {
|
|
228
|
+
const normalized = path?.toLowerCase() ?? '';
|
|
229
|
+
let penalty = 0;
|
|
230
|
+
if (normalized.includes('unity')) penalty += 4;
|
|
231
|
+
if (normalized.includes('draco')) penalty += 3;
|
|
232
|
+
if (normalized.includes('quantized')) penalty += 2;
|
|
233
|
+
if (normalized.includes('embedded')) penalty += 1;
|
|
234
|
+
return penalty;
|
|
235
|
+
}
|
|
236
|
+
|
|
116
237
|
export function searchLocalCatalog(
|
|
117
238
|
catalog: LocalAssetCatalog,
|
|
118
239
|
options: { q?: string; type?: string; category?: string; offset?: number; limit?: number },
|
|
@@ -121,7 +242,7 @@ export function searchLocalCatalog(
|
|
|
121
242
|
const category = options.category?.toLowerCase();
|
|
122
243
|
const type = options.type === 'all' ? undefined : options.type;
|
|
123
244
|
|
|
124
|
-
const matches = catalog.
|
|
245
|
+
const matches = catalog.families.map(preferredLocalAssetVariant).filter((asset) => {
|
|
125
246
|
if (!localAssetMatchesType(asset, type)) return false;
|
|
126
247
|
if (category && !asset.categories.some((value) => value.toLowerCase() === category)) {
|
|
127
248
|
return false;
|
|
@@ -151,7 +272,21 @@ export function localAssetSlug(asset: LocalCatalogAsset): string {
|
|
|
151
272
|
.toLowerCase()
|
|
152
273
|
.replace(/[^a-z0-9]+/g, '-')
|
|
153
274
|
.replace(/^-|-$/g, '');
|
|
154
|
-
return `${base || 'asset'}-${asset.
|
|
275
|
+
return `${base || 'asset'}-${asset.familyId.slice(0, 8)}`;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function localAssetCapabilities(asset: LocalCatalogAsset) {
|
|
279
|
+
const capabilities = assetCapabilities(asset.files[0]?.format ?? 'unknown');
|
|
280
|
+
return {
|
|
281
|
+
previewable: capabilities.previewable,
|
|
282
|
+
importable: capabilities.importable,
|
|
283
|
+
runtimeReady: capabilities.runtimeReady,
|
|
284
|
+
animated: capabilities.animated || (asset.animationCount ?? 0) > 0,
|
|
285
|
+
deliveryAvailable: asset.variants.some((variant) =>
|
|
286
|
+
variant.deliveries.some((delivery) => delivery.available),
|
|
287
|
+
),
|
|
288
|
+
health: capabilities.runtimeReady ? 'healthy' : capabilities.importable ? 'source-only' : 'unsupported',
|
|
289
|
+
} as const;
|
|
155
290
|
}
|
|
156
291
|
|
|
157
292
|
export function makeLocalAssetId(source: string, key: string): string {
|
package/server/packaged.ts
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* ENABLED) whose job is ONLY to resolve/transform the opened PROJECT's own
|
|
29
29
|
* source. Because that Vite instance's `root` is the project directory,
|
|
30
30
|
* Vite's own resolver naturally walks the PROJECT's OWN `node_modules` for
|
|
31
|
-
* every bare specifier (`three`, `zod`, `react`,
|
|
31
|
+
* every bare specifier (`three`, `zod`, `react`, ...) —
|
|
32
32
|
* for free, with zero hand-written import rewriting. This is the same
|
|
33
33
|
* pattern `packages/editor/scripts/run-conformance.ts` uses to run the
|
|
34
34
|
* adapter conformance kit against an arbitrary external folder, adapted for
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
* slice) is reconstructed here too — pointed at the PROJECT's own installed
|
|
40
40
|
* `@vgai/engine`, not a copy baked into this package's own tarball. A
|
|
41
41
|
* project pinning `@vgai/engine@0.3.0` must be served ITS 0.3.0 source, so
|
|
42
|
-
* `@vgai/engine
|
|
42
|
+
* `@vgai/engine` is resolved via `createRequire`, rooted
|
|
43
43
|
* at the project directory — Node's own resolution algorithm, not a
|
|
44
44
|
* hardcoded path (see `resolveInstalledPackageSrcDir` in `server-utils.ts`,
|
|
45
45
|
* shared with `editor-server.ts`'s equivalent fix for `prod.ts`'s route).
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
* packaged editor (`vgai.game.json`'s `kind: 'react'` "hud" world,
|
|
123
123
|
* `useGameState`) threw exactly this at mount: `TypeError: Cannot read
|
|
124
124
|
* properties of null (reading 'useContext')` inside `useGame()`
|
|
125
|
-
* (
|
|
125
|
+
* (`@vgai/engine/react/game-state`), called from the project's `Hud` component,
|
|
126
126
|
* rendered by the editor's OWN bundled `react_stack_bottom_frame`
|
|
127
127
|
* (`assets/index-*.js`).
|
|
128
128
|
*
|
|
@@ -208,21 +208,22 @@
|
|
|
208
208
|
* is the supported path today.
|
|
209
209
|
*/
|
|
210
210
|
|
|
211
|
-
import { realpathSync } from 'node:fs';
|
|
211
|
+
import { readFileSync, realpathSync } from 'node:fs';
|
|
212
212
|
import path from 'node:path';
|
|
213
213
|
import { fileURLToPath } from 'node:url';
|
|
214
214
|
import express from 'express';
|
|
215
215
|
import { createServer as createViteServer } from 'vite';
|
|
216
216
|
import { projectGameStaticPlugin } from '../vite-plugin-project-game-static';
|
|
217
|
-
import { reactWorldProviderPlugin } from '../vite-plugin-react-world-provider';
|
|
218
217
|
import { reactWorldRuntimePlugin } from '../vite-plugin-react-world-runtime';
|
|
219
218
|
import { uiOidPlugin } from '../vite-plugin-ui-oid';
|
|
220
219
|
import { canonicalProjectRoot } from './canonical-path';
|
|
221
220
|
import { createEditorServer } from './editor-server';
|
|
222
221
|
import { shouldShadowGameGlobals } from './game-globals-shadow';
|
|
222
|
+
import { closeHttpServer, createProcessShutdown } from './process-shutdown';
|
|
223
|
+
import { classifyProjectHotUpdate } from './project-hmr-files';
|
|
223
224
|
import {
|
|
224
|
-
SERVER_ONLY_PREBUNDLE_EXCLUDE,
|
|
225
225
|
computeProjectOptimizeDepsEntries,
|
|
226
|
+
SERVER_ONLY_PREBUNDLE_EXCLUDE,
|
|
226
227
|
} from './project-optimize-deps-entries';
|
|
227
228
|
import {
|
|
228
229
|
friendlyListenError,
|
|
@@ -243,7 +244,11 @@ const editorPackageRoot = path.resolve(__dirname, '..');
|
|
|
243
244
|
const distPath = path.resolve(editorPackageRoot, 'dist');
|
|
244
245
|
|
|
245
246
|
const PORT = Number(process.env['PORT']) || Number(process.env['VGAI_EDITOR_PORT']) || 5173;
|
|
246
|
-
|
|
247
|
+
// Keep generated projects' paired HMR sockets in a disjoint band from their
|
|
248
|
+
// persisted 20000-29999 editor-port preferences (plus launcher fall-forward).
|
|
249
|
+
const DEFAULT_HMR_PORT =
|
|
250
|
+
PORT >= 20_000 && PORT < 30_100 ? PORT + 10_000 : PORT === 5173 ? 24678 : PORT + 100;
|
|
251
|
+
const HMR_PORT = Number(process.env['VGAI_HMR_PORT']) || DEFAULT_HMR_PORT;
|
|
247
252
|
// S3: bind to loopback by default; opt-in to a wider interface via env.
|
|
248
253
|
const HOST = resolveBindHost();
|
|
249
254
|
|
|
@@ -320,6 +325,14 @@ async function main(): Promise<void> {
|
|
|
320
325
|
configFile: false,
|
|
321
326
|
root: projectPath,
|
|
322
327
|
appType: 'custom', // no HTML serving/SPA-fallback — this instance only serves project modules
|
|
328
|
+
// This Vite instance transforms not only project TSX (which uiOidPlugin
|
|
329
|
+
// already sends through automatic JSX) but also the installed engine's
|
|
330
|
+
// React bridge. Vite/esbuild otherwise compiles that dependency source to
|
|
331
|
+
// `React.createElement` even though the canonical bridge uses named React
|
|
332
|
+
// imports and intentionally has no global `React` binding. Keep Fast
|
|
333
|
+
// Refresh out (the custom app has no preamble host), but make the JSX
|
|
334
|
+
// runtime explicit for every TSX module served through this graph.
|
|
335
|
+
esbuild: { jsx: 'automatic' },
|
|
323
336
|
plugins: [
|
|
324
337
|
// React-world OID authoring parity (see this file's header doc comment,
|
|
325
338
|
// "React-world OID authoring parity"): live `data-oid` instrumentation +
|
|
@@ -328,12 +341,6 @@ async function main(): Promise<void> {
|
|
|
328
341
|
// (already project-scoped) unmodified — no extra scoping needed since this
|
|
329
342
|
// instance's `root` IS the project already.
|
|
330
343
|
uiOidPlugin(),
|
|
331
|
-
// Resolves the synthetic `/__vgai-game-provider` module
|
|
332
|
-
// `adapter-resolver.ts`'s `loadProjectGameProvider` imports for every
|
|
333
|
-
// `kind: 'react'` world's mount — WITHOUT this, a react world 404s at
|
|
334
|
-
// mount time in packaged mode (this is a correctness fix, not just an
|
|
335
|
-
// authoring nicety). See vite-plugin-react-world-provider.ts's doc comment.
|
|
336
|
-
reactWorldProviderPlugin(),
|
|
337
344
|
// Resolves the synthetic `/__vgai-react-world-runtime` module
|
|
338
345
|
// `adapter-resolver.ts`'s `resolveReactWorldMountRuntime` imports —
|
|
339
346
|
// the dual-React-instance fix (this file's header doc comment,
|
|
@@ -367,11 +374,25 @@ async function main(): Promise<void> {
|
|
|
367
374
|
file: string;
|
|
368
375
|
server: { ws: { send: (msg: unknown) => void } };
|
|
369
376
|
}) {
|
|
370
|
-
|
|
377
|
+
const kind = classifyProjectHotUpdate(
|
|
378
|
+
file,
|
|
379
|
+
currentProjectRoot,
|
|
380
|
+
/\.[jt]sx?$/.test(file) ? readFileSync(file, 'utf-8') : '',
|
|
381
|
+
);
|
|
382
|
+
if (kind === 'outside' || kind === 'stock-data') return;
|
|
383
|
+
if (kind === 'react') return;
|
|
384
|
+
if (kind === 'component') {
|
|
371
385
|
server.ws.send({ type: 'custom', event: 'vgai:script-update', data: { file } });
|
|
372
386
|
}
|
|
373
|
-
|
|
374
|
-
|
|
387
|
+
if (kind === 'restart') {
|
|
388
|
+
server.ws.send({
|
|
389
|
+
type: 'custom',
|
|
390
|
+
event: 'vgai:restart-required',
|
|
391
|
+
data: { file },
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
// Swallow non-runtime project changes. In particular, building or
|
|
395
|
+
// running a tool under scripts/ must never import it into Play mode.
|
|
375
396
|
return [];
|
|
376
397
|
},
|
|
377
398
|
},
|
|
@@ -465,6 +486,12 @@ async function main(): Promise<void> {
|
|
|
465
486
|
},
|
|
466
487
|
resolve: {
|
|
467
488
|
alias: [
|
|
489
|
+
// Project-authored editor tools import the editor's public widget kit
|
|
490
|
+
// through `@editor/widgets` (the scaffolder writes the matching
|
|
491
|
+
// tsconfig path). In checkout mode the root Vite config supplies this
|
|
492
|
+
// alias; this project-rooted packaged Vite instance has no configFile,
|
|
493
|
+
// so it must reconstruct the runtime half from its own package root.
|
|
494
|
+
{ find: /^@editor\//, replacement: `${path.join(editorPackageRoot, 'src')}/` },
|
|
468
495
|
// The `@engine/*` alias project source still uses (pending the
|
|
469
496
|
// `@engine/*` → `@vgai/engine/*` migration deferred to a follow-up
|
|
470
497
|
// slice) — pointed at THIS project's own installed engine version,
|
|
@@ -496,6 +523,7 @@ async function main(): Promise<void> {
|
|
|
496
523
|
const editorRouter = createEditorServer({
|
|
497
524
|
engineRoot: editorPackageRoot,
|
|
498
525
|
projectPath,
|
|
526
|
+
loadProjectModule: (moduleId) => vite.ssrLoadModule(moduleId),
|
|
499
527
|
onProjectOpened(newProjectPath: string) {
|
|
500
528
|
const allowed = vite.config.server.fs.allow;
|
|
501
529
|
if (!allowed.includes(newProjectPath)) {
|
|
@@ -555,13 +583,15 @@ async function main(): Promise<void> {
|
|
|
555
583
|
process.exit(1);
|
|
556
584
|
});
|
|
557
585
|
|
|
558
|
-
const shutdown = (
|
|
586
|
+
const shutdown = createProcessShutdown({
|
|
587
|
+
tasks: [() => closeHttpServer(server), () => editorRouter.close(), () => vite.close()],
|
|
588
|
+
});
|
|
589
|
+
const unregisterAndShutdown = (signal: string) => {
|
|
559
590
|
unregisterSession(process.pid);
|
|
560
|
-
|
|
561
|
-
server.close();
|
|
591
|
+
void shutdown(signal);
|
|
562
592
|
};
|
|
563
|
-
process.once('SIGINT',
|
|
564
|
-
process.once('SIGTERM',
|
|
593
|
+
process.once('SIGINT', () => unregisterAndShutdown('SIGINT'));
|
|
594
|
+
process.once('SIGTERM', () => unregisterAndShutdown('SIGTERM'));
|
|
565
595
|
process.once('exit', () => unregisterSession(process.pid));
|
|
566
596
|
}
|
|
567
597
|
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { type ChildProcess, execFileSync } from 'node:child_process';
|
|
2
|
+
import type { Server } from 'node:http';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Terminate a detached child and its whole process tree. Shared by the e2e
|
|
6
|
+
* server harness and the packaged probe runner: keeping this under `server/`
|
|
7
|
+
* makes the runtime utility part of the editor package instead of forcing a
|
|
8
|
+
* published command to import an unshipped e2e helper.
|
|
9
|
+
*/
|
|
10
|
+
export function stopProcess(proc: ChildProcess): Promise<void> {
|
|
11
|
+
const signalGroup = (signal: NodeJS.Signals) => {
|
|
12
|
+
if (proc.pid === undefined) return;
|
|
13
|
+
if (process.platform === 'win32') {
|
|
14
|
+
try {
|
|
15
|
+
execFileSync('taskkill', ['/PID', String(proc.pid), '/T', '/F'], { stdio: 'ignore' });
|
|
16
|
+
} catch {
|
|
17
|
+
/* already dead, or taskkill unavailable */
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
proc.kill(signal);
|
|
21
|
+
} catch {
|
|
22
|
+
/* already dead */
|
|
23
|
+
}
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
process.kill(-proc.pid, signal);
|
|
28
|
+
} catch {
|
|
29
|
+
try {
|
|
30
|
+
proc.kill(signal);
|
|
31
|
+
} catch {
|
|
32
|
+
/* already dead */
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return new Promise((resolve) => {
|
|
38
|
+
if (proc.exitCode !== null || proc.signalCode !== null) {
|
|
39
|
+
signalGroup('SIGKILL');
|
|
40
|
+
resolve();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const timer = setTimeout(() => {
|
|
44
|
+
signalGroup('SIGKILL');
|
|
45
|
+
resolve();
|
|
46
|
+
}, 5000);
|
|
47
|
+
proc.on('exit', () => {
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
signalGroup('SIGKILL');
|
|
50
|
+
resolve();
|
|
51
|
+
});
|
|
52
|
+
signalGroup('SIGTERM');
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ProcessShutdownOptions {
|
|
57
|
+
timeoutMs?: number;
|
|
58
|
+
tasks: ReadonlyArray<() => void | Promise<void>>;
|
|
59
|
+
exit?: (code: number) => void;
|
|
60
|
+
log?: (message: string) => void;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Stop accepting requests and forcibly release persistent HTTP/SSE sockets. */
|
|
64
|
+
export function closeHttpServer(server: Server): Promise<void> {
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
if (!server.listening) {
|
|
67
|
+
server.closeAllConnections?.();
|
|
68
|
+
resolve();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
server.close((error) => (error ? reject(error) : resolve()));
|
|
72
|
+
// `server.close()` waits for persistent SSE/keep-alive connections. They
|
|
73
|
+
// have no useful work after process shutdown begins, so close them after
|
|
74
|
+
// removing the listener and eliminate the restart race on the HTTP port.
|
|
75
|
+
server.closeAllConnections?.();
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Build an idempotent, bounded signal handler.
|
|
81
|
+
*
|
|
82
|
+
* Node's default signal exit is disabled as soon as an application installs
|
|
83
|
+
* a SIGTERM/SIGINT listener. Tooling loaders (notably tsx/esbuild) may retain
|
|
84
|
+
* internal handles even after every application server and watcher closes,
|
|
85
|
+
* so waiting for an empty event loop is not a shutdown contract. Resources
|
|
86
|
+
* get one bounded graceful window and then the process exits explicitly.
|
|
87
|
+
*/
|
|
88
|
+
export function createProcessShutdown(
|
|
89
|
+
options: ProcessShutdownOptions,
|
|
90
|
+
): (why: string) => Promise<void> {
|
|
91
|
+
const timeoutMs = options.timeoutMs ?? 5_000;
|
|
92
|
+
const exit = options.exit ?? ((code: number) => process.exit(code));
|
|
93
|
+
const log = options.log ?? ((message: string) => process.stderr.write(`${message}\n`));
|
|
94
|
+
let pending: Promise<void> | null = null;
|
|
95
|
+
|
|
96
|
+
return (why: string) => {
|
|
97
|
+
if (pending) return pending;
|
|
98
|
+
pending = (async () => {
|
|
99
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
100
|
+
const cleanup = Promise.allSettled(
|
|
101
|
+
options.tasks.map(async (task) => {
|
|
102
|
+
await task();
|
|
103
|
+
}),
|
|
104
|
+
);
|
|
105
|
+
const result = await Promise.race([
|
|
106
|
+
cleanup.then((settled) => ({ type: 'settled' as const, settled })),
|
|
107
|
+
new Promise<{ type: 'timeout' }>((resolve) => {
|
|
108
|
+
timeout = setTimeout(() => resolve({ type: 'timeout' }), timeoutMs);
|
|
109
|
+
}),
|
|
110
|
+
]);
|
|
111
|
+
if (timeout) clearTimeout(timeout);
|
|
112
|
+
|
|
113
|
+
if (result.type === 'timeout') {
|
|
114
|
+
log(`[vgai-editor] cleanup timed out after ${timeoutMs}ms (${why}); forcing exit`);
|
|
115
|
+
exit(1);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const failures = result.settled.filter(
|
|
119
|
+
(item): item is PromiseRejectedResult => item.status === 'rejected',
|
|
120
|
+
);
|
|
121
|
+
if (failures.length > 0) {
|
|
122
|
+
log(
|
|
123
|
+
`[vgai-editor] cleanup failed (${why}): ${failures
|
|
124
|
+
.map((item) =>
|
|
125
|
+
item.reason instanceof Error ? item.reason.message : String(item.reason),
|
|
126
|
+
)
|
|
127
|
+
.join('; ')}`,
|
|
128
|
+
);
|
|
129
|
+
exit(1);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
exit(0);
|
|
133
|
+
})();
|
|
134
|
+
return pending;
|
|
135
|
+
};
|
|
136
|
+
}
|
package/server/prod.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
13
13
|
import express from 'express';
|
|
14
14
|
import { canonicalProjectRoot } from './canonical-path';
|
|
15
15
|
import { createEditorServer } from './editor-server';
|
|
16
|
+
import { closeHttpServer, createProcessShutdown } from './process-shutdown';
|
|
16
17
|
import { friendlyListenError, resolveBindHost } from './server-utils';
|
|
17
18
|
|
|
18
19
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -47,7 +48,9 @@ async function main(): Promise<void> {
|
|
|
47
48
|
const server = app.listen(PORT, HOST, () => {
|
|
48
49
|
const addr = server.address();
|
|
49
50
|
const actualPort = typeof addr === 'object' && addr ? addr.port : PORT;
|
|
50
|
-
console.log(
|
|
51
|
+
console.log(
|
|
52
|
+
`\n \x1b[32mProduction server\x1b[0m running at \x1b[36mhttp://${HOST}:${actualPort}\x1b[0m`,
|
|
53
|
+
);
|
|
51
54
|
if (projectPath) {
|
|
52
55
|
console.log(` \x1b[33mProject\x1b[0m: ${projectPath}`);
|
|
53
56
|
}
|
|
@@ -62,11 +65,11 @@ async function main(): Promise<void> {
|
|
|
62
65
|
process.exit(1);
|
|
63
66
|
});
|
|
64
67
|
|
|
65
|
-
const shutdown = (
|
|
66
|
-
server.close()
|
|
67
|
-
};
|
|
68
|
-
process.once('SIGINT', shutdown);
|
|
69
|
-
process.once('SIGTERM', shutdown);
|
|
68
|
+
const shutdown = createProcessShutdown({
|
|
69
|
+
tasks: [() => closeHttpServer(server), () => editorRouter.close()],
|
|
70
|
+
});
|
|
71
|
+
process.once('SIGINT', () => void shutdown('SIGINT'));
|
|
72
|
+
process.once('SIGTERM', () => void shutdown('SIGTERM'));
|
|
70
73
|
}
|
|
71
74
|
|
|
72
75
|
main().catch((err) => {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
export type ProjectHotUpdateKind =
|
|
4
|
+
| 'outside'
|
|
5
|
+
| 'stock-data'
|
|
6
|
+
| 'react'
|
|
7
|
+
| 'component'
|
|
8
|
+
| 'restart'
|
|
9
|
+
| 'ignore';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Classify a Vite watcher update for the editor's project-script HMR seam.
|
|
13
|
+
*
|
|
14
|
+
* Only modules below the project's `src/` directory are game code. Build
|
|
15
|
+
* output, tests, one-off scripts, and dependency caches may also contain
|
|
16
|
+
* JavaScript, but importing them into the live game is both surprising and
|
|
17
|
+
* unsafe (a project build used to make the editor import `dist/assets/*`).
|
|
18
|
+
*/
|
|
19
|
+
export function classifyProjectHotUpdate(
|
|
20
|
+
file: string,
|
|
21
|
+
projectRoot: string,
|
|
22
|
+
source = '',
|
|
23
|
+
): ProjectHotUpdateKind {
|
|
24
|
+
const normalizedFile = file.replaceAll('\\', '/');
|
|
25
|
+
const normalizedRoot = projectRoot.replaceAll('\\', '/').replace(/\/$/, '');
|
|
26
|
+
const relative = path.posix.relative(normalizedRoot, normalizedFile);
|
|
27
|
+
|
|
28
|
+
if (relative === '' || relative === '..' || relative.startsWith('../')) return 'outside';
|
|
29
|
+
if (relative === 'vgai.game.json') return 'restart';
|
|
30
|
+
if (!relative.startsWith('src/')) return 'ignore';
|
|
31
|
+
// Project-operation wrappers are editor/server tooling. A game that wants
|
|
32
|
+
// shared generation imports the underlying ordinary module, not this
|
|
33
|
+
// discoverability wrapper, so editing an operation must not request a full
|
|
34
|
+
// game restart.
|
|
35
|
+
if (/^src\/operations\/.*\.operation\.(ts|js)$/.test(relative)) return 'ignore';
|
|
36
|
+
if (/\.data\.json$/.test(relative)) return 'stock-data';
|
|
37
|
+
if (!/\.(ts|tsx|js|jsx)$/.test(relative)) return 'ignore';
|
|
38
|
+
if (/\bextends\s+(?:[A-Za-z_$][\w$]*\.)?GameComponent\b/.test(source)) return 'component';
|
|
39
|
+
if (/\.(tsx|jsx)$/.test(relative)) return 'react';
|
|
40
|
+
return 'restart';
|
|
41
|
+
}
|