@specific.dev/cli 0.1.80 → 0.1.82
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.!KGRlZmF1bHQp.__PAGE__.txt +2 -2
- package/dist/admin/__next.!KGRlZmF1bHQp.txt +5 -5
- package/dist/admin/__next._full.txt +8 -8
- package/dist/admin/__next._head.txt +1 -1
- package/dist/admin/__next._index.txt +3 -3
- package/dist/admin/__next._tree.txt +1 -1
- package/dist/admin/_next/static/chunks/e7d77bc38df67cab.js +1 -0
- package/dist/admin/_not-found/__next._full.txt +3 -3
- package/dist/admin/_not-found/__next._head.txt +1 -1
- package/dist/admin/_not-found/__next._index.txt +3 -3
- 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 +3 -3
- package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.__PAGE__.txt +2 -2
- package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.txt +1 -1
- package/dist/admin/databases/__next.!KGRlZmF1bHQp.txt +5 -5
- package/dist/admin/databases/__next._full.txt +8 -8
- package/dist/admin/databases/__next._head.txt +1 -1
- package/dist/admin/databases/__next._index.txt +3 -3
- package/dist/admin/databases/__next._tree.txt +1 -1
- package/dist/admin/databases/index.html +1 -1
- package/dist/admin/databases/index.txt +8 -8
- package/dist/admin/fullscreen/__next._full.txt +4 -4
- package/dist/admin/fullscreen/__next._head.txt +1 -1
- package/dist/admin/fullscreen/__next._index.txt +3 -3
- package/dist/admin/fullscreen/__next._tree.txt +1 -1
- package/dist/admin/fullscreen/__next.fullscreen.__PAGE__.txt +2 -2
- package/dist/admin/fullscreen/__next.fullscreen.txt +1 -1
- package/dist/admin/fullscreen/databases/__next._full.txt +4 -4
- package/dist/admin/fullscreen/databases/__next._head.txt +1 -1
- package/dist/admin/fullscreen/databases/__next._index.txt +3 -3
- package/dist/admin/fullscreen/databases/__next._tree.txt +1 -1
- package/dist/admin/fullscreen/databases/__next.fullscreen.databases.__PAGE__.txt +2 -2
- package/dist/admin/fullscreen/databases/__next.fullscreen.databases.txt +1 -1
- package/dist/admin/fullscreen/databases/__next.fullscreen.txt +1 -1
- package/dist/admin/fullscreen/databases/index.html +1 -1
- package/dist/admin/fullscreen/databases/index.txt +4 -4
- package/dist/admin/fullscreen/index.html +1 -1
- package/dist/admin/fullscreen/index.txt +4 -4
- package/dist/admin/index.html +1 -1
- package/dist/admin/index.txt +8 -8
- package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.__PAGE__.txt +2 -2
- package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.txt +1 -1
- package/dist/admin/mail/__next.!KGRlZmF1bHQp.txt +5 -5
- package/dist/admin/mail/__next._full.txt +8 -8
- package/dist/admin/mail/__next._head.txt +1 -1
- package/dist/admin/mail/__next._index.txt +3 -3
- package/dist/admin/mail/__next._tree.txt +1 -1
- package/dist/admin/mail/index.html +1 -1
- package/dist/admin/mail/index.txt +8 -8
- package/dist/admin/workflows/__next.!KGRlZmF1bHQp.txt +5 -5
- package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.__PAGE__.txt +2 -2
- package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.txt +1 -1
- package/dist/admin/workflows/__next._full.txt +8 -8
- package/dist/admin/workflows/__next._head.txt +1 -1
- package/dist/admin/workflows/__next._index.txt +3 -3
- package/dist/admin/workflows/__next._tree.txt +1 -1
- package/dist/admin/workflows/index.html +1 -1
- package/dist/admin/workflows/index.txt +8 -8
- package/dist/cli.js +543 -332
- package/dist/docs/postgres.md +6 -0
- package/dist/postinstall.js +1 -1
- package/package.json +1 -1
- package/dist/admin/_next/static/chunks/41f3e5697670a8cc.js +0 -1
- /package/dist/admin/_next/static/{6PTV4P6Gn4KWCwgzL4Qmj → hBmPSh2M5utps8oJiVAZU}/_buildManifest.js +0 -0
- /package/dist/admin/_next/static/{6PTV4P6Gn4KWCwgzL4Qmj → hBmPSh2M5utps8oJiVAZU}/_clientMiddlewareManifest.json +0 -0
- /package/dist/admin/_next/static/{6PTV4P6Gn4KWCwgzL4Qmj → hBmPSh2M5utps8oJiVAZU}/_ssgManifest.js +0 -0
package/dist/cli.js
CHANGED
|
@@ -39,10 +39,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
39
39
|
));
|
|
40
40
|
|
|
41
41
|
// node_modules/.pnpm/is-docker@3.0.0/node_modules/is-docker/index.js
|
|
42
|
-
import
|
|
42
|
+
import fs6 from "node:fs";
|
|
43
43
|
function hasDockerEnv() {
|
|
44
44
|
try {
|
|
45
|
-
|
|
45
|
+
fs6.statSync("/.dockerenv");
|
|
46
46
|
return true;
|
|
47
47
|
} catch {
|
|
48
48
|
return false;
|
|
@@ -50,7 +50,7 @@ function hasDockerEnv() {
|
|
|
50
50
|
}
|
|
51
51
|
function hasDockerCGroup() {
|
|
52
52
|
try {
|
|
53
|
-
return
|
|
53
|
+
return fs6.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
|
|
54
54
|
} catch {
|
|
55
55
|
return false;
|
|
56
56
|
}
|
|
@@ -68,7 +68,7 @@ var init_is_docker = __esm({
|
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
// node_modules/.pnpm/is-inside-container@1.0.0/node_modules/is-inside-container/index.js
|
|
71
|
-
import
|
|
71
|
+
import fs7 from "node:fs";
|
|
72
72
|
function isInsideContainer() {
|
|
73
73
|
if (cachedResult === void 0) {
|
|
74
74
|
cachedResult = hasContainerEnv() || isDocker();
|
|
@@ -81,7 +81,7 @@ var init_is_inside_container = __esm({
|
|
|
81
81
|
init_is_docker();
|
|
82
82
|
hasContainerEnv = () => {
|
|
83
83
|
try {
|
|
84
|
-
|
|
84
|
+
fs7.statSync("/run/.containerenv");
|
|
85
85
|
return true;
|
|
86
86
|
} catch {
|
|
87
87
|
return false;
|
|
@@ -92,8 +92,8 @@ var init_is_inside_container = __esm({
|
|
|
92
92
|
|
|
93
93
|
// node_modules/.pnpm/is-wsl@3.1.0/node_modules/is-wsl/index.js
|
|
94
94
|
import process2 from "node:process";
|
|
95
|
-
import
|
|
96
|
-
import
|
|
95
|
+
import os5 from "node:os";
|
|
96
|
+
import fs8 from "node:fs";
|
|
97
97
|
var isWsl, is_wsl_default;
|
|
98
98
|
var init_is_wsl = __esm({
|
|
99
99
|
"node_modules/.pnpm/is-wsl@3.1.0/node_modules/is-wsl/index.js"() {
|
|
@@ -102,14 +102,14 @@ var init_is_wsl = __esm({
|
|
|
102
102
|
if (process2.platform !== "linux") {
|
|
103
103
|
return false;
|
|
104
104
|
}
|
|
105
|
-
if (
|
|
105
|
+
if (os5.release().toLowerCase().includes("microsoft")) {
|
|
106
106
|
if (isInsideContainer()) {
|
|
107
107
|
return false;
|
|
108
108
|
}
|
|
109
109
|
return true;
|
|
110
110
|
}
|
|
111
111
|
try {
|
|
112
|
-
return
|
|
112
|
+
return fs8.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft") ? !isInsideContainer() : false;
|
|
113
113
|
} catch {
|
|
114
114
|
return false;
|
|
115
115
|
}
|
|
@@ -179,7 +179,7 @@ var init_utilities = __esm({
|
|
|
179
179
|
// node_modules/.pnpm/wsl-utils@0.3.1/node_modules/wsl-utils/index.js
|
|
180
180
|
import { promisify as promisify2 } from "node:util";
|
|
181
181
|
import childProcess2 from "node:child_process";
|
|
182
|
-
import
|
|
182
|
+
import fs9, { constants as fsConstants } from "node:fs/promises";
|
|
183
183
|
var execFile2, wslDrivesMountPoint, powerShellPathFromWsl, powerShellPath2, canAccessPowerShellPromise, canAccessPowerShell, wslDefaultBrowser, convertWslPathToWindows;
|
|
184
184
|
var init_wsl_utils = __esm({
|
|
185
185
|
"node_modules/.pnpm/wsl-utils@0.3.1/node_modules/wsl-utils/index.js"() {
|
|
@@ -198,14 +198,14 @@ var init_wsl_utils = __esm({
|
|
|
198
198
|
const configFilePath = "/etc/wsl.conf";
|
|
199
199
|
let isConfigFileExists = false;
|
|
200
200
|
try {
|
|
201
|
-
await
|
|
201
|
+
await fs9.access(configFilePath, fsConstants.F_OK);
|
|
202
202
|
isConfigFileExists = true;
|
|
203
203
|
} catch {
|
|
204
204
|
}
|
|
205
205
|
if (!isConfigFileExists) {
|
|
206
206
|
return defaultMountPoint;
|
|
207
207
|
}
|
|
208
|
-
const configContent = await
|
|
208
|
+
const configContent = await fs9.readFile(configFilePath, { encoding: "utf8" });
|
|
209
209
|
const parsedMountPoint = parseMountPointFromConfig(configContent);
|
|
210
210
|
if (parsedMountPoint === void 0) {
|
|
211
211
|
return defaultMountPoint;
|
|
@@ -224,7 +224,7 @@ var init_wsl_utils = __esm({
|
|
|
224
224
|
canAccessPowerShellPromise ??= (async () => {
|
|
225
225
|
try {
|
|
226
226
|
const psPath = await powerShellPath2();
|
|
227
|
-
await
|
|
227
|
+
await fs9.access(psPath, fsConstants.X_OK);
|
|
228
228
|
return true;
|
|
229
229
|
} catch {
|
|
230
230
|
return false;
|
|
@@ -435,7 +435,7 @@ import process8 from "node:process";
|
|
|
435
435
|
import path5 from "node:path";
|
|
436
436
|
import { fileURLToPath } from "node:url";
|
|
437
437
|
import childProcess3 from "node:child_process";
|
|
438
|
-
import
|
|
438
|
+
import fs10, { constants as fsConstants2 } from "node:fs/promises";
|
|
439
439
|
function detectArchBinary(binary) {
|
|
440
440
|
if (typeof binary === "string" || Array.isArray(binary)) {
|
|
441
441
|
return binary;
|
|
@@ -446,16 +446,16 @@ function detectArchBinary(binary) {
|
|
|
446
446
|
}
|
|
447
447
|
return archBinary;
|
|
448
448
|
}
|
|
449
|
-
function detectPlatformBinary({ [
|
|
449
|
+
function detectPlatformBinary({ [platform4]: platformBinary }, { wsl } = {}) {
|
|
450
450
|
if (wsl && is_wsl_default) {
|
|
451
451
|
return detectArchBinary(wsl);
|
|
452
452
|
}
|
|
453
453
|
if (!platformBinary) {
|
|
454
|
-
throw new Error(`${
|
|
454
|
+
throw new Error(`${platform4} is not supported`);
|
|
455
455
|
}
|
|
456
456
|
return detectArchBinary(platformBinary);
|
|
457
457
|
}
|
|
458
|
-
var fallbackAttemptSymbol, __dirname, localXdgOpenPath,
|
|
458
|
+
var fallbackAttemptSymbol, __dirname, localXdgOpenPath, platform4, arch, tryEachApp, baseOpen, open, openApp, apps, open_default;
|
|
459
459
|
var init_open = __esm({
|
|
460
460
|
"node_modules/.pnpm/open@11.0.0/node_modules/open/index.js"() {
|
|
461
461
|
init_wsl_utils();
|
|
@@ -467,7 +467,7 @@ var init_open = __esm({
|
|
|
467
467
|
fallbackAttemptSymbol = Symbol("fallbackAttempt");
|
|
468
468
|
__dirname = import.meta.url ? path5.dirname(fileURLToPath(import.meta.url)) : "";
|
|
469
469
|
localXdgOpenPath = path5.join(__dirname, "xdg-open");
|
|
470
|
-
({ platform:
|
|
470
|
+
({ platform: platform4, arch } = process8);
|
|
471
471
|
tryEachApp = async (apps2, opener) => {
|
|
472
472
|
if (apps2.length === 0) {
|
|
473
473
|
return;
|
|
@@ -564,7 +564,7 @@ var init_open = __esm({
|
|
|
564
564
|
if (is_wsl_default && !isInsideContainer() && !is_in_ssh_default && !app) {
|
|
565
565
|
shouldUseWindowsInWsl = await canAccessPowerShell();
|
|
566
566
|
}
|
|
567
|
-
if (
|
|
567
|
+
if (platform4 === "darwin") {
|
|
568
568
|
command = "open";
|
|
569
569
|
if (options2.wait) {
|
|
570
570
|
cliArguments.push("--wait-apps");
|
|
@@ -578,7 +578,7 @@ var init_open = __esm({
|
|
|
578
578
|
if (app) {
|
|
579
579
|
cliArguments.push("-a", app);
|
|
580
580
|
}
|
|
581
|
-
} else if (
|
|
581
|
+
} else if (platform4 === "win32" || shouldUseWindowsInWsl) {
|
|
582
582
|
command = await powerShellPath2();
|
|
583
583
|
cliArguments.push(...executePowerShell.argumentsPrefix);
|
|
584
584
|
if (!is_wsl_default) {
|
|
@@ -614,11 +614,11 @@ var init_open = __esm({
|
|
|
614
614
|
const isBundled = !__dirname || __dirname === "/";
|
|
615
615
|
let exeLocalXdgOpen = false;
|
|
616
616
|
try {
|
|
617
|
-
await
|
|
617
|
+
await fs10.access(localXdgOpenPath, fsConstants2.X_OK);
|
|
618
618
|
exeLocalXdgOpen = true;
|
|
619
619
|
} catch {
|
|
620
620
|
}
|
|
621
|
-
const useSystemXdgOpen = process8.versions.electron ?? (
|
|
621
|
+
const useSystemXdgOpen = process8.versions.electron ?? (platform4 === "android" || isBundled || !exeLocalXdgOpen);
|
|
622
622
|
command = useSystemXdgOpen ? "xdg-open" : localXdgOpenPath;
|
|
623
623
|
}
|
|
624
624
|
if (appArguments.length > 0) {
|
|
@@ -629,7 +629,7 @@ var init_open = __esm({
|
|
|
629
629
|
childProcessOptions.detached = true;
|
|
630
630
|
}
|
|
631
631
|
}
|
|
632
|
-
if (
|
|
632
|
+
if (platform4 === "darwin" && appArguments.length > 0) {
|
|
633
633
|
cliArguments.push("--args", ...appArguments);
|
|
634
634
|
}
|
|
635
635
|
if (options2.target) {
|
|
@@ -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 fs33 = $global.require("fs");
|
|
758
|
+
"object" == typeof fs33 && null !== fs33 && 0 !== Object.keys(fs33).length && ($global.fs = fs33);
|
|
759
759
|
} catch (e) {
|
|
760
760
|
}
|
|
761
761
|
if (!$global.fs) {
|
|
@@ -183430,11 +183430,14 @@ import { Command } from "commander";
|
|
|
183430
183430
|
import React2, { useState, useEffect } from "react";
|
|
183431
183431
|
import { render as render2, Text as Text2, Box as Box2, useInput, useApp } from "ink";
|
|
183432
183432
|
import "ink-spinner";
|
|
183433
|
-
import * as
|
|
183433
|
+
import * as fs12 from "fs";
|
|
183434
183434
|
import * as path7 from "path";
|
|
183435
|
+
import * as os8 from "os";
|
|
183435
183436
|
|
|
183436
183437
|
// src/lib/dev/system-setup.ts
|
|
183437
183438
|
import { execSync as execSync2 } from "child_process";
|
|
183439
|
+
import * as fs4 from "fs";
|
|
183440
|
+
import * as os4 from "os";
|
|
183438
183441
|
import * as path3 from "path";
|
|
183439
183442
|
|
|
183440
183443
|
// src/lib/dev/local-ca.ts
|
|
@@ -183470,17 +183473,17 @@ function caInstalledInTrustStore() {
|
|
|
183470
183473
|
if (!caFilesExist()) {
|
|
183471
183474
|
return false;
|
|
183472
183475
|
}
|
|
183473
|
-
const
|
|
183476
|
+
const platform8 = os.platform();
|
|
183474
183477
|
const certPath = path.join(getCADir(), "ca.crt");
|
|
183475
183478
|
const diskCert = fs.readFileSync(certPath, "utf-8").replace(/\r\n/g, "\n").trim();
|
|
183476
183479
|
try {
|
|
183477
|
-
if (
|
|
183480
|
+
if (platform8 === "darwin") {
|
|
183478
183481
|
const keychainCert = execSync(
|
|
183479
|
-
|
|
183482
|
+
`security find-certificate -c "Specific Local Development CA" -p "${getLoginKeychainPath()}"`,
|
|
183480
183483
|
{ encoding: "utf-8" }
|
|
183481
183484
|
).replace(/\r\n/g, "\n").trim();
|
|
183482
183485
|
return keychainCert === diskCert;
|
|
183483
|
-
} else if (
|
|
183486
|
+
} else if (platform8 === "linux") {
|
|
183484
183487
|
const trustPaths = [
|
|
183485
183488
|
"/usr/local/share/ca-certificates/specific-local-ca.crt",
|
|
183486
183489
|
// Debian/Ubuntu
|
|
@@ -183591,16 +183594,23 @@ function removeCA() {
|
|
|
183591
183594
|
fs.unlinkSync(certPath);
|
|
183592
183595
|
}
|
|
183593
183596
|
}
|
|
183597
|
+
function getLoginKeychainPath() {
|
|
183598
|
+
return path.join(os.homedir(), "Library", "Keychains", "login.keychain-db");
|
|
183599
|
+
}
|
|
183600
|
+
function getCADeleteCommand() {
|
|
183601
|
+
return `security delete-certificate -c "Specific Local Development CA" "${getLoginKeychainPath()}"`;
|
|
183602
|
+
}
|
|
183603
|
+
function getCATrustCommand(certPath) {
|
|
183604
|
+
return `security add-trusted-cert -d -r trustRoot -k "${getLoginKeychainPath()}" "${certPath}"`;
|
|
183605
|
+
}
|
|
183594
183606
|
function getCAInstallCommands(certPath) {
|
|
183595
|
-
const
|
|
183596
|
-
if (
|
|
183607
|
+
const platform8 = os.platform();
|
|
183608
|
+
if (platform8 === "darwin") {
|
|
183597
183609
|
return [
|
|
183598
|
-
|
|
183599
|
-
|
|
183600
|
-
'security delete-certificate -c "Specific Local Development CA" /Library/Keychains/System.keychain 2>/dev/null || true',
|
|
183601
|
-
`security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "${certPath}"`
|
|
183610
|
+
`${getCADeleteCommand()} 2>/dev/null || true`,
|
|
183611
|
+
getCATrustCommand(certPath)
|
|
183602
183612
|
];
|
|
183603
|
-
} else if (
|
|
183613
|
+
} else if (platform8 === "linux") {
|
|
183604
183614
|
if (fs.existsSync("/usr/local/share/ca-certificates")) {
|
|
183605
183615
|
return [
|
|
183606
183616
|
`cp "${certPath}" /usr/local/share/ca-certificates/specific-local-ca.crt`,
|
|
@@ -183619,7 +183629,7 @@ function getCAInstallCommands(certPath) {
|
|
|
183619
183629
|
}
|
|
183620
183630
|
throw new Error("Could not detect Linux certificate trust mechanism");
|
|
183621
183631
|
}
|
|
183622
|
-
throw new Error(`Unsupported platform: ${
|
|
183632
|
+
throw new Error(`Unsupported platform: ${platform8}`);
|
|
183623
183633
|
}
|
|
183624
183634
|
function generateCertificate(domain, keys = []) {
|
|
183625
183635
|
const caDir = getCADir();
|
|
@@ -183665,23 +183675,23 @@ var RESOLVER_FILE_MACOS = "/etc/resolver/spcf.localhost";
|
|
|
183665
183675
|
var OLD_RESOLVER_FILE_MACOS = "/etc/resolver/local.spcf.app";
|
|
183666
183676
|
var RESOLVER_FILE_LINUX = "/etc/systemd/resolved.conf.d/specific-local.conf";
|
|
183667
183677
|
function resolverConfigExists() {
|
|
183668
|
-
const
|
|
183669
|
-
if (
|
|
183678
|
+
const platform8 = os2.platform();
|
|
183679
|
+
if (platform8 === "darwin") {
|
|
183670
183680
|
return fs2.existsSync(RESOLVER_FILE_MACOS);
|
|
183671
|
-
} else if (
|
|
183681
|
+
} else if (platform8 === "linux") {
|
|
183672
183682
|
return fs2.existsSync(RESOLVER_FILE_LINUX);
|
|
183673
183683
|
}
|
|
183674
183684
|
return false;
|
|
183675
183685
|
}
|
|
183676
183686
|
function getResolverInstallCommands(port) {
|
|
183677
|
-
const
|
|
183678
|
-
if (
|
|
183687
|
+
const platform8 = os2.platform();
|
|
183688
|
+
if (platform8 === "darwin") {
|
|
183679
183689
|
return [
|
|
183680
183690
|
"mkdir -p /etc/resolver",
|
|
183681
183691
|
`rm -f ${OLD_RESOLVER_FILE_MACOS}`,
|
|
183682
183692
|
`printf "nameserver 127.0.0.1\\nport ${port}\\n" > ${RESOLVER_FILE_MACOS}`
|
|
183683
183693
|
];
|
|
183684
|
-
} else if (
|
|
183694
|
+
} else if (platform8 === "linux") {
|
|
183685
183695
|
if (fs2.existsSync("/etc/systemd/resolved.conf.d") || fs2.existsSync("/etc/systemd")) {
|
|
183686
183696
|
return [
|
|
183687
183697
|
"mkdir -p /etc/systemd/resolved.conf.d",
|
|
@@ -183775,19 +183785,60 @@ async function startDnsServer(port = DNS_PORT) {
|
|
|
183775
183785
|
|
|
183776
183786
|
// src/lib/dev/system-setup.ts
|
|
183777
183787
|
function systemSetupNeeded() {
|
|
183778
|
-
return
|
|
183788
|
+
return dnsSetupNeeded() || tlsSetupNeeded();
|
|
183789
|
+
}
|
|
183790
|
+
function dnsSetupNeeded() {
|
|
183791
|
+
return !resolverConfigExists();
|
|
183792
|
+
}
|
|
183793
|
+
function tlsSetupNeeded() {
|
|
183794
|
+
return !caInstalledInTrustStore();
|
|
183795
|
+
}
|
|
183796
|
+
function ensureCAGenerated() {
|
|
183797
|
+
if (caFilesExist()) {
|
|
183798
|
+
return false;
|
|
183799
|
+
}
|
|
183800
|
+
const { key, cert } = generateRootCA();
|
|
183801
|
+
saveCA(key, cert);
|
|
183802
|
+
return true;
|
|
183803
|
+
}
|
|
183804
|
+
function performDnsSetup() {
|
|
183805
|
+
const commands = getResolverInstallCommands(DNS_PORT);
|
|
183806
|
+
if (commands.length === 0) return;
|
|
183807
|
+
if (os4.platform() === "darwin") {
|
|
183808
|
+
execPrivilegedMacOS(commands);
|
|
183809
|
+
} else {
|
|
183810
|
+
execSync2(`sudo sh -c '${commands.join(" && ")}'`, { stdio: "inherit" });
|
|
183811
|
+
}
|
|
183812
|
+
}
|
|
183813
|
+
function performTlsTrust() {
|
|
183814
|
+
const wasGenerated = ensureCAGenerated();
|
|
183815
|
+
const certPath = path3.join(getCADir(), "ca.crt");
|
|
183816
|
+
try {
|
|
183817
|
+
if (os4.platform() === "darwin") {
|
|
183818
|
+
execSync2(`${getCADeleteCommand()} 2>/dev/null || true`, {
|
|
183819
|
+
stdio: "inherit"
|
|
183820
|
+
});
|
|
183821
|
+
execSync2(getCATrustCommand(certPath), { stdio: "inherit" });
|
|
183822
|
+
} else {
|
|
183823
|
+
execSync2(
|
|
183824
|
+
`sudo sh -c '${getCAInstallCommands(certPath).join(" && ")}'`,
|
|
183825
|
+
{ stdio: "inherit" }
|
|
183826
|
+
);
|
|
183827
|
+
}
|
|
183828
|
+
} catch (err) {
|
|
183829
|
+
if (wasGenerated) {
|
|
183830
|
+
removeCA();
|
|
183831
|
+
}
|
|
183832
|
+
throw err;
|
|
183833
|
+
}
|
|
183779
183834
|
}
|
|
183780
183835
|
function performSystemSetup() {
|
|
183781
|
-
const
|
|
183836
|
+
const wasGenerated = ensureCAGenerated();
|
|
183782
183837
|
const needsInstallCA = !caInstalledInTrustStore();
|
|
183783
183838
|
const needsResolver = !resolverConfigExists();
|
|
183784
183839
|
if (!needsInstallCA && !needsResolver) {
|
|
183785
183840
|
return;
|
|
183786
183841
|
}
|
|
183787
|
-
if (needsGenerate) {
|
|
183788
|
-
const { key, cert } = generateRootCA();
|
|
183789
|
-
saveCA(key, cert);
|
|
183790
|
-
}
|
|
183791
183842
|
const certPath = path3.join(getCADir(), "ca.crt");
|
|
183792
183843
|
const commands = [];
|
|
183793
183844
|
if (needsInstallCA) {
|
|
@@ -183802,20 +183853,45 @@ function performSystemSetup() {
|
|
|
183802
183853
|
try {
|
|
183803
183854
|
execSync2(`sudo sh -c '${commands.join(" && ")}'`, { stdio: "inherit" });
|
|
183804
183855
|
} catch (err) {
|
|
183805
|
-
if (
|
|
183856
|
+
if (wasGenerated) {
|
|
183806
183857
|
removeCA();
|
|
183807
183858
|
}
|
|
183808
183859
|
throw err;
|
|
183809
183860
|
}
|
|
183810
183861
|
}
|
|
183862
|
+
function execPrivilegedMacOS(commands) {
|
|
183863
|
+
if (commands.length === 0) return;
|
|
183864
|
+
const scriptPath = path3.join(os4.tmpdir(), "specific-setup.sh");
|
|
183865
|
+
const appPath = path3.join(os4.tmpdir(), "Specific.app");
|
|
183866
|
+
try {
|
|
183867
|
+
fs4.writeFileSync(
|
|
183868
|
+
scriptPath,
|
|
183869
|
+
"#!/bin/sh\nset -e\n" + commands.join("\n") + "\n",
|
|
183870
|
+
{ mode: 448 }
|
|
183871
|
+
);
|
|
183872
|
+
execSync2(
|
|
183873
|
+
`osacompile -o '${appPath}' -e 'do shell script "sh ${scriptPath}" with administrator privileges'`
|
|
183874
|
+
);
|
|
183875
|
+
execSync2(`'${appPath}/Contents/MacOS/applet'`, { stdio: "pipe" });
|
|
183876
|
+
} finally {
|
|
183877
|
+
try {
|
|
183878
|
+
fs4.unlinkSync(scriptPath);
|
|
183879
|
+
} catch {
|
|
183880
|
+
}
|
|
183881
|
+
try {
|
|
183882
|
+
fs4.rmSync(appPath, { recursive: true });
|
|
183883
|
+
} catch {
|
|
183884
|
+
}
|
|
183885
|
+
}
|
|
183886
|
+
}
|
|
183811
183887
|
|
|
183812
183888
|
// src/lib/analytics/index.ts
|
|
183813
183889
|
import { PostHog } from "posthog-node";
|
|
183814
|
-
import * as
|
|
183890
|
+
import * as os7 from "os";
|
|
183815
183891
|
import * as crypto from "crypto";
|
|
183816
183892
|
|
|
183817
183893
|
// src/lib/project/config.ts
|
|
183818
|
-
import * as
|
|
183894
|
+
import * as fs5 from "fs";
|
|
183819
183895
|
import * as path4 from "path";
|
|
183820
183896
|
var PROJECT_ID_FILE = ".specific/project_id";
|
|
183821
183897
|
var ProjectNotLinkedError = class extends Error {
|
|
@@ -183832,10 +183908,10 @@ Run: specific deploy`
|
|
|
183832
183908
|
};
|
|
183833
183909
|
function readProjectId(projectDir = process.cwd()) {
|
|
183834
183910
|
const projectIdPath = path4.join(projectDir, PROJECT_ID_FILE);
|
|
183835
|
-
if (!
|
|
183911
|
+
if (!fs5.existsSync(projectIdPath)) {
|
|
183836
183912
|
throw new ProjectNotLinkedError();
|
|
183837
183913
|
}
|
|
183838
|
-
const projectId =
|
|
183914
|
+
const projectId = fs5.readFileSync(projectIdPath, "utf-8").trim();
|
|
183839
183915
|
if (!projectId) {
|
|
183840
183916
|
throw new Error(`${PROJECT_ID_FILE} is empty`);
|
|
183841
183917
|
}
|
|
@@ -183843,20 +183919,20 @@ function readProjectId(projectDir = process.cwd()) {
|
|
|
183843
183919
|
}
|
|
183844
183920
|
function hasProjectId(projectDir = process.cwd()) {
|
|
183845
183921
|
const projectIdPath = path4.join(projectDir, PROJECT_ID_FILE);
|
|
183846
|
-
return
|
|
183922
|
+
return fs5.existsSync(projectIdPath);
|
|
183847
183923
|
}
|
|
183848
183924
|
function writeProjectId(projectId, projectDir = process.cwd()) {
|
|
183849
183925
|
const specificDir = path4.join(projectDir, ".specific");
|
|
183850
|
-
if (!
|
|
183851
|
-
|
|
183926
|
+
if (!fs5.existsSync(specificDir)) {
|
|
183927
|
+
fs5.mkdirSync(specificDir, { recursive: true });
|
|
183852
183928
|
}
|
|
183853
|
-
|
|
183929
|
+
fs5.writeFileSync(path4.join(specificDir, "project_id"), projectId + "\n");
|
|
183854
183930
|
}
|
|
183855
183931
|
|
|
183856
183932
|
// src/lib/auth/credentials.ts
|
|
183857
|
-
import * as
|
|
183933
|
+
import * as fs11 from "fs";
|
|
183858
183934
|
import * as path6 from "path";
|
|
183859
|
-
import * as
|
|
183935
|
+
import * as os6 from "os";
|
|
183860
183936
|
|
|
183861
183937
|
// src/lib/auth/errors.ts
|
|
183862
183938
|
var RefreshTokenExpiredError = class extends Error {
|
|
@@ -184437,18 +184513,18 @@ var ApiClient = class {
|
|
|
184437
184513
|
|
|
184438
184514
|
// src/lib/auth/credentials.ts
|
|
184439
184515
|
function getUserCredentialsDir() {
|
|
184440
|
-
return path6.join(
|
|
184516
|
+
return path6.join(os6.homedir(), ".specific");
|
|
184441
184517
|
}
|
|
184442
184518
|
function getCredentialsPath() {
|
|
184443
184519
|
return path6.join(getUserCredentialsDir(), "credentials.json");
|
|
184444
184520
|
}
|
|
184445
184521
|
function readUserCredentials() {
|
|
184446
184522
|
const credentialsPath = getCredentialsPath();
|
|
184447
|
-
if (!
|
|
184523
|
+
if (!fs11.existsSync(credentialsPath)) {
|
|
184448
184524
|
return null;
|
|
184449
184525
|
}
|
|
184450
184526
|
try {
|
|
184451
|
-
const content =
|
|
184527
|
+
const content = fs11.readFileSync(credentialsPath, "utf-8");
|
|
184452
184528
|
return JSON.parse(content);
|
|
184453
184529
|
} catch {
|
|
184454
184530
|
return null;
|
|
@@ -184456,18 +184532,18 @@ function readUserCredentials() {
|
|
|
184456
184532
|
}
|
|
184457
184533
|
function writeUserCredentials(credentials) {
|
|
184458
184534
|
const dir = getUserCredentialsDir();
|
|
184459
|
-
if (!
|
|
184460
|
-
|
|
184535
|
+
if (!fs11.existsSync(dir)) {
|
|
184536
|
+
fs11.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
184461
184537
|
}
|
|
184462
184538
|
const credentialsPath = getCredentialsPath();
|
|
184463
|
-
|
|
184539
|
+
fs11.writeFileSync(credentialsPath, JSON.stringify(credentials, null, 2), {
|
|
184464
184540
|
mode: 384
|
|
184465
184541
|
});
|
|
184466
184542
|
}
|
|
184467
184543
|
function clearUserCredentials() {
|
|
184468
184544
|
const credentialsPath = getCredentialsPath();
|
|
184469
|
-
if (
|
|
184470
|
-
|
|
184545
|
+
if (fs11.existsSync(credentialsPath)) {
|
|
184546
|
+
fs11.unlinkSync(credentialsPath);
|
|
184471
184547
|
}
|
|
184472
184548
|
}
|
|
184473
184549
|
function isLoggedIn() {
|
|
@@ -184546,7 +184622,7 @@ function isEnabled() {
|
|
|
184546
184622
|
}
|
|
184547
184623
|
function getAnonymousId() {
|
|
184548
184624
|
if (anonymousId) return anonymousId;
|
|
184549
|
-
const machineId = `${
|
|
184625
|
+
const machineId = `${os7.hostname()}-${os7.userInfo().username}`;
|
|
184550
184626
|
anonymousId = crypto.createHash("sha256").update(machineId).digest("hex").slice(0, 16);
|
|
184551
184627
|
return anonymousId;
|
|
184552
184628
|
}
|
|
@@ -184593,7 +184669,7 @@ function trackEvent(event, properties) {
|
|
|
184593
184669
|
event,
|
|
184594
184670
|
properties: {
|
|
184595
184671
|
...properties,
|
|
184596
|
-
cli_version: "0.1.
|
|
184672
|
+
cli_version: "0.1.82",
|
|
184597
184673
|
platform: process.platform,
|
|
184598
184674
|
node_version: process.version,
|
|
184599
184675
|
project_id: getProjectId()
|
|
@@ -184630,57 +184706,57 @@ var options = [
|
|
|
184630
184706
|
];
|
|
184631
184707
|
function isGitProject() {
|
|
184632
184708
|
const gitPath = path7.join(process.cwd(), ".git");
|
|
184633
|
-
return
|
|
184709
|
+
return fs12.existsSync(gitPath);
|
|
184634
184710
|
}
|
|
184635
184711
|
function detectExistingAgents() {
|
|
184636
184712
|
const detected = {};
|
|
184637
184713
|
const cursorDir = path7.join(process.cwd(), ".cursor");
|
|
184638
|
-
if (
|
|
184714
|
+
if (fs12.existsSync(cursorDir)) {
|
|
184639
184715
|
detected["cursor"] = true;
|
|
184640
184716
|
}
|
|
184641
184717
|
const claudeDir = path7.join(process.cwd(), ".claude");
|
|
184642
184718
|
const claudeMd = path7.join(process.cwd(), "CLAUDE.md");
|
|
184643
|
-
if (
|
|
184719
|
+
if (fs12.existsSync(claudeDir) || fs12.existsSync(claudeMd)) {
|
|
184644
184720
|
detected["claude"] = true;
|
|
184645
184721
|
}
|
|
184646
184722
|
const agentsMd = path7.join(process.cwd(), "AGENTS.md");
|
|
184647
|
-
if (
|
|
184723
|
+
if (fs12.existsSync(agentsMd)) {
|
|
184648
184724
|
detected["codex"] = true;
|
|
184649
184725
|
}
|
|
184650
184726
|
return detected;
|
|
184651
184727
|
}
|
|
184652
184728
|
function appendOrCreateFile(filePath, content) {
|
|
184653
|
-
if (
|
|
184654
|
-
const existing =
|
|
184729
|
+
if (fs12.existsSync(filePath)) {
|
|
184730
|
+
const existing = fs12.readFileSync(filePath, "utf-8");
|
|
184655
184731
|
if (existing.includes("specific docs") || existing.includes("specific check")) {
|
|
184656
184732
|
return "unchanged";
|
|
184657
184733
|
}
|
|
184658
184734
|
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
184659
|
-
|
|
184735
|
+
fs12.writeFileSync(filePath, existing + separator + content + "\n");
|
|
184660
184736
|
return "modified";
|
|
184661
184737
|
} else {
|
|
184662
|
-
|
|
184738
|
+
fs12.writeFileSync(filePath, content + "\n");
|
|
184663
184739
|
return "created";
|
|
184664
184740
|
}
|
|
184665
184741
|
}
|
|
184666
184742
|
function addToGitignore() {
|
|
184667
184743
|
const gitignorePath = path7.join(process.cwd(), ".gitignore");
|
|
184668
184744
|
const entries = [".specific", "specific.local"];
|
|
184669
|
-
if (
|
|
184670
|
-
const existing =
|
|
184745
|
+
if (fs12.existsSync(gitignorePath)) {
|
|
184746
|
+
const existing = fs12.readFileSync(gitignorePath, "utf-8");
|
|
184671
184747
|
const lines = existing.split("\n").map((l) => l.trim());
|
|
184672
184748
|
const missingEntries = entries.filter((entry) => !lines.includes(entry));
|
|
184673
184749
|
if (missingEntries.length === 0) {
|
|
184674
184750
|
return "unchanged";
|
|
184675
184751
|
}
|
|
184676
184752
|
const separator = existing.endsWith("\n") ? "" : "\n";
|
|
184677
|
-
|
|
184753
|
+
fs12.writeFileSync(
|
|
184678
184754
|
gitignorePath,
|
|
184679
184755
|
existing + separator + missingEntries.join("\n") + "\n"
|
|
184680
184756
|
);
|
|
184681
184757
|
return "modified";
|
|
184682
184758
|
} else {
|
|
184683
|
-
|
|
184759
|
+
fs12.writeFileSync(gitignorePath, entries.join("\n") + "\n");
|
|
184684
184760
|
return "created";
|
|
184685
184761
|
}
|
|
184686
184762
|
}
|
|
@@ -184688,8 +184764,8 @@ function configureClaudeCodePermissions() {
|
|
|
184688
184764
|
const claudeDir = path7.join(process.cwd(), ".claude");
|
|
184689
184765
|
const settingsPath = path7.join(claudeDir, "settings.local.json");
|
|
184690
184766
|
const permissions = ["Bash(specific docs:*)", "Bash(specific check:*)"];
|
|
184691
|
-
if (
|
|
184692
|
-
const existing = JSON.parse(
|
|
184767
|
+
if (fs12.existsSync(settingsPath)) {
|
|
184768
|
+
const existing = JSON.parse(fs12.readFileSync(settingsPath, "utf-8"));
|
|
184693
184769
|
const allowList = existing?.permissions?.allow || [];
|
|
184694
184770
|
const missingPermissions = permissions.filter(
|
|
184695
184771
|
(p) => !allowList.includes(p)
|
|
@@ -184704,39 +184780,39 @@ function configureClaudeCodePermissions() {
|
|
|
184704
184780
|
existing.permissions.allow = [];
|
|
184705
184781
|
}
|
|
184706
184782
|
existing.permissions.allow.push(...missingPermissions);
|
|
184707
|
-
|
|
184783
|
+
fs12.writeFileSync(settingsPath, JSON.stringify(existing, null, 2) + "\n");
|
|
184708
184784
|
return "modified";
|
|
184709
184785
|
}
|
|
184710
|
-
if (!
|
|
184711
|
-
|
|
184786
|
+
if (!fs12.existsSync(claudeDir)) {
|
|
184787
|
+
fs12.mkdirSync(claudeDir);
|
|
184712
184788
|
}
|
|
184713
184789
|
const settings = {
|
|
184714
184790
|
permissions: {
|
|
184715
184791
|
allow: permissions
|
|
184716
184792
|
}
|
|
184717
184793
|
};
|
|
184718
|
-
|
|
184794
|
+
fs12.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
184719
184795
|
return "created";
|
|
184720
184796
|
}
|
|
184721
184797
|
function createCursorRule() {
|
|
184722
184798
|
const cursorDir = path7.join(process.cwd(), ".cursor");
|
|
184723
184799
|
const rulesDir = path7.join(cursorDir, "rules");
|
|
184724
184800
|
const mdcPath = path7.join(rulesDir, "specific.mdc");
|
|
184725
|
-
if (
|
|
184726
|
-
const existing =
|
|
184801
|
+
if (fs12.existsSync(mdcPath)) {
|
|
184802
|
+
const existing = fs12.readFileSync(mdcPath, "utf-8");
|
|
184727
184803
|
if (existing.includes("specific docs") || existing.includes("specific check")) {
|
|
184728
184804
|
return "unchanged";
|
|
184729
184805
|
}
|
|
184730
|
-
|
|
184806
|
+
fs12.writeFileSync(mdcPath, CURSOR_MDC_CONTENT);
|
|
184731
184807
|
return "modified";
|
|
184732
184808
|
}
|
|
184733
|
-
if (!
|
|
184734
|
-
|
|
184809
|
+
if (!fs12.existsSync(cursorDir)) {
|
|
184810
|
+
fs12.mkdirSync(cursorDir);
|
|
184735
184811
|
}
|
|
184736
|
-
if (!
|
|
184737
|
-
|
|
184812
|
+
if (!fs12.existsSync(rulesDir)) {
|
|
184813
|
+
fs12.mkdirSync(rulesDir);
|
|
184738
184814
|
}
|
|
184739
|
-
|
|
184815
|
+
fs12.writeFileSync(mdcPath, CURSOR_MDC_CONTENT);
|
|
184740
184816
|
return "created";
|
|
184741
184817
|
}
|
|
184742
184818
|
function configureAgents(checked) {
|
|
@@ -184777,6 +184853,19 @@ function configureAgents(checked) {
|
|
|
184777
184853
|
}
|
|
184778
184854
|
return { agents, git, showManualInstructions: !!checked["other"] };
|
|
184779
184855
|
}
|
|
184856
|
+
function getInitialSetupPhase() {
|
|
184857
|
+
if (!systemSetupNeeded()) return "agents";
|
|
184858
|
+
if (os8.platform() === "darwin") {
|
|
184859
|
+
if (dnsSetupNeeded()) return "setup-dns";
|
|
184860
|
+
if (tlsSetupNeeded()) return "setup-tls";
|
|
184861
|
+
return "agents";
|
|
184862
|
+
}
|
|
184863
|
+
return "setup-combined";
|
|
184864
|
+
}
|
|
184865
|
+
function getNextPhase(current) {
|
|
184866
|
+
if (current === "setup-dns" && tlsSetupNeeded()) return "setup-tls";
|
|
184867
|
+
return "agents";
|
|
184868
|
+
}
|
|
184780
184869
|
function InitUI() {
|
|
184781
184870
|
const { exit } = useApp();
|
|
184782
184871
|
const [initialState] = useState(() => {
|
|
@@ -184785,45 +184874,92 @@ function InitUI() {
|
|
|
184785
184874
|
return {
|
|
184786
184875
|
detected,
|
|
184787
184876
|
focusedIndex: hasDetected ? options.length : 0
|
|
184788
|
-
// Focus submit if any detected
|
|
184789
184877
|
};
|
|
184790
184878
|
});
|
|
184791
|
-
const [phase, setPhase] = useState(
|
|
184792
|
-
|
|
184879
|
+
const [phase, setPhase] = useState(getInitialSetupPhase);
|
|
184880
|
+
const [stepPhase, setStepPhase] = useState(
|
|
184881
|
+
() => getInitialSetupPhase() === "agents" ? "done" : "prompt"
|
|
184793
184882
|
);
|
|
184794
|
-
const [
|
|
184883
|
+
const [stepError, setStepError] = useState();
|
|
184795
184884
|
const [focusedIndex, setFocusedIndex] = useState(initialState.focusedIndex);
|
|
184796
184885
|
const [checked, setChecked] = useState(
|
|
184797
184886
|
initialState.detected
|
|
184798
184887
|
);
|
|
184799
184888
|
const [result, setResult] = useState(null);
|
|
184800
|
-
const [
|
|
184801
|
-
!systemSetupNeeded() ? { status: "success" } :
|
|
184889
|
+
const [setupResult, setSetupResult] = useState(
|
|
184890
|
+
() => !systemSetupNeeded() ? { dns: { status: "success" }, tls: { status: "success" } } : {}
|
|
184802
184891
|
);
|
|
184803
184892
|
useEffect(() => {
|
|
184804
|
-
if (
|
|
184805
|
-
|
|
184893
|
+
if (stepPhase !== "installing") return;
|
|
184894
|
+
if (phase === "setup-dns") {
|
|
184895
|
+
runSetupStep(
|
|
184896
|
+
() => {
|
|
184897
|
+
ensureCAGenerated();
|
|
184898
|
+
performDnsSetup();
|
|
184899
|
+
},
|
|
184900
|
+
"dns"
|
|
184901
|
+
);
|
|
184902
|
+
} else if (phase === "setup-tls") {
|
|
184903
|
+
runSetupStep(
|
|
184904
|
+
() => {
|
|
184905
|
+
ensureCAGenerated();
|
|
184906
|
+
performTlsTrust();
|
|
184907
|
+
},
|
|
184908
|
+
"tls"
|
|
184909
|
+
);
|
|
184910
|
+
} else if (phase === "setup-combined") {
|
|
184911
|
+
runSetupStep(
|
|
184912
|
+
() => performSystemSetup(),
|
|
184913
|
+
"combined"
|
|
184914
|
+
);
|
|
184806
184915
|
}
|
|
184807
|
-
}, [phase,
|
|
184808
|
-
|
|
184916
|
+
}, [phase, stepPhase]);
|
|
184917
|
+
function runSetupStep(fn, type) {
|
|
184809
184918
|
try {
|
|
184810
|
-
|
|
184811
|
-
|
|
184812
|
-
|
|
184813
|
-
|
|
184919
|
+
fn();
|
|
184920
|
+
if (type === "combined") {
|
|
184921
|
+
setSetupResult((r) => ({
|
|
184922
|
+
...r,
|
|
184923
|
+
dns: { status: "success" },
|
|
184924
|
+
tls: { status: "success" }
|
|
184925
|
+
}));
|
|
184926
|
+
} else {
|
|
184927
|
+
setSetupResult((r) => ({ ...r, [type]: { status: "success" } }));
|
|
184928
|
+
}
|
|
184929
|
+
setStepPhase("done");
|
|
184930
|
+
const next = getNextPhase(phase);
|
|
184931
|
+
setPhase(next);
|
|
184932
|
+
if (next !== "agents") {
|
|
184933
|
+
setStepPhase("prompt");
|
|
184934
|
+
}
|
|
184814
184935
|
} catch (err) {
|
|
184815
|
-
|
|
184816
|
-
|
|
184817
|
-
|
|
184818
|
-
|
|
184819
|
-
|
|
184820
|
-
|
|
184936
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
184937
|
+
if (type === "combined") {
|
|
184938
|
+
setSetupResult((r) => ({
|
|
184939
|
+
...r,
|
|
184940
|
+
dns: { status: "error", error: errorMsg },
|
|
184941
|
+
tls: { status: "error", error: errorMsg }
|
|
184942
|
+
}));
|
|
184943
|
+
} else {
|
|
184944
|
+
setSetupResult((r) => ({
|
|
184945
|
+
...r,
|
|
184946
|
+
[type]: { status: "error", error: errorMsg }
|
|
184947
|
+
}));
|
|
184948
|
+
}
|
|
184949
|
+
setStepError(errorMsg);
|
|
184950
|
+
setStepPhase("error");
|
|
184951
|
+
const next = getNextPhase(phase);
|
|
184952
|
+
setPhase(next);
|
|
184953
|
+
if (next !== "agents") {
|
|
184954
|
+
setStepPhase("prompt");
|
|
184955
|
+
setStepError(void 0);
|
|
184956
|
+
}
|
|
184821
184957
|
}
|
|
184822
184958
|
}
|
|
184823
184959
|
const isSubmitFocused = focusedIndex === options.length;
|
|
184824
184960
|
useInput((input, key) => {
|
|
184825
|
-
if (phase === "
|
|
184826
|
-
|
|
184961
|
+
if ((phase === "setup-dns" || phase === "setup-tls" || phase === "setup-combined") && stepPhase === "prompt" && key.return) {
|
|
184962
|
+
setStepPhase("installing");
|
|
184827
184963
|
return;
|
|
184828
184964
|
}
|
|
184829
184965
|
if (phase !== "agents") return;
|
|
@@ -184854,11 +184990,13 @@ function InitUI() {
|
|
|
184854
184990
|
return () => clearTimeout(timer);
|
|
184855
184991
|
}
|
|
184856
184992
|
}, [phase, exit]);
|
|
184857
|
-
if (phase === "
|
|
184858
|
-
if (
|
|
184859
|
-
|
|
184993
|
+
if (phase === "setup-dns" || phase === "setup-tls" || phase === "setup-combined") {
|
|
184994
|
+
if (stepPhase === "prompt") {
|
|
184995
|
+
const isDns = phase === "setup-dns";
|
|
184996
|
+
const isTls = phase === "setup-tls";
|
|
184997
|
+
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "cyan" }, "Configure development environment"), /* @__PURE__ */ React2.createElement(Text2, null, " "), (isDns || phase === "setup-combined") && /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(Text2, null, "We need to do a one-time setup for all your Specific projects."), /* @__PURE__ */ React2.createElement(Text2, null, "This is so we can run your apps locally with secure URLs:"), /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "green" }, "https://your-app.spcf.localhost"), /* @__PURE__ */ React2.createElement(Text2, null, " "), /* @__PURE__ */ React2.createElement(Text2, null, "You will be prompted to authorize with your password.")), isTls && /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(Text2, null, "We need to trust a development certificate for HTTPS."), /* @__PURE__ */ React2.createElement(Text2, null, " "), /* @__PURE__ */ React2.createElement(Text2, null, "You will be prompted to authorize.")), /* @__PURE__ */ React2.createElement(Text2, null, " "), /* @__PURE__ */ React2.createElement(Text2, null, "Press ", /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "cyan" }, "Enter"), " to continue."));
|
|
184860
184998
|
}
|
|
184861
|
-
if (
|
|
184999
|
+
if (stepPhase === "installing") {
|
|
184862
185000
|
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "cyan" }, "Configure development environment"), /* @__PURE__ */ React2.createElement(Text2, null, " "), /* @__PURE__ */ React2.createElement(Text2, null, "Awaiting authorization\u2026"));
|
|
184863
185001
|
}
|
|
184864
185002
|
}
|
|
@@ -184866,10 +185004,16 @@ function InitUI() {
|
|
|
184866
185004
|
const selectedAgents = Object.entries(checked).filter(([, v]) => v).length;
|
|
184867
185005
|
const agentChanges = result && (result.agents.filesCreated.length > 0 || result.agents.filesModified.length > 0);
|
|
184868
185006
|
const gitChanges = result?.git && (result.git.filesCreated.length > 0 || result.git.filesModified.length > 0);
|
|
184869
|
-
|
|
185007
|
+
const setupSuccess2 = (!setupResult.dns || setupResult.dns.status === "success") && (!setupResult.tls || setupResult.tls.status === "success");
|
|
185008
|
+
const setupError2 = setupResult.dns?.status === "error" || setupResult.tls?.status === "error";
|
|
185009
|
+
const setupErrorMsg2 = setupResult.dns?.error || setupResult.tls?.error;
|
|
185010
|
+
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, setupSuccess2 && /* @__PURE__ */ React2.createElement(Text2, null, /* @__PURE__ */ React2.createElement(Text2, { color: "green" }, "\u2713"), " TLS and DNS configured"), setupError2 && /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(Text2, null, /* @__PURE__ */ React2.createElement(Text2, { color: "red" }, "\u2717"), " Failed to set up TLS and DNS"), setupErrorMsg2 && /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, " ", setupErrorMsg2)), result && /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(Text2, null, /* @__PURE__ */ React2.createElement(Text2, { color: "green" }, "\u2713"), " Coding agents configured"), result.agents.filesCreated.length > 0 && /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, " ", "Created: ", result.agents.filesCreated.join(", ")), result.agents.filesModified.length > 0 && /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, " ", "Modified: ", result.agents.filesModified.join(", ")), !agentChanges && selectedAgents > 0 && /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, " ", "No changes needed (files already configured)"), selectedAgents === 0 && /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, " No agents selected")), result?.git && /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(Text2, null, /* @__PURE__ */ React2.createElement(Text2, { color: "green" }, "\u2713"), " Git configured"), result.git.filesCreated.length > 0 && /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, " ", "Created: ", result.git.filesCreated.join(", ")), result.git.filesModified.length > 0 && /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, " ", "Modified: ", result.git.filesModified.join(", ")), !gitChanges && /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, " ", "No changes needed (.gitignore already configured)")), result?.showManualInstructions && /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(Text2, null, " "), /* @__PURE__ */ React2.createElement(Text2, null, /* @__PURE__ */ React2.createElement(Text2, { color: "green" }, "\u2713"), " Manual configuration selected"), /* @__PURE__ */ React2.createElement(Text2, null, " "), /* @__PURE__ */ React2.createElement(Text2, null, " Add this to your agent's system prompt:"), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"), /* @__PURE__ */ React2.createElement(Text2, null, SPECIFIC_INSTRUCTIONS), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"), /* @__PURE__ */ React2.createElement(Text2, null, " "), /* @__PURE__ */ React2.createElement(Text2, null, " ", "We also recommend allowing your agent to run `specific docs *`"), /* @__PURE__ */ React2.createElement(Text2, null, " and `specific check *` without confirmation.")));
|
|
184870
185011
|
}
|
|
184871
|
-
const
|
|
184872
|
-
|
|
185012
|
+
const setupSuccess = (!setupResult.dns || setupResult.dns.status === "success") && (!setupResult.tls || setupResult.tls.status === "success");
|
|
185013
|
+
const setupError = setupResult.dns?.status === "error" || setupResult.tls?.status === "error";
|
|
185014
|
+
const setupErrorMsg = setupResult.dns?.error || setupResult.tls?.error;
|
|
185015
|
+
const setupLine = setupSuccess ? /* @__PURE__ */ React2.createElement(Text2, null, /* @__PURE__ */ React2.createElement(Text2, { color: "green" }, "\u2713"), " TLS and DNS configured") : setupError ? /* @__PURE__ */ React2.createElement(Text2, null, /* @__PURE__ */ React2.createElement(Text2, { color: "red" }, "\u2717"), " TLS and DNS setup failed", " ", /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "(", setupErrorMsg, ")")) : null;
|
|
185016
|
+
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, setupLine, /* @__PURE__ */ React2.createElement(Text2, null, /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "\u25CF"), " Configure coding agents", " ", /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "(space to select)")), /* @__PURE__ */ React2.createElement(Text2, null, " "), options.map((option, index) => {
|
|
184873
185017
|
const isFocused = focusedIndex === index;
|
|
184874
185018
|
const isChecked = checked[option.id];
|
|
184875
185019
|
return /* @__PURE__ */ React2.createElement(Box2, { key: option.id }, /* @__PURE__ */ React2.createElement(Text2, { ...isFocused && { color: "cyan" } }, isFocused ? "\u276F " : " ", isChecked ? "[\u2713]" : "[ ]", " ", option.label));
|
|
@@ -184979,7 +185123,7 @@ var BETA_REGISTRY = [
|
|
|
184979
185123
|
];
|
|
184980
185124
|
|
|
184981
185125
|
// src/lib/beta/storage.ts
|
|
184982
|
-
import { readFileSync as readFileSync5, writeFileSync as
|
|
185126
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync6, existsSync as existsSync6, mkdirSync as mkdirSync6 } from "fs";
|
|
184983
185127
|
import { join as join7 } from "path";
|
|
184984
185128
|
var BETAS_FILE = ".specific/betas.json";
|
|
184985
185129
|
function loadEnabledBetas(projectDir = process.cwd()) {
|
|
@@ -185012,7 +185156,7 @@ function saveBetas(enabled, projectDir) {
|
|
|
185012
185156
|
mkdirSync6(specificDir, { recursive: true });
|
|
185013
185157
|
}
|
|
185014
185158
|
const data = { enabled };
|
|
185015
|
-
|
|
185159
|
+
writeFileSync6(
|
|
185016
185160
|
join7(projectDir, BETAS_FILE),
|
|
185017
185161
|
JSON.stringify(data, null, 2) + "\n"
|
|
185018
185162
|
);
|
|
@@ -185079,7 +185223,7 @@ function resolveFilesystemDoc(path30) {
|
|
|
185079
185223
|
import React3, { useState as useState2, useEffect as useEffect2 } from "react";
|
|
185080
185224
|
import { render as render3, Text as Text3, Box as Box3 } from "ink";
|
|
185081
185225
|
import Spinner3 from "ink-spinner";
|
|
185082
|
-
import * as
|
|
185226
|
+
import * as fs14 from "fs";
|
|
185083
185227
|
import * as path9 from "path";
|
|
185084
185228
|
import { execFile as execFile7 } from "child_process";
|
|
185085
185229
|
|
|
@@ -185951,32 +186095,32 @@ var ExtractionError = class extends Error {
|
|
|
185951
186095
|
};
|
|
185952
186096
|
|
|
185953
186097
|
// src/lib/bin/manager.ts
|
|
185954
|
-
import * as
|
|
186098
|
+
import * as fs13 from "fs";
|
|
185955
186099
|
import * as path8 from "path";
|
|
185956
|
-
import * as
|
|
186100
|
+
import * as os9 from "os";
|
|
185957
186101
|
import { createReadStream } from "fs";
|
|
185958
186102
|
import { createTarExtractor, extractTo } from "tar-vern";
|
|
185959
186103
|
function getLibraryEnv(binary) {
|
|
185960
186104
|
if (!binary.libraryPath) {
|
|
185961
186105
|
return {};
|
|
185962
186106
|
}
|
|
185963
|
-
const
|
|
185964
|
-
if (
|
|
186107
|
+
const platform8 = os9.platform();
|
|
186108
|
+
if (platform8 === "darwin") {
|
|
185965
186109
|
return { DYLD_LIBRARY_PATH: binary.libraryPath };
|
|
185966
|
-
} else if (
|
|
186110
|
+
} else if (platform8 === "linux") {
|
|
185967
186111
|
return { LD_LIBRARY_PATH: binary.libraryPath };
|
|
185968
186112
|
}
|
|
185969
186113
|
return {};
|
|
185970
186114
|
}
|
|
185971
186115
|
function getBinBaseDir() {
|
|
185972
|
-
return path8.join(
|
|
186116
|
+
return path8.join(os9.homedir(), ".specific", "bin");
|
|
185973
186117
|
}
|
|
185974
186118
|
function getPlatformInfo() {
|
|
185975
|
-
const
|
|
185976
|
-
const arch3 =
|
|
185977
|
-
if (
|
|
186119
|
+
const platform8 = os9.platform();
|
|
186120
|
+
const arch3 = os9.arch();
|
|
186121
|
+
if (platform8 !== "darwin" && platform8 !== "linux") {
|
|
185978
186122
|
throw new Error(
|
|
185979
|
-
`Unsupported platform: ${
|
|
186123
|
+
`Unsupported platform: ${platform8}. Only macOS and Linux are supported.`
|
|
185980
186124
|
);
|
|
185981
186125
|
}
|
|
185982
186126
|
const archStr = arch3;
|
|
@@ -185990,7 +186134,7 @@ function getPlatformInfo() {
|
|
|
185990
186134
|
`Unsupported architecture: ${arch3}. Only x64 and arm64 are supported.`
|
|
185991
186135
|
);
|
|
185992
186136
|
}
|
|
185993
|
-
return { platform:
|
|
186137
|
+
return { platform: platform8, arch: mappedArch };
|
|
185994
186138
|
}
|
|
185995
186139
|
function getBinaryDir(definition, version, platformInfo) {
|
|
185996
186140
|
return path8.join(
|
|
@@ -186004,7 +186148,7 @@ function isBinaryInstalled(definition, version, platformInfo) {
|
|
|
186004
186148
|
const binDir = getBinaryDir(definition, version, platformInfo);
|
|
186005
186149
|
for (const execPath of definition.executables) {
|
|
186006
186150
|
const fullPath = path8.join(binDir, execPath);
|
|
186007
|
-
if (!
|
|
186151
|
+
if (!fs13.existsSync(fullPath)) {
|
|
186008
186152
|
return false;
|
|
186009
186153
|
}
|
|
186010
186154
|
}
|
|
@@ -186033,11 +186177,11 @@ async function downloadFile(url, destPath, onProgress) {
|
|
|
186033
186177
|
);
|
|
186034
186178
|
let bytesDownloaded = 0;
|
|
186035
186179
|
const parentDir = path8.dirname(destPath);
|
|
186036
|
-
if (!
|
|
186037
|
-
|
|
186180
|
+
if (!fs13.existsSync(parentDir)) {
|
|
186181
|
+
fs13.mkdirSync(parentDir, { recursive: true });
|
|
186038
186182
|
}
|
|
186039
186183
|
const partPath = destPath + ".part";
|
|
186040
|
-
const fileStream =
|
|
186184
|
+
const fileStream = fs13.createWriteStream(partPath);
|
|
186041
186185
|
try {
|
|
186042
186186
|
const reader = response.body.getReader();
|
|
186043
186187
|
while (true) {
|
|
@@ -186060,12 +186204,12 @@ async function downloadFile(url, destPath, onProgress) {
|
|
|
186060
186204
|
else resolve9();
|
|
186061
186205
|
});
|
|
186062
186206
|
});
|
|
186063
|
-
|
|
186207
|
+
fs13.renameSync(partPath, destPath);
|
|
186064
186208
|
} catch (error) {
|
|
186065
186209
|
try {
|
|
186066
186210
|
fileStream.close();
|
|
186067
|
-
if (
|
|
186068
|
-
|
|
186211
|
+
if (fs13.existsSync(partPath)) {
|
|
186212
|
+
fs13.unlinkSync(partPath);
|
|
186069
186213
|
}
|
|
186070
186214
|
} catch {
|
|
186071
186215
|
}
|
|
@@ -186074,8 +186218,8 @@ async function downloadFile(url, destPath, onProgress) {
|
|
|
186074
186218
|
}
|
|
186075
186219
|
async function extractTarball(archivePath, destDir, definition, onProgress) {
|
|
186076
186220
|
onProgress?.({ phase: "extracting" });
|
|
186077
|
-
if (!
|
|
186078
|
-
|
|
186221
|
+
if (!fs13.existsSync(destDir)) {
|
|
186222
|
+
fs13.mkdirSync(destDir, { recursive: true });
|
|
186079
186223
|
}
|
|
186080
186224
|
try {
|
|
186081
186225
|
const fileStream = createReadStream(archivePath);
|
|
@@ -186084,8 +186228,8 @@ async function extractTarball(archivePath, destDir, definition, onProgress) {
|
|
|
186084
186228
|
onProgress?.({ phase: "finalizing" });
|
|
186085
186229
|
for (const execPath of definition.executables) {
|
|
186086
186230
|
const fullPath = path8.join(destDir, execPath);
|
|
186087
|
-
if (
|
|
186088
|
-
|
|
186231
|
+
if (fs13.existsSync(fullPath)) {
|
|
186232
|
+
fs13.chmodSync(fullPath, 493);
|
|
186089
186233
|
}
|
|
186090
186234
|
}
|
|
186091
186235
|
} catch (error) {
|
|
@@ -186109,12 +186253,12 @@ async function ensureBinary(definition, version, onProgress) {
|
|
|
186109
186253
|
`Binary type definitions must have exactly one executable, got ${definition.executables.length}`
|
|
186110
186254
|
);
|
|
186111
186255
|
}
|
|
186112
|
-
if (!
|
|
186113
|
-
|
|
186256
|
+
if (!fs13.existsSync(binDir)) {
|
|
186257
|
+
fs13.mkdirSync(binDir, { recursive: true });
|
|
186114
186258
|
}
|
|
186115
186259
|
const execPath = path8.join(binDir, definition.executables[0]);
|
|
186116
186260
|
await downloadFile(url, execPath, onProgress);
|
|
186117
|
-
|
|
186261
|
+
fs13.chmodSync(execPath, 493);
|
|
186118
186262
|
onProgress?.({ phase: "finalizing" });
|
|
186119
186263
|
} else {
|
|
186120
186264
|
const downloadDir = path8.join(getBinBaseDir(), "downloads");
|
|
@@ -186125,8 +186269,8 @@ async function ensureBinary(definition, version, onProgress) {
|
|
|
186125
186269
|
await extractTarball(archivePath, binDir, definition, onProgress);
|
|
186126
186270
|
} finally {
|
|
186127
186271
|
try {
|
|
186128
|
-
if (
|
|
186129
|
-
|
|
186272
|
+
if (fs13.existsSync(archivePath)) {
|
|
186273
|
+
fs13.unlinkSync(archivePath);
|
|
186130
186274
|
}
|
|
186131
186275
|
} catch {
|
|
186132
186276
|
}
|
|
@@ -186302,20 +186446,20 @@ function CheckUI() {
|
|
|
186302
186446
|
useEffect2(() => {
|
|
186303
186447
|
async function load() {
|
|
186304
186448
|
const configPath = path9.join(process.cwd(), "specific.hcl");
|
|
186305
|
-
if (!
|
|
186449
|
+
if (!fs14.existsSync(configPath)) {
|
|
186306
186450
|
setState({
|
|
186307
186451
|
status: "error",
|
|
186308
186452
|
error: "No specific.hcl found in current directory"
|
|
186309
186453
|
});
|
|
186310
186454
|
return;
|
|
186311
186455
|
}
|
|
186312
|
-
const hcl =
|
|
186456
|
+
const hcl = fs14.readFileSync(configPath, "utf-8");
|
|
186313
186457
|
try {
|
|
186314
186458
|
const config2 = await parseConfig(hcl);
|
|
186315
186459
|
for (const build of config2.builds) {
|
|
186316
186460
|
if (build.dockerfile) {
|
|
186317
186461
|
const dockerfilePath = path9.resolve(process.cwd(), build.dockerfile);
|
|
186318
|
-
if (!
|
|
186462
|
+
if (!fs14.existsSync(dockerfilePath)) {
|
|
186319
186463
|
setState({
|
|
186320
186464
|
status: "error",
|
|
186321
186465
|
error: `Build "${build.name}": Dockerfile not found at "${build.dockerfile}" (resolved to ${dockerfilePath})`
|
|
@@ -186331,7 +186475,7 @@ function CheckUI() {
|
|
|
186331
186475
|
process.cwd(),
|
|
186332
186476
|
pg.reshape.migrations_dir ?? "migrations"
|
|
186333
186477
|
);
|
|
186334
|
-
if (!
|
|
186478
|
+
if (!fs14.existsSync(migrationsDir)) {
|
|
186335
186479
|
reshapeChecks2.push({
|
|
186336
186480
|
databaseName: pg.name,
|
|
186337
186481
|
migrationsDir: pg.reshape.migrations_dir ?? "migrations",
|
|
@@ -186384,7 +186528,8 @@ function checkCommand() {
|
|
|
186384
186528
|
import React6, { useState as useState5, useEffect as useEffect3, useRef } from "react";
|
|
186385
186529
|
import { render as render4, Text as Text6, Box as Box6, useApp as useApp2, Static, useInput as useInput4 } from "ink";
|
|
186386
186530
|
import Spinner4 from "ink-spinner";
|
|
186387
|
-
import * as
|
|
186531
|
+
import * as fs24 from "fs";
|
|
186532
|
+
import * as os11 from "os";
|
|
186388
186533
|
import * as path20 from "path";
|
|
186389
186534
|
|
|
186390
186535
|
// node_modules/.pnpm/chokidar@5.0.0/node_modules/chokidar/index.js
|
|
@@ -188139,7 +188284,7 @@ var PortAllocator = class {
|
|
|
188139
188284
|
};
|
|
188140
188285
|
|
|
188141
188286
|
// src/lib/dev/stable-port-allocator.ts
|
|
188142
|
-
import * as
|
|
188287
|
+
import * as fs15 from "fs";
|
|
188143
188288
|
import * as path10 from "path";
|
|
188144
188289
|
var PORT_RANGE_START2 = 4e4;
|
|
188145
188290
|
var PORT_RANGE_END2 = 49999;
|
|
@@ -188154,11 +188299,11 @@ var StablePortAllocator = class {
|
|
|
188154
188299
|
this.loadPorts();
|
|
188155
188300
|
}
|
|
188156
188301
|
loadPorts() {
|
|
188157
|
-
if (!
|
|
188302
|
+
if (!fs15.existsSync(this.portsFilePath)) {
|
|
188158
188303
|
return;
|
|
188159
188304
|
}
|
|
188160
188305
|
try {
|
|
188161
|
-
const content =
|
|
188306
|
+
const content = fs15.readFileSync(this.portsFilePath, "utf-8");
|
|
188162
188307
|
const data = JSON.parse(content);
|
|
188163
188308
|
if (data.version === 1 && data.ports) {
|
|
188164
188309
|
this.savedPorts = data.ports;
|
|
@@ -188171,14 +188316,14 @@ var StablePortAllocator = class {
|
|
|
188171
188316
|
}
|
|
188172
188317
|
}
|
|
188173
188318
|
savePorts() {
|
|
188174
|
-
if (!
|
|
188175
|
-
|
|
188319
|
+
if (!fs15.existsSync(this.portsDir)) {
|
|
188320
|
+
fs15.mkdirSync(this.portsDir, { recursive: true });
|
|
188176
188321
|
}
|
|
188177
188322
|
const data = {
|
|
188178
188323
|
version: 1,
|
|
188179
188324
|
ports: this.savedPorts
|
|
188180
188325
|
};
|
|
188181
|
-
|
|
188326
|
+
fs15.writeFileSync(this.portsFilePath, JSON.stringify(data, null, 2));
|
|
188182
188327
|
}
|
|
188183
188328
|
allocateRandom() {
|
|
188184
188329
|
const rangeSize = PORT_RANGE_END2 - PORT_RANGE_START2 + 1;
|
|
@@ -188205,7 +188350,7 @@ var StablePortAllocator = class {
|
|
|
188205
188350
|
};
|
|
188206
188351
|
|
|
188207
188352
|
// src/lib/dev/database-manager.ts
|
|
188208
|
-
import * as
|
|
188353
|
+
import * as fs16 from "fs";
|
|
188209
188354
|
import * as path11 from "path";
|
|
188210
188355
|
import * as net from "net";
|
|
188211
188356
|
import { spawn } from "child_process";
|
|
@@ -188217,9 +188362,9 @@ async function startPostgres(pg, port, dataDir, onProgress) {
|
|
|
188217
188362
|
const password = "postgres";
|
|
188218
188363
|
const libraryEnv = getLibraryEnv(binary);
|
|
188219
188364
|
const env2 = { ...process.env, ...libraryEnv };
|
|
188220
|
-
const dataExists =
|
|
188365
|
+
const dataExists = fs16.existsSync(dbDataPath);
|
|
188221
188366
|
if (!dataExists) {
|
|
188222
|
-
|
|
188367
|
+
fs16.mkdirSync(dbDataPath, { recursive: true });
|
|
188223
188368
|
await runCommand(
|
|
188224
188369
|
binary.executables["initdb"],
|
|
188225
188370
|
["-D", dbDataPath, "-U", user, "--auth=trust", "--no-locale", "-E", "UTF8"],
|
|
@@ -188296,8 +188441,8 @@ async function startRedis(redis, port, onProgress) {
|
|
|
188296
188441
|
async function startStorage(storage, port, dataDir) {
|
|
188297
188442
|
const S3rver = (await import("s3rver")).default;
|
|
188298
188443
|
const storageDataPath = path11.join(process.cwd(), dataDir, storage.name);
|
|
188299
|
-
if (!
|
|
188300
|
-
|
|
188444
|
+
if (!fs16.existsSync(storageDataPath)) {
|
|
188445
|
+
fs16.mkdirSync(storageDataPath, { recursive: true });
|
|
188301
188446
|
}
|
|
188302
188447
|
const host = "127.0.0.1";
|
|
188303
188448
|
const accessKey = "S3RVER";
|
|
@@ -188827,9 +188972,11 @@ function resolveEnv(env2, resources, secrets, configs, servicePort, serviceEndpo
|
|
|
188827
188972
|
}
|
|
188828
188973
|
function resolveEnvForExec(env2, resources, secrets, configs) {
|
|
188829
188974
|
if (!env2) {
|
|
188830
|
-
return {};
|
|
188975
|
+
return { env: {}, missingSecrets: [], missingConfigs: [] };
|
|
188831
188976
|
}
|
|
188832
188977
|
const resolved = {};
|
|
188978
|
+
const missingSecrets = [];
|
|
188979
|
+
const missingConfigs = [];
|
|
188833
188980
|
for (const [key, value] of Object.entries(env2)) {
|
|
188834
188981
|
if (typeof value === "string") {
|
|
188835
188982
|
resolved[key] = value;
|
|
@@ -188845,12 +188992,20 @@ function resolveEnvForExec(env2, resources, secrets, configs) {
|
|
|
188845
188992
|
if (hasSkippableRef) {
|
|
188846
188993
|
continue;
|
|
188847
188994
|
}
|
|
188995
|
+
}
|
|
188996
|
+
try {
|
|
188848
188997
|
resolved[key] = resolveEnvValue(value, resources, secrets, configs);
|
|
188849
|
-
|
|
188998
|
+
} catch (err) {
|
|
188999
|
+
if (err instanceof MissingSecretError) {
|
|
189000
|
+
missingSecrets.push({ name: err.secretName, envVar: key });
|
|
189001
|
+
} else if (err instanceof MissingConfigError) {
|
|
189002
|
+
missingConfigs.push({ name: err.configName, envVar: key });
|
|
189003
|
+
} else {
|
|
189004
|
+
throw err;
|
|
189005
|
+
}
|
|
188850
189006
|
}
|
|
188851
|
-
resolved[key] = resolveEnvValue(value, resources, secrets, configs);
|
|
188852
189007
|
}
|
|
188853
|
-
return resolved;
|
|
189008
|
+
return { env: resolved, missingSecrets, missingConfigs };
|
|
188854
189009
|
}
|
|
188855
189010
|
|
|
188856
189011
|
// src/lib/dev/service-runner.ts
|
|
@@ -188940,7 +189095,7 @@ function startService(service, resources, secrets, configs, endpointPorts, servi
|
|
|
188940
189095
|
}
|
|
188941
189096
|
|
|
188942
189097
|
// src/lib/dev/instance-state.ts
|
|
188943
|
-
import * as
|
|
189098
|
+
import * as fs17 from "fs";
|
|
188944
189099
|
import * as path12 from "path";
|
|
188945
189100
|
var InstanceStateManager = class {
|
|
188946
189101
|
stateDir;
|
|
@@ -188958,8 +189113,8 @@ var InstanceStateManager = class {
|
|
|
188958
189113
|
return this.key;
|
|
188959
189114
|
}
|
|
188960
189115
|
ensureStateDir() {
|
|
188961
|
-
if (!
|
|
188962
|
-
|
|
189116
|
+
if (!fs17.existsSync(this.stateDir)) {
|
|
189117
|
+
fs17.mkdirSync(this.stateDir, { recursive: true });
|
|
188963
189118
|
}
|
|
188964
189119
|
}
|
|
188965
189120
|
isProcessRunning(pid) {
|
|
@@ -188976,15 +189131,15 @@ var InstanceStateManager = class {
|
|
|
188976
189131
|
const startTime = Date.now();
|
|
188977
189132
|
while (Date.now() - startTime < timeoutMs) {
|
|
188978
189133
|
try {
|
|
188979
|
-
const fd =
|
|
189134
|
+
const fd = fs17.openSync(
|
|
188980
189135
|
this.lockPath,
|
|
188981
|
-
|
|
189136
|
+
fs17.constants.O_CREAT | fs17.constants.O_EXCL | fs17.constants.O_WRONLY
|
|
188982
189137
|
);
|
|
188983
|
-
|
|
188984
|
-
|
|
189138
|
+
fs17.writeSync(fd, String(process.pid));
|
|
189139
|
+
fs17.closeSync(fd);
|
|
188985
189140
|
return () => {
|
|
188986
189141
|
try {
|
|
188987
|
-
|
|
189142
|
+
fs17.unlinkSync(this.lockPath);
|
|
188988
189143
|
} catch {
|
|
188989
189144
|
}
|
|
188990
189145
|
};
|
|
@@ -188993,16 +189148,16 @@ var InstanceStateManager = class {
|
|
|
188993
189148
|
if (err.code === "EEXIST") {
|
|
188994
189149
|
try {
|
|
188995
189150
|
const lockPid = parseInt(
|
|
188996
|
-
|
|
189151
|
+
fs17.readFileSync(this.lockPath, "utf-8").trim(),
|
|
188997
189152
|
10
|
|
188998
189153
|
);
|
|
188999
189154
|
if (!this.isProcessRunning(lockPid)) {
|
|
189000
|
-
|
|
189155
|
+
fs17.unlinkSync(this.lockPath);
|
|
189001
189156
|
continue;
|
|
189002
189157
|
}
|
|
189003
189158
|
} catch {
|
|
189004
189159
|
try {
|
|
189005
|
-
|
|
189160
|
+
fs17.unlinkSync(this.lockPath);
|
|
189006
189161
|
} catch {
|
|
189007
189162
|
}
|
|
189008
189163
|
continue;
|
|
@@ -189016,12 +189171,12 @@ var InstanceStateManager = class {
|
|
|
189016
189171
|
throw new Error("Failed to acquire state lock (timeout)");
|
|
189017
189172
|
}
|
|
189018
189173
|
async getExistingInstances() {
|
|
189019
|
-
if (!
|
|
189174
|
+
if (!fs17.existsSync(this.statePath)) {
|
|
189020
189175
|
return null;
|
|
189021
189176
|
}
|
|
189022
189177
|
const releaseLock = await this.acquireLock();
|
|
189023
189178
|
try {
|
|
189024
|
-
const content =
|
|
189179
|
+
const content = fs17.readFileSync(this.statePath, "utf-8");
|
|
189025
189180
|
const state = JSON.parse(content);
|
|
189026
189181
|
if (!this.isProcessRunning(state.owner.pid)) {
|
|
189027
189182
|
return null;
|
|
@@ -189034,21 +189189,21 @@ var InstanceStateManager = class {
|
|
|
189034
189189
|
}
|
|
189035
189190
|
}
|
|
189036
189191
|
async cleanStaleState() {
|
|
189037
|
-
if (!
|
|
189192
|
+
if (!fs17.existsSync(this.statePath)) {
|
|
189038
189193
|
return false;
|
|
189039
189194
|
}
|
|
189040
189195
|
const releaseLock = await this.acquireLock();
|
|
189041
189196
|
try {
|
|
189042
|
-
const content =
|
|
189197
|
+
const content = fs17.readFileSync(this.statePath, "utf-8");
|
|
189043
189198
|
const state = JSON.parse(content);
|
|
189044
189199
|
if (!this.isProcessRunning(state.owner.pid)) {
|
|
189045
|
-
|
|
189200
|
+
fs17.unlinkSync(this.statePath);
|
|
189046
189201
|
return true;
|
|
189047
189202
|
}
|
|
189048
189203
|
return false;
|
|
189049
189204
|
} catch {
|
|
189050
189205
|
try {
|
|
189051
|
-
|
|
189206
|
+
fs17.unlinkSync(this.statePath);
|
|
189052
189207
|
return true;
|
|
189053
189208
|
} catch {
|
|
189054
189209
|
}
|
|
@@ -189060,8 +189215,8 @@ var InstanceStateManager = class {
|
|
|
189060
189215
|
async claimOwnership(command) {
|
|
189061
189216
|
const releaseLock = await this.acquireLock();
|
|
189062
189217
|
try {
|
|
189063
|
-
if (
|
|
189064
|
-
const content =
|
|
189218
|
+
if (fs17.existsSync(this.statePath)) {
|
|
189219
|
+
const content = fs17.readFileSync(this.statePath, "utf-8");
|
|
189065
189220
|
const state2 = JSON.parse(content);
|
|
189066
189221
|
if (this.isProcessRunning(state2.owner.pid)) {
|
|
189067
189222
|
throw new Error(`Instances already owned by PID ${state2.owner.pid}`);
|
|
@@ -189130,8 +189285,8 @@ var InstanceStateManager = class {
|
|
|
189130
189285
|
}
|
|
189131
189286
|
const releaseLock = await this.acquireLock();
|
|
189132
189287
|
try {
|
|
189133
|
-
if (
|
|
189134
|
-
|
|
189288
|
+
if (fs17.existsSync(this.statePath)) {
|
|
189289
|
+
fs17.unlinkSync(this.statePath);
|
|
189135
189290
|
}
|
|
189136
189291
|
this.ownsInstances = false;
|
|
189137
189292
|
} finally {
|
|
@@ -189139,21 +189294,21 @@ var InstanceStateManager = class {
|
|
|
189139
189294
|
}
|
|
189140
189295
|
}
|
|
189141
189296
|
readState() {
|
|
189142
|
-
const content =
|
|
189297
|
+
const content = fs17.readFileSync(this.statePath, "utf-8");
|
|
189143
189298
|
return JSON.parse(content);
|
|
189144
189299
|
}
|
|
189145
189300
|
writeStateAtomic(state) {
|
|
189146
189301
|
this.ensureStateDir();
|
|
189147
189302
|
const tmpPath = this.statePath + ".tmp";
|
|
189148
|
-
|
|
189149
|
-
|
|
189303
|
+
fs17.writeFileSync(tmpPath, JSON.stringify(state, null, 2));
|
|
189304
|
+
fs17.renameSync(tmpPath, this.statePath);
|
|
189150
189305
|
}
|
|
189151
189306
|
};
|
|
189152
189307
|
|
|
189153
189308
|
// src/lib/dev/http-proxy.ts
|
|
189154
189309
|
import * as http from "http";
|
|
189155
189310
|
import * as https from "https";
|
|
189156
|
-
import * as
|
|
189311
|
+
import * as fs18 from "fs";
|
|
189157
189312
|
import * as path13 from "path";
|
|
189158
189313
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
189159
189314
|
import httpProxy from "http-proxy";
|
|
@@ -189576,10 +189731,10 @@ function serveFilesystemFile(res, pathname) {
|
|
|
189576
189731
|
res.end("<h1>Forbidden</h1>");
|
|
189577
189732
|
return;
|
|
189578
189733
|
}
|
|
189579
|
-
if (
|
|
189580
|
-
if (
|
|
189734
|
+
if (fs18.existsSync(resolvedPath)) {
|
|
189735
|
+
if (fs18.statSync(resolvedPath).isDirectory()) {
|
|
189581
189736
|
const indexPath2 = path13.join(resolvedPath, "index.html");
|
|
189582
|
-
if (
|
|
189737
|
+
if (fs18.existsSync(indexPath2)) {
|
|
189583
189738
|
return serveFile(res, indexPath2);
|
|
189584
189739
|
}
|
|
189585
189740
|
} else {
|
|
@@ -189587,15 +189742,15 @@ function serveFilesystemFile(res, pathname) {
|
|
|
189587
189742
|
}
|
|
189588
189743
|
}
|
|
189589
189744
|
const htmlPath = resolvedPath + ".html";
|
|
189590
|
-
if (
|
|
189745
|
+
if (fs18.existsSync(htmlPath)) {
|
|
189591
189746
|
return serveFile(res, htmlPath);
|
|
189592
189747
|
}
|
|
189593
189748
|
const indexPath = path13.join(resolvedPath, "index.html");
|
|
189594
|
-
if (
|
|
189749
|
+
if (fs18.existsSync(indexPath)) {
|
|
189595
189750
|
return serveFile(res, indexPath);
|
|
189596
189751
|
}
|
|
189597
189752
|
const notFoundPath = path13.join(adminDir, "404.html");
|
|
189598
|
-
if (
|
|
189753
|
+
if (fs18.existsSync(notFoundPath)) {
|
|
189599
189754
|
return serveFileContent(res, notFoundPath, "text/html", 404);
|
|
189600
189755
|
}
|
|
189601
189756
|
res.writeHead(404, { "Content-Type": "text/html" });
|
|
@@ -189608,7 +189763,7 @@ function serveFile(res, filePath) {
|
|
|
189608
189763
|
}
|
|
189609
189764
|
function serveFileContent(res, filePath, contentType, statusCode = 200) {
|
|
189610
189765
|
try {
|
|
189611
|
-
const content =
|
|
189766
|
+
const content = fs18.readFileSync(filePath);
|
|
189612
189767
|
res.writeHead(statusCode, { "Content-Type": contentType });
|
|
189613
189768
|
res.end(content);
|
|
189614
189769
|
} catch (err) {
|
|
@@ -189983,7 +190138,7 @@ async function startMailServer(mail, smtpPort, apiPort) {
|
|
|
189983
190138
|
|
|
189984
190139
|
// src/lib/dev/drizzle-gateway-manager.ts
|
|
189985
190140
|
import * as net3 from "net";
|
|
189986
|
-
import * as
|
|
190141
|
+
import * as fs19 from "fs";
|
|
189987
190142
|
import * as path15 from "path";
|
|
189988
190143
|
import { spawn as spawn4 } from "child_process";
|
|
189989
190144
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
@@ -190017,12 +190172,12 @@ async function startDrizzleGateway(postgresInstances, port, configDir, options2)
|
|
|
190017
190172
|
);
|
|
190018
190173
|
const host = "127.0.0.1";
|
|
190019
190174
|
const drizzleConfigDir = path15.join(configDir, "drizzle-gateway");
|
|
190020
|
-
if (!
|
|
190021
|
-
|
|
190175
|
+
if (!fs19.existsSync(drizzleConfigDir)) {
|
|
190176
|
+
fs19.mkdirSync(drizzleConfigDir, { recursive: true });
|
|
190022
190177
|
}
|
|
190023
190178
|
const storeJson = generateStoreJson(postgresInstances);
|
|
190024
190179
|
const storeJsonPath = path15.join(drizzleConfigDir, "store.json");
|
|
190025
|
-
|
|
190180
|
+
fs19.writeFileSync(storeJsonPath, JSON.stringify(storeJson, null, 2));
|
|
190026
190181
|
writeLog("drizzle-gateway", `Starting Drizzle Gateway`);
|
|
190027
190182
|
writeLog("drizzle-gateway", `STORE_PATH: ${drizzleConfigDir}`);
|
|
190028
190183
|
writeLog("drizzle-gateway", `PORT: ${port}`);
|
|
@@ -190124,16 +190279,16 @@ function detectSyncDatabases(config) {
|
|
|
190124
190279
|
}
|
|
190125
190280
|
|
|
190126
190281
|
// src/lib/dev/reshape-watcher.ts
|
|
190127
|
-
import * as
|
|
190282
|
+
import * as fs20 from "fs";
|
|
190128
190283
|
import * as path16 from "path";
|
|
190129
190284
|
import { spawnSync } from "child_process";
|
|
190130
190285
|
function getMigrationFiles(dir, log) {
|
|
190131
190286
|
log(`Scanning migrations directory: ${dir}`);
|
|
190132
|
-
if (!
|
|
190287
|
+
if (!fs20.existsSync(dir)) {
|
|
190133
190288
|
log(`Migrations directory does not exist: ${dir}`);
|
|
190134
190289
|
return [];
|
|
190135
190290
|
}
|
|
190136
|
-
const files =
|
|
190291
|
+
const files = fs20.readdirSync(dir);
|
|
190137
190292
|
log(`Found ${files.length} files in directory`);
|
|
190138
190293
|
const tomlFiles = files.filter((f) => f.endsWith(".toml")).sort((a, b) => a.localeCompare(b));
|
|
190139
190294
|
log(`Found ${tomlFiles.length} .toml migration files: ${tomlFiles.join(", ") || "(none)"}`);
|
|
@@ -190183,7 +190338,7 @@ function runReshape(args, databaseUrl, migrationsDir, reshapeBinaryPath, log) {
|
|
|
190183
190338
|
}
|
|
190184
190339
|
function makeReadOnly(filePath, log) {
|
|
190185
190340
|
try {
|
|
190186
|
-
|
|
190341
|
+
fs20.chmodSync(filePath, 292);
|
|
190187
190342
|
log(`Set file permissions to read-only (444): ${filePath}`);
|
|
190188
190343
|
} catch (err) {
|
|
190189
190344
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -190269,9 +190424,9 @@ function createReshapeWatcher(options2) {
|
|
|
190269
190424
|
};
|
|
190270
190425
|
const startWatching = () => {
|
|
190271
190426
|
log(`Starting file watcher for migrations directory...`);
|
|
190272
|
-
if (!
|
|
190427
|
+
if (!fs20.existsSync(migrationsDir)) {
|
|
190273
190428
|
log(`Migrations directory does not exist, creating: ${migrationsDir}`);
|
|
190274
|
-
|
|
190429
|
+
fs20.mkdirSync(migrationsDir, { recursive: true });
|
|
190275
190430
|
}
|
|
190276
190431
|
log(`Watching directory: ${migrationsDir}`);
|
|
190277
190432
|
watcher = chokidar_default.watch(migrationsDir, {
|
|
@@ -190406,7 +190561,7 @@ function createReshapeWatcher(options2) {
|
|
|
190406
190561
|
}
|
|
190407
190562
|
|
|
190408
190563
|
// src/lib/dev/temporal-manager.ts
|
|
190409
|
-
import * as
|
|
190564
|
+
import * as fs21 from "fs";
|
|
190410
190565
|
import * as path17 from "path";
|
|
190411
190566
|
import * as net4 from "net";
|
|
190412
190567
|
import { spawn as spawn5 } from "child_process";
|
|
@@ -190414,8 +190569,8 @@ async function startTemporalDevServer(temporals, grpcPort, uiPort, dataDir, onPr
|
|
|
190414
190569
|
const binary = await ensureBinary(temporalBinary, void 0, onProgress);
|
|
190415
190570
|
const dbPath = path17.join(process.cwd(), dataDir, "temporal.db");
|
|
190416
190571
|
const dbDir = path17.dirname(dbPath);
|
|
190417
|
-
if (!
|
|
190418
|
-
|
|
190572
|
+
if (!fs21.existsSync(dbDir)) {
|
|
190573
|
+
fs21.mkdirSync(dbDir, { recursive: true });
|
|
190419
190574
|
}
|
|
190420
190575
|
const host = "127.0.0.1";
|
|
190421
190576
|
const namespaceArgs = temporals.flatMap((t) => ["--namespace", t.name]);
|
|
@@ -190744,7 +190899,7 @@ function watchConfigFile(configPath, debounceMs, onChange) {
|
|
|
190744
190899
|
}
|
|
190745
190900
|
|
|
190746
190901
|
// src/lib/dev/subdomain-generator.ts
|
|
190747
|
-
import * as
|
|
190902
|
+
import * as fs22 from "fs";
|
|
190748
190903
|
import * as path18 from "path";
|
|
190749
190904
|
import { generateSlug } from "random-word-slugs";
|
|
190750
190905
|
var StableSubdomainAllocator = class {
|
|
@@ -190757,11 +190912,11 @@ var StableSubdomainAllocator = class {
|
|
|
190757
190912
|
this.loadTunnels();
|
|
190758
190913
|
}
|
|
190759
190914
|
loadTunnels() {
|
|
190760
|
-
if (!
|
|
190915
|
+
if (!fs22.existsSync(this.tunnelsFilePath)) {
|
|
190761
190916
|
return;
|
|
190762
190917
|
}
|
|
190763
190918
|
try {
|
|
190764
|
-
const content =
|
|
190919
|
+
const content = fs22.readFileSync(this.tunnelsFilePath, "utf-8");
|
|
190765
190920
|
const data = JSON.parse(content);
|
|
190766
190921
|
if (data.version === 1 && data.baseSlug) {
|
|
190767
190922
|
this.baseSlug = data.baseSlug;
|
|
@@ -190771,14 +190926,14 @@ var StableSubdomainAllocator = class {
|
|
|
190771
190926
|
}
|
|
190772
190927
|
}
|
|
190773
190928
|
saveTunnels() {
|
|
190774
|
-
if (!
|
|
190775
|
-
|
|
190929
|
+
if (!fs22.existsSync(this.tunnelsDir)) {
|
|
190930
|
+
fs22.mkdirSync(this.tunnelsDir, { recursive: true });
|
|
190776
190931
|
}
|
|
190777
190932
|
const data = {
|
|
190778
190933
|
version: 1,
|
|
190779
190934
|
baseSlug: this.baseSlug
|
|
190780
190935
|
};
|
|
190781
|
-
|
|
190936
|
+
fs22.writeFileSync(this.tunnelsFilePath, JSON.stringify(data, null, 2));
|
|
190782
190937
|
}
|
|
190783
190938
|
generateBaseSlug() {
|
|
190784
190939
|
return generateSlug(2, {
|
|
@@ -190934,9 +191089,9 @@ async function startTunnel(serviceName, endpointName, port, subdomain, callbacks
|
|
|
190934
191089
|
}
|
|
190935
191090
|
|
|
190936
191091
|
// src/lib/dev/proxy-registry.ts
|
|
190937
|
-
import * as
|
|
191092
|
+
import * as fs23 from "fs";
|
|
190938
191093
|
import * as path19 from "path";
|
|
190939
|
-
import * as
|
|
191094
|
+
import * as os10 from "os";
|
|
190940
191095
|
import * as net6 from "net";
|
|
190941
191096
|
var ProxyRegistryManager = class {
|
|
190942
191097
|
proxyDir;
|
|
@@ -190946,14 +191101,14 @@ var ProxyRegistryManager = class {
|
|
|
190946
191101
|
isOwner = false;
|
|
190947
191102
|
registryWatcher = null;
|
|
190948
191103
|
constructor() {
|
|
190949
|
-
this.proxyDir = path19.join(
|
|
191104
|
+
this.proxyDir = path19.join(os10.homedir(), ".specific", "proxy");
|
|
190950
191105
|
this.ownerPath = path19.join(this.proxyDir, "owner.json");
|
|
190951
191106
|
this.registryPath = path19.join(this.proxyDir, "registry.json");
|
|
190952
191107
|
this.lockPath = path19.join(this.proxyDir, "registry.lock");
|
|
190953
191108
|
}
|
|
190954
191109
|
ensureProxyDir() {
|
|
190955
|
-
if (!
|
|
190956
|
-
|
|
191110
|
+
if (!fs23.existsSync(this.proxyDir)) {
|
|
191111
|
+
fs23.mkdirSync(this.proxyDir, { recursive: true });
|
|
190957
191112
|
}
|
|
190958
191113
|
}
|
|
190959
191114
|
isProcessRunning(pid) {
|
|
@@ -191010,15 +191165,15 @@ var ProxyRegistryManager = class {
|
|
|
191010
191165
|
const startTime = Date.now();
|
|
191011
191166
|
while (Date.now() - startTime < timeoutMs) {
|
|
191012
191167
|
try {
|
|
191013
|
-
const fd =
|
|
191168
|
+
const fd = fs23.openSync(
|
|
191014
191169
|
this.lockPath,
|
|
191015
|
-
|
|
191170
|
+
fs23.constants.O_CREAT | fs23.constants.O_EXCL | fs23.constants.O_WRONLY
|
|
191016
191171
|
);
|
|
191017
|
-
|
|
191018
|
-
|
|
191172
|
+
fs23.writeSync(fd, String(process.pid));
|
|
191173
|
+
fs23.closeSync(fd);
|
|
191019
191174
|
return () => {
|
|
191020
191175
|
try {
|
|
191021
|
-
|
|
191176
|
+
fs23.unlinkSync(this.lockPath);
|
|
191022
191177
|
} catch {
|
|
191023
191178
|
}
|
|
191024
191179
|
};
|
|
@@ -191027,16 +191182,16 @@ var ProxyRegistryManager = class {
|
|
|
191027
191182
|
if (err.code === "EEXIST") {
|
|
191028
191183
|
try {
|
|
191029
191184
|
const lockPid = parseInt(
|
|
191030
|
-
|
|
191185
|
+
fs23.readFileSync(this.lockPath, "utf-8").trim(),
|
|
191031
191186
|
10
|
|
191032
191187
|
);
|
|
191033
191188
|
if (!this.isProcessRunning(lockPid)) {
|
|
191034
|
-
|
|
191189
|
+
fs23.unlinkSync(this.lockPath);
|
|
191035
191190
|
continue;
|
|
191036
191191
|
}
|
|
191037
191192
|
} catch {
|
|
191038
191193
|
try {
|
|
191039
|
-
|
|
191194
|
+
fs23.unlinkSync(this.lockPath);
|
|
191040
191195
|
} catch {
|
|
191041
191196
|
}
|
|
191042
191197
|
continue;
|
|
@@ -191056,8 +191211,8 @@ var ProxyRegistryManager = class {
|
|
|
191056
191211
|
async claimProxyOwnership(key) {
|
|
191057
191212
|
const releaseLock = await this.acquireLock();
|
|
191058
191213
|
try {
|
|
191059
|
-
if (
|
|
191060
|
-
const content =
|
|
191214
|
+
if (fs23.existsSync(this.ownerPath)) {
|
|
191215
|
+
const content = fs23.readFileSync(this.ownerPath, "utf-8");
|
|
191061
191216
|
const ownerFile2 = JSON.parse(content);
|
|
191062
191217
|
if (await this.isProxyOwnerHealthy(ownerFile2.owner.pid)) {
|
|
191063
191218
|
return false;
|
|
@@ -191087,11 +191242,11 @@ var ProxyRegistryManager = class {
|
|
|
191087
191242
|
}
|
|
191088
191243
|
const releaseLock = await this.acquireLock();
|
|
191089
191244
|
try {
|
|
191090
|
-
if (
|
|
191091
|
-
const content =
|
|
191245
|
+
if (fs23.existsSync(this.ownerPath)) {
|
|
191246
|
+
const content = fs23.readFileSync(this.ownerPath, "utf-8");
|
|
191092
191247
|
const ownerFile = JSON.parse(content);
|
|
191093
191248
|
if (ownerFile.owner.pid === process.pid) {
|
|
191094
|
-
|
|
191249
|
+
fs23.unlinkSync(this.ownerPath);
|
|
191095
191250
|
}
|
|
191096
191251
|
}
|
|
191097
191252
|
this.isOwner = false;
|
|
@@ -191103,12 +191258,12 @@ var ProxyRegistryManager = class {
|
|
|
191103
191258
|
* Get the current proxy owner.
|
|
191104
191259
|
*/
|
|
191105
191260
|
async getProxyOwner() {
|
|
191106
|
-
if (!
|
|
191261
|
+
if (!fs23.existsSync(this.ownerPath)) {
|
|
191107
191262
|
return null;
|
|
191108
191263
|
}
|
|
191109
191264
|
const releaseLock = await this.acquireLock();
|
|
191110
191265
|
try {
|
|
191111
|
-
const content =
|
|
191266
|
+
const content = fs23.readFileSync(this.ownerPath, "utf-8");
|
|
191112
191267
|
const ownerFile = JSON.parse(content);
|
|
191113
191268
|
if (!await this.isProxyOwnerHealthy(ownerFile.owner.pid)) {
|
|
191114
191269
|
return null;
|
|
@@ -191205,7 +191360,7 @@ var ProxyRegistryManager = class {
|
|
|
191205
191360
|
*/
|
|
191206
191361
|
watchRegistry(onChange) {
|
|
191207
191362
|
this.ensureProxyDir();
|
|
191208
|
-
if (!
|
|
191363
|
+
if (!fs23.existsSync(this.registryPath)) {
|
|
191209
191364
|
const emptyRegistry = {
|
|
191210
191365
|
version: 1,
|
|
191211
191366
|
keys: {},
|
|
@@ -191248,13 +191403,13 @@ var ProxyRegistryManager = class {
|
|
|
191248
191403
|
async attemptElection(key) {
|
|
191249
191404
|
const releaseLock = await this.acquireLock();
|
|
191250
191405
|
try {
|
|
191251
|
-
if (
|
|
191252
|
-
const content =
|
|
191406
|
+
if (fs23.existsSync(this.ownerPath)) {
|
|
191407
|
+
const content = fs23.readFileSync(this.ownerPath, "utf-8");
|
|
191253
191408
|
const ownerFile2 = JSON.parse(content);
|
|
191254
191409
|
if (await this.isProxyOwnerHealthy(ownerFile2.owner.pid)) {
|
|
191255
191410
|
return false;
|
|
191256
191411
|
}
|
|
191257
|
-
|
|
191412
|
+
fs23.unlinkSync(this.ownerPath);
|
|
191258
191413
|
}
|
|
191259
191414
|
const ownerFile = {
|
|
191260
191415
|
version: 1,
|
|
@@ -191272,7 +191427,7 @@ var ProxyRegistryManager = class {
|
|
|
191272
191427
|
}
|
|
191273
191428
|
}
|
|
191274
191429
|
readRegistry() {
|
|
191275
|
-
if (!
|
|
191430
|
+
if (!fs23.existsSync(this.registryPath)) {
|
|
191276
191431
|
return {
|
|
191277
191432
|
version: 1,
|
|
191278
191433
|
keys: {},
|
|
@@ -191280,7 +191435,7 @@ var ProxyRegistryManager = class {
|
|
|
191280
191435
|
};
|
|
191281
191436
|
}
|
|
191282
191437
|
try {
|
|
191283
|
-
const content =
|
|
191438
|
+
const content = fs23.readFileSync(this.registryPath, "utf-8");
|
|
191284
191439
|
return JSON.parse(content);
|
|
191285
191440
|
} catch {
|
|
191286
191441
|
return {
|
|
@@ -191293,8 +191448,8 @@ var ProxyRegistryManager = class {
|
|
|
191293
191448
|
writeFileAtomic(filePath, data) {
|
|
191294
191449
|
this.ensureProxyDir();
|
|
191295
191450
|
const tmpPath = filePath + ".tmp";
|
|
191296
|
-
|
|
191297
|
-
|
|
191451
|
+
fs23.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
|
|
191452
|
+
fs23.renameSync(tmpPath, filePath);
|
|
191298
191453
|
}
|
|
191299
191454
|
};
|
|
191300
191455
|
|
|
@@ -191401,9 +191556,17 @@ function DevUI({ instanceKey, tunnelEnabled }) {
|
|
|
191401
191556
|
const { exit } = useApp2();
|
|
191402
191557
|
const [state, setState] = useState5(() => {
|
|
191403
191558
|
const setupDone = tunnelEnabled || !systemSetupNeeded();
|
|
191559
|
+
let setupStep;
|
|
191560
|
+
if (!setupDone) {
|
|
191561
|
+
if (os11.platform() === "darwin") {
|
|
191562
|
+
setupStep = dnsSetupNeeded() ? "setup-dns" : "setup-tls";
|
|
191563
|
+
} else {
|
|
191564
|
+
setupStep = "setup-combined";
|
|
191565
|
+
}
|
|
191566
|
+
}
|
|
191404
191567
|
return {
|
|
191405
|
-
status: setupDone ? "loading" : "
|
|
191406
|
-
...
|
|
191568
|
+
status: setupDone ? "loading" : "setup",
|
|
191569
|
+
...setupStep ? { setupStep, setupStepPhase: "prompt" } : {},
|
|
191407
191570
|
resources: /* @__PURE__ */ new Map(),
|
|
191408
191571
|
resourceStatus: /* @__PURE__ */ new Map(),
|
|
191409
191572
|
services: [],
|
|
@@ -191415,28 +191578,41 @@ function DevUI({ instanceKey, tunnelEnabled }) {
|
|
|
191415
191578
|
};
|
|
191416
191579
|
});
|
|
191417
191580
|
useEffect3(() => {
|
|
191418
|
-
if (state.status
|
|
191419
|
-
installSystemConfig();
|
|
191420
|
-
}
|
|
191421
|
-
}, [state.status, state.caInstallPhase]);
|
|
191422
|
-
useInput4((_input, key) => {
|
|
191423
|
-
if (key.return) {
|
|
191424
|
-
setState((s) => ({ ...s, caInstallPhase: "installing" }));
|
|
191425
|
-
}
|
|
191426
|
-
}, { isActive: state.status === "installing-ca" && state.caInstallPhase === "prompt" });
|
|
191427
|
-
async function installSystemConfig() {
|
|
191581
|
+
if (state.status !== "setup" || state.setupStepPhase !== "installing") return;
|
|
191428
191582
|
try {
|
|
191429
|
-
|
|
191430
|
-
|
|
191431
|
-
|
|
191583
|
+
if (state.setupStep === "setup-dns") {
|
|
191584
|
+
ensureCAGenerated();
|
|
191585
|
+
performDnsSetup();
|
|
191586
|
+
} else if (state.setupStep === "setup-tls") {
|
|
191587
|
+
ensureCAGenerated();
|
|
191588
|
+
performTlsTrust();
|
|
191589
|
+
} else if (state.setupStep === "setup-combined") {
|
|
191590
|
+
performSystemSetup();
|
|
191591
|
+
}
|
|
191592
|
+
const nextStep = state.setupStep === "setup-dns" && tlsSetupNeeded() ? "setup-tls" : void 0;
|
|
191593
|
+
if (nextStep) {
|
|
191594
|
+
setState((s) => ({
|
|
191595
|
+
...s,
|
|
191596
|
+
setupStep: nextStep,
|
|
191597
|
+
setupStepPhase: "prompt"
|
|
191598
|
+
}));
|
|
191599
|
+
} else {
|
|
191600
|
+
setState((s) => ({ ...s, status: "loading", setupStepPhase: "done" }));
|
|
191601
|
+
setReadyToStart(true);
|
|
191602
|
+
}
|
|
191432
191603
|
} catch (err) {
|
|
191433
191604
|
setState((s) => ({
|
|
191434
191605
|
...s,
|
|
191435
|
-
|
|
191436
|
-
|
|
191606
|
+
setupStepPhase: "error",
|
|
191607
|
+
setupError: err instanceof Error ? err.message : String(err)
|
|
191437
191608
|
}));
|
|
191438
191609
|
}
|
|
191439
|
-
}
|
|
191610
|
+
}, [state.status, state.setupStepPhase]);
|
|
191611
|
+
useInput4((_input, key) => {
|
|
191612
|
+
if (state.status === "setup" && state.setupStepPhase === "prompt" && key.return) {
|
|
191613
|
+
setState((s) => ({ ...s, setupStepPhase: "installing" }));
|
|
191614
|
+
}
|
|
191615
|
+
});
|
|
191440
191616
|
const shuttingDown = useRef(false);
|
|
191441
191617
|
const startTimeRef = useRef(null);
|
|
191442
191618
|
const stateManagerRef = useRef(null);
|
|
@@ -191617,7 +191793,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
|
|
|
191617
191793
|
const configPath = path20.join(process.cwd(), "specific.hcl");
|
|
191618
191794
|
const watcher = watchConfigFile(configPath, 1e3, () => {
|
|
191619
191795
|
try {
|
|
191620
|
-
const hcl =
|
|
191796
|
+
const hcl = fs24.readFileSync(configPath, "utf-8");
|
|
191621
191797
|
parseConfig(hcl).then(() => {
|
|
191622
191798
|
triggerReload();
|
|
191623
191799
|
}).catch((err) => {
|
|
@@ -191743,7 +191919,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
|
|
|
191743
191919
|
return;
|
|
191744
191920
|
}
|
|
191745
191921
|
const configPath = path20.join(process.cwd(), "specific.hcl");
|
|
191746
|
-
if (!
|
|
191922
|
+
if (!fs24.existsSync(configPath)) {
|
|
191747
191923
|
writeLog("system", "Waiting for specific.hcl to appear");
|
|
191748
191924
|
setState((s) => ({
|
|
191749
191925
|
...s,
|
|
@@ -191761,7 +191937,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
|
|
|
191761
191937
|
return;
|
|
191762
191938
|
}
|
|
191763
191939
|
let config2;
|
|
191764
|
-
const hcl =
|
|
191940
|
+
const hcl = fs24.readFileSync(configPath, "utf-8");
|
|
191765
191941
|
try {
|
|
191766
191942
|
config2 = await parseConfig(hcl);
|
|
191767
191943
|
} catch (err) {
|
|
@@ -192088,7 +192264,7 @@ Add them to the config block in specific.local`);
|
|
|
192088
192264
|
if (service.volumes) {
|
|
192089
192265
|
for (const vol of service.volumes) {
|
|
192090
192266
|
const volumeDir = path20.resolve(`.specific/keys/${instanceKey}/data/volumes/${service.name}/${vol.name}`);
|
|
192091
|
-
|
|
192267
|
+
fs24.mkdirSync(volumeDir, { recursive: true });
|
|
192092
192268
|
volumePaths.set(vol.name, volumeDir);
|
|
192093
192269
|
}
|
|
192094
192270
|
}
|
|
@@ -192144,7 +192320,7 @@ Add them to the config block in specific.local`);
|
|
|
192144
192320
|
if (service.volumes) {
|
|
192145
192321
|
for (const vol of service.volumes) {
|
|
192146
192322
|
const volumeDir = path20.resolve(`.specific/keys/${instanceKey}/data/volumes/${service.name}/${vol.name}`);
|
|
192147
|
-
|
|
192323
|
+
fs24.mkdirSync(volumeDir, { recursive: true });
|
|
192148
192324
|
volumePaths.set(vol.name, volumeDir);
|
|
192149
192325
|
}
|
|
192150
192326
|
}
|
|
@@ -192437,15 +192613,17 @@ Add them to the config block in specific.local`);
|
|
|
192437
192613
|
});
|
|
192438
192614
|
};
|
|
192439
192615
|
}, [reloadTrigger, readyToStart, instanceKey]);
|
|
192440
|
-
if (state.status === "
|
|
192441
|
-
if (state.
|
|
192442
|
-
|
|
192616
|
+
if (state.status === "setup") {
|
|
192617
|
+
if (state.setupStepPhase === "prompt") {
|
|
192618
|
+
const isDns = state.setupStep === "setup-dns";
|
|
192619
|
+
const isTls = state.setupStep === "setup-tls";
|
|
192620
|
+
return /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, "Configure development environment"), /* @__PURE__ */ React6.createElement(Text6, null, " "), (isDns || state.setupStep === "setup-combined") && /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, null, "We need to do a one-time setup for all your Specific projects."), /* @__PURE__ */ React6.createElement(Text6, null, "This is so we can run your apps locally with secure URLs:"), /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "green" }, "https://your-app.spcf.localhost"), /* @__PURE__ */ React6.createElement(Text6, null, " "), /* @__PURE__ */ React6.createElement(Text6, null, "You will be prompted to authorize with your password.")), isTls && /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, null, "We need to trust a development certificate for HTTPS."), /* @__PURE__ */ React6.createElement(Text6, null, " "), /* @__PURE__ */ React6.createElement(Text6, null, "You will be prompted to authorize.")), /* @__PURE__ */ React6.createElement(Text6, null, " "), /* @__PURE__ */ React6.createElement(Text6, null, "Press ", /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, "Enter"), " to continue."));
|
|
192443
192621
|
}
|
|
192444
|
-
if (state.
|
|
192622
|
+
if (state.setupStepPhase === "installing") {
|
|
192445
192623
|
return /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, "Configure development environment"), /* @__PURE__ */ React6.createElement(Text6, null, " "), /* @__PURE__ */ React6.createElement(Text6, null, "Awaiting authorization\u2026"));
|
|
192446
192624
|
}
|
|
192447
|
-
if (state.
|
|
192448
|
-
return /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Text6, { color: "red" }, "Setup failed: ", state.
|
|
192625
|
+
if (state.setupStepPhase === "error") {
|
|
192626
|
+
return /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Text6, { color: "red" }, "Setup failed: ", state.setupError));
|
|
192449
192627
|
}
|
|
192450
192628
|
}
|
|
192451
192629
|
if (state.status === "loading") {
|
|
@@ -192724,12 +192902,12 @@ init_open();
|
|
|
192724
192902
|
import React7, { useState as useState6, useEffect as useEffect4, useCallback } from "react";
|
|
192725
192903
|
import { render as render5, Text as Text7, Box as Box7, useApp as useApp3, useInput as useInput5 } from "ink";
|
|
192726
192904
|
import Spinner5 from "ink-spinner";
|
|
192727
|
-
import * as
|
|
192905
|
+
import * as fs26 from "fs";
|
|
192728
192906
|
import * as path23 from "path";
|
|
192729
192907
|
|
|
192730
192908
|
// src/lib/tarball/create.ts
|
|
192731
192909
|
import { execSync as execSync4 } from "child_process";
|
|
192732
|
-
import * as
|
|
192910
|
+
import * as fs25 from "fs";
|
|
192733
192911
|
import * as path22 from "path";
|
|
192734
192912
|
import { createTarPacker, createEntryItemGenerator } from "tar-vern";
|
|
192735
192913
|
function isInsideGitRepository(dir) {
|
|
@@ -192787,7 +192965,7 @@ var EXCLUDED_DIRS = [
|
|
|
192787
192965
|
];
|
|
192788
192966
|
async function collectPaths(baseDir, currentDir, exclude) {
|
|
192789
192967
|
const results = [];
|
|
192790
|
-
const entries = await
|
|
192968
|
+
const entries = await fs25.promises.readdir(currentDir, { withFileTypes: true });
|
|
192791
192969
|
for (const entry of entries) {
|
|
192792
192970
|
const fullPath = path22.join(currentDir, entry.name);
|
|
192793
192971
|
const relativePath = path22.relative(baseDir, fullPath);
|
|
@@ -192806,7 +192984,7 @@ async function collectPaths(baseDir, currentDir, exclude) {
|
|
|
192806
192984
|
async function createTarArchive(projectDir) {
|
|
192807
192985
|
writeLog("tarball", "Creating tarball using tar-vern (non-git project)");
|
|
192808
192986
|
const configPath = path22.join(projectDir, "specific.hcl");
|
|
192809
|
-
if (!
|
|
192987
|
+
if (!fs25.existsSync(configPath)) {
|
|
192810
192988
|
throw new Error("specific.hcl not found in project directory");
|
|
192811
192989
|
}
|
|
192812
192990
|
const relativePaths = await collectPaths(projectDir, projectDir, EXCLUDED_DIRS);
|
|
@@ -193728,12 +193906,12 @@ function DeployUI({ environment, config }) {
|
|
|
193728
193906
|
}
|
|
193729
193907
|
async function deployCommand(environment) {
|
|
193730
193908
|
const configPath = path23.join(process.cwd(), "specific.hcl");
|
|
193731
|
-
if (!
|
|
193909
|
+
if (!fs26.existsSync(configPath)) {
|
|
193732
193910
|
console.error("Error: No specific.hcl found in current directory");
|
|
193733
193911
|
process.exit(1);
|
|
193734
193912
|
}
|
|
193735
193913
|
let config;
|
|
193736
|
-
const hcl =
|
|
193914
|
+
const hcl = fs26.readFileSync(configPath, "utf-8");
|
|
193737
193915
|
try {
|
|
193738
193916
|
config = await parseConfig(hcl);
|
|
193739
193917
|
} catch (err) {
|
|
@@ -193754,8 +193932,21 @@ async function deployCommand(environment) {
|
|
|
193754
193932
|
|
|
193755
193933
|
// src/commands/exec.tsx
|
|
193756
193934
|
import { spawn as spawn6 } from "child_process";
|
|
193757
|
-
import * as
|
|
193935
|
+
import * as fs27 from "fs";
|
|
193758
193936
|
import * as path24 from "path";
|
|
193937
|
+
function startSpinner(text) {
|
|
193938
|
+
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
193939
|
+
let i = 0;
|
|
193940
|
+
const interval = setInterval(() => {
|
|
193941
|
+
process.stderr.write(`\r\x1B[2K\x1B[90m${frames[i++ % frames.length]} ${text}\x1B[0m`);
|
|
193942
|
+
}, 80);
|
|
193943
|
+
return {
|
|
193944
|
+
stop() {
|
|
193945
|
+
clearInterval(interval);
|
|
193946
|
+
process.stderr.write(`\r\x1B[2K`);
|
|
193947
|
+
}
|
|
193948
|
+
};
|
|
193949
|
+
}
|
|
193759
193950
|
async function execCommand(serviceName, command, instanceKey = "default") {
|
|
193760
193951
|
if (command.length === 0) {
|
|
193761
193952
|
console.error(
|
|
@@ -193784,12 +193975,12 @@ async function execCommand(serviceName, command, instanceKey = "default") {
|
|
|
193784
193975
|
}
|
|
193785
193976
|
};
|
|
193786
193977
|
const configPath = path24.join(process.cwd(), "specific.hcl");
|
|
193787
|
-
if (!
|
|
193978
|
+
if (!fs27.existsSync(configPath)) {
|
|
193788
193979
|
console.error("Error: No specific.hcl found in current directory");
|
|
193789
193980
|
process.exit(1);
|
|
193790
193981
|
}
|
|
193791
193982
|
let config;
|
|
193792
|
-
const hcl =
|
|
193983
|
+
const hcl = fs27.readFileSync(configPath, "utf-8");
|
|
193793
193984
|
try {
|
|
193794
193985
|
config = await parseConfig(hcl);
|
|
193795
193986
|
} catch (err) {
|
|
@@ -193810,13 +194001,15 @@ async function execCommand(serviceName, command, instanceKey = "default") {
|
|
|
193810
194001
|
stateManager = new InstanceStateManager(process.cwd(), instanceKey);
|
|
193811
194002
|
await stateManager.cleanStaleState();
|
|
193812
194003
|
const existingInstances = await stateManager.getExistingInstances();
|
|
194004
|
+
let spinner = null;
|
|
193813
194005
|
if (hasRequiredResources) {
|
|
193814
194006
|
if (existingInstances) {
|
|
193815
|
-
|
|
194007
|
+
spinner = startSpinner("Starting resources...");
|
|
193816
194008
|
const allRequiredNames = [...required.postgres, ...required.redis, ...required.storage];
|
|
193817
194009
|
for (const name of allRequiredNames) {
|
|
193818
194010
|
const dbState = existingInstances.databases[name];
|
|
193819
194011
|
if (!dbState) {
|
|
194012
|
+
spinner?.stop();
|
|
193820
194013
|
console.error(
|
|
193821
194014
|
`Error: Required resource "${name}" not found in running instances`
|
|
193822
194015
|
);
|
|
@@ -193844,11 +194037,12 @@ async function execCommand(serviceName, command, instanceKey = "default") {
|
|
|
193844
194037
|
});
|
|
193845
194038
|
}
|
|
193846
194039
|
} else {
|
|
193847
|
-
|
|
194040
|
+
spinner = startSpinner("Starting resources...");
|
|
193848
194041
|
try {
|
|
193849
194042
|
await stateManager.claimOwnership("exec");
|
|
193850
194043
|
ownsInstances = true;
|
|
193851
194044
|
} catch (err) {
|
|
194045
|
+
spinner?.stop();
|
|
193852
194046
|
console.error(
|
|
193853
194047
|
`Error: ${err instanceof Error ? err.message : String(err)}`
|
|
193854
194048
|
);
|
|
@@ -193867,15 +194061,14 @@ async function execCommand(serviceName, command, instanceKey = "default") {
|
|
|
193867
194061
|
stateManager,
|
|
193868
194062
|
dataDir: `.specific/keys/${instanceKey}/data`,
|
|
193869
194063
|
portAllocator: new PortAllocator(),
|
|
193870
|
-
callbacks: {
|
|
193871
|
-
log: (msg) => console.error(msg)
|
|
193872
|
-
},
|
|
194064
|
+
callbacks: {},
|
|
193873
194065
|
startElectric: true
|
|
193874
194066
|
});
|
|
193875
194067
|
resources = result.resources;
|
|
193876
194068
|
startedResources = result.startedResources;
|
|
193877
194069
|
startedElectric = result.startedElectric;
|
|
193878
194070
|
} catch (err) {
|
|
194071
|
+
spinner?.stop();
|
|
193879
194072
|
console.error(
|
|
193880
194073
|
`Error: Failed to start resources: ${err instanceof Error ? err.message : String(err)}`
|
|
193881
194074
|
);
|
|
@@ -193888,7 +194081,24 @@ async function execCommand(serviceName, command, instanceKey = "default") {
|
|
|
193888
194081
|
try {
|
|
193889
194082
|
const secrets = await prepareSecrets(config.secrets);
|
|
193890
194083
|
const configs = await prepareConfigs(config.configs);
|
|
193891
|
-
|
|
194084
|
+
const result = resolveEnvForExec(service.env, resources, secrets, configs);
|
|
194085
|
+
resolvedEnv = result.env;
|
|
194086
|
+
if (result.missingSecrets.length > 0 || result.missingConfigs.length > 0) {
|
|
194087
|
+
if (result.missingSecrets.length > 0) {
|
|
194088
|
+
console.error(`\x1B[33m\u26A0 Missing secrets (env vars will not be set):\x1B[0m`);
|
|
194089
|
+
for (const { name, envVar } of result.missingSecrets) {
|
|
194090
|
+
console.error(`\x1B[33m \u2022 ${name} \u2192 ${envVar}\x1B[0m`);
|
|
194091
|
+
}
|
|
194092
|
+
}
|
|
194093
|
+
if (result.missingConfigs.length > 0) {
|
|
194094
|
+
console.error(`\x1B[33m\u26A0 Missing configs (env vars will not be set):\x1B[0m`);
|
|
194095
|
+
for (const { name, envVar } of result.missingConfigs) {
|
|
194096
|
+
console.error(`\x1B[33m \u2022 ${name} \u2192 ${envVar}\x1B[0m`);
|
|
194097
|
+
}
|
|
194098
|
+
}
|
|
194099
|
+
console.error(`\x1B[33m Set values in specific.local to resolve.\x1B[0m`);
|
|
194100
|
+
console.error("");
|
|
194101
|
+
}
|
|
193892
194102
|
} catch (err) {
|
|
193893
194103
|
console.error(
|
|
193894
194104
|
`Error: Failed to resolve environment variables: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -193914,6 +194124,7 @@ async function execCommand(serviceName, command, instanceKey = "default") {
|
|
|
193914
194124
|
effectiveCwd = path24.resolve(process.cwd(), build.root);
|
|
193915
194125
|
}
|
|
193916
194126
|
}
|
|
194127
|
+
spinner?.stop();
|
|
193917
194128
|
child = spawn6(command[0], command.slice(1), {
|
|
193918
194129
|
cwd: effectiveCwd,
|
|
193919
194130
|
env: {
|
|
@@ -193939,7 +194150,7 @@ async function execCommand(serviceName, command, instanceKey = "default") {
|
|
|
193939
194150
|
|
|
193940
194151
|
// src/commands/psql.tsx
|
|
193941
194152
|
import { spawn as spawn7 } from "child_process";
|
|
193942
|
-
import * as
|
|
194153
|
+
import * as fs28 from "fs";
|
|
193943
194154
|
import * as path25 from "path";
|
|
193944
194155
|
async function psqlCommand(databaseName, instanceKey = "default", extraArgs = []) {
|
|
193945
194156
|
let startedResources = [];
|
|
@@ -193958,12 +194169,12 @@ async function psqlCommand(databaseName, instanceKey = "default", extraArgs = []
|
|
|
193958
194169
|
}
|
|
193959
194170
|
};
|
|
193960
194171
|
const configPath = path25.join(process.cwd(), "specific.hcl");
|
|
193961
|
-
if (!
|
|
194172
|
+
if (!fs28.existsSync(configPath)) {
|
|
193962
194173
|
console.error("Error: No specific.hcl found in current directory");
|
|
193963
194174
|
process.exit(1);
|
|
193964
194175
|
}
|
|
193965
194176
|
let config;
|
|
193966
|
-
const hcl =
|
|
194177
|
+
const hcl = fs28.readFileSync(configPath, "utf-8");
|
|
193967
194178
|
try {
|
|
193968
194179
|
config = await parseConfig(hcl);
|
|
193969
194180
|
} catch (err) {
|
|
@@ -194087,7 +194298,7 @@ async function psqlCommand(databaseName, instanceKey = "default", extraArgs = []
|
|
|
194087
194298
|
|
|
194088
194299
|
// src/commands/reshape.tsx
|
|
194089
194300
|
import { spawn as spawn8 } from "child_process";
|
|
194090
|
-
import * as
|
|
194301
|
+
import * as fs29 from "fs";
|
|
194091
194302
|
import * as path26 from "path";
|
|
194092
194303
|
var VALID_ACTIONS = ["start", "complete", "status", "abort", "check"];
|
|
194093
194304
|
var MIGRATION_SUBCOMMANDS = ["start", "complete", "abort"];
|
|
@@ -194105,8 +194316,8 @@ async function reshapeCommand(action, databaseName, instanceKey = "default") {
|
|
|
194105
194316
|
let migrationsDir = "migrations";
|
|
194106
194317
|
let targetDb;
|
|
194107
194318
|
try {
|
|
194108
|
-
if (
|
|
194109
|
-
const configContent =
|
|
194319
|
+
if (fs29.existsSync(configPath)) {
|
|
194320
|
+
const configContent = fs29.readFileSync(configPath, "utf-8");
|
|
194110
194321
|
config = await parseConfig(configContent);
|
|
194111
194322
|
if (databaseName) {
|
|
194112
194323
|
const postgresConfig = config.postgres.find((p) => p.name === databaseName);
|
|
@@ -194242,7 +194453,7 @@ async function reshapeCommand(action, databaseName, instanceKey = "default") {
|
|
|
194242
194453
|
const reshapeArgs = isMigrationSubcommand ? ["migration", action] : [action];
|
|
194243
194454
|
const fullMigrationsPath = path26.join(process.cwd(), migrationsDir);
|
|
194244
194455
|
if (action === "check" || action === "start") {
|
|
194245
|
-
if (
|
|
194456
|
+
if (fs29.existsSync(fullMigrationsPath)) {
|
|
194246
194457
|
reshapeArgs.push("--dirs", fullMigrationsPath);
|
|
194247
194458
|
} else if (action === "check") {
|
|
194248
194459
|
console.error(`Error: Migrations directory not found: ${fullMigrationsPath}`);
|
|
@@ -194292,7 +194503,7 @@ async function reshapeCommand(action, databaseName, instanceKey = "default") {
|
|
|
194292
194503
|
import React8, { useState as useState7, useEffect as useEffect5 } from "react";
|
|
194293
194504
|
import { render as render6, Text as Text8, Box as Box8 } from "ink";
|
|
194294
194505
|
import Spinner6 from "ink-spinner";
|
|
194295
|
-
import * as
|
|
194506
|
+
import * as fs30 from "fs";
|
|
194296
194507
|
import * as path27 from "path";
|
|
194297
194508
|
function CleanUI({ instanceKey }) {
|
|
194298
194509
|
const [state, setState] = useState7({ status: "checking" });
|
|
@@ -194300,13 +194511,13 @@ function CleanUI({ instanceKey }) {
|
|
|
194300
194511
|
async function clean() {
|
|
194301
194512
|
const projectRoot = process.cwd();
|
|
194302
194513
|
const specificDir = path27.join(projectRoot, ".specific");
|
|
194303
|
-
if (!
|
|
194514
|
+
if (!fs30.existsSync(specificDir)) {
|
|
194304
194515
|
setState({ status: "nothing" });
|
|
194305
194516
|
return;
|
|
194306
194517
|
}
|
|
194307
194518
|
if (instanceKey) {
|
|
194308
194519
|
const keyDir = path27.join(specificDir, "keys", instanceKey);
|
|
194309
|
-
if (!
|
|
194520
|
+
if (!fs30.existsSync(keyDir)) {
|
|
194310
194521
|
setState({ status: "nothing" });
|
|
194311
194522
|
return;
|
|
194312
194523
|
}
|
|
@@ -194322,7 +194533,7 @@ function CleanUI({ instanceKey }) {
|
|
|
194322
194533
|
await stateManager.cleanStaleState();
|
|
194323
194534
|
setState({ status: "cleaning" });
|
|
194324
194535
|
try {
|
|
194325
|
-
|
|
194536
|
+
fs30.rmSync(keyDir, { recursive: true, force: true });
|
|
194326
194537
|
setState({ status: "success" });
|
|
194327
194538
|
} catch (err) {
|
|
194328
194539
|
setState({
|
|
@@ -194332,12 +194543,12 @@ function CleanUI({ instanceKey }) {
|
|
|
194332
194543
|
}
|
|
194333
194544
|
} else {
|
|
194334
194545
|
const keysDir = path27.join(specificDir, "keys");
|
|
194335
|
-
if (!
|
|
194546
|
+
if (!fs30.existsSync(keysDir)) {
|
|
194336
194547
|
setState({ status: "nothing" });
|
|
194337
194548
|
return;
|
|
194338
194549
|
}
|
|
194339
|
-
const keys =
|
|
194340
|
-
(f) =>
|
|
194550
|
+
const keys = fs30.readdirSync(keysDir).filter(
|
|
194551
|
+
(f) => fs30.statSync(path27.join(keysDir, f)).isDirectory()
|
|
194341
194552
|
);
|
|
194342
194553
|
for (const key of keys) {
|
|
194343
194554
|
const stateManager2 = new InstanceStateManager(projectRoot, key);
|
|
@@ -194361,7 +194572,7 @@ function CleanUI({ instanceKey }) {
|
|
|
194361
194572
|
}
|
|
194362
194573
|
setState({ status: "cleaning" });
|
|
194363
194574
|
try {
|
|
194364
|
-
|
|
194575
|
+
fs30.rmSync(keysDir, { recursive: true, force: true });
|
|
194365
194576
|
setState({ status: "success" });
|
|
194366
194577
|
} catch (err) {
|
|
194367
194578
|
setState({
|
|
@@ -194526,7 +194737,7 @@ import { render as render9, Text as Text11, Box as Box10, useApp as useApp6 } fr
|
|
|
194526
194737
|
import Spinner7 from "ink-spinner";
|
|
194527
194738
|
|
|
194528
194739
|
// src/lib/update.ts
|
|
194529
|
-
import * as
|
|
194740
|
+
import * as fs31 from "fs";
|
|
194530
194741
|
import * as path28 from "path";
|
|
194531
194742
|
var BINARIES_BASE_URL = "https://binaries.specific.dev/cli";
|
|
194532
194743
|
function compareVersions(a, b) {
|
|
@@ -194541,7 +194752,7 @@ function compareVersions(a, b) {
|
|
|
194541
194752
|
return 0;
|
|
194542
194753
|
}
|
|
194543
194754
|
async function checkForUpdate() {
|
|
194544
|
-
const currentVersion = "0.1.
|
|
194755
|
+
const currentVersion = "0.1.82";
|
|
194545
194756
|
const response = await fetch(`${BINARIES_BASE_URL}/latest?t=${Date.now()}`);
|
|
194546
194757
|
if (!response.ok) {
|
|
194547
194758
|
throw new Error(`Failed to check for updates: HTTP ${response.status}`);
|
|
@@ -194557,7 +194768,7 @@ function isBinaryWritable() {
|
|
|
194557
194768
|
const binaryPath = getCurrentBinaryPath();
|
|
194558
194769
|
const dir = path28.dirname(binaryPath);
|
|
194559
194770
|
try {
|
|
194560
|
-
|
|
194771
|
+
fs31.accessSync(dir, fs31.constants.W_OK);
|
|
194561
194772
|
return true;
|
|
194562
194773
|
} catch {
|
|
194563
194774
|
return false;
|
|
@@ -194568,21 +194779,21 @@ async function performUpdate(version, onProgress) {
|
|
|
194568
194779
|
const binaryDir = path28.dirname(binaryPath);
|
|
194569
194780
|
const tempPath = path28.join(binaryDir, `.specific-update-${process.pid}`);
|
|
194570
194781
|
try {
|
|
194571
|
-
const { platform:
|
|
194572
|
-
const url = `${BINARIES_BASE_URL}/${version}/specific-${
|
|
194782
|
+
const { platform: platform8, arch: arch3 } = getPlatformInfo();
|
|
194783
|
+
const url = `${BINARIES_BASE_URL}/${version}/specific-${platform8}-${arch3}`;
|
|
194573
194784
|
await downloadFile(url, tempPath, onProgress);
|
|
194574
|
-
const stat4 =
|
|
194785
|
+
const stat4 = fs31.statSync(tempPath);
|
|
194575
194786
|
if (stat4.size === 0) {
|
|
194576
194787
|
throw new Error("Downloaded binary is empty");
|
|
194577
194788
|
}
|
|
194578
|
-
|
|
194789
|
+
fs31.chmodSync(tempPath, 493);
|
|
194579
194790
|
onProgress?.({ phase: "finalizing" });
|
|
194580
|
-
|
|
194581
|
-
|
|
194791
|
+
fs31.unlinkSync(binaryPath);
|
|
194792
|
+
fs31.renameSync(tempPath, binaryPath);
|
|
194582
194793
|
} catch (error) {
|
|
194583
194794
|
try {
|
|
194584
|
-
if (
|
|
194585
|
-
|
|
194795
|
+
if (fs31.existsSync(tempPath)) {
|
|
194796
|
+
fs31.unlinkSync(tempPath);
|
|
194586
194797
|
}
|
|
194587
194798
|
} catch {
|
|
194588
194799
|
}
|
|
@@ -194592,21 +194803,21 @@ async function performUpdate(version, onProgress) {
|
|
|
194592
194803
|
|
|
194593
194804
|
// src/lib/background-update.ts
|
|
194594
194805
|
import { spawn as spawn9 } from "child_process";
|
|
194595
|
-
import * as
|
|
194806
|
+
import * as fs32 from "fs";
|
|
194596
194807
|
import * as path29 from "path";
|
|
194597
|
-
import * as
|
|
194598
|
-
var SPECIFIC_DIR = path29.join(
|
|
194808
|
+
import * as os12 from "os";
|
|
194809
|
+
var SPECIFIC_DIR = path29.join(os12.homedir(), ".specific");
|
|
194599
194810
|
var RATE_LIMIT_FILE = path29.join(SPECIFIC_DIR, "last-update-check");
|
|
194600
194811
|
var LOCK_FILE = path29.join(SPECIFIC_DIR, "update.lock");
|
|
194601
194812
|
var RATE_LIMIT_MS = 60 * 60 * 1e3;
|
|
194602
194813
|
var STALE_LOCK_MS = 10 * 60 * 1e3;
|
|
194603
194814
|
function writeCheckTimestamp() {
|
|
194604
|
-
|
|
194605
|
-
|
|
194815
|
+
fs32.mkdirSync(SPECIFIC_DIR, { recursive: true });
|
|
194816
|
+
fs32.writeFileSync(RATE_LIMIT_FILE, String(Date.now()), "utf-8");
|
|
194606
194817
|
}
|
|
194607
194818
|
function isRateLimited() {
|
|
194608
194819
|
try {
|
|
194609
|
-
const content =
|
|
194820
|
+
const content = fs32.readFileSync(RATE_LIMIT_FILE, "utf-8").trim();
|
|
194610
194821
|
const lastCheck = parseInt(content, 10);
|
|
194611
194822
|
if (isNaN(lastCheck)) return false;
|
|
194612
194823
|
return Date.now() - lastCheck < RATE_LIMIT_MS;
|
|
@@ -194740,7 +194951,7 @@ function updateCommand() {
|
|
|
194740
194951
|
var program = new Command();
|
|
194741
194952
|
var env = "production";
|
|
194742
194953
|
var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
|
|
194743
|
-
program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.
|
|
194954
|
+
program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.82").enablePositionalOptions();
|
|
194744
194955
|
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));
|
|
194745
194956
|
program.command("docs [topic]").description("Fetch LLM-optimized documentation").action(docsCommand);
|
|
194746
194957
|
program.command("check").description("Validate specific.hcl configuration").action(checkCommand);
|