@forgeax/engine-pack 0.0.0-dev.8d955ade1c79
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/LICENSE +202 -0
- package/README.md +429 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/ai-recovery.unit.test.d.ts +2 -0
- package/dist/__tests__/ai-recovery.unit.test.d.ts.map +1 -0
- package/dist/__tests__/artifact-path.test.d.ts +2 -0
- package/dist/__tests__/artifact-path.test.d.ts.map +1 -0
- package/dist/__tests__/asset-evidence-error-hints.test.d.ts +2 -0
- package/dist/__tests__/asset-evidence-error-hints.test.d.ts.map +1 -0
- package/dist/__tests__/asset-evidence-lifecycle.test.d.ts +2 -0
- package/dist/__tests__/asset-evidence-lifecycle.test.d.ts.map +1 -0
- package/dist/__tests__/asset-evidence-schema.test.d.ts +2 -0
- package/dist/__tests__/asset-evidence-schema.test.d.ts.map +1 -0
- package/dist/__tests__/atlas-cli-region-mismatch.test.d.ts +2 -0
- package/dist/__tests__/atlas-cli-region-mismatch.test.d.ts.map +1 -0
- package/dist/__tests__/atlas-error-code-owner.test-d.d.ts +2 -0
- package/dist/__tests__/atlas-error-code-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/cli-asset-evidence.integration.test.d.ts +2 -0
- package/dist/__tests__/cli-asset-evidence.integration.test.d.ts.map +1 -0
- package/dist/__tests__/cli.unit.test.d.ts +2 -0
- package/dist/__tests__/cli.unit.test.d.ts.map +1 -0
- package/dist/__tests__/evidence-source-inventory.unit.test.d.ts +2 -0
- package/dist/__tests__/evidence-source-inventory.unit.test.d.ts.map +1 -0
- package/dist/__tests__/guid-collision.unit.test.d.ts +2 -0
- package/dist/__tests__/guid-collision.unit.test.d.ts.map +1 -0
- package/dist/__tests__/guid-validator-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/guid-validator-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/load-asset-config.test.d.ts +2 -0
- package/dist/__tests__/load-asset-config.test.d.ts.map +1 -0
- package/dist/__tests__/material-cook-dependencies.unit.test.d.ts +2 -0
- package/dist/__tests__/material-cook-dependencies.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-cook-receipt.unit.test.d.ts +2 -0
- package/dist/__tests__/material-cook-receipt.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-cook-schema.unit.test.d.ts +2 -0
- package/dist/__tests__/material-cook-schema.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-bin-consumer-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-bin-consumer-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-bin-contract.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-bin-contract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/meta-schema-optional.test.d.ts +2 -0
- package/dist/__tests__/meta-schema-optional.test.d.ts.map +1 -0
- package/dist/__tests__/meta-source-overrides.unit.test.d.ts +2 -0
- package/dist/__tests__/meta-source-overrides.unit.test.d.ts.map +1 -0
- package/dist/__tests__/native-cooker-registry.test.d.ts +2 -0
- package/dist/__tests__/native-cooker-registry.test.d.ts.map +1 -0
- package/dist/__tests__/offline-evidence-artifact-verification-owner.test-d.d.ts +2 -0
- package/dist/__tests__/offline-evidence-artifact-verification-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/offline-evidence.integration.test.d.ts +2 -0
- package/dist/__tests__/offline-evidence.integration.test.d.ts.map +1 -0
- package/dist/__tests__/owner-chain.integration.test.d.ts +2 -0
- package/dist/__tests__/owner-chain.integration.test.d.ts.map +1 -0
- package/dist/__tests__/pack-v2-fixture-migration.test.d.ts +2 -0
- package/dist/__tests__/pack-v2-fixture-migration.test.d.ts.map +1 -0
- package/dist/__tests__/pack-v2-schema.test.d.ts +2 -0
- package/dist/__tests__/pack-v2-schema.test.d.ts.map +1 -0
- package/dist/__tests__/pack.unit.test.d.ts +2 -0
- package/dist/__tests__/pack.unit.test.d.ts.map +1 -0
- package/dist/__tests__/package-finalizer.contract.test.d.ts +2 -0
- package/dist/__tests__/package-finalizer.contract.test.d.ts.map +1 -0
- package/dist/__tests__/producer-contract-schema.unit.test.d.ts +2 -0
- package/dist/__tests__/producer-contract-schema.unit.test.d.ts.map +1 -0
- package/dist/__tests__/resolve-asset-source.test.d.ts +2 -0
- package/dist/__tests__/resolve-asset-source.test.d.ts.map +1 -0
- package/dist/__tests__/runtime-projection.unit.test.d.ts +2 -0
- package/dist/__tests__/runtime-projection.unit.test.d.ts.map +1 -0
- package/dist/__tests__/runtime.browser.test.d.ts +2 -0
- package/dist/__tests__/runtime.browser.test.d.ts.map +1 -0
- package/dist/__tests__/scanner-inventory.contract.test.d.ts +2 -0
- package/dist/__tests__/scanner-inventory.contract.test.d.ts.map +1 -0
- package/dist/__tests__/scanner-inventory.test.d.ts +2 -0
- package/dist/__tests__/scanner-inventory.test.d.ts.map +1 -0
- package/dist/__tests__/scanner-mount-cycle.test.d.ts +2 -0
- package/dist/__tests__/scanner-mount-cycle.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-cli.integration.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-cli.integration.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-ddc-key.unit.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-ddc-key.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack.test-d.d.ts +2 -0
- package/dist/__tests__/scriptable-pack.test-d.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack.unit.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack.unit.test.d.ts.map +1 -0
- package/dist/__tests__/topology-actions.unit.test.d.ts +2 -0
- package/dist/__tests__/topology-actions.unit.test.d.ts.map +1 -0
- package/dist/__tests__/topology.unit.test.d.ts +2 -0
- package/dist/__tests__/topology.unit.test.d.ts.map +1 -0
- package/dist/artifact-path.d.ts +9 -0
- package/dist/artifact-path.d.ts.map +1 -0
- package/dist/artifact-path.mjs +53 -0
- package/dist/artifact-path.mjs.map +1 -0
- package/dist/atlas/run-atlas.d.ts +8 -0
- package/dist/atlas/run-atlas.d.ts.map +1 -0
- package/dist/atlas/shelf-pack.d.ts +55 -0
- package/dist/atlas/shelf-pack.d.ts.map +1 -0
- package/dist/bridge.d.ts +2 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/bridge.mjs +3 -0
- package/dist/bridge.mjs.map +1 -0
- package/dist/build.d.ts +19 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.mjs +10203 -0
- package/dist/build.mjs.map +1 -0
- package/dist/builtin.d.ts +44 -0
- package/dist/builtin.d.ts.map +1 -0
- package/dist/builtin.mjs +141 -0
- package/dist/builtin.mjs.map +1 -0
- package/dist/catalog-builder.d.ts +43 -0
- package/dist/catalog-builder.d.ts.map +1 -0
- package/dist/catalog-delta.d.ts +11 -0
- package/dist/catalog-delta.d.ts.map +1 -0
- package/dist/catalog-projection.d.ts +91 -0
- package/dist/catalog-projection.d.ts.map +1 -0
- package/dist/cli-asset.d.ts +22 -0
- package/dist/cli-asset.d.ts.map +1 -0
- package/dist/cli-asset.mjs +9697 -0
- package/dist/cli-asset.mjs.map +1 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.mjs +29 -0
- package/dist/config.mjs.map +1 -0
- package/dist/deriveAssetName.d.ts +15 -0
- package/dist/deriveAssetName.d.ts.map +1 -0
- package/dist/errors.d.ts +30 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.mjs +19 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/evidence/material-cook.d.ts +92 -0
- package/dist/evidence/material-cook.d.ts.map +1 -0
- package/dist/evidence/offline-evidence.d.ts +25 -0
- package/dist/evidence/offline-evidence.d.ts.map +1 -0
- package/dist/evidence/source-inventory.d.ts +29 -0
- package/dist/evidence/source-inventory.d.ts.map +1 -0
- package/dist/guid.d.ts +45 -0
- package/dist/guid.d.ts.map +1 -0
- package/dist/guid.mjs +94 -0
- package/dist/guid.mjs.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +8612 -0
- package/dist/index.mjs.map +1 -0
- package/dist/material/artifact-writer.d.ts +14 -0
- package/dist/material/artifact-writer.d.ts.map +1 -0
- package/dist/material-cook.d.ts +2 -0
- package/dist/material-cook.d.ts.map +1 -0
- package/dist/material-cook.mjs +248 -0
- package/dist/material-cook.mjs.map +1 -0
- package/dist/mesh-bin-contract.d.ts +36 -0
- package/dist/mesh-bin-contract.d.ts.map +1 -0
- package/dist/mesh-bin-contract.mjs +96 -0
- package/dist/mesh-bin-contract.mjs.map +1 -0
- package/dist/name.mjs +22 -0
- package/dist/name.mjs.map +1 -0
- package/dist/native-cooker-registry.d.ts +47 -0
- package/dist/native-cooker-registry.d.ts.map +1 -0
- package/dist/native-cooker.d.ts +3 -0
- package/dist/native-cooker.d.ts.map +1 -0
- package/dist/native-cooker.mjs +142 -0
- package/dist/native-cooker.mjs.map +1 -0
- package/dist/package-finalizer.d.ts +121 -0
- package/dist/package-finalizer.d.ts.map +1 -0
- package/dist/producer-contract.d.ts +19 -0
- package/dist/producer-contract.d.ts.map +1 -0
- package/dist/resolve-asset-source.d.ts +4 -0
- package/dist/resolve-asset-source.d.ts.map +1 -0
- package/dist/resolve-asset-source.mjs +109 -0
- package/dist/resolve-asset-source.mjs.map +1 -0
- package/dist/runtime-projection.d.ts +14 -0
- package/dist/runtime-projection.d.ts.map +1 -0
- package/dist/runtime-publication.d.ts +44 -0
- package/dist/runtime-publication.d.ts.map +1 -0
- package/dist/runtime.d.ts +8 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.mjs +7261 -0
- package/dist/runtime.mjs.map +1 -0
- package/dist/scanner.d.ts +113 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.mjs +8819 -0
- package/dist/scanner.mjs.map +1 -0
- package/dist/schema-compiled.d.ts +12 -0
- package/dist/schema-compiled.d.ts.map +1 -0
- package/dist/schema.mjs +7369 -0
- package/dist/schema.mjs.map +1 -0
- package/dist/scriptable-pack-node.d.ts +62 -0
- package/dist/scriptable-pack-node.d.ts.map +1 -0
- package/dist/scriptable-pack-node.mjs +1674 -0
- package/dist/scriptable-pack-node.mjs.map +1 -0
- package/dist/scriptable-pack-worker.d.ts +2 -0
- package/dist/scriptable-pack-worker.d.ts.map +1 -0
- package/dist/scriptable-pack-worker.mjs +224 -0
- package/dist/scriptable-pack-worker.mjs.map +1 -0
- package/dist/scriptable-pack.d.ts +558 -0
- package/dist/scriptable-pack.d.ts.map +1 -0
- package/dist/scriptable-pack.mjs +672 -0
- package/dist/scriptable-pack.mjs.map +1 -0
- package/dist/topology.d.ts +10 -0
- package/dist/topology.d.ts.map +1 -0
- package/package.json +154 -0
- package/schema/meta.schema.json +173 -0
- package/schema/pack.schema.json +232 -0
- package/src/__tests__/ai-recovery.unit.test.ts +114 -0
- package/src/__tests__/artifact-path.test.ts +41 -0
- package/src/__tests__/asset-evidence-error-hints.test.ts +46 -0
- package/src/__tests__/asset-evidence-lifecycle.test.ts +104 -0
- package/src/__tests__/asset-evidence-schema.test.ts +86 -0
- package/src/__tests__/atlas-cli-region-mismatch.test.ts +134 -0
- package/src/__tests__/atlas-error-code-owner.test-d.ts +23 -0
- package/src/__tests__/cli-asset-evidence.integration.test.ts +172 -0
- package/src/__tests__/cli.unit.test.ts +448 -0
- package/src/__tests__/evidence-source-inventory.unit.test.ts +37 -0
- package/src/__tests__/guid-collision.unit.test.ts +112 -0
- package/src/__tests__/guid-validator-surface.unit.test.ts +20 -0
- package/src/__tests__/load-asset-config.test.ts +121 -0
- package/src/__tests__/material-cook-dependencies.unit.test.ts +112 -0
- package/src/__tests__/material-cook-receipt.unit.test.ts +148 -0
- package/src/__tests__/material-cook-schema.unit.test.ts +152 -0
- package/src/__tests__/mesh-bin-consumer-surface.unit.test.ts +22 -0
- package/src/__tests__/mesh-bin-contract.unit.test.ts +32 -0
- package/src/__tests__/meta-schema-optional.test.ts +116 -0
- package/src/__tests__/meta-source-overrides.unit.test.ts +71 -0
- package/src/__tests__/native-cooker-registry.test.ts +64 -0
- package/src/__tests__/offline-evidence-artifact-verification-owner.test-d.ts +64 -0
- package/src/__tests__/offline-evidence.integration.test.ts +106 -0
- package/src/__tests__/owner-chain.integration.test.ts +78 -0
- package/src/__tests__/pack-v2-fixture-migration.test.ts +87 -0
- package/src/__tests__/pack-v2-schema.test.ts +142 -0
- package/src/__tests__/pack.unit.test.ts +2218 -0
- package/src/__tests__/package-finalizer.contract.test.ts +105 -0
- package/src/__tests__/producer-contract-schema.unit.test.ts +92 -0
- package/src/__tests__/resolve-asset-source.test.ts +121 -0
- package/src/__tests__/runtime-projection.unit.test.ts +87 -0
- package/src/__tests__/runtime.browser.test.ts +15 -0
- package/src/__tests__/scanner-inventory.contract.test.ts +167 -0
- package/src/__tests__/scanner-inventory.test.ts +76 -0
- package/src/__tests__/scanner-mount-cycle.test.ts +232 -0
- package/src/__tests__/scriptable-pack-cli.integration.test.ts +47 -0
- package/src/__tests__/scriptable-pack-ddc-key.unit.test.ts +63 -0
- package/src/__tests__/scriptable-pack.test-d.ts +131 -0
- package/src/__tests__/scriptable-pack.unit.test.ts +789 -0
- package/src/__tests__/topology-actions.unit.test.ts +94 -0
- package/src/__tests__/topology.unit.test.ts +85 -0
- package/src/artifact-path.ts +69 -0
- package/src/atlas/run-atlas.ts +358 -0
- package/src/atlas/shelf-pack.ts +172 -0
- package/src/atlas/upng-ambient.d.ts +13 -0
- package/src/bridge.ts +2 -0
- package/src/build.ts +139 -0
- package/src/builtin.ts +117 -0
- package/src/catalog-builder.ts +313 -0
- package/src/catalog-delta.ts +189 -0
- package/src/catalog-projection.ts +344 -0
- package/src/cli-asset.ts +586 -0
- package/src/config.ts +36 -0
- package/src/deriveAssetName.ts +42 -0
- package/src/errors.ts +47 -0
- package/src/evidence/material-cook.ts +411 -0
- package/src/evidence/offline-evidence.ts +90 -0
- package/src/evidence/source-inventory.ts +193 -0
- package/src/guid.ts +99 -0
- package/src/index.ts +108 -0
- package/src/material/artifact-writer.ts +26 -0
- package/src/material-cook.ts +15 -0
- package/src/mesh-bin-contract.ts +150 -0
- package/src/native-cooker-registry.ts +235 -0
- package/src/native-cooker.ts +2 -0
- package/src/package-finalizer.ts +394 -0
- package/src/producer-contract.ts +277 -0
- package/src/resolve-asset-source.ts +88 -0
- package/src/runtime-projection.ts +100 -0
- package/src/runtime-publication.ts +190 -0
- package/src/runtime.ts +23 -0
- package/src/scanner.ts +715 -0
- package/src/schema/material-cook.schema.json +119 -0
- package/src/schema-compiled.ts +238 -0
- package/src/scriptable-pack-node.ts +1394 -0
- package/src/scriptable-pack-worker.ts +308 -0
- package/src/scriptable-pack.ts +1006 -0
- package/src/topology.ts +157 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
finalizePackageProduct,
|
|
4
|
+
type PackageProduct,
|
|
5
|
+
packageTransportRevision,
|
|
6
|
+
} from '../package-finalizer.js';
|
|
7
|
+
|
|
8
|
+
const GUID = '019e3969-1d48-7c3b-ac24-6d68f457065f';
|
|
9
|
+
|
|
10
|
+
function product(overrides: Partial<PackageProduct> = {}): PackageProduct {
|
|
11
|
+
return {
|
|
12
|
+
assets: [
|
|
13
|
+
{
|
|
14
|
+
guid: GUID,
|
|
15
|
+
kind: 'texture',
|
|
16
|
+
payload: { kind: 'texture', width: 1, height: 1 },
|
|
17
|
+
refs: [],
|
|
18
|
+
artifacts: {
|
|
19
|
+
source: { mediaType: 'image/png', bytes: new Uint8Array([1, 2, 3]) },
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
receipts: [
|
|
24
|
+
{
|
|
25
|
+
guid: GUID,
|
|
26
|
+
origin: 'sourceMeta',
|
|
27
|
+
status: 'succeeded',
|
|
28
|
+
inputFingerprint: 'sha256:source',
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
diagnostics: [],
|
|
32
|
+
sourceRevision: 'sha256:source',
|
|
33
|
+
sourceKey: 'texture/source',
|
|
34
|
+
...overrides,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('engine-pack terminal product and finalizer contract', () => {
|
|
39
|
+
it('finalizes one complete product with source identity and receipts', async () => {
|
|
40
|
+
const result = await finalizePackageProduct(product(), {
|
|
41
|
+
base: '/assets',
|
|
42
|
+
packagePath: 'packages/texture.pack.json',
|
|
43
|
+
artifactPath: (guid, key) => `artifacts/${guid}/${key}.bin`,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
expect(result.ok).toBe(true);
|
|
47
|
+
if (!result.ok) return;
|
|
48
|
+
expect(result.value.pack.assets).toHaveLength(1);
|
|
49
|
+
expect(result.value.digest).toMatch(/^sha256:/);
|
|
50
|
+
expect(result.value.receipts).toHaveLength(1);
|
|
51
|
+
expect(result.value.sourceRevision).toBe('sha256:source');
|
|
52
|
+
expect(result.value.sourceKey).toBe('texture/source');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('fails closed on duplicate GUIDs and incomplete receipt evidence', async () => {
|
|
56
|
+
const firstAsset = product().assets[0];
|
|
57
|
+
if (firstAsset === undefined) throw new Error('test product asset is missing');
|
|
58
|
+
const duplicate = product({ assets: [firstAsset, firstAsset] });
|
|
59
|
+
const duplicateResult = await finalizePackageProduct(duplicate, {
|
|
60
|
+
base: '/',
|
|
61
|
+
packagePath: 'packages/duplicate.pack.json',
|
|
62
|
+
artifactPath: (guid, key) => `${guid}/${key}.bin`,
|
|
63
|
+
});
|
|
64
|
+
expect(duplicateResult.ok).toBe(false);
|
|
65
|
+
|
|
66
|
+
const missingReceipt = product({ receipts: [] });
|
|
67
|
+
const missingReceiptResult = await finalizePackageProduct(missingReceipt, {
|
|
68
|
+
base: '/',
|
|
69
|
+
packagePath: 'packages/missing-receipt.pack.json',
|
|
70
|
+
artifactPath: (guid, key) => `${guid}/${key}.bin`,
|
|
71
|
+
});
|
|
72
|
+
expect(missingReceiptResult.ok).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('hashes and transports large artifact bodies without JSON-expanding the bytes', async () => {
|
|
76
|
+
const body = new Uint8Array(4 * 1024 * 1024);
|
|
77
|
+
const artifact = { mediaType: 'application/octet-stream', bytes: body };
|
|
78
|
+
const asset = product().assets[0];
|
|
79
|
+
if (asset === undefined) throw new Error('test product asset is missing');
|
|
80
|
+
const large = product({
|
|
81
|
+
assets: [{ ...asset, artifacts: { body: artifact } }],
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const revision = packageTransportRevision(large);
|
|
85
|
+
expect(revision).toMatch(/^[0-9a-f]{64}$/);
|
|
86
|
+
|
|
87
|
+
const writes = new Map<string, Uint8Array>();
|
|
88
|
+
const result = await finalizePackageProduct(large, {
|
|
89
|
+
base: '/',
|
|
90
|
+
packagePath: 'large.pack.json',
|
|
91
|
+
artifactPath: (guid, key) => `${guid}/${key}.bin`,
|
|
92
|
+
sink: (path, bytes) => {
|
|
93
|
+
writes.set(path, bytes);
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
expect(result.ok).toBe(true);
|
|
97
|
+
const packageBytes = writes.get('large.pack.json');
|
|
98
|
+
expect(packageBytes).toBeDefined();
|
|
99
|
+
const published = JSON.parse(new TextDecoder().decode(packageBytes)) as {
|
|
100
|
+
assets: readonly [{ artifacts: { body: { byteLength: number } } }];
|
|
101
|
+
};
|
|
102
|
+
expect(published.assets[0]?.artifacts.body.byteLength).toBe(body.byteLength);
|
|
103
|
+
expect(writes.get(`${GUID}/body.bin`)).toBe(body);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { validateProducerContract, validateProducerOutputs } from '../producer-contract.js';
|
|
3
|
+
import { validateMeta, validatePack } from '../schema-compiled.js';
|
|
4
|
+
|
|
5
|
+
const GUID = '01890000-0000-7000-8000-aaaaaaaaaaaa';
|
|
6
|
+
|
|
7
|
+
function producerFields(): Record<string, unknown> {
|
|
8
|
+
return {
|
|
9
|
+
packageId: 'pkg/fixture',
|
|
10
|
+
provenance: { provider: 'fixture-provider', version: '1.0.0' },
|
|
11
|
+
revision: { digest: 'sha256:fixture', observedAt: 123, rootId: 'root-a' },
|
|
12
|
+
diagnostics: [
|
|
13
|
+
{
|
|
14
|
+
code: 'fixture-warning',
|
|
15
|
+
severity: 'warning',
|
|
16
|
+
subject: { type: 'package', id: 'pkg/fixture' },
|
|
17
|
+
expected: 'one stable source key',
|
|
18
|
+
hint: 'declare sourceKey on every imported output',
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe('producer contract schema and semantic validation', () => {
|
|
25
|
+
it('accepts open provider and asset kinds with producer facts', () => {
|
|
26
|
+
const meta = {
|
|
27
|
+
schemaVersion: 1,
|
|
28
|
+
kind: 'external-asset-package',
|
|
29
|
+
importer: 'host-provider',
|
|
30
|
+
...producerFields(),
|
|
31
|
+
importSettings: {},
|
|
32
|
+
subAssets: [{ guid: GUID, sourceIndex: 0, sourceKey: 'blob/main', kind: 'host/blob' }],
|
|
33
|
+
};
|
|
34
|
+
expect(validateMeta(meta)).toBe(true);
|
|
35
|
+
expect(validateProducerContract(meta).ok).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('keeps sourceIndex valid without using it to invent sourceKey', () => {
|
|
39
|
+
const declaration = { guid: GUID, sourceIndex: 0, kind: 'host/blob' };
|
|
40
|
+
const result = validateProducerContract(declaration);
|
|
41
|
+
expect(result).toEqual({
|
|
42
|
+
ok: false,
|
|
43
|
+
error: expect.objectContaining({ code: 'missing-source-key' }),
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('accepts structured fields on internal pack assets', () => {
|
|
48
|
+
const pack = {
|
|
49
|
+
schemaVersion: '1.0.0',
|
|
50
|
+
kind: 'internal-text-package',
|
|
51
|
+
...producerFields(),
|
|
52
|
+
assets: [
|
|
53
|
+
{
|
|
54
|
+
guid: GUID,
|
|
55
|
+
kind: 'host/blob',
|
|
56
|
+
execution: 'direct',
|
|
57
|
+
sourceKey: 'blob/main',
|
|
58
|
+
sourceIndex: 0,
|
|
59
|
+
payload: {},
|
|
60
|
+
refs: [],
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
expect(validatePack(pack)).toBe(true);
|
|
65
|
+
expect(validateProducerContract(pack.assets[0]).ok).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('rejects an authoring Pack asset without explicit execution', () => {
|
|
69
|
+
const pack = {
|
|
70
|
+
schemaVersion: '2.0.0',
|
|
71
|
+
kind: 'internal-text-package',
|
|
72
|
+
packageId: 'pkg/authoring',
|
|
73
|
+
assets: [{ guid: GUID, kind: 'host/blob', payload: {}, refs: [], artifacts: {} }],
|
|
74
|
+
};
|
|
75
|
+
expect(validatePack(pack)).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('rejects duplicate and sourceIndex-only imported output declarations', () => {
|
|
79
|
+
const outputs = [
|
|
80
|
+
{ guid: GUID, sourceIndex: 0, sourceKey: 'blob/main', kind: 'host/blob' },
|
|
81
|
+
{ guid: `${GUID.slice(0, -1)}b`, sourceIndex: 1, sourceKey: 'blob/main', kind: 'host/blob' },
|
|
82
|
+
];
|
|
83
|
+
const duplicate = validateProducerOutputs(outputs);
|
|
84
|
+
expect(duplicate.ok).toBe(false);
|
|
85
|
+
if (!duplicate.ok) expect(duplicate.error.code).toBe('duplicate-source-key');
|
|
86
|
+
const sourceIndexOnly = validateProducerOutputs(
|
|
87
|
+
outputs.map(({ sourceKey: _sourceKey, ...output }) => output),
|
|
88
|
+
);
|
|
89
|
+
expect(sourceIndexOnly.ok).toBe(false);
|
|
90
|
+
if (!sourceIndexOnly.ok) expect(sourceIndexOnly.error.code).toBe('source-index-ambiguous');
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// @ts-nocheck — PackErrorDetail is a discriminated union narrowed by runtime .code
|
|
2
|
+
// checks; TS cannot follow the narrowing through PackError.detail.
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import { resolveAssetSource } from '../resolve-asset-source.js';
|
|
6
|
+
|
|
7
|
+
describe('resolveAssetSource — omit derivation (w4)', () => {
|
|
8
|
+
const metaDir = '/project/assets';
|
|
9
|
+
|
|
10
|
+
it('omitted source derives companion file from meta filename (AC-2)', () => {
|
|
11
|
+
const metaPath = resolve(metaDir, 'foo.png.meta.json');
|
|
12
|
+
const result = resolveAssetSource(metaPath, undefined, {});
|
|
13
|
+
expect(result.ok).toBe(true);
|
|
14
|
+
if (result.ok) {
|
|
15
|
+
expect(result.value).toBe(resolve(metaDir, 'foo.png'));
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('renamed audio meta derives correct companion (AC-3)', () => {
|
|
20
|
+
const metaPath = resolve(metaDir, 'bleep.wav.meta.json');
|
|
21
|
+
const result = resolveAssetSource(metaPath, undefined, {});
|
|
22
|
+
expect(result.ok).toBe(true);
|
|
23
|
+
if (result.ok) {
|
|
24
|
+
expect(result.value).toBe(resolve(metaDir, 'bleep.wav'));
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('derivation is a single rule: strip .meta.json suffix only', () => {
|
|
29
|
+
const cases = [
|
|
30
|
+
{ meta: 'model.glb.meta.json', expected: 'model.glb' },
|
|
31
|
+
{ meta: 'sprite.png.meta.json', expected: 'sprite.png' },
|
|
32
|
+
{ meta: 'font.ttf.meta.json', expected: 'font.ttf' },
|
|
33
|
+
{ meta: 'bgm.mp3.meta.json', expected: 'bgm.mp3' },
|
|
34
|
+
];
|
|
35
|
+
for (const { meta, expected } of cases) {
|
|
36
|
+
const metaPath = resolve(metaDir, meta);
|
|
37
|
+
const result = resolveAssetSource(metaPath, undefined, {});
|
|
38
|
+
expect(result.ok).toBe(true);
|
|
39
|
+
if (result.ok) {
|
|
40
|
+
expect(result.value).toBe(resolve(metaDir, expected));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('resolveAssetSource — @name/ path + boundary + legacy (w5)', () => {
|
|
47
|
+
const metaDir = '/project/assets';
|
|
48
|
+
const metaPath = resolve(metaDir, 'foo.png.meta.json');
|
|
49
|
+
|
|
50
|
+
it('resolves @name/rest to absolute path via paths table (AC-5)', () => {
|
|
51
|
+
const paths = { shared: '/lib/assets' };
|
|
52
|
+
const result = resolveAssetSource(metaPath, '@shared/foo.png', paths);
|
|
53
|
+
expect(result.ok).toBe(true);
|
|
54
|
+
if (result.ok) {
|
|
55
|
+
expect(result.value).toBe(resolve('/lib/assets', 'foo.png'));
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('returns pack-unknown-path for undeclared @name (AC-8)', () => {
|
|
60
|
+
const paths = { shared: '/lib/assets' };
|
|
61
|
+
const result = resolveAssetSource(metaPath, '@nope/x.png', paths);
|
|
62
|
+
expect(result.ok).toBe(false);
|
|
63
|
+
if (!result.ok) {
|
|
64
|
+
expect(result.error.code).toBe('pack-unknown-path');
|
|
65
|
+
expect(result.error.detail.pathName).toBe('nope');
|
|
66
|
+
expect(result.error.detail.knownNames).toEqual(['shared']);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('returns pack-malformed-path-ref for bare @ with no name or rest (AC-9)', () => {
|
|
71
|
+
const result = resolveAssetSource(metaPath, '@', { shared: '/lib/assets' });
|
|
72
|
+
expect(result.ok).toBe(false);
|
|
73
|
+
if (!result.ok) {
|
|
74
|
+
expect(result.error.code).toBe('pack-malformed-path-ref');
|
|
75
|
+
expect(result.error.detail.rawSource).toBe('@');
|
|
76
|
+
if (result.error.detail.code === 'pack-malformed-path-ref') {
|
|
77
|
+
expect(result.error.detail.reason).toBe('format');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('returns pack-malformed-path-ref for @onlyname with no /rest (AC-9)', () => {
|
|
83
|
+
const result = resolveAssetSource(metaPath, '@onlyname', { shared: '/lib/assets' });
|
|
84
|
+
expect(result.ok).toBe(false);
|
|
85
|
+
if (!result.ok) {
|
|
86
|
+
expect(result.error.code).toBe('pack-malformed-path-ref');
|
|
87
|
+
expect(result.error.detail.rawSource).toBe('@onlyname');
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('returns pack-malformed-path-ref for ../ escape (AC-11)', () => {
|
|
92
|
+
const paths = { shared: '/data/shared' };
|
|
93
|
+
const result = resolveAssetSource(metaPath, '@shared/../escape.png', paths);
|
|
94
|
+
expect(result.ok).toBe(false);
|
|
95
|
+
if (!result.ok) {
|
|
96
|
+
expect(result.error.code).toBe('pack-malformed-path-ref');
|
|
97
|
+
expect(result.error.detail.rawSource).toBe('@shared/../escape.png');
|
|
98
|
+
if (result.error.detail.code === 'pack-malformed-path-ref') {
|
|
99
|
+
expect(result.error.detail.reason).toBe('escape');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('resolves legacy relative path unchanged', () => {
|
|
105
|
+
const result = resolveAssetSource(metaPath, 'sub/foo.png', {});
|
|
106
|
+
expect(result.ok).toBe(true);
|
|
107
|
+
if (result.ok) {
|
|
108
|
+
expect(result.value).toBe(resolve(metaDir, 'sub/foo.png'));
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('returns pack-unknown-path for @name/ when paths is empty', () => {
|
|
113
|
+
const result = resolveAssetSource(metaPath, '@any/x.png', {});
|
|
114
|
+
expect(result.ok).toBe(false);
|
|
115
|
+
if (!result.ok) {
|
|
116
|
+
expect(result.error.code).toBe('pack-unknown-path');
|
|
117
|
+
expect(result.error.detail.pathName).toBe('any');
|
|
118
|
+
expect(result.error.detail.knownNames).toEqual([]);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { projectRuntimePack } from '../runtime-projection.js';
|
|
4
|
+
|
|
5
|
+
const artifact = {
|
|
6
|
+
path: 'mesh.bin',
|
|
7
|
+
mediaType: 'application/octet-stream',
|
|
8
|
+
contentEncoding: 'identity' as const,
|
|
9
|
+
byteLength: 3,
|
|
10
|
+
integrity: { algorithm: 'sha256' as const, digest: 'sha256:mesh' },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const asset = {
|
|
14
|
+
guid: '019e2cc6-0c86-79da-aa76-b0984c86d45a',
|
|
15
|
+
kind: 'mesh',
|
|
16
|
+
payload: { kind: 'mesh' },
|
|
17
|
+
refs: [],
|
|
18
|
+
artifacts: { mesh: artifact },
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
describe('runtime Pack projection', () => {
|
|
22
|
+
it('preserves the material payload and artifact tuple without a runtime adapter', () => {
|
|
23
|
+
const material = {
|
|
24
|
+
guid: '019e2cc6-0c86-79da-aa76-b0984c86d45b',
|
|
25
|
+
kind: 'material',
|
|
26
|
+
payload: {
|
|
27
|
+
kind: 'material',
|
|
28
|
+
passes: [{ name: 'forward', program: { module: 'forgeax::standard' } }],
|
|
29
|
+
values: { baseColor: [1, 1, 1, 1] },
|
|
30
|
+
},
|
|
31
|
+
refs: [],
|
|
32
|
+
artifacts: {},
|
|
33
|
+
};
|
|
34
|
+
const result = projectRuntimePack({
|
|
35
|
+
scopeId: 'material-session',
|
|
36
|
+
generation: 1,
|
|
37
|
+
digest: 'sha256:material-pack',
|
|
38
|
+
outputSetDigest: 'sha256:material-outputs',
|
|
39
|
+
assets: [material],
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(result).toMatchObject({ ok: true, value: { assets: [material] } });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('publishes one atomic tuple and one Pack v2 envelope', () => {
|
|
46
|
+
const result = projectRuntimePack({
|
|
47
|
+
scopeId: 'play-session',
|
|
48
|
+
generation: 7,
|
|
49
|
+
digest: 'sha256:pack',
|
|
50
|
+
outputSetDigest: 'sha256:outputs',
|
|
51
|
+
assets: [asset],
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(result).toEqual({
|
|
55
|
+
ok: true,
|
|
56
|
+
value: {
|
|
57
|
+
schemaVersion: '2.0.0',
|
|
58
|
+
kind: 'internal-text-package',
|
|
59
|
+
scopeId: 'play-session',
|
|
60
|
+
generation: 7,
|
|
61
|
+
digest: 'sha256:pack',
|
|
62
|
+
outputSetDigest: 'sha256:outputs',
|
|
63
|
+
assets: [asset],
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('rejects legacy and duplicate-row input before publication', () => {
|
|
69
|
+
const legacy = projectRuntimePack({ schemaVersion: '1.0.0', assets: [asset] });
|
|
70
|
+
expect(legacy).toMatchObject({
|
|
71
|
+
ok: false,
|
|
72
|
+
error: { code: 'pack-v2-envelope-invalid', detail: { expected: 'runtime Pack v2' } },
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const duplicate = projectRuntimePack({
|
|
76
|
+
scopeId: 'play-session',
|
|
77
|
+
generation: 7,
|
|
78
|
+
digest: 'sha256:pack',
|
|
79
|
+
outputSetDigest: 'sha256:outputs',
|
|
80
|
+
assets: [asset, asset],
|
|
81
|
+
});
|
|
82
|
+
expect(duplicate).toMatchObject({
|
|
83
|
+
ok: false,
|
|
84
|
+
error: { code: 'pack-v2-duplicate-guid', detail: { guid: asset.guid } },
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { parsePackV2 } from '@forgeax/engine-pack/runtime';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
describe('Pack runtime entry', () => {
|
|
5
|
+
it('parses a Pack v2 envelope in a browser without the Node scanner barrel', () => {
|
|
6
|
+
const result = parsePackV2({
|
|
7
|
+
schemaVersion: '2.0.0',
|
|
8
|
+
kind: 'internal-text-package',
|
|
9
|
+
packageId: 'browser-runtime',
|
|
10
|
+
assets: [],
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
expect(result.ok).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { AssetGuid } from '../guid.js';
|
|
6
|
+
import { scan, scanInventory } from '../scanner.js';
|
|
7
|
+
|
|
8
|
+
const GUID = '019e3969-1d48-7c3b-ac24-6d68f457065f';
|
|
9
|
+
|
|
10
|
+
function guid(value: string): Uint8Array {
|
|
11
|
+
const parsed = AssetGuid.parse(value);
|
|
12
|
+
if (!parsed.ok) throw parsed.error;
|
|
13
|
+
return parsed.value;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('engine-pack scanner inventory contract', () => {
|
|
17
|
+
const roots: string[] = [];
|
|
18
|
+
|
|
19
|
+
afterEach(async () => {
|
|
20
|
+
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('returns one inventory declaration with source identity before finalization', async () => {
|
|
24
|
+
const root = await mkdtemp(join(tmpdir(), 'forgeax-pack-inventory-'));
|
|
25
|
+
roots.push(root);
|
|
26
|
+
await writeFile(
|
|
27
|
+
join(root, 'hero.pack.json'),
|
|
28
|
+
JSON.stringify({
|
|
29
|
+
schemaVersion: '2.0.0',
|
|
30
|
+
kind: 'internal-text-package',
|
|
31
|
+
assets: [
|
|
32
|
+
{
|
|
33
|
+
guid: GUID,
|
|
34
|
+
kind: 'texture',
|
|
35
|
+
payload: { kind: 'texture', width: 1, height: 1 },
|
|
36
|
+
refs: [],
|
|
37
|
+
sourceKey: 'hero/albedo',
|
|
38
|
+
sourceIndex: 0,
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const result = await scanInventory([root]);
|
|
45
|
+
|
|
46
|
+
expect(result.ok).toBe(true);
|
|
47
|
+
if (!result.ok) return;
|
|
48
|
+
expect(result.value.inventory).toHaveLength(1);
|
|
49
|
+
expect(result.value.inventory[0]).toMatchObject({
|
|
50
|
+
guid: GUID,
|
|
51
|
+
sourceKey: 'hero/albedo',
|
|
52
|
+
sourceIndex: 0,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('returns the parsed Meta, Pack, and ScriptablePack declarations from one scan pass', async () => {
|
|
57
|
+
const root = await mkdtemp(join(tmpdir(), 'forgeax-pack-declarations-'));
|
|
58
|
+
roots.push(root);
|
|
59
|
+
|
|
60
|
+
const metaPath = join(root, 'hero.meta.json');
|
|
61
|
+
const packPath = join(root, 'environment.pack.json');
|
|
62
|
+
const scriptablePath = join(root, 'effects.pack.ts');
|
|
63
|
+
const meta = {
|
|
64
|
+
schemaVersion: '1.0.0',
|
|
65
|
+
kind: 'external-asset-package',
|
|
66
|
+
importer: 'image',
|
|
67
|
+
source: 'hero.png',
|
|
68
|
+
importSettings: {},
|
|
69
|
+
subAssets: [
|
|
70
|
+
{ guid: '019e3969-1d48-7c3b-ac24-6d68f4570660', sourceIndex: 0, kind: 'texture' },
|
|
71
|
+
],
|
|
72
|
+
} as const;
|
|
73
|
+
const pack = {
|
|
74
|
+
schemaVersion: '2.0.0',
|
|
75
|
+
kind: 'internal-text-package',
|
|
76
|
+
assets: [
|
|
77
|
+
{
|
|
78
|
+
guid: '019e3969-1d48-7c3b-ac24-6d68f4570661',
|
|
79
|
+
kind: 'texture',
|
|
80
|
+
payload: { kind: 'texture', width: 1, height: 1 },
|
|
81
|
+
refs: [],
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
} as const;
|
|
85
|
+
await writeFile(join(root, 'hero.png'), 'source');
|
|
86
|
+
await writeFile(metaPath, JSON.stringify(meta));
|
|
87
|
+
await writeFile(packPath, JSON.stringify(pack));
|
|
88
|
+
await writeFile(scriptablePath, '// scanner fixture');
|
|
89
|
+
|
|
90
|
+
const scriptablePackageId = guid('019e3969-1d48-7c3b-ac24-6d68f4570662');
|
|
91
|
+
const scriptableAssetGuid = guid('019e3969-1d48-7c3b-ac24-6d68f4570663');
|
|
92
|
+
const result = await scanInventory([root], {
|
|
93
|
+
scriptablePack: {
|
|
94
|
+
executor: {
|
|
95
|
+
load: async () => ({
|
|
96
|
+
default: {
|
|
97
|
+
schemaVersion: '1.0.0',
|
|
98
|
+
packageId: scriptablePackageId,
|
|
99
|
+
assets: { effect: { guid: scriptableAssetGuid, kind: 'scene' } },
|
|
100
|
+
externalAssets: {},
|
|
101
|
+
build: () => ({ ok: true, value: {} }),
|
|
102
|
+
},
|
|
103
|
+
}),
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
expect(result.ok).toBe(true);
|
|
109
|
+
if (!result.ok) return;
|
|
110
|
+
expect(result.value.declarations.size).toBe(3);
|
|
111
|
+
expect(result.value.declarations.get(metaPath)).toMatchObject({
|
|
112
|
+
format: 'meta.json',
|
|
113
|
+
sourcePath: metaPath,
|
|
114
|
+
value: meta,
|
|
115
|
+
});
|
|
116
|
+
expect(result.value.declarations.get(packPath)).toMatchObject({
|
|
117
|
+
format: 'pack.json',
|
|
118
|
+
sourcePath: packPath,
|
|
119
|
+
value: pack,
|
|
120
|
+
});
|
|
121
|
+
const scriptableDeclaration = result.value.declarations.get(scriptablePath);
|
|
122
|
+
expect(scriptableDeclaration).toMatchObject({
|
|
123
|
+
format: 'pack.ts',
|
|
124
|
+
sourcePath: scriptablePath,
|
|
125
|
+
value: {
|
|
126
|
+
importer: 'pack-ts',
|
|
127
|
+
packageId: '019e3969-1d48-7c3b-ac24-6d68f4570662',
|
|
128
|
+
subAssets: [{ sourceKey: 'effect', kind: 'scene' }],
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
expect(
|
|
132
|
+
scriptableDeclaration?.format === 'pack.ts' ? scriptableDeclaration.meta : undefined,
|
|
133
|
+
).toBe(scriptableDeclaration?.format === 'pack.ts' ? scriptableDeclaration.value : undefined);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('releases the ScriptablePack loader for the path-only scan contract', async () => {
|
|
137
|
+
const root = await mkdtemp(join(tmpdir(), 'forgeax-pack-scan-release-'));
|
|
138
|
+
roots.push(root);
|
|
139
|
+
const scriptablePath = join(root, 'effects.pack.ts');
|
|
140
|
+
await writeFile(scriptablePath, '// scanner fixture');
|
|
141
|
+
|
|
142
|
+
const packageId = guid('019e3969-1d48-7c3b-ac24-6d68f4570664');
|
|
143
|
+
const assetGuid = guid('019e3969-1d48-7c3b-ac24-6d68f4570665');
|
|
144
|
+
let disposals = 0;
|
|
145
|
+
const result = await scan([root], {
|
|
146
|
+
scriptablePack: {
|
|
147
|
+
executor: {
|
|
148
|
+
load: async () => ({
|
|
149
|
+
default: {
|
|
150
|
+
schemaVersion: '1.0.0',
|
|
151
|
+
packageId,
|
|
152
|
+
assets: { effect: { guid: assetGuid, kind: 'scene' } },
|
|
153
|
+
externalAssets: {},
|
|
154
|
+
build: () => ({ ok: true, value: {} }),
|
|
155
|
+
},
|
|
156
|
+
}),
|
|
157
|
+
dispose: async () => {
|
|
158
|
+
disposals += 1;
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
expect(result.ok).toBe(true);
|
|
165
|
+
expect(disposals).toBe(1);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { mkdtemp, realpath, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
5
|
+
import { scanInventory } from '../scanner.js';
|
|
6
|
+
|
|
7
|
+
describe('AssetInventory', () => {
|
|
8
|
+
const guid = '00000000-0000-0000-0000-000000000002';
|
|
9
|
+
let root: string | undefined;
|
|
10
|
+
let sourcePath: string | undefined;
|
|
11
|
+
let result: Awaited<ReturnType<typeof scanInventory>> | undefined;
|
|
12
|
+
|
|
13
|
+
beforeAll(async () => {
|
|
14
|
+
root = await mkdtemp(join(tmpdir(), 'forgeax-scanner-inventory-'));
|
|
15
|
+
sourcePath = join(root, 'generated.pack.ts');
|
|
16
|
+
await writeFile(
|
|
17
|
+
sourcePath,
|
|
18
|
+
[
|
|
19
|
+
'const packageId = new Uint8Array(16);',
|
|
20
|
+
'packageId[15] = 1;',
|
|
21
|
+
'const meshGuid = new Uint8Array(16);',
|
|
22
|
+
'meshGuid[15] = 2;',
|
|
23
|
+
'export default {',
|
|
24
|
+
"schemaVersion: '1.0.0', packageId,",
|
|
25
|
+
"assets: { mesh: { guid: meshGuid, kind: 'mesh' } }, externalAssets: {},",
|
|
26
|
+
"build: () => ({ ok: true, value: { mesh: { kind: 'mesh' } } }),",
|
|
27
|
+
'};',
|
|
28
|
+
].join('\n'),
|
|
29
|
+
);
|
|
30
|
+
result = await scanInventory([root]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterAll(async () => {
|
|
34
|
+
if (root !== undefined) await rm(root, { recursive: true, force: true });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const getFixture = () => {
|
|
38
|
+
if (result === undefined || sourcePath === undefined) {
|
|
39
|
+
throw new Error('scanner fixture was not initialized');
|
|
40
|
+
}
|
|
41
|
+
if (!result.ok) throw new Error('scanner fixture failed');
|
|
42
|
+
return { inventory: result.value, sourcePath };
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
it('projects the ScriptablePack declaration and owner outputs', () => {
|
|
46
|
+
const { inventory, sourcePath } = getFixture();
|
|
47
|
+
const declaration = inventory.declarations.get(sourcePath);
|
|
48
|
+
expect(declaration).toMatchObject({
|
|
49
|
+
format: 'pack.ts',
|
|
50
|
+
sourcePath,
|
|
51
|
+
meta: {
|
|
52
|
+
importer: 'pack-ts',
|
|
53
|
+
subAssets: [{ guid, sourceKey: 'mesh', kind: 'mesh' }],
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('projects source closure evidence', async () => {
|
|
59
|
+
const { inventory, sourcePath } = getFixture();
|
|
60
|
+
const declaration = inventory.declarations.get(sourcePath);
|
|
61
|
+
expect(
|
|
62
|
+
declaration?.format === 'pack.ts'
|
|
63
|
+
? declaration.sourceClosure.map((entry) => entry.path)
|
|
64
|
+
: undefined,
|
|
65
|
+
).toEqual([await realpath(sourcePath)]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('indexes the ScriptablePack declaration without a second metadata registry', () => {
|
|
69
|
+
const { inventory, sourcePath } = getFixture();
|
|
70
|
+
const declaration = inventory.declarations.get(sourcePath);
|
|
71
|
+
expect(inventory.inventory).toEqual([]);
|
|
72
|
+
expect(declaration?.format === 'pack.ts' ? declaration.meta.subAssets : undefined).toEqual([
|
|
73
|
+
{ guid, sourceIndex: 0, sourceKey: 'mesh', kind: 'mesh' },
|
|
74
|
+
]);
|
|
75
|
+
});
|
|
76
|
+
});
|