@forgeax/engine-import 0.1.20 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +91 -3
  2. package/dist/__tests__/ies-contract.unit.test.d.ts +2 -0
  3. package/dist/__tests__/ies-contract.unit.test.d.ts.map +1 -0
  4. package/dist/__tests__/ies-producer.unit.test.d.ts +2 -0
  5. package/dist/__tests__/ies-producer.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/mesh-lod-contract.unit.test.d.ts +2 -0
  7. package/dist/__tests__/mesh-lod-contract.unit.test.d.ts.map +1 -0
  8. package/dist/__tests__/mesh-lod-reimport.integration.test.d.ts +2 -0
  9. package/dist/__tests__/mesh-lod-reimport.integration.test.d.ts.map +1 -0
  10. package/dist/__tests__/parameterized-scriptable-pack.unit.test.d.ts +2 -0
  11. package/dist/__tests__/parameterized-scriptable-pack.unit.test.d.ts.map +1 -0
  12. package/dist/__tests__/scriptable-pack-file-snapshot.unit.test.d.ts +2 -0
  13. package/dist/__tests__/scriptable-pack-file-snapshot.unit.test.d.ts.map +1 -0
  14. package/dist/__tests__/source-package-publication-lod.integration.test.d.ts +2 -0
  15. package/dist/__tests__/source-package-publication-lod.integration.test.d.ts.map +1 -0
  16. package/dist/browser.d.ts +1 -0
  17. package/dist/browser.d.ts.map +1 -1
  18. package/dist/browser.mjs +230 -3
  19. package/dist/browser.mjs.map +1 -1
  20. package/dist/build-production.d.ts +1 -8
  21. package/dist/build-production.d.ts.map +1 -1
  22. package/dist/ies/ies-importer.d.ts +4 -0
  23. package/dist/ies/ies-importer.d.ts.map +1 -0
  24. package/dist/ies/parse-lm63.d.ts +13 -0
  25. package/dist/ies/parse-lm63.d.ts.map +1 -0
  26. package/dist/ies/resample-type-c.d.ts +4 -0
  27. package/dist/ies/resample-type-c.d.ts.map +1 -0
  28. package/dist/import-runner.d.ts.map +1 -1
  29. package/dist/index.d.ts +11 -6
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.mjs +2550 -1029
  32. package/dist/index.mjs.map +1 -1
  33. package/dist/mesh-bin.d.ts.map +1 -1
  34. package/dist/mesh-bin.mjs +29 -1
  35. package/dist/mesh-bin.mjs.map +1 -1
  36. package/dist/mesh-lod.d.ts +60 -0
  37. package/dist/mesh-lod.d.ts.map +1 -0
  38. package/dist/parameterized-scriptable-pack.d.ts +68 -0
  39. package/dist/parameterized-scriptable-pack.d.ts.map +1 -0
  40. package/dist/scriptable-pack-file-snapshot.d.ts +71 -0
  41. package/dist/scriptable-pack-file-snapshot.d.ts.map +1 -0
  42. package/dist/scriptable-pack-host.d.ts +70 -39
  43. package/dist/scriptable-pack-host.d.ts.map +1 -1
  44. package/dist/scriptable-pack-output-producers.d.ts +1 -0
  45. package/dist/scriptable-pack-output-producers.d.ts.map +1 -1
  46. package/dist/scriptable-pack-staged-snapshot.d.ts +2 -2
  47. package/dist/scriptable-pack-staged-snapshot.d.ts.map +1 -1
  48. package/dist/scriptable-pack.d.ts +13 -26
  49. package/dist/scriptable-pack.d.ts.map +1 -1
  50. package/dist/source-package-errors.d.ts.map +1 -1
  51. package/dist/source-package-publication.d.ts.map +1 -1
  52. package/package.json +8 -7
  53. package/src/__tests__/ies-contract.unit.test.ts +31 -0
  54. package/src/__tests__/ies-producer.unit.test.ts +104 -0
  55. package/src/__tests__/mesh-bin.test.ts +69 -0
  56. package/src/__tests__/mesh-lod-contract.unit.test.ts +92 -0
  57. package/src/__tests__/mesh-lod-reimport.integration.test.ts +15 -0
  58. package/src/__tests__/parameterized-scriptable-pack.unit.test.ts +270 -0
  59. package/src/__tests__/scriptable-pack-file-snapshot.unit.test.ts +177 -0
  60. package/src/__tests__/scriptable-pack-host.unit.test.ts +196 -4
  61. package/src/__tests__/scriptable-pack-production-producers.unit.test.ts +7 -1
  62. package/src/__tests__/scriptable-pack-staged-snapshot.unit.test.ts +43 -12
  63. package/src/__tests__/source-package-publication-lod.integration.test.ts +29 -0
  64. package/src/__tests__/source-package-publication.integration.test.ts +26 -1
  65. package/src/browser.ts +5 -0
  66. package/src/build-production.ts +498 -141
  67. package/src/ies/ies-importer.ts +126 -0
  68. package/src/ies/parse-lm63.ts +98 -0
  69. package/src/ies/resample-type-c.ts +108 -0
  70. package/src/import-runner.ts +58 -0
  71. package/src/index.ts +49 -19
  72. package/src/mesh-bin.ts +38 -0
  73. package/src/mesh-lod.ts +265 -0
  74. package/src/parameterized-scriptable-pack.ts +725 -0
  75. package/src/scriptable-pack-file-snapshot.ts +346 -0
  76. package/src/scriptable-pack-host.ts +790 -343
  77. package/src/scriptable-pack-output-producers.ts +58 -1
  78. package/src/scriptable-pack-staged-snapshot.ts +12 -11
  79. package/src/scriptable-pack.ts +16 -485
  80. package/src/source-package-errors.ts +6 -0
  81. package/src/source-package-publication.ts +32 -2
  82. package/dist/.tsbuildinfo +0 -1
  83. package/dist/__tests__/scriptable-pack-product.contract.test.d.ts +0 -2
  84. package/dist/__tests__/scriptable-pack-product.contract.test.d.ts.map +0 -1
  85. package/dist/__tests__/scriptable-pack.integration.test.d.ts +0 -2
  86. package/dist/__tests__/scriptable-pack.integration.test.d.ts.map +0 -1
  87. package/dist/scriptable-source-package.d.ts +0 -14
  88. package/dist/scriptable-source-package.d.ts.map +0 -1
  89. package/src/__tests__/scriptable-pack-product.contract.test.ts +0 -46
  90. package/src/__tests__/scriptable-pack.integration.test.ts +0 -435
  91. package/src/scriptable-source-package.ts +0 -88
