// The decision half of the worklet loader: what to preload, what to run, and in which order.
//
// Pure and dependency-free like choose-bundle.js, for the same reason — this is the code path that
// decides what EXECUTES on a device with no adb, so it has to be testable without one.
// lib/loader.cjs is the shim that supplies the real preloader and the real require.
const BUNDLE_FLAG = '--bundle='
const ADDONS_FLAG = '--addons='
/**
* Read the loader's inputs out of argv.
*
* ⛔ BY TAG, NOT BY POSITION. bare-kit calls NativeBareKit.startFile(handle, filename, args) and whether
* the native side prepends the filename to Bare.argv is settled in native code that is not visible from
* JS. lib/node.js works around the same unknown by reading the LAST argv; tagging makes this immune from
* both ends — and leaves storage in last place, so a bundle staged before the loader existed still finds
* it unchanged. That backwards compatibility is what makes the loader safe to ship to a device that is
* already holding a staged bundle.
*/
export function loaderArgs (argv) {
const find = (flag) => {
const hit = (argv || []).find((v) => typeof v === 'string' && v.startsWith(flag))
return hit ? hit.slice(flag.length) : null
}
const bundle = find(BUNDLE_FLAG)
// ⛔ NO DEFAULT. Guessing a path here would run something nobody chose, on the one code path that
// decides what executes. A throw reaches the watchdog; a guess reaches the user.
if (!bundle) throw new Error('loader: no ' + BUNDLE_FLAG + ' in argv')
return { bundle, addons: find(ADDONS_FLAG) || null }
}
/**
* Preload the staged addons, then run the bundle.
*
* ⛔ NOTHING IS CAUGHT. worklet.js writes pending.json before starting and clears it once the node
* reports ready; a marker that survives a boot condemns that bundle for good. If a failure here were
* swallowed and the APK's bundle run instead, the node WOULD reach ready, pending.json WOULD be cleared,
* and the broken bundle would be chosen again on every boot for ever. Failing loudly is the thing that
* makes the existing watchdog work — the fallback already exists, one layer up, and it only works if
* this layer stays honest.
*/
export function runLoader ({ argv, preload, load, say = () => {} }) {
const { bundle, addons } = loaderArgs(argv)
const n = preload({ dir: addons, say })
say('preloaded ' + n + ' staged addon(s); starting ' + bundle)
return load(bundle)
}
// ⛔ THE PIECE THAT REMOVES THE SIDELOAD. A preloader that only lives in the APK means gaining this
// capability costs a new APK — which is the very cost the work exists to remove. It does not have to:
// `new Addon(fileURL)` dlopens an absolute path and needs no filesystem module, and a module inside a
// bundle already knows where its bundle is. Measured on a real packed bundle:
//
// import.meta.url = file:///…/L-boot/probe.bundle/selfloc-probe.mjs
//
// So a STAGED bundle can locate the addons staged beside it with no help from the shell, which means
// the preloader ships as an ordinary JS release over the OTA that already works — to the APK that is
// already on the device.
//
// ⛔ `.bundle` MUST BE A WHOLE SEGMENT that ends the bundle file. Substring matching would take
// `/x/my.bundles/lib/a.js` or `/x/a.bundle.old/…` for a bundle and derive an addon directory that does
// not exist — a silent no-op, which is the worst kind of wrong here.
export function bundlePathFromUrl (metaUrl) {
if (typeof metaUrl !== 'string' || metaUrl === '') return null
const path = metaUrl.startsWith('file://') ? metaUrl.slice('file://'.length) : metaUrl
const parts = path.split('/')
for (let i = 0; i < parts.length - 1; i++) {
if (parts[i].endsWith('.bundle')) return parts.slice(0, i + 1).join('/')
}
return null
}
/**
* Where this module's own bundle keeps its staged addons, or null when not running from a bundle.
*
* ⛔ NAMED OFF THE BUNDLE FILE, not the tree hash, precisely so it is DERIVABLE. A name keyed on the
* tree hash would have to be handed in by the shell, and a shell old enough to predate this cannot hand
* in anything — which would put the capability back behind an APK release.
*/
export function addonsDirFor (metaUrl) {
const bundle = bundlePathFromUrl(metaUrl)
return bundle === null ? null : bundle + '.addons'
}