vite-plugin-caddy-multiple-tls 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/index.js +231 -108
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# vite-plugin-caddy-multiple-tls
|
|
2
2
|
|
|
3
|
+
## What it does
|
|
4
|
+
Runs Caddy alongside Vite to give you HTTPS locally with automatic, per-branch domains like `<repo>.<branch>.localhost`, so you can use real hostnames, cookies, and secure APIs without manual proxy setup.
|
|
5
|
+
|
|
3
6
|
## Usage
|
|
4
7
|
|
|
5
8
|
```js
|
|
@@ -112,7 +115,7 @@ api.app.localhost
|
|
|
112
115
|
> - `nip.io`: dynamic parsing of the IP in the hostname (e.g. `app.192.168.1.50.nip.io`) so you can target LAN devices.
|
|
113
116
|
> Why use them: subdomains behave like real domains, no `/etc/hosts` edits, and closer parity for cookies/CORS rules.
|
|
114
117
|
>
|
|
115
|
-
> When using loopback domains, ensure your Vite config allows the Host header, e.g. `server: { allowedHosts: true }`.
|
|
118
|
+
> When using loopback domains, ensure your Vite config allows the Host header and binds to all interfaces, e.g. `server: { allowedHosts: true, host: true }`.
|
|
116
119
|
>
|
|
117
120
|
> For a permanent fix that handles all `*.localhost` domains automatically, install dnsmasq:
|
|
118
121
|
> ```bash
|
package/dist/index.js
CHANGED
|
@@ -174,7 +174,13 @@ async function ensureTlsAutomation() {
|
|
|
174
174
|
throw new Error(`Failed to initialize Caddy TLS automation: ${text}`);
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
|
-
|
|
177
|
+
function formatDialAddress(host, port) {
|
|
178
|
+
if (host.includes(":") && !host.startsWith("[")) {
|
|
179
|
+
return `[${host}]:${port}`;
|
|
180
|
+
}
|
|
181
|
+
return `${host}:${port}`;
|
|
182
|
+
}
|
|
183
|
+
async function addRoute(id, domains, port, cors, serverName = DEFAULT_SERVER_NAME, upstreamHost = "127.0.0.1") {
|
|
178
184
|
const handlers = [];
|
|
179
185
|
if (cors) {
|
|
180
186
|
handlers.push({
|
|
@@ -197,7 +203,7 @@ async function addRoute(id, domains, port, cors, serverName = DEFAULT_SERVER_NAM
|
|
|
197
203
|
}
|
|
198
204
|
handlers.push({
|
|
199
205
|
handler: "reverse_proxy",
|
|
200
|
-
upstreams: [{ dial:
|
|
206
|
+
upstreams: [{ dial: formatDialAddress(upstreamHost, port) }]
|
|
201
207
|
});
|
|
202
208
|
const route = {
|
|
203
209
|
"@id": id,
|
|
@@ -311,6 +317,15 @@ function resolveBaseDomain(options) {
|
|
|
311
317
|
}
|
|
312
318
|
return "localhost";
|
|
313
319
|
}
|
|
320
|
+
function resolveUpstreamHost(host) {
|
|
321
|
+
if (typeof host === "string") {
|
|
322
|
+
const trimmed = host.trim();
|
|
323
|
+
if (trimmed && trimmed !== "0.0.0.0" && trimmed !== "::") {
|
|
324
|
+
return trimmed;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return "127.0.0.1";
|
|
328
|
+
}
|
|
314
329
|
function sanitizeDomainLabel(value) {
|
|
315
330
|
return value.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
316
331
|
}
|
|
@@ -351,132 +366,240 @@ function viteCaddyTlsPlugin({
|
|
|
351
366
|
serverName,
|
|
352
367
|
internalTls
|
|
353
368
|
} = {}) {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
369
|
+
function isPreviewServer(server) {
|
|
370
|
+
return server.config.isProduction;
|
|
371
|
+
}
|
|
372
|
+
function getPreviewPort(config) {
|
|
373
|
+
if (typeof config.preview?.port === "number") {
|
|
374
|
+
return config.preview.port;
|
|
375
|
+
}
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
function getPreviewHost(config) {
|
|
379
|
+
if (config.preview && "host" in config.preview) {
|
|
380
|
+
return resolveUpstreamHost(config.preview.host);
|
|
381
|
+
}
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
function hasListen(server) {
|
|
385
|
+
return !!server && typeof server === "object" && "listen" in server && typeof server.listen === "function";
|
|
386
|
+
}
|
|
387
|
+
function setupServer(server) {
|
|
388
|
+
const { httpServer, config } = server;
|
|
389
|
+
const previewMode = isPreviewServer(server);
|
|
390
|
+
const fallbackPort = previewMode ? getPreviewPort(config) ?? 4173 : config.server.port || 5173;
|
|
391
|
+
const resolvedDomain = resolveDomain({
|
|
392
|
+
domain,
|
|
393
|
+
baseDomain,
|
|
394
|
+
loopbackDomain,
|
|
395
|
+
repo,
|
|
396
|
+
branch
|
|
397
|
+
});
|
|
398
|
+
const domainArray = resolvedDomain ? [resolvedDomain] : [];
|
|
399
|
+
const routeId = `vite-proxy-${Date.now()}-${Math.floor(Math.random() * 1e3)}`;
|
|
400
|
+
const shouldUseInternalTls = internalTls ?? (baseDomain !== void 0 || loopbackDomain !== void 0 || domain !== void 0);
|
|
401
|
+
const tlsPolicyId = shouldUseInternalTls ? `${routeId}-tls` : null;
|
|
402
|
+
let cleanupStarted = false;
|
|
403
|
+
let resolvedPort = null;
|
|
404
|
+
let resolvedHost = null;
|
|
405
|
+
let setupStarted = false;
|
|
406
|
+
if (domainArray.length === 0) {
|
|
407
|
+
console.error(
|
|
408
|
+
"No domain resolved. Provide domain, or run inside a git repo, or pass repo/branch."
|
|
409
|
+
);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
let tlsPolicyAdded = false;
|
|
413
|
+
function getPortFromAddress(address) {
|
|
414
|
+
if (address && typeof address === "object" && "port" in address) {
|
|
415
|
+
const port = address.port;
|
|
416
|
+
if (typeof port === "number") {
|
|
417
|
+
return port;
|
|
418
|
+
}
|
|
375
419
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
function updateResolvedTarget() {
|
|
423
|
+
if (resolvedPort !== null && resolvedHost !== null) return;
|
|
424
|
+
const resolvedUrl = server.resolvedUrls?.local?.[0];
|
|
425
|
+
if (resolvedUrl) {
|
|
426
|
+
try {
|
|
427
|
+
const url = new URL(resolvedUrl);
|
|
428
|
+
if (resolvedHost === null && url.hostname) {
|
|
429
|
+
resolvedHost = url.hostname === "localhost" ? "127.0.0.1" : url.hostname;
|
|
430
|
+
}
|
|
431
|
+
const port = Number(url.port);
|
|
432
|
+
if (resolvedPort === null && !Number.isNaN(port)) {
|
|
433
|
+
resolvedPort = port;
|
|
434
|
+
}
|
|
435
|
+
} catch (e) {
|
|
382
436
|
}
|
|
383
|
-
return fallbackPort;
|
|
384
437
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
438
|
+
if (httpServer) {
|
|
439
|
+
const address = httpServer.address();
|
|
440
|
+
if (address && typeof address === "object") {
|
|
441
|
+
const port = getPortFromAddress(address);
|
|
442
|
+
if (resolvedPort === null && port !== null) {
|
|
443
|
+
resolvedPort = port;
|
|
444
|
+
}
|
|
445
|
+
if (resolvedHost === null && "address" in address) {
|
|
446
|
+
const host = address.address;
|
|
447
|
+
if (typeof host === "string" && host !== "0.0.0.0" && host !== "::") {
|
|
448
|
+
resolvedHost = host;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
390
451
|
}
|
|
391
|
-
await removeRoute(routeId);
|
|
392
452
|
}
|
|
393
|
-
|
|
394
|
-
|
|
453
|
+
if (resolvedPort === null && typeof config.server.port === "number") {
|
|
454
|
+
resolvedPort = config.server.port;
|
|
395
455
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
});
|
|
456
|
+
if (previewMode && resolvedPort === null) {
|
|
457
|
+
const previewPort = getPreviewPort(config);
|
|
458
|
+
if (previewPort !== null) {
|
|
459
|
+
resolvedPort = previewPort;
|
|
460
|
+
}
|
|
402
461
|
}
|
|
403
|
-
|
|
404
|
-
|
|
462
|
+
if (resolvedHost === null) {
|
|
463
|
+
if (previewMode) {
|
|
464
|
+
resolvedHost = getPreviewHost(config);
|
|
465
|
+
}
|
|
466
|
+
if (resolvedHost === null) {
|
|
467
|
+
resolvedHost = resolveUpstreamHost(config.server.host);
|
|
468
|
+
}
|
|
405
469
|
}
|
|
406
|
-
|
|
407
|
-
|
|
470
|
+
}
|
|
471
|
+
function getServerPort() {
|
|
472
|
+
updateResolvedTarget();
|
|
473
|
+
return resolvedPort ?? fallbackPort;
|
|
474
|
+
}
|
|
475
|
+
function getUpstreamHost() {
|
|
476
|
+
updateResolvedTarget();
|
|
477
|
+
return resolvedHost ?? "127.0.0.1";
|
|
478
|
+
}
|
|
479
|
+
async function cleanupRoute() {
|
|
480
|
+
if (cleanupStarted) return;
|
|
481
|
+
cleanupStarted = true;
|
|
482
|
+
if (tlsPolicyId) {
|
|
483
|
+
await removeTlsPolicy(tlsPolicyId);
|
|
408
484
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
485
|
+
await removeRoute(routeId);
|
|
486
|
+
}
|
|
487
|
+
function onServerClose() {
|
|
488
|
+
void cleanupRoute();
|
|
489
|
+
}
|
|
490
|
+
function handleSignal(signal) {
|
|
491
|
+
process.off("SIGINT", onSigint);
|
|
492
|
+
process.off("SIGTERM", onSigterm);
|
|
493
|
+
void cleanupRoute().finally(() => {
|
|
494
|
+
process.kill(process.pid, signal);
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
function onSigint() {
|
|
498
|
+
handleSignal("SIGINT");
|
|
499
|
+
}
|
|
500
|
+
function onSigterm() {
|
|
501
|
+
handleSignal("SIGTERM");
|
|
502
|
+
}
|
|
503
|
+
function registerProcessCleanup() {
|
|
504
|
+
process.once("SIGINT", onSigint);
|
|
505
|
+
process.once("SIGTERM", onSigterm);
|
|
506
|
+
}
|
|
507
|
+
async function setupRoute() {
|
|
508
|
+
if (!validateCaddyIsInstalled()) {
|
|
509
|
+
return;
|
|
412
510
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
}
|
|
417
|
-
let running = await isCaddyRunning();
|
|
511
|
+
let running = await isCaddyRunning();
|
|
512
|
+
if (!running) {
|
|
513
|
+
running = await startCaddy();
|
|
418
514
|
if (!running) {
|
|
419
|
-
|
|
420
|
-
if (!running) {
|
|
421
|
-
console.error("Failed to start Caddy server.");
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
try {
|
|
426
|
-
await ensureBaseConfig(serverName);
|
|
427
|
-
} catch (e) {
|
|
428
|
-
console.error("Failed to configure Caddy base settings.", e);
|
|
515
|
+
console.error("Failed to start Caddy server.");
|
|
429
516
|
return;
|
|
430
517
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
518
|
+
}
|
|
519
|
+
try {
|
|
520
|
+
await ensureBaseConfig(serverName);
|
|
521
|
+
} catch (e) {
|
|
522
|
+
console.error("Failed to configure Caddy base settings.", e);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const port = getServerPort();
|
|
526
|
+
const upstreamHost = getUpstreamHost();
|
|
527
|
+
if (tlsPolicyId) {
|
|
441
528
|
try {
|
|
442
|
-
await
|
|
529
|
+
await addTlsPolicy(tlsPolicyId, domainArray);
|
|
530
|
+
tlsPolicyAdded = true;
|
|
443
531
|
} catch (e) {
|
|
444
|
-
|
|
445
|
-
await removeTlsPolicy(tlsPolicyId);
|
|
446
|
-
}
|
|
447
|
-
console.error("Failed to add route to Caddy.", e);
|
|
532
|
+
console.error("Failed to add TLS policy to Caddy.", e);
|
|
448
533
|
return;
|
|
449
534
|
}
|
|
450
|
-
console.log();
|
|
451
|
-
console.log("\u{1F512} Caddy is proxying your traffic on https");
|
|
452
|
-
console.log();
|
|
453
|
-
console.log(
|
|
454
|
-
`\u{1F517} Access your local ${domainArray.length > 1 ? "servers" : "server"} `
|
|
455
|
-
);
|
|
456
|
-
domainArray.forEach((domain2) => {
|
|
457
|
-
console.log(`\u{1F30D} https://${domain2}`);
|
|
458
|
-
});
|
|
459
|
-
if (process.platform === "linux" && !loopbackDomain) {
|
|
460
|
-
console.log();
|
|
461
|
-
console.log("\u{1F427} Linux users: if the domain doesn't resolve, run:");
|
|
462
|
-
domainArray.forEach((domain2) => {
|
|
463
|
-
console.log(` echo "127.0.0.1 ${domain2}" | sudo tee -a /etc/hosts`);
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
console.log();
|
|
467
|
-
registerProcessCleanup();
|
|
468
|
-
httpServer?.once("close", onServerClose);
|
|
469
535
|
}
|
|
470
|
-
|
|
471
|
-
|
|
536
|
+
try {
|
|
537
|
+
await addRoute(routeId, domainArray, port, cors, serverName, upstreamHost);
|
|
538
|
+
} catch (e) {
|
|
539
|
+
if (tlsPolicyAdded && tlsPolicyId) {
|
|
540
|
+
await removeTlsPolicy(tlsPolicyId);
|
|
541
|
+
}
|
|
542
|
+
console.error("Failed to add route to Caddy.", e);
|
|
543
|
+
return;
|
|
472
544
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
545
|
+
console.log("\n\u{1F512} Caddy is proxying your traffic on https");
|
|
546
|
+
console.log(
|
|
547
|
+
`
|
|
548
|
+
\u{1F517} Access your local ${domainArray.length > 1 ? "servers" : "server"}!`
|
|
549
|
+
);
|
|
550
|
+
domainArray.forEach((domain2) => {
|
|
551
|
+
console.log(`\u{1F30D} https://${domain2}`);
|
|
552
|
+
});
|
|
553
|
+
if (process.platform === "linux" && !loopbackDomain) {
|
|
554
|
+
console.log("\n\u{1F427} Linux users: if the domain doesn't resolve, run:");
|
|
555
|
+
domainArray.forEach((domain2) => {
|
|
556
|
+
console.log(` echo "127.0.0.1 ${domain2}" | sudo tee -a /etc/hosts`);
|
|
557
|
+
});
|
|
479
558
|
}
|
|
559
|
+
console.log();
|
|
560
|
+
registerProcessCleanup();
|
|
561
|
+
httpServer?.once("close", onServerClose);
|
|
562
|
+
}
|
|
563
|
+
function runSetupOnce() {
|
|
564
|
+
if (setupStarted) return;
|
|
565
|
+
setupStarted = true;
|
|
566
|
+
void setupRoute();
|
|
567
|
+
}
|
|
568
|
+
function wrapServerListen() {
|
|
569
|
+
if (!hasListen(server)) return false;
|
|
570
|
+
const originalListen = server.listen.bind(server);
|
|
571
|
+
server.listen = async function(port, isRestart) {
|
|
572
|
+
const result = await originalListen(port, isRestart);
|
|
573
|
+
if (typeof port === "number") {
|
|
574
|
+
resolvedPort = port;
|
|
575
|
+
} else {
|
|
576
|
+
updateResolvedTarget();
|
|
577
|
+
}
|
|
578
|
+
runSetupOnce();
|
|
579
|
+
return result;
|
|
580
|
+
};
|
|
581
|
+
return true;
|
|
582
|
+
}
|
|
583
|
+
function onListening() {
|
|
584
|
+
updateResolvedTarget();
|
|
585
|
+
runSetupOnce();
|
|
586
|
+
}
|
|
587
|
+
const listenWrapped = wrapServerListen();
|
|
588
|
+
if (httpServer?.listening) {
|
|
589
|
+
runSetupOnce();
|
|
590
|
+
} else if (httpServer) {
|
|
591
|
+
httpServer.once("listening", onListening);
|
|
592
|
+
} else if (!listenWrapped) {
|
|
593
|
+
runSetupOnce();
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return {
|
|
597
|
+
name: "vite:caddy-tls",
|
|
598
|
+
configureServer(server) {
|
|
599
|
+
setupServer(server);
|
|
600
|
+
},
|
|
601
|
+
configurePreviewServer(server) {
|
|
602
|
+
setupServer(server);
|
|
480
603
|
}
|
|
481
604
|
};
|
|
482
605
|
}
|