#!/usr/bin/env bun /** * link-polyfill.ts — Reactive local link for the @ng-eventually/polyfill polyfill. * * WHY a copy-overlay and not a symlink: * The committed prod dependency installs @ng-eventually/polyfill from Gitea (git+https) * into pnpm's store WITHOUT its own node_modules/@ng-org → @ng-org/web resolves up to * Festipod → ONE @ng-org instance (one verifier). The local polyfill CHECKOUT, however, * carries its own node_modules/@ng-org/* (symlinks into the ng-eventually-js monorepo * store). Symlinking node_modules/@ng-eventually/polyfill to that checkout puts the * checkout's @ng-org in the resolution path → a SECOND @ng-org instance → broken SDK * (two verifiers). So we overlay a real directory that contains ONLY the polyfill's * source (no node_modules) and keep it in sync by copying — @ng-org still resolves to * Festipod, single instance preserved. * * WHAT IT DOES: * 1. Replaces node_modules/@ng-eventually/polyfill (the pnpm store symlink) with a real * directory holding the local polyfill's package.json + src (NO node_modules). * 2. Asserts the single-instance invariant (same @ng-org/web realpath from Festipod and * from the overlay) — aborts if it would break. * 3. Watches the local polyfill src and copies each change into the overlay, so * `bun --hot` (bun run dev) reloads the edited file live. * * USAGE (reactive dev): * Terminal 1: pnpm run link:polyfill # overlays local source, then watches * Terminal 2: bun run dev # portless festipod bun --hot src/index.ts * Edit files under packages/polyfill/src → they land in node_modules → bun --hot reloads. * * pnpm run link:polyfill --once # overlay + verify, no watch (CI / one-shot) * Return to the committed git-installed dependency: pnpm install * * Override the local checkout path with NG_EVENTUALLY_LOCAL=/path/to/packages/polyfill. */ import { existsSync, lstatSync, mkdirSync, rmSync, cpSync, copyFileSync, realpathSync } from "node:fs"; import { watch } from "node:fs"; import { join, dirname } from "node:path"; const FESTIPOD = realpathSync(join(import.meta.dir, "..")); const LOCAL = process.env.NG_EVENTUALLY_LOCAL ?? "/home/sylvain/projects/nextgraph/ng-eventually-js/packages/polyfill"; const TARGET = join(FESTIPOD, "node_modules", "@ng-eventually", "polyfill"); const SRC_LOCAL = join(LOCAL, "src"); const SRC_TARGET = join(TARGET, "src"); const ONCE = process.argv.includes("--once"); function fail(msg: string): never { console.error(`✖ link:polyfill — ${msg}`); process.exit(1); } if (!existsSync(join(LOCAL, "package.json"))) { fail(`local polyfill not found at ${LOCAL} (set NG_EVENTUALLY_LOCAL to override)`); } // 1. Replace the pnpm store symlink with a real overlay dir (metadata + src, NO node_modules). console.log(`→ overlaying local polyfill: ${LOCAL}`); if (existsSync(TARGET) || lstatSync(TARGET, { throwIfNoEntry: false })) { rmSync(TARGET, { recursive: true, force: true }); } mkdirSync(TARGET, { recursive: true }); for (const meta of ["package.json", "tsconfig.json", "README.md"]) { const from = join(LOCAL, meta); if (existsSync(from)) copyFileSync(from, join(TARGET, meta)); } // Copy src fresh (NEVER a node_modules dir — that is what guarantees single @ng-org instance). cpSync(SRC_LOCAL, SRC_TARGET, { recursive: true }); // 2. Assert the single-instance invariant. const fromFestipod = realpathSync(Bun.resolveSync("@ng-org/web", FESTIPOD)); const overlayReal = realpathSync(TARGET); const fromPolyfill = realpathSync(Bun.resolveSync("@ng-org/web", overlayReal)); console.log(` @ng-org/web (Festipod): ${fromFestipod}`); console.log(` @ng-org/web (overlay) : ${fromPolyfill}`); if (fromFestipod !== fromPolyfill) { fail( "single-instance invariant BROKEN — @ng-org/web resolves to two different realpaths.\n" + " The overlay must not contain its own node_modules/@ng-org. Aborting.", ); } console.log("✓ single @ng-org/web instance preserved"); if (ONCE) { console.log("✓ overlay ready (--once, not watching)"); process.exit(0); } // 3. Watch and copy on change so `bun --hot` sees live edits. console.log(`👀 watching ${SRC_LOCAL} → ${SRC_TARGET} (Ctrl-C to stop)`); watch(SRC_LOCAL, { recursive: true }, (_event, filename) => { if (!filename) return; const from = join(SRC_LOCAL, filename); const to = join(SRC_TARGET, filename); try { if (existsSync(from)) { mkdirSync(dirname(to), { recursive: true }); copyFileSync(from, to); console.log(` ↻ ${filename}`); } else if (existsSync(to)) { rmSync(to, { force: true }); console.log(` ✗ ${filename} (removed)`); } } catch (err) { console.error(` ! failed to sync ${filename}:`, err); } });