buninu-linux 0.7.55 → 0.8.55

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.
Files changed (45) hide show
  1. package/Dockerfile +5 -2
  2. package/NOTICE.md +64 -3
  3. package/README.md +435 -140
  4. package/fetch-npm.sh +44 -0
  5. package/fetch-plus-build.sh +1 -0
  6. package/index.js +15 -13
  7. package/initramfs/bin/buninu-browser +24 -0
  8. package/initramfs/bin/jspinyin +12 -0
  9. package/initramfs/bin/jspulse +2 -0
  10. package/initramfs/bin/tar +40 -6
  11. package/initramfs/bin/top +1 -7
  12. package/initramfs/buninu/CHANGELOG.md +4 -0
  13. package/initramfs/buninu/apps/bunmsh/CHANGELOG.md +28 -0
  14. package/initramfs/buninu/apps/bunmsh/package.json +1 -1
  15. package/initramfs/buninu/apps/bunmsh/src/main.js +7 -5
  16. package/initramfs/buninu/apps/bunmsh/src/shell.js +584 -75
  17. package/initramfs/buninu/apps/bunmsh/test/shell1.test.js +64 -0
  18. package/initramfs/buninu/package.json +1 -1
  19. package/initramfs/init.js +5 -0
  20. package/initramfs/lib/browser-resources.js +25 -0
  21. package/initramfs/lib/tar-exclude.js +20 -0
  22. package/initramfs/lib/tar-strip.js +94 -0
  23. package/initramfs/lib/top-memory.js +12 -0
  24. package/initramfs/usr/share/doc/buninu-linux/tar.md +24 -1
  25. package/initramfs/usr/share/doc/buninu-linux/top.md +18 -0
  26. package/package.json +1 -1
  27. package/scripts/fetch-npm.js +51 -0
  28. package/scripts/fetch.js +1 -0
  29. package/scripts/js-tools.js +3 -0
  30. package/scripts/npm-packages.md +18 -0
  31. package/scripts/pack-initramfs.sh +1 -0
  32. package/scripts/pack.js +2 -0
  33. package/scripts/stage-npm.js +57 -0
  34. package/scripts/stage-npm.sh +62 -0
  35. package/test/fetch-npm.test.js +92 -0
  36. package/test/host/Dockerfile +6 -4
  37. package/test/host/boot-test.exp +2 -2
  38. package/test/host/host-test.sh +2 -2
  39. package/test/host/in-container.sh +4 -4
  40. package/test/js-tools-selection.test.js +19 -0
  41. package/test/npm-launchers.test.js +59 -0
  42. package/test/stage-npm.test.js +130 -0
  43. package/test/tar-strip.test.js +218 -0
  44. package/test/top-memory.test.js +23 -0
  45. package/initramfs/buninu/apps/bunmsh/test/shell.test.js +0 -2092
