@forgeax/engine-import 0.1.3 → 0.1.6
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 +6 -5
- package/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/owner-chain.integration.test.d.ts +2 -0
- package/dist/__tests__/owner-chain.integration.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-host.unit.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-host.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-product.contract.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-product.contract.test.d.ts.map +1 -0
- package/dist/browser.d.ts +5 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.mjs +782 -0
- package/dist/browser.mjs.map +1 -0
- package/dist/build-production.d.ts +48 -0
- package/dist/build-production.d.ts.map +1 -0
- package/dist/catalog-importer-policy.d.ts +13 -0
- package/dist/catalog-importer-policy.d.ts.map +1 -0
- package/dist/catalog-inventory.d.ts +4 -0
- package/dist/catalog-inventory.d.ts.map +1 -0
- package/dist/import-product.d.ts +16 -25
- package/dist/import-product.d.ts.map +1 -1
- package/dist/import-runner.d.ts +6 -3
- package/dist/import-runner.d.ts.map +1 -1
- package/dist/importer-registry.d.ts +9 -1
- package/dist/importer-registry.d.ts.map +1 -1
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +1380 -155
- package/dist/index.mjs.map +1 -1
- package/dist/mesh-bin.d.ts +1 -1
- package/dist/mesh-bin.d.ts.map +1 -1
- package/dist/mesh-bin.mjs +182 -0
- package/dist/mesh-bin.mjs.map +1 -0
- package/dist/pack-projection.d.ts +5 -0
- package/dist/pack-projection.d.ts.map +1 -0
- package/dist/scriptable-pack-host.d.ts +72 -0
- package/dist/scriptable-pack-host.d.ts.map +1 -0
- package/dist/scriptable-pack-output-producers.d.ts +3 -2
- package/dist/scriptable-pack-output-producers.d.ts.map +1 -1
- package/dist/scriptable-pack-staged-snapshot.d.ts +1 -2
- package/dist/scriptable-pack-staged-snapshot.d.ts.map +1 -1
- package/dist/scriptable-pack.d.ts +6 -18
- package/dist/scriptable-pack.d.ts.map +1 -1
- package/dist/scriptable-source-package.d.ts +14 -0
- package/dist/scriptable-source-package.d.ts.map +1 -0
- package/dist/source-package-errors.d.ts +52 -0
- package/dist/source-package-errors.d.ts.map +1 -0
- package/dist/source-package-publication.d.ts +63 -0
- package/dist/source-package-publication.d.ts.map +1 -0
- package/dist/source-package.d.ts +60 -0
- package/dist/source-package.d.ts.map +1 -0
- package/package.json +13 -7
- package/src/__tests__/import-contract-migration.test.ts +33 -0
- package/src/__tests__/import-local-artifacts.test.ts +3 -1
- package/src/__tests__/owner-chain.integration.test.ts +37 -0
- package/src/__tests__/scriptable-pack-host.unit.test.ts +86 -0
- package/src/__tests__/scriptable-pack-product.contract.test.ts +47 -0
- package/src/__tests__/scriptable-pack-production-producers.unit.test.ts +78 -19
- package/src/__tests__/scriptable-pack-staged-snapshot.unit.test.ts +52 -20
- package/src/browser.ts +14 -0
- package/src/build-production.ts +493 -0
- package/src/catalog-importer-policy.ts +58 -0
- package/src/catalog-inventory.ts +24 -0
- package/src/import-product.ts +37 -69
- package/src/import-runner.ts +36 -7
- package/src/importer-registry.ts +24 -1
- package/src/index.ts +80 -8
- package/src/mesh-bin.ts +2 -2
- package/src/pack-projection.ts +21 -0
- package/src/scriptable-pack-host.ts +544 -0
- package/src/scriptable-pack-output-producers.ts +48 -58
- package/src/scriptable-pack-staged-snapshot.ts +17 -6
- package/src/scriptable-pack.ts +37 -26
- package/src/scriptable-source-package.ts +88 -0
- package/src/source-package-errors.ts +201 -0
- package/src/source-package-publication.ts +295 -0
- package/src/source-package.ts +187 -0
- package/dist/__tests__/material-import-product.unit.test.d.ts +0 -2
- package/dist/__tests__/material-import-product.unit.test.d.ts.map +0 -1
- package/src/__tests__/material-import-product.unit.test.ts +0 -56
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { componentSchema } from '@forgeax/engine-ecs/internal';
|
|
2
1
|
import { AssetGuid } from '@forgeax/engine-pack/guid';
|
|
2
|
+
import type { ScriptablePackSceneComponent } from '@forgeax/engine-pack/source';
|
|
3
3
|
import { externalizeSceneAsset } from '@forgeax/engine-scene';
|
|
4
4
|
import type {
|
|
5
5
|
AnimationGraph,
|
|
6
|
-
Asset,
|
|
7
6
|
AssetGuid as AssetGuidType,
|
|
8
7
|
AssetRef,
|
|
9
8
|
AudioClipAsset,
|
|
@@ -16,7 +15,6 @@ import type {
|
|
|
16
15
|
MeshAsset,
|
|
17
16
|
ParticleEffectAsset,
|
|
18
17
|
RenderPipelineAsset,
|
|
19
|
-
Result,
|
|
20
18
|
SamplerAsset,
|
|
21
19
|
SceneAsset,
|
|
22
20
|
SkeletonAsset,
|
|
@@ -154,18 +152,6 @@ function materialProduct(input: AssetOutputInput): AssetOutputProduct {
|
|
|
154
152
|
};
|
|
155
153
|
}
|
|
156
154
|
|
|
157
|
-
export const materialAssetOutputProducer: AssetOutputProducer = {
|
|
158
|
-
kind: 'material',
|
|
159
|
-
version: 'material-pack/1',
|
|
160
|
-
produce(input): Result<AssetOutputProduct, ImportError> {
|
|
161
|
-
try {
|
|
162
|
-
return ok(materialProduct(input));
|
|
163
|
-
} catch (error) {
|
|
164
|
-
return err(producerError(input, error));
|
|
165
|
-
}
|
|
166
|
-
},
|
|
167
|
-
};
|
|
168
|
-
|
|
169
155
|
function meshProduct(input: AssetOutputInput): AssetOutputProduct {
|
|
170
156
|
if (input.asset.kind !== 'mesh') throw new TypeError('expected MeshAsset');
|
|
171
157
|
const mesh = input.asset as MeshAsset;
|
|
@@ -201,23 +187,19 @@ function meshProduct(input: AssetOutputInput): AssetOutputProduct {
|
|
|
201
187
|
};
|
|
202
188
|
}
|
|
203
189
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
try {
|
|
209
|
-
return ok(meshProduct(input));
|
|
210
|
-
} catch (error) {
|
|
211
|
-
return err(producerError(input, error));
|
|
212
|
-
}
|
|
213
|
-
},
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
function sceneProduct(input: AssetOutputInput): AssetOutputProduct {
|
|
190
|
+
function sceneProduct(
|
|
191
|
+
input: AssetOutputInput,
|
|
192
|
+
components: ReadonlyMap<string, Readonly<Record<string, string>>>,
|
|
193
|
+
): AssetOutputProduct {
|
|
217
194
|
if (input.asset.kind !== 'scene') throw new TypeError('expected SceneAsset');
|
|
218
195
|
const externalized = externalizeSceneAsset(input.asset as SceneAsset, (componentName) => {
|
|
219
|
-
const
|
|
220
|
-
|
|
196
|
+
const schema = components.get(componentName);
|
|
197
|
+
if (schema === undefined) {
|
|
198
|
+
throw new TypeError(
|
|
199
|
+
`ScriptablePack scene component ${componentName} is missing from sceneComponents`,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
return schema;
|
|
221
203
|
});
|
|
222
204
|
if (!externalized.ok) throw new TypeError(`scene field ${externalized.error.field} is invalid`);
|
|
223
205
|
return {
|
|
@@ -227,6 +209,24 @@ function sceneProduct(input: AssetOutputInput): AssetOutputProduct {
|
|
|
227
209
|
};
|
|
228
210
|
}
|
|
229
211
|
|
|
212
|
+
function createSafeProducer(
|
|
213
|
+
kind: AssetOutputProducer['kind'],
|
|
214
|
+
version: string,
|
|
215
|
+
product: (input: AssetOutputInput) => AssetOutputProduct,
|
|
216
|
+
): AssetOutputProducer {
|
|
217
|
+
return {
|
|
218
|
+
kind,
|
|
219
|
+
version,
|
|
220
|
+
produce(input) {
|
|
221
|
+
try {
|
|
222
|
+
return ok(product(input));
|
|
223
|
+
} catch (error) {
|
|
224
|
+
return err(producerError(input, error));
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
230
|
function jsonArtifact(value: unknown): ImportedArtifactBody {
|
|
231
231
|
return {
|
|
232
232
|
mediaType: 'application/json',
|
|
@@ -412,39 +412,29 @@ function ordinaryPodProduct(input: AssetOutputInput): AssetOutputProduct {
|
|
|
412
412
|
}
|
|
413
413
|
}
|
|
414
414
|
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
} catch (error) {
|
|
422
|
-
return err(producerError(input, error));
|
|
423
|
-
}
|
|
424
|
-
},
|
|
425
|
-
};
|
|
415
|
+
export const materialAssetOutputProducer = createSafeProducer(
|
|
416
|
+
'material',
|
|
417
|
+
'material-pack/1',
|
|
418
|
+
materialProduct,
|
|
419
|
+
);
|
|
420
|
+
export const meshAssetOutputProducer = createSafeProducer('mesh', 'mesh-binary/4', meshProduct);
|
|
426
421
|
|
|
427
|
-
function
|
|
428
|
-
|
|
422
|
+
export function createSceneAssetOutputProducer(
|
|
423
|
+
sceneComponents: readonly ScriptablePackSceneComponent[] = [],
|
|
424
|
+
): AssetOutputProducer {
|
|
425
|
+
const schemas = new Map(
|
|
426
|
+
sceneComponents.map((component) => [component.name, component.fields] as const),
|
|
427
|
+
);
|
|
428
|
+
return createSafeProducer('scene', 'scene-pack/3', (input) => sceneProduct(input, schemas));
|
|
429
429
|
}
|
|
430
430
|
|
|
431
|
-
export
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
produce(input): Result<AssetOutputProduct, ImportError> {
|
|
435
|
-
try {
|
|
436
|
-
return ok(sceneProduct(input));
|
|
437
|
-
} catch (error) {
|
|
438
|
-
return err(producerError(input, error));
|
|
439
|
-
}
|
|
440
|
-
},
|
|
441
|
-
};
|
|
442
|
-
|
|
443
|
-
export function createStandardAssetOutputProducerRegistry(): AssetOutputProducerRegistry {
|
|
431
|
+
export function createStandardAssetOutputProducerRegistry(
|
|
432
|
+
sceneComponents: readonly ScriptablePackSceneComponent[] = [],
|
|
433
|
+
): AssetOutputProducerRegistry {
|
|
444
434
|
const registry = new AssetOutputProducerRegistry();
|
|
445
435
|
registry.register(materialAssetOutputProducer);
|
|
446
436
|
registry.register(meshAssetOutputProducer);
|
|
447
|
-
registry.register(
|
|
437
|
+
registry.register(createSceneAssetOutputProducer(sceneComponents));
|
|
448
438
|
for (const kind of [
|
|
449
439
|
'texture',
|
|
450
440
|
'equirect',
|
|
@@ -460,7 +450,7 @@ export function createStandardAssetOutputProducerRegistry(): AssetOutputProducer
|
|
|
460
450
|
'audio',
|
|
461
451
|
'particle-effect',
|
|
462
452
|
] as const) {
|
|
463
|
-
registry.register(
|
|
453
|
+
registry.register(createSafeProducer(kind, 'ordinary-pod/1', ordinaryPodProduct));
|
|
464
454
|
}
|
|
465
455
|
return registry;
|
|
466
456
|
}
|
|
@@ -26,8 +26,7 @@ export interface ScriptablePackStagedOwner {
|
|
|
26
26
|
export interface ScriptablePackStagedSnapshotOptions {
|
|
27
27
|
readonly generation: number;
|
|
28
28
|
readonly owners: readonly ScriptablePackStagedOwner[];
|
|
29
|
-
|
|
30
|
-
readonly fallback?: ScriptablePackAssetSnapshotSource;
|
|
29
|
+
readonly declaredExternalOutputs?: readonly ScriptablePackStagedOutput[];
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
function stable(value: unknown): string {
|
|
@@ -81,8 +80,21 @@ function missingOutputError(owner: string, guid: string): ScriptablePackError {
|
|
|
81
80
|
export function createScriptablePackStagedAssetSnapshotSource(
|
|
82
81
|
options: ScriptablePackStagedSnapshotOptions,
|
|
83
82
|
): ScriptablePackAssetSnapshotSource {
|
|
83
|
+
const owners =
|
|
84
|
+
options.declaredExternalOutputs === undefined || options.declaredExternalOutputs.length === 0
|
|
85
|
+
? options.owners
|
|
86
|
+
: [
|
|
87
|
+
{
|
|
88
|
+
id: '<declared-pack-external>',
|
|
89
|
+
guids: options.declaredExternalOutputs.map((output) => output.guid),
|
|
90
|
+
async build() {
|
|
91
|
+
return ok(options.declaredExternalOutputs ?? []);
|
|
92
|
+
},
|
|
93
|
+
} satisfies ScriptablePackStagedOwner,
|
|
94
|
+
...options.owners,
|
|
95
|
+
];
|
|
84
96
|
const ownerByGuid = new Map<string, ScriptablePackStagedOwner>();
|
|
85
|
-
for (const owner of
|
|
97
|
+
for (const owner of owners) {
|
|
86
98
|
if (owner.id.trim().length === 0) throw new TypeError('staged owner id must be non-empty');
|
|
87
99
|
for (const guid of owner.guids) {
|
|
88
100
|
const key = AssetGuid.format(guid).toLowerCase();
|
|
@@ -104,12 +116,11 @@ export function createScriptablePackStagedAssetSnapshotSource(
|
|
|
104
116
|
if (cached !== undefined) return ok(structuredClone(cached));
|
|
105
117
|
const owner = ownerByGuid.get(key);
|
|
106
118
|
if (owner === undefined) {
|
|
107
|
-
if (options.fallback !== undefined) return options.fallback.readByGuid(guid);
|
|
108
119
|
return err(
|
|
109
120
|
new AssetError({
|
|
110
121
|
code: 'asset-not-imported',
|
|
111
|
-
expected: 'a staged local owner
|
|
112
|
-
hint: 'declare the local ScriptablePack output
|
|
122
|
+
expected: 'a staged local owner for the requested GUID',
|
|
123
|
+
hint: 'declare the local ScriptablePack output before rebuilding the generation',
|
|
113
124
|
}),
|
|
114
125
|
);
|
|
115
126
|
}
|
package/src/scriptable-pack.ts
CHANGED
|
@@ -1,29 +1,35 @@
|
|
|
1
|
-
import type { Component } from '@forgeax/engine-ecs';
|
|
2
1
|
import { isScriptablePackAssetKind, type ScriptablePackAssetKind } from '@forgeax/engine-pack';
|
|
3
2
|
import { AssetGuid } from '@forgeax/engine-pack/guid';
|
|
4
3
|
import {
|
|
5
4
|
type AssetReader,
|
|
6
5
|
projectScriptablePackMeta,
|
|
6
|
+
projectScriptablePackSceneComponents,
|
|
7
7
|
type ScriptablePackDefinition,
|
|
8
8
|
type ScriptablePackError,
|
|
9
9
|
type ScriptablePackReadError,
|
|
10
|
+
type ScriptablePackSourceClosureEntry,
|
|
10
11
|
} from '@forgeax/engine-pack/source';
|
|
11
12
|
import type {
|
|
12
13
|
AnimationGraph,
|
|
13
14
|
Asset,
|
|
14
15
|
AssetGuid as AssetGuidType,
|
|
15
16
|
AssetPublicationEnvelope,
|
|
16
|
-
AssetRef,
|
|
17
17
|
FontAsset,
|
|
18
|
-
ImportedArtifactBody,
|
|
19
18
|
ImportedAsset,
|
|
20
|
-
ImportProduct,
|
|
21
19
|
MaterialAsset,
|
|
22
20
|
MeshAsset,
|
|
23
21
|
Result,
|
|
24
22
|
TilesetAsset,
|
|
25
23
|
} from '@forgeax/engine-types';
|
|
24
|
+
|
|
25
|
+
export type { ScriptablePackSourceClosureEntry } from '@forgeax/engine-pack/source';
|
|
26
|
+
|
|
26
27
|
import { AssetError, err, ImportError, ok } from '@forgeax/engine-types';
|
|
28
|
+
import {
|
|
29
|
+
createImportProduct,
|
|
30
|
+
type ImportAssetProduct,
|
|
31
|
+
type TerminalImportProduct,
|
|
32
|
+
} from './import-product.js';
|
|
27
33
|
|
|
28
34
|
function commonPathPrefix(paths: readonly string[]): string {
|
|
29
35
|
const first = paths[0];
|
|
@@ -83,8 +89,6 @@ export interface AssetOutputInput {
|
|
|
83
89
|
readonly guid: string;
|
|
84
90
|
readonly sourceKey: string;
|
|
85
91
|
readonly asset: Asset;
|
|
86
|
-
/** World-local schema projection for scene ref externalization. */
|
|
87
|
-
readonly components?: ReadonlyMap<string, Component>;
|
|
88
92
|
}
|
|
89
93
|
|
|
90
94
|
type MaterialPackPayload = Omit<MaterialAsset, 'parent' | 'values'> & {
|
|
@@ -132,11 +136,7 @@ export type AssetOutputPayloadByKind = {
|
|
|
132
136
|
|
|
133
137
|
export type AssetOutputPayload = AssetOutputPayloadByKind[ScriptablePackAssetKind];
|
|
134
138
|
|
|
135
|
-
export
|
|
136
|
-
readonly payload: AssetOutputPayload;
|
|
137
|
-
readonly refs: readonly AssetRef[];
|
|
138
|
-
readonly artifacts: Readonly<Record<string, ImportedArtifactBody>>;
|
|
139
|
-
}
|
|
139
|
+
export type AssetOutputProduct = ImportAssetProduct<AssetOutputPayload>;
|
|
140
140
|
|
|
141
141
|
export interface AssetOutputProducer {
|
|
142
142
|
readonly kind: string;
|
|
@@ -181,13 +181,8 @@ export interface ScriptablePackExternalEvidence {
|
|
|
181
181
|
readonly digest?: string;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
export interface ScriptablePackSourceClosureEntry {
|
|
185
|
-
readonly path: string;
|
|
186
|
-
readonly digest: string;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
184
|
export interface ScriptablePackBuildProduct {
|
|
190
|
-
readonly product:
|
|
185
|
+
readonly product: TerminalImportProduct<unknown>;
|
|
191
186
|
/** Raw private outputs for the current staged build generation, never a runtime publication path. */
|
|
192
187
|
readonly stagedOutputs: readonly ScriptablePackStagedOutput[];
|
|
193
188
|
readonly externalEvidence: readonly ScriptablePackExternalEvidence[];
|
|
@@ -269,7 +264,7 @@ function observedAssetReader(source?: ScriptablePackAssetSnapshotSource): {
|
|
|
269
264
|
new AssetError({
|
|
270
265
|
code: 'asset-not-found',
|
|
271
266
|
expected: `a host Asset snapshot source for ScriptablePack content read ${key}`,
|
|
272
|
-
hint: 'configure
|
|
267
|
+
hint: 'configure the standard ScriptablePack asset source or remove the content read',
|
|
273
268
|
}),
|
|
274
269
|
);
|
|
275
270
|
}
|
|
@@ -435,8 +430,6 @@ export interface BuildScriptablePackOptions {
|
|
|
435
430
|
readonly outputs: AssetOutputProducerRegistry;
|
|
436
431
|
readonly sourceClosure: readonly ScriptablePackSourceClosureEntry[];
|
|
437
432
|
readonly authoringContractVersion: string;
|
|
438
|
-
/** Optional owner-provided component schema projection for scene output. */
|
|
439
|
-
readonly components?: ReadonlyMap<string, Component>;
|
|
440
433
|
}
|
|
441
434
|
|
|
442
435
|
/**
|
|
@@ -527,7 +520,6 @@ export async function buildScriptablePack(
|
|
|
527
520
|
guid: AssetGuid.format(descriptor.guid),
|
|
528
521
|
sourceKey,
|
|
529
522
|
asset,
|
|
530
|
-
...(options.components === undefined ? {} : { components: options.components }),
|
|
531
523
|
});
|
|
532
524
|
if (!product.ok) return err(product.error);
|
|
533
525
|
const productError = productContractError(sourceKey, descriptor.kind, product.value);
|
|
@@ -579,19 +571,38 @@ export async function buildScriptablePack(
|
|
|
579
571
|
usage,
|
|
580
572
|
...(digest === undefined ? {} : { digest }),
|
|
581
573
|
}));
|
|
582
|
-
const product: ImportProduct<unknown> = {
|
|
583
|
-
assets,
|
|
584
|
-
sourceDependencies: options.sourceClosure.map((entry) => entry.path),
|
|
585
|
-
};
|
|
586
574
|
const inputFingerprint = await fingerprint({
|
|
587
575
|
meta: projectScriptablePackMeta(options.definition, options.sourcePath),
|
|
576
|
+
sceneComponents: projectScriptablePackSceneComponents(options.definition.sceneComponents),
|
|
588
577
|
sourceClosure: stableSourceClosure(options.sourceClosure),
|
|
589
578
|
externalEvidence: fingerprintEvidence,
|
|
590
579
|
authoringContractVersion: options.authoringContractVersion,
|
|
591
580
|
producerVersions: options.outputs.versions(),
|
|
592
581
|
});
|
|
582
|
+
const refs = assets.flatMap((asset) => asset.refs);
|
|
583
|
+
const artifacts = Object.fromEntries(
|
|
584
|
+
assets.flatMap((asset) =>
|
|
585
|
+
Object.entries(asset.artifacts).map(([key, artifact]) => [`${asset.guid}/${key}`, artifact]),
|
|
586
|
+
),
|
|
587
|
+
);
|
|
588
|
+
const product = createImportProduct({
|
|
589
|
+
assets,
|
|
590
|
+
sourceDependencies: options.sourceClosure.map((entry) => entry.path),
|
|
591
|
+
refs,
|
|
592
|
+
artifacts,
|
|
593
|
+
receipts: assets.map((asset) => ({
|
|
594
|
+
guid: asset.guid,
|
|
595
|
+
origin: 'sourceMeta' as const,
|
|
596
|
+
status: 'succeeded' as const,
|
|
597
|
+
inputFingerprint,
|
|
598
|
+
})),
|
|
599
|
+
diagnostics: [],
|
|
600
|
+
sourceRevision: inputFingerprint,
|
|
601
|
+
sourceKey: options.sourcePath,
|
|
602
|
+
});
|
|
603
|
+
if (!product.ok) return err(product.error);
|
|
593
604
|
return ok({
|
|
594
|
-
product,
|
|
605
|
+
product: product.value,
|
|
595
606
|
stagedOutputs: await Promise.all(
|
|
596
607
|
Object.keys(options.definition.assets)
|
|
597
608
|
.sort()
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { AssetGuid } from '@forgeax/engine-pack/guid';
|
|
2
|
+
import { projectScriptablePackMeta, type ScriptablePackError } from '@forgeax/engine-pack/source';
|
|
3
|
+
import type {
|
|
4
|
+
AssetError,
|
|
5
|
+
ImportError,
|
|
6
|
+
ImportedAsset,
|
|
7
|
+
ImportProduct,
|
|
8
|
+
Result,
|
|
9
|
+
} from '@forgeax/engine-types';
|
|
10
|
+
import { err, ok } from '@forgeax/engine-types';
|
|
11
|
+
import {
|
|
12
|
+
type BuildScriptablePackOptions,
|
|
13
|
+
buildScriptablePack,
|
|
14
|
+
type ScriptablePackDomainError,
|
|
15
|
+
type ScriptablePackExternalEvidence,
|
|
16
|
+
type ScriptablePackStagedOutput,
|
|
17
|
+
} from './scriptable-pack.js';
|
|
18
|
+
|
|
19
|
+
const META_ARTIFACT_KEY = 'scriptable-pack.meta.json';
|
|
20
|
+
|
|
21
|
+
export interface ScriptableSourcePackageProduct {
|
|
22
|
+
readonly anchorGuid: string;
|
|
23
|
+
readonly declaredGuids: readonly string[];
|
|
24
|
+
readonly product: ImportProduct<unknown>;
|
|
25
|
+
readonly inputFingerprint: string;
|
|
26
|
+
readonly externalEvidence: readonly ScriptablePackExternalEvidence[];
|
|
27
|
+
readonly stagedOutputs: readonly ScriptablePackStagedOutput[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type ScriptableSourcePackageResult = Result<
|
|
31
|
+
ScriptableSourcePackageProduct,
|
|
32
|
+
ScriptablePackError | AssetError | ImportError | ScriptablePackDomainError
|
|
33
|
+
>;
|
|
34
|
+
|
|
35
|
+
function withPrebuiltMeta<P>(
|
|
36
|
+
product: ImportProduct<P>,
|
|
37
|
+
anchorGuid: string,
|
|
38
|
+
meta: unknown,
|
|
39
|
+
): ImportProduct<P> {
|
|
40
|
+
const bytes = new TextEncoder().encode(`${JSON.stringify(meta)}\n`);
|
|
41
|
+
return {
|
|
42
|
+
...product,
|
|
43
|
+
assets: product.assets.map(
|
|
44
|
+
(asset): ImportedAsset<P> =>
|
|
45
|
+
asset.guid.toLowerCase() === anchorGuid
|
|
46
|
+
? {
|
|
47
|
+
...asset,
|
|
48
|
+
artifacts: {
|
|
49
|
+
...asset.artifacts,
|
|
50
|
+
[META_ARTIFACT_KEY]: { mediaType: 'application/json', bytes },
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
: asset,
|
|
54
|
+
),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function produceScriptableSourcePackage(
|
|
59
|
+
options: BuildScriptablePackOptions,
|
|
60
|
+
): Promise<ScriptableSourcePackageResult> {
|
|
61
|
+
const built = await buildScriptablePack(options);
|
|
62
|
+
if (!built.ok) return err(built.error);
|
|
63
|
+
const declaredGuids = Object.values(options.definition.assets)
|
|
64
|
+
.map((descriptor) => AssetGuid.format(descriptor.guid).toLowerCase())
|
|
65
|
+
.sort();
|
|
66
|
+
const anchorGuid = declaredGuids[0];
|
|
67
|
+
if (anchorGuid === undefined) {
|
|
68
|
+
return err({
|
|
69
|
+
code: 'pack-source-output-invalid',
|
|
70
|
+
expected: 'at least one declared ScriptablePack output',
|
|
71
|
+
hint: 'add an output descriptor before building the package',
|
|
72
|
+
detail: { missingGuids: [], unexpectedSourceKeys: [], kindMismatches: [] },
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const product = withPrebuiltMeta(
|
|
76
|
+
built.value.product,
|
|
77
|
+
anchorGuid,
|
|
78
|
+
projectScriptablePackMeta(options.definition, options.sourcePath),
|
|
79
|
+
);
|
|
80
|
+
return ok({
|
|
81
|
+
anchorGuid,
|
|
82
|
+
declaredGuids,
|
|
83
|
+
product,
|
|
84
|
+
stagedOutputs: built.value.stagedOutputs,
|
|
85
|
+
inputFingerprint: built.value.inputFingerprint,
|
|
86
|
+
externalEvidence: built.value.externalEvidence,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import type { ImportError } from '@forgeax/engine-types';
|
|
2
|
+
|
|
3
|
+
const SOURCE_PACKAGE_ERROR_POLICY = {
|
|
4
|
+
'source-package-meta-invalid': {
|
|
5
|
+
expected: 'a valid source Meta declaration with complete GUID topology',
|
|
6
|
+
hint: 'repair the Meta declaration, then rebuild or cold-cook the source package',
|
|
7
|
+
},
|
|
8
|
+
'source-package-importer-missing': {
|
|
9
|
+
expected: 'a registered importer for the source Meta importer key',
|
|
10
|
+
hint: 'register the named importer, then rebuild or cold-cook the source package',
|
|
11
|
+
},
|
|
12
|
+
'source-package-conversion-failed': {
|
|
13
|
+
expected: 'the configured importer to convert the source successfully',
|
|
14
|
+
hint: 'repair the source or importer, then rebuild or cold-cook the source package',
|
|
15
|
+
},
|
|
16
|
+
'source-package-ddc-failed': {
|
|
17
|
+
expected: 'a readable persistent DDC entry with matching integrity evidence',
|
|
18
|
+
hint: 'discard the invalid derived entry, then rebuild or cold-cook the source package',
|
|
19
|
+
},
|
|
20
|
+
'source-package-publication-invalid': {
|
|
21
|
+
expected: 'a complete Pack body, refs, artifacts, and route integrity',
|
|
22
|
+
hint: 'repair the missing product bytes, then rebuild or cold-cook the source package',
|
|
23
|
+
},
|
|
24
|
+
'source-package-guid-closure-mismatch': {
|
|
25
|
+
expected: 'exactly one produced asset for every declared GUID',
|
|
26
|
+
hint: 'repair the Meta topology or importer output, then rebuild the whole source package',
|
|
27
|
+
},
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
export type SourcePackageErrorCode = keyof typeof SOURCE_PACKAGE_ERROR_POLICY;
|
|
31
|
+
|
|
32
|
+
export type SourcePackageErrorStage =
|
|
33
|
+
| 'meta'
|
|
34
|
+
| 'importer'
|
|
35
|
+
| 'conversion'
|
|
36
|
+
| 'closure'
|
|
37
|
+
| 'ddc'
|
|
38
|
+
| 'route-integrity';
|
|
39
|
+
|
|
40
|
+
export interface SourcePackageErrorContext {
|
|
41
|
+
readonly sourceMeta: string;
|
|
42
|
+
readonly anchorGuid: string;
|
|
43
|
+
readonly affectedGuids: readonly string[];
|
|
44
|
+
readonly producer: string;
|
|
45
|
+
readonly importer: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface SourcePackageErrorDetail extends SourcePackageErrorContext {
|
|
49
|
+
readonly stage: SourcePackageErrorStage;
|
|
50
|
+
readonly reason?: string;
|
|
51
|
+
readonly missing?: readonly string[];
|
|
52
|
+
readonly unexpected?: readonly string[];
|
|
53
|
+
readonly registeredImporters?: readonly string[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface SourcePackageError {
|
|
57
|
+
readonly code: SourcePackageErrorCode;
|
|
58
|
+
readonly expected: string;
|
|
59
|
+
readonly hint: string;
|
|
60
|
+
readonly detail: SourcePackageErrorDetail;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function sourcePackageError(
|
|
64
|
+
code: SourcePackageErrorCode,
|
|
65
|
+
context: SourcePackageErrorContext,
|
|
66
|
+
detail: Omit<SourcePackageErrorDetail, keyof SourcePackageErrorContext>,
|
|
67
|
+
): SourcePackageError {
|
|
68
|
+
const policy = SOURCE_PACKAGE_ERROR_POLICY[code];
|
|
69
|
+
return {
|
|
70
|
+
code,
|
|
71
|
+
expected: policy?.expected as string,
|
|
72
|
+
hint: policy?.hint as string,
|
|
73
|
+
detail: { ...context, ...detail },
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function unknownReason(error: unknown): string {
|
|
78
|
+
if (error !== null && typeof error === 'object') {
|
|
79
|
+
const value = error as Record<string, unknown>;
|
|
80
|
+
if (typeof value.code === 'string') {
|
|
81
|
+
const detail = value.detail;
|
|
82
|
+
const reason =
|
|
83
|
+
detail !== null && typeof detail === 'object' && 'reason' in detail
|
|
84
|
+
? (detail as { readonly reason?: unknown }).reason
|
|
85
|
+
: undefined;
|
|
86
|
+
const diagnostic =
|
|
87
|
+
detail !== null && typeof detail === 'object' && 'diagnostic' in detail
|
|
88
|
+
? (detail as { readonly diagnostic?: unknown }).diagnostic
|
|
89
|
+
: undefined;
|
|
90
|
+
return `${value.code}: ${
|
|
91
|
+
typeof diagnostic === 'string'
|
|
92
|
+
? diagnostic
|
|
93
|
+
: typeof reason === 'string'
|
|
94
|
+
? reason
|
|
95
|
+
: String(value.expected ?? 'producer failure')
|
|
96
|
+
}`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return error instanceof Error ? error.message : String(error);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function importFailureCode(error: ImportError): SourcePackageErrorCode {
|
|
103
|
+
switch (error.code) {
|
|
104
|
+
case 'importer-not-registered':
|
|
105
|
+
return 'source-package-importer-missing';
|
|
106
|
+
case 'import-produced-no-assets':
|
|
107
|
+
case 'guid-mismatch':
|
|
108
|
+
case 'source-validation-failed':
|
|
109
|
+
return 'source-package-guid-closure-mismatch';
|
|
110
|
+
case 'source-read-failed':
|
|
111
|
+
case 'import-internal-error':
|
|
112
|
+
case 'mesh-material-slot-topology-change':
|
|
113
|
+
case 'unknown-source-key':
|
|
114
|
+
case 'duplicate-source-key':
|
|
115
|
+
case 'invalid-source-overrides':
|
|
116
|
+
case 'invalid-source-override-payload':
|
|
117
|
+
return 'source-package-conversion-failed';
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function normalizeSourcePackageError(
|
|
122
|
+
error: unknown,
|
|
123
|
+
context: SourcePackageErrorContext,
|
|
124
|
+
): SourcePackageError {
|
|
125
|
+
if (isSourcePackageError(error)) return error;
|
|
126
|
+
if (isScriptablePackFailure(error)) {
|
|
127
|
+
return sourcePackageError('source-package-conversion-failed', context, {
|
|
128
|
+
stage: 'conversion',
|
|
129
|
+
reason: unknownReason(error),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
if (isImportError(error)) {
|
|
133
|
+
const code = importFailureCode(error);
|
|
134
|
+
const detail: Omit<SourcePackageErrorDetail, keyof SourcePackageErrorContext> = {
|
|
135
|
+
stage: code === 'source-package-importer-missing' ? 'importer' : 'conversion',
|
|
136
|
+
reason: unknownReason(error),
|
|
137
|
+
...(code === 'source-package-importer-missing' && 'registeredImporters' in error.detail
|
|
138
|
+
? { registeredImporters: error.detail.registeredImporters }
|
|
139
|
+
: {}),
|
|
140
|
+
};
|
|
141
|
+
return sourcePackageError(code, context, detail);
|
|
142
|
+
}
|
|
143
|
+
return sourcePackageError(contextErrorStage(error), context, {
|
|
144
|
+
stage: 'conversion',
|
|
145
|
+
reason: unknownReason(error),
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function isSourcePackageError(error: unknown): error is SourcePackageError {
|
|
150
|
+
return (
|
|
151
|
+
error !== null &&
|
|
152
|
+
typeof error === 'object' &&
|
|
153
|
+
'code' in error &&
|
|
154
|
+
typeof error.code === 'string' &&
|
|
155
|
+
error.code in SOURCE_PACKAGE_ERROR_POLICY &&
|
|
156
|
+
'expected' in error &&
|
|
157
|
+
typeof error.expected === 'string' &&
|
|
158
|
+
'hint' in error &&
|
|
159
|
+
typeof error.hint === 'string' &&
|
|
160
|
+
'detail' in error &&
|
|
161
|
+
error.detail !== null &&
|
|
162
|
+
typeof error.detail === 'object'
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function contextErrorStage(error: unknown): SourcePackageErrorCode {
|
|
167
|
+
return error instanceof SyntaxError
|
|
168
|
+
? 'source-package-meta-invalid'
|
|
169
|
+
: 'source-package-conversion-failed';
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function isImportError(error: unknown): error is ImportError {
|
|
173
|
+
return (
|
|
174
|
+
error !== null &&
|
|
175
|
+
typeof error === 'object' &&
|
|
176
|
+
'code' in error &&
|
|
177
|
+
'expected' in error &&
|
|
178
|
+
'hint' in error &&
|
|
179
|
+
'detail' in error
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function isScriptablePackFailure(error: unknown): error is {
|
|
184
|
+
readonly code: string;
|
|
185
|
+
readonly expected: string;
|
|
186
|
+
readonly hint: string;
|
|
187
|
+
readonly detail: unknown;
|
|
188
|
+
} {
|
|
189
|
+
return (
|
|
190
|
+
error !== null &&
|
|
191
|
+
typeof error === 'object' &&
|
|
192
|
+
'code' in error &&
|
|
193
|
+
typeof error.code === 'string' &&
|
|
194
|
+
error.code.startsWith('pack-source-') &&
|
|
195
|
+
'expected' in error &&
|
|
196
|
+
typeof error.expected === 'string' &&
|
|
197
|
+
'hint' in error &&
|
|
198
|
+
typeof error.hint === 'string' &&
|
|
199
|
+
'detail' in error
|
|
200
|
+
);
|
|
201
|
+
}
|