cf-orbs 0.2.3 → 0.2.4

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/README.md CHANGED
@@ -120,6 +120,7 @@ Before the deploy, `init` stops with one `Failed: … — fix: …` line when:
120
120
  - An extra image key has no `image/<key>.Dockerfile`.
121
121
  - The account has no workers.dev subdomain and you gave no `--subdomain` (in a terminal, `init` asks for one).
122
122
  - `wrangler containers list` fails with a clear plan or login error (Workers Paid is needed). Any other error is a warning.
123
+ - The folder has no saved deployment and a Worker of the same name already exists in the account. `init` stops before it makes a control token, sets a secret or deploys, so it cannot overwrite a Worker that another folder made. Pass `--adopt` to take it over: a new control token replaces the old one, and this folder's config and `image/` are deployed. A rerun in the folder that made the Worker is not checked. If the check cannot run, it is a warning.
123
124
 
124
125
  After the deploy, `init` checks that `/v1/health` answers, that `/v1/status` accepts the control token, that at least one prewarm instance starts from the new image (self-test; skipped when `prewarm` is 0, and with `--no-deploy` until a build has run), and that the provider runs for 15 s with no token rejection and no second provider on the token.
125
126
 
@@ -184,7 +185,7 @@ Example: 120 awake hours a month at 20% CPU costs about $27.50 a month in total
184
185
 
185
186
  | Command | Does |
186
187
  | --- | --- |
