| … | … | @@ -25,22 +25,57 @@ steps: |
| 25 | 25 | # Must overwrite keet-cli's version since rsync already copied it. |
| 26 | 26 | run: rm -rf node_modules/keet-daemon && cp -r /storage/projects/code/hypercore-kb/hyper-fleeky/packages/keet-daemon node_modules/keet-daemon |
| 27 | 27 | - name: patch-pear-runtime |
| 28 | | # Three patches to pear-runtime: |
| 29 | | # 1. bare-sidecar has native prebuilds that break pear stage. Lazy-load it since we never call run(). |
| 30 | | # 2. applyUpdate() sets this.applied=true BEFORE fsx.swap — if swap fails (Hyperdrive not fully |
| 31 | | # replicated), the retry on next 'updated' event is a no-op. Move flag after successful swap. |
| 32 | | # 3. Version comparison: use numeric > instead of !== to prevent replication race. |
| 33 | | # On first launch, pear-runtime replicates the Hyperdrive from scratch. As entries arrive, |
| 34 | | # 'append' fires at intermediate lengths. checkout(oldLength).get('/package.json') returns |
| 35 | | # an OLD version, triggering an update with incomplete by-arch data. The swap then destroys |
| 36 | | # the install dir. Fix: only update when remote version is strictly NEWER (commit count). |
| 28 | # Four patches to pear-runtime for large binary apps (71MB Godot binary): |
| 29 | # 1. Lazy-load bare-sidecar — native prebuilds break pear stage module traversal. |
| 30 | # 2. Don't delete staging on launch — enables incremental mirrors. Without this, |
| 31 | # _open() runs rm -rf on pear-runtime/next/ every launch, forcing a full 71MB |
| 32 | # re-mirror even when only index.js changed. With staging preserved, interrupted |
| 33 | # mirrors resume and repeated _update() calls during replication reuse existing files. |
| 34 | # 3. Move this.applied AFTER fsx.swap — if swap fails, retry on next 'updated' event |
| 35 | # actually retries instead of being a no-op. |
| 36 | # 4. Validate staging completeness before signaling update ready — prevents replication |
| 37 | # race where checkout(intermediateLength) returns an old deploy state's package.json |
| 38 | # triggering a mirror of incomplete by-arch data. Replaces the old version comparison |
| 39 | # hack with a proper file-presence check. |
| 37 | 40 | run: | |
| 38 | | sed -i "s/const Sidecar = require('bare-sidecar')/let Sidecar = null/" node_modules/pear-runtime/index.js |
| 39 | | sed -i "/return new Sidecar/i\\ if (!Sidecar) Sidecar = require('bare-sidecar')" node_modules/pear-runtime/index.js |
| 41 | RT=node_modules/pear-runtime/index.js |
| 42 | |
| 43 | # 1. Lazy-load bare-sidecar (prevents staging traversal failure) |
| 44 | sed -i "s/const Sidecar = require('bare-sidecar')/let Sidecar = null/" $RT |
| 45 | sed -i "/return new Sidecar/i\\ if (!Sidecar) Sidecar = require('bare-sidecar')" $RT |
| 40 | 46 | rm -rf node_modules/bare-sidecar node_modules/pear-runtime/node_modules/bare-sidecar |
| 41 | | sed -i '/if (!this.updated || this.applied || !this.bundled) return/{n;/this.applied = true/d}' node_modules/pear-runtime/index.js |
| 42 | | sed -i '/await fsx.swap/a\ this.applied = true' node_modules/pear-runtime/index.js |
| 43 | | sed -i 's/JSON.parse(manifest).version === this.version/(() => { const rv = JSON.parse(manifest).version; const rn = parseInt((rv || "0").split(".")[1] || "0"); const ln = parseInt((String(this.version) || "0").split(".")[1] || "0"); return rn <= ln; })()/' node_modules/pear-runtime/index.js |
| 47 | |
| 48 | # 2. Don't delete staging on launch — enables incremental mirrors |
| 49 | # Lines 53-56: await fs.promises.rm(path.join(this.dir, 'pear-runtime/next'), {...}) |
| 50 | sed -i '/await.*rm.*pear-runtime\/next/,+3d' $RT |
| 51 | |
| 52 | # 3. Move this.applied AFTER fsx.swap (not before — prevents retry no-op on failure) |
| 53 | sed -i '/if (!this.updated || this.applied || !this.bundled) return/{n;/this.applied = true/d}' $RT |
| 54 | sed -i '/await fsx.swap/a\ this.applied = true' $RT |
| 55 | |
| 56 | # 4. Fixed staging path — remove per-version id so mirror-drive diffs against |
| 57 | # previous version's files instead of always starting fresh. Each new Hypercore |
| 58 | # length created a new empty staging dir, forcing full 71MB re-download. |
| 59 | sed -i "s|path.join(this.dir, 'pear-runtime/next', id)|path.join(this.dir, 'pear-runtime/next')|" $RT |
| 60 | |
| 61 | # 5. Don't delete staging after swap — fsx.swap() exchanges dirs, so after swap |
| 62 | # staging has the OLD app files. Next mirror diffs against those = incremental. |
| 63 | # Costs 71MB disk but saves minutes of P2P bandwidth per update. |
| 64 | sed -i '/fs.promises.rm(this.next/d' $RT |
| 65 | |
| 66 | # 6. Numeric version comparison — prevent downgrades during replication race. |
| 67 | # On fresh corestore, append fires at intermediate lengths. checkout(oldLength) |
| 68 | # returns an old deploy's package.json with a LOWER version. Skip unless newer. |
| 69 | sed -i 's/JSON.parse(manifest).version === this.version/(() => { const rn = parseInt((JSON.parse(manifest).version || "0").split(".")[1] || "0"); const ln = parseInt((String(this.version) || "0").split(".")[1] || "0"); return rn <= ln })()/' $RT |
| 70 | |
| 71 | # 7. Update this.version after successful mirror — prevents double-fire. |
| 72 | # Without this, _update() sees version mismatch again (this.version is from |
| 73 | # startup, manifest has new version) and mirrors a second time redundantly. |
| 74 | sed -i '/this.next = next/a\ this.version = JSON.parse(manifest).version' $RT |
| 75 | |
| 76 | # 8. Validate staging completeness before signaling update ready |
| 77 | # Belt-and-suspenders: if staging doesn't have index.js + package.json, skip. |
| 78 | sed -i '/this.updated = true/i\ const _sa = path.join(next, "by-arch", host, "app", this.name); try { fs.statSync(path.join(_sa, "index.js")); fs.statSync(path.join(_sa, "package.json")) } catch { return }' $RT |
| 44 | 79 | - name: verify-deps |
| 45 | 80 | # Prove all critical imports resolve before staging |
| 46 | 81 | run: | |