Files
bare-operating-system/packages/bare-os-booter/lib/git-fs-adapter.js
T
Raven Scott 433cb2176d feat: complete Bare OS POSIX mega-plan (20-track)
Migrate booter and seeder host file/path access to bare-fs/bare-path via package
imports while keeping Node defaults for dev/CI; extend CI to reject bare fs/path
imports on Pear surfaces.

Wire bareOsPearUpdaterDelegate through pear-runtime-updater-style dynamic import,
expand process table and signal routing, add system RO alias mount and optional
Hyperbee bin hint, protomux proc metrics, cron dom/dow OR rule, boot-perf detail
with optional bare-hrtime, /lib/bare warm cache with metrics_live warmReadCache,
vault threat-model and users-manual crypto pointers, Pear inspect emit, gated
shell break/continue, kernel-ext-graph output and example schema, POSIX compliance
matrix + verifier in pretest.

Harden ACL sidecar evaluation toward POSIX-like mask semantics; tighten test/expr/
printf edge cases and man JSON; add blind-bootstrap/DHT registry protocol test and
troubleshooting notes; refresh handbook, KERNEL_CONTRACT, environment appendix, and
reference hub links.
2026-04-04 22:13:19 -04:00

269 lines
6.7 KiB
JavaScript

/**
* Node fs.promises-shaped adapter over ctx.vfs for isomorphic-git.
* Empty directories are represented by a hidden marker file (filtered from readdir).
* Stat/lstat return Node-like objects (mtime/ctime, mtimeMs/ctimeMs, mode, dev, ino, uid, gid)
* because isomorphic-git normalizeStats() requires them for the index.
*/
import path from '#host-path'
const MARKER = '.bareos_empty'
function err(code, message) {
const e = new Error(message || code)
// @ts-ignore
e.code = code
return e
}
/** Default Unix mode bits isomorphic-git expects for Git trees (see normalizeMode in upstream). */
const MODE_FILE = 0o100644
const MODE_DIR = 0o040755
const MODE_SYMLINK = 0o120000
/**
* Node fs.Stats-shaped object for isomorphic-git (normalizeStats reads mtime/ctime and numeric ids).
*/
export class VfsGitStats {
/**
* @param {'file' | 'directory' | 'symlink'} type
* @param {number} [size]
* @param {{ mtimeMs?: number, ctimeMs?: number, mode?: number, dev?: number, ino?: number, uid?: number, gid?: number }} [opts]
*/
constructor(type, size = 0, opts = {}) {
this.type = type
this.size = size
const ms = typeof opts.mtimeMs === 'number' ? opts.mtimeMs : Date.now()
this.mtimeMs = ms
this.ctimeMs = typeof opts.ctimeMs === 'number' ? opts.ctimeMs : ms
this.atimeMs = ms
this.birthtimeMs = this.ctimeMs
this.mtime = new Date(this.mtimeMs)
this.ctime = new Date(this.ctimeMs)
this.atime = this.mtime
this.birthtime = this.ctime
this.dev = opts.dev ?? 0
this.ino = opts.ino ?? 0
this.mode =
opts.mode ??
(type === 'directory'
? MODE_DIR
: type === 'symlink'
? MODE_SYMLINK
: MODE_FILE)
this.uid = opts.uid ?? 0
this.gid = opts.gid ?? 0
this.blocks = 0
this.blksize = 4096
this.nlink = 1
this.rdev = 0
}
isFile() {
return this.type === 'file'
}
isDirectory() {
return this.type === 'directory'
}
isSymbolicLink() {
return this.type === 'symlink'
}
}
/**
* Map vfs lstat/stat result to Stats or throw ENOENT.
* @param {Record<string, unknown> | null} st
*/
function toStats(st) {
if (!st) throw err('ENOENT', 'ENOENT')
const ts = {
mtimeMs: typeof st.mtimeMs === 'number' ? st.mtimeMs : undefined,
ctimeMs: typeof st.ctimeMs === 'number' ? st.ctimeMs : undefined,
mode: typeof st.mode === 'number' ? st.mode : undefined
}
if (st.type === 'file')
return new VfsGitStats('file', Number(st.size) || 0, ts)
if (st.type === 'directory') return new VfsGitStats('directory', 0, ts)
if (st.type === 'symlink')
return new VfsGitStats('symlink', Number(st.size) || 0, ts)
throw err('ENOENT', 'ENOENT')
}
/**
* @param {{ readFile: Function, writeFile: Function, unlink: Function, readdir: Function, stat: Function, lstat: Function, readlink: Function, symlink: Function }} vfs
*/
export function createGitFsFromVfs(vfs) {
async function rawReaddir(p) {
return vfs.readdir(p)
}
async function readDirFiltered(p) {
const names = await rawReaddir(p)
return names.filter((n) => n !== MARKER)
}
async function stat(p) {
const st = await vfs.stat(p)
return toStats(st)
}
async function lstat(p) {
const st = await vfs.lstat(p)
return toStats(st)
}
async function readFile(p, opts = {}) {
const buf = await vfs.readFile(p)
if (buf == null) throw err('ENOENT', 'ENOENT')
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
if (opts.encoding === 'utf8' || opts.encoding === 'utf-8') {
return Buffer.from(u8).toString('utf8')
}
return Buffer.from(u8)
}
async function writeFile(p, data, _opts = {}) {
const body =
typeof data === 'string'
? Buffer.from(data, 'utf8')
: data instanceof Buffer
? data
: Buffer.from(data)
await vfs.writeFile(p, body)
}
async function unlink(p) {
try {
await vfs.unlink(p)
} catch (e) {
if (e && e.code === 'ENOENT') throw e
throw e
}
}
async function mkdir(p, options = {}) {
const recursive = Boolean(options.recursive)
const norm = path.posix.normalize(p)
if (norm === '/' || norm === '') return
try {
const s = await stat(norm)
if (s.isDirectory()) return
if (s.isFile()) throw err('EEXIST', 'EEXIST')
} catch (e) {
if (e.code !== 'ENOENT') throw e
const parent = path.posix.dirname(norm)
if (parent !== norm && parent !== '/' && parent !== '') {
try {
const ps = await stat(parent)
if (!ps.isDirectory()) throw err('ENOTDIR', 'ENOTDIR')
} catch (pe) {
if (pe.code === 'ENOENT') {
if (recursive) await mkdir(parent, { recursive: true })
else throw err('ENOENT', 'ENOENT')
} else throw pe
}
}
const markerPath = path.posix.join(norm, MARKER)
await vfs.writeFile(markerPath, Buffer.alloc(0))
}
}
async function rmdir(p, opts = {}) {
if (opts.recursive) {
await rm(p, { recursive: true, force: true })
return
}
const names = await readDirFiltered(p)
if (names.length) throw err('ENOTEMPTY', 'ENOTEMPTY')
const markerPath = path.posix.join(p, MARKER)
try {
await vfs.unlink(markerPath)
} catch (e) {
if (e && e.code !== 'ENOENT') throw e
}
}
async function rm(p, opts = {}) {
const recursive = Boolean(opts.recursive)
const force = Boolean(opts.force)
let st
try {
st = await lstat(p)
} catch (e) {
if (e.code === 'ENOENT' && force) return
throw e
}
if (st.isSymbolicLink() || st.isFile()) {
await unlink(p)
return
}
if (!st.isDirectory()) {
await unlink(p)
return
}
if (!recursive) {
await rmdir(p, {})
return
}
const children = await rawReaddir(p)
for (const name of children) {
const full = path.posix.join(p, name)
await rm(full, { recursive: true, force: true })
}
try {
await vfs.unlink(path.posix.join(p, MARKER))
} catch {
/* */
}
try {
await rmdir(p, {})
} catch (e) {
if (e.code !== 'ENOENT' && e.code !== 'ENOTEMPTY') throw e
}
}
async function readdir(p) {
try {
await stat(p)
} catch (e) {
if (e.code === 'ENOENT') return null
throw e
}
return readDirFiltered(p)
}
async function readlink(p) {
return vfs.readlink(p)
}
async function symlink(target, p) {
return vfs.symlink(target, p)
}
async function chmod(p, mode) {
if (typeof vfs.chmod === 'function') {
await vfs.chmod(p, mode)
}
}
const promises = {
readFile,
writeFile,
unlink,
mkdir,
rmdir,
rm,
stat,
lstat,
readdir,
readlink,
symlink,
chmod
}
return { promises }
}