OverviewFilesCommitsIssuesDocsReleasesPeers

pgh commits / e3df372

0.1.152: the registry replica can live somewhere other than $HOME
⛔ MEASURED ON A HANDSET. `registryStoreDir()` joins `homedir()`, and Android has no home directory — so `resolveRelease` threw ENOENT stat'ing the path, `swarmFetcher` caught it, and OTA reported "no swarm fetcher — updates disabled this boot". A phone that could never update, with the reason visible nowhere until the app was taught to surface its own last OTA line. `opts.storeDir` overrides it. Additive: absent, behaviour is unchanged for every existing caller. ⚠ Third instance of this class in one day — a binary assumed on PATH (ffmpeg), a vault assumed under ~ (favela link), now a Corestore assumed under ~. A library that picks a path out of the environment works everywhere the environment has one. The embedder knows where its state goes; ask it. ⚠ pgh's own suite could not be run to check this: it uses Node `brittle` and hyper-overlay's bare-fs will not load under Node. Pre-existing and unrelated, but stated rather than left as an implied green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
e3df372fe536262d2fcda332a8200f19a1cf24d6
by plankatron <fleeky@gmail.com> · 2026-09-10T16:39:10.000Z

Changed files (2) +16 -5

lib/resolve-release.js +14 -3
……@@ -57,6 +57,8 @@ export function registryStoreDir (registryKeyHex) {
5757 * @param {string} [opts.version] exact version; omitted = latest
5858 * @param {boolean} [opts.preferSidecar=true] try the sidecar's replica first
5959 * @param {number} [opts.settleMs=500] post-flush settle before reading the view
60 * @param {string} [opts.storeDir] where to keep the registry replica. Defaults to a path under
61 * the home directory, which does not exist on Android — an embedder must pass its own.
6062 * @returns {Promise<{ entry: object|null, via: string, cleanup: function }>}
6163 */
6264export async function resolveRelease (opts = {}) {
……@@ -102,7 +104,16 @@ export async function resolveRelease (opts = {}) {
102104 try { if (store) await store.close() } catch {}
103105 }
104106
105 const dir = registryStoreDir(registryKeyHex)
107 // ⛔ CALLER-SUPPLIED OR THE DEFAULT, because the default assumes a HOME DIRECTORY and a phone has
108 // none. On Android this resolved to a path that cannot be stat'd, so `resolveRelease` threw
109 // ENOENT, `swarmFetcher` caught it, and OTA reported "no swarm fetcher — updates disabled this
110 // boot" — a handset that could never update, with the reason visible nowhere until the app was
111 // taught to report its own last OTA line.
112 //
113 // ⚠ Same class as a binary assumed on PATH and a vault assumed under ~: a library that picks a
114 // path from the environment works everywhere the environment has one. An embedder knows where its
115 // state goes; ask it.
116 const dir = opts.storeDir || registryStoreDir(registryKeyHex)
106117 fs.mkdirSync(dir, { recursive: true })
107118 store = new Corestore(dir)
108119 try {
……@@ -134,10 +145,10 @@ export async function resolveRelease (opts = {}) {
134145 * sidecar, `pgh app install`, and its own next resolve. Polling is hourly by
135146 * default, so the reconnect cost is irrelevant beside that.
136147 */
137export function makeGetLatestRelease ({ registryKeyHex, preferSidecar, settleMs } = {}) {
148export function makeGetLatestRelease ({ registryKeyHex, preferSidecar, settleMs, storeDir } = {}) {
138149 return async function getLatestRelease (name) {
139150 const { entry, cleanup } = await resolveRelease({
140 registryKeyHex, name, preferSidecar, settleMs
151 registryKeyHex, name, preferSidecar, settleMs, storeDir
141152 })
142153 try { return entry } finally { await cleanup() }
143154 }
package.json +2 -2
……@@ -1,7 +1,7 @@
11{
22 "name": "pgh",
3 "version": "0.1.151",
4 "description": "pgh — P2P content-addressed package manager and app launcher",
3 "version": "0.1.152",
4 "description": "pgh \u2014 P2P content-addressed package manager and app launcher",
55 "type": "module",
66 "main": "index.js",
77 "bin": {