@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.
- 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__/scriptable-pack-build.unit.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-build.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 +12 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +2850 -1157
- 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/scriptable-pack-build.d.ts +68 -0
- package/dist/scriptable-pack-build.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 +68 -37
- 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 +15 -27
- 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 +7 -0
- 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__/scriptable-pack-build.unit.test.ts +267 -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 +2 -0
- 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 +167 -1
- package/src/browser.ts +5 -0
- package/src/build-production.ts +491 -140
- 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 +47 -16
- package/src/mesh-bin.ts +38 -0
- package/src/mesh-lod.ts +265 -0
- package/src/scriptable-pack-build.ts +725 -0
- package/src/scriptable-pack-file-snapshot.ts +346 -0
- package/src/scriptable-pack-host.ts +785 -340
- package/src/scriptable-pack-output-producers.ts +55 -0
- package/src/scriptable-pack-staged-snapshot.ts +12 -11
- package/src/scriptable-pack.ts +18 -486
- package/src/source-package-errors.ts +6 -0
- package/src/source-package-publication.ts +273 -11
- 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 AnyScriptablePackDefinition,
|
|
24
|
+
type DirectPackAssetProjection,
|
|
25
|
+
isScriptablePackAssetKind,
|
|
26
|
+
parsePackSourceJson,
|
|
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,17 +45,21 @@ 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 {
|
|
35
|
-
|
|
48
|
+
import type {
|
|
49
|
+
ScriptablePackAssetSnapshotSource,
|
|
50
|
+
ScriptablePackDomainError,
|
|
51
|
+
ScriptablePackStagedOutput,
|
|
52
|
+
} from './scriptable-pack.js';
|
|
36
53
|
import {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
54
|
+
buildScriptablePack,
|
|
55
|
+
buildScriptablePackWorklist,
|
|
56
|
+
type ScriptablePackBuildProduct,
|
|
57
|
+
type ScriptablePackBuildWorkItem,
|
|
58
|
+
} from './scriptable-pack-build.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>>;
|
|
@@ -49,25 +67,41 @@ type FinalizedPackageTransport = Awaited<ReturnType<typeof finalizePackageTransp
|
|
|
49
67
|
export interface ScriptablePackInput {
|
|
50
68
|
readonly sourcePath: string;
|
|
51
69
|
readonly displaySourcePath: string;
|
|
52
|
-
readonly definition:
|
|
70
|
+
readonly definition: AnyScriptablePackDefinition;
|
|
53
71
|
readonly sourceClosure: readonly ScriptablePackSourceClosureEntry[];
|
|
54
72
|
readonly publicationGeneration: number;
|
|
55
|
-
readonly policy:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
readonly
|
|
60
|
-
readonly
|
|
73
|
+
readonly policy:
|
|
74
|
+
| PackageFinalizePolicy
|
|
75
|
+
| ((product: ScriptablePackBuildProduct) => 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
|
+
>;
|
|
61
81
|
}
|
|
62
82
|
|
|
63
83
|
export interface PreparedScriptablePack {
|
|
64
|
-
readonly product:
|
|
84
|
+
readonly product: ScriptablePackBuildProduct;
|
|
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 ScriptablePackProductionOptions {
|
|
92
|
+
readonly sources: readonly ScriptablePackInput[];
|
|
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,627 @@ 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 = parsePackSourceJson(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: ScriptablePackBuildProduct;
|
|
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(input: DirectPackTransportInput): ScriptablePackBuildProduct {
|
|
433
|
+
const inputFingerprint = `sha256:${packageTransportRevision({
|
|
434
|
+
packageId: input.projected.packageId,
|
|
435
|
+
sourceRevision: input.sourceRevision,
|
|
436
|
+
assets: input.projected.assets,
|
|
437
|
+
})}`;
|
|
438
|
+
const assets: ImportedAsset<unknown>[] = input.projected.assets.map((asset) => ({
|
|
439
|
+
guid: asset.guid,
|
|
440
|
+
kind: asset.kind,
|
|
441
|
+
...(asset.name === undefined ? {} : { name: asset.name }),
|
|
442
|
+
payload: directRuntimePayload(asset),
|
|
443
|
+
refs: asset.refs.map(directReference),
|
|
444
|
+
artifacts: {},
|
|
445
|
+
}));
|
|
446
|
+
return {
|
|
447
|
+
product: {
|
|
448
|
+
assets,
|
|
449
|
+
sourceDependencies: [input.sourcePath],
|
|
450
|
+
refs: assets.flatMap((asset) => asset.refs),
|
|
451
|
+
artifacts: importedAssetArtifacts(assets),
|
|
452
|
+
receipts: assets.map((asset) => ({
|
|
453
|
+
guid: asset.guid,
|
|
454
|
+
origin: 'authoredPack' as const,
|
|
455
|
+
status: 'succeeded' as const,
|
|
456
|
+
inputFingerprint,
|
|
457
|
+
})),
|
|
458
|
+
diagnostics: [],
|
|
459
|
+
sourceRevision: inputFingerprint,
|
|
460
|
+
sourceKey: input.sourcePath,
|
|
461
|
+
},
|
|
462
|
+
stagedOutputs: input.projected.assets.map((asset) => ({
|
|
463
|
+
guid: directGuid(asset.guid),
|
|
464
|
+
sourceKey: asset.sourceKey,
|
|
465
|
+
asset: {
|
|
466
|
+
...(directRuntimePayload(asset) as Record<string, unknown>),
|
|
467
|
+
kind: asset.kind,
|
|
468
|
+
} as Asset,
|
|
469
|
+
digest: `sha256:${packageTransportRevision({ kind: asset.kind, payload: asset.payload })}`,
|
|
470
|
+
})),
|
|
471
|
+
externalEvidence: [],
|
|
472
|
+
inputFingerprint,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function directGuid(value: string): AssetGuidType {
|
|
477
|
+
const parsed = AssetGuid.parse(value);
|
|
478
|
+
if (!parsed.ok) throw parsed.error;
|
|
479
|
+
return parsed.value;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function nativeRefs(refs: readonly string[]): AssetRef[] {
|
|
483
|
+
return refs.map((guid) => {
|
|
484
|
+
const parsed = AssetGuid.parse(guid);
|
|
485
|
+
if (!parsed.ok) throw parsed.error;
|
|
486
|
+
return { guid: AssetGuid.format(parsed.value) };
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function nativeCookSource(asset: { readonly kind: string; readonly payload: unknown }): unknown {
|
|
491
|
+
if (
|
|
492
|
+
asset.kind !== 'material' ||
|
|
493
|
+
asset.payload === null ||
|
|
494
|
+
typeof asset.payload !== 'object' ||
|
|
495
|
+
Array.isArray(asset.payload)
|
|
496
|
+
) {
|
|
497
|
+
return asset.payload;
|
|
498
|
+
}
|
|
499
|
+
return { ...(asset.payload as Record<string, unknown>), kind: asset.kind };
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function isEngineOwnedMaterial(asset: {
|
|
503
|
+
readonly kind: string;
|
|
504
|
+
readonly payload: unknown;
|
|
505
|
+
}): boolean {
|
|
506
|
+
if (
|
|
507
|
+
asset.kind !== 'material' ||
|
|
508
|
+
asset.payload === null ||
|
|
509
|
+
typeof asset.payload !== 'object' ||
|
|
510
|
+
Array.isArray(asset.payload)
|
|
511
|
+
) {
|
|
512
|
+
return false;
|
|
513
|
+
}
|
|
514
|
+
return isEngineMaterial(asset.payload as Pick<MaterialAsset, 'passes'>);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function directRuntimePayload(asset: DirectPackAssetProjection): unknown {
|
|
518
|
+
if (
|
|
519
|
+
asset.kind !== 'ui' ||
|
|
520
|
+
asset.payload === null ||
|
|
521
|
+
typeof asset.payload !== 'object' ||
|
|
522
|
+
Array.isArray(asset.payload)
|
|
523
|
+
) {
|
|
524
|
+
return asset.payload;
|
|
525
|
+
}
|
|
526
|
+
return { ...(asset.payload as Record<string, unknown>), guid: asset.guid };
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function directAssetForProducer(asset: DirectPackAssetProjection): Asset {
|
|
530
|
+
const payload = {
|
|
531
|
+
...(directRuntimePayload(asset) as Record<string, unknown>),
|
|
532
|
+
kind: asset.kind,
|
|
533
|
+
};
|
|
534
|
+
if (asset.kind !== 'mesh') return payload as Asset;
|
|
535
|
+
return (normalizeMeshPayload(payload, asset.refs) ?? payload) as Asset;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/** Cook one direct v3 Pack through the same producer/finalizer seam as pack.ts. */
|
|
539
|
+
export async function prepareDirectPackTransport(
|
|
540
|
+
input: DirectPackTransportInput,
|
|
541
|
+
): Promise<Result<DirectPackTransport, unknown>> {
|
|
542
|
+
const localGuids = new Set(input.projected.assets.map((asset) => asset.guid.toLowerCase()));
|
|
543
|
+
const availableGuids = new Set(
|
|
544
|
+
[...(input.availableGuids ?? []), ...BUILTIN_MESH_ASSETS.map((asset) => asset.guid)].filter(
|
|
545
|
+
(guid) => !localGuids.has(guid.toLowerCase()),
|
|
546
|
+
),
|
|
547
|
+
);
|
|
548
|
+
const directArtifacts = input.projected.assets.find(
|
|
549
|
+
(asset) => asset.artifacts !== undefined && Object.keys(asset.artifacts).length > 0,
|
|
550
|
+
);
|
|
551
|
+
if (directArtifacts !== undefined) {
|
|
552
|
+
return err({
|
|
553
|
+
code: 'pack-parameter-invalid',
|
|
554
|
+
expected: 'direct v3 authoring entries to leave artifacts empty for the producer',
|
|
555
|
+
hint: 'remove authored artifact data and let the registered Asset producer create it',
|
|
556
|
+
detail: { sourcePath: input.sourcePath, sourceKey: directArtifacts.sourceKey },
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
const packageId = PackageId.parse(input.projected.packageId);
|
|
560
|
+
if (!packageId.ok) return err(packageId.error);
|
|
561
|
+
const cookerRegistry = new NativeCookerRegistry();
|
|
562
|
+
for (const cooker of input.cookers ?? []) cookerRegistry.register(cooker);
|
|
563
|
+
const nativeDrafts = new Map<string, NativeCookDraft>();
|
|
564
|
+
const nativeFingerprints = new Map<string, string>();
|
|
565
|
+
for (const asset of input.projected.assets) {
|
|
566
|
+
if (!isScriptablePackAssetKind(asset.kind)) continue;
|
|
567
|
+
if (cookerRegistry.get(asset.kind) === undefined) continue;
|
|
568
|
+
if (isEngineOwnedMaterial(asset)) continue;
|
|
569
|
+
const draft = await cookerRegistry.runDraft(asset.kind, {
|
|
570
|
+
guid: asset.guid,
|
|
571
|
+
// Direct v3 entries keep `kind` at the entry boundary. Reattach it for
|
|
572
|
+
// producer contracts such as MaterialAsset, whose payload is otherwise
|
|
573
|
+
// intentionally allowed to omit the discriminant.
|
|
574
|
+
source: nativeCookSource(asset),
|
|
575
|
+
sourceKey: asset.sourceKey,
|
|
576
|
+
sourcePath: input.sourcePath,
|
|
577
|
+
refs: asset.refs,
|
|
578
|
+
});
|
|
579
|
+
if (!draft.ok) return err(draft.error);
|
|
580
|
+
if (draft.value.guid.toLowerCase() !== asset.guid.toLowerCase()) {
|
|
581
|
+
return err({
|
|
582
|
+
code: 'pack-parameter-invalid',
|
|
583
|
+
expected: 'a direct Pack cooker to preserve the derived AssetGuid',
|
|
584
|
+
hint: 'repair the native cooker output GUID and rebuild the direct Pack',
|
|
585
|
+
detail: {
|
|
586
|
+
sourcePath: input.sourcePath,
|
|
587
|
+
sourceKey: asset.sourceKey,
|
|
588
|
+
expectedGuid: asset.guid,
|
|
589
|
+
actualGuid: draft.value.guid,
|
|
590
|
+
},
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
nativeDrafts.set(asset.guid.toLowerCase(), draft.value);
|
|
594
|
+
nativeFingerprints.set(asset.sourceKey, draft.value.inputFingerprint);
|
|
595
|
+
}
|
|
596
|
+
const unsupported = input.projected.assets.filter(
|
|
597
|
+
(asset) => !isScriptablePackAssetKind(asset.kind),
|
|
598
|
+
);
|
|
599
|
+
if (unsupported.length > 0 && unsupported.length !== input.projected.assets.length) {
|
|
600
|
+
return err({
|
|
601
|
+
code: 'pack-parameter-invalid',
|
|
602
|
+
expected: 'a direct Pack to contain only producer-backed Engine Assets or only direct PODs',
|
|
603
|
+
hint: 'split custom direct PODs from producer-backed Assets into separate Packs',
|
|
604
|
+
detail: {
|
|
605
|
+
sourcePath: input.sourcePath,
|
|
606
|
+
unsupportedKinds: [...new Set(unsupported.map((asset) => asset.kind))].sort(),
|
|
607
|
+
},
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
if (unsupported.length === input.projected.assets.length) {
|
|
611
|
+
const raw = rawDirectProduct(input);
|
|
612
|
+
const known = new Set([...availableGuids, ...localGuids]);
|
|
613
|
+
const missing = new Set<string>();
|
|
614
|
+
for (const asset of raw.product.assets) {
|
|
615
|
+
for (const reference of asset.refs) {
|
|
616
|
+
if (!known.has(reference.guid.toLowerCase())) missing.add(reference.guid.toLowerCase());
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
if (missing.size > 0) return err(directReferenceFailure(input.sourcePath, [...missing]));
|
|
620
|
+
const finalized = await finalizePackageTransportSource(
|
|
621
|
+
projectImportProductForBuild(raw.product),
|
|
622
|
+
input.policy,
|
|
623
|
+
);
|
|
624
|
+
const facts = projectScriptablePackPublication(raw);
|
|
625
|
+
if (!facts.ok) return facts;
|
|
626
|
+
const displaySourcePath = input.displaySourcePath ?? input.sourcePath;
|
|
627
|
+
const revision = await scriptablePackResourceRevision(displaySourcePath, raw.inputFingerprint, [
|
|
628
|
+
{ path: input.sourcePath, digest: input.sourceRevision },
|
|
629
|
+
]);
|
|
630
|
+
return ok({
|
|
631
|
+
projected: input.projected,
|
|
632
|
+
product: raw,
|
|
633
|
+
finalized,
|
|
634
|
+
facts: facts.value,
|
|
635
|
+
revision,
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
const definition = {
|
|
639
|
+
schemaVersion: '2.0.0' as const,
|
|
640
|
+
packageId: packageId.value,
|
|
641
|
+
build: () =>
|
|
642
|
+
ok(
|
|
643
|
+
Object.fromEntries(
|
|
644
|
+
input.projected.assets.map((asset) => [
|
|
645
|
+
asset.sourceKey,
|
|
646
|
+
{
|
|
647
|
+
...((nativeDrafts.get(asset.guid.toLowerCase())?.payload as
|
|
648
|
+
| Record<string, unknown>
|
|
649
|
+
| undefined) ?? directAssetForProducer(asset)),
|
|
650
|
+
kind: asset.kind,
|
|
651
|
+
} as Asset,
|
|
652
|
+
]),
|
|
653
|
+
),
|
|
654
|
+
),
|
|
655
|
+
} as AnyScriptablePackDefinition;
|
|
656
|
+
const outputs = createStandardAssetOutputProducerRegistry();
|
|
657
|
+
outputs.register(createPreExternalizedSceneAssetOutputProducer());
|
|
658
|
+
const built = await buildScriptablePack({
|
|
659
|
+
definition,
|
|
660
|
+
sourcePath: input.sourcePath,
|
|
661
|
+
outputs,
|
|
662
|
+
sourceClosure: [{ path: input.sourcePath, digest: input.sourceRevision }],
|
|
663
|
+
availableGuids,
|
|
664
|
+
deferReferenceValidation: true,
|
|
665
|
+
});
|
|
666
|
+
if (!built.ok) return built;
|
|
667
|
+
const declaredByGuid = new Map(
|
|
668
|
+
input.projected.assets.map((asset) => [asset.guid.toLowerCase(), asset.refs]),
|
|
669
|
+
);
|
|
670
|
+
const assets = built.value.product.assets.map((asset) => ({
|
|
671
|
+
...asset,
|
|
672
|
+
...(nativeDrafts.has(asset.guid.toLowerCase())
|
|
673
|
+
? {
|
|
674
|
+
payload: nativeDrafts.get(asset.guid.toLowerCase())?.payload as Record<string, unknown>,
|
|
675
|
+
refs: mergeDirectReferences(
|
|
676
|
+
nativeRefs(nativeDrafts.get(asset.guid.toLowerCase())?.refs ?? []),
|
|
677
|
+
declaredByGuid.get(asset.guid.toLowerCase()) ?? [],
|
|
678
|
+
),
|
|
679
|
+
artifacts: nativeDrafts.get(asset.guid.toLowerCase())?.artifacts ?? {},
|
|
680
|
+
}
|
|
681
|
+
: {
|
|
682
|
+
refs: mergeDirectReferences(
|
|
683
|
+
asset.refs,
|
|
684
|
+
declaredByGuid.get(asset.guid.toLowerCase()) ?? [],
|
|
685
|
+
),
|
|
686
|
+
}),
|
|
687
|
+
}));
|
|
688
|
+
const missing = new Set<string>();
|
|
689
|
+
const known = new Set([...availableGuids, ...localGuids]);
|
|
690
|
+
for (const asset of assets) {
|
|
691
|
+
for (const reference of asset.refs) {
|
|
692
|
+
if (!known.has(reference.guid.toLowerCase())) missing.add(reference.guid.toLowerCase());
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if (missing.size > 0) return err(directReferenceFailure(input.sourcePath, [...missing]));
|
|
696
|
+
const inputFingerprint = directInputFingerprint(built.value.inputFingerprint, nativeFingerprints);
|
|
697
|
+
const stagedOutputs = input.projected.assets.map((asset) => ({
|
|
698
|
+
guid: directGuid(asset.guid),
|
|
699
|
+
sourceKey: asset.sourceKey,
|
|
700
|
+
asset: directAssetForProducer(asset),
|
|
701
|
+
digest: `sha256:${packageTransportRevision({ kind: asset.kind, payload: asset.payload })}`,
|
|
702
|
+
}));
|
|
703
|
+
const product: ScriptablePackBuildProduct = {
|
|
704
|
+
...built.value,
|
|
705
|
+
inputFingerprint,
|
|
706
|
+
stagedOutputs,
|
|
707
|
+
product: {
|
|
708
|
+
...built.value.product,
|
|
709
|
+
assets,
|
|
710
|
+
refs: assets.flatMap((asset) => asset.refs),
|
|
711
|
+
artifacts: importedAssetArtifacts(assets),
|
|
712
|
+
receipts: built.value.product.receipts.map((receipt) => ({
|
|
713
|
+
...receipt,
|
|
714
|
+
inputFingerprint,
|
|
715
|
+
})),
|
|
716
|
+
sourceRevision: inputFingerprint,
|
|
717
|
+
},
|
|
718
|
+
};
|
|
719
|
+
const finalized = await finalizePackageTransportSource(
|
|
720
|
+
projectImportProductForBuild(product.product),
|
|
721
|
+
input.policy,
|
|
722
|
+
);
|
|
723
|
+
const facts = projectScriptablePackPublication(product);
|
|
724
|
+
if (!facts.ok) return facts;
|
|
725
|
+
const displaySourcePath = input.displaySourcePath ?? input.sourcePath;
|
|
726
|
+
const revision = await scriptablePackResourceRevision(
|
|
727
|
+
displaySourcePath,
|
|
728
|
+
product.inputFingerprint,
|
|
729
|
+
[{ path: input.sourcePath, digest: input.sourceRevision }],
|
|
730
|
+
);
|
|
731
|
+
return ok({
|
|
732
|
+
projected: input.projected,
|
|
733
|
+
product,
|
|
734
|
+
finalized,
|
|
735
|
+
facts: facts.value,
|
|
736
|
+
revision,
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/** Normalize one explicit Pack v2 transport and invoke its registered cookers once. */
|
|
741
|
+
export async function prepareLegacyPackTransport(
|
|
742
|
+
authoredPack: LegacyPackInventoryDocument,
|
|
338
743
|
cookers: readonly NativeCooker[] | undefined,
|
|
339
744
|
policyFor: (guid: string) => PackageFinalizePolicy,
|
|
340
745
|
sourcePath?: string,
|
|
341
|
-
): Promise<
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
return { pack, ...(firstGuid === undefined ? {} : { firstGuid }) };
|
|
746
|
+
): Promise<LegacyPackTransport> {
|
|
747
|
+
const firstGuid = authoredPack.assets[0]?.guid?.toLowerCase();
|
|
748
|
+
if (authoredPack.schemaVersion !== '2.0.0' || firstGuid === undefined) {
|
|
749
|
+
return { pack: authoredPack, ...(firstGuid === undefined ? {} : { firstGuid }) };
|
|
346
750
|
}
|
|
347
|
-
const cooked = await readCookedAuthoredPack(
|
|
348
|
-
if (cooked === undefined) return { pack, firstGuid };
|
|
751
|
+
const cooked = await readCookedAuthoredPack(authoredPack, cookers, sourcePath);
|
|
752
|
+
if (cooked === undefined) return { pack: authoredPack, firstGuid };
|
|
349
753
|
return {
|
|
350
|
-
pack,
|
|
754
|
+
pack: authoredPack,
|
|
351
755
|
firstGuid,
|
|
352
756
|
cooked,
|
|
353
757
|
finalized: await finalizePackageTransportSource(cooked.logicalPackage, policyFor(firstGuid)),
|
|
354
758
|
};
|
|
355
759
|
}
|
|
356
760
|
|
|
357
|
-
/**
|
|
358
|
-
export function
|
|
359
|
-
|
|
761
|
+
/** Materialize a dynamic Pack product through the same dev transport seam. */
|
|
762
|
+
export async function materializePreparedScriptablePack(
|
|
763
|
+
prepared: PreparedScriptablePack,
|
|
764
|
+
paths: ScriptablePackTransportPaths,
|
|
765
|
+
sink: ScriptablePackTransportSink,
|
|
766
|
+
): Promise<ReadonlyMap<string, string>> {
|
|
767
|
+
const { product, finalized } = prepared;
|
|
768
|
+
await sink.writePackage(paths.packagePath, JSON.stringify(finalized.pack));
|
|
769
|
+
for (const artifact of finalized.artifacts) {
|
|
770
|
+
await sink.writeArtifact(
|
|
771
|
+
paths.artifactPath(artifact.path),
|
|
772
|
+
artifact.bytes,
|
|
773
|
+
artifact.path.endsWith('.json') ? 'application/json' : artifact.mediaType,
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
const receipts = new Map<string, string>();
|
|
777
|
+
for (const asset of product.product.assets) {
|
|
778
|
+
const path = paths.receiptPath(asset.guid);
|
|
779
|
+
await sink.writeReceipt(
|
|
780
|
+
path,
|
|
781
|
+
JSON.stringify({
|
|
782
|
+
guid: asset.guid,
|
|
783
|
+
origin: 'sourceMeta',
|
|
784
|
+
status: 'succeeded',
|
|
785
|
+
inputFingerprint: product.inputFingerprint,
|
|
786
|
+
outputDigest: finalized.digest,
|
|
787
|
+
}),
|
|
788
|
+
);
|
|
789
|
+
receipts.set(asset.guid.toLowerCase(), path);
|
|
790
|
+
}
|
|
791
|
+
return receipts;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function projectScriptablePackPublication(
|
|
795
|
+
product: ScriptablePackBuildProduct,
|
|
360
796
|
): Result<ScriptablePackPublicationFacts, ScriptablePackDomainError> {
|
|
361
797
|
const assets = new Map(product.product.assets.map((asset) => [asset.guid.toLowerCase(), asset]));
|
|
362
798
|
const outputs: AssetPublicationOutput[] = [];
|
|
363
799
|
for (const staged of product.stagedOutputs) {
|
|
364
800
|
const guid = AssetGuid.format(staged.guid).toLowerCase();
|
|
365
801
|
const asset = assets.get(guid);
|
|
366
|
-
if (asset === undefined || staged.digest === undefined) {
|
|
802
|
+
if (asset === undefined || staged.digest === undefined || staged.sourceKey === undefined) {
|
|
367
803
|
return err({
|
|
368
804
|
code: 'pack-source-output-invalid',
|
|
369
|
-
expected: `ScriptablePack publication output ${guid} to include a product and digest`,
|
|
370
|
-
hint: 'repair the
|
|
805
|
+
expected: `ScriptablePack publication output ${guid} to include a product, sourceKey, and digest`,
|
|
806
|
+
hint: 'repair the dynamic Pack output and rebuild the current generation',
|
|
371
807
|
detail: { stage: 'publication', guid },
|
|
372
808
|
});
|
|
373
809
|
}
|
|
374
810
|
outputs.push({
|
|
375
811
|
guid,
|
|
376
|
-
sourceKey: staged.sourceKey
|
|
812
|
+
sourceKey: staged.sourceKey,
|
|
377
813
|
kind: asset.kind,
|
|
378
814
|
digest: staged.digest,
|
|
379
815
|
refs: asset.refs.map((reference) => reference.guid),
|
|
@@ -390,154 +826,132 @@ export function projectScriptablePackPublication(
|
|
|
390
826
|
});
|
|
391
827
|
}
|
|
392
828
|
|
|
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
|
-
),
|
|
829
|
+
function snapshotSourceForStagedOutputs(
|
|
830
|
+
outputs: readonly ScriptablePackStagedOutput[],
|
|
831
|
+
): import('./scriptable-pack.js').ScriptablePackAssetSnapshotSource | undefined {
|
|
832
|
+
if (outputs.length === 0) return undefined;
|
|
833
|
+
const byGuid = new Map(
|
|
834
|
+
outputs.map((output) => [AssetGuid.format(output.guid).toLowerCase(), output]),
|
|
403
835
|
);
|
|
404
|
-
return {
|
|
836
|
+
return {
|
|
837
|
+
async readByGuid(guid) {
|
|
838
|
+
const key = AssetGuid.format(guid).toLowerCase();
|
|
839
|
+
const output = byGuid.get(key);
|
|
840
|
+
if (output === undefined) {
|
|
841
|
+
return err(
|
|
842
|
+
new AssetError({
|
|
843
|
+
code: 'asset-not-found',
|
|
844
|
+
expected: `an available Asset snapshot for ${key}`,
|
|
845
|
+
hint: 'publish the referenced Pack or repair the content dependency',
|
|
846
|
+
}),
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
return ok({
|
|
850
|
+
asset: structuredClone(output.asset),
|
|
851
|
+
generation: 1,
|
|
852
|
+
digest: output.digest ?? 'sha256:staged',
|
|
853
|
+
});
|
|
854
|
+
},
|
|
855
|
+
};
|
|
405
856
|
}
|
|
406
857
|
|
|
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
|
-
}
|
|
858
|
+
function composeScriptablePackAssetSource(
|
|
859
|
+
staged: import('./scriptable-pack.js').ScriptablePackAssetSnapshotSource | undefined,
|
|
860
|
+
published: import('./scriptable-pack.js').ScriptablePackAssetSnapshotSource | undefined,
|
|
861
|
+
): import('./scriptable-pack.js').ScriptablePackAssetSnapshotSource | undefined {
|
|
862
|
+
if (staged === undefined) return published;
|
|
863
|
+
if (published === undefined) return staged;
|
|
864
|
+
return {
|
|
865
|
+
async readByGuid(guid) {
|
|
866
|
+
const local = await staged.readByGuid(guid);
|
|
867
|
+
if (local.ok || local.error.code !== 'asset-not-found') return local;
|
|
868
|
+
return published.readByGuid(guid);
|
|
869
|
+
},
|
|
870
|
+
};
|
|
871
|
+
}
|
|
428
872
|
|
|
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
|
-
}),
|
|
873
|
+
/**
|
|
874
|
+
* Build ScriptablePack sources in one generation and hand their terminal
|
|
875
|
+
* products to the ordinary Pack v2 finalizer. The worklist is deliberately
|
|
876
|
+
* below publication: it only decides which current-generation products exist.
|
|
877
|
+
*/
|
|
878
|
+
export async function produceScriptablePackProducts(
|
|
879
|
+
options: ScriptablePackProductionOptions,
|
|
880
|
+
): Promise<Result<ReadonlyMap<string, PreparedScriptablePack>, unknown>> {
|
|
881
|
+
if (options.sources.length === 0) return ok(new Map());
|
|
882
|
+
const external = options.declaredExternalOutputs ?? [];
|
|
883
|
+
const assetSource = composeScriptablePackAssetSource(
|
|
884
|
+
snapshotSourceForStagedOutputs(external),
|
|
885
|
+
options.assetSource,
|
|
464
886
|
);
|
|
465
|
-
const
|
|
466
|
-
.
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
887
|
+
const availableGuids = new Set([
|
|
888
|
+
...(options.availableGuids ?? []),
|
|
889
|
+
...external.map((output) => AssetGuid.format(output.guid).toLowerCase()),
|
|
890
|
+
]);
|
|
891
|
+
const workItems: ScriptablePackBuildWorkItem[] = options.sources.map((source) => ({
|
|
892
|
+
definition: source.definition,
|
|
893
|
+
sourcePath: source.sourcePath,
|
|
894
|
+
...(source.subjectPackageId === undefined ? {} : { subjectPackageId: source.subjectPackageId }),
|
|
895
|
+
...(source.values === undefined ? {} : { values: source.values }),
|
|
896
|
+
...(source.inheritedValues === undefined ? {} : { inheritedValues: source.inheritedValues }),
|
|
897
|
+
sourceClosure: source.sourceClosure,
|
|
898
|
+
}));
|
|
899
|
+
const worklist = await buildScriptablePackWorklist({
|
|
900
|
+
subjects: workItems,
|
|
901
|
+
outputs: createStandardAssetOutputProducerRegistry(
|
|
902
|
+
options.sources.flatMap((source) =>
|
|
903
|
+
projectScriptablePackSceneComponents(source.definition.sceneComponents),
|
|
471
904
|
),
|
|
472
|
-
)
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
declaredExternalOutputs,
|
|
905
|
+
),
|
|
906
|
+
...(assetSource === undefined ? {} : { assetSource }),
|
|
907
|
+
availableGuids,
|
|
908
|
+
...(options.incomingRefs === undefined ? {} : { incomingRefs: options.incomingRefs }),
|
|
909
|
+
...(options.maxPasses === undefined ? {} : { maxPasses: options.maxPasses }),
|
|
478
910
|
});
|
|
911
|
+
if (!worklist.ok) return worklist;
|
|
479
912
|
|
|
913
|
+
const productsBySource = new Map(
|
|
914
|
+
worklist.value.buildProducts
|
|
915
|
+
.filter((product) => product.product.sourceKey !== undefined)
|
|
916
|
+
.map((product) => [product.product.sourceKey as string, product]),
|
|
917
|
+
);
|
|
480
918
|
const prepared = new Map<string, PreparedScriptablePack>();
|
|
481
|
-
for (const source of sources) {
|
|
482
|
-
const
|
|
483
|
-
if (
|
|
484
|
-
return err({
|
|
485
|
-
code: 'pack-source-path-invalid',
|
|
486
|
-
expected: 'a ScriptablePack source registered in the current inventory',
|
|
487
|
-
actual: source.displaySourcePath,
|
|
488
|
-
hint: 'rebuild the source inventory before producing this path',
|
|
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) {
|
|
919
|
+
for (const source of options.sources) {
|
|
920
|
+
const product = productsBySource.get(source.sourcePath);
|
|
921
|
+
if (product === undefined) {
|
|
496
922
|
return err({
|
|
497
923
|
code: 'pack-source-output-invalid',
|
|
498
|
-
expected: '
|
|
499
|
-
hint: '
|
|
500
|
-
detail: {
|
|
924
|
+
expected: 'one terminal dynamic Pack product for every source subject',
|
|
925
|
+
hint: 'rerun the bounded generation worklist and inspect its subject map',
|
|
926
|
+
detail: { sourcePath: source.sourcePath },
|
|
501
927
|
});
|
|
502
928
|
}
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
hint: 'retry the ScriptablePack production attempt',
|
|
511
|
-
detail: {
|
|
512
|
-
missingGuids: [AssetGuid.format(first.guid)],
|
|
513
|
-
unexpectedSourceKeys: [],
|
|
514
|
-
kindMismatches: [],
|
|
515
|
-
},
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
const transportPolicy =
|
|
519
|
-
typeof source.policy === 'function' ? source.policy(produced) : source.policy;
|
|
520
|
-
const logicalPackage = projectImportProductForBuild(produced.product);
|
|
521
|
-
const finalized = await finalizePackageTransportSource(logicalPackage, transportPolicy);
|
|
522
|
-
const facts = projectScriptablePackPublication(produced);
|
|
523
|
-
if (!facts.ok) return err(facts.error);
|
|
929
|
+
const policy = typeof source.policy === 'function' ? source.policy(product) : source.policy;
|
|
930
|
+
const finalized = await finalizePackageTransportSource(
|
|
931
|
+
projectImportProductForBuild(product.product),
|
|
932
|
+
policy,
|
|
933
|
+
);
|
|
934
|
+
const facts = projectScriptablePackPublication(product);
|
|
935
|
+
if (!facts.ok) return facts;
|
|
524
936
|
const revision = await scriptablePackResourceRevision(
|
|
525
937
|
source.displaySourcePath,
|
|
526
|
-
|
|
938
|
+
product.inputFingerprint,
|
|
527
939
|
source.sourceClosure,
|
|
528
940
|
);
|
|
529
941
|
const publication = createAcceptedPublication({
|
|
530
942
|
sourcePath: source.displaySourcePath,
|
|
531
|
-
sourceRevision:
|
|
943
|
+
sourceRevision: product.inputFingerprint,
|
|
532
944
|
generation: source.publicationGeneration,
|
|
533
945
|
digest: finalized.digest,
|
|
534
946
|
packageUrl: finalized.packageUrl,
|
|
535
|
-
inputFingerprint:
|
|
947
|
+
inputFingerprint: product.inputFingerprint,
|
|
536
948
|
outputs: facts.value.outputs,
|
|
537
|
-
externalEvidence:
|
|
949
|
+
externalEvidence: product.externalEvidence,
|
|
538
950
|
});
|
|
539
951
|
prepared.set(source.displaySourcePath, {
|
|
540
|
-
product:
|
|
952
|
+
product: {
|
|
953
|
+
...product,
|
|
954
|
+
},
|
|
541
955
|
finalized,
|
|
542
956
|
facts: facts.value,
|
|
543
957
|
revision,
|
|
@@ -546,3 +960,34 @@ export async function produceScriptablePackProducts(
|
|
|
546
960
|
}
|
|
547
961
|
return ok(prepared);
|
|
548
962
|
}
|
|
963
|
+
|
|
964
|
+
/** Project the immutable module closure into the Catalog revision contract. */
|
|
965
|
+
async function scriptablePackResourceRevision(
|
|
966
|
+
displaySourcePath: string,
|
|
967
|
+
digest: string,
|
|
968
|
+
sourceClosure: readonly ScriptablePackSourceClosureEntry[],
|
|
969
|
+
): Promise<ResourceRevision> {
|
|
970
|
+
const mtimes = await Promise.all(
|
|
971
|
+
sourceClosure.map(async (entry) => {
|
|
972
|
+
try {
|
|
973
|
+
return (await stat(entry.path)).mtimeMs;
|
|
974
|
+
} catch (error) {
|
|
975
|
+
// Direct Pack declarations may be supplied with a logical source path
|
|
976
|
+
// and sourceText rather than a file-backed module. The digest remains
|
|
977
|
+
// the authoritative revision in that case; preserve filesystem errors
|
|
978
|
+
// other than a missing path so real source failures stay visible.
|
|
979
|
+
if (
|
|
980
|
+
error !== null &&
|
|
981
|
+
typeof error === 'object' &&
|
|
982
|
+
'code' in error &&
|
|
983
|
+
(error.code === 'ENOENT' || error.code === 'ENOTDIR')
|
|
984
|
+
) {
|
|
985
|
+
return 0;
|
|
986
|
+
}
|
|
987
|
+
throw error;
|
|
988
|
+
}
|
|
989
|
+
}),
|
|
990
|
+
);
|
|
991
|
+
const observedAt = mtimes.length === 0 ? 0 : Math.trunc(Math.max(...mtimes));
|
|
992
|
+
return { digest, observedAt, rootId: displaySourcePath };
|
|
993
|
+
}
|