package/fetch-npm.sh ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ umask 022
4
+ cd "$(dirname "$0")"
5
+
6
+ # These application tarballs are architecture-independent. Do not install,
7
+ # extract, or run npm lifecycle scripts here; pack-time layout comes later.
8
+ downloads_dir="$PWD/downloads/npm"
9
+ . ./scripts/fetch.sh
10
+
11
+ # Fixed three-line YAML blocks, not shell code: never source or eval them.
12
+ header='^"((@[a-z0-9._-]+/)?[a-z0-9._-]+)@([0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?)":$'
13
+ source='^ - (https://registry\.npmjs\.org/[^[:space:]"#]+\.tgz)$'
14
+ digest='^ - sha256:([a-f0-9]{64})$'
15
+ invalid() { echo "error: invalid or duplicate npm pin: $line" >&2; exit 1; }
16
+ state=0
17
+ seen='|'
18
+ archives=() urls=() hashes=()
19
+ while IFS= read -r line || [[ -n "$line" ]]; do
20
+ line=${line%$'\r'}
21
+ [[ "$line" =~ ^[[:space:]]*$ || "$line" == \#* ]] && continue
22
+ case "$state" in
23
+ 0)
24
+ [[ "$line" =~ $header ]] || invalid
25
+ name=${BASH_REMATCH[1]} version=${BASH_REMATCH[3]}
26
+ archive=${name#@}
27
+ archive=${archive/\//-}-$version.tgz
28
+ [[ "$seen" != *"|$archive|"* ]] || invalid
29
+ seen="$seen$archive|"
30
+ state=1 ;;
31
+ 1)
32
+ [[ "$line" =~ $source ]] || invalid
33
+ url=${BASH_REMATCH[1]}
34
+ state=2 ;;
35
+ 2)
36
+ [[ "$line" =~ $digest ]] || invalid
37
+ archives+=("$archive") urls+=("$url") hashes+=("${BASH_REMATCH[1]}")
38
+ state=0 ;;
39
+ esac
40
+ done < scripts/npm-packages.md
41
+ [[ "$state" == 0 && ${#archives[@]} -gt 0 ]] || invalid
42
+ for ((i=0; i<${#archives[@]}; i++)); do
43
+ fetch "${urls[i]}" "${archives[i]}" "${hashes[i]}"
44
+ done
@@ -5,6 +5,7 @@ cd "$(dirname "$0")"
5
5
 
6
6
  ./fetch-alpine.sh
7
7
  ./fetch-bun.sh
8
+ ./fetch-npm.sh
8
9
  ./build-uki.sh
9
10
  ./build-image.sh
10
11
 
package/index.js CHANGED
@@ -12,6 +12,7 @@ import { createHash } from "node:crypto";
12
12
  import { accessSync, constants, copyFileSync, existsSync, readFileSync } from "node:fs";
13
13
  import { delimiter, dirname, join, resolve } from "node:path";
14
14
  import { fileURLToPath } from "node:url";
15
+ import { jsToolsEnabled } from "./scripts/js-tools.js";
15
16
 
16
17
  const rootDir = dirname(fileURLToPath(import.meta.url));
17
18
  const invocationDir = process.cwd();
@@ -43,7 +44,7 @@ const stages = [
43
44
  {
44
45
  flag: "fetch",
45
46
  short: "f",
46
- scripts: ["fetch-alpine.sh", "fetch-bun.sh"],
47
+ scripts: ["fetch-alpine.sh", "fetch-bun.sh", "fetch-npm.sh"],
47
48
  tools: () => ["curl", "unzip", "tar", "gzip", "sha256sum"],
48
49
  },
49
50
  {
@@ -83,17 +84,18 @@ Usage:
83
84
 
84
85
  Stages (always run in this order, whichever you pick):
85
86
  -f, --fetch download and verify the pinned kernel, musl, GCC runtime,
86
- EFI stub and Bun (fetch-alpine.sh, fetch-bun.sh)
87
+ EFI stub, Bun and npm application tarballs
88
+ (scripts/fetch.js by default)
87
89
  -b, --build pack the initramfs, assemble the UKI, write vda.img
88
- (build-uki.sh, build-image.sh)
90
+ (scripts/build.js by default)
89
91
  -r, --run boot vda.img under QEMU (run-qemu.sh), with KVM or
90
92
  Hypervisor.framework when the guest matches the host CPU
91
93
 
92
94
  --arch ARCH guest architecture: x86_64 (default; alias amd64, x64) or
93
95
  aarch64 (alias arm64, native speed on Apple Silicon). A bare
94
96
  -r boots whichever one vda.img was last built for
95
- --docker run fetch and build inside a Debian container built from
96
- ./Dockerfile; always on for macOS, which lacks the toolchain
97
+ --docker with JS tools disabled, run fetch/build in the Debian
98
+ container from ./Dockerfile; automatic on macOS in shell mode
97
99
 
98
100
  --export after -b/--build, copy the image to
99
101
  ./buninu-linux-${pkg.version}.img (x86_64) or
@@ -120,11 +122,12 @@ initramfs/init.js needs, -r just boots what is already there. Anything after
120
122
  -- goes to qemu-system-x86_64 or qemu-system-aarch64 verbatim (for example:
121
123
  -r -- -smp 4).
122
124
 
123
- With BUNINU_JS_TOOLS=1 in the environment, fetch and build are done in
124
- JavaScript (scripts/fetch.js, scripts/build.js) and need only node or bun:
125
- no Debian toolchain and no Docker.
125
+ Fetch and build use JavaScript by default (scripts/fetch.js, scripts/build.js).
126
+ Host Bun is required for npm extraction; no Debian toolchain or Docker is used.
127
+ Set BUNINU_JS_TOOLS=0, off or false to select the shell tools instead
128
+ (case-insensitive; surrounding whitespace is ignored).
126
129
 
127
- Fetch and build need the Debian toolchain from README "Build environment"
130
+ With JS tools disabled, fetch and build need the Debian toolchain from README "Build environment"
128
131
  (PRoot, a Debian or Ubuntu host, or --docker); run needs QEMU (Termux
129
132
  pkg, apt, or brew install qemu). Tools missing for the stages you picked are
130
133
  reported before anything starts.
@@ -254,10 +257,9 @@ function hasTool(name) {
254
257
  return false;
255
258
  }
256
259
 
257
- // BUNINU_JS_TOOLS=1: fetch and build run scripts/fetch.js and scripts/build.js
258
- // in this process instead of the shell scripts, so they need no toolchain
259
- // and no Docker; run is unchanged.
260
- const jsTools = process.env.BUNINU_JS_TOOLS === "1";
260
+ // JS tools are the default; explicit 0/off/false selects shell tools.
261
+ // Run remains unchanged. JS fetch/build bypass Docker.
262
+ const jsTools = jsToolsEnabled();
261
263
  const usesJs = (stage) => jsTools && stage.flag !== "run";
262
264
  const inDocker = (stage) => useDocker && stage.flag !== "run" && !jsTools;
263
265
 
@@ -0,0 +1,24 @@
1
+ #!/bin/bun
2
+ // Keep distribution resource setup and the offline casty launcher outside the
3
+ // npm package, so a later `bun i` can replace that package without losing them.
4
+ import { resolve, join } from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+ import { ensureBrowserResources } from "../lib/browser-resources.js";
7
+
8
+ const root = resolve(import.meta.dir, "..");
9
+ const modules = join(root, "usr/lib/node_modules");
10
+ const browser = join(modules, "buninu-browser");
11
+ ensureBrowserResources(browser, root);
12
+ const entry = join(browser, "index.js");
13
+ const { chooseFrontend } = await import(pathToFileURL(entry).href);
14
+ const args = process.argv.slice(2);
15
+ const choice = chooseFrontend(args);
16
+ const casty = choice.frontend === "casty";
17
+ const child = Bun.spawn({
18
+ cmd: [process.execPath, casty ? join(modules, "@drxiaozhi/casty/index.js") : entry,
19
+ ...(casty ? choice.args : args)],
20
+ env: casty ? { ...process.env, CASTY_BROWSER: join(browser, "buninu-browser.js") } : process.env,
21
+ stdio: ["inherit", "inherit", "inherit"],
22
+ });
23
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) process.on(signal, () => child.kill(signal));
24
+ process.exit(await child.exited);
@@ -0,0 +1,12 @@
1
+ #!/bin/bun
2
+ // Use the distribution's jsmdcui instead of the package's `bun x` launcher.
3
+ import { resolve } from "node:path";
4
+
5
+ const root = resolve(import.meta.dir, "..");
6
+ const child = Bun.spawn({
7
+ cmd: [process.execPath, resolve(root, "buninu/apps/jsmdcui/src/index.js"), "--tui",
8
+ ...process.argv.slice(2), resolve(root, "usr/lib/node_modules/@drxiaozhi/jspinyin/im.md")],
9
+ stdio: ["inherit", "inherit", "inherit"],
10
+ });
11
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) process.on(signal, () => child.kill(signal));
12
+ process.exit(await child.exited);
@@ -0,0 +1,2 @@
1
+ #!/bin/bun
2
+ import "../usr/lib/node_modules/@drxiaozhi/jspulse/jspulse.js";
package/initramfs/bin/tar CHANGED
@@ -5,6 +5,8 @@ import { lstat, readdir } from "node:fs/promises";
5
5
  import { linkSync, mkdirSync, rmSync, symlinkSync } from "node:fs";
6
6
  import { basename, dirname, join, resolve } from "node:path";
7
7
  import { showDocument } from "../lib/document.js";
8
+ import { extractStripped } from "../lib/tar-strip.js";
9
+ import { excludeMatcher } from "../lib/tar-exclude.js";
8
10
 
9
11
  const fail = (message) => {
10
12
  console.error(`tar: ${message}`);
@@ -22,7 +24,7 @@ Try 'tar --help' for the full manual.`);
22
24
  process.exit(2);
23
25
  };
24
26
 
25
- const options = { operation: null, archive: null, directory: ".", compression: null, verbose: false, operands: [] };
27
+ const options = { operation: null, archive: null, directory: ".", compression: null, verbose: false, operands: [], strip: 0, excludes: [] };
26
28
  const argv = process.argv.slice(2);
27
29
  if (argv.length === 0) usage();
28
30
  const take = (index, option) => {
@@ -41,14 +43,20 @@ for (let index = 0; index < argv.length; index++) {
41
43
  }
42
44
  if (argument === "--zstd") { options.compression = "zstd"; continue; }
43
45
  if (argument === "--gzip") { options.compression = "gzip"; continue; }
46
+ if (argument === "--strip-components" || argument.startsWith("--strip-components=")) {
47
+ const value = argument === "--strip-components" ? take(index++, argument) : argument.slice(19);
48
+ if (!/^\d+$/.test(value) || !Number.isSafeInteger(Number(value))) {
49
+ fail("--strip-components requires a non-negative safe integer");
50
+ }
51
+ options.strip = Number(value);
52
+ continue;
53
+ }
44
54
  // GNU tar compatibility used by js-udocker. Bun.Archive never restores
45
55
  // ownership or permissions, and extraction replaces existing files, so
46
56
  // these options already describe its normal extraction behaviour.
47
57
  if (argument === "--no-same-owner" || argument === "--no-same-permissions" || argument === "--overwrite") continue;
48
- // Bun.Archive does not extract device nodes. js-udocker separately removes
49
- // whiteout files after extraction, so its excludes are safe no-ops here.
50
- if (argument === "--exclude") { take(index++, argument); continue; }
51
- if (argument.startsWith("--exclude=")) continue;
58
+ if (argument === "--exclude") { options.excludes.push(take(index++, argument)); continue; }
59
+ if (argument.startsWith("--exclude=")) { options.excludes.push(argument.slice(10)); continue; }
52
60
  if (argument === "-C" || argument === "--directory") {
53
61
  options.directory = take(index++, argument);
54
62
  continue;
@@ -90,6 +98,7 @@ for (let index = 0; index < argv.length; index++) {
90
98
 
91
99
  if (!options.operation) fail("one of -c, -x, or -t is required");
92
100
  if (!options.archive) fail("an archive must be specified with -f");
101
+ const excluded = excludeMatcher(options.excludes);
93
102
 
94
103
  const archiveBytes = async () => {
95
104
  const bytes = options.archive === "-"
@@ -201,6 +210,8 @@ const scanEntries = (bytes) => {
201
210
  const target = extended.linkpath ?? longTarget ?? text(block, 157, 100);
202
211
  entries.push({
203
212
  name: name.replace(/^\.\//, ""),
213
+ archiveName: name,
214
+ dataOffset: offset,
204
215
  kind: entryKinds[flag] ?? "file",
205
216
  target,
206
217
  size,
@@ -281,6 +292,28 @@ const restoreLinks = (entries) => {
281
292
  }
282
293
  };
283
294
 
295
+ // Opt-in path only: keep the existing Bun.Archive extraction unchanged for
296
+ // ordinary extraction and --strip-components=0 without excludes. Reuse the
297
+ // scanned-entry extractor for excludes: no glob whitelist can accidentally
298
+ // reinterpret literal metacharacters in retained archive names.
299
+ if (options.operation === "x" && (options.strip > 0 || options.excludes.length)) {
300
+ try {
301
+ const bytes = tarBytes(await archiveBytes());
302
+ const entries = scanEntries(bytes);
303
+ if (!entries) throw new Error("invalid tar archive");
304
+ const wanted = options.operands.length ? patterns().map((pattern) => new Bun.Glob(pattern)) : null;
305
+ const members = entries.filter((entry) => !excluded(entry.name)
306
+ && !(entry.kind === "hardlink" && excluded(entry.target))
307
+ && (!wanted || wanted.some((glob) => glob.match(entry.name))));
308
+ extractStripped(bytes, members, options.directory, options.strip, {
309
+ verbose: options.verbose, filtered: options.operands.length > 0 || options.excludes.length > 0,
310
+ });
311
+ } catch (error) {
312
+ fail(error?.message || String(error));
313
+ }
314
+ process.exit(0);
315
+ }
316
+
284
317
  if (options.operation === "x" || options.operation === "t") {
285
318
  let archive;
286
319
  try {
@@ -299,7 +332,7 @@ if (options.operation === "x" || options.operation === "t") {
299
332
  .map(([name, file]) => ({ name, kind: "file", target: "", size: file.size, mode: 0o644, mtime: file.lastModified }));
300
333
 
301
334
  if (options.operation === "t") {
302
- for (const entry of members) {
335
+ for (const entry of members.filter((entry) => !excluded(entry.name))) {
303
336
  console.log(options.verbose ? verboseListing(entry) : entry.name);
304
337
  }
305
338
  } else {
@@ -321,6 +354,7 @@ const root = resolve(options.directory);
321
354
  const inputs = {};
322
355
 
323
356
  const add = async (diskPath, archivePath) => {
357
+ if (excluded(archivePath)) return;
324
358
  const info = await lstat(diskPath);
325
359
  if (info.isSymbolicLink()) fail(`${archivePath}: creating symbolic links is not supported by Bun.Archive`);
326
360
  if (info.isDirectory()) {
package/initramfs/bin/top CHANGED
@@ -2,6 +2,7 @@
2
2
  // top — periodically render snapshots and process lines supplied by ps.
3
3
 
4
4
  import { showDocument } from "../lib/document.js";
5
+ import { memoryText } from "../lib/top-memory.js";
5
6
  import {
6
7
  CLOCK_TICKS,
7
8
  formatProcessHeader,
@@ -20,13 +21,6 @@ const uptimeText = (seconds) => {
20
21
  return `${days ? `${days} day${days === 1 ? "" : "s"}, ` : ""}${hours}:${String(minutes).padStart(2, "0")}`;
21
22
  };
22
23
 
23
- const memoryText = (memory) => {
24
- const total = memory.MemTotal ?? 0;
25
- const free = memory.MemFree ?? 0;
26
- const cache = (memory.Buffers ?? 0) + (memory.Cached ?? 0) + (memory.SReclaimable ?? 0);
27
- return `MiB Mem : ${(total / 1024).toFixed(1)} total, ${(free / 1024).toFixed(1)} free, ${((total - free - cache) / 1024).toFixed(1)} used, ${(cache / 1024).toFixed(1)} buff/cache`;
28
- };
29
-
30
24
  const intervalRows = (snapshot, previous) => {
31
25
  if (!previous) return snapshot.processes;
32
26
  const elapsed = Math.max(0.001, (snapshot.at - previous.at) / 1000);
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.19 - 2026-10-12
4
+
5
+ - Sync bunmsh to 0.3.11: more POSIX syntax
6
+
3
7
  ## 0.4.18 - 2026-09-29
4
8
 
5
9
  - Sync jsgotty to 1.1.14: Kitty for Windows
@@ -2,6 +2,34 @@
2
2
 
3
3
  All notable user-visible changes to bunmsh are documented here.
4
4
 
5
+ ## [0.3.11] - 2026-10-12
6
+
7
+ ### Fixed
8
+
9
+ - Keep multiline quoted words, command substitutions and here-documents
10
+ together when parsing compound-command bodies, rather than treating their
11
+ continuation lines as separate commands or compound keywords.
12
+
13
+ - Accept additional `if`/`elif` and `while`/`until` header layouts, including
14
+ `then` or `do` on the following line, and execute commands placed on the
15
+ same line after `then`.
16
+
17
+ - Improve comment handling in compound keyword and case-arm scanning, and
18
+ include nearby source text in several compound-syntax error messages.
19
+
20
+ ### Added
21
+
22
+ - Initial support for parenthesized subshell groups, executed in a child
23
+ bunmsh with inherited shell state, including groups embedded in command
24
+ sequences and leading groups followed by `&&`, `||` or `;`.
25
+ Subshell redirection is still limited: the leading-group path handles
26
+ discarding stdout/stderr to `/dev/null`, but does not yet implement
27
+ general file redirection or descriptor duplication.
28
+
29
+ - Recognize general numeric output-descriptor duplication syntax such as
30
+ `3>&1`. Only the existing `1>&2` and `2>&1` handling is implemented for
31
+ ordinary commands; other numeric pairs are currently accepted but ignored.
32
+
5
33
  ## [0.3.10] - 2026-09-29
6
34
 
7
35
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunmsh",
3
- "version": "0.3.10",
3
+ "version": "0.3.11",
4
4
  "description": "Cross-platform command shell for Bun JavaScript",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -806,11 +806,13 @@ async function main(argv) {
806
806
 
807
807
  if (command !== null) {
808
808
  const name = argv[i] ?? "bunmsh";
809
- const state = createState({
810
- args: [name, ...argv.slice(i + 1)],
811
- mouseTracking: forceMouse || undefined,
812
- pathSearch: builtinOnly ? false : undefined,
813
- });
809
+ const state = process.env.BUNMSH_INTERNAL_PIPELINE_STATE
810
+ ? pipelineChildState()
811
+ : createState({
812
+ args: [name, ...argv.slice(i + 1)],
813
+ mouseTracking: forceMouse || undefined,
814
+ pathSearch: builtinOnly ? false : undefined,
815
+ });
814
816
  const result = await execute(command, state);
815
817
  return state.exitRequested ? state.exitStatus : result.status;
816
818
  }