@specific.dev/cli 0.1.49 → 0.1.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/admin/404/index.html +1 -1
  2. package/dist/admin/404.html +1 -1
  3. package/dist/admin/__next.__PAGE__.txt +1 -1
  4. package/dist/admin/__next._full.txt +1 -1
  5. package/dist/admin/__next._head.txt +1 -1
  6. package/dist/admin/__next._index.txt +1 -1
  7. package/dist/admin/__next._tree.txt +1 -1
  8. package/dist/admin/_not-found/__next._full.txt +1 -1
  9. package/dist/admin/_not-found/__next._head.txt +1 -1
  10. package/dist/admin/_not-found/__next._index.txt +1 -1
  11. package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +1 -1
  12. package/dist/admin/_not-found/__next._not-found.txt +1 -1
  13. package/dist/admin/_not-found/__next._tree.txt +1 -1
  14. package/dist/admin/_not-found/index.html +1 -1
  15. package/dist/admin/_not-found/index.txt +1 -1
  16. package/dist/admin/databases/__next._full.txt +1 -1
  17. package/dist/admin/databases/__next._head.txt +1 -1
  18. package/dist/admin/databases/__next._index.txt +1 -1
  19. package/dist/admin/databases/__next._tree.txt +1 -1
  20. package/dist/admin/databases/__next.databases.__PAGE__.txt +1 -1
  21. package/dist/admin/databases/__next.databases.txt +1 -1
  22. package/dist/admin/databases/index.html +1 -1
  23. package/dist/admin/databases/index.txt +1 -1
  24. package/dist/admin/index.html +1 -1
  25. package/dist/admin/index.txt +1 -1
  26. package/dist/cli.js +1585 -707
  27. package/dist/docs/builds.md +42 -0
  28. package/dist/docs/index.md +3 -0
  29. package/dist/docs/postgres/reshape/actions.md +90 -0
  30. package/dist/docs/postgres/reshape/index.md +149 -0
  31. package/dist/docs/postgres.md +20 -1
  32. package/dist/docs/services.md +34 -0
  33. package/dist/postinstall.js +1 -1
  34. package/package.json +2 -2
  35. /package/dist/admin/_next/static/{FMaKxl-Dpw5U-PgHqIMag → h5UEt0QGdPmIwztzVl3eF}/_buildManifest.js +0 -0
  36. /package/dist/admin/_next/static/{FMaKxl-Dpw5U-PgHqIMag → h5UEt0QGdPmIwztzVl3eF}/_clientMiddlewareManifest.json +0 -0
  37. /package/dist/admin/_next/static/{FMaKxl-Dpw5U-PgHqIMag → h5UEt0QGdPmIwztzVl3eF}/_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((resolve10, 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
+ resolve10(subprocess);
648
648
  });
649
649
  });
650
650
  }
651
651
  if (isFallbackAttempt) {
652
- return new Promise((resolve7, reject) => {
652
+ return new Promise((resolve10, 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
+ resolve10(subprocess);
663
663
  });
664
664
  });
665
665
  });
666
666
  }
667
667
  subprocess.unref();
