cursedops 0.10.25 → 0.10.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursedops",
3
- "version": "0.10.25",
3
+ "version": "0.10.26",
4
4
  "description": "The build-and-ops answers this generation's apps wrote independently and identically: finding a generation's roots — and printing a command that runs when pasted — without knowing a path, the generation's whole-tree laws run over one repo from a checkout or a worktree, macOS launchd agent install/replace/remove and the live port a job serves, the scaffolding and verdicts of a deployed smoke (origin probe, the smoke's own environment, a network that lies about DNS, a settled version), the static-serving helpers eight apps copied — the path-traversal guard among them — the API floor that keeps an unmatched /api/... from ever being answered with the app shell, the commit and dirty flag a checkout-served process reports, the Cloudflare Worker deploy toolkit four apps copied (the deploy sequence, exact-set secrets over a pipe, origin-first rollback, the curl edge fetch, the row-for-row D1 import proof, the billed-CPU tail check around a deploy's walk and smoke, and each app's worker:secrets and worker:smoke main as one function of its data), the relay a Worker fronts a Mac-bound app with (the Durable Object, the frames, the Mac's dialer and key rotation — lifted from station for roms — and the signed-in stage walk's skeleton and the relay app's whole worker:deploy), the Worker import-graph and await-port checks every Worker app's suite runs over its own source, and the public-surface ratchet three published libraries each carried a forked copy of. Mechanism only — no app knows its name from here. Bun, zero runtime dependencies (typescript is an optional peer, for public-surface only), ships source.",
5
5
  "type": "module",
