@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.
- package/README.md +91 -3
- package/dist/__tests__/ies-contract.unit.test.d.ts +2 -0
- package/dist/__tests__/ies-contract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/ies-producer.unit.test.d.ts +2 -0
- package/dist/__tests__/ies-producer.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-lod-contract.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-lod-contract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-lod-reimport.integration.test.d.ts +2 -0
- package/dist/__tests__/mesh-lod-reimport.integration.test.d.ts.map +1 -0
- package/dist/__tests__/parameterized-scriptable-pack.unit.test.d.ts +2 -0
- package/dist/__tests__/parameterized-scriptable-pack.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-file-snapshot.unit.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-file-snapshot.unit.test.d.ts.map +1 -0
- package/dist/__tests__/source-package-publication-lod.integration.test.d.ts +2 -0
- package/dist/__tests__/source-package-publication-lod.integration.test.d.ts.map +1 -0
- package/dist/browser.d.ts +1 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.mjs +230 -3
- package/dist/browser.mjs.map +1 -1
- package/dist/build-production.d.ts +1 -8
- package/dist/build-production.d.ts.map +1 -1
- package/dist/ies/ies-importer.d.ts +4 -0
- package/dist/ies/ies-importer.d.ts.map +1 -0
- package/dist/ies/parse-lm63.d.ts +13 -0
- package/dist/ies/parse-lm63.d.ts.map +1 -0
- package/dist/ies/resample-type-c.d.ts +4 -0
- package/dist/ies/resample-type-c.d.ts.map +1 -0
- package/dist/import-runner.d.ts.map +1 -1
- package/dist/index.d.ts +11 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +2550 -1029
- package/dist/index.mjs.map +1 -1
- package/dist/mesh-bin.d.ts.map +1 -1
- package/dist/mesh-bin.mjs +29 -1
- package/dist/mesh-bin.mjs.map +1 -1
- package/dist/mesh-lod.d.ts +60 -0
- package/dist/mesh-lod.d.ts.map +1 -0
- package/dist/parameterized-scriptable-pack.d.ts +68 -0
- package/dist/parameterized-scriptable-pack.d.ts.map +1 -0
- package/dist/scriptable-pack-file-snapshot.d.ts +71 -0
- package/dist/scriptable-pack-file-snapshot.d.ts.map +1 -0
- package/dist/scriptable-pack-host.d.ts +70 -39
- package/dist/scriptable-pack-host.d.ts.map +1 -1
- package/dist/scriptable-pack-output-producers.d.ts +1 -0
- package/dist/scriptable-pack-output-producers.d.ts.map +1 -1
- package/dist/scriptable-pack-staged-snapshot.d.ts +2 -2
- package/dist/scriptable-pack-staged-snapshot.d.ts.map +1 -1
- package/dist/scriptable-pack.d.ts +13 -26
- package/dist/scriptable-pack.d.ts.map +1 -1
- package/dist/source-package-errors.d.ts.map +1 -1
- package/dist/source-package-publication.d.ts.map +1 -1
- package/package.json +8 -7
- package/src/__tests__/ies-contract.unit.test.ts +31 -0
- package/src/__tests__/ies-producer.unit.test.ts +104 -0
- package/src/__tests__/mesh-bin.test.ts +69 -0
- package/src/__tests__/mesh-lod-contract.unit.test.ts +92 -0
- package/src/__tests__/mesh-lod-reimport.integration.test.ts +15 -0
- package/src/__tests__/parameterized-scriptable-pack.unit.test.ts +270 -0
- package/src/__tests__/scriptable-pack-file-snapshot.unit.test.ts +177 -0
- package/src/__tests__/scriptable-pack-host.unit.test.ts +196 -4
- package/src/__tests__/scriptable-pack-production-producers.unit.test.ts +7 -1
- package/src/__tests__/scriptable-pack-staged-snapshot.unit.test.ts +43 -12
- package/src/__tests__/source-package-publication-lod.integration.test.ts +29 -0
- package/src/__tests__/source-package-publication.integration.test.ts +26 -1
- package/src/browser.ts +5 -0
- package/src/build-production.ts +498 -141
- package/src/ies/ies-importer.ts +126 -0
- package/src/ies/parse-lm63.ts +98 -0
- package/src/ies/resample-type-c.ts +108 -0
- package/src/import-runner.ts +58 -0
- package/src/index.ts +49 -19
- package/src/mesh-bin.ts +38 -0
- package/src/mesh-lod.ts +265 -0
- package/src/parameterized-scriptable-pack.ts +725 -0
- package/src/scriptable-pack-file-snapshot.ts +346 -0
- package/src/scriptable-pack-host.ts +790 -343
- package/src/scriptable-pack-output-producers.ts +58 -1
- package/src/scriptable-pack-staged-snapshot.ts +12 -11
- package/src/scriptable-pack.ts +16 -485
- package/src/source-package-errors.ts +6 -0
- package/src/source-package-publication.ts +32 -2
- package/dist/.tsbuildinfo +0 -1
- package/dist/__tests__/scriptable-pack-product.contract.test.d.ts +0 -2
- package/dist/__tests__/scriptable-pack-product.contract.test.d.ts.map +0 -1
- package/dist/__tests__/scriptable-pack.integration.test.d.ts +0 -2
- package/dist/__tests__/scriptable-pack.integration.test.d.ts.map +0 -1
- package/dist/scriptable-source-package.d.ts +0 -14
- package/dist/scriptable-source-package.d.ts.map +0 -1
- package/src/__tests__/scriptable-pack-product.contract.test.ts +0 -46
- package/src/__tests__/scriptable-pack.integration.test.ts +0 -435
- package/src/scriptable-source-package.ts +0 -88
|
@@ -1,29 +1,43 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
1
|
import { stat } from 'node:fs/promises';
|
|
3
|
-
import { isAbsolute, relative, resolve } from 'node:path';
|
|
4
2
|
import { createAcceptedPublication } from '@forgeax/engine-ddc';
|
|
3
|
+
import { normalizeMeshPayload } from '@forgeax/engine-geometry';
|
|
5
4
|
import {
|
|
6
|
-
type AuthoredPackInput,
|
|
7
5
|
finalizePackageTransportSource,
|
|
8
|
-
loadAssetConfig,
|
|
9
6
|
type PackageFinalizePolicy,
|
|
7
|
+
type PackageProductAsset,
|
|
8
|
+
packageTransportRevision,
|
|
10
9
|
resolveAssetSource,
|
|
11
|
-
upgradeLegacyAuthoredPack,
|
|
12
10
|
} from '@forgeax/engine-pack/build';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import type { ScanSourceDeclaration } from '@forgeax/engine-pack/scanner';
|
|
11
|
+
import { BUILTIN_MESH_ASSETS } from '@forgeax/engine-pack/builtin';
|
|
12
|
+
import { AssetGuid, PackageId, type PackageId as PackageIdType } from '@forgeax/engine-pack/guid';
|
|
16
13
|
import {
|
|
14
|
+
type NativeCookDraft,
|
|
15
|
+
type NativeCooker,
|
|
16
|
+
NativeCookerRegistry,
|
|
17
|
+
} from '@forgeax/engine-pack/native-cooker';
|
|
18
|
+
import type {
|
|
19
|
+
LegacyPackInventoryDocument,
|
|
20
|
+
ScanSourceDeclaration,
|
|
21
|
+
} from '@forgeax/engine-pack/scanner';
|
|
22
|
+
import {
|
|
23
|
+
type AnyParameterizedPackDefinition,
|
|
24
|
+
type DirectPackAssetProjection,
|
|
25
|
+
isScriptablePackAssetKind,
|
|
26
|
+
parseParameterizedPackJson,
|
|
27
|
+
projectDirectPackJson,
|
|
17
28
|
projectScriptablePackSceneComponents,
|
|
18
|
-
type ScriptablePackDefinition,
|
|
19
29
|
type ScriptablePackSourceClosureEntry,
|
|
20
30
|
} from '@forgeax/engine-pack/source';
|
|
21
|
-
import {
|
|
31
|
+
import { isEngineMaterial } from '@forgeax/engine-shader';
|
|
22
32
|
import type {
|
|
23
33
|
Asset,
|
|
24
34
|
AssetGuid as AssetGuidType,
|
|
25
35
|
AssetPublicationEnvelope,
|
|
26
36
|
AssetPublicationOutput,
|
|
37
|
+
AssetRef,
|
|
38
|
+
ImportedArtifactBody,
|
|
39
|
+
ImportedAsset,
|
|
40
|
+
MaterialAsset,
|
|
27
41
|
ResourceRevision,
|
|
28
42
|
Result,
|
|
29
43
|
} from '@forgeax/engine-types';
|
|
@@ -31,43 +45,63 @@ import { AssetError, err, ok } from '@forgeax/engine-types';
|
|
|
31
45
|
import type { DdcPack, ImportRunnerFs, RunImportMeta } from './import-runner.js';
|
|
32
46
|
import type { ImporterRegistry } from './importer-registry.js';
|
|
33
47
|
import { projectImportProductForBuild } from './pack-projection.js';
|
|
34
|
-
import type { ScriptablePackDomainError, ScriptablePackStagedOutput } from './scriptable-pack.js';
|
|
35
|
-
import { createStandardAssetOutputProducerRegistry } from './scriptable-pack-output-producers.js';
|
|
36
48
|
import {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
49
|
+
buildParameterizedScriptablePack,
|
|
50
|
+
buildParameterizedScriptablePackWorklist,
|
|
51
|
+
type ParameterizedScriptablePackBuildProduct,
|
|
52
|
+
type ParameterizedScriptablePackWorkItem,
|
|
53
|
+
} from './parameterized-scriptable-pack.js';
|
|
54
|
+
import type {
|
|
55
|
+
ScriptablePackAssetSnapshotSource,
|
|
56
|
+
ScriptablePackDomainError,
|
|
57
|
+
ScriptablePackStagedOutput,
|
|
58
|
+
} from './scriptable-pack.js';
|
|
40
59
|
import {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
} from './scriptable-source-package.js';
|
|
60
|
+
createPreExternalizedSceneAssetOutputProducer,
|
|
61
|
+
createStandardAssetOutputProducerRegistry,
|
|
62
|
+
} from './scriptable-pack-output-producers.js';
|
|
45
63
|
import { produceSourcePackage } from './source-package.js';
|
|
46
64
|
|
|
47
65
|
type FinalizedPackageTransport = Awaited<ReturnType<typeof finalizePackageTransportSource>>;
|
|
48
66
|
|
|
49
|
-
export interface
|
|
67
|
+
export interface ParameterizedScriptablePackInput {
|
|
50
68
|
readonly sourcePath: string;
|
|
51
69
|
readonly displaySourcePath: string;
|
|
52
|
-
readonly definition:
|
|
70
|
+
readonly definition: AnyParameterizedPackDefinition;
|
|
53
71
|
readonly sourceClosure: readonly ScriptablePackSourceClosureEntry[];
|
|
54
72
|
readonly publicationGeneration: number;
|
|
55
|
-
readonly policy:
|
|
73
|
+
readonly policy:
|
|
74
|
+
| PackageFinalizePolicy
|
|
75
|
+
| ((product: ParameterizedScriptablePackBuildProduct) => PackageFinalizePolicy);
|
|
76
|
+
readonly subjectPackageId?: PackageIdType;
|
|
77
|
+
readonly values?: Readonly<Record<string, unknown>>;
|
|
78
|
+
readonly inheritedValues?: Readonly<
|
|
79
|
+
Record<string, import('@forgeax/engine-pack/source').PackParameterValue>
|
|
80
|
+
>;
|
|
56
81
|
}
|
|
57
82
|
|
|
58
|
-
export interface
|
|
59
|
-
readonly
|
|
60
|
-
readonly refs: ReadonlyMap<string, readonly string[]>;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export interface PreparedScriptablePack {
|
|
64
|
-
readonly product: ScriptableSourcePackageProduct;
|
|
83
|
+
export interface PreparedParameterizedScriptablePack {
|
|
84
|
+
readonly product: ParameterizedScriptablePackBuildProduct;
|
|
65
85
|
readonly finalized: FinalizedPackageTransport;
|
|
66
86
|
readonly facts: ScriptablePackPublicationFacts;
|
|
67
87
|
readonly revision: ResourceRevision;
|
|
68
88
|
readonly publication: AssetPublicationEnvelope;
|
|
69
89
|
}
|
|
70
90
|
|
|
91
|
+
export interface ParameterizedScriptablePackProductionOptions {
|
|
92
|
+
readonly sources: readonly ParameterizedScriptablePackInput[];
|
|
93
|
+
readonly declaredExternalOutputs?: readonly ScriptablePackStagedOutput[];
|
|
94
|
+
readonly assetSource?: ScriptablePackAssetSnapshotSource;
|
|
95
|
+
readonly availableGuids?: ReadonlySet<string>;
|
|
96
|
+
readonly incomingRefs?: ReadonlyMap<string, readonly string[]>;
|
|
97
|
+
readonly maxPasses?: number;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface ScriptablePackPublicationFacts {
|
|
101
|
+
readonly outputs: readonly AssetPublicationOutput[];
|
|
102
|
+
readonly refs: ReadonlyMap<string, readonly string[]>;
|
|
103
|
+
}
|
|
104
|
+
|
|
71
105
|
export interface ScriptablePackTransportPaths {
|
|
72
106
|
readonly packagePath: string;
|
|
73
107
|
readonly artifactPath: (path: string) => string;
|
|
@@ -80,31 +114,10 @@ export interface ScriptablePackTransportSink {
|
|
|
80
114
|
writeReceipt(path: string, body: string): void | Promise<void>;
|
|
81
115
|
}
|
|
82
116
|
|
|
83
|
-
|
|
84
|
-
| PackageFinalizePolicy
|
|
85
|
-
| ((product: ScriptableSourcePackageProduct) => PackageFinalizePolicy);
|
|
86
|
-
|
|
87
|
-
type ScriptablePackHostError =
|
|
88
|
-
ScriptableSourcePackageResult extends Result<unknown, infer E> ? E : never;
|
|
89
|
-
|
|
90
|
-
export interface CookedAuthoredPack {
|
|
91
|
-
readonly logicalPackage: DdcPack;
|
|
92
|
-
readonly refsByGuid: ReadonlyMap<string, readonly string[]>;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export interface AuthoredPackTransport {
|
|
96
|
-
readonly pack: AuthoredPackInput;
|
|
97
|
-
readonly firstGuid?: string;
|
|
98
|
-
readonly cooked?: CookedAuthoredPack;
|
|
99
|
-
readonly finalized?: FinalizedPackageTransport;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/** Build-time capabilities required to expose ordinary Meta products to a ScriptablePack. */
|
|
117
|
+
/** Build-time capabilities required to expose ordinary Meta products to a Pack. */
|
|
103
118
|
export interface ScriptablePackExternalImportOptions {
|
|
104
119
|
readonly importerRegistry: ImporterRegistry;
|
|
105
120
|
readonly fsForImport: ImportRunnerFs;
|
|
106
|
-
/** Package path aliases from forgeax.assets.paths; defaults to the current project config. */
|
|
107
|
-
readonly assetPaths?: Readonly<Record<string, string>>;
|
|
108
121
|
}
|
|
109
122
|
|
|
110
123
|
export function canonicalScriptableSourcePath(sourcePath: string): string {
|
|
@@ -114,127 +127,7 @@ export function canonicalScriptableSourcePath(sourcePath: string): string {
|
|
|
114
127
|
return markerIndex < 0 ? normalized : normalized.slice(markerIndex + 1);
|
|
115
128
|
}
|
|
116
129
|
|
|
117
|
-
|
|
118
|
-
sourcePaths: readonly string[],
|
|
119
|
-
declarations: ReadonlyMap<string, ScanSourceDeclaration>,
|
|
120
|
-
cwd: string,
|
|
121
|
-
generationFor: (displaySourcePath: string) => number,
|
|
122
|
-
policyFor: (displaySourcePath: string) => ScriptablePackTransportPolicy,
|
|
123
|
-
sourceIdentityFor?: (sourcePath: string) => string,
|
|
124
|
-
): ScriptablePackInput[] {
|
|
125
|
-
return sourcePaths.flatMap((sourcePath) => {
|
|
126
|
-
const declaration = declarations.get(resolve(cwd, sourcePath));
|
|
127
|
-
if (declaration?.format !== 'pack.ts') return [];
|
|
128
|
-
const projected = sourceIdentityFor?.(sourcePath);
|
|
129
|
-
const catalogSourcePath =
|
|
130
|
-
projected === undefined || isAbsolute(projected) ? relative(cwd, sourcePath) : projected;
|
|
131
|
-
const displaySourcePath = canonicalScriptableSourcePath(catalogSourcePath);
|
|
132
|
-
return [
|
|
133
|
-
{
|
|
134
|
-
sourcePath: resolve(cwd, sourcePath),
|
|
135
|
-
displaySourcePath,
|
|
136
|
-
definition: declaration.definition,
|
|
137
|
-
sourceClosure: declaration.sourceClosure,
|
|
138
|
-
publicationGeneration: generationFor(displaySourcePath),
|
|
139
|
-
policy: policyFor(displaySourcePath),
|
|
140
|
-
},
|
|
141
|
-
];
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/** Materialize one accepted ScriptablePack without duplicating dev/build loops. */
|
|
146
|
-
export async function materializePreparedScriptablePack(
|
|
147
|
-
prepared: PreparedScriptablePack,
|
|
148
|
-
paths: ScriptablePackTransportPaths,
|
|
149
|
-
sink: ScriptablePackTransportSink,
|
|
150
|
-
): Promise<ReadonlyMap<string, string>> {
|
|
151
|
-
const { product, finalized } = prepared;
|
|
152
|
-
await sink.writePackage(paths.packagePath, JSON.stringify(finalized.pack));
|
|
153
|
-
for (const artifact of finalized.artifacts) {
|
|
154
|
-
await sink.writeArtifact(
|
|
155
|
-
paths.artifactPath(artifact.path),
|
|
156
|
-
artifact.bytes,
|
|
157
|
-
artifact.path.endsWith('.json') ? 'application/json' : artifact.mediaType,
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
const receipts = new Map<string, string>();
|
|
161
|
-
for (const guid of product.declaredGuids) {
|
|
162
|
-
const path = paths.receiptPath(guid);
|
|
163
|
-
await sink.writeReceipt(
|
|
164
|
-
path,
|
|
165
|
-
JSON.stringify({
|
|
166
|
-
guid,
|
|
167
|
-
origin: 'sourceMeta',
|
|
168
|
-
status: 'succeeded',
|
|
169
|
-
inputFingerprint: product.inputFingerprint,
|
|
170
|
-
outputDigest: finalized.digest,
|
|
171
|
-
}),
|
|
172
|
-
);
|
|
173
|
-
receipts.set(guid, path);
|
|
174
|
-
}
|
|
175
|
-
return receipts;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
export async function readCookedAuthoredPack(
|
|
179
|
-
authoredPack: AuthoredPackInput,
|
|
180
|
-
cookers: readonly NativeCooker[] = [],
|
|
181
|
-
sourcePath?: string,
|
|
182
|
-
): Promise<CookedAuthoredPack | undefined> {
|
|
183
|
-
const parsed = upgradeLegacyAuthoredPack(authoredPack);
|
|
184
|
-
if (parsed.schemaVersion !== '2.0.0' || parsed.assets === undefined) return undefined;
|
|
185
|
-
const registry = new NativeCookerRegistry();
|
|
186
|
-
for (const cooker of cookers) registry.register(cooker);
|
|
187
|
-
const assets: DdcPack['assets'][number][] = [];
|
|
188
|
-
const refsByGuid = new Map<string, readonly string[]>();
|
|
189
|
-
let hasCookedAsset = false;
|
|
190
|
-
for (const asset of parsed.assets) {
|
|
191
|
-
const shouldCook = asset.execution === 'cooked';
|
|
192
|
-
if (!shouldCook) {
|
|
193
|
-
assets.push({
|
|
194
|
-
guid: asset.guid,
|
|
195
|
-
kind: asset.kind,
|
|
196
|
-
...(asset.name === undefined ? {} : { name: asset.name }),
|
|
197
|
-
...(asset.sourceKey === undefined ? {} : { sourceKey: asset.sourceKey }),
|
|
198
|
-
...(asset.sourceIndex === undefined ? {} : { sourceIndex: asset.sourceIndex }),
|
|
199
|
-
...(asset.relations === undefined ? {} : { relations: asset.relations }),
|
|
200
|
-
payload: asset.payload,
|
|
201
|
-
refs: asset.refs ?? [],
|
|
202
|
-
artifacts: {},
|
|
203
|
-
});
|
|
204
|
-
continue;
|
|
205
|
-
}
|
|
206
|
-
hasCookedAsset = true;
|
|
207
|
-
const result = await registry.runDraft(asset.kind, {
|
|
208
|
-
guid: asset.guid,
|
|
209
|
-
source: asset.payload,
|
|
210
|
-
...(asset.sourceKey === undefined ? {} : { sourceKey: asset.sourceKey }),
|
|
211
|
-
...(sourcePath === undefined ? {} : { sourcePath }),
|
|
212
|
-
refs: asset.refs ?? [],
|
|
213
|
-
});
|
|
214
|
-
if (!result.ok) throw result.error;
|
|
215
|
-
const draft = result.value;
|
|
216
|
-
const refs = [...draft.refs];
|
|
217
|
-
refsByGuid.set(asset.guid.toLowerCase(), refs);
|
|
218
|
-
assets.push({
|
|
219
|
-
guid: draft.guid,
|
|
220
|
-
kind: asset.kind,
|
|
221
|
-
...(asset.name === undefined ? {} : { name: asset.name }),
|
|
222
|
-
...(asset.sourceKey === undefined ? {} : { sourceKey: asset.sourceKey }),
|
|
223
|
-
...(asset.sourceIndex === undefined ? {} : { sourceIndex: asset.sourceIndex }),
|
|
224
|
-
...(asset.relations === undefined ? {} : { relations: asset.relations }),
|
|
225
|
-
payload: draft.payload as Record<string, unknown>,
|
|
226
|
-
refs,
|
|
227
|
-
artifacts: draft.artifacts,
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
return hasCookedAsset
|
|
231
|
-
? {
|
|
232
|
-
logicalPackage: { schemaVersion: '2.0.0', kind: 'internal-text-package', assets },
|
|
233
|
-
refsByGuid,
|
|
234
|
-
}
|
|
235
|
-
: undefined;
|
|
236
|
-
}
|
|
237
|
-
|
|
130
|
+
/** Stage materialized outputs owned by Meta or an already-cooked Pack v2 document. */
|
|
238
131
|
export async function declaredPackExternalOutputs(
|
|
239
132
|
declarations: ReadonlyMap<string, ScanSourceDeclaration>,
|
|
240
133
|
cookers: readonly NativeCooker[] = [],
|
|
@@ -243,6 +136,15 @@ export async function declaredPackExternalOutputs(
|
|
|
243
136
|
): Promise<readonly ScriptablePackStagedOutput[]> {
|
|
244
137
|
if (requiredGuids.length === 0) return [];
|
|
245
138
|
const required = new Set(requiredGuids.map((guid) => AssetGuid.format(guid).toLowerCase()));
|
|
139
|
+
const available = new Set(required);
|
|
140
|
+
for (const declaration of declarations.values()) {
|
|
141
|
+
if (declaration.format !== 'meta.json') continue;
|
|
142
|
+
for (const asset of declaration.value.subAssets) {
|
|
143
|
+
const parsed = AssetGuid.parse(asset.guid);
|
|
144
|
+
if (!parsed.ok) throw parsed.error;
|
|
145
|
+
available.add(AssetGuid.format(parsed.value).toLowerCase());
|
|
146
|
+
}
|
|
147
|
+
}
|
|
246
148
|
const outputs: ScriptablePackStagedOutput[] = [];
|
|
247
149
|
for (const declaration of declarations.values()) {
|
|
248
150
|
if (declaration.format === 'meta.json') {
|
|
@@ -250,22 +152,10 @@ export async function declaredPackExternalOutputs(
|
|
|
250
152
|
if (!declaration.value.subAssets.some((asset) => required.has(asset.guid.toLowerCase()))) {
|
|
251
153
|
continue;
|
|
252
154
|
}
|
|
253
|
-
const
|
|
254
|
-
const resolved = resolveAssetSource(
|
|
255
|
-
declaration.sourcePath,
|
|
256
|
-
declaration.value.source,
|
|
257
|
-
assetPaths,
|
|
258
|
-
);
|
|
259
|
-
if (!resolved.ok) {
|
|
260
|
-
throw new AssetError({
|
|
261
|
-
code: 'asset-not-imported',
|
|
262
|
-
expected: `a resolvable source for Meta dependency ${declaration.sourcePath}`,
|
|
263
|
-
hint: 'repair the Meta source path before rebuilding the ScriptablePack',
|
|
264
|
-
});
|
|
265
|
-
}
|
|
155
|
+
const resolved = resolveAssetSource(declaration.sourcePath, declaration.value.source);
|
|
266
156
|
const meta: RunImportMeta = {
|
|
267
157
|
importer: declaration.value.importer,
|
|
268
|
-
source: resolved
|
|
158
|
+
source: resolved,
|
|
269
159
|
sourceRevision: declaration.sourceRevision,
|
|
270
160
|
...(declaration.value.packageId === undefined
|
|
271
161
|
? {}
|
|
@@ -299,81 +189,629 @@ export async function declaredPackExternalOutputs(
|
|
|
299
189
|
if (!sourcePackage.ok) {
|
|
300
190
|
throw new AssetError({
|
|
301
191
|
code: 'asset-not-imported',
|
|
302
|
-
expected:
|
|
303
|
-
hint:
|
|
192
|
+
expected: 'the Meta importer to produce the requested dependency',
|
|
193
|
+
hint: 'repair the Meta source and rerun the Pack build',
|
|
194
|
+
detail: { sourcePath: declaration.sourcePath },
|
|
304
195
|
});
|
|
305
196
|
}
|
|
306
197
|
for (const asset of sourcePackage.value.product.assets) {
|
|
307
|
-
|
|
308
|
-
if (!required.has(key)) continue;
|
|
198
|
+
if (!required.has(asset.guid.toLowerCase())) continue;
|
|
309
199
|
const parsed = AssetGuid.parse(asset.guid);
|
|
310
200
|
if (!parsed.ok) throw parsed.error;
|
|
201
|
+
const declared = declaration.value.subAssets.find(
|
|
202
|
+
(candidate) => candidate.guid.toLowerCase() === asset.guid.toLowerCase(),
|
|
203
|
+
);
|
|
311
204
|
outputs.push({
|
|
312
205
|
guid: parsed.value,
|
|
313
|
-
|
|
206
|
+
sourceKey: declared?.sourceKey ?? asset.guid,
|
|
207
|
+
asset: { ...(asset.payload as Record<string, unknown>), kind: asset.kind } as Asset,
|
|
314
208
|
});
|
|
315
209
|
}
|
|
316
210
|
continue;
|
|
317
211
|
}
|
|
318
212
|
if (declaration.format !== 'pack.json') continue;
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
213
|
+
if (declaration.value.schemaVersion === '3.0.0') {
|
|
214
|
+
const parsed = parseParameterizedPackJson(declaration.value);
|
|
215
|
+
if (!parsed.ok || parsed.value.format !== 'direct') continue;
|
|
216
|
+
const projected = projectDirectPackJson(parsed.value);
|
|
217
|
+
if (!projected.ok) continue;
|
|
218
|
+
const prepared = await prepareDirectPackTransport({
|
|
219
|
+
projected: projected.value,
|
|
220
|
+
sourcePath: declaration.sourcePath,
|
|
221
|
+
sourceRevision: declaration.sourceRevision,
|
|
222
|
+
availableGuids: available,
|
|
223
|
+
...(cookers.length === 0 ? {} : { cookers }),
|
|
224
|
+
policy: {
|
|
225
|
+
base: '/',
|
|
226
|
+
packagePath: `assets/${projected.value.packageId}.pack.json`,
|
|
227
|
+
artifactPath: (assetGuid, key) => `${assetGuid}/${key}.bin`,
|
|
228
|
+
sink: () => {},
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
if (!prepared.ok) throw prepared.error;
|
|
232
|
+
const cookedByGuid = new Map(
|
|
233
|
+
prepared.value.finalized.pack.assets.map((asset) => [asset.guid.toLowerCase(), asset]),
|
|
234
|
+
);
|
|
235
|
+
for (const asset of projected.value.assets) {
|
|
236
|
+
if (!required.has(asset.guid.toLowerCase())) continue;
|
|
237
|
+
const parsedGuid = AssetGuid.parse(asset.guid);
|
|
238
|
+
if (!parsedGuid.ok) throw parsedGuid.error;
|
|
239
|
+
const cooked = cookedByGuid.get(asset.guid.toLowerCase());
|
|
240
|
+
if (cooked === undefined) {
|
|
241
|
+
throw new AssetError({
|
|
242
|
+
code: 'asset-not-imported',
|
|
243
|
+
expected: 'the direct Pack producer to retain every declared output',
|
|
244
|
+
hint: 'repair the direct Pack output and rerun the Pack build',
|
|
245
|
+
detail: { sourcePath: declaration.sourcePath },
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
outputs.push({
|
|
249
|
+
guid: parsedGuid.value,
|
|
250
|
+
sourceKey: asset.sourceKey,
|
|
251
|
+
asset: { kind: cooked.kind, ...cooked.payload } as Asset,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
323
256
|
const cooked = await readCookedAuthoredPack(declaration.value, cookers, declaration.sourcePath);
|
|
324
|
-
for (const asset of cooked?.logicalPackage.assets.
|
|
325
|
-
required.has(
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
257
|
+
for (const asset of cooked?.logicalPackage.assets ?? declaration.value.assets) {
|
|
258
|
+
if (!required.has(asset.guid.toLowerCase())) continue;
|
|
259
|
+
const parsedGuid = AssetGuid.parse(asset.guid);
|
|
260
|
+
if (!parsedGuid.ok) throw parsedGuid.error;
|
|
261
|
+
outputs.push({
|
|
262
|
+
guid: parsedGuid.value,
|
|
263
|
+
sourceKey: asset.sourceKey ?? asset.guid,
|
|
264
|
+
asset: { ...asset.payload, kind: asset.kind } as Asset,
|
|
265
|
+
});
|
|
330
266
|
}
|
|
331
267
|
}
|
|
332
268
|
return outputs;
|
|
333
269
|
}
|
|
334
270
|
|
|
335
|
-
/**
|
|
336
|
-
export async function
|
|
337
|
-
authoredPack:
|
|
271
|
+
/** Cook the explicit legacy Pack v2 transport without exposing v1 authoring APIs. */
|
|
272
|
+
export async function readCookedAuthoredPack(
|
|
273
|
+
authoredPack: LegacyPackInventoryDocument,
|
|
274
|
+
cookers: readonly NativeCooker[] = [],
|
|
275
|
+
sourcePath?: string,
|
|
276
|
+
): Promise<
|
|
277
|
+
| {
|
|
278
|
+
readonly logicalPackage: Pick<DdcPack, 'assets'>;
|
|
279
|
+
readonly refsByGuid: ReadonlyMap<string, readonly string[]>;
|
|
280
|
+
}
|
|
281
|
+
| undefined
|
|
282
|
+
> {
|
|
283
|
+
if (authoredPack.schemaVersion !== '2.0.0') return undefined;
|
|
284
|
+
const registry = new NativeCookerRegistry();
|
|
285
|
+
for (const cooker of cookers) registry.register(cooker);
|
|
286
|
+
const assets: PackageProductAsset[] = [];
|
|
287
|
+
const refsByGuid = new Map<string, readonly string[]>();
|
|
288
|
+
let hasCookedAsset = false;
|
|
289
|
+
for (const asset of authoredPack.assets) {
|
|
290
|
+
if (asset.execution !== 'cooked') {
|
|
291
|
+
assets.push({
|
|
292
|
+
guid: asset.guid,
|
|
293
|
+
kind: asset.kind,
|
|
294
|
+
...(asset.name === undefined ? {} : { name: asset.name }),
|
|
295
|
+
payload: asset.payload,
|
|
296
|
+
refs: asset.refs,
|
|
297
|
+
artifacts: {},
|
|
298
|
+
});
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (isEngineOwnedMaterial(asset)) {
|
|
302
|
+
assets.push({
|
|
303
|
+
guid: asset.guid,
|
|
304
|
+
kind: asset.kind,
|
|
305
|
+
...(asset.name === undefined ? {} : { name: asset.name }),
|
|
306
|
+
payload: asset.payload,
|
|
307
|
+
refs: asset.refs,
|
|
308
|
+
artifacts: {},
|
|
309
|
+
});
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
hasCookedAsset = true;
|
|
313
|
+
const result = await registry.runDraft(asset.kind, {
|
|
314
|
+
guid: asset.guid,
|
|
315
|
+
// Pack v2 stores the asset kind beside payload. Native producer APIs
|
|
316
|
+
// consume the runtime Asset shape, so reconstruct that one boundary
|
|
317
|
+
// field before invoking the cooker.
|
|
318
|
+
source: nativeCookSource(asset),
|
|
319
|
+
...(asset.sourceKey === undefined ? {} : { sourceKey: asset.sourceKey }),
|
|
320
|
+
...(sourcePath === undefined ? {} : { sourcePath }),
|
|
321
|
+
refs: asset.refs,
|
|
322
|
+
});
|
|
323
|
+
if (!result.ok) throw result.error;
|
|
324
|
+
const draft = result.value;
|
|
325
|
+
refsByGuid.set(asset.guid.toLowerCase(), [...draft.refs]);
|
|
326
|
+
assets.push({
|
|
327
|
+
guid: draft.guid,
|
|
328
|
+
kind: asset.kind,
|
|
329
|
+
...(asset.name === undefined ? {} : { name: asset.name }),
|
|
330
|
+
payload: draft.payload as Record<string, unknown>,
|
|
331
|
+
refs: [...draft.refs],
|
|
332
|
+
artifacts: draft.artifacts,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
return hasCookedAsset
|
|
336
|
+
? {
|
|
337
|
+
logicalPackage: { assets },
|
|
338
|
+
refsByGuid,
|
|
339
|
+
}
|
|
340
|
+
: undefined;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export interface LegacyPackTransport {
|
|
344
|
+
readonly pack: LegacyPackInventoryDocument;
|
|
345
|
+
readonly firstGuid?: string;
|
|
346
|
+
readonly cooked?: Awaited<ReturnType<typeof readCookedAuthoredPack>>;
|
|
347
|
+
readonly finalized?: FinalizedPackageTransport;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export interface DirectPackTransport {
|
|
351
|
+
readonly projected: {
|
|
352
|
+
readonly packageId: string;
|
|
353
|
+
readonly assets: readonly DirectPackAssetProjection[];
|
|
354
|
+
};
|
|
355
|
+
readonly product: ParameterizedScriptablePackBuildProduct;
|
|
356
|
+
readonly finalized: FinalizedPackageTransport;
|
|
357
|
+
readonly facts: ScriptablePackPublicationFacts;
|
|
358
|
+
readonly revision: ResourceRevision;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export interface DirectPackTransportInput {
|
|
362
|
+
readonly projected: DirectPackTransport['projected'];
|
|
363
|
+
readonly sourcePath: string;
|
|
364
|
+
readonly displaySourcePath?: string;
|
|
365
|
+
readonly sourceRevision: string;
|
|
366
|
+
readonly policy: PackageFinalizePolicy;
|
|
367
|
+
readonly availableGuids?: ReadonlySet<string>;
|
|
368
|
+
readonly cookers?: readonly NativeCooker[];
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function directReference(guid: string): AssetRef {
|
|
372
|
+
return { guid, sourceField: { fieldName: 'refs' } };
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function mergeDirectReferences(
|
|
376
|
+
produced: readonly AssetRef[],
|
|
377
|
+
declared: readonly string[],
|
|
378
|
+
): readonly AssetRef[] {
|
|
379
|
+
const merged = [...produced];
|
|
380
|
+
const seen = new Set(produced.map((reference) => reference.guid.toLowerCase()));
|
|
381
|
+
for (const guid of declared) {
|
|
382
|
+
const parsed = AssetGuid.parse(guid);
|
|
383
|
+
if (!parsed.ok) throw parsed.error;
|
|
384
|
+
const normalized = AssetGuid.format(parsed.value);
|
|
385
|
+
if (seen.has(normalized.toLowerCase())) continue;
|
|
386
|
+
seen.add(normalized.toLowerCase());
|
|
387
|
+
merged.push(directReference(normalized));
|
|
388
|
+
}
|
|
389
|
+
return merged;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function directReferenceFailure(
|
|
393
|
+
sourcePath: string,
|
|
394
|
+
guids: readonly string[],
|
|
395
|
+
): {
|
|
396
|
+
readonly code: 'pack-output-reference-missing';
|
|
397
|
+
readonly expected: string;
|
|
398
|
+
readonly hint: string;
|
|
399
|
+
readonly detail: { readonly sourcePath: string; readonly guids: readonly string[] };
|
|
400
|
+
} {
|
|
401
|
+
return {
|
|
402
|
+
code: 'pack-output-reference-missing',
|
|
403
|
+
expected: 'every direct Pack reference to resolve to a local or published AssetGuid',
|
|
404
|
+
hint: 'publish the referenced Pack or repair the direct refs before rebuilding the Pack',
|
|
405
|
+
detail: { sourcePath, guids: [...guids].sort() },
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function directInputFingerprint(
|
|
410
|
+
sourceRevision: string,
|
|
411
|
+
nativeFingerprints: ReadonlyMap<string, string>,
|
|
412
|
+
): string {
|
|
413
|
+
if (nativeFingerprints.size === 0) return sourceRevision;
|
|
414
|
+
return `sha256:${packageTransportRevision({
|
|
415
|
+
sourceRevision,
|
|
416
|
+
nativeCookers: [...nativeFingerprints.entries()].sort(([left], [right]) =>
|
|
417
|
+
left.localeCompare(right),
|
|
418
|
+
),
|
|
419
|
+
})}`;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function importedAssetArtifacts(
|
|
423
|
+
assets: readonly ImportedAsset<unknown>[],
|
|
424
|
+
): Readonly<Record<string, ImportedArtifactBody>> {
|
|
425
|
+
return Object.fromEntries(
|
|
426
|
+
assets.flatMap((asset) =>
|
|
427
|
+
Object.entries(asset.artifacts).map(([key, artifact]) => [`${asset.guid}/${key}`, artifact]),
|
|
428
|
+
),
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function rawDirectProduct(
|
|
433
|
+
input: DirectPackTransportInput,
|
|
434
|
+
): ParameterizedScriptablePackBuildProduct {
|
|
435
|
+
const inputFingerprint = `sha256:${packageTransportRevision({
|
|
436
|
+
packageId: input.projected.packageId,
|
|
437
|
+
sourceRevision: input.sourceRevision,
|
|
438
|
+
assets: input.projected.assets,
|
|
439
|
+
})}`;
|
|
440
|
+
const assets: ImportedAsset<unknown>[] = input.projected.assets.map((asset) => ({
|
|
441
|
+
guid: asset.guid,
|
|
442
|
+
kind: asset.kind,
|
|
443
|
+
...(asset.name === undefined ? {} : { name: asset.name }),
|
|
444
|
+
payload: directRuntimePayload(asset),
|
|
445
|
+
refs: asset.refs.map(directReference),
|
|
446
|
+
artifacts: {},
|
|
447
|
+
}));
|
|
448
|
+
return {
|
|
449
|
+
product: {
|
|
450
|
+
assets,
|
|
451
|
+
sourceDependencies: [input.sourcePath],
|
|
452
|
+
refs: assets.flatMap((asset) => asset.refs),
|
|
453
|
+
artifacts: importedAssetArtifacts(assets),
|
|
454
|
+
receipts: assets.map((asset) => ({
|
|
455
|
+
guid: asset.guid,
|
|
456
|
+
origin: 'authoredPack' as const,
|
|
457
|
+
status: 'succeeded' as const,
|
|
458
|
+
inputFingerprint,
|
|
459
|
+
})),
|
|
460
|
+
diagnostics: [],
|
|
461
|
+
sourceRevision: inputFingerprint,
|
|
462
|
+
sourceKey: input.sourcePath,
|
|
463
|
+
},
|
|
464
|
+
stagedOutputs: input.projected.assets.map((asset) => ({
|
|
465
|
+
guid: directGuid(asset.guid),
|
|
466
|
+
sourceKey: asset.sourceKey,
|
|
467
|
+
asset: {
|
|
468
|
+
...(directRuntimePayload(asset) as Record<string, unknown>),
|
|
469
|
+
kind: asset.kind,
|
|
470
|
+
} as Asset,
|
|
471
|
+
digest: `sha256:${packageTransportRevision({ kind: asset.kind, payload: asset.payload })}`,
|
|
472
|
+
})),
|
|
473
|
+
externalEvidence: [],
|
|
474
|
+
inputFingerprint,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function directGuid(value: string): AssetGuidType {
|
|
479
|
+
const parsed = AssetGuid.parse(value);
|
|
480
|
+
if (!parsed.ok) throw parsed.error;
|
|
481
|
+
return parsed.value;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function nativeRefs(refs: readonly string[]): AssetRef[] {
|
|
485
|
+
return refs.map((guid) => {
|
|
486
|
+
const parsed = AssetGuid.parse(guid);
|
|
487
|
+
if (!parsed.ok) throw parsed.error;
|
|
488
|
+
return { guid: AssetGuid.format(parsed.value) };
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function nativeCookSource(asset: { readonly kind: string; readonly payload: unknown }): unknown {
|
|
493
|
+
if (
|
|
494
|
+
asset.kind !== 'material' ||
|
|
495
|
+
asset.payload === null ||
|
|
496
|
+
typeof asset.payload !== 'object' ||
|
|
497
|
+
Array.isArray(asset.payload)
|
|
498
|
+
) {
|
|
499
|
+
return asset.payload;
|
|
500
|
+
}
|
|
501
|
+
return { ...(asset.payload as Record<string, unknown>), kind: asset.kind };
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function isEngineOwnedMaterial(asset: {
|
|
505
|
+
readonly kind: string;
|
|
506
|
+
readonly payload: unknown;
|
|
507
|
+
}): boolean {
|
|
508
|
+
if (
|
|
509
|
+
asset.kind !== 'material' ||
|
|
510
|
+
asset.payload === null ||
|
|
511
|
+
typeof asset.payload !== 'object' ||
|
|
512
|
+
Array.isArray(asset.payload)
|
|
513
|
+
) {
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
return isEngineMaterial(asset.payload as Pick<MaterialAsset, 'passes'>);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function directRuntimePayload(asset: DirectPackAssetProjection): unknown {
|
|
520
|
+
if (
|
|
521
|
+
asset.kind !== 'ui' ||
|
|
522
|
+
asset.payload === null ||
|
|
523
|
+
typeof asset.payload !== 'object' ||
|
|
524
|
+
Array.isArray(asset.payload)
|
|
525
|
+
) {
|
|
526
|
+
return asset.payload;
|
|
527
|
+
}
|
|
528
|
+
return { ...(asset.payload as Record<string, unknown>), guid: asset.guid };
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function directAssetForProducer(asset: DirectPackAssetProjection): Asset {
|
|
532
|
+
const payload = {
|
|
533
|
+
...(directRuntimePayload(asset) as Record<string, unknown>),
|
|
534
|
+
kind: asset.kind,
|
|
535
|
+
};
|
|
536
|
+
if (asset.kind !== 'mesh') return payload as Asset;
|
|
537
|
+
return (normalizeMeshPayload(payload, asset.refs) ?? payload) as Asset;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/** Cook one direct v3 Pack through the same producer/finalizer seam as pack.ts. */
|
|
541
|
+
export async function prepareDirectPackTransport(
|
|
542
|
+
input: DirectPackTransportInput,
|
|
543
|
+
): Promise<Result<DirectPackTransport, unknown>> {
|
|
544
|
+
const localGuids = new Set(input.projected.assets.map((asset) => asset.guid.toLowerCase()));
|
|
545
|
+
const availableGuids = new Set(
|
|
546
|
+
[...(input.availableGuids ?? []), ...BUILTIN_MESH_ASSETS.map((asset) => asset.guid)].filter(
|
|
547
|
+
(guid) => !localGuids.has(guid.toLowerCase()),
|
|
548
|
+
),
|
|
549
|
+
);
|
|
550
|
+
const directArtifacts = input.projected.assets.find(
|
|
551
|
+
(asset) => asset.artifacts !== undefined && Object.keys(asset.artifacts).length > 0,
|
|
552
|
+
);
|
|
553
|
+
if (directArtifacts !== undefined) {
|
|
554
|
+
return err({
|
|
555
|
+
code: 'pack-parameter-invalid',
|
|
556
|
+
expected: 'direct v3 authoring entries to leave artifacts empty for the producer',
|
|
557
|
+
hint: 'remove authored artifact data and let the registered Asset producer create it',
|
|
558
|
+
detail: { sourcePath: input.sourcePath, sourceKey: directArtifacts.sourceKey },
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
const packageId = PackageId.parse(input.projected.packageId);
|
|
562
|
+
if (!packageId.ok) return err(packageId.error);
|
|
563
|
+
const cookerRegistry = new NativeCookerRegistry();
|
|
564
|
+
for (const cooker of input.cookers ?? []) cookerRegistry.register(cooker);
|
|
565
|
+
const nativeDrafts = new Map<string, NativeCookDraft>();
|
|
566
|
+
const nativeFingerprints = new Map<string, string>();
|
|
567
|
+
for (const asset of input.projected.assets) {
|
|
568
|
+
if (!isScriptablePackAssetKind(asset.kind)) continue;
|
|
569
|
+
if (cookerRegistry.get(asset.kind) === undefined) continue;
|
|
570
|
+
if (isEngineOwnedMaterial(asset)) continue;
|
|
571
|
+
const draft = await cookerRegistry.runDraft(asset.kind, {
|
|
572
|
+
guid: asset.guid,
|
|
573
|
+
// Direct v3 entries keep `kind` at the entry boundary. Reattach it for
|
|
574
|
+
// producer contracts such as MaterialAsset, whose payload is otherwise
|
|
575
|
+
// intentionally allowed to omit the discriminant.
|
|
576
|
+
source: nativeCookSource(asset),
|
|
577
|
+
sourceKey: asset.sourceKey,
|
|
578
|
+
sourcePath: input.sourcePath,
|
|
579
|
+
refs: asset.refs,
|
|
580
|
+
});
|
|
581
|
+
if (!draft.ok) return err(draft.error);
|
|
582
|
+
if (draft.value.guid.toLowerCase() !== asset.guid.toLowerCase()) {
|
|
583
|
+
return err({
|
|
584
|
+
code: 'pack-parameter-invalid',
|
|
585
|
+
expected: 'a direct Pack cooker to preserve the derived AssetGuid',
|
|
586
|
+
hint: 'repair the native cooker output GUID and rebuild the direct Pack',
|
|
587
|
+
detail: {
|
|
588
|
+
sourcePath: input.sourcePath,
|
|
589
|
+
sourceKey: asset.sourceKey,
|
|
590
|
+
expectedGuid: asset.guid,
|
|
591
|
+
actualGuid: draft.value.guid,
|
|
592
|
+
},
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
nativeDrafts.set(asset.guid.toLowerCase(), draft.value);
|
|
596
|
+
nativeFingerprints.set(asset.sourceKey, draft.value.inputFingerprint);
|
|
597
|
+
}
|
|
598
|
+
const unsupported = input.projected.assets.filter(
|
|
599
|
+
(asset) => !isScriptablePackAssetKind(asset.kind),
|
|
600
|
+
);
|
|
601
|
+
if (unsupported.length > 0 && unsupported.length !== input.projected.assets.length) {
|
|
602
|
+
return err({
|
|
603
|
+
code: 'pack-parameter-invalid',
|
|
604
|
+
expected: 'a direct Pack to contain only producer-backed Engine Assets or only direct PODs',
|
|
605
|
+
hint: 'split custom direct PODs from producer-backed Assets into separate Packs',
|
|
606
|
+
detail: {
|
|
607
|
+
sourcePath: input.sourcePath,
|
|
608
|
+
unsupportedKinds: [...new Set(unsupported.map((asset) => asset.kind))].sort(),
|
|
609
|
+
},
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
if (unsupported.length === input.projected.assets.length) {
|
|
613
|
+
const raw = rawDirectProduct(input);
|
|
614
|
+
const known = new Set([...availableGuids, ...localGuids]);
|
|
615
|
+
const missing = new Set<string>();
|
|
616
|
+
for (const asset of raw.product.assets) {
|
|
617
|
+
for (const reference of asset.refs) {
|
|
618
|
+
if (!known.has(reference.guid.toLowerCase())) missing.add(reference.guid.toLowerCase());
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (missing.size > 0) return err(directReferenceFailure(input.sourcePath, [...missing]));
|
|
622
|
+
const finalized = await finalizePackageTransportSource(
|
|
623
|
+
projectImportProductForBuild(raw.product),
|
|
624
|
+
input.policy,
|
|
625
|
+
);
|
|
626
|
+
const facts = projectParameterizedPackPublication(raw);
|
|
627
|
+
if (!facts.ok) return facts;
|
|
628
|
+
const displaySourcePath = input.displaySourcePath ?? input.sourcePath;
|
|
629
|
+
const revision = await scriptablePackResourceRevision(displaySourcePath, raw.inputFingerprint, [
|
|
630
|
+
{ path: input.sourcePath, digest: input.sourceRevision },
|
|
631
|
+
]);
|
|
632
|
+
return ok({
|
|
633
|
+
projected: input.projected,
|
|
634
|
+
product: raw,
|
|
635
|
+
finalized,
|
|
636
|
+
facts: facts.value,
|
|
637
|
+
revision,
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
const definition = {
|
|
641
|
+
schemaVersion: '2.0.0' as const,
|
|
642
|
+
packageId: packageId.value,
|
|
643
|
+
build: () =>
|
|
644
|
+
ok(
|
|
645
|
+
Object.fromEntries(
|
|
646
|
+
input.projected.assets.map((asset) => [
|
|
647
|
+
asset.sourceKey,
|
|
648
|
+
{
|
|
649
|
+
...((nativeDrafts.get(asset.guid.toLowerCase())?.payload as
|
|
650
|
+
| Record<string, unknown>
|
|
651
|
+
| undefined) ?? directAssetForProducer(asset)),
|
|
652
|
+
kind: asset.kind,
|
|
653
|
+
} as Asset,
|
|
654
|
+
]),
|
|
655
|
+
),
|
|
656
|
+
),
|
|
657
|
+
} as AnyParameterizedPackDefinition;
|
|
658
|
+
const outputs = createStandardAssetOutputProducerRegistry();
|
|
659
|
+
outputs.register(createPreExternalizedSceneAssetOutputProducer());
|
|
660
|
+
const built = await buildParameterizedScriptablePack({
|
|
661
|
+
definition,
|
|
662
|
+
sourcePath: input.sourcePath,
|
|
663
|
+
outputs,
|
|
664
|
+
sourceClosure: [{ path: input.sourcePath, digest: input.sourceRevision }],
|
|
665
|
+
availableGuids,
|
|
666
|
+
deferReferenceValidation: true,
|
|
667
|
+
});
|
|
668
|
+
if (!built.ok) return built;
|
|
669
|
+
const declaredByGuid = new Map(
|
|
670
|
+
input.projected.assets.map((asset) => [asset.guid.toLowerCase(), asset.refs]),
|
|
671
|
+
);
|
|
672
|
+
const assets = built.value.product.assets.map((asset) => ({
|
|
673
|
+
...asset,
|
|
674
|
+
...(nativeDrafts.has(asset.guid.toLowerCase())
|
|
675
|
+
? {
|
|
676
|
+
payload: nativeDrafts.get(asset.guid.toLowerCase())?.payload as Record<string, unknown>,
|
|
677
|
+
refs: mergeDirectReferences(
|
|
678
|
+
nativeRefs(nativeDrafts.get(asset.guid.toLowerCase())?.refs ?? []),
|
|
679
|
+
declaredByGuid.get(asset.guid.toLowerCase()) ?? [],
|
|
680
|
+
),
|
|
681
|
+
artifacts: nativeDrafts.get(asset.guid.toLowerCase())?.artifacts ?? {},
|
|
682
|
+
}
|
|
683
|
+
: {
|
|
684
|
+
refs: mergeDirectReferences(
|
|
685
|
+
asset.refs,
|
|
686
|
+
declaredByGuid.get(asset.guid.toLowerCase()) ?? [],
|
|
687
|
+
),
|
|
688
|
+
}),
|
|
689
|
+
}));
|
|
690
|
+
const missing = new Set<string>();
|
|
691
|
+
const known = new Set([...availableGuids, ...localGuids]);
|
|
692
|
+
for (const asset of assets) {
|
|
693
|
+
for (const reference of asset.refs) {
|
|
694
|
+
if (!known.has(reference.guid.toLowerCase())) missing.add(reference.guid.toLowerCase());
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
if (missing.size > 0) return err(directReferenceFailure(input.sourcePath, [...missing]));
|
|
698
|
+
const inputFingerprint = directInputFingerprint(built.value.inputFingerprint, nativeFingerprints);
|
|
699
|
+
const stagedOutputs = input.projected.assets.map((asset) => ({
|
|
700
|
+
guid: directGuid(asset.guid),
|
|
701
|
+
sourceKey: asset.sourceKey,
|
|
702
|
+
asset: directAssetForProducer(asset),
|
|
703
|
+
digest: `sha256:${packageTransportRevision({ kind: asset.kind, payload: asset.payload })}`,
|
|
704
|
+
}));
|
|
705
|
+
const product: ParameterizedScriptablePackBuildProduct = {
|
|
706
|
+
...built.value,
|
|
707
|
+
inputFingerprint,
|
|
708
|
+
stagedOutputs,
|
|
709
|
+
product: {
|
|
710
|
+
...built.value.product,
|
|
711
|
+
assets,
|
|
712
|
+
refs: assets.flatMap((asset) => asset.refs),
|
|
713
|
+
artifacts: importedAssetArtifacts(assets),
|
|
714
|
+
receipts: built.value.product.receipts.map((receipt) => ({
|
|
715
|
+
...receipt,
|
|
716
|
+
inputFingerprint,
|
|
717
|
+
})),
|
|
718
|
+
sourceRevision: inputFingerprint,
|
|
719
|
+
},
|
|
720
|
+
};
|
|
721
|
+
const finalized = await finalizePackageTransportSource(
|
|
722
|
+
projectImportProductForBuild(product.product),
|
|
723
|
+
input.policy,
|
|
724
|
+
);
|
|
725
|
+
const facts = projectParameterizedPackPublication(product);
|
|
726
|
+
if (!facts.ok) return facts;
|
|
727
|
+
const displaySourcePath = input.displaySourcePath ?? input.sourcePath;
|
|
728
|
+
const revision = await scriptablePackResourceRevision(
|
|
729
|
+
displaySourcePath,
|
|
730
|
+
product.inputFingerprint,
|
|
731
|
+
[{ path: input.sourcePath, digest: input.sourceRevision }],
|
|
732
|
+
);
|
|
733
|
+
return ok({
|
|
734
|
+
projected: input.projected,
|
|
735
|
+
product,
|
|
736
|
+
finalized,
|
|
737
|
+
facts: facts.value,
|
|
738
|
+
revision,
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/** Normalize one explicit Pack v2 transport and invoke its registered cookers once. */
|
|
743
|
+
export async function prepareLegacyPackTransport(
|
|
744
|
+
authoredPack: LegacyPackInventoryDocument,
|
|
338
745
|
cookers: readonly NativeCooker[] | undefined,
|
|
339
746
|
policyFor: (guid: string) => PackageFinalizePolicy,
|
|
340
747
|
sourcePath?: string,
|
|
341
|
-
): Promise<
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
return { pack, ...(firstGuid === undefined ? {} : { firstGuid }) };
|
|
748
|
+
): Promise<LegacyPackTransport> {
|
|
749
|
+
const firstGuid = authoredPack.assets[0]?.guid?.toLowerCase();
|
|
750
|
+
if (authoredPack.schemaVersion !== '2.0.0' || firstGuid === undefined) {
|
|
751
|
+
return { pack: authoredPack, ...(firstGuid === undefined ? {} : { firstGuid }) };
|
|
346
752
|
}
|
|
347
|
-
const cooked = await readCookedAuthoredPack(
|
|
348
|
-
if (cooked === undefined) return { pack, firstGuid };
|
|
753
|
+
const cooked = await readCookedAuthoredPack(authoredPack, cookers, sourcePath);
|
|
754
|
+
if (cooked === undefined) return { pack: authoredPack, firstGuid };
|
|
349
755
|
return {
|
|
350
|
-
pack,
|
|
756
|
+
pack: authoredPack,
|
|
351
757
|
firstGuid,
|
|
352
758
|
cooked,
|
|
353
759
|
finalized: await finalizePackageTransportSource(cooked.logicalPackage, policyFor(firstGuid)),
|
|
354
760
|
};
|
|
355
761
|
}
|
|
356
762
|
|
|
357
|
-
/**
|
|
358
|
-
export function
|
|
359
|
-
|
|
763
|
+
/** Materialize a dynamic Pack product through the same dev transport seam. */
|
|
764
|
+
export async function materializePreparedParameterizedScriptablePack(
|
|
765
|
+
prepared: PreparedParameterizedScriptablePack,
|
|
766
|
+
paths: ScriptablePackTransportPaths,
|
|
767
|
+
sink: ScriptablePackTransportSink,
|
|
768
|
+
): Promise<ReadonlyMap<string, string>> {
|
|
769
|
+
const { product, finalized } = prepared;
|
|
770
|
+
await sink.writePackage(paths.packagePath, JSON.stringify(finalized.pack));
|
|
771
|
+
for (const artifact of finalized.artifacts) {
|
|
772
|
+
await sink.writeArtifact(
|
|
773
|
+
paths.artifactPath(artifact.path),
|
|
774
|
+
artifact.bytes,
|
|
775
|
+
artifact.path.endsWith('.json') ? 'application/json' : artifact.mediaType,
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
const receipts = new Map<string, string>();
|
|
779
|
+
for (const asset of product.product.assets) {
|
|
780
|
+
const path = paths.receiptPath(asset.guid);
|
|
781
|
+
await sink.writeReceipt(
|
|
782
|
+
path,
|
|
783
|
+
JSON.stringify({
|
|
784
|
+
guid: asset.guid,
|
|
785
|
+
origin: 'sourceMeta',
|
|
786
|
+
status: 'succeeded',
|
|
787
|
+
inputFingerprint: product.inputFingerprint,
|
|
788
|
+
outputDigest: finalized.digest,
|
|
789
|
+
}),
|
|
790
|
+
);
|
|
791
|
+
receipts.set(asset.guid.toLowerCase(), path);
|
|
792
|
+
}
|
|
793
|
+
return receipts;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function projectParameterizedPackPublication(
|
|
797
|
+
product: ParameterizedScriptablePackBuildProduct,
|
|
360
798
|
): Result<ScriptablePackPublicationFacts, ScriptablePackDomainError> {
|
|
361
799
|
const assets = new Map(product.product.assets.map((asset) => [asset.guid.toLowerCase(), asset]));
|
|
362
800
|
const outputs: AssetPublicationOutput[] = [];
|
|
363
801
|
for (const staged of product.stagedOutputs) {
|
|
364
802
|
const guid = AssetGuid.format(staged.guid).toLowerCase();
|
|
365
803
|
const asset = assets.get(guid);
|
|
366
|
-
if (asset === undefined || staged.digest === undefined) {
|
|
804
|
+
if (asset === undefined || staged.digest === undefined || staged.sourceKey === undefined) {
|
|
367
805
|
return err({
|
|
368
806
|
code: 'pack-source-output-invalid',
|
|
369
|
-
expected: `
|
|
370
|
-
hint: 'repair the
|
|
807
|
+
expected: `parameterized Pack publication output ${guid} to include a product, sourceKey, and digest`,
|
|
808
|
+
hint: 'repair the dynamic Pack output and rebuild the current generation',
|
|
371
809
|
detail: { stage: 'publication', guid },
|
|
372
810
|
});
|
|
373
811
|
}
|
|
374
812
|
outputs.push({
|
|
375
813
|
guid,
|
|
376
|
-
sourceKey: staged.sourceKey
|
|
814
|
+
sourceKey: staged.sourceKey,
|
|
377
815
|
kind: asset.kind,
|
|
378
816
|
digest: staged.digest,
|
|
379
817
|
refs: asset.refs.map((reference) => reference.guid),
|
|
@@ -390,154 +828,132 @@ export function projectScriptablePackPublication(
|
|
|
390
828
|
});
|
|
391
829
|
}
|
|
392
830
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
)
|
|
399
|
-
const observedAt = Math.trunc(
|
|
400
|
-
Math.max(
|
|
401
|
-
...(await Promise.all(sourceClosure.map(async (entry) => (await stat(entry.path)).mtimeMs))),
|
|
402
|
-
),
|
|
831
|
+
function snapshotSourceForStagedOutputs(
|
|
832
|
+
outputs: readonly ScriptablePackStagedOutput[],
|
|
833
|
+
): import('./scriptable-pack.js').ScriptablePackAssetSnapshotSource | undefined {
|
|
834
|
+
if (outputs.length === 0) return undefined;
|
|
835
|
+
const byGuid = new Map(
|
|
836
|
+
outputs.map((output) => [AssetGuid.format(output.guid).toLowerCase(), output]),
|
|
403
837
|
);
|
|
404
|
-
return {
|
|
838
|
+
return {
|
|
839
|
+
async readByGuid(guid) {
|
|
840
|
+
const key = AssetGuid.format(guid).toLowerCase();
|
|
841
|
+
const output = byGuid.get(key);
|
|
842
|
+
if (output === undefined) {
|
|
843
|
+
return err(
|
|
844
|
+
new AssetError({
|
|
845
|
+
code: 'asset-not-found',
|
|
846
|
+
expected: `an available Asset snapshot for ${key}`,
|
|
847
|
+
hint: 'publish the referenced Pack or repair the content dependency',
|
|
848
|
+
}),
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
return ok({
|
|
852
|
+
asset: structuredClone(output.asset),
|
|
853
|
+
generation: 1,
|
|
854
|
+
digest: output.digest ?? 'sha256:staged',
|
|
855
|
+
});
|
|
856
|
+
},
|
|
857
|
+
};
|
|
405
858
|
}
|
|
406
859
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
{
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
if (entries.has(source.displaySourcePath)) continue;
|
|
422
|
-
entries.set(source.displaySourcePath, {
|
|
423
|
-
source,
|
|
424
|
-
definition: source.definition,
|
|
425
|
-
closure: source.sourceClosure,
|
|
426
|
-
});
|
|
427
|
-
}
|
|
860
|
+
function composeParameterizedAssetSource(
|
|
861
|
+
staged: import('./scriptable-pack.js').ScriptablePackAssetSnapshotSource | undefined,
|
|
862
|
+
published: import('./scriptable-pack.js').ScriptablePackAssetSnapshotSource | undefined,
|
|
863
|
+
): import('./scriptable-pack.js').ScriptablePackAssetSnapshotSource | undefined {
|
|
864
|
+
if (staged === undefined) return published;
|
|
865
|
+
if (published === undefined) return staged;
|
|
866
|
+
return {
|
|
867
|
+
async readByGuid(guid) {
|
|
868
|
+
const local = await staged.readByGuid(guid);
|
|
869
|
+
if (local.ok || local.error.code !== 'asset-not-found') return local;
|
|
870
|
+
return published.readByGuid(guid);
|
|
871
|
+
},
|
|
872
|
+
};
|
|
873
|
+
}
|
|
428
874
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
sourceClosure: entry.closure,
|
|
443
|
-
authoringContractVersion: 'scriptable-pack-production/1',
|
|
444
|
-
});
|
|
445
|
-
if (!produced.ok) return produced;
|
|
446
|
-
const observedClosure = await inventoryScriptablePackSource(entry.source.sourcePath);
|
|
447
|
-
if (JSON.stringify(observedClosure) !== JSON.stringify(entry.closure)) {
|
|
448
|
-
return err({
|
|
449
|
-
code: 'pack-source-load-failed',
|
|
450
|
-
expected: 'the complete ScriptablePack module closure to remain fixed during one build',
|
|
451
|
-
hint: 'retry the build after source and helper writes have settled',
|
|
452
|
-
detail: {
|
|
453
|
-
sourcePath: displaySourcePath,
|
|
454
|
-
reason: 'source-changed',
|
|
455
|
-
phase: 'build',
|
|
456
|
-
diagnostic: 'module closure changed while the staged generation was building',
|
|
457
|
-
},
|
|
458
|
-
});
|
|
459
|
-
}
|
|
460
|
-
products.set(displaySourcePath, produced.value);
|
|
461
|
-
return ok(produced.value.stagedOutputs);
|
|
462
|
-
},
|
|
463
|
-
}),
|
|
875
|
+
/**
|
|
876
|
+
* Build parameterized sources in one generation and hand their terminal
|
|
877
|
+
* products to the ordinary Pack v2 finalizer. The worklist is deliberately
|
|
878
|
+
* below publication: it only decides which current-generation products exist.
|
|
879
|
+
*/
|
|
880
|
+
export async function produceParameterizedScriptablePackProducts(
|
|
881
|
+
options: ParameterizedScriptablePackProductionOptions,
|
|
882
|
+
): Promise<Result<ReadonlyMap<string, PreparedParameterizedScriptablePack>, unknown>> {
|
|
883
|
+
if (options.sources.length === 0) return ok(new Map());
|
|
884
|
+
const external = options.declaredExternalOutputs ?? [];
|
|
885
|
+
const assetSource = composeParameterizedAssetSource(
|
|
886
|
+
snapshotSourceForStagedOutputs(external),
|
|
887
|
+
options.assetSource,
|
|
464
888
|
);
|
|
465
|
-
const
|
|
466
|
-
.
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
889
|
+
const availableGuids = new Set([
|
|
890
|
+
...(options.availableGuids ?? []),
|
|
891
|
+
...external.map((output) => AssetGuid.format(output.guid).toLowerCase()),
|
|
892
|
+
]);
|
|
893
|
+
const workItems: ParameterizedScriptablePackWorkItem[] = options.sources.map((source) => ({
|
|
894
|
+
definition: source.definition,
|
|
895
|
+
sourcePath: source.sourcePath,
|
|
896
|
+
...(source.subjectPackageId === undefined ? {} : { subjectPackageId: source.subjectPackageId }),
|
|
897
|
+
...(source.values === undefined ? {} : { values: source.values }),
|
|
898
|
+
...(source.inheritedValues === undefined ? {} : { inheritedValues: source.inheritedValues }),
|
|
899
|
+
sourceClosure: source.sourceClosure,
|
|
900
|
+
}));
|
|
901
|
+
const worklist = await buildParameterizedScriptablePackWorklist({
|
|
902
|
+
subjects: workItems,
|
|
903
|
+
outputs: createStandardAssetOutputProducerRegistry(
|
|
904
|
+
options.sources.flatMap((source) =>
|
|
905
|
+
projectScriptablePackSceneComponents(source.definition.sceneComponents),
|
|
471
906
|
),
|
|
472
|
-
)
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
declaredExternalOutputs,
|
|
907
|
+
),
|
|
908
|
+
...(assetSource === undefined ? {} : { assetSource }),
|
|
909
|
+
availableGuids,
|
|
910
|
+
...(options.incomingRefs === undefined ? {} : { incomingRefs: options.incomingRefs }),
|
|
911
|
+
...(options.maxPasses === undefined ? {} : { maxPasses: options.maxPasses }),
|
|
478
912
|
});
|
|
913
|
+
if (!worklist.ok) return worklist;
|
|
479
914
|
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
retryable: true,
|
|
490
|
-
recoveryActions: ['rebuild-source-inventory'],
|
|
491
|
-
detail: { sourcePath: source.displaySourcePath },
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
const first = Object.values(entry.definition.assets)[0];
|
|
495
|
-
if (first === undefined) {
|
|
496
|
-
return err({
|
|
497
|
-
code: 'pack-source-output-invalid',
|
|
498
|
-
expected: 'at least one declared ScriptablePack output',
|
|
499
|
-
hint: 'add an output descriptor before building the package',
|
|
500
|
-
detail: { missingGuids: [], unexpectedSourceKeys: [], kindMismatches: [] },
|
|
501
|
-
});
|
|
502
|
-
}
|
|
503
|
-
const staged = await stagedSource.readByGuid(first.guid);
|
|
504
|
-
if (!staged.ok) return err(staged.error);
|
|
505
|
-
const produced = products.get(source.displaySourcePath);
|
|
506
|
-
if (produced === undefined) {
|
|
915
|
+
const productsBySource = new Map(
|
|
916
|
+
worklist.value.buildProducts
|
|
917
|
+
.filter((product) => product.product.sourceKey !== undefined)
|
|
918
|
+
.map((product) => [product.product.sourceKey as string, product]),
|
|
919
|
+
);
|
|
920
|
+
const prepared = new Map<string, PreparedParameterizedScriptablePack>();
|
|
921
|
+
for (const source of options.sources) {
|
|
922
|
+
const product = productsBySource.get(source.sourcePath);
|
|
923
|
+
if (product === undefined) {
|
|
507
924
|
return err({
|
|
508
925
|
code: 'pack-source-output-invalid',
|
|
509
|
-
expected: '
|
|
510
|
-
hint: '
|
|
511
|
-
detail: {
|
|
512
|
-
missingGuids: [AssetGuid.format(first.guid)],
|
|
513
|
-
unexpectedSourceKeys: [],
|
|
514
|
-
kindMismatches: [],
|
|
515
|
-
},
|
|
926
|
+
expected: 'one terminal dynamic Pack product for every source subject',
|
|
927
|
+
hint: 'rerun the bounded generation worklist and inspect its subject map',
|
|
928
|
+
detail: { sourcePath: source.sourcePath },
|
|
516
929
|
});
|
|
517
930
|
}
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
931
|
+
const policy = typeof source.policy === 'function' ? source.policy(product) : source.policy;
|
|
932
|
+
const finalized = await finalizePackageTransportSource(
|
|
933
|
+
projectImportProductForBuild(product.product),
|
|
934
|
+
policy,
|
|
935
|
+
);
|
|
936
|
+
const facts = projectParameterizedPackPublication(product);
|
|
937
|
+
if (!facts.ok) return facts;
|
|
524
938
|
const revision = await scriptablePackResourceRevision(
|
|
525
939
|
source.displaySourcePath,
|
|
526
|
-
|
|
940
|
+
product.inputFingerprint,
|
|
527
941
|
source.sourceClosure,
|
|
528
942
|
);
|
|
529
943
|
const publication = createAcceptedPublication({
|
|
530
944
|
sourcePath: source.displaySourcePath,
|
|
531
|
-
sourceRevision:
|
|
945
|
+
sourceRevision: product.inputFingerprint,
|
|
532
946
|
generation: source.publicationGeneration,
|
|
533
947
|
digest: finalized.digest,
|
|
534
948
|
packageUrl: finalized.packageUrl,
|
|
535
|
-
inputFingerprint:
|
|
949
|
+
inputFingerprint: product.inputFingerprint,
|
|
536
950
|
outputs: facts.value.outputs,
|
|
537
|
-
externalEvidence:
|
|
951
|
+
externalEvidence: product.externalEvidence,
|
|
538
952
|
});
|
|
539
953
|
prepared.set(source.displaySourcePath, {
|
|
540
|
-
product:
|
|
954
|
+
product: {
|
|
955
|
+
...product,
|
|
956
|
+
},
|
|
541
957
|
finalized,
|
|
542
958
|
facts: facts.value,
|
|
543
959
|
revision,
|
|
@@ -546,3 +962,34 @@ export async function produceScriptablePackProducts(
|
|
|
546
962
|
}
|
|
547
963
|
return ok(prepared);
|
|
548
964
|
}
|
|
965
|
+
|
|
966
|
+
/** Project the immutable module closure into the Catalog revision contract. */
|
|
967
|
+
async function scriptablePackResourceRevision(
|
|
968
|
+
displaySourcePath: string,
|
|
969
|
+
digest: string,
|
|
970
|
+
sourceClosure: readonly ScriptablePackSourceClosureEntry[],
|
|
971
|
+
): Promise<ResourceRevision> {
|
|
972
|
+
const mtimes = await Promise.all(
|
|
973
|
+
sourceClosure.map(async (entry) => {
|
|
974
|
+
try {
|
|
975
|
+
return (await stat(entry.path)).mtimeMs;
|
|
976
|
+
} catch (error) {
|
|
977
|
+
// Direct Pack declarations may be supplied with a logical source path
|
|
978
|
+
// and sourceText rather than a file-backed module. The digest remains
|
|
979
|
+
// the authoritative revision in that case; preserve filesystem errors
|
|
980
|
+
// other than a missing path so real source failures stay visible.
|
|
981
|
+
if (
|
|
982
|
+
error !== null &&
|
|
983
|
+
typeof error === 'object' &&
|
|
984
|
+
'code' in error &&
|
|
985
|
+
(error.code === 'ENOENT' || error.code === 'ENOTDIR')
|
|
986
|
+
) {
|
|
987
|
+
return 0;
|
|
988
|
+
}
|
|
989
|
+
throw error;
|
|
990
|
+
}
|
|
991
|
+
}),
|
|
992
|
+
);
|
|
993
|
+
const observedAt = mtimes.length === 0 ? 0 : Math.trunc(Math.max(...mtimes));
|
|
994
|
+
return { digest, observedAt, rootId: displaySourcePath };
|
|
995
|
+
}
|