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.
Files changed (77) hide show
  1. package/LICENSES/MIT-ZilchOS-bootstrap-from-tcc.txt +21 -0
  2. package/NOTICE.md +34 -0
  3. package/README.md +3 -0
  4. package/initramfs/bin/tar +34 -46
  5. package/initramfs/buninu/apps/bunmsh/CHANGELOG.md +103 -0
  6. package/initramfs/buninu/apps/bunmsh/PORTING.md +133 -15
  7. package/initramfs/buninu/apps/bunmsh/help/awk.md +38 -0
  8. package/initramfs/buninu/apps/bunmsh/help/break.md +21 -0
  9. package/initramfs/buninu/apps/bunmsh/help/chmod.md +8 -2
  10. package/initramfs/buninu/apps/bunmsh/help/cmp.md +29 -0
  11. package/initramfs/buninu/apps/bunmsh/help/continue.md +22 -0
  12. package/initramfs/buninu/apps/bunmsh/help/cp.md +4 -0
  13. package/initramfs/buninu/apps/bunmsh/help/diff.md +30 -0
  14. package/initramfs/buninu/apps/bunmsh/help/egrep.md +26 -0
  15. package/initramfs/buninu/apps/bunmsh/help/export.md +2 -2
  16. package/initramfs/buninu/apps/bunmsh/help/expr.md +30 -0
  17. package/initramfs/buninu/apps/bunmsh/help/fgrep.md +25 -0
  18. package/initramfs/buninu/apps/bunmsh/help/grep.md +4 -0
  19. package/initramfs/buninu/apps/bunmsh/help/ls.md +4 -2
  20. package/initramfs/buninu/apps/bunmsh/help/mkdir.md +1 -1
  21. package/initramfs/buninu/apps/bunmsh/help/print.md +3 -0
  22. package/initramfs/buninu/apps/bunmsh/help/return.md +23 -0
  23. package/initramfs/buninu/apps/bunmsh/help/sed.md +8 -3
  24. package/initramfs/buninu/apps/bunmsh/help/set.md +12 -3
  25. package/initramfs/buninu/apps/bunmsh/help/tr.md +4 -0
  26. package/initramfs/buninu/apps/bunmsh/help/trap.md +22 -0
  27. package/initramfs/buninu/apps/bunmsh/help/uniq.md +27 -0
  28. package/initramfs/buninu/apps/bunmsh/package.json +1 -1
  29. package/initramfs/buninu/apps/bunmsh/src/awk.js +203 -0
  30. package/initramfs/buninu/apps/bunmsh/src/diff.js +222 -0
  31. package/initramfs/buninu/apps/bunmsh/src/fancy-ls.js +15 -4
  32. package/initramfs/buninu/apps/bunmsh/src/main.js +21 -5
  33. package/initramfs/buninu/apps/bunmsh/src/sed.js +159 -0
  34. package/initramfs/buninu/apps/bunmsh/src/shell.js +1283 -540
  35. package/initramfs/buninu/apps/bunmsh/test/awk-reference.test.js +35 -0
  36. package/initramfs/buninu/apps/bunmsh/test/awk.test.js +62 -0
  37. package/initramfs/buninu/apps/bunmsh/test/posix-bootstrap.test.js +595 -0
  38. package/initramfs/buninu/apps/bunmsh/test/utility-reference-cases.mjs +98 -0
  39. package/initramfs/buninu/apps/bunmsh/test/utility-reference.test.js +23 -0
  40. package/initramfs/buninu/apps/jsgotty/client-kitty.mjs +61 -0
  41. package/initramfs/buninu/apps/jsgotty/client.mjs +16 -0
  42. package/initramfs/buninu/apps/jsgotty/package-lock.json +2 -2
  43. package/initramfs/buninu/apps/jsgotty/package.json +1 -1
  44. package/initramfs/buninu/apps/jsgotty/tests/client-kitty.test.mjs +43 -0
  45. package/initramfs/etc/resolv.conf +2 -1
  46. package/initramfs/usr/lib/bootstrap/1.prepare-rootfs.js +106 -0
  47. package/initramfs/usr/lib/bootstrap/10.build-m4.js +40 -0
  48. package/initramfs/usr/lib/bootstrap/11.build-flex.js +50 -0
  49. package/initramfs/usr/lib/bootstrap/12.build-gcc.js +261 -0
  50. package/initramfs/usr/lib/bootstrap/2.enter-rootfs.js +49 -0
  51. package/initramfs/usr/lib/bootstrap/3.gen-musl-dev.js +141 -0
  52. package/initramfs/usr/lib/bootstrap/4.build-tcc.js +61 -0
  53. package/initramfs/usr/lib/bootstrap/5.build-make.js +58 -0
  54. package/initramfs/usr/lib/bootstrap/6.build-binutils.js +77 -0
  55. package/initramfs/usr/lib/bootstrap/7.build-gmp.js +68 -0
  56. package/initramfs/usr/lib/bootstrap/8.build-mpfr.js +59 -0
  57. package/initramfs/usr/lib/bootstrap/9.build-mpc.js +55 -0
  58. package/initramfs/usr/lib/bootstrap/LICENSE +21 -0
  59. package/initramfs/usr/lib/bootstrap/LICENSES/MIT-ZilchOS-bootstrap-from-tcc.txt +21 -0
  60. package/initramfs/usr/lib/bootstrap/NOTICE.md +65 -0
  61. package/initramfs/usr/lib/bootstrap/README.md +204 -0
  62. package/initramfs/usr/lib/bootstrap/downloads.md +49 -0
  63. package/initramfs/usr/lib/bootstrap/lib/build.js +155 -0
  64. package/initramfs/usr/lib/bootstrap/lib/common.js +134 -0
  65. package/initramfs/usr/lib/bootstrap/lib/compile-object.c +14 -0
  66. package/initramfs/usr/lib/bootstrap/lib/musl-headers.js +59 -0
  67. package/initramfs/usr/lib/bootstrap/lib/seed.js +148 -0
  68. package/initramfs/usr/lib/bootstrap/lib/startup-check.c +14 -0
  69. package/initramfs/usr/lib/bootstrap/lib/tcc-build.js +53 -0
  70. package/initramfs/usr/lib/bootstrap/sources/musl-1.2.6.tar.gz +0 -0
  71. package/initramfs/usr/lib/bootstrap/sources/tinycc-05f0faf-upstream.tar.gz +0 -0
  72. package/initramfs/usr/lib/bootstrap/sources/tinycc-local-bootstrap-files.tar.gz +0 -0
  73. package/initramfs/usr/share/doc/buninu-linux/tar.md +11 -0
  74. package/package.json +1 -1
  75. package/scripts/fetch.js +2 -0
  76. package/test/bootstrap.test.js +65 -0
  77. package/test/tar-strip.test.js +36 -0
