@specific.dev/cli 0.1.87 → 0.1.89

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 (68) hide show
  1. package/dist/admin/404/index.html +1 -1
  2. package/dist/admin/404.html +1 -1
  3. package/dist/admin/__next.!KGRlZmF1bHQp.__PAGE__.txt +1 -1
  4. package/dist/admin/__next.!KGRlZmF1bHQp.txt +1 -1
  5. package/dist/admin/__next._full.txt +1 -1
  6. package/dist/admin/__next._head.txt +1 -1
  7. package/dist/admin/__next._index.txt +1 -1
  8. package/dist/admin/__next._tree.txt +1 -1
  9. package/dist/admin/_not-found/__next._full.txt +1 -1
  10. package/dist/admin/_not-found/__next._head.txt +1 -1
  11. package/dist/admin/_not-found/__next._index.txt +1 -1
  12. package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +1 -1
  13. package/dist/admin/_not-found/__next._not-found.txt +1 -1
  14. package/dist/admin/_not-found/__next._tree.txt +1 -1
  15. package/dist/admin/_not-found/index.html +1 -1
  16. package/dist/admin/_not-found/index.txt +1 -1
  17. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.__PAGE__.txt +1 -1
  18. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.txt +1 -1
  19. package/dist/admin/databases/__next.!KGRlZmF1bHQp.txt +1 -1
  20. package/dist/admin/databases/__next._full.txt +1 -1
  21. package/dist/admin/databases/__next._head.txt +1 -1
  22. package/dist/admin/databases/__next._index.txt +1 -1
  23. package/dist/admin/databases/__next._tree.txt +1 -1
  24. package/dist/admin/databases/index.html +1 -1
  25. package/dist/admin/databases/index.txt +1 -1
  26. package/dist/admin/fullscreen/__next._full.txt +1 -1
  27. package/dist/admin/fullscreen/__next._head.txt +1 -1
  28. package/dist/admin/fullscreen/__next._index.txt +1 -1
  29. package/dist/admin/fullscreen/__next._tree.txt +1 -1
  30. package/dist/admin/fullscreen/__next.fullscreen.__PAGE__.txt +1 -1
  31. package/dist/admin/fullscreen/__next.fullscreen.txt +1 -1
  32. package/dist/admin/fullscreen/databases/__next._full.txt +1 -1
  33. package/dist/admin/fullscreen/databases/__next._head.txt +1 -1
  34. package/dist/admin/fullscreen/databases/__next._index.txt +1 -1
  35. package/dist/admin/fullscreen/databases/__next._tree.txt +1 -1
  36. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.__PAGE__.txt +1 -1
  37. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.txt +1 -1
  38. package/dist/admin/fullscreen/databases/__next.fullscreen.txt +1 -1
  39. package/dist/admin/fullscreen/databases/index.html +1 -1
  40. package/dist/admin/fullscreen/databases/index.txt +1 -1
  41. package/dist/admin/fullscreen/index.html +1 -1
  42. package/dist/admin/fullscreen/index.txt +1 -1
  43. package/dist/admin/index.html +1 -1
  44. package/dist/admin/index.txt +1 -1
  45. package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.__PAGE__.txt +1 -1
  46. package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.txt +1 -1
  47. package/dist/admin/mail/__next.!KGRlZmF1bHQp.txt +1 -1
  48. package/dist/admin/mail/__next._full.txt +1 -1
  49. package/dist/admin/mail/__next._head.txt +1 -1
  50. package/dist/admin/mail/__next._index.txt +1 -1
  51. package/dist/admin/mail/__next._tree.txt +1 -1
  52. package/dist/admin/mail/index.html +1 -1
  53. package/dist/admin/mail/index.txt +1 -1
  54. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.txt +1 -1
  55. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.__PAGE__.txt +1 -1
  56. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.txt +1 -1
  57. package/dist/admin/workflows/__next._full.txt +1 -1
  58. package/dist/admin/workflows/__next._head.txt +1 -1
  59. package/dist/admin/workflows/__next._index.txt +1 -1
  60. package/dist/admin/workflows/__next._tree.txt +1 -1
  61. package/dist/admin/workflows/index.html +1 -1
  62. package/dist/admin/workflows/index.txt +1 -1
  63. package/dist/cli.js +210 -121
  64. package/dist/postinstall.js +1 -1
  65. package/package.json +1 -1
  66. /package/dist/admin/_next/static/{gTSwsUHBTwlXnncoAFB77 → QGNtzZ5y-zIMtO9d5ZuTF}/_buildManifest.js +0 -0
  67. /package/dist/admin/_next/static/{gTSwsUHBTwlXnncoAFB77 → QGNtzZ5y-zIMtO9d5ZuTF}/_clientMiddlewareManifest.json +0 -0
  68. /package/dist/admin/_next/static/{gTSwsUHBTwlXnncoAFB77 → QGNtzZ5y-zIMtO9d5ZuTF}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -185203,6 +185203,7 @@ import * as path7 from "path";