@@ -1,18 +1,27 @@
1
- import { resolve } from 'node:path';
1
+ import { isAbsolute, relative, resolve } from 'node:path';
2
2
  import {
3
3
  type CatalogBuildResult,
4
4
  createRuntimePackPublication,
5
5
  finalizePackageTransportSource,
6
- loadAssetConfig,
7
6
  metaPathForGuid,
8
- projectCookedPackageEntry,
7
+ projectPackageCatalog,
9
8
  } from '@forgeax/engine-pack/build';
9
+ import { AssetGuid } from '@forgeax/engine-pack/guid';
10
10
  import type { NativeCooker } from '@forgeax/engine-pack/native-cooker';
11
+ import type { LegacyPackInventoryDocument } from '@forgeax/engine-pack/scanner';
12
+ import {
13
+ PackageId,
14
+ parseParameterizedPackJson,
15
+ projectDirectPackJson,
16
+ resolvePackParameterInheritance,
17
+ } from '@forgeax/engine-pack/source';
18
+ import { loadParameterizedScriptablePack } from '@forgeax/engine-pack/source-node';
11
19
  import type {
12
20
  AssetPublicationEnvelope,
21
+ AssetPublicationOutput,
22
+ CookReceipt,
13
23
  Importer,
14
24
  PackIndexEntry,
15
- ResourceRevision,
16
25
  RuntimeAssetBinding,
17
26
  } from '@forgeax/engine-types';
18
27
  import type { ImportRunnerFs } from './import-runner.js';
@@ -21,10 +30,11 @@ import { projectImportProductForBuild } from './pack-projection.js';
21
30
  import {
22
31
  canonicalScriptableSourcePath,
23
32
  declaredPackExternalOutputs,
24
- materializePreparedScriptablePack,
25
- prepareAuthoredPackTransport,
26
- produceScriptablePackProducts,
27
- scriptablePackInputs,
33
+ type ParameterizedScriptablePackInput,
34
+ type PreparedParameterizedScriptablePack,
35
+ prepareDirectPackTransport,
36
+ prepareLegacyPackTransport,
37
+ produceParameterizedScriptablePackProducts,
28
38
  } from './scriptable-pack-host.js';
