buninu-linux 0.8.56 → 0.10.56
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/LICENSES/MIT-ZilchOS-bootstrap-from-tcc.txt +21 -0
- package/NOTICE.md +34 -0
- package/README.md +3 -0
- package/initramfs/bin/tar +34 -46
- package/initramfs/buninu/apps/bunmsh/CHANGELOG.md +103 -0
- package/initramfs/buninu/apps/bunmsh/PORTING.md +133 -15
- package/initramfs/buninu/apps/bunmsh/help/awk.md +38 -0
- package/initramfs/buninu/apps/bunmsh/help/break.md +21 -0
- package/initramfs/buninu/apps/bunmsh/help/chmod.md +8 -2
- package/initramfs/buninu/apps/bunmsh/help/cmp.md +29 -0
- package/initramfs/buninu/apps/bunmsh/help/continue.md +22 -0
- package/initramfs/buninu/apps/bunmsh/help/cp.md +4 -0
- package/initramfs/buninu/apps/bunmsh/help/diff.md +30 -0
- package/initramfs/buninu/apps/bunmsh/help/egrep.md +26 -0
- package/initramfs/buninu/apps/bunmsh/help/export.md +2 -2
- package/initramfs/buninu/apps/bunmsh/help/expr.md +30 -0
- package/initramfs/buninu/apps/bunmsh/help/fgrep.md +25 -0
- package/initramfs/buninu/apps/bunmsh/help/grep.md +4 -0
- package/initramfs/buninu/apps/bunmsh/help/ls.md +4 -2
- package/initramfs/buninu/apps/bunmsh/help/mkdir.md +1 -1
- package/initramfs/buninu/apps/bunmsh/help/print.md +3 -0
- package/initramfs/buninu/apps/bunmsh/help/return.md +23 -0
- package/initramfs/buninu/apps/bunmsh/help/sed.md +8 -3
- package/initramfs/buninu/apps/bunmsh/help/set.md +12 -3
- package/initramfs/buninu/apps/bunmsh/help/tr.md +4 -0
- package/initramfs/buninu/apps/bunmsh/help/trap.md +22 -0
- package/initramfs/buninu/apps/bunmsh/help/uniq.md +27 -0
- package/initramfs/buninu/apps/bunmsh/package.json +1 -1
- package/initramfs/buninu/apps/bunmsh/src/awk.js +203 -0
- package/initramfs/buninu/apps/bunmsh/src/diff.js +222 -0
- package/initramfs/buninu/apps/bunmsh/src/fancy-ls.js +15 -4
- package/initramfs/buninu/apps/bunmsh/src/main.js +21 -5
- package/initramfs/buninu/apps/bunmsh/src/sed.js +159 -0
- package/initramfs/buninu/apps/bunmsh/src/shell.js +1283 -540
- package/initramfs/buninu/apps/bunmsh/test/awk-reference.test.js +35 -0
- package/initramfs/buninu/apps/bunmsh/test/awk.test.js +62 -0
- package/initramfs/buninu/apps/bunmsh/test/posix-bootstrap.test.js +595 -0
- package/initramfs/buninu/apps/bunmsh/test/utility-reference-cases.mjs +98 -0
- package/initramfs/buninu/apps/bunmsh/test/utility-reference.test.js +23 -0
- package/initramfs/buninu/apps/jsgotty/client-kitty.mjs +61 -0
- package/initramfs/buninu/apps/jsgotty/client.mjs +16 -0
- package/initramfs/buninu/apps/jsgotty/package-lock.json +2 -2
- package/initramfs/buninu/apps/jsgotty/package.json +1 -1
- package/initramfs/buninu/apps/jsgotty/tests/client-kitty.test.mjs +43 -0
- package/initramfs/etc/resolv.conf +2 -1
- package/initramfs/usr/lib/bootstrap/1.prepare-rootfs.js +106 -0
- package/initramfs/usr/lib/bootstrap/10.build-m4.js +40 -0
- package/initramfs/usr/lib/bootstrap/11.build-flex.js +50 -0
- package/initramfs/usr/lib/bootstrap/12.build-gcc.js +261 -0
- package/initramfs/usr/lib/bootstrap/2.enter-rootfs.js +49 -0
- package/initramfs/usr/lib/bootstrap/3.gen-musl-dev.js +141 -0
- package/initramfs/usr/lib/bootstrap/4.build-tcc.js +61 -0
- package/initramfs/usr/lib/bootstrap/5.build-make.js +58 -0
- package/initramfs/usr/lib/bootstrap/6.build-binutils.js +77 -0
- package/initramfs/usr/lib/bootstrap/7.build-gmp.js +68 -0
- package/initramfs/usr/lib/bootstrap/8.build-mpfr.js +59 -0
- package/initramfs/usr/lib/bootstrap/9.build-mpc.js +55 -0
- package/initramfs/usr/lib/bootstrap/LICENSE +21 -0
- package/initramfs/usr/lib/bootstrap/LICENSES/MIT-ZilchOS-bootstrap-from-tcc.txt +21 -0
- package/initramfs/usr/lib/bootstrap/NOTICE.md +65 -0
- package/initramfs/usr/lib/bootstrap/README.md +204 -0
- package/initramfs/usr/lib/bootstrap/downloads.md +49 -0
- package/initramfs/usr/lib/bootstrap/lib/build.js +155 -0
- package/initramfs/usr/lib/bootstrap/lib/common.js +134 -0
- package/initramfs/usr/lib/bootstrap/lib/compile-object.c +14 -0
- package/initramfs/usr/lib/bootstrap/lib/musl-headers.js +59 -0
- package/initramfs/usr/lib/bootstrap/lib/seed.js +148 -0
- package/initramfs/usr/lib/bootstrap/lib/startup-check.c +14 -0
- package/initramfs/usr/lib/bootstrap/lib/tcc-build.js +53 -0
- package/initramfs/usr/lib/bootstrap/sources/musl-1.2.6.tar.gz +0 -0
- package/initramfs/usr/lib/bootstrap/sources/tinycc-05f0faf-upstream.tar.gz +0 -0
- package/initramfs/usr/lib/bootstrap/sources/tinycc-local-bootstrap-files.tar.gz +0 -0
- package/initramfs/usr/share/doc/buninu-linux/tar.md +11 -0
- package/package.json +1 -1
- package/scripts/fetch.js +2 -0
- package/test/bootstrap.test.js +65 -0
- package/test/tar-strip.test.js +36 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import {
|
|
2
|
+
appendFileSync, chmodSync, copyFileSync, existsSync, mkdirSync,
|
|
3
|
+
readFileSync, readdirSync, utimesSync, writeFileSync,
|
|
4
|
+
} from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { bootstrap, bun, download, quote, requireRootfs } from "./common.js";
|
|
7
|
+
|
|
8
|
+
export const tools = "/bootstrap/toolchain/bin";
|
|
9
|
+
export const toolchain = "/bootstrap/toolchain";
|
|
10
|
+
export const deps = "/bootstrap/deps";
|
|
11
|
+
export const sourceRoot = "/bootstrap/src";
|
|
12
|
+
export const buildRoot = "/bootstrap/build";
|
|
13
|
+
export const resultRoot = "/bootstrap/result";
|
|
14
|
+
export function useLog(path) { log = path; }
|
|
15
|
+
// Build shells preserve the explicit build PATH, without the guest entry
|
|
16
|
+
// shell's preference for installed result tools.
|
|
17
|
+
export const shell = tools + "/sh";
|
|
18
|
+
export const make = tools + "/make";
|
|
19
|
+
let log;
|
|
20
|
+
|
|
21
|
+
export function stage(number, name, args) {
|
|
22
|
+
if (args.length) throw new Error(`Usage: bun ${number}.build-${name}.js`);
|
|
23
|
+
const { arch } = requireRootfs();
|
|
24
|
+
mkdirSync(buildRoot + "/logs", { recursive: true });
|
|
25
|
+
log = `${buildRoot}/logs/${number}-${name}.log`;
|
|
26
|
+
note(`\n${new Date().toISOString()} Stage ${number}: ${name} (${arch})`);
|
|
27
|
+
return { arch, triple: `${arch}-unknown-linux-musl` };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function note(text) {
|
|
31
|
+
console.log(text);
|
|
32
|
+
if (log) appendFileSync(log, text + "\n");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function environment(extra = {}) {
|
|
36
|
+
return {
|
|
37
|
+
PATH: `${tools}:/bin:/usr/bin`, HOME: "/root", LC_ALL: "C",
|
|
38
|
+
SHELL: shell, CONFIG_SHELL: shell, CC: tools + "/cc", CC_FOR_BUILD: tools + "/cc",
|
|
39
|
+
AR: existsSync(toolchain + "/bin/ar") ? toolchain + "/bin/ar" : tools + "/bootstrap-ar",
|
|
40
|
+
LD: existsSync(toolchain + "/bin/ld") ? toolchain + "/bin/ld" : tools + "/bootstrap-ld",
|
|
41
|
+
RANLIB: existsSync(toolchain + "/bin/ranlib") ? toolchain + "/bin/ranlib" : ":",
|
|
42
|
+
NM: toolchain + "/bin/nm", MAKE: make, MAKEINFO: ":", CFLAGS: "-O2",
|
|
43
|
+
CFLAGS_FOR_TARGET: "-O2", lt_cv_sys_max_cmd_len: "32768",
|
|
44
|
+
...extra,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function command(argv, cwd = buildRoot, extra = {}, capture = false) {
|
|
49
|
+
mkdirSync(cwd, { recursive: true });
|
|
50
|
+
note("$ " + argv.map(quote).join(" "));
|
|
51
|
+
const child = Bun.spawn(argv, {
|
|
52
|
+
cwd, env: environment(extra), stdin: "ignore", stdout: "pipe", stderr: "pipe",
|
|
53
|
+
});
|
|
54
|
+
const chunks = [];
|
|
55
|
+
async function drain(stream, output, collect) {
|
|
56
|
+
for await (const bytes of stream) {
|
|
57
|
+
const data = Buffer.from(bytes);
|
|
58
|
+
output.write(data);
|
|
59
|
+
if (log) appendFileSync(log, data);
|
|
60
|
+
if (collect) chunks.push(data);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const [status] = await Promise.all([
|
|
64
|
+
child.exited, drain(child.stdout, process.stdout, capture), drain(child.stderr, process.stderr, false),
|
|
65
|
+
]);
|
|
66
|
+
if (status !== 0) throw new Error(`Command exited ${status}; see ${log}: ${argv[0]}`);
|
|
67
|
+
return capture ? Buffer.concat(chunks).toString() : "";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function source(pin, directory) {
|
|
71
|
+
// Verify on every stage invocation, including a cached archive.
|
|
72
|
+
const archive = await download(pin);
|
|
73
|
+
const target = join(sourceRoot, directory);
|
|
74
|
+
if (!existsSync(target)) {
|
|
75
|
+
mkdirSync(sourceRoot, { recursive: true });
|
|
76
|
+
const tar = new Bun.Archive(readFileSync(archive));
|
|
77
|
+
await tar.extract(sourceRoot);
|
|
78
|
+
// Preserve release-generated parsers/Makefiles: extraction time must
|
|
79
|
+
// not accidentally request autoconf, automake or bison regeneration.
|
|
80
|
+
for (const [name, file] of await tar.files()) {
|
|
81
|
+
const seconds = file.lastModified / 1000;
|
|
82
|
+
utimesSync(join(sourceRoot, name), seconds, seconds);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (!existsSync(join(target, "configure"))) throw new Error(`Incomplete source tree: ${target}`);
|
|
86
|
+
return target;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function patch(path, before, after) {
|
|
90
|
+
const old = readFileSync(path, "utf8");
|
|
91
|
+
if (old.includes(after)) return;
|
|
92
|
+
if (!old.includes(before)) throw new Error(`Patch context missing: ${path}`);
|
|
93
|
+
writeFileSync(path, old.replaceAll(before, after));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function script(path, text) {
|
|
97
|
+
writeFileSync(path, "#!/bin/bun\n" + text, { mode: 0o755 });
|
|
98
|
+
chmodSync(path, 0o755);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function installAdapters() {
|
|
102
|
+
mkdirSync(tools, { recursive: true });
|
|
103
|
+
script(shell, "await import('/buninu/apps/bunmsh/src/main.js');\n");
|
|
104
|
+
// Native Make/configure can exec these names; implementations remain JS.
|
|
105
|
+
const names = "awk basename cat chmod cmp cp cut date diff dirname echo egrep expr false fgrep grep head ln ls mkdir mktemp mv printf pwd rm rmdir sed sleep sort tail test touch tr true uname uniq wc".split(" ");
|
|
106
|
+
for (const name of names) script(`${tools}/${name}`, `
|
|
107
|
+
import {executeArgv,createState,finishShell} from '/buninu/apps/bunmsh/src/shell.js';
|
|
108
|
+
const state=createState({shellOptions:{posix:true}});
|
|
109
|
+
const result=await executeArgv(['builtin',${JSON.stringify(name)},...process.argv.slice(2)],state);
|
|
110
|
+
process.exitCode=(await finishShell(state,result.status)).status;
|
|
111
|
+
`);
|
|
112
|
+
script(tools + "/bootstrap-ar", `import {existsSync} from 'node:fs';const ar='/bootstrap/toolchain/bin/ar';const p=Bun.spawn([...(existsSync(ar)?[ar]:['/bootstrap/toolchain/bin/tcc','-ar']),...Bun.argv.slice(2)],{stdin:'inherit',stdout:'inherit',stderr:'inherit'});process.exitCode=await p.exited;\n`);
|
|
113
|
+
script(tools + "/bootstrap-ld", `import {existsSync} from 'node:fs';const ld='/bootstrap/toolchain/bin/ld';const p=Bun.spawn([existsSync(ld)?ld:'/bootstrap/toolchain/bin/tcc',...Bun.argv.slice(2)],{stdin:'inherit',stdout:'inherit',stderr:'inherit'});process.exitCode=await p.exited;\n`);
|
|
114
|
+
installCc();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function installCc() {
|
|
118
|
+
script(tools + "/cc", `
|
|
119
|
+
import {existsSync} from 'node:fs';
|
|
120
|
+
const args=Bun.argv.slice(2);
|
|
121
|
+
const info=args.length===0 || args.every(a=>['-v','--version','-h','-hh','--help','-dumpversion','-dumpmachine','-print-search-dirs'].includes(a));
|
|
122
|
+
const linking=!info && !args.some(a=>['-c','-E','-S','-r'].includes(a));
|
|
123
|
+
const archive=process.arch==='x64'?'/bootstrap/toolchain/lib/tcc/libbootstrap-tcc-x64.a':'/bootstrap/toolchain/lib/tcc/libbootstrap-tcc-arm64.a';
|
|
124
|
+
const runtime=linking&&existsSync(archive)?[archive]:[];
|
|
125
|
+
const p=Bun.spawn(['/bootstrap/toolchain/bin/tcc',...args,...(info?[]:['-B/bootstrap/toolchain/lib/tcc','-nostdinc','-I/usr/include','-I/bootstrap/toolchain/lib/tcc/include','-L/usr/lib','-L/lib',...runtime,...(linking?['-lgcc_s']:[])])],{stdin:'inherit',stdout:'inherit',stderr:'inherit'});
|
|
126
|
+
process.exitCode=await p.exited;
|
|
127
|
+
`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function configure(src, build, flags, extra = {}) {
|
|
131
|
+
await command([bun(), "/buninu/apps/bunmsh/index.js", src + "/configure", ...flags], build, extra);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function makeIn(build, targets = ["all"], extra = {}) {
|
|
135
|
+
const jobs = process.env.BOOTSTRAP_JOBS || "2";
|
|
136
|
+
if (!/^[1-9][0-9]*$/.test(jobs)) throw new Error("BOOTSTRAP_JOBS must be a positive integer");
|
|
137
|
+
await command([make, "SHELL=" + shell, "MAKEINFO=:", "-j" + jobs, ...targets], build, extra);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function canonicalizers(src) {
|
|
141
|
+
const binutils = sourceRoot + "/binutils-2.39";
|
|
142
|
+
for (const name of ["config.sub", "config.guess"]) {
|
|
143
|
+
copyFileSync(join(binutils, name), join(src, name));
|
|
144
|
+
chmodSync(join(src, name), 0o755);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function ready(number, message) {
|
|
149
|
+
note(`Stage ${number} verified: ${message}`);
|
|
150
|
+
if (number < 12) {
|
|
151
|
+
const next = readdirSync(bootstrap).find(name => name.startsWith(`${number + 1}.`) && name.endsWith(".js"));
|
|
152
|
+
if (!next) throw new Error(`Missing stage ${number + 1}`);
|
|
153
|
+
note(`Next: bun /bootstrap/${next}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync,
|
|
3
|
+
} from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { dirname, join, resolve } from "node:path";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
|
|
8
|
+
export const bootstrap = resolve(import.meta.dir, "..");
|
|
9
|
+
export const bun = () => Bun.which("bun") || process.argv0;
|
|
10
|
+
export const quote = value => "'" + value.replaceAll("'", "'\\''") + "'";
|
|
11
|
+
|
|
12
|
+
export function normalizeArch(name) {
|
|
13
|
+
if (["x", "x64", "x86_64", "x86-64"].includes(name)) return "x86_64";
|
|
14
|
+
if (["a", "aarch64", "arm64"].includes(name)) return "aarch64";
|
|
15
|
+
throw new Error(`Unsupported architecture: ${name}`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function options(args) {
|
|
19
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
20
|
+
console.log("Usage: bun SCRIPT [ARCH] [ROOTFS]");
|
|
21
|
+
console.log("ARCH: a/arm64/aarch64 or x/x64/x86_64/x86-64; default: host architecture.");
|
|
22
|
+
console.log("ROOTFS: default ./roota (arm64) or ./rootx (x64). A lone path is accepted.");
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
let arch = normalizeArch(process.arch);
|
|
26
|
+
const rest = [...args];
|
|
27
|
+
if (rest.length && /^(x|x64|x86_64|x86-64|a|arm64|aarch64)$/.test(rest[0])) {
|
|
28
|
+
arch = normalizeArch(rest.shift());
|
|
29
|
+
}
|
|
30
|
+
if (rest.length > 1 || rest.some(arg => arg.startsWith("-"))) {
|
|
31
|
+
throw new Error("Expected [ARCH] [ROOTFS]; use --help");
|
|
32
|
+
}
|
|
33
|
+
return { arch, rootfs: resolve(rest[0] ?? (arch === "aarch64" ? "roota" : "rootx")) };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Parse data only: do not execute os-release as shell code.
|
|
37
|
+
export function isBuninuLinux() {
|
|
38
|
+
let text;
|
|
39
|
+
try { text = readFileSync("/etc/os-release", "utf8"); }
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (error.code !== "ENOENT") throw error;
|
|
42
|
+
try { text = readFileSync("/usr/lib/os-release", "utf8"); }
|
|
43
|
+
catch (fallback) { if (fallback.code === "ENOENT") return false; throw fallback; }
|
|
44
|
+
}
|
|
45
|
+
return hasBuninuId(text);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function hasBuninuId(text) {
|
|
49
|
+
const ids = [...text.matchAll(/^ID=(.*)\r?$/gm)];
|
|
50
|
+
const value = ids.at(-1)?.[1].trim();
|
|
51
|
+
return ["buninu-linux", '"buninu-linux"', "'buninu-linux'"].includes(value);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function assertAbsent(path) {
|
|
55
|
+
try { lstatSync(path); }
|
|
56
|
+
catch (error) { if (error.code === "ENOENT") return; throw error; }
|
|
57
|
+
throw new Error(`Refusing existing destination: ${path}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function write(path, bytes, mode = 0o644) {
|
|
61
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
62
|
+
writeFileSync(path, bytes, { flag: "wx", mode });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const sha256 = bytes => createHash("sha256").update(bytes).digest("hex");
|
|
66
|
+
|
|
67
|
+
export async function download(name) {
|
|
68
|
+
const pins = Bun.YAML.parse(readFileSync(join(bootstrap, "downloads.md"), "utf8"));
|
|
69
|
+
const pin = pins[name];
|
|
70
|
+
if (!Array.isArray(pin) || !/^sha256:[a-f0-9]{64}$/.test(pin[1])) {
|
|
71
|
+
throw new Error(`Missing or invalid download pin: ${name}`);
|
|
72
|
+
}
|
|
73
|
+
const [url, hash] = pin;
|
|
74
|
+
const expected = hash.slice(7);
|
|
75
|
+
const cache = join(process.env.XDG_CACHE_HOME || join(homedir(), ".cache"), "buninu-bootstrap");
|
|
76
|
+
const path = join(cache, expected + "-" + new URL(url).pathname.split("/").at(-1));
|
|
77
|
+
if (existsSync(path)) {
|
|
78
|
+
const bytes = readFileSync(path);
|
|
79
|
+
if (sha256(bytes) !== expected) throw new Error(`Cached file hash mismatch: ${path}`);
|
|
80
|
+
console.log(`Verified cache: ${name}`);
|
|
81
|
+
return path;
|
|
82
|
+
}
|
|
83
|
+
console.log(`Fetching ${name}`);
|
|
84
|
+
const response = await fetch(url);
|
|
85
|
+
if (!response.ok) throw new Error(`${url}: HTTP ${response.status}`);
|
|
86
|
+
const bytes = Buffer.from(await response.arrayBuffer());
|
|
87
|
+
if (sha256(bytes) !== expected) throw new Error(`Download hash mismatch: ${name}`);
|
|
88
|
+
mkdirSync(cache, { recursive: true });
|
|
89
|
+
writeFileSync(path, bytes, { flag: "wx", mode: 0o644 });
|
|
90
|
+
return path;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function run(argv) {
|
|
94
|
+
console.log("$ " + argv.map(quote).join(" "));
|
|
95
|
+
const child = Bun.spawn(argv, { stdin: "inherit", stdout: "inherit", stderr: "inherit" });
|
|
96
|
+
const status = await child.exited;
|
|
97
|
+
if (status !== 0) throw new Error(`Command exited ${status}: ${argv[0]}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function cli(main) {
|
|
101
|
+
const mask = process.umask(0o022);
|
|
102
|
+
try { await main(Bun.argv.slice(2)); }
|
|
103
|
+
catch (error) { console.error(error.message); process.exitCode = 1; }
|
|
104
|
+
finally { process.umask(mask); }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function requireRootfs() {
|
|
108
|
+
// Prevent an accidental stage 3/4 invocation from writing host /usr/include.
|
|
109
|
+
const marker = JSON.parse(readFileSync("/etc/buninu-bootstrap.json", "utf8"));
|
|
110
|
+
if (marker.format !== 1 || marker.arch !== normalizeArch(process.arch)) {
|
|
111
|
+
throw new Error("Enter the prepared rootfs for the matching architecture first");
|
|
112
|
+
}
|
|
113
|
+
if (bootstrap !== "/bootstrap") throw new Error("Run this stage inside the prepared rootfs");
|
|
114
|
+
return marker;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function validateElf(name, data, arch) {
|
|
118
|
+
if (data.length < 64 || data.toString("hex", 0, 4) !== "7f454c46"
|
|
119
|
+
|| data[4] !== 2 || data[5] !== 1 || data.readUInt16LE(18) !== (arch === "aarch64" ? 183 : 62)) {
|
|
120
|
+
throw new Error(`Not a ${arch} little-endian ELF64 file: ${name}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function elfInterpreter(data) {
|
|
125
|
+
const start = Number(data.readBigUInt64LE(32)), stride = data.readUInt16LE(54), count = data.readUInt16LE(56);
|
|
126
|
+
for (let i = 0; i < count; i++) {
|
|
127
|
+
const p = start + i * stride;
|
|
128
|
+
if (!Number.isSafeInteger(p) || stride < 56 || p + 56 > data.length) throw new Error("Invalid Bun ELF program headers");
|
|
129
|
+
if (data.readUInt32LE(p) !== 3) continue;
|
|
130
|
+
const at = Number(data.readBigUInt64LE(p + 8)), size = Number(data.readBigUInt64LE(p + 32));
|
|
131
|
+
if (!Number.isSafeInteger(at) || !Number.isSafeInteger(size) || at + size > data.length) throw new Error("Invalid Bun ELF interpreter range");
|
|
132
|
+
return data.subarray(at, at + size).toString().replace(/\0.*$/s, "");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#include "/bootstrap/src/tinycc/libtcc.c"
|
|
2
|
+
|
|
3
|
+
int compile_object(const char *source, const char *output, const char *flags) {
|
|
4
|
+
TCCState *s = tcc_new();
|
|
5
|
+
if (!s) return 10;
|
|
6
|
+
tcc_set_lib_path(s, "/bootstrap/src/tinycc");
|
|
7
|
+
int result = tcc_set_options(s, flags);
|
|
8
|
+
if (!result) result = tcc_set_output_type(s, TCC_OUTPUT_OBJ);
|
|
9
|
+
if (!result) result = tcc_add_file(s, source);
|
|
10
|
+
if (!result) result = tcc_output_file(s, output);
|
|
11
|
+
tcc_delete(s);
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
#include "/bootstrap/src/tinycc/bun-x64-runtime.c"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Generate musl 1.2.6 public headers without Make, sed or a C compiler.
|
|
2
|
+
import {
|
|
3
|
+
existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync,
|
|
4
|
+
} from "node:fs";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
|
|
7
|
+
export function generateHeaders(arch, source, output) {
|
|
8
|
+
if (readFileSync(join(source, "VERSION"), "utf8").trim() !== "1.2.6") {
|
|
9
|
+
throw new Error("This generator follows musl 1.2.6's header rules");
|
|
10
|
+
}
|
|
11
|
+
if (existsSync(output)) throw new Error(`Refusing existing output: ${output}`);
|
|
12
|
+
const headers = new Map();
|
|
13
|
+
const copyHeaders = (directory, prefix = "") => {
|
|
14
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
15
|
+
if (entry.isFile() && entry.name.endsWith(".h")) {
|
|
16
|
+
headers.set(prefix + entry.name, readFileSync(join(directory, entry.name)));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Makefile's include/*.h and include/*/*.h public headers.
|
|
22
|
+
copyHeaders(join(source, "include"));
|
|
23
|
+
for (const entry of readdirSync(join(source, "include"), { withFileTypes: true })) {
|
|
24
|
+
if (entry.isDirectory()) copyHeaders(join(source, "include", entry.name), entry.name + "/");
|
|
25
|
+
}
|
|
26
|
+
// Architecture-specific bits override generic fallbacks.
|
|
27
|
+
copyHeaders(join(source, "arch/generic/bits"), "bits/");
|
|
28
|
+
copyHeaders(join(source, "arch", arch, "bits"), "bits/");
|
|
29
|
+
|
|
30
|
+
// Translate tools/mkalltypes.sed's three substitutions literally. The
|
|
31
|
+
// replacement's final newline is intentional, in addition to the input's.
|
|
32
|
+
const types = readFileSync(join(source, "arch", arch, "bits/alltypes.h.in"), "utf8")
|
|
33
|
+
+ readFileSync(join(source, "include/alltypes.h.in"), "utf8");
|
|
34
|
+
headers.set("bits/alltypes.h", types
|
|
35
|
+
.replace(/^TYPEDEF (.*) ([^ ]*);$/gm, (_, type, name) =>
|
|
36
|
+
`#if defined(__NEED_${name}) && !defined(__DEFINED_${name})\ntypedef ${type} ${name};\n#define __DEFINED_${name}\n#endif\n`)
|
|
37
|
+
.replace(/^STRUCT * ([^ ]*) (.*);$/gm, (_, name, body) =>
|
|
38
|
+
`#if defined(__NEED_struct_${name}) && !defined(__DEFINED_struct_${name})\nstruct ${name} ${body};\n#define __DEFINED_struct_${name}\n#endif\n`)
|
|
39
|
+
.replace(/^UNION * ([^ ]*) (.*);$/gm, (_, name, body) =>
|
|
40
|
+
`#if defined(__NEED_union_${name}) && !defined(__DEFINED_union_${name})\nunion ${name} ${body};\n#define __DEFINED_union_${name}\n#endif\n`));
|
|
41
|
+
|
|
42
|
+
// Makefile: copy the input, then append sed -n 's/__NR_/SYS_/p' output.
|
|
43
|
+
const syscalls = readFileSync(join(source, "arch", arch, "bits/syscall.h.in"), "utf8");
|
|
44
|
+
const aliases = syscalls.split("\n").filter(line => line.includes("__NR_"))
|
|
45
|
+
.map(line => line.replace("__NR_", "SYS_") + "\n").join("");
|
|
46
|
+
headers.set("bits/syscall.h", syscalls + aliases);
|
|
47
|
+
|
|
48
|
+
const previousUmask = process.umask(0o022);
|
|
49
|
+
try {
|
|
50
|
+
mkdirSync(output); // Parent must exist; refuse races with another writer.
|
|
51
|
+
for (const [name, data] of headers) {
|
|
52
|
+
const path = join(output, name);
|
|
53
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
54
|
+
writeFileSync(path, data, { flag: "wx", mode: 0o644 });
|
|
55
|
+
}
|
|
56
|
+
} finally { process.umask(previousUmask); }
|
|
57
|
+
console.log(`Generated ${headers.size} ${arch} headers at ${output}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
chmodSync, cpSync, mkdirSync, readFileSync, readdirSync,
|
|
3
|
+
renameSync, symlinkSync,
|
|
4
|
+
} from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import {
|
|
7
|
+
bootstrap, bun, download, elfInterpreter, run, sha256,
|
|
8
|
+
validateElf, write,
|
|
9
|
+
} from "./common.js";
|
|
10
|
+
|
|
11
|
+
const tools = [
|
|
12
|
+
"bin/tar", "bin/unzip", "usr/bin/env",
|
|
13
|
+
"lib/crc32.js", "lib/document.js", "lib/tar-strip.js", "lib/tar-exclude.js",
|
|
14
|
+
"usr/share/doc/buninu-linux/tar.md",
|
|
15
|
+
];
|
|
16
|
+
const shell = "buninu/apps/bunmsh/";
|
|
17
|
+
|
|
18
|
+
function addTree(files, directory, prefix) {
|
|
19
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
20
|
+
const path = join(directory, entry.name), target = prefix + entry.name;
|
|
21
|
+
if (entry.isDirectory()) addTree(files, path, target + "/");
|
|
22
|
+
else files.set(target, readFileSync(path)); // Resolve library/source symlinks.
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function localSeed(arch) {
|
|
27
|
+
const files = new Map();
|
|
28
|
+
for (const path of tools) files.set(path, readFileSync("/" + path));
|
|
29
|
+
addTree(files, "/" + shell, shell);
|
|
30
|
+
for (const name of ["libgcc_s.so.1", "libstdc++.so.6"]) {
|
|
31
|
+
let bytes;
|
|
32
|
+
try { bytes = readFileSync("/lib/" + name); }
|
|
33
|
+
catch (error) { if (error.code !== "ENOENT") throw error; bytes = readFileSync("/usr/lib/" + name); }
|
|
34
|
+
files.set("usr/lib/" + name, bytes);
|
|
35
|
+
}
|
|
36
|
+
files.set(`lib/ld-musl-${arch}.so.1`, readFileSync(`/lib/ld-musl-${arch}.so.1`));
|
|
37
|
+
files.set("bin/bun", readFileSync("/bin/bun"));
|
|
38
|
+
files.set("etc/resolv.conf", readFileSync("/etc/resolv.conf"));
|
|
39
|
+
return files;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function releaseSeed(arch, work) {
|
|
43
|
+
const tarball = await download("buninu-linux@0.9.56");
|
|
44
|
+
// Bun's archive reader works in-process; no host tar is required.
|
|
45
|
+
const entries = await new Bun.Archive(readFileSync(tarball)).files();
|
|
46
|
+
const get = async name => {
|
|
47
|
+
const file = entries.get("package/" + name);
|
|
48
|
+
if (!file) throw new Error(`Pinned buninu-linux tarball is missing ${name}`);
|
|
49
|
+
return Buffer.from(await file.arrayBuffer());
|
|
50
|
+
};
|
|
51
|
+
const files = new Map();
|
|
52
|
+
for (const path of tools) files.set(path, await get("initramfs/" + path));
|
|
53
|
+
for (const [name, file] of entries) {
|
|
54
|
+
const prefix = "package/initramfs/";
|
|
55
|
+
if (name.startsWith(prefix + shell)) files.set(name.slice(prefix.length), Buffer.from(await file.arrayBuffer()));
|
|
56
|
+
}
|
|
57
|
+
for (const name of ["libgcc_s.so.1", "libstdc++.so.6"]) {
|
|
58
|
+
files.set("usr/lib/" + name, await get(`native/${arch}/lib/${name}`));
|
|
59
|
+
}
|
|
60
|
+
const loader = `ld-musl-${arch}.so.1`;
|
|
61
|
+
files.set("lib/" + loader, await get(`native/${arch}/lib/${loader}`));
|
|
62
|
+
files.set("etc/resolv.conf", await get("initramfs/etc/resolv.conf"));
|
|
63
|
+
|
|
64
|
+
console.log("② Extracting pinned musl-bun");
|
|
65
|
+
const build = arch === "aarch64" ? "bun-linux-aarch64-musl" : "bun-linux-x64-musl-baseline";
|
|
66
|
+
const zip = await download(`${build}@1.4.2`);
|
|
67
|
+
// unzip is a CLI with top-level process.exit; invoke it separately.
|
|
68
|
+
for (const path of ["bin/unzip", "lib/crc32.js"]) write(join(work, path), files.get(path));
|
|
69
|
+
await run([bun(), join(work, "bin/unzip"), zip, `${build}/bun`, "-d", work]);
|
|
70
|
+
files.set("bin/bun", readFileSync(join(work, build, "bun")));
|
|
71
|
+
return files;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function validateSeed(files, arch) {
|
|
75
|
+
const loader = `lib/ld-musl-${arch}.so.1`;
|
|
76
|
+
for (const name of ["bin/bun", loader, "usr/lib/libgcc_s.so.1", "usr/lib/libstdc++.so.6"]) {
|
|
77
|
+
validateElf(name, files.get(name), arch);
|
|
78
|
+
}
|
|
79
|
+
if (elfInterpreter(files.get("bin/bun")) !== "/" + loader) throw new Error("Bun must use the target musl loader");
|
|
80
|
+
for (const name of ["index.js", "package.json", "src/main.js"]) {
|
|
81
|
+
if (!files.has(shell + name)) throw new Error(`Missing bunmsh ${name}`);
|
|
82
|
+
}
|
|
83
|
+
for (const name of files.keys()) {
|
|
84
|
+
if (name.includes("\\") || name.includes("\0") || name.split("/").some(p => ["", ".", ".."].includes(p))) {
|
|
85
|
+
throw new Error(`Unsafe seed path: ${name}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function writeSeed(root, arch, files, origin) {
|
|
91
|
+
mkdirSync(root);
|
|
92
|
+
for (const name of ["bin", "lib", "etc", "usr/lib", "usr/bin", "tmp", "root", "dev", "proc", "sys", "run"]) {
|
|
93
|
+
mkdirSync(join(root, name), { recursive: true });
|
|
94
|
+
}
|
|
95
|
+
for (const [name, bytes] of files) {
|
|
96
|
+
const executable = name.startsWith("bin/") || name.startsWith("usr/bin/")
|
|
97
|
+
|| name === shell + "index.js" || /\.so\.[16]$/.test(name);
|
|
98
|
+
write(join(root, name), bytes, executable ? 0o755 : 0o644);
|
|
99
|
+
}
|
|
100
|
+
chmodSync(join(root, "tmp"), 0o1777);
|
|
101
|
+
write(join(root, "bin/sh"), `#!/bin/bun
|
|
102
|
+
// Guest entry shell; build stages use their own shell and explicit PATH.
|
|
103
|
+
const paths = [
|
|
104
|
+
"/bootstrap/result/bin",
|
|
105
|
+
"/bootstrap/toolchain/bin",
|
|
106
|
+
...(process.env.PATH || "/bin:/usr/bin").split(":"),
|
|
107
|
+
];
|
|
108
|
+
process.env.PATH = [...new Set(paths)].join(":");
|
|
109
|
+
await import("/buninu/apps/bunmsh/src/main.js");
|
|
110
|
+
`, 0o755);
|
|
111
|
+
symlinkSync(`../../lib/ld-musl-${arch}.so.1`, join(root, "usr/lib/libc.so"));
|
|
112
|
+
symlinkSync("libgcc_s.so.1", join(root, "usr/lib/libgcc_s.so"));
|
|
113
|
+
symlinkSync("libc.so", join(root, "usr/lib/libm.so"));
|
|
114
|
+
write(join(root, "etc/buninu-bootstrap.json"), JSON.stringify({ format: 1, arch, origin }, null, 2) + "\n");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const sources = [
|
|
118
|
+
["musl-1.2.6.tar.gz", "d585fd3b613c66151fc3249e8ed44f77020cb5e6c1e635a616d3f9f82460512a"],
|
|
119
|
+
["tinycc-05f0faf-upstream.tar.gz", "300d4fd6fc4a7294403e17bfd29201954410ac2baa94f189526f6bef83524c19"],
|
|
120
|
+
["tinycc-local-bootstrap-files.tar.gz", "59216479774f3c61b06420ca9b03ad4b8dcbdbb242d2d236d2daace135e35d59"],
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
export function sourceArchives() {
|
|
124
|
+
return sources.map(([name, hash]) => {
|
|
125
|
+
const bytes = readFileSync(join(bootstrap, "sources", name));
|
|
126
|
+
if (sha256(bytes) !== hash) throw new Error(`Bundled source hash mismatch: ${name}`);
|
|
127
|
+
return new Bun.Archive(bytes);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function installSources(root, archives) {
|
|
132
|
+
const src = join(root, "bootstrap/src");
|
|
133
|
+
mkdirSync(src, { recursive: true });
|
|
134
|
+
await archives[0].extract(src);
|
|
135
|
+
renameSync(join(src, "musl-1.2.6"), join(src, "musl"));
|
|
136
|
+
await archives[1].extract(src);
|
|
137
|
+
renameSync(join(src, "tinycc-05f0faf"), join(src, "tinycc"));
|
|
138
|
+
await archives[2].extract(root);
|
|
139
|
+
// The overlay provides the cc wrapper. It becomes usable at stage 4.
|
|
140
|
+
chmodSync(join(root, "bin/cc"), 0o755);
|
|
141
|
+
// Copy the recipes/helpers, but do not duplicate the extracted tarballs.
|
|
142
|
+
const target = join(root, "bootstrap");
|
|
143
|
+
mkdirSync(target, { recursive: true });
|
|
144
|
+
const stages = readdirSync(bootstrap).filter(name => /^(?:[1-9]|1[0-2])\..*\.js$/.test(name));
|
|
145
|
+
for (const name of [...stages, "lib", "README.md", "downloads.md", "NOTICE.md", "LICENSE", "LICENSES"]) {
|
|
146
|
+
cpSync(join(bootstrap, name), join(target, name), { recursive: true, errorOnExist: true, force: false });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#include <stdio.h>
|
|
2
|
+
#include <stdlib.h>
|
|
3
|
+
#include <string.h>
|
|
4
|
+
static int initialized;
|
|
5
|
+
__attribute__((constructor)) static void init(void) { initialized = 37; }
|
|
6
|
+
__attribute__((destructor)) static void fini(void) { puts("destructor OK"); }
|
|
7
|
+
int main(int argc, char **argv) {
|
|
8
|
+
if (initialized != 37) return 1;
|
|
9
|
+
if (argc != 3 || strcmp(argv[1], "hello") || strcmp(argv[2], "two words")) return 2;
|
|
10
|
+
const char *value = getenv("CRT_TEST");
|
|
11
|
+
if (!value || strcmp(value,"seed-free")) return 3;
|
|
12
|
+
puts("argc/argv, environment, constructor OK");
|
|
13
|
+
return 42;
|
|
14
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Standalone TinyCC build used by stage 4 inside the target musl rootfs.
|
|
2
|
+
import { chmodSync, cpSync, existsSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { bun, run } from "./common.js";
|
|
5
|
+
|
|
6
|
+
export async function buildTcc() {
|
|
7
|
+
const root = "/bootstrap/src/tinycc";
|
|
8
|
+
if (!["x64", "arm64"].includes(process.arch)) throw new Error("Unsupported architecture");
|
|
9
|
+
const work = "/bootstrap/build/tinycc";
|
|
10
|
+
const runtime = "/bootstrap/toolchain/lib/tcc";
|
|
11
|
+
mkdirSync(work, { recursive: true });
|
|
12
|
+
mkdirSync(runtime, { recursive: true });
|
|
13
|
+
mkdirSync("/bootstrap/toolchain/bin", { recursive: true });
|
|
14
|
+
cpSync(join(root, "include"), join(runtime, "include"), { recursive: true });
|
|
15
|
+
const stage1 = "/bootstrap/toolchain/bin/tcc";
|
|
16
|
+
const stage2 = join(work, "tcc-stage2");
|
|
17
|
+
const flags = ["-B/bootstrap/toolchain/lib/tcc", "-nostdinc", "-I/usr/include", "-I/bootstrap/toolchain/lib/tcc/include", "-L/usr/lib", "-L/lib"];
|
|
18
|
+
|
|
19
|
+
console.log("1. Build standalone TinyCC using Bun.cc");
|
|
20
|
+
await run([bun(), join(root, "bun-bootstrap.js"), join(root, "bun-tcc-cli.c"), stage1, "0"]);
|
|
21
|
+
await run([stage1, "-v"]);
|
|
22
|
+
|
|
23
|
+
if (process.arch === "x64") {
|
|
24
|
+
console.log("2. Build x64 helpers for programs compiled by cc");
|
|
25
|
+
const objects = [];
|
|
26
|
+
for (const name of ["libtcc1", "va_list"]) {
|
|
27
|
+
const object = join(work, `${name}.o`);
|
|
28
|
+
await run([stage1, ...flags, "-c", join(root, "lib", `${name}.c`), "-o", object]);
|
|
29
|
+
objects.push(object);
|
|
30
|
+
}
|
|
31
|
+
await run([stage1, "-ar", "rcs", join(runtime, "libbootstrap-tcc-x64.a"), ...objects]);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log("3. Rebuild TinyCC with stage1");
|
|
35
|
+
await run([stage1, ...flags, join(root, "bun-tcc-cli.c"), "-lgcc_s", "-o", stage2]);
|
|
36
|
+
const a = await Bun.file(stage1).bytes();
|
|
37
|
+
const b = await Bun.file(stage2).bytes();
|
|
38
|
+
if (!Buffer.from(a).equals(Buffer.from(b))) throw new Error("stage1/stage2 differ");
|
|
39
|
+
console.log("stage1/stage2 byte-identical: true");
|
|
40
|
+
|
|
41
|
+
if (!existsSync("/bin/cc")) throw new Error("Extract the overlay's bin/cc first");
|
|
42
|
+
chmodSync("/bin/cc", 0o755);
|
|
43
|
+
console.log("4. Compile and run the libc smoke test through cc");
|
|
44
|
+
await run(["/bin/cc", join(root, "bun-hello-libc.c"), "-o", join(work, "hello-cc")]);
|
|
45
|
+
await run([join(work, "hello-cc")]);
|
|
46
|
+
if (process.arch === "x64") {
|
|
47
|
+
console.log("5. Check x64 conversions and variadic calls");
|
|
48
|
+
await run(["/bin/cc", join(root, "bun-runtime-check.c"), "-o", join(work, "runtime-check")]);
|
|
49
|
+
await run([join(work, "runtime-check")]);
|
|
50
|
+
}
|
|
51
|
+
console.log("Ready: /bin/cc (TinyCC stage1)");
|
|
52
|
+
}
|
|
53
|
+
|
|
Binary file
|
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
`/bin/tar` is a small Bun script over `Bun.Archive`. It handles uncompressed
|
|
4
4
|
tar archives plus gzip and zstd without invoking another program.
|
|
5
5
|
|
|
6
|
+
Alpine APK v2 archives are supported directly, including their concatenated
|
|
7
|
+
gzip streams and tar sections. For example:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
tar -tf musl-dev-1.2.6-r2.apk
|
|
11
|
+
tar -xf musl-dev-1.2.6-r2.apk -C ./musl-dev
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
This extracts archive contents, including package metadata; it does not
|
|
15
|
+
install the package or execute its package scripts.
|
|
16
|
+
|
|
6
17
|
## Synopsis
|
|
7
18
|
|
|
8
19
|
```sh
|
package/package.json
CHANGED
package/scripts/fetch.js
CHANGED
|
@@ -98,6 +98,8 @@ export const readTar = (path) => {
|
|
|
98
98
|
if (type === "0" || type === "\0") files.set(name.replace(/^\.\//, ""), body);
|
|
99
99
|
}
|
|
100
100
|
return {
|
|
101
|
+
// Enumerate regular files without exposing the Map's mutation methods.
|
|
102
|
+
entries() { return files.entries(); },
|
|
101
103
|
get(name) {
|
|
102
104
|
const bytes = files.get(name);
|
|
103
105
|
if (!bytes) throw new Error(`${path}: no ${name}`);
|