Use Bare modules for Holesail control under Pear
CI / test (push) Successful in 10m0s

Map Node builtins to bare-* via package.json imports and dual-load
with which-runtime so holesailBareControl runs on Bare without
MODULE_NOT_FOUND for crypto/fs/http/net.
This commit is contained in:
2026-07-10 23:20:50 -04:00
parent 5fbece2bd9
commit aa48876d75
4 changed files with 133 additions and 78 deletions
+36 -10
View File
@@ -2,14 +2,37 @@
* Bare/Node control plane for local Holesail client sessions. * Bare/Node control plane for local Holesail client sessions.
* Runs in the pear-electron Bare entry (index.js), where require.addon works. * Runs in the pear-electron Bare entry (index.js), where require.addon works.
* The UI renderer talks to this over http://127.0.0.1:<port> (never loads bare-tcp itself). * The UI renderer talks to this over http://127.0.0.1:<port> (never loads bare-tcp itself).
*
* Uses Bare modules under Pear (via package.json "imports" bare conditions and
* which-runtime dual-load). Do not require bare Node built-ins alone on Bare.
*/ */
'use strict' 'use strict'
const http = require('http') const { isBare } = require('which-runtime')
const fs = require('fs')
const path = require('path') // Prefer package.json imports maps; dual-load as fallback for older resolvers.
const crypto = require('crypto') function load (bareName, nodeName) {
const net = require('net') if (isBare) {
try {
return require(bareName)
} catch {
return require(nodeName)
}
}
try {
return require(nodeName)
} catch {
return require(bareName)
}
}
const http = load('bare-http1', 'http')
const fs = load('bare-fs', 'fs')
const path = load('bare-path', 'path')
const crypto = load('bare-crypto', 'crypto')
const net = load('bare-net', 'net')
const process = load('bare-process', 'process')
const b4a = require('b4a')
const Holesail = require('holesail') const Holesail = require('holesail')
/** @type {Map<string, { instance: any, localPort: number, host: string, url: string, info: object }>} */ /** @type {Map<string, { instance: any, localPort: number, host: string, url: string, info: object }>} */
@@ -112,12 +135,13 @@ function list() {
function readJson(req) { function readJson(req) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const chunks = [] const chunks = []
req.on('data', (c) => chunks.push(c)) req.on('data', (c) => chunks.push(typeof c === 'string' ? b4a.from(c) : c))
req.on('end', () => { req.on('end', () => {
if (!chunks.length) return resolve({}) if (!chunks.length) return resolve({})
try { try {
resolve(JSON.parse(Buffer.concat(chunks).toString('utf8'))) const raw = b4a.toString(chunks.length === 1 ? chunks[0] : b4a.concat(chunks))
} catch (err) { resolve(JSON.parse(raw || '{}'))
} catch {
reject(new Error('Invalid JSON body')) reject(new Error('Invalid JSON body'))
} }
}) })
@@ -146,6 +170,7 @@ function sendJson(res, status, body) {
async function start(opts = {}) { async function start(opts = {}) {
const token = opts.token || crypto.randomBytes(16).toString('hex') const token = opts.token || crypto.randomBytes(16).toString('hex')
const host = opts.host || '127.0.0.1' const host = opts.host || '127.0.0.1'
const pid = process.pid
const server = http.createServer(async (req, res) => { const server = http.createServer(async (req, res) => {
// CORS preflight (UI fetch from pear-bridge origin) // CORS preflight (UI fetch from pear-bridge origin)
@@ -159,6 +184,7 @@ async function start(opts = {}) {
} }
try { try {
// bare-url / WHATWG URL
const url = new URL(req.url || '/', `http://${host}`) const url = new URL(req.url || '/', `http://${host}`)
const pathname = url.pathname const pathname = url.pathname
@@ -219,7 +245,7 @@ async function start(opts = {}) {
const addr = server.address() const addr = server.address()
const port = typeof addr === 'object' && addr ? addr.port : 0 const port = typeof addr === 'object' && addr ? addr.port : 0
const baseUrl = `http://${host}:${port}` const baseUrl = `http://${host}:${port}`
const meta = { port, host, token, baseUrl, pid: process.pid } const meta = { port, host, token, baseUrl, pid }
if (opts.statePath) { if (opts.statePath) {
try { try {
@@ -230,7 +256,7 @@ async function start(opts = {}) {
} }
} }
console.log(`[holesail-control] listening on ${baseUrl}`) console.log(`[holesail-control] listening on ${baseUrl} (bare=${isBare})`)
return { return {
...meta, ...meta,
+1
View File
@@ -84,6 +84,7 @@ npx holesail 'hs://s000…'
2. **Client-side local proxy** — Tunnels → plug icon runs the **real `holesail` package**. 2. **Client-side local proxy** — Tunnels → plug icon runs the **real `holesail` package**.
Under pear-electron the UI renderer cannot load bare-tcp (`require.addon is not a function`). Real holesail runs in the **Bare main process**: Under pear-electron the UI renderer cannot load bare-tcp (`require.addon is not a function`). Real holesail runs in the **Bare main process**:
- Bare entry `index.js` starts `client/holesailBareControl.cjs` (`require('holesail')`) on `http://127.0.0.1:<port>` - Bare entry `index.js` starts `client/holesailBareControl.cjs` (`require('holesail')`) on `http://127.0.0.1:<port>`
- Control code uses **Bare modules** (`bare-crypto`, `bare-fs`, `bare-http1`, `bare-net`, …) via `package.json` `imports` maps + `which-runtime`
- Endpoint + token written to `<Pear.config.storage>/peardock-holesail-local.json` - Endpoint + token written to `<Pear.config.storage>/peardock-holesail-local.json`
- UI `client/holesailLocal.js` POSTs `/connect` / `/disconnect` (never loads bare-tcp) - UI `client/holesailLocal.js` POSTs `/connect` / `/disconnect` (never loads bare-tcp)
- Node tests / non-Pear: in-process `require('holesail')` - Node tests / non-Pear: in-process `require('holesail')`
+43 -68
View File
@@ -10,6 +10,16 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"b4a": "^1.8.1", "b4a": "^1.8.1",
"bare-crypto": "^1.15.3",
"bare-events": "^2.9.1",
"bare-fs": "^4.7.4",
"bare-http1": "^4.5.7",
"bare-net": "^2.3.2",
"bare-os": "^3.9.3",
"bare-path": "^3.1.1",
"bare-process": "^4.5.1",
"bare-subprocess": "^5.2.3",
"bare-url": "^2.4.5",
"compact-encoding": "^3.3.0", "compact-encoding": "^3.3.0",
"dockerode": "^5.0.1", "dockerode": "^5.0.1",
"dotenv": "^17.4.2", "dotenv": "^17.4.2",
@@ -25,6 +35,7 @@
"protomux": "^3.11.0", "protomux": "^3.11.0",
"protomux-rpc": "^1.10.0", "protomux-rpc": "^1.10.0",
"safety-catch": "^1.0.3", "safety-catch": "^1.0.3",
"which-runtime": "^1.4.0",
"z32": "^1.1.0" "z32": "^1.1.0"
}, },
"devDependencies": { "devDependencies": {
@@ -421,7 +432,6 @@
"version": "1.15.3", "version": "1.15.3",
"resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.15.3.tgz", "resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.15.3.tgz",
"integrity": "sha512-macV9lbyJTsLPRXJkBtz8ivTGEo3LCyJInLT9IB/PWJ7pRXwvHs/FP4bx/fWw+HZkiepIYCAV2cuU5CR92XWCw==", "integrity": "sha512-macV9lbyJTsLPRXJkBtz8ivTGEo3LCyJInLT9IB/PWJ7pRXwvHs/FP4bx/fWw+HZkiepIYCAV2cuU5CR92XWCw==",
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"bare-assert": "^1.2.0", "bare-assert": "^1.2.0",
"bare-stream": "^2.6.3" "bare-stream": "^2.6.3"
@@ -500,7 +510,6 @@
"version": "2.9.1", "version": "2.9.1",
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz", "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz",
"integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==", "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==",
"license": "Apache-2.0",
"peerDependencies": { "peerDependencies": {
"bare-abort-controller": "*" "bare-abort-controller": "*"
}, },
@@ -523,7 +532,6 @@
"version": "4.7.4", "version": "4.7.4",
"resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.4.tgz", "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.4.tgz",
"integrity": "sha512-y1kC+ffIx/tPLdTE693uNjHfzTfr+ravR5tvWlMXe25nELbkqV400S71qHDwbkAQ1FVEZobB1NFRzFbCCcyBCQ==", "integrity": "sha512-y1kC+ffIx/tPLdTE693uNjHfzTfr+ravR5tvWlMXe25nELbkqV400S71qHDwbkAQ1FVEZobB1NFRzFbCCcyBCQ==",
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"bare-events": "^2.5.4", "bare-events": "^2.5.4",
"bare-path": "^3.0.0", "bare-path": "^3.0.0",
@@ -559,7 +567,6 @@
"version": "4.5.7", "version": "4.5.7",
"resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.5.7.tgz", "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.5.7.tgz",
"integrity": "sha512-PRuzs9ywt4vUvrC3mnHhQyaQfLYzdFy8XKOH0oKeKmYfyYYUqXBkdbJE2csESLBxJEbKDBjxPGLU+Qa0l1ds4A==", "integrity": "sha512-PRuzs9ywt4vUvrC3mnHhQyaQfLYzdFy8XKOH0oKeKmYfyYYUqXBkdbJE2csESLBxJEbKDBjxPGLU+Qa0l1ds4A==",
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"bare-events": "^2.6.0", "bare-events": "^2.6.0",
"bare-http-parser": "^1.1.1", "bare-http-parser": "^1.1.1",
@@ -692,7 +699,6 @@
"version": "2.3.2", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.3.2.tgz", "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.3.2.tgz",
"integrity": "sha512-I+yz+pqbYsBkxDsnu5vkKvy7RSNY9CcAvu2jZT6PsmdXJQG1i3dmD5V7xc3334OVp2absgtUEYLmmuNFlphBzg==", "integrity": "sha512-I+yz+pqbYsBkxDsnu5vkKvy7RSNY9CcAvu2jZT6PsmdXJQG1i3dmD5V7xc3334OVp2absgtUEYLmmuNFlphBzg==",
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"bare-events": "^2.2.2", "bare-events": "^2.2.2",
"bare-pipe": "^4.0.0", "bare-pipe": "^4.0.0",
@@ -704,7 +710,6 @@
"version": "3.9.3", "version": "3.9.3",
"resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.3.tgz", "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.3.tgz",
"integrity": "sha512-fF4Q7QsyKVF5Rj0qvI8BgUNjqzC2JvQlpTaPLjVJVxYVUX5Zr9un+y3w1HmA4nNKdFmRBT8z/WmrjvXzXVerKQ==", "integrity": "sha512-fF4Q7QsyKVF5Rj0qvI8BgUNjqzC2JvQlpTaPLjVJVxYVUX5Zr9un+y3w1HmA4nNKdFmRBT8z/WmrjvXzXVerKQ==",
"license": "Apache-2.0",
"engines": { "engines": {
"bare": ">=1.14.0" "bare": ">=1.14.0"
} }
@@ -712,8 +717,7 @@
"node_modules/bare-path": { "node_modules/bare-path": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.1.tgz", "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.1.tgz",
"integrity": "sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ==", "integrity": "sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ=="
"license": "Apache-2.0"
}, },
"node_modules/bare-pipe": { "node_modules/bare-pipe": {
"version": "4.2.2", "version": "4.2.2",
@@ -738,7 +742,6 @@
"version": "4.5.1", "version": "4.5.1",
"resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.5.1.tgz", "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.5.1.tgz",
"integrity": "sha512-CaAvy1trputD49mtwfJ6G75vydhnirLrW/F3Sznp4H556e1uZn8YMo9ELicBTrGYy7RBNUgPl9bpB/ERzRCiDw==", "integrity": "sha512-CaAvy1trputD49mtwfJ6G75vydhnirLrW/F3Sznp4H556e1uZn8YMo9ELicBTrGYy7RBNUgPl9bpB/ERzRCiDw==",
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"bare-abort": "^2.0.13", "bare-abort": "^2.0.13",
"bare-env": "^3.0.0", "bare-env": "^3.0.0",
@@ -848,18 +851,14 @@
} }
}, },
"node_modules/bare-subprocess": { "node_modules/bare-subprocess": {
"version": "6.1.0", "version": "5.2.3",
"resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-6.1.0.tgz", "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.3.tgz",
"integrity": "sha512-8L6KtbmreDy4Fc1BdDqaDo9fg0nbedUmTSFQxYPV0uHIM2BBAX8+E0LyMDKgkk0I+mlKto80WYzOnIhT8VDdOg==", "integrity": "sha512-07wwswlV7M3sC9IykbZRZ/jHAkrXFWVLqdBWGv1y0ojCimtRD9hGwxdHmR5FUFmDUZLNsBmTYJNQqgio5+A85Q==",
"dev": true,
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"bare-env": "^3.0.0", "bare-env": "^3.0.0",
"bare-events": "^2.5.4", "bare-events": "^2.5.4",
"bare-os": "^3.0.1", "bare-os": "^3.0.1",
"bare-pipe": "^4.2.0", "bare-pipe": "^4.0.0",
"bare-structured-clone": "^1.5.2",
"bare-tcp": "^2.4.1",
"bare-url": "^2.2.2" "bare-url": "^2.2.2"
}, },
"engines": { "engines": {
@@ -954,7 +953,6 @@
"version": "2.4.5", "version": "2.4.5",
"resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.5.tgz", "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.5.tgz",
"integrity": "sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==", "integrity": "sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==",
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"bare-path": "^3.0.0" "bare-path": "^3.0.0"
} }
@@ -1163,6 +1161,32 @@
"brittle-node": "brittle-node.js" "brittle-node": "brittle-node.js"
} }
}, },
"node_modules/brittle/node_modules/bare-subprocess": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-6.1.0.tgz",
"integrity": "sha512-8L6KtbmreDy4Fc1BdDqaDo9fg0nbedUmTSFQxYPV0uHIM2BBAX8+E0LyMDKgkk0I+mlKto80WYzOnIhT8VDdOg==",
"dev": true,
"dependencies": {
"bare-env": "^3.0.0",
"bare-events": "^2.5.4",
"bare-os": "^3.0.1",
"bare-pipe": "^4.2.0",
"bare-structured-clone": "^1.5.2",
"bare-tcp": "^2.4.1",
"bare-url": "^2.2.2"
},
"engines": {
"bare": ">=1.7.0"
},
"peerDependencies": {
"bare-buffer": "*"
},
"peerDependenciesMeta": {
"bare-buffer": {
"optional": true
}
}
},
"node_modules/buffer": { "node_modules/buffer": {
"version": "5.7.1", "version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
@@ -2234,30 +2258,6 @@
"which-runtime": "^1.2.1" "which-runtime": "^1.2.1"
} }
}, },
"node_modules/pear-electron/node_modules/bare-subprocess": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.3.tgz",
"integrity": "sha512-07wwswlV7M3sC9IykbZRZ/jHAkrXFWVLqdBWGv1y0ojCimtRD9hGwxdHmR5FUFmDUZLNsBmTYJNQqgio5+A85Q==",
"license": "Apache-2.0",
"dependencies": {
"bare-env": "^3.0.0",
"bare-events": "^2.5.4",
"bare-os": "^3.0.1",
"bare-pipe": "^4.0.0",
"bare-url": "^2.2.2"
},
"engines": {
"bare": ">=1.7.0"
},
"peerDependencies": {
"bare-buffer": "*"
},
"peerDependenciesMeta": {
"bare-buffer": {
"optional": true
}
}
},
"node_modules/pear-electron/node_modules/compact-encoding": { "node_modules/pear-electron/node_modules/compact-encoding": {
"version": "2.19.2", "version": "2.19.2",
"resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-2.19.2.tgz", "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-2.19.2.tgz",
@@ -2444,30 +2444,6 @@
"which-runtime": "^1.3.2" "which-runtime": "^1.3.2"
} }
}, },
"node_modules/pear-run/node_modules/bare-subprocess": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.3.tgz",
"integrity": "sha512-07wwswlV7M3sC9IykbZRZ/jHAkrXFWVLqdBWGv1y0ojCimtRD9hGwxdHmR5FUFmDUZLNsBmTYJNQqgio5+A85Q==",
"license": "Apache-2.0",
"dependencies": {
"bare-env": "^3.0.0",
"bare-events": "^2.5.4",
"bare-os": "^3.0.1",
"bare-pipe": "^4.0.0",
"bare-url": "^2.2.2"
},
"engines": {
"bare": ">=1.7.0"
},
"peerDependencies": {
"bare-buffer": "*"
},
"peerDependenciesMeta": {
"bare-buffer": {
"optional": true
}
}
},
"node_modules/pear-stamp": { "node_modules/pear-stamp": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/pear-stamp/-/pear-stamp-1.0.2.tgz", "resolved": "https://registry.npmjs.org/pear-stamp/-/pear-stamp-1.0.2.tgz",
@@ -3277,8 +3253,7 @@
"node_modules/which-runtime": { "node_modules/which-runtime": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.4.0.tgz", "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.4.0.tgz",
"integrity": "sha512-0ugbP4CJW4e2D20jvEcC4973dCgIaHI4Rw1PT+26U9zEve7FyYdWAIwUnoeOYvoCfn+wXHoHTKb1KhkYlb60Pw==", "integrity": "sha512-0ugbP4CJW4e2D20jvEcC4973dCgIaHI4Rw1PT+26U9zEve7FyYdWAIwUnoeOYvoCfn+wXHoHTKb1KhkYlb60Pw=="
"license": "Apache-2.0"
}, },
"node_modules/wrap-ansi": { "node_modules/wrap-ansi": {
"version": "7.0.0", "version": "7.0.0",
+53
View File
@@ -5,6 +5,48 @@
"type": "module", "type": "module",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "index.js", "main": "index.js",
"imports": {
"crypto": {
"bare": "bare-crypto",
"default": "crypto"
},
"fs": {
"bare": "bare-fs",
"default": "fs"
},
"path": {
"bare": "bare-path",
"default": "path"
},
"http": {
"bare": "bare-http1",
"default": "http"
},
"net": {
"bare": "bare-net",
"default": "net"
},
"process": {
"bare": "bare-process",
"default": "process"
},
"events": {
"bare": "bare-events",
"default": "events"
},
"os": {
"bare": "bare-os",
"default": "os"
},
"child_process": {
"bare": "bare-subprocess",
"default": "child_process"
},
"url": {
"bare": "bare-url",
"default": "url"
}
},
"pear": { "pear": {
"pre": "pear-electron/pre", "pre": "pear-electron/pre",
"name": "peardock", "name": "peardock",
@@ -38,6 +80,16 @@
}, },
"dependencies": { "dependencies": {
"b4a": "^1.8.1", "b4a": "^1.8.1",
"bare-crypto": "^1.15.3",
"bare-events": "^2.9.1",
"bare-fs": "^4.7.4",
"bare-http1": "^4.5.7",
"bare-net": "^2.3.2",
"bare-os": "^3.9.3",
"bare-path": "^3.1.1",
"bare-process": "^4.5.1",
"bare-subprocess": "^5.2.3",
"bare-url": "^2.4.5",
"compact-encoding": "^3.3.0", "compact-encoding": "^3.3.0",
"dockerode": "^5.0.1", "dockerode": "^5.0.1",
"dotenv": "^17.4.2", "dotenv": "^17.4.2",
@@ -53,6 +105,7 @@
"protomux": "^3.11.0", "protomux": "^3.11.0",
"protomux-rpc": "^1.10.0", "protomux-rpc": "^1.10.0",
"safety-catch": "^1.0.3", "safety-catch": "^1.0.3",
"which-runtime": "^1.4.0",
"z32": "^1.1.0" "z32": "^1.1.0"
}, },
"devDependencies": { "devDependencies": {