@forgeax/engine-pack 0.1.21 → 0.1.24
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 +167 -48
- package/dist/__tests__/inventory-schema.test.d.ts +2 -0
- package/dist/__tests__/inventory-schema.test.d.ts.map +1 -0
- package/dist/__tests__/material-cook-receipt-layer-plan.unit.test.d.ts +2 -0
- package/dist/__tests__/material-cook-receipt-layer-plan.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pack-authoring-gateway.unit.test.d.ts +2 -0
- package/dist/__tests__/pack-authoring-gateway.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pack-authoring.unit.test.d.ts +2 -0
- package/dist/__tests__/pack-authoring.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pack-scanner.unit.test.d.ts +2 -0
- package/dist/__tests__/pack-scanner.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 +3449 -897
- 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 +1361 -845
- 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 +11 -0
- 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 +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +1297 -562
- 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.d.ts +1 -1
- package/dist/material-cook.d.ts.map +1 -1
- package/dist/material-cook.mjs +50 -3
- package/dist/material-cook.mjs.map +1 -1
- package/dist/name.mjs +4 -7
- package/dist/name.mjs.map +1 -1
- package/dist/pack-authoring-node.d.ts +26 -0
- package/dist/pack-authoring-node.d.ts.map +1 -0
- package/dist/pack-authoring-node.mjs +11089 -0
- package/dist/pack-authoring-node.mjs.map +1 -0
- package/dist/pack-authoring.d.ts +230 -0
- package/dist/pack-authoring.d.ts.map +1 -0
- package/dist/pack-authoring.mjs +1137 -0
- package/dist/pack-authoring.mjs.map +1 -0
- package/dist/package-finalizer.d.ts +2 -20
- package/dist/package-finalizer.d.ts.map +1 -1
- 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 +18 -7
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.mjs +1164 -687
- 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 +7 -31
- package/dist/scriptable-pack-node.d.ts.map +1 -1
- package/dist/scriptable-pack-node.mjs +572 -1019
- 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 +20 -532
- package/dist/scriptable-pack.d.ts.map +1 -1
- package/dist/scriptable-pack.mjs +1038 -527
- package/dist/scriptable-pack.mjs.map +1 -1
- package/package.json +2 -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__/material-cook-receipt-layer-plan.unit.test.ts +16 -0
- package/src/__tests__/material-cook-schema.unit.test.ts +62 -0
- package/src/__tests__/pack-authoring-gateway.unit.test.ts +249 -0
- package/src/__tests__/pack-authoring.unit.test.ts +266 -0
- package/src/__tests__/pack-scanner.unit.test.ts +123 -0
- package/src/__tests__/pack.unit.test.ts +1 -238
- package/src/__tests__/package-finalizer.contract.test.ts +55 -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 +11 -14
- package/src/__tests__/scanner-inventory.test.ts +23 -10
- 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 +60 -33
- package/src/cli-asset.ts +68 -83
- package/src/deriveAssetName.ts +14 -13
- package/src/evidence/material-cook.ts +79 -3
- package/src/evidence/source-inventory.ts +34 -21
- package/src/guid.ts +65 -4
- package/src/index.ts +15 -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/material-cook.ts +1 -0
- package/src/pack-authoring-node.ts +1966 -0
- package/src/pack-authoring.ts +1478 -0
- package/src/package-finalizer.ts +2 -42
- package/src/resolve-asset-source.ts +4 -76
- package/src/scanner.ts +236 -56
- package/src/schema/material-cook.schema.json +4 -1
- package/src/schema-compiled.ts +2 -0
- package/src/scriptable-pack-node.ts +108 -719
- package/src/scriptable-pack-worker.ts +48 -16
- package/src/scriptable-pack.ts +28 -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
package/src/package-finalizer.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import type { AssetCodec,
|
|
2
|
+
import type { AssetCodec, CookReceipt } from '@forgeax/engine-types';
|
|
3
3
|
|
|
4
4
|
export interface PackageArtifactBody {
|
|
5
5
|
readonly mediaType: string;
|
|
@@ -24,25 +24,6 @@ export interface PackageProduct {
|
|
|
24
24
|
readonly sourceKey?: string;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
/** Minimal authored Pack v1/v2 shape consumed by the file transport seam. */
|
|
28
|
-
export interface AuthoredPackAssetInput {
|
|
29
|
-
readonly guid: string;
|
|
30
|
-
readonly kind: string;
|
|
31
|
-
readonly name?: string;
|
|
32
|
-
readonly execution?: 'direct' | 'cooked';
|
|
33
|
-
readonly sourceKey?: string;
|
|
34
|
-
readonly sourceIndex?: number;
|
|
35
|
-
readonly relations?: readonly AssetRelation[];
|
|
36
|
-
readonly payload: Record<string, unknown>;
|
|
37
|
-
readonly refs?: readonly string[];
|
|
38
|
-
readonly artifacts?: Readonly<Record<string, unknown>>;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface AuthoredPackInput {
|
|
42
|
-
readonly schemaVersion?: string;
|
|
43
|
-
readonly assets?: readonly AuthoredPackAssetInput[];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
27
|
export interface PackageDocumentArtifact {
|
|
47
28
|
readonly path: string;
|
|
48
29
|
readonly mediaType: string;
|
|
@@ -66,28 +47,6 @@ export interface PackageDocument {
|
|
|
66
47
|
readonly assets: readonly PackageDocumentAsset[];
|
|
67
48
|
}
|
|
68
49
|
|
|
69
|
-
/** Upgrade the legacy authored Pack envelope at the Pack schema boundary. */
|
|
70
|
-
export function upgradeLegacyAuthoredPack(pack: AuthoredPackInput): AuthoredPackInput {
|
|
71
|
-
if (pack.schemaVersion !== '1.0.0' || pack.assets === undefined) return pack;
|
|
72
|
-
return {
|
|
73
|
-
...pack,
|
|
74
|
-
schemaVersion: '2.0.0',
|
|
75
|
-
assets: pack.assets.map((asset) => {
|
|
76
|
-
const fontRefs =
|
|
77
|
-
asset.kind === 'font'
|
|
78
|
-
? [asset.payload.atlasGuid, asset.payload.samplerGuid].filter(
|
|
79
|
-
(guid): guid is string => typeof guid === 'string',
|
|
80
|
-
)
|
|
81
|
-
: [];
|
|
82
|
-
return {
|
|
83
|
-
...asset,
|
|
84
|
-
refs: asset.refs ?? fontRefs,
|
|
85
|
-
artifacts: asset.artifacts ?? {},
|
|
86
|
-
};
|
|
87
|
-
}),
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
50
|
export interface PackageFinalizePolicy {
|
|
92
51
|
readonly base: string;
|
|
93
52
|
readonly packagePath: string;
|
|
@@ -364,6 +323,7 @@ export async function finalizePackageTransportSource(
|
|
|
364
323
|
packageUrl: finalized.value.packageUrl,
|
|
365
324
|
artifacts: transportArtifacts(product.assets, policy.artifactPath),
|
|
366
325
|
digest: finalized.value.digest,
|
|
326
|
+
receipts: finalized.value.receipts,
|
|
367
327
|
sourceRevision,
|
|
368
328
|
semantic: JSON.stringify(sorted(finalized.value.pack)),
|
|
369
329
|
};
|
|
@@ -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,18 +9,19 @@ 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 AnyScriptablePackDefinition,
|
|
16
|
+
parsePackSourceJson,
|
|
17
|
+
projectDirectPackJson,
|
|
18
|
+
projectScriptablePackMeta,
|
|
19
|
+
type ScriptablePackSourceMeta,
|
|
20
|
+
} from './pack-authoring.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
|
loadScriptablePack,
|
|
@@ -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: ScriptablePackSourceMeta;
|
|
89
|
+
readonly definition: Readonly<AnyScriptablePackDefinition>;
|
|
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,16 @@ export interface PackInventoryDocument {
|
|
|
120
125
|
readonly assets: readonly PackInventoryAsset[];
|
|
121
126
|
}
|
|
122
127
|
|
|
128
|
+
export interface PackSourceInventoryDocument {
|
|
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 = LegacyPackInventoryDocument | PackSourceInventoryDocument;
|
|
137
|
+
|
|
123
138
|
export interface PackInventoryAsset {
|
|
124
139
|
readonly guid: string;
|
|
125
140
|
readonly kind: string;
|
|
@@ -146,12 +161,14 @@ export type ScanSourceDeclaration =
|
|
|
146
161
|
readonly sourceText: string;
|
|
147
162
|
readonly value: PackInventoryDocument;
|
|
148
163
|
}
|
|
149
|
-
|
|
|
164
|
+
| {
|
|
150
165
|
readonly format: 'pack.ts';
|
|
151
166
|
readonly sourcePath: string;
|
|
152
167
|
readonly sourceRevision: string;
|
|
153
|
-
readonly value:
|
|
154
|
-
|
|
168
|
+
readonly value: ScriptablePackSourceMeta;
|
|
169
|
+
readonly definition: AnyScriptablePackDefinition;
|
|
170
|
+
readonly sourceClosure: readonly ScriptablePackSourceClosureEntry[];
|
|
171
|
+
};
|
|
155
172
|
|
|
156
173
|
interface ScanCapture {
|
|
157
174
|
readonly declarations: Map<string, ScanSourceDeclaration>;
|
|
@@ -169,6 +186,7 @@ interface ScanCapture {
|
|
|
169
186
|
*/
|
|
170
187
|
const BLACKLIST = new Set([
|
|
171
188
|
'node_modules',
|
|
189
|
+
'__tests__',
|
|
172
190
|
'.forgeax-harness',
|
|
173
191
|
'.forgeax',
|
|
174
192
|
'.git',
|
|
@@ -349,12 +367,99 @@ async function scanValidated(
|
|
|
349
367
|
// kind stays in the path/detail evidence; identity is the normalized GUID.
|
|
350
368
|
const guidToPath = new Map<string, string>();
|
|
351
369
|
const packRefs = new Map<string, string[]>(); // guid -> refs[]
|
|
370
|
+
const packageIdToPath = new Map<string, string>();
|
|
371
|
+
const packageKind = new Map<string, 'legacy' | 'direct' | 'instance' | 'scriptable'>();
|
|
372
|
+
const instanceParents: {
|
|
373
|
+
readonly path: string;
|
|
374
|
+
readonly packageId: string;
|
|
375
|
+
readonly parent: string;
|
|
376
|
+
}[] = [];
|
|
377
|
+
|
|
378
|
+
function registerPackage(
|
|
379
|
+
packageId: string | undefined,
|
|
380
|
+
path: string,
|
|
381
|
+
kind: 'legacy' | 'direct' | 'instance' | 'scriptable',
|
|
382
|
+
): PackError | undefined {
|
|
383
|
+
if (packageId === undefined) return undefined;
|
|
384
|
+
const normalized = packageId.toLowerCase();
|
|
385
|
+
const existing = packageIdToPath.get(normalized);
|
|
386
|
+
if (existing !== undefined) {
|
|
387
|
+
return makePackError('pack-guid-collision', {
|
|
388
|
+
paths: [existing, path],
|
|
389
|
+
guid: normalized,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
packageIdToPath.set(normalized, path);
|
|
393
|
+
packageKind.set(normalized, kind);
|
|
394
|
+
return undefined;
|
|
395
|
+
}
|
|
352
396
|
|
|
353
397
|
for (const packPath of packPaths) {
|
|
354
398
|
const loaded = await readValidatedJson(packPath, 'pack-malformed-pack', validatePack);
|
|
355
399
|
if (!loaded.ok) return loaded;
|
|
356
400
|
const { raw, parsed } = loaded.value;
|
|
357
401
|
|
|
402
|
+
if (record(parsed) && parsed.schemaVersion === '3.0.0') {
|
|
403
|
+
const authoring = parsePackSourceJson(parsed);
|
|
404
|
+
if (!authoring.ok) {
|
|
405
|
+
return packErr(
|
|
406
|
+
makePackError('pack-malformed-pack', {
|
|
407
|
+
path: packPath,
|
|
408
|
+
ajvErrors: [{ instancePath: '', message: authoring.error.code }],
|
|
409
|
+
}),
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
const packageId = authoring.value.packageId;
|
|
413
|
+
const packageCollision = registerPackage(
|
|
414
|
+
PackageId.format(packageId),
|
|
415
|
+
packPath,
|
|
416
|
+
authoring.value.format,
|
|
417
|
+
);
|
|
418
|
+
if (packageCollision !== undefined) return packErr(packageCollision);
|
|
419
|
+
if (authoring.value.format === 'instance') {
|
|
420
|
+
instanceParents.push({
|
|
421
|
+
path: packPath,
|
|
422
|
+
packageId: PackageId.format(authoring.value.packageId),
|
|
423
|
+
parent: PackageId.format(authoring.value.parent),
|
|
424
|
+
});
|
|
425
|
+
} else {
|
|
426
|
+
const projected = projectDirectPackJson(authoring.value);
|
|
427
|
+
if (!projected.ok) {
|
|
428
|
+
return packErr(
|
|
429
|
+
makePackError('pack-malformed-pack', {
|
|
430
|
+
path: packPath,
|
|
431
|
+
ajvErrors: [{ instancePath: '/assets', message: projected.error.code }],
|
|
432
|
+
}),
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
for (const asset of projected.value.assets) {
|
|
436
|
+
const normalizedGuid = asset.guid.toLowerCase();
|
|
437
|
+
const existing = guidToPath.get(normalizedGuid);
|
|
438
|
+
if (existing !== undefined) {
|
|
439
|
+
return packErr(
|
|
440
|
+
makePackError('pack-guid-collision', {
|
|
441
|
+
paths: [existing, packPath],
|
|
442
|
+
guid: normalizedGuid,
|
|
443
|
+
}),
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
guidToPath.set(normalizedGuid, packPath);
|
|
447
|
+
packRefs.set(normalizedGuid, [
|
|
448
|
+
...asset.refs.map((ref) => ref.toLowerCase()),
|
|
449
|
+
...extractMountSourceGuids(asset),
|
|
450
|
+
]);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
capture?.declarations.set(packPath, {
|
|
454
|
+
format: 'pack.json',
|
|
455
|
+
sourcePath: packPath,
|
|
456
|
+
sourceRevision: `sha256:${createHash('sha256').update(raw).digest('hex')}`,
|
|
457
|
+
sourceText: raw,
|
|
458
|
+
value: parsed as unknown as PackSourceInventoryDocument,
|
|
459
|
+
});
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
|
|
358
463
|
const packageContract = validateProducerContract(parsed);
|
|
359
464
|
if (!packageContract.ok) {
|
|
360
465
|
return packErr(
|
|
@@ -366,7 +471,9 @@ async function scanValidated(
|
|
|
366
471
|
}
|
|
367
472
|
|
|
368
473
|
// Step 3: validate GUIDs in pack
|
|
369
|
-
const packObj = parsed as unknown as
|
|
474
|
+
const packObj = parsed as unknown as LegacyPackInventoryDocument;
|
|
475
|
+
const packageCollision = registerPackage(packObj.packageId, packPath, 'legacy');
|
|
476
|
+
if (packageCollision !== undefined) return packErr(packageCollision);
|
|
370
477
|
for (const asset of packObj.assets) {
|
|
371
478
|
if (
|
|
372
479
|
asset.kind === 'particle-effect' &&
|
|
@@ -477,8 +584,9 @@ async function scanValidated(
|
|
|
477
584
|
});
|
|
478
585
|
}
|
|
479
586
|
|
|
480
|
-
// Step 2 + 3 + 5: parse + schema validate + GUID format validate + orphan check for meta files
|
|
481
|
-
|
|
587
|
+
// Step 2 + 3 + 5: parse + schema validate + GUID format validate + orphan check for meta files.
|
|
588
|
+
// Meta source paths are local to their sidecar: an omitted source names the
|
|
589
|
+
// companion file and an explicit source is resolved relative to the sidecar.
|
|
482
590
|
for (const metaPath of metaPaths) {
|
|
483
591
|
const loaded = await readValidatedJson(metaPath, 'pack-malformed-meta', validateMeta);
|
|
484
592
|
if (!loaded.ok) return loaded;
|
|
@@ -496,6 +604,8 @@ async function scanValidated(
|
|
|
496
604
|
|
|
497
605
|
// Step 3: validate GUIDs in meta subAssets
|
|
498
606
|
const metaObj = parsed as unknown as MetaInventoryDocument;
|
|
607
|
+
const packageCollision = registerPackage(metaObj.packageId, metaPath, 'legacy');
|
|
608
|
+
if (packageCollision !== undefined) return packErr(packageCollision);
|
|
499
609
|
capture?.declarations.set(metaPath, {
|
|
500
610
|
format: 'meta.json',
|
|
501
611
|
sourcePath: metaPath,
|
|
@@ -537,13 +647,7 @@ async function scanValidated(
|
|
|
537
647
|
}
|
|
538
648
|
|
|
539
649
|
// 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;
|
|
650
|
+
const expectedSourcePath = resolveAssetSource(metaPath, metaObj.source);
|
|
547
651
|
try {
|
|
548
652
|
await stat(expectedSourcePath);
|
|
549
653
|
} catch {
|
|
@@ -556,7 +660,8 @@ async function scanValidated(
|
|
|
556
660
|
}
|
|
557
661
|
}
|
|
558
662
|
|
|
559
|
-
// ScriptablePack identity enters the
|
|
663
|
+
// ScriptablePack source identity enters the package registry. Its outputs are
|
|
664
|
+
// intentionally absent until the dynamic build worklist evaluates it.
|
|
560
665
|
for (const sourcePath of scriptablePaths) {
|
|
561
666
|
let source: string;
|
|
562
667
|
try {
|
|
@@ -565,17 +670,17 @@ async function scanValidated(
|
|
|
565
670
|
return packErr(
|
|
566
671
|
makePackError('pack-malformed-meta', {
|
|
567
672
|
path: sourcePath,
|
|
568
|
-
ajvErrors: [{ instancePath: '', message: '
|
|
673
|
+
ajvErrors: [{ instancePath: '', message: 'Pack source read failed' }],
|
|
569
674
|
}),
|
|
570
675
|
);
|
|
571
676
|
}
|
|
572
|
-
const
|
|
677
|
+
const loaderOptions = {
|
|
573
678
|
...(opts.scriptablePack ?? {}),
|
|
574
679
|
...(capture === undefined ? { metadataOnly: true } : {}),
|
|
575
|
-
}
|
|
680
|
+
};
|
|
681
|
+
const loaded = await loadScriptablePack(sourcePath, loaderOptions);
|
|
576
682
|
if (!loaded.ok) {
|
|
577
|
-
const diagnostic =
|
|
578
|
-
'diagnostic' in loaded.error.detail ? loaded.error.detail.diagnostic : undefined;
|
|
683
|
+
const diagnostic = loaded.error.detail.diagnostic;
|
|
579
684
|
return packErr(
|
|
580
685
|
makePackError('pack-malformed-meta', {
|
|
581
686
|
path: sourcePath,
|
|
@@ -583,9 +688,9 @@ async function scanValidated(
|
|
|
583
688
|
{
|
|
584
689
|
instancePath: '',
|
|
585
690
|
message:
|
|
586
|
-
diagnostic ===
|
|
587
|
-
? loaded.error.code
|
|
588
|
-
:
|
|
691
|
+
typeof diagnostic === 'string'
|
|
692
|
+
? `${loaded.error.code}: ${diagnostic}`
|
|
693
|
+
: loaded.error.code,
|
|
589
694
|
},
|
|
590
695
|
],
|
|
591
696
|
}),
|
|
@@ -598,41 +703,84 @@ async function scanValidated(
|
|
|
598
703
|
return packErr(
|
|
599
704
|
makePackError('pack-malformed-meta', {
|
|
600
705
|
path: sourcePath,
|
|
601
|
-
ajvErrors: [{ instancePath: '', message: '
|
|
706
|
+
ajvErrors: [{ instancePath: '', message: 'Pack source closure read failed' }],
|
|
602
707
|
}),
|
|
603
708
|
);
|
|
604
709
|
}
|
|
710
|
+
const packageCollision = registerPackage(
|
|
711
|
+
PackageId.format(loaded.value.packageId),
|
|
712
|
+
sourcePath,
|
|
713
|
+
'scriptable',
|
|
714
|
+
);
|
|
715
|
+
if (packageCollision !== undefined) return packErr(packageCollision);
|
|
605
716
|
const meta = projectScriptablePackMeta(loaded.value, sourcePath);
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
return packErr(
|
|
609
|
-
makePackError('pack-malformed-meta', {
|
|
610
|
-
path: sourcePath,
|
|
611
|
-
ajvErrors: [{ instancePath: '/subAssets', message: topology.error.code }],
|
|
612
|
-
}),
|
|
613
|
-
);
|
|
614
|
-
}
|
|
615
|
-
const declaration = {
|
|
717
|
+
capture?.declarations.set(sourcePath, {
|
|
718
|
+
format: 'pack.ts',
|
|
616
719
|
sourcePath,
|
|
617
720
|
sourceRevision: `sha256:${createHash('sha256').update(source).digest('hex')}`,
|
|
618
|
-
meta,
|
|
721
|
+
value: meta,
|
|
619
722
|
definition: loaded.value,
|
|
620
723
|
sourceClosure,
|
|
621
|
-
} satisfies ScriptablePackInventoryDeclaration;
|
|
622
|
-
capture?.declarations.set(sourcePath, {
|
|
623
|
-
format: 'pack.ts',
|
|
624
|
-
...declaration,
|
|
625
|
-
value: meta,
|
|
626
724
|
});
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// v3 instance parent validation happens after all declarations have been
|
|
728
|
+
// registered, so scan order cannot change missing-parent or cycle results.
|
|
729
|
+
const parentByPackageId = new Map(
|
|
730
|
+
instanceParents.map((instance) => [instance.packageId.toLowerCase(), instance]),
|
|
731
|
+
);
|
|
732
|
+
for (const instance of instanceParents) {
|
|
733
|
+
const parentId = instance.parent.toLowerCase();
|
|
734
|
+
const parentPath = packageIdToPath.get(parentId);
|
|
735
|
+
if (parentPath === undefined) {
|
|
736
|
+
return packErr(
|
|
737
|
+
makePackError('pack-malformed-pack', {
|
|
738
|
+
path: instance.path,
|
|
739
|
+
reason: 'pack-parent-not-found',
|
|
740
|
+
ajvErrors: [
|
|
741
|
+
{
|
|
742
|
+
instancePath: '/parent',
|
|
743
|
+
message: `parent packageId ${instance.parent} was not found`,
|
|
744
|
+
},
|
|
745
|
+
],
|
|
746
|
+
}),
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
const kind = packageKind.get(parentId);
|
|
750
|
+
if (kind !== 'scriptable' && kind !== 'instance') {
|
|
751
|
+
return packErr(
|
|
752
|
+
makePackError('pack-malformed-pack', {
|
|
753
|
+
path: instance.path,
|
|
754
|
+
reason: 'pack-parent-has-no-parameters',
|
|
755
|
+
ajvErrors: [
|
|
756
|
+
{
|
|
757
|
+
instancePath: '/parent',
|
|
758
|
+
message: `parent ${instance.parent} is not a ScriptablePack source with parameters`,
|
|
759
|
+
},
|
|
760
|
+
],
|
|
761
|
+
}),
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
for (const instance of instanceParents) {
|
|
766
|
+
const chain = new Set<string>();
|
|
767
|
+
let current = instance.packageId.toLowerCase();
|
|
768
|
+
while (true) {
|
|
769
|
+
if (chain.has(current)) {
|
|
631
770
|
return packErr(
|
|
632
|
-
makePackError('pack-
|
|
771
|
+
makePackError('pack-malformed-pack', {
|
|
772
|
+
path: instance.path,
|
|
773
|
+
reason: 'pack-parent-cycle',
|
|
774
|
+
ajvErrors: [
|
|
775
|
+
{ instancePath: '/parent', message: `parent chain repeats packageId ${current}` },
|
|
776
|
+
],
|
|
777
|
+
}),
|
|
633
778
|
);
|
|
634
779
|
}
|
|
635
|
-
|
|
780
|
+
chain.add(current);
|
|
781
|
+
const next = parentByPackageId.get(current);
|
|
782
|
+
if (next === undefined) break;
|
|
783
|
+
current = next.parent.toLowerCase();
|
|
636
784
|
}
|
|
637
785
|
}
|
|
638
786
|
|
|
@@ -698,6 +846,38 @@ export async function scanInventory(
|
|
|
698
846
|
for (const sourcePath of scanned.value) {
|
|
699
847
|
const declaration = declarations.get(sourcePath);
|
|
700
848
|
if (declaration?.format !== 'pack.json') continue;
|
|
849
|
+
if (declaration.value.schemaVersion === '3.0.0') {
|
|
850
|
+
const parsed = parsePackSourceJson(declaration.value);
|
|
851
|
+
if (!parsed.ok) {
|
|
852
|
+
return packErr(
|
|
853
|
+
makePackError('pack-malformed-pack', {
|
|
854
|
+
path: sourcePath,
|
|
855
|
+
ajvErrors: [{ instancePath: '', message: parsed.error.code }],
|
|
856
|
+
}),
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
if (parsed.value.format !== 'direct') continue;
|
|
860
|
+
const projected = projectDirectPackJson(parsed.value);
|
|
861
|
+
if (!projected.ok) {
|
|
862
|
+
return packErr(
|
|
863
|
+
makePackError('pack-malformed-pack', {
|
|
864
|
+
path: sourcePath,
|
|
865
|
+
ajvErrors: [{ instancePath: '/assets', message: projected.error.code }],
|
|
866
|
+
}),
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
for (const [index, asset] of projected.value.assets.entries()) {
|
|
870
|
+
inventory.push({
|
|
871
|
+
guid: asset.guid,
|
|
872
|
+
kind: asset.kind,
|
|
873
|
+
sourcePath,
|
|
874
|
+
sourceRevision: declaration.sourceRevision,
|
|
875
|
+
sourceKey: asset.sourceKey,
|
|
876
|
+
sourceIndex: index,
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
continue;
|
|
880
|
+
}
|
|
701
881
|
for (const [index, asset] of declaration.value.assets.entries()) {
|
|
702
882
|
inventory.push({
|
|
703
883
|
guid: asset.guid,
|
|
@@ -107,7 +107,10 @@
|
|
|
107
107
|
"derivedInterface": {
|
|
108
108
|
"type": "object",
|
|
109
109
|
"required": ["layoutIdentity"],
|
|
110
|
-
"properties": {
|
|
110
|
+
"properties": {
|
|
111
|
+
"layoutIdentity": { "type": "string", "minLength": 1 },
|
|
112
|
+
"layerPlanIdentity": { "type": "string", "minLength": 1 }
|
|
113
|
+
},
|
|
111
114
|
"additionalProperties": false
|
|
112
115
|
},
|
|
113
116
|
"publicationGeneration": { "type": "integer", "minimum": 1 }
|
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,
|