@forgeax/engine-project 0.1.3 → 0.1.6
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 +3 -8
- package/dist/.tsbuildinfo +1 -1
- package/dist/errors.d.ts +0 -8
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +14 -61
- package/dist/index.mjs.map +1 -1
- package/dist/loader.d.ts +3 -6
- package/dist/loader.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/errors.test-d.ts +0 -8
- package/src/__tests__/errors.test.ts +2 -23
- package/src/__tests__/loader.test.ts +5 -90
- package/src/__tests__/resolve.test.ts +0 -44
- package/src/__tests__/structural.test.ts +13 -1
- package/src/errors.ts +1 -12
- package/src/index.ts +1 -3
- package/src/loader.ts +19 -81
package/src/loader.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
// loader.ts — loadGameProject + resolveDefaultScene (D-6, AC-06/07/08/09)
|
|
2
2
|
//
|
|
3
3
|
// loadGameProject: injection-only `read:(path)=>Promise<string>`, returns
|
|
4
|
-
// GuidResult<GameProject, GameProjectError>.
|
|
5
|
-
//
|
|
6
|
-
// validation then has the remaining five return paths:
|
|
4
|
+
// GuidResult<GameProject, GameProjectError>. Five return paths:
|
|
5
|
+
// 1. read throws → ok:false, error(code='forge-missing')
|
|
7
6
|
// 2. JSON.parse fails → ok:false, error(code='forge-parse-failed')
|
|
8
7
|
// 3. schema.parse fails (missing fields) → ok:false, error(code='forge-schema-invalid')
|
|
9
8
|
// 3b. schema.parse fails (unknown fields via .strict()) → ok:false, error(code='forge-unknown-field')
|
|
@@ -18,7 +17,6 @@
|
|
|
18
17
|
|
|
19
18
|
import type { GuidResult } from '@forgeax/engine-pack/guid';
|
|
20
19
|
import {
|
|
21
|
-
type ForgeReadFailureCauseCode,
|
|
22
20
|
GameProjectError,
|
|
23
21
|
type GameProjectErrorArgs,
|
|
24
22
|
type GameProjectErrorCode,
|
|
@@ -42,73 +40,6 @@ function ok<T>(value: T) {
|
|
|
42
40
|
return { ok: true as const, value };
|
|
43
41
|
}
|
|
44
42
|
|
|
45
|
-
type ReaderErrorMetadata = {
|
|
46
|
-
readonly code?: unknown;
|
|
47
|
-
readonly name?: unknown;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
function readerFailureToken(error: unknown): string | undefined {
|
|
51
|
-
if (typeof error !== 'object' || error === null) {
|
|
52
|
-
return undefined;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const metadata = error as ReaderErrorMetadata;
|
|
56
|
-
if (typeof metadata.code === 'string') {
|
|
57
|
-
return metadata.code;
|
|
58
|
-
}
|
|
59
|
-
if (typeof metadata.name === 'string') {
|
|
60
|
-
return metadata.name;
|
|
61
|
-
}
|
|
62
|
-
return undefined;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function readerFailureCause(token: string | undefined): ForgeReadFailureCauseCode {
|
|
66
|
-
switch (token) {
|
|
67
|
-
case 'EACCES':
|
|
68
|
-
case 'EPERM':
|
|
69
|
-
case 'PermissionDeniedError':
|
|
70
|
-
case 'NotAllowedError':
|
|
71
|
-
case 'SecurityError':
|
|
72
|
-
return 'permission-denied';
|
|
73
|
-
case 'EAGAIN':
|
|
74
|
-
case 'EBUSY':
|
|
75
|
-
case 'EINTR':
|
|
76
|
-
case 'ECONNABORTED':
|
|
77
|
-
case 'ECONNREFUSED':
|
|
78
|
-
case 'ECONNRESET':
|
|
79
|
-
case 'EHOSTUNREACH':
|
|
80
|
-
case 'ENETDOWN':
|
|
81
|
-
case 'ENETUNREACH':
|
|
82
|
-
case 'ETIMEDOUT':
|
|
83
|
-
case 'EPIPE':
|
|
84
|
-
case 'ABORT_ERR':
|
|
85
|
-
case 'AbortError':
|
|
86
|
-
case 'TimeoutError':
|
|
87
|
-
return 'transient';
|
|
88
|
-
default:
|
|
89
|
-
return 'unknown';
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function readerFailure(error: unknown) {
|
|
94
|
-
const token = readerFailureToken(error);
|
|
95
|
-
if (token === 'ENOENT') {
|
|
96
|
-
return err(
|
|
97
|
-
'forge-missing',
|
|
98
|
-
'forge.json file to exist at the game root',
|
|
99
|
-
'verify the game directory contains forge.json; if this is a new game, scaffold via the Studio UI or create a forge.json with id, name, schemaVersion fields',
|
|
100
|
-
{ path: FORGE_JSON },
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return err(
|
|
105
|
-
'forge-read-failed',
|
|
106
|
-
'forge.json reader to complete without refusal',
|
|
107
|
-
'retry the project reader; if the refusal persists, verify access to the game directory',
|
|
108
|
-
{ path: FORGE_JSON, cause: readerFailureCause(token) },
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
43
|
// ── validateGameProject: sync pure core (JSON.parse → schema validate → 5 return paths) ──
|
|
113
44
|
|
|
114
45
|
/**
|
|
@@ -198,15 +129,13 @@ export function validateGameProject(raw: string): GuidResult<GameProject, GamePr
|
|
|
198
129
|
* Load and validate a game project manifest from the abstract async reader.
|
|
199
130
|
*
|
|
200
131
|
* Wraps `validateGameProject`: reads `FORGE_JSON` via `read`, then validates.
|
|
201
|
-
* If `read` throws/rejects → `forge-missing
|
|
202
|
-
* `forge-read-failed` with a bounded cause code.
|
|
132
|
+
* If `read` throws/rejects → `forge-missing`.
|
|
203
133
|
*
|
|
204
134
|
* ## Return paths (5)
|
|
205
135
|
*
|
|
206
136
|
* | # | Condition | ok | code |
|
|
207
137
|
* |:--|:--|:--|:--|
|
|
208
|
-
* | 1 | `read(FORGE_JSON)` throws / rejects
|
|
209
|
-
* | 1b | `read(FORGE_JSON)` refuses for another reason | false | `forge-read-failed` |
|
|
138
|
+
* | 1 | `read(FORGE_JSON)` throws / rejects | false | `forge-missing` |
|
|
210
139
|
* | 2 | `JSON.parse` fails | false | `forge-parse-failed` |
|
|
211
140
|
* | 3a | zod `.strict()` unknown fields (e.g. `scenes[]`) | false | `forge-unknown-field` |
|
|
212
141
|
* | 3b | zod required field missing / wrong type | false | `forge-schema-invalid` |
|
|
@@ -228,8 +157,13 @@ export async function loadGameProject(
|
|
|
228
157
|
let raw: string;
|
|
229
158
|
try {
|
|
230
159
|
raw = await read(FORGE_JSON);
|
|
231
|
-
} catch (
|
|
232
|
-
return
|
|
160
|
+
} catch (_e: unknown) {
|
|
161
|
+
return err(
|
|
162
|
+
'forge-missing',
|
|
163
|
+
'forge.json file to exist at the game root',
|
|
164
|
+
'verify the game directory contains forge.json; if this is a new game, scaffold via the Studio UI or create a forge.json with id, name, schemaVersion fields',
|
|
165
|
+
{ path: FORGE_JSON },
|
|
166
|
+
);
|
|
233
167
|
}
|
|
234
168
|
|
|
235
169
|
return validateGameProject(raw);
|
|
@@ -242,8 +176,7 @@ export async function loadGameProject(
|
|
|
242
176
|
* (e.g. sync `ContextSlot` content functions).
|
|
243
177
|
*
|
|
244
178
|
* Accepts a sync `read:(path)=>string` injection (same contract shape,
|
|
245
|
-
* sync return). If `read` throws → `forge-missing
|
|
246
|
-
* `forge-read-failed` with a bounded cause code.
|
|
179
|
+
* sync return). If `read` throws → `forge-missing`.
|
|
247
180
|
*
|
|
248
181
|
* Uses the same `validateGameProject` pure core as `loadGameProject`.
|
|
249
182
|
*/
|
|
@@ -253,8 +186,13 @@ export function loadGameProjectSync(
|
|
|
253
186
|
let raw: string;
|
|
254
187
|
try {
|
|
255
188
|
raw = read(FORGE_JSON);
|
|
256
|
-
} catch (
|
|
257
|
-
return
|
|
189
|
+
} catch (_e: unknown) {
|
|
190
|
+
return err(
|
|
191
|
+
'forge-missing',
|
|
192
|
+
'forge.json file to exist at the game root',
|
|
193
|
+
'verify the game directory contains forge.json; if this is a new game, scaffold via the Studio UI or create a forge.json with id, name, schemaVersion fields',
|
|
194
|
+
{ path: FORGE_JSON },
|
|
195
|
+
);
|
|
258
196
|
}
|
|
259
197
|
|
|
260
198
|
return validateGameProject(raw);
|