29
39
  import {
30
40
  finalizeSourcePackage,
@@ -71,14 +81,6 @@ export interface BuildProductionOptions {
71
81
  readonly fail: (failure: BuildProductionFailure) => Error;
72
82
  }
73
83
 
74
- export interface ScriptableBuildPackage {
75
- readonly packageUrl: string;
76
- readonly receiptUrls: ReadonlyMap<string, string>;
77
- readonly refs: ReadonlyMap<string, readonly string[]>;
78
- readonly revision: ResourceRevision;
79
- readonly publication: AssetPublicationEnvelope;
80
- }
81
-
82
84
  function scriptableArtifactPath(guid: string, key: string): string {
83
85
  return `${guid.toLowerCase()}/${key.includes('.') ? key : `${key}.bin`}`;
84
86
  }
@@ -91,114 +93,327 @@ function runtimePublicationFor(
91
93
  readonly sourceRevision: string;
92
94
  readonly packageUrl: string;
93
95
  readonly digest?: string;
96
+ readonly inputFingerprint?: string;
97
+ /** Preserve producer-declared semantic keys on generic publication output rows. */
98
+ readonly sourceKeys?: ReadonlyMap<string, string>;
99
+ readonly outputs?: readonly AssetPublicationOutput[];
100
+ readonly externalEvidence?: readonly import('@forgeax/engine-types').AssetPublicationExternalEvidence[];
94
101
  },
95
102
  ) {
96
- return createRuntimePackPublication({
103
+ const publicationInput = {
97
104
  pack: input.pack,
98
105
  scopeId: context.runtimeBinding?.scopeId ?? 'asset-runtime',
99
106
  sourcePath: input.sourcePath,
100
107
  sourceRevision: input.sourceRevision,
101
108
  packageUrl: input.packageUrl,
102
109
  ...(input.digest === undefined ? {} : { digest: input.digest }),
110
+ ...(input.inputFingerprint === undefined ? {} : { inputFingerprint: input.inputFingerprint }),
111
+ ...(input.outputs === undefined ? {} : { outputs: input.outputs }),
112
+ ...(input.externalEvidence === undefined ? {} : { externalEvidence: input.externalEvidence }),
103
113
  generation: context.runtimeBinding?.generation ?? context.generation,
114
+ };
115
+ if (input.sourceKeys === undefined) return createRuntimePackPublication(publicationInput);
116
+ const derived = createRuntimePackPublication(publicationInput);
117
+ const outputs = derived.publication.outputs.map((output) => ({
118
+ ...output,
119
+ sourceKey: input.sourceKeys?.get(output.guid.toLowerCase()) ?? output.sourceKey,
120
+ }));
121
+ return createRuntimePackPublication({ ...publicationInput, outputs });
122
+ }
123
+
124
+ function sourceKeysFor(
125
+ declarations: readonly { readonly guid: string; readonly sourceKey?: string }[],
126
+ ): ReadonlyMap<string, string> {
127
+ return new Map(
128
+ declarations.flatMap((declaration) =>
129
+ declaration.sourceKey === undefined
130
+ ? []
131
+ : [[declaration.guid.toLowerCase(), declaration.sourceKey] as const],
132
+ ),
133
+ );
134
+ }
135
+
136
+ interface ParameterizedBuildBundle {
137
+ readonly input: ParameterizedScriptablePackInput;
138
+ readonly prepared: PreparedParameterizedScriptablePack;
139
+ readonly entries: readonly PackIndexEntry[];
140
+ }
141
+
142
+ type ParameterizedSourceSubject = {
143
+ readonly kind: 'source';
144
+ readonly packageId: PackageId;
145
+ readonly sourcePath: string;
146
+ readonly definition: import('@forgeax/engine-pack/source').AnyParameterizedPackDefinition;
147
+ readonly sourceClosure: readonly import('@forgeax/engine-pack/source').ScriptablePackSourceClosureEntry[];
148
+ };
149
+
150
+ type ParameterizedInstanceSubject = {
151
+ readonly kind: 'instance';
152
+ readonly packageId: PackageId;
153
+ readonly parent: PackageId;
154
+ readonly values: Readonly<Record<string, unknown>>;
155
+ readonly sourcePath: string;
156
+ };
157
+
158
+ type ParameterizedDirectSubject = {
159
+ readonly kind: 'direct';
160
+ readonly packageId: PackageId;
161
+ readonly sourcePath: string;
162
+ };
163
+
164
+ type ParameterizedSourceIndexSubject =
165
+ | ParameterizedSourceSubject
166
+ | ParameterizedInstanceSubject
167
+ | ParameterizedDirectSubject;
168
+
169
+ function dynamicFailure(context: BuildProductionOptions, value: unknown): never {
170
+ const candidate = value !== null && typeof value === 'object' ? value : {};
171
+ const error = candidate as {
172
+ readonly code?: unknown;
173
+ readonly expected?: unknown;
174
+ readonly hint?: unknown;
175
+ readonly detail?: unknown;
176
+ };
177
+ throw context.fail({
178
+ code: typeof error.code === 'string' ? error.code : 'parameterized-pack-build-failed',
179
+ expected:
180
+ typeof error.expected === 'string'
181
+ ? error.expected
182
+ : 'the parameterized Pack generation to produce a valid terminal result',
183
+ hint:
184
+ typeof error.hint === 'string'
185
+ ? error.hint
186
+ : 'inspect the parameterized Pack subject and rebuild the current generation',
187
+ ...(error.detail === undefined ? {} : { detail: error.detail }),
104
188
  });
105
189
  }
106
190
 
107
- async function buildAuthoredPackages(
191
+ function parameterizedCatalogSourcePath(
108
192
  context: BuildProductionOptions,
109
- ): Promise<ReadonlyMap<string, ScriptableBuildPackage>> {
110
- const scriptableSources = [...context.inventory.sourceDeclarations.entries()]
111
- .filter(([, declaration]) => declaration.format === 'pack.ts')
112
- .map(([sourcePath]) => sourcePath);
113
- if (scriptableSources.length === 0) return new Map();
114
- const inputs = scriptablePackInputs(
115
- scriptableSources,
116
- context.inventory.sourceDeclarations,
117
- context.cwd,
118
- () => context.generation,
119
- () => (product) => ({
120
- base: context.basePrefix === '' ? '/' : context.basePrefix,
121
- packagePath: `assets/${product.anchorGuid}.pack.json`,
122
- artifactPath: scriptableArtifactPath,
123
- }),
124
- context.fsForImport.sourceIdentityFor,
193
+ sourcePath: string,
194
+ ): string {
195
+ const projected = context.fsForImport.sourceIdentityFor?.(sourcePath);
196
+ const logical =
197
+ projected === undefined || isAbsolute(projected)
198
+ ? relative(context.cwd, sourcePath)
199
+ : projected;
200
+ return canonicalScriptableSourcePath(logical).replaceAll('\\', '/');
201
+ }
202
+
203
+ /** Build parameterized roots and JSON instances before normal Build emission. */
204
+ async function buildParameterizedPackages(
205
+ context: BuildProductionOptions,
206
+ ): Promise<readonly ParameterizedBuildBundle[]> {
207
+ const subjects = new Map<string, ParameterizedSourceIndexSubject>();
208
+ for (const [sourcePath, declaration] of context.inventory.sourceDeclarations) {
209
+ if (declaration.format === 'pack.ts-parameterized') {
210
+ const key = PackageId.format(declaration.definition.packageId).toLowerCase();
211
+ subjects.set(key, {
212
+ kind: 'source',
213
+ packageId: declaration.definition.packageId,
214
+ sourcePath,
215
+ definition: declaration.definition,
216
+ sourceClosure: declaration.sourceClosure,
217
+ });
218
+ continue;
219
+ }
220
+ if (declaration.format !== 'pack.json' || declaration.value.schemaVersion !== '3.0.0') {
221
+ continue;
222
+ }
223
+ const parsed = parseParameterizedPackJson(declaration.value);
224
+ if (!parsed.ok) dynamicFailure(context, parsed.error);
225
+ if (parsed.value.format === 'direct') {
226
+ subjects.set(PackageId.format(parsed.value.packageId).toLowerCase(), {
227
+ kind: 'direct',
228
+ packageId: parsed.value.packageId,
229
+ sourcePath,
230
+ });
231
+ } else {
232
+ subjects.set(PackageId.format(parsed.value.packageId).toLowerCase(), {
233
+ kind: 'instance',
234
+ packageId: parsed.value.packageId,
235
+ parent: parsed.value.parent,
236
+ values: parsed.value.values,
237
+ sourcePath,
238
+ });
239
+ }
240
+ }
241
+ const sourceSubject = (packageId: PackageId): ParameterizedSourceSubject | undefined => {
242
+ const subject = subjects.get(PackageId.format(packageId).toLowerCase());
243
+ return subject?.kind === 'source' ? subject : undefined;
244
+ };
245
+ const readSubject = async (packageId: PackageId) => {
246
+ const subject = subjects.get(PackageId.format(packageId).toLowerCase());
247
+ if (subject === undefined) return undefined;
248
+ if (subject.kind === 'source') {
249
+ return {
250
+ format: 'source' as const,
251
+ packageId: subject.packageId,
252
+ parameters: 'parameters' in subject.definition ? subject.definition.parameters : [],
253
+ } satisfies import('@forgeax/engine-pack/source').PackParameterRootSubject;
254
+ }
255
+ if (subject.kind === 'instance') {
256
+ return {
257
+ format: 'instance' as const,
258
+ packageId: subject.packageId,
259
+ parent: subject.parent,
260
+ values: subject.values,
261
+ } satisfies import('@forgeax/engine-pack/source').PackParameterInstanceSubject;
262
+ }
263
+ return {
264
+ format: 'direct' as const,
265
+ packageId: subject.packageId,
266
+ } satisfies import('@forgeax/engine-pack/source').PackDirectSubject;
267
+ };
268
+
269
+ const orderedSubjects = [...subjects.values()].sort((left, right) =>
270
+ left.sourcePath.localeCompare(right.sourcePath),
125
271
  );
126
- const preparedResult = await produceScriptablePackProducts(
127
- inputs,
128
- await declaredPackExternalOutputs(
129
- context.inventory.sourceDeclarations,
130
- context.cookers,
131
- inputs.flatMap((input) => Object.values(input.definition.externalAssets)),
272
+ const inputs: ParameterizedScriptablePackInput[] = [];
273
+ for (const subject of orderedSubjects) {
274
+ if (subject.kind === 'source') {
275
+ const packageId = PackageId.format(subject.packageId);
276
+ const loaded = await loadParameterizedScriptablePack(subject.sourcePath);
277
+ if (!loaded.ok) dynamicFailure(context, loaded.error);
278
+ if (PackageId.format(loaded.value.packageId) !== packageId) {
279
+ dynamicFailure(context, {
280
+ code: 'pack-source-revision-conflict',
281
+ expected: 'the parameterized source packageId to remain fixed during production',
282
+ hint: 'retry after source writes settle and rebuild the current generation',
283
+ detail: {
284
+ sourcePath: subject.sourcePath,
285
+ scannedPackageId: packageId,
286
+ loadedPackageId: PackageId.format(loaded.value.packageId),
287
+ },
288
+ });
289
+ }
290
+ inputs.push({
291
+ sourcePath: subject.sourcePath,
292
+ displaySourcePath: parameterizedCatalogSourcePath(context, subject.sourcePath),
293
+ definition: loaded.value,
294
+ sourceClosure: subject.sourceClosure,
295
+ publicationGeneration: context.generation,
296
+ policy: {
297
+ base: context.basePrefix === '' ? '/' : context.basePrefix,
298
+ packagePath: `assets/${packageId}.pack.json`,
299
+ artifactPath: scriptableArtifactPath,
300
+ },
301
+ });
302
+ }
303
+ }
304
+ for (const subject of orderedSubjects) {
305
+ if (subject.kind !== 'instance') continue;
306
+ const resolved = await resolvePackParameterInheritance(
132
307
  {
133
- importerRegistry: context.importerRegistry,
134
- fsForImport: context.fsForImport,
135
- assetPaths: loadAssetConfig(context.cwd).paths,
308
+ format: 'instance',
309
+ packageId: subject.packageId,
310
+ parent: subject.parent,
311
+ values: subject.values,
136
312
  },
137
- ),
313
+ readSubject,
314
+ );
315
+ if (!resolved.ok) dynamicFailure(context, resolved.error);
316
+ const root = sourceSubject(resolved.value.rootPackageId);
317
+ if (root === undefined) {
318
+ dynamicFailure(context, {
319
+ code: 'pack-parent-not-parameterized',
320
+ expected: 'the instance parent chain to terminate at a parameterized source',
321
+ hint: 'point the instance at a parameterized pack.ts source',
322
+ detail: { packageId: PackageId.format(subject.packageId) },
323
+ });
324
+ }
325
+ const loaded = await loadParameterizedScriptablePack(root.sourcePath);
326
+ if (!loaded.ok) dynamicFailure(context, loaded.error);
327
+ if (PackageId.format(loaded.value.packageId) !== PackageId.format(root.packageId)) {
328
+ dynamicFailure(context, {
329
+ code: 'pack-source-revision-conflict',
330
+ expected: 'the parameterized parent packageId to remain fixed during production',
331
+ hint: 'retry after source writes settle and rebuild the current generation',
332
+ detail: {
333
+ sourcePath: root.sourcePath,
334
+ scannedPackageId: PackageId.format(root.packageId),
335
+ loadedPackageId: PackageId.format(loaded.value.packageId),
336
+ },
337
+ });
338
+ }
339
+ const packageId = PackageId.format(subject.packageId);
340
+ inputs.push({
341
+ sourcePath: subject.sourcePath,
342
+ displaySourcePath: parameterizedCatalogSourcePath(context, subject.sourcePath),
343
+ definition: loaded.value,
344
+ sourceClosure: root.sourceClosure,
345
+ subjectPackageId: subject.packageId,
346
+ values: resolved.value.values,
347
+ publicationGeneration: context.generation,
348
+ policy: {
349
+ base: context.basePrefix === '' ? '/' : context.basePrefix,
350
+ packagePath: `assets/${packageId}.pack.json`,
351
+ artifactPath: scriptableArtifactPath,
352
+ },
353
+ });
354
+ }
355
+ if (inputs.length === 0) return [];
356
+
357
+ const requiredGuids = context.inventory.entries.flatMap((entry) => {
358
+ const parsed = AssetGuid.parse(entry.guid);
359
+ if (!parsed.ok) dynamicFailure(context, parsed.error);
360
+ return [parsed.value];
361
+ });
362
+ const externalOutputs = await declaredPackExternalOutputs(
363
+ context.inventory.sourceDeclarations,
364
+ context.cookers,
365
+ requiredGuids,
366
+ {
367
+ importerRegistry: context.importerRegistry,
368
+ fsForImport: context.fsForImport,
369
+ },
138
370
  );
139
- if (!preparedResult.ok) throw context.fail(preparedResult.error);
371
+ const availableGuids = new Set(requiredGuids.map((guid) => AssetGuid.format(guid).toLowerCase()));
372
+ const built = await produceParameterizedScriptablePackProducts({
373
+ sources: inputs,
374
+ declaredExternalOutputs: externalOutputs,
375
+ availableGuids,
376
+ });
377
+ if (!built.ok) dynamicFailure(context, built.error);
140
378
 
141
- const result = new Map<string, ScriptableBuildPackage>();
142
- for (const entry of context.inventory.entries) {
143
- if (!entry.sourcePath.endsWith('.pack.ts') || result.has(entry.sourcePath)) continue;
144
- const prepared = preparedResult.value.get(canonicalScriptableSourcePath(entry.sourcePath));
379
+ const bundles: ParameterizedBuildBundle[] = [];
380
+ for (const input of inputs) {
381
+ const prepared = built.value.get(input.displaySourcePath);
145
382
  if (prepared === undefined) {
146
- throw context.fail({
147
- code: 'catalog-declaration-missing',
148
- expected: 'the ScriptablePack inventory to retain every authored Pack source',
149
- hint: 'rerun the source inventory and rebuild from the accepted declaration set',
150
- detail: { stage: 'scan', sourcePath: entry.sourcePath },
383
+ dynamicFailure(context, {
384
+ code: 'parameterized-pack-build-failed',
385
+ expected: 'the worklist to return one prepared result per source subject',
386
+ hint: 'rerun the parameterized Pack generation from a clean inventory',
387
+ detail: { sourcePath: input.sourcePath },
151
388
  });
152
389
  }
153
- const { product, finalized, facts, revision, publication } = prepared;
154
- const packagePath = `assets/${product.anchorGuid}.pack.json`;
155
- const runtimePublication = createRuntimePackPublication({
156
- pack: { assets: finalized.pack.assets },
157
- scopeId: context.runtimeBinding?.scopeId ?? 'asset-runtime',
158
- sourcePath: publication.sourcePath,
159
- sourceRevision: publication.sourceRevision,
160
- packageUrl: finalized.packageUrl,
161
- inputFingerprint: publication.receipt.inputFingerprint,
162
- digest: finalized.digest,
163
- generation: context.runtimeBinding?.generation ?? publication.generation,
164
- outputs: facts.outputs,
165
- externalEvidence: publication.externalEvidence,
166
- });
167
- const receiptPaths = await materializePreparedScriptablePack(
168
- prepared,
169
- {
170
- packagePath,
171
- artifactPath: (path) => `assets/${path}`,
172
- receiptPath: (guid) => `assets/${guid}.receipt.json`,
173
- },
174
- {
175
- writePackage: (path) => {
176
- context.sink.emitFile({
177
- type: 'asset',
178
- fileName: path,
179
- originalFileName: `${context.cwd}/${entry.sourcePath}`,
180
- source: JSON.stringify(runtimePublication.pack),
181
- });
182
- },
183
- writeArtifact: (path, bytes) => {
184
- context.sink.emitFile({ type: 'asset', fileName: path, source: bytes });
185
- },
186
- writeReceipt: (path, source) => {
187
- context.sink.emitFile({ type: 'asset', fileName: path, source });
188
- },
189
- },
390
+ const packageId = PackageId.format(input.subjectPackageId ?? input.definition.packageId);
391
+ const stagedByGuid = new Map(
392
+ prepared.product.stagedOutputs.map((output) => [
393
+ AssetGuid.format(output.guid).toLowerCase(),
394
+ output,
395
+ ]),
190
396
  );
191
- result.set(entry.sourcePath, {
192
- packageUrl: finalized.packageUrl,
193
- receiptUrls: new Map(
194
- [...receiptPaths].map(([guid, path]) => [guid, context.sink.fileUrl(path)]),
195
- ),
196
- revision,
197
- publication: runtimePublication.publication,
198
- refs: facts.refs,
199
- });
397
+ const entries = projectPackageCatalog(
398
+ prepared.product.product.assets.map((asset, sourceIndex) => {
399
+ const staged = stagedByGuid.get(asset.guid.toLowerCase());
400
+ return {
401
+ guid: asset.guid,
402
+ kind: asset.kind,
403
+ sourcePath: input.displaySourcePath,
404
+ sourceIndex,
405
+ ...(staged?.sourceKey === undefined ? {} : { sourceKey: staged.sourceKey }),
406
+ refs: asset.refs.map((reference) => reference.guid),
407
+ execution: 'cooked' as const,
408
+ packageId,
409
+ provenance: { provider: 'pack-ts', version: '2.0.0' },
410
+ };
411
+ }),
412
+ `${context.basePrefix === '/' ? '' : context.basePrefix}/${input.displaySourcePath}`,
413
+ );
414
+ bundles.push({ input, prepared, entries });
200
415
  }
201
- return result;
416
+ return bundles;
202
417
  }
203
418
 
204
419
  function updateImportedEntries(
@@ -209,12 +424,15 @@ function updateImportedEntries(
209
424
  refsByGuid?: ReadonlyMap<string, readonly string[]>,
210
425
  publication?: AssetPublicationEnvelope,
211
426
  sourcePath?: string,
427
+ receiptUrls?: ReadonlyMap<string, string>,
428
+ revision?: PackIndexEntry['revision'],
212
429
  ): void {
213
430
  const selected = new Set(guids.map((guid) => guid.toLowerCase()));
214
431
  for (let index = 0; index < entries.length; index += 1) {
215
432
  const entry = entries[index];
216
433
  if (entry === undefined || !selected.has(entry.guid.toLowerCase())) continue;
217
434
  const refs = refsByGuid?.get(entry.guid.toLowerCase());
435
+ const cookReceiptUrl = receiptUrls?.get(entry.guid.toLowerCase());
218
436
  entries[index] = {
219
437
  ...entry,
220
438
  packageUrl,
@@ -222,6 +440,8 @@ function updateImportedEntries(
222
440
  ...(sourcePath === undefined ? {} : { sourcePath }),
223
441
  ...(publication === undefined ? {} : { publication }),
224
442
  ...(refs === undefined ? {} : { refs }),
443
+ ...(cookReceiptUrl === undefined ? {} : { cookReceiptUrl }),
444
+ ...(revision === undefined ? {} : { revision }),
225
445
  };
226
446
  }
227
447
  }
@@ -239,8 +459,11 @@ interface PackEmissionOptions {
239
459
  readonly guids: readonly string[];
240
460
  readonly projection?: Record<string, unknown>;
241
461
  readonly refsByGuid?: ReadonlyMap<string, readonly string[]>;
462
+ readonly receipts?: readonly CookReceipt[];
463
+ readonly receiptOutputDigest?: string;
242
464
  readonly publication?: AssetPublicationEnvelope;
243
465
  readonly sourcePath?: string;
466
+ readonly revision?: PackIndexEntry['revision'];
244
467
  }
245
468
 
246
469
  async function emitPackDocument(
@@ -256,11 +479,34 @@ async function emitPackDocument(
256
479
  }
257
480
  const referenceId = work.context.sink.emitFile({
258
481
  type: 'asset',
482
+ fileName: options.packageName.startsWith('assets/')
483
+ ? options.packageName
484
+ : `assets/${options.packageName}`,
259
485
  name: options.packageName,
260
486
  originalFileName: options.originalFileName,
261
487
  source: JSON.stringify(options.pack),
262
488
  });
263
489
  const packageUrl = work.context.sink.fileUrl(work.context.sink.getFileName(referenceId));
490
+ const receiptUrls = new Map<string, string>();
491
+ for (const receipt of options.receipts ?? []) {
492
+ const guid = receipt.guid.toLowerCase();
493
+ const receiptReferenceId = work.context.sink.emitFile({
494
+ type: 'asset',
495
+ fileName: `assets/${guid}.receipt.json`,
496
+ name: `${guid}.receipt.json`,
497
+ originalFileName: options.originalFileName,
498
+ source: JSON.stringify({
499
+ ...receipt,
500
+ ...(receipt.outputDigest === undefined && options.receiptOutputDigest === undefined
501
+ ? {}
502
+ : { outputDigest: receipt.outputDigest ?? options.receiptOutputDigest }),
503
+ }),
504
+ });
505
+ receiptUrls.set(
506
+ guid,
507
+ work.context.sink.fileUrl(work.context.sink.getFileName(receiptReferenceId)),
508
+ );
509
+ }
264
510
  updateImportedEntries(
265
511
  work.importedEntries,
266
512
  options.guids,
@@ -269,6 +515,8 @@ async function emitPackDocument(
269
515
  options.refsByGuid,
270
516
  options.publication,
271
517
  options.sourcePath,
518
+ receiptUrls,
519
+ options.revision,
272
520
  );
273
521
  return packageUrl;
274
522
  }
@@ -277,6 +525,7 @@ async function emitAuthoredPack(
277
525
  work: BuildEmissionWork,
278
526
  guidSeen: Set<string>,
279
527
  entry: PackIndexEntry,
528
+ availableGuids: ReadonlySet<string>,
280
529
  ): Promise<void> {
281
530
  const sourceDeclaration = sourceDeclarationForCatalogPath(
282
531
  entry.sourcePath,
@@ -294,50 +543,158 @@ async function emitAuthoredPack(
294
543
  detail: { stage: 'scan', sourcePath: packPath },
295
544
  });
296
545
  }
297
- const prepared = await prepareAuthoredPackTransport(
298
- declaration.value,
546
+ if (declaration.value.schemaVersion === '3.0.0') {
547
+ const parsed = parseParameterizedPackJson(declaration.value);
548
+ if (!parsed.ok) dynamicFailure(work.context, parsed.error);
549
+ if (parsed.value.format !== 'direct') {
550
+ dynamicFailure(work.context, {
551
+ code: 'catalog-declaration-missing',
552
+ expected: 'a direct v3 Pack declaration for an indexed authored output',
553
+ hint: 'instances are built from their parameterized parent and do not have direct Catalog rows',
554
+ detail: { stage: 'scan', sourcePath: packPath },
555
+ });
556
+ }
557
+ const projected = projectDirectPackJson(parsed.value);
558
+ if (!projected.ok) dynamicFailure(work.context, projected.error);
559
+ const prepared = await prepareDirectPackTransport({
560
+ projected: projected.value,
561
+ sourcePath: packPath,
562
+ sourceRevision: declaration.sourceRevision,
563
+ availableGuids,
564
+ cookers: work.context.cookers,
565
+ policy: {
566
+ base: work.context.basePrefix === '' ? '/' : work.context.basePrefix,
567
+ packagePath: `assets/${projected.value.packageId}.pack.json`,
568
+ artifactPath: (assetGuid, key) => `${assetGuid}/${key}.bin`,
569
+ sink: () => {},
570
+ },
571
+ });
572
+ if (!prepared.ok) dynamicFailure(work.context, prepared.error);
573
+ const { product, finalized, facts, revision } = prepared.value;
574
+ const publication = runtimePublicationFor(work.context, {
575
+ pack: finalized.pack,
576
+ sourcePath: entry.sourcePath,
577
+ sourceRevision: product.inputFingerprint,
578
+ packageUrl: finalized.packageUrl,
579
+ digest: finalized.digest,
580
+ inputFingerprint: product.inputFingerprint,
581
+ outputs: facts.outputs,
582
+ externalEvidence: product.externalEvidence,
583
+ });
584
+ await emitPackDocument(work, {
585
+ pack: publication.pack,
586
+ artifacts: finalized.artifacts,
587
+ packageName: `${prepared.value.projected.packageId}.pack.json`,
588
+ originalFileName: packPath,
589
+ guids: product.product.assets.map((asset) => asset.guid),
590
+ projection: work.context.authoredCookedCurrentProjection,
591
+ refsByGuid: facts.refs,
592
+ receipts: product.product.receipts,
593
+ receiptOutputDigest: finalized.digest,
594
+ publication: publication.publication,
595
+ sourcePath: entry.sourcePath,
596
+ revision,
597
+ });
598
+ for (const asset of product.product.assets) guidSeen.add(asset.guid.toLowerCase());
599
+ return;
600
+ }
601
+ // Legacy array-shaped documents are runtime Pack transport, not an
602
+ // authoring input anymore. Preserve the explicit v2 native-cooker seam for
603
+ // cooked transport while keeping source evaluation out of this path.
604
+ const legacy = declaration.value as LegacyPackInventoryDocument;
605
+ const prepared = await prepareLegacyPackTransport(
606
+ legacy,
299
607
  work.context.cookers,
300
608
  (guid) => ({
301
609
  base: work.context.basePrefix === '' ? '/' : work.context.basePrefix,
302
610
  packagePath: `assets/${guid}.pack.json`,
303
- artifactPath: (artifactGuid, key) => `${artifactGuid}/${key}.bin`,
611
+ artifactPath: (assetGuid, key) => `${assetGuid}/${key}.bin`,
304
612
  }),
305
613
  packPath,
306
614
  );
307
615
  const outputGuid = prepared.firstGuid ?? entry.guid;
308
- const authoredPack = prepared.finalized?.pack ?? prepared.pack;
309
- const runtimePublication =
310
- authoredPack.assets === undefined || authoredPack.assets.length === 0
311
- ? undefined
312
- : runtimePublicationFor(work.context, {
313
- pack: { assets: authoredPack.assets },
314
- sourcePath: entry.sourcePath,
315
- sourceRevision: declaration.sourceRevision,
316
- packageUrl:
317
- prepared.finalized?.packageUrl ??
318
- `${work.context.basePrefix === '/' ? '' : work.context.basePrefix}/assets/${outputGuid}.pack.json`,
319
- ...(prepared.finalized?.digest === undefined
320
- ? {}
321
- : { digest: prepared.finalized.digest }),
322
- });
616
+ const authoredPack = prepared.finalized?.pack ?? {
617
+ schemaVersion: '2.0.0' as const,
618
+ kind: 'internal-text-package' as const,
619
+ assets: legacy.assets.map((asset) => ({
620
+ guid: asset.guid,
621
+ kind: asset.kind,
622
+ ...(asset.name === undefined ? {} : { name: asset.name }),
623
+ payload: asset.payload,
624
+ refs: asset.refs,
625
+ artifacts: asset.artifacts ?? {},
626
+ })),
627
+ };
628
+ const runtimePublication = runtimePublicationFor(work.context, {
629
+ pack: authoredPack,
630
+ sourcePath: entry.sourcePath,
631
+ sourceRevision: declaration.sourceRevision,
632
+ packageUrl:
633
+ prepared.finalized?.packageUrl ??
634
+ `${work.context.basePrefix === '/' ? '' : work.context.basePrefix}/assets/${outputGuid}.pack.json`,
635
+ ...(prepared.finalized?.digest === undefined ? {} : { digest: prepared.finalized.digest }),
636
+ });
323
637
  await emitPackDocument(work, {
324
- pack: runtimePublication?.pack ?? authoredPack,
638
+ pack: runtimePublication.pack,
325
639
  artifacts: prepared.finalized?.artifacts ?? [],
326
640
  packageName: `${outputGuid}.pack.json`,
327
641
  originalFileName: packPath,
328
- guids: prepared.pack.assets?.map((asset) => asset.guid) ?? [outputGuid],
642
+ guids: authoredPack.assets.map((asset) => asset.guid),
329
643
  projection:
330
644
  prepared.finalized === undefined
331
645
  ? work.context.directCurrentProjection
332
646
  : work.context.authoredCookedCurrentProjection,
333
- ...(prepared.cooked === undefined ? {} : { refsByGuid: prepared.cooked.refsByGuid }),
334
- ...(runtimePublication === undefined ? {} : { publication: runtimePublication.publication }),
647
+ refsByGuid: new Map(authoredPack.assets.map((asset) => [asset.guid.toLowerCase(), asset.refs])),
648
+ ...(prepared.finalized?.receipts === undefined
649
+ ? {}
650
+ : {
651
+ receipts: prepared.finalized.receipts,
652
+ receiptOutputDigest: prepared.finalized.digest,
653
+ }),
654
+ publication: runtimePublication.publication,
655
+ sourcePath: entry.sourcePath,
335
656
  });
336
- for (const guid of prepared.pack.assets?.map((asset) => asset.guid) ?? [outputGuid]) {
657
+ for (const guid of authoredPack.assets.map((asset) => asset.guid)) {
337
658
  guidSeen.add(guid.toLowerCase());
338
659
  }
339
660
  }
340
661
 
662
+ async function emitParameterizedPack(
663
+ work: BuildEmissionWork,
664
+ guidSeen: Set<string>,
665
+ bundle: ParameterizedBuildBundle,
666
+ ): Promise<void> {
667
+ const { input, prepared } = bundle;
668
+ const packageId = PackageId.format(input.subjectPackageId ?? input.definition.packageId);
669
+ const runtimePublication = createRuntimePackPublication({
670
+ pack: { assets: prepared.finalized.pack.assets },
671
+ scopeId: work.context.runtimeBinding?.scopeId ?? 'asset-runtime',
672
+ sourcePath: input.displaySourcePath,
673
+ sourceRevision: prepared.product.inputFingerprint,
674
+ packageUrl: prepared.finalized.packageUrl,
675
+ inputFingerprint: prepared.product.inputFingerprint,
676
+ digest: prepared.finalized.digest,
677
+ generation: work.context.runtimeBinding?.generation ?? prepared.publication.generation,
678
+ outputs: prepared.facts.outputs,
679
+ externalEvidence: prepared.product.externalEvidence,
680
+ });
681
+ await emitPackDocument(work, {
682
+ pack: runtimePublication.pack,
683
+ artifacts: prepared.finalized.artifacts,
684
+ packageName: `${packageId}.pack.json`,
685
+ originalFileName: input.sourcePath,
686
+ guids: prepared.product.product.assets.map((asset) => asset.guid),
687
+ projection: work.context.authoredCookedCurrentProjection,
688
+ refsByGuid: prepared.facts.refs,
689
+ receipts: prepared.product.product.receipts,
690
+ receiptOutputDigest: prepared.finalized.digest,
691
+ publication: runtimePublication.publication,
692
+ sourcePath: input.displaySourcePath,
693
+ revision: prepared.revision,
694
+ });
695
+ for (const asset of prepared.product.product.assets) guidSeen.add(asset.guid.toLowerCase());
696
+ }
697
+
341
698
  async function emitFinalizedOwner(
342
699
  work: BuildEmissionWork,
343
700
  metaPath: string,
@@ -418,11 +775,13 @@ async function emitBuildEntry(
418
775
  work: BuildEmissionWork,
419
776
  guidSeen: Set<string>,
420
777
  entry: PackIndexEntry,
778
+ availableGuids: ReadonlySet<string>,
421
779
  ): Promise<void> {
422
780
  const guid = entry.guid.toLowerCase();
423
781
  const metaPath = metaPathForGuid(work.context.inventory.declarations, guid);
424
782
  if (metaPath === undefined) {
425
- if (entry.sourcePath.endsWith('.pack.json')) await emitAuthoredPack(work, guidSeen, entry);
783
+ if (entry.sourcePath.endsWith('.pack.json'))
784
+ await emitAuthoredPack(work, guidSeen, entry, availableGuids);
426
785
  else guidSeen.add(guid);
427
786
  return;
428
787
  }
@@ -436,7 +795,7 @@ async function emitBuildEntry(
436
795
  });
437
796
  }
438
797
  const runMeta = { ...declaration, buildPack: false };
439
- const subAssets: readonly { readonly guid: string }[] = runMeta.subAssets;
798
+ const subAssets = runMeta.subAssets;
440
799
  for (const sub of subAssets) guidSeen.add(sub.guid.toLowerCase());
441
800
  const sourcePackage = await produceSourcePackage({
442
801
  meta: runMeta,
@@ -475,6 +834,7 @@ async function emitBuildEntry(
475
834
  sourceRevision: finalized.sourceRevision,
476
835
  packageUrl: finalized.packageUrl,
477
836
  digest: finalized.digest,
837
+ sourceKeys: sourceKeysFor(subAssets),
478
838
  });
479
839
  await emitPackDocument(work, {
480
840
  pack: runtimePublication.pack,
@@ -494,24 +854,21 @@ async function emitBuildEntry(
494
854
  export async function produceBuildAssets(
495
855
  context: BuildProductionOptions,
496
856
  ): Promise<PackIndexEntry[]> {
497
- const entries = [...context.inventory.entries];
498
- const authored = await buildAuthoredPackages(context);
499
- const importedEntries = entries.map((entry) => {
500
- const scriptable = authored.get(entry.sourcePath);
501
- if (scriptable === undefined) return entry;
502
- const cookReceiptUrl = scriptable.receiptUrls.get(entry.guid.toLowerCase());
503
- return projectCookedPackageEntry(entry, {
504
- packageUrl: scriptable.packageUrl,
505
- revision: scriptable.revision,
506
- refs: scriptable.refs.get(entry.guid.toLowerCase()) ?? [],
507
- ...(cookReceiptUrl === undefined ? {} : { cookReceiptUrl }),
508
- publication: scriptable.publication,
509
- });
510
- });
857
+ const dynamicBundles = await buildParameterizedPackages(context);
858
+ const entries = [
859
+ ...context.inventory.entries,
860
+ ...dynamicBundles.flatMap((bundle) => bundle.entries),
861
+ ];
862
+ const availableGuids = new Set(entries.map((entry) => entry.guid.toLowerCase()));
863
+ const importedEntries = [...entries];
511
864
  const guidSeen = new Set<string>();
512
865
  const work: BuildEmissionWork = { importedEntries, context };
866
+ for (const bundle of dynamicBundles) {
867
+ await emitParameterizedPack(work, guidSeen, bundle);
868
+ }
513
869
  for (const entry of importedEntries) {
514
- if (!guidSeen.has(entry.guid.toLowerCase())) await emitBuildEntry(work, guidSeen, entry);
870
+ if (!guidSeen.has(entry.guid.toLowerCase()))
871
+ await emitBuildEntry(work, guidSeen, entry, availableGuids);
515
872
  }
516
873
  return importedEntries;
517
874
  }