buninu 0.2.2 → 0.2.3
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/CHANGELOG.md +14 -0
- package/README.md +26 -2
- package/bin/init.js +60 -4
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.3 - 2026-08-18
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Add `buninu.exitAfterCmd` package.json setting (default `false`). When
|
|
8
|
+
`true`, the shell/PTY exits with `buninu.command`'s status instead of
|
|
9
|
+
falling back to an interactive shell once the command finishes; `false`
|
|
10
|
+
keeps the existing fall-back-to-shell behavior.
|
|
11
|
+
- Add `--export-config [output.json]`, which writes this entire `package.json`
|
|
12
|
+
to `buninu.json` in the current directory by default (like `--export`, the
|
|
13
|
+
output path can be overridden), instead of exporting the whole installation
|
|
14
|
+
like `--export` does. If the output path already exists, it now asks for
|
|
15
|
+
confirmation (`[y/N]`) before overwriting.
|
|
16
|
+
|
|
3
17
|
## 0.2.2 - 2026-08-17
|
|
4
18
|
|
|
5
19
|
### Added
|
package/README.md
CHANGED
|
@@ -142,8 +142,8 @@ rejects a syscall bin-linking uses), so `bunx` can't install anything there yet.
|
|
|
142
142
|
Export the current Buninu installation as a gzip-compressed tar archive:
|
|
143
143
|
|
|
144
144
|
```sh
|
|
145
|
-
npx buninu --export
|
|
146
|
-
npx buninu --export /path/to/buninu.tgz
|
|
145
|
+
npx buninu@latest --export
|
|
146
|
+
npx buninu@latest --export /path/to/buninu.tgz
|
|
147
147
|
```
|
|
148
148
|
|
|
149
149
|
The default output is `./buninu.tgz`. The archive contains exactly one
|
|
@@ -151,6 +151,17 @@ top-level directory so consumers can remove one component while extracting.
|
|
|
151
151
|
Export requires `tar` in `PATH`; when replacing an existing output,
|
|
152
152
|
Buninu restores the previous file if archive creation or replacement fails.
|
|
153
153
|
|
|
154
|
+
Export this `package.json` on its own, instead of the whole installation:
|
|
155
|
+
|
|
156
|
+
```sh
|
|
157
|
+
npx buninu@latest --export-config
|
|
158
|
+
npx buninu@latest --export-config /path/to/buninu.json
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The default output is `./buninu.json`. This is the full `package.json` (not
|
|
162
|
+
just the `buninu` section), so the output is ready to use as-is anywhere a
|
|
163
|
+
complete replacement `package.json` is expected.
|
|
164
|
+
|
|
154
165
|
## Environment
|
|
155
166
|
|
|
156
167
|
`BUNINU_HOME` is the absolute path to the installed Buninu package root.
|
|
@@ -263,6 +274,19 @@ Override it for one run with:
|
|
|
263
274
|
bun ./bin/init.js --command "echo temporary command"
|
|
264
275
|
```
|
|
265
276
|
|
|
277
|
+
Set `buninu.exitAfterCmd` to `true` to exit once `buninu.command` finishes
|
|
278
|
+
instead of falling back to an interactive shell. It defaults to `false`,
|
|
279
|
+
which is the current fall-back-to-shell behavior described above.
|
|
280
|
+
|
|
281
|
+
```json
|
|
282
|
+
{
|
|
283
|
+
"buninu": {
|
|
284
|
+
"command": { "default": "echo Welcome to Buninu" },
|
|
285
|
+
"exitAfterCmd": true
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
266
290
|
## Shell selection (optional)
|
|
267
291
|
|
|
268
292
|
Set `buninu.shell.default` or a platform-specific value (`android`, `linux`,
|
package/bin/init.js
CHANGED
|
@@ -4,6 +4,7 @@ import pkg from "../package.json" with { type: "json" };
|
|
|
4
4
|
import { existsSync, lstatSync, mkdirSync, mkdtempSync, rmSync, symlinkSync, unlinkSync } from "node:fs";
|
|
5
5
|
import { tmpdir as systemTmpDir } from "node:os";
|
|
6
6
|
import { basename, delimiter, dirname, isAbsolute, resolve } from "node:path";
|
|
7
|
+
import { createInterface } from "node:readline/promises";
|
|
7
8
|
import { fileURLToPath } from "node:url";
|
|
8
9
|
|
|
9
10
|
const binDir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -27,6 +28,7 @@ Init options:
|
|
|
27
28
|
--readme Render README.md in the terminal and exit
|
|
28
29
|
--changelog Render CHANGELOG.md in the terminal and exit
|
|
29
30
|
--export [output.tgz] Export this Buninu installation (default: ./buninu.tgz)
|
|
31
|
+
--export-config [output.json] Export this package.json (default: ./buninu.json)
|
|
30
32
|
--shell <path|name> Override buninu.shell for this run
|
|
31
33
|
--command <command> Override buninu.command for this run
|
|
32
34
|
|
|
@@ -42,10 +44,13 @@ Package configuration:
|
|
|
42
44
|
package.json contains a buninu section for default and platform settings.
|
|
43
45
|
buninu.shell Select the shell to start
|
|
44
46
|
buninu.command Run a startup command before entering the shell
|
|
47
|
+
buninu.exitAfterCmd Exit after buninu.command instead of falling back to
|
|
48
|
+
the shell (default: false)
|
|
45
49
|
|
|
46
50
|
All other options are forwarded to jsgotty. Without buninu.command, the
|
|
47
51
|
selected shell starts directly. With a command, it runs from the package.json
|
|
48
|
-
directory and then returns to the interactive shell regardless of exit status
|
|
52
|
+
directory and then returns to the interactive shell regardless of exit status,
|
|
53
|
+
unless buninu.exitAfterCmd is true, in which case it exits instead.
|
|
49
54
|
`;
|
|
50
55
|
}
|
|
51
56
|
|
|
@@ -69,6 +74,12 @@ async function handleInformationArguments(arguments_) {
|
|
|
69
74
|
return true;
|
|
70
75
|
}
|
|
71
76
|
|
|
77
|
+
const exportConfigOption = readExportConfigArgument(arguments_);
|
|
78
|
+
if (exportConfigOption) {
|
|
79
|
+
await exportConfig(exportConfigOption.output);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
72
83
|
if (arguments_.includes("--readme")) {
|
|
73
84
|
const readmePath = resolve(REPO_ROOT, "README.md");
|
|
74
85
|
if (!await pathExists(readmePath)) fail(`README not found: ${readmePath}`);
|
|
@@ -106,6 +117,22 @@ function readExportArgument(arguments_) {
|
|
|
106
117
|
return null;
|
|
107
118
|
}
|
|
108
119
|
|
|
120
|
+
function readExportConfigArgument(arguments_) {
|
|
121
|
+
for (let index = 0; index < arguments_.length; index += 1) {
|
|
122
|
+
const argument = arguments_[index];
|
|
123
|
+
if (argument === "--export-config") {
|
|
124
|
+
const next = arguments_[index + 1];
|
|
125
|
+
return { output: next || "buninu.json" };
|
|
126
|
+
}
|
|
127
|
+
if (argument.startsWith("--export-config=")) {
|
|
128
|
+
const output = argument.slice("--export-config=".length);
|
|
129
|
+
if (!output) fail("--export-config= requires an output path");
|
|
130
|
+
return { output };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
109
136
|
async function exportInstallation(output) {
|
|
110
137
|
const outputPath = resolve(process.cwd(), output);
|
|
111
138
|
const tar = Bun.which(process.platform === "win32" ? "tar.exe" : "tar") || Bun.which("tar");
|
|
@@ -145,6 +172,25 @@ async function exportInstallation(output) {
|
|
|
145
172
|
console.log(outputPath);
|
|
146
173
|
}
|
|
147
174
|
|
|
175
|
+
async function exportConfig(output) {
|
|
176
|
+
const outputPath = resolve(process.cwd(), output);
|
|
177
|
+
|
|
178
|
+
if (existsSync(outputPath)) {
|
|
179
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
180
|
+
let answer;
|
|
181
|
+
try {
|
|
182
|
+
answer = await rl.question(`${outputPath} already exists and may be overwritten. Continue? [y/N] `);
|
|
183
|
+
} finally {
|
|
184
|
+
rl.close();
|
|
185
|
+
}
|
|
186
|
+
if (!/^[yY]/.test(answer.trim())) fail("Cancelled: output file was not overwritten");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const packageJsonPath = resolve(REPO_ROOT, "package.json");
|
|
190
|
+
await Bun.write(outputPath, Bun.file(packageJsonPath));
|
|
191
|
+
console.log(outputPath);
|
|
192
|
+
}
|
|
193
|
+
|
|
148
194
|
function splitCommand(command) {
|
|
149
195
|
const words = [];
|
|
150
196
|
let word = "";
|
|
@@ -460,16 +506,25 @@ function readInitArguments(arguments_) {
|
|
|
460
506
|
return { shell, command, forwarded };
|
|
461
507
|
}
|
|
462
508
|
|
|
463
|
-
function shellCommandArguments(shell, command) {
|
|
509
|
+
function shellCommandArguments(shell, command, exitAfterCmd) {
|
|
464
510
|
if (!command) return [shell];
|
|
465
511
|
|
|
466
512
|
const shellName = basename(shell).toLowerCase();
|
|
467
513
|
if (shellName === "cmd" || shellName === "cmd.exe") {
|
|
514
|
+
if (exitAfterCmd) return [shell, "/d", "/s", "/c", command];
|
|
468
515
|
const quotedShell = `"${shell.replaceAll('"', '""')}"`;
|
|
469
516
|
return [shell, "/d", "/s", "/c", `${command} & ${quotedShell}`];
|
|
470
517
|
}
|
|
471
518
|
|
|
472
519
|
if (["powershell", "powershell.exe", "pwsh", "pwsh.exe"].includes(shellName)) {
|
|
520
|
+
if (exitAfterCmd) {
|
|
521
|
+
return [
|
|
522
|
+
shell,
|
|
523
|
+
"-NoLogo",
|
|
524
|
+
"-Command",
|
|
525
|
+
`& { ${command}; if (-not $?) { Write-Error 'Startup command failed' } }`,
|
|
526
|
+
];
|
|
527
|
+
}
|
|
473
528
|
const escapedShell = shell.replaceAll("'", "''");
|
|
474
529
|
return [
|
|
475
530
|
shell,
|
|
@@ -483,7 +538,7 @@ function shellCommandArguments(shell, command) {
|
|
|
483
538
|
`buninu_command=$1; shift; ( eval "$buninu_command" ); status=$?; ` +
|
|
484
539
|
`if [ "$status" -ne 0 ]; then ` +
|
|
485
540
|
`echo "buninu: startup command failed with exit $status" >&2; ` +
|
|
486
|
-
`fi; exec "$0"
|
|
541
|
+
(exitAfterCmd ? `fi; exit "$status"` : `fi; exec "$0"`);
|
|
487
542
|
// Pass both the shell and startup command as argv instead of interpolating
|
|
488
543
|
// them into the wrapper. This keeps the wrapper single-line and avoids LF
|
|
489
544
|
// (Ctrl+J) characters in its process-list representation.
|
|
@@ -539,6 +594,7 @@ const configuredCommand =
|
|
|
539
594
|
commandConfiguration?.default ??
|
|
540
595
|
null;
|
|
541
596
|
const startupCommand = initArguments.command ?? configuredCommand;
|
|
597
|
+
const exitAfterCmd = Boolean(pkg.buninu?.exitAfterCmd);
|
|
542
598
|
|
|
543
599
|
const forwardedArguments = initArguments.forwarded;
|
|
544
600
|
const hasExplicitPort = forwardedArguments.some((argument) =>
|
|
@@ -559,7 +615,7 @@ const child = Bun.spawn(
|
|
|
559
615
|
...command,
|
|
560
616
|
...portArguments,
|
|
561
617
|
...forwardedArguments,
|
|
562
|
-
...shellCommandArguments(environment.shell, startupCommand),
|
|
618
|
+
...shellCommandArguments(environment.shell, startupCommand, exitAfterCmd),
|
|
563
619
|
],
|
|
564
620
|
{
|
|
565
621
|
cwd: rootDir,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "buninu",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Buninu is a portable, self-bootstrapping Unix-like userspace built on Bun that runs across operating systems",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./bin/init.js",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"linux": null,
|
|
23
23
|
"macos": null,
|
|
24
24
|
"windows": null
|
|
25
|
-
}
|
|
25
|
+
},
|
|
26
|
+
"exitAfterCmd": false
|
|
26
27
|
},
|
|
27
28
|
"scripts": {
|
|
28
29
|
"start": "bun ./apps/jsgotty/gotty.js --reconnect -r -w --webgl -a 127.0.0.1",
|