@forgeax/engine-pack 0.1.21 → 0.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +167 -48
- package/dist/__tests__/inventory-schema.test.d.ts +2 -0
- package/dist/__tests__/inventory-schema.test.d.ts.map +1 -0
- package/dist/__tests__/material-cook-receipt-layer-plan.unit.test.d.ts +2 -0
- package/dist/__tests__/material-cook-receipt-layer-plan.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pack-authoring-gateway.unit.test.d.ts +2 -0
- package/dist/__tests__/pack-authoring-gateway.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pack-authoring.unit.test.d.ts +2 -0
- package/dist/__tests__/pack-authoring.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pack-scanner.unit.test.d.ts +2 -0
- package/dist/__tests__/pack-scanner.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scanner-blacklist.test.d.ts +2 -0
- package/dist/__tests__/scanner-blacklist.test.d.ts.map +1 -0
- package/dist/build.d.ts +9 -4
- package/dist/build.d.ts.map +1 -1
- package/dist/build.mjs +3449 -897
- package/dist/build.mjs.map +1 -1
- package/dist/builtin.mjs +37 -4
- package/dist/builtin.mjs.map +1 -1
- package/dist/catalog-builder.d.ts.map +1 -1
- package/dist/cli-asset.d.ts.map +1 -1
- package/dist/cli-asset.mjs +1361 -845
- package/dist/cli-asset.mjs.map +1 -1
- package/dist/deriveAssetName.d.ts +11 -7
- package/dist/deriveAssetName.d.ts.map +1 -1
- package/dist/evidence/material-cook.d.ts +11 -0
- package/dist/evidence/material-cook.d.ts.map +1 -1
- package/dist/evidence/source-inventory.d.ts.map +1 -1
- package/dist/guid.d.ts +15 -0
- package/dist/guid.d.ts.map +1 -1
- package/dist/guid.mjs +53 -5
- package/dist/guid.mjs.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +1297 -562
- package/dist/index.mjs.map +1 -1
- package/dist/inventory/binding.d.ts +6 -0
- package/dist/inventory/binding.d.ts.map +1 -0
- package/dist/inventory/declaration.d.ts +24 -0
- package/dist/inventory/declaration.d.ts.map +1 -0
- package/dist/inventory/index.d.ts +3 -0
- package/dist/inventory/index.d.ts.map +1 -0
- package/dist/inventory/sync.d.ts +5 -0
- package/dist/inventory/sync.d.ts.map +1 -0
- package/dist/material-cook.d.ts +1 -1
- package/dist/material-cook.d.ts.map +1 -1
- package/dist/material-cook.mjs +50 -3
- package/dist/material-cook.mjs.map +1 -1
- package/dist/name.mjs +4 -7
- package/dist/name.mjs.map +1 -1
- package/dist/pack-authoring-node.d.ts +26 -0
- package/dist/pack-authoring-node.d.ts.map +1 -0
- package/dist/pack-authoring-node.mjs +11089 -0
- package/dist/pack-authoring-node.mjs.map +1 -0
- package/dist/pack-authoring.d.ts +230 -0
- package/dist/pack-authoring.d.ts.map +1 -0
- package/dist/pack-authoring.mjs +1137 -0
- package/dist/pack-authoring.mjs.map +1 -0
- package/dist/package-finalizer.d.ts +2 -20
- package/dist/package-finalizer.d.ts.map +1 -1
- package/dist/resolve-asset-source.d.ts +1 -3
- package/dist/resolve-asset-source.d.ts.map +1 -1
- package/dist/resolve-asset-source.mjs +6 -81
- package/dist/resolve-asset-source.mjs.map +1 -1
- package/dist/runtime.mjs +77 -7
- package/dist/runtime.mjs.map +1 -1
- package/dist/scanner.d.ts +18 -7
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.mjs +1164 -687
- package/dist/scanner.mjs.map +1 -1
- package/dist/schema-compiled.d.ts.map +1 -1
- package/dist/schema.mjs +79 -7
- package/dist/schema.mjs.map +1 -1
- package/dist/scriptable-pack-node.d.ts +7 -31
- package/dist/scriptable-pack-node.d.ts.map +1 -1
- package/dist/scriptable-pack-node.mjs +572 -1019
- package/dist/scriptable-pack-node.mjs.map +1 -1
- package/dist/scriptable-pack-worker.mjs +29 -11
- package/dist/scriptable-pack-worker.mjs.map +1 -1
- package/dist/scriptable-pack.d.ts +20 -532
- package/dist/scriptable-pack.d.ts.map +1 -1
- package/dist/scriptable-pack.mjs +1038 -527
- package/dist/scriptable-pack.mjs.map +1 -1
- package/package.json +2 -7
- package/schema/meta.schema.json +1 -1
- package/schema/pack.schema.json +76 -6
- package/src/__tests__/guid-collision.unit.test.ts +18 -9
- package/src/__tests__/inventory-schema.test.ts +109 -0
- package/src/__tests__/material-cook-receipt-layer-plan.unit.test.ts +16 -0
- package/src/__tests__/material-cook-schema.unit.test.ts +62 -0
- package/src/__tests__/pack-authoring-gateway.unit.test.ts +249 -0
- package/src/__tests__/pack-authoring.unit.test.ts +266 -0
- package/src/__tests__/pack-scanner.unit.test.ts +123 -0
- package/src/__tests__/pack.unit.test.ts +1 -238
- package/src/__tests__/package-finalizer.contract.test.ts +55 -0
- package/src/__tests__/resolve-asset-source.test.ts +15 -98
- package/src/__tests__/scanner-blacklist.test.ts +55 -0
- package/src/__tests__/scanner-inventory.contract.test.ts +11 -14
- package/src/__tests__/scanner-inventory.test.ts +23 -10
- package/src/__tests__/scriptable-pack-cli.integration.test.ts +4 -9
- package/src/__tests__/topology.unit.test.ts +23 -0
- package/src/build.ts +28 -18
- package/src/catalog-builder.ts +60 -33
- package/src/cli-asset.ts +68 -83
- package/src/deriveAssetName.ts +14 -13
- package/src/evidence/material-cook.ts +79 -3
- package/src/evidence/source-inventory.ts +34 -21
- package/src/guid.ts +65 -4
- package/src/index.ts +15 -11
- package/src/inventory/binding.ts +25 -0
- package/src/inventory/declaration.ts +168 -0
- package/src/inventory/index.ts +18 -0
- package/src/inventory/sync.ts +22 -0
- package/src/material-cook.ts +1 -0
- package/src/pack-authoring-node.ts +1966 -0
- package/src/pack-authoring.ts +1478 -0
- package/src/package-finalizer.ts +2 -42
- package/src/resolve-asset-source.ts +4 -76
- package/src/scanner.ts +236 -56
- package/src/schema/material-cook.schema.json +4 -1
- package/src/schema-compiled.ts +2 -0
- package/src/scriptable-pack-node.ts +108 -719
- package/src/scriptable-pack-worker.ts +48 -16
- package/src/scriptable-pack.ts +28 -939
- package/dist/.tsbuildinfo +0 -1
- package/dist/__tests__/load-asset-config.test.d.ts +0 -2
- package/dist/__tests__/load-asset-config.test.d.ts.map +0 -1
- package/dist/__tests__/scriptable-pack.test-d.d.ts +0 -2
- package/dist/__tests__/scriptable-pack.test-d.d.ts.map +0 -1
- package/dist/__tests__/scriptable-pack.unit.test.d.ts +0 -2
- package/dist/__tests__/scriptable-pack.unit.test.d.ts.map +0 -1
- package/dist/config.d.ts +0 -6
- package/dist/config.d.ts.map +0 -1
- package/dist/config.mjs +0 -29
- package/dist/config.mjs.map +0 -1
- package/src/__tests__/load-asset-config.test.ts +0 -121
- package/src/__tests__/scriptable-pack.test-d.ts +0 -131
- package/src/__tests__/scriptable-pack.unit.test.ts +0 -789
- package/src/config.ts +0 -36
package/src/catalog-builder.ts
CHANGED
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
projectExternalCatalogEntries,
|
|
6
6
|
projectPackageCatalog,
|
|
7
7
|
} from './catalog-projection.js';
|
|
8
|
-
import { loadAssetConfig } from './config.js';
|
|
9
8
|
import { deriveAssetName } from './deriveAssetName.js';
|
|
9
|
+
import { parsePackSourceJson, projectDirectPackJson } from './pack-authoring.js';
|
|
10
10
|
import { resolveAssetSource } from './resolve-asset-source.js';
|
|
11
11
|
import {
|
|
12
12
|
type MetaInventoryDocument,
|
|
@@ -138,7 +138,6 @@ function projectMeta(
|
|
|
138
138
|
rawPath: string,
|
|
139
139
|
cwd: string,
|
|
140
140
|
base: string,
|
|
141
|
-
assetPaths: Record<string, string>,
|
|
142
141
|
importerPolicy: (importer: string) => CatalogImporterPolicy,
|
|
143
142
|
sourceDeclaration: Extract<ScanSourceDeclaration, { readonly format: 'meta.json' }>,
|
|
144
143
|
sourceIdentityFor: CatalogBuildProjectionOptions['sourceIdentityFor'],
|
|
@@ -152,22 +151,12 @@ function projectMeta(
|
|
|
152
151
|
if (policy.disposition !== 'publish') {
|
|
153
152
|
return { entries: [], error: unsupportedPolicyError(rawPath, meta.importer, policy) };
|
|
154
153
|
}
|
|
155
|
-
const resolved = resolveAssetSource(rawPath, meta.source
|
|
156
|
-
|
|
157
|
-
return {
|
|
158
|
-
entries: [],
|
|
159
|
-
error: {
|
|
160
|
-
code: 'catalog-meta-schema-invalid',
|
|
161
|
-
path: rawPath,
|
|
162
|
-
message: `source resolution failed: ${resolved.error.code} - ${resolved.error.hint}`,
|
|
163
|
-
},
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
const sourcePath = catalogSourcePathFor(cwd, resolved.value, sourceIdentityFor);
|
|
154
|
+
const resolved = resolveAssetSource(rawPath, meta.source);
|
|
155
|
+
const sourcePath = catalogSourcePathFor(cwd, resolved, sourceIdentityFor);
|
|
167
156
|
const packageUrl = `${base}/__forgeax-ddc/${meta.subAssets[0]?.guid?.toLowerCase() ?? 'pack'}.pack.json`;
|
|
168
157
|
const declaration: CatalogImportMeta = {
|
|
169
158
|
importer: meta.importer,
|
|
170
|
-
source: resolved
|
|
159
|
+
source: resolved,
|
|
171
160
|
sourceRevision: sourceDeclaration.sourceRevision,
|
|
172
161
|
...(meta.packageId === undefined ? {} : { packageId: meta.packageId }),
|
|
173
162
|
...(meta.provenance === undefined ? {} : { provenance: meta.provenance }),
|
|
@@ -197,7 +186,7 @@ function projectMeta(
|
|
|
197
186
|
return {
|
|
198
187
|
declaration,
|
|
199
188
|
entries: projectExternalCatalogEntries(meta, sourcePath, packageUrl, meta.subAssets, (output) =>
|
|
200
|
-
deriveAssetName(resolved
|
|
189
|
+
deriveAssetName(resolved, meta.subAssets.length, output.name),
|
|
201
190
|
),
|
|
202
191
|
};
|
|
203
192
|
}
|
|
@@ -206,7 +195,6 @@ function projectSource(
|
|
|
206
195
|
path: string,
|
|
207
196
|
cwd: string,
|
|
208
197
|
base: string,
|
|
209
|
-
assetPaths: Record<string, string>,
|
|
210
198
|
importerPolicy: (importer: string) => CatalogImporterPolicy,
|
|
211
199
|
visibility: CatalogBuildProjectionOptions['visibility'],
|
|
212
200
|
sourceDeclaration: ScanSourceDeclaration,
|
|
@@ -214,19 +202,10 @@ function projectSource(
|
|
|
214
202
|
sourceIdentityFor: CatalogBuildProjectionOptions['sourceIdentityFor'],
|
|
215
203
|
): { readonly entries: PackIndexEntry[]; readonly error?: CatalogBuildError } {
|
|
216
204
|
if (sourceDeclaration.format === 'pack.ts') {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
: {
|
|
222
|
-
entries: projectExternalCatalogEntries(
|
|
223
|
-
meta,
|
|
224
|
-
catalogSourcePathFor(cwd, path, sourceIdentityFor),
|
|
225
|
-
`${base}/__forgeax-ddc/${anchorGuid}.pack.json`,
|
|
226
|
-
meta.subAssets,
|
|
227
|
-
(output) => deriveAssetName(path, meta.subAssets.length, output.name),
|
|
228
|
-
),
|
|
229
|
-
};
|
|
205
|
+
// A source declaration is an authoring identity and parameter contract,
|
|
206
|
+
// not a materialized output set. Dynamic build owns publication after its
|
|
207
|
+
// fixed-point worklist converges; Catalog scanning must not invent rows.
|
|
208
|
+
return { entries: [] };
|
|
230
209
|
}
|
|
231
210
|
if (sourceDeclaration.format === 'meta.json') {
|
|
232
211
|
const policy = importerPolicy(sourceDeclaration.value.importer);
|
|
@@ -244,7 +223,6 @@ function projectSource(
|
|
|
244
223
|
path,
|
|
245
224
|
cwd,
|
|
246
225
|
base,
|
|
247
|
-
assetPaths,
|
|
248
226
|
importerPolicy,
|
|
249
227
|
sourceDeclaration,
|
|
250
228
|
sourceIdentityFor,
|
|
@@ -254,6 +232,57 @@ function projectSource(
|
|
|
254
232
|
}
|
|
255
233
|
const parsed = sourceDeclaration.value;
|
|
256
234
|
const sourcePath = catalogSourcePathFor(cwd, path, sourceIdentityFor);
|
|
235
|
+
if (parsed.schemaVersion === '3.0.0') {
|
|
236
|
+
const authoring = parsePackSourceJson(parsed);
|
|
237
|
+
if (!authoring.ok) {
|
|
238
|
+
return {
|
|
239
|
+
entries: [],
|
|
240
|
+
error: {
|
|
241
|
+
code: 'catalog-meta-schema-invalid',
|
|
242
|
+
path,
|
|
243
|
+
message: `v3 Pack parse failed: ${authoring.error.code}`,
|
|
244
|
+
expected: 'a valid direct or instance v3 Pack document',
|
|
245
|
+
actual: authoring.error.code,
|
|
246
|
+
hint: authoring.error.hint,
|
|
247
|
+
subjects: [path],
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
if (authoring.value.format !== 'direct') return { entries: [] };
|
|
252
|
+
const projected = projectDirectPackJson(authoring.value);
|
|
253
|
+
if (!projected.ok) {
|
|
254
|
+
return {
|
|
255
|
+
entries: [],
|
|
256
|
+
error: {
|
|
257
|
+
code: 'catalog-meta-schema-invalid',
|
|
258
|
+
path,
|
|
259
|
+
message: `v3 Pack projection failed: ${projected.error.code}`,
|
|
260
|
+
expected: 'direct v3 assets to derive stable AssetGuids',
|
|
261
|
+
actual: projected.error.code,
|
|
262
|
+
hint: projected.error.hint,
|
|
263
|
+
subjects: [path],
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
const provenance = { provider: 'pack', version: '3.0.0' } satisfies ProviderProvenance;
|
|
268
|
+
return {
|
|
269
|
+
entries: projectPackageCatalog(
|
|
270
|
+
projected.value.assets.map((asset, sourceIndex) => ({
|
|
271
|
+
guid: asset.guid,
|
|
272
|
+
kind: asset.kind,
|
|
273
|
+
sourcePath,
|
|
274
|
+
sourceIndex,
|
|
275
|
+
sourceKey: asset.sourceKey,
|
|
276
|
+
refs: asset.refs,
|
|
277
|
+
execution: 'direct' as const,
|
|
278
|
+
packageId: projected.value.packageId,
|
|
279
|
+
provenance,
|
|
280
|
+
name: deriveAssetName(path, projected.value.assets.length, asset.name),
|
|
281
|
+
})),
|
|
282
|
+
`${base}/${sourcePath}`,
|
|
283
|
+
),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
257
286
|
const provenance =
|
|
258
287
|
parsed.provenance ??
|
|
259
288
|
({ provider: 'pack', version: parsed.schemaVersion } satisfies ProviderProvenance);
|
|
@@ -317,7 +346,6 @@ export async function buildCatalogProjection(
|
|
|
317
346
|
return empty('degraded', [error]);
|
|
318
347
|
}
|
|
319
348
|
const { paths, declarations: sourceDeclarations } = scanned.value;
|
|
320
|
-
const { paths: assetPaths } = loadAssetConfig(cwd);
|
|
321
349
|
const entries: PackIndexEntry[] = [];
|
|
322
350
|
const errors: CatalogBuildError[] = [];
|
|
323
351
|
const declarations = new Map<string, CatalogImportMeta>();
|
|
@@ -328,7 +356,6 @@ export async function buildCatalogProjection(
|
|
|
328
356
|
path,
|
|
329
357
|
cwd,
|
|
330
358
|
base,
|
|
331
|
-
assetPaths,
|
|
332
359
|
options.importerPolicy,
|
|
333
360
|
options.visibility,
|
|
334
361
|
sourceDeclaration,
|
package/src/cli-asset.ts
CHANGED
|
@@ -26,8 +26,8 @@ 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 {
|
|
30
|
-
import {
|
|
29
|
+
import { projectScriptablePackMeta } from './pack-authoring.js';
|
|
30
|
+
import { type ScanSourceDeclaration, scanInventory } from './scanner.js';
|
|
31
31
|
import { loadScriptablePack } from './scriptable-pack-node.js';
|
|
32
32
|
|
|
33
33
|
export interface PackEntry {
|
|
@@ -51,6 +51,10 @@ interface ScanErrShape {
|
|
|
51
51
|
detail?: unknown;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
55
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
56
|
+
}
|
|
57
|
+
|
|
54
58
|
function emitError(ctx: AssetCtx, err: ScanErrShape): number {
|
|
55
59
|
const payload: Record<string, unknown> = {
|
|
56
60
|
code: err.code,
|
|
@@ -66,7 +70,7 @@ export async function scanEntries(
|
|
|
66
70
|
roots: readonly string[],
|
|
67
71
|
ctx: AssetCtx,
|
|
68
72
|
): Promise<{ ok: true; value: PackEntry[] } | { ok: false }> {
|
|
69
|
-
const result = await
|
|
73
|
+
const result = await scanInventory(roots);
|
|
70
74
|
if (!result.ok) {
|
|
71
75
|
emitError(ctx, {
|
|
72
76
|
code: result.error.code,
|
|
@@ -77,94 +81,73 @@ export async function scanEntries(
|
|
|
77
81
|
return { ok: false };
|
|
78
82
|
}
|
|
79
83
|
const entries: PackEntry[] = [];
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const packPaths = result.value.filter((p) => p.endsWith('.pack.json'));
|
|
90
|
-
const metaPaths = result.value.filter((p) => p.endsWith('.meta.json'));
|
|
91
|
-
for (const packPath of packPaths) {
|
|
92
|
-
let parsed: unknown;
|
|
93
|
-
try {
|
|
94
|
-
const raw = await readFile(packPath, 'utf-8');
|
|
95
|
-
parsed = JSON.parse(raw);
|
|
96
|
-
} catch {
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
const packObj = parsed as { assets?: { guid?: unknown; kind?: unknown; name?: unknown }[] };
|
|
100
|
-
if (!Array.isArray(packObj.assets)) continue;
|
|
101
|
-
for (const asset of packObj.assets) {
|
|
102
|
-
if (typeof asset.guid === 'string' && typeof asset.kind === 'string') {
|
|
103
|
-
entries.push({
|
|
104
|
-
guid: asset.guid,
|
|
105
|
-
kind: asset.kind,
|
|
106
|
-
sourcePath: packPath,
|
|
107
|
-
...(typeof asset.name === 'string' ? { name: asset.name } : {}),
|
|
108
|
-
});
|
|
84
|
+
// The scanner is the single identity projection for Pack JSON. ScriptablePack
|
|
85
|
+
// source modules deliberately contribute no rows until a build generation.
|
|
86
|
+
for (const declaration of result.value.declarations.values()) {
|
|
87
|
+
if (declaration.format !== 'meta.json') continue;
|
|
88
|
+
const sourcePath = declaration.sourcePath;
|
|
89
|
+
const assets = declaration.value.subAssets;
|
|
90
|
+
for (const asset of assets) {
|
|
91
|
+
if (!isRecord(asset) || typeof asset.guid !== 'string' || typeof asset.kind !== 'string') {
|
|
92
|
+
continue;
|
|
109
93
|
}
|
|
94
|
+
const name =
|
|
95
|
+
typeof asset.name === 'string'
|
|
96
|
+
? asset.name
|
|
97
|
+
: typeof asset.sourceKey === 'string'
|
|
98
|
+
? asset.sourceKey
|
|
99
|
+
: undefined;
|
|
100
|
+
entries.push({
|
|
101
|
+
guid: asset.guid,
|
|
102
|
+
kind: asset.kind,
|
|
103
|
+
sourcePath,
|
|
104
|
+
...(name === undefined ? {} : { name }),
|
|
105
|
+
});
|
|
110
106
|
}
|
|
111
107
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
108
|
+
const names = new Map<string, string>();
|
|
109
|
+
for (const declaration of result.value.declarations.values()) {
|
|
110
|
+
if (declaration.format !== 'pack.json') continue;
|
|
111
|
+
if (declaration.value.schemaVersion === '3.0.0') {
|
|
112
|
+
for (const [sourceKey, asset] of Object.entries(declaration.value.assets ?? {})) {
|
|
113
|
+
if (isRecord(asset) && typeof asset.name === 'string') {
|
|
114
|
+
names.set(`${declaration.sourcePath}:${sourceKey}`, asset.name);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
118
117
|
continue;
|
|
119
118
|
}
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (!Array.isArray(metaObj.subAssets)) continue;
|
|
124
|
-
for (const sub of metaObj.subAssets) {
|
|
125
|
-
if (typeof sub.guid === 'string' && typeof sub.kind === 'string') {
|
|
126
|
-
const name =
|
|
127
|
-
typeof sub.name === 'string'
|
|
128
|
-
? sub.name
|
|
129
|
-
: typeof sub.sourceKey === 'string'
|
|
130
|
-
? sub.sourceKey
|
|
131
|
-
: undefined;
|
|
132
|
-
entries.push({
|
|
133
|
-
guid: sub.guid,
|
|
134
|
-
kind: sub.kind,
|
|
135
|
-
sourcePath: metaPath,
|
|
136
|
-
...(name === undefined ? {} : { name }),
|
|
137
|
-
});
|
|
119
|
+
for (const asset of declaration.value.assets) {
|
|
120
|
+
if (typeof asset.name === 'string') {
|
|
121
|
+
names.set(`${declaration.sourcePath}:${asset.guid.toLowerCase()}`, asset.name);
|
|
138
122
|
}
|
|
139
123
|
}
|
|
140
124
|
}
|
|
141
|
-
for (const
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
});
|
|
164
|
-
return { ok: false };
|
|
165
|
-
}
|
|
166
|
-
firstByGuid.set(guid, entry);
|
|
125
|
+
for (const inventoryEntry of result.value.inventory) {
|
|
126
|
+
const declaration = result.value.declarations.get(inventoryEntry.sourcePath) as
|
|
127
|
+
| ScanSourceDeclaration
|
|
128
|
+
| undefined;
|
|
129
|
+
const namedByKey =
|
|
130
|
+
inventoryEntry.sourceKey === undefined
|
|
131
|
+
? names.get(`${inventoryEntry.sourcePath}:${inventoryEntry.guid.toLowerCase()}`)
|
|
132
|
+
: names.get(`${inventoryEntry.sourcePath}:${inventoryEntry.sourceKey}`);
|
|
133
|
+
const name =
|
|
134
|
+
namedByKey ?? names.get(`${inventoryEntry.sourcePath}:${inventoryEntry.guid.toLowerCase()}`);
|
|
135
|
+
entries.push({
|
|
136
|
+
guid: inventoryEntry.guid,
|
|
137
|
+
kind: inventoryEntry.kind,
|
|
138
|
+
sourcePath: inventoryEntry.sourcePath,
|
|
139
|
+
...(name === undefined ? {} : { name }),
|
|
140
|
+
...(declaration?.format === 'pack.json' &&
|
|
141
|
+
declaration.value.schemaVersion === '3.0.0' &&
|
|
142
|
+
inventoryEntry.sourceKey !== undefined &&
|
|
143
|
+
name === undefined
|
|
144
|
+
? { name: inventoryEntry.sourceKey }
|
|
145
|
+
: {}),
|
|
146
|
+
});
|
|
167
147
|
}
|
|
148
|
+
entries.sort((left, right) =>
|
|
149
|
+
`${left.sourcePath}:${left.guid}`.localeCompare(`${right.sourcePath}:${right.guid}`),
|
|
150
|
+
);
|
|
168
151
|
return { ok: true, value: entries };
|
|
169
152
|
}
|
|
170
153
|
|
|
@@ -250,7 +233,9 @@ async function runMeta(rest: string[], ctx: AssetCtx): Promise<number> {
|
|
|
250
233
|
});
|
|
251
234
|
}
|
|
252
235
|
const cwd = ctx.cwd ?? process.cwd();
|
|
253
|
-
const loaded = await loadScriptablePack(absolutePath(source, cwd), {
|
|
236
|
+
const loaded = await loadScriptablePack(absolutePath(source, cwd), {
|
|
237
|
+
metadataOnly: true,
|
|
238
|
+
});
|
|
254
239
|
if (!loaded.ok) return emitError(ctx, loaded.error);
|
|
255
240
|
ctx.stdoutWrite(JSON.stringify(projectScriptablePackMeta(loaded.value, source)));
|
|
256
241
|
return 0;
|
package/src/deriveAssetName.ts
CHANGED
|
@@ -16,27 +16,28 @@ function baseName(p: string): string {
|
|
|
16
16
|
*
|
|
17
17
|
* Pure function -- zero IO, zero side effects, browser-safe. Build-time
|
|
18
18
|
* (build-catalog) and runtime (resolveName) share this single source of truth
|
|
19
|
-
* for
|
|
19
|
+
* for deterministic asset display-name resolution.
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
21
|
+
* Resolution contract:
|
|
22
|
+
* - an explicit storedName is authoritative;
|
|
23
|
+
* - a package path is the fallback when no storedName is present;
|
|
24
|
+
* - an asset without a package and without a storedName resolves to ''.
|
|
25
|
+
*
|
|
26
|
+
* `assetCount` remains in the signature because callers derive it from their
|
|
27
|
+
* package model, but it must not erase an explicitly authored name. In
|
|
28
|
+
* particular, a single authored entry in `Materials.pack.json` resolves to its
|
|
29
|
+
* entry name rather than temporarily appearing as the package basename.
|
|
26
30
|
*/
|
|
27
31
|
export function deriveAssetName(
|
|
28
32
|
packagePath: string | null,
|
|
29
|
-
|
|
33
|
+
_assetCount: number,
|
|
30
34
|
storedName?: string,
|
|
31
35
|
): string {
|
|
32
|
-
if (packagePath === null) {
|
|
33
|
-
return storedName ?? '';
|
|
34
|
-
}
|
|
35
|
-
if (assetCount === 1) {
|
|
36
|
-
return baseName(packagePath);
|
|
37
|
-
}
|
|
38
36
|
if (storedName !== undefined) {
|
|
39
37
|
return storedName;
|
|
40
38
|
}
|
|
39
|
+
if (packagePath === null) {
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
41
42
|
return baseName(packagePath);
|
|
42
43
|
}
|
|
@@ -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,9 +2,8 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { readdir, readFile } from 'node:fs/promises';
|
|
3
3
|
import { basename, dirname, join, relative, resolve, sep } from 'node:path';
|
|
4
4
|
import type { SourceDeclarationEvidence } from '@forgeax/engine-types';
|
|
5
|
+
import { parsePackSourceJson, projectDirectPackJson } from '../pack-authoring.js';
|
|
5
6
|
import { SCANNER_BLACKLIST, scan } from '../scanner.js';
|
|
6
|
-
import { projectScriptablePackMeta } from '../scriptable-pack.js';
|
|
7
|
-
import { loadScriptablePack } from '../scriptable-pack-node.js';
|
|
8
7
|
|
|
9
8
|
/** Project root and GUID used to locate an authoritative source declaration. */
|
|
10
9
|
export interface SourceInventoryRequest {
|
|
@@ -150,34 +149,43 @@ export async function readSourceInventory(
|
|
|
150
149
|
let authored: SourceDeclarationEvidence | undefined;
|
|
151
150
|
for (const path of paths) {
|
|
152
151
|
if (path.endsWith('.pack.ts')) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (meta.subAssets.some((asset) => asset.guid.toLowerCase() === guid)) {
|
|
157
|
-
return { origin: 'sourceMeta', sourcePath: path };
|
|
158
|
-
}
|
|
152
|
+
// ScriptablePack source identity is a build result, not a declaration
|
|
153
|
+
// table. The source index therefore records the source only after the
|
|
154
|
+
// producer publishes its materialized Pack output.
|
|
159
155
|
continue;
|
|
160
156
|
}
|
|
161
157
|
const raw = await readFile(path, 'utf8').catch(() => undefined);
|
|
162
158
|
if (raw === undefined) continue;
|
|
163
|
-
let parsed:
|
|
164
|
-
readonly assets?: readonly { readonly guid?: unknown }[];
|
|
165
|
-
readonly source?: unknown;
|
|
166
|
-
readonly inputFingerprint?: unknown;
|
|
167
|
-
readonly importSettings?: unknown;
|
|
168
|
-
readonly subAssets?: readonly { readonly guid?: unknown }[];
|
|
169
|
-
};
|
|
159
|
+
let parsed: Record<string, unknown>;
|
|
170
160
|
try {
|
|
171
|
-
|
|
161
|
+
const value: unknown = JSON.parse(raw);
|
|
162
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) continue;
|
|
163
|
+
parsed = value as Record<string, unknown>;
|
|
172
164
|
} catch {
|
|
173
165
|
continue;
|
|
174
166
|
}
|
|
175
167
|
if (path.endsWith('.pack.json')) {
|
|
176
|
-
if (
|
|
177
|
-
parsed
|
|
168
|
+
if (parsed.schemaVersion === '3.0.0') {
|
|
169
|
+
const direct = parsePackSourceJson(parsed);
|
|
170
|
+
if (direct.ok && direct.value.format === 'direct') {
|
|
171
|
+
const projected = projectDirectPackJson(direct.value);
|
|
172
|
+
if (
|
|
173
|
+
projected.ok &&
|
|
174
|
+
projected.value.assets.some((asset) => asset.guid.toLowerCase() === guid)
|
|
175
|
+
) {
|
|
176
|
+
authored = { origin: 'authoredPack', sourcePath: path };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
} else if (
|
|
180
|
+
Array.isArray(parsed.assets) &&
|
|
181
|
+
parsed.assets.some(
|
|
178
182
|
(asset) =>
|
|
179
|
-
|
|
180
|
-
|
|
183
|
+
asset !== null &&
|
|
184
|
+
typeof asset === 'object' &&
|
|
185
|
+
!Array.isArray(asset) &&
|
|
186
|
+
typeof (asset as { readonly guid?: unknown }).guid === 'string' &&
|
|
187
|
+
((asset as { readonly guid: string }).guid === request.guid ||
|
|
188
|
+
(asset as { readonly guid: string }).guid.toLowerCase() === guid),
|
|
181
189
|
)
|
|
182
190
|
) {
|
|
183
191
|
authored = { origin: 'authoredPack', sourcePath: path };
|
|
@@ -185,7 +193,12 @@ export async function readSourceInventory(
|
|
|
185
193
|
continue;
|
|
186
194
|
}
|
|
187
195
|
if (path.endsWith('.meta.json')) {
|
|
188
|
-
const source = sourceFromMeta(
|
|
196
|
+
const source = sourceFromMeta(
|
|
197
|
+
path,
|
|
198
|
+
parsed as Parameters<typeof sourceFromMeta>[1],
|
|
199
|
+
request.guid,
|
|
200
|
+
raw,
|
|
201
|
+
);
|
|
189
202
|
if (source !== undefined) return source;
|
|
190
203
|
}
|
|
191
204
|
}
|