vite-plugin-caddy-multiple-tls 1.5.1 → 1.6.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 +54 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +471 -139
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -92,6 +92,26 @@ export default config;
|
|
|
92
92
|
|
|
93
93
|
You can override auto-detection with `repo` or `branch` if needed.
|
|
94
94
|
|
|
95
|
+
If you run different projects that derive the same `<repo>.<branch>` host, add `instanceLabel` to keep domains unique:
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
// vite.config.js
|
|
99
|
+
import { defineConfig } from 'vite';
|
|
100
|
+
import caddyTls from 'vite-plugin-caddy-multiple-tls';
|
|
101
|
+
|
|
102
|
+
const config = defineConfig({
|
|
103
|
+
plugins: [
|
|
104
|
+
caddyTls({
|
|
105
|
+
instanceLabel: 'web-1',
|
|
106
|
+
})
|
|
107
|
+
]
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
export default config;
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
This derives a host like `<repo>.<branch>.web-1.localhost`.
|
|
114
|
+
|
|
95
115
|
For a zero-config experience, use `baseDomain: 'localhost'` (the default) so the derived domain works without editing `/etc/hosts`.
|
|
96
116
|
|
|
97
117
|
`internalTls` defaults to `true` when you pass `baseDomain` or `domain`. You can override it if needed.
|
|
@@ -116,6 +136,40 @@ const config = defineConfig({
|
|
|
116
136
|
export default config;
|
|
117
137
|
```
|
|
118
138
|
|
|
139
|
+
If your Caddy Admin API enforces a specific allowed origin that differs from `caddyApiUrl`, set `caddyAdminOrigin`.
|
|
140
|
+
|
|
141
|
+
```js
|
|
142
|
+
// vite.config.js
|
|
143
|
+
import { defineConfig } from 'vite';
|
|
144
|
+
import caddyTls from 'vite-plugin-caddy-multiple-tls';
|
|
145
|
+
|
|
146
|
+
const config = defineConfig({
|
|
147
|
+
plugins: [
|
|
148
|
+
caddyTls({
|
|
149
|
+
caddyApiUrl: 'http://127.0.0.1:2019',
|
|
150
|
+
caddyAdminOrigin: 'http://localhost:2019',
|
|
151
|
+
})
|
|
152
|
+
]
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
export default config;
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Troubleshooting
|
|
159
|
+
|
|
160
|
+
### `client is not allowed to access from origin ''`
|
|
161
|
+
|
|
162
|
+
This error comes from Caddy Admin API origin enforcement, not from Caddy being down.
|
|
163
|
+
|
|
164
|
+
- Check `caddyApiUrl` points to the correct Admin API endpoint.
|
|
165
|
+
- If Admin API expects a different origin than the API URL host, set `caddyAdminOrigin`.
|
|
166
|
+
- Verify behavior directly:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
curl -i http://127.0.0.1:2019/config/
|
|
170
|
+
curl -i -H 'Origin: http://127.0.0.1:2019' http://127.0.0.1:2019/config/
|
|
171
|
+
```
|
|
172
|
+
|
|
119
173
|
> [!IMPORTANT]
|
|
120
174
|
> **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.
|
|
121
175
|
|
package/dist/index.d.ts
CHANGED
|
@@ -11,11 +11,15 @@ interface ViteCaddyTlsPluginOptions {
|
|
|
11
11
|
repo?: string;
|
|
12
12
|
/** Override branch name used in derived domains */
|
|
13
13
|
branch?: string;
|
|
14
|
+
/** Extra unique label appended after branch in derived domains */
|
|
15
|
+
instanceLabel?: string;
|
|
14
16
|
cors?: string;
|
|
15
17
|
/** Override the default Caddy server name (srv0) */
|
|
16
18
|
serverName?: string;
|
|
17
19
|
/** Override the Caddy Admin API base URL (default: http://localhost:2019) */
|
|
18
20
|
caddyApiUrl?: string;
|
|
21
|
+
/** Override the Origin header used for Caddy Admin API requests (defaults to caddyApiUrl origin) */
|
|
22
|
+
caddyAdminOrigin?: string;
|
|
19
23
|
/** Use Caddy's internal CA for the provided domains (defaults to true when baseDomain or domain is set) */
|
|
20
24
|
internalTls?: boolean;
|
|
21
25
|
/**
|
|
@@ -37,6 +41,6 @@ type LoopbackDomain = 'localtest.me' | 'lvh.me' | 'nip.io';
|
|
|
37
41
|
* ```
|
|
38
42
|
* @returns {Plugin} - a Vite plugin
|
|
39
43
|
*/
|
|
40
|
-
declare function viteCaddyTlsPlugin({ domain, baseDomain, loopbackDomain, repo, branch, cors, serverName, caddyApiUrl, internalTls, upstreamHostHeader, }?: ViteCaddyTlsPluginOptions): PluginOption;
|
|
44
|
+
declare function viteCaddyTlsPlugin({ domain, baseDomain, loopbackDomain, repo, branch, instanceLabel, cors, serverName, caddyApiUrl, caddyAdminOrigin, internalTls, upstreamHostHeader, }?: ViteCaddyTlsPluginOptions): PluginOption;
|
|
41
45
|
|
|
42
46
|
export { type ViteCaddyTlsPluginOptions, viteCaddyTlsPlugin as default };
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { execSync as execSync2 } from "child_process";
|
|
3
|
-
import
|
|
3
|
+
import { createHash as createHash2 } from "crypto";
|
|
4
|
+
import path2 from "path";
|
|
4
5
|
|
|
5
6
|
// src/utils.ts
|
|
6
7
|
import { execSync } from "child_process";
|
|
8
|
+
import { createHash } from "crypto";
|
|
9
|
+
import { open, unlink } from "fs/promises";
|
|
10
|
+
import os from "os";
|
|
11
|
+
import path from "path";
|
|
7
12
|
var DEFAULT_SERVER_NAME = "srv0";
|
|
8
13
|
var DEFAULT_CADDY_API_URL = "http://localhost:2019";
|
|
9
|
-
var
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
var CADDY_ADMIN_ORIGIN_POLICY_ERROR_MESSAGE = "Caddy Admin API rejected request due to origin policy. Check caddyApiUrl and admin origin settings.";
|
|
15
|
+
var CONNECTIVITY_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
16
|
+
"ECONNREFUSED",
|
|
17
|
+
"ECONNRESET",
|
|
18
|
+
"EHOSTUNREACH",
|
|
19
|
+
"ENETUNREACH",
|
|
20
|
+
"ENOTFOUND",
|
|
21
|
+
"ETIMEDOUT",
|
|
22
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
23
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
24
|
+
"UND_ERR_SOCKET"
|
|
25
|
+
]);
|
|
16
26
|
function isRecord(value) {
|
|
17
27
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
18
28
|
}
|
|
@@ -27,6 +37,126 @@ function parseConfig(text) {
|
|
|
27
37
|
function isTlsPolicyOverlapError(text) {
|
|
28
38
|
return text.includes("cannot apply more than one automation policy to host");
|
|
29
39
|
}
|
|
40
|
+
function getApiUrl(apiUrl) {
|
|
41
|
+
return apiUrl ?? DEFAULT_CADDY_API_URL;
|
|
42
|
+
}
|
|
43
|
+
function toError(error) {
|
|
44
|
+
if (error instanceof Error) return error;
|
|
45
|
+
return new Error(String(error));
|
|
46
|
+
}
|
|
47
|
+
function isOriginPolicyError(status, text) {
|
|
48
|
+
if (status !== 403) return false;
|
|
49
|
+
const normalizedText = text.toLowerCase();
|
|
50
|
+
return normalizedText.includes("origin") && normalizedText.includes("not allowed");
|
|
51
|
+
}
|
|
52
|
+
function buildCaddyRequestError(message, status, text) {
|
|
53
|
+
if (isOriginPolicyError(status, text)) {
|
|
54
|
+
return new Error(CADDY_ADMIN_ORIGIN_POLICY_ERROR_MESSAGE);
|
|
55
|
+
}
|
|
56
|
+
const normalizedText = text.trim();
|
|
57
|
+
if (!normalizedText) {
|
|
58
|
+
return new Error(`${message}: HTTP ${status}`);
|
|
59
|
+
}
|
|
60
|
+
return new Error(`${message}: ${normalizedText}`);
|
|
61
|
+
}
|
|
62
|
+
function getErrorCode(error) {
|
|
63
|
+
if (!error || typeof error !== "object") return void 0;
|
|
64
|
+
if ("code" in error && typeof error.code === "string") {
|
|
65
|
+
return error.code;
|
|
66
|
+
}
|
|
67
|
+
if ("cause" in error) {
|
|
68
|
+
const cause = error.cause;
|
|
69
|
+
if (cause && typeof cause === "object") {
|
|
70
|
+
if ("code" in cause && typeof cause.code === "string") {
|
|
71
|
+
return cause.code;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return void 0;
|
|
76
|
+
}
|
|
77
|
+
function isConnectivityError(error) {
|
|
78
|
+
const code = getErrorCode(error);
|
|
79
|
+
return Boolean(code) && CONNECTIVITY_ERROR_CODES.has(code);
|
|
80
|
+
}
|
|
81
|
+
function getAdminOrigin(apiUrl, adminOrigin) {
|
|
82
|
+
const originSource = adminOrigin ?? getApiUrl(apiUrl);
|
|
83
|
+
try {
|
|
84
|
+
return new URL(originSource).origin;
|
|
85
|
+
} catch (e) {
|
|
86
|
+
return new URL(getApiUrl(apiUrl)).origin;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function caddyFetch(input, init, apiUrl, adminOrigin) {
|
|
90
|
+
const headers = new Headers(init?.headers);
|
|
91
|
+
headers.set("Origin", getAdminOrigin(apiUrl, adminOrigin));
|
|
92
|
+
return fetch(input, {
|
|
93
|
+
...init,
|
|
94
|
+
headers
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async function checkCaddyAdminStatus(apiUrl, adminOrigin) {
|
|
98
|
+
try {
|
|
99
|
+
const res = await caddyFetch(`${getApiUrl(apiUrl)}/config/`, void 0, apiUrl, adminOrigin);
|
|
100
|
+
if (res.ok) {
|
|
101
|
+
return { status: "running" };
|
|
102
|
+
}
|
|
103
|
+
const text = await res.text();
|
|
104
|
+
return {
|
|
105
|
+
status: "api-error",
|
|
106
|
+
error: buildCaddyRequestError("Failed to read Caddy config", res.status, text)
|
|
107
|
+
};
|
|
108
|
+
} catch (e) {
|
|
109
|
+
const error = toError(e);
|
|
110
|
+
if (isConnectivityError(error)) {
|
|
111
|
+
return {
|
|
112
|
+
status: "connectivity-error",
|
|
113
|
+
error
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
status: "api-error",
|
|
118
|
+
error
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function assertCaddyResponse(res, message) {
|
|
123
|
+
if (res.ok) return;
|
|
124
|
+
const text = await res.text();
|
|
125
|
+
throw buildCaddyRequestError(message, res.status, text);
|
|
126
|
+
}
|
|
127
|
+
function getLockPath(apiUrl) {
|
|
128
|
+
const key = createHash("sha1").update(getApiUrl(apiUrl)).digest("hex").slice(0, 12);
|
|
129
|
+
return path.join(os.tmpdir(), `vite-plugin-caddy-multiple-tls-${key}.lock`);
|
|
130
|
+
}
|
|
131
|
+
function sleep(ms) {
|
|
132
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
133
|
+
}
|
|
134
|
+
async function withApiLock(apiUrl, fn) {
|
|
135
|
+
const lockPath = getLockPath(apiUrl);
|
|
136
|
+
const startedAt = Date.now();
|
|
137
|
+
const timeoutMs = 5e3;
|
|
138
|
+
while (true) {
|
|
139
|
+
try {
|
|
140
|
+
const handle = await open(lockPath, "wx");
|
|
141
|
+
try {
|
|
142
|
+
await fn();
|
|
143
|
+
} finally {
|
|
144
|
+
await handle.close();
|
|
145
|
+
await unlink(lockPath).catch(() => void 0);
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
} catch (e) {
|
|
149
|
+
if (e.code !== "EEXIST") {
|
|
150
|
+
throw e;
|
|
151
|
+
}
|
|
152
|
+
if (Date.now() - startedAt >= timeoutMs) {
|
|
153
|
+
await fn();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
await sleep(50);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
30
160
|
function validateCaddyIsInstalled() {
|
|
31
161
|
try {
|
|
32
162
|
execSync("caddy version");
|
|
@@ -36,31 +166,36 @@ function validateCaddyIsInstalled() {
|
|
|
36
166
|
return false;
|
|
37
167
|
}
|
|
38
168
|
}
|
|
39
|
-
async function
|
|
169
|
+
async function startCaddy(apiUrl, adminOrigin) {
|
|
40
170
|
try {
|
|
41
|
-
|
|
42
|
-
return res.ok;
|
|
171
|
+
execSync("caddy start", { stdio: "ignore" });
|
|
43
172
|
} catch (e) {
|
|
44
|
-
return false;
|
|
45
173
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (await isCaddyRunning()) return true;
|
|
52
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
174
|
+
for (let i = 0; i < 10; i++) {
|
|
175
|
+
const status = await checkCaddyAdminStatus(apiUrl, adminOrigin);
|
|
176
|
+
if (status.status === "running") return true;
|
|
177
|
+
if (status.status === "api-error") {
|
|
178
|
+
throw status.error;
|
|
53
179
|
}
|
|
54
|
-
|
|
55
|
-
} catch (e) {
|
|
56
|
-
console.error("Failed to start Caddy:", e);
|
|
57
|
-
return false;
|
|
180
|
+
await sleep(500);
|
|
58
181
|
}
|
|
182
|
+
return false;
|
|
59
183
|
}
|
|
60
|
-
async function ensureBaseConfig(serverName = DEFAULT_SERVER_NAME) {
|
|
61
|
-
const
|
|
62
|
-
const
|
|
184
|
+
async function ensureBaseConfig(serverName = DEFAULT_SERVER_NAME, apiUrl, adminOrigin) {
|
|
185
|
+
const resolvedApiUrl = getApiUrl(apiUrl);
|
|
186
|
+
const serverUrl = `${resolvedApiUrl}/config/apps/http/servers/${serverName}`;
|
|
187
|
+
const res = await caddyFetch(serverUrl, void 0, apiUrl, adminOrigin);
|
|
63
188
|
if (res.ok) return;
|
|
189
|
+
if (res.status === 403) {
|
|
190
|
+
const text = await res.text();
|
|
191
|
+
if (isOriginPolicyError(res.status, text)) {
|
|
192
|
+
throw buildCaddyRequestError(
|
|
193
|
+
"Failed to initialize Caddy base configuration",
|
|
194
|
+
res.status,
|
|
195
|
+
text
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
64
199
|
const baseConfig = {
|
|
65
200
|
listen: [":443"],
|
|
66
201
|
routes: []
|
|
@@ -70,11 +205,13 @@ async function ensureBaseConfig(serverName = DEFAULT_SERVER_NAME) {
|
|
|
70
205
|
[serverName]: baseConfig
|
|
71
206
|
}
|
|
72
207
|
};
|
|
73
|
-
const configRes = await
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
208
|
+
const configRes = await caddyFetch(
|
|
209
|
+
`${resolvedApiUrl}/config/`,
|
|
210
|
+
void 0,
|
|
211
|
+
apiUrl,
|
|
212
|
+
adminOrigin
|
|
213
|
+
);
|
|
214
|
+
await assertCaddyResponse(configRes, "Failed to read Caddy config");
|
|
78
215
|
const configText = await configRes.text();
|
|
79
216
|
const config = parseConfig(configText);
|
|
80
217
|
if (config === void 0) {
|
|
@@ -82,18 +219,27 @@ async function ensureBaseConfig(serverName = DEFAULT_SERVER_NAME) {
|
|
|
82
219
|
}
|
|
83
220
|
const isEmptyConfig = configText.trim() === "" || config === null || isRecord(config) && Object.keys(config).length === 0;
|
|
84
221
|
if (isEmptyConfig) {
|
|
85
|
-
const loadRes = await
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
222
|
+
const loadRes = await caddyFetch(
|
|
223
|
+
`${resolvedApiUrl}/load`,
|
|
224
|
+
{
|
|
225
|
+
method: "POST",
|
|
226
|
+
headers: { "Content-Type": "application/json" },
|
|
227
|
+
body: JSON.stringify({
|
|
228
|
+
apps: {
|
|
229
|
+
http: httpAppConfig
|
|
230
|
+
}
|
|
231
|
+
})
|
|
232
|
+
},
|
|
233
|
+
apiUrl,
|
|
234
|
+
adminOrigin
|
|
235
|
+
);
|
|
94
236
|
if (!loadRes.ok) {
|
|
95
237
|
const text = await loadRes.text();
|
|
96
|
-
throw
|
|
238
|
+
throw buildCaddyRequestError(
|
|
239
|
+
"Failed to initialize Caddy base configuration",
|
|
240
|
+
loadRes.status,
|
|
241
|
+
text
|
|
242
|
+
);
|
|
97
243
|
}
|
|
98
244
|
return;
|
|
99
245
|
}
|
|
@@ -104,81 +250,136 @@ async function ensureBaseConfig(serverName = DEFAULT_SERVER_NAME) {
|
|
|
104
250
|
let hasHttp = isRecord(http);
|
|
105
251
|
let hasServers = isRecord(servers);
|
|
106
252
|
if (!hasApps) {
|
|
107
|
-
const createAppsRes = await
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
253
|
+
const createAppsRes = await caddyFetch(
|
|
254
|
+
`${resolvedApiUrl}/config/apps`,
|
|
255
|
+
{
|
|
256
|
+
method: "PUT",
|
|
257
|
+
headers: { "Content-Type": "application/json" },
|
|
258
|
+
body: JSON.stringify({})
|
|
259
|
+
},
|
|
260
|
+
apiUrl,
|
|
261
|
+
adminOrigin
|
|
262
|
+
);
|
|
112
263
|
if (!createAppsRes.ok && createAppsRes.status !== 409) {
|
|
113
264
|
const text = await createAppsRes.text();
|
|
114
|
-
throw
|
|
265
|
+
throw buildCaddyRequestError(
|
|
266
|
+
"Failed to initialize Caddy base configuration",
|
|
267
|
+
createAppsRes.status,
|
|
268
|
+
text
|
|
269
|
+
);
|
|
115
270
|
}
|
|
116
271
|
hasApps = true;
|
|
117
272
|
}
|
|
118
273
|
if (!hasHttp) {
|
|
119
|
-
const createHttpRes = await
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
274
|
+
const createHttpRes = await caddyFetch(
|
|
275
|
+
`${resolvedApiUrl}/config/apps/http`,
|
|
276
|
+
{
|
|
277
|
+
method: "PUT",
|
|
278
|
+
headers: { "Content-Type": "application/json" },
|
|
279
|
+
body: JSON.stringify({ servers: {} })
|
|
280
|
+
},
|
|
281
|
+
apiUrl,
|
|
282
|
+
adminOrigin
|
|
283
|
+
);
|
|
124
284
|
if (!createHttpRes.ok && createHttpRes.status !== 409) {
|
|
125
285
|
const text = await createHttpRes.text();
|
|
126
|
-
throw
|
|
286
|
+
throw buildCaddyRequestError(
|
|
287
|
+
"Failed to initialize Caddy base configuration",
|
|
288
|
+
createHttpRes.status,
|
|
289
|
+
text
|
|
290
|
+
);
|
|
127
291
|
}
|
|
128
292
|
hasHttp = true;
|
|
129
293
|
hasServers = true;
|
|
130
294
|
}
|
|
131
295
|
if (!hasServers) {
|
|
132
|
-
const createServersRes = await
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
296
|
+
const createServersRes = await caddyFetch(
|
|
297
|
+
`${resolvedApiUrl}/config/apps/http/servers`,
|
|
298
|
+
{
|
|
299
|
+
method: "PUT",
|
|
300
|
+
headers: { "Content-Type": "application/json" },
|
|
301
|
+
body: JSON.stringify({})
|
|
302
|
+
},
|
|
303
|
+
apiUrl,
|
|
304
|
+
adminOrigin
|
|
305
|
+
);
|
|
137
306
|
if (!createServersRes.ok && createServersRes.status !== 409) {
|
|
138
307
|
const text = await createServersRes.text();
|
|
139
|
-
throw
|
|
308
|
+
throw buildCaddyRequestError(
|
|
309
|
+
"Failed to initialize Caddy base configuration",
|
|
310
|
+
createServersRes.status,
|
|
311
|
+
text
|
|
312
|
+
);
|
|
140
313
|
}
|
|
141
314
|
}
|
|
142
|
-
const createServerRes = await
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
315
|
+
const createServerRes = await caddyFetch(
|
|
316
|
+
serverUrl,
|
|
317
|
+
{
|
|
318
|
+
method: "PUT",
|
|
319
|
+
headers: { "Content-Type": "application/json" },
|
|
320
|
+
body: JSON.stringify(baseConfig)
|
|
321
|
+
},
|
|
322
|
+
apiUrl,
|
|
323
|
+
adminOrigin
|
|
324
|
+
);
|
|
147
325
|
if (!createServerRes.ok && createServerRes.status !== 409) {
|
|
148
326
|
const text = await createServerRes.text();
|
|
149
|
-
throw
|
|
327
|
+
throw buildCaddyRequestError(
|
|
328
|
+
"Failed to initialize Caddy base configuration",
|
|
329
|
+
createServerRes.status,
|
|
330
|
+
text
|
|
331
|
+
);
|
|
150
332
|
}
|
|
151
333
|
}
|
|
152
|
-
async function ensureTlsAutomation() {
|
|
153
|
-
const
|
|
154
|
-
const
|
|
334
|
+
async function ensureTlsAutomation(apiUrl, adminOrigin) {
|
|
335
|
+
const resolvedApiUrl = getApiUrl(apiUrl);
|
|
336
|
+
const policiesUrl = `${resolvedApiUrl}/config/apps/tls/automation/policies`;
|
|
337
|
+
const policiesRes = await caddyFetch(policiesUrl, void 0, apiUrl, adminOrigin);
|
|
155
338
|
if (policiesRes.ok) return;
|
|
156
339
|
const policiesText = await policiesRes.text();
|
|
157
340
|
if (policiesRes.status !== 404 && !policiesText.includes("invalid traversal path")) {
|
|
158
|
-
throw
|
|
159
|
-
|
|
341
|
+
throw buildCaddyRequestError(
|
|
342
|
+
"Failed to initialize Caddy TLS automation",
|
|
343
|
+
policiesRes.status,
|
|
344
|
+
policiesText
|
|
160
345
|
);
|
|
161
346
|
}
|
|
162
|
-
const automationRes = await
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
347
|
+
const automationRes = await caddyFetch(
|
|
348
|
+
`${resolvedApiUrl}/config/apps/tls/automation`,
|
|
349
|
+
{
|
|
350
|
+
method: "PUT",
|
|
351
|
+
headers: { "Content-Type": "application/json" },
|
|
352
|
+
body: JSON.stringify({ policies: [] })
|
|
353
|
+
},
|
|
354
|
+
apiUrl,
|
|
355
|
+
adminOrigin
|
|
356
|
+
);
|
|
167
357
|
if (automationRes.ok || automationRes.status === 409) return;
|
|
168
358
|
const automationText = await automationRes.text();
|
|
169
359
|
if (!automationText.includes("invalid traversal path")) {
|
|
170
|
-
throw
|
|
171
|
-
|
|
360
|
+
throw buildCaddyRequestError(
|
|
361
|
+
"Failed to initialize Caddy TLS automation",
|
|
362
|
+
automationRes.status,
|
|
363
|
+
automationText
|
|
172
364
|
);
|
|
173
365
|
}
|
|
174
|
-
const tlsRes = await
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
366
|
+
const tlsRes = await caddyFetch(
|
|
367
|
+
`${resolvedApiUrl}/config/apps/tls`,
|
|
368
|
+
{
|
|
369
|
+
method: "PUT",
|
|
370
|
+
headers: { "Content-Type": "application/json" },
|
|
371
|
+
body: JSON.stringify({ automation: { policies: [] } })
|
|
372
|
+
},
|
|
373
|
+
apiUrl,
|
|
374
|
+
adminOrigin
|
|
375
|
+
);
|
|
179
376
|
if (!tlsRes.ok && tlsRes.status !== 409) {
|
|
180
377
|
const text = await tlsRes.text();
|
|
181
|
-
throw
|
|
378
|
+
throw buildCaddyRequestError(
|
|
379
|
+
"Failed to initialize Caddy TLS automation",
|
|
380
|
+
tlsRes.status,
|
|
381
|
+
text
|
|
382
|
+
);
|
|
182
383
|
}
|
|
183
384
|
}
|
|
184
385
|
function formatDialAddress(host, port) {
|
|
@@ -187,7 +388,50 @@ function formatDialAddress(host, port) {
|
|
|
187
388
|
}
|
|
188
389
|
return `${host}:${port}`;
|
|
189
390
|
}
|
|
190
|
-
|
|
391
|
+
function extractMatchedHosts(route) {
|
|
392
|
+
if (!isRecord(route)) return [];
|
|
393
|
+
const match = route.match;
|
|
394
|
+
if (!Array.isArray(match)) return [];
|
|
395
|
+
const hosts = [];
|
|
396
|
+
for (const item of match) {
|
|
397
|
+
if (!isRecord(item) || !Array.isArray(item.host)) continue;
|
|
398
|
+
for (const host of item.host) {
|
|
399
|
+
if (typeof host === "string") {
|
|
400
|
+
hosts.push(host);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return hosts;
|
|
405
|
+
}
|
|
406
|
+
function intersectsDomains(targetDomains, routeDomains) {
|
|
407
|
+
if (targetDomains.length === 0 || routeDomains.length === 0) return false;
|
|
408
|
+
const targetSet = new Set(targetDomains);
|
|
409
|
+
return routeDomains.some((domain) => targetSet.has(domain));
|
|
410
|
+
}
|
|
411
|
+
async function cleanupStaleRoutesForDomains(domains, currentRouteId, serverName = DEFAULT_SERVER_NAME, apiUrl, adminOrigin) {
|
|
412
|
+
if (domains.length === 0) return;
|
|
413
|
+
const res = await caddyFetch(
|
|
414
|
+
`${getApiUrl(apiUrl)}/config/apps/http/servers/${serverName}/routes`,
|
|
415
|
+
void 0,
|
|
416
|
+
apiUrl,
|
|
417
|
+
adminOrigin
|
|
418
|
+
);
|
|
419
|
+
if (!res.ok) return;
|
|
420
|
+
const text = await res.text();
|
|
421
|
+
const parsed = parseConfig(text);
|
|
422
|
+
if (!Array.isArray(parsed)) return;
|
|
423
|
+
for (const route of parsed) {
|
|
424
|
+
if (!isRecord(route)) continue;
|
|
425
|
+
const id = route["@id"];
|
|
426
|
+
if (typeof id !== "string") continue;
|
|
427
|
+
if (!id.startsWith("vite-proxy-")) continue;
|
|
428
|
+
if (id === currentRouteId) continue;
|
|
429
|
+
const routeDomains = extractMatchedHosts(route);
|
|
430
|
+
if (!intersectsDomains(domains, routeDomains)) continue;
|
|
431
|
+
await removeRoute(id, apiUrl, adminOrigin);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
async function addRoute(id, domains, port, cors, serverName = DEFAULT_SERVER_NAME, upstreamHost = "127.0.0.1", upstreamHostHeader, apiUrl, adminOrigin) {
|
|
191
435
|
const handlers = [];
|
|
192
436
|
if (cors) {
|
|
193
437
|
handlers.push({
|
|
@@ -237,22 +481,24 @@ async function addRoute(id, domains, port, cors, serverName = DEFAULT_SERVER_NAM
|
|
|
237
481
|
],
|
|
238
482
|
terminal: true
|
|
239
483
|
};
|
|
240
|
-
const res = await
|
|
241
|
-
`${
|
|
484
|
+
const res = await caddyFetch(
|
|
485
|
+
`${getApiUrl(apiUrl)}/config/apps/http/servers/${serverName}/routes`,
|
|
242
486
|
{
|
|
243
487
|
method: "POST",
|
|
244
488
|
// Append to routes list
|
|
245
489
|
headers: { "Content-Type": "application/json" },
|
|
246
490
|
body: JSON.stringify(route)
|
|
247
|
-
}
|
|
491
|
+
},
|
|
492
|
+
apiUrl,
|
|
493
|
+
adminOrigin
|
|
248
494
|
);
|
|
249
495
|
if (!res.ok) {
|
|
250
496
|
const text = await res.text();
|
|
251
|
-
throw
|
|
497
|
+
throw buildCaddyRequestError("Failed to add route", res.status, text);
|
|
252
498
|
}
|
|
253
499
|
}
|
|
254
|
-
async function addTlsPolicy(id, domains) {
|
|
255
|
-
await ensureTlsAutomation();
|
|
500
|
+
async function addTlsPolicy(id, domains, apiUrl, adminOrigin) {
|
|
501
|
+
await ensureTlsAutomation(apiUrl, adminOrigin);
|
|
256
502
|
const policy = {
|
|
257
503
|
"@id": id,
|
|
258
504
|
subjects: domains,
|
|
@@ -262,39 +508,78 @@ async function addTlsPolicy(id, domains) {
|
|
|
262
508
|
}
|
|
263
509
|
]
|
|
264
510
|
};
|
|
265
|
-
const res = await
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
511
|
+
const res = await caddyFetch(
|
|
512
|
+
`${getApiUrl(apiUrl)}/config/apps/tls/automation/policies`,
|
|
513
|
+
{
|
|
514
|
+
method: "POST",
|
|
515
|
+
headers: { "Content-Type": "application/json" },
|
|
516
|
+
body: JSON.stringify(policy)
|
|
517
|
+
},
|
|
518
|
+
apiUrl,
|
|
519
|
+
adminOrigin
|
|
520
|
+
);
|
|
270
521
|
if (!res.ok) {
|
|
271
522
|
const text = await res.text();
|
|
272
523
|
if (isTlsPolicyOverlapError(text)) {
|
|
273
524
|
return;
|
|
274
525
|
}
|
|
275
|
-
throw
|
|
526
|
+
throw buildCaddyRequestError("Failed to add TLS policy", res.status, text);
|
|
276
527
|
}
|
|
277
528
|
}
|
|
278
|
-
async function removeRoute(id) {
|
|
279
|
-
const res = await
|
|
280
|
-
|
|
281
|
-
|
|
529
|
+
async function removeRoute(id, apiUrl, adminOrigin) {
|
|
530
|
+
const res = await caddyFetch(
|
|
531
|
+
`${getApiUrl(apiUrl)}/id/${id}`,
|
|
532
|
+
{
|
|
533
|
+
method: "DELETE"
|
|
534
|
+
},
|
|
535
|
+
apiUrl,
|
|
536
|
+
adminOrigin
|
|
537
|
+
);
|
|
282
538
|
if (!res.ok && res.status !== 404) {
|
|
283
|
-
|
|
539
|
+
const text = await res.text();
|
|
540
|
+
const error = buildCaddyRequestError(`Failed to remove route ${id}`, res.status, text);
|
|
541
|
+
console.error(error.message);
|
|
284
542
|
return false;
|
|
285
543
|
}
|
|
286
544
|
return true;
|
|
287
545
|
}
|
|
288
|
-
async function removeTlsPolicy(id) {
|
|
289
|
-
const res = await
|
|
290
|
-
|
|
291
|
-
|
|
546
|
+
async function removeTlsPolicy(id, apiUrl, adminOrigin) {
|
|
547
|
+
const res = await caddyFetch(
|
|
548
|
+
`${getApiUrl(apiUrl)}/id/${id}`,
|
|
549
|
+
{
|
|
550
|
+
method: "DELETE"
|
|
551
|
+
},
|
|
552
|
+
apiUrl,
|
|
553
|
+
adminOrigin
|
|
554
|
+
);
|
|
292
555
|
if (!res.ok && res.status !== 404) {
|
|
293
|
-
|
|
556
|
+
const text = await res.text();
|
|
557
|
+
const error = buildCaddyRequestError(
|
|
558
|
+
`Failed to remove TLS policy ${id}`,
|
|
559
|
+
res.status,
|
|
560
|
+
text
|
|
561
|
+
);
|
|
562
|
+
console.error(error.message);
|
|
294
563
|
return false;
|
|
295
564
|
}
|
|
296
565
|
return true;
|
|
297
566
|
}
|
|
567
|
+
async function ensureCaddyReady(serverName = DEFAULT_SERVER_NAME, apiUrl, adminOrigin) {
|
|
568
|
+
await withApiLock(apiUrl, async () => {
|
|
569
|
+
const status = await checkCaddyAdminStatus(apiUrl, adminOrigin);
|
|
570
|
+
if (status.status === "api-error") {
|
|
571
|
+
throw status.error;
|
|
572
|
+
}
|
|
573
|
+
let running = status.status === "running";
|
|
574
|
+
if (status.status === "connectivity-error") {
|
|
575
|
+
running = await startCaddy(apiUrl, adminOrigin);
|
|
576
|
+
}
|
|
577
|
+
if (!running) {
|
|
578
|
+
throw new Error("Failed to start Caddy server.");
|
|
579
|
+
}
|
|
580
|
+
await ensureBaseConfig(serverName, apiUrl, adminOrigin);
|
|
581
|
+
});
|
|
582
|
+
}
|
|
298
583
|
|
|
299
584
|
// src/index.ts
|
|
300
585
|
var LOOPBACK_DOMAINS = {
|
|
@@ -310,7 +595,7 @@ function getGitRepoInfo() {
|
|
|
310
595
|
try {
|
|
311
596
|
const repoRoot = execGit("git rev-parse --show-toplevel");
|
|
312
597
|
if (repoRoot) {
|
|
313
|
-
info.repo =
|
|
598
|
+
info.repo = path2.basename(repoRoot);
|
|
314
599
|
}
|
|
315
600
|
} catch (e) {
|
|
316
601
|
}
|
|
@@ -364,7 +649,13 @@ function buildDerivedDomain(options) {
|
|
|
364
649
|
const repoLabel = sanitizeDomainLabel(repo);
|
|
365
650
|
const branchLabel = sanitizeDomainLabel(branch);
|
|
366
651
|
if (!repoLabel || !branchLabel) return null;
|
|
367
|
-
|
|
652
|
+
const labels = [repoLabel, branchLabel];
|
|
653
|
+
if (options.instanceLabel !== void 0) {
|
|
654
|
+
const instanceLabel = sanitizeDomainLabel(options.instanceLabel);
|
|
655
|
+
if (!instanceLabel) return null;
|
|
656
|
+
labels.push(instanceLabel);
|
|
657
|
+
}
|
|
658
|
+
return `${labels.join(".")}.${baseDomain}`;
|
|
368
659
|
}
|
|
369
660
|
function normalizeDomain(domain) {
|
|
370
661
|
const trimmed = domain.trim().toLowerCase();
|
|
@@ -382,6 +673,15 @@ function normalizeCaddyApiUrl(url) {
|
|
|
382
673
|
if (!trimmed) return null;
|
|
383
674
|
return trimmed.replace(/\/+$/g, "");
|
|
384
675
|
}
|
|
676
|
+
function normalizeCaddyAdminOrigin(origin) {
|
|
677
|
+
const trimmed = origin.trim();
|
|
678
|
+
if (!trimmed) return null;
|
|
679
|
+
try {
|
|
680
|
+
return new URL(trimmed).origin;
|
|
681
|
+
} catch (e) {
|
|
682
|
+
return null;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
385
685
|
function resolveDomains(options) {
|
|
386
686
|
if (options.domain) {
|
|
387
687
|
return normalizeDomains(options.domain);
|
|
@@ -396,22 +696,35 @@ function viteCaddyTlsPlugin({
|
|
|
396
696
|
loopbackDomain,
|
|
397
697
|
repo,
|
|
398
698
|
branch,
|
|
699
|
+
instanceLabel,
|
|
399
700
|
cors,
|
|
400
701
|
serverName,
|
|
401
|
-
caddyApiUrl
|
|
702
|
+
caddyApiUrl,
|
|
703
|
+
caddyAdminOrigin,
|
|
402
704
|
internalTls,
|
|
403
705
|
upstreamHostHeader
|
|
404
706
|
} = {}) {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
707
|
+
const normalizedApiUrl = caddyApiUrl ? normalizeCaddyApiUrl(caddyApiUrl) : null;
|
|
708
|
+
const pluginCaddyApiUrl = normalizedApiUrl ?? DEFAULT_CADDY_API_URL;
|
|
709
|
+
const normalizedAdminOrigin = caddyAdminOrigin ? normalizeCaddyAdminOrigin(caddyAdminOrigin) : null;
|
|
710
|
+
const pluginCaddyAdminOrigin = normalizedAdminOrigin ?? pluginCaddyApiUrl;
|
|
711
|
+
if (caddyApiUrl !== void 0 && !normalizedApiUrl) {
|
|
712
|
+
console.warn(
|
|
713
|
+
`caddyApiUrl is empty after trimming. Falling back to ${DEFAULT_CADDY_API_URL}.`
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
if (caddyAdminOrigin !== void 0 && !normalizedAdminOrigin) {
|
|
717
|
+
console.warn(
|
|
718
|
+
`caddyAdminOrigin is invalid. Falling back to ${pluginCaddyApiUrl}.`
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
function getInstanceKey(domains, configRoot) {
|
|
722
|
+
const keyMaterial = JSON.stringify({
|
|
723
|
+
domains: [...domains].sort(),
|
|
724
|
+
cwd: process.cwd(),
|
|
725
|
+
root: configRoot ?? null
|
|
726
|
+
});
|
|
727
|
+
return createHash2("sha1").update(keyMaterial).digest("hex").slice(0, 12);
|
|
415
728
|
}
|
|
416
729
|
function isPreviewServer(server) {
|
|
417
730
|
return server.config.isProduction;
|
|
@@ -440,10 +753,11 @@ function viteCaddyTlsPlugin({
|
|
|
440
753
|
baseDomain,
|
|
441
754
|
loopbackDomain,
|
|
442
755
|
repo,
|
|
443
|
-
branch
|
|
756
|
+
branch,
|
|
757
|
+
instanceLabel
|
|
444
758
|
});
|
|
445
759
|
const domainArray = resolvedDomains ?? [];
|
|
446
|
-
const routeId = `vite-proxy-${
|
|
760
|
+
const routeId = `vite-proxy-${getInstanceKey(domainArray, config.root)}`;
|
|
447
761
|
const shouldUseInternalTls = internalTls ?? (baseDomain !== void 0 || loopbackDomain !== void 0 || domain !== void 0);
|
|
448
762
|
const tlsPolicyId = shouldUseInternalTls ? `${routeId}-tls` : null;
|
|
449
763
|
let cleanupStarted = false;
|
|
@@ -458,6 +772,9 @@ function viteCaddyTlsPlugin({
|
|
|
458
772
|
if (baseDomain !== void 0 && !normalizeBaseDomain(baseDomain)) {
|
|
459
773
|
issues.push("`baseDomain` is empty after trimming");
|
|
460
774
|
}
|
|
775
|
+
if (instanceLabel !== void 0 && !sanitizeDomainLabel(instanceLabel)) {
|
|
776
|
+
issues.push("`instanceLabel` is empty after sanitization");
|
|
777
|
+
}
|
|
461
778
|
const info = getGitRepoInfo();
|
|
462
779
|
const resolvedRepo = repo ?? info.repo;
|
|
463
780
|
const resolvedBranch = branch ?? info.branch;
|
|
@@ -553,9 +870,15 @@ function viteCaddyTlsPlugin({
|
|
|
553
870
|
if (cleanupStarted) return;
|
|
554
871
|
cleanupStarted = true;
|
|
555
872
|
if (tlsPolicyId) {
|
|
556
|
-
await removeWithRetry(
|
|
873
|
+
await removeWithRetry(
|
|
874
|
+
() => removeTlsPolicy(tlsPolicyId, pluginCaddyApiUrl, pluginCaddyAdminOrigin),
|
|
875
|
+
"TLS policy"
|
|
876
|
+
);
|
|
557
877
|
}
|
|
558
|
-
await removeWithRetry(
|
|
878
|
+
await removeWithRetry(
|
|
879
|
+
() => removeRoute(routeId, pluginCaddyApiUrl, pluginCaddyAdminOrigin),
|
|
880
|
+
"route"
|
|
881
|
+
);
|
|
559
882
|
}
|
|
560
883
|
function onServerClose() {
|
|
561
884
|
void cleanupRoute();
|
|
@@ -599,32 +922,38 @@ function viteCaddyTlsPlugin({
|
|
|
599
922
|
if (!validateCaddyIsInstalled()) {
|
|
600
923
|
return;
|
|
601
924
|
}
|
|
602
|
-
let running = await isCaddyRunning();
|
|
603
|
-
if (!running) {
|
|
604
|
-
running = await startCaddy();
|
|
605
|
-
if (!running) {
|
|
606
|
-
console.error("Failed to start Caddy server.");
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
925
|
try {
|
|
611
|
-
await
|
|
926
|
+
await ensureCaddyReady(serverName, pluginCaddyApiUrl, pluginCaddyAdminOrigin);
|
|
612
927
|
} catch (e) {
|
|
613
928
|
console.error(
|
|
614
|
-
`Failed to configure Caddy base settings. Is the Caddy Admin API reachable at ${
|
|
929
|
+
`Failed to configure Caddy base settings. Is the Caddy Admin API reachable at ${pluginCaddyApiUrl}?`,
|
|
615
930
|
e
|
|
616
931
|
);
|
|
617
932
|
return;
|
|
618
933
|
}
|
|
619
934
|
const port = getServerPort();
|
|
620
935
|
const upstreamHost = getUpstreamHost();
|
|
936
|
+
await cleanupStaleRoutesForDomains(
|
|
937
|
+
domainArray,
|
|
938
|
+
routeId,
|
|
939
|
+
serverName,
|
|
940
|
+
pluginCaddyApiUrl,
|
|
941
|
+
pluginCaddyAdminOrigin
|
|
942
|
+
);
|
|
943
|
+
await removeRoute(routeId, pluginCaddyApiUrl, pluginCaddyAdminOrigin);
|
|
621
944
|
if (tlsPolicyId) {
|
|
945
|
+
await removeTlsPolicy(tlsPolicyId, pluginCaddyApiUrl, pluginCaddyAdminOrigin);
|
|
622
946
|
try {
|
|
623
|
-
await addTlsPolicy(
|
|
947
|
+
await addTlsPolicy(
|
|
948
|
+
tlsPolicyId,
|
|
949
|
+
domainArray,
|
|
950
|
+
pluginCaddyApiUrl,
|
|
951
|
+
pluginCaddyAdminOrigin
|
|
952
|
+
);
|
|
624
953
|
tlsPolicyAdded = true;
|
|
625
954
|
} catch (e) {
|
|
626
955
|
console.error(
|
|
627
|
-
`Failed to add TLS policy to Caddy. Is the Caddy Admin API reachable at ${
|
|
956
|
+
`Failed to add TLS policy to Caddy. Is the Caddy Admin API reachable at ${pluginCaddyApiUrl}?`,
|
|
628
957
|
e
|
|
629
958
|
);
|
|
630
959
|
return;
|
|
@@ -638,14 +967,16 @@ function viteCaddyTlsPlugin({
|
|
|
638
967
|
cors,
|
|
639
968
|
serverName,
|
|
640
969
|
upstreamHost,
|
|
641
|
-
upstreamHostHeader
|
|
970
|
+
upstreamHostHeader,
|
|
971
|
+
pluginCaddyApiUrl,
|
|
972
|
+
pluginCaddyAdminOrigin
|
|
642
973
|
);
|
|
643
974
|
} catch (e) {
|
|
644
975
|
if (tlsPolicyAdded && tlsPolicyId) {
|
|
645
|
-
await removeTlsPolicy(tlsPolicyId);
|
|
976
|
+
await removeTlsPolicy(tlsPolicyId, pluginCaddyApiUrl, pluginCaddyAdminOrigin);
|
|
646
977
|
}
|
|
647
978
|
console.error(
|
|
648
|
-
`Failed to add route to Caddy. Is the Caddy Admin API reachable at ${
|
|
979
|
+
`Failed to add route to Caddy. Is the Caddy Admin API reachable at ${pluginCaddyApiUrl}?`,
|
|
649
980
|
e
|
|
650
981
|
);
|
|
651
982
|
return;
|
|
@@ -708,7 +1039,8 @@ function viteCaddyTlsPlugin({
|
|
|
708
1039
|
baseDomain,
|
|
709
1040
|
loopbackDomain,
|
|
710
1041
|
repo,
|
|
711
|
-
branch
|
|
1042
|
+
branch,
|
|
1043
|
+
instanceLabel
|
|
712
1044
|
});
|
|
713
1045
|
const defaultHmrDomain = resolvedDomains?.[0];
|
|
714
1046
|
const hmrConfig = userConfig.server?.hmr === void 0 && defaultHmrDomain ? {
|