@forgeax/engine-import 0.1.21 → 0.1.24

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