vite-plugin-caddy-multiple-tls 1.8.1 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +8 -5
  2. package/dist/index.js +301 -164
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -130,7 +130,11 @@ export default config;
130
130
 
131
131
  This derives a host like `<repo>.<branch>.web-1.localhost`.
132
132
 
133
- The plugin now treats hostname ownership as explicit. If another live Vite server already owns the resolved domain, it will refuse takeover instead of deleting the other server's route. Use `instanceLabel`, `domain`, or stop the other server first.
133
+ The plugin treats hostname ownership as latest-started-wins. If another live Vite server already owns a resolved hostname, the newly started server replaces that hostname's Caddy route so the domain points at the server you just started.
134
+
135
+ When a plugin instance has multiple domains, each hostname is managed independently. Reusing `app.localhost` from another server will not remove a sibling route like `api.localhost`.
136
+
137
+ If the plugin replaces one hostname from an older combined multi-domain route, it preserves the remaining sibling hostnames before removing the old combined route.
134
138
 
135
139
  If a previous Vite process crashed and left stale ownership behind, the plugin will reclaim it automatically and clean up the stale Caddy route before continuing.
136
140
 
@@ -179,12 +183,11 @@ export default config;
179
183
 
180
184
  ## Troubleshooting
181
185
 
182
- ### `Cannot claim ... another Vite server already owns this domain`
186
+ ### A hostname points to the newest matching server
183
187
 
184
- This means another live dev server is already using the resolved hostname.
188
+ If two dev servers resolve to the same hostname, the most recently started server owns that hostname.
185
189
 
186
- - Stop the other server if you want this one to use the same host.
187
- - Add `instanceLabel` if both servers should run at the same time.
190
+ - Add `instanceLabel` if both servers should stay reachable at the same time.
188
191
  - Pass an explicit `domain` if you want total control over the hostname.
189
192
 
190
193
  ### `client is not allowed to access from origin ''`
package/dist/index.js CHANGED
@@ -14,7 +14,6 @@ var ROUTE_ID_PREFIX = "vite-proxy-";
14
14
  var LOCK_TIMEOUT_MS = 5e3;
15
15
  var LOCK_RETRY_MS = 50;
16
16
  var ROUTE_OWNERSHIP_VERSION = 1;
17
- var ROUTE_OWNERSHIP_STALE_AFTER_MS = 3e4;
18
17
  var ROUTE_OWNERSHIP_HEARTBEAT_INTERVAL_MS = 1e4;
19
18
  var CONNECTIVITY_ERROR_CODES = /* @__PURE__ */ new Set([
20
19
  "ECONNREFUSED",
@@ -33,16 +32,6 @@ function isRecord(value) {
33
32
  function normalizeRouteOwnershipDomains(domains) {
34
33
  return Array.from(new Set(domains)).sort();
35
34
  }
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
- }
46
35
  function getRouteOwnershipDirectory() {
47
36
  return path.join(os.tmpdir(), "vite-plugin-caddy-multiple-tls", "owners");
48
37
  }
@@ -225,7 +214,14 @@ async function withFileLock(lockPath, fn) {
225
214
  }
226
215
  }
227
216
  async function withApiLock(apiUrl, fn) {
228
- await withFileLock(getLockPath(apiUrl), fn);
217
+ let result;
218
+ await withFileLock(getLockPath(apiUrl), async () => {
219
+ result = await fn();
220
+ });
221
+ return result;
222
+ }
223
+ async function withCaddyApiLock(apiUrl, fn) {
224
+ return withApiLock(apiUrl, fn);
229
225
  }
230
226
  async function readRouteOwnershipByPath(recordPath) {
231
227
  try {
@@ -271,20 +267,6 @@ async function listRouteOwnershipRecords(scope) {
271
267
  );
272
268
  });
273
269
  }
