vite-plugin-caddy-multiple-tls 1.7.1 → 1.8.1
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 +105 -100
- 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";
|
|
@@ -35,6 +33,16 @@ function isRecord(value) {
|
|
|
35
33
|
function normalizeRouteOwnershipDomains(domains) {
|
|
36
34
|
return Array.from(new Set(domains)).sort();
|
|
37
35
|
}
|
|
36
|
+
function haveSameDomains(left, right) {
|
|
37
|
+
if (left.length !== right.length) return false;
|
|
38
|
+
return left.every((domain, index) => domain === right[index]);
|
|
39
|
+
}
|
|
40
|
+
function getRouteOwnershipProjectRoot(record) {
|
|
41
|
+
return record.configRoot ?? record.cwd;
|
|
42
|
+
}
|
|
43
|
+
function canReclaimLiveOwnership(currentRecord, existingRecord) {
|
|
44
|
+
return getRouteOwnershipProjectRoot(currentRecord) === getRouteOwnershipProjectRoot(existingRecord) && haveSameDomains(currentRecord.domains, existingRecord.domains);
|
|
45
|
+
}
|
|
38
46
|
function getRouteOwnershipDirectory() {
|
|
39
47
|
return path.join(os.tmpdir(), "vite-plugin-caddy-multiple-tls", "owners");
|
|
40
48
|
}
|
|
@@ -87,7 +95,7 @@ function parseConfig(text) {
|
|
|
87
95
|
if (!text.trim()) return {};
|
|
88
96
|
try {
|
|
89
97
|
return JSON.parse(text);
|
|
90
|
-
} catch
|
|
98
|
+
} catch {
|
|
91
99
|
return void 0;
|
|
92
100
|
}
|
|
93
101
|
}
|
|
@@ -142,7 +150,7 @@ function getAdminOrigin(apiUrl, adminOrigin) {
|
|
|
142
150
|
const originSource = adminOrigin ?? getApiUrl(apiUrl);
|
|
143
151
|
try {
|
|
144
152
|
return new URL(originSource).origin;
|
|
145
|
-
} catch
|
|
153
|
+
} catch {
|
|
146
154
|
return new URL(getApiUrl(apiUrl)).origin;
|
|
147
155
|
}
|
|
148
156
|
}
|
|
@@ -296,8 +304,14 @@ async function claimRouteOwnership(record) {
|
|
|
296
304
|
return candidate.ownerId !== normalizedRecord.ownerId && intersectsDomains(candidate.domains, normalizedRecord.domains);
|
|
297
305
|
}
|
|
298
306
|
);
|
|
307
|
+
const reclaimableRecords = overlappingRecords.filter((candidate) => {
|
|
308
|
+
if (!isRouteOwnershipActive(candidate)) {
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
return canReclaimLiveOwnership(normalizedRecord, candidate);
|
|
312
|
+
});
|
|
299
313
|
const activeConflict = overlappingRecords.find((candidate) => {
|
|
300
|
-
return isRouteOwnershipActive(candidate);
|
|
314
|
+
return isRouteOwnershipActive(candidate) && !canReclaimLiveOwnership(normalizedRecord, candidate);
|
|
301
315
|
});
|
|
302
316
|
if (activeConflict) {
|
|
303
317
|
claimResult = {
|
|
@@ -308,11 +322,11 @@ async function claimRouteOwnership(record) {
|
|
|
308
322
|
return;
|
|
309
323
|
}
|
|
310
324
|
await writeRouteOwnership(normalizedRecord);
|
|
311
|
-
if (
|
|
325
|
+
if (reclaimableRecords.length > 0) {
|
|
312
326
|
claimResult = {
|
|
313
327
|
status: "reclaimed",
|
|
314
328
|
currentRecord: normalizedRecord,
|
|
315
|
-
previousRecords:
|
|
329
|
+
previousRecords: reclaimableRecords
|
|
316
330
|
};
|
|
317
331
|
return;
|
|
318
332
|
}
|
|
@@ -363,7 +377,7 @@ function validateCaddyIsInstalled() {
|
|
|
363
377
|
try {
|
|
364
378
|
execSync("caddy version");
|
|
365
379
|
return true;
|
|
366
|
-
} catch
|
|
380
|
+
} catch {
|
|
367
381
|
console.error("caddy cli is not installed");
|
|
368
382
|
return false;
|
|
369
383
|
}
|
|
@@ -371,7 +385,7 @@ function validateCaddyIsInstalled() {
|
|
|
371
385
|
async function startCaddy(apiUrl, adminOrigin) {
|
|
372
386
|
try {
|
|
373
387
|
execSync("caddy start", { stdio: "ignore" });
|
|
374
|
-
} catch
|
|
388
|
+
} catch {
|
|
375
389
|
}
|
|
376
390
|
for (let i = 0; i < 10; i++) {
|
|
377
391
|
const status = await checkCaddyAdminStatus(apiUrl, adminOrigin);
|
|
@@ -407,12 +421,7 @@ async function ensureBaseConfig(serverName = DEFAULT_SERVER_NAME, apiUrl, adminO
|
|
|
407
421
|
[serverName]: baseConfig
|
|
408
422
|
}
|
|
409
423
|
};
|
|
410
|
-
const configRes = await caddyFetch(
|
|
411
|
-
`${resolvedApiUrl}/config/`,
|
|
412
|
-
void 0,
|
|
413
|
-
apiUrl,
|
|
414
|
-
adminOrigin
|
|
415
|
-
);
|
|
424
|
+
const configRes = await caddyFetch(`${resolvedApiUrl}/config/`, void 0, apiUrl, adminOrigin);
|
|
416
425
|
await assertCaddyResponse(configRes, "Failed to read Caddy config");
|
|
417
426
|
const configText = await configRes.text();
|
|
418
427
|
const config = parseConfig(configText);
|
|
@@ -577,11 +586,7 @@ async function ensureTlsAutomation(apiUrl, adminOrigin) {
|
|
|
577
586
|
);
|
|
578
587
|
if (!tlsRes.ok && tlsRes.status !== 409) {
|
|
579
588
|
const text = await tlsRes.text();
|
|
580
|
-
throw buildCaddyRequestError(
|
|
581
|
-
"Failed to initialize Caddy TLS automation",
|
|
582
|
-
tlsRes.status,
|
|
583
|
-
text
|
|
584
|
-
);
|
|
589
|
+
throw buildCaddyRequestError("Failed to initialize Caddy TLS automation", tlsRes.status, text);
|
|
585
590
|
}
|
|
586
591
|
}
|
|
587
592
|
function formatDialAddress(host, port) {
|
|
@@ -676,14 +681,7 @@ async function addRoute(id, domains, port, cors, serverName = DEFAULT_SERVER_NAM
|
|
|
676
681
|
response: {
|
|
677
682
|
set: {
|
|
678
683
|
"Access-Control-Allow-Origin": [cors],
|
|
679
|
-
"Access-Control-Allow-Methods": [
|
|
680
|
-
"GET",
|
|
681
|
-
"POST",
|
|
682
|
-
"PUT",
|
|
683
|
-
"PATCH",
|
|
684
|
-
"DELETE",
|
|
685
|
-
"OPTIONS"
|
|
686
|
-
],
|
|
684
|
+
"Access-Control-Allow-Methods": ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
|
687
685
|
"Access-Control-Allow-Headers": ["*"]
|
|
688
686
|
}
|
|
689
687
|
}
|
|
@@ -791,11 +789,7 @@ async function removeTlsPolicy(id, apiUrl, adminOrigin) {
|
|
|
791
789
|
);
|
|
792
790
|
if (!res.ok && res.status !== 404) {
|
|
793
791
|
const text = await res.text();
|
|
794
|
-
const error = buildCaddyRequestError(
|
|
795
|
-
`Failed to remove TLS policy ${id}`,
|
|
796
|
-
res.status,
|
|
797
|
-
text
|
|
798
|
-
);
|
|
792
|
+
const error = buildCaddyRequestError(`Failed to remove TLS policy ${id}`, res.status, text);
|
|
799
793
|
console.error(error.message);
|
|
800
794
|
return false;
|
|
801
795
|
}
|
|
@@ -818,7 +812,12 @@ async function ensureCaddyReady(serverName = DEFAULT_SERVER_NAME, apiUrl, adminO
|
|
|
818
812
|
});
|
|
819
813
|
}
|
|
820
814
|
|
|
821
|
-
// src/
|
|
815
|
+
// src/domain-resolution.js
|
|
816
|
+
import { execSync as execSync2 } from "child_process";
|
|
817
|
+
import { createHash as createHash2 } from "crypto";
|
|
818
|
+
import path2 from "path";
|
|
819
|
+
var MAX_DOMAIN_LABEL_LENGTH = 63;
|
|
820
|
+
var DOMAIN_LABEL_HASH_LENGTH = 10;
|
|
822
821
|
var LOOPBACK_DOMAINS = {
|
|
823
822
|
"localtest.me": "localtest.me",
|
|
824
823
|
"lvh.me": "lvh.me",
|
|
@@ -834,7 +833,7 @@ function getGitRepoInfo() {
|
|
|
834
833
|
if (repoRoot) {
|
|
835
834
|
info.repo = path2.basename(repoRoot);
|
|
836
835
|
}
|
|
837
|
-
} catch
|
|
836
|
+
} catch {
|
|
838
837
|
}
|
|
839
838
|
try {
|
|
840
839
|
let branch = execGit("git rev-parse --abbrev-ref HEAD");
|
|
@@ -844,7 +843,7 @@ function getGitRepoInfo() {
|
|
|
844
843
|
if (branch) {
|
|
845
844
|
info.branch = branch;
|
|
846
845
|
}
|
|
847
|
-
} catch
|
|
846
|
+
} catch {
|
|
848
847
|
}
|
|
849
848
|
return info;
|
|
850
849
|
}
|
|
@@ -860,18 +859,34 @@ function resolveBaseDomain(options) {
|
|
|
860
859
|
}
|
|
861
860
|
return "localhost";
|
|
862
861
|
}
|
|
863
|
-
function
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
862
|
+
function normalizeDomain(domain) {
|
|
863
|
+
const trimmed = domain.trim().toLowerCase();
|
|
864
|
+
if (!trimmed) return null;
|
|
865
|
+
return trimmed;
|
|
866
|
+
}
|
|
867
|
+
function normalizeDomains(domains) {
|
|
868
|
+
const domainList = Array.isArray(domains) ? domains : [domains];
|
|
869
|
+
const normalized = domainList.map((domain) => normalizeDomain(domain)).filter(Boolean);
|
|
870
|
+
if (normalized.length === 0) return null;
|
|
871
|
+
return Array.from(new Set(normalized));
|
|
871
872
|
}
|
|
872
873
|
function sanitizeDomainLabel(value) {
|
|
873
874
|
return value.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
874
875
|
}
|
|
876
|
+
function compactDomainLabel(value) {
|
|
877
|
+
const sanitized = sanitizeDomainLabel(value);
|
|
878
|
+
if (!sanitized) return "";
|
|
879
|
+
if (sanitized.length <= MAX_DOMAIN_LABEL_LENGTH) {
|
|
880
|
+
return sanitized;
|
|
881
|
+
}
|
|
882
|
+
const hash = createHash2("sha1").update(sanitized).digest("hex").slice(0, DOMAIN_LABEL_HASH_LENGTH);
|
|
883
|
+
const prefixLength = MAX_DOMAIN_LABEL_LENGTH - hash.length - 1;
|
|
884
|
+
const prefix = sanitized.slice(0, prefixLength).replace(/-+$/g, "");
|
|
885
|
+
if (!prefix) {
|
|
886
|
+
return hash;
|
|
887
|
+
}
|
|
888
|
+
return `${prefix}-${hash}`;
|
|
889
|
+
}
|
|
875
890
|
function buildDerivedDomain(options) {
|
|
876
891
|
const baseDomain = resolveBaseDomain(options);
|
|
877
892
|
if (!baseDomain) return null;
|
|
@@ -883,27 +898,40 @@ function buildDerivedDomain(options) {
|
|
|
883
898
|
if (!branch) branch = info.branch;
|
|
884
899
|
}
|
|
885
900
|
if (!repo || !branch) return null;
|
|
886
|
-
const repoLabel =
|
|
887
|
-
const branchLabel =
|
|
901
|
+
const repoLabel = compactDomainLabel(repo);
|
|
902
|
+
const branchLabel = compactDomainLabel(branch);
|
|
888
903
|
if (!repoLabel || !branchLabel) return null;
|
|
889
904
|
const labels = [repoLabel, branchLabel];
|
|
890
905
|
if (options.instanceLabel !== void 0) {
|
|
891
|
-
const instanceLabel =
|
|
906
|
+
const instanceLabel = compactDomainLabel(options.instanceLabel);
|
|
892
907
|
if (!instanceLabel) return null;
|
|
893
908
|
labels.push(instanceLabel);
|
|
894
909
|
}
|
|
895
910
|
return `${labels.join(".")}.${baseDomain}`;
|
|
896
911
|
}
|
|
897
|
-
function
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
912
|
+
function resolveCaddyTlsDomains(options = {}) {
|
|
913
|
+
if (options.domain) {
|
|
914
|
+
return normalizeDomains(options.domain);
|
|
915
|
+
}
|
|
916
|
+
const derivedDomain = buildDerivedDomain(options);
|
|
917
|
+
if (!derivedDomain) return null;
|
|
918
|
+
return [derivedDomain];
|
|
901
919
|
}
|
|
902
|
-
function
|
|
903
|
-
const
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
920
|
+
function resolveCaddyTlsUrl(options = {}) {
|
|
921
|
+
const domains = resolveCaddyTlsDomains(options);
|
|
922
|
+
if (!domains || domains.length !== 1) return null;
|
|
923
|
+
return `https://${domains[0]}`;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// src/index.ts
|
|
927
|
+
function resolveUpstreamHost(host) {
|
|
928
|
+
if (typeof host === "string") {
|
|
929
|
+
const trimmed = host.trim();
|
|
930
|
+
if (trimmed && trimmed !== "0.0.0.0" && trimmed !== "::") {
|
|
931
|
+
return trimmed;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
return "127.0.0.1";
|
|
907
935
|
}
|
|
908
936
|
function normalizeCaddyApiUrl(url) {
|
|
909
937
|
const trimmed = url.trim();
|
|
@@ -915,17 +943,15 @@ function normalizeCaddyAdminOrigin(origin) {
|
|
|
915
943
|
if (!trimmed) return null;
|
|
916
944
|
try {
|
|
917
945
|
return new URL(trimmed).origin;
|
|
918
|
-
} catch
|
|
946
|
+
} catch {
|
|
919
947
|
return null;
|
|
920
948
|
}
|
|
921
949
|
}
|
|
922
|
-
function
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
if (!derivedDomain) return null;
|
|
928
|
-
return [derivedDomain];
|
|
950
|
+
function resolveCaddyTlsDomains2(options = {}) {
|
|
951
|
+
return resolveCaddyTlsDomains(options);
|
|
952
|
+
}
|
|
953
|
+
function resolveCaddyTlsUrl2(options = {}) {
|
|
954
|
+
return resolveCaddyTlsUrl(options);
|
|
929
955
|
}
|
|
930
956
|
function viteCaddyTlsPlugin({
|
|
931
957
|
domain,
|
|
@@ -946,14 +972,10 @@ function viteCaddyTlsPlugin({
|
|
|
946
972
|
const normalizedAdminOrigin = caddyAdminOrigin ? normalizeCaddyAdminOrigin(caddyAdminOrigin) : null;
|
|
947
973
|
const pluginCaddyAdminOrigin = normalizedAdminOrigin ?? pluginCaddyApiUrl;
|
|
948
974
|
if (caddyApiUrl !== void 0 && !normalizedApiUrl) {
|
|
949
|
-
console.warn(
|
|
950
|
-
`caddyApiUrl is empty after trimming. Falling back to ${DEFAULT_CADDY_API_URL}.`
|
|
951
|
-
);
|
|
975
|
+
console.warn(`caddyApiUrl is empty after trimming. Falling back to ${DEFAULT_CADDY_API_URL}.`);
|
|
952
976
|
}
|
|
953
977
|
if (caddyAdminOrigin !== void 0 && !normalizedAdminOrigin) {
|
|
954
|
-
console.warn(
|
|
955
|
-
`caddyAdminOrigin is invalid. Falling back to ${pluginCaddyApiUrl}.`
|
|
956
|
-
);
|
|
978
|
+
console.warn(`caddyAdminOrigin is invalid. Falling back to ${pluginCaddyApiUrl}.`);
|
|
957
979
|
}
|
|
958
980
|
function createOwnerId() {
|
|
959
981
|
return `${process.pid}-${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
|
|
@@ -998,11 +1020,7 @@ function viteCaddyTlsPlugin({
|
|
|
998
1020
|
if (record.tlsPolicyId) {
|
|
999
1021
|
const tlsPolicyId = record.tlsPolicyId;
|
|
1000
1022
|
cleaned = await removeWithRetry(
|
|
1001
|
-
() => removeTlsPolicy(
|
|
1002
|
-
tlsPolicyId,
|
|
1003
|
-
pluginCaddyApiUrl,
|
|
1004
|
-
pluginCaddyAdminOrigin
|
|
1005
|
-
),
|
|
1023
|
+
() => removeTlsPolicy(tlsPolicyId, pluginCaddyApiUrl, pluginCaddyAdminOrigin),
|
|
1006
1024
|
"TLS policy"
|
|
1007
1025
|
) && cleaned;
|
|
1008
1026
|
}
|
|
@@ -1016,11 +1034,7 @@ function viteCaddyTlsPlugin({
|
|
|
1016
1034
|
let cleaned = true;
|
|
1017
1035
|
for (const managedTlsPolicyId of tlsPolicyIds) {
|
|
1018
1036
|
cleaned = await removeWithRetry(
|
|
1019
|
-
() => removeTlsPolicy(
|
|
1020
|
-
managedTlsPolicyId,
|
|
1021
|
-
pluginCaddyApiUrl,
|
|
1022
|
-
pluginCaddyAdminOrigin
|
|
1023
|
-
),
|
|
1037
|
+
() => removeTlsPolicy(managedTlsPolicyId, pluginCaddyApiUrl, pluginCaddyAdminOrigin),
|
|
1024
1038
|
`managed TLS policy ${managedTlsPolicyId}`
|
|
1025
1039
|
) && cleaned;
|
|
1026
1040
|
}
|
|
@@ -1054,7 +1068,7 @@ function viteCaddyTlsPlugin({
|
|
|
1054
1068
|
const { httpServer, config } = server;
|
|
1055
1069
|
const previewMode = isPreviewServer(server);
|
|
1056
1070
|
const fallbackPort = previewMode ? getPreviewPort(config) ?? 4173 : config.server.port || 5173;
|
|
1057
|
-
const resolvedDomains =
|
|
1071
|
+
const resolvedDomains = resolveCaddyTlsDomains2({
|
|
1058
1072
|
domain,
|
|
1059
1073
|
baseDomain,
|
|
1060
1074
|
loopbackDomain,
|
|
@@ -1125,7 +1139,7 @@ function viteCaddyTlsPlugin({
|
|
|
1125
1139
|
if (resolvedPort === null && !Number.isNaN(port)) {
|
|
1126
1140
|
resolvedPort = port;
|
|
1127
1141
|
}
|
|
1128
|
-
} catch
|
|
1142
|
+
} catch {
|
|
1129
1143
|
}
|
|
1130
1144
|
}
|
|
1131
1145
|
if (httpServer) {
|
|
@@ -1274,9 +1288,7 @@ function viteCaddyTlsPlugin({
|
|
|
1274
1288
|
return;
|
|
1275
1289
|
}
|
|
1276
1290
|
if (claimResult.status === "active-conflict") {
|
|
1277
|
-
console.error(
|
|
1278
|
-
buildOwnershipConflictMessage(domainArray, claimResult.existingRecord)
|
|
1279
|
-
);
|
|
1291
|
+
console.error(buildOwnershipConflictMessage(domainArray, claimResult.existingRecord));
|
|
1280
1292
|
return;
|
|
1281
1293
|
}
|
|
1282
1294
|
activeOwnershipRecord = claimResult.currentRecord;
|
|
@@ -1317,9 +1329,7 @@ function viteCaddyTlsPlugin({
|
|
|
1317
1329
|
removeWithRetry
|
|
1318
1330
|
);
|
|
1319
1331
|
if (reclaimedOrphans) {
|
|
1320
|
-
console.warn(
|
|
1321
|
-
`Reclaimed orphaned managed Caddy resources for ${domainArray.join(", ")}.`
|
|
1322
|
-
);
|
|
1332
|
+
console.warn(`Reclaimed orphaned managed Caddy resources for ${domainArray.join(", ")}.`);
|
|
1323
1333
|
} else {
|
|
1324
1334
|
console.error(
|
|
1325
1335
|
`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.`
|
|
@@ -1331,12 +1341,7 @@ function viteCaddyTlsPlugin({
|
|
|
1331
1341
|
}
|
|
1332
1342
|
if (tlsPolicyId) {
|
|
1333
1343
|
try {
|
|
1334
|
-
await addTlsPolicy(
|
|
1335
|
-
tlsPolicyId,
|
|
1336
|
-
domainArray,
|
|
1337
|
-
pluginCaddyApiUrl,
|
|
1338
|
-
pluginCaddyAdminOrigin
|
|
1339
|
-
);
|
|
1344
|
+
await addTlsPolicy(tlsPolicyId, domainArray, pluginCaddyApiUrl, pluginCaddyAdminOrigin);
|
|
1340
1345
|
tlsPolicyAdded = true;
|
|
1341
1346
|
} catch (e) {
|
|
1342
1347
|
console.error(
|
|
@@ -1378,10 +1383,8 @@ function viteCaddyTlsPlugin({
|
|
|
1378
1383
|
console.log("\n\u{1F512} Caddy is proxying your traffic on https");
|
|
1379
1384
|
console.log(`
|
|
1380
1385
|
\u27A1\uFE0F Upstream target: http://${formatUpstreamTarget(upstreamHost, port)}`);
|
|
1381
|
-
console.log(
|
|
1382
|
-
|
|
1383
|
-
\u{1F517} Access your local ${domainArray.length > 1 ? "servers" : "server"}!`
|
|
1384
|
-
);
|
|
1386
|
+
console.log(`
|
|
1387
|
+
\u{1F517} Access your local ${domainArray.length > 1 ? "servers" : "server"}!`);
|
|
1385
1388
|
domainArray.forEach((domain2) => {
|
|
1386
1389
|
console.log(`\u{1F30D} https://${domain2}`);
|
|
1387
1390
|
});
|
|
@@ -1428,7 +1431,7 @@ function viteCaddyTlsPlugin({
|
|
|
1428
1431
|
return {
|
|
1429
1432
|
name: "vite:caddy-tls",
|
|
1430
1433
|
config(userConfig) {
|
|
1431
|
-
const resolvedDomains =
|
|
1434
|
+
const resolvedDomains = resolveCaddyTlsDomains2({
|
|
1432
1435
|
domain,
|
|
1433
1436
|
baseDomain,
|
|
1434
1437
|
loopbackDomain,
|
|
@@ -1463,5 +1466,7 @@ function viteCaddyTlsPlugin({
|
|
|
1463
1466
|
};
|
|
1464
1467
|
}
|
|
1465
1468
|
export {
|
|
1466
|
-
viteCaddyTlsPlugin as default
|
|
1469
|
+
viteCaddyTlsPlugin as default,
|
|
1470
|
+
resolveCaddyTlsDomains2 as resolveCaddyTlsDomains,
|
|
1471
|
+
resolveCaddyTlsUrl2 as resolveCaddyTlsUrl
|
|
1467
1472
|
};
|
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.1",
|
|
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
|
}
|