unplugin-cloudflare-tunnel 0.1.0 → 0.1.2

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.
@@ -0,0 +1,1949 @@
1
+ import * as NodeModule from "node:module";
2
+ import { bin, install } from "cloudflared";
3
+ import * as NodeUtil from "node:util";
4
+ import * as NodeChildProcess from "node:child_process";
5
+ import { createUnplugin } from "unplugin";
6
+ import NodeFS from "node:fs/promises";
7
+ Object.freeze({ status: "aborted" });
8
+ function $constructor(name, initializer, params) {
9
+ function init(inst, def) {
10
+ if (!inst._zod) Object.defineProperty(inst, "_zod", {
11
+ value: {
12
+ def,
13
+ constr: _,
14
+ traits: /* @__PURE__ */ new Set()
15
+ },
16
+ enumerable: false
17
+ });
18
+ if (inst._zod.traits.has(name)) return;
19
+ inst._zod.traits.add(name);
20
+ initializer(inst, def);
21
+ const proto = _.prototype;
22
+ const keys = Object.keys(proto);
23
+ for (let i = 0; i < keys.length; i++) {
24
+ const k = keys[i];
25
+ if (!(k in inst)) inst[k] = proto[k].bind(inst);
26
+ }
27
+ }
28
+ const Parent = params?.Parent ?? Object;
29
+ class Definition extends Parent {}
30
+ Object.defineProperty(Definition, "name", { value: name });
31
+ function _(def) {
32
+ var _a;
33
+ const inst = params?.Parent ? new Definition() : this;
34
+ init(inst, def);
35
+ (_a = inst._zod).deferred ?? (_a.deferred = []);
36
+ for (const fn of inst._zod.deferred) fn();
37
+ return inst;
38
+ }
39
+ Object.defineProperty(_, "init", { value: init });
40
+ Object.defineProperty(_, Symbol.hasInstance, { value: (inst) => {
41
+ if (params?.Parent && inst instanceof params.Parent) return true;
42
+ return inst?._zod?.traits?.has(name);
43
+ } });
44
+ Object.defineProperty(_, "name", { value: name });
45
+ return _;
46
+ }
47
+ var $ZodAsyncError = class extends Error {
48
+ constructor() {
49
+ super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`);
50
+ }
51
+ };
52
+ const globalConfig = {};
53
+ function config(newConfig) {
54
+ if (newConfig) Object.assign(globalConfig, newConfig);
55
+ return globalConfig;
56
+ }
57
+ //#endregion
58
+ //#region node_modules/zod/v4/core/util.js
59
+ function jsonStringifyReplacer(_, value) {
60
+ if (typeof value === "bigint") return value.toString();
61
+ return value;
62
+ }
63
+ function cached(getter) {
64
+ return { get value() {
65
+ {
66
+ const value = getter();
67
+ Object.defineProperty(this, "value", { value });
68
+ return value;
69
+ }
70
+ throw new Error("cached value already set");
71
+ } };
72
+ }
73
+ function cleanRegex(source) {
74
+ const start = source.startsWith("^") ? 1 : 0;
75
+ const end = source.endsWith("$") ? source.length - 1 : source.length;
76
+ return source.slice(start, end);
77
+ }
78
+ const EVALUATING = Symbol("evaluating");
79
+ function defineLazy(object, key, getter) {
80
+ let value = void 0;
81
+ Object.defineProperty(object, key, {
82
+ get() {
83
+ if (value === EVALUATING) return;
84
+ if (value === void 0) {
85
+ value = EVALUATING;
86
+ value = getter();
87
+ }
88
+ return value;
89
+ },
90
+ set(v) {
91
+ Object.defineProperty(object, key, { value: v });
92
+ },
93
+ configurable: true
94
+ });
95
+ }
96
+ const captureStackTrace = "captureStackTrace" in Error ? Error.captureStackTrace : (..._args) => {};
97
+ function isObject(data) {
98
+ return typeof data === "object" && data !== null && !Array.isArray(data);
99
+ }
100
+ cached(() => {
101
+ if (typeof navigator !== "undefined" && navigator?.userAgent?.includes("Cloudflare")) return false;
102
+ try {
103
+ new Function("");
104
+ return true;
105
+ } catch (_) {
106
+ return false;
107
+ }
108
+ });
109
+ function clone(inst, def, params) {
110
+ const cl = new inst._zod.constr(def ?? inst._zod.def);
111
+ if (!def || params?.parent) cl._zod.parent = inst;
112
+ return cl;
113
+ }
114
+ function normalizeParams(_params) {
115
+ const params = _params;
116
+ if (!params) return {};
117
+ if (typeof params === "string") return { error: () => params };
118
+ if (params?.message !== void 0) {
119
+ if (params?.error !== void 0) throw new Error("Cannot specify both `message` and `error` params");
120
+ params.error = params.message;
121
+ }
122
+ delete params.message;
123
+ if (typeof params.error === "string") return {
124
+ ...params,
125
+ error: () => params.error
126
+ };
127
+ return params;
128
+ }
129
+ function optionalKeys(shape) {
130
+ return Object.keys(shape).filter((k) => {
131
+ return shape[k]._zod.optin === "optional" && shape[k]._zod.optout === "optional";
132
+ });
133
+ }
134
+ Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, -Number.MAX_VALUE, Number.MAX_VALUE;
135
+ function aborted(x, startIndex = 0) {
136
+ if (x.aborted === true) return true;
137
+ for (let i = startIndex; i < x.issues.length; i++) if (x.issues[i]?.continue !== true) return true;
138
+ return false;
139
+ }
140
+ function prefixIssues(path, issues) {
141
+ return issues.map((iss) => {
142
+ var _a;
143
+ (_a = iss).path ?? (_a.path = []);
144
+ iss.path.unshift(path);
145
+ return iss;
146
+ });
147
+ }
148
+ function unwrapMessage(message) {
149
+ return typeof message === "string" ? message : message?.message;
150
+ }
151
+ function finalizeIssue(iss, ctx, config) {
152
+ const full = {
153
+ ...iss,
154
+ path: iss.path ?? []
155
+ };
156
+ if (!iss.message) full.message = unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(config.customError?.(iss)) ?? unwrapMessage(config.localeError?.(iss)) ?? "Invalid input";
157
+ delete full.inst;
158
+ delete full.continue;
159
+ if (!ctx?.reportInput) delete full.input;
160
+ return full;
161
+ }
162
+ //#endregion
163
+ //#region node_modules/zod/v4/core/errors.js
164
+ const initializer = (inst, def) => {
165
+ inst.name = "$ZodError";
166
+ Object.defineProperty(inst, "_zod", {
167
+ value: inst._zod,
168
+ enumerable: false
169
+ });
170
+ Object.defineProperty(inst, "issues", {
171
+ value: def,
172
+ enumerable: false
173
+ });
174
+ inst.message = JSON.stringify(def, jsonStringifyReplacer, 2);
175
+ Object.defineProperty(inst, "toString", {
176
+ value: () => inst.message,
177
+ enumerable: false
178
+ });
179
+ };
180
+ const $ZodError = $constructor("$ZodError", initializer);
181
+ const $ZodRealError = $constructor("$ZodError", initializer, { Parent: Error });
182
+ //#endregion
183
+ //#region node_modules/zod/v4/core/parse.js
184
+ const _parse = (_Err) => (schema, value, _ctx, _params) => {
185
+ const ctx = _ctx ? Object.assign(_ctx, { async: false }) : { async: false };
186
+ const result = schema._zod.run({
187
+ value,
188
+ issues: []
189
+ }, ctx);
190
+ if (result instanceof Promise) throw new $ZodAsyncError();
191
+ if (result.issues.length) {
192
+ const e = new (_params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
193
+ captureStackTrace(e, _params?.callee);
194
+ throw e;
195
+ }
196
+ return result.value;
197
+ };
198
+ const parse = /* @__PURE__ */ _parse($ZodRealError);
199
+ const _parseAsync = (_Err) => async (schema, value, _ctx, params) => {
200
+ const ctx = _ctx ? Object.assign(_ctx, { async: true }) : { async: true };
201
+ let result = schema._zod.run({
202
+ value,
203
+ issues: []
204
+ }, ctx);
205
+ if (result instanceof Promise) result = await result;
206
+ if (result.issues.length) {
207
+ const e = new (params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
208
+ captureStackTrace(e, params?.callee);
209
+ throw e;
210
+ }
211
+ return result.value;
212
+ };
213
+ const parseAsync = /* @__PURE__ */ _parseAsync($ZodRealError);
214
+ const _safeParse = (_Err) => (schema, value, _ctx) => {
215
+ const ctx = _ctx ? {
216
+ ..._ctx,
217
+ async: false
218
+ } : { async: false };
219
+ const result = schema._zod.run({
220
+ value,
221
+ issues: []
222
+ }, ctx);
223
+ if (result instanceof Promise) throw new $ZodAsyncError();
224
+ return result.issues.length ? {
225
+ success: false,
226
+ error: new (_Err ?? $ZodError)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())))
227
+ } : {
228
+ success: true,
229
+ data: result.value
230
+ };
231
+ };
232
+ const safeParse = /* @__PURE__ */ _safeParse($ZodRealError);
233
+ const _safeParseAsync = (_Err) => async (schema, value, _ctx) => {
234
+ const ctx = _ctx ? Object.assign(_ctx, { async: true }) : { async: true };
235
+ let result = schema._zod.run({
236
+ value,
237
+ issues: []
238
+ }, ctx);
239
+ if (result instanceof Promise) result = await result;
240
+ return result.issues.length ? {
241
+ success: false,
242
+ error: new _Err(result.issues.map((iss) => finalizeIssue(iss, ctx, config())))
243
+ } : {
244
+ success: true,
245
+ data: result.value
246
+ };
247
+ };
248
+ const safeParseAsync = /* @__PURE__ */ _safeParseAsync($ZodRealError);
249
+ const string$1 = (params) => {
250
+ const regex = params ? `[\\s\\S]{${params?.minimum ?? 0},${params?.maximum ?? ""}}` : `[\\s\\S]*`;
251
+ return new RegExp(`^${regex}$`);
252
+ };
253
+ const number$1 = /^-?\d+(?:\.\d+)?$/;
254
+ const boolean$1 = /^(?:true|false)$/i;
255
+ //#endregion
256
+ //#region node_modules/zod/v4/core/versions.js
257
+ const version = {
258
+ major: 4,
259
+ minor: 3,
260
+ patch: 6
261
+ };
262
+ //#endregion
263
+ //#region node_modules/zod/v4/core/schemas.js
264
+ const $ZodType = /* @__PURE__ */ $constructor("$ZodType", (inst, def) => {
265
+ var _a;
266
+ inst ?? (inst = {});
267
+ inst._zod.def = def;
268
+ inst._zod.bag = inst._zod.bag || {};
269
+ inst._zod.version = version;
270
+ const checks = [...inst._zod.def.checks ?? []];
271
+ if (inst._zod.traits.has("$ZodCheck")) checks.unshift(inst);
272
+ for (const ch of checks) for (const fn of ch._zod.onattach) fn(inst);
273
+ if (checks.length === 0) {
274
+ (_a = inst._zod).deferred ?? (_a.deferred = []);
275
+ inst._zod.deferred?.push(() => {
276
+ inst._zod.run = inst._zod.parse;
277
+ });
278
+ } else {
279
+ const runChecks = (payload, checks, ctx) => {
280
+ let isAborted = aborted(payload);
281
+ let asyncResult;
282
+ for (const ch of checks) {
283
+ if (ch._zod.def.when) {
284
+ if (!ch._zod.def.when(payload)) continue;
285
+ } else if (isAborted) continue;
286
+ const currLen = payload.issues.length;
287
+ const _ = ch._zod.check(payload);
288
+ if (_ instanceof Promise && ctx?.async === false) throw new $ZodAsyncError();
289
+ if (asyncResult || _ instanceof Promise) asyncResult = (asyncResult ?? Promise.resolve()).then(async () => {
290
+ await _;
291
+ if (payload.issues.length === currLen) return;
292
+ if (!isAborted) isAborted = aborted(payload, currLen);
293
+ });
294
+ else {
295
+ if (payload.issues.length === currLen) continue;
296
+ if (!isAborted) isAborted = aborted(payload, currLen);
297
+ }
298
+ }
299
+ if (asyncResult) return asyncResult.then(() => {
300
+ return payload;
301
+ });
302
+ return payload;
303
+ };
304
+ const handleCanaryResult = (canary, payload, ctx) => {
305
+ if (aborted(canary)) {
306
+ canary.aborted = true;
307
+ return canary;
308
+ }
309
+ const checkResult = runChecks(payload, checks, ctx);
310
+ if (checkResult instanceof Promise) {
311
+ if (ctx.async === false) throw new $ZodAsyncError();
312
+ return checkResult.then((checkResult) => inst._zod.parse(checkResult, ctx));
313
+ }
314
+ return inst._zod.parse(checkResult, ctx);
315
+ };
316
+ inst._zod.run = (payload, ctx) => {
317
+ if (ctx.skipChecks) return inst._zod.parse(payload, ctx);
318
+ if (ctx.direction === "backward") {
319
+ const canary = inst._zod.parse({
320
+ value: payload.value,
321
+ issues: []
322
+ }, {
323
+ ...ctx,
324
+ skipChecks: true
325
+ });
326
+ if (canary instanceof Promise) return canary.then((canary) => {
327
+ return handleCanaryResult(canary, payload, ctx);
328
+ });
329
+ return handleCanaryResult(canary, payload, ctx);
330
+ }
331
+ const result = inst._zod.parse(payload, ctx);
332
+ if (result instanceof Promise) {
333
+ if (ctx.async === false) throw new $ZodAsyncError();
334
+ return result.then((result) => runChecks(result, checks, ctx));
335
+ }
336
+ return runChecks(result, checks, ctx);
337
+ };
338
+ }
339
+ defineLazy(inst, "~standard", () => ({
340
+ validate: (value) => {
341
+ try {
342
+ const r = safeParse(inst, value);
343
+ return r.success ? { value: r.data } : { issues: r.error?.issues };
344
+ } catch (_) {
345
+ return safeParseAsync(inst, value).then((r) => r.success ? { value: r.data } : { issues: r.error?.issues });
346
+ }
347
+ },
348
+ vendor: "zod",
349
+ version: 1
350
+ }));
351
+ });
352
+ const $ZodString = /* @__PURE__ */ $constructor("$ZodString", (inst, def) => {
353
+ $ZodType.init(inst, def);
354
+ inst._zod.pattern = [...inst?._zod.bag?.patterns ?? []].pop() ?? string$1(inst._zod.bag);
355
+ inst._zod.parse = (payload, _) => {
356
+ if (def.coerce) try {
357
+ payload.value = String(payload.value);
358
+ } catch (_) {}
359
+ if (typeof payload.value === "string") return payload;
360
+ payload.issues.push({
361
+ expected: "string",
362
+ code: "invalid_type",
363
+ input: payload.value,
364
+ inst
365
+ });
366
+ return payload;
367
+ };
368
+ });
369
+ const $ZodNumber = /* @__PURE__ */ $constructor("$ZodNumber", (inst, def) => {
370
+ $ZodType.init(inst, def);
371
+ inst._zod.pattern = inst._zod.bag.pattern ?? number$1;
372
+ inst._zod.parse = (payload, _ctx) => {
373
+ if (def.coerce) try {
374
+ payload.value = Number(payload.value);
375
+ } catch (_) {}
376
+ const input = payload.value;
377
+ if (typeof input === "number" && !Number.isNaN(input) && Number.isFinite(input)) return payload;
378
+ const received = typeof input === "number" ? Number.isNaN(input) ? "NaN" : !Number.isFinite(input) ? "Infinity" : void 0 : void 0;
379
+ payload.issues.push({
380
+ expected: "number",
381
+ code: "invalid_type",
382
+ input,
383
+ inst,
384
+ ...received ? { received } : {}
385
+ });
386
+ return payload;
387
+ };
388
+ });
389
+ const $ZodBoolean = /* @__PURE__ */ $constructor("$ZodBoolean", (inst, def) => {
390
+ $ZodType.init(inst, def);
391
+ inst._zod.pattern = boolean$1;
392
+ inst._zod.parse = (payload, _ctx) => {
393
+ if (def.coerce) try {
394
+ payload.value = Boolean(payload.value);
395
+ } catch (_) {}
396
+ const input = payload.value;
397
+ if (typeof input === "boolean") return payload;
398
+ payload.issues.push({
399
+ expected: "boolean",
400
+ code: "invalid_type",
401
+ input,
402
+ inst
403
+ });
404
+ return payload;
405
+ };
406
+ });
407
+ const $ZodAny = /* @__PURE__ */ $constructor("$ZodAny", (inst, def) => {
408
+ $ZodType.init(inst, def);
409
+ inst._zod.parse = (payload) => payload;
410
+ });
411
+ const $ZodUnknown = /* @__PURE__ */ $constructor("$ZodUnknown", (inst, def) => {
412
+ $ZodType.init(inst, def);
413
+ inst._zod.parse = (payload) => payload;
414
+ });
415
+ function handleArrayResult(result, final, index) {
416
+ if (result.issues.length) final.issues.push(...prefixIssues(index, result.issues));
417
+ final.value[index] = result.value;
418
+ }
419
+ const $ZodArray = /* @__PURE__ */ $constructor("$ZodArray", (inst, def) => {
420
+ $ZodType.init(inst, def);
421
+ inst._zod.parse = (payload, ctx) => {
422
+ const input = payload.value;
423
+ if (!Array.isArray(input)) {
424
+ payload.issues.push({
425
+ expected: "array",
426
+ code: "invalid_type",
427
+ input,
428
+ inst
429
+ });
430
+ return payload;
431
+ }
432
+ payload.value = Array(input.length);
433
+ const proms = [];
434
+ for (let i = 0; i < input.length; i++) {
435
+ const item = input[i];
436
+ const result = def.element._zod.run({
437
+ value: item,
438
+ issues: []
439
+ }, ctx);
440
+ if (result instanceof Promise) proms.push(result.then((result) => handleArrayResult(result, payload, i)));
441
+ else handleArrayResult(result, payload, i);
442
+ }
443
+ if (proms.length) return Promise.all(proms).then(() => payload);
444
+ return payload;
445
+ };
446
+ });
447
+ function handlePropertyResult(result, final, key, input, isOptionalOut) {
448
+ if (result.issues.length) {
449
+ if (isOptionalOut && !(key in input)) return;
450
+ final.issues.push(...prefixIssues(key, result.issues));
451
+ }
452
+ if (result.value === void 0) {
453
+ if (key in input) final.value[key] = void 0;
454
+ } else final.value[key] = result.value;
455
+ }
456
+ function normalizeDef(def) {
457
+ const keys = Object.keys(def.shape);
458
+ for (const k of keys) if (!def.shape?.[k]?._zod?.traits?.has("$ZodType")) throw new Error(`Invalid element at key "${k}": expected a Zod schema`);
459
+ const okeys = optionalKeys(def.shape);
460
+ return {
461
+ ...def,
462
+ keys,
463
+ keySet: new Set(keys),
464
+ numKeys: keys.length,
465
+ optionalKeys: new Set(okeys)
466
+ };
467
+ }
468
+ function handleCatchall(proms, input, payload, ctx, def, inst) {
469
+ const unrecognized = [];
470
+ const keySet = def.keySet;
471
+ const _catchall = def.catchall._zod;
472
+ const t = _catchall.def.type;
473
+ const isOptionalOut = _catchall.optout === "optional";
474
+ for (const key in input) {
475
+ if (keySet.has(key)) continue;
476
+ if (t === "never") {
477
+ unrecognized.push(key);
478
+ continue;
479
+ }
480
+ const r = _catchall.run({
481
+ value: input[key],
482
+ issues: []
483
+ }, ctx);
484
+ if (r instanceof Promise) proms.push(r.then((r) => handlePropertyResult(r, payload, key, input, isOptionalOut)));
485
+ else handlePropertyResult(r, payload, key, input, isOptionalOut);
486
+ }
487
+ if (unrecognized.length) payload.issues.push({
488
+ code: "unrecognized_keys",
489
+ keys: unrecognized,
490
+ input,
491
+ inst
492
+ });
493
+ if (!proms.length) return payload;
494
+ return Promise.all(proms).then(() => {
495
+ return payload;
496
+ });
497
+ }
498
+ const $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
499
+ $ZodType.init(inst, def);
500
+ if (!Object.getOwnPropertyDescriptor(def, "shape")?.get) {
501
+ const sh = def.shape;
502
+ Object.defineProperty(def, "shape", { get: () => {
503
+ const newSh = { ...sh };
504
+ Object.defineProperty(def, "shape", { value: newSh });
505
+ return newSh;
506
+ } });
507
+ }
508
+ const _normalized = cached(() => normalizeDef(def));
509
+ defineLazy(inst._zod, "propValues", () => {
510
+ const shape = def.shape;
511
+ const propValues = {};
512
+ for (const key in shape) {
513
+ const field = shape[key]._zod;
514
+ if (field.values) {
515
+ propValues[key] ?? (propValues[key] = /* @__PURE__ */ new Set());
516
+ for (const v of field.values) propValues[key].add(v);
517
+ }
518
+ }
519
+ return propValues;
520
+ });
521
+ const isObject$2 = isObject;
522
+ const catchall = def.catchall;
523
+ let value;
524
+ inst._zod.parse = (payload, ctx) => {
525
+ value ?? (value = _normalized.value);
526
+ const input = payload.value;
527
+ if (!isObject$2(input)) {
528
+ payload.issues.push({
529
+ expected: "object",
530
+ code: "invalid_type",
531
+ input,
532
+ inst
533
+ });
534
+ return payload;
535
+ }
536
+ payload.value = {};
537
+ const proms = [];
538
+ const shape = value.shape;
539
+ for (const key of value.keys) {
540
+ const el = shape[key];
541
+ const isOptionalOut = el._zod.optout === "optional";
542
+ const r = el._zod.run({
543
+ value: input[key],
544
+ issues: []
545
+ }, ctx);
546
+ if (r instanceof Promise) proms.push(r.then((r) => handlePropertyResult(r, payload, key, input, isOptionalOut)));
547
+ else handlePropertyResult(r, payload, key, input, isOptionalOut);
548
+ }
549
+ if (!catchall) return proms.length ? Promise.all(proms).then(() => payload) : payload;
550
+ return handleCatchall(proms, input, payload, ctx, _normalized.value, inst);
551
+ };
552
+ });
553
+ function handleOptionalResult(result, input) {
554
+ if (result.issues.length && input === void 0) return {
555
+ issues: [],
556
+ value: void 0
557
+ };
558
+ return result;
559
+ }
560
+ const $ZodOptional = /* @__PURE__ */ $constructor("$ZodOptional", (inst, def) => {
561
+ $ZodType.init(inst, def);
562
+ inst._zod.optin = "optional";
563
+ inst._zod.optout = "optional";
564
+ defineLazy(inst._zod, "values", () => {
565
+ return def.innerType._zod.values ? new Set([...def.innerType._zod.values, void 0]) : void 0;
566
+ });
567
+ defineLazy(inst._zod, "pattern", () => {
568
+ const pattern = def.innerType._zod.pattern;
569
+ return pattern ? new RegExp(`^(${cleanRegex(pattern.source)})?$`) : void 0;
570
+ });
571
+ inst._zod.parse = (payload, ctx) => {
572
+ if (def.innerType._zod.optin === "optional") {
573
+ const result = def.innerType._zod.run(payload, ctx);
574
+ if (result instanceof Promise) return result.then((r) => handleOptionalResult(r, payload.value));
575
+ return handleOptionalResult(result, payload.value);
576
+ }
577
+ if (payload.value === void 0) return payload;
578
+ return def.innerType._zod.run(payload, ctx);
579
+ };
580
+ });
581
+ const $ZodNullable = /* @__PURE__ */ $constructor("$ZodNullable", (inst, def) => {
582
+ $ZodType.init(inst, def);
583
+ defineLazy(inst._zod, "optin", () => def.innerType._zod.optin);
584
+ defineLazy(inst._zod, "optout", () => def.innerType._zod.optout);
585
+ defineLazy(inst._zod, "pattern", () => {
586
+ const pattern = def.innerType._zod.pattern;
587
+ return pattern ? new RegExp(`^(${cleanRegex(pattern.source)}|null)$`) : void 0;
588
+ });
589
+ defineLazy(inst._zod, "values", () => {
590
+ return def.innerType._zod.values ? new Set([...def.innerType._zod.values, null]) : void 0;
591
+ });
592
+ inst._zod.parse = (payload, ctx) => {
593
+ if (payload.value === null) return payload;
594
+ return def.innerType._zod.run(payload, ctx);
595
+ };
596
+ });
597
+ //#endregion
598
+ //#region node_modules/zod/v4/core/registries.js
599
+ var _a;
600
+ var $ZodRegistry = class {
601
+ constructor() {
602
+ this._map = /* @__PURE__ */ new WeakMap();
603
+ this._idmap = /* @__PURE__ */ new Map();
604
+ }
605
+ add(schema, ..._meta) {
606
+ const meta = _meta[0];
607
+ this._map.set(schema, meta);
608
+ if (meta && typeof meta === "object" && "id" in meta) this._idmap.set(meta.id, schema);
609
+ return this;
610
+ }
611
+ clear() {
612
+ this._map = /* @__PURE__ */ new WeakMap();
613
+ this._idmap = /* @__PURE__ */ new Map();
614
+ return this;
615
+ }
616
+ remove(schema) {
617
+ const meta = this._map.get(schema);
618
+ if (meta && typeof meta === "object" && "id" in meta) this._idmap.delete(meta.id);
619
+ this._map.delete(schema);
620
+ return this;
621
+ }
622
+ get(schema) {
623
+ const p = schema._zod.parent;
624
+ if (p) {
625
+ const pm = { ...this.get(p) ?? {} };
626
+ delete pm.id;
627
+ const f = {
628
+ ...pm,
629
+ ...this._map.get(schema)
630
+ };
631
+ return Object.keys(f).length ? f : void 0;
632
+ }
633
+ return this._map.get(schema);
634
+ }
635
+ has(schema) {
636
+ return this._map.has(schema);
637
+ }
638
+ };
639
+ function registry() {
640
+ return new $ZodRegistry();
641
+ }
642
+ (_a = globalThis).__zod_globalRegistry ?? (_a.__zod_globalRegistry = registry());
643
+ globalThis.__zod_globalRegistry;
644
+ //#endregion
645
+ //#region node_modules/zod/v4/core/api.js
646
+ /* @__NO_SIDE_EFFECTS__ */
647
+ function _string(Class, params) {
648
+ return new Class({
649
+ type: "string",
650
+ ...normalizeParams(params)
651
+ });
652
+ }
653
+ /* @__NO_SIDE_EFFECTS__ */
654
+ function _number(Class, params) {
655
+ return new Class({
656
+ type: "number",
657
+ checks: [],
658
+ ...normalizeParams(params)
659
+ });
660
+ }
661
+ /* @__NO_SIDE_EFFECTS__ */
662
+ function _boolean(Class, params) {
663
+ return new Class({
664
+ type: "boolean",
665
+ ...normalizeParams(params)
666
+ });
667
+ }
668
+ /* @__NO_SIDE_EFFECTS__ */
669
+ function _any(Class) {
670
+ return new Class({ type: "any" });
671
+ }
672
+ /* @__NO_SIDE_EFFECTS__ */
673
+ function _unknown(Class) {
674
+ return new Class({ type: "unknown" });
675
+ }
676
+ //#endregion
677
+ //#region node_modules/zod/v4/mini/schemas.js
678
+ const ZodMiniType = /* @__PURE__ */ $constructor("ZodMiniType", (inst, def) => {
679
+ if (!inst._zod) throw new Error("Uninitialized schema in ZodMiniType.");
680
+ $ZodType.init(inst, def);
681
+ inst.def = def;
682
+ inst.type = def.type;
683
+ inst.parse = (data, params) => parse(inst, data, params, { callee: inst.parse });
684
+ inst.safeParse = (data, params) => safeParse(inst, data, params);
685
+ inst.parseAsync = async (data, params) => parseAsync(inst, data, params, { callee: inst.parseAsync });
686
+ inst.safeParseAsync = async (data, params) => safeParseAsync(inst, data, params);
687
+ inst.check = (...checks) => {
688
+ return inst.clone({
689
+ ...def,
690
+ checks: [...def.checks ?? [], ...checks.map((ch) => typeof ch === "function" ? { _zod: {
691
+ check: ch,
692
+ def: { check: "custom" },
693
+ onattach: []
694
+ } } : ch)]
695
+ }, { parent: true });
696
+ };
697
+ inst.with = inst.check;
698
+ inst.clone = (_def, params) => clone(inst, _def, params);
699
+ inst.brand = () => inst;
700
+ inst.register = ((reg, meta) => {
701
+ reg.add(inst, meta);
702
+ return inst;
703
+ });
704
+ inst.apply = (fn) => fn(inst);
705
+ });
706
+ const ZodMiniString = /* @__PURE__ */ $constructor("ZodMiniString", (inst, def) => {
707
+ $ZodString.init(inst, def);
708
+ ZodMiniType.init(inst, def);
709
+ });
710
+ /* @__NO_SIDE_EFFECTS__ */
711
+ function string(params) {
712
+ return /* @__PURE__ */ _string(ZodMiniString, params);
713
+ }
714
+ const ZodMiniNumber = /* @__PURE__ */ $constructor("ZodMiniNumber", (inst, def) => {
715
+ $ZodNumber.init(inst, def);
716
+ ZodMiniType.init(inst, def);
717
+ });
718
+ /* @__NO_SIDE_EFFECTS__ */
719
+ function number(params) {
720
+ return /* @__PURE__ */ _number(ZodMiniNumber, params);
721
+ }
722
+ const ZodMiniBoolean = /* @__PURE__ */ $constructor("ZodMiniBoolean", (inst, def) => {
723
+ $ZodBoolean.init(inst, def);
724
+ ZodMiniType.init(inst, def);
725
+ });
726
+ /* @__NO_SIDE_EFFECTS__ */
727
+ function boolean(params) {
728
+ return /* @__PURE__ */ _boolean(ZodMiniBoolean, params);
729
+ }
730
+ const ZodMiniAny = /* @__PURE__ */ $constructor("ZodMiniAny", (inst, def) => {
731
+ $ZodAny.init(inst, def);
732
+ ZodMiniType.init(inst, def);
733
+ });
734
+ /* @__NO_SIDE_EFFECTS__ */
735
+ function any() {
736
+ return /* @__PURE__ */ _any(ZodMiniAny);
737
+ }
738
+ const ZodMiniUnknown = /* @__PURE__ */ $constructor("ZodMiniUnknown", (inst, def) => {
739
+ $ZodUnknown.init(inst, def);
740
+ ZodMiniType.init(inst, def);
741
+ });
742
+ /* @__NO_SIDE_EFFECTS__ */
743
+ function unknown() {
744
+ return /* @__PURE__ */ _unknown(ZodMiniUnknown);
745
+ }
746
+ const ZodMiniArray = /* @__PURE__ */ $constructor("ZodMiniArray", (inst, def) => {
747
+ $ZodArray.init(inst, def);
748
+ ZodMiniType.init(inst, def);
749
+ });
750
+ /* @__NO_SIDE_EFFECTS__ */
751
+ function array(element, params) {
752
+ return new ZodMiniArray({
753
+ type: "array",
754
+ element,
755
+ ...normalizeParams(params)
756
+ });
757
+ }
758
+ const ZodMiniObject = /* @__PURE__ */ $constructor("ZodMiniObject", (inst, def) => {
759
+ $ZodObject.init(inst, def);
760
+ ZodMiniType.init(inst, def);
761
+ defineLazy(inst, "shape", () => def.shape);
762
+ });
763
+ /* @__NO_SIDE_EFFECTS__ */
764
+ function object(shape, params) {
765
+ return new ZodMiniObject({
766
+ type: "object",
767
+ shape: shape ?? {},
768
+ ...normalizeParams(params)
769
+ });
770
+ }
771
+ const ZodMiniOptional = /* @__PURE__ */ $constructor("ZodMiniOptional", (inst, def) => {
772
+ $ZodOptional.init(inst, def);
773
+ ZodMiniType.init(inst, def);
774
+ });
775
+ /* @__NO_SIDE_EFFECTS__ */
776
+ function optional(innerType) {
777
+ return new ZodMiniOptional({
778
+ type: "optional",
779
+ innerType
780
+ });
781
+ }
782
+ const ZodMiniNullable = /* @__PURE__ */ $constructor("ZodMiniNullable", (inst, def) => {
783
+ $ZodNullable.init(inst, def);
784
+ ZodMiniType.init(inst, def);
785
+ });
786
+ /* @__NO_SIDE_EFFECTS__ */
787
+ function nullable(innerType) {
788
+ return new ZodMiniNullable({
789
+ type: "nullable",
790
+ innerType
791
+ });
792
+ }
793
+ /* @__NO_SIDE_EFFECTS__ */
794
+ function nullish(innerType) {
795
+ return /* @__PURE__ */ optional(/* @__PURE__ */ nullable(innerType));
796
+ }
797
+ const CloudflareApiResponseSchema = /* @__PURE__ */ object({
798
+ success: /* @__PURE__ */ boolean(),
799
+ errors: /* @__PURE__ */ optional(/* @__PURE__ */ array(/* @__PURE__ */ object({
800
+ code: /* @__PURE__ */ number(),
801
+ message: /* @__PURE__ */ string()
802
+ }))),
803
+ messages: /* @__PURE__ */ optional(/* @__PURE__ */ array(/* @__PURE__ */ string())),
804
+ result: /* @__PURE__ */ unknown()
805
+ });
806
+ const AccountSchema = /* @__PURE__ */ object({
807
+ id: /* @__PURE__ */ string(),
808
+ name: /* @__PURE__ */ string()
809
+ });
810
+ const ZoneSchema = /* @__PURE__ */ object({
811
+ id: /* @__PURE__ */ string(),
812
+ name: /* @__PURE__ */ string(),
813
+ account: /* @__PURE__ */ optional(/* @__PURE__ */ object({ id: /* @__PURE__ */ string() }))
814
+ });
815
+ const TunnelSchema = /* @__PURE__ */ object({
816
+ id: /* @__PURE__ */ string(),
817
+ name: /* @__PURE__ */ string(),
818
+ account_tag: /* @__PURE__ */ string(),
819
+ created_at: /* @__PURE__ */ string(),
820
+ connections: /* @__PURE__ */ optional(/* @__PURE__ */ array(/* @__PURE__ */ unknown()))
821
+ });
822
+ const DNSRecordSchema = /* @__PURE__ */ object({
823
+ id: /* @__PURE__ */ string(),
824
+ type: /* @__PURE__ */ string(),
825
+ name: /* @__PURE__ */ string(),
826
+ content: /* @__PURE__ */ string(),
827
+ proxied: /* @__PURE__ */ boolean(),
828
+ comment: /* @__PURE__ */ nullish(/* @__PURE__ */ string())
829
+ });
830
+ function normalizeHost(host) {
831
+ if (!host || host === "0.0.0.0" || host === "::" || host === "::0") return "localhost";
832
+ return host;
833
+ }
834
+ function normalizeAddress(address) {
835
+ if (address && typeof address === "object") return {
836
+ host: normalizeHost("address" in address && address.address ? address.address : void 0),
837
+ port: "port" in address && typeof address.port === "number" ? address.port : void 0
838
+ };
839
+ return { host: "localhost" };
840
+ }
841
+ async function ensureCloudflaredBinary(binPath) {
842
+ try {
843
+ await NodeFS.access(binPath);
844
+ } catch {
845
+ console.log("[unplugin-cloudflare-tunnel] Installing cloudflared binary...");
846
+ await install(binPath);
847
+ }
848
+ }
849
+ function getLocalTarget(host, port) {
850
+ return `http://${host.includes(":") ? `[${host}]` : host}:${port}`;
851
+ }
852
+ //#endregion
853
+ //#region src/core/process.ts
854
+ function createKillCloudflared(params) {
855
+ const { getChild, clearTunnelUrl, markShuttingDown, debugLog = () => {}, platform = process.platform, exec = NodeChildProcess.exec, setTimeoutFn = setTimeout, clearTimeoutFn = clearTimeout } = params;
856
+ return (signal = "SIGTERM") => {
857
+ const child = getChild();
858
+ if (!child || child.killed) return Promise.resolve();
859
+ markShuttingDown();
860
+ clearTunnelUrl();
861
+ const activeChild = child;
862
+ return new Promise((resolve) => {
863
+ let settled = false;
864
+ let forceKillTimer;
865
+ const settle = () => {
866
+ if (settled) return;
867
+ settled = true;
868
+ if (forceKillTimer) clearTimeoutFn(forceKillTimer);
869
+ resolve();
870
+ };
871
+ activeChild.once("exit", () => {
872
+ settle();
873
+ });
874
+ try {
875
+ debugLog(`[unplugin-cloudflare-tunnel] Terminating cloudflared process (PID: ${activeChild.pid}) with ${signal}...`);
876
+ if (!activeChild.kill(signal)) {
877
+ if (platform === "win32") exec(`taskkill /pid ${activeChild.pid} /T /F`, () => settle());
878
+ else settle();
879
+ return;
880
+ }
881
+ if (signal === "SIGTERM") forceKillTimer = setTimeoutFn(() => {
882
+ if (settled) return;
883
+ debugLog("[unplugin-cloudflare-tunnel] Force killing cloudflared process...");
884
+ if (platform === "win32") exec(`taskkill /pid ${activeChild.pid} /T /F`, () => settle());
885
+ else try {
886
+ if (!activeChild.kill("SIGKILL")) settle();
887
+ } catch {
888
+ settle();
889
+ }
890
+ }, 2e3);
891
+ } catch (error) {
892
+ debugLog(`[unplugin-cloudflare-tunnel] Note: Error killing cloudflared: ${error instanceof Error ? error.message : String(error)}`);
893
+ settle();
894
+ }
895
+ });
896
+ };
897
+ }
898
+ //#endregion
899
+ //#region src/index.ts
900
+ /**
901
+ * @fileoverview Cloudflare Tunnel Unplugin
902
+ *
903
+ * A cross-bundler plugin that automatically creates and manages
904
+ * Cloudflare tunnels for local development, providing instant HTTPS access
905
+ * to your local dev server from anywhere on the internet.
906
+ *
907
+ * @author Cloudflare Tunnel Plugin Contributors
908
+ * @version 1.0.0
909
+ * @license MIT
910
+ */
911
+ const PLUGIN_NAME = "unplugin-cloudflare-tunnel";
912
+ const INFO_LOG_REGEX = /^.*Z INF .*/;
913
+ const LOG_LEVEL_RANK = {
914
+ debug: 10,
915
+ info: 20,
916
+ warn: 30,
917
+ error: 40,
918
+ fatal: 50
919
+ };
920
+ function shouldLog(threshold, level) {
921
+ return LOG_LEVEL_RANK[level] >= LOG_LEVEL_RANK[threshold];
922
+ }
923
+ function supportsColor() {
924
+ if (!process.stdout.isTTY) return false;
925
+ if (process.env.NO_COLOR !== void 0) return false;
926
+ if (process.env.TERM === "dumb") return false;
927
+ if (process.env.FORCE_COLOR === "0") return false;
928
+ return true;
929
+ }
930
+ const ANSI = {
931
+ reset: "\x1B[0m",
932
+ dim: "\x1B[2m",
933
+ bold: "\x1B[1m",
934
+ blue: "\x1B[34m",
935
+ yellow: "\x1B[33m"
936
+ };
937
+ const ANSI_STYLE_SEQUENCE_REGEX = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
938
+ function stripAnsi(text) {
939
+ return text.replace(ANSI_STYLE_SEQUENCE_REGEX, "");
940
+ }
941
+ function colorize(text, ansi) {
942
+ if (!supportsColor()) return text;
943
+ return `${ansi}${text}${ANSI.reset}`;
944
+ }
945
+ const unpluginFactory = (options = {}) => {
946
+ const { enabled = true } = options;
947
+ if (enabled === false) {
948
+ const VIRTUAL_MODULE_ID_STUB = "virtual:unplugin-cloudflare-tunnel";
949
+ return {
950
+ name: PLUGIN_NAME,
951
+ enforce: "pre",
952
+ resolveId(id) {
953
+ if (id === VIRTUAL_MODULE_ID_STUB) return id;
954
+ },
955
+ loadInclude(id) {
956
+ return id === VIRTUAL_MODULE_ID_STUB;
957
+ },
958
+ load(id) {
959
+ if (id === VIRTUAL_MODULE_ID_STUB) return "export function getTunnelUrl() { return \"\"; }";
960
+ }
961
+ };
962
+ }
963
+ const GLOBAL_STATE = Symbol.for("unplugin-cloudflare-tunnel.state");
964
+ const globalState = globalThis[GLOBAL_STATE] ?? {};
965
+ globalThis[GLOBAL_STATE] = globalState;
966
+ let child = globalState.child;
967
+ const VIRTUAL_MODULE_ID = "virtual:unplugin-cloudflare-tunnel";
968
+ const requestedMode = options.mode;
969
+ if (requestedMode && !["quick", "named"].includes(requestedMode)) throw new Error("[unplugin-cloudflare-tunnel] mode must be one of: 'quick', 'named'");
970
+ const hasHostname = "hostname" in options && typeof options.hostname === "string";
971
+ const isQuickMode = requestedMode ? requestedMode === "quick" : !hasHostname;
972
+ if (requestedMode === "named" && !hasHostname) throw new Error("[unplugin-cloudflare-tunnel] hostname is required when mode is set to named");
973
+ if (isQuickMode) {
974
+ const invalidOptions = [
975
+ "apiToken",
976
+ "accountId",
977
+ "zoneId",
978
+ "tunnelName",
979
+ "dns",
980
+ "ssl",
981
+ "cleanup"
982
+ ].filter((opt) => opt in options);
983
+ if (invalidOptions.length > 0) throw new Error(`[unplugin-cloudflare-tunnel] The following options are only supported in named tunnel mode: ${invalidOptions.join(", ")}. Set mode to 'named' and provide a hostname, or remove these options for quick tunnel mode.`);
984
+ }
985
+ let providedApiToken;
986
+ let hostname;
987
+ let tunnelName;
988
+ let forcedAccount;
989
+ let forcedZone;
990
+ let dnsOption;
991
+ let sslOption;
992
+ let cleanupConfig;
993
+ if (isQuickMode) {
994
+ tunnelName = "quick-tunnel";
995
+ cleanupConfig = {};
996
+ } else {
997
+ const namedOptions = options;
998
+ providedApiToken = namedOptions.apiToken;
999
+ hostname = namedOptions.hostname;
1000
+ forcedAccount = namedOptions.accountId;
1001
+ forcedZone = namedOptions.zoneId;
1002
+ tunnelName = namedOptions.tunnelName || "dev-tunnel";
1003
+ dnsOption = namedOptions.dns;
1004
+ sslOption = namedOptions.ssl;
1005
+ cleanupConfig = namedOptions.cleanup || {};
1006
+ }
1007
+ const { port: userProvidedPort, logFile, logLevel, protocol = "http2", debug = false } = options;
1008
+ const effectivePluginLogLevel = logLevel ?? (debug ? "debug" : "info");
1009
+ const redactForDebug = (value) => {
1010
+ if (typeof value === "string") {
1011
+ if (value.startsWith("eyJ") && value.length > 40) return "[REDACTED_TOKEN]";
1012
+ return value;
1013
+ }
1014
+ if (Array.isArray(value)) return value.map((item) => redactForDebug(item));
1015
+ if (value && typeof value === "object") {
1016
+ const entries = Object.entries(value).map(([key, nestedValue]) => {
1017
+ if (/token|authorization|secret|password/i.test(key)) return [key, "[REDACTED]"];
1018
+ return [key, redactForDebug(nestedValue)];
1019
+ });
1020
+ return Object.fromEntries(entries);
1021
+ }
1022
+ return value;
1023
+ };
1024
+ const formatDebugValue = (value) => {
1025
+ const redactedValue = redactForDebug(value);
1026
+ if (typeof redactedValue === "string") return redactedValue;
1027
+ return NodeUtil.inspect(redactedValue, {
1028
+ depth: null,
1029
+ colors: supportsColor(),
1030
+ compact: false,
1031
+ breakLength: 120,
1032
+ sorted: true
1033
+ });
1034
+ };
1035
+ const pluginLog = {
1036
+ debug: (...args) => {
1037
+ if (debug || effectivePluginLogLevel === "debug") console.log("[cloudflare-tunnel:debug]", ...args.map((arg) => formatDebugValue(arg)));
1038
+ },
1039
+ info: (message) => {
1040
+ if (shouldLog(effectivePluginLogLevel, "info")) console.log(`[unplugin-cloudflare-tunnel] ${message}`);
1041
+ },
1042
+ warn: (message) => {
1043
+ if (shouldLog(effectivePluginLogLevel, "warn")) console.warn(`[unplugin-cloudflare-tunnel] ${message}`);
1044
+ },
1045
+ error: (message) => {
1046
+ if (shouldLog(effectivePluginLogLevel, "error")) console.error(`[unplugin-cloudflare-tunnel] ${message}`);
1047
+ }
1048
+ };
1049
+ const debugLog = pluginLog.debug;
1050
+ const makeLocalDisplay = (localTarget) => {
1051
+ if (!localTarget) return localTarget;
1052
+ return localTarget.replace("http://[::1]:", "http://localhost:").replace("http://127.0.0.1:", "http://localhost:");
1053
+ };
1054
+ const announceConnecting = () => {
1055
+ if (globalState.__tunnelConnectingAnnounced) return;
1056
+ globalState.__tunnelConnectingAnnounced = true;
1057
+ const message = isQuickMode ? "cf tunnel connecting…" : hostname ? `cf tunnel connecting… (${hostname})` : "cf tunnel connecting…";
1058
+ console.log("");
1059
+ console.log(colorize(message, ANSI.bold));
1060
+ };
1061
+ const announceTunnel = (params) => {
1062
+ if (!params.url) return;
1063
+ if (globalState.__lastAnnouncedTunnelKey === params.key) return;
1064
+ globalState.__lastAnnouncedTunnelKey = params.key;
1065
+ const cols = process.stdout.columns ?? 80;
1066
+ const maxWidth = Math.max(10, cols - 2);
1067
+ const header = `${colorize("[", ANSI.yellow)}unplugin-cloudflare-tunnel${colorize("]", ANSI.yellow)}`;
1068
+ const urlLine = colorize(params.url, ANSI.blue + ANSI.bold);
1069
+ const localLine = params.localTarget ? makeLocalDisplay(params.localTarget) : "";
1070
+ const headerPlainLen = stripAnsi(header).length;
1071
+ const contentPlainLen = Math.max(stripAnsi(urlLine).length, localLine.length, 10, 5);
1072
+ const width = Math.min(90, maxWidth, Math.max(44, headerPlainLen, contentPlainLen + 4));
1073
+ const rule = "─".repeat(width);
1074
+ const center = (text) => {
1075
+ const plainLen = stripAnsi(text).length;
1076
+ const pad = Math.max(0, Math.floor((width - plainLen) / 2));
1077
+ return `${" ".repeat(pad)}${text}`;
1078
+ };
1079
+ if (cols < 70) {
1080
+ const out = [];
1081
+ out.push("");
1082
+ out.push(`${header} ${colorize("Tunnel URL", ANSI.bold)} ${urlLine}`);
1083
+ if (localLine) out.push(`${header} ${colorize("Local", ANSI.dim + ANSI.bold)} ${localLine}`);
1084
+ out.push("");
1085
+ console.log(out.join("\n"));
1086
+ return;
1087
+ }
1088
+ const out = [];
1089
+ out.push("");
1090
+ out.push(center(header));
1091
+ out.push(rule);
1092
+ out.push(center(colorize("Tunnel URL", ANSI.bold)));
1093
+ out.push(center(urlLine));
1094
+ if (localLine) {
1095
+ out.push("");
1096
+ out.push(center(colorize("Local", ANSI.dim + ANSI.bold)));
1097
+ out.push(center(localLine));
1098
+ }
1099
+ out.push(rule);
1100
+ out.push("");
1101
+ console.log(out.join("\n"));
1102
+ };
1103
+ if (!isQuickMode && (!hostname || typeof hostname !== "string")) throw new Error("[unplugin-cloudflare-tunnel] hostname is required and must be a valid string in named tunnel mode");
1104
+ let tunnelUrl = hostname ? `https://${hostname}` : "";
1105
+ if (tunnelName && !/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/.test(tunnelName)) throw new Error("[unplugin-cloudflare-tunnel] tunnelName must contain only letters, numbers, and hyphens. It cannot start or end with a hyphen.");
1106
+ if (userProvidedPort && (typeof userProvidedPort !== "number" || userProvidedPort < 1 || userProvidedPort > 65535)) throw new Error("[unplugin-cloudflare-tunnel] port must be a valid number between 1 and 65535");
1107
+ if (logLevel && ![
1108
+ "debug",
1109
+ "info",
1110
+ "warn",
1111
+ "error",
1112
+ "fatal"
1113
+ ].includes(logLevel)) throw new Error("[unplugin-cloudflare-tunnel] logLevel must be one of: debug, info, warn, error, fatal");
1114
+ const effectiveLogLevel = logLevel ?? (debug ? "info" : "warn");
1115
+ const cloudflaredProcessLogLevel = effectiveLogLevel === "debug" ? "debug" : "info";
1116
+ debugLog("Effective cloudflared log level filter:", effectiveLogLevel);
1117
+ debugLog("Effective cloudflared process log level:", cloudflaredProcessLogLevel);
1118
+ debugLog("Effective cloudflared protocol:", protocol);
1119
+ if (dnsOption) {
1120
+ if (!dnsOption.startsWith("*.") && dnsOption !== hostname) throw new Error("[unplugin-cloudflare-tunnel] dns option must either be a wildcard (e.g., '*.example.com') or exactly match the hostname");
1121
+ }
1122
+ if (sslOption) {
1123
+ if (!sslOption.startsWith("*.") && sslOption !== hostname) throw new Error("[unplugin-cloudflare-tunnel] ssl option must either be a wildcard (e.g., '*.example.com') or exactly match the hostname");
1124
+ }
1125
+ if (!["http2", "quic"].includes(protocol)) throw new Error("[unplugin-cloudflare-tunnel] protocol must be one of: 'http2', 'quic'");
1126
+ const trackSslCertificate = (certificateId, hosts, tunnelName, timestamp = (/* @__PURE__ */ new Date()).toISOString()) => {
1127
+ const trackingKey = `ssl-cert-${certificateId}`;
1128
+ globalState[trackingKey] = {
1129
+ id: certificateId,
1130
+ hosts,
1131
+ tunnelName,
1132
+ timestamp,
1133
+ pluginVersion: "1.0.0"
1134
+ };
1135
+ debugLog(`Tracking SSL certificate: ${certificateId} for hosts: ${hosts.join(", ")}`);
1136
+ };
1137
+ const cleanupMismatchedDnsRecords = async (apiToken, zoneId, dnsComment, currentHostname, tunnelId) => {
1138
+ try {
1139
+ const pluginDnsRecords = await cf(apiToken, "GET", `/zones/${zoneId}/dns_records?comment=${dnsComment}&match=all`, void 0, /* @__PURE__ */ array(DNSRecordSchema));
1140
+ debugLog(`Found ${pluginDnsRecords.length} DNS records for current tunnel: ${dnsComment}`);
1141
+ const expectedCnameContent = `${tunnelId}.cfargotunnel.com`;
1142
+ const mismatchedRecords = pluginDnsRecords.filter((record) => {
1143
+ if (record.name === currentHostname && record.content === expectedCnameContent) return false;
1144
+ if (dnsOption && record.name === dnsOption && record.content === expectedCnameContent) return false;
1145
+ return true;
1146
+ });
1147
+ debugLog(`Found ${mismatchedRecords.length} mismatched DNS records`, mismatchedRecords.map((r) => ({
1148
+ name: r.name,
1149
+ content: r.content,
1150
+ expected: expectedCnameContent,
1151
+ comment: r.comment
1152
+ })));
1153
+ const deletedRecords = [];
1154
+ if (mismatchedRecords.length > 0) {
1155
+ console.log(`[unplugin-cloudflare-tunnel] 🧹 Cleaning up ${mismatchedRecords.length} mismatched DNS records from tunnel '${dnsComment}'...`);
1156
+ for (const record of mismatchedRecords) try {
1157
+ await cf(apiToken, "DELETE", `/zones/${zoneId}/dns_records/${record.id}`);
1158
+ deletedRecords.push(record);
1159
+ console.log(`[unplugin-cloudflare-tunnel] ✅ Deleted mismatched DNS record: ${record.name} → ${record.content}`);
1160
+ } catch (error) {
1161
+ console.error(`[unplugin-cloudflare-tunnel] ❌ Failed to delete DNS record ${record.name}: ${error.message}`);
1162
+ }
1163
+ }
1164
+ return {
1165
+ found: mismatchedRecords,
1166
+ deleted: deletedRecords
1167
+ };
1168
+ } catch (error) {
1169
+ console.error(`[unplugin-cloudflare-tunnel] ❌ DNS cleanup failed: ${error.message}`);
1170
+ return {
1171
+ found: [],
1172
+ deleted: []
1173
+ };
1174
+ }
1175
+ };
1176
+ const cf = async (apiToken, method, url, body, resultSchema) => {
1177
+ try {
1178
+ debugLog("→ CF API", method, url, body ? { body } : "");
1179
+ const response = await fetch(`https://api.cloudflare.com/client/v4${url}`, {
1180
+ method,
1181
+ headers: {
1182
+ Authorization: `Bearer ${apiToken}`,
1183
+ "Content-Type": "application/json",
1184
+ "User-Agent": "unplugin-cloudflare-tunnel/1.0.0"
1185
+ },
1186
+ ...body ? { body: JSON.stringify(body) } : {}
1187
+ });
1188
+ if (!response.ok) {
1189
+ const errorText = await response.text().catch(() => "Unknown error");
1190
+ throw new Error(`[unplugin-cloudflare-tunnel] API request failed: ${response.status} ${response.statusText}. Response: ${errorText}`);
1191
+ }
1192
+ const rawData = await response.json();
1193
+ debugLog("← CF API response", rawData);
1194
+ const apiResponse = CloudflareApiResponseSchema.parse(rawData);
1195
+ if (!apiResponse.success) {
1196
+ const errorMsg = apiResponse.errors?.map((e) => e.message || `Error ${e.code}`).join(", ") || "Unknown API error";
1197
+ throw new Error(`[unplugin-cloudflare-tunnel] Cloudflare API error: ${errorMsg}`);
1198
+ }
1199
+ if (resultSchema) {
1200
+ const parsed = resultSchema.parse(apiResponse.result);
1201
+ debugLog("← Parsed result", parsed);
1202
+ return parsed;
1203
+ }
1204
+ debugLog("← Result (untyped)", apiResponse.result);
1205
+ return apiResponse.result;
1206
+ } catch (error) {
1207
+ if (error instanceof Error) {
1208
+ if (error.message.includes("[unplugin-cloudflare-tunnel]")) throw error;
1209
+ throw new Error(`[unplugin-cloudflare-tunnel] API request failed: ${error.message}`);
1210
+ }
1211
+ throw new Error("[unplugin-cloudflare-tunnel] Unknown API error occurred");
1212
+ }
1213
+ };
1214
+ const retryWithBackoff = async (fn, maxRetries = 5, initialDelayMs = 1e3) => {
1215
+ let attempt = 0;
1216
+ while (true) try {
1217
+ return await fn();
1218
+ } catch (error) {
1219
+ attempt += 1;
1220
+ const message = error instanceof Error ? error.message : String(error);
1221
+ if (attempt > maxRetries) {
1222
+ console.error(`[unplugin-cloudflare-tunnel] ❌ Edge certificate request failed after ${maxRetries} retries: ${message}`);
1223
+ throw error;
1224
+ }
1225
+ const delay = initialDelayMs * 2 ** (attempt - 1);
1226
+ console.error(`[unplugin-cloudflare-tunnel] ⚠️ Edge certificate request failed (attempt ${attempt}/${maxRetries}): ${message}`);
1227
+ console.error(`[unplugin-cloudflare-tunnel] ⏳ Retrying in ${delay}ms...`);
1228
+ await new Promise((resolve) => setTimeout(resolve, delay));
1229
+ }
1230
+ };
1231
+ const spawnQuickTunnel = async (localTarget, protocol) => {
1232
+ const cloudflaredArgs = ["tunnel"];
1233
+ cloudflaredArgs.push("--loglevel", "info");
1234
+ if (logFile) cloudflaredArgs.push("--logfile", logFile);
1235
+ cloudflaredArgs.push("--protocol", protocol);
1236
+ cloudflaredArgs.push("--url", localTarget);
1237
+ debugLog("Spawning quick tunnel:", bin, cloudflaredArgs);
1238
+ const child = NodeChildProcess.spawn(bin, cloudflaredArgs, {
1239
+ stdio: [
1240
+ "ignore",
1241
+ "pipe",
1242
+ "pipe"
1243
+ ],
1244
+ detached: false,
1245
+ windowsHide: true,
1246
+ shell: process.platform === "win32"
1247
+ });
1248
+ debugLog(`[unplugin-cloudflare-tunnel] Quick tunnel process spawned with PID: ${child.pid}`);
1249
+ return new Promise((resolve, reject) => {
1250
+ let urlFound = false;
1251
+ let settled = false;
1252
+ const rejectOnce = (error) => {
1253
+ if (settled) return;
1254
+ settled = true;
1255
+ reject(error);
1256
+ };
1257
+ const resolveOnce = (result) => {
1258
+ if (settled) return;
1259
+ settled = true;
1260
+ resolve(result);
1261
+ };
1262
+ const timeout = setTimeout(() => {
1263
+ if (!urlFound) {
1264
+ try {
1265
+ child.kill("SIGTERM");
1266
+ } catch {}
1267
+ rejectOnce(/* @__PURE__ */ new Error("Quick tunnel URL not found in output within 30 seconds"));
1268
+ }
1269
+ }, 3e4);
1270
+ child.stdout?.on("data", (data) => {
1271
+ const output = data.toString();
1272
+ if (!globalState.shuttingDown || debug) {
1273
+ if (effectiveLogLevel === "debug" || effectiveLogLevel === "info") console.log(`[cloudflared stdout] ${output.trim()}`);
1274
+ else for (const line of output.split("\n")) if (!INFO_LOG_REGEX.test(line)) console.log(`[cloudflared stdout] ${line.trim()}`);
1275
+ }
1276
+ });
1277
+ child.stderr?.on("data", (data) => {
1278
+ const error = data.toString().trim();
1279
+ const urlMatch = error.match(/https:\/\/[a-zA-Z0-9-]+\.trycloudflare\.com/);
1280
+ if (urlMatch && !urlFound) {
1281
+ urlFound = true;
1282
+ clearTimeout(timeout);
1283
+ resolveOnce({
1284
+ child,
1285
+ url: urlMatch[0]
1286
+ });
1287
+ }
1288
+ if (error.includes("Failed to parse ICMP reply") || error.includes("unknow ip version 0")) {
1289
+ if (logLevel === "debug") console.log(`[cloudflared debug] ${error}`);
1290
+ return;
1291
+ }
1292
+ if (!globalState.shuttingDown || debug) {
1293
+ if (effectiveLogLevel === "debug" || effectiveLogLevel === "info") console.error(`[cloudflared stderr] ${error}`);
1294
+ else for (const line of error.split("\n")) if (!INFO_LOG_REGEX.test(line)) console.error(`[cloudflared stderr] ${line.trim()}`);
1295
+ }
1296
+ });
1297
+ child.on("error", (error) => {
1298
+ clearTimeout(timeout);
1299
+ rejectOnce(/* @__PURE__ */ new Error(`Failed to start quick tunnel process: ${error.message}`));
1300
+ });
1301
+ child.on("exit", (code, signal) => {
1302
+ clearTimeout(timeout);
1303
+ if (!urlFound) rejectOnce(/* @__PURE__ */ new Error(`Quick tunnel process exited before URL was found (code: ${code}, signal: ${signal})`));
1304
+ });
1305
+ });
1306
+ };
1307
+ const killCloudflared = createKillCloudflared({
1308
+ getChild: () => child,
1309
+ clearTunnelUrl: () => {
1310
+ globalState.tunnelUrl = void 0;
1311
+ },
1312
+ markShuttingDown: () => {
1313
+ globalState.shuttingDown = true;
1314
+ },
1315
+ debugLog
1316
+ });
1317
+ let exitHandlersRegistered = globalState.exitHandlersRegistered ?? false;
1318
+ const registerExitHandler = () => {
1319
+ if (exitHandlersRegistered) return;
1320
+ exitHandlersRegistered = true;
1321
+ globalState.exitHandlersRegistered = true;
1322
+ const cleanup = () => killCloudflared("SIGTERM");
1323
+ process.once("exit", cleanup);
1324
+ process.once("beforeExit", cleanup);
1325
+ [
1326
+ "SIGINT",
1327
+ "SIGTERM",
1328
+ "SIGQUIT",
1329
+ "SIGHUP"
1330
+ ].forEach((signal) => {
1331
+ process.once(signal, () => {
1332
+ killCloudflared(signal);
1333
+ try {
1334
+ process.kill(process.pid, signal);
1335
+ } catch {
1336
+ process.exit(0);
1337
+ }
1338
+ });
1339
+ });
1340
+ process.once("uncaughtExceptionMonitor", (error) => {
1341
+ console.error("[unplugin-cloudflare-tunnel] Uncaught exception, cleaning up cloudflared...", error);
1342
+ killCloudflared("SIGTERM");
1343
+ });
1344
+ };
1345
+ const configureServer = async (server) => {
1346
+ const generateDnsComment = () => {
1347
+ return `unplugin-cloudflare-tunnel:${tunnelName}`;
1348
+ };
1349
+ const registerListeningHandler = (handler) => {
1350
+ const httpServer = server.httpServer;
1351
+ const invokeHandler = () => {
1352
+ try {
1353
+ const maybePromise = handler();
1354
+ if (maybePromise && typeof maybePromise.then === "function") maybePromise.catch((error) => {
1355
+ console.error(`[unplugin-cloudflare-tunnel] ❌ Dev server listening hook failed: ${error.message}`);
1356
+ });
1357
+ } catch (error) {
1358
+ console.error(`[unplugin-cloudflare-tunnel] ❌ Dev server listening hook failed: ${error.message}`);
1359
+ }
1360
+ };
1361
+ if (!httpServer) {
1362
+ invokeHandler();
1363
+ return;
1364
+ }
1365
+ httpServer.on("listening", invokeHandler);
1366
+ if (httpServer.listening) invokeHandler();
1367
+ };
1368
+ try {
1369
+ const { host: serverHost, port: detectedPort } = normalizeAddress(server.httpServer?.address());
1370
+ const configPortValue = server.config?.server?.port;
1371
+ const resolvedConfigPort = typeof configPortValue === "string" ? Number.parseInt(configPortValue, 10) : configPortValue;
1372
+ const port = userProvidedPort || detectedPort || (typeof resolvedConfigPort === "number" && !Number.isNaN(resolvedConfigPort) ? resolvedConfigPort : void 0) || 5173;
1373
+ const originRequest = server.httpServer ? void 0 : { httpHostHeader: `${serverHost}:${port}` };
1374
+ const newConfigHash = JSON.stringify({
1375
+ isQuickMode,
1376
+ hostname,
1377
+ port,
1378
+ tunnelName,
1379
+ dnsOption,
1380
+ sslOption,
1381
+ originRequest
1382
+ });
1383
+ if (globalState.child && !globalState.child.killed && globalState.configHash === newConfigHash) {
1384
+ tunnelUrl = await globalState.tunnelUrl ?? "";
1385
+ debugLog("[unplugin-cloudflare-tunnel] Config unchanged – re-using existing tunnel");
1386
+ globalState.shuttingDown = false;
1387
+ registerExitHandler();
1388
+ return;
1389
+ }
1390
+ if (globalState.child && !globalState.child.killed) {
1391
+ debugLog("[unplugin-cloudflare-tunnel] Config changed – terminating previous tunnel...");
1392
+ try {
1393
+ globalState.child.kill("SIGTERM");
1394
+ } catch {}
1395
+ }
1396
+ delete globalState.child;
1397
+ delete globalState.configHash;
1398
+ globalState.shuttingDown = false;
1399
+ if (isQuickMode) {
1400
+ debugLog("[unplugin-cloudflare-tunnel] Starting quick tunnel mode...");
1401
+ debugLog("Quick tunnel mode - no API token or hostname required");
1402
+ await ensureCloudflaredBinary(bin);
1403
+ const localTarget = getLocalTarget(serverHost, port);
1404
+ debugLog("← Quick tunnel connecting to local target", localTarget);
1405
+ try {
1406
+ const { child: quickChild, url } = await spawnQuickTunnel(localTarget, protocol);
1407
+ tunnelUrl = url;
1408
+ child = quickChild;
1409
+ globalState.child = child;
1410
+ globalState.configHash = newConfigHash;
1411
+ globalState.tunnelUrl = Promise.resolve(url);
1412
+ registerExitHandler();
1413
+ registerListeningHandler(() => {
1414
+ const { host: actualServerHost, port: actualPort } = normalizeAddress(server.httpServer?.address());
1415
+ const actualLocalTarget = getLocalTarget(actualServerHost, actualPort ?? port);
1416
+ announceTunnel({
1417
+ key: `quick:${url}:${actualPort ?? port}`,
1418
+ url,
1419
+ localTarget: actualLocalTarget
1420
+ });
1421
+ });
1422
+ registerListeningHandler(async () => {
1423
+ try {
1424
+ const { host: actualServerHost, port: actualPort } = normalizeAddress(server.httpServer?.address());
1425
+ if (server.httpServer && actualPort !== void 0 && actualPort !== port) {
1426
+ pluginLog.warn(`Port conflict detected - server is using port ${actualPort} instead of ${port}`);
1427
+ pluginLog.info("Restarting quick tunnel for the new port...");
1428
+ killCloudflared("SIGTERM");
1429
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
1430
+ const newLocalTarget = getLocalTarget(actualServerHost, actualPort ?? port);
1431
+ const { child: newChild, url: newUrl } = await spawnQuickTunnel(newLocalTarget, protocol);
1432
+ tunnelUrl = newUrl;
1433
+ child = newChild;
1434
+ globalState.child = child;
1435
+ globalState.tunnelUrl = Promise.resolve(newUrl);
1436
+ announceTunnel({
1437
+ key: `quick:${newUrl}:${actualPort ?? port}`,
1438
+ url: newUrl,
1439
+ localTarget: newLocalTarget
1440
+ });
1441
+ globalState.configHash = JSON.stringify({
1442
+ isQuickMode,
1443
+ hostname,
1444
+ port: actualPort,
1445
+ tunnelName,
1446
+ dnsOption,
1447
+ sslOption
1448
+ });
1449
+ }
1450
+ } catch (error) {
1451
+ console.error(`[unplugin-cloudflare-tunnel] ❌ Failed to update quick tunnel for port change: ${error.message}`);
1452
+ }
1453
+ });
1454
+ server.httpServer?.once("close", () => {
1455
+ killCloudflared("SIGTERM");
1456
+ });
1457
+ return;
1458
+ } catch (error) {
1459
+ console.error(`[unplugin-cloudflare-tunnel] ❌ Quick tunnel setup failed: ${error.message}`);
1460
+ throw error;
1461
+ }
1462
+ }
1463
+ debugLog("[unplugin-cloudflare-tunnel] Starting named tunnel mode...");
1464
+ const apiToken = providedApiToken || process.env.CLOUDFLARE_API_TOKEN;
1465
+ if (!apiToken) throw new Error("[unplugin-cloudflare-tunnel] API token is required. Provide it via 'apiToken' option or set the CLOUDFLARE_API_TOKEN environment variable. Get your token at: https://dash.cloudflare.com/profile/api-tokens");
1466
+ debugLog(`[unplugin-cloudflare-tunnel] Using port ${port}${userProvidedPort === port ? " (user-provided)" : " (from bundler config)"}`);
1467
+ await ensureCloudflaredBinary(bin);
1468
+ const apexDomain = hostname.split(".").slice(-2).join(".");
1469
+ const parentDomain = hostname.split(".").slice(1).join(".");
1470
+ debugLog("← Apex domain", apexDomain);
1471
+ debugLog("← Parent domain", parentDomain);
1472
+ let resolvedZone;
1473
+ let zoneId = forcedZone;
1474
+ if (!zoneId) {
1475
+ let zones = [];
1476
+ try {
1477
+ zones = await cf(apiToken, "GET", `/zones?name=${parentDomain}`, void 0, /* @__PURE__ */ array(ZoneSchema));
1478
+ } catch (error) {
1479
+ debugLog("← Error fetching zone for parent domain", error);
1480
+ }
1481
+ if (zones.length === 0) zones = await cf(apiToken, "GET", `/zones?name=${apexDomain}`, void 0, /* @__PURE__ */ array(ZoneSchema));
1482
+ resolvedZone = zones[0];
1483
+ zoneId = resolvedZone?.id;
1484
+ }
1485
+ let accountId = forcedAccount || resolvedZone?.account?.id;
1486
+ if (!accountId) accountId = (await cf(apiToken, "GET", "/accounts", void 0, /* @__PURE__ */ array(AccountSchema)))[0]?.id;
1487
+ if (!accountId) throw new Error("Unable to determine Cloudflare account ID");
1488
+ if (!zoneId) throw new Error(`Zone ${apexDomain} not found in account ${accountId}`);
1489
+ const { autoCleanup = true } = cleanupConfig;
1490
+ let [tunnel] = await cf(apiToken, "GET", `/accounts/${accountId}/cfd_tunnel?name=${tunnelName}`, void 0, /* @__PURE__ */ array(TunnelSchema));
1491
+ if (!tunnel) {
1492
+ pluginLog.info(`Creating tunnel '${tunnelName}'...`);
1493
+ tunnel = await cf(apiToken, "POST", `/accounts/${accountId}/cfd_tunnel`, {
1494
+ name: tunnelName,
1495
+ config_src: "cloudflare"
1496
+ }, TunnelSchema);
1497
+ }
1498
+ const tunnelId = tunnel.id;
1499
+ const localTarget = getLocalTarget(serverHost, port);
1500
+ debugLog("← Connecting to local target", localTarget);
1501
+ await cf(apiToken, "PUT", `/accounts/${accountId}/cfd_tunnel/${tunnelId}/configurations`, { config: { ingress: [{
1502
+ hostname,
1503
+ service: localTarget,
1504
+ ...originRequest ? { originRequest } : {}
1505
+ }, { service: "http_status:404" }] } });
1506
+ const token = await cf(apiToken, "GET", `/accounts/${accountId}/cfd_tunnel/${tunnelId}/token`, void 0, /* @__PURE__ */ string());
1507
+ const generateSslTagHostname = () => {
1508
+ return `cf-tunnel-plugin-${tunnelName}--${parentDomain}`;
1509
+ };
1510
+ const finalizeNamedTunnelSetup = async () => {
1511
+ if (autoCleanup) {
1512
+ debugLog(`[unplugin-cloudflare-tunnel] Running resource cleanup for tunnel '${tunnelName}'...`);
1513
+ const dnsCleanup = await cleanupMismatchedDnsRecords(apiToken, zoneId, generateDnsComment(), hostname, tunnelId);
1514
+ if (dnsCleanup.found.length > 0) pluginLog.warn(`DNS cleanup: ${dnsCleanup.found.length} mismatched, ${dnsCleanup.deleted.length} deleted`);
1515
+ } else debugLog("← Cleanup skipped", cleanupConfig);
1516
+ if (dnsOption) {
1517
+ const ensureDnsRecord = async (type, content) => {
1518
+ if ((await cf(apiToken, "GET", `/zones/${zoneId}/dns_records?type=${type}&name=${encodeURIComponent(dnsOption)}`, void 0, /* @__PURE__ */ array(DNSRecordSchema))).length === 0) {
1519
+ console.log(`[unplugin-cloudflare-tunnel] Creating ${type} record for ${dnsOption}...`);
1520
+ await cf(apiToken, "POST", `/zones/${zoneId}/dns_records`, {
1521
+ type,
1522
+ name: dnsOption,
1523
+ content,
1524
+ proxied: true,
1525
+ comment: generateDnsComment()
1526
+ }, DNSRecordSchema);
1527
+ }
1528
+ };
1529
+ await ensureDnsRecord("CNAME", `${tunnelId}.cfargotunnel.com`);
1530
+ } else {
1531
+ const wildcardDns = `*.${parentDomain}`;
1532
+ if ((await cf(apiToken, "GET", `/zones/${zoneId}/dns_records?type=CNAME&name=${wildcardDns}`, void 0, /* @__PURE__ */ array(DNSRecordSchema))).length === 0) {
1533
+ const existingRecord = (await cf(apiToken, "GET", `/zones/${zoneId}/dns_records?type=CNAME&name=${hostname}`, void 0, /* @__PURE__ */ array(DNSRecordSchema)))[0];
1534
+ const expectedContent = `${tunnelId}.cfargotunnel.com`;
1535
+ if (!existingRecord) {
1536
+ console.log(`[unplugin-cloudflare-tunnel] Creating DNS record for ${hostname}...`);
1537
+ await cf(apiToken, "POST", `/zones/${zoneId}/dns_records`, {
1538
+ type: "CNAME",
1539
+ name: hostname,
1540
+ content: expectedContent,
1541
+ proxied: true,
1542
+ comment: generateDnsComment()
1543
+ }, DNSRecordSchema);
1544
+ } else if (existingRecord.content !== expectedContent) {
1545
+ debugLog(`← DNS record for ${hostname} points to different tunnel, updating...`);
1546
+ pluginLog.info(`Updating DNS record for ${hostname} to point to tunnel '${tunnelName}'...`);
1547
+ await cf(apiToken, "PUT", `/zones/${zoneId}/dns_records/${existingRecord.id}`, {
1548
+ type: "CNAME",
1549
+ name: hostname,
1550
+ content: expectedContent,
1551
+ proxied: true,
1552
+ comment: generateDnsComment()
1553
+ }, DNSRecordSchema);
1554
+ }
1555
+ }
1556
+ }
1557
+ const certListRaw = await cf(apiToken, "GET", `/zones/${zoneId}/ssl/certificate_packs?status=all`, void 0, /* @__PURE__ */ any());
1558
+ const certPacks = Array.isArray(certListRaw) ? certListRaw : certListRaw.result || [];
1559
+ if (autoCleanup) {
1560
+ const mismatchedSslCerts = certPacks.filter((cert) => {
1561
+ return (cert.hostnames || cert.hosts || []).some((host) => host.startsWith(`cf-tunnel-plugin-${tunnelName}--`));
1562
+ }).filter((cert) => {
1563
+ return !(cert.hostnames || cert.hosts || []).some((host) => {
1564
+ if (host.startsWith("cf-tunnel-plugin-")) return false;
1565
+ return host === hostname || host.startsWith("*.") && hostname.endsWith(host.slice(1));
1566
+ });
1567
+ });
1568
+ if (mismatchedSslCerts.length > 0) {
1569
+ for (const cert of mismatchedSslCerts) await cf(apiToken, "DELETE", `/zones/${zoneId}/ssl/certificate_packs/${cert.id}`);
1570
+ pluginLog.warn(`SSL cleanup: ${mismatchedSslCerts.length} deleted`);
1571
+ }
1572
+ }
1573
+ const certContainingHost = (host) => certPacks.filter((c) => (c.hostnames || c.hosts || []).includes(host))?.[0];
1574
+ if (sslOption) {
1575
+ const isWildcard = sslOption.startsWith("*.");
1576
+ const certNeededHost = sslOption;
1577
+ const matchingCert = certContainingHost(certNeededHost);
1578
+ if (!matchingCert) {
1579
+ console.log(`[unplugin-cloudflare-tunnel] Requesting ${isWildcard ? "wildcard " : ""}certificate for ${certNeededHost}...`);
1580
+ const tagHostname = generateSslTagHostname();
1581
+ const certificateHosts = [certNeededHost, tagHostname];
1582
+ debugLog(`Adding tag hostname to certificate: ${tagHostname}`);
1583
+ const newCert = await retryWithBackoff(() => cf(apiToken, "POST", `/zones/${zoneId}/ssl/certificate_packs/order`, {
1584
+ hosts: certificateHosts,
1585
+ certificate_authority: "lets_encrypt",
1586
+ type: "advanced",
1587
+ validation_method: isWildcard ? "txt" : "http",
1588
+ validity_days: 90,
1589
+ cloudflare_branding: false
1590
+ }));
1591
+ if (newCert?.id) trackSslCertificate(newCert.id, certificateHosts, tunnelName);
1592
+ } else debugLog("← Edge certificate already exists", matchingCert);
1593
+ } else {
1594
+ const wildcardDomain = `*.${parentDomain}`;
1595
+ const wildcardExists = certContainingHost(wildcardDomain);
1596
+ if (!wildcardExists) {
1597
+ const totalTls = await cf(apiToken, "GET", `/zones/${zoneId}/acm/total_tls`, void 0, /* @__PURE__ */ object({ status: /* @__PURE__ */ string() }));
1598
+ debugLog("← Total TLS", totalTls);
1599
+ const existingHostnameCert = certContainingHost(hostname);
1600
+ if (totalTls.status !== "on" && !existingHostnameCert) {
1601
+ pluginLog.info(`Requesting edge certificate for ${hostname}...`);
1602
+ const tagHostname = generateSslTagHostname();
1603
+ const certificateHosts = [hostname, tagHostname];
1604
+ debugLog(`Adding tag hostname to certificate: ${tagHostname}`);
1605
+ const newCert = await retryWithBackoff(() => cf(apiToken, "POST", `/zones/${zoneId}/ssl/certificate_packs/order`, {
1606
+ hosts: certificateHosts,
1607
+ certificate_authority: "lets_encrypt",
1608
+ type: "advanced",
1609
+ validation_method: "txt",
1610
+ validity_days: 90,
1611
+ cloudflare_branding: false
1612
+ }));
1613
+ if (newCert?.id) trackSslCertificate(newCert.id, certificateHosts, tunnelName);
1614
+ } else debugLog("← Edge certificate already exists", existingHostnameCert);
1615
+ } else debugLog("← Edge certificate (wildcard) already exists", wildcardExists, wildcardDomain);
1616
+ }
1617
+ };
1618
+ let tunnelReady = false;
1619
+ let localTargetForAnnouncement = localTarget;
1620
+ let activeTunnelProtocol;
1621
+ const announceNamedTunnelIfReady = () => {
1622
+ if (!tunnelReady) return;
1623
+ announceTunnel({
1624
+ key: `named:${hostname}:${localTargetForAnnouncement}`,
1625
+ url: `https://${hostname}`,
1626
+ localTarget: localTargetForAnnouncement
1627
+ });
1628
+ };
1629
+ const logCloudflaredLines = (kind, text) => {
1630
+ if (globalState.shuttingDown && !debug) return;
1631
+ const isVerbose = effectiveLogLevel === "debug" || effectiveLogLevel === "info";
1632
+ const lines = text.split("\n").map((l) => l.trim()).filter(Boolean);
1633
+ if (isVerbose) {
1634
+ for (const line of lines) {
1635
+ const prefix = kind === "stdout" ? "[cloudflared stdout]" : "[cloudflared stderr]";
1636
+ if (kind === "stdout") console.log(`${prefix} ${line}`);
1637
+ else console.error(`${prefix} ${line}`);
1638
+ }
1639
+ return;
1640
+ }
1641
+ for (const line of lines) {
1642
+ if (INFO_LOG_REGEX.test(line)) continue;
1643
+ const prefix = kind === "stdout" ? "[cloudflared stdout]" : "[cloudflared stderr]";
1644
+ if (kind === "stdout") console.log(`${prefix} ${line}`);
1645
+ else console.error(`${prefix} ${line}`);
1646
+ }
1647
+ };
1648
+ const spawnNamedTunnelProcess = (protocol) => {
1649
+ const cloudflaredArgs = ["tunnel"];
1650
+ cloudflaredArgs.push("--loglevel", cloudflaredProcessLogLevel);
1651
+ if (logFile) cloudflaredArgs.push("--logfile", logFile);
1652
+ cloudflaredArgs.push("--protocol", protocol);
1653
+ debugLog("Spawning cloudflared", bin, cloudflaredArgs);
1654
+ const spawnedChild = NodeChildProcess.spawn(bin, [
1655
+ ...cloudflaredArgs,
1656
+ "run",
1657
+ "--token",
1658
+ token
1659
+ ], {
1660
+ stdio: [
1661
+ "ignore",
1662
+ "pipe",
1663
+ "pipe"
1664
+ ],
1665
+ detached: false,
1666
+ windowsHide: true,
1667
+ shell: process.platform === "win32"
1668
+ });
1669
+ child = spawnedChild;
1670
+ globalState.child = spawnedChild;
1671
+ globalState.configHash = newConfigHash;
1672
+ debugLog(`[unplugin-cloudflare-tunnel] Process spawned with PID: ${spawnedChild.pid}`);
1673
+ const handleCloudflaredOutput = (kind, text) => {
1674
+ if (text.includes("Failed to parse ICMP reply") || text.includes("unknow ip version 0")) {
1675
+ if (logLevel === "debug") console.log(`[cloudflared debug] ${text.trim()}`);
1676
+ return;
1677
+ }
1678
+ logCloudflaredLines(kind, text);
1679
+ if (/registered tunnel connection|connection.*registered/i.test(text)) {
1680
+ activeTunnelProtocol = protocol;
1681
+ if (!tunnelReady) {
1682
+ tunnelReady = true;
1683
+ pluginLog.info(`Tunnel connected for https://${hostname} via ${protocol.toUpperCase()}`);
1684
+ }
1685
+ announceNamedTunnelIfReady();
1686
+ }
1687
+ };
1688
+ spawnedChild.stdout?.on("data", (data) => {
1689
+ handleCloudflaredOutput("stdout", data.toString());
1690
+ });
1691
+ spawnedChild.stderr?.on("data", (data) => {
1692
+ handleCloudflaredOutput("stderr", data.toString());
1693
+ });
1694
+ spawnedChild.on("error", (error) => {
1695
+ console.error(`[unplugin-cloudflare-tunnel] ❌ Failed to start tunnel process: ${error.message}`);
1696
+ if (error.message.includes("ENOENT")) console.error(`[unplugin-cloudflare-tunnel] Hint: cloudflared binary may not be installed correctly`);
1697
+ });
1698
+ spawnedChild.on("exit", (code, signal) => {
1699
+ if (globalState.child !== spawnedChild) return;
1700
+ if (code !== 0 && code !== null) {
1701
+ console.error(`[unplugin-cloudflare-tunnel] ❌ Tunnel process exited with code ${code}`);
1702
+ if (signal) console.error(`[unplugin-cloudflare-tunnel] Process terminated by signal: ${signal}`);
1703
+ } else if (code === 0) console.log(`[unplugin-cloudflare-tunnel] ✅ Tunnel process exited cleanly`);
1704
+ });
1705
+ };
1706
+ spawnNamedTunnelProcess(protocol);
1707
+ registerExitHandler();
1708
+ finalizeNamedTunnelSetup().catch((error) => {
1709
+ console.error(`[unplugin-cloudflare-tunnel] ❌ Post-start setup failed: ${error.message}`);
1710
+ });
1711
+ registerListeningHandler(() => {
1712
+ const { host: actualServerHost, port: actualPort } = normalizeAddress(server.httpServer?.address());
1713
+ localTargetForAnnouncement = getLocalTarget(actualServerHost, actualPort ?? port);
1714
+ announceNamedTunnelIfReady();
1715
+ });
1716
+ server.httpServer?.once("close", () => {
1717
+ killCloudflared("SIGTERM");
1718
+ });
1719
+ registerListeningHandler(async () => {
1720
+ try {
1721
+ const { host: actualServerHost, port: actualPort } = normalizeAddress(server.httpServer?.address());
1722
+ if (server.httpServer && actualPort !== void 0 && actualPort !== port) {
1723
+ pluginLog.warn(`Port conflict detected - server is using port ${actualPort} instead of ${port}`);
1724
+ pluginLog.info("Updating tunnel configuration...");
1725
+ const newLocalTarget = getLocalTarget(actualServerHost, actualPort ?? port);
1726
+ localTargetForAnnouncement = newLocalTarget;
1727
+ debugLog("← Updating local target to", newLocalTarget);
1728
+ await cf(apiToken, "PUT", `/accounts/${accountId}/cfd_tunnel/${tunnelId}/configurations`, { config: { ingress: [{
1729
+ hostname,
1730
+ service: newLocalTarget,
1731
+ ...originRequest ? { originRequest: { httpHostHeader: `${actualServerHost}:${actualPort ?? port}` } } : {}
1732
+ }, { service: "http_status:404" }] } });
1733
+ pluginLog.info(`Tunnel configuration updated to use port ${actualPort}`);
1734
+ globalState.configHash = JSON.stringify({
1735
+ hostname,
1736
+ port: actualPort,
1737
+ tunnelName,
1738
+ dnsOption,
1739
+ sslOption,
1740
+ originRequest: server.httpServer ? void 0 : { httpHostHeader: `${actualServerHost}:${actualPort ?? port}` }
1741
+ });
1742
+ if (tunnelReady && activeTunnelProtocol) pluginLog.info(`Tunnel remains connected via ${activeTunnelProtocol.toUpperCase()} after port update`);
1743
+ announceNamedTunnelIfReady();
1744
+ }
1745
+ } catch (error) {
1746
+ console.error(`[unplugin-cloudflare-tunnel] ❌ Failed to update tunnel for port change: ${error.message}`);
1747
+ }
1748
+ });
1749
+ } catch (error) {
1750
+ if (error instanceof Error) {
1751
+ console.error(`[unplugin-cloudflare-tunnel] ❌ Setup failed: ${error.message}`);
1752
+ if (error.message.includes("API token")) {
1753
+ console.error(`[unplugin-cloudflare-tunnel] 💡 Check your API token at: https://dash.cloudflare.com/profile/api-tokens`);
1754
+ console.error(`[unplugin-cloudflare-tunnel] 💡 Required permissions: Zone:Zone:Read, Zone:DNS:Edit, Account:Cloudflare Tunnel:Edit`);
1755
+ } else if (error.message.includes("Zone") && error.message.includes("not found")) console.error(`[unplugin-cloudflare-tunnel] 💡 Make sure '${hostname}' domain is added to your Cloudflare account`);
1756
+ else if (error.message.includes("cloudflared")) console.error(`[unplugin-cloudflare-tunnel] 💡 Try deleting node_modules and reinstalling to get a fresh cloudflared binary`);
1757
+ }
1758
+ throw error;
1759
+ }
1760
+ };
1761
+ const ensureWebpackAllowedHosts = (devServerOptions, bundler) => {
1762
+ if (!devServerOptions) return;
1763
+ const hostToAllow = isQuickMode ? ".trycloudflare.com" : hostname;
1764
+ if (!hostToAllow) return;
1765
+ const label = bundler === "webpack" ? "Webpack" : "Rspack";
1766
+ const normalizeArray = (values) => {
1767
+ const unique = Array.from(new Set(values.filter(Boolean)));
1768
+ devServerOptions.allowedHosts = unique;
1769
+ return unique;
1770
+ };
1771
+ let modified = false;
1772
+ const current = devServerOptions.allowedHosts;
1773
+ if (current === "all") return;
1774
+ if (typeof current === "undefined" || current === "auto") {
1775
+ normalizeArray(["localhost", hostToAllow]);
1776
+ modified = true;
1777
+ } else if (typeof current === "string") {
1778
+ if (current !== hostToAllow) {
1779
+ normalizeArray([current, hostToAllow]);
1780
+ modified = true;
1781
+ }
1782
+ } else if (Array.isArray(current)) {
1783
+ if (!current.includes(hostToAllow)) {
1784
+ current.push(hostToAllow);
1785
+ modified = true;
1786
+ }
1787
+ }
1788
+ if (modified) debugLog(`[unplugin-cloudflare-tunnel] Configured ${label} devServer.allowedHosts to include ${hostToAllow}`);
1789
+ };
1790
+ const ensureViteAllowedHosts = (serverConfig) => {
1791
+ const hostToAllow = isQuickMode ? ".trycloudflare.com" : hostname;
1792
+ if (!hostToAllow) return;
1793
+ const current = serverConfig.allowedHosts;
1794
+ if (current === true) return;
1795
+ if (typeof current === "undefined") serverConfig.allowedHosts = [hostToAllow];
1796
+ else if (typeof current === "string") {
1797
+ if (current !== hostToAllow) serverConfig.allowedHosts = [current, hostToAllow];
1798
+ } else if (Array.isArray(current)) {
1799
+ if (!current.includes(hostToAllow)) current.push(hostToAllow);
1800
+ }
1801
+ };
1802
+ const setupWebpackVirtualScheme = (compiler) => {
1803
+ const plugins = compiler.options.plugins ??= [];
1804
+ if (plugins.some((plugin) => plugin?.__unpluginCloudflareTunnelVirtualSchemePlugin)) return;
1805
+ const context = typeof compiler.options.context === "string" && compiler.options.context.length > 0 ? compiler.options.context : process.cwd();
1806
+ let VirtualUrlPlugin;
1807
+ try {
1808
+ VirtualUrlPlugin = NodeModule.createRequire(`${context}/package.json`)("webpack/lib/schemes/VirtualUrlPlugin");
1809
+ } catch {
1810
+ return;
1811
+ }
1812
+ const virtualSchemePlugin = new VirtualUrlPlugin({ "unplugin-cloudflare-tunnel": {
1813
+ type: ".js",
1814
+ async source() {
1815
+ const url = await globalState.tunnelUrl;
1816
+ return `export function getTunnelUrl() { return ${JSON.stringify(url || "")}; }`;
1817
+ }
1818
+ } }, "virtual");
1819
+ virtualSchemePlugin.__unpluginCloudflareTunnelVirtualSchemePlugin = true;
1820
+ plugins.unshift(virtualSchemePlugin);
1821
+ virtualSchemePlugin.apply(compiler);
1822
+ };
1823
+ const setupWebpackLikeDevServerIntegration = (compiler, bundler) => {
1824
+ if ((compiler?.options?.mode ?? process.env.NODE_ENV) === "production") return;
1825
+ const optionsContainer = compiler.options;
1826
+ if (!optionsContainer.devServer) optionsContainer.devServer = {};
1827
+ const devServerOptions = optionsContainer.devServer;
1828
+ ensureWebpackAllowedHosts(devServerOptions, bundler);
1829
+ let lastHttpServer;
1830
+ let missingServerWarned = false;
1831
+ const runConfiguration = (devServerInstance) => {
1832
+ if (!devServerInstance) {
1833
+ if (!missingServerWarned) {
1834
+ console.warn(`[unplugin-cloudflare-tunnel] ${bundler} dev server instance unavailable; skipping tunnel setup`);
1835
+ missingServerWarned = true;
1836
+ }
1837
+ return;
1838
+ }
1839
+ const httpServer = [
1840
+ devServerInstance.server,
1841
+ devServerInstance.httpServer,
1842
+ devServerInstance.listeningApp,
1843
+ devServerInstance.server?.server
1844
+ ].find((candidate) => candidate);
1845
+ if (!httpServer) {
1846
+ if (!missingServerWarned) {
1847
+ console.warn(`[unplugin-cloudflare-tunnel] Unable to locate HTTP server from ${bundler} dev server; tunnel will not start`);
1848
+ missingServerWarned = true;
1849
+ }
1850
+ return;
1851
+ }
1852
+ if (lastHttpServer === httpServer) return;
1853
+ lastHttpServer = httpServer;
1854
+ httpServer.once("close", () => {
1855
+ if (lastHttpServer === httpServer) lastHttpServer = void 0;
1856
+ });
1857
+ const configuredPromise = configureServer({
1858
+ httpServer,
1859
+ config: { server: { port: devServerInstance.options?.port ?? devServerOptions?.port } }
1860
+ });
1861
+ globalState.tunnelUrl = configuredPromise.then(() => tunnelUrl).catch(() => "");
1862
+ configuredPromise.catch(() => {});
1863
+ };
1864
+ const scheduleConfiguration = (devServerInstance) => {
1865
+ const httpServer = devServerInstance?.server || devServerInstance?.httpServer || devServerInstance?.listeningApp;
1866
+ if (httpServer && typeof httpServer.once === "function") if (httpServer.listening) runConfiguration(devServerInstance);
1867
+ else httpServer.once("listening", () => runConfiguration(devServerInstance));
1868
+ else runConfiguration(devServerInstance);
1869
+ };
1870
+ const originalSetupMiddlewares = devServerOptions.setupMiddlewares;
1871
+ devServerOptions.setupMiddlewares = function(middlewares, devServer) {
1872
+ scheduleConfiguration(devServer);
1873
+ if (typeof originalSetupMiddlewares === "function") return originalSetupMiddlewares.call(this, middlewares, devServer);
1874
+ return middlewares;
1875
+ };
1876
+ const originalOnListening = devServerOptions.onListening;
1877
+ devServerOptions.onListening = function(devServer) {
1878
+ scheduleConfiguration(devServer);
1879
+ if (typeof originalOnListening === "function") return originalOnListening.call(this, devServer);
1880
+ };
1881
+ };
1882
+ return {
1883
+ name: PLUGIN_NAME,
1884
+ enforce: "pre",
1885
+ resolveId(id) {
1886
+ if (id === VIRTUAL_MODULE_ID) {
1887
+ debugLog("resolveId called for", id);
1888
+ return id;
1889
+ }
1890
+ },
1891
+ loadInclude(id) {
1892
+ return id === VIRTUAL_MODULE_ID;
1893
+ },
1894
+ async load(id) {
1895
+ if (id === VIRTUAL_MODULE_ID) {
1896
+ const url = await globalState.tunnelUrl;
1897
+ return `export function getTunnelUrl() { return ${JSON.stringify(url || "")}; }`;
1898
+ }
1899
+ },
1900
+ vite: {
1901
+ config: (config) => {
1902
+ announceConnecting();
1903
+ if (!config.server) config.server = {};
1904
+ ensureViteAllowedHosts(config.server);
1905
+ if (!isQuickMode) debugLog(`[unplugin-cloudflare-tunnel] Configured Vite to allow requests from ${hostname}`);
1906
+ },
1907
+ configureServer: (server) => {
1908
+ const configuredPromise = configureServer(server);
1909
+ globalState.tunnelUrl = configuredPromise.then(() => tunnelUrl).catch(() => "");
1910
+ return async () => {
1911
+ await configuredPromise;
1912
+ };
1913
+ }
1914
+ },
1915
+ esbuild: { config() {
1916
+ announceConnecting();
1917
+ if (typeof userProvidedPort === "number" && !Number.isNaN(userProvidedPort)) globalState.tunnelUrl = configureServer({ config: { server: { port: userProvidedPort } } }).then(() => tunnelUrl).catch(() => "");
1918
+ else {
1919
+ globalState.tunnelUrl = Promise.resolve("");
1920
+ console.warn("[unplugin-cloudflare-tunnel] esbuild requires the plugin `port` option to enable tunnel startup");
1921
+ }
1922
+ if (!isQuickMode) debugLog(`[unplugin-cloudflare-tunnel] Configured esbuild tunnel target for ${hostname}`);
1923
+ } },
1924
+ rspack: (compiler) => {
1925
+ setupWebpackLikeDevServerIntegration(compiler, "rspack");
1926
+ },
1927
+ webpack: (compiler) => {
1928
+ setupWebpackVirtualScheme(compiler);
1929
+ setupWebpackLikeDevServerIntegration(compiler, "webpack");
1930
+ },
1931
+ buildStart() {
1932
+ if (!this?.meta?.watchMode) return;
1933
+ if (typeof userProvidedPort !== "number" || Number.isNaN(userProvidedPort)) return;
1934
+ if (globalState.tunnelUrl) return;
1935
+ announceConnecting();
1936
+ globalState.tunnelUrl = configureServer({ config: { server: { port: userProvidedPort } } }).then(() => tunnelUrl).catch(() => "");
1937
+ },
1938
+ closeBundle() {
1939
+ if (this?.meta?.watchMode) return;
1940
+ killCloudflared("SIGTERM");
1941
+ delete globalState.child;
1942
+ delete globalState.configHash;
1943
+ delete globalState.shuttingDown;
1944
+ }
1945
+ };
1946
+ };
1947
+ const CloudflareTunnel = createUnplugin(unpluginFactory);
1948
+ //#endregion
1949
+ export { CloudflareTunnel as t };