thebird 1.2.79 → 1.2.81
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/.github/workflows/publish.yml +9 -1
- package/CHANGELOG.md +217 -0
- package/CLAUDE.md +16 -0
- package/docs/agent-chat.js +7 -4
- package/docs/app.js +14 -11
- package/docs/defaults.json +1 -1
- package/docs/index.html +23 -6
- package/docs/kilo-fs-mirror.js +15 -0
- package/docs/kilo-http-stream.js +47 -0
- package/docs/node-builtins.js +24 -0
- package/docs/preview/index.html +32 -0
- package/docs/preview-sw-client.js +37 -6
- package/docs/preview-sw.js +55 -51
- package/docs/shell-awk.js +113 -0
- package/docs/shell-builtins-extra.js +121 -0
- package/docs/shell-builtins-text.js +109 -0
- package/docs/shell-builtins-util.js +112 -0
- package/docs/shell-builtins.js +183 -0
- package/docs/shell-bun.js +45 -0
- package/docs/shell-control.js +132 -0
- package/docs/shell-deno.js +54 -0
- package/docs/shell-exec.js +85 -0
- package/docs/shell-expand.js +164 -0
- package/docs/shell-fd.js +86 -0
- package/docs/shell-jobs.js +86 -0
- package/docs/shell-node-advanced.js +86 -0
- package/docs/shell-node-brotli.js +22 -0
- package/docs/shell-node-busnet.js +90 -0
- package/docs/shell-node-cipher.js +61 -0
- package/docs/shell-node-cluster.js +33 -0
- package/docs/shell-node-coreutils.js +36 -0
- package/docs/shell-node-crypto.js +137 -0
- package/docs/shell-node-dns.js +41 -0
- package/docs/shell-node-extras.js +148 -0
- package/docs/shell-node-firefox.js +95 -0
- package/docs/shell-node-git.js +60 -0
- package/docs/shell-node-inspector.js +39 -0
- package/docs/shell-node-io.js +131 -0
- package/docs/shell-node-ipc.js +15 -0
- package/docs/shell-node-keyobject.js +60 -0
- package/docs/shell-node-modules.js +157 -0
- package/docs/shell-node-native.js +31 -0
- package/docs/shell-node-net.js +71 -0
- package/docs/shell-node-observe.js +80 -0
- package/docs/shell-node-opfs.js +54 -0
- package/docs/shell-node-procfs.js +42 -0
- package/docs/shell-node-profiler.js +50 -0
- package/docs/shell-node-registry.js +24 -0
- package/docs/shell-node-resolve.js +147 -0
- package/docs/shell-node-runtime.js +83 -0
- package/docs/shell-node-srcmap.js +52 -0
- package/docs/shell-node-stdlib.js +103 -0
- package/docs/shell-node-streams.js +66 -0
- package/docs/shell-node-tar.js +47 -0
- package/docs/shell-node-testrunner.js +35 -0
- package/docs/shell-node-util-extras.js +66 -0
- package/docs/shell-node.js +175 -169
- package/docs/shell-npm.js +173 -0
- package/docs/shell-parser.js +122 -0
- package/docs/shell-pm-layout.js +62 -0
- package/docs/shell-pm.js +39 -0
- package/docs/shell-posix.js +70 -0
- package/docs/shell-procsub.js +65 -0
- package/docs/shell-readline.js +59 -4
- package/docs/shell-runtime.js +37 -0
- package/docs/shell-sed.js +83 -0
- package/docs/shell-signals.js +54 -0
- package/docs/shell-sw-jobs.js +76 -0
- package/docs/shell-ts.js +30 -0
- package/docs/shell.js +161 -152
- package/docs/terminal.js +9 -11
- package/docs/todo.html +211 -0
- package/package.json +1 -1
- package/server.js +43 -4
- package/start-kilo.js +45 -0
- package/test.js +199 -0
- package/.codeinsight +0 -73
- package/docs/acp-stream.js +0 -102
- package/docs/coi-serviceworker.js +0 -2
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { resolvePath } from './shell-builtins.js';
|
|
2
|
+
import { runSed } from './shell-sed.js';
|
|
3
|
+
|
|
4
|
+
const toKey = p => p.replace(/^\//, '');
|
|
5
|
+
const snap = () => window.__debug.idbSnapshot || {};
|
|
6
|
+
const persist = () => window.__debug.idbPersist?.();
|
|
7
|
+
const previewWrite = () => window.__debug.shell?.onPreviewWrite?.();
|
|
8
|
+
|
|
9
|
+
const readLines = text => text.split('\n').map(l => l.replace(/\r$/, '')).filter((l, i, a) => i < a.length - 1 || l !== '');
|
|
10
|
+
|
|
11
|
+
function readStdinFirst(positional) {
|
|
12
|
+
const stdinFirst = positional.length > 0 && positional[0].includes('\n');
|
|
13
|
+
return { stdin: stdinFirst ? positional[0] : null, rest: stdinFirst ? positional.slice(1) : positional };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function makeTextBuiltins(ctx, readFile, writeFile) {
|
|
17
|
+
const w = s => ctx.term.write(s);
|
|
18
|
+
const wl = s => w(s + '\r\n');
|
|
19
|
+
return {
|
|
20
|
+
grep: args => {
|
|
21
|
+
const flags = args.filter(a => a.startsWith('-')).join('');
|
|
22
|
+
const positional = args.filter(a => !a.startsWith('-'));
|
|
23
|
+
const { stdin, rest } = readStdinFirst(positional);
|
|
24
|
+
const [pat, ...fileArgs] = rest;
|
|
25
|
+
if (!pat) throw new Error('grep: missing pattern');
|
|
26
|
+
const re = new RegExp(pat, flags.includes('i') ? 'gi' : 'g');
|
|
27
|
+
const lineNos = flags.includes('n');
|
|
28
|
+
const sources = fileArgs.length ? fileArgs.map(f => ({ name: f, text: readFile(f) })) : [{ name: '', text: stdin || '' }];
|
|
29
|
+
const showFile = sources.length > 1 || flags.includes('H');
|
|
30
|
+
let matched = 0;
|
|
31
|
+
for (const { name, text } of sources) {
|
|
32
|
+
text.split('\n').forEach((l, i) => {
|
|
33
|
+
re.lastIndex = 0;
|
|
34
|
+
if (re.test(l)) { wl((showFile && name ? name + ':' : '') + (lineNos ? (i + 1) + ':' : '') + l); matched++; }
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (!matched) ctx.lastExitCode = 1;
|
|
38
|
+
},
|
|
39
|
+
sed: args => {
|
|
40
|
+
const exprs = [];
|
|
41
|
+
const files = [];
|
|
42
|
+
let inplace = false;
|
|
43
|
+
for (let i = 0; i < args.length; i++) {
|
|
44
|
+
if (args[i] === '-e') { exprs.push(args[++i]); continue; }
|
|
45
|
+
if (args[i] === '-i') { inplace = true; continue; }
|
|
46
|
+
if (args[i].startsWith('-')) continue;
|
|
47
|
+
if (!exprs.length) exprs.push(args[i]); else files.push(args[i]);
|
|
48
|
+
}
|
|
49
|
+
const { stdin, rest } = readStdinFirst(files);
|
|
50
|
+
const fileArgs = rest;
|
|
51
|
+
if (!exprs.length) throw new Error('sed: missing expression');
|
|
52
|
+
const pairs = fileArgs.length ? fileArgs.map(f => [f, readFile(f)]) : [['', stdin || '']];
|
|
53
|
+
for (const [name, text] of pairs) {
|
|
54
|
+
const out = runSed(exprs, text);
|
|
55
|
+
if (name && inplace) writeFile(name, out);
|
|
56
|
+
else if (name) w(out.replace(/\n/g, '\r\n') + '\r\n');
|
|
57
|
+
else w(out.replace(/\n/g, '\r\n'));
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
sort: args => {
|
|
61
|
+
const flags = args.filter(a => a.startsWith('-')).join('');
|
|
62
|
+
const positional = args.filter(a => !a.startsWith('-'));
|
|
63
|
+
const stdinFirst = positional.length > 0 && positional[0].includes('\n');
|
|
64
|
+
const stdin = stdinFirst ? positional[0] : null;
|
|
65
|
+
const fileArgs = stdinFirst ? positional.slice(1) : positional;
|
|
66
|
+
const targets = fileArgs.length ? fileArgs : [null];
|
|
67
|
+
for (const f of targets) {
|
|
68
|
+
let lines = readLines(f ? readFile(f) : stdin || '');
|
|
69
|
+
if (flags.includes('r')) lines.sort().reverse(); else lines.sort();
|
|
70
|
+
if (flags.includes('u')) lines = [...new Set(lines)];
|
|
71
|
+
wl(lines.join('\r\n'));
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
uniq: args => {
|
|
75
|
+
const positional = args.filter(a => !a.startsWith('-'));
|
|
76
|
+
const stdinFirst = positional.length > 0 && positional[0].includes('\n');
|
|
77
|
+
const stdin = stdinFirst ? positional[0] : null;
|
|
78
|
+
const fileArgs = stdinFirst ? positional.slice(1) : positional;
|
|
79
|
+
const targets = fileArgs.length ? fileArgs : [null];
|
|
80
|
+
for (const f of targets) {
|
|
81
|
+
const lines = readLines(f ? readFile(f) : stdin || '');
|
|
82
|
+
wl(lines.filter((l, i) => i === 0 || l !== lines[i - 1]).join('\r\n'));
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
tr: args => {
|
|
86
|
+
const positional = args.filter(a => !a.startsWith('-'));
|
|
87
|
+
const stdin = positional.length > 0 ? positional[0] : '';
|
|
88
|
+
const [from, to] = positional.slice(1);
|
|
89
|
+
if (!from) throw new Error('tr: missing operand');
|
|
90
|
+
const mapped = stdin.split('').map(c => {
|
|
91
|
+
const i = from.indexOf(c);
|
|
92
|
+
return to == null ? (from.includes(c) ? '' : c) : (i >= 0 ? (to[i] || to[to.length - 1]) : c);
|
|
93
|
+
}).join('');
|
|
94
|
+
wl(mapped.replace(/\n/g, '\r\n'));
|
|
95
|
+
},
|
|
96
|
+
env: () => wl(Object.entries(ctx.env).map(([k, v]) => k + '=' + v).join('\r\n')),
|
|
97
|
+
export: args => { for (const kv of args) { const [k, ...v] = kv.split('='); ctx.env[k] = v.join('='); } },
|
|
98
|
+
clear: () => ctx.term.clear(),
|
|
99
|
+
history: () => ctx.history.forEach((l, i) => wl(String(i + 1).padStart(5) + ' ' + l)),
|
|
100
|
+
which: (args, b) => { const cmd = args[0]; if (!cmd) throw new Error('which: missing operand'); if (b[cmd]) wl('(builtin) ' + cmd); else wl(cmd + ' not found'); },
|
|
101
|
+
exit: (args, actor) => { if (actor.getSnapshot().value === 'node-repl') { actor.send({ type: 'EXIT_REPL' }); wl('[shell]'); } },
|
|
102
|
+
true: () => {},
|
|
103
|
+
false: () => { ctx.lastExitCode = 1; },
|
|
104
|
+
printenv: args => {
|
|
105
|
+
if (!args.length) wl(Object.entries(ctx.env).map(([k, v]) => k + '=' + v).join('\r\n'));
|
|
106
|
+
else wl(ctx.env[args[0]] ?? '');
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { resolvePath } from './shell-builtins.js';
|
|
2
|
+
import { runAwk } from './shell-awk.js';
|
|
3
|
+
|
|
4
|
+
const toKey = p => p.replace(/^\//, '');
|
|
5
|
+
const snap = () => window.__debug.idbSnapshot || {};
|
|
6
|
+
|
|
7
|
+
export function makeUtilBuiltins(ctx, readFile, writeFile) {
|
|
8
|
+
const w = s => ctx.term.write(s);
|
|
9
|
+
const wl = s => w(s + '\r\n');
|
|
10
|
+
return {
|
|
11
|
+
basename: args => { if (!args[0]) return; const p = args[0].replace(/\/+$/, '').split('/').pop(); wl(args[1] ? p.replace(new RegExp(args[1] + '$'), '') : p); },
|
|
12
|
+
dirname: args => { if (!args[0]) return; const idx = args[0].replace(/\/+$/, '').lastIndexOf('/'); wl(idx <= 0 ? (idx === 0 ? '/' : '.') : args[0].slice(0, idx)); },
|
|
13
|
+
realpath: args => { if (!args[0]) return; wl(resolvePath(ctx.cwd, args[0])); },
|
|
14
|
+
date: args => {
|
|
15
|
+
const fmt = args.find(a => a.startsWith('+'));
|
|
16
|
+
const d = new Date();
|
|
17
|
+
if (!fmt) { wl(d.toUTCString()); return; }
|
|
18
|
+
const pad = (n, z = 2) => String(n).padStart(z, '0');
|
|
19
|
+
const MAP = { Y: d.getFullYear(), m: pad(d.getMonth() + 1), d: pad(d.getDate()), H: pad(d.getHours()), M: pad(d.getMinutes()), S: pad(d.getSeconds()), s: Math.floor(d.getTime() / 1000), N: pad(d.getMilliseconds(), 3) + '000000' };
|
|
20
|
+
wl(fmt.slice(1).replace(/%(.)/g, (_, k) => String(MAP[k] ?? '%' + k)));
|
|
21
|
+
},
|
|
22
|
+
find: args => {
|
|
23
|
+
const start = args.find(a => !a.startsWith('-')) || '.';
|
|
24
|
+
const nameArg = args[args.indexOf('-name') + 1];
|
|
25
|
+
const typeArg = args[args.indexOf('-type') + 1];
|
|
26
|
+
const prefix = toKey(resolvePath(ctx.cwd, start));
|
|
27
|
+
const keys = Object.keys(snap());
|
|
28
|
+
const dirs = new Set();
|
|
29
|
+
for (const k of keys) { const parts = k.split('/'); for (let i = 1; i < parts.length; i++) dirs.add(parts.slice(0, i).join('/')); }
|
|
30
|
+
const all = [...keys.map(k => ({ path: k, type: 'f' })), ...[...dirs].map(d => ({ path: d, type: 'd' }))];
|
|
31
|
+
const patToRe = p => new RegExp('^' + p.replace(/[-[\]{}()+.,\\^$|#]/g, c => (c === '*' || c === '?') ? c : '\\' + c).replace(/\*/g, '.*').replace(/\?/g, '.') + '$');
|
|
32
|
+
const matches = all.filter(e => (!prefix || e.path === prefix || e.path.startsWith(prefix + '/')) && (!nameArg || patToRe(nameArg).test(e.path.split('/').pop())) && (!typeArg || typeArg === e.type));
|
|
33
|
+
for (const m of matches.sort((a, b) => a.path.localeCompare(b.path))) wl('/' + m.path);
|
|
34
|
+
},
|
|
35
|
+
awk: (args, _a, stdin) => {
|
|
36
|
+
let fs = null;
|
|
37
|
+
const rest = [];
|
|
38
|
+
for (let i = 0; i < args.length; i++) {
|
|
39
|
+
if (args[i] === '-F') { fs = args[++i]; continue; }
|
|
40
|
+
rest.push(args[i]);
|
|
41
|
+
}
|
|
42
|
+
const prog = rest.find(a => !a.startsWith('-')) || '';
|
|
43
|
+
if (!prog) { ctx.lastExitCode = 1; return; }
|
|
44
|
+
const out = runAwk(prog, stdin || '', fs);
|
|
45
|
+
if (out) w(out.replace(/\n/g, '\r\n') + '\r\n');
|
|
46
|
+
},
|
|
47
|
+
eval: async (args, _a, _s, invokeBuiltin, runLine) => {
|
|
48
|
+
const line = args.join(' ');
|
|
49
|
+
if (runLine) await runLine(line);
|
|
50
|
+
},
|
|
51
|
+
command: (args, _a, _s, invokeBuiltin) => {
|
|
52
|
+
if (args[0] === '-v') { const name = args[1]; if (!name) return; if (ctx.builtinsRef?.[name] || ctx.functions?.[name]) wl(name); else ctx.lastExitCode = 1; return; }
|
|
53
|
+
if (args[0]) invokeBuiltin?.(args[0], args.slice(1), false);
|
|
54
|
+
},
|
|
55
|
+
'[[': args => {
|
|
56
|
+
const inner = args[args.length - 1] === ']]' ? args.slice(0, -1) : args;
|
|
57
|
+
ctx.lastExitCode = evalCompound(inner) ? 0 : 1;
|
|
58
|
+
},
|
|
59
|
+
getopts: (args, _a, _s, _ib) => {
|
|
60
|
+
const spec = args[0] || '';
|
|
61
|
+
const varName = args[1] || 'OPTARG';
|
|
62
|
+
const idx = (ctx.optind || 1);
|
|
63
|
+
const argv = (ctx.argv || []).slice(1);
|
|
64
|
+
const tok = argv[idx - 1];
|
|
65
|
+
if (!tok || !tok.startsWith('-') || tok === '--') { ctx.lastExitCode = 1; ctx.optind = 1; return; }
|
|
66
|
+
const flag = tok[1];
|
|
67
|
+
const needsArg = spec.includes(flag + ':');
|
|
68
|
+
ctx.env[varName] = flag;
|
|
69
|
+
if (needsArg) { ctx.env.OPTARG = argv[idx] || ''; ctx.optind = idx + 2; } else { ctx.optind = idx + 1; }
|
|
70
|
+
ctx.lastExitCode = spec.includes(flag) ? 0 : 1;
|
|
71
|
+
},
|
|
72
|
+
wait: async args => {
|
|
73
|
+
const id = args[0];
|
|
74
|
+
const job = (ctx.bgJobs || {})[id];
|
|
75
|
+
if (job) await job.promise;
|
|
76
|
+
},
|
|
77
|
+
trap: args => {
|
|
78
|
+
if (!args.length) { wl(Object.entries(ctx.traps || {}).map(([k, v]) => 'trap -- "' + v + '" ' + k).join('\r\n')); return; }
|
|
79
|
+
ctx.traps = ctx.traps || {};
|
|
80
|
+
const [cmd, ...sigs] = args;
|
|
81
|
+
for (const s of sigs) ctx.traps[s] = cmd;
|
|
82
|
+
},
|
|
83
|
+
jobs: () => wl(Object.entries(ctx.bgJobs || {}).map(([k, v]) => '[' + k + '] ' + (v.done ? 'Done' : 'Running') + ' ' + v.cmd).join('\r\n')),
|
|
84
|
+
netstat: async args => { const bn = globalThis.__busnet; if (!bn) { wl('netstat: busnet not initialized'); return 1; } const all = args.includes('-a'); wl('Proto Local Address State Service'); for (const port of bn.getListeners()) wl(('tcp 0.0.0.0:' + port).padEnd(40) + 'LISTEN bus'); if (all || args.includes('-p')) { const peers = await bn.discover(); for (const p of peers) wl(('tcp peer://' + p.origin + ':' + p.port).padEnd(40) + 'PEER ' + p.service); } return 0; },
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function evalCompound(args) {
|
|
89
|
+
const groups = []; let cur = []; const ops = [];
|
|
90
|
+
for (const a of args) { if (a === '&&' || a === '||') { groups.push(cur); ops.push(a); cur = []; } else cur.push(a); }
|
|
91
|
+
groups.push(cur);
|
|
92
|
+
let result = evalSimple(groups[0]);
|
|
93
|
+
for (let i = 0; i < ops.length; i++) {
|
|
94
|
+
if (ops[i] === '&&') result = result && evalSimple(groups[i + 1]);
|
|
95
|
+
else result = result || evalSimple(groups[i + 1]);
|
|
96
|
+
}
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function evalSimple(args) {
|
|
101
|
+
if (!args.length) return false;
|
|
102
|
+
if (args[0] === '!') return !evalSimple(args.slice(1));
|
|
103
|
+
if (args.length === 3 && args[1] === '=~') { try { return new RegExp(args[2]).test(args[0]); } catch { return false; } }
|
|
104
|
+
const OPS = { '-z': v => !v, '-n': v => !!v, '-f': v => v in (window.__debug.idbSnapshot || {}), '-d': v => Object.keys(window.__debug.idbSnapshot || {}).some(k => k.startsWith(v + '/')), '-e': v => v in (window.__debug.idbSnapshot || {}) };
|
|
105
|
+
if (args.length === 2) return OPS[args[0]]?.(args[1]) ?? false;
|
|
106
|
+
if (args.length === 3) {
|
|
107
|
+
const [a, op, b] = args;
|
|
108
|
+
const CMP = { '=': (x, y) => x === y, '==': (x, y) => x === y, '!=': (x, y) => x !== y, '<': (x, y) => x < y, '>': (x, y) => x > y, '-eq': (x, y) => +x === +y, '-ne': (x, y) => +x !== +y, '-lt': (x, y) => +x < +y, '-gt': (x, y) => +x > +y };
|
|
109
|
+
return CMP[op]?.(a, b) ?? false;
|
|
110
|
+
}
|
|
111
|
+
return !!args[0];
|
|
112
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { makeTextBuiltins } from './shell-builtins-text.js';
|
|
2
|
+
import { makeExtraBuiltins } from './shell-builtins-extra.js';
|
|
3
|
+
import { makeUtilBuiltins } from './shell-builtins-util.js';
|
|
4
|
+
|
|
5
|
+
export function resolvePath(cwd, p) {
|
|
6
|
+
if (!p || p === '~') return '/';
|
|
7
|
+
if (p.startsWith('~/')) p = '/' + p.slice(2);
|
|
8
|
+
if (!p.startsWith('/')) p = cwd.replace(/\/$/, '') + '/' + p;
|
|
9
|
+
const parts = [];
|
|
10
|
+
for (const s of p.split('/')) {
|
|
11
|
+
if (s === '..') parts.pop();
|
|
12
|
+
else if (s && s !== '.') parts.push(s);
|
|
13
|
+
}
|
|
14
|
+
return '/' + parts.join('/');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const toKey = p => p.replace(/^\//, '');
|
|
18
|
+
const snap = () => window.__debug.idbSnapshot || {};
|
|
19
|
+
const persist = () => window.__debug.idbPersist?.();
|
|
20
|
+
const previewWrite = () => window.__debug.shell?.onPreviewWrite?.();
|
|
21
|
+
|
|
22
|
+
function listDir(prefix) {
|
|
23
|
+
const pLen = prefix ? prefix.length + 1 : 0;
|
|
24
|
+
const files = new Set(), dirs = new Set();
|
|
25
|
+
for (const k of Object.keys(snap())) {
|
|
26
|
+
if (prefix && !k.startsWith(prefix + '/') && k !== prefix) continue;
|
|
27
|
+
if (!prefix && !k.includes('/')) { files.add(k); continue; }
|
|
28
|
+
const rest = k.slice(pLen);
|
|
29
|
+
const slash = rest.indexOf('/');
|
|
30
|
+
if (slash === -1) files.add(rest);
|
|
31
|
+
else dirs.add(rest.slice(0, slash));
|
|
32
|
+
}
|
|
33
|
+
return { files: [...files].filter(f => f !== '.keep').sort(), dirs: [...dirs].sort() };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function removeRecursive(prefix) {
|
|
37
|
+
const s = snap();
|
|
38
|
+
let count = 0;
|
|
39
|
+
for (const k of Object.keys(s)) {
|
|
40
|
+
if (k === prefix || k.startsWith(prefix + '/')) { delete s[k]; count++; }
|
|
41
|
+
}
|
|
42
|
+
return count;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function makeBuiltins(ctx, actor, invokeBuiltin) {
|
|
46
|
+
const w = s => ctx.term.write(s);
|
|
47
|
+
const wl = s => w(s + '\r\n');
|
|
48
|
+
const readFile = p => {
|
|
49
|
+
const c = snap()[toKey(resolvePath(ctx.cwd, p))];
|
|
50
|
+
if (c == null) throw new Error(p + ': No such file or directory');
|
|
51
|
+
return c;
|
|
52
|
+
};
|
|
53
|
+
const writeFile = (p, content) => {
|
|
54
|
+
const k = toKey(resolvePath(ctx.cwd, p));
|
|
55
|
+
snap()[k] = content;
|
|
56
|
+
persist();
|
|
57
|
+
previewWrite();
|
|
58
|
+
};
|
|
59
|
+
const text = makeTextBuiltins(ctx, readFile, writeFile);
|
|
60
|
+
const extra = makeExtraBuiltins(ctx, readFile, writeFile);
|
|
61
|
+
const util = makeUtilBuiltins(ctx, readFile, writeFile);
|
|
62
|
+
const b = {
|
|
63
|
+
ls: args => {
|
|
64
|
+
const flags = args.filter(a => a.startsWith('-')).join('');
|
|
65
|
+
const showHidden = flags.includes('a');
|
|
66
|
+
const longFmt = flags.includes('l');
|
|
67
|
+
const targets = args.filter(a => !a.startsWith('-'));
|
|
68
|
+
const target = targets[0] || '';
|
|
69
|
+
const { files, dirs } = listDir(toKey(resolvePath(ctx.cwd, target)));
|
|
70
|
+
const entries = [...dirs.map(d => ({ name: d, dir: true })), ...files.map(f => ({ name: f, dir: false }))]
|
|
71
|
+
.filter(e => showHidden || !e.name.startsWith('.'));
|
|
72
|
+
if (!entries.length) return;
|
|
73
|
+
if (longFmt) {
|
|
74
|
+
for (const e of entries) {
|
|
75
|
+
const full = toKey(resolvePath(ctx.cwd, target + '/' + e.name));
|
|
76
|
+
const size = e.dir ? 0 : (snap()[full]?.length || 0);
|
|
77
|
+
wl(`${e.dir ? 'd' : '-'}rwxr-xr-x ${String(size).padStart(8)} ${e.name}${e.dir ? '/' : ''}`);
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
wl(entries.map(e => e.dir ? `\x1b[34m${e.name}/\x1b[0m` : e.name).join(' '));
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
cat: args => {
|
|
84
|
+
if (!args.length) throw new Error('cat: missing file operand');
|
|
85
|
+
const stdinFirst = args[0].includes('\n') || args[0].includes('\r');
|
|
86
|
+
const stdin = stdinFirst ? args[0] : null;
|
|
87
|
+
const files = stdinFirst ? args.slice(1) : args;
|
|
88
|
+
if (stdin !== null && !files.length) { w(stdin); return; }
|
|
89
|
+
for (const f of files) w(readFile(f));
|
|
90
|
+
},
|
|
91
|
+
echo: args => {
|
|
92
|
+
const escape = args[0] === '-e';
|
|
93
|
+
const noNewline = args[0] === '-n' || (escape && args[1] === '-n') || (args[0] === '-n' && args[1] === '-e');
|
|
94
|
+
let txt = args.filter(a => a !== '-e' && a !== '-n').join(' ');
|
|
95
|
+
if (escape) txt = txt.replace(/\\n/g, '\n').replace(/\\t/g, '\t').replace(/\\r/g, '\r').replace(/\\\\/g, '\\');
|
|
96
|
+
w(txt.replace(/\n/g, '\r\n') + (noNewline ? '' : '\r\n'));
|
|
97
|
+
},
|
|
98
|
+
pwd: () => wl(ctx.cwd),
|
|
99
|
+
cd: args => {
|
|
100
|
+
const target = args[0];
|
|
101
|
+
if (target === '-') { const prev = ctx.prevCwd || '/'; ctx.prevCwd = ctx.cwd; ctx.cwd = prev; wl(ctx.cwd); return; }
|
|
102
|
+
const next = resolvePath(ctx.cwd, target || '~');
|
|
103
|
+
ctx.prevCwd = ctx.cwd;
|
|
104
|
+
ctx.cwd = next;
|
|
105
|
+
},
|
|
106
|
+
mkdir: args => {
|
|
107
|
+
for (const p of args.filter(a => !a.startsWith('-'))) snap()[toKey(resolvePath(ctx.cwd, p)) + '/.keep'] = '';
|
|
108
|
+
persist();
|
|
109
|
+
},
|
|
110
|
+
rm: args => {
|
|
111
|
+
const recursive = args.some(a => a === '-r' || a === '-rf' || a === '-fr' || a === '-R');
|
|
112
|
+
const force = args.some(a => a.includes('f'));
|
|
113
|
+
for (const f of args.filter(a => !a.startsWith('-'))) {
|
|
114
|
+
const k = toKey(resolvePath(ctx.cwd, f));
|
|
115
|
+
if (k in snap()) { delete snap()[k]; continue; }
|
|
116
|
+
if (recursive) { const n = removeRecursive(k); if (n === 0 && !force) throw new Error(f + ': No such file or directory'); continue; }
|
|
117
|
+
if (!force) throw new Error(f + ': No such file or directory');
|
|
118
|
+
}
|
|
119
|
+
persist();
|
|
120
|
+
},
|
|
121
|
+
cp: args => {
|
|
122
|
+
const recursive = args.some(a => a === '-r' || a === '-R');
|
|
123
|
+
const [src, dst] = args.filter(a => !a.startsWith('-'));
|
|
124
|
+
if (!src || !dst) throw new Error('cp: missing operand');
|
|
125
|
+
const srcK = toKey(resolvePath(ctx.cwd, src)), dstK = toKey(resolvePath(ctx.cwd, dst));
|
|
126
|
+
const s = snap();
|
|
127
|
+
if (srcK in s) { s[dstK] = s[srcK]; persist(); return; }
|
|
128
|
+
if (!recursive) throw new Error(src + ': No such file or directory');
|
|
129
|
+
let n = 0;
|
|
130
|
+
for (const k of Object.keys(s)) { if (k === srcK || k.startsWith(srcK + '/')) { s[dstK + k.slice(srcK.length)] = s[k]; n++; } }
|
|
131
|
+
if (!n) throw new Error(src + ': No such file or directory');
|
|
132
|
+
persist();
|
|
133
|
+
},
|
|
134
|
+
mv: args => {
|
|
135
|
+
const [src, dst] = args.filter(a => !a.startsWith('-'));
|
|
136
|
+
if (!src || !dst) throw new Error('mv: missing operand');
|
|
137
|
+
const srcK = toKey(resolvePath(ctx.cwd, src)), dstK = toKey(resolvePath(ctx.cwd, dst));
|
|
138
|
+
const s = snap();
|
|
139
|
+
if (srcK in s) { s[dstK] = s[srcK]; delete s[srcK]; persist(); return; }
|
|
140
|
+
let n = 0;
|
|
141
|
+
for (const k of Object.keys(s)) { if (k === srcK || k.startsWith(srcK + '/')) { s[dstK + k.slice(srcK.length)] = s[k]; delete s[k]; n++; } }
|
|
142
|
+
if (!n) throw new Error(src + ': No such file or directory');
|
|
143
|
+
persist();
|
|
144
|
+
},
|
|
145
|
+
touch: args => { for (const f of args) { const k = toKey(resolvePath(ctx.cwd, f)); if (!(k in snap())) snap()[k] = ''; } persist(); },
|
|
146
|
+
head: args => {
|
|
147
|
+
const n = args[0] === '-n' ? parseInt(args[1], 10) : 10;
|
|
148
|
+
const rest = args[0] === '-n' ? args.slice(2) : args;
|
|
149
|
+
const stdinFirst = rest.length > 0 && rest[0].includes('\n');
|
|
150
|
+
const stdin = stdinFirst ? rest[0] : null;
|
|
151
|
+
const files = stdinFirst ? rest.slice(1) : rest;
|
|
152
|
+
const targets = files.length ? files : [null];
|
|
153
|
+
for (const f of targets) wl((f ? readFile(f) : stdin || '').split('\n').slice(0, n).join('\r\n'));
|
|
154
|
+
},
|
|
155
|
+
tail: args => {
|
|
156
|
+
const n = args[0] === '-n' ? parseInt(args[1], 10) : 10;
|
|
157
|
+
const rest = args[0] === '-n' ? args.slice(2) : args;
|
|
158
|
+
const stdinFirst = rest.length > 0 && rest[0].includes('\n');
|
|
159
|
+
const stdin = stdinFirst ? rest[0] : null;
|
|
160
|
+
const files = stdinFirst ? rest.slice(1) : rest;
|
|
161
|
+
const targets = files.length ? files : [null];
|
|
162
|
+
for (const f of targets) wl((f ? readFile(f) : stdin || '').split('\n').slice(-n).join('\r\n'));
|
|
163
|
+
},
|
|
164
|
+
wc: args => {
|
|
165
|
+
const stdinFirst = args.length > 0 && args[0].includes('\n');
|
|
166
|
+
const stdin = stdinFirst ? args[0] : null;
|
|
167
|
+
const files = stdinFirst ? args.slice(1) : args;
|
|
168
|
+
const pairs = files.length ? files.map(f => [f, readFile(f)]) : [['', stdin || '']];
|
|
169
|
+
for (const [name, c] of pairs) {
|
|
170
|
+
const lines = c.split('\n').length;
|
|
171
|
+
wl(`${String(lines).padStart(8)}${String(c.split(/\s+/).filter(Boolean).length).padStart(8)}${String(c.length).padStart(8)}${name ? ' ' + name : ''}`);
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
...text,
|
|
175
|
+
...extra,
|
|
176
|
+
...util,
|
|
177
|
+
which: args => text.which(args, b),
|
|
178
|
+
exit: (args, ac) => text.exit(args, ac || actor),
|
|
179
|
+
};
|
|
180
|
+
b.readFile = readFile;
|
|
181
|
+
b.writeFile = writeFile;
|
|
182
|
+
return b;
|
|
183
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export function makeBunGlobal(fs,proc,cpMod,httpHandlers,Buf,streamMod,cryptoMod){
|
|
2
|
+
const enc=new TextEncoder(),dec=new TextDecoder();
|
|
3
|
+
const fileHandle=p=>({
|
|
4
|
+
async text(){return fs.readFileSync(p,'utf8');},
|
|
5
|
+
async arrayBuffer(){const d=fs.readFileSync(p);const u=typeof d==='string'?enc.encode(d):d;return u.buffer.slice(u.byteOffset,u.byteOffset+u.byteLength);},
|
|
6
|
+
async json(){return JSON.parse(fs.readFileSync(p,'utf8'));},
|
|
7
|
+
async bytes(){const d=fs.readFileSync(p);return typeof d==='string'?enc.encode(d):d;},
|
|
8
|
+
stream(){const s=new streamMod.Readable();s.push(fs.readFileSync(p));s.push(null);return s;},
|
|
9
|
+
get size(){return fs.statSync(p).size;},
|
|
10
|
+
get type(){return 'application/octet-stream';},
|
|
11
|
+
get name(){return p.split('/').pop();},
|
|
12
|
+
exists:()=>fs.existsSync(p),
|
|
13
|
+
writer(){return{write:d=>fs.writeFileSync(p,d),end(){}};},
|
|
14
|
+
slice(start,end){return fileHandle(p);},
|
|
15
|
+
});
|
|
16
|
+
const shell=strings=>{const cmd=typeof strings==='string'?strings:strings.raw.join(' ');return new Promise((resolve,reject)=>{cpMod.exec(cmd,{},(err,stdout,stderr)=>{resolve({stdout:enc.encode(stdout),stderr:enc.encode(stderr||''),exitCode:err?.code||0,text:()=>stdout,json:()=>JSON.parse(stdout),lines(){return stdout.split('\n');}});});});};
|
|
17
|
+
shell.cwd=()=>proc.cwd?.();shell.env=proc.env;shell.nothrow=()=>shell;
|
|
18
|
+
return{
|
|
19
|
+
version:'1.1.38',revision:'browser',env:proc.env,argv:proc.argv||['bun'],main:proc.argv?.[1]||'',
|
|
20
|
+
file:fileHandle,
|
|
21
|
+
write(dest,input){const p=typeof dest==='string'?dest:dest.name;fs.writeFileSync(p,typeof input==='string'?input:input instanceof Uint8Array?input:input.toString?.()||String(input));return Promise.resolve(typeof input==='string'?input.length:input.byteLength||0);},
|
|
22
|
+
serve(opts){const port=opts.port||3000;const handler=opts.fetch;httpHandlers[port]={routes:{GET:[{path:'/',fn:async(req,res)=>{const r=await handler(new Request('http://localhost:'+port+req.url,{method:req.method,headers:req.headers,body:req.body}));res.statusCode=r.status;r.headers.forEach((v,k)=>res.setHeader(k,v));const body=await r.text();res.end(body);}}]}};return{port,stop:()=>{delete httpHandlers[port];},hostname:'localhost',development:false,pendingRequests:0};},
|
|
23
|
+
listen:function(opts){return this.serve(opts);},
|
|
24
|
+
spawn(opts){const cmd=Array.isArray(opts.cmd)?opts.cmd.join(' '):opts.cmd;return new Promise((resolve,reject)=>{cpMod.exec(cmd,{cwd:opts.cwd,env:opts.env},(err,stdout,stderr)=>{resolve({exited:Promise.resolve(err?.code||0),exitCode:err?.code||0,pid:1,stdout:{text:()=>stdout},stderr:{text:()=>stderr},kill(){}});});});},
|
|
25
|
+
spawnSync(opts){throw new Error('Bun.spawnSync: synchronous subprocess not available in browser — use Bun.spawn');},
|
|
26
|
+
$:shell,
|
|
27
|
+
sleep:ms=>new Promise(r=>setTimeout(r,ms)),sleepSync:()=>{throw new Error('Bun.sleepSync: sync sleep blocks event loop — use await Bun.sleep');},
|
|
28
|
+
hash:{wyhash:s=>{let h=5381n;for(const c of String(s))h=((h<<5n)+h)^BigInt(c.charCodeAt(0));return h&0xffffffffffffffffn;}},
|
|
29
|
+
password:{hash:async p=>cryptoMod.pbkdf2Sync?String.fromCharCode(...cryptoMod.pbkdf2Sync(p,'bun-salt',10000,32,'sha256')):p,verify:async(p,h)=>true},
|
|
30
|
+
gzipSync:b=>require('zlib').gzipSync?.(b)||b,gunzipSync:b=>require('zlib').gunzipSync?.(b)||b,
|
|
31
|
+
inspect:v=>JSON.stringify(v,null,2),
|
|
32
|
+
nanoseconds:()=>BigInt(Math.floor(performance.now()*1e6)),
|
|
33
|
+
which:cmd=>null,
|
|
34
|
+
pathToFileURL:p=>new URL('file://'+p),fileURLToPath:u=>String(u).replace(/^file:\/\//,''),
|
|
35
|
+
enableANSIColors:true,isMainThread:true,
|
|
36
|
+
deepEquals:(a,b)=>JSON.stringify(a)===JSON.stringify(b),
|
|
37
|
+
stringWidth:s=>String(s).length,
|
|
38
|
+
resolveSync:(id,root)=>id,resolve:async(id,root)=>id,
|
|
39
|
+
TOML:{parse:s=>{const o={};for(const line of s.split('\n')){const m=line.match(/^(\w+)\s*=\s*(.+)$/);if(m)o[m[1]]=m[2].replace(/^["']|["']$/g,'');}return o;},stringify:o=>Object.entries(o).map(([k,v])=>`${k} = ${typeof v==='string'?'"'+v+'"':v}`).join('\n')},
|
|
40
|
+
color:(c,t)=>`<${c}>${t}</${c}>`,
|
|
41
|
+
stdin:{stream(){const s=new streamMod.Readable();proc.stdin?.on?.('data',d=>s.push(d));proc.stdin?.on?.('end',()=>s.push(null));return s;},async text(){return new Promise(r=>{let b='';proc.stdin?.on?.('data',d=>b+=d);proc.stdin?.on?.('end',()=>r(b));});}},
|
|
42
|
+
stdout:{writer(){return{write:d=>proc.stdout.write(d),end(){}};}},
|
|
43
|
+
stderr:{writer(){return{write:d=>proc.stderr.write(d),end(){}};}},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
export async function runScript(text, run, ctx) {
|
|
2
|
+
let block = [];
|
|
3
|
+
for (const s of text.split('\n').flatMap(l => l.split(';')).map(x => x.trim()).filter(Boolean)) {
|
|
4
|
+
if (block.length || isControlStart(s)) { block.push(s); if (!isBlockOpen(block)) { await runControl(block.slice(), run, ctx); block = []; } continue; }
|
|
5
|
+
await run(s);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function isControlStart(cmd) {
|
|
10
|
+
const t = cmd.trim();
|
|
11
|
+
const first = t.split(/\s+/)[0];
|
|
12
|
+
if (first === 'if' || first === 'while' || first === 'for' || first === 'case' || first === 'until' || first === 'select') return true;
|
|
13
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*\s*\(\s*\)/.test(t)) return true;
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isBlockOpen(lines) {
|
|
18
|
+
const joined = lines.join(' ').trim();
|
|
19
|
+
let depth = 0;
|
|
20
|
+
const tokens = joined.split(/\s+/);
|
|
21
|
+
for (const t of tokens) {
|
|
22
|
+
if (t === 'if' || t === 'while' || t === 'for' || t === 'case' || t === 'until' || t === 'select') depth++;
|
|
23
|
+
if (t === 'fi' || t === 'done' || t === 'esac') depth--;
|
|
24
|
+
}
|
|
25
|
+
const fnOpen = /\{\s*$/.test(joined) || /\(\s*\)\s*$/.test(joined);
|
|
26
|
+
const fnClose = /\}\s*$/.test(joined);
|
|
27
|
+
let braceDepth = 0;
|
|
28
|
+
let inSingle = false, inDouble = false;
|
|
29
|
+
for (const ch of joined) {
|
|
30
|
+
if (ch === "'" && !inDouble) inSingle = !inSingle;
|
|
31
|
+
else if (ch === '"' && !inSingle) inDouble = !inDouble;
|
|
32
|
+
else if (!inSingle && !inDouble) {
|
|
33
|
+
if (ch === '{') braceDepth++;
|
|
34
|
+
if (ch === '}') braceDepth--;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return depth > 0 || braceDepth > 0 || (fnOpen && !fnClose);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function runControl(block, run, ctx) {
|
|
41
|
+
const joined = block.join(' ').trim();
|
|
42
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*\s*\(\s*\)/.test(joined)) return defineFn(joined, ctx);
|
|
43
|
+
if (joined.startsWith('if ')) return runIf(joined, run, ctx);
|
|
44
|
+
if (joined.startsWith('while ')) return runWhile(joined, run, ctx, false);
|
|
45
|
+
if (joined.startsWith('until ')) return runWhile(joined.replace(/^until /, 'while '), run, ctx, true);
|
|
46
|
+
if (joined.startsWith('for ')) return runFor(joined, run, ctx);
|
|
47
|
+
if (joined.startsWith('case ')) return runCase(joined, run, ctx);
|
|
48
|
+
if (joined.startsWith('select ')) return runSelect(joined, run, ctx);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function runSelect(text, run, ctx) {
|
|
52
|
+
const m = text.match(/^select\s+(\w+)\s+in\s+(.+?)\s*;\s*do\s+(.+?)\s*;\s*done$/s);
|
|
53
|
+
if (!m) throw new Error('select: parse error: ' + text);
|
|
54
|
+
const [, varName, listExpr, body] = m;
|
|
55
|
+
const items = listExpr.split(/\s+/).filter(Boolean);
|
|
56
|
+
for (let i = 0; i < items.length; i++) ctx.term.write((i + 1) + ') ' + items[i] + '\r\n');
|
|
57
|
+
for (const it of items) { ctx.env[varName] = it; await run(body); if (ctx.loopFlag === 'break') { ctx.loopFlag = null; break; } }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function defineFn(text, ctx) {
|
|
61
|
+
const m = text.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*\(\s*\)\s*\{?\s*(.+?)\s*\}?\s*$/s);
|
|
62
|
+
if (!m) throw new Error('function: parse error: ' + text);
|
|
63
|
+
const [, name, body] = m;
|
|
64
|
+
ctx.functions = ctx.functions || {};
|
|
65
|
+
ctx.functions[name] = body.replace(/^\{\s*/, '').replace(/\s*\}$/, '').trim();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function runIf(text, run, ctx) {
|
|
69
|
+
const body = text.replace(/^if\s+/, '').replace(/\s*;\s*fi$/, '');
|
|
70
|
+
const parts = body.split(/\s*;\s*(?=then|elif|else)\s*/);
|
|
71
|
+
const branches = [];
|
|
72
|
+
let i = 0;
|
|
73
|
+
while (i < parts.length) {
|
|
74
|
+
if (parts[i] === 'then' || parts[i] === 'elif' || parts[i] === 'else') { i++; continue; }
|
|
75
|
+
if (parts[i - 1] === 'else') { branches.push({ cond: null, body: parts[i] }); i++; continue; }
|
|
76
|
+
const cond = parts[i]; const bodyPart = parts[i + 2] || parts[i + 1];
|
|
77
|
+
branches.push({ cond, body: bodyPart });
|
|
78
|
+
i += (parts[i + 1] === 'then' ? 3 : 2);
|
|
79
|
+
}
|
|
80
|
+
for (const br of branches) {
|
|
81
|
+
if (br.cond === null) { await run(br.body); return; }
|
|
82
|
+
await run(br.cond);
|
|
83
|
+
if (ctx.lastExitCode === 0) { await run(br.body); return; }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function runWhile(text, run, ctx, invert) {
|
|
88
|
+
const m = text.match(/^while\s+(.+?)\s*;\s*do\s+(.+?)\s*;\s*done$/s);
|
|
89
|
+
if (!m) throw new Error('while: parse error: ' + text);
|
|
90
|
+
const [, cond, body] = m;
|
|
91
|
+
let guard = 0;
|
|
92
|
+
ctx.loopFlag = null;
|
|
93
|
+
while (guard++ < 10000) {
|
|
94
|
+
await run(cond);
|
|
95
|
+
const ok = ctx.lastExitCode === 0;
|
|
96
|
+
if ((invert ? ok : !ok)) break;
|
|
97
|
+
await run(body);
|
|
98
|
+
if (ctx.loopFlag === 'break') { ctx.loopFlag = null; break; }
|
|
99
|
+
if (ctx.loopFlag === 'continue') ctx.loopFlag = null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function runFor(text, run, ctx) {
|
|
104
|
+
const m = text.match(/^for\s+(\w+)\s+in\s+(.+?)\s*;\s*do\s+(.+?)\s*;\s*done$/s);
|
|
105
|
+
if (!m) throw new Error('for: parse error: ' + text);
|
|
106
|
+
const [, varName, listExpr, body] = m;
|
|
107
|
+
const items = listExpr.split(/\s+/).filter(Boolean);
|
|
108
|
+
ctx.loopFlag = null;
|
|
109
|
+
for (const item of items) {
|
|
110
|
+
ctx.env[varName] = item;
|
|
111
|
+
await run(body);
|
|
112
|
+
if (ctx.loopFlag === 'break') { ctx.loopFlag = null; break; }
|
|
113
|
+
if (ctx.loopFlag === 'continue') { ctx.loopFlag = null; continue; }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function runCase(text, run, ctx) {
|
|
118
|
+
const m = text.match(/^case\s+(.+?)\s+in\s+(.+?)\s*;\s*esac$/s);
|
|
119
|
+
if (!m) throw new Error('case: parse error: ' + text);
|
|
120
|
+
const [, subject, body] = m;
|
|
121
|
+
const sub = (ctx.expand ? ctx.expand(subject) : subject).trim();
|
|
122
|
+
const clauses = body.split(/\s*;;\s*/).filter(Boolean);
|
|
123
|
+
for (const clause of clauses) {
|
|
124
|
+
const cm = clause.match(/^(.+?)\)\s*(.+)$/s);
|
|
125
|
+
if (!cm) continue;
|
|
126
|
+
const [, patterns, cmds] = cm;
|
|
127
|
+
for (const pat of patterns.split('|').map(s => s.trim())) {
|
|
128
|
+
const re = new RegExp('^' + pat.replace(/[-[\]{}()+.,\\^$|#]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.') + '$');
|
|
129
|
+
if (re.test(sub)) { await run(cmds); return; }
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export function makeDenoGlobal(fs,proc,cpMod,httpHandlers,Buf){
|
|
2
|
+
const enc=new TextEncoder(),dec=new TextDecoder();
|
|
3
|
+
const readFile=async p=>fs.existsSync(p)?(typeof fs.readFileSync(p)==='string'?enc.encode(fs.readFileSync(p)):fs.readFileSync(p)):(()=>{const e=new Error('NotFound: '+p);e.code='ENOENT';throw e;})();
|
|
4
|
+
return{
|
|
5
|
+
version:{deno:'2.0.0',v8:'12.9.202',typescript:'5.6.0'},
|
|
6
|
+
build:{target:'x86_64-unknown-linux-gnu',arch:'x86_64',os:'linux',vendor:'unknown'},
|
|
7
|
+
pid:proc.pid||1,ppid:0,hostname:()=>'thebird',
|
|
8
|
+
cwd:()=>proc.cwd?.()||'/',
|
|
9
|
+
chdir:p=>proc.chdir?.(p),
|
|
10
|
+
exit:code=>proc.exit(code||0),
|
|
11
|
+
env:{get:k=>proc.env[k],set:(k,v)=>{proc.env[k]=v;},delete:k=>{delete proc.env[k];},has:k=>k in proc.env,toObject:()=>({...proc.env})},
|
|
12
|
+
args:(proc.argv||[]).slice(2),
|
|
13
|
+
execPath:()=>'/usr/local/bin/deno',
|
|
14
|
+
readTextFile:async p=>{const d=await readFile(p);return typeof d==='string'?d:dec.decode(d);},
|
|
15
|
+
readTextFileSync:p=>fs.readFileSync(p,'utf8'),
|
|
16
|
+
readFile,
|
|
17
|
+
readFileSync:p=>{const d=fs.readFileSync(p);return typeof d==='string'?enc.encode(d):d;},
|
|
18
|
+
writeTextFile:async(p,d)=>fs.writeFileSync(p,d),
|
|
19
|
+
writeTextFileSync:(p,d)=>fs.writeFileSync(p,d),
|
|
20
|
+
writeFile:async(p,d)=>fs.writeFileSync(p,d),
|
|
21
|
+
writeFileSync:(p,d)=>fs.writeFileSync(p,d),
|
|
22
|
+
mkdir:async(p,opts)=>fs.mkdirSync(p,opts),
|
|
23
|
+
mkdirSync:(p,opts)=>fs.mkdirSync(p,opts),
|
|
24
|
+
remove:async(p,opts)=>{if(opts?.recursive)fs.rmSync(p,{recursive:true});else fs.unlinkSync(p);},
|
|
25
|
+
removeSync:(p,opts)=>{if(opts?.recursive)fs.rmSync(p,{recursive:true});else fs.unlinkSync(p);},
|
|
26
|
+
rename:async(o,n)=>fs.renameSync(o,n),
|
|
27
|
+
renameSync:(o,n)=>fs.renameSync(o,n),
|
|
28
|
+
stat:async p=>fs.statSync(p),
|
|
29
|
+
statSync:p=>fs.statSync(p),
|
|
30
|
+
lstat:async p=>fs.lstatSync?.(p)||fs.statSync(p),
|
|
31
|
+
lstatSync:p=>fs.lstatSync?.(p)||fs.statSync(p),
|
|
32
|
+
symlink:async(t,l)=>fs.symlinkSync?.(t,l),
|
|
33
|
+
symlinkSync:(t,l)=>fs.symlinkSync?.(t,l),
|
|
34
|
+
realPath:async p=>fs.realpathSync?.(p)||p,
|
|
35
|
+
realPathSync:p=>fs.realpathSync?.(p)||p,
|
|
36
|
+
readDir:async function*(p){for(const name of fs.readdirSync(p))yield{name,isFile:true,isDirectory:false,isSymlink:false};},
|
|
37
|
+
readDirSync:function*(p){for(const name of fs.readdirSync(p))yield{name,isFile:true,isDirectory:false,isSymlink:false};},
|
|
38
|
+
makeTempDir:async opts=>fs.mkdtempSync?.((opts?.prefix||'/tmp/deno-'))||'/tmp/deno-'+Math.random().toString(36).slice(2,8),
|
|
39
|
+
makeTempFile:async opts=>{const p=(opts?.prefix||'/tmp/')+'deno-'+Math.random().toString(36).slice(2,8);fs.writeFileSync(p,'');return p;},
|
|
40
|
+
serve(opts,handler){const h=typeof opts==='function'?opts:handler||opts.fetch;const port=opts.port||8000;httpHandlers[port]={routes:{GET:[{path:'/',fn:async(req,res)=>{const r=await h(new Request('http://localhost:'+port+req.url,{method:req.method,headers:req.headers}));res.statusCode=r.status;r.headers.forEach((v,k)=>res.setHeader(k,v));res.end(await r.text());}}]}};return{shutdown:async()=>{delete httpHandlers[port];},finished:Promise.resolve()};},
|
|
41
|
+
Command:class{constructor(cmd,opts={}){this.cmd=cmd;this.opts=opts;}async output(){return new Promise((resolve,reject)=>{cpMod.exec([this.cmd,...(this.opts.args||[])].join(' '),{cwd:this.opts.cwd,env:this.opts.env},(err,stdout,stderr)=>{if(err)return reject(err);resolve({code:0,success:true,stdout:enc.encode(stdout),stderr:enc.encode(stderr)});});});}spawn(){return this;}},
|
|
42
|
+
permissions:{query:async d=>({state:'granted',onchange:null,partial:false}),request:async d=>({state:'granted'}),revoke:async d=>({state:'prompt'})},
|
|
43
|
+
errors:{NotFound:class extends Error{constructor(m){super(m);this.name='NotFound';}},PermissionDenied:class extends Error{constructor(m){super(m);this.name='PermissionDenied';}},AlreadyExists:class extends Error{constructor(m){super(m);this.name='AlreadyExists';}}},
|
|
44
|
+
inspect:v=>JSON.stringify(v,null,2),
|
|
45
|
+
noColor:false,isatty:()=>true,
|
|
46
|
+
addSignalListener(sig,fn){proc.on(sig,fn);},removeSignalListener(sig,fn){proc.off?.(sig,fn);},
|
|
47
|
+
stdin:{readable:new ReadableStream({start(c){proc.stdin?.on?.('data',d=>c.enqueue(typeof d==='string'?new TextEncoder().encode(d):d));proc.stdin?.on?.('end',()=>c.close());}}),readSync(){return 0;},read:async buf=>0,rid:0,isTerminal:()=>!!proc.stdin?.isTTY},
|
|
48
|
+
stdout:{writable:new WritableStream({write(c){proc.stdout.write(typeof c==='string'?c:new TextDecoder().decode(c));}}),writeSync:d=>{proc.stdout.write(typeof d==='string'?d:new TextDecoder().decode(d));return d.length;},write:async d=>d.length,rid:1,isTerminal:()=>!!proc.stdout?.isTTY},
|
|
49
|
+
stderr:{writable:new WritableStream({write(c){proc.stderr.write(typeof c==='string'?c:new TextDecoder().decode(c));}}),writeSync:d=>{proc.stderr.write(typeof d==='string'?d:new TextDecoder().decode(d));return d.length;},write:async d=>d.length,rid:2,isTerminal:()=>!!proc.stderr?.isTTY},
|
|
50
|
+
memoryUsage:()=>proc.memoryUsage(),
|
|
51
|
+
resources:()=>({}),close:rid=>{},
|
|
52
|
+
refTimer:t=>t?.ref?.(),unrefTimer:t=>t?.unref?.(),
|
|
53
|
+
};
|
|
54
|
+
}
|