185203
185203
  import { fileURLToPath } from "url";
185204
185204
  import httpProxy from "http-proxy";
185205
185205
  import * as net2 from "net";
185206
+ import * as net3 from "net";
185206
185207
  import { spawn as spawn3 } from "child_process";
185207
185208
  import { readFile as readFile2, writeFile as writeFile2, mkdir } from "fs/promises";
185208
185209
  import { existsSync as existsSync8 } from "fs";
@@ -185210,7 +185211,7 @@ import * as path8 from "path";
185210
185211
  import * as crypto from "crypto";
185211
185212
  import * as http2 from "http";
185212
185213
  import * as crypto2 from "crypto";
185213
- import * as net3 from "net";
185214
+ import * as net4 from "net";
185214
185215
  import * as fs8 from "fs";
185215
185216
  import * as path9 from "path";
185216
185217
  import { spawn as spawn4 } from "child_process";
@@ -185220,7 +185221,7 @@ import * as path10 from "path";
185220
185221
  import { spawnSync } from "child_process";
185221
185222
  import * as fs10 from "fs";
185222
185223
  import * as path11 from "path";
185223
- import * as net4 from "net";
185224
+ import * as net5 from "net";
185224
185225
  import { spawn as spawn5 } from "child_process";
185225
185226
  import * as fs11 from "fs";
185226
185227
  import * as path12 from "path";
@@ -185238,11 +185239,11 @@ import * as fs14 from "fs";
185238
185239
  import * as path14 from "path";
185239
185240
  import { generateSlug } from "random-word-slugs";
185240
185241
  import { EventEmitter as EventEmitter2 } from "node:events";
185241
- import * as net5 from "node:net";
185242
+ import * as net6 from "node:net";
185242
185243
  import * as fs15 from "fs";
185243
185244
  import * as path15 from "path";
185244
185245
  import * as os6 from "os";
185245
- import * as net6 from "net";
185246
+ import * as net7 from "net";
185246
185247
  import { EventEmitter as EventEmitter22 } from "events";
185247
185248
  import { execSync as execSync4 } from "child_process";
185248
185249
  import * as path17 from "path";
@@ -370228,6 +370229,7 @@ var adminDir = path7.join(__dirname, "admin");
370228
370229
  var _embeddedAdmin = null;
370229
370230
  var HTTP_PORT = 80;
370230
370231
  var HTTPS_PORT = 443;
370232
+ var FALLBACK_HTTPS_PORT = 3443;
370231
370233
  var DOMAIN_SUFFIX = ".spcf.localhost";
370232
370234
  var ADMIN_DOMAIN = "spcf.localhost";
370233
370235
  var DRIZZLE_GATEWAY_PREFIX = "__drizzle_gateway";
@@ -370246,7 +370248,23 @@ var MIME_TYPES = {
370246
370248
  ".woff2": "font/woff2",
370247
370249
  ".txt": "text/plain"
370248
370250
  };
