@specific.dev/cli 0.1.37 → 0.1.38

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 (173) hide show
  1. package/dist/admin/404/index.html +1 -1
  2. package/dist/admin/404.html +1 -1
  3. package/dist/admin/__next.__PAGE__.txt +2 -2
  4. package/dist/admin/__next._full.txt +3 -3
  5. package/dist/admin/__next._head.txt +1 -1
  6. package/dist/admin/__next._index.txt +2 -2
  7. package/dist/admin/__next._tree.txt +2 -2
  8. package/dist/admin/_next/static/chunks/{de6af6d8adf8b50a.js → 0476153b08658d87.js} +2 -2
  9. package/dist/admin/_next/static/chunks/{4ab079bdcb131778.js → 1de437acc5206b1f.js} +2 -2
  10. package/dist/admin/_next/static/chunks/5ff94899b8b7a03a.css +3 -0
  11. package/dist/admin/_next/static/chunks/{895a6f91f0b479fb.js → 6aae09c9429b9f24.js} +2 -2
  12. package/dist/admin/_next/static/chunks/b71388016463cab2.js +1 -0
  13. package/dist/admin/_not-found/__next._full.txt +2 -2
  14. package/dist/admin/_not-found/__next._head.txt +1 -1
  15. package/dist/admin/_not-found/__next._index.txt +2 -2
  16. package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +1 -1
  17. package/dist/admin/_not-found/__next._not-found.txt +1 -1
  18. package/dist/admin/_not-found/__next._tree.txt +2 -2
  19. package/dist/admin/_not-found/index.html +1 -1
  20. package/dist/admin/_not-found/index.txt +2 -2
  21. package/dist/admin/databases/__next._full.txt +3 -3
  22. package/dist/admin/databases/__next._head.txt +1 -1
  23. package/dist/admin/databases/__next._index.txt +2 -2
  24. package/dist/admin/databases/__next._tree.txt +2 -2
  25. package/dist/admin/databases/__next.databases.__PAGE__.txt +2 -2
  26. package/dist/admin/databases/__next.databases.txt +1 -1
  27. package/dist/admin/databases/index.html +1 -1
  28. package/dist/admin/databases/index.txt +3 -3
  29. package/dist/admin/index.html +1 -1
  30. package/dist/admin/index.txt +3 -3
  31. package/dist/cli.js +1182 -496
  32. package/package.json +1 -1
  33. package/dist/admin/_next/static/1o2O2QPOKhuZyxcNaCjq8/_buildManifest.js +0 -11
  34. package/dist/admin/_next/static/1o2O2QPOKhuZyxcNaCjq8/_clientMiddlewareManifest.json +0 -1
  35. package/dist/admin/_next/static/1o2O2QPOKhuZyxcNaCjq8/_ssgManifest.js +0 -1
  36. package/dist/admin/_next/static/2ZtqtFX2EkuM82mTM7NWT/_buildManifest.js +0 -11
  37. package/dist/admin/_next/static/2ZtqtFX2EkuM82mTM7NWT/_clientMiddlewareManifest.json +0 -1
  38. package/dist/admin/_next/static/2ZtqtFX2EkuM82mTM7NWT/_ssgManifest.js +0 -1
  39. package/dist/admin/_next/static/2vLF5bo4ZtUfhs8bdQBoF/_buildManifest.js +0 -11
  40. package/dist/admin/_next/static/2vLF5bo4ZtUfhs8bdQBoF/_clientMiddlewareManifest.json +0 -1
  41. package/dist/admin/_next/static/2vLF5bo4ZtUfhs8bdQBoF/_ssgManifest.js +0 -1
  42. package/dist/admin/_next/static/8_8s51m7RbLVvbIechG-b/_buildManifest.js +0 -11
  43. package/dist/admin/_next/static/8_8s51m7RbLVvbIechG-b/_clientMiddlewareManifest.json +0 -1
  44. package/dist/admin/_next/static/8_8s51m7RbLVvbIechG-b/_ssgManifest.js +0 -1
  45. package/dist/admin/_next/static/C4Y44KtLnP684m57gN3Ga/_buildManifest.js +0 -11
  46. package/dist/admin/_next/static/C4Y44KtLnP684m57gN3Ga/_clientMiddlewareManifest.json +0 -1
  47. package/dist/admin/_next/static/C4Y44KtLnP684m57gN3Ga/_ssgManifest.js +0 -1
  48. package/dist/admin/_next/static/DBeh36kAJnZBeBMJSPBW8/_buildManifest.js +0 -11
  49. package/dist/admin/_next/static/DBeh36kAJnZBeBMJSPBW8/_clientMiddlewareManifest.json +0 -1
  50. package/dist/admin/_next/static/DBeh36kAJnZBeBMJSPBW8/_ssgManifest.js +0 -1
  51. package/dist/admin/_next/static/REKEUu6DP2t99jKSAqNOu/_buildManifest.js +0 -11
  52. package/dist/admin/_next/static/REKEUu6DP2t99jKSAqNOu/_clientMiddlewareManifest.json +0 -1
  53. package/dist/admin/_next/static/REKEUu6DP2t99jKSAqNOu/_ssgManifest.js +0 -1
  54. package/dist/admin/_next/static/TDGT3mkKtTe0w_CxujLxI/_buildManifest.js +0 -11
  55. package/dist/admin/_next/static/TDGT3mkKtTe0w_CxujLxI/_clientMiddlewareManifest.json +0 -1
  56. package/dist/admin/_next/static/TDGT3mkKtTe0w_CxujLxI/_ssgManifest.js +0 -1
  57. package/dist/admin/_next/static/ZHMJ-g7mAlAMt_2uCXeEk/_buildManifest.js +0 -11
  58. package/dist/admin/_next/static/ZHMJ-g7mAlAMt_2uCXeEk/_clientMiddlewareManifest.json +0 -1
  59. package/dist/admin/_next/static/ZHMJ-g7mAlAMt_2uCXeEk/_ssgManifest.js +0 -1
  60. package/dist/admin/_next/static/a6JDAB-CdvWPvS4sBgJji/_buildManifest.js +0 -11
  61. package/dist/admin/_next/static/a6JDAB-CdvWPvS4sBgJji/_clientMiddlewareManifest.json +0 -1
  62. package/dist/admin/_next/static/a6JDAB-CdvWPvS4sBgJji/_ssgManifest.js +0 -1
  63. package/dist/admin/_next/static/abFUeBpymhlx-IxygVnM9/_buildManifest.js +0 -11
  64. package/dist/admin/_next/static/abFUeBpymhlx-IxygVnM9/_clientMiddlewareManifest.json +0 -1
  65. package/dist/admin/_next/static/abFUeBpymhlx-IxygVnM9/_ssgManifest.js +0 -1
  66. package/dist/admin/_next/static/chunks/1584f10ea1cebcb2.js +0 -4
  67. package/dist/admin/_next/static/chunks/195bbec70cfcd241.js +0 -2
  68. package/dist/admin/_next/static/chunks/2583656ea9ac4ad6.js +0 -5
  69. package/dist/admin/_next/static/chunks/605800ff25160d05.js +0 -1
  70. package/dist/admin/_next/static/chunks/656b870f0567ed5f.js +0 -1
  71. package/dist/admin/_next/static/chunks/71098a6cd6181738.css +0 -3
  72. package/dist/admin/_next/static/chunks/9032f4a1aac1ca5d.css +0 -3
  73. package/dist/admin/_next/static/chunks/a28af2dc6f5fbaad.js +0 -1
  74. package/dist/admin/_next/static/chunks/b4205fc2f84bda68.css +0 -3
  75. package/dist/admin/_next/static/chunks/c1a750c25bc8d092.js +0 -1
  76. package/dist/admin/_next/static/chunks/c3d30f6f144dca51.js +0 -2
  77. package/dist/admin/_next/static/chunks/cbf55ce8731457ae.js +0 -2
  78. package/dist/admin/_next/static/chunks/f0c001244d275aab.js +0 -5
  79. package/dist/admin/_next/static/chunks/fde89fd76ad6a3d0.css +0 -3
  80. package/dist/admin/_next/static/chunks/turbopack-a3d691c83d4b1778.js +0 -4
  81. package/dist/admin/_next/static/dcjrfD44GuB6g8bZ6BcFm/_buildManifest.js +0 -11
  82. package/dist/admin/_next/static/dcjrfD44GuB6g8bZ6BcFm/_clientMiddlewareManifest.json +0 -1
  83. package/dist/admin/_next/static/dcjrfD44GuB6g8bZ6BcFm/_ssgManifest.js +0 -1
  84. package/dist/admin/_next/static/fTEa8Scx922n-dMuqN3Vc/_buildManifest.js +0 -11
  85. package/dist/admin/_next/static/fTEa8Scx922n-dMuqN3Vc/_clientMiddlewareManifest.json +0 -1
  86. package/dist/admin/_next/static/fTEa8Scx922n-dMuqN3Vc/_ssgManifest.js +0 -1
  87. package/dist/admin/_next/static/kZNwyhHft01wPtJ_AvqQT/_buildManifest.js +0 -11
  88. package/dist/admin/_next/static/kZNwyhHft01wPtJ_AvqQT/_clientMiddlewareManifest.json +0 -1
  89. package/dist/admin/_next/static/kZNwyhHft01wPtJ_AvqQT/_ssgManifest.js +0 -1
  90. package/dist/admin/_next/static/kyVInC6N3DY6NWnjkLQ4J/_buildManifest.js +0 -11
  91. package/dist/admin/_next/static/kyVInC6N3DY6NWnjkLQ4J/_clientMiddlewareManifest.json +0 -1
  92. package/dist/admin/_next/static/kyVInC6N3DY6NWnjkLQ4J/_ssgManifest.js +0 -1
  93. package/dist/admin/_next/static/twedXBdhyxpjS68UQoNAc/_buildManifest.js +0 -11
  94. package/dist/admin/_next/static/twedXBdhyxpjS68UQoNAc/_clientMiddlewareManifest.json +0 -1
  95. package/dist/admin/_next/static/twedXBdhyxpjS68UQoNAc/_ssgManifest.js +0 -1
  96. package/dist/cli.d.ts +0 -3
  97. package/dist/cli.d.ts.map +0 -1
  98. package/dist/cli.js.map +0 -1
  99. package/dist/commands/check.d.ts +0 -2
  100. package/dist/commands/check.d.ts.map +0 -1
  101. package/dist/commands/check.js +0 -104
  102. package/dist/commands/check.js.map +0 -1
  103. package/dist/commands/clean.d.ts +0 -2
  104. package/dist/commands/clean.d.ts.map +0 -1
  105. package/dist/commands/clean.js +0 -70
  106. package/dist/commands/clean.js.map +0 -1
  107. package/dist/commands/deploy.d.ts +0 -2
  108. package/dist/commands/deploy.d.ts.map +0 -1
  109. package/dist/commands/deploy.js +0 -11
  110. package/dist/commands/deploy.js.map +0 -1
  111. package/dist/commands/dev.d.ts +0 -2
  112. package/dist/commands/dev.d.ts.map +0 -1
  113. package/dist/commands/dev.js +0 -398
  114. package/dist/commands/dev.js.map +0 -1
  115. package/dist/commands/docs.d.ts +0 -2
  116. package/dist/commands/docs.d.ts.map +0 -1
  117. package/dist/commands/docs.js +0 -32
  118. package/dist/commands/docs.js.map +0 -1
  119. package/dist/commands/exec.d.ts +0 -2
  120. package/dist/commands/exec.d.ts.map +0 -1
  121. package/dist/commands/exec.js +0 -178
  122. package/dist/commands/exec.js.map +0 -1
  123. package/dist/commands/init.d.ts +0 -2
  124. package/dist/commands/init.d.ts.map +0 -1
  125. package/dist/commands/init.js +0 -339
  126. package/dist/commands/init.js.map +0 -1
  127. package/dist/commands/psql.d.ts +0 -2
  128. package/dist/commands/psql.d.ts.map +0 -1
  129. package/dist/commands/psql.js +0 -53
  130. package/dist/commands/psql.js.map +0 -1
  131. package/dist/commands/secrets.d.ts +0 -3
  132. package/dist/commands/secrets.d.ts.map +0 -1
  133. package/dist/commands/secrets.js +0 -136
  134. package/dist/commands/secrets.js.map +0 -1
  135. package/dist/lib/dev/database-manager.d.ts +0 -17
  136. package/dist/lib/dev/database-manager.d.ts.map +0 -1
  137. package/dist/lib/dev/database-manager.js +0 -114
  138. package/dist/lib/dev/database-manager.js.map +0 -1
  139. package/dist/lib/dev/env-resolver.d.ts +0 -14
  140. package/dist/lib/dev/env-resolver.d.ts.map +0 -1
  141. package/dist/lib/dev/env-resolver.js +0 -109
  142. package/dist/lib/dev/env-resolver.js.map +0 -1
  143. package/dist/lib/dev/http-proxy.d.ts +0 -11
  144. package/dist/lib/dev/http-proxy.d.ts.map +0 -1
  145. package/dist/lib/dev/http-proxy.js +0 -165
  146. package/dist/lib/dev/http-proxy.js.map +0 -1
  147. package/dist/lib/dev/index.d.ts +0 -11
  148. package/dist/lib/dev/index.d.ts.map +0 -1
  149. package/dist/lib/dev/index.js +0 -7
  150. package/dist/lib/dev/index.js.map +0 -1
  151. package/dist/lib/dev/instance-state.d.ts +0 -45
  152. package/dist/lib/dev/instance-state.d.ts.map +0 -1
  153. package/dist/lib/dev/instance-state.js +0 -213
  154. package/dist/lib/dev/instance-state.js.map +0 -1
  155. package/dist/lib/dev/port-allocator.d.ts +0 -5
  156. package/dist/lib/dev/port-allocator.d.ts.map +0 -1
  157. package/dist/lib/dev/port-allocator.js +0 -21
  158. package/dist/lib/dev/port-allocator.js.map +0 -1
  159. package/dist/lib/dev/service-runner.d.ts +0 -16
  160. package/dist/lib/dev/service-runner.d.ts.map +0 -1
  161. package/dist/lib/dev/service-runner.js +0 -61
  162. package/dist/lib/dev/service-runner.js.map +0 -1
  163. package/dist/lib/secrets/index.d.ts +0 -2
  164. package/dist/lib/secrets/index.d.ts.map +0 -1
  165. package/dist/lib/secrets/index.js +0 -2
  166. package/dist/lib/secrets/index.js.map +0 -1
  167. package/dist/lib/secrets/parser.d.ts +0 -37
  168. package/dist/lib/secrets/parser.d.ts.map +0 -1
  169. package/dist/lib/secrets/parser.js +0 -116
  170. package/dist/lib/secrets/parser.js.map +0 -1
  171. /package/dist/admin/_next/static/{0zkv3YeV6IWOWVWE1S1A1 → dtzWfchSIjRVrII5xoJl6}/_buildManifest.js +0 -0
  172. /package/dist/admin/_next/static/{0zkv3YeV6IWOWVWE1S1A1 → dtzWfchSIjRVrII5xoJl6}/_clientMiddlewareManifest.json +0 -0
  173. /package/dist/admin/_next/static/{0zkv3YeV6IWOWVWE1S1A1 → dtzWfchSIjRVrII5xoJl6}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -41,8 +41,8 @@ var require_dist = __commonJS({
41
41
  var $global, $module, $NaN = NaN;
42
42
  if ("undefined" != typeof window ? $global = window : "undefined" != typeof self ? $global = self : "undefined" != typeof global ? ($global = global).require = __require : $global = this, void 0 === $global || void 0 === $global.Array) throw new Error("no global object found");
43
43
  if ("undefined" != typeof module && ($module = module), !$global.fs && $global.require) try {
44
- var fs25 = $global.require("fs");
45
- "object" == typeof fs25 && null !== fs25 && 0 !== Object.keys(fs25).length && ($global.fs = fs25);
44
+ var fs26 = $global.require("fs");
45
+ "object" == typeof fs26 && null !== fs26 && 0 !== Object.keys(fs26).length && ($global.fs = fs26);
46
46
  } catch (e) {
47
47
  }
48
48
  if (!$global.fs) {
@@ -182368,36 +182368,39 @@ function installCAToTrustStore(certPath) {
182368
182368
  throw new Error(`Unsupported platform: ${platform5}`);
182369
182369
  }
182370
182370
  }
182371
- function generateCertificate(domain) {
182371
+ function generateCertificate(domain, keys = []) {
182372
182372
  const caDir = getCADir();
182373
182373
  const caCertPem = fs.readFileSync(path.join(caDir, "ca.crt"), "utf-8");
182374
182374
  const caKeyPem = fs.readFileSync(path.join(caDir, "ca.key"), "utf-8");
182375
182375
  const caCert = forge.pki.certificateFromPem(caCertPem);
182376
182376
  const caKey = forge.pki.privateKeyFromPem(caKeyPem);
182377
- const keys = forge.pki.rsa.generateKeyPair(2048);
182377
+ const certKeys = forge.pki.rsa.generateKeyPair(2048);
182378
182378
  const cert = forge.pki.createCertificate();
182379
- cert.publicKey = keys.publicKey;
182379
+ cert.publicKey = certKeys.publicKey;
182380
182380
  cert.serialNumber = forge.util.bytesToHex(forge.random.getBytesSync(16));
182381
182381
  cert.validity.notBefore = /* @__PURE__ */ new Date();
182382
182382
  cert.validity.notAfter = /* @__PURE__ */ new Date();
182383
182383
  cert.validity.notAfter.setDate(cert.validity.notBefore.getDate() + 7);
182384
182384
  cert.setSubject([{ name: "commonName", value: domain }]);
182385
182385
  cert.setIssuer(caCert.subject.attributes);
182386
+ const altNames = [
182387
+ { type: 2, value: domain },
182388
+ { type: 2, value: `*.${domain}` }
182389
+ ];
182390
+ for (const key of keys) {
182391
+ if (key !== "default") {
182392
+ altNames.push({ type: 2, value: `*.${key}.${domain}` });
182393
+ }
182394
+ }
182386
182395
  cert.setExtensions([
182387
182396
  { name: "basicConstraints", cA: false },
182388
182397
  { name: "keyUsage", digitalSignature: true, keyEncipherment: true },
182389
182398
  { name: "extKeyUsage", serverAuth: true },
182390
- {
182391
- name: "subjectAltName",
182392
- altNames: [
182393
- { type: 2, value: domain },
182394
- { type: 2, value: `*.${domain}` }
182395
- ]
182396
- }
182399
+ { name: "subjectAltName", altNames }
182397
182400
  ]);
182398
182401
  cert.sign(caKey, forge.md.sha256.create());
182399
182402
  return {
182400
- key: Buffer.from(forge.pki.privateKeyToPem(keys.privateKey)),
182403
+ key: Buffer.from(forge.pki.privateKeyToPem(certKeys.privateKey)),
182401
182404
  cert: Buffer.from(forge.pki.certificateToPem(cert))
182402
182405
  };
182403
182406
  }
@@ -182677,11 +182680,11 @@ import { join as join3, dirname } from "path";
182677
182680
  import { fileURLToPath } from "url";
182678
182681
  var __dirname = dirname(fileURLToPath(import.meta.url));
182679
182682
  var docsDir = join3(__dirname, "docs");
182680
- function docsCommand(path20) {
182681
- const docPath = resolveDocPath(path20);
182683
+ function docsCommand(path22) {
182684
+ const docPath = resolveDocPath(path22);
182682
182685
  if (!docPath) {
182683
182686
  console.error(
182684
- `Documentation not found: ${path20 || "index"}
182687
+ `Documentation not found: ${path22 || "index"}
182685
182688
 
182686
182689
  Run 'specific docs' to see available topics.`
182687
182690
  );
@@ -182690,16 +182693,16 @@ Run 'specific docs' to see available topics.`
182690
182693
  const content = readFileSync3(docPath, "utf-8");
182691
182694
  console.log(content);
182692
182695
  }
182693
- function resolveDocPath(path20) {
182694
- if (!path20) {
182696
+ function resolveDocPath(path22) {
182697
+ if (!path22) {
182695
182698
  const indexPath2 = join3(docsDir, "index.md");
182696
182699
  return existsSync3(indexPath2) ? indexPath2 : null;
182697
182700
  }
182698
- const directPath = join3(docsDir, `${path20}.md`);
182701
+ const directPath = join3(docsDir, `${path22}.md`);
182699
182702
  if (existsSync3(directPath)) {
182700
182703
  return directPath;
182701
182704
  }
182702
- const indexPath = join3(docsDir, path20, "index.md");
182705
+ const indexPath = join3(docsDir, path22, "index.md");
182703
182706
  if (existsSync3(indexPath)) {
182704
182707
  return indexPath;
182705
182708
  }
@@ -183234,8 +183237,8 @@ function checkCommand() {
183234
183237
  import React3, { useState as useState3, useEffect as useEffect3, useRef } from "react";
183235
183238
  import { render as render3, Text as Text3, Box as Box3, useApp as useApp2, Static } from "ink";
183236
183239
  import Spinner3 from "ink-spinner";
183237
- import * as fs11 from "fs";
183238
- import * as path12 from "path";
183240
+ import * as fs12 from "fs";
183241
+ import * as path13 from "path";
183239
183242
 
183240
183243
  // node_modules/chokidar/index.js
183241
183244
  import { EventEmitter } from "node:events";
@@ -183327,7 +183330,7 @@ var ReaddirpStream = class extends Readable {
183327
183330
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
183328
183331
  const statMethod = opts.lstat ? lstat : stat;
183329
183332
  if (wantBigintFsStats) {
183330
- this._stat = (path20) => statMethod(path20, { bigint: true });
183333
+ this._stat = (path22) => statMethod(path22, { bigint: true });
183331
183334
  } else {
183332
183335
  this._stat = statMethod;
183333
183336
  }
@@ -183352,8 +183355,8 @@ var ReaddirpStream = class extends Readable {
183352
183355
  const par = this.parent;
183353
183356
  const fil = par && par.files;
183354
183357
  if (fil && fil.length > 0) {
183355
- const { path: path20, depth } = par;
183356
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path20));
183358
+ const { path: path22, depth } = par;
183359
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path22));
183357
183360
  const awaited = await Promise.all(slice);
183358
183361
  for (const entry of awaited) {
183359
183362
  if (!entry)
@@ -183393,21 +183396,21 @@ var ReaddirpStream = class extends Readable {
183393
183396
  this.reading = false;
183394
183397
  }
183395
183398
  }
183396
- async _exploreDir(path20, depth) {
183399
+ async _exploreDir(path22, depth) {
183397
183400
  let files;
183398
183401
  try {
183399
- files = await readdir(path20, this._rdOptions);
183402
+ files = await readdir(path22, this._rdOptions);
183400
183403
  } catch (error) {
183401
183404
  this._onError(error);
183402
183405
  }
183403
- return { files, depth, path: path20 };
183406
+ return { files, depth, path: path22 };
183404
183407
  }
183405
- async _formatEntry(dirent, path20) {
183408
+ async _formatEntry(dirent, path22) {
183406
183409
  let entry;
183407
- const basename4 = this._isDirent ? dirent.name : dirent;
183410
+ const basename5 = this._isDirent ? dirent.name : dirent;
183408
183411
  try {
183409
- const fullPath = presolve(pjoin(path20, basename4));
183410
- entry = { path: prelative(this._root, fullPath), fullPath, basename: basename4 };
183412
+ const fullPath = presolve(pjoin(path22, basename5));
183413
+ entry = { path: prelative(this._root, fullPath), fullPath, basename: basename5 };
183411
183414
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
183412
183415
  } catch (err) {
183413
183416
  this._onError(err);
@@ -183806,16 +183809,16 @@ var delFromSet = (main, prop, item) => {
183806
183809
  };
183807
183810
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
183808
183811
  var FsWatchInstances = /* @__PURE__ */ new Map();
183809
- function createFsWatchInstance(path20, options2, listener, errHandler, emitRaw) {
183812
+ function createFsWatchInstance(path22, options2, listener, errHandler, emitRaw) {
183810
183813
  const handleEvent = (rawEvent, evPath) => {
183811
- listener(path20);
183812
- emitRaw(rawEvent, evPath, { watchedPath: path20 });
183813
- if (evPath && path20 !== evPath) {
183814
- fsWatchBroadcast(sp.resolve(path20, evPath), KEY_LISTENERS, sp.join(path20, evPath));
183814
+ listener(path22);
183815
+ emitRaw(rawEvent, evPath, { watchedPath: path22 });
183816
+ if (evPath && path22 !== evPath) {
183817
+ fsWatchBroadcast(sp.resolve(path22, evPath), KEY_LISTENERS, sp.join(path22, evPath));
183815
183818
  }
183816
183819
  };
183817
183820
  try {
183818
- return fs_watch(path20, {
183821
+ return fs_watch(path22, {
183819
183822
  persistent: options2.persistent
183820
183823
  }, handleEvent);
183821
183824
  } catch (error) {
@@ -183831,12 +183834,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
183831
183834
  listener(val1, val2, val3);
183832
183835
  });
183833
183836
  };
183834
- var setFsWatchListener = (path20, fullPath, options2, handlers) => {
183837
+ var setFsWatchListener = (path22, fullPath, options2, handlers) => {
183835
183838
  const { listener, errHandler, rawEmitter } = handlers;
183836
183839
  let cont = FsWatchInstances.get(fullPath);
183837
183840
  let watcher;
183838
183841
  if (!options2.persistent) {
183839
- watcher = createFsWatchInstance(path20, options2, listener, errHandler, rawEmitter);
183842
+ watcher = createFsWatchInstance(path22, options2, listener, errHandler, rawEmitter);
183840
183843
  if (!watcher)
183841
183844
  return;
183842
183845
  return watcher.close.bind(watcher);
@@ -183847,7 +183850,7 @@ var setFsWatchListener = (path20, fullPath, options2, handlers) => {
183847
183850
  addAndConvert(cont, KEY_RAW, rawEmitter);
183848
183851
  } else {
183849
183852
  watcher = createFsWatchInstance(
183850
- path20,
183853
+ path22,
183851
183854
  options2,
183852
183855
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
183853
183856
  errHandler,
@@ -183862,7 +183865,7 @@ var setFsWatchListener = (path20, fullPath, options2, handlers) => {
183862
183865
  cont.watcherUnusable = true;
183863
183866
  if (isWindows && error.code === "EPERM") {
183864
183867
  try {
183865
- const fd = await open(path20, "r");
183868
+ const fd = await open(path22, "r");
183866
183869
  await fd.close();
183867
183870
  broadcastErr(error);
183868
183871
  } catch (err) {
@@ -183893,7 +183896,7 @@ var setFsWatchListener = (path20, fullPath, options2, handlers) => {
183893
183896
  };
183894
183897
  };
183895
183898
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
183896
- var setFsWatchFileListener = (path20, fullPath, options2, handlers) => {
183899
+ var setFsWatchFileListener = (path22, fullPath, options2, handlers) => {
183897
183900
  const { listener, rawEmitter } = handlers;
183898
183901
  let cont = FsWatchFileInstances.get(fullPath);
183899
183902
  const copts = cont && cont.options;
@@ -183915,7 +183918,7 @@ var setFsWatchFileListener = (path20, fullPath, options2, handlers) => {
183915
183918
  });
183916
183919
  const currmtime = curr.mtimeMs;
183917
183920
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
183918
- foreach(cont.listeners, (listener2) => listener2(path20, curr));
183921
+ foreach(cont.listeners, (listener2) => listener2(path22, curr));
183919
183922
  }
183920
183923
  })
183921
183924
  };
@@ -183945,13 +183948,13 @@ var NodeFsHandler = class {
183945
183948
  * @param listener on fs change
183946
183949
  * @returns closer for the watcher instance
183947
183950
  */
183948
- _watchWithNodeFs(path20, listener) {
183951
+ _watchWithNodeFs(path22, listener) {
183949
183952
  const opts = this.fsw.options;
183950
- const directory = sp.dirname(path20);
183951
- const basename4 = sp.basename(path20);
183953
+ const directory = sp.dirname(path22);
183954
+ const basename5 = sp.basename(path22);
183952
183955
  const parent = this.fsw._getWatchedDir(directory);
183953
- parent.add(basename4);
183954
- const absolutePath = sp.resolve(path20);
183956
+ parent.add(basename5);
183957
+ const absolutePath = sp.resolve(path22);
183955
183958
  const options2 = {
183956
183959
  persistent: opts.persistent
183957
183960
  };
@@ -183960,13 +183963,13 @@ var NodeFsHandler = class {
183960
183963
  let closer;
183961
183964
  if (opts.usePolling) {
183962
183965
  const enableBin = opts.interval !== opts.binaryInterval;
183963
- options2.interval = enableBin && isBinaryPath(basename4) ? opts.binaryInterval : opts.interval;
183964
- closer = setFsWatchFileListener(path20, absolutePath, options2, {
183966
+ options2.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
183967
+ closer = setFsWatchFileListener(path22, absolutePath, options2, {
183965
183968
  listener,
183966
183969
  rawEmitter: this.fsw._emitRaw
183967
183970
  });
183968
183971
  } else {
183969
- closer = setFsWatchListener(path20, absolutePath, options2, {
183972
+ closer = setFsWatchListener(path22, absolutePath, options2, {
183970
183973
  listener,
183971
183974
  errHandler: this._boundHandleError,
183972
183975
  rawEmitter: this.fsw._emitRaw
@@ -183983,12 +183986,12 @@ var NodeFsHandler = class {
183983
183986
  return;
183984
183987
  }
183985
183988
  const dirname8 = sp.dirname(file);
183986
- const basename4 = sp.basename(file);
183989
+ const basename5 = sp.basename(file);
183987
183990
  const parent = this.fsw._getWatchedDir(dirname8);
183988
183991
  let prevStats = stats;
183989
- if (parent.has(basename4))
183992
+ if (parent.has(basename5))
183990
183993
  return;
183991
- const listener = async (path20, newStats) => {
183994
+ const listener = async (path22, newStats) => {
183992
183995
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
183993
183996
  return;
183994
183997
  if (!newStats || newStats.mtimeMs === 0) {
@@ -184002,18 +184005,18 @@ var NodeFsHandler = class {
184002
184005
  this.fsw._emit(EV.CHANGE, file, newStats2);
184003
184006
  }
184004
184007
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
184005
- this.fsw._closeFile(path20);
184008
+ this.fsw._closeFile(path22);
184006
184009
  prevStats = newStats2;
184007
184010
  const closer2 = this._watchWithNodeFs(file, listener);
184008
184011
  if (closer2)
184009
- this.fsw._addPathCloser(path20, closer2);
184012
+ this.fsw._addPathCloser(path22, closer2);
184010
184013
  } else {
184011
184014
  prevStats = newStats2;
184012
184015
  }
184013
184016
  } catch (error) {
184014
- this.fsw._remove(dirname8, basename4);
184017
+ this.fsw._remove(dirname8, basename5);
184015
184018
  }
184016
- } else if (parent.has(basename4)) {
184019
+ } else if (parent.has(basename5)) {
184017
184020
  const at = newStats.atimeMs;
184018
184021
  const mt = newStats.mtimeMs;
184019
184022
  if (!at || at <= mt || mt !== prevStats.mtimeMs) {
@@ -184038,7 +184041,7 @@ var NodeFsHandler = class {
184038
184041
  * @param item basename of this item
184039
184042
  * @returns true if no more processing is needed for this entry.
184040
184043
  */
184041
- async _handleSymlink(entry, directory, path20, item) {
184044
+ async _handleSymlink(entry, directory, path22, item) {
184042
184045
  if (this.fsw.closed) {
184043
184046
  return;
184044
184047
  }
@@ -184048,7 +184051,7 @@ var NodeFsHandler = class {
184048
184051
  this.fsw._incrReadyCount();
184049
184052
  let linkPath;
184050
184053
  try {
184051
- linkPath = await fsrealpath(path20);
184054
+ linkPath = await fsrealpath(path22);
184052
184055
  } catch (e) {
184053
184056
  this.fsw._emitReady();
184054
184057
  return true;
@@ -184058,12 +184061,12 @@ var NodeFsHandler = class {
184058
184061
  if (dir.has(item)) {
184059
184062
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
184060
184063
  this.fsw._symlinkPaths.set(full, linkPath);
184061
- this.fsw._emit(EV.CHANGE, path20, entry.stats);
184064
+ this.fsw._emit(EV.CHANGE, path22, entry.stats);
184062
184065
  }
184063
184066
  } else {
184064
184067
  dir.add(item);
184065
184068
  this.fsw._symlinkPaths.set(full, linkPath);
184066
- this.fsw._emit(EV.ADD, path20, entry.stats);
184069
+ this.fsw._emit(EV.ADD, path22, entry.stats);
184067
184070
  }
184068
184071
  this.fsw._emitReady();
184069
184072
  return true;
@@ -184093,9 +184096,9 @@ var NodeFsHandler = class {
184093
184096
  return;
184094
184097
  }
184095
184098
  const item = entry.path;
184096
- let path20 = sp.join(directory, item);
184099
+ let path22 = sp.join(directory, item);
184097
184100
  current.add(item);
184098
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path20, item)) {
184101
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path22, item)) {
184099
184102
  return;
184100
184103
  }
184101
184104
  if (this.fsw.closed) {
@@ -184104,11 +184107,11 @@ var NodeFsHandler = class {
184104
184107
  }
184105
184108
  if (item === target || !target && !previous.has(item)) {
184106
184109
  this.fsw._incrReadyCount();
184107
- path20 = sp.join(dir, sp.relative(dir, path20));
184108
- this._addToNodeFs(path20, initialAdd, wh, depth + 1);
184110
+ path22 = sp.join(dir, sp.relative(dir, path22));
184111
+ this._addToNodeFs(path22, initialAdd, wh, depth + 1);
184109
184112
  }
184110
184113
  }).on(EV.ERROR, this._boundHandleError);
184111
- return new Promise((resolve4, reject) => {
184114
+ return new Promise((resolve5, reject) => {
184112
184115
  if (!stream)
184113
184116
  return reject();
184114
184117
  stream.once(STR_END, () => {
@@ -184117,7 +184120,7 @@ var NodeFsHandler = class {
184117
184120
  return;
184118
184121
  }
184119
184122
  const wasThrottled = throttler ? throttler.clear() : false;
184120
- resolve4(void 0);
184123
+ resolve5(void 0);
184121
184124
  previous.getChildren().filter((item) => {
184122
184125
  return item !== directory && !current.has(item);
184123
184126
  }).forEach((item) => {
@@ -184174,13 +184177,13 @@ var NodeFsHandler = class {
184174
184177
  * @param depth Child path actually targeted for watch
184175
184178
  * @param target Child path actually targeted for watch
184176
184179
  */
184177
- async _addToNodeFs(path20, initialAdd, priorWh, depth, target) {
184180
+ async _addToNodeFs(path22, initialAdd, priorWh, depth, target) {
184178
184181
  const ready = this.fsw._emitReady;
184179
- if (this.fsw._isIgnored(path20) || this.fsw.closed) {
184182
+ if (this.fsw._isIgnored(path22) || this.fsw.closed) {
184180
184183
  ready();
184181
184184
  return false;
184182
184185
  }
184183
- const wh = this.fsw._getWatchHelpers(path20);
184186
+ const wh = this.fsw._getWatchHelpers(path22);
184184
184187
  if (priorWh) {
184185
184188
  wh.filterPath = (entry) => priorWh.filterPath(entry);
184186
184189
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -184196,8 +184199,8 @@ var NodeFsHandler = class {
184196
184199
  const follow = this.fsw.options.followSymlinks;
184197
184200
  let closer;
184198
184201
  if (stats.isDirectory()) {
184199
- const absPath = sp.resolve(path20);
184200
- const targetPath = follow ? await fsrealpath(path20) : path20;
184202
+ const absPath = sp.resolve(path22);
184203
+ const targetPath = follow ? await fsrealpath(path22) : path22;
184201
184204
  if (this.fsw.closed)
184202
184205
  return;
184203
184206
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -184207,29 +184210,29 @@ var NodeFsHandler = class {
184207
184210
  this.fsw._symlinkPaths.set(absPath, targetPath);
184208
184211
  }
184209
184212
  } else if (stats.isSymbolicLink()) {
184210
- const targetPath = follow ? await fsrealpath(path20) : path20;
184213
+ const targetPath = follow ? await fsrealpath(path22) : path22;
184211
184214
  if (this.fsw.closed)
184212
184215
  return;
184213
184216
  const parent = sp.dirname(wh.watchPath);
184214
184217
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
184215
184218
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
184216
- closer = await this._handleDir(parent, stats, initialAdd, depth, path20, wh, targetPath);
184219
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path22, wh, targetPath);
184217
184220
  if (this.fsw.closed)
184218
184221
  return;
184219
184222
  if (targetPath !== void 0) {
184220
- this.fsw._symlinkPaths.set(sp.resolve(path20), targetPath);
184223
+ this.fsw._symlinkPaths.set(sp.resolve(path22), targetPath);
184221
184224
  }
184222
184225
  } else {
184223
184226
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
184224
184227
  }
184225
184228
  ready();
184226
184229
  if (closer)
184227
- this.fsw._addPathCloser(path20, closer);
184230
+ this.fsw._addPathCloser(path22, closer);
184228
184231
  return false;
184229
184232
  } catch (error) {
184230
184233
  if (this.fsw._handleError(error)) {
184231
184234
  ready();
184232
- return path20;
184235
+ return path22;
184233
184236
  }
184234
184237
  }
184235
184238
  }
@@ -184272,24 +184275,24 @@ function createPattern(matcher) {
184272
184275
  }
184273
184276
  return () => false;
184274
184277
  }
184275
- function normalizePath(path20) {
184276
- if (typeof path20 !== "string")
184278
+ function normalizePath(path22) {
184279
+ if (typeof path22 !== "string")
184277
184280
  throw new Error("string expected");
184278
- path20 = sp2.normalize(path20);
184279
- path20 = path20.replace(/\\/g, "/");
184281
+ path22 = sp2.normalize(path22);
184282
+ path22 = path22.replace(/\\/g, "/");
184280
184283
  let prepend = false;
184281
- if (path20.startsWith("//"))
184284
+ if (path22.startsWith("//"))
184282
184285
  prepend = true;
184283
- path20 = path20.replace(DOUBLE_SLASH_RE, "/");
184286
+ path22 = path22.replace(DOUBLE_SLASH_RE, "/");
184284
184287
  if (prepend)
184285
- path20 = "/" + path20;
184286
- return path20;
184288
+ path22 = "/" + path22;
184289
+ return path22;
184287
184290
  }
184288
184291
  function matchPatterns(patterns, testString, stats) {
184289
- const path20 = normalizePath(testString);
184292
+ const path22 = normalizePath(testString);
184290
184293
  for (let index = 0; index < patterns.length; index++) {
184291
184294
  const pattern = patterns[index];
184292
- if (pattern(path20, stats)) {
184295
+ if (pattern(path22, stats)) {
184293
184296
  return true;
184294
184297
  }
184295
184298
  }
@@ -184327,19 +184330,19 @@ var toUnix = (string) => {
184327
184330
  }
184328
184331
  return str;
184329
184332
  };
184330
- var normalizePathToUnix = (path20) => toUnix(sp2.normalize(toUnix(path20)));
184331
- var normalizeIgnored = (cwd = "") => (path20) => {
184332
- if (typeof path20 === "string") {
184333
- return normalizePathToUnix(sp2.isAbsolute(path20) ? path20 : sp2.join(cwd, path20));
184333
+ var normalizePathToUnix = (path22) => toUnix(sp2.normalize(toUnix(path22)));
184334
+ var normalizeIgnored = (cwd = "") => (path22) => {
184335
+ if (typeof path22 === "string") {
184336
+ return normalizePathToUnix(sp2.isAbsolute(path22) ? path22 : sp2.join(cwd, path22));
184334
184337
  } else {
184335
- return path20;
184338
+ return path22;
184336
184339
  }
184337
184340
  };
184338
- var getAbsolutePath = (path20, cwd) => {
184339
- if (sp2.isAbsolute(path20)) {
184340
- return path20;
184341
+ var getAbsolutePath = (path22, cwd) => {
184342
+ if (sp2.isAbsolute(path22)) {
184343
+ return path22;
184341
184344
  }
184342
- return sp2.join(cwd, path20);
184345
+ return sp2.join(cwd, path22);
184343
184346
  };
184344
184347
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
184345
184348
  var DirEntry = class {
@@ -184404,10 +184407,10 @@ var WatchHelper = class {
184404
184407
  dirParts;
184405
184408
  followSymlinks;
184406
184409
  statMethod;
184407
- constructor(path20, follow, fsw) {
184410
+ constructor(path22, follow, fsw) {
184408
184411
  this.fsw = fsw;
184409
- const watchPath = path20;
184410
- this.path = path20 = path20.replace(REPLACER_RE, "");
184412
+ const watchPath = path22;
184413
+ this.path = path22 = path22.replace(REPLACER_RE, "");
184411
184414
  this.watchPath = watchPath;
184412
184415
  this.fullWatchPath = sp2.resolve(watchPath);
184413
184416
  this.dirParts = [];
@@ -184547,20 +184550,20 @@ var FSWatcher = class extends EventEmitter {
184547
184550
  this._closePromise = void 0;
184548
184551
  let paths = unifyPaths(paths_);
184549
184552
  if (cwd) {
184550
- paths = paths.map((path20) => {
184551
- const absPath = getAbsolutePath(path20, cwd);
184553
+ paths = paths.map((path22) => {
184554
+ const absPath = getAbsolutePath(path22, cwd);
184552
184555
  return absPath;
184553
184556
  });
184554
184557
  }
184555
- paths.forEach((path20) => {
184556
- this._removeIgnoredPath(path20);
184558
+ paths.forEach((path22) => {
184559
+ this._removeIgnoredPath(path22);
184557
184560
  });
184558
184561
  this._userIgnored = void 0;
184559
184562
  if (!this._readyCount)
184560
184563
  this._readyCount = 0;
184561
184564
  this._readyCount += paths.length;
184562
- Promise.all(paths.map(async (path20) => {
184563
- const res = await this._nodeFsHandler._addToNodeFs(path20, !_internal, void 0, 0, _origAdd);
184565
+ Promise.all(paths.map(async (path22) => {
184566
+ const res = await this._nodeFsHandler._addToNodeFs(path22, !_internal, void 0, 0, _origAdd);
184564
184567
  if (res)
184565
184568
  this._emitReady();
184566
184569
  return res;
@@ -184582,17 +184585,17 @@ var FSWatcher = class extends EventEmitter {
184582
184585
  return this;
184583
184586
  const paths = unifyPaths(paths_);
184584
184587
  const { cwd } = this.options;
184585
- paths.forEach((path20) => {
184586
- if (!sp2.isAbsolute(path20) && !this._closers.has(path20)) {
184588
+ paths.forEach((path22) => {
184589
+ if (!sp2.isAbsolute(path22) && !this._closers.has(path22)) {
184587
184590
  if (cwd)
184588
- path20 = sp2.join(cwd, path20);
184589
- path20 = sp2.resolve(path20);
184591
+ path22 = sp2.join(cwd, path22);
184592
+ path22 = sp2.resolve(path22);
184590
184593
  }
184591
- this._closePath(path20);
184592
- this._addIgnoredPath(path20);
184593
- if (this._watched.has(path20)) {
184594
+ this._closePath(path22);
184595
+ this._addIgnoredPath(path22);
184596
+ if (this._watched.has(path22)) {
184594
184597
  this._addIgnoredPath({
184595
- path: path20,
184598
+ path: path22,
184596
184599
  recursive: true
184597
184600
  });
184598
184601
  }
@@ -184656,38 +184659,38 @@ var FSWatcher = class extends EventEmitter {
184656
184659
  * @param stats arguments to be passed with event
184657
184660
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
184658
184661
  */
184659
- async _emit(event, path20, stats) {
184662
+ async _emit(event, path22, stats) {
184660
184663
  if (this.closed)
184661
184664
  return;
184662
184665
  const opts = this.options;
184663
184666
  if (isWindows)
184664
- path20 = sp2.normalize(path20);
184667
+ path22 = sp2.normalize(path22);
184665
184668
  if (opts.cwd)
184666
- path20 = sp2.relative(opts.cwd, path20);
184667
- const args = [path20];
184669
+ path22 = sp2.relative(opts.cwd, path22);
184670
+ const args = [path22];
184668
184671
  if (stats != null)
184669
184672
  args.push(stats);
184670
184673
  const awf = opts.awaitWriteFinish;
184671
184674
  let pw;
184672
- if (awf && (pw = this._pendingWrites.get(path20))) {
184675
+ if (awf && (pw = this._pendingWrites.get(path22))) {
184673
184676
  pw.lastChange = /* @__PURE__ */ new Date();
184674
184677
  return this;
184675
184678
  }
184676
184679
  if (opts.atomic) {
184677
184680
  if (event === EVENTS.UNLINK) {
184678
- this._pendingUnlinks.set(path20, [event, ...args]);
184681
+ this._pendingUnlinks.set(path22, [event, ...args]);
184679
184682
  setTimeout(() => {
184680
- this._pendingUnlinks.forEach((entry, path21) => {
184683
+ this._pendingUnlinks.forEach((entry, path23) => {
184681
184684
  this.emit(...entry);
184682
184685
  this.emit(EVENTS.ALL, ...entry);
184683
- this._pendingUnlinks.delete(path21);
184686
+ this._pendingUnlinks.delete(path23);
184684
184687
  });
184685
184688
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
184686
184689
  return this;
184687
184690
  }
184688
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path20)) {
184691
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path22)) {
184689
184692
  event = EVENTS.CHANGE;
184690
- this._pendingUnlinks.delete(path20);
184693
+ this._pendingUnlinks.delete(path22);
184691
184694
  }
184692
184695
  }
184693
184696
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -184705,16 +184708,16 @@ var FSWatcher = class extends EventEmitter {
184705
184708
  this.emitWithAll(event, args);
184706
184709
  }
184707
184710
  };
184708
- this._awaitWriteFinish(path20, awf.stabilityThreshold, event, awfEmit);
184711
+ this._awaitWriteFinish(path22, awf.stabilityThreshold, event, awfEmit);
184709
184712
  return this;
184710
184713
  }
184711
184714
  if (event === EVENTS.CHANGE) {
184712
- const isThrottled = !this._throttle(EVENTS.CHANGE, path20, 50);
184715
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path22, 50);
184713
184716
  if (isThrottled)
184714
184717
  return this;
184715
184718
  }
184716
184719
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
184717
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path20) : path20;
184720
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path22) : path22;
184718
184721
  let stats2;
184719
184722
  try {
184720
184723
  stats2 = await stat3(fullPath);
@@ -184745,23 +184748,23 @@ var FSWatcher = class extends EventEmitter {
184745
184748
  * @param timeout duration of time to suppress duplicate actions
184746
184749
  * @returns tracking object or false if action should be suppressed
184747
184750
  */
184748
- _throttle(actionType, path20, timeout) {
184751
+ _throttle(actionType, path22, timeout) {
184749
184752
  if (!this._throttled.has(actionType)) {
184750
184753
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
184751
184754
  }
184752
184755
  const action = this._throttled.get(actionType);
184753
184756
  if (!action)
184754
184757
  throw new Error("invalid throttle");
184755
- const actionPath = action.get(path20);
184758
+ const actionPath = action.get(path22);
184756
184759
  if (actionPath) {
184757
184760
  actionPath.count++;
184758
184761
  return false;
184759
184762
  }
184760
184763
  let timeoutObject;
184761
184764
  const clear = () => {
184762
- const item = action.get(path20);
184765
+ const item = action.get(path22);
184763
184766
  const count = item ? item.count : 0;
184764
- action.delete(path20);
184767
+ action.delete(path22);
184765
184768
  clearTimeout(timeoutObject);
184766
184769
  if (item)
184767
184770
  clearTimeout(item.timeoutObject);
@@ -184769,7 +184772,7 @@ var FSWatcher = class extends EventEmitter {
184769
184772
  };
184770
184773
  timeoutObject = setTimeout(clear, timeout);
184771
184774
  const thr = { timeoutObject, clear, count: 0 };
184772
- action.set(path20, thr);
184775
+ action.set(path22, thr);
184773
184776
  return thr;
184774
184777
  }
184775
184778
  _incrReadyCount() {
@@ -184783,44 +184786,44 @@ var FSWatcher = class extends EventEmitter {
184783
184786
  * @param event
184784
184787
  * @param awfEmit Callback to be called when ready for event to be emitted.
184785
184788
  */
184786
- _awaitWriteFinish(path20, threshold, event, awfEmit) {
184789
+ _awaitWriteFinish(path22, threshold, event, awfEmit) {
184787
184790
  const awf = this.options.awaitWriteFinish;
184788
184791
  if (typeof awf !== "object")
184789
184792
  return;
184790
184793
  const pollInterval = awf.pollInterval;
184791
184794
  let timeoutHandler;
184792
- let fullPath = path20;
184793
- if (this.options.cwd && !sp2.isAbsolute(path20)) {
184794
- fullPath = sp2.join(this.options.cwd, path20);
184795
+ let fullPath = path22;
184796
+ if (this.options.cwd && !sp2.isAbsolute(path22)) {
184797
+ fullPath = sp2.join(this.options.cwd, path22);
184795
184798
  }
184796
184799
  const now = /* @__PURE__ */ new Date();
184797
184800
  const writes = this._pendingWrites;
184798
184801
  function awaitWriteFinishFn(prevStat) {
184799
184802
  statcb(fullPath, (err, curStat) => {
184800
- if (err || !writes.has(path20)) {
184803
+ if (err || !writes.has(path22)) {
184801
184804
  if (err && err.code !== "ENOENT")
184802
184805
  awfEmit(err);
184803
184806
  return;
184804
184807
  }
184805
184808
  const now2 = Number(/* @__PURE__ */ new Date());
184806
184809
  if (prevStat && curStat.size !== prevStat.size) {
184807
- writes.get(path20).lastChange = now2;
184810
+ writes.get(path22).lastChange = now2;
184808
184811
  }
184809
- const pw = writes.get(path20);
184812
+ const pw = writes.get(path22);
184810
184813
  const df = now2 - pw.lastChange;
184811
184814
  if (df >= threshold) {
184812
- writes.delete(path20);
184815
+ writes.delete(path22);
184813
184816
  awfEmit(void 0, curStat);
184814
184817
  } else {
184815
184818
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
184816
184819
  }
184817
184820
  });
184818
184821
  }
184819
- if (!writes.has(path20)) {
184820
- writes.set(path20, {
184822
+ if (!writes.has(path22)) {
184823
+ writes.set(path22, {
184821
184824
  lastChange: now,
184822
184825
  cancelWait: () => {
184823
- writes.delete(path20);
184826
+ writes.delete(path22);
184824
184827
  clearTimeout(timeoutHandler);
184825
184828
  return event;
184826
184829
  }
@@ -184831,8 +184834,8 @@ var FSWatcher = class extends EventEmitter {
184831
184834
  /**
184832
184835
  * Determines whether user has asked to ignore this path.
184833
184836
  */
184834
- _isIgnored(path20, stats) {
184835
- if (this.options.atomic && DOT_RE.test(path20))
184837
+ _isIgnored(path22, stats) {
184838
+ if (this.options.atomic && DOT_RE.test(path22))
184836
184839
  return true;
184837
184840
  if (!this._userIgnored) {
184838
184841
  const { cwd } = this.options;
@@ -184842,17 +184845,17 @@ var FSWatcher = class extends EventEmitter {
184842
184845
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
184843
184846
  this._userIgnored = anymatch(list, void 0);
184844
184847
  }
184845
- return this._userIgnored(path20, stats);
184848
+ return this._userIgnored(path22, stats);
184846
184849
  }
184847
- _isntIgnored(path20, stat4) {
184848
- return !this._isIgnored(path20, stat4);
184850
+ _isntIgnored(path22, stat4) {
184851
+ return !this._isIgnored(path22, stat4);
184849
184852
  }
184850
184853
  /**
184851
184854
  * Provides a set of common helpers and properties relating to symlink handling.
184852
184855
  * @param path file or directory pattern being watched
184853
184856
  */
184854
- _getWatchHelpers(path20) {
184855
- return new WatchHelper(path20, this.options.followSymlinks, this);
184857
+ _getWatchHelpers(path22) {
184858
+ return new WatchHelper(path22, this.options.followSymlinks, this);
184856
184859
  }
184857
184860
  // Directory helpers
184858
184861
  // -----------------
@@ -184884,63 +184887,63 @@ var FSWatcher = class extends EventEmitter {
184884
184887
  * @param item base path of item/directory
184885
184888
  */
184886
184889
  _remove(directory, item, isDirectory) {
184887
- const path20 = sp2.join(directory, item);
184888
- const fullPath = sp2.resolve(path20);
184889
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path20) || this._watched.has(fullPath);
184890
- if (!this._throttle("remove", path20, 100))
184890
+ const path22 = sp2.join(directory, item);
184891
+ const fullPath = sp2.resolve(path22);
184892
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path22) || this._watched.has(fullPath);
184893
+ if (!this._throttle("remove", path22, 100))
184891
184894
  return;
184892
184895
  if (!isDirectory && this._watched.size === 1) {
184893
184896
  this.add(directory, item, true);
184894
184897
  }
184895
- const wp = this._getWatchedDir(path20);
184898
+ const wp = this._getWatchedDir(path22);
184896
184899
  const nestedDirectoryChildren = wp.getChildren();
184897
- nestedDirectoryChildren.forEach((nested) => this._remove(path20, nested));
184900
+ nestedDirectoryChildren.forEach((nested) => this._remove(path22, nested));
184898
184901
  const parent = this._getWatchedDir(directory);
184899
184902
  const wasTracked = parent.has(item);
184900
184903
  parent.remove(item);
184901
184904
  if (this._symlinkPaths.has(fullPath)) {
184902
184905
  this._symlinkPaths.delete(fullPath);
184903
184906
  }
184904
- let relPath = path20;
184907
+ let relPath = path22;
184905
184908
  if (this.options.cwd)
184906
- relPath = sp2.relative(this.options.cwd, path20);
184909
+ relPath = sp2.relative(this.options.cwd, path22);
184907
184910
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
184908
184911
  const event = this._pendingWrites.get(relPath).cancelWait();
184909
184912
  if (event === EVENTS.ADD)
184910
184913
  return;
184911
184914
  }
184912
- this._watched.delete(path20);
184915
+ this._watched.delete(path22);
184913
184916
  this._watched.delete(fullPath);
184914
184917
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
184915
- if (wasTracked && !this._isIgnored(path20))
184916
- this._emit(eventName, path20);
184917
- this._closePath(path20);
184918
+ if (wasTracked && !this._isIgnored(path22))
184919
+ this._emit(eventName, path22);
184920
+ this._closePath(path22);
184918
184921
  }
184919
184922
  /**
184920
184923
  * Closes all watchers for a path
184921
184924
  */
184922
- _closePath(path20) {
184923
- this._closeFile(path20);
184924
- const dir = sp2.dirname(path20);
184925
- this._getWatchedDir(dir).remove(sp2.basename(path20));
184925
+ _closePath(path22) {
184926
+ this._closeFile(path22);
184927
+ const dir = sp2.dirname(path22);
184928
+ this._getWatchedDir(dir).remove(sp2.basename(path22));
184926
184929
  }
184927
184930
  /**
184928
184931
  * Closes only file-specific watchers
184929
184932
  */
184930
- _closeFile(path20) {
184931
- const closers = this._closers.get(path20);
184933
+ _closeFile(path22) {
184934
+ const closers = this._closers.get(path22);
184932
184935
  if (!closers)
184933
184936
  return;
184934
184937
  closers.forEach((closer) => closer());
184935
- this._closers.delete(path20);
184938
+ this._closers.delete(path22);
184936
184939
  }
184937
- _addPathCloser(path20, closer) {
184940
+ _addPathCloser(path22, closer) {
184938
184941
  if (!closer)
184939
184942
  return;
184940
- let list = this._closers.get(path20);
184943
+ let list = this._closers.get(path22);
184941
184944
  if (!list) {
184942
184945
  list = [];
184943
- this._closers.set(path20, list);
184946
+ this._closers.set(path22, list);
184944
184947
  }
184945
184948
  list.push(closer);
184946
184949
  }
@@ -184998,8 +185001,8 @@ var StablePortAllocator = class {
184998
185001
  portsFilePath;
184999
185002
  savedPorts = {};
185000
185003
  usedPorts = /* @__PURE__ */ new Set();
185001
- constructor(projectRoot) {
185002
- this.portsDir = path4.join(projectRoot, ".specific");
185004
+ constructor(projectRoot, key = "default") {
185005
+ this.portsDir = path4.join(projectRoot, ".specific", "keys", key);
185003
185006
  this.portsFilePath = path4.join(this.portsDir, "ports.json");
185004
185007
  this.loadPorts();
185005
185008
  }
@@ -185185,10 +185188,10 @@ async function downloadFile(url, destPath, onProgress) {
185185
185188
  });
185186
185189
  }
185187
185190
  }
185188
- await new Promise((resolve4, reject) => {
185191
+ await new Promise((resolve5, reject) => {
185189
185192
  fileStream.end((err) => {
185190
185193
  if (err) reject(err);
185191
- else resolve4();
185194
+ else resolve5();
185192
185195
  });
185193
185196
  });
185194
185197
  fs5.renameSync(partPath, destPath);
@@ -185552,7 +185555,7 @@ async function startStorage(storage, port, dataDir) {
185552
185555
  };
185553
185556
  }
185554
185557
  async function runCommand(command, args, env2) {
185555
- return new Promise((resolve4, reject) => {
185558
+ return new Promise((resolve5, reject) => {
185556
185559
  const proc = spawn(command, args, {
185557
185560
  stdio: ["ignore", "pipe", "pipe"],
185558
185561
  env: env2
@@ -185563,7 +185566,7 @@ async function runCommand(command, args, env2) {
185563
185566
  });
185564
185567
  proc.on("close", (code) => {
185565
185568
  if (code === 0) {
185566
- resolve4();
185569
+ resolve5();
185567
185570
  } else {
185568
185571
  reject(new Error(`Command failed with code ${code}: ${stderr}`));
185569
185572
  }
@@ -185572,7 +185575,7 @@ async function runCommand(command, args, env2) {
185572
185575
  });
185573
185576
  }
185574
185577
  async function createPostgresDatabase(postgresPath, dataDir, dbName, env2) {
185575
- return new Promise((resolve4, reject) => {
185578
+ return new Promise((resolve5, reject) => {
185576
185579
  const proc = spawn(
185577
185580
  postgresPath,
185578
185581
  ["--single", "-D", dataDir, "postgres"],
@@ -185586,7 +185589,7 @@ async function createPostgresDatabase(postgresPath, dataDir, dbName, env2) {
185586
185589
  stderr += data.toString();
185587
185590
  });
185588
185591
  proc.on("close", (code) => {
185589
- resolve4();
185592
+ resolve5();
185590
185593
  });
185591
185594
  proc.on("error", reject);
185592
185595
  proc.stdin?.write(`CREATE DATABASE "${dbName}";
@@ -185606,33 +185609,33 @@ async function waitForTcpPort(host, port, timeoutMs = 3e4) {
185606
185609
  throw new Error(`Port ${port} did not become available within timeout`);
185607
185610
  }
185608
185611
  function checkTcpPort(host, port) {
185609
- return new Promise((resolve4) => {
185612
+ return new Promise((resolve5) => {
185610
185613
  const socket = new net.Socket();
185611
185614
  socket.setTimeout(1e3);
185612
185615
  socket.on("connect", () => {
185613
185616
  socket.destroy();
185614
- resolve4(true);
185617
+ resolve5(true);
185615
185618
  });
185616
185619
  socket.on("timeout", () => {
185617
185620
  socket.destroy();
185618
- resolve4(false);
185621
+ resolve5(false);
185619
185622
  });
185620
185623
  socket.on("error", () => {
185621
185624
  socket.destroy();
185622
- resolve4(false);
185625
+ resolve5(false);
185623
185626
  });
185624
185627
  socket.connect(port, host);
185625
185628
  });
185626
185629
  }
185627
185630
  async function stopProcess(proc) {
185628
- return new Promise((resolve4) => {
185631
+ return new Promise((resolve5) => {
185629
185632
  if (proc.killed || proc.exitCode !== null) {
185630
- resolve4();
185633
+ resolve5();
185631
185634
  return;
185632
185635
  }
185633
185636
  proc.once("exit", () => {
185634
185637
  clearTimeout(forceKillTimeout);
185635
- resolve4();
185638
+ resolve5();
185636
185639
  });
185637
185640
  proc.kill("SIGTERM");
185638
185641
  const forceKillTimeout = setTimeout(() => {
@@ -185643,7 +185646,7 @@ async function stopProcess(proc) {
185643
185646
  });
185644
185647
  }
185645
185648
  function sleep(ms) {
185646
- return new Promise((resolve4) => setTimeout(resolve4, ms));
185649
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
185647
185650
  }
185648
185651
 
185649
185652
  // src/lib/dev/service-runner.ts
@@ -186041,14 +186044,14 @@ function startService(service, resources, secrets, configs, endpointPorts, servi
186041
186044
  ports: endpointPorts,
186042
186045
  process: child,
186043
186046
  async stop() {
186044
- return new Promise((resolve4) => {
186047
+ return new Promise((resolve5) => {
186045
186048
  if (child.killed || child.exitCode !== null) {
186046
- resolve4();
186049
+ resolve5();
186047
186050
  return;
186048
186051
  }
186049
186052
  child.once("exit", () => {
186050
186053
  clearTimeout(forceKillTimeout);
186051
- resolve4();
186054
+ resolve5();
186052
186055
  });
186053
186056
  child.kill("SIGTERM");
186054
186057
  const forceKillTimeout = setTimeout(() => {
@@ -186069,11 +186072,16 @@ var InstanceStateManager = class {
186069
186072
  statePath;
186070
186073
  lockPath;
186071
186074
  ownsInstances = false;
186072
- constructor(projectRoot) {
186073
- this.stateDir = path9.join(projectRoot, ".specific");
186075
+ key;
186076
+ constructor(projectRoot, key = "default") {
186077
+ this.key = key;
186078
+ this.stateDir = path9.join(projectRoot, ".specific", "keys", key);
186074
186079
  this.statePath = path9.join(this.stateDir, "state.json");
186075
186080
  this.lockPath = path9.join(this.stateDir, "state.lock");
186076
186081
  }
186082
+ getKey() {
186083
+ return this.key;
186084
+ }
186077
186085
  ensureStateDir() {
186078
186086
  if (!fs8.existsSync(this.stateDir)) {
186079
186087
  fs8.mkdirSync(this.stateDir, { recursive: true });
@@ -186124,7 +186132,7 @@ var InstanceStateManager = class {
186124
186132
  }
186125
186133
  continue;
186126
186134
  }
186127
- await new Promise((resolve4) => setTimeout(resolve4, 100));
186135
+ await new Promise((resolve5) => setTimeout(resolve5, 100));
186128
186136
  } else {
186129
186137
  throw e;
186130
186138
  }
@@ -186280,7 +186288,7 @@ var HTTP_PORT = 80;
186280
186288
  var HTTPS_PORT = 443;
186281
186289
  var DOMAIN_SUFFIX = ".local.spcf.app";
186282
186290
  var ADMIN_DOMAIN = "local.spcf.app";
186283
- var DRIZZLE_GATEWAY_DOMAIN = "__drizzle_gateway.local.spcf.app";
186291
+ var DRIZZLE_GATEWAY_PREFIX = "__drizzle_gateway";
186284
186292
  var MIME_TYPES = {
186285
186293
  ".html": "text/html",
186286
186294
  ".css": "text/css",
@@ -186295,11 +186303,31 @@ var MIME_TYPES = {
186295
186303
  ".woff2": "font/woff2",
186296
186304
  ".txt": "text/plain"
186297
186305
  };
186298
- async function startHttpProxy(services, certificate, getState, drizzleGatewayPort) {
186306
+ async function startHttpProxy(services, certificate, getState, instanceKey = "default") {
186299
186307
  const serviceMap = /* @__PURE__ */ new Map();
186308
+ const adminPortMap = /* @__PURE__ */ new Map();
186309
+ const drizzleGatewayPortMap = /* @__PURE__ */ new Map();
186310
+ const buildMapKey = (serviceName, key) => {
186311
+ return key === "default" ? serviceName : `${serviceName}.${key}`;
186312
+ };
186300
186313
  for (const svc of services) {
186301
- serviceMap.set(svc.name, svc.port);
186314
+ serviceMap.set(buildMapKey(svc.name, instanceKey), svc.port);
186302
186315
  }
186316
+ const updateServices = (registryServices, keys) => {
186317
+ serviceMap.clear();
186318
+ adminPortMap.clear();
186319
+ drizzleGatewayPortMap.clear();
186320
+ for (const svc of registryServices) {
186321
+ serviceMap.set(buildMapKey(svc.serviceName, svc.key), svc.port);
186322
+ }
186323
+ for (const [key, registration] of Object.entries(keys)) {
186324
+ adminPortMap.set(key, registration.adminPort);
186325
+ if (registration.drizzleGatewayPort !== void 0) {
186326
+ drizzleGatewayPortMap.set(key, registration.drizzleGatewayPort);
186327
+ }
186328
+ }
186329
+ writeLog("proxy", `Updated service map: ${serviceMap.size} services, ${adminPortMap.size} admin instances, ${drizzleGatewayPortMap.size} drizzle gateways`);
186330
+ };
186303
186331
  const { key: defaultKey, cert: defaultCert } = certificate;
186304
186332
  const proxy = httpProxy.createProxyServer({});
186305
186333
  proxy.on("error", (err, req, res) => {
@@ -186313,38 +186341,64 @@ async function startHttpProxy(services, certificate, getState, drizzleGatewayPor
186313
186341
  const handleRequest = (req, res) => {
186314
186342
  const host = req.headers.host || "";
186315
186343
  const hostname = host.split(":")[0];
186316
- if (hostname === ADMIN_DOMAIN) {
186317
- return handleAdminRequest(req, res, proxy, getState);
186318
- }
186319
- if (hostname === DRIZZLE_GATEWAY_DOMAIN && drizzleGatewayPort) {
186320
- proxy.web(req, res, {
186321
- target: `http://127.0.0.1:${drizzleGatewayPort}`
186322
- });
186344
+ const drizzleKey = extractDrizzleGatewayKey(host);
186345
+ if (drizzleKey !== null) {
186346
+ const drizzlePort = drizzleGatewayPortMap.get(drizzleKey);
186347
+ if (drizzlePort) {
186348
+ proxy.web(req, res, {
186349
+ target: `http://127.0.0.1:${drizzlePort}`
186350
+ });
186351
+ return;
186352
+ }
186353
+ res.writeHead(503, { "Content-Type": "text/html" });
186354
+ res.end("<h1>Database Viewer</h1><p>No database viewer running for this instance.</p>");
186323
186355
  return;
186324
186356
  }
186325
- const serviceName = extractServiceName(host);
186326
- if (!serviceName) {
186327
- return sendNotFound(res, null, services);
186357
+ const adminKey = extractAdminKey(host);
186358
+ if (adminKey !== null) {
186359
+ const adminPort = adminPortMap.get(adminKey);
186360
+ if (adminPort) {
186361
+ const url = new URL(req.url || "/", "http://localhost");
186362
+ if (url.pathname.startsWith("/api/")) {
186363
+ proxy.web(req, res, { target: `http://127.0.0.1:${adminPort}` });
186364
+ return;
186365
+ }
186366
+ serveStaticFile(res, url.pathname);
186367
+ return;
186368
+ }
186369
+ if (adminKey === "default" && hostname === ADMIN_DOMAIN) {
186370
+ res.writeHead(503, { "Content-Type": "text/html" });
186371
+ res.end("<h1>Admin UI</h1><p>No dev instance running. Start <code>specific dev</code> first.</p>");
186372
+ return;
186373
+ }
186374
+ }
186375
+ const route = extractServiceAndKey(host);
186376
+ if (!route) {
186377
+ return sendNotFound(res, null, serviceMap);
186328
186378
  }
186329
- const targetPort = serviceMap.get(serviceName);
186379
+ const mapKey = buildMapKey(route.serviceName, route.key);
186380
+ const targetPort = serviceMap.get(mapKey);
186330
186381
  if (!targetPort) {
186331
- return sendNotFound(res, serviceName, services);
186382
+ return sendNotFound(res, route.serviceName, serviceMap);
186332
186383
  }
186333
186384
  proxy.web(req, res, { target: `http://127.0.0.1:${targetPort}` });
186334
186385
  };
186335
186386
  const handleUpgrade = (req, socket, head) => {
186336
186387
  const host = req.headers.host || "";
186337
- const hostname = host.split(":")[0];
186338
- if (hostname === ADMIN_DOMAIN) {
186339
- socket.end("HTTP/1.1 404 Not Found\r\n\r\n");
186340
- return;
186388
+ const adminKey = extractAdminKey(host);
186389
+ if (adminKey !== null) {
186390
+ if (adminPortMap.has(adminKey)) {
186391
+ socket.end("HTTP/1.1 404 Not Found\r\n\r\n");
186392
+ return;
186393
+ }
186341
186394
  }
186342
- const serviceName = extractServiceName(host);
186343
- if (!serviceName) {
186395
+ const route = extractServiceAndKey(host);
186396
+ if (!route) {
186344
186397
  socket.end("HTTP/1.1 404 Not Found\r\n\r\n");
186345
186398
  return;
186346
186399
  }
186347
- const targetPort = serviceMap.get(serviceName);
186400
+ const mapKey = buildMapKey(route.serviceName, route.key);
186401
+ const targetPort = serviceMap.get(mapKey);
186348
186402
  if (!targetPort) {
186349
186403
  socket.end("HTTP/1.1 404 Not Found\r\n\r\n");
186350
186404
  return;
@@ -186361,7 +186415,7 @@ async function startHttpProxy(services, certificate, getState, drizzleGatewayPor
186361
186415
  handleRequest
186362
186416
  );
186363
186417
  httpsServer.on("upgrade", handleUpgrade);
186364
- return new Promise((resolve4, reject) => {
186418
+ return new Promise((resolve5, reject) => {
186365
186419
  let httpStarted = false;
186366
186420
  let httpsStarted = false;
186367
186421
  let failed = false;
@@ -186372,17 +186426,25 @@ async function startHttpProxy(services, certificate, getState, drizzleGatewayPor
186372
186426
  "proxy",
186373
186427
  `HTTP/HTTPS proxy started on ports ${HTTP_PORT}/${HTTPS_PORT}`
186374
186428
  );
186375
- resolve4({
186429
+ resolve5({
186376
186430
  httpPort: HTTP_PORT,
186377
186431
  httpsPort: HTTPS_PORT,
186432
+ updateServices,
186433
+ updateCertificate(newCert) {
186434
+ httpsServer.setSecureContext({
186435
+ key: newCert.key,
186436
+ cert: newCert.cert
186437
+ });
186438
+ writeLog("proxy", "Certificate updated");
186439
+ },
186378
186440
  async stop() {
186379
- return new Promise((resolve5) => {
186441
+ return new Promise((resolve6) => {
186380
186442
  let closed = 0;
186381
186443
  const onClose = () => {
186382
186444
  closed++;
186383
186445
  if (closed === 2) {
186384
186446
  clearTimeout(forceCloseTimeout);
186385
- resolve5();
186447
+ resolve6();
186386
186448
  }
186387
186449
  };
186388
186450
  httpServer.close(onClose);
@@ -186390,7 +186452,7 @@ async function startHttpProxy(services, certificate, getState, drizzleGatewayPor
186390
186452
  const forceCloseTimeout = setTimeout(() => {
186391
186453
  httpServer.closeAllConnections?.();
186392
186454
  httpsServer.closeAllConnections?.();
186393
- resolve5();
186455
+ resolve6();
186394
186456
  }, 2e3);
186395
186457
  });
186396
186458
  }
@@ -186424,15 +186486,70 @@ async function startHttpProxy(services, certificate, getState, drizzleGatewayPor
186424
186486
  });
186425
186487
  });
186426
186488
  }
186427
- function extractServiceName(host) {
186489
+ function extractServiceAndKey(host) {
186490
+ const hostname = host.split(":")[0];
186491
+ if (!hostname || !hostname.endsWith(DOMAIN_SUFFIX)) {
186492
+ return null;
186493
+ }
186494
+ const prefix = hostname.slice(0, -DOMAIN_SUFFIX.length);
186495
+ if (!prefix) {
186496
+ return null;
186497
+ }
186498
+ const parts = prefix.split(".");
186499
+ if (parts.length === 1) {
186500
+ return { serviceName: parts[0], key: "default" };
186501
+ } else if (parts.length === 2) {
186502
+ return { serviceName: parts[0], key: parts[1] };
186503
+ }
186504
+ return null;
186505
+ }
186506
+ function extractDrizzleGatewayKey(host) {
186428
186507
  const hostname = host.split(":")[0];
186429
186508
  if (!hostname || !hostname.endsWith(DOMAIN_SUFFIX)) {
186430
186509
  return null;
186431
186510
  }
186432
- return hostname.slice(0, -DOMAIN_SUFFIX.length);
186511
+ const prefix = hostname.slice(0, -DOMAIN_SUFFIX.length);
186512
+ if (!prefix) {
186513
+ return null;
186514
+ }
186515
+ const parts = prefix.split(".");
186516
+ if (parts.length === 1 && parts[0] === DRIZZLE_GATEWAY_PREFIX) {
186517
+ return "default";
186518
+ } else if (parts.length === 2 && parts[0] === DRIZZLE_GATEWAY_PREFIX) {
186519
+ return parts[1];
186520
+ }
186521
+ return null;
186433
186522
  }
186434
- function sendNotFound(res, requestedService, availableServices) {
186435
- const serviceList = availableServices.map((s) => `<li>${s.name}.local.spcf.app</li>`).join("\n");
186523
+ function extractAdminKey(host) {
186524
+ const hostname = host.split(":")[0];
186525
+ if (!hostname) {
186526
+ return null;
186527
+ }
186528
+ if (hostname === ADMIN_DOMAIN) {
186529
+ return "default";
186530
+ }
186531
+ if (!hostname.endsWith(DOMAIN_SUFFIX)) {
186532
+ return null;
186533
+ }
186534
+ const prefix = hostname.slice(0, -DOMAIN_SUFFIX.length);
186535
+ if (!prefix) {
186536
+ return null;
186537
+ }
186538
+ const parts = prefix.split(".");
186539
+ if (parts.length === 1) {
186540
+ return parts[0];
186541
+ }
186542
+ return null;
186543
+ }
186544
+ function sendNotFound(res, requestedService, serviceMap) {
186545
+ const serviceList = Array.from(serviceMap.keys()).map((key) => {
186546
+ const parts = key.split(".");
186547
+ if (parts.length === 1) {
186548
+ return `<li>${parts[0]}.local.spcf.app</li>`;
186549
+ } else {
186550
+ return `<li>${key}.local.spcf.app</li>`;
186551
+ }
186552
+ }).join("\n");
186436
186553
  const message = requestedService ? `No service named "${requestedService}" is running.` : "Invalid host header.";
186437
186554
  res.writeHead(404, { "Content-Type": "text/html" });
186438
186555
  res.end(`<!DOCTYPE html>
@@ -186441,36 +186558,11 @@ function sendNotFound(res, requestedService, availableServices) {
186441
186558
  <body>
186442
186559
  <h1>Service Not Found</h1>
186443
186560
  <p>${message}</p>
186444
- ${availableServices.length > 0 ? `<p>Available services:</p>
186561
+ ${serviceMap.size > 0 ? `<p>Available services:</p>
186445
186562
  <ul>${serviceList}</ul>` : "<p>No services are currently exposed.</p>"}
186446
186563
  </body>
186447
186564
  </html>`);
186448
186565
  }
186449
- function handleAdminRequest(req, res, proxy, getState) {
186450
- const url = new URL(req.url || "/", "http://localhost");
186451
- if (url.pathname.startsWith("/api/")) {
186452
- return handleAdminApi(req, res, url, getState);
186453
- }
186454
- serveStaticFile(res, url.pathname);
186455
- }
186456
- function handleAdminApi(_req, res, url, getState) {
186457
- res.setHeader("Content-Type", "application/json");
186458
- res.setHeader("Access-Control-Allow-Origin", "*");
186459
- const pathname = url.pathname.endsWith("/") && url.pathname.length > 1 ? url.pathname.slice(0, -1) : url.pathname;
186460
- if (pathname === "/api/state") {
186461
- if (!getState) {
186462
- res.writeHead(500);
186463
- res.end(JSON.stringify({ error: "State not available" }));
186464
- return;
186465
- }
186466
- const state = getState();
186467
- res.writeHead(200);
186468
- res.end(JSON.stringify(state));
186469
- return;
186470
- }
186471
- res.writeHead(404);
186472
- res.end(JSON.stringify({ error: "Not found" }));
186473
- }
186474
186566
  function serveStaticFile(res, pathname) {
186475
186567
  let filePath = pathname;
186476
186568
  if (filePath === "/" || filePath.endsWith("/")) {
@@ -186522,6 +186614,48 @@ function serveFileContent(res, filePath, contentType, statusCode = 200) {
186522
186614
  res.end("<h1>Internal Server Error</h1>");
186523
186615
  }
186524
186616
  }
186617
+ async function startAdminServer(getState) {
186618
+ return new Promise((resolve5, reject) => {
186619
+ const server = http.createServer((req, res) => {
186620
+ const url = new URL(req.url || "/", "http://localhost");
186621
+ res.setHeader("Access-Control-Allow-Origin", "*");
186622
+ res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
186623
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
186624
+ if (req.method === "OPTIONS") {
186625
+ res.writeHead(204);
186626
+ res.end();
186627
+ return;
186628
+ }
186629
+ const pathname = url.pathname.endsWith("/") && url.pathname.length > 1 ? url.pathname.slice(0, -1) : url.pathname;
186630
+ if (pathname === "/api/state") {
186631
+ res.setHeader("Content-Type", "application/json");
186632
+ const state = getState();
186633
+ res.writeHead(200);
186634
+ res.end(JSON.stringify(state));
186635
+ return;
186636
+ }
186637
+ res.setHeader("Content-Type", "application/json");
186638
+ res.writeHead(404);
186639
+ res.end(JSON.stringify({ error: "Not found" }));
186640
+ });
186641
+ server.on("error", reject);
186642
+ server.listen(0, "127.0.0.1", () => {
186643
+ const addr = server.address();
186644
+ if (!addr || typeof addr === "string") {
186645
+ reject(new Error("Failed to get server address"));
186646
+ return;
186647
+ }
186648
+ const port = addr.port;
186649
+ writeLog("admin", `Admin API server started on port ${port}`);
186650
+ resolve5({
186651
+ port,
186652
+ stop: () => new Promise((res, rej) => {
186653
+ server.close((err) => err ? rej(err) : res());
186654
+ })
186655
+ });
186656
+ });
186657
+ });
186658
+ }
186525
186659
 
186526
186660
  // src/lib/dev/electric-manager.ts
186527
186661
  import * as net2 from "net";
@@ -186579,33 +186713,33 @@ async function waitForTcpPort2(host, port, timeoutMs = 3e4) {
186579
186713
  throw new Error(`Electric port ${port} did not become available within timeout`);
186580
186714
  }
186581
186715
  function checkTcpPort2(host, port) {
186582
- return new Promise((resolve4) => {
186716
+ return new Promise((resolve5) => {
186583
186717
  const socket = new net2.Socket();
186584
186718
  socket.setTimeout(1e3);
186585
186719
  socket.on("connect", () => {
186586
186720
  socket.destroy();
186587
- resolve4(true);
186721
+ resolve5(true);
186588
186722
  });
186589
186723
  socket.on("timeout", () => {
186590
186724
  socket.destroy();
186591
- resolve4(false);
186725
+ resolve5(false);
186592
186726
  });
186593
186727
  socket.on("error", () => {
186594
186728
  socket.destroy();
186595
- resolve4(false);
186729
+ resolve5(false);
186596
186730
  });
186597
186731
  socket.connect(port, host);
186598
186732
  });
186599
186733
  }
186600
186734
  async function stopProcess2(proc) {
186601
- return new Promise((resolve4) => {
186735
+ return new Promise((resolve5) => {
186602
186736
  if (proc.killed || proc.exitCode !== null) {
186603
- resolve4();
186737
+ resolve5();
186604
186738
  return;
186605
186739
  }
186606
186740
  proc.once("exit", () => {
186607
186741
  clearTimeout(forceKillTimeout);
186608
- resolve4();
186742
+ resolve5();
186609
186743
  });
186610
186744
  proc.kill("SIGTERM");
186611
186745
  const forceKillTimeout = setTimeout(() => {
@@ -186616,7 +186750,7 @@ async function stopProcess2(proc) {
186616
186750
  });
186617
186751
  }
186618
186752
  function sleep2(ms) {
186619
- return new Promise((resolve4) => setTimeout(resolve4, ms));
186753
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
186620
186754
  }
186621
186755
 
186622
186756
  // src/lib/dev/drizzle-gateway-manager.ts
@@ -186700,33 +186834,33 @@ async function waitForTcpPort3(host, port, timeoutMs = 3e4) {
186700
186834
  );
186701
186835
  }
186702
186836
  function checkTcpPort3(host, port) {
186703
- return new Promise((resolve4) => {
186837
+ return new Promise((resolve5) => {
186704
186838
  const socket = new net3.Socket();
186705
186839
  socket.setTimeout(1e3);
186706
186840
  socket.on("connect", () => {
186707
186841
  socket.destroy();
186708
- resolve4(true);
186842
+ resolve5(true);
186709
186843
  });
186710
186844
  socket.on("timeout", () => {
186711
186845
  socket.destroy();
186712
- resolve4(false);
186846
+ resolve5(false);
186713
186847
  });
186714
186848
  socket.on("error", () => {
186715
186849
  socket.destroy();
186716
- resolve4(false);
186850
+ resolve5(false);
186717
186851
  });
186718
186852
  socket.connect(port, host);
186719
186853
  });
186720
186854
  }
186721
186855
  async function stopProcess3(proc) {
186722
- return new Promise((resolve4) => {
186856
+ return new Promise((resolve5) => {
186723
186857
  if (proc.killed || proc.exitCode !== null) {
186724
- resolve4();
186858
+ resolve5();
186725
186859
  return;
186726
186860
  }
186727
186861
  proc.once("exit", () => {
186728
186862
  clearTimeout(forceKillTimeout);
186729
- resolve4();
186863
+ resolve5();
186730
186864
  });
186731
186865
  proc.kill("SIGTERM");
186732
186866
  const forceKillTimeout = setTimeout(() => {
@@ -186737,7 +186871,7 @@ async function stopProcess3(proc) {
186737
186871
  });
186738
186872
  }
186739
186873
  function sleep3(ms) {
186740
- return new Promise((resolve4) => setTimeout(resolve4, ms));
186874
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
186741
186875
  }
186742
186876
 
186743
186877
  // src/lib/dev/sync-detector.ts
@@ -186928,19 +187062,345 @@ function watchConfigFile(configPath, debounceMs, onChange) {
186928
187062
  };
186929
187063
  }
186930
187064
 
187065
+ // src/lib/dev/proxy-registry.ts
187066
+ import * as fs11 from "fs";
187067
+ import * as path12 from "path";
187068
+ import * as os4 from "os";
187069
+ var ProxyRegistryManager = class {
187070
+ proxyDir;
187071
+ ownerPath;
187072
+ registryPath;
187073
+ lockPath;
187074
+ isOwner = false;
187075
+ registryWatcher = null;
187076
+ constructor() {
187077
+ this.proxyDir = path12.join(os4.homedir(), ".specific", "proxy");
187078
+ this.ownerPath = path12.join(this.proxyDir, "owner.json");
187079
+ this.registryPath = path12.join(this.proxyDir, "registry.json");
187080
+ this.lockPath = path12.join(this.proxyDir, "registry.lock");
187081
+ }
187082
+ ensureProxyDir() {
187083
+ if (!fs11.existsSync(this.proxyDir)) {
187084
+ fs11.mkdirSync(this.proxyDir, { recursive: true });
187085
+ }
187086
+ }
187087
+ isProcessRunning(pid) {
187088
+ try {
187089
+ process.kill(pid, 0);
187090
+ return true;
187091
+ } catch (e) {
187092
+ const err = e;
187093
+ return err.code !== "ESRCH";
187094
+ }
187095
+ }
187096
+ async acquireLock(timeoutMs = 5e3) {
187097
+ this.ensureProxyDir();
187098
+ const startTime = Date.now();
187099
+ while (Date.now() - startTime < timeoutMs) {
187100
+ try {
187101
+ const fd = fs11.openSync(
187102
+ this.lockPath,
187103
+ fs11.constants.O_CREAT | fs11.constants.O_EXCL | fs11.constants.O_WRONLY
187104
+ );
187105
+ fs11.writeSync(fd, String(process.pid));
187106
+ fs11.closeSync(fd);
187107
+ return () => {
187108
+ try {
187109
+ fs11.unlinkSync(this.lockPath);
187110
+ } catch {
187111
+ }
187112
+ };
187113
+ } catch (e) {
187114
+ const err = e;
187115
+ if (err.code === "EEXIST") {
187116
+ try {
187117
+ const lockPid = parseInt(
187118
+ fs11.readFileSync(this.lockPath, "utf-8").trim(),
187119
+ 10
187120
+ );
187121
+ if (!this.isProcessRunning(lockPid)) {
187122
+ fs11.unlinkSync(this.lockPath);
187123
+ continue;
187124
+ }
187125
+ } catch {
187126
+ try {
187127
+ fs11.unlinkSync(this.lockPath);
187128
+ } catch {
187129
+ }
187130
+ continue;
187131
+ }
187132
+ await new Promise((resolve5) => setTimeout(resolve5, 100));
187133
+ } else {
187134
+ throw e;
187135
+ }
187136
+ }
187137
+ }
187138
+ throw new Error("Failed to acquire proxy registry lock (timeout)");
187139
+ }
187140
+ /**
187141
+ * Try to claim ownership of the proxy.
187142
+ * Returns true if this process is now the owner.
187143
+ */
187144
+ async claimProxyOwnership(key) {
187145
+ const releaseLock = await this.acquireLock();
187146
+ try {
187147
+ if (fs11.existsSync(this.ownerPath)) {
187148
+ const content = fs11.readFileSync(this.ownerPath, "utf-8");
187149
+ const ownerFile2 = JSON.parse(content);
187150
+ if (this.isProcessRunning(ownerFile2.owner.pid)) {
187151
+ return false;
187152
+ }
187153
+ }
187154
+ const ownerFile = {
187155
+ version: 1,
187156
+ owner: {
187157
+ pid: process.pid,
187158
+ key,
187159
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
187160
+ }
187161
+ };
187162
+ this.writeFileAtomic(this.ownerPath, ownerFile);
187163
+ this.isOwner = true;
187164
+ return true;
187165
+ } finally {
187166
+ releaseLock();
187167
+ }
187168
+ }
187169
+ /**
187170
+ * Release ownership of the proxy.
187171
+ */
187172
+ async releaseProxyOwnership() {
187173
+ if (!this.isOwner) {
187174
+ return;
187175
+ }
187176
+ const releaseLock = await this.acquireLock();
187177
+ try {
187178
+ if (fs11.existsSync(this.ownerPath)) {
187179
+ const content = fs11.readFileSync(this.ownerPath, "utf-8");
187180
+ const ownerFile = JSON.parse(content);
187181
+ if (ownerFile.owner.pid === process.pid) {
187182
+ fs11.unlinkSync(this.ownerPath);
187183
+ }
187184
+ }
187185
+ this.isOwner = false;
187186
+ } finally {
187187
+ releaseLock();
187188
+ }
187189
+ }
187190
+ /**
187191
+ * Get the current proxy owner.
187192
+ */
187193
+ async getProxyOwner() {
187194
+ if (!fs11.existsSync(this.ownerPath)) {
187195
+ return null;
187196
+ }
187197
+ const releaseLock = await this.acquireLock();
187198
+ try {
187199
+ const content = fs11.readFileSync(this.ownerPath, "utf-8");
187200
+ const ownerFile = JSON.parse(content);
187201
+ if (!this.isProcessRunning(ownerFile.owner.pid)) {
187202
+ return null;
187203
+ }
187204
+ return ownerFile.owner;
187205
+ } catch {
187206
+ return null;
187207
+ } finally {
187208
+ releaseLock();
187209
+ }
187210
+ }
187211
+ /**
187212
+ * Check if this process is the proxy owner.
187213
+ */
187214
+ isProxyOwner() {
187215
+ return this.isOwner;
187216
+ }
187217
+ /**
187218
+ * Register services for a key.
187219
+ */
187220
+ async registerServices(key, adminPort, services, drizzleGatewayPort) {
187221
+ const releaseLock = await this.acquireLock();
187222
+ try {
187223
+ const registry = this.readRegistry();
187224
+ const registration = {
187225
+ adminPort,
187226
+ services
187227
+ };
187228
+ if (drizzleGatewayPort !== void 0) {
187229
+ registration.drizzleGatewayPort = drizzleGatewayPort;
187230
+ }
187231
+ registry.keys[key] = registration;
187232
+ registry.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
187233
+ this.writeFileAtomic(this.registryPath, registry);
187234
+ } finally {
187235
+ releaseLock();
187236
+ }
187237
+ }
187238
+ /**
187239
+ * Unregister all services for a key.
187240
+ */
187241
+ async unregisterServices(key) {
187242
+ const releaseLock = await this.acquireLock();
187243
+ try {
187244
+ const registry = this.readRegistry();
187245
+ delete registry.keys[key];
187246
+ registry.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
187247
+ this.writeFileAtomic(this.registryPath, registry);
187248
+ } finally {
187249
+ releaseLock();
187250
+ }
187251
+ }
187252
+ /**
187253
+ * Get all registered services (flattened view for proxy routing).
187254
+ */
187255
+ async getAllServices() {
187256
+ const releaseLock = await this.acquireLock();
187257
+ try {
187258
+ const registry = this.readRegistry();
187259
+ const services = [];
187260
+ for (const [key, registration] of Object.entries(registry.keys)) {
187261
+ for (const svc of registration.services) {
187262
+ services.push({
187263
+ key,
187264
+ serviceName: svc.serviceName,
187265
+ port: svc.port
187266
+ });
187267
+ }
187268
+ }
187269
+ return services;
187270
+ } finally {
187271
+ releaseLock();
187272
+ }
187273
+ }
187274
+ /**
187275
+ * Get all registered keys and their admin ports.
187276
+ */
187277
+ async getAllKeyRegistrations() {
187278
+ const releaseLock = await this.acquireLock();
187279
+ try {
187280
+ const registry = this.readRegistry();
187281
+ return { ...registry.keys };
187282
+ } finally {
187283
+ releaseLock();
187284
+ }
187285
+ }
187286
+ /**
187287
+ * Watch the registry for changes. Returns a cleanup function.
187288
+ * Callback receives both the flattened services list and all key registrations (for admin ports).
187289
+ * Uses chokidar instead of fs.watch for reliable detection of atomic file writes on macOS.
187290
+ */
187291
+ watchRegistry(onChange) {
187292
+ this.ensureProxyDir();
187293
+ if (!fs11.existsSync(this.registryPath)) {
187294
+ const emptyRegistry = {
187295
+ version: 1,
187296
+ keys: {},
187297
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
187298
+ };
187299
+ this.writeFileAtomic(this.registryPath, emptyRegistry);
187300
+ }
187301
+ let debounceTimer = null;
187302
+ this.registryWatcher = chokidar_default.watch(this.registryPath, {
187303
+ persistent: true,
187304
+ awaitWriteFinish: {
187305
+ stabilityThreshold: 100,
187306
+ pollInterval: 50
187307
+ }
187308
+ });
187309
+ this.registryWatcher.on("change", async () => {
187310
+ if (debounceTimer) {
187311
+ clearTimeout(debounceTimer);
187312
+ }
187313
+ debounceTimer = setTimeout(async () => {
187314
+ try {
187315
+ const services = await this.getAllServices();
187316
+ const keys = await this.getAllKeyRegistrations();
187317
+ onChange(services, keys);
187318
+ } catch {
187319
+ }
187320
+ }, 50);
187321
+ });
187322
+ return () => {
187323
+ if (debounceTimer) {
187324
+ clearTimeout(debounceTimer);
187325
+ }
187326
+ this.registryWatcher?.close();
187327
+ this.registryWatcher = null;
187328
+ };
187329
+ }
187330
+ /**
187331
+ * Attempt to become the proxy owner (election).
187332
+ * This is called when the current owner is detected as dead.
187333
+ */
187334
+ async attemptElection(key) {
187335
+ const releaseLock = await this.acquireLock();
187336
+ try {
187337
+ if (fs11.existsSync(this.ownerPath)) {
187338
+ const content = fs11.readFileSync(this.ownerPath, "utf-8");
187339
+ const ownerFile2 = JSON.parse(content);
187340
+ if (this.isProcessRunning(ownerFile2.owner.pid)) {
187341
+ return false;
187342
+ }
187343
+ fs11.unlinkSync(this.ownerPath);
187344
+ }
187345
+ const ownerFile = {
187346
+ version: 1,
187347
+ owner: {
187348
+ pid: process.pid,
187349
+ key,
187350
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
187351
+ }
187352
+ };
187353
+ this.writeFileAtomic(this.ownerPath, ownerFile);
187354
+ this.isOwner = true;
187355
+ return true;
187356
+ } finally {
187357
+ releaseLock();
187358
+ }
187359
+ }
187360
+ readRegistry() {
187361
+ if (!fs11.existsSync(this.registryPath)) {
187362
+ return {
187363
+ version: 1,
187364
+ keys: {},
187365
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
187366
+ };
187367
+ }
187368
+ try {
187369
+ const content = fs11.readFileSync(this.registryPath, "utf-8");
187370
+ return JSON.parse(content);
187371
+ } catch {
187372
+ return {
187373
+ version: 1,
187374
+ keys: {},
187375
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
187376
+ };
187377
+ }
187378
+ }
187379
+ writeFileAtomic(filePath, data) {
187380
+ this.ensureProxyDir();
187381
+ const tmpPath = filePath + ".tmp";
187382
+ fs11.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
187383
+ fs11.renameSync(tmpPath, filePath);
187384
+ }
187385
+ };
187386
+
186931
187387
  // src/commands/dev.tsx
186932
187388
  var COLORS = ["cyan", "yellow", "green", "magenta", "blue"];
186933
- function DevUI() {
187389
+ function DevUI({ instanceKey }) {
186934
187390
  const { exit } = useApp2();
186935
- const [state, setState] = useState3(() => ({
186936
- status: caFilesExist() ? "loading" : "installing-ca",
186937
- caInstallPhase: caFilesExist() ? void 0 : "installing",
186938
- resources: /* @__PURE__ */ new Map(),
186939
- resourceStatus: /* @__PURE__ */ new Map(),
186940
- services: [],
186941
- output: [],
186942
- colorMap: /* @__PURE__ */ new Map()
186943
- }));
187391
+ const [state, setState] = useState3(() => {
187392
+ const caExists = caFilesExist();
187393
+ return {
187394
+ status: caExists ? "loading" : "installing-ca",
187395
+ ...caExists ? {} : { caInstallPhase: "installing" },
187396
+ resources: /* @__PURE__ */ new Map(),
187397
+ resourceStatus: /* @__PURE__ */ new Map(),
187398
+ services: [],
187399
+ output: [],
187400
+ colorMap: /* @__PURE__ */ new Map(),
187401
+ isProxyOwner: false
187402
+ };
187403
+ });
186944
187404
  useEffect3(() => {
186945
187405
  if (state.status === "installing-ca" && state.caInstallPhase === "installing") {
186946
187406
  installCA();
@@ -186964,9 +187424,13 @@ function DevUI() {
186964
187424
  }
186965
187425
  const shuttingDown = useRef(false);
186966
187426
  const stateManagerRef = useRef(null);
187427
+ const proxyRegistryRef = useRef(null);
186967
187428
  const electricInstancesRef = useRef([]);
186968
187429
  const drizzleGatewayRef = useRef(null);
187430
+ const registryWatcherCleanupRef = useRef(null);
187431
+ const electionIntervalRef = useRef(null);
186969
187432
  const proxyRef = useRef(null);
187433
+ const adminServerRef = useRef(null);
186970
187434
  const servicesRef = useRef([]);
186971
187435
  const resourcesRef = useRef(/* @__PURE__ */ new Map());
186972
187436
  const [reloadTrigger, setReloadTrigger] = useState3(0);
@@ -186976,9 +187440,19 @@ function DevUI() {
186976
187440
  shuttingDown.current = true;
186977
187441
  writeLog("system", "Shutting down");
186978
187442
  setState((s) => ({ ...s, status: "stopping" }));
187443
+ if (electionIntervalRef.current) {
187444
+ clearInterval(electionIntervalRef.current);
187445
+ electionIntervalRef.current = null;
187446
+ }
187447
+ if (registryWatcherCleanupRef.current) {
187448
+ registryWatcherCleanupRef.current();
187449
+ registryWatcherCleanupRef.current = null;
187450
+ }
186979
187451
  await Promise.all([
186980
187452
  // Stop proxy
186981
187453
  proxyRef.current?.stop(),
187454
+ // Stop admin server
187455
+ adminServerRef.current?.stop(),
186982
187456
  // Stop all services
186983
187457
  ...servicesRef.current.map((service) => service.stop()),
186984
187458
  // Stop all Electric instances
@@ -186988,6 +187462,10 @@ function DevUI() {
186988
187462
  // Stop all resources
186989
187463
  ...[...resourcesRef.current.values()].map((resource) => resource.stop())
186990
187464
  ]);
187465
+ if (proxyRegistryRef.current) {
187466
+ await proxyRegistryRef.current.unregisterServices(instanceKey);
187467
+ await proxyRegistryRef.current.releaseProxyOwnership();
187468
+ }
186991
187469
  if (stateManagerRef.current) {
186992
187470
  await stateManagerRef.current.releaseOwnership();
186993
187471
  }
@@ -186999,9 +187477,19 @@ function DevUI() {
186999
187477
  if (shuttingDown.current || state.status === "reloading") return;
187000
187478
  writeLog("system", "Reloading (config changed)");
187001
187479
  setState((s) => ({ ...s, status: "reloading", parseError: void 0 }));
187480
+ if (electionIntervalRef.current) {
187481
+ clearInterval(electionIntervalRef.current);
187482
+ electionIntervalRef.current = null;
187483
+ }
187484
+ if (registryWatcherCleanupRef.current) {
187485
+ registryWatcherCleanupRef.current();
187486
+ registryWatcherCleanupRef.current = null;
187487
+ }
187002
187488
  await Promise.all([
187003
187489
  // Stop proxy
187004
187490
  proxyRef.current?.stop(),
187491
+ // Stop admin server
187492
+ adminServerRef.current?.stop(),
187005
187493
  // Stop all services
187006
187494
  ...servicesRef.current.map((service) => service.stop()),
187007
187495
  // Stop all Electric instances
@@ -187014,8 +187502,13 @@ function DevUI() {
187014
187502
  electricInstancesRef.current = [];
187015
187503
  drizzleGatewayRef.current = null;
187016
187504
  proxyRef.current = null;
187505
+ adminServerRef.current = null;
187017
187506
  servicesRef.current = [];
187018
187507
  resourcesRef.current = /* @__PURE__ */ new Map();
187508
+ if (proxyRegistryRef.current) {
187509
+ await proxyRegistryRef.current.unregisterServices(instanceKey);
187510
+ await proxyRegistryRef.current.releaseProxyOwnership();
187511
+ }
187019
187512
  if (stateManagerRef.current) {
187020
187513
  await stateManagerRef.current.releaseOwnership();
187021
187514
  }
@@ -187026,7 +187519,8 @@ function DevUI() {
187026
187519
  resources: /* @__PURE__ */ new Map(),
187027
187520
  resourceStatus: /* @__PURE__ */ new Map(),
187028
187521
  services: [],
187029
- proxy: void 0
187522
+ proxy: void 0,
187523
+ isProxyOwner: false
187030
187524
  }));
187031
187525
  setReloadTrigger((t) => t + 1);
187032
187526
  };
@@ -187061,10 +187555,10 @@ function DevUI() {
187061
187555
  }, []);
187062
187556
  useEffect3(() => {
187063
187557
  if (state.status !== "running") return;
187064
- const configPath = path12.join(process.cwd(), "specific.hcl");
187558
+ const configPath = path13.join(process.cwd(), "specific.hcl");
187065
187559
  const watcher = watchConfigFile(configPath, 1e3, () => {
187066
187560
  try {
187067
- const hcl = fs11.readFileSync(configPath, "utf-8");
187561
+ const hcl = fs12.readFileSync(configPath, "utf-8");
187068
187562
  parseConfig(hcl).then(() => {
187069
187563
  triggerReload();
187070
187564
  }).catch((err) => {
@@ -187104,8 +187598,10 @@ function DevUI() {
187104
187598
  let startedDrizzleGateway = null;
187105
187599
  const startedServices = [];
187106
187600
  let startedProxy = null;
187107
- const stateManager = new InstanceStateManager(process.cwd());
187601
+ const stateManager = new InstanceStateManager(process.cwd(), instanceKey);
187108
187602
  stateManagerRef.current = stateManager;
187603
+ const proxyRegistry = new ProxyRegistryManager();
187604
+ proxyRegistryRef.current = proxyRegistry;
187109
187605
  async function start() {
187110
187606
  writeLog("system", "Starting dev server");
187111
187607
  await stateManager.cleanStaleState();
@@ -187128,8 +187624,8 @@ function DevUI() {
187128
187624
  }));
187129
187625
  return;
187130
187626
  }
187131
- const configPath = path12.join(process.cwd(), "specific.hcl");
187132
- if (!fs11.existsSync(configPath)) {
187627
+ const configPath = path13.join(process.cwd(), "specific.hcl");
187628
+ if (!fs12.existsSync(configPath)) {
187133
187629
  writeLog("system", "Waiting for specific.hcl to appear");
187134
187630
  setState((s) => ({
187135
187631
  ...s,
@@ -187148,7 +187644,7 @@ function DevUI() {
187148
187644
  }
187149
187645
  let config2;
187150
187646
  try {
187151
- const hcl = fs11.readFileSync(configPath, "utf-8");
187647
+ const hcl = fs12.readFileSync(configPath, "utf-8");
187152
187648
  config2 = await parseConfig(hcl);
187153
187649
  } catch (err) {
187154
187650
  setState((s) => ({
@@ -187181,7 +187677,7 @@ function DevUI() {
187181
187677
  colorIndex++;
187182
187678
  }
187183
187679
  setState((s) => ({ ...s, status: "starting", config: config2, colorMap }));
187184
- const portAllocator = new StablePortAllocator(process.cwd());
187680
+ const portAllocator = new StablePortAllocator(process.cwd(), instanceKey);
187185
187681
  const resourceStatus = /* @__PURE__ */ new Map();
187186
187682
  const syncDatabases = detectSyncDatabases(config2);
187187
187683
  let resources2;
@@ -187190,7 +187686,7 @@ function DevUI() {
187190
187686
  config: config2,
187191
187687
  selection: { mode: "all" },
187192
187688
  stateManager,
187193
- dataDir: ".specific/data",
187689
+ dataDir: `.specific/keys/${instanceKey}/data`,
187194
187690
  portAllocator,
187195
187691
  callbacks: {
187196
187692
  onResourceStarting: (name, type) => {
@@ -187254,7 +187750,7 @@ function DevUI() {
187254
187750
  const drizzleGateway = await startDrizzleGateway(
187255
187751
  postgresResources,
187256
187752
  drizzlePort,
187257
- path12.join(process.cwd(), ".specific")
187753
+ path13.join(process.cwd(), ".specific", "keys", instanceKey)
187258
187754
  );
187259
187755
  startedDrizzleGateway = drizzleGateway;
187260
187756
  drizzleGatewayRef.current = drizzleGateway;
@@ -187362,6 +187858,7 @@ function DevUI() {
187362
187858
  }
187363
187859
  }
187364
187860
  if (cancelled) return;
187861
+ const serviceInfos = [];
187365
187862
  const exposedServices = [];
187366
187863
  for (const service of config2.services) {
187367
187864
  const endpointInfos = serviceEndpoints.get(service.name) || [];
@@ -187372,6 +187869,10 @@ function DevUI() {
187372
187869
  if (endpointConfig?.public) {
187373
187870
  const proxyName = info.endpointName === "default" ? service.name : `${service.name}-${info.endpointName}`;
187374
187871
  exposedServices.push({ name: proxyName, port: info.port });
187872
+ serviceInfos.push({
187873
+ serviceName: proxyName,
187874
+ port: info.port
187875
+ });
187375
187876
  }
187376
187877
  }
187377
187878
  }
@@ -187395,26 +187896,114 @@ function DevUI() {
187395
187896
  syncEnabled: r.type === "postgres" && syncDatabases.has(name)
187396
187897
  }))
187397
187898
  });
187398
- try {
187399
- const certificate = generateCertificate("local.spcf.app");
187400
- const proxy2 = await startHttpProxy(
187401
- exposedServices,
187402
- certificate,
187403
- getState,
187404
- startedDrizzleGateway?.port
187405
- );
187406
- startedProxy = proxy2;
187407
- proxyRef.current = proxy2;
187408
- setState((s) => ({ ...s, proxy: proxy2 }));
187409
- } catch (err) {
187410
- const errorMsg = `Failed to start HTTP proxy: ${err instanceof Error ? err.message : String(err)}`;
187411
- writeLog("system:error", errorMsg);
187412
- setState((s) => ({
187413
- ...s,
187414
- status: "error",
187415
- error: errorMsg
187416
- }));
187417
- return;
187899
+ const adminServer = await startAdminServer(getState);
187900
+ adminServerRef.current = adminServer;
187901
+ writeLog("system", `Admin API server started on port ${adminServer.port}`);
187902
+ await proxyRegistry.registerServices(
187903
+ instanceKey,
187904
+ adminServer.port,
187905
+ serviceInfos,
187906
+ startedDrizzleGateway?.port
187907
+ );
187908
+ writeLog("system", `Registered ${serviceInfos.length} services with proxy registry`);
187909
+ const becameProxyOwner = await proxyRegistry.claimProxyOwnership(instanceKey);
187910
+ if (becameProxyOwner) {
187911
+ writeLog("system", "Claimed proxy ownership, starting HTTP proxy");
187912
+ try {
187913
+ const currentServices = await proxyRegistry.getAllServices();
187914
+ const registeredKeys = [...new Set(currentServices.map((s) => s.key))];
187915
+ const certificate = generateCertificate("local.spcf.app", registeredKeys);
187916
+ const proxy2 = await startHttpProxy(
187917
+ exposedServices,
187918
+ certificate,
187919
+ getState,
187920
+ instanceKey
187921
+ );
187922
+ startedProxy = proxy2;
187923
+ proxyRef.current = proxy2;
187924
+ setState((s) => ({ ...s, proxy: proxy2, isProxyOwner: true }));
187925
+ const knownKeys = new Set(registeredKeys);
187926
+ registryWatcherCleanupRef.current = proxyRegistry.watchRegistry(async (updatedServices, updatedKeys) => {
187927
+ writeLog("system", `Registry updated: ${updatedServices.length} services`);
187928
+ proxy2.updateServices(updatedServices, updatedKeys);
187929
+ const newKeyNames = Object.keys(updatedKeys).filter((k) => !knownKeys.has(k));
187930
+ if (newKeyNames.length > 0) {
187931
+ writeLog("system", `New keys detected: ${newKeyNames.join(", ")} - regenerating certificate`);
187932
+ for (const key of newKeyNames) {
187933
+ knownKeys.add(key);
187934
+ }
187935
+ const allKeys = [...knownKeys];
187936
+ const newCertificate = generateCertificate("local.spcf.app", allKeys);
187937
+ proxy2.updateCertificate(newCertificate);
187938
+ }
187939
+ });
187940
+ const currentKeys = await proxyRegistry.getAllKeyRegistrations();
187941
+ proxy2.updateServices(currentServices, currentKeys);
187942
+ writeLog("system", `Loaded ${currentServices.length} services from registry`);
187943
+ } catch (err) {
187944
+ const errorMsg = `Failed to start HTTP proxy: ${err instanceof Error ? err.message : String(err)}`;
187945
+ writeLog("system:error", errorMsg);
187946
+ setState((s) => ({
187947
+ ...s,
187948
+ status: "error",
187949
+ error: errorMsg
187950
+ }));
187951
+ return;
187952
+ }
187953
+ } else {
187954
+ writeLog("system", "Another instance owns the proxy, starting election watcher");
187955
+ setState((s) => ({ ...s, isProxyOwner: false }));
187956
+ const isProcessRunning = (pid) => {
187957
+ try {
187958
+ process.kill(pid, 0);
187959
+ return true;
187960
+ } catch {
187961
+ return false;
187962
+ }
187963
+ };
187964
+ electionIntervalRef.current = setInterval(async () => {
187965
+ if (cancelled || shuttingDown.current) {
187966
+ if (electionIntervalRef.current) {
187967
+ clearInterval(electionIntervalRef.current);
187968
+ electionIntervalRef.current = null;
187969
+ }
187970
+ return;
187971
+ }
187972
+ const owner = await proxyRegistry.getProxyOwner();
187973
+ if (!owner || !isProcessRunning(owner.pid)) {
187974
+ writeLog("system", "Proxy owner died, attempting election");
187975
+ const won = await proxyRegistry.attemptElection(instanceKey);
187976
+ if (won) {
187977
+ writeLog("system", "Won election, starting HTTP proxy");
187978
+ if (electionIntervalRef.current) {
187979
+ clearInterval(electionIntervalRef.current);
187980
+ electionIntervalRef.current = null;
187981
+ }
187982
+ try {
187983
+ const electionServices = await proxyRegistry.getAllServices();
187984
+ const electionKeyRegistrations = await proxyRegistry.getAllKeyRegistrations();
187985
+ const electionKeyNames = Object.keys(electionKeyRegistrations);
187986
+ const certificate = generateCertificate("local.spcf.app", electionKeyNames);
187987
+ const proxy2 = await startHttpProxy(
187988
+ exposedServices,
187989
+ certificate,
187990
+ getState,
187991
+ instanceKey
187992
+ );
187993
+ startedProxy = proxy2;
187994
+ proxyRef.current = proxy2;
187995
+ setState((s) => ({ ...s, proxy: proxy2, isProxyOwner: true }));
187996
+ registryWatcherCleanupRef.current = proxyRegistry.watchRegistry((updatedServices, updatedKeys) => {
187997
+ writeLog("system", `Registry updated: ${updatedServices.length} services`);
187998
+ proxy2.updateServices(updatedServices, updatedKeys);
187999
+ });
188000
+ proxy2.updateServices(electionServices, electionKeyRegistrations);
188001
+ } catch (err) {
188002
+ writeLog("system:error", `Failed to start proxy after election: ${err}`);
188003
+ }
188004
+ }
188005
+ }
188006
+ }, 1e3);
187418
188007
  }
187419
188008
  if (cancelled) return;
187420
188009
  writeLog("system", "Dev server running");
@@ -187423,6 +188012,14 @@ function DevUI() {
187423
188012
  start();
187424
188013
  return () => {
187425
188014
  cancelled = true;
188015
+ if (electionIntervalRef.current) {
188016
+ clearInterval(electionIntervalRef.current);
188017
+ electionIntervalRef.current = null;
188018
+ }
188019
+ if (registryWatcherCleanupRef.current) {
188020
+ registryWatcherCleanupRef.current();
188021
+ registryWatcherCleanupRef.current = null;
188022
+ }
187426
188023
  if (startedProxy) {
187427
188024
  startedProxy.stop().catch(() => {
187428
188025
  });
@@ -187443,10 +188040,14 @@ function DevUI() {
187443
188040
  resource.stop().catch(() => {
187444
188041
  });
187445
188042
  }
188043
+ proxyRegistry.unregisterServices(instanceKey).catch(() => {
188044
+ });
188045
+ proxyRegistry.releaseProxyOwnership().catch(() => {
188046
+ });
187446
188047
  stateManager.releaseOwnership().catch(() => {
187447
188048
  });
187448
188049
  };
187449
- }, [reloadTrigger, readyToStart]);
188050
+ }, [reloadTrigger, readyToStart, instanceKey]);
187450
188051
  if (state.status === "installing-ca") {
187451
188052
  if (state.caInstallPhase === "installing") {
187452
188053
  return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: "cyan" }, "TLS Certificate Setup"), /* @__PURE__ */ React3.createElement(Text3, null, " "), /* @__PURE__ */ React3.createElement(Text3, null, "Installing a local certificate authority (CA) to enable HTTPS"), /* @__PURE__ */ React3.createElement(Text3, null, "for local development. The CA is limited to Specific projects."), /* @__PURE__ */ React3.createElement(Text3, null, " "), /* @__PURE__ */ React3.createElement(Text3, null, "Your password is required to add the CA to your system's trust store."), /* @__PURE__ */ React3.createElement(Text3, null, " "));
@@ -187524,17 +188125,15 @@ function DevUI() {
187524
188125
  const staticItems = [
187525
188126
  {
187526
188127
  key: "title",
187527
- content: /* @__PURE__ */ React3.createElement(Text3, null, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: "cyan" }, "Specific dev server"), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " (Ctrl+C to stop)"))
188128
+ content: /* @__PURE__ */ React3.createElement(Text3, null, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: "cyan" }, "Specific dev server"), instanceKey !== "default" && /* @__PURE__ */ React3.createElement(Text3, { color: "yellow" }, " [", instanceKey, "]"), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " (Ctrl+C to stop)"))
187528
188129
  },
187529
188130
  { key: "space1", content: /* @__PURE__ */ React3.createElement(Text3, null, " ") },
187530
- // Show admin UI URL when proxy is running
187531
- ...proxy ? [
187532
- {
187533
- key: "admin",
187534
- content: /* @__PURE__ */ React3.createElement(Text3, null, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Admin:"), /* @__PURE__ */ React3.createElement(Text3, null, " "), /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "https://local.spcf.app"))
187535
- },
187536
- { key: "admin-space", content: /* @__PURE__ */ React3.createElement(Text3, null, " ") }
187537
- ] : [],
188131
+ // Show admin UI URL
188132
+ {
188133
+ key: "admin",
188134
+ content: /* @__PURE__ */ React3.createElement(Text3, null, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Admin:"), /* @__PURE__ */ React3.createElement(Text3, null, " "), /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "https://", instanceKey === "default" ? "" : `${instanceKey}.`, "local.spcf.app"))
188135
+ },
188136
+ { key: "admin-space", content: /* @__PURE__ */ React3.createElement(Text3, null, " ") },
187538
188137
  ...services.length > 0 ? [
187539
188138
  { key: "svc-header", content: /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Services:") },
187540
188139
  ...services.flatMap((svc) => {
@@ -187553,17 +188152,14 @@ function DevUI() {
187553
188152
  const proxyName = endpoint.name === "default" ? svc.name : `${svc.name}-${endpoint.name}`;
187554
188153
  return {
187555
188154
  key: `svc-${svc.name}-${endpoint.name}`,
187556
- content: /* @__PURE__ */ React3.createElement(Text3, null, /* @__PURE__ */ React3.createElement(Text3, { color: "green" }, " \u25CF "), /* @__PURE__ */ React3.createElement(Text3, null, displayName), port ? endpoint.public && proxy ? /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(Text3, null, " \u2192 "), /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "https://", proxyName, ".local.spcf.app"), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " (localhost:", port, ")")) : /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " (localhost:", port, ")") : null)
188155
+ content: /* @__PURE__ */ React3.createElement(Text3, null, /* @__PURE__ */ React3.createElement(Text3, { color: "green" }, " \u25CF "), /* @__PURE__ */ React3.createElement(Text3, null, displayName), port ? endpoint.public ? /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(Text3, null, " \u2192 "), /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "https://", proxyName, instanceKey === "default" ? "" : `.${instanceKey}`, ".local.spcf.app"), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " (localhost:", port, ")")) : /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " (localhost:", port, ")") : null)
187557
188156
  };
187558
188157
  });
187559
188158
  }),
187560
188159
  { key: "space2", content: /* @__PURE__ */ React3.createElement(Text3, null, " ") }
187561
188160
  ] : [],
187562
188161
  ...config.postgres.length > 0 ? [
187563
- {
187564
- key: "pg-header",
187565
- content: /* @__PURE__ */ React3.createElement(Text3, null, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Postgres:"), drizzleGatewayRef.current && /* @__PURE__ */ React3.createElement(Text3, null, " ", "(admin \u2192 ", /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "http://localhost:", drizzleGatewayRef.current.port), ")"))
187566
- },
188162
+ { key: "pg-header", content: /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Postgres:") },
187567
188163
  ...config.postgres.map((pg) => {
187568
188164
  const instance = resources.get(pg.name);
187569
188165
  return {
@@ -187609,8 +188205,47 @@ function DevUI() {
187609
188205
  ];
187610
188206
  return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Static, { items: staticItems }, (item) => /* @__PURE__ */ React3.createElement(Box3, { key: item.key }, item.content)));
187611
188207
  }
187612
- function devCommand() {
187613
- render3(/* @__PURE__ */ React3.createElement(DevUI, null));
188208
+ function devCommand(instanceKey) {
188209
+ render3(/* @__PURE__ */ React3.createElement(DevUI, { instanceKey }));
188210
+ }
188211
+
188212
+ // src/lib/dev/git-worktree.ts
188213
+ import { execSync as execSync2 } from "child_process";
188214
+ import * as path14 from "path";
188215
+ function isInWorktree() {
188216
+ try {
188217
+ const commonDir = execSync2("git rev-parse --git-common-dir", {
188218
+ encoding: "utf-8",
188219
+ stdio: ["pipe", "pipe", "pipe"]
188220
+ }).trim();
188221
+ const gitDir = execSync2("git rev-parse --git-dir", {
188222
+ encoding: "utf-8",
188223
+ stdio: ["pipe", "pipe", "pipe"]
188224
+ }).trim();
188225
+ const resolvedCommonDir = path14.resolve(commonDir);
188226
+ const resolvedGitDir = path14.resolve(gitDir);
188227
+ return resolvedCommonDir !== resolvedGitDir;
188228
+ } catch {
188229
+ return false;
188230
+ }
188231
+ }
188232
+ function getWorktreeName() {
188233
+ if (!isInWorktree()) {
188234
+ return null;
188235
+ }
188236
+ try {
188237
+ const gitDir = execSync2("git rev-parse --git-dir", {
188238
+ encoding: "utf-8",
188239
+ stdio: ["pipe", "pipe", "pipe"]
188240
+ }).trim();
188241
+ return path14.basename(gitDir);
188242
+ } catch {
188243
+ return null;
188244
+ }
188245
+ }
188246
+ function getDefaultKey() {
188247
+ const worktreeName = getWorktreeName();
188248
+ return worktreeName ?? "default";
187614
188249
  }
187615
188250
 
187616
188251
  // src/commands/deploy.tsx
@@ -187620,30 +188255,30 @@ import Spinner5 from "ink-spinner";
187620
188255
 
187621
188256
  // ../../node_modules/open/index.js
187622
188257
  import process8 from "node:process";
187623
- import path13 from "node:path";
188258
+ import path15 from "node:path";
187624
188259
  import { fileURLToPath as fileURLToPath3 } from "node:url";
187625
188260
  import childProcess3 from "node:child_process";
187626
- import fs16, { constants as fsConstants2 } from "node:fs/promises";
188261
+ import fs17, { constants as fsConstants2 } from "node:fs/promises";
187627
188262
 
187628
188263
  // ../../node_modules/wsl-utils/index.js
187629
188264
  import { promisify as promisify2 } from "node:util";
187630
188265
  import childProcess2 from "node:child_process";
187631
- import fs15, { constants as fsConstants } from "node:fs/promises";
188266
+ import fs16, { constants as fsConstants } from "node:fs/promises";
187632
188267
 
187633
188268
  // ../../node_modules/is-wsl/index.js
187634
188269
  import process2 from "node:process";
187635
- import os4 from "node:os";
187636
- import fs14 from "node:fs";
188270
+ import os5 from "node:os";
188271
+ import fs15 from "node:fs";
187637
188272
 
187638
188273
  // ../../node_modules/is-inside-container/index.js
187639
- import fs13 from "node:fs";
188274
+ import fs14 from "node:fs";
187640
188275
 
187641
188276
  // ../../node_modules/is-docker/index.js
187642
- import fs12 from "node:fs";
188277
+ import fs13 from "node:fs";
187643
188278
  var isDockerCached;
187644
188279
  function hasDockerEnv() {
187645
188280
  try {
187646
- fs12.statSync("/.dockerenv");
188281
+ fs13.statSync("/.dockerenv");
187647
188282
  return true;
187648
188283
  } catch {
187649
188284
  return false;
@@ -187651,7 +188286,7 @@ function hasDockerEnv() {
187651
188286
  }
187652
188287
  function hasDockerCGroup() {
187653
188288
  try {
187654
- return fs12.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
188289
+ return fs13.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
187655
188290
  } catch {
187656
188291
  return false;
187657
188292
  }
@@ -187667,7 +188302,7 @@ function isDocker() {
187667
188302
  var cachedResult;
187668
188303
  var hasContainerEnv = () => {
187669
188304
  try {
187670
- fs13.statSync("/run/.containerenv");
188305
+ fs14.statSync("/run/.containerenv");
187671
188306
  return true;
187672
188307
  } catch {
187673
188308
  return false;
@@ -187685,14 +188320,14 @@ var isWsl = () => {
187685
188320
  if (process2.platform !== "linux") {
187686
188321
  return false;
187687
188322
  }
187688
- if (os4.release().toLowerCase().includes("microsoft")) {
188323
+ if (os5.release().toLowerCase().includes("microsoft")) {
187689
188324
  if (isInsideContainer()) {
187690
188325
  return false;
187691
188326
  }
187692
188327
  return true;
187693
188328
  }
187694
188329
  try {
187695
- return fs14.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft") ? !isInsideContainer() : false;
188330
+ return fs15.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft") ? !isInsideContainer() : false;
187696
188331
  } catch {
187697
188332
  return false;
187698
188333
  }
@@ -187760,14 +188395,14 @@ var wslDrivesMountPoint = /* @__PURE__ */ (() => {
187760
188395
  const configFilePath = "/etc/wsl.conf";
187761
188396
  let isConfigFileExists = false;
187762
188397
  try {
187763
- await fs15.access(configFilePath, fsConstants.F_OK);
188398
+ await fs16.access(configFilePath, fsConstants.F_OK);
187764
188399
  isConfigFileExists = true;
187765
188400
  } catch {
187766
188401
  }
187767
188402
  if (!isConfigFileExists) {
187768
188403
  return defaultMountPoint;
187769
188404
  }
187770
- const configContent = await fs15.readFile(configFilePath, { encoding: "utf8" });
188405
+ const configContent = await fs16.readFile(configFilePath, { encoding: "utf8" });
187771
188406
  const parsedMountPoint = parseMountPointFromConfig(configContent);
187772
188407
  if (parsedMountPoint === void 0) {
187773
188408
  return defaultMountPoint;
@@ -187787,7 +188422,7 @@ var canAccessPowerShell = async () => {
187787
188422
  canAccessPowerShellPromise ??= (async () => {
187788
188423
  try {
187789
188424
  const psPath = await powerShellPath2();
187790
- await fs15.access(psPath, fsConstants.X_OK);
188425
+ await fs16.access(psPath, fsConstants.X_OK);
187791
188426
  return true;
187792
188427
  } catch {
187793
188428
  return false;
@@ -187801,15 +188436,15 @@ var wslDefaultBrowser = async () => {
187801
188436
  const { stdout } = await executePowerShell(command, { powerShellPath: psPath });
187802
188437
  return stdout.trim();
187803
188438
  };
187804
- var convertWslPathToWindows = async (path20) => {
187805
- if (/^[a-z]+:\/\//i.test(path20)) {
187806
- return path20;
188439
+ var convertWslPathToWindows = async (path22) => {
188440
+ if (/^[a-z]+:\/\//i.test(path22)) {
188441
+ return path22;
187807
188442
  }
187808
188443
  try {
187809
- const { stdout } = await execFile2("wslpath", ["-aw", path20], { encoding: "utf8" });
188444
+ const { stdout } = await execFile2("wslpath", ["-aw", path22], { encoding: "utf8" });
187810
188445
  return stdout.trim();
187811
188446
  } catch {
187812
- return path20;
188447
+ return path22;
187813
188448
  }
187814
188449
  };
187815
188450
 
@@ -187951,8 +188586,8 @@ var is_in_ssh_default = isInSsh;
187951
188586
 
187952
188587
  // ../../node_modules/open/index.js
187953
188588
  var fallbackAttemptSymbol = Symbol("fallbackAttempt");
187954
- var __dirname3 = import.meta.url ? path13.dirname(fileURLToPath3(import.meta.url)) : "";
187955
- var localXdgOpenPath = path13.join(__dirname3, "xdg-open");
188589
+ var __dirname3 = import.meta.url ? path15.dirname(fileURLToPath3(import.meta.url)) : "";
188590
+ var localXdgOpenPath = path15.join(__dirname3, "xdg-open");
187956
188591
  var { platform: platform4, arch: arch2 } = process8;
187957
188592
  var tryEachApp = async (apps2, opener) => {
187958
188593
  if (apps2.length === 0) {
@@ -188100,7 +188735,7 @@ var baseOpen = async (options2) => {
188100
188735
  const isBundled = !__dirname3 || __dirname3 === "/";
188101
188736
  let exeLocalXdgOpen = false;
188102
188737
  try {
188103
- await fs16.access(localXdgOpenPath, fsConstants2.X_OK);
188738
+ await fs17.access(localXdgOpenPath, fsConstants2.X_OK);
188104
188739
  exeLocalXdgOpen = true;
188105
188740
  } catch {
188106
188741
  }
@@ -188123,19 +188758,19 @@ var baseOpen = async (options2) => {
188123
188758
  }
188124
188759
  const subprocess = childProcess3.spawn(command, cliArguments, childProcessOptions);
188125
188760
  if (options2.wait) {
188126
- return new Promise((resolve4, reject) => {
188761
+ return new Promise((resolve5, reject) => {
188127
188762
  subprocess.once("error", reject);
188128
188763
  subprocess.once("close", (exitCode) => {
188129
188764
  if (!options2.allowNonzeroExitCode && exitCode !== 0) {
188130
188765
  reject(new Error(`Exited with code ${exitCode}`));
188131
188766
  return;
188132
188767
  }
188133
- resolve4(subprocess);
188768
+ resolve5(subprocess);
188134
188769
  });
188135
188770
  });
188136
188771
  }
188137
188772
  if (isFallbackAttempt) {
188138
- return new Promise((resolve4, reject) => {
188773
+ return new Promise((resolve5, reject) => {
188139
188774
  subprocess.once("error", reject);
188140
188775
  subprocess.once("spawn", () => {
188141
188776
  subprocess.once("close", (exitCode) => {
@@ -188145,17 +188780,17 @@ var baseOpen = async (options2) => {
188145
188780
  return;
188146
188781
  }
188147
188782
  subprocess.unref();
188148
- resolve4(subprocess);
188783
+ resolve5(subprocess);
188149
188784
  });
188150
188785
  });
188151
188786
  });
188152
188787
  }
188153
188788
  subprocess.unref();
188154
- return new Promise((resolve4, reject) => {
188789
+ return new Promise((resolve5, reject) => {
188155
188790
  subprocess.once("error", reject);
188156
188791
  subprocess.once("spawn", () => {
188157
188792
  subprocess.off("error", reject);
188158
- resolve4(subprocess);
188793
+ resolve5(subprocess);
188159
188794
  });
188160
188795
  });
188161
188796
  };
@@ -188232,35 +188867,35 @@ defineLazyProperty(apps, "safari", () => detectPlatformBinary({
188232
188867
  var open_default = open2;
188233
188868
 
188234
188869
  // src/commands/deploy.tsx
188235
- import * as fs20 from "fs";
188236
- import * as path17 from "path";
188870
+ import * as fs21 from "fs";
188871
+ import * as path19 from "path";
188237
188872
 
188238
188873
  // src/lib/deploy/build-tester.ts
188239
188874
  import { spawn as spawn5 } from "child_process";
188240
- import { existsSync as existsSync14 } from "fs";
188241
- import { join as join15 } from "path";
188875
+ import { existsSync as existsSync15 } from "fs";
188876
+ import { join as join16 } from "path";
188242
188877
  function getDependencyInstallCommand(build, projectDir) {
188243
188878
  switch (build.base) {
188244
188879
  case "node":
188245
- if (existsSync14(join15(projectDir, "pnpm-lock.yaml"))) {
188880
+ if (existsSync15(join16(projectDir, "pnpm-lock.yaml"))) {
188246
188881
  return "pnpm install --frozen-lockfile";
188247
- } else if (existsSync14(join15(projectDir, "yarn.lock"))) {
188882
+ } else if (existsSync15(join16(projectDir, "yarn.lock"))) {
188248
188883
  return "yarn install --frozen-lockfile";
188249
- } else if (existsSync14(join15(projectDir, "package-lock.json"))) {
188884
+ } else if (existsSync15(join16(projectDir, "package-lock.json"))) {
188250
188885
  return "npm ci";
188251
188886
  } else {
188252
188887
  return "npm install";
188253
188888
  }
188254
188889
  case "python":
188255
- if (existsSync14(join15(projectDir, "poetry.lock"))) {
188890
+ if (existsSync15(join16(projectDir, "poetry.lock"))) {
188256
188891
  return "poetry install --no-interaction";
188257
- } else if (existsSync14(join15(projectDir, "Pipfile.lock"))) {
188892
+ } else if (existsSync15(join16(projectDir, "Pipfile.lock"))) {
188258
188893
  return "pipenv install --deploy";
188259
- } else if (existsSync14(join15(projectDir, "Pipfile"))) {
188894
+ } else if (existsSync15(join16(projectDir, "Pipfile"))) {
188260
188895
  return "pipenv install";
188261
- } else if (existsSync14(join15(projectDir, "pyproject.toml"))) {
188896
+ } else if (existsSync15(join16(projectDir, "pyproject.toml"))) {
188262
188897
  return "pip install .";
188263
- } else if (existsSync14(join15(projectDir, "requirements.txt"))) {
188898
+ } else if (existsSync15(join16(projectDir, "requirements.txt"))) {
188264
188899
  return "pip install -r requirements.txt";
188265
188900
  }
188266
188901
  return null;
@@ -188274,7 +188909,7 @@ function getDependencyInstallCommand(build, projectDir) {
188274
188909
  }
188275
188910
  }
188276
188911
  function runCommand2(command, projectDir, buildName) {
188277
- return new Promise((resolve4) => {
188912
+ return new Promise((resolve5) => {
188278
188913
  const stdout = [];
188279
188914
  const stderr = [];
188280
188915
  writeLog("build-test", `[${buildName}] Running: ${command}`);
@@ -188304,7 +188939,7 @@ function runCommand2(command, projectDir, buildName) {
188304
188939
  });
188305
188940
  child.on("error", (err) => {
188306
188941
  writeLog("build-test:error", `[${buildName}] Failed to start: ${err.message}`);
188307
- resolve4({
188942
+ resolve5({
188308
188943
  success: false,
188309
188944
  output: `Failed to start command: ${err.message}`
188310
188945
  });
@@ -188313,10 +188948,10 @@ function runCommand2(command, projectDir, buildName) {
188313
188948
  const output = [...stdout, ...stderr].join("");
188314
188949
  if (code === 0) {
188315
188950
  writeLog("build-test", `[${buildName}] Command succeeded (exit code 0)`);
188316
- resolve4({ success: true, output });
188951
+ resolve5({ success: true, output });
188317
188952
  } else {
188318
188953
  writeLog("build-test:error", `[${buildName}] Command failed with exit code ${code}`);
188319
- resolve4({
188954
+ resolve5({
188320
188955
  success: false,
188321
188956
  output: output || `Exit code: ${code}`
188322
188957
  });
@@ -188655,13 +189290,13 @@ var ApiClient = class {
188655
189290
  };
188656
189291
 
188657
189292
  // src/lib/tarball/create.ts
188658
- import { execSync as execSync2 } from "child_process";
188659
- import * as fs17 from "fs";
188660
- import * as path14 from "path";
189293
+ import { execSync as execSync3 } from "child_process";
189294
+ import * as fs18 from "fs";
189295
+ import * as path16 from "path";
188661
189296
  import { createTarPacker, createEntryItemGenerator } from "tar-vern";
188662
189297
  function isInsideGitRepository(dir) {
188663
189298
  try {
188664
- const result = execSync2("git rev-parse --is-inside-work-tree", {
189299
+ const result = execSync3("git rev-parse --is-inside-work-tree", {
188665
189300
  cwd: dir,
188666
189301
  encoding: "utf-8",
188667
189302
  stdio: ["pipe", "pipe", "pipe"]
@@ -188671,50 +189306,42 @@ function isInsideGitRepository(dir) {
188671
189306
  return false;
188672
189307
  }
188673
189308
  }
188674
- function getGitInfo(projectDir) {
188675
- const gitRoot = execSync2("git rev-parse --show-toplevel", {
188676
- cwd: projectDir,
188677
- encoding: "utf-8"
188678
- }).trim();
188679
- const relativePath = path14.relative(gitRoot, projectDir);
188680
- return { gitRoot, relativePath: relativePath || "." };
188681
- }
188682
- function createGitArchive(projectDir) {
188683
- writeLog("tarball", "Creating tarball using git archive");
188684
- const { gitRoot, relativePath } = getGitInfo(projectDir);
188685
- const isSubdirectory = relativePath !== ".";
188686
- writeLog(
188687
- "tarball",
188688
- isSubdirectory ? `Project is in subdirectory: ${relativePath}` : "Project is at git root"
188689
- );
188690
- const stashResult = execSync2("git stash create --include-untracked", {
188691
- cwd: gitRoot,
188692
- encoding: "utf-8"
188693
- }).trim();
188694
- const treeish = stashResult || "HEAD";
188695
- writeLog(
188696
- "tarball",
188697
- `Archiving from: ${treeish === "HEAD" ? "HEAD (no uncommitted changes)" : "stash (includes uncommitted changes)"}`
188698
- );
188699
- const archiveTarget = isSubdirectory ? `${treeish}:${relativePath}` : treeish;
188700
- const tarball = execSync2(
188701
- `git archive --format=tar.gz ${archiveTarget} -- . ":!.specific"`,
189309
+ async function createGitArchive(projectDir) {
189310
+ writeLog("tarball", "Creating tarball using git ls-files");
189311
+ const filesOutput = execSync3(
189312
+ "git ls-files --cached --others --exclude-standard",
188702
189313
  {
188703
- cwd: gitRoot,
189314
+ cwd: projectDir,
189315
+ encoding: "utf-8",
188704
189316
  maxBuffer: 100 * 1024 * 1024
188705
189317
  // 100MB max
188706
189318
  }
188707
189319
  );
188708
- writeLog("tarball", `Git archive created: ${tarball.length} bytes`);
189320
+ const allFiles = filesOutput.trim().split("\n").filter(Boolean);
189321
+ const filteredFiles = allFiles.filter(
189322
+ (file) => !file.startsWith(".specific/") && file !== ".specific"
189323
+ );
189324
+ writeLog(
189325
+ "tarball",
189326
+ `Found ${allFiles.length} files, ${filteredFiles.length} after filtering .specific`
189327
+ );
189328
+ const entryGenerator = createEntryItemGenerator(projectDir, filteredFiles);
189329
+ const tarStream = createTarPacker(entryGenerator, "gzip");
189330
+ const chunks = [];
189331
+ for await (const chunk of tarStream) {
189332
+ chunks.push(Buffer.from(chunk));
189333
+ }
189334
+ const tarball = Buffer.concat(chunks);
189335
+ writeLog("tarball", `Tarball created: ${tarball.length} bytes`);
188709
189336
  return tarball;
188710
189337
  }
188711
189338
  var EXCLUDED_DIRS = [".specific"];
188712
189339
  async function collectPaths(baseDir, currentDir, exclude) {
188713
189340
  const results = [];
188714
- const entries = await fs17.promises.readdir(currentDir, { withFileTypes: true });
189341
+ const entries = await fs18.promises.readdir(currentDir, { withFileTypes: true });
188715
189342
  for (const entry of entries) {
188716
- const fullPath = path14.join(currentDir, entry.name);
188717
- const relativePath = path14.relative(baseDir, fullPath);
189343
+ const fullPath = path16.join(currentDir, entry.name);
189344
+ const relativePath = path16.relative(baseDir, fullPath);
188718
189345
  if (entry.isDirectory()) {
188719
189346
  if (!exclude.includes(entry.name)) {
188720
189347
  results.push(relativePath);
@@ -188729,8 +189356,8 @@ async function collectPaths(baseDir, currentDir, exclude) {
188729
189356
  }
188730
189357
  async function createTarArchive(projectDir) {
188731
189358
  writeLog("tarball", "Creating tarball using tar-vern (non-git project)");
188732
- const configPath = path14.join(projectDir, "specific.hcl");
188733
- if (!fs17.existsSync(configPath)) {
189359
+ const configPath = path16.join(projectDir, "specific.hcl");
189360
+ if (!fs18.existsSync(configPath)) {
188734
189361
  throw new Error("specific.hcl not found in project directory");
188735
189362
  }
188736
189363
  const relativePaths = await collectPaths(projectDir, projectDir, EXCLUDED_DIRS);
@@ -188753,8 +189380,8 @@ async function createProjectTarball(projectDir) {
188753
189380
  }
188754
189381
 
188755
189382
  // src/lib/project/config.ts
188756
- import * as fs18 from "fs";
188757
- import * as path15 from "path";
189383
+ import * as fs19 from "fs";
189384
+ import * as path17 from "path";
188758
189385
  var PROJECT_ID_FILE = ".specific/project_id";
188759
189386
  var ProjectNotLinkedError = class extends Error {
188760
189387
  constructor() {
@@ -188769,32 +189396,32 @@ Run: specific deploy`
188769
189396
  }
188770
189397
  };
188771
189398
  function readProjectId(projectDir = process.cwd()) {
188772
- const projectIdPath = path15.join(projectDir, PROJECT_ID_FILE);
188773
- if (!fs18.existsSync(projectIdPath)) {
189399
+ const projectIdPath = path17.join(projectDir, PROJECT_ID_FILE);
189400
+ if (!fs19.existsSync(projectIdPath)) {
188774
189401
  throw new ProjectNotLinkedError();
188775
189402
  }
188776
- const projectId = fs18.readFileSync(projectIdPath, "utf-8").trim();
189403
+ const projectId = fs19.readFileSync(projectIdPath, "utf-8").trim();
188777
189404
  if (!projectId) {
188778
189405
  throw new Error(`${PROJECT_ID_FILE} is empty`);
188779
189406
  }
188780
189407
  return projectId;
188781
189408
  }
188782
189409
  function hasProjectId(projectDir = process.cwd()) {
188783
- const projectIdPath = path15.join(projectDir, PROJECT_ID_FILE);
188784
- return fs18.existsSync(projectIdPath);
189410
+ const projectIdPath = path17.join(projectDir, PROJECT_ID_FILE);
189411
+ return fs19.existsSync(projectIdPath);
188785
189412
  }
188786
189413
  function writeProjectId(projectId, projectDir = process.cwd()) {
188787
- const specificDir = path15.join(projectDir, ".specific");
188788
- if (!fs18.existsSync(specificDir)) {
188789
- fs18.mkdirSync(specificDir, { recursive: true });
189414
+ const specificDir = path17.join(projectDir, ".specific");
189415
+ if (!fs19.existsSync(specificDir)) {
189416
+ fs19.mkdirSync(specificDir, { recursive: true });
188790
189417
  }
188791
- fs18.writeFileSync(path15.join(specificDir, "project_id"), projectId + "\n");
189418
+ fs19.writeFileSync(path17.join(specificDir, "project_id"), projectId + "\n");
188792
189419
  }
188793
189420
 
188794
189421
  // src/lib/auth/credentials.ts
188795
- import * as fs19 from "fs";
188796
- import * as path16 from "path";
188797
- import * as os5 from "os";
189422
+ import * as fs20 from "fs";
189423
+ import * as path18 from "path";
189424
+ import * as os6 from "os";
188798
189425
 
188799
189426
  // src/lib/auth/errors.ts
188800
189427
  var RefreshTokenExpiredError = class extends Error {
@@ -188871,7 +189498,7 @@ import React4, { useState as useState4, useEffect as useEffect4 } from "react";
188871
189498
  import { render as render4, Box as Box4, Text as Text4, useApp as useApp3 } from "ink";
188872
189499
  import Spinner4 from "ink-spinner";
188873
189500
  function performLogin(options2 = {}) {
188874
- return new Promise((resolve4) => {
189501
+ return new Promise((resolve5) => {
188875
189502
  const instance = render4(
188876
189503
  /* @__PURE__ */ React4.createElement(
188877
189504
  LoginFlow,
@@ -188879,7 +189506,7 @@ function performLogin(options2 = {}) {
188879
189506
  isReauthentication: options2.isReauthentication,
188880
189507
  onComplete: (result) => {
188881
189508
  instance.unmount();
188882
- resolve4(result);
189509
+ resolve5(result);
188883
189510
  }
188884
189511
  }
188885
189512
  )
@@ -188985,18 +189612,18 @@ function LoginFlow({ isReauthentication, onComplete }) {
188985
189612
 
188986
189613
  // src/lib/auth/credentials.ts
188987
189614
  function getUserCredentialsDir() {
188988
- return path16.join(os5.homedir(), ".specific");
189615
+ return path18.join(os6.homedir(), ".specific");
188989
189616
  }
188990
189617
  function getCredentialsPath() {
188991
- return path16.join(getUserCredentialsDir(), "credentials.json");
189618
+ return path18.join(getUserCredentialsDir(), "credentials.json");
188992
189619
  }
188993
189620
  function readUserCredentials() {
188994
189621
  const credentialsPath = getCredentialsPath();
188995
- if (!fs19.existsSync(credentialsPath)) {
189622
+ if (!fs20.existsSync(credentialsPath)) {
188996
189623
  return null;
188997
189624
  }
188998
189625
  try {
188999
- const content = fs19.readFileSync(credentialsPath, "utf-8");
189626
+ const content = fs20.readFileSync(credentialsPath, "utf-8");
189000
189627
  return JSON.parse(content);
189001
189628
  } catch {
189002
189629
  return null;
@@ -189004,18 +189631,18 @@ function readUserCredentials() {
189004
189631
  }
189005
189632
  function writeUserCredentials(credentials) {
189006
189633
  const dir = getUserCredentialsDir();
189007
- if (!fs19.existsSync(dir)) {
189008
- fs19.mkdirSync(dir, { recursive: true, mode: 448 });
189634
+ if (!fs20.existsSync(dir)) {
189635
+ fs20.mkdirSync(dir, { recursive: true, mode: 448 });
189009
189636
  }
189010
189637
  const credentialsPath = getCredentialsPath();
189011
- fs19.writeFileSync(credentialsPath, JSON.stringify(credentials, null, 2), {
189638
+ fs20.writeFileSync(credentialsPath, JSON.stringify(credentials, null, 2), {
189012
189639
  mode: 384
189013
189640
  });
189014
189641
  }
189015
189642
  function clearUserCredentials() {
189016
189643
  const credentialsPath = getCredentialsPath();
189017
- if (fs19.existsSync(credentialsPath)) {
189018
- fs19.unlinkSync(credentialsPath);
189644
+ if (fs20.existsSync(credentialsPath)) {
189645
+ fs20.unlinkSync(credentialsPath);
189019
189646
  }
189020
189647
  }
189021
189648
  function isLoggedIn() {
@@ -189837,14 +190464,14 @@ ${errorMsg}`
189837
190464
  ), phase === "error" && /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text5, { color: "red" }, "Error: ", error)), phase === "success" && /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Text5, { color: "green" }, "Deployment successful!"), deployment?.publicUrls && Object.keys(deployment.publicUrls).length > 0 && /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Text5, { bold: true }, "Public URLs:"), Object.entries(deployment.publicUrls).map(([name, url]) => /* @__PURE__ */ React5.createElement(Text5, { key: name }, " ", name, ": ", /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, url))))));
189838
190465
  }
189839
190466
  async function deployCommand(environment, options2) {
189840
- const configPath = path17.join(process.cwd(), "specific.hcl");
189841
- if (!fs20.existsSync(configPath)) {
190467
+ const configPath = path19.join(process.cwd(), "specific.hcl");
190468
+ if (!fs21.existsSync(configPath)) {
189842
190469
  console.error("Error: No specific.hcl found in current directory");
189843
190470
  process.exit(1);
189844
190471
  }
189845
190472
  let config;
189846
190473
  try {
189847
- const hcl = fs20.readFileSync(configPath, "utf-8");
190474
+ const hcl = fs21.readFileSync(configPath, "utf-8");
189848
190475
  config = await parseConfig(hcl);
189849
190476
  } catch (err) {
189850
190477
  console.error(
@@ -189868,9 +190495,9 @@ async function deployCommand(environment, options2) {
189868
190495
 
189869
190496
  // src/commands/exec.tsx
189870
190497
  import { spawn as spawn6 } from "child_process";
189871
- import * as fs21 from "fs";
189872
- import * as path18 from "path";
189873
- async function execCommand(serviceName, command) {
190498
+ import * as fs22 from "fs";
190499
+ import * as path20 from "path";
190500
+ async function execCommand(serviceName, command, instanceKey = "default") {
189874
190501
  if (command.length === 0) {
189875
190502
  console.error(
189876
190503
  "Error: No command provided. Usage: specific exec <service> -- <command>"
@@ -189897,14 +190524,14 @@ async function execCommand(serviceName, command) {
189897
190524
  }
189898
190525
  }
189899
190526
  };
189900
- const configPath = path18.join(process.cwd(), "specific.hcl");
189901
- if (!fs21.existsSync(configPath)) {
190527
+ const configPath = path20.join(process.cwd(), "specific.hcl");
190528
+ if (!fs22.existsSync(configPath)) {
189902
190529
  console.error("Error: No specific.hcl found in current directory");
189903
190530
  process.exit(1);
189904
190531
  }
189905
190532
  let config;
189906
190533
  try {
189907
- const hcl = fs21.readFileSync(configPath, "utf-8");
190534
+ const hcl = fs22.readFileSync(configPath, "utf-8");
189908
190535
  config = await parseConfig(hcl);
189909
190536
  } catch (err) {
189910
190537
  console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
@@ -189921,7 +190548,7 @@ async function execCommand(serviceName, command) {
189921
190548
  const required = findRequiredResources(service);
189922
190549
  let resources = /* @__PURE__ */ new Map();
189923
190550
  const hasRequiredResources = required.postgres.length > 0 || required.redis.length > 0 || required.storage.length > 0;
189924
- stateManager = new InstanceStateManager(process.cwd());
190551
+ stateManager = new InstanceStateManager(process.cwd(), instanceKey);
189925
190552
  await stateManager.cleanStaleState();
189926
190553
  const existingInstances = await stateManager.getExistingInstances();
189927
190554
  if (hasRequiredResources) {
@@ -189976,7 +190603,7 @@ async function execCommand(serviceName, command) {
189976
190603
  storage: required.storage
189977
190604
  },
189978
190605
  stateManager,
189979
- dataDir: ".specific/data",
190606
+ dataDir: `.specific/keys/${instanceKey}/data`,
189980
190607
  portAllocator: new PortAllocator(),
189981
190608
  callbacks: {
189982
190609
  log: (msg) => console.error(msg)
@@ -190041,8 +190668,8 @@ async function execCommand(serviceName, command) {
190041
190668
 
190042
190669
  // src/commands/psql.tsx
190043
190670
  import { spawn as spawn7 } from "child_process";
190044
- async function psqlCommand(databaseName) {
190045
- const stateManager = new InstanceStateManager(process.cwd());
190671
+ async function psqlCommand(databaseName, instanceKey = "default") {
190672
+ const stateManager = new InstanceStateManager(process.cwd(), instanceKey);
190046
190673
  await stateManager.cleanStaleState();
190047
190674
  const existingInstances = await stateManager.getExistingInstances();
190048
190675
  if (!existingInstances) {
@@ -190098,41 +190725,85 @@ async function psqlCommand(databaseName) {
190098
190725
  import React6, { useState as useState6, useEffect as useEffect6 } from "react";
190099
190726
  import { render as render6, Text as Text6, Box as Box6 } from "ink";
190100
190727
  import Spinner6 from "ink-spinner";
190101
- import * as fs22 from "fs";
190102
- import * as path19 from "path";
190103
- function CleanUI() {
190728
+ import * as fs23 from "fs";
190729
+ import * as path21 from "path";
190730
+ function CleanUI({ instanceKey }) {
190104
190731
  const [state, setState] = useState6({ status: "checking" });
190105
190732
  useEffect6(() => {
190106
190733
  async function clean() {
190107
190734
  const projectRoot = process.cwd();
190108
- const specificDir = path19.join(projectRoot, ".specific");
190109
- if (!fs22.existsSync(specificDir)) {
190735
+ const specificDir = path21.join(projectRoot, ".specific");
190736
+ if (!fs23.existsSync(specificDir)) {
190110
190737
  setState({ status: "nothing" });
190111
190738
  return;
190112
190739
  }
190113
- const stateManager = new InstanceStateManager(projectRoot);
190114
- const existingInstances = await stateManager.getExistingInstances();
190115
- if (existingInstances) {
190116
- setState({
190117
- status: "error",
190118
- error: `Cannot clean while 'specific dev' is running (PID ${existingInstances.owner.pid}). Please stop it first.`
190119
- });
190120
- return;
190121
- }
190122
- await stateManager.cleanStaleState();
190123
- setState({ status: "cleaning" });
190124
- try {
190125
- fs22.rmSync(specificDir, { recursive: true, force: true });
190126
- setState({ status: "success" });
190127
- } catch (err) {
190128
- setState({
190129
- status: "error",
190130
- error: err instanceof Error ? err.message : String(err)
190131
- });
190740
+ if (instanceKey) {
190741
+ const keyDir = path21.join(specificDir, "keys", instanceKey);
190742
+ if (!fs23.existsSync(keyDir)) {
190743
+ setState({ status: "nothing" });
190744
+ return;
190745
+ }
190746
+ const stateManager = new InstanceStateManager(projectRoot, instanceKey);
190747
+ const existingInstances = await stateManager.getExistingInstances();
190748
+ if (existingInstances) {
190749
+ setState({
190750
+ status: "error",
190751
+ error: `Cannot clean while 'specific dev' is running for key "${instanceKey}" (PID ${existingInstances.owner.pid}). Please stop it first.`
190752
+ });
190753
+ return;
190754
+ }
190755
+ await stateManager.cleanStaleState();
190756
+ setState({ status: "cleaning" });
190757
+ try {
190758
+ fs23.rmSync(keyDir, { recursive: true, force: true });
190759
+ setState({ status: "success" });
190760
+ } catch (err) {
190761
+ setState({
190762
+ status: "error",
190763
+ error: err instanceof Error ? err.message : String(err)
190764
+ });
190765
+ }
190766
+ } else {
190767
+ const keysDir = path21.join(specificDir, "keys");
190768
+ if (fs23.existsSync(keysDir)) {
190769
+ const keys = fs23.readdirSync(keysDir).filter(
190770
+ (f) => fs23.statSync(path21.join(keysDir, f)).isDirectory()
190771
+ );
190772
+ for (const key of keys) {
190773
+ const stateManager2 = new InstanceStateManager(projectRoot, key);
190774
+ const existingInstances2 = await stateManager2.getExistingInstances();
190775
+ if (existingInstances2) {
190776
+ setState({
190777
+ status: "error",
190778
+ error: `Cannot clean while 'specific dev' is running for key "${key}" (PID ${existingInstances2.owner.pid}). Please stop it first.`
190779
+ });
190780
+ return;
190781
+ }
190782
+ }
190783
+ }
190784
+ const stateManager = new InstanceStateManager(projectRoot);
190785
+ const existingInstances = await stateManager.getExistingInstances();
190786
+ if (existingInstances) {
190787
+ setState({
190788
+ status: "error",
190789
+ error: `Cannot clean while 'specific dev' is running (PID ${existingInstances.owner.pid}). Please stop it first.`
190790
+ });
190791
+ return;
190792
+ }
190793
+ setState({ status: "cleaning" });
190794
+ try {
190795
+ fs23.rmSync(specificDir, { recursive: true, force: true });
190796
+ setState({ status: "success" });
190797
+ } catch (err) {
190798
+ setState({
190799
+ status: "error",
190800
+ error: err instanceof Error ? err.message : String(err)
190801
+ });
190802
+ }
190132
190803
  }
190133
190804
  }
190134
190805
  clean();
190135
- }, []);
190806
+ }, [instanceKey]);
190136
190807
  if (state.status === "checking") {
190137
190808
  return /* @__PURE__ */ React6.createElement(Box6, null, /* @__PURE__ */ React6.createElement(Text6, { color: "blue" }, /* @__PURE__ */ React6.createElement(Spinner6, { type: "dots" })), /* @__PURE__ */ React6.createElement(Text6, null, " Checking for running instances..."));
190138
190809
  }
@@ -190143,18 +190814,24 @@ function CleanUI() {
190143
190814
  return /* @__PURE__ */ React6.createElement(Text6, { color: "red" }, "Error: ", state.error);
190144
190815
  }
190145
190816
  if (state.status === "nothing") {
190146
- return /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, "Nothing to clean (.specific directory does not exist)");
190817
+ const target2 = instanceKey ? `key "${instanceKey}"` : ".specific directory";
190818
+ return /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, "Nothing to clean (", target2, " does not exist)");
190147
190819
  }
190148
- return /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, "Cleaned .specific directory");
190820
+ const target = instanceKey ? `key "${instanceKey}"` : ".specific directory";
190821
+ return /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, "Cleaned ", target);
190149
190822
  }
190150
- function cleanCommand() {
190151
- render6(/* @__PURE__ */ React6.createElement(CleanUI, null));
190823
+ function cleanCommand(instanceKey) {
190824
+ if (instanceKey) {
190825
+ render6(/* @__PURE__ */ React6.createElement(CleanUI, { instanceKey }));
190826
+ } else {
190827
+ render6(/* @__PURE__ */ React6.createElement(CleanUI, null));
190828
+ }
190152
190829
  }
190153
190830
 
190154
190831
  // src/commands/secrets.tsx
190155
190832
  import React7, { useState as useState7, useEffect as useEffect7 } from "react";
190156
190833
  import { render as render7, Text as Text7, Box as Box7, useInput as useInput4, useApp as useApp5 } from "ink";
190157
- import * as fs23 from "fs";
190834
+ import * as fs24 from "fs";
190158
190835
  var HEADER_COMMENT = "# Do not commit this file - it contains secrets\n";
190159
190836
  function SetSecretUI({ secretName }) {
190160
190837
  const { exit } = useApp5();
@@ -190173,8 +190850,8 @@ function SetSecretUI({ secretName }) {
190173
190850
  const hclLine = `${secretName} = "${escapedValue}"`;
190174
190851
  let content = "";
190175
190852
  let hasHeader = false;
190176
- if (fs23.existsSync(SECRETS_FILE)) {
190177
- content = fs23.readFileSync(SECRETS_FILE, "utf-8");
190853
+ if (fs24.existsSync(SECRETS_FILE)) {
190854
+ content = fs24.readFileSync(SECRETS_FILE, "utf-8");
190178
190855
  hasHeader = content.startsWith("#");
190179
190856
  const lines = content.split("\n");
190180
190857
  const newLines = [];
@@ -190189,7 +190866,7 @@ function SetSecretUI({ secretName }) {
190189
190866
  }
190190
190867
  }
190191
190868
  if (found) {
190192
- fs23.writeFileSync(SECRETS_FILE, newLines.join("\n"));
190869
+ fs24.writeFileSync(SECRETS_FILE, newLines.join("\n"));
190193
190870
  setDone(true);
190194
190871
  return;
190195
190872
  }
@@ -190202,7 +190879,7 @@ function SetSecretUI({ secretName }) {
190202
190879
  }
190203
190880
  newContent += `${hclLine}
190204
190881
  `;
190205
- fs23.writeFileSync(SECRETS_FILE, newContent);
190882
+ fs24.writeFileSync(SECRETS_FILE, newContent);
190206
190883
  setDone(true);
190207
190884
  } catch (err) {
190208
190885
  setError(err instanceof Error ? err.message : String(err));
@@ -190262,7 +190939,7 @@ async function secretsCommand(action, secretName) {
190262
190939
  // src/commands/config.tsx
190263
190940
  import React8, { useState as useState8, useEffect as useEffect8 } from "react";
190264
190941
  import { render as render8, Text as Text8, Box as Box8, useInput as useInput5, useApp as useApp6 } from "ink";
190265
- import * as fs24 from "fs";
190942
+ import * as fs25 from "fs";
190266
190943
  var HEADER_COMMENT2 = "# Configuration values for this project\n# These values override defaults defined in specific.hcl\n";
190267
190944
  function SetConfigUI({ configName, initialValue }) {
190268
190945
  const { exit } = useApp6();
@@ -190281,8 +190958,8 @@ function SetConfigUI({ configName, initialValue }) {
190281
190958
  const hclLine = `${configName} = "${escapedValue}"`;
190282
190959
  let content = "";
190283
190960
  let hasHeader = false;
190284
- if (fs24.existsSync(CONFIG_FILE)) {
190285
- content = fs24.readFileSync(CONFIG_FILE, "utf-8");
190961
+ if (fs25.existsSync(CONFIG_FILE)) {
190962
+ content = fs25.readFileSync(CONFIG_FILE, "utf-8");
190286
190963
  hasHeader = content.startsWith("#");
190287
190964
  const lines = content.split("\n");
190288
190965
  const newLines = [];
@@ -190297,7 +190974,7 @@ function SetConfigUI({ configName, initialValue }) {
190297
190974
  }
190298
190975
  }
190299
190976
  if (found) {
190300
- fs24.writeFileSync(CONFIG_FILE, newLines.join("\n"));
190977
+ fs25.writeFileSync(CONFIG_FILE, newLines.join("\n"));
190301
190978
  setDone(true);
190302
190979
  return;
190303
190980
  }
@@ -190310,7 +190987,7 @@ function SetConfigUI({ configName, initialValue }) {
190310
190987
  }
190311
190988
  newContent += `${hclLine}
190312
190989
  `;
190313
- fs24.writeFileSync(CONFIG_FILE, newContent);
190990
+ fs25.writeFileSync(CONFIG_FILE, newContent);
190314
190991
  setDone(true);
190315
190992
  } catch (err) {
190316
190993
  setError(err instanceof Error ? err.message : String(err));
@@ -190349,8 +191026,8 @@ async function configSetCommand(configName, configValue) {
190349
191026
  const hclLine = `${configName} = "${escapedValue}"`;
190350
191027
  let content = "";
190351
191028
  let hasHeader = false;
190352
- if (fs24.existsSync(CONFIG_FILE)) {
190353
- content = fs24.readFileSync(CONFIG_FILE, "utf-8");
191029
+ if (fs25.existsSync(CONFIG_FILE)) {
191030
+ content = fs25.readFileSync(CONFIG_FILE, "utf-8");
190354
191031
  hasHeader = content.startsWith("#");
190355
191032
  const lines = content.split("\n");
190356
191033
  const newLines = [];
@@ -190365,7 +191042,7 @@ async function configSetCommand(configName, configValue) {
190365
191042
  }
190366
191043
  }
190367
191044
  if (found) {
190368
- fs24.writeFileSync(CONFIG_FILE, newLines.join("\n"));
191045
+ fs25.writeFileSync(CONFIG_FILE, newLines.join("\n"));
190369
191046
  console.log(`Config '${configName}' saved to ${CONFIG_FILE}`);
190370
191047
  return;
190371
191048
  }
@@ -190378,7 +191055,7 @@ async function configSetCommand(configName, configValue) {
190378
191055
  }
190379
191056
  newContent += `${hclLine}
190380
191057
  `;
190381
- fs24.writeFileSync(CONFIG_FILE, newContent);
191058
+ fs25.writeFileSync(CONFIG_FILE, newContent);
190382
191059
  console.log(`Config '${configName}' saved to ${CONFIG_FILE}`);
190383
191060
  } catch (err) {
190384
191061
  console.error("Error:", err instanceof Error ? err.message : String(err));
@@ -190497,20 +191174,29 @@ function logoutCommand() {
190497
191174
  var program = new Command();
190498
191175
  var env = "production";
190499
191176
  var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
190500
- program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.37").enablePositionalOptions();
191177
+ program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.38").enablePositionalOptions();
190501
191178
  program.command("init").description("Initialize project for use with a coding agent").action(initCommand);
190502
191179
  program.command("docs [topic]").description("Fetch LLM-optimized documentation").action(docsCommand);
190503
191180
  program.command("check").description("Validate specific.hcl configuration").action(checkCommand);
190504
- program.command("dev").description("Start local development environment").action(devCommand);
191181
+ program.command("dev").description("Start local development environment").option("-k, --key <key>", "Namespace for isolated dev environment (auto-detected from git worktree if not specified)").action((options2) => {
191182
+ const key = options2.key ?? getDefaultKey();
191183
+ devCommand(key);
191184
+ });
190505
191185
  program.command("deploy [environment]").description("Deploy to Specific infrastructure").option("--skip-build-test", "Skip local build testing before deploy").action((environment, options2) => {
190506
191186
  deployCommand(environment, options2);
190507
191187
  });
190508
- program.command("exec <service> [args...]").description("Run a one-off command with service environment").passThroughOptions().action(async (service, args) => {
191188
+ program.command("exec <service> [args...]").description("Run a one-off command with service environment").option("-k, --key <key>", "Dev environment namespace (auto-detected from git worktree if not specified)").passThroughOptions().action(async (service, args, options2) => {
190509
191189
  const filteredArgs = args[0] === "--" ? args.slice(1) : args;
190510
- await execCommand(service, filteredArgs);
191190
+ const key = options2.key ?? getDefaultKey();
191191
+ await execCommand(service, filteredArgs, key);
191192
+ });
191193
+ program.command("psql [database]").description("Connect to a running Postgres database").option("-k, --key <key>", "Dev environment namespace (auto-detected from git worktree if not specified)").action((database, options2) => {
191194
+ const key = options2.key ?? getDefaultKey();
191195
+ psqlCommand(database, key);
191196
+ });
191197
+ program.command("clean").description("Remove .specific directory for a clean slate").option("-k, --key <key>", "Clean only the specified dev environment key").action((options2) => {
191198
+ cleanCommand(options2.key);
190511
191199
  });
190512
- program.command("psql [database]").description("Connect to a running Postgres database").action(psqlCommand);
190513
- program.command("clean").description("Remove .specific directory for a clean slate").action(cleanCommand);
190514
191200
  program.command("secrets [action] [name]").description("Manage secrets").action(secretsCommand);
190515
191201
  program.command("config [action] [name] [value]").description("Manage configuration values").action(configCommand);
190516
191202
  program.command("login").description("Log in to Specific").action(loginCommand);