@forgeax/engine-pack 0.1.21 → 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 +3314 -803
- 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/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 +1252 -560
- 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/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/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
|
@@ -2,9 +2,8 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { readdir, readFile } from 'node:fs/promises';
|
|
3
3
|
import { basename, dirname, join, relative, resolve, sep } from 'node:path';
|
|
4
4
|
import type { SourceDeclarationEvidence } from '@forgeax/engine-types';
|
|
5
|
+
import { parseParameterizedPackJson, projectDirectPackJson } from '../parameterized-pack.js';
|
|
5
6
|
import { SCANNER_BLACKLIST, scan } from '../scanner.js';
|
|
6
|
-
import { projectScriptablePackMeta } from '../scriptable-pack.js';
|
|
7
|
-
import { loadScriptablePack } from '../scriptable-pack-node.js';
|
|
8
7
|
|
|
9
8
|
/** Project root and GUID used to locate an authoritative source declaration. */
|
|
10
9
|
export interface SourceInventoryRequest {
|
|
@@ -150,34 +149,43 @@ export async function readSourceInventory(
|
|
|
150
149
|
let authored: SourceDeclarationEvidence | undefined;
|
|
151
150
|
for (const path of paths) {
|
|
152
151
|
if (path.endsWith('.pack.ts')) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (meta.subAssets.some((asset) => asset.guid.toLowerCase() === guid)) {
|
|
157
|
-
return { origin: 'sourceMeta', sourcePath: path };
|
|
158
|
-
}
|
|
152
|
+
// Parameterized source identity is a build result, not a declaration
|
|
153
|
+
// table. The source index therefore records the source only after the
|
|
154
|
+
// producer publishes its materialized Pack output.
|
|
159
155
|
continue;
|
|
160
156
|
}
|
|
161
157
|
const raw = await readFile(path, 'utf8').catch(() => undefined);
|
|
162
158
|
if (raw === undefined) continue;
|
|
163
|
-
let parsed:
|
|
164
|
-
readonly assets?: readonly { readonly guid?: unknown }[];
|
|
165
|
-
readonly source?: unknown;
|
|
166
|
-
readonly inputFingerprint?: unknown;
|
|
167
|
-
readonly importSettings?: unknown;
|
|
168
|
-
readonly subAssets?: readonly { readonly guid?: unknown }[];
|
|
169
|
-
};
|
|
159
|
+
let parsed: Record<string, unknown>;
|
|
170
160
|
try {
|
|
171
|
-
|
|
161
|
+
const value: unknown = JSON.parse(raw);
|
|
162
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) continue;
|
|
163
|
+
parsed = value as Record<string, unknown>;
|
|
172
164
|
} catch {
|
|
173
165
|
continue;
|
|
174
166
|
}
|
|
175
167
|
if (path.endsWith('.pack.json')) {
|
|
176
|
-
if (
|
|
177
|
-
parsed
|
|
168
|
+
if (parsed.schemaVersion === '3.0.0') {
|
|
169
|
+
const direct = parseParameterizedPackJson(parsed);
|
|
170
|
+
if (direct.ok && direct.value.format === 'direct') {
|
|
171
|
+
const projected = projectDirectPackJson(direct.value);
|
|
172
|
+
if (
|
|
173
|
+
projected.ok &&
|
|
174
|
+
projected.value.assets.some((asset) => asset.guid.toLowerCase() === guid)
|
|
175
|
+
) {
|
|
176
|
+
authored = { origin: 'authoredPack', sourcePath: path };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
} else if (
|
|
180
|
+
Array.isArray(parsed.assets) &&
|
|
181
|
+
parsed.assets.some(
|
|
178
182
|
(asset) =>
|
|
179
|
-
|
|
180
|
-
|
|
183
|
+
asset !== null &&
|
|
184
|
+
typeof asset === 'object' &&
|
|
185
|
+
!Array.isArray(asset) &&
|
|
186
|
+
typeof (asset as { readonly guid?: unknown }).guid === 'string' &&
|
|
187
|
+
((asset as { readonly guid: string }).guid === request.guid ||
|
|
188
|
+
(asset as { readonly guid: string }).guid.toLowerCase() === guid),
|
|
181
189
|
)
|
|
182
190
|
) {
|
|
183
191
|
authored = { origin: 'authoredPack', sourcePath: path };
|
|
@@ -185,7 +193,12 @@ export async function readSourceInventory(
|
|
|
185
193
|
continue;
|
|
186
194
|
}
|
|
187
195
|
if (path.endsWith('.meta.json')) {
|
|
188
|
-
const source = sourceFromMeta(
|
|
196
|
+
const source = sourceFromMeta(
|
|
197
|
+
path,
|
|
198
|
+
parsed as Parameters<typeof sourceFromMeta>[1],
|
|
199
|
+
request.guid,
|
|
200
|
+
raw,
|
|
201
|
+
);
|
|
189
202
|
if (source !== undefined) return source;
|
|
190
203
|
}
|
|
191
204
|
}
|
package/src/guid.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sha1 } from '@noble/hashes/legacy.js';
|
|
1
2
|
import { uuidv7obj } from 'uuidv7';
|
|
2
3
|
import { PackError } from './errors.js';
|
|
3
4
|
|
|
@@ -8,6 +9,9 @@ import { PackError } from './errors.js';
|
|
|
8
9
|
*/
|
|
9
10
|
export type AssetGuid = Uint8Array & { readonly __guidBrand: 'AssetGuid' };
|
|
10
11
|
|
|
12
|
+
/** Pack-level identity. It is intentionally not assignable to AssetGuid. */
|
|
13
|
+
export type PackageId = Uint8Array & { readonly __packageIdBrand: 'PackageId' };
|
|
14
|
+
|
|
11
15
|
/** Minimal Result alias for this module (structurally compatible with ScanResult). */
|
|
12
16
|
export type GuidResult<T, E> =
|
|
13
17
|
| { readonly ok: true; readonly value: T }
|
|
@@ -27,6 +31,13 @@ function bytesToDashForm(bytes: Uint8Array): string {
|
|
|
27
31
|
|
|
28
32
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
29
33
|
|
|
34
|
+
/** Stable author-owned output identity. Paths and payloads are not identity inputs. */
|
|
35
|
+
export const PACK_SOURCE_KEY_RE = /^[a-z0-9][a-z0-9._-]*(\/[a-z0-9][a-z0-9._-]*)*$/;
|
|
36
|
+
|
|
37
|
+
export function isValidPackSourceKey(value: unknown): value is string {
|
|
38
|
+
return typeof value === 'string' && PACK_SOURCE_KEY_RE.test(value);
|
|
39
|
+
}
|
|
40
|
+
|
|
30
41
|
export function isValidAssetGuidString(value: unknown): value is string {
|
|
31
42
|
return typeof value === 'string' && UUID_RE.test(value);
|
|
32
43
|
}
|
|
@@ -40,6 +51,54 @@ function dashFormToBytes(dashForm: string): Uint8Array {
|
|
|
40
51
|
return bytes;
|
|
41
52
|
}
|
|
42
53
|
|
|
54
|
+
function randomUuidBytes(): Uint8Array {
|
|
55
|
+
const uuid = uuidv7obj();
|
|
56
|
+
const bytes = new Uint8Array(16);
|
|
57
|
+
bytes.set(uuid.bytes);
|
|
58
|
+
return bytes;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function packageId(value: Uint8Array): PackageId {
|
|
62
|
+
return value as PackageId;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Utilities for the pack-level UUID identity. */
|
|
66
|
+
export const PackageId = {
|
|
67
|
+
parse(dashForm: string): GuidResult<PackageId, PackError> {
|
|
68
|
+
const parsed = AssetGuid.parse(dashForm);
|
|
69
|
+
return parsed.ok ? { ok: true, value: packageId(parsed.value) } : parsed;
|
|
70
|
+
},
|
|
71
|
+
format(value: PackageId): string {
|
|
72
|
+
return bytesToDashForm(value);
|
|
73
|
+
},
|
|
74
|
+
random(): PackageId {
|
|
75
|
+
return packageId(randomUuidBytes());
|
|
76
|
+
},
|
|
77
|
+
} as const;
|
|
78
|
+
|
|
79
|
+
function derivedGuid(namespace: PackageId, sourceKey: string): AssetGuid {
|
|
80
|
+
if (!(namespace instanceof Uint8Array) || namespace.byteLength !== 16) {
|
|
81
|
+
throw new TypeError('AssetGuid.derive requires a 16-byte PackageId');
|
|
82
|
+
}
|
|
83
|
+
if (!isValidPackSourceKey(sourceKey)) {
|
|
84
|
+
const error = new TypeError(
|
|
85
|
+
`AssetGuid.derive received invalid sourceKey ${JSON.stringify(sourceKey)}`,
|
|
86
|
+
) as TypeError & { readonly code?: string };
|
|
87
|
+
Object.defineProperty(error, 'code', { value: 'pack-source-key-invalid' });
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
const name = new TextEncoder().encode(sourceKey);
|
|
91
|
+
const input = new Uint8Array(namespace.byteLength + name.byteLength);
|
|
92
|
+
input.set(namespace, 0);
|
|
93
|
+
input.set(name, namespace.byteLength);
|
|
94
|
+
const digest = sha1(input);
|
|
95
|
+
const result = digest.slice(0, 16);
|
|
96
|
+
// RFC 4122 §4.1.1 / §4.1.3: UUID version 5, RFC variant.
|
|
97
|
+
result[6] = ((result[6] ?? 0) & 0x0f) | 0x50;
|
|
98
|
+
result[8] = ((result[8] ?? 0) & 0x3f) | 0x80;
|
|
99
|
+
return brand(result);
|
|
100
|
+
}
|
|
101
|
+
|
|
43
102
|
/**
|
|
44
103
|
* Utilities for the AssetGuid branded 16-byte UUID type.
|
|
45
104
|
* Declare the type via `import type { AssetGuid } from '@forgeax/engine-pack/guid'`
|
|
@@ -91,9 +150,11 @@ export const AssetGuid = {
|
|
|
91
150
|
* Works in both Node.js and browser environments.
|
|
92
151
|
*/
|
|
93
152
|
random(): AssetGuid {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
153
|
+
return brand(randomUuidBytes());
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
/** Derive the stable UUIDv5 projection for one Pack subject and sourceKey. */
|
|
157
|
+
derive(namespace: PackageId, sourceKey: string): AssetGuid {
|
|
158
|
+
return derivedGuid(namespace, sourceKey);
|
|
98
159
|
},
|
|
99
160
|
} as const;
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,13 @@ export {
|
|
|
22
22
|
validateMaterialCookReceipt,
|
|
23
23
|
} from './evidence/material-cook.js';
|
|
24
24
|
export { buildOfflineAssetEvidence, packageVerification } from './evidence/offline-evidence.js';
|
|
25
|
+
export {
|
|
26
|
+
AssetGuid,
|
|
27
|
+
isValidAssetGuidString,
|
|
28
|
+
isValidPackSourceKey,
|
|
29
|
+
PACK_SOURCE_KEY_RE,
|
|
30
|
+
PackageId,
|
|
31
|
+
} from './guid.js';
|
|
25
32
|
export {
|
|
26
33
|
decodeMeshBinHeader,
|
|
27
34
|
MESH_BIN_DIGEST_BYTES,
|
|
@@ -33,24 +40,18 @@ export {
|
|
|
33
40
|
type MeshBinHeaderV4,
|
|
34
41
|
writeMeshBinHeader,
|
|
35
42
|
} from './mesh-bin-contract.js';
|
|
43
|
+
export * from './parameterized-pack.js';
|
|
36
44
|
export { validateProducerContract, validateProducerOutputs } from './producer-contract.js';
|
|
37
45
|
export {
|
|
38
46
|
type AssetReader,
|
|
39
47
|
isScriptablePackAssetKind,
|
|
40
|
-
|
|
48
|
+
projectScriptablePackSceneComponents,
|
|
41
49
|
SCRIPTABLE_PACK_ASSET_KINDS,
|
|
42
|
-
type ScriptablePackAssetDeclaration,
|
|
43
|
-
type ScriptablePackAssetDeclarations,
|
|
44
|
-
type ScriptablePackAssetFor,
|
|
45
50
|
type ScriptablePackAssetKind,
|
|
46
|
-
type ScriptablePackDefinition,
|
|
47
|
-
type ScriptablePackError,
|
|
48
|
-
type ScriptablePackExternalAssets,
|
|
49
|
-
type ScriptablePackMetaJson,
|
|
50
|
-
type ScriptablePackOutputs,
|
|
51
|
-
type ScriptablePackPublicationEnvelope,
|
|
52
51
|
type ScriptablePackReadError,
|
|
53
|
-
|
|
52
|
+
type ScriptablePackSceneComponent,
|
|
53
|
+
type ScriptablePackSceneComponentInput,
|
|
54
|
+
type ScriptablePackSourceClosureEntry,
|
|
54
55
|
} from './scriptable-pack.js';
|
|
55
56
|
export { calculateTopologyDiff, diffTopology } from './topology.js';
|
|
56
57
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AssetRef, SceneEntityRef } from '@forgeax/engine-types';
|
|
2
|
+
import type { AuthorInventory } from './declaration.js';
|
|
3
|
+
|
|
4
|
+
export function projectAssetRefs(inventory: AuthorInventory): readonly AssetRef[] {
|
|
5
|
+
return inventory.declarations.map(
|
|
6
|
+
(row) =>
|
|
7
|
+
({
|
|
8
|
+
guid: row.guid,
|
|
9
|
+
sourceKey: row.sourceKey,
|
|
10
|
+
kind: row.kind,
|
|
11
|
+
}) as AssetRef,
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Derive instance-relative scene refs from the same validated author rows. */
|
|
16
|
+
export function projectSceneEntityRefs(inventory: AuthorInventory): readonly SceneEntityRef[] {
|
|
17
|
+
return inventory.declarations.flatMap((row) =>
|
|
18
|
+
row.sceneBindings === undefined
|
|
19
|
+
? []
|
|
20
|
+
: row.sceneBindings.map((bindingKey) => ({
|
|
21
|
+
sceneSourceKey: row.sourceKey,
|
|
22
|
+
bindingKey,
|
|
23
|
+
})),
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { err, ok, type Result } from '@forgeax/engine-types';
|
|
2
|
+
|
|
3
|
+
export interface AuthorInventoryRow {
|
|
4
|
+
readonly guid: string;
|
|
5
|
+
readonly sourceKey: string;
|
|
6
|
+
readonly kind: string;
|
|
7
|
+
readonly payload: Readonly<Record<string, unknown>>;
|
|
8
|
+
readonly refs: readonly string[];
|
|
9
|
+
readonly sceneBindings?: readonly string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface AuthorInventory {
|
|
13
|
+
readonly declarations: readonly AuthorInventoryRow[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type InventoryErrorCode =
|
|
17
|
+
| 'inventory-source-key-missing'
|
|
18
|
+
| 'inventory-source-key-duplicate'
|
|
19
|
+
| 'inventory-guid-duplicate'
|
|
20
|
+
| 'inventory-scene-binding-missing'
|
|
21
|
+
| 'inventory-scene-binding-duplicate';
|
|
22
|
+
|
|
23
|
+
export type InventoryError = {
|
|
24
|
+
readonly code: InventoryErrorCode;
|
|
25
|
+
readonly expected: string;
|
|
26
|
+
readonly hint: string;
|
|
27
|
+
readonly detail: {
|
|
28
|
+
readonly guid?: string;
|
|
29
|
+
readonly sourceKey?: string;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function failure(
|
|
34
|
+
code: InventoryErrorCode,
|
|
35
|
+
expected: string,
|
|
36
|
+
hint: string,
|
|
37
|
+
detail: InventoryError['detail'],
|
|
38
|
+
): Result<never, InventoryError> {
|
|
39
|
+
return err({ code, expected, hint, detail });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function validateAuthorInventory(value: unknown): Result<AuthorInventory, InventoryError> {
|
|
43
|
+
if (
|
|
44
|
+
typeof value !== 'object' ||
|
|
45
|
+
value === null ||
|
|
46
|
+
!Array.isArray((value as { readonly declarations?: unknown }).declarations)
|
|
47
|
+
) {
|
|
48
|
+
return failure(
|
|
49
|
+
'inventory-source-key-missing',
|
|
50
|
+
'an author inventory with declaration rows',
|
|
51
|
+
'declare each author asset in the source inventory',
|
|
52
|
+
{},
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const guids = new Set<string>();
|
|
57
|
+
const sourceKeys = new Set<string>();
|
|
58
|
+
const declarations: AuthorInventoryRow[] = [];
|
|
59
|
+
const rows = (value as { readonly declarations: readonly unknown[] }).declarations;
|
|
60
|
+
for (const raw of rows) {
|
|
61
|
+
if (typeof raw !== 'object' || raw === null) {
|
|
62
|
+
return failure(
|
|
63
|
+
'inventory-source-key-missing',
|
|
64
|
+
'each declaration has a non-empty sourceKey',
|
|
65
|
+
'add sourceKey to the author declaration before projection',
|
|
66
|
+
{},
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
const row = raw as Record<string, unknown>;
|
|
70
|
+
const guid = typeof row.guid === 'string' ? row.guid : undefined;
|
|
71
|
+
const sourceKey = typeof row.sourceKey === 'string' ? row.sourceKey : undefined;
|
|
72
|
+
if (
|
|
73
|
+
guid === undefined ||
|
|
74
|
+
sourceKey === undefined ||
|
|
75
|
+
sourceKey.trim().length === 0 ||
|
|
76
|
+
sourceKey !== sourceKey.trim()
|
|
77
|
+
) {
|
|
78
|
+
return failure(
|
|
79
|
+
'inventory-source-key-missing',
|
|
80
|
+
'each declaration has a non-empty sourceKey',
|
|
81
|
+
'add sourceKey to the author declaration before projection',
|
|
82
|
+
{
|
|
83
|
+
...(guid === undefined ? {} : { guid }),
|
|
84
|
+
...(sourceKey === undefined ? {} : { sourceKey }),
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
const normalizedGuid = guid.toLowerCase();
|
|
89
|
+
if (guids.has(normalizedGuid)) {
|
|
90
|
+
return failure(
|
|
91
|
+
'inventory-guid-duplicate',
|
|
92
|
+
'every GUID identifies exactly one author declaration',
|
|
93
|
+
'remove or rename the duplicate GUID before publishing the inventory',
|
|
94
|
+
{ guid, sourceKey },
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
if (sourceKeys.has(sourceKey)) {
|
|
98
|
+
return failure(
|
|
99
|
+
'inventory-source-key-duplicate',
|
|
100
|
+
'every sourceKey identifies exactly one author declaration',
|
|
101
|
+
'rename the duplicate semantic sourceKey before publishing the inventory',
|
|
102
|
+
{ guid, sourceKey },
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
if (row.kind === 'scene') {
|
|
106
|
+
const payload = row.payload;
|
|
107
|
+
const payloadRecord =
|
|
108
|
+
typeof payload === 'object' && payload !== null
|
|
109
|
+
? (payload as Record<string, unknown>)
|
|
110
|
+
: undefined;
|
|
111
|
+
const entities =
|
|
112
|
+
payloadRecord !== undefined && Array.isArray(payloadRecord.entities)
|
|
113
|
+
? payloadRecord.entities
|
|
114
|
+
: [];
|
|
115
|
+
const bindings = new Set<string>();
|
|
116
|
+
for (const entity of entities) {
|
|
117
|
+
const bindingKey =
|
|
118
|
+
typeof entity === 'object' && entity !== null && typeof entity.bindingKey === 'string'
|
|
119
|
+
? entity.bindingKey
|
|
120
|
+
: undefined;
|
|
121
|
+
if (bindingKey !== undefined && bindingKey.length === 0) {
|
|
122
|
+
return failure(
|
|
123
|
+
'inventory-scene-binding-missing',
|
|
124
|
+
'each declared scene bindingKey is non-empty',
|
|
125
|
+
'remove the empty bindingKey or declare a stable key in the scene producer',
|
|
126
|
+
{ sourceKey },
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
if (bindingKey === undefined) continue;
|
|
130
|
+
if (bindings.has(bindingKey)) {
|
|
131
|
+
return failure(
|
|
132
|
+
'inventory-scene-binding-duplicate',
|
|
133
|
+
'bindingKey values are unique within one scene',
|
|
134
|
+
'rename the duplicate bindingKey in the scene producer',
|
|
135
|
+
{ sourceKey },
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
bindings.add(bindingKey);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
guids.add(normalizedGuid);
|
|
142
|
+
sourceKeys.add(sourceKey);
|
|
143
|
+
const sceneBindings =
|
|
144
|
+
row.kind === 'scene' && typeof row.payload === 'object' && row.payload !== null
|
|
145
|
+
? (row.payload as { readonly entities?: readonly unknown[] }).entities?.flatMap(
|
|
146
|
+
(entity) => {
|
|
147
|
+
if (typeof entity !== 'object' || entity === null) return [];
|
|
148
|
+
const key = (entity as { readonly bindingKey?: unknown }).bindingKey;
|
|
149
|
+
return typeof key === 'string' && key.length > 0 ? [key] : [];
|
|
150
|
+
},
|
|
151
|
+
)
|
|
152
|
+
: undefined;
|
|
153
|
+
declarations.push({
|
|
154
|
+
guid,
|
|
155
|
+
sourceKey,
|
|
156
|
+
kind: typeof row.kind === 'string' ? row.kind : 'unknown',
|
|
157
|
+
payload:
|
|
158
|
+
typeof row.payload === 'object' && row.payload !== null
|
|
159
|
+
? (row.payload as Readonly<Record<string, unknown>>)
|
|
160
|
+
: {},
|
|
161
|
+
refs: Array.isArray(row.refs)
|
|
162
|
+
? row.refs.filter((ref): ref is string => typeof ref === 'string')
|
|
163
|
+
: [],
|
|
164
|
+
...(sceneBindings === undefined ? {} : { sceneBindings }),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return ok({ declarations });
|
|
168
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { validateProducerContract, validateProducerOutputs } from '../producer-contract.js';
|
|
2
|
+
export {
|
|
3
|
+
type InventoryDeclaration,
|
|
4
|
+
type MetaInventoryDocument,
|
|
5
|
+
type MetaInventorySubAsset,
|
|
6
|
+
type PackInventoryAsset,
|
|
7
|
+
type PackInventoryDocument,
|
|
8
|
+
SCANNER_BLACKLIST,
|
|
9
|
+
type ScanInventory,
|
|
10
|
+
type ScanOptions,
|
|
11
|
+
type ScanResult,
|
|
12
|
+
type ScanSourceDeclaration,
|
|
13
|
+
type ScriptablePackInventoryDeclaration,
|
|
14
|
+
type ScriptablePackScanOptions,
|
|
15
|
+
STANDARD_SCRIPTABLE_PACK_SCAN_OPTIONS,
|
|
16
|
+
scan,
|
|
17
|
+
scanInventory,
|
|
18
|
+
} from '../scanner.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AuthorInventory } from './declaration.js';
|
|
2
|
+
|
|
3
|
+
export type InventoryDigest = string;
|
|
4
|
+
|
|
5
|
+
export function inventoryDigest(inventory: AuthorInventory): InventoryDigest {
|
|
6
|
+
return inventory.declarations
|
|
7
|
+
.map(
|
|
8
|
+
(row) => `${row.guid}\0${row.sourceKey}\0${row.kind}\0${row.sceneBindings?.join('\0') ?? ''}`,
|
|
9
|
+
)
|
|
10
|
+
.sort()
|
|
11
|
+
.join('\n');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function syncAuthorInventory(inventory: AuthorInventory): AuthorInventory {
|
|
15
|
+
return {
|
|
16
|
+
declarations: inventory.declarations.map((row) => ({
|
|
17
|
+
...row,
|
|
18
|
+
refs: [...row.refs],
|
|
19
|
+
...(row.sceneBindings === undefined ? {} : { sceneBindings: [...row.sceneBindings] }),
|
|
20
|
+
})),
|
|
21
|
+
};
|
|
22
|
+
}
|
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
|
};
|