@auraindustry/aurajs 0.0.7 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -2
- package/benchmarks/perf-thresholds.json +54 -0
- package/package.json +4 -7
- package/src/asset-pack.mjs +8 -1
- package/src/authored-project.mjs +1449 -0
- package/src/authored-runtime.mjs +2016 -0
- package/src/authoring/avatar-animation-graph.mjs +648 -0
- package/src/bin-integrity.mjs +272 -0
- package/src/build-contract/assets.mjs +130 -0
- package/src/build-contract/capabilities.mjs +116 -0
- package/src/build-contract/constants.mjs +6 -0
- package/src/build-contract/helpers.mjs +44 -0
- package/src/build-contract/web-templates.mjs +5993 -0
- package/src/build-contract.mjs +27 -2910
- package/src/bundler.mjs +188 -55
- package/src/cli.mjs +4840 -1512
- package/src/commands/project-authoring.mjs +454 -0
- package/src/config.mjs +44 -0
- package/src/conformance/cases/app-and-ui-runtime-cases.mjs +3309 -0
- package/src/conformance/cases/core-runtime-cases.mjs +1431 -0
- package/src/conformance/cases/index.mjs +11 -0
- package/src/conformance/cases/scene3d-and-media-cases.mjs +2094 -0
- package/src/conformance/cases/systems-and-gameplay-cases.mjs +1776 -0
- package/src/conformance/shared.mjs +27 -0
- package/src/conformance-runner.mjs +25 -13
- package/src/conformance.mjs +619 -4020
- package/src/cutscene.mjs +362 -5
- package/src/dev-cli-action.mjs +249 -0
- package/src/dev-cli-inspect.mjs +92 -0
- package/src/dev-cli-state.mjs +80 -0
- package/src/external-asset-cache.mjs +587 -0
- package/src/external-asset-policy.mjs +217 -0
- package/src/external-package-surface.mjs +206 -0
- package/src/game-action-runtime.mjs +869 -0
- package/src/game-state-runtime.mjs +206 -6
- package/src/headless-action.mjs +186 -0
- package/src/headless-test/runtime-animation.mjs +1173 -0
- package/src/headless-test/runtime-coordinator.mjs +1514 -0
- package/src/headless-test/runtime-primitives.mjs +320 -0
- package/src/headless-test/runtime-world.mjs +2253 -0
- package/src/headless-test.mjs +392 -4298
- package/src/host-binary.mjs +342 -14
- package/src/icon-discovery.mjs +64 -0
- package/src/make-catalog.mjs +109 -0
- package/src/make.mjs +197 -0
- package/src/package-integrity.mjs +586 -0
- package/src/perf-benchmark.mjs +353 -0
- package/src/postinstall.mjs +5 -5
- package/src/prefabs/index.mjs +34 -0
- package/src/prefabs/scene-serialization.mjs +184 -0
- package/src/project-importer.mjs +620 -0
- package/src/project-registry.mjs +24 -0
- package/src/publish-command.mjs +195 -0
- package/src/publish-env-example.mjs +83 -0
- package/src/publish-validation.mjs +708 -0
- package/src/retro/assets/compile.mjs +232 -0
- package/src/retro/backend-gba/authoring.mjs +1029 -0
- package/src/retro/backend-gba/rom.mjs +363 -0
- package/src/retro/backend-gbc/rom.mjs +85 -0
- package/src/retro/build.mjs +278 -0
- package/src/retro/cli/commands.mjs +292 -0
- package/src/retro/cli/templates.mjs +84 -0
- package/src/retro/diagnostics/catalog.mjs +110 -0
- package/src/retro/diagnostics/emit.mjs +72 -0
- package/src/retro/emulator/case-overlay.mjs +64 -0
- package/src/retro/emulator/discovery.mjs +158 -0
- package/src/retro/emulator/macos-case-overlay.swift +220 -0
- package/src/retro/emulator/profiles.mjs +146 -0
- package/src/retro/emulator/runner.mjs +289 -0
- package/src/retro/frontend/load-project.mjs +98 -0
- package/src/retro/index.mjs +30 -0
- package/src/retro/ir/build-ir.mjs +108 -0
- package/src/retro/runtime-gba/contract.mjs +151 -0
- package/src/retro/runtime-gbc/contract.mjs +117 -0
- package/src/retro/shared/span.mjs +26 -0
- package/src/retro/shared/targets.mjs +64 -0
- package/src/retro/validator/check-project.mjs +114 -0
- package/src/runtime-hotspot-audit.mjs +707 -0
- package/src/scaffold/config.mjs +1000 -0
- package/src/scaffold/fs.mjs +56 -0
- package/src/scaffold/layout.mjs +318 -0
- package/src/scaffold/project-docs.mjs +439 -0
- package/src/scaffold.mjs +93 -596
- package/src/scene-composition/index.mjs +326 -0
- package/src/scene-composition/runtime.mjs +751 -0
- package/src/self-hosted-assets.mjs +604 -0
- package/src/session-client.mjs +750 -0
- package/src/session-native-launcher.mjs +74 -0
- package/src/session-protocol.mjs +75 -0
- package/src/session-runtime.mjs +321 -0
- package/src/session-server.mjs +360 -0
- package/src/shader-kits/index.mjs +773 -0
- package/src/starter-content-registry.mjs +292 -0
- package/src/state-artifacts.mjs +662 -24
- package/src/state-dev-reload.mjs +99 -2
- package/src/terminal-ui.mjs +245 -0
- package/src/web-conformance.mjs +219 -0
- package/templates/create/2d/config/gameplay/shooter.config.js +26 -0
- package/templates/create/2d/content/gameplay/waves.json +26 -0
- package/templates/create/2d/content/registries/.gitkeep +1 -0
- package/templates/create/2d/docs/design/.gitkeep +1 -0
- package/templates/create/2d/docs/design/loop.md +5 -0
- package/templates/create/2d/prefabs/enemies.prefab.js +90 -0
- package/templates/create/2d/prefabs/enemy-basic.prefab.js +18 -0
- package/templates/create/2d/prefabs/player.prefab.js +36 -0
- package/templates/create/2d/prefabs/projectiles.prefab.js +35 -0
- package/templates/create/2d/scenes/boot.scene.js +12 -0
- package/templates/create/2d/scenes/gameplay.scene.js +230 -0
- package/templates/create/2d/scenes/menu.scene.js +9 -0
- package/templates/create/2d/src/main.js +6 -185
- package/templates/create/2d/src/runtime/app.js +49 -0
- package/templates/create/2d/src/runtime/capabilities.js +35 -0
- package/templates/create/2d/ui/hud.screen.js +40 -0
- package/templates/create/2d/ui/pause.screen.js +149 -0
- package/templates/create/2d/ui/settings.screen.js +347 -0
- package/templates/create/2d/ui/title.screen.js +13 -0
- package/templates/create/2d-adventure/aura.config.json +28 -0
- package/templates/create/2d-adventure/config/gameplay/adventure.config.js +14 -0
- package/templates/create/2d-adventure/content/gameplay/world.js +46 -0
- package/templates/create/2d-adventure/content/registries/.gitkeep +1 -0
- package/templates/create/2d-adventure/docs/design/loop.md +5 -0
- package/templates/create/2d-adventure/prefabs/player.prefab.js +54 -0
- package/templates/create/2d-adventure/prefabs/relic.prefab.js +38 -0
- package/templates/create/2d-adventure/prefabs/world.prefab.js +125 -0
- package/templates/create/2d-adventure/scenes/gameplay.scene.js +256 -0
- package/templates/create/2d-adventure/src/runtime/capabilities.js +34 -0
- package/templates/create/2d-adventure/ui/hud.screen.js +60 -0
- package/templates/create/2d-survivor/config/gameplay/survivor.config.js +33 -0
- package/templates/create/2d-survivor/content/gameplay/spawn-zones.json +29 -0
- package/templates/create/2d-survivor/content/registries/.gitkeep +1 -0
- package/templates/create/2d-survivor/docs/design/.gitkeep +1 -0
- package/templates/create/2d-survivor/docs/design/loop.md +5 -0
- package/templates/create/2d-survivor/prefabs/enemies.prefab.js +178 -0
- package/templates/create/2d-survivor/prefabs/enemy-swarm.prefab.js +18 -0
- package/templates/create/2d-survivor/prefabs/player.prefab.js +42 -0
- package/templates/create/2d-survivor/prefabs/projectiles.prefab.js +56 -0
- package/templates/create/2d-survivor/scenes/boot.scene.js +12 -0
- package/templates/create/2d-survivor/scenes/gameplay.scene.js +314 -0
- package/templates/create/2d-survivor/scenes/menu.scene.js +9 -0
- package/templates/create/2d-survivor/src/main.js +5 -332
- package/templates/create/2d-survivor/src/runtime/app.js +49 -0
- package/templates/create/2d-survivor/src/runtime/capabilities.js +35 -0
- package/templates/create/2d-survivor/ui/hud.screen.js +45 -0
- package/templates/create/2d-survivor/ui/title.screen.js +13 -0
- package/templates/create/3d/assets/models/starter-avatar.gltf +184 -0
- package/templates/create/3d/config/gameplay/.gitkeep +1 -0
- package/templates/create/3d/content/gameplay/checkpoints.json +33 -0
- package/templates/create/3d/content/gameplay/course.js +40 -0
- package/templates/create/3d/content/registries/.gitkeep +1 -0
- package/templates/create/3d/docs/design/.gitkeep +1 -0
- package/templates/create/3d/docs/design/loop.md +5 -0
- package/templates/create/3d/prefabs/checkpoint.prefab.js +15 -0
- package/templates/create/3d/prefabs/player.prefab.js +204 -0
- package/templates/create/3d/prefabs/world.prefab.js +112 -0
- package/templates/create/3d/scenes/boot.scene.js +12 -0
- package/templates/create/3d/scenes/checkpoint.scene.js +9 -0
- package/templates/create/3d/scenes/gameplay.scene.js +292 -0
- package/templates/create/3d/src/main.js +6 -295
- package/templates/create/3d/src/runtime/app.js +49 -0
- package/templates/create/3d/src/runtime/capabilities.js +53 -0
- package/templates/create/3d/src/runtime/materials.js +34 -0
- package/templates/create/3d/src/runtime/state.js +39 -0
- package/templates/create/3d/ui/hud.screen.js +75 -0
- package/templates/create/3d/ui/pause.screen.js +166 -0
- package/templates/create/3d/ui/settings.screen.js +387 -0
- package/templates/create/3d-adventure/assets/models/starter-avatar.gltf +184 -0
- package/templates/create/3d-adventure/aura.config.json +28 -0
- package/templates/create/3d-adventure/config/gameplay/adventure.config.js +9 -0
- package/templates/create/3d-adventure/content/gameplay/course.js +62 -0
- package/templates/create/3d-adventure/content/registries/.gitkeep +1 -0
- package/templates/create/3d-adventure/docs/design/loop.md +5 -0
- package/templates/create/3d-adventure/prefabs/player.prefab.js +168 -0
- package/templates/create/3d-adventure/prefabs/relic.prefab.js +35 -0
- package/templates/create/3d-adventure/prefabs/world.prefab.js +119 -0
- package/templates/create/3d-adventure/scenes/gameplay.scene.js +358 -0
- package/templates/create/3d-adventure/src/runtime/capabilities.js +56 -0
- package/templates/create/3d-adventure/src/runtime/materials.js +39 -0
- package/templates/create/3d-adventure/src/runtime/state.js +31 -0
- package/templates/create/3d-adventure/ui/hud.screen.js +70 -0
- package/templates/create/3d-adventure/ui/pause.screen.js +437 -0
- package/templates/create/3d-collectathon/assets/models/starter-avatar.gltf +184 -0
- package/templates/create/3d-collectathon/config/gameplay/.gitkeep +1 -0
- package/templates/create/3d-collectathon/content/gameplay/collectibles.json +26 -0
- package/templates/create/3d-collectathon/content/gameplay/course.js +46 -0
- package/templates/create/3d-collectathon/content/registries/.gitkeep +1 -0
- package/templates/create/3d-collectathon/docs/design/.gitkeep +1 -0
- package/templates/create/3d-collectathon/docs/design/loop.md +5 -0
- package/templates/create/3d-collectathon/prefabs/collectible.prefab.js +15 -0
- package/templates/create/3d-collectathon/prefabs/player.prefab.js +207 -0
- package/templates/create/3d-collectathon/prefabs/world.prefab.js +112 -0
- package/templates/create/3d-collectathon/scenes/boot.scene.js +12 -0
- package/templates/create/3d-collectathon/scenes/checkpoint.scene.js +9 -0
- package/templates/create/3d-collectathon/scenes/gameplay.scene.js +200 -0
- package/templates/create/3d-collectathon/src/main.js +5 -355
- package/templates/create/3d-collectathon/src/runtime/app.js +49 -0
- package/templates/create/3d-collectathon/src/runtime/capabilities.js +53 -0
- package/templates/create/3d-collectathon/src/runtime/materials.js +34 -0
- package/templates/create/3d-collectathon/src/runtime/state.js +27 -0
- package/templates/create/3d-collectathon/ui/hud.screen.js +66 -0
- package/templates/create/3d-collectathon/ui/pause.screen.js +13 -0
- package/templates/create/blank/config/gameplay/.gitkeep +1 -0
- package/templates/create/blank/content/gameplay/.gitkeep +1 -0
- package/templates/create/blank/content/registries/.gitkeep +1 -0
- package/templates/create/blank/docs/design/.gitkeep +1 -0
- package/templates/create/blank/docs/design/loop.md +5 -0
- package/templates/create/blank/prefabs/.gitkeep +1 -0
- package/templates/create/blank/scenes/.gitkeep +1 -0
- package/templates/create/blank/src/runtime/.gitkeep +1 -0
- package/templates/create/blank/ui/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/assets/audio/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/assets/fonts/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/assets/sprites/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/assets/starter/README.md +11 -0
- package/templates/create/deckbuilder-2d/assets/ui/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/aura.config.json +28 -0
- package/templates/create/deckbuilder-2d/config/gameplay/deckbuilder.config.js +26 -0
- package/templates/create/deckbuilder-2d/content/cards/guard.card.js +19 -0
- package/templates/create/deckbuilder-2d/content/cards/spark.card.js +20 -0
- package/templates/create/deckbuilder-2d/content/cards/starter.deck.js +69 -0
- package/templates/create/deckbuilder-2d/content/cards/strike.card.js +19 -0
- package/templates/create/deckbuilder-2d/content/cards/survey.card.js +20 -0
- package/templates/create/deckbuilder-2d/content/encounters/training-battle.encounter.js +14 -0
- package/templates/create/deckbuilder-2d/content/encounters/training-battle.js +65 -0
- package/templates/create/deckbuilder-2d/content/enemies/training-automaton.enemy.js +48 -0
- package/templates/create/deckbuilder-2d/content/gameplay/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/content/registries/cards.registry.js +26 -0
- package/templates/create/deckbuilder-2d/content/registries/encounters.registry.js +20 -0
- package/templates/create/deckbuilder-2d/content/registries/enemies.registry.js +20 -0
- package/templates/create/deckbuilder-2d/content/registries/relics.registry.js +20 -0
- package/templates/create/deckbuilder-2d/content/relics/ember-charm.relic.js +18 -0
- package/templates/create/deckbuilder-2d/docs/design/loop.md +12 -0
- package/templates/create/deckbuilder-2d/prefabs/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/scenes/boot.scene.js +84 -0
- package/templates/create/deckbuilder-2d/scenes/gameplay.scene.js +641 -0
- package/templates/create/deckbuilder-2d/src/components/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/src/main.js +17 -0
- package/templates/create/deckbuilder-2d/src/runtime/capabilities.js +22 -0
- package/templates/create/deckbuilder-2d/src/shared/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/src/systems/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/tests/smoke/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/ui/hud.screen.js +80 -0
- package/templates/create/deckbuilder-2d/ui/pause.screen.js +146 -0
- package/templates/create/deckbuilder-2d/ui/settings.screen.js +342 -0
- package/templates/create/local-multiplayer/aura.config.json +41 -0
- package/templates/create/local-multiplayer/config/gameplay/local-multiplayer.config.js +26 -0
- package/templates/create/local-multiplayer/content/gameplay/room-layout.js +13 -0
- package/templates/create/local-multiplayer/content/registries/.gitkeep +1 -0
- package/templates/create/local-multiplayer/docs/design/loop.md +16 -0
- package/templates/create/local-multiplayer/prefabs/player.prefab.js +99 -0
- package/templates/create/local-multiplayer/scenes/boot.scene.js +12 -0
- package/templates/create/local-multiplayer/scenes/gameplay.scene.js +472 -0
- package/templates/create/local-multiplayer/src/main.js +17 -0
- package/templates/create/local-multiplayer/src/runtime/capabilities.js +28 -0
- package/templates/create/local-multiplayer/ui/hud.screen.js +65 -0
- package/templates/create/shared/src/runtime/project-inspector.js +105 -0
- package/templates/create/shared/src/runtime/scene-flow.js +290 -0
- package/templates/create/shared/src/runtime/screen-shell.js +222 -0
- package/templates/create/shared/src/runtime/ui-forms.js +209 -0
- package/templates/create/shared/src/runtime/ui-settings.js +237 -0
- package/templates/create/shared/src/runtime/ui-theme.js +352 -0
- package/templates/create/shared/src/starter-utils/adventure-objectives.js +102 -0
- package/templates/create/shared/src/starter-utils/animation-2d.js +337 -0
- package/templates/create/shared/src/starter-utils/avatar-3d.js +404 -0
- package/templates/create/shared/src/starter-utils/combat-feedback-2d.js +320 -0
- package/templates/create/shared/src/starter-utils/core.js +39 -3
- package/templates/create/shared/src/starter-utils/index.js +8 -2
- package/templates/create/shared/src/starter-utils/platformer-3d.js +34 -3
- package/templates/create/shared/src/starter-utils/triggers.js +662 -0
- package/templates/create/shared/src/starter-utils/tween-2d.js +615 -0
- package/templates/create/video-cutscene/assets/video/.gitkeep +0 -0
- package/templates/create/video-cutscene/aura.config.json +28 -0
- package/templates/create/video-cutscene/config/gameplay/.gitkeep +0 -0
- package/templates/create/video-cutscene/content/gameplay/.gitkeep +0 -0
- package/templates/create/video-cutscene/content/registries/.gitkeep +0 -0
- package/templates/create/video-cutscene/docs/design/loop.md +22 -0
- package/templates/create/video-cutscene/prefabs/.gitkeep +0 -0
- package/templates/create/video-cutscene/scenes/boot.scene.js +11 -0
- package/templates/create/video-cutscene/scenes/cutscene.scene.js +113 -0
- package/templates/create/video-cutscene/scenes/gameplay.scene.js +50 -0
- package/templates/create/video-cutscene/src/main.js +17 -0
- package/templates/create/video-cutscene/src/runtime/app.js +52 -0
- package/templates/create/video-cutscene/src/runtime/capabilities.js +35 -0
- package/templates/create/video-cutscene/src/runtime/state.js +13 -0
- package/templates/create/video-cutscene/ui/.gitkeep +0 -0
- package/templates/create-bin/play.js +1192 -0
- package/templates/make/README.md +46 -0
- package/templates/make/catalog.json +51 -0
- package/templates/make/component/files/{{MAKE_NAME}}.component.js +20 -0
- package/templates/make/component/manifest.json +9 -0
- package/templates/make/data/files/{{MAKE_NAME}}.json +14 -0
- package/templates/make/data/manifest.json +9 -0
- package/templates/make/material/files/{{MAKE_NAME}}.material.json +17 -0
- package/templates/make/material/manifest.json +9 -0
- package/templates/make/prefab/files/{{MAKE_NAME}}.prefab.js +20 -0
- package/templates/make/prefab/manifest.json +9 -0
- package/templates/make/scene/files/{{MAKE_NAME}}.scene.js +31 -0
- package/templates/make/scene/manifest.json +9 -0
- package/templates/make/shader/files/{{MAKE_NAME}}.shader.js +23 -0
- package/templates/make/shader/manifest.json +9 -0
- package/templates/make/system/files/{{MAKE_NAME}}.system.js +15 -0
- package/templates/make/system/manifest.json +9 -0
- package/templates/make/ui-screen/files/{{MAKE_NAME}}.screen.js +16 -0
- package/templates/make/ui-screen/files/{{MAKE_NAME}}.screen.json +23 -0
- package/templates/make/ui-screen/manifest.json +10 -0
- package/templates/make-starters/deckbuilder-2d/card/files/{{MAKE_NAME}}.card.js +22 -0
- package/templates/make-starters/deckbuilder-2d/card/manifest.json +9 -0
- package/templates/make-starters/deckbuilder-2d/catalog.json +34 -0
- package/templates/make-starters/deckbuilder-2d/encounter/files/{{MAKE_NAME}}.encounter.js +18 -0
- package/templates/make-starters/deckbuilder-2d/encounter/manifest.json +9 -0
- package/templates/make-starters/deckbuilder-2d/enemy/files/{{MAKE_NAME}}.enemy.js +28 -0
- package/templates/make-starters/deckbuilder-2d/enemy/manifest.json +9 -0
- package/templates/make-starters/deckbuilder-2d/relic/files/{{MAKE_NAME}}.relic.js +23 -0
- package/templates/make-starters/deckbuilder-2d/relic/manifest.json +9 -0
- package/templates/retro/platformer/README.md +10 -0
- package/templates/retro/platformer/assets/retro/assets.json +91 -0
- package/templates/retro/platformer/aura.config.json +7 -0
- package/templates/retro/platformer/package.json +5 -0
- package/templates/retro/platformer/src/main.js +40 -0
- package/templates/retro/puzzle-grid/README.md +10 -0
- package/templates/retro/puzzle-grid/assets/retro/assets.json +90 -0
- package/templates/retro/puzzle-grid/aura.config.json +7 -0
- package/templates/retro/puzzle-grid/package.json +5 -0
- package/templates/retro/puzzle-grid/src/main.js +29 -0
- package/templates/retro/tactics-grid/README.md +10 -0
- package/templates/retro/tactics-grid/assets/retro/assets.json +90 -0
- package/templates/retro/tactics-grid/aura.config.json +7 -0
- package/templates/retro/tactics-grid/package.json +5 -0
- package/templates/retro/tactics-grid/src/main.js +35 -0
- package/templates/retro/topdown-adventure/README.md +10 -0
- package/templates/retro/topdown-adventure/assets/retro/assets.json +95 -0
- package/templates/retro/topdown-adventure/aura.config.json +7 -0
- package/templates/retro/topdown-adventure/package.json +5 -0
- package/templates/retro/topdown-adventure/src/main.js +29 -0
- package/templates/skills/aurajs/SKILL.md +61 -5
|
@@ -0,0 +1,1449 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, relative, resolve } from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import {
|
|
5
|
+
PROJECT_MAIN_ENTRY_RELATIVE_PATH,
|
|
6
|
+
PROJECT_RUNTIME_APP_RELATIVE_PATH,
|
|
7
|
+
syncRegistryBackedRuntimeFiles,
|
|
8
|
+
} from './authored-runtime.mjs';
|
|
9
|
+
import {
|
|
10
|
+
listStarterContentRegistries,
|
|
11
|
+
validateStarterContentRegistries,
|
|
12
|
+
} from './starter-content-registry.mjs';
|
|
13
|
+
import { loadMakeCatalog } from './make-catalog.mjs';
|
|
14
|
+
|
|
15
|
+
export const PROJECT_REGISTRY_SCHEMA = 'aurajs.project-registry.v1';
|
|
16
|
+
export const PROJECT_EXPLAIN_SCHEMA = 'aurajs.project-explain.v1';
|
|
17
|
+
export const PROJECT_CHECK_SCHEMA = 'aurajs.project-check.v1';
|
|
18
|
+
export const PROJECT_CONTINUITY_SCHEMA = 'aurajs.project-continuity.v1';
|
|
19
|
+
export const CANONICAL_PREFAB_ROLES = Object.freeze(['custom', 'enemy', 'pickup', 'player', 'world']);
|
|
20
|
+
|
|
21
|
+
const RUNTIME_DIR = 'src/runtime';
|
|
22
|
+
export const PROJECT_REGISTRY_RELATIVE_PATH = `${RUNTIME_DIR}/project-registry.js`;
|
|
23
|
+
export const SCENE_REGISTRY_RELATIVE_PATH = `${RUNTIME_DIR}/scene-registry.js`;
|
|
24
|
+
const RUNTIME_BOOTSTRAP_FILES = [
|
|
25
|
+
PROJECT_MAIN_ENTRY_RELATIVE_PATH,
|
|
26
|
+
PROJECT_RUNTIME_APP_RELATIVE_PATH,
|
|
27
|
+
SCENE_REGISTRY_RELATIVE_PATH,
|
|
28
|
+
];
|
|
29
|
+
const SELF_WIRING_KINDS = new Set(['scene', 'ui-screen', 'prefab']);
|
|
30
|
+
const CANONICAL_PREFAB_ROLE_SET = new Set(CANONICAL_PREFAB_ROLES);
|
|
31
|
+
const POSIX_SEP = '/';
|
|
32
|
+
|
|
33
|
+
const BASE_PROJECT_CONFIG_FILES = ['config/gameplay/game.config.json'];
|
|
34
|
+
const BASE_PROJECT_CONTENT_FILES = [];
|
|
35
|
+
|
|
36
|
+
const STARTER_PROJECT_REGISTRY_PRESETS = {
|
|
37
|
+
blank: {
|
|
38
|
+
startSceneId: 'gameplay',
|
|
39
|
+
scenes: [
|
|
40
|
+
{ id: 'boot', path: 'scenes/boot.scene.js', exportName: 'createBootScene' },
|
|
41
|
+
{ id: 'gameplay', path: 'scenes/gameplay.scene.js', exportName: 'createGameplayScene' },
|
|
42
|
+
],
|
|
43
|
+
screens: [
|
|
44
|
+
{ id: 'hud', path: 'ui/hud.screen.js', exportName: 'default' },
|
|
45
|
+
],
|
|
46
|
+
prefabs: [
|
|
47
|
+
{ id: 'player', role: 'player', path: 'prefabs/player.prefab.js', exportName: 'default' },
|
|
48
|
+
],
|
|
49
|
+
configFiles: [],
|
|
50
|
+
contentFiles: [],
|
|
51
|
+
},
|
|
52
|
+
'2d-adventure': {
|
|
53
|
+
startSceneId: 'gameplay',
|
|
54
|
+
scenes: [
|
|
55
|
+
{ id: 'boot', path: 'scenes/boot.scene.js', exportName: 'createBootScene' },
|
|
56
|
+
{ id: 'gameplay', path: 'scenes/gameplay.scene.js', exportName: 'createGameplayScene' },
|
|
57
|
+
],
|
|
58
|
+
screens: [
|
|
59
|
+
{ id: 'hud', path: 'ui/hud.screen.js', exportName: 'default' },
|
|
60
|
+
],
|
|
61
|
+
prefabs: [
|
|
62
|
+
{ id: 'player', role: 'player', path: 'prefabs/player.prefab.js', exportName: 'default' },
|
|
63
|
+
{ id: 'relic', role: 'pickup', path: 'prefabs/relic.prefab.js', exportName: 'default' },
|
|
64
|
+
{ id: 'world', role: 'world', path: 'prefabs/world.prefab.js', exportName: 'default' },
|
|
65
|
+
],
|
|
66
|
+
configFiles: ['config/gameplay/adventure.config.js'],
|
|
67
|
+
contentFiles: ['content/gameplay/world.js'],
|
|
68
|
+
},
|
|
69
|
+
'2d-shooter': {
|
|
70
|
+
startSceneId: 'gameplay',
|
|
71
|
+
scenes: [
|
|
72
|
+
{ id: 'boot', path: 'scenes/boot.scene.js', exportName: 'createBootScene' },
|
|
73
|
+
{ id: 'gameplay', path: 'scenes/gameplay.scene.js', exportName: 'createGameplayScene' },
|
|
74
|
+
{ id: 'menu', path: 'scenes/menu.scene.js', exportName: 'createMenuScene' },
|
|
75
|
+
],
|
|
76
|
+
screens: [
|
|
77
|
+
{ id: 'hud', path: 'ui/hud.screen.js', exportName: 'default' },
|
|
78
|
+
{ id: 'pause', path: 'ui/pause.screen.js', exportName: 'default' },
|
|
79
|
+
{ id: 'settings', path: 'ui/settings.screen.js', exportName: 'default' },
|
|
80
|
+
{ id: 'title', path: 'ui/title.screen.js', exportName: 'default' },
|
|
81
|
+
],
|
|
82
|
+
prefabs: [
|
|
83
|
+
{ id: 'enemy-basic', role: 'enemy', path: 'prefabs/enemy-basic.prefab.js', exportName: 'default' },
|
|
84
|
+
{ id: 'enemies', role: 'enemy', path: 'prefabs/enemies.prefab.js', exportName: 'default' },
|
|
85
|
+
{ id: 'player', role: 'player', path: 'prefabs/player.prefab.js', exportName: 'default' },
|
|
86
|
+
{ id: 'projectiles', role: 'custom', path: 'prefabs/projectiles.prefab.js', exportName: 'default' },
|
|
87
|
+
],
|
|
88
|
+
configFiles: ['config/gameplay/shooter.config.js'],
|
|
89
|
+
contentFiles: ['content/gameplay/waves.json'],
|
|
90
|
+
},
|
|
91
|
+
'2d-survivor': {
|
|
92
|
+
startSceneId: 'gameplay',
|
|
93
|
+
scenes: [
|
|
94
|
+
{ id: 'boot', path: 'scenes/boot.scene.js', exportName: 'createBootScene' },
|
|
95
|
+
{ id: 'gameplay', path: 'scenes/gameplay.scene.js', exportName: 'createGameplayScene' },
|
|
96
|
+
{ id: 'menu', path: 'scenes/menu.scene.js', exportName: 'createMenuScene' },
|
|
97
|
+
],
|
|
98
|
+
screens: [
|
|
99
|
+
{ id: 'hud', path: 'ui/hud.screen.js', exportName: 'default' },
|
|
100
|
+
{ id: 'title', path: 'ui/title.screen.js', exportName: 'default' },
|
|
101
|
+
],
|
|
102
|
+
prefabs: [
|
|
103
|
+
{ id: 'enemies', role: 'enemy', path: 'prefabs/enemies.prefab.js', exportName: 'default' },
|
|
104
|
+
{ id: 'enemy-swarm', role: 'enemy', path: 'prefabs/enemy-swarm.prefab.js', exportName: 'default' },
|
|
105
|
+
{ id: 'player', role: 'player', path: 'prefabs/player.prefab.js', exportName: 'default' },
|
|
106
|
+
{ id: 'projectiles', role: 'custom', path: 'prefabs/projectiles.prefab.js', exportName: 'default' },
|
|
107
|
+
],
|
|
108
|
+
configFiles: ['config/gameplay/survivor.config.js'],
|
|
109
|
+
contentFiles: ['content/gameplay/spawn-zones.json'],
|
|
110
|
+
},
|
|
111
|
+
'3d-adventure': {
|
|
112
|
+
startSceneId: 'gameplay',
|
|
113
|
+
scenes: [
|
|
114
|
+
{ id: 'boot', path: 'scenes/boot.scene.js', exportName: 'createBootScene' },
|
|
115
|
+
{ id: 'gameplay', path: 'scenes/gameplay.scene.js', exportName: 'createGameplayScene' },
|
|
116
|
+
],
|
|
117
|
+
screens: [
|
|
118
|
+
{ id: 'hud', path: 'ui/hud.screen.js', exportName: 'default' },
|
|
119
|
+
{ id: 'pause', path: 'ui/pause.screen.js', exportName: 'default' },
|
|
120
|
+
],
|
|
121
|
+
prefabs: [
|
|
122
|
+
{ id: 'player', role: 'player', path: 'prefabs/player.prefab.js', exportName: 'default' },
|
|
123
|
+
{ id: 'relic', role: 'pickup', path: 'prefabs/relic.prefab.js', exportName: 'default' },
|
|
124
|
+
{ id: 'world', role: 'world', path: 'prefabs/world.prefab.js', exportName: 'default' },
|
|
125
|
+
],
|
|
126
|
+
configFiles: ['config/gameplay/adventure.config.js'],
|
|
127
|
+
contentFiles: ['content/gameplay/course.js'],
|
|
128
|
+
},
|
|
129
|
+
'3d-platformer': {
|
|
130
|
+
startSceneId: 'gameplay',
|
|
131
|
+
scenes: [
|
|
132
|
+
{ id: 'boot', path: 'scenes/boot.scene.js', exportName: 'createBootScene' },
|
|
133
|
+
{ id: 'checkpoint', path: 'scenes/checkpoint.scene.js', exportName: 'createCheckpointScene' },
|
|
134
|
+
{ id: 'gameplay', path: 'scenes/gameplay.scene.js', exportName: 'createGameplayScene' },
|
|
135
|
+
],
|
|
136
|
+
screens: [
|
|
137
|
+
{ id: 'hud', path: 'ui/hud.screen.js', exportName: 'default' },
|
|
138
|
+
{ id: 'pause', path: 'ui/pause.screen.js', exportName: 'default' },
|
|
139
|
+
{ id: 'settings', path: 'ui/settings.screen.js', exportName: 'default' },
|
|
140
|
+
],
|
|
141
|
+
prefabs: [
|
|
142
|
+
{ id: 'checkpoint', role: 'world', path: 'prefabs/checkpoint.prefab.js', exportName: 'default' },
|
|
143
|
+
{ id: 'player', role: 'player', path: 'prefabs/player.prefab.js', exportName: 'default' },
|
|
144
|
+
{ id: 'world', role: 'world', path: 'prefabs/world.prefab.js', exportName: 'default' },
|
|
145
|
+
],
|
|
146
|
+
configFiles: [],
|
|
147
|
+
contentFiles: ['content/gameplay/checkpoints.json', 'content/gameplay/course.js'],
|
|
148
|
+
},
|
|
149
|
+
'3d-collectathon': {
|
|
150
|
+
startSceneId: 'gameplay',
|
|
151
|
+
scenes: [
|
|
152
|
+
{ id: 'boot', path: 'scenes/boot.scene.js', exportName: 'createBootScene' },
|
|
153
|
+
{ id: 'checkpoint', path: 'scenes/checkpoint.scene.js', exportName: 'createCheckpointScene' },
|
|
154
|
+
{ id: 'gameplay', path: 'scenes/gameplay.scene.js', exportName: 'createGameplayScene' },
|
|
155
|
+
],
|
|
156
|
+
screens: [
|
|
157
|
+
{ id: 'hud', path: 'ui/hud.screen.js', exportName: 'default' },
|
|
158
|
+
{ id: 'pause', path: 'ui/pause.screen.js', exportName: 'default' },
|
|
159
|
+
],
|
|
160
|
+
prefabs: [
|
|
161
|
+
{ id: 'collectible', role: 'pickup', path: 'prefabs/collectible.prefab.js', exportName: 'default' },
|
|
162
|
+
{ id: 'player', role: 'player', path: 'prefabs/player.prefab.js', exportName: 'default' },
|
|
163
|
+
{ id: 'world', role: 'world', path: 'prefabs/world.prefab.js', exportName: 'default' },
|
|
164
|
+
],
|
|
165
|
+
configFiles: [],
|
|
166
|
+
contentFiles: ['content/gameplay/collectibles.json', 'content/gameplay/course.js'],
|
|
167
|
+
},
|
|
168
|
+
'local-multiplayer': {
|
|
169
|
+
startSceneId: 'gameplay',
|
|
170
|
+
scenes: [
|
|
171
|
+
{ id: 'boot', path: 'scenes/boot.scene.js', exportName: 'createBootScene' },
|
|
172
|
+
{ id: 'gameplay', path: 'scenes/gameplay.scene.js', exportName: 'createGameplayScene' },
|
|
173
|
+
],
|
|
174
|
+
screens: [
|
|
175
|
+
{ id: 'hud', path: 'ui/hud.screen.js', exportName: 'default' },
|
|
176
|
+
],
|
|
177
|
+
prefabs: [
|
|
178
|
+
{ id: 'player', role: 'player', path: 'prefabs/player.prefab.js', exportName: 'default' },
|
|
179
|
+
],
|
|
180
|
+
configFiles: ['config/gameplay/local-multiplayer.config.js'],
|
|
181
|
+
contentFiles: ['content/gameplay/room-layout.js'],
|
|
182
|
+
},
|
|
183
|
+
'deckbuilder-2d': {
|
|
184
|
+
startSceneId: 'boot',
|
|
185
|
+
scenes: [
|
|
186
|
+
{ id: 'boot', path: 'scenes/boot.scene.js', exportName: 'createBootScene' },
|
|
187
|
+
{ id: 'gameplay', path: 'scenes/gameplay.scene.js', exportName: 'createGameplayScene' },
|
|
188
|
+
],
|
|
189
|
+
screens: [
|
|
190
|
+
{ id: 'hud', path: 'ui/hud.screen.js', exportName: 'default' },
|
|
191
|
+
{ id: 'pause', path: 'ui/pause.screen.js', exportName: 'default' },
|
|
192
|
+
{ id: 'settings', path: 'ui/settings.screen.js', exportName: 'default' },
|
|
193
|
+
],
|
|
194
|
+
prefabs: [],
|
|
195
|
+
configFiles: [
|
|
196
|
+
'config/gameplay/deckbuilder.config.js',
|
|
197
|
+
],
|
|
198
|
+
contentFiles: [
|
|
199
|
+
'content/cards/guard.card.js',
|
|
200
|
+
'content/cards/spark.card.js',
|
|
201
|
+
'content/cards/strike.card.js',
|
|
202
|
+
'content/cards/survey.card.js',
|
|
203
|
+
'content/cards/starter.deck.js',
|
|
204
|
+
'content/enemies/training-automaton.enemy.js',
|
|
205
|
+
'content/relics/ember-charm.relic.js',
|
|
206
|
+
'content/encounters/training-battle.encounter.js',
|
|
207
|
+
'content/encounters/training-battle.js',
|
|
208
|
+
'content/registries/cards.registry.js',
|
|
209
|
+
'content/registries/enemies.registry.js',
|
|
210
|
+
'content/registries/relics.registry.js',
|
|
211
|
+
'content/registries/encounters.registry.js',
|
|
212
|
+
],
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export function isSelfWiringMakeKind(kind) {
|
|
217
|
+
return SELF_WIRING_KINDS.has(String(kind || '').trim());
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function getProjectRegistryRelativePath() {
|
|
221
|
+
return PROJECT_REGISTRY_RELATIVE_PATH;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function getSceneRegistryRelativePath() {
|
|
225
|
+
return SCENE_REGISTRY_RELATIVE_PATH;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function createScaffoldProjectRegistryModel({ projectRoot, template, projectTitle }) {
|
|
229
|
+
const normalizedTemplate = String(template || '').trim() || 'blank';
|
|
230
|
+
const preset = STARTER_PROJECT_REGISTRY_PRESETS[normalizedTemplate];
|
|
231
|
+
if (!preset) {
|
|
232
|
+
return createEmptyProjectRegistryModel({
|
|
233
|
+
template: normalizedTemplate,
|
|
234
|
+
projectTitle,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return normalizeProjectRegistryData({
|
|
239
|
+
schema: PROJECT_REGISTRY_SCHEMA,
|
|
240
|
+
template: normalizedTemplate,
|
|
241
|
+
projectTitle,
|
|
242
|
+
startSceneId: preset.startSceneId,
|
|
243
|
+
scenes: preset.scenes,
|
|
244
|
+
screens: preset.screens,
|
|
245
|
+
prefabs: preset.prefabs,
|
|
246
|
+
configFiles: [...BASE_PROJECT_CONFIG_FILES, ...(preset.configFiles || [])],
|
|
247
|
+
contentFiles: [...BASE_PROJECT_CONTENT_FILES, ...(preset.contentFiles || [])],
|
|
248
|
+
}, { projectRoot, allowMissingStartScene: true });
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function buildStarterRegistryEntries(template) {
|
|
252
|
+
const preset = STARTER_PROJECT_REGISTRY_PRESETS[String(template || '').trim()];
|
|
253
|
+
if (!preset) {
|
|
254
|
+
return {
|
|
255
|
+
startSceneId: null,
|
|
256
|
+
scenes: [],
|
|
257
|
+
screens: [],
|
|
258
|
+
prefabs: [],
|
|
259
|
+
configFiles: [...BASE_PROJECT_CONFIG_FILES],
|
|
260
|
+
contentFiles: [...BASE_PROJECT_CONTENT_FILES],
|
|
261
|
+
dataFiles: [...BASE_PROJECT_CONFIG_FILES, ...BASE_PROJECT_CONTENT_FILES],
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
const mergedConfigFiles = [...BASE_PROJECT_CONFIG_FILES, ...(preset.configFiles || [])];
|
|
265
|
+
const mergedContentFiles = [...BASE_PROJECT_CONTENT_FILES, ...(preset.contentFiles || [])];
|
|
266
|
+
return {
|
|
267
|
+
startSceneId: preset.startSceneId,
|
|
268
|
+
scenes: preset.scenes,
|
|
269
|
+
screens: preset.screens,
|
|
270
|
+
prefabs: preset.prefabs,
|
|
271
|
+
configFiles: mergedConfigFiles,
|
|
272
|
+
contentFiles: mergedContentFiles,
|
|
273
|
+
dataFiles: [...mergedConfigFiles, ...mergedContentFiles],
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function inferPrefabRole(name) {
|
|
278
|
+
const normalized = String(name || '').trim().toLowerCase();
|
|
279
|
+
if (!normalized) return 'custom';
|
|
280
|
+
if (normalized === 'player' || normalized.endsWith('-player') || normalized.startsWith('player-')) {
|
|
281
|
+
return 'player';
|
|
282
|
+
}
|
|
283
|
+
if (normalized.includes('enemy') || normalized.includes('foe') || normalized.includes('boss')) {
|
|
284
|
+
return 'enemy';
|
|
285
|
+
}
|
|
286
|
+
if (/(pickup|collect|coin|orb|loot|reward)/.test(normalized)) {
|
|
287
|
+
return 'pickup';
|
|
288
|
+
}
|
|
289
|
+
if (/(world|checkpoint|terrain|level|stage|map|environment)/.test(normalized)) {
|
|
290
|
+
return 'world';
|
|
291
|
+
}
|
|
292
|
+
return 'custom';
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function normalizePrefabRole(role, { fallback = null } = {}) {
|
|
296
|
+
const normalized = String(role || '').trim().toLowerCase();
|
|
297
|
+
if (!normalized) {
|
|
298
|
+
return fallback;
|
|
299
|
+
}
|
|
300
|
+
if (!CANONICAL_PREFAB_ROLE_SET.has(normalized)) {
|
|
301
|
+
throw new Error(`Invalid prefab role "${role}". Expected one of: ${CANONICAL_PREFAB_ROLES.join(', ')}.`);
|
|
302
|
+
}
|
|
303
|
+
return normalized;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export function createStarterProjectRegistry({
|
|
307
|
+
template,
|
|
308
|
+
projectTitle,
|
|
309
|
+
startSceneId,
|
|
310
|
+
scenes,
|
|
311
|
+
screens,
|
|
312
|
+
prefabs,
|
|
313
|
+
configFiles,
|
|
314
|
+
contentFiles,
|
|
315
|
+
dataFiles,
|
|
316
|
+
}) {
|
|
317
|
+
const ownedFiles = normalizeOwnedFiles({
|
|
318
|
+
configFiles,
|
|
319
|
+
contentFiles,
|
|
320
|
+
dataFiles,
|
|
321
|
+
});
|
|
322
|
+
return {
|
|
323
|
+
schema: PROJECT_REGISTRY_SCHEMA,
|
|
324
|
+
template: String(template || 'blank').trim() || 'blank',
|
|
325
|
+
projectTitle: String(projectTitle || 'AuraJS Game').trim() || 'AuraJS Game',
|
|
326
|
+
startSceneId: startSceneId == null ? null : String(startSceneId).trim(),
|
|
327
|
+
scenes: Array.isArray(scenes) ? scenes : [],
|
|
328
|
+
screens: Array.isArray(screens) ? screens : [],
|
|
329
|
+
prefabs: Array.isArray(prefabs) ? prefabs : [],
|
|
330
|
+
configFiles: ownedFiles.configFiles,
|
|
331
|
+
contentFiles: ownedFiles.contentFiles,
|
|
332
|
+
dataFiles: ownedFiles.dataFiles,
|
|
333
|
+
continuity: createProjectContinuityModel({
|
|
334
|
+
startSceneId,
|
|
335
|
+
}),
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function writeProjectRegistryFiles({ projectRoot, registry }) {
|
|
340
|
+
const registryPath = resolve(projectRoot, PROJECT_REGISTRY_RELATIVE_PATH);
|
|
341
|
+
const adapterPath = resolve(projectRoot, SCENE_REGISTRY_RELATIVE_PATH);
|
|
342
|
+
const normalized = normalizeProjectRegistryData(registry, { projectRoot, allowMissingStartScene: true });
|
|
343
|
+
|
|
344
|
+
mkdirSync(dirname(registryPath), { recursive: true });
|
|
345
|
+
writeFileSync(registryPath, renderProjectRegistryModule(normalized, { projectRoot, registryPath }), 'utf8');
|
|
346
|
+
writeFileSync(
|
|
347
|
+
adapterPath,
|
|
348
|
+
renderSceneRegistryAdapterModule({
|
|
349
|
+
projectRoot,
|
|
350
|
+
sceneRegistryPath: adapterPath,
|
|
351
|
+
registry: normalized,
|
|
352
|
+
}),
|
|
353
|
+
'utf8',
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export function renderProjectRegistryModule(model, { projectRoot = process.cwd(), registryPath = null } = {}) {
|
|
358
|
+
const normalized = normalizeProjectRegistryData(model, {
|
|
359
|
+
projectRoot,
|
|
360
|
+
allowMissingStartScene: true,
|
|
361
|
+
});
|
|
362
|
+
const modulePath = registryPath || resolve(projectRoot, PROJECT_REGISTRY_RELATIVE_PATH);
|
|
363
|
+
const imports = [];
|
|
364
|
+
const screenLines = [];
|
|
365
|
+
|
|
366
|
+
for (const [index, entry] of normalized.screens.entries()) {
|
|
367
|
+
const binding = `screenModule${index + 1}`;
|
|
368
|
+
const importPath = toImportPath(relative(dirname(modulePath), resolve(projectRoot, entry.path)));
|
|
369
|
+
if (entry.exportName === 'default') {
|
|
370
|
+
imports.push(`import ${binding} from ${JSON.stringify(importPath)};`);
|
|
371
|
+
} else {
|
|
372
|
+
imports.push(`import { ${entry.exportName} as ${binding} } from ${JSON.stringify(importPath)};`);
|
|
373
|
+
}
|
|
374
|
+
screenLines.push(` ${JSON.stringify(entry.id)}: ${binding},`);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const importBlock = imports.length > 0 ? `${imports.join('\n')}\n\n` : '';
|
|
378
|
+
const screenFactoriesBlock = screenLines.length > 0
|
|
379
|
+
? `const screenModules = {\n${screenLines.join('\n')}\n};`
|
|
380
|
+
: 'const screenModules = {};';
|
|
381
|
+
|
|
382
|
+
return `${importBlock}const projectRegistry = ${serializeValue({
|
|
383
|
+
schema: PROJECT_REGISTRY_SCHEMA,
|
|
384
|
+
template: normalized.template,
|
|
385
|
+
projectTitle: normalized.projectTitle,
|
|
386
|
+
startSceneId: normalized.startSceneId,
|
|
387
|
+
scenes: normalized.scenes,
|
|
388
|
+
screens: normalized.screens,
|
|
389
|
+
prefabs: normalized.prefabs,
|
|
390
|
+
configFiles: normalized.configFiles,
|
|
391
|
+
contentFiles: normalized.contentFiles,
|
|
392
|
+
continuity: normalized.continuity,
|
|
393
|
+
}, 0)};
|
|
394
|
+
|
|
395
|
+
${screenFactoriesBlock}
|
|
396
|
+
|
|
397
|
+
Object.defineProperty(projectRegistry, 'dataFiles', {
|
|
398
|
+
enumerable: false,
|
|
399
|
+
get() {
|
|
400
|
+
return [...projectRegistry.configFiles, ...projectRegistry.contentFiles];
|
|
401
|
+
},
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
export function resolveProjectScreen(screenId) {
|
|
405
|
+
return screenModules[String(screenId || '')] || null;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export function createProjectRegistry() {
|
|
409
|
+
return projectRegistry;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export default projectRegistry;
|
|
413
|
+
`;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export function renderSceneRegistryAdapterModule({ projectRoot, sceneRegistryPath, registry }) {
|
|
417
|
+
const normalized = normalizeProjectRegistryData(registry, { projectRoot, allowMissingStartScene: true });
|
|
418
|
+
const imports = [`import projectRegistry from './project-registry.js';`];
|
|
419
|
+
const lines = [];
|
|
420
|
+
|
|
421
|
+
for (const [index, entry] of normalized.scenes.entries()) {
|
|
422
|
+
const binding = `sceneFactory${index + 1}`;
|
|
423
|
+
const importPath = toImportPath(relative(dirname(sceneRegistryPath), resolve(projectRoot, entry.path)));
|
|
424
|
+
if (entry.exportName === 'default') {
|
|
425
|
+
imports.push(`import ${binding} from ${JSON.stringify(importPath)};`);
|
|
426
|
+
} else {
|
|
427
|
+
imports.push(`import { ${entry.exportName} as ${binding} } from ${JSON.stringify(importPath)};`);
|
|
428
|
+
}
|
|
429
|
+
lines.push(` ${JSON.stringify(entry.id)}: ${binding},`);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return `${imports.join('\n')}
|
|
433
|
+
|
|
434
|
+
const sceneFactories = {
|
|
435
|
+
${lines.join('\n')}
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
export function createSceneRegistry(context = {}) {
|
|
439
|
+
const scenes = {};
|
|
440
|
+
for (const entry of projectRegistry.scenes || []) {
|
|
441
|
+
const resolved = sceneFactories[entry.id];
|
|
442
|
+
if (typeof resolved === 'function') {
|
|
443
|
+
scenes[entry.id] = resolved(context);
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
if (typeof resolved !== 'undefined' && resolved !== null) {
|
|
447
|
+
scenes[entry.id] = resolved;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return {
|
|
452
|
+
defaultSceneId: projectRegistry.startSceneId || null,
|
|
453
|
+
scenes,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
`;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export async function selfWireGeneratedProjectFiles({ projectRoot, kind, name, title, role = null, files = [] }) {
|
|
460
|
+
const normalizedKind = String(kind || '').trim();
|
|
461
|
+
const normalizedFiles = Array.isArray(files)
|
|
462
|
+
? files.map((entry) => normalizeProjectRelativePath(entry)).filter(Boolean)
|
|
463
|
+
: [];
|
|
464
|
+
if (!isSelfWiringMakeKind(normalizedKind) && normalizedKind !== 'config' && normalizedKind !== 'content') {
|
|
465
|
+
return { wired: false, registryCreated: false, files: [] };
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
assertAuraProjectRoot(projectRoot);
|
|
469
|
+
|
|
470
|
+
const template = loadProjectTemplate(projectRoot);
|
|
471
|
+
const projectTitle = loadProjectTitle(projectRoot);
|
|
472
|
+
const registryPath = resolve(projectRoot, PROJECT_REGISTRY_RELATIVE_PATH);
|
|
473
|
+
const adapterPath = resolve(projectRoot, SCENE_REGISTRY_RELATIVE_PATH);
|
|
474
|
+
|
|
475
|
+
let registryCreated = false;
|
|
476
|
+
let model = null;
|
|
477
|
+
if (existsSync(registryPath)) {
|
|
478
|
+
model = await loadProjectRegistryModel({ projectRoot });
|
|
479
|
+
} else {
|
|
480
|
+
model = shouldSeedStarterRegistry({ projectRoot, template })
|
|
481
|
+
? createScaffoldProjectRegistryModel({ projectRoot, template, projectTitle })
|
|
482
|
+
: createEmptyProjectRegistryModel({ template, projectTitle });
|
|
483
|
+
registryCreated = true;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (normalizedKind === 'scene') {
|
|
487
|
+
upsertEntry(model.scenes, {
|
|
488
|
+
id: String(name),
|
|
489
|
+
path: `scenes/${name}.scene.js`,
|
|
490
|
+
exportName: 'default',
|
|
491
|
+
});
|
|
492
|
+
if (!model.startSceneId) {
|
|
493
|
+
model.startSceneId = String(name);
|
|
494
|
+
}
|
|
495
|
+
} else if (normalizedKind === 'ui-screen') {
|
|
496
|
+
upsertEntry(model.screens, {
|
|
497
|
+
id: String(name),
|
|
498
|
+
path: `ui/${name}.screen.js`,
|
|
499
|
+
exportName: 'default',
|
|
500
|
+
});
|
|
501
|
+
} else if (normalizedKind === 'prefab') {
|
|
502
|
+
upsertEntry(model.prefabs, {
|
|
503
|
+
id: String(name),
|
|
504
|
+
role: normalizePrefabRole(role, { fallback: 'custom' }),
|
|
505
|
+
path: `prefabs/${name}.prefab.js`,
|
|
506
|
+
exportName: 'default',
|
|
507
|
+
});
|
|
508
|
+
} else if (normalizedKind === 'config' || normalizedKind === 'content') {
|
|
509
|
+
const targetFiles = normalizedKind === 'config'
|
|
510
|
+
? model.configFiles
|
|
511
|
+
: model.contentFiles;
|
|
512
|
+
const fallbackFile = normalizedKind === 'config'
|
|
513
|
+
? `config/gameplay/${name}.json`
|
|
514
|
+
: `content/gameplay/${name}.json`;
|
|
515
|
+
for (const relativePath of normalizedFiles.length > 0 ? normalizedFiles : [fallbackFile]) {
|
|
516
|
+
upsertOwnedFile(targetFiles, relativePath);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
model = normalizeProjectRegistryData(model, { projectRoot, allowMissingStartScene: true });
|
|
521
|
+
|
|
522
|
+
mkdirSync(dirname(registryPath), { recursive: true });
|
|
523
|
+
writeProjectRegistryFiles({
|
|
524
|
+
projectRoot,
|
|
525
|
+
registry: model,
|
|
526
|
+
});
|
|
527
|
+
const runtimeSync = syncRegistryBackedRuntimeFiles({
|
|
528
|
+
projectRoot,
|
|
529
|
+
template,
|
|
530
|
+
projectTitle,
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
return {
|
|
534
|
+
wired: true,
|
|
535
|
+
registryCreated,
|
|
536
|
+
files: [
|
|
537
|
+
toProjectRelativePath(projectRoot, registryPath),
|
|
538
|
+
toProjectRelativePath(projectRoot, adapterPath),
|
|
539
|
+
...(Array.isArray(runtimeSync.files) ? runtimeSync.files : []),
|
|
540
|
+
],
|
|
541
|
+
title: title || null,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
export async function explainProject({ projectRoot, catalogRoot = null }) {
|
|
546
|
+
assertAuraProjectRoot(projectRoot);
|
|
547
|
+
|
|
548
|
+
const template = loadProjectTemplate(projectRoot);
|
|
549
|
+
const registryPath = resolve(projectRoot, PROJECT_REGISTRY_RELATIVE_PATH);
|
|
550
|
+
const mode = existsSync(registryPath) ? 'registry' : 'bootstrap-only';
|
|
551
|
+
const runtimeBootstrap = RUNTIME_BOOTSTRAP_FILES.filter((relativePath) => existsSync(resolve(projectRoot, relativePath)));
|
|
552
|
+
const starterContentRegistries = await listStarterContentRegistries({ projectRoot, catalogRoot });
|
|
553
|
+
const availableMakeKinds = loadMakeCatalog({ projectRoot }).map((entry) => ({
|
|
554
|
+
id: entry.id,
|
|
555
|
+
owner: entry.owner,
|
|
556
|
+
scope: entry.scope,
|
|
557
|
+
}));
|
|
558
|
+
|
|
559
|
+
if (mode === 'bootstrap-only') {
|
|
560
|
+
return {
|
|
561
|
+
schema: PROJECT_EXPLAIN_SCHEMA,
|
|
562
|
+
mode,
|
|
563
|
+
template,
|
|
564
|
+
projectRoot,
|
|
565
|
+
startingSceneId: null,
|
|
566
|
+
sourceOfTruth: {
|
|
567
|
+
projectRegistry: null,
|
|
568
|
+
primary: 'src/main.js',
|
|
569
|
+
runtimeBootstrap: runtimeBootstrap.length > 0 ? runtimeBootstrap : ['src/main.js'],
|
|
570
|
+
},
|
|
571
|
+
scenes: [],
|
|
572
|
+
screens: [],
|
|
573
|
+
prefabs: [],
|
|
574
|
+
configFiles: [],
|
|
575
|
+
contentFiles: [],
|
|
576
|
+
dataFiles: [],
|
|
577
|
+
continuity: createProjectContinuityModel(),
|
|
578
|
+
starterContentRegistries,
|
|
579
|
+
availableMakeKinds,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const registry = await loadProjectRegistryModel({ projectRoot });
|
|
584
|
+
return {
|
|
585
|
+
schema: PROJECT_EXPLAIN_SCHEMA,
|
|
586
|
+
mode,
|
|
587
|
+
template: registry.template || template,
|
|
588
|
+
projectRoot,
|
|
589
|
+
startingSceneId: registry.startSceneId || null,
|
|
590
|
+
sourceOfTruth: {
|
|
591
|
+
projectRegistry: PROJECT_REGISTRY_RELATIVE_PATH,
|
|
592
|
+
primary: PROJECT_REGISTRY_RELATIVE_PATH,
|
|
593
|
+
runtimeBootstrap,
|
|
594
|
+
},
|
|
595
|
+
scenes: registry.scenes.map((entry) => ({ id: entry.id, path: entry.path })),
|
|
596
|
+
screens: registry.screens.map((entry) => ({ id: entry.id, path: entry.path })),
|
|
597
|
+
prefabs: registry.prefabs.map((entry) => ({
|
|
598
|
+
id: entry.id,
|
|
599
|
+
role: entry.role || null,
|
|
600
|
+
path: entry.path,
|
|
601
|
+
})),
|
|
602
|
+
configFiles: [...registry.configFiles],
|
|
603
|
+
contentFiles: [...registry.contentFiles],
|
|
604
|
+
dataFiles: [...registry.dataFiles],
|
|
605
|
+
continuity: registry.continuity,
|
|
606
|
+
starterContentRegistries,
|
|
607
|
+
availableMakeKinds,
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
export async function checkProject({ projectRoot, catalogRoot = null }) {
|
|
612
|
+
const errors = [];
|
|
613
|
+
const warnings = [];
|
|
614
|
+
const template = loadProjectTemplate(projectRoot);
|
|
615
|
+
const registryPath = resolve(projectRoot, PROJECT_REGISTRY_RELATIVE_PATH);
|
|
616
|
+
const adapterPath = resolve(projectRoot, SCENE_REGISTRY_RELATIVE_PATH);
|
|
617
|
+
|
|
618
|
+
if (!existsSync(resolve(projectRoot, 'aura.config.json'))) {
|
|
619
|
+
return buildProjectCheckResult({
|
|
620
|
+
mode: 'invalid',
|
|
621
|
+
ok: false,
|
|
622
|
+
reasonCode: 'project_root_missing',
|
|
623
|
+
errors: [{ reasonCode: 'project_root_missing', message: 'Missing aura.config.json in the current directory.' }],
|
|
624
|
+
warnings,
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (!existsSync(registryPath)) {
|
|
629
|
+
if (template === 'blank' && !hasAuthoredProjectFiles(projectRoot)) {
|
|
630
|
+
return buildProjectCheckResult({
|
|
631
|
+
mode: 'bootstrap-only',
|
|
632
|
+
ok: true,
|
|
633
|
+
reasonCode: 'bootstrap_only_project_ok',
|
|
634
|
+
errors,
|
|
635
|
+
warnings,
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
return buildProjectCheckResult({
|
|
639
|
+
mode: 'registry',
|
|
640
|
+
ok: false,
|
|
641
|
+
reasonCode: 'project_registry_missing',
|
|
642
|
+
errors: [{ reasonCode: 'project_registry_missing', message: 'Missing src/runtime/project-registry.js.' }],
|
|
643
|
+
warnings,
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
let registry = null;
|
|
648
|
+
try {
|
|
649
|
+
registry = await loadProjectRegistryModel({ projectRoot });
|
|
650
|
+
} catch (error) {
|
|
651
|
+
return buildProjectCheckResult({
|
|
652
|
+
mode: 'registry',
|
|
653
|
+
ok: false,
|
|
654
|
+
reasonCode: 'project_registry_invalid',
|
|
655
|
+
errors: [{
|
|
656
|
+
reasonCode: 'project_registry_invalid',
|
|
657
|
+
message: error instanceof Error ? error.message : String(error),
|
|
658
|
+
path: PROJECT_REGISTRY_RELATIVE_PATH,
|
|
659
|
+
}],
|
|
660
|
+
warnings,
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
pushDuplicateErrors(errors, registry.scenes, 'scene');
|
|
665
|
+
pushDuplicateErrors(errors, registry.screens, 'screen');
|
|
666
|
+
pushDuplicateErrors(errors, registry.prefabs, 'prefab');
|
|
667
|
+
|
|
668
|
+
if (registry.scenes.length > 0 && (!registry.startSceneId || !registry.scenes.some((entry) => entry.id === registry.startSceneId))) {
|
|
669
|
+
errors.push({
|
|
670
|
+
reasonCode: 'project_start_scene_missing',
|
|
671
|
+
message: 'Starting scene id is missing or is not registered in project-registry.js.',
|
|
672
|
+
path: PROJECT_REGISTRY_RELATIVE_PATH,
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
pushMissingPathErrors(errors, registry.scenes, projectRoot, 'project_scene_path_missing');
|
|
677
|
+
pushMissingPathErrors(errors, registry.screens, projectRoot, 'project_screen_path_missing');
|
|
678
|
+
pushMissingPathErrors(errors, registry.prefabs, projectRoot, 'project_prefab_path_missing');
|
|
679
|
+
pushMissingOwnedFileErrors(errors, registry.configFiles, projectRoot, 'project_config_path_missing');
|
|
680
|
+
pushMissingOwnedFileErrors(errors, registry.contentFiles, projectRoot, 'project_content_path_missing');
|
|
681
|
+
|
|
682
|
+
const expectedAdapter = renderSceneRegistryAdapterModule({
|
|
683
|
+
projectRoot,
|
|
684
|
+
sceneRegistryPath: adapterPath,
|
|
685
|
+
registry,
|
|
686
|
+
});
|
|
687
|
+
if (!existsSync(adapterPath) || readFileSync(adapterPath, 'utf8') !== expectedAdapter) {
|
|
688
|
+
errors.push({
|
|
689
|
+
reasonCode: 'project_registry_adapter_drift',
|
|
690
|
+
message: 'src/runtime/scene-registry.js is missing or has drifted from the canonical project-registry adapter.',
|
|
691
|
+
path: SCENE_REGISTRY_RELATIVE_PATH,
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
await pushImportErrors(errors, registry.scenes, projectRoot);
|
|
696
|
+
await pushImportErrors(errors, registry.screens, projectRoot);
|
|
697
|
+
await pushPrefabContractErrors(errors, registry.prefabs, projectRoot);
|
|
698
|
+
const starterContentValidation = await validateStarterContentRegistries({ projectRoot, catalogRoot });
|
|
699
|
+
errors.push(...starterContentValidation.errors);
|
|
700
|
+
|
|
701
|
+
return buildProjectCheckResult({
|
|
702
|
+
mode: 'registry',
|
|
703
|
+
ok: errors.length === 0,
|
|
704
|
+
reasonCode: errors.length === 0 ? 'project_check_ok' : errors[0].reasonCode,
|
|
705
|
+
errors,
|
|
706
|
+
warnings,
|
|
707
|
+
starterContentRegistries: starterContentValidation.registries,
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
export async function registerGeneratedProjectFiles({ projectRoot, kind, name, role = null, files = [] }) {
|
|
712
|
+
const result = await selfWireGeneratedProjectFiles({
|
|
713
|
+
projectRoot,
|
|
714
|
+
kind,
|
|
715
|
+
name,
|
|
716
|
+
title: name,
|
|
717
|
+
role,
|
|
718
|
+
files,
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
return {
|
|
722
|
+
changed: result.wired === true,
|
|
723
|
+
registryCreated: result.registryCreated === true,
|
|
724
|
+
registryPath: result.wired ? PROJECT_REGISTRY_RELATIVE_PATH : null,
|
|
725
|
+
sceneRegistryPath: result.wired ? SCENE_REGISTRY_RELATIVE_PATH : null,
|
|
726
|
+
files: Array.isArray(result.files) ? result.files : [],
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
export async function runProjectExplain({ projectRoot, json = false }) {
|
|
731
|
+
const payload = normalizeExplainPayload(await explainProject({ projectRoot }));
|
|
732
|
+
return {
|
|
733
|
+
exitCode: 0,
|
|
734
|
+
text: json ? `${JSON.stringify(payload, null, 2)}\n` : formatExplainReport(payload),
|
|
735
|
+
payload,
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
export async function runProjectCheck({ projectRoot, json = false }) {
|
|
740
|
+
const payload = normalizeCheckPayload({ projectRoot, checked: await checkProject({ projectRoot }) });
|
|
741
|
+
return {
|
|
742
|
+
exitCode: payload.ok ? 0 : 2,
|
|
743
|
+
text: json ? `${JSON.stringify(payload, null, 2)}\n` : formatCheckReport(payload),
|
|
744
|
+
payload,
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
async function loadProjectRegistryModel({ projectRoot }) {
|
|
749
|
+
const registryPath = resolve(projectRoot, PROJECT_REGISTRY_RELATIVE_PATH);
|
|
750
|
+
if (!existsSync(registryPath)) {
|
|
751
|
+
throw new Error('Missing src/runtime/project-registry.js.');
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const imported = await import(`${pathToFileURL(registryPath).href}?t=${Date.now()}-${Math.random().toString(16).slice(2)}`);
|
|
755
|
+
return normalizeProjectRegistryData(imported.default, { projectRoot });
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function normalizeProjectRegistryData(data, { projectRoot, allowMissingStartScene = false }) {
|
|
759
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
760
|
+
throw new Error('Project registry must default-export an object.');
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const normalized = {
|
|
764
|
+
schema: typeof data.schema === 'string' ? data.schema : PROJECT_REGISTRY_SCHEMA,
|
|
765
|
+
template: typeof data.template === 'string' && data.template.trim().length > 0 ? data.template.trim() : loadProjectTemplate(projectRoot),
|
|
766
|
+
projectTitle: typeof data.projectTitle === 'string' && data.projectTitle.trim().length > 0 ? data.projectTitle.trim() : loadProjectTitle(projectRoot),
|
|
767
|
+
startSceneId: data.startSceneId == null ? null : String(data.startSceneId).trim(),
|
|
768
|
+
scenes: normalizeRegistryEntries(data.scenes, { kind: 'scene' }),
|
|
769
|
+
screens: normalizeRegistryEntries(data.screens, { kind: 'screen' }),
|
|
770
|
+
prefabs: normalizeRegistryEntries(data.prefabs, { kind: 'prefab' }),
|
|
771
|
+
...normalizeOwnedFiles({
|
|
772
|
+
configFiles: data.configFiles,
|
|
773
|
+
contentFiles: data.contentFiles,
|
|
774
|
+
dataFiles: data.dataFiles,
|
|
775
|
+
}),
|
|
776
|
+
projectRoot: resolve(projectRoot),
|
|
777
|
+
};
|
|
778
|
+
normalized.continuity = createProjectContinuityModel({
|
|
779
|
+
startSceneId: normalized.startSceneId,
|
|
780
|
+
continuity: data.continuity,
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
if (normalized.schema !== PROJECT_REGISTRY_SCHEMA) {
|
|
784
|
+
throw new Error(`Unsupported project registry schema "${normalized.schema}". Expected "${PROJECT_REGISTRY_SCHEMA}".`);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
if (!allowMissingStartScene && normalized.scenes.length > 0 && !normalized.startSceneId) {
|
|
788
|
+
throw new Error('Project registry scenes require startSceneId.');
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
normalized.scenes.sort(compareRegistryEntries);
|
|
792
|
+
normalized.screens.sort(compareRegistryEntries);
|
|
793
|
+
normalized.prefabs.sort(compareRegistryEntries);
|
|
794
|
+
normalized.configFiles.sort((a, b) => a.localeCompare(b));
|
|
795
|
+
normalized.contentFiles.sort((a, b) => a.localeCompare(b));
|
|
796
|
+
normalized.dataFiles.sort((a, b) => a.localeCompare(b));
|
|
797
|
+
|
|
798
|
+
return normalized;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function normalizeRegistryEntries(value, { kind }) {
|
|
802
|
+
if (value == null) return [];
|
|
803
|
+
if (!Array.isArray(value)) {
|
|
804
|
+
throw new Error(`Project registry ${kind}s must be an array.`);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
return value.map((entry, index) => {
|
|
808
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
809
|
+
throw new Error(`Project registry ${kind} entry at index ${index} must be an object.`);
|
|
810
|
+
}
|
|
811
|
+
const id = String(entry.id || '').trim();
|
|
812
|
+
const path = String(entry.path || '').trim();
|
|
813
|
+
if (!id) {
|
|
814
|
+
throw new Error(`Project registry ${kind} entry at index ${index} is missing id.`);
|
|
815
|
+
}
|
|
816
|
+
if (!path) {
|
|
817
|
+
throw new Error(`Project registry ${kind} entry "${id}" is missing path.`);
|
|
818
|
+
}
|
|
819
|
+
return {
|
|
820
|
+
id,
|
|
821
|
+
path: normalizeProjectRelativePath(path),
|
|
822
|
+
exportName: typeof entry.exportName === 'string' && entry.exportName.trim().length > 0 ? entry.exportName.trim() : 'default',
|
|
823
|
+
role: kind === 'prefab'
|
|
824
|
+
? normalizePrefabRole(entry.role, { fallback: 'custom' })
|
|
825
|
+
: null,
|
|
826
|
+
};
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function normalizeDataFiles(value) {
|
|
831
|
+
if (value == null) return [];
|
|
832
|
+
if (!Array.isArray(value)) {
|
|
833
|
+
throw new Error('Project registry dataFiles must be an array.');
|
|
834
|
+
}
|
|
835
|
+
return value.map((entry, index) => {
|
|
836
|
+
const path = String(entry || '').trim();
|
|
837
|
+
if (!path) {
|
|
838
|
+
throw new Error(`Project registry dataFiles entry at index ${index} must be a non-empty string.`);
|
|
839
|
+
}
|
|
840
|
+
return normalizeProjectRelativePath(path);
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function normalizeOwnedFiles({ configFiles = null, contentFiles = null, dataFiles = null } = {}) {
|
|
845
|
+
const normalizedConfigFiles = normalizeDataFiles(configFiles);
|
|
846
|
+
const normalizedContentFiles = normalizeDataFiles(contentFiles);
|
|
847
|
+
const legacyDataFiles = normalizeDataFiles(dataFiles);
|
|
848
|
+
const configFileSet = new Set(normalizedConfigFiles);
|
|
849
|
+
const contentFileSet = new Set(normalizedContentFiles);
|
|
850
|
+
|
|
851
|
+
for (const path of legacyDataFiles) {
|
|
852
|
+
if (path.startsWith('config/')) {
|
|
853
|
+
configFileSet.add(path);
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
contentFileSet.add(path);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
const ownedConfigFiles = [...configFileSet];
|
|
860
|
+
const ownedContentFiles = [...contentFileSet];
|
|
861
|
+
return {
|
|
862
|
+
configFiles: ownedConfigFiles,
|
|
863
|
+
contentFiles: ownedContentFiles,
|
|
864
|
+
dataFiles: [...ownedConfigFiles, ...ownedContentFiles],
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
function createProjectContinuityModel({
|
|
869
|
+
startSceneId = null,
|
|
870
|
+
continuity = null,
|
|
871
|
+
} = {}) {
|
|
872
|
+
const normalizedStartSceneId = startSceneId == null ? null : String(startSceneId).trim() || null;
|
|
873
|
+
const source = continuity && typeof continuity === 'object' && !Array.isArray(continuity)
|
|
874
|
+
? continuity
|
|
875
|
+
: {};
|
|
876
|
+
const saveRoots = source.saveRoots && typeof source.saveRoots === 'object' && !Array.isArray(source.saveRoots)
|
|
877
|
+
? source.saveRoots
|
|
878
|
+
: {};
|
|
879
|
+
const devRestore = devRestoreOrFallback(source.devRestore);
|
|
880
|
+
const runtimeFiles = Array.isArray(source.runtimeFiles)
|
|
881
|
+
? source.runtimeFiles.map((entry) => normalizeProjectRelativePath(entry)).filter(Boolean)
|
|
882
|
+
: [];
|
|
883
|
+
const restoreFlags = Array.isArray(devRestore.flags)
|
|
884
|
+
? devRestore.flags.map((entry) => String(entry || '').trim()).filter(Boolean)
|
|
885
|
+
: [];
|
|
886
|
+
const notes = Array.isArray(source.notes)
|
|
887
|
+
? source.notes.map((entry) => String(entry || '').trim()).filter(Boolean)
|
|
888
|
+
: [];
|
|
889
|
+
|
|
890
|
+
return {
|
|
891
|
+
schema: PROJECT_CONTINUITY_SCHEMA,
|
|
892
|
+
startSceneId: normalizedStartSceneId,
|
|
893
|
+
sharedAppStatePath: normalizeProjectRelativePath(
|
|
894
|
+
source.sharedAppStatePath || 'src/runtime/app-state.js',
|
|
895
|
+
),
|
|
896
|
+
sceneFlowPath: normalizeProjectRelativePath(
|
|
897
|
+
source.sceneFlowPath || 'src/runtime/scene-flow.js',
|
|
898
|
+
),
|
|
899
|
+
screenShellPath: normalizeProjectRelativePath(
|
|
900
|
+
source.screenShellPath || 'src/runtime/screen-shell.js',
|
|
901
|
+
),
|
|
902
|
+
uiThemePath: normalizeProjectRelativePath(
|
|
903
|
+
source.uiThemePath || 'src/runtime/ui-theme.js',
|
|
904
|
+
),
|
|
905
|
+
uiSettingsPath: normalizeProjectRelativePath(
|
|
906
|
+
source.uiSettingsPath || 'src/runtime/ui-settings.js',
|
|
907
|
+
),
|
|
908
|
+
uiFormsPath: normalizeProjectRelativePath(
|
|
909
|
+
source.uiFormsPath || 'src/runtime/ui-forms.js',
|
|
910
|
+
),
|
|
911
|
+
projectRegistryPath: normalizeProjectRelativePath(
|
|
912
|
+
source.projectRegistryPath || PROJECT_REGISTRY_RELATIVE_PATH,
|
|
913
|
+
),
|
|
914
|
+
sceneRegistryPath: normalizeProjectRelativePath(
|
|
915
|
+
source.sceneRegistryPath || SCENE_REGISTRY_RELATIVE_PATH,
|
|
916
|
+
),
|
|
917
|
+
sceneStateOwnerPath: normalizeProjectRelativePath(
|
|
918
|
+
source.sceneStateOwnerPath || 'scenes',
|
|
919
|
+
),
|
|
920
|
+
runtimeFiles: runtimeFiles.length > 0
|
|
921
|
+
? [...new Set(runtimeFiles)].sort((left, right) => left.localeCompare(right))
|
|
922
|
+
: [
|
|
923
|
+
'src/runtime/app-state.js',
|
|
924
|
+
'src/runtime/scene-flow.js',
|
|
925
|
+
'src/runtime/screen-shell.js',
|
|
926
|
+
'src/runtime/ui-theme.js',
|
|
927
|
+
'src/runtime/ui-settings.js',
|
|
928
|
+
'src/runtime/ui-forms.js',
|
|
929
|
+
PROJECT_REGISTRY_RELATIVE_PATH,
|
|
930
|
+
SCENE_REGISTRY_RELATIVE_PATH,
|
|
931
|
+
],
|
|
932
|
+
saveRoots: {
|
|
933
|
+
slots: normalizeProjectRelativePath(saveRoots.slots || '.aura/state/slots'),
|
|
934
|
+
checkpoints: normalizeProjectRelativePath(saveRoots.checkpoints || '.aura/state/checkpoints'),
|
|
935
|
+
},
|
|
936
|
+
devRestore: {
|
|
937
|
+
flags: restoreFlags.length > 0
|
|
938
|
+
? [...new Set(restoreFlags)]
|
|
939
|
+
: ['--restore-slot', '--restore-checkpoint'],
|
|
940
|
+
note: typeof devRestore.note === 'string' && devRestore.note.trim().length > 0
|
|
941
|
+
? devRestore.note.trim()
|
|
942
|
+
: 'Supported native dev restarts reapply slot, checkpoint, or live continuity payloads after setup.',
|
|
943
|
+
},
|
|
944
|
+
notes: notes.length > 0
|
|
945
|
+
? notes
|
|
946
|
+
: [
|
|
947
|
+
'Keep shared continuity in appState.session/ui/runtime through the app-context helpers backed by src/runtime/app-state.js, and keep scene-local restore state JSON-safe inside scene getState/applyState hooks.',
|
|
948
|
+
'Use src/runtime/scene-flow.js for route-style scene payload continuity read through context.getCurrentScenePayload(), and use src/runtime/screen-shell.js for HUD, overlay, and modal payload continuity instead of ad hoc globals.',
|
|
949
|
+
],
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
function devRestoreOrFallback(value) {
|
|
954
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
function buildProjectCheckResult({ mode, ok, reasonCode, errors, warnings, starterContentRegistries = [] }) {
|
|
958
|
+
return {
|
|
959
|
+
schema: PROJECT_CHECK_SCHEMA,
|
|
960
|
+
ok,
|
|
961
|
+
mode,
|
|
962
|
+
reasonCode,
|
|
963
|
+
errors,
|
|
964
|
+
warnings,
|
|
965
|
+
starterContentRegistries,
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
function formatExplainReport(report) {
|
|
970
|
+
const sourceOfTruth = report.sourceOfTruth && typeof report.sourceOfTruth === 'object'
|
|
971
|
+
? report.sourceOfTruth
|
|
972
|
+
: null;
|
|
973
|
+
const lines = [
|
|
974
|
+
`Project root: ${report.projectRoot}`,
|
|
975
|
+
`Template: ${report.template || 'unknown'}`,
|
|
976
|
+
`Mode: ${report.mode}`,
|
|
977
|
+
`Source of truth: ${sourceOfTruth?.primary || 'src/main.js'}`,
|
|
978
|
+
`Project registry: ${sourceOfTruth?.projectRegistry || '(none)'}`,
|
|
979
|
+
`Start scene: ${report.startingSceneId || '(none yet)'}`,
|
|
980
|
+
'',
|
|
981
|
+
'Runtime bootstrap:',
|
|
982
|
+
];
|
|
983
|
+
|
|
984
|
+
for (const entry of report.runtimeBootstrapFiles || []) {
|
|
985
|
+
lines.push(` ${entry}`);
|
|
986
|
+
}
|
|
987
|
+
if ((report.runtimeBootstrapFiles || []).length === 0) {
|
|
988
|
+
lines.push(' (none detected)');
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
lines.push('');
|
|
992
|
+
lines.push('Available make kinds:');
|
|
993
|
+
if ((report.availableMakeKinds || []).length === 0) {
|
|
994
|
+
lines.push(' (none detected)');
|
|
995
|
+
} else {
|
|
996
|
+
for (const entry of report.availableMakeKinds) {
|
|
997
|
+
lines.push(` ${entry.id} [${entry.owner}]`);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
lines.push('');
|
|
1002
|
+
lines.push('Scenes:');
|
|
1003
|
+
if ((report.scenes || []).length === 0) {
|
|
1004
|
+
lines.push(' (none registered)');
|
|
1005
|
+
} else {
|
|
1006
|
+
for (const entry of report.scenes) {
|
|
1007
|
+
lines.push(` ${entry.id} -> ${entry.path}`);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
lines.push('');
|
|
1012
|
+
lines.push('Screens:');
|
|
1013
|
+
if ((report.screens || []).length === 0) {
|
|
1014
|
+
lines.push(' (none registered)');
|
|
1015
|
+
} else {
|
|
1016
|
+
for (const entry of report.screens) {
|
|
1017
|
+
lines.push(` ${entry.id} -> ${entry.path}`);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
lines.push('');
|
|
1022
|
+
lines.push('Prefabs:');
|
|
1023
|
+
if ((report.prefabs || []).length === 0) {
|
|
1024
|
+
lines.push(' (none registered)');
|
|
1025
|
+
} else {
|
|
1026
|
+
for (const entry of report.prefabs) {
|
|
1027
|
+
lines.push(` ${entry.id}${entry.role ? ` [${entry.role}]` : ''} -> ${entry.path}`);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
lines.push('');
|
|
1032
|
+
lines.push('Config files:');
|
|
1033
|
+
if ((report.configFiles || []).length === 0) {
|
|
1034
|
+
lines.push(' (none registered)');
|
|
1035
|
+
} else {
|
|
1036
|
+
for (const entry of report.configFiles) {
|
|
1037
|
+
lines.push(` ${entry.path}`);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
lines.push('');
|
|
1042
|
+
lines.push('Content files:');
|
|
1043
|
+
if ((report.contentFiles || []).length === 0) {
|
|
1044
|
+
lines.push(' (none registered)');
|
|
1045
|
+
} else {
|
|
1046
|
+
for (const entry of report.contentFiles) {
|
|
1047
|
+
lines.push(` ${entry.path}`);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
lines.push('');
|
|
1052
|
+
lines.push('Continuity ownership:');
|
|
1053
|
+
const continuity = report.continuity && typeof report.continuity === 'object'
|
|
1054
|
+
? report.continuity
|
|
1055
|
+
: null;
|
|
1056
|
+
if (!continuity) {
|
|
1057
|
+
lines.push(' (none detected)');
|
|
1058
|
+
} else {
|
|
1059
|
+
lines.push(` appState.session/ui/runtime -> ${continuity.sharedAppStatePath || 'src/runtime/app-state.js'}`);
|
|
1060
|
+
lines.push(' app context helpers -> ensureSessionState, ensureUiState, getSessionState, getUiState');
|
|
1061
|
+
lines.push(` scene flow payloads -> ${continuity.sceneFlowPath || 'src/runtime/scene-flow.js'} (read current payload via getCurrentScenePayload())`);
|
|
1062
|
+
lines.push(` screen shell payloads -> ${continuity.screenShellPath || 'src/runtime/screen-shell.js'}`);
|
|
1063
|
+
lines.push(` ui theme module -> ${continuity.uiThemePath || 'src/runtime/ui-theme.js'} (appState.ui.preferences + theme apply/reset)`);
|
|
1064
|
+
lines.push(` retained settings helpers -> ${continuity.uiSettingsPath || 'src/runtime/ui-settings.js'}`);
|
|
1065
|
+
lines.push(` retained forms helpers -> ${continuity.uiFormsPath || 'src/runtime/ui-forms.js'}`);
|
|
1066
|
+
lines.push(' screen payloads -> screenShell.hud|overlay|modals[].data');
|
|
1067
|
+
lines.push(` scene-local state -> ${continuity.sceneStateOwnerPath || 'scenes'} (sceneState)`);
|
|
1068
|
+
lines.push(` slots -> ${continuity.saveRoots?.slots || '.aura/state/slots'}`);
|
|
1069
|
+
lines.push(` checkpoints -> ${continuity.saveRoots?.checkpoints || '.aura/state/checkpoints'}`);
|
|
1070
|
+
lines.push(` dev restore flags -> ${Array.isArray(continuity.devRestore?.flags) && continuity.devRestore.flags.length > 0 ? continuity.devRestore.flags.join(', ') : '(none)'}`);
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
lines.push('');
|
|
1074
|
+
lines.push('Starter content registries:');
|
|
1075
|
+
if ((report.starterContentRegistries || []).length === 0) {
|
|
1076
|
+
lines.push(' (none registered)');
|
|
1077
|
+
} else {
|
|
1078
|
+
for (const registry of report.starterContentRegistries) {
|
|
1079
|
+
lines.push(` ${registry.family} -> ${registry.registryPath}`);
|
|
1080
|
+
lines.push(` kinds: ${(registry.kinds || []).join(', ') || '(none)'}`);
|
|
1081
|
+
if (!Array.isArray(registry.entries) || registry.entries.length === 0) {
|
|
1082
|
+
lines.push(' entries: (none registered)');
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
for (const entry of registry.entries) {
|
|
1086
|
+
lines.push(` ${entry.id} [${entry.kind}] -> ${entry.path}`);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
return `${lines.join('\n')}\n`;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
function formatCheckReport(report) {
|
|
1095
|
+
const lines = [
|
|
1096
|
+
`aura check: ${report.ok ? 'ok' : 'failed'}`,
|
|
1097
|
+
`Mode: ${report.mode}`,
|
|
1098
|
+
`Reason code: ${report.reasonCode}`,
|
|
1099
|
+
];
|
|
1100
|
+
|
|
1101
|
+
if ((report.errors || []).length > 0) {
|
|
1102
|
+
lines.push('');
|
|
1103
|
+
lines.push('Errors:');
|
|
1104
|
+
for (const entry of report.errors) {
|
|
1105
|
+
lines.push(` - ${entry.reasonCode}: ${entry.message}${entry.path ? ` [${entry.path}]` : ''}`);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
if ((report.warnings || []).length > 0) {
|
|
1110
|
+
lines.push('');
|
|
1111
|
+
lines.push('Warnings:');
|
|
1112
|
+
for (const entry of report.warnings) {
|
|
1113
|
+
lines.push(` - ${entry.reasonCode}: ${entry.message}${entry.path ? ` [${entry.path}]` : ''}`);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
return `${lines.join('\n')}\n`;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
function pushDuplicateErrors(errors, entries, label) {
|
|
1121
|
+
const seen = new Set();
|
|
1122
|
+
for (const entry of entries) {
|
|
1123
|
+
if (seen.has(entry.id)) {
|
|
1124
|
+
errors.push({
|
|
1125
|
+
reasonCode: 'project_duplicate_id',
|
|
1126
|
+
message: `Duplicate ${label} id "${entry.id}" in project-registry.js.`,
|
|
1127
|
+
path: entry.path,
|
|
1128
|
+
});
|
|
1129
|
+
continue;
|
|
1130
|
+
}
|
|
1131
|
+
seen.add(entry.id);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
function pushMissingPathErrors(errors, entries, projectRoot, reasonCode) {
|
|
1136
|
+
for (const entry of entries) {
|
|
1137
|
+
const absolutePath = resolve(projectRoot, entry.path);
|
|
1138
|
+
if (!existsSync(absolutePath)) {
|
|
1139
|
+
errors.push({
|
|
1140
|
+
reasonCode,
|
|
1141
|
+
message: `Registered path does not exist: ${entry.path}`,
|
|
1142
|
+
path: entry.path,
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
function pushMissingOwnedFileErrors(errors, entries, projectRoot, reasonCode) {
|
|
1149
|
+
for (const entry of entries) {
|
|
1150
|
+
const filePath = typeof entry === 'string' ? entry : String(entry?.path || '').trim();
|
|
1151
|
+
if (!filePath) continue;
|
|
1152
|
+
const absolutePath = resolve(projectRoot, filePath);
|
|
1153
|
+
if (!existsSync(absolutePath)) {
|
|
1154
|
+
errors.push({
|
|
1155
|
+
reasonCode,
|
|
1156
|
+
message: `Registered authored file is missing: ${filePath}.`,
|
|
1157
|
+
path: filePath,
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
async function pushImportErrors(errors, entries, projectRoot) {
|
|
1164
|
+
for (const entry of entries) {
|
|
1165
|
+
const absolutePath = resolve(projectRoot, entry.path);
|
|
1166
|
+
if (!existsSync(absolutePath)) continue;
|
|
1167
|
+
try {
|
|
1168
|
+
const imported = await import(`${pathToFileURL(absolutePath).href}?t=${Date.now()}-${Math.random().toString(16).slice(2)}`);
|
|
1169
|
+
if (entry.exportName !== 'default' && typeof imported?.[entry.exportName] === 'undefined') {
|
|
1170
|
+
throw new Error(`Missing export "${entry.exportName}".`);
|
|
1171
|
+
}
|
|
1172
|
+
if (entry.exportName === 'default' && typeof imported?.default === 'undefined') {
|
|
1173
|
+
throw new Error('Missing default export.');
|
|
1174
|
+
}
|
|
1175
|
+
} catch (error) {
|
|
1176
|
+
errors.push({
|
|
1177
|
+
reasonCode: 'project_authored_import_broken',
|
|
1178
|
+
message: `Unable to import ${entry.path}: ${error instanceof Error ? error.message : String(error)}`,
|
|
1179
|
+
path: entry.path,
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
async function pushPrefabContractErrors(errors, entries, projectRoot) {
|
|
1186
|
+
for (const entry of entries) {
|
|
1187
|
+
const absolutePath = resolve(projectRoot, entry.path);
|
|
1188
|
+
if (!existsSync(absolutePath)) continue;
|
|
1189
|
+
|
|
1190
|
+
let imported = null;
|
|
1191
|
+
try {
|
|
1192
|
+
imported = await import(`${pathToFileURL(absolutePath).href}?t=${Date.now()}-${Math.random().toString(16).slice(2)}`);
|
|
1193
|
+
} catch (error) {
|
|
1194
|
+
errors.push({
|
|
1195
|
+
reasonCode: 'project_authored_import_broken',
|
|
1196
|
+
message: `Unable to import ${entry.path}: ${error instanceof Error ? error.message : String(error)}`,
|
|
1197
|
+
path: entry.path,
|
|
1198
|
+
});
|
|
1199
|
+
continue;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
const prefab = imported?.default;
|
|
1203
|
+
const issues = [];
|
|
1204
|
+
const prefabId = typeof prefab?.id === 'string' ? prefab.id.trim() : '';
|
|
1205
|
+
const prefabKind = typeof prefab?.kind === 'string' ? prefab.kind.trim() : '';
|
|
1206
|
+
const prefabRole = typeof prefab?.role === 'string' ? prefab.role.trim() : '';
|
|
1207
|
+
|
|
1208
|
+
if (!prefab || typeof prefab !== 'object' || Array.isArray(prefab)) {
|
|
1209
|
+
issues.push('default export must be a prefab descriptor object');
|
|
1210
|
+
} else {
|
|
1211
|
+
if (!prefabId) {
|
|
1212
|
+
issues.push('default export is missing id');
|
|
1213
|
+
} else if (prefabId !== entry.id) {
|
|
1214
|
+
issues.push(`default export id "${prefabId}" does not match registry id "${entry.id}"`);
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
if (prefabKind !== 'prefab') {
|
|
1218
|
+
issues.push(`default export kind must be "prefab" (received "${prefabKind || 'missing'}")`);
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
if (!prefabRole) {
|
|
1222
|
+
issues.push('default export is missing role');
|
|
1223
|
+
} else if (!CANONICAL_PREFAB_ROLE_SET.has(prefabRole)) {
|
|
1224
|
+
issues.push(`default export role "${prefabRole}" is not canonical (${CANONICAL_PREFAB_ROLES.join(', ')})`);
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
if (!entry.role) {
|
|
1228
|
+
issues.push('registry entry is missing a canonical role');
|
|
1229
|
+
} else if (!CANONICAL_PREFAB_ROLE_SET.has(entry.role)) {
|
|
1230
|
+
issues.push(`registry role "${entry.role}" is not canonical (${CANONICAL_PREFAB_ROLES.join(', ')})`);
|
|
1231
|
+
} else if (prefabRole && entry.role !== prefabRole) {
|
|
1232
|
+
issues.push(`registry role "${entry.role}" does not match prefab role "${prefabRole}"`);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
if (!prefab.view || typeof prefab.view !== 'object' || Array.isArray(prefab.view)) {
|
|
1236
|
+
issues.push('default export is missing a view object');
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
if (issues.length > 0) {
|
|
1241
|
+
errors.push({
|
|
1242
|
+
reasonCode: 'project_prefab_contract_invalid',
|
|
1243
|
+
message: `Prefab contract invalid: ${issues.join('; ')}.`,
|
|
1244
|
+
path: entry.path,
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
function createEmptyProjectRegistryModel({ template, projectTitle }) {
|
|
1251
|
+
return {
|
|
1252
|
+
schema: PROJECT_REGISTRY_SCHEMA,
|
|
1253
|
+
template: String(template || 'blank').trim() || 'blank',
|
|
1254
|
+
projectTitle: String(projectTitle || 'AuraJS Game').trim() || 'AuraJS Game',
|
|
1255
|
+
startSceneId: null,
|
|
1256
|
+
scenes: [],
|
|
1257
|
+
screens: [],
|
|
1258
|
+
prefabs: [],
|
|
1259
|
+
configFiles: [],
|
|
1260
|
+
contentFiles: [],
|
|
1261
|
+
dataFiles: [],
|
|
1262
|
+
continuity: createProjectContinuityModel(),
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
function upsertEntry(entries, nextEntry) {
|
|
1267
|
+
const index = entries.findIndex((entry) => entry.id === nextEntry.id);
|
|
1268
|
+
if (index === -1) {
|
|
1269
|
+
entries.push(nextEntry);
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
entries[index] = {
|
|
1273
|
+
...entries[index],
|
|
1274
|
+
...nextEntry,
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
function upsertOwnedFile(entries, nextPath) {
|
|
1279
|
+
const normalizedPath = normalizeProjectRelativePath(nextPath);
|
|
1280
|
+
if (!normalizedPath) return;
|
|
1281
|
+
if (!entries.includes(normalizedPath)) {
|
|
1282
|
+
entries.push(normalizedPath);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
function compareRegistryEntries(left, right) {
|
|
1287
|
+
return left.id.localeCompare(right.id) || left.path.localeCompare(right.path);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
function hasAuthoredProjectFiles(projectRoot) {
|
|
1291
|
+
const authoredRoots = ['scenes', 'prefabs', 'ui'];
|
|
1292
|
+
for (const relativePath of authoredRoots) {
|
|
1293
|
+
const absolutePath = resolve(projectRoot, relativePath);
|
|
1294
|
+
if (!existsSync(absolutePath)) continue;
|
|
1295
|
+
if (readdirSync(absolutePath).some((entry) => entry !== '.gitkeep')) {
|
|
1296
|
+
return true;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
return existsSync(resolve(projectRoot, 'config/gameplay/game.config.json'));
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
function shouldSeedStarterRegistry({ projectRoot, template }) {
|
|
1303
|
+
if (template !== 'blank') {
|
|
1304
|
+
return true;
|
|
1305
|
+
}
|
|
1306
|
+
return [
|
|
1307
|
+
'scenes/boot.scene.js',
|
|
1308
|
+
'scenes/gameplay.scene.js',
|
|
1309
|
+
'prefabs/player.prefab.js',
|
|
1310
|
+
'ui/hud.screen.js',
|
|
1311
|
+
].every((relativePath) => existsSync(resolve(projectRoot, relativePath)));
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
function loadProjectTemplate(projectRoot) {
|
|
1315
|
+
const capabilitiesPath = resolve(projectRoot, 'aura.capabilities.json');
|
|
1316
|
+
if (!existsSync(capabilitiesPath)) return 'blank';
|
|
1317
|
+
try {
|
|
1318
|
+
const capabilities = JSON.parse(readFileSync(capabilitiesPath, 'utf8'));
|
|
1319
|
+
if (typeof capabilities?.template === 'string' && capabilities.template.trim().length > 0) {
|
|
1320
|
+
return capabilities.template.trim();
|
|
1321
|
+
}
|
|
1322
|
+
} catch {}
|
|
1323
|
+
return 'blank';
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
function loadProjectTitle(projectRoot) {
|
|
1327
|
+
const configPath = resolve(projectRoot, 'aura.config.json');
|
|
1328
|
+
if (!existsSync(configPath)) return 'AuraJS Game';
|
|
1329
|
+
try {
|
|
1330
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
1331
|
+
if (typeof config?.identity?.name === 'string' && config.identity.name.trim().length > 0) {
|
|
1332
|
+
return config.identity.name.trim();
|
|
1333
|
+
}
|
|
1334
|
+
} catch {}
|
|
1335
|
+
return 'AuraJS Game';
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
export function assertAuraProjectRoot(projectRoot, message = 'Missing aura.config.json in the current directory.') {
|
|
1339
|
+
if (!existsSync(resolve(projectRoot, 'aura.config.json'))) {
|
|
1340
|
+
throw new Error(message);
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
function toProjectRelativePath(projectRoot, absolutePath) {
|
|
1345
|
+
return normalizeProjectRelativePath(relative(projectRoot, absolutePath));
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
function normalizeProjectRelativePath(value) {
|
|
1349
|
+
return String(value || '').split(/[/\\]+/g).filter(Boolean).join(POSIX_SEP);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
function toImportPath(relativePath) {
|
|
1353
|
+
const normalized = normalizeProjectRelativePath(relativePath);
|
|
1354
|
+
if (normalized.startsWith('.')) return normalized;
|
|
1355
|
+
return `./${normalized}`;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
function toPascalCase(value) {
|
|
1359
|
+
return String(value || '')
|
|
1360
|
+
.split(/[-_\s]+/)
|
|
1361
|
+
.filter(Boolean)
|
|
1362
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
1363
|
+
.join('');
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
function serializeValue(value, indent = 0) {
|
|
1367
|
+
return JSON.stringify(value, null, 2)
|
|
1368
|
+
.split('\n')
|
|
1369
|
+
.map((line, index) => (index === 0 ? line : `${' '.repeat(indent)}${line}`))
|
|
1370
|
+
.join('\n');
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
function normalizeExplainPayload(explained) {
|
|
1374
|
+
const mode = explained.mode === 'registry' ? 'registry' : 'bootstrap-only';
|
|
1375
|
+
const runtimeBootstrapFiles = Array.isArray(explained?.sourceOfTruth?.runtimeBootstrap)
|
|
1376
|
+
? [...explained.sourceOfTruth.runtimeBootstrap]
|
|
1377
|
+
: [];
|
|
1378
|
+
|
|
1379
|
+
return {
|
|
1380
|
+
schema: PROJECT_EXPLAIN_SCHEMA,
|
|
1381
|
+
projectRoot: explained.projectRoot,
|
|
1382
|
+
template: explained.template || 'blank',
|
|
1383
|
+
mode,
|
|
1384
|
+
sourceOfTruth: {
|
|
1385
|
+
projectRegistry: explained?.sourceOfTruth?.projectRegistry || null,
|
|
1386
|
+
primary: explained?.sourceOfTruth?.primary || explained?.sourceOfTruth?.projectRegistry || 'src/main.js',
|
|
1387
|
+
runtimeBootstrap: runtimeBootstrapFiles,
|
|
1388
|
+
},
|
|
1389
|
+
startingSceneId: explained.startingSceneId || null,
|
|
1390
|
+
runtimeBootstrapFiles,
|
|
1391
|
+
scenes: Array.isArray(explained.scenes) ? explained.scenes : [],
|
|
1392
|
+
screens: Array.isArray(explained.screens) ? explained.screens : [],
|
|
1393
|
+
prefabs: Array.isArray(explained.prefabs) ? explained.prefabs : [],
|
|
1394
|
+
configFiles: Array.isArray(explained.configFiles)
|
|
1395
|
+
? explained.configFiles.map((entry) => (typeof entry === 'string' ? { path: entry } : entry))
|
|
1396
|
+
: [],
|
|
1397
|
+
contentFiles: Array.isArray(explained.contentFiles)
|
|
1398
|
+
? explained.contentFiles.map((entry) => (typeof entry === 'string' ? { path: entry } : entry))
|
|
1399
|
+
: [],
|
|
1400
|
+
dataFiles: Array.isArray(explained.dataFiles)
|
|
1401
|
+
? explained.dataFiles.map((entry) => (typeof entry === 'string' ? { path: entry } : entry))
|
|
1402
|
+
: [],
|
|
1403
|
+
continuity: explained?.continuity && typeof explained.continuity === 'object' && !Array.isArray(explained.continuity)
|
|
1404
|
+
? explained.continuity
|
|
1405
|
+
: createProjectContinuityModel(),
|
|
1406
|
+
starterContentRegistries: Array.isArray(explained.starterContentRegistries)
|
|
1407
|
+
? explained.starterContentRegistries.map((registry) => ({
|
|
1408
|
+
family: registry.family,
|
|
1409
|
+
registryPath: registry.registryPath,
|
|
1410
|
+
kinds: Array.isArray(registry.kinds) ? registry.kinds : [],
|
|
1411
|
+
exists: registry.exists === true,
|
|
1412
|
+
title: registry.title || null,
|
|
1413
|
+
entries: Array.isArray(registry.entries) ? registry.entries : [],
|
|
1414
|
+
}))
|
|
1415
|
+
: [],
|
|
1416
|
+
availableMakeKinds: Array.isArray(explained.availableMakeKinds)
|
|
1417
|
+
? explained.availableMakeKinds.map((entry) => ({
|
|
1418
|
+
id: entry.id,
|
|
1419
|
+
owner: entry.owner || 'core',
|
|
1420
|
+
scope: entry.scope || 'core',
|
|
1421
|
+
}))
|
|
1422
|
+
: [],
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
function normalizeCheckPayload({ projectRoot, checked }) {
|
|
1427
|
+
return {
|
|
1428
|
+
schema: PROJECT_CHECK_SCHEMA,
|
|
1429
|
+
ok: checked.ok === true,
|
|
1430
|
+
mode: checked.mode === 'registry' ? 'registry' : checked.mode,
|
|
1431
|
+
projectRoot,
|
|
1432
|
+
reasonCode: checked.reasonCode || (checked.ok ? 'project_check_ok' : 'project_check_failed'),
|
|
1433
|
+
errors: Array.isArray(checked.errors) ? checked.errors : [],
|
|
1434
|
+
warnings: Array.isArray(checked.warnings) ? checked.warnings : [],
|
|
1435
|
+
starterContentRegistries: Array.isArray(checked.starterContentRegistries) ? checked.starterContentRegistries : [],
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
function appendExplainLines(lines, title, entries) {
|
|
1440
|
+
lines.push(` ${title}:`);
|
|
1441
|
+
if (!Array.isArray(entries) || entries.length === 0) {
|
|
1442
|
+
lines.push(' (none registered)');
|
|
1443
|
+
} else {
|
|
1444
|
+
for (const entry of entries) {
|
|
1445
|
+
lines.push(` ${entry.id} -> ${entry.path}`);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
lines.push('');
|
|
1449
|
+
}
|