vite-plugin-caddy-multiple-tls 1.7.0 → 1.8.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 +69 -41
- package/dist/index.d.ts +15 -3
- package/dist/index.js +96 -99
- package/package.json +20 -20
package/README.md
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
# vite-plugin-caddy-multiple-tls
|
|
2
2
|
|
|
3
3
|
## What it does
|
|
4
|
+
|
|
4
5
|
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
6
|
|
|
6
7
|
## Usage
|
|
7
8
|
|
|
8
9
|
```js
|
|
9
10
|
// vite.config.js
|
|
10
|
-
import { defineConfig } from
|
|
11
|
-
import caddyTls from
|
|
11
|
+
import { defineConfig } from "vite";
|
|
12
|
+
import caddyTls from "vite-plugin-caddy-multiple-tls";
|
|
12
13
|
|
|
13
14
|
const config = defineConfig({
|
|
14
|
-
plugins: [
|
|
15
|
-
caddyTls(),
|
|
16
|
-
]
|
|
15
|
+
plugins: [caddyTls()],
|
|
17
16
|
});
|
|
18
17
|
|
|
19
18
|
export default config;
|
|
@@ -22,6 +21,7 @@ export default config;
|
|
|
22
21
|
Will give this in the terminal, allow you to connect to your app on HTTPS with a self-signed and trusted cert.
|
|
23
22
|
|
|
24
23
|
The plugin defaults `server.host = true` and `server.allowedHosts = true` (plus preview equivalents) so custom hostnames work without extra config. When a domain is resolved, it also defaults `server.hmr` to use `wss` on port `443` and the resolved host, isolating multiple Vite instances without extra config. Override these in your Vite config if you need different values.
|
|
24
|
+
|
|
25
25
|
```
|
|
26
26
|
> vite
|
|
27
27
|
|
|
@@ -36,19 +36,37 @@ The plugin defaults `server.host = true` and `server.allowedHosts = true` (plus
|
|
|
36
36
|
By default, the plugin derives `<repo>.<branch>.localhost` from git.
|
|
37
37
|
If repo or branch can't be detected, pass `repo`/`branch` or use `domain`.
|
|
38
38
|
|
|
39
|
+
If you need the resolved hostname in tooling, the package also exports pure helpers:
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
import { resolveCaddyTlsDomains, resolveCaddyTlsUrl } from "vite-plugin-caddy-multiple-tls";
|
|
43
|
+
|
|
44
|
+
const url = resolveCaddyTlsUrl({
|
|
45
|
+
baseDomain: "localhost",
|
|
46
|
+
});
|
|
47
|
+
// https://my-repo.my-branch.localhost
|
|
48
|
+
|
|
49
|
+
const domains = resolveCaddyTlsDomains({
|
|
50
|
+
domain: ["app.localhost", "api.localhost"],
|
|
51
|
+
});
|
|
52
|
+
// ['app.localhost', 'api.localhost']
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`resolveCaddyTlsUrl()` returns `null` when the config resolves to zero or multiple domains.
|
|
56
|
+
|
|
39
57
|
If you want a fixed host without repo/branch in the URL, pass a single domain:
|
|
40
58
|
|
|
41
59
|
```js
|
|
42
60
|
// vite.config.js
|
|
43
|
-
import { defineConfig } from
|
|
44
|
-
import caddyTls from
|
|
61
|
+
import { defineConfig } from "vite";
|
|
62
|
+
import caddyTls from "vite-plugin-caddy-multiple-tls";
|
|
45
63
|
|
|
46
64
|
const config = defineConfig({
|
|
47
65
|
plugins: [
|
|
48
66
|
caddyTls({
|
|
49
|
-
domain:
|
|
50
|
-
})
|
|
51
|
-
]
|
|
67
|
+
domain: "app.localhost",
|
|
68
|
+
}),
|
|
69
|
+
],
|
|
52
70
|
});
|
|
53
71
|
|
|
54
72
|
export default config;
|
|
@@ -58,15 +76,15 @@ You can also pass multiple explicit domains:
|
|
|
58
76
|
|
|
59
77
|
```js
|
|
60
78
|
// vite.config.js
|
|
61
|
-
import { defineConfig } from
|
|
62
|
-
import caddyTls from
|
|
79
|
+
import { defineConfig } from "vite";
|
|
80
|
+
import caddyTls from "vite-plugin-caddy-multiple-tls";
|
|
63
81
|
|
|
64
82
|
const config = defineConfig({
|
|
65
83
|
plugins: [
|
|
66
84
|
caddyTls({
|
|
67
|
-
domain: [
|
|
68
|
-
})
|
|
69
|
-
]
|
|
85
|
+
domain: ["app.localhost", "api.localhost"],
|
|
86
|
+
}),
|
|
87
|
+
],
|
|
70
88
|
});
|
|
71
89
|
|
|
72
90
|
export default config;
|
|
@@ -76,15 +94,15 @@ To derive a domain like `<repo>.<branch>.<baseDomain>` automatically from git (r
|
|
|
76
94
|
|
|
77
95
|
```js
|
|
78
96
|
// vite.config.js
|
|
79
|
-
import { defineConfig } from
|
|
80
|
-
import caddyTls from
|
|
97
|
+
import { defineConfig } from "vite";
|
|
98
|
+
import caddyTls from "vite-plugin-caddy-multiple-tls";
|
|
81
99
|
|
|
82
100
|
const config = defineConfig({
|
|
83
101
|
plugins: [
|
|
84
102
|
caddyTls({
|
|
85
|
-
baseDomain:
|
|
86
|
-
})
|
|
87
|
-
]
|
|
103
|
+
baseDomain: "local.conekto.eu",
|
|
104
|
+
}),
|
|
105
|
+
],
|
|
88
106
|
});
|
|
89
107
|
|
|
90
108
|
export default config;
|
|
@@ -96,15 +114,15 @@ If you run different projects that derive the same `<repo>.<branch>` host, add `
|
|
|
96
114
|
|
|
97
115
|
```js
|
|
98
116
|
// vite.config.js
|
|
99
|
-
import { defineConfig } from
|
|
100
|
-
import caddyTls from
|
|
117
|
+
import { defineConfig } from "vite";
|
|
118
|
+
import caddyTls from "vite-plugin-caddy-multiple-tls";
|
|
101
119
|
|
|
102
120
|
const config = defineConfig({
|
|
103
121
|
plugins: [
|
|
104
122
|
caddyTls({
|
|
105
|
-
instanceLabel:
|
|
106
|
-
})
|
|
107
|
-
]
|
|
123
|
+
instanceLabel: "web-1",
|
|
124
|
+
}),
|
|
125
|
+
],
|
|
108
126
|
});
|
|
109
127
|
|
|
110
128
|
export default config;
|
|
@@ -126,15 +144,15 @@ If your Caddy Admin API is not on the default `http://localhost:2019`, set `cadd
|
|
|
126
144
|
|
|
127
145
|
```js
|
|
128
146
|
// vite.config.js
|
|
129
|
-
import { defineConfig } from
|
|
130
|
-
import caddyTls from
|
|
147
|
+
import { defineConfig } from "vite";
|
|
148
|
+
import caddyTls from "vite-plugin-caddy-multiple-tls";
|
|
131
149
|
|
|
132
150
|
const config = defineConfig({
|
|
133
151
|
plugins: [
|
|
134
152
|
caddyTls({
|
|
135
|
-
caddyApiUrl:
|
|
136
|
-
})
|
|
137
|
-
]
|
|
153
|
+
caddyApiUrl: "http://localhost:2020",
|
|
154
|
+
}),
|
|
155
|
+
],
|
|
138
156
|
});
|
|
139
157
|
|
|
140
158
|
export default config;
|
|
@@ -144,16 +162,16 @@ If your Caddy Admin API enforces a specific allowed origin that differs from `ca
|
|
|
144
162
|
|
|
145
163
|
```js
|
|
146
164
|
// vite.config.js
|
|
147
|
-
import { defineConfig } from
|
|
148
|
-
import caddyTls from
|
|
165
|
+
import { defineConfig } from "vite";
|
|
166
|
+
import caddyTls from "vite-plugin-caddy-multiple-tls";
|
|
149
167
|
|
|
150
168
|
const config = defineConfig({
|
|
151
169
|
plugins: [
|
|
152
170
|
caddyTls({
|
|
153
|
-
caddyApiUrl:
|
|
154
|
-
caddyAdminOrigin:
|
|
155
|
-
})
|
|
156
|
-
]
|
|
171
|
+
caddyApiUrl: "http://127.0.0.1:2019",
|
|
172
|
+
caddyAdminOrigin: "http://localhost:2019",
|
|
173
|
+
}),
|
|
174
|
+
],
|
|
157
175
|
});
|
|
158
176
|
|
|
159
177
|
export default config;
|
|
@@ -182,11 +200,13 @@ curl -i http://127.0.0.1:2019/config/
|
|
|
182
200
|
curl -i -H 'Origin: http://127.0.0.1:2019' http://127.0.0.1:2019/config/
|
|
183
201
|
```
|
|
184
202
|
|
|
185
|
-
> [!IMPORTANT]
|
|
203
|
+
> [!IMPORTANT]
|
|
186
204
|
> **Hosts file limitation:** If you use a custom domain, you must **manually** add each generated subdomain to your `/etc/hosts` file (e.g., `127.0.0.1 repo.branch.local.example.test`). System hosts files **do not support wildcards** (e.g., `*.local.example.test`), so you lose the benefit of automatic domain resolution that `localhost` provides.
|
|
187
205
|
|
|
188
206
|
## Recommended base domain: `.localhost`
|
|
207
|
+
|
|
189
208
|
Why `localhost` is the best option for local development:
|
|
209
|
+
|
|
190
210
|
- Reserved by RFC 6761 (never on the public internet).
|
|
191
211
|
- Automatic resolution on macOS: `*.localhost` maps to `127.0.0.1` and `::1` without DNS or `/etc/hosts`.
|
|
192
212
|
- Subdomain support: `api.localhost`, `foo.bar.localhost`, etc.
|
|
@@ -194,6 +214,7 @@ Why `localhost` is the best option for local development:
|
|
|
194
214
|
- Works well with Caddy and other local reverse proxies.
|
|
195
215
|
|
|
196
216
|
Example usage:
|
|
217
|
+
|
|
197
218
|
```
|
|
198
219
|
app.localhost
|
|
199
220
|
api.app.localhost
|
|
@@ -201,27 +222,32 @@ api.app.localhost
|
|
|
201
222
|
|
|
202
223
|
> [!NOTE]
|
|
203
224
|
> **Linux users:** Unlike macOS, most Linux distributions don't automatically resolve `*.localhost` subdomains. The plugin will detect Linux and show you the exact command to run:
|
|
225
|
+
>
|
|
204
226
|
> ```
|
|
205
227
|
> 🐧 Linux users: if the domain doesn't resolve, run:
|
|
206
228
|
> echo "127.0.0.1 my-repo.my-branch.localhost" | sudo tee -a /etc/hosts
|
|
207
229
|
> ```
|
|
208
230
|
>
|
|
209
231
|
> If you want to avoid `/etc/hosts` edits on Linux, set `loopbackDomain` to a public loopback domain:
|
|
232
|
+
>
|
|
210
233
|
> ```ts
|
|
211
234
|
> caddyTls({
|
|
212
|
-
> loopbackDomain:
|
|
213
|
-
> })
|
|
235
|
+
> loopbackDomain: "localtest.me",
|
|
236
|
+
> });
|
|
214
237
|
> ```
|
|
238
|
+
>
|
|
215
239
|
> Supported values: `localtest.me`, `lvh.me`, `nip.io` (maps to `127.0.0.1.nip.io`). These rely on public DNS, so they can fail offline or on restricted networks.
|
|
216
240
|
>
|
|
217
241
|
> Why these work: they use wildcard DNS so any subdomain resolves to `127.0.0.1`, meaning the request loops back to your machine after DNS.
|
|
242
|
+
>
|
|
218
243
|
> - `localtest.me` and `lvh.me`: static wildcard -> always `127.0.0.1` (great for subdomain testing).
|
|
219
244
|
> - `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.
|
|
220
|
-
>
|
|
245
|
+
> Why use them: subdomains behave like real domains, no `/etc/hosts` edits, and closer parity for cookies/CORS rules.
|
|
221
246
|
>
|
|
222
247
|
> When using loopback domains, ensure your Vite config allows the Host header and binds to all interfaces, e.g. `server: { allowedHosts: true, host: true }`.
|
|
223
248
|
>
|
|
224
249
|
> For a permanent fix that handles all `*.localhost` domains automatically, install dnsmasq:
|
|
250
|
+
>
|
|
225
251
|
> ```bash
|
|
226
252
|
> sudo apt install dnsmasq
|
|
227
253
|
> echo "address=/.localhost/127.0.0.1" | sudo tee /etc/dnsmasq.d/localhost.conf
|
|
@@ -229,13 +255,15 @@ api.app.localhost
|
|
|
229
255
|
> ```
|
|
230
256
|
|
|
231
257
|
## Development
|
|
258
|
+
|
|
232
259
|
This repo uses npm workspaces. Install from the root with `npm install`, then run workspace scripts like `npm run build --workspace packages/plugin` or `npm run dev --workspace playground`.
|
|
233
260
|
|
|
234
261
|
The published package README is synced from the root `README.md` via `packages/plugin/scripts/sync-readme.sh`.
|
|
235
262
|
|
|
236
263
|
## Contributing
|
|
264
|
+
|
|
237
265
|
See [CONTRIBUTING.md](./CONTRIBUTING.md) to see how to get started.
|
|
238
|
-
|
|
266
|
+
|
|
239
267
|
## License
|
|
240
268
|
|
|
241
269
|
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { PluginOption } from 'vite';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
type LoopbackDomain = "localtest.me" | "lvh.me" | "nip.io";
|
|
4
|
+
|
|
5
|
+
interface CaddyTlsDomainOptions {
|
|
6
|
+
domain?: string | string[];
|
|
7
|
+
baseDomain?: string;
|
|
8
|
+
loopbackDomain?: LoopbackDomain;
|
|
9
|
+
repo?: string;
|
|
10
|
+
branch?: string;
|
|
11
|
+
instanceLabel?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface ViteCaddyTlsPluginOptions extends CaddyTlsDomainOptions {
|
|
4
15
|
/** Explicit domain to proxy without repo/branch derivation */
|
|
5
16
|
domain?: string | string[];
|
|
6
17
|
/** Base domain to build <repo>.<branch>.<baseDomain> (defaults to localhost) */
|
|
@@ -28,7 +39,8 @@ interface ViteCaddyTlsPluginOptions {
|
|
|
28
39
|
*/
|
|
29
40
|
upstreamHostHeader?: string;
|
|
30
41
|
}
|
|
31
|
-
|
|
42
|
+
declare function resolveCaddyTlsDomains(options?: ViteCaddyTlsPluginOptions): string[] | null;
|
|
43
|
+
declare function resolveCaddyTlsUrl(options?: ViteCaddyTlsPluginOptions): string | null;
|
|
32
44
|
/**
|
|
33
45
|
* Vite plugin to run Caddy server to proxy traffic on https for local development
|
|
34
46
|
*
|
|
@@ -43,4 +55,4 @@ type LoopbackDomain = 'localtest.me' | 'lvh.me' | 'nip.io';
|
|
|
43
55
|
*/
|
|
44
56
|
declare function viteCaddyTlsPlugin({ domain, baseDomain, loopbackDomain, repo, branch, instanceLabel, cors, serverName, caddyApiUrl, caddyAdminOrigin, internalTls, upstreamHostHeader, }?: ViteCaddyTlsPluginOptions): PluginOption;
|
|
45
57
|
|
|
46
|
-
export { type ViteCaddyTlsPluginOptions, viteCaddyTlsPlugin as default };
|
|
58
|
+
export { type ViteCaddyTlsPluginOptions, viteCaddyTlsPlugin as default, resolveCaddyTlsDomains, resolveCaddyTlsUrl };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { execSync as execSync2 } from "child_process";
|
|
3
2
|
import { randomUUID } from "crypto";
|
|
4
|
-
import path2 from "path";
|
|
5
3
|
|
|
6
4
|
// src/utils.ts
|
|
7
5
|
import { execSync } from "child_process";
|
|
@@ -87,7 +85,7 @@ function parseConfig(text) {
|
|
|
87
85
|
if (!text.trim()) return {};
|
|
88
86
|
try {
|
|
89
87
|
return JSON.parse(text);
|
|
90
|
-
} catch
|
|
88
|
+
} catch {
|
|
91
89
|
return void 0;
|
|
92
90
|
}
|
|
93
91
|
}
|
|
@@ -142,7 +140,7 @@ function getAdminOrigin(apiUrl, adminOrigin) {
|
|
|
142
140
|
const originSource = adminOrigin ?? getApiUrl(apiUrl);
|
|
143
141
|
try {
|
|
144
142
|
return new URL(originSource).origin;
|
|
145
|
-
} catch
|
|
143
|
+
} catch {
|
|
146
144
|
return new URL(getApiUrl(apiUrl)).origin;
|
|
147
145
|
}
|
|
148
146
|
}
|
|
@@ -272,7 +270,10 @@ function isProcessAlive(pid) {
|
|
|
272
270
|
}
|
|
273
271
|
}
|
|
274
272
|
function isRouteOwnershipActive(record, now = Date.now()) {
|
|
275
|
-
|
|
273
|
+
if (isProcessAlive(record.pid)) {
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
return record.pid <= 0 && now - record.lastSeenAt <= ROUTE_OWNERSHIP_STALE_AFTER_MS;
|
|
276
277
|
}
|
|
277
278
|
async function claimRouteOwnership(record) {
|
|
278
279
|
const normalizedRecord = normalizeRouteOwnershipRecord(record);
|
|
@@ -360,7 +361,7 @@ function validateCaddyIsInstalled() {
|
|
|
360
361
|
try {
|
|
361
362
|
execSync("caddy version");
|
|
362
363
|
return true;
|
|
363
|
-
} catch
|
|
364
|
+
} catch {
|
|
364
365
|
console.error("caddy cli is not installed");
|
|
365
366
|
return false;
|
|
366
367
|
}
|
|
@@ -368,7 +369,7 @@ function validateCaddyIsInstalled() {
|
|
|
368
369
|
async function startCaddy(apiUrl, adminOrigin) {
|
|
369
370
|
try {
|
|
370
371
|
execSync("caddy start", { stdio: "ignore" });
|
|
371
|
-
} catch
|
|
372
|
+
} catch {
|
|
372
373
|
}
|
|
373
374
|
for (let i = 0; i < 10; i++) {
|
|
374
375
|
const status = await checkCaddyAdminStatus(apiUrl, adminOrigin);
|
|
@@ -404,12 +405,7 @@ async function ensureBaseConfig(serverName = DEFAULT_SERVER_NAME, apiUrl, adminO
|
|
|
404
405
|
[serverName]: baseConfig
|
|
405
406
|
}
|
|
406
407
|
};
|
|
407
|
-
const configRes = await caddyFetch(
|
|
408
|
-
`${resolvedApiUrl}/config/`,
|
|
409
|
-
void 0,
|
|
410
|
-
apiUrl,
|
|
411
|
-
adminOrigin
|
|
412
|
-
);
|
|
408
|
+
const configRes = await caddyFetch(`${resolvedApiUrl}/config/`, void 0, apiUrl, adminOrigin);
|
|
413
409
|
await assertCaddyResponse(configRes, "Failed to read Caddy config");
|
|
414
410
|
const configText = await configRes.text();
|
|
415
411
|
const config = parseConfig(configText);
|
|
@@ -574,11 +570,7 @@ async function ensureTlsAutomation(apiUrl, adminOrigin) {
|
|
|
574
570
|
);
|
|
575
571
|
if (!tlsRes.ok && tlsRes.status !== 409) {
|
|
576
572
|
const text = await tlsRes.text();
|
|
577
|
-
throw buildCaddyRequestError(
|
|
578
|
-
"Failed to initialize Caddy TLS automation",
|
|
579
|
-
tlsRes.status,
|
|
580
|
-
text
|
|
581
|
-
);
|
|
573
|
+
throw buildCaddyRequestError("Failed to initialize Caddy TLS automation", tlsRes.status, text);
|
|
582
574
|
}
|
|
583
575
|
}
|
|
584
576
|
function formatDialAddress(host, port) {
|
|
@@ -673,14 +665,7 @@ async function addRoute(id, domains, port, cors, serverName = DEFAULT_SERVER_NAM
|
|
|
673
665
|
response: {
|
|
674
666
|
set: {
|
|
675
667
|
"Access-Control-Allow-Origin": [cors],
|
|
676
|
-
"Access-Control-Allow-Methods": [
|
|
677
|
-
"GET",
|
|
678
|
-
"POST",
|
|
679
|
-
"PUT",
|
|
680
|
-
"PATCH",
|
|
681
|
-
"DELETE",
|
|
682
|
-
"OPTIONS"
|
|
683
|
-
],
|
|
668
|
+
"Access-Control-Allow-Methods": ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
|
684
669
|
"Access-Control-Allow-Headers": ["*"]
|
|
685
670
|
}
|
|
686
671
|
}
|
|
@@ -788,11 +773,7 @@ async function removeTlsPolicy(id, apiUrl, adminOrigin) {
|
|
|
788
773
|
);
|
|
789
774
|
if (!res.ok && res.status !== 404) {
|
|
790
775
|
const text = await res.text();
|
|
791
|
-
const error = buildCaddyRequestError(
|
|
792
|
-
`Failed to remove TLS policy ${id}`,
|
|
793
|
-
res.status,
|
|
794
|
-
text
|
|
795
|
-
);
|
|
776
|
+
const error = buildCaddyRequestError(`Failed to remove TLS policy ${id}`, res.status, text);
|
|
796
777
|
console.error(error.message);
|
|
797
778
|
return false;
|
|
798
779
|
}
|
|
@@ -815,7 +796,12 @@ async function ensureCaddyReady(serverName = DEFAULT_SERVER_NAME, apiUrl, adminO
|
|
|
815
796
|
});
|
|
816
797
|
}
|
|
817
798
|
|
|
818
|
-
// src/
|
|
799
|
+
// src/domain-resolution.js
|
|
800
|
+
import { execSync as execSync2 } from "child_process";
|
|
801
|
+
import { createHash as createHash2 } from "crypto";
|
|
802
|
+
import path2 from "path";
|
|
803
|
+
var MAX_DOMAIN_LABEL_LENGTH = 63;
|
|
804
|
+
var DOMAIN_LABEL_HASH_LENGTH = 10;
|
|
819
805
|
var LOOPBACK_DOMAINS = {
|
|
820
806
|
"localtest.me": "localtest.me",
|
|
821
807
|
"lvh.me": "lvh.me",
|
|
@@ -831,7 +817,7 @@ function getGitRepoInfo() {
|
|
|
831
817
|
if (repoRoot) {
|
|
832
818
|
info.repo = path2.basename(repoRoot);
|
|
833
819
|
}
|
|
834
|
-
} catch
|
|
820
|
+
} catch {
|
|
835
821
|
}
|
|
836
822
|
try {
|
|
837
823
|
let branch = execGit("git rev-parse --abbrev-ref HEAD");
|
|
@@ -841,7 +827,7 @@ function getGitRepoInfo() {
|
|
|
841
827
|
if (branch) {
|
|
842
828
|
info.branch = branch;
|
|
843
829
|
}
|
|
844
|
-
} catch
|
|
830
|
+
} catch {
|
|
845
831
|
}
|
|
846
832
|
return info;
|
|
847
833
|
}
|
|
@@ -857,18 +843,34 @@ function resolveBaseDomain(options) {
|
|
|
857
843
|
}
|
|
858
844
|
return "localhost";
|
|
859
845
|
}
|
|
860
|
-
function
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
846
|
+
function normalizeDomain(domain) {
|
|
847
|
+
const trimmed = domain.trim().toLowerCase();
|
|
848
|
+
if (!trimmed) return null;
|
|
849
|
+
return trimmed;
|
|
850
|
+
}
|
|
851
|
+
function normalizeDomains(domains) {
|
|
852
|
+
const domainList = Array.isArray(domains) ? domains : [domains];
|
|
853
|
+
const normalized = domainList.map((domain) => normalizeDomain(domain)).filter(Boolean);
|
|
854
|
+
if (normalized.length === 0) return null;
|
|
855
|
+
return Array.from(new Set(normalized));
|
|
868
856
|
}
|
|
869
857
|
function sanitizeDomainLabel(value) {
|
|
870
858
|
return value.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
871
859
|
}
|
|
860
|
+
function compactDomainLabel(value) {
|
|
861
|
+
const sanitized = sanitizeDomainLabel(value);
|
|
862
|
+
if (!sanitized) return "";
|
|
863
|
+
if (sanitized.length <= MAX_DOMAIN_LABEL_LENGTH) {
|
|
864
|
+
return sanitized;
|
|
865
|
+
}
|
|
866
|
+
const hash = createHash2("sha1").update(sanitized).digest("hex").slice(0, DOMAIN_LABEL_HASH_LENGTH);
|
|
867
|
+
const prefixLength = MAX_DOMAIN_LABEL_LENGTH - hash.length - 1;
|
|
868
|
+
const prefix = sanitized.slice(0, prefixLength).replace(/-+$/g, "");
|
|
869
|
+
if (!prefix) {
|
|
870
|
+
return hash;
|
|
871
|
+
}
|
|
872
|
+
return `${prefix}-${hash}`;
|
|
873
|
+
}
|
|
872
874
|
function buildDerivedDomain(options) {
|
|
873
875
|
const baseDomain = resolveBaseDomain(options);
|
|
874
876
|
if (!baseDomain) return null;
|
|
@@ -880,27 +882,40 @@ function buildDerivedDomain(options) {
|
|
|
880
882
|
if (!branch) branch = info.branch;
|
|
881
883
|
}
|
|
882
884
|
if (!repo || !branch) return null;
|
|
883
|
-
const repoLabel =
|
|
884
|
-
const branchLabel =
|
|
885
|
+
const repoLabel = compactDomainLabel(repo);
|
|
886
|
+
const branchLabel = compactDomainLabel(branch);
|
|
885
887
|
if (!repoLabel || !branchLabel) return null;
|
|
886
888
|
const labels = [repoLabel, branchLabel];
|
|
887
889
|
if (options.instanceLabel !== void 0) {
|
|
888
|
-
const instanceLabel =
|
|
890
|
+
const instanceLabel = compactDomainLabel(options.instanceLabel);
|
|
889
891
|
if (!instanceLabel) return null;
|
|
890
892
|
labels.push(instanceLabel);
|
|
891
893
|
}
|
|
892
894
|
return `${labels.join(".")}.${baseDomain}`;
|
|
893
895
|
}
|
|
894
|
-
function
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
896
|
+
function resolveCaddyTlsDomains(options = {}) {
|
|
897
|
+
if (options.domain) {
|
|
898
|
+
return normalizeDomains(options.domain);
|
|
899
|
+
}
|
|
900
|
+
const derivedDomain = buildDerivedDomain(options);
|
|
901
|
+
if (!derivedDomain) return null;
|
|
902
|
+
return [derivedDomain];
|
|
898
903
|
}
|
|
899
|
-
function
|
|
900
|
-
const
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
+
function resolveCaddyTlsUrl(options = {}) {
|
|
905
|
+
const domains = resolveCaddyTlsDomains(options);
|
|
906
|
+
if (!domains || domains.length !== 1) return null;
|
|
907
|
+
return `https://${domains[0]}`;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// src/index.ts
|
|
911
|
+
function resolveUpstreamHost(host) {
|
|
912
|
+
if (typeof host === "string") {
|
|
913
|
+
const trimmed = host.trim();
|
|
914
|
+
if (trimmed && trimmed !== "0.0.0.0" && trimmed !== "::") {
|
|
915
|
+
return trimmed;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
return "127.0.0.1";
|
|
904
919
|
}
|
|
905
920
|
function normalizeCaddyApiUrl(url) {
|
|
906
921
|
const trimmed = url.trim();
|
|
@@ -912,17 +927,15 @@ function normalizeCaddyAdminOrigin(origin) {
|
|
|
912
927
|
if (!trimmed) return null;
|
|
913
928
|
try {
|
|
914
929
|
return new URL(trimmed).origin;
|
|
915
|
-
} catch
|
|
930
|
+
} catch {
|
|
916
931
|
return null;
|
|
917
932
|
}
|
|
918
933
|
}
|
|
919
|
-
function
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
if (!derivedDomain) return null;
|
|
925
|
-
return [derivedDomain];
|
|
934
|
+
function resolveCaddyTlsDomains2(options = {}) {
|
|
935
|
+
return resolveCaddyTlsDomains(options);
|
|
936
|
+
}
|
|
937
|
+
function resolveCaddyTlsUrl2(options = {}) {
|
|
938
|
+
return resolveCaddyTlsUrl(options);
|
|
926
939
|
}
|
|
927
940
|
function viteCaddyTlsPlugin({
|
|
928
941
|
domain,
|
|
@@ -943,14 +956,10 @@ function viteCaddyTlsPlugin({
|
|
|
943
956
|
const normalizedAdminOrigin = caddyAdminOrigin ? normalizeCaddyAdminOrigin(caddyAdminOrigin) : null;
|
|
944
957
|
const pluginCaddyAdminOrigin = normalizedAdminOrigin ?? pluginCaddyApiUrl;
|
|
945
958
|
if (caddyApiUrl !== void 0 && !normalizedApiUrl) {
|
|
946
|
-
console.warn(
|
|
947
|
-
`caddyApiUrl is empty after trimming. Falling back to ${DEFAULT_CADDY_API_URL}.`
|
|
948
|
-
);
|
|
959
|
+
console.warn(`caddyApiUrl is empty after trimming. Falling back to ${DEFAULT_CADDY_API_URL}.`);
|
|
949
960
|
}
|
|
950
961
|
if (caddyAdminOrigin !== void 0 && !normalizedAdminOrigin) {
|
|
951
|
-
console.warn(
|
|
952
|
-
`caddyAdminOrigin is invalid. Falling back to ${pluginCaddyApiUrl}.`
|
|
953
|
-
);
|
|
962
|
+
console.warn(`caddyAdminOrigin is invalid. Falling back to ${pluginCaddyApiUrl}.`);
|
|
954
963
|
}
|
|
955
964
|
function createOwnerId() {
|
|
956
965
|
return `${process.pid}-${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
|
|
@@ -995,11 +1004,7 @@ function viteCaddyTlsPlugin({
|
|
|
995
1004
|
if (record.tlsPolicyId) {
|
|
996
1005
|
const tlsPolicyId = record.tlsPolicyId;
|
|
997
1006
|
cleaned = await removeWithRetry(
|
|
998
|
-
() => removeTlsPolicy(
|
|
999
|
-
tlsPolicyId,
|
|
1000
|
-
pluginCaddyApiUrl,
|
|
1001
|
-
pluginCaddyAdminOrigin
|
|
1002
|
-
),
|
|
1007
|
+
() => removeTlsPolicy(tlsPolicyId, pluginCaddyApiUrl, pluginCaddyAdminOrigin),
|
|
1003
1008
|
"TLS policy"
|
|
1004
1009
|
) && cleaned;
|
|
1005
1010
|
}
|
|
@@ -1013,11 +1018,7 @@ function viteCaddyTlsPlugin({
|
|
|
1013
1018
|
let cleaned = true;
|
|
1014
1019
|
for (const managedTlsPolicyId of tlsPolicyIds) {
|
|
1015
1020
|
cleaned = await removeWithRetry(
|
|
1016
|
-
() => removeTlsPolicy(
|
|
1017
|
-
managedTlsPolicyId,
|
|
1018
|
-
pluginCaddyApiUrl,
|
|
1019
|
-
pluginCaddyAdminOrigin
|
|
1020
|
-
),
|
|
1021
|
+
() => removeTlsPolicy(managedTlsPolicyId, pluginCaddyApiUrl, pluginCaddyAdminOrigin),
|
|
1021
1022
|
`managed TLS policy ${managedTlsPolicyId}`
|
|
1022
1023
|
) && cleaned;
|
|
1023
1024
|
}
|
|
@@ -1051,7 +1052,7 @@ function viteCaddyTlsPlugin({
|
|
|
1051
1052
|
const { httpServer, config } = server;
|
|
1052
1053
|
const previewMode = isPreviewServer(server);
|
|
1053
1054
|
const fallbackPort = previewMode ? getPreviewPort(config) ?? 4173 : config.server.port || 5173;
|
|
1054
|
-
const resolvedDomains =
|
|
1055
|
+
const resolvedDomains = resolveCaddyTlsDomains2({
|
|
1055
1056
|
domain,
|
|
1056
1057
|
baseDomain,
|
|
1057
1058
|
loopbackDomain,
|
|
@@ -1122,7 +1123,7 @@ function viteCaddyTlsPlugin({
|
|
|
1122
1123
|
if (resolvedPort === null && !Number.isNaN(port)) {
|
|
1123
1124
|
resolvedPort = port;
|
|
1124
1125
|
}
|
|
1125
|
-
} catch
|
|
1126
|
+
} catch {
|
|
1126
1127
|
}
|
|
1127
1128
|
}
|
|
1128
1129
|
if (httpServer) {
|
|
@@ -1189,11 +1190,16 @@ function viteCaddyTlsPlugin({
|
|
|
1189
1190
|
function onServerClose() {
|
|
1190
1191
|
void cleanupRoute();
|
|
1191
1192
|
}
|
|
1193
|
+
function getSignalExitCode(signal) {
|
|
1194
|
+
if (signal === "SIGINT") return 130;
|
|
1195
|
+
if (signal === "SIGTERM") return 143;
|
|
1196
|
+
return 1;
|
|
1197
|
+
}
|
|
1192
1198
|
function handleSignal(signal) {
|
|
1193
1199
|
process.off("SIGINT", onSigint);
|
|
1194
1200
|
process.off("SIGTERM", onSigterm);
|
|
1195
1201
|
void cleanupRoute().finally(() => {
|
|
1196
|
-
process.
|
|
1202
|
+
process.exit(getSignalExitCode(signal));
|
|
1197
1203
|
});
|
|
1198
1204
|
}
|
|
1199
1205
|
function onSigint() {
|
|
@@ -1266,9 +1272,7 @@ function viteCaddyTlsPlugin({
|
|
|
1266
1272
|
return;
|
|
1267
1273
|
}
|
|
1268
1274
|
if (claimResult.status === "active-conflict") {
|
|
1269
|
-
console.error(
|
|
1270
|
-
buildOwnershipConflictMessage(domainArray, claimResult.existingRecord)
|
|
1271
|
-
);
|
|
1275
|
+
console.error(buildOwnershipConflictMessage(domainArray, claimResult.existingRecord));
|
|
1272
1276
|
return;
|
|
1273
1277
|
}
|
|
1274
1278
|
activeOwnershipRecord = claimResult.currentRecord;
|
|
@@ -1309,9 +1313,7 @@ function viteCaddyTlsPlugin({
|
|
|
1309
1313
|
removeWithRetry
|
|
1310
1314
|
);
|
|
1311
1315
|
if (reclaimedOrphans) {
|
|
1312
|
-
console.warn(
|
|
1313
|
-
`Reclaimed orphaned managed Caddy resources for ${domainArray.join(", ")}.`
|
|
1314
|
-
);
|
|
1316
|
+
console.warn(`Reclaimed orphaned managed Caddy resources for ${domainArray.join(", ")}.`);
|
|
1315
1317
|
} else {
|
|
1316
1318
|
console.error(
|
|
1317
1319
|
`Cannot claim ${domainArray.join(", ")} because Caddy still has orphaned managed resources. Remove the stale Caddy state or use \`instanceLabel\` or \`domain\` to make the hostname unique.`
|
|
@@ -1323,12 +1325,7 @@ function viteCaddyTlsPlugin({
|
|
|
1323
1325
|
}
|
|
1324
1326
|
if (tlsPolicyId) {
|
|
1325
1327
|
try {
|
|
1326
|
-
await addTlsPolicy(
|
|
1327
|
-
tlsPolicyId,
|
|
1328
|
-
domainArray,
|
|
1329
|
-
pluginCaddyApiUrl,
|
|
1330
|
-
pluginCaddyAdminOrigin
|
|
1331
|
-
);
|
|
1328
|
+
await addTlsPolicy(tlsPolicyId, domainArray, pluginCaddyApiUrl, pluginCaddyAdminOrigin);
|
|
1332
1329
|
tlsPolicyAdded = true;
|
|
1333
1330
|
} catch (e) {
|
|
1334
1331
|
console.error(
|
|
@@ -1370,10 +1367,8 @@ function viteCaddyTlsPlugin({
|
|
|
1370
1367
|
console.log("\n\u{1F512} Caddy is proxying your traffic on https");
|
|
1371
1368
|
console.log(`
|
|
1372
1369
|
\u27A1\uFE0F Upstream target: http://${formatUpstreamTarget(upstreamHost, port)}`);
|
|
1373
|
-
console.log(
|
|
1374
|
-
|
|
1375
|
-
\u{1F517} Access your local ${domainArray.length > 1 ? "servers" : "server"}!`
|
|
1376
|
-
);
|
|
1370
|
+
console.log(`
|
|
1371
|
+
\u{1F517} Access your local ${domainArray.length > 1 ? "servers" : "server"}!`);
|
|
1377
1372
|
domainArray.forEach((domain2) => {
|
|
1378
1373
|
console.log(`\u{1F30D} https://${domain2}`);
|
|
1379
1374
|
});
|
|
@@ -1420,7 +1415,7 @@ function viteCaddyTlsPlugin({
|
|
|
1420
1415
|
return {
|
|
1421
1416
|
name: "vite:caddy-tls",
|
|
1422
1417
|
config(userConfig) {
|
|
1423
|
-
const resolvedDomains =
|
|
1418
|
+
const resolvedDomains = resolveCaddyTlsDomains2({
|
|
1424
1419
|
domain,
|
|
1425
1420
|
baseDomain,
|
|
1426
1421
|
loopbackDomain,
|
|
@@ -1455,5 +1450,7 @@ function viteCaddyTlsPlugin({
|
|
|
1455
1450
|
};
|
|
1456
1451
|
}
|
|
1457
1452
|
export {
|
|
1458
|
-
viteCaddyTlsPlugin as default
|
|
1453
|
+
viteCaddyTlsPlugin as default,
|
|
1454
|
+
resolveCaddyTlsDomains2 as resolveCaddyTlsDomains,
|
|
1455
|
+
resolveCaddyTlsUrl2 as resolveCaddyTlsUrl
|
|
1459
1456
|
};
|
package/package.json
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-caddy-multiple-tls",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Vite plugin that uses Caddy to provide local HTTPS with derived domains.",
|
|
5
5
|
"keywords": [
|
|
6
|
-
"vite",
|
|
7
|
-
"plugin",
|
|
8
6
|
"caddy",
|
|
9
7
|
"https",
|
|
8
|
+
"local-development",
|
|
9
|
+
"plugin",
|
|
10
10
|
"tls",
|
|
11
|
-
"
|
|
11
|
+
"vite"
|
|
12
12
|
],
|
|
13
|
+
"homepage": "https://github.com/vampaz/vite-plugin-caddy-multiple-tls/#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/vampaz/vite-plugin-caddy-multiple-tls/issues"
|
|
16
|
+
},
|
|
13
17
|
"license": "MIT",
|
|
14
18
|
"author": "vampaz",
|
|
15
|
-
"
|
|
16
|
-
"
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/vampaz/vite-plugin-caddy-multiple-tls.git"
|
|
17
22
|
},
|
|
18
23
|
"files": [
|
|
19
24
|
"dist"
|
|
@@ -27,6 +32,9 @@
|
|
|
27
32
|
"import": "./dist/index.js"
|
|
28
33
|
}
|
|
29
34
|
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
30
38
|
"scripts": {
|
|
31
39
|
"dev": "tsup --watch src/**/* src/index.ts --format esm --dts-resolve",
|
|
32
40
|
"sync-readme": "bash scripts/sync-readme.sh",
|
|
@@ -35,20 +43,6 @@
|
|
|
35
43
|
"test": "vitest run",
|
|
36
44
|
"test:watch": "vitest"
|
|
37
45
|
},
|
|
38
|
-
"engines": {
|
|
39
|
-
"node": ">=22.0.0"
|
|
40
|
-
},
|
|
41
|
-
"repository": {
|
|
42
|
-
"type": "git",
|
|
43
|
-
"url": "git+https://github.com/vampaz/vite-plugin-caddy-multiple-tls.git"
|
|
44
|
-
},
|
|
45
|
-
"bugs": {
|
|
46
|
-
"url": "https://github.com/vampaz/vite-plugin-caddy-multiple-tls/issues"
|
|
47
|
-
},
|
|
48
|
-
"homepage": "https://github.com/vampaz/vite-plugin-caddy-multiple-tls/#readme",
|
|
49
|
-
"peerDependencies": {
|
|
50
|
-
"vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
|
|
51
|
-
},
|
|
52
46
|
"devDependencies": {
|
|
53
47
|
"@types/fs-extra": "^11.0.4",
|
|
54
48
|
"@types/node": "^25.0.3",
|
|
@@ -56,5 +50,11 @@
|
|
|
56
50
|
"tsup": "^8.5.1",
|
|
57
51
|
"vite": "^8.0.0",
|
|
58
52
|
"vitest": "^4.0.16"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=22.0.0"
|
|
59
59
|
}
|
|
60
60
|
}
|