thebird 1.2.78 → 1.2.80
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 +226 -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-http-stream.js +24 -0
- package/docs/node-builtins.js +194 -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 +188 -97
- 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 +159 -167
- 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 +17 -0
- package/test.js +199 -0
- package/.codeinsight +0 -73
- package/docs/acp-stream.js +0 -102
- package/docs/coi-serviceworker.js +0 -2
package/docs/shell-node.js
CHANGED
|
@@ -1,108 +1,199 @@
|
|
|
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
|
-
prepare(sql) {
|
|
32
|
-
const db = this._db;
|
|
33
|
-
return {
|
|
34
|
-
run: (...params) => { db.run(sql, params); return { changes: 1 }; },
|
|
35
|
-
get: (...params) => { const r = db.exec(sql, params); return r[0]?.values[0] ? Object.fromEntries(r[0].columns.map((c, i) => [c, r[0].values[0][i]])) : undefined; },
|
|
36
|
-
all: (...params) => { const r = db.exec(sql, params); if (!r[0]) return []; return r[0].values.map(row => Object.fromEntries(r[0].columns.map((c, i) => [c, row[i]]))); },
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
});
|
|
1
|
+
import { createPath, createFs, createEvents, createUrl, createQuerystring, createBuffer } from './node-builtins.js';
|
|
2
|
+
import { createExpress, createHttp, createSqlite, createConsole, createProcess, NODE_VERSION, NODE_VERSIONS, NodeExit } from './shell-node-modules.js';
|
|
3
|
+
import { inspect, format, createZlib, preloadFflate } from './shell-node-stdlib.js';
|
|
4
|
+
import { createHash, createHmac, pbkdf2Sync, randomBytes } from './shell-node-crypto.js';
|
|
5
|
+
import { createChildProcess, createHttpClient, extendProcess, rewriteStack, isEsmCode, runEsm, parseDotEnv } from './shell-node-io.js';
|
|
6
|
+
import { resolveExports, resolveImports, walkUpNodeModules, resolvePackageEntry, makeModuleModule, makeModuleNotFoundError, makeFsPromises, makeFsWatch, makeNetStub, makeDgramStub, makeWorkerThreadsStub } from './shell-node-resolve.js';
|
|
7
|
+
import { extendBuffer, extendPath, createUrlExt, makeStringDecoder, makeReadline, makeTimersMod, makePerfHooks, makeV8Mod, makeAsyncHooks, makeStubs, makeErrorCodes, extendProcessExtras, makeStreamConsumers } from './shell-node-extras.js';
|
|
8
|
+
import { makeStream, extendFsStreams } from './shell-node-streams.js';
|
|
9
|
+
import { extendCrypto } from './shell-node-cipher.js';
|
|
10
|
+
import { extendKeys } from './shell-node-keyobject.js';
|
|
11
|
+
import { makeStreamingZlib, makeVmModule, makeModuleRegister, makeHttp2, makeWasi } from './shell-node-advanced.js';
|
|
12
|
+
import { makeDebugRegistry, makeDiagnosticsChannel, makeTraceEvents, makeBufferPool, makeProcessBindings, makePerfMemory, makeFetchPool, makeFsWatchReal, installPrepareStackTraceHook, installCaptureStackTrace } from './shell-node-observe.js';
|
|
13
|
+
import { makeWorkerThreads, makeChildProcessReal, makeRepl } from './shell-node-runtime.js';
|
|
14
|
+
import { detectBrowser, registerPolyfill, makeCompressionStreamZlib, makeWebCodecs, makeWebPush, makeStorageHelpers } from './shell-node-firefox.js';
|
|
15
|
+
import { makeOpfsBackend, wireOpfsIntoFs } from './shell-node-opfs.js';
|
|
16
|
+
import { preloadBrotli, makeBrotli } from './shell-node-brotli.js';
|
|
17
|
+
import { preloadSourceMap, installSourceMapStacks } from './shell-node-srcmap.js';
|
|
18
|
+
import { makeNet, makeTls, makeDgram } from './shell-node-net.js';
|
|
19
|
+
import { makeInspector } from './shell-node-inspector.js';
|
|
20
|
+
import { makeV8Profiler, makeHeapSnapshot } from './shell-node-profiler.js';
|
|
21
|
+
import { makeCluster } from './shell-node-cluster.js';
|
|
22
|
+
import { preloadX509 } from './shell-node-keyobject.js';
|
|
23
|
+
import { detectRuntime, registerRuntime, switchRuntime, logRuntimeSwitch } from './shell-runtime.js';
|
|
24
|
+
import { makeDenoGlobal } from './shell-deno.js'; import { makeBunGlobal } from './shell-bun.js';
|
|
25
|
+
import { makePmDispatcher, detectPm, makeCorepackStub } from './shell-pm.js';
|
|
26
|
+
import { isTsFile, preprocessSource } from './shell-ts.js'; import { installPosixFs, installFds, installTmpAndMisc } from './shell-posix.js';
|
|
27
|
+
import { makeTestRunner, makeTapReporter } from './shell-node-testrunner.js'; import { makeForkIpc } from './shell-node-ipc.js';
|
|
28
|
+
import { styleText, stripVTControlCharacters, getCallSites, MIMEType, MIMEParams, makeConsoleExtras } from './shell-node-util-extras.js';
|
|
29
|
+
import { makeProcFs, wireProcFs } from './shell-node-procfs.js'; import { makeGit } from './shell-node-git.js'; import { makeTar } from './shell-node-tar.js'; import { makeDns } from './shell-node-dns.js'; import { makeNativeLoader } from './shell-node-native.js'; import { makeRegistry } from './shell-node-registry.js';
|
|
30
|
+
import { makeBusnet, makeBusHttp } from './shell-node-busnet.js';
|
|
41
31
|
|
|
42
32
|
export function createNodeEnv({ ctx, term }) {
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
33
|
+
const pathmod = extendPath(createPath()); const Buf = makeBufferPool(extendBuffer(createBuffer())); const debugReg = makeDebugRegistry();
|
|
34
|
+
const browserInfo = detectBrowser(); debugReg.browser = browserInfo; const snapFn = () => window.__debug?.idbSnapshot || {};
|
|
35
|
+
const fsmod = installTmpAndMisc(installFds(installPosixFs(extendFsStreams(createFs(), Buf), Buf, ctx), Buf), Buf, ctx);
|
|
36
|
+
const opfs = makeOpfsBackend(Buf); if (opfs) wireOpfsIntoFs(fsmod, opfs, debugReg);
|
|
37
|
+
const runtime = detectRuntime(); registerRuntime(debugReg, runtime); fsmod.promises = makeFsPromises(fsmod); fsmod.watch = makeFsWatchReal(snapFn);
|
|
38
|
+
fsmod.glob = (pat, opts, cb) => { if (typeof opts === 'function') { cb = opts; opts = {}; } const matches = Object.keys(window.__debug?.idbSnapshot || {}).filter(k => new RegExp('^' + pat.replace(/\*\*/g, '.+').replace(/\*/g, '[^/]*') + '$').test(k)); queueMicrotask(() => cb?.(null, matches)); };
|
|
39
|
+
fsmod.globSync = pat => Object.keys(window.__debug?.idbSnapshot || {}).filter(k => new RegExp('^' + pat.replace(/\*\*/g, '.+').replace(/\*/g, '[^/]*') + '$').test(k));
|
|
40
|
+
const zlibMod = createZlib(Buf); const httpClient = createHttpClient(Buf); const cpMod = createChildProcess(ctx); const streamMod = makeStream();
|
|
41
|
+
const cpReal = makeChildProcessReal(Buf, streamMod); Object.assign(cpMod, { exec: cpReal.exec.bind(cpReal), spawn: cpReal.spawn.bind(cpReal), execFile: cpReal.execFile.bind(cpReal), execSync: cpReal.execSync, spawnSync: cpReal.spawnSync, fork: cpReal.fork });
|
|
42
|
+
let cryptoMod = { createHash, createHmac, pbkdf2Sync, pbkdf2: (pw, salt, iter, len, dig, cb) => queueMicrotask(() => { try { cb(null, Buf.from(pbkdf2Sync(pw, salt, iter, len, dig))); } catch (e) { cb(e); } }), randomBytes: n => Buf.from(randomBytes(n)), randomUUID: () => crypto.randomUUID(), randomInt: (a, b) => Math.floor(Math.random() * (b - a) + a), webcrypto: globalThis.crypto, constants: {} };
|
|
43
|
+
cryptoMod = extendKeys(extendCrypto(cryptoMod, Buf)); cryptoMod._ops = () => ++debugReg.cryptoOps; cryptoMod.secureHeapUsed = () => ({ total: 0, min: 0, used: 0, utilization: 0 });
|
|
44
|
+
const errorCodes = makeErrorCodes(); const stubs = makeStubs(ctx); const diagCh = makeDiagnosticsChannel(); const traceEv = makeTraceEvents(debugReg);
|
|
45
|
+
const vmMod = makeVmModule(); const http2Mod = makeHttp2(); const wasiMod = makeWasi(); const moduleRegister = makeModuleRegister(); const workerThreads = makeWorkerThreads(snapFn, Buf);
|
|
46
|
+
const getMem = makePerfMemory(performance); const FetchAgent = makeFetchPool(); const netMod = makeNet(Buf); const tlsMod = makeTls(netMod, Buf); const dgramMod = makeDgram(Buf);
|
|
47
|
+
const v8Real = makeV8Profiler(debugReg); const heapSnap = makeHeapSnapshot(); const clusterReal = makeCluster(); const inspector = makeInspector(debugReg);
|
|
48
|
+
const nativeCS = makeCompressionStreamZlib(streamMod, Buf); const webCodecs = makeWebCodecs(); const webPush = makeWebPush(); const storage = makeStorageHelpers();
|
|
49
|
+
const gitMod = makeGit(fsmod); const tarMod = makeTar(fsmod, null, Buf); const dnsMod = makeDns(); const nativeLoader = makeNativeLoader(); const registryMod = makeRegistry(); const busnet = makeBusnet(); globalThis.__busnet = busnet; const busHttp = makeBusHttp(busnet); debugReg.busnet = busnet;
|
|
50
|
+
if (nativeCS) registerPolyfill(debugReg, 'compressionStream', 'native', 'CompressionStream available'); if (browserInfo.capabilities.webCodecs) registerPolyfill(debugReg, 'webCodecs', 'native', 'WebCodecs available');
|
|
51
|
+
const proc = extendProcessExtras(extendProcess(createProcess(term, ctx), ctx), ctx);
|
|
52
|
+
proc.stdin.setRawMode = () => proc.stdin; proc.stdin.isRaw = false; proc.binding = makeProcessBindings(); proc.memoryUsage = getMem; proc.storage = storage; proc.storageBuckets = storage.buckets; proc.cwd = () => ctx.cwd; proc.chdir = p => { ctx.cwd = p.startsWith('/') ? p : pathmod.resolve(ctx.cwd, p); }; proc.umask = m => { const prev = ctx.umask || 0o022; if (m != null) ctx.umask = m; return prev; }; makeForkIpc(proc); proc.dlopen = (t, p) => nativeLoader.dlopen(t, p); proc.resourceUsage = () => { const m = performance.memory || {}; return { userCPUTime: performance.now() * 1000 | 0, systemCPUTime: 0, maxRSS: (m.totalJSHeapSize || 0) / 1024 | 0, sharedMemorySize: 0, unsharedDataSize: 0, unsharedStackSize: 0, minorPageFault: 0, majorPageFault: 0, swappedOut: 0, fsRead: 0, fsWrite: 0, ipcSent: 0, ipcReceived: 0, signalsCount: 0, voluntaryContextSwitches: 0, involuntaryContextSwitches: 0 }; };
|
|
53
|
+
wireProcFs(fsmod, makeProcFs(proc)); const denoGlobal = makeDenoGlobal(fsmod, proc, cpMod, ctx.httpHandlers || {}, Buf); const bunGlobal = makeBunGlobal(fsmod, proc, cpMod, ctx.httpHandlers || {}, Buf, streamMod, cryptoMod);
|
|
54
|
+
const MODULES = {
|
|
55
|
+
path: () => pathmod, fs: () => fsmod, events: () => createEvents(), url: () => createUrlExt(), querystring: () => createQuerystring(),
|
|
56
|
+
os: () => { const n=navigator?.hardwareConcurrency||1; const mem=performance.memory||{}; return { platform: () => 'linux', arch: () => 'x64', homedir: () => ctx.env.HOME || '/root', tmpdir: () => '/tmp', cpus: () => Array.from({length:n},(_,i)=>({model:'Browser CPU',speed:3000,times:{user:0,nice:0,sys:0,idle:0,irq:0}})), totalmem: () => mem.jsHeapSizeLimit || 1073741824, freemem: () => (mem.jsHeapSizeLimit || 1073741824) - (mem.usedJSHeapSize || 0), hostname: () => ctx.env.HOSTNAME || 'thebird', EOL: '\n', release: () => '6.0.0-browser', type: () => 'Linux', uptime: () => performance.now() / 1000, networkInterfaces: () => ({ lo: [{ address: '127.0.0.1', netmask: '255.0.0.0', family: 'IPv4', mac: '00:00:00:00:00:00', internal: true, cidr: '127.0.0.1/8' }] }), loadavg: () => [0, 0, 0], userInfo: () => ({ username: ctx.env.USER || 'root', uid: 0, gid: 0, shell: ctx.env.SHELL || '/bin/sh', homedir: ctx.env.HOME || '/root' }), endianness: () => 'LE', version: () => '#1 SMP', machine: () => 'x86_64', devNull: '/dev/null', availableParallelism: () => n, constants: { signals: { SIGINT: 2, SIGTERM: 15, SIGKILL: 9, SIGHUP: 1 }, errno: { EACCES: 13, EEXIST: 17, ENOENT: 2, EISDIR: 21, ENOTDIR: 20 } } }; },
|
|
57
|
+
util: () => ({ inspect, format, promisify: fn => (...a) => new Promise((r, j) => fn(...a, (e, v) => e ? j(e) : r(v))), callbackify: fn => (...a) => { const cb = a.pop(); fn(...a).then(v => cb(null, v), e => cb(e)); }, types: { isPromise: p => p instanceof Promise, isDate: v => v instanceof Date, isRegExp: v => v instanceof RegExp, isBuffer: v => v instanceof Uint8Array, isTypedArray: v => ArrayBuffer.isView(v) && !(v instanceof DataView), isAsyncFunction: f => f?.constructor?.name === 'AsyncFunction', isNativeError: e => e instanceof Error }, deprecate: fn => fn, inherits: (a, b) => { Object.setPrototypeOf(a.prototype, b.prototype); }, debuglog: () => () => {}, isDeepStrictEqual: (a, b) => JSON.stringify(a) === JSON.stringify(b), styleText, stripVTControlCharacters, getCallSites, MIMEType, MIMEParams, parseArgs: ({ args = [], options = {} }) => { const values = {}, positionals = []; for (let i = 0; i < args.length; i++) { const a = args[i]; if (a.startsWith('--')) { const [k, v] = a.slice(2).split('='); if (v !== undefined) values[k] = v; else if (options[k]?.type === 'string') values[k] = args[++i]; else values[k] = true; } else positionals.push(a); } return { values, positionals }; } }),
|
|
58
|
+
crypto: () => cryptoMod,
|
|
59
|
+
stream: () => streamMod, 'stream/promises': () => streamMod.promises, 'stream/consumers': () => makeStreamConsumers(), 'stream/web': () => ({ ReadableStream, WritableStream, TransformStream }),
|
|
60
|
+
http: () => ({ ...httpClient, Agent: FetchAgent, globalAgent: new FetchAgent() }), https: () => ({ ...httpClient, Agent: FetchAgent, globalAgent: new FetchAgent() }),
|
|
61
|
+
http2: () => http2Mod, 'node:http2': () => http2Mod,
|
|
62
|
+
vm: () => vmMod, 'node:vm': () => vmMod,
|
|
63
|
+
buffer: () => ({ Buffer: Buf, constants: { MAX_LENGTH: 4294967295, MAX_STRING_LENGTH: 536870888 }, kMaxLength: 4294967295, Blob, File }),
|
|
64
|
+
child_process: () => cpMod,
|
|
65
|
+
net: () => netMod, dgram: () => dgramMod, tls: () => tlsMod, worker_threads: () => workerThreads,
|
|
66
|
+
zlib: () => ({ ...zlibMod, ...makeStreamingZlib(streamMod, Buf, globalThis.__fflate || {}), ...(nativeCS || {}), ...makeBrotli(streamMod, Buf) }),
|
|
67
|
+
assert: () => { const a = (v, m) => { if (!v) throw new Error(m || 'assertion failed'); }; a.ok = a; a.equal = (x, y, m) => a(x === y, m); a.deepEqual = (x, y, m) => a(JSON.stringify(x) === JSON.stringify(y), m); a.deepStrictEqual = a.deepEqual; a.strictEqual = a.equal; a.notEqual = (x, y, m) => a(x !== y, m); a.notDeepEqual = (x, y, m) => a(JSON.stringify(x) !== JSON.stringify(y), m); a.notStrictEqual = a.notEqual; a.throws = (fn, m) => { try { fn(); throw new Error('did not throw'); } catch (e) {} }; a.doesNotThrow = fn => fn(); a.rejects = async fn => { try { await (typeof fn === 'function' ? fn() : fn); throw new Error('did not reject'); } catch {} }; a.fail = m => { throw new Error(m || 'failed'); }; a.match = (s, re) => a(re.test(s)); return a; },
|
|
68
|
+
string_decoder: () => stubs.string_decoder, readline: () => makeReadline(term, proc), 'readline/promises': () => stubs.readline_promises,
|
|
69
|
+
timers: () => makeTimersMod(), 'timers/promises': () => makeTimersMod().promises, perf_hooks: () => makePerfHooks(),
|
|
70
|
+
v8: () => ({ ...makeV8Mod(), ...v8Real, ...heapSnap }), async_hooks: () => makeAsyncHooks(),
|
|
71
|
+
inspector: () => inspector, cluster: () => clusterReal || stubs.cluster,
|
|
72
|
+
codecs: () => { if (!webCodecs) throw makeModuleNotFoundError('codecs', []); return webCodecs; }, 'web-push': () => webPush,
|
|
73
|
+
sea: () => stubs.sea, 'node:sea': () => stubs.sea, test: () => makeTestRunner(term), 'node:test': () => makeTestRunner(term),
|
|
74
|
+
'node:test/reporters': () => ({ tap: makeTapReporter(term), spec: class {}, dot: class {} }), tty: () => stubs.tty, domain: () => stubs.domain,
|
|
75
|
+
diagnostics_channel: () => diagCh, punycode: () => stubs.punycode, errors: () => errorCodes, trace_events: () => traceEv,
|
|
76
|
+
wasi: () => wasiMod, module: () => ({ ...makeModuleModule(() => {}, MODULES), register: moduleRegister.register, _registerHooks: moduleRegister._hooks }), express: () => createExpress(term, fsmod),
|
|
77
|
+
'better-sqlite3': createSqlite, sqlite: () => ({ DatabaseSync: createSqlite, StatementSync: class {} }), 'node:sqlite': () => ({ DatabaseSync: createSqlite, StatementSync: class {} }),
|
|
78
|
+
dns: () => dnsMod, 'dns/promises': () => dnsMod.promises, 'node:dns': () => dnsMod, 'isomorphic-git': () => gitMod, git: () => gitMod, tar: () => tarMod, 'npm-registry-fetch': () => registryMod,
|
|
79
|
+
busnet: () => busnet, 'bus-http': () => busHttp,
|
|
80
|
+
};
|
|
81
|
+
for (const k of Object.keys(MODULES)) if (!k.startsWith('node:')) MODULES['node:' + k] = MODULES[k];
|
|
82
|
+
const cons = createConsole(term);
|
|
83
|
+
cons.log = (...a) => term.write(format(...a) + '\r\n'); cons.info = cons.log; cons.error = (...a) => term.write('\x1b[31m' + format(...a) + '\x1b[0m\r\n'); cons.warn = (...a) => term.write('\x1b[33m' + format(...a) + '\x1b[0m\r\n'); cons.debug = cons.log; Object.assign(cons, makeConsoleExtras(cons, term));
|
|
84
|
+
const pkgCache = {}; const reqCache = {}; let requireStack = [];
|
|
85
|
+
function loadDotEnv() { const envFile = snapFn()[ctx.cwd.replace(/^\//, '').replace(/\/$/, '') + '/.env'] || snapFn()['.env']; if (!envFile) return; for (const [k, v] of Object.entries(parseDotEnv(envFile))) if (!(k in ctx.env)) ctx.env[k] = v; }
|
|
86
|
+
|
|
87
|
+
const resolveCandidates = (dir, id) => [pathmod.resolve(dir, id) + '.js', pathmod.resolve(dir, id), pathmod.resolve(dir, id) + '/index.js', pathmod.resolve(dir, id, 'index.js')];
|
|
88
|
+
function findPkgJsonDir(s, dir) { let d = dir.replace(/^\//, '').replace(/\/$/, ''); while (true) { const k = (d ? d + '/' : '') + 'package.json'; if (k in s) return d; if (!d) return null; const up = d.slice(0, d.lastIndexOf('/')); if (up === d) return null; d = up; } }
|
|
89
|
+
function isEsmPkg(s, filePath) { const pjDir = findPkgJsonDir(s, pathmod.dirname(filePath)); try { const pj = JSON.parse(s[(pjDir || '') + (pjDir ? '/' : '') + 'package.json']); return pj.type === 'module'; } catch { return false; } }
|
|
90
|
+
|
|
91
|
+
function makeRequire(dir) {
|
|
92
|
+
const req = function require(id) {
|
|
93
|
+
if (id === 'module') return makeModuleModule(req, MODULES);
|
|
94
|
+
if (MODULES[id]) return MODULES[id]();
|
|
95
|
+
const s = snapFn();
|
|
96
|
+
if (id.startsWith('#')) {
|
|
97
|
+
const pjRoot = findPkgJsonDir(s, dir);
|
|
98
|
+
if (pjRoot) { const pj = JSON.parse(s[pjRoot + '/package.json']); const target = resolveImports(pj, id); if (target) { const resolved = pathmod.resolve('/' + pjRoot, target); return loadFile(resolved.replace(/^\//, ''), s); } }
|
|
99
|
+
throw makeModuleNotFoundError(id, requireStack);
|
|
100
|
+
}
|
|
101
|
+
if (!id.startsWith('.')) {
|
|
102
|
+
if (pkgCache[id]) return pkgCache[id];
|
|
103
|
+
const pkgDir = walkUpNodeModules(s, dir, id);
|
|
104
|
+
if (pkgDir) { const entry = resolvePackageEntry(s, pkgDir); if (entry) { const m = loadFile(entry.replace(/^\//, ''), s); if (m) return m; } }
|
|
105
|
+
throw makeModuleNotFoundError(id, requireStack);
|
|
106
|
+
}
|
|
107
|
+
for (const c of resolveCandidates(dir, id)) {
|
|
108
|
+
const key = c.replace(/^\//, '');
|
|
109
|
+
if (key in s) { const loaded = loadFile(key, s); if (loaded !== undefined) return loaded; }
|
|
110
|
+
}
|
|
111
|
+
throw makeModuleNotFoundError(id, requireStack);
|
|
112
|
+
};
|
|
113
|
+
function loadFile(key, s) {
|
|
114
|
+
if (key.endsWith('.json')) return JSON.parse(s[key]);
|
|
115
|
+
if (reqCache[key]) return reqCache[key].exports;
|
|
61
116
|
const mod = { exports: {} };
|
|
62
|
-
|
|
117
|
+
reqCache[key] = mod;
|
|
118
|
+
const modDir = pathmod.dirname('/' + key);
|
|
119
|
+
requireStack.push('/' + key);
|
|
120
|
+
try {
|
|
121
|
+
const src = s[key]; const esm = key.endsWith('.mjs') || (key.endsWith('.js') && isEsmPkg(s, '/' + key)) || isEsmCode(src);
|
|
122
|
+
if (esm) throw new Error("ESM module '" + key + "' requested via require() — use dynamic import() or run entry as ESM");
|
|
123
|
+
new Function('module', 'exports', 'require', '__filename', '__dirname', 'process', 'console', 'Buffer', 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval', 'fetch', src)(mod, mod.exports, makeRequire(modDir), '/' + key, modDir, proc, cons, Buf, setTimeout, setInterval, clearTimeout, clearInterval, fetch);
|
|
124
|
+
}
|
|
125
|
+
finally { requireStack.pop(); }
|
|
126
|
+
mod.loaded = true;
|
|
63
127
|
return mod.exports;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (
|
|
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
|
-
};
|
|
128
|
+
}
|
|
129
|
+
req.resolve = id => {
|
|
130
|
+
if (MODULES[id] || id === 'module') return id;
|
|
131
|
+
const s = snapFn();
|
|
132
|
+
if (!id.startsWith('.')) { const pkgDir = walkUpNodeModules(s, dir, id); if (pkgDir) return resolvePackageEntry(s, pkgDir) || pkgDir; throw makeModuleNotFoundError(id, requireStack); }
|
|
133
|
+
for (const c of resolveCandidates(dir, id)) { const key = c.replace(/^\//, ''); if (key in s) return '/' + key; }
|
|
134
|
+
throw makeModuleNotFoundError(id, requireStack);
|
|
135
|
+
};
|
|
136
|
+
req.cache = reqCache;
|
|
137
|
+
return req;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function preloadAsyncPkgs(entryCode, entryDir) {
|
|
141
|
+
const s = snapFn();
|
|
142
|
+
const visited = new Set(); const queue = [{ code: entryCode, dir: entryDir }]; const pkgIds = new Set();
|
|
143
|
+
const re = /require\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
144
|
+
while (queue.length) {
|
|
145
|
+
const { code, dir } = queue.shift(); let m; re.lastIndex = 0;
|
|
146
|
+
while ((m = re.exec(code))) { const id = m[1]; if (MODULES[id]) continue; if (!id.startsWith('.')) { pkgIds.add(id); continue; } for (const c of resolveCandidates(dir, id)) { const key = c.replace(/^\//, ''); if (visited.has(key) || !(key in s)) continue; visited.add(key); queue.push({ code: s[key], dir: pathmod.dirname('/' + key) }); break; } }
|
|
147
|
+
}
|
|
148
|
+
for (const id of pkgIds) {
|
|
149
|
+
if (pkgCache[id]) continue;
|
|
150
|
+
const key = 'node_modules/' + id + '/index.js'; if (!(key in s)) continue;
|
|
151
|
+
const urlMatch = s[key].match(/import\((".+?")\)/); if (!urlMatch) continue;
|
|
152
|
+
const url = JSON.parse(urlMatch[1]);
|
|
153
|
+
try { const mod = await import(url); const exp = { ...mod }; if (mod.default && typeof mod.default === 'object') Object.assign(exp, mod.default); pkgCache[id] = mod.default && Object.keys(mod).length === 1 ? mod.default : exp; }
|
|
154
|
+
catch (e) { term.write('\x1b[31mfailed to load ' + id + ': ' + e.message + '\x1b[0m\r\n'); }
|
|
155
|
+
}
|
|
156
|
+
}
|
|
97
157
|
|
|
98
|
-
return async function nodeEval(code, filename) {
|
|
158
|
+
return async function nodeEval(code, filename, argv, stdinBuf) {
|
|
159
|
+
const dir = filename ? pathmod.dirname(filename) : ctx.cwd;
|
|
160
|
+
const fpath = filename || '[eval]';
|
|
161
|
+
proc.argv = filename ? ['node', fpath, ...(argv || [])] : ['node'];
|
|
162
|
+
proc.exitCode = 0;
|
|
163
|
+
loadDotEnv();
|
|
164
|
+
globalThis.__fflate = await preloadFflate().catch(() => ({}));
|
|
165
|
+
if (proc.sourceMapsEnabled) { await preloadSourceMap().catch(() => {}); installSourceMapStacks(snapFn); }
|
|
166
|
+
const rtName = switchRuntime(code.startsWith('#!') ? code.slice(0, code.indexOf('\n')) : ''); logRuntimeSwitch(debugReg, debugReg.runtime.active, rtName, fpath);
|
|
167
|
+
if (isTsFile(fpath)) code = await preprocessSource(fpath, code);
|
|
168
|
+
await preloadAsyncPkgs(code, dir);
|
|
169
|
+
const reqFn = makeRequire(dir);
|
|
170
|
+
const scope = { process: proc, console: cons, require: reqFn, Buffer: Buf, __filename: fpath, __dirname: dir, setTimeout, setInterval, clearTimeout, clearInterval, fetch, module: { exports: {} }, exports: {}, global: globalThis, URL, URLSearchParams, TextEncoder, TextDecoder };
|
|
171
|
+
const prevGlobals = { process: globalThis.process, Buffer: globalThis.Buffer, Deno: globalThis.Deno, Bun: globalThis.Bun };
|
|
172
|
+
globalThis.process = proc; globalThis.Buffer = Buf;
|
|
173
|
+
if (rtName === 'deno') globalThis.Deno = denoGlobal; else delete globalThis.Deno;
|
|
174
|
+
if (rtName === 'bun') globalThis.Bun = bunGlobal; else delete globalThis.Bun;
|
|
175
|
+
installCaptureStackTrace(); installPrepareStackTraceHook();
|
|
176
|
+
const unhandledH = e => { e.preventDefault?.(); const err = e.reason || e; term.write('\x1b[31m' + rewriteStack(err, fpath) + '\x1b[0m\r\n'); ctx.lastExitCode = 1; };
|
|
177
|
+
window.addEventListener('unhandledrejection', unhandledH);
|
|
99
178
|
try {
|
|
100
|
-
const
|
|
101
|
-
const vals = Object.values(scope);
|
|
179
|
+
if (isEsmCode(code)) { const preamble = '\nconst __filename = ' + JSON.stringify(fpath) + ';\nconst __dirname = ' + JSON.stringify(dir) + ';\n'; const mod = await runEsm(preamble + code, scope); if (mod && !filename) { for (const [k, v] of Object.entries(mod)) if (k !== 'default') cons.log(k + ':', v); } ctx.lastExitCode = proc.exitCode | 0; return; }
|
|
180
|
+
const keys = Object.keys(scope), vals = Object.values(scope);
|
|
102
181
|
const fn = new Function(...keys, 'return (async () => {\n' + code + '\n})()');
|
|
103
|
-
|
|
182
|
+
const pending = fn(...vals);
|
|
183
|
+
if (stdinBuf) queueMicrotask(() => proc.stdin._feed(stdinBuf));
|
|
184
|
+
const result = await pending;
|
|
185
|
+
if (result !== undefined && !filename) cons.log(result);
|
|
186
|
+
ctx.lastExitCode = proc.exitCode | 0;
|
|
104
187
|
} catch (e) {
|
|
105
|
-
|
|
188
|
+
if (e && e.__nodeExit) { ctx.lastExitCode = e.code | 0; return; }
|
|
189
|
+
term.write('\x1b[31m' + rewriteStack(e, fpath) + '\x1b[0m\r\n');
|
|
190
|
+
ctx.lastExitCode = 1;
|
|
191
|
+
} finally {
|
|
192
|
+
window.removeEventListener('unhandledrejection', unhandledH);
|
|
193
|
+
if (prevGlobals.process !== undefined) globalThis.process = prevGlobals.process; else delete globalThis.process;
|
|
194
|
+
if (prevGlobals.Buffer !== undefined) globalThis.Buffer = prevGlobals.Buffer; else delete globalThis.Buffer;
|
|
195
|
+
if (prevGlobals.Deno !== undefined) globalThis.Deno = prevGlobals.Deno; else delete globalThis.Deno;
|
|
196
|
+
if (prevGlobals.Bun !== undefined) globalThis.Bun = prevGlobals.Bun; else delete globalThis.Bun;
|
|
106
197
|
}
|
|
107
198
|
};
|
|
108
199
|
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { NPM_VERSION } from './shell-node-modules.js';
|
|
2
|
+
|
|
3
|
+
const toKey = p => p.replace(/^\//, '');
|
|
4
|
+
const snap = () => window.__debug.idbSnapshot || {};
|
|
5
|
+
const persist = () => window.__debug.idbPersist?.();
|
|
6
|
+
|
|
7
|
+
function resolvePkgJson(cwd, ctx) {
|
|
8
|
+
const path = cwd.replace(/\/$/, '') + '/package.json';
|
|
9
|
+
const raw = snap()[toKey(path)];
|
|
10
|
+
if (!raw) throw new Error('npm: no package.json in ' + cwd);
|
|
11
|
+
try { return { path, data: JSON.parse(raw) }; } catch (e) { throw new Error('npm: invalid package.json: ' + e.message); }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function installOne(pkg, version, term) {
|
|
15
|
+
const spec = version && version !== 'latest' ? pkg + '@' + version.replace(/^[\^~]/, '') : pkg;
|
|
16
|
+
const url = 'https://esm.sh/' + spec + '?bundle&target=es2022';
|
|
17
|
+
term.write(' → ' + spec + '\r\n');
|
|
18
|
+
await import(url);
|
|
19
|
+
const stubPath = 'node_modules/' + pkg + '/index.js';
|
|
20
|
+
snap()[stubPath] = '// esm.sh async stub\nawait import(' + JSON.stringify(url) + ');';
|
|
21
|
+
const meta = { name: pkg, version: version || 'latest', _resolved: url, _from: 'esm.sh' };
|
|
22
|
+
snap()['node_modules/' + pkg + '/package.json'] = JSON.stringify(meta, null, 2);
|
|
23
|
+
persist();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function writePkgJson(pkgPath, data) {
|
|
27
|
+
snap()[toKey(pkgPath)] = JSON.stringify(data, null, 2);
|
|
28
|
+
persist();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function injectNpmEnv(ctx, data, scriptName) {
|
|
32
|
+
const prev = {};
|
|
33
|
+
const set = (k, v) => { prev[k] = ctx.env[k]; ctx.env[k] = v; };
|
|
34
|
+
set('npm_lifecycle_event', scriptName);
|
|
35
|
+
set('npm_package_name', data.name || '');
|
|
36
|
+
set('npm_package_version', data.version || '');
|
|
37
|
+
set('npm_config_user_agent', 'npm/' + NPM_VERSION + ' node/v23.10.0');
|
|
38
|
+
set('NODE_ENV', ctx.env.NODE_ENV || 'development');
|
|
39
|
+
for (const [k, v] of Object.entries(data.scripts || {})) set('npm_package_scripts_' + k.replace(/[^a-z0-9_]/gi, '_'), v);
|
|
40
|
+
return () => { for (const k of Object.keys(prev)) { if (prev[k] === undefined) delete ctx.env[k]; else ctx.env[k] = prev[k]; } };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function makeNpm(ctx) {
|
|
44
|
+
const w = s => ctx.term.write(s);
|
|
45
|
+
const wl = s => w(s + '\r\n');
|
|
46
|
+
|
|
47
|
+
async function cmdInstall(args) {
|
|
48
|
+
const saveDev = args.includes('--save-dev') || args.includes('-D');
|
|
49
|
+
const noSave = args.includes('--no-save');
|
|
50
|
+
const pkgs = args.filter(a => !a.startsWith('-'));
|
|
51
|
+
if (!pkgs.length) {
|
|
52
|
+
const { data } = resolvePkgJson(ctx.cwd, ctx);
|
|
53
|
+
const all = { ...(data.dependencies || {}), ...(data.devDependencies || {}), ...(data.peerDependencies || {}) };
|
|
54
|
+
const entries = Object.entries(all);
|
|
55
|
+
if (!entries.length) { wl('up to date, 0 packages'); return; }
|
|
56
|
+
wl('installing ' + entries.length + ' packages from package.json');
|
|
57
|
+
for (const [name, ver] of entries) await installOne(name, ver, ctx.term);
|
|
58
|
+
wl('added ' + entries.length + ' package' + (entries.length === 1 ? '' : 's'));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
for (const spec of pkgs) {
|
|
62
|
+
const m = spec.match(/^(@?[^@]+?)(?:@(.+))?$/);
|
|
63
|
+
const [, name, version] = m;
|
|
64
|
+
await installOne(name, version, ctx.term);
|
|
65
|
+
}
|
|
66
|
+
if (!noSave) {
|
|
67
|
+
const { path: pkgPath, data } = resolvePkgJson(ctx.cwd, ctx);
|
|
68
|
+
const target = saveDev ? 'devDependencies' : 'dependencies';
|
|
69
|
+
data[target] = data[target] || {};
|
|
70
|
+
for (const spec of pkgs) {
|
|
71
|
+
const m = spec.match(/^(@?[^@]+?)(?:@(.+))?$/);
|
|
72
|
+
data[target][m[1]] = m[2] || 'latest';
|
|
73
|
+
}
|
|
74
|
+
writePkgJson(pkgPath, data);
|
|
75
|
+
}
|
|
76
|
+
wl('added ' + pkgs.length + ' package' + (pkgs.length === 1 ? '' : 's'));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function cmdUninstall(args) {
|
|
80
|
+
const pkgs = args.filter(a => !a.startsWith('-'));
|
|
81
|
+
if (!pkgs.length) throw new Error('npm uninstall <pkg>');
|
|
82
|
+
for (const pkg of pkgs) {
|
|
83
|
+
const s = snap();
|
|
84
|
+
let n = 0;
|
|
85
|
+
for (const k of Object.keys(s)) {
|
|
86
|
+
if (k === 'node_modules/' + pkg + '/index.js' || k.startsWith('node_modules/' + pkg + '/')) { delete s[k]; n++; }
|
|
87
|
+
}
|
|
88
|
+
wl(n ? 'removed ' + pkg : pkg + ' not installed');
|
|
89
|
+
}
|
|
90
|
+
const { path: pkgPath, data } = resolvePkgJson(ctx.cwd, ctx);
|
|
91
|
+
for (const pkg of pkgs) { delete data.dependencies?.[pkg]; delete data.devDependencies?.[pkg]; }
|
|
92
|
+
writePkgJson(pkgPath, data);
|
|
93
|
+
persist();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function cmdList(args) {
|
|
97
|
+
const filter = args.find(a => !a.startsWith('-'));
|
|
98
|
+
const s = snap();
|
|
99
|
+
const installed = Object.keys(s).filter(k => k.match(/^node_modules\/[^/]+\/package\.json$/) || k.match(/^node_modules\/@[^/]+\/[^/]+\/package\.json$/));
|
|
100
|
+
try {
|
|
101
|
+
const { data } = resolvePkgJson(ctx.cwd, ctx);
|
|
102
|
+
wl(data.name + '@' + (data.version || '1.0.0') + ' ' + ctx.cwd);
|
|
103
|
+
} catch { wl('(no package.json)'); }
|
|
104
|
+
for (const k of installed) {
|
|
105
|
+
const name = k.replace(/^node_modules\//, '').replace(/\/package\.json$/, '');
|
|
106
|
+
if (filter && !name.includes(filter)) continue;
|
|
107
|
+
const pj = JSON.parse(s[k]);
|
|
108
|
+
wl('├── ' + name + '@' + (pj.version || 'latest'));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function cmdRun(args) {
|
|
113
|
+
const [scriptName, ...rest] = args;
|
|
114
|
+
const { data } = resolvePkgJson(ctx.cwd, ctx);
|
|
115
|
+
if (!scriptName) {
|
|
116
|
+
wl('Lifecycle scripts included in ' + (data.name || 'package') + '@' + (data.version || '') + ':');
|
|
117
|
+
for (const [n, s] of Object.entries(data.scripts || {})) wl(' ' + n + '\r\n ' + s);
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
const cmd = data.scripts?.[scriptName];
|
|
121
|
+
if (!cmd) throw new Error('npm: Missing script: "' + scriptName + '"');
|
|
122
|
+
const pre = data.scripts?.['pre' + scriptName];
|
|
123
|
+
const post = data.scripts?.['post' + scriptName];
|
|
124
|
+
const restore = injectNpmEnv(ctx, data, scriptName);
|
|
125
|
+
try {
|
|
126
|
+
const chain = [];
|
|
127
|
+
if (pre) chain.push({ name: 'pre' + scriptName, cmd: pre });
|
|
128
|
+
chain.push({ name: scriptName, cmd: cmd + (rest.length ? ' ' + rest.join(' ') : '') });
|
|
129
|
+
if (post) chain.push({ name: 'post' + scriptName, cmd: post });
|
|
130
|
+
return { runInShell: null, npmChain: chain, pkgName: data.name || 'package', pkgVersion: data.version || '' };
|
|
131
|
+
} finally { queueMicrotask(restore); }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function cmdInit(args) {
|
|
135
|
+
const yes = args.includes('-y') || args.includes('--yes');
|
|
136
|
+
if (!yes) { wl('npm init -y — use -y for non-interactive'); return; }
|
|
137
|
+
const pj = { name: ctx.cwd.split('/').filter(Boolean).pop() || 'project', version: '1.0.0', main: 'index.js', scripts: { start: 'node index.js', test: 'echo "Error: no test specified" && exit 1' }, dependencies: {} };
|
|
138
|
+
writePkgJson(ctx.cwd.replace(/\/$/, '') + '/package.json', pj);
|
|
139
|
+
wl('Wrote to ' + ctx.cwd.replace(/\/$/, '') + '/package.json');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function cmdExec(args) {
|
|
143
|
+
const pkg = args[0];
|
|
144
|
+
if (!pkg) throw new Error('npx: package required');
|
|
145
|
+
const s = snap();
|
|
146
|
+
if (!s['node_modules/' + pkg + '/index.js']) await installOne(pkg, null, ctx.term);
|
|
147
|
+
const binPath = 'node_modules/.bin/' + pkg;
|
|
148
|
+
if (s[binPath]) return { runInShell: 'node /' + binPath + (args.length > 1 ? ' ' + args.slice(1).join(' ') : '') };
|
|
149
|
+
return { runInShell: 'node -e "require(\'' + pkg + '\')"' };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return async function npm(args) {
|
|
153
|
+
const sub = args[0];
|
|
154
|
+
const rest = args.slice(1);
|
|
155
|
+
if (sub === 'install' || sub === 'i' || sub === 'add') return cmdInstall(rest);
|
|
156
|
+
if (sub === 'uninstall' || sub === 'remove' || sub === 'rm') return cmdUninstall(rest);
|
|
157
|
+
if (sub === 'ls' || sub === 'list') return cmdList(rest);
|
|
158
|
+
if (sub === 'run' || sub === 'run-script') return cmdRun(rest);
|
|
159
|
+
if (sub === 'start') return cmdRun(['start', ...rest]);
|
|
160
|
+
if (sub === 'test' || sub === 't') return cmdRun(['test', ...rest]);
|
|
161
|
+
if (sub === 'init' || sub === 'create') return cmdInit(rest);
|
|
162
|
+
if (sub === 'exec' || sub === 'x') return cmdExec(rest);
|
|
163
|
+
if (sub === '--version' || sub === '-v') { wl(NPM_VERSION); return; }
|
|
164
|
+
if (sub === 'prefix') { wl(ctx.cwd); return; }
|
|
165
|
+
if (sub === 'root') { wl(ctx.cwd.replace(/\/$/, '') + '/node_modules'); return; }
|
|
166
|
+
if (sub === 'view' || sub === 'info' || sub === 'show') { const p = rest[0]; wl(p + ' — use esm.sh to inspect'); return; }
|
|
167
|
+
throw new Error('npm: unknown command "' + sub + '"');
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function makeNpx(npmCmd) {
|
|
172
|
+
return args => npmCmd(['exec', ...args]);
|
|
173
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
export function tokenize(line) {
|
|
2
|
+
const tokens = [];
|
|
3
|
+
let cur = '';
|
|
4
|
+
let quote = null;
|
|
5
|
+
let escape = false;
|
|
6
|
+
for (let i = 0; i < line.length; i++) {
|
|
7
|
+
const c = line[i];
|
|
8
|
+
if (escape) {
|
|
9
|
+
if (quote === '"' && !'"\\`$'.includes(c)) cur += '\\';
|
|
10
|
+
cur += c; escape = false; continue;
|
|
11
|
+
}
|
|
12
|
+
if (c === '\\' && quote !== "'") { escape = true; continue; }
|
|
13
|
+
if (quote) {
|
|
14
|
+
if (c === quote) { quote = null; continue; }
|
|
15
|
+
cur += c;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (c === '"' || c === "'") { quote = c; continue; }
|
|
19
|
+
if (/\s/.test(c)) {
|
|
20
|
+
if (cur) { tokens.push(cur); cur = ''; }
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
cur += c;
|
|
24
|
+
}
|
|
25
|
+
if (cur) tokens.push(cur);
|
|
26
|
+
return tokens;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function expand(token, env, lastExitCode, argv) {
|
|
30
|
+
return token.replace(/\$\(([^)]+)\)|\$\{?(\?|#|@|[0-9]|[A-Za-z_][A-Za-z0-9_]*)\}?/g, (match, sub, name) => {
|
|
31
|
+
if (sub) return match;
|
|
32
|
+
if (name === '?') return String(lastExitCode ?? 0);
|
|
33
|
+
if (name === '#') return String((argv || []).length);
|
|
34
|
+
if (name === '@') return (argv || []).join(' ');
|
|
35
|
+
if (name === '0') return (argv || [])[0] || '';
|
|
36
|
+
if (/^[1-9]$/.test(name)) return (argv || [])[parseInt(name)] || '';
|
|
37
|
+
return env[name] ?? '';
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function expandCmdSub(token, env, lastExitCode, runCapture, argv) {
|
|
42
|
+
if (!token.includes('$(')) return expand(token, env, lastExitCode, argv);
|
|
43
|
+
return token.replace(/\$\(([^)]+)\)/g, (_, cmd) => runCapture ? runCapture(cmd) : '');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function parsePipeline(line) {
|
|
47
|
+
const chunks = splitTopLevel(line, ['&&', '||', ';']);
|
|
48
|
+
return chunks;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function splitTopLevel(line, seps) {
|
|
52
|
+
const cmds = [];
|
|
53
|
+
const separators = [];
|
|
54
|
+
let cur = '';
|
|
55
|
+
let quote = null;
|
|
56
|
+
let escape = false;
|
|
57
|
+
for (let i = 0; i < line.length; i++) {
|
|
58
|
+
const c = line[i];
|
|
59
|
+
if (escape) { cur += c; escape = false; continue; }
|
|
60
|
+
if (c === '\\' && quote !== "'") { cur += c; escape = true; continue; }
|
|
61
|
+
if (quote) {
|
|
62
|
+
if (c === quote) quote = null;
|
|
63
|
+
cur += c;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (c === '"' || c === "'") { quote = c; cur += c; continue; }
|
|
67
|
+
let matched = null;
|
|
68
|
+
for (const sep of seps) if (line.startsWith(sep, i)) { matched = sep; break; }
|
|
69
|
+
if (matched) {
|
|
70
|
+
cmds.push(cur.trim());
|
|
71
|
+
separators.push(matched);
|
|
72
|
+
cur = '';
|
|
73
|
+
i += matched.length - 1;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
cur += c;
|
|
77
|
+
}
|
|
78
|
+
if (cur.trim()) cmds.push(cur.trim());
|
|
79
|
+
return cmds.map((cmd, i) => ({ cmd, sep: separators[i - 1] || null }));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function parseRedirects(tokens) {
|
|
83
|
+
const out = { args: [], stdout: null, stdoutAppend: false, stdin: null };
|
|
84
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
85
|
+
const t = tokens[i];
|
|
86
|
+
if (t === '>' || t === '>>') { out.stdout = tokens[++i]; out.stdoutAppend = t === '>>'; continue; }
|
|
87
|
+
if (t === '<') { out.stdin = tokens[++i]; continue; }
|
|
88
|
+
out.args.push(t);
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function parsePipes(line) {
|
|
94
|
+
return splitTopLevel(line, ['|']).map(p => p.cmd);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function globToRe(pattern) {
|
|
98
|
+
let re = '';
|
|
99
|
+
let i = 0;
|
|
100
|
+
while (i < pattern.length) {
|
|
101
|
+
const c = pattern[i];
|
|
102
|
+
if (c === '[') {
|
|
103
|
+
const close = pattern.indexOf(']', i + 1);
|
|
104
|
+
if (close < 0) { re += '\\['; i++; continue; }
|
|
105
|
+
let cls = pattern.slice(i + 1, close);
|
|
106
|
+
if (cls[0] === '!') cls = '^' + cls.slice(1);
|
|
107
|
+
re += '[' + cls + ']';
|
|
108
|
+
i = close + 1; continue;
|
|
109
|
+
}
|
|
110
|
+
if (c === '*') { re += pattern[i + 1] === '*' ? ((i += 2), '.*') : ((i++), '[^/]*'); continue; }
|
|
111
|
+
if (c === '?') { re += '[^/]'; i++; continue; }
|
|
112
|
+
if ('-{}()+.,\\^$|#'.includes(c)) { re += '\\' + c; i++; continue; }
|
|
113
|
+
re += c; i++;
|
|
114
|
+
}
|
|
115
|
+
return new RegExp('^' + re + '$');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function parseCommand(line, env) {
|
|
119
|
+
const raw = tokenize(line);
|
|
120
|
+
const expanded = raw.map(t => expand(t, env));
|
|
121
|
+
return parseRedirects(expanded);
|
|
122
|
+
}
|