@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,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-tree native-React ingest-game discovery (Track N, N2 —
|
|
3
|
+
* docs/REACT-INGEST-LANDING-DESIGN.md D-N2). The react sibling of
|
|
4
|
+
* `discovery.ts`/`discovery2d.ts`: every vendored native-React game gets its
|
|
5
|
+
* own `vgai.game.json` under `./games-react/<id>/` (one `react` world, an
|
|
6
|
+
* `{ ingest: { strategy: 'deduped' } }` adapter, `entry` pointing at a
|
|
7
|
+
* host-shim module in the SAME folder — D-N2). This module discovers them
|
|
8
|
+
* via `import.meta.glob`, mirroring `discovery2d.ts`'s mechanics.
|
|
9
|
+
*
|
|
10
|
+
* UNLIKE `discovery.ts`/`discovery2d.ts`, this module does NOT also glob the
|
|
11
|
+
* entry module itself: an `ingest-react` world mounts through
|
|
12
|
+
* `resolveWorldAdapter` (`adapter-resolver.ts`'s `resolveIngestReactAdapter`,
|
|
13
|
+
* N1) — the SAME `/@fs/`-dynamic-import + `createRoot` host stack
|
|
14
|
+
* `default-react` uses — rather than a capture-based bridge, so there is no
|
|
15
|
+
* "build an `IngestGame`/`IngestGame2D` descriptor with its own `load`
|
|
16
|
+
* thunk" step to replicate here. This module's ONLY job is: find every
|
|
17
|
+
* `games-react/*\/vgai.game.json`, validate its single world is a real
|
|
18
|
+
* `ingest-react` `'deduped'` world, and hand back the parsed
|
|
19
|
+
* `ResolvedGameManifest` + that world's absolute on-disk folder (the
|
|
20
|
+
* `projectRoot` `resolveWorldAdapter` needs for its `entry`'s `/@fs/`
|
|
21
|
+
* dynamic import and its `loadProjectGameProvider` lookup) — see
|
|
22
|
+
* `ingest-mode.ts`'s `enterIngestModeReact` for the mount step.
|
|
23
|
+
*
|
|
24
|
+
* `projectRoot` (an ABSOLUTE filesystem path) can't come from
|
|
25
|
+
* `import.meta.glob`/`import.meta.url` alone (those resolve to HTTP URLs in
|
|
26
|
+
* the browser, not filesystem paths) — it is built from
|
|
27
|
+
* `__VGAI_ENGINE_ROOT__` (a dev-server-only Vite `define`, `server/dev.ts`,
|
|
28
|
+
* mirroring the existing `__VGAI_PROJECT_PATH__` define) plus this folder's
|
|
29
|
+
* FIXED, known-at-build-time relative offset. `browser` editor-mode builds
|
|
30
|
+
* (no dev server) never define `__VGAI_ENGINE_ROOT__` — `getIngestGameReact`
|
|
31
|
+
* callers must check `getEditorMode() !== 'browser'` first (mirrors every
|
|
32
|
+
* other `/@fs/`-dependent ingest route's `getEditorMode() === 'browser'`
|
|
33
|
+
* guard, e.g. `ingest-mode.ts`'s `tryManifestIngestRoute`).
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { loadGameManifest, type ResolvedGameManifest } from '@engine/manifest/load';
|
|
37
|
+
|
|
38
|
+
declare const __VGAI_ENGINE_ROOT__: string;
|
|
39
|
+
|
|
40
|
+
const manifestModules = import.meta.glob('./games-react/*/vgai.game.json', {
|
|
41
|
+
eager: true,
|
|
42
|
+
import: 'default',
|
|
43
|
+
}) as Record<string, unknown>;
|
|
44
|
+
|
|
45
|
+
const MANIFEST_KEY_RE = /^\.\/games-react\/([^/]+)\/vgai\.game\.json$/;
|
|
46
|
+
|
|
47
|
+
function folderIdFromManifestKey(key: string): string {
|
|
48
|
+
const match = MANIFEST_KEY_RE.exec(key);
|
|
49
|
+
if (!match?.[1]) {
|
|
50
|
+
throw new Error(`react ingest discovery: unexpected manifest glob key "${key}"`);
|
|
51
|
+
}
|
|
52
|
+
return match[1];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** A discovered native-React ingest fixture: its parsed manifest (exactly one
|
|
56
|
+
* `ingest-react` `'deduped'` world) plus that world for convenience. */
|
|
57
|
+
export interface IngestGameReact {
|
|
58
|
+
readonly folderId: string;
|
|
59
|
+
readonly manifest: ResolvedGameManifest;
|
|
60
|
+
readonly worldId: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function buildIngestGameReact(folderId: string, raw: unknown): IngestGameReact {
|
|
64
|
+
const manifest = loadGameManifest(raw);
|
|
65
|
+
if (manifest.worlds.length !== 1) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`react ingest discovery: "${folderId}/vgai.game.json" must declare exactly one world ` +
|
|
68
|
+
`(found ${manifest.worlds.length}).`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
const world = manifest.worlds[0]!;
|
|
72
|
+
if (
|
|
73
|
+
world.kind !== 'react' ||
|
|
74
|
+
world.adapter.type !== 'ingest' ||
|
|
75
|
+
world.adapter.identity !== 'ingest-react'
|
|
76
|
+
) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`react ingest discovery: "${folderId}/vgai.game.json"'s world must be a react { ingest } ` +
|
|
79
|
+
`world (found kind "${world.kind}", adapter identity "${world.adapter.identity}").`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
if (world.adapter.strategy !== 'deduped') {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`react ingest discovery: "${folderId}" declares strategy "${world.adapter.strategy}" — ` +
|
|
85
|
+
"react ingestion v1 supports ONLY 'deduped' (D-N3).",
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if (!world.entry) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`react ingest discovery: "${folderId}" has no \`entry\` — an ingest-react world's \`entry\` ` +
|
|
91
|
+
'must point at the host-shim module in the same folder (D-N2).',
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
return { folderId, manifest, worldId: world.id };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let _cache: IngestGameReact[] | null = null;
|
|
98
|
+
|
|
99
|
+
/** Every in-tree native-React ingest fixture, discovered from
|
|
100
|
+
* `./games-react/*\/vgai.game.json`. */
|
|
101
|
+
export function discoverIngestGamesReact(): IngestGameReact[] {
|
|
102
|
+
if (!_cache) {
|
|
103
|
+
_cache = Object.entries(manifestModules)
|
|
104
|
+
.map(([key, raw]) => buildIngestGameReact(folderIdFromManifestKey(key), raw))
|
|
105
|
+
.sort((a, b) => a.folderId.localeCompare(b.folderId));
|
|
106
|
+
}
|
|
107
|
+
return _cache;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Look up one discovered react ingest game by its folder id (`?ingest=<id>`
|
|
111
|
+
* resolution — the folder id IS the world id by convention, one world per
|
|
112
|
+
* manifest, but callers should key off `folderId` since that's what the URL
|
|
113
|
+
* param names). */
|
|
114
|
+
export function getIngestGameReact(folderId: string): IngestGameReact | undefined {
|
|
115
|
+
return discoverIngestGamesReact().find((g) => g.folderId === folderId);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The absolute on-disk `projectRoot` for a discovered react ingest game's
|
|
120
|
+
* folder — what `resolveWorldAdapter`/`resolveAllWorlds` need to resolve its
|
|
121
|
+
* `entry` (and, transitively, `loadProjectGameProvider`'s
|
|
122
|
+
* `src/ui/game-state.tsx` lookup). `undefined` when
|
|
123
|
+
* `__VGAI_ENGINE_ROOT__` isn't defined (browser-mode static build — no dev
|
|
124
|
+
* server, no `/@fs/` route to resolve through anyway).
|
|
125
|
+
*/
|
|
126
|
+
export function ingestGameReactProjectRoot(folderId: string): string | undefined {
|
|
127
|
+
if (typeof __VGAI_ENGINE_ROOT__ !== 'string' || !__VGAI_ENGINE_ROOT__) return undefined;
|
|
128
|
+
const root = __VGAI_ENGINE_ROOT__.endsWith('/')
|
|
129
|
+
? __VGAI_ENGINE_ROOT__.slice(0, -1)
|
|
130
|
+
: __VGAI_ENGINE_ROOT__;
|
|
131
|
+
return `${root}/packages/editor/src/ingest/games-react/${folderId}`;
|
|
132
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-tree ingest-game discovery — T3.3 slice 3 (docs/CLI-ON-FOLDER-DESIGN.md
|
|
3
|
+
* §1E). Replaces the deleted `ingest/games/registry.ts` (`INGEST_GAMES`
|
|
4
|
+
* array): every fixture game folder under `ingest/games/<id>/` now carries
|
|
5
|
+
* its own `vgai.game.json` (one `threejs` world, `{ ingest }` adapter); this
|
|
6
|
+
* module discovers them via `import.meta.glob` and builds the SAME
|
|
7
|
+
* `IngestGame[]` shape the registry used to hand-maintain — adding a new
|
|
8
|
+
* fixture becomes "drop a folder with a manifest", zero code edits here.
|
|
9
|
+
*
|
|
10
|
+
* Module-loading choice (the slice's CAVEAT): `resolveIngestDescriptor`'s
|
|
11
|
+
* `/@fs/${projectRoot}/...` dynamic-import route (adapter-resolver.ts) is
|
|
12
|
+
* built for an EXTERNAL project folder outside this package's own source
|
|
13
|
+
* tree (the CLI-on-folder route, §1E's other half) — not for these IN-TREE
|
|
14
|
+
* fixtures, which Vite already knows about statically. So this module does
|
|
15
|
+
* NOT call `resolveIngestDescriptor` at all; it uses separate
|
|
16
|
+
* `import.meta.glob`s, all statically analyzable by Vite (and by Vitest,
|
|
17
|
+
* which shares Vite's transform pipeline) in both dev AND unit tests, with
|
|
18
|
+
* no runtime fetch/`/@fs/` involved:
|
|
19
|
+
* - manifests (`vgai.game.json`, eager, parsed JSON) — the source of truth
|
|
20
|
+
* for id/description/strategy/assets/domStubs/captureTimeoutMs (`name`
|
|
21
|
+
* comes from the manifest's top-level game name);
|
|
22
|
+
* - entry modules (`*.js`, LAZY thunks) for the `shared`/`deduped`
|
|
23
|
+
* strategies — same shape as the registry's `load: () => import(...)`,
|
|
24
|
+
* side-effecting only when actually launched (a bare `import.meta.glob`
|
|
25
|
+
* with no `eager`/`query` option yields exactly `() => Promise<Module>`
|
|
26
|
+
* thunks, matching `IngestGame.load` byte-for-byte);
|
|
27
|
+
* - raw text (`?raw` query) for `entryHtml` files (iframe-reachable's `.js`
|
|
28
|
+
* module source / opaque-embed's `.html` document) — never
|
|
29
|
+
* imported/executed by the host, just read as a string;
|
|
30
|
+
* - `?url` asset globs for the manifest's `assets` map (relative paths ->
|
|
31
|
+
* Vite's built/served asset URL, same as the registry's per-game `?url`
|
|
32
|
+
* imports, just data-driven instead of one hand-written import per game).
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { loadGameManifest } from '@engine/manifest/load';
|
|
36
|
+
import type { IngestGame } from './types';
|
|
37
|
+
|
|
38
|
+
const manifestModules = import.meta.glob('./games/*/vgai.game.json', {
|
|
39
|
+
eager: true,
|
|
40
|
+
import: 'default',
|
|
41
|
+
}) as Record<string, unknown>;
|
|
42
|
+
|
|
43
|
+
const entryModules = import.meta.glob('./games/*/*.js') as Record<string, () => Promise<unknown>>;
|
|
44
|
+
|
|
45
|
+
const entryHtmlText = import.meta.glob('./games/*/*.{html,js}', {
|
|
46
|
+
eager: true,
|
|
47
|
+
query: '?raw',
|
|
48
|
+
import: 'default',
|
|
49
|
+
}) as Record<string, string>;
|
|
50
|
+
|
|
51
|
+
const assetUrls = import.meta.glob('./games/*/assets/**', {
|
|
52
|
+
eager: true,
|
|
53
|
+
query: '?url',
|
|
54
|
+
import: 'default',
|
|
55
|
+
}) as Record<string, string>;
|
|
56
|
+
|
|
57
|
+
const MANIFEST_KEY_RE = /^\.\/games\/([^/]+)\/vgai\.game\.json$/;
|
|
58
|
+
|
|
59
|
+
function folderIdFromManifestKey(key: string): string {
|
|
60
|
+
const match = MANIFEST_KEY_RE.exec(key);
|
|
61
|
+
if (!match?.[1]) {
|
|
62
|
+
throw new Error(`ingest discovery: unexpected manifest glob key "${key}"`);
|
|
63
|
+
}
|
|
64
|
+
return match[1];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function resolveAssetUrls(
|
|
68
|
+
folderId: string,
|
|
69
|
+
assets: Record<string, string>,
|
|
70
|
+
): Record<string, string> {
|
|
71
|
+
const resolved: Record<string, string> = {};
|
|
72
|
+
for (const [pathSubstring, relPath] of Object.entries(assets)) {
|
|
73
|
+
const key = `./games/${folderId}/${relPath}`;
|
|
74
|
+
const url = assetUrls[key];
|
|
75
|
+
if (!url) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`ingest discovery: "${folderId}"'s asset "${relPath}" did not match any served asset ` +
|
|
78
|
+
`(looked for glob key "${key}" — available: ${Object.keys(assetUrls).join(', ')})`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
resolved[pathSubstring] = url;
|
|
82
|
+
}
|
|
83
|
+
return resolved;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
type BaseFields = Pick<
|
|
87
|
+
IngestGame,
|
|
88
|
+
'id' | 'name' | 'description' | 'assets' | 'domStubs' | 'captureTimeoutMs' | 'gameVersion'
|
|
89
|
+
>;
|
|
90
|
+
|
|
91
|
+
/** `shared`/`deduped`: a `load` thunk off the entry-module glob. */
|
|
92
|
+
function buildModuleLoadedGame(
|
|
93
|
+
folderId: string,
|
|
94
|
+
world: { entry: string | undefined },
|
|
95
|
+
strategy: 'shared' | 'deduped',
|
|
96
|
+
base: BaseFields,
|
|
97
|
+
): IngestGame {
|
|
98
|
+
if (!world.entry) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`ingest discovery: "${folderId}" (strategy "${strategy}") has no \`entry\` — ` +
|
|
101
|
+
"shared/deduped ingest worlds need the game module's folder-relative path there.",
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
const key = `./games/${folderId}/${world.entry}`;
|
|
105
|
+
const load = entryModules[key];
|
|
106
|
+
if (!load) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`ingest discovery: "${folderId}"'s entry "${world.entry}" did not match any game ` +
|
|
109
|
+
`module (looked for glob key "${key}").`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
return { ...base, load };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** `iframe-reachable`/`opaque-embed`: raw text off the entryHtml file glob. */
|
|
116
|
+
function buildFileBackedGame(
|
|
117
|
+
folderId: string,
|
|
118
|
+
entryHtml: string | undefined,
|
|
119
|
+
strategy: 'iframe-reachable' | 'opaque-embed',
|
|
120
|
+
base: BaseFields,
|
|
121
|
+
): IngestGame {
|
|
122
|
+
if (!entryHtml) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
`ingest discovery: "${folderId}" (strategy "${strategy}") has no \`entryHtml\`.`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
const key = `./games/${folderId}/${entryHtml}`;
|
|
128
|
+
const text = entryHtmlText[key];
|
|
129
|
+
if (text === undefined) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`ingest discovery: "${folderId}"'s entryHtml "${entryHtml}" did not match any file ` +
|
|
132
|
+
`(looked for glob key "${key}").`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return strategy === 'iframe-reachable'
|
|
136
|
+
? { ...base, iframeReachableSrc: text }
|
|
137
|
+
: { ...base, embedHtml: text };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function buildIngestGame(folderId: string, raw: unknown): IngestGame {
|
|
141
|
+
const manifest = loadGameManifest(raw);
|
|
142
|
+
if (manifest.worlds.length !== 1) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`ingest discovery: "${folderId}/vgai.game.json" must declare exactly one world ` +
|
|
145
|
+
`(found ${manifest.worlds.length}).`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
const world = manifest.worlds[0]!;
|
|
149
|
+
if (world.adapter.type !== 'ingest' || world.adapter.identity !== 'ingest-three') {
|
|
150
|
+
throw new Error(
|
|
151
|
+
`ingest discovery: "${folderId}/vgai.game.json"'s world must be a threejs { ingest } ` +
|
|
152
|
+
`world (found adapter identity "${world.adapter.identity}").`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
const { adapter } = world;
|
|
156
|
+
|
|
157
|
+
const base: BaseFields = {
|
|
158
|
+
id: world.id,
|
|
159
|
+
name: manifest.name,
|
|
160
|
+
description: world.description ?? '',
|
|
161
|
+
// D9 (docs/DECISIONS-PENDING.md §D9): the manifest's own `version`, so a
|
|
162
|
+
// later mount can compare a saved overlay's `authoredAgainst.gameVersion`
|
|
163
|
+
// against what's currently running (`overlay-report.ts`).
|
|
164
|
+
gameVersion: manifest.version,
|
|
165
|
+
...(adapter.assets !== undefined ? { assets: resolveAssetUrls(folderId, adapter.assets) } : {}),
|
|
166
|
+
...(adapter.domStubs !== undefined ? { domStubs: adapter.domStubs } : {}),
|
|
167
|
+
...(adapter.captureTimeoutMs !== undefined
|
|
168
|
+
? { captureTimeoutMs: adapter.captureTimeoutMs }
|
|
169
|
+
: {}),
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
switch (adapter.strategy) {
|
|
173
|
+
case 'shared':
|
|
174
|
+
case 'deduped':
|
|
175
|
+
return buildModuleLoadedGame(folderId, world, adapter.strategy, base);
|
|
176
|
+
case 'iframe-reachable':
|
|
177
|
+
case 'opaque-embed':
|
|
178
|
+
return buildFileBackedGame(folderId, adapter.entryHtml, adapter.strategy, base);
|
|
179
|
+
default: {
|
|
180
|
+
// Exhaustive per IngestStrategySchema's 4 members — a new strategy
|
|
181
|
+
// value added to the schema without a branch here is a compile error.
|
|
182
|
+
const exhaustive: never = adapter.strategy;
|
|
183
|
+
throw new Error(
|
|
184
|
+
`ingest discovery: "${folderId}" has an unhandled ingest strategy ${JSON.stringify(exhaustive)}.`,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let _cache: IngestGame[] | null = null;
|
|
191
|
+
|
|
192
|
+
/** Every in-tree ingest fixture game, discovered from `./games/*\/vgai.game.json`. */
|
|
193
|
+
export function discoverIngestGames(): IngestGame[] {
|
|
194
|
+
if (!_cache) {
|
|
195
|
+
_cache = Object.entries(manifestModules)
|
|
196
|
+
.map(([key, raw]) => buildIngestGame(folderIdFromManifestKey(key), raw))
|
|
197
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
198
|
+
}
|
|
199
|
+
return _cache;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Look up one discovered ingest game by id (`?ingest=<id>` resolution). */
|
|
203
|
+
export function getIngestGame(id: string): IngestGame | undefined {
|
|
204
|
+
return discoverIngestGames().find((g) => g.id === id);
|
|
205
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-tree world2d (PixiJS) ingest-game discovery — T6.1 slice 4
|
|
3
|
+
* (docs/MULTI-WORLD-DESIGN.md §1.E/§3 slice 4, world2d silo retirement). The
|
|
4
|
+
* pixi sibling of `discovery.ts`: replaces the deleted
|
|
5
|
+
* `world2d-demo/games/registry.ts` (`WORLD2D_INGEST_GAMES` array) — the three
|
|
6
|
+
* vgai-authored mini-games (bunnymark, flappy, tilemap-camera) each now carry
|
|
7
|
+
* their own `vgai.game.json` under `./games-2d/<id>/` (one `pixijs` world, a
|
|
8
|
+
* `{ ingest }` adapter); this module discovers them via `import.meta.glob`,
|
|
9
|
+
* mirroring `discovery.ts`'s mechanics exactly (see that file's doc comment
|
|
10
|
+
* for the full "why glob, not resolveIngest2DDescriptor" rationale — same
|
|
11
|
+
* reasoning applies here: these are IN-TREE fixtures Vite already knows about
|
|
12
|
+
* statically, not an external project folder).
|
|
13
|
+
*
|
|
14
|
+
* OVERLAY-KEY CONTINUITY (§1.E/§1.G): each fixture's world id is UNCHANGED
|
|
15
|
+
* from its old registry-era id (`bunnymark`/`flappy`/`tilemap-camera`, no
|
|
16
|
+
* `2d-` prefixing) — a pre-existing silo-era overlay would have lived at
|
|
17
|
+
* `.vgai/overlays/2d-<id>.json` (the retired `overlayPath2D`); this slice's
|
|
18
|
+
* unified overlay path is `.vgai/overlays/<id>.json` (`authoring/
|
|
19
|
+
* world-overlay.ts`, §1.G) — a DIFFERENT file, not a rename. Old `2d-*.json`
|
|
20
|
+
* overlays are dev-local artifacts, left in place, per §1.H's "no migration"
|
|
21
|
+
* note. `id` reachability (the id string itself) survives; the overlay FILE
|
|
22
|
+
* does not carry forward automatically.
|
|
23
|
+
*
|
|
24
|
+
* Track P (docs/PIXI-INGEST-LANDING-DESIGN.md §2, P1/P2) added a real
|
|
25
|
+
* `iframe-reachable` branch alongside `shared`/`deduped`: an externalized
|
|
26
|
+
* built bundle descriptor built straight from the manifest's six new
|
|
27
|
+
* `ingest.{bundleUrl,baseHref,assetBaseUrl,extraDeps,pixiModuleUrl,bodyHtml}`
|
|
28
|
+
* fields — see `buildIframeReachableGame2D` below. `opaque-embed` still
|
|
29
|
+
* throws HERE (no in-tree world2d fixture uses it, and this synchronous
|
|
30
|
+
* glob-discovery route has no runtime fetch to build a lazy `embedHtml`
|
|
31
|
+
* thunk from) — D-W2 (docs/WAVE4-FTUE-HARDENING-DESIGN.md) wires it for the
|
|
32
|
+
* manifest-driven external-folder route instead
|
|
33
|
+
* (`resolveIngest2DDescriptor`/`adapter-resolver.ts`), where `IngestGame2D`
|
|
34
|
+
* now DOES have an `embedHtml` field.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import { loadGameManifest } from '@engine/manifest/load';
|
|
38
|
+
import type { IngestGame2D } from '@engine/world2d/ingest2d';
|
|
39
|
+
import { resolveExtraDeps } from './host-namespace-registry';
|
|
40
|
+
|
|
41
|
+
const manifestModules = import.meta.glob('./games-2d/*/vgai.game.json', {
|
|
42
|
+
eager: true,
|
|
43
|
+
import: 'default',
|
|
44
|
+
}) as Record<string, unknown>;
|
|
45
|
+
|
|
46
|
+
const entryModules = import.meta.glob('./games-2d/*/*.ts') as Record<
|
|
47
|
+
string,
|
|
48
|
+
() => Promise<unknown>
|
|
49
|
+
>;
|
|
50
|
+
|
|
51
|
+
const MANIFEST_KEY_RE = /^\.\/games-2d\/([^/]+)\/vgai\.game\.json$/;
|
|
52
|
+
|
|
53
|
+
function folderIdFromManifestKey(key: string): string {
|
|
54
|
+
const match = MANIFEST_KEY_RE.exec(key);
|
|
55
|
+
if (!match?.[1]) {
|
|
56
|
+
throw new Error(`world2d ingest discovery: unexpected manifest glob key "${key}"`);
|
|
57
|
+
}
|
|
58
|
+
return match[1];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
type BaseFields2D = Pick<IngestGame2D, 'id' | 'name' | 'description' | 'captureTimeoutMs'>;
|
|
62
|
+
|
|
63
|
+
function buildModuleLoadedGame2D(
|
|
64
|
+
folderId: string,
|
|
65
|
+
world: { entry: string | undefined },
|
|
66
|
+
strategy: 'shared' | 'deduped',
|
|
67
|
+
base: BaseFields2D,
|
|
68
|
+
): IngestGame2D {
|
|
69
|
+
if (!world.entry) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`world2d ingest discovery: "${folderId}" (strategy "${strategy}") has no \`entry\` — ` +
|
|
72
|
+
"shared/deduped ingest-pixi worlds need the game module's folder-relative path there.",
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
const key = `./games-2d/${folderId}/${world.entry}`;
|
|
76
|
+
const load = entryModules[key];
|
|
77
|
+
if (!load) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`world2d ingest discovery: "${folderId}"'s entry "${world.entry}" did not match any game ` +
|
|
80
|
+
`module (looked for glob key "${key}").`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return { ...base, tier: 'shared', load };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Build an `IngestGame2D` for the `iframe-reachable` strategy straight from
|
|
88
|
+
* the manifest's Track P fields (docs/PIXI-INGEST-LANDING-DESIGN.md §2) —
|
|
89
|
+
* `bundleUrl` is required by the schema's cross-field check for this
|
|
90
|
+
* strategy (exactly one of `bundleUrl`/`entryHtml`); `entryHtml`-only
|
|
91
|
+
* iframe-reachable worlds still have no builder here (mirrors the
|
|
92
|
+
* `resolveIngestDescriptor`/three-side gap: reading a raw HTML/inline-source
|
|
93
|
+
* file's contents needs a runtime fetch this synchronous builder doesn't do).
|
|
94
|
+
* `extraDeps`/`pixiModuleUrl` become LAZY thunks (`IngestGame2D.extraDeps`/
|
|
95
|
+
* `.pixiNamespace`) closing over the host-namespace registry / a dynamic
|
|
96
|
+
* `import()` — never resolved just to build this descriptor.
|
|
97
|
+
*/
|
|
98
|
+
export function buildIframeReachableGame2D(
|
|
99
|
+
folderId: string,
|
|
100
|
+
adapter: {
|
|
101
|
+
bundleUrl: string | undefined;
|
|
102
|
+
baseHref: string | undefined;
|
|
103
|
+
assetBaseUrl: string | undefined;
|
|
104
|
+
extraDeps: string[] | undefined;
|
|
105
|
+
pixiModuleUrl: string | undefined;
|
|
106
|
+
bodyHtml: string | undefined;
|
|
107
|
+
},
|
|
108
|
+
base: BaseFields2D,
|
|
109
|
+
): IngestGame2D {
|
|
110
|
+
if (!adapter.bundleUrl) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`world2d ingest discovery: "${folderId}" (strategy "iframe-reachable") has no \`bundleUrl\` ` +
|
|
113
|
+
'— only the externalized-bundle route is buildable here (an `entryHtml`-only iframe-' +
|
|
114
|
+
"reachable world needs a runtime file fetch this synchronous glob discovery doesn't do).",
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
const { bundleUrl, baseHref, assetBaseUrl, extraDeps, pixiModuleUrl, bodyHtml } = adapter;
|
|
118
|
+
return {
|
|
119
|
+
...base,
|
|
120
|
+
tier: 'iframe-reachable',
|
|
121
|
+
bundleUrl,
|
|
122
|
+
...(baseHref !== undefined ? { baseHref } : {}),
|
|
123
|
+
...(assetBaseUrl !== undefined ? { assetBaseUrl } : {}),
|
|
124
|
+
...(extraDeps !== undefined ? { extraDeps: () => resolveExtraDeps(extraDeps) } : {}),
|
|
125
|
+
...(pixiModuleUrl !== undefined
|
|
126
|
+
? { pixiNamespace: () => import(/* @vite-ignore */ pixiModuleUrl) }
|
|
127
|
+
: {}),
|
|
128
|
+
...(bodyHtml !== undefined ? { bodyHtml } : {}),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function buildIngestGame2D(folderId: string, raw: unknown): IngestGame2D {
|
|
133
|
+
const manifest = loadGameManifest(raw);
|
|
134
|
+
if (manifest.worlds.length !== 1) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`world2d ingest discovery: "${folderId}/vgai.game.json" must declare exactly one world ` +
|
|
137
|
+
`(found ${manifest.worlds.length}).`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
const world = manifest.worlds[0]!;
|
|
141
|
+
if (world.adapter.type !== 'ingest' || world.adapter.identity !== 'ingest-pixi') {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`world2d ingest discovery: "${folderId}/vgai.game.json"'s world must be a pixijs ` +
|
|
144
|
+
`{ ingest } world (found adapter identity "${world.adapter.identity}").`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
const { adapter } = world;
|
|
148
|
+
|
|
149
|
+
const base: BaseFields2D = {
|
|
150
|
+
id: world.id,
|
|
151
|
+
name: manifest.name,
|
|
152
|
+
description: world.description ?? '',
|
|
153
|
+
...(adapter.captureTimeoutMs !== undefined
|
|
154
|
+
? { captureTimeoutMs: adapter.captureTimeoutMs }
|
|
155
|
+
: {}),
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
switch (adapter.strategy) {
|
|
159
|
+
case 'shared':
|
|
160
|
+
case 'deduped':
|
|
161
|
+
return buildModuleLoadedGame2D(folderId, world, adapter.strategy, base);
|
|
162
|
+
case 'iframe-reachable':
|
|
163
|
+
return buildIframeReachableGame2D(folderId, adapter, base);
|
|
164
|
+
case 'opaque-embed':
|
|
165
|
+
// D-W2 (docs/WAVE4-FTUE-HARDENING-DESIGN.md) wired `IngestGame2D.embedHtml`
|
|
166
|
+
// for the manifest-driven external-folder route (`resolveIngest2DDescriptor`,
|
|
167
|
+
// reading `entryHtml`'s bytes through the project-static route as a lazy
|
|
168
|
+
// thunk) — but no in-tree world2d fixture uses opaque-embed, and this
|
|
169
|
+
// SYNCHRONOUS glob-discovery route has no runtime fetch to build that
|
|
170
|
+
// thunk from (mirrors the three side's identical `entryHtml`-only gap,
|
|
171
|
+
// `discovery.ts`'s own file-glob route DOES have one via `?raw`, since
|
|
172
|
+
// Vite already knows these files statically — porting that mechanism
|
|
173
|
+
// here is unclaimed, not attempted, since nothing needs it yet).
|
|
174
|
+
throw new Error(
|
|
175
|
+
`world2d ingest discovery: "${folderId}" declares strategy "${adapter.strategy}" — not ` +
|
|
176
|
+
'buildable from this synchronous glob-discovery route (no in-tree world2d opaque-embed ' +
|
|
177
|
+
'fixture exists yet; see resolveIngest2DDescriptor for the manifest-driven route).',
|
|
178
|
+
);
|
|
179
|
+
default: {
|
|
180
|
+
// Exhaustive per IngestStrategySchema's 4 members.
|
|
181
|
+
const exhaustive: never = adapter.strategy;
|
|
182
|
+
throw new Error(
|
|
183
|
+
`world2d ingest discovery: "${folderId}" has an unhandled ingest strategy ${JSON.stringify(exhaustive)}.`,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let _cache: IngestGame2D[] | null = null;
|
|
190
|
+
|
|
191
|
+
/** Every in-tree world2d ingest fixture game, discovered from `./games-2d/*\/vgai.game.json`. */
|
|
192
|
+
export function discoverIngestGames2D(): IngestGame2D[] {
|
|
193
|
+
if (!_cache) {
|
|
194
|
+
_cache = Object.entries(manifestModules)
|
|
195
|
+
.map(([key, raw]) => buildIngestGame2D(folderIdFromManifestKey(key), raw))
|
|
196
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
197
|
+
}
|
|
198
|
+
return _cache;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Look up one discovered world2d ingest game by id. */
|
|
202
|
+
export function getIngestGame2D(id: string): IngestGame2D | undefined {
|
|
203
|
+
return discoverIngestGames2D().find((g) => g.id === id);
|
|
204
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESM-entry resolution from a parsed `package.json` — the ONE pure-logic
|
|
3
|
+
* home for the minimal `exports`/`module`-field subset D-Z3
|
|
4
|
+
* (docs/EXTERNAL-FOLDER-INGEST-DESIGN.md's wave-3 addendum) and D-Z4
|
|
5
|
+
* (docs/WAVE3-ADAPTER-PLUMBING-DESIGN.md §1) both need.
|
|
6
|
+
*
|
|
7
|
+
* Two callers share this logic on purpose, per D-Z4's own design note ("put
|
|
8
|
+
* shared pure logic in one place if feasible rather than duplicating"):
|
|
9
|
+
* - `host-namespace-registry.ts`'s `probeProjectNodeModules` — editor-side,
|
|
10
|
+
* FETCHES a project's `node_modules/<spec>/package.json` through the
|
|
11
|
+
* `/project-game-static` dev-server route (browser-realm, no `node:fs`).
|
|
12
|
+
* - `packages/editor/scripts/run-bundle.ts`'s extraDeps pre-build
|
|
13
|
+
* validation — a plain Node CLI script, READS the same file straight off
|
|
14
|
+
* disk via `node:fs`.
|
|
15
|
+
* Both need byte-identical SEMANTICS (same exports-subset, same "found"
|
|
16
|
+
* dump for the CJS-only case) even though the I/O differs — this module is
|
|
17
|
+
* the fs-free, fetch-free core both wrap.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** The one exports-map shape this fallback honors: a "." entry (string, or an
|
|
21
|
+
* { import | module | default } conditions object — the real shapes
|
|
22
|
+
* `@pixi/sound` and `gsap` ship, D-Z3's reference packages). Reads a single
|
|
23
|
+
* string condition off `conditions`, preferring `import`, then `module`,
|
|
24
|
+
* then `default` — the order D-Z3 specifies. */
|
|
25
|
+
function esmEntryFromConditions(conditions: Record<string, unknown>): string | null {
|
|
26
|
+
for (const key of ['import', 'module', 'default']) {
|
|
27
|
+
const v = conditions[key];
|
|
28
|
+
if (typeof v === 'string') return v;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Resolve the ESM entry PATH (package-folder-relative, e.g. "./lib/index.mjs")
|
|
35
|
+
* from a parsed `package.json`'s `exports` field, honoring the minimal subset
|
|
36
|
+
* this fallback supports (see module doc comment). Returns `null` (never
|
|
37
|
+
* throws) when the shape doesn't fit this subset or no ESM entry is declared
|
|
38
|
+
* at all — the CALLER decides how to report that (trap: exports-map parsing
|
|
39
|
+
* is a rabbit hole, so this stays an honest minimal subset, not a full
|
|
40
|
+
* resolver).
|
|
41
|
+
*/
|
|
42
|
+
function esmEntryFromExportsField(exportsField: unknown): string | null {
|
|
43
|
+
if (typeof exportsField === 'string') return exportsField;
|
|
44
|
+
if (!exportsField || typeof exportsField !== 'object' || Array.isArray(exportsField)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const exportsObj = exportsField as Record<string, unknown>;
|
|
48
|
+
const dot = exportsObj['.'];
|
|
49
|
+
if (typeof dot === 'string') return dot;
|
|
50
|
+
if (dot && typeof dot === 'object' && !Array.isArray(dot)) {
|
|
51
|
+
return esmEntryFromConditions(dot as Record<string, unknown>);
|
|
52
|
+
}
|
|
53
|
+
if (dot === undefined) {
|
|
54
|
+
// No "." subpath key at all — a package with a SINGLE entry point may
|
|
55
|
+
// declare `exports` as a bare conditions map with no subpaths (no key
|
|
56
|
+
// starts with '.'). Anything else (real subpaths like "./foo") is
|
|
57
|
+
// outside this minimal subset — leave it to the caller's "found" dump.
|
|
58
|
+
const hasSubpathKeys = Object.keys(exportsObj).some((k) => k.startsWith('.'));
|
|
59
|
+
if (!hasSubpathKeys) return esmEntryFromConditions(exportsObj);
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Resolve the ESM entry PATH from a parsed `package.json`: `exports["."]`
|
|
66
|
+
* (string or conditions object) first, then the top-level `module` field.
|
|
67
|
+
* `null` when neither is present/parseable (CJS-only or an unrecognized
|
|
68
|
+
* shape) — never throws.
|
|
69
|
+
*/
|
|
70
|
+
export function resolveEsmEntryPath(pkg: Record<string, unknown>): string | null {
|
|
71
|
+
const fromExports = esmEntryFromExportsField(pkg['exports']);
|
|
72
|
+
if (fromExports) return fromExports;
|
|
73
|
+
|
|
74
|
+
const moduleField = pkg['module'];
|
|
75
|
+
if (typeof moduleField === 'string') return moduleField;
|
|
76
|
+
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Outcome of probing one parsed `package.json` for a usable ESM entry. */
|
|
81
|
+
export type EsmEntryProbeResult =
|
|
82
|
+
| { readonly kind: 'cjs-only'; readonly found: Record<string, unknown> }
|
|
83
|
+
| { readonly kind: 'esm-entry'; readonly entryPath: string };
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Probe an already-parsed `package.json` for a usable ESM entry, per the
|
|
87
|
+
* minimal subset this module supports. Package-folder-relative `entryPath`
|
|
88
|
+
* has any leading `./` stripped (callers join it onto their own base path/URL
|
|
89
|
+
* however they need). `found` on the CJS-only branch is the same
|
|
90
|
+
* introspection dump both callers surface in their distinct named error
|
|
91
|
+
* ("installed but CJS-only").
|
|
92
|
+
*/
|
|
93
|
+
export function probePackageJsonForEsmEntry(pkg: Record<string, unknown>): EsmEntryProbeResult {
|
|
94
|
+
const esmEntryPath = resolveEsmEntryPath(pkg);
|
|
95
|
+
if (!esmEntryPath) {
|
|
96
|
+
return {
|
|
97
|
+
kind: 'cjs-only',
|
|
98
|
+
found: { main: pkg['main'], module: pkg['module'], exports: pkg['exports'] },
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return { kind: 'esm-entry', entryPath: esmEntryPath.replace(/^\.\//, '') };
|
|
102
|
+
}
|