@specific.dev/cli 0.1.47 → 0.1.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/404/index.html +1 -1
- package/dist/admin/404.html +1 -1
- package/dist/admin/__next.__PAGE__.txt +1 -1
- package/dist/admin/__next._full.txt +1 -1
- package/dist/admin/__next._head.txt +1 -1
- package/dist/admin/__next._index.txt +1 -1
- package/dist/admin/__next._tree.txt +1 -1
- package/dist/admin/_not-found/__next._full.txt +1 -1
- package/dist/admin/_not-found/__next._head.txt +1 -1
- package/dist/admin/_not-found/__next._index.txt +1 -1
- package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +1 -1
- package/dist/admin/_not-found/__next._not-found.txt +1 -1
- package/dist/admin/_not-found/__next._tree.txt +1 -1
- package/dist/admin/_not-found/index.html +1 -1
- package/dist/admin/_not-found/index.txt +1 -1
- package/dist/admin/databases/__next._full.txt +1 -1
- package/dist/admin/databases/__next._head.txt +1 -1
- package/dist/admin/databases/__next._index.txt +1 -1
- package/dist/admin/databases/__next._tree.txt +1 -1
- package/dist/admin/databases/__next.databases.__PAGE__.txt +1 -1
- package/dist/admin/databases/__next.databases.txt +1 -1
- package/dist/admin/databases/index.html +1 -1
- package/dist/admin/databases/index.txt +1 -1
- package/dist/admin/index.html +1 -1
- package/dist/admin/index.txt +1 -1
- package/dist/cli.js +889 -477
- package/dist/docs/builds.md +22 -0
- package/dist/docs/postgres.md +15 -0
- package/dist/docs/services.md +62 -8
- package/dist/postinstall.js +141 -0
- package/package.json +7 -3
- /package/dist/admin/_next/static/{pcYHo7d7--ealoH3_ELSO → FMaKxl-Dpw5U-PgHqIMag}/_buildManifest.js +0 -0
- /package/dist/admin/_next/static/{pcYHo7d7--ealoH3_ELSO → FMaKxl-Dpw5U-PgHqIMag}/_clientMiddlewareManifest.json +0 -0
- /package/dist/admin/_next/static/{pcYHo7d7--ealoH3_ELSO → FMaKxl-Dpw5U-PgHqIMag}/_ssgManifest.js +0 -0
package/dist/cli.js
CHANGED
|
@@ -238,15 +238,15 @@ var init_wsl_utils = __esm({
|
|
|
238
238
|
const { stdout } = await executePowerShell(command, { powerShellPath: psPath });
|
|
239
239
|
return stdout.trim();
|
|
240
240
|
};
|
|
241
|
-
convertWslPathToWindows = async (
|
|
242
|
-
if (/^[a-z]+:\/\//i.test(
|
|
243
|
-
return
|
|
241
|
+
convertWslPathToWindows = async (path24) => {
|
|
242
|
+
if (/^[a-z]+:\/\//i.test(path24)) {
|
|
243
|
+
return path24;
|
|
244
244
|
}
|
|
245
245
|
try {
|
|
246
|
-
const { stdout } = await execFile2("wslpath", ["-aw",
|
|
246
|
+
const { stdout } = await execFile2("wslpath", ["-aw", path24], { encoding: "utf8" });
|
|
247
247
|
return stdout.trim();
|
|
248
248
|
} catch {
|
|
249
|
-
return
|
|
249
|
+
return path24;
|
|
250
250
|
}
|
|
251
251
|
};
|
|
252
252
|
}
|
|
@@ -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 fs26 = $global.require("fs");
|
|
758
|
+
"object" == typeof fs26 && null !== fs26 && 0 !== Object.keys(fs26).length && ($global.fs = fs26);
|
|
759
759
|
} catch (e) {
|
|
760
760
|
}
|
|
761
761
|
if (!$global.fs) {
|
|
@@ -183055,13 +183055,13 @@ function removeCA() {
|
|
|
183055
183055
|
}
|
|
183056
183056
|
}
|
|
183057
183057
|
function installCAToTrustStore(certPath) {
|
|
183058
|
-
const
|
|
183059
|
-
if (
|
|
183058
|
+
const platform4 = os.platform();
|
|
183059
|
+
if (platform4 === "darwin") {
|
|
183060
183060
|
execSync(
|
|
183061
183061
|
`sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "${certPath}"`,
|
|
183062
183062
|
{ stdio: "inherit" }
|
|
183063
183063
|
);
|
|
183064
|
-
} else if (
|
|
183064
|
+
} else if (platform4 === "linux") {
|
|
183065
183065
|
if (fs.existsSync("/usr/local/share/ca-certificates")) {
|
|
183066
183066
|
execSync(
|
|
183067
183067
|
`sudo cp "${certPath}" /usr/local/share/ca-certificates/specific-local-ca.crt`,
|
|
@@ -183078,7 +183078,7 @@ function installCAToTrustStore(certPath) {
|
|
|
183078
183078
|
throw new Error("Could not detect Linux certificate trust mechanism");
|
|
183079
183079
|
}
|
|
183080
183080
|
} else {
|
|
183081
|
-
throw new Error(`Unsupported platform: ${
|
|
183081
|
+
throw new Error(`Unsupported platform: ${platform4}`);
|
|
183082
183082
|
}
|
|
183083
183083
|
}
|
|
183084
183084
|
function generateCertificate(domain, keys = []) {
|
|
@@ -183873,7 +183873,7 @@ function trackEvent(event, properties) {
|
|
|
183873
183873
|
event,
|
|
183874
183874
|
properties: {
|
|
183875
183875
|
...properties,
|
|
183876
|
-
cli_version: "0.1.
|
|
183876
|
+
cli_version: "0.1.49",
|
|
183877
183877
|
platform: process.platform,
|
|
183878
183878
|
node_version: process.version,
|
|
183879
183879
|
project_id: getProjectId(),
|
|
@@ -183888,6 +183888,11 @@ async function shutdown() {
|
|
|
183888
183888
|
}
|
|
183889
183889
|
}
|
|
183890
183890
|
|
|
183891
|
+
// src/lib/ui/interactive.ts
|
|
183892
|
+
function isInteractive() {
|
|
183893
|
+
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
183894
|
+
}
|
|
183895
|
+
|
|
183891
183896
|
// src/commands/init.tsx
|
|
183892
183897
|
var SPECIFIC_INSTRUCTIONS = `Use Specific to handle all infrastructure and development environments: like deployments, databases, real-time sync, services, secrets, configuration and environment variables. Run \`specific docs\` to learn how to use Specific. ALWAYS run \`specific check\` after making changes to the Specific configuration.`;
|
|
183893
183898
|
var CURSOR_MDC_CONTENT = `---
|
|
@@ -184156,8 +184161,77 @@ function InitUI() {
|
|
|
184156
184161
|
" "
|
|
184157
184162
|
))));
|
|
184158
184163
|
}
|
|
184159
|
-
|
|
184160
|
-
|
|
184164
|
+
var VALID_AGENT_IDS = options.map((o) => o.id);
|
|
184165
|
+
function initCommand(opts) {
|
|
184166
|
+
if (isInteractive()) {
|
|
184167
|
+
render2(/* @__PURE__ */ React2.createElement(InitUI, null));
|
|
184168
|
+
return;
|
|
184169
|
+
}
|
|
184170
|
+
if (!opts.agent || opts.agent.length === 0) {
|
|
184171
|
+
console.error(
|
|
184172
|
+
`Error: --agent is required in non-interactive environments.
|
|
184173
|
+
Usage: specific init --agent cursor claude codex
|
|
184174
|
+
Valid agents: ${VALID_AGENT_IDS.join(", ")}`
|
|
184175
|
+
);
|
|
184176
|
+
process.exit(1);
|
|
184177
|
+
}
|
|
184178
|
+
const invalid = opts.agent.filter((a) => !VALID_AGENT_IDS.includes(a));
|
|
184179
|
+
if (invalid.length > 0) {
|
|
184180
|
+
console.error(
|
|
184181
|
+
`Error: Unknown agent(s): ${invalid.join(", ")}
|
|
184182
|
+
Valid agents: ${VALID_AGENT_IDS.join(", ")}`
|
|
184183
|
+
);
|
|
184184
|
+
process.exit(1);
|
|
184185
|
+
}
|
|
184186
|
+
const checked = {};
|
|
184187
|
+
for (const agent of opts.agent) {
|
|
184188
|
+
checked[agent] = true;
|
|
184189
|
+
}
|
|
184190
|
+
const result = configureAgents(checked);
|
|
184191
|
+
trackEvent("project_initialized", {
|
|
184192
|
+
agents: opts.agent
|
|
184193
|
+
});
|
|
184194
|
+
const selectedAgents = opts.agent.length;
|
|
184195
|
+
const agentChanges = result.agents.filesCreated.length > 0 || result.agents.filesModified.length > 0;
|
|
184196
|
+
console.log("\u2713 Coding agents configured");
|
|
184197
|
+
if (result.agents.filesCreated.length > 0) {
|
|
184198
|
+
console.log(` Created: ${result.agents.filesCreated.join(", ")}`);
|
|
184199
|
+
}
|
|
184200
|
+
if (result.agents.filesModified.length > 0) {
|
|
184201
|
+
console.log(` Modified: ${result.agents.filesModified.join(", ")}`);
|
|
184202
|
+
}
|
|
184203
|
+
if (!agentChanges && selectedAgents > 0) {
|
|
184204
|
+
console.log(" No changes needed (files already configured)");
|
|
184205
|
+
}
|
|
184206
|
+
if (result.git) {
|
|
184207
|
+
const gitChanges = result.git.filesCreated.length > 0 || result.git.filesModified.length > 0;
|
|
184208
|
+
console.log("\u2713 Git configured");
|
|
184209
|
+
if (result.git.filesCreated.length > 0) {
|
|
184210
|
+
console.log(` Created: ${result.git.filesCreated.join(", ")}`);
|
|
184211
|
+
}
|
|
184212
|
+
if (result.git.filesModified.length > 0) {
|
|
184213
|
+
console.log(` Modified: ${result.git.filesModified.join(", ")}`);
|
|
184214
|
+
}
|
|
184215
|
+
if (!gitChanges) {
|
|
184216
|
+
console.log(" No changes needed (.gitignore already configured)");
|
|
184217
|
+
}
|
|
184218
|
+
}
|
|
184219
|
+
if (!caFilesExist()) {
|
|
184220
|
+
console.log(
|
|
184221
|
+
"\u26A0 TLS certificates not installed (run `specific init` in a terminal to set up)"
|
|
184222
|
+
);
|
|
184223
|
+
}
|
|
184224
|
+
if (result.showManualInstructions) {
|
|
184225
|
+
console.log("\n\u2713 Manual configuration selected");
|
|
184226
|
+
console.log("\n Add this to your agent's system prompt:");
|
|
184227
|
+
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
184228
|
+
console.log(` ${SPECIFIC_INSTRUCTIONS}`);
|
|
184229
|
+
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
184230
|
+
console.log(
|
|
184231
|
+
"\n We also recommend allowing your agent to run `specific docs *`"
|
|
184232
|
+
);
|
|
184233
|
+
console.log(" and `specific check *` without confirmation.");
|
|
184234
|
+
}
|
|
184161
184235
|
}
|
|
184162
184236
|
|
|
184163
184237
|
// src/commands/docs.tsx
|
|
@@ -184166,11 +184240,11 @@ import { join as join6, dirname as dirname2 } from "path";
|
|
|
184166
184240
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
184167
184241
|
var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
184168
184242
|
var docsDir = join6(__dirname2, "docs");
|
|
184169
|
-
function docsCommand(
|
|
184170
|
-
const docPath = resolveDocPath(
|
|
184243
|
+
function docsCommand(path24) {
|
|
184244
|
+
const docPath = resolveDocPath(path24);
|
|
184171
184245
|
if (!docPath) {
|
|
184172
184246
|
console.error(
|
|
184173
|
-
`Documentation not found: ${
|
|
184247
|
+
`Documentation not found: ${path24 || "index"}
|
|
184174
184248
|
|
|
184175
184249
|
Run 'specific docs' to see available topics.`
|
|
184176
184250
|
);
|
|
@@ -184179,16 +184253,16 @@ Run 'specific docs' to see available topics.`
|
|
|
184179
184253
|
const content = readFileSync5(docPath, "utf-8");
|
|
184180
184254
|
console.log(content);
|
|
184181
184255
|
}
|
|
184182
|
-
function resolveDocPath(
|
|
184183
|
-
if (!
|
|
184256
|
+
function resolveDocPath(path24) {
|
|
184257
|
+
if (!path24) {
|
|
184184
184258
|
const indexPath2 = join6(docsDir, "index.md");
|
|
184185
184259
|
return existsSync5(indexPath2) ? indexPath2 : null;
|
|
184186
184260
|
}
|
|
184187
|
-
const directPath = join6(docsDir, `${
|
|
184261
|
+
const directPath = join6(docsDir, `${path24}.md`);
|
|
184188
184262
|
if (existsSync5(directPath)) {
|
|
184189
184263
|
return directPath;
|
|
184190
184264
|
}
|
|
184191
|
-
const indexPath = join6(docsDir,
|
|
184265
|
+
const indexPath = join6(docsDir, path24, "index.md");
|
|
184192
184266
|
if (existsSync5(indexPath)) {
|
|
184193
184267
|
return indexPath;
|
|
184194
184268
|
}
|
|
@@ -184283,7 +184357,7 @@ function parseReferenceString(str) {
|
|
|
184283
184357
|
attribute: "port"
|
|
184284
184358
|
};
|
|
184285
184359
|
}
|
|
184286
|
-
const serviceEndpointMatch = str.match(/^service\.(\w+)\.endpoint\.(\w+)\.(url|host|port)$/);
|
|
184360
|
+
const serviceEndpointMatch = str.match(/^service\.(\w+)\.endpoint\.(\w+)\.(url|private_url|host|port|public_url)$/);
|
|
184287
184361
|
if (serviceEndpointMatch && serviceEndpointMatch[1] && serviceEndpointMatch[2] && serviceEndpointMatch[3]) {
|
|
184288
184362
|
return {
|
|
184289
184363
|
type: "service",
|
|
@@ -184292,7 +184366,7 @@ function parseReferenceString(str) {
|
|
|
184292
184366
|
attribute: serviceEndpointMatch[3]
|
|
184293
184367
|
};
|
|
184294
184368
|
}
|
|
184295
|
-
const serviceMatch = str.match(/^service\.(\w+)\.(url|host|port)$/);
|
|
184369
|
+
const serviceMatch = str.match(/^service\.(\w+)\.(url|private_url|host|port|public_url)$/);
|
|
184296
184370
|
if (serviceMatch && serviceMatch[1] && serviceMatch[2]) {
|
|
184297
184371
|
return {
|
|
184298
184372
|
type: "service",
|
|
@@ -184397,6 +184471,17 @@ function parseBuilds(buildData) {
|
|
|
184397
184471
|
if (dev) {
|
|
184398
184472
|
build.dev = dev;
|
|
184399
184473
|
}
|
|
184474
|
+
const env2 = parseEnv(fieldObj.env);
|
|
184475
|
+
if (env2) {
|
|
184476
|
+
for (const [key, value] of Object.entries(env2)) {
|
|
184477
|
+
if (typeof value === "string")
|
|
184478
|
+
continue;
|
|
184479
|
+
if (value.type === "service" && value.attribute === "public_url")
|
|
184480
|
+
continue;
|
|
184481
|
+
throw new Error(`Build "${name}" env var "${key}" uses an unsupported reference type. Build env vars only support string literals and \${service.<name>.public_url} references.`);
|
|
184482
|
+
}
|
|
184483
|
+
build.env = env2;
|
|
184484
|
+
}
|
|
184400
184485
|
builds.push(build);
|
|
184401
184486
|
}
|
|
184402
184487
|
return builds;
|
|
@@ -184704,6 +184789,26 @@ function validateEndpointReferences(config) {
|
|
|
184704
184789
|
});
|
|
184705
184790
|
}
|
|
184706
184791
|
}
|
|
184792
|
+
if (serviceRef.attribute === "public_url") {
|
|
184793
|
+
const endpointName = serviceRef.endpointName;
|
|
184794
|
+
if (endpointName) {
|
|
184795
|
+
const targetEndpoint = targetEndpoints.find((e) => e.name === endpointName);
|
|
184796
|
+
if (targetEndpoint && !targetEndpoint.public) {
|
|
184797
|
+
errors.push({
|
|
184798
|
+
service: service.name,
|
|
184799
|
+
message: `Service "${service.name}" references public_url of endpoint "${endpointName}" on service "${serviceRef.serviceName}" in env var "${key}", but that endpoint is not public. Add \`public = true\` to the endpoint.`
|
|
184800
|
+
});
|
|
184801
|
+
}
|
|
184802
|
+
} else {
|
|
184803
|
+
const defaultEndpoint = targetEndpoints.length === 1 ? targetEndpoints[0] : targetEndpoints.find((e) => e.name === "default");
|
|
184804
|
+
if (defaultEndpoint && !defaultEndpoint.public) {
|
|
184805
|
+
errors.push({
|
|
184806
|
+
service: service.name,
|
|
184807
|
+
message: `Service "${service.name}" references public_url of service "${serviceRef.serviceName}" in env var "${key}", but its endpoint is not public. Add \`public = true\` to the endpoint.`
|
|
184808
|
+
});
|
|
184809
|
+
}
|
|
184810
|
+
}
|
|
184811
|
+
}
|
|
184707
184812
|
}
|
|
184708
184813
|
if (typeof value === "object" && value.type === "endpoint") {
|
|
184709
184814
|
const endpointRef = value;
|
|
@@ -184767,8 +184872,8 @@ function checkCommand() {
|
|
|
184767
184872
|
import React6, { useState as useState5, useEffect as useEffect3, useRef } from "react";
|
|
184768
184873
|
import { render as render4, Text as Text6, Box as Box6, useApp as useApp2, Static } from "ink";
|
|
184769
184874
|
import Spinner4 from "ink-spinner";
|
|
184770
|
-
import * as
|
|
184771
|
-
import * as
|
|
184875
|
+
import * as fs20 from "fs";
|
|
184876
|
+
import * as path17 from "path";
|
|
184772
184877
|
|
|
184773
184878
|
// node_modules/.pnpm/chokidar@5.0.0/node_modules/chokidar/index.js
|
|
184774
184879
|
import { EventEmitter } from "node:events";
|
|
@@ -184860,7 +184965,7 @@ var ReaddirpStream = class extends Readable {
|
|
|
184860
184965
|
this._directoryFilter = normalizeFilter(opts.directoryFilter);
|
|
184861
184966
|
const statMethod = opts.lstat ? lstat : stat;
|
|
184862
184967
|
if (wantBigintFsStats) {
|
|
184863
|
-
this._stat = (
|
|
184968
|
+
this._stat = (path24) => statMethod(path24, { bigint: true });
|
|
184864
184969
|
} else {
|
|
184865
184970
|
this._stat = statMethod;
|
|
184866
184971
|
}
|
|
@@ -184885,8 +184990,8 @@ var ReaddirpStream = class extends Readable {
|
|
|
184885
184990
|
const par = this.parent;
|
|
184886
184991
|
const fil = par && par.files;
|
|
184887
184992
|
if (fil && fil.length > 0) {
|
|
184888
|
-
const { path:
|
|
184889
|
-
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent,
|
|
184993
|
+
const { path: path24, depth } = par;
|
|
184994
|
+
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path24));
|
|
184890
184995
|
const awaited = await Promise.all(slice);
|
|
184891
184996
|
for (const entry of awaited) {
|
|
184892
184997
|
if (!entry)
|
|
@@ -184926,20 +185031,20 @@ var ReaddirpStream = class extends Readable {
|
|
|
184926
185031
|
this.reading = false;
|
|
184927
185032
|
}
|
|
184928
185033
|
}
|
|
184929
|
-
async _exploreDir(
|
|
185034
|
+
async _exploreDir(path24, depth) {
|
|
184930
185035
|
let files;
|
|
184931
185036
|
try {
|
|
184932
|
-
files = await readdir(
|
|
185037
|
+
files = await readdir(path24, this._rdOptions);
|
|
184933
185038
|
} catch (error) {
|
|
184934
185039
|
this._onError(error);
|
|
184935
185040
|
}
|
|
184936
|
-
return { files, depth, path:
|
|
185041
|
+
return { files, depth, path: path24 };
|
|
184937
185042
|
}
|
|
184938
|
-
async _formatEntry(dirent,
|
|
185043
|
+
async _formatEntry(dirent, path24) {
|
|
184939
185044
|
let entry;
|
|
184940
185045
|
const basename5 = this._isDirent ? dirent.name : dirent;
|
|
184941
185046
|
try {
|
|
184942
|
-
const fullPath = presolve(pjoin(
|
|
185047
|
+
const fullPath = presolve(pjoin(path24, basename5));
|
|
184943
185048
|
entry = { path: prelative(this._root, fullPath), fullPath, basename: basename5 };
|
|
184944
185049
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
184945
185050
|
} catch (err) {
|
|
@@ -185339,16 +185444,16 @@ var delFromSet = (main, prop, item) => {
|
|
|
185339
185444
|
};
|
|
185340
185445
|
var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
|
|
185341
185446
|
var FsWatchInstances = /* @__PURE__ */ new Map();
|
|
185342
|
-
function createFsWatchInstance(
|
|
185447
|
+
function createFsWatchInstance(path24, options2, listener, errHandler, emitRaw) {
|
|
185343
185448
|
const handleEvent = (rawEvent, evPath) => {
|
|
185344
|
-
listener(
|
|
185345
|
-
emitRaw(rawEvent, evPath, { watchedPath:
|
|
185346
|
-
if (evPath &&
|
|
185347
|
-
fsWatchBroadcast(sp.resolve(
|
|
185449
|
+
listener(path24);
|
|
185450
|
+
emitRaw(rawEvent, evPath, { watchedPath: path24 });
|
|
185451
|
+
if (evPath && path24 !== evPath) {
|
|
185452
|
+
fsWatchBroadcast(sp.resolve(path24, evPath), KEY_LISTENERS, sp.join(path24, evPath));
|
|
185348
185453
|
}
|
|
185349
185454
|
};
|
|
185350
185455
|
try {
|
|
185351
|
-
return fs_watch(
|
|
185456
|
+
return fs_watch(path24, {
|
|
185352
185457
|
persistent: options2.persistent
|
|
185353
185458
|
}, handleEvent);
|
|
185354
185459
|
} catch (error) {
|
|
@@ -185364,12 +185469,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
|
|
|
185364
185469
|
listener(val1, val2, val3);
|
|
185365
185470
|
});
|
|
185366
185471
|
};
|
|
185367
|
-
var setFsWatchListener = (
|
|
185472
|
+
var setFsWatchListener = (path24, fullPath, options2, handlers) => {
|
|
185368
185473
|
const { listener, errHandler, rawEmitter } = handlers;
|
|
185369
185474
|
let cont = FsWatchInstances.get(fullPath);
|
|
185370
185475
|
let watcher;
|
|
185371
185476
|
if (!options2.persistent) {
|
|
185372
|
-
watcher = createFsWatchInstance(
|
|
185477
|
+
watcher = createFsWatchInstance(path24, options2, listener, errHandler, rawEmitter);
|
|
185373
185478
|
if (!watcher)
|
|
185374
185479
|
return;
|
|
185375
185480
|
return watcher.close.bind(watcher);
|
|
@@ -185380,7 +185485,7 @@ var setFsWatchListener = (path22, fullPath, options2, handlers) => {
|
|
|
185380
185485
|
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
185381
185486
|
} else {
|
|
185382
185487
|
watcher = createFsWatchInstance(
|
|
185383
|
-
|
|
185488
|
+
path24,
|
|
185384
185489
|
options2,
|
|
185385
185490
|
fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
|
|
185386
185491
|
errHandler,
|
|
@@ -185395,7 +185500,7 @@ var setFsWatchListener = (path22, fullPath, options2, handlers) => {
|
|
|
185395
185500
|
cont.watcherUnusable = true;
|
|
185396
185501
|
if (isWindows && error.code === "EPERM") {
|
|
185397
185502
|
try {
|
|
185398
|
-
const fd = await open2(
|
|
185503
|
+
const fd = await open2(path24, "r");
|
|
185399
185504
|
await fd.close();
|
|
185400
185505
|
broadcastErr(error);
|
|
185401
185506
|
} catch (err) {
|
|
@@ -185426,7 +185531,7 @@ var setFsWatchListener = (path22, fullPath, options2, handlers) => {
|
|
|
185426
185531
|
};
|
|
185427
185532
|
};
|
|
185428
185533
|
var FsWatchFileInstances = /* @__PURE__ */ new Map();
|
|
185429
|
-
var setFsWatchFileListener = (
|
|
185534
|
+
var setFsWatchFileListener = (path24, fullPath, options2, handlers) => {
|
|
185430
185535
|
const { listener, rawEmitter } = handlers;
|
|
185431
185536
|
let cont = FsWatchFileInstances.get(fullPath);
|
|
185432
185537
|
const copts = cont && cont.options;
|
|
@@ -185448,7 +185553,7 @@ var setFsWatchFileListener = (path22, fullPath, options2, handlers) => {
|
|
|
185448
185553
|
});
|
|
185449
185554
|
const currmtime = curr.mtimeMs;
|
|
185450
185555
|
if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
|
|
185451
|
-
foreach(cont.listeners, (listener2) => listener2(
|
|
185556
|
+
foreach(cont.listeners, (listener2) => listener2(path24, curr));
|
|
185452
185557
|
}
|
|
185453
185558
|
})
|
|
185454
185559
|
};
|
|
@@ -185478,13 +185583,13 @@ var NodeFsHandler = class {
|
|
|
185478
185583
|
* @param listener on fs change
|
|
185479
185584
|
* @returns closer for the watcher instance
|
|
185480
185585
|
*/
|
|
185481
|
-
_watchWithNodeFs(
|
|
185586
|
+
_watchWithNodeFs(path24, listener) {
|
|
185482
185587
|
const opts = this.fsw.options;
|
|
185483
|
-
const directory = sp.dirname(
|
|
185484
|
-
const basename5 = sp.basename(
|
|
185588
|
+
const directory = sp.dirname(path24);
|
|
185589
|
+
const basename5 = sp.basename(path24);
|
|
185485
185590
|
const parent = this.fsw._getWatchedDir(directory);
|
|
185486
185591
|
parent.add(basename5);
|
|
185487
|
-
const absolutePath = sp.resolve(
|
|
185592
|
+
const absolutePath = sp.resolve(path24);
|
|
185488
185593
|
const options2 = {
|
|
185489
185594
|
persistent: opts.persistent
|
|
185490
185595
|
};
|
|
@@ -185494,12 +185599,12 @@ var NodeFsHandler = class {
|
|
|
185494
185599
|
if (opts.usePolling) {
|
|
185495
185600
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
185496
185601
|
options2.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
|
|
185497
|
-
closer = setFsWatchFileListener(
|
|
185602
|
+
closer = setFsWatchFileListener(path24, absolutePath, options2, {
|
|
185498
185603
|
listener,
|
|
185499
185604
|
rawEmitter: this.fsw._emitRaw
|
|
185500
185605
|
});
|
|
185501
185606
|
} else {
|
|
185502
|
-
closer = setFsWatchListener(
|
|
185607
|
+
closer = setFsWatchListener(path24, absolutePath, options2, {
|
|
185503
185608
|
listener,
|
|
185504
185609
|
errHandler: this._boundHandleError,
|
|
185505
185610
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -185521,7 +185626,7 @@ var NodeFsHandler = class {
|
|
|
185521
185626
|
let prevStats = stats;
|
|
185522
185627
|
if (parent.has(basename5))
|
|
185523
185628
|
return;
|
|
185524
|
-
const listener = async (
|
|
185629
|
+
const listener = async (path24, newStats) => {
|
|
185525
185630
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
|
|
185526
185631
|
return;
|
|
185527
185632
|
if (!newStats || newStats.mtimeMs === 0) {
|
|
@@ -185535,11 +185640,11 @@ var NodeFsHandler = class {
|
|
|
185535
185640
|
this.fsw._emit(EV.CHANGE, file, newStats2);
|
|
185536
185641
|
}
|
|
185537
185642
|
if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
|
|
185538
|
-
this.fsw._closeFile(
|
|
185643
|
+
this.fsw._closeFile(path24);
|
|
185539
185644
|
prevStats = newStats2;
|
|
185540
185645
|
const closer2 = this._watchWithNodeFs(file, listener);
|
|
185541
185646
|
if (closer2)
|
|
185542
|
-
this.fsw._addPathCloser(
|
|
185647
|
+
this.fsw._addPathCloser(path24, closer2);
|
|
185543
185648
|
} else {
|
|
185544
185649
|
prevStats = newStats2;
|
|
185545
185650
|
}
|
|
@@ -185571,7 +185676,7 @@ var NodeFsHandler = class {
|
|
|
185571
185676
|
* @param item basename of this item
|
|
185572
185677
|
* @returns true if no more processing is needed for this entry.
|
|
185573
185678
|
*/
|
|
185574
|
-
async _handleSymlink(entry, directory,
|
|
185679
|
+
async _handleSymlink(entry, directory, path24, item) {
|
|
185575
185680
|
if (this.fsw.closed) {
|
|
185576
185681
|
return;
|
|
185577
185682
|
}
|
|
@@ -185581,7 +185686,7 @@ var NodeFsHandler = class {
|
|
|
185581
185686
|
this.fsw._incrReadyCount();
|
|
185582
185687
|
let linkPath;
|
|
185583
185688
|
try {
|
|
185584
|
-
linkPath = await fsrealpath(
|
|
185689
|
+
linkPath = await fsrealpath(path24);
|
|
185585
185690
|
} catch (e) {
|
|
185586
185691
|
this.fsw._emitReady();
|
|
185587
185692
|
return true;
|
|
@@ -185591,12 +185696,12 @@ var NodeFsHandler = class {
|
|
|
185591
185696
|
if (dir.has(item)) {
|
|
185592
185697
|
if (this.fsw._symlinkPaths.get(full) !== linkPath) {
|
|
185593
185698
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
185594
|
-
this.fsw._emit(EV.CHANGE,
|
|
185699
|
+
this.fsw._emit(EV.CHANGE, path24, entry.stats);
|
|
185595
185700
|
}
|
|
185596
185701
|
} else {
|
|
185597
185702
|
dir.add(item);
|
|
185598
185703
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
185599
|
-
this.fsw._emit(EV.ADD,
|
|
185704
|
+
this.fsw._emit(EV.ADD, path24, entry.stats);
|
|
185600
185705
|
}
|
|
185601
185706
|
this.fsw._emitReady();
|
|
185602
185707
|
return true;
|
|
@@ -185626,9 +185731,9 @@ var NodeFsHandler = class {
|
|
|
185626
185731
|
return;
|
|
185627
185732
|
}
|
|
185628
185733
|
const item = entry.path;
|
|
185629
|
-
let
|
|
185734
|
+
let path24 = sp.join(directory, item);
|
|
185630
185735
|
current.add(item);
|
|
185631
|
-
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory,
|
|
185736
|
+
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path24, item)) {
|
|
185632
185737
|
return;
|
|
185633
185738
|
}
|
|
185634
185739
|
if (this.fsw.closed) {
|
|
@@ -185637,8 +185742,8 @@ var NodeFsHandler = class {
|
|
|
185637
185742
|
}
|
|
185638
185743
|
if (item === target || !target && !previous.has(item)) {
|
|
185639
185744
|
this.fsw._incrReadyCount();
|
|
185640
|
-
|
|
185641
|
-
this._addToNodeFs(
|
|
185745
|
+
path24 = sp.join(dir, sp.relative(dir, path24));
|
|
185746
|
+
this._addToNodeFs(path24, initialAdd, wh, depth + 1);
|
|
185642
185747
|
}
|
|
185643
185748
|
}).on(EV.ERROR, this._boundHandleError);
|
|
185644
185749
|
return new Promise((resolve7, reject) => {
|
|
@@ -185707,13 +185812,13 @@ var NodeFsHandler = class {
|
|
|
185707
185812
|
* @param depth Child path actually targeted for watch
|
|
185708
185813
|
* @param target Child path actually targeted for watch
|
|
185709
185814
|
*/
|
|
185710
|
-
async _addToNodeFs(
|
|
185815
|
+
async _addToNodeFs(path24, initialAdd, priorWh, depth, target) {
|
|
185711
185816
|
const ready = this.fsw._emitReady;
|
|
185712
|
-
if (this.fsw._isIgnored(
|
|
185817
|
+
if (this.fsw._isIgnored(path24) || this.fsw.closed) {
|
|
185713
185818
|
ready();
|
|
185714
185819
|
return false;
|
|
185715
185820
|
}
|
|
185716
|
-
const wh = this.fsw._getWatchHelpers(
|
|
185821
|
+
const wh = this.fsw._getWatchHelpers(path24);
|
|
185717
185822
|
if (priorWh) {
|
|
185718
185823
|
wh.filterPath = (entry) => priorWh.filterPath(entry);
|
|
185719
185824
|
wh.filterDir = (entry) => priorWh.filterDir(entry);
|
|
@@ -185729,8 +185834,8 @@ var NodeFsHandler = class {
|
|
|
185729
185834
|
const follow = this.fsw.options.followSymlinks;
|
|
185730
185835
|
let closer;
|
|
185731
185836
|
if (stats.isDirectory()) {
|
|
185732
|
-
const absPath = sp.resolve(
|
|
185733
|
-
const targetPath = follow ? await fsrealpath(
|
|
185837
|
+
const absPath = sp.resolve(path24);
|
|
185838
|
+
const targetPath = follow ? await fsrealpath(path24) : path24;
|
|
185734
185839
|
if (this.fsw.closed)
|
|
185735
185840
|
return;
|
|
185736
185841
|
closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
|
|
@@ -185740,29 +185845,29 @@ var NodeFsHandler = class {
|
|
|
185740
185845
|
this.fsw._symlinkPaths.set(absPath, targetPath);
|
|
185741
185846
|
}
|
|
185742
185847
|
} else if (stats.isSymbolicLink()) {
|
|
185743
|
-
const targetPath = follow ? await fsrealpath(
|
|
185848
|
+
const targetPath = follow ? await fsrealpath(path24) : path24;
|
|
185744
185849
|
if (this.fsw.closed)
|
|
185745
185850
|
return;
|
|
185746
185851
|
const parent = sp.dirname(wh.watchPath);
|
|
185747
185852
|
this.fsw._getWatchedDir(parent).add(wh.watchPath);
|
|
185748
185853
|
this.fsw._emit(EV.ADD, wh.watchPath, stats);
|
|
185749
|
-
closer = await this._handleDir(parent, stats, initialAdd, depth,
|
|
185854
|
+
closer = await this._handleDir(parent, stats, initialAdd, depth, path24, wh, targetPath);
|
|
185750
185855
|
if (this.fsw.closed)
|
|
185751
185856
|
return;
|
|
185752
185857
|
if (targetPath !== void 0) {
|
|
185753
|
-
this.fsw._symlinkPaths.set(sp.resolve(
|
|
185858
|
+
this.fsw._symlinkPaths.set(sp.resolve(path24), targetPath);
|
|
185754
185859
|
}
|
|
185755
185860
|
} else {
|
|
185756
185861
|
closer = this._handleFile(wh.watchPath, stats, initialAdd);
|
|
185757
185862
|
}
|
|
185758
185863
|
ready();
|
|
185759
185864
|
if (closer)
|
|
185760
|
-
this.fsw._addPathCloser(
|
|
185865
|
+
this.fsw._addPathCloser(path24, closer);
|
|
185761
185866
|
return false;
|
|
185762
185867
|
} catch (error) {
|
|
185763
185868
|
if (this.fsw._handleError(error)) {
|
|
185764
185869
|
ready();
|
|
185765
|
-
return
|
|
185870
|
+
return path24;
|
|
185766
185871
|
}
|
|
185767
185872
|
}
|
|
185768
185873
|
}
|
|
@@ -185805,24 +185910,24 @@ function createPattern(matcher) {
|
|
|
185805
185910
|
}
|
|
185806
185911
|
return () => false;
|
|
185807
185912
|
}
|
|
185808
|
-
function normalizePath(
|
|
185809
|
-
if (typeof
|
|
185913
|
+
function normalizePath(path24) {
|
|
185914
|
+
if (typeof path24 !== "string")
|
|
185810
185915
|
throw new Error("string expected");
|
|
185811
|
-
|
|
185812
|
-
|
|
185916
|
+
path24 = sp2.normalize(path24);
|
|
185917
|
+
path24 = path24.replace(/\\/g, "/");
|
|
185813
185918
|
let prepend = false;
|
|
185814
|
-
if (
|
|
185919
|
+
if (path24.startsWith("//"))
|
|
185815
185920
|
prepend = true;
|
|
185816
|
-
|
|
185921
|
+
path24 = path24.replace(DOUBLE_SLASH_RE, "/");
|
|
185817
185922
|
if (prepend)
|
|
185818
|
-
|
|
185819
|
-
return
|
|
185923
|
+
path24 = "/" + path24;
|
|
185924
|
+
return path24;
|
|
185820
185925
|
}
|
|
185821
185926
|
function matchPatterns(patterns, testString, stats) {
|
|
185822
|
-
const
|
|
185927
|
+
const path24 = normalizePath(testString);
|
|
185823
185928
|
for (let index = 0; index < patterns.length; index++) {
|
|
185824
185929
|
const pattern = patterns[index];
|
|
185825
|
-
if (pattern(
|
|
185930
|
+
if (pattern(path24, stats)) {
|
|
185826
185931
|
return true;
|
|
185827
185932
|
}
|
|
185828
185933
|
}
|
|
@@ -185860,19 +185965,19 @@ var toUnix = (string) => {
|
|
|
185860
185965
|
}
|
|
185861
185966
|
return str;
|
|
185862
185967
|
};
|
|
185863
|
-
var normalizePathToUnix = (
|
|
185864
|
-
var normalizeIgnored = (cwd = "") => (
|
|
185865
|
-
if (typeof
|
|
185866
|
-
return normalizePathToUnix(sp2.isAbsolute(
|
|
185968
|
+
var normalizePathToUnix = (path24) => toUnix(sp2.normalize(toUnix(path24)));
|
|
185969
|
+
var normalizeIgnored = (cwd = "") => (path24) => {
|
|
185970
|
+
if (typeof path24 === "string") {
|
|
185971
|
+
return normalizePathToUnix(sp2.isAbsolute(path24) ? path24 : sp2.join(cwd, path24));
|
|
185867
185972
|
} else {
|
|
185868
|
-
return
|
|
185973
|
+
return path24;
|
|
185869
185974
|
}
|
|
185870
185975
|
};
|
|
185871
|
-
var getAbsolutePath = (
|
|
185872
|
-
if (sp2.isAbsolute(
|
|
185873
|
-
return
|
|
185976
|
+
var getAbsolutePath = (path24, cwd) => {
|
|
185977
|
+
if (sp2.isAbsolute(path24)) {
|
|
185978
|
+
return path24;
|
|
185874
185979
|
}
|
|
185875
|
-
return sp2.join(cwd,
|
|
185980
|
+
return sp2.join(cwd, path24);
|
|
185876
185981
|
};
|
|
185877
185982
|
var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
|
|
185878
185983
|
var DirEntry = class {
|
|
@@ -185937,10 +186042,10 @@ var WatchHelper = class {
|
|
|
185937
186042
|
dirParts;
|
|
185938
186043
|
followSymlinks;
|
|
185939
186044
|
statMethod;
|
|
185940
|
-
constructor(
|
|
186045
|
+
constructor(path24, follow, fsw) {
|
|
185941
186046
|
this.fsw = fsw;
|
|
185942
|
-
const watchPath =
|
|
185943
|
-
this.path =
|
|
186047
|
+
const watchPath = path24;
|
|
186048
|
+
this.path = path24 = path24.replace(REPLACER_RE, "");
|
|
185944
186049
|
this.watchPath = watchPath;
|
|
185945
186050
|
this.fullWatchPath = sp2.resolve(watchPath);
|
|
185946
186051
|
this.dirParts = [];
|
|
@@ -186080,20 +186185,20 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186080
186185
|
this._closePromise = void 0;
|
|
186081
186186
|
let paths = unifyPaths(paths_);
|
|
186082
186187
|
if (cwd) {
|
|
186083
|
-
paths = paths.map((
|
|
186084
|
-
const absPath = getAbsolutePath(
|
|
186188
|
+
paths = paths.map((path24) => {
|
|
186189
|
+
const absPath = getAbsolutePath(path24, cwd);
|
|
186085
186190
|
return absPath;
|
|
186086
186191
|
});
|
|
186087
186192
|
}
|
|
186088
|
-
paths.forEach((
|
|
186089
|
-
this._removeIgnoredPath(
|
|
186193
|
+
paths.forEach((path24) => {
|
|
186194
|
+
this._removeIgnoredPath(path24);
|
|
186090
186195
|
});
|
|
186091
186196
|
this._userIgnored = void 0;
|
|
186092
186197
|
if (!this._readyCount)
|
|
186093
186198
|
this._readyCount = 0;
|
|
186094
186199
|
this._readyCount += paths.length;
|
|
186095
|
-
Promise.all(paths.map(async (
|
|
186096
|
-
const res = await this._nodeFsHandler._addToNodeFs(
|
|
186200
|
+
Promise.all(paths.map(async (path24) => {
|
|
186201
|
+
const res = await this._nodeFsHandler._addToNodeFs(path24, !_internal, void 0, 0, _origAdd);
|
|
186097
186202
|
if (res)
|
|
186098
186203
|
this._emitReady();
|
|
186099
186204
|
return res;
|
|
@@ -186115,17 +186220,17 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186115
186220
|
return this;
|
|
186116
186221
|
const paths = unifyPaths(paths_);
|
|
186117
186222
|
const { cwd } = this.options;
|
|
186118
|
-
paths.forEach((
|
|
186119
|
-
if (!sp2.isAbsolute(
|
|
186223
|
+
paths.forEach((path24) => {
|
|
186224
|
+
if (!sp2.isAbsolute(path24) && !this._closers.has(path24)) {
|
|
186120
186225
|
if (cwd)
|
|
186121
|
-
|
|
186122
|
-
|
|
186226
|
+
path24 = sp2.join(cwd, path24);
|
|
186227
|
+
path24 = sp2.resolve(path24);
|
|
186123
186228
|
}
|
|
186124
|
-
this._closePath(
|
|
186125
|
-
this._addIgnoredPath(
|
|
186126
|
-
if (this._watched.has(
|
|
186229
|
+
this._closePath(path24);
|
|
186230
|
+
this._addIgnoredPath(path24);
|
|
186231
|
+
if (this._watched.has(path24)) {
|
|
186127
186232
|
this._addIgnoredPath({
|
|
186128
|
-
path:
|
|
186233
|
+
path: path24,
|
|
186129
186234
|
recursive: true
|
|
186130
186235
|
});
|
|
186131
186236
|
}
|
|
@@ -186189,38 +186294,38 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186189
186294
|
* @param stats arguments to be passed with event
|
|
186190
186295
|
* @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
|
|
186191
186296
|
*/
|
|
186192
|
-
async _emit(event,
|
|
186297
|
+
async _emit(event, path24, stats) {
|
|
186193
186298
|
if (this.closed)
|
|
186194
186299
|
return;
|
|
186195
186300
|
const opts = this.options;
|
|
186196
186301
|
if (isWindows)
|
|
186197
|
-
|
|
186302
|
+
path24 = sp2.normalize(path24);
|
|
186198
186303
|
if (opts.cwd)
|
|
186199
|
-
|
|
186200
|
-
const args = [
|
|
186304
|
+
path24 = sp2.relative(opts.cwd, path24);
|
|
186305
|
+
const args = [path24];
|
|
186201
186306
|
if (stats != null)
|
|
186202
186307
|
args.push(stats);
|
|
186203
186308
|
const awf = opts.awaitWriteFinish;
|
|
186204
186309
|
let pw;
|
|
186205
|
-
if (awf && (pw = this._pendingWrites.get(
|
|
186310
|
+
if (awf && (pw = this._pendingWrites.get(path24))) {
|
|
186206
186311
|
pw.lastChange = /* @__PURE__ */ new Date();
|
|
186207
186312
|
return this;
|
|
186208
186313
|
}
|
|
186209
186314
|
if (opts.atomic) {
|
|
186210
186315
|
if (event === EVENTS.UNLINK) {
|
|
186211
|
-
this._pendingUnlinks.set(
|
|
186316
|
+
this._pendingUnlinks.set(path24, [event, ...args]);
|
|
186212
186317
|
setTimeout(() => {
|
|
186213
|
-
this._pendingUnlinks.forEach((entry,
|
|
186318
|
+
this._pendingUnlinks.forEach((entry, path25) => {
|
|
186214
186319
|
this.emit(...entry);
|
|
186215
186320
|
this.emit(EVENTS.ALL, ...entry);
|
|
186216
|
-
this._pendingUnlinks.delete(
|
|
186321
|
+
this._pendingUnlinks.delete(path25);
|
|
186217
186322
|
});
|
|
186218
186323
|
}, typeof opts.atomic === "number" ? opts.atomic : 100);
|
|
186219
186324
|
return this;
|
|
186220
186325
|
}
|
|
186221
|
-
if (event === EVENTS.ADD && this._pendingUnlinks.has(
|
|
186326
|
+
if (event === EVENTS.ADD && this._pendingUnlinks.has(path24)) {
|
|
186222
186327
|
event = EVENTS.CHANGE;
|
|
186223
|
-
this._pendingUnlinks.delete(
|
|
186328
|
+
this._pendingUnlinks.delete(path24);
|
|
186224
186329
|
}
|
|
186225
186330
|
}
|
|
186226
186331
|
if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
|
|
@@ -186238,16 +186343,16 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186238
186343
|
this.emitWithAll(event, args);
|
|
186239
186344
|
}
|
|
186240
186345
|
};
|
|
186241
|
-
this._awaitWriteFinish(
|
|
186346
|
+
this._awaitWriteFinish(path24, awf.stabilityThreshold, event, awfEmit);
|
|
186242
186347
|
return this;
|
|
186243
186348
|
}
|
|
186244
186349
|
if (event === EVENTS.CHANGE) {
|
|
186245
|
-
const isThrottled = !this._throttle(EVENTS.CHANGE,
|
|
186350
|
+
const isThrottled = !this._throttle(EVENTS.CHANGE, path24, 50);
|
|
186246
186351
|
if (isThrottled)
|
|
186247
186352
|
return this;
|
|
186248
186353
|
}
|
|
186249
186354
|
if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
|
|
186250
|
-
const fullPath = opts.cwd ? sp2.join(opts.cwd,
|
|
186355
|
+
const fullPath = opts.cwd ? sp2.join(opts.cwd, path24) : path24;
|
|
186251
186356
|
let stats2;
|
|
186252
186357
|
try {
|
|
186253
186358
|
stats2 = await stat3(fullPath);
|
|
@@ -186278,23 +186383,23 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186278
186383
|
* @param timeout duration of time to suppress duplicate actions
|
|
186279
186384
|
* @returns tracking object or false if action should be suppressed
|
|
186280
186385
|
*/
|
|
186281
|
-
_throttle(actionType,
|
|
186386
|
+
_throttle(actionType, path24, timeout) {
|
|
186282
186387
|
if (!this._throttled.has(actionType)) {
|
|
186283
186388
|
this._throttled.set(actionType, /* @__PURE__ */ new Map());
|
|
186284
186389
|
}
|
|
186285
186390
|
const action = this._throttled.get(actionType);
|
|
186286
186391
|
if (!action)
|
|
186287
186392
|
throw new Error("invalid throttle");
|
|
186288
|
-
const actionPath = action.get(
|
|
186393
|
+
const actionPath = action.get(path24);
|
|
186289
186394
|
if (actionPath) {
|
|
186290
186395
|
actionPath.count++;
|
|
186291
186396
|
return false;
|
|
186292
186397
|
}
|
|
186293
186398
|
let timeoutObject;
|
|
186294
186399
|
const clear = () => {
|
|
186295
|
-
const item = action.get(
|
|
186400
|
+
const item = action.get(path24);
|
|
186296
186401
|
const count = item ? item.count : 0;
|
|
186297
|
-
action.delete(
|
|
186402
|
+
action.delete(path24);
|
|
186298
186403
|
clearTimeout(timeoutObject);
|
|
186299
186404
|
if (item)
|
|
186300
186405
|
clearTimeout(item.timeoutObject);
|
|
@@ -186302,7 +186407,7 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186302
186407
|
};
|
|
186303
186408
|
timeoutObject = setTimeout(clear, timeout);
|
|
186304
186409
|
const thr = { timeoutObject, clear, count: 0 };
|
|
186305
|
-
action.set(
|
|
186410
|
+
action.set(path24, thr);
|
|
186306
186411
|
return thr;
|
|
186307
186412
|
}
|
|
186308
186413
|
_incrReadyCount() {
|
|
@@ -186316,44 +186421,44 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186316
186421
|
* @param event
|
|
186317
186422
|
* @param awfEmit Callback to be called when ready for event to be emitted.
|
|
186318
186423
|
*/
|
|
186319
|
-
_awaitWriteFinish(
|
|
186424
|
+
_awaitWriteFinish(path24, threshold, event, awfEmit) {
|
|
186320
186425
|
const awf = this.options.awaitWriteFinish;
|
|
186321
186426
|
if (typeof awf !== "object")
|
|
186322
186427
|
return;
|
|
186323
186428
|
const pollInterval = awf.pollInterval;
|
|
186324
186429
|
let timeoutHandler;
|
|
186325
|
-
let fullPath =
|
|
186326
|
-
if (this.options.cwd && !sp2.isAbsolute(
|
|
186327
|
-
fullPath = sp2.join(this.options.cwd,
|
|
186430
|
+
let fullPath = path24;
|
|
186431
|
+
if (this.options.cwd && !sp2.isAbsolute(path24)) {
|
|
186432
|
+
fullPath = sp2.join(this.options.cwd, path24);
|
|
186328
186433
|
}
|
|
186329
186434
|
const now = /* @__PURE__ */ new Date();
|
|
186330
186435
|
const writes = this._pendingWrites;
|
|
186331
186436
|
function awaitWriteFinishFn(prevStat) {
|
|
186332
186437
|
statcb(fullPath, (err, curStat) => {
|
|
186333
|
-
if (err || !writes.has(
|
|
186438
|
+
if (err || !writes.has(path24)) {
|
|
186334
186439
|
if (err && err.code !== "ENOENT")
|
|
186335
186440
|
awfEmit(err);
|
|
186336
186441
|
return;
|
|
186337
186442
|
}
|
|
186338
186443
|
const now2 = Number(/* @__PURE__ */ new Date());
|
|
186339
186444
|
if (prevStat && curStat.size !== prevStat.size) {
|
|
186340
|
-
writes.get(
|
|
186445
|
+
writes.get(path24).lastChange = now2;
|
|
186341
186446
|
}
|
|
186342
|
-
const pw = writes.get(
|
|
186447
|
+
const pw = writes.get(path24);
|
|
186343
186448
|
const df = now2 - pw.lastChange;
|
|
186344
186449
|
if (df >= threshold) {
|
|
186345
|
-
writes.delete(
|
|
186450
|
+
writes.delete(path24);
|
|
186346
186451
|
awfEmit(void 0, curStat);
|
|
186347
186452
|
} else {
|
|
186348
186453
|
timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
|
|
186349
186454
|
}
|
|
186350
186455
|
});
|
|
186351
186456
|
}
|
|
186352
|
-
if (!writes.has(
|
|
186353
|
-
writes.set(
|
|
186457
|
+
if (!writes.has(path24)) {
|
|
186458
|
+
writes.set(path24, {
|
|
186354
186459
|
lastChange: now,
|
|
186355
186460
|
cancelWait: () => {
|
|
186356
|
-
writes.delete(
|
|
186461
|
+
writes.delete(path24);
|
|
186357
186462
|
clearTimeout(timeoutHandler);
|
|
186358
186463
|
return event;
|
|
186359
186464
|
}
|
|
@@ -186364,8 +186469,8 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186364
186469
|
/**
|
|
186365
186470
|
* Determines whether user has asked to ignore this path.
|
|
186366
186471
|
*/
|
|
186367
|
-
_isIgnored(
|
|
186368
|
-
if (this.options.atomic && DOT_RE.test(
|
|
186472
|
+
_isIgnored(path24, stats) {
|
|
186473
|
+
if (this.options.atomic && DOT_RE.test(path24))
|
|
186369
186474
|
return true;
|
|
186370
186475
|
if (!this._userIgnored) {
|
|
186371
186476
|
const { cwd } = this.options;
|
|
@@ -186375,17 +186480,17 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186375
186480
|
const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
|
|
186376
186481
|
this._userIgnored = anymatch(list, void 0);
|
|
186377
186482
|
}
|
|
186378
|
-
return this._userIgnored(
|
|
186483
|
+
return this._userIgnored(path24, stats);
|
|
186379
186484
|
}
|
|
186380
|
-
_isntIgnored(
|
|
186381
|
-
return !this._isIgnored(
|
|
186485
|
+
_isntIgnored(path24, stat4) {
|
|
186486
|
+
return !this._isIgnored(path24, stat4);
|
|
186382
186487
|
}
|
|
186383
186488
|
/**
|
|
186384
186489
|
* Provides a set of common helpers and properties relating to symlink handling.
|
|
186385
186490
|
* @param path file or directory pattern being watched
|
|
186386
186491
|
*/
|
|
186387
|
-
_getWatchHelpers(
|
|
186388
|
-
return new WatchHelper(
|
|
186492
|
+
_getWatchHelpers(path24) {
|
|
186493
|
+
return new WatchHelper(path24, this.options.followSymlinks, this);
|
|
186389
186494
|
}
|
|
186390
186495
|
// Directory helpers
|
|
186391
186496
|
// -----------------
|
|
@@ -186417,63 +186522,63 @@ var FSWatcher = class extends EventEmitter {
|
|
|
186417
186522
|
* @param item base path of item/directory
|
|
186418
186523
|
*/
|
|
186419
186524
|
_remove(directory, item, isDirectory) {
|
|
186420
|
-
const
|
|
186421
|
-
const fullPath = sp2.resolve(
|
|
186422
|
-
isDirectory = isDirectory != null ? isDirectory : this._watched.has(
|
|
186423
|
-
if (!this._throttle("remove",
|
|
186525
|
+
const path24 = sp2.join(directory, item);
|
|
186526
|
+
const fullPath = sp2.resolve(path24);
|
|
186527
|
+
isDirectory = isDirectory != null ? isDirectory : this._watched.has(path24) || this._watched.has(fullPath);
|
|
186528
|
+
if (!this._throttle("remove", path24, 100))
|
|
186424
186529
|
return;
|
|
186425
186530
|
if (!isDirectory && this._watched.size === 1) {
|
|
186426
186531
|
this.add(directory, item, true);
|
|
186427
186532
|
}
|
|
186428
|
-
const wp = this._getWatchedDir(
|
|
186533
|
+
const wp = this._getWatchedDir(path24);
|
|
186429
186534
|
const nestedDirectoryChildren = wp.getChildren();
|
|
186430
|
-
nestedDirectoryChildren.forEach((nested) => this._remove(
|
|
186535
|
+
nestedDirectoryChildren.forEach((nested) => this._remove(path24, nested));
|
|
186431
186536
|
const parent = this._getWatchedDir(directory);
|
|
186432
186537
|
const wasTracked = parent.has(item);
|
|
186433
186538
|
parent.remove(item);
|
|
186434
186539
|
if (this._symlinkPaths.has(fullPath)) {
|
|
186435
186540
|
this._symlinkPaths.delete(fullPath);
|
|
186436
186541
|
}
|
|
186437
|
-
let relPath =
|
|
186542
|
+
let relPath = path24;
|
|
186438
186543
|
if (this.options.cwd)
|
|
186439
|
-
relPath = sp2.relative(this.options.cwd,
|
|
186544
|
+
relPath = sp2.relative(this.options.cwd, path24);
|
|
186440
186545
|
if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
|
|
186441
186546
|
const event = this._pendingWrites.get(relPath).cancelWait();
|
|
186442
186547
|
if (event === EVENTS.ADD)
|
|
186443
186548
|
return;
|
|
186444
186549
|
}
|
|
186445
|
-
this._watched.delete(
|
|
186550
|
+
this._watched.delete(path24);
|
|
186446
186551
|
this._watched.delete(fullPath);
|
|
186447
186552
|
const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
|
|
186448
|
-
if (wasTracked && !this._isIgnored(
|
|
186449
|
-
this._emit(eventName,
|
|
186450
|
-
this._closePath(
|
|
186553
|
+
if (wasTracked && !this._isIgnored(path24))
|
|
186554
|
+
this._emit(eventName, path24);
|
|
186555
|
+
this._closePath(path24);
|
|
186451
186556
|
}
|
|
186452
186557
|
/**
|
|
186453
186558
|
* Closes all watchers for a path
|
|
186454
186559
|
*/
|
|
186455
|
-
_closePath(
|
|
186456
|
-
this._closeFile(
|
|
186457
|
-
const dir = sp2.dirname(
|
|
186458
|
-
this._getWatchedDir(dir).remove(sp2.basename(
|
|
186560
|
+
_closePath(path24) {
|
|
186561
|
+
this._closeFile(path24);
|
|
186562
|
+
const dir = sp2.dirname(path24);
|
|
186563
|
+
this._getWatchedDir(dir).remove(sp2.basename(path24));
|
|
186459
186564
|
}
|
|
186460
186565
|
/**
|
|
186461
186566
|
* Closes only file-specific watchers
|
|
186462
186567
|
*/
|
|
186463
|
-
_closeFile(
|
|
186464
|
-
const closers = this._closers.get(
|
|
186568
|
+
_closeFile(path24) {
|
|
186569
|
+
const closers = this._closers.get(path24);
|
|
186465
186570
|
if (!closers)
|
|
186466
186571
|
return;
|
|
186467
186572
|
closers.forEach((closer) => closer());
|
|
186468
|
-
this._closers.delete(
|
|
186573
|
+
this._closers.delete(path24);
|
|
186469
186574
|
}
|
|
186470
|
-
_addPathCloser(
|
|
186575
|
+
_addPathCloser(path24, closer) {
|
|
186471
186576
|
if (!closer)
|
|
186472
186577
|
return;
|
|
186473
|
-
let list = this._closers.get(
|
|
186578
|
+
let list = this._closers.get(path24);
|
|
186474
186579
|
if (!list) {
|
|
186475
186580
|
list = [];
|
|
186476
|
-
this._closers.set(
|
|
186581
|
+
this._closers.set(path24, list);
|
|
186477
186582
|
}
|
|
186478
186583
|
list.push(closer);
|
|
186479
186584
|
}
|
|
@@ -186591,7 +186696,6 @@ var StablePortAllocator = class {
|
|
|
186591
186696
|
import * as fs14 from "fs";
|
|
186592
186697
|
import * as path10 from "path";
|
|
186593
186698
|
import * as net from "net";
|
|
186594
|
-
import * as os7 from "os";
|
|
186595
186699
|
import { spawn } from "child_process";
|
|
186596
186700
|
|
|
186597
186701
|
// src/lib/bin/types.ts
|
|
@@ -186634,15 +186738,27 @@ import * as path9 from "path";
|
|
|
186634
186738
|
import * as os6 from "os";
|
|
186635
186739
|
import { createReadStream } from "fs";
|
|
186636
186740
|
import { createTarExtractor, extractTo } from "tar-vern";
|
|
186741
|
+
function getLibraryEnv(binary) {
|
|
186742
|
+
if (!binary.libraryPath) {
|
|
186743
|
+
return {};
|
|
186744
|
+
}
|
|
186745
|
+
const platform4 = os6.platform();
|
|
186746
|
+
if (platform4 === "darwin") {
|
|
186747
|
+
return { DYLD_LIBRARY_PATH: binary.libraryPath };
|
|
186748
|
+
} else if (platform4 === "linux") {
|
|
186749
|
+
return { LD_LIBRARY_PATH: binary.libraryPath };
|
|
186750
|
+
}
|
|
186751
|
+
return {};
|
|
186752
|
+
}
|
|
186637
186753
|
function getBinBaseDir() {
|
|
186638
186754
|
return path9.join(os6.homedir(), ".specific", "bin");
|
|
186639
186755
|
}
|
|
186640
186756
|
function getPlatformInfo() {
|
|
186641
|
-
const
|
|
186757
|
+
const platform4 = os6.platform();
|
|
186642
186758
|
const arch3 = os6.arch();
|
|
186643
|
-
if (
|
|
186759
|
+
if (platform4 !== "darwin" && platform4 !== "linux") {
|
|
186644
186760
|
throw new Error(
|
|
186645
|
-
`Unsupported platform: ${
|
|
186761
|
+
`Unsupported platform: ${platform4}. Only macOS and Linux are supported.`
|
|
186646
186762
|
);
|
|
186647
186763
|
}
|
|
186648
186764
|
const archStr = arch3;
|
|
@@ -186656,7 +186772,7 @@ function getPlatformInfo() {
|
|
|
186656
186772
|
`Unsupported architecture: ${arch3}. Only x64 and arm64 are supported.`
|
|
186657
186773
|
);
|
|
186658
186774
|
}
|
|
186659
|
-
return { platform:
|
|
186775
|
+
return { platform: platform4, arch: mappedArch };
|
|
186660
186776
|
}
|
|
186661
186777
|
function getBinaryDir(definition, version, platformInfo) {
|
|
186662
186778
|
return path9.join(
|
|
@@ -186832,7 +186948,7 @@ var postgresBinary = {
|
|
|
186832
186948
|
// Archive contains bin/ and lib/ directories at root
|
|
186833
186949
|
stripComponents: 0,
|
|
186834
186950
|
// Core PostgreSQL executables (in bin/ directory)
|
|
186835
|
-
executables: ["bin/postgres", "bin/initdb"],
|
|
186951
|
+
executables: ["bin/postgres", "bin/initdb", "bin/psql"],
|
|
186836
186952
|
// Library directory relative to root (for setting LD_LIBRARY_PATH)
|
|
186837
186953
|
libraryDir: "lib"
|
|
186838
186954
|
};
|
|
@@ -186904,18 +187020,6 @@ var drizzleGatewayBinary = {
|
|
|
186904
187020
|
};
|
|
186905
187021
|
|
|
186906
187022
|
// src/lib/dev/database-manager.ts
|
|
186907
|
-
function getLibraryEnv(binary) {
|
|
186908
|
-
if (!binary.libraryPath) {
|
|
186909
|
-
return {};
|
|
186910
|
-
}
|
|
186911
|
-
const platform5 = os7.platform();
|
|
186912
|
-
if (platform5 === "darwin") {
|
|
186913
|
-
return { DYLD_LIBRARY_PATH: binary.libraryPath };
|
|
186914
|
-
} else if (platform5 === "linux") {
|
|
186915
|
-
return { LD_LIBRARY_PATH: binary.libraryPath };
|
|
186916
|
-
}
|
|
186917
|
-
return {};
|
|
186918
|
-
}
|
|
186919
187023
|
async function startPostgres(pg, port, dataDir, onProgress) {
|
|
186920
187024
|
const binary = await ensureBinary(postgresBinary, void 0, onProgress);
|
|
186921
187025
|
const dbDataPath = path10.join(process.cwd(), dataDir, pg.name);
|
|
@@ -187271,7 +187375,7 @@ config {
|
|
|
187271
187375
|
this.name = "MissingConfigError";
|
|
187272
187376
|
}
|
|
187273
187377
|
};
|
|
187274
|
-
function resolveEnvValue(value, resources, secrets, configs, servicePort, serviceEndpoints, currentServicePorts) {
|
|
187378
|
+
function resolveEnvValue(value, resources, secrets, configs, servicePort, serviceEndpoints, currentServicePorts, publicUrls) {
|
|
187275
187379
|
if (typeof value === "string") {
|
|
187276
187380
|
return value;
|
|
187277
187381
|
}
|
|
@@ -187312,11 +187416,25 @@ function resolveEnvValue(value, resources, secrets, configs, servicePort, servic
|
|
|
187312
187416
|
}
|
|
187313
187417
|
switch (serviceRef.attribute) {
|
|
187314
187418
|
case "url":
|
|
187419
|
+
case "private_url":
|
|
187315
187420
|
return `localhost:${endpoint.port}`;
|
|
187316
187421
|
case "host":
|
|
187317
187422
|
return "localhost";
|
|
187318
187423
|
case "port":
|
|
187319
187424
|
return String(endpoint.port);
|
|
187425
|
+
case "public_url": {
|
|
187426
|
+
if (!publicUrls) {
|
|
187427
|
+
throw new Error("public_url reference used but no public URL map provided");
|
|
187428
|
+
}
|
|
187429
|
+
const k8sName = endpointName === "default" ? serviceRef.serviceName : `${serviceRef.serviceName}-${endpointName}`;
|
|
187430
|
+
const publicUrl = publicUrls.get(k8sName);
|
|
187431
|
+
if (!publicUrl) {
|
|
187432
|
+
throw new Error(
|
|
187433
|
+
`No public URL found for service "${serviceRef.serviceName}" endpoint "${endpointName}"`
|
|
187434
|
+
);
|
|
187435
|
+
}
|
|
187436
|
+
return publicUrl;
|
|
187437
|
+
}
|
|
187320
187438
|
default:
|
|
187321
187439
|
throw new Error(`Unknown service attribute: ${serviceRef.attribute}`);
|
|
187322
187440
|
}
|
|
@@ -187421,7 +187539,7 @@ function resolveEnvValue(value, resources, secrets, configs, servicePort, servic
|
|
|
187421
187539
|
throw new Error(`Unknown reference type`);
|
|
187422
187540
|
}
|
|
187423
187541
|
}
|
|
187424
|
-
function resolveEnv(env2, resources, secrets, configs, servicePort, serviceEndpoints, currentServicePorts) {
|
|
187542
|
+
function resolveEnv(env2, resources, secrets, configs, servicePort, serviceEndpoints, currentServicePorts, publicUrls) {
|
|
187425
187543
|
if (!env2) {
|
|
187426
187544
|
return {};
|
|
187427
187545
|
}
|
|
@@ -187434,7 +187552,8 @@ function resolveEnv(env2, resources, secrets, configs, servicePort, serviceEndpo
|
|
|
187434
187552
|
configs,
|
|
187435
187553
|
servicePort,
|
|
187436
187554
|
serviceEndpoints,
|
|
187437
|
-
currentServicePorts
|
|
187555
|
+
currentServicePorts,
|
|
187556
|
+
publicUrls
|
|
187438
187557
|
);
|
|
187439
187558
|
}
|
|
187440
187559
|
return resolved;
|
|
@@ -187458,7 +187577,7 @@ function resolveEnvForExec(env2, resources, secrets, configs) {
|
|
|
187458
187577
|
}
|
|
187459
187578
|
|
|
187460
187579
|
// src/lib/dev/service-runner.ts
|
|
187461
|
-
function startService(service, resources, secrets, configs, endpointPorts, serviceEndpoints, onLog) {
|
|
187580
|
+
function startService(service, resources, secrets, configs, endpointPorts, serviceEndpoints, onLog, publicUrls) {
|
|
187462
187581
|
const command = service.dev?.command ?? service.command;
|
|
187463
187582
|
if (!command) {
|
|
187464
187583
|
throw new Error(`Service "${service.name}" has no command`);
|
|
@@ -187475,7 +187594,8 @@ function startService(service, resources, secrets, configs, endpointPorts, servi
|
|
|
187475
187594
|
configs,
|
|
187476
187595
|
defaultPort,
|
|
187477
187596
|
serviceEndpoints,
|
|
187478
|
-
endpointPorts
|
|
187597
|
+
endpointPorts,
|
|
187598
|
+
publicUrls
|
|
187479
187599
|
);
|
|
187480
187600
|
const child = spawn2(command, {
|
|
187481
187601
|
shell: true,
|
|
@@ -188620,10 +188740,107 @@ function watchConfigFile(configPath, debounceMs, onChange) {
|
|
|
188620
188740
|
};
|
|
188621
188741
|
}
|
|
188622
188742
|
|
|
188623
|
-
// src/lib/dev/
|
|
188743
|
+
// src/lib/dev/subdomain-generator.ts
|
|
188624
188744
|
import * as fs18 from "fs";
|
|
188625
188745
|
import * as path15 from "path";
|
|
188626
|
-
import
|
|
188746
|
+
import { generateSlug } from "random-word-slugs";
|
|
188747
|
+
var StableSubdomainAllocator = class {
|
|
188748
|
+
tunnelsDir;
|
|
188749
|
+
tunnelsFilePath;
|
|
188750
|
+
baseSlug = null;
|
|
188751
|
+
constructor(projectRoot, key = "default") {
|
|
188752
|
+
this.tunnelsDir = path15.join(projectRoot, ".specific", "keys", key);
|
|
188753
|
+
this.tunnelsFilePath = path15.join(this.tunnelsDir, "tunnels.json");
|
|
188754
|
+
this.loadTunnels();
|
|
188755
|
+
}
|
|
188756
|
+
loadTunnels() {
|
|
188757
|
+
if (!fs18.existsSync(this.tunnelsFilePath)) {
|
|
188758
|
+
return;
|
|
188759
|
+
}
|
|
188760
|
+
try {
|
|
188761
|
+
const content = fs18.readFileSync(this.tunnelsFilePath, "utf-8");
|
|
188762
|
+
const data = JSON.parse(content);
|
|
188763
|
+
if (data.version === 1 && data.baseSlug) {
|
|
188764
|
+
this.baseSlug = data.baseSlug;
|
|
188765
|
+
}
|
|
188766
|
+
} catch {
|
|
188767
|
+
this.baseSlug = null;
|
|
188768
|
+
}
|
|
188769
|
+
}
|
|
188770
|
+
saveTunnels() {
|
|
188771
|
+
if (!fs18.existsSync(this.tunnelsDir)) {
|
|
188772
|
+
fs18.mkdirSync(this.tunnelsDir, { recursive: true });
|
|
188773
|
+
}
|
|
188774
|
+
const data = {
|
|
188775
|
+
version: 1,
|
|
188776
|
+
baseSlug: this.baseSlug
|
|
188777
|
+
};
|
|
188778
|
+
fs18.writeFileSync(this.tunnelsFilePath, JSON.stringify(data, null, 2));
|
|
188779
|
+
}
|
|
188780
|
+
generateBaseSlug() {
|
|
188781
|
+
return generateSlug(2, {
|
|
188782
|
+
format: "kebab",
|
|
188783
|
+
partsOfSpeech: ["adjective", "noun"],
|
|
188784
|
+
categories: {
|
|
188785
|
+
adjective: ["color", "appearance"],
|
|
188786
|
+
noun: ["animals"]
|
|
188787
|
+
}
|
|
188788
|
+
});
|
|
188789
|
+
}
|
|
188790
|
+
/**
|
|
188791
|
+
* Get the base slug, generating one if needed.
|
|
188792
|
+
*/
|
|
188793
|
+
getBaseSlug() {
|
|
188794
|
+
if (!this.baseSlug) {
|
|
188795
|
+
this.baseSlug = this.generateBaseSlug();
|
|
188796
|
+
this.saveTunnels();
|
|
188797
|
+
}
|
|
188798
|
+
return this.baseSlug;
|
|
188799
|
+
}
|
|
188800
|
+
/**
|
|
188801
|
+
* Allocate a subdomain for a service.
|
|
188802
|
+
* If multipleServices is true, appends the service name to the base slug.
|
|
188803
|
+
*/
|
|
188804
|
+
allocate(serviceName, multipleServices) {
|
|
188805
|
+
const baseSlug = this.getBaseSlug();
|
|
188806
|
+
if (multipleServices) {
|
|
188807
|
+
return `${baseSlug}-${serviceName}`;
|
|
188808
|
+
}
|
|
188809
|
+
return baseSlug;
|
|
188810
|
+
}
|
|
188811
|
+
};
|
|
188812
|
+
|
|
188813
|
+
// src/lib/dev/tunnel-manager.ts
|
|
188814
|
+
import localtunnel from "localtunnel";
|
|
188815
|
+
var TUNNEL_HOST = "https://tunnel.spcf.app";
|
|
188816
|
+
async function startTunnel(serviceName, endpointName, port, subdomain, callbacks) {
|
|
188817
|
+
const tunnel = await localtunnel({
|
|
188818
|
+
port,
|
|
188819
|
+
subdomain,
|
|
188820
|
+
host: TUNNEL_HOST
|
|
188821
|
+
});
|
|
188822
|
+
tunnel.on("error", (err) => {
|
|
188823
|
+
callbacks?.onError?.(serviceName, endpointName, err);
|
|
188824
|
+
});
|
|
188825
|
+
tunnel.on("close", () => {
|
|
188826
|
+
callbacks?.onClose?.(serviceName, endpointName);
|
|
188827
|
+
});
|
|
188828
|
+
return {
|
|
188829
|
+
serviceName,
|
|
188830
|
+
endpointName,
|
|
188831
|
+
localPort: port,
|
|
188832
|
+
url: tunnel.url,
|
|
188833
|
+
subdomain,
|
|
188834
|
+
stop: async () => {
|
|
188835
|
+
tunnel.close();
|
|
188836
|
+
}
|
|
188837
|
+
};
|
|
188838
|
+
}
|
|
188839
|
+
|
|
188840
|
+
// src/lib/dev/proxy-registry.ts
|
|
188841
|
+
import * as fs19 from "fs";
|
|
188842
|
+
import * as path16 from "path";
|
|
188843
|
+
import * as os7 from "os";
|
|
188627
188844
|
import * as net4 from "net";
|
|
188628
188845
|
var ProxyRegistryManager = class {
|
|
188629
188846
|
proxyDir;
|
|
@@ -188633,14 +188850,14 @@ var ProxyRegistryManager = class {
|
|
|
188633
188850
|
isOwner = false;
|
|
188634
188851
|
registryWatcher = null;
|
|
188635
188852
|
constructor() {
|
|
188636
|
-
this.proxyDir =
|
|
188637
|
-
this.ownerPath =
|
|
188638
|
-
this.registryPath =
|
|
188639
|
-
this.lockPath =
|
|
188853
|
+
this.proxyDir = path16.join(os7.homedir(), ".specific", "proxy");
|
|
188854
|
+
this.ownerPath = path16.join(this.proxyDir, "owner.json");
|
|
188855
|
+
this.registryPath = path16.join(this.proxyDir, "registry.json");
|
|
188856
|
+
this.lockPath = path16.join(this.proxyDir, "registry.lock");
|
|
188640
188857
|
}
|
|
188641
188858
|
ensureProxyDir() {
|
|
188642
|
-
if (!
|
|
188643
|
-
|
|
188859
|
+
if (!fs19.existsSync(this.proxyDir)) {
|
|
188860
|
+
fs19.mkdirSync(this.proxyDir, { recursive: true });
|
|
188644
188861
|
}
|
|
188645
188862
|
}
|
|
188646
188863
|
isProcessRunning(pid) {
|
|
@@ -188697,15 +188914,15 @@ var ProxyRegistryManager = class {
|
|
|
188697
188914
|
const startTime = Date.now();
|
|
188698
188915
|
while (Date.now() - startTime < timeoutMs) {
|
|
188699
188916
|
try {
|
|
188700
|
-
const fd =
|
|
188917
|
+
const fd = fs19.openSync(
|
|
188701
188918
|
this.lockPath,
|
|
188702
|
-
|
|
188919
|
+
fs19.constants.O_CREAT | fs19.constants.O_EXCL | fs19.constants.O_WRONLY
|
|
188703
188920
|
);
|
|
188704
|
-
|
|
188705
|
-
|
|
188921
|
+
fs19.writeSync(fd, String(process.pid));
|
|
188922
|
+
fs19.closeSync(fd);
|
|
188706
188923
|
return () => {
|
|
188707
188924
|
try {
|
|
188708
|
-
|
|
188925
|
+
fs19.unlinkSync(this.lockPath);
|
|
188709
188926
|
} catch {
|
|
188710
188927
|
}
|
|
188711
188928
|
};
|
|
@@ -188714,16 +188931,16 @@ var ProxyRegistryManager = class {
|
|
|
188714
188931
|
if (err.code === "EEXIST") {
|
|
188715
188932
|
try {
|
|
188716
188933
|
const lockPid = parseInt(
|
|
188717
|
-
|
|
188934
|
+
fs19.readFileSync(this.lockPath, "utf-8").trim(),
|
|
188718
188935
|
10
|
|
188719
188936
|
);
|
|
188720
188937
|
if (!this.isProcessRunning(lockPid)) {
|
|
188721
|
-
|
|
188938
|
+
fs19.unlinkSync(this.lockPath);
|
|
188722
188939
|
continue;
|
|
188723
188940
|
}
|
|
188724
188941
|
} catch {
|
|
188725
188942
|
try {
|
|
188726
|
-
|
|
188943
|
+
fs19.unlinkSync(this.lockPath);
|
|
188727
188944
|
} catch {
|
|
188728
188945
|
}
|
|
188729
188946
|
continue;
|
|
@@ -188743,8 +188960,8 @@ var ProxyRegistryManager = class {
|
|
|
188743
188960
|
async claimProxyOwnership(key) {
|
|
188744
188961
|
const releaseLock = await this.acquireLock();
|
|
188745
188962
|
try {
|
|
188746
|
-
if (
|
|
188747
|
-
const content =
|
|
188963
|
+
if (fs19.existsSync(this.ownerPath)) {
|
|
188964
|
+
const content = fs19.readFileSync(this.ownerPath, "utf-8");
|
|
188748
188965
|
const ownerFile2 = JSON.parse(content);
|
|
188749
188966
|
if (await this.isProxyOwnerHealthy(ownerFile2.owner.pid)) {
|
|
188750
188967
|
return false;
|
|
@@ -188774,11 +188991,11 @@ var ProxyRegistryManager = class {
|
|
|
188774
188991
|
}
|
|
188775
188992
|
const releaseLock = await this.acquireLock();
|
|
188776
188993
|
try {
|
|
188777
|
-
if (
|
|
188778
|
-
const content =
|
|
188994
|
+
if (fs19.existsSync(this.ownerPath)) {
|
|
188995
|
+
const content = fs19.readFileSync(this.ownerPath, "utf-8");
|
|
188779
188996
|
const ownerFile = JSON.parse(content);
|
|
188780
188997
|
if (ownerFile.owner.pid === process.pid) {
|
|
188781
|
-
|
|
188998
|
+
fs19.unlinkSync(this.ownerPath);
|
|
188782
188999
|
}
|
|
188783
189000
|
}
|
|
188784
189001
|
this.isOwner = false;
|
|
@@ -188790,12 +189007,12 @@ var ProxyRegistryManager = class {
|
|
|
188790
189007
|
* Get the current proxy owner.
|
|
188791
189008
|
*/
|
|
188792
189009
|
async getProxyOwner() {
|
|
188793
|
-
if (!
|
|
189010
|
+
if (!fs19.existsSync(this.ownerPath)) {
|
|
188794
189011
|
return null;
|
|
188795
189012
|
}
|
|
188796
189013
|
const releaseLock = await this.acquireLock();
|
|
188797
189014
|
try {
|
|
188798
|
-
const content =
|
|
189015
|
+
const content = fs19.readFileSync(this.ownerPath, "utf-8");
|
|
188799
189016
|
const ownerFile = JSON.parse(content);
|
|
188800
189017
|
if (!await this.isProxyOwnerHealthy(ownerFile.owner.pid)) {
|
|
188801
189018
|
return null;
|
|
@@ -188889,7 +189106,7 @@ var ProxyRegistryManager = class {
|
|
|
188889
189106
|
*/
|
|
188890
189107
|
watchRegistry(onChange) {
|
|
188891
189108
|
this.ensureProxyDir();
|
|
188892
|
-
if (!
|
|
189109
|
+
if (!fs19.existsSync(this.registryPath)) {
|
|
188893
189110
|
const emptyRegistry = {
|
|
188894
189111
|
version: 1,
|
|
188895
189112
|
keys: {},
|
|
@@ -188933,13 +189150,13 @@ var ProxyRegistryManager = class {
|
|
|
188933
189150
|
async attemptElection(key) {
|
|
188934
189151
|
const releaseLock = await this.acquireLock();
|
|
188935
189152
|
try {
|
|
188936
|
-
if (
|
|
188937
|
-
const content =
|
|
189153
|
+
if (fs19.existsSync(this.ownerPath)) {
|
|
189154
|
+
const content = fs19.readFileSync(this.ownerPath, "utf-8");
|
|
188938
189155
|
const ownerFile2 = JSON.parse(content);
|
|
188939
189156
|
if (await this.isProxyOwnerHealthy(ownerFile2.owner.pid)) {
|
|
188940
189157
|
return false;
|
|
188941
189158
|
}
|
|
188942
|
-
|
|
189159
|
+
fs19.unlinkSync(this.ownerPath);
|
|
188943
189160
|
}
|
|
188944
189161
|
const ownerFile = {
|
|
188945
189162
|
version: 1,
|
|
@@ -188957,7 +189174,7 @@ var ProxyRegistryManager = class {
|
|
|
188957
189174
|
}
|
|
188958
189175
|
}
|
|
188959
189176
|
readRegistry() {
|
|
188960
|
-
if (!
|
|
189177
|
+
if (!fs19.existsSync(this.registryPath)) {
|
|
188961
189178
|
return {
|
|
188962
189179
|
version: 1,
|
|
188963
189180
|
keys: {},
|
|
@@ -188965,7 +189182,7 @@ var ProxyRegistryManager = class {
|
|
|
188965
189182
|
};
|
|
188966
189183
|
}
|
|
188967
189184
|
try {
|
|
188968
|
-
const content =
|
|
189185
|
+
const content = fs19.readFileSync(this.registryPath, "utf-8");
|
|
188969
189186
|
return JSON.parse(content);
|
|
188970
189187
|
} catch {
|
|
188971
189188
|
return {
|
|
@@ -188978,8 +189195,8 @@ var ProxyRegistryManager = class {
|
|
|
188978
189195
|
writeFileAtomic(filePath, data) {
|
|
188979
189196
|
this.ensureProxyDir();
|
|
188980
189197
|
const tmpPath = filePath + ".tmp";
|
|
188981
|
-
|
|
188982
|
-
|
|
189198
|
+
fs19.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
|
|
189199
|
+
fs19.renameSync(tmpPath, filePath);
|
|
188983
189200
|
}
|
|
188984
189201
|
};
|
|
188985
189202
|
|
|
@@ -189078,17 +189295,12 @@ function ConfigInput({ configName, onSubmit, onCancel }) {
|
|
|
189078
189295
|
return /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text5, null, "Enter value for config ", /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, configName), ":"), /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, "> "), /* @__PURE__ */ React5.createElement(Text5, null, value), /* @__PURE__ */ React5.createElement(Text5, { color: "gray" }, "|")), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "(Press Enter to save, Esc to cancel)"));
|
|
189079
189296
|
}
|
|
189080
189297
|
|
|
189081
|
-
// src/lib/ui/interactive.ts
|
|
189082
|
-
function isInteractive() {
|
|
189083
|
-
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
189084
|
-
}
|
|
189085
|
-
|
|
189086
189298
|
// src/commands/dev.tsx
|
|
189087
189299
|
var COLORS = ["cyan", "yellow", "green", "magenta", "blue"];
|
|
189088
|
-
function DevUI({ instanceKey }) {
|
|
189300
|
+
function DevUI({ instanceKey, tunnelEnabled }) {
|
|
189089
189301
|
const { exit } = useApp2();
|
|
189090
189302
|
const [state, setState] = useState5(() => {
|
|
189091
|
-
const caExists = caFilesExist();
|
|
189303
|
+
const caExists = tunnelEnabled || caFilesExist();
|
|
189092
189304
|
return {
|
|
189093
189305
|
status: caExists ? "loading" : "installing-ca",
|
|
189094
189306
|
...caExists ? {} : { caInstallPhase: "installing" },
|
|
@@ -189097,7 +189309,9 @@ function DevUI({ instanceKey }) {
|
|
|
189097
189309
|
services: [],
|
|
189098
189310
|
output: [],
|
|
189099
189311
|
colorMap: /* @__PURE__ */ new Map(),
|
|
189100
|
-
isProxyOwner: false
|
|
189312
|
+
isProxyOwner: false,
|
|
189313
|
+
tunnels: /* @__PURE__ */ new Map(),
|
|
189314
|
+
tunnelStatus: /* @__PURE__ */ new Map()
|
|
189101
189315
|
};
|
|
189102
189316
|
});
|
|
189103
189317
|
useEffect3(() => {
|
|
@@ -189129,6 +189343,7 @@ function DevUI({ instanceKey }) {
|
|
|
189129
189343
|
const drizzleGatewayRef = useRef(null);
|
|
189130
189344
|
const registryWatcherCleanupRef = useRef(null);
|
|
189131
189345
|
const electionIntervalRef = useRef(null);
|
|
189346
|
+
const tunnelsRef = useRef([]);
|
|
189132
189347
|
const proxyRef = useRef(null);
|
|
189133
189348
|
const adminServerRef = useRef(null);
|
|
189134
189349
|
const servicesRef = useRef([]);
|
|
@@ -189162,7 +189377,9 @@ function DevUI({ instanceKey }) {
|
|
|
189162
189377
|
// Stop Drizzle Gateway
|
|
189163
189378
|
drizzleGatewayRef.current?.stop(),
|
|
189164
189379
|
// Stop all resources
|
|
189165
|
-
...[...resourcesRef.current.values()].map((resource) => resource.stop())
|
|
189380
|
+
...[...resourcesRef.current.values()].map((resource) => resource.stop()),
|
|
189381
|
+
// Stop all tunnels
|
|
189382
|
+
...tunnelsRef.current.map((tunnel) => tunnel.stop())
|
|
189166
189383
|
]);
|
|
189167
189384
|
if (proxyRegistryRef.current) {
|
|
189168
189385
|
await proxyRegistryRef.current.unregisterServices(instanceKey);
|
|
@@ -189199,14 +189416,20 @@ function DevUI({ instanceKey }) {
|
|
|
189199
189416
|
// Stop Drizzle Gateway
|
|
189200
189417
|
drizzleGatewayRef.current?.stop(),
|
|
189201
189418
|
// Stop all resources
|
|
189202
|
-
...[...resourcesRef.current.values()].map((resource) => resource.stop())
|
|
189419
|
+
...[...resourcesRef.current.values()].map((resource) => resource.stop()),
|
|
189420
|
+
// Stop all tunnels
|
|
189421
|
+
...tunnelsRef.current.map((tunnel) => tunnel.stop())
|
|
189203
189422
|
]);
|
|
189423
|
+
if (tunnelsRef.current.length > 0) {
|
|
189424
|
+
await new Promise((resolve7) => setTimeout(resolve7, 1500));
|
|
189425
|
+
}
|
|
189204
189426
|
electricInstancesRef.current = [];
|
|
189205
189427
|
drizzleGatewayRef.current = null;
|
|
189206
189428
|
proxyRef.current = null;
|
|
189207
189429
|
adminServerRef.current = null;
|
|
189208
189430
|
servicesRef.current = [];
|
|
189209
189431
|
resourcesRef.current = /* @__PURE__ */ new Map();
|
|
189432
|
+
tunnelsRef.current = [];
|
|
189210
189433
|
if (proxyRegistryRef.current) {
|
|
189211
189434
|
await proxyRegistryRef.current.unregisterServices(instanceKey);
|
|
189212
189435
|
await proxyRegistryRef.current.releaseProxyOwnership();
|
|
@@ -189222,7 +189445,9 @@ function DevUI({ instanceKey }) {
|
|
|
189222
189445
|
resourceStatus: /* @__PURE__ */ new Map(),
|
|
189223
189446
|
services: [],
|
|
189224
189447
|
proxy: void 0,
|
|
189225
|
-
isProxyOwner: false
|
|
189448
|
+
isProxyOwner: false,
|
|
189449
|
+
tunnels: /* @__PURE__ */ new Map(),
|
|
189450
|
+
tunnelStatus: /* @__PURE__ */ new Map()
|
|
189226
189451
|
}));
|
|
189227
189452
|
setReloadTrigger((t) => t + 1);
|
|
189228
189453
|
};
|
|
@@ -189263,10 +189488,10 @@ function DevUI({ instanceKey }) {
|
|
|
189263
189488
|
}, [state.status]);
|
|
189264
189489
|
useEffect3(() => {
|
|
189265
189490
|
if (state.status !== "running") return;
|
|
189266
|
-
const configPath =
|
|
189491
|
+
const configPath = path17.join(process.cwd(), "specific.hcl");
|
|
189267
189492
|
const watcher = watchConfigFile(configPath, 1e3, () => {
|
|
189268
189493
|
try {
|
|
189269
|
-
const hcl =
|
|
189494
|
+
const hcl = fs20.readFileSync(configPath, "utf-8");
|
|
189270
189495
|
parseConfig(hcl).then(() => {
|
|
189271
189496
|
triggerReload();
|
|
189272
189497
|
}).catch((err) => {
|
|
@@ -189391,8 +189616,8 @@ function DevUI({ instanceKey }) {
|
|
|
189391
189616
|
}));
|
|
189392
189617
|
return;
|
|
189393
189618
|
}
|
|
189394
|
-
const configPath =
|
|
189395
|
-
if (!
|
|
189619
|
+
const configPath = path17.join(process.cwd(), "specific.hcl");
|
|
189620
|
+
if (!fs20.existsSync(configPath)) {
|
|
189396
189621
|
writeLog("system", "Waiting for specific.hcl to appear");
|
|
189397
189622
|
setState((s) => ({
|
|
189398
189623
|
...s,
|
|
@@ -189411,7 +189636,7 @@ function DevUI({ instanceKey }) {
|
|
|
189411
189636
|
}
|
|
189412
189637
|
let config2;
|
|
189413
189638
|
try {
|
|
189414
|
-
const hcl =
|
|
189639
|
+
const hcl = fs20.readFileSync(configPath, "utf-8");
|
|
189415
189640
|
config2 = await parseConfig(hcl);
|
|
189416
189641
|
} catch (err) {
|
|
189417
189642
|
setState((s) => ({
|
|
@@ -189517,7 +189742,7 @@ function DevUI({ instanceKey }) {
|
|
|
189517
189742
|
const drizzleGateway = await startDrizzleGateway(
|
|
189518
189743
|
postgresResources,
|
|
189519
189744
|
drizzlePort,
|
|
189520
|
-
|
|
189745
|
+
path17.join(process.cwd(), ".specific", "keys", instanceKey)
|
|
189521
189746
|
);
|
|
189522
189747
|
startedDrizzleGateway = drizzleGateway;
|
|
189523
189748
|
drizzleGatewayRef.current = drizzleGateway;
|
|
@@ -189617,6 +189842,39 @@ Add them to the config block in specific.local`);
|
|
|
189617
189842
|
serviceEndpointPorts.set(service.name, endpointPorts);
|
|
189618
189843
|
serviceEndpoints.set(service.name, endpointInfos);
|
|
189619
189844
|
}
|
|
189845
|
+
const serviceInfos = [];
|
|
189846
|
+
const exposedServices = [];
|
|
189847
|
+
for (const service of config2.services) {
|
|
189848
|
+
const endpointInfos = serviceEndpoints.get(service.name) || [];
|
|
189849
|
+
for (const info of endpointInfos) {
|
|
189850
|
+
const endpointConfig = service.endpoints.find(
|
|
189851
|
+
(e) => e.name === info.endpointName
|
|
189852
|
+
);
|
|
189853
|
+
if (endpointConfig?.public) {
|
|
189854
|
+
const proxyName = info.endpointName === "default" ? service.name : `${service.name}-${info.endpointName}`;
|
|
189855
|
+
exposedServices.push({ name: proxyName, port: info.port });
|
|
189856
|
+
serviceInfos.push({
|
|
189857
|
+
serviceName: proxyName,
|
|
189858
|
+
port: info.port
|
|
189859
|
+
});
|
|
189860
|
+
}
|
|
189861
|
+
}
|
|
189862
|
+
}
|
|
189863
|
+
const publicUrls = /* @__PURE__ */ new Map();
|
|
189864
|
+
let subdomainAllocator;
|
|
189865
|
+
if (tunnelEnabled && exposedServices.length > 0) {
|
|
189866
|
+
subdomainAllocator = new StableSubdomainAllocator(process.cwd(), instanceKey);
|
|
189867
|
+
const multipleServices = exposedServices.length > 1;
|
|
189868
|
+
for (const svc of exposedServices) {
|
|
189869
|
+
const subdomain = subdomainAllocator.allocate(svc.name, multipleServices);
|
|
189870
|
+
publicUrls.set(svc.name, `${subdomain}.tunnel.spcf.app`);
|
|
189871
|
+
}
|
|
189872
|
+
} else {
|
|
189873
|
+
const keySuffix = instanceKey === "default" ? "" : `.${instanceKey}`;
|
|
189874
|
+
for (const svc of exposedServices) {
|
|
189875
|
+
publicUrls.set(svc.name, `${svc.name}${keySuffix}.local.spcf.app`);
|
|
189876
|
+
}
|
|
189877
|
+
}
|
|
189620
189878
|
const services2 = [];
|
|
189621
189879
|
for (const service of config2.services) {
|
|
189622
189880
|
if (cancelled) break;
|
|
@@ -189640,7 +189898,8 @@ Add them to the config block in specific.local`);
|
|
|
189640
189898
|
configs,
|
|
189641
189899
|
endpointPorts,
|
|
189642
189900
|
serviceEndpoints,
|
|
189643
|
-
(line) => addLog(line, colorMap)
|
|
189901
|
+
(line) => addLog(line, colorMap),
|
|
189902
|
+
publicUrls
|
|
189644
189903
|
);
|
|
189645
189904
|
services2.push(running);
|
|
189646
189905
|
startedServices.push(running);
|
|
@@ -189668,154 +189927,195 @@ Add them to the config block in specific.local`);
|
|
|
189668
189927
|
}
|
|
189669
189928
|
}
|
|
189670
189929
|
if (cancelled) return;
|
|
189671
|
-
|
|
189672
|
-
|
|
189673
|
-
|
|
189674
|
-
|
|
189675
|
-
|
|
189676
|
-
const
|
|
189677
|
-
|
|
189678
|
-
);
|
|
189679
|
-
|
|
189680
|
-
|
|
189681
|
-
|
|
189682
|
-
|
|
189683
|
-
|
|
189684
|
-
|
|
189685
|
-
|
|
189686
|
-
|
|
189687
|
-
|
|
189688
|
-
|
|
189689
|
-
|
|
189690
|
-
|
|
189691
|
-
|
|
189692
|
-
|
|
189693
|
-
|
|
189694
|
-
|
|
189695
|
-
|
|
189696
|
-
|
|
189697
|
-
|
|
189698
|
-
|
|
189699
|
-
|
|
189700
|
-
|
|
189701
|
-
|
|
189702
|
-
|
|
189703
|
-
|
|
189704
|
-
|
|
189705
|
-
|
|
189706
|
-
|
|
189707
|
-
|
|
189708
|
-
|
|
189709
|
-
|
|
189710
|
-
|
|
189711
|
-
|
|
189712
|
-
|
|
189713
|
-
|
|
189714
|
-
|
|
189715
|
-
|
|
189716
|
-
|
|
189717
|
-
serviceInfos,
|
|
189718
|
-
startedDrizzleGateway?.port
|
|
189719
|
-
);
|
|
189720
|
-
writeLog("system", `Registered ${serviceInfos.length} services with proxy registry`);
|
|
189721
|
-
const becameProxyOwner = await proxyRegistry.claimProxyOwnership(instanceKey);
|
|
189722
|
-
if (becameProxyOwner) {
|
|
189723
|
-
writeLog("system", "Claimed proxy ownership, starting HTTP proxy");
|
|
189724
|
-
try {
|
|
189725
|
-
const currentServices = await proxyRegistry.getAllServices();
|
|
189726
|
-
const registeredKeys = [...new Set(currentServices.map((s) => s.key))];
|
|
189727
|
-
const certificate = generateCertificate("local.spcf.app", registeredKeys);
|
|
189728
|
-
const proxy2 = await startHttpProxy(
|
|
189729
|
-
exposedServices,
|
|
189730
|
-
certificate,
|
|
189731
|
-
getState,
|
|
189732
|
-
instanceKey
|
|
189733
|
-
);
|
|
189734
|
-
startedProxy = proxy2;
|
|
189735
|
-
proxyRef.current = proxy2;
|
|
189736
|
-
setState((s) => ({ ...s, proxy: proxy2, isProxyOwner: true }));
|
|
189737
|
-
const knownKeys = new Set(registeredKeys);
|
|
189738
|
-
registryWatcherCleanupRef.current = proxyRegistry.watchRegistry(async (updatedServices, updatedKeys) => {
|
|
189739
|
-
writeLog("system", `Registry updated: ${updatedServices.length} services`);
|
|
189740
|
-
proxy2.updateServices(updatedServices, updatedKeys);
|
|
189741
|
-
const newKeyNames = Object.keys(updatedKeys).filter((k) => !knownKeys.has(k));
|
|
189742
|
-
if (newKeyNames.length > 0) {
|
|
189743
|
-
writeLog("system", `New keys detected: ${newKeyNames.join(", ")} - regenerating certificate`);
|
|
189744
|
-
for (const key of newKeyNames) {
|
|
189745
|
-
knownKeys.add(key);
|
|
189930
|
+
if (tunnelEnabled) {
|
|
189931
|
+
writeLog("system", "Tunnel mode enabled, starting tunnels for public services");
|
|
189932
|
+
if (exposedServices.length === 0) {
|
|
189933
|
+
writeLog("system", "No public services to tunnel");
|
|
189934
|
+
} else {
|
|
189935
|
+
const multipleServices = exposedServices.length > 1;
|
|
189936
|
+
const tunnelInstances = [];
|
|
189937
|
+
const tunnelStatusMap = /* @__PURE__ */ new Map();
|
|
189938
|
+
for (const svc of exposedServices) {
|
|
189939
|
+
tunnelStatusMap.set(svc.name, "connecting");
|
|
189940
|
+
}
|
|
189941
|
+
setState((s) => ({ ...s, tunnelStatus: new Map(tunnelStatusMap) }));
|
|
189942
|
+
for (const svc of exposedServices) {
|
|
189943
|
+
if (cancelled) return;
|
|
189944
|
+
const subdomain = subdomainAllocator.allocate(svc.name, multipleServices);
|
|
189945
|
+
writeLog("system", `Starting tunnel for ${svc.name} on port ${svc.port} (subdomain: ${subdomain})`);
|
|
189946
|
+
try {
|
|
189947
|
+
const tunnel = await startTunnel(
|
|
189948
|
+
svc.name,
|
|
189949
|
+
"default",
|
|
189950
|
+
svc.port,
|
|
189951
|
+
subdomain,
|
|
189952
|
+
{
|
|
189953
|
+
onError: (serviceName, _endpointName, error) => {
|
|
189954
|
+
writeLog("system:error", `Tunnel error for ${serviceName}: ${error.message}`);
|
|
189955
|
+
},
|
|
189956
|
+
onClose: (serviceName) => {
|
|
189957
|
+
writeLog("system", `Tunnel closed for ${serviceName}`);
|
|
189958
|
+
}
|
|
189959
|
+
}
|
|
189960
|
+
);
|
|
189961
|
+
tunnelInstances.push(tunnel);
|
|
189962
|
+
tunnelsRef.current = [...tunnelInstances];
|
|
189963
|
+
tunnelStatusMap.set(svc.name, "connected");
|
|
189964
|
+
setState((s) => ({
|
|
189965
|
+
...s,
|
|
189966
|
+
tunnels: new Map([...s.tunnels, [svc.name, tunnel]]),
|
|
189967
|
+
tunnelStatus: new Map(tunnelStatusMap)
|
|
189968
|
+
}));
|
|
189969
|
+
writeLog("system", `Tunnel ready for ${svc.name}: ${tunnel.url}`);
|
|
189970
|
+
} catch (err) {
|
|
189971
|
+
const errorMsg = `Failed to start tunnel for ${svc.name}: ${err instanceof Error ? err.message : String(err)}`;
|
|
189972
|
+
writeLog("system:error", errorMsg);
|
|
189973
|
+
tunnelStatusMap.set(svc.name, "error");
|
|
189974
|
+
for (const t of tunnelInstances) {
|
|
189975
|
+
await t.stop();
|
|
189746
189976
|
}
|
|
189747
|
-
|
|
189748
|
-
|
|
189749
|
-
|
|
189977
|
+
setState((s) => ({
|
|
189978
|
+
...s,
|
|
189979
|
+
status: "error",
|
|
189980
|
+
error: errorMsg,
|
|
189981
|
+
tunnelStatus: new Map(tunnelStatusMap)
|
|
189982
|
+
}));
|
|
189983
|
+
return;
|
|
189750
189984
|
}
|
|
189751
|
-
}
|
|
189752
|
-
const currentKeys = await proxyRegistry.getAllKeyRegistrations();
|
|
189753
|
-
proxy2.updateServices(currentServices, currentKeys);
|
|
189754
|
-
writeLog("system", `Loaded ${currentServices.length} services from registry`);
|
|
189755
|
-
} catch (err) {
|
|
189756
|
-
const errorMsg = `Failed to start HTTP proxy: ${err instanceof Error ? err.message : String(err)}`;
|
|
189757
|
-
writeLog("system:error", errorMsg);
|
|
189758
|
-
setState((s) => ({
|
|
189759
|
-
...s,
|
|
189760
|
-
status: "error",
|
|
189761
|
-
error: errorMsg
|
|
189762
|
-
}));
|
|
189763
|
-
return;
|
|
189985
|
+
}
|
|
189764
189986
|
}
|
|
189765
189987
|
} else {
|
|
189766
|
-
|
|
189767
|
-
|
|
189768
|
-
|
|
189988
|
+
const runningServicePorts = /* @__PURE__ */ new Map();
|
|
189989
|
+
for (const s of services2) {
|
|
189990
|
+
runningServicePorts.set(s.name, s.ports.get("default"));
|
|
189991
|
+
}
|
|
189992
|
+
const projectId = hasProjectId() ? readProjectId() : void 0;
|
|
189993
|
+
const getState = () => ({
|
|
189994
|
+
status: "running",
|
|
189995
|
+
services: config2.services.filter((svc) => runningServicePorts.has(svc.name) || svc.serve).map((svc) => ({
|
|
189996
|
+
name: svc.name,
|
|
189997
|
+
port: runningServicePorts.get(svc.name),
|
|
189998
|
+
exposed: !!svc.serve || svc.endpoints.some((e) => e.public),
|
|
189999
|
+
env: svc.env
|
|
190000
|
+
})),
|
|
190001
|
+
resources: [...resources2.entries()].map(([name, r]) => ({
|
|
190002
|
+
name,
|
|
190003
|
+
type: r.type,
|
|
190004
|
+
port: r.port,
|
|
190005
|
+
host: r.host,
|
|
190006
|
+
syncEnabled: r.type === "postgres" && syncDatabases.has(name)
|
|
190007
|
+
})),
|
|
190008
|
+
projectId
|
|
190009
|
+
});
|
|
190010
|
+
const adminServer = await startAdminServer(getState);
|
|
190011
|
+
adminServerRef.current = adminServer;
|
|
190012
|
+
writeLog("system", `Admin API server started on port ${adminServer.port}`);
|
|
190013
|
+
await proxyRegistry.registerServices(
|
|
190014
|
+
instanceKey,
|
|
190015
|
+
adminServer.port,
|
|
190016
|
+
serviceInfos,
|
|
190017
|
+
startedDrizzleGateway?.port
|
|
190018
|
+
);
|
|
190019
|
+
writeLog("system", `Registered ${serviceInfos.length} services with proxy registry`);
|
|
190020
|
+
const becameProxyOwner = await proxyRegistry.claimProxyOwnership(instanceKey);
|
|
190021
|
+
if (becameProxyOwner) {
|
|
190022
|
+
writeLog("system", "Claimed proxy ownership, starting HTTP proxy");
|
|
189769
190023
|
try {
|
|
189770
|
-
|
|
189771
|
-
|
|
189772
|
-
|
|
189773
|
-
|
|
189774
|
-
|
|
189775
|
-
|
|
189776
|
-
|
|
189777
|
-
|
|
189778
|
-
|
|
189779
|
-
|
|
189780
|
-
|
|
189781
|
-
}
|
|
190024
|
+
const currentServices = await proxyRegistry.getAllServices();
|
|
190025
|
+
const registeredKeys = [...new Set(currentServices.map((s) => s.key))];
|
|
190026
|
+
const certificate = generateCertificate("local.spcf.app", registeredKeys);
|
|
190027
|
+
const proxy2 = await startHttpProxy(
|
|
190028
|
+
exposedServices,
|
|
190029
|
+
certificate,
|
|
190030
|
+
getState,
|
|
190031
|
+
instanceKey
|
|
190032
|
+
);
|
|
190033
|
+
startedProxy = proxy2;
|
|
190034
|
+
proxyRef.current = proxy2;
|
|
190035
|
+
setState((s) => ({ ...s, proxy: proxy2, isProxyOwner: true }));
|
|
190036
|
+
const knownKeys = new Set(registeredKeys);
|
|
190037
|
+
registryWatcherCleanupRef.current = proxyRegistry.watchRegistry(async (updatedServices, updatedKeys) => {
|
|
190038
|
+
writeLog("system", `Registry updated: ${updatedServices.length} services`);
|
|
190039
|
+
proxy2.updateServices(updatedServices, updatedKeys);
|
|
190040
|
+
const newKeyNames = Object.keys(updatedKeys).filter((k) => !knownKeys.has(k));
|
|
190041
|
+
if (newKeyNames.length > 0) {
|
|
190042
|
+
writeLog("system", `New keys detected: ${newKeyNames.join(", ")} - regenerating certificate`);
|
|
190043
|
+
for (const key of newKeyNames) {
|
|
190044
|
+
knownKeys.add(key);
|
|
190045
|
+
}
|
|
190046
|
+
const allKeys = [...knownKeys];
|
|
190047
|
+
const newCertificate = generateCertificate("local.spcf.app", allKeys);
|
|
190048
|
+
proxy2.updateCertificate(newCertificate);
|
|
190049
|
+
}
|
|
190050
|
+
});
|
|
190051
|
+
const currentKeys = await proxyRegistry.getAllKeyRegistrations();
|
|
190052
|
+
proxy2.updateServices(currentServices, currentKeys);
|
|
190053
|
+
writeLog("system", `Loaded ${currentServices.length} services from registry`);
|
|
190054
|
+
} catch (err) {
|
|
190055
|
+
const errorMsg = `Failed to start HTTP proxy: ${err instanceof Error ? err.message : String(err)}`;
|
|
190056
|
+
writeLog("system:error", errorMsg);
|
|
190057
|
+
setState((s) => ({
|
|
190058
|
+
...s,
|
|
190059
|
+
status: "error",
|
|
190060
|
+
error: errorMsg
|
|
190061
|
+
}));
|
|
189782
190062
|
return;
|
|
189783
190063
|
}
|
|
189784
|
-
|
|
189785
|
-
|
|
189786
|
-
|
|
189787
|
-
|
|
189788
|
-
|
|
189789
|
-
|
|
190064
|
+
} else {
|
|
190065
|
+
writeLog("system", "Another instance owns the proxy, starting election watcher");
|
|
190066
|
+
setState((s) => ({ ...s, isProxyOwner: false }));
|
|
190067
|
+
const isProcessRunning = (pid) => {
|
|
190068
|
+
try {
|
|
190069
|
+
process.kill(pid, 0);
|
|
190070
|
+
return true;
|
|
190071
|
+
} catch {
|
|
190072
|
+
return false;
|
|
190073
|
+
}
|
|
190074
|
+
};
|
|
190075
|
+
electionIntervalRef.current = setInterval(async () => {
|
|
190076
|
+
if (cancelled || shuttingDown.current) {
|
|
189790
190077
|
if (electionIntervalRef.current) {
|
|
189791
190078
|
clearInterval(electionIntervalRef.current);
|
|
189792
190079
|
electionIntervalRef.current = null;
|
|
189793
190080
|
}
|
|
189794
|
-
|
|
189795
|
-
|
|
189796
|
-
|
|
189797
|
-
|
|
189798
|
-
|
|
189799
|
-
|
|
189800
|
-
|
|
189801
|
-
|
|
189802
|
-
|
|
189803
|
-
|
|
189804
|
-
|
|
189805
|
-
|
|
189806
|
-
|
|
189807
|
-
|
|
189808
|
-
|
|
189809
|
-
|
|
189810
|
-
|
|
189811
|
-
|
|
189812
|
-
|
|
189813
|
-
|
|
189814
|
-
|
|
190081
|
+
return;
|
|
190082
|
+
}
|
|
190083
|
+
const owner = await proxyRegistry.getProxyOwner();
|
|
190084
|
+
if (!owner || !isProcessRunning(owner.pid)) {
|
|
190085
|
+
writeLog("system", "Proxy owner died, attempting election");
|
|
190086
|
+
const won = await proxyRegistry.attemptElection(instanceKey);
|
|
190087
|
+
if (won) {
|
|
190088
|
+
writeLog("system", "Won election, starting HTTP proxy");
|
|
190089
|
+
if (electionIntervalRef.current) {
|
|
190090
|
+
clearInterval(electionIntervalRef.current);
|
|
190091
|
+
electionIntervalRef.current = null;
|
|
190092
|
+
}
|
|
190093
|
+
try {
|
|
190094
|
+
const electionServices = await proxyRegistry.getAllServices();
|
|
190095
|
+
const electionKeyRegistrations = await proxyRegistry.getAllKeyRegistrations();
|
|
190096
|
+
const electionKeyNames = Object.keys(electionKeyRegistrations);
|
|
190097
|
+
const certificate = generateCertificate("local.spcf.app", electionKeyNames);
|
|
190098
|
+
const proxy2 = await startHttpProxy(
|
|
190099
|
+
exposedServices,
|
|
190100
|
+
certificate,
|
|
190101
|
+
getState,
|
|
190102
|
+
instanceKey
|
|
190103
|
+
);
|
|
190104
|
+
startedProxy = proxy2;
|
|
190105
|
+
proxyRef.current = proxy2;
|
|
190106
|
+
setState((s) => ({ ...s, proxy: proxy2, isProxyOwner: true }));
|
|
190107
|
+
registryWatcherCleanupRef.current = proxyRegistry.watchRegistry((updatedServices, updatedKeys) => {
|
|
190108
|
+
writeLog("system", `Registry updated: ${updatedServices.length} services`);
|
|
190109
|
+
proxy2.updateServices(updatedServices, updatedKeys);
|
|
190110
|
+
});
|
|
190111
|
+
proxy2.updateServices(electionServices, electionKeyRegistrations);
|
|
190112
|
+
} catch (err) {
|
|
190113
|
+
writeLog("system:error", `Failed to start proxy after election: ${err}`);
|
|
190114
|
+
}
|
|
189815
190115
|
}
|
|
189816
190116
|
}
|
|
189817
|
-
}
|
|
189818
|
-
}
|
|
190117
|
+
}, 1e3);
|
|
190118
|
+
}
|
|
189819
190119
|
}
|
|
189820
190120
|
if (cancelled) return;
|
|
189821
190121
|
writeLog("system", "Dev server running");
|
|
@@ -189961,39 +190261,58 @@ Add them to the config block in specific.local`);
|
|
|
189961
190261
|
const staticItems = [
|
|
189962
190262
|
{
|
|
189963
190263
|
key: "title",
|
|
189964
|
-
content: /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, "Specific dev server"), instanceKey !== "default" && /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, " [", instanceKey, "]"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (Ctrl+C to stop)"))
|
|
190264
|
+
content: /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, "Specific dev server"), tunnelEnabled && /* @__PURE__ */ React6.createElement(Text6, { color: "magenta" }, " [tunnel]"), instanceKey !== "default" && /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, " [", instanceKey, "]"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (Ctrl+C to stop)"))
|
|
189965
190265
|
},
|
|
189966
190266
|
{ key: "space1", content: /* @__PURE__ */ React6.createElement(Text6, null, " ") },
|
|
189967
|
-
// Show admin UI URL
|
|
189968
|
-
|
|
189969
|
-
|
|
189970
|
-
|
|
189971
|
-
|
|
189972
|
-
|
|
189973
|
-
|
|
189974
|
-
|
|
189975
|
-
|
|
189976
|
-
|
|
189977
|
-
|
|
189978
|
-
|
|
189979
|
-
|
|
189980
|
-
|
|
189981
|
-
|
|
189982
|
-
content: /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, " \u25CF "), /* @__PURE__ */ React6.createElement(Text6, null, svc.name), defaultPort && /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (localhost:", defaultPort, ")"))
|
|
189983
|
-
}];
|
|
189984
|
-
}
|
|
189985
|
-
return endpoints.map((endpoint) => {
|
|
189986
|
-
const port = svc.ports.get(endpoint.name);
|
|
189987
|
-
const displayName = endpoint.name === "default" ? svc.name : `${svc.name}:${endpoint.name}`;
|
|
189988
|
-
const proxyName = endpoint.name === "default" ? svc.name : `${svc.name}-${endpoint.name}`;
|
|
190267
|
+
// Show admin UI URL (only in non-tunnel mode)
|
|
190268
|
+
...!tunnelEnabled ? [
|
|
190269
|
+
{
|
|
190270
|
+
key: "admin",
|
|
190271
|
+
content: /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "Admin:"), /* @__PURE__ */ React6.createElement(Text6, null, " "), /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "https://", instanceKey === "default" ? "" : `${instanceKey}.`, "local.spcf.app"))
|
|
190272
|
+
},
|
|
190273
|
+
{ key: "admin-space", content: /* @__PURE__ */ React6.createElement(Text6, null, " ") }
|
|
190274
|
+
] : [],
|
|
190275
|
+
// Services section - different rendering for tunnel mode vs local mode
|
|
190276
|
+
...tunnelEnabled ? (
|
|
190277
|
+
// Tunnel mode: show tunnel URLs for public services
|
|
190278
|
+
state.tunnels.size > 0 || state.tunnelStatus.size > 0 ? [
|
|
190279
|
+
{ key: "svc-header", content: /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "Services:") },
|
|
190280
|
+
...[...state.tunnelStatus.entries()].map(([serviceName, status]) => {
|
|
190281
|
+
const tunnel = state.tunnels.get(serviceName);
|
|
189989
190282
|
return {
|
|
189990
|
-
key: `svc-${
|
|
189991
|
-
content: /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, " \u25CF "), /* @__PURE__ */ React6.createElement(Text6, null,
|
|
190283
|
+
key: `svc-${serviceName}`,
|
|
190284
|
+
content: /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { color: status === "connected" ? "green" : status === "error" ? "red" : "yellow" }, " \u25CF "), /* @__PURE__ */ React6.createElement(Text6, null, serviceName), status === "connected" && tunnel ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, null, " \u2192 "), /* @__PURE__ */ React6.createElement(Text6, { bold: true }, tunnel.url)) : status === "connecting" ? /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " connecting...") : /* @__PURE__ */ React6.createElement(Text6, { color: "red" }, " error"))
|
|
189992
190285
|
};
|
|
189993
|
-
})
|
|
189994
|
-
|
|
189995
|
-
|
|
189996
|
-
|
|
190286
|
+
}),
|
|
190287
|
+
{ key: "space2", content: /* @__PURE__ */ React6.createElement(Text6, null, " ") }
|
|
190288
|
+
] : []
|
|
190289
|
+
) : (
|
|
190290
|
+
// Local mode: show local URLs
|
|
190291
|
+
services.length > 0 ? [
|
|
190292
|
+
{ key: "svc-header", content: /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "Services:") },
|
|
190293
|
+
...services.flatMap((svc) => {
|
|
190294
|
+
const serviceConfig = config.services.find((s) => s.name === svc.name);
|
|
190295
|
+
const endpoints = serviceConfig?.endpoints || [];
|
|
190296
|
+
if (endpoints.length === 0 && svc.ports.size > 0) {
|
|
190297
|
+
const defaultPort = svc.ports.get("default");
|
|
190298
|
+
return [{
|
|
190299
|
+
key: `svc-${svc.name}`,
|
|
190300
|
+
content: /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, " \u25CF "), /* @__PURE__ */ React6.createElement(Text6, null, svc.name), defaultPort && /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (localhost:", defaultPort, ")"))
|
|
190301
|
+
}];
|
|
190302
|
+
}
|
|
190303
|
+
return endpoints.map((endpoint) => {
|
|
190304
|
+
const port = svc.ports.get(endpoint.name);
|
|
190305
|
+
const displayName = endpoint.name === "default" ? svc.name : `${svc.name}:${endpoint.name}`;
|
|
190306
|
+
const proxyName = endpoint.name === "default" ? svc.name : `${svc.name}-${endpoint.name}`;
|
|
190307
|
+
return {
|
|
190308
|
+
key: `svc-${svc.name}-${endpoint.name}`,
|
|
190309
|
+
content: /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, " \u25CF "), /* @__PURE__ */ React6.createElement(Text6, null, displayName), port ? endpoint.public ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, null, " \u2192 "), /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "https://", proxyName, instanceKey === "default" ? "" : `.${instanceKey}`, ".local.spcf.app"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (localhost:", port, ")")) : /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (localhost:", port, ")") : null)
|
|
190310
|
+
};
|
|
190311
|
+
});
|
|
190312
|
+
}),
|
|
190313
|
+
{ key: "space2", content: /* @__PURE__ */ React6.createElement(Text6, null, " ") }
|
|
190314
|
+
] : []
|
|
190315
|
+
),
|
|
189997
190316
|
...config.postgres.length > 0 ? [
|
|
189998
190317
|
{ key: "pg-header", content: /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "Postgres:") },
|
|
189999
190318
|
...config.postgres.map((pg) => {
|
|
@@ -190041,13 +190360,13 @@ Add them to the config block in specific.local`);
|
|
|
190041
190360
|
];
|
|
190042
190361
|
return /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Static, { items: staticItems }, (item) => /* @__PURE__ */ React6.createElement(Box6, { key: item.key }, item.content)));
|
|
190043
190362
|
}
|
|
190044
|
-
function devCommand(instanceKey) {
|
|
190045
|
-
render4(/* @__PURE__ */ React6.createElement(DevUI, { instanceKey }));
|
|
190363
|
+
function devCommand(instanceKey, tunnelEnabled = false) {
|
|
190364
|
+
render4(/* @__PURE__ */ React6.createElement(DevUI, { instanceKey, tunnelEnabled }));
|
|
190046
190365
|
}
|
|
190047
190366
|
|
|
190048
190367
|
// src/lib/dev/git-worktree.ts
|
|
190049
190368
|
import { execSync as execSync2 } from "child_process";
|
|
190050
|
-
import * as
|
|
190369
|
+
import * as path18 from "path";
|
|
190051
190370
|
function isInWorktree() {
|
|
190052
190371
|
try {
|
|
190053
190372
|
const commonDir = execSync2("git rev-parse --git-common-dir", {
|
|
@@ -190058,8 +190377,8 @@ function isInWorktree() {
|
|
|
190058
190377
|
encoding: "utf-8",
|
|
190059
190378
|
stdio: ["pipe", "pipe", "pipe"]
|
|
190060
190379
|
}).trim();
|
|
190061
|
-
const resolvedCommonDir =
|
|
190062
|
-
const resolvedGitDir =
|
|
190380
|
+
const resolvedCommonDir = path18.resolve(commonDir);
|
|
190381
|
+
const resolvedGitDir = path18.resolve(gitDir);
|
|
190063
190382
|
return resolvedCommonDir !== resolvedGitDir;
|
|
190064
190383
|
} catch {
|
|
190065
190384
|
return false;
|
|
@@ -190074,7 +190393,7 @@ function getWorktreeName() {
|
|
|
190074
190393
|
encoding: "utf-8",
|
|
190075
190394
|
stdio: ["pipe", "pipe", "pipe"]
|
|
190076
190395
|
}).trim();
|
|
190077
|
-
return
|
|
190396
|
+
return path18.basename(gitDir);
|
|
190078
190397
|
} catch {
|
|
190079
190398
|
return null;
|
|
190080
190399
|
}
|
|
@@ -190089,35 +190408,35 @@ init_open();
|
|
|
190089
190408
|
import React7, { useState as useState6, useEffect as useEffect4, useCallback } from "react";
|
|
190090
190409
|
import { render as render5, Text as Text7, Box as Box7, useApp as useApp3, useInput as useInput5 } from "ink";
|
|
190091
190410
|
import Spinner5 from "ink-spinner";
|
|
190092
|
-
import * as
|
|
190093
|
-
import * as
|
|
190411
|
+
import * as fs22 from "fs";
|
|
190412
|
+
import * as path20 from "path";
|
|
190094
190413
|
|
|
190095
190414
|
// src/lib/deploy/build-tester.ts
|
|
190096
190415
|
import { spawn as spawn5 } from "child_process";
|
|
190097
|
-
import { existsSync as
|
|
190098
|
-
import { join as
|
|
190416
|
+
import { existsSync as existsSync18 } from "fs";
|
|
190417
|
+
import { join as join19 } from "path";
|
|
190099
190418
|
function getDependencyInstallCommand(build, projectDir) {
|
|
190100
190419
|
switch (build.base) {
|
|
190101
190420
|
case "node":
|
|
190102
|
-
if (
|
|
190421
|
+
if (existsSync18(join19(projectDir, "pnpm-lock.yaml"))) {
|
|
190103
190422
|
return "pnpm install --frozen-lockfile";
|
|
190104
|
-
} else if (
|
|
190423
|
+
} else if (existsSync18(join19(projectDir, "yarn.lock"))) {
|
|
190105
190424
|
return "yarn install --frozen-lockfile";
|
|
190106
|
-
} else if (
|
|
190425
|
+
} else if (existsSync18(join19(projectDir, "package-lock.json"))) {
|
|
190107
190426
|
return "npm ci";
|
|
190108
190427
|
} else {
|
|
190109
190428
|
return "npm install";
|
|
190110
190429
|
}
|
|
190111
190430
|
case "python":
|
|
190112
|
-
if (
|
|
190431
|
+
if (existsSync18(join19(projectDir, "poetry.lock"))) {
|
|
190113
190432
|
return "poetry install --no-interaction";
|
|
190114
|
-
} else if (
|
|
190433
|
+
} else if (existsSync18(join19(projectDir, "Pipfile.lock"))) {
|
|
190115
190434
|
return "pipenv install --deploy";
|
|
190116
|
-
} else if (
|
|
190435
|
+
} else if (existsSync18(join19(projectDir, "Pipfile"))) {
|
|
190117
190436
|
return "pipenv install";
|
|
190118
|
-
} else if (
|
|
190437
|
+
} else if (existsSync18(join19(projectDir, "pyproject.toml"))) {
|
|
190119
190438
|
return "pip install .";
|
|
190120
|
-
} else if (
|
|
190439
|
+
} else if (existsSync18(join19(projectDir, "requirements.txt"))) {
|
|
190121
190440
|
return "pip install -r requirements.txt";
|
|
190122
190441
|
}
|
|
190123
190442
|
return null;
|
|
@@ -190263,8 +190582,8 @@ async function testAllBuilds(builds, projectDir) {
|
|
|
190263
190582
|
|
|
190264
190583
|
// src/lib/tarball/create.ts
|
|
190265
190584
|
import { execSync as execSync3 } from "child_process";
|
|
190266
|
-
import * as
|
|
190267
|
-
import * as
|
|
190585
|
+
import * as fs21 from "fs";
|
|
190586
|
+
import * as path19 from "path";
|
|
190268
190587
|
import { createTarPacker, createEntryItemGenerator } from "tar-vern";
|
|
190269
190588
|
function isInsideGitRepository(dir) {
|
|
190270
190589
|
try {
|
|
@@ -190321,10 +190640,10 @@ var EXCLUDED_DIRS = [
|
|
|
190321
190640
|
];
|
|
190322
190641
|
async function collectPaths(baseDir, currentDir, exclude) {
|
|
190323
190642
|
const results = [];
|
|
190324
|
-
const entries = await
|
|
190643
|
+
const entries = await fs21.promises.readdir(currentDir, { withFileTypes: true });
|
|
190325
190644
|
for (const entry of entries) {
|
|
190326
|
-
const fullPath =
|
|
190327
|
-
const relativePath =
|
|
190645
|
+
const fullPath = path19.join(currentDir, entry.name);
|
|
190646
|
+
const relativePath = path19.relative(baseDir, fullPath);
|
|
190328
190647
|
if (entry.isDirectory()) {
|
|
190329
190648
|
if (!exclude.includes(entry.name)) {
|
|
190330
190649
|
results.push(relativePath);
|
|
@@ -190339,8 +190658,8 @@ async function collectPaths(baseDir, currentDir, exclude) {
|
|
|
190339
190658
|
}
|
|
190340
190659
|
async function createTarArchive(projectDir) {
|
|
190341
190660
|
writeLog("tarball", "Creating tarball using tar-vern (non-git project)");
|
|
190342
|
-
const configPath =
|
|
190343
|
-
if (!
|
|
190661
|
+
const configPath = path19.join(projectDir, "specific.hcl");
|
|
190662
|
+
if (!fs21.existsSync(configPath)) {
|
|
190344
190663
|
throw new Error("specific.hcl not found in project directory");
|
|
190345
190664
|
}
|
|
190346
190665
|
const relativePaths = await collectPaths(projectDir, projectDir, EXCLUDED_DIRS);
|
|
@@ -190357,8 +190676,8 @@ async function createTarArchive(projectDir) {
|
|
|
190357
190676
|
}
|
|
190358
190677
|
function findWidestContext(projectDir, contexts) {
|
|
190359
190678
|
if (contexts.length === 0) return ".";
|
|
190360
|
-
const absolute = contexts.map((c) =>
|
|
190361
|
-
const segments = absolute.map((p) => p.split(
|
|
190679
|
+
const absolute = contexts.map((c) => path19.resolve(projectDir, c));
|
|
190680
|
+
const segments = absolute.map((p) => p.split(path19.sep).filter(Boolean));
|
|
190362
190681
|
const firstSegments = segments[0];
|
|
190363
190682
|
if (!firstSegments) return ".";
|
|
190364
190683
|
const minLen = Math.min(...segments.map((s) => s.length));
|
|
@@ -190372,12 +190691,12 @@ function findWidestContext(projectDir, contexts) {
|
|
|
190372
190691
|
}
|
|
190373
190692
|
}
|
|
190374
190693
|
const ancestorSegments = firstSegments.slice(0, commonLength);
|
|
190375
|
-
const ancestor =
|
|
190376
|
-
return
|
|
190694
|
+
const ancestor = path19.sep + ancestorSegments.join(path19.sep);
|
|
190695
|
+
return path19.relative(projectDir, ancestor) || ".";
|
|
190377
190696
|
}
|
|
190378
190697
|
async function createProjectTarball(projectDir, context = ".") {
|
|
190379
|
-
const contextDir =
|
|
190380
|
-
const appPath =
|
|
190698
|
+
const contextDir = path19.resolve(projectDir, context);
|
|
190699
|
+
const appPath = path19.relative(contextDir, projectDir) || ".";
|
|
190381
190700
|
writeLog("tarball", `Context: ${contextDir}, appPath: ${appPath}`);
|
|
190382
190701
|
let tarball;
|
|
190383
190702
|
if (isInsideGitRepository(contextDir)) {
|
|
@@ -191264,14 +191583,14 @@ ${errorMsg}`
|
|
|
191264
191583
|
), phase === "error" && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, { color: "red" }, "Error: ", error)), phase === "success" && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text7, { color: "green" }, "Deployment successful!"), deployment?.publicUrls && Object.keys(deployment.publicUrls).length > 0 && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "Public URLs:"), Object.entries(deployment.publicUrls).map(([name, url]) => /* @__PURE__ */ React7.createElement(Text7, { key: name }, " ", name, ": ", /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, url))))));
|
|
191265
191584
|
}
|
|
191266
191585
|
async function deployCommand(environment, options2) {
|
|
191267
|
-
const configPath =
|
|
191268
|
-
if (!
|
|
191586
|
+
const configPath = path20.join(process.cwd(), "specific.hcl");
|
|
191587
|
+
if (!fs22.existsSync(configPath)) {
|
|
191269
191588
|
console.error("Error: No specific.hcl found in current directory");
|
|
191270
191589
|
process.exit(1);
|
|
191271
191590
|
}
|
|
191272
191591
|
let config;
|
|
191273
191592
|
try {
|
|
191274
|
-
const hcl =
|
|
191593
|
+
const hcl = fs22.readFileSync(configPath, "utf-8");
|
|
191275
191594
|
config = await parseConfig(hcl);
|
|
191276
191595
|
} catch (err) {
|
|
191277
191596
|
console.error(
|
|
@@ -191295,8 +191614,8 @@ async function deployCommand(environment, options2) {
|
|
|
191295
191614
|
|
|
191296
191615
|
// src/commands/exec.tsx
|
|
191297
191616
|
import { spawn as spawn6 } from "child_process";
|
|
191298
|
-
import * as
|
|
191299
|
-
import * as
|
|
191617
|
+
import * as fs23 from "fs";
|
|
191618
|
+
import * as path21 from "path";
|
|
191300
191619
|
async function execCommand(serviceName, command, instanceKey = "default") {
|
|
191301
191620
|
if (command.length === 0) {
|
|
191302
191621
|
console.error(
|
|
@@ -191324,14 +191643,14 @@ async function execCommand(serviceName, command, instanceKey = "default") {
|
|
|
191324
191643
|
}
|
|
191325
191644
|
}
|
|
191326
191645
|
};
|
|
191327
|
-
const configPath =
|
|
191328
|
-
if (!
|
|
191646
|
+
const configPath = path21.join(process.cwd(), "specific.hcl");
|
|
191647
|
+
if (!fs23.existsSync(configPath)) {
|
|
191329
191648
|
console.error("Error: No specific.hcl found in current directory");
|
|
191330
191649
|
process.exit(1);
|
|
191331
191650
|
}
|
|
191332
191651
|
let config;
|
|
191333
191652
|
try {
|
|
191334
|
-
const hcl =
|
|
191653
|
+
const hcl = fs23.readFileSync(configPath, "utf-8");
|
|
191335
191654
|
config = await parseConfig(hcl);
|
|
191336
191655
|
} catch (err) {
|
|
191337
191656
|
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -191468,27 +191787,48 @@ async function execCommand(serviceName, command, instanceKey = "default") {
|
|
|
191468
191787
|
|
|
191469
191788
|
// src/commands/psql.tsx
|
|
191470
191789
|
import { spawn as spawn7 } from "child_process";
|
|
191471
|
-
|
|
191472
|
-
|
|
191473
|
-
|
|
191474
|
-
|
|
191475
|
-
|
|
191476
|
-
|
|
191790
|
+
import * as fs24 from "fs";
|
|
191791
|
+
import * as path22 from "path";
|
|
191792
|
+
async function psqlCommand(databaseName, instanceKey = "default", extraArgs = []) {
|
|
191793
|
+
let startedResources = [];
|
|
191794
|
+
let ownsInstances = false;
|
|
191795
|
+
let stateManager = null;
|
|
191796
|
+
const cleanup = async () => {
|
|
191797
|
+
if (ownsInstances) {
|
|
191798
|
+
for (const resource of startedResources) {
|
|
191799
|
+
await resource.stop().catch(() => {
|
|
191800
|
+
});
|
|
191801
|
+
}
|
|
191802
|
+
if (stateManager) {
|
|
191803
|
+
await stateManager.releaseOwnership().catch(() => {
|
|
191804
|
+
});
|
|
191805
|
+
}
|
|
191806
|
+
}
|
|
191807
|
+
};
|
|
191808
|
+
const configPath = path22.join(process.cwd(), "specific.hcl");
|
|
191809
|
+
if (!fs24.existsSync(configPath)) {
|
|
191810
|
+
console.error("Error: No specific.hcl found in current directory");
|
|
191811
|
+
process.exit(1);
|
|
191812
|
+
}
|
|
191813
|
+
let config;
|
|
191814
|
+
try {
|
|
191815
|
+
const hcl = fs24.readFileSync(configPath, "utf-8");
|
|
191816
|
+
config = await parseConfig(hcl);
|
|
191817
|
+
} catch (err) {
|
|
191818
|
+
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
191477
191819
|
process.exit(1);
|
|
191478
191820
|
}
|
|
191479
|
-
const availableDatabases =
|
|
191821
|
+
const availableDatabases = config.postgres.map((p) => p.name);
|
|
191480
191822
|
if (availableDatabases.length === 0) {
|
|
191481
|
-
console.error("Error: No databases
|
|
191823
|
+
console.error("Error: No postgres databases defined in specific.hcl");
|
|
191482
191824
|
process.exit(1);
|
|
191483
191825
|
}
|
|
191484
191826
|
let targetDb;
|
|
191827
|
+
if (databaseName && !availableDatabases.includes(databaseName)) {
|
|
191828
|
+
extraArgs = [databaseName, ...extraArgs];
|
|
191829
|
+
databaseName = void 0;
|
|
191830
|
+
}
|
|
191485
191831
|
if (databaseName) {
|
|
191486
|
-
if (!existingInstances.databases[databaseName]) {
|
|
191487
|
-
console.error(
|
|
191488
|
-
`Error: Database "${databaseName}" not found. Available: ${availableDatabases.join(", ")}`
|
|
191489
|
-
);
|
|
191490
|
-
process.exit(1);
|
|
191491
|
-
}
|
|
191492
191832
|
targetDb = databaseName;
|
|
191493
191833
|
} else {
|
|
191494
191834
|
if (availableDatabases.length > 1) {
|
|
@@ -191499,24 +191839,95 @@ async function psqlCommand(databaseName, instanceKey = "default") {
|
|
|
191499
191839
|
}
|
|
191500
191840
|
targetDb = availableDatabases[0];
|
|
191501
191841
|
}
|
|
191502
|
-
|
|
191503
|
-
|
|
191842
|
+
stateManager = new InstanceStateManager(process.cwd(), instanceKey);
|
|
191843
|
+
await stateManager.cleanStaleState();
|
|
191844
|
+
const existingInstances = await stateManager.getExistingInstances();
|
|
191845
|
+
let dbState;
|
|
191846
|
+
if (existingInstances && existingInstances.databases[targetDb]) {
|
|
191847
|
+
const db = existingInstances.databases[targetDb];
|
|
191848
|
+
dbState = { host: db.host, port: db.port, user: db.user, password: db.password, dbName: db.dbName };
|
|
191849
|
+
} else {
|
|
191850
|
+
console.error("Starting database...");
|
|
191851
|
+
try {
|
|
191852
|
+
await stateManager.claimOwnership("psql");
|
|
191853
|
+
ownsInstances = true;
|
|
191854
|
+
} catch (err) {
|
|
191855
|
+
console.error(
|
|
191856
|
+
`Error: ${err instanceof Error ? err.message : String(err)}`
|
|
191857
|
+
);
|
|
191858
|
+
process.exit(1);
|
|
191859
|
+
}
|
|
191860
|
+
try {
|
|
191861
|
+
const result = await startResources({
|
|
191862
|
+
config,
|
|
191863
|
+
selection: {
|
|
191864
|
+
mode: "required",
|
|
191865
|
+
postgres: [targetDb],
|
|
191866
|
+
redis: [],
|
|
191867
|
+
storage: []
|
|
191868
|
+
},
|
|
191869
|
+
stateManager,
|
|
191870
|
+
dataDir: `.specific/keys/${instanceKey}/data`,
|
|
191871
|
+
portAllocator: new PortAllocator(),
|
|
191872
|
+
callbacks: {
|
|
191873
|
+
log: (msg) => console.error(msg)
|
|
191874
|
+
},
|
|
191875
|
+
startElectric: false
|
|
191876
|
+
});
|
|
191877
|
+
startedResources = result.startedResources;
|
|
191878
|
+
const resource = result.resources.get(targetDb);
|
|
191879
|
+
if (!resource) {
|
|
191880
|
+
console.error(`Error: Failed to start database "${targetDb}"`);
|
|
191881
|
+
await cleanup();
|
|
191882
|
+
process.exit(1);
|
|
191883
|
+
}
|
|
191884
|
+
dbState = {
|
|
191885
|
+
host: resource.host,
|
|
191886
|
+
port: resource.port,
|
|
191887
|
+
user: resource.user,
|
|
191888
|
+
password: resource.password,
|
|
191889
|
+
dbName: resource.dbName
|
|
191890
|
+
};
|
|
191891
|
+
} catch (err) {
|
|
191892
|
+
console.error(
|
|
191893
|
+
`Error: Failed to start database: ${err instanceof Error ? err.message : String(err)}`
|
|
191894
|
+
);
|
|
191895
|
+
await cleanup();
|
|
191896
|
+
process.exit(1);
|
|
191897
|
+
}
|
|
191898
|
+
}
|
|
191899
|
+
const binary = await ensureBinary(postgresBinary);
|
|
191900
|
+
const psqlPath = binary.executables["psql"];
|
|
191901
|
+
const libraryEnv = getLibraryEnv(binary);
|
|
191902
|
+
let child = null;
|
|
191903
|
+
const handleSignal = async (signal) => {
|
|
191904
|
+
if (child && !child.killed) {
|
|
191905
|
+
child.kill(signal);
|
|
191906
|
+
}
|
|
191907
|
+
await cleanup();
|
|
191908
|
+
};
|
|
191909
|
+
process.on("SIGINT", () => handleSignal("SIGINT"));
|
|
191910
|
+
process.on("SIGTERM", () => handleSignal("SIGTERM"));
|
|
191911
|
+
child = spawn7(psqlPath, ["-h", dbState.host, "-p", String(dbState.port), "-U", dbState.user, "-d", dbState.dbName, ...extraArgs], {
|
|
191504
191912
|
cwd: process.cwd(),
|
|
191505
191913
|
env: {
|
|
191506
191914
|
...process.env,
|
|
191915
|
+
...libraryEnv,
|
|
191507
191916
|
PGPASSWORD: dbState.password
|
|
191508
191917
|
},
|
|
191509
191918
|
stdio: "inherit"
|
|
191510
191919
|
});
|
|
191511
|
-
child.on("exit", (code, signal) => {
|
|
191920
|
+
child.on("exit", async (code, signal) => {
|
|
191921
|
+
await cleanup();
|
|
191512
191922
|
if (signal) {
|
|
191513
191923
|
process.kill(process.pid, signal);
|
|
191514
191924
|
} else {
|
|
191515
191925
|
process.exit(code ?? 0);
|
|
191516
191926
|
}
|
|
191517
191927
|
});
|
|
191518
|
-
child.on("error", (err) => {
|
|
191928
|
+
child.on("error", async (err) => {
|
|
191519
191929
|
console.error(`Error: Failed to start psql: ${err.message}`);
|
|
191930
|
+
await cleanup();
|
|
191520
191931
|
process.exit(1);
|
|
191521
191932
|
});
|
|
191522
191933
|
}
|
|
@@ -191525,21 +191936,21 @@ async function psqlCommand(databaseName, instanceKey = "default") {
|
|
|
191525
191936
|
import React8, { useState as useState7, useEffect as useEffect5 } from "react";
|
|
191526
191937
|
import { render as render6, Text as Text8, Box as Box8 } from "ink";
|
|
191527
191938
|
import Spinner6 from "ink-spinner";
|
|
191528
|
-
import * as
|
|
191529
|
-
import * as
|
|
191939
|
+
import * as fs25 from "fs";
|
|
191940
|
+
import * as path23 from "path";
|
|
191530
191941
|
function CleanUI({ instanceKey }) {
|
|
191531
191942
|
const [state, setState] = useState7({ status: "checking" });
|
|
191532
191943
|
useEffect5(() => {
|
|
191533
191944
|
async function clean() {
|
|
191534
191945
|
const projectRoot = process.cwd();
|
|
191535
|
-
const specificDir =
|
|
191536
|
-
if (!
|
|
191946
|
+
const specificDir = path23.join(projectRoot, ".specific");
|
|
191947
|
+
if (!fs25.existsSync(specificDir)) {
|
|
191537
191948
|
setState({ status: "nothing" });
|
|
191538
191949
|
return;
|
|
191539
191950
|
}
|
|
191540
191951
|
if (instanceKey) {
|
|
191541
|
-
const keyDir =
|
|
191542
|
-
if (!
|
|
191952
|
+
const keyDir = path23.join(specificDir, "keys", instanceKey);
|
|
191953
|
+
if (!fs25.existsSync(keyDir)) {
|
|
191543
191954
|
setState({ status: "nothing" });
|
|
191544
191955
|
return;
|
|
191545
191956
|
}
|
|
@@ -191555,7 +191966,7 @@ function CleanUI({ instanceKey }) {
|
|
|
191555
191966
|
await stateManager.cleanStaleState();
|
|
191556
191967
|
setState({ status: "cleaning" });
|
|
191557
191968
|
try {
|
|
191558
|
-
|
|
191969
|
+
fs25.rmSync(keyDir, { recursive: true, force: true });
|
|
191559
191970
|
setState({ status: "success" });
|
|
191560
191971
|
} catch (err) {
|
|
191561
191972
|
setState({
|
|
@@ -191564,10 +191975,10 @@ function CleanUI({ instanceKey }) {
|
|
|
191564
191975
|
});
|
|
191565
191976
|
}
|
|
191566
191977
|
} else {
|
|
191567
|
-
const keysDir =
|
|
191568
|
-
if (
|
|
191569
|
-
const keys =
|
|
191570
|
-
(f) =>
|
|
191978
|
+
const keysDir = path23.join(specificDir, "keys");
|
|
191979
|
+
if (fs25.existsSync(keysDir)) {
|
|
191980
|
+
const keys = fs25.readdirSync(keysDir).filter(
|
|
191981
|
+
(f) => fs25.statSync(path23.join(keysDir, f)).isDirectory()
|
|
191571
191982
|
);
|
|
191572
191983
|
for (const key of keys) {
|
|
191573
191984
|
const stateManager2 = new InstanceStateManager(projectRoot, key);
|
|
@@ -191592,7 +192003,7 @@ function CleanUI({ instanceKey }) {
|
|
|
191592
192003
|
}
|
|
191593
192004
|
setState({ status: "cleaning" });
|
|
191594
192005
|
try {
|
|
191595
|
-
|
|
192006
|
+
fs25.rmSync(specificDir, { recursive: true, force: true });
|
|
191596
192007
|
setState({ status: "success" });
|
|
191597
192008
|
} catch (err) {
|
|
191598
192009
|
setState({
|
|
@@ -191679,13 +192090,13 @@ function logoutCommand() {
|
|
|
191679
192090
|
var program = new Command();
|
|
191680
192091
|
var env = "production";
|
|
191681
192092
|
var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
|
|
191682
|
-
program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.
|
|
191683
|
-
program.command("init").description("Initialize project for use with a coding agent").action(initCommand);
|
|
192093
|
+
program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.49").enablePositionalOptions();
|
|
192094
|
+
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));
|
|
191684
192095
|
program.command("docs [topic]").description("Fetch LLM-optimized documentation").action(docsCommand);
|
|
191685
192096
|
program.command("check").description("Validate specific.hcl configuration").action(checkCommand);
|
|
191686
|
-
program.command("dev").description("Start local development environment").option("-k, --key <key>", "Namespace for isolated dev environment (auto-detected from git worktree if not specified)").action((options2) => {
|
|
192097
|
+
program.command("dev").description("Start local development environment").option("-k, --key <key>", "Namespace for isolated dev environment (auto-detected from git worktree if not specified)").option("--tunnel", "Expose public services via localtunnel URLs").action((options2) => {
|
|
191687
192098
|
const key = options2.key ?? getDefaultKey();
|
|
191688
|
-
devCommand(key);
|
|
192099
|
+
devCommand(key, options2.tunnel ?? false);
|
|
191689
192100
|
});
|
|
191690
192101
|
program.command("deploy [environment]").description("Deploy to Specific infrastructure").option("--skip-build-test", "Skip local build testing before deploy").action((environment, options2) => {
|
|
191691
192102
|
deployCommand(environment, options2);
|
|
@@ -191695,9 +192106,10 @@ program.command("exec <service> [args...]").description("Run a one-off command w
|
|
|
191695
192106
|
const key = options2.key ?? getDefaultKey();
|
|
191696
192107
|
await execCommand(service, filteredArgs, key);
|
|
191697
192108
|
});
|
|
191698
|
-
program.command("psql [database]").description("Connect to a
|
|
192109
|
+
program.command("psql [database] [args...]").description("Connect to a Postgres database").option("-k, --key <key>", "Dev environment namespace (auto-detected from git worktree if not specified)").passThroughOptions().action((database, args, options2) => {
|
|
192110
|
+
const filteredArgs = args[0] === "--" ? args.slice(1) : args;
|
|
191699
192111
|
const key = options2.key ?? getDefaultKey();
|
|
191700
|
-
psqlCommand(database, key);
|
|
192112
|
+
psqlCommand(database, key, filteredArgs);
|
|
191701
192113
|
});
|
|
191702
192114
|
program.command("clean").description("Remove .specific directory for a clean slate").option("-k, --key <key>", "Clean only the specified dev environment key").action((options2) => {
|
|
191703
192115
|
cleanCommand(options2.key);
|