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