@volter/project 0.5.210 → 0.5.212
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/NOTICE +2 -2
- package/README.md +8 -15
- package/package.json +5 -26
- package/schemas/volter-project.schema.json +33 -145
- package/schemas/volter-settings.schema.json +11 -49
- package/src/adapter/adapter-module.ts +204 -465
- package/src/adapter/adapter-surface.ts +4 -32
- package/src/adapter/authoring-seam-contract.ts +7 -24
- package/src/adapter/authoring.ts +126 -265
- package/src/adapter/editor-looks.ts +96 -127
- package/src/adapter/finders/registry.ts +6 -21
- package/src/adapter/host-context.ts +17 -72
- package/src/adapter/index.ts +4 -77
- package/src/adapter/manifest-interpreter.ts +7 -50
- package/src/adapter/physics-adapter-2d.ts +4 -21
- package/src/adapter/root-adapter.ts +37 -111
- package/src/adapter/root-seam-contract.ts +3 -6
- package/src/adapter/root-stacking.ts +4 -46
- package/src/adapter/seam-evidence.ts +0 -16
- package/src/adapter/system-adapter.ts +24 -656
- package/src/adapter/system-seam-contract.ts +1 -91
- package/src/adapter/transform.ts +4 -3
- package/src/adapter/workspace-arrangement.ts +29 -49
- package/src/manifest/editor-port.ts +10 -36
- package/src/manifest/filename.ts +3 -42
- package/src/manifest/load-file.ts +9 -15
- package/src/manifest/load.ts +44 -179
- package/src/manifest/locate.ts +6 -29
- package/src/manifest/schema.ts +155 -427
- package/src/semver.ts +16 -0
- package/src/settings/keys.ts +9 -11
- package/src/settings/schema.ts +55 -44
- package/schemas/engine-api.json +0 -57
- package/schemas/engine-api.md +0 -22
- package/src/adapter/binding.ts +0 -445
- package/src/adapter/debug-registry-ref.ts +0 -19
- package/src/adapter/frame-capture.ts +0 -80
- package/src/adapter/ingest/contract-system-slots.ts +0 -28
- package/src/adapter/ingest/game-contract-seams.ts +0 -86
- package/src/adapter/ingest/game-contract.ts +0 -506
- package/src/adapter/ingest/mount-readiness.ts +0 -140
- package/src/adapter/loop-gate-report.ts +0 -53
- package/src/adapter/native-entry-surface.ts +0 -100
- package/src/adapter/render-audio.ts +0 -20
- package/src/adapter/render-memory.ts +0 -34
- package/src/adapter/renderer-config.ts +0 -84
- package/src/adapter/system-slot.ts +0 -301
- package/src/core/system-phase.ts +0 -52
- package/src/inspection.ts +0 -281
- package/src/manifest/configuration-kinds.ts +0 -241
- package/src/manifest/kind-modules.ts +0 -58
- package/src/manifest/runtime-environment.ts +0 -48
|
@@ -4,25 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
import type { PhysicsAdapter2D } from './physics-adapter-2d';
|
|
6
6
|
import { defineSeamShape, type SeamShape } from './seam-evidence';
|
|
7
|
-
import type {
|
|
8
|
-
AudioAdapter,
|
|
9
|
-
CameraAdapter,
|
|
10
|
-
DebugAdapter,
|
|
11
|
-
NavigationAdapter,
|
|
12
|
-
NetworkingAdapter,
|
|
13
|
-
PhysicsAdapter,
|
|
14
|
-
RenderDebugAdapter,
|
|
15
|
-
SystemAdapters,
|
|
16
|
-
} from './system-adapter';
|
|
7
|
+
import type { CameraAdapter, PhysicsAdapter, SystemAdapters } from './system-adapter';
|
|
17
8
|
|
|
18
9
|
export const SYSTEM_ADAPTERS_SHAPE = defineSeamShape<SystemAdapters>()({
|
|
19
10
|
physics: { optional: true, kind: 'value', required: 'effect' },
|
|
20
|
-
networking: { optional: true, kind: 'value', required: 'operation' },
|
|
21
|
-
navigation: { optional: true, kind: 'value', required: 'operation' },
|
|
22
|
-
audio: { optional: true, kind: 'value', required: 'effect' },
|
|
23
11
|
camera: { optional: true, kind: 'value', required: 'effect' },
|
|
24
|
-
debug: { optional: true, kind: 'value', required: 'operation' },
|
|
25
|
-
renderDebug: { optional: true, kind: 'value', required: 'effect' },
|
|
26
12
|
});
|
|
27
13
|
|
|
28
14
|
export const PHYSICS_ADAPTER_SHAPE = defineSeamShape<PhysicsAdapter>()({
|
|
@@ -48,91 +34,15 @@ export const PHYSICS_2D_ADAPTER_SHAPE = defineSeamShape<PhysicsAdapter2D>()({
|
|
|
48
34
|
unfreeze: { optional: false, kind: 'function', required: 'effect' },
|
|
49
35
|
});
|
|
50
36
|
|
|
51
|
-
export const NETWORKING_ADAPTER_SHAPE = defineSeamShape<NetworkingAdapter>()({
|
|
52
|
-
peers: { optional: false, kind: 'function', required: 'operation' },
|
|
53
|
-
networkId: { optional: false, kind: 'function', required: 'operation' },
|
|
54
|
-
authority: { optional: false, kind: 'function', required: 'operation' },
|
|
55
|
-
editable: { optional: false, kind: 'function', required: 'operation' },
|
|
56
|
-
getConnectionState: { optional: true, kind: 'function', required: 'operation' },
|
|
57
|
-
getRoomInfo: { optional: true, kind: 'function', required: 'operation' },
|
|
58
|
-
getReplicationStats: { optional: true, kind: 'function', required: 'operation' },
|
|
59
|
-
subscribe: { optional: false, kind: 'function', required: 'effect' },
|
|
60
|
-
getStateSnapshot: { optional: true, kind: 'function', required: 'operation' },
|
|
61
|
-
messageEvents: { optional: true, kind: 'function', required: 'operation' },
|
|
62
|
-
getRates: { optional: true, kind: 'function', required: 'operation' },
|
|
63
|
-
getConditioning: { optional: true, kind: 'function', required: 'operation' },
|
|
64
|
-
setConditioning: { optional: true, kind: 'function', required: 'effect' },
|
|
65
|
-
getServerConfig: { optional: true, kind: 'function', required: 'operation' },
|
|
66
|
-
getPlayerIdentity: { optional: true, kind: 'function', required: 'operation' },
|
|
67
|
-
setPlayerIdentity: { optional: true, kind: 'function', required: 'effect' },
|
|
68
|
-
getTrafficByType: { optional: true, kind: 'function', required: 'operation' },
|
|
69
|
-
sendMessage: { optional: true, kind: 'function', required: 'effect' },
|
|
70
|
-
getConditioningLimits: { optional: true, kind: 'function', required: 'operation' },
|
|
71
|
-
ping: { optional: true, kind: 'function', required: 'effect' },
|
|
72
|
-
inspectServer: { optional: true, kind: 'function', required: 'operation' },
|
|
73
|
-
disconnectClient: { optional: true, kind: 'function', required: 'effect' },
|
|
74
|
-
editServerState: { optional: true, kind: 'function', required: 'effect' },
|
|
75
|
-
deleteServerState: { optional: true, kind: 'function', required: 'effect' },
|
|
76
|
-
sendToClient: { optional: true, kind: 'function', required: 'effect' },
|
|
77
|
-
broadcast: { optional: true, kind: 'function', required: 'effect' },
|
|
78
|
-
disposeRoom: { optional: true, kind: 'function', required: 'effect' },
|
|
79
|
-
stateFieldType: { optional: true, kind: 'function', required: 'operation' },
|
|
80
|
-
clearTraffic: { optional: true, kind: 'function', required: 'effect' },
|
|
81
|
-
getTrafficByEntity: { optional: true, kind: 'function', required: 'operation' },
|
|
82
|
-
isRecording: { optional: true, kind: 'function', required: 'operation' },
|
|
83
|
-
setRecording: { optional: true, kind: 'function', required: 'effect' },
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
export const NAVIGATION_ADAPTER_SHAPE = defineSeamShape<NavigationAdapter>()({
|
|
87
|
-
hasNavMesh: { optional: false, kind: 'function', required: 'operation' },
|
|
88
|
-
findPath: { optional: false, kind: 'function', required: 'effect' },
|
|
89
|
-
debugMesh: { optional: false, kind: 'function', required: 'operation' },
|
|
90
|
-
bake: { optional: true, kind: 'function', required: 'effect' },
|
|
91
|
-
exportData: { optional: true, kind: 'function', required: 'operation' },
|
|
92
|
-
clear: { optional: true, kind: 'function', required: 'effect' },
|
|
93
|
-
crowdAgents: { optional: true, kind: 'function', required: 'operation' },
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
export const AUDIO_ADAPTER_SHAPE = defineSeamShape<AudioAdapter>()({
|
|
97
|
-
resume: { optional: true, kind: 'function', required: 'effect' },
|
|
98
|
-
setMuted: { optional: false, kind: 'function', required: 'effect' },
|
|
99
|
-
isMuted: { optional: false, kind: 'function', required: 'operation' },
|
|
100
|
-
graphSnapshot: { optional: true, kind: 'function', required: 'operation' },
|
|
101
|
-
transportState: { optional: true, kind: 'function', required: 'operation' },
|
|
102
|
-
acquireMeters: { optional: true, kind: 'function', required: 'effect' },
|
|
103
|
-
acquireRecordingStream: { optional: true, kind: 'function', required: 'effect' },
|
|
104
|
-
renderOffline: { optional: true, kind: 'function', required: 'operation' },
|
|
105
|
-
audioEvents: { optional: true, kind: 'function', required: 'operation' },
|
|
106
|
-
});
|
|
107
|
-
|
|
108
37
|
export const CAMERA_ADAPTER_SHAPE = defineSeamShape<CameraAdapter>()({
|
|
109
38
|
snapshot: { optional: false, kind: 'function', required: 'operation' },
|
|
110
39
|
subscribe: { optional: false, kind: 'function', required: 'effect' },
|
|
111
40
|
});
|
|
112
41
|
|
|
113
|
-
export const DEBUG_ADAPTER_SHAPE = defineSeamShape<DebugAdapter>()({
|
|
114
|
-
providers: { optional: false, kind: 'function', required: 'operation' },
|
|
115
|
-
state: { optional: false, kind: 'function', required: 'operation' },
|
|
116
|
-
stateAll: { optional: false, kind: 'function', required: 'operation' },
|
|
117
|
-
commands: { optional: false, kind: 'function', required: 'operation' },
|
|
118
|
-
invoke: { optional: false, kind: 'function', required: 'effect' },
|
|
119
|
-
events: { optional: false, kind: 'function', required: 'operation' },
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
export const RENDER_DEBUG_ADAPTER_SHAPE = defineSeamShape<RenderDebugAdapter>()({
|
|
123
|
-
captureFrame: { optional: false, kind: 'function', required: 'effect' },
|
|
124
|
-
memorySnapshot: { optional: true, kind: 'function', required: 'operation' },
|
|
125
|
-
});
|
|
126
|
-
|
|
127
42
|
type NonPhysicsSystemSlot = Exclude<keyof SystemAdapters, 'physics'>;
|
|
128
43
|
|
|
129
44
|
export const SYSTEM_PROVIDER_SHAPES: {
|
|
130
45
|
readonly [K in NonPhysicsSystemSlot]: SeamShape<NonNullable<SystemAdapters[K]>>;
|
|
131
46
|
} = {
|
|
132
|
-
networking: NETWORKING_ADAPTER_SHAPE,
|
|
133
|
-
navigation: NAVIGATION_ADAPTER_SHAPE,
|
|
134
|
-
audio: AUDIO_ADAPTER_SHAPE,
|
|
135
47
|
camera: CAMERA_ADAPTER_SHAPE,
|
|
136
|
-
debug: DEBUG_ADAPTER_SHAPE,
|
|
137
|
-
renderDebug: RENDER_DEBUG_ADAPTER_SHAPE,
|
|
138
48
|
};
|
package/src/adapter/transform.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The neutral transform value used across adapter interfaces. Position + scale
|
|
3
|
-
* are xyz; rotation is a quaternion (xyzw) — matching
|
|
4
|
-
* adapters never have to agree on an
|
|
3
|
+
* are xyz; rotation is a quaternion (xyzw) — matching, e.g., the 3D
|
|
4
|
+
* integration's scene-object quaternion, so adapters never have to agree on an
|
|
5
|
+
* Euler convention.
|
|
5
6
|
*/
|
|
6
7
|
export interface Transform {
|
|
7
8
|
position: [number, number, number];
|
|
@@ -21,7 +22,7 @@ export interface Transform {
|
|
|
21
22
|
* `'unresolved'` is the answer for a node THIS ADAPTER DOES NOT KNOW, and it is
|
|
22
23
|
* a distinct answer on purpose: every other member names a driver, so returning
|
|
23
24
|
* one of them for an unknown id is a positive claim about a node the
|
|
24
|
-
* implementer has never seen. A
|
|
25
|
+
* implementer has never seen. A project-scoped physics adapter is asked about
|
|
25
26
|
* every node the editor touches, including nodes belonging to another root —
|
|
26
27
|
* "not mine" has to be sayable.
|
|
27
28
|
*/
|
|
@@ -1,52 +1,37 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
* layout contribution
|
|
1
|
+
/**
|
|
2
|
+
* Dock arrangement data shared by layout contributions. Each package keeps
|
|
3
|
+
* its workspace arrangement beside the owning layout contribution.
|
|
4
|
+
*/
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* and Animation a top/bottom split, and both the dock and the frame have
|
|
12
|
-
* exactly that shape in editor groups — a second group beside or below the
|
|
13
|
-
* document, with the workspace's own split ratio. So the UV editor, the node
|
|
14
|
-
* editor and the image view are DOCUMENTS a workspace opens
|
|
15
|
-
* into editor groups, never drawer views; the drawer keeps the utilities that
|
|
16
|
-
* are not Blender areas (Console, Profiler, Network).
|
|
17
|
-
*
|
|
18
|
-
* THIS AMENDS "a workspace positions chrome and dock groups only; it never
|
|
19
|
-
* opens documents" (ARCHITECTURE-CORE §Editor chrome, 2026-08-30), and the
|
|
20
|
-
* amendment is narrow: a workspace opens the documents ITS OWN REFERENCE
|
|
21
|
-
* FRAME'S SCREEN HAS, named here, and never a session's content. That is why
|
|
22
|
-
* `normalizePresetLayout` still strips every `kind: 'document'` panel out of a
|
|
23
|
-
* captured arrangement — the capture carries the GROUP and its settled size,
|
|
24
|
-
* this list carries which document fills it, and the two never overlap.
|
|
7
|
+
* A workspace editor area is an editor group filled by a declared document.
|
|
8
|
+
* Areas specify their position and initial split ratio; the drawer hosts
|
|
9
|
+
* utilities. Captured arrangements retain group geometry, while this list
|
|
10
|
+
* names each area’s document contribution. normalizePresetLayout removes
|
|
11
|
+
* captured document panels so the two declarations do not overlap.
|
|
25
12
|
*/
|
|
26
13
|
export interface WorkspaceAreaContribution {
|
|
27
|
-
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
|
|
31
|
-
* (WORK.md §THE TIMELINE item 1, landed 2026-09-20). */
|
|
14
|
+
/**
|
|
15
|
+
* Stable workspace-scoped area id. The dock keys its group as
|
|
16
|
+
* volter:area:<id>; the native frame maps the same id to its editor group.
|
|
17
|
+
*/
|
|
32
18
|
readonly id: string;
|
|
33
|
-
/** The `workspace.document`
|
|
34
|
-
*
|
|
35
|
-
* document id: the document is minted per open subject. */
|
|
19
|
+
/** The declared `workspace.document` contribution id this area opens.
|
|
20
|
+
* The document instance id is minted per open subject. */
|
|
36
21
|
readonly document: string;
|
|
37
22
|
/** Where the group sits relative to the centre document's group. */
|
|
38
23
|
readonly place: 'left' | 'right' | 'above' | 'below';
|
|
39
|
-
/** The area's share of that split, 0..1
|
|
40
|
-
*
|
|
24
|
+
/** The area's share of that split, 0..1, with its source cited by the
|
|
25
|
+
* workspace that declares it. It is the BOOTSTRAP size only:
|
|
41
26
|
* once a capture of this workspace exists, the captured leaf's own size
|
|
42
27
|
* wins, exactly as the drawer's captured height wins over its default.
|
|
43
28
|
* Under the frame the same rule is `setSize` applied ONCE at group
|
|
44
29
|
* creation, against the two groups' combined extent — a person's drag on
|
|
45
30
|
* the sash is never overwritten, and a group they closed is re-created at
|
|
46
|
-
* this ratio on the next reconcile, because
|
|
47
|
-
*
|
|
31
|
+
* this ratio on the next reconcile, because an area is resized or joined,
|
|
32
|
+
* never closed. The one other stand-up is the area's document
|
|
48
33
|
* changing its job and asking for that job's size
|
|
49
|
-
* (
|
|
34
|
+
* (editor-core's `src/workspace-areas.ts`' `setWorkspaceAreaRatio`). */
|
|
50
35
|
readonly ratio: number;
|
|
51
36
|
}
|
|
52
37
|
|
|
@@ -55,35 +40,30 @@ export interface WorkspaceArrangement {
|
|
|
55
40
|
readonly id: string;
|
|
56
41
|
readonly title: string;
|
|
57
42
|
readonly regions?: {
|
|
58
|
-
/** The WORKSPACE tab strip in the top bar
|
|
43
|
+
/** The WORKSPACE tab strip in the top bar; a click on a tab
|
|
59
44
|
* is the explicit act that switches. Absent means hidden. */
|
|
60
45
|
readonly workspaceTabs?: 'shown' | 'hidden';
|
|
61
46
|
readonly header?: 'shown' | 'hidden';
|
|
62
47
|
readonly shelf?: 'shown' | 'hidden';
|
|
63
48
|
readonly inspector?: 'column' | 'properties';
|
|
64
49
|
readonly drawer?: 'shown' | 'hidden';
|
|
65
|
-
/** The top bar's runtime TELEMETRY cluster — the frame-rate readout, its
|
|
66
|
-
* sparkline and the audio meter. `hidden` leaves the corner to the quiet
|
|
67
|
-
* product controls (Invite, Account); the Profiler utility remains the
|
|
68
|
-
* door to the same numbers. Absent means shown. */
|
|
69
|
-
readonly telemetry?: 'shown' | 'hidden';
|
|
70
50
|
/** The `·TypeName` a component-instance row prints after its name in the
|
|
71
|
-
* hierarchy. `hidden` leaves the row its NAME alone,
|
|
72
|
-
*
|
|
51
|
+
* hierarchy. `hidden` leaves the row its NAME alone, for a look whose
|
|
52
|
+
* type is the row's glyph. The type stays reachable
|
|
73
53
|
* through the row's own tooltip and the inspector. Absent means shown. */
|
|
74
54
|
readonly hierarchyTypeSuffix?: 'shown' | 'hidden';
|
|
75
55
|
/** The dotted RULE a component-instance row draws under its name in the
|
|
76
|
-
* hierarchy. `hidden` leaves the name unmarked,
|
|
77
|
-
*
|
|
78
|
-
* state. The fact stays reachable through the row's own tooltip, its Go to
|
|
56
|
+
* hierarchy. `hidden` leaves the name unmarked, for a look that marks an
|
|
57
|
+
* instance nowhere in that panel, in any selection state. The fact stays reachable through the row's own tooltip, its Go to
|
|
79
58
|
* Callsite / Open Component Source actions and the inspector. Absent means
|
|
80
59
|
* shown. */
|
|
81
60
|
readonly hierarchyInstanceRule?: 'shown' | 'hidden';
|
|
82
61
|
/** Which restriction columns a hierarchy row carries at its right edge:
|
|
83
62
|
* this editor's selection lock beside the eye (`select+viewport`), or
|
|
84
|
-
*
|
|
85
|
-
* column with the render column reserved and blank,
|
|
86
|
-
* visibility flag for viewport and
|
|
63
|
+
* the viewport and render pair (`viewport+render`) — the eye in its own
|
|
64
|
+
* column with the render column reserved and blank, since e.g. the 3D
|
|
65
|
+
* integration's scene objects have one visibility flag for viewport and
|
|
66
|
+
* render alike. Absent means the pair. */
|
|
87
67
|
readonly hierarchyRestrictions?: 'select+viewport' | 'viewport+render';
|
|
88
68
|
};
|
|
89
69
|
/** The workspace's EDITOR AREAS beside the centre document — see
|
|
@@ -1,21 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The editor
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* three servers (dev / prod / packaged), and a client default — a duplicated
|
|
6
|
-
* literal across those drifts silently.
|
|
7
|
-
*
|
|
8
|
-
* **Why not Vite's 5173.** It used to be exactly that, and 5173 is the default
|
|
9
|
-
* of every Vite app on the machine. On a box running any other Vite dev server
|
|
10
|
-
* the engine-repo editor and that app fight over one port, and — worse —
|
|
11
|
-
* `http://…:5173` can resolve to a FOREIGN application. Measured 2026-08-07:
|
|
12
|
-
* an unrelated process on 5173 was what the editor's tab self-heal opened.
|
|
13
|
-
*
|
|
14
|
-
* **The band.** 20000-20199 is Volter's own reserved band: fixed, hand-picked
|
|
15
|
-
* ports that must never be minted for a worktree session. Machine-local
|
|
16
|
-
* allocations start at 20200, so one can never land on this default by chance.
|
|
17
|
-
* 20173 keeps Vite's memorable `173` tail while sitting in a range nothing
|
|
18
|
-
* else claims.
|
|
2
|
+
* The editor’s default port and paired HMR-socket port. Launchers, servers and
|
|
3
|
+
* clients share this constant. The reserved 20000–20199 band is excluded from
|
|
4
|
+
* machine-local session allocation, which starts at 20200.
|
|
19
5
|
*/
|
|
20
6
|
export const DEFAULT_EDITOR_PORT = 20_173;
|
|
21
7
|
|
|
@@ -26,9 +12,8 @@ export const DEFAULT_EDITOR_PORT = 20_173;
|
|
|
26
12
|
* worktree sessions cannot collide merely because their editor ports differ
|
|
27
13
|
* by 100. Anything outside that band keeps the simple +100 offset.
|
|
28
14
|
*
|
|
29
|
-
* `DEFAULT_EDITOR_PORT` derives 30173 through the first arm
|
|
30
|
-
*
|
|
31
|
-
* exception precisely because the default sat outside the band it designed.
|
|
15
|
+
* `DEFAULT_EDITOR_PORT` derives 30173 through the first arm, with no special
|
|
16
|
+
* case.
|
|
32
17
|
*/
|
|
33
18
|
export function editorHmrPort(editorPort: number): number {
|
|
34
19
|
return editorPort >= 20_000 && editorPort < 30_100 ? editorPort + 10_000 : editorPort + 100;
|
|
@@ -44,23 +29,12 @@ function urlAuthorityHost(host: string): string {
|
|
|
44
29
|
}
|
|
45
30
|
|
|
46
31
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* **It never says `localhost`.** A bare-`localhost` URL is a different address
|
|
51
|
-
* from the one the server is listening on: on a dual-stack box `localhost`
|
|
52
|
-
* resolves `::1` first while the editor binds `127.0.0.1`, so the URL reaches
|
|
53
|
-
* whatever else happens to hold that port on IPv6. Measured 2026-08-07 — the
|
|
54
|
-
* tab bijection's self-heal opened an UNRELATED application in the owner's
|
|
55
|
-
* browser because it composed its reopen URL from `localhost` instead of the
|
|
56
|
-
* host the server had actually bound.
|
|
32
|
+
* Compose an editor origin from its actual bind host. Callers that only know
|
|
33
|
+
* a port use IPv4 loopback. Wildcard binds also use loopback for a visitable
|
|
34
|
+
* local URL, including WSL2 loopback forwarding.
|
|
57
35
|
*
|
|
58
|
-
* `
|
|
59
|
-
*
|
|
60
|
-
* and get the IPv4 loopback literal. A wildcard bind (`0.0.0.0` / `::`, which
|
|
61
|
-
* is what WSL gets) is not an address a browser can visit, so it degrades to
|
|
62
|
-
* that same literal: reachable from the same box, and reachable from Windows
|
|
63
|
-
* through WSL2's loopback forwarding.
|
|
36
|
+
* A `localhost` URL can resolve to IPv6 while the server binds IPv4, reaching
|
|
37
|
+
* a different listener; emitted origins therefore use an explicit address.
|
|
64
38
|
*/
|
|
65
39
|
export function editorOrigin(port: number, bindHost?: string): string {
|
|
66
40
|
const host = (bindHost ?? '').trim();
|
package/src/manifest/filename.ts
CHANGED
|
@@ -1,45 +1,6 @@
|
|
|
1
|
-
// The project manifest's FILENAME. There is exactly ONE
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// (the legacy-removal doctrine, `docs/ARCHITECTURE-CORE.md` §Vocabulary). Two
|
|
5
|
-
// accepted names is the defect, not the convenience: both get written, and
|
|
6
|
-
// copies of the "try both" probe spread into modules that agree with the real
|
|
7
|
-
// resolver only by coincidence.
|
|
8
|
-
//
|
|
9
|
-
// The removed name REJECTS LOUDLY, naming the exact one-line fix — never
|
|
10
|
-
// silently ignored, never redirected. A directory
|
|
11
|
-
// carrying only `volter.game.json` is an error with a `git mv` in the message,
|
|
12
|
-
// not a project that quietly fails to open.
|
|
13
|
-
//
|
|
14
|
-
// This module is the ONE place either literal is spelled out, and it is
|
|
15
|
-
// dependency-free (no `node:fs`) so browser bundles can import it. The
|
|
16
|
-
// filesystem side lives in `./locate` (`resolveManifestPath`, `hasManifest`,
|
|
17
|
-
// `assertNoRemovedManifestFilename`).
|
|
1
|
+
// The project manifest's FILENAME. There is exactly ONE, and this module is the one place it is
|
|
2
|
+
// spelled out. It is dependency-free (no `node:fs`) so browser bundles can import it; the
|
|
3
|
+
// filesystem side lives in `./locate` (`resolveManifestPath`, `hasManifest`).
|
|
18
4
|
|
|
19
5
|
/** The manifest filename. Every reader reads it; every writer writes it. */
|
|
20
6
|
export const MANIFEST_FILENAME = 'volter.project.json';
|
|
21
|
-
|
|
22
|
-
/** The removed filename. Read by nothing — its only job is the error message. */
|
|
23
|
-
export const REMOVED_MANIFEST_FILENAME = 'volter.game.json';
|
|
24
|
-
|
|
25
|
-
/** True for the manifest filename (basename comparison). */
|
|
26
|
-
export function isManifestFilename(name: string): boolean {
|
|
27
|
-
return name === MANIFEST_FILENAME;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* The verbatim rejection an on-disk `volter.game.json` earns — the one-line fix
|
|
32
|
-
* spelled out, per the removed-format contract. Shared by the Node
|
|
33
|
-
* (`./locate`) and browser (storage-backend) halves so a project author sees
|
|
34
|
-
* the same sentence wherever the stale name is found.
|
|
35
|
-
*/
|
|
36
|
-
export function removedManifestFilenameMessage(where: string): string {
|
|
37
|
-
return (
|
|
38
|
-
`${where}: found \`${REMOVED_MANIFEST_FILENAME}\` and no \`${MANIFEST_FILENAME}\`. ` +
|
|
39
|
-
`\`${REMOVED_MANIFEST_FILENAME}\` was REMOVED (the legacy-removal doctrine, ` +
|
|
40
|
-
'docs/ARCHITECTURE-CORE.md §Vocabulary) — nothing reads it any more, and it is ' +
|
|
41
|
-
'deliberately NOT read as a fallback, because a second accepted name is the defect.\n' +
|
|
42
|
-
`Fix: rename it — \`git mv ${REMOVED_MANIFEST_FILENAME} ${MANIFEST_FILENAME}\`. ` +
|
|
43
|
-
'The contents are unchanged; only the filename moved.'
|
|
44
|
-
);
|
|
45
|
-
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Node-only path wrapper around `loadProjectManifest`.
|
|
2
2
|
//
|
|
3
3
|
// Kept in its own module (rather than folded into `load.ts`) so that
|
|
4
|
-
// `
|
|
4
|
+
// `loadProjectManifest` itself stays free of `node:fs` — anything that only
|
|
5
5
|
// needs the pure parse/resolve function (e.g. a future browser/hosted-editor
|
|
6
6
|
// caller) can import `./load` without pulling a Node-only module into its
|
|
7
7
|
// bundle.
|
|
8
8
|
|
|
9
9
|
import { readFileSync } from 'node:fs';
|
|
10
|
-
import {
|
|
10
|
+
import { loadProjectManifest, type ResolvedProjectManifest } from './load';
|
|
11
11
|
import { resolveManifestPath } from './locate';
|
|
12
12
|
|
|
13
13
|
// The dual-name filesystem lookup lives in `./locate` (Zod-free on purpose);
|
|
@@ -15,19 +15,13 @@ import { resolveManifestPath } from './locate';
|
|
|
15
15
|
// module.
|
|
16
16
|
export { hasManifest, manifestWritePath, resolveManifestPath } from './locate';
|
|
17
17
|
|
|
18
|
-
/** Read + JSON.parse `path`, then resolve it via `
|
|
19
|
-
export function
|
|
20
|
-
path: string,
|
|
21
|
-
options: LoadGameManifestOptions = {},
|
|
22
|
-
): ResolvedGameManifest {
|
|
18
|
+
/** Read + JSON.parse `path`, then resolve it via `loadProjectManifest`. Node-only. */
|
|
19
|
+
export function loadProjectManifestFile(path: string): ResolvedProjectManifest {
|
|
23
20
|
const raw: unknown = JSON.parse(readFileSync(path, 'utf-8'));
|
|
24
|
-
return
|
|
21
|
+
return loadProjectManifest(raw);
|
|
25
22
|
}
|
|
26
23
|
|
|
27
|
-
/** {@link
|
|
28
|
-
export function
|
|
29
|
-
dir
|
|
30
|
-
options: LoadGameManifestOptions = {},
|
|
31
|
-
): ResolvedGameManifest {
|
|
32
|
-
return loadGameManifestFile(resolveManifestPath(dir), options);
|
|
24
|
+
/** {@link loadProjectManifestFile} against `dir`'s `volter.project.json`. */
|
|
25
|
+
export function loadProjectManifestDir(dir: string): ResolvedProjectManifest {
|
|
26
|
+
return loadProjectManifestFile(resolveManifestPath(dir));
|
|
33
27
|
}
|