274
- function isProcessAlive(pid) {
275
- try {
276
- process.kill(pid, 0);
277
- return true;
278
- } catch (e) {
279
- return isNodeError(e) && e.code === "EPERM";
280
- }
281
- }
282
- function isRouteOwnershipActive(record, now = Date.now()) {
283
- if (isProcessAlive(record.pid)) {
284
- return true;
285
- }
286
- return record.pid <= 0 && now - record.lastSeenAt <= ROUTE_OWNERSHIP_STALE_AFTER_MS;
287
- }
288
270
  async function claimRouteOwnership(record) {
289
271
  const normalizedRecord = normalizeRouteOwnershipRecord(record);
290
272
  const { lockPath, recordPath } = getRouteOwnershipPaths(normalizedRecord);
@@ -304,29 +286,12 @@ async function claimRouteOwnership(record) {
304
286
  return candidate.ownerId !== normalizedRecord.ownerId && intersectsDomains(candidate.domains, normalizedRecord.domains);
305
287
  }
306
288
  );
307
- const reclaimableRecords = overlappingRecords.filter((candidate) => {
308
- if (!isRouteOwnershipActive(candidate)) {
309
- return true;
310
- }
311
- return canReclaimLiveOwnership(normalizedRecord, candidate);
312
- });
313
- const activeConflict = overlappingRecords.find((candidate) => {
314
- return isRouteOwnershipActive(candidate) && !canReclaimLiveOwnership(normalizedRecord, candidate);
315
- });
316
- if (activeConflict) {
317
- claimResult = {
318
- status: "active-conflict",
319
- currentRecord: normalizedRecord,
320
- existingRecord: activeConflict
321
- };
322
- return;
323
- }
324
289
  await writeRouteOwnership(normalizedRecord);
