cf-orbs 0.2.2 → 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 +8 -3
- package/dist/commands/init.js +29 -8
- package/dist/index.js +4 -1
- package/dist/wrangler.js +21 -0
- package/package.json +1 -1
- package/templates/worker/src/api.ts +1 -1
package/README.md
CHANGED
|
@@ -70,7 +70,7 @@ What `init` does:
|
|
|
70
70
|
amp -ox "hello from cf-orbs" --orb-provider <name>
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
The name after `--orb-provider` must match the provider name in Amp Settings. In the Amp app, pick the provider under **Self-Hosted** when you start a thread.
|
|
73
|
+
The name after `--orb-provider` must match the provider name in Amp Settings. The default name is `cloudflare`. A saved name from an earlier `init` wins. In the Amp app, pick the provider under **Self-Hosted** when you start a thread.
|
|
74
74
|
|
|
75
75
|
`init` is safe to run again. It keeps the control token, and it keeps the Amp token secret unless you give a new one.
|
|
76
76
|
|
|
@@ -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) |
|
|
@@ -225,6 +226,10 @@ After `cf-orbs deploy`, an orb that is awake keeps the old Worker code until it
|
|
|
225
226
|
|
|
226
227
|
`cf-orbs deploy` then starts `prewarm` throwaway instances in the background (`prewarm: started 2 instances in the background`); `cf-orbs status` shows how fast they were ready.
|
|
227
228
|
|
|
229
|
+
### Back up
|
|
230
|
+
|
|
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/).
|
|
232
|
+
|
|
228
233
|
## Troubleshooting
|
|
229
234
|
|
|
230
235
|
Run `cf-orbs doctor` first.
|
|
@@ -275,7 +280,7 @@ To report a vulnerability, see [SECURITY.md](https://github.com/meirdick/cf-orbs
|
|
|
275
280
|
|
|
276
281
|
## Status and known gaps
|
|
277
282
|
|
|
278
|
-
v0.2.
|
|
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.
|
|
279
284
|
|
|
280
285
|
Known gaps:
|
|
281
286
|
|
package/dist/commands/init.js
CHANGED
|
@@ -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). */
|
|
@@ -22,6 +22,8 @@ export const SELFTEST_WAIT_MS = 180_000;
|
|
|
22
22
|
/** Provider must stay up this long with no reject, no second-provider line and no restart. */
|
|
23
23
|
export const PROVIDER_STABLE_MS = 15_000;
|
|
24
24
|
const CRON_PERIOD_MS = 5 * 60_000;
|
|
25
|
+
/** One line at the end of a successful init, before the amp command. */
|
|
26
|
+
export const BACKUP_LINE = "back up: cf-orbs.config.jsonc and image/. Guide: https://cf-orbs-site.mdick85.workers.dev/docs/guides/back-up-and-restore/";
|
|
25
27
|
export function helloCommand(providerName) {
|
|
26
28
|
return `amp -ox "hello from cf-orbs" --orb-provider ${providerName}`;
|
|
27
29
|
}
|
|
@@ -132,6 +134,12 @@ export function plan(o, p) {
|
|
|
132
134
|
{ kind: "plan-check" },
|
|
133
135
|
{ kind: "scaffold", mode: o.mode, git: Boolean(o.noDeploy) },
|
|
134
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) });
|
|
135
143
|
steps.push({ kind: "control-token", action: p.hasControlToken ? "reuse" : "generate" });
|
|
136
144
|
if (ampSource)
|
|
137
145
|
steps.push({ kind: "amp-token", source: ampSource });
|
|
@@ -328,6 +336,19 @@ export async function runInit(ctx, o, steps) {
|
|
|
328
336
|
await writeState(paths, state);
|
|
329
337
|
break;
|
|
330
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
|
+
}
|
|
331
352
|
case "control-token":
|
|
332
353
|
controlToken = s.action === "reuse" ? await readControlToken(paths) : undefined;
|
|
333
354
|
if (!controlToken) {
|
|
@@ -340,8 +361,7 @@ export async function runInit(ctx, o, steps) {
|
|
|
340
361
|
break;
|
|
341
362
|
case "amp-token":
|
|
342
363
|
// No interactive name question here: say so if the name is a fallback, not given
|
|
343
|
-
if (s.source !== "prompt" &&
|
|
344
|
-
(o.providerNameSource === "config" || o.providerNameSource === "default"))
|
|
364
|
+
if (s.source !== "prompt" && o.providerNameSource === "default")
|
|
345
365
|
ui.warn(`provider name ${providerName} was not given explicitly; it must match the name in Amp Settings → Orbs (pass --provider-name <name> if it differs)`);
|
|
346
366
|
if (s.source === "env")
|
|
347
367
|
ampToken = checkAmpToken(ctx, (ctx.env.AMP_ORB_PROVIDER_TOKEN ?? "").trim());
|
|
@@ -580,6 +600,7 @@ export async function runInit(ctx, o, steps) {
|
|
|
580
600
|
case "done":
|
|
581
601
|
command = s.command === helloCommand(o.providerName) ? helloCommand(providerName) : s.command;
|
|
582
602
|
ui.step(`provider name ${providerName} must match the name in Amp Settings → Orbs`);
|
|
603
|
+
ui.out(BACKUP_LINE);
|
|
583
604
|
break;
|
|
584
605
|
}
|
|
585
606
|
}
|
|
@@ -745,6 +766,8 @@ export function parseVcpuFlag(v) {
|
|
|
745
766
|
throw new CliError(`--vcpu must be 1, 2, 3 or 4, got ${JSON.stringify(v)}`, "pass --vcpu 4 (default, 12 GiB) or --vcpu 2 (6 GiB, about half the cost)");
|
|
746
767
|
return Number(v);
|
|
747
768
|
}
|
|
769
|
+
/** Default Amp provider name when none is given. The Worker name default is separate: cf-orbs. */
|
|
770
|
+
export const DEFAULT_PROVIDER_NAME = "cloudflare";
|
|
748
771
|
const WORKER_NAME_RE = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
749
772
|
export function initOptions(ctx, existing) {
|
|
750
773
|
const modeFlag = flagString(ctx.args, "provider");
|
|
@@ -761,6 +784,7 @@ export function initOptions(ctx, existing) {
|
|
|
761
784
|
vcpu: parseVcpuFlag(flagString(ctx.args, "vcpu")),
|
|
762
785
|
name,
|
|
763
786
|
noDeploy: flagBool(ctx.args, "no-deploy"),
|
|
787
|
+
adopt: flagBool(ctx.args, "adopt"),
|
|
764
788
|
mode: modeFlag ?? existing?.providerHost ?? "cloudflare",
|
|
765
789
|
nonInteractive: flagBool(ctx.args, "non-interactive") || Boolean(ctx.env.CI),
|
|
766
790
|
tokenFile: flagString(ctx.args, "token-file"),
|
|
@@ -768,17 +792,14 @@ export function initOptions(ctx, existing) {
|
|
|
768
792
|
providerName: flagString(ctx.args, "provider-name") ??
|
|
769
793
|
ctx.env.AMP_ORB_PROVIDER_NAME ??
|
|
770
794
|
existing?.providerName ??
|
|
771
|
-
|
|
772
|
-
"cf-orbs",
|
|
795
|
+
DEFAULT_PROVIDER_NAME,
|
|
773
796
|
providerNameSource: flagString(ctx.args, "provider-name") !== undefined
|
|
774
797
|
? "flag"
|
|
775
798
|
: ctx.env.AMP_ORB_PROVIDER_NAME !== undefined
|
|
776
799
|
? "env"
|
|
777
800
|
: existing?.providerName !== undefined
|
|
778
801
|
? "saved"
|
|
779
|
-
:
|
|
780
|
-
? "config"
|
|
781
|
-
: "default",
|
|
802
|
+
: "default",
|
|
782
803
|
};
|
|
783
804
|
}
|
|
784
805
|
export async function initCommand(ctx) {
|
package/dist/index.js
CHANGED
|
@@ -30,12 +30,14 @@ export const COMMANDS = {
|
|
|
30
30
|
"--provider cloudflare|external where amp orb provider runs (default cloudflare)",
|
|
31
31
|
"--token-file <file> Amp provider token file (else AMP_ORB_PROVIDER_TOKEN, else prompt)",
|
|
32
32
|
"--account-id <id> Cloudflare account (else CLOUDFLARE_ACCOUNT_ID, else ask)",
|
|
33
|
-
"--provider-name <name> Amp provider name, as in Amp Settings → Orbs (
|
|
33
|
+
"--provider-name <name> Amp provider name, as in Amp Settings → Orbs (default cloudflare; AMP_ORB_PROVIDER_NAME or a saved name wins)",
|
|
34
34
|
"--subdomain <name> workers.dev subdomain to register if the account has none",
|
|
35
35
|
"--vcpu <1|2|3|4> orb size: N vCPU, N x 3 GiB, 20 GB disk (default 4; 2 is cheaper)",
|
|
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
|
@@ -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.
|
|
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}$/;
|