Add edit program

This commit is contained in:
Raven Scott
2026-04-03 21:59:05 -04:00
parent 57b1296981
commit 32d3eaa833
40 changed files with 7211 additions and 412 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ node index.js
| `swarm-disk.js` | Peer mux, MBR/read RPC, replication hooks |
| `kernel-runner.js` | `runKernelFromSource`, `runBinCommand`, `resolveBinInPath` (PATH on system drive); `git` / `curl` / `wget` delegates |
| `vfs.js` | Two-drive routing; `mkdir`/`rmdir` (`.bareos_empty`), `chmod`, `symlink`, pseudo `/proc`/`/sys`, `watch`, … |
| `shell.js` | Tokenize, pipelines, redirections, builtins (`barerc`, `unset`, `readonly`, `umask`, `command`, `type`, …), `execShellLine`, `loadBarerc` |
| `shell.js` | Tokenize, pipelines, redirections, builtins (`barerc`, `unset`, `readonly`, `umask`, `command`, `type`, …), `execShellLine`, `loadBarerc`; default aliases include **`nano``edit`** (TTY editor) |
| `bare-os-theme-presets.js` | Named themes (`BARE_OS_THEME`), `applyBareOsThemeFromEnv`, `BARE_OS_COLOR_DEPTH` downgrades for REPL colors |
| `bare-os-ipc.js` | FIFOs, JSON-RPC (`pushJson`/`takeJson`, token + line limits), fan-out pub/sub, `stats` |
| `bare-os-abort.js` | `raceWithAbortAndTimeout` for `execLine` / `readLine` / VFS / `runBinCommand` |
+6 -3
View File
@@ -88,18 +88,21 @@ export async function runKernelFromSource(source, ctx) {
}
/**
* Evaluate user script source: top-level statements run in an async function with `ctx` and `argv`.
* If the script defines a top-level `run` function, it is awaited after the body (same as `/bin` utilities).
*
* @param {Record<string, unknown>} ctx
* @param {string} src
* @param {string[]} argv
* @param {string} [label]
* @param {string} [_label] reserved for diagnostics
*/
async function runScriptFromSource(ctx, src, argv, label = argv[0]) {
async function runScriptFromSource(ctx, src, argv, _label = argv[0]) {
try {
const body = stripShebang(src)
const fn = new AsyncFunction(
'ctx',
'argv',
`${body}\nif (typeof run !== 'function') throw new Error('missing run() in ${label}')\nreturn run(ctx, argv)\n`
`${body}\nif (typeof run === 'function') await run(ctx, argv)\n`
)
await fn(ctx, argv)
if (ctx.exitCode === undefined || ctx.exitCode === null) ctx.exitCode = 0
+1
View File
@@ -95,6 +95,7 @@ export function getBareOsPipelineLimits(env) {
*/
export function defaultShellAliases() {
return {
nano: 'edit',
ll: 'ls -la',
la: 'ls -A',
l: 'ls',
+119
View File
@@ -433,6 +433,49 @@ async function run(ctx) { ctx.out.push('ok') }
rmSync(dir, { recursive: true, force: true })
})
test('runBinCommand runs top-level user script without run()', async (t) => {
const dir = testCorestoreDir('norun')
const store = new Corestore(dir)
const drive = new Hyperdrive(store)
const personal = new Hyperdrive(store.namespace('pnr'))
await drive.ready()
await personal.ready()
await personal.put(
personalHomeBacking('/home/user', 'plain.js'),
b4a.from(`ctx.out.push('only-top')`)
)
const out = []
const ctx = testCtx(drive, personal)
ctx.out = out
await runBinCommand(ctx, ['./plain.js'])
t.is(out[0], 'only-top')
t.is(ctx.exitCode, 0)
await store.close()
rmSync(dir, { recursive: true, force: true })
})
test('runBinCommand runs top-level then run() when both present', async (t) => {
const dir = testCorestoreDir('bothrun')
const store = new Corestore(dir)
const drive = new Hyperdrive(store)
const personal = new Hyperdrive(store.namespace('pbr'))
await drive.ready()
await personal.ready()
await personal.put(
personalHomeBacking('/home/user', 'hybrid.js'),
b4a.from(`ctx.out.push('first')
async function run(ctx) { ctx.out.push('second') }
`)
)
const out = []
const ctx = testCtx(drive, personal)
ctx.out = out
await runBinCommand(ctx, ['./hybrid.js'])
t.alike(out, ['first', 'second'])
await store.close()
rmSync(dir, { recursive: true, force: true })
})
test('runBinCommand user script error is caught and logged', async (t) => {
const dir = testCorestoreDir('throwjs')
const store = new Corestore(dir)
@@ -1228,6 +1271,11 @@ test('defaultShellAliases does not remap sed', async (t) => {
t.is(defaultShellAliases().sed, undefined)
})
test('defaultShellAliases nano maps to edit', async (t) => {
t.is(defaultShellAliases().nano, 'edit')
t.alike(expandArgvAliases(['nano', 'x'], defaultShellAliases()), ['edit', 'x'])
})
test('expandArgvAliases throws on cyclic alias chain', async (t) => {
const cyclic = { a: 'b', b: 'a' }
t.exception(
@@ -1439,6 +1487,77 @@ test('runBinCommand dircolors -p prints database', async (t) => {
rmSync(dir, { recursive: true, force: true })
})
test('runBinCommand edit --help prints usage', async (t) => {
const editPath = path.join(__dirname, '../../kernel/bin/edit')
const editSrc = await readFile(editPath, 'utf8')
const dir = testCorestoreDir('edithlp')
const store = new Corestore(dir)
const drive = new Hyperdrive(store)
const personal = new Hyperdrive(store.namespace('edh'))
await drive.ready()
await personal.ready()
await drive.put('/bin/edit', b4a.from(editSrc))
const lines = []
const ctx = testCtx(drive, personal)
ctx.console = {
log: (s) => lines.push(String(s)),
error: (...a) => lines.push(a.join(' '))
}
await runBinCommand(ctx, ['edit', '--help'])
const out = lines.join('\n')
t.ok(out.includes('usage:'))
t.ok(out.includes('TTY'))
await store.close()
rmSync(dir, { recursive: true, force: true })
})
test('runBinCommand edit requires TTY', async (t) => {
const editPath = path.join(__dirname, '../../kernel/bin/edit')
const editSrc = await readFile(editPath, 'utf8')
const dir = testCorestoreDir('ednotty')
const store = new Corestore(dir)
const drive = new Hyperdrive(store)
const personal = new Hyperdrive(store.namespace('ednt'))
await drive.ready()
await personal.ready()
await drive.put('/bin/edit', b4a.from(editSrc))
const lines = []
const ctx = testCtx(drive, personal)
ctx.replStdin = { isTTY: false }
ctx.console = {
log: (s) => lines.push(String(s)),
error: (...a) => lines.push(a.join(' '))
}
await runBinCommand(ctx, ['edit', 'x.txt'])
t.is(ctx.exitCode, 1)
t.ok(lines.some((l) => l.includes('TTY')))
await store.close()
rmSync(dir, { recursive: true, force: true })
})
test('runBinCommand nano --help matches edit bundle', async (t) => {
const nanoPath = path.join(__dirname, '../../kernel/bin/nano')
const nanoSrc = await readFile(nanoPath, 'utf8')
const dir = testCorestoreDir('nanohlp')
const store = new Corestore(dir)
const drive = new Hyperdrive(store)
const personal = new Hyperdrive(store.namespace('nanh'))
await drive.ready()
await personal.ready()
await drive.put('/bin/nano', b4a.from(nanoSrc))
const lines = []
const ctx = testCtx(drive, personal)
ctx.console = {
log: (s) => lines.push(String(s)),
error: (...a) => lines.push(a.join(' '))
}
await runBinCommand(ctx, ['nano', '-h'])
const out = lines.join('\n')
t.ok(out.includes('usage:'))
await store.close()
rmSync(dir, { recursive: true, force: true })
})
test('tokenize leaves echo 2 > file as stdout redirect not 2>', async (t) => {
const toks = tokenize('echo 2 > /tmp/x')
const words = toks.filter((x) => x.type === 'word').map((x) => x.value)