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