187
- | `cf-orbs init [dir]` | Scaffold, deploy and check a provider. Flags: `--provider cloudflare\|external`, `--token-file`, `--account-id`, `--provider-name`, `--subdomain`, `--vcpu <1\|2\|3\|4>`, `--name <worker>`, `--no-deploy` (preview: [Workers Builds](https://github.com/meirdick/cf-orbs/blob/main/docs/install-workers-builds.md), no Docker) |
188
+ | `cf-orbs init [dir]` | Scaffold, deploy and check a provider. Flags: `--provider cloudflare\|external`, `--token-file`, `--account-id`, `--provider-name`, `--subdomain`, `--vcpu <1\|2\|3\|4>`, `--name <worker>`, `--no-deploy` (preview: [Workers Builds](https://github.com/meirdick/cf-orbs/blob/main/docs/install-workers-builds.md), no Docker), `--adopt` (take over an existing Worker of the same name; see Back up) |
188
189
  | `cf-orbs status [orb]` | Install status (provider, awake orbs, snapshots), or one orb |
189
190
  | `cf-orbs logs <orb>` | Redacted bootstrap log of one orb. `--lines N` (default 200) |
190
191
  | `cf-orbs logs --provider` | Redacted provider log (last 20 lines) |
@@ -227,7 +228,7 @@ After `cf-orbs deploy`, an orb that is awake keeps the old Worker code until it
227
228
 
228
229
  ### Back up
229
230
 
230
- Running orbs need nothing on your machine: the Worker, the orbs and the snapshots are in Cloudflare. Back up `cf-orbs.config.jsonc` and `image/` (a private git repo is enough; the `.gitignore` that `init` writes keeps the control token out). Keep the Amp provider token in a password manager if you want to restore without making a new one. The control token can be made again. See [Back up and restore](https://cf-orbs-site.mdick85.workers.dev/docs/guides/back-up-and-restore/).
231
+ Running orbs need nothing on your machine: the Worker, the orbs and the snapshots are in Cloudflare. Back up `cf-orbs.config.jsonc` and `image/` (a private git repo is enough; the `.gitignore` that `init` writes keeps the control token out). Keep the Amp provider token in a password manager if you want to restore without making a new one. The control token can be made again. To restore in a new folder, run `init --adopt`: without it, `init` stops because the Worker already exists. See [Back up and restore](https://cf-orbs-site.mdick85.workers.dev/docs/guides/back-up-and-restore/).
231
232
 
232
233
  ## Troubleshooting
233
234
 
@@ -279,7 +280,7 @@ To report a vulnerability, see [SECURITY.md](https://github.com/meirdick/cf-orbs
279
280
 
280
281
  ## Status and known gaps
281
282
 
282
- v0.2.3. The control plane, dispatch program, provider host and CLI are built and unit tested. On 2026-10-30 a real Amp thread ran end to end on a fresh install: spawn from the image, setup, pause with a project snapshot, wake from the snapshot in 2.3 s, first answer 41 s after the message.
283
+ v0.2.4. The control plane, dispatch program, provider host and CLI are built and unit tested. On 2026-10-30 a real Amp thread ran end to end on a fresh install: spawn from the image, setup, pause with a project snapshot, wake from the snapshot in 2.3 s, first answer 41 s after the message.
283
284
 
284
285
  Known gaps:
285
286
 
@@ -14,7 +14,7 @@ import { dockerOk, nodeMajor, whoami } from "../probes.js";
14
14
  import { cliVersion, GIT_DEPLOY_COMMAND, generateToken, gitPackageJson, materialize, missingDockerfiles, readControlToken, readState, withSecretsFile, writeControlToken, writeState, writeWranglerConfig, } from "../project.js";
15
15
  import { checkSubdomain, registerSubdomain, suggestSubdomain, wranglerBearer } from "../subdomain.js";
16
16
  import { ask, askHidden } from "../ui.js";
17
- import { classifyDeployFailure, deployFailureError, lastErrorLine, makeWrangler, WRANGLER_PIN, } from "../wrangler.js";
17
+ import { classifyDeployFailure, deployFailureError, lastErrorLine, lookupWorker, makeWrangler, WRANGLER_PIN, } from "../wrangler.js";
18
18
  import { deployProject, startPrewarm } from "./deploy.js";
19
19
  export const PROVIDER_WAIT_MS = 90_000;
20
20
  /** How long init waits for the prewarm instances (a first start on a cold host pulls the image). */
@@ -134,6 +134,12 @@ export function plan(o, p) {
134
134
  { kind: "plan-check" },
135
135
  { kind: "scaffold", mode: o.mode, git: Boolean(o.noDeploy) },
136
136
  ];
137
+ // A folder with no saved deployment and no control token must not overwrite a Worker it did not create.
138
+ // A folder with a control token made the Worker before (a run that failed before it saved the URL) or holds a
139
+ // restored backup: it keeps the same token, so nothing is lost. Runs after scaffold (the Worker name is in the
140
+ // config then) and before the control token, the secrets and the deploy.
141
+ if (!p.deployed && !p.hasControlToken)
142
+ steps.push({ kind: "adopt-check", adopt: Boolean(o.adopt) });
137
143
  steps.push({ kind: "control-token", action: p.hasControlToken ? "reuse" : "generate" });
138
144
  if (ampSource)
139
145
  steps.push({ kind: "amp-token", source: ampSource });
@@ -330,6 +336,19 @@ export async function runInit(ctx, o, steps) {
330
336
  await writeState(paths, state);
331
337
  break;
332
338
  }
339
+ case "adopt-check": {
340
+ if (!config)
341
+ throw new Error("init plan bug: adopt-check before scaffold");
342
+ const found = await lookupWorker(makeWrangler(ctx.run, { accountId }), config.name);
343
+ if (found.kind === "found") {
344
+ if (!s.adopt)
345
+ throw new CliError(`Worker ${config.name} already exists in this account and this folder did not create it`, "run init in the folder that made it, restore that folder (see Back up and restore), pass --name <new> for a second install, or pass --adopt to take it over");
346
+ ui.warn(`adopt: taking over Worker ${config.name}; a new control token replaces the old one (the folder that made it loses CLI access); this folder's config and image/ are deployed`);
347
+ }
348
+ else if (found.kind === "unknown")
349
+ ui.warn(`existing-Worker check could not run (${found.reason}); init goes on`);
350
+ break;
351
+ }
333
352
  case "control-token":
334
353
  controlToken = s.action === "reuse" ? await readControlToken(paths) : undefined;
335
354
  if (!controlToken) {
@@ -765,6 +784,7 @@ export function initOptions(ctx, existing) {
765
784
  vcpu: parseVcpuFlag(flagString(ctx.args, "vcpu")),
766
785
  name,
767
786
  noDeploy: flagBool(ctx.args, "no-deploy"),
787
+ adopt: flagBool(ctx.args, "adopt"),
768
788
  mode: modeFlag ?? existing?.providerHost ?? "cloudflare",
769
789
  nonInteractive: flagBool(ctx.args, "non-interactive") || Boolean(ctx.env.CI),
770
790
  tokenFile: flagString(ctx.args, "token-file"),
package/dist/index.js CHANGED
@@ -36,6 +36,8 @@ export const COMMANDS = {
36
36
  "--name <name> Worker name (default cf-orbs; a second install needs another)",
37
37
  "--no-deploy preview: no Docker, no deploy. Make a git repo for Workers Builds,",
38
38
  " create the Worker and its secrets, print the dashboard steps",
39
+ "--adopt take over a Worker of the same name that this folder did not create;",
40
+ " init stops without it. A new control token replaces the old one",
39
41
  ],
40
42
  flags: {
41
43
  provider: { type: "string" },
@@ -46,6 +48,7 @@ export const COMMANDS = {
46
48
  vcpu: { type: "string" },
47
49
  name: { type: "string" },
48
50
  "no-deploy": { type: "boolean" },
51
+ adopt: { type: "boolean" },
49
52
  },
50
53
  dirPositional: true,
51
54
  run: initCommand,
package/dist/wrangler.js CHANGED
@@ -135,6 +135,27 @@ export function deployFailureError(kind, output) {
135
135
  return new CliError(`wrangler deploy failed: ${lastErrorLine(output)}`, "fix the error above, then rerun cf-orbs deploy");
136
136
  }
137
137
  }
138
+ /**
139
+ * Does a Worker with this name exist in the account? Runs `wrangler deployments list --json --name <name>`.
140
+ * Verified with wrangler 4.156.0: an existing Worker exits 0 and prints a JSON array. A missing one exits 1,
141
+ * prints nothing on stdout and prints `This Worker does not exist on your account. [code: 10007]` on stderr.
142
+ * Any other result (auth, network, bad account id, odd output) is "unknown".
143
+ */
144
+ export async function lookupWorker(w, name) {
145
+ const r = await w.run(["deployments", "list", "--json", "--name", name], { timeoutMs: 60_000 });
146
+ const out = `${r.stdout}\n${r.stderr}`;
147
+ if (r.code === 0) {
148
+ try {
149
+ if (Array.isArray(JSON.parse(r.stdout.slice(Math.max(0, r.stdout.indexOf("["))))))
150
+ return { kind: "found" };
151
+ }
152
+ catch { }
153
+ return { kind: "unknown", reason: "unreadable wrangler output" };
154
+ }
155
+ if (/\[code: 10007\]|This Worker does not exist/.test(out))
156
+ return { kind: "missing" };
157
+ return { kind: "unknown", reason: lastErrorLine(out) };
158
+ }
138
159
  /** Last meaningful line of wrangler output, preferring the "✘ [ERROR]" line. */
139
160
  export function lastErrorLine(output) {
140
161
  const clean = output.replace(new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g"), "");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cf-orbs",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Run Amp orbs on Cloudflare Containers: self-hosted orb provider, one command to install.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -18,7 +18,7 @@ import { type Env, orbDoName, PROVIDER_DO_NAME, REGISTRY_DO_NAME } from "./env";
18
18
  import { BENCH_ORB_RE, type BenchStartRequest, parseBenchStart } from "./orb/bench";
19
19
  import { restartProvider } from "./provider-restart";
20
20
 
21
- export const API_VERSION = "0.2.3";
21
+ export const API_VERSION = "0.2.4";
22
22
 
23
23
  /** D2. Amp sends `amp-<thread id>`. Never starts with `_` or `.`, so it cannot look like an internal name. */
24
24
  export const ORB_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;