numbl 0.4.11 → 0.4.13
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/dist-browser/browser.js +1 -1
- package/dist-browser/numbl-core/fileIOAdapter.d.ts +4 -0
- package/dist-browser/numbl-core/native/lapack-bridge.d.ts +1 -1
- package/dist-browser/numbl-core/native/wasm-lapack-bridge.d.ts +114 -0
- package/dist-browser/numbl-core/native/wasm-lapack-browser.d.ts +34 -0
- package/dist-browser/numbl-core/runtime/datetime-format.d.ts +10 -0
- package/dist-browser/numbl-core/runtime/display.d.ts +5 -1
- package/dist-browser/numbl-core/runtime/index.d.ts +1 -1
- package/dist-browser/numbl-core/version.d.ts +1 -1
- package/dist-browser/vfs/BrowserFileIOAdapter.d.ts +1 -0
- package/dist-cli/cli.js +434 -131
- package/dist-lib/cli-fileio.d.ts +62 -0
- package/dist-lib/cli-scan.d.ts +6 -0
- package/dist-lib/cli-system.d.ts +13 -0
- package/dist-lib/lib.js +368 -73
- package/dist-lib/node.d.ts +30 -0
- package/dist-lib/node.js +829 -0
- package/dist-lib/numbl-core/fileIOAdapter.d.ts +4 -0
- package/dist-lib/numbl-core/native/lapack-bridge.d.ts +1 -1
- package/dist-lib/numbl-core/native/wasm-lapack-bridge.d.ts +114 -0
- package/dist-lib/numbl-core/native/wasm-lapack-browser.d.ts +34 -0
- package/dist-lib/numbl-core/runtime/datetime-format.d.ts +10 -0
- package/dist-lib/numbl-core/runtime/display.d.ts +5 -1
- package/dist-lib/numbl-core/runtime/index.d.ts +1 -1
- package/dist-lib/numbl-core/version.d.ts +1 -1
- package/dist-lib/vfs/BrowserFileIOAdapter.d.ts +1 -0
- package/dist-site-viewer/assets/{index-fhKbvuOM.js → index-C2ItFYSf.js} +322 -322
- package/dist-site-viewer/assets/{numbl-worker-BFP9xj5e.js → numbl-worker-CtfdLxJh.js} +114 -102
- package/dist-site-viewer/index.html +1 -1
- package/package.json +6 -2
package/dist-lib/node.js
ADDED
|
@@ -0,0 +1,829 @@
|
|
|
1
|
+
// src/cli-fileio.ts
|
|
2
|
+
import {
|
|
3
|
+
openSync,
|
|
4
|
+
closeSync,
|
|
5
|
+
readSync,
|
|
6
|
+
writeSync,
|
|
7
|
+
readFileSync as readFileSync2,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
statSync as statSync2,
|
|
10
|
+
fstatSync,
|
|
11
|
+
mkdirSync,
|
|
12
|
+
unlinkSync,
|
|
13
|
+
readdirSync as readdirSync2,
|
|
14
|
+
rmSync,
|
|
15
|
+
rmdirSync,
|
|
16
|
+
renameSync,
|
|
17
|
+
chmodSync,
|
|
18
|
+
realpathSync
|
|
19
|
+
} from "fs";
|
|
20
|
+
import { unzipSync, zipSync } from "fflate";
|
|
21
|
+
import { execFileSync } from "child_process";
|
|
22
|
+
import { homedir, tmpdir } from "os";
|
|
23
|
+
import { join as join2, resolve, relative, dirname, basename } from "path";
|
|
24
|
+
|
|
25
|
+
// src/cli-scan.ts
|
|
26
|
+
import { readFileSync, readdirSync, statSync } from "fs";
|
|
27
|
+
import { join } from "path";
|
|
28
|
+
function scanMFiles(dirPath, excludeFile) {
|
|
29
|
+
const files = [];
|
|
30
|
+
let entries;
|
|
31
|
+
try {
|
|
32
|
+
entries = readdirSync(dirPath);
|
|
33
|
+
} catch {
|
|
34
|
+
return files;
|
|
35
|
+
}
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
const fullPath = join(dirPath, entry);
|
|
38
|
+
if (excludeFile && fullPath === excludeFile) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const stat = statSync(fullPath);
|
|
43
|
+
if (stat.isDirectory()) {
|
|
44
|
+
if (entry.startsWith("@") || entry.startsWith("+") || entry === "private") {
|
|
45
|
+
files.push(...scanMFiles(fullPath, excludeFile));
|
|
46
|
+
}
|
|
47
|
+
} else if (stat.isFile() && (entry.endsWith(".m") || entry.endsWith(".numbl.js"))) {
|
|
48
|
+
const source = readFileSync(fullPath, "utf-8");
|
|
49
|
+
files.push({
|
|
50
|
+
name: fullPath,
|
|
51
|
+
source
|
|
52
|
+
});
|
|
53
|
+
} else if (stat.isFile() && entry.endsWith(".wasm")) {
|
|
54
|
+
const data = readFileSync(fullPath);
|
|
55
|
+
files.push({
|
|
56
|
+
name: fullPath,
|
|
57
|
+
source: "",
|
|
58
|
+
data: new Uint8Array(data)
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
} catch (err) {
|
|
62
|
+
console.warn(
|
|
63
|
+
`Warning: could not read ${fullPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return files;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/cli-fileio.ts
|
|
71
|
+
function expandTilde(filepath) {
|
|
72
|
+
if (filepath.startsWith("~/")) {
|
|
73
|
+
return join2(homedir(), filepath.slice(2));
|
|
74
|
+
}
|
|
75
|
+
return filepath;
|
|
76
|
+
}
|
|
77
|
+
function copyDirRecursive(src, dst) {
|
|
78
|
+
mkdirSync(dst, { recursive: true });
|
|
79
|
+
for (const entry of readdirSync2(src, { withFileTypes: true })) {
|
|
80
|
+
const s = join2(src, entry.name);
|
|
81
|
+
const d = join2(dst, entry.name);
|
|
82
|
+
if (entry.isDirectory()) {
|
|
83
|
+
copyDirRecursive(s, d);
|
|
84
|
+
} else {
|
|
85
|
+
writeFileSync(d, readFileSync2(s));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function permissionToFlags(permission) {
|
|
90
|
+
switch (permission) {
|
|
91
|
+
case "r":
|
|
92
|
+
return "r";
|
|
93
|
+
case "w":
|
|
94
|
+
return "w";
|
|
95
|
+
case "a":
|
|
96
|
+
return "a";
|
|
97
|
+
case "r+":
|
|
98
|
+
return "r+";
|
|
99
|
+
case "w+":
|
|
100
|
+
return "w+";
|
|
101
|
+
case "a+":
|
|
102
|
+
return "a+";
|
|
103
|
+
default:
|
|
104
|
+
return permission;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
var READ_CHUNK_SIZE = 8192;
|
|
108
|
+
function buildFetchOptions(options) {
|
|
109
|
+
const init = {};
|
|
110
|
+
if (options?.requestMethod) {
|
|
111
|
+
init.method = options.requestMethod.toUpperCase();
|
|
112
|
+
}
|
|
113
|
+
const headers = {};
|
|
114
|
+
if (options?.username && options?.password) {
|
|
115
|
+
headers["Authorization"] = "Basic " + Buffer.from(`${options.username}:${options.password}`).toString("base64");
|
|
116
|
+
}
|
|
117
|
+
if (options?.keyName && options?.keyValue) {
|
|
118
|
+
headers[options.keyName] = options.keyValue;
|
|
119
|
+
}
|
|
120
|
+
if (Object.keys(headers).length > 0) init.headers = headers;
|
|
121
|
+
return init;
|
|
122
|
+
}
|
|
123
|
+
var NodeFileIOAdapter = class {
|
|
124
|
+
nextFid = 3;
|
|
125
|
+
// 0=stdin, 1=stdout, 2=stderr reserved
|
|
126
|
+
openFiles = /* @__PURE__ */ new Map();
|
|
127
|
+
fopen(filename, permission) {
|
|
128
|
+
try {
|
|
129
|
+
const flags = permissionToFlags(permission);
|
|
130
|
+
const fd = openSync(expandTilde(filename), flags);
|
|
131
|
+
const fid = this.nextFid++;
|
|
132
|
+
this.openFiles.set(fid, {
|
|
133
|
+
fd,
|
|
134
|
+
permission,
|
|
135
|
+
lastError: "",
|
|
136
|
+
buffer: "",
|
|
137
|
+
eof: false,
|
|
138
|
+
pos: 0
|
|
139
|
+
});
|
|
140
|
+
return fid;
|
|
141
|
+
} catch (e) {
|
|
142
|
+
return -1;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
fclose(fidOrAll) {
|
|
146
|
+
if (fidOrAll === "all") {
|
|
147
|
+
for (const [, entry2] of this.openFiles) {
|
|
148
|
+
try {
|
|
149
|
+
closeSync(entry2.fd);
|
|
150
|
+
} catch {
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
this.openFiles.clear();
|
|
154
|
+
return 0;
|
|
155
|
+
}
|
|
156
|
+
const entry = this.openFiles.get(fidOrAll);
|
|
157
|
+
if (!entry) return -1;
|
|
158
|
+
try {
|
|
159
|
+
closeSync(entry.fd);
|
|
160
|
+
} catch {
|
|
161
|
+
return -1;
|
|
162
|
+
}
|
|
163
|
+
this.openFiles.delete(fidOrAll);
|
|
164
|
+
return 0;
|
|
165
|
+
}
|
|
166
|
+
fgetl(fid) {
|
|
167
|
+
const entry = this.getEntry(fid);
|
|
168
|
+
if (!entry) return -1;
|
|
169
|
+
return this.readLine(entry, false);
|
|
170
|
+
}
|
|
171
|
+
fgets(fid) {
|
|
172
|
+
const entry = this.getEntry(fid);
|
|
173
|
+
if (!entry) return -1;
|
|
174
|
+
return this.readLine(entry, true);
|
|
175
|
+
}
|
|
176
|
+
fileread(filename) {
|
|
177
|
+
return readFileSync2(expandTilde(filename), "utf-8");
|
|
178
|
+
}
|
|
179
|
+
feof(fid) {
|
|
180
|
+
const entry = this.getEntry(fid);
|
|
181
|
+
if (!entry) return 1;
|
|
182
|
+
if (entry.buffer.length > 0) return 0;
|
|
183
|
+
if (entry.eof) return 1;
|
|
184
|
+
const buf = Buffer.alloc(1);
|
|
185
|
+
try {
|
|
186
|
+
const bytesRead = readSync(entry.fd, buf, 0, 1, entry.pos);
|
|
187
|
+
if (bytesRead === 0) {
|
|
188
|
+
entry.eof = true;
|
|
189
|
+
return 1;
|
|
190
|
+
}
|
|
191
|
+
entry.buffer = buf.toString("utf-8", 0, bytesRead);
|
|
192
|
+
return 0;
|
|
193
|
+
} catch {
|
|
194
|
+
entry.eof = true;
|
|
195
|
+
return 1;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
ferror(fid) {
|
|
199
|
+
const entry = this.getEntry(fid);
|
|
200
|
+
if (!entry) return "Invalid file identifier";
|
|
201
|
+
return entry.lastError;
|
|
202
|
+
}
|
|
203
|
+
fwrite(fid, text) {
|
|
204
|
+
const entry = this.getEntry(fid);
|
|
205
|
+
if (!entry) throw new Error(`Invalid file identifier: ${fid}`);
|
|
206
|
+
try {
|
|
207
|
+
writeSync(entry.fd, text);
|
|
208
|
+
entry.lastError = "";
|
|
209
|
+
} catch (e) {
|
|
210
|
+
entry.lastError = e instanceof Error ? e.message : String(e);
|
|
211
|
+
throw e;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
freadBytes(fid, count) {
|
|
215
|
+
const entry = this.getEntry(fid);
|
|
216
|
+
if (!entry) throw new Error(`Invalid file identifier: ${fid}`);
|
|
217
|
+
if (entry.buffer.length > 0) {
|
|
218
|
+
entry.buffer = "";
|
|
219
|
+
}
|
|
220
|
+
const buf = Buffer.alloc(count);
|
|
221
|
+
try {
|
|
222
|
+
const bytesRead = readSync(entry.fd, buf, 0, count, entry.pos);
|
|
223
|
+
entry.pos += bytesRead;
|
|
224
|
+
if (bytesRead === 0) entry.eof = true;
|
|
225
|
+
else if (bytesRead < count) entry.eof = true;
|
|
226
|
+
entry.lastError = "";
|
|
227
|
+
return new Uint8Array(buf.buffer, buf.byteOffset, bytesRead);
|
|
228
|
+
} catch (e) {
|
|
229
|
+
entry.lastError = e instanceof Error ? e.message : String(e);
|
|
230
|
+
entry.eof = true;
|
|
231
|
+
return new Uint8Array(0);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
fwriteBytes(fid, data) {
|
|
235
|
+
const entry = this.getEntry(fid);
|
|
236
|
+
if (!entry) throw new Error(`Invalid file identifier: ${fid}`);
|
|
237
|
+
try {
|
|
238
|
+
const written = writeSync(entry.fd, data, 0, data.length, entry.pos);
|
|
239
|
+
entry.pos += written;
|
|
240
|
+
entry.lastError = "";
|
|
241
|
+
return written;
|
|
242
|
+
} catch (e) {
|
|
243
|
+
entry.lastError = e instanceof Error ? e.message : String(e);
|
|
244
|
+
throw e;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
fseek(fid, offset, origin) {
|
|
248
|
+
const entry = this.getEntry(fid);
|
|
249
|
+
if (!entry) return -1;
|
|
250
|
+
entry.buffer = "";
|
|
251
|
+
entry.eof = false;
|
|
252
|
+
try {
|
|
253
|
+
if (origin === -1) {
|
|
254
|
+
entry.pos = offset;
|
|
255
|
+
} else if (origin === 0) {
|
|
256
|
+
entry.pos += offset;
|
|
257
|
+
} else {
|
|
258
|
+
const size = fstatSync(entry.fd).size;
|
|
259
|
+
entry.pos = size + offset;
|
|
260
|
+
}
|
|
261
|
+
return 0;
|
|
262
|
+
} catch {
|
|
263
|
+
return -1;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
ftell(fid) {
|
|
267
|
+
const entry = this.getEntry(fid);
|
|
268
|
+
if (!entry) return -1;
|
|
269
|
+
return entry.pos;
|
|
270
|
+
}
|
|
271
|
+
scanDirectory(dirPath) {
|
|
272
|
+
return scanMFiles(expandTilde(dirPath));
|
|
273
|
+
}
|
|
274
|
+
resolvePath(dirPath) {
|
|
275
|
+
return resolve(process.cwd(), expandTilde(dirPath));
|
|
276
|
+
}
|
|
277
|
+
existsPath(path) {
|
|
278
|
+
try {
|
|
279
|
+
const s = statSync2(expandTilde(path));
|
|
280
|
+
return s.isDirectory() ? "dir" : "file";
|
|
281
|
+
} catch {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
mkdir(dirPath) {
|
|
286
|
+
try {
|
|
287
|
+
mkdirSync(expandTilde(dirPath), { recursive: true });
|
|
288
|
+
return true;
|
|
289
|
+
} catch {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
tempdir() {
|
|
294
|
+
return tmpdir();
|
|
295
|
+
}
|
|
296
|
+
userpath() {
|
|
297
|
+
return join2(homedir(), ".numbl");
|
|
298
|
+
}
|
|
299
|
+
websave(url, filename, options) {
|
|
300
|
+
const dest = expandTilde(filename);
|
|
301
|
+
const timeoutMs = options?.timeout ? Math.round(options.timeout * 1e3) : 3e4;
|
|
302
|
+
const fetchOpts = buildFetchOptions(options);
|
|
303
|
+
const script = `
|
|
304
|
+
const ac = new AbortController();
|
|
305
|
+
const t = setTimeout(() => ac.abort(), ${timeoutMs});
|
|
306
|
+
fetch(${JSON.stringify(url)}, { ...${JSON.stringify(fetchOpts)}, signal: ac.signal })
|
|
307
|
+
.then(r => { clearTimeout(t); if (!r.ok) throw new Error('HTTP ' + r.status); return r.arrayBuffer(); })
|
|
308
|
+
.then(buf => require('fs').writeFileSync(${JSON.stringify(dest)}, Buffer.from(buf)))
|
|
309
|
+
.catch(e => { process.stderr.write(e.message + '\\n'); process.exit(1); });
|
|
310
|
+
`;
|
|
311
|
+
try {
|
|
312
|
+
execFileSync(process.execPath, ["-e", script], {
|
|
313
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
314
|
+
timeout: timeoutMs + 5e3
|
|
315
|
+
});
|
|
316
|
+
} catch (e) {
|
|
317
|
+
const msg = e instanceof Error && "stderr" in e ? e.stderr.toString().trim() : String(e);
|
|
318
|
+
throw new Error(`websave: failed to download ${url}: ${msg}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
webread(url, options) {
|
|
322
|
+
const timeoutMs = options?.timeout ? Math.round(options.timeout * 1e3) : 3e4;
|
|
323
|
+
const fetchOpts = buildFetchOptions(options);
|
|
324
|
+
const script = `
|
|
325
|
+
const ac = new AbortController();
|
|
326
|
+
const t = setTimeout(() => ac.abort(), ${timeoutMs});
|
|
327
|
+
fetch(${JSON.stringify(url)}, { ...${JSON.stringify(fetchOpts)}, signal: ac.signal })
|
|
328
|
+
.then(r => { clearTimeout(t); if (!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })
|
|
329
|
+
.then(t => process.stdout.write(t))
|
|
330
|
+
.catch(e => { process.stderr.write(e.message + '\\n'); process.exit(1); });
|
|
331
|
+
`;
|
|
332
|
+
try {
|
|
333
|
+
const result = execFileSync(process.execPath, ["-e", script], {
|
|
334
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
335
|
+
timeout: timeoutMs + 5e3,
|
|
336
|
+
maxBuffer: 50 * 1024 * 1024
|
|
337
|
+
});
|
|
338
|
+
return result.toString();
|
|
339
|
+
} catch (e) {
|
|
340
|
+
const msg = e instanceof Error && "stderr" in e ? e.stderr.toString().trim() : String(e);
|
|
341
|
+
throw new Error(`webread: failed to fetch ${url}: ${msg}`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
rmdir(dirPath, recursive) {
|
|
345
|
+
try {
|
|
346
|
+
const p = expandTilde(dirPath);
|
|
347
|
+
if (recursive) {
|
|
348
|
+
rmSync(p, { recursive: true });
|
|
349
|
+
} else {
|
|
350
|
+
rmdirSync(p);
|
|
351
|
+
}
|
|
352
|
+
return true;
|
|
353
|
+
} catch {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
movefile(source, destination, force) {
|
|
358
|
+
try {
|
|
359
|
+
const src = expandTilde(source);
|
|
360
|
+
const srcStat = statSync2(src);
|
|
361
|
+
let dst = expandTilde(destination);
|
|
362
|
+
let dstType = null;
|
|
363
|
+
try {
|
|
364
|
+
const s = statSync2(dst);
|
|
365
|
+
dstType = s.isDirectory() ? "dir" : "file";
|
|
366
|
+
} catch {
|
|
367
|
+
dstType = null;
|
|
368
|
+
}
|
|
369
|
+
if (dstType === "dir") {
|
|
370
|
+
dst = join2(dst, basename(src));
|
|
371
|
+
try {
|
|
372
|
+
dstType = statSync2(dst).isDirectory() ? "dir" : "file";
|
|
373
|
+
} catch {
|
|
374
|
+
dstType = null;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (dstType === "file" && force) {
|
|
378
|
+
try {
|
|
379
|
+
chmodSync(dst, 438);
|
|
380
|
+
} catch {
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (srcStat.isDirectory() && dstType === "file") {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
const parent = dirname(dst);
|
|
387
|
+
if (parent && parent !== "." && parent !== "/") {
|
|
388
|
+
try {
|
|
389
|
+
mkdirSync(parent, { recursive: true });
|
|
390
|
+
} catch {
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
try {
|
|
394
|
+
renameSync(src, dst);
|
|
395
|
+
return true;
|
|
396
|
+
} catch {
|
|
397
|
+
if (srcStat.isDirectory()) {
|
|
398
|
+
copyDirRecursive(src, dst);
|
|
399
|
+
rmSync(src, { recursive: true });
|
|
400
|
+
} else {
|
|
401
|
+
writeFileSync(dst, readFileSync2(src));
|
|
402
|
+
unlinkSync(src);
|
|
403
|
+
}
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
} catch {
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
copyfile(source, destination, force) {
|
|
411
|
+
try {
|
|
412
|
+
const src = expandTilde(source);
|
|
413
|
+
const srcStat = statSync2(src);
|
|
414
|
+
let dst = expandTilde(destination);
|
|
415
|
+
try {
|
|
416
|
+
if (statSync2(dst).isDirectory()) dst = join2(dst, basename(src));
|
|
417
|
+
} catch {
|
|
418
|
+
}
|
|
419
|
+
if (srcStat.isDirectory()) {
|
|
420
|
+
copyDirRecursive(src, dst);
|
|
421
|
+
return true;
|
|
422
|
+
}
|
|
423
|
+
const parent = dirname(dst);
|
|
424
|
+
if (parent && parent !== "." && parent !== "/") {
|
|
425
|
+
mkdirSync(parent, { recursive: true });
|
|
426
|
+
}
|
|
427
|
+
if (force) {
|
|
428
|
+
try {
|
|
429
|
+
chmodSync(dst, 438);
|
|
430
|
+
} catch {
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
writeFileSync(dst, readFileSync2(src));
|
|
434
|
+
return true;
|
|
435
|
+
} catch {
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
fileattrib(path) {
|
|
440
|
+
try {
|
|
441
|
+
const p = expandTilde(path);
|
|
442
|
+
const st = statSync2(p);
|
|
443
|
+
const mode = st.mode;
|
|
444
|
+
return {
|
|
445
|
+
Name: realpathSync(p),
|
|
446
|
+
directory: st.isDirectory(),
|
|
447
|
+
UserRead: (mode & 256) !== 0,
|
|
448
|
+
UserWrite: (mode & 128) !== 0,
|
|
449
|
+
UserExecute: (mode & 64) !== 0
|
|
450
|
+
};
|
|
451
|
+
} catch {
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
deleteFile(pattern) {
|
|
456
|
+
const p = expandTilde(pattern);
|
|
457
|
+
if (p.includes("*") || p.includes("?")) {
|
|
458
|
+
const lastSep = Math.max(p.lastIndexOf("/"), p.lastIndexOf("\\"));
|
|
459
|
+
const dir = lastSep >= 0 ? p.slice(0, lastSep) : ".";
|
|
460
|
+
const globPat = lastSep >= 0 ? p.slice(lastSep + 1) : p;
|
|
461
|
+
const re = new RegExp(
|
|
462
|
+
"^" + globPat.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
|
|
463
|
+
);
|
|
464
|
+
let entries;
|
|
465
|
+
try {
|
|
466
|
+
entries = readdirSync2(dir);
|
|
467
|
+
} catch {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
for (const entry of entries) {
|
|
471
|
+
if (re.test(entry)) {
|
|
472
|
+
try {
|
|
473
|
+
unlinkSync(join2(dir, entry));
|
|
474
|
+
} catch {
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
} else {
|
|
479
|
+
try {
|
|
480
|
+
unlinkSync(p);
|
|
481
|
+
} catch {
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
unzip(zipfilename, outputfolder) {
|
|
486
|
+
const src = expandTilde(zipfilename);
|
|
487
|
+
const dest = resolve(expandTilde(outputfolder));
|
|
488
|
+
mkdirSync(dest, { recursive: true });
|
|
489
|
+
const buf = readFileSync2(src);
|
|
490
|
+
const files = unzipSync(
|
|
491
|
+
new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength)
|
|
492
|
+
);
|
|
493
|
+
const extracted = [];
|
|
494
|
+
for (const [entryName, data] of Object.entries(files)) {
|
|
495
|
+
if (entryName.endsWith("/")) {
|
|
496
|
+
mkdirSync(join2(dest, entryName), { recursive: true });
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
const outPath = join2(dest, entryName);
|
|
500
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
501
|
+
writeFileSync(outPath, data);
|
|
502
|
+
extracted.push(outPath);
|
|
503
|
+
}
|
|
504
|
+
return extracted;
|
|
505
|
+
}
|
|
506
|
+
zip(zipfilename, filenames, rootDir) {
|
|
507
|
+
const root = resolve(expandTilde(rootDir));
|
|
508
|
+
const dest = resolve(expandTilde(zipfilename));
|
|
509
|
+
const entries = {};
|
|
510
|
+
const names = [];
|
|
511
|
+
const toEntryName = (abs) => {
|
|
512
|
+
const rel = relative(root, abs);
|
|
513
|
+
if (rel.startsWith("..")) return basename(abs);
|
|
514
|
+
return rel.split("\\").join("/");
|
|
515
|
+
};
|
|
516
|
+
const addFile = (abs) => {
|
|
517
|
+
const name = toEntryName(abs);
|
|
518
|
+
const buf = readFileSync2(abs);
|
|
519
|
+
entries[name] = new Uint8Array(
|
|
520
|
+
buf.buffer,
|
|
521
|
+
buf.byteOffset,
|
|
522
|
+
buf.byteLength
|
|
523
|
+
);
|
|
524
|
+
names.push(name);
|
|
525
|
+
};
|
|
526
|
+
const addDir = (absDir) => {
|
|
527
|
+
for (const entry of readdirSync2(absDir)) {
|
|
528
|
+
const abs = join2(absDir, entry);
|
|
529
|
+
let st;
|
|
530
|
+
try {
|
|
531
|
+
st = statSync2(abs);
|
|
532
|
+
} catch {
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
if (st.isDirectory()) addDir(abs);
|
|
536
|
+
else addFile(abs);
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
const addPath = (abs) => {
|
|
540
|
+
const st = statSync2(abs);
|
|
541
|
+
if (st.isDirectory()) addDir(abs);
|
|
542
|
+
else addFile(abs);
|
|
543
|
+
};
|
|
544
|
+
for (const f of filenames) {
|
|
545
|
+
const p = expandTilde(f);
|
|
546
|
+
const abs = resolve(root, p);
|
|
547
|
+
const last = basename(p);
|
|
548
|
+
if (last.includes("*") || last.includes("?")) {
|
|
549
|
+
const dir = dirname(abs);
|
|
550
|
+
const re = new RegExp(
|
|
551
|
+
"^" + last.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
|
|
552
|
+
);
|
|
553
|
+
let dirEntries;
|
|
554
|
+
try {
|
|
555
|
+
dirEntries = readdirSync2(dir);
|
|
556
|
+
} catch {
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
for (const entry of dirEntries) {
|
|
560
|
+
if (re.test(entry)) addPath(join2(dir, entry));
|
|
561
|
+
}
|
|
562
|
+
} else {
|
|
563
|
+
addPath(abs);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
writeFileSync(dest, zipSync(entries));
|
|
567
|
+
return names;
|
|
568
|
+
}
|
|
569
|
+
listDir(dirPath) {
|
|
570
|
+
const p = expandTilde(dirPath);
|
|
571
|
+
if (p.includes("**")) {
|
|
572
|
+
return this.listDirRecursive(p);
|
|
573
|
+
}
|
|
574
|
+
if (p.includes("*") || p.includes("?")) {
|
|
575
|
+
return this.listDirGlob(p);
|
|
576
|
+
}
|
|
577
|
+
try {
|
|
578
|
+
const s = statSync2(p);
|
|
579
|
+
if (s.isDirectory()) {
|
|
580
|
+
const absDir = resolve(p);
|
|
581
|
+
const results = [];
|
|
582
|
+
results.push({
|
|
583
|
+
name: ".",
|
|
584
|
+
folder: absDir,
|
|
585
|
+
bytes: 0,
|
|
586
|
+
isdir: true,
|
|
587
|
+
mtimeMs: s.mtimeMs
|
|
588
|
+
});
|
|
589
|
+
try {
|
|
590
|
+
const parentStat = statSync2(resolve(absDir, ".."));
|
|
591
|
+
results.push({
|
|
592
|
+
name: "..",
|
|
593
|
+
folder: absDir,
|
|
594
|
+
bytes: 0,
|
|
595
|
+
isdir: true,
|
|
596
|
+
mtimeMs: parentStat.mtimeMs
|
|
597
|
+
});
|
|
598
|
+
} catch {
|
|
599
|
+
results.push({
|
|
600
|
+
name: "..",
|
|
601
|
+
folder: absDir,
|
|
602
|
+
bytes: 0,
|
|
603
|
+
isdir: true,
|
|
604
|
+
mtimeMs: 0
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
const entries = readdirSync2(absDir);
|
|
608
|
+
for (const entry of entries) {
|
|
609
|
+
try {
|
|
610
|
+
const es = statSync2(join2(absDir, entry));
|
|
611
|
+
results.push({
|
|
612
|
+
name: entry,
|
|
613
|
+
folder: absDir,
|
|
614
|
+
bytes: es.isDirectory() ? 0 : es.size,
|
|
615
|
+
isdir: es.isDirectory(),
|
|
616
|
+
mtimeMs: es.mtimeMs
|
|
617
|
+
});
|
|
618
|
+
} catch {
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return results;
|
|
622
|
+
} else {
|
|
623
|
+
const absPath = resolve(p);
|
|
624
|
+
return [
|
|
625
|
+
{
|
|
626
|
+
name: basename(absPath),
|
|
627
|
+
folder: dirname(absPath),
|
|
628
|
+
bytes: s.size,
|
|
629
|
+
isdir: false,
|
|
630
|
+
mtimeMs: s.mtimeMs
|
|
631
|
+
}
|
|
632
|
+
];
|
|
633
|
+
}
|
|
634
|
+
} catch {
|
|
635
|
+
return [];
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
listDirGlob(pattern) {
|
|
639
|
+
const lastSep = Math.max(
|
|
640
|
+
pattern.lastIndexOf("/"),
|
|
641
|
+
pattern.lastIndexOf("\\")
|
|
642
|
+
);
|
|
643
|
+
const dir = lastSep >= 0 ? pattern.slice(0, lastSep) : ".";
|
|
644
|
+
const globPat = lastSep >= 0 ? pattern.slice(lastSep + 1) : pattern;
|
|
645
|
+
const re = new RegExp(
|
|
646
|
+
"^" + globPat.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
|
|
647
|
+
);
|
|
648
|
+
const absDir = resolve(dir);
|
|
649
|
+
const results = [];
|
|
650
|
+
let entries;
|
|
651
|
+
try {
|
|
652
|
+
entries = readdirSync2(absDir);
|
|
653
|
+
} catch {
|
|
654
|
+
return [];
|
|
655
|
+
}
|
|
656
|
+
for (const entry of entries) {
|
|
657
|
+
if (re.test(entry)) {
|
|
658
|
+
try {
|
|
659
|
+
const es = statSync2(join2(absDir, entry));
|
|
660
|
+
results.push({
|
|
661
|
+
name: entry,
|
|
662
|
+
folder: absDir,
|
|
663
|
+
bytes: es.isDirectory() ? 0 : es.size,
|
|
664
|
+
isdir: es.isDirectory(),
|
|
665
|
+
mtimeMs: es.mtimeMs
|
|
666
|
+
});
|
|
667
|
+
} catch {
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return results;
|
|
672
|
+
}
|
|
673
|
+
listDirRecursive(pattern) {
|
|
674
|
+
const norm = pattern.replace(/\\/g, "/");
|
|
675
|
+
const idx = norm.indexOf("**");
|
|
676
|
+
let baseDir = norm.slice(0, idx);
|
|
677
|
+
if (baseDir.endsWith("/")) {
|
|
678
|
+
baseDir = baseDir.slice(0, -1);
|
|
679
|
+
}
|
|
680
|
+
if (!baseDir) baseDir = ".";
|
|
681
|
+
const absBase = resolve(baseDir);
|
|
682
|
+
try {
|
|
683
|
+
if (!statSync2(absBase).isDirectory()) return [];
|
|
684
|
+
} catch {
|
|
685
|
+
return [];
|
|
686
|
+
}
|
|
687
|
+
const suffix = norm.slice(idx);
|
|
688
|
+
const DIRS = "\0";
|
|
689
|
+
const ANY = "";
|
|
690
|
+
const re = new RegExp(
|
|
691
|
+
"^" + suffix.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*\//g, DIRS).replace(/\*\*/g, ANY).replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(new RegExp(DIRS, "g"), "(?:.*/)?").replace(new RegExp(ANY, "g"), ".*") + "$"
|
|
692
|
+
);
|
|
693
|
+
const results = [];
|
|
694
|
+
const pushIfMatch = (rel, name, folder, bytes, isdir, mtimeMs) => {
|
|
695
|
+
if (re.test(rel)) results.push({ name, folder, bytes, isdir, mtimeMs });
|
|
696
|
+
};
|
|
697
|
+
const walkDir = (dir, rel) => {
|
|
698
|
+
const relPrefix = rel ? rel + "/" : "";
|
|
699
|
+
let dirMtime = 0;
|
|
700
|
+
try {
|
|
701
|
+
dirMtime = statSync2(dir).mtimeMs;
|
|
702
|
+
} catch {
|
|
703
|
+
}
|
|
704
|
+
pushIfMatch(relPrefix + ".", ".", dir, 0, true, dirMtime);
|
|
705
|
+
let parentMtime = 0;
|
|
706
|
+
try {
|
|
707
|
+
parentMtime = statSync2(resolve(dir, "..")).mtimeMs;
|
|
708
|
+
} catch {
|
|
709
|
+
}
|
|
710
|
+
pushIfMatch(relPrefix + "..", "..", dir, 0, true, parentMtime);
|
|
711
|
+
let entries;
|
|
712
|
+
try {
|
|
713
|
+
entries = readdirSync2(dir);
|
|
714
|
+
} catch {
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
const subdirs = [];
|
|
718
|
+
for (const entry of entries) {
|
|
719
|
+
try {
|
|
720
|
+
const es = statSync2(join2(dir, entry));
|
|
721
|
+
pushIfMatch(
|
|
722
|
+
relPrefix + entry,
|
|
723
|
+
entry,
|
|
724
|
+
dir,
|
|
725
|
+
es.isDirectory() ? 0 : es.size,
|
|
726
|
+
es.isDirectory(),
|
|
727
|
+
es.mtimeMs
|
|
728
|
+
);
|
|
729
|
+
if (es.isDirectory()) subdirs.push(entry);
|
|
730
|
+
} catch {
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
for (const sub of subdirs) {
|
|
734
|
+
walkDir(join2(dir, sub), relPrefix + sub);
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
walkDir(absBase, "");
|
|
738
|
+
return results;
|
|
739
|
+
}
|
|
740
|
+
getEntry(fid) {
|
|
741
|
+
return this.openFiles.get(fid);
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Read one line from the file's buffer, refilling from the fd as needed.
|
|
745
|
+
*
|
|
746
|
+
* All reads are positional so `entry.pos` stays the single source of truth
|
|
747
|
+
* for the logical file position: the buffer always holds the bytes starting
|
|
748
|
+
* at `entry.pos`, and consuming a line advances `entry.pos` by its byte
|
|
749
|
+
* length. This keeps text I/O (fgetl/fgets) coherent with binary I/O and
|
|
750
|
+
* fscanf (freadBytes/fseek/ftell).
|
|
751
|
+
* @param keepNewline - if true, keep the trailing newline (fgets); if false, strip it (fgetl)
|
|
752
|
+
*/
|
|
753
|
+
readLine(entry, keepNewline) {
|
|
754
|
+
while (!entry.eof) {
|
|
755
|
+
const nlIdx2 = entry.buffer.indexOf("\n");
|
|
756
|
+
if (nlIdx2 !== -1) break;
|
|
757
|
+
const buf = Buffer.alloc(READ_CHUNK_SIZE);
|
|
758
|
+
let bytesRead;
|
|
759
|
+
try {
|
|
760
|
+
bytesRead = readSync(
|
|
761
|
+
entry.fd,
|
|
762
|
+
buf,
|
|
763
|
+
0,
|
|
764
|
+
READ_CHUNK_SIZE,
|
|
765
|
+
entry.pos + Buffer.byteLength(entry.buffer, "utf-8")
|
|
766
|
+
);
|
|
767
|
+
entry.lastError = "";
|
|
768
|
+
} catch (e) {
|
|
769
|
+
entry.lastError = e instanceof Error ? e.message : String(e);
|
|
770
|
+
entry.eof = true;
|
|
771
|
+
break;
|
|
772
|
+
}
|
|
773
|
+
if (bytesRead === 0) {
|
|
774
|
+
entry.eof = true;
|
|
775
|
+
break;
|
|
776
|
+
}
|
|
777
|
+
entry.buffer += buf.toString("utf-8", 0, bytesRead);
|
|
778
|
+
}
|
|
779
|
+
if (entry.buffer.length === 0) {
|
|
780
|
+
return -1;
|
|
781
|
+
}
|
|
782
|
+
const nlIdx = entry.buffer.indexOf("\n");
|
|
783
|
+
if (nlIdx !== -1) {
|
|
784
|
+
const consumed = entry.buffer.slice(0, nlIdx + 1);
|
|
785
|
+
const line2 = keepNewline ? consumed : entry.buffer.slice(0, nlIdx);
|
|
786
|
+
entry.buffer = entry.buffer.slice(nlIdx + 1);
|
|
787
|
+
entry.pos += Buffer.byteLength(consumed, "utf-8");
|
|
788
|
+
return line2;
|
|
789
|
+
}
|
|
790
|
+
const line = entry.buffer;
|
|
791
|
+
entry.buffer = "";
|
|
792
|
+
entry.pos += Buffer.byteLength(line, "utf-8");
|
|
793
|
+
return line;
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
// src/cli-system.ts
|
|
798
|
+
var NodeSystemAdapter = class {
|
|
799
|
+
getEnv(name) {
|
|
800
|
+
return process.env[name];
|
|
801
|
+
}
|
|
802
|
+
getAllEnv() {
|
|
803
|
+
const result = {};
|
|
804
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
805
|
+
if (v !== void 0) result[k] = v;
|
|
806
|
+
}
|
|
807
|
+
return result;
|
|
808
|
+
}
|
|
809
|
+
setEnv(name, value) {
|
|
810
|
+
process.env[name] = value;
|
|
811
|
+
}
|
|
812
|
+
cwd() {
|
|
813
|
+
return process.cwd();
|
|
814
|
+
}
|
|
815
|
+
chdir(dir) {
|
|
816
|
+
process.chdir(dir);
|
|
817
|
+
}
|
|
818
|
+
platform() {
|
|
819
|
+
return process.platform;
|
|
820
|
+
}
|
|
821
|
+
arch() {
|
|
822
|
+
return process.arch;
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
export {
|
|
826
|
+
NodeFileIOAdapter,
|
|
827
|
+
NodeSystemAdapter,
|
|
828
|
+
scanMFiles
|
|
829
|
+
};
|