@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.
Files changed (139) hide show
  1. package/README.md +167 -48
  2. package/dist/__tests__/inventory-schema.test.d.ts +2 -0
  3. package/dist/__tests__/inventory-schema.test.d.ts.map +1 -0
  4. package/dist/__tests__/material-cook-receipt-layer-plan.unit.test.d.ts +2 -0
  5. package/dist/__tests__/material-cook-receipt-layer-plan.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/pack-authoring-gateway.unit.test.d.ts +2 -0
  7. package/dist/__tests__/pack-authoring-gateway.unit.test.d.ts.map +1 -0
  8. package/dist/__tests__/pack-authoring.unit.test.d.ts +2 -0
  9. package/dist/__tests__/pack-authoring.unit.test.d.ts.map +1 -0
  10. package/dist/__tests__/pack-scanner.unit.test.d.ts +2 -0
  11. package/dist/__tests__/pack-scanner.unit.test.d.ts.map +1 -0
  12. package/dist/__tests__/scanner-blacklist.test.d.ts +2 -0
  13. package/dist/__tests__/scanner-blacklist.test.d.ts.map +1 -0
  14. package/dist/build.d.ts +9 -4
  15. package/dist/build.d.ts.map +1 -1
  16. package/dist/build.mjs +3449 -897
  17. package/dist/build.mjs.map +1 -1
  18. package/dist/builtin.mjs +37 -4
  19. package/dist/builtin.mjs.map +1 -1
  20. package/dist/catalog-builder.d.ts.map +1 -1
  21. package/dist/cli-asset.d.ts.map +1 -1
  22. package/dist/cli-asset.mjs +1361 -845
  23. package/dist/cli-asset.mjs.map +1 -1
  24. package/dist/deriveAssetName.d.ts +11 -7
  25. package/dist/deriveAssetName.d.ts.map +1 -1
  26. package/dist/evidence/material-cook.d.ts +11 -0
  27. package/dist/evidence/material-cook.d.ts.map +1 -1
  28. package/dist/evidence/source-inventory.d.ts.map +1 -1
  29. package/dist/guid.d.ts +15 -0
  30. package/dist/guid.d.ts.map +1 -1
  31. package/dist/guid.mjs +53 -5
  32. package/dist/guid.mjs.map +1 -1
  33. package/dist/index.d.ts +4 -2
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.mjs +1297 -562
  36. package/dist/index.mjs.map +1 -1
  37. package/dist/inventory/binding.d.ts +6 -0
  38. package/dist/inventory/binding.d.ts.map +1 -0
  39. package/dist/inventory/declaration.d.ts +24 -0
  40. package/dist/inventory/declaration.d.ts.map +1 -0
  41. package/dist/inventory/index.d.ts +3 -0
  42. package/dist/inventory/index.d.ts.map +1 -0
  43. package/dist/inventory/sync.d.ts +5 -0
  44. package/dist/inventory/sync.d.ts.map +1 -0
  45. package/dist/material-cook.d.ts +1 -1
  46. package/dist/material-cook.d.ts.map +1 -1
  47. package/dist/material-cook.mjs +50 -3
  48. package/dist/material-cook.mjs.map +1 -1
  49. package/dist/name.mjs +4 -7
  50. package/dist/name.mjs.map +1 -1
  51. package/dist/pack-authoring-node.d.ts +26 -0
  52. package/dist/pack-authoring-node.d.ts.map +1 -0
  53. package/dist/pack-authoring-node.mjs +11089 -0
  54. package/dist/pack-authoring-node.mjs.map +1 -0
  55. package/dist/pack-authoring.d.ts +230 -0
  56. package/dist/pack-authoring.d.ts.map +1 -0
  57. package/dist/pack-authoring.mjs +1137 -0
  58. package/dist/pack-authoring.mjs.map +1 -0
  59. package/dist/package-finalizer.d.ts +2 -20
  60. package/dist/package-finalizer.d.ts.map +1 -1
  61. package/dist/resolve-asset-source.d.ts +1 -3
  62. package/dist/resolve-asset-source.d.ts.map +1 -1
  63. package/dist/resolve-asset-source.mjs +6 -81
  64. package/dist/resolve-asset-source.mjs.map +1 -1
  65. package/dist/runtime.mjs +77 -7
  66. package/dist/runtime.mjs.map +1 -1
  67. package/dist/scanner.d.ts +18 -7
  68. package/dist/scanner.d.ts.map +1 -1
  69. package/dist/scanner.mjs +1164 -687
  70. package/dist/scanner.mjs.map +1 -1
  71. package/dist/schema-compiled.d.ts.map +1 -1
  72. package/dist/schema.mjs +79 -7
  73. package/dist/schema.mjs.map +1 -1
  74. package/dist/scriptable-pack-node.d.ts +7 -31
  75. package/dist/scriptable-pack-node.d.ts.map +1 -1
  76. package/dist/scriptable-pack-node.mjs +572 -1019
  77. package/dist/scriptable-pack-node.mjs.map +1 -1
  78. package/dist/scriptable-pack-worker.mjs +29 -11
  79. package/dist/scriptable-pack-worker.mjs.map +1 -1
  80. package/dist/scriptable-pack.d.ts +20 -532
  81. package/dist/scriptable-pack.d.ts.map +1 -1
  82. package/dist/scriptable-pack.mjs +1038 -527
  83. package/dist/scriptable-pack.mjs.map +1 -1
  84. package/package.json +2 -7
  85. package/schema/meta.schema.json +1 -1
  86. package/schema/pack.schema.json +76 -6
  87. package/src/__tests__/guid-collision.unit.test.ts +18 -9
  88. package/src/__tests__/inventory-schema.test.ts +109 -0
  89. package/src/__tests__/material-cook-receipt-layer-plan.unit.test.ts +16 -0
  90. package/src/__tests__/material-cook-schema.unit.test.ts +62 -0
  91. package/src/__tests__/pack-authoring-gateway.unit.test.ts +249 -0
  92. package/src/__tests__/pack-authoring.unit.test.ts +266 -0
  93. package/src/__tests__/pack-scanner.unit.test.ts +123 -0
  94. package/src/__tests__/pack.unit.test.ts +1 -238
  95. package/src/__tests__/package-finalizer.contract.test.ts +55 -0
  96. package/src/__tests__/resolve-asset-source.test.ts +15 -98
  97. package/src/__tests__/scanner-blacklist.test.ts +55 -0
  98. package/src/__tests__/scanner-inventory.contract.test.ts +11 -14
  99. package/src/__tests__/scanner-inventory.test.ts +23 -10
  100. package/src/__tests__/scriptable-pack-cli.integration.test.ts +4 -9
  101. package/src/__tests__/topology.unit.test.ts +23 -0
  102. package/src/build.ts +28 -18
  103. package/src/catalog-builder.ts +60 -33
  104. package/src/cli-asset.ts +68 -83
  105. package/src/deriveAssetName.ts +14 -13
  106. package/src/evidence/material-cook.ts +79 -3
  107. package/src/evidence/source-inventory.ts +34 -21
  108. package/src/guid.ts +65 -4
  109. package/src/index.ts +15 -11
  110. package/src/inventory/binding.ts +25 -0
  111. package/src/inventory/declaration.ts +168 -0
  112. package/src/inventory/index.ts +18 -0
  113. package/src/inventory/sync.ts +22 -0
  114. package/src/material-cook.ts +1 -0
  115. package/src/pack-authoring-node.ts +1966 -0
  116. package/src/pack-authoring.ts +1478 -0
  117. package/src/package-finalizer.ts +2 -42
  118. package/src/resolve-asset-source.ts +4 -76
  119. package/src/scanner.ts +236 -56
  120. package/src/schema/material-cook.schema.json +4 -1
  121. package/src/schema-compiled.ts +2 -0
  122. package/src/scriptable-pack-node.ts +108 -719
  123. package/src/scriptable-pack-worker.ts +48 -16
  124. package/src/scriptable-pack.ts +28 -939
  125. package/dist/.tsbuildinfo +0 -1
  126. package/dist/__tests__/load-asset-config.test.d.ts +0 -2
  127. package/dist/__tests__/load-asset-config.test.d.ts.map +0 -1
  128. package/dist/__tests__/scriptable-pack.test-d.d.ts +0 -2
  129. package/dist/__tests__/scriptable-pack.test-d.d.ts.map +0 -1
  130. package/dist/__tests__/scriptable-pack.unit.test.d.ts +0 -2
  131. package/dist/__tests__/scriptable-pack.unit.test.d.ts.map +0 -1
  132. package/dist/config.d.ts +0 -6
  133. package/dist/config.d.ts.map +0 -1
  134. package/dist/config.mjs +0 -29
  135. package/dist/config.mjs.map +0 -1
  136. package/src/__tests__/load-asset-config.test.ts +0 -121
  137. package/src/__tests__/scriptable-pack.test-d.ts +0 -131
  138. package/src/__tests__/scriptable-pack.unit.test.ts +0 -789
  139. package/src/config.ts +0 -36
