vite-plugin-caddy-multiple-tls 1.6.0 → 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 CHANGED
@@ -136,6 +136,40 @@ const config = defineConfig({
136
136
  export default config;
137
137
  ```
138
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
+
139
173
  > [!IMPORTANT]
140
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.
141
175
 
package/dist/index.d.ts CHANGED
@@ -18,6 +18,8 @@ interface ViteCaddyTlsPluginOptions {
18
18
  serverName?: string;
19
19
  /** Override the Caddy Admin API base URL (default: http://localhost:2019) */
20
20
  caddyApiUrl?: string;
21
+ /** Override the Origin header used for Caddy Admin API requests (defaults to caddyApiUrl origin) */
22
+ caddyAdminOrigin?: string;
21
23
  /** Use Caddy's internal CA for the provided domains (defaults to true when baseDomain or domain is set) */
22
24
  internalTls?: boolean;
23
25
  /**
@@ -39,6 +41,6 @@ type LoopbackDomain = 'localtest.me' | 'lvh.me' | 'nip.io';
39
41
  * ```
40
42
  * @returns {Plugin} - a Vite plugin
41
43
  */
42
- declare function viteCaddyTlsPlugin({ domain, baseDomain, loopbackDomain, repo, branch, instanceLabel, 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;
43
45
 
44
46
  export { type ViteCaddyTlsPluginOptions, viteCaddyTlsPlugin as default };
package/dist/index.js CHANGED
@@ -11,6 +11,18 @@ import os from "os";
11
11
  import path from "path";
12
12
  var DEFAULT_SERVER_NAME = "srv0";
13
13
  var DEFAULT_CADDY_API_URL = "http://localhost:2019";
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
+ ]);
14
26
  function isRecord(value) {
15
27
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
16
28
  }
@@ -28,6 +40,90 @@ function isTlsPolicyOverlapError(text) {
28
40
  function getApiUrl(apiUrl) {
29
41
  return apiUrl ?? DEFAULT_CADDY_API_URL;
30
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
+ }
31
127
  function getLockPath(apiUrl) {
32
128
  const key = createHash("sha1").update(getApiUrl(apiUrl)).digest("hex").slice(0, 12);
33
129
  return path.join(os.tmpdir(), `vite-plugin-caddy-multiple-tls-${key}.lock`);
@@ -70,30 +166,36 @@ function validateCaddyIsInstalled() {
70
166
  return false;
71
167
  }
72
168
  }
73
- async function isCaddyRunning(apiUrl) {
74
- try {
75
- const res = await fetch(`${getApiUrl(apiUrl)}/config/`);
76
- return res.ok;
77
- } catch (e) {
78
- return false;
79
- }
80
- }
81
- async function startCaddy(apiUrl) {
169
+ async function startCaddy(apiUrl, adminOrigin) {
82
170
  try {
83
171
  execSync("caddy start", { stdio: "ignore" });
84
172
  } catch (e) {
85
173
  }
86
174
  for (let i = 0; i < 10; i++) {
87
- if (await isCaddyRunning(apiUrl)) return true;
175
+ const status = await checkCaddyAdminStatus(apiUrl, adminOrigin);
176
+ if (status.status === "running") return true;
177
+ if (status.status === "api-error") {
178
+ throw status.error;
179
+ }
88
180
  await sleep(500);
89
181
  }
90
182
  return false;
91
183
  }
92
- async function ensureBaseConfig(serverName = DEFAULT_SERVER_NAME, apiUrl) {
184
+ async function ensureBaseConfig(serverName = DEFAULT_SERVER_NAME, apiUrl, adminOrigin) {
93
185
  const resolvedApiUrl = getApiUrl(apiUrl);
94
186
  const serverUrl = `${resolvedApiUrl}/config/apps/http/servers/${serverName}`;
95
- const res = await fetch(serverUrl);
187
+ const res = await caddyFetch(serverUrl, void 0, apiUrl, adminOrigin);
96
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
+ }
97
199
  const baseConfig = {
98
200
  listen: [":443"],
99
201
  routes: []
@@ -103,11 +205,13 @@ async function ensureBaseConfig(serverName = DEFAULT_SERVER_NAME, apiUrl) {
103
205
  [serverName]: baseConfig
104
206
  }
105
207
  };
106
- const configRes = await fetch(`${resolvedApiUrl}/config/`);
107
- if (!configRes.ok) {
108
- const text = await configRes.text();
109
- throw new Error(`Failed to read Caddy config: ${text}`);
110
- }
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");
111
215
  const configText = await configRes.text();
112
216
  const config = parseConfig(configText);
113
217
  if (config === void 0) {
@@ -115,18 +219,27 @@ async function ensureBaseConfig(serverName = DEFAULT_SERVER_NAME, apiUrl) {
115
219
  }
116
220
  const isEmptyConfig = configText.trim() === "" || config === null || isRecord(config) && Object.keys(config).length === 0;
117
221
  if (isEmptyConfig) {
118
- const loadRes = await fetch(`${resolvedApiUrl}/load`, {
119
- method: "POST",
120
- headers: { "Content-Type": "application/json" },
121
- body: JSON.stringify({
122
- apps: {
123
- http: httpAppConfig
124
- }
125
- })
126
- });
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
+ );
127
236
  if (!loadRes.ok) {
128
237
  const text = await loadRes.text();
129
- throw new Error(`Failed to initialize Caddy base configuration: ${text}`);
238
+ throw buildCaddyRequestError(
239
+ "Failed to initialize Caddy base configuration",
240
+ loadRes.status,
241
+ text
242
+ );
130
243
  }
131
244
  return;
132
245
  }
@@ -137,82 +250,136 @@ async function ensureBaseConfig(serverName = DEFAULT_SERVER_NAME, apiUrl) {
137
250
  let hasHttp = isRecord(http);
138
251
  let hasServers = isRecord(servers);
139
252
  if (!hasApps) {
140
- const createAppsRes = await fetch(`${resolvedApiUrl}/config/apps`, {
141
- method: "PUT",
142
- headers: { "Content-Type": "application/json" },
143
- body: JSON.stringify({})
144
- });
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
+ );
145
263
  if (!createAppsRes.ok && createAppsRes.status !== 409) {
146
264
  const text = await createAppsRes.text();
147
- throw new Error(`Failed to initialize Caddy base configuration: ${text}`);
265
+ throw buildCaddyRequestError(
266
+ "Failed to initialize Caddy base configuration",
267
+ createAppsRes.status,
268
+ text
269
+ );
148
270
  }
149
271
  hasApps = true;
150
272
  }
151
273
  if (!hasHttp) {
152
- const createHttpRes = await fetch(`${resolvedApiUrl}/config/apps/http`, {
153
- method: "PUT",
154
- headers: { "Content-Type": "application/json" },
155
- body: JSON.stringify({ servers: {} })
156
- });
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
+ );
157
284
  if (!createHttpRes.ok && createHttpRes.status !== 409) {
158
285
  const text = await createHttpRes.text();
159
- throw new Error(`Failed to initialize Caddy base configuration: ${text}`);
286
+ throw buildCaddyRequestError(
287
+ "Failed to initialize Caddy base configuration",
288
+ createHttpRes.status,
289
+ text
290
+ );
160
291
  }
161
292
  hasHttp = true;
162
293
  hasServers = true;
163
294
  }
164
295
  if (!hasServers) {
165
- const createServersRes = await fetch(`${resolvedApiUrl}/config/apps/http/servers`, {
166
- method: "PUT",
167
- headers: { "Content-Type": "application/json" },
168
- body: JSON.stringify({})
169
- });
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
+ );
170
306
  if (!createServersRes.ok && createServersRes.status !== 409) {
171
307
  const text = await createServersRes.text();
172
- throw new Error(`Failed to initialize Caddy base configuration: ${text}`);
308
+ throw buildCaddyRequestError(
309
+ "Failed to initialize Caddy base configuration",
310
+ createServersRes.status,
311
+ text
312
+ );
173
313
  }
174
314
  }
175
- const createServerRes = await fetch(serverUrl, {
176
- method: "PUT",
177
- headers: { "Content-Type": "application/json" },
178
- body: JSON.stringify(baseConfig)
179
- });
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
+ );
180
325
  if (!createServerRes.ok && createServerRes.status !== 409) {
181
326
  const text = await createServerRes.text();
182
- throw new Error(`Failed to initialize Caddy base configuration: ${text}`);
327
+ throw buildCaddyRequestError(
328
+ "Failed to initialize Caddy base configuration",
329
+ createServerRes.status,
330
+ text
331
+ );
183
332
  }
184
333
  }
185
- async function ensureTlsAutomation(apiUrl) {
334
+ async function ensureTlsAutomation(apiUrl, adminOrigin) {
186
335
  const resolvedApiUrl = getApiUrl(apiUrl);
187
336
  const policiesUrl = `${resolvedApiUrl}/config/apps/tls/automation/policies`;
188
- const policiesRes = await fetch(policiesUrl);
337
+ const policiesRes = await caddyFetch(policiesUrl, void 0, apiUrl, adminOrigin);
189
338
  if (policiesRes.ok) return;
190
339
  const policiesText = await policiesRes.text();
191
340
  if (policiesRes.status !== 404 && !policiesText.includes("invalid traversal path")) {
192
- throw new Error(
193
- `Failed to initialize Caddy TLS automation: ${policiesText}`
341
+ throw buildCaddyRequestError(
342
+ "Failed to initialize Caddy TLS automation",
343
+ policiesRes.status,
344
+ policiesText
194
345
  );
195
346
  }
196
- const automationRes = await fetch(`${resolvedApiUrl}/config/apps/tls/automation`, {
197
- method: "PUT",
198
- headers: { "Content-Type": "application/json" },
199
- body: JSON.stringify({ policies: [] })
200
- });
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
+ );
201
357
  if (automationRes.ok || automationRes.status === 409) return;
202
358
  const automationText = await automationRes.text();
203
359
  if (!automationText.includes("invalid traversal path")) {
204
- throw new Error(
205
- `Failed to initialize Caddy TLS automation: ${automationText}`
360
+ throw buildCaddyRequestError(
361
+ "Failed to initialize Caddy TLS automation",
362
+ automationRes.status,
363
+ automationText
206
364
  );
207
365
  }
208
- const tlsRes = await fetch(`${resolvedApiUrl}/config/apps/tls`, {
209
- method: "PUT",
210
- headers: { "Content-Type": "application/json" },
211
- body: JSON.stringify({ automation: { policies: [] } })
212
- });
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
+ );
213
376
  if (!tlsRes.ok && tlsRes.status !== 409) {
214
377
  const text = await tlsRes.text();
215
- throw new Error(`Failed to initialize Caddy TLS automation: ${text}`);
378
+ throw buildCaddyRequestError(
379
+ "Failed to initialize Caddy TLS automation",
380
+ tlsRes.status,
381
+ text
382
+ );
216
383
  }
217
384
  }
218
385
  function formatDialAddress(host, port) {
@@ -241,10 +408,13 @@ function intersectsDomains(targetDomains, routeDomains) {
241
408
  const targetSet = new Set(targetDomains);
242
409
  return routeDomains.some((domain) => targetSet.has(domain));
243
410
  }
244
- async function cleanupStaleRoutesForDomains(domains, currentRouteId, serverName = DEFAULT_SERVER_NAME, apiUrl) {
411
+ async function cleanupStaleRoutesForDomains(domains, currentRouteId, serverName = DEFAULT_SERVER_NAME, apiUrl, adminOrigin) {
245
412
  if (domains.length === 0) return;
246
- const res = await fetch(
247
- `${getApiUrl(apiUrl)}/config/apps/http/servers/${serverName}/routes`
413
+ const res = await caddyFetch(
414
+ `${getApiUrl(apiUrl)}/config/apps/http/servers/${serverName}/routes`,
415
+ void 0,
416
+ apiUrl,
417
+ adminOrigin
248
418
  );
249
419
  if (!res.ok) return;
250
420
  const text = await res.text();
@@ -258,10 +428,10 @@ async function cleanupStaleRoutesForDomains(domains, currentRouteId, serverName
258
428
  if (id === currentRouteId) continue;
259
429
  const routeDomains = extractMatchedHosts(route);
260
430
  if (!intersectsDomains(domains, routeDomains)) continue;
261
- await removeRoute(id, apiUrl);
431
+ await removeRoute(id, apiUrl, adminOrigin);
262
432
  }
263
433
  }
264
- async function addRoute(id, domains, port, cors, serverName = DEFAULT_SERVER_NAME, upstreamHost = "127.0.0.1", upstreamHostHeader, apiUrl) {
434
+ async function addRoute(id, domains, port, cors, serverName = DEFAULT_SERVER_NAME, upstreamHost = "127.0.0.1", upstreamHostHeader, apiUrl, adminOrigin) {
265
435
  const handlers = [];
266
436
  if (cors) {
267
437
  handlers.push({
@@ -311,22 +481,24 @@ async function addRoute(id, domains, port, cors, serverName = DEFAULT_SERVER_NAM
311
481
  ],
312
482
  terminal: true
313
483
  };
314
- const res = await fetch(
484
+ const res = await caddyFetch(
315
485
  `${getApiUrl(apiUrl)}/config/apps/http/servers/${serverName}/routes`,
316
486
  {
317
487
  method: "POST",
318
488
  // Append to routes list
319
489
  headers: { "Content-Type": "application/json" },
320
490
  body: JSON.stringify(route)
321
- }
491
+ },
492
+ apiUrl,
493
+ adminOrigin
322
494
  );
323
495
  if (!res.ok) {
324
496
  const text = await res.text();
325
- throw new Error(`Failed to add route: ${text}`);
497
+ throw buildCaddyRequestError("Failed to add route", res.status, text);
326
498
  }
327
499
  }
328
- async function addTlsPolicy(id, domains, apiUrl) {
329
- await ensureTlsAutomation(apiUrl);
500
+ async function addTlsPolicy(id, domains, apiUrl, adminOrigin) {
501
+ await ensureTlsAutomation(apiUrl, adminOrigin);
330
502
  const policy = {
331
503
  "@id": id,
332
504
  subjects: domains,
@@ -336,49 +508,76 @@ async function addTlsPolicy(id, domains, apiUrl) {
336
508
  }
337
509
  ]
338
510
  };
339
- const res = await fetch(`${getApiUrl(apiUrl)}/config/apps/tls/automation/policies`, {
340
- method: "POST",
341
- headers: { "Content-Type": "application/json" },
342
- body: JSON.stringify(policy)
343
- });
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
+ );
344
521
  if (!res.ok) {
345
522
  const text = await res.text();
346
523
  if (isTlsPolicyOverlapError(text)) {
347
524
  return;
348
525
  }
349
- throw new Error(`Failed to add TLS policy: ${text}`);
526
+ throw buildCaddyRequestError("Failed to add TLS policy", res.status, text);
350
527
  }
351
528
  }
352
- async function removeRoute(id, apiUrl) {
353
- const res = await fetch(`${getApiUrl(apiUrl)}/id/${id}`, {
354
- method: "DELETE"
355
- });
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
+ );
356
538
  if (!res.ok && res.status !== 404) {
357
- console.error(`Failed to remove route ${id}`);
539
+ const text = await res.text();
540
+ const error = buildCaddyRequestError(`Failed to remove route ${id}`, res.status, text);
541
+ console.error(error.message);
358
542
  return false;
359
543
  }
360
544
  return true;
361
545
  }
362
- async function removeTlsPolicy(id, apiUrl) {
363
- const res = await fetch(`${getApiUrl(apiUrl)}/id/${id}`, {
364
- method: "DELETE"
365
- });
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
+ );
366
555
  if (!res.ok && res.status !== 404) {
367
- console.error(`Failed to remove TLS policy ${id}`);
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);
368
563
  return false;
369
564
  }
370
565
  return true;
371
566
  }
372
- async function ensureCaddyReady(serverName = DEFAULT_SERVER_NAME, apiUrl) {
567
+ async function ensureCaddyReady(serverName = DEFAULT_SERVER_NAME, apiUrl, adminOrigin) {
373
568
  await withApiLock(apiUrl, async () => {
374
- let running = await isCaddyRunning(apiUrl);
375
- if (!running) {
376
- running = await startCaddy(apiUrl);
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);
377
576
  }
378
577
  if (!running) {
379
578
  throw new Error("Failed to start Caddy server.");
380
579
  }
381
- await ensureBaseConfig(serverName, apiUrl);
580
+ await ensureBaseConfig(serverName, apiUrl, adminOrigin);
382
581
  });
383
582
  }
384
583
 
@@ -474,6 +673,15 @@ function normalizeCaddyApiUrl(url) {
474
673
  if (!trimmed) return null;
475
674
  return trimmed.replace(/\/+$/g, "");
476
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
+ }
477
685
  function resolveDomains(options) {
478
686
  if (options.domain) {
479
687
  return normalizeDomains(options.domain);
@@ -492,16 +700,24 @@ function viteCaddyTlsPlugin({
492
700
  cors,
493
701
  serverName,
494
702
  caddyApiUrl,
703
+ caddyAdminOrigin,
495
704
  internalTls,
496
705
  upstreamHostHeader
497
706
  } = {}) {
498
707
  const normalizedApiUrl = caddyApiUrl ? normalizeCaddyApiUrl(caddyApiUrl) : null;
499
708
  const pluginCaddyApiUrl = normalizedApiUrl ?? DEFAULT_CADDY_API_URL;
709
+ const normalizedAdminOrigin = caddyAdminOrigin ? normalizeCaddyAdminOrigin(caddyAdminOrigin) : null;
710
+ const pluginCaddyAdminOrigin = normalizedAdminOrigin ?? pluginCaddyApiUrl;
500
711
  if (caddyApiUrl !== void 0 && !normalizedApiUrl) {
501
712
  console.warn(
502
713
  `caddyApiUrl is empty after trimming. Falling back to ${DEFAULT_CADDY_API_URL}.`
503
714
  );
504
715
  }
716
+ if (caddyAdminOrigin !== void 0 && !normalizedAdminOrigin) {
717
+ console.warn(
718
+ `caddyAdminOrigin is invalid. Falling back to ${pluginCaddyApiUrl}.`
719
+ );
720
+ }
505
721
  function getInstanceKey(domains, configRoot) {
506
722
  const keyMaterial = JSON.stringify({
507
723
  domains: [...domains].sort(),
@@ -655,11 +871,14 @@ function viteCaddyTlsPlugin({
655
871
  cleanupStarted = true;
656
872
  if (tlsPolicyId) {
657
873
  await removeWithRetry(
658
- () => removeTlsPolicy(tlsPolicyId, pluginCaddyApiUrl),
874
+ () => removeTlsPolicy(tlsPolicyId, pluginCaddyApiUrl, pluginCaddyAdminOrigin),
659
875
  "TLS policy"
660
876
  );
661
877
  }
662
- await removeWithRetry(() => removeRoute(routeId, pluginCaddyApiUrl), "route");
878
+ await removeWithRetry(
879
+ () => removeRoute(routeId, pluginCaddyApiUrl, pluginCaddyAdminOrigin),
880
+ "route"
881
+ );
663
882
  }
664
883
  function onServerClose() {
665
884
  void cleanupRoute();
@@ -704,7 +923,7 @@ function viteCaddyTlsPlugin({
704
923
  return;
705
924
  }
706
925
  try {
707
- await ensureCaddyReady(serverName, pluginCaddyApiUrl);
926
+ await ensureCaddyReady(serverName, pluginCaddyApiUrl, pluginCaddyAdminOrigin);
708
927
  } catch (e) {
709
928
  console.error(
710
929
  `Failed to configure Caddy base settings. Is the Caddy Admin API reachable at ${pluginCaddyApiUrl}?`,
@@ -718,13 +937,19 @@ function viteCaddyTlsPlugin({
718
937
  domainArray,
719
938
  routeId,
720
939
  serverName,
721
- pluginCaddyApiUrl
940
+ pluginCaddyApiUrl,
941
+ pluginCaddyAdminOrigin
722
942
  );
723
- await removeRoute(routeId, pluginCaddyApiUrl);
943
+ await removeRoute(routeId, pluginCaddyApiUrl, pluginCaddyAdminOrigin);
724
944
  if (tlsPolicyId) {
725
- await removeTlsPolicy(tlsPolicyId, pluginCaddyApiUrl);
945
+ await removeTlsPolicy(tlsPolicyId, pluginCaddyApiUrl, pluginCaddyAdminOrigin);
726
946
  try {
727
- await addTlsPolicy(tlsPolicyId, domainArray, pluginCaddyApiUrl);
947
+ await addTlsPolicy(
948
+ tlsPolicyId,
949
+ domainArray,
950
+ pluginCaddyApiUrl,
951
+ pluginCaddyAdminOrigin
952
+ );
728
953
  tlsPolicyAdded = true;
729
954
  } catch (e) {
730
955
  console.error(
@@ -743,11 +968,12 @@ function viteCaddyTlsPlugin({
743
968
  serverName,
744
969
  upstreamHost,
745
970
  upstreamHostHeader,
746
- pluginCaddyApiUrl
971
+ pluginCaddyApiUrl,
972
+ pluginCaddyAdminOrigin
747
973
  );
748
974
  } catch (e) {
749
975
  if (tlsPolicyAdded && tlsPolicyId) {
750
- await removeTlsPolicy(tlsPolicyId, pluginCaddyApiUrl);
976
+ await removeTlsPolicy(tlsPolicyId, pluginCaddyApiUrl, pluginCaddyAdminOrigin);
751
977
  }
752
978
  console.error(
753
979
  `Failed to add route to Caddy. Is the Caddy Admin API reachable at ${pluginCaddyApiUrl}?`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-caddy-multiple-tls",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "description": "Vite plugin that uses Caddy to provide local HTTPS with derived domains.",
5
5
  "keywords": [
6
6
  "vite",