370249
- async function startHttpProxy(services, certificate, getState, instanceKey = "default") {
370251
+ function isPortAvailable(port) {
370252
+ return new Promise((resolve52) => {
370253
+ const server = net2.createServer();
370254
+ server.once("error", () => resolve52(false));
370255
+ server.once("listening", () => {
370256
+ server.close(() => resolve52(true));
370257
+ });
370258
+ server.listen(port, "0.0.0.0");
370259
+ });
370260
+ }
370261
+ async function probeHttpsPort() {
370262
+ for (const port of [HTTPS_PORT, FALLBACK_HTTPS_PORT]) {
370263
+ if (await isPortAvailable(port)) return port;
370264
+ }
370265
+ return 0;
370266
+ }
370267
+ async function startHttpProxy(services, certificate, getState, instanceKey = "default", preferredHttpsPort) {
370250
370268
  const serviceMap = /* @__PURE__ */ new Map();
370251
370269
  const adminPortMap = /* @__PURE__ */ new Map();
370252
370270
  const drizzleGatewayPortMap = /* @__PURE__ */ new Map();
@@ -370348,11 +370366,11 @@ async function startHttpProxy(services, certificate, getState, instanceKey = "de
370348
370366
  return sendNotFound(res, null, serviceMap);
370349
370367
  }
370350
370368
  const mapKey = buildMapKey(route.serviceName, route.key);
370351
- const targetPort = serviceMap.get(mapKey);
370352
- if (!targetPort) {
370369
+ const targetPort2 = serviceMap.get(mapKey);
370370
+ if (!targetPort2) {
370353
370371
  return sendNotFound(res, route.serviceName, serviceMap);
370354
370372
  }
370355
- proxy.web(req, res, { target: `http://127.0.0.1:${targetPort}` });
370373
+ proxy.web(req, res, { target: `http://127.0.0.1:${targetPort2}` });
370356
370374
  };
370357
370375
  const handleUpgrade = (req, socket, head) => {
370358
370376
  const host = req.headers.host || "";
@@ -370389,108 +370407,145 @@ async function startHttpProxy(services, certificate, getState, instanceKey = "de
370389
370407
  return;
370390
370408
  }
370391
370409
  const mapKey = buildMapKey(route.serviceName, route.key);
370392
- const targetPort = serviceMap.get(mapKey);
370393
- if (!targetPort) {
370410
+ const targetPort2 = serviceMap.get(mapKey);
370411
+ if (!targetPort2) {
370394
370412
  socket.end("HTTP/1.1 404 Not Found\r\n\r\n");
370395
370413
  return;
370396
370414
  }
370397
- proxy.ws(req, socket, head, { target: `http://127.0.0.1:${targetPort}` });
370415
+ proxy.ws(req, socket, head, { target: `http://127.0.0.1:${targetPort2}` });
370398
370416
  };
370399
- const httpServer = http.createServer(handleRequest);
370400
- httpServer.on("upgrade", handleUpgrade);
370401
- let httpsServer = https.createServer(
370402
- {
370403
- key: defaultKey,
370404
- cert: defaultCert
370405
- },
370406
- handleRequest
370407
- );
370408
- httpsServer.on("upgrade", handleUpgrade);
370409
370417
  const createHttpsServer = (key, cert) => {
370410
370418
  const server = https.createServer({ key, cert }, handleRequest);
370411
370419
  server.on("upgrade", handleUpgrade);
370412
370420
  return server;
370413
370421
  };
370414
- return new Promise((resolve52, reject) => {
370415
- let httpStarted = false;
370416
- let httpsStarted = false;
370417
- let failed = false;
370418
- const checkReady = () => {
370419
- if (failed) return;
370420
- if (httpStarted && httpsStarted) {
370421
- writeLog(
370422
- "proxy",
370423
- `HTTP/HTTPS proxy started on ports ${HTTP_PORT}/${HTTPS_PORT}`
370424
- );
370425
- resolve52({
370426
- httpPort: HTTP_PORT,
370427
- httpsPort: HTTPS_PORT,
370428
- updateServices,
370429
- updateCertificate(newCert) {
370430
- if (typeof httpsServer.setSecureContext === "function") {
370431
- httpsServer.setSecureContext({
370432
- key: newCert.key,
370433
- cert: newCert.cert
370434
- });
370435
- writeLog("proxy", "Certificate updated");
370436
- } else {
370437
- const oldServer = httpsServer;
370438
- httpsServer = createHttpsServer(newCert.key, newCert.cert);
370439
- httpsServer.listen(HTTPS_PORT, "0.0.0.0", () => {
370440
- writeLog("proxy", "Certificate updated (server restarted)");
370441
- });
370442
- oldServer.closeAllConnections?.();
370443
- oldServer.close();
370422
+ const makeInstance = (httpsServer, actualHttpsPort, httpServer, actualHttpPort) => {
370423
+ let currentHttpsServer = httpsServer;
370424
+ return {
370425
+ httpPort: actualHttpPort ?? 0,
370426
+ httpsPort: actualHttpsPort,
370427
+ updateServices,
370428
+ updateCertificate(newCert) {
370429
+ if (typeof currentHttpsServer.setSecureContext === "function") {
370430
+ currentHttpsServer.setSecureContext({
370431
+ key: newCert.key,
370432
+ cert: newCert.cert
370433
+ });
370434
+ writeLog("proxy", "Certificate updated");
370435
+ } else {
370436
+ const oldServer = currentHttpsServer;
370437
+ currentHttpsServer = createHttpsServer(newCert.key, newCert.cert);
370438
+ currentHttpsServer.listen(actualHttpsPort, "0.0.0.0", () => {
370439
+ writeLog("proxy", "Certificate updated (server restarted)");
370440
+ });
370441
+ oldServer.closeAllConnections?.();
370442
+ oldServer.close();
370443
+ }
370444
+ },
370445
+ async stop() {
370446
+ return new Promise((resolve52) => {
370447
+ const serverCount = httpServer ? 2 : 1;
370448
+ let closed = 0;
370449
+ const onClose = () => {
370450
+ closed++;
370451
+ if (closed === serverCount) {
370452
+ clearTimeout(forceCloseTimeout);
370453
+ resolve52();
370444
370454
  }
370445
- },
370446
- async stop() {
370447
- return new Promise((resolve62) => {
370448
- let closed = 0;
370449
- const onClose = () => {
370450
- closed++;
370451
- if (closed === 2) {
370452
- clearTimeout(forceCloseTimeout);
370453
- resolve62();
370454
- }
370455
- };
370456
- httpServer.close(onClose);
370457
- httpsServer.close(onClose);
370458
- const forceCloseTimeout = setTimeout(() => {
370459
- httpServer.closeAllConnections?.();
370460
- httpsServer.closeAllConnections?.();
370461
- resolve62();
370462
- }, 2e3);
370463
- });
370464
- }
370455
+ };
370456
+ currentHttpsServer.close(onClose);
370457
+ httpServer?.close(onClose);
370458
+ const forceCloseTimeout = setTimeout(() => {
370459
+ currentHttpsServer.closeAllConnections?.();
370460
+ httpServer?.closeAllConnections?.();
370461
+ resolve52();
370462
+ }, 2e3);
370465
370463
  });
370466
370464
  }
370467
370465
  };
370468
- const handleError = (port) => (err) => {
370469
- if (failed) return;
370470
- failed = true;
370471
- if (err.code === "EACCES") {
370472
- reject(
370473
- new Error(
370474
- `Cannot bind to port ${port}. Try running with sudo: sudo specific dev`
370475
- )
370476
- );
370477
- } else if (err.code === "EADDRINUSE") {
370478
- reject(new Error(`Port ${port} is already in use`));
370479
- } else {
370480
- reject(err);
370481
- }
370482
- };
370483
- httpServer.on("error", handleError(HTTP_PORT));
370484
- httpsServer.on("error", handleError(HTTPS_PORT));
370485
- httpServer.listen(HTTP_PORT, "0.0.0.0", () => {
370486
- httpStarted = true;
370487
- checkReady();
370466
+ };
370467
+ const useStandardPorts = !preferredHttpsPort || preferredHttpsPort === HTTPS_PORT;
370468
+ if (useStandardPorts) {
370469
+ try {
370470
+ return await startStandardProxy();
370471
+ } catch (err) {
370472
+ const isPortError = err instanceof Error && (err.message.includes("already in use") || err.message.includes("Cannot bind to port"));
370473
+ if (!isPortError) throw err;
370474
+ writeLog("proxy", `Standard ports unavailable (${err instanceof Error ? err.message : err}), falling back to HTTPS-only`);
370475
+ }
370476
+ }
370477
+ const targetPort = preferredHttpsPort && preferredHttpsPort !== HTTPS_PORT ? preferredHttpsPort : FALLBACK_HTTPS_PORT;
370478
+ return startFallbackProxy(targetPort);
370479
+ function startStandardProxy() {
370480
+ const httpServer = http.createServer(handleRequest);
370481
+ httpServer.on("upgrade", handleUpgrade);
370482
+ const httpsServer = https.createServer(
370483
+ { key: defaultKey, cert: defaultCert },
370484
+ handleRequest
370485
+ );
370486
+ httpsServer.on("upgrade", handleUpgrade);
370487
+ return new Promise((resolve52, reject) => {
370488
+ let httpStarted = false;
370489
+ let httpsStarted = false;
370490
+ let failed = false;
370491
+ const checkReady = () => {
370492
+ if (failed) return;
370493
+ if (httpStarted && httpsStarted) {
370494
+ writeLog("proxy", `HTTP/HTTPS proxy started on ports ${HTTP_PORT}/${HTTPS_PORT}`);
370495
+ resolve52(makeInstance(httpsServer, HTTPS_PORT, httpServer, HTTP_PORT));
370496
+ }
370497
+ };
370498
+ const handleError = (port) => (err) => {
370499
+ if (failed) return;
370500
+ failed = true;
370501
+ httpServer.close(() => {
370502
+ });
370503
+ httpsServer.close(() => {
370504
+ });
370505
+ if (err.code === "EACCES" || err.code === "EADDRINUSE") {
370506
+ reject(new Error(`Port ${port} is already in use`));
370507
+ } else {
370508
+ reject(err);
370509
+ }
370510
+ };
370511
+ httpServer.on("error", handleError(HTTP_PORT));
370512
+ httpsServer.on("error", handleError(HTTPS_PORT));
370513
+ httpServer.listen(HTTP_PORT, "0.0.0.0", () => {
370514
+ httpStarted = true;
370515
+ checkReady();
370516
+ });
370517
+ httpsServer.listen(HTTPS_PORT, "0.0.0.0", () => {
370518
+ httpsStarted = true;
370519
+ checkReady();
370520
+ });
370488
370521
  });
370489
- httpsServer.listen(HTTPS_PORT, "0.0.0.0", () => {
370490
- httpsStarted = true;
370491
- checkReady();
370522
+ }
370523
+ function startFallbackProxy(port) {
370524
+ const httpsServer = https.createServer(
370525
+ { key: defaultKey, cert: defaultCert },
370526
+ handleRequest
370527
+ );
370528
+ httpsServer.on("upgrade", handleUpgrade);
370529
+ return new Promise((resolve52, reject) => {
370530
+ const tryPort = (p) => {
370531
+ httpsServer.removeAllListeners("error");
370532
+ httpsServer.on("error", (err) => {
370533
+ if ((err.code === "EADDRINUSE" || err.code === "EACCES") && p !== 0) {
370534
+ tryPort(0);
370535
+ } else {
370536
+ reject(err);
370537
+ }
370538
+ });
370539
+ httpsServer.listen(p, "0.0.0.0", () => {
370540
+ const addr = httpsServer.address();
370541
+ const actualPort = typeof addr === "object" && addr ? addr.port : p;
370542
+ writeLog("proxy", `HTTPS proxy started on port ${actualPort} (fallback mode)`);
370543
+ resolve52(makeInstance(httpsServer, actualPort));
370544
+ });
370545
+ };
370546
+ tryPort(port);
370492
370547
  });
370493
- });
370548
+ }
370494
370549
  }
370495
370550
  function extractServiceAndKey(host) {
370496
370551
  const hostname2 = host.split(":")[0];
@@ -370866,7 +370921,7 @@ async function waitForTcpPort2(host, port, timeoutMs = 3e4) {
370866
370921
  }
370867
370922
  function checkTcpPort2(host, port) {
370868
370923
  return new Promise((resolve52) => {
370869
- const socket = new net2.Socket();
370924
+ const socket = new net3.Socket();
370870
370925
  socket.setTimeout(1e3);
370871
370926
  socket.on("connect", () => {
370872
370927
  socket.destroy();
@@ -371104,7 +371159,7 @@ async function waitForTcpPort3(host, port, timeoutMs = 3e4) {
371104
371159
  }
371105
371160
  function checkTcpPort3(host, port) {
371106
371161
  return new Promise((resolve52) => {
371107
- const socket = new net3.Socket();
371162
+ const socket = new net4.Socket();
371108
371163
  socket.setTimeout(1e3);
371109
371164
  socket.on("connect", () => {
371110
371165
  socket.destroy();
@@ -371498,7 +371553,7 @@ async function waitForTcpPort4(host, port, timeoutMs = 3e4) {
371498
371553
  }
371499
371554
  function checkTcpPort4(host, port) {
371500
371555
  return new Promise((resolve52) => {
371501
- const socket = new net4.Socket();
371556
+ const socket = new net5.Socket();
371502
371557
  socket.setTimeout(1e3);
371503
371558
  socket.on("connect", () => {
371504
371559
  socket.destroy();
@@ -372269,7 +372324,7 @@ var TunnelClientImpl = class extends EventEmitter2 {
372269
372324
  }
372270
372325
  }
372271
372326
  addPoolConnection() {
372272
- const remote = net5.connect({ host: this.tunnelHost, port: this.info.port });
372327
+ const remote = net6.connect({ host: this.tunnelHost, port: this.info.port });
372273
372328
  remote.setKeepAlive(true, 3e4);
372274
372329
  this.pool.add(remote);
372275
372330
  remote.once("data", (firstChunk) => {
@@ -372284,7 +372339,7 @@ var TunnelClientImpl = class extends EventEmitter2 {
372284
372339
  remote.on("close", () => this.onIdleClose(remote, errored));
372285
372340
  }
372286
372341
  pipeToLocal(remote, firstChunk) {
372287
- const local = net5.connect({ host: "127.0.0.1", port: this.localPort }, () => {
372342
+ const local = net6.connect({ host: "127.0.0.1", port: this.localPort }, () => {
372288
372343
  local.write(firstChunk);
372289
372344
  remote.pipe(local);
372290
372345
  local.pipe(remote);
@@ -372375,7 +372430,7 @@ var ProxyRegistryManager = class {
372375
372430
  */
372376
372431
  isProxyListening(port, timeoutMs = 1e3) {
372377
372432
  return new Promise((resolve52) => {
372378
- const socket = new net6.Socket();
372433
+ const socket = new net7.Socket();
372379
372434
  let resolved = false;
372380
372435
  const cleanup = () => {
372381
372436
  if (!resolved) {
@@ -372402,11 +372457,12 @@ var ProxyRegistryManager = class {
372402
372457
  /**
372403
372458
  * Check if the proxy owner is healthy (process running AND proxy listening).
372404
372459
  */
372405
- async isProxyOwnerHealthy(pid) {
372406
- if (!this.isProcessRunning(pid)) {
372460
+ async isProxyOwnerHealthy(owner) {
372461
+ if (!this.isProcessRunning(owner.pid)) {
372407
372462
  return false;
372408
372463
  }
372409
- const isListening = await this.isProxyListening(443);
372464
+ const port = owner.httpsPort ?? 443;
372465
+ const isListening = await this.isProxyListening(port);
372410
372466
  return isListening;
372411
372467
  }
372412
372468
  async acquireLock(timeoutMs = 5e3) {
@@ -372457,13 +372513,13 @@ var ProxyRegistryManager = class {
372457
372513
  * Try to claim ownership of the proxy.
372458
372514
  * Returns true if this process is now the owner.
372459
372515
  */
372460
- async claimProxyOwnership(key) {
372516
+ async claimProxyOwnership(key, httpsPort) {
372461
372517
  const releaseLock = await this.acquireLock();
372462
372518
  try {
372463
372519
  if (fs15.existsSync(this.ownerPath)) {
372464
372520
  const content = fs15.readFileSync(this.ownerPath, "utf-8");
372465
372521
  const ownerFile2 = JSON.parse(content);
372466
- if (await this.isProxyOwnerHealthy(ownerFile2.owner.pid)) {
372522
+ if (await this.isProxyOwnerHealthy(ownerFile2.owner)) {
372467
372523
  return false;
372468
372524
  }
372469
372525
  }
@@ -372472,7 +372528,8 @@ var ProxyRegistryManager = class {
372472
372528
  owner: {
372473
372529
  pid: process.pid,
372474
372530
  key,
372475
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
372531
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
372532
+ httpsPort
372476
372533
  }
372477
372534
  };
372478
372535
  this.writeFileAtomic(this.ownerPath, ownerFile);
@@ -372514,7 +372571,7 @@ var ProxyRegistryManager = class {
372514
372571
  try {
372515
372572
  const content = fs15.readFileSync(this.ownerPath, "utf-8");
372516
372573
  const ownerFile = JSON.parse(content);
372517
- if (!await this.isProxyOwnerHealthy(ownerFile.owner.pid)) {
372574
+ if (!await this.isProxyOwnerHealthy(ownerFile.owner)) {
372518
372575
  return null;
372519
372576
  }
372520
372577
  return ownerFile.owner;
@@ -372530,6 +372587,20 @@ var ProxyRegistryManager = class {
372530
372587
  isProxyOwner() {
372531
372588
  return this.isOwner;
372532
372589
  }
372590
+ /**
372591
+ * Get the HTTPS port used by the current proxy owner.
372592
+ * Returns 443 if unknown.
372593
+ */
372594
+ getProxyHttpsPort() {
372595
+ try {
372596
+ if (!fs15.existsSync(this.ownerPath)) return 443;
372597
+ const content = fs15.readFileSync(this.ownerPath, "utf-8");
372598
+ const ownerFile = JSON.parse(content);
372599
+ return ownerFile.owner.httpsPort ?? 443;
372600
+ } catch {
372601
+ return 443;
372602
+ }
372603
+ }
372533
372604
  /**
372534
372605
  * Register services for a key.
372535
372606
  */
@@ -372649,13 +372720,13 @@ var ProxyRegistryManager = class {
372649
372720
  * Attempt to become the proxy owner (election).
372650
372721
  * This is called when the current owner is detected as dead.
372651
372722
  */
372652
- async attemptElection(key) {
372723
+ async attemptElection(key, httpsPort) {
372653
372724
  const releaseLock = await this.acquireLock();
372654
372725
  try {
372655
372726
  if (fs15.existsSync(this.ownerPath)) {
372656
372727
  const content = fs15.readFileSync(this.ownerPath, "utf-8");
372657
372728
  const ownerFile2 = JSON.parse(content);
372658
- if (await this.isProxyOwnerHealthy(ownerFile2.owner.pid)) {
372729
+ if (await this.isProxyOwnerHealthy(ownerFile2.owner)) {
372659
372730
  return false;
372660
372731
  }
372661
372732
  fs15.unlinkSync(this.ownerPath);
@@ -372665,7 +372736,8 @@ var ProxyRegistryManager = class {
372665
372736
  owner: {
372666
372737
  pid: process.pid,
372667
372738
  key,
372668
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
372739
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
372740
+ httpsPort
372669
372741
  }
372670
372742
  };
372671
372743
  this.writeFileAtomic(this.ownerPath, ownerFile);
@@ -373503,6 +373575,9 @@ Add them to the config block in specific.local`
373503
373575
  }
373504
373576
  }
373505
373577
  }
373578
+ const existingOwner = await proxyRegistry.getProxyOwner();
373579
+ const expectedHttpsPort = existingOwner ? existingOwner.httpsPort ?? 443 : await probeHttpsPort();
373580
+ const portSuffix = expectedHttpsPort !== 443 && expectedHttpsPort !== 0 ? `:${expectedHttpsPort}` : "";
373506
373581
  const publicUrls = /* @__PURE__ */ new Map();
373507
373582
  let subdomainAllocator;
373508
373583
  if (this.tunnelEnabled && exposedServices.length > 0) {
@@ -373521,7 +373596,7 @@ Add them to the config block in specific.local`
373521
373596
  } else {
373522
373597
  const keySuffix = this.instanceKey === "default" ? "" : `.${this.instanceKey}`;
373523
373598
  for (const svc of exposedServices) {
373524
- publicUrls.set(svc.name, `${svc.name}${keySuffix}.spcf.localhost`);
373599
+ publicUrls.set(svc.name, `${svc.name}${keySuffix}.spcf.localhost${portSuffix}`);
373525
373600
  }
373526
373601
  }
373527
373602
  const runningServices = [];
@@ -373740,7 +373815,7 @@ Add them to the config block in specific.local`
373740
373815
  this.emit("adminReady", `http://localhost:${adminServer.port}`);
373741
373816
  } else {
373742
373817
  const adminHost = this.instanceKey === "default" ? "" : `${this.instanceKey}.`;
373743
- this.emit("adminReady", `https://${adminHost}spcf.localhost`);
373818
+ this.emit("adminReady", `https://${adminHost}spcf.localhost${portSuffix}`);
373744
373819
  await proxyRegistry.registerServices(
373745
373820
  this.instanceKey,
373746
373821
  adminServer.port,
@@ -373752,7 +373827,8 @@ Add them to the config block in specific.local`
373752
373827
  `Registered ${serviceInfos.length} services with proxy registry`
373753
373828
  );
373754
373829
  const becameProxyOwner = await proxyRegistry.claimProxyOwnership(
373755
- this.instanceKey
373830
+ this.instanceKey,
373831
+ expectedHttpsPort
373756
373832
  );
373757
373833
  if (becameProxyOwner) {
373758
373834
  this.systemLog(
@@ -373773,7 +373849,8 @@ Add them to the config block in specific.local`
373773
373849
  exposedServices,
373774
373850
  certificate,
373775
373851
  getState,
373776
- this.instanceKey
373852
+ this.instanceKey,
373853
+ expectedHttpsPort
373777
373854
  );
373778
373855
  this.proxy = proxy;
373779
373856
  const knownKeys = new Set(registeredKeys);
@@ -373839,8 +373916,10 @@ Add them to the config block in specific.local`
373839
373916
  this.systemLog(
373840
373917
  "Proxy owner died, attempting election"
373841
373918
  );
373919
+ const previousPort = proxyRegistry.getProxyHttpsPort();
373842
373920
  const won = await proxyRegistry.attemptElection(
373843
- this.instanceKey
373921
+ this.instanceKey,
373922
+ previousPort
373844
373923
  );
373845
373924
  if (won) {
373846
373925
  this.systemLog(
@@ -373866,7 +373945,8 @@ Add them to the config block in specific.local`
373866
373945
  exposedServices,
373867
373946
  certificate,
373868
373947
  getState,
373869
- this.instanceKey
373948
+ this.instanceKey,
373949
+ previousPort
373870
373950
  );
373871
373951
  this.proxy = proxy;
373872
373952
  this.registryWatcherCleanup = proxyRegistry.watchRegistry(
@@ -374383,7 +374463,7 @@ function trackEvent(event, properties) {
374383
374463
  event,
374384
374464
  properties: {
374385
374465
  ...properties,
374386
- cli_version: "0.1.87",
374466
+ cli_version: "0.1.89",
374387
374467
  platform: process.platform,
374388
374468
  node_version: process.version,
374389
374469
  project_id: getProjectId()
@@ -376154,6 +376234,15 @@ function DevUI({ instanceKey, tunnelEnabled }) {
376154
376234
  return /* @__PURE__ */ React6.createElement(Text6, { key: svc.name }, /* @__PURE__ */ React6.createElement(Text6, { color: isReady ? "green" : "gray" }, " \u25CF "), /* @__PURE__ */ React6.createElement(Text6, null, svc.name, portInfo));
376155
376235
  }), /* @__PURE__ */ React6.createElement(Text6, null, " ")), /* @__PURE__ */ React6.createElement(Box6, null, /* @__PURE__ */ React6.createElement(Text6, { color: "blue" }, /* @__PURE__ */ React6.createElement(Spinner4, { type: "dots" })), /* @__PURE__ */ React6.createElement(Text6, null, " Starting...")));
376156
376236
  }
376237
+ const portSuffix = (() => {
376238
+ if (!state.adminUrl || !state.adminUrl.startsWith("https://")) return "";
376239
+ try {
376240
+ const p = new URL(state.adminUrl).port;
376241
+ return p ? `:${p}` : "";
376242
+ } catch {
376243
+ return "";
376244
+ }
376245
+ })();
376157
376246
  const staticItems = [
376158
376247
  {
376159
376248
  key: "title",
@@ -376202,7 +376291,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
376202
376291
  const proxyName = endpoint.name === "default" ? svc.name : `${svc.name}-${endpoint.name}`;
376203
376292
  return {
376204
376293
  key: `svc-${svc.name}-${endpoint.name}`,
376205
- content: /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, " \u25CF "), /* @__PURE__ */ React6.createElement(Text6, null, displayName), port ? endpoint.public ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, null, " \u2192 "), /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "https://", proxyName, instanceKey === "default" ? "" : `.${instanceKey}`, ".spcf.localhost"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (localhost:", port, ")")) : /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (localhost:", port, ")") : null)
376294
+ content: /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, " \u25CF "), /* @__PURE__ */ React6.createElement(Text6, null, displayName), port ? endpoint.public ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, null, " \u2192 "), /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "https://", proxyName, instanceKey === "default" ? "" : `.${instanceKey}`, ".spcf.localhost", portSuffix), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (localhost:", port, ")")) : /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (localhost:", port, ")") : null)
376206
376295
  };
376207
376296
  });
376208
376297
  }),
@@ -377998,7 +378087,7 @@ function compareVersions(a, b) {
377998
378087
  return 0;
377999
378088
  }
378000
378089
  async function checkForUpdate() {
378001
- const currentVersion = "0.1.87";
378090
+ const currentVersion = "0.1.89";
378002
378091
  const response = await fetch(`${BINARIES_BASE_URL}/latest?t=${Date.now()}`);
378003
378092
  if (!response.ok) {
378004
378093
  throw new Error(`Failed to check for updates: HTTP ${response.status}`);
@@ -378197,7 +378286,7 @@ function updateCommand() {
378197
378286
  var program = new Command();
378198
378287
  var env = "production";
378199
378288
  var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
378200
- program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.87").enablePositionalOptions();
378289
+ program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.89").enablePositionalOptions();
378201
378290
  program.command("init").description("Initialize project for use with a coding agent").option("--agent <name...>", "Agents to configure (cursor, claude, codex, other)").action((options2) => initCommand(options2));
378202
378291
  program.command("docs [topic]").description("Fetch LLM-optimized documentation").action(docsCommand);
378203
378292
  program.command("check").description("Validate specific.hcl configuration").action(checkCommand);
@@ -182883,7 +182883,7 @@ function trackEvent(event, properties) {
182883
182883
  event,
182884
182884
  properties: {
182885
182885
  ...properties,
182886
- cli_version: "0.1.87",
182886
+ cli_version: "0.1.89",
182887
182887
  platform: process.platform,
182888
182888
  node_version: process.version,
182889
182889
  project_id: getProjectId()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specific.dev/cli",
3
- "version": "0.1.87",
3
+ "version": "0.1.89",
4
4
  "description": "CLI for Specific infrastructure-as-code",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",