325
- if (reclaimableRecords.length > 0) {
290
+ if (overlappingRecords.length > 0) {
326
291
  claimResult = {
327
- status: "reclaimed",
292
+ status: "replaced",
328
293
  currentRecord: normalizedRecord,
329
- previousRecords: reclaimableRecords
294
+ previousRecords: overlappingRecords
330
295
  };
331
296
  return;
332
297
  }
@@ -625,6 +590,106 @@ function intersectsDomains(targetDomains, routeDomains) {
625
590
  const targetSet = new Set(targetDomains);
626
591
  return routeDomains.some((domain) => targetSet.has(domain));
627
592
  }
593
+ async function readManagedConfigById(id, apiUrl, adminOrigin) {
594
+ const res = await caddyFetch(`${getApiUrl(apiUrl)}/id/${id}`, void 0, apiUrl, adminOrigin);
595
+ if (res.status === 404) return null;
596
+ await assertCaddyResponse(res, `Failed to read managed Caddy resource ${id}`);
597
+ const text = await res.text();
598
+ const parsed = parseConfig(text);
599
+ return isRecord(parsed) ? parsed : null;
600
+ }
601
+ function createPreservedOwnershipRecord(record, domains) {
602
+ const normalizedDomains = normalizeRouteOwnershipDomains(domains);
603
+ const hash = createHash("sha1").update(`${record.routeId}:${normalizedDomains.join(",")}:${process.pid}:${Date.now()}`).digest("hex").slice(0, 12);
604
+ const ownerId = `${record.ownerId}-preserved-${hash}`;
605
+ const routeId = `${ROUTE_ID_PREFIX}preserved-${hash}`;
606
+ const now = Date.now();
607
+ return {
608
+ ...record,
609
+ ownerId,
610
+ domains: normalizedDomains,
611
+ routeId,
612
+ tlsPolicyId: record.tlsPolicyId ? `${routeId}-tls` : null,
613
+ startedAt: now,
614
+ lastSeenAt: now
615
+ };
616
+ }
617
+ async function postManagedRouteConfig(route, serverName = DEFAULT_SERVER_NAME, apiUrl, adminOrigin) {
618
+ const res = await caddyFetch(
619
+ `${getApiUrl(apiUrl)}/config/apps/http/servers/${serverName}/routes`,
620
+ {
621
+ method: "POST",
622
+ headers: { "Content-Type": "application/json" },
623
+ body: JSON.stringify(route)
624
+ },
625
+ apiUrl,
626
+ adminOrigin
627
+ );
628
+ await assertCaddyResponse(res, "Failed to preserve managed Caddy route");
629
+ }
630
+ async function postManagedTlsPolicyConfig(policy, apiUrl, adminOrigin) {
631
+ const res = await caddyFetch(
632
+ `${getApiUrl(apiUrl)}/config/apps/tls/automation/policies`,
633
+ {
634
+ method: "POST",
635
+ headers: { "Content-Type": "application/json" },
636
+ body: JSON.stringify(policy)
637
+ },
638
+ apiUrl,
639
+ adminOrigin
640
+ );
641
+ if (!res.ok) {
642
+ const text = await res.text();
643
+ if (isTlsPolicyOverlapError(text)) {
644
+ return;
645
+ }
646
+ throw buildCaddyRequestError("Failed to preserve managed Caddy TLS policy", res.status, text);
647
+ }
648
+ }
649
+ async function createManagedResourcePreservation(record, domains, serverName = DEFAULT_SERVER_NAME, apiUrl, adminOrigin) {
650
+ const remainingDomains = normalizeRouteOwnershipDomains(
651
+ domains.filter((domain) => record.domains.includes(domain))
652
+ );
653
+ if (remainingDomains.length === 0) return null;
654
+ const routeConfig = await readManagedConfigById(record.routeId, apiUrl, adminOrigin);
655
+ if (!routeConfig) return null;
656
+ const preservedRecord = createPreservedOwnershipRecord(record, remainingDomains);
657
+ const preservedRoute = {
658
+ ...routeConfig,
659
+ "@id": preservedRecord.routeId,
660
+ match: [{ host: preservedRecord.domains }]
661
+ };
662
+ const sourceTlsPolicy = record.tlsPolicyId && preservedRecord.tlsPolicyId ? await readManagedConfigById(record.tlsPolicyId, apiUrl, adminOrigin) : null;
663
+ const preservedTlsPolicy = preservedRecord.tlsPolicyId ? {
664
+ ...sourceTlsPolicy ?? {
665
+ issuers: [
666
+ {
667
+ module: "internal"
668
+ }
669
+ ]
670
+ },
671
+ "@id": preservedRecord.tlsPolicyId,
672
+ subjects: preservedRecord.domains
673
+ } : null;
674
+ await postManagedRouteConfig(preservedRoute, serverName, apiUrl, adminOrigin);
675
+ return {
676
+ record: preservedRecord,
677
+ tlsPolicy: preservedTlsPolicy
678
+ };
679
+ }
680
+ async function commitManagedResourcePreservation(preservation, apiUrl, adminOrigin) {
681
+ if (preservation.tlsPolicy) {
682
+ await postManagedTlsPolicyConfig(preservation.tlsPolicy, apiUrl, adminOrigin);
683
+ }
684
+ await writeRouteOwnership(preservation.record);
685
+ }
686
+ async function discardManagedResourcePreservation(preservation, apiUrl, adminOrigin) {
687
+ let discarded = await removeRoute(preservation.record.routeId, apiUrl, adminOrigin);
688
+ if (preservation.record.tlsPolicyId) {
689
+ discarded = await removeTlsPolicy(preservation.record.tlsPolicyId, apiUrl, adminOrigin) && discarded;
690
+ }
691
+ return discarded;
692
+ }
628
693
  async function findManagedRoutesForDomains(domains, serverName = DEFAULT_SERVER_NAME, apiUrl, adminOrigin) {
629
694
  if (domains.length === 0) return [];
630
695
  const res = await caddyFetch(
@@ -999,15 +1064,6 @@ function viteCaddyTlsPlugin({
999
1064
  lastSeenAt: now
1000
1065
  };
1001
1066
  }
1002
- function buildOwnershipConflictMessage(domains, existingRecord) {
1003
- const ownerLocation = existingRecord.configRoot ?? existingRecord.cwd;
1004
- const domainLabel = domains.join(", ");
1005
- return [
1006
- `Cannot claim ${domainLabel}: another Vite server already owns ${domains.length > 1 ? "these domains" : "this domain"}.`,
1007
- `Existing owner pid ${existingRecord.pid} from ${ownerLocation}.`,
1008
- "Stop the other server first, or use `instanceLabel` or `domain` to make the hostname unique."
1009
- ].join(" ");
1010
- }
1011
1067
  async function releaseOwnershipRecord(record) {
1012
1068
  if (!record) return;
1013
1069
  await releaseRouteOwnership(record);
@@ -1015,6 +1071,10 @@ function viteCaddyTlsPlugin({
1015
1071
  async function releaseOwnershipRecords(records) {
1016
1072
  await Promise.all(records.map((record) => releaseOwnershipRecord(record)));
1017
1073
  }
1074
+ function getPreservedDomains(record, replacedDomains) {
1075
+ const replacedDomainSet = new Set(replacedDomains);
1076
+ return record.domains.filter((domain2) => !replacedDomainSet.has(domain2));
1077
+ }
1018
1078
  async function cleanupClaimedResources(record, removeWithRetry) {
1019
1079
  let cleaned = true;
1020
1080
  if (record.tlsPolicyId) {
@@ -1046,6 +1106,17 @@ function viteCaddyTlsPlugin({
1046
1106
  }
1047
1107
  return cleaned;
1048
1108
  }
1109
+ async function discardManagedResourcePreservations(preservations) {
1110
+ await Promise.all(
1111
+ preservations.map((preservation) => {
1112
+ return discardManagedResourcePreservation(
1113
+ preservation,
1114
+ pluginCaddyApiUrl,
1115
+ pluginCaddyAdminOrigin
1116
+ );
1117
+ })
1118
+ );
1119
+ }
1049
1120
  function isPreviewServer(server) {
1050
1121
  return server.config.isProduction;
1051
1122
  }
@@ -1077,15 +1148,14 @@ function viteCaddyTlsPlugin({
1077
1148
  instanceLabel
1078
1149
  });
1079
1150
  const domainArray = resolvedDomains ?? [];
1080
- const ownerId = createOwnerId();
1081
- const ownershipRecord = createRouteOwnershipRecord(ownerId, domainArray, config.root);
1082
- const routeId = ownershipRecord.routeId;
1083
- const tlsPolicyId = ownershipRecord.tlsPolicyId;
1151
+ const ownershipRecords = domainArray.map((resolvedDomain) => {
1152
+ return createRouteOwnershipRecord(createOwnerId(), [resolvedDomain], config.root);
1153
+ });
1084
1154
  let cleanupStarted = false;
1085
1155
  let resolvedPort = null;
1086
1156
  let resolvedHost = null;
1087
1157
  let setupStarted = false;
1088
- let activeOwnershipRecord = null;
1158
+ let activeOwnershipRecords = [];
1089
1159
  let ownershipHeartbeat = null;
1090
1160
  function buildDomainResolutionMessage() {
1091
1161
  const issues = [];
@@ -1116,7 +1186,6 @@ function viteCaddyTlsPlugin({
1116
1186
  console.error(buildDomainResolutionMessage());
1117
1187
  return;
1118
1188
  }
1119
- let tlsPolicyAdded = false;
1120
1189
  function getPortFromAddress(address) {
1121
1190
  if (address && typeof address === "object" && "port" in address) {
1122
1191
  const port = address.port;
@@ -1196,12 +1265,21 @@ function viteCaddyTlsPlugin({
1196
1265
  clearInterval(ownershipHeartbeat);
1197
1266
  ownershipHeartbeat = null;
1198
1267
  }
1199
- if (!activeOwnershipRecord) return;
1200
- const cleaned = await cleanupClaimedResources(activeOwnershipRecord, removeWithRetry);
1201
- if (cleaned) {
1202
- await releaseOwnershipRecord(activeOwnershipRecord);
1203
- activeOwnershipRecord = null;
1268
+ if (activeOwnershipRecords.length === 0) return;
1269
+ await cleanupOwnershipRecords(activeOwnershipRecords);
1270
+ }
1271
+ async function cleanupOwnershipRecords(records) {
1272
+ const cleanedOwnerIds = /* @__PURE__ */ new Set();
1273
+ for (const activeOwnershipRecord of records) {
1274
+ const cleaned = await cleanupClaimedResources(activeOwnershipRecord, removeWithRetry);
1275
+ if (cleaned) {
1276
+ await releaseOwnershipRecord(activeOwnershipRecord);
1277
+ cleanedOwnerIds.add(activeOwnershipRecord.ownerId);
1278
+ }
1204
1279
  }
1280
+ activeOwnershipRecords = activeOwnershipRecords.filter((record) => {
1281
+ return !cleanedOwnerIds.has(record.ownerId);
1282
+ });
1205
1283
  }
1206
1284
  function onServerClose() {
1207
1285
  void cleanupRoute();
@@ -1246,16 +1324,28 @@ function viteCaddyTlsPlugin({
1246
1324
  console.error(`Failed to remove ${label} after ${maxAttempts} attempts.`);
1247
1325
  return false;
1248
1326
  }
1249
- function startOwnershipHeartbeat(record) {
1327
+ function startOwnershipHeartbeat() {
1250
1328
  ownershipHeartbeat = setInterval(() => {
1251
- void touchRouteOwnership(record).then((touched) => {
1252
- if (touched || !ownershipHeartbeat) {
1329
+ const records = [...activeOwnershipRecords];
1330
+ if (records.length === 0) {
1331
+ if (ownershipHeartbeat) {
1332
+ clearInterval(ownershipHeartbeat);
1333
+ ownershipHeartbeat = null;
1334
+ }
1335
+ return;
1336
+ }
1337
+ void Promise.all(records.map((record) => touchRouteOwnership(record))).then((touchResults) => {
1338
+ if (touchResults.every(Boolean) || !ownershipHeartbeat) {
1253
1339
  return;
1254
1340
  }
1341
+ const lostRecords = records.filter((_, index) => {
1342
+ return !touchResults[index];
1343
+ });
1344
+ const lostDomains = lostRecords.flatMap((record) => record.domains);
1255
1345
  console.error(
1256
- `Lost route ownership for ${domainArray.join(", ")}. Cleaning up managed Caddy resources.`
1346
+ `Lost route ownership for ${lostDomains.join(", ")}. Cleaning up managed Caddy resources.`
1257
1347
  );
1258
- void cleanupRoute();
1348
+ void cleanupOwnershipRecords(lostRecords);
1259
1349
  }).catch((error) => {
1260
1350
  console.error(
1261
1351
  `Failed to refresh route ownership for ${domainArray.join(", ")}.`,
@@ -1280,105 +1370,152 @@ function viteCaddyTlsPlugin({
1280
1370
  }
1281
1371
  const port = getServerPort();
1282
1372
  const upstreamHost = getUpstreamHost();
1283
- let claimResult;
1373
+ let configuredRoutes = false;
1284
1374
  try {
1285
- claimResult = await claimRouteOwnership(ownershipRecord);
1375
+ configuredRoutes = await withCaddyApiLock(pluginCaddyApiUrl, async () => {
1376
+ for (const ownershipRecord of ownershipRecords) {
1377
+ const routeDomains = ownershipRecord.domains;
1378
+ const routeId = ownershipRecord.routeId;
1379
+ const tlsPolicyId = ownershipRecord.tlsPolicyId;
1380
+ try {
1381
+ const claimResult = await claimRouteOwnership(ownershipRecord);
1382
+ activeOwnershipRecords.push(claimResult.currentRecord);
1383
+ if (claimResult.status === "replaced") {
1384
+ const preservations = [];
1385
+ let replaced = true;
1386
+ for (const previousRecord of claimResult.previousRecords) {
1387
+ const preservedDomains = getPreservedDomains(previousRecord, routeDomains);
1388
+ if (preservedDomains.length > 0) {
1389
+ const preservation = await createManagedResourcePreservation(
1390
+ previousRecord,
1391
+ preservedDomains,
1392
+ serverName,
1393
+ pluginCaddyApiUrl,
1394
+ pluginCaddyAdminOrigin
1395
+ );
1396
+ if (preservation) {
1397
+ preservations.push(preservation);
1398
+ }
1399
+ }
1400
+ replaced = await cleanupClaimedResources(previousRecord, removeWithRetry) && replaced;
1401
+ }
1402
+ if (!replaced) {
1403
+ console.error(
1404
+ `Failed to replace previous ownership for ${routeDomains.join(", ")}. Try stopping the other server or removing stale Caddy state manually.`
1405
+ );
1406
+ await discardManagedResourcePreservations(preservations);
1407
+ await cleanupRoute();
1408
+ return false;
1409
+ }
1410
+ try {
1411
+ await Promise.all(
1412
+ preservations.map((preservation) => {
1413
+ return commitManagedResourcePreservation(
1414
+ preservation,
1415
+ pluginCaddyApiUrl,
1416
+ pluginCaddyAdminOrigin
1417
+ );
1418
+ })
1419
+ );
1420
+ } catch (e) {
1421
+ console.error(
1422
+ `Failed to preserve sibling domains while replacing ${routeDomains.join(", ")}.`,
1423
+ e
1424
+ );
1425
+ await discardManagedResourcePreservations(preservations);
1426
+ await cleanupRoute();
1427
+ return false;
1428
+ }
1429
+ await releaseOwnershipRecords(claimResult.previousRecords);
1430
+ }
1431
+ } catch (e) {
1432
+ console.error("Failed to claim route ownership for the resolved domains.", e);
1433
+ await cleanupRoute();
1434
+ return false;
1435
+ }
1436
+ const conflictingRouteIds = (await findManagedRoutesForDomains(
1437
+ routeDomains,
1438
+ serverName,
1439
+ pluginCaddyApiUrl,
1440
+ pluginCaddyAdminOrigin
1441
+ )).filter((existingRouteId) => {
1442
+ return existingRouteId !== routeId;
1443
+ });
1444
+ const conflictingTlsPolicyIds = (await findManagedTlsPoliciesForDomains(
1445
+ routeDomains,
1446
+ pluginCaddyApiUrl,
1447
+ pluginCaddyAdminOrigin
1448
+ )).filter((existingTlsPolicyId) => {
1449
+ return existingTlsPolicyId !== tlsPolicyId;
1450
+ });
1451
+ if (conflictingRouteIds.length > 0 || conflictingTlsPolicyIds.length > 0) {
1452
+ const cleanedOrphans = await cleanupManagedResources(
1453
+ conflictingRouteIds,
1454
+ conflictingTlsPolicyIds,
1455
+ removeWithRetry
1456
+ );
1457
+ if (cleanedOrphans) {
1458
+ console.warn(
1459
+ `Reclaimed orphaned managed Caddy resources for ${routeDomains.join(", ")}.`
1460
+ );
1461
+ } else {
1462
+ console.error(
1463
+ `Cannot point ${routeDomains.join(", ")} to this dev server because Caddy still has orphaned managed resources. Remove the stale Caddy state manually.`
1464
+ );
1465
+ await cleanupRoute();
1466
+ return false;
1467
+ }
1468
+ }
1469
+ if (tlsPolicyId) {
1470
+ try {
1471
+ await addTlsPolicy(
1472
+ tlsPolicyId,
1473
+ routeDomains,
1474
+ pluginCaddyApiUrl,
1475
+ pluginCaddyAdminOrigin
1476
+ );
1477
+ } catch (e) {
1478
+ console.error(
1479
+ `Failed to add TLS policy to Caddy. Is the Caddy Admin API reachable at ${pluginCaddyApiUrl}?`,
1480
+ e
1481
+ );
1482
+ await cleanupRoute();
1483
+ return false;
1484
+ }
1485
+ }
1486
+ try {
1487
+ await addRoute(
1488
+ routeId,
1489
+ routeDomains,
1490
+ port,
1491
+ cors,
1492
+ serverName,
1493
+ upstreamHost,
1494
+ upstreamHostHeader,
1495
+ pluginCaddyApiUrl,
1496
+ pluginCaddyAdminOrigin
1497
+ );
1498
+ } catch (e) {
1499
+ console.error(
1500
+ `Failed to add route to Caddy. Is the Caddy Admin API reachable at ${pluginCaddyApiUrl}?`,
1501
+ e
1502
+ );
1503
+ await cleanupRoute();
1504
+ return false;
1505
+ }
1506
+ }
1507
+ return true;
1508
+ });
1286
1509
  } catch (e) {
1287
- console.error("Failed to claim route ownership for the resolved domains.", e);
1510
+ console.error("Failed to update Caddy routes.", e);
1511
+ await cleanupRoute();
1288
1512
  return;
1289
1513
  }
1290
- if (claimResult.status === "active-conflict") {
1291
- console.error(buildOwnershipConflictMessage(domainArray, claimResult.existingRecord));
1292
- return;
1293
- }
1294
- activeOwnershipRecord = claimResult.currentRecord;
1295
- if (claimResult.status === "reclaimed") {
1296
- let reclaimed = true;
1297
- for (const previousRecord of claimResult.previousRecords) {
1298
- reclaimed = await cleanupClaimedResources(previousRecord, removeWithRetry) && reclaimed;
1299
- }
1300
- if (!reclaimed) {
1301
- console.error(
1302
- `Failed to reclaim stale ownership for ${domainArray.join(", ")}. Try stopping the other server or removing stale Caddy state manually.`
1303
- );
1304
- await releaseOwnershipRecord(activeOwnershipRecord);
1305
- activeOwnershipRecord = null;
1306
- return;
1307
- }
1308
- await releaseOwnershipRecords(claimResult.previousRecords);
1309
- }
1310
- const conflictingRouteIds = (await findManagedRoutesForDomains(
1311
- domainArray,
1312
- serverName,
1313
- pluginCaddyApiUrl,
1314
- pluginCaddyAdminOrigin
1315
- )).filter((existingRouteId) => {
1316
- return existingRouteId !== routeId;
1317
- });
1318
- const conflictingTlsPolicyIds = (await findManagedTlsPoliciesForDomains(
1319
- domainArray,
1320
- pluginCaddyApiUrl,
1321
- pluginCaddyAdminOrigin
1322
- )).filter((existingTlsPolicyId) => {
1323
- return existingTlsPolicyId !== tlsPolicyId;
1324
- });
1325
- if (conflictingRouteIds.length > 0 || conflictingTlsPolicyIds.length > 0) {
1326
- const reclaimedOrphans = await cleanupManagedResources(
1327
- conflictingRouteIds,
1328
- conflictingTlsPolicyIds,
1329
- removeWithRetry
1330
- );
1331
- if (reclaimedOrphans) {
1332
- console.warn(`Reclaimed orphaned managed Caddy resources for ${domainArray.join(", ")}.`);
1333
- } else {
1334
- console.error(
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.`
1336
- );
1337
- await releaseOwnershipRecord(activeOwnershipRecord);
1338
- activeOwnershipRecord = null;
1339
- return;
1340
- }
1341
- }
1342
- if (tlsPolicyId) {
1343
- try {
1344
- await addTlsPolicy(tlsPolicyId, domainArray, pluginCaddyApiUrl, pluginCaddyAdminOrigin);
1345
- tlsPolicyAdded = true;
1346
- } catch (e) {
1347
- console.error(
1348
- `Failed to add TLS policy to Caddy. Is the Caddy Admin API reachable at ${pluginCaddyApiUrl}?`,
1349
- e
1350
- );
1351
- await releaseOwnershipRecord(activeOwnershipRecord);
1352
- activeOwnershipRecord = null;
1353
- return;
1354
- }
1355
- }
1356
- try {
1357
- await addRoute(
1358
- routeId,
1359
- domainArray,
1360
- port,
1361
- cors,
1362
- serverName,
1363
- upstreamHost,
1364
- upstreamHostHeader,
1365
- pluginCaddyApiUrl,
1366
- pluginCaddyAdminOrigin
1367
- );
1368
- } catch (e) {
1369
- if (tlsPolicyAdded && tlsPolicyId) {
1370
- await removeTlsPolicy(tlsPolicyId, pluginCaddyApiUrl, pluginCaddyAdminOrigin);
1371
- }
1372
- await releaseOwnershipRecord(activeOwnershipRecord);
1373
- activeOwnershipRecord = null;
1374
- console.error(
1375
- `Failed to add route to Caddy. Is the Caddy Admin API reachable at ${pluginCaddyApiUrl}?`,
1376
- e
1377
- );
1514
+ if (!configuredRoutes) {
1378
1515
  return;
1379
1516
  }
1380
- if (activeOwnershipRecord) {
1381
- startOwnershipHeartbeat(activeOwnershipRecord);
1517
+ if (activeOwnershipRecords.length > 0) {
1518
+ startOwnershipHeartbeat();
1382
1519
  }
1383
1520
  console.log("\n\u{1F512} Caddy is proxying your traffic on https");
1384
1521
  console.log(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-caddy-multiple-tls",
3
- "version": "1.8.1",
3
+ "version": "1.9.0",
4
4
  "description": "Vite plugin that uses Caddy to provide local HTTPS with derived domains.",
5
5
  "keywords": [
6
6
  "caddy",