@forgeax/engine-pack 0.1.27 → 0.1.29
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 +30 -17
- package/dist/build.mjs +35 -52
- package/dist/build.mjs.map +1 -1
- package/dist/cli-asset.d.ts +1 -1
- package/dist/cli-asset.d.ts.map +1 -1
- package/dist/cli-asset.mjs +40 -33
- package/dist/cli-asset.mjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/inventory/declaration.d.ts +1 -1
- package/dist/inventory/declaration.d.ts.map +1 -1
- package/dist/inventory/sync.d.ts.map +1 -1
- package/dist/native-cooker-registry.d.ts +1 -0
- package/dist/native-cooker-registry.d.ts.map +1 -1
- package/dist/native-cooker.mjs.map +1 -1
- package/dist/pack-authoring-node.mjs +22 -13
- package/dist/pack-authoring-node.mjs.map +1 -1
- package/dist/runtime.mjs +1 -1
- package/dist/runtime.mjs.map +1 -1
- package/dist/scanner.mjs +22 -13
- package/dist/scanner.mjs.map +1 -1
- package/dist/schema-compiled.d.ts.map +1 -1
- package/dist/schema.mjs +41 -63
- package/dist/schema.mjs.map +1 -1
- package/dist/scriptable-pack-node.d.ts.map +1 -1
- package/dist/scriptable-pack-node.mjs +11 -3
- package/dist/scriptable-pack-node.mjs.map +1 -1
- package/dist/scriptable-pack-worker.mjs +5 -1
- package/dist/scriptable-pack-worker.mjs.map +1 -1
- package/package.json +3 -6
- package/schema/meta.schema.json +1 -1
- package/src/__tests__/atlas-cli-region-mismatch.test.ts +1 -1
- package/src/__tests__/cli.unit.test.ts +1 -1
- package/src/__tests__/inventory-schema.test.ts +10 -10
- package/src/__tests__/pack-authoring.unit.test.ts +2 -4
- package/src/__tests__/pack.unit.test.ts +89 -127
- package/src/__tests__/scanner-instance-cycle.test.ts +92 -0
- package/src/__tests__/scanner-inventory.contract.test.ts +2 -2
- package/src/__tests__/scriptable-pack-diagnostic.integration.test.ts +45 -0
- package/src/atlas/run-atlas.ts +3 -3
- package/src/atlas/shelf-pack.ts +1 -1
- package/src/atlas/upng-ambient.d.ts +1 -1
- package/src/cli-asset.ts +22 -23
- package/src/inventory/binding.ts +3 -3
- package/src/inventory/declaration.ts +10 -48
- package/src/inventory/sync.ts +3 -2
- package/src/native-cooker-registry.ts +1 -0
- package/src/scanner.ts +29 -29
- package/src/schema-compiled.ts +40 -79
- package/src/scriptable-pack-node.ts +11 -3
- package/src/scriptable-pack-worker.ts +8 -1
- package/src/__tests__/scanner-mount-cycle.test.ts +0 -232
package/src/schema-compiled.ts
CHANGED
|
@@ -75,108 +75,69 @@ export function buildSceneAssetValidator(
|
|
|
75
75
|
componentsProperties[name] = sub;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
// feat-20260713-mount-override-component-add-and-shared-ref-round M1 / w3;
|
|
80
|
-
// requirements §S-10 / AC-03): validates each entry in mounts[].overrides[].
|
|
81
|
-
// `field` is optional — it is a component-granular add-or-patch discriminant
|
|
82
|
-
// (present -> patch one field with `value`; absent -> add/upsert the whole
|
|
83
|
-
// `comp` with `value` as its per-field map). `localId` / `comp` / `value`
|
|
84
|
-
// stay required; `additionalProperties: false` rejects any `op`-tag
|
|
85
|
-
// discriminant so downstream consumers never branch on it. Field-level type
|
|
86
|
-
// validation (matching the schema vocab) is enforced at runtime via
|
|
87
|
-
// setSceneOverride (D-9 / EcsErrorCode 'scene-override-type-mismatch').
|
|
88
|
-
const mountOverrideSchema = {
|
|
78
|
+
const componentsSchema = {
|
|
89
79
|
type: 'object',
|
|
90
80
|
additionalProperties: false,
|
|
91
|
-
|
|
92
|
-
properties: {
|
|
93
|
-
localId: { type: 'integer', minimum: 0 },
|
|
94
|
-
comp: { type: 'string', minLength: 1 },
|
|
95
|
-
field: { type: 'string', minLength: 1 },
|
|
96
|
-
value: {},
|
|
97
|
-
},
|
|
81
|
+
properties: componentsProperties,
|
|
98
82
|
};
|
|
99
|
-
|
|
100
|
-
// SceneInstanceMount schema (feat-20260608-scene-nesting-ecs-fication
|
|
101
|
-
// M1 / w11; requirements §S-10): validates each mounts[] entry. parent /
|
|
102
|
-
// components / overrides are optional; cross-mount window collision and
|
|
103
|
-
// mount.source / mount.memberCount agreement with referenced child
|
|
104
|
-
// SceneAsset are checked at the build-time scanner (D-1, w14), not here.
|
|
105
|
-
const mountSchema = {
|
|
83
|
+
const instanceSchema = {
|
|
106
84
|
type: 'object',
|
|
107
85
|
additionalProperties: false,
|
|
108
|
-
required: ['
|
|
86
|
+
required: ['source'],
|
|
109
87
|
properties: {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
additionalProperties: false,
|
|
118
|
-
properties: componentsProperties,
|
|
88
|
+
// Authored scenes use a GUID string. Pack output may carry a refs[]
|
|
89
|
+
// index, which is resolved by the Scene decoder before instantiation.
|
|
90
|
+
source: {
|
|
91
|
+
oneOf: [
|
|
92
|
+
{ type: 'string', minLength: 1 },
|
|
93
|
+
{ type: 'integer', minimum: 0 },
|
|
94
|
+
],
|
|
119
95
|
},
|
|
120
96
|
overrides: {
|
|
121
97
|
type: 'array',
|
|
122
|
-
items:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
'sourceRevision',
|
|
131
|
-
'publicationGeneration',
|
|
132
|
-
'outputDigest',
|
|
133
|
-
'outputSetDigest',
|
|
134
|
-
'receiptIdentity',
|
|
135
|
-
],
|
|
136
|
-
properties: {
|
|
137
|
-
schemaVersion: { type: 'string', const: 'scene-publication-fence/1' },
|
|
138
|
-
sourcePath: { type: 'string', minLength: 1 },
|
|
139
|
-
sourceRevision: { type: 'string', minLength: 1 },
|
|
140
|
-
publicationGeneration: { type: 'integer', minimum: 1 },
|
|
141
|
-
outputDigest: { type: 'string', minLength: 1 },
|
|
142
|
-
outputSetDigest: { type: 'string', minLength: 1 },
|
|
143
|
-
receiptIdentity: { type: 'string', minLength: 1 },
|
|
98
|
+
items: {
|
|
99
|
+
type: 'object',
|
|
100
|
+
additionalProperties: false,
|
|
101
|
+
required: ['target', 'components'],
|
|
102
|
+
properties: {
|
|
103
|
+
target: { type: 'array', minItems: 1, items: { type: 'string', minLength: 1 } },
|
|
104
|
+
components: componentsSchema,
|
|
105
|
+
},
|
|
144
106
|
},
|
|
145
107
|
},
|
|
146
108
|
},
|
|
147
109
|
};
|
|
148
|
-
|
|
110
|
+
const entitySchema = {
|
|
111
|
+
type: 'object',
|
|
112
|
+
additionalProperties: false,
|
|
113
|
+
required: ['components'],
|
|
114
|
+
properties: {
|
|
115
|
+
components: componentsSchema,
|
|
116
|
+
instance: instanceSchema,
|
|
117
|
+
},
|
|
118
|
+
};
|
|
149
119
|
const sceneSchema = {
|
|
150
120
|
type: 'object',
|
|
151
121
|
additionalProperties: false,
|
|
152
122
|
required: ['kind', 'entities'],
|
|
153
123
|
properties: {
|
|
154
124
|
kind: { type: 'string', const: 'scene' },
|
|
155
|
-
|
|
156
|
-
|
|
125
|
+
// Skin assets are explicit scene dependencies so async catalog loading
|
|
126
|
+
// can finish the joint-wiring path before the Scene is instantiated.
|
|
127
|
+
// Authored producers use GUID strings; Pack output may use refs[] indices.
|
|
128
|
+
skinGuids: {
|
|
157
129
|
type: 'array',
|
|
158
130
|
items: {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
localId: { type: 'integer', minimum: 0 },
|
|
164
|
-
bindingKey: { type: 'string', minLength: 1 },
|
|
165
|
-
components: {
|
|
166
|
-
type: 'object',
|
|
167
|
-
additionalProperties: false,
|
|
168
|
-
properties: componentsProperties,
|
|
169
|
-
},
|
|
170
|
-
},
|
|
131
|
+
oneOf: [
|
|
132
|
+
{ type: 'string', minLength: 1 },
|
|
133
|
+
{ type: 'integer', minimum: 0 },
|
|
134
|
+
],
|
|
171
135
|
},
|
|
172
136
|
},
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
mounts: {
|
|
178
|
-
type: 'array',
|
|
179
|
-
items: mountSchema,
|
|
137
|
+
entities: {
|
|
138
|
+
type: 'object',
|
|
139
|
+
propertyNames: { type: 'string', minLength: 1 },
|
|
140
|
+
additionalProperties: entitySchema,
|
|
180
141
|
},
|
|
181
142
|
},
|
|
182
143
|
};
|
|
@@ -266,9 +266,17 @@ class WorkerScriptablePackExecutor
|
|
|
266
266
|
}
|
|
267
267
|
};
|
|
268
268
|
worker.on('message', onMessage);
|
|
269
|
-
|
|
269
|
+
const fail = (error: Error): void => {
|
|
270
|
+
rejectLoad(error);
|
|
271
|
+
const active = this.build;
|
|
272
|
+
this.build = undefined;
|
|
273
|
+
active?.reject(error);
|
|
274
|
+
void this.dispose('failure');
|
|
275
|
+
};
|
|
276
|
+
worker.once('error', fail);
|
|
270
277
|
worker.once('exit', (code) => {
|
|
271
|
-
if (
|
|
278
|
+
if (this.disposal !== undefined) return;
|
|
279
|
+
fail(new Error(`ScriptablePack worker exited with code ${code}`));
|
|
272
280
|
});
|
|
273
281
|
});
|
|
274
282
|
}
|
|
@@ -393,7 +401,7 @@ class ReusableWorkerScriptablePackExecutor
|
|
|
393
401
|
void this.dispose('failure');
|
|
394
402
|
});
|
|
395
403
|
this.worker.on('exit', (code) => {
|
|
396
|
-
if (
|
|
404
|
+
if (this.disposal !== undefined) return;
|
|
397
405
|
this.failed = true;
|
|
398
406
|
this.rejectActive(new Error(`ScriptablePack worker exited with code ${code}`));
|
|
399
407
|
void this.dispose('failure');
|
|
@@ -215,7 +215,14 @@ function compileModuleClosure(entryPath: string, outputRoot: string, loadId?: nu
|
|
|
215
215
|
if (errors.length > 0) {
|
|
216
216
|
throw new Error(
|
|
217
217
|
`ScriptablePack TypeScript transpile failed for ${path}: ${errors
|
|
218
|
-
.map((diagnostic) =>
|
|
218
|
+
.map((diagnostic) => {
|
|
219
|
+
const position = diagnostic.file?.getLineAndCharacterOfPosition(diagnostic.start ?? 0);
|
|
220
|
+
const location =
|
|
221
|
+
position === undefined
|
|
222
|
+
? path
|
|
223
|
+
: `${path}:${position.line + 1}:${position.character + 1}`;
|
|
224
|
+
return `${location}: ${ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`;
|
|
225
|
+
})
|
|
219
226
|
.join('; ')}`,
|
|
220
227
|
);
|
|
221
228
|
}
|
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
// scanner-mount-cycle.test - scanner step-6 mount-asset cycle coverage
|
|
2
|
-
// (feat-20260608-scene-nesting-ecs-fication M1 / w13).
|
|
3
|
-
//
|
|
4
|
-
// Coverage (AC-12; plan-strategy D-1):
|
|
5
|
-
// (a) acyclic mount chain A -> B -> C: scanner returns Ok (no cycle).
|
|
6
|
-
// (b) two-asset cycle A -> B -> A: scanner returns
|
|
7
|
-
// Err(PackError{ code: 'pack-cyclic-reference', detail.kind: 'mount-asset',
|
|
8
|
-
// detail.cycle: GUID[] }) with the GUIDs in cycle order (first === last).
|
|
9
|
-
// (c) three-asset cycle A -> B -> C -> A: same, longer cycle.
|
|
10
|
-
// (d) self-mount A -> A: same, single-element cycle.
|
|
11
|
-
//
|
|
12
|
-
// The fixtures are hand-authored scene .pack.json files in a tmpdir;
|
|
13
|
-
// each scene asset uses `payload.mounts[].source` as an integer index into
|
|
14
|
-
// the parent scene's `refs[]` (D-1 + research Finding 11). The mount window
|
|
15
|
-
// (memberFirst / memberCount) is set conservatively so the scanner step-6
|
|
16
|
-
// cycle pass triggers before any mount-window collision check.
|
|
17
|
-
//
|
|
18
|
-
// TDD red signal: until w14 wires `payload.mounts[].source` -> `refsByGuid`
|
|
19
|
-
// edge into scanner step-6, the scanner does NOT see the mount edge in the
|
|
20
|
-
// DFS and the cycle assertions miss. With w14 the cycle producer surfaces
|
|
21
|
-
// `pack-cyclic-reference + detail.kind: 'mount-asset'` (w8 landed the
|
|
22
|
-
// detail.kind tagging at the producer).
|
|
23
|
-
|
|
24
|
-
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
25
|
-
import { tmpdir } from 'node:os';
|
|
26
|
-
import { join } from 'node:path';
|
|
27
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
28
|
-
import { scan } from '../scanner.js';
|
|
29
|
-
|
|
30
|
-
const GUID_A = 'aa000000-0000-4000-8000-000000000aaa';
|
|
31
|
-
const GUID_B = 'bb000000-0000-4000-8000-000000000bbb';
|
|
32
|
-
const GUID_C = 'cc000000-0000-4000-8000-000000000ccc';
|
|
33
|
-
|
|
34
|
-
interface MountSpec {
|
|
35
|
-
readonly localId: number;
|
|
36
|
-
readonly source: number;
|
|
37
|
-
readonly memberFirst: number;
|
|
38
|
-
readonly memberCount: number;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
interface SceneAssetSpec {
|
|
42
|
-
readonly guid: string;
|
|
43
|
-
/** GUIDs the scene asset's `payload.mounts[].source` integers index into. */
|
|
44
|
-
readonly refs: readonly string[];
|
|
45
|
-
readonly mounts: readonly MountSpec[];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function sceneAssetPack(spec: SceneAssetSpec): unknown {
|
|
49
|
-
return {
|
|
50
|
-
schemaVersion: '1.0.0',
|
|
51
|
-
kind: 'internal-text-package',
|
|
52
|
-
assets: [
|
|
53
|
-
{
|
|
54
|
-
guid: spec.guid,
|
|
55
|
-
kind: 'scene',
|
|
56
|
-
refs: [...spec.refs],
|
|
57
|
-
payload: {
|
|
58
|
-
kind: 'scene',
|
|
59
|
-
entities: [{ localId: 0, components: {} }],
|
|
60
|
-
mounts: spec.mounts.map((m) => ({
|
|
61
|
-
localId: m.localId,
|
|
62
|
-
source: m.source,
|
|
63
|
-
memberFirst: m.memberFirst,
|
|
64
|
-
memberCount: m.memberCount,
|
|
65
|
-
})),
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
],
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
interface CycleErrorShape {
|
|
73
|
-
readonly code: string;
|
|
74
|
-
readonly detail: {
|
|
75
|
-
readonly code?: string;
|
|
76
|
-
readonly kind?: string;
|
|
77
|
-
readonly cycle?: readonly string[];
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
describe('scanner step-6 mount-asset cycle detection (AC-12; D-1)', () => {
|
|
82
|
-
let dir: string;
|
|
83
|
-
|
|
84
|
-
beforeEach(async () => {
|
|
85
|
-
dir = await mkdtemp(join(tmpdir(), 'pack-mount-cycle-'));
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
afterEach(async () => {
|
|
89
|
-
await rm(dir, { recursive: true, force: true });
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('(a) accepts an acyclic mount chain A -> B -> C', async () => {
|
|
93
|
-
// A mounts B (source=0 in A.refs) -> B mounts C (source=0 in B.refs) ->
|
|
94
|
-
// C mounts nothing.
|
|
95
|
-
await writeFile(
|
|
96
|
-
join(dir, 'a.pack.json'),
|
|
97
|
-
JSON.stringify(
|
|
98
|
-
sceneAssetPack({
|
|
99
|
-
guid: GUID_A,
|
|
100
|
-
refs: [GUID_B],
|
|
101
|
-
mounts: [{ localId: 1, source: 0, memberFirst: 2, memberCount: 1 }],
|
|
102
|
-
}),
|
|
103
|
-
),
|
|
104
|
-
'utf-8',
|
|
105
|
-
);
|
|
106
|
-
await writeFile(
|
|
107
|
-
join(dir, 'b.pack.json'),
|
|
108
|
-
JSON.stringify(
|
|
109
|
-
sceneAssetPack({
|
|
110
|
-
guid: GUID_B,
|
|
111
|
-
refs: [GUID_C],
|
|
112
|
-
mounts: [{ localId: 1, source: 0, memberFirst: 2, memberCount: 1 }],
|
|
113
|
-
}),
|
|
114
|
-
),
|
|
115
|
-
'utf-8',
|
|
116
|
-
);
|
|
117
|
-
await writeFile(
|
|
118
|
-
join(dir, 'c.pack.json'),
|
|
119
|
-
JSON.stringify(sceneAssetPack({ guid: GUID_C, refs: [], mounts: [] })),
|
|
120
|
-
'utf-8',
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
const result = await scan([dir]);
|
|
124
|
-
expect(result.ok).toBe(true);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('(b) rejects a two-asset cycle A -> B -> A with kind: mount-asset', async () => {
|
|
128
|
-
await writeFile(
|
|
129
|
-
join(dir, 'a.pack.json'),
|
|
130
|
-
JSON.stringify(
|
|
131
|
-
sceneAssetPack({
|
|
132
|
-
guid: GUID_A,
|
|
133
|
-
refs: [GUID_B],
|
|
134
|
-
mounts: [{ localId: 1, source: 0, memberFirst: 2, memberCount: 1 }],
|
|
135
|
-
}),
|
|
136
|
-
),
|
|
137
|
-
'utf-8',
|
|
138
|
-
);
|
|
139
|
-
await writeFile(
|
|
140
|
-
join(dir, 'b.pack.json'),
|
|
141
|
-
JSON.stringify(
|
|
142
|
-
sceneAssetPack({
|
|
143
|
-
guid: GUID_B,
|
|
144
|
-
refs: [GUID_A],
|
|
145
|
-
mounts: [{ localId: 1, source: 0, memberFirst: 2, memberCount: 1 }],
|
|
146
|
-
}),
|
|
147
|
-
),
|
|
148
|
-
'utf-8',
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
const result = await scan([dir]);
|
|
152
|
-
expect(result.ok).toBe(false);
|
|
153
|
-
if (result.ok) return;
|
|
154
|
-
const err = result.error as unknown as CycleErrorShape;
|
|
155
|
-
expect(err.code).toBe('pack-cyclic-reference');
|
|
156
|
-
expect(err.detail.kind).toBe('mount-asset');
|
|
157
|
-
const cycle = err.detail.cycle ?? [];
|
|
158
|
-
expect(cycle.length).toBeGreaterThanOrEqual(2);
|
|
159
|
-
expect(cycle[0]).toBe(cycle[cycle.length - 1]);
|
|
160
|
-
// Both GUIDs participate in the cycle (case-insensitive compare; scanner
|
|
161
|
-
// lowercases before storing).
|
|
162
|
-
const lower = cycle.map((g) => g.toLowerCase());
|
|
163
|
-
expect(lower).toContain(GUID_A.toLowerCase());
|
|
164
|
-
expect(lower).toContain(GUID_B.toLowerCase());
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it('(c) rejects a three-asset cycle A -> B -> C -> A with kind: mount-asset', async () => {
|
|
168
|
-
await writeFile(
|
|
169
|
-
join(dir, 'a.pack.json'),
|
|
170
|
-
JSON.stringify(
|
|
171
|
-
sceneAssetPack({
|
|
172
|
-
guid: GUID_A,
|
|
173
|
-
refs: [GUID_B],
|
|
174
|
-
mounts: [{ localId: 1, source: 0, memberFirst: 2, memberCount: 1 }],
|
|
175
|
-
}),
|
|
176
|
-
),
|
|
177
|
-
'utf-8',
|
|
178
|
-
);
|
|
179
|
-
await writeFile(
|
|
180
|
-
join(dir, 'b.pack.json'),
|
|
181
|
-
JSON.stringify(
|
|
182
|
-
sceneAssetPack({
|
|
183
|
-
guid: GUID_B,
|
|
184
|
-
refs: [GUID_C],
|
|
185
|
-
mounts: [{ localId: 1, source: 0, memberFirst: 2, memberCount: 1 }],
|
|
186
|
-
}),
|
|
187
|
-
),
|
|
188
|
-
'utf-8',
|
|
189
|
-
);
|
|
190
|
-
await writeFile(
|
|
191
|
-
join(dir, 'c.pack.json'),
|
|
192
|
-
JSON.stringify(
|
|
193
|
-
sceneAssetPack({
|
|
194
|
-
guid: GUID_C,
|
|
195
|
-
refs: [GUID_A],
|
|
196
|
-
mounts: [{ localId: 1, source: 0, memberFirst: 2, memberCount: 1 }],
|
|
197
|
-
}),
|
|
198
|
-
),
|
|
199
|
-
'utf-8',
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
const result = await scan([dir]);
|
|
203
|
-
expect(result.ok).toBe(false);
|
|
204
|
-
if (result.ok) return;
|
|
205
|
-
const err = result.error as unknown as CycleErrorShape;
|
|
206
|
-
expect(err.code).toBe('pack-cyclic-reference');
|
|
207
|
-
expect(err.detail.kind).toBe('mount-asset');
|
|
208
|
-
const cycle = err.detail.cycle ?? [];
|
|
209
|
-
expect(cycle.length).toBeGreaterThanOrEqual(3);
|
|
210
|
-
expect(cycle[0]).toBe(cycle[cycle.length - 1]);
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it('(d) rejects a self-mount A -> A', async () => {
|
|
214
|
-
await writeFile(
|
|
215
|
-
join(dir, 'a.pack.json'),
|
|
216
|
-
JSON.stringify(
|
|
217
|
-
sceneAssetPack({
|
|
218
|
-
guid: GUID_A,
|
|
219
|
-
refs: [GUID_A],
|
|
220
|
-
mounts: [{ localId: 1, source: 0, memberFirst: 2, memberCount: 1 }],
|
|
221
|
-
}),
|
|
222
|
-
),
|
|
223
|
-
'utf-8',
|
|
224
|
-
);
|
|
225
|
-
const result = await scan([dir]);
|
|
226
|
-
expect(result.ok).toBe(false);
|
|
227
|
-
if (result.ok) return;
|
|
228
|
-
const err = result.error as unknown as CycleErrorShape;
|
|
229
|
-
expect(err.code).toBe('pack-cyclic-reference');
|
|
230
|
-
expect(err.detail.kind).toBe('mount-asset');
|
|
231
|
-
});
|
|
232
|
-
});
|