typescript-virtual-container 1.5.5 → 1.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -35
- package/dist/.tsbuildinfo +1 -1
- package/dist/SSHMimic/index.d.ts +5 -1
- package/dist/SSHMimic/index.js +27 -3
- package/dist/SSHMimic/scp.d.ts +34 -0
- package/dist/SSHMimic/scp.js +285 -0
- package/dist/SSHMimic/sftp.d.ts +53 -3
- package/dist/SSHMimic/sftp.js +9 -3
- package/dist/VirtualFileSystem/binaryPack.d.ts +7 -0
- package/dist/VirtualFileSystem/binaryPack.js +37 -1
- package/dist/VirtualFileSystem/index.d.ts +7 -0
- package/dist/VirtualFileSystem/index.js +67 -27
- package/dist/VirtualFileSystem/internalTypes.d.ts +2 -0
- package/dist/VirtualFileSystem/path.d.ts +5 -0
- package/dist/VirtualFileSystem/path.js +24 -11
- package/dist/VirtualPackageManager/index.d.ts +4 -2
- package/dist/VirtualPackageManager/index.js +24 -4
- package/dist/VirtualShell/index.d.ts +4 -0
- package/dist/VirtualShell/index.js +1 -7
- package/dist/VirtualShell/shell.js +40 -10
- package/dist/VirtualShell/shellParser.js +1 -22
- package/dist/commands/awk.d.ts +6 -11
- package/dist/commands/awk.js +462 -109
- package/dist/commands/bzip2.d.ts +11 -0
- package/dist/commands/bzip2.js +91 -0
- package/dist/commands/exit.js +1 -1
- package/dist/commands/find.d.ts +2 -2
- package/dist/commands/find.js +209 -37
- package/dist/commands/helpers.d.ts +0 -20
- package/dist/commands/helpers.js +0 -97
- package/dist/commands/lsof.d.ts +6 -0
- package/dist/commands/lsof.js +30 -0
- package/dist/commands/perl.d.ts +6 -0
- package/dist/commands/perl.js +76 -0
- package/dist/commands/python.js +5 -2
- package/dist/commands/registry.js +19 -1
- package/dist/commands/runtime.js +65 -87
- package/dist/commands/sed.d.ts +2 -2
- package/dist/commands/sed.js +216 -34
- package/dist/commands/sh.js +42 -0
- package/dist/commands/strace.d.ts +6 -0
- package/dist/commands/strace.js +26 -0
- package/dist/commands/tar.d.ts +2 -1
- package/dist/commands/tar.js +138 -52
- package/dist/commands/test.js +2 -2
- package/dist/commands/zip.d.ts +11 -0
- package/dist/commands/zip.js +232 -0
- package/dist/modules/linuxRootfs.js +1 -4
- package/dist/modules/neofetch.js +2 -2
- package/dist/types/commands.d.ts +4 -0
- package/dist/utils/argv.d.ts +6 -0
- package/dist/utils/argv.js +32 -0
- package/dist/utils/expand.d.ts +5 -2
- package/dist/utils/expand.js +112 -45
- package/dist/utils/glob.d.ts +6 -0
- package/dist/utils/glob.js +34 -0
- package/dist/utils/tokenize.js +13 -13
- package/package.json +9 -7
- package/dist/self-standalone.d.ts +0 -1
- package/dist/self-standalone.js +0 -444
- package/dist/standalone-wo-sftp.d.ts +0 -1
- package/dist/standalone-wo-sftp.js +0 -30
- package/dist/standalone.d.ts +0 -1
- package/dist/standalone.js +0 -61
package/dist/commands/tar.js
CHANGED
|
@@ -1,6 +1,71 @@
|
|
|
1
|
+
import { gunzipSync, gzipSync } from "fflate";
|
|
1
2
|
import { resolvePath } from "./helpers";
|
|
3
|
+
// ── POSIX ustar tar format ────────────────────────────────────────────────────
|
|
4
|
+
function makeTarHeader(name, size, isDir) {
|
|
5
|
+
const hdr = Buffer.alloc(512);
|
|
6
|
+
const enc = (s, off, len) => {
|
|
7
|
+
const b = Buffer.from(s, "ascii");
|
|
8
|
+
b.copy(hdr, off, 0, Math.min(b.length, len));
|
|
9
|
+
};
|
|
10
|
+
enc(isDir ? `${name}/` : name, 0, 100);
|
|
11
|
+
enc(isDir ? "0000755\0" : "0000644\0", 100, 8);
|
|
12
|
+
enc("0000000\0", 108, 8);
|
|
13
|
+
enc("0000000\0", 116, 8);
|
|
14
|
+
enc(`${size.toString(8).padStart(11, "0")}\0`, 124, 12);
|
|
15
|
+
enc(`${Math.floor(Date.now() / 1000).toString(8).padStart(11, "0")}\0`, 136, 12);
|
|
16
|
+
hdr[156] = isDir ? 0x35 : 0x30; // '5' dir, '0' file
|
|
17
|
+
enc("ustar\0", 257, 6);
|
|
18
|
+
enc("00", 263, 2);
|
|
19
|
+
enc("root\0", 265, 32); // uname
|
|
20
|
+
enc("root\0", 297, 32); // gname
|
|
21
|
+
// Checksum: fill field with spaces, compute, write
|
|
22
|
+
for (let i = 148; i < 156; i++)
|
|
23
|
+
hdr[i] = 0x20;
|
|
24
|
+
let sum = 0;
|
|
25
|
+
for (let i = 0; i < 512; i++)
|
|
26
|
+
sum += hdr[i];
|
|
27
|
+
Buffer.from(`${sum.toString(8).padStart(6, "0")}\0 `).copy(hdr, 148);
|
|
28
|
+
return hdr;
|
|
29
|
+
}
|
|
30
|
+
function tarPad(size) {
|
|
31
|
+
const rem = size % 512;
|
|
32
|
+
return rem === 0 ? Buffer.alloc(0) : Buffer.alloc(512 - rem);
|
|
33
|
+
}
|
|
34
|
+
function buildTar(entries) {
|
|
35
|
+
const parts = [];
|
|
36
|
+
for (const { name, content, isDir } of entries) {
|
|
37
|
+
parts.push(makeTarHeader(name, isDir ? 0 : content.length, isDir));
|
|
38
|
+
if (!isDir) {
|
|
39
|
+
parts.push(content);
|
|
40
|
+
parts.push(tarPad(content.length));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
parts.push(Buffer.alloc(1024)); // end-of-archive
|
|
44
|
+
return Buffer.concat(parts);
|
|
45
|
+
}
|
|
46
|
+
function parseTar(raw) {
|
|
47
|
+
const files = [];
|
|
48
|
+
let off = 0;
|
|
49
|
+
while (off + 512 <= raw.length) {
|
|
50
|
+
const hdr = raw.slice(off, off + 512);
|
|
51
|
+
if (hdr.every((b) => b === 0))
|
|
52
|
+
break;
|
|
53
|
+
const name = hdr.slice(0, 100).toString("ascii").replace(/\0.*/, "");
|
|
54
|
+
const sizeStr = hdr.slice(124, 135).toString("ascii").replace(/\0.*/, "").trim();
|
|
55
|
+
const size = parseInt(sizeStr, 8) || 0;
|
|
56
|
+
const typeflag = hdr[156];
|
|
57
|
+
off += 512;
|
|
58
|
+
if (name && typeflag !== 0x35 && typeflag !== 53) { // not a directory
|
|
59
|
+
const content = raw.slice(off, off + size);
|
|
60
|
+
files.push({ name, content });
|
|
61
|
+
}
|
|
62
|
+
off += Math.ceil(size / 512) * 512;
|
|
63
|
+
}
|
|
64
|
+
return files;
|
|
65
|
+
}
|
|
2
66
|
/**
|
|
3
|
-
* Archive or extract files with tar
|
|
67
|
+
* Archive or extract files with tar — writes real POSIX ustar binary format.
|
|
68
|
+
* Supports -c/-x/-t, -z (gzip), -j (bzip2 stub), -v (verbose), -f.
|
|
4
69
|
* @category archive
|
|
5
70
|
* @params ["[-czf|-xzf|-tf] <archive> [files...]"]
|
|
6
71
|
*/
|
|
@@ -10,17 +75,15 @@ export const tarCommand = {
|
|
|
10
75
|
category: "archive",
|
|
11
76
|
params: ["[-czf|-xzf|-tf] <archive> [files...]"],
|
|
12
77
|
run: ({ authUser, shell, cwd, args }) => {
|
|
13
|
-
// Expand combined flags: -czf
|
|
78
|
+
// Expand combined flags: -czf → ["-c", "-z", "-f"]
|
|
14
79
|
const expanded = [];
|
|
15
80
|
let foundModeStr = false;
|
|
16
81
|
for (const a of args) {
|
|
17
82
|
if (/^-[a-zA-Z]{2,}$/.test(a)) {
|
|
18
|
-
// -czf style
|
|
19
83
|
for (const ch of a.slice(1))
|
|
20
84
|
expanded.push(`-${ch}`);
|
|
21
85
|
}
|
|
22
|
-
else if (!foundModeStr && /^[cxtdru]
|
|
23
|
-
// czf bare style (first non-path arg)
|
|
86
|
+
else if (!foundModeStr && /^[cxtdru][a-zA-Z]*$/.test(a) && !a.includes("/") && !a.startsWith("-")) {
|
|
24
87
|
foundModeStr = true;
|
|
25
88
|
for (const ch of a)
|
|
26
89
|
expanded.push(`-${ch}`);
|
|
@@ -32,70 +95,93 @@ export const tarCommand = {
|
|
|
32
95
|
const create = expanded.includes("-c");
|
|
33
96
|
const extract = expanded.includes("-x");
|
|
34
97
|
const list = expanded.includes("-t");
|
|
98
|
+
const useGzip = expanded.includes("-z");
|
|
99
|
+
const verbose = expanded.includes("-v");
|
|
35
100
|
const fIdx = expanded.indexOf("-f");
|
|
36
101
|
const archiveName = fIdx !== -1
|
|
37
102
|
? expanded[fIdx + 1]
|
|
38
|
-
: expanded.find((a) => a.endsWith(".tar") || a.endsWith(".tar.gz") || a.endsWith(".tgz"));
|
|
39
|
-
if (!create && !extract && !list)
|
|
40
|
-
return { stderr: "tar: must specify -c, -x, or -t
|
|
41
|
-
}
|
|
103
|
+
: expanded.find((a) => a.endsWith(".tar") || a.endsWith(".tar.gz") || a.endsWith(".tgz") || a.endsWith(".tar.bz2"));
|
|
104
|
+
if (!create && !extract && !list)
|
|
105
|
+
return { stderr: "tar: must specify -c, -x, or -t", exitCode: 1 };
|
|
42
106
|
if (!archiveName)
|
|
43
|
-
return { stderr: "tar: no archive specified
|
|
107
|
+
return { stderr: "tar: no archive specified", exitCode: 1 };
|
|
44
108
|
const archivePath = resolvePath(cwd, archiveName);
|
|
109
|
+
const autoGzip = useGzip || archiveName.endsWith(".gz") || archiveName.endsWith(".tgz");
|
|
45
110
|
if (create) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
const
|
|
111
|
+
const skipSet = new Set();
|
|
112
|
+
if (fIdx !== -1 && expanded[fIdx + 1])
|
|
113
|
+
skipSet.add(expanded[fIdx + 1]);
|
|
114
|
+
const fileArgs = expanded.filter((a) => !a.startsWith("-") && !skipSet.has(a));
|
|
115
|
+
const entries = [];
|
|
116
|
+
const verboseLines = [];
|
|
52
117
|
for (const f of fileArgs) {
|
|
53
118
|
const p = resolvePath(cwd, f);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const s = shell.vfs.stat(full);
|
|
63
|
-
if (s.type === "file")
|
|
64
|
-
entries[rel] = shell.vfs.readFile(full);
|
|
65
|
-
else
|
|
66
|
-
walk(full, rel);
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
walk(p, f);
|
|
70
|
-
}
|
|
119
|
+
if (!shell.vfs.exists(p))
|
|
120
|
+
return { stderr: `tar: ${f}: No such file or directory`, exitCode: 1 };
|
|
121
|
+
const st = shell.vfs.stat(p);
|
|
122
|
+
if (st.type === "file") {
|
|
123
|
+
const content = shell.vfs.readFileRaw(p);
|
|
124
|
+
entries.push({ name: f, content, isDir: false });
|
|
125
|
+
if (verbose)
|
|
126
|
+
verboseLines.push(f);
|
|
71
127
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
128
|
+
else {
|
|
129
|
+
entries.push({ name: f, content: Buffer.alloc(0), isDir: true });
|
|
130
|
+
if (verbose)
|
|
131
|
+
verboseLines.push(`${f}/`);
|
|
132
|
+
const walk = (dir, prefix) => {
|
|
133
|
+
for (const e of shell.vfs.list(dir)) {
|
|
134
|
+
const full = `${dir}/${e}`, rel = `${prefix}/${e}`;
|
|
135
|
+
const s = shell.vfs.stat(full);
|
|
136
|
+
if (s.type === "directory") {
|
|
137
|
+
entries.push({ name: rel, content: Buffer.alloc(0), isDir: true });
|
|
138
|
+
if (verbose)
|
|
139
|
+
verboseLines.push(`${rel}/`);
|
|
140
|
+
walk(full, rel);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
const content = shell.vfs.readFileRaw(full);
|
|
144
|
+
entries.push({ name: rel, content, isDir: false });
|
|
145
|
+
if (verbose)
|
|
146
|
+
verboseLines.push(rel);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
76
149
|
};
|
|
150
|
+
walk(p, f);
|
|
77
151
|
}
|
|
78
152
|
}
|
|
79
|
-
|
|
80
|
-
|
|
153
|
+
const tarBuf = buildTar(entries);
|
|
154
|
+
const finalBuf = autoGzip ? Buffer.from(gzipSync(tarBuf)) : tarBuf;
|
|
155
|
+
shell.vfs.writeFile(archivePath, finalBuf);
|
|
156
|
+
return { stdout: verbose ? verboseLines.join("\n") : undefined, exitCode: 0 };
|
|
81
157
|
}
|
|
82
158
|
if (list || extract) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
159
|
+
const rawArchive = shell.vfs.readFileRaw(archivePath);
|
|
160
|
+
let raw;
|
|
161
|
+
if (autoGzip) {
|
|
162
|
+
try {
|
|
163
|
+
raw = Buffer.from(gunzipSync(rawArchive));
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return { stderr: `tar: ${archiveName}: not a gzip file`, exitCode: 1 };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
raw = rawArchive;
|
|
86
171
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
};
|
|
172
|
+
const files = parseTar(raw);
|
|
173
|
+
if (list) {
|
|
174
|
+
const names = files.map((f) => (verbose ? `-rw-r--r-- 0/0 ${f.content.length.toString().padStart(8)} 1970-01-01 00:00 ${f.name}` : f.name));
|
|
175
|
+
return { stdout: names.join("\n"), exitCode: 0 };
|
|
92
176
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
shell.writeFileAsUser(authUser,
|
|
177
|
+
const verboseLines = [];
|
|
178
|
+
for (const { name, content } of files) {
|
|
179
|
+
const destPath = resolvePath(cwd, name);
|
|
180
|
+
shell.writeFileAsUser(authUser, destPath, content);
|
|
181
|
+
if (verbose)
|
|
182
|
+
verboseLines.push(name);
|
|
97
183
|
}
|
|
98
|
-
return { exitCode: 0 };
|
|
184
|
+
return { stdout: verbose ? verboseLines.join("\n") : undefined, exitCode: 0 };
|
|
99
185
|
}
|
|
100
186
|
return { stderr: "tar: must specify -c, -x, or -t", exitCode: 1 };
|
|
101
187
|
},
|
package/dist/commands/test.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolvePath } from "./helpers";
|
|
1
2
|
/**
|
|
2
3
|
* Evaluate a POSIX test expression.
|
|
3
4
|
* Supports: -f, -d, -e, -r, -w, -x, -s, -z, -n,
|
|
@@ -33,8 +34,7 @@ function evalTest(tokens, shell, cwd) {
|
|
|
33
34
|
// Unary file tests
|
|
34
35
|
if (tokens.length === 2) {
|
|
35
36
|
const [flag, operand = ""] = tokens;
|
|
36
|
-
const
|
|
37
|
-
const path = resolvePath(operand);
|
|
37
|
+
const path = resolvePath(cwd, operand);
|
|
38
38
|
switch (flag) {
|
|
39
39
|
case "-e":
|
|
40
40
|
return shell.vfs.exists(path);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ShellModule } from "../types/commands";
|
|
2
|
+
/**
|
|
3
|
+
* Create ZIP archives using real PKZIP format with DEFLATE compression.
|
|
4
|
+
* @category archive
|
|
5
|
+
*/
|
|
6
|
+
export declare const zipCommand: ShellModule;
|
|
7
|
+
/**
|
|
8
|
+
* Extract ZIP archives (real PKZIP DEFLATE format).
|
|
9
|
+
* @category archive
|
|
10
|
+
*/
|
|
11
|
+
export declare const unzipCommand: ShellModule;
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { deflateSync, inflateSync } from "fflate";
|
|
2
|
+
import { resolvePath } from "./helpers";
|
|
3
|
+
// ── CRC32 ─────────────────────────────────────────────────────────────────────
|
|
4
|
+
const CRC_TABLE = (() => {
|
|
5
|
+
const t = new Uint32Array(256);
|
|
6
|
+
for (let i = 0; i < 256; i++) {
|
|
7
|
+
let c = i;
|
|
8
|
+
for (let j = 0; j < 8; j++)
|
|
9
|
+
c = c & 1 ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1);
|
|
10
|
+
t[i] = c;
|
|
11
|
+
}
|
|
12
|
+
return t;
|
|
13
|
+
})();
|
|
14
|
+
function crc32(buf) {
|
|
15
|
+
let crc = 0xffffffff;
|
|
16
|
+
for (let i = 0; i < buf.length; i++)
|
|
17
|
+
crc = (CRC_TABLE[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8)) >>> 0;
|
|
18
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
19
|
+
}
|
|
20
|
+
// ── DOS date/time from JS Date ────────────────────────────────────────────────
|
|
21
|
+
function dosDateTime() {
|
|
22
|
+
const d = new Date();
|
|
23
|
+
const date = ((d.getFullYear() - 1980) << 9) | ((d.getMonth() + 1) << 5) | d.getDate();
|
|
24
|
+
const time = (d.getHours() << 11) | (d.getMinutes() << 5) | Math.floor(d.getSeconds() / 2);
|
|
25
|
+
return [time, date];
|
|
26
|
+
}
|
|
27
|
+
// ── ZIP builder ───────────────────────────────────────────────────────────────
|
|
28
|
+
function buildZip(entries) {
|
|
29
|
+
const parts = [];
|
|
30
|
+
const cdParts = [];
|
|
31
|
+
let offset = 0;
|
|
32
|
+
const [modTime, modDate] = dosDateTime();
|
|
33
|
+
for (const { name, content } of entries) {
|
|
34
|
+
const nameBuf = Buffer.from(name, "utf8");
|
|
35
|
+
const compressed = Buffer.from(deflateSync(content, { level: 6 }));
|
|
36
|
+
const useDeflate = compressed.length < content.length;
|
|
37
|
+
const stored = useDeflate ? compressed : content;
|
|
38
|
+
const crc = crc32(content);
|
|
39
|
+
const method = useDeflate ? 8 : 0;
|
|
40
|
+
// Local file header
|
|
41
|
+
const lfh = Buffer.alloc(30 + nameBuf.length);
|
|
42
|
+
lfh.writeUInt32LE(0x04034b50, 0);
|
|
43
|
+
lfh.writeUInt16LE(20, 4);
|
|
44
|
+
lfh.writeUInt16LE(0x0800, 6); // UTF-8 flag
|
|
45
|
+
lfh.writeUInt16LE(method, 8);
|
|
46
|
+
lfh.writeUInt16LE(modTime, 10);
|
|
47
|
+
lfh.writeUInt16LE(modDate, 12);
|
|
48
|
+
lfh.writeUInt32LE(crc, 14);
|
|
49
|
+
lfh.writeUInt32LE(stored.length, 18);
|
|
50
|
+
lfh.writeUInt32LE(content.length, 22);
|
|
51
|
+
lfh.writeUInt16LE(nameBuf.length, 26);
|
|
52
|
+
lfh.writeUInt16LE(0, 28);
|
|
53
|
+
nameBuf.copy(lfh, 30);
|
|
54
|
+
// Central directory entry
|
|
55
|
+
const cd = Buffer.alloc(46 + nameBuf.length);
|
|
56
|
+
cd.writeUInt32LE(0x02014b50, 0);
|
|
57
|
+
cd.writeUInt16LE(20, 4);
|
|
58
|
+
cd.writeUInt16LE(20, 6);
|
|
59
|
+
cd.writeUInt16LE(0x0800, 8);
|
|
60
|
+
cd.writeUInt16LE(method, 10);
|
|
61
|
+
cd.writeUInt16LE(modTime, 12);
|
|
62
|
+
cd.writeUInt16LE(modDate, 14);
|
|
63
|
+
cd.writeUInt32LE(crc, 16);
|
|
64
|
+
cd.writeUInt32LE(stored.length, 20);
|
|
65
|
+
cd.writeUInt32LE(content.length, 24);
|
|
66
|
+
cd.writeUInt16LE(nameBuf.length, 28);
|
|
67
|
+
cd.writeUInt16LE(0, 30); // extra
|
|
68
|
+
cd.writeUInt16LE(0, 32); // comment
|
|
69
|
+
cd.writeUInt16LE(0, 34); // disk start
|
|
70
|
+
cd.writeUInt16LE(0, 36); // int attr
|
|
71
|
+
cd.writeUInt32LE(0x81a40000, 38); // ext attr: -rw-r--r--
|
|
72
|
+
cd.writeUInt32LE(offset, 42);
|
|
73
|
+
nameBuf.copy(cd, 46);
|
|
74
|
+
parts.push(lfh, stored);
|
|
75
|
+
cdParts.push(cd);
|
|
76
|
+
offset += lfh.length + stored.length;
|
|
77
|
+
}
|
|
78
|
+
const cdBuf = Buffer.concat(cdParts);
|
|
79
|
+
const eocd = Buffer.alloc(22);
|
|
80
|
+
eocd.writeUInt32LE(0x06054b50, 0);
|
|
81
|
+
eocd.writeUInt16LE(0, 4);
|
|
82
|
+
eocd.writeUInt16LE(0, 6);
|
|
83
|
+
eocd.writeUInt16LE(entries.length, 8);
|
|
84
|
+
eocd.writeUInt16LE(entries.length, 10);
|
|
85
|
+
eocd.writeUInt32LE(cdBuf.length, 12);
|
|
86
|
+
eocd.writeUInt32LE(offset, 16);
|
|
87
|
+
eocd.writeUInt16LE(0, 20);
|
|
88
|
+
return Buffer.concat([...parts, cdBuf, eocd]);
|
|
89
|
+
}
|
|
90
|
+
// ── ZIP parser ────────────────────────────────────────────────────────────────
|
|
91
|
+
function parseZip(raw) {
|
|
92
|
+
const files = [];
|
|
93
|
+
let off = 0;
|
|
94
|
+
while (off + 4 <= raw.length) {
|
|
95
|
+
const sig = raw.readUInt32LE(off);
|
|
96
|
+
if (sig === 0x02014b50 || sig === 0x06054b50)
|
|
97
|
+
break; // central dir / EOCD
|
|
98
|
+
if (sig !== 0x04034b50) {
|
|
99
|
+
off++;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const method = raw.readUInt16LE(off + 8);
|
|
103
|
+
const compSize = raw.readUInt32LE(off + 18);
|
|
104
|
+
const uncompSize = raw.readUInt32LE(off + 22);
|
|
105
|
+
const nameLen = raw.readUInt16LE(off + 26);
|
|
106
|
+
const extraLen = raw.readUInt16LE(off + 28);
|
|
107
|
+
const name = raw.subarray(off + 30, off + 30 + nameLen).toString("utf8");
|
|
108
|
+
const dataOff = off + 30 + nameLen + extraLen;
|
|
109
|
+
const compData = raw.subarray(dataOff, dataOff + compSize);
|
|
110
|
+
let content;
|
|
111
|
+
if (method === 8) {
|
|
112
|
+
try {
|
|
113
|
+
content = Buffer.from(inflateSync(compData));
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
content = compData;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
content = compData;
|
|
121
|
+
}
|
|
122
|
+
if (name && !name.endsWith("/")) {
|
|
123
|
+
// Validate size
|
|
124
|
+
if (content.length === uncompSize || method !== 0)
|
|
125
|
+
files.push({ name, content });
|
|
126
|
+
else
|
|
127
|
+
files.push({ name, content });
|
|
128
|
+
}
|
|
129
|
+
off = dataOff + compSize;
|
|
130
|
+
}
|
|
131
|
+
return files;
|
|
132
|
+
}
|
|
133
|
+
// ── Commands ──────────────────────────────────────────────────────────────────
|
|
134
|
+
/**
|
|
135
|
+
* Create ZIP archives using real PKZIP format with DEFLATE compression.
|
|
136
|
+
* @category archive
|
|
137
|
+
*/
|
|
138
|
+
export const zipCommand = {
|
|
139
|
+
name: "zip",
|
|
140
|
+
description: "Package and compress files",
|
|
141
|
+
category: "archive",
|
|
142
|
+
params: ["[-r] <archive.zip> <file...>"],
|
|
143
|
+
run: ({ shell, cwd, args }) => {
|
|
144
|
+
const recursive = args.includes("-r") || args.includes("-R");
|
|
145
|
+
const files = args.filter((a) => !a.startsWith("-"));
|
|
146
|
+
const archiveArg = files[0];
|
|
147
|
+
const sources = files.slice(1);
|
|
148
|
+
if (!archiveArg)
|
|
149
|
+
return { stderr: "zip: no archive specified", exitCode: 1 };
|
|
150
|
+
if (sources.length === 0)
|
|
151
|
+
return { stderr: "zip: nothing to do!", exitCode: 12 };
|
|
152
|
+
const archivePath = resolvePath(cwd, archiveArg.endsWith(".zip") ? archiveArg : `${archiveArg}.zip`);
|
|
153
|
+
const entries = [];
|
|
154
|
+
const verboseLines = [];
|
|
155
|
+
for (const src of sources) {
|
|
156
|
+
const p = resolvePath(cwd, src);
|
|
157
|
+
if (!shell.vfs.exists(p))
|
|
158
|
+
return { stderr: `zip warning: name not matched: ${src}`, exitCode: 12 };
|
|
159
|
+
const st = shell.vfs.stat(p);
|
|
160
|
+
if (st.type === "file") {
|
|
161
|
+
const content = shell.vfs.readFileRaw(p);
|
|
162
|
+
entries.push({ name: src, content });
|
|
163
|
+
verboseLines.push(` adding: ${src} (deflated)`);
|
|
164
|
+
}
|
|
165
|
+
else if (recursive) {
|
|
166
|
+
const walk = (dir, prefix) => {
|
|
167
|
+
for (const e of shell.vfs.list(dir)) {
|
|
168
|
+
const full = `${dir}/${e}`, rel = `${prefix}/${e}`;
|
|
169
|
+
const s = shell.vfs.stat(full);
|
|
170
|
+
if (s.type === "directory")
|
|
171
|
+
walk(full, rel);
|
|
172
|
+
else {
|
|
173
|
+
const content = shell.vfs.readFileRaw(full);
|
|
174
|
+
entries.push({ name: rel, content });
|
|
175
|
+
verboseLines.push(` adding: ${rel} (deflated)`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
walk(p, src);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (entries.length === 0)
|
|
183
|
+
return { stderr: "zip: nothing to do!", exitCode: 12 };
|
|
184
|
+
const zipBuf = buildZip(entries);
|
|
185
|
+
shell.vfs.writeFile(archivePath, zipBuf);
|
|
186
|
+
return { stdout: verboseLines.join("\n"), exitCode: 0 };
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* Extract ZIP archives (real PKZIP DEFLATE format).
|
|
191
|
+
* @category archive
|
|
192
|
+
*/
|
|
193
|
+
export const unzipCommand = {
|
|
194
|
+
name: "unzip",
|
|
195
|
+
description: "Extract compressed files from ZIP archives",
|
|
196
|
+
category: "archive",
|
|
197
|
+
params: ["[-l] [-o] <archive.zip> [-d <dir>]"],
|
|
198
|
+
run: ({ shell, cwd, args }) => {
|
|
199
|
+
const listOnly = args.includes("-l");
|
|
200
|
+
const dIdx = args.indexOf("-d");
|
|
201
|
+
const destDir = dIdx !== -1 ? args[dIdx + 1] : undefined;
|
|
202
|
+
const archive = args.find((a) => !a.startsWith("-") && a !== destDir);
|
|
203
|
+
if (!archive)
|
|
204
|
+
return { stderr: "unzip: missing archive operand", exitCode: 1 };
|
|
205
|
+
const archivePath = resolvePath(cwd, archive);
|
|
206
|
+
if (!shell.vfs.exists(archivePath))
|
|
207
|
+
return { stderr: `unzip: cannot find or open ${archive}`, exitCode: 9 };
|
|
208
|
+
const raw = shell.vfs.readFileRaw(archivePath);
|
|
209
|
+
let files;
|
|
210
|
+
try {
|
|
211
|
+
files = parseZip(raw);
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return { stderr: `unzip: ${archive}: not a valid ZIP file`, exitCode: 1 };
|
|
215
|
+
}
|
|
216
|
+
const dest = destDir ? resolvePath(cwd, destDir) : cwd;
|
|
217
|
+
if (listOnly) {
|
|
218
|
+
const header = `Archive: ${archive}\n Length Date Time Name\n--------- ---------- ----- ----`;
|
|
219
|
+
const rows = files.map((f) => ` ${String(f.content.length).padStart(8)} 2024-01-01 00:00 ${f.name}`);
|
|
220
|
+
const total = files.reduce((s, f) => s + f.content.length, 0);
|
|
221
|
+
const footer = `--------- -------\n ${String(total).padStart(8)} ${files.length} file${files.length !== 1 ? "s" : ""}`;
|
|
222
|
+
return { stdout: `${header}\n${rows.join("\n")}\n${footer}`, exitCode: 0 };
|
|
223
|
+
}
|
|
224
|
+
const out = [`Archive: ${archive}`];
|
|
225
|
+
for (const { name, content } of files) {
|
|
226
|
+
const destPath = `${dest}/${name}`;
|
|
227
|
+
shell.vfs.writeFile(destPath, content);
|
|
228
|
+
out.push(` inflating: ${destPath}`);
|
|
229
|
+
}
|
|
230
|
+
return { stdout: out.join("\n"), exitCode: 0 };
|
|
231
|
+
},
|
|
232
|
+
};
|
|
@@ -1281,7 +1281,7 @@ Installed-Size: 6800
|
|
|
1281
1281
|
Maintainer: Fortune Package Team <dpkg@fortune.local>
|
|
1282
1282
|
Architecture: amd64
|
|
1283
1283
|
Version: 1.22.6nyx1
|
|
1284
|
-
Depends: libc6 (>= 2.17), libzstd1 (>= 1.5.
|
|
1284
|
+
Depends: libc6 (>= 2.17), libzstd1 (>= 1.5.7)
|
|
1285
1285
|
Description: Fortune package management system
|
|
1286
1286
|
This package provides the low-level infrastructure for handling the
|
|
1287
1287
|
installation and removal of Fortune software packages.
|
|
@@ -1547,12 +1547,9 @@ export function bootstrapLinuxRootfs(vfs, users, hostname, props, shellStartTime
|
|
|
1547
1547
|
const snapshot = getStaticRootfsSnapshot(hostname, props);
|
|
1548
1548
|
const hasRestoredData = vfs.getMode() === "fs" && vfs.exists("/home");
|
|
1549
1549
|
if (hasRestoredData) {
|
|
1550
|
-
// Snapshot was already restored — merge static rootfs without
|
|
1551
|
-
// clobbering user files and directories.
|
|
1552
1550
|
vfs.mergeRootTree(decodeVfs(snapshot));
|
|
1553
1551
|
}
|
|
1554
1552
|
else {
|
|
1555
|
-
// Fresh start — replace the empty tree with the full static rootfs.
|
|
1556
1553
|
vfs.importRootTree(decodeVfs(snapshot));
|
|
1557
1554
|
}
|
|
1558
1555
|
bootstrapRoot(vfs);
|
package/dist/modules/neofetch.js
CHANGED
|
@@ -208,10 +208,10 @@ function resolveDefaults(info) {
|
|
|
208
208
|
os: info.osName ?? `${readOsPrettyName() ?? os.type()} ${os.arch()}`,
|
|
209
209
|
arch: os.arch(),
|
|
210
210
|
},
|
|
211
|
-
resolution: info.resolution ?? "n/a (ssh)",
|
|
211
|
+
resolution: info.resolution ?? shellProps?.resolution ?? "n/a (ssh)",
|
|
212
212
|
terminal: info.terminal ?? "unknown",
|
|
213
213
|
cpu: info.cpu ?? resolveCpuLabel(),
|
|
214
|
-
gpu: info.gpu ?? "n/a",
|
|
214
|
+
gpu: info.gpu ?? shellProps?.gpu ?? "n/a",
|
|
215
215
|
memoryUsedMiB: info.memoryUsedMiB ?? toMiB(usedMem),
|
|
216
216
|
memoryTotalMiB: info.memoryTotalMiB ?? toMiB(totalMem),
|
|
217
217
|
};
|
package/dist/types/commands.d.ts
CHANGED
|
@@ -94,6 +94,10 @@ export interface ShellEnv {
|
|
|
94
94
|
vars: Record<string, string>;
|
|
95
95
|
/** Exit status of the last executed command. */
|
|
96
96
|
lastExitCode: number;
|
|
97
|
+
/** @internal Cached split of vars.PATH — invalidated when _pathRaw !== vars.PATH. */
|
|
98
|
+
_pathRaw?: string;
|
|
99
|
+
/** @internal Pre-split PATH directories for resolveVfsBinary hot-path. */
|
|
100
|
+
_pathDirs?: string[];
|
|
97
101
|
}
|
|
98
102
|
/** Runtime context object passed to each command module. */
|
|
99
103
|
export interface CommandContext {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Returns true if `name` appears in `argv`. */
|
|
2
|
+
export declare function getFlag(argv: string[], name: string): boolean;
|
|
3
|
+
/** Returns the string value for `--name=VALUE` or `--name VALUE`, or `fallback`. */
|
|
4
|
+
export declare function getOptionString(argv: string[], name: string, fallback: string): string;
|
|
5
|
+
/** Returns the integer value for `--name=VALUE` or `--name VALUE`, or `fallback`. */
|
|
6
|
+
export declare function getOptionInt(argv: string[], name: string, fallback: number): number;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Returns true if `name` appears in `argv`. */
|
|
2
|
+
export function getFlag(argv, name) {
|
|
3
|
+
return argv.includes(name);
|
|
4
|
+
}
|
|
5
|
+
/** Returns the string value for `--name=VALUE` or `--name VALUE`, or `fallback`. */
|
|
6
|
+
export function getOptionString(argv, name, fallback) {
|
|
7
|
+
const prefix = `${name}=`;
|
|
8
|
+
for (let i = 0; i < argv.length; i++) {
|
|
9
|
+
const a = argv[i];
|
|
10
|
+
if (a.startsWith(prefix))
|
|
11
|
+
return a.slice(prefix.length);
|
|
12
|
+
if (a === name) {
|
|
13
|
+
const next = argv[i + 1];
|
|
14
|
+
return (next && !next.startsWith("--")) ? next : fallback;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return fallback;
|
|
18
|
+
}
|
|
19
|
+
/** Returns the integer value for `--name=VALUE` or `--name VALUE`, or `fallback`. */
|
|
20
|
+
export function getOptionInt(argv, name, fallback) {
|
|
21
|
+
const prefix = `${name}=`;
|
|
22
|
+
for (let i = 0; i < argv.length; i++) {
|
|
23
|
+
const a = argv[i];
|
|
24
|
+
if (a.startsWith(prefix))
|
|
25
|
+
return parseInt(a.slice(prefix.length), 10);
|
|
26
|
+
if (a === name) {
|
|
27
|
+
const next = argv[i + 1];
|
|
28
|
+
return next ? parseInt(next, 10) : fallback;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return fallback;
|
|
32
|
+
}
|
package/dist/utils/expand.d.ts
CHANGED
|
@@ -61,10 +61,13 @@ export declare function expandAsync(input: string, env: Record<string, string>,
|
|
|
61
61
|
* Supports * (any chars in segment) and ** (any path).
|
|
62
62
|
* Returns the original pattern if no matches found (bash behavior).
|
|
63
63
|
*/
|
|
64
|
-
|
|
64
|
+
type GlobVfs = {
|
|
65
65
|
list: (p: string) => string[];
|
|
66
66
|
exists: (p: string) => boolean;
|
|
67
67
|
stat: (p: string) => {
|
|
68
68
|
type: string;
|
|
69
69
|
};
|
|
70
|
-
|
|
70
|
+
statType?: (p: string) => string | null;
|
|
71
|
+
};
|
|
72
|
+
export declare function expandGlob(pattern: string, cwd: string, vfs: GlobVfs): string[];
|
|
73
|
+
export {};
|