buncargo 3.2.4 → 3.2.5
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/dist/cli/bin.js +5 -5
- package/dist/cli/index.js +2 -2
- package/dist/core/quick-tunnel/cloudflared-process.d.ts +2 -0
- package/dist/core/quick-tunnel/constants.d.ts +2 -0
- package/dist/core/quick-tunnel/index.d.ts +4 -3
- package/dist/environment/index.js +2 -2
- package/dist/index-39s6ez1q.js +250 -0
- package/dist/index-6att53sd.js +250 -0
- package/dist/index-94kgbw4m.js +72 -0
- package/dist/index-96q4yh56.js +72 -0
- package/dist/index-bgcx898h.js +451 -0
- package/dist/index-c28x1pjb.js +250 -0
- package/dist/index-gfs10vb8.js +389 -0
- package/dist/index-pbwvaz4v.js +666 -0
- package/dist/index-pmbmwg3x.js +72 -0
- package/dist/index-pt8t9tkg.js +389 -0
- package/dist/index-qnpd5fn5.js +666 -0
- package/dist/index-qtprmjbm.js +399 -0
- package/dist/index-thsdxz7m.js +250 -0
- package/dist/index-ymdvr5sn.js +666 -0
- package/dist/index-yw46g4tr.js +666 -0
- package/dist/index-znaek8z2.js +72 -0
- package/dist/index.js +4 -4
- package/dist/loader/index.js +3 -3
- package/package.json +1 -1
- package/src/core/quick-tunnel/cloudflared-process.ts +40 -2
- package/src/core/quick-tunnel/constants.ts +14 -1
- package/src/core/quick-tunnel/index.ts +53 -49
- package/src/core/quick-tunnel/install.ts +1 -1
- package/src/core/tunnel.ts +1 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createDevEnvironment
|
|
3
|
+
} from "./index-ymdvr5sn.js";
|
|
4
|
+
|
|
5
|
+
// src/loader/cache.ts
|
|
6
|
+
var cachedEnv = null;
|
|
7
|
+
function setCachedDevEnv(env) {
|
|
8
|
+
cachedEnv = env;
|
|
9
|
+
}
|
|
10
|
+
function getCachedDevEnv() {
|
|
11
|
+
return cachedEnv;
|
|
12
|
+
}
|
|
13
|
+
function clearDevEnvCache() {
|
|
14
|
+
cachedEnv = null;
|
|
15
|
+
}
|
|
16
|
+
// src/loader/find-config-file.ts
|
|
17
|
+
import { existsSync } from "node:fs";
|
|
18
|
+
import { dirname, join } from "node:path";
|
|
19
|
+
var CONFIG_FILES = [
|
|
20
|
+
"dev.config.ts",
|
|
21
|
+
"dev.config.js",
|
|
22
|
+
"dev-tools.config.ts",
|
|
23
|
+
"dev-tools.config.js"
|
|
24
|
+
];
|
|
25
|
+
function findConfigFile(startDir) {
|
|
26
|
+
let currentDir = startDir;
|
|
27
|
+
while (true) {
|
|
28
|
+
for (const file of CONFIG_FILES) {
|
|
29
|
+
const configPath = join(currentDir, file);
|
|
30
|
+
if (existsSync(configPath)) {
|
|
31
|
+
return configPath;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const parentDir = dirname(currentDir);
|
|
35
|
+
if (parentDir === currentDir) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
currentDir = parentDir;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// src/loader/load-dev-env.ts
|
|
42
|
+
async function loadDevEnv(options) {
|
|
43
|
+
if (!options?.reload) {
|
|
44
|
+
const cached = getCachedDevEnv();
|
|
45
|
+
if (cached)
|
|
46
|
+
return cached;
|
|
47
|
+
}
|
|
48
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
49
|
+
const configPath = findConfigFile(cwd);
|
|
50
|
+
if (configPath) {
|
|
51
|
+
const mod = await import(configPath);
|
|
52
|
+
const config = mod.default;
|
|
53
|
+
if (!config?.projectPrefix || !config?.services) {
|
|
54
|
+
throw new Error(`Invalid config in "${configPath}". Use defineDevConfig() and export as default.`);
|
|
55
|
+
}
|
|
56
|
+
const env = createDevEnvironment(config);
|
|
57
|
+
setCachedDevEnv(env);
|
|
58
|
+
return env;
|
|
59
|
+
}
|
|
60
|
+
throw new Error("No config file found. Create dev.config.ts with: export default defineDevConfig({ ... })");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/loader/index.ts
|
|
64
|
+
function getDevEnv() {
|
|
65
|
+
const env = getCachedDevEnv();
|
|
66
|
+
if (!env) {
|
|
67
|
+
throw new Error("Dev environment not loaded. Call loadDevEnv() first.");
|
|
68
|
+
}
|
|
69
|
+
return env;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { clearDevEnvCache, CONFIG_FILES, findConfigFile, loadDevEnv, getDevEnv };
|
package/dist/index.js
CHANGED
|
@@ -3,15 +3,15 @@ import {
|
|
|
3
3
|
getFlagValue,
|
|
4
4
|
hasFlag,
|
|
5
5
|
runCli
|
|
6
|
-
} from "./index-
|
|
6
|
+
} from "./index-c28x1pjb.js";
|
|
7
7
|
import {
|
|
8
8
|
clearDevEnvCache,
|
|
9
9
|
getDevEnv,
|
|
10
10
|
loadDevEnv
|
|
11
|
-
} from "./index-
|
|
11
|
+
} from "./index-znaek8z2.js";
|
|
12
12
|
import {
|
|
13
13
|
createDevEnvironment
|
|
14
|
-
} from "./index-
|
|
14
|
+
} from "./index-ymdvr5sn.js";
|
|
15
15
|
import {
|
|
16
16
|
DOCKER_NOT_RUNNING_MESSAGE,
|
|
17
17
|
MAX_ATTEMPTS,
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
resolveExposeTargets,
|
|
34
34
|
startPublicTunnels,
|
|
35
35
|
stopPublicTunnels
|
|
36
|
-
} from "./index-
|
|
36
|
+
} from "./index-bgcx898h.js";
|
|
37
37
|
import {
|
|
38
38
|
getHeartbeatFile,
|
|
39
39
|
getWatchdogPidFile,
|
package/dist/loader/index.js
CHANGED
|
@@ -4,11 +4,11 @@ import {
|
|
|
4
4
|
findConfigFile,
|
|
5
5
|
getDevEnv,
|
|
6
6
|
loadDevEnv
|
|
7
|
-
} from "../index-
|
|
8
|
-
import"../index-
|
|
7
|
+
} from "../index-znaek8z2.js";
|
|
8
|
+
import"../index-ymdvr5sn.js";
|
|
9
9
|
import"../index-twwcjn9p.js";
|
|
10
10
|
import"../index-5t9jxqm0.js";
|
|
11
|
-
import"../index-
|
|
11
|
+
import"../index-bgcx898h.js";
|
|
12
12
|
import"../index-mam0bcyz.js";
|
|
13
13
|
import"../index-mm412dkp.js";
|
|
14
14
|
import"../index-fkgqg6w2.js";
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Derived from unjs/untun (MIT), originally forked from node-cloudflared.
|
|
4
4
|
*/
|
|
5
5
|
import { type ChildProcess, spawn } from "node:child_process";
|
|
6
|
-
import {
|
|
6
|
+
import { resolvedCloudflaredBinPath } from "./constants";
|
|
7
7
|
|
|
8
8
|
/** Primary: ASCII box line from cloudflared (`| https://… |`). */
|
|
9
9
|
const urlRegexPipe = /\|\s+(https?:\/\/\S+)/;
|
|
@@ -13,6 +13,16 @@ const urlRegexTryCloudflare =
|
|
|
13
13
|
|
|
14
14
|
const MAX_CAPTURED_LOG = 24_000;
|
|
15
15
|
|
|
16
|
+
/** Default 30s; set `BUNCARGO_QUICK_TUNNEL_TIMEOUT_MS=0` to disable. */
|
|
17
|
+
export function resolveQuickTunnelUrlTimeoutMs(): number {
|
|
18
|
+
const raw = process.env.BUNCARGO_QUICK_TUNNEL_TIMEOUT_MS;
|
|
19
|
+
if (raw === undefined || raw === "") {
|
|
20
|
+
return 30_000;
|
|
21
|
+
}
|
|
22
|
+
const n = Number.parseInt(raw, 10);
|
|
23
|
+
return Number.isFinite(n) && n >= 0 ? n : 30_000;
|
|
24
|
+
}
|
|
25
|
+
|
|
16
26
|
export function parseQuickTunnelUrlFromOutput(log: string): string | null {
|
|
17
27
|
const pipe = log.match(urlRegexPipe);
|
|
18
28
|
if (pipe?.[1]) {
|
|
@@ -43,7 +53,8 @@ export function startCloudflaredTunnel(
|
|
|
43
53
|
args.push("--url", "localhost:8080");
|
|
44
54
|
}
|
|
45
55
|
|
|
46
|
-
const
|
|
56
|
+
const binPath = resolvedCloudflaredBinPath();
|
|
57
|
+
const child = spawn(binPath, args, {
|
|
47
58
|
stdio: ["ignore", "pipe", "pipe"],
|
|
48
59
|
});
|
|
49
60
|
|
|
@@ -55,19 +66,46 @@ export function startCloudflaredTunnel(
|
|
|
55
66
|
let settled = false;
|
|
56
67
|
let urlResolver!: (value: string | PromiseLike<string>) => void;
|
|
57
68
|
let urlRejector!: (reason: unknown) => void;
|
|
69
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
70
|
+
|
|
71
|
+
const clearUrlTimeout = () => {
|
|
72
|
+
if (timeoutId !== undefined) {
|
|
73
|
+
clearTimeout(timeoutId);
|
|
74
|
+
timeoutId = undefined;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
58
78
|
const url = new Promise<string>((resolve, reject) => {
|
|
59
79
|
urlResolver = (v) => {
|
|
60
80
|
if (!settled) {
|
|
61
81
|
settled = true;
|
|
82
|
+
clearUrlTimeout();
|
|
62
83
|
resolve(v);
|
|
63
84
|
}
|
|
64
85
|
};
|
|
65
86
|
urlRejector = (e) => {
|
|
66
87
|
if (!settled) {
|
|
67
88
|
settled = true;
|
|
89
|
+
clearUrlTimeout();
|
|
68
90
|
reject(e);
|
|
69
91
|
}
|
|
70
92
|
};
|
|
93
|
+
|
|
94
|
+
const timeoutMs = resolveQuickTunnelUrlTimeoutMs();
|
|
95
|
+
if (timeoutMs > 0) {
|
|
96
|
+
timeoutId = setTimeout(() => {
|
|
97
|
+
try {
|
|
98
|
+
child.kill("SIGINT");
|
|
99
|
+
} catch {
|
|
100
|
+
/* ignore */
|
|
101
|
+
}
|
|
102
|
+
urlRejector(
|
|
103
|
+
new Error(
|
|
104
|
+
`quick tunnel URL timed out after ${timeoutMs}ms (no public URL from cloudflared)`,
|
|
105
|
+
),
|
|
106
|
+
);
|
|
107
|
+
}, timeoutMs);
|
|
108
|
+
}
|
|
71
109
|
});
|
|
72
110
|
|
|
73
111
|
const log: { buf: string } = { buf: "" };
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
* Derived from unjs/untun (MIT), originally forked from node-cloudflared.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
6
7
|
import { tmpdir } from "node:os";
|
|
7
8
|
import path from "node:path";
|
|
8
9
|
|
|
9
10
|
export const CLOUDFLARED_VERSION =
|
|
10
|
-
process.env.CLOUDFLARED_VERSION || "
|
|
11
|
+
process.env.CLOUDFLARED_VERSION || "2026.3.0";
|
|
11
12
|
|
|
12
13
|
export const RELEASE_BASE =
|
|
13
14
|
"https://github.com/cloudflare/cloudflared/releases/";
|
|
@@ -21,6 +22,18 @@ export const cloudflaredBinPath = path.join(
|
|
|
21
22
|
: `cloudflared.${CLOUDFLARED_VERSION}`,
|
|
22
23
|
);
|
|
23
24
|
|
|
25
|
+
/** Spawn/install target: optional `BUNCARGO_CLOUDFLARED_PATH` overrides the bundled cache path. */
|
|
26
|
+
export function resolvedCloudflaredBinPath(): string {
|
|
27
|
+
const override = process.env.BUNCARGO_CLOUDFLARED_PATH?.trim();
|
|
28
|
+
if (override) {
|
|
29
|
+
if (!existsSync(override)) {
|
|
30
|
+
throw new Error(`BUNCARGO_CLOUDFLARED_PATH does not exist: ${override}`);
|
|
31
|
+
}
|
|
32
|
+
return path.resolve(override);
|
|
33
|
+
}
|
|
34
|
+
return cloudflaredBinPath;
|
|
35
|
+
}
|
|
36
|
+
|
|
24
37
|
export const cloudflaredNotice = `
|
|
25
38
|
🔥 Your installation of cloudflared software constitutes a symbol of your signature
|
|
26
39
|
indicating that you accept the terms of the Cloudflare License, Terms and Privacy Policy.
|
|
@@ -3,39 +3,74 @@
|
|
|
3
3
|
* License / download flow adapted from unjs/untun (MIT).
|
|
4
4
|
*/
|
|
5
5
|
import { existsSync } from "node:fs";
|
|
6
|
-
import { createInterface } from "node:readline";
|
|
7
6
|
import { sleep } from "../utils";
|
|
8
7
|
import { startCloudflaredTunnel } from "./cloudflared-process";
|
|
9
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
cloudflaredBinPath,
|
|
10
|
+
cloudflaredNotice,
|
|
11
|
+
resolvedCloudflaredBinPath,
|
|
12
|
+
} from "./constants";
|
|
10
13
|
import { installCloudflared } from "./install";
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
function resolveMaxQuickTunnelAttempts(): number {
|
|
16
|
+
const raw = process.env.BUNCARGO_QUICK_TUNNEL_MAX_ATTEMPTS;
|
|
17
|
+
if (raw === undefined || raw === "") {
|
|
18
|
+
return 5;
|
|
19
|
+
}
|
|
20
|
+
const n = Number.parseInt(raw, 10);
|
|
21
|
+
return Number.isFinite(n) && n >= 1 ? n : 5;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function resolveQuickTunnelRetryBaseMs(): number {
|
|
25
|
+
const raw = process.env.BUNCARGO_QUICK_TUNNEL_RETRY_BASE_MS;
|
|
26
|
+
if (raw === undefined || raw === "") {
|
|
27
|
+
return 2000;
|
|
28
|
+
}
|
|
29
|
+
const n = Number.parseInt(raw, 10);
|
|
30
|
+
return Number.isFinite(n) && n >= 0 ? n : 2000;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function usesBundledCloudflaredCache(): boolean {
|
|
34
|
+
return !process.env.BUNCARGO_CLOUDFLARED_PATH?.trim();
|
|
35
|
+
}
|
|
13
36
|
|
|
14
|
-
|
|
37
|
+
/** True when trycloudflare.com is overloaded / rate-limited or returns non-JSON (cloudflared then errors on unmarshal). */
|
|
38
|
+
export function isRetryableQuickTunnelError(message: string): boolean {
|
|
15
39
|
return (
|
|
16
40
|
message.includes("429") ||
|
|
17
41
|
message.includes("Too Many Requests") ||
|
|
18
|
-
message.includes('status_code="429')
|
|
42
|
+
message.includes('status_code="429') ||
|
|
43
|
+
// Plain-text "error" or HTML error pages — see cloudflare/cloudflared#972
|
|
44
|
+
message.includes("failed to unmarshal quick Tunnel") ||
|
|
45
|
+
message.includes("failed to unmarshall quick Tunnel") ||
|
|
46
|
+
message.includes("Error unmarshaling QuickTunnel") ||
|
|
47
|
+
message.includes("invalid character '<'") ||
|
|
48
|
+
message.includes("quick tunnel URL timed out")
|
|
19
49
|
);
|
|
20
50
|
}
|
|
21
51
|
|
|
22
52
|
async function startCloudflaredTunnelWithRetry(
|
|
23
53
|
cfArgs: Record<string, string | number | null>,
|
|
24
54
|
): Promise<ReturnType<typeof startCloudflaredTunnel>> {
|
|
25
|
-
|
|
55
|
+
const maxAttempts = resolveMaxQuickTunnelAttempts();
|
|
56
|
+
const baseMs = resolveQuickTunnelRetryBaseMs();
|
|
57
|
+
|
|
58
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
26
59
|
const tunnel = startCloudflaredTunnel(cfArgs);
|
|
27
60
|
try {
|
|
28
61
|
await tunnel.url;
|
|
29
62
|
return tunnel;
|
|
30
63
|
} catch (e) {
|
|
64
|
+
try {
|
|
65
|
+
tunnel.stop();
|
|
66
|
+
} catch {
|
|
67
|
+
/* ignore */
|
|
68
|
+
}
|
|
31
69
|
const msg = String(e);
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
isQuickTunnelRateLimitedError(msg)
|
|
35
|
-
) {
|
|
36
|
-
const delayMs = 2000 * attempt;
|
|
70
|
+
if (attempt < maxAttempts && isRetryableQuickTunnelError(msg)) {
|
|
71
|
+
const delayMs = baseMs * attempt;
|
|
37
72
|
console.log(
|
|
38
|
-
`Cloudflare quick tunnel
|
|
73
|
+
`Cloudflare quick tunnel temporarily unavailable (${attempt}/${maxAttempts}), retrying in ${delayMs}ms…`,
|
|
39
74
|
);
|
|
40
75
|
await sleep(delayMs);
|
|
41
76
|
continue;
|
|
@@ -52,7 +87,6 @@ export interface QuickTunnelOptions {
|
|
|
52
87
|
hostname?: string;
|
|
53
88
|
protocol?: "http" | "https";
|
|
54
89
|
verifyTLS?: boolean;
|
|
55
|
-
acceptCloudflareNotice?: boolean;
|
|
56
90
|
}
|
|
57
91
|
|
|
58
92
|
export interface QuickTunnel {
|
|
@@ -67,52 +101,22 @@ function resolvedLocalUrl(opts: QuickTunnelOptions): string {
|
|
|
67
101
|
);
|
|
68
102
|
}
|
|
69
103
|
|
|
70
|
-
function envAcceptsCloudflareNotice(): boolean {
|
|
71
|
-
const v = process.env.BUNCARGO_ACCEPT_CLOUDFLARE_NOTICE;
|
|
72
|
-
const u = process.env.UNTUN_ACCEPT_CLOUDFLARE_NOTICE;
|
|
73
|
-
return v === "1" || v === "true" || u === "1" || u === "true";
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async function promptInstallCloudflared(): Promise<boolean> {
|
|
77
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
return new Promise((resolve) => {
|
|
81
|
-
const rl = createInterface({
|
|
82
|
-
input: process.stdin,
|
|
83
|
-
output: process.stdout,
|
|
84
|
-
});
|
|
85
|
-
rl.question(
|
|
86
|
-
"Do you agree with the above terms and wish to install the binary from GitHub? (y/N) ",
|
|
87
|
-
(answer) => {
|
|
88
|
-
rl.close();
|
|
89
|
-
resolve(/^y(es)?$/i.test(answer.trim()));
|
|
90
|
-
},
|
|
91
|
-
);
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
104
|
/**
|
|
96
105
|
* Start a Cloudflare quick tunnel to a local HTTP(S) URL.
|
|
97
|
-
*
|
|
106
|
+
* If the cloudflared binary is missing, prints the license notice and installs it from GitHub.
|
|
98
107
|
*/
|
|
99
108
|
export async function startQuickTunnel(
|
|
100
109
|
opts: QuickTunnelOptions,
|
|
101
|
-
): Promise<QuickTunnel
|
|
110
|
+
): Promise<QuickTunnel> {
|
|
102
111
|
const url = resolvedLocalUrl(opts);
|
|
103
112
|
|
|
104
113
|
console.log(`Starting cloudflared tunnel to ${url}`);
|
|
105
114
|
|
|
106
|
-
if
|
|
115
|
+
// Resolve path first (throws if BUNCARGO_CLOUDFLARED_PATH is invalid).
|
|
116
|
+
resolvedCloudflaredBinPath();
|
|
117
|
+
|
|
118
|
+
if (usesBundledCloudflaredCache() && !existsSync(cloudflaredBinPath)) {
|
|
107
119
|
console.log(cloudflaredNotice);
|
|
108
|
-
const canInstall =
|
|
109
|
-
opts.acceptCloudflareNotice ||
|
|
110
|
-
envAcceptsCloudflareNotice() ||
|
|
111
|
-
(await promptInstallCloudflared());
|
|
112
|
-
if (!canInstall) {
|
|
113
|
-
console.error("Skipping tunnel setup.");
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
120
|
await installCloudflared();
|
|
117
121
|
}
|
|
118
122
|
|
|
@@ -20,7 +20,7 @@ const LINUX_URL: Partial<Record<NodeJS.Architecture, string>> = {
|
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
const MACOS_URL: Partial<Record<NodeJS.Architecture, string>> = {
|
|
23
|
-
arm64: "cloudflared-darwin-
|
|
23
|
+
arm64: "cloudflared-darwin-arm64.tgz",
|
|
24
24
|
x64: "cloudflared-darwin-amd64.tgz",
|
|
25
25
|
};
|
|
26
26
|
|
package/src/core/tunnel.ts
CHANGED
|
@@ -150,7 +150,7 @@ export async function startPublicTunnels(
|
|
|
150
150
|
})) as TunnelBackendResult | undefined;
|
|
151
151
|
if (tunnel === undefined) {
|
|
152
152
|
throw new Error(
|
|
153
|
-
`Tunnel for "${target.name}" could not be started (
|
|
153
|
+
`Tunnel for "${target.name}" could not be started (tunnel backend returned no instance)`,
|
|
154
154
|
);
|
|
155
155
|
}
|
|
156
156
|
const publicUrl = await resolvePublicUrl(tunnel);
|