6
6
  "scripts": {
package/src/relayLink.ts CHANGED
@@ -318,7 +318,7 @@ export async function rotateLinkKey(spec: RotateSpec): Promise<{ ok: true; file:
318
318
  const put =
319
319
  spec.putSecret ??
320
320
  ((name: string, value: string) => {
321
- const r = spawnSync("bunx", ["wrangler@4", "secret", "put", name, ...(spec.env ? ["--env", spec.env] : [])], {
321
+ const r = spawnSync("bunx", ["wrangler", "secret", "put", name, ...(spec.env ? ["--env", spec.env] : [])], {
322
322
  cwd: spec.appDir,
323
323
  input: value,
324
324
  env: { ...process.env, ...spec.credential },
package/src/relayStage.ts CHANGED
@@ -54,6 +54,37 @@ export async function until(ms: number, ok: () => Promise<boolean>, clock: Clock
54
54
  return false;
55
55
  }
56
56
 
57
+ /**
58
+ * True once `isLinked` has said yes on every ask for a whole `windowMs` (default 15 s) — false if that
59
+ * never happens within `maxMs` (default 2 min). A throw counts as no, and restarts the window.
60
+ *
61
+ * 🔴 Why not a sleep and then {@link until}: a digest put is a new Worker version that reaches the
62
+ * edge in its own time, and `until` passes the first instant the link is up. Measured 2026-09-28 on
63
+ * station, beside three other deploys: the walk slept its fixed 10 s, the link came up, then flapped
64
+ * (`1002 Expected 101` — an edge still on the old digest refusing the new key) and the first
65
+ * signed-in read got the offline 503. `oldKeyRefused` already records that 15 s was not always
66
+ * enough. So the walk waits for a link that STAYS up, and how long that took is printed.
67
+ */
68
+ export async function linkedSteadily(spec: {
69
+ isLinked: () => Promise<boolean>;
70
+ windowMs?: number;
71
+ maxMs?: number;
72
+ clock?: Clock;
73
+ }): Promise<{ steady: boolean; seconds: number }> {
74
+ const clock = spec.clock ?? realClock;
75
+ const window = spec.windowMs ?? 15_000;
76
+ const started = clock.now();
77
+ let upSince: number | null = null;
78
+ while (clock.now() - started < (spec.maxMs ?? 120_000)) {
79
+ const up = await spec.isLinked().catch(() => false);
80
+ if (!up) upSince = null;
81
+ else if (upSince === null) upSince = clock.now();
82
+ else if (clock.now() - upSince >= window) return { steady: true, seconds: Math.round((clock.now() - started) / 1000) };
83
+ await clock.sleep(1_000);
84
+ }
85
+ return { steady: false, seconds: Math.round((clock.now() - started) / 1000) };
86
+ }
87
+
57
88
  /** The Access service token, as the pair a Mac end's two variables and a roster socket carry. */
58
89
  export function accessPair(headers: Record<string, string>): { id: string; secret: string } {
59
90
  return { id: headers["CF-Access-Client-Id"] ?? "", secret: headers["CF-Access-Client-Secret"] ?? "" };
@@ -142,10 +173,11 @@ export interface DigestPut {
142
173
  /**
143
174
  * Put `key`'s digest on the STAGE Worker (`--env stage`, always — this module has no production
144
175
  * path). A per-run key for the walk, and again for the rotation leg. `ok: false` carries wrangler's
145
- * stderr. A secret put is a new version: the caller gives it a few seconds to reach the edge.
176
+ * stderr. A secret put is a new version that reaches the edge in its own time: the caller waits for
177
+ * a link that stays up ({@link linkedSteadily}), never a fixed sleep.
146
178
  */
147
179
  export async function putStageLinkDigest(put: DigestPut): Promise<{ ok: boolean; stderr: string }> {
148
- const done = (put.run ?? realRun)(["bunx", "wrangler@4", "secret", "put", put.secretName, "--env", "stage"], {
180
+ const done = (put.run ?? realRun)(["bunx", "wrangler", "secret", "put", put.secretName, "--env", "stage"], {
149
181
  cwd: put.cwd,
150
182
  input: await sha256Hex(put.key),
151
183
  env: put.credential,
@@ -53,7 +53,7 @@
53
53
  * refusal without wrangler, git or a network.
54
54
  */
55
55
  import { spawn, spawnSync } from "node:child_process";
56
- import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
56
+ import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync } from "node:fs";
57
57
  import { dirname, join } from "node:path";
58
58
  import { forgeState } from "cursedops/roots";
59
59
 
@@ -308,6 +308,10 @@ export interface DeployDeps {
308
308
  error?: (line: string) => void;
309
309
  /** The clock the legs are timed on. Default `Date.now`. */
310
310
  now?: () => number;
311
+ /** Whether a path in the checkout exists — the local wrangler. Default `existsSync` against the process's cwd. */
312
+ exists?: (path: string) => boolean;
313
+ /** The unpublished library builds in `node_modules` ({@link previewMarks}). Default: read the process's cwd. */
314
+ previews?: () => PreviewMark[];
311
315
  /**
312
316
  * Where the run's {@link DeployEventLine} goes, on EVERY exit — refused, red or green.
313
317
  * {@link workerDeployDeps} appends it to the generation's ledger; absent, nothing is recorded.
@@ -548,6 +552,90 @@ export function judgeSchemaApply(code: number, stdout: string): { applied: boole
548
552
  * Run the sequence in the header's order, stopping at the first red with a sentence. Never
549
553
  * exits — the caller does, with {@link DeployResult.code}.
550
554
  */
555
+ /** Where an app's OWN wrangler is, once `bun install` has run. */
556
+ export const LOCAL_WRANGLER = "node_modules/.bin/wrangler";
557
+
558
+ /**
559
+ * Refuse a deploy whose app has no wrangler of its own — `null` when it has one.
560
+ *
561
+ * 🔴 Why (task 2260): with no local install, `bunx wrangler` resolves into ONE directory shared by
562
+ * every process on the Mac (`$TMPDIR/bunx-<uid>-wrangler@latest`), and bunx refreshes it in place.
563
+ * On 2026-09-28 it was rewritten at 12:17 while four deploys ran, and station's stage walk died on
564
+ * `Cannot find module './lib/websocket-server'` — a half-extracted `ws`. A version spec
565
+ * (`wrangler@4`) is worse: bunx then skips a local install even when there is one. So every app
566
+ * declares `wrangler` in its own `devDependencies`, locked by its own `bun.lock`, and nothing here
567
+ * passes bunx a version (`workerDeploy.test.ts` scans `src/` for one).
568
+ */
569
+ export function localWranglerRefusal(exists: (path: string) => boolean): string | null {
570
+ if (exists(LOCAL_WRANGLER)) return null;
571
+ return `this app has no wrangler of its own (${LOCAL_WRANGLER} is missing), so \`bunx wrangler\` would use the one install every deploy on this Mac shares — and concurrent deploys have corrupted it mid-run. Run \`bun add -d wrangler@^4\` in the app and commit package.json and bun.lock. Nothing was deployed.`;
572
+ }
573
+
574
+ /** The file `tools/lib-preview.ts` writes INSIDE a package it overlaid with an unpublished build. */
575
+ export const PREVIEW_MARK = ".lib-preview.json";
576
+
577
+ /** One unpublished library build sitting in `node_modules` — what its {@link PREVIEW_MARK} says. */
578
+ export interface PreviewMark {
579
+ lib: string;
580
+ /** The version in the library's own tree — never the one `bun.lock` names. */
581
+ version: string;
582
+ /** The library tree's HEAD when it was packed, `-dirty` when it had uncommitted changes. */
583
+ commit: string;
584
+ }
585
+
586
+ /**
587
+ * Every package under `nodeModules` (top level and `@scope/name`) carrying a {@link PREVIEW_MARK}.
588
+ *
589
+ * The mark lives inside the package's own directory, never beside it, so it cannot outlive the bytes
590
+ * it describes: an install that replaces the package (a lockfile bump, `--force`) removes it too.
591
+ * `bun install --frozen-lockfile` leaves an overlay whose `package.json` version matches the lock, so
592
+ * the preview writes the locked version there and its real one here (measured 2026-09-28).
593
+ */
594
+ export function previewMarks(nodeModules = "node_modules"): PreviewMark[] {
595
+ const marks: PreviewMark[] = [];
596
+ const read = (dir: string): void => {
597
+ const file = join(dir, PREVIEW_MARK);
598
+ if (!existsSync(file)) return;
599
+ try {
600
+ const mark = JSON.parse(readFileSync(file, "utf8")) as Partial<PreviewMark>;
601
+ marks.push({ lib: mark.lib ?? dir, version: mark.version ?? "?", commit: mark.commit ?? "?" });
602
+ } catch {
603
+ marks.push({ lib: dir, version: "?", commit: "unreadable mark" });
604
+ }
605
+ };
606
+ let names: string[] = [];
607
+ try {
608
+ names = readdirSync(nodeModules);
609
+ } catch {
610
+ return marks;
611
+ }
612
+ for (const name of names) {
613
+ if (name.startsWith("@")) {
614
+ let scoped: string[] = [];
615
+ try {
616
+ scoped = readdirSync(join(nodeModules, name));
617
+ } catch {}
618
+ for (const inner of scoped) read(join(nodeModules, name, inner));
619
+ } else if (!name.startsWith(".")) read(join(nodeModules, name));
620
+ }
621
+ return marks;
622
+ }
623
+
624
+ /** `<lib>@<version> (<commit>)`, joined — how a deploy names what it would ship unpublished. */
625
+ export function describePreviews(marks: readonly PreviewMark[]): string {
626
+ return marks.map((m) => `${m.lib}@${m.version} (${m.commit})`).join(", ");
627
+ }
628
+
629
+ /**
630
+ * Production never ships an unpublished library (task 2252) — `null` when nothing is previewed. A
631
+ * stage deploy ships it, says so, and stamps itself dirty: `/healthz` then says the bytes are not
632
+ * the commit's, which is true.
633
+ */
634
+ export function previewRefusal(env: WorkerEnv, marks: readonly PreviewMark[]): string | null {
635
+ if (env !== "production" || marks.length === 0) return null;
636
+ return `node_modules holds an UNPUBLISHED build of ${describePreviews(marks)} — production ships only published libraries. Publish it (\`tools/publish-libs.sh\`), bump this app to it, or put the locked version back with \`bun $FORGE/tools/lib-preview.ts --restore\`. Nothing was deployed.`;
637
+ }
638
+
551
639
  /** What {@link runWorkerDeploy} runs first: `node_modules` exactly as `bun.lock` says. */
552
640
  export const INSTALL: readonly string[] = ["bun", "install", "--frozen-lockfile"];
553
641
 
@@ -631,6 +719,13 @@ export function runWorkerDeploy(spec: WorkerDeploySpec, deps: DeployDeps): Deplo
631
719
  if (deps.run(INSTALL, {}) !== 0) {
632
720
  return stop("install", `\`${INSTALL.join(" ")}\` failed — node_modules cannot be made to match bun.lock, so nothing was built or deployed.`);
633
721
  }
722
+ const noWrangler = localWranglerRefusal(deps.exists ?? existsSync);
723
+ if (noWrangler) return stop("wrangler", noWrangler);
724
+ const previews = (deps.previews ?? previewMarks)();
725
+ const previewed = previewRefusal(spec.env, previews);
726
+ if (previewed) return stop("preview", previewed);
727
+ if (previews.length > 0) log(`\n🟡 ${tag} this stage deploy ships an UNPUBLISHED ${describePreviews(previews)} — stamped dirty; production will refuse it until it is published`);
728
+ const shipsDirty = dirty || previews.length > 0;
634
729
  if (spec.env === "production") {
635
730
  for (const command of spec.stageFirst ?? []) {
636
731
  step(`the stage first — production waits on it: ${command.join(" ")}`);
@@ -683,9 +778,9 @@ export function runWorkerDeploy(spec: WorkerDeploySpec, deps: DeployDeps): Deplo
683
778
  log(` ${verdict.line}`);
684
779
  }
685
780
  }
686
- step(`deploy ${spec.workerName} @ ${commit.slice(0, 8)}${dirty ? " (dirty — stage only)" : ""}`);
781
+ step(`deploy ${spec.workerName} @ ${commit.slice(0, 8)}${shipsDirty ? " (dirty — stage only)" : ""}`);
687
782
  const stamp = spec.stampVars
688
- ? ["--var", `${spec.stampVars.commit}:${commit}`, "--var", `${spec.stampVars.dirty}:${dirty ? "true" : "false"}`]
783
+ ? ["--var", `${spec.stampVars.commit}:${commit}`, "--var", `${spec.stampVars.dirty}:${shipsDirty ? "true" : "false"}`]
689
784
  : [];
690
785
  enter("upload");
691
786
  if (deps.run(["bunx", "wrangler", "deploy", ...envArgs, ...stamp], spec.credential) !== 0) return stop("deploy", "wrangler deploy failed.");
@@ -1144,13 +1239,13 @@ export interface WorkerCpuDeps {
1144
1239
  error: (line: string) => void;
1145
1240
  }
1146
1241
 
1147
- /** The real {@link WorkerCpuDeps}: `bunx wrangler@4 tail`, pinned to the major whose JSON shape the parser reads. */
1242
+ /** The real {@link WorkerCpuDeps}: `bunx wrangler tail` — the APP's own wrangler, whose major (4) its `devDependencies` pin: {@link localWranglerRefusal}. */
1148
1243
  export function workerCpuDeps(): WorkerCpuDeps {
1149
1244
  return {
1150
1245
  startTail: (workerName, cwd, env) => {
1151
1246
  let out = "";
1152
1247
  let err = "";
1153
- const child = spawn("bunx", ["wrangler@4", "tail", workerName, "--format", "json"], {
1248
+ const child = spawn("bunx", ["wrangler", "tail", workerName, "--format", "json"], {
1154
1249
  cwd,
1155
1250
  env: { ...process.env, ...env },
1156
1251
  stdio: ["ignore", "pipe", "pipe"],