@forgeax/engine-pack 0.1.20 → 0.1.23
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 +159 -46
- package/dist/__tests__/inventory-schema.test.d.ts +2 -0
- package/dist/__tests__/inventory-schema.test.d.ts.map +1 -0
- package/dist/__tests__/parameterized-pack-authoring-gateway.unit.test.d.ts +2 -0
- package/dist/__tests__/parameterized-pack-authoring-gateway.unit.test.d.ts.map +1 -0
- package/dist/__tests__/parameterized-pack-scanner.unit.test.d.ts +2 -0
- package/dist/__tests__/parameterized-pack-scanner.unit.test.d.ts.map +1 -0
- package/dist/__tests__/parameterized-pack.unit.test.d.ts +2 -0
- package/dist/__tests__/parameterized-pack.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scanner-blacklist.test.d.ts +2 -0
- package/dist/__tests__/scanner-blacklist.test.d.ts.map +1 -0
- package/dist/build.d.ts +9 -4
- package/dist/build.d.ts.map +1 -1
- package/dist/build.mjs +3317 -804
- package/dist/build.mjs.map +1 -1
- package/dist/builtin.mjs +37 -4
- package/dist/builtin.mjs.map +1 -1
- package/dist/catalog-builder.d.ts.map +1 -1
- package/dist/cli-asset.d.ts.map +1 -1
- package/dist/cli-asset.mjs +1368 -850
- package/dist/cli-asset.mjs.map +1 -1
- package/dist/deriveAssetName.d.ts +11 -7
- package/dist/deriveAssetName.d.ts.map +1 -1
- package/dist/evidence/material-cook.d.ts.map +1 -1
- package/dist/evidence/source-inventory.d.ts.map +1 -1
- package/dist/guid.d.ts +15 -0
- package/dist/guid.d.ts.map +1 -1
- package/dist/guid.mjs +53 -5
- package/dist/guid.mjs.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +1255 -561
- package/dist/index.mjs.map +1 -1
- package/dist/inventory/binding.d.ts +6 -0
- package/dist/inventory/binding.d.ts.map +1 -0
- package/dist/inventory/declaration.d.ts +24 -0
- package/dist/inventory/declaration.d.ts.map +1 -0
- package/dist/inventory/index.d.ts +3 -0
- package/dist/inventory/index.d.ts.map +1 -0
- package/dist/inventory/sync.d.ts +5 -0
- package/dist/inventory/sync.d.ts.map +1 -0
- package/dist/material-cook.mjs +3 -1
- package/dist/material-cook.mjs.map +1 -1
- package/dist/name.mjs +4 -7
- package/dist/name.mjs.map +1 -1
- package/dist/package-finalizer.d.ts +2 -20
- package/dist/package-finalizer.d.ts.map +1 -1
- package/dist/parameterized-pack-node.d.ts +26 -0
- package/dist/parameterized-pack-node.d.ts.map +1 -0
- package/dist/parameterized-pack-node.mjs +11093 -0
- package/dist/parameterized-pack-node.mjs.map +1 -0
- package/dist/parameterized-pack.d.ts +233 -0
- package/dist/parameterized-pack.d.ts.map +1 -0
- package/dist/resolve-asset-source.d.ts +1 -3
- package/dist/resolve-asset-source.d.ts.map +1 -1
- package/dist/resolve-asset-source.mjs +6 -81
- package/dist/resolve-asset-source.mjs.map +1 -1
- package/dist/runtime.mjs +77 -7
- package/dist/runtime.mjs.map +1 -1
- package/dist/scanner.d.ts +19 -8
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.mjs +1169 -690
- package/dist/scanner.mjs.map +1 -1
- package/dist/schema-compiled.d.ts.map +1 -1
- package/dist/schema.mjs +79 -7
- package/dist/schema.mjs.map +1 -1
- package/dist/scriptable-pack-node.d.ts +8 -32
- package/dist/scriptable-pack-node.d.ts.map +1 -1
- package/dist/scriptable-pack-node.mjs +576 -1021
- package/dist/scriptable-pack-node.mjs.map +1 -1
- package/dist/scriptable-pack-worker.mjs +29 -11
- package/dist/scriptable-pack-worker.mjs.map +1 -1
- package/dist/scriptable-pack.d.ts +19 -532
- package/dist/scriptable-pack.d.ts.map +1 -1
- package/dist/scriptable-pack.mjs +1042 -527
- package/dist/scriptable-pack.mjs.map +1 -1
- package/package.json +7 -7
- package/schema/meta.schema.json +1 -1
- package/schema/pack.schema.json +76 -6
- package/src/__tests__/guid-collision.unit.test.ts +18 -9
- package/src/__tests__/inventory-schema.test.ts +109 -0
- package/src/__tests__/pack.unit.test.ts +1 -238
- package/src/__tests__/package-finalizer.contract.test.ts +55 -0
- package/src/__tests__/parameterized-pack-authoring-gateway.unit.test.ts +249 -0
- package/src/__tests__/parameterized-pack-scanner.unit.test.ts +123 -0
- package/src/__tests__/parameterized-pack.unit.test.ts +266 -0
- package/src/__tests__/resolve-asset-source.test.ts +15 -98
- package/src/__tests__/scanner-blacklist.test.ts +55 -0
- package/src/__tests__/scanner-inventory.contract.test.ts +14 -15
- package/src/__tests__/scanner-inventory.test.ts +24 -12
- package/src/__tests__/scriptable-pack-cli.integration.test.ts +4 -9
- package/src/__tests__/topology.unit.test.ts +23 -0
- package/src/build.ts +28 -18
- package/src/catalog-builder.ts +61 -34
- package/src/cli-asset.ts +70 -85
- package/src/deriveAssetName.ts +14 -13
- package/src/evidence/material-cook.ts +3 -1
- package/src/evidence/source-inventory.ts +34 -21
- package/src/guid.ts +65 -4
- package/src/index.ts +12 -11
- package/src/inventory/binding.ts +25 -0
- package/src/inventory/declaration.ts +168 -0
- package/src/inventory/index.ts +18 -0
- package/src/inventory/sync.ts +22 -0
- package/src/package-finalizer.ts +2 -42
- package/src/parameterized-pack-node.ts +1968 -0
- package/src/parameterized-pack.ts +1493 -0
- package/src/resolve-asset-source.ts +4 -76
- package/src/scanner.ts +241 -59
- package/src/schema-compiled.ts +2 -0
- package/src/scriptable-pack-node.ts +111 -722
- package/src/scriptable-pack-worker.ts +48 -16
- package/src/scriptable-pack.ts +27 -939
- package/dist/.tsbuildinfo +0 -1
- package/dist/__tests__/load-asset-config.test.d.ts +0 -2
- package/dist/__tests__/load-asset-config.test.d.ts.map +0 -1
- package/dist/__tests__/scriptable-pack.test-d.d.ts +0 -2
- package/dist/__tests__/scriptable-pack.test-d.d.ts.map +0 -1
- package/dist/__tests__/scriptable-pack.unit.test.d.ts +0 -2
- package/dist/__tests__/scriptable-pack.unit.test.d.ts.map +0 -1
- package/dist/config.d.ts +0 -6
- package/dist/config.d.ts.map +0 -1
- package/dist/config.mjs +0 -29
- package/dist/config.mjs.map +0 -1
- package/src/__tests__/load-asset-config.test.ts +0 -121
- package/src/__tests__/scriptable-pack.test-d.ts +0 -131
- package/src/__tests__/scriptable-pack.unit.test.ts +0 -789
- package/src/config.ts +0 -36
|
@@ -1,88 +1,16 @@
|
|
|
1
|
-
import { dirname,
|
|
2
|
-
import type { Result } from '@forgeax/engine-types';
|
|
3
|
-
import { PACK_ERROR_HINTS, err as resultErr, ok as resultOk } from '@forgeax/engine-types';
|
|
1
|
+
import { dirname, resolve } from 'node:path';
|
|
4
2
|
import { deriveAssetName } from './deriveAssetName.js';
|
|
5
|
-
import { PackError } from './errors.js';
|
|
6
3
|
|
|
7
4
|
function deriveSourceName(metaFileName: string): string {
|
|
8
5
|
return metaFileName.replace(/\.meta\.json$/, '');
|
|
9
6
|
}
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
function isPathEscape(pathDir: string, resolved: string): boolean {
|
|
14
|
-
const rel = posix.relative(pathDir, resolved);
|
|
15
|
-
return rel.startsWith('..') || posix.isAbsolute(rel);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function resolveAssetSource(
|
|
19
|
-
metaPath: string,
|
|
20
|
-
source: string | undefined,
|
|
21
|
-
paths: Record<string, string>,
|
|
22
|
-
): Result<string, PackError> {
|
|
8
|
+
export function resolveAssetSource(metaPath: string, source: string | undefined): string {
|
|
23
9
|
if (source === undefined) {
|
|
24
10
|
const metaDir = dirname(metaPath);
|
|
25
11
|
const metaName = deriveAssetName(metaPath, 1);
|
|
26
12
|
const derived = deriveSourceName(metaName);
|
|
27
|
-
return
|
|
13
|
+
return resolve(metaDir, derived);
|
|
28
14
|
}
|
|
29
|
-
|
|
30
|
-
if (source.startsWith('@')) {
|
|
31
|
-
const match = source.match(PATH_REF_RE);
|
|
32
|
-
if (!match) {
|
|
33
|
-
return resultErr(
|
|
34
|
-
new PackError({
|
|
35
|
-
code: 'pack-malformed-path-ref',
|
|
36
|
-
expected: 'source in format @<name>/<rest>',
|
|
37
|
-
hint: PACK_ERROR_HINTS['pack-malformed-path-ref'],
|
|
38
|
-
detail: {
|
|
39
|
-
code: 'pack-malformed-path-ref',
|
|
40
|
-
reason: 'format',
|
|
41
|
-
rawSource: source,
|
|
42
|
-
expectedFormat: '@<name>/<rest>',
|
|
43
|
-
},
|
|
44
|
-
}),
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const [, name, rest] = match as [string, string, string];
|
|
49
|
-
const pathDir = paths[name];
|
|
50
|
-
|
|
51
|
-
if (pathDir === undefined) {
|
|
52
|
-
return resultErr(
|
|
53
|
-
new PackError({
|
|
54
|
-
code: 'pack-unknown-path',
|
|
55
|
-
expected: 'path name found in package.json#forgeax.assets.paths',
|
|
56
|
-
hint: PACK_ERROR_HINTS['pack-unknown-path'],
|
|
57
|
-
detail: {
|
|
58
|
-
code: 'pack-unknown-path',
|
|
59
|
-
pathName: name,
|
|
60
|
-
knownNames: Object.keys(paths),
|
|
61
|
-
},
|
|
62
|
-
}),
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const resolved = resolve(pathDir, rest);
|
|
67
|
-
|
|
68
|
-
if (isPathEscape(pathDir, resolved)) {
|
|
69
|
-
return resultErr(
|
|
70
|
-
new PackError({
|
|
71
|
-
code: 'pack-malformed-path-ref',
|
|
72
|
-
expected: 'rest path must not escape the declared path directory',
|
|
73
|
-
hint: PACK_ERROR_HINTS['pack-malformed-path-ref'],
|
|
74
|
-
detail: {
|
|
75
|
-
code: 'pack-malformed-path-ref',
|
|
76
|
-
reason: 'escape',
|
|
77
|
-
rawSource: source,
|
|
78
|
-
expectedFormat: '@<name>/<rest>',
|
|
79
|
-
},
|
|
80
|
-
}),
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return resultOk(resolved);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return resultOk(resolve(dirname(metaPath), source));
|
|
15
|
+
return resolve(dirname(metaPath), source);
|
|
88
16
|
}
|
package/src/scanner.ts
CHANGED
|
@@ -9,21 +9,22 @@ import type {
|
|
|
9
9
|
SourceOverrideDescriptor,
|
|
10
10
|
} from '@forgeax/engine-types';
|
|
11
11
|
import { PACK_ERROR_HINTS } from '@forgeax/engine-types';
|
|
12
|
-
import { loadAssetConfig } from './config.js';
|
|
13
12
|
import { PackError } from './errors.js';
|
|
14
|
-
import { isValidAssetGuidString } from './guid.js';
|
|
13
|
+
import { isValidAssetGuidString, PackageId } from './guid.js';
|
|
14
|
+
import {
|
|
15
|
+
type AnyParameterizedPackDefinition,
|
|
16
|
+
type ParameterizedPackSourceMeta,
|
|
17
|
+
parseParameterizedPackJson,
|
|
18
|
+
projectDirectPackJson,
|
|
19
|
+
projectParameterizedPackMeta,
|
|
20
|
+
} from './parameterized-pack.js';
|
|
15
21
|
import { validateProducerContract, validateProducerOutputs } from './producer-contract.js';
|
|
16
22
|
import { resolveAssetSource } from './resolve-asset-source.js';
|
|
17
23
|
import { validateMeta, validatePack } from './schema-compiled.js';
|
|
18
|
-
import {
|
|
19
|
-
projectScriptablePackMeta,
|
|
20
|
-
type ScriptablePackDefinition,
|
|
21
|
-
type ScriptablePackMetaJson,
|
|
22
|
-
type ScriptablePackSourceClosureEntry,
|
|
23
|
-
} from './scriptable-pack.js';
|
|
24
|
+
import type { ScriptablePackSourceClosureEntry } from './scriptable-pack.js';
|
|
24
25
|
import {
|
|
25
26
|
inventoryScriptablePackSource,
|
|
26
|
-
|
|
27
|
+
loadParameterizedScriptablePack,
|
|
27
28
|
type ScriptablePackModuleExecutor,
|
|
28
29
|
} from './scriptable-pack-node.js';
|
|
29
30
|
|
|
@@ -41,6 +42,10 @@ function packErr<E>(error: E): ScanResult<never, E> {
|
|
|
41
42
|
return { ok: false, error };
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
function record(value: unknown): value is Record<string, unknown> {
|
|
46
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
47
|
+
}
|
|
48
|
+
|
|
44
49
|
/** Host-owned source paths that should not enter the Pack catalog. */
|
|
45
50
|
export interface ScanOptions {
|
|
46
51
|
readonly ignorePath?: (path: string) => boolean;
|
|
@@ -80,8 +85,8 @@ export interface ScanInventory {
|
|
|
80
85
|
export interface ScriptablePackInventoryDeclaration {
|
|
81
86
|
readonly sourcePath: string;
|
|
82
87
|
readonly sourceRevision: string;
|
|
83
|
-
readonly meta:
|
|
84
|
-
readonly definition: Readonly<
|
|
88
|
+
readonly meta: ParameterizedPackSourceMeta;
|
|
89
|
+
readonly definition: Readonly<AnyParameterizedPackDefinition>;
|
|
85
90
|
readonly sourceClosure: readonly ScriptablePackSourceClosureEntry[];
|
|
86
91
|
}
|
|
87
92
|
|
|
@@ -110,7 +115,7 @@ export interface MetaInventorySubAsset {
|
|
|
110
115
|
readonly kind: string;
|
|
111
116
|
}
|
|
112
117
|
|
|
113
|
-
export interface
|
|
118
|
+
export interface LegacyPackInventoryDocument {
|
|
114
119
|
readonly schemaVersion: '1.0.0' | '2.0.0';
|
|
115
120
|
readonly kind: 'internal-text-package';
|
|
116
121
|
readonly packageId?: string;
|
|
@@ -120,6 +125,18 @@ export interface PackInventoryDocument {
|
|
|
120
125
|
readonly assets: readonly PackInventoryAsset[];
|
|
121
126
|
}
|
|
122
127
|
|
|
128
|
+
export interface ParameterizedPackInventoryDocument {
|
|
129
|
+
readonly schemaVersion: '3.0.0';
|
|
130
|
+
readonly packageId: string;
|
|
131
|
+
readonly assets?: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
|
|
132
|
+
readonly parent?: string;
|
|
133
|
+
readonly values?: Readonly<Record<string, unknown>>;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export type PackInventoryDocument =
|
|
137
|
+
| LegacyPackInventoryDocument
|
|
138
|
+
| ParameterizedPackInventoryDocument;
|
|
139
|
+
|
|
123
140
|
export interface PackInventoryAsset {
|
|
124
141
|
readonly guid: string;
|
|
125
142
|
readonly kind: string;
|
|
@@ -146,12 +163,14 @@ export type ScanSourceDeclaration =
|
|
|
146
163
|
readonly sourceText: string;
|
|
147
164
|
readonly value: PackInventoryDocument;
|
|
148
165
|
}
|
|
149
|
-
|
|
|
150
|
-
readonly format: 'pack.ts';
|
|
166
|
+
| {
|
|
167
|
+
readonly format: 'pack.ts-parameterized';
|
|
151
168
|
readonly sourcePath: string;
|
|
152
169
|
readonly sourceRevision: string;
|
|
153
|
-
readonly value:
|
|
154
|
-
|
|
170
|
+
readonly value: ParameterizedPackSourceMeta;
|
|
171
|
+
readonly definition: AnyParameterizedPackDefinition;
|
|
172
|
+
readonly sourceClosure: readonly ScriptablePackSourceClosureEntry[];
|
|
173
|
+
};
|
|
155
174
|
|
|
156
175
|
interface ScanCapture {
|
|
157
176
|
readonly declarations: Map<string, ScanSourceDeclaration>;
|
|
@@ -169,6 +188,7 @@ interface ScanCapture {
|
|
|
169
188
|
*/
|
|
170
189
|
const BLACKLIST = new Set([
|
|
171
190
|
'node_modules',
|
|
191
|
+
'__tests__',
|
|
172
192
|
'.forgeax-harness',
|
|
173
193
|
'.forgeax',
|
|
174
194
|
'.git',
|
|
@@ -349,12 +369,99 @@ async function scanValidated(
|
|
|
349
369
|
// kind stays in the path/detail evidence; identity is the normalized GUID.
|
|
350
370
|
const guidToPath = new Map<string, string>();
|
|
351
371
|
const packRefs = new Map<string, string[]>(); // guid -> refs[]
|
|
372
|
+
const packageIdToPath = new Map<string, string>();
|
|
373
|
+
const packageKind = new Map<string, 'legacy' | 'direct' | 'instance' | 'parameterized'>();
|
|
374
|
+
const instanceParents: {
|
|
375
|
+
readonly path: string;
|
|
376
|
+
readonly packageId: string;
|
|
377
|
+
readonly parent: string;
|
|
378
|
+
}[] = [];
|
|
379
|
+
|
|
380
|
+
function registerPackage(
|
|
381
|
+
packageId: string | undefined,
|
|
382
|
+
path: string,
|
|
383
|
+
kind: 'legacy' | 'direct' | 'instance' | 'parameterized',
|
|
384
|
+
): PackError | undefined {
|
|
385
|
+
if (packageId === undefined) return undefined;
|
|
386
|
+
const normalized = packageId.toLowerCase();
|
|
387
|
+
const existing = packageIdToPath.get(normalized);
|
|
388
|
+
if (existing !== undefined) {
|
|
389
|
+
return makePackError('pack-guid-collision', {
|
|
390
|
+
paths: [existing, path],
|
|
391
|
+
guid: normalized,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
packageIdToPath.set(normalized, path);
|
|
395
|
+
packageKind.set(normalized, kind);
|
|
396
|
+
return undefined;
|
|
397
|
+
}
|
|
352
398
|
|
|
353
399
|
for (const packPath of packPaths) {
|
|
354
400
|
const loaded = await readValidatedJson(packPath, 'pack-malformed-pack', validatePack);
|
|
355
401
|
if (!loaded.ok) return loaded;
|
|
356
402
|
const { raw, parsed } = loaded.value;
|
|
357
403
|
|
|
404
|
+
if (record(parsed) && parsed.schemaVersion === '3.0.0') {
|
|
405
|
+
const authoring = parseParameterizedPackJson(parsed);
|
|
406
|
+
if (!authoring.ok) {
|
|
407
|
+
return packErr(
|
|
408
|
+
makePackError('pack-malformed-pack', {
|
|
409
|
+
path: packPath,
|
|
410
|
+
ajvErrors: [{ instancePath: '', message: authoring.error.code }],
|
|
411
|
+
}),
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
const packageId = authoring.value.packageId;
|
|
415
|
+
const packageCollision = registerPackage(
|
|
416
|
+
PackageId.format(packageId),
|
|
417
|
+
packPath,
|
|
418
|
+
authoring.value.format,
|
|
419
|
+
);
|
|
420
|
+
if (packageCollision !== undefined) return packErr(packageCollision);
|
|
421
|
+
if (authoring.value.format === 'instance') {
|
|
422
|
+
instanceParents.push({
|
|
423
|
+
path: packPath,
|
|
424
|
+
packageId: PackageId.format(authoring.value.packageId),
|
|
425
|
+
parent: PackageId.format(authoring.value.parent),
|
|
426
|
+
});
|
|
427
|
+
} else {
|
|
428
|
+
const projected = projectDirectPackJson(authoring.value);
|
|
429
|
+
if (!projected.ok) {
|
|
430
|
+
return packErr(
|
|
431
|
+
makePackError('pack-malformed-pack', {
|
|
432
|
+
path: packPath,
|
|
433
|
+
ajvErrors: [{ instancePath: '/assets', message: projected.error.code }],
|
|
434
|
+
}),
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
for (const asset of projected.value.assets) {
|
|
438
|
+
const normalizedGuid = asset.guid.toLowerCase();
|
|
439
|
+
const existing = guidToPath.get(normalizedGuid);
|
|
440
|
+
if (existing !== undefined) {
|
|
441
|
+
return packErr(
|
|
442
|
+
makePackError('pack-guid-collision', {
|
|
443
|
+
paths: [existing, packPath],
|
|
444
|
+
guid: normalizedGuid,
|
|
445
|
+
}),
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
guidToPath.set(normalizedGuid, packPath);
|
|
449
|
+
packRefs.set(normalizedGuid, [
|
|
450
|
+
...asset.refs.map((ref) => ref.toLowerCase()),
|
|
451
|
+
...extractMountSourceGuids(asset),
|
|
452
|
+
]);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
capture?.declarations.set(packPath, {
|
|
456
|
+
format: 'pack.json',
|
|
457
|
+
sourcePath: packPath,
|
|
458
|
+
sourceRevision: `sha256:${createHash('sha256').update(raw).digest('hex')}`,
|
|
459
|
+
sourceText: raw,
|
|
460
|
+
value: parsed as unknown as ParameterizedPackInventoryDocument,
|
|
461
|
+
});
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
|
|
358
465
|
const packageContract = validateProducerContract(parsed);
|
|
359
466
|
if (!packageContract.ok) {
|
|
360
467
|
return packErr(
|
|
@@ -366,7 +473,9 @@ async function scanValidated(
|
|
|
366
473
|
}
|
|
367
474
|
|
|
368
475
|
// Step 3: validate GUIDs in pack
|
|
369
|
-
const packObj = parsed as unknown as
|
|
476
|
+
const packObj = parsed as unknown as LegacyPackInventoryDocument;
|
|
477
|
+
const packageCollision = registerPackage(packObj.packageId, packPath, 'legacy');
|
|
478
|
+
if (packageCollision !== undefined) return packErr(packageCollision);
|
|
370
479
|
for (const asset of packObj.assets) {
|
|
371
480
|
if (
|
|
372
481
|
asset.kind === 'particle-effect' &&
|
|
@@ -477,8 +586,9 @@ async function scanValidated(
|
|
|
477
586
|
});
|
|
478
587
|
}
|
|
479
588
|
|
|
480
|
-
// Step 2 + 3 + 5: parse + schema validate + GUID format validate + orphan check for meta files
|
|
481
|
-
|
|
589
|
+
// Step 2 + 3 + 5: parse + schema validate + GUID format validate + orphan check for meta files.
|
|
590
|
+
// Meta source paths are local to their sidecar: an omitted source names the
|
|
591
|
+
// companion file and an explicit source is resolved relative to the sidecar.
|
|
482
592
|
for (const metaPath of metaPaths) {
|
|
483
593
|
const loaded = await readValidatedJson(metaPath, 'pack-malformed-meta', validateMeta);
|
|
484
594
|
if (!loaded.ok) return loaded;
|
|
@@ -496,6 +606,8 @@ async function scanValidated(
|
|
|
496
606
|
|
|
497
607
|
// Step 3: validate GUIDs in meta subAssets
|
|
498
608
|
const metaObj = parsed as unknown as MetaInventoryDocument;
|
|
609
|
+
const packageCollision = registerPackage(metaObj.packageId, metaPath, 'legacy');
|
|
610
|
+
if (packageCollision !== undefined) return packErr(packageCollision);
|
|
499
611
|
capture?.declarations.set(metaPath, {
|
|
500
612
|
format: 'meta.json',
|
|
501
613
|
sourcePath: metaPath,
|
|
@@ -537,13 +649,7 @@ async function scanValidated(
|
|
|
537
649
|
}
|
|
538
650
|
|
|
539
651
|
// Step 5: orphan .meta.json check — the source file declared in meta.source must exist.
|
|
540
|
-
|
|
541
|
-
// relative path), replacing the former hardcoded join(metaDir, metaObj.source).
|
|
542
|
-
const sourceResolved = resolveAssetSource(metaPath, metaObj.source, assetPaths);
|
|
543
|
-
if (!sourceResolved.ok) {
|
|
544
|
-
return packErr(sourceResolved.error);
|
|
545
|
-
}
|
|
546
|
-
const expectedSourcePath = sourceResolved.value;
|
|
652
|
+
const expectedSourcePath = resolveAssetSource(metaPath, metaObj.source);
|
|
547
653
|
try {
|
|
548
654
|
await stat(expectedSourcePath);
|
|
549
655
|
} catch {
|
|
@@ -556,7 +662,8 @@ async function scanValidated(
|
|
|
556
662
|
}
|
|
557
663
|
}
|
|
558
664
|
|
|
559
|
-
//
|
|
665
|
+
// Parameterized source identity enters the package registry. Its outputs are
|
|
666
|
+
// intentionally absent until the dynamic build worklist evaluates it.
|
|
560
667
|
for (const sourcePath of scriptablePaths) {
|
|
561
668
|
let source: string;
|
|
562
669
|
try {
|
|
@@ -565,17 +672,17 @@ async function scanValidated(
|
|
|
565
672
|
return packErr(
|
|
566
673
|
makePackError('pack-malformed-meta', {
|
|
567
674
|
path: sourcePath,
|
|
568
|
-
ajvErrors: [{ instancePath: '', message: '
|
|
675
|
+
ajvErrors: [{ instancePath: '', message: 'Pack source read failed' }],
|
|
569
676
|
}),
|
|
570
677
|
);
|
|
571
678
|
}
|
|
572
|
-
const
|
|
679
|
+
const loaderOptions = {
|
|
573
680
|
...(opts.scriptablePack ?? {}),
|
|
574
681
|
...(capture === undefined ? { metadataOnly: true } : {}),
|
|
575
|
-
}
|
|
682
|
+
};
|
|
683
|
+
const loaded = await loadParameterizedScriptablePack(sourcePath, loaderOptions);
|
|
576
684
|
if (!loaded.ok) {
|
|
577
|
-
const diagnostic =
|
|
578
|
-
'diagnostic' in loaded.error.detail ? loaded.error.detail.diagnostic : undefined;
|
|
685
|
+
const diagnostic = loaded.error.detail.diagnostic;
|
|
579
686
|
return packErr(
|
|
580
687
|
makePackError('pack-malformed-meta', {
|
|
581
688
|
path: sourcePath,
|
|
@@ -583,9 +690,9 @@ async function scanValidated(
|
|
|
583
690
|
{
|
|
584
691
|
instancePath: '',
|
|
585
692
|
message:
|
|
586
|
-
diagnostic ===
|
|
587
|
-
? loaded.error.code
|
|
588
|
-
:
|
|
693
|
+
typeof diagnostic === 'string'
|
|
694
|
+
? `${loaded.error.code}: ${diagnostic}`
|
|
695
|
+
: loaded.error.code,
|
|
589
696
|
},
|
|
590
697
|
],
|
|
591
698
|
}),
|
|
@@ -598,41 +705,84 @@ async function scanValidated(
|
|
|
598
705
|
return packErr(
|
|
599
706
|
makePackError('pack-malformed-meta', {
|
|
600
707
|
path: sourcePath,
|
|
601
|
-
ajvErrors: [{ instancePath: '', message: '
|
|
708
|
+
ajvErrors: [{ instancePath: '', message: 'Pack source closure read failed' }],
|
|
602
709
|
}),
|
|
603
710
|
);
|
|
604
711
|
}
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
}
|
|
615
|
-
const declaration = {
|
|
712
|
+
const packageCollision = registerPackage(
|
|
713
|
+
PackageId.format(loaded.value.packageId),
|
|
714
|
+
sourcePath,
|
|
715
|
+
'parameterized',
|
|
716
|
+
);
|
|
717
|
+
if (packageCollision !== undefined) return packErr(packageCollision);
|
|
718
|
+
const meta = projectParameterizedPackMeta(loaded.value, sourcePath);
|
|
719
|
+
capture?.declarations.set(sourcePath, {
|
|
720
|
+
format: 'pack.ts-parameterized',
|
|
616
721
|
sourcePath,
|
|
617
722
|
sourceRevision: `sha256:${createHash('sha256').update(source).digest('hex')}`,
|
|
618
|
-
meta,
|
|
723
|
+
value: meta,
|
|
619
724
|
definition: loaded.value,
|
|
620
725
|
sourceClosure,
|
|
621
|
-
} satisfies ScriptablePackInventoryDeclaration;
|
|
622
|
-
capture?.declarations.set(sourcePath, {
|
|
623
|
-
format: 'pack.ts',
|
|
624
|
-
...declaration,
|
|
625
|
-
value: meta,
|
|
626
726
|
});
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// v3 instance parent validation happens after all declarations have been
|
|
730
|
+
// registered, so scan order cannot change missing-parent or cycle results.
|
|
731
|
+
const parentByPackageId = new Map(
|
|
732
|
+
instanceParents.map((instance) => [instance.packageId.toLowerCase(), instance]),
|
|
733
|
+
);
|
|
734
|
+
for (const instance of instanceParents) {
|
|
735
|
+
const parentId = instance.parent.toLowerCase();
|
|
736
|
+
const parentPath = packageIdToPath.get(parentId);
|
|
737
|
+
if (parentPath === undefined) {
|
|
738
|
+
return packErr(
|
|
739
|
+
makePackError('pack-malformed-pack', {
|
|
740
|
+
path: instance.path,
|
|
741
|
+
reason: 'pack-parent-not-found',
|
|
742
|
+
ajvErrors: [
|
|
743
|
+
{
|
|
744
|
+
instancePath: '/parent',
|
|
745
|
+
message: `parent packageId ${instance.parent} was not found`,
|
|
746
|
+
},
|
|
747
|
+
],
|
|
748
|
+
}),
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
const kind = packageKind.get(parentId);
|
|
752
|
+
if (kind !== 'parameterized' && kind !== 'instance') {
|
|
753
|
+
return packErr(
|
|
754
|
+
makePackError('pack-malformed-pack', {
|
|
755
|
+
path: instance.path,
|
|
756
|
+
reason: 'pack-parent-not-parameterized',
|
|
757
|
+
ajvErrors: [
|
|
758
|
+
{
|
|
759
|
+
instancePath: '/parent',
|
|
760
|
+
message: `parent ${instance.parent} is not a parameterized Pack`,
|
|
761
|
+
},
|
|
762
|
+
],
|
|
763
|
+
}),
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
for (const instance of instanceParents) {
|
|
768
|
+
const chain = new Set<string>();
|
|
769
|
+
let current = instance.packageId.toLowerCase();
|
|
770
|
+
while (true) {
|
|
771
|
+
if (chain.has(current)) {
|
|
631
772
|
return packErr(
|
|
632
|
-
makePackError('pack-
|
|
773
|
+
makePackError('pack-malformed-pack', {
|
|
774
|
+
path: instance.path,
|
|
775
|
+
reason: 'pack-parent-cycle',
|
|
776
|
+
ajvErrors: [
|
|
777
|
+
{ instancePath: '/parent', message: `parent chain repeats packageId ${current}` },
|
|
778
|
+
],
|
|
779
|
+
}),
|
|
633
780
|
);
|
|
634
781
|
}
|
|
635
|
-
|
|
782
|
+
chain.add(current);
|
|
783
|
+
const next = parentByPackageId.get(current);
|
|
784
|
+
if (next === undefined) break;
|
|
785
|
+
current = next.parent.toLowerCase();
|
|
636
786
|
}
|
|
637
787
|
}
|
|
638
788
|
|
|
@@ -698,6 +848,38 @@ export async function scanInventory(
|
|
|
698
848
|
for (const sourcePath of scanned.value) {
|
|
699
849
|
const declaration = declarations.get(sourcePath);
|
|
700
850
|
if (declaration?.format !== 'pack.json') continue;
|
|
851
|
+
if (declaration.value.schemaVersion === '3.0.0') {
|
|
852
|
+
const parsed = parseParameterizedPackJson(declaration.value);
|
|
853
|
+
if (!parsed.ok) {
|
|
854
|
+
return packErr(
|
|
855
|
+
makePackError('pack-malformed-pack', {
|
|
856
|
+
path: sourcePath,
|
|
857
|
+
ajvErrors: [{ instancePath: '', message: parsed.error.code }],
|
|
858
|
+
}),
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
if (parsed.value.format !== 'direct') continue;
|
|
862
|
+
const projected = projectDirectPackJson(parsed.value);
|
|
863
|
+
if (!projected.ok) {
|
|
864
|
+
return packErr(
|
|
865
|
+
makePackError('pack-malformed-pack', {
|
|
866
|
+
path: sourcePath,
|
|
867
|
+
ajvErrors: [{ instancePath: '/assets', message: projected.error.code }],
|
|
868
|
+
}),
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
for (const [index, asset] of projected.value.assets.entries()) {
|
|
872
|
+
inventory.push({
|
|
873
|
+
guid: asset.guid,
|
|
874
|
+
kind: asset.kind,
|
|
875
|
+
sourcePath,
|
|
876
|
+
sourceRevision: declaration.sourceRevision,
|
|
877
|
+
sourceKey: asset.sourceKey,
|
|
878
|
+
sourceIndex: index,
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
701
883
|
for (const [index, asset] of declaration.value.assets.entries()) {
|
|
702
884
|
inventory.push({
|
|
703
885
|
guid: asset.guid,
|
package/src/schema-compiled.ts
CHANGED
|
@@ -152,6 +152,7 @@ export function buildSceneAssetValidator(
|
|
|
152
152
|
required: ['kind', 'entities'],
|
|
153
153
|
properties: {
|
|
154
154
|
kind: { type: 'string', const: 'scene' },
|
|
155
|
+
sourceKey: { type: 'string', minLength: 1 },
|
|
155
156
|
entities: {
|
|
156
157
|
type: 'array',
|
|
157
158
|
items: {
|
|
@@ -160,6 +161,7 @@ export function buildSceneAssetValidator(
|
|
|
160
161
|
required: ['localId', 'components'],
|
|
161
162
|
properties: {
|
|
162
163
|
localId: { type: 'integer', minimum: 0 },
|
|
164
|
+
bindingKey: { type: 'string', minLength: 1 },
|
|
163
165
|
components: {
|
|
164
166
|
type: 'object',
|
|
165
167
|
additionalProperties: false,
|