@@ -102,4 +102,59 @@ describe('engine-pack terminal product and finalizer contract', () => {
102
102
  expect(published.assets[0]?.artifacts.body.byteLength).toBe(body.byteLength);
103
103
  expect(writes.get(`${GUID}/body.bin`)).toBe(body);
104
104
  });
105
+
106
+ it('publishes a mesh LOD relation and keeps the lower mesh in the closure', async () => {
107
+ const lodGuid = '019e3969-1d48-7c3b-ac24-6d68f4570660';
108
+ const root = product().assets[0];
109
+ if (root === undefined) throw new Error('test product asset is missing');
110
+ const result = await finalizePackageProduct(
111
+ product({
112
+ assets: [
113
+ {
114
+ ...root,
115
+ kind: 'mesh',
116
+ payload: {
117
+ kind: 'mesh',
118
+ lods: [{ mesh: lodGuid, screenCoverage: 0.5 }],
119
+ },
120
+ refs: [lodGuid],
121
+ },
122
+ {
123
+ ...root,
124
+ guid: lodGuid,
125
+ kind: 'mesh',
126
+ payload: { kind: 'mesh' },
127
+ refs: [],
128
+ },
129
+ ],
130
+ receipts: [
131
+ {
132
+ guid: GUID,
133
+ origin: 'sourceMeta',
134
+ status: 'succeeded',
135
+ inputFingerprint: 'sha256:source',
136
+ },
137
+ {
138
+ guid: lodGuid,
139
+ origin: 'sourceMeta',
140
+ status: 'succeeded',
141
+ inputFingerprint: 'sha256:source',
142
+ },
143
+ ],
144
+ }),
145
+ {
146
+ base: '/',
147
+ packagePath: 'mesh.pack.json',
148
+ artifactPath: (guid, key) => `${guid}/${key}.bin`,
149
+ },
150
+ );
151
+ expect(result.ok).toBe(true);
152
+ if (!result.ok) return;
153
+ const publishedRoot = result.value.pack.assets.find((asset) => asset.guid === GUID);
154
+ expect(publishedRoot?.refs).toEqual([lodGuid]);
155
+ expect(publishedRoot?.payload).toMatchObject({
156
+ lods: [{ mesh: lodGuid, screenCoverage: 0.5 }],
157
+ });
158
+ expect(result.value.pack.assets.map((asset) => asset.guid)).toContain(lodGuid);
159
+ });
105
160
  });
