Files
dohooo__helmor/cloud/scripts/team-containers.ts
Caspian Zhao f4b562dbe6 fix(team-cloud): run wrangler under node, never bun — dev default provision works again (round6 F-A)
bun 1.3.2 running wrangler 4.100's deploy / deployments list swallows the
output (banner only, exit 0) → provision's Worker-URL parsing dies on the
dev default path (repo .ts under PATH bun); d1 list --json fails the same
way intermittently. The scripts now self-decide wrangler's runtime
(wrangler-runtime.ts, pure + unit-tested): HELMOR_WRANGLER_BIN override
unchanged → under bun prefer the HELMOR_WRANGLER_NODE hint (vendored node,
injected by the Rust spawn points when it exists) → a PATH node → loud
warning + bun as the last resort. Under node (release .mjs) nothing
changes. The .ts orchestration layer stays on bun — the vendored node
can't execute .ts.
2026-07-10 18:08:34 -07:00

81 lines
3.0 KiB
TypeScript

#!/usr/bin/env bun
//
// Dev-tools helper: list / delete the operator's Cloudflare Containers via
// wrangler. Backs the Developer settings "Team cloud" group (list / delete the
// remote sandboxes you stood up). Thin on purpose — the Rust command just runs
// this and hands the output to the UI.
//
// list -> prints `wrangler containers list --json` to stdout
// delete <id> -> deletes the container, prints {"ok":true}
//
// Human-readable wrangler chatter goes to stderr; only the machine payload is
// on stdout. wrangler is self-resolved from the node_modules next to this
// script (repo cloud/ in dev, staged vendor/team-cloud in a release bundle) and
// run under a node runtime — never bun, which swallows wrangler's output
// (round6 F-A) — same contract as provision-team.ts; decision rules live in
// wrangler-runtime.ts (HELMOR_WRANGLER_BIN stays as a manual override).
import { spawnSync } from "node:child_process";
import { existsSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { decideWranglerCommand, findNodeOnPath } from "./wrangler-runtime";
const cloudRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const WRANGLER_JS = resolve(cloudRoot, "node_modules/wrangler/bin/wrangler.js");
const nodeHint = process.env.HELMOR_WRANGLER_NODE || "";
const wranglerCmd = decideWranglerCommand({
overrideBin: process.env.HELMOR_WRANGLER_BIN || "",
wranglerJs: WRANGLER_JS,
execPath: process.execPath,
isBun: Boolean(process.versions.bun),
nodeHint: nodeHint && existsSync(nodeHint) ? nodeHint : "",
pathNode: findNodeOnPath(
process.env.PATH,
process.platform === "win32",
existsSync,
),
});
if (wranglerCmd.warning) {
process.stderr.write(`⚠️ ${wranglerCmd.warning}\n`);
}
function wrangler(args: string[], input?: string) {
// cwd: never cloudRoot — it's read-only inside a release bundle.
return spawnSync(wranglerCmd.argv0, [...wranglerCmd.prefixArgs, ...args], {
cwd: tmpdir(),
input,
encoding: "utf8",
env: { ...process.env, WRANGLER_SEND_METRICS: "false" },
});
}
const [action, id] = process.argv.slice(2);
if (action === "list") {
const r = wrangler(["containers", "list", "--json"]);
if (r.status !== 0) {
process.stderr.write(r.stderr ?? "");
process.exit(1);
}
process.stdout.write((r.stdout ?? "[]").trim() || "[]");
} else if (action === "delete") {
if (!id) {
process.stderr.write("delete requires an <id>\n");
process.exit(1);
}
// `containers delete` has no documented --yes flag; with no TTY wrangler
// runs non-interactively, but pipe a confirmation as belt-and-suspenders in
// case a future version prompts on stdin.
const r = wrangler(["containers", "delete", id], "y\n");
if (r.status !== 0) {
process.stderr.write(r.stderr ?? "");
process.exit(1);
}
process.stdout.write(JSON.stringify({ ok: true }));
} else {
process.stderr.write(`unknown action: ${action ?? "(none)"}\n`);
process.exit(1);
}