@holoscript/core 6.0.3 → 7.0.0
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 +40 -40
- package/dist/codebase/index.d.ts +25 -0
- package/dist/compiler/index.d.ts +82 -0
- package/dist/constants.d.ts +4 -0
- package/dist/coordinators/index.d.ts +189 -0
- package/dist/entries/interop.d.ts +9 -0
- package/dist/entries/scripting.d.ts +7 -0
- package/dist/index.d.ts +520 -19
- package/dist/paper-0c-spike/index.d.ts +29 -0
- package/dist/reconstruction/index.d.ts +78 -0
- package/dist/self-improvement/index.d.ts +0 -48
- package/dist/storage/index.d.ts +24 -0
- package/dist/tools/index.d.ts +33 -0
- package/dist/traitDocs/traitDocs.d.ts +69 -0
- package/dist/traits/index.d.ts +61 -0
- package/package.json +102 -19
- package/dist/GLTFPipeline-3KLWWUQO.cjs +0 -34
- package/dist/GLTFPipeline-LYII2ZVQ.js +0 -13
- package/dist/HoloScriptPlusParser-BZR5DELQ.js +0 -692
- package/dist/HoloScriptPlusParser-SBMYDNXO.cjs +0 -697
- package/dist/USDZExporter-MHEHXZB4.cjs +0 -709
- package/dist/USDZExporter-YA55ZS35.js +0 -707
- package/dist/chunk-26TLYBFD.js +0 -309
- package/dist/chunk-27LWW5FU.cjs +0 -410
- package/dist/chunk-2UX5LRAP.cjs +0 -833
- package/dist/chunk-32TWR3HE.js +0 -70
- package/dist/chunk-3FXV5S25.cjs +0 -633
- package/dist/chunk-3FYZOR7S.cjs +0 -69
- package/dist/chunk-3KEU5QYY.cjs +0 -175
- package/dist/chunk-4FCZDTD5.js +0 -610
- package/dist/chunk-4KJ2R7VP.cjs +0 -507
- package/dist/chunk-4SMUJFHL.js +0 -1213
- package/dist/chunk-54FHG2MX.js +0 -655
- package/dist/chunk-56IF4NTA.js +0 -56
- package/dist/chunk-5GLCTO44.js +0 -161
- package/dist/chunk-5TOFIR6L.js +0 -407
- package/dist/chunk-65RFOWZI.js +0 -765
- package/dist/chunk-6OQBLABR.js +0 -5240
- package/dist/chunk-6WNCRE6F.js +0 -126
- package/dist/chunk-74YCHHTE.js +0 -1872
- package/dist/chunk-7H5UNJZD.cjs +0 -1973
- package/dist/chunk-7KPI4EKH.cjs +0 -701
- package/dist/chunk-7LCYLPW2.cjs +0 -663
- package/dist/chunk-7N3JIJMT.js +0 -1395
- package/dist/chunk-7X2IEJIE.cjs +0 -2526
- package/dist/chunk-A6GO3DPZ.cjs +0 -1002
- package/dist/chunk-AC4BSHFV.js +0 -765
- package/dist/chunk-AIUXRS74.cjs +0 -3681
- package/dist/chunk-ARNKA274.cjs +0 -130
- package/dist/chunk-AW7WAELW.js +0 -975
- package/dist/chunk-BKIGPPVM.js +0 -629
- package/dist/chunk-BO2OKHIY.js +0 -6055
- package/dist/chunk-BU5ZAFMC.js +0 -115
- package/dist/chunk-BY3B7ZYV.cjs +0 -457
- package/dist/chunk-C2QHVHZF.js +0 -4066
- package/dist/chunk-C7BNX4XJ.js +0 -1043
- package/dist/chunk-CLYEIWA5.cjs +0 -408
- package/dist/chunk-CMYAWUX3.js +0 -4455
- package/dist/chunk-CN4NOESF.cjs +0 -416
- package/dist/chunk-CNVM7J3M.js +0 -883
- package/dist/chunk-CO2VM2DK.js +0 -1965
- package/dist/chunk-CQDOF3G7.cjs +0 -1404
- package/dist/chunk-DH5G2JUA.cjs +0 -614
- package/dist/chunk-DIEDKX5B.cjs +0 -660
- package/dist/chunk-DN7UFU63.cjs +0 -172
- package/dist/chunk-DXVCEFZB.js +0 -2027
- package/dist/chunk-EGIZHYJP.cjs +0 -85
- package/dist/chunk-EJA7G2C4.cjs +0 -428
- package/dist/chunk-ELLQPFAF.js +0 -830
- package/dist/chunk-EMO7HAKJ.cjs +0 -1875
- package/dist/chunk-ENV7K6XA.js +0 -282
- package/dist/chunk-EPWRXL6S.js +0 -2522
- package/dist/chunk-EUFLX2PI.js +0 -56956
- package/dist/chunk-F5LVGHNT.js +0 -449
- package/dist/chunk-FEFHPUEM.cjs +0 -6122
- package/dist/chunk-FF3Q7ADR.js +0 -380
- package/dist/chunk-GC3YU46J.js +0 -762
- package/dist/chunk-H6WMMLQK.cjs +0 -4492
- package/dist/chunk-H7XMORZI.js +0 -2731
- package/dist/chunk-HAN4V3PF.cjs +0 -2037
- package/dist/chunk-HBZYCASG.js +0 -4208
- package/dist/chunk-HF4OFY25.cjs +0 -4097
- package/dist/chunk-HHS6FMOU.cjs +0 -3813
- package/dist/chunk-HJJLD366.js +0 -1169
- package/dist/chunk-HKCVM6OK.cjs +0 -2237
- package/dist/chunk-HUFNKFA6.js +0 -698
- package/dist/chunk-I3XGTIHM.cjs +0 -1505
- package/dist/chunk-I6XZ5DZF.cjs +0 -488
- package/dist/chunk-IHOUYQIA.js +0 -406
- package/dist/chunk-ILRBGFVD.cjs +0 -72
- package/dist/chunk-KDB6BUMB.js +0 -3666
- package/dist/chunk-KUJRR4FJ.js +0 -657
- package/dist/chunk-KYM4XRFG.js +0 -421
- package/dist/chunk-L3Z2HIWJ.cjs +0 -793
- package/dist/chunk-LBPEZQAF.js +0 -2232
- package/dist/chunk-LER4WXW5.cjs +0 -286
- package/dist/chunk-LK5IAVH6.cjs +0 -1173
- package/dist/chunk-M6FU6S22.js +0 -2911
- package/dist/chunk-MWEFR6YQ.js +0 -1857
- package/dist/chunk-N5FTS757.cjs +0 -415
- package/dist/chunk-NCUHGRTZ.js +0 -752
- package/dist/chunk-NKRKT6V2.js +0 -69
- package/dist/chunk-NLPSZT4C.js +0 -630
- package/dist/chunk-NRUB55IT.cjs +0 -771
- package/dist/chunk-NTMZSDXM.cjs +0 -633
- package/dist/chunk-ODGMVILH.js +0 -9662
- package/dist/chunk-PBA6NXCT.cjs +0 -1216
- package/dist/chunk-PHENW4MQ.js +0 -65
- package/dist/chunk-PJMOXFPR.cjs +0 -1861
- package/dist/chunk-PLMYCCA4.js +0 -504
- package/dist/chunk-PPULB4GG.cjs +0 -9728
- package/dist/chunk-PQLGZKMC.cjs +0 -755
- package/dist/chunk-Q56TT7IL.js +0 -483
- package/dist/chunk-QHVVVN47.cjs +0 -2033
- package/dist/chunk-QWKUKVRE.js +0 -2026
- package/dist/chunk-RE3OKSYF.cjs +0 -424
- package/dist/chunk-RT7LJRSF.cjs +0 -57378
- package/dist/chunk-SPDELRRV.cjs +0 -319
- package/dist/chunk-SUCBB66F.js +0 -229
- package/dist/chunk-TSWTWZ42.cjs +0 -4214
- package/dist/chunk-UITWA6DV.cjs +0 -2829
- package/dist/chunk-UQW6SLM5.js +0 -172
- package/dist/chunk-V2ILLPHK.cjs +0 -237
- package/dist/chunk-V42NTCFH.js +0 -3645
- package/dist/chunk-VJ3L7CHT.cjs +0 -169
- package/dist/chunk-VJVCD5T5.cjs +0 -769
- package/dist/chunk-VK5AXKO3.js +0 -746
- package/dist/chunk-VSXOIUCF.cjs +0 -473
- package/dist/chunk-VZNKJZTT.cjs +0 -749
- package/dist/chunk-W76ETJTI.js +0 -678
- package/dist/chunk-WFJIDI2N.cjs +0 -424
- package/dist/chunk-WN3YF33G.cjs +0 -118
- package/dist/chunk-WUXIRGZP.cjs +0 -2915
- package/dist/chunk-WYH4GVZ5.js +0 -1488
- package/dist/chunk-X4YVN7H3.cjs +0 -888
- package/dist/chunk-X67XRI2T.js +0 -410
- package/dist/chunk-XDXZM3ZP.cjs +0 -1046
- package/dist/chunk-XH7SE4HH.cjs +0 -72
- package/dist/chunk-XNMEH2BI.js +0 -126
- package/dist/chunk-XSF76QRU.js +0 -468
- package/dist/chunk-XSUZMPVQ.cjs +0 -682
- package/dist/chunk-XUILI4KB.cjs +0 -430
- package/dist/chunk-Y5MUAYTO.js +0 -167
- package/dist/chunk-Y7VK5TH3.cjs +0 -129
- package/dist/chunk-YCMQQQ5U.cjs +0 -5244
- package/dist/chunk-YYXH45E2.js +0 -426
- package/dist/chunk-ZGTGVSTZ.js +0 -422
- package/dist/chunk-ZL6VJ6SN.js +0 -421
- package/dist/cli/holoscript-runner.cjs +0 -2258
- package/dist/cli/holoscript-runner.js +0 -2233
- package/dist/codebase/index.cjs +0 -336
- package/dist/codebase/index.js +0 -331
- package/dist/compiler/agent-inference.cjs +0 -29
- package/dist/compiler/agent-inference.js +0 -6
- package/dist/compiler/android-xr.cjs +0 -20
- package/dist/compiler/android-xr.js +0 -10
- package/dist/compiler/android.cjs +0 -21
- package/dist/compiler/android.js +0 -11
- package/dist/compiler/ar.cjs +0 -21
- package/dist/compiler/ar.js +0 -9
- package/dist/compiler/babylon.cjs +0 -18
- package/dist/compiler/babylon.js +0 -12
- package/dist/compiler/coco.cjs +0 -27
- package/dist/compiler/coco.js +0 -4
- package/dist/compiler/domain-block-utils.cjs +0 -595
- package/dist/compiler/domain-block-utils.js +0 -9
- package/dist/compiler/dtdl.cjs +0 -25
- package/dist/compiler/dtdl.js +0 -9
- package/dist/compiler/gltf-pipeline.cjs +0 -39
- package/dist/compiler/gltf-pipeline.js +0 -4
- package/dist/compiler/godot.cjs +0 -16
- package/dist/compiler/godot.js +0 -10
- package/dist/compiler/incremental.cjs +0 -30
- package/dist/compiler/incremental.js +0 -5
- package/dist/compiler/index.cjs +0 -1522
- package/dist/compiler/index.js +0 -1321
- package/dist/compiler/ios.cjs +0 -21
- package/dist/compiler/ios.js +0 -11
- package/dist/compiler/multi-layer.cjs +0 -27
- package/dist/compiler/multi-layer.js +0 -12
- package/dist/compiler/nodetoy.cjs +0 -23
- package/dist/compiler/nodetoy.js +0 -4
- package/dist/compiler/openxr.cjs +0 -16
- package/dist/compiler/openxr.js +0 -10
- package/dist/compiler/playcanvas.cjs +0 -17
- package/dist/compiler/playcanvas.js +0 -11
- package/dist/compiler/r3f.cjs +0 -35
- package/dist/compiler/r3f.js +0 -13
- package/dist/compiler/remotion.cjs +0 -23
- package/dist/compiler/remotion.js +0 -4
- package/dist/compiler/reproducibility.cjs +0 -39
- package/dist/compiler/reproducibility.js +0 -4
- package/dist/compiler/sdf.cjs +0 -22
- package/dist/compiler/sdf.js +0 -10
- package/dist/compiler/semantic-scene.cjs +0 -31
- package/dist/compiler/semantic-scene.js +0 -4
- package/dist/compiler/state.cjs +0 -18
- package/dist/compiler/state.js +0 -8
- package/dist/compiler/trait-composition.cjs +0 -27
- package/dist/compiler/trait-composition.js +0 -6
- package/dist/compiler/unity.cjs +0 -16
- package/dist/compiler/unity.js +0 -10
- package/dist/compiler/unreal.cjs +0 -20
- package/dist/compiler/unreal.js +0 -10
- package/dist/compiler/urdf.cjs +0 -46
- package/dist/compiler/urdf.js +0 -10
- package/dist/compiler/usd-physics.cjs +0 -24
- package/dist/compiler/usd-physics.js +0 -7
- package/dist/compiler/visionos.cjs +0 -16
- package/dist/compiler/visionos.js +0 -10
- package/dist/compiler/vrchat.cjs +0 -20
- package/dist/compiler/vrchat.js +0 -10
- package/dist/compiler/vrr.cjs +0 -22
- package/dist/compiler/vrr.js +0 -10
- package/dist/compiler/wasm.cjs +0 -34
- package/dist/compiler/wasm.js +0 -10
- package/dist/compiler/webgpu.cjs +0 -16
- package/dist/compiler/webgpu.js +0 -10
- package/dist/debugger.cjs +0 -21
- package/dist/debugger.js +0 -8
- package/dist/entries/interop.cjs +0 -62
- package/dist/entries/interop.js +0 -5
- package/dist/entries/scripting.cjs +0 -67
- package/dist/entries/scripting.js +0 -10
- package/dist/index.cjs +0 -61703
- package/dist/index.js +0 -58652
- package/dist/math/vec3.cjs +0 -57
- package/dist/math/vec3.js +0 -4
- package/dist/parser.cjs +0 -26
- package/dist/parser.js +0 -9
- package/dist/playwright-7DTEQCBD.cjs +0 -98673
- package/dist/playwright-BIZXMLD2.js +0 -98660
- package/dist/post-quantum-JTTAAGO3.cjs +0 -6
- package/dist/post-quantum-RVPVDEPI.js +0 -4
- package/dist/runtime.cjs +0 -16
- package/dist/runtime.js +0 -7
- package/dist/storage/index.cjs +0 -37
- package/dist/storage/index.js +0 -4
- package/dist/traits/index.cjs +0 -14331
- package/dist/traits/index.js +0 -12126
- package/dist/type-checker.cjs +0 -18
- package/dist/type-checker.js +0 -5
- package/dist/wot/index.cjs +0 -29
- package/dist/wot/index.js +0 -4
package/dist/chunk-AW7WAELW.js
DELETED
|
@@ -1,975 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import { createHash } from 'crypto';
|
|
3
|
-
|
|
4
|
-
// src/stdlib/StdlibActions.ts
|
|
5
|
-
var DEFAULT_STDLIB_POLICY = {
|
|
6
|
-
allowedPaths: ["compositions", "data", "src", "packages"],
|
|
7
|
-
maxFileBytes: 2 * 1024 * 1024,
|
|
8
|
-
allowShell: false,
|
|
9
|
-
allowedShellCommands: [],
|
|
10
|
-
maxShellOutputBytes: 100 * 1024,
|
|
11
|
-
shellTimeoutMs: 6e4,
|
|
12
|
-
allowNetwork: false,
|
|
13
|
-
allowedHosts: [],
|
|
14
|
-
rootDir: ".",
|
|
15
|
-
allowMediaDecode: false,
|
|
16
|
-
allowDepthInference: false,
|
|
17
|
-
allowGpuCompute: false,
|
|
18
|
-
maxGifFrames: 500,
|
|
19
|
-
maxVideoDurationSec: 300,
|
|
20
|
-
maxMediaResolution: 4096
|
|
21
|
-
};
|
|
22
|
-
function resolveRepoRelativePath(targetPath, rootDir) {
|
|
23
|
-
const normalized = targetPath.replace(/\\/g, "/").replace(/^\.\//, "").trim();
|
|
24
|
-
if (!normalized) return { ok: false, error: "Path is required" };
|
|
25
|
-
const abs = path.resolve(rootDir, normalized);
|
|
26
|
-
const rel = path.relative(rootDir, abs).replace(/\\/g, "/");
|
|
27
|
-
if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
28
|
-
return { ok: false, error: "Path escapes repository root" };
|
|
29
|
-
}
|
|
30
|
-
return { ok: true, rel, abs };
|
|
31
|
-
}
|
|
32
|
-
function isPathAllowed(relPath, allowedRoots) {
|
|
33
|
-
const normalized = relPath.replace(/\\/g, "/");
|
|
34
|
-
return allowedRoots.some((root) => normalized === root || normalized.startsWith(`${root}/`));
|
|
35
|
-
}
|
|
36
|
-
function parseHostFromUrl(url) {
|
|
37
|
-
try {
|
|
38
|
-
return new URL(url).hostname.toLowerCase();
|
|
39
|
-
} catch {
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
function truncateText(value, max) {
|
|
44
|
-
const str = typeof value === "string" ? value : String(value ?? "");
|
|
45
|
-
if (!Number.isFinite(max) || max <= 0 || str.length <= max) return str;
|
|
46
|
-
return `${str.slice(0, max)}
|
|
47
|
-
...[truncated ${str.length - max} chars]`;
|
|
48
|
-
}
|
|
49
|
-
function toStringArray(value) {
|
|
50
|
-
if (!Array.isArray(value)) return [];
|
|
51
|
-
return value.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean);
|
|
52
|
-
}
|
|
53
|
-
function resolveInto(params, defaultPrefix) {
|
|
54
|
-
return typeof params.into === "string" && params.into.trim().length > 0 ? params.into.trim() : defaultPrefix;
|
|
55
|
-
}
|
|
56
|
-
function createStdlibActions(options) {
|
|
57
|
-
const { policy, hostCapabilities: caps, debug } = options;
|
|
58
|
-
const log = debug ? (msg) => console.log(` [stdlib] ${msg}`) : () => {
|
|
59
|
-
};
|
|
60
|
-
return {
|
|
61
|
-
// ── File System ──────────────────────────────────────────────────────
|
|
62
|
-
fs_read: async (params, bb) => {
|
|
63
|
-
const prefix = resolveInto(params, "fs_read");
|
|
64
|
-
const filePath = typeof params.path === "string" ? params.path : "";
|
|
65
|
-
const resolved = resolveRepoRelativePath(filePath, policy.rootDir);
|
|
66
|
-
if (!resolved.ok) {
|
|
67
|
-
bb[`${prefix}_error`] = resolved.error;
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
if (!isPathAllowed(resolved.rel, policy.allowedPaths)) {
|
|
71
|
-
bb[`${prefix}_error`] = `path "${resolved.rel}" is outside allowed roots`;
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
if (!caps?.fileSystem) {
|
|
75
|
-
bb[`${prefix}_error`] = "fileSystem capability not available";
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
const exists = caps.fileSystem.exists ? await Promise.resolve(caps.fileSystem.exists(resolved.rel)) : true;
|
|
79
|
-
if (!exists) {
|
|
80
|
-
bb[`${prefix}_error`] = `file not found: ${resolved.rel}`;
|
|
81
|
-
bb[`${prefix}_exists`] = false;
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
const content = await Promise.resolve(caps.fileSystem.readFile(resolved.rel));
|
|
85
|
-
if (Buffer.byteLength(content, "utf-8") > policy.maxFileBytes) {
|
|
86
|
-
bb[`${prefix}_error`] = `file exceeds max size (${policy.maxFileBytes} bytes)`;
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
bb[`${prefix}_content`] = content;
|
|
90
|
-
bb[`${prefix}_exists`] = true;
|
|
91
|
-
log(`fs_read: ${resolved.rel} (${Buffer.byteLength(content, "utf-8")} bytes)`);
|
|
92
|
-
return true;
|
|
93
|
-
},
|
|
94
|
-
fs_write: async (params, bb) => {
|
|
95
|
-
const prefix = resolveInto(params, "fs_write");
|
|
96
|
-
const filePath = typeof params.path === "string" ? params.path : "";
|
|
97
|
-
const content = typeof params.content === "string" ? params.content : null;
|
|
98
|
-
const append = params.append === true;
|
|
99
|
-
if (content === null) {
|
|
100
|
-
bb[`${prefix}_error`] = "content is required";
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
if (Buffer.byteLength(content, "utf-8") > policy.maxFileBytes) {
|
|
104
|
-
bb[`${prefix}_error`] = `content exceeds max size (${policy.maxFileBytes} bytes)`;
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
107
|
-
const resolved = resolveRepoRelativePath(filePath, policy.rootDir);
|
|
108
|
-
if (!resolved.ok) {
|
|
109
|
-
bb[`${prefix}_error`] = resolved.error;
|
|
110
|
-
return false;
|
|
111
|
-
}
|
|
112
|
-
if (!isPathAllowed(resolved.rel, policy.allowedPaths)) {
|
|
113
|
-
bb[`${prefix}_error`] = `path "${resolved.rel}" is outside allowed roots`;
|
|
114
|
-
return false;
|
|
115
|
-
}
|
|
116
|
-
if (!caps?.fileSystem) {
|
|
117
|
-
bb[`${prefix}_error`] = "fileSystem capability not available";
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
let finalContent = content;
|
|
121
|
-
if (append && caps.fileSystem.exists) {
|
|
122
|
-
const exists = await Promise.resolve(caps.fileSystem.exists(resolved.rel));
|
|
123
|
-
if (exists) {
|
|
124
|
-
const current = await Promise.resolve(caps.fileSystem.readFile(resolved.rel));
|
|
125
|
-
finalContent = `${current}${content}`;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
await Promise.resolve(caps.fileSystem.writeFile(resolved.rel, finalContent));
|
|
129
|
-
bb[`${prefix}_path`] = resolved.rel;
|
|
130
|
-
bb[`${prefix}_bytes`] = Buffer.byteLength(content, "utf-8");
|
|
131
|
-
log(`fs_write: ${resolved.rel} (${bb[`${prefix}_bytes`]} bytes, append=${append})`);
|
|
132
|
-
return true;
|
|
133
|
-
},
|
|
134
|
-
fs_exists: async (params, bb) => {
|
|
135
|
-
const prefix = resolveInto(params, "fs_exists");
|
|
136
|
-
const filePath = typeof params.path === "string" ? params.path : "";
|
|
137
|
-
const resolved = resolveRepoRelativePath(filePath, policy.rootDir);
|
|
138
|
-
if (!resolved.ok) {
|
|
139
|
-
bb[`${prefix}_error`] = resolved.error;
|
|
140
|
-
return false;
|
|
141
|
-
}
|
|
142
|
-
if (!caps?.fileSystem?.exists) {
|
|
143
|
-
bb[`${prefix}_error`] = "fileSystem.exists capability not available";
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
const exists = await Promise.resolve(caps.fileSystem.exists(resolved.rel));
|
|
147
|
-
bb[`${prefix}_exists`] = exists;
|
|
148
|
-
log(`fs_exists: ${resolved.rel} \u2192 ${exists}`);
|
|
149
|
-
return true;
|
|
150
|
-
},
|
|
151
|
-
fs_delete: async (params, bb) => {
|
|
152
|
-
const prefix = resolveInto(params, "fs_delete");
|
|
153
|
-
const filePath = typeof params.path === "string" ? params.path : "";
|
|
154
|
-
const resolved = resolveRepoRelativePath(filePath, policy.rootDir);
|
|
155
|
-
if (!resolved.ok) {
|
|
156
|
-
bb[`${prefix}_error`] = resolved.error;
|
|
157
|
-
return false;
|
|
158
|
-
}
|
|
159
|
-
if (!isPathAllowed(resolved.rel, policy.allowedPaths)) {
|
|
160
|
-
bb[`${prefix}_error`] = `path "${resolved.rel}" is outside allowed roots`;
|
|
161
|
-
return false;
|
|
162
|
-
}
|
|
163
|
-
if (!caps?.fileSystem?.deleteFile) {
|
|
164
|
-
bb[`${prefix}_error`] = "fileSystem.deleteFile capability not available";
|
|
165
|
-
return false;
|
|
166
|
-
}
|
|
167
|
-
await Promise.resolve(caps.fileSystem.deleteFile(resolved.rel));
|
|
168
|
-
log(`fs_delete: ${resolved.rel}`);
|
|
169
|
-
return true;
|
|
170
|
-
},
|
|
171
|
-
// ── Process Execution ─────────────────────────────────────────────────
|
|
172
|
-
process_exec: async (params, bb) => {
|
|
173
|
-
const prefix = resolveInto(params, "process_exec");
|
|
174
|
-
if (!policy.allowShell) {
|
|
175
|
-
bb[`${prefix}_error`] = `${prefix} is disabled by policy`;
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
const command = typeof params.cmd === "string" ? params.cmd.trim() : "";
|
|
179
|
-
const args = toStringArray(params.args);
|
|
180
|
-
if (!command) {
|
|
181
|
-
bb[`${prefix}_error`] = "cmd is required";
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
if (policy.allowedShellCommands.length > 0) {
|
|
185
|
-
const executable = command.split(/\s+/)[0].toLowerCase();
|
|
186
|
-
const allowed = policy.allowedShellCommands.some((c) => c.toLowerCase() === executable);
|
|
187
|
-
if (!allowed) {
|
|
188
|
-
bb[`${prefix}_error`] = `command "${executable}" is not allowlisted`;
|
|
189
|
-
return false;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
if (!caps?.process?.exec) {
|
|
193
|
-
bb[`${prefix}_error`] = "process.exec capability not available";
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
const timeoutMs = typeof params.timeout === "number" && Number.isFinite(params.timeout) ? Math.max(1e3, Math.min(policy.shellTimeoutMs, Math.floor(params.timeout))) : policy.shellTimeoutMs;
|
|
197
|
-
const cwd = typeof params.cwd === "string" ? params.cwd : policy.rootDir;
|
|
198
|
-
const result = await Promise.resolve(caps.process.exec(command, args, { cwd, timeoutMs }));
|
|
199
|
-
bb[`${prefix}_code`] = result.code ?? -1;
|
|
200
|
-
bb[`${prefix}_stdout`] = truncateText(result.stdout, policy.maxShellOutputBytes);
|
|
201
|
-
bb[`${prefix}_stderr`] = truncateText(result.stderr, policy.maxShellOutputBytes);
|
|
202
|
-
log(`process_exec: ${command} ${args.join(" ")} \u2192 code ${result.code}`);
|
|
203
|
-
return result.code === 0;
|
|
204
|
-
},
|
|
205
|
-
// ── Network ───────────────────────────────────────────────────────────
|
|
206
|
-
net_fetch: async (params, bb) => {
|
|
207
|
-
const prefix = resolveInto(params, "net_fetch");
|
|
208
|
-
const url = typeof params.url === "string" ? params.url.trim() : "";
|
|
209
|
-
if (!url) {
|
|
210
|
-
bb[`${prefix}_error`] = "url is required";
|
|
211
|
-
return false;
|
|
212
|
-
}
|
|
213
|
-
if (!policy.allowNetwork) {
|
|
214
|
-
bb[`${prefix}_error`] = "network access is disabled by policy";
|
|
215
|
-
return false;
|
|
216
|
-
}
|
|
217
|
-
const hostName = parseHostFromUrl(url);
|
|
218
|
-
if (!hostName) {
|
|
219
|
-
bb[`${prefix}_error`] = "invalid url";
|
|
220
|
-
return false;
|
|
221
|
-
}
|
|
222
|
-
if (policy.allowedHosts.length > 0) {
|
|
223
|
-
const allowed = policy.allowedHosts.includes(hostName) || policy.allowedHosts.some((h) => hostName.endsWith(`.${h}`));
|
|
224
|
-
if (!allowed) {
|
|
225
|
-
bb[`${prefix}_error`] = `host "${hostName}" is not allowlisted`;
|
|
226
|
-
return false;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
if (caps?.network?.fetch) {
|
|
230
|
-
try {
|
|
231
|
-
const response = await Promise.resolve(
|
|
232
|
-
caps.network.fetch(url, {
|
|
233
|
-
method: typeof params.method === "string" ? params.method : "GET",
|
|
234
|
-
headers: typeof params.headers === "object" && params.headers ? params.headers : void 0,
|
|
235
|
-
body: typeof params.body === "string" ? params.body : void 0
|
|
236
|
-
})
|
|
237
|
-
);
|
|
238
|
-
bb[`${prefix}_status`] = response.status;
|
|
239
|
-
bb[`${prefix}_body`] = truncateText(response.text ?? "", policy.maxShellOutputBytes);
|
|
240
|
-
bb[`${prefix}_ok`] = response.ok;
|
|
241
|
-
log(`net_fetch: ${url} \u2192 ${response.status}`);
|
|
242
|
-
return response.ok;
|
|
243
|
-
} catch (error) {
|
|
244
|
-
bb[`${prefix}_error`] = error.message;
|
|
245
|
-
return false;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
try {
|
|
249
|
-
const response = await globalThis.fetch(url, {
|
|
250
|
-
method: typeof params.method === "string" ? params.method : "GET",
|
|
251
|
-
headers: typeof params.headers === "object" && params.headers ? params.headers : void 0
|
|
252
|
-
});
|
|
253
|
-
const text = await response.text();
|
|
254
|
-
bb[`${prefix}_status`] = response.status;
|
|
255
|
-
bb[`${prefix}_body`] = truncateText(text, policy.maxShellOutputBytes);
|
|
256
|
-
bb[`${prefix}_ok`] = response.ok;
|
|
257
|
-
log(`net_fetch: ${url} \u2192 ${response.status}`);
|
|
258
|
-
return response.ok;
|
|
259
|
-
} catch (error) {
|
|
260
|
-
bb[`${prefix}_error`] = error.message;
|
|
261
|
-
return false;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
function registerStdlib(runtime, options) {
|
|
267
|
-
const actions = createStdlibActions(options);
|
|
268
|
-
for (const [name, handler] of Object.entries(actions)) {
|
|
269
|
-
runtime.registerAction(name, handler);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// src/traits/TestTrait.ts
|
|
274
|
-
var CompositionTestRunner = class {
|
|
275
|
-
constructor(initialState, computedDefs = [], config = {}) {
|
|
276
|
-
this.initialState = { ...initialState };
|
|
277
|
-
this.computedDefs = computedDefs;
|
|
278
|
-
this.config = {
|
|
279
|
-
bail: config.bail ?? false,
|
|
280
|
-
debug: config.debug ?? false,
|
|
281
|
-
timeout: config.timeout ?? 5e3
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
/**
|
|
285
|
-
* Extract and run all @test blocks from .hsplus source
|
|
286
|
-
*/
|
|
287
|
-
runTestsFromSource(source) {
|
|
288
|
-
const tests = this.extractTestBlocks(source);
|
|
289
|
-
if (this.config.debug) {
|
|
290
|
-
console.log(`[composition_test] Found ${tests.length} @test blocks`);
|
|
291
|
-
}
|
|
292
|
-
return this.runAll(tests);
|
|
293
|
-
}
|
|
294
|
-
/**
|
|
295
|
-
* Run all test blocks
|
|
296
|
-
*/
|
|
297
|
-
runAll(tests) {
|
|
298
|
-
const results = [];
|
|
299
|
-
for (const test of tests) {
|
|
300
|
-
if (test.skip) {
|
|
301
|
-
results.push({
|
|
302
|
-
name: test.name,
|
|
303
|
-
status: "skipped",
|
|
304
|
-
durationMs: 0,
|
|
305
|
-
assertions: test.assertions.length,
|
|
306
|
-
passedAssertions: 0
|
|
307
|
-
});
|
|
308
|
-
continue;
|
|
309
|
-
}
|
|
310
|
-
const result = this.runSingle(test);
|
|
311
|
-
results.push(result);
|
|
312
|
-
if (this.config.bail && result.status === "failed") {
|
|
313
|
-
break;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
return results;
|
|
317
|
-
}
|
|
318
|
-
/**
|
|
319
|
-
* Run a single test block:
|
|
320
|
-
* 1. Clone initial state
|
|
321
|
-
* 2. Apply setup assignments
|
|
322
|
-
* 3. Evaluate computed values
|
|
323
|
-
* 4. Check assertions
|
|
324
|
-
*/
|
|
325
|
-
runSingle(test) {
|
|
326
|
-
const start = Date.now();
|
|
327
|
-
let passedAssertions = 0;
|
|
328
|
-
try {
|
|
329
|
-
const state = { ...this.initialState };
|
|
330
|
-
for (const { variable, expression } of test.setupAssignments) {
|
|
331
|
-
const value = this.evaluateValue(expression, state);
|
|
332
|
-
state[variable] = value;
|
|
333
|
-
}
|
|
334
|
-
const computed = this.evaluateComputedValues(state);
|
|
335
|
-
const fullState = { ...state, ...computed };
|
|
336
|
-
if (this.config.debug) {
|
|
337
|
-
console.log(`[composition_test] "${test.name}" state:`, fullState);
|
|
338
|
-
}
|
|
339
|
-
for (const assertion of test.assertions) {
|
|
340
|
-
const passed = this.evaluateAssertion(assertion.expression, fullState);
|
|
341
|
-
if (passed) {
|
|
342
|
-
passedAssertions++;
|
|
343
|
-
} else {
|
|
344
|
-
return {
|
|
345
|
-
name: test.name,
|
|
346
|
-
status: "failed",
|
|
347
|
-
durationMs: Date.now() - start,
|
|
348
|
-
error: `Assertion failed: ${assertion.expression}`,
|
|
349
|
-
assertions: test.assertions.length,
|
|
350
|
-
passedAssertions
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
return {
|
|
355
|
-
name: test.name,
|
|
356
|
-
status: "passed",
|
|
357
|
-
durationMs: Date.now() - start,
|
|
358
|
-
assertions: test.assertions.length,
|
|
359
|
-
passedAssertions
|
|
360
|
-
};
|
|
361
|
-
} catch (error) {
|
|
362
|
-
return {
|
|
363
|
-
name: test.name,
|
|
364
|
-
status: "failed",
|
|
365
|
-
durationMs: Date.now() - start,
|
|
366
|
-
error: error instanceof Error ? error.message : String(error),
|
|
367
|
-
assertions: test.assertions.length,
|
|
368
|
-
passedAssertions
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
// ─── Extraction ──────────────────────────────────────────────────────────────
|
|
373
|
-
/**
|
|
374
|
-
* Extract @test { name: "...", setup: { ... }, assert: { ... } } blocks
|
|
375
|
-
*/
|
|
376
|
-
extractTestBlocks(source) {
|
|
377
|
-
const blocks = [];
|
|
378
|
-
const regex = /@test\s*\{/g;
|
|
379
|
-
let match;
|
|
380
|
-
while ((match = regex.exec(source)) !== null) {
|
|
381
|
-
const startIdx = match.index + match[0].length;
|
|
382
|
-
let depth = 1;
|
|
383
|
-
let endIdx = startIdx;
|
|
384
|
-
while (depth > 0 && endIdx < source.length) {
|
|
385
|
-
if (source[endIdx] === "{") depth++;
|
|
386
|
-
if (source[endIdx] === "}") depth--;
|
|
387
|
-
endIdx++;
|
|
388
|
-
}
|
|
389
|
-
const body = source.substring(startIdx, endIdx - 1).trim();
|
|
390
|
-
const block = this.parseTestBody(body);
|
|
391
|
-
if (block) blocks.push(block);
|
|
392
|
-
}
|
|
393
|
-
return blocks;
|
|
394
|
-
}
|
|
395
|
-
/**
|
|
396
|
-
* Parse the body of a @test block into structured data
|
|
397
|
-
*/
|
|
398
|
-
parseTestBody(body) {
|
|
399
|
-
const nameMatch = body.match(/name:\s*"([^"]+)"/);
|
|
400
|
-
if (!nameMatch) return null;
|
|
401
|
-
const name = nameMatch[1];
|
|
402
|
-
const skipMatch = body.match(/skip:\s*(true|false)/);
|
|
403
|
-
const skip = skipMatch?.[1] === "true";
|
|
404
|
-
const setupAssignments = [];
|
|
405
|
-
const setupMatch = body.match(/setup:\s*\{([^}]+)\}/);
|
|
406
|
-
if (setupMatch) {
|
|
407
|
-
const setupBody = setupMatch[1].trim();
|
|
408
|
-
const assignRegex = /\$([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)/g;
|
|
409
|
-
let assignMatch;
|
|
410
|
-
while ((assignMatch = assignRegex.exec(setupBody)) !== null) {
|
|
411
|
-
setupAssignments.push({
|
|
412
|
-
variable: assignMatch[1],
|
|
413
|
-
expression: assignMatch[2].trim().replace(/[,;]\s*$/, "")
|
|
414
|
-
});
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
const assertions = [];
|
|
418
|
-
const assertRegex = /assert:\s*\{([^}]+)\}/g;
|
|
419
|
-
let assertMatch;
|
|
420
|
-
while ((assertMatch = assertRegex.exec(body)) !== null) {
|
|
421
|
-
const assertBody = assertMatch[1].trim();
|
|
422
|
-
const lines = assertBody.split(/[;\n]/).map((l) => l.trim()).filter(Boolean);
|
|
423
|
-
for (const line of lines) {
|
|
424
|
-
assertions.push({ expression: line });
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
return { name, setupAssignments, assertions, skip };
|
|
428
|
-
}
|
|
429
|
-
// ─── Evaluation ──────────────────────────────────────────────────────────────
|
|
430
|
-
/**
|
|
431
|
-
* Evaluate a single value expression, resolving $vars from state
|
|
432
|
-
*/
|
|
433
|
-
evaluateValue(expr, state) {
|
|
434
|
-
const trimmed = expr.trim();
|
|
435
|
-
if (trimmed === "true") return true;
|
|
436
|
-
if (trimmed === "false") return false;
|
|
437
|
-
if (trimmed === "null") return null;
|
|
438
|
-
if (/^-?\d+(\.\d+)?$/.test(trimmed)) return Number(trimmed);
|
|
439
|
-
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
440
|
-
return trimmed.slice(1, -1);
|
|
441
|
-
}
|
|
442
|
-
if (trimmed.startsWith("$")) {
|
|
443
|
-
const varName = trimmed.slice(1);
|
|
444
|
-
return state[varName];
|
|
445
|
-
}
|
|
446
|
-
try {
|
|
447
|
-
const transformed = trimmed.replace(/\$([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, varName) => varName);
|
|
448
|
-
const keys = Object.keys(state);
|
|
449
|
-
const values = Object.values(state);
|
|
450
|
-
const fn = new Function(
|
|
451
|
-
...keys,
|
|
452
|
-
"Math",
|
|
453
|
-
"String",
|
|
454
|
-
"Number",
|
|
455
|
-
"Boolean",
|
|
456
|
-
"Date",
|
|
457
|
-
"JSON",
|
|
458
|
-
`"use strict"; return (${transformed})`
|
|
459
|
-
);
|
|
460
|
-
return fn(...values, Math, String, Number, Boolean, Date, JSON);
|
|
461
|
-
} catch {
|
|
462
|
-
return trimmed;
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
/**
|
|
466
|
-
* Evaluate an assertion expression against state.
|
|
467
|
-
* Supports: $var == value, $var != value, $var > value, etc.
|
|
468
|
-
*/
|
|
469
|
-
evaluateAssertion(expr, state) {
|
|
470
|
-
const trimmed = expr.trim();
|
|
471
|
-
const transformed = trimmed.replace(/\$([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, varName) => varName);
|
|
472
|
-
try {
|
|
473
|
-
const keys = Object.keys(state);
|
|
474
|
-
const values = Object.values(state);
|
|
475
|
-
const fn = new Function(
|
|
476
|
-
...keys,
|
|
477
|
-
"Math",
|
|
478
|
-
"String",
|
|
479
|
-
"Number",
|
|
480
|
-
"Boolean",
|
|
481
|
-
"Date",
|
|
482
|
-
"JSON",
|
|
483
|
-
`"use strict"; return !!(${transformed})`
|
|
484
|
-
);
|
|
485
|
-
return fn(...values, Math, String, Number, Boolean, Date, JSON);
|
|
486
|
-
} catch {
|
|
487
|
-
return false;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
/**
|
|
491
|
-
* Evaluate computed values from their definitions
|
|
492
|
-
*/
|
|
493
|
-
evaluateComputedValues(state) {
|
|
494
|
-
const result = {};
|
|
495
|
-
for (const def of this.computedDefs) {
|
|
496
|
-
try {
|
|
497
|
-
const transformed = def.expression.replace(
|
|
498
|
-
/\$([a-zA-Z_][a-zA-Z0-9_]*)/g,
|
|
499
|
-
(_, varName) => varName
|
|
500
|
-
);
|
|
501
|
-
const keys = Object.keys(state);
|
|
502
|
-
const values = Object.values(state);
|
|
503
|
-
const fn = new Function(
|
|
504
|
-
...keys,
|
|
505
|
-
"Math",
|
|
506
|
-
"String",
|
|
507
|
-
"Number",
|
|
508
|
-
"Boolean",
|
|
509
|
-
"Date",
|
|
510
|
-
"JSON",
|
|
511
|
-
`"use strict"; return (${transformed})`
|
|
512
|
-
);
|
|
513
|
-
result[def.name] = fn(...values, Math, String, Number, Boolean, Date, JSON);
|
|
514
|
-
} catch {
|
|
515
|
-
result[def.name] = void 0;
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
return result;
|
|
519
|
-
}
|
|
520
|
-
// ─── Static helpers ──────────────────────────────────────────────────────────
|
|
521
|
-
/**
|
|
522
|
-
* Extract state block from .hsplus source.
|
|
523
|
-
* Handles nested objects { ... } and arrays [ ... ].
|
|
524
|
-
*/
|
|
525
|
-
static extractStateFromSource(source) {
|
|
526
|
-
const result = {};
|
|
527
|
-
const stateMatch = source.match(/\bstate\s*\{/);
|
|
528
|
-
if (stateMatch) {
|
|
529
|
-
const startIdx = stateMatch.index + stateMatch[0].length;
|
|
530
|
-
const body = extractBracedBody(source, startIdx);
|
|
531
|
-
Object.assign(result, parseNestedBlock(body));
|
|
532
|
-
}
|
|
533
|
-
const inlineRegex = /^\s*state\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*\w+\s*=\s*(.+)$/gm;
|
|
534
|
-
let inlineMatch;
|
|
535
|
-
while ((inlineMatch = inlineRegex.exec(source)) !== null) {
|
|
536
|
-
const key = inlineMatch[1];
|
|
537
|
-
if (!(key in result)) {
|
|
538
|
-
result[key] = parseStateValue(inlineMatch[2].trim());
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
return result;
|
|
542
|
-
}
|
|
543
|
-
/**
|
|
544
|
-
* Extract computed definitions from .hsplus source
|
|
545
|
-
*/
|
|
546
|
-
static extractComputedFromSource(source) {
|
|
547
|
-
const defs = [];
|
|
548
|
-
const computedMatch = source.match(/\bcomputed\s*\{/);
|
|
549
|
-
if (!computedMatch) return defs;
|
|
550
|
-
const startIdx = computedMatch.index + computedMatch[0].length;
|
|
551
|
-
let depth = 1;
|
|
552
|
-
let endIdx = startIdx;
|
|
553
|
-
while (depth > 0 && endIdx < source.length) {
|
|
554
|
-
if (source[endIdx] === "{") depth++;
|
|
555
|
-
if (source[endIdx] === "}") depth--;
|
|
556
|
-
endIdx++;
|
|
557
|
-
}
|
|
558
|
-
const body = source.substring(startIdx, endIdx - 1);
|
|
559
|
-
const lines = body.split("\n");
|
|
560
|
-
for (const line of lines) {
|
|
561
|
-
const trimmed = line.trim();
|
|
562
|
-
if (!trimmed || trimmed.startsWith("//")) continue;
|
|
563
|
-
const kvMatch = trimmed.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*(.+?)\s*$/);
|
|
564
|
-
if (kvMatch) {
|
|
565
|
-
defs.push({ name: kvMatch[1], expression: kvMatch[2] });
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
return defs;
|
|
569
|
-
}
|
|
570
|
-
};
|
|
571
|
-
function extractBracedBody(source, startIdx) {
|
|
572
|
-
let depth = 1;
|
|
573
|
-
let i = startIdx;
|
|
574
|
-
while (depth > 0 && i < source.length) {
|
|
575
|
-
if (source[i] === "{") depth++;
|
|
576
|
-
if (source[i] === "}") depth--;
|
|
577
|
-
i++;
|
|
578
|
-
}
|
|
579
|
-
return source.substring(startIdx, i - 1);
|
|
580
|
-
}
|
|
581
|
-
function parseNestedBlock(body) {
|
|
582
|
-
const result = {};
|
|
583
|
-
let i = 0;
|
|
584
|
-
while (i < body.length) {
|
|
585
|
-
while (i < body.length && /\s/.test(body[i])) i++;
|
|
586
|
-
if (i >= body.length) break;
|
|
587
|
-
if (body[i] === "/" && body[i + 1] === "/") {
|
|
588
|
-
while (i < body.length && body[i] !== "\n") i++;
|
|
589
|
-
continue;
|
|
590
|
-
}
|
|
591
|
-
const keyMatch = body.slice(i).match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*/);
|
|
592
|
-
if (!keyMatch) {
|
|
593
|
-
i++;
|
|
594
|
-
continue;
|
|
595
|
-
}
|
|
596
|
-
const key = keyMatch[1];
|
|
597
|
-
i += keyMatch[0].length;
|
|
598
|
-
while (i < body.length && /\s/.test(body[i])) i++;
|
|
599
|
-
if (body[i] === "{") {
|
|
600
|
-
i++;
|
|
601
|
-
const nestedBody = extractBracedBody(body, i);
|
|
602
|
-
i += nestedBody.length + 1;
|
|
603
|
-
result[key] = parseNestedBlock(nestedBody);
|
|
604
|
-
} else if (body[i] === "[") {
|
|
605
|
-
const arrStart = i;
|
|
606
|
-
let depth = 1;
|
|
607
|
-
i++;
|
|
608
|
-
while (depth > 0 && i < body.length) {
|
|
609
|
-
if (body[i] === "[") depth++;
|
|
610
|
-
if (body[i] === "]") depth--;
|
|
611
|
-
i++;
|
|
612
|
-
}
|
|
613
|
-
const arrStr = body.substring(arrStart, i).trim();
|
|
614
|
-
try {
|
|
615
|
-
result[key] = JSON.parse(arrStr);
|
|
616
|
-
} catch {
|
|
617
|
-
result[key] = arrStr;
|
|
618
|
-
}
|
|
619
|
-
} else {
|
|
620
|
-
const valMatch = body.slice(i).match(/^(.+?)(?:\n|$)/);
|
|
621
|
-
if (valMatch) {
|
|
622
|
-
const raw = valMatch[1].trim().replace(/,\s*$/, "");
|
|
623
|
-
result[key] = parseStateValue(raw);
|
|
624
|
-
i += valMatch[0].length;
|
|
625
|
-
} else {
|
|
626
|
-
i++;
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
return result;
|
|
631
|
-
}
|
|
632
|
-
function parseStateValue(raw) {
|
|
633
|
-
const v = raw.trim().replace(/,\s*$/, "");
|
|
634
|
-
if (v === "true") return true;
|
|
635
|
-
if (v === "false") return false;
|
|
636
|
-
if (v === "null") return null;
|
|
637
|
-
if (/^-?\d+(\.\d+)?$/.test(v)) return Number(v);
|
|
638
|
-
if (v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'")) {
|
|
639
|
-
return v.slice(1, -1);
|
|
640
|
-
}
|
|
641
|
-
return v;
|
|
642
|
-
}
|
|
643
|
-
var TEST_TRAIT = {
|
|
644
|
-
name: "test",
|
|
645
|
-
category: "testing",
|
|
646
|
-
description: "Native composition testing with $stateVar syntax",
|
|
647
|
-
compileTargets: ["node", "python", "ros2", "headless"],
|
|
648
|
-
requiresRenderer: false,
|
|
649
|
-
parameters: [
|
|
650
|
-
{ name: "name", type: "string", required: true, description: "Test name" },
|
|
651
|
-
{ name: "setup", type: "object", required: false, description: "State setup assignments" },
|
|
652
|
-
{ name: "assert", type: "object", required: true, description: "Assertion expressions" },
|
|
653
|
-
{
|
|
654
|
-
name: "skip",
|
|
655
|
-
type: "boolean",
|
|
656
|
-
required: false,
|
|
657
|
-
default: false,
|
|
658
|
-
description: "Skip this test"
|
|
659
|
-
}
|
|
660
|
-
]
|
|
661
|
-
};
|
|
662
|
-
var testHandler = {
|
|
663
|
-
name: "test",
|
|
664
|
-
defaultConfig: { bail: false, debug: false, timeout: 5e3 },
|
|
665
|
-
onAttach(node, config, ctx) {
|
|
666
|
-
const instance = new CompositionTestRunner({}, [], config);
|
|
667
|
-
node.__test_instance = instance;
|
|
668
|
-
ctx.emit?.("test:attached", { node, config });
|
|
669
|
-
},
|
|
670
|
-
onDetach(node, _config, ctx) {
|
|
671
|
-
delete node.__test_instance;
|
|
672
|
-
ctx.emit?.("test:detached", { node });
|
|
673
|
-
},
|
|
674
|
-
onUpdate() {
|
|
675
|
-
},
|
|
676
|
-
onEvent(node, _config, ctx, event) {
|
|
677
|
-
const instance = node.__test_instance;
|
|
678
|
-
if (!instance) return;
|
|
679
|
-
const eventType = typeof event === "string" ? event : event.type;
|
|
680
|
-
if (eventType === "test:run" && event.source) {
|
|
681
|
-
const results = instance.runTestsFromSource(event.source);
|
|
682
|
-
ctx.emit?.("test:results", { results });
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
};
|
|
686
|
-
function computeContentHash(source) {
|
|
687
|
-
const normalized = source.replace(/\r\n/g, "\n");
|
|
688
|
-
return createHash("sha256").update(normalized, "utf8").digest("hex");
|
|
689
|
-
}
|
|
690
|
-
function classifyPublishMode(ast) {
|
|
691
|
-
const imports = ast?.imports ?? ast?.ast?.imports ?? [];
|
|
692
|
-
const hasImports = Array.isArray(imports) && imports.length > 0;
|
|
693
|
-
if (!hasImports) return "original";
|
|
694
|
-
const body = ast?.body ?? ast?.ast?.body ?? [];
|
|
695
|
-
const hasOwnContent = Array.isArray(body) && body.some(
|
|
696
|
-
(node) => node.type === "composition" || node.type === "ObjectDeclaration" || node.type === "scene" || node.type === "object" || node.type === "character" || node.type === "environment"
|
|
697
|
-
);
|
|
698
|
-
if (hasOwnContent) return "remix";
|
|
699
|
-
return "curated";
|
|
700
|
-
}
|
|
701
|
-
function extractImports(ast) {
|
|
702
|
-
const imports = ast?.imports ?? ast?.ast?.imports ?? [];
|
|
703
|
-
if (!Array.isArray(imports)) return [];
|
|
704
|
-
return imports.map((imp) => ({
|
|
705
|
-
// @ts-expect-error
|
|
706
|
-
path: imp.path ?? imp.source ?? "",
|
|
707
|
-
// @ts-expect-error
|
|
708
|
-
hash: imp.hash,
|
|
709
|
-
// @ts-expect-error
|
|
710
|
-
author: imp.author
|
|
711
|
-
})).filter((imp) => imp.path.length > 0);
|
|
712
|
-
}
|
|
713
|
-
function generateProvenance(source, ast, options) {
|
|
714
|
-
return {
|
|
715
|
-
author: options.author,
|
|
716
|
-
created: options.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
717
|
-
hash: computeContentHash(source),
|
|
718
|
-
license: options.license,
|
|
719
|
-
version: 1,
|
|
720
|
-
publishMode: classifyPublishMode(ast),
|
|
721
|
-
imports: extractImports(ast)
|
|
722
|
-
};
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
// src/deploy/license-checker.ts
|
|
726
|
-
var RESTRICTIVENESS = {
|
|
727
|
-
free: 0,
|
|
728
|
-
cc_by: 1,
|
|
729
|
-
cc_by_sa: 2,
|
|
730
|
-
cc_by_nc: 3,
|
|
731
|
-
commercial: 4,
|
|
732
|
-
exclusive: 5
|
|
733
|
-
};
|
|
734
|
-
function checkLicenseCompatibility(compositionLicense, importedLicenses) {
|
|
735
|
-
const errors = [];
|
|
736
|
-
const warnings = [];
|
|
737
|
-
let forcedLicense;
|
|
738
|
-
if (importedLicenses.length === 0) {
|
|
739
|
-
return { compatible: true, errors: [], warnings: [] };
|
|
740
|
-
}
|
|
741
|
-
for (const imp of importedLicenses) {
|
|
742
|
-
if (imp.license === "exclusive") {
|
|
743
|
-
errors.push(
|
|
744
|
-
`Cannot import "${imp.path}" \u2014 license is "exclusive" (view-only, no import allowed)`
|
|
745
|
-
);
|
|
746
|
-
continue;
|
|
747
|
-
}
|
|
748
|
-
if (imp.license === "commercial") {
|
|
749
|
-
warnings.push(
|
|
750
|
-
`Import "${imp.path}" has "commercial" license \u2014 ensure purchase is verified before publishing`
|
|
751
|
-
);
|
|
752
|
-
continue;
|
|
753
|
-
}
|
|
754
|
-
if (imp.license === "cc_by_sa") {
|
|
755
|
-
const compositionLevel = RESTRICTIVENESS[compositionLicense];
|
|
756
|
-
const requiredLevel = RESTRICTIVENESS.cc_by_sa;
|
|
757
|
-
if (compositionLevel < requiredLevel) {
|
|
758
|
-
if (!forcedLicense || RESTRICTIVENESS[forcedLicense] < requiredLevel) {
|
|
759
|
-
forcedLicense = "cc_by_sa";
|
|
760
|
-
}
|
|
761
|
-
warnings.push(
|
|
762
|
-
`Import "${imp.path}" is "cc_by_sa" \u2014 your composition license forced to "cc_by_sa" (share-alike)`
|
|
763
|
-
);
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
if (imp.license === "cc_by_nc") {
|
|
767
|
-
if (compositionLicense === "free" || compositionLicense === "cc_by") {
|
|
768
|
-
if (!forcedLicense || RESTRICTIVENESS[forcedLicense] < RESTRICTIVENESS.cc_by_nc) {
|
|
769
|
-
forcedLicense = "cc_by_nc";
|
|
770
|
-
}
|
|
771
|
-
warnings.push(
|
|
772
|
-
`Import "${imp.path}" is "cc_by_nc" \u2014 your composition license forced to "cc_by_nc" (non-commercial)`
|
|
773
|
-
);
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
if (imp.license === "cc_by") {
|
|
777
|
-
if (compositionLicense === "free") {
|
|
778
|
-
if (!forcedLicense || RESTRICTIVENESS[forcedLicense] < RESTRICTIVENESS.cc_by) {
|
|
779
|
-
forcedLicense = "cc_by";
|
|
780
|
-
}
|
|
781
|
-
warnings.push(
|
|
782
|
-
`Import "${imp.path}" is "cc_by" \u2014 your composition license forced to at least "cc_by" (attribution required)`
|
|
783
|
-
);
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
const hasShareAlike = importedLicenses.some((i) => i.license === "cc_by_sa");
|
|
788
|
-
const hasNonCommercial = importedLicenses.some((i) => i.license === "cc_by_nc");
|
|
789
|
-
if (hasShareAlike && hasNonCommercial) {
|
|
790
|
-
errors.push(
|
|
791
|
-
'License conflict: cannot mix "cc_by_sa" (share-alike) and "cc_by_nc" (non-commercial) imports \u2014 cc_by_sa requires derivatives use the same license, but cc_by_nc requires non-commercial use'
|
|
792
|
-
);
|
|
793
|
-
}
|
|
794
|
-
return {
|
|
795
|
-
compatible: errors.length === 0,
|
|
796
|
-
errors,
|
|
797
|
-
warnings,
|
|
798
|
-
forcedLicense
|
|
799
|
-
};
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
// src/deploy/protocol-types.ts
|
|
803
|
-
var PROTOCOL_CONSTANTS = {
|
|
804
|
-
/** Platform fee in basis points (2.5%) */
|
|
805
|
-
PLATFORM_FEE_BPS: 250,
|
|
806
|
-
/** Import royalty per level in basis points (5% per level) */
|
|
807
|
-
IMPORT_ROYALTY_BPS: 500,
|
|
808
|
-
/** Maximum import depth for revenue distribution */
|
|
809
|
-
MAX_IMPORT_DEPTH: 3,
|
|
810
|
-
/** Default referral reward in basis points (2%) */
|
|
811
|
-
DEFAULT_REFERRAL_BPS: 200,
|
|
812
|
-
/** Basis points denominator */
|
|
813
|
-
BPS_DENOMINATOR: 1e4,
|
|
814
|
-
/** Zora mint fee per edition (in wei) */
|
|
815
|
-
ZORA_MINT_FEE_WEI: 777000000000000n,
|
|
816
|
-
// 0.000777 ETH
|
|
817
|
-
/** Chain configuration */
|
|
818
|
-
CHAIN: {
|
|
819
|
-
id: 8453,
|
|
820
|
-
name: "base",
|
|
821
|
-
testnet: {
|
|
822
|
-
id: 84532,
|
|
823
|
-
name: "base-sepolia"
|
|
824
|
-
}
|
|
825
|
-
},
|
|
826
|
-
/** Protocol registry base URL */
|
|
827
|
-
REGISTRY_BASE_URL: "https://holoscript.net",
|
|
828
|
-
/** Zora 1155 factory address (same on all chains) */
|
|
829
|
-
ZORA_1155_FACTORY: "0x777777C338d93e2C7adf08D102d45CA7CC4Ed021",
|
|
830
|
-
/** Environment variable names */
|
|
831
|
-
ENV: {
|
|
832
|
-
WALLET_KEY: "HOLOSCRIPT_WALLET_KEY",
|
|
833
|
-
COLLECTION_ADDRESS: "HOLOSCRIPT_COLLECTION_ADDRESS",
|
|
834
|
-
SERVER_URL: "HOLOSCRIPT_SERVER_URL"
|
|
835
|
-
}
|
|
836
|
-
};
|
|
837
|
-
|
|
838
|
-
// src/deploy/revenue-splitter.ts
|
|
839
|
-
function calculateRevenueDistribution(collectPrice, creator, importChain, options = {}) {
|
|
840
|
-
const {
|
|
841
|
-
platformFeeBps = PROTOCOL_CONSTANTS.PLATFORM_FEE_BPS,
|
|
842
|
-
importRoyaltyBps = PROTOCOL_CONSTANTS.IMPORT_ROYALTY_BPS,
|
|
843
|
-
maxImportDepth = PROTOCOL_CONSTANTS.MAX_IMPORT_DEPTH,
|
|
844
|
-
referralBps = PROTOCOL_CONSTANTS.DEFAULT_REFERRAL_BPS,
|
|
845
|
-
referrer,
|
|
846
|
-
platformAddress = "platform"
|
|
847
|
-
} = options;
|
|
848
|
-
const flows = [];
|
|
849
|
-
if (collectPrice === 0n) {
|
|
850
|
-
return { totalPrice: 0n, flows: [] };
|
|
851
|
-
}
|
|
852
|
-
const bpsDenom = BigInt(PROTOCOL_CONSTANTS.BPS_DENOMINATOR);
|
|
853
|
-
let remaining = collectPrice;
|
|
854
|
-
const platformFee = collectPrice * BigInt(platformFeeBps) / bpsDenom;
|
|
855
|
-
if (platformFee > 0n) {
|
|
856
|
-
flows.push({
|
|
857
|
-
recipient: platformAddress,
|
|
858
|
-
amount: platformFee,
|
|
859
|
-
reason: "platform",
|
|
860
|
-
bps: platformFeeBps
|
|
861
|
-
});
|
|
862
|
-
remaining -= platformFee;
|
|
863
|
-
}
|
|
864
|
-
if (referrer) {
|
|
865
|
-
const referralAmount = collectPrice * BigInt(referralBps) / bpsDenom;
|
|
866
|
-
if (referralAmount > 0n) {
|
|
867
|
-
flows.push({
|
|
868
|
-
recipient: referrer,
|
|
869
|
-
amount: referralAmount,
|
|
870
|
-
reason: "referral",
|
|
871
|
-
bps: referralBps
|
|
872
|
-
});
|
|
873
|
-
remaining -= referralAmount;
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
const flattenedByDepth = flattenImportsByDepth(importChain, maxImportDepth);
|
|
877
|
-
for (const [depth, nodes] of flattenedByDepth.entries()) {
|
|
878
|
-
if (nodes.length === 0) continue;
|
|
879
|
-
const levelRoyalty = collectPrice * BigInt(importRoyaltyBps) / bpsDenom;
|
|
880
|
-
const perImportRoyalty = levelRoyalty / BigInt(nodes.length);
|
|
881
|
-
const dust = levelRoyalty - perImportRoyalty * BigInt(nodes.length);
|
|
882
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
883
|
-
const amount = i === 0 ? perImportRoyalty + dust : perImportRoyalty;
|
|
884
|
-
if (amount > 0n) {
|
|
885
|
-
flows.push({
|
|
886
|
-
recipient: nodes[i].author,
|
|
887
|
-
amount,
|
|
888
|
-
reason: "import_royalty",
|
|
889
|
-
depth,
|
|
890
|
-
bps: Math.floor(importRoyaltyBps / nodes.length)
|
|
891
|
-
});
|
|
892
|
-
remaining -= amount;
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
if (remaining > 0n) {
|
|
897
|
-
const creatorBps = PROTOCOL_CONSTANTS.BPS_DENOMINATOR - platformFeeBps - (referrer ? referralBps : 0) - importRoyaltyBps * flattenedByDepth.size;
|
|
898
|
-
flows.push({
|
|
899
|
-
recipient: creator,
|
|
900
|
-
amount: remaining,
|
|
901
|
-
reason: "creator",
|
|
902
|
-
bps: Math.max(creatorBps, 0)
|
|
903
|
-
});
|
|
904
|
-
}
|
|
905
|
-
return { totalPrice: collectPrice, flows };
|
|
906
|
-
}
|
|
907
|
-
function flattenImportsByDepth(chain, maxDepth) {
|
|
908
|
-
const byDepth = /* @__PURE__ */ new Map();
|
|
909
|
-
const seen = /* @__PURE__ */ new Set();
|
|
910
|
-
function walk(nodes, depth) {
|
|
911
|
-
if (depth > maxDepth) return;
|
|
912
|
-
for (const node of nodes) {
|
|
913
|
-
if (seen.has(node.contentHash)) continue;
|
|
914
|
-
seen.add(node.contentHash);
|
|
915
|
-
const list = byDepth.get(depth) ?? [];
|
|
916
|
-
list.push(node);
|
|
917
|
-
byDepth.set(depth, list);
|
|
918
|
-
if (node.children.length > 0) {
|
|
919
|
-
walk(node.children, depth + 1);
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
walk(chain, 1);
|
|
924
|
-
return byDepth;
|
|
925
|
-
}
|
|
926
|
-
async function resolveImportChain(imports, resolver, maxDepth = PROTOCOL_CONSTANTS.MAX_IMPORT_DEPTH) {
|
|
927
|
-
const seen = /* @__PURE__ */ new Set();
|
|
928
|
-
async function resolve(importList, depth) {
|
|
929
|
-
if (depth > maxDepth) return [];
|
|
930
|
-
const nodes = [];
|
|
931
|
-
for (const imp of importList) {
|
|
932
|
-
const hash = imp.hash;
|
|
933
|
-
if (!hash || seen.has(hash)) continue;
|
|
934
|
-
seen.add(hash);
|
|
935
|
-
const record = await resolver(hash);
|
|
936
|
-
const author = record?.author ?? imp.author ?? "unknown";
|
|
937
|
-
const children = record && depth < maxDepth ? await resolve(
|
|
938
|
-
record.importHashes.map((h) => ({ hash: h, path: "", author: "" })),
|
|
939
|
-
depth + 1
|
|
940
|
-
) : [];
|
|
941
|
-
nodes.push({
|
|
942
|
-
contentHash: hash,
|
|
943
|
-
author,
|
|
944
|
-
depth,
|
|
945
|
-
children
|
|
946
|
-
});
|
|
947
|
-
}
|
|
948
|
-
return nodes;
|
|
949
|
-
}
|
|
950
|
-
return resolve(imports, 1);
|
|
951
|
-
}
|
|
952
|
-
function formatRevenueDistribution(dist) {
|
|
953
|
-
if (dist.totalPrice === 0n) return ["Free collect \u2014 no revenue distribution"];
|
|
954
|
-
return dist.flows.map((flow) => {
|
|
955
|
-
const pct = (Number(flow.amount) / Number(dist.totalPrice) * 100).toFixed(1);
|
|
956
|
-
const depthLabel = flow.depth ? `, depth ${flow.depth}` : "";
|
|
957
|
-
return `${pct}% \u2192 ${flow.recipient} (${flow.reason}${depthLabel})`;
|
|
958
|
-
});
|
|
959
|
-
}
|
|
960
|
-
function ethToWei(eth) {
|
|
961
|
-
const parts = eth.split(".");
|
|
962
|
-
const whole = parts[0] || "0";
|
|
963
|
-
const fraction = (parts[1] || "").padEnd(18, "0").slice(0, 18);
|
|
964
|
-
return BigInt(whole) * 10n ** 18n + BigInt(fraction);
|
|
965
|
-
}
|
|
966
|
-
function weiToEth(wei) {
|
|
967
|
-
const str = wei.toString().padStart(19, "0");
|
|
968
|
-
const whole = str.slice(0, str.length - 18) || "0";
|
|
969
|
-
const fraction = str.slice(str.length - 18).replace(/0+$/, "") || "0";
|
|
970
|
-
return `${whole}.${fraction}`;
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
export { CompositionTestRunner, DEFAULT_STDLIB_POLICY, PROTOCOL_CONSTANTS, TEST_TRAIT, calculateRevenueDistribution, checkLicenseCompatibility, classifyPublishMode, computeContentHash, createStdlibActions, ethToWei, extractImports, formatRevenueDistribution, generateProvenance, isPathAllowed, parseHostFromUrl, registerStdlib, resolveImportChain, resolveRepoRelativePath, testHandler, toStringArray, truncateText, weiToEth };
|
|
974
|
-
//# sourceMappingURL=chunk-AW7WAELW.js.map
|
|
975
|
-
//# sourceMappingURL=chunk-AW7WAELW.js.map
|