cursedops 0.12.0 → 0.12.1
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 +1 -1
- package/src/workerDeploy.ts +46 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursedops",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.1",
|
|
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/workerDeploy.ts
CHANGED
|
@@ -530,6 +530,34 @@ export function schemaApplyArgv(databaseName: string, commands: readonly string[
|
|
|
530
530
|
return commands.map((command) => ["bunx", "wrangler", "d1", "execute", databaseName, "--remote", "--command", command, "--json", "-y", ...envArgs]);
|
|
531
531
|
}
|
|
532
532
|
|
|
533
|
+
/**
|
|
534
|
+
* The reason a non-zero `wrangler … --json` gave, or `null` when it printed no error document.
|
|
535
|
+
*
|
|
536
|
+
* With `--json`, wrangler writes its failure to STDOUT as `{"error":{"text":…,"notes":[{"text":…}],
|
|
537
|
+
* "kind":"error","name":"APIError","code":7403}}` (task 2427: vault's production deploy stopped on
|
|
538
|
+
* 2026-10-01 with only "wrangler exited 1", and the ship batch rebuilt the call by hand to learn it
|
|
539
|
+
* was a transient 7403). `api` is whether it is a Cloudflare API error — worth one retry, since the
|
|
540
|
+
* schema is idempotent and the same commands applied minutes later.
|
|
541
|
+
*/
|
|
542
|
+
export function wranglerError(stdout: string): { reason: string; api: boolean } | null {
|
|
543
|
+
const start = stdout.search(/\{\s*"error"\s*:/);
|
|
544
|
+
if (start < 0) return null;
|
|
545
|
+
let parsed: unknown;
|
|
546
|
+
try {
|
|
547
|
+
parsed = JSON.parse(stdout.slice(start, stdout.lastIndexOf("}") + 1));
|
|
548
|
+
} catch {
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
const error = (parsed as { error?: unknown } | null)?.error;
|
|
552
|
+
if (!error || typeof error !== "object") return null;
|
|
553
|
+
const { text, notes, name, kind } = error as { text?: unknown; notes?: unknown; name?: unknown; kind?: unknown };
|
|
554
|
+
const parts = [typeof text === "string" ? text.trim() : ""];
|
|
555
|
+
if (Array.isArray(notes)) for (const note of notes) if (typeof (note as { text?: unknown })?.text === "string") parts.push((note as { text: string }).text.trim());
|
|
556
|
+
const reason = parts.filter(Boolean).join(" — ");
|
|
557
|
+
if (!reason) return null;
|
|
558
|
+
return { reason, api: name === "APIError" || (kind === "error" && /cloudflare api/i.test(reason)) };
|
|
559
|
+
}
|
|
560
|
+
|
|
533
561
|
/**
|
|
534
562
|
* Did one schema chunk APPLY? Its exit code alone does not say.
|
|
535
563
|
*
|
|
@@ -544,22 +572,26 @@ export function schemaApplyArgv(databaseName: string, commands: readonly string[
|
|
|
544
572
|
* one result per statement, every one `success: true`. Anything else — no JSON, an empty array,
|
|
545
573
|
* a failed result — is not applied. Pure, so its failure path is tested without wrangler.
|
|
546
574
|
*/
|
|
547
|
-
export function judgeSchemaApply(code: number, stdout: string): { applied: boolean; line: string } {
|
|
548
|
-
if (code !== 0)
|
|
575
|
+
export function judgeSchemaApply(code: number, stdout: string): { applied: boolean; line: string; retry: boolean } {
|
|
576
|
+
if (code !== 0) {
|
|
577
|
+
const failure = wranglerError(stdout);
|
|
578
|
+
if (!failure) return { applied: false, line: `wrangler exited ${code}`, retry: false };
|
|
579
|
+
return { applied: false, line: `wrangler exited ${code}: ${failure.reason}`, retry: failure.api };
|
|
580
|
+
}
|
|
549
581
|
// the first `[` that opens a JSON array, never the `[ERROR]`/`[WARNING]` of a banner — as `wranglerRows`
|
|
550
582
|
const start = stdout.search(/\[\s*[{\]]/);
|
|
551
583
|
const banner = stdout.trim().slice(0, 160).replace(/\s+/g, " ") || "(nothing)";
|
|
552
|
-
if (start < 0) return { applied: false, line: `wrangler exited 0 and printed no JSON result — it did not run the query. It printed: ${banner}` };
|
|
584
|
+
if (start < 0) return { applied: false, retry: true, line: `wrangler exited 0 and printed no JSON result — it did not run the query. It printed: ${banner}` };
|
|
553
585
|
let parsed: unknown;
|
|
554
586
|
try {
|
|
555
587
|
parsed = JSON.parse(stdout.slice(start));
|
|
556
588
|
} catch (cause) {
|
|
557
|
-
return { applied: false, line: `wrangler's JSON result did not parse (${(cause as Error).message})` };
|
|
589
|
+
return { applied: false, retry: true, line: `wrangler's JSON result did not parse (${(cause as Error).message})` };
|
|
558
590
|
}
|
|
559
|
-
if (!Array.isArray(parsed) || parsed.length === 0) return { applied: false, line: "wrangler printed an empty result — no statement ran" };
|
|
591
|
+
if (!Array.isArray(parsed) || parsed.length === 0) return { applied: false, retry: true, line: "wrangler printed an empty result — no statement ran" };
|
|
560
592
|
const failed = parsed.filter((result) => (result as { success?: unknown } | null)?.success !== true);
|
|
561
|
-
if (failed.length > 0) return { applied: false, line: `${failed.length} of ${parsed.length} result(s) did not report success` };
|
|
562
|
-
return { applied: true, line: `applied — ${parsed.length} result(s), every one success` };
|
|
593
|
+
if (failed.length > 0) return { applied: false, retry: true, line: `${failed.length} of ${parsed.length} result(s) did not report success` };
|
|
594
|
+
return { applied: true, retry: false, line: `applied — ${parsed.length} result(s), every one success` };
|
|
563
595
|
}
|
|
564
596
|
|
|
565
597
|
/**
|
|
@@ -792,16 +824,16 @@ export function runWorkerDeploy(spec: WorkerDeploySpec, deps: DeployDeps): Deplo
|
|
|
792
824
|
return stop("schema", `${schema} could not be read as statements (${(cause as Error).message}) — nothing was deployed.`);
|
|
793
825
|
}
|
|
794
826
|
for (const argv of schemaApplyArgv(spec.databaseName, commands, envArgs)) {
|
|
795
|
-
// a silent exit 0
|
|
796
|
-
// failure
|
|
797
|
-
const attempt = ():
|
|
827
|
+
// a silent exit 0 and a Cloudflare API error are intermittent and the schema is idempotent,
|
|
828
|
+
// so ONE retry; any other failure is not retried — it would fail the same way
|
|
829
|
+
const attempt = (): ReturnType<typeof judgeSchemaApply> => {
|
|
798
830
|
const { code, stdout } = deps.capture(argv, spec.credential);
|
|
799
|
-
return
|
|
831
|
+
return judgeSchemaApply(code, stdout);
|
|
800
832
|
};
|
|
801
|
-
let
|
|
802
|
-
if (!verdict.applied &&
|
|
833
|
+
let verdict = attempt();
|
|
834
|
+
if (!verdict.applied && verdict.retry) {
|
|
803
835
|
error(` 🟡 ${verdict.line} — retrying once`);
|
|
804
|
-
|
|
836
|
+
verdict = attempt();
|
|
805
837
|
}
|
|
806
838
|
if (!verdict.applied) return stop("schema", `the schema did not apply (${verdict.line}) — nothing was deployed.`);
|
|
807
839
|
log(` ${verdict.line}`);
|