@iamken/cloudtunnel 0.3.0 → 0.5.0
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 +82 -77
- package/dist/{chunk-2TCFCMJS.js → chunk-CWJA4L7J.js} +2 -2
- package/dist/{chunk-YLTQB4F7.js → chunk-NZKGIDIW.js} +2 -2
- package/dist/{chunk-UPBVRXLF.js → chunk-RWR6VXNB.js} +7 -1
- package/dist/{chunk-UPBVRXLF.js.map → chunk-RWR6VXNB.js.map} +1 -1
- package/dist/{dns-PAPFSYFP.js → dns-5OXFAQ4D.js} +3 -3
- package/dist/index.js +681 -551
- package/dist/index.js.map +1 -1
- package/dist/zones-QYV3DSEY.js +11 -0
- package/package.json +1 -1
- package/dist/zones-YNGQYXAF.js +0 -11
- /package/dist/{chunk-2TCFCMJS.js.map → chunk-CWJA4L7J.js.map} +0 -0
- /package/dist/{chunk-YLTQB4F7.js.map → chunk-NZKGIDIW.js.map} +0 -0
- /package/dist/{dns-PAPFSYFP.js.map → dns-5OXFAQ4D.js.map} +0 -0
- /package/dist/{zones-YNGQYXAF.js.map → zones-QYV3DSEY.js.map} +0 -0
package/README.md
CHANGED
|
@@ -16,21 +16,21 @@ Instant, self-owned tunnel sharing — the tunnel and DNS live in **your** Cloud
|
|
|
16
16
|
```bash
|
|
17
17
|
npm i -g @iamken/cloudtunnel
|
|
18
18
|
|
|
19
|
-
cloudtunnel login
|
|
20
|
-
cloudtunnel
|
|
19
|
+
cloudtunnel login # once — paste a Cloudflare token; account + domain auto-resolved
|
|
20
|
+
cloudtunnel 8080 # → https://brave-otter-1a2b.example.com is live ✨
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
> 💨 Prefer less typing? **`ctun`** is a built-in short alias — `ctun
|
|
23
|
+
> 💨 Prefer less typing? **`ctun`** is a built-in short alias — `ctun 8080`, `ctun ls`, `ctun delete 1`.
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
27
27
|
## ✨ Why cloudtunnel
|
|
28
28
|
|
|
29
29
|
- 🔗 **Your domains, real subdomains** — routes through native Cloudflare Tunnel to `*.your-domain.com`, not a shared third-party host.
|
|
30
|
-
- ⚡ **One command** — `cloudtunnel
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
- 🌙 **Background
|
|
30
|
+
- ⚡ **One command, one or many** — `cloudtunnel api:8080 web:5173` brings up several tunnels at once, each a live HTTPS URL.
|
|
31
|
+
- 🎯 **Spec-driven** — a tunnel is just `[subdomain:]port[@host]`. No profiles to define, nothing to save.
|
|
32
|
+
- 🧭 **Two states** — `up` brings subdomains online; `delete` (or Ctrl-C) releases them (removes the tunnel + DNS). Re-running `up` always starts clean.
|
|
33
|
+
- 🌙 **Background & boot** — `--detach` keeps connectors running after you close the terminal; `--service` registers a native OS service (systemd · launchd · Task Scheduler) that survives restarts.
|
|
34
34
|
- 🔒 **Secure by default** — token passed via env (never argv), stored `0600`, destructive ops are ownership-gated and re-verified.
|
|
35
35
|
|
|
36
36
|
---
|
|
@@ -38,32 +38,25 @@ cloudtunnel 3000 # → https://brave-otter-1a2b.example.com is live ✨
|
|
|
38
38
|
## 🚀 Quickstart
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
cloudtunnel login
|
|
42
|
-
cloudtunnel
|
|
43
|
-
cloudtunnel
|
|
44
|
-
cloudtunnel
|
|
45
|
-
cloudtunnel
|
|
46
|
-
cloudtunnel
|
|
47
|
-
cloudtunnel
|
|
41
|
+
cloudtunnel login # authenticate once
|
|
42
|
+
cloudtunnel 8080 # asks for a subdomain (+ domain), then goes live
|
|
43
|
+
cloudtunnel api:8080 # api.<your-domain> → localhost:8080
|
|
44
|
+
cloudtunnel api:8080@192.168.1.20 # forward to another host/IP (IPv4/IPv6)
|
|
45
|
+
cloudtunnel api:8080 web:5173 -d foo.io # several tunnels at once, under foo.io
|
|
46
|
+
cloudtunnel api:8080 --detach # run in the background
|
|
47
|
+
cloudtunnel api:8080 --service # register a boot service (Linux/macOS/Windows)
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
A **spec** is `[subdomain:]port[@host]`:
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
| Spec | Means |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| `8080` | random subdomain → `localhost:8080` |
|
|
55
|
+
| `api:8080` | `api.<domain>` → `localhost:8080` |
|
|
56
|
+
| `api:8080@192.168.1.20` | `api.<domain>` → `192.168.1.20:8080` (a LAN device, container, another server) |
|
|
57
|
+
| `@:8080` | the root/apex domain itself |
|
|
53
58
|
|
|
54
|
-
|
|
55
|
-
┌ cloudtunnel
|
|
56
|
-
◇ Choose a domain
|
|
57
|
-
│ ● example.com ○ foo.io
|
|
58
|
-
◇ Subdomain
|
|
59
|
-
│ api (leave blank for a random name)
|
|
60
|
-
◇ Connected
|
|
61
|
-
│
|
|
62
|
-
◇ Live ─────────────────────────────────────────╮
|
|
63
|
-
│ https://api.example.com → http://localhost:3000
|
|
64
|
-
│ Ctrl-C stops and releases this subdomain
|
|
65
|
-
╰─────────────────────────────────────────────────╯
|
|
66
|
-
```
|
|
59
|
+
Run `cloudtunnel` with no arguments and it guides you (port → subdomain → domain). A missing subdomain becomes a friendly random name; a missing host is `localhost`. The local-service protocol is `--proto http|https` (default `http`); replacing an existing DNS record asks first — pass `-y` to skip, `-f` to also replace a non-tunnel record.
|
|
67
60
|
|
|
68
61
|
---
|
|
69
62
|
|
|
@@ -72,43 +65,24 @@ Run `cloudtunnel 3000` with no flags and it guides you:
|
|
|
72
65
|
| Command | What it does |
|
|
73
66
|
| --- | --- |
|
|
74
67
|
| `cloudtunnel login` | Authenticate; resolve account + list your domains. `--status` to inspect. |
|
|
75
|
-
| `cloudtunnel <
|
|
76
|
-
| `cloudtunnel ls` · `ps` | List
|
|
77
|
-
| `cloudtunnel
|
|
68
|
+
| `cloudtunnel <spec…>` · `up` | Bring one or more tunnels online. `-d/--domain`, `--proto`, `--protocol`, `--detach`, `--service`, `-f/--force`, `-y/--yes`. |
|
|
69
|
+
| `cloudtunnel ls` · `ps` | List tunnels — `# · URL · TARGET · STATE · SERVICE · PID`. `--all` scans the whole account. |
|
|
70
|
+
| `cloudtunnel delete <target…>` | Release tunnel(s) — stop connector + delete tunnel + DNS + any boot service. `--all`, `--dry-run`, `-f`. |
|
|
78
71
|
| `cloudtunnel logs <target>` | Show a connector's log. `-f` to follow, `-n` for line count. |
|
|
79
|
-
| `cloudtunnel zones` | List the domains in your account. |
|
|
80
|
-
| `cloudtunnel save <profile> <svc…>` | Save a group of services. `svc` = `name:port[:proto][@host]`, `-d/--domain`, `--protocol`, or `--from-running`. |
|
|
81
|
-
| `cloudtunnel run <profile> [--detach]` | Bring up every service in a profile at once. `--protocol` overrides the saved transport. |
|
|
82
|
-
| `cloudtunnel profiles [--rm <name>]` | List saved profiles — `PROFILE · SERVICES · DOMAIN · PROTOCOL · SERVICE`. |
|
|
83
|
-
| `cloudtunnel service enable\|disable\|status <profile>` | Register a profile as a systemd boot service (Linux, needs sudo). |
|
|
84
72
|
|
|
85
|
-
> A **`<target>`** is a `#` number, a subdomain name, a full hostname, or a tunnel-id prefix — all shown in `ls`.
|
|
73
|
+
> A **`<target>`** is a `#` number, a subdomain name, a full hostname/URL, or a tunnel-id prefix — all shown in `ls`.
|
|
86
74
|
|
|
87
75
|
```
|
|
88
76
|
$ cloudtunnel ls
|
|
89
|
-
|
|
90
|
-
│ # │
|
|
91
|
-
|
|
92
|
-
│ 1 │ api.example.com
|
|
93
|
-
│ 2 │ web.example.com
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
$ cloudtunnel
|
|
97
|
-
$ cloudtunnel
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
## 🗂️ Profiles
|
|
103
|
-
|
|
104
|
-
Expose a whole project's services with one command:
|
|
105
|
-
|
|
106
|
-
```bash
|
|
107
|
-
cloudtunnel save mb api:3000 web:5173:https # define the group (or: save mb --from-running)
|
|
108
|
-
cloudtunnel save mb api:3000@192.168.1.5 # forward a service to another host/IP
|
|
109
|
-
cloudtunnel run mb --detach # backend + frontend live in the background
|
|
110
|
-
cloudtunnel logs api -f # follow one service's log
|
|
111
|
-
cloudtunnel down --all # release them all
|
|
77
|
+
┌───┬────────────────────────────┬────────────────────────┬───────┬─────────┬───────┐
|
|
78
|
+
│ # │ URL │ TARGET │ STATE │ SERVICE │ PID │
|
|
79
|
+
├───┼────────────────────────────┼────────────────────────┼───────┼─────────┼───────┤
|
|
80
|
+
│ 1 │ https://api.example.com │ http://localhost:8080 │ up │ active │ 48213 │
|
|
81
|
+
│ 2 │ https://web.example.com │ https://localhost:5173 │ down │ - │ - │
|
|
82
|
+
└───┴────────────────────────────┴────────────────────────┴───────┴─────────┴───────┘
|
|
83
|
+
|
|
84
|
+
$ cloudtunnel delete 1 # release by number
|
|
85
|
+
$ cloudtunnel delete --all # release everything
|
|
112
86
|
```
|
|
113
87
|
|
|
114
88
|
---
|
|
@@ -121,34 +95,47 @@ errors. Force **`http2`** (TCP) there:
|
|
|
121
95
|
|
|
122
96
|
```bash
|
|
123
97
|
cloudtunnel up 8080 --protocol http2
|
|
124
|
-
cloudtunnel save mb api:3000 web:5173 --protocol http2 # persist per profile
|
|
125
|
-
cloudtunnel run mb --protocol http2 # or override per run
|
|
126
98
|
```
|
|
127
99
|
|
|
128
100
|
Values: `auto` (default) · `http2` · `quic`.
|
|
129
101
|
|
|
130
102
|
---
|
|
131
103
|
|
|
132
|
-
## 🔁 Run on boot (
|
|
104
|
+
## 🔁 Run on boot (`--service`)
|
|
105
|
+
|
|
106
|
+
Add **`--service`** to register each subdomain as a native OS service so it comes
|
|
107
|
+
back automatically — cross-platform:
|
|
133
108
|
|
|
134
|
-
|
|
135
|
-
|
|
109
|
+
| OS | Backend | Autostart | Privilege |
|
|
110
|
+
| --- | --- | --- | --- |
|
|
111
|
+
| Linux | systemd unit (`/etc/systemd/system`) | boot | sudo |
|
|
112
|
+
| macOS | launchd LaunchAgent (`~/Library/LaunchAgents`) | login | none |
|
|
113
|
+
| Windows | Task Scheduler (`cloudtunnel\<name>`) | logon | none |
|
|
136
114
|
|
|
137
115
|
```bash
|
|
138
|
-
cloudtunnel service
|
|
139
|
-
cloudtunnel
|
|
140
|
-
cloudtunnel
|
|
116
|
+
cloudtunnel api:8080 --service --protocol http2 # install + enable + start now
|
|
117
|
+
cloudtunnel api:8080 web:5173 --service # one service per subdomain
|
|
118
|
+
cloudtunnel ls # the SERVICE column shows each one's state
|
|
119
|
+
cloudtunnel delete api # stops + removes the tunnel and its service
|
|
141
120
|
```
|
|
142
121
|
|
|
143
|
-
|
|
122
|
+
Each subdomain gets its own service, so deleting one never touches the others.
|
|
123
|
+
The concrete subdomain is baked in, so the URL stays stable across restarts. All
|
|
124
|
+
backends restart the connector on failure.
|
|
144
125
|
|
|
145
126
|
---
|
|
146
127
|
|
|
147
|
-
## 🧭 Two states: up &
|
|
128
|
+
## 🧭 Two states: up & delete
|
|
148
129
|
|
|
149
|
-
There are only two states. **`up`** brings
|
|
130
|
+
There are only two states. **`up`** brings subdomains online (creating the tunnel
|
|
131
|
+
+ DNS). **`delete`** — or pressing <kbd>Ctrl-C</kbd> in a foreground `up` —
|
|
132
|
+
**releases** them: it stops the connector and deletes the tunnel + DNS on
|
|
133
|
+
Cloudflare (and any `--service` unit). Running `up` again recreates cleanly (any
|
|
134
|
+
leftover tunnel record for that name is cleaned up first, so you never hit conflicts).
|
|
150
135
|
|
|
151
|
-
Add **`--detach`** to keep
|
|
136
|
+
Add **`--detach`** to keep connectors running in the **background** after the CLI
|
|
137
|
+
exits. Release them later with `cloudtunnel delete <target>` (or `--all`) and tail
|
|
138
|
+
their output with `cloudtunnel logs <target> -f`.
|
|
152
139
|
|
|
153
140
|
---
|
|
154
141
|
|
|
@@ -173,7 +160,25 @@ Provide it via (highest precedence first): `CLOUDFLARE_API_TOKEN` env → `cloud
|
|
|
173
160
|
|
|
174
161
|
- **Node.js ≥ 20**
|
|
175
162
|
- **`cloudflared`** on your `PATH` — install via `brew install cloudflared`, your package manager, or the [releases page](https://github.com/cloudflare/cloudflared/releases).
|
|
176
|
-
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## 🧳 Upgrading from 0.3.x (profiles removed)
|
|
167
|
+
|
|
168
|
+
`save`, `run`, `profiles`, `zones`, and the `service …` sub-commands are gone —
|
|
169
|
+
everything is now inline specs on `up`, and reads live in `ls`:
|
|
170
|
+
|
|
171
|
+
| Old | New |
|
|
172
|
+
| --- | --- |
|
|
173
|
+
| `run mb` | `cloudtunnel api:8080 web:5173` (pass the specs directly) |
|
|
174
|
+
| `service enable mb --protocol http2` | `cloudtunnel <spec…> --service --protocol http2` |
|
|
175
|
+
| `service disable mb` / `profiles --rm mb` | `cloudtunnel delete <target>` |
|
|
176
|
+
| `service status` / `profiles` | `cloudtunnel ls` (SERVICE column) |
|
|
177
|
+
| `zones` | `cloudtunnel login --status` (also a ZONE in each `ls` URL) |
|
|
178
|
+
| `down <t>` / `rm <t>` | `cloudtunnel delete <t>` |
|
|
179
|
+
|
|
180
|
+
Profiles that were registered as boot services are **migrated automatically** to
|
|
181
|
+
the new per-subdomain units the first time you run cloudtunnel in a terminal.
|
|
177
182
|
|
|
178
183
|
---
|
|
179
184
|
|
|
@@ -181,10 +186,10 @@ Provide it via (highest precedence first): `CLOUDFLARE_API_TOKEN` env → `cloud
|
|
|
181
186
|
|
|
182
187
|
| Symptom | Cause & fix |
|
|
183
188
|
| --- | --- |
|
|
184
|
-
| **HTTP 1016** / subdomain won't load | The connector isn't running (`STATE = down` in `ls`). Bring it back up: `cloudtunnel <
|
|
185
|
-
| **`
|
|
189
|
+
| **HTTP 1016** / subdomain won't load | The connector isn't running (`STATE = down` in `ls`). Bring it back up: `cloudtunnel <spec>`. |
|
|
190
|
+
| **`delete` says "active connections"** | Handled automatically — cloudtunnel cleans up the connections and retries the delete. |
|
|
186
191
|
| **"grey-clouded" error** | The zone couldn't proxy the record; cfargotunnel routing needs an orange-cloud (proxied) CNAME. |
|
|
187
|
-
| **A DNS record already occupies the name** | Pick another
|
|
192
|
+
| **A DNS record already occupies the name** | Pick another subdomain/domain, or pass `-f/--force` to replace a non-tunnel record. |
|
|
188
193
|
|
|
189
194
|
---
|
|
190
195
|
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
CliError,
|
|
4
4
|
cfPaginate,
|
|
5
5
|
cfRequest
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-RWR6VXNB.js";
|
|
7
7
|
|
|
8
8
|
// src/cloudflare/zones.ts
|
|
9
9
|
function listZones(token) {
|
|
@@ -24,4 +24,4 @@ export {
|
|
|
24
24
|
listZones,
|
|
25
25
|
resolveZone
|
|
26
26
|
};
|
|
27
|
-
//# sourceMappingURL=chunk-
|
|
27
|
+
//# sourceMappingURL=chunk-CWJA4L7J.js.map
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
CliError,
|
|
4
4
|
cfPaginate,
|
|
5
5
|
cfRequest
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-RWR6VXNB.js";
|
|
7
7
|
|
|
8
8
|
// src/cloudflare/dns.ts
|
|
9
9
|
var MANAGED_DNS_COMMENT = "managed-by:cloudtunnel";
|
|
@@ -54,4 +54,4 @@ export {
|
|
|
54
54
|
listCargoCnames,
|
|
55
55
|
deleteDnsRecord
|
|
56
56
|
};
|
|
57
|
-
//# sourceMappingURL=chunk-
|
|
57
|
+
//# sourceMappingURL=chunk-NZKGIDIW.js.map
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
|
+
};
|
|
2
7
|
|
|
3
8
|
// src/ui/errors.ts
|
|
4
9
|
import pc from "picocolors";
|
|
@@ -128,6 +133,7 @@ async function cfPaginate(token, basePath) {
|
|
|
128
133
|
}
|
|
129
134
|
|
|
130
135
|
export {
|
|
136
|
+
__export,
|
|
131
137
|
CliError,
|
|
132
138
|
reportError,
|
|
133
139
|
configFile,
|
|
@@ -143,4 +149,4 @@ export {
|
|
|
143
149
|
cfRequest,
|
|
144
150
|
cfPaginate
|
|
145
151
|
};
|
|
146
|
-
//# sourceMappingURL=chunk-
|
|
152
|
+
//# sourceMappingURL=chunk-RWR6VXNB.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ui/errors.ts","../src/config/store.ts","../src/config/paths.ts","../src/cloudflare/client.ts"],"sourcesContent":["import pc from \"picocolors\";\n\n/**\n * A user-facing CLI error. The message is printed as-is (no stack trace) and\n * `exitCode` drives the process exit code. Use `hint` to tell the user exactly\n * which command to run next — every error should be actionable.\n */\nexport class CliError extends Error {\n readonly exitCode: number;\n readonly hint?: string;\n /** HTTP status when this wraps a Cloudflare API error (lets callers tell a\n * genuine 404 \"already gone\" from a transient failure). */\n readonly status?: number;\n\n constructor(message: string, opts: { exitCode?: number; hint?: string; status?: number } = {}) {\n super(message);\n this.name = \"CliError\";\n this.exitCode = opts.exitCode ?? 1;\n this.hint = opts.hint;\n this.status = opts.status;\n }\n}\n\n/** Print an error and return the exit code. Known CliErrors print cleanly; the\n * rest print their message plus a note that it was unexpected. */\nexport function reportError(err: unknown): number {\n if (err instanceof CliError) {\n console.error(pc.red(`✗ ${err.message}`));\n if (err.hint) console.error(pc.dim(` → ${err.hint}`));\n return err.exitCode;\n }\n const message = err instanceof Error ? err.message : String(err);\n console.error(pc.red(`✗ ${message}`));\n console.error(pc.dim(\" (unexpected error — please report if this persists)\"));\n return 1;\n}\n","import { chmodSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { CliError } from \"../ui/errors.js\";\nimport { configFile, ensureDirs } from \"./paths.js\";\n\nexport interface CloudtunnelConfig {\n apiToken?: string;\n accountId?: string;\n defaultZone?: string;\n}\n\nexport interface Credentials {\n apiToken: string;\n accountId?: string;\n defaultZone?: string;\n}\n\n/** Read config.json (missing/invalid → empty config, never throws). */\nexport function loadConfig(): CloudtunnelConfig {\n try {\n return JSON.parse(readFileSync(configFile, \"utf8\")) as CloudtunnelConfig;\n } catch {\n return {};\n }\n}\n\n/** Persist config.json with owner-only perms (0600). */\nexport function saveConfig(config: CloudtunnelConfig): void {\n ensureDirs();\n writeFileSync(configFile, JSON.stringify(config, null, 2), { mode: 0o600 });\n chmodSync(configFile, 0o600); // enforce even if the file pre-existed\n}\n\n/**\n * Resolve credentials: env vars override the config file. Throws an actionable\n * CliError if no API token is available anywhere.\n */\nexport function getCredentials(): Credentials {\n const config = loadConfig();\n const apiToken = process.env.CLOUDFLARE_API_TOKEN ?? config.apiToken;\n const accountId = process.env.CLOUDFLARE_ACCOUNT_ID ?? config.accountId;\n if (!apiToken) {\n throw new CliError(\"Not authenticated with Cloudflare.\", {\n hint: \"run `cloudtunnel login` (or set CLOUDFLARE_API_TOKEN)\",\n });\n }\n return { apiToken, accountId, defaultZone: config.defaultZone };\n}\n","import envPaths from \"env-paths\";\nimport { mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n// `~/.config/cloudtunnel/` (XDG). suffix:'' avoids env-paths' default \"-nodejs\".\nconst paths = envPaths(\"cloudtunnel\", { suffix: \"\" });\n\nexport const configDir = paths.config;\nexport const configFile = join(configDir, \"config.json\");\nexport const registryFile = join(configDir, \"tunnels.json\");\nexport const profilesFile = join(configDir, \"profiles.json\");\nexport const binDir = join(configDir, \"bin\");\nexport const logDir = join(configDir, \"logs\");\n\n/** Create the app dirs with owner-only perms (secrets live here). Idempotent. */\nexport function ensureDirs(): void {\n for (const dir of [configDir, binDir, logDir]) {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n}\n","import { CliError } from \"../ui/errors.js\";\nimport { getCredentials } from \"../config/store.js\";\n\nconst API_BASE = \"https://api.cloudflare.com/client/v4\";\nconst RETRYABLE = new Set([429, 500, 502, 503, 504]);\nconst MAX_ATTEMPTS = 4;\n\n/** Resolved Cloudflare context for account-scoped calls. */\nexport interface Cf {\n token: string;\n accountId: string;\n}\n\ninterface CfEnvelope<T> {\n success: boolean;\n result: T;\n result_info?: { page: number; total_pages?: number; per_page: number; count: number };\n errors?: { message: string }[];\n}\n\n/** Token + account id required for tunnel ops. Throws actionably if unresolved. */\nexport function resolveCf(): Cf {\n const creds = getCredentials();\n if (!creds.accountId) {\n throw new CliError(\"No Cloudflare account id resolved.\", {\n hint: \"run `cloudtunnel login` (or set CLOUDFLARE_ACCOUNT_ID)\",\n });\n }\n return { token: creds.apiToken, accountId: creds.accountId };\n}\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\n/**\n * Single Cloudflare API call with backoff on 429/5xx (honors Retry-After).\n * Errors are sanitized — the bearer token is only ever sent in the header and\n * never appears in a thrown message.\n */\nexport async function cfRequest<T>(\n token: string,\n method: string,\n path: string,\n body?: unknown,\n): Promise<CfEnvelope<T>> {\n for (let attempt = 0; ; attempt++) {\n let res: Response;\n try {\n res = await fetch(`${API_BASE}${path}`, {\n method,\n headers: { Authorization: `Bearer ${token}`, \"Content-Type\": \"application/json\" },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n } catch {\n throw new CliError(\"Could not reach the Cloudflare API (network error).\");\n }\n if (RETRYABLE.has(res.status) && attempt < MAX_ATTEMPTS) {\n const retryAfter = Number(res.headers.get(\"retry-after\")) || 0;\n await sleep(retryAfter > 0 ? retryAfter * 1000 : 2 ** attempt * 500);\n continue;\n }\n const env = (await res.json().catch(() => ({}))) as CfEnvelope<T>;\n if (!res.ok || !env.success) {\n const msg = env.errors?.[0]?.message ?? `HTTP ${res.status}`;\n throw new CliError(`Cloudflare API error: ${msg}`, { status: res.status });\n }\n return env;\n }\n}\n\n/**\n * Fetch every page of a list endpoint. Robust when `total_pages` is absent:\n * stops when a page returns fewer than per_page items.\n */\nexport async function cfPaginate<T>(token: string, basePath: string): Promise<T[]> {\n const sep = basePath.includes(\"?\") ? \"&\" : \"?\";\n const out: T[] = [];\n for (let page = 1; ; page++) {\n const env = await cfRequest<T[]>(token, \"GET\", `${basePath}${sep}per_page=50&page=${page}`);\n const items = env.result ?? [];\n out.push(...items);\n const perPage = env.result_info?.per_page ?? 50;\n const totalPages = env.result_info?.total_pages;\n const more = totalPages ? page < totalPages : items.length === perPage;\n if (items.length === 0 || !more) break;\n }\n return out;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/ui/errors.ts","../src/config/store.ts","../src/config/paths.ts","../src/cloudflare/client.ts"],"sourcesContent":["import pc from \"picocolors\";\n\n/**\n * A user-facing CLI error. The message is printed as-is (no stack trace) and\n * `exitCode` drives the process exit code. Use `hint` to tell the user exactly\n * which command to run next — every error should be actionable.\n */\nexport class CliError extends Error {\n readonly exitCode: number;\n readonly hint?: string;\n /** HTTP status when this wraps a Cloudflare API error (lets callers tell a\n * genuine 404 \"already gone\" from a transient failure). */\n readonly status?: number;\n\n constructor(message: string, opts: { exitCode?: number; hint?: string; status?: number } = {}) {\n super(message);\n this.name = \"CliError\";\n this.exitCode = opts.exitCode ?? 1;\n this.hint = opts.hint;\n this.status = opts.status;\n }\n}\n\n/** Print an error and return the exit code. Known CliErrors print cleanly; the\n * rest print their message plus a note that it was unexpected. */\nexport function reportError(err: unknown): number {\n if (err instanceof CliError) {\n console.error(pc.red(`✗ ${err.message}`));\n if (err.hint) console.error(pc.dim(` → ${err.hint}`));\n return err.exitCode;\n }\n const message = err instanceof Error ? err.message : String(err);\n console.error(pc.red(`✗ ${message}`));\n console.error(pc.dim(\" (unexpected error — please report if this persists)\"));\n return 1;\n}\n","import { chmodSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { CliError } from \"../ui/errors.js\";\nimport { configFile, ensureDirs } from \"./paths.js\";\n\nexport interface CloudtunnelConfig {\n apiToken?: string;\n accountId?: string;\n defaultZone?: string;\n}\n\nexport interface Credentials {\n apiToken: string;\n accountId?: string;\n defaultZone?: string;\n}\n\n/** Read config.json (missing/invalid → empty config, never throws). */\nexport function loadConfig(): CloudtunnelConfig {\n try {\n return JSON.parse(readFileSync(configFile, \"utf8\")) as CloudtunnelConfig;\n } catch {\n return {};\n }\n}\n\n/** Persist config.json with owner-only perms (0600). */\nexport function saveConfig(config: CloudtunnelConfig): void {\n ensureDirs();\n writeFileSync(configFile, JSON.stringify(config, null, 2), { mode: 0o600 });\n chmodSync(configFile, 0o600); // enforce even if the file pre-existed\n}\n\n/**\n * Resolve credentials: env vars override the config file. Throws an actionable\n * CliError if no API token is available anywhere.\n */\nexport function getCredentials(): Credentials {\n const config = loadConfig();\n const apiToken = process.env.CLOUDFLARE_API_TOKEN ?? config.apiToken;\n const accountId = process.env.CLOUDFLARE_ACCOUNT_ID ?? config.accountId;\n if (!apiToken) {\n throw new CliError(\"Not authenticated with Cloudflare.\", {\n hint: \"run `cloudtunnel login` (or set CLOUDFLARE_API_TOKEN)\",\n });\n }\n return { apiToken, accountId, defaultZone: config.defaultZone };\n}\n","import envPaths from \"env-paths\";\nimport { mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n// `~/.config/cloudtunnel/` (XDG). suffix:'' avoids env-paths' default \"-nodejs\".\nconst paths = envPaths(\"cloudtunnel\", { suffix: \"\" });\n\nexport const configDir = paths.config;\nexport const configFile = join(configDir, \"config.json\");\nexport const registryFile = join(configDir, \"tunnels.json\");\nexport const profilesFile = join(configDir, \"profiles.json\");\nexport const binDir = join(configDir, \"bin\");\nexport const logDir = join(configDir, \"logs\");\n\n/** Create the app dirs with owner-only perms (secrets live here). Idempotent. */\nexport function ensureDirs(): void {\n for (const dir of [configDir, binDir, logDir]) {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n}\n","import { CliError } from \"../ui/errors.js\";\nimport { getCredentials } from \"../config/store.js\";\n\nconst API_BASE = \"https://api.cloudflare.com/client/v4\";\nconst RETRYABLE = new Set([429, 500, 502, 503, 504]);\nconst MAX_ATTEMPTS = 4;\n\n/** Resolved Cloudflare context for account-scoped calls. */\nexport interface Cf {\n token: string;\n accountId: string;\n}\n\ninterface CfEnvelope<T> {\n success: boolean;\n result: T;\n result_info?: { page: number; total_pages?: number; per_page: number; count: number };\n errors?: { message: string }[];\n}\n\n/** Token + account id required for tunnel ops. Throws actionably if unresolved. */\nexport function resolveCf(): Cf {\n const creds = getCredentials();\n if (!creds.accountId) {\n throw new CliError(\"No Cloudflare account id resolved.\", {\n hint: \"run `cloudtunnel login` (or set CLOUDFLARE_ACCOUNT_ID)\",\n });\n }\n return { token: creds.apiToken, accountId: creds.accountId };\n}\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\n/**\n * Single Cloudflare API call with backoff on 429/5xx (honors Retry-After).\n * Errors are sanitized — the bearer token is only ever sent in the header and\n * never appears in a thrown message.\n */\nexport async function cfRequest<T>(\n token: string,\n method: string,\n path: string,\n body?: unknown,\n): Promise<CfEnvelope<T>> {\n for (let attempt = 0; ; attempt++) {\n let res: Response;\n try {\n res = await fetch(`${API_BASE}${path}`, {\n method,\n headers: { Authorization: `Bearer ${token}`, \"Content-Type\": \"application/json\" },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n } catch {\n throw new CliError(\"Could not reach the Cloudflare API (network error).\");\n }\n if (RETRYABLE.has(res.status) && attempt < MAX_ATTEMPTS) {\n const retryAfter = Number(res.headers.get(\"retry-after\")) || 0;\n await sleep(retryAfter > 0 ? retryAfter * 1000 : 2 ** attempt * 500);\n continue;\n }\n const env = (await res.json().catch(() => ({}))) as CfEnvelope<T>;\n if (!res.ok || !env.success) {\n const msg = env.errors?.[0]?.message ?? `HTTP ${res.status}`;\n throw new CliError(`Cloudflare API error: ${msg}`, { status: res.status });\n }\n return env;\n }\n}\n\n/**\n * Fetch every page of a list endpoint. Robust when `total_pages` is absent:\n * stops when a page returns fewer than per_page items.\n */\nexport async function cfPaginate<T>(token: string, basePath: string): Promise<T[]> {\n const sep = basePath.includes(\"?\") ? \"&\" : \"?\";\n const out: T[] = [];\n for (let page = 1; ; page++) {\n const env = await cfRequest<T[]>(token, \"GET\", `${basePath}${sep}per_page=50&page=${page}`);\n const items = env.result ?? [];\n out.push(...items);\n const perPage = env.result_info?.per_page ?? 50;\n const totalPages = env.result_info?.total_pages;\n const more = totalPages ? page < totalPages : items.length === perPage;\n if (items.length === 0 || !more) break;\n }\n return out;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -7,8 +7,8 @@ import {
|
|
|
7
7
|
findCname,
|
|
8
8
|
isManagedDns,
|
|
9
9
|
listCargoCnames
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-NZKGIDIW.js";
|
|
11
|
+
import "./chunk-RWR6VXNB.js";
|
|
12
12
|
export {
|
|
13
13
|
MANAGED_DNS_COMMENT,
|
|
14
14
|
cargoTarget,
|
|
@@ -18,4 +18,4 @@ export {
|
|
|
18
18
|
isManagedDns,
|
|
19
19
|
listCargoCnames
|
|
20
20
|
};
|
|
21
|
-
//# sourceMappingURL=dns-
|
|
21
|
+
//# sourceMappingURL=dns-5OXFAQ4D.js.map
|