@dutodev/cli 1.2.0 → 1.3.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
CHANGED
|
@@ -203,3 +203,4 @@ The page has the list on the left and the details on the right, with the tabs **
|
|
|
203
203
|
|
|
204
204
|
- Config lives in `~/.duto/config.json` (mode 600). `DUTO_HOME` changes that folder.
|
|
205
205
|
- `DUTO_API_URL` points the CLI at another server (default: `https://duto.dev`).
|
|
206
|
+
- Once a day, duto asks npm for the latest version and shows a line when there is a newer one. `DUTO_NO_UPDATE_CHECK=1` turns that off.
|
|
@@ -388,6 +388,107 @@ function placeOf(colo) {
|
|
|
388
388
|
return entry ? { city: entry[0], country: entry[1] } : null;
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
+
// ../shared/src/cliVersion.ts
|
|
392
|
+
var CLI_VERSION_HEADER = "x-duto-cli-version";
|
|
393
|
+
var CLI_USER_AGENT = "duto-cli";
|
|
394
|
+
var CLI_UPDATE_COMMAND = "npm i -g @dutodev/cli";
|
|
395
|
+
var VERSION = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/;
|
|
396
|
+
function parts(version) {
|
|
397
|
+
const m = VERSION.exec(version.trim());
|
|
398
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
399
|
+
}
|
|
400
|
+
function compareVersions(a, b) {
|
|
401
|
+
const x = parts(a);
|
|
402
|
+
const y = parts(b);
|
|
403
|
+
if (!x || !y) return null;
|
|
404
|
+
for (let i = 0; i < 3; i++) {
|
|
405
|
+
const d = (x[i] ?? 0) - (y[i] ?? 0);
|
|
406
|
+
if (d !== 0) return d;
|
|
407
|
+
}
|
|
408
|
+
return 0;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// src/config.ts
|
|
412
|
+
import { randomUUID } from "crypto";
|
|
413
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
414
|
+
import { homedir } from "os";
|
|
415
|
+
import { join } from "path";
|
|
416
|
+
function dutoHome() {
|
|
417
|
+
return process.env.DUTO_HOME || join(homedir(), ".duto");
|
|
418
|
+
}
|
|
419
|
+
function configPath() {
|
|
420
|
+
return join(dutoHome(), "config.json");
|
|
421
|
+
}
|
|
422
|
+
function apiUrl() {
|
|
423
|
+
return (process.env.DUTO_API_URL || "https://duto.dev").replace(/\/+$/, "");
|
|
424
|
+
}
|
|
425
|
+
function saveConfig(c) {
|
|
426
|
+
mkdirSync(dutoHome(), { recursive: true, mode: 448 });
|
|
427
|
+
const path = configPath();
|
|
428
|
+
writeFileSync(path, JSON.stringify(c, null, 2) + "\n", { mode: 384 });
|
|
429
|
+
chmodSync(path, 384);
|
|
430
|
+
}
|
|
431
|
+
function loadConfig() {
|
|
432
|
+
const path = configPath();
|
|
433
|
+
if (existsSync(path)) {
|
|
434
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
435
|
+
if (parsed.deviceId) return parsed;
|
|
436
|
+
const fixed = { ...parsed, deviceId: randomUUID() };
|
|
437
|
+
saveConfig(fixed);
|
|
438
|
+
return fixed;
|
|
439
|
+
}
|
|
440
|
+
const fresh = { deviceId: randomUUID() };
|
|
441
|
+
saveConfig(fresh);
|
|
442
|
+
return fresh;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// src/updates.ts
|
|
446
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
447
|
+
import { join as join2 } from "path";
|
|
448
|
+
var LATEST_URL = "https://registry.npmjs.org/@dutodev/cli/latest";
|
|
449
|
+
var CHECK_EVERY_MS = 24 * 60 * 60 * 1e3;
|
|
450
|
+
var TIMEOUT_MS = 3e3;
|
|
451
|
+
async function fetchLatest(fetchImpl) {
|
|
452
|
+
const res = await fetchImpl(LATEST_URL, { headers: { accept: "application/json" }, signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
453
|
+
if (!res.ok) return null;
|
|
454
|
+
const { version } = await res.json();
|
|
455
|
+
return typeof version === "string" && compareVersions(version, version) === 0 ? version : null;
|
|
456
|
+
}
|
|
457
|
+
async function availableUpdate(o) {
|
|
458
|
+
const cached = o.read();
|
|
459
|
+
let latest = cached?.latest ?? null;
|
|
460
|
+
if (!cached || o.now - cached.checkedAt >= CHECK_EVERY_MS) {
|
|
461
|
+
latest = await fetchLatest(o.fetchImpl ?? fetch).catch(() => null) ?? latest;
|
|
462
|
+
o.write({ checkedAt: o.now, latest });
|
|
463
|
+
}
|
|
464
|
+
const diff = latest ? compareVersions(latest, o.current) : null;
|
|
465
|
+
return diff !== null && diff > 0 ? latest : null;
|
|
466
|
+
}
|
|
467
|
+
var cachePath = () => join2(dutoHome(), "update.json");
|
|
468
|
+
function readUpdateCache() {
|
|
469
|
+
try {
|
|
470
|
+
const c = JSON.parse(readFileSync2(cachePath(), "utf8"));
|
|
471
|
+
return typeof c.checkedAt === "number" ? { checkedAt: c.checkedAt, latest: typeof c.latest === "string" ? c.latest : null } : null;
|
|
472
|
+
} catch {
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function writeUpdateCache(cache) {
|
|
477
|
+
try {
|
|
478
|
+
mkdirSync2(dutoHome(), { recursive: true, mode: 448 });
|
|
479
|
+
writeFileSync2(cachePath(), JSON.stringify(cache) + "\n", { mode: 384 });
|
|
480
|
+
} catch {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function checkForUpdate(current) {
|
|
485
|
+
if (process.env.DUTO_NO_UPDATE_CHECK) return Promise.resolve(null);
|
|
486
|
+
return availableUpdate({ current, now: Date.now(), read: readUpdateCache, write: writeUpdateCache }).catch(() => null);
|
|
487
|
+
}
|
|
488
|
+
function updateLine(latest) {
|
|
489
|
+
return `duto ${latest} is available. Update: ${CLI_UPDATE_COMMAND}`;
|
|
490
|
+
}
|
|
491
|
+
|
|
391
492
|
// src/ui/theme.ts
|
|
392
493
|
var C = {
|
|
393
494
|
signal: "#2dd4bf",
|
|
@@ -449,6 +550,12 @@ export {
|
|
|
449
550
|
normalizeName,
|
|
450
551
|
validateName,
|
|
451
552
|
placeOf,
|
|
553
|
+
CLI_VERSION_HEADER,
|
|
554
|
+
CLI_USER_AGENT,
|
|
555
|
+
dutoHome,
|
|
556
|
+
apiUrl,
|
|
557
|
+
saveConfig,
|
|
558
|
+
loadConfig,
|
|
452
559
|
C,
|
|
453
560
|
tunnelColor,
|
|
454
561
|
fit,
|
|
@@ -458,5 +565,7 @@ export {
|
|
|
458
565
|
formatDuration,
|
|
459
566
|
formatBytes,
|
|
460
567
|
formatTime,
|
|
461
|
-
localLabel
|
|
568
|
+
localLabel,
|
|
569
|
+
checkForUpdate,
|
|
570
|
+
updateLine
|
|
462
571
|
};
|
|
@@ -12,8 +12,9 @@ import {
|
|
|
12
12
|
normalizeName,
|
|
13
13
|
statusColor,
|
|
14
14
|
tunnelColor,
|
|
15
|
+
updateLine,
|
|
15
16
|
validateName
|
|
16
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-4SIBC52A.js";
|
|
17
18
|
|
|
18
19
|
// src/ui/fullscreen.tsx
|
|
19
20
|
import { render } from "ink";
|
|
@@ -284,7 +285,7 @@ var KEYS = [
|
|
|
284
285
|
["tab", "focus"],
|
|
285
286
|
["q", "quit"]
|
|
286
287
|
];
|
|
287
|
-
function App({ session, version, system, onExit, inspector = null, inspectorError }) {
|
|
288
|
+
function App({ session, version, system, onExit, inspector = null, inspectorError, update = null }) {
|
|
288
289
|
const subState = useCallback((fn) => session.subscribe(fn), [session]);
|
|
289
290
|
const subRecords = useCallback((fn) => session.records.subscribe(fn), [session]);
|
|
290
291
|
const state = useSyncExternalStore(subState, () => session.getState());
|
|
@@ -435,7 +436,7 @@ function App({ session, version, system, onExit, inspector = null, inspectorErro
|
|
|
435
436
|
});
|
|
436
437
|
const live = state.tunnels.filter((t) => t.status === "live").length;
|
|
437
438
|
const conn = state.connection === "online" ? { text: "connected \u25CF", color: C.signal } : state.connection === "connecting" ? { text: "connecting\u2026", color: C.muted } : { text: "reconnecting\u2026", color: C.warn };
|
|
438
|
-
const listHeight = Math.max(3, rows - state.tunnels.length - (inspector ? 10 : 9));
|
|
439
|
+
const listHeight = Math.max(3, rows - state.tunnels.length - (inspector ? 10 : 9) - (update ? 1 : 0));
|
|
439
440
|
const detailRecord = mode.kind === "detail" ? all.find((r) => r.id === mode.id) : void 0;
|
|
440
441
|
const qrTunnel = mode.kind === "qr" ? state.tunnels.find((t) => t.key === mode.key) : void 0;
|
|
441
442
|
return /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", width, children: [
|
|
@@ -460,6 +461,7 @@ function App({ session, version, system, onExit, inspector = null, inspectorErro
|
|
|
460
461
|
] }),
|
|
461
462
|
/* @__PURE__ */ jsx6(Text6, { color: conn.color, children: conn.text })
|
|
462
463
|
] }),
|
|
464
|
+
update && /* @__PURE__ */ jsx6(Text6, { color: C.warn, children: updateLine(update) }),
|
|
463
465
|
/* @__PURE__ */ jsx6(Text6, { children: " " }),
|
|
464
466
|
/* @__PURE__ */ jsx6(TunnelTable, { tunnels: state.tunnels, selected: selTun, focused: focus === "tunnels" }),
|
|
465
467
|
inspector && /* @__PURE__ */ jsx6(Text6, { color: C.muted, children: `inspector ${inspector.url}` }),
|
|
@@ -548,7 +550,7 @@ var realSystem = {
|
|
|
548
550
|
|
|
549
551
|
// src/ui/fullscreen.tsx
|
|
550
552
|
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
551
|
-
function runFullscreen(session, specs, version, inspector, inspectorError) {
|
|
553
|
+
function runFullscreen(session, specs, version, inspector, inspectorError, update = Promise.resolve(null)) {
|
|
552
554
|
return withFullscreen(
|
|
553
555
|
process.stdout,
|
|
554
556
|
(signal) => new Promise((resolve) => {
|
|
@@ -559,7 +561,11 @@ function runFullscreen(session, specs, version, inspector, inspectorError) {
|
|
|
559
561
|
app.unmount();
|
|
560
562
|
resolve(code);
|
|
561
563
|
};
|
|
562
|
-
const
|
|
564
|
+
const view = (latest) => /* @__PURE__ */ jsx7(App, { session, version, system: realSystem, inspector, inspectorError, update: latest, onExit: () => finish(0) });
|
|
565
|
+
const app = render(view(null), { exitOnCtrlC: false });
|
|
566
|
+
void update.then((latest) => {
|
|
567
|
+
if (latest && !done) app.rerender(view(latest));
|
|
568
|
+
});
|
|
563
569
|
signal.addEventListener("abort", () => void session.stop().then(() => finish(0)), { once: true });
|
|
564
570
|
session.start(specs).catch(() => void 0);
|
|
565
571
|
})
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
C,
|
|
4
|
+
CLI_USER_AGENT,
|
|
5
|
+
CLI_VERSION_HEADER,
|
|
4
6
|
MAX_NAME,
|
|
5
7
|
TRUNCATED_NOTE,
|
|
8
|
+
apiUrl,
|
|
6
9
|
bodyNote,
|
|
10
|
+
checkForUpdate,
|
|
11
|
+
dutoHome,
|
|
7
12
|
formatBytes,
|
|
8
13
|
formatDuration,
|
|
9
14
|
formatTime,
|
|
15
|
+
loadConfig,
|
|
10
16
|
localLabel,
|
|
11
17
|
normalizeName,
|
|
12
18
|
placeOf,
|
|
19
|
+
saveConfig,
|
|
13
20
|
statusColor,
|
|
21
|
+
updateLine,
|
|
14
22
|
validateName
|
|
15
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-4SIBC52A.js";
|
|
16
24
|
|
|
17
25
|
// src/ui/brand.ts
|
|
18
26
|
function colorOn(stream = process.stdout) {
|
|
@@ -38,9 +46,12 @@ var warn = (msg) => `${paint("warn", "\u26A0")} ${msg}`;
|
|
|
38
46
|
|
|
39
47
|
// src/index.ts
|
|
40
48
|
import { hostname as hostname2 } from "os";
|
|
41
|
-
import { join as
|
|
49
|
+
import { join as join5, relative } from "path";
|
|
42
50
|
import cac from "cac";
|
|
43
51
|
|
|
52
|
+
// src/version.ts
|
|
53
|
+
var CLI_VERSION = "1.3.0";
|
|
54
|
+
|
|
44
55
|
// src/api.ts
|
|
45
56
|
var DutoApiError = class extends Error {
|
|
46
57
|
constructor(message2, status, code) {
|
|
@@ -71,7 +82,8 @@ function createApiClient(o) {
|
|
|
71
82
|
...o.token === void 0 ? {} : { authorization: `Bearer ${o.token}` },
|
|
72
83
|
...body === void 0 ? {} : { "content-type": "application/json" },
|
|
73
84
|
...bookmark === null ? {} : { [BOOKMARK_HEADER]: bookmark },
|
|
74
|
-
"user-agent":
|
|
85
|
+
"user-agent": CLI_USER_AGENT,
|
|
86
|
+
[CLI_VERSION_HEADER]: CLI_VERSION
|
|
75
87
|
},
|
|
76
88
|
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
77
89
|
});
|
|
@@ -118,8 +130,8 @@ function createApiClient(o) {
|
|
|
118
130
|
|
|
119
131
|
// src/cloudflared.ts
|
|
120
132
|
import { createHash } from "crypto";
|
|
121
|
-
import { chmodSync
|
|
122
|
-
import { join
|
|
133
|
+
import { chmodSync, existsSync, mkdirSync, mkdtempSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
134
|
+
import { join } from "path";
|
|
123
135
|
import { extract } from "tar";
|
|
124
136
|
|
|
125
137
|
// src/checksums.ts
|
|
@@ -132,40 +144,6 @@ var CHECKSUMS = {
|
|
|
132
144
|
"cloudflared-windows-amd64.exe": "2493dbc168923bb3e59e5e7716f71deced92992eea9acd6e9d74f84ac7751343"
|
|
133
145
|
};
|
|
134
146
|
|
|
135
|
-
// src/config.ts
|
|
136
|
-
import { randomUUID } from "crypto";
|
|
137
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
138
|
-
import { homedir } from "os";
|
|
139
|
-
import { join } from "path";
|
|
140
|
-
function dutoHome() {
|
|
141
|
-
return process.env.DUTO_HOME || join(homedir(), ".duto");
|
|
142
|
-
}
|
|
143
|
-
function configPath() {
|
|
144
|
-
return join(dutoHome(), "config.json");
|
|
145
|
-
}
|
|
146
|
-
function apiUrl() {
|
|
147
|
-
return (process.env.DUTO_API_URL || "https://duto.dev").replace(/\/+$/, "");
|
|
148
|
-
}
|
|
149
|
-
function saveConfig(c) {
|
|
150
|
-
mkdirSync(dutoHome(), { recursive: true, mode: 448 });
|
|
151
|
-
const path = configPath();
|
|
152
|
-
writeFileSync(path, JSON.stringify(c, null, 2) + "\n", { mode: 384 });
|
|
153
|
-
chmodSync(path, 384);
|
|
154
|
-
}
|
|
155
|
-
function loadConfig() {
|
|
156
|
-
const path = configPath();
|
|
157
|
-
if (existsSync(path)) {
|
|
158
|
-
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
159
|
-
if (parsed.deviceId) return parsed;
|
|
160
|
-
const fixed = { ...parsed, deviceId: randomUUID() };
|
|
161
|
-
saveConfig(fixed);
|
|
162
|
-
return fixed;
|
|
163
|
-
}
|
|
164
|
-
const fresh = { deviceId: randomUUID() };
|
|
165
|
-
saveConfig(fresh);
|
|
166
|
-
return fresh;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
147
|
// src/cloudflared.ts
|
|
170
148
|
function assetFor(platform, arch) {
|
|
171
149
|
const key = `${platform}/${arch}`;
|
|
@@ -190,11 +168,11 @@ function downloadUrl(asset, version = CLOUDFLARED_VERSION) {
|
|
|
190
168
|
async function ensureCloudflared(opts = {}) {
|
|
191
169
|
const platform = opts.platform ?? process.platform;
|
|
192
170
|
const { asset, kind } = assetFor(platform, opts.arch ?? process.arch);
|
|
193
|
-
const binDir =
|
|
194
|
-
const target =
|
|
195
|
-
if (
|
|
171
|
+
const binDir = join(dutoHome(), "bin");
|
|
172
|
+
const target = join(binDir, `cloudflared-${CLOUDFLARED_VERSION}${platform === "win32" ? ".exe" : ""}`);
|
|
173
|
+
if (existsSync(target)) return target;
|
|
196
174
|
opts.onDownload?.();
|
|
197
|
-
|
|
175
|
+
mkdirSync(binDir, { recursive: true });
|
|
198
176
|
const res = await (opts.fetchImpl ?? fetch)(downloadUrl(asset));
|
|
199
177
|
if (!res.ok) throw new Error(`Could not download cloudflared (HTTP ${res.status}).`);
|
|
200
178
|
const bytes = Buffer.from(await res.arrayBuffer());
|
|
@@ -204,22 +182,22 @@ async function ensureCloudflared(opts = {}) {
|
|
|
204
182
|
rmSync(target, { force: true });
|
|
205
183
|
throw new Error("cloudflared checksum mismatch. Try again; if it keeps failing, update duto.");
|
|
206
184
|
}
|
|
207
|
-
const tmp = mkdtempSync(
|
|
185
|
+
const tmp = mkdtempSync(join(binDir, ".download-"));
|
|
208
186
|
try {
|
|
209
187
|
if (kind === "tgz") {
|
|
210
|
-
const tgz =
|
|
211
|
-
|
|
188
|
+
const tgz = join(tmp, asset);
|
|
189
|
+
writeFileSync(tgz, bytes);
|
|
212
190
|
await extract({ file: tgz, cwd: tmp });
|
|
213
|
-
renameSync(
|
|
191
|
+
renameSync(join(tmp, "cloudflared"), target);
|
|
214
192
|
} else {
|
|
215
|
-
const file =
|
|
216
|
-
|
|
193
|
+
const file = join(tmp, "cloudflared");
|
|
194
|
+
writeFileSync(file, bytes);
|
|
217
195
|
renameSync(file, target);
|
|
218
196
|
}
|
|
219
197
|
} finally {
|
|
220
198
|
rmSync(tmp, { recursive: true, force: true });
|
|
221
199
|
}
|
|
222
|
-
if (platform !== "win32")
|
|
200
|
+
if (platform !== "win32") chmodSync(target, 493);
|
|
223
201
|
return target;
|
|
224
202
|
}
|
|
225
203
|
async function ensureCloudflaredWithProgress(write, opts = {}) {
|
|
@@ -241,9 +219,9 @@ async function ensureCloudflaredWithProgress(write, opts = {}) {
|
|
|
241
219
|
}
|
|
242
220
|
|
|
243
221
|
// src/session/allowlist.ts
|
|
244
|
-
import { mkdirSync as
|
|
222
|
+
import { mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2 } from "fs";
|
|
245
223
|
import { BlockList, isIP } from "net";
|
|
246
|
-
import { dirname, join as
|
|
224
|
+
import { dirname, join as join2 } from "path";
|
|
247
225
|
|
|
248
226
|
// src/session/allowlist-snapshot.ts
|
|
249
227
|
var ALLOW_SNAPSHOT = {
|
|
@@ -312,7 +290,7 @@ function blockListOf(cidrs) {
|
|
|
312
290
|
}
|
|
313
291
|
function readCache(file) {
|
|
314
292
|
try {
|
|
315
|
-
const c = JSON.parse(
|
|
293
|
+
const c = JSON.parse(readFileSync(file, "utf8"));
|
|
316
294
|
if (typeof c?.fetchedAt === "number" && c.lists && typeof c.lists === "object") return c;
|
|
317
295
|
} catch {
|
|
318
296
|
}
|
|
@@ -329,7 +307,7 @@ async function fetchRanges(name, f) {
|
|
|
329
307
|
return out.map((x) => x.includes("/") ? x : `${x}/${splitCidr(x).prefix}`);
|
|
330
308
|
}
|
|
331
309
|
async function resolveAllow(entries, o = {}) {
|
|
332
|
-
const cacheFile = o.cacheFile ??
|
|
310
|
+
const cacheFile = o.cacheFile ?? join2(dutoHome(), "allowlist.json");
|
|
333
311
|
const f = o.fetch ?? fetch;
|
|
334
312
|
const now = (o.now ?? Date.now)();
|
|
335
313
|
const parsed = entries.map((raw) => ({ raw, e: parseAllowEntry(raw) }));
|
|
@@ -352,8 +330,8 @@ async function resolveAllow(entries, o = {}) {
|
|
|
352
330
|
const everyListFromNetwork = names.length > 0 && Object.keys(net).length === names.length;
|
|
353
331
|
if (everyListFromNetwork) {
|
|
354
332
|
try {
|
|
355
|
-
|
|
356
|
-
|
|
333
|
+
mkdirSync2(dirname(cacheFile), { recursive: true });
|
|
334
|
+
writeFileSync2(cacheFile, JSON.stringify({ fetchedAt: now, lists: net }));
|
|
357
335
|
} catch {
|
|
358
336
|
}
|
|
359
337
|
}
|
|
@@ -375,8 +353,8 @@ async function resolveAllow(entries, o = {}) {
|
|
|
375
353
|
}
|
|
376
354
|
|
|
377
355
|
// src/session/config.ts
|
|
378
|
-
import { existsSync as
|
|
379
|
-
import { dirname as dirname2, join as
|
|
356
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
357
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
380
358
|
import { isMap, isScalar, isSeq, LineCounter, parseDocument } from "yaml";
|
|
381
359
|
var ConfigError = class extends Error {
|
|
382
360
|
constructor(message2, line, file = CONFIG_FILES[0]) {
|
|
@@ -522,13 +500,13 @@ function parseConfig(text, file = CONFIG_FILES[0], env = process.env) {
|
|
|
522
500
|
function findConfig(cwd) {
|
|
523
501
|
let dir = cwd;
|
|
524
502
|
for (; ; ) {
|
|
525
|
-
const present = CONFIG_FILES.filter((f) =>
|
|
503
|
+
const present = CONFIG_FILES.filter((f) => existsSync2(join3(dir, f)));
|
|
526
504
|
if (present.length > 1) throw new ConfigError(`${CONFIG_FILES.join(" and ")} in the same folder. Delete one of them.`, void 0, "");
|
|
527
505
|
if (present.length === 1) {
|
|
528
|
-
const path =
|
|
529
|
-
return { path, tunnels: parseConfig(
|
|
506
|
+
const path = join3(dir, present[0]);
|
|
507
|
+
return { path, tunnels: parseConfig(readFileSync2(path, "utf8"), present[0]) };
|
|
530
508
|
}
|
|
531
|
-
if (
|
|
509
|
+
if (existsSync2(join3(dir, ".git"))) return null;
|
|
532
510
|
const up = dirname2(dir);
|
|
533
511
|
if (up === dir) return null;
|
|
534
512
|
dir = up;
|
|
@@ -545,7 +523,7 @@ async function browserLogin(o) {
|
|
|
545
523
|
const start = await api.startLogin({
|
|
546
524
|
deviceId: o.deviceId,
|
|
547
525
|
deviceName: hostname(),
|
|
548
|
-
cliVersion: "1.
|
|
526
|
+
cliVersion: "1.3.0",
|
|
549
527
|
nodeVersion: process.version,
|
|
550
528
|
os: process.platform,
|
|
551
529
|
arch: process.arch
|
|
@@ -2249,13 +2227,13 @@ async function startProxy(o) {
|
|
|
2249
2227
|
|
|
2250
2228
|
// src/session/orphans.ts
|
|
2251
2229
|
import { spawnSync } from "child_process";
|
|
2252
|
-
import { existsSync as
|
|
2253
|
-
import { join as
|
|
2254
|
-
var fileOf = (dir, cfPid) =>
|
|
2230
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync3, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
2231
|
+
import { join as join4 } from "path";
|
|
2232
|
+
var fileOf = (dir, cfPid) => join4(dir, `cloudflared-${cfPid}.json`);
|
|
2255
2233
|
function recordCloudflared(dir, cfPid, sessionPid = process.pid) {
|
|
2256
2234
|
try {
|
|
2257
|
-
|
|
2258
|
-
|
|
2235
|
+
mkdirSync3(dir, { recursive: true });
|
|
2236
|
+
writeFileSync3(fileOf(dir, cfPid), JSON.stringify({ sessionPid, cfPid, at: Date.now() }));
|
|
2259
2237
|
} catch {
|
|
2260
2238
|
}
|
|
2261
2239
|
}
|
|
@@ -2273,7 +2251,7 @@ var realProc = {
|
|
|
2273
2251
|
},
|
|
2274
2252
|
isCloudflared(pid) {
|
|
2275
2253
|
try {
|
|
2276
|
-
if (process.platform === "linux") return
|
|
2254
|
+
if (process.platform === "linux") return readFileSync3(`/proc/${pid}/cmdline`, "utf8").includes("cloudflared");
|
|
2277
2255
|
if (process.platform === "darwin") return spawnSync("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf8" }).stdout.includes("cloudflared");
|
|
2278
2256
|
} catch {
|
|
2279
2257
|
return false;
|
|
@@ -2288,14 +2266,14 @@ var realProc = {
|
|
|
2288
2266
|
}
|
|
2289
2267
|
};
|
|
2290
2268
|
function reapOrphans(dir, proc = realProc) {
|
|
2291
|
-
if (!
|
|
2269
|
+
if (!existsSync3(dir)) return 0;
|
|
2292
2270
|
let killed = 0;
|
|
2293
2271
|
for (const name of readdirSync(dir)) {
|
|
2294
2272
|
if (!/^cloudflared-\d+\.json$/.test(name)) continue;
|
|
2295
|
-
const path =
|
|
2273
|
+
const path = join4(dir, name);
|
|
2296
2274
|
let rec;
|
|
2297
2275
|
try {
|
|
2298
|
-
rec = JSON.parse(
|
|
2276
|
+
rec = JSON.parse(readFileSync3(path, "utf8"));
|
|
2299
2277
|
} catch {
|
|
2300
2278
|
rmSync2(path, { force: true });
|
|
2301
2279
|
continue;
|
|
@@ -2686,13 +2664,14 @@ async function runSession(specs, source, flags) {
|
|
|
2686
2664
|
const config = loadConfig();
|
|
2687
2665
|
if (!config.authtoken) fail2(LOGIN_HINT);
|
|
2688
2666
|
const api = createApiClient({ baseUrl: apiUrl(), token: config.authtoken });
|
|
2667
|
+
const update = checkForUpdate(CLI_VERSION);
|
|
2689
2668
|
let cloudflaredPath;
|
|
2690
2669
|
try {
|
|
2691
2670
|
cloudflaredPath = process.env.DUTO_CLOUDFLARED || await ensureCloudflaredWithProgress((s) => process.stdout.write(s));
|
|
2692
2671
|
} catch (err) {
|
|
2693
2672
|
fail2(err instanceof Error ? err.message : String(err));
|
|
2694
2673
|
}
|
|
2695
|
-
const session = new Session({ api, deviceId: config.deviceId, deviceName: hostname2(), cloudflaredPath, source, runDir:
|
|
2674
|
+
const session = new Session({ api, deviceId: config.deviceId, deviceName: hostname2(), cloudflaredPath, source, runDir: join5(dutoHome(), "run") });
|
|
2696
2675
|
let inspector = null;
|
|
2697
2676
|
let inspectorError;
|
|
2698
2677
|
if (flags.inspect !== false) {
|
|
@@ -2703,14 +2682,15 @@ async function runSession(specs, source, flags) {
|
|
|
2703
2682
|
}
|
|
2704
2683
|
}
|
|
2705
2684
|
if (wantsUi(flags)) {
|
|
2706
|
-
const { runFullscreen } = await import("./fullscreen-
|
|
2707
|
-
const code = await runFullscreen(session, specs,
|
|
2685
|
+
const { runFullscreen } = await import("./fullscreen-AI6PC7RL.js");
|
|
2686
|
+
const code = await runFullscreen(session, specs, CLI_VERSION, inspector, inspectorError, update);
|
|
2708
2687
|
await inspector?.close().catch(() => void 0);
|
|
2709
2688
|
process.exit(code);
|
|
2710
2689
|
}
|
|
2711
|
-
console.log(`${mark()} ${paint("muted", `${
|
|
2690
|
+
console.log(`${mark()} ${paint("muted", `${CLI_VERSION} \xB7 ${source} \xB7 Ctrl+C to stop`)}`);
|
|
2712
2691
|
if (inspector) console.log(paint("muted", `inspector: ${inspector.openUrl}`));
|
|
2713
2692
|
if (inspectorError) console.log(warn(`inspector failed to start: ${inspectorError}`));
|
|
2693
|
+
void update.then((latest) => latest && console.log(warn(updateLine(latest))));
|
|
2714
2694
|
const stopLog = runPlain(session, (l) => console.log(l));
|
|
2715
2695
|
const stopped = new Promise((resolve) => {
|
|
2716
2696
|
const onSignal = () => {
|
|
@@ -2836,7 +2816,7 @@ cli.command("status", "Show your account and live tunnels").action(async () => {
|
|
|
2836
2816
|
}
|
|
2837
2817
|
});
|
|
2838
2818
|
cli.help();
|
|
2839
|
-
cli.version(
|
|
2819
|
+
cli.version(CLI_VERSION);
|
|
2840
2820
|
try {
|
|
2841
2821
|
cli.parse(process.argv, { run: false });
|
|
2842
2822
|
await cli.runMatchedCommand();
|