@specific.dev/cli 0.1.49 → 0.1.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/404/index.html +1 -1
- package/dist/admin/404.html +1 -1
- package/dist/admin/__next.__PAGE__.txt +1 -1
- package/dist/admin/__next._full.txt +1 -1
- package/dist/admin/__next._head.txt +1 -1
- package/dist/admin/__next._index.txt +1 -1
- package/dist/admin/__next._tree.txt +1 -1
- package/dist/admin/_not-found/__next._full.txt +1 -1
- package/dist/admin/_not-found/__next._head.txt +1 -1
- package/dist/admin/_not-found/__next._index.txt +1 -1
- package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +1 -1
- package/dist/admin/_not-found/__next._not-found.txt +1 -1
- package/dist/admin/_not-found/__next._tree.txt +1 -1
- package/dist/admin/_not-found/index.html +1 -1
- package/dist/admin/_not-found/index.txt +1 -1
- package/dist/admin/databases/__next._full.txt +1 -1
- package/dist/admin/databases/__next._head.txt +1 -1
- package/dist/admin/databases/__next._index.txt +1 -1
- package/dist/admin/databases/__next._tree.txt +1 -1
- package/dist/admin/databases/__next.databases.__PAGE__.txt +1 -1
- package/dist/admin/databases/__next.databases.txt +1 -1
- package/dist/admin/databases/index.html +1 -1
- package/dist/admin/databases/index.txt +1 -1
- package/dist/admin/index.html +1 -1
- package/dist/admin/index.txt +1 -1
- package/dist/cli.js +1536 -687
- package/dist/docs/index.md +3 -0
- package/dist/docs/postgres/reshape/actions.md +90 -0
- package/dist/docs/postgres/reshape/index.md +149 -0
- package/dist/docs/postgres.md +20 -1
- package/dist/postinstall.js +1 -1
- package/package.json +1 -1
- /package/dist/admin/_next/static/{FMaKxl-Dpw5U-PgHqIMag → o2Qo92jA0gWbtB1ZWKQFF}/_buildManifest.js +0 -0
- /package/dist/admin/_next/static/{FMaKxl-Dpw5U-PgHqIMag → o2Qo92jA0gWbtB1ZWKQFF}/_clientMiddlewareManifest.json +0 -0
- /package/dist/admin/_next/static/{FMaKxl-Dpw5U-PgHqIMag → o2Qo92jA0gWbtB1ZWKQFF}/_ssgManifest.js +0 -0
package/dist/cli.js
CHANGED
|
@@ -238,15 +238,15 @@ var init_wsl_utils = __esm({
|
|
|
238
238
|
const { stdout } = await executePowerShell(command, { powerShellPath: psPath });
|
|
239
239
|
return stdout.trim();
|
|
240
240
|
};
|
|
241
|
-
convertWslPathToWindows = async (
|
|
242
|
-
if (/^[a-z]+:\/\//i.test(
|
|
243
|
-
return
|
|
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",
|
|
246
|
+
const { stdout } = await execFile2("wslpath", ["-aw", path26], { encoding: "utf8" });
|
|
247
247
|
return stdout.trim();
|
|
248
248
|
} catch {
|
|
249
|
-
return
|
|
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((
|
|
640
|
+
return new Promise((resolve9, reject) => {
|
|
641
641
|
subprocess.once("error", reject);
|
|
642
642
|
subprocess.once("close", (exitCode) => {
|
|
643
643
|
if (!options2.allowNonzeroExitCode && exitCode !== 0) {
|
|
644
644
|
reject(new Error(`Exited with code ${exitCode}`));
|
|
645
645
|
return;
|
|
646
646
|
}
|
|
647
|
-
|
|
647
|
+
resolve9(subprocess);
|
|
648
648
|
});
|
|
649
649
|
});
|
|
650
650
|
}
|
|
651
651
|
if (isFallbackAttempt) {
|
|
652
|
-
return new Promise((
|
|
652
|
+
return new Promise((resolve9, reject) => {
|
|
653
653
|
subprocess.once("error", reject);
|
|
654
654
|
subprocess.once("spawn", () => {
|
|
655
655
|
subprocess.once("close", (exitCode) => {
|
|
@@ -659,17 +659,17 @@ var init_open = __esm({
|
|
|
659
659
|
return;
|
|
660
660
|
}
|
|
661
661
|
subprocess.unref();
|
|
662
|
-
|
|
662
|
+
resolve9(subprocess);
|
|
663
663
|
});
|
|
664
664
|
});
|
|
665
665
|
});
|
|
666
666
|
}
|
|
667
667
|
subprocess.unref();
|
|
668
|
-
return new Promise((
|
|
668
|
+
return new Promise((resolve9, reject) => {
|
|
669
669
|
subprocess.once("error", reject);
|
|
670
670
|
subprocess.once("spawn", () => {
|
|
671
671
|
subprocess.off("error", reject);
|
|
672
|
-
|
|
672
|
+
resolve9(subprocess);
|
|
673
673
|
});
|
|
674
674
|
});
|
|
675
675
|
};
|
|
@@ -754,8 +754,8 @@ var require_dist = __commonJS({
|
|
|
754
754
|
var $global, $module, $NaN = NaN;
|
|
755
755
|
if ("undefined" != typeof window ? $global = window : "undefined" != typeof self ? $global = self : "undefined" != typeof global ? ($global = global).require = __require : $global = this, void 0 === $global || void 0 === $global.Array) throw new Error("no global object found");
|
|
756
756
|
if ("undefined" != typeof module && ($module = module), !$global.fs && $global.require) try {
|
|
757
|
-
var
|
|
758
|
-
"object" == typeof
|
|
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((
|
|
183685
|
+
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
183686
183686
|
}
|
|
183687
183687
|
|
|
183688
183688
|
// src/lib/auth/login.tsx
|
|
@@ -183699,7 +183699,7 @@ function LoginUI({
|
|
|
183699
183699
|
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, isReauthentication && /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "Session expired. Please log in again."), /* @__PURE__ */ React.createElement(Text, { bold: true }, "Log in to Specific"), state.phase === "waiting-for-browser" ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, null, "Your authentication code:", " ", /* @__PURE__ */ React.createElement(Text, { color: "cyan", bold: true }, state.userCode))), /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "blue" }, /* @__PURE__ */ React.createElement(Spinner, { type: "dots" })), /* @__PURE__ */ React.createElement(Text, null, " Waiting for authentication in browser...")), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "If the browser didn't open, visit: ", state.verificationUri)) : /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "blue" }, /* @__PURE__ */ React.createElement(Spinner, { type: "dots" })), /* @__PURE__ */ React.createElement(Text, null, " Initiating login...")));
|
|
183700
183700
|
}
|
|
183701
183701
|
function performLogin(options2 = {}) {
|
|
183702
|
-
return new Promise((
|
|
183702
|
+
return new Promise((resolve9) => {
|
|
183703
183703
|
let currentState = { phase: "initiating" };
|
|
183704
183704
|
let flowHandle;
|
|
183705
183705
|
const instance = render(
|
|
@@ -183739,14 +183739,14 @@ function performLogin(options2 = {}) {
|
|
|
183739
183739
|
process.off("SIGINT", handleExit);
|
|
183740
183740
|
process.off("SIGTERM", handleExit);
|
|
183741
183741
|
instance.unmount();
|
|
183742
|
-
|
|
183742
|
+
resolve9({ success: true, userEmail: newState.email });
|
|
183743
183743
|
}, 100);
|
|
183744
183744
|
} else if (newState.phase === "error") {
|
|
183745
183745
|
setTimeout(() => {
|
|
183746
183746
|
process.off("SIGINT", handleExit);
|
|
183747
183747
|
process.off("SIGTERM", handleExit);
|
|
183748
183748
|
instance.unmount();
|
|
183749
|
-
|
|
183749
|
+
resolve9({ success: false, error: new Error(newState.message) });
|
|
183750
183750
|
}, 100);
|
|
183751
183751
|
}
|
|
183752
183752
|
}
|
|
@@ -183873,7 +183873,7 @@ function trackEvent(event, properties) {
|
|
|
183873
183873
|
event,
|
|
183874
183874
|
properties: {
|
|
183875
183875
|
...properties,
|
|
183876
|
-
cli_version: "0.1.
|
|
183876
|
+
cli_version: "0.1.50",
|
|
183877
183877
|
platform: process.platform,
|
|
183878
183878
|
node_version: process.version,
|
|
183879
183879
|
project_id: getProjectId(),
|
|
@@ -184235,35 +184235,95 @@ Valid agents: ${VALID_AGENT_IDS.join(", ")}`
|
|
|
184235
184235
|
}
|
|
184236
184236
|
|
|
184237
184237
|
// src/commands/docs.tsx
|
|
184238
|
-
import { readFileSync as
|
|
184239
|
-
import { join as
|
|
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 =
|
|
184243
|
-
function docsCommand(
|
|
184244
|
-
const docPath = resolveDocPath(
|
|
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: ${
|
|
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
|
|
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
|
|
184257
|
-
|
|
184258
|
-
|
|
184259
|
-
|
|
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 =
|
|
184262
|
-
if (
|
|
184321
|
+
const directPath = join7(docsDir, `${path26}.md`);
|
|
184322
|
+
if (existsSync6(directPath)) {
|
|
184263
184323
|
return directPath;
|
|
184264
184324
|
}
|
|
184265
|
-
const indexPath =
|
|
184266
|
-
if (
|
|
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
|
|
184277
|
-
import * as
|
|
184336
|
+
import * as fs12 from "fs";
|
|
184337
|
+
import * as path8 from "path";
|
|
184338
|
+
import { execFile as execFile7 } from "child_process";
|
|
184278
184339
|
|
|
184279
184340
|
// node_modules/.pnpm/@specific+config@file+..+config/node_modules/@specific/config/dist/parser.js
|
|
184280
184341
|
var import_hcl2_json_parser = __toESM(require_dist(), 1);
|
|
@@ -184589,13 +184650,43 @@ function parseServices(serviceData) {
|
|
|
184589
184650
|
}
|
|
184590
184651
|
return services;
|
|
184591
184652
|
}
|
|
184653
|
+
function parseReshapeBlock(reshape) {
|
|
184654
|
+
if (!reshape) {
|
|
184655
|
+
return void 0;
|
|
184656
|
+
}
|
|
184657
|
+
const reshapeArray = Array.isArray(reshape) ? reshape : [reshape];
|
|
184658
|
+
const reshapeObj = reshapeArray[0];
|
|
184659
|
+
if (!reshapeObj || typeof reshapeObj !== "object" || Array.isArray(reshapeObj)) {
|
|
184660
|
+
return void 0;
|
|
184661
|
+
}
|
|
184662
|
+
const fieldObj = reshapeObj;
|
|
184663
|
+
if (fieldObj.enabled !== true) {
|
|
184664
|
+
return void 0;
|
|
184665
|
+
}
|
|
184666
|
+
const result = {
|
|
184667
|
+
enabled: true
|
|
184668
|
+
};
|
|
184669
|
+
if (typeof fieldObj.migrations_dir === "string") {
|
|
184670
|
+
result.migrations_dir = fieldObj.migrations_dir;
|
|
184671
|
+
}
|
|
184672
|
+
return result;
|
|
184673
|
+
}
|
|
184592
184674
|
function parsePostgres(data) {
|
|
184593
184675
|
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
184594
184676
|
return [];
|
|
184595
184677
|
}
|
|
184596
184678
|
const result = [];
|
|
184597
|
-
for (const [name] of Object.entries(data)) {
|
|
184598
|
-
|
|
184679
|
+
for (const [name, fieldsArray] of Object.entries(data)) {
|
|
184680
|
+
const postgres = { name };
|
|
184681
|
+
const fields = Array.isArray(fieldsArray) ? fieldsArray[0] : fieldsArray;
|
|
184682
|
+
if (fields && typeof fields === "object" && !Array.isArray(fields)) {
|
|
184683
|
+
const fieldObj = fields;
|
|
184684
|
+
const reshape = parseReshapeBlock(fieldObj.reshape);
|
|
184685
|
+
if (reshape) {
|
|
184686
|
+
postgres.reshape = reshape;
|
|
184687
|
+
}
|
|
184688
|
+
}
|
|
184689
|
+
result.push(postgres);
|
|
184599
184690
|
}
|
|
184600
184691
|
return result;
|
|
184601
184692
|
}
|
|
@@ -184826,13 +184917,374 @@ function validateEndpointReferences(config) {
|
|
|
184826
184917
|
return errors;
|
|
184827
184918
|
}
|
|
184828
184919
|
|
|
184920
|
+
// src/lib/bin/types.ts
|
|
184921
|
+
var BinaryNotFoundError = class extends Error {
|
|
184922
|
+
constructor(binaryName, version, platformInfo) {
|
|
184923
|
+
super(
|
|
184924
|
+
`Binary "${binaryName}" version ${version} is not available for ${platformInfo.platform}-${platformInfo.arch}`
|
|
184925
|
+
);
|
|
184926
|
+
this.binaryName = binaryName;
|
|
184927
|
+
this.version = version;
|
|
184928
|
+
this.platformInfo = platformInfo;
|
|
184929
|
+
this.name = "BinaryNotFoundError";
|
|
184930
|
+
}
|
|
184931
|
+
};
|
|
184932
|
+
var DownloadError = class extends Error {
|
|
184933
|
+
constructor(url, statusCode, cause) {
|
|
184934
|
+
super(
|
|
184935
|
+
statusCode ? `Failed to download ${url}: HTTP ${statusCode}` : `Failed to download ${url}: ${cause?.message || "Unknown error"}`
|
|
184936
|
+
);
|
|
184937
|
+
this.url = url;
|
|
184938
|
+
this.statusCode = statusCode;
|
|
184939
|
+
this.cause = cause;
|
|
184940
|
+
this.name = "DownloadError";
|
|
184941
|
+
}
|
|
184942
|
+
};
|
|
184943
|
+
var ExtractionError = class extends Error {
|
|
184944
|
+
constructor(archivePath, cause) {
|
|
184945
|
+
super(
|
|
184946
|
+
`Failed to extract ${archivePath}: ${cause?.message || "Unknown error"}`
|
|
184947
|
+
);
|
|
184948
|
+
this.archivePath = archivePath;
|
|
184949
|
+
this.cause = cause;
|
|
184950
|
+
this.name = "ExtractionError";
|
|
184951
|
+
}
|
|
184952
|
+
};
|
|
184953
|
+
|
|
184954
|
+
// src/lib/bin/manager.ts
|
|
184955
|
+
import * as fs11 from "fs";
|
|
184956
|
+
import * as path7 from "path";
|
|
184957
|
+
import * as os6 from "os";
|
|
184958
|
+
import { createReadStream } from "fs";
|
|
184959
|
+
import { createTarExtractor, extractTo } from "tar-vern";
|
|
184960
|
+
function getLibraryEnv(binary) {
|
|
184961
|
+
if (!binary.libraryPath) {
|
|
184962
|
+
return {};
|
|
184963
|
+
}
|
|
184964
|
+
const platform4 = os6.platform();
|
|
184965
|
+
if (platform4 === "darwin") {
|
|
184966
|
+
return { DYLD_LIBRARY_PATH: binary.libraryPath };
|
|
184967
|
+
} else if (platform4 === "linux") {
|
|
184968
|
+
return { LD_LIBRARY_PATH: binary.libraryPath };
|
|
184969
|
+
}
|
|
184970
|
+
return {};
|
|
184971
|
+
}
|
|
184972
|
+
function getBinBaseDir() {
|
|
184973
|
+
return path7.join(os6.homedir(), ".specific", "bin");
|
|
184974
|
+
}
|
|
184975
|
+
function getPlatformInfo() {
|
|
184976
|
+
const platform4 = os6.platform();
|
|
184977
|
+
const arch3 = os6.arch();
|
|
184978
|
+
if (platform4 !== "darwin" && platform4 !== "linux") {
|
|
184979
|
+
throw new Error(
|
|
184980
|
+
`Unsupported platform: ${platform4}. Only macOS and Linux are supported.`
|
|
184981
|
+
);
|
|
184982
|
+
}
|
|
184983
|
+
const archStr = arch3;
|
|
184984
|
+
let mappedArch;
|
|
184985
|
+
if (archStr === "x64" || archStr === "x86_64") {
|
|
184986
|
+
mappedArch = "x64";
|
|
184987
|
+
} else if (archStr === "arm64" || archStr === "aarch64") {
|
|
184988
|
+
mappedArch = "arm64";
|
|
184989
|
+
} else {
|
|
184990
|
+
throw new Error(
|
|
184991
|
+
`Unsupported architecture: ${arch3}. Only x64 and arm64 are supported.`
|
|
184992
|
+
);
|
|
184993
|
+
}
|
|
184994
|
+
return { platform: platform4, arch: mappedArch };
|
|
184995
|
+
}
|
|
184996
|
+
function getBinaryDir(definition, version, platformInfo) {
|
|
184997
|
+
return path7.join(
|
|
184998
|
+
getBinBaseDir(),
|
|
184999
|
+
definition.name,
|
|
185000
|
+
version,
|
|
185001
|
+
`${platformInfo.platform}-${platformInfo.arch}`
|
|
185002
|
+
);
|
|
185003
|
+
}
|
|
185004
|
+
function isBinaryInstalled(definition, version, platformInfo) {
|
|
185005
|
+
const binDir = getBinaryDir(definition, version, platformInfo);
|
|
185006
|
+
for (const execPath of definition.executables) {
|
|
185007
|
+
const fullPath = path7.join(binDir, execPath);
|
|
185008
|
+
if (!fs11.existsSync(fullPath)) {
|
|
185009
|
+
return false;
|
|
185010
|
+
}
|
|
185011
|
+
}
|
|
185012
|
+
return true;
|
|
185013
|
+
}
|
|
185014
|
+
function buildDownloadUrl(definition, version, platformInfo) {
|
|
185015
|
+
const mapping = definition.platformMapping[platformInfo.platform]?.[platformInfo.arch];
|
|
185016
|
+
if (!mapping) {
|
|
185017
|
+
throw new BinaryNotFoundError(definition.name, version, platformInfo);
|
|
185018
|
+
}
|
|
185019
|
+
return definition.urlTemplate.replace(/{version}/g, version).replace(/{platform}/g, mapping.platform).replace(/{arch}/g, mapping.arch);
|
|
185020
|
+
}
|
|
185021
|
+
async function downloadFile(url, destPath, onProgress) {
|
|
185022
|
+
let response;
|
|
185023
|
+
try {
|
|
185024
|
+
response = await fetch(url);
|
|
185025
|
+
} catch (error) {
|
|
185026
|
+
throw new DownloadError(url, void 0, error);
|
|
185027
|
+
}
|
|
185028
|
+
if (!response.ok) {
|
|
185029
|
+
throw new DownloadError(url, response.status);
|
|
185030
|
+
}
|
|
185031
|
+
const totalBytes = parseInt(
|
|
185032
|
+
response.headers.get("content-length") || "0",
|
|
185033
|
+
10
|
|
185034
|
+
);
|
|
185035
|
+
let bytesDownloaded = 0;
|
|
185036
|
+
const parentDir = path7.dirname(destPath);
|
|
185037
|
+
if (!fs11.existsSync(parentDir)) {
|
|
185038
|
+
fs11.mkdirSync(parentDir, { recursive: true });
|
|
185039
|
+
}
|
|
185040
|
+
const partPath = destPath + ".part";
|
|
185041
|
+
const fileStream = fs11.createWriteStream(partPath);
|
|
185042
|
+
try {
|
|
185043
|
+
const reader = response.body.getReader();
|
|
185044
|
+
while (true) {
|
|
185045
|
+
const { done, value } = await reader.read();
|
|
185046
|
+
if (done) break;
|
|
185047
|
+
bytesDownloaded += value.length;
|
|
185048
|
+
fileStream.write(Buffer.from(value));
|
|
185049
|
+
if (onProgress) {
|
|
185050
|
+
onProgress({
|
|
185051
|
+
phase: "downloading",
|
|
185052
|
+
bytesDownloaded,
|
|
185053
|
+
totalBytes: totalBytes || void 0,
|
|
185054
|
+
percent: totalBytes ? Math.round(bytesDownloaded / totalBytes * 100) : void 0
|
|
185055
|
+
});
|
|
185056
|
+
}
|
|
185057
|
+
}
|
|
185058
|
+
await new Promise((resolve9, reject) => {
|
|
185059
|
+
fileStream.end((err) => {
|
|
185060
|
+
if (err) reject(err);
|
|
185061
|
+
else resolve9();
|
|
185062
|
+
});
|
|
185063
|
+
});
|
|
185064
|
+
fs11.renameSync(partPath, destPath);
|
|
185065
|
+
} catch (error) {
|
|
185066
|
+
try {
|
|
185067
|
+
fileStream.close();
|
|
185068
|
+
if (fs11.existsSync(partPath)) {
|
|
185069
|
+
fs11.unlinkSync(partPath);
|
|
185070
|
+
}
|
|
185071
|
+
} catch {
|
|
185072
|
+
}
|
|
185073
|
+
throw new DownloadError(url, void 0, error);
|
|
185074
|
+
}
|
|
185075
|
+
}
|
|
185076
|
+
async function extractTarball(archivePath, destDir, definition, onProgress) {
|
|
185077
|
+
onProgress?.({ phase: "extracting" });
|
|
185078
|
+
if (!fs11.existsSync(destDir)) {
|
|
185079
|
+
fs11.mkdirSync(destDir, { recursive: true });
|
|
185080
|
+
}
|
|
185081
|
+
try {
|
|
185082
|
+
const fileStream = createReadStream(archivePath);
|
|
185083
|
+
const entries = createTarExtractor(fileStream, "gzip");
|
|
185084
|
+
await extractTo(entries, destDir);
|
|
185085
|
+
onProgress?.({ phase: "finalizing" });
|
|
185086
|
+
for (const execPath of definition.executables) {
|
|
185087
|
+
const fullPath = path7.join(destDir, execPath);
|
|
185088
|
+
if (fs11.existsSync(fullPath)) {
|
|
185089
|
+
fs11.chmodSync(fullPath, 493);
|
|
185090
|
+
}
|
|
185091
|
+
}
|
|
185092
|
+
} catch (error) {
|
|
185093
|
+
throw new ExtractionError(archivePath, error);
|
|
185094
|
+
}
|
|
185095
|
+
}
|
|
185096
|
+
async function ensureBinary(definition, version, onProgress) {
|
|
185097
|
+
const platformInfo = getPlatformInfo();
|
|
185098
|
+
const resolvedVersion = version || definition.versions[0];
|
|
185099
|
+
if (!definition.versions.includes(resolvedVersion)) {
|
|
185100
|
+
throw new Error(
|
|
185101
|
+
`Version ${resolvedVersion} not supported for ${definition.name}. Supported versions: ${definition.versions.join(", ")}`
|
|
185102
|
+
);
|
|
185103
|
+
}
|
|
185104
|
+
const binDir = getBinaryDir(definition, resolvedVersion, platformInfo);
|
|
185105
|
+
if (!isBinaryInstalled(definition, resolvedVersion, platformInfo)) {
|
|
185106
|
+
const url = buildDownloadUrl(definition, resolvedVersion, platformInfo);
|
|
185107
|
+
if (definition.type === "binary") {
|
|
185108
|
+
if (definition.executables.length !== 1) {
|
|
185109
|
+
throw new Error(
|
|
185110
|
+
`Binary type definitions must have exactly one executable, got ${definition.executables.length}`
|
|
185111
|
+
);
|
|
185112
|
+
}
|
|
185113
|
+
if (!fs11.existsSync(binDir)) {
|
|
185114
|
+
fs11.mkdirSync(binDir, { recursive: true });
|
|
185115
|
+
}
|
|
185116
|
+
const execPath = path7.join(binDir, definition.executables[0]);
|
|
185117
|
+
await downloadFile(url, execPath, onProgress);
|
|
185118
|
+
fs11.chmodSync(execPath, 493);
|
|
185119
|
+
onProgress?.({ phase: "finalizing" });
|
|
185120
|
+
} else {
|
|
185121
|
+
const downloadDir = path7.join(getBinBaseDir(), "downloads");
|
|
185122
|
+
const archiveName = `${definition.name}-${resolvedVersion}-${platformInfo.platform}-${platformInfo.arch}.tar.gz`;
|
|
185123
|
+
const archivePath = path7.join(downloadDir, archiveName);
|
|
185124
|
+
try {
|
|
185125
|
+
await downloadFile(url, archivePath, onProgress);
|
|
185126
|
+
await extractTarball(archivePath, binDir, definition, onProgress);
|
|
185127
|
+
} finally {
|
|
185128
|
+
try {
|
|
185129
|
+
if (fs11.existsSync(archivePath)) {
|
|
185130
|
+
fs11.unlinkSync(archivePath);
|
|
185131
|
+
}
|
|
185132
|
+
} catch {
|
|
185133
|
+
}
|
|
185134
|
+
}
|
|
185135
|
+
}
|
|
185136
|
+
}
|
|
185137
|
+
const executables = {};
|
|
185138
|
+
for (const execPath of definition.executables) {
|
|
185139
|
+
const name = path7.basename(execPath);
|
|
185140
|
+
executables[name] = path7.join(binDir, execPath);
|
|
185141
|
+
}
|
|
185142
|
+
const libraryPath = definition.libraryDir ? path7.join(binDir, definition.libraryDir) : void 0;
|
|
185143
|
+
return {
|
|
185144
|
+
rootDir: binDir,
|
|
185145
|
+
version: resolvedVersion,
|
|
185146
|
+
executables,
|
|
185147
|
+
libraryPath
|
|
185148
|
+
};
|
|
185149
|
+
}
|
|
185150
|
+
|
|
185151
|
+
// src/lib/bin/definitions/postgres.ts
|
|
185152
|
+
var postgresBinary = {
|
|
185153
|
+
name: "postgres",
|
|
185154
|
+
versions: ["18.1"],
|
|
185155
|
+
// URL: binaries.specific.dev/{SOFTWARE}/{VERSION}/{ARCH}.tar.gz
|
|
185156
|
+
urlTemplate: "https://binaries.specific.dev/postgres/{version}/{arch}.tar.gz",
|
|
185157
|
+
platformMapping: {
|
|
185158
|
+
darwin: {
|
|
185159
|
+
arm64: { platform: "macos", arch: "macos_arm" },
|
|
185160
|
+
x64: { platform: "macos", arch: "macos_x86" }
|
|
185161
|
+
},
|
|
185162
|
+
linux: {
|
|
185163
|
+
arm64: { platform: "linux", arch: "linux_arm" },
|
|
185164
|
+
x64: { platform: "linux", arch: "linux_x86" }
|
|
185165
|
+
}
|
|
185166
|
+
},
|
|
185167
|
+
// Archive contains bin/ and lib/ directories at root
|
|
185168
|
+
stripComponents: 0,
|
|
185169
|
+
// Core PostgreSQL executables (in bin/ directory)
|
|
185170
|
+
executables: ["bin/postgres", "bin/initdb", "bin/psql"],
|
|
185171
|
+
// Library directory relative to root (for setting LD_LIBRARY_PATH)
|
|
185172
|
+
libraryDir: "lib"
|
|
185173
|
+
};
|
|
185174
|
+
|
|
185175
|
+
// src/lib/bin/definitions/redis.ts
|
|
185176
|
+
var redisBinary = {
|
|
185177
|
+
name: "redis",
|
|
185178
|
+
versions: ["8.4.0"],
|
|
185179
|
+
// URL: binaries.specific.dev/{SOFTWARE}/{VERSION}/{ARCH}.tar.gz
|
|
185180
|
+
urlTemplate: "https://binaries.specific.dev/redis/{version}/{arch}.tar.gz",
|
|
185181
|
+
platformMapping: {
|
|
185182
|
+
darwin: {
|
|
185183
|
+
arm64: { platform: "macos", arch: "macos_arm" },
|
|
185184
|
+
x64: { platform: "macos", arch: "macos_x86" }
|
|
185185
|
+
},
|
|
185186
|
+
linux: {
|
|
185187
|
+
arm64: { platform: "linux", arch: "linux_arm" },
|
|
185188
|
+
x64: { platform: "linux", arch: "linux_x86" }
|
|
185189
|
+
}
|
|
185190
|
+
},
|
|
185191
|
+
// Binaries are at the root of the archive
|
|
185192
|
+
stripComponents: 0,
|
|
185193
|
+
// Core Redis executable
|
|
185194
|
+
executables: ["redis-server"]
|
|
185195
|
+
};
|
|
185196
|
+
|
|
185197
|
+
// src/lib/bin/definitions/electric.ts
|
|
185198
|
+
var electricBinary = {
|
|
185199
|
+
name: "electric",
|
|
185200
|
+
versions: ["1.2.11"],
|
|
185201
|
+
// URL: binaries.specific.dev/{SOFTWARE}/{VERSION}/{ARCH}.tar.gz
|
|
185202
|
+
urlTemplate: "https://binaries.specific.dev/electric/{version}/{arch}.tar.gz",
|
|
185203
|
+
platformMapping: {
|
|
185204
|
+
darwin: {
|
|
185205
|
+
arm64: { platform: "macos", arch: "macos_arm" },
|
|
185206
|
+
x64: { platform: "macos", arch: "macos_x86" }
|
|
185207
|
+
},
|
|
185208
|
+
linux: {
|
|
185209
|
+
arm64: { platform: "linux", arch: "linux_arm" },
|
|
185210
|
+
x64: { platform: "linux", arch: "linux_x86" }
|
|
185211
|
+
}
|
|
185212
|
+
},
|
|
185213
|
+
// Binaries are at the root of the archive
|
|
185214
|
+
stripComponents: 0,
|
|
185215
|
+
// Core Electric executable
|
|
185216
|
+
executables: ["electric"]
|
|
185217
|
+
};
|
|
185218
|
+
|
|
185219
|
+
// src/lib/bin/definitions/drizzle-gateway.ts
|
|
185220
|
+
var drizzleGatewayBinary = {
|
|
185221
|
+
name: "drizzle-gateway",
|
|
185222
|
+
versions: ["1.2.0"],
|
|
185223
|
+
// Official Drizzle Gateway download URL from R2
|
|
185224
|
+
urlTemplate: "https://pub-e240a4fd7085425baf4a7951e7611520.r2.dev/drizzle-gateway-{version}-{arch}",
|
|
185225
|
+
platformMapping: {
|
|
185226
|
+
darwin: {
|
|
185227
|
+
arm64: { platform: "macos", arch: "macos-arm64" },
|
|
185228
|
+
x64: { platform: "macos", arch: "macos-x64" }
|
|
185229
|
+
},
|
|
185230
|
+
linux: {
|
|
185231
|
+
arm64: { platform: "linux", arch: "linux-arm64" },
|
|
185232
|
+
x64: { platform: "linux", arch: "linux-x64" }
|
|
185233
|
+
}
|
|
185234
|
+
},
|
|
185235
|
+
// Raw binary, not a tarball
|
|
185236
|
+
type: "binary",
|
|
185237
|
+
// The executable name
|
|
185238
|
+
executables: ["drizzle-gateway"]
|
|
185239
|
+
};
|
|
185240
|
+
|
|
185241
|
+
// src/lib/bin/definitions/reshape.ts
|
|
185242
|
+
var reshapeBinary = {
|
|
185243
|
+
name: "reshape",
|
|
185244
|
+
versions: ["0.9.0"],
|
|
185245
|
+
// GitHub release assets
|
|
185246
|
+
urlTemplate: "https://github.com/fabianlindfors/reshape/releases/download/v{version}/reshape-{arch}",
|
|
185247
|
+
platformMapping: {
|
|
185248
|
+
darwin: {
|
|
185249
|
+
arm64: { platform: "darwin", arch: "darwin_aarch64" },
|
|
185250
|
+
x64: { platform: "darwin", arch: "darwin_amd64" }
|
|
185251
|
+
},
|
|
185252
|
+
linux: {
|
|
185253
|
+
arm64: { platform: "linux", arch: "linux_aarch64" },
|
|
185254
|
+
x64: { platform: "linux", arch: "linux_amd64" }
|
|
185255
|
+
}
|
|
185256
|
+
},
|
|
185257
|
+
// Raw binary, not a tarball
|
|
185258
|
+
type: "binary",
|
|
185259
|
+
// The executable name
|
|
185260
|
+
executables: ["reshape"]
|
|
185261
|
+
};
|
|
185262
|
+
|
|
184829
185263
|
// src/commands/check.tsx
|
|
185264
|
+
async function runReshapeCheck(migrationsDir) {
|
|
185265
|
+
try {
|
|
185266
|
+
const binary = await ensureBinary(reshapeBinary);
|
|
185267
|
+
const reshapePath = binary.executables["reshape"];
|
|
185268
|
+
return new Promise((resolve9) => {
|
|
185269
|
+
execFile7(reshapePath, ["check", "--dirs", migrationsDir], (err, _stdout, stderr) => {
|
|
185270
|
+
if (err) {
|
|
185271
|
+
const errorMsg = stderr.trim() || err.message;
|
|
185272
|
+
resolve9({ success: false, error: errorMsg });
|
|
185273
|
+
} else {
|
|
185274
|
+
resolve9({ success: true });
|
|
185275
|
+
}
|
|
185276
|
+
});
|
|
185277
|
+
});
|
|
185278
|
+
} catch (err) {
|
|
185279
|
+
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
185280
|
+
}
|
|
185281
|
+
}
|
|
184830
185282
|
function CheckUI() {
|
|
184831
185283
|
const [state, setState] = useState2({ status: "loading" });
|
|
184832
185284
|
useEffect2(() => {
|
|
184833
185285
|
async function load() {
|
|
184834
|
-
const configPath =
|
|
184835
|
-
if (!
|
|
185286
|
+
const configPath = path8.join(process.cwd(), "specific.hcl");
|
|
185287
|
+
if (!fs12.existsSync(configPath)) {
|
|
184836
185288
|
setState({
|
|
184837
185289
|
status: "error",
|
|
184838
185290
|
error: "No specific.hcl found in current directory"
|
|
@@ -184840,9 +185292,36 @@ function CheckUI() {
|
|
|
184840
185292
|
return;
|
|
184841
185293
|
}
|
|
184842
185294
|
try {
|
|
184843
|
-
const hcl =
|
|
185295
|
+
const hcl = fs12.readFileSync(configPath, "utf-8");
|
|
184844
185296
|
const config2 = await parseConfig(hcl);
|
|
184845
|
-
|
|
185297
|
+
const reshapeChecks2 = [];
|
|
185298
|
+
for (const pg of config2.postgres) {
|
|
185299
|
+
if (pg.reshape?.enabled) {
|
|
185300
|
+
const migrationsDir = path8.resolve(
|
|
185301
|
+
process.cwd(),
|
|
185302
|
+
pg.reshape.migrations_dir ?? "migrations"
|
|
185303
|
+
);
|
|
185304
|
+
if (!fs12.existsSync(migrationsDir)) {
|
|
185305
|
+
reshapeChecks2.push({
|
|
185306
|
+
databaseName: pg.name,
|
|
185307
|
+
migrationsDir: pg.reshape.migrations_dir ?? "migrations",
|
|
185308
|
+
success: false,
|
|
185309
|
+
error: `Migrations directory not found: ${migrationsDir}`
|
|
185310
|
+
});
|
|
185311
|
+
continue;
|
|
185312
|
+
}
|
|
185313
|
+
const result = await runReshapeCheck(migrationsDir);
|
|
185314
|
+
reshapeChecks2.push({
|
|
185315
|
+
databaseName: pg.name,
|
|
185316
|
+
migrationsDir: pg.reshape.migrations_dir ?? "migrations",
|
|
185317
|
+
...result
|
|
185318
|
+
});
|
|
185319
|
+
}
|
|
185320
|
+
}
|
|
185321
|
+
if (reshapeChecks2.some((r) => !r.success)) {
|
|
185322
|
+
process.exitCode = 1;
|
|
185323
|
+
}
|
|
185324
|
+
setState({ status: "success", config: config2, reshapeChecks: reshapeChecks2 });
|
|
184846
185325
|
} catch (err) {
|
|
184847
185326
|
setState({
|
|
184848
185327
|
status: "error",
|
|
@@ -184862,7 +185341,10 @@ function CheckUI() {
|
|
|
184862
185341
|
if (!config) {
|
|
184863
185342
|
return null;
|
|
184864
185343
|
}
|
|
184865
|
-
|
|
185344
|
+
const reshapeChecks = state.reshapeChecks ?? [];
|
|
185345
|
+
const reshapeErrors = reshapeChecks.filter((r) => !r.success);
|
|
185346
|
+
const hasReshapeErrors = reshapeErrors.length > 0;
|
|
185347
|
+
return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React3.createElement(Text3, { color: hasReshapeErrors ? "yellow" : "green" }, hasReshapeErrors ? "Configuration is valid but reshape migrations have errors" : "Configuration is valid"), config.builds.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Builds (", config.builds.length, "):"), config.builds.map((b) => /* @__PURE__ */ React3.createElement(Text3, { key: b.name }, " ", "- ", b.name))), config.services.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Services (", config.services.length, "):"), config.services.map((s) => /* @__PURE__ */ React3.createElement(Text3, { key: s.name }, " ", "- ", s.name))), config.postgres.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Postgres (", config.postgres.length, "):"), config.postgres.map((p) => /* @__PURE__ */ React3.createElement(Text3, { key: p.name }, " ", "- ", p.name, p.reshape?.enabled ? " (reshape enabled)" : ""))), reshapeChecks.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Reshape Migrations:"), reshapeChecks.map((r) => /* @__PURE__ */ React3.createElement(Box3, { key: r.databaseName, flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, null, " ", /* @__PURE__ */ React3.createElement(Text3, { color: r.success ? "green" : "red" }, r.success ? "pass" : "fail"), " ", r.databaseName, " (", r.migrationsDir, ")"), r.error && /* @__PURE__ */ React3.createElement(Text3, { color: "red" }, " ", r.error)))), config.redis.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Redis (", config.redis.length, "):"), config.redis.map((r) => /* @__PURE__ */ React3.createElement(Text3, { key: r.name }, " ", "- ", r.name))), config.storage.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Storage (", config.storage.length, "):"), config.storage.map((s) => /* @__PURE__ */ React3.createElement(Text3, { key: s.name }, " ", "- ", s.name))), config.configs.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Config (", config.configs.length, "):"), config.configs.map((c) => /* @__PURE__ */ React3.createElement(Text3, { key: c.name }, " ", "- ", c.name, c.default ? ` (default: ${c.default})` : ""))), config.environments.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Environments (", config.environments.length, "):"), config.environments.map((e) => /* @__PURE__ */ React3.createElement(Text3, { key: e.name }, " ", "- ", e.name))));
|
|
184866
185348
|
}
|
|
184867
185349
|
function checkCommand() {
|
|
184868
185350
|
render3(/* @__PURE__ */ React3.createElement(CheckUI, null));
|
|
@@ -184872,8 +185354,8 @@ function checkCommand() {
|
|
|
184872
185354
|
import React6, { useState as useState5, useEffect as useEffect3, useRef } from "react";
|
|
184873
185355
|
import { render as render4, Text as Text6, Box as Box6, useApp as useApp2, Static } from "ink";
|
|
184874
185356
|
import Spinner4 from "ink-spinner";
|
|
184875
|
-
import * as
|
|
184876
|
-
import * as
|
|
185357
|
+
import * as fs21 from "fs";
|
|
185358
|
+
import * as path18 from "path";
|
|
184877
185359
|
|
|
184878
185360
|
// node_modules/.pnpm/chokidar@5.0.0/node_modules/chokidar/index.js
|
|
184879
185361
|
import { EventEmitter } from "node:events";
|
|
@@ -184965,7 +185447,7 @@ var ReaddirpStream = class extends Readable {
|
|
|
184965
185447
|
this._directoryFilter = normalizeFilter(opts.directoryFilter);
|
|
184966
185448
|
const statMethod = opts.lstat ? lstat : stat;
|
|
184967
185449
|
if (wantBigintFsStats) {
|
|
184968
|
-
this._stat = (
|
|
185450
|
+
this._stat = (path26) => statMethod(path26, { bigint: true });
|
|
184969
185451
|
} else {
|
|
184970
185452
|
this._stat = statMethod;
|
|
184971
185453
|
}
|
|
@@ -184990,8 +185472,8 @@ var ReaddirpStream = class extends Readable {
|
|
|
184990
185472
|
const par = this.parent;
|
|
184991
185473
|
const fil = par && par.files;
|
|
184992
185474
|
if (fil && fil.length > 0) {
|
|
184993
|
-
const { path:
|
|
184994
|
-
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent,
|
|
185475
|
+
const { path: path26, depth } = par;
|
|
185476
|
+
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path26));
|
|
184995
185477
|
const awaited = await Promise.all(slice);
|
|
184996
185478
|
for (const entry of awaited) {
|
|
184997
185479
|
if (!entry)
|
|
@@ -185031,21 +185513,21 @@ var ReaddirpStream = class extends Readable {
|
|
|
185031
185513
|
this.reading = false;
|
|
185032
185514
|
}
|
|
185033
185515
|
}
|
|
185034
|
-
async _exploreDir(
|
|
185516
|
+
async _exploreDir(path26, depth) {
|
|
185035
185517
|
let files;
|
|
185036
185518
|
try {
|
|
185037
|
-
files = await readdir(
|
|
185519
|
+
files = await readdir(path26, this._rdOptions);
|
|
185038
185520
|
} catch (error) {
|
|
185039
185521
|
this._onError(error);
|
|
185040
185522
|
}
|
|
185041
|
-
return { files, depth, path:
|
|
185523
|
+
return { files, depth, path: path26 };
|
|
185042
185524
|
}
|
|
185043
|
-
async _formatEntry(dirent,
|
|
185525
|
+
async _formatEntry(dirent, path26) {
|
|
185044
185526
|
let entry;
|
|
185045
|
-
const
|
|
185527
|
+
const basename6 = this._isDirent ? dirent.name : dirent;
|
|
185046
185528
|
try {
|
|
185047
|
-
const fullPath = presolve(pjoin(
|
|
185048
|
-
entry = { path: prelative(this._root, fullPath), fullPath, basename:
|
|
185529
|
+
const fullPath = presolve(pjoin(path26, basename6));
|
|
185530
|
+
entry = { path: prelative(this._root, fullPath), fullPath, basename: basename6 };
|
|
185049
185531
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
185050
185532
|
} catch (err) {
|
|
185051
185533
|
this._onError(err);
|
|
@@ -185444,16 +185926,16 @@ var delFromSet = (main, prop, item) => {
|
|
|
185444
185926
|
};
|
|
185445
185927
|
var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
|
|
185446
185928
|
var FsWatchInstances = /* @__PURE__ */ new Map();
|
|
185447
|
-
function createFsWatchInstance(
|
|
185929
|
+
function createFsWatchInstance(path26, options2, listener, errHandler, emitRaw) {
|
|
185448
185930
|
const handleEvent = (rawEvent, evPath) => {
|
|
185449
|
-
listener(
|
|
185450
|
-
emitRaw(rawEvent, evPath, { watchedPath:
|
|
185451
|
-
if (evPath &&
|
|
185452
|
-
fsWatchBroadcast(sp.resolve(
|
|
185931
|
+
listener(path26);
|
|
185932
|
+
emitRaw(rawEvent, evPath, { watchedPath: path26 });
|
|
185933
|
+
if (evPath && path26 !== evPath) {
|
|
185934
|
+
fsWatchBroadcast(sp.resolve(path26, evPath), KEY_LISTENERS, sp.join(path26, evPath));
|
|
185453
185935
|
}
|
|
185454
185936
|
};
|
|
185455
185937
|
try {
|
|
185456
|
-
return fs_watch(
|
|
185938
|
+
return fs_watch(path26, {
|
|
185457
185939
|
persistent: options2.persistent
|
|
185458
185940
|
}, handleEvent);
|
|
185459
185941
|
} catch (error) {
|
|
@@ -185469,12 +185951,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
|
|
|
185469
185951
|
listener(val1, val2, val3);
|
|
185470
185952
|
});
|
|
185471
185953
|
};
|
|
185472
|
-
var setFsWatchListener = (
|
|
185954
|
+
var setFsWatchListener = (path26, fullPath, options2, handlers) => {
|
|
185473
185955
|
const { listener, errHandler, rawEmitter } = handlers;
|
|
185474
185956
|
let cont = FsWatchInstances.get(fullPath);
|
|
185475
185957
|
let watcher;
|
|
185476
185958
|
if (!options2.persistent) {
|
|
185477
|
-
watcher = createFsWatchInstance(
|
|
185959
|
+
watcher = createFsWatchInstance(path26, options2, listener, errHandler, rawEmitter);
|
|
185478
185960
|
if (!watcher)
|
|
185479
185961
|
return;
|
|
185480
185962
|
return watcher.close.bind(watcher);
|
|
@@ -185485,7 +185967,7 @@ var setFsWatchListener = (path24, fullPath, options2, handlers) => {
|
|
|
185485
185967
|
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
185486
185968
|
} else {
|
|
185487
185969
|
watcher = createFsWatchInstance(
|
|
185488
|
-
|
|
185970
|
+
path26,
|
|
185489
185971
|
options2,
|
|
185490
185972
|
fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
|
|
185491
185973
|
errHandler,
|
|
@@ -185500,7 +185982,7 @@ var setFsWatchListener = (path24, fullPath, options2, handlers) => {
|
|
|
185500
185982
|
cont.watcherUnusable = true;
|
|
185501
185983
|
if (isWindows && error.code === "EPERM") {
|
|
185502
185984
|
try {
|
|
185503
|
-
const fd = await open2(
|
|
185985
|
+
const fd = await open2(path26, "r");
|
|
185504
185986
|
await fd.close();
|
|
185505
185987
|
broadcastErr(error);
|
|
185506
185988
|
} catch (err) {
|
|
@@ -185531,7 +186013,7 @@ var setFsWatchListener = (path24, fullPath, options2, handlers) => {
|
|
|
185531
186013
|
};
|
|
185532
186014
|
};
|
|
185533
186015
|
var FsWatchFileInstances = /* @__PURE__ */ new Map();
|
|
185534
|
-
var setFsWatchFileListener = (
|
|
186016
|
+
var setFsWatchFileListener = (path26, fullPath, options2, handlers) => {
|
|
185535
186017
|
const { listener, rawEmitter } = handlers;
|
|
185536
186018
|
let cont = FsWatchFileInstances.get(fullPath);
|
|
185537
186019
|
const copts = cont && cont.options;
|
|
@@ -185553,7 +186035,7 @@ var setFsWatchFileListener = (path24, fullPath, options2, handlers) => {
|
|
|
185553
186035
|
});
|
|
185554
186036
|
const currmtime = curr.mtimeMs;
|
|
185555
186037
|
if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
|
|
185556
|
-
foreach(cont.listeners, (listener2) => listener2(
|
|
186038
|
+
foreach(cont.listeners, (listener2) => listener2(path26, curr));
|
|
185557
186039
|
}
|
|
185558
186040
|
})
|
|
185559
186041
|
};
|
|
@@ -185583,13 +186065,13 @@ var NodeFsHandler = class {
|
|
|
185583
186065
|
* @param listener on fs change
|
|
185584
186066
|
* @returns closer for the watcher instance
|
|
185585
186067
|
*/
|
|
185586
|
-
_watchWithNodeFs(
|
|
186068
|
+
_watchWithNodeFs(path26, listener) {
|
|
185587
186069
|
const opts = this.fsw.options;
|
|
185588
|
-
const directory = sp.dirname(
|
|
185589
|
-
const
|
|
186070
|
+
const directory = sp.dirname(path26);
|
|
186071
|
+
const basename6 = sp.basename(path26);
|
|
185590
186072
|
const parent = this.fsw._getWatchedDir(directory);
|
|
185591
|
-
parent.add(
|
|
185592
|
-
const absolutePath = sp.resolve(
|
|
186073
|
+
parent.add(basename6);
|
|
186074
|
+
const absolutePath = sp.resolve(path26);
|
|
185593
186075
|
const options2 = {
|
|
185594
186076
|
persistent: opts.persistent
|
|
185595
186077
|
};
|
|
@@ -185598,13 +186080,13 @@ var NodeFsHandler = class {
|
|
|
185598
186080
|
let closer;
|
|
185599
186081
|
if (opts.usePolling) {
|
|
185600
186082
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
185601
|
-
options2.interval = enableBin && isBinaryPath(
|
|
185602
|
-
closer = setFsWatchFileListener(
|
|
186083
|
+
options2.interval = enableBin && isBinaryPath(basename6) ? opts.binaryInterval : opts.interval;
|
|
186084
|
+
closer = setFsWatchFileListener(path26, absolutePath, options2, {
|
|
185603
186085
|
listener,
|
|
185604
186086
|
rawEmitter: this.fsw._emitRaw
|
|
185605
186087
|
});
|
|
185606
186088
|
} else {
|
|
185607
|
-
closer = setFsWatchListener(
|
|
186089
|
+
closer = setFsWatchListener(path26, absolutePath, options2, {
|
|
185608
186090
|
listener,
|
|
185609
186091
|
errHandler: this._boundHandleError,
|
|
185610
186092
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -185621,12 +186103,12 @@ var NodeFsHandler = class {
|
|
|
185621
186103
|
return;
|
|
185622
186104
|
}
|
|
185623
186105
|
const dirname8 = sp.dirname(file);
|
|
185624
|
-
const
|
|
186106
|
+
const basename6 = sp.basename(file);
|
|
185625
186107
|
const parent = this.fsw._getWatchedDir(dirname8);
|
|
185626
186108
|
let prevStats = stats;
|
|
185627
|
-
if (parent.has(
|
|
186109
|
+
if (parent.has(basename6))
|
|
185628
186110
|
return;
|
|
185629
|
-
const listener = async (
|
|
186111
|
+
const listener = async (path26, newStats) => {
|
|
185630
186112
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
|
|
185631
186113
|
return;
|
|
185632
186114
|
if (!newStats || newStats.mtimeMs === 0) {
|
|
@@ -185640,18 +186122,18 @@ var NodeFsHandler = class {
|
|
|
185640
186122
|
this.fsw._emit(EV.CHANGE, file, newStats2);
|
|
185641
186123
|
}
|
|
185642
186124
|
if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
|
|
185643
|
-
this.fsw._closeFile(
|
|
186125
|
+
this.fsw._closeFile(path26);
|
|
185644
186126
|
prevStats = newStats2;
|
|
185645
186127
|
const closer2 = this._watchWithNodeFs(file, listener);
|
|
185646
186128
|
if (closer2)
|
|
185647
|
-
this.fsw._addPathCloser(
|
|
186129
|
+
this.fsw._addPathCloser(path26, closer2);
|
|
185648
186130
|
} else {
|
|
185649
186131
|
prevStats = newStats2;
|
|
185650
186132
|
}
|
|
185651
186133
|
} catch (error) {
|
|
185652
|
-
this.fsw._remove(dirname8,
|
|
186134
|
+
this.fsw._remove(dirname8, basename6);
|
|
185653
186135
|
}
|
|
185654
|
-
} else if (parent.has(
|
|
186136
|
+
} else if (parent.has(basename6)) {
|
|
185655
186137
|
const at = newStats.atimeMs;
|
|
185656
186138
|
const mt = newStats.mtimeMs;
|
|
185657
186139
|
if (!at || at <= mt || mt !== prevStats.mtimeMs) {
|
|
@@ -185676,7 +186158,7 @@ var NodeFsHandler = class {
|
|
|
185676
186158
|
* @param item basename of this item
|
|
185677
186159
|
* @returns true if no more processing is needed for this entry.
|
|
185678
186160
|
*/
|
|
185679
|
-
async _handleSymlink(entry, directory,
|
|
186161
|
+
async _handleSymlink(entry, directory, path26, item) {
|
|
185680
186162
|
if (this.fsw.closed) {
|
|
185681
186163
|
return;
|
|
185682
186164
|
}
|
|
@@ -185686,7 +186168,7 @@ var NodeFsHandler = class {
|
|
|
185686
186168
|
this.fsw._incrReadyCount();
|
|
185687
186169
|
let linkPath;
|
|
185688
186170
|
try {
|
|
185689
|
-
linkPath = await fsrealpath(
|
|
186171
|
+
linkPath = await fsrealpath(path26);
|
|
185690
186172
|
} catch (e) {
|
|
185691
186173
|
this.fsw._emitReady();
|
|
185692
186174
|
return true;
|
|
@@ -185696,12 +186178,12 @@ var NodeFsHandler = class {
|
|
|
185696
186178
|
if (dir.has(item)) {
|
|
185697
186179
|
if (this.fsw._symlinkPaths.get(full) !== linkPath) {
|
|
185698
186180
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
185699
|
-
this.fsw._emit(EV.CHANGE,
|
|
186181
|
+
this.fsw._emit(EV.CHANGE, path26, entry.stats);
|
|
185700
186182
|
}
|
|
185701
186183
|
} else {
|
|
185702
186184
|
dir.add(item);
|
|
185703
186185
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
185704
|
-
this.fsw._emit(EV.ADD,
|
|
186186
|
+
this.fsw._emit(EV.ADD, path26, entry.stats);
|
|
185705
186187
|
}
|
|
185706
186188
|
this.fsw._emitReady();
|
|
185707
186189
|
return true;
|
|
@@ -185731,9 +186213,9 @@ var NodeFsHandler = class {
|
|
|
185731
186213
|
return;
|
|
185732
186214
|
}
|
|
185733
186215
|
const item = entry.path;
|
|
185734
|
-
let
|
|
186216
|
+
let path26 = sp.join(directory, item);
|
|
185735
186217
|
current.add(item);
|
|
185736
|
-
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory,
|
|
186218
|
+
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path26, item)) {
|
|
185737
186219
|
return;
|
|
185738
186220
|
}
|
|
185739
186221
|
if (this.fsw.closed) {
|
|
@@ -185742,11 +186224,11 @@ var NodeFsHandler = class {
|
|
|
185742
186224
|
}
|
|
185743
186225
|
if (item === target || !target && !previous.has(item)) {
|
|
185744
186226
|
this.fsw._incrReadyCount();
|
|
185745
|
-
|
|
185746
|
-
this._addToNodeFs(
|
|
186227
|
+
path26 = sp.join(dir, sp.relative(dir, path26));
|
|
186228
|
+
this._addToNodeFs(path26, initialAdd, wh, depth + 1);
|
|
185747
186229
|
}
|
|
185748
186230
|
}).on(EV.ERROR, this._boundHandleError);
|
|
185749
|
-
return new Promise((
|
|
186231
|
+
return new Promise((resolve9, reject) => {
|
|
185750
186232
|
if (!stream)
|
|
185751
186233
|
return reject();
|
|
185752
186234
|
stream.once(STR_END, () => {
|
|
@@ -185755,7 +186237,7 @@ var NodeFsHandler = class {
|
|
|
185755
186237
|
return;
|
|
185756
186238
|
}
|
|
185757
186239
|
const wasThrottled = throttler ? throttler.clear() : false;
|
|
185758
|
-
|
|
186240
|
+
resolve9(void 0);
|
|
185759
186241
|
previous.getChildren().filter((item) => {
|
|
185760
186242
|
return item !== directory && !current.has(item);
|
|
185761
186243
|
}).forEach((item) => {
|
|
@@ -185812,13 +186294,13 @@ var NodeFsHandler = class {
|
|
|
185812
186294
|
* @param depth Child path actually targeted for watch
|
|
185813
186295
|
* @param target Child path actually targeted for watch
|
|
185814
186296
|
*/
|
|
185815
|
-
async _addToNodeFs(
|
|
186297
|
+
async _addToNodeFs(path26, initialAdd, priorWh, depth, target) {
|
|
185816
186298
|
const ready = this.fsw._emitReady;
|
|
185817
|
-
if (this.fsw._isIgnored(
|
|
186299
|
+
if (this.fsw._isIgnored(path26) || this.fsw.closed) {
|
|
185818
186300
|
ready();
|
|
185819
186301
|
return false;
|
|
185820
186302
|
}
|
|
185821
|
-
const wh = this.fsw._getWatchHelpers(
|
|
186303
|
+
const wh = this.fsw._getWatchHelpers(path26);
|
|
185822
186304
|
if (priorWh) {
|
|
185823
186305
|
wh.filterPath = (entry) => priorWh.filterPath(entry);
|
|
185824
186306
|
wh.filterDir = (entry) => priorWh.filterDir(entry);
|
|
@@ -185834,8 +186316,8 @@ var NodeFsHandler = class {
|
|
|
185834
186316
|
const follow = this.fsw.options.followSymlinks;
|
|
185835
186317
|
let closer;
|
|
185836
186318
|
if (stats.isDirectory()) {
|
|
185837
|
-
const absPath = sp.resolve(
|
|
185838
|
-
const targetPath = follow ? await fsrealpath(
|
|
186319
|
+
const absPath = sp.resolve(path26);
|
|
186320
|
+
const targetPath = follow ? await fsrealpath(path26) : path26;
|
|
185839
186321
|
if (this.fsw.closed)
|
|
185840
186322
|
return;
|
|
185841
186323
|
closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
|
|
@@ -185845,29 +186327,29 @@ var NodeFsHandler = class {
|
|
|
185845
186327
|
this.fsw._symlinkPaths.set(absPath, targetPath);
|
|
185846
186328
|
}
|
|
185847
186329
|
} else if (stats.isSymbolicLink()) {
|
|
185848
|
-
const targetPath = follow ? await fsrealpath(
|
|
186330
|
+
const targetPath = follow ? await fsrealpath(path26) : path26;
|
|
185849
186331
|
if (this.fsw.closed)
|
|
185850
186332
|
return;
|
|
185851
186333
|
const parent = sp.dirname(wh.watchPath);
|
|
185852
186334
|
this.fsw._getWatchedDir(parent).add(wh.watchPath);
|
|
185853
186335
|
this.fsw._emit(EV.ADD, wh.watchPath, stats);
|
|
185854
|
-
closer = await this._handleDir(parent, stats, initialAdd, depth,
|
|
186336
|
+
closer = await this._handleDir(parent, stats, initialAdd, depth, path26, wh, targetPath);
|
|
185855
186337
|
if (this.fsw.closed)
|
|
185856
186338
|
return;
|
|
185857
186339
|
if (targetPath !== void 0) {
|
|
185858
|
-
this.fsw._symlinkPaths.set(sp.resolve(
|
|
186340
|
+
this.fsw._symlinkPaths.set(sp.resolve(path26), targetPath);
|
|
185859
186341
|
}
|
|
185860
186342
|
} else {
|
|
185861
186343
|
closer = this._handleFile(wh.watchPath, stats, initialAdd);
|
|
185862
186344
|
}
|
|
185863
186345
|
ready();
|
|
185864
186346
|
if (closer)
|
|
185865
|
-
this.fsw._addPathCloser(
|
|
186347
|
+
this.fsw._addPathCloser(path26, closer);
|
|
185866
186348
|
return false;
|
|
185867
186349
|
} catch (error) {
|
|
185868
186350
|
if (this.fsw._handleError(error)) {
|
|
185869
186351
|
ready();
|
|
185870
|
-
return
|
|
186352
|
+
return path26;
|
|
185871
186353
|
}
|
|
185872
186354
|
}
|
|
185873
186355
|
}
|
|
@@ -185910,24 +186392,24 @@ function createPattern(matcher) {
|
|
|
185910
186392
|
}
|
|
185911
186393
|
return () => false;
|
|
185912
186394
|
}
|
|
185913
|
-
function normalizePath(
|
|
185914
|
-
if (typeof
|
|
186395
|
+
function normalizePath(path26) {
|
|
186396
|
+
if (typeof path26 !== "string")
|
|
185915
186397
|
throw new Error("string expected");
|
|
185916
|
-
|
|
185917
|
-
|
|
186398
|
+
path26 = sp2.normalize(path26);
|
|
186399
|
+
path26 = path26.replace(/\\/g, "/");
|
|
185918
186400
|
let prepend = false;
|
|
185919
|
-
if (
|
|
186401
|
+
if (path26.startsWith("//"))
|
|
185920
186402
|
prepend = true;
|
|
185921
|
-
|
|
186403
|
+
path26 = path26.replace(DOUBLE_SLASH_RE, "/");
|
|
185922
186404
|
if (prepend)
|
|
185923
|
-
|
|
185924
|
-
return
|
|
186405
|
+
path26 = "/" + path26;
|
|
186406
|
+
return path26;
|
|
185925
186407
|
}
|
|
185926
186408
|
function matchPatterns(patterns, testString, stats) {
|
|
185927
|
-
const
|
|
186409
|
+
const path26 = normalizePath(testString);
|
|
185928
186410
|
for (let index = 0; index < patterns.length; index++) {
|
|
185929
186411
|
const pattern = patterns[index];
|
|
185930
|
-
if (pattern(
|
|
186412
|
+
if (pattern(path26, stats)) {
|
|
185931
186413
|
return true;
|
|
185932
186414
|
}
|
|
185933
186415
|
}
|
|
@@ -185965,19 +186447,19 @@ var toUnix = (string) => {
|
|
|
185965
186447
|
}
|
|
185966
186448
|
return str;
|
|
185967
186449
|
};
|
|
185968
|
-
var normalizePathToUnix = (
|
|
185969
|
-
var normalizeIgnored = (cwd = "") => (
|
|
185970
|
-
if (typeof
|
|
185971
|
-
return normalizePathToUnix(sp2.isAbsolute(
|
|
186450
|
+
var normalizePathToUnix = (path26) => toUnix(sp2.normalize(toUnix(path26)));
|
|
186451
|
+
var normalizeIgnored = (cwd = "") => (path26) => {
|
|
186452
|
+
if (typeof path26 === "string") {
|
|
186453
|
+
return normalizePathToUnix(sp2.isAbsolute(path26) ? path26 : sp2.join(cwd, path26));
|
|
185972
186454
|
} else {
|
|
185973
|
-
return
|
|
186455
|
+
return path26;
|
|
185974
186456
|
}
|
|
185975
186457
|
};
|
|
185976
|
-
var getAbsolutePath = (
|
|
185977
|
-
if (sp2.isAbsolute(
|
|
185978
|
-
return
|
|
186458
|
+
var getAbsolutePath = (path26, cwd) => {
|
|
186459
|
+
if (sp2.isAbsolute(path26)) {
|
|
186460
|
+
return path26;
|
|
185979
186461
|
}
|
|
185980
|
-
return sp2.join(cwd,
|
|
186462
|
+
return sp2.join(cwd, path26);
|
|
185981
186463
|
};
|
|
185982
186464
|
var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
|
|
185983
186465
|
var DirEntry = class {
|
|
@@ -186042,10 +186524,10 @@ var WatchHelper = class {
|
|
|
186042
186524
|
dirParts;
|
|
186043
186525
|
followSymlinks;
|
|
186044
186526
|
statMethod;
|
|
186045
|
-
constructor(
|
|
186527
|
+
constructor(path26, follow, fsw) {
|
|
186046
186528
|
this.fsw = fsw;
|
|
186047
|
-
const watchPath =
|
|
186048
|
-
this.path =
|
|
186529
|
+
const watchPath = path26;
|
|
186530
|
+
this.path = path26 = path26.replace(REPLACER_RE, "");
|
|
186049
186531
|
this.watchPath = watchPath;
|
|
186050
186532
|
this.fullWatchPath = sp2.resolve(watchPath);
|
|
186051
186533
|
this.dirParts = [];
|
|
@@ -186185,20 +186667,20 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186185
186667
|
this._closePromise = void 0;
|
|
186186
186668
|
let paths = unifyPaths(paths_);
|
|
186187
186669
|
if (cwd) {
|
|
186188
|
-
paths = paths.map((
|
|
186189
|
-
const absPath = getAbsolutePath(
|
|
186670
|
+
paths = paths.map((path26) => {
|
|
186671
|
+
const absPath = getAbsolutePath(path26, cwd);
|
|
186190
186672
|
return absPath;
|
|
186191
186673
|
});
|
|
186192
186674
|
}
|
|
186193
|
-
paths.forEach((
|
|
186194
|
-
this._removeIgnoredPath(
|
|
186675
|
+
paths.forEach((path26) => {
|
|
186676
|
+
this._removeIgnoredPath(path26);
|
|
186195
186677
|
});
|
|
186196
186678
|
this._userIgnored = void 0;
|
|
186197
186679
|
if (!this._readyCount)
|
|
186198
186680
|
this._readyCount = 0;
|
|
186199
186681
|
this._readyCount += paths.length;
|
|
186200
|
-
Promise.all(paths.map(async (
|
|
186201
|
-
const res = await this._nodeFsHandler._addToNodeFs(
|
|
186682
|
+
Promise.all(paths.map(async (path26) => {
|
|
186683
|
+
const res = await this._nodeFsHandler._addToNodeFs(path26, !_internal, void 0, 0, _origAdd);
|
|
186202
186684
|
if (res)
|
|
186203
186685
|
this._emitReady();
|
|
186204
186686
|
return res;
|
|
@@ -186220,17 +186702,17 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186220
186702
|
return this;
|
|
186221
186703
|
const paths = unifyPaths(paths_);
|
|
186222
186704
|
const { cwd } = this.options;
|
|
186223
|
-
paths.forEach((
|
|
186224
|
-
if (!sp2.isAbsolute(
|
|
186705
|
+
paths.forEach((path26) => {
|
|
186706
|
+
if (!sp2.isAbsolute(path26) && !this._closers.has(path26)) {
|
|
186225
186707
|
if (cwd)
|
|
186226
|
-
|
|
186227
|
-
|
|
186708
|
+
path26 = sp2.join(cwd, path26);
|
|
186709
|
+
path26 = sp2.resolve(path26);
|
|
186228
186710
|
}
|
|
186229
|
-
this._closePath(
|
|
186230
|
-
this._addIgnoredPath(
|
|
186231
|
-
if (this._watched.has(
|
|
186711
|
+
this._closePath(path26);
|
|
186712
|
+
this._addIgnoredPath(path26);
|
|
186713
|
+
if (this._watched.has(path26)) {
|
|
186232
186714
|
this._addIgnoredPath({
|
|
186233
|
-
path:
|
|
186715
|
+
path: path26,
|
|
186234
186716
|
recursive: true
|
|
186235
186717
|
});
|
|
186236
186718
|
}
|
|
@@ -186294,38 +186776,38 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186294
186776
|
* @param stats arguments to be passed with event
|
|
186295
186777
|
* @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
|
|
186296
186778
|
*/
|
|
186297
|
-
async _emit(event,
|
|
186779
|
+
async _emit(event, path26, stats) {
|
|
186298
186780
|
if (this.closed)
|
|
186299
186781
|
return;
|
|
186300
186782
|
const opts = this.options;
|
|
186301
186783
|
if (isWindows)
|
|
186302
|
-
|
|
186784
|
+
path26 = sp2.normalize(path26);
|
|
186303
186785
|
if (opts.cwd)
|
|
186304
|
-
|
|
186305
|
-
const args = [
|
|
186786
|
+
path26 = sp2.relative(opts.cwd, path26);
|
|
186787
|
+
const args = [path26];
|
|
186306
186788
|
if (stats != null)
|
|
186307
186789
|
args.push(stats);
|
|
186308
186790
|
const awf = opts.awaitWriteFinish;
|
|
186309
186791
|
let pw;
|
|
186310
|
-
if (awf && (pw = this._pendingWrites.get(
|
|
186792
|
+
if (awf && (pw = this._pendingWrites.get(path26))) {
|
|
186311
186793
|
pw.lastChange = /* @__PURE__ */ new Date();
|
|
186312
186794
|
return this;
|
|
186313
186795
|
}
|
|
186314
186796
|
if (opts.atomic) {
|
|
186315
186797
|
if (event === EVENTS.UNLINK) {
|
|
186316
|
-
this._pendingUnlinks.set(
|
|
186798
|
+
this._pendingUnlinks.set(path26, [event, ...args]);
|
|
186317
186799
|
setTimeout(() => {
|
|
186318
|
-
this._pendingUnlinks.forEach((entry,
|
|
186800
|
+
this._pendingUnlinks.forEach((entry, path27) => {
|
|
186319
186801
|
this.emit(...entry);
|
|
186320
186802
|
this.emit(EVENTS.ALL, ...entry);
|
|
186321
|
-
this._pendingUnlinks.delete(
|
|
186803
|
+
this._pendingUnlinks.delete(path27);
|
|
186322
186804
|
});
|
|
186323
186805
|
}, typeof opts.atomic === "number" ? opts.atomic : 100);
|
|
186324
186806
|
return this;
|
|
186325
186807
|
}
|
|
186326
|
-
if (event === EVENTS.ADD && this._pendingUnlinks.has(
|
|
186808
|
+
if (event === EVENTS.ADD && this._pendingUnlinks.has(path26)) {
|
|
186327
186809
|
event = EVENTS.CHANGE;
|
|
186328
|
-
this._pendingUnlinks.delete(
|
|
186810
|
+
this._pendingUnlinks.delete(path26);
|
|
186329
186811
|
}
|
|
186330
186812
|
}
|
|
186331
186813
|
if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
|
|
@@ -186343,16 +186825,16 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186343
186825
|
this.emitWithAll(event, args);
|
|
186344
186826
|
}
|
|
186345
186827
|
};
|
|
186346
|
-
this._awaitWriteFinish(
|
|
186828
|
+
this._awaitWriteFinish(path26, awf.stabilityThreshold, event, awfEmit);
|
|
186347
186829
|
return this;
|
|
186348
186830
|
}
|
|
186349
186831
|
if (event === EVENTS.CHANGE) {
|
|
186350
|
-
const isThrottled = !this._throttle(EVENTS.CHANGE,
|
|
186832
|
+
const isThrottled = !this._throttle(EVENTS.CHANGE, path26, 50);
|
|
186351
186833
|
if (isThrottled)
|
|
186352
186834
|
return this;
|
|
186353
186835
|
}
|
|
186354
186836
|
if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
|
|
186355
|
-
const fullPath = opts.cwd ? sp2.join(opts.cwd,
|
|
186837
|
+
const fullPath = opts.cwd ? sp2.join(opts.cwd, path26) : path26;
|
|
186356
186838
|
let stats2;
|
|
186357
186839
|
try {
|
|
186358
186840
|
stats2 = await stat3(fullPath);
|
|
@@ -186383,23 +186865,23 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186383
186865
|
* @param timeout duration of time to suppress duplicate actions
|
|
186384
186866
|
* @returns tracking object or false if action should be suppressed
|
|
186385
186867
|
*/
|
|
186386
|
-
_throttle(actionType,
|
|
186868
|
+
_throttle(actionType, path26, timeout) {
|
|
186387
186869
|
if (!this._throttled.has(actionType)) {
|
|
186388
186870
|
this._throttled.set(actionType, /* @__PURE__ */ new Map());
|
|
186389
186871
|
}
|
|
186390
186872
|
const action = this._throttled.get(actionType);
|
|
186391
186873
|
if (!action)
|
|
186392
186874
|
throw new Error("invalid throttle");
|
|
186393
|
-
const actionPath = action.get(
|
|
186875
|
+
const actionPath = action.get(path26);
|
|
186394
186876
|
if (actionPath) {
|
|
186395
186877
|
actionPath.count++;
|
|
186396
186878
|
return false;
|
|
186397
186879
|
}
|
|
186398
186880
|
let timeoutObject;
|
|
186399
186881
|
const clear = () => {
|
|
186400
|
-
const item = action.get(
|
|
186882
|
+
const item = action.get(path26);
|
|
186401
186883
|
const count = item ? item.count : 0;
|
|
186402
|
-
action.delete(
|
|
186884
|
+
action.delete(path26);
|
|
186403
186885
|
clearTimeout(timeoutObject);
|
|
186404
186886
|
if (item)
|
|
186405
186887
|
clearTimeout(item.timeoutObject);
|
|
@@ -186407,7 +186889,7 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186407
186889
|
};
|
|
186408
186890
|
timeoutObject = setTimeout(clear, timeout);
|
|
186409
186891
|
const thr = { timeoutObject, clear, count: 0 };
|
|
186410
|
-
action.set(
|
|
186892
|
+
action.set(path26, thr);
|
|
186411
186893
|
return thr;
|
|
186412
186894
|
}
|
|
186413
186895
|
_incrReadyCount() {
|
|
@@ -186421,44 +186903,44 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186421
186903
|
* @param event
|
|
186422
186904
|
* @param awfEmit Callback to be called when ready for event to be emitted.
|
|
186423
186905
|
*/
|
|
186424
|
-
_awaitWriteFinish(
|
|
186906
|
+
_awaitWriteFinish(path26, threshold, event, awfEmit) {
|
|
186425
186907
|
const awf = this.options.awaitWriteFinish;
|
|
186426
186908
|
if (typeof awf !== "object")
|
|
186427
186909
|
return;
|
|
186428
186910
|
const pollInterval = awf.pollInterval;
|
|
186429
186911
|
let timeoutHandler;
|
|
186430
|
-
let fullPath =
|
|
186431
|
-
if (this.options.cwd && !sp2.isAbsolute(
|
|
186432
|
-
fullPath = sp2.join(this.options.cwd,
|
|
186912
|
+
let fullPath = path26;
|
|
186913
|
+
if (this.options.cwd && !sp2.isAbsolute(path26)) {
|
|
186914
|
+
fullPath = sp2.join(this.options.cwd, path26);
|
|
186433
186915
|
}
|
|
186434
186916
|
const now = /* @__PURE__ */ new Date();
|
|
186435
186917
|
const writes = this._pendingWrites;
|
|
186436
186918
|
function awaitWriteFinishFn(prevStat) {
|
|
186437
186919
|
statcb(fullPath, (err, curStat) => {
|
|
186438
|
-
if (err || !writes.has(
|
|
186920
|
+
if (err || !writes.has(path26)) {
|
|
186439
186921
|
if (err && err.code !== "ENOENT")
|
|
186440
186922
|
awfEmit(err);
|
|
186441
186923
|
return;
|
|
186442
186924
|
}
|
|
186443
186925
|
const now2 = Number(/* @__PURE__ */ new Date());
|
|
186444
186926
|
if (prevStat && curStat.size !== prevStat.size) {
|
|
186445
|
-
writes.get(
|
|
186927
|
+
writes.get(path26).lastChange = now2;
|
|
186446
186928
|
}
|
|
186447
|
-
const pw = writes.get(
|
|
186929
|
+
const pw = writes.get(path26);
|
|
186448
186930
|
const df = now2 - pw.lastChange;
|
|
186449
186931
|
if (df >= threshold) {
|
|
186450
|
-
writes.delete(
|
|
186932
|
+
writes.delete(path26);
|
|
186451
186933
|
awfEmit(void 0, curStat);
|
|
186452
186934
|
} else {
|
|
186453
186935
|
timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
|
|
186454
186936
|
}
|
|
186455
186937
|
});
|
|
186456
186938
|
}
|
|
186457
|
-
if (!writes.has(
|
|
186458
|
-
writes.set(
|
|
186939
|
+
if (!writes.has(path26)) {
|
|
186940
|
+
writes.set(path26, {
|
|
186459
186941
|
lastChange: now,
|
|
186460
186942
|
cancelWait: () => {
|
|
186461
|
-
writes.delete(
|
|
186943
|
+
writes.delete(path26);
|
|
186462
186944
|
clearTimeout(timeoutHandler);
|
|
186463
186945
|
return event;
|
|
186464
186946
|
}
|
|
@@ -186469,8 +186951,8 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186469
186951
|
/**
|
|
186470
186952
|
* Determines whether user has asked to ignore this path.
|
|
186471
186953
|
*/
|
|
186472
|
-
_isIgnored(
|
|
186473
|
-
if (this.options.atomic && DOT_RE.test(
|
|
186954
|
+
_isIgnored(path26, stats) {
|
|
186955
|
+
if (this.options.atomic && DOT_RE.test(path26))
|
|
186474
186956
|
return true;
|
|
186475
186957
|
if (!this._userIgnored) {
|
|
186476
186958
|
const { cwd } = this.options;
|
|
@@ -186480,17 +186962,17 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186480
186962
|
const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
|
|
186481
186963
|
this._userIgnored = anymatch(list, void 0);
|
|
186482
186964
|
}
|
|
186483
|
-
return this._userIgnored(
|
|
186965
|
+
return this._userIgnored(path26, stats);
|
|
186484
186966
|
}
|
|
186485
|
-
_isntIgnored(
|
|
186486
|
-
return !this._isIgnored(
|
|
186967
|
+
_isntIgnored(path26, stat4) {
|
|
186968
|
+
return !this._isIgnored(path26, stat4);
|
|
186487
186969
|
}
|
|
186488
186970
|
/**
|
|
186489
186971
|
* Provides a set of common helpers and properties relating to symlink handling.
|
|
186490
186972
|
* @param path file or directory pattern being watched
|
|
186491
186973
|
*/
|
|
186492
|
-
_getWatchHelpers(
|
|
186493
|
-
return new WatchHelper(
|
|
186974
|
+
_getWatchHelpers(path26) {
|
|
186975
|
+
return new WatchHelper(path26, this.options.followSymlinks, this);
|
|
186494
186976
|
}
|
|
186495
186977
|
// Directory helpers
|
|
186496
186978
|
// -----------------
|
|
@@ -186522,63 +187004,63 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186522
187004
|
* @param item base path of item/directory
|
|
186523
187005
|
*/
|
|
186524
187006
|
_remove(directory, item, isDirectory) {
|
|
186525
|
-
const
|
|
186526
|
-
const fullPath = sp2.resolve(
|
|
186527
|
-
isDirectory = isDirectory != null ? isDirectory : this._watched.has(
|
|
186528
|
-
if (!this._throttle("remove",
|
|
187007
|
+
const path26 = sp2.join(directory, item);
|
|
187008
|
+
const fullPath = sp2.resolve(path26);
|
|
187009
|
+
isDirectory = isDirectory != null ? isDirectory : this._watched.has(path26) || this._watched.has(fullPath);
|
|
187010
|
+
if (!this._throttle("remove", path26, 100))
|
|
186529
187011
|
return;
|
|
186530
187012
|
if (!isDirectory && this._watched.size === 1) {
|
|
186531
187013
|
this.add(directory, item, true);
|
|
186532
187014
|
}
|
|
186533
|
-
const wp = this._getWatchedDir(
|
|
187015
|
+
const wp = this._getWatchedDir(path26);
|
|
186534
187016
|
const nestedDirectoryChildren = wp.getChildren();
|
|
186535
|
-
nestedDirectoryChildren.forEach((nested) => this._remove(
|
|
187017
|
+
nestedDirectoryChildren.forEach((nested) => this._remove(path26, nested));
|
|
186536
187018
|
const parent = this._getWatchedDir(directory);
|
|
186537
187019
|
const wasTracked = parent.has(item);
|
|
186538
187020
|
parent.remove(item);
|
|
186539
187021
|
if (this._symlinkPaths.has(fullPath)) {
|
|
186540
187022
|
this._symlinkPaths.delete(fullPath);
|
|
186541
187023
|
}
|
|
186542
|
-
let relPath =
|
|
187024
|
+
let relPath = path26;
|
|
186543
187025
|
if (this.options.cwd)
|
|
186544
|
-
relPath = sp2.relative(this.options.cwd,
|
|
187026
|
+
relPath = sp2.relative(this.options.cwd, path26);
|
|
186545
187027
|
if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
|
|
186546
187028
|
const event = this._pendingWrites.get(relPath).cancelWait();
|
|
186547
187029
|
if (event === EVENTS.ADD)
|
|
186548
187030
|
return;
|
|
186549
187031
|
}
|
|
186550
|
-
this._watched.delete(
|
|
187032
|
+
this._watched.delete(path26);
|
|
186551
187033
|
this._watched.delete(fullPath);
|
|
186552
187034
|
const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
|
|
186553
|
-
if (wasTracked && !this._isIgnored(
|
|
186554
|
-
this._emit(eventName,
|
|
186555
|
-
this._closePath(
|
|
187035
|
+
if (wasTracked && !this._isIgnored(path26))
|
|
187036
|
+
this._emit(eventName, path26);
|
|
187037
|
+
this._closePath(path26);
|
|
186556
187038
|
}
|
|
186557
187039
|
/**
|
|
186558
187040
|
* Closes all watchers for a path
|
|
186559
187041
|
*/
|
|
186560
|
-
_closePath(
|
|
186561
|
-
this._closeFile(
|
|
186562
|
-
const dir = sp2.dirname(
|
|
186563
|
-
this._getWatchedDir(dir).remove(sp2.basename(
|
|
187042
|
+
_closePath(path26) {
|
|
187043
|
+
this._closeFile(path26);
|
|
187044
|
+
const dir = sp2.dirname(path26);
|
|
187045
|
+
this._getWatchedDir(dir).remove(sp2.basename(path26));
|
|
186564
187046
|
}
|
|
186565
187047
|
/**
|
|
186566
187048
|
* Closes only file-specific watchers
|
|
186567
187049
|
*/
|
|
186568
|
-
_closeFile(
|
|
186569
|
-
const closers = this._closers.get(
|
|
187050
|
+
_closeFile(path26) {
|
|
187051
|
+
const closers = this._closers.get(path26);
|
|
186570
187052
|
if (!closers)
|
|
186571
187053
|
return;
|
|
186572
187054
|
closers.forEach((closer) => closer());
|
|
186573
|
-
this._closers.delete(
|
|
187055
|
+
this._closers.delete(path26);
|
|
186574
187056
|
}
|
|
186575
|
-
_addPathCloser(
|
|
187057
|
+
_addPathCloser(path26, closer) {
|
|
186576
187058
|
if (!closer)
|
|
186577
187059
|
return;
|
|
186578
|
-
let list = this._closers.get(
|
|
187060
|
+
let list = this._closers.get(path26);
|
|
186579
187061
|
if (!list) {
|
|
186580
187062
|
list = [];
|
|
186581
|
-
this._closers.set(
|
|
187063
|
+
this._closers.set(path26, list);
|
|
186582
187064
|
}
|
|
186583
187065
|
list.push(closer);
|
|
186584
187066
|
}
|
|
@@ -186627,8 +187109,8 @@ var PortAllocator = class {
|
|
|
186627
187109
|
};
|
|
186628
187110
|
|
|
186629
187111
|
// src/lib/dev/stable-port-allocator.ts
|
|
186630
|
-
import * as
|
|
186631
|
-
import * as
|
|
187112
|
+
import * as fs13 from "fs";
|
|
187113
|
+
import * as path9 from "path";
|
|
186632
187114
|
var PORT_RANGE_START2 = 4e4;
|
|
186633
187115
|
var PORT_RANGE_END2 = 49999;
|
|
186634
187116
|
var StablePortAllocator = class {
|
|
@@ -186637,16 +187119,16 @@ var StablePortAllocator = class {
|
|
|
186637
187119
|
savedPorts = {};
|
|
186638
187120
|
usedPorts = /* @__PURE__ */ new Set();
|
|
186639
187121
|
constructor(projectRoot, key = "default") {
|
|
186640
|
-
this.portsDir =
|
|
186641
|
-
this.portsFilePath =
|
|
187122
|
+
this.portsDir = path9.join(projectRoot, ".specific", "keys", key);
|
|
187123
|
+
this.portsFilePath = path9.join(this.portsDir, "ports.json");
|
|
186642
187124
|
this.loadPorts();
|
|
186643
187125
|
}
|
|
186644
187126
|
loadPorts() {
|
|
186645
|
-
if (!
|
|
187127
|
+
if (!fs13.existsSync(this.portsFilePath)) {
|
|
186646
187128
|
return;
|
|
186647
187129
|
}
|
|
186648
187130
|
try {
|
|
186649
|
-
const content =
|
|
187131
|
+
const content = fs13.readFileSync(this.portsFilePath, "utf-8");
|
|
186650
187132
|
const data = JSON.parse(content);
|
|
186651
187133
|
if (data.version === 1 && data.ports) {
|
|
186652
187134
|
this.savedPorts = data.ports;
|
|
@@ -186659,14 +187141,14 @@ var StablePortAllocator = class {
|
|
|
186659
187141
|
}
|
|
186660
187142
|
}
|
|
186661
187143
|
savePorts() {
|
|
186662
|
-
if (!
|
|
186663
|
-
|
|
187144
|
+
if (!fs13.existsSync(this.portsDir)) {
|
|
187145
|
+
fs13.mkdirSync(this.portsDir, { recursive: true });
|
|
186664
187146
|
}
|
|
186665
187147
|
const data = {
|
|
186666
187148
|
version: 1,
|
|
186667
187149
|
ports: this.savedPorts
|
|
186668
187150
|
};
|
|
186669
|
-
|
|
187151
|
+
fs13.writeFileSync(this.portsFilePath, JSON.stringify(data, null, 2));
|
|
186670
187152
|
}
|
|
186671
187153
|
allocateRandom() {
|
|
186672
187154
|
const rangeSize = PORT_RANGE_END2 - PORT_RANGE_START2 + 1;
|
|
@@ -186697,329 +187179,6 @@ import * as fs14 from "fs";
|
|
|
186697
187179
|
import * as path10 from "path";
|
|
186698
187180
|
import * as net from "net";
|
|
186699
187181
|
import { spawn } from "child_process";
|
|
186700
|
-
|
|
186701
|
-
// src/lib/bin/types.ts
|
|
186702
|
-
var BinaryNotFoundError = class extends Error {
|
|
186703
|
-
constructor(binaryName, version, platformInfo) {
|
|
186704
|
-
super(
|
|
186705
|
-
`Binary "${binaryName}" version ${version} is not available for ${platformInfo.platform}-${platformInfo.arch}`
|
|
186706
|
-
);
|
|
186707
|
-
this.binaryName = binaryName;
|
|
186708
|
-
this.version = version;
|
|
186709
|
-
this.platformInfo = platformInfo;
|
|
186710
|
-
this.name = "BinaryNotFoundError";
|
|
186711
|
-
}
|
|
186712
|
-
};
|
|
186713
|
-
var DownloadError = class extends Error {
|
|
186714
|
-
constructor(url, statusCode, cause) {
|
|
186715
|
-
super(
|
|
186716
|
-
statusCode ? `Failed to download ${url}: HTTP ${statusCode}` : `Failed to download ${url}: ${cause?.message || "Unknown error"}`
|
|
186717
|
-
);
|
|
186718
|
-
this.url = url;
|
|
186719
|
-
this.statusCode = statusCode;
|
|
186720
|
-
this.cause = cause;
|
|
186721
|
-
this.name = "DownloadError";
|
|
186722
|
-
}
|
|
186723
|
-
};
|
|
186724
|
-
var ExtractionError = class extends Error {
|
|
186725
|
-
constructor(archivePath, cause) {
|
|
186726
|
-
super(
|
|
186727
|
-
`Failed to extract ${archivePath}: ${cause?.message || "Unknown error"}`
|
|
186728
|
-
);
|
|
186729
|
-
this.archivePath = archivePath;
|
|
186730
|
-
this.cause = cause;
|
|
186731
|
-
this.name = "ExtractionError";
|
|
186732
|
-
}
|
|
186733
|
-
};
|
|
186734
|
-
|
|
186735
|
-
// src/lib/bin/manager.ts
|
|
186736
|
-
import * as fs13 from "fs";
|
|
186737
|
-
import * as path9 from "path";
|
|
186738
|
-
import * as os6 from "os";
|
|
186739
|
-
import { createReadStream } from "fs";
|
|
186740
|
-
import { createTarExtractor, extractTo } from "tar-vern";
|
|
186741
|
-
function getLibraryEnv(binary) {
|
|
186742
|
-
if (!binary.libraryPath) {
|
|
186743
|
-
return {};
|
|
186744
|
-
}
|
|
186745
|
-
const platform4 = os6.platform();
|
|
186746
|
-
if (platform4 === "darwin") {
|
|
186747
|
-
return { DYLD_LIBRARY_PATH: binary.libraryPath };
|
|
186748
|
-
} else if (platform4 === "linux") {
|
|
186749
|
-
return { LD_LIBRARY_PATH: binary.libraryPath };
|
|
186750
|
-
}
|
|
186751
|
-
return {};
|
|
186752
|
-
}
|
|
186753
|
-
function getBinBaseDir() {
|
|
186754
|
-
return path9.join(os6.homedir(), ".specific", "bin");
|
|
186755
|
-
}
|
|
186756
|
-
function getPlatformInfo() {
|
|
186757
|
-
const platform4 = os6.platform();
|
|
186758
|
-
const arch3 = os6.arch();
|
|
186759
|
-
if (platform4 !== "darwin" && platform4 !== "linux") {
|
|
186760
|
-
throw new Error(
|
|
186761
|
-
`Unsupported platform: ${platform4}. Only macOS and Linux are supported.`
|
|
186762
|
-
);
|
|
186763
|
-
}
|
|
186764
|
-
const archStr = arch3;
|
|
186765
|
-
let mappedArch;
|
|
186766
|
-
if (archStr === "x64" || archStr === "x86_64") {
|
|
186767
|
-
mappedArch = "x64";
|
|
186768
|
-
} else if (archStr === "arm64" || archStr === "aarch64") {
|
|
186769
|
-
mappedArch = "arm64";
|
|
186770
|
-
} else {
|
|
186771
|
-
throw new Error(
|
|
186772
|
-
`Unsupported architecture: ${arch3}. Only x64 and arm64 are supported.`
|
|
186773
|
-
);
|
|
186774
|
-
}
|
|
186775
|
-
return { platform: platform4, arch: mappedArch };
|
|
186776
|
-
}
|
|
186777
|
-
function getBinaryDir(definition, version, platformInfo) {
|
|
186778
|
-
return path9.join(
|
|
186779
|
-
getBinBaseDir(),
|
|
186780
|
-
definition.name,
|
|
186781
|
-
version,
|
|
186782
|
-
`${platformInfo.platform}-${platformInfo.arch}`
|
|
186783
|
-
);
|
|
186784
|
-
}
|
|
186785
|
-
function isBinaryInstalled(definition, version, platformInfo) {
|
|
186786
|
-
const binDir = getBinaryDir(definition, version, platformInfo);
|
|
186787
|
-
for (const execPath of definition.executables) {
|
|
186788
|
-
const fullPath = path9.join(binDir, execPath);
|
|
186789
|
-
if (!fs13.existsSync(fullPath)) {
|
|
186790
|
-
return false;
|
|
186791
|
-
}
|
|
186792
|
-
}
|
|
186793
|
-
return true;
|
|
186794
|
-
}
|
|
186795
|
-
function buildDownloadUrl(definition, version, platformInfo) {
|
|
186796
|
-
const mapping = definition.platformMapping[platformInfo.platform]?.[platformInfo.arch];
|
|
186797
|
-
if (!mapping) {
|
|
186798
|
-
throw new BinaryNotFoundError(definition.name, version, platformInfo);
|
|
186799
|
-
}
|
|
186800
|
-
return definition.urlTemplate.replace(/{version}/g, version).replace(/{platform}/g, mapping.platform).replace(/{arch}/g, mapping.arch);
|
|
186801
|
-
}
|
|
186802
|
-
async function downloadFile(url, destPath, onProgress) {
|
|
186803
|
-
let response;
|
|
186804
|
-
try {
|
|
186805
|
-
response = await fetch(url);
|
|
186806
|
-
} catch (error) {
|
|
186807
|
-
throw new DownloadError(url, void 0, error);
|
|
186808
|
-
}
|
|
186809
|
-
if (!response.ok) {
|
|
186810
|
-
throw new DownloadError(url, response.status);
|
|
186811
|
-
}
|
|
186812
|
-
const totalBytes = parseInt(
|
|
186813
|
-
response.headers.get("content-length") || "0",
|
|
186814
|
-
10
|
|
186815
|
-
);
|
|
186816
|
-
let bytesDownloaded = 0;
|
|
186817
|
-
const parentDir = path9.dirname(destPath);
|
|
186818
|
-
if (!fs13.existsSync(parentDir)) {
|
|
186819
|
-
fs13.mkdirSync(parentDir, { recursive: true });
|
|
186820
|
-
}
|
|
186821
|
-
const partPath = destPath + ".part";
|
|
186822
|
-
const fileStream = fs13.createWriteStream(partPath);
|
|
186823
|
-
try {
|
|
186824
|
-
const reader = response.body.getReader();
|
|
186825
|
-
while (true) {
|
|
186826
|
-
const { done, value } = await reader.read();
|
|
186827
|
-
if (done) break;
|
|
186828
|
-
bytesDownloaded += value.length;
|
|
186829
|
-
fileStream.write(Buffer.from(value));
|
|
186830
|
-
if (onProgress) {
|
|
186831
|
-
onProgress({
|
|
186832
|
-
phase: "downloading",
|
|
186833
|
-
bytesDownloaded,
|
|
186834
|
-
totalBytes: totalBytes || void 0,
|
|
186835
|
-
percent: totalBytes ? Math.round(bytesDownloaded / totalBytes * 100) : void 0
|
|
186836
|
-
});
|
|
186837
|
-
}
|
|
186838
|
-
}
|
|
186839
|
-
await new Promise((resolve7, reject) => {
|
|
186840
|
-
fileStream.end((err) => {
|
|
186841
|
-
if (err) reject(err);
|
|
186842
|
-
else resolve7();
|
|
186843
|
-
});
|
|
186844
|
-
});
|
|
186845
|
-
fs13.renameSync(partPath, destPath);
|
|
186846
|
-
} catch (error) {
|
|
186847
|
-
try {
|
|
186848
|
-
fileStream.close();
|
|
186849
|
-
if (fs13.existsSync(partPath)) {
|
|
186850
|
-
fs13.unlinkSync(partPath);
|
|
186851
|
-
}
|
|
186852
|
-
} catch {
|
|
186853
|
-
}
|
|
186854
|
-
throw new DownloadError(url, void 0, error);
|
|
186855
|
-
}
|
|
186856
|
-
}
|
|
186857
|
-
async function extractTarball(archivePath, destDir, definition, onProgress) {
|
|
186858
|
-
onProgress?.({ phase: "extracting" });
|
|
186859
|
-
if (!fs13.existsSync(destDir)) {
|
|
186860
|
-
fs13.mkdirSync(destDir, { recursive: true });
|
|
186861
|
-
}
|
|
186862
|
-
try {
|
|
186863
|
-
const fileStream = createReadStream(archivePath);
|
|
186864
|
-
const entries = createTarExtractor(fileStream, "gzip");
|
|
186865
|
-
await extractTo(entries, destDir);
|
|
186866
|
-
onProgress?.({ phase: "finalizing" });
|
|
186867
|
-
for (const execPath of definition.executables) {
|
|
186868
|
-
const fullPath = path9.join(destDir, execPath);
|
|
186869
|
-
if (fs13.existsSync(fullPath)) {
|
|
186870
|
-
fs13.chmodSync(fullPath, 493);
|
|
186871
|
-
}
|
|
186872
|
-
}
|
|
186873
|
-
} catch (error) {
|
|
186874
|
-
throw new ExtractionError(archivePath, error);
|
|
186875
|
-
}
|
|
186876
|
-
}
|
|
186877
|
-
async function ensureBinary(definition, version, onProgress) {
|
|
186878
|
-
const platformInfo = getPlatformInfo();
|
|
186879
|
-
const resolvedVersion = version || definition.versions[0];
|
|
186880
|
-
if (!definition.versions.includes(resolvedVersion)) {
|
|
186881
|
-
throw new Error(
|
|
186882
|
-
`Version ${resolvedVersion} not supported for ${definition.name}. Supported versions: ${definition.versions.join(", ")}`
|
|
186883
|
-
);
|
|
186884
|
-
}
|
|
186885
|
-
const binDir = getBinaryDir(definition, resolvedVersion, platformInfo);
|
|
186886
|
-
if (!isBinaryInstalled(definition, resolvedVersion, platformInfo)) {
|
|
186887
|
-
const url = buildDownloadUrl(definition, resolvedVersion, platformInfo);
|
|
186888
|
-
if (definition.type === "binary") {
|
|
186889
|
-
if (definition.executables.length !== 1) {
|
|
186890
|
-
throw new Error(
|
|
186891
|
-
`Binary type definitions must have exactly one executable, got ${definition.executables.length}`
|
|
186892
|
-
);
|
|
186893
|
-
}
|
|
186894
|
-
if (!fs13.existsSync(binDir)) {
|
|
186895
|
-
fs13.mkdirSync(binDir, { recursive: true });
|
|
186896
|
-
}
|
|
186897
|
-
const execPath = path9.join(binDir, definition.executables[0]);
|
|
186898
|
-
await downloadFile(url, execPath, onProgress);
|
|
186899
|
-
fs13.chmodSync(execPath, 493);
|
|
186900
|
-
onProgress?.({ phase: "finalizing" });
|
|
186901
|
-
} else {
|
|
186902
|
-
const downloadDir = path9.join(getBinBaseDir(), "downloads");
|
|
186903
|
-
const archiveName = `${definition.name}-${resolvedVersion}-${platformInfo.platform}-${platformInfo.arch}.tar.gz`;
|
|
186904
|
-
const archivePath = path9.join(downloadDir, archiveName);
|
|
186905
|
-
try {
|
|
186906
|
-
await downloadFile(url, archivePath, onProgress);
|
|
186907
|
-
await extractTarball(archivePath, binDir, definition, onProgress);
|
|
186908
|
-
} finally {
|
|
186909
|
-
try {
|
|
186910
|
-
if (fs13.existsSync(archivePath)) {
|
|
186911
|
-
fs13.unlinkSync(archivePath);
|
|
186912
|
-
}
|
|
186913
|
-
} catch {
|
|
186914
|
-
}
|
|
186915
|
-
}
|
|
186916
|
-
}
|
|
186917
|
-
}
|
|
186918
|
-
const executables = {};
|
|
186919
|
-
for (const execPath of definition.executables) {
|
|
186920
|
-
const name = path9.basename(execPath);
|
|
186921
|
-
executables[name] = path9.join(binDir, execPath);
|
|
186922
|
-
}
|
|
186923
|
-
const libraryPath = definition.libraryDir ? path9.join(binDir, definition.libraryDir) : void 0;
|
|
186924
|
-
return {
|
|
186925
|
-
rootDir: binDir,
|
|
186926
|
-
version: resolvedVersion,
|
|
186927
|
-
executables,
|
|
186928
|
-
libraryPath
|
|
186929
|
-
};
|
|
186930
|
-
}
|
|
186931
|
-
|
|
186932
|
-
// src/lib/bin/definitions/postgres.ts
|
|
186933
|
-
var postgresBinary = {
|
|
186934
|
-
name: "postgres",
|
|
186935
|
-
versions: ["18.1"],
|
|
186936
|
-
// URL: binaries.specific.dev/{SOFTWARE}/{VERSION}/{ARCH}.tar.gz
|
|
186937
|
-
urlTemplate: "https://binaries.specific.dev/postgres/{version}/{arch}.tar.gz",
|
|
186938
|
-
platformMapping: {
|
|
186939
|
-
darwin: {
|
|
186940
|
-
arm64: { platform: "macos", arch: "macos_arm" },
|
|
186941
|
-
x64: { platform: "macos", arch: "macos_x86" }
|
|
186942
|
-
},
|
|
186943
|
-
linux: {
|
|
186944
|
-
arm64: { platform: "linux", arch: "linux_arm" },
|
|
186945
|
-
x64: { platform: "linux", arch: "linux_x86" }
|
|
186946
|
-
}
|
|
186947
|
-
},
|
|
186948
|
-
// Archive contains bin/ and lib/ directories at root
|
|
186949
|
-
stripComponents: 0,
|
|
186950
|
-
// Core PostgreSQL executables (in bin/ directory)
|
|
186951
|
-
executables: ["bin/postgres", "bin/initdb", "bin/psql"],
|
|
186952
|
-
// Library directory relative to root (for setting LD_LIBRARY_PATH)
|
|
186953
|
-
libraryDir: "lib"
|
|
186954
|
-
};
|
|
186955
|
-
|
|
186956
|
-
// src/lib/bin/definitions/redis.ts
|
|
186957
|
-
var redisBinary = {
|
|
186958
|
-
name: "redis",
|
|
186959
|
-
versions: ["8.4.0"],
|
|
186960
|
-
// URL: binaries.specific.dev/{SOFTWARE}/{VERSION}/{ARCH}.tar.gz
|
|
186961
|
-
urlTemplate: "https://binaries.specific.dev/redis/{version}/{arch}.tar.gz",
|
|
186962
|
-
platformMapping: {
|
|
186963
|
-
darwin: {
|
|
186964
|
-
arm64: { platform: "macos", arch: "macos_arm" },
|
|
186965
|
-
x64: { platform: "macos", arch: "macos_x86" }
|
|
186966
|
-
},
|
|
186967
|
-
linux: {
|
|
186968
|
-
arm64: { platform: "linux", arch: "linux_arm" },
|
|
186969
|
-
x64: { platform: "linux", arch: "linux_x86" }
|
|
186970
|
-
}
|
|
186971
|
-
},
|
|
186972
|
-
// Binaries are at the root of the archive
|
|
186973
|
-
stripComponents: 0,
|
|
186974
|
-
// Core Redis executable
|
|
186975
|
-
executables: ["redis-server"]
|
|
186976
|
-
};
|
|
186977
|
-
|
|
186978
|
-
// src/lib/bin/definitions/electric.ts
|
|
186979
|
-
var electricBinary = {
|
|
186980
|
-
name: "electric",
|
|
186981
|
-
versions: ["1.2.11"],
|
|
186982
|
-
// URL: binaries.specific.dev/{SOFTWARE}/{VERSION}/{ARCH}.tar.gz
|
|
186983
|
-
urlTemplate: "https://binaries.specific.dev/electric/{version}/{arch}.tar.gz",
|
|
186984
|
-
platformMapping: {
|
|
186985
|
-
darwin: {
|
|
186986
|
-
arm64: { platform: "macos", arch: "macos_arm" },
|
|
186987
|
-
x64: { platform: "macos", arch: "macos_x86" }
|
|
186988
|
-
},
|
|
186989
|
-
linux: {
|
|
186990
|
-
arm64: { platform: "linux", arch: "linux_arm" },
|
|
186991
|
-
x64: { platform: "linux", arch: "linux_x86" }
|
|
186992
|
-
}
|
|
186993
|
-
},
|
|
186994
|
-
// Binaries are at the root of the archive
|
|
186995
|
-
stripComponents: 0,
|
|
186996
|
-
// Core Electric executable
|
|
186997
|
-
executables: ["electric"]
|
|
186998
|
-
};
|
|
186999
|
-
|
|
187000
|
-
// src/lib/bin/definitions/drizzle-gateway.ts
|
|
187001
|
-
var drizzleGatewayBinary = {
|
|
187002
|
-
name: "drizzle-gateway",
|
|
187003
|
-
versions: ["1.2.0"],
|
|
187004
|
-
// Official Drizzle Gateway download URL from R2
|
|
187005
|
-
urlTemplate: "https://pub-e240a4fd7085425baf4a7951e7611520.r2.dev/drizzle-gateway-{version}-{arch}",
|
|
187006
|
-
platformMapping: {
|
|
187007
|
-
darwin: {
|
|
187008
|
-
arm64: { platform: "macos", arch: "macos-arm64" },
|
|
187009
|
-
x64: { platform: "macos", arch: "macos-x64" }
|
|
187010
|
-
},
|
|
187011
|
-
linux: {
|
|
187012
|
-
arm64: { platform: "linux", arch: "linux-arm64" },
|
|
187013
|
-
x64: { platform: "linux", arch: "linux-x64" }
|
|
187014
|
-
}
|
|
187015
|
-
},
|
|
187016
|
-
// Raw binary, not a tarball
|
|
187017
|
-
type: "binary",
|
|
187018
|
-
// The executable name
|
|
187019
|
-
executables: ["drizzle-gateway"]
|
|
187020
|
-
};
|
|
187021
|
-
|
|
187022
|
-
// src/lib/dev/database-manager.ts
|
|
187023
187182
|
async function startPostgres(pg, port, dataDir, onProgress) {
|
|
187024
187183
|
const binary = await ensureBinary(postgresBinary, void 0, onProgress);
|
|
187025
187184
|
const dbDataPath = path10.join(process.cwd(), dataDir, pg.name);
|
|
@@ -187142,7 +187301,7 @@ async function startStorage(storage, port, dataDir) {
|
|
|
187142
187301
|
};
|
|
187143
187302
|
}
|
|
187144
187303
|
async function runCommand(command, args, env2) {
|
|
187145
|
-
return new Promise((
|
|
187304
|
+
return new Promise((resolve9, reject) => {
|
|
187146
187305
|
const proc = spawn(command, args, {
|
|
187147
187306
|
stdio: ["ignore", "pipe", "pipe"],
|
|
187148
187307
|
env: env2
|
|
@@ -187153,7 +187312,7 @@ async function runCommand(command, args, env2) {
|
|
|
187153
187312
|
});
|
|
187154
187313
|
proc.on("close", (code) => {
|
|
187155
187314
|
if (code === 0) {
|
|
187156
|
-
|
|
187315
|
+
resolve9();
|
|
187157
187316
|
} else {
|
|
187158
187317
|
reject(new Error(`Command failed with code ${code}: ${stderr}`));
|
|
187159
187318
|
}
|
|
@@ -187162,7 +187321,7 @@ async function runCommand(command, args, env2) {
|
|
|
187162
187321
|
});
|
|
187163
187322
|
}
|
|
187164
187323
|
async function createPostgresDatabase(postgresPath, dataDir, dbName, env2) {
|
|
187165
|
-
return new Promise((
|
|
187324
|
+
return new Promise((resolve9, reject) => {
|
|
187166
187325
|
const proc = spawn(
|
|
187167
187326
|
postgresPath,
|
|
187168
187327
|
["--single", "-D", dataDir, "postgres"],
|
|
@@ -187176,7 +187335,7 @@ async function createPostgresDatabase(postgresPath, dataDir, dbName, env2) {
|
|
|
187176
187335
|
stderr += data.toString();
|
|
187177
187336
|
});
|
|
187178
187337
|
proc.on("close", (code) => {
|
|
187179
|
-
|
|
187338
|
+
resolve9();
|
|
187180
187339
|
});
|
|
187181
187340
|
proc.on("error", reject);
|
|
187182
187341
|
proc.stdin?.write(`CREATE DATABASE "${dbName}";
|
|
@@ -187196,33 +187355,33 @@ async function waitForTcpPort(host, port, timeoutMs = 3e4) {
|
|
|
187196
187355
|
throw new Error(`Port ${port} did not become available within timeout`);
|
|
187197
187356
|
}
|
|
187198
187357
|
function checkTcpPort(host, port) {
|
|
187199
|
-
return new Promise((
|
|
187358
|
+
return new Promise((resolve9) => {
|
|
187200
187359
|
const socket = new net.Socket();
|
|
187201
187360
|
socket.setTimeout(1e3);
|
|
187202
187361
|
socket.on("connect", () => {
|
|
187203
187362
|
socket.destroy();
|
|
187204
|
-
|
|
187363
|
+
resolve9(true);
|
|
187205
187364
|
});
|
|
187206
187365
|
socket.on("timeout", () => {
|
|
187207
187366
|
socket.destroy();
|
|
187208
|
-
|
|
187367
|
+
resolve9(false);
|
|
187209
187368
|
});
|
|
187210
187369
|
socket.on("error", () => {
|
|
187211
187370
|
socket.destroy();
|
|
187212
|
-
|
|
187371
|
+
resolve9(false);
|
|
187213
187372
|
});
|
|
187214
187373
|
socket.connect(port, host);
|
|
187215
187374
|
});
|
|
187216
187375
|
}
|
|
187217
187376
|
async function stopProcess(proc) {
|
|
187218
|
-
return new Promise((
|
|
187377
|
+
return new Promise((resolve9) => {
|
|
187219
187378
|
if (proc.killed || proc.exitCode !== null) {
|
|
187220
|
-
|
|
187379
|
+
resolve9();
|
|
187221
187380
|
return;
|
|
187222
187381
|
}
|
|
187223
187382
|
proc.once("exit", () => {
|
|
187224
187383
|
clearTimeout(forceKillTimeout);
|
|
187225
|
-
|
|
187384
|
+
resolve9();
|
|
187226
187385
|
});
|
|
187227
187386
|
proc.kill("SIGTERM");
|
|
187228
187387
|
const forceKillTimeout = setTimeout(() => {
|
|
@@ -187233,7 +187392,7 @@ async function stopProcess(proc) {
|
|
|
187233
187392
|
});
|
|
187234
187393
|
}
|
|
187235
187394
|
function sleep2(ms) {
|
|
187236
|
-
return new Promise((
|
|
187395
|
+
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
187237
187396
|
}
|
|
187238
187397
|
|
|
187239
187398
|
// src/lib/dev/service-runner.ts
|
|
@@ -187242,7 +187401,7 @@ import { spawn as spawn2 } from "child_process";
|
|
|
187242
187401
|
// src/lib/local/parser.ts
|
|
187243
187402
|
var import_hcl2_json_parser2 = __toESM(require_dist(), 1);
|
|
187244
187403
|
import { readFile, writeFile } from "fs/promises";
|
|
187245
|
-
import { existsSync as
|
|
187404
|
+
import { existsSync as existsSync11 } from "fs";
|
|
187246
187405
|
var { parseToObject: parseToObject2 } = import_hcl2_json_parser2.default;
|
|
187247
187406
|
var LOCAL_FILE = "specific.local";
|
|
187248
187407
|
var HEADER_COMMENT = `# Local secrets and configuration
|
|
@@ -187283,7 +187442,7 @@ async function parseLocalFile(content) {
|
|
|
187283
187442
|
return { secrets, configs };
|
|
187284
187443
|
}
|
|
187285
187444
|
async function loadLocal() {
|
|
187286
|
-
if (!
|
|
187445
|
+
if (!existsSync11(LOCAL_FILE)) {
|
|
187287
187446
|
return { secrets: /* @__PURE__ */ new Map(), configs: /* @__PURE__ */ new Map() };
|
|
187288
187447
|
}
|
|
187289
187448
|
const content = await readFile(LOCAL_FILE, "utf-8");
|
|
@@ -187323,7 +187482,7 @@ ${newLine}
|
|
|
187323
187482
|
}
|
|
187324
187483
|
async function saveLocalSecret(name, value) {
|
|
187325
187484
|
let content = "";
|
|
187326
|
-
if (
|
|
187485
|
+
if (existsSync11(LOCAL_FILE)) {
|
|
187327
187486
|
content = await readFile(LOCAL_FILE, "utf-8");
|
|
187328
187487
|
} else {
|
|
187329
187488
|
content = HEADER_COMMENT;
|
|
@@ -187333,7 +187492,7 @@ async function saveLocalSecret(name, value) {
|
|
|
187333
187492
|
}
|
|
187334
187493
|
async function saveLocalConfig(name, value) {
|
|
187335
187494
|
let content = "";
|
|
187336
|
-
if (
|
|
187495
|
+
if (existsSync11(LOCAL_FILE)) {
|
|
187337
187496
|
content = await readFile(LOCAL_FILE, "utf-8");
|
|
187338
187497
|
} else {
|
|
187339
187498
|
content = HEADER_COMMENT;
|
|
@@ -187342,6 +187501,13 @@ async function saveLocalConfig(name, value) {
|
|
|
187342
187501
|
await writeFile(LOCAL_FILE, content);
|
|
187343
187502
|
}
|
|
187344
187503
|
|
|
187504
|
+
// src/lib/dev/reshape-manager.ts
|
|
187505
|
+
function appendSearchPathToUrl(baseUrl, searchPath) {
|
|
187506
|
+
const url = new URL(baseUrl);
|
|
187507
|
+
url.searchParams.set("options", `-c search_path=${searchPath}`);
|
|
187508
|
+
return url.toString();
|
|
187509
|
+
}
|
|
187510
|
+
|
|
187345
187511
|
// src/lib/dev/env-resolver.ts
|
|
187346
187512
|
var MissingSecretError = class extends Error {
|
|
187347
187513
|
constructor(secretName) {
|
|
@@ -187446,6 +187612,9 @@ function resolveEnvValue(value, resources, secrets, configs, servicePort, servic
|
|
|
187446
187612
|
}
|
|
187447
187613
|
switch (value.attribute) {
|
|
187448
187614
|
case "url":
|
|
187615
|
+
if (pg.reshapeEnabled && pg.reshapeSearchPath) {
|
|
187616
|
+
return appendSearchPathToUrl(pg.url, pg.reshapeSearchPath);
|
|
187617
|
+
}
|
|
187449
187618
|
return pg.url;
|
|
187450
187619
|
case "host":
|
|
187451
187620
|
return pg.host;
|
|
@@ -187627,14 +187796,14 @@ function startService(service, resources, secrets, configs, endpointPorts, servi
|
|
|
187627
187796
|
ports: endpointPorts,
|
|
187628
187797
|
process: child,
|
|
187629
187798
|
async stop() {
|
|
187630
|
-
return new Promise((
|
|
187799
|
+
return new Promise((resolve9) => {
|
|
187631
187800
|
if (child.killed || child.exitCode !== null) {
|
|
187632
|
-
|
|
187801
|
+
resolve9();
|
|
187633
187802
|
return;
|
|
187634
187803
|
}
|
|
187635
187804
|
child.once("exit", () => {
|
|
187636
187805
|
clearTimeout(forceKillTimeout);
|
|
187637
|
-
|
|
187806
|
+
resolve9();
|
|
187638
187807
|
});
|
|
187639
187808
|
child.kill("SIGTERM");
|
|
187640
187809
|
const forceKillTimeout = setTimeout(() => {
|
|
@@ -187715,7 +187884,7 @@ var InstanceStateManager = class {
|
|
|
187715
187884
|
}
|
|
187716
187885
|
continue;
|
|
187717
187886
|
}
|
|
187718
|
-
await new Promise((
|
|
187887
|
+
await new Promise((resolve9) => setTimeout(resolve9, 100));
|
|
187719
187888
|
} else {
|
|
187720
187889
|
throw e;
|
|
187721
187890
|
}
|
|
@@ -187998,7 +188167,7 @@ async function startHttpProxy(services, certificate, getState, instanceKey = "de
|
|
|
187998
188167
|
handleRequest
|
|
187999
188168
|
);
|
|
188000
188169
|
httpsServer.on("upgrade", handleUpgrade);
|
|
188001
|
-
return new Promise((
|
|
188170
|
+
return new Promise((resolve9, reject) => {
|
|
188002
188171
|
let httpStarted = false;
|
|
188003
188172
|
let httpsStarted = false;
|
|
188004
188173
|
let failed = false;
|
|
@@ -188009,7 +188178,7 @@ async function startHttpProxy(services, certificate, getState, instanceKey = "de
|
|
|
188009
188178
|
"proxy",
|
|
188010
188179
|
`HTTP/HTTPS proxy started on ports ${HTTP_PORT}/${HTTPS_PORT}`
|
|
188011
188180
|
);
|
|
188012
|
-
|
|
188181
|
+
resolve9({
|
|
188013
188182
|
httpPort: HTTP_PORT,
|
|
188014
188183
|
httpsPort: HTTPS_PORT,
|
|
188015
188184
|
updateServices,
|
|
@@ -188021,13 +188190,13 @@ async function startHttpProxy(services, certificate, getState, instanceKey = "de
|
|
|
188021
188190
|
writeLog("proxy", "Certificate updated");
|
|
188022
188191
|
},
|
|
188023
188192
|
async stop() {
|
|
188024
|
-
return new Promise((
|
|
188193
|
+
return new Promise((resolve10) => {
|
|
188025
188194
|
let closed = 0;
|
|
188026
188195
|
const onClose = () => {
|
|
188027
188196
|
closed++;
|
|
188028
188197
|
if (closed === 2) {
|
|
188029
188198
|
clearTimeout(forceCloseTimeout);
|
|
188030
|
-
|
|
188199
|
+
resolve10();
|
|
188031
188200
|
}
|
|
188032
188201
|
};
|
|
188033
188202
|
httpServer.close(onClose);
|
|
@@ -188035,7 +188204,7 @@ async function startHttpProxy(services, certificate, getState, instanceKey = "de
|
|
|
188035
188204
|
const forceCloseTimeout = setTimeout(() => {
|
|
188036
188205
|
httpServer.closeAllConnections?.();
|
|
188037
188206
|
httpsServer.closeAllConnections?.();
|
|
188038
|
-
|
|
188207
|
+
resolve10();
|
|
188039
188208
|
}, 2e3);
|
|
188040
188209
|
});
|
|
188041
188210
|
}
|
|
@@ -188198,7 +188367,7 @@ function serveFileContent(res, filePath, contentType, statusCode = 200) {
|
|
|
188198
188367
|
}
|
|
188199
188368
|
}
|
|
188200
188369
|
async function startAdminServer(getState) {
|
|
188201
|
-
return new Promise((
|
|
188370
|
+
return new Promise((resolve9, reject) => {
|
|
188202
188371
|
const server = http.createServer((req, res) => {
|
|
188203
188372
|
const url = new URL(req.url || "/", "http://localhost");
|
|
188204
188373
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
@@ -188230,7 +188399,7 @@ async function startAdminServer(getState) {
|
|
|
188230
188399
|
}
|
|
188231
188400
|
const port = addr.port;
|
|
188232
188401
|
writeLog("admin", `Admin API server started on port ${port}`);
|
|
188233
|
-
|
|
188402
|
+
resolve9({
|
|
188234
188403
|
port,
|
|
188235
188404
|
stop: () => new Promise((res, rej) => {
|
|
188236
188405
|
server.close((err) => err ? rej(err) : res());
|
|
@@ -188246,7 +188415,7 @@ import { spawn as spawn3 } from "child_process";
|
|
|
188246
188415
|
|
|
188247
188416
|
// src/lib/secrets/parser.ts
|
|
188248
188417
|
import { readFile as readFile2, writeFile as writeFile2, mkdir } from "fs/promises";
|
|
188249
|
-
import { existsSync as
|
|
188418
|
+
import { existsSync as existsSync14 } from "fs";
|
|
188250
188419
|
import * as path13 from "path";
|
|
188251
188420
|
import * as crypto2 from "crypto";
|
|
188252
188421
|
var GENERATED_SECRETS_FILE = ".specific/generated-secrets.json";
|
|
@@ -188264,7 +188433,7 @@ function generateRandomString(length = 64) {
|
|
|
188264
188433
|
return result;
|
|
188265
188434
|
}
|
|
188266
188435
|
async function loadGeneratedSecrets() {
|
|
188267
|
-
if (!
|
|
188436
|
+
if (!existsSync14(GENERATED_SECRETS_FILE)) {
|
|
188268
188437
|
return /* @__PURE__ */ new Map();
|
|
188269
188438
|
}
|
|
188270
188439
|
const content = await readFile2(GENERATED_SECRETS_FILE, "utf-8");
|
|
@@ -188273,13 +188442,13 @@ async function loadGeneratedSecrets() {
|
|
|
188273
188442
|
}
|
|
188274
188443
|
async function saveGeneratedSecret(name, value) {
|
|
188275
188444
|
let secrets = {};
|
|
188276
|
-
if (
|
|
188445
|
+
if (existsSync14(GENERATED_SECRETS_FILE)) {
|
|
188277
188446
|
const content = await readFile2(GENERATED_SECRETS_FILE, "utf-8");
|
|
188278
188447
|
secrets = JSON.parse(content);
|
|
188279
188448
|
}
|
|
188280
188449
|
secrets[name] = value;
|
|
188281
188450
|
const dir = path13.dirname(GENERATED_SECRETS_FILE);
|
|
188282
|
-
if (!
|
|
188451
|
+
if (!existsSync14(dir)) {
|
|
188283
188452
|
await mkdir(dir, { recursive: true });
|
|
188284
188453
|
}
|
|
188285
188454
|
await writeFile2(GENERATED_SECRETS_FILE, JSON.stringify(secrets, null, 2) + "\n");
|
|
@@ -188391,33 +188560,33 @@ async function waitForTcpPort2(host, port, timeoutMs = 3e4) {
|
|
|
188391
188560
|
throw new Error(`Electric port ${port} did not become available within timeout`);
|
|
188392
188561
|
}
|
|
188393
188562
|
function checkTcpPort2(host, port) {
|
|
188394
|
-
return new Promise((
|
|
188563
|
+
return new Promise((resolve9) => {
|
|
188395
188564
|
const socket = new net2.Socket();
|
|
188396
188565
|
socket.setTimeout(1e3);
|
|
188397
188566
|
socket.on("connect", () => {
|
|
188398
188567
|
socket.destroy();
|
|
188399
|
-
|
|
188568
|
+
resolve9(true);
|
|
188400
188569
|
});
|
|
188401
188570
|
socket.on("timeout", () => {
|
|
188402
188571
|
socket.destroy();
|
|
188403
|
-
|
|
188572
|
+
resolve9(false);
|
|
188404
188573
|
});
|
|
188405
188574
|
socket.on("error", () => {
|
|
188406
188575
|
socket.destroy();
|
|
188407
|
-
|
|
188576
|
+
resolve9(false);
|
|
188408
188577
|
});
|
|
188409
188578
|
socket.connect(port, host);
|
|
188410
188579
|
});
|
|
188411
188580
|
}
|
|
188412
188581
|
async function stopProcess2(proc) {
|
|
188413
|
-
return new Promise((
|
|
188582
|
+
return new Promise((resolve9) => {
|
|
188414
188583
|
if (proc.killed || proc.exitCode !== null) {
|
|
188415
|
-
|
|
188584
|
+
resolve9();
|
|
188416
188585
|
return;
|
|
188417
188586
|
}
|
|
188418
188587
|
proc.once("exit", () => {
|
|
188419
188588
|
clearTimeout(forceKillTimeout);
|
|
188420
|
-
|
|
188589
|
+
resolve9();
|
|
188421
188590
|
});
|
|
188422
188591
|
proc.kill("SIGTERM");
|
|
188423
188592
|
const forceKillTimeout = setTimeout(() => {
|
|
@@ -188428,7 +188597,7 @@ async function stopProcess2(proc) {
|
|
|
188428
188597
|
});
|
|
188429
188598
|
}
|
|
188430
188599
|
function sleep3(ms) {
|
|
188431
|
-
return new Promise((
|
|
188600
|
+
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
188432
188601
|
}
|
|
188433
188602
|
|
|
188434
188603
|
// src/lib/dev/drizzle-gateway-manager.ts
|
|
@@ -188512,33 +188681,33 @@ async function waitForTcpPort3(host, port, timeoutMs = 3e4) {
|
|
|
188512
188681
|
);
|
|
188513
188682
|
}
|
|
188514
188683
|
function checkTcpPort3(host, port) {
|
|
188515
|
-
return new Promise((
|
|
188684
|
+
return new Promise((resolve9) => {
|
|
188516
188685
|
const socket = new net3.Socket();
|
|
188517
188686
|
socket.setTimeout(1e3);
|
|
188518
188687
|
socket.on("connect", () => {
|
|
188519
188688
|
socket.destroy();
|
|
188520
|
-
|
|
188689
|
+
resolve9(true);
|
|
188521
188690
|
});
|
|
188522
188691
|
socket.on("timeout", () => {
|
|
188523
188692
|
socket.destroy();
|
|
188524
|
-
|
|
188693
|
+
resolve9(false);
|
|
188525
188694
|
});
|
|
188526
188695
|
socket.on("error", () => {
|
|
188527
188696
|
socket.destroy();
|
|
188528
|
-
|
|
188697
|
+
resolve9(false);
|
|
188529
188698
|
});
|
|
188530
188699
|
socket.connect(port, host);
|
|
188531
188700
|
});
|
|
188532
188701
|
}
|
|
188533
188702
|
async function stopProcess3(proc) {
|
|
188534
|
-
return new Promise((
|
|
188703
|
+
return new Promise((resolve9) => {
|
|
188535
188704
|
if (proc.killed || proc.exitCode !== null) {
|
|
188536
|
-
|
|
188705
|
+
resolve9();
|
|
188537
188706
|
return;
|
|
188538
188707
|
}
|
|
188539
188708
|
proc.once("exit", () => {
|
|
188540
188709
|
clearTimeout(forceKillTimeout);
|
|
188541
|
-
|
|
188710
|
+
resolve9();
|
|
188542
188711
|
});
|
|
188543
188712
|
proc.kill("SIGTERM");
|
|
188544
188713
|
const forceKillTimeout = setTimeout(() => {
|
|
@@ -188549,7 +188718,7 @@ async function stopProcess3(proc) {
|
|
|
188549
188718
|
});
|
|
188550
188719
|
}
|
|
188551
188720
|
function sleep4(ms) {
|
|
188552
|
-
return new Promise((
|
|
188721
|
+
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
188553
188722
|
}
|
|
188554
188723
|
|
|
188555
188724
|
// src/lib/dev/sync-detector.ts
|
|
@@ -188569,6 +188738,288 @@ function detectSyncDatabases(config) {
|
|
|
188569
188738
|
return needsSync;
|
|
188570
188739
|
}
|
|
188571
188740
|
|
|
188741
|
+
// src/lib/dev/reshape-watcher.ts
|
|
188742
|
+
import * as fs18 from "fs";
|
|
188743
|
+
import * as path15 from "path";
|
|
188744
|
+
import { spawnSync } from "child_process";
|
|
188745
|
+
function getMigrationFiles(dir, log) {
|
|
188746
|
+
log(`Scanning migrations directory: ${dir}`);
|
|
188747
|
+
if (!fs18.existsSync(dir)) {
|
|
188748
|
+
log(`Migrations directory does not exist: ${dir}`);
|
|
188749
|
+
return [];
|
|
188750
|
+
}
|
|
188751
|
+
const files = fs18.readdirSync(dir);
|
|
188752
|
+
log(`Found ${files.length} files in directory`);
|
|
188753
|
+
const tomlFiles = files.filter((f) => f.endsWith(".toml")).sort((a, b) => a.localeCompare(b));
|
|
188754
|
+
log(`Found ${tomlFiles.length} .toml migration files: ${tomlFiles.join(", ") || "(none)"}`);
|
|
188755
|
+
return tomlFiles;
|
|
188756
|
+
}
|
|
188757
|
+
function getMigrationName(filename) {
|
|
188758
|
+
return filename.replace(/\.toml$/, "");
|
|
188759
|
+
}
|
|
188760
|
+
function getSchemaName(filename) {
|
|
188761
|
+
return `migration_${getMigrationName(filename)}`;
|
|
188762
|
+
}
|
|
188763
|
+
function runReshape(args, databaseUrl, migrationsDir, reshapeBinaryPath, log) {
|
|
188764
|
+
const fullArgs = ["migration", ...args];
|
|
188765
|
+
if (args[0] === "start") {
|
|
188766
|
+
fullArgs.push("--dirs", ".");
|
|
188767
|
+
}
|
|
188768
|
+
log(`Executing: reshape ${fullArgs.join(" ")}`);
|
|
188769
|
+
log(` Working directory: ${migrationsDir}`);
|
|
188770
|
+
try {
|
|
188771
|
+
const result = spawnSync(reshapeBinaryPath, fullArgs, {
|
|
188772
|
+
cwd: migrationsDir,
|
|
188773
|
+
env: {
|
|
188774
|
+
...process.env,
|
|
188775
|
+
DB_URL: databaseUrl
|
|
188776
|
+
},
|
|
188777
|
+
encoding: "utf-8",
|
|
188778
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
188779
|
+
});
|
|
188780
|
+
const stdout = result.stdout || "";
|
|
188781
|
+
const stderr = result.stderr || "";
|
|
188782
|
+
const output = stdout + stderr;
|
|
188783
|
+
if (result.status !== 0) {
|
|
188784
|
+
log(`Reshape command failed with exit code ${result.status}`);
|
|
188785
|
+
if (stdout.trim()) log(` stdout: ${stdout.trim()}`);
|
|
188786
|
+
if (stderr.trim()) log(` stderr: ${stderr.trim()}`);
|
|
188787
|
+
return { success: false, output };
|
|
188788
|
+
}
|
|
188789
|
+
log(`Reshape command succeeded`);
|
|
188790
|
+
if (stdout.trim()) log(` stdout: ${stdout.trim()}`);
|
|
188791
|
+
if (stderr.trim()) log(` stderr: ${stderr.trim()}`);
|
|
188792
|
+
return { success: true, output };
|
|
188793
|
+
} catch (err) {
|
|
188794
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
188795
|
+
log(`Reshape command threw exception: ${message}`);
|
|
188796
|
+
return { success: false, output: message };
|
|
188797
|
+
}
|
|
188798
|
+
}
|
|
188799
|
+
function makeReadOnly(filePath, log) {
|
|
188800
|
+
try {
|
|
188801
|
+
fs18.chmodSync(filePath, 292);
|
|
188802
|
+
log(`Set file permissions to read-only (444): ${filePath}`);
|
|
188803
|
+
} catch (err) {
|
|
188804
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
188805
|
+
log(`Warning: Failed to make file read-only: ${filePath} - ${message}`);
|
|
188806
|
+
}
|
|
188807
|
+
}
|
|
188808
|
+
function createReshapeWatcher(options2) {
|
|
188809
|
+
const { databaseUrl, migrationsDir, reshapeBinaryPath, onSearchPathChanged, onError, log } = options2;
|
|
188810
|
+
let watcher = null;
|
|
188811
|
+
let currentMigrationFiles = [];
|
|
188812
|
+
let startedMigration = null;
|
|
188813
|
+
log(`Creating Reshape watcher`);
|
|
188814
|
+
log(` Migrations directory: ${migrationsDir}`);
|
|
188815
|
+
log(` Database URL: ${databaseUrl.replace(/:[^:@]+@/, ":***@")}`);
|
|
188816
|
+
const initialize = async () => {
|
|
188817
|
+
log(`Initializing Reshape migrations...`);
|
|
188818
|
+
currentMigrationFiles = getMigrationFiles(migrationsDir, log);
|
|
188819
|
+
if (currentMigrationFiles.length === 0) {
|
|
188820
|
+
log("No migration files found - skipping Reshape initialization");
|
|
188821
|
+
return null;
|
|
188822
|
+
}
|
|
188823
|
+
log(`Processing ${currentMigrationFiles.length} migration file(s)`);
|
|
188824
|
+
if (currentMigrationFiles.length === 1) {
|
|
188825
|
+
const migrationFile = currentMigrationFiles[0];
|
|
188826
|
+
const migrationName = getMigrationName(migrationFile);
|
|
188827
|
+
log(`Single migration detected: ${migrationFile}`);
|
|
188828
|
+
log(`Starting migration "${migrationName}" (will not be completed, allowing iteration)`);
|
|
188829
|
+
const result = runReshape(["start"], databaseUrl, migrationsDir, reshapeBinaryPath, log);
|
|
188830
|
+
if (!result.success) {
|
|
188831
|
+
log(`ERROR: Failed to start migration "${migrationName}"`);
|
|
188832
|
+
onError(new Error(`Failed to start migration: ${result.output}`));
|
|
188833
|
+
return null;
|
|
188834
|
+
}
|
|
188835
|
+
startedMigration = migrationFile;
|
|
188836
|
+
log(`Migration "${migrationName}" started successfully`);
|
|
188837
|
+
log(` Started migration file: ${startedMigration}`);
|
|
188838
|
+
} else {
|
|
188839
|
+
const secondToLast = currentMigrationFiles[currentMigrationFiles.length - 2];
|
|
188840
|
+
const secondToLastName = getMigrationName(secondToLast);
|
|
188841
|
+
const lastMigration = currentMigrationFiles[currentMigrationFiles.length - 1];
|
|
188842
|
+
const lastMigrationName = getMigrationName(lastMigration);
|
|
188843
|
+
log(`Multiple migrations detected (${currentMigrationFiles.length} total)`);
|
|
188844
|
+
log(` Migrations to complete: ${currentMigrationFiles.slice(0, -1).join(", ")}`);
|
|
188845
|
+
log(` Migration to start (latest): ${lastMigration}`);
|
|
188846
|
+
log(`Completing all migrations up to and including "${secondToLastName}"...`);
|
|
188847
|
+
const completeResult = runReshape(
|
|
188848
|
+
["start", "--complete", "--migration", secondToLastName],
|
|
188849
|
+
databaseUrl,
|
|
188850
|
+
migrationsDir,
|
|
188851
|
+
reshapeBinaryPath,
|
|
188852
|
+
log
|
|
188853
|
+
);
|
|
188854
|
+
if (!completeResult.success) {
|
|
188855
|
+
log(`ERROR: Failed to complete migrations up to "${secondToLastName}"`);
|
|
188856
|
+
onError(new Error(`Failed to complete migrations: ${completeResult.output}`));
|
|
188857
|
+
return null;
|
|
188858
|
+
}
|
|
188859
|
+
log(`Successfully completed ${currentMigrationFiles.length - 1} migration(s)`);
|
|
188860
|
+
log(`Making completed migration files read-only...`);
|
|
188861
|
+
for (let i = 0; i < currentMigrationFiles.length - 1; i++) {
|
|
188862
|
+
const filePath = path15.join(migrationsDir, currentMigrationFiles[i]);
|
|
188863
|
+
makeReadOnly(filePath, log);
|
|
188864
|
+
}
|
|
188865
|
+
log(`Starting latest migration "${lastMigrationName}" (will not be completed, allowing iteration)`);
|
|
188866
|
+
const startResult = runReshape(["start"], databaseUrl, migrationsDir, reshapeBinaryPath, log);
|
|
188867
|
+
if (!startResult.success) {
|
|
188868
|
+
log(`ERROR: Failed to start migration "${lastMigrationName}"`);
|
|
188869
|
+
onError(new Error(`Failed to start last migration: ${startResult.output}`));
|
|
188870
|
+
return null;
|
|
188871
|
+
}
|
|
188872
|
+
startedMigration = lastMigration;
|
|
188873
|
+
log(`Migration "${lastMigrationName}" started successfully`);
|
|
188874
|
+
log(` Started migration file: ${startedMigration}`);
|
|
188875
|
+
}
|
|
188876
|
+
const searchPath = startedMigration ? getSchemaName(startedMigration) : null;
|
|
188877
|
+
if (searchPath) {
|
|
188878
|
+
log(`Search path (schema name): ${searchPath}`);
|
|
188879
|
+
} else {
|
|
188880
|
+
log(`No search_path (no migration started)`);
|
|
188881
|
+
}
|
|
188882
|
+
log(`Reshape initialization complete`);
|
|
188883
|
+
return searchPath;
|
|
188884
|
+
};
|
|
188885
|
+
const startWatching = () => {
|
|
188886
|
+
log(`Starting file watcher for migrations directory...`);
|
|
188887
|
+
if (!fs18.existsSync(migrationsDir)) {
|
|
188888
|
+
log(`Migrations directory does not exist, creating: ${migrationsDir}`);
|
|
188889
|
+
fs18.mkdirSync(migrationsDir, { recursive: true });
|
|
188890
|
+
}
|
|
188891
|
+
log(`Watching directory: ${migrationsDir}`);
|
|
188892
|
+
watcher = chokidar_default.watch(migrationsDir, {
|
|
188893
|
+
persistent: true,
|
|
188894
|
+
ignoreInitial: true,
|
|
188895
|
+
depth: 0,
|
|
188896
|
+
awaitWriteFinish: {
|
|
188897
|
+
stabilityThreshold: 200,
|
|
188898
|
+
pollInterval: 50
|
|
188899
|
+
}
|
|
188900
|
+
});
|
|
188901
|
+
watcher.on("change", (filePath) => {
|
|
188902
|
+
const filename = path15.basename(filePath);
|
|
188903
|
+
if (!filename.endsWith(".toml")) return;
|
|
188904
|
+
log(`File change detected: ${filename}`);
|
|
188905
|
+
log(` Full path: ${filePath}`);
|
|
188906
|
+
log(` Currently started migration: ${startedMigration || "(none)"}`);
|
|
188907
|
+
if (filename === startedMigration) {
|
|
188908
|
+
log(`Changed file is the currently started migration - will abort and restart`);
|
|
188909
|
+
log(`Aborting current migration...`);
|
|
188910
|
+
const abortResult = runReshape(["abort"], databaseUrl, migrationsDir, reshapeBinaryPath, log);
|
|
188911
|
+
if (!abortResult.success) {
|
|
188912
|
+
log(`ERROR: Failed to abort migration`);
|
|
188913
|
+
onError(new Error(`Failed to abort migration: ${abortResult.output}`));
|
|
188914
|
+
return;
|
|
188915
|
+
}
|
|
188916
|
+
log(`Migration aborted successfully`);
|
|
188917
|
+
log(`Restarting migration...`);
|
|
188918
|
+
const startResult = runReshape(["start"], databaseUrl, migrationsDir, reshapeBinaryPath, log);
|
|
188919
|
+
if (!startResult.success) {
|
|
188920
|
+
log(`ERROR: Failed to restart migration`);
|
|
188921
|
+
onError(new Error(`Failed to restart migration: ${startResult.output}`));
|
|
188922
|
+
return;
|
|
188923
|
+
}
|
|
188924
|
+
log(`Migration restarted successfully`);
|
|
188925
|
+
log(`Note: Services do NOT need to restart (search_path unchanged)`);
|
|
188926
|
+
} else {
|
|
188927
|
+
log(`Warning: Completed migration "${filename}" was modified`);
|
|
188928
|
+
log(` This file should be read-only. Changes may not take effect.`);
|
|
188929
|
+
log(` To modify this migration, you may need to reset and re-run all migrations.`);
|
|
188930
|
+
}
|
|
188931
|
+
});
|
|
188932
|
+
watcher.on("add", (filePath) => {
|
|
188933
|
+
const filename = path15.basename(filePath);
|
|
188934
|
+
if (!filename.endsWith(".toml")) return;
|
|
188935
|
+
log(`New file detected: ${filename}`);
|
|
188936
|
+
log(` Full path: ${filePath}`);
|
|
188937
|
+
const newMigrationFiles = getMigrationFiles(migrationsDir, log);
|
|
188938
|
+
log(`Updated migration list: ${newMigrationFiles.join(", ")}`);
|
|
188939
|
+
const isNewest = newMigrationFiles[newMigrationFiles.length - 1] === filename;
|
|
188940
|
+
log(`Is this the newest migration? ${isNewest}`);
|
|
188941
|
+
if (!isNewest) {
|
|
188942
|
+
log(`Warning: Added migration "${filename}" is not the latest in lexical order`);
|
|
188943
|
+
log(` Latest migration: ${newMigrationFiles[newMigrationFiles.length - 1]}`);
|
|
188944
|
+
log(` Manual intervention may be needed to ensure correct migration order`);
|
|
188945
|
+
return;
|
|
188946
|
+
}
|
|
188947
|
+
if (startedMigration) {
|
|
188948
|
+
const previousMigrationName = getMigrationName(startedMigration);
|
|
188949
|
+
log(`Completing previous migration "${previousMigrationName}"...`);
|
|
188950
|
+
const completeResult = runReshape(["complete"], databaseUrl, migrationsDir, reshapeBinaryPath, log);
|
|
188951
|
+
if (!completeResult.success) {
|
|
188952
|
+
log(`ERROR: Failed to complete previous migration`);
|
|
188953
|
+
onError(new Error(`Failed to complete previous migration: ${completeResult.output}`));
|
|
188954
|
+
return;
|
|
188955
|
+
}
|
|
188956
|
+
log(`Previous migration completed successfully`);
|
|
188957
|
+
const completedPath = path15.join(migrationsDir, startedMigration);
|
|
188958
|
+
makeReadOnly(completedPath, log);
|
|
188959
|
+
} else {
|
|
188960
|
+
log(`No previous migration was started`);
|
|
188961
|
+
}
|
|
188962
|
+
const newMigrationName = getMigrationName(filename);
|
|
188963
|
+
log(`Starting new migration "${newMigrationName}"...`);
|
|
188964
|
+
const startResult = runReshape(["start"], databaseUrl, migrationsDir, reshapeBinaryPath, log);
|
|
188965
|
+
if (!startResult.success) {
|
|
188966
|
+
log(`ERROR: Failed to start new migration`);
|
|
188967
|
+
onError(new Error(`Failed to start new migration: ${startResult.output}`));
|
|
188968
|
+
return;
|
|
188969
|
+
}
|
|
188970
|
+
const previousStarted = startedMigration;
|
|
188971
|
+
startedMigration = filename;
|
|
188972
|
+
currentMigrationFiles = newMigrationFiles;
|
|
188973
|
+
log(`New migration started successfully`);
|
|
188974
|
+
log(` Previous started migration: ${previousStarted || "(none)"}`);
|
|
188975
|
+
log(` New started migration: ${startedMigration}`);
|
|
188976
|
+
const newSearchPath = getSchemaName(filename);
|
|
188977
|
+
log(`New search_path: ${newSearchPath}`);
|
|
188978
|
+
log(`Notifying services to restart with new search_path...`);
|
|
188979
|
+
onSearchPathChanged(newSearchPath);
|
|
188980
|
+
});
|
|
188981
|
+
watcher.on("unlink", (filePath) => {
|
|
188982
|
+
const filename = path15.basename(filePath);
|
|
188983
|
+
if (!filename.endsWith(".toml")) return;
|
|
188984
|
+
log(`File deleted: ${filename}`);
|
|
188985
|
+
log(` Full path: ${filePath}`);
|
|
188986
|
+
log(` Currently started migration: ${startedMigration || "(none)"}`);
|
|
188987
|
+
if (filename === startedMigration) {
|
|
188988
|
+
log(`Warning: The currently started migration was deleted!`);
|
|
188989
|
+
log(` Manual intervention is required to recover.`);
|
|
188990
|
+
} else {
|
|
188991
|
+
log(`Warning: A migration file was deleted. This may cause issues.`);
|
|
188992
|
+
}
|
|
188993
|
+
log(` Automatic recovery is not supported - manual intervention may be needed.`);
|
|
188994
|
+
});
|
|
188995
|
+
watcher.on("error", (error) => {
|
|
188996
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
188997
|
+
log(`File watcher error: ${err.message}`);
|
|
188998
|
+
onError(err);
|
|
188999
|
+
});
|
|
189000
|
+
watcher.on("ready", () => {
|
|
189001
|
+
log(`File watcher is ready and monitoring for changes`);
|
|
189002
|
+
});
|
|
189003
|
+
log(`File watcher started`);
|
|
189004
|
+
};
|
|
189005
|
+
const close = async () => {
|
|
189006
|
+
log(`Closing Reshape watcher...`);
|
|
189007
|
+
if (watcher) {
|
|
189008
|
+
await watcher.close();
|
|
189009
|
+
watcher = null;
|
|
189010
|
+
log(`File watcher closed`);
|
|
189011
|
+
} else {
|
|
189012
|
+
log(`No file watcher was active`);
|
|
189013
|
+
}
|
|
189014
|
+
log(`Reshape watcher closed`);
|
|
189015
|
+
};
|
|
189016
|
+
return {
|
|
189017
|
+
initialize,
|
|
189018
|
+
startWatching,
|
|
189019
|
+
close
|
|
189020
|
+
};
|
|
189021
|
+
}
|
|
189022
|
+
|
|
188572
189023
|
// src/lib/dev/resource-starter.ts
|
|
188573
189024
|
function findRequiredResources(service) {
|
|
188574
189025
|
const required = { postgres: [], redis: [], storage: [] };
|
|
@@ -188627,11 +189078,14 @@ async function startResources(options2) {
|
|
|
188627
189078
|
const instance = await startPostgres(pg, port, dataDir, (progress) => {
|
|
188628
189079
|
callbacks.onResourceProgress?.(pg.name, progress);
|
|
188629
189080
|
});
|
|
189081
|
+
if (pg.reshape?.enabled) {
|
|
189082
|
+
instance.reshapeEnabled = true;
|
|
189083
|
+
}
|
|
188630
189084
|
resources.set(pg.name, instance);
|
|
188631
189085
|
startedResources.push(instance);
|
|
188632
189086
|
callbacks.onResourceReady?.(pg.name, instance);
|
|
188633
189087
|
log(`Postgres "${pg.name}" ready`);
|
|
188634
|
-
|
|
189088
|
+
const dbState = {
|
|
188635
189089
|
engine: "postgres",
|
|
188636
189090
|
port: instance.port,
|
|
188637
189091
|
host: instance.host,
|
|
@@ -188639,7 +189093,14 @@ async function startResources(options2) {
|
|
|
188639
189093
|
password: instance.password,
|
|
188640
189094
|
dbName: instance.dbName,
|
|
188641
189095
|
url: instance.url
|
|
188642
|
-
}
|
|
189096
|
+
};
|
|
189097
|
+
if (instance.reshapeEnabled) {
|
|
189098
|
+
dbState.reshapeEnabled = true;
|
|
189099
|
+
if (instance.reshapeSearchPath) {
|
|
189100
|
+
dbState.reshapeSearchPath = instance.reshapeSearchPath;
|
|
189101
|
+
}
|
|
189102
|
+
}
|
|
189103
|
+
await stateManager.registerDatabase(pg.name, dbState);
|
|
188643
189104
|
}
|
|
188644
189105
|
for (const redis of redisConfigs) {
|
|
188645
189106
|
if (signal?.cancelled) {
|
|
@@ -188741,24 +189202,24 @@ function watchConfigFile(configPath, debounceMs, onChange) {
|
|
|
188741
189202
|
}
|
|
188742
189203
|
|
|
188743
189204
|
// src/lib/dev/subdomain-generator.ts
|
|
188744
|
-
import * as
|
|
188745
|
-
import * as
|
|
189205
|
+
import * as fs19 from "fs";
|
|
189206
|
+
import * as path16 from "path";
|
|
188746
189207
|
import { generateSlug } from "random-word-slugs";
|
|
188747
189208
|
var StableSubdomainAllocator = class {
|
|
188748
189209
|
tunnelsDir;
|
|
188749
189210
|
tunnelsFilePath;
|
|
188750
189211
|
baseSlug = null;
|
|
188751
189212
|
constructor(projectRoot, key = "default") {
|
|
188752
|
-
this.tunnelsDir =
|
|
188753
|
-
this.tunnelsFilePath =
|
|
189213
|
+
this.tunnelsDir = path16.join(projectRoot, ".specific", "keys", key);
|
|
189214
|
+
this.tunnelsFilePath = path16.join(this.tunnelsDir, "tunnels.json");
|
|
188754
189215
|
this.loadTunnels();
|
|
188755
189216
|
}
|
|
188756
189217
|
loadTunnels() {
|
|
188757
|
-
if (!
|
|
189218
|
+
if (!fs19.existsSync(this.tunnelsFilePath)) {
|
|
188758
189219
|
return;
|
|
188759
189220
|
}
|
|
188760
189221
|
try {
|
|
188761
|
-
const content =
|
|
189222
|
+
const content = fs19.readFileSync(this.tunnelsFilePath, "utf-8");
|
|
188762
189223
|
const data = JSON.parse(content);
|
|
188763
189224
|
if (data.version === 1 && data.baseSlug) {
|
|
188764
189225
|
this.baseSlug = data.baseSlug;
|
|
@@ -188768,14 +189229,14 @@ var StableSubdomainAllocator = class {
|
|
|
188768
189229
|
}
|
|
188769
189230
|
}
|
|
188770
189231
|
saveTunnels() {
|
|
188771
|
-
if (!
|
|
188772
|
-
|
|
189232
|
+
if (!fs19.existsSync(this.tunnelsDir)) {
|
|
189233
|
+
fs19.mkdirSync(this.tunnelsDir, { recursive: true });
|
|
188773
189234
|
}
|
|
188774
189235
|
const data = {
|
|
188775
189236
|
version: 1,
|
|
188776
189237
|
baseSlug: this.baseSlug
|
|
188777
189238
|
};
|
|
188778
|
-
|
|
189239
|
+
fs19.writeFileSync(this.tunnelsFilePath, JSON.stringify(data, null, 2));
|
|
188779
189240
|
}
|
|
188780
189241
|
generateBaseSlug() {
|
|
188781
189242
|
return generateSlug(2, {
|
|
@@ -188838,8 +189299,8 @@ async function startTunnel(serviceName, endpointName, port, subdomain, callbacks
|
|
|
188838
189299
|
}
|
|
188839
189300
|
|
|
188840
189301
|
// src/lib/dev/proxy-registry.ts
|
|
188841
|
-
import * as
|
|
188842
|
-
import * as
|
|
189302
|
+
import * as fs20 from "fs";
|
|
189303
|
+
import * as path17 from "path";
|
|
188843
189304
|
import * as os7 from "os";
|
|
188844
189305
|
import * as net4 from "net";
|
|
188845
189306
|
var ProxyRegistryManager = class {
|
|
@@ -188850,14 +189311,14 @@ var ProxyRegistryManager = class {
|
|
|
188850
189311
|
isOwner = false;
|
|
188851
189312
|
registryWatcher = null;
|
|
188852
189313
|
constructor() {
|
|
188853
|
-
this.proxyDir =
|
|
188854
|
-
this.ownerPath =
|
|
188855
|
-
this.registryPath =
|
|
188856
|
-
this.lockPath =
|
|
189314
|
+
this.proxyDir = path17.join(os7.homedir(), ".specific", "proxy");
|
|
189315
|
+
this.ownerPath = path17.join(this.proxyDir, "owner.json");
|
|
189316
|
+
this.registryPath = path17.join(this.proxyDir, "registry.json");
|
|
189317
|
+
this.lockPath = path17.join(this.proxyDir, "registry.lock");
|
|
188857
189318
|
}
|
|
188858
189319
|
ensureProxyDir() {
|
|
188859
|
-
if (!
|
|
188860
|
-
|
|
189320
|
+
if (!fs20.existsSync(this.proxyDir)) {
|
|
189321
|
+
fs20.mkdirSync(this.proxyDir, { recursive: true });
|
|
188861
189322
|
}
|
|
188862
189323
|
}
|
|
188863
189324
|
isProcessRunning(pid) {
|
|
@@ -188874,7 +189335,7 @@ var ProxyRegistryManager = class {
|
|
|
188874
189335
|
* This catches cases where the owner process is alive but the proxy has crashed.
|
|
188875
189336
|
*/
|
|
188876
189337
|
isProxyListening(port, timeoutMs = 1e3) {
|
|
188877
|
-
return new Promise((
|
|
189338
|
+
return new Promise((resolve9) => {
|
|
188878
189339
|
const socket = new net4.Socket();
|
|
188879
189340
|
let resolved = false;
|
|
188880
189341
|
const cleanup = () => {
|
|
@@ -188886,15 +189347,15 @@ var ProxyRegistryManager = class {
|
|
|
188886
189347
|
socket.setTimeout(timeoutMs);
|
|
188887
189348
|
socket.on("connect", () => {
|
|
188888
189349
|
cleanup();
|
|
188889
|
-
|
|
189350
|
+
resolve9(true);
|
|
188890
189351
|
});
|
|
188891
189352
|
socket.on("timeout", () => {
|
|
188892
189353
|
cleanup();
|
|
188893
|
-
|
|
189354
|
+
resolve9(false);
|
|
188894
189355
|
});
|
|
188895
189356
|
socket.on("error", () => {
|
|
188896
189357
|
cleanup();
|
|
188897
|
-
|
|
189358
|
+
resolve9(false);
|
|
188898
189359
|
});
|
|
188899
189360
|
socket.connect(port, "127.0.0.1");
|
|
188900
189361
|
});
|
|
@@ -188914,15 +189375,15 @@ var ProxyRegistryManager = class {
|
|
|
188914
189375
|
const startTime = Date.now();
|
|
188915
189376
|
while (Date.now() - startTime < timeoutMs) {
|
|
188916
189377
|
try {
|
|
188917
|
-
const fd =
|
|
189378
|
+
const fd = fs20.openSync(
|
|
188918
189379
|
this.lockPath,
|
|
188919
|
-
|
|
189380
|
+
fs20.constants.O_CREAT | fs20.constants.O_EXCL | fs20.constants.O_WRONLY
|
|
188920
189381
|
);
|
|
188921
|
-
|
|
188922
|
-
|
|
189382
|
+
fs20.writeSync(fd, String(process.pid));
|
|
189383
|
+
fs20.closeSync(fd);
|
|
188923
189384
|
return () => {
|
|
188924
189385
|
try {
|
|
188925
|
-
|
|
189386
|
+
fs20.unlinkSync(this.lockPath);
|
|
188926
189387
|
} catch {
|
|
188927
189388
|
}
|
|
188928
189389
|
};
|
|
@@ -188931,21 +189392,21 @@ var ProxyRegistryManager = class {
|
|
|
188931
189392
|
if (err.code === "EEXIST") {
|
|
188932
189393
|
try {
|
|
188933
189394
|
const lockPid = parseInt(
|
|
188934
|
-
|
|
189395
|
+
fs20.readFileSync(this.lockPath, "utf-8").trim(),
|
|
188935
189396
|
10
|
|
188936
189397
|
);
|
|
188937
189398
|
if (!this.isProcessRunning(lockPid)) {
|
|
188938
|
-
|
|
189399
|
+
fs20.unlinkSync(this.lockPath);
|
|
188939
189400
|
continue;
|
|
188940
189401
|
}
|
|
188941
189402
|
} catch {
|
|
188942
189403
|
try {
|
|
188943
|
-
|
|
189404
|
+
fs20.unlinkSync(this.lockPath);
|
|
188944
189405
|
} catch {
|
|
188945
189406
|
}
|
|
188946
189407
|
continue;
|
|
188947
189408
|
}
|
|
188948
|
-
await new Promise((
|
|
189409
|
+
await new Promise((resolve9) => setTimeout(resolve9, 100));
|
|
188949
189410
|
} else {
|
|
188950
189411
|
throw e;
|
|
188951
189412
|
}
|
|
@@ -188960,8 +189421,8 @@ var ProxyRegistryManager = class {
|
|
|
188960
189421
|
async claimProxyOwnership(key) {
|
|
188961
189422
|
const releaseLock = await this.acquireLock();
|
|
188962
189423
|
try {
|
|
188963
|
-
if (
|
|
188964
|
-
const content =
|
|
189424
|
+
if (fs20.existsSync(this.ownerPath)) {
|
|
189425
|
+
const content = fs20.readFileSync(this.ownerPath, "utf-8");
|
|
188965
189426
|
const ownerFile2 = JSON.parse(content);
|
|
188966
189427
|
if (await this.isProxyOwnerHealthy(ownerFile2.owner.pid)) {
|
|
188967
189428
|
return false;
|
|
@@ -188991,11 +189452,11 @@ var ProxyRegistryManager = class {
|
|
|
188991
189452
|
}
|
|
188992
189453
|
const releaseLock = await this.acquireLock();
|
|
188993
189454
|
try {
|
|
188994
|
-
if (
|
|
188995
|
-
const content =
|
|
189455
|
+
if (fs20.existsSync(this.ownerPath)) {
|
|
189456
|
+
const content = fs20.readFileSync(this.ownerPath, "utf-8");
|
|
188996
189457
|
const ownerFile = JSON.parse(content);
|
|
188997
189458
|
if (ownerFile.owner.pid === process.pid) {
|
|
188998
|
-
|
|
189459
|
+
fs20.unlinkSync(this.ownerPath);
|
|
188999
189460
|
}
|
|
189000
189461
|
}
|
|
189001
189462
|
this.isOwner = false;
|
|
@@ -189007,12 +189468,12 @@ var ProxyRegistryManager = class {
|
|
|
189007
189468
|
* Get the current proxy owner.
|
|
189008
189469
|
*/
|
|
189009
189470
|
async getProxyOwner() {
|
|
189010
|
-
if (!
|
|
189471
|
+
if (!fs20.existsSync(this.ownerPath)) {
|
|
189011
189472
|
return null;
|
|
189012
189473
|
}
|
|
189013
189474
|
const releaseLock = await this.acquireLock();
|
|
189014
189475
|
try {
|
|
189015
|
-
const content =
|
|
189476
|
+
const content = fs20.readFileSync(this.ownerPath, "utf-8");
|
|
189016
189477
|
const ownerFile = JSON.parse(content);
|
|
189017
189478
|
if (!await this.isProxyOwnerHealthy(ownerFile.owner.pid)) {
|
|
189018
189479
|
return null;
|
|
@@ -189106,7 +189567,7 @@ var ProxyRegistryManager = class {
|
|
|
189106
189567
|
*/
|
|
189107
189568
|
watchRegistry(onChange) {
|
|
189108
189569
|
this.ensureProxyDir();
|
|
189109
|
-
if (!
|
|
189570
|
+
if (!fs20.existsSync(this.registryPath)) {
|
|
189110
189571
|
const emptyRegistry = {
|
|
189111
189572
|
version: 1,
|
|
189112
189573
|
keys: {},
|
|
@@ -189150,13 +189611,13 @@ var ProxyRegistryManager = class {
|
|
|
189150
189611
|
async attemptElection(key) {
|
|
189151
189612
|
const releaseLock = await this.acquireLock();
|
|
189152
189613
|
try {
|
|
189153
|
-
if (
|
|
189154
|
-
const content =
|
|
189614
|
+
if (fs20.existsSync(this.ownerPath)) {
|
|
189615
|
+
const content = fs20.readFileSync(this.ownerPath, "utf-8");
|
|
189155
189616
|
const ownerFile2 = JSON.parse(content);
|
|
189156
189617
|
if (await this.isProxyOwnerHealthy(ownerFile2.owner.pid)) {
|
|
189157
189618
|
return false;
|
|
189158
189619
|
}
|
|
189159
|
-
|
|
189620
|
+
fs20.unlinkSync(this.ownerPath);
|
|
189160
189621
|
}
|
|
189161
189622
|
const ownerFile = {
|
|
189162
189623
|
version: 1,
|
|
@@ -189174,7 +189635,7 @@ var ProxyRegistryManager = class {
|
|
|
189174
189635
|
}
|
|
189175
189636
|
}
|
|
189176
189637
|
readRegistry() {
|
|
189177
|
-
if (!
|
|
189638
|
+
if (!fs20.existsSync(this.registryPath)) {
|
|
189178
189639
|
return {
|
|
189179
189640
|
version: 1,
|
|
189180
189641
|
keys: {},
|
|
@@ -189182,7 +189643,7 @@ var ProxyRegistryManager = class {
|
|
|
189182
189643
|
};
|
|
189183
189644
|
}
|
|
189184
189645
|
try {
|
|
189185
|
-
const content =
|
|
189646
|
+
const content = fs20.readFileSync(this.registryPath, "utf-8");
|
|
189186
189647
|
return JSON.parse(content);
|
|
189187
189648
|
} catch {
|
|
189188
189649
|
return {
|
|
@@ -189195,8 +189656,8 @@ var ProxyRegistryManager = class {
|
|
|
189195
189656
|
writeFileAtomic(filePath, data) {
|
|
189196
189657
|
this.ensureProxyDir();
|
|
189197
189658
|
const tmpPath = filePath + ".tmp";
|
|
189198
|
-
|
|
189199
|
-
|
|
189659
|
+
fs20.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
|
|
189660
|
+
fs20.renameSync(tmpPath, filePath);
|
|
189200
189661
|
}
|
|
189201
189662
|
};
|
|
189202
189663
|
|
|
@@ -189341,6 +189802,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
|
|
|
189341
189802
|
const proxyRegistryRef = useRef(null);
|
|
189342
189803
|
const electricInstancesRef = useRef([]);
|
|
189343
189804
|
const drizzleGatewayRef = useRef(null);
|
|
189805
|
+
const reshapeWatchersRef = useRef([]);
|
|
189344
189806
|
const registryWatcherCleanupRef = useRef(null);
|
|
189345
189807
|
const electionIntervalRef = useRef(null);
|
|
189346
189808
|
const tunnelsRef = useRef([]);
|
|
@@ -189348,6 +189810,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
|
|
|
189348
189810
|
const adminServerRef = useRef(null);
|
|
189349
189811
|
const servicesRef = useRef([]);
|
|
189350
189812
|
const resourcesRef = useRef(/* @__PURE__ */ new Map());
|
|
189813
|
+
const restartServicesRef = useRef(null);
|
|
189351
189814
|
const [reloadTrigger, setReloadTrigger] = useState5(0);
|
|
189352
189815
|
const [readyToStart, setReadyToStart] = useState5(() => caFilesExist());
|
|
189353
189816
|
const shutdown2 = async () => {
|
|
@@ -189374,6 +189837,8 @@ function DevUI({ instanceKey, tunnelEnabled }) {
|
|
|
189374
189837
|
...servicesRef.current.map((service) => service.stop()),
|
|
189375
189838
|
// Stop all Electric instances
|
|
189376
189839
|
...electricInstancesRef.current.map((electric) => electric.stop()),
|
|
189840
|
+
// Stop all Reshape watchers
|
|
189841
|
+
...reshapeWatchersRef.current.map((watcher) => watcher.close()),
|
|
189377
189842
|
// Stop Drizzle Gateway
|
|
189378
189843
|
drizzleGatewayRef.current?.stop(),
|
|
189379
189844
|
// Stop all resources
|
|
@@ -189381,6 +189846,8 @@ function DevUI({ instanceKey, tunnelEnabled }) {
|
|
|
189381
189846
|
// Stop all tunnels
|
|
189382
189847
|
...tunnelsRef.current.map((tunnel) => tunnel.stop())
|
|
189383
189848
|
]);
|
|
189849
|
+
reshapeWatchersRef.current = [];
|
|
189850
|
+
restartServicesRef.current = null;
|
|
189384
189851
|
if (proxyRegistryRef.current) {
|
|
189385
189852
|
await proxyRegistryRef.current.unregisterServices(instanceKey);
|
|
189386
189853
|
await proxyRegistryRef.current.releaseProxyOwnership();
|
|
@@ -189413,6 +189880,8 @@ function DevUI({ instanceKey, tunnelEnabled }) {
|
|
|
189413
189880
|
...servicesRef.current.map((service) => service.stop()),
|
|
189414
189881
|
// Stop all Electric instances
|
|
189415
189882
|
...electricInstancesRef.current.map((electric) => electric.stop()),
|
|
189883
|
+
// Stop all Reshape watchers
|
|
189884
|
+
...reshapeWatchersRef.current.map((watcher) => watcher.close()),
|
|
189416
189885
|
// Stop Drizzle Gateway
|
|
189417
189886
|
drizzleGatewayRef.current?.stop(),
|
|
189418
189887
|
// Stop all resources
|
|
@@ -189421,9 +189890,11 @@ function DevUI({ instanceKey, tunnelEnabled }) {
|
|
|
189421
189890
|
...tunnelsRef.current.map((tunnel) => tunnel.stop())
|
|
189422
189891
|
]);
|
|
189423
189892
|
if (tunnelsRef.current.length > 0) {
|
|
189424
|
-
await new Promise((
|
|
189893
|
+
await new Promise((resolve9) => setTimeout(resolve9, 1500));
|
|
189425
189894
|
}
|
|
189426
189895
|
electricInstancesRef.current = [];
|
|
189896
|
+
reshapeWatchersRef.current = [];
|
|
189897
|
+
restartServicesRef.current = null;
|
|
189427
189898
|
drizzleGatewayRef.current = null;
|
|
189428
189899
|
proxyRef.current = null;
|
|
189429
189900
|
adminServerRef.current = null;
|
|
@@ -189488,10 +189959,10 @@ function DevUI({ instanceKey, tunnelEnabled }) {
|
|
|
189488
189959
|
}, [state.status]);
|
|
189489
189960
|
useEffect3(() => {
|
|
189490
189961
|
if (state.status !== "running") return;
|
|
189491
|
-
const configPath =
|
|
189962
|
+
const configPath = path18.join(process.cwd(), "specific.hcl");
|
|
189492
189963
|
const watcher = watchConfigFile(configPath, 1e3, () => {
|
|
189493
189964
|
try {
|
|
189494
|
-
const hcl =
|
|
189965
|
+
const hcl = fs21.readFileSync(configPath, "utf-8");
|
|
189495
189966
|
parseConfig(hcl).then(() => {
|
|
189496
189967
|
triggerReload();
|
|
189497
189968
|
}).catch((err) => {
|
|
@@ -189616,8 +190087,8 @@ function DevUI({ instanceKey, tunnelEnabled }) {
|
|
|
189616
190087
|
}));
|
|
189617
190088
|
return;
|
|
189618
190089
|
}
|
|
189619
|
-
const configPath =
|
|
189620
|
-
if (!
|
|
190090
|
+
const configPath = path18.join(process.cwd(), "specific.hcl");
|
|
190091
|
+
if (!fs21.existsSync(configPath)) {
|
|
189621
190092
|
writeLog("system", "Waiting for specific.hcl to appear");
|
|
189622
190093
|
setState((s) => ({
|
|
189623
190094
|
...s,
|
|
@@ -189636,7 +190107,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
|
|
|
189636
190107
|
}
|
|
189637
190108
|
let config2;
|
|
189638
190109
|
try {
|
|
189639
|
-
const hcl =
|
|
190110
|
+
const hcl = fs21.readFileSync(configPath, "utf-8");
|
|
189640
190111
|
config2 = await parseConfig(hcl);
|
|
189641
190112
|
} catch (err) {
|
|
189642
190113
|
setState((s) => ({
|
|
@@ -189742,7 +190213,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
|
|
|
189742
190213
|
const drizzleGateway = await startDrizzleGateway(
|
|
189743
190214
|
postgresResources,
|
|
189744
190215
|
drizzlePort,
|
|
189745
|
-
|
|
190216
|
+
path18.join(process.cwd(), ".specific", "keys", instanceKey)
|
|
189746
190217
|
);
|
|
189747
190218
|
startedDrizzleGateway = drizzleGateway;
|
|
189748
190219
|
drizzleGatewayRef.current = drizzleGateway;
|
|
@@ -189755,6 +190226,59 @@ function DevUI({ instanceKey, tunnelEnabled }) {
|
|
|
189755
190226
|
}
|
|
189756
190227
|
}
|
|
189757
190228
|
if (cancelled) return;
|
|
190229
|
+
const startedReshapeWatchers = [];
|
|
190230
|
+
const hasReshapeEnabled = config2.postgres.some((pg) => pg.reshape?.enabled);
|
|
190231
|
+
const reshapeBin = hasReshapeEnabled ? await ensureBinary(reshapeBinary) : void 0;
|
|
190232
|
+
for (const pg of config2.postgres) {
|
|
190233
|
+
if (pg.reshape?.enabled) {
|
|
190234
|
+
const resource = resources2.get(pg.name);
|
|
190235
|
+
if (!resource) continue;
|
|
190236
|
+
const migrationsDir = path18.resolve(
|
|
190237
|
+
process.cwd(),
|
|
190238
|
+
pg.reshape.migrations_dir ?? "migrations"
|
|
190239
|
+
);
|
|
190240
|
+
writeLog("system", `Initializing Reshape migrations for "${pg.name}" from ${migrationsDir}`);
|
|
190241
|
+
const watcher = createReshapeWatcher({
|
|
190242
|
+
databaseUrl: resource.url,
|
|
190243
|
+
migrationsDir,
|
|
190244
|
+
reshapeBinaryPath: reshapeBin.executables["reshape"],
|
|
190245
|
+
onSearchPathChanged: async (newSearchPath) => {
|
|
190246
|
+
resource.reshapeSearchPath = newSearchPath ?? void 0;
|
|
190247
|
+
if (newSearchPath) {
|
|
190248
|
+
await stateManager.updateDatabase(pg.name, {
|
|
190249
|
+
reshapeSearchPath: newSearchPath
|
|
190250
|
+
});
|
|
190251
|
+
}
|
|
190252
|
+
writeLog("system", `Reshape search_path changed for "${pg.name}": ${newSearchPath}`);
|
|
190253
|
+
if (restartServicesRef.current) {
|
|
190254
|
+
await restartServicesRef.current();
|
|
190255
|
+
}
|
|
190256
|
+
},
|
|
190257
|
+
onError: (err) => {
|
|
190258
|
+
writeLog("system:error", `Reshape error for "${pg.name}": ${err.message}`);
|
|
190259
|
+
},
|
|
190260
|
+
log: (msg) => writeLog("system", `[reshape:${pg.name}] ${msg}`)
|
|
190261
|
+
});
|
|
190262
|
+
try {
|
|
190263
|
+
const initialSearchPath = await watcher.initialize();
|
|
190264
|
+
resource.reshapeSearchPath = initialSearchPath ?? void 0;
|
|
190265
|
+
if (initialSearchPath) {
|
|
190266
|
+
await stateManager.updateDatabase(pg.name, {
|
|
190267
|
+
reshapeSearchPath: initialSearchPath
|
|
190268
|
+
});
|
|
190269
|
+
writeLog("system", `Reshape search_path for "${pg.name}": ${initialSearchPath}`);
|
|
190270
|
+
}
|
|
190271
|
+
startedReshapeWatchers.push(watcher);
|
|
190272
|
+
} catch (err) {
|
|
190273
|
+
writeLog(
|
|
190274
|
+
"system:warning",
|
|
190275
|
+
`Failed to initialize Reshape for "${pg.name}": ${err instanceof Error ? err.message : String(err)}`
|
|
190276
|
+
);
|
|
190277
|
+
}
|
|
190278
|
+
}
|
|
190279
|
+
}
|
|
190280
|
+
reshapeWatchersRef.current = startedReshapeWatchers;
|
|
190281
|
+
if (cancelled) return;
|
|
189758
190282
|
const secrets = await prepareSecrets(config2.secrets, true);
|
|
189759
190283
|
const configs = await prepareConfigs(config2.configs, void 0, true);
|
|
189760
190284
|
const usedSecrets = findUsedSecrets(config2.services, true);
|
|
@@ -189927,6 +190451,40 @@ Add them to the config block in specific.local`);
|
|
|
189927
190451
|
}
|
|
189928
190452
|
}
|
|
189929
190453
|
if (cancelled) return;
|
|
190454
|
+
restartServicesRef.current = async () => {
|
|
190455
|
+
if (shuttingDown.current) return;
|
|
190456
|
+
writeLog("system", "Restarting services (migration changed)");
|
|
190457
|
+
await Promise.all(servicesRef.current.map((s) => s.stop()));
|
|
190458
|
+
const newServices = [];
|
|
190459
|
+
for (const service of config2.services) {
|
|
190460
|
+
if (service.serve) continue;
|
|
190461
|
+
if (!service.command && !service.dev?.command) continue;
|
|
190462
|
+
try {
|
|
190463
|
+
const endpointPorts = serviceEndpointPorts.get(service.name) || /* @__PURE__ */ new Map();
|
|
190464
|
+
const running = startService(
|
|
190465
|
+
service,
|
|
190466
|
+
resources2,
|
|
190467
|
+
secrets,
|
|
190468
|
+
configs,
|
|
190469
|
+
endpointPorts,
|
|
190470
|
+
serviceEndpoints,
|
|
190471
|
+
(line) => addLog(line, colorMap)
|
|
190472
|
+
);
|
|
190473
|
+
newServices.push(running);
|
|
190474
|
+
} catch (err) {
|
|
190475
|
+
writeLog(
|
|
190476
|
+
"system:error",
|
|
190477
|
+
`Failed to restart service "${service.name}": ${err instanceof Error ? err.message : String(err)}`
|
|
190478
|
+
);
|
|
190479
|
+
}
|
|
190480
|
+
}
|
|
190481
|
+
servicesRef.current = newServices;
|
|
190482
|
+
setState((s) => ({ ...s, services: newServices }));
|
|
190483
|
+
writeLog("system", "Services restarted");
|
|
190484
|
+
};
|
|
190485
|
+
for (const watcher of reshapeWatchersRef.current) {
|
|
190486
|
+
watcher.startWatching();
|
|
190487
|
+
}
|
|
189930
190488
|
if (tunnelEnabled) {
|
|
189931
190489
|
writeLog("system", "Tunnel mode enabled, starting tunnels for public services");
|
|
189932
190490
|
if (exposedServices.length === 0) {
|
|
@@ -190144,6 +190702,12 @@ Add them to the config block in specific.local`);
|
|
|
190144
190702
|
electric.stop().catch(() => {
|
|
190145
190703
|
});
|
|
190146
190704
|
}
|
|
190705
|
+
for (const watcher of reshapeWatchersRef.current) {
|
|
190706
|
+
watcher.close().catch(() => {
|
|
190707
|
+
});
|
|
190708
|
+
}
|
|
190709
|
+
reshapeWatchersRef.current = [];
|
|
190710
|
+
restartServicesRef.current = null;
|
|
190147
190711
|
if (startedDrizzleGateway) {
|
|
190148
190712
|
startedDrizzleGateway.stop().catch(() => {
|
|
190149
190713
|
});
|
|
@@ -190366,7 +190930,7 @@ function devCommand(instanceKey, tunnelEnabled = false) {
|
|
|
190366
190930
|
|
|
190367
190931
|
// src/lib/dev/git-worktree.ts
|
|
190368
190932
|
import { execSync as execSync2 } from "child_process";
|
|
190369
|
-
import * as
|
|
190933
|
+
import * as path19 from "path";
|
|
190370
190934
|
function isInWorktree() {
|
|
190371
190935
|
try {
|
|
190372
190936
|
const commonDir = execSync2("git rev-parse --git-common-dir", {
|
|
@@ -190377,8 +190941,8 @@ function isInWorktree() {
|
|
|
190377
190941
|
encoding: "utf-8",
|
|
190378
190942
|
stdio: ["pipe", "pipe", "pipe"]
|
|
190379
190943
|
}).trim();
|
|
190380
|
-
const resolvedCommonDir =
|
|
190381
|
-
const resolvedGitDir =
|
|
190944
|
+
const resolvedCommonDir = path19.resolve(commonDir);
|
|
190945
|
+
const resolvedGitDir = path19.resolve(gitDir);
|
|
190382
190946
|
return resolvedCommonDir !== resolvedGitDir;
|
|
190383
190947
|
} catch {
|
|
190384
190948
|
return false;
|
|
@@ -190393,7 +190957,7 @@ function getWorktreeName() {
|
|
|
190393
190957
|
encoding: "utf-8",
|
|
190394
190958
|
stdio: ["pipe", "pipe", "pipe"]
|
|
190395
190959
|
}).trim();
|
|
190396
|
-
return
|
|
190960
|
+
return path19.basename(gitDir);
|
|
190397
190961
|
} catch {
|
|
190398
190962
|
return null;
|
|
190399
190963
|
}
|
|
@@ -190408,35 +190972,35 @@ init_open();
|
|
|
190408
190972
|
import React7, { useState as useState6, useEffect as useEffect4, useCallback } from "react";
|
|
190409
190973
|
import { render as render5, Text as Text7, Box as Box7, useApp as useApp3, useInput as useInput5 } from "ink";
|
|
190410
190974
|
import Spinner5 from "ink-spinner";
|
|
190411
|
-
import * as
|
|
190412
|
-
import * as
|
|
190975
|
+
import * as fs23 from "fs";
|
|
190976
|
+
import * as path21 from "path";
|
|
190413
190977
|
|
|
190414
190978
|
// src/lib/deploy/build-tester.ts
|
|
190415
190979
|
import { spawn as spawn5 } from "child_process";
|
|
190416
|
-
import { existsSync as
|
|
190417
|
-
import { join as
|
|
190980
|
+
import { existsSync as existsSync20 } from "fs";
|
|
190981
|
+
import { join as join21 } from "path";
|
|
190418
190982
|
function getDependencyInstallCommand(build, projectDir) {
|
|
190419
190983
|
switch (build.base) {
|
|
190420
190984
|
case "node":
|
|
190421
|
-
if (
|
|
190985
|
+
if (existsSync20(join21(projectDir, "pnpm-lock.yaml"))) {
|
|
190422
190986
|
return "pnpm install --frozen-lockfile";
|
|
190423
|
-
} else if (
|
|
190987
|
+
} else if (existsSync20(join21(projectDir, "yarn.lock"))) {
|
|
190424
190988
|
return "yarn install --frozen-lockfile";
|
|
190425
|
-
} else if (
|
|
190989
|
+
} else if (existsSync20(join21(projectDir, "package-lock.json"))) {
|
|
190426
190990
|
return "npm ci";
|
|
190427
190991
|
} else {
|
|
190428
190992
|
return "npm install";
|
|
190429
190993
|
}
|
|
190430
190994
|
case "python":
|
|
190431
|
-
if (
|
|
190995
|
+
if (existsSync20(join21(projectDir, "poetry.lock"))) {
|
|
190432
190996
|
return "poetry install --no-interaction";
|
|
190433
|
-
} else if (
|
|
190997
|
+
} else if (existsSync20(join21(projectDir, "Pipfile.lock"))) {
|
|
190434
190998
|
return "pipenv install --deploy";
|
|
190435
|
-
} else if (
|
|
190999
|
+
} else if (existsSync20(join21(projectDir, "Pipfile"))) {
|
|
190436
191000
|
return "pipenv install";
|
|
190437
|
-
} else if (
|
|
191001
|
+
} else if (existsSync20(join21(projectDir, "pyproject.toml"))) {
|
|
190438
191002
|
return "pip install .";
|
|
190439
|
-
} else if (
|
|
191003
|
+
} else if (existsSync20(join21(projectDir, "requirements.txt"))) {
|
|
190440
191004
|
return "pip install -r requirements.txt";
|
|
190441
191005
|
}
|
|
190442
191006
|
return null;
|
|
@@ -190450,7 +191014,7 @@ function getDependencyInstallCommand(build, projectDir) {
|
|
|
190450
191014
|
}
|
|
190451
191015
|
}
|
|
190452
191016
|
function runCommand2(command, projectDir, buildName) {
|
|
190453
|
-
return new Promise((
|
|
191017
|
+
return new Promise((resolve9) => {
|
|
190454
191018
|
const stdout = [];
|
|
190455
191019
|
const stderr = [];
|
|
190456
191020
|
writeLog("build-test", `[${buildName}] Running: ${command}`);
|
|
@@ -190480,7 +191044,7 @@ function runCommand2(command, projectDir, buildName) {
|
|
|
190480
191044
|
});
|
|
190481
191045
|
child.on("error", (err) => {
|
|
190482
191046
|
writeLog("build-test:error", `[${buildName}] Failed to start: ${err.message}`);
|
|
190483
|
-
|
|
191047
|
+
resolve9({
|
|
190484
191048
|
success: false,
|
|
190485
191049
|
output: `Failed to start command: ${err.message}`
|
|
190486
191050
|
});
|
|
@@ -190489,10 +191053,10 @@ function runCommand2(command, projectDir, buildName) {
|
|
|
190489
191053
|
const output = [...stdout, ...stderr].join("");
|
|
190490
191054
|
if (code === 0) {
|
|
190491
191055
|
writeLog("build-test", `[${buildName}] Command succeeded (exit code 0)`);
|
|
190492
|
-
|
|
191056
|
+
resolve9({ success: true, output });
|
|
190493
191057
|
} else {
|
|
190494
191058
|
writeLog("build-test:error", `[${buildName}] Command failed with exit code ${code}`);
|
|
190495
|
-
|
|
191059
|
+
resolve9({
|
|
190496
191060
|
success: false,
|
|
190497
191061
|
output: output || `Exit code: ${code}`
|
|
190498
191062
|
});
|
|
@@ -190582,8 +191146,8 @@ async function testAllBuilds(builds, projectDir) {
|
|
|
190582
191146
|
|
|
190583
191147
|
// src/lib/tarball/create.ts
|
|
190584
191148
|
import { execSync as execSync3 } from "child_process";
|
|
190585
|
-
import * as
|
|
190586
|
-
import * as
|
|
191149
|
+
import * as fs22 from "fs";
|
|
191150
|
+
import * as path20 from "path";
|
|
190587
191151
|
import { createTarPacker, createEntryItemGenerator } from "tar-vern";
|
|
190588
191152
|
function isInsideGitRepository(dir) {
|
|
190589
191153
|
try {
|
|
@@ -190640,10 +191204,10 @@ var EXCLUDED_DIRS = [
|
|
|
190640
191204
|
];
|
|
190641
191205
|
async function collectPaths(baseDir, currentDir, exclude) {
|
|
190642
191206
|
const results = [];
|
|
190643
|
-
const entries = await
|
|
191207
|
+
const entries = await fs22.promises.readdir(currentDir, { withFileTypes: true });
|
|
190644
191208
|
for (const entry of entries) {
|
|
190645
|
-
const fullPath =
|
|
190646
|
-
const relativePath =
|
|
191209
|
+
const fullPath = path20.join(currentDir, entry.name);
|
|
191210
|
+
const relativePath = path20.relative(baseDir, fullPath);
|
|
190647
191211
|
if (entry.isDirectory()) {
|
|
190648
191212
|
if (!exclude.includes(entry.name)) {
|
|
190649
191213
|
results.push(relativePath);
|
|
@@ -190658,8 +191222,8 @@ async function collectPaths(baseDir, currentDir, exclude) {
|
|
|
190658
191222
|
}
|
|
190659
191223
|
async function createTarArchive(projectDir) {
|
|
190660
191224
|
writeLog("tarball", "Creating tarball using tar-vern (non-git project)");
|
|
190661
|
-
const configPath =
|
|
190662
|
-
if (!
|
|
191225
|
+
const configPath = path20.join(projectDir, "specific.hcl");
|
|
191226
|
+
if (!fs22.existsSync(configPath)) {
|
|
190663
191227
|
throw new Error("specific.hcl not found in project directory");
|
|
190664
191228
|
}
|
|
190665
191229
|
const relativePaths = await collectPaths(projectDir, projectDir, EXCLUDED_DIRS);
|
|
@@ -190676,8 +191240,8 @@ async function createTarArchive(projectDir) {
|
|
|
190676
191240
|
}
|
|
190677
191241
|
function findWidestContext(projectDir, contexts) {
|
|
190678
191242
|
if (contexts.length === 0) return ".";
|
|
190679
|
-
const absolute = contexts.map((c) =>
|
|
190680
|
-
const segments = absolute.map((p) => p.split(
|
|
191243
|
+
const absolute = contexts.map((c) => path20.resolve(projectDir, c));
|
|
191244
|
+
const segments = absolute.map((p) => p.split(path20.sep).filter(Boolean));
|
|
190681
191245
|
const firstSegments = segments[0];
|
|
190682
191246
|
if (!firstSegments) return ".";
|
|
190683
191247
|
const minLen = Math.min(...segments.map((s) => s.length));
|
|
@@ -190691,12 +191255,12 @@ function findWidestContext(projectDir, contexts) {
|
|
|
190691
191255
|
}
|
|
190692
191256
|
}
|
|
190693
191257
|
const ancestorSegments = firstSegments.slice(0, commonLength);
|
|
190694
|
-
const ancestor =
|
|
190695
|
-
return
|
|
191258
|
+
const ancestor = path20.sep + ancestorSegments.join(path20.sep);
|
|
191259
|
+
return path20.relative(projectDir, ancestor) || ".";
|
|
190696
191260
|
}
|
|
190697
191261
|
async function createProjectTarball(projectDir, context = ".") {
|
|
190698
|
-
const contextDir =
|
|
190699
|
-
const appPath =
|
|
191262
|
+
const contextDir = path20.resolve(projectDir, context);
|
|
191263
|
+
const appPath = path20.relative(contextDir, projectDir) || ".";
|
|
190700
191264
|
writeLog("tarball", `Context: ${contextDir}, appPath: ${appPath}`);
|
|
190701
191265
|
let tarball;
|
|
190702
191266
|
if (isInsideGitRepository(contextDir)) {
|
|
@@ -191583,14 +192147,14 @@ ${errorMsg}`
|
|
|
191583
192147
|
), phase === "error" && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, { color: "red" }, "Error: ", error)), phase === "success" && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text7, { color: "green" }, "Deployment successful!"), deployment?.publicUrls && Object.keys(deployment.publicUrls).length > 0 && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "Public URLs:"), Object.entries(deployment.publicUrls).map(([name, url]) => /* @__PURE__ */ React7.createElement(Text7, { key: name }, " ", name, ": ", /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, url))))));
|
|
191584
192148
|
}
|
|
191585
192149
|
async function deployCommand(environment, options2) {
|
|
191586
|
-
const configPath =
|
|
191587
|
-
if (!
|
|
192150
|
+
const configPath = path21.join(process.cwd(), "specific.hcl");
|
|
192151
|
+
if (!fs23.existsSync(configPath)) {
|
|
191588
192152
|
console.error("Error: No specific.hcl found in current directory");
|
|
191589
192153
|
process.exit(1);
|
|
191590
192154
|
}
|
|
191591
192155
|
let config;
|
|
191592
192156
|
try {
|
|
191593
|
-
const hcl =
|
|
192157
|
+
const hcl = fs23.readFileSync(configPath, "utf-8");
|
|
191594
192158
|
config = await parseConfig(hcl);
|
|
191595
192159
|
} catch (err) {
|
|
191596
192160
|
console.error(
|
|
@@ -191614,8 +192178,8 @@ async function deployCommand(environment, options2) {
|
|
|
191614
192178
|
|
|
191615
192179
|
// src/commands/exec.tsx
|
|
191616
192180
|
import { spawn as spawn6 } from "child_process";
|
|
191617
|
-
import * as
|
|
191618
|
-
import * as
|
|
192181
|
+
import * as fs24 from "fs";
|
|
192182
|
+
import * as path22 from "path";
|
|
191619
192183
|
async function execCommand(serviceName, command, instanceKey = "default") {
|
|
191620
192184
|
if (command.length === 0) {
|
|
191621
192185
|
console.error(
|
|
@@ -191643,14 +192207,14 @@ async function execCommand(serviceName, command, instanceKey = "default") {
|
|
|
191643
192207
|
}
|
|
191644
192208
|
}
|
|
191645
192209
|
};
|
|
191646
|
-
const configPath =
|
|
191647
|
-
if (!
|
|
192210
|
+
const configPath = path22.join(process.cwd(), "specific.hcl");
|
|
192211
|
+
if (!fs24.existsSync(configPath)) {
|
|
191648
192212
|
console.error("Error: No specific.hcl found in current directory");
|
|
191649
192213
|
process.exit(1);
|
|
191650
192214
|
}
|
|
191651
192215
|
let config;
|
|
191652
192216
|
try {
|
|
191653
|
-
const hcl =
|
|
192217
|
+
const hcl = fs24.readFileSync(configPath, "utf-8");
|
|
191654
192218
|
config = await parseConfig(hcl);
|
|
191655
192219
|
} catch (err) {
|
|
191656
192220
|
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -191697,6 +192261,8 @@ async function execCommand(serviceName, command, instanceKey = "default") {
|
|
|
191697
192261
|
bucket: dbState.engine === "object_store" ? name : void 0,
|
|
191698
192262
|
syncUrl: dbState.syncUrl,
|
|
191699
192263
|
syncSecret: dbState.syncSecret,
|
|
192264
|
+
reshapeEnabled: dbState.reshapeEnabled,
|
|
192265
|
+
reshapeSearchPath: dbState.reshapeSearchPath,
|
|
191700
192266
|
async stop() {
|
|
191701
192267
|
}
|
|
191702
192268
|
});
|
|
@@ -191787,8 +192353,8 @@ async function execCommand(serviceName, command, instanceKey = "default") {
|
|
|
191787
192353
|
|
|
191788
192354
|
// src/commands/psql.tsx
|
|
191789
192355
|
import { spawn as spawn7 } from "child_process";
|
|
191790
|
-
import * as
|
|
191791
|
-
import * as
|
|
192356
|
+
import * as fs25 from "fs";
|
|
192357
|
+
import * as path23 from "path";
|
|
191792
192358
|
async function psqlCommand(databaseName, instanceKey = "default", extraArgs = []) {
|
|
191793
192359
|
let startedResources = [];
|
|
191794
192360
|
let ownsInstances = false;
|
|
@@ -191805,14 +192371,14 @@ async function psqlCommand(databaseName, instanceKey = "default", extraArgs = []
|
|
|
191805
192371
|
}
|
|
191806
192372
|
}
|
|
191807
192373
|
};
|
|
191808
|
-
const configPath =
|
|
191809
|
-
if (!
|
|
192374
|
+
const configPath = path23.join(process.cwd(), "specific.hcl");
|
|
192375
|
+
if (!fs25.existsSync(configPath)) {
|
|
191810
192376
|
console.error("Error: No specific.hcl found in current directory");
|
|
191811
192377
|
process.exit(1);
|
|
191812
192378
|
}
|
|
191813
192379
|
let config;
|
|
191814
192380
|
try {
|
|
191815
|
-
const hcl =
|
|
192381
|
+
const hcl = fs25.readFileSync(configPath, "utf-8");
|
|
191816
192382
|
config = await parseConfig(hcl);
|
|
191817
192383
|
} catch (err) {
|
|
191818
192384
|
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -191932,25 +192498,227 @@ async function psqlCommand(databaseName, instanceKey = "default", extraArgs = []
|
|
|
191932
192498
|
});
|
|
191933
192499
|
}
|
|
191934
192500
|
|
|
192501
|
+
// src/commands/reshape.tsx
|
|
192502
|
+
import { spawn as spawn8 } from "child_process";
|
|
192503
|
+
import * as fs26 from "fs";
|
|
192504
|
+
import * as path24 from "path";
|
|
192505
|
+
var VALID_ACTIONS = ["start", "complete", "status", "abort", "check"];
|
|
192506
|
+
var MIGRATION_SUBCOMMANDS = ["start", "complete", "abort"];
|
|
192507
|
+
var OFFLINE_ACTIONS = ["check"];
|
|
192508
|
+
async function reshapeCommand(action, databaseName, instanceKey = "default") {
|
|
192509
|
+
if (!VALID_ACTIONS.includes(action)) {
|
|
192510
|
+
console.error(
|
|
192511
|
+
`Error: Invalid action "${action}". Valid actions: ${VALID_ACTIONS.join(", ")}`
|
|
192512
|
+
);
|
|
192513
|
+
process.exit(1);
|
|
192514
|
+
}
|
|
192515
|
+
const isOfflineAction = OFFLINE_ACTIONS.includes(action);
|
|
192516
|
+
const configPath = path24.join(process.cwd(), "specific.hcl");
|
|
192517
|
+
let config;
|
|
192518
|
+
let migrationsDir = "migrations";
|
|
192519
|
+
let targetDb;
|
|
192520
|
+
try {
|
|
192521
|
+
if (fs26.existsSync(configPath)) {
|
|
192522
|
+
const configContent = fs26.readFileSync(configPath, "utf-8");
|
|
192523
|
+
config = await parseConfig(configContent);
|
|
192524
|
+
if (databaseName) {
|
|
192525
|
+
const postgresConfig = config.postgres.find((p) => p.name === databaseName);
|
|
192526
|
+
if (postgresConfig?.reshape?.migrations_dir) {
|
|
192527
|
+
migrationsDir = postgresConfig.reshape.migrations_dir;
|
|
192528
|
+
}
|
|
192529
|
+
targetDb = databaseName;
|
|
192530
|
+
} else {
|
|
192531
|
+
const reshapePostgres = config.postgres.find((p) => p.reshape?.enabled);
|
|
192532
|
+
if (reshapePostgres) {
|
|
192533
|
+
if (reshapePostgres.reshape?.migrations_dir) {
|
|
192534
|
+
migrationsDir = reshapePostgres.reshape.migrations_dir;
|
|
192535
|
+
}
|
|
192536
|
+
targetDb = reshapePostgres.name;
|
|
192537
|
+
} else if (config.postgres.length > 0) {
|
|
192538
|
+
const first = config.postgres[0];
|
|
192539
|
+
if (first.reshape?.migrations_dir) {
|
|
192540
|
+
migrationsDir = first.reshape.migrations_dir;
|
|
192541
|
+
}
|
|
192542
|
+
targetDb = first.name;
|
|
192543
|
+
}
|
|
192544
|
+
}
|
|
192545
|
+
}
|
|
192546
|
+
} catch {
|
|
192547
|
+
}
|
|
192548
|
+
let dbUrl;
|
|
192549
|
+
let startedResources = [];
|
|
192550
|
+
let ownsInstances = false;
|
|
192551
|
+
let stateManager = null;
|
|
192552
|
+
const cleanup = async () => {
|
|
192553
|
+
if (ownsInstances) {
|
|
192554
|
+
for (const resource of startedResources) {
|
|
192555
|
+
await resource.stop().catch(() => {
|
|
192556
|
+
});
|
|
192557
|
+
}
|
|
192558
|
+
if (stateManager) {
|
|
192559
|
+
await stateManager.releaseOwnership().catch(() => {
|
|
192560
|
+
});
|
|
192561
|
+
}
|
|
192562
|
+
}
|
|
192563
|
+
};
|
|
192564
|
+
if (!isOfflineAction) {
|
|
192565
|
+
stateManager = new InstanceStateManager(process.cwd(), instanceKey);
|
|
192566
|
+
await stateManager.cleanStaleState();
|
|
192567
|
+
const existingInstances = await stateManager.getExistingInstances();
|
|
192568
|
+
if (existingInstances) {
|
|
192569
|
+
const availableDatabases = Object.keys(existingInstances.databases).filter(
|
|
192570
|
+
(name) => existingInstances.databases[name]?.engine === "postgres"
|
|
192571
|
+
);
|
|
192572
|
+
if (availableDatabases.length === 0) {
|
|
192573
|
+
console.error("Error: No PostgreSQL databases found in running instances.");
|
|
192574
|
+
process.exit(1);
|
|
192575
|
+
}
|
|
192576
|
+
if (databaseName) {
|
|
192577
|
+
if (!existingInstances.databases[databaseName]) {
|
|
192578
|
+
console.error(
|
|
192579
|
+
`Error: Database "${databaseName}" not found. Available: ${availableDatabases.join(", ")}`
|
|
192580
|
+
);
|
|
192581
|
+
process.exit(1);
|
|
192582
|
+
}
|
|
192583
|
+
if (existingInstances.databases[databaseName]?.engine !== "postgres") {
|
|
192584
|
+
console.error(
|
|
192585
|
+
`Error: Database "${databaseName}" is not a PostgreSQL database.`
|
|
192586
|
+
);
|
|
192587
|
+
process.exit(1);
|
|
192588
|
+
}
|
|
192589
|
+
targetDb = databaseName;
|
|
192590
|
+
} else {
|
|
192591
|
+
if (availableDatabases.length > 1) {
|
|
192592
|
+
console.error(
|
|
192593
|
+
`Error: Multiple databases available. Specify one: ${availableDatabases.join(", ")}`
|
|
192594
|
+
);
|
|
192595
|
+
process.exit(1);
|
|
192596
|
+
}
|
|
192597
|
+
targetDb = availableDatabases[0];
|
|
192598
|
+
}
|
|
192599
|
+
dbUrl = existingInstances.databases[targetDb].url;
|
|
192600
|
+
} else {
|
|
192601
|
+
if (!config) {
|
|
192602
|
+
console.error("Error: No specific.hcl found in current directory");
|
|
192603
|
+
process.exit(1);
|
|
192604
|
+
}
|
|
192605
|
+
if (!targetDb) {
|
|
192606
|
+
console.error("Error: No PostgreSQL database found in configuration.");
|
|
192607
|
+
process.exit(1);
|
|
192608
|
+
}
|
|
192609
|
+
console.error("Starting resources...");
|
|
192610
|
+
try {
|
|
192611
|
+
await stateManager.claimOwnership("reshape");
|
|
192612
|
+
ownsInstances = true;
|
|
192613
|
+
} catch (err) {
|
|
192614
|
+
console.error(
|
|
192615
|
+
`Error: ${err instanceof Error ? err.message : String(err)}`
|
|
192616
|
+
);
|
|
192617
|
+
process.exit(1);
|
|
192618
|
+
}
|
|
192619
|
+
try {
|
|
192620
|
+
const result = await startResources({
|
|
192621
|
+
config,
|
|
192622
|
+
selection: {
|
|
192623
|
+
mode: "required",
|
|
192624
|
+
postgres: [targetDb],
|
|
192625
|
+
redis: [],
|
|
192626
|
+
storage: []
|
|
192627
|
+
},
|
|
192628
|
+
stateManager,
|
|
192629
|
+
dataDir: `.specific/keys/${instanceKey}/data`,
|
|
192630
|
+
portAllocator: new PortAllocator(),
|
|
192631
|
+
callbacks: {
|
|
192632
|
+
log: (msg) => console.error(msg)
|
|
192633
|
+
},
|
|
192634
|
+
startElectric: false
|
|
192635
|
+
});
|
|
192636
|
+
startedResources = result.startedResources;
|
|
192637
|
+
const resource = result.resources.get(targetDb);
|
|
192638
|
+
if (!resource) {
|
|
192639
|
+
console.error(`Error: Failed to start database "${targetDb}"`);
|
|
192640
|
+
await cleanup();
|
|
192641
|
+
process.exit(1);
|
|
192642
|
+
}
|
|
192643
|
+
dbUrl = resource.url;
|
|
192644
|
+
} catch (err) {
|
|
192645
|
+
console.error(
|
|
192646
|
+
`Error: Failed to start resources: ${err instanceof Error ? err.message : String(err)}`
|
|
192647
|
+
);
|
|
192648
|
+
await cleanup();
|
|
192649
|
+
process.exit(1);
|
|
192650
|
+
}
|
|
192651
|
+
}
|
|
192652
|
+
}
|
|
192653
|
+
const isMigrationSubcommand = MIGRATION_SUBCOMMANDS.includes(action);
|
|
192654
|
+
const reshapeArgs = isMigrationSubcommand ? ["migration", action] : [action];
|
|
192655
|
+
const fullMigrationsPath = path24.join(process.cwd(), migrationsDir);
|
|
192656
|
+
if (action === "check" || action === "start") {
|
|
192657
|
+
if (fs26.existsSync(fullMigrationsPath)) {
|
|
192658
|
+
reshapeArgs.push("--dirs", fullMigrationsPath);
|
|
192659
|
+
} else if (action === "check") {
|
|
192660
|
+
console.error(`Error: Migrations directory not found: ${fullMigrationsPath}`);
|
|
192661
|
+
await cleanup();
|
|
192662
|
+
process.exit(1);
|
|
192663
|
+
}
|
|
192664
|
+
}
|
|
192665
|
+
if (action === "status" && dbUrl) {
|
|
192666
|
+
reshapeArgs.push("--url", dbUrl);
|
|
192667
|
+
}
|
|
192668
|
+
const binary = await ensureBinary(reshapeBinary);
|
|
192669
|
+
const reshapePath = binary.executables["reshape"];
|
|
192670
|
+
let child = null;
|
|
192671
|
+
const handleSignal = async (signal) => {
|
|
192672
|
+
if (child && !child.killed) {
|
|
192673
|
+
child.kill(signal);
|
|
192674
|
+
}
|
|
192675
|
+
await cleanup();
|
|
192676
|
+
};
|
|
192677
|
+
process.on("SIGINT", () => handleSignal("SIGINT"));
|
|
192678
|
+
process.on("SIGTERM", () => handleSignal("SIGTERM"));
|
|
192679
|
+
child = spawn8(reshapePath, reshapeArgs, {
|
|
192680
|
+
cwd: process.cwd(),
|
|
192681
|
+
env: {
|
|
192682
|
+
...process.env,
|
|
192683
|
+
// For migration subcommands, pass DB_URL via env
|
|
192684
|
+
...isMigrationSubcommand && dbUrl ? { DB_URL: dbUrl } : {}
|
|
192685
|
+
},
|
|
192686
|
+
stdio: "inherit"
|
|
192687
|
+
});
|
|
192688
|
+
child.on("exit", async (code, signal) => {
|
|
192689
|
+
await cleanup();
|
|
192690
|
+
if (signal) {
|
|
192691
|
+
process.kill(process.pid, signal);
|
|
192692
|
+
} else {
|
|
192693
|
+
process.exit(code ?? 0);
|
|
192694
|
+
}
|
|
192695
|
+
});
|
|
192696
|
+
child.on("error", async (err) => {
|
|
192697
|
+
console.error(`Error: Failed to start reshape: ${err.message}`);
|
|
192698
|
+
await cleanup();
|
|
192699
|
+
process.exit(1);
|
|
192700
|
+
});
|
|
192701
|
+
}
|
|
192702
|
+
|
|
191935
192703
|
// src/commands/clean.tsx
|
|
191936
192704
|
import React8, { useState as useState7, useEffect as useEffect5 } from "react";
|
|
191937
192705
|
import { render as render6, Text as Text8, Box as Box8 } from "ink";
|
|
191938
192706
|
import Spinner6 from "ink-spinner";
|
|
191939
|
-
import * as
|
|
191940
|
-
import * as
|
|
192707
|
+
import * as fs27 from "fs";
|
|
192708
|
+
import * as path25 from "path";
|
|
191941
192709
|
function CleanUI({ instanceKey }) {
|
|
191942
192710
|
const [state, setState] = useState7({ status: "checking" });
|
|
191943
192711
|
useEffect5(() => {
|
|
191944
192712
|
async function clean() {
|
|
191945
192713
|
const projectRoot = process.cwd();
|
|
191946
|
-
const specificDir =
|
|
191947
|
-
if (!
|
|
192714
|
+
const specificDir = path25.join(projectRoot, ".specific");
|
|
192715
|
+
if (!fs27.existsSync(specificDir)) {
|
|
191948
192716
|
setState({ status: "nothing" });
|
|
191949
192717
|
return;
|
|
191950
192718
|
}
|
|
191951
192719
|
if (instanceKey) {
|
|
191952
|
-
const keyDir =
|
|
191953
|
-
if (!
|
|
192720
|
+
const keyDir = path25.join(specificDir, "keys", instanceKey);
|
|
192721
|
+
if (!fs27.existsSync(keyDir)) {
|
|
191954
192722
|
setState({ status: "nothing" });
|
|
191955
192723
|
return;
|
|
191956
192724
|
}
|
|
@@ -191966,7 +192734,7 @@ function CleanUI({ instanceKey }) {
|
|
|
191966
192734
|
await stateManager.cleanStaleState();
|
|
191967
192735
|
setState({ status: "cleaning" });
|
|
191968
192736
|
try {
|
|
191969
|
-
|
|
192737
|
+
fs27.rmSync(keyDir, { recursive: true, force: true });
|
|
191970
192738
|
setState({ status: "success" });
|
|
191971
192739
|
} catch (err) {
|
|
191972
192740
|
setState({
|
|
@@ -191975,10 +192743,10 @@ function CleanUI({ instanceKey }) {
|
|
|
191975
192743
|
});
|
|
191976
192744
|
}
|
|
191977
192745
|
} else {
|
|
191978
|
-
const keysDir =
|
|
191979
|
-
if (
|
|
191980
|
-
const keys =
|
|
191981
|
-
(f) =>
|
|
192746
|
+
const keysDir = path25.join(specificDir, "keys");
|
|
192747
|
+
if (fs27.existsSync(keysDir)) {
|
|
192748
|
+
const keys = fs27.readdirSync(keysDir).filter(
|
|
192749
|
+
(f) => fs27.statSync(path25.join(keysDir, f)).isDirectory()
|
|
191982
192750
|
);
|
|
191983
192751
|
for (const key of keys) {
|
|
191984
192752
|
const stateManager2 = new InstanceStateManager(projectRoot, key);
|
|
@@ -192003,7 +192771,7 @@ function CleanUI({ instanceKey }) {
|
|
|
192003
192771
|
}
|
|
192004
192772
|
setState({ status: "cleaning" });
|
|
192005
192773
|
try {
|
|
192006
|
-
|
|
192774
|
+
fs27.rmSync(specificDir, { recursive: true, force: true });
|
|
192007
192775
|
setState({ status: "success" });
|
|
192008
192776
|
} catch (err) {
|
|
192009
192777
|
setState({
|
|
@@ -192086,11 +192854,87 @@ function logoutCommand() {
|
|
|
192086
192854
|
render7(/* @__PURE__ */ React9.createElement(LogoutUI, null));
|
|
192087
192855
|
}
|
|
192088
192856
|
|
|
192857
|
+
// src/commands/beta.tsx
|
|
192858
|
+
import React10, { useState as useState9, useEffect as useEffect7 } from "react";
|
|
192859
|
+
import { render as render8, Text as Text10, Box as Box9, useInput as useInput6, useApp as useApp5 } from "ink";
|
|
192860
|
+
function BetaToggleUI() {
|
|
192861
|
+
const { exit } = useApp5();
|
|
192862
|
+
const [enabled, setEnabled] = useState9(() => {
|
|
192863
|
+
const current = loadEnabledBetas();
|
|
192864
|
+
const state = {};
|
|
192865
|
+
for (const beta of BETA_REGISTRY) {
|
|
192866
|
+
state[beta.name] = current.includes(beta.name);
|
|
192867
|
+
}
|
|
192868
|
+
return state;
|
|
192869
|
+
});
|
|
192870
|
+
const [focusedIndex, setFocusedIndex] = useState9(0);
|
|
192871
|
+
const [saved, setSaved] = useState9(false);
|
|
192872
|
+
const isSubmitFocused = focusedIndex === BETA_REGISTRY.length;
|
|
192873
|
+
useInput6((input, key) => {
|
|
192874
|
+
if (saved) return;
|
|
192875
|
+
if (key.upArrow || input === "k") {
|
|
192876
|
+
setFocusedIndex((prev) => Math.max(0, prev - 1));
|
|
192877
|
+
} else if (key.downArrow || input === "j") {
|
|
192878
|
+
setFocusedIndex((prev) => Math.min(BETA_REGISTRY.length, prev + 1));
|
|
192879
|
+
} else if (input === " " && !isSubmitFocused) {
|
|
192880
|
+
const beta = BETA_REGISTRY[focusedIndex];
|
|
192881
|
+
setEnabled((prev) => ({ ...prev, [beta.name]: !prev[beta.name] }));
|
|
192882
|
+
} else if ((key.return || input === " ") && isSubmitFocused) {
|
|
192883
|
+
for (const beta of BETA_REGISTRY) {
|
|
192884
|
+
if (enabled[beta.name]) {
|
|
192885
|
+
enableBeta(beta.name);
|
|
192886
|
+
} else {
|
|
192887
|
+
disableBeta(beta.name);
|
|
192888
|
+
}
|
|
192889
|
+
}
|
|
192890
|
+
trackEvent("beta_toggled", {
|
|
192891
|
+
enabled: Object.keys(enabled).filter((k) => enabled[k])
|
|
192892
|
+
});
|
|
192893
|
+
setSaved(true);
|
|
192894
|
+
}
|
|
192895
|
+
});
|
|
192896
|
+
useEffect7(() => {
|
|
192897
|
+
if (saved) {
|
|
192898
|
+
const timer = setTimeout(() => exit(), 100);
|
|
192899
|
+
return () => clearTimeout(timer);
|
|
192900
|
+
}
|
|
192901
|
+
}, [saved, exit]);
|
|
192902
|
+
if (saved) {
|
|
192903
|
+
const enabledNames = Object.keys(enabled).filter((k) => enabled[k]);
|
|
192904
|
+
return /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text10, null, /* @__PURE__ */ React10.createElement(Text10, { color: "green" }, "\u2713"), " Beta features updated"), enabledNames.length > 0 ? /* @__PURE__ */ React10.createElement(Text10, { dimColor: true }, " Enabled: ", enabledNames.join(", ")) : /* @__PURE__ */ React10.createElement(Text10, { dimColor: true }, " No betas enabled"));
|
|
192905
|
+
}
|
|
192906
|
+
return /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text10, null, /* @__PURE__ */ React10.createElement(Text10, { dimColor: true }, "\u25CF"), " Configure beta features", " ", /* @__PURE__ */ React10.createElement(Text10, { dimColor: true }, "(space to select)")), /* @__PURE__ */ React10.createElement(Text10, null, " "), BETA_REGISTRY.map((beta, index) => {
|
|
192907
|
+
const isFocused = focusedIndex === index;
|
|
192908
|
+
const isChecked = enabled[beta.name];
|
|
192909
|
+
return /* @__PURE__ */ React10.createElement(Box9, { key: beta.name }, /* @__PURE__ */ React10.createElement(Text10, { ...isFocused && { color: "cyan" } }, isFocused ? "\u276F " : " ", isChecked ? "[\u2713]" : "[ ]", " ", beta.name, /* @__PURE__ */ React10.createElement(Text10, { dimColor: true }, " - ", beta.description)));
|
|
192910
|
+
}), /* @__PURE__ */ React10.createElement(Text10, null, " "), /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Text10, { ...isSubmitFocused && { color: "cyan" } }, isSubmitFocused ? "\u276F " : " ", /* @__PURE__ */ React10.createElement(
|
|
192911
|
+
Text10,
|
|
192912
|
+
{
|
|
192913
|
+
inverse: isSubmitFocused,
|
|
192914
|
+
...!isSubmitFocused && { backgroundColor: "gray" }
|
|
192915
|
+
},
|
|
192916
|
+
" ",
|
|
192917
|
+
"Save",
|
|
192918
|
+
" "
|
|
192919
|
+
))));
|
|
192920
|
+
}
|
|
192921
|
+
function betaCommand() {
|
|
192922
|
+
if (BETA_REGISTRY.length === 0) {
|
|
192923
|
+
console.log("No beta features available.");
|
|
192924
|
+
return;
|
|
192925
|
+
}
|
|
192926
|
+
if (!isInteractive()) {
|
|
192927
|
+
console.error("Error: specific beta requires an interactive terminal.");
|
|
192928
|
+
process.exit(1);
|
|
192929
|
+
}
|
|
192930
|
+
render8(/* @__PURE__ */ React10.createElement(BetaToggleUI, null));
|
|
192931
|
+
}
|
|
192932
|
+
|
|
192089
192933
|
// src/cli.tsx
|
|
192090
192934
|
var program = new Command();
|
|
192091
192935
|
var env = "production";
|
|
192092
192936
|
var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
|
|
192093
|
-
program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.
|
|
192937
|
+
program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.50").enablePositionalOptions();
|
|
192094
192938
|
program.command("init").description("Initialize project for use with a coding agent").option("--agent <name...>", "Agents to configure (cursor, claude, codex, other)").action((options2) => initCommand(options2));
|
|
192095
192939
|
program.command("docs [topic]").description("Fetch LLM-optimized documentation").action(docsCommand);
|
|
192096
192940
|
program.command("check").description("Validate specific.hcl configuration").action(checkCommand);
|
|
@@ -192111,9 +192955,14 @@ program.command("psql [database] [args...]").description("Connect to a Postgres
|
|
|
192111
192955
|
const key = options2.key ?? getDefaultKey();
|
|
192112
192956
|
psqlCommand(database, key, filteredArgs);
|
|
192113
192957
|
});
|
|
192958
|
+
program.command("reshape <action> [database]").description("Run Reshape migrations (start|complete|status|abort)").option("-k, --key <key>", "Dev environment namespace (auto-detected from git worktree if not specified)").action((action, database, options2) => {
|
|
192959
|
+
const key = options2.key ?? getDefaultKey();
|
|
192960
|
+
reshapeCommand(action, database, key);
|
|
192961
|
+
});
|
|
192114
192962
|
program.command("clean").description("Remove .specific directory for a clean slate").option("-k, --key <key>", "Clean only the specified dev environment key").action((options2) => {
|
|
192115
192963
|
cleanCommand(options2.key);
|
|
192116
192964
|
});
|
|
192965
|
+
program.command("beta").description("Manage beta feature flags").action(betaCommand);
|
|
192117
192966
|
program.command("login").description("Log in to Specific").action(loginCommand);
|
|
192118
192967
|
program.command("logout").description("Log out of Specific").action(logoutCommand);
|
|
192119
192968
|
var commandName = process.argv[2] || "help";
|