@specific.dev/cli 0.1.48 → 0.1.50

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 (37) 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 +1 -1
  4. package/dist/admin/__next._full.txt +1 -1
  5. package/dist/admin/__next._head.txt +1 -1
  6. package/dist/admin/__next._index.txt +1 -1
  7. package/dist/admin/__next._tree.txt +1 -1
  8. package/dist/admin/_not-found/__next._full.txt +1 -1
  9. package/dist/admin/_not-found/__next._head.txt +1 -1
  10. package/dist/admin/_not-found/__next._index.txt +1 -1
  11. package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +1 -1
  12. package/dist/admin/_not-found/__next._not-found.txt +1 -1
  13. package/dist/admin/_not-found/__next._tree.txt +1 -1
  14. package/dist/admin/_not-found/index.html +1 -1
  15. package/dist/admin/_not-found/index.txt +1 -1
  16. package/dist/admin/databases/__next._full.txt +1 -1
  17. package/dist/admin/databases/__next._head.txt +1 -1
  18. package/dist/admin/databases/__next._index.txt +1 -1
  19. package/dist/admin/databases/__next._tree.txt +1 -1
  20. package/dist/admin/databases/__next.databases.__PAGE__.txt +1 -1
  21. package/dist/admin/databases/__next.databases.txt +1 -1
  22. package/dist/admin/databases/index.html +1 -1
  23. package/dist/admin/databases/index.txt +1 -1
  24. package/dist/admin/index.html +1 -1
  25. package/dist/admin/index.txt +1 -1
  26. package/dist/cli.js +1814 -739
  27. package/dist/docs/builds.md +22 -0
  28. package/dist/docs/index.md +3 -0
  29. package/dist/docs/postgres/reshape/actions.md +90 -0
  30. package/dist/docs/postgres/reshape/index.md +149 -0
  31. package/dist/docs/postgres.md +35 -1
  32. package/dist/docs/services.md +62 -8
  33. package/dist/postinstall.js +141 -0
  34. package/package.json +3 -2
  35. /package/dist/admin/_next/static/{dyH4SZNKyN31L1iV-yPZA → o2Qo92jA0gWbtB1ZWKQFF}/_buildManifest.js +0 -0
  36. /package/dist/admin/_next/static/{dyH4SZNKyN31L1iV-yPZA → o2Qo92jA0gWbtB1ZWKQFF}/_clientMiddlewareManifest.json +0 -0
  37. /package/dist/admin/_next/static/{dyH4SZNKyN31L1iV-yPZA → o2Qo92jA0gWbtB1ZWKQFF}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -238,15 +238,15 @@ var init_wsl_utils = __esm({
238
238
  const { stdout } = await executePowerShell(command, { powerShellPath: psPath });
239
239
  return stdout.trim();
240
240
  };
241
- convertWslPathToWindows = async (path23) => {
242
- if (/^[a-z]+:\/\//i.test(path23)) {
243
- return path23;
241
+ convertWslPathToWindows = async (path26) => {
242
+ if (/^[a-z]+:\/\//i.test(path26)) {
243
+ return path26;
244
244
  }
245
245
  try {
246
- const { stdout } = await execFile2("wslpath", ["-aw", path23], { encoding: "utf8" });
246
+ const { stdout } = await execFile2("wslpath", ["-aw", path26], { encoding: "utf8" });
247
247
  return stdout.trim();
248
248
  } catch {
249
- return path23;
249
+ return path26;
250
250
  }
251
251
  };
252
252
  }
@@ -637,19 +637,19 @@ var init_open = __esm({
637
637
  }
638
638
  const subprocess = childProcess3.spawn(command, cliArguments, childProcessOptions);
639
639
  if (options2.wait) {
640
- return new Promise((resolve7, reject) => {
640
+ return new Promise((resolve9, reject) => {
641
641
  subprocess.once("error", reject);
642
642
  subprocess.once("close", (exitCode) => {
643
643
  if (!options2.allowNonzeroExitCode && exitCode !== 0) {
644
644
  reject(new Error(`Exited with code ${exitCode}`));
645
645
  return;
646
646
  }
647
- resolve7(subprocess);
647
+ resolve9(subprocess);
648
648
  });
649
649
  });
650
650
  }
651
651
  if (isFallbackAttempt) {
652
- return new Promise((resolve7, reject) => {
652
+ return new Promise((resolve9, reject) => {
653
653
  subprocess.once("error", reject);
654
654
  subprocess.once("spawn", () => {
655
655
  subprocess.once("close", (exitCode) => {
@@ -659,17 +659,17 @@ var init_open = __esm({
659
659
  return;
660
660
  }
661
661
  subprocess.unref();
662
- resolve7(subprocess);
662
+ resolve9(subprocess);
663
663
  });
664
664
  });
665
665
  });
666
666
  }
667
667
  subprocess.unref();
668
- return new Promise((resolve7, reject) => {
668
+ return new Promise((resolve9, reject) => {
669
669
  subprocess.once("error", reject);
670
670
  subprocess.once("spawn", () => {
671
671
  subprocess.off("error", reject);
672
- resolve7(subprocess);
672
+ resolve9(subprocess);
673
673
  });
674
674
  });
675
675
  };
@@ -754,8 +754,8 @@ var require_dist = __commonJS({
754
754
  var $global, $module, $NaN = NaN;
755
755
  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");
756
756
  if ("undefined" != typeof module && ($module = module), !$global.fs && $global.require) try {
757
- var fs25 = $global.require("fs");
758
- "object" == typeof fs25 && null !== fs25 && 0 !== Object.keys(fs25).length && ($global.fs = fs25);
757
+ var fs28 = $global.require("fs");
758
+ "object" == typeof fs28 && null !== fs28 && 0 !== Object.keys(fs28).length && ($global.fs = fs28);
759
759
  } catch (e) {
760
760
  }
761
761
  if (!$global.fs) {
@@ -183055,13 +183055,13 @@ function removeCA() {
183055
183055
  }
183056
183056
  }
183057
183057
  function installCAToTrustStore(certPath) {
183058
- const platform5 = os.platform();
183059
- if (platform5 === "darwin") {
183058
+ const platform4 = os.platform();
183059
+ if (platform4 === "darwin") {
183060
183060
  execSync(
183061
183061
  `sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "${certPath}"`,
183062
183062
  { stdio: "inherit" }
183063
183063
  );
183064
- } else if (platform5 === "linux") {
183064
+ } else if (platform4 === "linux") {
183065
183065
  if (fs.existsSync("/usr/local/share/ca-certificates")) {
183066
183066
  execSync(
183067
183067
  `sudo cp "${certPath}" /usr/local/share/ca-certificates/specific-local-ca.crt`,
@@ -183078,7 +183078,7 @@ function installCAToTrustStore(certPath) {
183078
183078
  throw new Error("Could not detect Linux certificate trust mechanism");
183079
183079
  }
183080
183080
  } else {
183081
- throw new Error(`Unsupported platform: ${platform5}`);
183081
+ throw new Error(`Unsupported platform: ${platform4}`);
183082
183082
  }
183083
183083
  }
183084
183084
  function generateCertificate(domain, keys = []) {
@@ -183682,7 +183682,7 @@ async function pollUntilToken(deviceAuth, isCancelled) {
183682
183682
  return null;
183683
183683
  }
183684
183684
  function sleep(ms) {
183685
- return new Promise((resolve7) => setTimeout(resolve7, ms));
183685
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
183686
183686
  }
183687
183687
 
183688
183688
  // src/lib/auth/login.tsx
@@ -183699,7 +183699,7 @@ function LoginUI({
183699
183699
  return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, isReauthentication && /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "Session expired. Please log in again."), /* @__PURE__ */ React.createElement(Text, { bold: true }, "Log in to Specific"), state.phase === "waiting-for-browser" ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, null, "Your authentication code:", " ", /* @__PURE__ */ React.createElement(Text, { color: "cyan", bold: true }, state.userCode))), /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "blue" }, /* @__PURE__ */ React.createElement(Spinner, { type: "dots" })), /* @__PURE__ */ React.createElement(Text, null, " Waiting for authentication in browser...")), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "If the browser didn't open, visit: ", state.verificationUri)) : /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "blue" }, /* @__PURE__ */ React.createElement(Spinner, { type: "dots" })), /* @__PURE__ */ React.createElement(Text, null, " Initiating login...")));
183700
183700
  }
183701
183701
  function performLogin(options2 = {}) {
183702
- return new Promise((resolve7) => {
183702
+ return new Promise((resolve9) => {
183703
183703
  let currentState = { phase: "initiating" };
183704
183704
  let flowHandle;
183705
183705
  const instance = render(
@@ -183739,14 +183739,14 @@ function performLogin(options2 = {}) {
183739
183739
  process.off("SIGINT", handleExit);
183740
183740
  process.off("SIGTERM", handleExit);
183741
183741
  instance.unmount();
183742
- resolve7({ success: true, userEmail: newState.email });
183742
+ resolve9({ success: true, userEmail: newState.email });
183743
183743
  }, 100);
183744
183744
  } else if (newState.phase === "error") {
183745
183745
  setTimeout(() => {
183746
183746
  process.off("SIGINT", handleExit);
183747
183747
  process.off("SIGTERM", handleExit);
183748
183748
  instance.unmount();
183749
- resolve7({ success: false, error: new Error(newState.message) });
183749
+ resolve9({ success: false, error: new Error(newState.message) });
183750
183750
  }, 100);
183751
183751
  }
183752
183752
  }
@@ -183873,7 +183873,7 @@ function trackEvent(event, properties) {
183873
183873
  event,
183874
183874
  properties: {
183875
183875
  ...properties,
183876
- cli_version: "0.1.48",
183876
+ cli_version: "0.1.50",
183877
183877
  platform: process.platform,
183878
183878
  node_version: process.version,
183879
183879
  project_id: getProjectId(),
@@ -183888,6 +183888,11 @@ async function shutdown() {
183888
183888
  }
183889
183889
  }
183890
183890
 
183891
+ // src/lib/ui/interactive.ts
183892
+ function isInteractive() {
183893
+ return process.stdin.isTTY === true && process.stdout.isTTY === true;
183894
+ }
183895
+
183891
183896
  // src/commands/init.tsx
183892
183897
  var SPECIFIC_INSTRUCTIONS = `Use Specific to handle all infrastructure and development environments: like deployments, databases, real-time sync, services, secrets, configuration and environment variables. Run \`specific docs\` to learn how to use Specific. ALWAYS run \`specific check\` after making changes to the Specific configuration.`;
183893
183898
  var CURSOR_MDC_CONTENT = `---
@@ -184156,40 +184161,169 @@ function InitUI() {
184156
184161
  " "
184157
184162
  ))));
184158
184163
  }
184159
- function initCommand() {
184160
- render2(/* @__PURE__ */ React2.createElement(InitUI, null));
184164
+ var VALID_AGENT_IDS = options.map((o) => o.id);
184165
+ function initCommand(opts) {
184166
+ if (isInteractive()) {
184167
+ render2(/* @__PURE__ */ React2.createElement(InitUI, null));
184168
+ return;
184169
+ }
184170
+ if (!opts.agent || opts.agent.length === 0) {
184171
+ console.error(
184172
+ `Error: --agent is required in non-interactive environments.
184173
+ Usage: specific init --agent cursor claude codex
184174
+ Valid agents: ${VALID_AGENT_IDS.join(", ")}`
184175
+ );
184176
+ process.exit(1);
184177
+ }
184178
+ const invalid = opts.agent.filter((a) => !VALID_AGENT_IDS.includes(a));
184179
+ if (invalid.length > 0) {
184180
+ console.error(
184181
+ `Error: Unknown agent(s): ${invalid.join(", ")}
184182
+ Valid agents: ${VALID_AGENT_IDS.join(", ")}`
184183
+ );
184184
+ process.exit(1);
184185
+ }
184186
+ const checked = {};
184187
+ for (const agent of opts.agent) {
184188
+ checked[agent] = true;
184189
+ }
184190
+ const result = configureAgents(checked);
184191
+ trackEvent("project_initialized", {
184192
+ agents: opts.agent
184193
+ });
184194
+ const selectedAgents = opts.agent.length;
184195
+ const agentChanges = result.agents.filesCreated.length > 0 || result.agents.filesModified.length > 0;
184196
+ console.log("\u2713 Coding agents configured");
184197
+ if (result.agents.filesCreated.length > 0) {
184198
+ console.log(` Created: ${result.agents.filesCreated.join(", ")}`);
184199
+ }
184200
+ if (result.agents.filesModified.length > 0) {
184201
+ console.log(` Modified: ${result.agents.filesModified.join(", ")}`);
184202
+ }
184203
+ if (!agentChanges && selectedAgents > 0) {
184204
+ console.log(" No changes needed (files already configured)");
184205
+ }
184206
+ if (result.git) {
184207
+ const gitChanges = result.git.filesCreated.length > 0 || result.git.filesModified.length > 0;
184208
+ console.log("\u2713 Git configured");
184209
+ if (result.git.filesCreated.length > 0) {
184210
+ console.log(` Created: ${result.git.filesCreated.join(", ")}`);
184211
+ }
184212
+ if (result.git.filesModified.length > 0) {
184213
+ console.log(` Modified: ${result.git.filesModified.join(", ")}`);
184214
+ }
184215
+ if (!gitChanges) {
184216
+ console.log(" No changes needed (.gitignore already configured)");
184217
+ }
184218
+ }
184219
+ if (!caFilesExist()) {
184220
+ console.log(
184221
+ "\u26A0 TLS certificates not installed (run `specific init` in a terminal to set up)"
184222
+ );
184223
+ }
184224
+ if (result.showManualInstructions) {
184225
+ console.log("\n\u2713 Manual configuration selected");
184226
+ console.log("\n Add this to your agent's system prompt:");
184227
+ console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
184228
+ console.log(` ${SPECIFIC_INSTRUCTIONS}`);
184229
+ console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
184230
+ console.log(
184231
+ "\n We also recommend allowing your agent to run `specific docs *`"
184232
+ );
184233
+ console.log(" and `specific check *` without confirmation.");
184234
+ }
184161
184235
  }
184162
184236
 
184163
184237
  // src/commands/docs.tsx
184164
- import { readFileSync as readFileSync5, existsSync as existsSync5 } from "fs";
184165
- import { join as join6, dirname as dirname2 } from "path";
184238
+ import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
184239
+ import { join as join7, dirname as dirname2 } from "path";
184166
184240
  import { fileURLToPath as fileURLToPath2 } from "url";
184241
+
184242
+ // src/lib/beta/registry.ts
184243
+ var BETA_REGISTRY = [
184244
+ {
184245
+ name: "reshape",
184246
+ description: "Zero-downtime database schema migrations using Reshape"
184247
+ }
184248
+ ];
184249
+
184250
+ // src/lib/beta/storage.ts
184251
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync5, mkdirSync as mkdirSync6 } from "fs";
184252
+ import { join as join6 } from "path";
184253
+ var BETAS_FILE = ".specific/betas.json";
184254
+ function loadEnabledBetas(projectDir = process.cwd()) {
184255
+ const filePath = join6(projectDir, BETAS_FILE);
184256
+ if (!existsSync5(filePath)) {
184257
+ return [];
184258
+ }
184259
+ try {
184260
+ const content = readFileSync5(filePath, "utf-8");
184261
+ const data = JSON.parse(content);
184262
+ return data.enabled ?? [];
184263
+ } catch {
184264
+ return [];
184265
+ }
184266
+ }
184267
+ function enableBeta(name, projectDir = process.cwd()) {
184268
+ const enabled = loadEnabledBetas(projectDir);
184269
+ if (!enabled.includes(name)) {
184270
+ enabled.push(name);
184271
+ saveBetas(enabled, projectDir);
184272
+ }
184273
+ }
184274
+ function disableBeta(name, projectDir = process.cwd()) {
184275
+ const enabled = loadEnabledBetas(projectDir).filter((b) => b !== name);
184276
+ saveBetas(enabled, projectDir);
184277
+ }
184278
+ function saveBetas(enabled, projectDir) {
184279
+ const specificDir = join6(projectDir, ".specific");
184280
+ if (!existsSync5(specificDir)) {
184281
+ mkdirSync6(specificDir, { recursive: true });
184282
+ }
184283
+ const data = { enabled };
184284
+ writeFileSync5(
184285
+ join6(projectDir, BETAS_FILE),
184286
+ JSON.stringify(data, null, 2) + "\n"
184287
+ );
184288
+ }
184289
+
184290
+ // src/commands/docs.tsx
184167
184291
  var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
184168
- var docsDir = join6(__dirname2, "docs");
184169
- function docsCommand(path23) {
184170
- const docPath = resolveDocPath(path23);
184292
+ var docsDir = join7(__dirname2, "docs");
184293
+ function docsCommand(path26) {
184294
+ const docPath = resolveDocPath(path26);
184171
184295
  if (!docPath) {
184172
184296
  console.error(
184173
- `Documentation not found: ${path23 || "index"}
184297
+ `Documentation not found: ${path26 || "index"}
184174
184298
 
184175
184299
  Run 'specific docs' to see available topics.`
184176
184300
  );
184177
184301
  process.exit(1);
184178
184302
  }
184179
- const content = readFileSync5(docPath, "utf-8");
184303
+ const enabledBetas = new Set(loadEnabledBetas());
184304
+ let content = readFileSync6(docPath, "utf-8");
184305
+ content = filterBetaTags(content, enabledBetas);
184180
184306
  console.log(content);
184181
184307
  }
184182
- function resolveDocPath(path23) {
184183
- if (!path23) {
184184
- const indexPath2 = join6(docsDir, "index.md");
184185
- return existsSync5(indexPath2) ? indexPath2 : null;
184308
+ function filterBetaTags(content, enabledBetas) {
184309
+ return content.replace(
184310
+ /<!-- beta:(\w[\w-]*) -->\n?([\s\S]*?)<!-- \/beta:\1 -->\n?/g,
184311
+ (_, betaName, betaContent) => {
184312
+ return enabledBetas.has(betaName) ? betaContent : "";
184313
+ }
184314
+ );
184315
+ }
184316
+ function resolveDocPath(path26) {
184317
+ if (!path26) {
184318
+ const indexPath2 = join7(docsDir, "index.md");
184319
+ return existsSync6(indexPath2) ? indexPath2 : null;
184186
184320
  }
184187
- const directPath = join6(docsDir, `${path23}.md`);
184188
- if (existsSync5(directPath)) {
184321
+ const directPath = join7(docsDir, `${path26}.md`);
184322
+ if (existsSync6(directPath)) {
184189
184323
  return directPath;
184190
184324
  }
184191
- const indexPath = join6(docsDir, path23, "index.md");
184192
- if (existsSync5(indexPath)) {
184325
+ const indexPath = join7(docsDir, path26, "index.md");
184326
+ if (existsSync6(indexPath)) {
184193
184327
  return indexPath;
184194
184328
  }
184195
184329
  return null;
@@ -184199,8 +184333,9 @@ function resolveDocPath(path23) {
184199
184333
  import React3, { useState as useState2, useEffect as useEffect2 } from "react";
184200
184334
  import { render as render3, Text as Text3, Box as Box3 } from "ink";
184201
184335
  import Spinner3 from "ink-spinner";
184202
- import * as fs11 from "fs";
184203
- import * as path7 from "path";
184336
+ import * as fs12 from "fs";
184337
+ import * as path8 from "path";
184338
+ import { execFile as execFile7 } from "child_process";
184204
184339
 
184205
184340
  // node_modules/.pnpm/@specific+config@file+..+config/node_modules/@specific/config/dist/parser.js
184206
184341
  var import_hcl2_json_parser = __toESM(require_dist(), 1);
@@ -184283,7 +184418,7 @@ function parseReferenceString(str) {
184283
184418
  attribute: "port"
184284
184419
  };
184285
184420
  }
184286
- const serviceEndpointMatch = str.match(/^service\.(\w+)\.endpoint\.(\w+)\.(url|host|port)$/);
184421
+ const serviceEndpointMatch = str.match(/^service\.(\w+)\.endpoint\.(\w+)\.(url|private_url|host|port|public_url)$/);
184287
184422
  if (serviceEndpointMatch && serviceEndpointMatch[1] && serviceEndpointMatch[2] && serviceEndpointMatch[3]) {
184288
184423
  return {
184289
184424
  type: "service",
@@ -184292,7 +184427,7 @@ function parseReferenceString(str) {
184292
184427
  attribute: serviceEndpointMatch[3]
184293
184428
  };
184294
184429
  }
184295
- const serviceMatch = str.match(/^service\.(\w+)\.(url|host|port)$/);
184430
+ const serviceMatch = str.match(/^service\.(\w+)\.(url|private_url|host|port|public_url)$/);
184296
184431
  if (serviceMatch && serviceMatch[1] && serviceMatch[2]) {
184297
184432
  return {
184298
184433
  type: "service",
@@ -184397,6 +184532,17 @@ function parseBuilds(buildData) {
184397
184532
  if (dev) {
184398
184533
  build.dev = dev;
184399
184534
  }
184535
+ const env2 = parseEnv(fieldObj.env);
184536
+ if (env2) {
184537
+ for (const [key, value] of Object.entries(env2)) {
184538
+ if (typeof value === "string")
184539
+ continue;
184540
+ if (value.type === "service" && value.attribute === "public_url")
184541
+ continue;
184542
+ throw new Error(`Build "${name}" env var "${key}" uses an unsupported reference type. Build env vars only support string literals and \${service.<name>.public_url} references.`);
184543
+ }
184544
+ build.env = env2;
184545
+ }
184400
184546
  builds.push(build);
184401
184547
  }
184402
184548
  return builds;
@@ -184504,13 +184650,43 @@ function parseServices(serviceData) {
184504
184650
  }
184505
184651
  return services;
184506
184652
  }
184653
+ function parseReshapeBlock(reshape) {
184654
+ if (!reshape) {
184655
+ return void 0;
184656
+ }
184657
+ const reshapeArray = Array.isArray(reshape) ? reshape : [reshape];
184658
+ const reshapeObj = reshapeArray[0];
184659
+ if (!reshapeObj || typeof reshapeObj !== "object" || Array.isArray(reshapeObj)) {
184660
+ return void 0;
184661
+ }
184662
+ const fieldObj = reshapeObj;
184663
+ if (fieldObj.enabled !== true) {
184664
+ return void 0;
184665
+ }
184666
+ const result = {
184667
+ enabled: true
184668
+ };
184669
+ if (typeof fieldObj.migrations_dir === "string") {
184670
+ result.migrations_dir = fieldObj.migrations_dir;
184671
+ }
184672
+ return result;
184673
+ }
184507
184674
  function parsePostgres(data) {
184508
184675
  if (!data || typeof data !== "object" || Array.isArray(data)) {
184509
184676
  return [];
184510
184677
  }
184511
184678
  const result = [];
184512
- for (const [name] of Object.entries(data)) {
184513
- result.push({ name });
184679
+ for (const [name, fieldsArray] of Object.entries(data)) {
184680
+ const postgres = { name };
184681
+ const fields = Array.isArray(fieldsArray) ? fieldsArray[0] : fieldsArray;
184682
+ if (fields && typeof fields === "object" && !Array.isArray(fields)) {
184683
+ const fieldObj = fields;
184684
+ const reshape = parseReshapeBlock(fieldObj.reshape);
184685
+ if (reshape) {
184686
+ postgres.reshape = reshape;
184687
+ }
184688
+ }
184689
+ result.push(postgres);
184514
184690
  }
184515
184691
  return result;
184516
184692
  }
@@ -184704,6 +184880,26 @@ function validateEndpointReferences(config) {
184704
184880
  });
184705
184881
  }
184706
184882
  }
184883
+ if (serviceRef.attribute === "public_url") {
184884
+ const endpointName = serviceRef.endpointName;
184885
+ if (endpointName) {
184886
+ const targetEndpoint = targetEndpoints.find((e) => e.name === endpointName);
184887
+ if (targetEndpoint && !targetEndpoint.public) {
184888
+ errors.push({
184889
+ service: service.name,
184890
+ message: `Service "${service.name}" references public_url of endpoint "${endpointName}" on service "${serviceRef.serviceName}" in env var "${key}", but that endpoint is not public. Add \`public = true\` to the endpoint.`
184891
+ });
184892
+ }
184893
+ } else {
184894
+ const defaultEndpoint = targetEndpoints.length === 1 ? targetEndpoints[0] : targetEndpoints.find((e) => e.name === "default");
184895
+ if (defaultEndpoint && !defaultEndpoint.public) {
184896
+ errors.push({
184897
+ service: service.name,
184898
+ message: `Service "${service.name}" references public_url of service "${serviceRef.serviceName}" in env var "${key}", but its endpoint is not public. Add \`public = true\` to the endpoint.`
184899
+ });
184900
+ }
184901
+ }
184902
+ }
184707
184903
  }
184708
184904
  if (typeof value === "object" && value.type === "endpoint") {
184709
184905
  const endpointRef = value;
@@ -184721,13 +184917,374 @@ function validateEndpointReferences(config) {
184721
184917
  return errors;
184722
184918
  }
184723
184919
 
184920
+ // src/lib/bin/types.ts
184921
+ var BinaryNotFoundError = class extends Error {
184922
+ constructor(binaryName, version, platformInfo) {
184923
+ super(
184924
+ `Binary "${binaryName}" version ${version} is not available for ${platformInfo.platform}-${platformInfo.arch}`
184925
+ );
184926
+ this.binaryName = binaryName;
184927
+ this.version = version;
184928
+ this.platformInfo = platformInfo;
184929
+ this.name = "BinaryNotFoundError";
184930
+ }
184931
+ };
184932
+ var DownloadError = class extends Error {
184933
+ constructor(url, statusCode, cause) {
184934
+ super(
184935
+ statusCode ? `Failed to download ${url}: HTTP ${statusCode}` : `Failed to download ${url}: ${cause?.message || "Unknown error"}`
184936
+ );
184937
+ this.url = url;
184938
+ this.statusCode = statusCode;
184939
+ this.cause = cause;
184940
+ this.name = "DownloadError";
184941
+ }
184942
+ };
184943
+ var ExtractionError = class extends Error {
184944
+ constructor(archivePath, cause) {
184945
+ super(
184946
+ `Failed to extract ${archivePath}: ${cause?.message || "Unknown error"}`
184947
+ );
184948
+ this.archivePath = archivePath;
184949
+ this.cause = cause;
184950
+ this.name = "ExtractionError";
184951
+ }
184952
+ };
184953
+
184954
+ // src/lib/bin/manager.ts
184955
+ import * as fs11 from "fs";
184956
+ import * as path7 from "path";
184957
+ import * as os6 from "os";
184958
+ import { createReadStream } from "fs";
184959
+ import { createTarExtractor, extractTo } from "tar-vern";
184960
+ function getLibraryEnv(binary) {
184961
+ if (!binary.libraryPath) {
184962
+ return {};
184963
+ }
184964
+ const platform4 = os6.platform();
184965
+ if (platform4 === "darwin") {
184966
+ return { DYLD_LIBRARY_PATH: binary.libraryPath };
184967
+ } else if (platform4 === "linux") {
184968
+ return { LD_LIBRARY_PATH: binary.libraryPath };
184969
+ }
184970
+ return {};
184971
+ }
184972
+ function getBinBaseDir() {
184973
+ return path7.join(os6.homedir(), ".specific", "bin");
184974
+ }
184975
+ function getPlatformInfo() {
184976
+ const platform4 = os6.platform();
184977
+ const arch3 = os6.arch();
184978
+ if (platform4 !== "darwin" && platform4 !== "linux") {
184979
+ throw new Error(
184980
+ `Unsupported platform: ${platform4}. Only macOS and Linux are supported.`
184981
+ );
184982
+ }
184983
+ const archStr = arch3;
184984
+ let mappedArch;
184985
+ if (archStr === "x64" || archStr === "x86_64") {
184986
+ mappedArch = "x64";
184987
+ } else if (archStr === "arm64" || archStr === "aarch64") {
184988
+ mappedArch = "arm64";
184989
+ } else {
184990
+ throw new Error(
184991
+ `Unsupported architecture: ${arch3}. Only x64 and arm64 are supported.`
184992
+ );
184993
+ }
184994
+ return { platform: platform4, arch: mappedArch };
184995
+ }
184996
+ function getBinaryDir(definition, version, platformInfo) {
184997
+ return path7.join(
184998
+ getBinBaseDir(),
184999
+ definition.name,
185000
+ version,
185001
+ `${platformInfo.platform}-${platformInfo.arch}`
185002
+ );
185003
+ }
185004
+ function isBinaryInstalled(definition, version, platformInfo) {
185005
+ const binDir = getBinaryDir(definition, version, platformInfo);
185006
+ for (const execPath of definition.executables) {
185007
+ const fullPath = path7.join(binDir, execPath);
185008
+ if (!fs11.existsSync(fullPath)) {
185009
+ return false;
185010
+ }
185011
+ }
185012
+ return true;
185013
+ }
185014
+ function buildDownloadUrl(definition, version, platformInfo) {
185015
+ const mapping = definition.platformMapping[platformInfo.platform]?.[platformInfo.arch];
185016
+ if (!mapping) {
185017
+ throw new BinaryNotFoundError(definition.name, version, platformInfo);
185018
+ }
185019
+ return definition.urlTemplate.replace(/{version}/g, version).replace(/{platform}/g, mapping.platform).replace(/{arch}/g, mapping.arch);
185020
+ }
185021
+ async function downloadFile(url, destPath, onProgress) {
185022
+ let response;
185023
+ try {
185024
+ response = await fetch(url);
185025
+ } catch (error) {
185026
+ throw new DownloadError(url, void 0, error);
185027
+ }
185028
+ if (!response.ok) {
185029
+ throw new DownloadError(url, response.status);
185030
+ }
185031
+ const totalBytes = parseInt(
185032
+ response.headers.get("content-length") || "0",
185033
+ 10
185034
+ );
185035
+ let bytesDownloaded = 0;
185036
+ const parentDir = path7.dirname(destPath);
185037
+ if (!fs11.existsSync(parentDir)) {
185038
+ fs11.mkdirSync(parentDir, { recursive: true });
185039
+ }
185040
+ const partPath = destPath + ".part";
185041
+ const fileStream = fs11.createWriteStream(partPath);
185042
+ try {
185043
+ const reader = response.body.getReader();
185044
+ while (true) {
185045
+ const { done, value } = await reader.read();
185046
+ if (done) break;
185047
+ bytesDownloaded += value.length;
185048
+ fileStream.write(Buffer.from(value));
185049
+ if (onProgress) {
185050
+ onProgress({
185051
+ phase: "downloading",
185052
+ bytesDownloaded,
185053
+ totalBytes: totalBytes || void 0,
185054
+ percent: totalBytes ? Math.round(bytesDownloaded / totalBytes * 100) : void 0
185055
+ });
185056
+ }
185057
+ }
185058
+ await new Promise((resolve9, reject) => {
185059
+ fileStream.end((err) => {
185060
+ if (err) reject(err);
185061
+ else resolve9();
185062
+ });
185063
+ });
185064
+ fs11.renameSync(partPath, destPath);
185065
+ } catch (error) {
185066
+ try {
185067
+ fileStream.close();
185068
+ if (fs11.existsSync(partPath)) {
185069
+ fs11.unlinkSync(partPath);
185070
+ }
185071
+ } catch {
185072
+ }
185073
+ throw new DownloadError(url, void 0, error);
185074
+ }
185075
+ }
185076
+ async function extractTarball(archivePath, destDir, definition, onProgress) {
185077
+ onProgress?.({ phase: "extracting" });
185078
+ if (!fs11.existsSync(destDir)) {
185079
+ fs11.mkdirSync(destDir, { recursive: true });
185080
+ }
185081
+ try {
185082
+ const fileStream = createReadStream(archivePath);
185083
+ const entries = createTarExtractor(fileStream, "gzip");
185084
+ await extractTo(entries, destDir);
185085
+ onProgress?.({ phase: "finalizing" });
185086
+ for (const execPath of definition.executables) {
185087
+ const fullPath = path7.join(destDir, execPath);
185088
+ if (fs11.existsSync(fullPath)) {
185089
+ fs11.chmodSync(fullPath, 493);
185090
+ }
185091
+ }
185092
+ } catch (error) {
185093
+ throw new ExtractionError(archivePath, error);
185094
+ }
185095
+ }
185096
+ async function ensureBinary(definition, version, onProgress) {
185097
+ const platformInfo = getPlatformInfo();
185098
+ const resolvedVersion = version || definition.versions[0];
185099
+ if (!definition.versions.includes(resolvedVersion)) {
185100
+ throw new Error(
185101
+ `Version ${resolvedVersion} not supported for ${definition.name}. Supported versions: ${definition.versions.join(", ")}`
185102
+ );
185103
+ }
185104
+ const binDir = getBinaryDir(definition, resolvedVersion, platformInfo);
185105
+ if (!isBinaryInstalled(definition, resolvedVersion, platformInfo)) {
185106
+ const url = buildDownloadUrl(definition, resolvedVersion, platformInfo);
185107
+ if (definition.type === "binary") {
185108
+ if (definition.executables.length !== 1) {
185109
+ throw new Error(
185110
+ `Binary type definitions must have exactly one executable, got ${definition.executables.length}`
185111
+ );
185112
+ }
185113
+ if (!fs11.existsSync(binDir)) {
185114
+ fs11.mkdirSync(binDir, { recursive: true });
185115
+ }
185116
+ const execPath = path7.join(binDir, definition.executables[0]);
185117
+ await downloadFile(url, execPath, onProgress);
185118
+ fs11.chmodSync(execPath, 493);
185119
+ onProgress?.({ phase: "finalizing" });
185120
+ } else {
185121
+ const downloadDir = path7.join(getBinBaseDir(), "downloads");
185122
+ const archiveName = `${definition.name}-${resolvedVersion}-${platformInfo.platform}-${platformInfo.arch}.tar.gz`;
185123
+ const archivePath = path7.join(downloadDir, archiveName);
185124
+ try {
185125
+ await downloadFile(url, archivePath, onProgress);
185126
+ await extractTarball(archivePath, binDir, definition, onProgress);
185127
+ } finally {
185128
+ try {
185129
+ if (fs11.existsSync(archivePath)) {
185130
+ fs11.unlinkSync(archivePath);
185131
+ }
185132
+ } catch {
185133
+ }
185134
+ }
185135
+ }
185136
+ }
185137
+ const executables = {};
185138
+ for (const execPath of definition.executables) {
185139
+ const name = path7.basename(execPath);
185140
+ executables[name] = path7.join(binDir, execPath);
185141
+ }
185142
+ const libraryPath = definition.libraryDir ? path7.join(binDir, definition.libraryDir) : void 0;
185143
+ return {
185144
+ rootDir: binDir,
185145
+ version: resolvedVersion,
185146
+ executables,
185147
+ libraryPath
185148
+ };
185149
+ }
185150
+
185151
+ // src/lib/bin/definitions/postgres.ts
185152
+ var postgresBinary = {
185153
+ name: "postgres",
185154
+ versions: ["18.1"],
185155
+ // URL: binaries.specific.dev/{SOFTWARE}/{VERSION}/{ARCH}.tar.gz
185156
+ urlTemplate: "https://binaries.specific.dev/postgres/{version}/{arch}.tar.gz",
185157
+ platformMapping: {
185158
+ darwin: {
185159
+ arm64: { platform: "macos", arch: "macos_arm" },
185160
+ x64: { platform: "macos", arch: "macos_x86" }
185161
+ },
185162
+ linux: {
185163
+ arm64: { platform: "linux", arch: "linux_arm" },
185164
+ x64: { platform: "linux", arch: "linux_x86" }
185165
+ }
185166
+ },
185167
+ // Archive contains bin/ and lib/ directories at root
185168
+ stripComponents: 0,
185169
+ // Core PostgreSQL executables (in bin/ directory)
185170
+ executables: ["bin/postgres", "bin/initdb", "bin/psql"],
185171
+ // Library directory relative to root (for setting LD_LIBRARY_PATH)
185172
+ libraryDir: "lib"
185173
+ };
185174
+
185175
+ // src/lib/bin/definitions/redis.ts
185176
+ var redisBinary = {
185177
+ name: "redis",
185178
+ versions: ["8.4.0"],
185179
+ // URL: binaries.specific.dev/{SOFTWARE}/{VERSION}/{ARCH}.tar.gz
185180
+ urlTemplate: "https://binaries.specific.dev/redis/{version}/{arch}.tar.gz",
185181
+ platformMapping: {
185182
+ darwin: {
185183
+ arm64: { platform: "macos", arch: "macos_arm" },
185184
+ x64: { platform: "macos", arch: "macos_x86" }
185185
+ },
185186
+ linux: {
185187
+ arm64: { platform: "linux", arch: "linux_arm" },
185188
+ x64: { platform: "linux", arch: "linux_x86" }
185189
+ }
185190
+ },
185191
+ // Binaries are at the root of the archive
185192
+ stripComponents: 0,
185193
+ // Core Redis executable
185194
+ executables: ["redis-server"]
185195
+ };
185196
+
185197
+ // src/lib/bin/definitions/electric.ts
185198
+ var electricBinary = {
185199
+ name: "electric",
185200
+ versions: ["1.2.11"],
185201
+ // URL: binaries.specific.dev/{SOFTWARE}/{VERSION}/{ARCH}.tar.gz
185202
+ urlTemplate: "https://binaries.specific.dev/electric/{version}/{arch}.tar.gz",
185203
+ platformMapping: {
185204
+ darwin: {
185205
+ arm64: { platform: "macos", arch: "macos_arm" },
185206
+ x64: { platform: "macos", arch: "macos_x86" }
185207
+ },
185208
+ linux: {
185209
+ arm64: { platform: "linux", arch: "linux_arm" },
185210
+ x64: { platform: "linux", arch: "linux_x86" }
185211
+ }
185212
+ },
185213
+ // Binaries are at the root of the archive
185214
+ stripComponents: 0,
185215
+ // Core Electric executable
185216
+ executables: ["electric"]
185217
+ };
185218
+
185219
+ // src/lib/bin/definitions/drizzle-gateway.ts
185220
+ var drizzleGatewayBinary = {
185221
+ name: "drizzle-gateway",
185222
+ versions: ["1.2.0"],
185223
+ // Official Drizzle Gateway download URL from R2
185224
+ urlTemplate: "https://pub-e240a4fd7085425baf4a7951e7611520.r2.dev/drizzle-gateway-{version}-{arch}",
185225
+ platformMapping: {
185226
+ darwin: {
185227
+ arm64: { platform: "macos", arch: "macos-arm64" },
185228
+ x64: { platform: "macos", arch: "macos-x64" }
185229
+ },
185230
+ linux: {
185231
+ arm64: { platform: "linux", arch: "linux-arm64" },
185232
+ x64: { platform: "linux", arch: "linux-x64" }
185233
+ }
185234
+ },
185235
+ // Raw binary, not a tarball
185236
+ type: "binary",
185237
+ // The executable name
185238
+ executables: ["drizzle-gateway"]
185239
+ };
185240
+
185241
+ // src/lib/bin/definitions/reshape.ts
185242
+ var reshapeBinary = {
185243
+ name: "reshape",
185244
+ versions: ["0.9.0"],
185245
+ // GitHub release assets
185246
+ urlTemplate: "https://github.com/fabianlindfors/reshape/releases/download/v{version}/reshape-{arch}",
185247
+ platformMapping: {
185248
+ darwin: {
185249
+ arm64: { platform: "darwin", arch: "darwin_aarch64" },
185250
+ x64: { platform: "darwin", arch: "darwin_amd64" }
185251
+ },
185252
+ linux: {
185253
+ arm64: { platform: "linux", arch: "linux_aarch64" },
185254
+ x64: { platform: "linux", arch: "linux_amd64" }
185255
+ }
185256
+ },
185257
+ // Raw binary, not a tarball
185258
+ type: "binary",
185259
+ // The executable name
185260
+ executables: ["reshape"]
185261
+ };
185262
+
184724
185263
  // src/commands/check.tsx
185264
+ async function runReshapeCheck(migrationsDir) {
185265
+ try {
185266
+ const binary = await ensureBinary(reshapeBinary);
185267
+ const reshapePath = binary.executables["reshape"];
185268
+ return new Promise((resolve9) => {
185269
+ execFile7(reshapePath, ["check", "--dirs", migrationsDir], (err, _stdout, stderr) => {
185270
+ if (err) {
185271
+ const errorMsg = stderr.trim() || err.message;
185272
+ resolve9({ success: false, error: errorMsg });
185273
+ } else {
185274
+ resolve9({ success: true });
185275
+ }
185276
+ });
185277
+ });
185278
+ } catch (err) {
185279
+ return { success: false, error: err instanceof Error ? err.message : String(err) };
185280
+ }
185281
+ }
184725
185282
  function CheckUI() {
184726
185283
  const [state, setState] = useState2({ status: "loading" });
184727
185284
  useEffect2(() => {
184728
185285
  async function load() {
184729
- const configPath = path7.join(process.cwd(), "specific.hcl");
184730
- if (!fs11.existsSync(configPath)) {
185286
+ const configPath = path8.join(process.cwd(), "specific.hcl");
185287
+ if (!fs12.existsSync(configPath)) {
184731
185288
  setState({
184732
185289
  status: "error",
184733
185290
  error: "No specific.hcl found in current directory"
@@ -184735,9 +185292,36 @@ function CheckUI() {
184735
185292
  return;
184736
185293
  }
184737
185294
  try {
184738
- const hcl = fs11.readFileSync(configPath, "utf-8");
185295
+ const hcl = fs12.readFileSync(configPath, "utf-8");
184739
185296
  const config2 = await parseConfig(hcl);
184740
- setState({ status: "success", config: config2 });
185297
+ const reshapeChecks2 = [];
185298
+ for (const pg of config2.postgres) {
185299
+ if (pg.reshape?.enabled) {
185300
+ const migrationsDir = path8.resolve(
185301
+ process.cwd(),
185302
+ pg.reshape.migrations_dir ?? "migrations"
185303
+ );
185304
+ if (!fs12.existsSync(migrationsDir)) {
185305
+ reshapeChecks2.push({
185306
+ databaseName: pg.name,
185307
+ migrationsDir: pg.reshape.migrations_dir ?? "migrations",
185308
+ success: false,
185309
+ error: `Migrations directory not found: ${migrationsDir}`
185310
+ });
185311
+ continue;
185312
+ }
185313
+ const result = await runReshapeCheck(migrationsDir);
185314
+ reshapeChecks2.push({
185315
+ databaseName: pg.name,
185316
+ migrationsDir: pg.reshape.migrations_dir ?? "migrations",
185317
+ ...result
185318
+ });
185319
+ }
185320
+ }
185321
+ if (reshapeChecks2.some((r) => !r.success)) {
185322
+ process.exitCode = 1;
185323
+ }
185324
+ setState({ status: "success", config: config2, reshapeChecks: reshapeChecks2 });
184741
185325
  } catch (err) {
184742
185326
  setState({
184743
185327
  status: "error",
@@ -184757,7 +185341,10 @@ function CheckUI() {
184757
185341
  if (!config) {
184758
185342
  return null;
184759
185343
  }
184760
- return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React3.createElement(Text3, { color: "green" }, "Configuration is valid"), config.builds.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Builds (", config.builds.length, "):"), config.builds.map((b) => /* @__PURE__ */ React3.createElement(Text3, { key: b.name }, " ", "- ", b.name))), config.services.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Services (", config.services.length, "):"), config.services.map((s) => /* @__PURE__ */ React3.createElement(Text3, { key: s.name }, " ", "- ", s.name))), config.postgres.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Postgres (", config.postgres.length, "):"), config.postgres.map((p) => /* @__PURE__ */ React3.createElement(Text3, { key: p.name }, " ", "- ", p.name))), config.redis.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Redis (", config.redis.length, "):"), config.redis.map((r) => /* @__PURE__ */ React3.createElement(Text3, { key: r.name }, " ", "- ", r.name))), config.storage.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Storage (", config.storage.length, "):"), config.storage.map((s) => /* @__PURE__ */ React3.createElement(Text3, { key: s.name }, " ", "- ", s.name))), config.configs.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Config (", config.configs.length, "):"), config.configs.map((c) => /* @__PURE__ */ React3.createElement(Text3, { key: c.name }, " ", "- ", c.name, c.default ? ` (default: ${c.default})` : ""))), config.environments.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Environments (", config.environments.length, "):"), config.environments.map((e) => /* @__PURE__ */ React3.createElement(Text3, { key: e.name }, " ", "- ", e.name))));
185344
+ const reshapeChecks = state.reshapeChecks ?? [];
185345
+ const reshapeErrors = reshapeChecks.filter((r) => !r.success);
185346
+ const hasReshapeErrors = reshapeErrors.length > 0;
185347
+ return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React3.createElement(Text3, { color: hasReshapeErrors ? "yellow" : "green" }, hasReshapeErrors ? "Configuration is valid but reshape migrations have errors" : "Configuration is valid"), config.builds.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Builds (", config.builds.length, "):"), config.builds.map((b) => /* @__PURE__ */ React3.createElement(Text3, { key: b.name }, " ", "- ", b.name))), config.services.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Services (", config.services.length, "):"), config.services.map((s) => /* @__PURE__ */ React3.createElement(Text3, { key: s.name }, " ", "- ", s.name))), config.postgres.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Postgres (", config.postgres.length, "):"), config.postgres.map((p) => /* @__PURE__ */ React3.createElement(Text3, { key: p.name }, " ", "- ", p.name, p.reshape?.enabled ? " (reshape enabled)" : ""))), reshapeChecks.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Reshape Migrations:"), reshapeChecks.map((r) => /* @__PURE__ */ React3.createElement(Box3, { key: r.databaseName, flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, null, " ", /* @__PURE__ */ React3.createElement(Text3, { color: r.success ? "green" : "red" }, r.success ? "pass" : "fail"), " ", r.databaseName, " (", r.migrationsDir, ")"), r.error && /* @__PURE__ */ React3.createElement(Text3, { color: "red" }, " ", r.error)))), config.redis.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Redis (", config.redis.length, "):"), config.redis.map((r) => /* @__PURE__ */ React3.createElement(Text3, { key: r.name }, " ", "- ", r.name))), config.storage.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Storage (", config.storage.length, "):"), config.storage.map((s) => /* @__PURE__ */ React3.createElement(Text3, { key: s.name }, " ", "- ", s.name))), config.configs.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Config (", config.configs.length, "):"), config.configs.map((c) => /* @__PURE__ */ React3.createElement(Text3, { key: c.name }, " ", "- ", c.name, c.default ? ` (default: ${c.default})` : ""))), config.environments.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Environments (", config.environments.length, "):"), config.environments.map((e) => /* @__PURE__ */ React3.createElement(Text3, { key: e.name }, " ", "- ", e.name))));
184761
185348
  }
184762
185349
  function checkCommand() {
184763
185350
  render3(/* @__PURE__ */ React3.createElement(CheckUI, null));
@@ -184767,8 +185354,8 @@ function checkCommand() {
184767
185354
  import React6, { useState as useState5, useEffect as useEffect3, useRef } from "react";
184768
185355
  import { render as render4, Text as Text6, Box as Box6, useApp as useApp2, Static } from "ink";
184769
185356
  import Spinner4 from "ink-spinner";
184770
- import * as fs20 from "fs";
184771
- import * as path17 from "path";
185357
+ import * as fs21 from "fs";
185358
+ import * as path18 from "path";
184772
185359
 
184773
185360
  // node_modules/.pnpm/chokidar@5.0.0/node_modules/chokidar/index.js
184774
185361
  import { EventEmitter } from "node:events";
@@ -184860,7 +185447,7 @@ var ReaddirpStream = class extends Readable {
184860
185447
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
184861
185448
  const statMethod = opts.lstat ? lstat : stat;
184862
185449
  if (wantBigintFsStats) {
184863
- this._stat = (path23) => statMethod(path23, { bigint: true });
185450
+ this._stat = (path26) => statMethod(path26, { bigint: true });
184864
185451
  } else {
184865
185452
  this._stat = statMethod;
184866
185453
  }
@@ -184885,8 +185472,8 @@ var ReaddirpStream = class extends Readable {
184885
185472
  const par = this.parent;
184886
185473
  const fil = par && par.files;
184887
185474
  if (fil && fil.length > 0) {
184888
- const { path: path23, depth } = par;
184889
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path23));
185475
+ const { path: path26, depth } = par;
185476
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path26));
184890
185477
  const awaited = await Promise.all(slice);
184891
185478
  for (const entry of awaited) {
184892
185479
  if (!entry)
@@ -184926,21 +185513,21 @@ var ReaddirpStream = class extends Readable {
184926
185513
  this.reading = false;
184927
185514
  }
184928
185515
  }
184929
- async _exploreDir(path23, depth) {
185516
+ async _exploreDir(path26, depth) {
184930
185517
  let files;
184931
185518
  try {
184932
- files = await readdir(path23, this._rdOptions);
185519
+ files = await readdir(path26, this._rdOptions);
184933
185520
  } catch (error) {
184934
185521
  this._onError(error);
184935
185522
  }
184936
- return { files, depth, path: path23 };
185523
+ return { files, depth, path: path26 };
184937
185524
  }
184938
- async _formatEntry(dirent, path23) {
185525
+ async _formatEntry(dirent, path26) {
184939
185526
  let entry;
184940
- const basename5 = this._isDirent ? dirent.name : dirent;
185527
+ const basename6 = this._isDirent ? dirent.name : dirent;
184941
185528
  try {
184942
- const fullPath = presolve(pjoin(path23, basename5));
184943
- entry = { path: prelative(this._root, fullPath), fullPath, basename: basename5 };
185529
+ const fullPath = presolve(pjoin(path26, basename6));
185530
+ entry = { path: prelative(this._root, fullPath), fullPath, basename: basename6 };
184944
185531
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
184945
185532
  } catch (err) {
184946
185533
  this._onError(err);
@@ -185339,16 +185926,16 @@ var delFromSet = (main, prop, item) => {
185339
185926
  };
185340
185927
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
185341
185928
  var FsWatchInstances = /* @__PURE__ */ new Map();
185342
- function createFsWatchInstance(path23, options2, listener, errHandler, emitRaw) {
185929
+ function createFsWatchInstance(path26, options2, listener, errHandler, emitRaw) {
185343
185930
  const handleEvent = (rawEvent, evPath) => {
185344
- listener(path23);
185345
- emitRaw(rawEvent, evPath, { watchedPath: path23 });
185346
- if (evPath && path23 !== evPath) {
185347
- fsWatchBroadcast(sp.resolve(path23, evPath), KEY_LISTENERS, sp.join(path23, evPath));
185931
+ listener(path26);
185932
+ emitRaw(rawEvent, evPath, { watchedPath: path26 });
185933
+ if (evPath && path26 !== evPath) {
185934
+ fsWatchBroadcast(sp.resolve(path26, evPath), KEY_LISTENERS, sp.join(path26, evPath));
185348
185935
  }
185349
185936
  };
185350
185937
  try {
185351
- return fs_watch(path23, {
185938
+ return fs_watch(path26, {
185352
185939
  persistent: options2.persistent
185353
185940
  }, handleEvent);
185354
185941
  } catch (error) {
@@ -185364,12 +185951,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
185364
185951
  listener(val1, val2, val3);
185365
185952
  });
185366
185953
  };
185367
- var setFsWatchListener = (path23, fullPath, options2, handlers) => {
185954
+ var setFsWatchListener = (path26, fullPath, options2, handlers) => {
185368
185955
  const { listener, errHandler, rawEmitter } = handlers;
185369
185956
  let cont = FsWatchInstances.get(fullPath);
185370
185957
  let watcher;
185371
185958
  if (!options2.persistent) {
185372
- watcher = createFsWatchInstance(path23, options2, listener, errHandler, rawEmitter);
185959
+ watcher = createFsWatchInstance(path26, options2, listener, errHandler, rawEmitter);
185373
185960
  if (!watcher)
185374
185961
  return;
185375
185962
  return watcher.close.bind(watcher);
@@ -185380,7 +185967,7 @@ var setFsWatchListener = (path23, fullPath, options2, handlers) => {
185380
185967
  addAndConvert(cont, KEY_RAW, rawEmitter);
185381
185968
  } else {
185382
185969
  watcher = createFsWatchInstance(
185383
- path23,
185970
+ path26,
185384
185971
  options2,
185385
185972
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
185386
185973
  errHandler,
@@ -185395,7 +185982,7 @@ var setFsWatchListener = (path23, fullPath, options2, handlers) => {
185395
185982
  cont.watcherUnusable = true;
185396
185983
  if (isWindows && error.code === "EPERM") {
185397
185984
  try {
185398
- const fd = await open2(path23, "r");
185985
+ const fd = await open2(path26, "r");
185399
185986
  await fd.close();
185400
185987
  broadcastErr(error);
185401
185988
  } catch (err) {
@@ -185426,7 +186013,7 @@ var setFsWatchListener = (path23, fullPath, options2, handlers) => {
185426
186013
  };
185427
186014
  };
185428
186015
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
185429
- var setFsWatchFileListener = (path23, fullPath, options2, handlers) => {
186016
+ var setFsWatchFileListener = (path26, fullPath, options2, handlers) => {
185430
186017
  const { listener, rawEmitter } = handlers;
185431
186018
  let cont = FsWatchFileInstances.get(fullPath);
185432
186019
  const copts = cont && cont.options;
@@ -185448,7 +186035,7 @@ var setFsWatchFileListener = (path23, fullPath, options2, handlers) => {
185448
186035
  });
185449
186036
  const currmtime = curr.mtimeMs;
185450
186037
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
185451
- foreach(cont.listeners, (listener2) => listener2(path23, curr));
186038
+ foreach(cont.listeners, (listener2) => listener2(path26, curr));
185452
186039
  }
185453
186040
  })
185454
186041
  };
@@ -185478,13 +186065,13 @@ var NodeFsHandler = class {
185478
186065
  * @param listener on fs change
185479
186066
  * @returns closer for the watcher instance
185480
186067
  */
185481
- _watchWithNodeFs(path23, listener) {
186068
+ _watchWithNodeFs(path26, listener) {
185482
186069
  const opts = this.fsw.options;
185483
- const directory = sp.dirname(path23);
185484
- const basename5 = sp.basename(path23);
186070
+ const directory = sp.dirname(path26);
186071
+ const basename6 = sp.basename(path26);
185485
186072
  const parent = this.fsw._getWatchedDir(directory);
185486
- parent.add(basename5);
185487
- const absolutePath = sp.resolve(path23);
186073
+ parent.add(basename6);
186074
+ const absolutePath = sp.resolve(path26);
185488
186075
  const options2 = {
185489
186076
  persistent: opts.persistent
185490
186077
  };
@@ -185493,13 +186080,13 @@ var NodeFsHandler = class {
185493
186080
  let closer;
185494
186081
  if (opts.usePolling) {
185495
186082
  const enableBin = opts.interval !== opts.binaryInterval;
185496
- options2.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
185497
- closer = setFsWatchFileListener(path23, absolutePath, options2, {
186083
+ options2.interval = enableBin && isBinaryPath(basename6) ? opts.binaryInterval : opts.interval;
186084
+ closer = setFsWatchFileListener(path26, absolutePath, options2, {
185498
186085
  listener,
185499
186086
  rawEmitter: this.fsw._emitRaw
185500
186087
  });
185501
186088
  } else {
185502
- closer = setFsWatchListener(path23, absolutePath, options2, {
186089
+ closer = setFsWatchListener(path26, absolutePath, options2, {
185503
186090
  listener,
185504
186091
  errHandler: this._boundHandleError,
185505
186092
  rawEmitter: this.fsw._emitRaw
@@ -185516,12 +186103,12 @@ var NodeFsHandler = class {
185516
186103
  return;
185517
186104
  }
185518
186105
  const dirname8 = sp.dirname(file);
185519
- const basename5 = sp.basename(file);
186106
+ const basename6 = sp.basename(file);
185520
186107
  const parent = this.fsw._getWatchedDir(dirname8);
185521
186108
  let prevStats = stats;
185522
- if (parent.has(basename5))
186109
+ if (parent.has(basename6))
185523
186110
  return;
185524
- const listener = async (path23, newStats) => {
186111
+ const listener = async (path26, newStats) => {
185525
186112
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
185526
186113
  return;
185527
186114
  if (!newStats || newStats.mtimeMs === 0) {
@@ -185535,18 +186122,18 @@ var NodeFsHandler = class {
185535
186122
  this.fsw._emit(EV.CHANGE, file, newStats2);
185536
186123
  }
185537
186124
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
185538
- this.fsw._closeFile(path23);
186125
+ this.fsw._closeFile(path26);
185539
186126
  prevStats = newStats2;
185540
186127
  const closer2 = this._watchWithNodeFs(file, listener);
185541
186128
  if (closer2)
185542
- this.fsw._addPathCloser(path23, closer2);
186129
+ this.fsw._addPathCloser(path26, closer2);
185543
186130
  } else {
185544
186131
  prevStats = newStats2;
185545
186132
  }
185546
186133
  } catch (error) {
185547
- this.fsw._remove(dirname8, basename5);
186134
+ this.fsw._remove(dirname8, basename6);
185548
186135
  }
185549
- } else if (parent.has(basename5)) {
186136
+ } else if (parent.has(basename6)) {
185550
186137
  const at = newStats.atimeMs;
185551
186138
  const mt = newStats.mtimeMs;
185552
186139
  if (!at || at <= mt || mt !== prevStats.mtimeMs) {
@@ -185571,7 +186158,7 @@ var NodeFsHandler = class {
185571
186158
  * @param item basename of this item
185572
186159
  * @returns true if no more processing is needed for this entry.
185573
186160
  */
185574
- async _handleSymlink(entry, directory, path23, item) {
186161
+ async _handleSymlink(entry, directory, path26, item) {
185575
186162
  if (this.fsw.closed) {
185576
186163
  return;
185577
186164
  }
@@ -185581,7 +186168,7 @@ var NodeFsHandler = class {
185581
186168
  this.fsw._incrReadyCount();
185582
186169
  let linkPath;
185583
186170
  try {
185584
- linkPath = await fsrealpath(path23);
186171
+ linkPath = await fsrealpath(path26);
185585
186172
  } catch (e) {
185586
186173
  this.fsw._emitReady();
185587
186174
  return true;
@@ -185591,12 +186178,12 @@ var NodeFsHandler = class {
185591
186178
  if (dir.has(item)) {
185592
186179
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
185593
186180
  this.fsw._symlinkPaths.set(full, linkPath);
185594
- this.fsw._emit(EV.CHANGE, path23, entry.stats);
186181
+ this.fsw._emit(EV.CHANGE, path26, entry.stats);
185595
186182
  }
185596
186183
  } else {
185597
186184
  dir.add(item);
185598
186185
  this.fsw._symlinkPaths.set(full, linkPath);
185599
- this.fsw._emit(EV.ADD, path23, entry.stats);
186186
+ this.fsw._emit(EV.ADD, path26, entry.stats);
185600
186187
  }
185601
186188
  this.fsw._emitReady();
185602
186189
  return true;
@@ -185626,9 +186213,9 @@ var NodeFsHandler = class {
185626
186213
  return;
185627
186214
  }
185628
186215
  const item = entry.path;
185629
- let path23 = sp.join(directory, item);
186216
+ let path26 = sp.join(directory, item);
185630
186217
  current.add(item);
185631
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path23, item)) {
186218
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path26, item)) {
185632
186219
  return;
185633
186220
  }
185634
186221
  if (this.fsw.closed) {
@@ -185637,11 +186224,11 @@ var NodeFsHandler = class {
185637
186224
  }
185638
186225
  if (item === target || !target && !previous.has(item)) {
185639
186226
  this.fsw._incrReadyCount();
185640
- path23 = sp.join(dir, sp.relative(dir, path23));
185641
- this._addToNodeFs(path23, initialAdd, wh, depth + 1);
186227
+ path26 = sp.join(dir, sp.relative(dir, path26));
186228
+ this._addToNodeFs(path26, initialAdd, wh, depth + 1);
185642
186229
  }
185643
186230
  }).on(EV.ERROR, this._boundHandleError);
185644
- return new Promise((resolve7, reject) => {
186231
+ return new Promise((resolve9, reject) => {
185645
186232
  if (!stream)
185646
186233
  return reject();
185647
186234
  stream.once(STR_END, () => {
@@ -185650,7 +186237,7 @@ var NodeFsHandler = class {
185650
186237
  return;
185651
186238
  }
185652
186239
  const wasThrottled = throttler ? throttler.clear() : false;
185653
- resolve7(void 0);
186240
+ resolve9(void 0);
185654
186241
  previous.getChildren().filter((item) => {
185655
186242
  return item !== directory && !current.has(item);
185656
186243
  }).forEach((item) => {
@@ -185707,13 +186294,13 @@ var NodeFsHandler = class {
185707
186294
  * @param depth Child path actually targeted for watch
185708
186295
  * @param target Child path actually targeted for watch
185709
186296
  */
185710
- async _addToNodeFs(path23, initialAdd, priorWh, depth, target) {
186297
+ async _addToNodeFs(path26, initialAdd, priorWh, depth, target) {
185711
186298
  const ready = this.fsw._emitReady;
185712
- if (this.fsw._isIgnored(path23) || this.fsw.closed) {
186299
+ if (this.fsw._isIgnored(path26) || this.fsw.closed) {
185713
186300
  ready();
185714
186301
  return false;
185715
186302
  }
185716
- const wh = this.fsw._getWatchHelpers(path23);
186303
+ const wh = this.fsw._getWatchHelpers(path26);
185717
186304
  if (priorWh) {
185718
186305
  wh.filterPath = (entry) => priorWh.filterPath(entry);
185719
186306
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -185729,8 +186316,8 @@ var NodeFsHandler = class {
185729
186316
  const follow = this.fsw.options.followSymlinks;
185730
186317
  let closer;
185731
186318
  if (stats.isDirectory()) {
185732
- const absPath = sp.resolve(path23);
185733
- const targetPath = follow ? await fsrealpath(path23) : path23;
186319
+ const absPath = sp.resolve(path26);
186320
+ const targetPath = follow ? await fsrealpath(path26) : path26;
185734
186321
  if (this.fsw.closed)
185735
186322
  return;
185736
186323
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -185740,29 +186327,29 @@ var NodeFsHandler = class {
185740
186327
  this.fsw._symlinkPaths.set(absPath, targetPath);
185741
186328
  }
185742
186329
  } else if (stats.isSymbolicLink()) {
185743
- const targetPath = follow ? await fsrealpath(path23) : path23;
186330
+ const targetPath = follow ? await fsrealpath(path26) : path26;
185744
186331
  if (this.fsw.closed)
185745
186332
  return;
185746
186333
  const parent = sp.dirname(wh.watchPath);
185747
186334
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
185748
186335
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
185749
- closer = await this._handleDir(parent, stats, initialAdd, depth, path23, wh, targetPath);
186336
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path26, wh, targetPath);
185750
186337
  if (this.fsw.closed)
185751
186338
  return;
185752
186339
  if (targetPath !== void 0) {
185753
- this.fsw._symlinkPaths.set(sp.resolve(path23), targetPath);
186340
+ this.fsw._symlinkPaths.set(sp.resolve(path26), targetPath);
185754
186341
  }
185755
186342
  } else {
185756
186343
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
185757
186344
  }
185758
186345
  ready();
185759
186346
  if (closer)
185760
- this.fsw._addPathCloser(path23, closer);
186347
+ this.fsw._addPathCloser(path26, closer);
185761
186348
  return false;
185762
186349
  } catch (error) {
185763
186350
  if (this.fsw._handleError(error)) {
185764
186351
  ready();
185765
- return path23;
186352
+ return path26;
185766
186353
  }
185767
186354
  }
185768
186355
  }
@@ -185805,24 +186392,24 @@ function createPattern(matcher) {
185805
186392
  }
185806
186393
  return () => false;
185807
186394
  }
185808
- function normalizePath(path23) {
185809
- if (typeof path23 !== "string")
186395
+ function normalizePath(path26) {
186396
+ if (typeof path26 !== "string")
185810
186397
  throw new Error("string expected");
185811
- path23 = sp2.normalize(path23);
185812
- path23 = path23.replace(/\\/g, "/");
186398
+ path26 = sp2.normalize(path26);
186399
+ path26 = path26.replace(/\\/g, "/");
185813
186400
  let prepend = false;
185814
- if (path23.startsWith("//"))
186401
+ if (path26.startsWith("//"))
185815
186402
  prepend = true;
185816
- path23 = path23.replace(DOUBLE_SLASH_RE, "/");
186403
+ path26 = path26.replace(DOUBLE_SLASH_RE, "/");
185817
186404
  if (prepend)
185818
- path23 = "/" + path23;
185819
- return path23;
186405
+ path26 = "/" + path26;
186406
+ return path26;
185820
186407
  }
185821
186408
  function matchPatterns(patterns, testString, stats) {
185822
- const path23 = normalizePath(testString);
186409
+ const path26 = normalizePath(testString);
185823
186410
  for (let index = 0; index < patterns.length; index++) {
185824
186411
  const pattern = patterns[index];
185825
- if (pattern(path23, stats)) {
186412
+ if (pattern(path26, stats)) {
185826
186413
  return true;
185827
186414
  }
185828
186415
  }
@@ -185860,19 +186447,19 @@ var toUnix = (string) => {
185860
186447
  }
185861
186448
  return str;
185862
186449
  };
185863
- var normalizePathToUnix = (path23) => toUnix(sp2.normalize(toUnix(path23)));
185864
- var normalizeIgnored = (cwd = "") => (path23) => {
185865
- if (typeof path23 === "string") {
185866
- return normalizePathToUnix(sp2.isAbsolute(path23) ? path23 : sp2.join(cwd, path23));
186450
+ var normalizePathToUnix = (path26) => toUnix(sp2.normalize(toUnix(path26)));
186451
+ var normalizeIgnored = (cwd = "") => (path26) => {
186452
+ if (typeof path26 === "string") {
186453
+ return normalizePathToUnix(sp2.isAbsolute(path26) ? path26 : sp2.join(cwd, path26));
185867
186454
  } else {
185868
- return path23;
186455
+ return path26;
185869
186456
  }
185870
186457
  };
185871
- var getAbsolutePath = (path23, cwd) => {
185872
- if (sp2.isAbsolute(path23)) {
185873
- return path23;
186458
+ var getAbsolutePath = (path26, cwd) => {
186459
+ if (sp2.isAbsolute(path26)) {
186460
+ return path26;
185874
186461
  }
185875
- return sp2.join(cwd, path23);
186462
+ return sp2.join(cwd, path26);
185876
186463
  };
185877
186464
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
185878
186465
  var DirEntry = class {
@@ -185937,10 +186524,10 @@ var WatchHelper = class {
185937
186524
  dirParts;
185938
186525
  followSymlinks;
185939
186526
  statMethod;
185940
- constructor(path23, follow, fsw) {
186527
+ constructor(path26, follow, fsw) {
185941
186528
  this.fsw = fsw;
185942
- const watchPath = path23;
185943
- this.path = path23 = path23.replace(REPLACER_RE, "");
186529
+ const watchPath = path26;
186530
+ this.path = path26 = path26.replace(REPLACER_RE, "");
185944
186531
  this.watchPath = watchPath;
185945
186532
  this.fullWatchPath = sp2.resolve(watchPath);
185946
186533
  this.dirParts = [];
@@ -186080,20 +186667,20 @@ var FSWatcher = class extends EventEmitter {
186080
186667
  this._closePromise = void 0;
186081
186668
  let paths = unifyPaths(paths_);
186082
186669
  if (cwd) {
186083
- paths = paths.map((path23) => {
186084
- const absPath = getAbsolutePath(path23, cwd);
186670
+ paths = paths.map((path26) => {
186671
+ const absPath = getAbsolutePath(path26, cwd);
186085
186672
  return absPath;
186086
186673
  });
186087
186674
  }
186088
- paths.forEach((path23) => {
186089
- this._removeIgnoredPath(path23);
186675
+ paths.forEach((path26) => {
186676
+ this._removeIgnoredPath(path26);
186090
186677
  });
186091
186678
  this._userIgnored = void 0;
186092
186679
  if (!this._readyCount)
186093
186680
  this._readyCount = 0;
186094
186681
  this._readyCount += paths.length;
186095
- Promise.all(paths.map(async (path23) => {
186096
- const res = await this._nodeFsHandler._addToNodeFs(path23, !_internal, void 0, 0, _origAdd);
186682
+ Promise.all(paths.map(async (path26) => {
186683
+ const res = await this._nodeFsHandler._addToNodeFs(path26, !_internal, void 0, 0, _origAdd);
186097
186684
  if (res)
186098
186685
  this._emitReady();
186099
186686
  return res;
@@ -186115,17 +186702,17 @@ var FSWatcher = class extends EventEmitter {
186115
186702
  return this;
186116
186703
  const paths = unifyPaths(paths_);
186117
186704
  const { cwd } = this.options;
186118
- paths.forEach((path23) => {
186119
- if (!sp2.isAbsolute(path23) && !this._closers.has(path23)) {
186705
+ paths.forEach((path26) => {
186706
+ if (!sp2.isAbsolute(path26) && !this._closers.has(path26)) {
186120
186707
  if (cwd)
186121
- path23 = sp2.join(cwd, path23);
186122
- path23 = sp2.resolve(path23);
186708
+ path26 = sp2.join(cwd, path26);
186709
+ path26 = sp2.resolve(path26);
186123
186710
  }
186124
- this._closePath(path23);
186125
- this._addIgnoredPath(path23);
186126
- if (this._watched.has(path23)) {
186711
+ this._closePath(path26);
186712
+ this._addIgnoredPath(path26);
186713
+ if (this._watched.has(path26)) {
186127
186714
  this._addIgnoredPath({
186128
- path: path23,
186715
+ path: path26,
186129
186716
  recursive: true
186130
186717
  });
186131
186718
  }
@@ -186189,38 +186776,38 @@ var FSWatcher = class extends EventEmitter {
186189
186776
  * @param stats arguments to be passed with event
186190
186777
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
186191
186778
  */
186192
- async _emit(event, path23, stats) {
186779
+ async _emit(event, path26, stats) {
186193
186780
  if (this.closed)
186194
186781
  return;
186195
186782
  const opts = this.options;
186196
186783
  if (isWindows)
186197
- path23 = sp2.normalize(path23);
186784
+ path26 = sp2.normalize(path26);
186198
186785
  if (opts.cwd)
186199
- path23 = sp2.relative(opts.cwd, path23);
186200
- const args = [path23];
186786
+ path26 = sp2.relative(opts.cwd, path26);
186787
+ const args = [path26];
186201
186788
  if (stats != null)
186202
186789
  args.push(stats);
186203
186790
  const awf = opts.awaitWriteFinish;
186204
186791
  let pw;
186205
- if (awf && (pw = this._pendingWrites.get(path23))) {
186792
+ if (awf && (pw = this._pendingWrites.get(path26))) {
186206
186793
  pw.lastChange = /* @__PURE__ */ new Date();
186207
186794
  return this;
186208
186795
  }
186209
186796
  if (opts.atomic) {
186210
186797
  if (event === EVENTS.UNLINK) {
186211
- this._pendingUnlinks.set(path23, [event, ...args]);
186798
+ this._pendingUnlinks.set(path26, [event, ...args]);
186212
186799
  setTimeout(() => {
186213
- this._pendingUnlinks.forEach((entry, path24) => {
186800
+ this._pendingUnlinks.forEach((entry, path27) => {
186214
186801
  this.emit(...entry);
186215
186802
  this.emit(EVENTS.ALL, ...entry);
186216
- this._pendingUnlinks.delete(path24);
186803
+ this._pendingUnlinks.delete(path27);
186217
186804
  });
186218
186805
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
186219
186806
  return this;
186220
186807
  }
186221
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path23)) {
186808
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path26)) {
186222
186809
  event = EVENTS.CHANGE;
186223
- this._pendingUnlinks.delete(path23);
186810
+ this._pendingUnlinks.delete(path26);
186224
186811
  }
186225
186812
  }
186226
186813
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -186238,16 +186825,16 @@ var FSWatcher = class extends EventEmitter {
186238
186825
  this.emitWithAll(event, args);
186239
186826
  }
186240
186827
  };
186241
- this._awaitWriteFinish(path23, awf.stabilityThreshold, event, awfEmit);
186828
+ this._awaitWriteFinish(path26, awf.stabilityThreshold, event, awfEmit);
186242
186829
  return this;
186243
186830
  }
186244
186831
  if (event === EVENTS.CHANGE) {
186245
- const isThrottled = !this._throttle(EVENTS.CHANGE, path23, 50);
186832
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path26, 50);
186246
186833
  if (isThrottled)
186247
186834
  return this;
186248
186835
  }
186249
186836
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
186250
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path23) : path23;
186837
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path26) : path26;
186251
186838
  let stats2;
186252
186839
  try {
186253
186840
  stats2 = await stat3(fullPath);
@@ -186278,23 +186865,23 @@ var FSWatcher = class extends EventEmitter {
186278
186865
  * @param timeout duration of time to suppress duplicate actions
186279
186866
  * @returns tracking object or false if action should be suppressed
186280
186867
  */
186281
- _throttle(actionType, path23, timeout) {
186868
+ _throttle(actionType, path26, timeout) {
186282
186869
  if (!this._throttled.has(actionType)) {
186283
186870
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
186284
186871
  }
186285
186872
  const action = this._throttled.get(actionType);
186286
186873
  if (!action)
186287
186874
  throw new Error("invalid throttle");
186288
- const actionPath = action.get(path23);
186875
+ const actionPath = action.get(path26);
186289
186876
  if (actionPath) {
186290
186877
  actionPath.count++;
186291
186878
  return false;
186292
186879
  }
186293
186880
  let timeoutObject;
186294
186881
  const clear = () => {
186295
- const item = action.get(path23);
186882
+ const item = action.get(path26);
186296
186883
  const count = item ? item.count : 0;
186297
- action.delete(path23);
186884
+ action.delete(path26);
186298
186885
  clearTimeout(timeoutObject);
186299
186886
  if (item)
186300
186887
  clearTimeout(item.timeoutObject);
@@ -186302,7 +186889,7 @@ var FSWatcher = class extends EventEmitter {
186302
186889
  };
186303
186890
  timeoutObject = setTimeout(clear, timeout);
186304
186891
  const thr = { timeoutObject, clear, count: 0 };
186305
- action.set(path23, thr);
186892
+ action.set(path26, thr);
186306
186893
  return thr;
186307
186894
  }
186308
186895
  _incrReadyCount() {
@@ -186316,44 +186903,44 @@ var FSWatcher = class extends EventEmitter {
186316
186903
  * @param event
186317
186904
  * @param awfEmit Callback to be called when ready for event to be emitted.
186318
186905
  */
186319
- _awaitWriteFinish(path23, threshold, event, awfEmit) {
186906
+ _awaitWriteFinish(path26, threshold, event, awfEmit) {
186320
186907
  const awf = this.options.awaitWriteFinish;
186321
186908
  if (typeof awf !== "object")
186322
186909
  return;
186323
186910
  const pollInterval = awf.pollInterval;
186324
186911
  let timeoutHandler;
186325
- let fullPath = path23;
186326
- if (this.options.cwd && !sp2.isAbsolute(path23)) {
186327
- fullPath = sp2.join(this.options.cwd, path23);
186912
+ let fullPath = path26;
186913
+ if (this.options.cwd && !sp2.isAbsolute(path26)) {
186914
+ fullPath = sp2.join(this.options.cwd, path26);
186328
186915
  }
186329
186916
  const now = /* @__PURE__ */ new Date();
186330
186917
  const writes = this._pendingWrites;
186331
186918
  function awaitWriteFinishFn(prevStat) {
186332
186919
  statcb(fullPath, (err, curStat) => {
186333
- if (err || !writes.has(path23)) {
186920
+ if (err || !writes.has(path26)) {
186334
186921
  if (err && err.code !== "ENOENT")
186335
186922
  awfEmit(err);
186336
186923
  return;
186337
186924
  }
186338
186925
  const now2 = Number(/* @__PURE__ */ new Date());
186339
186926
  if (prevStat && curStat.size !== prevStat.size) {
186340
- writes.get(path23).lastChange = now2;
186927
+ writes.get(path26).lastChange = now2;
186341
186928
  }
186342
- const pw = writes.get(path23);
186929
+ const pw = writes.get(path26);
186343
186930
  const df = now2 - pw.lastChange;
186344
186931
  if (df >= threshold) {
186345
- writes.delete(path23);
186932
+ writes.delete(path26);
186346
186933
  awfEmit(void 0, curStat);
186347
186934
  } else {
186348
186935
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
186349
186936
  }
186350
186937
  });
186351
186938
  }
186352
- if (!writes.has(path23)) {
186353
- writes.set(path23, {
186939
+ if (!writes.has(path26)) {
186940
+ writes.set(path26, {
186354
186941
  lastChange: now,
186355
186942
  cancelWait: () => {
186356
- writes.delete(path23);
186943
+ writes.delete(path26);
186357
186944
  clearTimeout(timeoutHandler);
186358
186945
  return event;
186359
186946
  }
@@ -186364,8 +186951,8 @@ var FSWatcher = class extends EventEmitter {
186364
186951
  /**
186365
186952
  * Determines whether user has asked to ignore this path.
186366
186953
  */
186367
- _isIgnored(path23, stats) {
186368
- if (this.options.atomic && DOT_RE.test(path23))
186954
+ _isIgnored(path26, stats) {
186955
+ if (this.options.atomic && DOT_RE.test(path26))
186369
186956
  return true;
186370
186957
  if (!this._userIgnored) {
186371
186958
  const { cwd } = this.options;
@@ -186375,17 +186962,17 @@ var FSWatcher = class extends EventEmitter {
186375
186962
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
186376
186963
  this._userIgnored = anymatch(list, void 0);
186377
186964
  }
186378
- return this._userIgnored(path23, stats);
186965
+ return this._userIgnored(path26, stats);
186379
186966
  }
186380
- _isntIgnored(path23, stat4) {
186381
- return !this._isIgnored(path23, stat4);
186967
+ _isntIgnored(path26, stat4) {
186968
+ return !this._isIgnored(path26, stat4);
186382
186969
  }
186383
186970
  /**
186384
186971
  * Provides a set of common helpers and properties relating to symlink handling.
186385
186972
  * @param path file or directory pattern being watched
186386
186973
  */
186387
- _getWatchHelpers(path23) {
186388
- return new WatchHelper(path23, this.options.followSymlinks, this);
186974
+ _getWatchHelpers(path26) {
186975
+ return new WatchHelper(path26, this.options.followSymlinks, this);
186389
186976
  }
186390
186977
  // Directory helpers
186391
186978
  // -----------------
@@ -186417,63 +187004,63 @@ var FSWatcher = class extends EventEmitter {
186417
187004
  * @param item base path of item/directory
186418
187005
  */
186419
187006
  _remove(directory, item, isDirectory) {
186420
- const path23 = sp2.join(directory, item);
186421
- const fullPath = sp2.resolve(path23);
186422
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path23) || this._watched.has(fullPath);
186423
- if (!this._throttle("remove", path23, 100))
187007
+ const path26 = sp2.join(directory, item);
187008
+ const fullPath = sp2.resolve(path26);
187009
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path26) || this._watched.has(fullPath);
187010
+ if (!this._throttle("remove", path26, 100))
186424
187011
  return;
186425
187012
  if (!isDirectory && this._watched.size === 1) {
186426
187013
  this.add(directory, item, true);
186427
187014
  }
186428
- const wp = this._getWatchedDir(path23);
187015
+ const wp = this._getWatchedDir(path26);
186429
187016
  const nestedDirectoryChildren = wp.getChildren();
186430
- nestedDirectoryChildren.forEach((nested) => this._remove(path23, nested));
187017
+ nestedDirectoryChildren.forEach((nested) => this._remove(path26, nested));
186431
187018
  const parent = this._getWatchedDir(directory);
186432
187019
  const wasTracked = parent.has(item);
186433
187020
  parent.remove(item);
186434
187021
  if (this._symlinkPaths.has(fullPath)) {
186435
187022
  this._symlinkPaths.delete(fullPath);
186436
187023
  }
186437
- let relPath = path23;
187024
+ let relPath = path26;
186438
187025
  if (this.options.cwd)
186439
- relPath = sp2.relative(this.options.cwd, path23);
187026
+ relPath = sp2.relative(this.options.cwd, path26);
186440
187027
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
186441
187028
  const event = this._pendingWrites.get(relPath).cancelWait();
186442
187029
  if (event === EVENTS.ADD)
186443
187030
  return;
186444
187031
  }
186445
- this._watched.delete(path23);
187032
+ this._watched.delete(path26);
186446
187033
  this._watched.delete(fullPath);
186447
187034
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
186448
- if (wasTracked && !this._isIgnored(path23))
186449
- this._emit(eventName, path23);
186450
- this._closePath(path23);
187035
+ if (wasTracked && !this._isIgnored(path26))
187036
+ this._emit(eventName, path26);
187037
+ this._closePath(path26);
186451
187038
  }
186452
187039
  /**
186453
187040
  * Closes all watchers for a path
186454
187041
  */
186455
- _closePath(path23) {
186456
- this._closeFile(path23);
186457
- const dir = sp2.dirname(path23);
186458
- this._getWatchedDir(dir).remove(sp2.basename(path23));
187042
+ _closePath(path26) {
187043
+ this._closeFile(path26);
187044
+ const dir = sp2.dirname(path26);
187045
+ this._getWatchedDir(dir).remove(sp2.basename(path26));
186459
187046
  }
186460
187047
  /**
186461
187048
  * Closes only file-specific watchers
186462
187049
  */
186463
- _closeFile(path23) {
186464
- const closers = this._closers.get(path23);
187050
+ _closeFile(path26) {
187051
+ const closers = this._closers.get(path26);
186465
187052
  if (!closers)
186466
187053
  return;
186467
187054
  closers.forEach((closer) => closer());
186468
- this._closers.delete(path23);
187055
+ this._closers.delete(path26);
186469
187056
  }
186470
- _addPathCloser(path23, closer) {
187057
+ _addPathCloser(path26, closer) {
186471
187058
  if (!closer)
186472
187059
  return;
186473
- let list = this._closers.get(path23);
187060
+ let list = this._closers.get(path26);
186474
187061
  if (!list) {
186475
187062
  list = [];
186476
- this._closers.set(path23, list);
187063
+ this._closers.set(path26, list);
186477
187064
  }
186478
187065
  list.push(closer);
186479
187066
  }
@@ -186522,8 +187109,8 @@ var PortAllocator = class {
186522
187109
  };
186523
187110
 
186524
187111
  // src/lib/dev/stable-port-allocator.ts
186525
- import * as fs12 from "fs";
186526
- import * as path8 from "path";
187112
+ import * as fs13 from "fs";
187113
+ import * as path9 from "path";
186527
187114
  var PORT_RANGE_START2 = 4e4;
186528
187115
  var PORT_RANGE_END2 = 49999;
186529
187116
  var StablePortAllocator = class {
@@ -186532,16 +187119,16 @@ var StablePortAllocator = class {
186532
187119
  savedPorts = {};
186533
187120
  usedPorts = /* @__PURE__ */ new Set();
186534
187121
  constructor(projectRoot, key = "default") {
186535
- this.portsDir = path8.join(projectRoot, ".specific", "keys", key);
186536
- this.portsFilePath = path8.join(this.portsDir, "ports.json");
187122
+ this.portsDir = path9.join(projectRoot, ".specific", "keys", key);
187123
+ this.portsFilePath = path9.join(this.portsDir, "ports.json");
186537
187124
  this.loadPorts();
186538
187125
  }
186539
187126
  loadPorts() {
186540
- if (!fs12.existsSync(this.portsFilePath)) {
187127
+ if (!fs13.existsSync(this.portsFilePath)) {
186541
187128
  return;
186542
187129
  }
186543
187130
  try {
186544
- const content = fs12.readFileSync(this.portsFilePath, "utf-8");
187131
+ const content = fs13.readFileSync(this.portsFilePath, "utf-8");
186545
187132
  const data = JSON.parse(content);
186546
187133
  if (data.version === 1 && data.ports) {
186547
187134
  this.savedPorts = data.ports;
@@ -186554,14 +187141,14 @@ var StablePortAllocator = class {
186554
187141
  }
186555
187142
  }
186556
187143
  savePorts() {
186557
- if (!fs12.existsSync(this.portsDir)) {
186558
- fs12.mkdirSync(this.portsDir, { recursive: true });
187144
+ if (!fs13.existsSync(this.portsDir)) {
187145
+ fs13.mkdirSync(this.portsDir, { recursive: true });
186559
187146
  }
186560
187147
  const data = {
186561
187148
  version: 1,
186562
187149
  ports: this.savedPorts
186563
187150
  };
186564
- fs12.writeFileSync(this.portsFilePath, JSON.stringify(data, null, 2));
187151
+ fs13.writeFileSync(this.portsFilePath, JSON.stringify(data, null, 2));
186565
187152
  }
186566
187153
  allocateRandom() {
186567
187154
  const rangeSize = PORT_RANGE_END2 - PORT_RANGE_START2 + 1;
@@ -186591,331 +187178,7 @@ var StablePortAllocator = class {
186591
187178
  import * as fs14 from "fs";
186592
187179
  import * as path10 from "path";
186593
187180
  import * as net from "net";
186594
- import * as os7 from "os";
186595
187181
  import { spawn } from "child_process";
186596
-
186597
- // src/lib/bin/types.ts
186598
- var BinaryNotFoundError = class extends Error {
186599
- constructor(binaryName, version, platformInfo) {
186600
- super(
186601
- `Binary "${binaryName}" version ${version} is not available for ${platformInfo.platform}-${platformInfo.arch}`
186602
- );
186603
- this.binaryName = binaryName;
186604
- this.version = version;
186605
- this.platformInfo = platformInfo;
186606
- this.name = "BinaryNotFoundError";
186607
- }
186608
- };
186609
- var DownloadError = class extends Error {
186610
- constructor(url, statusCode, cause) {
186611
- super(
186612
- statusCode ? `Failed to download ${url}: HTTP ${statusCode}` : `Failed to download ${url}: ${cause?.message || "Unknown error"}`
186613
- );
186614
- this.url = url;
186615
- this.statusCode = statusCode;
186616
- this.cause = cause;
186617
- this.name = "DownloadError";
186618
- }
186619
- };
186620
- var ExtractionError = class extends Error {
186621
- constructor(archivePath, cause) {
186622
- super(
186623
- `Failed to extract ${archivePath}: ${cause?.message || "Unknown error"}`
186624
- );
186625
- this.archivePath = archivePath;
186626
- this.cause = cause;
186627
- this.name = "ExtractionError";
186628
- }
186629
- };
186630
-
186631
- // src/lib/bin/manager.ts
186632
- import * as fs13 from "fs";
186633
- import * as path9 from "path";
186634
- import * as os6 from "os";
186635
- import { createReadStream } from "fs";
186636
- import { createTarExtractor, extractTo } from "tar-vern";
186637
- function getBinBaseDir() {
186638
- return path9.join(os6.homedir(), ".specific", "bin");
186639
- }
186640
- function getPlatformInfo() {
186641
- const platform5 = os6.platform();
186642
- const arch3 = os6.arch();
186643
- if (platform5 !== "darwin" && platform5 !== "linux") {
186644
- throw new Error(
186645
- `Unsupported platform: ${platform5}. Only macOS and Linux are supported.`
186646
- );
186647
- }
186648
- const archStr = arch3;
186649
- let mappedArch;
186650
- if (archStr === "x64" || archStr === "x86_64") {
186651
- mappedArch = "x64";
186652
- } else if (archStr === "arm64" || archStr === "aarch64") {
186653
- mappedArch = "arm64";
186654
- } else {
186655
- throw new Error(
186656
- `Unsupported architecture: ${arch3}. Only x64 and arm64 are supported.`
186657
- );
186658
- }
186659
- return { platform: platform5, arch: mappedArch };
186660
- }
186661
- function getBinaryDir(definition, version, platformInfo) {
186662
- return path9.join(
186663
- getBinBaseDir(),
186664
- definition.name,
186665
- version,
186666
- `${platformInfo.platform}-${platformInfo.arch}`
186667
- );
186668
- }
186669
- function isBinaryInstalled(definition, version, platformInfo) {
186670
- const binDir = getBinaryDir(definition, version, platformInfo);
186671
- for (const execPath of definition.executables) {
186672
- const fullPath = path9.join(binDir, execPath);
186673
- if (!fs13.existsSync(fullPath)) {
186674
- return false;
186675
- }
186676
- }
186677
- return true;
186678
- }
186679
- function buildDownloadUrl(definition, version, platformInfo) {
186680
- const mapping = definition.platformMapping[platformInfo.platform]?.[platformInfo.arch];
186681
- if (!mapping) {
186682
- throw new BinaryNotFoundError(definition.name, version, platformInfo);
186683
- }
186684
- return definition.urlTemplate.replace(/{version}/g, version).replace(/{platform}/g, mapping.platform).replace(/{arch}/g, mapping.arch);
186685
- }
186686
- async function downloadFile(url, destPath, onProgress) {
186687
- let response;
186688
- try {
186689
- response = await fetch(url);
186690
- } catch (error) {
186691
- throw new DownloadError(url, void 0, error);
186692
- }
186693
- if (!response.ok) {
186694
- throw new DownloadError(url, response.status);
186695
- }
186696
- const totalBytes = parseInt(
186697
- response.headers.get("content-length") || "0",
186698
- 10
186699
- );
186700
- let bytesDownloaded = 0;
186701
- const parentDir = path9.dirname(destPath);
186702
- if (!fs13.existsSync(parentDir)) {
186703
- fs13.mkdirSync(parentDir, { recursive: true });
186704
- }
186705
- const partPath = destPath + ".part";
186706
- const fileStream = fs13.createWriteStream(partPath);
186707
- try {
186708
- const reader = response.body.getReader();
186709
- while (true) {
186710
- const { done, value } = await reader.read();
186711
- if (done) break;
186712
- bytesDownloaded += value.length;
186713
- fileStream.write(Buffer.from(value));
186714
- if (onProgress) {
186715
- onProgress({
186716
- phase: "downloading",
186717
- bytesDownloaded,
186718
- totalBytes: totalBytes || void 0,
186719
- percent: totalBytes ? Math.round(bytesDownloaded / totalBytes * 100) : void 0
186720
- });
186721
- }
186722
- }
186723
- await new Promise((resolve7, reject) => {
186724
- fileStream.end((err) => {
186725
- if (err) reject(err);
186726
- else resolve7();
186727
- });
186728
- });
186729
- fs13.renameSync(partPath, destPath);
186730
- } catch (error) {
186731
- try {
186732
- fileStream.close();
186733
- if (fs13.existsSync(partPath)) {
186734
- fs13.unlinkSync(partPath);
186735
- }
186736
- } catch {
186737
- }
186738
- throw new DownloadError(url, void 0, error);
186739
- }
186740
- }
186741
- async function extractTarball(archivePath, destDir, definition, onProgress) {
186742
- onProgress?.({ phase: "extracting" });
186743
- if (!fs13.existsSync(destDir)) {
186744
- fs13.mkdirSync(destDir, { recursive: true });
186745
- }
186746
- try {
186747
- const fileStream = createReadStream(archivePath);
186748
- const entries = createTarExtractor(fileStream, "gzip");
186749
- await extractTo(entries, destDir);
186750
- onProgress?.({ phase: "finalizing" });
186751
- for (const execPath of definition.executables) {
186752
- const fullPath = path9.join(destDir, execPath);
186753
- if (fs13.existsSync(fullPath)) {
186754
- fs13.chmodSync(fullPath, 493);
186755
- }
186756
- }
186757
- } catch (error) {
186758
- throw new ExtractionError(archivePath, error);
186759
- }
186760
- }
186761
- async function ensureBinary(definition, version, onProgress) {
186762
- const platformInfo = getPlatformInfo();
186763
- const resolvedVersion = version || definition.versions[0];
186764
- if (!definition.versions.includes(resolvedVersion)) {
186765
- throw new Error(
186766
- `Version ${resolvedVersion} not supported for ${definition.name}. Supported versions: ${definition.versions.join(", ")}`
186767
- );
186768
- }
186769
- const binDir = getBinaryDir(definition, resolvedVersion, platformInfo);
186770
- if (!isBinaryInstalled(definition, resolvedVersion, platformInfo)) {
186771
- const url = buildDownloadUrl(definition, resolvedVersion, platformInfo);
186772
- if (definition.type === "binary") {
186773
- if (definition.executables.length !== 1) {
186774
- throw new Error(
186775
- `Binary type definitions must have exactly one executable, got ${definition.executables.length}`
186776
- );
186777
- }
186778
- if (!fs13.existsSync(binDir)) {
186779
- fs13.mkdirSync(binDir, { recursive: true });
186780
- }
186781
- const execPath = path9.join(binDir, definition.executables[0]);
186782
- await downloadFile(url, execPath, onProgress);
186783
- fs13.chmodSync(execPath, 493);
186784
- onProgress?.({ phase: "finalizing" });
186785
- } else {
186786
- const downloadDir = path9.join(getBinBaseDir(), "downloads");
186787
- const archiveName = `${definition.name}-${resolvedVersion}-${platformInfo.platform}-${platformInfo.arch}.tar.gz`;
186788
- const archivePath = path9.join(downloadDir, archiveName);
186789
- try {
186790
- await downloadFile(url, archivePath, onProgress);
186791
- await extractTarball(archivePath, binDir, definition, onProgress);
186792
- } finally {
186793
- try {
186794
- if (fs13.existsSync(archivePath)) {
186795
- fs13.unlinkSync(archivePath);
186796
- }
186797
- } catch {
186798
- }
186799
- }
186800
- }
186801
- }
186802
- const executables = {};
186803
- for (const execPath of definition.executables) {
186804
- const name = path9.basename(execPath);
186805
- executables[name] = path9.join(binDir, execPath);
186806
- }
186807
- const libraryPath = definition.libraryDir ? path9.join(binDir, definition.libraryDir) : void 0;
186808
- return {
186809
- rootDir: binDir,
186810
- version: resolvedVersion,
186811
- executables,
186812
- libraryPath
186813
- };
186814
- }
186815
-
186816
- // src/lib/bin/definitions/postgres.ts
186817
- var postgresBinary = {
186818
- name: "postgres",
186819
- versions: ["18.1"],
186820
- // URL: binaries.specific.dev/{SOFTWARE}/{VERSION}/{ARCH}.tar.gz
186821
- urlTemplate: "https://binaries.specific.dev/postgres/{version}/{arch}.tar.gz",
186822
- platformMapping: {
186823
- darwin: {
186824
- arm64: { platform: "macos", arch: "macos_arm" },
186825
- x64: { platform: "macos", arch: "macos_x86" }
186826
- },
186827
- linux: {
186828
- arm64: { platform: "linux", arch: "linux_arm" },
186829
- x64: { platform: "linux", arch: "linux_x86" }
186830
- }
186831
- },
186832
- // Archive contains bin/ and lib/ directories at root
186833
- stripComponents: 0,
186834
- // Core PostgreSQL executables (in bin/ directory)
186835
- executables: ["bin/postgres", "bin/initdb"],
186836
- // Library directory relative to root (for setting LD_LIBRARY_PATH)
186837
- libraryDir: "lib"
186838
- };
186839
-
186840
- // src/lib/bin/definitions/redis.ts
186841
- var redisBinary = {
186842
- name: "redis",
186843
- versions: ["8.4.0"],
186844
- // URL: binaries.specific.dev/{SOFTWARE}/{VERSION}/{ARCH}.tar.gz
186845
- urlTemplate: "https://binaries.specific.dev/redis/{version}/{arch}.tar.gz",
186846
- platformMapping: {
186847
- darwin: {
186848
- arm64: { platform: "macos", arch: "macos_arm" },
186849
- x64: { platform: "macos", arch: "macos_x86" }
186850
- },
186851
- linux: {
186852
- arm64: { platform: "linux", arch: "linux_arm" },
186853
- x64: { platform: "linux", arch: "linux_x86" }
186854
- }
186855
- },
186856
- // Binaries are at the root of the archive
186857
- stripComponents: 0,
186858
- // Core Redis executable
186859
- executables: ["redis-server"]
186860
- };
186861
-
186862
- // src/lib/bin/definitions/electric.ts
186863
- var electricBinary = {
186864
- name: "electric",
186865
- versions: ["1.2.11"],
186866
- // URL: binaries.specific.dev/{SOFTWARE}/{VERSION}/{ARCH}.tar.gz
186867
- urlTemplate: "https://binaries.specific.dev/electric/{version}/{arch}.tar.gz",
186868
- platformMapping: {
186869
- darwin: {
186870
- arm64: { platform: "macos", arch: "macos_arm" },
186871
- x64: { platform: "macos", arch: "macos_x86" }
186872
- },
186873
- linux: {
186874
- arm64: { platform: "linux", arch: "linux_arm" },
186875
- x64: { platform: "linux", arch: "linux_x86" }
186876
- }
186877
- },
186878
- // Binaries are at the root of the archive
186879
- stripComponents: 0,
186880
- // Core Electric executable
186881
- executables: ["electric"]
186882
- };
186883
-
186884
- // src/lib/bin/definitions/drizzle-gateway.ts
186885
- var drizzleGatewayBinary = {
186886
- name: "drizzle-gateway",
186887
- versions: ["1.2.0"],
186888
- // Official Drizzle Gateway download URL from R2
186889
- urlTemplate: "https://pub-e240a4fd7085425baf4a7951e7611520.r2.dev/drizzle-gateway-{version}-{arch}",
186890
- platformMapping: {
186891
- darwin: {
186892
- arm64: { platform: "macos", arch: "macos-arm64" },
186893
- x64: { platform: "macos", arch: "macos-x64" }
186894
- },
186895
- linux: {
186896
- arm64: { platform: "linux", arch: "linux-arm64" },
186897
- x64: { platform: "linux", arch: "linux-x64" }
186898
- }
186899
- },
186900
- // Raw binary, not a tarball
186901
- type: "binary",
186902
- // The executable name
186903
- executables: ["drizzle-gateway"]
186904
- };
186905
-
186906
- // src/lib/dev/database-manager.ts
186907
- function getLibraryEnv(binary) {
186908
- if (!binary.libraryPath) {
186909
- return {};
186910
- }
186911
- const platform5 = os7.platform();
186912
- if (platform5 === "darwin") {
186913
- return { DYLD_LIBRARY_PATH: binary.libraryPath };
186914
- } else if (platform5 === "linux") {
186915
- return { LD_LIBRARY_PATH: binary.libraryPath };
186916
- }
186917
- return {};
186918
- }
186919
187182
  async function startPostgres(pg, port, dataDir, onProgress) {
186920
187183
  const binary = await ensureBinary(postgresBinary, void 0, onProgress);
186921
187184
  const dbDataPath = path10.join(process.cwd(), dataDir, pg.name);
@@ -187038,7 +187301,7 @@ async function startStorage(storage, port, dataDir) {
187038
187301
  };
187039
187302
  }
187040
187303
  async function runCommand(command, args, env2) {
187041
- return new Promise((resolve7, reject) => {
187304
+ return new Promise((resolve9, reject) => {
187042
187305
  const proc = spawn(command, args, {
187043
187306
  stdio: ["ignore", "pipe", "pipe"],
187044
187307
  env: env2
@@ -187049,7 +187312,7 @@ async function runCommand(command, args, env2) {
187049
187312
  });
187050
187313
  proc.on("close", (code) => {
187051
187314
  if (code === 0) {
187052
- resolve7();
187315
+ resolve9();
187053
187316
  } else {
187054
187317
  reject(new Error(`Command failed with code ${code}: ${stderr}`));
187055
187318
  }
@@ -187058,7 +187321,7 @@ async function runCommand(command, args, env2) {
187058
187321
  });
187059
187322
  }
187060
187323
  async function createPostgresDatabase(postgresPath, dataDir, dbName, env2) {
187061
- return new Promise((resolve7, reject) => {
187324
+ return new Promise((resolve9, reject) => {
187062
187325
  const proc = spawn(
187063
187326
  postgresPath,
187064
187327
  ["--single", "-D", dataDir, "postgres"],
@@ -187072,7 +187335,7 @@ async function createPostgresDatabase(postgresPath, dataDir, dbName, env2) {
187072
187335
  stderr += data.toString();
187073
187336
  });
187074
187337
  proc.on("close", (code) => {
187075
- resolve7();
187338
+ resolve9();
187076
187339
  });
187077
187340
  proc.on("error", reject);
187078
187341
  proc.stdin?.write(`CREATE DATABASE "${dbName}";
@@ -187092,33 +187355,33 @@ async function waitForTcpPort(host, port, timeoutMs = 3e4) {
187092
187355
  throw new Error(`Port ${port} did not become available within timeout`);
187093
187356
  }
187094
187357
  function checkTcpPort(host, port) {
187095
- return new Promise((resolve7) => {
187358
+ return new Promise((resolve9) => {
187096
187359
  const socket = new net.Socket();
187097
187360
  socket.setTimeout(1e3);
187098
187361
  socket.on("connect", () => {
187099
187362
  socket.destroy();
187100
- resolve7(true);
187363
+ resolve9(true);
187101
187364
  });
187102
187365
  socket.on("timeout", () => {
187103
187366
  socket.destroy();
187104
- resolve7(false);
187367
+ resolve9(false);
187105
187368
  });
187106
187369
  socket.on("error", () => {
187107
187370
  socket.destroy();
187108
- resolve7(false);
187371
+ resolve9(false);
187109
187372
  });
187110
187373
  socket.connect(port, host);
187111
187374
  });
187112
187375
  }
187113
187376
  async function stopProcess(proc) {
187114
- return new Promise((resolve7) => {
187377
+ return new Promise((resolve9) => {
187115
187378
  if (proc.killed || proc.exitCode !== null) {
187116
- resolve7();
187379
+ resolve9();
187117
187380
  return;
187118
187381
  }
187119
187382
  proc.once("exit", () => {
187120
187383
  clearTimeout(forceKillTimeout);
187121
- resolve7();
187384
+ resolve9();
187122
187385
  });
187123
187386
  proc.kill("SIGTERM");
187124
187387
  const forceKillTimeout = setTimeout(() => {
@@ -187129,7 +187392,7 @@ async function stopProcess(proc) {
187129
187392
  });
187130
187393
  }
187131
187394
  function sleep2(ms) {
187132
- return new Promise((resolve7) => setTimeout(resolve7, ms));
187395
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
187133
187396
  }
187134
187397
 
187135
187398
  // src/lib/dev/service-runner.ts
@@ -187138,7 +187401,7 @@ import { spawn as spawn2 } from "child_process";
187138
187401
  // src/lib/local/parser.ts
187139
187402
  var import_hcl2_json_parser2 = __toESM(require_dist(), 1);
187140
187403
  import { readFile, writeFile } from "fs/promises";
187141
- import { existsSync as existsSync10 } from "fs";
187404
+ import { existsSync as existsSync11 } from "fs";
187142
187405
  var { parseToObject: parseToObject2 } = import_hcl2_json_parser2.default;
187143
187406
  var LOCAL_FILE = "specific.local";
187144
187407
  var HEADER_COMMENT = `# Local secrets and configuration
@@ -187179,7 +187442,7 @@ async function parseLocalFile(content) {
187179
187442
  return { secrets, configs };
187180
187443
  }
187181
187444
  async function loadLocal() {
187182
- if (!existsSync10(LOCAL_FILE)) {
187445
+ if (!existsSync11(LOCAL_FILE)) {
187183
187446
  return { secrets: /* @__PURE__ */ new Map(), configs: /* @__PURE__ */ new Map() };
187184
187447
  }
187185
187448
  const content = await readFile(LOCAL_FILE, "utf-8");
@@ -187219,7 +187482,7 @@ ${newLine}
187219
187482
  }
187220
187483
  async function saveLocalSecret(name, value) {
187221
187484
  let content = "";
187222
- if (existsSync10(LOCAL_FILE)) {
187485
+ if (existsSync11(LOCAL_FILE)) {
187223
187486
  content = await readFile(LOCAL_FILE, "utf-8");
187224
187487
  } else {
187225
187488
  content = HEADER_COMMENT;
@@ -187229,7 +187492,7 @@ async function saveLocalSecret(name, value) {
187229
187492
  }
187230
187493
  async function saveLocalConfig(name, value) {
187231
187494
  let content = "";
187232
- if (existsSync10(LOCAL_FILE)) {
187495
+ if (existsSync11(LOCAL_FILE)) {
187233
187496
  content = await readFile(LOCAL_FILE, "utf-8");
187234
187497
  } else {
187235
187498
  content = HEADER_COMMENT;
@@ -187238,6 +187501,13 @@ async function saveLocalConfig(name, value) {
187238
187501
  await writeFile(LOCAL_FILE, content);
187239
187502
  }
187240
187503
 
187504
+ // src/lib/dev/reshape-manager.ts
187505
+ function appendSearchPathToUrl(baseUrl, searchPath) {
187506
+ const url = new URL(baseUrl);
187507
+ url.searchParams.set("options", `-c search_path=${searchPath}`);
187508
+ return url.toString();
187509
+ }
187510
+
187241
187511
  // src/lib/dev/env-resolver.ts
187242
187512
  var MissingSecretError = class extends Error {
187243
187513
  constructor(secretName) {
@@ -187271,7 +187541,7 @@ config {
187271
187541
  this.name = "MissingConfigError";
187272
187542
  }
187273
187543
  };
187274
- function resolveEnvValue(value, resources, secrets, configs, servicePort, serviceEndpoints, currentServicePorts) {
187544
+ function resolveEnvValue(value, resources, secrets, configs, servicePort, serviceEndpoints, currentServicePorts, publicUrls) {
187275
187545
  if (typeof value === "string") {
187276
187546
  return value;
187277
187547
  }
@@ -187312,11 +187582,25 @@ function resolveEnvValue(value, resources, secrets, configs, servicePort, servic
187312
187582
  }
187313
187583
  switch (serviceRef.attribute) {
187314
187584
  case "url":
187585
+ case "private_url":
187315
187586
  return `localhost:${endpoint.port}`;
187316
187587
  case "host":
187317
187588
  return "localhost";
187318
187589
  case "port":
187319
187590
  return String(endpoint.port);
187591
+ case "public_url": {
187592
+ if (!publicUrls) {
187593
+ throw new Error("public_url reference used but no public URL map provided");
187594
+ }
187595
+ const k8sName = endpointName === "default" ? serviceRef.serviceName : `${serviceRef.serviceName}-${endpointName}`;
187596
+ const publicUrl = publicUrls.get(k8sName);
187597
+ if (!publicUrl) {
187598
+ throw new Error(
187599
+ `No public URL found for service "${serviceRef.serviceName}" endpoint "${endpointName}"`
187600
+ );
187601
+ }
187602
+ return publicUrl;
187603
+ }
187320
187604
  default:
187321
187605
  throw new Error(`Unknown service attribute: ${serviceRef.attribute}`);
187322
187606
  }
@@ -187328,6 +187612,9 @@ function resolveEnvValue(value, resources, secrets, configs, servicePort, servic
187328
187612
  }
187329
187613
  switch (value.attribute) {
187330
187614
  case "url":
187615
+ if (pg.reshapeEnabled && pg.reshapeSearchPath) {
187616
+ return appendSearchPathToUrl(pg.url, pg.reshapeSearchPath);
187617
+ }
187331
187618
  return pg.url;
187332
187619
  case "host":
187333
187620
  return pg.host;
@@ -187421,7 +187708,7 @@ function resolveEnvValue(value, resources, secrets, configs, servicePort, servic
187421
187708
  throw new Error(`Unknown reference type`);
187422
187709
  }
187423
187710
  }
187424
- function resolveEnv(env2, resources, secrets, configs, servicePort, serviceEndpoints, currentServicePorts) {
187711
+ function resolveEnv(env2, resources, secrets, configs, servicePort, serviceEndpoints, currentServicePorts, publicUrls) {
187425
187712
  if (!env2) {
187426
187713
  return {};
187427
187714
  }
@@ -187434,7 +187721,8 @@ function resolveEnv(env2, resources, secrets, configs, servicePort, serviceEndpo
187434
187721
  configs,
187435
187722
  servicePort,
187436
187723
  serviceEndpoints,
187437
- currentServicePorts
187724
+ currentServicePorts,
187725
+ publicUrls
187438
187726
  );
187439
187727
  }
187440
187728
  return resolved;
@@ -187458,7 +187746,7 @@ function resolveEnvForExec(env2, resources, secrets, configs) {
187458
187746
  }
187459
187747
 
187460
187748
  // src/lib/dev/service-runner.ts
187461
- function startService(service, resources, secrets, configs, endpointPorts, serviceEndpoints, onLog) {
187749
+ function startService(service, resources, secrets, configs, endpointPorts, serviceEndpoints, onLog, publicUrls) {
187462
187750
  const command = service.dev?.command ?? service.command;
187463
187751
  if (!command) {
187464
187752
  throw new Error(`Service "${service.name}" has no command`);
@@ -187475,7 +187763,8 @@ function startService(service, resources, secrets, configs, endpointPorts, servi
187475
187763
  configs,
187476
187764
  defaultPort,
187477
187765
  serviceEndpoints,
187478
- endpointPorts
187766
+ endpointPorts,
187767
+ publicUrls
187479
187768
  );
187480
187769
  const child = spawn2(command, {
187481
187770
  shell: true,
@@ -187507,14 +187796,14 @@ function startService(service, resources, secrets, configs, endpointPorts, servi
187507
187796
  ports: endpointPorts,
187508
187797
  process: child,
187509
187798
  async stop() {
187510
- return new Promise((resolve7) => {
187799
+ return new Promise((resolve9) => {
187511
187800
  if (child.killed || child.exitCode !== null) {
187512
- resolve7();
187801
+ resolve9();
187513
187802
  return;
187514
187803
  }
187515
187804
  child.once("exit", () => {
187516
187805
  clearTimeout(forceKillTimeout);
187517
- resolve7();
187806
+ resolve9();
187518
187807
  });
187519
187808
  child.kill("SIGTERM");
187520
187809
  const forceKillTimeout = setTimeout(() => {
@@ -187595,7 +187884,7 @@ var InstanceStateManager = class {
187595
187884
  }
187596
187885
  continue;
187597
187886
  }
187598
- await new Promise((resolve7) => setTimeout(resolve7, 100));
187887
+ await new Promise((resolve9) => setTimeout(resolve9, 100));
187599
187888
  } else {
187600
187889
  throw e;
187601
187890
  }
@@ -187878,7 +188167,7 @@ async function startHttpProxy(services, certificate, getState, instanceKey = "de
187878
188167
  handleRequest
187879
188168
  );
187880
188169
  httpsServer.on("upgrade", handleUpgrade);
187881
- return new Promise((resolve7, reject) => {
188170
+ return new Promise((resolve9, reject) => {
187882
188171
  let httpStarted = false;
187883
188172
  let httpsStarted = false;
187884
188173
  let failed = false;
@@ -187889,7 +188178,7 @@ async function startHttpProxy(services, certificate, getState, instanceKey = "de
187889
188178
  "proxy",
187890
188179
  `HTTP/HTTPS proxy started on ports ${HTTP_PORT}/${HTTPS_PORT}`
187891
188180
  );
187892
- resolve7({
188181
+ resolve9({
187893
188182
  httpPort: HTTP_PORT,
187894
188183
  httpsPort: HTTPS_PORT,
187895
188184
  updateServices,
@@ -187901,13 +188190,13 @@ async function startHttpProxy(services, certificate, getState, instanceKey = "de
187901
188190
  writeLog("proxy", "Certificate updated");
187902
188191
  },
187903
188192
  async stop() {
187904
- return new Promise((resolve8) => {
188193
+ return new Promise((resolve10) => {
187905
188194
  let closed = 0;
187906
188195
  const onClose = () => {
187907
188196
  closed++;
187908
188197
  if (closed === 2) {
187909
188198
  clearTimeout(forceCloseTimeout);
187910
- resolve8();
188199
+ resolve10();
187911
188200
  }
187912
188201
  };
187913
188202
  httpServer.close(onClose);
@@ -187915,7 +188204,7 @@ async function startHttpProxy(services, certificate, getState, instanceKey = "de
187915
188204
  const forceCloseTimeout = setTimeout(() => {
187916
188205
  httpServer.closeAllConnections?.();
187917
188206
  httpsServer.closeAllConnections?.();
187918
- resolve8();
188207
+ resolve10();
187919
188208
  }, 2e3);
187920
188209
  });
187921
188210
  }
@@ -188078,7 +188367,7 @@ function serveFileContent(res, filePath, contentType, statusCode = 200) {
188078
188367
  }
188079
188368
  }
188080
188369
  async function startAdminServer(getState) {
188081
- return new Promise((resolve7, reject) => {
188370
+ return new Promise((resolve9, reject) => {
188082
188371
  const server = http.createServer((req, res) => {
188083
188372
  const url = new URL(req.url || "/", "http://localhost");
188084
188373
  res.setHeader("Access-Control-Allow-Origin", "*");
@@ -188110,7 +188399,7 @@ async function startAdminServer(getState) {
188110
188399
  }
188111
188400
  const port = addr.port;
188112
188401
  writeLog("admin", `Admin API server started on port ${port}`);
188113
- resolve7({
188402
+ resolve9({
188114
188403
  port,
188115
188404
  stop: () => new Promise((res, rej) => {
188116
188405
  server.close((err) => err ? rej(err) : res());
@@ -188126,7 +188415,7 @@ import { spawn as spawn3 } from "child_process";
188126
188415
 
188127
188416
  // src/lib/secrets/parser.ts
188128
188417
  import { readFile as readFile2, writeFile as writeFile2, mkdir } from "fs/promises";
188129
- import { existsSync as existsSync13 } from "fs";
188418
+ import { existsSync as existsSync14 } from "fs";
188130
188419
  import * as path13 from "path";
188131
188420
  import * as crypto2 from "crypto";
188132
188421
  var GENERATED_SECRETS_FILE = ".specific/generated-secrets.json";
@@ -188144,7 +188433,7 @@ function generateRandomString(length = 64) {
188144
188433
  return result;
188145
188434
  }
188146
188435
  async function loadGeneratedSecrets() {
188147
- if (!existsSync13(GENERATED_SECRETS_FILE)) {
188436
+ if (!existsSync14(GENERATED_SECRETS_FILE)) {
188148
188437
  return /* @__PURE__ */ new Map();
188149
188438
  }
188150
188439
  const content = await readFile2(GENERATED_SECRETS_FILE, "utf-8");
@@ -188153,13 +188442,13 @@ async function loadGeneratedSecrets() {
188153
188442
  }
188154
188443
  async function saveGeneratedSecret(name, value) {
188155
188444
  let secrets = {};
188156
- if (existsSync13(GENERATED_SECRETS_FILE)) {
188445
+ if (existsSync14(GENERATED_SECRETS_FILE)) {
188157
188446
  const content = await readFile2(GENERATED_SECRETS_FILE, "utf-8");
188158
188447
  secrets = JSON.parse(content);
188159
188448
  }
188160
188449
  secrets[name] = value;
188161
188450
  const dir = path13.dirname(GENERATED_SECRETS_FILE);
188162
- if (!existsSync13(dir)) {
188451
+ if (!existsSync14(dir)) {
188163
188452
  await mkdir(dir, { recursive: true });
188164
188453
  }
188165
188454
  await writeFile2(GENERATED_SECRETS_FILE, JSON.stringify(secrets, null, 2) + "\n");
@@ -188271,33 +188560,33 @@ async function waitForTcpPort2(host, port, timeoutMs = 3e4) {
188271
188560
  throw new Error(`Electric port ${port} did not become available within timeout`);
188272
188561
  }
188273
188562
  function checkTcpPort2(host, port) {
188274
- return new Promise((resolve7) => {
188563
+ return new Promise((resolve9) => {
188275
188564
  const socket = new net2.Socket();
188276
188565
  socket.setTimeout(1e3);
188277
188566
  socket.on("connect", () => {
188278
188567
  socket.destroy();
188279
- resolve7(true);
188568
+ resolve9(true);
188280
188569
  });
188281
188570
  socket.on("timeout", () => {
188282
188571
  socket.destroy();
188283
- resolve7(false);
188572
+ resolve9(false);
188284
188573
  });
188285
188574
  socket.on("error", () => {
188286
188575
  socket.destroy();
188287
- resolve7(false);
188576
+ resolve9(false);
188288
188577
  });
188289
188578
  socket.connect(port, host);
188290
188579
  });
188291
188580
  }
188292
188581
  async function stopProcess2(proc) {
188293
- return new Promise((resolve7) => {
188582
+ return new Promise((resolve9) => {
188294
188583
  if (proc.killed || proc.exitCode !== null) {
188295
- resolve7();
188584
+ resolve9();
188296
188585
  return;
188297
188586
  }
188298
188587
  proc.once("exit", () => {
188299
188588
  clearTimeout(forceKillTimeout);
188300
- resolve7();
188589
+ resolve9();
188301
188590
  });
188302
188591
  proc.kill("SIGTERM");
188303
188592
  const forceKillTimeout = setTimeout(() => {
@@ -188308,7 +188597,7 @@ async function stopProcess2(proc) {
188308
188597
  });
188309
188598
  }
188310
188599
  function sleep3(ms) {
188311
- return new Promise((resolve7) => setTimeout(resolve7, ms));
188600
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
188312
188601
  }
188313
188602
 
188314
188603
  // src/lib/dev/drizzle-gateway-manager.ts
@@ -188392,33 +188681,33 @@ async function waitForTcpPort3(host, port, timeoutMs = 3e4) {
188392
188681
  );
188393
188682
  }
188394
188683
  function checkTcpPort3(host, port) {
188395
- return new Promise((resolve7) => {
188684
+ return new Promise((resolve9) => {
188396
188685
  const socket = new net3.Socket();
188397
188686
  socket.setTimeout(1e3);
188398
188687
  socket.on("connect", () => {
188399
188688
  socket.destroy();
188400
- resolve7(true);
188689
+ resolve9(true);
188401
188690
  });
188402
188691
  socket.on("timeout", () => {
188403
188692
  socket.destroy();
188404
- resolve7(false);
188693
+ resolve9(false);
188405
188694
  });
188406
188695
  socket.on("error", () => {
188407
188696
  socket.destroy();
188408
- resolve7(false);
188697
+ resolve9(false);
188409
188698
  });
188410
188699
  socket.connect(port, host);
188411
188700
  });
188412
188701
  }
188413
188702
  async function stopProcess3(proc) {
188414
- return new Promise((resolve7) => {
188703
+ return new Promise((resolve9) => {
188415
188704
  if (proc.killed || proc.exitCode !== null) {
188416
- resolve7();
188705
+ resolve9();
188417
188706
  return;
188418
188707
  }
188419
188708
  proc.once("exit", () => {
188420
188709
  clearTimeout(forceKillTimeout);
188421
- resolve7();
188710
+ resolve9();
188422
188711
  });
188423
188712
  proc.kill("SIGTERM");
188424
188713
  const forceKillTimeout = setTimeout(() => {
@@ -188429,7 +188718,7 @@ async function stopProcess3(proc) {
188429
188718
  });
188430
188719
  }
188431
188720
  function sleep4(ms) {
188432
- return new Promise((resolve7) => setTimeout(resolve7, ms));
188721
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
188433
188722
  }
188434
188723
 
188435
188724
  // src/lib/dev/sync-detector.ts
@@ -188449,6 +188738,288 @@ function detectSyncDatabases(config) {
188449
188738
  return needsSync;
188450
188739
  }
188451
188740
 
188741
+ // src/lib/dev/reshape-watcher.ts
188742
+ import * as fs18 from "fs";
188743
+ import * as path15 from "path";
188744
+ import { spawnSync } from "child_process";
188745
+ function getMigrationFiles(dir, log) {
188746
+ log(`Scanning migrations directory: ${dir}`);
188747
+ if (!fs18.existsSync(dir)) {
188748
+ log(`Migrations directory does not exist: ${dir}`);
188749
+ return [];
188750
+ }
188751
+ const files = fs18.readdirSync(dir);
188752
+ log(`Found ${files.length} files in directory`);
188753
+ const tomlFiles = files.filter((f) => f.endsWith(".toml")).sort((a, b) => a.localeCompare(b));
188754
+ log(`Found ${tomlFiles.length} .toml migration files: ${tomlFiles.join(", ") || "(none)"}`);
188755
+ return tomlFiles;
188756
+ }
188757
+ function getMigrationName(filename) {
188758
+ return filename.replace(/\.toml$/, "");
188759
+ }
188760
+ function getSchemaName(filename) {
188761
+ return `migration_${getMigrationName(filename)}`;
188762
+ }
188763
+ function runReshape(args, databaseUrl, migrationsDir, reshapeBinaryPath, log) {
188764
+ const fullArgs = ["migration", ...args];
188765
+ if (args[0] === "start") {
188766
+ fullArgs.push("--dirs", ".");
188767
+ }
188768
+ log(`Executing: reshape ${fullArgs.join(" ")}`);
188769
+ log(` Working directory: ${migrationsDir}`);
188770
+ try {
188771
+ const result = spawnSync(reshapeBinaryPath, fullArgs, {
188772
+ cwd: migrationsDir,
188773
+ env: {
188774
+ ...process.env,
188775
+ DB_URL: databaseUrl
188776
+ },
188777
+ encoding: "utf-8",
188778
+ stdio: ["pipe", "pipe", "pipe"]
188779
+ });
188780
+ const stdout = result.stdout || "";
188781
+ const stderr = result.stderr || "";
188782
+ const output = stdout + stderr;
188783
+ if (result.status !== 0) {
188784
+ log(`Reshape command failed with exit code ${result.status}`);
188785
+ if (stdout.trim()) log(` stdout: ${stdout.trim()}`);
188786
+ if (stderr.trim()) log(` stderr: ${stderr.trim()}`);
188787
+ return { success: false, output };
188788
+ }
188789
+ log(`Reshape command succeeded`);
188790
+ if (stdout.trim()) log(` stdout: ${stdout.trim()}`);
188791
+ if (stderr.trim()) log(` stderr: ${stderr.trim()}`);
188792
+ return { success: true, output };
188793
+ } catch (err) {
188794
+ const message = err instanceof Error ? err.message : String(err);
188795
+ log(`Reshape command threw exception: ${message}`);
188796
+ return { success: false, output: message };
188797
+ }
188798
+ }
188799
+ function makeReadOnly(filePath, log) {
188800
+ try {
188801
+ fs18.chmodSync(filePath, 292);
188802
+ log(`Set file permissions to read-only (444): ${filePath}`);
188803
+ } catch (err) {
188804
+ const message = err instanceof Error ? err.message : String(err);
188805
+ log(`Warning: Failed to make file read-only: ${filePath} - ${message}`);
188806
+ }
188807
+ }
188808
+ function createReshapeWatcher(options2) {
188809
+ const { databaseUrl, migrationsDir, reshapeBinaryPath, onSearchPathChanged, onError, log } = options2;
188810
+ let watcher = null;
188811
+ let currentMigrationFiles = [];
188812
+ let startedMigration = null;
188813
+ log(`Creating Reshape watcher`);
188814
+ log(` Migrations directory: ${migrationsDir}`);
188815
+ log(` Database URL: ${databaseUrl.replace(/:[^:@]+@/, ":***@")}`);
188816
+ const initialize = async () => {
188817
+ log(`Initializing Reshape migrations...`);
188818
+ currentMigrationFiles = getMigrationFiles(migrationsDir, log);
188819
+ if (currentMigrationFiles.length === 0) {
188820
+ log("No migration files found - skipping Reshape initialization");
188821
+ return null;
188822
+ }
188823
+ log(`Processing ${currentMigrationFiles.length} migration file(s)`);
188824
+ if (currentMigrationFiles.length === 1) {
188825
+ const migrationFile = currentMigrationFiles[0];
188826
+ const migrationName = getMigrationName(migrationFile);
188827
+ log(`Single migration detected: ${migrationFile}`);
188828
+ log(`Starting migration "${migrationName}" (will not be completed, allowing iteration)`);
188829
+ const result = runReshape(["start"], databaseUrl, migrationsDir, reshapeBinaryPath, log);
188830
+ if (!result.success) {
188831
+ log(`ERROR: Failed to start migration "${migrationName}"`);
188832
+ onError(new Error(`Failed to start migration: ${result.output}`));
188833
+ return null;
188834
+ }
188835
+ startedMigration = migrationFile;
188836
+ log(`Migration "${migrationName}" started successfully`);
188837
+ log(` Started migration file: ${startedMigration}`);
188838
+ } else {
188839
+ const secondToLast = currentMigrationFiles[currentMigrationFiles.length - 2];
188840
+ const secondToLastName = getMigrationName(secondToLast);
188841
+ const lastMigration = currentMigrationFiles[currentMigrationFiles.length - 1];
188842
+ const lastMigrationName = getMigrationName(lastMigration);
188843
+ log(`Multiple migrations detected (${currentMigrationFiles.length} total)`);
188844
+ log(` Migrations to complete: ${currentMigrationFiles.slice(0, -1).join(", ")}`);
188845
+ log(` Migration to start (latest): ${lastMigration}`);
188846
+ log(`Completing all migrations up to and including "${secondToLastName}"...`);
188847
+ const completeResult = runReshape(
188848
+ ["start", "--complete", "--migration", secondToLastName],
188849
+ databaseUrl,
188850
+ migrationsDir,
188851
+ reshapeBinaryPath,
188852
+ log
188853
+ );
188854
+ if (!completeResult.success) {
188855
+ log(`ERROR: Failed to complete migrations up to "${secondToLastName}"`);
188856
+ onError(new Error(`Failed to complete migrations: ${completeResult.output}`));
188857
+ return null;
188858
+ }
188859
+ log(`Successfully completed ${currentMigrationFiles.length - 1} migration(s)`);
188860
+ log(`Making completed migration files read-only...`);
188861
+ for (let i = 0; i < currentMigrationFiles.length - 1; i++) {
188862
+ const filePath = path15.join(migrationsDir, currentMigrationFiles[i]);
188863
+ makeReadOnly(filePath, log);
188864
+ }
188865
+ log(`Starting latest migration "${lastMigrationName}" (will not be completed, allowing iteration)`);
188866
+ const startResult = runReshape(["start"], databaseUrl, migrationsDir, reshapeBinaryPath, log);
188867
+ if (!startResult.success) {
188868
+ log(`ERROR: Failed to start migration "${lastMigrationName}"`);
188869
+ onError(new Error(`Failed to start last migration: ${startResult.output}`));
188870
+ return null;
188871
+ }
188872
+ startedMigration = lastMigration;
188873
+ log(`Migration "${lastMigrationName}" started successfully`);
188874
+ log(` Started migration file: ${startedMigration}`);
188875
+ }
188876
+ const searchPath = startedMigration ? getSchemaName(startedMigration) : null;
188877
+ if (searchPath) {
188878
+ log(`Search path (schema name): ${searchPath}`);
188879
+ } else {
188880
+ log(`No search_path (no migration started)`);
188881
+ }
188882
+ log(`Reshape initialization complete`);
188883
+ return searchPath;
188884
+ };
188885
+ const startWatching = () => {
188886
+ log(`Starting file watcher for migrations directory...`);
188887
+ if (!fs18.existsSync(migrationsDir)) {
188888
+ log(`Migrations directory does not exist, creating: ${migrationsDir}`);
188889
+ fs18.mkdirSync(migrationsDir, { recursive: true });
188890
+ }
188891
+ log(`Watching directory: ${migrationsDir}`);
188892
+ watcher = chokidar_default.watch(migrationsDir, {
188893
+ persistent: true,
188894
+ ignoreInitial: true,
188895
+ depth: 0,
188896
+ awaitWriteFinish: {
188897
+ stabilityThreshold: 200,
188898
+ pollInterval: 50
188899
+ }
188900
+ });
188901
+ watcher.on("change", (filePath) => {
188902
+ const filename = path15.basename(filePath);
188903
+ if (!filename.endsWith(".toml")) return;
188904
+ log(`File change detected: ${filename}`);
188905
+ log(` Full path: ${filePath}`);
188906
+ log(` Currently started migration: ${startedMigration || "(none)"}`);
188907
+ if (filename === startedMigration) {
188908
+ log(`Changed file is the currently started migration - will abort and restart`);
188909
+ log(`Aborting current migration...`);
188910
+ const abortResult = runReshape(["abort"], databaseUrl, migrationsDir, reshapeBinaryPath, log);
188911
+ if (!abortResult.success) {
188912
+ log(`ERROR: Failed to abort migration`);
188913
+ onError(new Error(`Failed to abort migration: ${abortResult.output}`));
188914
+ return;
188915
+ }
188916
+ log(`Migration aborted successfully`);
188917
+ log(`Restarting migration...`);
188918
+ const startResult = runReshape(["start"], databaseUrl, migrationsDir, reshapeBinaryPath, log);
188919
+ if (!startResult.success) {
188920
+ log(`ERROR: Failed to restart migration`);
188921
+ onError(new Error(`Failed to restart migration: ${startResult.output}`));
188922
+ return;
188923
+ }
188924
+ log(`Migration restarted successfully`);
188925
+ log(`Note: Services do NOT need to restart (search_path unchanged)`);
188926
+ } else {
188927
+ log(`Warning: Completed migration "${filename}" was modified`);
188928
+ log(` This file should be read-only. Changes may not take effect.`);
188929
+ log(` To modify this migration, you may need to reset and re-run all migrations.`);
188930
+ }
188931
+ });
188932
+ watcher.on("add", (filePath) => {
188933
+ const filename = path15.basename(filePath);
188934
+ if (!filename.endsWith(".toml")) return;
188935
+ log(`New file detected: ${filename}`);
188936
+ log(` Full path: ${filePath}`);
188937
+ const newMigrationFiles = getMigrationFiles(migrationsDir, log);
188938
+ log(`Updated migration list: ${newMigrationFiles.join(", ")}`);
188939
+ const isNewest = newMigrationFiles[newMigrationFiles.length - 1] === filename;
188940
+ log(`Is this the newest migration? ${isNewest}`);
188941
+ if (!isNewest) {
188942
+ log(`Warning: Added migration "${filename}" is not the latest in lexical order`);
188943
+ log(` Latest migration: ${newMigrationFiles[newMigrationFiles.length - 1]}`);
188944
+ log(` Manual intervention may be needed to ensure correct migration order`);
188945
+ return;
188946
+ }
188947
+ if (startedMigration) {
188948
+ const previousMigrationName = getMigrationName(startedMigration);
188949
+ log(`Completing previous migration "${previousMigrationName}"...`);
188950
+ const completeResult = runReshape(["complete"], databaseUrl, migrationsDir, reshapeBinaryPath, log);
188951
+ if (!completeResult.success) {
188952
+ log(`ERROR: Failed to complete previous migration`);
188953
+ onError(new Error(`Failed to complete previous migration: ${completeResult.output}`));
188954
+ return;
188955
+ }
188956
+ log(`Previous migration completed successfully`);
188957
+ const completedPath = path15.join(migrationsDir, startedMigration);
188958
+ makeReadOnly(completedPath, log);
188959
+ } else {
188960
+ log(`No previous migration was started`);
188961
+ }
188962
+ const newMigrationName = getMigrationName(filename);
188963
+ log(`Starting new migration "${newMigrationName}"...`);
188964
+ const startResult = runReshape(["start"], databaseUrl, migrationsDir, reshapeBinaryPath, log);
188965
+ if (!startResult.success) {
188966
+ log(`ERROR: Failed to start new migration`);
188967
+ onError(new Error(`Failed to start new migration: ${startResult.output}`));
188968
+ return;
188969
+ }
188970
+ const previousStarted = startedMigration;
188971
+ startedMigration = filename;
188972
+ currentMigrationFiles = newMigrationFiles;
188973
+ log(`New migration started successfully`);
188974
+ log(` Previous started migration: ${previousStarted || "(none)"}`);
188975
+ log(` New started migration: ${startedMigration}`);
188976
+ const newSearchPath = getSchemaName(filename);
188977
+ log(`New search_path: ${newSearchPath}`);
188978
+ log(`Notifying services to restart with new search_path...`);
188979
+ onSearchPathChanged(newSearchPath);
188980
+ });
188981
+ watcher.on("unlink", (filePath) => {
188982
+ const filename = path15.basename(filePath);
188983
+ if (!filename.endsWith(".toml")) return;
188984
+ log(`File deleted: ${filename}`);
188985
+ log(` Full path: ${filePath}`);
188986
+ log(` Currently started migration: ${startedMigration || "(none)"}`);
188987
+ if (filename === startedMigration) {
188988
+ log(`Warning: The currently started migration was deleted!`);
188989
+ log(` Manual intervention is required to recover.`);
188990
+ } else {
188991
+ log(`Warning: A migration file was deleted. This may cause issues.`);
188992
+ }
188993
+ log(` Automatic recovery is not supported - manual intervention may be needed.`);
188994
+ });
188995
+ watcher.on("error", (error) => {
188996
+ const err = error instanceof Error ? error : new Error(String(error));
188997
+ log(`File watcher error: ${err.message}`);
188998
+ onError(err);
188999
+ });
189000
+ watcher.on("ready", () => {
189001
+ log(`File watcher is ready and monitoring for changes`);
189002
+ });
189003
+ log(`File watcher started`);
189004
+ };
189005
+ const close = async () => {
189006
+ log(`Closing Reshape watcher...`);
189007
+ if (watcher) {
189008
+ await watcher.close();
189009
+ watcher = null;
189010
+ log(`File watcher closed`);
189011
+ } else {
189012
+ log(`No file watcher was active`);
189013
+ }
189014
+ log(`Reshape watcher closed`);
189015
+ };
189016
+ return {
189017
+ initialize,
189018
+ startWatching,
189019
+ close
189020
+ };
189021
+ }
189022
+
188452
189023
  // src/lib/dev/resource-starter.ts
188453
189024
  function findRequiredResources(service) {
188454
189025
  const required = { postgres: [], redis: [], storage: [] };
@@ -188507,11 +189078,14 @@ async function startResources(options2) {
188507
189078
  const instance = await startPostgres(pg, port, dataDir, (progress) => {
188508
189079
  callbacks.onResourceProgress?.(pg.name, progress);
188509
189080
  });
189081
+ if (pg.reshape?.enabled) {
189082
+ instance.reshapeEnabled = true;
189083
+ }
188510
189084
  resources.set(pg.name, instance);
188511
189085
  startedResources.push(instance);
188512
189086
  callbacks.onResourceReady?.(pg.name, instance);
188513
189087
  log(`Postgres "${pg.name}" ready`);
188514
- await stateManager.registerDatabase(pg.name, {
189088
+ const dbState = {
188515
189089
  engine: "postgres",
188516
189090
  port: instance.port,
188517
189091
  host: instance.host,
@@ -188519,7 +189093,14 @@ async function startResources(options2) {
188519
189093
  password: instance.password,
188520
189094
  dbName: instance.dbName,
188521
189095
  url: instance.url
188522
- });
189096
+ };
189097
+ if (instance.reshapeEnabled) {
189098
+ dbState.reshapeEnabled = true;
189099
+ if (instance.reshapeSearchPath) {
189100
+ dbState.reshapeSearchPath = instance.reshapeSearchPath;
189101
+ }
189102
+ }
189103
+ await stateManager.registerDatabase(pg.name, dbState);
188523
189104
  }
188524
189105
  for (const redis of redisConfigs) {
188525
189106
  if (signal?.cancelled) {
@@ -188621,24 +189202,24 @@ function watchConfigFile(configPath, debounceMs, onChange) {
188621
189202
  }
188622
189203
 
188623
189204
  // src/lib/dev/subdomain-generator.ts
188624
- import * as fs18 from "fs";
188625
- import * as path15 from "path";
189205
+ import * as fs19 from "fs";
189206
+ import * as path16 from "path";
188626
189207
  import { generateSlug } from "random-word-slugs";
188627
189208
  var StableSubdomainAllocator = class {
188628
189209
  tunnelsDir;
188629
189210
  tunnelsFilePath;
188630
189211
  baseSlug = null;
188631
189212
  constructor(projectRoot, key = "default") {
188632
- this.tunnelsDir = path15.join(projectRoot, ".specific", "keys", key);
188633
- this.tunnelsFilePath = path15.join(this.tunnelsDir, "tunnels.json");
189213
+ this.tunnelsDir = path16.join(projectRoot, ".specific", "keys", key);
189214
+ this.tunnelsFilePath = path16.join(this.tunnelsDir, "tunnels.json");
188634
189215
  this.loadTunnels();
188635
189216
  }
188636
189217
  loadTunnels() {
188637
- if (!fs18.existsSync(this.tunnelsFilePath)) {
189218
+ if (!fs19.existsSync(this.tunnelsFilePath)) {
188638
189219
  return;
188639
189220
  }
188640
189221
  try {
188641
- const content = fs18.readFileSync(this.tunnelsFilePath, "utf-8");
189222
+ const content = fs19.readFileSync(this.tunnelsFilePath, "utf-8");
188642
189223
  const data = JSON.parse(content);
188643
189224
  if (data.version === 1 && data.baseSlug) {
188644
189225
  this.baseSlug = data.baseSlug;
@@ -188648,14 +189229,14 @@ var StableSubdomainAllocator = class {
188648
189229
  }
188649
189230
  }
188650
189231
  saveTunnels() {
188651
- if (!fs18.existsSync(this.tunnelsDir)) {
188652
- fs18.mkdirSync(this.tunnelsDir, { recursive: true });
189232
+ if (!fs19.existsSync(this.tunnelsDir)) {
189233
+ fs19.mkdirSync(this.tunnelsDir, { recursive: true });
188653
189234
  }
188654
189235
  const data = {
188655
189236
  version: 1,
188656
189237
  baseSlug: this.baseSlug
188657
189238
  };
188658
- fs18.writeFileSync(this.tunnelsFilePath, JSON.stringify(data, null, 2));
189239
+ fs19.writeFileSync(this.tunnelsFilePath, JSON.stringify(data, null, 2));
188659
189240
  }
188660
189241
  generateBaseSlug() {
188661
189242
  return generateSlug(2, {
@@ -188718,9 +189299,9 @@ async function startTunnel(serviceName, endpointName, port, subdomain, callbacks
188718
189299
  }
188719
189300
 
188720
189301
  // src/lib/dev/proxy-registry.ts
188721
- import * as fs19 from "fs";
188722
- import * as path16 from "path";
188723
- import * as os8 from "os";
189302
+ import * as fs20 from "fs";
189303
+ import * as path17 from "path";
189304
+ import * as os7 from "os";
188724
189305
  import * as net4 from "net";
188725
189306
  var ProxyRegistryManager = class {
188726
189307
  proxyDir;
@@ -188730,14 +189311,14 @@ var ProxyRegistryManager = class {
188730
189311
  isOwner = false;
188731
189312
  registryWatcher = null;
188732
189313
  constructor() {
188733
- this.proxyDir = path16.join(os8.homedir(), ".specific", "proxy");
188734
- this.ownerPath = path16.join(this.proxyDir, "owner.json");
188735
- this.registryPath = path16.join(this.proxyDir, "registry.json");
188736
- this.lockPath = path16.join(this.proxyDir, "registry.lock");
189314
+ this.proxyDir = path17.join(os7.homedir(), ".specific", "proxy");
189315
+ this.ownerPath = path17.join(this.proxyDir, "owner.json");
189316
+ this.registryPath = path17.join(this.proxyDir, "registry.json");
189317
+ this.lockPath = path17.join(this.proxyDir, "registry.lock");
188737
189318
  }
188738
189319
  ensureProxyDir() {
188739
- if (!fs19.existsSync(this.proxyDir)) {
188740
- fs19.mkdirSync(this.proxyDir, { recursive: true });
189320
+ if (!fs20.existsSync(this.proxyDir)) {
189321
+ fs20.mkdirSync(this.proxyDir, { recursive: true });
188741
189322
  }
188742
189323
  }
188743
189324
  isProcessRunning(pid) {
@@ -188754,7 +189335,7 @@ var ProxyRegistryManager = class {
188754
189335
  * This catches cases where the owner process is alive but the proxy has crashed.
188755
189336
  */
188756
189337
  isProxyListening(port, timeoutMs = 1e3) {
188757
- return new Promise((resolve7) => {
189338
+ return new Promise((resolve9) => {
188758
189339
  const socket = new net4.Socket();
188759
189340
  let resolved = false;
188760
189341
  const cleanup = () => {
@@ -188766,15 +189347,15 @@ var ProxyRegistryManager = class {
188766
189347
  socket.setTimeout(timeoutMs);
188767
189348
  socket.on("connect", () => {
188768
189349
  cleanup();
188769
- resolve7(true);
189350
+ resolve9(true);
188770
189351
  });
188771
189352
  socket.on("timeout", () => {
188772
189353
  cleanup();
188773
- resolve7(false);
189354
+ resolve9(false);
188774
189355
  });
188775
189356
  socket.on("error", () => {
188776
189357
  cleanup();
188777
- resolve7(false);
189358
+ resolve9(false);
188778
189359
  });
188779
189360
  socket.connect(port, "127.0.0.1");
188780
189361
  });
@@ -188794,15 +189375,15 @@ var ProxyRegistryManager = class {
188794
189375
  const startTime = Date.now();
188795
189376
  while (Date.now() - startTime < timeoutMs) {
188796
189377
  try {
188797
- const fd = fs19.openSync(
189378
+ const fd = fs20.openSync(
188798
189379
  this.lockPath,
188799
- fs19.constants.O_CREAT | fs19.constants.O_EXCL | fs19.constants.O_WRONLY
189380
+ fs20.constants.O_CREAT | fs20.constants.O_EXCL | fs20.constants.O_WRONLY
188800
189381
  );
188801
- fs19.writeSync(fd, String(process.pid));
188802
- fs19.closeSync(fd);
189382
+ fs20.writeSync(fd, String(process.pid));
189383
+ fs20.closeSync(fd);
188803
189384
  return () => {
188804
189385
  try {
188805
- fs19.unlinkSync(this.lockPath);
189386
+ fs20.unlinkSync(this.lockPath);
188806
189387
  } catch {
188807
189388
  }
188808
189389
  };
@@ -188811,21 +189392,21 @@ var ProxyRegistryManager = class {
188811
189392
  if (err.code === "EEXIST") {
188812
189393
  try {
188813
189394
  const lockPid = parseInt(
188814
- fs19.readFileSync(this.lockPath, "utf-8").trim(),
189395
+ fs20.readFileSync(this.lockPath, "utf-8").trim(),
188815
189396
  10
188816
189397
  );
188817
189398
  if (!this.isProcessRunning(lockPid)) {
188818
- fs19.unlinkSync(this.lockPath);
189399
+ fs20.unlinkSync(this.lockPath);
188819
189400
  continue;
188820
189401
  }
188821
189402
  } catch {
188822
189403
  try {
188823
- fs19.unlinkSync(this.lockPath);
189404
+ fs20.unlinkSync(this.lockPath);
188824
189405
  } catch {
188825
189406
  }
188826
189407
  continue;
188827
189408
  }
188828
- await new Promise((resolve7) => setTimeout(resolve7, 100));
189409
+ await new Promise((resolve9) => setTimeout(resolve9, 100));
188829
189410
  } else {
188830
189411
  throw e;
188831
189412
  }
@@ -188840,8 +189421,8 @@ var ProxyRegistryManager = class {
188840
189421
  async claimProxyOwnership(key) {
188841
189422
  const releaseLock = await this.acquireLock();
188842
189423
  try {
188843
- if (fs19.existsSync(this.ownerPath)) {
188844
- const content = fs19.readFileSync(this.ownerPath, "utf-8");
189424
+ if (fs20.existsSync(this.ownerPath)) {
189425
+ const content = fs20.readFileSync(this.ownerPath, "utf-8");
188845
189426
  const ownerFile2 = JSON.parse(content);
188846
189427
  if (await this.isProxyOwnerHealthy(ownerFile2.owner.pid)) {
188847
189428
  return false;
@@ -188871,11 +189452,11 @@ var ProxyRegistryManager = class {
188871
189452
  }
188872
189453
  const releaseLock = await this.acquireLock();
188873
189454
  try {
188874
- if (fs19.existsSync(this.ownerPath)) {
188875
- const content = fs19.readFileSync(this.ownerPath, "utf-8");
189455
+ if (fs20.existsSync(this.ownerPath)) {
189456
+ const content = fs20.readFileSync(this.ownerPath, "utf-8");
188876
189457
  const ownerFile = JSON.parse(content);
188877
189458
  if (ownerFile.owner.pid === process.pid) {
188878
- fs19.unlinkSync(this.ownerPath);
189459
+ fs20.unlinkSync(this.ownerPath);
188879
189460
  }
188880
189461
  }
188881
189462
  this.isOwner = false;
@@ -188887,12 +189468,12 @@ var ProxyRegistryManager = class {
188887
189468
  * Get the current proxy owner.
188888
189469
  */
188889
189470
  async getProxyOwner() {
188890
- if (!fs19.existsSync(this.ownerPath)) {
189471
+ if (!fs20.existsSync(this.ownerPath)) {
188891
189472
  return null;
188892
189473
  }
188893
189474
  const releaseLock = await this.acquireLock();
188894
189475
  try {
188895
- const content = fs19.readFileSync(this.ownerPath, "utf-8");
189476
+ const content = fs20.readFileSync(this.ownerPath, "utf-8");
188896
189477
  const ownerFile = JSON.parse(content);
188897
189478
  if (!await this.isProxyOwnerHealthy(ownerFile.owner.pid)) {
188898
189479
  return null;
@@ -188986,7 +189567,7 @@ var ProxyRegistryManager = class {
188986
189567
  */
188987
189568
  watchRegistry(onChange) {
188988
189569
  this.ensureProxyDir();
188989
- if (!fs19.existsSync(this.registryPath)) {
189570
+ if (!fs20.existsSync(this.registryPath)) {
188990
189571
  const emptyRegistry = {
188991
189572
  version: 1,
188992
189573
  keys: {},
@@ -189030,13 +189611,13 @@ var ProxyRegistryManager = class {
189030
189611
  async attemptElection(key) {
189031
189612
  const releaseLock = await this.acquireLock();
189032
189613
  try {
189033
- if (fs19.existsSync(this.ownerPath)) {
189034
- const content = fs19.readFileSync(this.ownerPath, "utf-8");
189614
+ if (fs20.existsSync(this.ownerPath)) {
189615
+ const content = fs20.readFileSync(this.ownerPath, "utf-8");
189035
189616
  const ownerFile2 = JSON.parse(content);
189036
189617
  if (await this.isProxyOwnerHealthy(ownerFile2.owner.pid)) {
189037
189618
  return false;
189038
189619
  }
189039
- fs19.unlinkSync(this.ownerPath);
189620
+ fs20.unlinkSync(this.ownerPath);
189040
189621
  }
189041
189622
  const ownerFile = {
189042
189623
  version: 1,
@@ -189054,7 +189635,7 @@ var ProxyRegistryManager = class {
189054
189635
  }
189055
189636
  }
189056
189637
  readRegistry() {
189057
- if (!fs19.existsSync(this.registryPath)) {
189638
+ if (!fs20.existsSync(this.registryPath)) {
189058
189639
  return {
189059
189640
  version: 1,
189060
189641
  keys: {},
@@ -189062,7 +189643,7 @@ var ProxyRegistryManager = class {
189062
189643
  };
189063
189644
  }
189064
189645
  try {
189065
- const content = fs19.readFileSync(this.registryPath, "utf-8");
189646
+ const content = fs20.readFileSync(this.registryPath, "utf-8");
189066
189647
  return JSON.parse(content);
189067
189648
  } catch {
189068
189649
  return {
@@ -189075,8 +189656,8 @@ var ProxyRegistryManager = class {
189075
189656
  writeFileAtomic(filePath, data) {
189076
189657
  this.ensureProxyDir();
189077
189658
  const tmpPath = filePath + ".tmp";
189078
- fs19.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
189079
- fs19.renameSync(tmpPath, filePath);
189659
+ fs20.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
189660
+ fs20.renameSync(tmpPath, filePath);
189080
189661
  }
189081
189662
  };
189082
189663
 
@@ -189175,11 +189756,6 @@ function ConfigInput({ configName, onSubmit, onCancel }) {
189175
189756
  return /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text5, null, "Enter value for config ", /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, configName), ":"), /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, "> "), /* @__PURE__ */ React5.createElement(Text5, null, value), /* @__PURE__ */ React5.createElement(Text5, { color: "gray" }, "|")), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "(Press Enter to save, Esc to cancel)"));
189176
189757
  }
189177
189758
 
189178
- // src/lib/ui/interactive.ts
189179
- function isInteractive() {
189180
- return process.stdin.isTTY === true && process.stdout.isTTY === true;
189181
- }
189182
-
189183
189759
  // src/commands/dev.tsx
189184
189760
  var COLORS = ["cyan", "yellow", "green", "magenta", "blue"];
189185
189761
  function DevUI({ instanceKey, tunnelEnabled }) {
@@ -189226,6 +189802,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189226
189802
  const proxyRegistryRef = useRef(null);
189227
189803
  const electricInstancesRef = useRef([]);
189228
189804
  const drizzleGatewayRef = useRef(null);
189805
+ const reshapeWatchersRef = useRef([]);
189229
189806
  const registryWatcherCleanupRef = useRef(null);
189230
189807
  const electionIntervalRef = useRef(null);
189231
189808
  const tunnelsRef = useRef([]);
@@ -189233,6 +189810,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189233
189810
  const adminServerRef = useRef(null);
189234
189811
  const servicesRef = useRef([]);
189235
189812
  const resourcesRef = useRef(/* @__PURE__ */ new Map());
189813
+ const restartServicesRef = useRef(null);
189236
189814
  const [reloadTrigger, setReloadTrigger] = useState5(0);
189237
189815
  const [readyToStart, setReadyToStart] = useState5(() => caFilesExist());
189238
189816
  const shutdown2 = async () => {
@@ -189259,6 +189837,8 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189259
189837
  ...servicesRef.current.map((service) => service.stop()),
189260
189838
  // Stop all Electric instances
189261
189839
  ...electricInstancesRef.current.map((electric) => electric.stop()),
189840
+ // Stop all Reshape watchers
189841
+ ...reshapeWatchersRef.current.map((watcher) => watcher.close()),
189262
189842
  // Stop Drizzle Gateway
189263
189843
  drizzleGatewayRef.current?.stop(),
189264
189844
  // Stop all resources
@@ -189266,6 +189846,8 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189266
189846
  // Stop all tunnels
189267
189847
  ...tunnelsRef.current.map((tunnel) => tunnel.stop())
189268
189848
  ]);
189849
+ reshapeWatchersRef.current = [];
189850
+ restartServicesRef.current = null;
189269
189851
  if (proxyRegistryRef.current) {
189270
189852
  await proxyRegistryRef.current.unregisterServices(instanceKey);
189271
189853
  await proxyRegistryRef.current.releaseProxyOwnership();
@@ -189298,6 +189880,8 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189298
189880
  ...servicesRef.current.map((service) => service.stop()),
189299
189881
  // Stop all Electric instances
189300
189882
  ...electricInstancesRef.current.map((electric) => electric.stop()),
189883
+ // Stop all Reshape watchers
189884
+ ...reshapeWatchersRef.current.map((watcher) => watcher.close()),
189301
189885
  // Stop Drizzle Gateway
189302
189886
  drizzleGatewayRef.current?.stop(),
189303
189887
  // Stop all resources
@@ -189305,7 +189889,12 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189305
189889
  // Stop all tunnels
189306
189890
  ...tunnelsRef.current.map((tunnel) => tunnel.stop())
189307
189891
  ]);
189892
+ if (tunnelsRef.current.length > 0) {
189893
+ await new Promise((resolve9) => setTimeout(resolve9, 1500));
189894
+ }
189308
189895
  electricInstancesRef.current = [];
189896
+ reshapeWatchersRef.current = [];
189897
+ restartServicesRef.current = null;
189309
189898
  drizzleGatewayRef.current = null;
189310
189899
  proxyRef.current = null;
189311
189900
  adminServerRef.current = null;
@@ -189370,10 +189959,10 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189370
189959
  }, [state.status]);
189371
189960
  useEffect3(() => {
189372
189961
  if (state.status !== "running") return;
189373
- const configPath = path17.join(process.cwd(), "specific.hcl");
189962
+ const configPath = path18.join(process.cwd(), "specific.hcl");
189374
189963
  const watcher = watchConfigFile(configPath, 1e3, () => {
189375
189964
  try {
189376
- const hcl = fs20.readFileSync(configPath, "utf-8");
189965
+ const hcl = fs21.readFileSync(configPath, "utf-8");
189377
189966
  parseConfig(hcl).then(() => {
189378
189967
  triggerReload();
189379
189968
  }).catch((err) => {
@@ -189498,8 +190087,8 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189498
190087
  }));
189499
190088
  return;
189500
190089
  }
189501
- const configPath = path17.join(process.cwd(), "specific.hcl");
189502
- if (!fs20.existsSync(configPath)) {
190090
+ const configPath = path18.join(process.cwd(), "specific.hcl");
190091
+ if (!fs21.existsSync(configPath)) {
189503
190092
  writeLog("system", "Waiting for specific.hcl to appear");
189504
190093
  setState((s) => ({
189505
190094
  ...s,
@@ -189518,7 +190107,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189518
190107
  }
189519
190108
  let config2;
189520
190109
  try {
189521
- const hcl = fs20.readFileSync(configPath, "utf-8");
190110
+ const hcl = fs21.readFileSync(configPath, "utf-8");
189522
190111
  config2 = await parseConfig(hcl);
189523
190112
  } catch (err) {
189524
190113
  setState((s) => ({
@@ -189624,7 +190213,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189624
190213
  const drizzleGateway = await startDrizzleGateway(
189625
190214
  postgresResources,
189626
190215
  drizzlePort,
189627
- path17.join(process.cwd(), ".specific", "keys", instanceKey)
190216
+ path18.join(process.cwd(), ".specific", "keys", instanceKey)
189628
190217
  );
189629
190218
  startedDrizzleGateway = drizzleGateway;
189630
190219
  drizzleGatewayRef.current = drizzleGateway;
@@ -189637,6 +190226,59 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189637
190226
  }
189638
190227
  }
189639
190228
  if (cancelled) return;
190229
+ const startedReshapeWatchers = [];
190230
+ const hasReshapeEnabled = config2.postgres.some((pg) => pg.reshape?.enabled);
190231
+ const reshapeBin = hasReshapeEnabled ? await ensureBinary(reshapeBinary) : void 0;
190232
+ for (const pg of config2.postgres) {
190233
+ if (pg.reshape?.enabled) {
190234
+ const resource = resources2.get(pg.name);
190235
+ if (!resource) continue;
190236
+ const migrationsDir = path18.resolve(
190237
+ process.cwd(),
190238
+ pg.reshape.migrations_dir ?? "migrations"
190239
+ );
190240
+ writeLog("system", `Initializing Reshape migrations for "${pg.name}" from ${migrationsDir}`);
190241
+ const watcher = createReshapeWatcher({
190242
+ databaseUrl: resource.url,
190243
+ migrationsDir,
190244
+ reshapeBinaryPath: reshapeBin.executables["reshape"],
190245
+ onSearchPathChanged: async (newSearchPath) => {
190246
+ resource.reshapeSearchPath = newSearchPath ?? void 0;
190247
+ if (newSearchPath) {
190248
+ await stateManager.updateDatabase(pg.name, {
190249
+ reshapeSearchPath: newSearchPath
190250
+ });
190251
+ }
190252
+ writeLog("system", `Reshape search_path changed for "${pg.name}": ${newSearchPath}`);
190253
+ if (restartServicesRef.current) {
190254
+ await restartServicesRef.current();
190255
+ }
190256
+ },
190257
+ onError: (err) => {
190258
+ writeLog("system:error", `Reshape error for "${pg.name}": ${err.message}`);
190259
+ },
190260
+ log: (msg) => writeLog("system", `[reshape:${pg.name}] ${msg}`)
190261
+ });
190262
+ try {
190263
+ const initialSearchPath = await watcher.initialize();
190264
+ resource.reshapeSearchPath = initialSearchPath ?? void 0;
190265
+ if (initialSearchPath) {
190266
+ await stateManager.updateDatabase(pg.name, {
190267
+ reshapeSearchPath: initialSearchPath
190268
+ });
190269
+ writeLog("system", `Reshape search_path for "${pg.name}": ${initialSearchPath}`);
190270
+ }
190271
+ startedReshapeWatchers.push(watcher);
190272
+ } catch (err) {
190273
+ writeLog(
190274
+ "system:warning",
190275
+ `Failed to initialize Reshape for "${pg.name}": ${err instanceof Error ? err.message : String(err)}`
190276
+ );
190277
+ }
190278
+ }
190279
+ }
190280
+ reshapeWatchersRef.current = startedReshapeWatchers;
190281
+ if (cancelled) return;
189640
190282
  const secrets = await prepareSecrets(config2.secrets, true);
189641
190283
  const configs = await prepareConfigs(config2.configs, void 0, true);
189642
190284
  const usedSecrets = findUsedSecrets(config2.services, true);
@@ -189724,6 +190366,39 @@ Add them to the config block in specific.local`);
189724
190366
  serviceEndpointPorts.set(service.name, endpointPorts);
189725
190367
  serviceEndpoints.set(service.name, endpointInfos);
189726
190368
  }
190369
+ const serviceInfos = [];
190370
+ const exposedServices = [];
190371
+ for (const service of config2.services) {
190372
+ const endpointInfos = serviceEndpoints.get(service.name) || [];
190373
+ for (const info of endpointInfos) {
190374
+ const endpointConfig = service.endpoints.find(
190375
+ (e) => e.name === info.endpointName
190376
+ );
190377
+ if (endpointConfig?.public) {
190378
+ const proxyName = info.endpointName === "default" ? service.name : `${service.name}-${info.endpointName}`;
190379
+ exposedServices.push({ name: proxyName, port: info.port });
190380
+ serviceInfos.push({
190381
+ serviceName: proxyName,
190382
+ port: info.port
190383
+ });
190384
+ }
190385
+ }
190386
+ }
190387
+ const publicUrls = /* @__PURE__ */ new Map();
190388
+ let subdomainAllocator;
190389
+ if (tunnelEnabled && exposedServices.length > 0) {
190390
+ subdomainAllocator = new StableSubdomainAllocator(process.cwd(), instanceKey);
190391
+ const multipleServices = exposedServices.length > 1;
190392
+ for (const svc of exposedServices) {
190393
+ const subdomain = subdomainAllocator.allocate(svc.name, multipleServices);
190394
+ publicUrls.set(svc.name, `${subdomain}.tunnel.spcf.app`);
190395
+ }
190396
+ } else {
190397
+ const keySuffix = instanceKey === "default" ? "" : `.${instanceKey}`;
190398
+ for (const svc of exposedServices) {
190399
+ publicUrls.set(svc.name, `${svc.name}${keySuffix}.local.spcf.app`);
190400
+ }
190401
+ }
189727
190402
  const services2 = [];
189728
190403
  for (const service of config2.services) {
189729
190404
  if (cancelled) break;
@@ -189747,7 +190422,8 @@ Add them to the config block in specific.local`);
189747
190422
  configs,
189748
190423
  endpointPorts,
189749
190424
  serviceEndpoints,
189750
- (line) => addLog(line, colorMap)
190425
+ (line) => addLog(line, colorMap),
190426
+ publicUrls
189751
190427
  );
189752
190428
  services2.push(running);
189753
190429
  startedServices.push(running);
@@ -189775,30 +190451,45 @@ Add them to the config block in specific.local`);
189775
190451
  }
189776
190452
  }
189777
190453
  if (cancelled) return;
189778
- const serviceInfos = [];
189779
- const exposedServices = [];
189780
- for (const service of config2.services) {
189781
- const endpointInfos = serviceEndpoints.get(service.name) || [];
189782
- for (const info of endpointInfos) {
189783
- const endpointConfig = service.endpoints.find(
189784
- (e) => e.name === info.endpointName
189785
- );
189786
- if (endpointConfig?.public) {
189787
- const proxyName = info.endpointName === "default" ? service.name : `${service.name}-${info.endpointName}`;
189788
- exposedServices.push({ name: proxyName, port: info.port });
189789
- serviceInfos.push({
189790
- serviceName: proxyName,
189791
- port: info.port
189792
- });
190454
+ restartServicesRef.current = async () => {
190455
+ if (shuttingDown.current) return;
190456
+ writeLog("system", "Restarting services (migration changed)");
190457
+ await Promise.all(servicesRef.current.map((s) => s.stop()));
190458
+ const newServices = [];
190459
+ for (const service of config2.services) {
190460
+ if (service.serve) continue;
190461
+ if (!service.command && !service.dev?.command) continue;
190462
+ try {
190463
+ const endpointPorts = serviceEndpointPorts.get(service.name) || /* @__PURE__ */ new Map();
190464
+ const running = startService(
190465
+ service,
190466
+ resources2,
190467
+ secrets,
190468
+ configs,
190469
+ endpointPorts,
190470
+ serviceEndpoints,
190471
+ (line) => addLog(line, colorMap)
190472
+ );
190473
+ newServices.push(running);
190474
+ } catch (err) {
190475
+ writeLog(
190476
+ "system:error",
190477
+ `Failed to restart service "${service.name}": ${err instanceof Error ? err.message : String(err)}`
190478
+ );
189793
190479
  }
189794
190480
  }
190481
+ servicesRef.current = newServices;
190482
+ setState((s) => ({ ...s, services: newServices }));
190483
+ writeLog("system", "Services restarted");
190484
+ };
190485
+ for (const watcher of reshapeWatchersRef.current) {
190486
+ watcher.startWatching();
189795
190487
  }
189796
190488
  if (tunnelEnabled) {
189797
190489
  writeLog("system", "Tunnel mode enabled, starting tunnels for public services");
189798
190490
  if (exposedServices.length === 0) {
189799
190491
  writeLog("system", "No public services to tunnel");
189800
190492
  } else {
189801
- const subdomainAllocator = new StableSubdomainAllocator(process.cwd(), instanceKey);
189802
190493
  const multipleServices = exposedServices.length > 1;
189803
190494
  const tunnelInstances = [];
189804
190495
  const tunnelStatusMap = /* @__PURE__ */ new Map();
@@ -190011,6 +190702,12 @@ Add them to the config block in specific.local`);
190011
190702
  electric.stop().catch(() => {
190012
190703
  });
190013
190704
  }
190705
+ for (const watcher of reshapeWatchersRef.current) {
190706
+ watcher.close().catch(() => {
190707
+ });
190708
+ }
190709
+ reshapeWatchersRef.current = [];
190710
+ restartServicesRef.current = null;
190014
190711
  if (startedDrizzleGateway) {
190015
190712
  startedDrizzleGateway.stop().catch(() => {
190016
190713
  });
@@ -190233,7 +190930,7 @@ function devCommand(instanceKey, tunnelEnabled = false) {
190233
190930
 
190234
190931
  // src/lib/dev/git-worktree.ts
190235
190932
  import { execSync as execSync2 } from "child_process";
190236
- import * as path18 from "path";
190933
+ import * as path19 from "path";
190237
190934
  function isInWorktree() {
190238
190935
  try {
190239
190936
  const commonDir = execSync2("git rev-parse --git-common-dir", {
@@ -190244,8 +190941,8 @@ function isInWorktree() {
190244
190941
  encoding: "utf-8",
190245
190942
  stdio: ["pipe", "pipe", "pipe"]
190246
190943
  }).trim();
190247
- const resolvedCommonDir = path18.resolve(commonDir);
190248
- const resolvedGitDir = path18.resolve(gitDir);
190944
+ const resolvedCommonDir = path19.resolve(commonDir);
190945
+ const resolvedGitDir = path19.resolve(gitDir);
190249
190946
  return resolvedCommonDir !== resolvedGitDir;
190250
190947
  } catch {
190251
190948
  return false;
@@ -190260,7 +190957,7 @@ function getWorktreeName() {
190260
190957
  encoding: "utf-8",
190261
190958
  stdio: ["pipe", "pipe", "pipe"]
190262
190959
  }).trim();
190263
- return path18.basename(gitDir);
190960
+ return path19.basename(gitDir);
190264
190961
  } catch {
190265
190962
  return null;
190266
190963
  }
@@ -190275,35 +190972,35 @@ init_open();
190275
190972
  import React7, { useState as useState6, useEffect as useEffect4, useCallback } from "react";
190276
190973
  import { render as render5, Text as Text7, Box as Box7, useApp as useApp3, useInput as useInput5 } from "ink";
190277
190974
  import Spinner5 from "ink-spinner";
190278
- import * as fs22 from "fs";
190279
- import * as path20 from "path";
190975
+ import * as fs23 from "fs";
190976
+ import * as path21 from "path";
190280
190977
 
190281
190978
  // src/lib/deploy/build-tester.ts
190282
190979
  import { spawn as spawn5 } from "child_process";
190283
- import { existsSync as existsSync18 } from "fs";
190284
- import { join as join19 } from "path";
190980
+ import { existsSync as existsSync20 } from "fs";
190981
+ import { join as join21 } from "path";
190285
190982
  function getDependencyInstallCommand(build, projectDir) {
190286
190983
  switch (build.base) {
190287
190984
  case "node":
190288
- if (existsSync18(join19(projectDir, "pnpm-lock.yaml"))) {
190985
+ if (existsSync20(join21(projectDir, "pnpm-lock.yaml"))) {
190289
190986
  return "pnpm install --frozen-lockfile";
190290
- } else if (existsSync18(join19(projectDir, "yarn.lock"))) {
190987
+ } else if (existsSync20(join21(projectDir, "yarn.lock"))) {
190291
190988
  return "yarn install --frozen-lockfile";
190292
- } else if (existsSync18(join19(projectDir, "package-lock.json"))) {
190989
+ } else if (existsSync20(join21(projectDir, "package-lock.json"))) {
190293
190990
  return "npm ci";
190294
190991
  } else {
190295
190992
  return "npm install";
190296
190993
  }
190297
190994
  case "python":
190298
- if (existsSync18(join19(projectDir, "poetry.lock"))) {
190995
+ if (existsSync20(join21(projectDir, "poetry.lock"))) {
190299
190996
  return "poetry install --no-interaction";
190300
- } else if (existsSync18(join19(projectDir, "Pipfile.lock"))) {
190997
+ } else if (existsSync20(join21(projectDir, "Pipfile.lock"))) {
190301
190998
  return "pipenv install --deploy";
190302
- } else if (existsSync18(join19(projectDir, "Pipfile"))) {
190999
+ } else if (existsSync20(join21(projectDir, "Pipfile"))) {
190303
191000
  return "pipenv install";
190304
- } else if (existsSync18(join19(projectDir, "pyproject.toml"))) {
191001
+ } else if (existsSync20(join21(projectDir, "pyproject.toml"))) {
190305
191002
  return "pip install .";
190306
- } else if (existsSync18(join19(projectDir, "requirements.txt"))) {
191003
+ } else if (existsSync20(join21(projectDir, "requirements.txt"))) {
190307
191004
  return "pip install -r requirements.txt";
190308
191005
  }
190309
191006
  return null;
@@ -190317,7 +191014,7 @@ function getDependencyInstallCommand(build, projectDir) {
190317
191014
  }
190318
191015
  }
190319
191016
  function runCommand2(command, projectDir, buildName) {
190320
- return new Promise((resolve7) => {
191017
+ return new Promise((resolve9) => {
190321
191018
  const stdout = [];
190322
191019
  const stderr = [];
190323
191020
  writeLog("build-test", `[${buildName}] Running: ${command}`);
@@ -190347,7 +191044,7 @@ function runCommand2(command, projectDir, buildName) {
190347
191044
  });
190348
191045
  child.on("error", (err) => {
190349
191046
  writeLog("build-test:error", `[${buildName}] Failed to start: ${err.message}`);
190350
- resolve7({
191047
+ resolve9({
190351
191048
  success: false,
190352
191049
  output: `Failed to start command: ${err.message}`
190353
191050
  });
@@ -190356,10 +191053,10 @@ function runCommand2(command, projectDir, buildName) {
190356
191053
  const output = [...stdout, ...stderr].join("");
190357
191054
  if (code === 0) {
190358
191055
  writeLog("build-test", `[${buildName}] Command succeeded (exit code 0)`);
190359
- resolve7({ success: true, output });
191056
+ resolve9({ success: true, output });
190360
191057
  } else {
190361
191058
  writeLog("build-test:error", `[${buildName}] Command failed with exit code ${code}`);
190362
- resolve7({
191059
+ resolve9({
190363
191060
  success: false,
190364
191061
  output: output || `Exit code: ${code}`
190365
191062
  });
@@ -190449,8 +191146,8 @@ async function testAllBuilds(builds, projectDir) {
190449
191146
 
190450
191147
  // src/lib/tarball/create.ts
190451
191148
  import { execSync as execSync3 } from "child_process";
190452
- import * as fs21 from "fs";
190453
- import * as path19 from "path";
191149
+ import * as fs22 from "fs";
191150
+ import * as path20 from "path";
190454
191151
  import { createTarPacker, createEntryItemGenerator } from "tar-vern";
190455
191152
  function isInsideGitRepository(dir) {
190456
191153
  try {
@@ -190507,10 +191204,10 @@ var EXCLUDED_DIRS = [
190507
191204
  ];
190508
191205
  async function collectPaths(baseDir, currentDir, exclude) {
190509
191206
  const results = [];
190510
- const entries = await fs21.promises.readdir(currentDir, { withFileTypes: true });
191207
+ const entries = await fs22.promises.readdir(currentDir, { withFileTypes: true });
190511
191208
  for (const entry of entries) {
190512
- const fullPath = path19.join(currentDir, entry.name);
190513
- const relativePath = path19.relative(baseDir, fullPath);
191209
+ const fullPath = path20.join(currentDir, entry.name);
191210
+ const relativePath = path20.relative(baseDir, fullPath);
190514
191211
  if (entry.isDirectory()) {
190515
191212
  if (!exclude.includes(entry.name)) {
190516
191213
  results.push(relativePath);
@@ -190525,8 +191222,8 @@ async function collectPaths(baseDir, currentDir, exclude) {
190525
191222
  }
190526
191223
  async function createTarArchive(projectDir) {
190527
191224
  writeLog("tarball", "Creating tarball using tar-vern (non-git project)");
190528
- const configPath = path19.join(projectDir, "specific.hcl");
190529
- if (!fs21.existsSync(configPath)) {
191225
+ const configPath = path20.join(projectDir, "specific.hcl");
191226
+ if (!fs22.existsSync(configPath)) {
190530
191227
  throw new Error("specific.hcl not found in project directory");
190531
191228
  }
190532
191229
  const relativePaths = await collectPaths(projectDir, projectDir, EXCLUDED_DIRS);
@@ -190543,8 +191240,8 @@ async function createTarArchive(projectDir) {
190543
191240
  }
190544
191241
  function findWidestContext(projectDir, contexts) {
190545
191242
  if (contexts.length === 0) return ".";
190546
- const absolute = contexts.map((c) => path19.resolve(projectDir, c));
190547
- const segments = absolute.map((p) => p.split(path19.sep).filter(Boolean));
191243
+ const absolute = contexts.map((c) => path20.resolve(projectDir, c));
191244
+ const segments = absolute.map((p) => p.split(path20.sep).filter(Boolean));
190548
191245
  const firstSegments = segments[0];
190549
191246
  if (!firstSegments) return ".";
190550
191247
  const minLen = Math.min(...segments.map((s) => s.length));
@@ -190558,12 +191255,12 @@ function findWidestContext(projectDir, contexts) {
190558
191255
  }
190559
191256
  }
190560
191257
  const ancestorSegments = firstSegments.slice(0, commonLength);
190561
- const ancestor = path19.sep + ancestorSegments.join(path19.sep);
190562
- return path19.relative(projectDir, ancestor) || ".";
191258
+ const ancestor = path20.sep + ancestorSegments.join(path20.sep);
191259
+ return path20.relative(projectDir, ancestor) || ".";
190563
191260
  }
190564
191261
  async function createProjectTarball(projectDir, context = ".") {
190565
- const contextDir = path19.resolve(projectDir, context);
190566
- const appPath = path19.relative(contextDir, projectDir) || ".";
191262
+ const contextDir = path20.resolve(projectDir, context);
191263
+ const appPath = path20.relative(contextDir, projectDir) || ".";
190567
191264
  writeLog("tarball", `Context: ${contextDir}, appPath: ${appPath}`);
190568
191265
  let tarball;
190569
191266
  if (isInsideGitRepository(contextDir)) {
@@ -191450,14 +192147,14 @@ ${errorMsg}`
191450
192147
  ), phase === "error" && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, { color: "red" }, "Error: ", error)), phase === "success" && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text7, { color: "green" }, "Deployment successful!"), deployment?.publicUrls && Object.keys(deployment.publicUrls).length > 0 && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "Public URLs:"), Object.entries(deployment.publicUrls).map(([name, url]) => /* @__PURE__ */ React7.createElement(Text7, { key: name }, " ", name, ": ", /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, url))))));
191451
192148
  }
191452
192149
  async function deployCommand(environment, options2) {
191453
- const configPath = path20.join(process.cwd(), "specific.hcl");
191454
- if (!fs22.existsSync(configPath)) {
192150
+ const configPath = path21.join(process.cwd(), "specific.hcl");
192151
+ if (!fs23.existsSync(configPath)) {
191455
192152
  console.error("Error: No specific.hcl found in current directory");
191456
192153
  process.exit(1);
191457
192154
  }
191458
192155
  let config;
191459
192156
  try {
191460
- const hcl = fs22.readFileSync(configPath, "utf-8");
192157
+ const hcl = fs23.readFileSync(configPath, "utf-8");
191461
192158
  config = await parseConfig(hcl);
191462
192159
  } catch (err) {
191463
192160
  console.error(
@@ -191481,8 +192178,8 @@ async function deployCommand(environment, options2) {
191481
192178
 
191482
192179
  // src/commands/exec.tsx
191483
192180
  import { spawn as spawn6 } from "child_process";
191484
- import * as fs23 from "fs";
191485
- import * as path21 from "path";
192181
+ import * as fs24 from "fs";
192182
+ import * as path22 from "path";
191486
192183
  async function execCommand(serviceName, command, instanceKey = "default") {
191487
192184
  if (command.length === 0) {
191488
192185
  console.error(
@@ -191510,14 +192207,14 @@ async function execCommand(serviceName, command, instanceKey = "default") {
191510
192207
  }
191511
192208
  }
191512
192209
  };
191513
- const configPath = path21.join(process.cwd(), "specific.hcl");
191514
- if (!fs23.existsSync(configPath)) {
192210
+ const configPath = path22.join(process.cwd(), "specific.hcl");
192211
+ if (!fs24.existsSync(configPath)) {
191515
192212
  console.error("Error: No specific.hcl found in current directory");
191516
192213
  process.exit(1);
191517
192214
  }
191518
192215
  let config;
191519
192216
  try {
191520
- const hcl = fs23.readFileSync(configPath, "utf-8");
192217
+ const hcl = fs24.readFileSync(configPath, "utf-8");
191521
192218
  config = await parseConfig(hcl);
191522
192219
  } catch (err) {
191523
192220
  console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
@@ -191564,6 +192261,8 @@ async function execCommand(serviceName, command, instanceKey = "default") {
191564
192261
  bucket: dbState.engine === "object_store" ? name : void 0,
191565
192262
  syncUrl: dbState.syncUrl,
191566
192263
  syncSecret: dbState.syncSecret,
192264
+ reshapeEnabled: dbState.reshapeEnabled,
192265
+ reshapeSearchPath: dbState.reshapeSearchPath,
191567
192266
  async stop() {
191568
192267
  }
191569
192268
  });
@@ -191654,27 +192353,48 @@ async function execCommand(serviceName, command, instanceKey = "default") {
191654
192353
 
191655
192354
  // src/commands/psql.tsx
191656
192355
  import { spawn as spawn7 } from "child_process";
191657
- async function psqlCommand(databaseName, instanceKey = "default") {
191658
- const stateManager = new InstanceStateManager(process.cwd(), instanceKey);
191659
- await stateManager.cleanStaleState();
191660
- const existingInstances = await stateManager.getExistingInstances();
191661
- if (!existingInstances) {
191662
- console.error("Error: No running instances found. Run `specific dev` first.");
192356
+ import * as fs25 from "fs";
192357
+ import * as path23 from "path";
192358
+ async function psqlCommand(databaseName, instanceKey = "default", extraArgs = []) {
192359
+ let startedResources = [];
192360
+ let ownsInstances = false;
192361
+ let stateManager = null;
192362
+ const cleanup = async () => {
192363
+ if (ownsInstances) {
192364
+ for (const resource of startedResources) {
192365
+ await resource.stop().catch(() => {
192366
+ });
192367
+ }
192368
+ if (stateManager) {
192369
+ await stateManager.releaseOwnership().catch(() => {
192370
+ });
192371
+ }
192372
+ }
192373
+ };
192374
+ const configPath = path23.join(process.cwd(), "specific.hcl");
192375
+ if (!fs25.existsSync(configPath)) {
192376
+ console.error("Error: No specific.hcl found in current directory");
191663
192377
  process.exit(1);
191664
192378
  }
191665
- const availableDatabases = Object.keys(existingInstances.databases);
192379
+ let config;
192380
+ try {
192381
+ const hcl = fs25.readFileSync(configPath, "utf-8");
192382
+ config = await parseConfig(hcl);
192383
+ } catch (err) {
192384
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
192385
+ process.exit(1);
192386
+ }
192387
+ const availableDatabases = config.postgres.map((p) => p.name);
191666
192388
  if (availableDatabases.length === 0) {
191667
- console.error("Error: No databases found in running instances.");
192389
+ console.error("Error: No postgres databases defined in specific.hcl");
191668
192390
  process.exit(1);
191669
192391
  }
191670
192392
  let targetDb;
192393
+ if (databaseName && !availableDatabases.includes(databaseName)) {
192394
+ extraArgs = [databaseName, ...extraArgs];
192395
+ databaseName = void 0;
192396
+ }
191671
192397
  if (databaseName) {
191672
- if (!existingInstances.databases[databaseName]) {
191673
- console.error(
191674
- `Error: Database "${databaseName}" not found. Available: ${availableDatabases.join(", ")}`
191675
- );
191676
- process.exit(1);
191677
- }
191678
192398
  targetDb = databaseName;
191679
192399
  } else {
191680
192400
  if (availableDatabases.length > 1) {
@@ -191685,24 +192405,297 @@ async function psqlCommand(databaseName, instanceKey = "default") {
191685
192405
  }
191686
192406
  targetDb = availableDatabases[0];
191687
192407
  }
191688
- const dbState = existingInstances.databases[targetDb];
191689
- const child = spawn7("psql", ["-h", dbState.host, "-p", String(dbState.port), "-U", dbState.user, "-d", dbState.dbName], {
192408
+ stateManager = new InstanceStateManager(process.cwd(), instanceKey);
192409
+ await stateManager.cleanStaleState();
192410
+ const existingInstances = await stateManager.getExistingInstances();
192411
+ let dbState;
192412
+ if (existingInstances && existingInstances.databases[targetDb]) {
192413
+ const db = existingInstances.databases[targetDb];
192414
+ dbState = { host: db.host, port: db.port, user: db.user, password: db.password, dbName: db.dbName };
192415
+ } else {
192416
+ console.error("Starting database...");
192417
+ try {
192418
+ await stateManager.claimOwnership("psql");
192419
+ ownsInstances = true;
192420
+ } catch (err) {
192421
+ console.error(
192422
+ `Error: ${err instanceof Error ? err.message : String(err)}`
192423
+ );
192424
+ process.exit(1);
192425
+ }
192426
+ try {
192427
+ const result = await startResources({
192428
+ config,
192429
+ selection: {
192430
+ mode: "required",
192431
+ postgres: [targetDb],
192432
+ redis: [],
192433
+ storage: []
192434
+ },
192435
+ stateManager,
192436
+ dataDir: `.specific/keys/${instanceKey}/data`,
192437
+ portAllocator: new PortAllocator(),
192438
+ callbacks: {
192439
+ log: (msg) => console.error(msg)
192440
+ },
192441
+ startElectric: false
192442
+ });
192443
+ startedResources = result.startedResources;
192444
+ const resource = result.resources.get(targetDb);
192445
+ if (!resource) {
192446
+ console.error(`Error: Failed to start database "${targetDb}"`);
192447
+ await cleanup();
192448
+ process.exit(1);
192449
+ }
192450
+ dbState = {
192451
+ host: resource.host,
192452
+ port: resource.port,
192453
+ user: resource.user,
192454
+ password: resource.password,
192455
+ dbName: resource.dbName
192456
+ };
192457
+ } catch (err) {
192458
+ console.error(
192459
+ `Error: Failed to start database: ${err instanceof Error ? err.message : String(err)}`
192460
+ );
192461
+ await cleanup();
192462
+ process.exit(1);
192463
+ }
192464
+ }
192465
+ const binary = await ensureBinary(postgresBinary);
192466
+ const psqlPath = binary.executables["psql"];
192467
+ const libraryEnv = getLibraryEnv(binary);
192468
+ let child = null;
192469
+ const handleSignal = async (signal) => {
192470
+ if (child && !child.killed) {
192471
+ child.kill(signal);
192472
+ }
192473
+ await cleanup();
192474
+ };
192475
+ process.on("SIGINT", () => handleSignal("SIGINT"));
192476
+ process.on("SIGTERM", () => handleSignal("SIGTERM"));
192477
+ child = spawn7(psqlPath, ["-h", dbState.host, "-p", String(dbState.port), "-U", dbState.user, "-d", dbState.dbName, ...extraArgs], {
191690
192478
  cwd: process.cwd(),
191691
192479
  env: {
191692
192480
  ...process.env,
192481
+ ...libraryEnv,
191693
192482
  PGPASSWORD: dbState.password
191694
192483
  },
191695
192484
  stdio: "inherit"
191696
192485
  });
191697
- child.on("exit", (code, signal) => {
192486
+ child.on("exit", async (code, signal) => {
192487
+ await cleanup();
191698
192488
  if (signal) {
191699
192489
  process.kill(process.pid, signal);
191700
192490
  } else {
191701
192491
  process.exit(code ?? 0);
191702
192492
  }
191703
192493
  });
191704
- child.on("error", (err) => {
192494
+ child.on("error", async (err) => {
191705
192495
  console.error(`Error: Failed to start psql: ${err.message}`);
192496
+ await cleanup();
192497
+ process.exit(1);
192498
+ });
192499
+ }
192500
+
192501
+ // src/commands/reshape.tsx
192502
+ import { spawn as spawn8 } from "child_process";
192503
+ import * as fs26 from "fs";
192504
+ import * as path24 from "path";
192505
+ var VALID_ACTIONS = ["start", "complete", "status", "abort", "check"];
192506
+ var MIGRATION_SUBCOMMANDS = ["start", "complete", "abort"];
192507
+ var OFFLINE_ACTIONS = ["check"];
192508
+ async function reshapeCommand(action, databaseName, instanceKey = "default") {
192509
+ if (!VALID_ACTIONS.includes(action)) {
192510
+ console.error(
192511
+ `Error: Invalid action "${action}". Valid actions: ${VALID_ACTIONS.join(", ")}`
192512
+ );
192513
+ process.exit(1);
192514
+ }
192515
+ const isOfflineAction = OFFLINE_ACTIONS.includes(action);
192516
+ const configPath = path24.join(process.cwd(), "specific.hcl");
192517
+ let config;
192518
+ let migrationsDir = "migrations";
192519
+ let targetDb;
192520
+ try {
192521
+ if (fs26.existsSync(configPath)) {
192522
+ const configContent = fs26.readFileSync(configPath, "utf-8");
192523
+ config = await parseConfig(configContent);
192524
+ if (databaseName) {
192525
+ const postgresConfig = config.postgres.find((p) => p.name === databaseName);
192526
+ if (postgresConfig?.reshape?.migrations_dir) {
192527
+ migrationsDir = postgresConfig.reshape.migrations_dir;
192528
+ }
192529
+ targetDb = databaseName;
192530
+ } else {
192531
+ const reshapePostgres = config.postgres.find((p) => p.reshape?.enabled);
192532
+ if (reshapePostgres) {
192533
+ if (reshapePostgres.reshape?.migrations_dir) {
192534
+ migrationsDir = reshapePostgres.reshape.migrations_dir;
192535
+ }
192536
+ targetDb = reshapePostgres.name;
192537
+ } else if (config.postgres.length > 0) {
192538
+ const first = config.postgres[0];
192539
+ if (first.reshape?.migrations_dir) {
192540
+ migrationsDir = first.reshape.migrations_dir;
192541
+ }
192542
+ targetDb = first.name;
192543
+ }
192544
+ }
192545
+ }
192546
+ } catch {
192547
+ }
192548
+ let dbUrl;
192549
+ let startedResources = [];
192550
+ let ownsInstances = false;
192551
+ let stateManager = null;
192552
+ const cleanup = async () => {
192553
+ if (ownsInstances) {
192554
+ for (const resource of startedResources) {
192555
+ await resource.stop().catch(() => {
192556
+ });
192557
+ }
192558
+ if (stateManager) {
192559
+ await stateManager.releaseOwnership().catch(() => {
192560
+ });
192561
+ }
192562
+ }
192563
+ };
192564
+ if (!isOfflineAction) {
192565
+ stateManager = new InstanceStateManager(process.cwd(), instanceKey);
192566
+ await stateManager.cleanStaleState();
192567
+ const existingInstances = await stateManager.getExistingInstances();
192568
+ if (existingInstances) {
192569
+ const availableDatabases = Object.keys(existingInstances.databases).filter(
192570
+ (name) => existingInstances.databases[name]?.engine === "postgres"
192571
+ );
192572
+ if (availableDatabases.length === 0) {
192573
+ console.error("Error: No PostgreSQL databases found in running instances.");
192574
+ process.exit(1);
192575
+ }
192576
+ if (databaseName) {
192577
+ if (!existingInstances.databases[databaseName]) {
192578
+ console.error(
192579
+ `Error: Database "${databaseName}" not found. Available: ${availableDatabases.join(", ")}`
192580
+ );
192581
+ process.exit(1);
192582
+ }
192583
+ if (existingInstances.databases[databaseName]?.engine !== "postgres") {
192584
+ console.error(
192585
+ `Error: Database "${databaseName}" is not a PostgreSQL database.`
192586
+ );
192587
+ process.exit(1);
192588
+ }
192589
+ targetDb = databaseName;
192590
+ } else {
192591
+ if (availableDatabases.length > 1) {
192592
+ console.error(
192593
+ `Error: Multiple databases available. Specify one: ${availableDatabases.join(", ")}`
192594
+ );
192595
+ process.exit(1);
192596
+ }
192597
+ targetDb = availableDatabases[0];
192598
+ }
192599
+ dbUrl = existingInstances.databases[targetDb].url;
192600
+ } else {
192601
+ if (!config) {
192602
+ console.error("Error: No specific.hcl found in current directory");
192603
+ process.exit(1);
192604
+ }
192605
+ if (!targetDb) {
192606
+ console.error("Error: No PostgreSQL database found in configuration.");
192607
+ process.exit(1);
192608
+ }
192609
+ console.error("Starting resources...");
192610
+ try {
192611
+ await stateManager.claimOwnership("reshape");
192612
+ ownsInstances = true;
192613
+ } catch (err) {
192614
+ console.error(
192615
+ `Error: ${err instanceof Error ? err.message : String(err)}`
192616
+ );
192617
+ process.exit(1);
192618
+ }
192619
+ try {
192620
+ const result = await startResources({
192621
+ config,
192622
+ selection: {
192623
+ mode: "required",
192624
+ postgres: [targetDb],
192625
+ redis: [],
192626
+ storage: []
192627
+ },
192628
+ stateManager,
192629
+ dataDir: `.specific/keys/${instanceKey}/data`,
192630
+ portAllocator: new PortAllocator(),
192631
+ callbacks: {
192632
+ log: (msg) => console.error(msg)
192633
+ },
192634
+ startElectric: false
192635
+ });
192636
+ startedResources = result.startedResources;
192637
+ const resource = result.resources.get(targetDb);
192638
+ if (!resource) {
192639
+ console.error(`Error: Failed to start database "${targetDb}"`);
192640
+ await cleanup();
192641
+ process.exit(1);
192642
+ }
192643
+ dbUrl = resource.url;
192644
+ } catch (err) {
192645
+ console.error(
192646
+ `Error: Failed to start resources: ${err instanceof Error ? err.message : String(err)}`
192647
+ );
192648
+ await cleanup();
192649
+ process.exit(1);
192650
+ }
192651
+ }
192652
+ }
192653
+ const isMigrationSubcommand = MIGRATION_SUBCOMMANDS.includes(action);
192654
+ const reshapeArgs = isMigrationSubcommand ? ["migration", action] : [action];
192655
+ const fullMigrationsPath = path24.join(process.cwd(), migrationsDir);
192656
+ if (action === "check" || action === "start") {
192657
+ if (fs26.existsSync(fullMigrationsPath)) {
192658
+ reshapeArgs.push("--dirs", fullMigrationsPath);
192659
+ } else if (action === "check") {
192660
+ console.error(`Error: Migrations directory not found: ${fullMigrationsPath}`);
192661
+ await cleanup();
192662
+ process.exit(1);
192663
+ }
192664
+ }
192665
+ if (action === "status" && dbUrl) {
192666
+ reshapeArgs.push("--url", dbUrl);
192667
+ }
192668
+ const binary = await ensureBinary(reshapeBinary);
192669
+ const reshapePath = binary.executables["reshape"];
192670
+ let child = null;
192671
+ const handleSignal = async (signal) => {
192672
+ if (child && !child.killed) {
192673
+ child.kill(signal);
192674
+ }
192675
+ await cleanup();
192676
+ };
192677
+ process.on("SIGINT", () => handleSignal("SIGINT"));
192678
+ process.on("SIGTERM", () => handleSignal("SIGTERM"));
192679
+ child = spawn8(reshapePath, reshapeArgs, {
192680
+ cwd: process.cwd(),
192681
+ env: {
192682
+ ...process.env,
192683
+ // For migration subcommands, pass DB_URL via env
192684
+ ...isMigrationSubcommand && dbUrl ? { DB_URL: dbUrl } : {}
192685
+ },
192686
+ stdio: "inherit"
192687
+ });
192688
+ child.on("exit", async (code, signal) => {
192689
+ await cleanup();
192690
+ if (signal) {
192691
+ process.kill(process.pid, signal);
192692
+ } else {
192693
+ process.exit(code ?? 0);
192694
+ }
192695
+ });
192696
+ child.on("error", async (err) => {
192697
+ console.error(`Error: Failed to start reshape: ${err.message}`);
192698
+ await cleanup();
191706
192699
  process.exit(1);
191707
192700
  });
191708
192701
  }
@@ -191711,21 +192704,21 @@ async function psqlCommand(databaseName, instanceKey = "default") {
191711
192704
  import React8, { useState as useState7, useEffect as useEffect5 } from "react";
191712
192705
  import { render as render6, Text as Text8, Box as Box8 } from "ink";
191713
192706
  import Spinner6 from "ink-spinner";
191714
- import * as fs24 from "fs";
191715
- import * as path22 from "path";
192707
+ import * as fs27 from "fs";
192708
+ import * as path25 from "path";
191716
192709
  function CleanUI({ instanceKey }) {
191717
192710
  const [state, setState] = useState7({ status: "checking" });
191718
192711
  useEffect5(() => {
191719
192712
  async function clean() {
191720
192713
  const projectRoot = process.cwd();
191721
- const specificDir = path22.join(projectRoot, ".specific");
191722
- if (!fs24.existsSync(specificDir)) {
192714
+ const specificDir = path25.join(projectRoot, ".specific");
192715
+ if (!fs27.existsSync(specificDir)) {
191723
192716
  setState({ status: "nothing" });
191724
192717
  return;
191725
192718
  }
191726
192719
  if (instanceKey) {
191727
- const keyDir = path22.join(specificDir, "keys", instanceKey);
191728
- if (!fs24.existsSync(keyDir)) {
192720
+ const keyDir = path25.join(specificDir, "keys", instanceKey);
192721
+ if (!fs27.existsSync(keyDir)) {
191729
192722
  setState({ status: "nothing" });
191730
192723
  return;
191731
192724
  }
@@ -191741,7 +192734,7 @@ function CleanUI({ instanceKey }) {
191741
192734
  await stateManager.cleanStaleState();
191742
192735
  setState({ status: "cleaning" });
191743
192736
  try {
191744
- fs24.rmSync(keyDir, { recursive: true, force: true });
192737
+ fs27.rmSync(keyDir, { recursive: true, force: true });
191745
192738
  setState({ status: "success" });
191746
192739
  } catch (err) {
191747
192740
  setState({
@@ -191750,10 +192743,10 @@ function CleanUI({ instanceKey }) {
191750
192743
  });
191751
192744
  }
191752
192745
  } else {
191753
- const keysDir = path22.join(specificDir, "keys");
191754
- if (fs24.existsSync(keysDir)) {
191755
- const keys = fs24.readdirSync(keysDir).filter(
191756
- (f) => fs24.statSync(path22.join(keysDir, f)).isDirectory()
192746
+ const keysDir = path25.join(specificDir, "keys");
192747
+ if (fs27.existsSync(keysDir)) {
192748
+ const keys = fs27.readdirSync(keysDir).filter(
192749
+ (f) => fs27.statSync(path25.join(keysDir, f)).isDirectory()
191757
192750
  );
191758
192751
  for (const key of keys) {
191759
192752
  const stateManager2 = new InstanceStateManager(projectRoot, key);
@@ -191778,7 +192771,7 @@ function CleanUI({ instanceKey }) {
191778
192771
  }
191779
192772
  setState({ status: "cleaning" });
191780
192773
  try {
191781
- fs24.rmSync(specificDir, { recursive: true, force: true });
192774
+ fs27.rmSync(specificDir, { recursive: true, force: true });
191782
192775
  setState({ status: "success" });
191783
192776
  } catch (err) {
191784
192777
  setState({
@@ -191861,12 +192854,88 @@ function logoutCommand() {
191861
192854
  render7(/* @__PURE__ */ React9.createElement(LogoutUI, null));
191862
192855
  }
191863
192856
 
192857
+ // src/commands/beta.tsx
192858
+ import React10, { useState as useState9, useEffect as useEffect7 } from "react";
192859
+ import { render as render8, Text as Text10, Box as Box9, useInput as useInput6, useApp as useApp5 } from "ink";
192860
+ function BetaToggleUI() {
192861
+ const { exit } = useApp5();
192862
+ const [enabled, setEnabled] = useState9(() => {
192863
+ const current = loadEnabledBetas();
192864
+ const state = {};
192865
+ for (const beta of BETA_REGISTRY) {
192866
+ state[beta.name] = current.includes(beta.name);
192867
+ }
192868
+ return state;
192869
+ });
192870
+ const [focusedIndex, setFocusedIndex] = useState9(0);
192871
+ const [saved, setSaved] = useState9(false);
192872
+ const isSubmitFocused = focusedIndex === BETA_REGISTRY.length;
192873
+ useInput6((input, key) => {
192874
+ if (saved) return;
192875
+ if (key.upArrow || input === "k") {
192876
+ setFocusedIndex((prev) => Math.max(0, prev - 1));
192877
+ } else if (key.downArrow || input === "j") {
192878
+ setFocusedIndex((prev) => Math.min(BETA_REGISTRY.length, prev + 1));
192879
+ } else if (input === " " && !isSubmitFocused) {
192880
+ const beta = BETA_REGISTRY[focusedIndex];
192881
+ setEnabled((prev) => ({ ...prev, [beta.name]: !prev[beta.name] }));
192882
+ } else if ((key.return || input === " ") && isSubmitFocused) {
192883
+ for (const beta of BETA_REGISTRY) {
192884
+ if (enabled[beta.name]) {
192885
+ enableBeta(beta.name);
192886
+ } else {
192887
+ disableBeta(beta.name);
192888
+ }
192889
+ }
192890
+ trackEvent("beta_toggled", {
192891
+ enabled: Object.keys(enabled).filter((k) => enabled[k])
192892
+ });
192893
+ setSaved(true);
192894
+ }
192895
+ });
192896
+ useEffect7(() => {
192897
+ if (saved) {
192898
+ const timer = setTimeout(() => exit(), 100);
192899
+ return () => clearTimeout(timer);
192900
+ }
192901
+ }, [saved, exit]);
192902
+ if (saved) {
192903
+ const enabledNames = Object.keys(enabled).filter((k) => enabled[k]);
192904
+ return /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text10, null, /* @__PURE__ */ React10.createElement(Text10, { color: "green" }, "\u2713"), " Beta features updated"), enabledNames.length > 0 ? /* @__PURE__ */ React10.createElement(Text10, { dimColor: true }, " Enabled: ", enabledNames.join(", ")) : /* @__PURE__ */ React10.createElement(Text10, { dimColor: true }, " No betas enabled"));
192905
+ }
192906
+ return /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text10, null, /* @__PURE__ */ React10.createElement(Text10, { dimColor: true }, "\u25CF"), " Configure beta features", " ", /* @__PURE__ */ React10.createElement(Text10, { dimColor: true }, "(space to select)")), /* @__PURE__ */ React10.createElement(Text10, null, " "), BETA_REGISTRY.map((beta, index) => {
192907
+ const isFocused = focusedIndex === index;
192908
+ const isChecked = enabled[beta.name];
192909
+ return /* @__PURE__ */ React10.createElement(Box9, { key: beta.name }, /* @__PURE__ */ React10.createElement(Text10, { ...isFocused && { color: "cyan" } }, isFocused ? "\u276F " : " ", isChecked ? "[\u2713]" : "[ ]", " ", beta.name, /* @__PURE__ */ React10.createElement(Text10, { dimColor: true }, " - ", beta.description)));
192910
+ }), /* @__PURE__ */ React10.createElement(Text10, null, " "), /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text10, { ...isSubmitFocused && { color: "cyan" } }, isSubmitFocused ? "\u276F " : " ", /* @__PURE__ */ React10.createElement(
192911
+ Text10,
192912
+ {
192913
+ inverse: isSubmitFocused,
192914
+ ...!isSubmitFocused && { backgroundColor: "gray" }
192915
+ },
192916
+ " ",
192917
+ "Save",
192918
+ " "
192919
+ ))));
192920
+ }
192921
+ function betaCommand() {
192922
+ if (BETA_REGISTRY.length === 0) {
192923
+ console.log("No beta features available.");
192924
+ return;
192925
+ }
192926
+ if (!isInteractive()) {
192927
+ console.error("Error: specific beta requires an interactive terminal.");
192928
+ process.exit(1);
192929
+ }
192930
+ render8(/* @__PURE__ */ React10.createElement(BetaToggleUI, null));
192931
+ }
192932
+
191864
192933
  // src/cli.tsx
191865
192934
  var program = new Command();
191866
192935
  var env = "production";
191867
192936
  var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
191868
- program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.48").enablePositionalOptions();
191869
- program.command("init").description("Initialize project for use with a coding agent").action(initCommand);
192937
+ program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.50").enablePositionalOptions();
192938
+ program.command("init").description("Initialize project for use with a coding agent").option("--agent <name...>", "Agents to configure (cursor, claude, codex, other)").action((options2) => initCommand(options2));
191870
192939
  program.command("docs [topic]").description("Fetch LLM-optimized documentation").action(docsCommand);
191871
192940
  program.command("check").description("Validate specific.hcl configuration").action(checkCommand);
191872
192941
  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)").option("--tunnel", "Expose public services via localtunnel URLs").action((options2) => {
@@ -191881,13 +192950,19 @@ program.command("exec <service> [args...]").description("Run a one-off command w
191881
192950
  const key = options2.key ?? getDefaultKey();
191882
192951
  await execCommand(service, filteredArgs, key);
191883
192952
  });
191884
- 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) => {
192953
+ program.command("psql [database] [args...]").description("Connect to a Postgres database").option("-k, --key <key>", "Dev environment namespace (auto-detected from git worktree if not specified)").passThroughOptions().action((database, args, options2) => {
192954
+ const filteredArgs = args[0] === "--" ? args.slice(1) : args;
192955
+ const key = options2.key ?? getDefaultKey();
192956
+ psqlCommand(database, key, filteredArgs);
192957
+ });
192958
+ program.command("reshape <action> [database]").description("Run Reshape migrations (start|complete|status|abort)").option("-k, --key <key>", "Dev environment namespace (auto-detected from git worktree if not specified)").action((action, database, options2) => {
191885
192959
  const key = options2.key ?? getDefaultKey();
191886
- psqlCommand(database, key);
192960
+ reshapeCommand(action, database, key);
191887
192961
  });
191888
192962
  program.command("clean").description("Remove .specific directory for a clean slate").option("-k, --key <key>", "Clean only the specified dev environment key").action((options2) => {
191889
192963
  cleanCommand(options2.key);
191890
192964
  });
192965
+ program.command("beta").description("Manage beta feature flags").action(betaCommand);
191891
192966
  program.command("login").description("Log in to Specific").action(loginCommand);
191892
192967
  program.command("logout").description("Log out of Specific").action(logoutCommand);
191893
192968
  var commandName = process.argv[2] || "help";