@specific.dev/cli 0.1.49 → 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 (35) 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 +1536 -687
  27. package/dist/docs/index.md +3 -0
  28. package/dist/docs/postgres/reshape/actions.md +90 -0
  29. package/dist/docs/postgres/reshape/index.md +149 -0
  30. package/dist/docs/postgres.md +20 -1
  31. package/dist/postinstall.js +1 -1
  32. package/package.json +1 -1
  33. /package/dist/admin/_next/static/{FMaKxl-Dpw5U-PgHqIMag → o2Qo92jA0gWbtB1ZWKQFF}/_buildManifest.js +0 -0
  34. /package/dist/admin/_next/static/{FMaKxl-Dpw5U-PgHqIMag → o2Qo92jA0gWbtB1ZWKQFF}/_clientMiddlewareManifest.json +0 -0
  35. /package/dist/admin/_next/static/{FMaKxl-Dpw5U-PgHqIMag → 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 (path24) => {
242
- if (/^[a-z]+:\/\//i.test(path24)) {
243
- return path24;
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", path24], { encoding: "utf8" });
246
+ const { stdout } = await execFile2("wslpath", ["-aw", path26], { encoding: "utf8" });
247
247
  return stdout.trim();
248
248
  } catch {
249
- return path24;
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 fs26 = $global.require("fs");
758
- "object" == typeof fs26 && null !== fs26 && 0 !== Object.keys(fs26).length && ($global.fs = fs26);
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) {
@@ -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.49",
183876
+ cli_version: "0.1.50",
183877
183877
  platform: process.platform,
183878
183878
  node_version: process.version,
183879
183879
  project_id: getProjectId(),
@@ -184235,35 +184235,95 @@ Valid agents: ${VALID_AGENT_IDS.join(", ")}`
184235
184235
  }
184236
184236
 
184237
184237
  // src/commands/docs.tsx
184238
- import { readFileSync as readFileSync5, existsSync as existsSync5 } from "fs";
184239
- 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";
184240
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
184241
184291
  var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
184242
- var docsDir = join6(__dirname2, "docs");
184243
- function docsCommand(path24) {
184244
- const docPath = resolveDocPath(path24);
184292
+ var docsDir = join7(__dirname2, "docs");
184293
+ function docsCommand(path26) {
184294
+ const docPath = resolveDocPath(path26);
184245
184295
  if (!docPath) {
184246
184296
  console.error(
184247
- `Documentation not found: ${path24 || "index"}
184297
+ `Documentation not found: ${path26 || "index"}
184248
184298
 
184249
184299
  Run 'specific docs' to see available topics.`
184250
184300
  );
184251
184301
  process.exit(1);
184252
184302
  }
184253
- const content = readFileSync5(docPath, "utf-8");
184303
+ const enabledBetas = new Set(loadEnabledBetas());
184304
+ let content = readFileSync6(docPath, "utf-8");
184305
+ content = filterBetaTags(content, enabledBetas);
184254
184306
  console.log(content);
184255
184307
  }