@@ -0,0 +1,98 @@
1
+ // Record system-tool results separately from the isolated bootstrap runtime:
2
+ // node test/utility-reference-cases.mjs OUTPUT.json
3
+ // BUNMSH_UTILITY_REFERENCE=/guest/OUTPUT.json bun test test/utility-reference.test.js
4
+ import {mkdtempSync, mkdirSync, writeFileSync, rmSync, chmodSync, statSync} from 'node:fs';
5
+ import {tmpdir} from 'node:os';
6
+ import {join, resolve, dirname} from 'node:path';
7
+ import {fileURLToPath} from 'node:url';
8
+ import {spawnSync} from 'node:child_process';
9
+ const diff = (name, before, after, args=[]) => ({name, tool:'diff', files:{before,after}, args:[...args,'before','after']});
10
+ const sed = (name, script, input, args=[]) => ({name, tool:'sed', files:{script}, args:[...args,'-f','script'], input});
11
+ export const cases = [
12
+ ...[
13
+ ['adjacent only', [], 'a\na\nb\na\n'],
14
+ ['count', ['-c'], 'a\na\nb\n'],
15
+ ['repeated', ['-d'], 'a\na\nb\na\n'],
16
+ ['single', ['-u'], 'a\na\nb\na\n'],
17
+ ['count repeated', ['-cd'], 'a\na\nb\n'],
18
+ ['no repeated singles', ['-du'], 'a\na\nb\n'],
19
+ ['empty input', [], ''],
20
+ ['blank lines', [], '\n\nfirst\n\n'],
21
+ ['unterminated duplicate', [], 'a\na'],
22
+ ['unterminated distinct', [], 'a\nb'],
23
+ ['binary NUL', [], 'a\0b\na\0b\na\0c\n'],
24
+ ['file operand', ['--','input'], null],
25
+ ].map(([name,args,input])=>({name,tool:'uniq',args,files:input===null?{input:'a\na\nb\n'}:{},input:input??''})),
26
+ ...[
27
+ ['remove all write bits',0o666,'a-w'],
28
+ ['add user read and write',0o004,'u+rw'],
29
+ ['assign group and other permissions',0o770,'go=rx'],
30
+ ['sequential classes',0o777,'u=rw,g=r,o='],
31
+ ['conditional execute on plain file',0o644,'a+X'],
32
+ ['conditional execute on executable',0o744,'a+X'],
33
+ ['conditional execute after removal',0o755,'a-x,a+X'],
34
+ ['assignment clears file special bits',0o4766,'a=rw'],
35
+ ['multiple operations for one class',0o777,'u+rw-x'],
36
+ ].map(([name,mode,spec])=>({name,tool:'chmod',files:{file:'content'},modes:{file:mode},args:[spec,'--','file']})),
37
+ ...[
38
+ ['identical bytes', 'abc\n', 'abc\n', []],
39
+ ['first differing byte', 'same\nabc\n', 'same\nabX\n', []],
40
+ ['quiet difference', 'abc', 'ab', ['-s']],
41
+ ['shorter file', 'abc', 'abcd', []],
42
+ ['empty file', '', 'abc', []],
43
+ ['binary difference', 'a\0b', 'a\0c', []],
44
+ ].map(([name,before,after,args])=>({name,tool:'cmp',files:{before,after},args:[...args,'--','before','after']})),
45
+ {name:'squeeze spaces without translating s',tool:'tr',files:{},args:['-s',' '],input:'size_t inbytesleft\n'},
46
+ {name:'translate then squeeze',tool:'tr',files:{},args:['-s','ab','x'],input:'aabbbcaa\n'},
47
+ {name:'delete then squeeze',tool:'tr',files:{},args:['-ds','x','a'],input:'axaxa bbb\n'},
48
+ {name:'delete only',tool:'tr',files:{},args:['-d','x'],input:'axxbbb\n'},
49
+ {name:'squeeze repeated newlines',tool:'tr',files:{},args:['-s','\\n'],input:'a\n\n\nb\n'},
50
+ {name:'literal hyphen set',tool:'tr',files:{},args:['--','-s','XY'],input:'-s-size_t\n'},
51
+ sed("make object extension","s=$[\\(\\{]OBJEXT[\\)\\}]=o=g\n","file.$(OBJEXT) file.${OBJEXT}\n"),
52
+ sed("BRE interior anchors","s/a^b/X/;s/c$d/Y/;s/^z$/Z/\n","a^b c$d\nz\n"),
53
+ {name:'read file after address',tool:'sed',files:{extra:'inserted\n'},args:['/mark/r extra'],input:'before\nmark\nafter\n'},
54
+ {name:'read missing file',tool:'sed',files:{},args:['r missing'],input:'kept\n'},
55
+ {name:'read file with deleted pattern',tool:'sed',files:{extra:'inserted\n'},args:['-n','-e','r extra','-e','d'],input:'one\ntwo\n'},
56
+ diff('identical','a\nb\n','a\nb\n'),
57
+ diff('middle insertion','a\nb\nc\n','a\nx\nb\nc\n'),
58
+ diff('separated insertions','a\nb\nc\nd\n','a\nx\nb\nc\ny\nd\n'),
59
+ diff('separated deletions','a\nx\nb\nc\ny\nd\n','a\nb\nc\nd\n'),
60
+ diff('insertion then replacement','a\nb\nc\nd\ne\n','a\nx\nb\nc\nD\ne\n'),
61
+ diff('repeated lines','a\nsame\nsame\nz\n','a\nsame\nnew\nsame\nz\n'),
62
+ diff('empty left','','added\n'), diff('empty right','deleted\n',''),
63
+ diff('missing final newline','a\n','a'), diff('both missing final newline','old','new'),
64
+ diff('Chinese','前\n後\n','前\n中\n後\n'),
65
+ diff('CRLF','a\r\nb\r\n','a\r\nx\r\nb\r\n'),
66
+ diff('brief difference','a\n','b\n',['-q']),
67
+ sed('file script and branch','s/a/b/\nt done\n:done\np\n','a\n',['-n']),
68
+ sed('address range','2,3p\n','one\ntwo\nthree\nfour\n',['-n']),
69
+ sed('inverted address','/skip/!p\n','keep\nskip\nkeep again\n',['-n']),
70
+ sed('hold exchange','1h\n2{x;p;}\n','first\nsecond\n',['-n']),
71
+ sed('hold append','1h\n2{H;g;p;}\n','first\nsecond\n',['-n']),
72
+ sed('multiline N','N;s/\n/:/;p\n'.replace('s/\n','s/\\n'),'first\nsecond\n',['-n']),
73
+ sed('branch loop',':again\ns/aa/a/\nt again\np\n','aaaa\n',['-n']),
74
+ sed('translation','y/abc/ABC/\n','abc cab\n'),
75
+ sed('escaped delimiter','s|a\\|b|X|g\n','a|b a|b\n'),
76
+ sed('backreference','s/\\(ab\\)/[\\1]/g\n','abab\n'),
77
+ sed('missing input newline','s/a/b/\n','a'),
78
+ {name:'attached -f',tool:'sed',files:{script:'s/a/b/\n'},args:['-fscript'],input:'a\n'},
79
+ ];
80
+ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
81
+ if (!process.argv[2]) throw new Error('Output JSON path required');
82
+ const root=mkdtempSync(join(tmpdir(),'bunmsh-system-reference-'));
83
+ try {
84
+ const results=cases.map((c,index) => {
85
+ const cwd=join(root,String(index)); mkdirSync(cwd);
86
+ for (const [name,content] of Object.entries(c.files)) {
87
+ mkdirSync(dirname(join(cwd,name)),{recursive:true});writeFileSync(join(cwd,name),content);
88
+ }
89
+ for (const [name,mode] of Object.entries(c.modes??{})) chmodSync(join(cwd,name),mode);
90
+ const r=spawnSync(`/usr/bin/${c.tool}`,c.args,{cwd,input:c.input??'',encoding:'utf8',env:{...process.env,LC_ALL:'C'}});
91
+ if (r.error || r.signal || r.status===null) throw r.error??new Error(`Reference failed: ${c.name}`);
92
+ return {...c,expected:{status:r.status,stdout:r.stdout,stderr:r.stderr,...(c.modes?{modes:Object.fromEntries(Object.keys(c.modes).map(name=>[name,statSync(join(cwd,name)).mode&0o7777]))}:{})}};
93
+ });
94
+ const versions=Object.fromEntries(['diff','sed','tr','cmp','chmod','uniq'].map(tool=>[tool,spawnSync(`/usr/bin/${tool}`,['--version'],{encoding:'utf8'}).stdout.split('\n')[0]]));
95
+ writeFileSync(process.argv[2],JSON.stringify({versions,cases:results},null,2)+'\n');
96
+ console.log(JSON.stringify({versions,cases:results.length}));
97
+ } finally {rmSync(root,{recursive:true,force:true});}
98
+ }
@@ -0,0 +1,23 @@
1
+ import {test,expect} from 'bun:test';
2
+ import {mkdtempSync,mkdirSync,readFileSync,writeFileSync,rmSync,chmodSync,statSync} from 'node:fs';
3
+ import {tmpdir} from 'node:os';
4
+ import {join,dirname} from 'node:path';
5
+ import {createState,execute,decode} from '../src/shell.js';
6
+
7
+ // Only data crosses into the guest. No host executables are mounted or spawned.
8
+ const path=process.env.BUNMSH_UTILITY_REFERENCE;
9
+ const fixtures=path ? JSON.parse(readFileSync(path,'utf8')).cases : [];
10
+ if (!path) test.skip('system diff/sed reference (set BUNMSH_UTILITY_REFERENCE)',()=>{});
11
+ for (const c of fixtures) test(`system ${c.tool} reference: ${c.name}`,async()=>{
12
+ const cwd=mkdtempSync(join(tmpdir(),'bunmsh-utility-reference-'));
13
+ try {
14
+ for (const [name,content] of Object.entries(c.files)) {
15
+ mkdirSync(dirname(join(cwd,name)),{recursive:true});writeFileSync(join(cwd,name),content);
16
+ }
17
+ for (const [name,mode] of Object.entries(c.modes??{})) chmodSync(join(cwd,name),mode);
18
+ const state=createState({cwd,env:{LC_ALL:'C'},shellOptions:{posix:true}});
19
+ const quote = value => "'" + value.replaceAll("'", "'\\''") + "'";
20
+ const r=await execute(['builtin',c.tool,...c.args].map(quote).join(' '),state,{capture:true,input:new Response(c.input??'').body});
21
+ expect({status:r.status,stdout:decode(r.stdout),stderr:decode(r.stderr),...(c.modes?{modes:Object.fromEntries(Object.keys(c.modes).map(name=>[name,statSync(join(cwd,name)).mode&0o7777]))}:{})}).toEqual(c.expected);
22
+ } finally {rmSync(cwd,{recursive:true,force:true});}
23
+ });
@@ -0,0 +1,61 @@
1
+ const KITTY_CHUNK_SIZE = 4096;
2
+ const KITTY_TRANSPORT_KEYS = new Set(["m", "o", "O", "S", "t", "U", "mime"]);
3
+
4
+ function kittyPacket(control, payload = "") {
5
+ const entries = Object.entries(control)
6
+ .filter(([, value]) => value !== undefined && value !== null && value !== false)
7
+ .map(([key, value]) => value === true ? key : `${key}=${value}`);
8
+ return `\u001b_G${entries.join(",")};${payload}\u001b\\`;
9
+ }
10
+
11
+ /** Convert the server's browser-neutral Kitty message back to native APCs. */
12
+ export function encodeKittyGraphics(message) {
13
+ if (!message || typeof message !== "object") return "";
14
+ if (message.kind === "delete") {
15
+ const deletion = message.delete || {};
16
+ return kittyPacket({
17
+ a: "d",
18
+ d: deletion.scope || "a",
19
+ i: deletion.imageId || undefined,
20
+ p: deletion.placementId || undefined,
21
+ });
22
+ }
23
+ if (message.kind !== "placement" || !message.image?.data) return "";
24
+
25
+ const original = { ...(message.control || {}) };
26
+ const action = original.a || "t";
27
+ const placementOnly = action === "p";
28
+ const transfer = {};
29
+ for (const [key, value] of Object.entries(original)) {
30
+ if (!KITTY_TRANSPORT_KEYS.has(key)) transfer[key] = value;
31
+ }
32
+ transfer.a = placementOnly ? "t" : action;
33
+ transfer.t = "d";
34
+ transfer.f = message.image.format || transfer.f || 32;
35
+ if (message.image.width > 0) transfer.s = message.image.width;
36
+ if (message.image.height > 0) transfer.v = message.image.height;
37
+ if (!transfer.i && /^\d+$/.test(String(message.image.id || ""))) transfer.i = message.image.id;
38
+
39
+ const payload = String(message.image.data);
40
+ let output = "";
41
+ for (let offset = 0; offset < payload.length; offset += KITTY_CHUNK_SIZE) {
42
+ const more = offset + KITTY_CHUNK_SIZE < payload.length;
43
+ const control = offset === 0 ? { ...transfer, m: more ? 1 : 0 } : { m: more ? 1 : 0 };
44
+ output += kittyPacket(control, payload.slice(offset, offset + KITTY_CHUNK_SIZE));
45
+ }
46
+ if (placementOnly) output += kittyPacket(original);
47
+ return output;
48
+ }
49
+
50
+ /** Cursor motion the server adds for browser overlays; native Kitty does it itself. */
51
+ export function syntheticKittyCursorMotion(message) {
52
+ if (message?.kind !== "placement") return "";
53
+ const control = message.control || {};
54
+ if (control.C === "1") return "";
55
+ const cols = Math.max(0, Number.parseInt(control.c || "0", 10) || 0);
56
+ const rows = Math.max(0, Number.parseInt(control.r || "0", 10) || 0);
57
+ const cursorCol = Math.max(1, Number.parseInt(message.cursor?.col || "1", 10) || 1);
58
+ const targetCol = Math.max(1, cursorCol + cols);
59
+ if (rows > 0) return "\r\n".repeat(rows) + `\u001b[${targetCol}G`;
60
+ return cols > 0 ? `\u001b[${targetCol}G` : "";
61
+ }
@@ -3,6 +3,7 @@
3
3
  import process from "node:process";