668
- return new Promise((resolve7, reject) => {
668
+ return new Promise((resolve10, reject) => {
669
669
  subprocess.once("error", reject);
670
670
  subprocess.once("spawn", () => {
671
671
  subprocess.off("error", reject);
672
- resolve7(subprocess);
672
+ resolve10(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((resolve10) => setTimeout(resolve10, 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((resolve10) => {
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
+ resolve10({ 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
+ resolve10({ 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.51",
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);
@@ -184468,6 +184529,9 @@ function parseBuilds(buildData) {
184468
184529
  if (fieldObj.context) {
184469
184530
  build.context = String(fieldObj.context);
184470
184531
  }
184532
+ if (fieldObj.root) {
184533
+ build.root = String(fieldObj.root);
184534
+ }
184471
184535
  if (dev) {
184472
184536
  build.dev = dev;
184473
184537
  }
@@ -184555,6 +184619,9 @@ function parseServices(serviceData) {
184555
184619
  if (fieldObj.command) {
184556
184620
  service.command = String(fieldObj.command);
184557
184621
  }
184622
+ if (fieldObj.root) {
184623
+ service.root = String(fieldObj.root);
184624
+ }
184558
184625
  const endpoints = parseEndpoints(fieldObj);
184559
184626
  if (fieldObj.expose !== void 0) {
184560
184627
  service.expose = {};
@@ -184589,13 +184656,43 @@ function parseServices(serviceData) {
184589
184656
  }
184590
184657
  return services;
184591
184658
  }
184659
+ function parseReshapeBlock(reshape) {
184660
+ if (!reshape) {
184661
+ return void 0;
184662
+ }
184663
+ const reshapeArray = Array.isArray(reshape) ? reshape : [reshape];
184664
+ const reshapeObj = reshapeArray[0];
184665
+ if (!reshapeObj || typeof reshapeObj !== "object" || Array.isArray(reshapeObj)) {
184666
+ return void 0;
184667
+ }
184668
+ const fieldObj = reshapeObj;
184669
+ if (fieldObj.enabled !== true) {
184670
+ return void 0;
184671
+ }
184672
+ const result = {
184673
+ enabled: true
184674
+ };
184675
+ if (typeof fieldObj.migrations_dir === "string") {
184676
+ result.migrations_dir = fieldObj.migrations_dir;
184677
+ }
184678
+ return result;
184679
+ }
184592
184680
  function parsePostgres(data) {
184593
184681
  if (!data || typeof data !== "object" || Array.isArray(data)) {
184594
184682
  return [];
184595
184683
  }
184596
184684
  const result = [];
184597
- for (const [name] of Object.entries(data)) {
184598
- result.push({ name });
184685
+ for (const [name, fieldsArray] of Object.entries(data)) {
184686
+ const postgres = { name };
184687
+ const fields = Array.isArray(fieldsArray) ? fieldsArray[0] : fieldsArray;
184688
+ if (fields && typeof fields === "object" && !Array.isArray(fields)) {
184689
+ const fieldObj = fields;
184690
+ const reshape = parseReshapeBlock(fieldObj.reshape);
184691
+ if (reshape) {
184692
+ postgres.reshape = reshape;
184693
+ }
184694
+ }
184695
+ result.push(postgres);
184599
184696
  }
184600
184697
  return result;
184601
184698
  }
@@ -184826,13 +184923,374 @@ function validateEndpointReferences(config) {
184826
184923
  return errors;
184827
184924
  }
184828
184925
 
184926
+ // src/lib/bin/types.ts
184927
+ var BinaryNotFoundError = class extends Error {
184928
+ constructor(binaryName, version, platformInfo) {
184929
+ super(
184930
+ `Binary "${binaryName}" version ${version} is not available for ${platformInfo.platform}-${platformInfo.arch}`
184931
+ );
184932
+ this.binaryName = binaryName;
184933
+ this.version = version;
184934
+ this.platformInfo = platformInfo;
184935
+ this.name = "BinaryNotFoundError";
184936
+ }
184937
+ };
184938
+ var DownloadError = class extends Error {
184939
+ constructor(url, statusCode, cause) {
184940
+ super(
184941
+ statusCode ? `Failed to download ${url}: HTTP ${statusCode}` : `Failed to download ${url}: ${cause?.message || "Unknown error"}`
184942
+ );
184943
+ this.url = url;
184944
+ this.statusCode = statusCode;
184945
+ this.cause = cause;
184946
+ this.name = "DownloadError";
184947
+ }
184948
+ };
184949
+ var ExtractionError = class extends Error {
184950
+ constructor(archivePath, cause) {
184951
+ super(
184952
+ `Failed to extract ${archivePath}: ${cause?.message || "Unknown error"}`
184953
+ );
184954
+ this.archivePath = archivePath;
184955
+ this.cause = cause;
184956
+ this.name = "ExtractionError";
184957
+ }
184958
+ };
184959
+
184960
+ // src/lib/bin/manager.ts
184961
+ import * as fs11 from "fs";
184962
+ import * as path7 from "path";
184963
+ import * as os6 from "os";
184964
+ import { createReadStream } from "fs";
184965
+ import { createTarExtractor, extractTo } from "tar-vern";
184966
+ function getLibraryEnv(binary) {
184967
+ if (!binary.libraryPath) {
184968
+ return {};
184969
+ }
184970
+ const platform4 = os6.platform();
184971
+ if (platform4 === "darwin") {
184972
+ return { DYLD_LIBRARY_PATH: binary.libraryPath };
184973
+ } else if (platform4 === "linux") {
184974
+ return { LD_LIBRARY_PATH: binary.libraryPath };
184975
+ }
184976
+ return {};
184977
+ }
184978
+ function getBinBaseDir() {
184979
+ return path7.join(os6.homedir(), ".specific", "bin");
184980
+ }
184981
+ function getPlatformInfo() {
184982
+ const platform4 = os6.platform();
184983
+ const arch3 = os6.arch();
184984
+ if (platform4 !== "darwin" && platform4 !== "linux") {
184985
+ throw new Error(
184986
+ `Unsupported platform: ${platform4}. Only macOS and Linux are supported.`
184987
+ );
184988
+ }
184989
+ const archStr = arch3;
184990
+ let mappedArch;
184991
+ if (archStr === "x64" || archStr === "x86_64") {
184992
+ mappedArch = "x64";
184993
+ } else if (archStr === "arm64" || archStr === "aarch64") {
184994
+ mappedArch = "arm64";
184995
+ } else {
184996
+ throw new Error(
184997
+ `Unsupported architecture: ${arch3}. Only x64 and arm64 are supported.`
184998
+ );
184999
+ }
185000
+ return { platform: platform4, arch: mappedArch };
185001
+ }
185002
+ function getBinaryDir(definition, version, platformInfo) {
185003
+ return path7.join(
185004
+ getBinBaseDir(),
185005
+ definition.name,
185006
+ version,
185007
+ `${platformInfo.platform}-${platformInfo.arch}`
185008
+ );
185009
+ }
185010
+ function isBinaryInstalled(definition, version, platformInfo) {
185011
+ const binDir = getBinaryDir(definition, version, platformInfo);
185012
+ for (const execPath of definition.executables) {
185013
+ const fullPath = path7.join(binDir, execPath);
185014
+ if (!fs11.existsSync(fullPath)) {
185015
+ return false;
185016
+ }
185017
+ }
185018
+ return true;
185019
+ }
185020
+ function buildDownloadUrl(definition, version, platformInfo) {
185021
+ const mapping = definition.platformMapping[platformInfo.platform]?.[platformInfo.arch];
185022
+ if (!mapping) {
185023
+ throw new BinaryNotFoundError(definition.name, version, platformInfo);
185024
+ }
185025
+ return definition.urlTemplate.replace(/{version}/g, version).replace(/{platform}/g, mapping.platform).replace(/{arch}/g, mapping.arch);
185026
+ }
185027
+ async function downloadFile(url, destPath, onProgress) {
185028
+ let response;
185029
+ try {
185030
+ response = await fetch(url);
185031
+ } catch (error) {
185032
+ throw new DownloadError(url, void 0, error);
185033
+ }
185034
+ if (!response.ok) {
185035
+ throw new DownloadError(url, response.status);
185036
+ }
185037
+ const totalBytes = parseInt(
185038
+ response.headers.get("content-length") || "0",
185039
+ 10
185040
+ );
185041
+ let bytesDownloaded = 0;
185042
+ const parentDir = path7.dirname(destPath);
185043
+ if (!fs11.existsSync(parentDir)) {
185044
+ fs11.mkdirSync(parentDir, { recursive: true });
185045
+ }
185046
+ const partPath = destPath + ".part";
185047
+ const fileStream = fs11.createWriteStream(partPath);
185048
+ try {
185049
+ const reader = response.body.getReader();
185050
+ while (true) {
185051
+ const { done, value } = await reader.read();
185052
+ if (done) break;
185053
+ bytesDownloaded += value.length;
185054
+ fileStream.write(Buffer.from(value));
185055
+ if (onProgress) {
185056
+ onProgress({
185057
+ phase: "downloading",
185058
+ bytesDownloaded,
185059
+ totalBytes: totalBytes || void 0,
185060
+ percent: totalBytes ? Math.round(bytesDownloaded / totalBytes * 100) : void 0
185061
+ });
185062
+ }
185063
+ }
185064
+ await new Promise((resolve10, reject) => {
185065
+ fileStream.end((err) => {
185066
+ if (err) reject(err);
185067
+ else resolve10();
185068
+ });
185069
+ });
185070
+ fs11.renameSync(partPath, destPath);
185071
+ } catch (error) {
185072
+ try {
185073
+ fileStream.close();
185074
+ if (fs11.existsSync(partPath)) {
185075
+ fs11.unlinkSync(partPath);
185076
+ }
185077
+ } catch {
185078
+ }
185079
+ throw new DownloadError(url, void 0, error);
185080
+ }
185081
+ }
185082
+ async function extractTarball(archivePath, destDir, definition, onProgress) {
185083
+ onProgress?.({ phase: "extracting" });
185084
+ if (!fs11.existsSync(destDir)) {
185085
+ fs11.mkdirSync(destDir, { recursive: true });
185086
+ }
185087
+ try {
185088
+ const fileStream = createReadStream(archivePath);
185089
+ const entries = createTarExtractor(fileStream, "gzip");
185090
+ await extractTo(entries, destDir);
185091
+ onProgress?.({ phase: "finalizing" });
185092
+ for (const execPath of definition.executables) {
185093
+ const fullPath = path7.join(destDir, execPath);
185094
+ if (fs11.existsSync(fullPath)) {
185095
+ fs11.chmodSync(fullPath, 493);
185096
+ }
185097
+ }
185098
+ } catch (error) {
185099
+ throw new ExtractionError(archivePath, error);
185100
+ }
185101
+ }
185102
+ async function ensureBinary(definition, version, onProgress) {
185103
+ const platformInfo = getPlatformInfo();
185104
+ const resolvedVersion = version || definition.versions[0];
185105
+ if (!definition.versions.includes(resolvedVersion)) {
185106
+ throw new Error(
185107
+ `Version ${resolvedVersion} not supported for ${definition.name}. Supported versions: ${definition.versions.join(", ")}`
185108
+ );
185109
+ }
185110
+ const binDir = getBinaryDir(definition, resolvedVersion, platformInfo);
185111
+ if (!isBinaryInstalled(definition, resolvedVersion, platformInfo)) {
185112
+ const url = buildDownloadUrl(definition, resolvedVersion, platformInfo);
185113
+ if (definition.type === "binary") {
185114
+ if (definition.executables.length !== 1) {
185115
+ throw new Error(
185116
+ `Binary type definitions must have exactly one executable, got ${definition.executables.length}`
185117
+ );
185118
+ }
185119
+ if (!fs11.existsSync(binDir)) {
185120
+ fs11.mkdirSync(binDir, { recursive: true });
185121
+ }
185122
+ const execPath = path7.join(binDir, definition.executables[0]);
185123
+ await downloadFile(url, execPath, onProgress);
185124
+ fs11.chmodSync(execPath, 493);
185125
+ onProgress?.({ phase: "finalizing" });
185126
+ } else {
185127
+ const downloadDir = path7.join(getBinBaseDir(), "downloads");
185128
+ const archiveName = `${definition.name}-${resolvedVersion}-${platformInfo.platform}-${platformInfo.arch}.tar.gz`;
185129
+ const archivePath = path7.join(downloadDir, archiveName);
185130
+ try {
185131
+ await downloadFile(url, archivePath, onProgress);
185132
+ await extractTarball(archivePath, binDir, definition, onProgress);
185133
+ } finally {
185134
+ try {
185135
+ if (fs11.existsSync(archivePath)) {
185136
+ fs11.unlinkSync(archivePath);
185137
+ }
185138
+ } catch {
185139
+ }
185140
+ }
185141
+ }
185142
+ }
185143
+ const executables = {};
185144
+ for (const execPath of definition.executables) {
185145
+ const name = path7.basename(execPath);
185146
+ executables[name] = path7.join(binDir, execPath);
185147
+ }
185148
+ const libraryPath = definition.libraryDir ? path7.join(binDir, definition.libraryDir) : void 0;
185149
+ return {
185150
+ rootDir: binDir,
185151
+ version: resolvedVersion,
185152
+ executables,
185153
+ libraryPath
185154
+ };
185155
+ }
185156
+
185157
+ // src/lib/bin/definitions/postgres.ts
185158
+ var postgresBinary = {
185159
+ name: "postgres",
185160
+ versions: ["18.1"],
185161
+ // URL: binaries.specific.dev/{SOFTWARE}/{VERSION}/{ARCH}.tar.gz
185162
+ urlTemplate: "https://binaries.specific.dev/postgres/{version}/{arch}.tar.gz",
185163
+ platformMapping: {
185164
+ darwin: {
185165
+ arm64: { platform: "macos", arch: "macos_arm" },
185166
+ x64: { platform: "macos", arch: "macos_x86" }
185167
+ },
185168
+ linux: {
185169
+ arm64: { platform: "linux", arch: "linux_arm" },
185170
+ x64: { platform: "linux", arch: "linux_x86" }
185171
+ }
185172
+ },
185173
+ // Archive contains bin/ and lib/ directories at root
185174
+ stripComponents: 0,
185175
+ // Core PostgreSQL executables (in bin/ directory)
185176
+ executables: ["bin/postgres", "bin/initdb", "bin/psql"],
185177
+ // Library directory relative to root (for setting LD_LIBRARY_PATH)
185178
+ libraryDir: "lib"
185179
+ };
185180
+
185181
+ // src/lib/bin/definitions/redis.ts
185182
+ var redisBinary = {
185183
+ name: "redis",
185184
+ versions: ["8.4.0"],
185185
+ // URL: binaries.specific.dev/{SOFTWARE}/{VERSION}/{ARCH}.tar.gz
185186
+ urlTemplate: "https://binaries.specific.dev/redis/{version}/{arch}.tar.gz",
185187
+ platformMapping: {
185188
+ darwin: {
185189
+ arm64: { platform: "macos", arch: "macos_arm" },
185190
+ x64: { platform: "macos", arch: "macos_x86" }
185191
+ },
185192
+ linux: {
185193
+ arm64: { platform: "linux", arch: "linux_arm" },
185194
+ x64: { platform: "linux", arch: "linux_x86" }
185195
+ }
185196
+ },
185197
+ // Binaries are at the root of the archive
185198
+ stripComponents: 0,
185199
+ // Core Redis executable
185200
+ executables: ["redis-server"]
185201
+ };
185202
+
185203
+ // src/lib/bin/definitions/electric.ts
185204
+ var electricBinary = {
185205
+ name: "electric",
185206
+ versions: ["1.2.11"],
185207
+ // URL: binaries.specific.dev/{SOFTWARE}/{VERSION}/{ARCH}.tar.gz
185208
+ urlTemplate: "https://binaries.specific.dev/electric/{version}/{arch}.tar.gz",
185209
+ platformMapping: {
185210
+ darwin: {
185211
+ arm64: { platform: "macos", arch: "macos_arm" },
185212
+ x64: { platform: "macos", arch: "macos_x86" }
185213
+ },
185214
+ linux: {
185215
+ arm64: { platform: "linux", arch: "linux_arm" },
185216
+ x64: { platform: "linux", arch: "linux_x86" }
185217
+ }
185218
+ },
185219
+ // Binaries are at the root of the archive
185220
+ stripComponents: 0,
185221
+ // Core Electric executable
185222
+ executables: ["electric"]
185223
+ };
185224
+
185225
+ // src/lib/bin/definitions/drizzle-gateway.ts
185226
+ var drizzleGatewayBinary = {
185227
+ name: "drizzle-gateway",
185228
+ versions: ["1.2.0"],
185229
+ // Official Drizzle Gateway download URL from R2
185230
+ urlTemplate: "https://pub-e240a4fd7085425baf4a7951e7611520.r2.dev/drizzle-gateway-{version}-{arch}",
185231
+ platformMapping: {
185232
+ darwin: {
185233
+ arm64: { platform: "macos", arch: "macos-arm64" },
185234
+ x64: { platform: "macos", arch: "macos-x64" }
185235
+ },
185236
+ linux: {
185237
+ arm64: { platform: "linux", arch: "linux-arm64" },
185238
+ x64: { platform: "linux", arch: "linux-x64" }
185239
+ }
185240
+ },
185241
+ // Raw binary, not a tarball
185242
+ type: "binary",
185243
+ // The executable name
185244
+ executables: ["drizzle-gateway"]
185245
+ };
185246
+
185247
+ // src/lib/bin/definitions/reshape.ts
185248
+ var reshapeBinary = {
185249
+ name: "reshape",
185250
+ versions: ["0.9.1"],
185251
+ // GitHub release assets
185252
+ urlTemplate: "https://github.com/fabianlindfors/reshape/releases/download/v{version}/reshape-{arch}",
185253
+ platformMapping: {
185254
+ darwin: {
185255
+ arm64: { platform: "darwin", arch: "darwin_aarch64" },
185256
+ x64: { platform: "darwin", arch: "darwin_amd64" }
185257
+ },
185258
+ linux: {
185259
+ arm64: { platform: "linux", arch: "linux_aarch64" },
185260
+ x64: { platform: "linux", arch: "linux_amd64" }
185261
+ }
185262
+ },
185263
+ // Raw binary, not a tarball
185264
+ type: "binary",
185265
+ // The executable name
185266
+ executables: ["reshape"]
185267
+ };
185268
+
184829
185269
  // src/commands/check.tsx
185270
+ async function runReshapeCheck(migrationsDir) {
185271
+ try {
185272
+ const binary = await ensureBinary(reshapeBinary);
185273
+ const reshapePath = binary.executables["reshape"];
185274
+ return new Promise((resolve10) => {
185275
+ execFile7(reshapePath, ["check", "--dirs", migrationsDir], (err, _stdout, stderr) => {
185276
+ if (err) {
185277
+ const errorMsg = stderr.trim() || err.message;
185278
+ resolve10({ success: false, error: errorMsg });
185279
+ } else {
185280
+ resolve10({ success: true });
185281
+ }
185282
+ });
185283
+ });
185284
+ } catch (err) {
185285
+ return { success: false, error: err instanceof Error ? err.message : String(err) };
185286
+ }
185287
+ }
184830
185288
  function CheckUI() {
184831
185289
  const [state, setState] = useState2({ status: "loading" });
184832
185290
  useEffect2(() => {
184833
185291
  async function load() {
184834
- const configPath = path7.join(process.cwd(), "specific.hcl");
184835
- if (!fs11.existsSync(configPath)) {
185292
+ const configPath = path8.join(process.cwd(), "specific.hcl");
185293
+ if (!fs12.existsSync(configPath)) {
184836
185294
  setState({
184837
185295
  status: "error",
184838
185296
  error: "No specific.hcl found in current directory"
@@ -184840,9 +185298,36 @@ function CheckUI() {
184840
185298
  return;
184841
185299
  }
184842
185300
  try {
184843
- const hcl = fs11.readFileSync(configPath, "utf-8");
185301
+ const hcl = fs12.readFileSync(configPath, "utf-8");
184844
185302
  const config2 = await parseConfig(hcl);
184845
- setState({ status: "success", config: config2 });
185303
+ const reshapeChecks2 = [];
185304
+ for (const pg of config2.postgres) {
185305
+ if (pg.reshape?.enabled) {
185306
+ const migrationsDir = path8.resolve(
185307
+ process.cwd(),
185308
+ pg.reshape.migrations_dir ?? "migrations"
185309
+ );
185310
+ if (!fs12.existsSync(migrationsDir)) {
185311
+ reshapeChecks2.push({
185312
+ databaseName: pg.name,
185313
+ migrationsDir: pg.reshape.migrations_dir ?? "migrations",
185314
+ success: false,
185315
+ error: `Migrations directory not found: ${migrationsDir}`
185316
+ });
185317
+ continue;
185318
+ }
185319
+ const result = await runReshapeCheck(migrationsDir);
185320
+ reshapeChecks2.push({
185321
+ databaseName: pg.name,
185322
+ migrationsDir: pg.reshape.migrations_dir ?? "migrations",
185323
+ ...result
185324
+ });
185325
+ }
185326
+ }
185327
+ if (reshapeChecks2.some((r) => !r.success)) {
185328
+ process.exitCode = 1;
185329
+ }
185330
+ setState({ status: "success", config: config2, reshapeChecks: reshapeChecks2 });
184846
185331
  } catch (err) {
184847
185332
  setState({
184848
185333
  status: "error",
@@ -184862,7 +185347,10 @@ function CheckUI() {
184862
185347
  if (!config) {
184863
185348
  return null;
184864
185349
  }
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))));
185350
+ const reshapeChecks = state.reshapeChecks ?? [];
185351
+ const reshapeErrors = reshapeChecks.filter((r) => !r.success);
185352
+ const hasReshapeErrors = reshapeErrors.length > 0;
185353
+ 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
185354
  }
184867
185355
  function checkCommand() {
184868
185356
  render3(/* @__PURE__ */ React3.createElement(CheckUI, null));
@@ -184872,8 +185360,8 @@ function checkCommand() {
184872
185360
  import React6, { useState as useState5, useEffect as useEffect3, useRef } from "react";
184873
185361
  import { render as render4, Text as Text6, Box as Box6, useApp as useApp2, Static } from "ink";
184874
185362
  import Spinner4 from "ink-spinner";
184875
- import * as fs20 from "fs";
184876
- import * as path17 from "path";
185363
+ import * as fs21 from "fs";
185364
+ import * as path18 from "path";
184877
185365
 
184878
185366
  // node_modules/.pnpm/chokidar@5.0.0/node_modules/chokidar/index.js
184879
185367
  import { EventEmitter } from "node:events";
@@ -184965,7 +185453,7 @@ var ReaddirpStream = class extends Readable {
184965
185453
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
184966
185454
  const statMethod = opts.lstat ? lstat : stat;
184967
185455
  if (wantBigintFsStats) {
184968
- this._stat = (path24) => statMethod(path24, { bigint: true });
185456
+ this._stat = (path26) => statMethod(path26, { bigint: true });
184969
185457
  } else {
184970
185458
  this._stat = statMethod;
184971
185459
  }
@@ -184990,8 +185478,8 @@ var ReaddirpStream = class extends Readable {
184990
185478
  const par = this.parent;
184991
185479
  const fil = par && par.files;
184992
185480
  if (fil && fil.length > 0) {
184993
- const { path: path24, depth } = par;
184994
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path24));
185481
+ const { path: path26, depth } = par;
185482
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path26));
184995
185483
  const awaited = await Promise.all(slice);
184996
185484
  for (const entry of awaited) {
184997
185485
  if (!entry)
@@ -185031,21 +185519,21 @@ var ReaddirpStream = class extends Readable {
185031
185519
  this.reading = false;
185032
185520
  }
185033
185521
  }
185034
- async _exploreDir(path24, depth) {
185522
+ async _exploreDir(path26, depth) {
185035
185523
  let files;
185036
185524
  try {
185037
- files = await readdir(path24, this._rdOptions);
185525
+ files = await readdir(path26, this._rdOptions);
185038
185526
  } catch (error) {
185039
185527
  this._onError(error);
185040
185528
  }
185041
- return { files, depth, path: path24 };
185529
+ return { files, depth, path: path26 };
185042
185530
  }
185043
- async _formatEntry(dirent, path24) {
185531
+ async _formatEntry(dirent, path26) {
185044
185532
  let entry;
185045
- const basename5 = this._isDirent ? dirent.name : dirent;
185533
+ const basename6 = this._isDirent ? dirent.name : dirent;
185046
185534
  try {
185047
- const fullPath = presolve(pjoin(path24, basename5));
185048
- entry = { path: prelative(this._root, fullPath), fullPath, basename: basename5 };
185535
+ const fullPath = presolve(pjoin(path26, basename6));
185536
+ entry = { path: prelative(this._root, fullPath), fullPath, basename: basename6 };
185049
185537
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
185050
185538
  } catch (err) {
185051
185539
  this._onError(err);
@@ -185444,16 +185932,16 @@ var delFromSet = (main, prop, item) => {
185444
185932
  };
185445
185933
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
185446
185934
  var FsWatchInstances = /* @__PURE__ */ new Map();
185447
- function createFsWatchInstance(path24, options2, listener, errHandler, emitRaw) {
185935
+ function createFsWatchInstance(path26, options2, listener, errHandler, emitRaw) {
185448
185936
  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));
185937
+ listener(path26);
185938
+ emitRaw(rawEvent, evPath, { watchedPath: path26 });
185939
+ if (evPath && path26 !== evPath) {
185940
+ fsWatchBroadcast(sp.resolve(path26, evPath), KEY_LISTENERS, sp.join(path26, evPath));
185453
185941
  }
185454
185942
  };
185455
185943
  try {
185456
- return fs_watch(path24, {
185944
+ return fs_watch(path26, {
185457
185945
  persistent: options2.persistent
185458
185946
  }, handleEvent);
185459
185947
  } catch (error) {
@@ -185469,12 +185957,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
185469
185957
  listener(val1, val2, val3);
185470
185958
  });
185471
185959
  };
185472
- var setFsWatchListener = (path24, fullPath, options2, handlers) => {
185960
+ var setFsWatchListener = (path26, fullPath, options2, handlers) => {
185473
185961
  const { listener, errHandler, rawEmitter } = handlers;
185474
185962
  let cont = FsWatchInstances.get(fullPath);
185475
185963
  let watcher;
185476
185964
  if (!options2.persistent) {
185477
- watcher = createFsWatchInstance(path24, options2, listener, errHandler, rawEmitter);
185965
+ watcher = createFsWatchInstance(path26, options2, listener, errHandler, rawEmitter);
185478
185966
  if (!watcher)
185479
185967
  return;
185480
185968
  return watcher.close.bind(watcher);
@@ -185485,7 +185973,7 @@ var setFsWatchListener = (path24, fullPath, options2, handlers) => {
185485
185973
  addAndConvert(cont, KEY_RAW, rawEmitter);
185486
185974
  } else {
185487
185975
  watcher = createFsWatchInstance(
185488
- path24,
185976
+ path26,
185489
185977
  options2,
185490
185978
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
185491
185979
  errHandler,
@@ -185500,7 +185988,7 @@ var setFsWatchListener = (path24, fullPath, options2, handlers) => {
185500
185988
  cont.watcherUnusable = true;
185501
185989
  if (isWindows && error.code === "EPERM") {
185502
185990
  try {
185503
- const fd = await open2(path24, "r");
185991
+ const fd = await open2(path26, "r");
185504
185992
  await fd.close();
185505
185993
  broadcastErr(error);
185506
185994
  } catch (err) {
@@ -185531,7 +186019,7 @@ var setFsWatchListener = (path24, fullPath, options2, handlers) => {
185531
186019
  };
185532
186020
  };
185533
186021
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
185534
- var setFsWatchFileListener = (path24, fullPath, options2, handlers) => {
186022
+ var setFsWatchFileListener = (path26, fullPath, options2, handlers) => {
185535
186023
  const { listener, rawEmitter } = handlers;
185536
186024
  let cont = FsWatchFileInstances.get(fullPath);
185537
186025
  const copts = cont && cont.options;
@@ -185553,7 +186041,7 @@ var setFsWatchFileListener = (path24, fullPath, options2, handlers) => {
185553
186041
  });
185554
186042
  const currmtime = curr.mtimeMs;
185555
186043
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
185556
- foreach(cont.listeners, (listener2) => listener2(path24, curr));
186044
+ foreach(cont.listeners, (listener2) => listener2(path26, curr));
185557
186045
  }
185558
186046
  })
185559
186047
  };
@@ -185583,13 +186071,13 @@ var NodeFsHandler = class {
185583
186071
  * @param listener on fs change
185584
186072
  * @returns closer for the watcher instance
185585
186073
  */
185586
- _watchWithNodeFs(path24, listener) {
186074
+ _watchWithNodeFs(path26, listener) {
185587
186075
  const opts = this.fsw.options;
185588
- const directory = sp.dirname(path24);
185589
- const basename5 = sp.basename(path24);
186076
+ const directory = sp.dirname(path26);
186077
+ const basename6 = sp.basename(path26);
185590
186078
  const parent = this.fsw._getWatchedDir(directory);
185591
- parent.add(basename5);
185592
- const absolutePath = sp.resolve(path24);
186079
+ parent.add(basename6);
186080
+ const absolutePath = sp.resolve(path26);
185593
186081
  const options2 = {
185594
186082
  persistent: opts.persistent
185595
186083
  };
@@ -185598,13 +186086,13 @@ var NodeFsHandler = class {
185598
186086
  let closer;
185599
186087
  if (opts.usePolling) {
185600
186088
  const enableBin = opts.interval !== opts.binaryInterval;
185601
- options2.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
185602
- closer = setFsWatchFileListener(path24, absolutePath, options2, {
186089
+ options2.interval = enableBin && isBinaryPath(basename6) ? opts.binaryInterval : opts.interval;
186090
+ closer = setFsWatchFileListener(path26, absolutePath, options2, {
185603
186091
  listener,
185604
186092
  rawEmitter: this.fsw._emitRaw
185605
186093
  });
185606
186094
  } else {
185607
- closer = setFsWatchListener(path24, absolutePath, options2, {
186095
+ closer = setFsWatchListener(path26, absolutePath, options2, {
185608
186096
  listener,
185609
186097
  errHandler: this._boundHandleError,
185610
186098
  rawEmitter: this.fsw._emitRaw
@@ -185621,12 +186109,12 @@ var NodeFsHandler = class {
185621
186109
  return;
185622
186110
  }
185623
186111
  const dirname8 = sp.dirname(file);
185624
- const basename5 = sp.basename(file);
186112
+ const basename6 = sp.basename(file);
185625
186113
  const parent = this.fsw._getWatchedDir(dirname8);
185626
186114
  let prevStats = stats;
185627
- if (parent.has(basename5))
186115
+ if (parent.has(basename6))
185628
186116
  return;
185629
- const listener = async (path24, newStats) => {
186117
+ const listener = async (path26, newStats) => {
185630
186118
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
185631
186119
  return;
185632
186120
  if (!newStats || newStats.mtimeMs === 0) {
@@ -185640,18 +186128,18 @@ var NodeFsHandler = class {
185640
186128
  this.fsw._emit(EV.CHANGE, file, newStats2);
185641
186129
  }
185642
186130
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
185643
- this.fsw._closeFile(path24);
186131
+ this.fsw._closeFile(path26);
185644
186132
  prevStats = newStats2;
185645
186133
  const closer2 = this._watchWithNodeFs(file, listener);
185646
186134
  if (closer2)
185647
- this.fsw._addPathCloser(path24, closer2);
186135
+ this.fsw._addPathCloser(path26, closer2);
185648
186136
  } else {
185649
186137
  prevStats = newStats2;
185650
186138
  }
185651
186139
  } catch (error) {
185652
- this.fsw._remove(dirname8, basename5);
186140
+ this.fsw._remove(dirname8, basename6);
185653
186141
  }
185654
- } else if (parent.has(basename5)) {
186142
+ } else if (parent.has(basename6)) {
185655
186143
  const at = newStats.atimeMs;
185656
186144
  const mt = newStats.mtimeMs;
185657
186145
  if (!at || at <= mt || mt !== prevStats.mtimeMs) {
@@ -185676,7 +186164,7 @@ var NodeFsHandler = class {
185676
186164
  * @param item basename of this item
185677
186165
  * @returns true if no more processing is needed for this entry.
185678
186166
  */
185679
- async _handleSymlink(entry, directory, path24, item) {
186167
+ async _handleSymlink(entry, directory, path26, item) {
185680
186168
  if (this.fsw.closed) {
185681
186169
  return;
185682
186170
  }
@@ -185686,7 +186174,7 @@ var NodeFsHandler = class {
185686
186174
  this.fsw._incrReadyCount();
185687
186175
  let linkPath;
185688
186176
  try {
185689
- linkPath = await fsrealpath(path24);
186177
+ linkPath = await fsrealpath(path26);
185690
186178
  } catch (e) {
185691
186179
  this.fsw._emitReady();
185692
186180
  return true;
@@ -185696,12 +186184,12 @@ var NodeFsHandler = class {
185696
186184
  if (dir.has(item)) {
185697
186185
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
185698
186186
  this.fsw._symlinkPaths.set(full, linkPath);
185699
- this.fsw._emit(EV.CHANGE, path24, entry.stats);
186187
+ this.fsw._emit(EV.CHANGE, path26, entry.stats);
185700
186188
  }
185701
186189
  } else {
185702
186190
  dir.add(item);
185703
186191
  this.fsw._symlinkPaths.set(full, linkPath);
185704
- this.fsw._emit(EV.ADD, path24, entry.stats);
186192
+ this.fsw._emit(EV.ADD, path26, entry.stats);
185705
186193
  }
185706
186194
  this.fsw._emitReady();
185707
186195
  return true;
@@ -185731,9 +186219,9 @@ var NodeFsHandler = class {
185731
186219
  return;
185732
186220
  }
185733
186221
  const item = entry.path;
185734
- let path24 = sp.join(directory, item);
186222
+ let path26 = sp.join(directory, item);
185735
186223
  current.add(item);
185736
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path24, item)) {
186224
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path26, item)) {
185737
186225
  return;
185738
186226
  }
185739
186227
  if (this.fsw.closed) {
@@ -185742,11 +186230,11 @@ var NodeFsHandler = class {
185742
186230
  }
185743
186231
  if (item === target || !target && !previous.has(item)) {
185744
186232
  this.fsw._incrReadyCount();
185745
- path24 = sp.join(dir, sp.relative(dir, path24));
185746
- this._addToNodeFs(path24, initialAdd, wh, depth + 1);
186233
+ path26 = sp.join(dir, sp.relative(dir, path26));
186234
+ this._addToNodeFs(path26, initialAdd, wh, depth + 1);
185747
186235
  }
185748
186236
  }).on(EV.ERROR, this._boundHandleError);
185749
- return new Promise((resolve7, reject) => {
186237
+ return new Promise((resolve10, reject) => {
185750
186238
  if (!stream)
185751
186239
  return reject();
185752
186240
  stream.once(STR_END, () => {
@@ -185755,7 +186243,7 @@ var NodeFsHandler = class {
185755
186243
  return;
185756
186244
  }
185757
186245
  const wasThrottled = throttler ? throttler.clear() : false;
185758
- resolve7(void 0);
186246
+ resolve10(void 0);
185759
186247
  previous.getChildren().filter((item) => {
185760
186248
  return item !== directory && !current.has(item);
185761
186249
  }).forEach((item) => {
@@ -185812,13 +186300,13 @@ var NodeFsHandler = class {
185812
186300
  * @param depth Child path actually targeted for watch
185813
186301
  * @param target Child path actually targeted for watch
185814
186302
  */
185815
- async _addToNodeFs(path24, initialAdd, priorWh, depth, target) {
186303
+ async _addToNodeFs(path26, initialAdd, priorWh, depth, target) {
185816
186304
  const ready = this.fsw._emitReady;
185817
- if (this.fsw._isIgnored(path24) || this.fsw.closed) {
186305
+ if (this.fsw._isIgnored(path26) || this.fsw.closed) {
185818
186306
  ready();
185819
186307
  return false;
185820
186308
  }
185821
- const wh = this.fsw._getWatchHelpers(path24);
186309
+ const wh = this.fsw._getWatchHelpers(path26);
185822
186310
  if (priorWh) {
185823
186311
  wh.filterPath = (entry) => priorWh.filterPath(entry);
185824
186312
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -185834,8 +186322,8 @@ var NodeFsHandler = class {
185834
186322
  const follow = this.fsw.options.followSymlinks;
185835
186323
  let closer;
185836
186324
  if (stats.isDirectory()) {
185837
- const absPath = sp.resolve(path24);
185838
- const targetPath = follow ? await fsrealpath(path24) : path24;
186325
+ const absPath = sp.resolve(path26);
186326
+ const targetPath = follow ? await fsrealpath(path26) : path26;
185839
186327
  if (this.fsw.closed)
185840
186328
  return;
185841
186329
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -185845,29 +186333,29 @@ var NodeFsHandler = class {
185845
186333
  this.fsw._symlinkPaths.set(absPath, targetPath);
185846
186334
  }
185847
186335
  } else if (stats.isSymbolicLink()) {
185848
- const targetPath = follow ? await fsrealpath(path24) : path24;
186336
+ const targetPath = follow ? await fsrealpath(path26) : path26;
185849
186337
  if (this.fsw.closed)
185850
186338
  return;
185851
186339
  const parent = sp.dirname(wh.watchPath);
185852
186340
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
185853
186341
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
185854
- closer = await this._handleDir(parent, stats, initialAdd, depth, path24, wh, targetPath);
186342
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path26, wh, targetPath);
185855
186343
  if (this.fsw.closed)
185856
186344
  return;
185857
186345
  if (targetPath !== void 0) {
185858
- this.fsw._symlinkPaths.set(sp.resolve(path24), targetPath);
186346
+ this.fsw._symlinkPaths.set(sp.resolve(path26), targetPath);
185859
186347
  }
185860
186348
  } else {
185861
186349
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
185862
186350
  }
185863
186351
  ready();
185864
186352
  if (closer)
185865
- this.fsw._addPathCloser(path24, closer);
186353
+ this.fsw._addPathCloser(path26, closer);
185866
186354
  return false;
185867
186355
  } catch (error) {
185868
186356
  if (this.fsw._handleError(error)) {
185869
186357
  ready();
185870
- return path24;
186358
+ return path26;
185871
186359
  }
185872
186360
  }
185873
186361
  }
@@ -185910,24 +186398,24 @@ function createPattern(matcher) {
185910
186398
  }
185911
186399
  return () => false;
185912
186400
  }
185913
- function normalizePath(path24) {
185914
- if (typeof path24 !== "string")
186401
+ function normalizePath(path26) {
186402
+ if (typeof path26 !== "string")
185915
186403
  throw new Error("string expected");
185916
- path24 = sp2.normalize(path24);
185917
- path24 = path24.replace(/\\/g, "/");
186404
+ path26 = sp2.normalize(path26);
186405
+ path26 = path26.replace(/\\/g, "/");
185918
186406
  let prepend = false;
185919
- if (path24.startsWith("//"))
186407
+ if (path26.startsWith("//"))
185920
186408
  prepend = true;
185921
- path24 = path24.replace(DOUBLE_SLASH_RE, "/");
186409
+ path26 = path26.replace(DOUBLE_SLASH_RE, "/");
185922
186410
  if (prepend)
185923
- path24 = "/" + path24;
185924
- return path24;
186411
+ path26 = "/" + path26;
186412
+ return path26;
185925
186413
  }
185926
186414
  function matchPatterns(patterns, testString, stats) {
185927
- const path24 = normalizePath(testString);
186415
+ const path26 = normalizePath(testString);
185928
186416
  for (let index = 0; index < patterns.length; index++) {
185929
186417
  const pattern = patterns[index];
185930
- if (pattern(path24, stats)) {
186418
+ if (pattern(path26, stats)) {
185931
186419
  return true;
185932
186420
  }
185933
186421
  }
@@ -185965,19 +186453,19 @@ var toUnix = (string) => {
185965
186453
  }
185966
186454
  return str;
185967
186455
  };
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));
186456
+ var normalizePathToUnix = (path26) => toUnix(sp2.normalize(toUnix(path26)));
186457
+ var normalizeIgnored = (cwd = "") => (path26) => {
186458
+ if (typeof path26 === "string") {
186459
+ return normalizePathToUnix(sp2.isAbsolute(path26) ? path26 : sp2.join(cwd, path26));
185972
186460
  } else {
185973
- return path24;
186461
+ return path26;
185974
186462
  }
185975
186463
  };
185976
- var getAbsolutePath = (path24, cwd) => {
185977
- if (sp2.isAbsolute(path24)) {
185978
- return path24;
186464
+ var getAbsolutePath = (path26, cwd) => {
186465
+ if (sp2.isAbsolute(path26)) {
186466
+ return path26;
185979
186467
  }
185980
- return sp2.join(cwd, path24);
186468
+ return sp2.join(cwd, path26);
185981
186469
  };
185982
186470
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
185983
186471
  var DirEntry = class {
@@ -186042,10 +186530,10 @@ var WatchHelper = class {
186042
186530
  dirParts;
186043
186531
  followSymlinks;
186044
186532
  statMethod;
186045
- constructor(path24, follow, fsw) {
186533
+ constructor(path26, follow, fsw) {
186046
186534
  this.fsw = fsw;
186047
- const watchPath = path24;
186048
- this.path = path24 = path24.replace(REPLACER_RE, "");
186535
+ const watchPath = path26;
186536
+ this.path = path26 = path26.replace(REPLACER_RE, "");
186049
186537
  this.watchPath = watchPath;
186050
186538
  this.fullWatchPath = sp2.resolve(watchPath);
186051
186539
  this.dirParts = [];
@@ -186185,20 +186673,20 @@ var FSWatcher = class extends EventEmitter {
186185
186673
  this._closePromise = void 0;
186186
186674
  let paths = unifyPaths(paths_);
186187
186675
  if (cwd) {
186188
- paths = paths.map((path24) => {
186189
- const absPath = getAbsolutePath(path24, cwd);
186676
+ paths = paths.map((path26) => {
186677
+ const absPath = getAbsolutePath(path26, cwd);
186190
186678
  return absPath;
186191
186679
  });
186192
186680
  }
186193
- paths.forEach((path24) => {
186194
- this._removeIgnoredPath(path24);
186681
+ paths.forEach((path26) => {
186682
+ this._removeIgnoredPath(path26);
186195
186683
  });
186196
186684
  this._userIgnored = void 0;
186197
186685
  if (!this._readyCount)
186198
186686
  this._readyCount = 0;
186199
186687
  this._readyCount += paths.length;
186200
- Promise.all(paths.map(async (path24) => {
186201
- const res = await this._nodeFsHandler._addToNodeFs(path24, !_internal, void 0, 0, _origAdd);
186688
+ Promise.all(paths.map(async (path26) => {
186689
+ const res = await this._nodeFsHandler._addToNodeFs(path26, !_internal, void 0, 0, _origAdd);
186202
186690
  if (res)
186203
186691
  this._emitReady();
186204
186692
  return res;
@@ -186220,17 +186708,17 @@ var FSWatcher = class extends EventEmitter {
186220
186708
  return this;
186221
186709
  const paths = unifyPaths(paths_);
186222
186710
  const { cwd } = this.options;
186223
- paths.forEach((path24) => {
186224
- if (!sp2.isAbsolute(path24) && !this._closers.has(path24)) {
186711
+ paths.forEach((path26) => {
186712
+ if (!sp2.isAbsolute(path26) && !this._closers.has(path26)) {
186225
186713
  if (cwd)
186226
- path24 = sp2.join(cwd, path24);
186227
- path24 = sp2.resolve(path24);
186714
+ path26 = sp2.join(cwd, path26);
186715
+ path26 = sp2.resolve(path26);
186228
186716
  }
186229
- this._closePath(path24);
186230
- this._addIgnoredPath(path24);
186231
- if (this._watched.has(path24)) {
186717
+ this._closePath(path26);
186718
+ this._addIgnoredPath(path26);
186719
+ if (this._watched.has(path26)) {
186232
186720
  this._addIgnoredPath({
186233
- path: path24,
186721
+ path: path26,
186234
186722
  recursive: true
186235
186723
  });
186236
186724
  }
@@ -186294,38 +186782,38 @@ var FSWatcher = class extends EventEmitter {
186294
186782
  * @param stats arguments to be passed with event
186295
186783
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
186296
186784
  */
186297
- async _emit(event, path24, stats) {
186785
+ async _emit(event, path26, stats) {
186298
186786
  if (this.closed)
186299
186787
  return;
186300
186788
  const opts = this.options;
186301
186789
  if (isWindows)
186302
- path24 = sp2.normalize(path24);
186790
+ path26 = sp2.normalize(path26);
186303
186791
  if (opts.cwd)
186304
- path24 = sp2.relative(opts.cwd, path24);
186305
- const args = [path24];
186792
+ path26 = sp2.relative(opts.cwd, path26);
186793
+ const args = [path26];
186306
186794
  if (stats != null)
186307
186795
  args.push(stats);
186308
186796
  const awf = opts.awaitWriteFinish;
186309
186797
  let pw;
186310
- if (awf && (pw = this._pendingWrites.get(path24))) {
186798
+ if (awf && (pw = this._pendingWrites.get(path26))) {
186311
186799
  pw.lastChange = /* @__PURE__ */ new Date();
186312
186800
  return this;
186313
186801
  }
186314
186802
  if (opts.atomic) {
186315
186803
  if (event === EVENTS.UNLINK) {
186316
- this._pendingUnlinks.set(path24, [event, ...args]);
186804
+ this._pendingUnlinks.set(path26, [event, ...args]);
186317
186805
  setTimeout(() => {
186318
- this._pendingUnlinks.forEach((entry, path25) => {
186806
+ this._pendingUnlinks.forEach((entry, path27) => {
186319
186807
  this.emit(...entry);
186320
186808
  this.emit(EVENTS.ALL, ...entry);
186321
- this._pendingUnlinks.delete(path25);
186809
+ this._pendingUnlinks.delete(path27);
186322
186810
  });
186323
186811
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
186324
186812
  return this;
186325
186813
  }
186326
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path24)) {
186814
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path26)) {
186327
186815
  event = EVENTS.CHANGE;
186328
- this._pendingUnlinks.delete(path24);
186816
+ this._pendingUnlinks.delete(path26);
186329
186817
  }
186330
186818
  }
186331
186819
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -186343,16 +186831,16 @@ var FSWatcher = class extends EventEmitter {
186343
186831
  this.emitWithAll(event, args);
186344
186832
  }
186345
186833
  };
186346
- this._awaitWriteFinish(path24, awf.stabilityThreshold, event, awfEmit);
186834
+ this._awaitWriteFinish(path26, awf.stabilityThreshold, event, awfEmit);
186347
186835
  return this;
186348
186836
  }
186349
186837
  if (event === EVENTS.CHANGE) {
186350
- const isThrottled = !this._throttle(EVENTS.CHANGE, path24, 50);
186838
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path26, 50);
186351
186839
  if (isThrottled)
186352
186840
  return this;
186353
186841
  }
186354
186842
  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;
186843
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path26) : path26;
186356
186844
  let stats2;
186357
186845
  try {
186358
186846
  stats2 = await stat3(fullPath);
@@ -186383,23 +186871,23 @@ var FSWatcher = class extends EventEmitter {
186383
186871
  * @param timeout duration of time to suppress duplicate actions
186384
186872
  * @returns tracking object or false if action should be suppressed
186385
186873
  */
186386
- _throttle(actionType, path24, timeout) {
186874
+ _throttle(actionType, path26, timeout) {
186387
186875
  if (!this._throttled.has(actionType)) {
186388
186876
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
186389
186877
  }
186390
186878
  const action = this._throttled.get(actionType);
186391
186879
  if (!action)
186392
186880
  throw new Error("invalid throttle");
186393
- const actionPath = action.get(path24);
186881
+ const actionPath = action.get(path26);
186394
186882
  if (actionPath) {
186395
186883
  actionPath.count++;
186396
186884
  return false;
186397
186885
  }
186398
186886
  let timeoutObject;
186399
186887
  const clear = () => {
186400
- const item = action.get(path24);
186888
+ const item = action.get(path26);
186401
186889
  const count = item ? item.count : 0;
186402
- action.delete(path24);
186890
+ action.delete(path26);
186403
186891
  clearTimeout(timeoutObject);
186404
186892
  if (item)
186405
186893
  clearTimeout(item.timeoutObject);
@@ -186407,7 +186895,7 @@ var FSWatcher = class extends EventEmitter {
186407
186895
  };
186408
186896
  timeoutObject = setTimeout(clear, timeout);
186409
186897
  const thr = { timeoutObject, clear, count: 0 };
186410
- action.set(path24, thr);
186898
+ action.set(path26, thr);
186411
186899
  return thr;
186412
186900
  }
186413
186901
  _incrReadyCount() {
@@ -186421,44 +186909,44 @@ var FSWatcher = class extends EventEmitter {
186421
186909
  * @param event
186422
186910
  * @param awfEmit Callback to be called when ready for event to be emitted.
186423
186911
  */
186424
- _awaitWriteFinish(path24, threshold, event, awfEmit) {
186912
+ _awaitWriteFinish(path26, threshold, event, awfEmit) {
186425
186913
  const awf = this.options.awaitWriteFinish;
186426
186914
  if (typeof awf !== "object")
186427
186915
  return;
186428
186916
  const pollInterval = awf.pollInterval;
186429
186917
  let timeoutHandler;
186430
- let fullPath = path24;
186431
- if (this.options.cwd && !sp2.isAbsolute(path24)) {
186432
- fullPath = sp2.join(this.options.cwd, path24);
186918
+ let fullPath = path26;
186919
+ if (this.options.cwd && !sp2.isAbsolute(path26)) {
186920
+ fullPath = sp2.join(this.options.cwd, path26);
186433
186921
  }
186434
186922
  const now = /* @__PURE__ */ new Date();
186435
186923
  const writes = this._pendingWrites;
186436
186924
  function awaitWriteFinishFn(prevStat) {
186437
186925
  statcb(fullPath, (err, curStat) => {
186438
- if (err || !writes.has(path24)) {
186926
+ if (err || !writes.has(path26)) {
186439
186927
  if (err && err.code !== "ENOENT")
186440
186928
  awfEmit(err);
186441
186929
  return;
186442
186930
  }
186443
186931
  const now2 = Number(/* @__PURE__ */ new Date());
186444
186932
  if (prevStat && curStat.size !== prevStat.size) {
186445
- writes.get(path24).lastChange = now2;
186933
+ writes.get(path26).lastChange = now2;
186446
186934
  }
186447
- const pw = writes.get(path24);
186935
+ const pw = writes.get(path26);
186448
186936
  const df = now2 - pw.lastChange;
186449
186937
  if (df >= threshold) {
186450
- writes.delete(path24);
186938
+ writes.delete(path26);
186451
186939
  awfEmit(void 0, curStat);
186452
186940
  } else {
186453
186941
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
186454
186942
  }
186455
186943
  });
186456
186944
  }
186457
- if (!writes.has(path24)) {
186458
- writes.set(path24, {
186945
+ if (!writes.has(path26)) {
186946
+ writes.set(path26, {
186459
186947
  lastChange: now,
186460
186948
  cancelWait: () => {
186461
- writes.delete(path24);
186949
+ writes.delete(path26);
186462
186950
  clearTimeout(timeoutHandler);
186463
186951
  return event;
186464
186952
  }
@@ -186469,8 +186957,8 @@ var FSWatcher = class extends EventEmitter {
186469
186957
  /**
186470
186958
  * Determines whether user has asked to ignore this path.
186471
186959
  */
186472
- _isIgnored(path24, stats) {
186473
- if (this.options.atomic && DOT_RE.test(path24))
186960
+ _isIgnored(path26, stats) {
186961
+ if (this.options.atomic && DOT_RE.test(path26))
186474
186962
  return true;
186475
186963
  if (!this._userIgnored) {
186476
186964
  const { cwd } = this.options;
@@ -186480,17 +186968,17 @@ var FSWatcher = class extends EventEmitter {
186480
186968
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
186481
186969
  this._userIgnored = anymatch(list, void 0);
186482
186970
  }
186483
- return this._userIgnored(path24, stats);
186971
+ return this._userIgnored(path26, stats);
186484
186972
  }
186485
- _isntIgnored(path24, stat4) {
186486
- return !this._isIgnored(path24, stat4);
186973
+ _isntIgnored(path26, stat4) {
186974
+ return !this._isIgnored(path26, stat4);
186487
186975
  }
186488
186976
  /**
186489
186977
  * Provides a set of common helpers and properties relating to symlink handling.
186490
186978
  * @param path file or directory pattern being watched
186491
186979
  */
186492
- _getWatchHelpers(path24) {
186493
- return new WatchHelper(path24, this.options.followSymlinks, this);
186980
+ _getWatchHelpers(path26) {
186981
+ return new WatchHelper(path26, this.options.followSymlinks, this);
186494
186982
  }
186495
186983
  // Directory helpers
186496
186984
  // -----------------
@@ -186522,63 +187010,63 @@ var FSWatcher = class extends EventEmitter {
186522
187010
  * @param item base path of item/directory
186523
187011
  */
186524
187012
  _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))
187013
+ const path26 = sp2.join(directory, item);
187014
+ const fullPath = sp2.resolve(path26);
187015
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path26) || this._watched.has(fullPath);
187016
+ if (!this._throttle("remove", path26, 100))
186529
187017
  return;
186530
187018
  if (!isDirectory && this._watched.size === 1) {
186531
187019
  this.add(directory, item, true);
186532
187020
  }
186533
- const wp = this._getWatchedDir(path24);
187021
+ const wp = this._getWatchedDir(path26);
186534
187022
  const nestedDirectoryChildren = wp.getChildren();
186535
- nestedDirectoryChildren.forEach((nested) => this._remove(path24, nested));
187023
+ nestedDirectoryChildren.forEach((nested) => this._remove(path26, nested));
186536
187024
  const parent = this._getWatchedDir(directory);
186537
187025
  const wasTracked = parent.has(item);
186538
187026
  parent.remove(item);
186539
187027
  if (this._symlinkPaths.has(fullPath)) {
186540
187028
  this._symlinkPaths.delete(fullPath);
186541
187029
  }
186542
- let relPath = path24;
187030
+ let relPath = path26;
186543
187031
  if (this.options.cwd)
186544
- relPath = sp2.relative(this.options.cwd, path24);
187032
+ relPath = sp2.relative(this.options.cwd, path26);
186545
187033
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
186546
187034
  const event = this._pendingWrites.get(relPath).cancelWait();
186547
187035
  if (event === EVENTS.ADD)
186548
187036
  return;
186549
187037
  }
186550
- this._watched.delete(path24);
187038
+ this._watched.delete(path26);
186551
187039
  this._watched.delete(fullPath);
186552
187040
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
186553
- if (wasTracked && !this._isIgnored(path24))
186554
- this._emit(eventName, path24);
186555
- this._closePath(path24);
187041
+ if (wasTracked && !this._isIgnored(path26))
187042
+ this._emit(eventName, path26);
187043
+ this._closePath(path26);
186556
187044
  }
186557
187045
  /**
186558
187046
  * Closes all watchers for a path
186559
187047
  */
186560
- _closePath(path24) {
186561
- this._closeFile(path24);
186562
- const dir = sp2.dirname(path24);
186563
- this._getWatchedDir(dir).remove(sp2.basename(path24));
187048
+ _closePath(path26) {
187049
+ this._closeFile(path26);
187050
+ const dir = sp2.dirname(path26);
187051
+ this._getWatchedDir(dir).remove(sp2.basename(path26));
186564
187052
  }
186565
187053
  /**
186566
187054
  * Closes only file-specific watchers
186567
187055
  */
186568
- _closeFile(path24) {
186569
- const closers = this._closers.get(path24);
187056
+ _closeFile(path26) {
187057
+ const closers = this._closers.get(path26);
186570
187058
  if (!closers)
186571
187059
  return;
186572
187060
  closers.forEach((closer) => closer());
186573
- this._closers.delete(path24);
187061
+ this._closers.delete(path26);
186574
187062
  }
186575
- _addPathCloser(path24, closer) {
187063
+ _addPathCloser(path26, closer) {
186576
187064
  if (!closer)
186577
187065
  return;
186578
- let list = this._closers.get(path24);
187066
+ let list = this._closers.get(path26);
186579
187067
  if (!list) {
186580
187068
  list = [];
186581
- this._closers.set(path24, list);
187069
+ this._closers.set(path26, list);
186582
187070
  }
186583
187071
  list.push(closer);
186584
187072
  }
@@ -186627,8 +187115,8 @@ var PortAllocator = class {
186627
187115
  };
186628
187116
 
186629
187117
  // src/lib/dev/stable-port-allocator.ts
186630
- import * as fs12 from "fs";
186631
- import * as path8 from "path";
187118
+ import * as fs13 from "fs";
187119
+ import * as path9 from "path";
186632
187120
  var PORT_RANGE_START2 = 4e4;
186633
187121
  var PORT_RANGE_END2 = 49999;
186634
187122
  var StablePortAllocator = class {
@@ -186637,16 +187125,16 @@ var StablePortAllocator = class {
186637
187125
  savedPorts = {};
186638
187126
  usedPorts = /* @__PURE__ */ new Set();
186639
187127
  constructor(projectRoot, key = "default") {
186640
- this.portsDir = path8.join(projectRoot, ".specific", "keys", key);
186641
- this.portsFilePath = path8.join(this.portsDir, "ports.json");
187128
+ this.portsDir = path9.join(projectRoot, ".specific", "keys", key);
187129
+ this.portsFilePath = path9.join(this.portsDir, "ports.json");
186642
187130
  this.loadPorts();
186643
187131
  }
186644
187132
  loadPorts() {
186645
- if (!fs12.existsSync(this.portsFilePath)) {
187133
+ if (!fs13.existsSync(this.portsFilePath)) {
186646
187134
  return;
186647
187135
  }
186648
187136
  try {
186649
- const content = fs12.readFileSync(this.portsFilePath, "utf-8");
187137
+ const content = fs13.readFileSync(this.portsFilePath, "utf-8");
186650
187138
  const data = JSON.parse(content);
186651
187139
  if (data.version === 1 && data.ports) {
186652
187140
  this.savedPorts = data.ports;
@@ -186659,14 +187147,14 @@ var StablePortAllocator = class {
186659
187147
  }
186660
187148
  }
186661
187149
  savePorts() {
186662
- if (!fs12.existsSync(this.portsDir)) {
186663
- fs12.mkdirSync(this.portsDir, { recursive: true });
187150
+ if (!fs13.existsSync(this.portsDir)) {
187151
+ fs13.mkdirSync(this.portsDir, { recursive: true });
186664
187152
  }
186665
187153
  const data = {
186666
187154
  version: 1,
186667
187155
  ports: this.savedPorts
186668
187156
  };
186669
- fs12.writeFileSync(this.portsFilePath, JSON.stringify(data, null, 2));
187157
+ fs13.writeFileSync(this.portsFilePath, JSON.stringify(data, null, 2));
186670
187158
  }
186671
187159
  allocateRandom() {
186672
187160
  const rangeSize = PORT_RANGE_END2 - PORT_RANGE_START2 + 1;
@@ -186697,329 +187185,6 @@ import * as fs14 from "fs";
186697
187185
  import * as path10 from "path";
186698
187186
  import * as net from "net";
186699
187187
  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
187188
  async function startPostgres(pg, port, dataDir, onProgress) {
187024
187189
  const binary = await ensureBinary(postgresBinary, void 0, onProgress);
187025
187190
  const dbDataPath = path10.join(process.cwd(), dataDir, pg.name);
@@ -187142,7 +187307,7 @@ async function startStorage(storage, port, dataDir) {
187142
187307
  };
187143
187308
  }
187144
187309
  async function runCommand(command, args, env2) {
187145
- return new Promise((resolve7, reject) => {
187310
+ return new Promise((resolve10, reject) => {
187146
187311
  const proc = spawn(command, args, {
187147
187312
  stdio: ["ignore", "pipe", "pipe"],
187148
187313
  env: env2
@@ -187153,7 +187318,7 @@ async function runCommand(command, args, env2) {
187153
187318
  });
187154
187319
  proc.on("close", (code) => {
187155
187320
  if (code === 0) {
187156
- resolve7();
187321
+ resolve10();
187157
187322
  } else {
187158
187323
  reject(new Error(`Command failed with code ${code}: ${stderr}`));
187159
187324
  }
@@ -187162,7 +187327,7 @@ async function runCommand(command, args, env2) {
187162
187327
  });
187163
187328
  }
187164
187329
  async function createPostgresDatabase(postgresPath, dataDir, dbName, env2) {
187165
- return new Promise((resolve7, reject) => {
187330
+ return new Promise((resolve10, reject) => {
187166
187331
  const proc = spawn(
187167
187332
  postgresPath,
187168
187333
  ["--single", "-D", dataDir, "postgres"],
@@ -187176,7 +187341,7 @@ async function createPostgresDatabase(postgresPath, dataDir, dbName, env2) {
187176
187341
  stderr += data.toString();
187177
187342
  });
187178
187343
  proc.on("close", (code) => {
187179
- resolve7();
187344
+ resolve10();
187180
187345
  });
187181
187346
  proc.on("error", reject);
187182
187347
  proc.stdin?.write(`CREATE DATABASE "${dbName}";
@@ -187196,33 +187361,33 @@ async function waitForTcpPort(host, port, timeoutMs = 3e4) {
187196
187361
  throw new Error(`Port ${port} did not become available within timeout`);
187197
187362
  }
187198
187363
  function checkTcpPort(host, port) {
187199
- return new Promise((resolve7) => {
187364
+ return new Promise((resolve10) => {
187200
187365
  const socket = new net.Socket();
187201
187366
  socket.setTimeout(1e3);
187202
187367
  socket.on("connect", () => {
187203
187368
  socket.destroy();
187204
- resolve7(true);
187369
+ resolve10(true);
187205
187370
  });
187206
187371
  socket.on("timeout", () => {
187207
187372
  socket.destroy();
187208
- resolve7(false);
187373
+ resolve10(false);
187209
187374
  });
187210
187375
  socket.on("error", () => {
187211
187376
  socket.destroy();
187212
- resolve7(false);
187377
+ resolve10(false);
187213
187378
  });
187214
187379
  socket.connect(port, host);
187215
187380
  });
187216
187381
  }
187217
187382
  async function stopProcess(proc) {
187218
- return new Promise((resolve7) => {
187383
+ return new Promise((resolve10) => {
187219
187384
  if (proc.killed || proc.exitCode !== null) {
187220
- resolve7();
187385
+ resolve10();
187221
187386
  return;
187222
187387
  }
187223
187388
  proc.once("exit", () => {
187224
187389
  clearTimeout(forceKillTimeout);
187225
- resolve7();
187390
+ resolve10();
187226
187391
  });
187227
187392
  proc.kill("SIGTERM");
187228
187393
  const forceKillTimeout = setTimeout(() => {
@@ -187233,7 +187398,7 @@ async function stopProcess(proc) {
187233
187398
  });
187234
187399
  }
187235
187400
  function sleep2(ms) {
187236
- return new Promise((resolve7) => setTimeout(resolve7, ms));
187401
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
187237
187402
  }
187238
187403
 
187239
187404
  // src/lib/dev/service-runner.ts
@@ -187242,7 +187407,7 @@ import { spawn as spawn2 } from "child_process";
187242
187407
  // src/lib/local/parser.ts
187243
187408
  var import_hcl2_json_parser2 = __toESM(require_dist(), 1);
187244
187409
  import { readFile, writeFile } from "fs/promises";
187245
- import { existsSync as existsSync10 } from "fs";
187410
+ import { existsSync as existsSync11 } from "fs";
187246
187411
  var { parseToObject: parseToObject2 } = import_hcl2_json_parser2.default;
187247
187412
  var LOCAL_FILE = "specific.local";
187248
187413
  var HEADER_COMMENT = `# Local secrets and configuration
@@ -187283,7 +187448,7 @@ async function parseLocalFile(content) {
187283
187448
  return { secrets, configs };
187284
187449
  }
187285
187450
  async function loadLocal() {
187286
- if (!existsSync10(LOCAL_FILE)) {
187451
+ if (!existsSync11(LOCAL_FILE)) {
187287
187452
  return { secrets: /* @__PURE__ */ new Map(), configs: /* @__PURE__ */ new Map() };
187288
187453
  }
187289
187454
  const content = await readFile(LOCAL_FILE, "utf-8");
@@ -187323,7 +187488,7 @@ ${newLine}
187323
187488
  }
187324
187489
  async function saveLocalSecret(name, value) {
187325
187490
  let content = "";
187326
- if (existsSync10(LOCAL_FILE)) {
187491
+ if (existsSync11(LOCAL_FILE)) {
187327
187492
  content = await readFile(LOCAL_FILE, "utf-8");
187328
187493
  } else {
187329
187494
  content = HEADER_COMMENT;
@@ -187333,7 +187498,7 @@ async function saveLocalSecret(name, value) {
187333
187498
  }
187334
187499
  async function saveLocalConfig(name, value) {
187335
187500
  let content = "";
187336
- if (existsSync10(LOCAL_FILE)) {
187501
+ if (existsSync11(LOCAL_FILE)) {
187337
187502
  content = await readFile(LOCAL_FILE, "utf-8");
187338
187503
  } else {
187339
187504
  content = HEADER_COMMENT;
@@ -187342,6 +187507,13 @@ async function saveLocalConfig(name, value) {
187342
187507
  await writeFile(LOCAL_FILE, content);
187343
187508
  }
187344
187509
 
187510
+ // src/lib/dev/reshape-manager.ts
187511
+ function appendSearchPathToUrl(baseUrl, searchPath) {
187512
+ const url = new URL(baseUrl);
187513
+ url.searchParams.set("options", `-c search_path=${searchPath}`);
187514
+ return url.toString();
187515
+ }
187516
+
187345
187517
  // src/lib/dev/env-resolver.ts
187346
187518
  var MissingSecretError = class extends Error {
187347
187519
  constructor(secretName) {
@@ -187446,6 +187618,9 @@ function resolveEnvValue(value, resources, secrets, configs, servicePort, servic
187446
187618
  }
187447
187619
  switch (value.attribute) {
187448
187620
  case "url":
187621
+ if (pg.reshapeEnabled && pg.reshapeSearchPath) {
187622
+ return appendSearchPathToUrl(pg.url, pg.reshapeSearchPath);
187623
+ }
187449
187624
  return pg.url;
187450
187625
  case "host":
187451
187626
  return pg.host;
@@ -187577,7 +187752,7 @@ function resolveEnvForExec(env2, resources, secrets, configs) {
187577
187752
  }
187578
187753
 
187579
187754
  // src/lib/dev/service-runner.ts
187580
- function startService(service, resources, secrets, configs, endpointPorts, serviceEndpoints, onLog, publicUrls) {
187755
+ function startService(service, resources, secrets, configs, endpointPorts, serviceEndpoints, onLog, publicUrls, cwd) {
187581
187756
  const command = service.dev?.command ?? service.command;
187582
187757
  if (!command) {
187583
187758
  throw new Error(`Service "${service.name}" has no command`);
@@ -187599,7 +187774,7 @@ function startService(service, resources, secrets, configs, endpointPorts, servi
187599
187774
  );
187600
187775
  const child = spawn2(command, {
187601
187776
  shell: true,
187602
- cwd: process.cwd(),
187777
+ cwd: cwd || process.cwd(),
187603
187778
  env: {
187604
187779
  ...process.env,
187605
187780
  ...resolvedEnv
@@ -187627,14 +187802,14 @@ function startService(service, resources, secrets, configs, endpointPorts, servi
187627
187802
  ports: endpointPorts,
187628
187803
  process: child,
187629
187804
  async stop() {
187630
- return new Promise((resolve7) => {
187805
+ return new Promise((resolve10) => {
187631
187806
  if (child.killed || child.exitCode !== null) {
187632
- resolve7();
187807
+ resolve10();
187633
187808
  return;
187634
187809
  }
187635
187810
  child.once("exit", () => {
187636
187811
  clearTimeout(forceKillTimeout);
187637
- resolve7();
187812
+ resolve10();
187638
187813
  });
187639
187814
  child.kill("SIGTERM");
187640
187815
  const forceKillTimeout = setTimeout(() => {
@@ -187715,7 +187890,7 @@ var InstanceStateManager = class {
187715
187890
  }
187716
187891
  continue;
187717
187892
  }
187718
- await new Promise((resolve7) => setTimeout(resolve7, 100));
187893
+ await new Promise((resolve10) => setTimeout(resolve10, 100));
187719
187894
  } else {
187720
187895
  throw e;
187721
187896
  }
@@ -187998,7 +188173,7 @@ async function startHttpProxy(services, certificate, getState, instanceKey = "de
187998
188173
  handleRequest
187999
188174
  );
188000
188175
  httpsServer.on("upgrade", handleUpgrade);
188001
- return new Promise((resolve7, reject) => {
188176
+ return new Promise((resolve10, reject) => {
188002
188177
  let httpStarted = false;
188003
188178
  let httpsStarted = false;
188004
188179
  let failed = false;
@@ -188009,7 +188184,7 @@ async function startHttpProxy(services, certificate, getState, instanceKey = "de
188009
188184
  "proxy",
188010
188185
  `HTTP/HTTPS proxy started on ports ${HTTP_PORT}/${HTTPS_PORT}`
188011
188186
  );
188012
- resolve7({
188187
+ resolve10({
188013
188188
  httpPort: HTTP_PORT,
188014
188189
  httpsPort: HTTPS_PORT,
188015
188190
  updateServices,
@@ -188021,13 +188196,13 @@ async function startHttpProxy(services, certificate, getState, instanceKey = "de
188021
188196
  writeLog("proxy", "Certificate updated");
188022
188197
  },
188023
188198
  async stop() {
188024
- return new Promise((resolve8) => {
188199
+ return new Promise((resolve11) => {
188025
188200
  let closed = 0;
188026
188201
  const onClose = () => {
188027
188202
  closed++;
188028
188203
  if (closed === 2) {
188029
188204
  clearTimeout(forceCloseTimeout);
188030
- resolve8();
188205
+ resolve11();
188031
188206
  }
188032
188207
  };
188033
188208
  httpServer.close(onClose);
@@ -188035,7 +188210,7 @@ async function startHttpProxy(services, certificate, getState, instanceKey = "de
188035
188210
  const forceCloseTimeout = setTimeout(() => {
188036
188211
  httpServer.closeAllConnections?.();
188037
188212
  httpsServer.closeAllConnections?.();
188038
- resolve8();
188213
+ resolve11();
188039
188214
  }, 2e3);
188040
188215
  });
188041
188216
  }
@@ -188198,7 +188373,7 @@ function serveFileContent(res, filePath, contentType, statusCode = 200) {
188198
188373
  }
188199
188374
  }
188200
188375
  async function startAdminServer(getState) {
188201
- return new Promise((resolve7, reject) => {
188376
+ return new Promise((resolve10, reject) => {
188202
188377
  const server = http.createServer((req, res) => {
188203
188378
  const url = new URL(req.url || "/", "http://localhost");
188204
188379
  res.setHeader("Access-Control-Allow-Origin", "*");
@@ -188230,7 +188405,7 @@ async function startAdminServer(getState) {
188230
188405
  }
188231
188406
  const port = addr.port;
188232
188407
  writeLog("admin", `Admin API server started on port ${port}`);
188233
- resolve7({
188408
+ resolve10({
188234
188409
  port,
188235
188410
  stop: () => new Promise((res, rej) => {
188236
188411
  server.close((err) => err ? rej(err) : res());
@@ -188246,7 +188421,7 @@ import { spawn as spawn3 } from "child_process";
188246
188421
 
188247
188422
  // src/lib/secrets/parser.ts
188248
188423
  import { readFile as readFile2, writeFile as writeFile2, mkdir } from "fs/promises";
188249
- import { existsSync as existsSync13 } from "fs";
188424
+ import { existsSync as existsSync14 } from "fs";
188250
188425
  import * as path13 from "path";
188251
188426
  import * as crypto2 from "crypto";
188252
188427
  var GENERATED_SECRETS_FILE = ".specific/generated-secrets.json";
@@ -188264,7 +188439,7 @@ function generateRandomString(length = 64) {
188264
188439
  return result;
188265
188440
  }
188266
188441
  async function loadGeneratedSecrets() {
188267
- if (!existsSync13(GENERATED_SECRETS_FILE)) {
188442
+ if (!existsSync14(GENERATED_SECRETS_FILE)) {
188268
188443
  return /* @__PURE__ */ new Map();
188269
188444
  }
188270
188445
  const content = await readFile2(GENERATED_SECRETS_FILE, "utf-8");
@@ -188273,13 +188448,13 @@ async function loadGeneratedSecrets() {
188273
188448
  }
188274
188449
  async function saveGeneratedSecret(name, value) {
188275
188450
  let secrets = {};
188276
- if (existsSync13(GENERATED_SECRETS_FILE)) {
188451
+ if (existsSync14(GENERATED_SECRETS_FILE)) {
188277
188452
  const content = await readFile2(GENERATED_SECRETS_FILE, "utf-8");
188278
188453
  secrets = JSON.parse(content);
188279
188454
  }
188280
188455
  secrets[name] = value;
188281
188456
  const dir = path13.dirname(GENERATED_SECRETS_FILE);
188282
- if (!existsSync13(dir)) {
188457
+ if (!existsSync14(dir)) {
188283
188458
  await mkdir(dir, { recursive: true });
188284
188459
  }
188285
188460
  await writeFile2(GENERATED_SECRETS_FILE, JSON.stringify(secrets, null, 2) + "\n");
@@ -188391,33 +188566,33 @@ async function waitForTcpPort2(host, port, timeoutMs = 3e4) {
188391
188566
  throw new Error(`Electric port ${port} did not become available within timeout`);
188392
188567
  }
188393
188568
  function checkTcpPort2(host, port) {
188394
- return new Promise((resolve7) => {
188569
+ return new Promise((resolve10) => {
188395
188570
  const socket = new net2.Socket();
188396
188571
  socket.setTimeout(1e3);
188397
188572
  socket.on("connect", () => {
188398
188573
  socket.destroy();
188399
- resolve7(true);
188574
+ resolve10(true);
188400
188575
  });
188401
188576
  socket.on("timeout", () => {
188402
188577
  socket.destroy();
188403
- resolve7(false);
188578
+ resolve10(false);
188404
188579
  });
188405
188580
  socket.on("error", () => {
188406
188581
  socket.destroy();
188407
- resolve7(false);
188582
+ resolve10(false);
188408
188583
  });
188409
188584
  socket.connect(port, host);
188410
188585
  });
188411
188586
  }
188412
188587
  async function stopProcess2(proc) {
188413
- return new Promise((resolve7) => {
188588
+ return new Promise((resolve10) => {
188414
188589
  if (proc.killed || proc.exitCode !== null) {
188415
- resolve7();
188590
+ resolve10();
188416
188591
  return;
188417
188592
  }
188418
188593
  proc.once("exit", () => {
188419
188594
  clearTimeout(forceKillTimeout);
188420
- resolve7();
188595
+ resolve10();
188421
188596
  });
188422
188597
  proc.kill("SIGTERM");
188423
188598
  const forceKillTimeout = setTimeout(() => {
@@ -188428,7 +188603,7 @@ async function stopProcess2(proc) {
188428
188603
  });
188429
188604
  }
188430
188605
  function sleep3(ms) {
188431
- return new Promise((resolve7) => setTimeout(resolve7, ms));
188606
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
188432
188607
  }
188433
188608
 
188434
188609
  // src/lib/dev/drizzle-gateway-manager.ts
@@ -188512,33 +188687,33 @@ async function waitForTcpPort3(host, port, timeoutMs = 3e4) {
188512
188687
  );
188513
188688
  }
188514
188689
  function checkTcpPort3(host, port) {
188515
- return new Promise((resolve7) => {
188690
+ return new Promise((resolve10) => {
188516
188691
  const socket = new net3.Socket();
188517
188692
  socket.setTimeout(1e3);
188518
188693
  socket.on("connect", () => {
188519
188694
  socket.destroy();
188520
- resolve7(true);
188695
+ resolve10(true);
188521
188696
  });
188522
188697
  socket.on("timeout", () => {
188523
188698
  socket.destroy();
188524
- resolve7(false);
188699
+ resolve10(false);
188525
188700
  });
188526
188701
  socket.on("error", () => {
188527
188702
  socket.destroy();
188528
- resolve7(false);
188703
+ resolve10(false);
188529
188704
  });
188530
188705
  socket.connect(port, host);
188531
188706
  });
188532
188707
  }
188533
188708
  async function stopProcess3(proc) {
188534
- return new Promise((resolve7) => {
188709
+ return new Promise((resolve10) => {
188535
188710
  if (proc.killed || proc.exitCode !== null) {
188536
- resolve7();
188711
+ resolve10();
188537
188712
  return;
188538
188713
  }
188539
188714
  proc.once("exit", () => {
188540
188715
  clearTimeout(forceKillTimeout);
188541
- resolve7();
188716
+ resolve10();
188542
188717
  });
188543
188718
  proc.kill("SIGTERM");
188544
188719
  const forceKillTimeout = setTimeout(() => {
@@ -188549,7 +188724,7 @@ async function stopProcess3(proc) {
188549
188724
  });
188550
188725
  }
188551
188726
  function sleep4(ms) {
188552
- return new Promise((resolve7) => setTimeout(resolve7, ms));
188727
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
188553
188728
  }
188554
188729
 
188555
188730
  // src/lib/dev/sync-detector.ts
@@ -188569,6 +188744,288 @@ function detectSyncDatabases(config) {
188569
188744
  return needsSync;
188570
188745
  }
188571
188746
 
188747
+ // src/lib/dev/reshape-watcher.ts
188748
+ import * as fs18 from "fs";
188749
+ import * as path15 from "path";
188750
+ import { spawnSync } from "child_process";
188751
+ function getMigrationFiles(dir, log) {
188752
+ log(`Scanning migrations directory: ${dir}`);
188753
+ if (!fs18.existsSync(dir)) {
188754
+ log(`Migrations directory does not exist: ${dir}`);
188755
+ return [];
188756
+ }
188757
+ const files = fs18.readdirSync(dir);
188758
+ log(`Found ${files.length} files in directory`);
188759
+ const tomlFiles = files.filter((f) => f.endsWith(".toml")).sort((a, b) => a.localeCompare(b));
188760
+ log(`Found ${tomlFiles.length} .toml migration files: ${tomlFiles.join(", ") || "(none)"}`);
188761
+ return tomlFiles;
188762
+ }
188763
+ function getMigrationName(filename) {
188764
+ return filename.replace(/\.toml$/, "");
188765
+ }
188766
+ function getSchemaName(filename) {
188767
+ return `migration_${getMigrationName(filename)}`;
188768
+ }
188769
+ function runReshape(args, databaseUrl, migrationsDir, reshapeBinaryPath, log) {
188770
+ const fullArgs = ["migration", ...args];
188771
+ if (args[0] === "start") {
188772
+ fullArgs.push("--dirs", ".");
188773
+ }
188774
+ log(`Executing: reshape ${fullArgs.join(" ")}`);
188775
+ log(` Working directory: ${migrationsDir}`);
188776
+ try {
188777
+ const result = spawnSync(reshapeBinaryPath, fullArgs, {
188778
+ cwd: migrationsDir,
188779
+ env: {
188780
+ ...process.env,
188781
+ DB_URL: databaseUrl
188782
+ },
188783
+ encoding: "utf-8",
188784
+ stdio: ["pipe", "pipe", "pipe"]
188785
+ });
188786
+ const stdout = result.stdout || "";
188787
+ const stderr = result.stderr || "";
188788
+ const output = stdout + stderr;
188789
+ if (result.status !== 0) {
188790
+ log(`Reshape command failed with exit code ${result.status}`);
188791
+ if (stdout.trim()) log(` stdout: ${stdout.trim()}`);
188792
+ if (stderr.trim()) log(` stderr: ${stderr.trim()}`);
188793
+ return { success: false, output };
188794
+ }
188795
+ log(`Reshape command succeeded`);
188796
+ if (stdout.trim()) log(` stdout: ${stdout.trim()}`);
188797
+ if (stderr.trim()) log(` stderr: ${stderr.trim()}`);
188798
+ return { success: true, output };
188799
+ } catch (err) {
188800
+ const message = err instanceof Error ? err.message : String(err);
188801
+ log(`Reshape command threw exception: ${message}`);
188802
+ return { success: false, output: message };
188803
+ }
188804
+ }
188805
+ function makeReadOnly(filePath, log) {
188806
+ try {
188807
+ fs18.chmodSync(filePath, 292);
188808
+ log(`Set file permissions to read-only (444): ${filePath}`);
188809
+ } catch (err) {
188810
+ const message = err instanceof Error ? err.message : String(err);
188811
+ log(`Warning: Failed to make file read-only: ${filePath} - ${message}`);
188812
+ }
188813
+ }
188814
+ function createReshapeWatcher(options2) {
188815
+ const { databaseUrl, migrationsDir, reshapeBinaryPath, onSearchPathChanged, onError, log } = options2;
188816
+ let watcher = null;
188817
+ let currentMigrationFiles = [];
188818
+ let startedMigration = null;
188819
+ log(`Creating Reshape watcher`);
188820
+ log(` Migrations directory: ${migrationsDir}`);
188821
+ log(` Database URL: ${databaseUrl.replace(/:[^:@]+@/, ":***@")}`);
188822
+ const initialize = async () => {
188823
+ log(`Initializing Reshape migrations...`);
188824
+ currentMigrationFiles = getMigrationFiles(migrationsDir, log);
188825
+ if (currentMigrationFiles.length === 0) {
188826
+ log("No migration files found - skipping Reshape initialization");
188827
+ return null;
188828
+ }
188829
+ log(`Processing ${currentMigrationFiles.length} migration file(s)`);
188830
+ if (currentMigrationFiles.length === 1) {
188831
+ const migrationFile = currentMigrationFiles[0];
188832
+ const migrationName = getMigrationName(migrationFile);
188833
+ log(`Single migration detected: ${migrationFile}`);
188834
+ log(`Starting migration "${migrationName}" (will not be completed, allowing iteration)`);
188835
+ const result = runReshape(["start"], databaseUrl, migrationsDir, reshapeBinaryPath, log);
188836
+ if (!result.success) {
188837
+ log(`ERROR: Failed to start migration "${migrationName}"`);
188838
+ onError(new Error(`Failed to start migration: ${result.output}`));
188839
+ return null;
188840
+ }
188841
+ startedMigration = migrationFile;
188842
+ log(`Migration "${migrationName}" started successfully`);
188843
+ log(` Started migration file: ${startedMigration}`);
188844
+ } else {
188845
+ const secondToLast = currentMigrationFiles[currentMigrationFiles.length - 2];
188846
+ const secondToLastName = getMigrationName(secondToLast);
188847
+ const lastMigration = currentMigrationFiles[currentMigrationFiles.length - 1];
188848
+ const lastMigrationName = getMigrationName(lastMigration);
188849
+ log(`Multiple migrations detected (${currentMigrationFiles.length} total)`);
188850
+ log(` Migrations to complete: ${currentMigrationFiles.slice(0, -1).join(", ")}`);
188851
+ log(` Migration to start (latest): ${lastMigration}`);
188852
+ log(`Completing all migrations up to and including "${secondToLastName}"...`);
188853
+ const completeResult = runReshape(
188854
+ ["start", "--complete", "--migration", secondToLastName],
188855
+ databaseUrl,
188856
+ migrationsDir,
188857
+ reshapeBinaryPath,
188858
+ log
188859
+ );
188860
+ if (!completeResult.success) {
188861
+ log(`ERROR: Failed to complete migrations up to "${secondToLastName}"`);
188862
+ onError(new Error(`Failed to complete migrations: ${completeResult.output}`));
188863
+ return null;
188864
+ }
188865
+ log(`Successfully completed ${currentMigrationFiles.length - 1} migration(s)`);
188866
+ log(`Making completed migration files read-only...`);
188867
+ for (let i = 0; i < currentMigrationFiles.length - 1; i++) {
188868
+ const filePath = path15.join(migrationsDir, currentMigrationFiles[i]);
188869
+ makeReadOnly(filePath, log);
188870
+ }
188871
+ log(`Starting latest migration "${lastMigrationName}" (will not be completed, allowing iteration)`);
188872
+ const startResult = runReshape(["start"], databaseUrl, migrationsDir, reshapeBinaryPath, log);
188873
+ if (!startResult.success) {
188874
+ log(`ERROR: Failed to start migration "${lastMigrationName}"`);
188875
+ onError(new Error(`Failed to start last migration: ${startResult.output}`));
188876
+ return null;
188877
+ }
188878
+ startedMigration = lastMigration;
188879
+ log(`Migration "${lastMigrationName}" started successfully`);
188880
+ log(` Started migration file: ${startedMigration}`);
188881
+ }
188882
+ const searchPath = startedMigration ? getSchemaName(startedMigration) : null;
188883
+ if (searchPath) {
188884
+ log(`Search path (schema name): ${searchPath}`);
188885
+ } else {
188886
+ log(`No search_path (no migration started)`);
188887
+ }
188888
+ log(`Reshape initialization complete`);
188889
+ return searchPath;
188890
+ };
188891
+ const startWatching = () => {
188892
+ log(`Starting file watcher for migrations directory...`);
188893
+ if (!fs18.existsSync(migrationsDir)) {
188894
+ log(`Migrations directory does not exist, creating: ${migrationsDir}`);
188895
+ fs18.mkdirSync(migrationsDir, { recursive: true });
188896
+ }
188897
+ log(`Watching directory: ${migrationsDir}`);
188898
+ watcher = chokidar_default.watch(migrationsDir, {
188899
+ persistent: true,
188900
+ ignoreInitial: true,
188901
+ depth: 0,
188902
+ awaitWriteFinish: {
188903
+ stabilityThreshold: 200,
188904
+ pollInterval: 50
188905
+ }
188906
+ });
188907
+ watcher.on("change", (filePath) => {
188908
+ const filename = path15.basename(filePath);
188909
+ if (!filename.endsWith(".toml")) return;
188910
+ log(`File change detected: ${filename}`);
188911
+ log(` Full path: ${filePath}`);
188912
+ log(` Currently started migration: ${startedMigration || "(none)"}`);
188913
+ if (filename === startedMigration) {
188914
+ log(`Changed file is the currently started migration - will abort and restart`);
188915
+ log(`Aborting current migration...`);
188916
+ const abortResult = runReshape(["abort"], databaseUrl, migrationsDir, reshapeBinaryPath, log);
188917
+ if (!abortResult.success) {
188918
+ log(`ERROR: Failed to abort migration`);
188919
+ onError(new Error(`Failed to abort migration: ${abortResult.output}`));
188920
+ return;
188921
+ }
188922
+ log(`Migration aborted successfully`);
188923
+ log(`Restarting migration...`);
188924
+ const startResult = runReshape(["start"], databaseUrl, migrationsDir, reshapeBinaryPath, log);
188925
+ if (!startResult.success) {
188926
+ log(`ERROR: Failed to restart migration`);
188927
+ onError(new Error(`Failed to restart migration: ${startResult.output}`));
188928
+ return;
188929
+ }
188930
+ log(`Migration restarted successfully`);
188931
+ log(`Note: Services do NOT need to restart (search_path unchanged)`);
188932
+ } else {
188933
+ log(`Warning: Completed migration "${filename}" was modified`);
188934
+ log(` This file should be read-only. Changes may not take effect.`);
188935
+ log(` To modify this migration, you may need to reset and re-run all migrations.`);
188936
+ }
188937
+ });
188938
+ watcher.on("add", (filePath) => {
188939
+ const filename = path15.basename(filePath);
188940
+ if (!filename.endsWith(".toml")) return;
188941
+ log(`New file detected: ${filename}`);
188942
+ log(` Full path: ${filePath}`);
188943
+ const newMigrationFiles = getMigrationFiles(migrationsDir, log);
188944
+ log(`Updated migration list: ${newMigrationFiles.join(", ")}`);
188945
+ const isNewest = newMigrationFiles[newMigrationFiles.length - 1] === filename;
188946
+ log(`Is this the newest migration? ${isNewest}`);
188947
+ if (!isNewest) {
188948
+ log(`Warning: Added migration "${filename}" is not the latest in lexical order`);
188949
+ log(` Latest migration: ${newMigrationFiles[newMigrationFiles.length - 1]}`);
188950
+ log(` Manual intervention may be needed to ensure correct migration order`);
188951
+ return;
188952
+ }
188953
+ if (startedMigration) {
188954
+ const previousMigrationName = getMigrationName(startedMigration);
188955
+ log(`Completing previous migration "${previousMigrationName}"...`);
188956
+ const completeResult = runReshape(["complete"], databaseUrl, migrationsDir, reshapeBinaryPath, log);
188957
+ if (!completeResult.success) {
188958
+ log(`ERROR: Failed to complete previous migration`);
188959
+ onError(new Error(`Failed to complete previous migration: ${completeResult.output}`));
188960
+ return;
188961
+ }
188962
+ log(`Previous migration completed successfully`);
188963
+ const completedPath = path15.join(migrationsDir, startedMigration);
188964
+ makeReadOnly(completedPath, log);
188965
+ } else {
188966
+ log(`No previous migration was started`);
188967
+ }
188968
+ const newMigrationName = getMigrationName(filename);
188969
+ log(`Starting new migration "${newMigrationName}"...`);
188970
+ const startResult = runReshape(["start"], databaseUrl, migrationsDir, reshapeBinaryPath, log);
188971
+ if (!startResult.success) {
188972
+ log(`ERROR: Failed to start new migration`);
188973
+ onError(new Error(`Failed to start new migration: ${startResult.output}`));
188974
+ return;
188975
+ }
188976
+ const previousStarted = startedMigration;
188977
+ startedMigration = filename;
188978
+ currentMigrationFiles = newMigrationFiles;
188979
+ log(`New migration started successfully`);
188980
+ log(` Previous started migration: ${previousStarted || "(none)"}`);
188981
+ log(` New started migration: ${startedMigration}`);
188982
+ const newSearchPath = getSchemaName(filename);
188983
+ log(`New search_path: ${newSearchPath}`);
188984
+ log(`Notifying services to restart with new search_path...`);
188985
+ onSearchPathChanged(newSearchPath);
188986
+ });
188987
+ watcher.on("unlink", (filePath) => {
188988
+ const filename = path15.basename(filePath);
188989
+ if (!filename.endsWith(".toml")) return;
188990
+ log(`File deleted: ${filename}`);
188991
+ log(` Full path: ${filePath}`);
188992
+ log(` Currently started migration: ${startedMigration || "(none)"}`);
188993
+ if (filename === startedMigration) {
188994
+ log(`Warning: The currently started migration was deleted!`);
188995
+ log(` Manual intervention is required to recover.`);
188996
+ } else {
188997
+ log(`Warning: A migration file was deleted. This may cause issues.`);
188998
+ }
188999
+ log(` Automatic recovery is not supported - manual intervention may be needed.`);
189000
+ });
189001
+ watcher.on("error", (error) => {
189002
+ const err = error instanceof Error ? error : new Error(String(error));
189003
+ log(`File watcher error: ${err.message}`);
189004
+ onError(err);
189005
+ });
189006
+ watcher.on("ready", () => {
189007
+ log(`File watcher is ready and monitoring for changes`);
189008
+ });
189009
+ log(`File watcher started`);
189010
+ };
189011
+ const close = async () => {
189012
+ log(`Closing Reshape watcher...`);
189013
+ if (watcher) {
189014
+ await watcher.close();
189015
+ watcher = null;
189016
+ log(`File watcher closed`);
189017
+ } else {
189018
+ log(`No file watcher was active`);
189019
+ }
189020
+ log(`Reshape watcher closed`);
189021
+ };
189022
+ return {
189023
+ initialize,
189024
+ startWatching,
189025
+ close
189026
+ };
189027
+ }
189028
+
188572
189029
  // src/lib/dev/resource-starter.ts
188573
189030
  function findRequiredResources(service) {
188574
189031
  const required = { postgres: [], redis: [], storage: [] };
@@ -188627,11 +189084,14 @@ async function startResources(options2) {
188627
189084
  const instance = await startPostgres(pg, port, dataDir, (progress) => {
188628
189085
  callbacks.onResourceProgress?.(pg.name, progress);
188629
189086
  });
189087
+ if (pg.reshape?.enabled) {
189088
+ instance.reshapeEnabled = true;
189089
+ }
188630
189090
  resources.set(pg.name, instance);
188631
189091
  startedResources.push(instance);
188632
189092
  callbacks.onResourceReady?.(pg.name, instance);
188633
189093
  log(`Postgres "${pg.name}" ready`);
188634
- await stateManager.registerDatabase(pg.name, {
189094
+ const dbState = {
188635
189095
  engine: "postgres",
188636
189096
  port: instance.port,
188637
189097
  host: instance.host,
@@ -188639,7 +189099,14 @@ async function startResources(options2) {
188639
189099
  password: instance.password,
188640
189100
  dbName: instance.dbName,
188641
189101
  url: instance.url
188642
- });
189102
+ };
189103
+ if (instance.reshapeEnabled) {
189104
+ dbState.reshapeEnabled = true;
189105
+ if (instance.reshapeSearchPath) {
189106
+ dbState.reshapeSearchPath = instance.reshapeSearchPath;
189107
+ }
189108
+ }
189109
+ await stateManager.registerDatabase(pg.name, dbState);
188643
189110
  }
188644
189111
  for (const redis of redisConfigs) {
188645
189112
  if (signal?.cancelled) {
@@ -188741,24 +189208,24 @@ function watchConfigFile(configPath, debounceMs, onChange) {
188741
189208
  }
188742
189209
 
188743
189210
  // src/lib/dev/subdomain-generator.ts
188744
- import * as fs18 from "fs";
188745
- import * as path15 from "path";
189211
+ import * as fs19 from "fs";
189212
+ import * as path16 from "path";
188746
189213
  import { generateSlug } from "random-word-slugs";
188747
189214
  var StableSubdomainAllocator = class {
188748
189215
  tunnelsDir;
188749
189216
  tunnelsFilePath;
188750
189217
  baseSlug = null;
188751
189218
  constructor(projectRoot, key = "default") {
188752
- this.tunnelsDir = path15.join(projectRoot, ".specific", "keys", key);
188753
- this.tunnelsFilePath = path15.join(this.tunnelsDir, "tunnels.json");
189219
+ this.tunnelsDir = path16.join(projectRoot, ".specific", "keys", key);
189220
+ this.tunnelsFilePath = path16.join(this.tunnelsDir, "tunnels.json");
188754
189221
  this.loadTunnels();
188755
189222
  }
188756
189223
  loadTunnels() {
188757
- if (!fs18.existsSync(this.tunnelsFilePath)) {
189224
+ if (!fs19.existsSync(this.tunnelsFilePath)) {
188758
189225
  return;
188759
189226
  }
188760
189227
  try {
188761
- const content = fs18.readFileSync(this.tunnelsFilePath, "utf-8");
189228
+ const content = fs19.readFileSync(this.tunnelsFilePath, "utf-8");
188762
189229
  const data = JSON.parse(content);
188763
189230
  if (data.version === 1 && data.baseSlug) {
188764
189231
  this.baseSlug = data.baseSlug;
@@ -188768,14 +189235,14 @@ var StableSubdomainAllocator = class {
188768
189235
  }
188769
189236
  }
188770
189237
  saveTunnels() {
188771
- if (!fs18.existsSync(this.tunnelsDir)) {
188772
- fs18.mkdirSync(this.tunnelsDir, { recursive: true });
189238
+ if (!fs19.existsSync(this.tunnelsDir)) {
189239
+ fs19.mkdirSync(this.tunnelsDir, { recursive: true });
188773
189240
  }
188774
189241
  const data = {
188775
189242
  version: 1,
188776
189243
  baseSlug: this.baseSlug
188777
189244
  };
188778
- fs18.writeFileSync(this.tunnelsFilePath, JSON.stringify(data, null, 2));
189245
+ fs19.writeFileSync(this.tunnelsFilePath, JSON.stringify(data, null, 2));
188779
189246
  }
188780
189247
  generateBaseSlug() {
188781
189248
  return generateSlug(2, {
@@ -188838,8 +189305,8 @@ async function startTunnel(serviceName, endpointName, port, subdomain, callbacks
188838
189305
  }
188839
189306
 
188840
189307
  // src/lib/dev/proxy-registry.ts
188841
- import * as fs19 from "fs";
188842
- import * as path16 from "path";
189308
+ import * as fs20 from "fs";
189309
+ import * as path17 from "path";
188843
189310
  import * as os7 from "os";
188844
189311
  import * as net4 from "net";
188845
189312
  var ProxyRegistryManager = class {
@@ -188850,14 +189317,14 @@ var ProxyRegistryManager = class {
188850
189317
  isOwner = false;
188851
189318
  registryWatcher = null;
188852
189319
  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");
189320
+ this.proxyDir = path17.join(os7.homedir(), ".specific", "proxy");
189321
+ this.ownerPath = path17.join(this.proxyDir, "owner.json");
189322
+ this.registryPath = path17.join(this.proxyDir, "registry.json");
189323
+ this.lockPath = path17.join(this.proxyDir, "registry.lock");
188857
189324
  }
188858
189325
  ensureProxyDir() {
188859
- if (!fs19.existsSync(this.proxyDir)) {
188860
- fs19.mkdirSync(this.proxyDir, { recursive: true });
189326
+ if (!fs20.existsSync(this.proxyDir)) {
189327
+ fs20.mkdirSync(this.proxyDir, { recursive: true });
188861
189328
  }
188862
189329
  }
188863
189330
  isProcessRunning(pid) {
@@ -188874,7 +189341,7 @@ var ProxyRegistryManager = class {
188874
189341
  * This catches cases where the owner process is alive but the proxy has crashed.
188875
189342
  */
188876
189343
  isProxyListening(port, timeoutMs = 1e3) {
188877
- return new Promise((resolve7) => {
189344
+ return new Promise((resolve10) => {
188878
189345
  const socket = new net4.Socket();
188879
189346
  let resolved = false;
188880
189347
  const cleanup = () => {
@@ -188886,15 +189353,15 @@ var ProxyRegistryManager = class {
188886
189353
  socket.setTimeout(timeoutMs);
188887
189354
  socket.on("connect", () => {
188888
189355
  cleanup();
188889
- resolve7(true);
189356
+ resolve10(true);
188890
189357
  });
188891
189358
  socket.on("timeout", () => {
188892
189359
  cleanup();
188893
- resolve7(false);
189360
+ resolve10(false);
188894
189361
  });
188895
189362
  socket.on("error", () => {
188896
189363
  cleanup();
188897
- resolve7(false);
189364
+ resolve10(false);
188898
189365
  });
188899
189366
  socket.connect(port, "127.0.0.1");
188900
189367
  });
@@ -188914,15 +189381,15 @@ var ProxyRegistryManager = class {
188914
189381
  const startTime = Date.now();
188915
189382
  while (Date.now() - startTime < timeoutMs) {
188916
189383
  try {
188917
- const fd = fs19.openSync(
189384
+ const fd = fs20.openSync(
188918
189385
  this.lockPath,
188919
- fs19.constants.O_CREAT | fs19.constants.O_EXCL | fs19.constants.O_WRONLY
189386
+ fs20.constants.O_CREAT | fs20.constants.O_EXCL | fs20.constants.O_WRONLY
188920
189387
  );
188921
- fs19.writeSync(fd, String(process.pid));
188922
- fs19.closeSync(fd);
189388
+ fs20.writeSync(fd, String(process.pid));
189389
+ fs20.closeSync(fd);
188923
189390
  return () => {
188924
189391
  try {
188925
- fs19.unlinkSync(this.lockPath);
189392
+ fs20.unlinkSync(this.lockPath);
188926
189393
  } catch {
188927
189394
  }
188928
189395
  };
@@ -188931,21 +189398,21 @@ var ProxyRegistryManager = class {
188931
189398
  if (err.code === "EEXIST") {
188932
189399
  try {
188933
189400
  const lockPid = parseInt(
188934
- fs19.readFileSync(this.lockPath, "utf-8").trim(),
189401
+ fs20.readFileSync(this.lockPath, "utf-8").trim(),
188935
189402
  10
188936
189403
  );
188937
189404
  if (!this.isProcessRunning(lockPid)) {
188938
- fs19.unlinkSync(this.lockPath);
189405
+ fs20.unlinkSync(this.lockPath);
188939
189406
  continue;
188940
189407
  }
188941
189408
  } catch {
188942
189409
  try {
188943
- fs19.unlinkSync(this.lockPath);
189410
+ fs20.unlinkSync(this.lockPath);
188944
189411
  } catch {
188945
189412
  }
188946
189413
  continue;
188947
189414
  }
188948
- await new Promise((resolve7) => setTimeout(resolve7, 100));
189415
+ await new Promise((resolve10) => setTimeout(resolve10, 100));
188949
189416
  } else {
188950
189417
  throw e;
188951
189418
  }
@@ -188960,8 +189427,8 @@ var ProxyRegistryManager = class {
188960
189427
  async claimProxyOwnership(key) {
188961
189428
  const releaseLock = await this.acquireLock();
188962
189429
  try {
188963
- if (fs19.existsSync(this.ownerPath)) {
188964
- const content = fs19.readFileSync(this.ownerPath, "utf-8");
189430
+ if (fs20.existsSync(this.ownerPath)) {
189431
+ const content = fs20.readFileSync(this.ownerPath, "utf-8");
188965
189432
  const ownerFile2 = JSON.parse(content);
188966
189433
  if (await this.isProxyOwnerHealthy(ownerFile2.owner.pid)) {
188967
189434
  return false;
@@ -188991,11 +189458,11 @@ var ProxyRegistryManager = class {
188991
189458
  }
188992
189459
  const releaseLock = await this.acquireLock();
188993
189460
  try {
188994
- if (fs19.existsSync(this.ownerPath)) {
188995
- const content = fs19.readFileSync(this.ownerPath, "utf-8");
189461
+ if (fs20.existsSync(this.ownerPath)) {
189462
+ const content = fs20.readFileSync(this.ownerPath, "utf-8");
188996
189463
  const ownerFile = JSON.parse(content);
188997
189464
  if (ownerFile.owner.pid === process.pid) {
188998
- fs19.unlinkSync(this.ownerPath);
189465
+ fs20.unlinkSync(this.ownerPath);
188999
189466
  }
189000
189467
  }
189001
189468
  this.isOwner = false;
@@ -189007,12 +189474,12 @@ var ProxyRegistryManager = class {
189007
189474
  * Get the current proxy owner.
189008
189475
  */
189009
189476
  async getProxyOwner() {
189010
- if (!fs19.existsSync(this.ownerPath)) {
189477
+ if (!fs20.existsSync(this.ownerPath)) {
189011
189478
  return null;
189012
189479
  }
189013
189480
  const releaseLock = await this.acquireLock();
189014
189481
  try {
189015
- const content = fs19.readFileSync(this.ownerPath, "utf-8");
189482
+ const content = fs20.readFileSync(this.ownerPath, "utf-8");
189016
189483
  const ownerFile = JSON.parse(content);
189017
189484
  if (!await this.isProxyOwnerHealthy(ownerFile.owner.pid)) {
189018
189485
  return null;
@@ -189106,7 +189573,7 @@ var ProxyRegistryManager = class {
189106
189573
  */
189107
189574
  watchRegistry(onChange) {
189108
189575
  this.ensureProxyDir();
189109
- if (!fs19.existsSync(this.registryPath)) {
189576
+ if (!fs20.existsSync(this.registryPath)) {
189110
189577
  const emptyRegistry = {
189111
189578
  version: 1,
189112
189579
  keys: {},
@@ -189150,13 +189617,13 @@ var ProxyRegistryManager = class {
189150
189617
  async attemptElection(key) {
189151
189618
  const releaseLock = await this.acquireLock();
189152
189619
  try {
189153
- if (fs19.existsSync(this.ownerPath)) {
189154
- const content = fs19.readFileSync(this.ownerPath, "utf-8");
189620
+ if (fs20.existsSync(this.ownerPath)) {
189621
+ const content = fs20.readFileSync(this.ownerPath, "utf-8");
189155
189622
  const ownerFile2 = JSON.parse(content);
189156
189623
  if (await this.isProxyOwnerHealthy(ownerFile2.owner.pid)) {
189157
189624
  return false;
189158
189625
  }
189159
- fs19.unlinkSync(this.ownerPath);
189626
+ fs20.unlinkSync(this.ownerPath);
189160
189627
  }
189161
189628
  const ownerFile = {
189162
189629
  version: 1,
@@ -189174,7 +189641,7 @@ var ProxyRegistryManager = class {
189174
189641
  }
189175
189642
  }
189176
189643
  readRegistry() {
189177
- if (!fs19.existsSync(this.registryPath)) {
189644
+ if (!fs20.existsSync(this.registryPath)) {
189178
189645
  return {
189179
189646
  version: 1,
189180
189647
  keys: {},
@@ -189182,7 +189649,7 @@ var ProxyRegistryManager = class {
189182
189649
  };
189183
189650
  }
189184
189651
  try {
189185
- const content = fs19.readFileSync(this.registryPath, "utf-8");
189652
+ const content = fs20.readFileSync(this.registryPath, "utf-8");
189186
189653
  return JSON.parse(content);
189187
189654
  } catch {
189188
189655
  return {
@@ -189195,8 +189662,8 @@ var ProxyRegistryManager = class {
189195
189662
  writeFileAtomic(filePath, data) {
189196
189663
  this.ensureProxyDir();
189197
189664
  const tmpPath = filePath + ".tmp";
189198
- fs19.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
189199
- fs19.renameSync(tmpPath, filePath);
189665
+ fs20.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
189666
+ fs20.renameSync(tmpPath, filePath);
189200
189667
  }
189201
189668
  };
189202
189669
 
@@ -189341,6 +189808,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189341
189808
  const proxyRegistryRef = useRef(null);
189342
189809
  const electricInstancesRef = useRef([]);
189343
189810
  const drizzleGatewayRef = useRef(null);
189811
+ const reshapeWatchersRef = useRef([]);
189344
189812
  const registryWatcherCleanupRef = useRef(null);
189345
189813
  const electionIntervalRef = useRef(null);
189346
189814
  const tunnelsRef = useRef([]);
@@ -189348,6 +189816,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189348
189816
  const adminServerRef = useRef(null);
189349
189817
  const servicesRef = useRef([]);
189350
189818
  const resourcesRef = useRef(/* @__PURE__ */ new Map());
189819
+ const restartServicesRef = useRef(null);
189351
189820
  const [reloadTrigger, setReloadTrigger] = useState5(0);
189352
189821
  const [readyToStart, setReadyToStart] = useState5(() => caFilesExist());
189353
189822
  const shutdown2 = async () => {
@@ -189374,6 +189843,8 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189374
189843
  ...servicesRef.current.map((service) => service.stop()),
189375
189844
  // Stop all Electric instances
189376
189845
  ...electricInstancesRef.current.map((electric) => electric.stop()),
189846
+ // Stop all Reshape watchers
189847
+ ...reshapeWatchersRef.current.map((watcher) => watcher.close()),
189377
189848
  // Stop Drizzle Gateway
189378
189849
  drizzleGatewayRef.current?.stop(),
189379
189850
  // Stop all resources
@@ -189381,6 +189852,8 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189381
189852
  // Stop all tunnels
189382
189853
  ...tunnelsRef.current.map((tunnel) => tunnel.stop())
189383
189854
  ]);
189855
+ reshapeWatchersRef.current = [];
189856
+ restartServicesRef.current = null;
189384
189857
  if (proxyRegistryRef.current) {
189385
189858
  await proxyRegistryRef.current.unregisterServices(instanceKey);
189386
189859
  await proxyRegistryRef.current.releaseProxyOwnership();
@@ -189413,6 +189886,8 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189413
189886
  ...servicesRef.current.map((service) => service.stop()),
189414
189887
  // Stop all Electric instances
189415
189888
  ...electricInstancesRef.current.map((electric) => electric.stop()),
189889
+ // Stop all Reshape watchers
189890
+ ...reshapeWatchersRef.current.map((watcher) => watcher.close()),
189416
189891
  // Stop Drizzle Gateway
189417
189892
  drizzleGatewayRef.current?.stop(),
189418
189893
  // Stop all resources
@@ -189421,9 +189896,11 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189421
189896
  ...tunnelsRef.current.map((tunnel) => tunnel.stop())
189422
189897
  ]);
189423
189898
  if (tunnelsRef.current.length > 0) {
189424
- await new Promise((resolve7) => setTimeout(resolve7, 1500));
189899
+ await new Promise((resolve10) => setTimeout(resolve10, 1500));
189425
189900
  }
189426
189901
  electricInstancesRef.current = [];
189902
+ reshapeWatchersRef.current = [];
189903
+ restartServicesRef.current = null;
189427
189904
  drizzleGatewayRef.current = null;
189428
189905
  proxyRef.current = null;
189429
189906
  adminServerRef.current = null;
@@ -189488,10 +189965,10 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189488
189965
  }, [state.status]);
189489
189966
  useEffect3(() => {
189490
189967
  if (state.status !== "running") return;
189491
- const configPath = path17.join(process.cwd(), "specific.hcl");
189968
+ const configPath = path18.join(process.cwd(), "specific.hcl");
189492
189969
  const watcher = watchConfigFile(configPath, 1e3, () => {
189493
189970
  try {
189494
- const hcl = fs20.readFileSync(configPath, "utf-8");
189971
+ const hcl = fs21.readFileSync(configPath, "utf-8");
189495
189972
  parseConfig(hcl).then(() => {
189496
189973
  triggerReload();
189497
189974
  }).catch((err) => {
@@ -189616,8 +190093,8 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189616
190093
  }));
189617
190094
  return;
189618
190095
  }
189619
- const configPath = path17.join(process.cwd(), "specific.hcl");
189620
- if (!fs20.existsSync(configPath)) {
190096
+ const configPath = path18.join(process.cwd(), "specific.hcl");
190097
+ if (!fs21.existsSync(configPath)) {
189621
190098
  writeLog("system", "Waiting for specific.hcl to appear");
189622
190099
  setState((s) => ({
189623
190100
  ...s,
@@ -189636,7 +190113,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189636
190113
  }
189637
190114
  let config2;
189638
190115
  try {
189639
- const hcl = fs20.readFileSync(configPath, "utf-8");
190116
+ const hcl = fs21.readFileSync(configPath, "utf-8");
189640
190117
  config2 = await parseConfig(hcl);
189641
190118
  } catch (err) {
189642
190119
  setState((s) => ({
@@ -189742,7 +190219,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189742
190219
  const drizzleGateway = await startDrizzleGateway(
189743
190220
  postgresResources,
189744
190221
  drizzlePort,
189745
- path17.join(process.cwd(), ".specific", "keys", instanceKey)
190222
+ path18.join(process.cwd(), ".specific", "keys", instanceKey)
189746
190223
  );
189747
190224
  startedDrizzleGateway = drizzleGateway;
189748
190225
  drizzleGatewayRef.current = drizzleGateway;
@@ -189755,6 +190232,59 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189755
190232
  }
189756
190233
  }
189757
190234
  if (cancelled) return;
190235
+ const startedReshapeWatchers = [];
190236
+ const hasReshapeEnabled = config2.postgres.some((pg) => pg.reshape?.enabled);
190237
+ const reshapeBin = hasReshapeEnabled ? await ensureBinary(reshapeBinary) : void 0;
190238
+ for (const pg of config2.postgres) {
190239
+ if (pg.reshape?.enabled) {
190240
+ const resource = resources2.get(pg.name);
190241
+ if (!resource) continue;
190242
+ const migrationsDir = path18.resolve(
190243
+ process.cwd(),
190244
+ pg.reshape.migrations_dir ?? "migrations"
190245
+ );
190246
+ writeLog("system", `Initializing Reshape migrations for "${pg.name}" from ${migrationsDir}`);
190247
+ const watcher = createReshapeWatcher({
190248
+ databaseUrl: resource.url,
190249
+ migrationsDir,
190250
+ reshapeBinaryPath: reshapeBin.executables["reshape"],
190251
+ onSearchPathChanged: async (newSearchPath) => {
190252
+ resource.reshapeSearchPath = newSearchPath ?? void 0;
190253
+ if (newSearchPath) {
190254
+ await stateManager.updateDatabase(pg.name, {
190255
+ reshapeSearchPath: newSearchPath
190256
+ });
190257
+ }
190258
+ writeLog("system", `Reshape search_path changed for "${pg.name}": ${newSearchPath}`);
190259
+ if (restartServicesRef.current) {
190260
+ await restartServicesRef.current();
190261
+ }
190262
+ },
190263
+ onError: (err) => {
190264
+ writeLog("system:error", `Reshape error for "${pg.name}": ${err.message}`);
190265
+ },
190266
+ log: (msg) => writeLog("system", `[reshape:${pg.name}] ${msg}`)
190267
+ });
190268
+ try {
190269
+ const initialSearchPath = await watcher.initialize();
190270
+ resource.reshapeSearchPath = initialSearchPath ?? void 0;
190271
+ if (initialSearchPath) {
190272
+ await stateManager.updateDatabase(pg.name, {
190273
+ reshapeSearchPath: initialSearchPath
190274
+ });
190275
+ writeLog("system", `Reshape search_path for "${pg.name}": ${initialSearchPath}`);
190276
+ }
190277
+ startedReshapeWatchers.push(watcher);
190278
+ } catch (err) {
190279
+ writeLog(
190280
+ "system:warning",
190281
+ `Failed to initialize Reshape for "${pg.name}": ${err instanceof Error ? err.message : String(err)}`
190282
+ );
190283
+ }
190284
+ }
190285
+ }
190286
+ reshapeWatchersRef.current = startedReshapeWatchers;
190287
+ if (cancelled) return;
189758
190288
  const secrets = await prepareSecrets(config2.secrets, true);
189759
190289
  const configs = await prepareConfigs(config2.configs, void 0, true);
189760
190290
  const usedSecrets = findUsedSecrets(config2.services, true);
@@ -189876,6 +190406,14 @@ Add them to the config block in specific.local`);
189876
190406
  }
189877
190407
  }
189878
190408
  const services2 = [];
190409
+ function resolveServiceCwd(service) {
190410
+ if (service.root) return path18.resolve(process.cwd(), service.root);
190411
+ if (service.build) {
190412
+ const build = config2.builds.find((b) => b.name === service.build.name);
190413
+ if (build?.root) return path18.resolve(process.cwd(), build.root);
190414
+ }
190415
+ return process.cwd();
190416
+ }
189879
190417
  for (const service of config2.services) {
189880
190418
  if (cancelled) break;
189881
190419
  if (service.serve) {
@@ -189899,7 +190437,8 @@ Add them to the config block in specific.local`);
189899
190437
  endpointPorts,
189900
190438
  serviceEndpoints,
189901
190439
  (line) => addLog(line, colorMap),
189902
- publicUrls
190440
+ publicUrls,
190441
+ resolveServiceCwd(service)
189903
190442
  );
189904
190443
  services2.push(running);
189905
190444
  startedServices.push(running);
@@ -189927,6 +190466,42 @@ Add them to the config block in specific.local`);
189927
190466
  }
189928
190467
  }
189929
190468
  if (cancelled) return;
190469
+ restartServicesRef.current = async () => {
190470
+ if (shuttingDown.current) return;
190471
+ writeLog("system", "Restarting services (migration changed)");
190472
+ await Promise.all(servicesRef.current.map((s) => s.stop()));
190473
+ const newServices = [];
190474
+ for (const service of config2.services) {
190475
+ if (service.serve) continue;
190476
+ if (!service.command && !service.dev?.command) continue;
190477
+ try {
190478
+ const endpointPorts = serviceEndpointPorts.get(service.name) || /* @__PURE__ */ new Map();
190479
+ const running = startService(
190480
+ service,
190481
+ resources2,
190482
+ secrets,
190483
+ configs,
190484
+ endpointPorts,
190485
+ serviceEndpoints,
190486
+ (line) => addLog(line, colorMap),
190487
+ void 0,
190488
+ resolveServiceCwd(service)
190489
+ );
190490
+ newServices.push(running);
190491
+ } catch (err) {
190492
+ writeLog(
190493
+ "system:error",
190494
+ `Failed to restart service "${service.name}": ${err instanceof Error ? err.message : String(err)}`
190495
+ );
190496
+ }
190497
+ }
190498
+ servicesRef.current = newServices;
190499
+ setState((s) => ({ ...s, services: newServices }));
190500
+ writeLog("system", "Services restarted");
190501
+ };
190502
+ for (const watcher of reshapeWatchersRef.current) {
190503
+ watcher.startWatching();
190504
+ }
189930
190505
  if (tunnelEnabled) {
189931
190506
  writeLog("system", "Tunnel mode enabled, starting tunnels for public services");
189932
190507
  if (exposedServices.length === 0) {
@@ -190144,6 +190719,12 @@ Add them to the config block in specific.local`);
190144
190719
  electric.stop().catch(() => {
190145
190720
  });
190146
190721
  }
190722
+ for (const watcher of reshapeWatchersRef.current) {
190723
+ watcher.close().catch(() => {
190724
+ });
190725
+ }
190726
+ reshapeWatchersRef.current = [];
190727
+ restartServicesRef.current = null;
190147
190728
  if (startedDrizzleGateway) {
190148
190729
  startedDrizzleGateway.stop().catch(() => {
190149
190730
  });
@@ -190366,7 +190947,7 @@ function devCommand(instanceKey, tunnelEnabled = false) {
190366
190947
 
190367
190948
  // src/lib/dev/git-worktree.ts
190368
190949
  import { execSync as execSync2 } from "child_process";
190369
- import * as path18 from "path";
190950
+ import * as path19 from "path";
190370
190951
  function isInWorktree() {
190371
190952
  try {
190372
190953
  const commonDir = execSync2("git rev-parse --git-common-dir", {
@@ -190377,8 +190958,8 @@ function isInWorktree() {
190377
190958
  encoding: "utf-8",
190378
190959
  stdio: ["pipe", "pipe", "pipe"]
190379
190960
  }).trim();
190380
- const resolvedCommonDir = path18.resolve(commonDir);
190381
- const resolvedGitDir = path18.resolve(gitDir);
190961
+ const resolvedCommonDir = path19.resolve(commonDir);
190962
+ const resolvedGitDir = path19.resolve(gitDir);
190382
190963
  return resolvedCommonDir !== resolvedGitDir;
190383
190964
  } catch {
190384
190965
  return false;
@@ -190393,7 +190974,7 @@ function getWorktreeName() {
190393
190974
  encoding: "utf-8",
190394
190975
  stdio: ["pipe", "pipe", "pipe"]
190395
190976
  }).trim();
190396
- return path18.basename(gitDir);
190977
+ return path19.basename(gitDir);
190397
190978
  } catch {
190398
190979
  return null;
190399
190980
  }
@@ -190408,35 +190989,36 @@ init_open();
190408
190989
  import React7, { useState as useState6, useEffect as useEffect4, useCallback } from "react";
190409
190990
  import { render as render5, Text as Text7, Box as Box7, useApp as useApp3, useInput as useInput5 } from "ink";
190410
190991
  import Spinner5 from "ink-spinner";
190411
- import * as fs22 from "fs";
190412
- import * as path20 from "path";
190992
+ import * as fs23 from "fs";
190993
+ import * as path21 from "path";
190413
190994
 
190414
190995
  // src/lib/deploy/build-tester.ts
190415
190996
  import { spawn as spawn5 } from "child_process";
190416
- import { existsSync as existsSync18 } from "fs";
190417
- import { join as join19 } from "path";
190997
+ import { existsSync as existsSync20 } from "fs";
190998
+ import { join as join21, resolve as resolve7 } from "path";
190418
190999
  function getDependencyInstallCommand(build, projectDir) {
190419
191000
  switch (build.base) {
190420
191001
  case "node":
190421
- if (existsSync18(join19(projectDir, "pnpm-lock.yaml"))) {
191002
+ if (existsSync20(join21(projectDir, "pnpm-lock.yaml"))) {
190422
191003
  return "pnpm install --frozen-lockfile";
190423
- } else if (existsSync18(join19(projectDir, "yarn.lock"))) {
191004
+ } else if (existsSync20(join21(projectDir, "yarn.lock"))) {
190424
191005
  return "yarn install --frozen-lockfile";
190425
- } else if (existsSync18(join19(projectDir, "package-lock.json"))) {
191006
+ } else if (existsSync20(join21(projectDir, "package-lock.json"))) {
190426
191007
  return "npm ci";
190427
- } else {
191008
+ } else if (existsSync20(join21(projectDir, "package.json"))) {
190428
191009
  return "npm install";
190429
191010
  }
191011
+ return null;
190430
191012
  case "python":
190431
- if (existsSync18(join19(projectDir, "poetry.lock"))) {
191013
+ if (existsSync20(join21(projectDir, "poetry.lock"))) {
190432
191014
  return "poetry install --no-interaction";
190433
- } else if (existsSync18(join19(projectDir, "Pipfile.lock"))) {
191015
+ } else if (existsSync20(join21(projectDir, "Pipfile.lock"))) {
190434
191016
  return "pipenv install --deploy";
190435
- } else if (existsSync18(join19(projectDir, "Pipfile"))) {
191017
+ } else if (existsSync20(join21(projectDir, "Pipfile"))) {
190436
191018
  return "pipenv install";
190437
- } else if (existsSync18(join19(projectDir, "pyproject.toml"))) {
191019
+ } else if (existsSync20(join21(projectDir, "pyproject.toml"))) {
190438
191020
  return "pip install .";
190439
- } else if (existsSync18(join19(projectDir, "requirements.txt"))) {
191021
+ } else if (existsSync20(join21(projectDir, "requirements.txt"))) {
190440
191022
  return "pip install -r requirements.txt";
190441
191023
  }
190442
191024
  return null;
@@ -190450,7 +191032,7 @@ function getDependencyInstallCommand(build, projectDir) {
190450
191032
  }
190451
191033
  }
190452
191034
  function runCommand2(command, projectDir, buildName) {
190453
- return new Promise((resolve7) => {
191035
+ return new Promise((resolve10) => {
190454
191036
  const stdout = [];
190455
191037
  const stderr = [];
190456
191038
  writeLog("build-test", `[${buildName}] Running: ${command}`);
@@ -190480,7 +191062,7 @@ function runCommand2(command, projectDir, buildName) {
190480
191062
  });
190481
191063
  child.on("error", (err) => {
190482
191064
  writeLog("build-test:error", `[${buildName}] Failed to start: ${err.message}`);
190483
- resolve7({
191065
+ resolve10({
190484
191066
  success: false,
190485
191067
  output: `Failed to start command: ${err.message}`
190486
191068
  });
@@ -190489,10 +191071,10 @@ function runCommand2(command, projectDir, buildName) {
190489
191071
  const output = [...stdout, ...stderr].join("");
190490
191072
  if (code === 0) {
190491
191073
  writeLog("build-test", `[${buildName}] Command succeeded (exit code 0)`);
190492
- resolve7({ success: true, output });
191074
+ resolve10({ success: true, output });
190493
191075
  } else {
190494
191076
  writeLog("build-test:error", `[${buildName}] Command failed with exit code ${code}`);
190495
- resolve7({
191077
+ resolve10({
190496
191078
  success: false,
190497
191079
  output: output || `Exit code: ${code}`
190498
191080
  });
@@ -190503,7 +191085,7 @@ function runCommand2(command, projectDir, buildName) {
190503
191085
  async function testBuild(build, projectDir) {
190504
191086
  const startTime = Date.now();
190505
191087
  const outputs = [];
190506
- const workDir = projectDir;
191088
+ const workDir = build.root ? resolve7(projectDir, build.root) : projectDir;
190507
191089
  writeLog("build-test", `Starting test for build "${build.name}" (base: ${build.base}, workDir: ${workDir})`);
190508
191090
  const depsCommand = getDependencyInstallCommand(build, workDir);
190509
191091
  if (depsCommand) {
@@ -190582,8 +191164,8 @@ async function testAllBuilds(builds, projectDir) {
190582
191164
 
190583
191165
  // src/lib/tarball/create.ts
190584
191166
  import { execSync as execSync3 } from "child_process";
190585
- import * as fs21 from "fs";
190586
- import * as path19 from "path";
191167
+ import * as fs22 from "fs";
191168
+ import * as path20 from "path";
190587
191169
  import { createTarPacker, createEntryItemGenerator } from "tar-vern";
190588
191170
  function isInsideGitRepository(dir) {
190589
191171
  try {
@@ -190640,10 +191222,10 @@ var EXCLUDED_DIRS = [
190640
191222
  ];
190641
191223
  async function collectPaths(baseDir, currentDir, exclude) {
190642
191224
  const results = [];
190643
- const entries = await fs21.promises.readdir(currentDir, { withFileTypes: true });
191225
+ const entries = await fs22.promises.readdir(currentDir, { withFileTypes: true });
190644
191226
  for (const entry of entries) {
190645
- const fullPath = path19.join(currentDir, entry.name);
190646
- const relativePath = path19.relative(baseDir, fullPath);
191227
+ const fullPath = path20.join(currentDir, entry.name);
191228
+ const relativePath = path20.relative(baseDir, fullPath);
190647
191229
  if (entry.isDirectory()) {
190648
191230
  if (!exclude.includes(entry.name)) {
190649
191231
  results.push(relativePath);
@@ -190658,8 +191240,8 @@ async function collectPaths(baseDir, currentDir, exclude) {
190658
191240
  }
190659
191241
  async function createTarArchive(projectDir) {
190660
191242
  writeLog("tarball", "Creating tarball using tar-vern (non-git project)");
190661
- const configPath = path19.join(projectDir, "specific.hcl");
190662
- if (!fs21.existsSync(configPath)) {
191243
+ const configPath = path20.join(projectDir, "specific.hcl");
191244
+ if (!fs22.existsSync(configPath)) {
190663
191245
  throw new Error("specific.hcl not found in project directory");
190664
191246
  }
190665
191247
  const relativePaths = await collectPaths(projectDir, projectDir, EXCLUDED_DIRS);
@@ -190676,8 +191258,8 @@ async function createTarArchive(projectDir) {
190676
191258
  }
190677
191259
  function findWidestContext(projectDir, contexts) {
190678
191260
  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));
191261
+ const absolute = contexts.map((c) => path20.resolve(projectDir, c));
191262
+ const segments = absolute.map((p) => p.split(path20.sep).filter(Boolean));
190681
191263
  const firstSegments = segments[0];
190682
191264
  if (!firstSegments) return ".";
190683
191265
  const minLen = Math.min(...segments.map((s) => s.length));
@@ -190691,12 +191273,12 @@ function findWidestContext(projectDir, contexts) {
190691
191273
  }
190692
191274
  }
190693
191275
  const ancestorSegments = firstSegments.slice(0, commonLength);
190694
- const ancestor = path19.sep + ancestorSegments.join(path19.sep);
190695
- return path19.relative(projectDir, ancestor) || ".";
191276
+ const ancestor = path20.sep + ancestorSegments.join(path20.sep);
191277
+ return path20.relative(projectDir, ancestor) || ".";
190696
191278
  }
190697
191279
  async function createProjectTarball(projectDir, context = ".") {
190698
- const contextDir = path19.resolve(projectDir, context);
190699
- const appPath = path19.relative(contextDir, projectDir) || ".";
191280
+ const contextDir = path20.resolve(projectDir, context);
191281
+ const appPath = path20.relative(contextDir, projectDir) || ".";
190700
191282
  writeLog("tarball", `Context: ${contextDir}, appPath: ${appPath}`);
190701
191283
  let tarball;
190702
191284
  if (isInsideGitRepository(contextDir)) {
@@ -191583,14 +192165,14 @@ ${errorMsg}`
191583
192165
  ), 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
192166
  }
191585
192167
  async function deployCommand(environment, options2) {
191586
- const configPath = path20.join(process.cwd(), "specific.hcl");
191587
- if (!fs22.existsSync(configPath)) {
192168
+ const configPath = path21.join(process.cwd(), "specific.hcl");
192169
+ if (!fs23.existsSync(configPath)) {
191588
192170
  console.error("Error: No specific.hcl found in current directory");
191589
192171
  process.exit(1);
191590
192172
  }
191591
192173
  let config;
191592
192174
  try {
191593
- const hcl = fs22.readFileSync(configPath, "utf-8");
192175
+ const hcl = fs23.readFileSync(configPath, "utf-8");
191594
192176
  config = await parseConfig(hcl);
191595
192177
  } catch (err) {
191596
192178
  console.error(
@@ -191614,8 +192196,8 @@ async function deployCommand(environment, options2) {
191614
192196
 
191615
192197
  // src/commands/exec.tsx
191616
192198
  import { spawn as spawn6 } from "child_process";
191617
- import * as fs23 from "fs";
191618
- import * as path21 from "path";
192199
+ import * as fs24 from "fs";
192200
+ import * as path22 from "path";
191619
192201
  async function execCommand(serviceName, command, instanceKey = "default") {
191620
192202
  if (command.length === 0) {
191621
192203
  console.error(
@@ -191643,14 +192225,14 @@ async function execCommand(serviceName, command, instanceKey = "default") {
191643
192225
  }
191644
192226
  }
191645
192227
  };
191646
- const configPath = path21.join(process.cwd(), "specific.hcl");
191647
- if (!fs23.existsSync(configPath)) {
192228
+ const configPath = path22.join(process.cwd(), "specific.hcl");
192229
+ if (!fs24.existsSync(configPath)) {
191648
192230
  console.error("Error: No specific.hcl found in current directory");
191649
192231
  process.exit(1);
191650
192232
  }
191651
192233
  let config;
191652
192234
  try {
191653
- const hcl = fs23.readFileSync(configPath, "utf-8");
192235
+ const hcl = fs24.readFileSync(configPath, "utf-8");
191654
192236
  config = await parseConfig(hcl);
191655
192237
  } catch (err) {
191656
192238
  console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
@@ -191697,6 +192279,8 @@ async function execCommand(serviceName, command, instanceKey = "default") {
191697
192279
  bucket: dbState.engine === "object_store" ? name : void 0,
191698
192280
  syncUrl: dbState.syncUrl,
191699
192281
  syncSecret: dbState.syncSecret,
192282
+ reshapeEnabled: dbState.reshapeEnabled,
192283
+ reshapeSearchPath: dbState.reshapeSearchPath,
191700
192284
  async stop() {
191701
192285
  }
191702
192286
  });
@@ -191762,8 +192346,17 @@ async function execCommand(serviceName, command, instanceKey = "default") {
191762
192346
  };
191763
192347
  process.on("SIGINT", () => handleSignal("SIGINT"));
191764
192348
  process.on("SIGTERM", () => handleSignal("SIGTERM"));
192349
+ let effectiveCwd = process.cwd();
192350
+ if (service.root) {
192351
+ effectiveCwd = path22.resolve(process.cwd(), service.root);
192352
+ } else if (service.build) {
192353
+ const build = config.builds.find((b) => b.name === service.build.name);
192354
+ if (build?.root) {
192355
+ effectiveCwd = path22.resolve(process.cwd(), build.root);
192356
+ }
192357
+ }
191765
192358
  child = spawn6(command[0], command.slice(1), {
191766
- cwd: process.cwd(),
192359
+ cwd: effectiveCwd,
191767
192360
  env: {
191768
192361
  ...process.env,
191769
192362
  ...resolvedEnv
@@ -191787,8 +192380,8 @@ async function execCommand(serviceName, command, instanceKey = "default") {
191787
192380
 
191788
192381
  // src/commands/psql.tsx
191789
192382
  import { spawn as spawn7 } from "child_process";
191790
- import * as fs24 from "fs";
191791
- import * as path22 from "path";
192383
+ import * as fs25 from "fs";
192384
+ import * as path23 from "path";
191792
192385
  async function psqlCommand(databaseName, instanceKey = "default", extraArgs = []) {
191793
192386
  let startedResources = [];
191794
192387
  let ownsInstances = false;
@@ -191805,14 +192398,14 @@ async function psqlCommand(databaseName, instanceKey = "default", extraArgs = []
191805
192398
  }
191806
192399
  }
191807
192400
  };
191808
- const configPath = path22.join(process.cwd(), "specific.hcl");
191809
- if (!fs24.existsSync(configPath)) {
192401
+ const configPath = path23.join(process.cwd(), "specific.hcl");
192402
+ if (!fs25.existsSync(configPath)) {
191810
192403
  console.error("Error: No specific.hcl found in current directory");
191811
192404
  process.exit(1);
191812
192405
  }
191813
192406
  let config;
191814
192407
  try {
191815
- const hcl = fs24.readFileSync(configPath, "utf-8");
192408
+ const hcl = fs25.readFileSync(configPath, "utf-8");
191816
192409
  config = await parseConfig(hcl);
191817
192410
  } catch (err) {
191818
192411
  console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
@@ -191932,25 +192525,227 @@ async function psqlCommand(databaseName, instanceKey = "default", extraArgs = []
191932
192525
  });
191933
192526
  }
191934
192527
 
192528
+ // src/commands/reshape.tsx
192529
+ import { spawn as spawn8 } from "child_process";
192530
+ import * as fs26 from "fs";
192531
+ import * as path24 from "path";
192532
+ var VALID_ACTIONS = ["start", "complete", "status", "abort", "check"];
192533
+ var MIGRATION_SUBCOMMANDS = ["start", "complete", "abort"];
192534
+ var OFFLINE_ACTIONS = ["check"];
192535
+ async function reshapeCommand(action, databaseName, instanceKey = "default") {
192536
+ if (!VALID_ACTIONS.includes(action)) {
192537
+ console.error(
192538
+ `Error: Invalid action "${action}". Valid actions: ${VALID_ACTIONS.join(", ")}`
192539
+ );
192540
+ process.exit(1);
192541
+ }
192542
+ const isOfflineAction = OFFLINE_ACTIONS.includes(action);
192543
+ const configPath = path24.join(process.cwd(), "specific.hcl");
192544
+ let config;
192545
+ let migrationsDir = "migrations";
192546
+ let targetDb;
192547
+ try {
192548
+ if (fs26.existsSync(configPath)) {
192549
+ const configContent = fs26.readFileSync(configPath, "utf-8");
192550
+ config = await parseConfig(configContent);
192551
+ if (databaseName) {
192552
+ const postgresConfig = config.postgres.find((p) => p.name === databaseName);
192553
+ if (postgresConfig?.reshape?.migrations_dir) {
192554
+ migrationsDir = postgresConfig.reshape.migrations_dir;
192555
+ }
192556
+ targetDb = databaseName;
192557
+ } else {
192558
+ const reshapePostgres = config.postgres.find((p) => p.reshape?.enabled);
192559
+ if (reshapePostgres) {
192560
+ if (reshapePostgres.reshape?.migrations_dir) {
192561
+ migrationsDir = reshapePostgres.reshape.migrations_dir;
192562
+ }
192563
+ targetDb = reshapePostgres.name;
192564
+ } else if (config.postgres.length > 0) {
192565
+ const first = config.postgres[0];
192566
+ if (first.reshape?.migrations_dir) {
192567
+ migrationsDir = first.reshape.migrations_dir;
192568
+ }
192569
+ targetDb = first.name;
192570
+ }
192571
+ }
192572
+ }
192573
+ } catch {
192574
+ }
192575
+ let dbUrl;
192576
+ let startedResources = [];
192577
+ let ownsInstances = false;
192578
+ let stateManager = null;
192579
+ const cleanup = async () => {
192580
+ if (ownsInstances) {
192581
+ for (const resource of startedResources) {
192582
+ await resource.stop().catch(() => {
192583
+ });
192584
+ }
192585
+ if (stateManager) {
192586
+ await stateManager.releaseOwnership().catch(() => {
192587
+ });
192588
+ }
192589
+ }
192590
+ };
192591
+ if (!isOfflineAction) {
192592
+ stateManager = new InstanceStateManager(process.cwd(), instanceKey);
192593
+ await stateManager.cleanStaleState();
192594
+ const existingInstances = await stateManager.getExistingInstances();
192595
+ if (existingInstances) {
192596
+ const availableDatabases = Object.keys(existingInstances.databases).filter(
192597
+ (name) => existingInstances.databases[name]?.engine === "postgres"
192598
+ );
192599
+ if (availableDatabases.length === 0) {
192600
+ console.error("Error: No PostgreSQL databases found in running instances.");
192601
+ process.exit(1);
192602
+ }
192603
+ if (databaseName) {
192604
+ if (!existingInstances.databases[databaseName]) {
192605
+ console.error(
192606
+ `Error: Database "${databaseName}" not found. Available: ${availableDatabases.join(", ")}`
192607
+ );
192608
+ process.exit(1);
192609
+ }
192610
+ if (existingInstances.databases[databaseName]?.engine !== "postgres") {
192611
+ console.error(
192612
+ `Error: Database "${databaseName}" is not a PostgreSQL database.`
192613
+ );
192614
+ process.exit(1);
192615
+ }
192616
+ targetDb = databaseName;
192617
+ } else {
192618
+ if (availableDatabases.length > 1) {
192619
+ console.error(
192620
+ `Error: Multiple databases available. Specify one: ${availableDatabases.join(", ")}`
192621
+ );
192622
+ process.exit(1);
192623
+ }
192624
+ targetDb = availableDatabases[0];
192625
+ }
192626
+ dbUrl = existingInstances.databases[targetDb].url;
192627
+ } else {
192628
+ if (!config) {
192629
+ console.error("Error: No specific.hcl found in current directory");
192630
+ process.exit(1);
192631
+ }
192632
+ if (!targetDb) {
192633
+ console.error("Error: No PostgreSQL database found in configuration.");
192634
+ process.exit(1);
192635
+ }
192636
+ console.error("Starting resources...");
192637
+ try {
192638
+ await stateManager.claimOwnership("reshape");
192639
+ ownsInstances = true;
192640
+ } catch (err) {
192641
+ console.error(
192642
+ `Error: ${err instanceof Error ? err.message : String(err)}`
192643
+ );
192644
+ process.exit(1);
192645
+ }
192646
+ try {
192647
+ const result = await startResources({
192648
+ config,
192649
+ selection: {
192650
+ mode: "required",
192651
+ postgres: [targetDb],
192652
+ redis: [],
192653
+ storage: []
192654
+ },
192655
+ stateManager,
192656
+ dataDir: `.specific/keys/${instanceKey}/data`,
192657
+ portAllocator: new PortAllocator(),
192658
+ callbacks: {
192659
+ log: (msg) => console.error(msg)
192660
+ },
192661
+ startElectric: false
192662
+ });
192663
+ startedResources = result.startedResources;
192664
+ const resource = result.resources.get(targetDb);
192665
+ if (!resource) {
192666
+ console.error(`Error: Failed to start database "${targetDb}"`);
192667
+ await cleanup();
192668
+ process.exit(1);
192669
+ }
192670
+ dbUrl = resource.url;
192671
+ } catch (err) {
192672
+ console.error(
192673
+ `Error: Failed to start resources: ${err instanceof Error ? err.message : String(err)}`
192674
+ );
192675
+ await cleanup();
192676
+ process.exit(1);
192677
+ }
192678
+ }
192679
+ }
192680
+ const isMigrationSubcommand = MIGRATION_SUBCOMMANDS.includes(action);
192681
+ const reshapeArgs = isMigrationSubcommand ? ["migration", action] : [action];
192682
+ const fullMigrationsPath = path24.join(process.cwd(), migrationsDir);
192683
+ if (action === "check" || action === "start") {
192684
+ if (fs26.existsSync(fullMigrationsPath)) {
192685
+ reshapeArgs.push("--dirs", fullMigrationsPath);
192686
+ } else if (action === "check") {
192687
+ console.error(`Error: Migrations directory not found: ${fullMigrationsPath}`);
192688
+ await cleanup();
192689
+ process.exit(1);
192690
+ }
192691
+ }
192692
+ if (action === "status" && dbUrl) {
192693
+ reshapeArgs.push("--url", dbUrl);
192694
+ }
192695
+ const binary = await ensureBinary(reshapeBinary);
192696
+ const reshapePath = binary.executables["reshape"];
192697
+ let child = null;
192698
+ const handleSignal = async (signal) => {
192699
+ if (child && !child.killed) {
192700
+ child.kill(signal);
192701
+ }
192702
+ await cleanup();
192703
+ };
192704
+ process.on("SIGINT", () => handleSignal("SIGINT"));
192705
+ process.on("SIGTERM", () => handleSignal("SIGTERM"));
192706
+ child = spawn8(reshapePath, reshapeArgs, {
192707
+ cwd: process.cwd(),
192708
+ env: {
192709
+ ...process.env,
192710
+ // For migration subcommands, pass DB_URL via env
192711
+ ...isMigrationSubcommand && dbUrl ? { DB_URL: dbUrl } : {}
192712
+ },
192713
+ stdio: "inherit"
192714
+ });
192715
+ child.on("exit", async (code, signal) => {
192716
+ await cleanup();
192717
+ if (signal) {
192718
+ process.kill(process.pid, signal);
192719
+ } else {
192720
+ process.exit(code ?? 0);
192721
+ }
192722
+ });
192723
+ child.on("error", async (err) => {
192724
+ console.error(`Error: Failed to start reshape: ${err.message}`);
192725
+ await cleanup();
192726
+ process.exit(1);
192727
+ });
192728
+ }
192729
+
191935
192730
  // src/commands/clean.tsx
191936
192731
  import React8, { useState as useState7, useEffect as useEffect5 } from "react";
191937
192732
  import { render as render6, Text as Text8, Box as Box8 } from "ink";
191938
192733
  import Spinner6 from "ink-spinner";
191939
- import * as fs25 from "fs";
191940
- import * as path23 from "path";
192734
+ import * as fs27 from "fs";
192735
+ import * as path25 from "path";
191941
192736
  function CleanUI({ instanceKey }) {
191942
192737
  const [state, setState] = useState7({ status: "checking" });
191943
192738
  useEffect5(() => {
191944
192739
  async function clean() {
191945
192740
  const projectRoot = process.cwd();
191946
- const specificDir = path23.join(projectRoot, ".specific");
191947
- if (!fs25.existsSync(specificDir)) {
192741
+ const specificDir = path25.join(projectRoot, ".specific");
192742
+ if (!fs27.existsSync(specificDir)) {
191948
192743
  setState({ status: "nothing" });
191949
192744
  return;
191950
192745
  }
191951
192746
  if (instanceKey) {
191952
- const keyDir = path23.join(specificDir, "keys", instanceKey);
191953
- if (!fs25.existsSync(keyDir)) {
192747
+ const keyDir = path25.join(specificDir, "keys", instanceKey);
192748
+ if (!fs27.existsSync(keyDir)) {
191954
192749
  setState({ status: "nothing" });
191955
192750
  return;
191956
192751
  }
@@ -191966,7 +192761,7 @@ function CleanUI({ instanceKey }) {
191966
192761
  await stateManager.cleanStaleState();
191967
192762
  setState({ status: "cleaning" });
191968
192763
  try {
191969
- fs25.rmSync(keyDir, { recursive: true, force: true });
192764
+ fs27.rmSync(keyDir, { recursive: true, force: true });
191970
192765
  setState({ status: "success" });
191971
192766
  } catch (err) {
191972
192767
  setState({
@@ -191975,21 +192770,23 @@ function CleanUI({ instanceKey }) {
191975
192770
  });
191976
192771
  }
191977
192772
  } 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()
191982
- );
191983
- for (const key of keys) {
191984
- const stateManager2 = new InstanceStateManager(projectRoot, key);
191985
- const existingInstances2 = await stateManager2.getExistingInstances();
191986
- if (existingInstances2) {
191987
- setState({
191988
- status: "error",
191989
- error: `Cannot clean while 'specific dev' is running for key "${key}" (PID ${existingInstances2.owner.pid}). Please stop it first.`
191990
- });
191991
- return;
191992
- }
192773
+ const keysDir = path25.join(specificDir, "keys");
192774
+ if (!fs27.existsSync(keysDir)) {
192775
+ setState({ status: "nothing" });
192776
+ return;
192777
+ }
192778
+ const keys = fs27.readdirSync(keysDir).filter(
192779
+ (f) => fs27.statSync(path25.join(keysDir, f)).isDirectory()
192780
+ );
192781
+ for (const key of keys) {
192782
+ const stateManager2 = new InstanceStateManager(projectRoot, key);
192783
+ const existingInstances2 = await stateManager2.getExistingInstances();
192784
+ if (existingInstances2) {
192785
+ setState({
192786
+ status: "error",
192787
+ error: `Cannot clean while 'specific dev' is running for key "${key}" (PID ${existingInstances2.owner.pid}). Please stop it first.`
192788
+ });
192789
+ return;
191993
192790
  }
191994
192791
  }
191995
192792
  const stateManager = new InstanceStateManager(projectRoot);
@@ -192003,7 +192800,7 @@ function CleanUI({ instanceKey }) {
192003
192800
  }
192004
192801
  setState({ status: "cleaning" });
192005
192802
  try {
192006
- fs25.rmSync(specificDir, { recursive: true, force: true });
192803
+ fs27.rmSync(keysDir, { recursive: true, force: true });
192007
192804
  setState({ status: "success" });
192008
192805
  } catch (err) {
192009
192806
  setState({
@@ -192019,16 +192816,16 @@ function CleanUI({ instanceKey }) {
192019
192816
  return /* @__PURE__ */ React8.createElement(Box8, null, /* @__PURE__ */ React8.createElement(Text8, { color: "blue" }, /* @__PURE__ */ React8.createElement(Spinner6, { type: "dots" })), /* @__PURE__ */ React8.createElement(Text8, null, " Checking for running instances..."));
192020
192817
  }
192021
192818
  if (state.status === "cleaning") {
192022
- return /* @__PURE__ */ React8.createElement(Box8, null, /* @__PURE__ */ React8.createElement(Text8, { color: "blue" }, /* @__PURE__ */ React8.createElement(Spinner6, { type: "dots" })), /* @__PURE__ */ React8.createElement(Text8, null, " Removing .specific directory..."));
192819
+ return /* @__PURE__ */ React8.createElement(Box8, null, /* @__PURE__ */ React8.createElement(Text8, { color: "blue" }, /* @__PURE__ */ React8.createElement(Spinner6, { type: "dots" })), /* @__PURE__ */ React8.createElement(Text8, null, " Cleaning app data..."));
192023
192820
  }
192024
192821
  if (state.status === "error") {
192025
192822
  return /* @__PURE__ */ React8.createElement(Text8, { color: "red" }, "Error: ", state.error);
192026
192823
  }
192027
192824
  if (state.status === "nothing") {
192028
- const target2 = instanceKey ? `key "${instanceKey}"` : ".specific directory";
192825
+ const target2 = instanceKey ? `key "${instanceKey}"` : "app data";
192029
192826
  return /* @__PURE__ */ React8.createElement(Text8, { color: "yellow" }, "Nothing to clean (", target2, " does not exist)");
192030
192827
  }
192031
- const target = instanceKey ? `key "${instanceKey}"` : ".specific directory";
192828
+ const target = instanceKey ? `key "${instanceKey}"` : "app data";
192032
192829
  return /* @__PURE__ */ React8.createElement(Text8, { color: "green" }, "Cleaned ", target);
192033
192830
  }
192034
192831
  function cleanCommand(instanceKey) {
@@ -192086,11 +192883,87 @@ function logoutCommand() {
192086
192883
  render7(/* @__PURE__ */ React9.createElement(LogoutUI, null));
192087
192884
  }
192088
192885
 
192886
+ // src/commands/beta.tsx
192887
+ import React10, { useState as useState9, useEffect as useEffect7 } from "react";
192888
+ import { render as render8, Text as Text10, Box as Box9, useInput as useInput6, useApp as useApp5 } from "ink";
192889
+ function BetaToggleUI() {
192890
+ const { exit } = useApp5();
192891
+ const [enabled, setEnabled] = useState9(() => {
192892
+ const current = loadEnabledBetas();
192893
+ const state = {};
192894
+ for (const beta of BETA_REGISTRY) {
192895
+ state[beta.name] = current.includes(beta.name);
192896
+ }
192897
+ return state;
192898
+ });
192899
+ const [focusedIndex, setFocusedIndex] = useState9(0);
192900
+ const [saved, setSaved] = useState9(false);
192901
+ const isSubmitFocused = focusedIndex === BETA_REGISTRY.length;
192902
+ useInput6((input, key) => {
192903
+ if (saved) return;
192904
+ if (key.upArrow || input === "k") {
192905
+ setFocusedIndex((prev) => Math.max(0, prev - 1));
192906
+ } else if (key.downArrow || input === "j") {
192907
+ setFocusedIndex((prev) => Math.min(BETA_REGISTRY.length, prev + 1));
192908
+ } else if (input === " " && !isSubmitFocused) {
192909
+ const beta = BETA_REGISTRY[focusedIndex];
192910
+ setEnabled((prev) => ({ ...prev, [beta.name]: !prev[beta.name] }));
192911
+ } else if ((key.return || input === " ") && isSubmitFocused) {
192912
+ for (const beta of BETA_REGISTRY) {
192913
+ if (enabled[beta.name]) {
192914
+ enableBeta(beta.name);
192915
+ } else {
192916
+ disableBeta(beta.name);
192917
+ }
192918
+ }
192919
+ trackEvent("beta_toggled", {
192920
+ enabled: Object.keys(enabled).filter((k) => enabled[k])
192921
+ });
192922
+ setSaved(true);
192923
+ }
192924
+ });
192925
+ useEffect7(() => {
192926
+ if (saved) {
192927
+ const timer = setTimeout(() => exit(), 100);
192928
+ return () => clearTimeout(timer);
192929
+ }
192930
+ }, [saved, exit]);
192931
+ if (saved) {
192932
+ const enabledNames = Object.keys(enabled).filter((k) => enabled[k]);
192933
+ 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"));
192934
+ }
192935
+ 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) => {
192936
+ const isFocused = focusedIndex === index;
192937
+ const isChecked = enabled[beta.name];
192938
+ 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)));
192939
+ }), /* @__PURE__ */ React10.createElement(Text10, null, " "), /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text10, { ...isSubmitFocused && { color: "cyan" } }, isSubmitFocused ? "\u276F " : " ", /* @__PURE__ */ React10.createElement(
192940
+ Text10,
192941
+ {
192942
+ inverse: isSubmitFocused,
192943
+ ...!isSubmitFocused && { backgroundColor: "gray" }
192944
+ },
192945
+ " ",
192946
+ "Save",
192947
+ " "
192948
+ ))));
192949
+ }
192950
+ function betaCommand() {
192951
+ if (BETA_REGISTRY.length === 0) {
192952
+ console.log("No beta features available.");
192953
+ return;
192954
+ }
192955
+ if (!isInteractive()) {
192956
+ console.error("Error: specific beta requires an interactive terminal.");
192957
+ process.exit(1);
192958
+ }
192959
+ render8(/* @__PURE__ */ React10.createElement(BetaToggleUI, null));
192960
+ }
192961
+
192089
192962
  // src/cli.tsx
192090
192963
  var program = new Command();
192091
192964
  var env = "production";
192092
192965
  var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
192093
- program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.49").enablePositionalOptions();
192966
+ program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.51").enablePositionalOptions();
192094
192967
  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
192968
  program.command("docs [topic]").description("Fetch LLM-optimized documentation").action(docsCommand);
192096
192969
  program.command("check").description("Validate specific.hcl configuration").action(checkCommand);
@@ -192111,9 +192984,14 @@ program.command("psql [database] [args...]").description("Connect to a Postgres
192111
192984
  const key = options2.key ?? getDefaultKey();
192112
192985
  psqlCommand(database, key, filteredArgs);
192113
192986
  });
192987
+ 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) => {
192988
+ const key = options2.key ?? getDefaultKey();
192989
+ reshapeCommand(action, database, key);
192990
+ });
192114
192991
  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
192992
  cleanCommand(options2.key);
192116
192993
  });
192994
+ program.command("beta").description("Manage beta feature flags").action(betaCommand);
192117
192995
  program.command("login").description("Log in to Specific").action(loginCommand);
192118
192996
  program.command("logout").description("Log out of Specific").action(logoutCommand);
192119
192997
  var commandName = process.argv[2] || "help";