@forgeax/engine-pack 0.1.2
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/LICENSE +202 -0
- package/README.md +370 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/ai-recovery.unit.test.d.ts +2 -0
- package/dist/__tests__/ai-recovery.unit.test.d.ts.map +1 -0
- package/dist/__tests__/artifact-path.test.d.ts +2 -0
- package/dist/__tests__/artifact-path.test.d.ts.map +1 -0
- package/dist/__tests__/asset-evidence-error-hints.test.d.ts +2 -0
- package/dist/__tests__/asset-evidence-error-hints.test.d.ts.map +1 -0
- package/dist/__tests__/asset-evidence-lifecycle.test.d.ts +2 -0
- package/dist/__tests__/asset-evidence-lifecycle.test.d.ts.map +1 -0
- package/dist/__tests__/asset-evidence-schema.test.d.ts +2 -0
- package/dist/__tests__/asset-evidence-schema.test.d.ts.map +1 -0
- package/dist/__tests__/atlas-cli-region-mismatch.test.d.ts +2 -0
- package/dist/__tests__/atlas-cli-region-mismatch.test.d.ts.map +1 -0
- package/dist/__tests__/atlas-error-code-owner.test-d.d.ts +2 -0
- package/dist/__tests__/atlas-error-code-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/cli-asset-evidence.integration.test.d.ts +2 -0
- package/dist/__tests__/cli-asset-evidence.integration.test.d.ts.map +1 -0
- package/dist/__tests__/cli.unit.test.d.ts +2 -0
- package/dist/__tests__/cli.unit.test.d.ts.map +1 -0
- package/dist/__tests__/evidence-source-inventory.unit.test.d.ts +2 -0
- package/dist/__tests__/evidence-source-inventory.unit.test.d.ts.map +1 -0
- package/dist/__tests__/guid-collision.unit.test.d.ts +2 -0
- package/dist/__tests__/guid-collision.unit.test.d.ts.map +1 -0
- package/dist/__tests__/guid-validator-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/guid-validator-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/load-asset-config.test.d.ts +2 -0
- package/dist/__tests__/load-asset-config.test.d.ts.map +1 -0
- package/dist/__tests__/material-cook-dependencies.unit.test.d.ts +2 -0
- package/dist/__tests__/material-cook-dependencies.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-cook-receipt.unit.test.d.ts +2 -0
- package/dist/__tests__/material-cook-receipt.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-cook-schema.unit.test.d.ts +2 -0
- package/dist/__tests__/material-cook-schema.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-bin-consumer-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-bin-consumer-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-bin-contract.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-bin-contract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/meta-schema-optional.test.d.ts +2 -0
- package/dist/__tests__/meta-schema-optional.test.d.ts.map +1 -0
- package/dist/__tests__/meta-source-overrides.unit.test.d.ts +2 -0
- package/dist/__tests__/meta-source-overrides.unit.test.d.ts.map +1 -0
- package/dist/__tests__/native-cooker-registry.test.d.ts +2 -0
- package/dist/__tests__/native-cooker-registry.test.d.ts.map +1 -0
- package/dist/__tests__/offline-evidence-artifact-verification-owner.test-d.d.ts +2 -0
- package/dist/__tests__/offline-evidence-artifact-verification-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/offline-evidence.integration.test.d.ts +2 -0
- package/dist/__tests__/offline-evidence.integration.test.d.ts.map +1 -0
- package/dist/__tests__/pack-v2-fixture-migration.test.d.ts +2 -0
- package/dist/__tests__/pack-v2-fixture-migration.test.d.ts.map +1 -0
- package/dist/__tests__/pack-v2-schema.test.d.ts +2 -0
- package/dist/__tests__/pack-v2-schema.test.d.ts.map +1 -0
- package/dist/__tests__/pack.unit.test.d.ts +2 -0
- package/dist/__tests__/pack.unit.test.d.ts.map +1 -0
- package/dist/__tests__/producer-contract-schema.unit.test.d.ts +2 -0
- package/dist/__tests__/producer-contract-schema.unit.test.d.ts.map +1 -0
- package/dist/__tests__/resolve-asset-source.test.d.ts +2 -0
- package/dist/__tests__/resolve-asset-source.test.d.ts.map +1 -0
- package/dist/__tests__/runtime-projection.unit.test.d.ts +2 -0
- package/dist/__tests__/runtime-projection.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scanner-inventory.test.d.ts +2 -0
- package/dist/__tests__/scanner-inventory.test.d.ts.map +1 -0
- package/dist/__tests__/scanner-mount-cycle.test.d.ts +2 -0
- package/dist/__tests__/scanner-mount-cycle.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-cli.integration.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-cli.integration.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-ddc-key.unit.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-ddc-key.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack.test-d.d.ts +2 -0
- package/dist/__tests__/scriptable-pack.test-d.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack.unit.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack.unit.test.d.ts.map +1 -0
- package/dist/__tests__/topology-actions.unit.test.d.ts +2 -0
- package/dist/__tests__/topology-actions.unit.test.d.ts.map +1 -0
- package/dist/__tests__/topology.unit.test.d.ts +2 -0
- package/dist/__tests__/topology.unit.test.d.ts.map +1 -0
- package/dist/artifact-path.d.ts +9 -0
- package/dist/artifact-path.d.ts.map +1 -0
- package/dist/atlas/run-atlas.d.ts +8 -0
- package/dist/atlas/run-atlas.d.ts.map +1 -0
- package/dist/atlas/shelf-pack.d.ts +55 -0
- package/dist/atlas/shelf-pack.d.ts.map +1 -0
- package/dist/bridge.d.ts +2 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/bridge.mjs +3 -0
- package/dist/bridge.mjs.map +1 -0
- package/dist/builtin.d.ts +24 -0
- package/dist/builtin.d.ts.map +1 -0
- package/dist/cli-asset.d.ts +22 -0
- package/dist/cli-asset.d.ts.map +1 -0
- package/dist/cli-asset.mjs +9950 -0
- package/dist/cli-asset.mjs.map +1 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.mjs +29 -0
- package/dist/config.mjs.map +1 -0
- package/dist/deriveAssetName.d.ts +15 -0
- package/dist/deriveAssetName.d.ts.map +1 -0
- package/dist/errors.d.ts +30 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.mjs +19 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/evidence/material-cook.d.ts +70 -0
- package/dist/evidence/material-cook.d.ts.map +1 -0
- package/dist/evidence/offline-evidence.d.ts +25 -0
- package/dist/evidence/offline-evidence.d.ts.map +1 -0
- package/dist/evidence/source-inventory.d.ts +29 -0
- package/dist/evidence/source-inventory.d.ts.map +1 -0
- package/dist/guid.d.ts +45 -0
- package/dist/guid.d.ts.map +1 -0
- package/dist/guid.mjs +94 -0
- package/dist/guid.mjs.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +8475 -0
- package/dist/index.mjs.map +1 -0
- package/dist/material/artifact-writer.d.ts +14 -0
- package/dist/material/artifact-writer.d.ts.map +1 -0
- package/dist/mesh-bin-contract.d.ts +36 -0
- package/dist/mesh-bin-contract.d.ts.map +1 -0
- package/dist/name.mjs +22 -0
- package/dist/name.mjs.map +1 -0
- package/dist/native-cooker-registry.d.ts +28 -0
- package/dist/native-cooker-registry.d.ts.map +1 -0
- package/dist/native-cooker.d.ts +3 -0
- package/dist/native-cooker.d.ts.map +1 -0
- package/dist/native-cooker.mjs +105 -0
- package/dist/native-cooker.mjs.map +1 -0
- package/dist/producer-contract.d.ts +19 -0
- package/dist/producer-contract.d.ts.map +1 -0
- package/dist/resolve-asset-source.d.ts +4 -0
- package/dist/resolve-asset-source.d.ts.map +1 -0
- package/dist/resolve-asset-source.mjs +99 -0
- package/dist/resolve-asset-source.mjs.map +1 -0
- package/dist/runtime-projection.d.ts +14 -0
- package/dist/runtime-projection.d.ts.map +1 -0
- package/dist/scanner.d.ts +80 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.mjs +9015 -0
- package/dist/scanner.mjs.map +1 -0
- package/dist/schema-compiled.d.ts +12 -0
- package/dist/schema-compiled.d.ts.map +1 -0
- package/dist/schema.mjs +7369 -0
- package/dist/schema.mjs.map +1 -0
- package/dist/scriptable-pack-node.d.ts +67 -0
- package/dist/scriptable-pack-node.d.ts.map +1 -0
- package/dist/scriptable-pack-node.mjs +1590 -0
- package/dist/scriptable-pack-node.mjs.map +1 -0
- package/dist/scriptable-pack-worker.d.ts +2 -0
- package/dist/scriptable-pack-worker.d.ts.map +1 -0
- package/dist/scriptable-pack-worker.mjs +223 -0
- package/dist/scriptable-pack-worker.mjs.map +1 -0
- package/dist/scriptable-pack.d.ts +525 -0
- package/dist/scriptable-pack.d.ts.map +1 -0
- package/dist/scriptable-pack.mjs +583 -0
- package/dist/scriptable-pack.mjs.map +1 -0
- package/dist/topology.d.ts +10 -0
- package/dist/topology.d.ts.map +1 -0
- package/package.json +129 -0
- package/schema/meta.schema.json +173 -0
- package/schema/pack.schema.json +232 -0
- package/src/__tests__/ai-recovery.unit.test.ts +114 -0
- package/src/__tests__/artifact-path.test.ts +41 -0
- package/src/__tests__/asset-evidence-error-hints.test.ts +46 -0
- package/src/__tests__/asset-evidence-lifecycle.test.ts +104 -0
- package/src/__tests__/asset-evidence-schema.test.ts +86 -0
- package/src/__tests__/atlas-cli-region-mismatch.test.ts +134 -0
- package/src/__tests__/atlas-error-code-owner.test-d.ts +23 -0
- package/src/__tests__/cli-asset-evidence.integration.test.ts +172 -0
- package/src/__tests__/cli.unit.test.ts +448 -0
- package/src/__tests__/evidence-source-inventory.unit.test.ts +37 -0
- package/src/__tests__/guid-collision.unit.test.ts +112 -0
- package/src/__tests__/guid-validator-surface.unit.test.ts +20 -0
- package/src/__tests__/load-asset-config.test.ts +121 -0
- package/src/__tests__/material-cook-dependencies.unit.test.ts +45 -0
- package/src/__tests__/material-cook-receipt.unit.test.ts +91 -0
- package/src/__tests__/material-cook-schema.unit.test.ts +98 -0
- package/src/__tests__/mesh-bin-consumer-surface.unit.test.ts +21 -0
- package/src/__tests__/mesh-bin-contract.unit.test.ts +32 -0
- package/src/__tests__/meta-schema-optional.test.ts +116 -0
- package/src/__tests__/meta-source-overrides.unit.test.ts +71 -0
- package/src/__tests__/native-cooker-registry.test.ts +64 -0
- package/src/__tests__/offline-evidence-artifact-verification-owner.test-d.ts +64 -0
- package/src/__tests__/offline-evidence.integration.test.ts +106 -0
- package/src/__tests__/pack-v2-fixture-migration.test.ts +64 -0
- package/src/__tests__/pack-v2-schema.test.ts +142 -0
- package/src/__tests__/pack.unit.test.ts +2218 -0
- package/src/__tests__/producer-contract-schema.unit.test.ts +92 -0
- package/src/__tests__/resolve-asset-source.test.ts +121 -0
- package/src/__tests__/runtime-projection.unit.test.ts +87 -0
- package/src/__tests__/scanner-inventory.test.ts +72 -0
- package/src/__tests__/scanner-mount-cycle.test.ts +232 -0
- package/src/__tests__/scriptable-pack-cli.integration.test.ts +47 -0
- package/src/__tests__/scriptable-pack-ddc-key.unit.test.ts +63 -0
- package/src/__tests__/scriptable-pack.test-d.ts +131 -0
- package/src/__tests__/scriptable-pack.unit.test.ts +747 -0
- package/src/__tests__/topology-actions.unit.test.ts +94 -0
- package/src/__tests__/topology.unit.test.ts +85 -0
- package/src/artifact-path.ts +69 -0
- package/src/atlas/run-atlas.ts +358 -0
- package/src/atlas/shelf-pack.ts +176 -0
- package/src/atlas/upng-ambient.d.ts +13 -0
- package/src/bridge.ts +2 -0
- package/src/builtin.ts +79 -0
- package/src/cli-asset.ts +590 -0
- package/src/config.ts +36 -0
- package/src/deriveAssetName.ts +42 -0
- package/src/errors.ts +47 -0
- package/src/evidence/material-cook.ts +332 -0
- package/src/evidence/offline-evidence.ts +90 -0
- package/src/evidence/source-inventory.ts +193 -0
- package/src/guid.ts +99 -0
- package/src/index.ts +104 -0
- package/src/material/artifact-writer.ts +26 -0
- package/src/mesh-bin-contract.ts +150 -0
- package/src/native-cooker-registry.ts +163 -0
- package/src/native-cooker.ts +2 -0
- package/src/producer-contract.ts +277 -0
- package/src/resolve-asset-source.ts +93 -0
- package/src/runtime-projection.ts +100 -0
- package/src/scanner.ts +716 -0
- package/src/schema/material-cook.schema.json +79 -0
- package/src/schema-compiled.ts +238 -0
- package/src/scriptable-pack-node.ts +1390 -0
- package/src/scriptable-pack-worker.ts +306 -0
- package/src/scriptable-pack.ts +884 -0
- package/src/topology.ts +156 -0
|
@@ -0,0 +1,1590 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { realpath, readFile, stat, mkdtemp, rm, mkdir, writeFile, rename } from 'fs/promises';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
import { resolve, dirname, extname, relative, sep, isAbsolute } from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import { Worker } from 'worker_threads';
|
|
8
|
+
import { ok, err, AssetError, ImportError } from '@forgeax/engine-types';
|
|
9
|
+
import ts from 'typescript';
|
|
10
|
+
import { uuidv7obj } from 'uuidv7';
|
|
11
|
+
|
|
12
|
+
// src/scriptable-pack-node.ts
|
|
13
|
+
|
|
14
|
+
// src/errors.ts
|
|
15
|
+
var PackError = class extends Error {
|
|
16
|
+
code;
|
|
17
|
+
expected;
|
|
18
|
+
hint;
|
|
19
|
+
detail;
|
|
20
|
+
constructor(args) {
|
|
21
|
+
super(`[PackError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);
|
|
22
|
+
this.name = "PackError";
|
|
23
|
+
this.code = args.code;
|
|
24
|
+
this.expected = args.expected;
|
|
25
|
+
this.hint = args.hint;
|
|
26
|
+
this.detail = args.detail;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
function brand(bytes) {
|
|
30
|
+
return bytes;
|
|
31
|
+
}
|
|
32
|
+
var HEX_BYTE = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
|
|
33
|
+
function bytesToDashForm(bytes) {
|
|
34
|
+
const h = HEX_BYTE;
|
|
35
|
+
return `${h[bytes[0]]}${h[bytes[1]]}${h[bytes[2]]}${h[bytes[3]]}-${h[bytes[4]]}${h[bytes[5]]}-${h[bytes[6]]}${h[bytes[7]]}-${h[bytes[8]]}${h[bytes[9]]}-${h[bytes[10]]}${h[bytes[11]]}${h[bytes[12]]}${h[bytes[13]]}${h[bytes[14]]}${h[bytes[15]]}`;
|
|
36
|
+
}
|
|
37
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
38
|
+
function isValidAssetGuidString(value) {
|
|
39
|
+
return typeof value === "string" && UUID_RE.test(value);
|
|
40
|
+
}
|
|
41
|
+
function dashFormToBytes(dashForm) {
|
|
42
|
+
const hex = dashForm.replace(/-/g, "");
|
|
43
|
+
const bytes = new Uint8Array(16);
|
|
44
|
+
for (let i = 0; i < 16; i++) {
|
|
45
|
+
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
46
|
+
}
|
|
47
|
+
return bytes;
|
|
48
|
+
}
|
|
49
|
+
var AssetGuid = {
|
|
50
|
+
/**
|
|
51
|
+
* Parse a 36-char RFC 4122 dash-form UUID string into an AssetGuid.
|
|
52
|
+
* Returns Ok(AssetGuid) on success or Err(PackError) with code 'pack-guid-malformed' on failure.
|
|
53
|
+
* Never throws for expected failures (requirements §4.2 / §14 / charter proposition 4).
|
|
54
|
+
*/
|
|
55
|
+
parse(dashForm) {
|
|
56
|
+
if (!isValidAssetGuidString(dashForm)) {
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
error: new PackError({
|
|
60
|
+
code: "pack-guid-malformed",
|
|
61
|
+
expected: "36-char RFC 4122 dash-form UUID",
|
|
62
|
+
hint: "use AssetGuid.random() or a UUIDv7 generator; all GUID fields must be 36-char RFC 4122 dash-form",
|
|
63
|
+
detail: {
|
|
64
|
+
raw: dashForm,
|
|
65
|
+
reason: "expected 36-char RFC 4122 dash-form UUID"
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return { ok: true, value: brand(dashFormToBytes(dashForm)) };
|
|
71
|
+
},
|
|
72
|
+
/**
|
|
73
|
+
* Format an AssetGuid as a 36-char RFC 4122 lowercase dash-form string.
|
|
74
|
+
*/
|
|
75
|
+
format(guid) {
|
|
76
|
+
return bytesToDashForm(guid);
|
|
77
|
+
},
|
|
78
|
+
/**
|
|
79
|
+
* Test byte-by-byte equality between two AssetGuids.
|
|
80
|
+
*/
|
|
81
|
+
equals(a, b) {
|
|
82
|
+
for (let i = 0; i < 16; i++) {
|
|
83
|
+
if (a[i] !== b[i]) return false;
|
|
84
|
+
}
|
|
85
|
+
return true;
|
|
86
|
+
},
|
|
87
|
+
/**
|
|
88
|
+
* Mint a new time-ordered UUIDv7 as an AssetGuid.
|
|
89
|
+
* Works in both Node.js and browser environments.
|
|
90
|
+
*/
|
|
91
|
+
random() {
|
|
92
|
+
const uuid = uuidv7obj();
|
|
93
|
+
const bytes = new Uint8Array(16);
|
|
94
|
+
bytes.set(uuid.bytes);
|
|
95
|
+
return brand(bytes);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
var SCRIPTABLE_PACK_ASSET_KINDS = [
|
|
99
|
+
"mesh",
|
|
100
|
+
"material",
|
|
101
|
+
"scene",
|
|
102
|
+
"texture",
|
|
103
|
+
"equirect",
|
|
104
|
+
"sampler",
|
|
105
|
+
"font",
|
|
106
|
+
"render-pipeline",
|
|
107
|
+
"tileset",
|
|
108
|
+
"video",
|
|
109
|
+
"skeleton",
|
|
110
|
+
"skin",
|
|
111
|
+
"animation-clip",
|
|
112
|
+
"animation-graph",
|
|
113
|
+
"audio",
|
|
114
|
+
"particle-effect"
|
|
115
|
+
];
|
|
116
|
+
function isScriptablePackAssetKind(value) {
|
|
117
|
+
return SCRIPTABLE_PACK_ASSET_KINDS.includes(value);
|
|
118
|
+
}
|
|
119
|
+
var SCRIPTABLE_PACK_REQUEST_ID_SCHEMA = {
|
|
120
|
+
type: "string",
|
|
121
|
+
minLength: 1,
|
|
122
|
+
maxLength: 128,
|
|
123
|
+
pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$",
|
|
124
|
+
description: "Caller-minted identity for one idempotent ScriptablePack operation."
|
|
125
|
+
};
|
|
126
|
+
var SCRIPTABLE_PACK_SOURCE_PATH_SCHEMA = {
|
|
127
|
+
type: "string",
|
|
128
|
+
minLength: 8,
|
|
129
|
+
pattern: "^[^/].*\\.pack\\.ts$",
|
|
130
|
+
description: "Game-root-relative ScriptablePack source path."
|
|
131
|
+
};
|
|
132
|
+
var SCRIPTABLE_PACK_OWNER_GUID_SCHEMA = {
|
|
133
|
+
type: "string",
|
|
134
|
+
format: "uuid",
|
|
135
|
+
description: "Catalog GUID used to resolve the producer-owned source subject."
|
|
136
|
+
};
|
|
137
|
+
var SCRIPTABLE_PACK_REVISION_SCHEMA = {
|
|
138
|
+
type: "string",
|
|
139
|
+
minLength: 64,
|
|
140
|
+
maxLength: 64,
|
|
141
|
+
pattern: "^[a-f0-9]{64}$",
|
|
142
|
+
description: "SHA-256 source revision returned by preflight."
|
|
143
|
+
};
|
|
144
|
+
var SCRIPTABLE_PACK_SOURCE_KEY_SCHEMA = { type: "string", minLength: 1 };
|
|
145
|
+
var SCRIPTABLE_PACK_ASSET_KIND_SCHEMA = {
|
|
146
|
+
enum: SCRIPTABLE_PACK_ASSET_KINDS,
|
|
147
|
+
description: "Ordinary Asset kind supported by the ScriptablePack producer/loader matrix."
|
|
148
|
+
};
|
|
149
|
+
var SCRIPTABLE_PACK_NAME_SCHEMA = { type: "string", minLength: 1 };
|
|
150
|
+
function scriptablePackArgsSchema(properties, required, options = {}) {
|
|
151
|
+
const subjectRequired = ["requestId", ...required];
|
|
152
|
+
return {
|
|
153
|
+
type: "object",
|
|
154
|
+
properties: {
|
|
155
|
+
sourcePath: SCRIPTABLE_PACK_SOURCE_PATH_SCHEMA,
|
|
156
|
+
ownerGuid: SCRIPTABLE_PACK_OWNER_GUID_SCHEMA,
|
|
157
|
+
requestId: SCRIPTABLE_PACK_REQUEST_ID_SCHEMA,
|
|
158
|
+
...properties
|
|
159
|
+
},
|
|
160
|
+
required: subjectRequired,
|
|
161
|
+
...options.subject === false ? {} : {
|
|
162
|
+
anyOf: [
|
|
163
|
+
{ required: [...subjectRequired, "sourcePath"] },
|
|
164
|
+
{ required: [...subjectRequired, "ownerGuid"] },
|
|
165
|
+
...options.guidSubject === false ? [] : [{ required: [...subjectRequired, "guid"] }]
|
|
166
|
+
]
|
|
167
|
+
},
|
|
168
|
+
additionalProperties: false
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
[
|
|
172
|
+
{
|
|
173
|
+
id: "asset-source.create",
|
|
174
|
+
kind: "create",
|
|
175
|
+
domain: "session",
|
|
176
|
+
title: "Create Asset Source",
|
|
177
|
+
argsSchema: scriptablePackArgsSchema(
|
|
178
|
+
{
|
|
179
|
+
name: SCRIPTABLE_PACK_NAME_SCHEMA,
|
|
180
|
+
initialOutput: {
|
|
181
|
+
type: "object",
|
|
182
|
+
properties: {
|
|
183
|
+
sourceKey: SCRIPTABLE_PACK_SOURCE_KEY_SCHEMA,
|
|
184
|
+
kind: SCRIPTABLE_PACK_ASSET_KIND_SCHEMA,
|
|
185
|
+
name: SCRIPTABLE_PACK_NAME_SCHEMA
|
|
186
|
+
},
|
|
187
|
+
required: ["sourceKey", "kind"],
|
|
188
|
+
additionalProperties: false
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
["sourcePath", "initialOutput"],
|
|
192
|
+
{ subject: false }
|
|
193
|
+
)
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: "asset-source.add-output",
|
|
197
|
+
kind: "add-output",
|
|
198
|
+
domain: "session",
|
|
199
|
+
title: "Add Asset Source Output",
|
|
200
|
+
argsSchema: scriptablePackArgsSchema(
|
|
201
|
+
{
|
|
202
|
+
sourceKey: SCRIPTABLE_PACK_SOURCE_KEY_SCHEMA,
|
|
203
|
+
assetKind: SCRIPTABLE_PACK_ASSET_KIND_SCHEMA,
|
|
204
|
+
name: SCRIPTABLE_PACK_NAME_SCHEMA,
|
|
205
|
+
expectedRevision: SCRIPTABLE_PACK_REVISION_SCHEMA
|
|
206
|
+
},
|
|
207
|
+
["sourceKey", "assetKind", "expectedRevision"]
|
|
208
|
+
)
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
id: "asset-source.add-external-asset",
|
|
212
|
+
kind: "add-external-asset",
|
|
213
|
+
domain: "session",
|
|
214
|
+
title: "Add Asset Source External Asset",
|
|
215
|
+
argsSchema: scriptablePackArgsSchema(
|
|
216
|
+
{
|
|
217
|
+
alias: SCRIPTABLE_PACK_SOURCE_KEY_SCHEMA,
|
|
218
|
+
guid: { type: "string", format: "uuid" },
|
|
219
|
+
expectedRevision: SCRIPTABLE_PACK_REVISION_SCHEMA
|
|
220
|
+
},
|
|
221
|
+
["alias", "guid", "expectedRevision"],
|
|
222
|
+
{ guidSubject: false }
|
|
223
|
+
)
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
id: "asset-source.rename",
|
|
227
|
+
kind: "rename",
|
|
228
|
+
domain: "session",
|
|
229
|
+
title: "Rename Asset Source Display Name",
|
|
230
|
+
argsSchema: scriptablePackArgsSchema(
|
|
231
|
+
{
|
|
232
|
+
target: {
|
|
233
|
+
oneOf: [
|
|
234
|
+
{
|
|
235
|
+
type: "object",
|
|
236
|
+
properties: { kind: { const: "package" } },
|
|
237
|
+
required: ["kind"],
|
|
238
|
+
additionalProperties: false
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
type: "object",
|
|
242
|
+
properties: {
|
|
243
|
+
kind: { const: "output" },
|
|
244
|
+
sourceKey: SCRIPTABLE_PACK_SOURCE_KEY_SCHEMA
|
|
245
|
+
},
|
|
246
|
+
required: ["kind", "sourceKey"],
|
|
247
|
+
additionalProperties: false
|
|
248
|
+
}
|
|
249
|
+
]
|
|
250
|
+
},
|
|
251
|
+
name: SCRIPTABLE_PACK_NAME_SCHEMA,
|
|
252
|
+
expectedRevision: SCRIPTABLE_PACK_REVISION_SCHEMA
|
|
253
|
+
},
|
|
254
|
+
["target", "name", "expectedRevision"]
|
|
255
|
+
)
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
id: "asset-source.remove-output",
|
|
259
|
+
kind: "remove-output",
|
|
260
|
+
domain: "session",
|
|
261
|
+
title: "Remove Asset Source Output",
|
|
262
|
+
destructive: true,
|
|
263
|
+
argsSchema: scriptablePackArgsSchema(
|
|
264
|
+
{
|
|
265
|
+
sourceKey: SCRIPTABLE_PACK_SOURCE_KEY_SCHEMA,
|
|
266
|
+
confirmIncomingRefs: { type: "array", items: { type: "string" } },
|
|
267
|
+
expectedRevision: SCRIPTABLE_PACK_REVISION_SCHEMA
|
|
268
|
+
},
|
|
269
|
+
["sourceKey", "expectedRevision"]
|
|
270
|
+
)
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
id: "asset-source.clone",
|
|
274
|
+
kind: "clone",
|
|
275
|
+
domain: "session",
|
|
276
|
+
title: "Clone Asset Source",
|
|
277
|
+
argsSchema: scriptablePackArgsSchema(
|
|
278
|
+
{
|
|
279
|
+
targetPath: SCRIPTABLE_PACK_SOURCE_PATH_SCHEMA,
|
|
280
|
+
expectedRevision: SCRIPTABLE_PACK_REVISION_SCHEMA
|
|
281
|
+
},
|
|
282
|
+
["targetPath", "expectedRevision"]
|
|
283
|
+
)
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
id: "asset-source.rebuild",
|
|
287
|
+
kind: "rebuild",
|
|
288
|
+
domain: "session",
|
|
289
|
+
title: "Rebuild Asset Source",
|
|
290
|
+
argsSchema: scriptablePackArgsSchema({ expectedRevision: SCRIPTABLE_PACK_REVISION_SCHEMA }, [
|
|
291
|
+
"expectedRevision"
|
|
292
|
+
])
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
id: "asset-source.cold-cook",
|
|
296
|
+
kind: "cold-cook",
|
|
297
|
+
domain: "session",
|
|
298
|
+
title: "Cold Cook Asset Source",
|
|
299
|
+
argsSchema: scriptablePackArgsSchema({ expectedRevision: SCRIPTABLE_PACK_REVISION_SCHEMA }, [
|
|
300
|
+
"expectedRevision"
|
|
301
|
+
])
|
|
302
|
+
}
|
|
303
|
+
];
|
|
304
|
+
function actual(value) {
|
|
305
|
+
if (value === null) return "null";
|
|
306
|
+
if (Array.isArray(value)) return "array";
|
|
307
|
+
if (ArrayBuffer.isView(value)) return value.constructor.name;
|
|
308
|
+
return typeof value;
|
|
309
|
+
}
|
|
310
|
+
function invalid(propertyPath, expected, value, sourcePath) {
|
|
311
|
+
return err({
|
|
312
|
+
code: "pack-source-definition-invalid",
|
|
313
|
+
expected,
|
|
314
|
+
hint: "repair the default exported ScriptablePack definition, then inspect Meta again",
|
|
315
|
+
detail: {
|
|
316
|
+
...sourcePath === void 0 ? {} : { sourcePath },
|
|
317
|
+
propertyPath,
|
|
318
|
+
actual: actual(value)
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
function isRecord(value) {
|
|
323
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
324
|
+
}
|
|
325
|
+
function isAssetGuid(value) {
|
|
326
|
+
return value instanceof Uint8Array && value.byteLength === 16;
|
|
327
|
+
}
|
|
328
|
+
function guidKey(guid) {
|
|
329
|
+
return AssetGuid.format(guid);
|
|
330
|
+
}
|
|
331
|
+
function freezeDefinition(definition) {
|
|
332
|
+
const assets = Object.fromEntries(
|
|
333
|
+
Object.entries(definition.assets).map(([sourceKey, descriptor]) => [
|
|
334
|
+
sourceKey,
|
|
335
|
+
Object.freeze({ ...descriptor, guid: descriptor.guid.slice() })
|
|
336
|
+
])
|
|
337
|
+
);
|
|
338
|
+
const externalAssets = Object.fromEntries(
|
|
339
|
+
Object.entries(definition.externalAssets).map(([alias, guid]) => [
|
|
340
|
+
alias,
|
|
341
|
+
guid.slice()
|
|
342
|
+
])
|
|
343
|
+
);
|
|
344
|
+
return Object.freeze({
|
|
345
|
+
...definition,
|
|
346
|
+
packageId: definition.packageId.slice(),
|
|
347
|
+
assets: Object.freeze(assets),
|
|
348
|
+
externalAssets: Object.freeze(externalAssets)
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
function validateScriptablePackDefinition(value, sourcePath) {
|
|
352
|
+
if (!isRecord(value))
|
|
353
|
+
return invalid("$", "a ScriptablePack definition object", value, sourcePath);
|
|
354
|
+
if (value.schemaVersion !== "1.0.0") {
|
|
355
|
+
return invalid("$.schemaVersion", "the literal '1.0.0'", value.schemaVersion, sourcePath);
|
|
356
|
+
}
|
|
357
|
+
if (!isAssetGuid(value.packageId)) {
|
|
358
|
+
return invalid("$.packageId", "a 16-byte AssetGuid", value.packageId, sourcePath);
|
|
359
|
+
}
|
|
360
|
+
if (value.name !== void 0 && typeof value.name !== "string") {
|
|
361
|
+
return invalid("$.name", "a string when present", value.name, sourcePath);
|
|
362
|
+
}
|
|
363
|
+
if (!isRecord(value.assets) || Object.keys(value.assets).length === 0) {
|
|
364
|
+
return invalid(
|
|
365
|
+
"$.assets",
|
|
366
|
+
"a non-empty sourceKey to descriptor object",
|
|
367
|
+
value.assets,
|
|
368
|
+
sourcePath
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
if (!isRecord(value.externalAssets)) {
|
|
372
|
+
return invalid(
|
|
373
|
+
"$.externalAssets",
|
|
374
|
+
"an alias to AssetGuid object",
|
|
375
|
+
value.externalAssets,
|
|
376
|
+
sourcePath
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
if (typeof value.build !== "function") {
|
|
380
|
+
return invalid("$.build", "a build(assetReader) function", value.build, sourcePath);
|
|
381
|
+
}
|
|
382
|
+
const packageGuid = guidKey(value.packageId);
|
|
383
|
+
const localGuids = /* @__PURE__ */ new Set();
|
|
384
|
+
for (const [sourceKey, descriptor] of Object.entries(value.assets)) {
|
|
385
|
+
if (sourceKey.trim().length === 0) {
|
|
386
|
+
return invalid("$.assets", "non-empty sourceKey object keys", sourceKey, sourcePath);
|
|
387
|
+
}
|
|
388
|
+
if (!isRecord(descriptor)) {
|
|
389
|
+
return invalid(
|
|
390
|
+
`$.assets[${JSON.stringify(sourceKey)}]`,
|
|
391
|
+
"an asset descriptor",
|
|
392
|
+
descriptor,
|
|
393
|
+
sourcePath
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
if (!isAssetGuid(descriptor.guid)) {
|
|
397
|
+
return invalid(
|
|
398
|
+
`$.assets[${JSON.stringify(sourceKey)}].guid`,
|
|
399
|
+
"a 16-byte AssetGuid",
|
|
400
|
+
descriptor.guid,
|
|
401
|
+
sourcePath
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
if (typeof descriptor.kind !== "string" || !isScriptablePackAssetKind(descriptor.kind)) {
|
|
405
|
+
return invalid(
|
|
406
|
+
`$.assets[${JSON.stringify(sourceKey)}].kind`,
|
|
407
|
+
"one of SCRIPTABLE_PACK_ASSET_KINDS",
|
|
408
|
+
descriptor.kind,
|
|
409
|
+
sourcePath
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
if (descriptor.name !== void 0 && typeof descriptor.name !== "string") {
|
|
413
|
+
return invalid(
|
|
414
|
+
`$.assets[${JSON.stringify(sourceKey)}].name`,
|
|
415
|
+
"a string when present",
|
|
416
|
+
descriptor.name,
|
|
417
|
+
sourcePath
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
const guid = guidKey(descriptor.guid);
|
|
421
|
+
if (guid === packageGuid || localGuids.has(guid)) {
|
|
422
|
+
return invalid(
|
|
423
|
+
`$.assets[${JSON.stringify(sourceKey)}].guid`,
|
|
424
|
+
"a GUID unique within the package and distinct from packageId",
|
|
425
|
+
descriptor.guid,
|
|
426
|
+
sourcePath
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
localGuids.add(guid);
|
|
430
|
+
}
|
|
431
|
+
const externalGuids = /* @__PURE__ */ new Set();
|
|
432
|
+
for (const [alias, guidValue] of Object.entries(value.externalAssets)) {
|
|
433
|
+
if (alias.trim().length === 0) {
|
|
434
|
+
return invalid("$.externalAssets", "non-empty alias object keys", alias, sourcePath);
|
|
435
|
+
}
|
|
436
|
+
if (!isAssetGuid(guidValue)) {
|
|
437
|
+
return invalid(
|
|
438
|
+
`$.externalAssets[${JSON.stringify(alias)}]`,
|
|
439
|
+
"a 16-byte AssetGuid",
|
|
440
|
+
guidValue,
|
|
441
|
+
sourcePath
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
const guid = guidKey(guidValue);
|
|
445
|
+
if (guid === packageGuid || localGuids.has(guid) || externalGuids.has(guid)) {
|
|
446
|
+
return invalid(
|
|
447
|
+
`$.externalAssets[${JSON.stringify(alias)}]`,
|
|
448
|
+
"a unique foreign GUID not owned by this package",
|
|
449
|
+
guidValue,
|
|
450
|
+
sourcePath
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
externalGuids.add(guid);
|
|
454
|
+
}
|
|
455
|
+
return ok(freezeDefinition(value));
|
|
456
|
+
}
|
|
457
|
+
function projectScriptablePackMeta(definition, source) {
|
|
458
|
+
const subAssets = Object.entries(definition.assets).sort(([left], [right]) => left.localeCompare(right)).map(([sourceKey, descriptor], sourceIndex) => ({
|
|
459
|
+
guid: guidKey(descriptor.guid),
|
|
460
|
+
sourceIndex,
|
|
461
|
+
sourceKey,
|
|
462
|
+
kind: descriptor.kind,
|
|
463
|
+
...descriptor.name === void 0 ? {} : { name: descriptor.name }
|
|
464
|
+
}));
|
|
465
|
+
const externalAssets = Object.entries(definition.externalAssets).sort(([left], [right]) => left.localeCompare(right)).map(([alias, guid]) => ({ alias, guid: guidKey(guid) }));
|
|
466
|
+
return {
|
|
467
|
+
schemaVersion: "1.0.0",
|
|
468
|
+
kind: "external-asset-package",
|
|
469
|
+
packageId: guidKey(definition.packageId),
|
|
470
|
+
...definition.name === void 0 ? {} : { name: definition.name },
|
|
471
|
+
importer: "pack-ts",
|
|
472
|
+
source,
|
|
473
|
+
importSettings: { contractVersion: "1.0.0", externalAssets },
|
|
474
|
+
subAssets
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/evidence/source-inventory.ts
|
|
479
|
+
function stableValue(value) {
|
|
480
|
+
if (Array.isArray(value)) return value.map(stableValue);
|
|
481
|
+
if (value !== null && typeof value === "object") {
|
|
482
|
+
return Object.fromEntries(
|
|
483
|
+
Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, stableValue(entry)])
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
return value;
|
|
487
|
+
}
|
|
488
|
+
function producerRelativeLogicalPath(producerRoot, sourcePath) {
|
|
489
|
+
const root = resolve(producerRoot);
|
|
490
|
+
const source = resolve(sourcePath);
|
|
491
|
+
const path = relative(root, source);
|
|
492
|
+
if (path.length === 0 || path === ".." || path.startsWith(`..${sep}`) || resolve(root, path) !== source) {
|
|
493
|
+
throw new Error("producer source must be inside the injected producer root");
|
|
494
|
+
}
|
|
495
|
+
return path.split(sep).join("/");
|
|
496
|
+
}
|
|
497
|
+
function producerRelativeDdcKey(input) {
|
|
498
|
+
const logicalPath = producerRelativeLogicalPath(input.producerRoot, input.sourcePath);
|
|
499
|
+
const semantic = {
|
|
500
|
+
schemaVersion: input.schemaVersion,
|
|
501
|
+
logicalPath,
|
|
502
|
+
sourceDigest: input.sourceDigest,
|
|
503
|
+
importer: input.importer,
|
|
504
|
+
codec: input.codec,
|
|
505
|
+
settings: stableValue(input.settings),
|
|
506
|
+
producer: input.producer,
|
|
507
|
+
profile: input.profile,
|
|
508
|
+
declaredGuids: [...input.declaredGuids ?? []].sort()
|
|
509
|
+
};
|
|
510
|
+
return createHash("sha256").update(JSON.stringify(semantic)).digest("hex");
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// src/scriptable-pack-node.ts
|
|
514
|
+
var IMPORT_META_RESOLVE_FLAG = "--experimental-import-meta-resolve";
|
|
515
|
+
function scriptablePackWorkerExecArgv() {
|
|
516
|
+
const inherited = process.execArgv.filter((arg) => !arg.startsWith("--input-type"));
|
|
517
|
+
return inherited.includes(IMPORT_META_RESOLVE_FLAG) ? inherited : [...inherited, IMPORT_META_RESOLVE_FLAG];
|
|
518
|
+
}
|
|
519
|
+
function scriptablePackDdcKey(input) {
|
|
520
|
+
return producerRelativeDdcKey(input);
|
|
521
|
+
}
|
|
522
|
+
async function readOptionalBuildCache(cache) {
|
|
523
|
+
try {
|
|
524
|
+
const cached = await cache.read();
|
|
525
|
+
if (cached !== null) return { value: cached, fromCache: true };
|
|
526
|
+
} catch {
|
|
527
|
+
}
|
|
528
|
+
return { value: await cache.coldCook(), fromCache: false };
|
|
529
|
+
}
|
|
530
|
+
async function resolveRelativeSourceImport(sourcePath, specifier) {
|
|
531
|
+
if (!specifier.startsWith(".")) return void 0;
|
|
532
|
+
const raw = resolve(dirname(sourcePath), specifier);
|
|
533
|
+
const candidates = extname(raw).length > 0 ? [raw] : [
|
|
534
|
+
raw,
|
|
535
|
+
`${raw}.ts`,
|
|
536
|
+
`${raw}.tsx`,
|
|
537
|
+
`${raw}.mts`,
|
|
538
|
+
`${raw}.js`,
|
|
539
|
+
`${raw}.mjs`,
|
|
540
|
+
`${raw}.json`,
|
|
541
|
+
resolve(raw, "index.ts")
|
|
542
|
+
];
|
|
543
|
+
for (const candidate of candidates) {
|
|
544
|
+
try {
|
|
545
|
+
if ((await stat(candidate)).isFile()) return candidate;
|
|
546
|
+
} catch {
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return void 0;
|
|
550
|
+
}
|
|
551
|
+
async function inventoryScriptablePackSource(sourcePath) {
|
|
552
|
+
const pending = [await realpath(sourcePath)];
|
|
553
|
+
const seen = /* @__PURE__ */ new Set();
|
|
554
|
+
const entries = [];
|
|
555
|
+
while (pending.length > 0) {
|
|
556
|
+
const path = pending.pop();
|
|
557
|
+
if (path === void 0 || seen.has(path)) continue;
|
|
558
|
+
seen.add(path);
|
|
559
|
+
const bytes = await readFile(path);
|
|
560
|
+
entries.push({ path, digest: `sha256:${createHash("sha256").update(bytes).digest("hex")}` });
|
|
561
|
+
const imports = ts.preProcessFile(new TextDecoder().decode(bytes), true, true).importedFiles;
|
|
562
|
+
for (const imported of imports) {
|
|
563
|
+
const resolved = await resolveRelativeSourceImport(path, imported.fileName);
|
|
564
|
+
if (resolved !== void 0) pending.push(await realpath(resolved));
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return entries.sort((left, right) => left.path.localeCompare(right.path));
|
|
568
|
+
}
|
|
569
|
+
function serializeFailure(value) {
|
|
570
|
+
if (value !== null && typeof value === "object") {
|
|
571
|
+
const failure = value;
|
|
572
|
+
return {
|
|
573
|
+
name: failure.name,
|
|
574
|
+
code: failure.code,
|
|
575
|
+
expected: failure.expected,
|
|
576
|
+
actual: failure.actual,
|
|
577
|
+
hint: failure.hint,
|
|
578
|
+
detail: failure.detail,
|
|
579
|
+
message: failure.message
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
return { message: String(value) };
|
|
583
|
+
}
|
|
584
|
+
function hydrateFailure(value) {
|
|
585
|
+
if (value === null || typeof value !== "object") return value;
|
|
586
|
+
const failure = value;
|
|
587
|
+
if (failure.name === "AssetError" && typeof failure.code === "string" && typeof failure.expected === "string" && typeof failure.hint === "string") {
|
|
588
|
+
const args = {
|
|
589
|
+
code: failure.code,
|
|
590
|
+
expected: failure.expected,
|
|
591
|
+
hint: failure.hint
|
|
592
|
+
};
|
|
593
|
+
return failure.detail === void 0 ? new AssetError(args) : new AssetError({
|
|
594
|
+
...args,
|
|
595
|
+
detail: failure.detail
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
if (failure.name === "ImportError" && typeof failure.code === "string" && typeof failure.expected === "string" && typeof failure.hint === "string" && failure.detail !== void 0) {
|
|
599
|
+
return new ImportError({
|
|
600
|
+
code: failure.code,
|
|
601
|
+
expected: failure.expected,
|
|
602
|
+
hint: failure.hint,
|
|
603
|
+
detail: failure.detail,
|
|
604
|
+
...typeof failure.actual === "string" ? { actual: failure.actual } : {}
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
if (typeof failure.message === "string") {
|
|
608
|
+
const error = new Error(failure.message);
|
|
609
|
+
if (typeof failure.name === "string") error.name = failure.name;
|
|
610
|
+
return error;
|
|
611
|
+
}
|
|
612
|
+
return value;
|
|
613
|
+
}
|
|
614
|
+
var WorkerScriptablePackExecutor = class {
|
|
615
|
+
worker;
|
|
616
|
+
compileRoot;
|
|
617
|
+
nextBuildId = 0;
|
|
618
|
+
disposal;
|
|
619
|
+
build;
|
|
620
|
+
async load(sourcePath) {
|
|
621
|
+
const workerUrl = scriptablePackWorkerUrl();
|
|
622
|
+
const compileRoot = await mkdtemp(resolve(tmpdir(), "forgeax-scriptable-pack-"));
|
|
623
|
+
this.compileRoot = compileRoot;
|
|
624
|
+
const worker = new Worker(workerUrl, {
|
|
625
|
+
workerData: { sourcePath: resolve(sourcePath), compileRoot },
|
|
626
|
+
execArgv: scriptablePackWorkerExecArgv(),
|
|
627
|
+
resourceLimits: { maxOldGenerationSizeMb: 256, maxYoungGenerationSizeMb: 64 }
|
|
628
|
+
});
|
|
629
|
+
this.worker = worker;
|
|
630
|
+
worker.on("message", (message) => this.onMessage(message));
|
|
631
|
+
worker.unref();
|
|
632
|
+
return new Promise((resolveLoad, rejectLoad) => {
|
|
633
|
+
const onMessage = (message) => {
|
|
634
|
+
if (message === null || typeof message !== "object") return;
|
|
635
|
+
const value = message;
|
|
636
|
+
if (value.kind === "loaded") {
|
|
637
|
+
worker.off("message", onMessage);
|
|
638
|
+
resolveLoad({
|
|
639
|
+
default: value.definition === void 0 ? void 0 : {
|
|
640
|
+
...value.definition,
|
|
641
|
+
build: (reader) => this.runBuild(reader)
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
} else if (value.kind === "load-threw") {
|
|
645
|
+
worker.off("message", onMessage);
|
|
646
|
+
rejectLoad(hydrateFailure(value.error));
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
worker.on("message", onMessage);
|
|
650
|
+
worker.once("error", rejectLoad);
|
|
651
|
+
worker.once("exit", (code) => {
|
|
652
|
+
if (code !== 0) rejectLoad(new Error(`ScriptablePack worker exited with code ${code}`));
|
|
653
|
+
});
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
dispose(reason = "complete") {
|
|
657
|
+
if (this.disposal !== void 0) return this.disposal;
|
|
658
|
+
const worker = this.worker;
|
|
659
|
+
this.worker = void 0;
|
|
660
|
+
const compileRoot = this.compileRoot;
|
|
661
|
+
this.compileRoot = void 0;
|
|
662
|
+
const active = this.build;
|
|
663
|
+
this.build = void 0;
|
|
664
|
+
if (active !== void 0) {
|
|
665
|
+
active.reject(new Error(`ScriptablePack build disposed during ${reason}`));
|
|
666
|
+
}
|
|
667
|
+
this.disposal = (async () => {
|
|
668
|
+
if (worker !== void 0) await worker.terminate();
|
|
669
|
+
if (compileRoot !== void 0) await rm(compileRoot, { recursive: true, force: true });
|
|
670
|
+
})();
|
|
671
|
+
return this.disposal;
|
|
672
|
+
}
|
|
673
|
+
runBuild(reader) {
|
|
674
|
+
const worker = this.worker;
|
|
675
|
+
if (worker === void 0) return Promise.reject(new Error("ScriptablePack worker is closed"));
|
|
676
|
+
if (this.build !== void 0)
|
|
677
|
+
return Promise.reject(new Error("ScriptablePack worker already has an active build"));
|
|
678
|
+
const id = this.nextBuildId++;
|
|
679
|
+
return new Promise((resolveBuild, rejectBuild) => {
|
|
680
|
+
this.build = { id, reader, resolve: resolveBuild, reject: rejectBuild };
|
|
681
|
+
worker.postMessage({ kind: "build", buildId: id });
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
onMessage(message) {
|
|
685
|
+
if (message === null || typeof message !== "object") return;
|
|
686
|
+
const value = message;
|
|
687
|
+
const active = this.build;
|
|
688
|
+
if (active === void 0 || value.buildId !== active.id) return;
|
|
689
|
+
if (value.kind === "asset-read" && typeof value.readId === "number") {
|
|
690
|
+
void active.reader.readByGuid(value.guid).then(
|
|
691
|
+
(result) => this.worker?.postMessage({
|
|
692
|
+
kind: "asset-result",
|
|
693
|
+
readId: value.readId,
|
|
694
|
+
result: result.ok === true ? result : { ...result, error: serializeFailure(result.error) }
|
|
695
|
+
})
|
|
696
|
+
).catch((error) => {
|
|
697
|
+
if (this.build?.id !== active.id) return;
|
|
698
|
+
this.build = void 0;
|
|
699
|
+
active.reject(error);
|
|
700
|
+
void this.dispose("failure");
|
|
701
|
+
});
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
this.build = void 0;
|
|
705
|
+
if (value.kind === "build-result") active.resolve(value.result);
|
|
706
|
+
else if (value.kind === "build-threw") active.reject(hydrateFailure(value.error));
|
|
707
|
+
else this.build = active;
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
function scriptablePackWorkerUrl() {
|
|
711
|
+
const bundledWorker = new URL("./scriptable-pack-worker.mjs", import.meta.url);
|
|
712
|
+
return existsSync(fileURLToPath(bundledWorker)) ? bundledWorker : new URL("../dist/scriptable-pack-worker.mjs", import.meta.url);
|
|
713
|
+
}
|
|
714
|
+
var ReusableWorkerScriptablePackExecutor = class {
|
|
715
|
+
constructor(maxTasksPerWorker, release) {
|
|
716
|
+
this.maxTasksPerWorker = maxTasksPerWorker;
|
|
717
|
+
this.release = release;
|
|
718
|
+
this.worker = new Worker(scriptablePackWorkerUrl(), {
|
|
719
|
+
workerData: { mode: "reusable" },
|
|
720
|
+
execArgv: scriptablePackWorkerExecArgv(),
|
|
721
|
+
resourceLimits: { maxOldGenerationSizeMb: 256, maxYoungGenerationSizeMb: 64 }
|
|
722
|
+
});
|
|
723
|
+
this.worker.on("message", (message) => this.onMessage(message));
|
|
724
|
+
this.worker.on("error", (error) => {
|
|
725
|
+
this.failed = true;
|
|
726
|
+
this.rejectActive(error);
|
|
727
|
+
void this.dispose("failure");
|
|
728
|
+
});
|
|
729
|
+
this.worker.on("exit", (code) => {
|
|
730
|
+
if (code === 0 || this.disposal !== void 0) return;
|
|
731
|
+
this.failed = true;
|
|
732
|
+
this.rejectActive(new Error(`ScriptablePack worker exited with code ${code}`));
|
|
733
|
+
void this.dispose("failure");
|
|
734
|
+
});
|
|
735
|
+
this.worker.unref();
|
|
736
|
+
}
|
|
737
|
+
maxTasksPerWorker;
|
|
738
|
+
release;
|
|
739
|
+
worker;
|
|
740
|
+
compileRootReady = mkdtemp(resolve(tmpdir(), "forgeax-scriptable-pack-pool-"));
|
|
741
|
+
compileRoot;
|
|
742
|
+
nextLoadId = 0;
|
|
743
|
+
nextBuildId = 0;
|
|
744
|
+
pendingLoad;
|
|
745
|
+
build;
|
|
746
|
+
disposal;
|
|
747
|
+
forceDispose = false;
|
|
748
|
+
failed = false;
|
|
749
|
+
taskCount = 0;
|
|
750
|
+
released = false;
|
|
751
|
+
async load(sourcePath) {
|
|
752
|
+
const activeDisposal = this.disposal;
|
|
753
|
+
if (activeDisposal !== void 0) {
|
|
754
|
+
await activeDisposal;
|
|
755
|
+
if (this.failed) {
|
|
756
|
+
return Promise.reject(new Error("ScriptablePack executor is not leased"));
|
|
757
|
+
}
|
|
758
|
+
this.disposal = void 0;
|
|
759
|
+
}
|
|
760
|
+
this.released = false;
|
|
761
|
+
if (this.pendingLoad !== void 0 || this.build !== void 0) {
|
|
762
|
+
return Promise.reject(new Error("ScriptablePack executor already has an active task"));
|
|
763
|
+
}
|
|
764
|
+
const compileRoot = this.compileRoot ?? await this.compileRootReady;
|
|
765
|
+
if (this.disposal !== void 0) {
|
|
766
|
+
const disposal = this.disposal;
|
|
767
|
+
await disposal;
|
|
768
|
+
if (this.failed) {
|
|
769
|
+
return Promise.reject(new Error("ScriptablePack executor is not leased"));
|
|
770
|
+
}
|
|
771
|
+
this.disposal = void 0;
|
|
772
|
+
}
|
|
773
|
+
const loadId = this.nextLoadId++;
|
|
774
|
+
this.compileRoot = compileRoot;
|
|
775
|
+
this.taskCount += 1;
|
|
776
|
+
return new Promise((resolveLoad, rejectLoad) => {
|
|
777
|
+
this.pendingLoad = { id: loadId, resolve: resolveLoad, reject: rejectLoad };
|
|
778
|
+
try {
|
|
779
|
+
this.worker.postMessage({
|
|
780
|
+
kind: "load",
|
|
781
|
+
loadId,
|
|
782
|
+
sourcePath: resolve(sourcePath),
|
|
783
|
+
compileRoot
|
|
784
|
+
});
|
|
785
|
+
} catch (error) {
|
|
786
|
+
this.pendingLoad = void 0;
|
|
787
|
+
rejectLoad(error);
|
|
788
|
+
void this.dispose("failure");
|
|
789
|
+
}
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
dispose(reason = "complete") {
|
|
793
|
+
if (reason === "failure") this.forceDispose = true;
|
|
794
|
+
const activeDisposal = this.disposal;
|
|
795
|
+
if (activeDisposal !== void 0) {
|
|
796
|
+
return activeDisposal;
|
|
797
|
+
}
|
|
798
|
+
this.rejectActive(new Error(`ScriptablePack task disposed during ${reason}`));
|
|
799
|
+
this.disposal = (async () => {
|
|
800
|
+
const compileRoot = this.compileRoot ?? await this.compileRootReady;
|
|
801
|
+
const reusable = reason === "complete" && !this.failed && !this.forceDispose && this.taskCount < this.maxTasksPerWorker;
|
|
802
|
+
this.forceDispose = false;
|
|
803
|
+
if (!reusable) {
|
|
804
|
+
this.compileRoot = void 0;
|
|
805
|
+
await this.worker.terminate();
|
|
806
|
+
await rm(compileRoot, { recursive: true, force: true });
|
|
807
|
+
}
|
|
808
|
+
this.released = true;
|
|
809
|
+
this.disposal = void 0;
|
|
810
|
+
this.release(this, reusable);
|
|
811
|
+
})();
|
|
812
|
+
return this.disposal;
|
|
813
|
+
}
|
|
814
|
+
rejectActive(error) {
|
|
815
|
+
const loading = this.pendingLoad;
|
|
816
|
+
this.pendingLoad = void 0;
|
|
817
|
+
loading?.reject(error);
|
|
818
|
+
const building = this.build;
|
|
819
|
+
this.build = void 0;
|
|
820
|
+
building?.reject(error);
|
|
821
|
+
}
|
|
822
|
+
runBuild(reader) {
|
|
823
|
+
if (this.released || this.disposal !== void 0) {
|
|
824
|
+
return Promise.reject(new Error("ScriptablePack executor is not leased"));
|
|
825
|
+
}
|
|
826
|
+
if (this.build !== void 0) {
|
|
827
|
+
return Promise.reject(new Error("ScriptablePack worker already has an active build"));
|
|
828
|
+
}
|
|
829
|
+
const id = this.nextBuildId++;
|
|
830
|
+
return new Promise((resolveBuild, rejectBuild) => {
|
|
831
|
+
this.build = { id, reader, resolve: resolveBuild, reject: rejectBuild };
|
|
832
|
+
try {
|
|
833
|
+
this.worker.postMessage({ kind: "build", buildId: id });
|
|
834
|
+
} catch (error) {
|
|
835
|
+
this.build = void 0;
|
|
836
|
+
rejectBuild(error);
|
|
837
|
+
void this.dispose("failure");
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
onMessage(message) {
|
|
842
|
+
if (message === null || typeof message !== "object") return;
|
|
843
|
+
const value = message;
|
|
844
|
+
const loading = this.pendingLoad;
|
|
845
|
+
if (loading !== void 0 && value.kind === "loaded" && value.loadId === loading.id) {
|
|
846
|
+
this.pendingLoad = void 0;
|
|
847
|
+
loading.resolve({
|
|
848
|
+
default: value.definition === void 0 ? void 0 : {
|
|
849
|
+
...value.definition,
|
|
850
|
+
build: (reader) => this.runBuild(reader)
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
if (loading !== void 0 && value.kind === "load-threw" && value.loadId === loading.id) {
|
|
856
|
+
this.pendingLoad = void 0;
|
|
857
|
+
loading.reject(hydrateFailure(value.error));
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
const active = this.build;
|
|
861
|
+
if (active === void 0 || value.buildId !== active.id) return;
|
|
862
|
+
if (value.kind === "asset-read" && typeof value.readId === "number") {
|
|
863
|
+
void active.reader.readByGuid(value.guid).then(
|
|
864
|
+
(result) => this.worker.postMessage({
|
|
865
|
+
kind: "asset-result",
|
|
866
|
+
readId: value.readId,
|
|
867
|
+
result: result.ok === true ? result : { ...result, error: serializeFailure(result.error) }
|
|
868
|
+
})
|
|
869
|
+
).catch((error) => {
|
|
870
|
+
if (this.build?.id !== active.id) return;
|
|
871
|
+
this.build = void 0;
|
|
872
|
+
active.reject(error);
|
|
873
|
+
void this.dispose("failure");
|
|
874
|
+
});
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
this.build = void 0;
|
|
878
|
+
if (value.kind === "build-result") active.resolve(value.result);
|
|
879
|
+
else if (value.kind === "build-threw") active.reject(hydrateFailure(value.error));
|
|
880
|
+
else this.build = active;
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
var DefaultScriptablePackModuleExecutorPool = class {
|
|
884
|
+
maxWorkers;
|
|
885
|
+
maxTasksPerWorker;
|
|
886
|
+
idle = [];
|
|
887
|
+
waiters = [];
|
|
888
|
+
executors = /* @__PURE__ */ new Set();
|
|
889
|
+
workerCount = 0;
|
|
890
|
+
closed = false;
|
|
891
|
+
constructor(options = {}) {
|
|
892
|
+
this.maxWorkers = Math.max(1, Math.trunc(options.maxWorkers ?? 2));
|
|
893
|
+
this.maxTasksPerWorker = Math.max(1, Math.trunc(options.maxTasksPerWorker ?? 32));
|
|
894
|
+
}
|
|
895
|
+
acquire() {
|
|
896
|
+
if (this.closed) return Promise.reject(new Error("ScriptablePack executor pool is closed"));
|
|
897
|
+
const executor = this.idle.pop();
|
|
898
|
+
if (executor !== void 0) return Promise.resolve(executor);
|
|
899
|
+
if (this.workerCount < this.maxWorkers) {
|
|
900
|
+
this.workerCount += 1;
|
|
901
|
+
return Promise.resolve(this.createExecutor());
|
|
902
|
+
}
|
|
903
|
+
return new Promise(
|
|
904
|
+
(resolveAcquire, rejectAcquire) => this.waiters.push({ resolve: resolveAcquire, reject: rejectAcquire })
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
async dispose() {
|
|
908
|
+
if (this.closed) return;
|
|
909
|
+
this.closed = true;
|
|
910
|
+
const waiters = this.waiters.splice(0);
|
|
911
|
+
for (const waiter of waiters) {
|
|
912
|
+
waiter.reject(new Error("ScriptablePack executor pool is closed"));
|
|
913
|
+
}
|
|
914
|
+
this.idle.splice(0);
|
|
915
|
+
await Promise.all([...this.executors].map((executor) => executor.dispose("failure")));
|
|
916
|
+
}
|
|
917
|
+
createExecutor() {
|
|
918
|
+
const executor = new ReusableWorkerScriptablePackExecutor(
|
|
919
|
+
this.maxTasksPerWorker,
|
|
920
|
+
(executor2, reusable) => {
|
|
921
|
+
if (this.closed) {
|
|
922
|
+
this.executors.delete(executor2);
|
|
923
|
+
if (!reusable) this.workerCount -= 1;
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
if (!reusable) {
|
|
927
|
+
this.executors.delete(executor2);
|
|
928
|
+
this.workerCount -= 1;
|
|
929
|
+
} else {
|
|
930
|
+
this.idle.push(executor2);
|
|
931
|
+
}
|
|
932
|
+
this.drain();
|
|
933
|
+
}
|
|
934
|
+
);
|
|
935
|
+
this.executors.add(executor);
|
|
936
|
+
return executor;
|
|
937
|
+
}
|
|
938
|
+
drain() {
|
|
939
|
+
if (this.closed) return;
|
|
940
|
+
while (this.waiters.length > 0) {
|
|
941
|
+
const idle = this.idle.pop();
|
|
942
|
+
if (idle !== void 0) {
|
|
943
|
+
this.waiters.shift()?.resolve(idle);
|
|
944
|
+
continue;
|
|
945
|
+
}
|
|
946
|
+
if (this.workerCount >= this.maxWorkers) return;
|
|
947
|
+
this.workerCount += 1;
|
|
948
|
+
this.waiters.shift()?.resolve(this.createExecutor());
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
function createScriptablePackModuleExecutorPool(options = {}) {
|
|
953
|
+
return new DefaultScriptablePackModuleExecutorPool(options);
|
|
954
|
+
}
|
|
955
|
+
function loadFailure(sourcePath, reason, phase, diagnostic, timeoutMs) {
|
|
956
|
+
return err({
|
|
957
|
+
code: "pack-source-load-failed",
|
|
958
|
+
expected: "a trusted synchronous module with a valid default ScriptablePack export",
|
|
959
|
+
hint: "repair the module load or initialization failure, then inspect Meta again",
|
|
960
|
+
detail: {
|
|
961
|
+
sourcePath,
|
|
962
|
+
reason,
|
|
963
|
+
phase,
|
|
964
|
+
diagnostic,
|
|
965
|
+
...timeoutMs === void 0 ? {} : { timeoutMs }
|
|
966
|
+
}
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
function buildTimeoutFailure(sourcePath, timeoutMs) {
|
|
970
|
+
return {
|
|
971
|
+
code: "pack-source-load-failed",
|
|
972
|
+
expected: "a ScriptablePack build to settle within the configured build timeout",
|
|
973
|
+
hint: "repair the authored build, then retry through a fresh ScriptablePack generation",
|
|
974
|
+
detail: {
|
|
975
|
+
sourcePath,
|
|
976
|
+
reason: "timeout",
|
|
977
|
+
phase: "build",
|
|
978
|
+
timeoutMs,
|
|
979
|
+
diagnostic: `ScriptablePack build exceeded ${timeoutMs}ms`
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
async function loadScriptablePack(sourcePath, options = {}) {
|
|
984
|
+
const timeoutMs = options.timeoutMs ?? 5e3;
|
|
985
|
+
const buildTimeoutMs = options.buildTimeoutMs ?? 5e3;
|
|
986
|
+
const executor = options.executor ?? new WorkerScriptablePackExecutor();
|
|
987
|
+
let disposeReason;
|
|
988
|
+
let timeout;
|
|
989
|
+
try {
|
|
990
|
+
const timeoutSignal = /* @__PURE__ */ Symbol("scriptable-pack-module-timeout");
|
|
991
|
+
const loaded = await Promise.race([
|
|
992
|
+
executor.load(sourcePath),
|
|
993
|
+
new Promise((resolve3) => {
|
|
994
|
+
timeout = setTimeout(() => resolve3(timeoutSignal), timeoutMs);
|
|
995
|
+
})
|
|
996
|
+
]);
|
|
997
|
+
if (loaded === timeoutSignal) {
|
|
998
|
+
disposeReason = "timeout";
|
|
999
|
+
return loadFailure(
|
|
1000
|
+
sourcePath,
|
|
1001
|
+
"timeout",
|
|
1002
|
+
"module-load",
|
|
1003
|
+
`ScriptablePack module initialization exceeded ${timeoutMs}ms`,
|
|
1004
|
+
timeoutMs
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
1007
|
+
const moduleValue = loaded !== null && typeof loaded === "object" && "default" in loaded ? loaded.default : void 0;
|
|
1008
|
+
const validated = validateScriptablePackDefinition(moduleValue, sourcePath);
|
|
1009
|
+
if (!validated.ok) {
|
|
1010
|
+
disposeReason = "failure";
|
|
1011
|
+
return validated;
|
|
1012
|
+
}
|
|
1013
|
+
if (options.metadataOnly === true) {
|
|
1014
|
+
disposeReason = "complete";
|
|
1015
|
+
return validated;
|
|
1016
|
+
}
|
|
1017
|
+
const definition = validated.value;
|
|
1018
|
+
return ok({
|
|
1019
|
+
...definition,
|
|
1020
|
+
build: async (reader) => {
|
|
1021
|
+
let buildTimedOut = false;
|
|
1022
|
+
let buildTimeout;
|
|
1023
|
+
const timeoutResult = err(buildTimeoutFailure(sourcePath, buildTimeoutMs));
|
|
1024
|
+
try {
|
|
1025
|
+
const built = await Promise.race([
|
|
1026
|
+
Promise.resolve(definition.build(reader)),
|
|
1027
|
+
new Promise((resolve3) => {
|
|
1028
|
+
buildTimeout = setTimeout(() => {
|
|
1029
|
+
buildTimedOut = true;
|
|
1030
|
+
resolve3(timeoutResult);
|
|
1031
|
+
}, buildTimeoutMs);
|
|
1032
|
+
})
|
|
1033
|
+
]);
|
|
1034
|
+
if (buildTimedOut) {
|
|
1035
|
+
await executor.dispose?.("timeout");
|
|
1036
|
+
return built;
|
|
1037
|
+
}
|
|
1038
|
+
await executor.dispose?.("complete");
|
|
1039
|
+
return built;
|
|
1040
|
+
} catch (error) {
|
|
1041
|
+
await executor.dispose?.("failure");
|
|
1042
|
+
throw error;
|
|
1043
|
+
} finally {
|
|
1044
|
+
if (buildTimeout !== void 0) clearTimeout(buildTimeout);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
});
|
|
1048
|
+
} catch (error) {
|
|
1049
|
+
const diagnostic = error instanceof Error ? error.message : String(error);
|
|
1050
|
+
disposeReason = "failure";
|
|
1051
|
+
return loadFailure(sourcePath, "module-load", "module-load", diagnostic);
|
|
1052
|
+
} finally {
|
|
1053
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
1054
|
+
if (disposeReason !== void 0) await executor.dispose?.(disposeReason);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
var CANONICAL_SCAFFOLD_MARKER = "// @forgeax-scriptable-pack canonical-v1";
|
|
1058
|
+
var CANONICAL_MANIFEST_NAME = "canonicalManifest";
|
|
1059
|
+
var CANONICAL_MUTABLE_KINDS = /* @__PURE__ */ new Set(["scene", "mesh", "material"]);
|
|
1060
|
+
function authoringError(code, input) {
|
|
1061
|
+
return {
|
|
1062
|
+
code,
|
|
1063
|
+
expected: input.expected,
|
|
1064
|
+
...input.actual === void 0 ? {} : { actual: input.actual },
|
|
1065
|
+
hint: input.hint,
|
|
1066
|
+
retryable: input.retryable,
|
|
1067
|
+
recoveryActions: input.recoveryActions,
|
|
1068
|
+
detail: {
|
|
1069
|
+
...input.requestId === void 0 ? {} : { requestId: input.requestId },
|
|
1070
|
+
...input.sourcePath === void 0 ? {} : { sourcePath: input.sourcePath },
|
|
1071
|
+
...input.sourceKey === void 0 ? {} : { sourceKey: input.sourceKey },
|
|
1072
|
+
...input.incomingRefs === void 0 ? {} : { incomingRefs: input.incomingRefs }
|
|
1073
|
+
}
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
function confinedSourcePath(gameRoot, sourcePath) {
|
|
1077
|
+
const root = resolve(gameRoot);
|
|
1078
|
+
const absolute = resolve(root, sourcePath);
|
|
1079
|
+
const rel = relative(root, absolute);
|
|
1080
|
+
if (rel.length === 0 || rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(sourcePath) || !rel.endsWith(".pack.ts")) {
|
|
1081
|
+
return err(
|
|
1082
|
+
authoringError("pack-source-path-invalid", {
|
|
1083
|
+
expected: "a game-root-relative *.pack.ts path confined to the selected game",
|
|
1084
|
+
actual: sourcePath,
|
|
1085
|
+
hint: "choose a relative ScriptablePack source path inside the selected game root",
|
|
1086
|
+
retryable: false,
|
|
1087
|
+
recoveryActions: ["choose-game-relative-source-path"],
|
|
1088
|
+
sourcePath
|
|
1089
|
+
})
|
|
1090
|
+
);
|
|
1091
|
+
}
|
|
1092
|
+
return ok({ absolute, relative: rel.split(sep).join("/") });
|
|
1093
|
+
}
|
|
1094
|
+
function revisionOf(source) {
|
|
1095
|
+
return createHash("sha256").update(source).digest("hex");
|
|
1096
|
+
}
|
|
1097
|
+
function unwrapExpression(expression) {
|
|
1098
|
+
if (ts.isAsExpression(expression) || ts.isSatisfiesExpression(expression) || ts.isParenthesizedExpression(expression)) {
|
|
1099
|
+
return unwrapExpression(expression.expression);
|
|
1100
|
+
}
|
|
1101
|
+
return expression;
|
|
1102
|
+
}
|
|
1103
|
+
function literalValue(expression) {
|
|
1104
|
+
const value = unwrapExpression(expression);
|
|
1105
|
+
if (ts.isStringLiteral(value) || ts.isNoSubstitutionTemplateLiteral(value)) return value.text;
|
|
1106
|
+
if (value.kind === ts.SyntaxKind.TrueKeyword) return true;
|
|
1107
|
+
if (value.kind === ts.SyntaxKind.FalseKeyword) return false;
|
|
1108
|
+
if (value.kind === ts.SyntaxKind.NullKeyword) return null;
|
|
1109
|
+
if (ts.isNumericLiteral(value)) return Number(value.text);
|
|
1110
|
+
if (ts.isArrayLiteralExpression(value)) return value.elements.map(literalValue);
|
|
1111
|
+
if (ts.isObjectLiteralExpression(value)) {
|
|
1112
|
+
const result = {};
|
|
1113
|
+
for (const property of value.properties) {
|
|
1114
|
+
if (!ts.isPropertyAssignment(property))
|
|
1115
|
+
throw new TypeError("manifest properties must be data");
|
|
1116
|
+
const name = property.name;
|
|
1117
|
+
const key = ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name) ? name.text : void 0;
|
|
1118
|
+
if (key === void 0) throw new TypeError("manifest property names must be static");
|
|
1119
|
+
result[key] = literalValue(property.initializer);
|
|
1120
|
+
}
|
|
1121
|
+
return result;
|
|
1122
|
+
}
|
|
1123
|
+
throw new TypeError("manifest values must be literal data");
|
|
1124
|
+
}
|
|
1125
|
+
function parseCanonicalManifest(source, sourcePath) {
|
|
1126
|
+
if (!source.startsWith(`${CANONICAL_SCAFFOLD_MARKER}
|
|
1127
|
+
`)) return void 0;
|
|
1128
|
+
const file = ts.createSourceFile(
|
|
1129
|
+
sourcePath,
|
|
1130
|
+
source,
|
|
1131
|
+
ts.ScriptTarget.Latest,
|
|
1132
|
+
true,
|
|
1133
|
+
ts.ScriptKind.TS
|
|
1134
|
+
);
|
|
1135
|
+
for (const statement of file.statements) {
|
|
1136
|
+
if (!ts.isVariableStatement(statement)) continue;
|
|
1137
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
1138
|
+
if (ts.isIdentifier(declaration.name) && declaration.name.text === CANONICAL_MANIFEST_NAME && declaration.initializer !== void 0) {
|
|
1139
|
+
const value = literalValue(declaration.initializer);
|
|
1140
|
+
if (value === null || typeof value !== "object") return void 0;
|
|
1141
|
+
return value;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
return void 0;
|
|
1146
|
+
}
|
|
1147
|
+
function guidExpression(value) {
|
|
1148
|
+
return `parseGuid(${JSON.stringify(value)})`;
|
|
1149
|
+
}
|
|
1150
|
+
function renderCanonicalScriptablePack(manifest) {
|
|
1151
|
+
const data = JSON.stringify(manifest, null, 2);
|
|
1152
|
+
return `${CANONICAL_SCAFFOLD_MARKER}
|
|
1153
|
+
import { AssetGuid } from '@forgeax/engine-pack/guid';
|
|
1154
|
+
import { buildMeshAttributeMapForUvSets } from '@forgeax/engine-geometry';
|
|
1155
|
+
import type { ScriptablePackDefinition } from '@forgeax/engine-pack/source';
|
|
1156
|
+
import type { Asset, AssetGuid as AssetGuidType } from '@forgeax/engine-types';
|
|
1157
|
+
import { ok } from '@forgeax/engine-types';
|
|
1158
|
+
|
|
1159
|
+
const ${CANONICAL_MANIFEST_NAME} = ${data} as const;
|
|
1160
|
+
|
|
1161
|
+
function parseGuid(value: string): AssetGuidType {
|
|
1162
|
+
const parsed = AssetGuid.parse(value);
|
|
1163
|
+
if (!parsed.ok) throw parsed.error;
|
|
1164
|
+
return parsed.value;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
function createOutput(kind: string): Asset {
|
|
1168
|
+
switch (kind) {
|
|
1169
|
+
case 'scene':
|
|
1170
|
+
return { kind: 'scene', entities: [], mounts: [] };
|
|
1171
|
+
case 'material':
|
|
1172
|
+
return { kind: 'material', values: {} };
|
|
1173
|
+
case 'mesh': {
|
|
1174
|
+
const vertices = new Float32Array([
|
|
1175
|
+
0, 0.7, 0, 0, 0, 1, 0.5, 1, 0, 0, 0, 1,
|
|
1176
|
+
-0.7, -0.6, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1,
|
|
1177
|
+
0.7, -0.6, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1,
|
|
1178
|
+
]);
|
|
1179
|
+
return {
|
|
1180
|
+
kind: 'mesh',
|
|
1181
|
+
vertices,
|
|
1182
|
+
indices: new Uint16Array([0, 1, 2]),
|
|
1183
|
+
attributes: { ...buildMeshAttributeMapForUvSets(1), position: vertices },
|
|
1184
|
+
aabb: new Float32Array([-0.7, -0.6, 0, 0.7, 0.7, 0]),
|
|
1185
|
+
submeshes: [{ indexOffset: 0, indexCount: 3, vertexCount: 3, topology: 'triangle-list', materialSlot: 0 }],
|
|
1186
|
+
materialSlots: [{ slotName: 'Default' }],
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
default:
|
|
1190
|
+
throw new Error(\`canonical ScriptablePack output kind is not mutable: \${kind}\`);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
const assets = Object.fromEntries(
|
|
1195
|
+
Object.entries(${CANONICAL_MANIFEST_NAME}.assets).map(([sourceKey, asset]) => [
|
|
1196
|
+
sourceKey,
|
|
1197
|
+
{ ...asset, guid: parseGuid(asset.guid) },
|
|
1198
|
+
]),
|
|
1199
|
+
);
|
|
1200
|
+
const externalAssets = Object.fromEntries(
|
|
1201
|
+
Object.entries(${CANONICAL_MANIFEST_NAME}.externalAssets).map(([alias, guid]) => [alias, parseGuid(guid)]),
|
|
1202
|
+
);
|
|
1203
|
+
|
|
1204
|
+
export default {
|
|
1205
|
+
schemaVersion: '1.0.0',
|
|
1206
|
+
packageId: ${guidExpression(manifest.packageId)},
|
|
1207
|
+
${manifest.name === void 0 ? "" : `name: ${JSON.stringify(manifest.name)},
|
|
1208
|
+
`}assets,
|
|
1209
|
+
externalAssets,
|
|
1210
|
+
build: () => ok(Object.fromEntries(Object.entries(assets).map(([sourceKey, asset]) => [sourceKey, createOutput(asset.kind)])) as never),
|
|
1211
|
+
} satisfies ScriptablePackDefinition;
|
|
1212
|
+
`;
|
|
1213
|
+
}
|
|
1214
|
+
function definitionFromCanonical(manifest) {
|
|
1215
|
+
const packageId = AssetGuid.parse(manifest.packageId);
|
|
1216
|
+
if (!packageId.ok) return invalidCanonicalGuid("$.packageId", manifest.packageId);
|
|
1217
|
+
const assets = {};
|
|
1218
|
+
for (const [sourceKey, output] of Object.entries(manifest.assets)) {
|
|
1219
|
+
const guid = AssetGuid.parse(output.guid);
|
|
1220
|
+
if (!guid.ok)
|
|
1221
|
+
return invalidCanonicalGuid(`$.assets[${JSON.stringify(sourceKey)}].guid`, output.guid);
|
|
1222
|
+
assets[sourceKey] = {
|
|
1223
|
+
guid: guid.value,
|
|
1224
|
+
kind: output.kind,
|
|
1225
|
+
...output.name === void 0 ? {} : { name: output.name }
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
const externalAssets = {};
|
|
1229
|
+
for (const [alias, value] of Object.entries(manifest.externalAssets)) {
|
|
1230
|
+
const guid = AssetGuid.parse(value);
|
|
1231
|
+
if (!guid.ok) return invalidCanonicalGuid(`$.externalAssets[${JSON.stringify(alias)}]`, value);
|
|
1232
|
+
externalAssets[alias] = guid.value;
|
|
1233
|
+
}
|
|
1234
|
+
return validateScriptablePackDefinition({
|
|
1235
|
+
schemaVersion: "1.0.0",
|
|
1236
|
+
packageId: packageId.value,
|
|
1237
|
+
...manifest.name === void 0 ? {} : { name: manifest.name },
|
|
1238
|
+
assets,
|
|
1239
|
+
externalAssets,
|
|
1240
|
+
build: () => ok({})
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
function invalidCanonicalGuid(propertyPath, actual2) {
|
|
1244
|
+
return err({
|
|
1245
|
+
code: "pack-source-definition-invalid",
|
|
1246
|
+
expected: "a canonical UUID string",
|
|
1247
|
+
hint: "repair the canonical scaffold manifest GUID and inspect Meta again",
|
|
1248
|
+
detail: { propertyPath, actual: actual2 }
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
function invalidCanonicalSource(sourcePath, error) {
|
|
1252
|
+
return err({
|
|
1253
|
+
code: "pack-source-definition-invalid",
|
|
1254
|
+
expected: "a literal canonical-v1 manifest with the required package/assets/external fields",
|
|
1255
|
+
hint: "repair the canonical scaffold or open it as custom source without structured mutation",
|
|
1256
|
+
detail: {
|
|
1257
|
+
sourcePath,
|
|
1258
|
+
propertyPath: "$.canonicalManifest",
|
|
1259
|
+
actual: error instanceof Error ? error.message : String(error)
|
|
1260
|
+
}
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
async function atomicWrite(path, source) {
|
|
1264
|
+
await mkdir(dirname(path), { recursive: true });
|
|
1265
|
+
const temporary = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
1266
|
+
try {
|
|
1267
|
+
await writeFile(temporary, source, { encoding: "utf8", flag: "wx" });
|
|
1268
|
+
await rename(temporary, path);
|
|
1269
|
+
} catch (error) {
|
|
1270
|
+
await rm(temporary, { force: true });
|
|
1271
|
+
throw error;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
function sameStrings(left, right) {
|
|
1275
|
+
return [...left].sort().join("\0") === [...right].sort().join("\0");
|
|
1276
|
+
}
|
|
1277
|
+
function createFileSystemScriptablePackAuthoringPort(options) {
|
|
1278
|
+
const resolvePath = (sourcePath) => confinedSourcePath(options.gameRoot, sourcePath);
|
|
1279
|
+
async function readSource(sourcePath) {
|
|
1280
|
+
const path = resolvePath(sourcePath);
|
|
1281
|
+
if (!path.ok) return path;
|
|
1282
|
+
try {
|
|
1283
|
+
const source = await readFile(path.value.absolute, "utf8");
|
|
1284
|
+
return ok({ ...path.value, source, revision: revisionOf(source) });
|
|
1285
|
+
} catch (error) {
|
|
1286
|
+
return err(
|
|
1287
|
+
authoringError("pack-source-write-failed", {
|
|
1288
|
+
expected: "a readable ScriptablePack source file",
|
|
1289
|
+
actual: error instanceof Error ? error.message : String(error),
|
|
1290
|
+
hint: "restore the source file or create it through the ScriptablePack gateway",
|
|
1291
|
+
retryable: true,
|
|
1292
|
+
recoveryActions: ["inspect-source-path", "retry"],
|
|
1293
|
+
sourcePath
|
|
1294
|
+
})
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
async function inspect(sourcePath) {
|
|
1299
|
+
const read = await readSource(sourcePath);
|
|
1300
|
+
if (!read.ok) return read;
|
|
1301
|
+
let canonical;
|
|
1302
|
+
try {
|
|
1303
|
+
canonical = parseCanonicalManifest(read.value.source, read.value.relative);
|
|
1304
|
+
} catch (error) {
|
|
1305
|
+
return invalidCanonicalSource(read.value.relative, error);
|
|
1306
|
+
}
|
|
1307
|
+
const loaded = canonical === void 0 ? await loadScriptablePack(read.value.absolute, { metadataOnly: true }) : definitionFromCanonical(canonical);
|
|
1308
|
+
if (!loaded.ok) return loaded;
|
|
1309
|
+
return ok({
|
|
1310
|
+
revision: read.value.revision,
|
|
1311
|
+
meta: projectScriptablePackMeta(loaded.value, read.value.relative)
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
async function preflight(sourcePath) {
|
|
1315
|
+
const read = await readSource(sourcePath);
|
|
1316
|
+
if (!read.ok) return read;
|
|
1317
|
+
const inspected = await inspect(sourcePath);
|
|
1318
|
+
if (!inspected.ok) return inspected;
|
|
1319
|
+
let canonical;
|
|
1320
|
+
try {
|
|
1321
|
+
canonical = parseCanonicalManifest(read.value.source, read.value.relative);
|
|
1322
|
+
} catch (error) {
|
|
1323
|
+
return invalidCanonicalSource(read.value.relative, error);
|
|
1324
|
+
}
|
|
1325
|
+
const incomingRefs = await options.incomingRefs?.(read.value.relative) ?? [];
|
|
1326
|
+
return ok({
|
|
1327
|
+
revision: inspected.value.revision,
|
|
1328
|
+
meta: inspected.value.meta,
|
|
1329
|
+
capabilities: canonical === void 0 ? {
|
|
1330
|
+
inspect: true,
|
|
1331
|
+
rebuild: true,
|
|
1332
|
+
coldCook: true,
|
|
1333
|
+
reason: "structured mutation is available only for canonical-v1 scaffolds"
|
|
1334
|
+
} : {
|
|
1335
|
+
inspect: true,
|
|
1336
|
+
rebuild: true,
|
|
1337
|
+
coldCook: true,
|
|
1338
|
+
addOutput: true,
|
|
1339
|
+
addExternalAsset: true,
|
|
1340
|
+
renameDisplay: true,
|
|
1341
|
+
removeOutput: true,
|
|
1342
|
+
clone: true
|
|
1343
|
+
},
|
|
1344
|
+
incomingRefs
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
async function mutate(operation) {
|
|
1348
|
+
const targetPath = operation.kind === "clone-scriptable-pack" ? operation.targetPath : operation.sourcePath;
|
|
1349
|
+
const target = resolvePath(targetPath);
|
|
1350
|
+
if (!target.ok) return target;
|
|
1351
|
+
let manifest;
|
|
1352
|
+
let currentRevision;
|
|
1353
|
+
if (operation.kind === "create-scriptable-pack") {
|
|
1354
|
+
if (!CANONICAL_MUTABLE_KINDS.has(operation.initialOutput.kind)) {
|
|
1355
|
+
return err(
|
|
1356
|
+
authoringError("pack-source-mutation-unsupported", {
|
|
1357
|
+
expected: "a canonical scene, mesh, or material initial output",
|
|
1358
|
+
actual: operation.initialOutput.kind,
|
|
1359
|
+
hint: "create one of the canonical output kinds, then hand-author advanced outputs",
|
|
1360
|
+
retryable: false,
|
|
1361
|
+
recoveryActions: ["choose-supported-output-kind"],
|
|
1362
|
+
requestId: operation.requestId,
|
|
1363
|
+
sourcePath: operation.sourcePath
|
|
1364
|
+
})
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1367
|
+
try {
|
|
1368
|
+
await stat(target.value.absolute);
|
|
1369
|
+
return err(
|
|
1370
|
+
authoringError("pack-source-revision-conflict", {
|
|
1371
|
+
expected: "an unused target source path",
|
|
1372
|
+
actual: "source already exists",
|
|
1373
|
+
hint: "inspect the existing source or choose a new path",
|
|
1374
|
+
retryable: false,
|
|
1375
|
+
recoveryActions: ["asset.preflight", "choose-new-source-path"],
|
|
1376
|
+
requestId: operation.requestId,
|
|
1377
|
+
sourcePath: operation.sourcePath
|
|
1378
|
+
})
|
|
1379
|
+
);
|
|
1380
|
+
} catch {
|
|
1381
|
+
}
|
|
1382
|
+
manifest = {
|
|
1383
|
+
schemaVersion: "1.0.0",
|
|
1384
|
+
packageId: AssetGuid.format(operation.packageId),
|
|
1385
|
+
...operation.name === void 0 ? {} : { name: operation.name },
|
|
1386
|
+
assets: {
|
|
1387
|
+
[operation.initialOutput.sourceKey]: {
|
|
1388
|
+
guid: AssetGuid.format(operation.initialOutput.guid),
|
|
1389
|
+
kind: operation.initialOutput.kind,
|
|
1390
|
+
...operation.initialOutput.name === void 0 ? {} : { name: operation.initialOutput.name }
|
|
1391
|
+
}
|
|
1392
|
+
},
|
|
1393
|
+
externalAssets: {}
|
|
1394
|
+
};
|
|
1395
|
+
} else {
|
|
1396
|
+
const read = await readSource(operation.sourcePath);
|
|
1397
|
+
if (!read.ok) return read;
|
|
1398
|
+
currentRevision = read.value.revision;
|
|
1399
|
+
if (operation.expectedRevision !== void 0 && operation.expectedRevision !== currentRevision) {
|
|
1400
|
+
return err(
|
|
1401
|
+
authoringError("pack-source-revision-conflict", {
|
|
1402
|
+
expected: operation.expectedRevision,
|
|
1403
|
+
actual: currentRevision,
|
|
1404
|
+
hint: "inspect the current source revision, reconcile the edit, then retry with a new requestId",
|
|
1405
|
+
retryable: true,
|
|
1406
|
+
recoveryActions: ["asset.preflight", "mint-request-id"],
|
|
1407
|
+
requestId: operation.requestId,
|
|
1408
|
+
sourcePath: operation.sourcePath
|
|
1409
|
+
})
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
let parsed;
|
|
1413
|
+
try {
|
|
1414
|
+
parsed = parseCanonicalManifest(read.value.source, read.value.relative);
|
|
1415
|
+
} catch (error) {
|
|
1416
|
+
return invalidCanonicalSource(read.value.relative, error);
|
|
1417
|
+
}
|
|
1418
|
+
if (parsed === void 0) {
|
|
1419
|
+
return err(
|
|
1420
|
+
authoringError("pack-source-mutation-unsupported", {
|
|
1421
|
+
expected: "a canonical-v1 scaffold for structured source mutation",
|
|
1422
|
+
actual: "custom ScriptablePack source",
|
|
1423
|
+
hint: "clone into a canonical scaffold or edit custom source with a code editor",
|
|
1424
|
+
retryable: false,
|
|
1425
|
+
recoveryActions: ["clone-scriptable-pack", "open-code-editor"],
|
|
1426
|
+
requestId: operation.requestId,
|
|
1427
|
+
sourcePath: operation.sourcePath
|
|
1428
|
+
})
|
|
1429
|
+
);
|
|
1430
|
+
}
|
|
1431
|
+
manifest = parsed;
|
|
1432
|
+
}
|
|
1433
|
+
if (operation.kind === "clone-scriptable-pack") {
|
|
1434
|
+
try {
|
|
1435
|
+
await stat(target.value.absolute);
|
|
1436
|
+
return err(
|
|
1437
|
+
authoringError("pack-source-revision-conflict", {
|
|
1438
|
+
expected: "an unused clone target source path",
|
|
1439
|
+
actual: "source already exists",
|
|
1440
|
+
hint: "inspect the existing target or choose a new path",
|
|
1441
|
+
retryable: false,
|
|
1442
|
+
recoveryActions: ["asset.preflight", "choose-new-source-path"],
|
|
1443
|
+
requestId: operation.requestId,
|
|
1444
|
+
sourcePath: target.value.relative
|
|
1445
|
+
})
|
|
1446
|
+
);
|
|
1447
|
+
} catch {
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
if (operation.kind === "add-output") {
|
|
1451
|
+
if (!CANONICAL_MUTABLE_KINDS.has(operation.assetKind)) {
|
|
1452
|
+
return err(
|
|
1453
|
+
authoringError("pack-source-mutation-unsupported", {
|
|
1454
|
+
expected: "a canonical scene, mesh, or material output",
|
|
1455
|
+
actual: operation.assetKind,
|
|
1456
|
+
hint: "hand-author advanced output kinds in the source module",
|
|
1457
|
+
retryable: false,
|
|
1458
|
+
recoveryActions: ["open-code-editor"],
|
|
1459
|
+
requestId: operation.requestId,
|
|
1460
|
+
sourcePath: operation.sourcePath,
|
|
1461
|
+
sourceKey: operation.sourceKey
|
|
1462
|
+
})
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1465
|
+
manifest = {
|
|
1466
|
+
...manifest,
|
|
1467
|
+
assets: {
|
|
1468
|
+
...manifest.assets,
|
|
1469
|
+
[operation.sourceKey]: {
|
|
1470
|
+
guid: AssetGuid.format(operation.guid),
|
|
1471
|
+
kind: operation.assetKind,
|
|
1472
|
+
...operation.name === void 0 ? {} : { name: operation.name }
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
};
|
|
1476
|
+
} else if (operation.kind === "add-external-asset") {
|
|
1477
|
+
manifest = {
|
|
1478
|
+
...manifest,
|
|
1479
|
+
externalAssets: {
|
|
1480
|
+
...manifest.externalAssets,
|
|
1481
|
+
[operation.alias]: AssetGuid.format(operation.guid)
|
|
1482
|
+
}
|
|
1483
|
+
};
|
|
1484
|
+
} else if (operation.kind === "rename-display") {
|
|
1485
|
+
if (operation.target.kind === "package") manifest = { ...manifest, name: operation.name };
|
|
1486
|
+
else {
|
|
1487
|
+
const output = manifest.assets[operation.target.sourceKey];
|
|
1488
|
+
if (output === void 0)
|
|
1489
|
+
return err(
|
|
1490
|
+
authoringError("pack-source-mutation-unsupported", {
|
|
1491
|
+
expected: "an existing sourceKey",
|
|
1492
|
+
actual: operation.target.sourceKey,
|
|
1493
|
+
hint: "inspect Meta and choose a current output",
|
|
1494
|
+
retryable: true,
|
|
1495
|
+
recoveryActions: ["asset.preflight"],
|
|
1496
|
+
requestId: operation.requestId,
|
|
1497
|
+
sourcePath: operation.sourcePath,
|
|
1498
|
+
sourceKey: operation.target.sourceKey
|
|
1499
|
+
})
|
|
1500
|
+
);
|
|
1501
|
+
manifest = {
|
|
1502
|
+
...manifest,
|
|
1503
|
+
assets: {
|
|
1504
|
+
...manifest.assets,
|
|
1505
|
+
[operation.target.sourceKey]: { ...output, name: operation.name }
|
|
1506
|
+
}
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
} else if (operation.kind === "remove-output") {
|
|
1510
|
+
const incomingRefs = await options.incomingRefs?.(operation.sourcePath, operation.sourceKey) ?? [];
|
|
1511
|
+
if (incomingRefs.length > 0 && !sameStrings(incomingRefs, operation.confirmIncomingRefs ?? [])) {
|
|
1512
|
+
return err(
|
|
1513
|
+
authoringError("pack-source-reference-conflict", {
|
|
1514
|
+
expected: "explicit acknowledgement of every current incoming reference",
|
|
1515
|
+
actual: `${incomingRefs.length} incoming references`,
|
|
1516
|
+
hint: "inspect dependents, then confirm the exact incoming reference identities or cancel",
|
|
1517
|
+
retryable: true,
|
|
1518
|
+
recoveryActions: ["inspect-incoming-refs", "confirm-remove-output"],
|
|
1519
|
+
requestId: operation.requestId,
|
|
1520
|
+
sourcePath: operation.sourcePath,
|
|
1521
|
+
sourceKey: operation.sourceKey,
|
|
1522
|
+
incomingRefs
|
|
1523
|
+
})
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
1526
|
+
const { [operation.sourceKey]: removed, ...assets } = manifest.assets;
|
|
1527
|
+
if (removed === void 0 || Object.keys(assets).length === 0) {
|
|
1528
|
+
return err(
|
|
1529
|
+
authoringError("pack-source-mutation-unsupported", {
|
|
1530
|
+
expected: "an existing output while retaining at least one package output",
|
|
1531
|
+
actual: operation.sourceKey,
|
|
1532
|
+
hint: "keep one output or remove the entire source file through an explicit file operation",
|
|
1533
|
+
retryable: false,
|
|
1534
|
+
recoveryActions: ["asset.preflight"],
|
|
1535
|
+
requestId: operation.requestId,
|
|
1536
|
+
sourcePath: operation.sourcePath,
|
|
1537
|
+
sourceKey: operation.sourceKey
|
|
1538
|
+
})
|
|
1539
|
+
);
|
|
1540
|
+
}
|
|
1541
|
+
manifest = { ...manifest, assets };
|
|
1542
|
+
} else if (operation.kind === "clone-scriptable-pack") {
|
|
1543
|
+
manifest = {
|
|
1544
|
+
...manifest,
|
|
1545
|
+
packageId: AssetGuid.format(operation.packageId),
|
|
1546
|
+
assets: Object.fromEntries(
|
|
1547
|
+
Object.entries(manifest.assets).map(([sourceKey, output]) => [
|
|
1548
|
+
sourceKey,
|
|
1549
|
+
{
|
|
1550
|
+
...output,
|
|
1551
|
+
guid: AssetGuid.format(operation.outputGuids[sourceKey])
|
|
1552
|
+
}
|
|
1553
|
+
])
|
|
1554
|
+
)
|
|
1555
|
+
};
|
|
1556
|
+
}
|
|
1557
|
+
const source = renderCanonicalScriptablePack(manifest);
|
|
1558
|
+
try {
|
|
1559
|
+
await atomicWrite(target.value.absolute, source);
|
|
1560
|
+
return ok({ sourcePath: target.value.relative, revision: revisionOf(source) });
|
|
1561
|
+
} catch (error) {
|
|
1562
|
+
return err(
|
|
1563
|
+
authoringError("pack-source-write-failed", {
|
|
1564
|
+
expected: "an atomic source-file replacement",
|
|
1565
|
+
actual: error instanceof Error ? error.message : String(error),
|
|
1566
|
+
hint: "repair filesystem permissions or disk capacity, then retry with a new requestId",
|
|
1567
|
+
retryable: true,
|
|
1568
|
+
recoveryActions: ["inspect-filesystem", "mint-request-id"],
|
|
1569
|
+
requestId: operation.requestId,
|
|
1570
|
+
sourcePath: target.value.relative
|
|
1571
|
+
})
|
|
1572
|
+
);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
return {
|
|
1576
|
+
preflight,
|
|
1577
|
+
mutate,
|
|
1578
|
+
inspect,
|
|
1579
|
+
async rebuild(sourcePath, mode) {
|
|
1580
|
+
const path = resolvePath(sourcePath);
|
|
1581
|
+
if (!path.ok) return path;
|
|
1582
|
+
const rebuilt = await options.rebuild?.(path.value.relative, mode) ?? ok(void 0);
|
|
1583
|
+
return rebuilt.ok ? inspect(path.value.relative) : rebuilt;
|
|
1584
|
+
}
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
export { createFileSystemScriptablePackAuthoringPort, createScriptablePackModuleExecutorPool, inventoryScriptablePackSource, loadScriptablePack, readOptionalBuildCache, renderCanonicalScriptablePack, scriptablePackDdcKey };
|
|
1589
|
+
//# sourceMappingURL=scriptable-pack-node.mjs.map
|
|
1590
|
+
//# sourceMappingURL=scriptable-pack-node.mjs.map
|