@vgai/editor 0.2.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/LICENSE +661 -0
- package/README.md +35 -0
- package/dist/a/b/c/test-nested.json +1 -0
- package/dist/assets/CCDIKSolver-Dz-ysxZF.js +1 -0
- package/dist/assets/SelectionBox-BXsv_6aH.js +1 -0
- package/dist/assets/browser-project-scripts-DOru4L1x.js +2 -0
- package/dist/assets/browserAll-CEkWYlFk.js +1 -0
- package/dist/assets/collision-world-DniMB0qq.glb +0 -0
- package/dist/assets/create-runtime-DYIWr3va.js +1 -0
- package/dist/assets/data-asset-Bb9sIM7i.js +5 -0
- package/dist/assets/esbuild-BHljloGq.wasm +0 -0
- package/dist/assets/fake-three-BMTQ9rl_.js +1 -0
- package/dist/assets/game-8SDZPPgH.js +1 -0
- package/dist/assets/game-BI111o5f.js +10 -0
- package/dist/assets/game-CevorxyV.js +1 -0
- package/dist/assets/game-Di1H0gFj.js +1 -0
- package/dist/assets/game-Di7XlywD.js +1 -0
- package/dist/assets/game-DjnCaw8R.js +1 -0
- package/dist/assets/game-Q9A0aXSV.js +1 -0
- package/dist/assets/game-pt_1lQLv.js +1 -0
- package/dist/assets/game-source-CvidN7AB.js +1 -0
- package/dist/assets/game-xsOho_wg.js +83 -0
- package/dist/assets/index-Bp5cecD6.js +12 -0
- package/dist/assets/index-CPVV6pjw.js +1 -0
- package/dist/assets/index-CpOnYyeD.js +213 -0
- package/dist/assets/index-Cs8xwv3e.js +1 -0
- package/dist/assets/index-DL7RcQ0n.js +27 -0
- package/dist/assets/index-Dr8H_6Rl.js +1 -0
- package/dist/assets/index-_bCaipUk.js +13650 -0
- package/dist/assets/ingest-siblings-Dz8eEqrV.js +1 -0
- package/dist/assets/mount-manifest-Bhk0ohsl.js +1 -0
- package/dist/assets/recast-navigation.wasm-compat-CvP4UGK6.js +1 -0
- package/dist/assets/scene-query-BivAHhJu.js +1 -0
- package/dist/assets/stats.module--VATS4Kh.js +1 -0
- package/dist/assets/webworkerAll-BLrH0F6F.js +1 -0
- package/dist/assets/xstate-animation-binding-1NGLmw6y.js +1 -0
- package/dist/assets/xstate.esm-BgUD33nl.js +3 -0
- package/dist/index.html +37 -0
- package/dist/ingest/racing-game/UPSTREAM.md +68 -0
- package/dist/ingest/racing-game/a/Inter-Black.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Black.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-BlackItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-BlackItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Bold.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Bold.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-BoldItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-BoldItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBold.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBold.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBoldItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBoldItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLight.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLight.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLightItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLightItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Italic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Italic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Light.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Light.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-LightItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-LightItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Medium.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Medium.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-MediumItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-MediumItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Regular.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Regular.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBold.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBold.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBoldItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBoldItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Thin.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Thin.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ThinItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ThinItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-italic.var.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-roman.var.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter.var.woff2 +0 -0
- package/dist/ingest/racing-game/a/index.css +1 -0
- package/dist/ingest/racing-game/images/bronze.png +0 -0
- package/dist/ingest/racing-game/images/gold.png +0 -0
- package/dist/ingest/racing-game/images/silver.png +0 -0
- package/dist/ingest/racing-game/index.html +15 -0
- package/dist/ingest/racing-game/models/chassis-draco.glb +0 -0
- package/dist/ingest/racing-game/models/track-draco.glb +0 -0
- package/dist/ingest/racing-game/models/wheel-draco.glb +0 -0
- package/dist/ingest/racing-game/rg-entry.js +1 -0
- package/dist/ingest/racing-game/sounds/accelerate.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/boost.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/crash.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/engine.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/honk.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/tire-brake.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/train.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/water.mp3 +0 -0
- package/dist/ingest/racing-game/textures/cursor.svg +3 -0
- package/dist/ingest/racing-game/textures/dikhololo_night_1k.hdr +0 -0
- package/dist/ingest/racing-game/textures/heightmap_1024.png +0 -0
- package/dist/ingest/racing-game/textures/mask.svg +11 -0
- package/dist/ingest/racing-game/textures/no_sound.png +0 -0
- package/dist/ingest/racing-game/three-r139.module.js +50617 -0
- package/dist/ingest/racing-game/vendor.js +179 -0
- package/dist/ingest/racing-game/vgai.game.json +20 -0
- package/dist/ingest/simcity/UPSTREAM.md +65 -0
- package/dist/ingest/simcity/favicon.ico +0 -0
- package/dist/ingest/simcity/fonts/Bebas-Regular.otf +0 -0
- package/dist/ingest/simcity/fonts/Digitalt.otf +0 -0
- package/dist/ingest/simcity/fonts/Stanberry.ttf +0 -0
- package/dist/ingest/simcity/icons/bulldozer-color.png +0 -0
- package/dist/ingest/simcity/icons/bulldozer.png +0 -0
- package/dist/ingest/simcity/icons/calendar.png +0 -0
- package/dist/ingest/simcity/icons/factory-color.png +0 -0
- package/dist/ingest/simcity/icons/factory.png +0 -0
- package/dist/ingest/simcity/icons/house-color.png +0 -0
- package/dist/ingest/simcity/icons/house.png +0 -0
- package/dist/ingest/simcity/icons/job.png +0 -0
- package/dist/ingest/simcity/icons/office.png +0 -0
- package/dist/ingest/simcity/icons/pause-color.png +0 -0
- package/dist/ingest/simcity/icons/pause.png +0 -0
- package/dist/ingest/simcity/icons/person.png +0 -0
- package/dist/ingest/simcity/icons/play-color.png +0 -0
- package/dist/ingest/simcity/icons/play.png +0 -0
- package/dist/ingest/simcity/icons/power-color.png +0 -0
- package/dist/ingest/simcity/icons/power-line-color.png +0 -0
- package/dist/ingest/simcity/icons/power.png +0 -0
- package/dist/ingest/simcity/icons/road-color.png +0 -0
- package/dist/ingest/simcity/icons/road.png +0 -0
- package/dist/ingest/simcity/icons/select-color.png +0 -0
- package/dist/ingest/simcity/icons/select.png +0 -0
- package/dist/ingest/simcity/icons/store-color.png +0 -0
- package/dist/ingest/simcity/index.html +94 -0
- package/dist/ingest/simcity/index.js +58 -0
- package/dist/ingest/simcity/main.css +233 -0
- package/dist/ingest/simcity/models/armored-truck.glb +0 -0
- package/dist/ingest/simcity/models/building-block-4floor-corner.glb +0 -0
- package/dist/ingest/simcity/models/building-block-4floor-short.glb +0 -0
- package/dist/ingest/simcity/models/building-block-5floor.glb +0 -0
- package/dist/ingest/simcity/models/building-burger-joint.glb +0 -0
- package/dist/ingest/simcity/models/building-cafe.glb +0 -0
- package/dist/ingest/simcity/models/building-casino.glb +0 -0
- package/dist/ingest/simcity/models/building-cinema.glb +0 -0
- package/dist/ingest/simcity/models/building-house-block-big.glb +0 -0
- package/dist/ingest/simcity/models/building-house-family-large.glb +0 -0
- package/dist/ingest/simcity/models/building-house-family-small.glb +0 -0
- package/dist/ingest/simcity/models/building-office-balcony.glb +0 -0
- package/dist/ingest/simcity/models/building-office-big.glb +0 -0
- package/dist/ingest/simcity/models/building-office-pyramid.glb +0 -0
- package/dist/ingest/simcity/models/building-office-tall.glb +0 -0
- package/dist/ingest/simcity/models/building-office.glb +0 -0
- package/dist/ingest/simcity/models/building-restaurant.glb +0 -0
- package/dist/ingest/simcity/models/building-skyscraper.glb +0 -0
- package/dist/ingest/simcity/models/car-ambulance-pickup.glb +0 -0
- package/dist/ingest/simcity/models/car-baywatch.glb +0 -0
- package/dist/ingest/simcity/models/car-hippie-van.glb +0 -0
- package/dist/ingest/simcity/models/car-passenger-race.glb +0 -0
- package/dist/ingest/simcity/models/car-passenger.glb +0 -0
- package/dist/ingest/simcity/models/car-police.glb +0 -0
- package/dist/ingest/simcity/models/car-taxi.glb +0 -0
- package/dist/ingest/simcity/models/car-tow-truck.glb +0 -0
- package/dist/ingest/simcity/models/car-truck-dump.glb +0 -0
- package/dist/ingest/simcity/models/car-veteran.glb +0 -0
- package/dist/ingest/simcity/models/construction-small.glb +0 -0
- package/dist/ingest/simcity/models/data-center.glb +0 -0
- package/dist/ingest/simcity/models/industry-factory-old.glb +0 -0
- package/dist/ingest/simcity/models/industry-factory.glb +0 -0
- package/dist/ingest/simcity/models/industry-refinery.glb +0 -0
- package/dist/ingest/simcity/models/industry-warehouse.glb +0 -0
- package/dist/ingest/simcity/models/power_line_pole_modified.glb +0 -0
- package/dist/ingest/simcity/models/tile-plain_grass.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-curve.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-end.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-intersection-t.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-intersection.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-straight.glb +0 -0
- package/dist/ingest/simcity/models/truck.glb +0 -0
- package/dist/ingest/simcity/statusIcons/no-power.png +0 -0
- package/dist/ingest/simcity/statusIcons/no-road-access.png +0 -0
- package/dist/ingest/simcity/textures/base.png +0 -0
- package/dist/ingest/simcity/textures/grid.png +0 -0
- package/dist/ingest/simcity/textures/specular.png +0 -0
- package/dist/ingest/simcity/three-r155.module.js +51466 -0
- package/dist/ingest/simcity/vgai.game.json +20 -0
- package/dist/ingest/tanks/UPSTREAM.md +69 -0
- package/dist/ingest/tanks/index.html +214 -0
- package/dist/ingest/tanks/models/pixelTank.fbx +0 -0
- package/dist/ingest/tanks/tanks-entry.js +92 -0
- package/dist/ingest/tanks/three-r170.module.js +54571 -0
- package/dist/ingest/tanks/vgai.game.json +20 -0
- package/dist/inputmaps/default.inputmap.json +41 -0
- package/dist/jsm/libs/draco/gltf/draco_decoder.js +33 -0
- package/dist/jsm/libs/draco/gltf/draco_decoder.wasm +0 -0
- package/dist/jsm/libs/draco/gltf/draco_wasm_wrapper.js +116 -0
- package/dist/scenes/untitled.vscn.json +29 -0
- package/dist/textures/waternormals.jpg +0 -0
- package/dist-server/packaged.mjs +18155 -0
- package/package.json +36 -0
- package/server/asset-library-routes.ts +833 -0
- package/server/canonical-path.ts +50 -0
- package/server/cloud-asset-catalog.ts +101 -0
- package/server/dev.ts +350 -0
- package/server/editor-server.ts +1537 -0
- package/server/editor-sse.ts +73 -0
- package/server/game-globals-shadow.ts +58 -0
- package/server/local-asset-catalog.ts +163 -0
- package/server/packaged.ts +571 -0
- package/server/prod.ts +75 -0
- package/server/project-optimize-deps-entries.ts +149 -0
- package/server/project-view.ts +65 -0
- package/server/server-utils.ts +430 -0
- package/server/session-registry.ts +93 -0
- package/src/EditorContext.tsx +87 -0
- package/src/achieved-tier.ts +126 -0
- package/src/action-registry.ts +268 -0
- package/src/adapter-resolver.ts +1858 -0
- package/src/asset-events.ts +100 -0
- package/src/authoring/active-adapter.ts +98 -0
- package/src/authoring/active-systems.ts +50 -0
- package/src/authoring/boundary-authoring-adapter.ts +178 -0
- package/src/authoring/composite-authoring-adapter.ts +891 -0
- package/src/authoring/design-time-layers.ts +776 -0
- package/src/authoring/edit-mode-authoring.ts +643 -0
- package/src/authoring/embed-only-adapter.ts +57 -0
- package/src/authoring/ephemeral-persistence.ts +26 -0
- package/src/authoring/eyedropper-session.ts +69 -0
- package/src/authoring/first-party-hierarchy-menu.ts +74 -0
- package/src/authoring/first-party-inspector/AddSectionMenu.tsx +118 -0
- package/src/authoring/first-party-inspector/EnvironmentSection.tsx +550 -0
- package/src/authoring/first-party-inspector/HeaderSection.tsx +329 -0
- package/src/authoring/first-party-inspector/is-first-party-selection.ts +44 -0
- package/src/authoring/first-party-inspector/register.tsx +108 -0
- package/src/authoring/ingest-authoring-adapter.ts +475 -0
- package/src/authoring/ingest-capture.ts +94 -0
- package/src/authoring/ingest-game-adapter.ts +291 -0
- package/src/authoring/ingest-iframe-adapter.ts +38 -0
- package/src/authoring/ingest-iframe-reachable-adapter.ts +401 -0
- package/src/authoring/ingest-overlay.ts +193 -0
- package/src/authoring/layered-pick.ts +87 -0
- package/src/authoring/mount-failure-report.ts +112 -0
- package/src/authoring/overlay-report.ts +52 -0
- package/src/authoring/project-scenes.ts +111 -0
- package/src/authoring/react-dom-authoring-adapter.ts +795 -0
- package/src/authoring/react-inspector-section.tsx +1041 -0
- package/src/authoring/react-world-authoring-adapter.ts +2400 -0
- package/src/authoring/root-activation.ts +61 -0
- package/src/authoring/scene-ui-layer.ts +485 -0
- package/src/authoring/story-overlay.ts +48 -0
- package/src/authoring/ui-authoring-adapter.ts +325 -0
- package/src/authoring/ui-hierarchy-menu.ts +40 -0
- package/src/authoring/ui-inspector-sections.tsx +156 -0
- package/src/authoring/undo-timeline.ts +145 -0
- package/src/authoring/vgai-scene-authoring-adapter.ts +746 -0
- package/src/authoring/viewport-pick-context.ts +31 -0
- package/src/authoring/viewport-raycast.ts +80 -0
- package/src/authoring/world-overlay.ts +76 -0
- package/src/authoring/world-pan-state.ts +115 -0
- package/src/authoring/world-session-state.ts +102 -0
- package/src/authoring/world2d-authoring-adapter.ts +313 -0
- package/src/browser-external-watch.ts +98 -0
- package/src/browser-play-entry.ts +112 -0
- package/src/browser-project-scripts.ts +112 -0
- package/src/browser-transpile.ts +246 -0
- package/src/browser-watch-reload.ts +100 -0
- package/src/command-listener.ts +326 -0
- package/src/components/AlignToolbar.tsx +189 -0
- package/src/components/AppRoot.tsx +73 -0
- package/src/components/AssetBrowser.tsx +502 -0
- package/src/components/BottomPanel.tsx +326 -0
- package/src/components/BuildPanel.tsx +455 -0
- package/src/components/CameraInfo.tsx +56 -0
- package/src/components/CommandPalette.tsx +265 -0
- package/src/components/ConsolePanel.tsx +272 -0
- package/src/components/DataPanel.tsx +1268 -0
- package/src/components/DefaultEditorLayout.tsx +509 -0
- package/src/components/FolderTree.tsx +179 -0
- package/src/components/GameHierarchy.tsx +1349 -0
- package/src/components/Inspector.tsx +671 -0
- package/src/components/InspectorToolSection.tsx +74 -0
- package/src/components/MountFailureBanner.tsx +110 -0
- package/src/components/NetworkSection.tsx +109 -0
- package/src/components/OnlineAssetBrowser.tsx +448 -0
- package/src/components/OverlayReportBanner.tsx +122 -0
- package/src/components/PlayBar.tsx +356 -0
- package/src/components/PrefabSaveDialog.tsx +201 -0
- package/src/components/ProjectHeader.tsx +174 -0
- package/src/components/ProjectScreen.tsx +1004 -0
- package/src/components/RightPanel.tsx +166 -0
- package/src/components/RootActivateDialog.tsx +105 -0
- package/src/components/SaveDialog.tsx +197 -0
- package/src/components/SaveStatus.tsx +69 -0
- package/src/components/StatsOverlay.tsx +35 -0
- package/src/components/ToolHost.tsx +80 -0
- package/src/components/Toolbar.tsx +307 -0
- package/src/components/ViewportOverlay.tsx +234 -0
- package/src/components/ViewportPanel.tsx +369 -0
- package/src/components/WorldContextMenu.tsx +257 -0
- package/src/components/WorldSelectionOverlay.tsx +2003 -0
- package/src/components/WorldTextEditor.tsx +86 -0
- package/src/components/asset-viewers/AssetViewer.tsx +38 -0
- package/src/components/asset-viewers/AudioViewer.tsx +149 -0
- package/src/components/asset-viewers/GenericJsonViewer.tsx +77 -0
- package/src/components/asset-viewers/ImageViewer.tsx +65 -0
- package/src/components/asset-viewers/InputMapViewer.tsx +206 -0
- package/src/components/asset-viewers/ModelViewer.tsx +180 -0
- package/src/components/asset-viewers/OnlineAssetDetail.tsx +236 -0
- package/src/components/data-csv.ts +301 -0
- package/src/components/entity-name-commit.ts +18 -0
- package/src/components/inspector-property-grouping.ts +65 -0
- package/src/components/inspector-widgets/AlignmentGrid.tsx +188 -0
- package/src/components/inspector-widgets/BorderEditor.tsx +305 -0
- package/src/components/inspector-widgets/ColorPicker.tsx +548 -0
- package/src/components/inspector-widgets/FilterEditor.tsx +108 -0
- package/src/components/inspector-widgets/FontPicker.tsx +196 -0
- package/src/components/inspector-widgets/GradientEditor.tsx +277 -0
- package/src/components/inspector-widgets/ScrubbableInput.tsx +152 -0
- package/src/components/inspector-widgets/ShadowEditor.tsx +332 -0
- package/src/components/inspector-widgets/color-utils.ts +202 -0
- package/src/components/inspector-widgets/gallery-main.tsx +167 -0
- package/src/components/inspector-widgets/index.ts +22 -0
- package/src/components/inspector-widgets/shared.tsx +135 -0
- package/src/components/inspectors/AnimationSection.tsx +143 -0
- package/src/components/inspectors/AudioSection.tsx +215 -0
- package/src/components/inspectors/BoneAttachmentSection.tsx +82 -0
- package/src/components/inspectors/CameraSection.tsx +105 -0
- package/src/components/inspectors/ComponentsSection.tsx +358 -0
- package/src/components/inspectors/JointsSection.tsx +255 -0
- package/src/components/inspectors/LightSection.tsx +116 -0
- package/src/components/inspectors/MaterialSection.tsx +1189 -0
- package/src/components/inspectors/MeshSection.tsx +99 -0
- package/src/components/inspectors/NavigationSection.tsx +58 -0
- package/src/components/inspectors/ParticleSection.tsx +681 -0
- package/src/components/inspectors/PhysicsSection.tsx +326 -0
- package/src/components/inspectors/PostProcessingSection.tsx +1290 -0
- package/src/components/inspectors/RenderingSection.tsx +109 -0
- package/src/components/inspectors/ShadowSection.tsx +84 -0
- package/src/components/inspectors/SplineSection.tsx +191 -0
- package/src/components/inspectors/TransformSection.tsx +191 -0
- package/src/components/inspectors/json-schema-fields.ts +281 -0
- package/src/components/inspectors/multi-edit-utils.ts +49 -0
- package/src/components/inspectors/zod-fields.ts +184 -0
- package/src/components/primitives/ColorInput.tsx +109 -0
- package/src/components/primitives/JsonInput.tsx +98 -0
- package/src/components/primitives/NumberInput.tsx +164 -0
- package/src/components/primitives/Panel.tsx +68 -0
- package/src/components/primitives/SectionHeader.tsx +45 -0
- package/src/components/primitives/Tooltip.tsx +82 -0
- package/src/components/primitives/Vec3Input.tsx +62 -0
- package/src/components/world-overlay-gestures.ts +1438 -0
- package/src/doctor/data-checks.ts +285 -0
- package/src/doctor/report.ts +386 -0
- package/src/editor-api.ts +700 -0
- package/src/editor-console.ts +194 -0
- package/src/editor-hotkeys.ts +441 -0
- package/src/editor-layers.ts +2 -0
- package/src/editor-mode.ts +28 -0
- package/src/editor-store.ts +2225 -0
- package/src/editor-viewport.ts +1924 -0
- package/src/entity-factory.ts +221 -0
- package/src/game-globals-prelude.ts +27 -0
- package/src/gated-globals.ts +131 -0
- package/src/generated/setup-by-example.generated.ts +53 -0
- package/src/gizmo-registry.ts +608 -0
- package/src/handle-system.ts +221 -0
- package/src/hierarchy-menu-registry.ts +70 -0
- package/src/hierarchy-rows.ts +73 -0
- package/src/hosted-example.ts +160 -0
- package/src/hotkeys.ts +142 -0
- package/src/ingest/discovery-react.ts +132 -0
- package/src/ingest/discovery.ts +205 -0
- package/src/ingest/discovery2d.ts +204 -0
- package/src/ingest/esm-entry-resolution.ts +102 -0
- package/src/ingest/games/bloom-composer/game.js +99 -0
- package/src/ingest/games/bloom-composer/vgai.game.json +21 -0
- package/src/ingest/games/embed-bundle/embed.html +10 -0
- package/src/ingest/games/embed-bundle/vgai.game.json +21 -0
- package/src/ingest/games/games-fps/assets/collision-world.glb +0 -0
- package/src/ingest/games/games-fps/game.js +479 -0
- package/src/ingest/games/games-fps/vgai.game.json +24 -0
- package/src/ingest/games/iframe-reachable/game-source.js +58 -0
- package/src/ingest/games/iframe-reachable/vgai.game.json +21 -0
- package/src/ingest/games/interactive-cubes/game.js +139 -0
- package/src/ingest/games/interactive-cubes/vgai.game.json +21 -0
- package/src/ingest/games/mismatched-three/fake-three.js +38 -0
- package/src/ingest/games/mismatched-three/game.js +10 -0
- package/src/ingest/games/mismatched-three/vgai.game.json +22 -0
- package/src/ingest/games/own-three-bundled/game.js +43 -0
- package/src/ingest/games/own-three-bundled/vgai.game.json +21 -0
- package/src/ingest/games/pointerlock-maze/game.js +288 -0
- package/src/ingest/games/pointerlock-maze/vgai.game.json +22 -0
- package/src/ingest/games-2d/_fixtures/first-party-scene.scn2d.json +124 -0
- package/src/ingest/games-2d/bubbo-bubbo/vgai.game.json +25 -0
- package/src/ingest/games-2d/bunnymark/game.ts +88 -0
- package/src/ingest/games-2d/bunnymark/vgai.game.json +21 -0
- package/src/ingest/games-2d/flappy/game.ts +148 -0
- package/src/ingest/games-2d/flappy/vgai.game.json +21 -0
- package/src/ingest/games-2d/flappy-pixi/vgai.game.json +25 -0
- package/src/ingest/games-2d/puzzling-potions/vgai.game.json +25 -0
- package/src/ingest/games-2d/tilemap-camera/game.ts +115 -0
- package/src/ingest/games-2d/tilemap-camera/vgai.game.json +21 -0
- package/src/ingest/games-react/react-rpg/entry.tsx +185 -0
- package/src/ingest/games-react/react-rpg/shims.d.ts +26 -0
- package/src/ingest/games-react/react-rpg/src/ui/game-state.tsx +129 -0
- package/src/ingest/games-react/react-rpg/vgai.game.json +21 -0
- package/src/ingest/host-namespace-registry.ts +196 -0
- package/src/ingest/types.ts +127 -0
- package/src/ingest-hierarchy-rows.ts +207 -0
- package/src/ingest-mode.ts +2092 -0
- package/src/ingest-siblings.ts +466 -0
- package/src/initial-scene.ts +194 -0
- package/src/inspector-section-registry.ts +117 -0
- package/src/interactive-edit-scope.ts +34 -0
- package/src/main.tsx +23 -0
- package/src/manifest-project.ts +43 -0
- package/src/model-thumbnail.ts +156 -0
- package/src/module-mode.ts +513 -0
- package/src/navmesh-handler.ts +94 -0
- package/src/packaged-runtime.ts +49 -0
- package/src/play-mode.ts +1148 -0
- package/src/playground/main.ts +206 -0
- package/src/project-manager.ts +173 -0
- package/src/scene-diff.ts +112 -0
- package/src/scene-serializer.ts +144 -0
- package/src/scene-sync.ts +587 -0
- package/src/scene-ui-mode/active-scene-ui.ts +58 -0
- package/src/scene-ui-mode/scene-ui-palette-panel.tsx +152 -0
- package/src/scene-ui-mode/scene-ui-timeline-tab.tsx +29 -0
- package/src/scene-ui-mode/ui-prefab-dialog-host.tsx +74 -0
- package/src/scene-ui-mode/ui-prefab-hierarchy-menu.ts +49 -0
- package/src/scene-ui-mode/ui-prefab-io.ts +76 -0
- package/src/script-loader.ts +143 -0
- package/src/storage/fsa-storage.ts +55 -0
- package/src/storage/handle-storage.ts +150 -0
- package/src/storage/handle-store.ts +116 -0
- package/src/storage/http-storage.ts +137 -0
- package/src/storage/index.ts +147 -0
- package/src/storage/mem-storage.ts +135 -0
- package/src/storage/opfs-storage.ts +25 -0
- package/src/storage/paths.ts +26 -0
- package/src/storage/seed.ts +106 -0
- package/src/storage/types.ts +110 -0
- package/src/tool-loader.ts +291 -0
- package/src/ui-editor/inspector.tsx +563 -0
- package/src/ui-editor/node-ops.ts +266 -0
- package/src/ui-editor/overlay.tsx +380 -0
- package/src/ui-editor/palette.tsx +114 -0
- package/src/ui-editor/react-store.ts +78 -0
- package/src/ui-editor/timeline-panel.tsx +443 -0
- package/src/ui-editor/ui-edit-store.ts +552 -0
- package/src/ui-editor/ui-prefab.ts +95 -0
- package/src/ui-source/inspect.ts +544 -0
- package/src/ui-source/oid-transform.ts +133 -0
- package/src/ui-source/source-write-backend.ts +203 -0
- package/src/ui-source/tailwind-map.ts +174 -0
- package/src/ui-source/writer.ts +1175 -0
- package/src/widgets/index.ts +73 -0
- package/template/.claude/skills/editor/SKILL.md +252 -0
- package/template/CLAUDE.md +512 -0
- package/template/LICENSE +30 -0
- package/template/emit-schemas.ts +42 -0
- package/template/index.html +17 -0
- package/template/package.json +53 -0
- package/template/project.json +9 -0
- package/template/public/audio/ambient-arena.wav +0 -0
- package/template/public/audio/ambient-music.wav +0 -0
- package/template/public/audio/ambient-nature.wav +0 -0
- package/template/public/audio/ambient-wind.wav +0 -0
- package/template/public/inputmaps/default.inputmap.json +41 -0
- package/template/public/jsm/libs/draco/gltf/draco_decoder.js +33 -0
- package/template/public/jsm/libs/draco/gltf/draco_decoder.wasm +0 -0
- package/template/public/jsm/libs/draco/gltf/draco_encoder.js +33 -0
- package/template/public/jsm/libs/draco/gltf/draco_wasm_wrapper.js +116 -0
- package/template/public/scenes/default.vscn.json +43 -0
- package/template/public/scenes/untitled.vscn.json +29 -0
- package/template/public/textures/waternormals.jpg +0 -0
- package/template/server/colyseus-setup.ts +85 -0
- package/template/server/main.ts +34 -0
- package/template/server/rooms/arena-room.ts +420 -0
- package/template/server/rooms/game-room.ts +162 -0
- package/template/server/rooms.ts +15 -0
- package/template/server/tsconfig.json +19 -0
- package/template/src/data/README.md +40 -0
- package/template/src/data/assets.ts +31 -0
- package/template/src/data/tuning.data.json +5 -0
- package/template/src/data/tuning.schema.json +21 -0
- package/template/src/data/tuning.schema.ts +34 -0
- package/template/src/data/tuning.ts +24 -0
- package/template/src/main.ts +157 -0
- package/template/src/scripts/components/data-spinner.ts +45 -0
- package/template/src/scripts/components/scene-camera.ts +40 -0
- package/template/src/scripts/main.ts +94 -0
- package/template/src/scripts/registry.ts +46 -0
- package/template/src/tools/README.md +14 -0
- package/template/src/tools/example.tool.tsx +91 -0
- package/template/src/tools/spin.tool.tsx +101 -0
- package/template/src/ui/Button.tsx +27 -0
- package/template/src/ui/DefaultHud.tsx +40 -0
- package/template/src/ui/KeyHint.tsx +25 -0
- package/template/src/ui/Panel.tsx +52 -0
- package/template/src/ui/RebindPanel.tsx +129 -0
- package/template/src/ui/Slider.tsx +28 -0
- package/template/src/ui/StatusDot.tsx +22 -0
- package/template/src/ui/Toggle.tsx +19 -0
- package/template/src/ui/game-state.tsx +37 -0
- package/template/src/ui/index.ts +7 -0
- package/template/src/ui/mount-default-hud.tsx +19 -0
- package/template/src/ui/mount-ui.ts +15 -0
- package/template/tsconfig.json +36 -0
- package/template/validate-asset-content.ts +78 -0
- package/template/validate-manifest.ts +55 -0
- package/template/validate-scenes.ts +211 -0
- package/template/vgai.game.json +21 -0
- package/template/vite.config.ts +128 -0
- package/vite-plugin-example-assets.ts +60 -0
- package/vite-plugin-game-globals.ts +46 -0
- package/vite-plugin-game-static.ts +71 -0
- package/vite-plugin-project-game-static.ts +84 -0
- package/vite-plugin-react-game-static.ts +98 -0
- package/vite-plugin-react-world-provider.ts +81 -0
- package/vite-plugin-react-world-runtime.ts +76 -0
- package/vite-plugin-ui-oid.ts +561 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// C3 (phase-b/c3-r3f-packaged) — the adaptive `optimizeDeps.entries` source
|
|
2
|
+
// for `packaged.ts`'s project-rooted Vite instance. See that file's header
|
|
3
|
+
// doc comment, "optimizeDeps" section, for the full crawl-vs-crash story;
|
|
4
|
+
// this module only computes WHICH files to scope the crawl to.
|
|
5
|
+
|
|
6
|
+
import { existsSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { loadGameManifestFile } from '../../engine/src/manifest/load-file';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* SERVER-ONLY packages that must never be crawled INTO during the client
|
|
12
|
+
* dep-prebundle — passed to `optimizeDeps.exclude` in `packaged.ts` (C3
|
|
13
|
+
* hardening follow-on). Belt to the entries-scoping suspenders.
|
|
14
|
+
*
|
|
15
|
+
* ## The pathological case this closes
|
|
16
|
+
*
|
|
17
|
+
* The entries-scoping fix (`computeProjectOptimizeDepsEntries` below) makes
|
|
18
|
+
* the crawl start ONLY from a project's declared world entries — which
|
|
19
|
+
* NORMALLY never reach `server/` code, so the colyseus server stack
|
|
20
|
+
* (`colyseus` -> `@colyseus/core` -> `@pm2/io`) is never crawled and the
|
|
21
|
+
* esbuild-scanner crash that `noDiscovery: true` used to guard against never
|
|
22
|
+
* fires. But if a project VIOLATES the repo's D2 rule (CLAUDE.md
|
|
23
|
+
* "Networking": client code calls Colyseus via the CLIENT SDK
|
|
24
|
+
* `@colyseus/sdk`, dynamic-imported, and NEVER value-imports the `server/`
|
|
25
|
+
* room classes) by having a WORLD ENTRY statically `import` from `server/`,
|
|
26
|
+
* the crawl reaches `@colyseus/core`'s `Stats.mjs`, whose `import("@pm2/io")`
|
|
27
|
+
* has no resolvable browser entry, and esbuild's prebundle step THROWS from
|
|
28
|
+
* inside the async optimizer — which, unhandled, HARD-KILLS the whole
|
|
29
|
+
* packaged node process (the only authoring surface). That is a strictly
|
|
30
|
+
* harsher degrade than the old `noDiscovery: true`, which at least still
|
|
31
|
+
* booted an editor.
|
|
32
|
+
*
|
|
33
|
+
* `exclude`-ing these three names keeps esbuild from crawling into them
|
|
34
|
+
* during prebundle: even a world entry that (wrongly) reaches them no longer
|
|
35
|
+
* crashes the scanner — the offending world degrades to a benign browser-
|
|
36
|
+
* side runtime import failure for that ONE world, and the editor process
|
|
37
|
+
* survives and serves everything else. Verified live (a real, checkout-
|
|
38
|
+
* absent packaged boot against a project whose world entry value-imports
|
|
39
|
+
* `server/rooms`): without this list the server process dies at boot; with
|
|
40
|
+
* it, the server boots and serves.
|
|
41
|
+
*
|
|
42
|
+
* CRITICAL: this list must NOT contain `@colyseus/sdk` — that is the
|
|
43
|
+
* legitimate CLIENT SDK a real multiplayer project's own game code imports
|
|
44
|
+
* (dynamic-imported per D2; e.g. `examples/third-person-arena/src/
|
|
45
|
+
* network.ts`). It is a SEPARATE package from `@colyseus/core` — its own
|
|
46
|
+
* published `build/index.mjs` is a self-contained client bundle — and MUST
|
|
47
|
+
* still prebundle so those projects work. Excluding only the server-side
|
|
48
|
+
* names leaves the client SDK's prebundle path untouched (proven: a world
|
|
49
|
+
* entry importing `@colyseus/sdk` still prebundles it cleanly with this
|
|
50
|
+
* exclude list active).
|
|
51
|
+
*/
|
|
52
|
+
export const SERVER_ONLY_PREBUNDLE_EXCLUDE: readonly string[] = [
|
|
53
|
+
'colyseus',
|
|
54
|
+
'@colyseus/core',
|
|
55
|
+
'@pm2/io',
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The project-relative CLIENT entry files `optimizeDeps.entries` should scope
|
|
60
|
+
* Vite's dependency-DISCOVERY crawl to, for the project rooted at
|
|
61
|
+
* `projectRoot` — every `worlds[].entry` its OWN `vgai.game.json` declares
|
|
62
|
+
* (default-adapter, code-authored worlds; scene-only worlds have no `entry`
|
|
63
|
+
* and contribute nothing here — their gameplay code is registered
|
|
64
|
+
* elsewhere, reached transitively from whichever world DOES declare an
|
|
65
|
+
* `entry`, e.g. the template's single world), deduped, and filtered to paths
|
|
66
|
+
* that exist on disk.
|
|
67
|
+
*
|
|
68
|
+
* ## Why THESE files, not `index.html` or the project's own bootstrap `main.ts`
|
|
69
|
+
*
|
|
70
|
+
* A project's `index.html` -> `src/main.ts` bootstrap ties every declared
|
|
71
|
+
* world together for the STANDALONE (`npm run game`) build, and — critically
|
|
72
|
+
* — the scaffolded template's own `main.ts` also carries the P2P-Colyseus
|
|
73
|
+
* URL-param branch that dynamically imports the project's OWN `server/rooms`
|
|
74
|
+
* (`await import('../server/rooms')`, gated behind `?net=p2p-host`, but
|
|
75
|
+
* esbuild's dependency scanner follows a string-literal dynamic `import()`
|
|
76
|
+
* as an unconditional graph edge regardless of the runtime guard around it).
|
|
77
|
+
* `server/rooms.ts` re-exports real Colyseus `Room` classes, whose `colyseus`
|
|
78
|
+
* (SERVER package) import chain reaches `@colyseus/core` -> `@pm2/io`, a
|
|
79
|
+
* package esbuild's prebundle step cannot resolve an entry for (confirmed
|
|
80
|
+
* empirically, not theoretically: a direct `vite.optimizeDeps` run scoped to
|
|
81
|
+
* `index.html`-style discovery against `packages/editor/template` throws
|
|
82
|
+
* `Failed to resolve entry for package "@pm2/io"` from inside
|
|
83
|
+
* `@colyseus/core/build/Stats.mjs`'s own `import("@pm2/io")` — this is
|
|
84
|
+
* `packaged.ts`'s ORIGINAL reason for `noDiscovery: true`, verified still
|
|
85
|
+
* true on this Vite version at the time of the C3 fix).
|
|
86
|
+
*
|
|
87
|
+
* A manifest's `worlds[].entry` files never reach that chain: they are the
|
|
88
|
+
* PER-WORLD mount code the editor's own adapter-resolver dynamically imports
|
|
89
|
+
* directly for Play mode / world mounting (never `src/main.ts` — that
|
|
90
|
+
* bootstrap is the STANDALONE build's concern, unrelated to how the editor
|
|
91
|
+
* itself mounts a world; see `packaged.ts`'s header doc comment). Scoping the
|
|
92
|
+
* crawl to exactly these files means: (a) it never touches `server/`, so the
|
|
93
|
+
* `@pm2/io` class of crash cannot occur no matter what a project's server
|
|
94
|
+
* room code imports, and (b) any CJS-needing-prebundle client dependency a
|
|
95
|
+
* world's own code reaches — `react`, `@react-three/fiber`, `@react-three/
|
|
96
|
+
* drei`, or some future ecosystem this repo has never heard of — gets
|
|
97
|
+
* auto-discovered and prebundled for free, with no per-package allowlist to
|
|
98
|
+
* maintain (this is what actually fixes the R3F class of bug this file's own
|
|
99
|
+
* feature exists for: `@react-three/fiber`'s CJS transitive deps like
|
|
100
|
+
* `use-sync-external-store` are reached from `examples/r3f-probe`'s
|
|
101
|
+
* `src/net-entry.tsx`, never from anything server-shaped).
|
|
102
|
+
*
|
|
103
|
+
* The client SDK case (`@colyseus/sdk`, the package a world's own game code
|
|
104
|
+
* legitimately imports for real multiplayer — e.g.
|
|
105
|
+
* `examples/third-person-arena/src/network.ts`) is DIFFERENT from the
|
|
106
|
+
* server-package case above and does NOT crash: `@colyseus/sdk`'s own
|
|
107
|
+
* published build (`build/index.mjs`) is a self-contained client bundle that
|
|
108
|
+
* does not pull in `@colyseus/core`'s server-only `Stats.mjs` (confirmed
|
|
109
|
+
* empirically the same way: a full prebundle scoped to
|
|
110
|
+
* `examples/third-person-arena`'s own `src/index.tsx` world entry succeeds
|
|
111
|
+
* and prebundles `@colyseus/sdk` cleanly).
|
|
112
|
+
*
|
|
113
|
+
* ## Known scope trim
|
|
114
|
+
*
|
|
115
|
+
* Computed ONCE, from the boot-time `VGAI_PROJECT`, exactly like this file's
|
|
116
|
+
* sibling `engineSrc`/`resolve.alias` resolution in `packaged.ts` — NOT
|
|
117
|
+
* re-computed when `onProjectOpened` switches to a different project mid-
|
|
118
|
+
* session (a pre-existing limitation of that callback: it only extends
|
|
119
|
+
* `fs.allow`/the watcher/the game-globals shadow, never anything that was
|
|
120
|
+
* baked into the Vite instance's `createServer` call, like `resolve.alias`
|
|
121
|
+
* already wasn't either). A switched-to project keeps whatever entries the
|
|
122
|
+
* ORIGINALLY opened project computed.
|
|
123
|
+
*
|
|
124
|
+
* Never throws: an absent/unparseable manifest (no project open yet, a
|
|
125
|
+
* pre-Phase-B project, a broken `vgai.game.json`) degrades to `[]` — the
|
|
126
|
+
* SAFEST fallback, not a guess. Per Vite's own `optimizeDeps.entries`
|
|
127
|
+
* resolution (`computeEntries` in vite's dep-scanner), a truthy-but-empty
|
|
128
|
+
* array short-circuits the `**\/*.html` crawl fallback WITHOUT crawling
|
|
129
|
+
* anything itself, so an unreadable manifest degrades to "no auto-discovery
|
|
130
|
+
* beyond the static `include` list" — the same conservative posture
|
|
131
|
+
* `noDiscovery: true` had, rather than falling back to a full crawl that
|
|
132
|
+
* could reach server code we know nothing about.
|
|
133
|
+
*/
|
|
134
|
+
export function computeProjectOptimizeDepsEntries(projectRoot: string): string[] {
|
|
135
|
+
let manifest: ReturnType<typeof loadGameManifestFile>;
|
|
136
|
+
try {
|
|
137
|
+
manifest = loadGameManifestFile(join(projectRoot, 'vgai.game.json'));
|
|
138
|
+
} catch {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const entries = new Set<string>();
|
|
143
|
+
for (const world of manifest.worlds) {
|
|
144
|
+
if (!world.entry) continue;
|
|
145
|
+
if (!existsSync(join(projectRoot, world.entry))) continue;
|
|
146
|
+
entries.add(world.entry);
|
|
147
|
+
}
|
|
148
|
+
return [...entries];
|
|
149
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// T3.3 slice 1 — server-side compat-view synthesis for `project.json` reads
|
|
2
|
+
// (docs/CLI-ON-FOLDER-DESIGN.md §1C).
|
|
3
|
+
//
|
|
4
|
+
// A project folder's config view can come from two places: `project.json`
|
|
5
|
+
// itself (legacy, still authoritative when present — including fields like
|
|
6
|
+
// `appId` that have no manifest home) or, when that file is ABSENT, a
|
|
7
|
+
// synthesized `{ name, defaultScene }` view derived from `vgai.game.json`
|
|
8
|
+
// (`name` from the manifest; `defaultScene` from the first world's `scene`,
|
|
9
|
+
// explicitly `null` if that world has none — e.g. an `entry`-based or ingest
|
|
10
|
+
// world — NEVER omitted: a consumer that saw the key simply missing collapsed
|
|
11
|
+
// "not declared" into "guess a path" via `?? 'scenes/default.vscn.json'`,
|
|
12
|
+
// which is what caused `initial-scene.ts` to silently WRITE a placeholder
|
|
13
|
+
// scene for a legal entry-only first world — §7.1-12 / R2b fix 1. `null` is
|
|
14
|
+
// the explicit, typed "no scene declared" the consumer is forced to branch
|
|
15
|
+
// on instead of defaulting through.
|
|
16
|
+
//
|
|
17
|
+
// This is the ONE place the project.json/manifest fallback rule lives —
|
|
18
|
+
// every server route that reads project config goes through `readProjectView`
|
|
19
|
+
// rather than re-implementing the fallback (editor-server.ts's static
|
|
20
|
+
// `/project.json` serve, `GET /__editor/project`, `POST /__editor/open-project`'s
|
|
21
|
+
// existence check, and the recent-projects existence check all call this).
|
|
22
|
+
|
|
23
|
+
import { readFile } from 'node:fs/promises';
|
|
24
|
+
import { join } from 'node:path';
|
|
25
|
+
import { loadGameManifestFile } from '../../engine/src/manifest/load-file';
|
|
26
|
+
|
|
27
|
+
export type ProjectView = Record<string, unknown>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Read the compat project view for `root`.
|
|
31
|
+
*
|
|
32
|
+
* - `project.json` present -> its parsed contents verbatim (whatever fields
|
|
33
|
+
* it has, including `appId`).
|
|
34
|
+
* - `project.json` absent, `vgai.game.json` present -> synthesized
|
|
35
|
+
* `{ name, defaultScene }`, where `defaultScene` is the first world's
|
|
36
|
+
* `scene` if declared, else explicit `null` (never omitted — appId is
|
|
37
|
+
* omitted, though: it has no manifest home and nothing reads it per
|
|
38
|
+
* docs/CLI-ON-FOLDER-DESIGN.md §1C).
|
|
39
|
+
* - Neither present, or the manifest exists but fails to parse -> `null`.
|
|
40
|
+
* (The hard fail-loud-on-a-broken-manifest behavior lives at the CLI
|
|
41
|
+
* discovery gate, `packages/vgai-cli/src/discovery.ts` — a server route
|
|
42
|
+
* reading a broken manifest degrades to "no project" rather than crashing
|
|
43
|
+
* the request.)
|
|
44
|
+
*/
|
|
45
|
+
export async function readProjectView(root: string): Promise<ProjectView | null> {
|
|
46
|
+
try {
|
|
47
|
+
const raw = await readFile(join(root, 'project.json'), 'utf-8');
|
|
48
|
+
return JSON.parse(raw) as ProjectView;
|
|
49
|
+
} catch {
|
|
50
|
+
// project.json absent/unreadable — fall through to the manifest.
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const manifest = loadGameManifestFile(join(root, 'vgai.game.json'));
|
|
55
|
+
const firstWorld = manifest.worlds[0];
|
|
56
|
+
// Always set the key — explicit `null`, not an omitted property, when the
|
|
57
|
+
// first world declares no scene (e.g. `entry`-only). See the module doc
|
|
58
|
+
// comment above: an omitted key is what let a naive `?? 'scenes/
|
|
59
|
+
// default.vscn.json'` consumer silently guess a path.
|
|
60
|
+
const view: ProjectView = { name: manifest.name, defaultScene: firstWorld?.scene ?? null };
|
|
61
|
+
return view;
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, dependency-light helpers for the editor server.
|
|
3
|
+
*
|
|
4
|
+
* Everything here is side-effect free and unit-testable (see
|
|
5
|
+
* packages/editor/test/server-security.test.ts). The route handlers in
|
|
6
|
+
* editor-server.ts / asset-library-routes.ts delegate their security and
|
|
7
|
+
* correctness checks to these functions so the logic can be verified in
|
|
8
|
+
* isolation.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync } from 'node:fs';
|
|
12
|
+
import { createRequire } from 'node:module';
|
|
13
|
+
import { dirname, join, resolve, sep } from 'node:path';
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Path containment (S1 / S5 / S6)
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* True if `child` resolves to the same path as `parent`, or to a path strictly
|
|
21
|
+
* inside it. Defends against `..` traversal and sibling-prefix attacks where a
|
|
22
|
+
* naive `startsWith` would accept `/a/public-secrets` as inside `/a/public`.
|
|
23
|
+
*/
|
|
24
|
+
export function isPathInside(parent: string, child: string): boolean {
|
|
25
|
+
const resolvedParent = resolve(parent);
|
|
26
|
+
const resolvedChild = resolve(child);
|
|
27
|
+
if (resolvedChild === resolvedParent) return true;
|
|
28
|
+
const parentWithSep = resolvedParent.endsWith(sep) ? resolvedParent : resolvedParent + sep;
|
|
29
|
+
return resolvedChild.startsWith(parentWithSep);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Asset source + host allowlists (S1 / S2)
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
export const ALLOWED_ASSET_SOURCES = ['polyhaven', 'ambientcg', 'local'] as const;
|
|
37
|
+
export type AllowedAssetSource = (typeof ALLOWED_ASSET_SOURCES)[number];
|
|
38
|
+
|
|
39
|
+
/** Allowlist the `source` segment so it can never escape the library directory. */
|
|
40
|
+
export function isAllowedAssetSource(source: unknown): source is AllowedAssetSource {
|
|
41
|
+
return (
|
|
42
|
+
typeof source === 'string' && (ALLOWED_ASSET_SOURCES as readonly string[]).includes(source)
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Known CDN / API domains the server is permitted to fetch from. */
|
|
47
|
+
export const ASSET_HOST_ALLOWLIST = [
|
|
48
|
+
'polyhaven.com',
|
|
49
|
+
'polyhaven.org',
|
|
50
|
+
'ambientcg.com',
|
|
51
|
+
'struffelproduction.com', // ambientCG's download CDN
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* True if `host` is an IPv4/IPv6 literal inside a private, loopback,
|
|
56
|
+
* link-local, or otherwise non-routable range. Used to block SSRF to metadata
|
|
57
|
+
* endpoints (e.g. 169.254.169.254) and internal services.
|
|
58
|
+
*/
|
|
59
|
+
export function isPrivateOrLinkLocalHost(host: string): boolean {
|
|
60
|
+
const h = host.replace(/^\[|\]$/g, '').toLowerCase();
|
|
61
|
+
|
|
62
|
+
if (h === 'localhost' || h.endsWith('.localhost')) return true;
|
|
63
|
+
|
|
64
|
+
// IPv6 loopback / link-local / unique-local.
|
|
65
|
+
if (h === '::1' || h === '::') return true;
|
|
66
|
+
if (h.startsWith('fe80:') || h.startsWith('fc') || h.startsWith('fd')) return true;
|
|
67
|
+
// IPv4-mapped IPv6, e.g. ::ffff:169.254.169.254
|
|
68
|
+
const mapped = h.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
|
|
69
|
+
if (mapped) return isPrivateOrLinkLocalHost(mapped[1]!);
|
|
70
|
+
|
|
71
|
+
const m = h.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
72
|
+
if (!m) return false;
|
|
73
|
+
const a = Number(m[1]);
|
|
74
|
+
const b = Number(m[2]);
|
|
75
|
+
if (a === 0 || a === 10 || a === 127) return true; // 0.x, private, loopback
|
|
76
|
+
if (a === 169 && b === 254) return true; // link-local (cloud metadata)
|
|
77
|
+
if (a === 172 && b >= 16 && b <= 31) return true; // private
|
|
78
|
+
if (a === 192 && b === 168) return true; // private
|
|
79
|
+
if (a === 100 && b >= 64 && b <= 127) return true; // CGNAT
|
|
80
|
+
if (a >= 224) return true; // multicast / reserved
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Validate a body-supplied download URL: must be HTTPS, point at an allowlisted
|
|
86
|
+
* CDN host, and not resolve to a private / link-local literal IP. (S2)
|
|
87
|
+
*/
|
|
88
|
+
export function isAllowedAssetHost(urlString: string): boolean {
|
|
89
|
+
let u: URL;
|
|
90
|
+
try {
|
|
91
|
+
u = new URL(urlString);
|
|
92
|
+
} catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
if (u.protocol !== 'https:') return false;
|
|
96
|
+
const host = u.hostname.toLowerCase();
|
|
97
|
+
if (isPrivateOrLinkLocalHost(host)) return false;
|
|
98
|
+
return ASSET_HOST_ALLOWLIST.some((d) => host === d || host.endsWith('.' + d));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// AppleScript escaping (S4)
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
// Matches ASCII control characters (U+0000–U+001F).
|
|
106
|
+
const CONTROL_CHARS = /[\u0000-\u001f]/g;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Escape a string for safe inclusion inside a double-quoted AppleScript literal.
|
|
110
|
+
* Backslashes must be doubled BEFORE quotes are escaped, otherwise an injected
|
|
111
|
+
* trailing backslash neutralises the closing quote. Control characters are
|
|
112
|
+
* stripped so they can't break out of the `-e` argument.
|
|
113
|
+
*/
|
|
114
|
+
export function escapeAppleScriptString(s: string): string {
|
|
115
|
+
return s.replace(CONTROL_CHARS, ' ').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// Content-Disposition filename sanitization (S7)
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
// Control chars, double-quote, and backslash are unsafe in a quoted filename.
|
|
123
|
+
const UNSAFE_FILENAME_CHARS = /[\u0000-\u001f"\\]/g;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Reduce an arbitrary name to a safe `filename="..."` value: basename only, no
|
|
127
|
+
* path separators, quotes, backslashes, or control characters.
|
|
128
|
+
*/
|
|
129
|
+
export function sanitizeContentDispositionFilename(name: string): string {
|
|
130
|
+
const base = name.split(/[\\/]/).pop() ?? '';
|
|
131
|
+
const cleaned = base.replace(UNSAFE_FILENAME_CHARS, '').trim();
|
|
132
|
+
return cleaned || 'download';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// /@fs served-extension allowlist (S5)
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
const SERVABLE_FS_EXTENSIONS = new Set([
|
|
140
|
+
'ts',
|
|
141
|
+
'tsx',
|
|
142
|
+
'js',
|
|
143
|
+
'jsx',
|
|
144
|
+
'mjs',
|
|
145
|
+
'cjs',
|
|
146
|
+
'json',
|
|
147
|
+
'wasm',
|
|
148
|
+
'css',
|
|
149
|
+
'glsl',
|
|
150
|
+
'vert',
|
|
151
|
+
'frag',
|
|
152
|
+
]);
|
|
153
|
+
|
|
154
|
+
/** Restrict /@fs serving to source/asset file types (never .env, keys, etc.). */
|
|
155
|
+
export function isServableFsExtension(ext: string): boolean {
|
|
156
|
+
return SERVABLE_FS_EXTENSIONS.has(ext.toLowerCase());
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// Origin allowlist for mutating editor routes (S3)
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
/** True for loopback hostnames (localhost / 127.0.0.0/8 / ::1). */
|
|
164
|
+
export function isLoopbackHostname(host: string): boolean {
|
|
165
|
+
const h = host.replace(/^\[|\]$/g, '').toLowerCase();
|
|
166
|
+
if (h === 'localhost' || h === '::1') return true;
|
|
167
|
+
return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* CSRF / drive-by defense for mutating `/__editor/*` routes. A browser always
|
|
172
|
+
* sends an `Origin` on cross-site POSTs; if present it must be a loopback origin
|
|
173
|
+
* (or an explicitly-allowed host). Non-browser clients (the CLI/SDK using Node
|
|
174
|
+
* `fetch`) send no `Origin` and are allowed.
|
|
175
|
+
*/
|
|
176
|
+
export function isAllowedEditorOrigin(
|
|
177
|
+
origin: string | undefined,
|
|
178
|
+
extraAllowedHosts: string[] = [],
|
|
179
|
+
): boolean {
|
|
180
|
+
if (!origin) return true;
|
|
181
|
+
let u: URL;
|
|
182
|
+
try {
|
|
183
|
+
u = new URL(origin);
|
|
184
|
+
} catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
const host = u.hostname.toLowerCase();
|
|
188
|
+
if (isLoopbackHostname(host)) return true;
|
|
189
|
+
return extraAllowedHosts.some((h) => h.toLowerCase() === host);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
// Installed-package source resolution (B1b, packaging plan §3(b)1)
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Resolve `packageName`'s own `src/` directory as INSTALLED in `fromDir`'s
|
|
198
|
+
* `node_modules` — Node's own resolution algorithm, rooted at `fromDir` (a
|
|
199
|
+
* project directory), not at wherever this server process's own code lives.
|
|
200
|
+
*
|
|
201
|
+
* Why: a project that pins `@vgai/engine@0.3.0` must be served ITS 0.3.0
|
|
202
|
+
* source, never a copy baked into whatever package happens to be running the
|
|
203
|
+
* editor server — otherwise the exact version-skew class Phase B exists to
|
|
204
|
+
* kill (an editor silently serving a different engine version than the one
|
|
205
|
+
* the project declares) reappears one layer down, inside script-serving.
|
|
206
|
+
* `packageName`'s `exports` map must publish a `./package.json` entry (both
|
|
207
|
+
* `@vgai/engine` and `@vgai/scene-ui` do, as of B1a) for `require.resolve` to
|
|
208
|
+
* find it.
|
|
209
|
+
*
|
|
210
|
+
* Returns `null` (never throws) when `packageName` isn't resolvable from
|
|
211
|
+
* `fromDir` — no project open, a pre-Phase-B project with no such
|
|
212
|
+
* dependency, or a project that hasn't `npm install`ed yet. Callers fall back
|
|
213
|
+
* to a checkout-relative default in that case.
|
|
214
|
+
*/
|
|
215
|
+
export function resolveInstalledPackageSrcDir(fromDir: string, packageName: string): string | null {
|
|
216
|
+
try {
|
|
217
|
+
const req = createRequire(join(fromDir, 'package.json'));
|
|
218
|
+
const pkgJsonPath = req.resolve(`${packageName}/package.json`);
|
|
219
|
+
return join(dirname(pkgJsonPath), 'src');
|
|
220
|
+
} catch {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
// Bind host + listen-error helpers (S3 / SC5)
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
|
|
229
|
+
/** Resolve the interface to bind to. Defaults to loopback; opt-in via env. */
|
|
230
|
+
export function resolveBindHost(env: NodeJS.ProcessEnv = process.env): string {
|
|
231
|
+
return env['VGAI_EDITOR_HOST'] || env['EDITOR_HOST'] || '127.0.0.1';
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Format a friendly message for a server `listen` error (notably EADDRINUSE). */
|
|
235
|
+
export function friendlyListenError(
|
|
236
|
+
err: NodeJS.ErrnoException,
|
|
237
|
+
port: number,
|
|
238
|
+
host: string,
|
|
239
|
+
): string {
|
|
240
|
+
if (err.code === 'EADDRINUSE') {
|
|
241
|
+
return (
|
|
242
|
+
`Port ${port} is already in use on ${host}. ` +
|
|
243
|
+
`Another editor instance may already be running — stop it, or set PORT / VGAI_EDITOR_HOST ` +
|
|
244
|
+
`to a free address and try again.`
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
if (err.code === 'EACCES') {
|
|
248
|
+
return `Permission denied binding to ${host}:${port}. Try a port above 1024.`;
|
|
249
|
+
}
|
|
250
|
+
return `Failed to start server on ${host}:${port}: ${err.message}`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
// File-move disambiguation (SC2)
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
|
|
257
|
+
export interface MoveCandidate {
|
|
258
|
+
hash: string;
|
|
259
|
+
size: number;
|
|
260
|
+
timestamp: number;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* A delete+create pair is treated as a move (and triggers scene-ref rewriting)
|
|
265
|
+
* only when it is unambiguous: the added file must match a pending unlink by
|
|
266
|
+
* BOTH content hash AND byte size, that match must be unique, and it must fall
|
|
267
|
+
* within the move window. Hash-only matching risks rewriting scene files on an
|
|
268
|
+
* unrelated delete+create that happens to collide. (SC2)
|
|
269
|
+
*/
|
|
270
|
+
export function findUniqueMoveMatch<T extends MoveCandidate>(
|
|
271
|
+
candidates: readonly T[],
|
|
272
|
+
addedHash: string,
|
|
273
|
+
addedSize: number,
|
|
274
|
+
now: number,
|
|
275
|
+
windowMs: number,
|
|
276
|
+
): T | null {
|
|
277
|
+
const matches = candidates.filter(
|
|
278
|
+
(c) => c.hash === addedHash && c.size === addedSize && now - c.timestamp < windowMs,
|
|
279
|
+
);
|
|
280
|
+
return matches.length === 1 ? matches[0]! : null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
// Manifest write validation (A4, D8) — POST /__editor/manifest
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
|
|
287
|
+
export interface ManifestWriteBody {
|
|
288
|
+
path?: unknown;
|
|
289
|
+
content?: unknown;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export type ManifestWriteValidation =
|
|
293
|
+
| { ok: true; content: string }
|
|
294
|
+
| { ok: false; status: number; error: string };
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Validate a `POST /__editor/manifest` request body. The route HARD-CODES the
|
|
298
|
+
* write destination (`join(projectRoot, 'vgai.game.json')`) — this function
|
|
299
|
+
* never returns a path, only a green light + the (already-string, already-
|
|
300
|
+
* valid-JSON) content to write, so there is no way for a caller to derive the
|
|
301
|
+
* destination from user input even by accident. Rejects: no project open,
|
|
302
|
+
* any `path` other than the literal `'vgai.game.json'` (traversal, a
|
|
303
|
+
* different filename, an absolute path — all rejected identically), a
|
|
304
|
+
* non-string `content`, and content that fails to `JSON.parse`.
|
|
305
|
+
*/
|
|
306
|
+
export function validateManifestWrite(
|
|
307
|
+
projectRoot: string,
|
|
308
|
+
engineRoot: string,
|
|
309
|
+
body: ManifestWriteBody,
|
|
310
|
+
): ManifestWriteValidation {
|
|
311
|
+
if (projectRoot === engineRoot) {
|
|
312
|
+
return { ok: false, status: 400, error: 'No project open.' };
|
|
313
|
+
}
|
|
314
|
+
if (body.path !== undefined && body.path !== 'vgai.game.json') {
|
|
315
|
+
return {
|
|
316
|
+
ok: false,
|
|
317
|
+
status: 400,
|
|
318
|
+
error: "Invalid path — only 'vgai.game.json' may be written.",
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
if (typeof body.content !== 'string') {
|
|
322
|
+
return { ok: false, status: 400, error: 'Invalid content — expected a JSON string.' };
|
|
323
|
+
}
|
|
324
|
+
try {
|
|
325
|
+
JSON.parse(body.content);
|
|
326
|
+
} catch {
|
|
327
|
+
return { ok: false, status: 400, error: 'Content is not valid JSON.' };
|
|
328
|
+
}
|
|
329
|
+
return { ok: true, content: body.content };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ---------------------------------------------------------------------------
|
|
333
|
+
// Project src/ watcher classification (W6a, docs/DATA-TOOLS-DESIGN.md §7)
|
|
334
|
+
// ---------------------------------------------------------------------------
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Which editor list a `src/**` add/unlink invalidates, or `null` if it's
|
|
338
|
+
* irrelevant. Feeds the editor-server's second `src/`-scoped chokidar
|
|
339
|
+
* watcher (`startWatcher()` in editor-server.ts) so a NEW
|
|
340
|
+
* `src/tools/*.tool.tsx` or `src/data/*.data.json`/`*.schema.json` shows up
|
|
341
|
+
* in the dock/DATA tab without a full editor reload (spec §7 W4 field note
|
|
342
|
+
* c). `.schema.json` is included on the `data` side: its add/unlink flips the
|
|
343
|
+
* DATA tab between the no-emitted-schema teaching message and real widgets.
|
|
344
|
+
*
|
|
345
|
+
* `relPath` is project-root-relative with forward slashes (same convention
|
|
346
|
+
* `/__editor/data-files` and `/__editor/tool-files` already use).
|
|
347
|
+
*/
|
|
348
|
+
export function classifyProjectSrcPath(relPath: string): 'tools' | 'data' | null {
|
|
349
|
+
if (relPath.startsWith('src/tools/') && relPath.endsWith('.tool.tsx')) return 'tools';
|
|
350
|
+
if (
|
|
351
|
+
relPath.startsWith('src/data/') &&
|
|
352
|
+
(relPath.endsWith('.data.json') || relPath.endsWith('.schema.json'))
|
|
353
|
+
) {
|
|
354
|
+
return 'data';
|
|
355
|
+
}
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ---------------------------------------------------------------------------
|
|
360
|
+
// Command-relay response shaping (SC1)
|
|
361
|
+
// ---------------------------------------------------------------------------
|
|
362
|
+
|
|
363
|
+
export interface CommandResult {
|
|
364
|
+
ok?: boolean;
|
|
365
|
+
timedOut?: boolean;
|
|
366
|
+
error?: string;
|
|
367
|
+
/** Optional payload for commands that answer with data (e.g. `capture-viewport`'s base64 PNG). */
|
|
368
|
+
data?: Record<string, unknown>;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** Map a relayed-command result to an HTTP status + body. A timeout (no editor
|
|
372
|
+
* connected) is a distinct 504 error rather than a fake success. (SC1)
|
|
373
|
+
* `data`, when present on a success, is spread into the response body
|
|
374
|
+
* alongside `ok: true` so callers (`HttpEditorTransport.sendCommand`) see it
|
|
375
|
+
* at the top level rather than needing a second field of indirection. */
|
|
376
|
+
export function commandResponseFor(result: CommandResult): {
|
|
377
|
+
status: number;
|
|
378
|
+
body: Record<string, unknown>;
|
|
379
|
+
} {
|
|
380
|
+
if (result.ok) return { status: 200, body: { ok: true, ...(result.data ?? {}) } };
|
|
381
|
+
if (result.timedOut) {
|
|
382
|
+
return {
|
|
383
|
+
status: 504,
|
|
384
|
+
body: { ok: false, error: result.error ?? 'Command timed out — no editor connected.' },
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
return { status: 400, body: { ok: false, error: result.error } };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ---------------------------------------------------------------------------
|
|
391
|
+
// "New Project" packaged-mode guard (Phase B cleanup, ITEM 2)
|
|
392
|
+
// ---------------------------------------------------------------------------
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* True iff `engineRoot` looks like a real monorepo checkout `scaffoldProject`
|
|
396
|
+
* can read a starter/example template from — i.e.
|
|
397
|
+
* `<engineRoot>/packages/editor/template` exists, the EXACT path
|
|
398
|
+
* `create-vgai-project`'s `scaffoldProject` reads for the starter template
|
|
399
|
+
* (`packages/create-vgai-project/src/scaffold.ts`'s `templateDir`; the
|
|
400
|
+
* `--example <id>` path similarly reads `<engineRoot>/examples/<id>`, which
|
|
401
|
+
* only exists under the same monorepo layout).
|
|
402
|
+
*
|
|
403
|
+
* A packaged (`node_modules`-installed) `@vgai/editor`'s `engineRoot` is the
|
|
404
|
+
* EDITOR PACKAGE's own root instead of a monorepo checkout root (see
|
|
405
|
+
* `packaged.ts`'s header doc comment, "Known scope trims" — `createEditorServer`
|
|
406
|
+
* is constructed with `engineRoot: editorPackageRoot` there, since there is no
|
|
407
|
+
* real monorepo on disk to point at). That directory has no
|
|
408
|
+
* `packages/editor/template` subtree — only a bare `template/` at its own
|
|
409
|
+
* root — so this correctly reports `false` there, and `true` for both `dev.ts`
|
|
410
|
+
* (real `engineRoot`) and `prod.ts` (Electron packaging, also a real checkout
|
|
411
|
+
* at build time).
|
|
412
|
+
*/
|
|
413
|
+
export function isMonorepoScaffoldRoot(engineRoot: string): boolean {
|
|
414
|
+
return existsSync(join(engineRoot, 'packages', 'editor', 'template'));
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* The actionable error `POST /__editor/create-project` returns when
|
|
419
|
+
* `!isMonorepoScaffoldRoot(engineRoot)` (packaged mode). `scaffoldProject`'s
|
|
420
|
+
* `monoRoot` assumes a monorepo checkout layout the packaged runtime doesn't
|
|
421
|
+
* have (`isMonorepoScaffoldRoot`'s doc comment) — rather than let it crash
|
|
422
|
+
* partway through with a confusing `ENOENT` reading a nonexistent template
|
|
423
|
+
* dir, the route fails loudly, up front, with the supported alternative.
|
|
424
|
+
* Relocating `template/` into `create-vgai-project` (so a packaged editor's
|
|
425
|
+
* "New Project" could scaffold without a monorepo at all) is the real fix,
|
|
426
|
+
* tracked as a follow-up (phase plan §6 landmine 5) — not attempted here.
|
|
427
|
+
*/
|
|
428
|
+
export const PACKAGED_NO_MONOREPO_TEMPLATE_ERROR =
|
|
429
|
+
"Creating a new project from inside the packaged editor isn't supported yet. " +
|
|
430
|
+
'Run `vgai create <name>` in a terminal, then open the result with `vgai edit <name>`.';
|