@@ -1,31 +1,16 @@
1
- // @ts-nocheck — PackErrorDetail is a discriminated union narrowed by runtime .code
2
- // checks; TS cannot follow the narrowing through PackError.detail.
3
1
  import { resolve } from 'node:path';
4
2
  import { describe, expect, it } from 'vitest';
5
3
  import { resolveAssetSource } from '../resolve-asset-source.js';
6
4
 
7
- describe('resolveAssetSource — omit derivation (w4)', () => {
5
+ describe('resolveAssetSource — companion and sidecar-relative sources', () => {
8
6
  const metaDir = '/project/assets';
9
7
 
10
- it('omitted source derives companion file from meta filename (AC-2)', () => {
8
+ it('derives an omitted source from the companion filename', () => {
11
9
  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
- }
10
+ expect(resolveAssetSource(metaPath, undefined)).toBe(resolve(metaDir, 'foo.png'));
26
11
  });
27
12
 
28
- it('derivation is a single rule: strip .meta.json suffix only', () => {
13
+ it('supports every ordinary companion extension without a special table', () => {
29
14
  const cases = [
30
15
  { meta: 'model.glb.meta.json', expected: 'model.glb' },
31
16
  { meta: 'sprite.png.meta.json', expected: 'sprite.png' },
@@ -33,89 +18,21 @@ describe('resolveAssetSource — omit derivation (w4)', () => {
33
18
  { meta: 'bgm.mp3.meta.json', expected: 'bgm.mp3' },
34
19
  ];
35
20
  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']);
21
+ expect(resolveAssetSource(resolve(metaDir, meta), undefined)).toBe(
22
+ resolve(metaDir, expected),
23
+ );
67
24
  }
68
25
  });
69
26
 
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
- }
27
+ it('resolves an explicit source relative to its sidecar', () => {
28
+ const metaPath = resolve(metaDir, 'foo.png.meta.json');
29
+ expect(resolveAssetSource(metaPath, 'sub/foo.png')).toBe(resolve(metaDir, 'sub/foo.png'));
110
30
  });
111
31
 
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
- }
32
+ it('keeps source resolution independent of package.json and path aliases', () => {
33
+ const metaPath = resolve(metaDir, 'foo.png.meta.json');
34
+ expect(resolveAssetSource(metaPath, '@shared/foo.png')).toBe(
35
+ resolve(metaDir, '@shared/foo.png'),
36
+ );
120
37
  });
121
38
  });
@@ -0,0 +1,55 @@
1
+ import { mkdir, 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 { scan } from '../scanner.js';
6
+
7
+ const EMPTY_PACK = JSON.stringify({
8
+ schemaVersion: '1.0.0',
9
+ kind: 'internal-text-package',
10
+ assets: [],
11
+ });
12
+
13
+ describe('asset scanner test-directory boundary', () => {
14
+ const temporaryRoots: string[] = [];
15
+
16
+ afterEach(async () => {
17
+ await Promise.all(
18
+ temporaryRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })),
19
+ );
20
+ });
21
+
22
+ it('skips arbitrarily nested __tests__ declarations while keeping normal assets', async () => {
23
+ const root = await mkdtemp(join(tmpdir(), 'forgeax-pack-scanner-tests-'));
24
+ temporaryRoots.push(root);
25
+ const visible = join(root, 'world', 'scene.pack.json');
26
+ const nestedFixture = join(root, 'world', '__tests__', 'fixture.pack.json');
27
+ const rootFixture = join(root, '__tests__', 'nested', 'fixture.pack.json');
28
+
29
+ await mkdir(join(root, 'world', '__tests__'), { recursive: true });
30
+ await mkdir(join(root, '__tests__', 'nested'), { recursive: true });
31
+ await writeFile(visible, EMPTY_PACK);
32
+ // Deliberately malformed: a test fixture must not be able to degrade the
33
+ // production scan simply because it lives below the asset root.
34
+ await writeFile(nestedFixture, '{}');
35
+ await writeFile(rootFixture, '{}');
36
+
37
+ const result = await scan([root]);
38
+
39
+ expect(result).toMatchObject({ ok: true, value: [visible] });
40
+ });
41
+
42
+ it('allows an explicit __tests__ root when a test intentionally opts in', async () => {
43
+ const root = await mkdtemp(join(tmpdir(), 'forgeax-pack-scanner-tests-'));
44
+ temporaryRoots.push(root);
45
+ const testRoot = join(root, '__tests__');
46
+ const fixture = join(testRoot, 'fixture.pack.json');
47
+
48
+ await mkdir(testRoot, { recursive: true });
49
+ await writeFile(fixture, EMPTY_PACK);
50
+
51
+ const result = await scan([testRoot]);
52
+
53
+ expect(result).toMatchObject({ ok: true, value: [fixture] });
54
+ });
55
+ });
@@ -88,17 +88,14 @@ describe('engine-pack scanner inventory contract', () => {
88
88
  await writeFile(scriptablePath, '// scanner fixture');
89
89
 
90
90
  const scriptablePackageId = guid('019e3969-1d48-7c3b-ac24-6d68f4570662');
91
- const scriptableAssetGuid = guid('019e3969-1d48-7c3b-ac24-6d68f4570663');
92
91
  const result = await scanInventory([root], {
93
92
  scriptablePack: {
94
93
  executor: {
95
94
  load: async () => ({
96
95
  default: {
97
- schemaVersion: '1.0.0',
96
+ schemaVersion: '2.0.0',
98
97
  packageId: scriptablePackageId,
99
- assets: { effect: { guid: scriptableAssetGuid, kind: 'scene' } },
100
- externalAssets: {},
101
- build: () => ({ ok: true, value: {} }),
98
+ build: () => ({ ok: true, value: { effect: { kind: 'scene', entities: [] } } }),
102
99
  },
103
100
  }),
104
101
  },
@@ -123,14 +120,12 @@ describe('engine-pack scanner inventory contract', () => {
123
120
  format: 'pack.ts',
124
121
  sourcePath: scriptablePath,
125
122
  value: {
126
- importer: 'pack-ts',
127
123
  packageId: '019e3969-1d48-7c3b-ac24-6d68f4570662',
128
- subAssets: [{ sourceKey: 'effect', kind: 'scene' }],
129
124
  },
130
125
  });
131
126
  expect(
132
- scriptableDeclaration?.format === 'pack.ts' ? scriptableDeclaration.meta : undefined,
133
- ).toBe(scriptableDeclaration?.format === 'pack.ts' ? scriptableDeclaration.value : undefined);
127
+ scriptableDeclaration?.format === 'pack.ts' ? scriptableDeclaration.value : undefined,
128
+ ).not.toHaveProperty('subAssets');
134
129
  });
135
130
 
136
131
  it('releases the ScriptablePack loader for the path-only scan contract', async () => {
@@ -140,18 +135,15 @@ describe('engine-pack scanner inventory contract', () => {
140
135
  await writeFile(scriptablePath, '// scanner fixture');
141
136
 
142
137
  const packageId = guid('019e3969-1d48-7c3b-ac24-6d68f4570664');
143
- const assetGuid = guid('019e3969-1d48-7c3b-ac24-6d68f4570665');
144
138
  let disposals = 0;
145
139
  const result = await scan([root], {
146
140
  scriptablePack: {
147
141
  executor: {
148
142
  load: async () => ({
149
143
  default: {
150
- schemaVersion: '1.0.0',
144
+ schemaVersion: '2.0.0',
151
145
  packageId,
152
- assets: { effect: { guid: assetGuid, kind: 'scene' } },
153
- externalAssets: {},
154
- build: () => ({ ok: true, value: {} }),
146
+ build: () => ({ ok: true, value: { effect: { kind: 'scene', entities: [] } } }),
155
147
  },
156
148
  }),
157
149
  dispose: async () => {
@@ -164,4 +156,9 @@ describe('engine-pack scanner inventory contract', () => {
164
156
  expect(result.ok).toBe(true);
165
157
  expect(disposals).toBe(1);
166
158
  });
159
+
160
+ it('characterizes the public scanner inventory vocabulary', () => {
161
+ expect(typeof scan).toBe('function');
162
+ expect(typeof scanInventory).toBe('function');
163
+ });
167
164
  });
@@ -2,6 +2,7 @@ import { mkdtemp, realpath, rm, writeFile } from 'node:fs/promises';
2
2
  import { tmpdir } from 'node:os';
3
3
  import { join } from 'node:path';
4
4
  import { afterAll, beforeAll, describe, expect, it } from 'vitest';
5
+ import { inventoryDigest } from '../inventory/sync.js';
5
6
  import { scanInventory } from '../scanner.js';
6
7
 
7
8
  describe('AssetInventory', () => {
@@ -18,11 +19,8 @@ describe('AssetInventory', () => {
18
19
  [
19
20
  'const packageId = new Uint8Array(16);',
20
21
  'packageId[15] = 1;',
21
- 'const meshGuid = new Uint8Array(16);',
22
- 'meshGuid[15] = 2;',
23
22
  'export default {',
24
- "schemaVersion: '1.0.0', packageId,",
25
- "assets: { mesh: { guid: meshGuid, kind: 'mesh' } }, externalAssets: {},",
23
+ "schemaVersion: '2.0.0', packageId,",
26
24
  "build: () => ({ ok: true, value: { mesh: { kind: 'mesh' } } }),",
27
25
  '};',
28
26
  ].join('\n'),
@@ -48,9 +46,11 @@ describe('AssetInventory', () => {
48
46
  expect(declaration).toMatchObject({
49
47
  format: 'pack.ts',
50
48
  sourcePath,
51
- meta: {
52
- importer: 'pack-ts',
53
- subAssets: [{ guid, sourceKey: 'mesh', kind: 'mesh' }],
49
+ value: {
50
+ schemaVersion: '2.0.0',
51
+ kind: 'scriptable-pack-source',
52
+ packageId: '00000000-0000-0000-0000-000000000001',
53
+ source: sourcePath,
54
54
  },
55
55
  });
56
56
  });
@@ -69,8 +69,21 @@ describe('AssetInventory', () => {
69
69
  const { inventory, sourcePath } = getFixture();
70
70
  const declaration = inventory.declarations.get(sourcePath);
71
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
- ]);
72
+ expect(declaration?.format === 'pack.ts' ? declaration.definition : undefined).toMatchObject({
73
+ packageId: expect.any(Uint8Array),
74
+ schemaVersion: '2.0.0',
75
+ });
76
+ });
77
+
78
+ it('keeps semantic identity stable when the source path is renamed', () => {
79
+ const before = inventoryDigest({
80
+ declarations: [{ guid, sourceKey: 'hero/body', kind: 'mesh', payload: {}, refs: [] }],
81
+ });
82
+ const after = inventoryDigest({
83
+ declarations: [{ guid, sourceKey: 'hero/body', kind: 'mesh', payload: {}, refs: [] }],
84
+ });
85
+
86
+ expect(after).toBe(before);
87
+ expect(after).not.toContain('generated.pack.ts');
75
88
  });
76
89
  });
@@ -14,14 +14,9 @@ describe('ScriptablePack CLI Meta inspection', () => {
14
14
  await writeFile(
15
15
  source,
16
16
  `
17
- const guid = (hex) => new Uint8Array(hex.match(/../g).map((byte) => Number.parseInt(byte, 16)));
18
17
  export default {
19
- schemaVersion: '1.0.0',
20
- packageId: guid('019ffa978b397ad284cc273268591ab4'),
21
- assets: {
22
- wall: { guid: guid('019ffa97a5ee7645b613043323952808'), kind: 'mesh' }
23
- },
24
- externalAssets: {},
18
+ schemaVersion: '2.0.0',
19
+ packageId: new Uint8Array([1, 159, 250, 151, 139, 57, 122, 210, 132, 204, 39, 50, 104, 89, 26, 180]),
25
20
  build() { throw new Error('META_MUST_NOT_BUILD'); }
26
21
  };
27
22
  `,
@@ -36,9 +31,9 @@ describe('ScriptablePack CLI Meta inspection', () => {
36
31
  expect(code).toBe(0);
37
32
  expect(stderr).toEqual([]);
38
33
  expect(JSON.parse(stdout[0] ?? '{}')).toMatchObject({
39
- importer: 'pack-ts',
34
+ kind: 'scriptable-pack-source',
40
35
  source,
41
- subAssets: [{ sourceKey: 'wall', kind: 'mesh' }],
36
+ packageId: '019ffa97-8b39-7ad2-84cc-273268591ab4',
42
37
  });
43
38
  } finally {
44
39
  await rm(root, { recursive: true, force: true });
@@ -82,4 +82,27 @@ describe('diffTopology', () => {
82
82
  expect(result.preserved).toEqual([]);
83
83
  expect(result.ambiguous).toMatchObject([{ reason: 'duplicate-source-key' }]);
84
84
  });
85
+
86
+ it('keeps LOD source keys prefix-stable while allowing a suffix level', () => {
87
+ const result = diffTopology(
88
+ [
89
+ { guid: 'root', sourceKey: 'mesh/root', sourceIndex: 0, kind: 'mesh' },
90
+ { guid: 'lod1', sourceKey: 'mesh/lod1', sourceIndex: 1, kind: 'mesh' },
91
+ ],
92
+ [
93
+ { guid: 'root-next', sourceKey: 'mesh/root', sourceIndex: 0, kind: 'mesh' },
94
+ { guid: 'lod1-next', sourceKey: 'mesh/lod1', sourceIndex: 1, kind: 'mesh' },
95
+ { guid: 'lod2-next', sourceKey: 'mesh/lod2', sourceIndex: 2, kind: 'mesh' },
96
+ ],
97
+ );
98
+ expect(result.preserved).toEqual([
99
+ { guid: 'root', oldKey: 'mesh/root', newKey: 'mesh/root' },
100
+ { guid: 'lod1', oldKey: 'mesh/lod1', newKey: 'mesh/lod1' },
101
+ ]);
102
+ expect(result.added).toEqual([
103
+ { guid: 'lod2-next', sourceKey: 'mesh/lod2', sourceIndex: 2, kind: 'mesh' },
104
+ ]);
105
+ expect(result.removed).toEqual([]);
106
+ expect(result.ambiguous).toEqual([]);
107
+ });
85
108
  });
package/src/build.ts CHANGED
@@ -51,7 +51,6 @@ export {
51
51
  projectRuntimeCatalogRow,
52
52
  type RuntimeCatalogRowInput,
53
53
  } from './catalog-projection.js';
54
- export { type LoadAssetConfigResult, loadAssetConfig } from './config.js';
55
54
  export {
56
55
  type CookedMaterialRecord,
57
56
  collectMaterialCookRefs,
@@ -68,6 +67,22 @@ export {
68
67
  validateMaterialCookReceipt,
69
68
  } from './evidence/material-cook.js';
70
69
  export { buildOfflineAssetEvidence, packageVerification } from './evidence/offline-evidence.js';
70
+ export {
71
+ AssetGuid,
72
+ isValidAssetGuidString,
73
+ isValidPackSourceKey,
74
+ PACK_SOURCE_KEY_RE,
75
+ PackageId,
76
+ } from './guid.js';
77
+ export { projectAssetRefs, projectSceneEntityRefs } from './inventory/binding.js';
78
+ export {
79
+ type AuthorInventory,
80
+ type AuthorInventoryRow,
81
+ type InventoryError,
82
+ type InventoryErrorCode,
83
+ validateAuthorInventory,
84
+ } from './inventory/declaration.js';
85
+ export { inventoryDigest, syncAuthorInventory } from './inventory/sync.js';
71
86
  export {
72
87
  decodeMeshBinHeader,
73
88
  MESH_BIN_DIGEST_BYTES,
@@ -79,9 +94,14 @@ export {
79
94
  type MeshBinHeaderV4,
80
95
  writeMeshBinHeader,
81
96
  } from './mesh-bin-contract.js';
97
+ export * from './pack-authoring.js';
98
+ export {
99
+ createFileSystemPackAuthoringGateway,
100
+ createFileSystemPackAuthoringPort,
101
+ type FileSystemPackAuthoringOptions,
102
+ type PackAuthoringMaterializedAsset,
103
+ } from './pack-authoring-node.js';
82
104
  export {
83
- type AuthoredPackAssetInput,
84
- type AuthoredPackInput,
85
105
  type FinalizedPackageProduct,
86
106
  finalizePackageProduct,
87
107
  finalizePackageTransportSource,
@@ -92,7 +112,6 @@ export {
92
112
  type PackageProduct,
93
113
  type PackageProductAsset,
94
114
  packageTransportRevision,
95
- upgradeLegacyAuthoredPack,
96
115
  } from './package-finalizer.js';
97
116
  export { validateProducerContract, validateProducerOutputs } from './producer-contract.js';
98
117
  export { resolveAssetSource } from './resolve-asset-source.js';
@@ -107,8 +126,13 @@ export {
107
126
  } from './runtime-publication.js';
108
127
  export {
109
128
  type InventoryDeclaration,
129
+ type LegacyPackInventoryDocument,
130
+ type PackInventoryAsset,
131
+ type PackInventoryDocument,
132
+ type PackSourceInventoryDocument,
110
133
  type ScanInventory,
111
134
  type ScanOptions,
135
+ type ScanSourceDeclaration,
112
136
  type ScriptablePackInventoryDeclaration,
113
137
  type ScriptablePackScanOptions,
114
138
  STANDARD_SCRIPTABLE_PACK_SCAN_OPTIONS,
@@ -117,24 +141,10 @@ export {
117
141
  export {
118
142
  type AssetReader,
119
143
  isScriptablePackAssetKind,
120
- projectScriptablePackMeta,
121
144
  projectScriptablePackSceneComponents,
122
145
  SCRIPTABLE_PACK_ASSET_KINDS,
123
- SCRIPTABLE_PACK_CAPABILITY_MANIFEST,
124
- type ScriptablePackAssetDeclaration,
125
- type ScriptablePackAssetDeclarations,
126
- type ScriptablePackAssetFor,
127
146
  type ScriptablePackAssetKind,
128
- type ScriptablePackDefinition,
129
- type ScriptablePackError,
130
- type ScriptablePackExternalAssets,
131
- type ScriptablePackMetaJson,
132
- type ScriptablePackOutputs,
133
- type ScriptablePackPublicationEnvelope,
134
147
  type ScriptablePackReadError,
135
- type ScriptablePackSceneComponent,
136
- type ScriptablePackSceneComponentInput,
137
148
  type ScriptablePackSourceClosureEntry,
138
- validateScriptablePackDefinition,
139
149
  } from './scriptable-pack.js';
140
150
  export { calculateTopologyDiff, diffTopology } from './topology.js';