184256
- function resolveDocPath(path24) {
184257
- if (!path24) {
184258
- const indexPath2 = join6(docsDir, "index.md");
184259
- 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;
184260
184320
  }
184261
- const directPath = join6(docsDir, `${path24}.md`);
184262
- if (existsSync5(directPath)) {
184321
+ const directPath = join7(docsDir, `${path26}.md`);
184322
+ if (existsSync6(directPath)) {
184263
184323
  return directPath;
184264
184324
  }
184265
- const indexPath = join6(docsDir, path24, "index.md");
184266
- if (existsSync5(indexPath)) {
184325
+ const indexPath = join7(docsDir, path26, "index.md");
184326
+ if (existsSync6(indexPath)) {
184267
184327
  return indexPath;
184268
184328
  }
184269
184329
  return null;
@@ -184273,8 +184333,9 @@ function resolveDocPath(path24) {
184273
184333
  import React3, { useState as useState2, useEffect as useEffect2 } from "react";
184274
184334
  import { render as render3, Text as Text3, Box as Box3 } from "ink";
184275
184335
  import Spinner3 from "ink-spinner";
184276
- import * as fs11 from "fs";
184277
- 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";
184278
184339
 
184279
184340
  // node_modules/.pnpm/@specific+config@file+..+config/node_modules/@specific/config/dist/parser.js
184280
184341
  var import_hcl2_json_parser = __toESM(require_dist(), 1);
@@ -184589,13 +184650,43 @@ function parseServices(serviceData) {
184589
184650
  }
184590
184651
  return services;
184591
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
+ }
184592
184674
  function parsePostgres(data) {
184593
184675
  if (!data || typeof data !== "object" || Array.isArray(data)) {
184594
184676
  return [];
184595
184677
  }
184596
184678
  const result = [];
184597
- for (const [name] of Object.entries(data)) {
184598
- 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);
184599
184690
  }
184600
184691
  return result;
184601
184692
  }
@@ -184826,13 +184917,374 @@ function validateEndpointReferences(config) {
184826
184917
  return errors;
184827
184918
  }
184828
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
+
184829
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
+ }
184830
185282
  function CheckUI() {
184831
185283
  const [state, setState] = useState2({ status: "loading" });
184832
185284
  useEffect2(() => {
184833
185285
  async function load() {
184834
- const configPath = path7.join(process.cwd(), "specific.hcl");
184835
- if (!fs11.existsSync(configPath)) {
185286
+ const configPath = path8.join(process.cwd(), "specific.hcl");
185287
+ if (!fs12.existsSync(configPath)) {
184836
185288
  setState({
184837
185289
  status: "error",
184838
185290
  error: "No specific.hcl found in current directory"
@@ -184840,9 +185292,36 @@ function CheckUI() {
184840
185292
  return;
184841
185293
  }
184842
185294
  try {
184843
- const hcl = fs11.readFileSync(configPath, "utf-8");
185295
+ const hcl = fs12.readFileSync(configPath, "utf-8");
184844
185296
  const config2 = await parseConfig(hcl);
184845
- 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 });
184846
185325
  } catch (err) {
184847
185326
  setState({
184848
185327
  status: "error",
@@ -184862,7 +185341,10 @@ function CheckUI() {
184862
185341
  if (!config) {
184863
185342
  return null;
184864
185343
  }
184865
- 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))));
184866
185348
  }
184867
185349
  function checkCommand() {
184868
185350
  render3(/* @__PURE__ */ React3.createElement(CheckUI, null));
@@ -184872,8 +185354,8 @@ function checkCommand() {
184872
185354
  import React6, { useState as useState5, useEffect as useEffect3, useRef } from "react";
184873
185355
  import { render as render4, Text as Text6, Box as Box6, useApp as useApp2, Static } from "ink";
184874
185356
  import Spinner4 from "ink-spinner";
184875
- import * as fs20 from "fs";
184876
- import * as path17 from "path";
185357
+ import * as fs21 from "fs";
185358
+ import * as path18 from "path";
184877
185359
 
184878
185360
  // node_modules/.pnpm/chokidar@5.0.0/node_modules/chokidar/index.js
184879
185361
  import { EventEmitter } from "node:events";
@@ -184965,7 +185447,7 @@ var ReaddirpStream = class extends Readable {
184965
185447
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
184966
185448
  const statMethod = opts.lstat ? lstat : stat;
184967
185449
  if (wantBigintFsStats) {
184968
- this._stat = (path24) => statMethod(path24, { bigint: true });
185450
+ this._stat = (path26) => statMethod(path26, { bigint: true });
184969
185451
  } else {
184970
185452
  this._stat = statMethod;
184971
185453
  }
@@ -184990,8 +185472,8 @@ var ReaddirpStream = class extends Readable {
184990
185472
  const par = this.parent;
184991
185473
  const fil = par && par.files;
184992
185474
  if (fil && fil.length > 0) {
184993
- const { path: path24, depth } = par;
184994
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path24));
185475
+ const { path: path26, depth } = par;
185476
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path26));
184995
185477
  const awaited = await Promise.all(slice);
184996
185478
  for (const entry of awaited) {
184997
185479
  if (!entry)
@@ -185031,21 +185513,21 @@ var ReaddirpStream = class extends Readable {
185031
185513
  this.reading = false;
185032
185514
  }
185033
185515
  }
185034
- async _exploreDir(path24, depth) {
185516
+ async _exploreDir(path26, depth) {
185035
185517
  let files;
185036
185518
  try {
185037
- files = await readdir(path24, this._rdOptions);
185519
+ files = await readdir(path26, this._rdOptions);
185038
185520
  } catch (error) {
185039
185521
  this._onError(error);
185040
185522
  }
185041
- return { files, depth, path: path24 };
185523
+ return { files, depth, path: path26 };
185042
185524
  }
185043
- async _formatEntry(dirent, path24) {
185525
+ async _formatEntry(dirent, path26) {
185044
185526
  let entry;
185045
- const basename5 = this._isDirent ? dirent.name : dirent;
185527
+ const basename6 = this._isDirent ? dirent.name : dirent;
185046
185528
  try {
185047
- const fullPath = presolve(pjoin(path24, basename5));
185048
- 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 };
185049
185531
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
185050
185532
  } catch (err) {
185051
185533
  this._onError(err);
@@ -185444,16 +185926,16 @@ var delFromSet = (main, prop, item) => {
185444
185926
  };
185445
185927
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
185446
185928
  var FsWatchInstances = /* @__PURE__ */ new Map();
185447
- function createFsWatchInstance(path24, options2, listener, errHandler, emitRaw) {
185929
+ function createFsWatchInstance(path26, options2, listener, errHandler, emitRaw) {
185448
185930
  const handleEvent = (rawEvent, evPath) => {
185449
- listener(path24);
185450
- emitRaw(rawEvent, evPath, { watchedPath: path24 });
185451
- if (evPath && path24 !== evPath) {
185452
- fsWatchBroadcast(sp.resolve(path24, evPath), KEY_LISTENERS, sp.join(path24, 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));
185453
185935
  }
185454
185936
  };
185455
185937
  try {
185456
- return fs_watch(path24, {
185938
+ return fs_watch(path26, {
185457
185939
  persistent: options2.persistent
185458
185940
  }, handleEvent);
185459
185941
  } catch (error) {
@@ -185469,12 +185951,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
185469
185951
  listener(val1, val2, val3);
185470
185952
  });
185471
185953
  };
185472
- var setFsWatchListener = (path24, fullPath, options2, handlers) => {
185954
+ var setFsWatchListener = (path26, fullPath, options2, handlers) => {
185473
185955
  const { listener, errHandler, rawEmitter } = handlers;
185474
185956
  let cont = FsWatchInstances.get(fullPath);
185475
185957
  let watcher;
185476
185958
  if (!options2.persistent) {
185477
- watcher = createFsWatchInstance(path24, options2, listener, errHandler, rawEmitter);
185959
+ watcher = createFsWatchInstance(path26, options2, listener, errHandler, rawEmitter);
185478
185960
  if (!watcher)
185479
185961
  return;
185480
185962
  return watcher.close.bind(watcher);
@@ -185485,7 +185967,7 @@ var setFsWatchListener = (path24, fullPath, options2, handlers) => {
185485
185967
  addAndConvert(cont, KEY_RAW, rawEmitter);
185486
185968
  } else {
185487
185969
  watcher = createFsWatchInstance(
185488
- path24,
185970
+ path26,
185489
185971
  options2,
185490
185972
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
185491
185973
  errHandler,
@@ -185500,7 +185982,7 @@ var setFsWatchListener = (path24, fullPath, options2, handlers) => {
185500
185982
  cont.watcherUnusable = true;
185501
185983
  if (isWindows && error.code === "EPERM") {
185502
185984
  try {
185503
- const fd = await open2(path24, "r");
185985
+ const fd = await open2(path26, "r");
185504
185986
  await fd.close();
185505
185987
  broadcastErr(error);
185506
185988
  } catch (err) {
@@ -185531,7 +186013,7 @@ var setFsWatchListener = (path24, fullPath, options2, handlers) => {
185531
186013
  };
185532
186014
  };
185533
186015
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
185534
- var setFsWatchFileListener = (path24, fullPath, options2, handlers) => {
186016
+ var setFsWatchFileListener = (path26, fullPath, options2, handlers) => {
185535
186017
  const { listener, rawEmitter } = handlers;
185536
186018
  let cont = FsWatchFileInstances.get(fullPath);
185537
186019
  const copts = cont && cont.options;
@@ -185553,7 +186035,7 @@ var setFsWatchFileListener = (path24, fullPath, options2, handlers) => {
185553
186035
  });
185554
186036
  const currmtime = curr.mtimeMs;
185555
186037
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
185556
- foreach(cont.listeners, (listener2) => listener2(path24, curr));
186038
+ foreach(cont.listeners, (listener2) => listener2(path26, curr));
185557
186039
  }
185558
186040
  })
185559
186041
  };
@@ -185583,13 +186065,13 @@ var NodeFsHandler = class {
185583
186065
  * @param listener on fs change
185584
186066
  * @returns closer for the watcher instance
185585
186067
  */
185586
- _watchWithNodeFs(path24, listener) {
186068
+ _watchWithNodeFs(path26, listener) {
185587
186069
  const opts = this.fsw.options;
185588
- const directory = sp.dirname(path24);
185589
- const basename5 = sp.basename(path24);
186070
+ const directory = sp.dirname(path26);
186071
+ const basename6 = sp.basename(path26);
185590
186072
  const parent = this.fsw._getWatchedDir(directory);
185591
- parent.add(basename5);
185592
- const absolutePath = sp.resolve(path24);
186073
+ parent.add(basename6);
186074
+ const absolutePath = sp.resolve(path26);
185593
186075
  const options2 = {
185594
186076
  persistent: opts.persistent
185595
186077
  };
@@ -185598,13 +186080,13 @@ var NodeFsHandler = class {
185598
186080
  let closer;
185599
186081
  if (opts.usePolling) {
185600
186082
  const enableBin = opts.interval !== opts.binaryInterval;
185601
- options2.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
185602
- closer = setFsWatchFileListener(path24, absolutePath, options2, {
186083
+ options2.interval = enableBin && isBinaryPath(basename6) ? opts.binaryInterval : opts.interval;
186084
+ closer = setFsWatchFileListener(path26, absolutePath, options2, {
185603
186085
  listener,
185604
186086
  rawEmitter: this.fsw._emitRaw
185605
186087
  });
185606
186088
  } else {
185607
- closer = setFsWatchListener(path24, absolutePath, options2, {
186089
+ closer = setFsWatchListener(path26, absolutePath, options2, {
185608
186090
  listener,
185609
186091
  errHandler: this._boundHandleError,
185610
186092
  rawEmitter: this.fsw._emitRaw
@@ -185621,12 +186103,12 @@ var NodeFsHandler = class {
185621
186103
  return;
185622
186104
  }
185623
186105
  const dirname8 = sp.dirname(file);
185624
- const basename5 = sp.basename(file);
186106
+ const basename6 = sp.basename(file);
185625
186107
  const parent = this.fsw._getWatchedDir(dirname8);
185626
186108
  let prevStats = stats;
185627
- if (parent.has(basename5))
186109
+ if (parent.has(basename6))
185628
186110
  return;
185629
- const listener = async (path24, newStats) => {
186111
+ const listener = async (path26, newStats) => {
185630
186112
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
185631
186113
  return;
185632
186114
  if (!newStats || newStats.mtimeMs === 0) {
@@ -185640,18 +186122,18 @@ var NodeFsHandler = class {
185640
186122
  this.fsw._emit(EV.CHANGE, file, newStats2);
185641
186123
  }
185642
186124
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
185643
- this.fsw._closeFile(path24);
186125
+ this.fsw._closeFile(path26);
185644
186126
  prevStats = newStats2;
185645
186127
  const closer2 = this._watchWithNodeFs(file, listener);
185646
186128
  if (closer2)
185647
- this.fsw._addPathCloser(path24, closer2);
186129
+ this.fsw._addPathCloser(path26, closer2);
185648
186130
  } else {
185649
186131
  prevStats = newStats2;
185650
186132
  }
185651
186133
  } catch (error) {
185652
- this.fsw._remove(dirname8, basename5);
186134
+ this.fsw._remove(dirname8, basename6);
185653
186135
  }
185654
- } else if (parent.has(basename5)) {
186136
+ } else if (parent.has(basename6)) {
185655
186137
  const at = newStats.atimeMs;
185656
186138
  const mt = newStats.mtimeMs;
185657
186139
  if (!at || at <= mt || mt !== prevStats.mtimeMs) {
@@ -185676,7 +186158,7 @@ var NodeFsHandler = class {
185676
186158
  * @param item basename of this item
185677
186159
  * @returns true if no more processing is needed for this entry.
185678
186160
  */
185679
- async _handleSymlink(entry, directory, path24, item) {
186161
+ async _handleSymlink(entry, directory, path26, item) {
185680
186162
  if (this.fsw.closed) {
185681
186163
  return;
185682
186164
  }
@@ -185686,7 +186168,7 @@ var NodeFsHandler = class {
185686
186168
  this.fsw._incrReadyCount();
185687
186169
  let linkPath;
185688
186170
  try {
185689
- linkPath = await fsrealpath(path24);
186171
+ linkPath = await fsrealpath(path26);
185690
186172
  } catch (e) {
185691
186173
  this.fsw._emitReady();
185692
186174
  return true;
@@ -185696,12 +186178,12 @@ var NodeFsHandler = class {
185696
186178
  if (dir.has(item)) {
185697
186179
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
185698
186180
  this.fsw._symlinkPaths.set(full, linkPath);
185699
- this.fsw._emit(EV.CHANGE, path24, entry.stats);
186181
+ this.fsw._emit(EV.CHANGE, path26, entry.stats);
185700
186182
  }
185701
186183
  } else {
185702
186184
  dir.add(item);
185703
186185
  this.fsw._symlinkPaths.set(full, linkPath);
185704
- this.fsw._emit(EV.ADD, path24, entry.stats);
186186
+ this.fsw._emit(EV.ADD, path26, entry.stats);
185705
186187
  }
185706
186188
  this.fsw._emitReady();
185707
186189
  return true;
@@ -185731,9 +186213,9 @@ var NodeFsHandler = class {
185731
186213
  return;
185732
186214
  }
185733
186215
  const item = entry.path;
185734
- let path24 = sp.join(directory, item);
186216
+ let path26 = sp.join(directory, item);
185735
186217
  current.add(item);
185736
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path24, item)) {
186218
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path26, item)) {
185737
186219
  return;
185738
186220
  }
185739
186221
  if (this.fsw.closed) {
@@ -185742,11 +186224,11 @@ var NodeFsHandler = class {
185742
186224
  }
185743
186225
  if (item === target || !target && !previous.has(item)) {
185744
186226
  this.fsw._incrReadyCount();
185745
- path24 = sp.join(dir, sp.relative(dir, path24));
185746
- this._addToNodeFs(path24, initialAdd, wh, depth + 1);
186227
+ path26 = sp.join(dir, sp.relative(dir, path26));
186228
+ this._addToNodeFs(path26, initialAdd, wh, depth + 1);
185747
186229
  }
185748
186230
  }).on(EV.ERROR, this._boundHandleError);
185749
- return new Promise((resolve7, reject) => {
186231
+ return new Promise((resolve9, reject) => {
185750
186232
  if (!stream)
185751
186233
  return reject();
185752
186234
  stream.once(STR_END, () => {
@@ -185755,7 +186237,7 @@ var NodeFsHandler = class {
185755
186237
  return;
185756
186238
  }
185757
186239
  const wasThrottled = throttler ? throttler.clear() : false;
185758
- resolve7(void 0);
186240
+ resolve9(void 0);
185759
186241
  previous.getChildren().filter((item) => {
185760
186242
  return item !== directory && !current.has(item);
185761
186243
  }).forEach((item) => {
@@ -185812,13 +186294,13 @@ var NodeFsHandler = class {
185812
186294
  * @param depth Child path actually targeted for watch
185813
186295
  * @param target Child path actually targeted for watch
185814
186296
  */
185815
- async _addToNodeFs(path24, initialAdd, priorWh, depth, target) {
186297
+ async _addToNodeFs(path26, initialAdd, priorWh, depth, target) {
185816
186298
  const ready = this.fsw._emitReady;
185817
- if (this.fsw._isIgnored(path24) || this.fsw.closed) {
186299
+ if (this.fsw._isIgnored(path26) || this.fsw.closed) {
185818
186300
  ready();
185819
186301
  return false;
185820
186302
  }
185821
- const wh = this.fsw._getWatchHelpers(path24);
186303
+ const wh = this.fsw._getWatchHelpers(path26);
185822
186304
  if (priorWh) {
185823
186305
  wh.filterPath = (entry) => priorWh.filterPath(entry);
185824
186306
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -185834,8 +186316,8 @@ var NodeFsHandler = class {
185834
186316
  const follow = this.fsw.options.followSymlinks;
185835
186317
  let closer;
185836
186318
  if (stats.isDirectory()) {
185837
- const absPath = sp.resolve(path24);
185838
- const targetPath = follow ? await fsrealpath(path24) : path24;
186319
+ const absPath = sp.resolve(path26);
186320
+ const targetPath = follow ? await fsrealpath(path26) : path26;
185839
186321
  if (this.fsw.closed)
185840
186322
  return;
185841
186323
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -185845,29 +186327,29 @@ var NodeFsHandler = class {
185845
186327
  this.fsw._symlinkPaths.set(absPath, targetPath);
185846
186328
  }
185847
186329
  } else if (stats.isSymbolicLink()) {
185848
- const targetPath = follow ? await fsrealpath(path24) : path24;
186330
+ const targetPath = follow ? await fsrealpath(path26) : path26;
185849
186331
  if (this.fsw.closed)
185850
186332
  return;
185851
186333
  const parent = sp.dirname(wh.watchPath);
185852
186334
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
185853
186335
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
185854
- closer = await this._handleDir(parent, stats, initialAdd, depth, path24, wh, targetPath);
186336
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path26, wh, targetPath);
185855
186337
  if (this.fsw.closed)
185856
186338
  return;
185857
186339
  if (targetPath !== void 0) {
185858
- this.fsw._symlinkPaths.set(sp.resolve(path24), targetPath);
186340
+ this.fsw._symlinkPaths.set(sp.resolve(path26), targetPath);
185859
186341
  }
185860
186342
  } else {
185861
186343
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
185862
186344
  }
185863
186345
  ready();
185864
186346
  if (closer)
185865
- this.fsw._addPathCloser(path24, closer);
186347
+ this.fsw._addPathCloser(path26, closer);
185866
186348
  return false;
185867
186349
  } catch (error) {
185868
186350
  if (this.fsw._handleError(error)) {
185869
186351
  ready();
185870
- return path24;
186352
+ return path26;
185871
186353
  }
185872
186354
  }
185873
186355
  }
@@ -185910,24 +186392,24 @@ function createPattern(matcher) {
185910
186392
  }
185911
186393
  return () => false;
185912
186394
  }
185913
- function normalizePath(path24) {
185914
- if (typeof path24 !== "string")
186395
+ function normalizePath(path26) {
186396
+ if (typeof path26 !== "string")
185915
186397
  throw new Error("string expected");
185916
- path24 = sp2.normalize(path24);
185917
- path24 = path24.replace(/\\/g, "/");
186398
+ path26 = sp2.normalize(path26);
186399
+ path26 = path26.replace(/\\/g, "/");
185918
186400
  let prepend = false;
185919
- if (path24.startsWith("//"))
186401
+ if (path26.startsWith("//"))
185920
186402
  prepend = true;
185921
- path24 = path24.replace(DOUBLE_SLASH_RE, "/");
186403
+ path26 = path26.replace(DOUBLE_SLASH_RE, "/");
185922
186404
  if (prepend)
185923
- path24 = "/" + path24;
185924
- return path24;
186405
+ path26 = "/" + path26;
186406
+ return path26;
185925
186407
  }
185926
186408
  function matchPatterns(patterns, testString, stats) {
185927
- const path24 = normalizePath(testString);
186409
+ const path26 = normalizePath(testString);
185928
186410
  for (let index = 0; index < patterns.length; index++) {
185929
186411
  const pattern = patterns[index];
185930
- if (pattern(path24, stats)) {
186412
+ if (pattern(path26, stats)) {
185931
186413
  return true;
185932
186414
  }
185933
186415
  }
@@ -185965,19 +186447,19 @@ var toUnix = (string) => {
185965
186447
  }
185966
186448
  return str;
185967
186449
  };
185968
- var normalizePathToUnix = (path24) => toUnix(sp2.normalize(toUnix(path24)));
185969
- var normalizeIgnored = (cwd = "") => (path24) => {
185970
- if (typeof path24 === "string") {
185971
- return normalizePathToUnix(sp2.isAbsolute(path24) ? path24 : sp2.join(cwd, path24));
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));
185972
186454
  } else {
185973
- return path24;
186455
+ return path26;
185974
186456
  }
185975
186457
  };
185976
- var getAbsolutePath = (path24, cwd) => {
185977
- if (sp2.isAbsolute(path24)) {
185978
- return path24;
186458
+ var getAbsolutePath = (path26, cwd) => {
186459
+ if (sp2.isAbsolute(path26)) {
186460
+ return path26;
185979
186461
  }
185980
- return sp2.join(cwd, path24);
186462
+ return sp2.join(cwd, path26);
185981
186463
  };
185982
186464
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
185983
186465
  var DirEntry = class {
@@ -186042,10 +186524,10 @@ var WatchHelper = class {
186042
186524
  dirParts;
186043
186525
  followSymlinks;
186044
186526
  statMethod;
186045
- constructor(path24, follow, fsw) {
186527
+ constructor(path26, follow, fsw) {
186046
186528
  this.fsw = fsw;
186047
- const watchPath = path24;
186048
- this.path = path24 = path24.replace(REPLACER_RE, "");
186529
+ const watchPath = path26;
186530
+ this.path = path26 = path26.replace(REPLACER_RE, "");
186049
186531
  this.watchPath = watchPath;
186050
186532
  this.fullWatchPath = sp2.resolve(watchPath);
186051
186533
  this.dirParts = [];
@@ -186185,20 +186667,20 @@ var FSWatcher = class extends EventEmitter {
186185
186667
  this._closePromise = void 0;
186186
186668
  let paths = unifyPaths(paths_);
186187
186669
  if (cwd) {
186188
- paths = paths.map((path24) => {
186189
- const absPath = getAbsolutePath(path24, cwd);
186670
+ paths = paths.map((path26) => {
186671
+ const absPath = getAbsolutePath(path26, cwd);
186190
186672
  return absPath;
186191
186673
  });
186192
186674
  }
186193
- paths.forEach((path24) => {
186194
- this._removeIgnoredPath(path24);
186675
+ paths.forEach((path26) => {
186676
+ this._removeIgnoredPath(path26);
186195
186677
  });
186196
186678
  this._userIgnored = void 0;
186197
186679
  if (!this._readyCount)
186198
186680
  this._readyCount = 0;
186199
186681
  this._readyCount += paths.length;
186200
- Promise.all(paths.map(async (path24) => {
186201
- const res = await this._nodeFsHandler._addToNodeFs(path24, !_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);
186202
186684
  if (res)
186203
186685
  this._emitReady();
186204
186686
  return res;
@@ -186220,17 +186702,17 @@ var FSWatcher = class extends EventEmitter {
186220
186702
  return this;
186221
186703
  const paths = unifyPaths(paths_);
186222
186704
  const { cwd } = this.options;
186223
- paths.forEach((path24) => {
186224
- if (!sp2.isAbsolute(path24) && !this._closers.has(path24)) {
186705
+ paths.forEach((path26) => {
186706
+ if (!sp2.isAbsolute(path26) && !this._closers.has(path26)) {
186225
186707
  if (cwd)
186226
- path24 = sp2.join(cwd, path24);
186227
- path24 = sp2.resolve(path24);
186708
+ path26 = sp2.join(cwd, path26);
186709
+ path26 = sp2.resolve(path26);
186228
186710
  }
186229
- this._closePath(path24);
186230
- this._addIgnoredPath(path24);
186231
- if (this._watched.has(path24)) {
186711
+ this._closePath(path26);
186712
+ this._addIgnoredPath(path26);
186713
+ if (this._watched.has(path26)) {
186232
186714
  this._addIgnoredPath({
186233
- path: path24,
186715
+ path: path26,
186234
186716
  recursive: true
186235
186717
  });
186236
186718
  }
@@ -186294,38 +186776,38 @@ var FSWatcher = class extends EventEmitter {
186294
186776
  * @param stats arguments to be passed with event
186295
186777
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
186296
186778
  */
186297
- async _emit(event, path24, stats) {
186779
+ async _emit(event, path26, stats) {
186298
186780
  if (this.closed)
186299
186781
  return;
186300
186782
  const opts = this.options;
186301
186783
  if (isWindows)
186302
- path24 = sp2.normalize(path24);
186784
+ path26 = sp2.normalize(path26);
186303
186785
  if (opts.cwd)
186304
- path24 = sp2.relative(opts.cwd, path24);
186305
- const args = [path24];
186786
+ path26 = sp2.relative(opts.cwd, path26);
186787
+ const args = [path26];
186306
186788
  if (stats != null)
186307
186789
  args.push(stats);
186308
186790
  const awf = opts.awaitWriteFinish;
186309
186791
  let pw;
186310
- if (awf && (pw = this._pendingWrites.get(path24))) {
186792
+ if (awf && (pw = this._pendingWrites.get(path26))) {
186311
186793
  pw.lastChange = /* @__PURE__ */ new Date();
186312
186794
  return this;
186313
186795
  }
186314
186796
  if (opts.atomic) {
186315
186797
  if (event === EVENTS.UNLINK) {
186316
- this._pendingUnlinks.set(path24, [event, ...args]);
186798
+ this._pendingUnlinks.set(path26, [event, ...args]);
186317
186799
  setTimeout(() => {
186318
- this._pendingUnlinks.forEach((entry, path25) => {
186800
+ this._pendingUnlinks.forEach((entry, path27) => {
186319
186801
  this.emit(...entry);
186320
186802
  this.emit(EVENTS.ALL, ...entry);
186321
- this._pendingUnlinks.delete(path25);
186803
+ this._pendingUnlinks.delete(path27);
186322
186804
  });
186323
186805
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
186324
186806
  return this;
186325
186807
  }
186326
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path24)) {
186808
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path26)) {
186327
186809
  event = EVENTS.CHANGE;
186328
- this._pendingUnlinks.delete(path24);
186810
+ this._pendingUnlinks.delete(path26);
186329
186811
  }
186330
186812
  }
186331
186813
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -186343,16 +186825,16 @@ var FSWatcher = class extends EventEmitter {
186343
186825
  this.emitWithAll(event, args);
186344
186826
  }
186345
186827
  };
186346
- this._awaitWriteFinish(path24, awf.stabilityThreshold, event, awfEmit);
186828
+ this._awaitWriteFinish(path26, awf.stabilityThreshold, event, awfEmit);
186347
186829
  return this;
186348
186830
  }
186349
186831
  if (event === EVENTS.CHANGE) {
186350
- const isThrottled = !this._throttle(EVENTS.CHANGE, path24, 50);
186832
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path26, 50);
186351
186833
  if (isThrottled)
186352
186834
  return this;
186353
186835
  }
186354
186836
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
186355
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path24) : path24;
186837
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path26) : path26;
186356
186838
  let stats2;
186357
186839
  try {
186358
186840
  stats2 = await stat3(fullPath);
@@ -186383,23 +186865,23 @@ var FSWatcher = class extends EventEmitter {
186383
186865
  * @param timeout duration of time to suppress duplicate actions
186384
186866
  * @returns tracking object or false if action should be suppressed
186385
186867
  */
186386
- _throttle(actionType, path24, timeout) {
186868
+ _throttle(actionType, path26, timeout) {
186387
186869
  if (!this._throttled.has(actionType)) {
186388
186870
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
186389
186871
  }
186390
186872
  const action = this._throttled.get(actionType);
186391
186873
  if (!action)
186392
186874
  throw new Error("invalid throttle");
186393
- const actionPath = action.get(path24);
186875
+ const actionPath = action.get(path26);
186394
186876
  if (actionPath) {
186395
186877
  actionPath.count++;
186396
186878
  return false;
186397
186879
  }
186398
186880
  let timeoutObject;
186399
186881
  const clear = () => {
186400
- const item = action.get(path24);
186882
+ const item = action.get(path26);
186401
186883
  const count = item ? item.count : 0;
186402
- action.delete(path24);
186884
+ action.delete(path26);
186403
186885
  clearTimeout(timeoutObject);
186404
186886
  if (item)
186405
186887
  clearTimeout(item.timeoutObject);
@@ -186407,7 +186889,7 @@ var FSWatcher = class extends EventEmitter {
186407
186889
  };
186408
186890
  timeoutObject = setTimeout(clear, timeout);
186409
186891
  const thr = { timeoutObject, clear, count: 0 };
186410
- action.set(path24, thr);
186892
+ action.set(path26, thr);
186411
186893
  return thr;
186412
186894
  }
186413
186895
  _incrReadyCount() {
@@ -186421,44 +186903,44 @@ var FSWatcher = class extends EventEmitter {
186421
186903
  * @param event
186422
186904
  * @param awfEmit Callback to be called when ready for event to be emitted.
186423
186905
  */
186424
- _awaitWriteFinish(path24, threshold, event, awfEmit) {
186906
+ _awaitWriteFinish(path26, threshold, event, awfEmit) {
186425
186907
  const awf = this.options.awaitWriteFinish;
186426
186908
  if (typeof awf !== "object")
186427
186909
  return;
186428
186910
  const pollInterval = awf.pollInterval;
186429
186911
  let timeoutHandler;
186430
- let fullPath = path24;
186431
- if (this.options.cwd && !sp2.isAbsolute(path24)) {
186432
- fullPath = sp2.join(this.options.cwd, path24);
186912
+ let fullPath = path26;
186913
+ if (this.options.cwd && !sp2.isAbsolute(path26)) {
186914
+ fullPath = sp2.join(this.options.cwd, path26);
186433
186915
  }
186434
186916
  const now = /* @__PURE__ */ new Date();
186435
186917
  const writes = this._pendingWrites;
186436
186918
  function awaitWriteFinishFn(prevStat) {
186437
186919
  statcb(fullPath, (err, curStat) => {
186438
- if (err || !writes.has(path24)) {
186920
+ if (err || !writes.has(path26)) {
186439
186921
  if (err && err.code !== "ENOENT")
186440
186922
  awfEmit(err);
186441
186923
  return;
186442
186924
  }
186443
186925
  const now2 = Number(/* @__PURE__ */ new Date());
186444
186926
  if (prevStat && curStat.size !== prevStat.size) {
186445
- writes.get(path24).lastChange = now2;
186927
+ writes.get(path26).lastChange = now2;
186446
186928
  }
186447
- const pw = writes.get(path24);
186929
+ const pw = writes.get(path26);
186448
186930
  const df = now2 - pw.lastChange;
186449
186931
  if (df >= threshold) {
186450
- writes.delete(path24);
186932
+ writes.delete(path26);
186451
186933
  awfEmit(void 0, curStat);
186452
186934
  } else {
186453
186935
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
186454
186936
  }
186455
186937
  });
186456
186938
  }
186457
- if (!writes.has(path24)) {
186458
- writes.set(path24, {
186939
+ if (!writes.has(path26)) {
186940
+ writes.set(path26, {
186459
186941
  lastChange: now,
186460
186942
  cancelWait: () => {
186461
- writes.delete(path24);
186943
+ writes.delete(path26);
186462
186944
  clearTimeout(timeoutHandler);
186463
186945
  return event;
186464
186946
  }
@@ -186469,8 +186951,8 @@ var FSWatcher = class extends EventEmitter {
186469
186951
  /**
186470
186952
  * Determines whether user has asked to ignore this path.
186471
186953
  */
186472
- _isIgnored(path24, stats) {
186473
- if (this.options.atomic && DOT_RE.test(path24))
186954
+ _isIgnored(path26, stats) {
186955
+ if (this.options.atomic && DOT_RE.test(path26))
186474
186956
  return true;
186475
186957
  if (!this._userIgnored) {
186476
186958
  const { cwd } = this.options;
@@ -186480,17 +186962,17 @@ var FSWatcher = class extends EventEmitter {
186480
186962
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
186481
186963
  this._userIgnored = anymatch(list, void 0);
186482
186964
  }
186483
- return this._userIgnored(path24, stats);
186965
+ return this._userIgnored(path26, stats);
186484
186966
  }
186485
- _isntIgnored(path24, stat4) {
186486
- return !this._isIgnored(path24, stat4);
186967
+ _isntIgnored(path26, stat4) {
186968
+ return !this._isIgnored(path26, stat4);
186487
186969
  }
186488
186970
  /**
186489
186971
  * Provides a set of common helpers and properties relating to symlink handling.
186490
186972
  * @param path file or directory pattern being watched
186491
186973
  */
186492
- _getWatchHelpers(path24) {
186493
- return new WatchHelper(path24, this.options.followSymlinks, this);
186974
+ _getWatchHelpers(path26) {
186975
+ return new WatchHelper(path26, this.options.followSymlinks, this);
186494
186976
  }
186495
186977
  // Directory helpers
186496
186978
  // -----------------
@@ -186522,63 +187004,63 @@ var FSWatcher = class extends EventEmitter {
186522
187004
  * @param item base path of item/directory
186523
187005
  */
186524
187006
  _remove(directory, item, isDirectory) {
186525
- const path24 = sp2.join(directory, item);
186526
- const fullPath = sp2.resolve(path24);
186527
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path24) || this._watched.has(fullPath);
186528
- if (!this._throttle("remove", path24, 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))
186529
187011
  return;
186530
187012
  if (!isDirectory && this._watched.size === 1) {
186531
187013
  this.add(directory, item, true);
186532
187014
  }
186533
- const wp = this._getWatchedDir(path24);
187015
+ const wp = this._getWatchedDir(path26);
186534
187016
  const nestedDirectoryChildren = wp.getChildren();
186535
- nestedDirectoryChildren.forEach((nested) => this._remove(path24, nested));
187017
+ nestedDirectoryChildren.forEach((nested) => this._remove(path26, nested));
186536
187018
  const parent = this._getWatchedDir(directory);
186537
187019
  const wasTracked = parent.has(item);
186538
187020
  parent.remove(item);
186539
187021
  if (this._symlinkPaths.has(fullPath)) {
186540
187022
  this._symlinkPaths.delete(fullPath);
186541
187023
  }
186542
- let relPath = path24;
187024
+ let relPath = path26;
186543
187025
  if (this.options.cwd)
186544
- relPath = sp2.relative(this.options.cwd, path24);
187026
+ relPath = sp2.relative(this.options.cwd, path26);
186545
187027
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
186546
187028
  const event = this._pendingWrites.get(relPath).cancelWait();
186547
187029
  if (event === EVENTS.ADD)
186548
187030
  return;
186549
187031
  }
186550
- this._watched.delete(path24);
187032
+ this._watched.delete(path26);
186551
187033
  this._watched.delete(fullPath);
186552
187034
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
186553
- if (wasTracked && !this._isIgnored(path24))
186554
- this._emit(eventName, path24);
186555
- this._closePath(path24);
187035
+ if (wasTracked && !this._isIgnored(path26))
187036
+ this._emit(eventName, path26);
187037
+ this._closePath(path26);
186556
187038
  }
186557
187039
  /**
186558
187040
  * Closes all watchers for a path
186559
187041
  */
186560
- _closePath(path24) {
186561
- this._closeFile(path24);
186562
- const dir = sp2.dirname(path24);
186563
- this._getWatchedDir(dir).remove(sp2.basename(path24));
187042
+ _closePath(path26) {
187043
+ this._closeFile(path26);
187044
+ const dir = sp2.dirname(path26);
187045
+ this._getWatchedDir(dir).remove(sp2.basename(path26));
186564
187046
  }
186565
187047
  /**
186566
187048
  * Closes only file-specific watchers
186567
187049
  */
186568
- _closeFile(path24) {
186569
- const closers = this._closers.get(path24);
187050
+ _closeFile(path26) {
187051
+ const closers = this._closers.get(path26);
186570
187052
  if (!closers)
186571
187053
  return;
186572
187054
  closers.forEach((closer) => closer());
186573
- this._closers.delete(path24);
187055
+ this._closers.delete(path26);
186574
187056
  }
186575
- _addPathCloser(path24, closer) {
187057
+ _addPathCloser(path26, closer) {
186576
187058
  if (!closer)
186577
187059
  return;
186578
- let list = this._closers.get(path24);
187060
+ let list = this._closers.get(path26);
186579
187061
  if (!list) {
186580
187062
  list = [];
186581
- this._closers.set(path24, list);
187063
+ this._closers.set(path26, list);
186582
187064
  }
186583
187065
  list.push(closer);
186584
187066
  }
@@ -186627,8 +187109,8 @@ var PortAllocator = class {
186627
187109
  };
186628
187110
 
186629
187111
  // src/lib/dev/stable-port-allocator.ts
186630
- import * as fs12 from "fs";
186631
- import * as path8 from "path";
187112
+ import * as fs13 from "fs";
187113
+ import * as path9 from "path";
186632
187114
  var PORT_RANGE_START2 = 4e4;
186633
187115
  var PORT_RANGE_END2 = 49999;
186634
187116
  var StablePortAllocator = class {
@@ -186637,16 +187119,16 @@ var StablePortAllocator = class {
186637
187119
  savedPorts = {};
186638
187120
  usedPorts = /* @__PURE__ */ new Set();
186639
187121
  constructor(projectRoot, key = "default") {
186640
- this.portsDir = path8.join(projectRoot, ".specific", "keys", key);
186641
- 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");
186642
187124
  this.loadPorts();
186643
187125
  }
186644
187126
  loadPorts() {
186645
- if (!fs12.existsSync(this.portsFilePath)) {
187127
+ if (!fs13.existsSync(this.portsFilePath)) {
186646
187128
  return;
186647
187129
  }
186648
187130
  try {
186649
- const content = fs12.readFileSync(this.portsFilePath, "utf-8");
187131
+ const content = fs13.readFileSync(this.portsFilePath, "utf-8");
186650
187132
  const data = JSON.parse(content);
186651
187133
  if (data.version === 1 && data.ports) {
186652
187134
  this.savedPorts = data.ports;
@@ -186659,14 +187141,14 @@ var StablePortAllocator = class {
186659
187141
  }
186660
187142
  }
186661
187143
  savePorts() {
186662
- if (!fs12.existsSync(this.portsDir)) {
186663
- fs12.mkdirSync(this.portsDir, { recursive: true });
187144
+ if (!fs13.existsSync(this.portsDir)) {
187145
+ fs13.mkdirSync(this.portsDir, { recursive: true });
186664
187146
  }
186665
187147
  const data = {
186666
187148
  version: 1,
186667
187149
  ports: this.savedPorts
186668
187150
  };
186669
- fs12.writeFileSync(this.portsFilePath, JSON.stringify(data, null, 2));
187151
+ fs13.writeFileSync(this.portsFilePath, JSON.stringify(data, null, 2));
186670
187152
  }
186671
187153
  allocateRandom() {
186672
187154
  const rangeSize = PORT_RANGE_END2 - PORT_RANGE_START2 + 1;
@@ -186697,329 +187179,6 @@ import * as fs14 from "fs";
186697
187179
  import * as path10 from "path";
186698
187180
  import * as net from "net";
186699
187181
  import { spawn } from "child_process";
186700
-
186701
- // src/lib/bin/types.ts
186702
- var BinaryNotFoundError = class extends Error {
186703
- constructor(binaryName, version, platformInfo) {
186704
- super(
186705
- `Binary "${binaryName}" version ${version} is not available for ${platformInfo.platform}-${platformInfo.arch}`
186706
- );
186707
- this.binaryName = binaryName;
186708
- this.version = version;
186709
- this.platformInfo = platformInfo;
186710
- this.name = "BinaryNotFoundError";
186711
- }
186712
- };
186713
- var DownloadError = class extends Error {
186714
- constructor(url, statusCode, cause) {
186715
- super(
186716
- statusCode ? `Failed to download ${url}: HTTP ${statusCode}` : `Failed to download ${url}: ${cause?.message || "Unknown error"}`
186717
- );
186718
- this.url = url;
186719
- this.statusCode = statusCode;
186720
- this.cause = cause;
186721
- this.name = "DownloadError";
186722
- }
186723
- };
186724
- var ExtractionError = class extends Error {
186725
- constructor(archivePath, cause) {
186726
- super(
186727
- `Failed to extract ${archivePath}: ${cause?.message || "Unknown error"}`
186728
- );
186729
- this.archivePath = archivePath;
186730
- this.cause = cause;
186731
- this.name = "ExtractionError";
186732
- }
186733
- };
186734
-
186735
- // src/lib/bin/manager.ts
186736
- import * as fs13 from "fs";
186737
- import * as path9 from "path";
186738
- import * as os6 from "os";
186739
- import { createReadStream } from "fs";
186740
- import { createTarExtractor, extractTo } from "tar-vern";
186741
- function getLibraryEnv(binary) {
186742
- if (!binary.libraryPath) {
186743
- return {};
186744
- }
186745
- const platform4 = os6.platform();
186746
- if (platform4 === "darwin") {
186747
- return { DYLD_LIBRARY_PATH: binary.libraryPath };
186748
- } else if (platform4 === "linux") {
186749
- return { LD_LIBRARY_PATH: binary.libraryPath };
186750
- }
186751
- return {};
186752
- }
186753
- function getBinBaseDir() {
186754
- return path9.join(os6.homedir(), ".specific", "bin");
186755
- }
186756
- function getPlatformInfo() {
186757
- const platform4 = os6.platform();
186758
- const arch3 = os6.arch();
186759
- if (platform4 !== "darwin" && platform4 !== "linux") {
186760
- throw new Error(
186761
- `Unsupported platform: ${platform4}. Only macOS and Linux are supported.`
186762
- );
186763
- }
186764
- const archStr = arch3;
186765
- let mappedArch;
186766
- if (archStr === "x64" || archStr === "x86_64") {
186767
- mappedArch = "x64";
186768
- } else if (archStr === "arm64" || archStr === "aarch64") {
186769
- mappedArch = "arm64";
186770
- } else {
186771
- throw new Error(
186772
- `Unsupported architecture: ${arch3}. Only x64 and arm64 are supported.`
186773
- );
186774
- }
186775
- return { platform: platform4, arch: mappedArch };
186776
- }
186777
- function getBinaryDir(definition, version, platformInfo) {
186778
- return path9.join(
186779
- getBinBaseDir(),
186780
- definition.name,
186781
- version,
186782
- `${platformInfo.platform}-${platformInfo.arch}`
186783
- );
186784
- }
186785
- function isBinaryInstalled(definition, version, platformInfo) {
186786
- const binDir = getBinaryDir(definition, version, platformInfo);
186787
- for (const execPath of definition.executables) {
186788
- const fullPath = path9.join(binDir, execPath);
186789
- if (!fs13.existsSync(fullPath)) {
186790
- return false;
186791
- }
186792
- }
186793
- return true;
186794
- }
186795
- function buildDownloadUrl(definition, version, platformInfo) {
186796
- const mapping = definition.platformMapping[platformInfo.platform]?.[platformInfo.arch];
186797
- if (!mapping) {
186798
- throw new BinaryNotFoundError(definition.name, version, platformInfo);
186799
- }
186800
- return definition.urlTemplate.replace(/{version}/g, version).replace(/{platform}/g, mapping.platform).replace(/{arch}/g, mapping.arch);
186801
- }
186802
- async function downloadFile(url, destPath, onProgress) {
186803
- let response;
186804
- try {
186805
- response = await fetch(url);
186806
- } catch (error) {
186807
- throw new DownloadError(url, void 0, error);
186808
- }
186809
- if (!response.ok) {
186810
- throw new DownloadError(url, response.status);
186811
- }
186812
- const totalBytes = parseInt(
186813
- response.headers.get("content-length") || "0",
186814
- 10
186815
- );
186816
- let bytesDownloaded = 0;
186817
- const parentDir = path9.dirname(destPath);
186818
- if (!fs13.existsSync(parentDir)) {
186819
- fs13.mkdirSync(parentDir, { recursive: true });
186820
- }
186821
- const partPath = destPath + ".part";
186822
- const fileStream = fs13.createWriteStream(partPath);
186823
- try {
186824
- const reader = response.body.getReader();
186825
- while (true) {
186826
- const { done, value } = await reader.read();
186827
- if (done) break;
186828
- bytesDownloaded += value.length;
186829
- fileStream.write(Buffer.from(value));
186830
- if (onProgress) {
186831
- onProgress({
186832
- phase: "downloading",
186833
- bytesDownloaded,
186834
- totalBytes: totalBytes || void 0,
186835
- percent: totalBytes ? Math.round(bytesDownloaded / totalBytes * 100) : void 0
186836
- });
186837
- }
186838
- }
186839
- await new Promise((resolve7, reject) => {
186840
- fileStream.end((err) => {
186841
- if (err) reject(err);
186842
- else resolve7();
186843
- });
186844
- });
186845
- fs13.renameSync(partPath, destPath);
186846
- } catch (error) {
186847
- try {
186848
- fileStream.close();
186849
- if (fs13.existsSync(partPath)) {
186850
- fs13.unlinkSync(partPath);
186851
- }
186852
- } catch {
186853
- }
186854
- throw new DownloadError(url, void 0, error);
186855
- }
186856
- }
186857
- async function extractTarball(archivePath, destDir, definition, onProgress) {
186858
- onProgress?.({ phase: "extracting" });
186859
- if (!fs13.existsSync(destDir)) {
186860
- fs13.mkdirSync(destDir, { recursive: true });
186861
- }
186862
- try {
186863
- const fileStream = createReadStream(archivePath);
186864
- const entries = createTarExtractor(fileStream, "gzip");
186865
- await extractTo(entries, destDir);
186866
- onProgress?.({ phase: "finalizing" });
186867
- for (const execPath of definition.executables) {
186868
- const fullPath = path9.join(destDir, execPath);
186869
- if (fs13.existsSync(fullPath)) {
186870
- fs13.chmodSync(fullPath, 493);
186871
- }
186872
- }
186873
- } catch (error) {
186874
- throw new ExtractionError(archivePath, error);
186875
- }
186876
- }
186877
- async function ensureBinary(definition, version, onProgress) {
186878
- const platformInfo = getPlatformInfo();
186879
- const resolvedVersion = version || definition.versions[0];
186880
- if (!definition.versions.includes(resolvedVersion)) {
186881
- throw new Error(
186882
- `Version ${resolvedVersion} not supported for ${definition.name}. Supported versions: ${definition.versions.join(", ")}`
186883
- );
186884
- }
186885
- const binDir = getBinaryDir(definition, resolvedVersion, platformInfo);
186886
- if (!isBinaryInstalled(definition, resolvedVersion, platformInfo)) {
186887
- const url = buildDownloadUrl(definition, resolvedVersion, platformInfo);
186888
- if (definition.type === "binary") {
186889
- if (definition.executables.length !== 1) {
186890
- throw new Error(
186891
- `Binary type definitions must have exactly one executable, got ${definition.executables.length}`
186892
- );
186893
- }
186894
- if (!fs13.existsSync(binDir)) {
186895
- fs13.mkdirSync(binDir, { recursive: true });
186896
- }
186897
- const execPath = path9.join(binDir, definition.executables[0]);
186898
- await downloadFile(url, execPath, onProgress);
186899
- fs13.chmodSync(execPath, 493);
186900
- onProgress?.({ phase: "finalizing" });
186901
- } else {
186902
- const downloadDir = path9.join(getBinBaseDir(), "downloads");
186903
- const archiveName = `${definition.name}-${resolvedVersion}-${platformInfo.platform}-${platformInfo.arch}.tar.gz`;
186904
- const archivePath = path9.join(downloadDir, archiveName);
186905
- try {
186906
- await downloadFile(url, archivePath, onProgress);
186907
- await extractTarball(archivePath, binDir, definition, onProgress);
186908
- } finally {
186909
- try {
186910
- if (fs13.existsSync(archivePath)) {
186911
- fs13.unlinkSync(archivePath);
186912
- }
186913
- } catch {
186914
- }
186915
- }
186916
- }
186917
- }
186918
- const executables = {};
186919
- for (const execPath of definition.executables) {
186920
- const name = path9.basename(execPath);
186921
- executables[name] = path9.join(binDir, execPath);
186922
- }
186923
- const libraryPath = definition.libraryDir ? path9.join(binDir, definition.libraryDir) : void 0;
186924
- return {
186925
- rootDir: binDir,
186926
- version: resolvedVersion,
186927
- executables,
186928
- libraryPath
186929
- };
186930
- }
186931
-
186932
- // src/lib/bin/definitions/postgres.ts
186933
- var postgresBinary = {
186934
- name: "postgres",
186935
- versions: ["18.1"],
186936
- // URL: binaries.specific.dev/{SOFTWARE}/{VERSION}/{ARCH}.tar.gz
186937
- urlTemplate: "https://binaries.specific.dev/postgres/{version}/{arch}.tar.gz",
186938
- platformMapping: {
186939
- darwin: {
186940
- arm64: { platform: "macos", arch: "macos_arm" },
186941
- x64: { platform: "macos", arch: "macos_x86" }
186942
- },
186943
- linux: {
186944
- arm64: { platform: "linux", arch: "linux_arm" },
186945
- x64: { platform: "linux", arch: "linux_x86" }
186946
- }
186947
- },
186948
- // Archive contains bin/ and lib/ directories at root
186949
- stripComponents: 0,
186950
- // Core PostgreSQL executables (in bin/ directory)
186951
- executables: ["bin/postgres", "bin/initdb", "bin/psql"],
186952
- // Library directory relative to root (for setting LD_LIBRARY_PATH)
186953
- libraryDir: "lib"
186954
- };
186955
-
186956
- // src/lib/bin/definitions/redis.ts
186957
- var redisBinary = {
186958
- name: "redis",
186959
- versions: ["8.4.0"],
186960
- // URL: binaries.specific.dev/{SOFTWARE}/{VERSION}/{ARCH}.tar.gz
186961
- urlTemplate: "https://binaries.specific.dev/redis/{version}/{arch}.tar.gz",
186962
- platformMapping: {
186963
- darwin: {
186964
- arm64: { platform: "macos", arch: "macos_arm" },
186965
- x64: { platform: "macos", arch: "macos_x86" }
186966
- },
186967
- linux: {
186968
- arm64: { platform: "linux", arch: "linux_arm" },
186969
- x64: { platform: "linux", arch: "linux_x86" }
186970
- }
186971
- },
186972
- // Binaries are at the root of the archive
186973
- stripComponents: 0,
186974
- // Core Redis executable
186975
- executables: ["redis-server"]
186976
- };
186977
-
186978
- // src/lib/bin/definitions/electric.ts
186979
- var electricBinary = {
186980
- name: "electric",
186981
- versions: ["1.2.11"],
186982
- // URL: binaries.specific.dev/{SOFTWARE}/{VERSION}/{ARCH}.tar.gz
186983
- urlTemplate: "https://binaries.specific.dev/electric/{version}/{arch}.tar.gz",
186984
- platformMapping: {
186985
- darwin: {
186986
- arm64: { platform: "macos", arch: "macos_arm" },
186987
- x64: { platform: "macos", arch: "macos_x86" }
186988
- },
186989
- linux: {
186990
- arm64: { platform: "linux", arch: "linux_arm" },
186991
- x64: { platform: "linux", arch: "linux_x86" }
186992
- }
186993
- },
186994
- // Binaries are at the root of the archive
186995
- stripComponents: 0,
186996
- // Core Electric executable
186997
- executables: ["electric"]
186998
- };
186999
-
187000
- // src/lib/bin/definitions/drizzle-gateway.ts
187001
- var drizzleGatewayBinary = {
187002
- name: "drizzle-gateway",
187003
- versions: ["1.2.0"],
187004
- // Official Drizzle Gateway download URL from R2
187005
- urlTemplate: "https://pub-e240a4fd7085425baf4a7951e7611520.r2.dev/drizzle-gateway-{version}-{arch}",
187006
- platformMapping: {
187007
- darwin: {
187008
- arm64: { platform: "macos", arch: "macos-arm64" },
187009
- x64: { platform: "macos", arch: "macos-x64" }
187010
- },
187011
- linux: {
187012
- arm64: { platform: "linux", arch: "linux-arm64" },
187013
- x64: { platform: "linux", arch: "linux-x64" }
187014
- }
187015
- },
187016
- // Raw binary, not a tarball
187017
- type: "binary",
187018
- // The executable name
187019
- executables: ["drizzle-gateway"]
187020
- };
187021
-
187022
- // src/lib/dev/database-manager.ts
187023
187182
  async function startPostgres(pg, port, dataDir, onProgress) {
187024
187183
  const binary = await ensureBinary(postgresBinary, void 0, onProgress);
187025
187184
  const dbDataPath = path10.join(process.cwd(), dataDir, pg.name);
@@ -187142,7 +187301,7 @@ async function startStorage(storage, port, dataDir) {
187142
187301
  };
187143
187302
  }
187144
187303
  async function runCommand(command, args, env2) {
187145
- return new Promise((resolve7, reject) => {
187304
+ return new Promise((resolve9, reject) => {
187146
187305
  const proc = spawn(command, args, {
187147
187306
  stdio: ["ignore", "pipe", "pipe"],
187148
187307
  env: env2
@@ -187153,7 +187312,7 @@ async function runCommand(command, args, env2) {
187153
187312
  });
187154
187313
  proc.on("close", (code) => {
187155
187314
  if (code === 0) {
187156
- resolve7();
187315
+ resolve9();
187157
187316
  } else {
187158
187317
  reject(new Error(`Command failed with code ${code}: ${stderr}`));
187159
187318
  }
@@ -187162,7 +187321,7 @@ async function runCommand(command, args, env2) {
187162
187321
  });
187163
187322
  }
187164
187323
  async function createPostgresDatabase(postgresPath, dataDir, dbName, env2) {
187165
- return new Promise((resolve7, reject) => {
187324
+ return new Promise((resolve9, reject) => {
187166
187325
  const proc = spawn(
187167
187326
  postgresPath,
187168
187327
  ["--single", "-D", dataDir, "postgres"],
@@ -187176,7 +187335,7 @@ async function createPostgresDatabase(postgresPath, dataDir, dbName, env2) {
187176
187335
  stderr += data.toString();
187177
187336
  });
187178
187337
  proc.on("close", (code) => {
187179
- resolve7();
187338
+ resolve9();
187180
187339
  });
187181
187340
  proc.on("error", reject);
187182
187341
  proc.stdin?.write(`CREATE DATABASE "${dbName}";
@@ -187196,33 +187355,33 @@ async function waitForTcpPort(host, port, timeoutMs = 3e4) {
187196
187355
  throw new Error(`Port ${port} did not become available within timeout`);
187197
187356
  }
187198
187357
  function checkTcpPort(host, port) {
187199
- return new Promise((resolve7) => {
187358
+ return new Promise((resolve9) => {
187200
187359
  const socket = new net.Socket();
187201
187360
  socket.setTimeout(1e3);
187202
187361
  socket.on("connect", () => {
187203
187362
  socket.destroy();
187204
- resolve7(true);
187363
+ resolve9(true);
187205
187364
  });
187206
187365
  socket.on("timeout", () => {
187207
187366
  socket.destroy();
187208
- resolve7(false);
187367
+ resolve9(false);
187209
187368
  });
187210
187369
  socket.on("error", () => {
187211
187370
  socket.destroy();
187212
- resolve7(false);
187371
+ resolve9(false);
187213
187372
  });
187214
187373
  socket.connect(port, host);
187215
187374
  });
187216
187375
  }
187217
187376
  async function stopProcess(proc) {
187218
- return new Promise((resolve7) => {
187377
+ return new Promise((resolve9) => {
187219
187378
  if (proc.killed || proc.exitCode !== null) {
187220
- resolve7();
187379
+ resolve9();
187221
187380
  return;
187222
187381
  }
187223
187382
  proc.once("exit", () => {
187224
187383
  clearTimeout(forceKillTimeout);
187225
- resolve7();
187384
+ resolve9();
187226
187385
  });
187227
187386
  proc.kill("SIGTERM");
187228
187387
  const forceKillTimeout = setTimeout(() => {
@@ -187233,7 +187392,7 @@ async function stopProcess(proc) {
187233
187392
  });
187234
187393
  }
187235
187394
  function sleep2(ms) {
187236
- return new Promise((resolve7) => setTimeout(resolve7, ms));
187395
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
187237
187396
  }
187238
187397
 
187239
187398
  // src/lib/dev/service-runner.ts
@@ -187242,7 +187401,7 @@ import { spawn as spawn2 } from "child_process";
187242
187401
  // src/lib/local/parser.ts
187243
187402
  var import_hcl2_json_parser2 = __toESM(require_dist(), 1);
187244
187403
  import { readFile, writeFile } from "fs/promises";
187245
- import { existsSync as existsSync10 } from "fs";
187404
+ import { existsSync as existsSync11 } from "fs";
187246
187405
  var { parseToObject: parseToObject2 } = import_hcl2_json_parser2.default;
187247
187406
  var LOCAL_FILE = "specific.local";
187248
187407
  var HEADER_COMMENT = `# Local secrets and configuration
@@ -187283,7 +187442,7 @@ async function parseLocalFile(content) {
187283
187442
  return { secrets, configs };
187284
187443
  }
187285
187444
  async function loadLocal() {
187286
- if (!existsSync10(LOCAL_FILE)) {
187445
+ if (!existsSync11(LOCAL_FILE)) {
187287
187446
  return { secrets: /* @__PURE__ */ new Map(), configs: /* @__PURE__ */ new Map() };
187288
187447
  }
187289
187448
  const content = await readFile(LOCAL_FILE, "utf-8");
@@ -187323,7 +187482,7 @@ ${newLine}
187323
187482
  }
187324
187483
  async function saveLocalSecret(name, value) {
187325
187484
  let content = "";
187326
- if (existsSync10(LOCAL_FILE)) {
187485
+ if (existsSync11(LOCAL_FILE)) {
187327
187486
  content = await readFile(LOCAL_FILE, "utf-8");
187328
187487
  } else {
187329
187488
  content = HEADER_COMMENT;
@@ -187333,7 +187492,7 @@ async function saveLocalSecret(name, value) {
187333
187492
  }
187334
187493
  async function saveLocalConfig(name, value) {
187335
187494
  let content = "";
187336
- if (existsSync10(LOCAL_FILE)) {
187495
+ if (existsSync11(LOCAL_FILE)) {
187337
187496
  content = await readFile(LOCAL_FILE, "utf-8");
187338
187497
  } else {
187339
187498
  content = HEADER_COMMENT;
@@ -187342,6 +187501,13 @@ async function saveLocalConfig(name, value) {
187342
187501
  await writeFile(LOCAL_FILE, content);
187343
187502
  }
187344
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
+
187345
187511
  // src/lib/dev/env-resolver.ts
187346
187512
  var MissingSecretError = class extends Error {
187347
187513
  constructor(secretName) {
@@ -187446,6 +187612,9 @@ function resolveEnvValue(value, resources, secrets, configs, servicePort, servic
187446
187612
  }
187447
187613
  switch (value.attribute) {
187448
187614
  case "url":
187615
+ if (pg.reshapeEnabled && pg.reshapeSearchPath) {
187616
+ return appendSearchPathToUrl(pg.url, pg.reshapeSearchPath);
187617
+ }
187449
187618
  return pg.url;
187450
187619
  case "host":
187451
187620
  return pg.host;
@@ -187627,14 +187796,14 @@ function startService(service, resources, secrets, configs, endpointPorts, servi
187627
187796
  ports: endpointPorts,
187628
187797
  process: child,
187629
187798
  async stop() {
187630
- return new Promise((resolve7) => {
187799
+ return new Promise((resolve9) => {
187631
187800
  if (child.killed || child.exitCode !== null) {
187632
- resolve7();
187801
+ resolve9();
187633
187802
  return;
187634
187803
  }
187635
187804
  child.once("exit", () => {
187636
187805
  clearTimeout(forceKillTimeout);
187637
- resolve7();
187806
+ resolve9();
187638
187807
  });
187639
187808
  child.kill("SIGTERM");
187640
187809
  const forceKillTimeout = setTimeout(() => {
@@ -187715,7 +187884,7 @@ var InstanceStateManager = class {
187715
187884
  }
187716
187885
  continue;
187717
187886
  }
187718
- await new Promise((resolve7) => setTimeout(resolve7, 100));
187887
+ await new Promise((resolve9) => setTimeout(resolve9, 100));
187719
187888
  } else {
187720
187889
  throw e;
187721
187890
  }
@@ -187998,7 +188167,7 @@ async function startHttpProxy(services, certificate, getState, instanceKey = "de
187998
188167
  handleRequest
187999
188168
  );
188000
188169
  httpsServer.on("upgrade", handleUpgrade);
188001
- return new Promise((resolve7, reject) => {
188170
+ return new Promise((resolve9, reject) => {
188002
188171
  let httpStarted = false;
188003
188172
  let httpsStarted = false;
188004
188173
  let failed = false;
@@ -188009,7 +188178,7 @@ async function startHttpProxy(services, certificate, getState, instanceKey = "de
188009
188178
  "proxy",
188010
188179
  `HTTP/HTTPS proxy started on ports ${HTTP_PORT}/${HTTPS_PORT}`
188011
188180
  );
188012
- resolve7({
188181
+ resolve9({
188013
188182
  httpPort: HTTP_PORT,
188014
188183
  httpsPort: HTTPS_PORT,
188015
188184
  updateServices,
@@ -188021,13 +188190,13 @@ async function startHttpProxy(services, certificate, getState, instanceKey = "de
188021
188190
  writeLog("proxy", "Certificate updated");
188022
188191
  },
188023
188192
  async stop() {
188024
- return new Promise((resolve8) => {
188193
+ return new Promise((resolve10) => {
188025
188194
  let closed = 0;
188026
188195
  const onClose = () => {
188027
188196
  closed++;
188028
188197
  if (closed === 2) {
188029
188198
  clearTimeout(forceCloseTimeout);
188030
- resolve8();
188199
+ resolve10();
188031
188200
  }
188032
188201
  };
188033
188202
  httpServer.close(onClose);
@@ -188035,7 +188204,7 @@ async function startHttpProxy(services, certificate, getState, instanceKey = "de
188035
188204
  const forceCloseTimeout = setTimeout(() => {
188036
188205
  httpServer.closeAllConnections?.();
188037
188206
  httpsServer.closeAllConnections?.();
188038
- resolve8();
188207
+ resolve10();
188039
188208
  }, 2e3);
188040
188209
  });
188041
188210
  }
@@ -188198,7 +188367,7 @@ function serveFileContent(res, filePath, contentType, statusCode = 200) {
188198
188367
  }
188199
188368
  }
188200
188369
  async function startAdminServer(getState) {
188201
- return new Promise((resolve7, reject) => {
188370
+ return new Promise((resolve9, reject) => {
188202
188371
  const server = http.createServer((req, res) => {
188203
188372
  const url = new URL(req.url || "/", "http://localhost");
188204
188373
  res.setHeader("Access-Control-Allow-Origin", "*");
@@ -188230,7 +188399,7 @@ async function startAdminServer(getState) {
188230
188399
  }
188231
188400
  const port = addr.port;
188232
188401
  writeLog("admin", `Admin API server started on port ${port}`);
188233
- resolve7({
188402
+ resolve9({
188234
188403
  port,
188235
188404
  stop: () => new Promise((res, rej) => {
188236
188405
  server.close((err) => err ? rej(err) : res());
@@ -188246,7 +188415,7 @@ import { spawn as spawn3 } from "child_process";
188246
188415
 
188247
188416
  // src/lib/secrets/parser.ts
188248
188417
  import { readFile as readFile2, writeFile as writeFile2, mkdir } from "fs/promises";
188249
- import { existsSync as existsSync13 } from "fs";
188418
+ import { existsSync as existsSync14 } from "fs";
188250
188419
  import * as path13 from "path";
188251
188420
  import * as crypto2 from "crypto";
188252
188421
  var GENERATED_SECRETS_FILE = ".specific/generated-secrets.json";
@@ -188264,7 +188433,7 @@ function generateRandomString(length = 64) {
188264
188433
  return result;
188265
188434
  }
188266
188435
  async function loadGeneratedSecrets() {
188267
- if (!existsSync13(GENERATED_SECRETS_FILE)) {
188436
+ if (!existsSync14(GENERATED_SECRETS_FILE)) {
188268
188437
  return /* @__PURE__ */ new Map();
188269
188438
  }
188270
188439
  const content = await readFile2(GENERATED_SECRETS_FILE, "utf-8");
@@ -188273,13 +188442,13 @@ async function loadGeneratedSecrets() {
188273
188442
  }
188274
188443
  async function saveGeneratedSecret(name, value) {
188275
188444
  let secrets = {};
188276
- if (existsSync13(GENERATED_SECRETS_FILE)) {
188445
+ if (existsSync14(GENERATED_SECRETS_FILE)) {
188277
188446
  const content = await readFile2(GENERATED_SECRETS_FILE, "utf-8");
188278
188447
  secrets = JSON.parse(content);
188279
188448
  }
188280
188449
  secrets[name] = value;
188281
188450
  const dir = path13.dirname(GENERATED_SECRETS_FILE);
188282
- if (!existsSync13(dir)) {
188451
+ if (!existsSync14(dir)) {
188283
188452
  await mkdir(dir, { recursive: true });
188284
188453
  }
188285
188454
  await writeFile2(GENERATED_SECRETS_FILE, JSON.stringify(secrets, null, 2) + "\n");
@@ -188391,33 +188560,33 @@ async function waitForTcpPort2(host, port, timeoutMs = 3e4) {
188391
188560
  throw new Error(`Electric port ${port} did not become available within timeout`);
188392
188561
  }
188393
188562
  function checkTcpPort2(host, port) {
188394
- return new Promise((resolve7) => {
188563
+ return new Promise((resolve9) => {
188395
188564
  const socket = new net2.Socket();
188396
188565
  socket.setTimeout(1e3);
188397
188566
  socket.on("connect", () => {
188398
188567
  socket.destroy();
188399
- resolve7(true);
188568
+ resolve9(true);
188400
188569
  });
188401
188570
  socket.on("timeout", () => {
188402
188571
  socket.destroy();
188403
- resolve7(false);
188572
+ resolve9(false);
188404
188573
  });
188405
188574
  socket.on("error", () => {
188406
188575
  socket.destroy();
188407
- resolve7(false);
188576
+ resolve9(false);
188408
188577
  });
188409
188578
  socket.connect(port, host);
188410
188579
  });
188411
188580
  }
188412
188581
  async function stopProcess2(proc) {
188413
- return new Promise((resolve7) => {
188582
+ return new Promise((resolve9) => {
188414
188583
  if (proc.killed || proc.exitCode !== null) {
188415
- resolve7();
188584
+ resolve9();
188416
188585
  return;
188417
188586
  }
188418
188587
  proc.once("exit", () => {
188419
188588
  clearTimeout(forceKillTimeout);
188420
- resolve7();
188589
+ resolve9();
188421
188590
  });
188422
188591
  proc.kill("SIGTERM");
188423
188592
  const forceKillTimeout = setTimeout(() => {
@@ -188428,7 +188597,7 @@ async function stopProcess2(proc) {
188428
188597
  });
188429
188598
  }
188430
188599
  function sleep3(ms) {
188431
- return new Promise((resolve7) => setTimeout(resolve7, ms));
188600
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
188432
188601
  }
188433
188602
 
188434
188603
  // src/lib/dev/drizzle-gateway-manager.ts
@@ -188512,33 +188681,33 @@ async function waitForTcpPort3(host, port, timeoutMs = 3e4) {
188512
188681
  );
188513
188682
  }
188514
188683
  function checkTcpPort3(host, port) {
188515
- return new Promise((resolve7) => {
188684
+ return new Promise((resolve9) => {
188516
188685
  const socket = new net3.Socket();
188517
188686
  socket.setTimeout(1e3);
188518
188687
  socket.on("connect", () => {
188519
188688
  socket.destroy();
188520
- resolve7(true);
188689
+ resolve9(true);
188521
188690
  });
188522
188691
  socket.on("timeout", () => {
188523
188692
  socket.destroy();
188524
- resolve7(false);
188693
+ resolve9(false);
188525
188694
  });
188526
188695
  socket.on("error", () => {
188527
188696
  socket.destroy();
188528
- resolve7(false);
188697
+ resolve9(false);
188529
188698
  });
188530
188699
  socket.connect(port, host);
188531
188700
  });
188532
188701
  }
188533
188702
  async function stopProcess3(proc) {
188534
- return new Promise((resolve7) => {
188703
+ return new Promise((resolve9) => {
188535
188704
  if (proc.killed || proc.exitCode !== null) {
188536
- resolve7();
188705
+ resolve9();
188537
188706
  return;
188538
188707
  }
188539
188708
  proc.once("exit", () => {
188540
188709
  clearTimeout(forceKillTimeout);
188541
- resolve7();
188710
+ resolve9();
188542
188711
  });
188543
188712
  proc.kill("SIGTERM");
188544
188713
  const forceKillTimeout = setTimeout(() => {
@@ -188549,7 +188718,7 @@ async function stopProcess3(proc) {
188549
188718
  });
188550
188719
  }
188551
188720
  function sleep4(ms) {
188552
- return new Promise((resolve7) => setTimeout(resolve7, ms));
188721
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
188553
188722
  }
188554
188723
 
188555
188724
  // src/lib/dev/sync-detector.ts
@@ -188569,6 +188738,288 @@ function detectSyncDatabases(config) {
188569
188738
  return needsSync;
188570
188739
  }
188571
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
+
188572
189023
  // src/lib/dev/resource-starter.ts
188573
189024
  function findRequiredResources(service) {
188574
189025
  const required = { postgres: [], redis: [], storage: [] };
@@ -188627,11 +189078,14 @@ async function startResources(options2) {
188627
189078
  const instance = await startPostgres(pg, port, dataDir, (progress) => {
188628
189079
  callbacks.onResourceProgress?.(pg.name, progress);
188629
189080
  });
189081
+ if (pg.reshape?.enabled) {
189082
+ instance.reshapeEnabled = true;
189083
+ }
188630
189084
  resources.set(pg.name, instance);
188631
189085
  startedResources.push(instance);
188632
189086
  callbacks.onResourceReady?.(pg.name, instance);
188633
189087
  log(`Postgres "${pg.name}" ready`);
188634
- await stateManager.registerDatabase(pg.name, {
189088
+ const dbState = {
188635
189089
  engine: "postgres",
188636
189090
  port: instance.port,
188637
189091
  host: instance.host,
@@ -188639,7 +189093,14 @@ async function startResources(options2) {
188639
189093
  password: instance.password,
188640
189094
  dbName: instance.dbName,
188641
189095
  url: instance.url
188642
- });
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);
188643
189104
  }
188644
189105
  for (const redis of redisConfigs) {
188645
189106
  if (signal?.cancelled) {
@@ -188741,24 +189202,24 @@ function watchConfigFile(configPath, debounceMs, onChange) {
188741
189202
  }
188742
189203
 
188743
189204
  // src/lib/dev/subdomain-generator.ts
188744
- import * as fs18 from "fs";
188745
- import * as path15 from "path";
189205
+ import * as fs19 from "fs";
189206
+ import * as path16 from "path";
188746
189207
  import { generateSlug } from "random-word-slugs";
188747
189208
  var StableSubdomainAllocator = class {
188748
189209
  tunnelsDir;
188749
189210
  tunnelsFilePath;
188750
189211
  baseSlug = null;
188751
189212
  constructor(projectRoot, key = "default") {
188752
- this.tunnelsDir = path15.join(projectRoot, ".specific", "keys", key);
188753
- 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");
188754
189215
  this.loadTunnels();
188755
189216
  }
188756
189217
  loadTunnels() {
188757
- if (!fs18.existsSync(this.tunnelsFilePath)) {
189218
+ if (!fs19.existsSync(this.tunnelsFilePath)) {
188758
189219
  return;
188759
189220
  }
188760
189221
  try {
188761
- const content = fs18.readFileSync(this.tunnelsFilePath, "utf-8");
189222
+ const content = fs19.readFileSync(this.tunnelsFilePath, "utf-8");
188762
189223
  const data = JSON.parse(content);
188763
189224
  if (data.version === 1 && data.baseSlug) {
188764
189225
  this.baseSlug = data.baseSlug;
@@ -188768,14 +189229,14 @@ var StableSubdomainAllocator = class {
188768
189229
  }
188769
189230
  }
188770
189231
  saveTunnels() {
188771
- if (!fs18.existsSync(this.tunnelsDir)) {
188772
- fs18.mkdirSync(this.tunnelsDir, { recursive: true });
189232
+ if (!fs19.existsSync(this.tunnelsDir)) {
189233
+ fs19.mkdirSync(this.tunnelsDir, { recursive: true });
188773
189234
  }
188774
189235
  const data = {
188775
189236
  version: 1,
188776
189237
  baseSlug: this.baseSlug
188777
189238
  };
188778
- fs18.writeFileSync(this.tunnelsFilePath, JSON.stringify(data, null, 2));
189239
+ fs19.writeFileSync(this.tunnelsFilePath, JSON.stringify(data, null, 2));
188779
189240
  }
188780
189241
  generateBaseSlug() {
188781
189242
  return generateSlug(2, {
@@ -188838,8 +189299,8 @@ async function startTunnel(serviceName, endpointName, port, subdomain, callbacks
188838
189299
  }
188839
189300
 
188840
189301
  // src/lib/dev/proxy-registry.ts
188841
- import * as fs19 from "fs";
188842
- import * as path16 from "path";
189302
+ import * as fs20 from "fs";
189303
+ import * as path17 from "path";
188843
189304
  import * as os7 from "os";
188844
189305
  import * as net4 from "net";
188845
189306
  var ProxyRegistryManager = class {
@@ -188850,14 +189311,14 @@ var ProxyRegistryManager = class {
188850
189311
  isOwner = false;
188851
189312
  registryWatcher = null;
188852
189313
  constructor() {
188853
- this.proxyDir = path16.join(os7.homedir(), ".specific", "proxy");
188854
- this.ownerPath = path16.join(this.proxyDir, "owner.json");
188855
- this.registryPath = path16.join(this.proxyDir, "registry.json");
188856
- 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");
188857
189318
  }
188858
189319
  ensureProxyDir() {
188859
- if (!fs19.existsSync(this.proxyDir)) {
188860
- fs19.mkdirSync(this.proxyDir, { recursive: true });
189320
+ if (!fs20.existsSync(this.proxyDir)) {
189321
+ fs20.mkdirSync(this.proxyDir, { recursive: true });
188861
189322
  }
188862
189323
  }
188863
189324
  isProcessRunning(pid) {
@@ -188874,7 +189335,7 @@ var ProxyRegistryManager = class {
188874
189335
  * This catches cases where the owner process is alive but the proxy has crashed.
188875
189336
  */
188876
189337
  isProxyListening(port, timeoutMs = 1e3) {
188877
- return new Promise((resolve7) => {
189338
+ return new Promise((resolve9) => {
188878
189339
  const socket = new net4.Socket();
188879
189340
  let resolved = false;
188880
189341
  const cleanup = () => {
@@ -188886,15 +189347,15 @@ var ProxyRegistryManager = class {
188886
189347
  socket.setTimeout(timeoutMs);
188887
189348
  socket.on("connect", () => {
188888
189349
  cleanup();
188889
- resolve7(true);
189350
+ resolve9(true);
188890
189351
  });
188891
189352
  socket.on("timeout", () => {
188892
189353
  cleanup();
188893
- resolve7(false);
189354
+ resolve9(false);
188894
189355
  });
188895
189356
  socket.on("error", () => {
188896
189357
  cleanup();
188897
- resolve7(false);
189358
+ resolve9(false);
188898
189359
  });
188899
189360
  socket.connect(port, "127.0.0.1");
188900
189361
  });
@@ -188914,15 +189375,15 @@ var ProxyRegistryManager = class {
188914
189375
  const startTime = Date.now();
188915
189376
  while (Date.now() - startTime < timeoutMs) {
188916
189377
  try {
188917
- const fd = fs19.openSync(
189378
+ const fd = fs20.openSync(
188918
189379
  this.lockPath,
188919
- 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
188920
189381
  );
188921
- fs19.writeSync(fd, String(process.pid));
188922
- fs19.closeSync(fd);
189382
+ fs20.writeSync(fd, String(process.pid));
189383
+ fs20.closeSync(fd);
188923
189384
  return () => {
188924
189385
  try {
188925
- fs19.unlinkSync(this.lockPath);
189386
+ fs20.unlinkSync(this.lockPath);
188926
189387
  } catch {
188927
189388
  }
188928
189389
  };
@@ -188931,21 +189392,21 @@ var ProxyRegistryManager = class {
188931
189392
  if (err.code === "EEXIST") {
188932
189393
  try {
188933
189394
  const lockPid = parseInt(
188934
- fs19.readFileSync(this.lockPath, "utf-8").trim(),
189395
+ fs20.readFileSync(this.lockPath, "utf-8").trim(),
188935
189396
  10
188936
189397
  );
188937
189398
  if (!this.isProcessRunning(lockPid)) {
188938
- fs19.unlinkSync(this.lockPath);
189399
+ fs20.unlinkSync(this.lockPath);
188939
189400
  continue;
188940
189401
  }
188941
189402
  } catch {
188942
189403
  try {
188943
- fs19.unlinkSync(this.lockPath);
189404
+ fs20.unlinkSync(this.lockPath);
188944
189405
  } catch {
188945
189406
  }
188946
189407
  continue;
188947
189408
  }
188948
- await new Promise((resolve7) => setTimeout(resolve7, 100));
189409
+ await new Promise((resolve9) => setTimeout(resolve9, 100));
188949
189410
  } else {
188950
189411
  throw e;
188951
189412
  }
@@ -188960,8 +189421,8 @@ var ProxyRegistryManager = class {
188960
189421
  async claimProxyOwnership(key) {
188961
189422
  const releaseLock = await this.acquireLock();
188962
189423
  try {
188963
- if (fs19.existsSync(this.ownerPath)) {
188964
- const content = fs19.readFileSync(this.ownerPath, "utf-8");
189424
+ if (fs20.existsSync(this.ownerPath)) {
189425
+ const content = fs20.readFileSync(this.ownerPath, "utf-8");
188965
189426
  const ownerFile2 = JSON.parse(content);
188966
189427
  if (await this.isProxyOwnerHealthy(ownerFile2.owner.pid)) {
188967
189428
  return false;
@@ -188991,11 +189452,11 @@ var ProxyRegistryManager = class {
188991
189452
  }
188992
189453
  const releaseLock = await this.acquireLock();
188993
189454
  try {
188994
- if (fs19.existsSync(this.ownerPath)) {
188995
- const content = fs19.readFileSync(this.ownerPath, "utf-8");
189455
+ if (fs20.existsSync(this.ownerPath)) {
189456
+ const content = fs20.readFileSync(this.ownerPath, "utf-8");
188996
189457
  const ownerFile = JSON.parse(content);
188997
189458
  if (ownerFile.owner.pid === process.pid) {
188998
- fs19.unlinkSync(this.ownerPath);
189459
+ fs20.unlinkSync(this.ownerPath);
188999
189460
  }
189000
189461
  }
189001
189462
  this.isOwner = false;
@@ -189007,12 +189468,12 @@ var ProxyRegistryManager = class {
189007
189468
  * Get the current proxy owner.
189008
189469
  */
189009
189470
  async getProxyOwner() {
189010
- if (!fs19.existsSync(this.ownerPath)) {
189471
+ if (!fs20.existsSync(this.ownerPath)) {
189011
189472
  return null;
189012
189473
  }
189013
189474
  const releaseLock = await this.acquireLock();
189014
189475
  try {
189015
- const content = fs19.readFileSync(this.ownerPath, "utf-8");
189476
+ const content = fs20.readFileSync(this.ownerPath, "utf-8");
189016
189477
  const ownerFile = JSON.parse(content);
189017
189478
  if (!await this.isProxyOwnerHealthy(ownerFile.owner.pid)) {
189018
189479
  return null;
@@ -189106,7 +189567,7 @@ var ProxyRegistryManager = class {
189106
189567
  */
189107
189568
  watchRegistry(onChange) {
189108
189569
  this.ensureProxyDir();
189109
- if (!fs19.existsSync(this.registryPath)) {
189570
+ if (!fs20.existsSync(this.registryPath)) {
189110
189571
  const emptyRegistry = {
189111
189572
  version: 1,
189112
189573
  keys: {},
@@ -189150,13 +189611,13 @@ var ProxyRegistryManager = class {
189150
189611
  async attemptElection(key) {
189151
189612
  const releaseLock = await this.acquireLock();
189152
189613
  try {
189153
- if (fs19.existsSync(this.ownerPath)) {
189154
- const content = fs19.readFileSync(this.ownerPath, "utf-8");
189614
+ if (fs20.existsSync(this.ownerPath)) {
189615
+ const content = fs20.readFileSync(this.ownerPath, "utf-8");
189155
189616
  const ownerFile2 = JSON.parse(content);
189156
189617
  if (await this.isProxyOwnerHealthy(ownerFile2.owner.pid)) {
189157
189618
  return false;
189158
189619
  }
189159
- fs19.unlinkSync(this.ownerPath);
189620
+ fs20.unlinkSync(this.ownerPath);
189160
189621
  }
189161
189622
  const ownerFile = {
189162
189623
  version: 1,
@@ -189174,7 +189635,7 @@ var ProxyRegistryManager = class {
189174
189635
  }
189175
189636
  }
189176
189637
  readRegistry() {
189177
- if (!fs19.existsSync(this.registryPath)) {
189638
+ if (!fs20.existsSync(this.registryPath)) {
189178
189639
  return {
189179
189640
  version: 1,
189180
189641
  keys: {},
@@ -189182,7 +189643,7 @@ var ProxyRegistryManager = class {
189182
189643
  };
189183
189644
  }
189184
189645
  try {
189185
- const content = fs19.readFileSync(this.registryPath, "utf-8");
189646
+ const content = fs20.readFileSync(this.registryPath, "utf-8");
189186
189647
  return JSON.parse(content);
189187
189648
  } catch {
189188
189649
  return {
@@ -189195,8 +189656,8 @@ var ProxyRegistryManager = class {
189195
189656
  writeFileAtomic(filePath, data) {
189196
189657
  this.ensureProxyDir();
189197
189658
  const tmpPath = filePath + ".tmp";
189198
- fs19.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
189199
- fs19.renameSync(tmpPath, filePath);
189659
+ fs20.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
189660
+ fs20.renameSync(tmpPath, filePath);
189200
189661
  }
189201
189662
  };
189202
189663
 
@@ -189341,6 +189802,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189341
189802
  const proxyRegistryRef = useRef(null);
189342
189803
  const electricInstancesRef = useRef([]);
189343
189804
  const drizzleGatewayRef = useRef(null);
189805
+ const reshapeWatchersRef = useRef([]);
189344
189806
  const registryWatcherCleanupRef = useRef(null);
189345
189807
  const electionIntervalRef = useRef(null);
189346
189808
  const tunnelsRef = useRef([]);
@@ -189348,6 +189810,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189348
189810
  const adminServerRef = useRef(null);
189349
189811
  const servicesRef = useRef([]);
189350
189812
  const resourcesRef = useRef(/* @__PURE__ */ new Map());
189813
+ const restartServicesRef = useRef(null);
189351
189814
  const [reloadTrigger, setReloadTrigger] = useState5(0);
189352
189815
  const [readyToStart, setReadyToStart] = useState5(() => caFilesExist());
189353
189816
  const shutdown2 = async () => {
@@ -189374,6 +189837,8 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189374
189837
  ...servicesRef.current.map((service) => service.stop()),
189375
189838
  // Stop all Electric instances
189376
189839
  ...electricInstancesRef.current.map((electric) => electric.stop()),
189840
+ // Stop all Reshape watchers
189841
+ ...reshapeWatchersRef.current.map((watcher) => watcher.close()),
189377
189842
  // Stop Drizzle Gateway
189378
189843
  drizzleGatewayRef.current?.stop(),
189379
189844
  // Stop all resources
@@ -189381,6 +189846,8 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189381
189846
  // Stop all tunnels
189382
189847
  ...tunnelsRef.current.map((tunnel) => tunnel.stop())
189383
189848
  ]);
189849
+ reshapeWatchersRef.current = [];
189850
+ restartServicesRef.current = null;
189384
189851
  if (proxyRegistryRef.current) {
189385
189852
  await proxyRegistryRef.current.unregisterServices(instanceKey);
189386
189853
  await proxyRegistryRef.current.releaseProxyOwnership();
@@ -189413,6 +189880,8 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189413
189880
  ...servicesRef.current.map((service) => service.stop()),
189414
189881
  // Stop all Electric instances
189415
189882
  ...electricInstancesRef.current.map((electric) => electric.stop()),
189883
+ // Stop all Reshape watchers
189884
+ ...reshapeWatchersRef.current.map((watcher) => watcher.close()),
189416
189885
  // Stop Drizzle Gateway
189417
189886
  drizzleGatewayRef.current?.stop(),
189418
189887
  // Stop all resources
@@ -189421,9 +189890,11 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189421
189890
  ...tunnelsRef.current.map((tunnel) => tunnel.stop())
189422
189891
  ]);
189423
189892
  if (tunnelsRef.current.length > 0) {
189424
- await new Promise((resolve7) => setTimeout(resolve7, 1500));
189893
+ await new Promise((resolve9) => setTimeout(resolve9, 1500));
189425
189894
  }
189426
189895
  electricInstancesRef.current = [];
189896
+ reshapeWatchersRef.current = [];
189897
+ restartServicesRef.current = null;
189427
189898
  drizzleGatewayRef.current = null;
189428
189899
  proxyRef.current = null;
189429
189900
  adminServerRef.current = null;
@@ -189488,10 +189959,10 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189488
189959
  }, [state.status]);
189489
189960
  useEffect3(() => {
189490
189961
  if (state.status !== "running") return;
189491
- const configPath = path17.join(process.cwd(), "specific.hcl");
189962
+ const configPath = path18.join(process.cwd(), "specific.hcl");
189492
189963
  const watcher = watchConfigFile(configPath, 1e3, () => {
189493
189964
  try {
189494
- const hcl = fs20.readFileSync(configPath, "utf-8");
189965
+ const hcl = fs21.readFileSync(configPath, "utf-8");
189495
189966
  parseConfig(hcl).then(() => {
189496
189967
  triggerReload();
189497
189968
  }).catch((err) => {
@@ -189616,8 +190087,8 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189616
190087
  }));
189617
190088
  return;
189618
190089
  }
189619
- const configPath = path17.join(process.cwd(), "specific.hcl");
189620
- if (!fs20.existsSync(configPath)) {
190090
+ const configPath = path18.join(process.cwd(), "specific.hcl");
190091
+ if (!fs21.existsSync(configPath)) {
189621
190092
  writeLog("system", "Waiting for specific.hcl to appear");
189622
190093
  setState((s) => ({
189623
190094
  ...s,
@@ -189636,7 +190107,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189636
190107
  }
189637
190108
  let config2;
189638
190109
  try {
189639
- const hcl = fs20.readFileSync(configPath, "utf-8");
190110
+ const hcl = fs21.readFileSync(configPath, "utf-8");
189640
190111
  config2 = await parseConfig(hcl);
189641
190112
  } catch (err) {
189642
190113
  setState((s) => ({
@@ -189742,7 +190213,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189742
190213
  const drizzleGateway = await startDrizzleGateway(
189743
190214
  postgresResources,
189744
190215
  drizzlePort,
189745
- path17.join(process.cwd(), ".specific", "keys", instanceKey)
190216
+ path18.join(process.cwd(), ".specific", "keys", instanceKey)
189746
190217
  );
189747
190218
  startedDrizzleGateway = drizzleGateway;
189748
190219
  drizzleGatewayRef.current = drizzleGateway;
@@ -189755,6 +190226,59 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189755
190226
  }
189756
190227
  }
189757
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;
189758
190282
  const secrets = await prepareSecrets(config2.secrets, true);
189759
190283
  const configs = await prepareConfigs(config2.configs, void 0, true);
189760
190284
  const usedSecrets = findUsedSecrets(config2.services, true);
@@ -189927,6 +190451,40 @@ Add them to the config block in specific.local`);
189927
190451
  }
189928
190452
  }
189929
190453
  if (cancelled) return;
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
+ );
190479
+ }
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();
190487
+ }
189930
190488
  if (tunnelEnabled) {
189931
190489
  writeLog("system", "Tunnel mode enabled, starting tunnels for public services");
189932
190490
  if (exposedServices.length === 0) {
@@ -190144,6 +190702,12 @@ Add them to the config block in specific.local`);
190144
190702
  electric.stop().catch(() => {
190145
190703
  });
190146
190704
  }
190705
+ for (const watcher of reshapeWatchersRef.current) {
190706
+ watcher.close().catch(() => {
190707
+ });
190708
+ }
190709
+ reshapeWatchersRef.current = [];
190710
+ restartServicesRef.current = null;
190147
190711
  if (startedDrizzleGateway) {
190148
190712
  startedDrizzleGateway.stop().catch(() => {
190149
190713
  });
@@ -190366,7 +190930,7 @@ function devCommand(instanceKey, tunnelEnabled = false) {
190366
190930
 
190367
190931
  // src/lib/dev/git-worktree.ts
190368
190932
  import { execSync as execSync2 } from "child_process";
190369
- import * as path18 from "path";
190933
+ import * as path19 from "path";
190370
190934
  function isInWorktree() {
190371
190935
  try {
190372
190936
  const commonDir = execSync2("git rev-parse --git-common-dir", {
@@ -190377,8 +190941,8 @@ function isInWorktree() {
190377
190941
  encoding: "utf-8",
190378
190942
  stdio: ["pipe", "pipe", "pipe"]
190379
190943
  }).trim();
190380
- const resolvedCommonDir = path18.resolve(commonDir);
190381
- const resolvedGitDir = path18.resolve(gitDir);
190944
+ const resolvedCommonDir = path19.resolve(commonDir);
190945
+ const resolvedGitDir = path19.resolve(gitDir);
190382
190946
  return resolvedCommonDir !== resolvedGitDir;
190383
190947
  } catch {
190384
190948
  return false;
@@ -190393,7 +190957,7 @@ function getWorktreeName() {
190393
190957
  encoding: "utf-8",
190394
190958
  stdio: ["pipe", "pipe", "pipe"]
190395
190959
  }).trim();
190396
- return path18.basename(gitDir);
190960
+ return path19.basename(gitDir);
190397
190961
  } catch {
190398
190962
  return null;
190399
190963
  }
@@ -190408,35 +190972,35 @@ init_open();
190408
190972
  import React7, { useState as useState6, useEffect as useEffect4, useCallback } from "react";
190409
190973
  import { render as render5, Text as Text7, Box as Box7, useApp as useApp3, useInput as useInput5 } from "ink";
190410
190974
  import Spinner5 from "ink-spinner";
190411
- import * as fs22 from "fs";
190412
- import * as path20 from "path";
190975
+ import * as fs23 from "fs";
190976
+ import * as path21 from "path";
190413
190977
 
190414
190978
  // src/lib/deploy/build-tester.ts
190415
190979
  import { spawn as spawn5 } from "child_process";
190416
- import { existsSync as existsSync18 } from "fs";
190417
- import { join as join19 } from "path";
190980
+ import { existsSync as existsSync20 } from "fs";
190981
+ import { join as join21 } from "path";
190418
190982
  function getDependencyInstallCommand(build, projectDir) {
190419
190983
  switch (build.base) {
190420
190984
  case "node":
190421
- if (existsSync18(join19(projectDir, "pnpm-lock.yaml"))) {
190985
+ if (existsSync20(join21(projectDir, "pnpm-lock.yaml"))) {
190422
190986
  return "pnpm install --frozen-lockfile";
190423
- } else if (existsSync18(join19(projectDir, "yarn.lock"))) {
190987
+ } else if (existsSync20(join21(projectDir, "yarn.lock"))) {
190424
190988
  return "yarn install --frozen-lockfile";
190425
- } else if (existsSync18(join19(projectDir, "package-lock.json"))) {
190989
+ } else if (existsSync20(join21(projectDir, "package-lock.json"))) {
190426
190990
  return "npm ci";
190427
190991
  } else {
190428
190992
  return "npm install";
190429
190993
  }
190430
190994
  case "python":
190431
- if (existsSync18(join19(projectDir, "poetry.lock"))) {
190995
+ if (existsSync20(join21(projectDir, "poetry.lock"))) {
190432
190996
  return "poetry install --no-interaction";
190433
- } else if (existsSync18(join19(projectDir, "Pipfile.lock"))) {
190997
+ } else if (existsSync20(join21(projectDir, "Pipfile.lock"))) {
190434
190998
  return "pipenv install --deploy";
190435
- } else if (existsSync18(join19(projectDir, "Pipfile"))) {
190999
+ } else if (existsSync20(join21(projectDir, "Pipfile"))) {
190436
191000
  return "pipenv install";
190437
- } else if (existsSync18(join19(projectDir, "pyproject.toml"))) {
191001
+ } else if (existsSync20(join21(projectDir, "pyproject.toml"))) {
190438
191002
  return "pip install .";
190439
- } else if (existsSync18(join19(projectDir, "requirements.txt"))) {
191003
+ } else if (existsSync20(join21(projectDir, "requirements.txt"))) {
190440
191004
  return "pip install -r requirements.txt";
190441
191005
  }
190442
191006
  return null;
@@ -190450,7 +191014,7 @@ function getDependencyInstallCommand(build, projectDir) {
190450
191014
  }
190451
191015
  }
190452
191016
  function runCommand2(command, projectDir, buildName) {
190453
- return new Promise((resolve7) => {
191017
+ return new Promise((resolve9) => {
190454
191018
  const stdout = [];
190455
191019
  const stderr = [];
190456
191020
  writeLog("build-test", `[${buildName}] Running: ${command}`);
@@ -190480,7 +191044,7 @@ function runCommand2(command, projectDir, buildName) {
190480
191044
  });
190481
191045
  child.on("error", (err) => {
190482
191046
  writeLog("build-test:error", `[${buildName}] Failed to start: ${err.message}`);
190483
- resolve7({
191047
+ resolve9({
190484
191048
  success: false,
190485
191049
  output: `Failed to start command: ${err.message}`
190486
191050
  });
@@ -190489,10 +191053,10 @@ function runCommand2(command, projectDir, buildName) {
190489
191053
  const output = [...stdout, ...stderr].join("");
190490
191054
  if (code === 0) {
190491
191055
  writeLog("build-test", `[${buildName}] Command succeeded (exit code 0)`);
190492
- resolve7({ success: true, output });
191056
+ resolve9({ success: true, output });
190493
191057
  } else {
190494
191058
  writeLog("build-test:error", `[${buildName}] Command failed with exit code ${code}`);
190495
- resolve7({
191059
+ resolve9({
190496
191060
  success: false,
190497
191061
  output: output || `Exit code: ${code}`
190498
191062
  });
@@ -190582,8 +191146,8 @@ async function testAllBuilds(builds, projectDir) {
190582
191146
 
190583
191147
  // src/lib/tarball/create.ts
190584
191148
  import { execSync as execSync3 } from "child_process";
190585
- import * as fs21 from "fs";
190586
- import * as path19 from "path";
191149
+ import * as fs22 from "fs";
191150
+ import * as path20 from "path";
190587
191151
  import { createTarPacker, createEntryItemGenerator } from "tar-vern";
190588
191152
  function isInsideGitRepository(dir) {
190589
191153
  try {
@@ -190640,10 +191204,10 @@ var EXCLUDED_DIRS = [
190640
191204
  ];
190641
191205
  async function collectPaths(baseDir, currentDir, exclude) {
190642
191206
  const results = [];
190643
- const entries = await fs21.promises.readdir(currentDir, { withFileTypes: true });
191207
+ const entries = await fs22.promises.readdir(currentDir, { withFileTypes: true });
190644
191208
  for (const entry of entries) {
190645
- const fullPath = path19.join(currentDir, entry.name);
190646
- const relativePath = path19.relative(baseDir, fullPath);
191209
+ const fullPath = path20.join(currentDir, entry.name);
191210
+ const relativePath = path20.relative(baseDir, fullPath);
190647
191211
  if (entry.isDirectory()) {
190648
191212
  if (!exclude.includes(entry.name)) {
190649
191213
  results.push(relativePath);
@@ -190658,8 +191222,8 @@ async function collectPaths(baseDir, currentDir, exclude) {
190658
191222
  }
190659
191223
  async function createTarArchive(projectDir) {
190660
191224
  writeLog("tarball", "Creating tarball using tar-vern (non-git project)");
190661
- const configPath = path19.join(projectDir, "specific.hcl");
190662
- if (!fs21.existsSync(configPath)) {
191225
+ const configPath = path20.join(projectDir, "specific.hcl");
191226
+ if (!fs22.existsSync(configPath)) {
190663
191227
  throw new Error("specific.hcl not found in project directory");
190664
191228
  }
190665
191229
  const relativePaths = await collectPaths(projectDir, projectDir, EXCLUDED_DIRS);
@@ -190676,8 +191240,8 @@ async function createTarArchive(projectDir) {
190676
191240
  }
190677
191241
  function findWidestContext(projectDir, contexts) {
190678
191242
  if (contexts.length === 0) return ".";
190679
- const absolute = contexts.map((c) => path19.resolve(projectDir, c));
190680
- 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));
190681
191245
  const firstSegments = segments[0];
190682
191246
  if (!firstSegments) return ".";
190683
191247
  const minLen = Math.min(...segments.map((s) => s.length));
@@ -190691,12 +191255,12 @@ function findWidestContext(projectDir, contexts) {
190691
191255
  }
190692
191256
  }
190693
191257
  const ancestorSegments = firstSegments.slice(0, commonLength);
190694
- const ancestor = path19.sep + ancestorSegments.join(path19.sep);
190695
- return path19.relative(projectDir, ancestor) || ".";
191258
+ const ancestor = path20.sep + ancestorSegments.join(path20.sep);
191259
+ return path20.relative(projectDir, ancestor) || ".";
190696
191260
  }
190697
191261
  async function createProjectTarball(projectDir, context = ".") {
190698
- const contextDir = path19.resolve(projectDir, context);
190699
- const appPath = path19.relative(contextDir, projectDir) || ".";
191262
+ const contextDir = path20.resolve(projectDir, context);
191263
+ const appPath = path20.relative(contextDir, projectDir) || ".";
190700
191264
  writeLog("tarball", `Context: ${contextDir}, appPath: ${appPath}`);
190701
191265
  let tarball;
190702
191266
  if (isInsideGitRepository(contextDir)) {
@@ -191583,14 +192147,14 @@ ${errorMsg}`
191583
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))))));
191584
192148
  }
191585
192149
  async function deployCommand(environment, options2) {
191586
- const configPath = path20.join(process.cwd(), "specific.hcl");
191587
- if (!fs22.existsSync(configPath)) {
192150
+ const configPath = path21.join(process.cwd(), "specific.hcl");
192151
+ if (!fs23.existsSync(configPath)) {
191588
192152
  console.error("Error: No specific.hcl found in current directory");
191589
192153
  process.exit(1);
191590
192154
  }
191591
192155
  let config;
191592
192156
  try {
191593
- const hcl = fs22.readFileSync(configPath, "utf-8");
192157
+ const hcl = fs23.readFileSync(configPath, "utf-8");
191594
192158
  config = await parseConfig(hcl);
191595
192159
  } catch (err) {
191596
192160
  console.error(
@@ -191614,8 +192178,8 @@ async function deployCommand(environment, options2) {
191614
192178
 
191615
192179
  // src/commands/exec.tsx
191616
192180
  import { spawn as spawn6 } from "child_process";
191617
- import * as fs23 from "fs";
191618
- import * as path21 from "path";
192181
+ import * as fs24 from "fs";
192182
+ import * as path22 from "path";
191619
192183
  async function execCommand(serviceName, command, instanceKey = "default") {
191620
192184
  if (command.length === 0) {
191621
192185
  console.error(
@@ -191643,14 +192207,14 @@ async function execCommand(serviceName, command, instanceKey = "default") {
191643
192207
  }
191644
192208
  }
191645
192209
  };
191646
- const configPath = path21.join(process.cwd(), "specific.hcl");
191647
- if (!fs23.existsSync(configPath)) {
192210
+ const configPath = path22.join(process.cwd(), "specific.hcl");
192211
+ if (!fs24.existsSync(configPath)) {
191648
192212
  console.error("Error: No specific.hcl found in current directory");
191649
192213
  process.exit(1);
191650
192214
  }
191651
192215
  let config;
191652
192216
  try {
191653
- const hcl = fs23.readFileSync(configPath, "utf-8");
192217
+ const hcl = fs24.readFileSync(configPath, "utf-8");
191654
192218
  config = await parseConfig(hcl);
191655
192219
  } catch (err) {
191656
192220
  console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
@@ -191697,6 +192261,8 @@ async function execCommand(serviceName, command, instanceKey = "default") {
191697
192261
  bucket: dbState.engine === "object_store" ? name : void 0,
191698
192262
  syncUrl: dbState.syncUrl,
191699
192263
  syncSecret: dbState.syncSecret,
192264
+ reshapeEnabled: dbState.reshapeEnabled,
192265
+ reshapeSearchPath: dbState.reshapeSearchPath,
191700
192266
  async stop() {
191701
192267
  }
191702
192268
  });
@@ -191787,8 +192353,8 @@ async function execCommand(serviceName, command, instanceKey = "default") {
191787
192353
 
191788
192354
  // src/commands/psql.tsx
191789
192355
  import { spawn as spawn7 } from "child_process";
191790
- import * as fs24 from "fs";
191791
- import * as path22 from "path";
192356
+ import * as fs25 from "fs";
192357
+ import * as path23 from "path";
191792
192358
  async function psqlCommand(databaseName, instanceKey = "default", extraArgs = []) {
191793
192359
  let startedResources = [];
191794
192360
  let ownsInstances = false;
@@ -191805,14 +192371,14 @@ async function psqlCommand(databaseName, instanceKey = "default", extraArgs = []
191805
192371
  }
191806
192372
  }
191807
192373
  };
191808
- const configPath = path22.join(process.cwd(), "specific.hcl");
191809
- if (!fs24.existsSync(configPath)) {
192374
+ const configPath = path23.join(process.cwd(), "specific.hcl");
192375
+ if (!fs25.existsSync(configPath)) {
191810
192376
  console.error("Error: No specific.hcl found in current directory");
191811
192377
  process.exit(1);
191812
192378
  }
191813
192379
  let config;
191814
192380
  try {
191815
- const hcl = fs24.readFileSync(configPath, "utf-8");
192381
+ const hcl = fs25.readFileSync(configPath, "utf-8");
191816
192382
  config = await parseConfig(hcl);
191817
192383
  } catch (err) {
191818
192384
  console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
@@ -191932,25 +192498,227 @@ async function psqlCommand(databaseName, instanceKey = "default", extraArgs = []
191932
192498
  });
191933
192499
  }
191934
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();
192699
+ process.exit(1);
192700
+ });
192701
+ }
192702
+
191935
192703
  // src/commands/clean.tsx
191936
192704
  import React8, { useState as useState7, useEffect as useEffect5 } from "react";
191937
192705
  import { render as render6, Text as Text8, Box as Box8 } from "ink";
191938
192706
  import Spinner6 from "ink-spinner";
191939
- import * as fs25 from "fs";
191940
- import * as path23 from "path";
192707
+ import * as fs27 from "fs";
192708
+ import * as path25 from "path";
191941
192709
  function CleanUI({ instanceKey }) {
191942
192710
  const [state, setState] = useState7({ status: "checking" });
191943
192711
  useEffect5(() => {
191944
192712
  async function clean() {
191945
192713
  const projectRoot = process.cwd();
191946
- const specificDir = path23.join(projectRoot, ".specific");
191947
- if (!fs25.existsSync(specificDir)) {
192714
+ const specificDir = path25.join(projectRoot, ".specific");
192715
+ if (!fs27.existsSync(specificDir)) {
191948
192716
  setState({ status: "nothing" });
191949
192717
  return;
191950
192718
  }
191951
192719
  if (instanceKey) {
191952
- const keyDir = path23.join(specificDir, "keys", instanceKey);
191953
- if (!fs25.existsSync(keyDir)) {
192720
+ const keyDir = path25.join(specificDir, "keys", instanceKey);
192721
+ if (!fs27.existsSync(keyDir)) {
191954
192722
  setState({ status: "nothing" });
191955
192723
  return;
191956
192724
  }
@@ -191966,7 +192734,7 @@ function CleanUI({ instanceKey }) {
191966
192734
  await stateManager.cleanStaleState();
191967
192735
  setState({ status: "cleaning" });
191968
192736
  try {
191969
- fs25.rmSync(keyDir, { recursive: true, force: true });
192737
+ fs27.rmSync(keyDir, { recursive: true, force: true });
191970
192738
  setState({ status: "success" });
191971
192739
  } catch (err) {
191972
192740
  setState({
@@ -191975,10 +192743,10 @@ function CleanUI({ instanceKey }) {
191975
192743
  });
191976
192744
  }
191977
192745
  } else {
191978
- const keysDir = path23.join(specificDir, "keys");
191979
- if (fs25.existsSync(keysDir)) {
191980
- const keys = fs25.readdirSync(keysDir).filter(
191981
- (f) => fs25.statSync(path23.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()
191982
192750
  );
191983
192751
  for (const key of keys) {
191984
192752
  const stateManager2 = new InstanceStateManager(projectRoot, key);
@@ -192003,7 +192771,7 @@ function CleanUI({ instanceKey }) {
192003
192771
  }
192004
192772
  setState({ status: "cleaning" });
192005
192773
  try {
192006
- fs25.rmSync(specificDir, { recursive: true, force: true });
192774
+ fs27.rmSync(specificDir, { recursive: true, force: true });
192007
192775
  setState({ status: "success" });
192008
192776
  } catch (err) {
192009
192777
  setState({
@@ -192086,11 +192854,87 @@ function logoutCommand() {
192086
192854
  render7(/* @__PURE__ */ React9.createElement(LogoutUI, null));
192087
192855
  }
192088
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
+
192089
192933
  // src/cli.tsx
192090
192934
  var program = new Command();
192091
192935
  var env = "production";
192092
192936
  var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
192093
- program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.49").enablePositionalOptions();
192937
+ program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.50").enablePositionalOptions();
192094
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));
192095
192939
  program.command("docs [topic]").description("Fetch LLM-optimized documentation").action(docsCommand);
192096
192940
  program.command("check").description("Validate specific.hcl configuration").action(checkCommand);
@@ -192111,9 +192955,14 @@ program.command("psql [database] [args...]").description("Connect to a Postgres
192111
192955
  const key = options2.key ?? getDefaultKey();
192112
192956
  psqlCommand(database, key, filteredArgs);
192113
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) => {
192959
+ const key = options2.key ?? getDefaultKey();
192960
+ reshapeCommand(action, database, key);
192961
+ });
192114
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) => {
192115
192963
  cleanCommand(options2.key);
192116
192964
  });
192965
+ program.command("beta").description("Manage beta feature flags").action(betaCommand);
192117
192966
  program.command("login").description("Log in to Specific").action(loginCommand);
192118
192967
  program.command("logout").description("Log out of Specific").action(logoutCommand);
192119
192968
  var commandName = process.argv[2] || "help";