@forgeax/engine-pack 0.1.23 → 0.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -6
- package/dist/__tests__/material-cook-receipt-layer-plan.unit.test.d.ts +2 -0
- package/dist/__tests__/material-cook-receipt-layer-plan.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pack-authoring-gateway.unit.test.d.ts +2 -0
- package/dist/__tests__/pack-authoring-gateway.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pack-authoring.unit.test.d.ts +2 -0
- package/dist/__tests__/pack-authoring.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pack-scanner.unit.test.d.ts +2 -0
- package/dist/__tests__/pack-scanner.unit.test.d.ts.map +1 -0
- package/dist/build.d.ts +3 -3
- package/dist/build.d.ts.map +1 -1
- package/dist/build.mjs +331 -290
- package/dist/build.mjs.map +1 -1
- package/dist/cli-asset.mjs +33 -35
- package/dist/cli-asset.mjs.map +1 -1
- package/dist/evidence/material-cook.d.ts +11 -0
- package/dist/evidence/material-cook.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +69 -26
- package/dist/index.mjs.map +1 -1
- package/dist/material-cook.d.ts +1 -1
- package/dist/material-cook.d.ts.map +1 -1
- package/dist/material-cook.mjs +50 -3
- package/dist/material-cook.mjs.map +1 -1
- package/dist/{parameterized-pack-node.d.ts → pack-authoring-node.d.ts} +7 -7
- package/dist/pack-authoring-node.d.ts.map +1 -0
- package/dist/{parameterized-pack-node.mjs → pack-authoring-node.mjs} +59 -63
- package/dist/pack-authoring-node.mjs.map +1 -0
- package/dist/{parameterized-pack.d.ts → pack-authoring.d.ts} +28 -31
- package/dist/pack-authoring.d.ts.map +1 -0
- package/dist/pack-authoring.mjs +1137 -0
- package/dist/pack-authoring.mjs.map +1 -0
- package/dist/runtime.mjs +1 -1
- package/dist/runtime.mjs.map +1 -1
- package/dist/scanner.d.ts +8 -8
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.mjs +29 -31
- package/dist/scanner.mjs.map +1 -1
- package/dist/schema.mjs +1 -1
- package/dist/schema.mjs.map +1 -1
- package/dist/scriptable-pack-node.d.ts +4 -4
- package/dist/scriptable-pack-node.d.ts.map +1 -1
- package/dist/scriptable-pack-node.mjs +18 -20
- package/dist/scriptable-pack-node.mjs.map +1 -1
- package/dist/scriptable-pack.d.ts +3 -2
- package/dist/scriptable-pack.d.ts.map +1 -1
- package/dist/scriptable-pack.mjs +19 -23
- package/dist/scriptable-pack.mjs.map +1 -1
- package/package.json +2 -7
- package/schema/pack.schema.json +1 -1
- package/src/__tests__/material-cook-receipt-layer-plan.unit.test.ts +16 -0
- package/src/__tests__/material-cook-schema.unit.test.ts +62 -0
- package/src/__tests__/{parameterized-pack-authoring-gateway.unit.test.ts → pack-authoring-gateway.unit.test.ts} +9 -9
- package/src/__tests__/{parameterized-pack.unit.test.ts → pack-authoring.unit.test.ts} +10 -10
- package/src/__tests__/{parameterized-pack-scanner.unit.test.ts → pack-scanner.unit.test.ts} +6 -6
- package/src/__tests__/scanner-inventory.contract.test.ts +2 -4
- package/src/__tests__/scanner-inventory.test.ts +7 -6
- package/src/__tests__/scriptable-pack-cli.integration.test.ts +1 -1
- package/src/build.ts +8 -8
- package/src/catalog-builder.ts +3 -3
- package/src/cli-asset.ts +5 -5
- package/src/evidence/material-cook.ts +79 -3
- package/src/evidence/source-inventory.ts +3 -3
- package/src/index.ts +4 -1
- package/src/material-cook.ts +1 -0
- package/src/{parameterized-pack-node.ts → pack-authoring-node.ts} +71 -73
- package/src/{parameterized-pack.ts → pack-authoring.ts} +59 -74
- package/src/scanner.ts +26 -28
- package/src/schema/material-cook.schema.json +4 -1
- package/src/scriptable-pack-node.ts +25 -25
- package/src/scriptable-pack.ts +3 -2
- package/dist/__tests__/parameterized-pack-authoring-gateway.unit.test.d.ts +0 -2
- package/dist/__tests__/parameterized-pack-authoring-gateway.unit.test.d.ts.map +0 -1
- package/dist/__tests__/parameterized-pack-scanner.unit.test.d.ts +0 -2
- package/dist/__tests__/parameterized-pack-scanner.unit.test.d.ts.map +0 -1
- package/dist/__tests__/parameterized-pack.unit.test.d.ts +0 -2
- package/dist/__tests__/parameterized-pack.unit.test.d.ts.map +0 -1
- package/dist/parameterized-pack-node.d.ts.map +0 -1
- package/dist/parameterized-pack-node.mjs.map +0 -1
- package/dist/parameterized-pack.d.ts.map +0 -1
|
@@ -3,7 +3,7 @@ import { tmpdir } from 'node:os';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
5
5
|
import { AssetGuid, PackageId } from '../guid.js';
|
|
6
|
-
import {
|
|
6
|
+
import { createFileSystemPackAuthoringGateway } from '../pack-authoring-node.js';
|
|
7
7
|
|
|
8
8
|
const DIRECT_PACKAGE = '01900000-0000-7000-8000-000000000070';
|
|
9
9
|
const CLONE_PACKAGE = '01900000-0000-7000-8000-000000000071';
|
|
@@ -23,11 +23,11 @@ afterEach(async () => {
|
|
|
23
23
|
if (root !== undefined) await rm(root, { recursive: true, force: true });
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
describe('
|
|
26
|
+
describe('Pack authoring gateway', () => {
|
|
27
27
|
it('writes guid-less direct JSON, derives identity, and makes mutations idempotent', async () => {
|
|
28
28
|
const root = await mkdtemp(join(tmpdir(), 'forgeax-pack-gateway-direct-'));
|
|
29
29
|
roots.push(root);
|
|
30
|
-
const gateway =
|
|
30
|
+
const gateway = createFileSystemPackAuthoringGateway({ gameRoot: root });
|
|
31
31
|
const operation = {
|
|
32
32
|
operation: 'asset-source.create' as const,
|
|
33
33
|
requestId: 'create-direct',
|
|
@@ -72,7 +72,7 @@ describe('parameterized Pack authoring gateway', () => {
|
|
|
72
72
|
});
|
|
73
73
|
expect(unresolved).toMatchObject({ ok: false, error: { code: 'asset-not-ready' } });
|
|
74
74
|
|
|
75
|
-
const publishedGateway =
|
|
75
|
+
const publishedGateway = createFileSystemPackAuthoringGateway({
|
|
76
76
|
gameRoot: root,
|
|
77
77
|
materialized: () => [
|
|
78
78
|
{
|
|
@@ -121,10 +121,10 @@ describe('parameterized Pack authoring gateway', () => {
|
|
|
121
121
|
expect(cloneResolved).toMatchObject({ ok: true, value: { status: 'identity' } });
|
|
122
122
|
});
|
|
123
123
|
|
|
124
|
-
it('creates
|
|
124
|
+
it('creates ScriptablePack source and Pack instance documents with CAS-protected value edits', async () => {
|
|
125
125
|
const root = await mkdtemp(join(tmpdir(), 'forgeax-pack-gateway-instance-'));
|
|
126
126
|
roots.push(root);
|
|
127
|
-
const gateway =
|
|
127
|
+
const gateway = createFileSystemPackAuthoringGateway({ gameRoot: root });
|
|
128
128
|
const source = await gateway.execute({
|
|
129
129
|
operation: 'asset-source.create',
|
|
130
130
|
requestId: 'create-source',
|
|
@@ -188,7 +188,7 @@ describe('parameterized Pack authoring gateway', () => {
|
|
|
188
188
|
it('fails closed when the current materialized projection violates derived identity', async () => {
|
|
189
189
|
const root = await mkdtemp(join(tmpdir(), 'forgeax-pack-gateway-projection-'));
|
|
190
190
|
roots.push(root);
|
|
191
|
-
const gateway =
|
|
191
|
+
const gateway = createFileSystemPackAuthoringGateway({
|
|
192
192
|
gameRoot: root,
|
|
193
193
|
materialized: () => [
|
|
194
194
|
{
|
|
@@ -216,7 +216,7 @@ describe('parameterized Pack authoring gateway', () => {
|
|
|
216
216
|
it('does not fall back from a missing sourcePath to another package locator', async () => {
|
|
217
217
|
const root = await mkdtemp(join(tmpdir(), 'forgeax-pack-gateway-locator-'));
|
|
218
218
|
roots.push(root);
|
|
219
|
-
const gateway =
|
|
219
|
+
const gateway = createFileSystemPackAuthoringGateway({ gameRoot: root });
|
|
220
220
|
await gateway.execute({
|
|
221
221
|
operation: 'asset-source.create',
|
|
222
222
|
requestId: 'create-locator-source',
|
|
@@ -237,7 +237,7 @@ describe('parameterized Pack authoring gateway', () => {
|
|
|
237
237
|
it('converts a broken materialized projection callback into a structured error', async () => {
|
|
238
238
|
const root = await mkdtemp(join(tmpdir(), 'forgeax-pack-gateway-projection-callback-'));
|
|
239
239
|
roots.push(root);
|
|
240
|
-
const gateway =
|
|
240
|
+
const gateway = createFileSystemPackAuthoringGateway({
|
|
241
241
|
gameRoot: root,
|
|
242
242
|
materialized: () => {
|
|
243
243
|
throw new Error('projection unavailable');
|
|
@@ -5,12 +5,12 @@ import {
|
|
|
5
5
|
definePack,
|
|
6
6
|
definePackageId,
|
|
7
7
|
type PackParameterInheritanceSubject,
|
|
8
|
-
|
|
8
|
+
parsePackSourceJson,
|
|
9
9
|
projectDirectPackJson,
|
|
10
10
|
resolvePackParameterInheritance,
|
|
11
11
|
resolvePackParameterValues,
|
|
12
12
|
validatePackDefinition,
|
|
13
|
-
} from '../
|
|
13
|
+
} from '../pack-authoring.js';
|
|
14
14
|
|
|
15
15
|
const NAMESPACE = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
|
|
16
16
|
|
|
@@ -20,7 +20,7 @@ function packageId(value: string) {
|
|
|
20
20
|
return result.value;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
describe('
|
|
23
|
+
describe('ScriptablePack and Pack authoring', () => {
|
|
24
24
|
it('derives the RFC 4122 UUIDv5 vector and keeps PackageId branded separately', () => {
|
|
25
25
|
const namespace = packageId(NAMESPACE);
|
|
26
26
|
expect(AssetGuid.format(AssetGuid.derive(namespace, 'www.widgets.com'))).toBe(
|
|
@@ -30,7 +30,7 @@ describe('parameterized Pack authoring', () => {
|
|
|
30
30
|
expect(AssetGuid.derive(namespace, 'mesh/a')).not.toBe(namespace);
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
it('derives an explicit context shape for zero-parameter and
|
|
33
|
+
it('derives an explicit context shape for zero-parameter and parameter-bearing ScriptablePacks', async () => {
|
|
34
34
|
const zero = definePack({
|
|
35
35
|
schemaVersion: '2.0.0',
|
|
36
36
|
packageId: definePackageId('01900000-0000-7000-8000-000000000001'),
|
|
@@ -51,7 +51,7 @@ describe('parameterized Pack authoring', () => {
|
|
|
51
51
|
});
|
|
52
52
|
expect(zeroResult.ok).toBe(true);
|
|
53
53
|
|
|
54
|
-
const
|
|
54
|
+
const withParameters = definePack({
|
|
55
55
|
schemaVersion: '2.0.0',
|
|
56
56
|
packageId: definePackageId('01900000-0000-7000-8000-000000000002'),
|
|
57
57
|
parameters: [
|
|
@@ -67,9 +67,9 @@ describe('parameterized Pack authoring', () => {
|
|
|
67
67
|
},
|
|
68
68
|
}),
|
|
69
69
|
});
|
|
70
|
-
const values = resolvePackParameterValues(
|
|
70
|
+
const values = resolvePackParameterValues(withParameters, { segments: 24 });
|
|
71
71
|
expect(values).toMatchObject({ ok: true, value: { segments: 24, style: 'oak' } });
|
|
72
|
-
expect(resolvePackParameterValues(
|
|
72
|
+
expect(resolvePackParameterValues(withParameters, { unknown: true })).toMatchObject({
|
|
73
73
|
ok: false,
|
|
74
74
|
error: { code: 'pack-parameter-invalid' },
|
|
75
75
|
});
|
|
@@ -148,7 +148,7 @@ describe('parameterized Pack authoring', () => {
|
|
|
148
148
|
});
|
|
149
149
|
|
|
150
150
|
it('parses direct and instance JSON without accepting GUID fields', () => {
|
|
151
|
-
const direct =
|
|
151
|
+
const direct = parsePackSourceJson({
|
|
152
152
|
schemaVersion: '3.0.0',
|
|
153
153
|
packageId: '01900000-0000-7000-8000-000000000010',
|
|
154
154
|
assets: {
|
|
@@ -167,14 +167,14 @@ describe('parameterized Pack authoring', () => {
|
|
|
167
167
|
});
|
|
168
168
|
}
|
|
169
169
|
expect(
|
|
170
|
-
|
|
170
|
+
parsePackSourceJson({
|
|
171
171
|
schemaVersion: '3.0.0',
|
|
172
172
|
packageId: '01900000-0000-7000-8000-000000000010',
|
|
173
173
|
assets: { 'scene/main': { guid: NAMESPACE, kind: 'scene', payload: {}, refs: [] } },
|
|
174
174
|
}),
|
|
175
175
|
).toMatchObject({ ok: false, error: { code: 'pack-parameter-invalid' } });
|
|
176
176
|
expect(
|
|
177
|
-
|
|
177
|
+
parsePackSourceJson({
|
|
178
178
|
schemaVersion: '3.0.0',
|
|
179
179
|
packageId: '01900000-0000-7000-8000-000000000020',
|
|
180
180
|
parent: '01900000-0000-7000-8000-000000000002',
|
|
@@ -16,9 +16,9 @@ afterEach(async () => {
|
|
|
16
16
|
if (root !== undefined) await rm(root, { recursive: true, force: true });
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
describe('
|
|
19
|
+
describe('ScriptablePack and Pack source scanner', () => {
|
|
20
20
|
it('indexes direct v3 output keys as derived GUIDs without requiring entry GUIDs', async () => {
|
|
21
|
-
const root = await mkdtemp(join(tmpdir(), 'forgeax-
|
|
21
|
+
const root = await mkdtemp(join(tmpdir(), 'forgeax-scriptable-pack-scan-'));
|
|
22
22
|
roots.push(root);
|
|
23
23
|
const path = join(root, 'direct.pack.json');
|
|
24
24
|
await writeFile(
|
|
@@ -65,8 +65,8 @@ describe('parameterized Pack scanner', () => {
|
|
|
65
65
|
expect(stderr).toEqual([]);
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
it('captures a
|
|
69
|
-
const root = await mkdtemp(join(tmpdir(), 'forgeax-
|
|
68
|
+
it('captures a ScriptablePack source as metadata and resolves an instance parent', async () => {
|
|
69
|
+
const root = await mkdtemp(join(tmpdir(), 'forgeax-scriptable-pack-source-scan-'));
|
|
70
70
|
roots.push(root);
|
|
71
71
|
await writeFile(
|
|
72
72
|
join(root, 'source.pack.ts'),
|
|
@@ -96,12 +96,12 @@ describe('parameterized Pack scanner', () => {
|
|
|
96
96
|
expect(result.ok).toBe(true);
|
|
97
97
|
if (!result.ok) return;
|
|
98
98
|
const declaration = result.value.declarations.get(join(root, 'source.pack.ts'));
|
|
99
|
-
expect(declaration?.format).toBe('pack.ts
|
|
99
|
+
expect(declaration?.format).toBe('pack.ts');
|
|
100
100
|
expect(result.value.inventory).toEqual([]);
|
|
101
101
|
});
|
|
102
102
|
|
|
103
103
|
it('fails closed when an instance parent is missing', async () => {
|
|
104
|
-
const root = await mkdtemp(join(tmpdir(), 'forgeax-
|
|
104
|
+
const root = await mkdtemp(join(tmpdir(), 'forgeax-scriptable-pack-parent-scan-'));
|
|
105
105
|
roots.push(root);
|
|
106
106
|
await writeFile(
|
|
107
107
|
join(root, 'missing.pack.json'),
|
|
@@ -117,16 +117,14 @@ describe('engine-pack scanner inventory contract', () => {
|
|
|
117
117
|
});
|
|
118
118
|
const scriptableDeclaration = result.value.declarations.get(scriptablePath);
|
|
119
119
|
expect(scriptableDeclaration).toMatchObject({
|
|
120
|
-
format: 'pack.ts
|
|
120
|
+
format: 'pack.ts',
|
|
121
121
|
sourcePath: scriptablePath,
|
|
122
122
|
value: {
|
|
123
123
|
packageId: '019e3969-1d48-7c3b-ac24-6d68f4570662',
|
|
124
124
|
},
|
|
125
125
|
});
|
|
126
126
|
expect(
|
|
127
|
-
scriptableDeclaration?.format === 'pack.ts
|
|
128
|
-
? scriptableDeclaration.value
|
|
129
|
-
: undefined,
|
|
127
|
+
scriptableDeclaration?.format === 'pack.ts' ? scriptableDeclaration.value : undefined,
|
|
130
128
|
).not.toHaveProperty('subAssets');
|
|
131
129
|
});
|
|
132
130
|
|
|
@@ -44,11 +44,11 @@ describe('AssetInventory', () => {
|
|
|
44
44
|
const { inventory, sourcePath } = getFixture();
|
|
45
45
|
const declaration = inventory.declarations.get(sourcePath);
|
|
46
46
|
expect(declaration).toMatchObject({
|
|
47
|
-
format: 'pack.ts
|
|
47
|
+
format: 'pack.ts',
|
|
48
48
|
sourcePath,
|
|
49
49
|
value: {
|
|
50
50
|
schemaVersion: '2.0.0',
|
|
51
|
-
kind: '
|
|
51
|
+
kind: 'scriptable-pack-source',
|
|
52
52
|
packageId: '00000000-0000-0000-0000-000000000001',
|
|
53
53
|
source: sourcePath,
|
|
54
54
|
},
|
|
@@ -59,7 +59,7 @@ describe('AssetInventory', () => {
|
|
|
59
59
|
const { inventory, sourcePath } = getFixture();
|
|
60
60
|
const declaration = inventory.declarations.get(sourcePath);
|
|
61
61
|
expect(
|
|
62
|
-
declaration?.format === 'pack.ts
|
|
62
|
+
declaration?.format === 'pack.ts'
|
|
63
63
|
? declaration.sourceClosure.map((entry) => entry.path)
|
|
64
64
|
: undefined,
|
|
65
65
|
).toEqual([await realpath(sourcePath)]);
|
|
@@ -69,9 +69,10 @@ describe('AssetInventory', () => {
|
|
|
69
69
|
const { inventory, sourcePath } = getFixture();
|
|
70
70
|
const declaration = inventory.declarations.get(sourcePath);
|
|
71
71
|
expect(inventory.inventory).toEqual([]);
|
|
72
|
-
expect(
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
expect(declaration?.format === 'pack.ts' ? declaration.definition : undefined).toMatchObject({
|
|
73
|
+
packageId: expect.any(Uint8Array),
|
|
74
|
+
schemaVersion: '2.0.0',
|
|
75
|
+
});
|
|
75
76
|
});
|
|
76
77
|
|
|
77
78
|
it('keeps semantic identity stable when the source path is renamed', () => {
|
|
@@ -31,7 +31,7 @@ describe('ScriptablePack CLI Meta inspection', () => {
|
|
|
31
31
|
expect(code).toBe(0);
|
|
32
32
|
expect(stderr).toEqual([]);
|
|
33
33
|
expect(JSON.parse(stdout[0] ?? '{}')).toMatchObject({
|
|
34
|
-
kind: '
|
|
34
|
+
kind: 'scriptable-pack-source',
|
|
35
35
|
source,
|
|
36
36
|
packageId: '019ffa97-8b39-7ad2-84cc-273268591ab4',
|
|
37
37
|
});
|
package/src/build.ts
CHANGED
|
@@ -94,6 +94,13 @@ export {
|
|
|
94
94
|
type MeshBinHeaderV4,
|
|
95
95
|
writeMeshBinHeader,
|
|
96
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';
|
|
97
104
|
export {
|
|
98
105
|
type FinalizedPackageProduct,
|
|
99
106
|
finalizePackageProduct,
|
|
@@ -106,13 +113,6 @@ export {
|
|
|
106
113
|
type PackageProductAsset,
|
|
107
114
|
packageTransportRevision,
|
|
108
115
|
} from './package-finalizer.js';
|
|
109
|
-
export * from './parameterized-pack.js';
|
|
110
|
-
export {
|
|
111
|
-
createFileSystemParameterizedPackAuthoringGateway,
|
|
112
|
-
createFileSystemParameterizedPackAuthoringPort,
|
|
113
|
-
type FileSystemParameterizedPackAuthoringOptions,
|
|
114
|
-
type ParameterizedPackMaterializedAsset,
|
|
115
|
-
} from './parameterized-pack-node.js';
|
|
116
116
|
export { validateProducerContract, validateProducerOutputs } from './producer-contract.js';
|
|
117
117
|
export { resolveAssetSource } from './resolve-asset-source.js';
|
|
118
118
|
export { parsePackV2, validateMeta, validatePack, validatePackV2 } from './runtime.js';
|
|
@@ -129,7 +129,7 @@ export {
|
|
|
129
129
|
type LegacyPackInventoryDocument,
|
|
130
130
|
type PackInventoryAsset,
|
|
131
131
|
type PackInventoryDocument,
|
|
132
|
-
type
|
|
132
|
+
type PackSourceInventoryDocument,
|
|
133
133
|
type ScanInventory,
|
|
134
134
|
type ScanOptions,
|
|
135
135
|
type ScanSourceDeclaration,
|
package/src/catalog-builder.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
projectPackageCatalog,
|
|
7
7
|
} from './catalog-projection.js';
|
|
8
8
|
import { deriveAssetName } from './deriveAssetName.js';
|
|
9
|
-
import {
|
|
9
|
+
import { parsePackSourceJson, projectDirectPackJson } from './pack-authoring.js';
|
|
10
10
|
import { resolveAssetSource } from './resolve-asset-source.js';
|
|
11
11
|
import {
|
|
12
12
|
type MetaInventoryDocument,
|
|
@@ -201,7 +201,7 @@ function projectSource(
|
|
|
201
201
|
declarations: Map<string, CatalogImportMeta>,
|
|
202
202
|
sourceIdentityFor: CatalogBuildProjectionOptions['sourceIdentityFor'],
|
|
203
203
|
): { readonly entries: PackIndexEntry[]; readonly error?: CatalogBuildError } {
|
|
204
|
-
if (sourceDeclaration.format === 'pack.ts
|
|
204
|
+
if (sourceDeclaration.format === 'pack.ts') {
|
|
205
205
|
// A source declaration is an authoring identity and parameter contract,
|
|
206
206
|
// not a materialized output set. Dynamic build owns publication after its
|
|
207
207
|
// fixed-point worklist converges; Catalog scanning must not invent rows.
|
|
@@ -233,7 +233,7 @@ function projectSource(
|
|
|
233
233
|
const parsed = sourceDeclaration.value;
|
|
234
234
|
const sourcePath = catalogSourcePathFor(cwd, path, sourceIdentityFor);
|
|
235
235
|
if (parsed.schemaVersion === '3.0.0') {
|
|
236
|
-
const authoring =
|
|
236
|
+
const authoring = parsePackSourceJson(parsed);
|
|
237
237
|
if (!authoring.ok) {
|
|
238
238
|
return {
|
|
239
239
|
entries: [],
|
package/src/cli-asset.ts
CHANGED
|
@@ -26,9 +26,9 @@ import {
|
|
|
26
26
|
import { readSourceInventory } from './evidence/source-inventory.js';
|
|
27
27
|
import { isValidAssetGuidString } from './guid.js';
|
|
28
28
|
import { parsePackV2 } from './index.js';
|
|
29
|
-
import {
|
|
29
|
+
import { projectScriptablePackMeta } from './pack-authoring.js';
|
|
30
30
|
import { type ScanSourceDeclaration, scanInventory } from './scanner.js';
|
|
31
|
-
import {
|
|
31
|
+
import { loadScriptablePack } from './scriptable-pack-node.js';
|
|
32
32
|
|
|
33
33
|
export interface PackEntry {
|
|
34
34
|
readonly guid: string;
|
|
@@ -81,7 +81,7 @@ export async function scanEntries(
|
|
|
81
81
|
return { ok: false };
|
|
82
82
|
}
|
|
83
83
|
const entries: PackEntry[] = [];
|
|
84
|
-
// The scanner is the single identity projection for Pack JSON.
|
|
84
|
+
// The scanner is the single identity projection for Pack JSON. ScriptablePack
|
|
85
85
|
// source modules deliberately contribute no rows until a build generation.
|
|
86
86
|
for (const declaration of result.value.declarations.values()) {
|
|
87
87
|
if (declaration.format !== 'meta.json') continue;
|
|
@@ -233,11 +233,11 @@ async function runMeta(rest: string[], ctx: AssetCtx): Promise<number> {
|
|
|
233
233
|
});
|
|
234
234
|
}
|
|
235
235
|
const cwd = ctx.cwd ?? process.cwd();
|
|
236
|
-
const loaded = await
|
|
236
|
+
const loaded = await loadScriptablePack(absolutePath(source, cwd), {
|
|
237
237
|
metadataOnly: true,
|
|
238
238
|
});
|
|
239
239
|
if (!loaded.ok) return emitError(ctx, loaded.error);
|
|
240
|
-
ctx.stdoutWrite(JSON.stringify(
|
|
240
|
+
ctx.stdoutWrite(JSON.stringify(projectScriptablePackMeta(loaded.value, source)));
|
|
241
241
|
return 0;
|
|
242
242
|
}
|
|
243
243
|
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
MaterialValue,
|
|
8
8
|
Result,
|
|
9
9
|
} from '@forgeax/engine-types';
|
|
10
|
-
import { err, MATERIAL_TEXTURE_SLOTS, ok } from '@forgeax/engine-types';
|
|
10
|
+
import { deriveStandardLayerPlan, err, MATERIAL_TEXTURE_SLOTS, ok } from '@forgeax/engine-types';
|
|
11
11
|
import { sha256 } from '@noble/hashes/sha2.js';
|
|
12
12
|
import { bytesToHex } from '@noble/hashes/utils.js';
|
|
13
13
|
|
|
@@ -56,7 +56,11 @@ export interface MaterialCookReceipt {
|
|
|
56
56
|
readonly profile: string;
|
|
57
57
|
readonly compilerVersion: string;
|
|
58
58
|
readonly identity: MaterialCookIdentity;
|
|
59
|
-
readonly derivedInterface: {
|
|
59
|
+
readonly derivedInterface: {
|
|
60
|
+
readonly layoutIdentity: string;
|
|
61
|
+
/** Build-time Standard physical lowering identity, when the root is Standard. */
|
|
62
|
+
readonly layerPlanIdentity?: string;
|
|
63
|
+
};
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
export interface MaterialParameterContract {
|
|
@@ -101,6 +105,36 @@ export interface MaterialCookIdentityExpectation {
|
|
|
101
105
|
readonly inputDigest?: string;
|
|
102
106
|
}
|
|
103
107
|
|
|
108
|
+
const STANDARD_ROOT_MODULES = new Set([
|
|
109
|
+
'forgeax::default-standard-pbr',
|
|
110
|
+
'forgeax::pbr-skin',
|
|
111
|
+
'forgeax_material::standard',
|
|
112
|
+
'forgeax_material::pbr-skin',
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
/** Shared ownership predicate for the built-in Standard material root. */
|
|
116
|
+
export function isStandardRootModule(module: string): boolean {
|
|
117
|
+
return STANDARD_ROOT_MODULES.has(module);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function isStandardMaterialRecord(record: Pick<CookedMaterialRecord, 'resolved'>): boolean {
|
|
121
|
+
return record.resolved.passes.some((pass) => isStandardRootModule(pass.program.module));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Recompute the Standard layer identity from the admitted resolved root.
|
|
126
|
+
* Material admission and runtime loading share this owner so a stale receipt
|
|
127
|
+
* cannot select a different physical layer plan after cooking.
|
|
128
|
+
*/
|
|
129
|
+
export function materialLayerPlanIdentity(
|
|
130
|
+
record: Pick<CookedMaterialRecord, 'resolved'>,
|
|
131
|
+
): string | undefined {
|
|
132
|
+
if (!isStandardMaterialRecord(record)) {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
return deriveStandardLayerPlan(record.resolved.parameters, record.resolved.passes).identity;
|
|
136
|
+
}
|
|
137
|
+
|
|
104
138
|
function unique(values: readonly string[]): readonly string[] {
|
|
105
139
|
return [...new Set(values)].sort();
|
|
106
140
|
}
|
|
@@ -286,6 +320,16 @@ export function validateMaterialCookReceipt(
|
|
|
286
320
|
if (derivedInterface.layoutIdentity !== identity.layoutIdentity) {
|
|
287
321
|
return invalid('receipt.derivedInterface.layoutIdentity', derivedInterface.layoutIdentity);
|
|
288
322
|
}
|
|
323
|
+
if (
|
|
324
|
+
derivedInterface.layerPlanIdentity !== undefined &&
|
|
325
|
+
(typeof derivedInterface.layerPlanIdentity !== 'string' ||
|
|
326
|
+
derivedInterface.layerPlanIdentity.length === 0)
|
|
327
|
+
) {
|
|
328
|
+
return invalid(
|
|
329
|
+
'receipt.derivedInterface.layerPlanIdentity',
|
|
330
|
+
derivedInterface.layerPlanIdentity,
|
|
331
|
+
);
|
|
332
|
+
}
|
|
289
333
|
for (const field of ['sourceClosure', 'profile', 'compilerVersion']) {
|
|
290
334
|
const fieldValue = candidate[field];
|
|
291
335
|
if (
|
|
@@ -320,7 +364,12 @@ export function validateMaterialCookReceipt(
|
|
|
320
364
|
profile: candidate.profile as string,
|
|
321
365
|
compilerVersion: candidate.compilerVersion as string,
|
|
322
366
|
identity,
|
|
323
|
-
derivedInterface: {
|
|
367
|
+
derivedInterface: {
|
|
368
|
+
layoutIdentity: derivedInterface.layoutIdentity as string,
|
|
369
|
+
...(derivedInterface.layerPlanIdentity === undefined
|
|
370
|
+
? {}
|
|
371
|
+
: { layerPlanIdentity: derivedInterface.layerPlanIdentity as string }),
|
|
372
|
+
},
|
|
324
373
|
});
|
|
325
374
|
}
|
|
326
375
|
|
|
@@ -380,6 +429,33 @@ export function validateCookedMaterialRecord(
|
|
|
380
429
|
artifactDigest: artifact.digest as string,
|
|
381
430
|
});
|
|
382
431
|
if (!receiptResult.ok) return receiptResult;
|
|
432
|
+
const resolved = candidate.resolved as Record<string, unknown>;
|
|
433
|
+
if (!Array.isArray(resolved.passes)) return invalid('resolved.passes', resolved.passes);
|
|
434
|
+
if (!Array.isArray(resolved.parameters))
|
|
435
|
+
return invalid('resolved.parameters', resolved.parameters);
|
|
436
|
+
if (
|
|
437
|
+
resolved.values === null ||
|
|
438
|
+
typeof resolved.values !== 'object' ||
|
|
439
|
+
Array.isArray(resolved.values)
|
|
440
|
+
)
|
|
441
|
+
return invalid('resolved.values', resolved.values);
|
|
442
|
+
let expectedLayerPlanIdentity: string | undefined;
|
|
443
|
+
try {
|
|
444
|
+
expectedLayerPlanIdentity = materialLayerPlanIdentity({
|
|
445
|
+
resolved: resolved as unknown as CookedMaterialRecord['resolved'],
|
|
446
|
+
});
|
|
447
|
+
} catch (error) {
|
|
448
|
+
return invalid('resolved.layerPlanIdentity', error instanceof Error ? error.message : error);
|
|
449
|
+
}
|
|
450
|
+
if (
|
|
451
|
+
expectedLayerPlanIdentity !== undefined &&
|
|
452
|
+
receiptResult.value.derivedInterface.layerPlanIdentity !== expectedLayerPlanIdentity
|
|
453
|
+
) {
|
|
454
|
+
return invalid(
|
|
455
|
+
'receipt.derivedInterface.layerPlanIdentity',
|
|
456
|
+
receiptResult.value.derivedInterface.layerPlanIdentity,
|
|
457
|
+
);
|
|
458
|
+
}
|
|
383
459
|
if (candidate.artifactDigest !== undefined && candidate.artifactDigest !== artifact.digest)
|
|
384
460
|
return invalid('artifactDigest', candidate.artifactDigest);
|
|
385
461
|
if (
|
|
@@ -2,7 +2,7 @@ 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 {
|
|
5
|
+
import { parsePackSourceJson, projectDirectPackJson } from '../pack-authoring.js';
|
|
6
6
|
import { SCANNER_BLACKLIST, scan } from '../scanner.js';
|
|
7
7
|
|
|
8
8
|
/** Project root and GUID used to locate an authoritative source declaration. */
|
|
@@ -149,7 +149,7 @@ export async function readSourceInventory(
|
|
|
149
149
|
let authored: SourceDeclarationEvidence | undefined;
|
|
150
150
|
for (const path of paths) {
|
|
151
151
|
if (path.endsWith('.pack.ts')) {
|
|
152
|
-
//
|
|
152
|
+
// ScriptablePack source identity is a build result, not a declaration
|
|
153
153
|
// table. The source index therefore records the source only after the
|
|
154
154
|
// producer publishes its materialized Pack output.
|
|
155
155
|
continue;
|
|
@@ -166,7 +166,7 @@ export async function readSourceInventory(
|
|
|
166
166
|
}
|
|
167
167
|
if (path.endsWith('.pack.json')) {
|
|
168
168
|
if (parsed.schemaVersion === '3.0.0') {
|
|
169
|
-
const direct =
|
|
169
|
+
const direct = parsePackSourceJson(parsed);
|
|
170
170
|
if (direct.ok && direct.value.format === 'direct') {
|
|
171
171
|
const projected = projectDirectPackJson(direct.value);
|
|
172
172
|
if (
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,8 @@ export {
|
|
|
7
7
|
collectMaterialCookRefs,
|
|
8
8
|
createMaterialArtifactDigest,
|
|
9
9
|
createMaterialCookIdentity,
|
|
10
|
+
isStandardMaterialRecord,
|
|
11
|
+
isStandardRootModule,
|
|
10
12
|
type MaterialCookArtifact,
|
|
11
13
|
type MaterialCookIdentity,
|
|
12
14
|
type MaterialCookIdentityExpectation,
|
|
@@ -15,6 +17,7 @@ export {
|
|
|
15
17
|
type MaterialCookRecordError,
|
|
16
18
|
type MaterialCookRefs,
|
|
17
19
|
type MaterialCookWasmProvenance,
|
|
20
|
+
materialLayerPlanIdentity,
|
|
18
21
|
projectCookedMaterialRecord,
|
|
19
22
|
serializeCookedMaterialRecord,
|
|
20
23
|
serializeMaterialCookReceipt,
|
|
@@ -40,7 +43,7 @@ export {
|
|
|
40
43
|
type MeshBinHeaderV4,
|
|
41
44
|
writeMeshBinHeader,
|
|
42
45
|
} from './mesh-bin-contract.js';
|
|
43
|
-
export * from './
|
|
46
|
+
export * from './pack-authoring.js';
|
|
44
47
|
export { validateProducerContract, validateProducerOutputs } from './producer-contract.js';
|
|
45
48
|
export {
|
|
46
49
|
type AssetReader,
|
package/src/material-cook.ts
CHANGED