4
4
  import { Buffer } from "node:buffer";
5
5
  import WebSocket from "ws";
6
+ import { encodeKittyGraphics, syntheticKittyCursorMotion } from "./client-kitty.mjs";
6
7
 
7
8
  const MSG_INPUT = "1";
8
9
  const MSG_PING = "2";
@@ -15,6 +16,7 @@ const MSG_SET_WINDOW_TITLE = "3";
15
16
  const MSG_SET_PREFERENCES = "4";
16
17
  const MSG_SET_RECONNECT = "5";
17
18
  const MSG_SET_BUFFER_SIZE = "6";
19
+ const MSG_KITTY_GRAPHICS = "7";
18
20
  const MSG_SET_ROLE = "a";
19
21
  const MSG_CONTROL_RESULT = "b";
20
22
 
@@ -420,6 +422,7 @@ async function main() {
420
422
  let fallbackOutputTail = "";
421
423
  let viewerInputActive = false;
422
424
  let sessionPid = "(unknown)";
425
+ let pendingKittyCursorMotion = "";
423
426
 
424
427
  const normalizeEcho = (value) => String(value)
425
428
  .replaceAll("\r", "")
@@ -603,6 +606,11 @@ pid:
603
606
  case MSG_OUTPUT:
604
607
  {
605
608
  const output = Buffer.from(payload, "base64");
609
+ if (pendingKittyCursorMotion && output.equals(Buffer.from(pendingKittyCursorMotion, "utf8"))) {
610
+ pendingKittyCursorMotion = "";
611
+ break;
612
+ }
613
+ pendingKittyCursorMotion = "";
606
614
  process.stdout.write(output);
607
615
  if (fallbackProbe && accessRole === "") {
608
616
  fallbackOutputTail = normalizeEcho(
@@ -625,6 +633,14 @@ pid:
625
633
  process.stdout.write(`\u001b]0;${payload}\u0007`);
626
634
  }
627
635
  break;
636
+ case MSG_KITTY_GRAPHICS:
637
+ try {
638
+ const message = JSON.parse(payload);
639
+ const encoded = encodeKittyGraphics(message);
640
+ pendingKittyCursorMotion = syntheticKittyCursorMotion(message);
641
+ if (encoded) process.stdout.write(encoded);
642
+ } catch {}
643
+ break;
628
644
  case MSG_SET_RECONNECT: {
629
645
  try {
630
646
  const reconnect = JSON.parse(payload);
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "jsgotty",
3
- "version": "1.1.14",
3
+ "version": "1.1.15",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "jsgotty",
9
- "version": "1.1.14",
9
+ "version": "1.1.15",
10
10
  "dependencies": {
11
11
  "node-addon-api": "^7.1.1",
12
12
  "ws": "^8.18.3"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsgotty",
3
- "version": "1.1.14",
3
+ "version": "1.1.15",
4
4
  "description": "Share your terminal as a web application: A JavaScript rewrite of GoTTY",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -0,0 +1,43 @@
1
+ import { expect, test } from "bun:test";
2
+ import { encodeKittyGraphics, syntheticKittyCursorMotion } from "../client-kitty.mjs";
3
+
4
+ test("CLI converts a server Kitty placement to native direct-transfer APC", () => {
5
+ const encoded = encodeKittyGraphics({
6
+ kind: "placement",
7
+ control: { a: "T", t: "f", f: "100", i: "7", p: "9", c: "4", r: "2", o: "z" },
8
+ image: { id: "7", format: 100, width: 30, height: 20, data: "AA==" },
9
+ });
10
+ expect(encoded).toBe("\x1b_Ga=T,f=100,i=7,p=9,c=4,r=2,t=d,s=30,v=20,m=0;AA==\x1b\\");
11
+ });
12
+
13
+ test("CLI retransmits cached data before a placement-only Kitty command", () => {
14
+ const data = "A".repeat(4100);
15
+ const encoded = encodeKittyGraphics({
16
+ kind: "placement",
17
+ control: { a: "p", i: "8", p: "2", C: "1" },
18
+ image: { id: "8", format: 100, width: 1, height: 1, data },
19
+ });
20
+ expect(encoded).toContain("\x1b_Ga=t,i=8,p=2,C=1,t=d,f=100,s=1,v=1,m=1;");
21
+ expect(encoded).toContain("\x1b_Gm=0;AAAA\x1b\\");
22
+ expect(encoded.endsWith("\x1b_Ga=p,i=8,p=2,C=1;\x1b\\")).toBe(true);
23
+ });
24
+
25
+ test("CLI converts Kitty deletion messages", () => {
26
+ expect(encodeKittyGraphics({
27
+ kind: "delete",
28
+ delete: { scope: "I", imageId: "7", placementId: "3" },
29
+ })).toBe("\x1b_Ga=d,d=I,i=7,p=3;\x1b\\");
30
+ });
31
+
32
+ test("CLI identifies browser-only cursor motion after native Kitty placement", () => {
33
+ expect(syntheticKittyCursorMotion({
34
+ kind: "placement",
35
+ cursor: { row: 3, col: 4 },
36
+ control: { c: "6", r: "2" },
37
+ })).toBe("\r\n\r\n\x1b[10G");
38
+ expect(syntheticKittyCursorMotion({
39
+ kind: "placement",
40
+ cursor: { row: 3, col: 4 },
41
+ control: { c: "6", r: "2", C: "1" },
42
+ })).toBe("");
43
+ });
@@ -1 +1,2 @@
1
- nameserver 10.0.2.3
1
+ nameserver 1.1.1.1
2
+ nameserver 8.8.8.8
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env bun
2
+ import { mkdtempSync, rmSync, statSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ import {
6
+ assertAbsent, cli, isBuninuLinux,
7
+ normalizeArch, options,
8
+ } from "./lib/common.js";
9
+ import {
10
+ installSources, localSeed,
11
+ releaseSeed, sourceArchives,
12
+ validateSeed, writeSeed,
13
+ } from "./lib/seed.js";
14
+ import { printEntry } from "./2.enter-rootfs.js";
15
+
16
+ async function main(args) {
17
+ // Optional arguments: ARCH ROOTFS.
18
+ // ARCH defaults to this Bun's CPU.
19
+ // ROOTFS defaults to roota / rootx
20
+ // in the current working directory.
21
+ const selected = options(args);
22
+ if (!selected) return;
23
+ const { arch, rootfs } = selected;
24
+ assertAbsent(rootfs);
25
+ const parent = statSync(
26
+ dirname(rootfs)
27
+ );
28
+ if (!parent.isDirectory()) {
29
+ throw new Error(
30
+ "ROOTFS parent must exist"
31
+ );
32
+ }
33
+
34
+ // Bundled musl 1.2.6 + TinyCC +
35
+ // overlay; verify before any writes.
36
+ // No musl-dev APK is required.
37
+ const archives = sourceArchives();
38
+ const work = mkdtempSync(
39
+ join(tmpdir(), "buninu-bootstrap-")
40
+ );
41
+ try {
42
+ // Local mode reads /etc/os-release
43
+ // ID=buninu-linux, then copies:
44
+ // /bin/bun, /lib/ld-musl-*.so.1,
45
+ // libgcc_s.so.1, libstdc++.so.6,
46
+ // resolv.conf, tar, unzip, env,
47
+ // their JS helpers and bunmsh.
48
+ // Resolve links to copy real data.
49
+ const local = isBuninuLinux()
50
+ && arch === normalizeArch(
51
+ process.arch
52
+ );
53
+ console.log(
54
+ "① Preparing runtime and tools " +
55
+ (local ? "from this Buninu Linux"
56
+ : "from buninu-linux@0.9.56")
57
+ );
58
+
59
+ // Otherwise fetch npm 0.9.56:
60
+ // native/<arch>/lib -> rootfs libs
61
+ // initramfs tools -> same paths.
62
+ // Fetch musl Bun 1.4.2 ZIP for
63
+ // the selected arch -> /bin/bun.
64
+ // URLs + SHA-256: downloads.md,
65
+ // parsed as YAML, like the repo's
66
+ // scripts/npm-packages.md.
67
+ const files = local
68
+ ? localSeed(arch)
69
+ : await releaseSeed(arch, work);
70
+ if (local) console.log(
71
+ "② Reusing system musl-bun"
72
+ );
73
+ validateSeed(files, arch);
74
+ writeSeed(
75
+ rootfs, arch, files,
76
+ local ? "local" : "npm@0.9.56"
77
+ );
78
+
79
+ // Extract musl and TinyCC into
80
+ // /bootstrap/src. The overlay
81
+ // adds build recipes and /bin/cc.
82
+ // Stage 3 already needs TinyCC
83
+ // source to generate CRT objects.
84
+ // Copy stages 1–12 and helpers to
85
+ // /bootstrap in rootfs.
86
+ console.log(
87
+ "③ Extracting musl, TinyCC " +
88
+ "and bootstrap overlay sources"
89
+ );
90
+ await installSources(
91
+ rootfs, archives
92
+ );
93
+ console.log(
94
+ `Prepared ${arch}: ${rootfs}`
95
+ );
96
+ printEntry(rootfs);
97
+ } finally {
98
+ // Preserve a partially written
99
+ // rootfs for inspection on error.
100
+ rmSync(work, {
101
+ recursive: true, force: true,
102
+ });
103
+ }
104
+ }
105
+
106
+ if (import.meta.main) await cli(main);
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env bun
2
+ import { writeFileSync } from "node:fs";
3
+ import { cli } from "./lib/common.js";
4
+ import {
5
+ buildRoot, command, configure,
6
+ makeIn, ready, source, stage, tools,
7
+ } from "./lib/build.js";
8
+
9
+ async function main(args) {
10
+ const { triple } = stage(
11
+ 10, "m4", args
12
+ );
13
+ const src = await source(
14
+ "m4@1.4.18", "m4-1.4.18"
15
+ );
16
+ const build = buildRoot + "/m4";
17
+ await configure(src, build, [
18
+ "--build=" + triple,
19
+ "--host=" + triple,
20
+ "--prefix=/bootstrap/toolchain",
21
+ "--disable-dependency-tracking",
22
+ ]);
23
+ await makeIn(build);
24
+ await makeIn(build, ["install"]);
25
+ writeFileSync(
26
+ build + "/verify.m4",
27
+ "define(`square',`eval($1*$1)')" +
28
+ "square(12)\n"
29
+ );
30
+ const output = await command(
31
+ [tools + "/m4", "verify.m4"],
32
+ build, {}, true
33
+ );
34
+ if (output.trim() !== "144") {
35
+ throw new Error("m4 check failed");
36
+ }
37
+ ready(10, "GNU m4 expansion");
38
+ }
39
+
40
+ if (import.meta.main) await cli(main);
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env bun
2
+ import { writeFileSync } from "node:fs";
3
+ import { cli } from "./lib/common.js";
4
+ import {
5
+ buildRoot, command, configure,
6
+ makeIn, ready, source, stage, tools,
7
+ } from "./lib/build.js";
8
+
9
+ async function main(args) {
10
+ const { triple } = stage(
11
+ 11, "flex", args
12
+ );
13
+ const src = await source(
14
+ "flex@2.6.4", "flex-2.6.4"
15
+ );
16
+ const build = buildRoot + "/flex";
17
+ const env = { M4: tools + "/m4" };
18
+ await configure(src, build, [
19
+ "--build=" + triple,
20
+ "--host=" + triple,
21
+ "--prefix=/bootstrap/toolchain",
22
+ "--disable-nls",
23
+ "--disable-dependency-tracking",
24
+ "--disable-shared",
25
+ ], env);
26
+ await makeIn(
27
+ build, ["-C", "src"], env
28
+ );
29
+ await makeIn(
30
+ build, ["-C", "src", "install"],
31
+ env
32
+ );
33
+ await verifyLexer(build, env);
34
+ ready(11, "Flex generated lexer");
35
+ }
36
+
37
+ async function verifyLexer(build, env) {
38
+ writeFileSync(build + "/verify.l", `%option noyywrap
39
+ %%
40
+ [0-9]+ return 7;
41
+ . return 1;
42
+ %%
43
+ int main(void){yy_scan_string("123");return yylex()!=7;}
44
+ `);
45
+ await command([tools + "/flex", "-o", "verify.c", "verify.l"], build, env);
46
+ await command([tools + "/cc", "verify.c", "-o", "verify-flex"], build, env);
47
+ await command([build + "/verify-flex"], build, env);
48
+ }
49
+
50
+ if (import.meta.main) await cli(main);