1113 lines
34 KiB
JavaScript
1113 lines
34 KiB
JavaScript
/**
|
|
* Host-resolved Bare / companion npm modules for in-image scripts (`ctx.bare`),
|
|
* plus optional merge from trusted IIFE bundles on the system drive.
|
|
*/
|
|
|
|
import b4a from 'b4a'
|
|
import { readFileSync } from '#host-fs'
|
|
import { dirname, join } from '#host-path'
|
|
import { fileURLToPath } from 'url'
|
|
import bareModuleManifestEmbedded from './bare-module-manifest.data.mjs'
|
|
import { pearCtxHostByPackage } from '#pear-ctx-host'
|
|
import { pearCtxPearOnlyHostByPackage } from './bare-os-ctx-pear-host-pear-only.js'
|
|
import { bareOsHostBooterWarn } from './bare-os-host-booter-log.js'
|
|
import {
|
|
bareOsStandaloneHostModulesByPackage,
|
|
bareOsStandaloneNodeBuiltinModules
|
|
} from './bare-os-ctx-bare-host-modules.mjs'
|
|
|
|
/**
|
|
* Used only by {@link tryLoadBareCtxKeyFromDriveBundlePath}: some Bare booters lack
|
|
* `TextDecoder`. Dynamic `import('b4a')` fails on Pear (no referrer); static import is fine.
|
|
* @param {Uint8Array} buf
|
|
*/
|
|
function decodeUtf8BytesBareFallback(buf) {
|
|
if (typeof TextDecoder !== 'undefined') {
|
|
try {
|
|
return new TextDecoder().decode(buf)
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
return b4a.toString(buf, 'utf8')
|
|
}
|
|
|
|
/**
|
|
* `fileURLToPath` only accepts `file:` URLs. Pear uses `pear://dev/...` for `import.meta.url`,
|
|
* so we must not call it at load time or when running under `pear run`.
|
|
* @returns {string | null}
|
|
*/
|
|
function bareOsBooterPackageJsonPathForCreateRequire() {
|
|
const href = String(import.meta.url)
|
|
if (!href.startsWith('file:')) return null
|
|
try {
|
|
return fileURLToPath(new URL('../package.json', import.meta.url))
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Root of the packed app bundle (`bare:/app.bundle/`) when running a standalone binary.
|
|
* @returns {string | null}
|
|
*/
|
|
function appBundleRootHref() {
|
|
const href = String(import.meta.url || '')
|
|
const marker = '/app.bundle/'
|
|
const i = href.indexOf(marker)
|
|
if (i < 0) return null
|
|
return href.slice(0, i + marker.length)
|
|
}
|
|
|
|
/** Packages whose `.bare` addons drive IIFEs commonly need via `require.addon()`. */
|
|
const DRIVE_BUNDLE_ADDON_PACKAGE_CANDIDATES = [
|
|
'bare-os',
|
|
'bare-url',
|
|
'bare-path',
|
|
'bare-fs',
|
|
'bare-crypto',
|
|
'bare-buffer',
|
|
'bare-tcp',
|
|
'bare-tls',
|
|
'bare-pipe',
|
|
'bare-dns',
|
|
'bare-zlib',
|
|
'bare-hrtime',
|
|
'bare-signals',
|
|
'bare-tty',
|
|
'bare-stdio',
|
|
'bare-module',
|
|
'bare-inspect',
|
|
'bare-performance',
|
|
'bare-subprocess',
|
|
'bare-net',
|
|
'bare-dgram',
|
|
'bare-https',
|
|
'bare-http1',
|
|
'bare-ws',
|
|
'bare-fetch',
|
|
'bare-abort',
|
|
'bare-type',
|
|
'bare-type-stripper',
|
|
'bare-module-lexer',
|
|
'udx-native',
|
|
'sodium-native',
|
|
'quickbit-native',
|
|
'simdle-native',
|
|
'rocksdb-native',
|
|
'fs-native-extensions'
|
|
]
|
|
|
|
/**
|
|
* Drive IIFEs call `require("events")`, `require("bare-stream")`, …; `new Function` has no
|
|
* lexical `require`, so they look at **`globalThis.require`**. Bundled code also calls
|
|
* **`__require.addon()`** (native bindings); that only works when `globalThis.require` is from
|
|
* **`bare-module`'s `createRequire`** (exposes **`require.addon`**). A bare function or Node-only
|
|
* `require` breaks eval with **`__require.addon is not a function`**.
|
|
*
|
|
* Order: if existing `require` already has **`addon`**, use it; else install **`createRequire`**
|
|
* from the booter **`file:`** package.json when available; else **`createRequire(import.meta.url)`**
|
|
* (covers **`pear://`** on Bare). If **`addon`** is still missing (e.g. Node dev), attach a stub
|
|
* for the duration of eval only.
|
|
*
|
|
* On **`bare:/app.bundle/`** standalones, prefer **`createRequire`** rooted at
|
|
* **`bare-node-runtime`** (Node builtin imports map) and fall back **`require.addon('.')`** across
|
|
* packed native packages — drive IIFEs otherwise resolve addons against this file's URL and fail.
|
|
*
|
|
* Prefer the statically packed `bare-module` binding: dynamic `import('bare-module')` often
|
|
* fails under `bare:/app.bundle/` (no per-referrer resolution entry for this file).
|
|
*
|
|
* @param {string} parentURL
|
|
* @returns {Promise<Function | null>}
|
|
*/
|
|
async function tryBareModuleCreateRequire(parentURL) {
|
|
try {
|
|
const bm =
|
|
bareOsStandaloneHostModulesByPackage['bare-module'] ||
|
|
(await import('bare-module'))
|
|
const createRequire = bm.createRequire || bm.default?.createRequire
|
|
if (typeof createRequire !== 'function') return null
|
|
return createRequire(parentURL)
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolve a CommonJS-style specifier for drive IIFEs using packed host modules when
|
|
* `createRequire` cannot see Node builtins under the app bundle.
|
|
* @param {string} specifier
|
|
* @returns {unknown | undefined}
|
|
*/
|
|
function resolveDriveBundleHostModule(specifier) {
|
|
if (typeof specifier !== 'string' || !specifier) return undefined
|
|
const builtin = bareOsStandaloneNodeBuiltinModules[specifier]
|
|
if (builtin !== undefined) {
|
|
return builtin?.default !== undefined ? builtin.default : builtin
|
|
}
|
|
if (specifier.startsWith('node:')) {
|
|
const bare = bareOsStandaloneNodeBuiltinModules[specifier.slice(5)]
|
|
if (bare !== undefined) {
|
|
return bare?.default !== undefined ? bare.default : bare
|
|
}
|
|
}
|
|
const packed = bareOsStandaloneHostModulesByPackage[specifier]
|
|
if (packed !== undefined) {
|
|
return packed?.default !== undefined ? packed.default : packed
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
/**
|
|
* @param {Function | null} primary
|
|
* @param {Function[]} fallbacks
|
|
*/
|
|
function composeDriveBundleAddon(primary, fallbacks) {
|
|
const list = []
|
|
if (typeof primary === 'function') list.push(primary)
|
|
for (const fn of fallbacks) {
|
|
if (typeof fn === 'function' && !list.includes(fn)) list.push(fn)
|
|
}
|
|
if (!list.length) return driveBundleRequireAddonStub()
|
|
|
|
const composed = function bareOsDriveBundleAddon(specifier, opts) {
|
|
const spec = specifier == null || specifier === '' ? '.' : specifier
|
|
/** @type {unknown} */
|
|
let lastErr = null
|
|
for (const fn of list) {
|
|
try {
|
|
return fn.call(this, spec, opts)
|
|
} catch (err) {
|
|
lastErr = err
|
|
}
|
|
}
|
|
throw lastErr instanceof Error
|
|
? lastErr
|
|
: new Error(String(lastErr || 'ADDON_NOT_FOUND'))
|
|
}
|
|
const first = list[0]
|
|
try {
|
|
if (typeof first.resolve === 'function')
|
|
composed.resolve = first.resolve.bind(first)
|
|
if (first.host !== undefined) composed.host = first.host
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
return composed
|
|
}
|
|
|
|
/**
|
|
* Pear `import('pkg')` can fail when the current referrer is a `pear://` booter URL.
|
|
* Try loading via bare-module's createRequire from the booter package root first,
|
|
* then fall back to `createRequire(import.meta.url)`.
|
|
*
|
|
* @param {string} specifier
|
|
* @returns {Promise<unknown>}
|
|
*/
|
|
export async function tryRequireFromBooter(specifier) {
|
|
if (typeof specifier !== 'string' || !specifier.trim()) return undefined
|
|
|
|
const liveRequire = globalThis.require
|
|
if (typeof liveRequire === 'function') {
|
|
try {
|
|
return liveRequire(specifier)
|
|
} catch {
|
|
/* continue */
|
|
}
|
|
}
|
|
|
|
const pkgJson = bareOsBooterPackageJsonPathForCreateRequire()
|
|
if (pkgJson) {
|
|
try {
|
|
const urlMod = await import('url')
|
|
const href = urlMod.pathToFileURL(pkgJson).href
|
|
const req = await tryBareModuleCreateRequire(href)
|
|
if (typeof req === 'function') return req(specifier)
|
|
} catch {
|
|
/* continue */
|
|
}
|
|
}
|
|
|
|
try {
|
|
const req = await tryBareModuleCreateRequire(import.meta.url)
|
|
if (typeof req === 'function') return req(specifier)
|
|
} catch {
|
|
/* continue */
|
|
}
|
|
|
|
return undefined
|
|
}
|
|
|
|
/**
|
|
* Install `globalThis.require` from bare-module `createRequire` when missing.
|
|
* Vendored CJS (bare-discord-js) and drive IIFEs look at the global.
|
|
*
|
|
* @returns {Promise<Function | null>}
|
|
*/
|
|
export async function ensureGlobalRequireForCtxBareHostImports() {
|
|
const existing = globalThis.require
|
|
if (typeof existing === 'function' && typeof existing.addon === 'function') {
|
|
return existing
|
|
}
|
|
|
|
const pkgJson = bareOsBooterPackageJsonPathForCreateRequire()
|
|
if (pkgJson) {
|
|
try {
|
|
const urlMod = await import('url')
|
|
const href = urlMod.pathToFileURL(pkgJson).href
|
|
const req = await tryBareModuleCreateRequire(href)
|
|
if (typeof req === 'function') {
|
|
globalThis.require = req
|
|
return req
|
|
}
|
|
} catch {
|
|
/* continue */
|
|
}
|
|
}
|
|
|
|
try {
|
|
const req = await tryBareModuleCreateRequire(import.meta.url)
|
|
if (typeof req === 'function') {
|
|
globalThis.require = req
|
|
return req
|
|
}
|
|
} catch {
|
|
/* continue */
|
|
}
|
|
|
|
return typeof existing === 'function' ? existing : null
|
|
}
|
|
|
|
/** @returns {import('bare-module').Require['addon']} */
|
|
function driveBundleRequireAddonStub() {
|
|
const stub = function bareOsDriveBundleAddon() {
|
|
return new Proxy(
|
|
{},
|
|
{
|
|
get() {
|
|
return function bareOsDriveBundleAddonExport() {
|
|
return {}
|
|
}
|
|
}
|
|
}
|
|
)
|
|
}
|
|
stub.resolve = function bareOsDriveBundleAddonResolve() {
|
|
return ''
|
|
}
|
|
stub.host = ''
|
|
return stub
|
|
}
|
|
|
|
/**
|
|
* Install a temporary `require` wrapper for drive-bundle eval so we control both
|
|
* module resolution and the `require.addon` surface seen by esbuild IIFEs.
|
|
*
|
|
* @param {Function | null} delegate
|
|
* @param {Function} addon
|
|
*/
|
|
function createDriveBundleRequireWrapper(delegate, addon) {
|
|
const wrapped = function bareOsDriveBundleRequire(specifier, ...rest) {
|
|
const host = resolveDriveBundleHostModule(specifier)
|
|
if (host !== undefined) return host
|
|
if (typeof delegate === 'function') {
|
|
return Reflect.apply(delegate, this, [specifier, ...rest])
|
|
}
|
|
throw new Error(`Dynamic require of "${specifier}" is not supported`)
|
|
}
|
|
if (typeof delegate === 'function') {
|
|
for (const key of ['cache', 'extensions', 'main']) {
|
|
try {
|
|
const v = delegate[key]
|
|
if (v !== undefined) wrapped[key] = v
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
}
|
|
wrapped.resolve = function bareOsDriveBundleResolve(specifier, options) {
|
|
if (resolveDriveBundleHostModule(specifier) !== undefined) return specifier
|
|
if (typeof delegate?.resolve === 'function') {
|
|
return delegate.resolve(specifier, options)
|
|
}
|
|
throw new Error(`Cannot resolve module "${specifier}"`)
|
|
}
|
|
wrapped.addon = addon
|
|
return wrapped
|
|
}
|
|
|
|
async function withDriveBundleGlobalRequire(fn) {
|
|
const origRequire = globalThis.require
|
|
/** @type {Function | null} */
|
|
let delegate =
|
|
typeof origRequire === 'function'
|
|
? /** @type {Function} */ (origRequire)
|
|
: null
|
|
|
|
const bundleRoot = appBundleRootHref()
|
|
|
|
// Standalone: bare-node-runtime's createRequire carries the Node builtin import map
|
|
// (path/os/util/…) that drive IIFEs need. createRequire(this file) does not.
|
|
if (bundleRoot) {
|
|
try {
|
|
const bnrReq = await tryBareModuleCreateRequire(
|
|
new URL('node_modules/bare-node-runtime/package.json', bundleRoot).href
|
|
)
|
|
if (typeof bnrReq === 'function') delegate = bnrReq
|
|
} catch {
|
|
/* continue */
|
|
}
|
|
}
|
|
|
|
if (!delegate) {
|
|
const pkgJson = bareOsBooterPackageJsonPathForCreateRequire()
|
|
if (pkgJson) {
|
|
try {
|
|
const urlMod = await import('url')
|
|
const href = urlMod.pathToFileURL(pkgJson).href
|
|
const req = await tryBareModuleCreateRequire(href)
|
|
if (typeof req === 'function') delegate = req
|
|
} catch {
|
|
/* continue */
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!delegate) {
|
|
const req = await tryBareModuleCreateRequire(import.meta.url)
|
|
if (typeof req === 'function') delegate = req
|
|
}
|
|
|
|
/** @type {Function | null} */
|
|
let addon =
|
|
typeof delegate?.addon === 'function'
|
|
? /** @type {Function} */ (delegate.addon)
|
|
: null
|
|
|
|
if (!addon) {
|
|
const addonReq = await tryBareModuleCreateRequire(import.meta.url)
|
|
if (addonReq && typeof addonReq.addon === 'function') addon = addonReq.addon
|
|
}
|
|
|
|
/** @type {Function[]} */
|
|
const addonFallbacks = []
|
|
if (bundleRoot) {
|
|
for (const name of DRIVE_BUNDLE_ADDON_PACKAGE_CANDIDATES) {
|
|
try {
|
|
const req = await tryBareModuleCreateRequire(
|
|
new URL(`node_modules/${name}/package.json`, bundleRoot).href
|
|
)
|
|
if (req && typeof req.addon === 'function')
|
|
addonFallbacks.push(req.addon)
|
|
} catch {
|
|
/* skip missing / unpackaged natives */
|
|
}
|
|
}
|
|
}
|
|
|
|
globalThis.require = createDriveBundleRequireWrapper(
|
|
delegate,
|
|
composeDriveBundleAddon(addon, addonFallbacks)
|
|
)
|
|
|
|
try {
|
|
return await fn(globalThis.require)
|
|
} finally {
|
|
if (origRequire === undefined) delete globalThis.require
|
|
else globalThis.require = origRequire
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Eval a drive IIFE with an explicit `require` binding (must expose `.addon`) and
|
|
* CommonJS-ish `__filename` / `__dirname` shims.
|
|
* @param {string} logicalPath
|
|
* @param {string} source
|
|
* @param {Function} [req]
|
|
*/
|
|
function runDriveBundleSource(logicalPath, source, req) {
|
|
const filename = `bare-drive-bundle:${logicalPath}`
|
|
const slash = logicalPath.lastIndexOf('/')
|
|
const dirname = slash >= 0 ? logicalPath.slice(0, slash) || '/' : '/'
|
|
const requireFn =
|
|
typeof req === 'function'
|
|
? req
|
|
: typeof globalThis.require === 'function'
|
|
? globalThis.require
|
|
: function missingRequire(x) {
|
|
throw new Error(`Dynamic require of "${x}" is not supported`)
|
|
}
|
|
const run = new Function(
|
|
'require',
|
|
'__filename',
|
|
'__dirname',
|
|
`"use strict";\n${source}\n//# sourceURL=${filename}`
|
|
)
|
|
run(requireFn, filename, dirname)
|
|
}
|
|
|
|
export const BARE_OS_STDLIB_GLOBAL = '__bare_os_stdlib__'
|
|
|
|
const MANIFEST_NAME = 'bare-module-manifest.json'
|
|
|
|
/**
|
|
* @param {unknown} m
|
|
* @returns {typeof bareModuleManifestEmbedded}
|
|
*/
|
|
function cloneBareModuleManifest(m) {
|
|
return /** @type {typeof bareModuleManifestEmbedded} */ (
|
|
JSON.parse(JSON.stringify(m))
|
|
)
|
|
}
|
|
|
|
/** @type {{ version: number, entries: Array<{ ctxKey: string, package: string, export?: string, bundle?: boolean, optional?: boolean, tier?: string }> } | null} */
|
|
let _cachedManifest = null
|
|
|
|
/**
|
|
* Embedded copy of **`bare-module-manifest.json`** for Pear (`pear://` **import.meta.url**).
|
|
* `bare-fs` coerces **`URL`** arguments through **`bare-url.fileURLToPath`**, which rejects
|
|
* non-`file:` schemes, so **`readFileSync(new URL('pear:…'))`** cannot load the JSON from the
|
|
* bundle. Static import of **`bare-module-manifest.data.mjs`** (regenerated by
|
|
* **`npm run sync:bare-manifest`**) keeps the manifest in the Pear module graph.
|
|
*
|
|
* On **`file:`** trees, the JSON on disk is preferred so edits apply without regenerating `.data.mjs`.
|
|
*/
|
|
export function loadBareModuleManifest() {
|
|
if (_cachedManifest) return _cachedManifest
|
|
const href = String(import.meta.url)
|
|
if (href.startsWith('pear:')) {
|
|
_cachedManifest = cloneBareModuleManifest(bareModuleManifestEmbedded)
|
|
return _cachedManifest
|
|
}
|
|
if (href.startsWith('file:')) {
|
|
try {
|
|
const libDir = dirname(fileURLToPath(href))
|
|
const raw = readFileSync(join(libDir, MANIFEST_NAME), 'utf8')
|
|
_cachedManifest = JSON.parse(raw)
|
|
} catch {
|
|
_cachedManifest = cloneBareModuleManifest(bareModuleManifestEmbedded)
|
|
}
|
|
return _cachedManifest
|
|
}
|
|
_cachedManifest = cloneBareModuleManifest(bareModuleManifestEmbedded)
|
|
return _cachedManifest
|
|
}
|
|
|
|
/** Exported for tests: default export object from **`bare-module-manifest.data.mjs`**. */
|
|
export function bareOsBareModuleManifestEmbeddedRef() {
|
|
return bareModuleManifestEmbedded
|
|
}
|
|
|
|
/** Cached pear manifest (reuses the same underlying manifest object for v1). */
|
|
let _cachedPearManifest = null
|
|
|
|
export function loadPearModuleManifest() {
|
|
if (_cachedPearManifest) return _cachedPearManifest
|
|
const full = loadBareModuleManifest()
|
|
_cachedPearManifest = {
|
|
version: full.version,
|
|
entries: Array.isArray(full.pearEntries) ? full.pearEntries : []
|
|
}
|
|
return _cachedPearManifest
|
|
}
|
|
|
|
/**
|
|
* @param {Record<string, string>} shellEnv
|
|
*/
|
|
export function bareOsBareModulesEnabled(shellEnv) {
|
|
if (!shellEnv || typeof shellEnv !== 'object') return true
|
|
const v = shellEnv.BARE_OS_BARE_MODULES
|
|
return v !== '0' && v !== 'false'
|
|
}
|
|
|
|
/**
|
|
* @param {Record<string, string>} shellEnv
|
|
*/
|
|
export function bareOsBareDriveBundlesEnabled(shellEnv) {
|
|
if (!shellEnv || typeof shellEnv !== 'object') return true
|
|
const v = shellEnv.BARE_OS_BARE_DRIVE_BUNDLES
|
|
if (v === '0' || v === 'false') return false
|
|
return bareOsBareModulesEnabled(shellEnv)
|
|
}
|
|
|
|
/**
|
|
* When false, skip host `import(pkg)` for ctx.bare (use only `/lib/bare` IIFEs).
|
|
* Set **`BARE_OS_BARE_HOST_IMPORTS=0`** for a fully image-local stdlib.
|
|
*/
|
|
export function bareOsBareHostImportsEnabled(shellEnv) {
|
|
if (!shellEnv || typeof shellEnv !== 'object') return true
|
|
const v = shellEnv.BARE_OS_BARE_HOST_IMPORTS
|
|
if (v === '0' || v === 'false') return false
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* Populate object with host `import()` results (per manifest). Mutates `target`.
|
|
* @param {Record<string, string>} shellEnv
|
|
* @param {Record<string, unknown>} target
|
|
*/
|
|
function bareHostRuntime() {
|
|
if (typeof globalThis.Bare !== 'undefined') return true
|
|
const v = globalThis.process?.versions
|
|
return Boolean(v && typeof v.bare === 'string')
|
|
}
|
|
|
|
export async function buildBareCtxObjectFromHost(shellEnv, target) {
|
|
if (!bareOsBareModulesEnabled(shellEnv)) return
|
|
if (!bareOsBareHostImportsEnabled(shellEnv)) return
|
|
const pearBooter =
|
|
typeof import.meta !== 'undefined' &&
|
|
String(import.meta.url || '').startsWith('pear:')
|
|
const { entries } = loadBareModuleManifest()
|
|
const skipHostKeys = new Set(
|
|
String(shellEnv?.BARE_OS_BARE_HOST_SKIP_CTX_KEYS || '')
|
|
.split(/[\s,]+/)
|
|
.map((s) => s.trim())
|
|
.filter(Boolean)
|
|
)
|
|
const onlyHostKeysRaw = String(
|
|
shellEnv?.BARE_OS_BARE_HOST_ONLY_CTX_KEYS || ''
|
|
)
|
|
.split(/[\s,]+/)
|
|
.map((s) => s.trim())
|
|
.filter(Boolean)
|
|
const onlyHostKeys =
|
|
onlyHostKeysRaw.length > 0 ? new Set(onlyHostKeysRaw) : null
|
|
const onBare = bareHostRuntime()
|
|
const tasks = entries.map(async (ent) => {
|
|
if (!onBare && ent.nativeHint === true) return null
|
|
const key = ent.ctxKey
|
|
if (!key || target[key] !== undefined) return null
|
|
if (skipHostKeys.has(key)) return null
|
|
if (onlyHostKeys && !onlyHostKeys.has(key)) return null
|
|
// Pear (`pear:` import.meta.url): bare-module cannot resolve `import("holesail")` from
|
|
// this booter URL (MODULE_NOT_FOUND / no referrer). Drive IIFEs supply bundle:true keys.
|
|
if (pearBooter && ent.bundle === true) return null
|
|
try {
|
|
const packed = bareOsStandaloneHostModulesByPackage[ent.package]
|
|
if (packed !== undefined) {
|
|
const val = ent.sideEffectImport
|
|
? packed?.default !== undefined
|
|
? packed.default
|
|
: true
|
|
: ent.export === '*'
|
|
? packed
|
|
: packed?.default !== undefined
|
|
? packed.default
|
|
: packed
|
|
return { key, ent, val }
|
|
}
|
|
const mod = await import(/* webpackIgnore: true */ ent.package)
|
|
const val = ent.sideEffectImport
|
|
? mod?.default !== undefined
|
|
? mod.default
|
|
: true
|
|
: ent.export === '*'
|
|
? mod
|
|
: mod?.default !== undefined
|
|
? mod.default
|
|
: mod
|
|
return { key, ent, val }
|
|
} catch (err) {
|
|
return { key, ent, err }
|
|
}
|
|
})
|
|
const settled = await Promise.all(tasks)
|
|
for (const r of settled) {
|
|
if (!r) continue
|
|
const { key, ent, val, err } = r
|
|
if (err) {
|
|
if (!ent.optional) {
|
|
bareOsHostBooterWarn(
|
|
'ctx_bare_module_load_failed',
|
|
`ctx.bare.${key}: failed to load "${ent.package}"`,
|
|
err?.message || String(err)
|
|
)
|
|
}
|
|
continue
|
|
}
|
|
if (val !== undefined) target[key] = val
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {{ sideEffectImport?: boolean, export?: string }} ent
|
|
* @param {unknown} mod
|
|
*/
|
|
function pearEntryExportValue(ent, mod) {
|
|
if (ent.sideEffectImport) {
|
|
return mod?.default !== undefined ? mod.default : true
|
|
}
|
|
if (ent.export === '*') return mod
|
|
return mod?.default !== undefined ? mod.default : mod
|
|
}
|
|
|
|
/** @type {Record<string, unknown> | null} */
|
|
let _pearCtxPearOnlyHostByPackage = null
|
|
|
|
function pearCtxPearOnlyHostMap() {
|
|
if (_pearCtxPearOnlyHostByPackage) return _pearCtxPearOnlyHostByPackage
|
|
_pearCtxPearOnlyHostByPackage = pearCtxPearOnlyHostByPackage
|
|
return _pearCtxPearOnlyHostByPackage
|
|
}
|
|
|
|
/**
|
|
* Load one Pear-tier package from the booter host. On Bare/Pear, use the static
|
|
* `#pear-ctx-host` map (Pear-traced imports). pear-bundle and pear-ref come from
|
|
* bare-os-ctx-pear-host-pear-only.js (populated when globalThis.Pear exists).
|
|
* On Node dev, fall back to require/import.
|
|
*
|
|
* @param {{ package: string, sideEffectImport?: boolean, export?: string }} ent
|
|
* @param {{ pearBooter: boolean, pearRequire: Function | null, onBare: boolean }} opts
|
|
*/
|
|
async function loadPearPackageFromHost(
|
|
ent,
|
|
{ pearBooter, pearRequire, onBare }
|
|
) {
|
|
if (
|
|
(pearBooter || onBare) &&
|
|
Object.prototype.hasOwnProperty.call(pearCtxHostByPackage, ent.package)
|
|
) {
|
|
return pearEntryExportValue(ent, pearCtxHostByPackage[ent.package])
|
|
}
|
|
|
|
const pearOnly = pearCtxPearOnlyHostMap()
|
|
if (
|
|
(pearBooter || globalThis.Pear) &&
|
|
Object.prototype.hasOwnProperty.call(pearOnly, ent.package)
|
|
) {
|
|
return pearEntryExportValue(ent, pearOnly[ent.package])
|
|
}
|
|
|
|
if (typeof pearRequire === 'function') {
|
|
try {
|
|
const mod = pearRequire(ent.package)
|
|
return pearEntryExportValue(ent, mod)
|
|
} catch {
|
|
/* continue */
|
|
}
|
|
}
|
|
|
|
try {
|
|
const mod = await import(/* webpackIgnore: true */ ent.package)
|
|
return pearEntryExportValue(ent, mod)
|
|
} catch (directErr) {
|
|
const mod = await tryRequireFromBooter(ent.package)
|
|
if (mod !== undefined) return pearEntryExportValue(ent, mod)
|
|
throw directErr
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Populate a target object with Pear-tier packages from the manifest (host import path only for v1).
|
|
* Parallel to buildBareCtxObjectFromHost but operates on pearEntries.
|
|
* @param {Record<string, string>} shellEnv
|
|
* @param {Record<string, unknown>} target
|
|
*/
|
|
export async function buildPearCtxObjectFromHost(
|
|
shellEnv,
|
|
target,
|
|
bareFallback = null
|
|
) {
|
|
// For v1 we gate on the same master bare modules flag.
|
|
// A dedicated BARE_OS_PEAR_MODULES env can be added later.
|
|
if (!bareOsBareModulesEnabled(shellEnv)) return
|
|
if (!bareOsBareHostImportsEnabled(shellEnv)) return
|
|
|
|
const { entries } = loadPearModuleManifest()
|
|
if (!entries || entries.length === 0) return
|
|
|
|
const onBare = bareHostRuntime()
|
|
const pearBooter =
|
|
typeof import.meta !== 'undefined' &&
|
|
String(import.meta.url || '').startsWith('pear:')
|
|
|
|
/** @type {Function | null} */
|
|
let pearRequire = null
|
|
if (!pearBooter) {
|
|
try {
|
|
const pkgJsonPath = bareOsBooterPackageJsonPathForCreateRequire()
|
|
if (pkgJsonPath) {
|
|
const urlMod = await import('url')
|
|
const href = urlMod.pathToFileURL(pkgJsonPath).href
|
|
pearRequire = await tryBareModuleCreateRequire(href)
|
|
}
|
|
} catch (_) {}
|
|
if (!pearRequire) {
|
|
pearRequire = await tryBareModuleCreateRequire(import.meta.url)
|
|
}
|
|
}
|
|
|
|
const tasks = entries.map(async (ent) => {
|
|
if (!onBare && ent.nativeHint === true) return null
|
|
const key = ent.ctxKey
|
|
if (!key || target[key] !== undefined) return null
|
|
|
|
try {
|
|
const val = await loadPearPackageFromHost(ent, {
|
|
pearBooter,
|
|
pearRequire,
|
|
onBare
|
|
})
|
|
return { key, ent, val }
|
|
} catch (err) {
|
|
if (
|
|
bareFallback &&
|
|
typeof bareFallback === 'object' &&
|
|
bareFallback[key] !== undefined
|
|
) {
|
|
return { key, ent, val: bareFallback[key], fromFallback: true }
|
|
}
|
|
return { key, ent, err }
|
|
}
|
|
})
|
|
|
|
const settled = await Promise.all(tasks)
|
|
for (const r of settled) {
|
|
if (!r) continue
|
|
const { key, ent, val, err, fromFallback } = r
|
|
if (err) {
|
|
const pearOnly =
|
|
ent.package === 'pear-bundle' ||
|
|
ent.package === 'pear-ref' ||
|
|
ent.package === 'pear-build'
|
|
if (pearOnly && !globalThis.Pear) {
|
|
// Bare standalone has no Pear runtime; skip noisy optional failures.
|
|
continue
|
|
}
|
|
if (ent.optional && pearOnly) continue
|
|
bareOsHostBooterWarn(
|
|
'ctx_pear_module_load_failed',
|
|
`ctx.pear.${key}: failed to load "${ent.package}"`,
|
|
err?.message || String(err)
|
|
)
|
|
continue
|
|
}
|
|
if (val !== undefined) {
|
|
target[key] = val
|
|
if (fromFallback) {
|
|
target[`_${key}_fromBare`] = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Execute trusted bundle sources that assign `globalThis.__bare_os_stdlib__[ctxKey]`.
|
|
* Fills missing keys on `target` only.
|
|
* @param {Record<string, string>} shellEnv
|
|
* @param {{ readFile: (p: string, opts?: unknown) => Promise<Uint8Array | null> }} vfs
|
|
* @param {Record<string, unknown>} target
|
|
*/
|
|
/**
|
|
* Optional `/lib/bare/bare-module-lock.json` on the system image: `{ "pins": { "ctxKey": "semver" } }`.
|
|
* Emits warnings for pins whose `ctxKey` is still missing after drive merge + host import.
|
|
* @param {{ readFile: (p: string) => Promise<Uint8Array | null> }} vfs
|
|
* @param {Record<string, unknown>} target
|
|
* @returns {Promise<string[]>}
|
|
*/
|
|
export async function verifyBareModuleLockfile(vfs, target) {
|
|
/** @type {string[]} */
|
|
const warnings = []
|
|
if (!vfs || typeof vfs.readFile !== 'function') return warnings
|
|
let buf
|
|
try {
|
|
buf = await vfs.readFile('/lib/bare/bare-module-lock.json')
|
|
} catch {
|
|
return warnings
|
|
}
|
|
if (!buf || !buf.byteLength) return warnings
|
|
let j
|
|
try {
|
|
let u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
|
if (
|
|
u8.byteLength >= 3 &&
|
|
u8[0] === 0xef &&
|
|
u8[1] === 0xbb &&
|
|
u8[2] === 0xbf
|
|
) {
|
|
u8 = u8.subarray(3)
|
|
}
|
|
j = JSON.parse(new TextDecoder().decode(u8))
|
|
} catch {
|
|
warnings.push('bare-module-lock.json: invalid JSON')
|
|
return warnings
|
|
}
|
|
const pins = j && typeof j === 'object' ? j.pins : null
|
|
if (!pins || typeof pins !== 'object') return warnings
|
|
for (const [k, ver] of Object.entries(pins)) {
|
|
if (target[k] === undefined) {
|
|
warnings.push(
|
|
`bare-module-lock: ctx.bare.${k} missing (pinned ${String(ver)})`
|
|
)
|
|
}
|
|
}
|
|
return warnings
|
|
}
|
|
|
|
/**
|
|
* esbuild IIFE assigns `__bare_os_bundle_exports__` (CJS interop object), not the
|
|
* default export itself. Entries use `export default <binding>` → `{ default: binding }`.
|
|
* @param {unknown} val
|
|
*/
|
|
function unwrapDriveBundleExport(val) {
|
|
if (val === null || val === undefined) return val
|
|
if (typeof val !== 'object') return val
|
|
if (Object.prototype.hasOwnProperty.call(val, 'default')) {
|
|
const d = /** @type {{ default?: unknown }} */ (val).default
|
|
if (d !== undefined) return d
|
|
}
|
|
return val
|
|
}
|
|
|
|
/**
|
|
* Eval a single `/lib/bare/bundles/*.js` IIFE (same as {@link maybeMergeBareFromDrive}) and
|
|
* return one `ctxKey` from `globalThis.__bare_os_stdlib__`. Does not mutate `ctx.bare`.
|
|
* Used when Pear skipped populating a key (merge short-circuit or eval failure).
|
|
*
|
|
* @param {Record<string, unknown>} ctx
|
|
* @param {{ readFile: (p: string) => Promise<Uint8Array | null> }} vfs
|
|
* @param {string} logicalPath
|
|
* @param {string} ctxKey
|
|
* @returns {Promise<unknown>}
|
|
*/
|
|
export async function tryLoadBareCtxKeyFromDriveBundlePath(
|
|
ctx,
|
|
vfs,
|
|
logicalPath,
|
|
ctxKey
|
|
) {
|
|
void ctx
|
|
if (!vfs || typeof vfs.readFile !== 'function') return undefined
|
|
if (
|
|
typeof logicalPath !== 'string' ||
|
|
!logicalPath.startsWith('/lib/bare/') ||
|
|
typeof ctxKey !== 'string' ||
|
|
!ctxKey
|
|
) {
|
|
return undefined
|
|
}
|
|
let buf
|
|
try {
|
|
buf = await vfs.readFile(logicalPath)
|
|
} catch {
|
|
return undefined
|
|
}
|
|
if (!buf || !buf.byteLength) return undefined
|
|
const text = decodeUtf8BytesBareFallback(buf)
|
|
if (text.length > 12 * 1024 * 1024) return undefined
|
|
|
|
const g = globalThis
|
|
const sym = BARE_OS_STDLIB_GLOBAL
|
|
/** @type {string | null} */
|
|
let errMsg = null
|
|
|
|
await withDriveBundleGlobalRequire(async (req) => {
|
|
g[sym] = g[sym] && typeof g[sym] === 'object' ? g[sym] : {}
|
|
try {
|
|
runDriveBundleSource(logicalPath, text, req)
|
|
} catch (err) {
|
|
errMsg =
|
|
(err && /** @type {{ message?: string }} */ (err).message) ||
|
|
String(err)
|
|
}
|
|
})
|
|
|
|
if (errMsg) {
|
|
bareOsHostBooterWarn(
|
|
'drive_bundle_eval_failed',
|
|
`tryLoadBareCtxKeyFromDriveBundlePath ${logicalPath}`,
|
|
errMsg
|
|
)
|
|
return undefined
|
|
}
|
|
const snap = g[sym]
|
|
if (!snap || typeof snap !== 'object') return undefined
|
|
if (!Object.prototype.hasOwnProperty.call(snap, ctxKey)) return undefined
|
|
return unwrapDriveBundleExport(snap[ctxKey])
|
|
}
|
|
|
|
export async function maybeMergeBareFromDrive(shellEnv, vfs, target) {
|
|
if (!bareOsBareModulesEnabled(shellEnv)) return
|
|
if (!bareOsBareDriveBundlesEnabled(shellEnv)) return
|
|
if (!vfs || typeof vfs.readFile !== 'function') return
|
|
|
|
const perfBareStdlib =
|
|
shellEnv?.BARE_OS_BOOT_PERF_DETAIL === '1' ||
|
|
shellEnv?.BARE_OS_BOOT_PERF_DETAIL === 'true'
|
|
const hrtBig =
|
|
typeof globalThis.process?.hrtime?.bigint === 'function'
|
|
? () => globalThis.process.hrtime.bigint()
|
|
: null
|
|
const t0 = perfBareStdlib && hrtBig ? hrtBig() : null
|
|
|
|
let metaBuf
|
|
try {
|
|
metaBuf = await vfs.readFile('/lib/bare/manifest.json')
|
|
} catch {
|
|
metaBuf = null
|
|
}
|
|
if (!metaBuf || metaBuf.byteLength === 0) return
|
|
|
|
let meta
|
|
try {
|
|
const text = new TextDecoder().decode(metaBuf)
|
|
meta = JSON.parse(text)
|
|
} catch {
|
|
return
|
|
}
|
|
const bundles = meta?.bundles
|
|
if (!Array.isArray(bundles)) return
|
|
|
|
const g = globalThis
|
|
const sym = BARE_OS_STDLIB_GLOBAL
|
|
g[sym] = g[sym] && typeof g[sym] === 'object' ? g[sym] : {}
|
|
|
|
const concRaw = String(
|
|
shellEnv?.BARE_OS_BARE_STDLIB_RESOLVE_CONCURRENCY ?? ''
|
|
).trim()
|
|
const concParsed = Number.parseInt(concRaw, 10)
|
|
const readConc =
|
|
Number.isFinite(concParsed) && concParsed >= 1
|
|
? Math.min(concParsed, 32)
|
|
: 4
|
|
|
|
// Standalone already host-imports packed modules. Drive IIFEs for optional
|
|
// native tooling (bare-apk, bare-ffmpeg, …) fail without their own addons —
|
|
// only chase keys that are still missing and either required or bundle:true.
|
|
const standaloneBare = String(import.meta.url || '').startsWith('bare:')
|
|
const { entries: manifestEntries } = loadBareModuleManifest()
|
|
/** @type {Map<string, { optional?: boolean, bundle?: boolean }>} */
|
|
const entByKey = new Map(manifestEntries.map((e) => [e.ctxKey, e]))
|
|
/**
|
|
* @param {string[]} keys
|
|
* @returns {string[]}
|
|
*/
|
|
const keysNeedingDrive = (keys) =>
|
|
keys.filter((k) => {
|
|
if (typeof k !== 'string' || !k || target[k] !== undefined) return false
|
|
if (!standaloneBare) return true
|
|
const ent = entByKey.get(k)
|
|
if (!ent) return true
|
|
if (ent.optional && ent.bundle !== true) return false
|
|
return true
|
|
})
|
|
|
|
await withDriveBundleGlobalRequire(async (req) => {
|
|
/** @type {{ path: string, keys: string[] }[]} */
|
|
const work = []
|
|
/** @type {Set<string>} */
|
|
const seenPaths = new Set()
|
|
let allKeysSatisfied = true
|
|
for (const b of bundles) {
|
|
const path = typeof b?.path === 'string' ? b.path : ''
|
|
const keys = Array.isArray(b?.keys) ? b.keys : []
|
|
if (!path.startsWith('/lib/bare/')) continue
|
|
if (seenPaths.has(path)) continue
|
|
seenPaths.add(path)
|
|
const needed = keysNeedingDrive(keys)
|
|
if (needed.length > 0) allKeysSatisfied = false
|
|
if (needed.length === 0) continue
|
|
work.push({ path, keys })
|
|
}
|
|
|
|
if (work.length === 0 || allKeysSatisfied) {
|
|
await tryBareFetchImportWhenDriveMissing(target)
|
|
return
|
|
}
|
|
|
|
/** @type {{ path: string, keys: string[], source: string | null }[]} */
|
|
const prepared = []
|
|
for (let i = 0; i < work.length; ) {
|
|
const slice = work.slice(i, i + readConc)
|
|
i += slice.length
|
|
const chunk = await Promise.all(
|
|
slice.map(async ({ path, keys }) => {
|
|
let srcBuf
|
|
try {
|
|
srcBuf = await vfs.readFile(path)
|
|
} catch {
|
|
srcBuf = null
|
|
}
|
|
if (!srcBuf || srcBuf.byteLength === 0) {
|
|
return { path, keys, source: null }
|
|
}
|
|
const source = new TextDecoder().decode(srcBuf)
|
|
if (source.length > 12 * 1024 * 1024) {
|
|
return { path, keys, source: null }
|
|
}
|
|
return { path, keys, source }
|
|
})
|
|
)
|
|
prepared.push(...chunk)
|
|
}
|
|
|
|
for (const { path, keys, source } of prepared) {
|
|
if (!source) continue
|
|
|
|
const stillNeeded = keysNeedingDrive(keys)
|
|
if (stillNeeded.length === 0) continue
|
|
|
|
try {
|
|
runDriveBundleSource(path, source, req)
|
|
} catch (err) {
|
|
bareOsHostBooterWarn(
|
|
'drive_bundle_eval_failed',
|
|
`drive bundle failed ${path}`,
|
|
err?.message || String(err)
|
|
)
|
|
continue
|
|
}
|
|
|
|
const snap = g[sym]
|
|
if (!snap || typeof snap !== 'object') continue
|
|
|
|
for (const k of keys) {
|
|
if (typeof k !== 'string' || !k) continue
|
|
if (target[k] !== undefined) continue
|
|
if (Object.prototype.hasOwnProperty.call(snap, k)) {
|
|
target[k] = unwrapDriveBundleExport(snap[k])
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
await tryBareFetchImportWhenDriveMissing(target)
|
|
|
|
if (t0 && hrtBig) {
|
|
const ns = Number(hrtBig() - t0)
|
|
bareOsHostBooterWarn(
|
|
'bare_stdlib_merge_ns',
|
|
'maybeMergeBareFromDrive wall (hrtime bigint delta ns)',
|
|
String(ns)
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* When drive `fetch.js` eval fails (no `globalThis.require` or native addons), Bare can still
|
|
* `import('bare-fetch')` from the booter — same as `buildBareCtxObjectFromHost`, but runs even
|
|
* when the manifest marks the entry host-only.
|
|
* @param {Record<string, unknown>} target
|
|
*/
|
|
async function tryBareFetchImportWhenDriveMissing(target) {
|
|
if (target.fetch !== undefined) return
|
|
if (!bareHostRuntime()) return
|
|
try {
|
|
let mod
|
|
if (typeof import.meta.resolve === 'function') {
|
|
try {
|
|
mod = await import(import.meta.resolve('bare-fetch'))
|
|
} catch {
|
|
mod = await import('bare-fetch')
|
|
}
|
|
} else {
|
|
mod = await import('bare-fetch')
|
|
}
|
|
const f = mod?.default
|
|
if (typeof f === 'function') target.fetch = f
|
|
} catch {
|
|
/* optional */
|
|
}
|
|
}
|