-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathbuild.mjs
More file actions
107 lines (91 loc) · 7.3 KB
/
Copy pathbuild.mjs
File metadata and controls
107 lines (91 loc) · 7.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#!/usr/bin/env node
// scripts/bash-bundle/build.mjs
//
// Builds the ha:bash module bundle from just-bash.
// Output: builtin-modules/bash.js (self-contained ESM module for QuickJS)
//
// Usage: node scripts/bash-bundle/build.mjs
//
// Prerequisites: npm install (just-bash and esbuild must be in node_modules)
import { build } from "esbuild";
import { readFileSync, writeFileSync, existsSync, unlinkSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const repoRoot = join(__dirname, "..", "..");
const outFile = join(repoRoot, "builtin-modules", "bash.js");
// ── Step 1: esbuild bundle ──────────────────────────────────────────
console.log("Building ha:bash bundle from just-bash...");
const stubDir = __dirname;
const entryFile = join(stubDir, "entry.mjs");
// Check prerequisites
if (!existsSync(join(repoRoot, "node_modules", "just-bash"))) {
console.error(
"Error: just-bash not found in node_modules. Run npm install first.",
);
process.exit(1);
}
const alias = {
"node:module": join(stubDir, "module-stub.mjs"),
"node:zlib": join(stubDir, "zlib-stub.mjs"),
"node:worker_threads": join(stubDir, "worker-stub.mjs"),
"node:path": join(stubDir, "node-path-stub.mjs"),
"node:dns": join(stubDir, "dns-stub.mjs"),
"node:crypto": join(stubDir, "crypto-stub.mjs"),
"node:url": join(stubDir, "url-stub.mjs"),
"node:fs": join(stubDir, "fs-stub.mjs"),
"node:fs/promises": join(stubDir, "fs-stub.mjs"),
"node:child_process": join(stubDir, "worker-stub.mjs"),
"node:os": join(stubDir, "worker-stub.mjs"),
"node:async_hooks": join(stubDir, "worker-stub.mjs"),
turndown: join(stubDir, "turndown-stub.mjs"),
"seek-bzip": join(stubDir, "bzip-stub.mjs"),
"node-liblzma": join(stubDir, "liblzma-stub.mjs"),
"@mongodb-js/zstd": join(stubDir, "zstd-stub.mjs"),
"sql.js": join(stubDir, "sqljs-stub.mjs"),
};
const tmpBundle = join(stubDir, "_tmp_bundle.js");
// Use esbuild's JS API rather than spawning the CLI. This avoids passing
// interpolated paths through a shell entirely (resolving the CodeQL
// shell-injection alert) and is portable: the esbuild CLI bin is a native
// binary on some platforms, so it can't be launched via `node`.
await build({
entryPoints: [entryFile],
bundle: true,
format: "esm",
platform: "neutral",
target: "es2020",
mainFields: ["module", "main"],
alias,
outfile: tmpBundle,
minify: true,
treeShaking: true,
});
// ── Step 2: Prepend polyfills ───────────────────────────────────────
const polyfills = `// @module bash
// @description Sandboxed bash interpreter (just-bash) with polyfills for QuickJS
// @author system
// @generated DO NOT EDIT — built by scripts/bash-bundle/build.mjs
// ── QuickJS Polyfills ────────────────────────────────────────────────
if(typeof globalThis.URL==='undefined'){globalThis.URL=class URL{constructor(input,base){let full=String(input);if(base&&!full.match(/^[a-z]+:\\/\\//i)){full=String(base).replace(/\\/[^\\/]*$/,'/')+full}const m=full.match(/^(https?:)\\/\\/([^\\/:]+)(:\\d+)?(\\/[^?#]*)?(\\?[^#]*)?(#.*)?$/i);if(m){this.protocol=m[1];this.hostname=m[2];this.port=m[3]?m[3].slice(1):'';this.pathname=m[4]||'/';this.search=m[5]||'';this.hash=m[6]||'';this.host=this.hostname+(this.port?':'+this.port:'');this.origin=this.protocol+'//'+this.host;this.href=this.origin+this.pathname+this.search+this.hash;this.searchParams=new URLSearchParams(this.search);this.username='';this.password=''}else{this.href=full;this.protocol='';this.hostname='';this.port='';this.pathname=full;this.search='';this.hash='';this.host='';this.origin='';this.searchParams=new URLSearchParams();this.username='';this.password=''}}toString(){return this.href}}}
if(typeof globalThis.URLSearchParams==='undefined'){globalThis.URLSearchParams=class URLSearchParams{constructor(init){this._p=[];if(typeof init==='string'){const s=init.startsWith('?')?init.slice(1):init;for(const pair of s.split('&')){const[k,v]=pair.split('=');if(k)this._p.push([decodeURIComponent(k),decodeURIComponent(v||'')])}}}get(k){const p=this._p.find(([a])=>a===k);return p?p[1]:null}has(k){return this._p.some(([a])=>a===k)}toString(){return this._p.map(([k,v])=>encodeURIComponent(k)+'='+encodeURIComponent(v)).join('&')}entries(){return this._p[Symbol.iterator]()}[Symbol.iterator](){return this._p[Symbol.iterator]()}forEach(fn){this._p.forEach(([k,v])=>fn(v,k))}}}
if(typeof globalThis.Buffer==='undefined'){const _e=new TextEncoder();const _d=new TextDecoder();class HaBuffer extends Uint8Array{toString(encoding){if(!encoding||encoding==='utf-8'||encoding==='utf8')return _d.decode(this);if(encoding==='base64')return btoa(String.fromCharCode.apply(null,this));if(encoding==='latin1'||encoding==='binary'){let s='';for(let i=0;i<this.length;i++)s+=String.fromCharCode(this[i]);return s}if(encoding==='hex'){let s='';for(let i=0;i<this.length;i++)s+=this[i].toString(16).padStart(2,'0');return s}return _d.decode(this)}}globalThis.Buffer={from(d,e){if(typeof d==='string'){if(e==='base64'){const b=atob(d);const a=new HaBuffer(b.length);for(let i=0;i<b.length;i++)a[i]=b.charCodeAt(i);return a}if(e==='hex'){const a=new HaBuffer(d.length/2);for(let i=0;i<d.length;i+=2)a[i/2]=parseInt(d.substr(i,2),16);return a}const enc=_e.encode(d);const r=new HaBuffer(enc.length);r.set(enc);return r}if(d instanceof Uint8Array){const r=new HaBuffer(d.length);r.set(d);return r}if(Array.isArray(d))return new HaBuffer(d);return new HaBuffer(0)},isBuffer(o){return o instanceof Uint8Array},concat(l){const t=l.reduce((s,b)=>s+b.length,0);const r=new HaBuffer(t);let o=0;for(const b of l){r.set(b,o);o+=b.length}return r},alloc(s){return new HaBuffer(s)},byteLength(s,e){if(typeof s==='string')return _e.encode(s).length;return s.length}}}
if(typeof globalThis.process==='undefined'){globalThis.process={env:{},nextTick(fn){queueMicrotask(fn)},execPath:'/usr/bin/node',mainModule:null,umask(){return 18},type:'renderer'}}
if(typeof globalThis.AbortController==='undefined'){globalThis.AbortController=class AbortController{constructor(){this.signal={aborted:false,addEventListener(){}}}abort(){this.signal.aborted=true}}}
// crypto.getRandomValues, crypto.randomUUID, and Math.random are provided
// natively by the Hyperlight runtime via RDRAND — no JS polyfill needed.
if(typeof globalThis.setTimeout==='undefined'){globalThis.setTimeout=(fn)=>{fn();return 0};globalThis.clearTimeout=()=>{};globalThis.setInterval=()=>0;globalThis.clearInterval=()=>{}}
if(typeof globalThis.performance==='undefined'){globalThis.performance={now(){return Date.now()}}}
// ── End Polyfills ────────────────────────────────────────────────────
`;
const bundleSource = readFileSync(tmpBundle, "utf-8");
const output = polyfills + bundleSource;
writeFileSync(outFile, output);
// Clean up temp file
try {
unlinkSync(tmpBundle);
} catch {
// Best-effort cleanup — .gitignore covers it anyway
}
const sizeKb = (output.length / 1024).toFixed(0);
console.log(`\\n✅ Built builtin-modules/bash.js (${sizeKb} KB)`);