openvibers/openvibe.games Commits
Run Games' vitest files through test/run.js so ov test can shard them
Changes
@@@ -92,15 +92,15 @@ freeze. Props are protected: only you and players you trust can move them.## Commands--| Command | Purpose |--| --------------------------------------- | ------------------------------------------------------------------------------------------------------------ |--| `pnpm dev` | Server + client dev processes in parallel |--| `pnpm test` | All unit tests (vitest) |--| `pnpm typecheck` | Strict TypeScript across every package |--| `pnpm lint` | ESLint (typescript-eslint, no-explicit-any) |--| `pnpm format` | Prettier write |--| `pnpm build` | Production build of all packages + client bundle |--| `tsx apps/server/scripts/sliceTest.ts` | End-to-end vertical-slice test (boots a real server, drives protocol clients, restarts, asserts persistence) |++| Command | Purpose |++| -------------------------------------- | ------------------------------------------------------------------------------------------------------------ |++| `pnpm dev` | Server + client dev processes in parallel |++| `pnpm test` | All unit tests (vitest, through `test/run.js`) |++| `pnpm typecheck` | Strict TypeScript across every package |++| `pnpm lint` | ESLint (typescript-eslint, no-explicit-any) |++| `pnpm format` | Prettier write |++| `pnpm build` | Production build of all packages + client bundle |++| `tsx apps/server/scripts/sliceTest.ts` | End-to-end vertical-slice test (boots a real server, drives protocol clients, restarts, asserts persistence) |## Production@@@ -238,6 +238,10 @@ revocation and mod-grant subscriptions), `media.object.upload``pnpm test` runs every vitest file (unit tests of the packages and the server, including`apps/server/src/game/platformIntegration.test.ts`, which boots the real server twice on one database);++it goes through `test/run.js`, which maps the flat `test/*.test.js` pointers back to the colocated++TypeScript tests so `ov test` can select and shard them (vitest reads the real files, so their++`@vitest-environment` docblocks and imports are untouched). After adding or moving a `*.test.ts`,++run `node test/run.js --index` to regenerate the pointers — a missing or stale pointer fails the run.`pnpm typecheck` and `pnpm lint` keep strict TypeScript and no `any`;`tsx apps/server/scripts/sliceTest.ts` drives protocol clients against a real server across a restart.@@@ -13,7 +13,7 @@"dev:server": "pnpm --filter @openvibe/server dev","dev:client": "pnpm --filter @openvibe/client dev","build": "pnpm -r build",-- "test": "vitest run",++ "test": "node test/run.js","audit:editor": "vitest run --config vitest.audit.config.ts","test:editor": "node --import tsx apps/client/e2e/editorCheck.ts","test:collab": "node --import tsx apps/client/e2e/editorCollabCheck.ts",nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/architectureAudit.audit.ts'++// covers: 'apps/client/src/editor'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/assets/assetController.test.ts'++// covers: 'apps/client/src/editor/assets'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/bindings.test.ts'++// covers: 'apps/client/src/editor'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/collaboration/lockController.test.ts'++// covers: 'apps/client/src/editor/collaboration'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/document/editorDocument.test.ts'++// covers: 'apps/client/src/editor/document'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/document/editorObject.test.ts'++// covers: 'apps/client/src/editor/document'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/history/commandHistory.test.ts'++// covers: 'apps/client/src/editor/history'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/history/commands.test.ts'++// covers: 'apps/client/src/editor/history'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/input/actionRouter.test.ts'++// covers: 'apps/client/src/editor/input'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/interaction/editorPicker.test.ts'++// covers: 'apps/client/src/editor/interaction'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/interaction/interactionController.test.ts'++// covers: 'apps/client/src/editor/interaction'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/materials/paintableSurface.test.ts'++// covers: 'apps/client/src/editor/materials'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/perf/editorProfiler.test.ts'++// covers: 'apps/client/src/editor/perf'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/recovery/draftStore.test.ts'++// covers: 'apps/client/src/editor/recovery'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/selection/faceSelection.test.ts'++// covers: 'apps/client/src/editor/selection'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/selection/selectionManager.test.ts'++// covers: 'apps/client/src/editor/selection'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/tools/lightZoneTool.test.ts'++// covers: 'apps/client/src/editor/tools'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/tools/terrainTool.test.ts'++// covers: 'apps/client/src/editor/tools'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/tools/toolManager.test.ts'++// covers: 'apps/client/src/editor/tools'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/ui/inspector.test.ts'++// covers: 'apps/client/src/editor/ui'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/ui/numberField.test.ts'++// covers: 'apps/client/src/editor/ui'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/ui/outliner.test.ts'++// covers: 'apps/client/src/editor/ui'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/ui/panels.test.ts'++// covers: 'apps/client/src/editor/ui'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/viewport/editorPreferences.test.ts'++// covers: 'apps/client/src/editor/viewport'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/viewport/editorViewRegistry.test.ts'++// covers: 'apps/client/src/editor/viewport'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/viewport/transformMath.test.ts'++// covers: 'apps/client/src/editor/viewport'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/editor/viewport/transformSession.test.ts'++// covers: 'apps/client/src/editor/viewport'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/input/inputSource.test.ts'++// covers: 'apps/client/src/input'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/pages/pageIcon.test.ts'++// covers: 'apps/client/src/pages'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/client/src/render/paintedStatic.test.ts'++// covers: 'apps/client/src/render'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/scripts/deployWrapper.test.ts'++// covers: 'apps/server/scripts'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/architectureAudit.audit.ts'++// covers: 'apps/server/src'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/config.test.ts'++// covers: 'apps/server/src'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/game/flusher.test.ts'++// covers: 'apps/server/src/game'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/game/handlerTable.test.ts'++// covers: 'apps/server/src/game'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/game/mapProvenance.test.ts'++// covers: 'apps/server/src/game'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/game/mapStaticLayer.test.ts'++// covers: 'apps/server/src/game'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/game/mapTerrainLayer.test.ts'++// covers: 'apps/server/src/game'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/game/platformIntegration.test.ts'++// covers: 'apps/server/src/game'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/mods/manifest.test.ts'++// covers: 'apps/server/src/mods'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/mods/moderationEvents.test.ts'++// covers: 'apps/server/src/mods'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/mods/networkGrants.test.ts'++// covers: 'apps/server/src/mods'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/mods/routes.test.ts'++// covers: 'apps/server/src/mods'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/mods/runtime.test.ts'++// covers: 'apps/server/src/mods'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/mods/security.test.ts'++// covers: 'apps/server/src/mods'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/net/actorLimits.test.ts'++// covers: 'apps/server/src/net'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/net/clientAddress.test.ts'++// covers: 'apps/server/src/net'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/net/discovery.test.ts'++// covers: 'apps/server/src/net'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/net/editorLocks.test.ts'++// covers: 'apps/server/src/net'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/net/httpServer.test.ts'++// covers: 'apps/server/src/net'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/net/legalPages.test.ts'++// covers: 'apps/server/src/net'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/net/mapAssetStore.test.ts'++// covers: 'apps/server/src/net'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/net/mapStore.test.ts'++// covers: 'apps/server/src/net'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/net/networkAuth.test.ts'++// covers: 'apps/server/src/net'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/net/sharedAssets.test.ts'++// covers: 'apps/server/src/net'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/net/sso.test.ts'++// covers: 'apps/server/src/net'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/net/staffAuthorization.test.ts'++// covers: 'apps/server/src/net'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/net/wsTicket.test.ts'++// covers: 'apps/server/src/net'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/net/wsTicketEndpoint.test.ts'++// covers: 'apps/server/src/net'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/net/wsTransport.test.ts'++// covers: 'apps/server/src/net'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/observability/readiness.test.ts'++// covers: 'apps/server/src/observability'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/observability/release.test.ts'++// covers: 'apps/server/src/observability'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/platform/accountData.test.ts'++// covers: 'apps/server/src/platform'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/platform/accounts.test.ts'++// covers: 'apps/server/src/platform'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/platform/gameEvents.test.ts'++// covers: 'apps/server/src/platform'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/platform/mediaMirror.test.ts'++// covers: 'apps/server/src/platform'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/platform/progressSummary.test.ts'++// covers: 'apps/server/src/platform'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/platform/proposals.test.ts'++// covers: 'apps/server/src/platform'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/platform/revocationEvents.test.ts'++// covers: 'apps/server/src/platform'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/platform/staffAuth.test.ts'++// covers: 'apps/server/src/platform'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'apps/server/src/shutdown.test.ts'++// covers: 'apps/server/src'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/content/src/mapDiff.test.ts'++// covers: 'packages/content/src'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/content/src/mapFileV2.test.ts'++// covers: 'packages/content/src'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/content/src/surface.test.ts'++// covers: 'packages/content/src'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/gameplay/src/combat/damage.test.ts'++// covers: 'packages/gameplay/src/combat'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/gameplay/src/constraints/constraints.test.ts'++// covers: 'packages/gameplay/src/constraints'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/gameplay/src/constraints/islands.test.ts'++// covers: 'packages/gameplay/src/constraints'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/gameplay/src/containers/containers.test.ts'++// covers: 'packages/gameplay/src/containers'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/gameplay/src/crafting/crafting.test.ts'++// covers: 'packages/gameplay/src/crafting'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/gameplay/src/environment/environment.test.ts'++// covers: 'packages/gameplay/src/environment'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/gameplay/src/farming/farming.test.ts'++// covers: 'packages/gameplay/src/farming'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/gameplay/src/inventory/inventory.test.ts'++// covers: 'packages/gameplay/src/inventory'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/gameplay/src/machines/machines.test.ts'++// covers: 'packages/gameplay/src/machines'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/gameplay/src/movement/determinism.test.ts'++// covers: 'packages/gameplay/src/movement'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/gameplay/src/movement/simulate.test.ts'++// covers: 'packages/gameplay/src/movement'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/gameplay/src/npc/npc.test.ts'++// covers: 'packages/gameplay/src/npc'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/gameplay/src/skills/skills.test.ts'++// covers: 'packages/gameplay/src/skills'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/gameplay/src/spatial/spatialHash.test.ts'++// covers: 'packages/gameplay/src/spatial'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/gameplay/src/survival/survival.test.ts'++// covers: 'packages/gameplay/src/survival'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/gameplay/src/zones/zones.test.ts'++// covers: 'packages/gameplay/src/zones'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/persistence/src/accountKey.test.ts'++// covers: 'packages/persistence/src'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/persistence/src/pg/pgStore.test.ts'++// covers: 'packages/persistence/src/pg'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/persistence/src/pg/platformRepositories.test.ts'++// covers: 'packages/persistence/src/pg'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/physics/src/conformance.test.ts'++// covers: 'packages/physics/src'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/protocol/src/codec.test.ts'++// covers: 'packages/protocol/src'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/protocol/src/editor.test.ts'++// covers: 'packages/protocol/src'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/shared/src/math/quat.test.ts'++// covers: 'packages/shared/src/math'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,3 @@++// file: 'packages/shared/src/math/scalar.test.ts'++// covers: 'packages/shared/src/math'++// Generated by node test/run.js --index; do not edit.nnew file mode 100644@@@ -0,0 +1,195 @@++#!/usr/bin/env node++// test/run.js — Games' own test runner, the shape `ov test` drives. ds-testsel calls++// `node test/run.js <test/… entries>`; each entry under test/ is a one-line pointer to a++// colocated TypeScript test, and this runs those through vitest and prints the per-file lines++// ov test parses (✓/✗ <entry> <ms>ms). With no argument it runs every vitest file (`pnpm test`).++//++// The pointers exist because Games' tests are TypeScript and colocated, not the test/*.test.js++// files the harness's runner convention expects; vitest reads the real files, so their++// @vitest-environment docblocks and imports stay exactly as written. The pointers are generated++// from the vitest include globs by `node test/run.js --index`; a missing or stale one fails the++// run rather than silently testing less.++//++// node test/run.js every test file++// node test/run.js <entry|word>… the entries whose name matches a word++// node test/run.js --index (re)write the test/ pointers from the vitest include globs++import { spawnSync } from 'node:child_process'++import {++ existsSync,++ mkdtempSync,++ readdirSync,++ readFileSync,++ rmSync,++ unlinkSync,++ writeFileSync,++} from 'node:fs'++import { tmpdir } from 'node:os'++import path from 'node:path'++import { fileURLToPath } from 'node:url'++++const dir = path.dirname(fileURLToPath(import.meta.url))++const root = path.join(dir, '..')++const POINTER = /^\/\/ file: '(.+)'$/m++++/** Every file vitest.config.ts runs: the .test.ts and .audit.ts files under apps and packages. */++function vitestFiles() {++ const out = []++ const walk = (rel) => {++ for (const e of readdirSync(path.join(root, rel), { withFileTypes: true })) {++ if (e.name === 'node_modules' || e.name === 'dist' || e.name === 'dist-types') continue++ const r = `${rel}/${e.name}`++ if (e.isDirectory()) walk(r)++ else if (/\.(test|audit)\.ts$/.test(e.name)) out.push(r)++ }++ }++ for (const top of ['apps', 'packages']) if (existsSync(path.join(root, top))) walk(top)++ return out.sort()++}++++/** test/<slug>.test.js for a TypeScript test: flat, so ds-testsel can list and shard them. */++function pointerName(ts) {++ const stem = ts.replace(/\.ts$/, '')++ return `${stem}${stem.endsWith('.test') ? '.js' : '.test.js'}`.replace(/\//g, '__')++}++++/** The pointers in test/: entry basename → the TypeScript file it runs. */++function pointers() {++ const map = new Map()++ for (const f of readdirSync(dir).filter((f) => f.endsWith('.test.js'))) {++ const m = POINTER.exec(readFileSync(path.join(dir, f), 'utf8'))++ if (m) map.set(f, m[1].trim())++ }++ return map++}++++/** Exactly one pointer per vitest file; drift fails the run rather than silently testing less. */++function check(ents, files) {++ const byTs = new Map([...ents].map(([f, ts]) => [ts, f]))++ const missing = files.filter((ts) => !byTs.has(ts))++ const stale = [...ents].filter(([, ts]) => !files.includes(ts))++ if (!missing.length && !stale.length) return true++ for (const ts of missing) {++ console.error(`test/run.js: no test/ pointer for ${ts} — run \`node test/run.js --index\``)++ }++ for (const [f, ts] of stale) {++ console.error(++ `test/run.js: ${f} points at ${ts}, which vitest no longer runs — run \`node test/run.js --index\``,++ )++ }++ return false++}++++function index() {++ const files = vitestFiles()++ const want = new Map(files.map((ts) => [pointerName(ts), ts]))++ if (want.size !== files.length) {++ console.error('test/run.js: two test paths map to the same pointer name; rename one')++ process.exit(1)++ }++ // The `covers:` line is quoted so ds-testsel's path-based selection (a change in the test's own++ // directory) reaches the pointer even when the source file's name does not match the test's.++ const body = (ts) =>++ `// file: '${ts}'\n// covers: '${path.posix.dirname(ts)}'\n// Generated by node test/run.js --index; do not edit.\n`++ for (const [name, ts] of want) {++ const p = path.join(dir, name)++ if (!existsSync(p) || readFileSync(p, 'utf8') !== body(ts)) writeFileSync(p, body(ts))++ }++ let removed = 0++ for (const f of readdirSync(dir)) {++ if (!f.endsWith('.test.js') || want.has(f)) continue++ if (POINTER.exec(readFileSync(path.join(dir, f), 'utf8'))) {++ unlinkSync(path.join(dir, f))++ removed++++ }++ }++ console.log(++ `test/run.js: ${want.size} pointers written${removed ? `, ${removed} stale removed` : ''}`,++ )++}++++const argv = process.argv.slice(2)++if (argv.includes('--index')) {++ index()++ process.exit(0)++}++++const ents = pointers()++const files = vitestFiles()++if (!check(ents, files)) process.exit(1)++++const words = argv.filter((a) => !a.startsWith('--'))++let chosen++if (!words.length) {++ chosen = [...ents.keys()]++} else {++ const set = new Set()++ for (const w of words) {++ const base = path.basename(w)++ if (ents.has(base)) {++ set.add(base)++ continue++ }++ const hits = [...ents.keys()].filter(++ (f) => f.includes(base) || f.includes(base.replace(/\.test\.js$/, '')),++ )++ if (!hits.length) {++ console.error(`test/run.js: no test/ pointer matches ${w}`)++ process.exit(1)++ }++ for (const h of hits) set.add(h)++ }++ chosen = [...set]++}++++const vitest = path.join(root, 'node_modules', '.bin', 'vitest')++if (!existsSync(vitest)) {++ console.error('test/run.js: vitest is not installed — run `pnpm install`')++ process.exit(127)++}++++const tsFiles = [...new Set(chosen.map((f) => ents.get(f)))].sort()++const tmp = mkdtempSync(path.join(tmpdir(), 'ov-games-test-'))++const reportFile = path.join(tmp, 'report.json')++const r = spawnSync(vitest, ['run', '--reporter=json', `--outputFile=${reportFile}`, ...tsFiles], {++ cwd: root,++ stdio: ['ignore', 'pipe', 'pipe'],++})++const vitestOut = `${r.stdout || ''}${r.stderr || ''}`.trim()++let report = null++try {++ report = JSON.parse(readFileSync(reportFile, 'utf8'))++} catch {++ // vitest died before writing the report; the captured output below says why++}++rmSync(tmp, { recursive: true, force: true })++++const byAbs = new Map()++for (const t of (report && report.testResults) || []) byAbs.set(path.resolve(t.name), t)++++const tails = []++let bad = 0++for (const f of chosen) {++ const ts = ents.get(f)++ const t = byAbs.get(path.resolve(root, ts))++ const failed =++ !t || t.status === 'failed' || (t.assertionResults || []).some((a) => a.status === 'failed')++ const ms = t && t.endTime != null ? Math.max(0, Math.round(t.endTime - t.startTime)) : 0++ console.log(`${failed ? '✗' : '✓'} ${f} ${ms}ms`)++ if (!failed) continue++ bad++++ const detail = !t++ ? `no vitest result for ${ts}${r.status ? ` (vitest exited ${r.status})` : ''}\n${vitestOut++ .split('\n')++ .slice(-15)++ .join('\n')}`++ : (t.assertionResults || [])++ .filter((a) => a.status === 'failed')++ .map((a) => (a.failureMessages || []).join('\n'))++ .filter(Boolean)++ .join('\n') ||++ t.message ||++ 'failed'++ tails.push(`── ${f} (exit 1) ──\n${detail}`)++}++for (const tail of tails) console.log(`\n${tail}`)++process.exit(bad ? 1 : 0)
@@ -92,15 +92,15 @@ freeze. Props are protected: only you and players you trust can move them.
@@ -92,15 +92,15 @@ freeze. Props are protected: only you and players you trust can move them.
## Commands
## Commands
-| Command | Purpose |
-| --------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
-| `pnpm dev` | Server + client dev processes in parallel |
-| `pnpm test` | All unit tests (vitest) |
-| `pnpm typecheck` | Strict TypeScript across every package |
-| `pnpm lint` | ESLint (typescript-eslint, no-explicit-any) |
-| `pnpm format` | Prettier write |
-| `pnpm build` | Production build of all packages + client bundle |
-| `tsx apps/server/scripts/sliceTest.ts` | End-to-end vertical-slice test (boots a real server, drives protocol clients, restarts, asserts persistence) |
+| Command | Purpose |
+| -------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
+| `pnpm dev` | Server + client dev processes in parallel |
+| `pnpm test` | All unit tests (vitest, through `test/run.js`) |
+| `pnpm typecheck` | Strict TypeScript across every package |
+| `pnpm lint` | ESLint (typescript-eslint, no-explicit-any) |
+| `pnpm format` | Prettier write |
+| `pnpm build` | Production build of all packages + client bundle |
+| `tsx apps/server/scripts/sliceTest.ts` | End-to-end vertical-slice test (boots a real server, drives protocol clients, restarts, asserts persistence) |
## Production
## Production
@@ -238,6 +238,10 @@ revocation and mod-grant subscriptions), `media.object.upload`
@@ -238,6 +238,10 @@ revocation and mod-grant subscriptions), `media.object.upload`
`pnpm test` runs every vitest file (unit tests of the packages and the server, including
`pnpm test` runs every vitest file (unit tests of the packages and the server, including
`apps/server/src/game/platformIntegration.test.ts`, which boots the real server twice on one database);
`apps/server/src/game/platformIntegration.test.ts`, which boots the real server twice on one database);
+it goes through `test/run.js`, which maps the flat `test/*.test.js` pointers back to the colocated
+TypeScript tests so `ov test` can select and shard them (vitest reads the real files, so their
+`@vitest-environment` docblocks and imports are untouched). After adding or moving a `*.test.ts`,
+run `node test/run.js --index` to regenerate the pointers — a missing or stale pointer fails the run.
`pnpm typecheck` and `pnpm lint` keep strict TypeScript and no `any`;
`pnpm typecheck` and `pnpm lint` keep strict TypeScript and no `any`;
`tsx apps/server/scripts/sliceTest.ts` drives protocol clients against a real server across a restart.
`tsx apps/server/scripts/sliceTest.ts` drives protocol clients against a real server across a restart.
@@ -13,7 +13,7 @@
@@ -13,7 +13,7 @@
"dev:server": "pnpm --filter @openvibe/server dev",
"dev:server": "pnpm --filter @openvibe/server dev",
"dev:client": "pnpm --filter @openvibe/client dev",
"dev:client": "pnpm --filter @openvibe/client dev",
"build": "pnpm -r build",
"build": "pnpm -r build",
- "test": "vitest run",
+ "test": "node test/run.js",
"audit:editor": "vitest run --config vitest.audit.config.ts",
"audit:editor": "vitest run --config vitest.audit.config.ts",
"test:editor": "node --import tsx apps/client/e2e/editorCheck.ts",
"test:editor": "node --import tsx apps/client/e2e/editorCheck.ts",
"test:collab": "node --import tsx apps/client/e2e/editorCollabCheck.ts",
"test:collab": "node --import tsx apps/client/e2e/editorCollabCheck.ts",
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/architectureAudit.audit.ts'
+// covers: 'apps/client/src/editor'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/assets/assetController.test.ts'
+// covers: 'apps/client/src/editor/assets'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/bindings.test.ts'
+// covers: 'apps/client/src/editor'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/collaboration/lockController.test.ts'
+// covers: 'apps/client/src/editor/collaboration'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/document/editorDocument.test.ts'
+// covers: 'apps/client/src/editor/document'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/document/editorObject.test.ts'
+// covers: 'apps/client/src/editor/document'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/history/commandHistory.test.ts'
+// covers: 'apps/client/src/editor/history'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/history/commands.test.ts'
+// covers: 'apps/client/src/editor/history'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/input/actionRouter.test.ts'
+// covers: 'apps/client/src/editor/input'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/interaction/editorPicker.test.ts'
+// covers: 'apps/client/src/editor/interaction'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/interaction/interactionController.test.ts'
+// covers: 'apps/client/src/editor/interaction'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/materials/paintableSurface.test.ts'
+// covers: 'apps/client/src/editor/materials'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/perf/editorProfiler.test.ts'
+// covers: 'apps/client/src/editor/perf'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/recovery/draftStore.test.ts'
+// covers: 'apps/client/src/editor/recovery'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/selection/faceSelection.test.ts'
+// covers: 'apps/client/src/editor/selection'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/selection/selectionManager.test.ts'
+// covers: 'apps/client/src/editor/selection'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/tools/lightZoneTool.test.ts'
+// covers: 'apps/client/src/editor/tools'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/tools/terrainTool.test.ts'
+// covers: 'apps/client/src/editor/tools'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/tools/toolManager.test.ts'
+// covers: 'apps/client/src/editor/tools'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/ui/inspector.test.ts'
+// covers: 'apps/client/src/editor/ui'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/ui/numberField.test.ts'
+// covers: 'apps/client/src/editor/ui'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/ui/outliner.test.ts'
+// covers: 'apps/client/src/editor/ui'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/ui/panels.test.ts'
+// covers: 'apps/client/src/editor/ui'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/viewport/editorPreferences.test.ts'
+// covers: 'apps/client/src/editor/viewport'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/viewport/editorViewRegistry.test.ts'
+// covers: 'apps/client/src/editor/viewport'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/viewport/transformMath.test.ts'
+// covers: 'apps/client/src/editor/viewport'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/editor/viewport/transformSession.test.ts'
+// covers: 'apps/client/src/editor/viewport'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/input/inputSource.test.ts'
+// covers: 'apps/client/src/input'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/pages/pageIcon.test.ts'
+// covers: 'apps/client/src/pages'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/client/src/render/paintedStatic.test.ts'
+// covers: 'apps/client/src/render'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/scripts/deployWrapper.test.ts'
+// covers: 'apps/server/scripts'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/architectureAudit.audit.ts'
+// covers: 'apps/server/src'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/config.test.ts'
+// covers: 'apps/server/src'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/game/flusher.test.ts'
+// covers: 'apps/server/src/game'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/game/handlerTable.test.ts'
+// covers: 'apps/server/src/game'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/game/mapProvenance.test.ts'
+// covers: 'apps/server/src/game'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/game/mapStaticLayer.test.ts'
+// covers: 'apps/server/src/game'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/game/mapTerrainLayer.test.ts'
+// covers: 'apps/server/src/game'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/game/platformIntegration.test.ts'
+// covers: 'apps/server/src/game'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/mods/manifest.test.ts'
+// covers: 'apps/server/src/mods'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/mods/moderationEvents.test.ts'
+// covers: 'apps/server/src/mods'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/mods/networkGrants.test.ts'
+// covers: 'apps/server/src/mods'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/mods/routes.test.ts'
+// covers: 'apps/server/src/mods'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/mods/runtime.test.ts'
+// covers: 'apps/server/src/mods'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/mods/security.test.ts'
+// covers: 'apps/server/src/mods'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/net/actorLimits.test.ts'
+// covers: 'apps/server/src/net'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/net/clientAddress.test.ts'
+// covers: 'apps/server/src/net'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/net/discovery.test.ts'
+// covers: 'apps/server/src/net'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/net/editorLocks.test.ts'
+// covers: 'apps/server/src/net'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/net/httpServer.test.ts'
+// covers: 'apps/server/src/net'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/net/legalPages.test.ts'
+// covers: 'apps/server/src/net'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/net/mapAssetStore.test.ts'
+// covers: 'apps/server/src/net'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/net/mapStore.test.ts'
+// covers: 'apps/server/src/net'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/net/networkAuth.test.ts'
+// covers: 'apps/server/src/net'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/net/sharedAssets.test.ts'
+// covers: 'apps/server/src/net'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/net/sso.test.ts'
+// covers: 'apps/server/src/net'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/net/staffAuthorization.test.ts'
+// covers: 'apps/server/src/net'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/net/wsTicket.test.ts'
+// covers: 'apps/server/src/net'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/net/wsTicketEndpoint.test.ts'
+// covers: 'apps/server/src/net'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/net/wsTransport.test.ts'
+// covers: 'apps/server/src/net'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/observability/readiness.test.ts'
+// covers: 'apps/server/src/observability'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/observability/release.test.ts'
+// covers: 'apps/server/src/observability'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/platform/accountData.test.ts'
+// covers: 'apps/server/src/platform'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/platform/accounts.test.ts'
+// covers: 'apps/server/src/platform'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/platform/gameEvents.test.ts'
+// covers: 'apps/server/src/platform'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/platform/mediaMirror.test.ts'
+// covers: 'apps/server/src/platform'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/platform/progressSummary.test.ts'
+// covers: 'apps/server/src/platform'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/platform/proposals.test.ts'
+// covers: 'apps/server/src/platform'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/platform/revocationEvents.test.ts'
+// covers: 'apps/server/src/platform'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/platform/staffAuth.test.ts'
+// covers: 'apps/server/src/platform'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'apps/server/src/shutdown.test.ts'
+// covers: 'apps/server/src'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/content/src/mapDiff.test.ts'
+// covers: 'packages/content/src'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/content/src/mapFileV2.test.ts'
+// covers: 'packages/content/src'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/content/src/surface.test.ts'
+// covers: 'packages/content/src'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/gameplay/src/combat/damage.test.ts'
+// covers: 'packages/gameplay/src/combat'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/gameplay/src/constraints/constraints.test.ts'
+// covers: 'packages/gameplay/src/constraints'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/gameplay/src/constraints/islands.test.ts'
+// covers: 'packages/gameplay/src/constraints'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/gameplay/src/containers/containers.test.ts'
+// covers: 'packages/gameplay/src/containers'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/gameplay/src/crafting/crafting.test.ts'
+// covers: 'packages/gameplay/src/crafting'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/gameplay/src/environment/environment.test.ts'
+// covers: 'packages/gameplay/src/environment'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/gameplay/src/farming/farming.test.ts'
+// covers: 'packages/gameplay/src/farming'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/gameplay/src/inventory/inventory.test.ts'
+// covers: 'packages/gameplay/src/inventory'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/gameplay/src/machines/machines.test.ts'
+// covers: 'packages/gameplay/src/machines'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/gameplay/src/movement/determinism.test.ts'
+// covers: 'packages/gameplay/src/movement'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/gameplay/src/movement/simulate.test.ts'
+// covers: 'packages/gameplay/src/movement'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/gameplay/src/npc/npc.test.ts'
+// covers: 'packages/gameplay/src/npc'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/gameplay/src/skills/skills.test.ts'
+// covers: 'packages/gameplay/src/skills'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/gameplay/src/spatial/spatialHash.test.ts'
+// covers: 'packages/gameplay/src/spatial'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/gameplay/src/survival/survival.test.ts'
+// covers: 'packages/gameplay/src/survival'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/gameplay/src/zones/zones.test.ts'
+// covers: 'packages/gameplay/src/zones'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/persistence/src/accountKey.test.ts'
+// covers: 'packages/persistence/src'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/persistence/src/pg/pgStore.test.ts'
+// covers: 'packages/persistence/src/pg'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/persistence/src/pg/platformRepositories.test.ts'
+// covers: 'packages/persistence/src/pg'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/physics/src/conformance.test.ts'
+// covers: 'packages/physics/src'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/protocol/src/codec.test.ts'
+// covers: 'packages/protocol/src'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/protocol/src/editor.test.ts'
+// covers: 'packages/protocol/src'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/shared/src/math/quat.test.ts'
+// covers: 'packages/shared/src/math'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+// file: 'packages/shared/src/math/scalar.test.ts'
+// covers: 'packages/shared/src/math'
+// Generated by node test/run.js --index; do not edit.
new file mode 100644
new file mode 100644
@@ -0,0 +1,195 @@
@@ -0,0 +1,195 @@
+#!/usr/bin/env node
+// test/run.js — Games' own test runner, the shape `ov test` drives. ds-testsel calls
+// `node test/run.js <test/… entries>`; each entry under test/ is a one-line pointer to a
+// colocated TypeScript test, and this runs those through vitest and prints the per-file lines
+// ov test parses (✓/✗ <entry> <ms>ms). With no argument it runs every vitest file (`pnpm test`).
+//
+// The pointers exist because Games' tests are TypeScript and colocated, not the test/*.test.js
+// files the harness's runner convention expects; vitest reads the real files, so their
+// @vitest-environment docblocks and imports stay exactly as written. The pointers are generated
+// from the vitest include globs by `node test/run.js --index`; a missing or stale one fails the
+// run rather than silently testing less.
+//
+// node test/run.js every test file
+// node test/run.js <entry|word>… the entries whose name matches a word
+// node test/run.js --index (re)write the test/ pointers from the vitest include globs
+import { spawnSync } from 'node:child_process'
+import {
+ existsSync,
+ mkdtempSync,
+ readdirSync,
+ readFileSync,
+ rmSync,
+ unlinkSync,
+ writeFileSync,
+} from 'node:fs'
+import { tmpdir } from 'node:os'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
+
+const dir = path.dirname(fileURLToPath(import.meta.url))
+const root = path.join(dir, '..')
+const POINTER = /^\/\/ file: '(.+)'$/m
+
+/** Every file vitest.config.ts runs: the .test.ts and .audit.ts files under apps and packages. */
+function vitestFiles() {
+ const out = []
+ const walk = (rel) => {
+ for (const e of readdirSync(path.join(root, rel), { withFileTypes: true })) {
+ if (e.name === 'node_modules' || e.name === 'dist' || e.name === 'dist-types') continue
+ const r = `${rel}/${e.name}`
+ if (e.isDirectory()) walk(r)
+ else if (/\.(test|audit)\.ts$/.test(e.name)) out.push(r)
+ }
+ }
+ for (const top of ['apps', 'packages']) if (existsSync(path.join(root, top))) walk(top)
+ return out.sort()
+}
+
+/** test/<slug>.test.js for a TypeScript test: flat, so ds-testsel can list and shard them. */
+function pointerName(ts) {
+ const stem = ts.replace(/\.ts$/, '')
+ return `${stem}${stem.endsWith('.test') ? '.js' : '.test.js'}`.replace(/\//g, '__')
+}
+
+/** The pointers in test/: entry basename → the TypeScript file it runs. */
+function pointers() {
+ const map = new Map()
+ for (const f of readdirSync(dir).filter((f) => f.endsWith('.test.js'))) {
+ const m = POINTER.exec(readFileSync(path.join(dir, f), 'utf8'))
+ if (m) map.set(f, m[1].trim())
+ }
+ return map
+}
+
+/** Exactly one pointer per vitest file; drift fails the run rather than silently testing less. */
+function check(ents, files) {
+ const byTs = new Map([...ents].map(([f, ts]) => [ts, f]))
+ const missing = files.filter((ts) => !byTs.has(ts))
+ const stale = [...ents].filter(([, ts]) => !files.includes(ts))
+ if (!missing.length && !stale.length) return true
+ for (const ts of missing) {
+ console.error(`test/run.js: no test/ pointer for ${ts} — run \`node test/run.js --index\``)
+ }
+ for (const [f, ts] of stale) {
+ console.error(
+ `test/run.js: ${f} points at ${ts}, which vitest no longer runs — run \`node test/run.js --index\``,
+ )
+ }
+ return false
+}
+
+function index() {
+ const files = vitestFiles()
+ const want = new Map(files.map((ts) => [pointerName(ts), ts]))
+ if (want.size !== files.length) {
+ console.error('test/run.js: two test paths map to the same pointer name; rename one')
+ process.exit(1)
+ }
+ // The `covers:` line is quoted so ds-testsel's path-based selection (a change in the test's own
+ // directory) reaches the pointer even when the source file's name does not match the test's.
+ const body = (ts) =>
+ `// file: '${ts}'\n// covers: '${path.posix.dirname(ts)}'\n// Generated by node test/run.js --index; do not edit.\n`
+ for (const [name, ts] of want) {
+ const p = path.join(dir, name)
+ if (!existsSync(p) || readFileSync(p, 'utf8') !== body(ts)) writeFileSync(p, body(ts))
+ }
+ let removed = 0
+ for (const f of readdirSync(dir)) {
+ if (!f.endsWith('.test.js') || want.has(f)) continue
+ if (POINTER.exec(readFileSync(path.join(dir, f), 'utf8'))) {
+ unlinkSync(path.join(dir, f))
+ removed++
+ }
+ }
+ console.log(
+ `test/run.js: ${want.size} pointers written${removed ? `, ${removed} stale removed` : ''}`,
+ )
+}
+
+const argv = process.argv.slice(2)
+if (argv.includes('--index')) {
+ index()
+ process.exit(0)
+}
+
+const ents = pointers()
+const files = vitestFiles()
+if (!check(ents, files)) process.exit(1)
+
+const words = argv.filter((a) => !a.startsWith('--'))
+let chosen
+if (!words.length) {
+ chosen = [...ents.keys()]
+} else {
+ const set = new Set()
+ for (const w of words) {
+ const base = path.basename(w)
+ if (ents.has(base)) {
+ set.add(base)
+ continue
+ }
+ const hits = [...ents.keys()].filter(
+ (f) => f.includes(base) || f.includes(base.replace(/\.test\.js$/, '')),
+ )
+ if (!hits.length) {
+ console.error(`test/run.js: no test/ pointer matches ${w}`)
+ process.exit(1)
+ }
+ for (const h of hits) set.add(h)
+ }
+ chosen = [...set]
+}
+
+const vitest = path.join(root, 'node_modules', '.bin', 'vitest')
+if (!existsSync(vitest)) {
+ console.error('test/run.js: vitest is not installed — run `pnpm install`')
+ process.exit(127)
+}
+
+const tsFiles = [...new Set(chosen.map((f) => ents.get(f)))].sort()
+const tmp = mkdtempSync(path.join(tmpdir(), 'ov-games-test-'))
+const reportFile = path.join(tmp, 'report.json')
+const r = spawnSync(vitest, ['run', '--reporter=json', `--outputFile=${reportFile}`, ...tsFiles], {
+ cwd: root,
+ stdio: ['ignore', 'pipe', 'pipe'],
+})
+const vitestOut = `${r.stdout || ''}${r.stderr || ''}`.trim()
+let report = null
+try {
+ report = JSON.parse(readFileSync(reportFile, 'utf8'))
+} catch {
+ // vitest died before writing the report; the captured output below says why
+}
+rmSync(tmp, { recursive: true, force: true })
+
+const byAbs = new Map()
+for (const t of (report && report.testResults) || []) byAbs.set(path.resolve(t.name), t)
+
+const tails = []
+let bad = 0
+for (const f of chosen) {
+ const ts = ents.get(f)
+ const t = byAbs.get(path.resolve(root, ts))
+ const failed =
+ !t || t.status === 'failed' || (t.assertionResults || []).some((a) => a.status === 'failed')
+ const ms = t && t.endTime != null ? Math.max(0, Math.round(t.endTime - t.startTime)) : 0
+ console.log(`${failed ? '✗' : '✓'} ${f} ${ms}ms`)
+ if (!failed) continue
+ bad++
+ const detail = !t
+ ? `no vitest result for ${ts}${r.status ? ` (vitest exited ${r.status})` : ''}\n${vitestOut
+ .split('\n')
+ .slice(-15)
+ .join('\n')}`
+ : (t.assertionResults || [])
+ .filter((a) => a.status === 'failed')
+ .map((a) => (a.failureMessages || []).join('\n'))
+ .filter(Boolean)
+ .join('\n') ||
+ t.message ||
+ 'failed'
+ tails.push(`── ${f} (exit 1) ──\n${detail}`)
+}
+for (const tail of tails) console.log(`\n${tail}`)
+process.exit(bad ? 1 : 0)