@specific.dev/cli 0.1.125 → 0.1.127
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/404/index.html +1 -1
- package/dist/admin/404.html +1 -1
- package/dist/admin/__next.!KGRlZmF1bHQp.__PAGE__.txt +1 -1
- package/dist/admin/__next.!KGRlZmF1bHQp.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.!KGRlZmF1bHQp.databases.__PAGE__.txt +1 -1
- package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.txt +1 -1
- package/dist/admin/databases/__next.!KGRlZmF1bHQp.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/index.html +1 -1
- package/dist/admin/databases/index.txt +1 -1
- package/dist/admin/fullscreen/__next._full.txt +1 -1
- package/dist/admin/fullscreen/__next._head.txt +1 -1
- package/dist/admin/fullscreen/__next._index.txt +1 -1
- package/dist/admin/fullscreen/__next._tree.txt +1 -1
- package/dist/admin/fullscreen/__next.fullscreen.__PAGE__.txt +1 -1
- package/dist/admin/fullscreen/__next.fullscreen.txt +1 -1
- package/dist/admin/fullscreen/databases/__next._full.txt +1 -1
- package/dist/admin/fullscreen/databases/__next._head.txt +1 -1
- package/dist/admin/fullscreen/databases/__next._index.txt +1 -1
- package/dist/admin/fullscreen/databases/__next._tree.txt +1 -1
- package/dist/admin/fullscreen/databases/__next.fullscreen.databases.__PAGE__.txt +1 -1
- package/dist/admin/fullscreen/databases/__next.fullscreen.databases.txt +1 -1
- package/dist/admin/fullscreen/databases/__next.fullscreen.txt +1 -1
- package/dist/admin/fullscreen/databases/index.html +1 -1
- package/dist/admin/fullscreen/databases/index.txt +1 -1
- package/dist/admin/fullscreen/index.html +1 -1
- package/dist/admin/fullscreen/index.txt +1 -1
- package/dist/admin/index.html +1 -1
- package/dist/admin/index.txt +1 -1
- package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.__PAGE__.txt +1 -1
- package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.txt +1 -1
- package/dist/admin/mail/__next.!KGRlZmF1bHQp.txt +1 -1
- package/dist/admin/mail/__next._full.txt +1 -1
- package/dist/admin/mail/__next._head.txt +1 -1
- package/dist/admin/mail/__next._index.txt +1 -1
- package/dist/admin/mail/__next._tree.txt +1 -1
- package/dist/admin/mail/index.html +1 -1
- package/dist/admin/mail/index.txt +1 -1
- package/dist/admin/workflows/__next.!KGRlZmF1bHQp.txt +1 -1
- package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.__PAGE__.txt +1 -1
- package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.txt +1 -1
- package/dist/admin/workflows/__next._full.txt +1 -1
- package/dist/admin/workflows/__next._head.txt +1 -1
- package/dist/admin/workflows/__next._index.txt +1 -1
- package/dist/admin/workflows/__next._tree.txt +1 -1
- package/dist/admin/workflows/index.html +1 -1
- package/dist/admin/workflows/index.txt +1 -1
- package/dist/cli.js +447 -87
- package/package.json +4 -4
- /package/dist/admin/_next/static/{r3veCvOuDWyK-S0rsl-XQ → tWkoycW-NhVqIuvprR9OV}/_buildManifest.js +0 -0
- /package/dist/admin/_next/static/{r3veCvOuDWyK-S0rsl-XQ → tWkoycW-NhVqIuvprR9OV}/_clientMiddlewareManifest.json +0 -0
- /package/dist/admin/_next/static/{r3veCvOuDWyK-S0rsl-XQ → tWkoycW-NhVqIuvprR9OV}/_ssgManifest.js +0 -0
package/dist/cli.js
CHANGED
|
@@ -185179,25 +185179,26 @@ var chokidar_default = { watch, FSWatcher };
|
|
|
185179
185179
|
var import_code_frame = __toESM(require_lib2(), 1);
|
|
185180
185180
|
import * as fs5 from "fs";
|
|
185181
185181
|
import * as path5 from "path";
|
|
185182
|
-
import * as
|
|
185182
|
+
import * as net2 from "net";
|
|
185183
185183
|
import { spawn } from "child_process";
|
|
185184
185184
|
import * as fs4 from "fs";
|
|
185185
185185
|
import * as path4 from "path";
|
|
185186
185186
|
import * as os2 from "os";
|
|
185187
185187
|
import { createReadStream } from "fs";
|
|
185188
185188
|
import { createTarExtractor, extractTo } from "tar-vern";
|
|
185189
|
+
import * as net from "net";
|
|
185190
|
+
import { execFile } from "child_process";
|
|
185191
|
+
import { promisify } from "util";
|
|
185189
185192
|
import { spawn as spawn2 } from "child_process";
|
|
185190
185193
|
import { readFile, writeFile } from "fs/promises";
|
|
185191
185194
|
import { existsSync as existsSync5 } from "fs";
|
|
185192
185195
|
import * as fs6 from "fs";
|
|
185193
185196
|
import * as path6 from "path";
|
|
185194
|
-
import { execFile } from "child_process";
|
|
185195
|
-
import { promisify } from "util";
|
|
185196
185197
|
import * as http from "http";
|
|
185197
185198
|
import * as fs7 from "fs";
|
|
185198
185199
|
import * as path7 from "path";
|
|
185199
185200
|
import { fileURLToPath } from "url";
|
|
185200
|
-
import * as
|
|
185201
|
+
import * as net3 from "net";
|
|
185201
185202
|
import * as fs8 from "fs";
|
|
185202
185203
|
import * as path9 from "path";
|
|
185203
185204
|
import { spawn as spawn3 } from "child_process";
|
|
@@ -185207,7 +185208,7 @@ import * as path8 from "path";
|
|
|
185207
185208
|
import * as crypto from "crypto";
|
|
185208
185209
|
import * as http2 from "http";
|
|
185209
185210
|
import * as crypto2 from "crypto";
|
|
185210
|
-
import * as
|
|
185211
|
+
import * as net4 from "net";
|
|
185211
185212
|
import * as fs9 from "fs";
|
|
185212
185213
|
import * as path10 from "path";
|
|
185213
185214
|
import { spawn as spawn4 } from "child_process";
|
|
@@ -185217,14 +185218,14 @@ import * as path11 from "path";
|
|
|
185217
185218
|
import { spawnSync } from "child_process";
|
|
185218
185219
|
import * as fs11 from "fs";
|
|
185219
185220
|
import * as path12 from "path";
|
|
185220
|
-
import * as net4 from "net";
|
|
185221
|
-
import { spawn as spawn5 } from "child_process";
|
|
185222
185221
|
import * as net5 from "net";
|
|
185222
|
+
import { spawn as spawn5 } from "child_process";
|
|
185223
|
+
import * as net6 from "net";
|
|
185223
185224
|
import * as fs12 from "fs";
|
|
185224
185225
|
import * as path13 from "path";
|
|
185225
185226
|
import { generateSlug } from "random-word-slugs";
|
|
185226
185227
|
import { EventEmitter as EventEmitter2 } from "node:events";
|
|
185227
|
-
import * as
|
|
185228
|
+
import * as net7 from "node:net";
|
|
185228
185229
|
import { EventEmitter as EventEmitter22 } from "events";
|
|
185229
185230
|
import { execSync as execSync2 } from "child_process";
|
|
185230
185231
|
import * as path15 from "path";
|
|
@@ -369275,14 +369276,22 @@ var temporalBinary = {
|
|
|
369275
369276
|
stripComponents: 0,
|
|
369276
369277
|
executables: ["temporal"]
|
|
369277
369278
|
};
|
|
369278
|
-
function killProcess(proc) {
|
|
369279
|
+
function killProcess(proc, opts = {}) {
|
|
369279
369280
|
return new Promise((resolve52) => {
|
|
369280
369281
|
if (proc.killed || proc.exitCode !== null) {
|
|
369281
369282
|
resolve52();
|
|
369282
369283
|
return;
|
|
369283
369284
|
}
|
|
369284
369285
|
proc.once("exit", () => resolve52());
|
|
369285
|
-
proc.
|
|
369286
|
+
if (opts.detached && proc.pid) {
|
|
369287
|
+
try {
|
|
369288
|
+
process.kill(-proc.pid, "SIGKILL");
|
|
369289
|
+
} catch {
|
|
369290
|
+
proc.kill("SIGKILL");
|
|
369291
|
+
}
|
|
369292
|
+
} else {
|
|
369293
|
+
proc.kill("SIGKILL");
|
|
369294
|
+
}
|
|
369286
369295
|
});
|
|
369287
369296
|
}
|
|
369288
369297
|
function killProcessGroup(pid) {
|
|
@@ -369291,6 +369300,143 @@ function killProcessGroup(pid) {
|
|
|
369291
369300
|
} catch {
|
|
369292
369301
|
}
|
|
369293
369302
|
}
|
|
369303
|
+
var execFileAsync = promisify(execFile);
|
|
369304
|
+
function isPortInUse(host, port) {
|
|
369305
|
+
return new Promise((resolve52) => {
|
|
369306
|
+
const socket = new net.Socket();
|
|
369307
|
+
socket.setTimeout(1e3);
|
|
369308
|
+
socket.on("connect", () => {
|
|
369309
|
+
socket.destroy();
|
|
369310
|
+
resolve52(true);
|
|
369311
|
+
});
|
|
369312
|
+
socket.on("timeout", () => {
|
|
369313
|
+
socket.destroy();
|
|
369314
|
+
resolve52(false);
|
|
369315
|
+
});
|
|
369316
|
+
socket.on("error", () => {
|
|
369317
|
+
socket.destroy();
|
|
369318
|
+
resolve52(false);
|
|
369319
|
+
});
|
|
369320
|
+
socket.connect(port, host);
|
|
369321
|
+
});
|
|
369322
|
+
}
|
|
369323
|
+
async function waitForPortFree(host, port, timeoutMs = 3e3) {
|
|
369324
|
+
const deadline = Date.now() + timeoutMs;
|
|
369325
|
+
while (Date.now() < deadline) {
|
|
369326
|
+
if (!await isPortInUse(host, port)) return true;
|
|
369327
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
369328
|
+
}
|
|
369329
|
+
return !await isPortInUse(host, port);
|
|
369330
|
+
}
|
|
369331
|
+
async function getListenersOnPort(port) {
|
|
369332
|
+
let pids = [];
|
|
369333
|
+
try {
|
|
369334
|
+
const { stdout } = await execFileAsync(
|
|
369335
|
+
"lsof",
|
|
369336
|
+
["-i", `:${port}`, "-sTCP:LISTEN", "-t"],
|
|
369337
|
+
{ timeout: 5e3 }
|
|
369338
|
+
);
|
|
369339
|
+
pids = stdout.trim().split("\n").map((l) => parseInt(l.trim(), 10)).filter((n) => !isNaN(n));
|
|
369340
|
+
} catch {
|
|
369341
|
+
return [];
|
|
369342
|
+
}
|
|
369343
|
+
const results = [];
|
|
369344
|
+
for (const pid of pids) {
|
|
369345
|
+
results.push({ pid, command: await resolveCommand(pid) });
|
|
369346
|
+
}
|
|
369347
|
+
return results;
|
|
369348
|
+
}
|
|
369349
|
+
async function resolveCommand(pid) {
|
|
369350
|
+
try {
|
|
369351
|
+
const { stdout } = await execFileAsync(
|
|
369352
|
+
"ps",
|
|
369353
|
+
["-p", String(pid), "-o", "command="],
|
|
369354
|
+
{ timeout: 1e3 }
|
|
369355
|
+
);
|
|
369356
|
+
return stdout.trim();
|
|
369357
|
+
} catch {
|
|
369358
|
+
return "";
|
|
369359
|
+
}
|
|
369360
|
+
}
|
|
369361
|
+
async function reclaimSpecificOrphanOnPort(port) {
|
|
369362
|
+
const listeners = await getListenersOnPort(port);
|
|
369363
|
+
let killed = false;
|
|
369364
|
+
for (const { pid, command } of listeners) {
|
|
369365
|
+
if (!isOwnedBySpecific(command)) continue;
|
|
369366
|
+
try {
|
|
369367
|
+
process.kill(-pid, "SIGKILL");
|
|
369368
|
+
killed = true;
|
|
369369
|
+
} catch {
|
|
369370
|
+
try {
|
|
369371
|
+
process.kill(pid, "SIGKILL");
|
|
369372
|
+
killed = true;
|
|
369373
|
+
} catch {
|
|
369374
|
+
}
|
|
369375
|
+
}
|
|
369376
|
+
}
|
|
369377
|
+
return killed;
|
|
369378
|
+
}
|
|
369379
|
+
function isOwnedBySpecific(argv) {
|
|
369380
|
+
return argv.includes("/.specific/bin/") || argv.includes("/.burrito/electric_");
|
|
369381
|
+
}
|
|
369382
|
+
async function killSpecificProcessesReferencing(needle) {
|
|
369383
|
+
if (!needle) return 0;
|
|
369384
|
+
let stdout = "";
|
|
369385
|
+
try {
|
|
369386
|
+
const result = await execFileAsync("ps", ["-A", "-o", "pid=,command="], {
|
|
369387
|
+
timeout: 5e3,
|
|
369388
|
+
maxBuffer: 10 * 1024 * 1024
|
|
369389
|
+
});
|
|
369390
|
+
stdout = result.stdout;
|
|
369391
|
+
} catch {
|
|
369392
|
+
return 0;
|
|
369393
|
+
}
|
|
369394
|
+
const self2 = process.pid;
|
|
369395
|
+
const candidates = [];
|
|
369396
|
+
for (const raw of stdout.split("\n")) {
|
|
369397
|
+
const line = raw.trim();
|
|
369398
|
+
if (!line) continue;
|
|
369399
|
+
const space = line.indexOf(" ");
|
|
369400
|
+
if (space <= 0) continue;
|
|
369401
|
+
const pid = parseInt(line.slice(0, space), 10);
|
|
369402
|
+
if (isNaN(pid) || pid === self2) continue;
|
|
369403
|
+
const command = line.slice(space + 1);
|
|
369404
|
+
if (!isOwnedBySpecific(command)) continue;
|
|
369405
|
+
if (command.includes(needle) || await processOpensPathUnder(pid, needle)) {
|
|
369406
|
+
candidates.push(pid);
|
|
369407
|
+
}
|
|
369408
|
+
}
|
|
369409
|
+
let killed = 0;
|
|
369410
|
+
for (const pid of candidates) {
|
|
369411
|
+
try {
|
|
369412
|
+
process.kill(-pid, "SIGKILL");
|
|
369413
|
+
killed++;
|
|
369414
|
+
} catch {
|
|
369415
|
+
try {
|
|
369416
|
+
process.kill(pid, "SIGKILL");
|
|
369417
|
+
killed++;
|
|
369418
|
+
} catch {
|
|
369419
|
+
}
|
|
369420
|
+
}
|
|
369421
|
+
}
|
|
369422
|
+
return killed;
|
|
369423
|
+
}
|
|
369424
|
+
async function processOpensPathUnder(pid, prefix) {
|
|
369425
|
+
try {
|
|
369426
|
+
const { stdout } = await execFileAsync("lsof", ["-p", String(pid), "-F", "n"], {
|
|
369427
|
+
timeout: 3e3,
|
|
369428
|
+
maxBuffer: 5 * 1024 * 1024
|
|
369429
|
+
});
|
|
369430
|
+
for (const raw of stdout.split("\n")) {
|
|
369431
|
+
if (raw.length > 1 && raw[0] === "n" && raw.slice(1).startsWith(prefix)) {
|
|
369432
|
+
return true;
|
|
369433
|
+
}
|
|
369434
|
+
}
|
|
369435
|
+
} catch {
|
|
369436
|
+
return false;
|
|
369437
|
+
}
|
|
369438
|
+
return false;
|
|
369439
|
+
}
|
|
369294
369440
|
async function startPostgres(pg, port, dataDir, onProgress) {
|
|
369295
369441
|
const binary = await ensureBinary(postgresBinary, void 0, onProgress);
|
|
369296
369442
|
const dbDataPath = path5.join(process.cwd(), dataDir, pg.name);
|
|
@@ -369299,6 +369445,7 @@ async function startPostgres(pg, port, dataDir, onProgress) {
|
|
|
369299
369445
|
const password = "postgres";
|
|
369300
369446
|
const libraryEnv = getLibraryEnv(binary);
|
|
369301
369447
|
const env2 = { ...process.env, ...libraryEnv };
|
|
369448
|
+
reclaimOrphanedPostmaster(dbDataPath);
|
|
369302
369449
|
const pgConfPath = path5.join(dbDataPath, "postgresql.conf");
|
|
369303
369450
|
const dataValid = fs5.existsSync(pgConfPath);
|
|
369304
369451
|
if (!dataValid) {
|
|
@@ -369314,6 +369461,10 @@ async function startPostgres(pg, port, dataDir, onProgress) {
|
|
|
369314
369461
|
} catch {
|
|
369315
369462
|
}
|
|
369316
369463
|
}
|
|
369464
|
+
if (await isPortInUse(host, port)) {
|
|
369465
|
+
await reclaimSpecificOrphanOnPort(port);
|
|
369466
|
+
await waitForPortFree(host, port, 1e3);
|
|
369467
|
+
}
|
|
369317
369468
|
const postgres = spawn(
|
|
369318
369469
|
binary.executables["postgres"],
|
|
369319
369470
|
[
|
|
@@ -369332,9 +369483,11 @@ async function startPostgres(pg, port, dataDir, onProgress) {
|
|
|
369332
369483
|
],
|
|
369333
369484
|
{
|
|
369334
369485
|
stdio: ["ignore", "pipe", "pipe"],
|
|
369335
|
-
env: env2
|
|
369486
|
+
env: env2,
|
|
369487
|
+
detached: true
|
|
369336
369488
|
}
|
|
369337
369489
|
);
|
|
369490
|
+
postgres.unref();
|
|
369338
369491
|
pipeProcess("postgres", postgres);
|
|
369339
369492
|
await Promise.race([
|
|
369340
369493
|
waitForTcpPort(host, port),
|
|
@@ -369358,21 +369511,28 @@ async function startPostgres(pg, port, dataDir, onProgress) {
|
|
|
369358
369511
|
dbName: pg.name,
|
|
369359
369512
|
url: `postgres://${user}:${password}@${host}:${port}/${pg.name}`,
|
|
369360
369513
|
pid: postgres.pid,
|
|
369514
|
+
detached: true,
|
|
369361
369515
|
async stop() {
|
|
369362
|
-
return killProcess(postgres);
|
|
369516
|
+
return killProcess(postgres, { detached: true });
|
|
369363
369517
|
}
|
|
369364
369518
|
};
|
|
369365
369519
|
}
|
|
369366
369520
|
async function startRedis(redis, port, onProgress) {
|
|
369367
369521
|
const binary = await ensureBinary(redisBinary, void 0, onProgress);
|
|
369368
369522
|
const host = "127.0.0.1";
|
|
369523
|
+
if (await isPortInUse(host, port)) {
|
|
369524
|
+
await reclaimSpecificOrphanOnPort(port);
|
|
369525
|
+
await waitForPortFree(host, port, 1e3);
|
|
369526
|
+
}
|
|
369369
369527
|
const redisProc = spawn(
|
|
369370
369528
|
binary.executables["redis-server"],
|
|
369371
369529
|
["--port", String(port), "--bind", host, "--daemonize", "no"],
|
|
369372
369530
|
{
|
|
369373
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
369531
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
369532
|
+
detached: true
|
|
369374
369533
|
}
|
|
369375
369534
|
);
|
|
369535
|
+
redisProc.unref();
|
|
369376
369536
|
pipeProcess("redis", redisProc);
|
|
369377
369537
|
await waitForTcpPort(host, port);
|
|
369378
369538
|
return {
|
|
@@ -369385,8 +369545,9 @@ async function startRedis(redis, port, onProgress) {
|
|
|
369385
369545
|
dbName: redis.name,
|
|
369386
369546
|
url: `redis://${host}:${port}`,
|
|
369387
369547
|
pid: redisProc.pid,
|
|
369548
|
+
detached: true,
|
|
369388
369549
|
async stop() {
|
|
369389
|
-
return killProcess(redisProc);
|
|
369550
|
+
return killProcess(redisProc, { detached: true });
|
|
369390
369551
|
}
|
|
369391
369552
|
};
|
|
369392
369553
|
}
|
|
@@ -369474,6 +369635,39 @@ async function createPostgresDatabase(postgresPath, dataDir, dbName, env2) {
|
|
|
369474
369635
|
proc.stdin?.end();
|
|
369475
369636
|
});
|
|
369476
369637
|
}
|
|
369638
|
+
function reclaimOrphanedPostmaster(dbDataPath) {
|
|
369639
|
+
const pidFile = path5.join(dbDataPath, "postmaster.pid");
|
|
369640
|
+
if (!fs5.existsSync(pidFile)) return;
|
|
369641
|
+
let pid;
|
|
369642
|
+
try {
|
|
369643
|
+
const firstLine = fs5.readFileSync(pidFile, "utf-8").split("\n")[0] ?? "";
|
|
369644
|
+
pid = parseInt(firstLine.trim(), 10);
|
|
369645
|
+
} catch {
|
|
369646
|
+
return;
|
|
369647
|
+
}
|
|
369648
|
+
if (isNaN(pid) || pid <= 0) return;
|
|
369649
|
+
let alive = false;
|
|
369650
|
+
try {
|
|
369651
|
+
process.kill(pid, 0);
|
|
369652
|
+
alive = true;
|
|
369653
|
+
} catch (e) {
|
|
369654
|
+
alive = e.code !== "ESRCH";
|
|
369655
|
+
}
|
|
369656
|
+
if (alive) {
|
|
369657
|
+
try {
|
|
369658
|
+
process.kill(-pid, "SIGKILL");
|
|
369659
|
+
} catch {
|
|
369660
|
+
try {
|
|
369661
|
+
process.kill(pid, "SIGKILL");
|
|
369662
|
+
} catch {
|
|
369663
|
+
}
|
|
369664
|
+
}
|
|
369665
|
+
}
|
|
369666
|
+
try {
|
|
369667
|
+
fs5.unlinkSync(pidFile);
|
|
369668
|
+
} catch {
|
|
369669
|
+
}
|
|
369670
|
+
}
|
|
369477
369671
|
async function waitForTcpPort(host, port, timeoutMs = 3e4) {
|
|
369478
369672
|
const startTime = Date.now();
|
|
369479
369673
|
while (Date.now() - startTime < timeoutMs) {
|
|
@@ -369487,7 +369681,7 @@ async function waitForTcpPort(host, port, timeoutMs = 3e4) {
|
|
|
369487
369681
|
}
|
|
369488
369682
|
function checkTcpPort(host, port) {
|
|
369489
369683
|
return new Promise((resolve52) => {
|
|
369490
|
-
const socket = new
|
|
369684
|
+
const socket = new net2.Socket();
|
|
369491
369685
|
socket.setTimeout(1e3);
|
|
369492
369686
|
socket.on("connect", () => {
|
|
369493
369687
|
socket.destroy();
|
|
@@ -369882,31 +370076,59 @@ function resolveEnv(env2, resources, secrets, configs, servicePort, serviceEndpo
|
|
|
369882
370076
|
}
|
|
369883
370077
|
return resolved;
|
|
369884
370078
|
}
|
|
369885
|
-
function resolveEnvForExec(env2, resources, secrets, configs) {
|
|
370079
|
+
function resolveEnvForExec(env2, resources, secrets, configs, runningContext) {
|
|
369886
370080
|
if (!env2) {
|
|
369887
370081
|
return { env: {}, missingSecrets: [], missingConfigs: [] };
|
|
369888
370082
|
}
|
|
369889
370083
|
const resolved = {};
|
|
369890
370084
|
const missingSecrets = [];
|
|
369891
370085
|
const missingConfigs = [];
|
|
370086
|
+
const canResolveRef = (ref) => {
|
|
370087
|
+
if (!runningContext) return false;
|
|
370088
|
+
switch (ref.type) {
|
|
370089
|
+
case "port":
|
|
370090
|
+
return runningContext.servicePort !== void 0;
|
|
370091
|
+
case "endpoint":
|
|
370092
|
+
return runningContext.currentServicePorts !== void 0;
|
|
370093
|
+
case "service": {
|
|
370094
|
+
if (!runningContext.serviceEndpoints) return false;
|
|
370095
|
+
if (ref.attribute === "public_url" && !runningContext.publicUrls) return false;
|
|
370096
|
+
return true;
|
|
370097
|
+
}
|
|
370098
|
+
case "volume":
|
|
370099
|
+
return false;
|
|
370100
|
+
default:
|
|
370101
|
+
return true;
|
|
370102
|
+
}
|
|
370103
|
+
};
|
|
370104
|
+
const isSkippableType = (type) => type === "port" || type === "endpoint" || type === "service" || type === "volume";
|
|
369892
370105
|
for (const [key, value] of Object.entries(env2)) {
|
|
369893
370106
|
if (typeof value === "string") {
|
|
369894
370107
|
resolved[key] = value;
|
|
369895
370108
|
continue;
|
|
369896
370109
|
}
|
|
369897
|
-
if (value.type
|
|
370110
|
+
if (isSkippableType(value.type) && !canResolveRef(value)) {
|
|
369898
370111
|
continue;
|
|
369899
370112
|
}
|
|
369900
370113
|
if (value.type === "interpolated") {
|
|
369901
|
-
const
|
|
369902
|
-
(part) => part.type === "ref" && (part.ref.type
|
|
370114
|
+
const hasUnresolvableRef = value.parts.some(
|
|
370115
|
+
(part) => part.type === "ref" && isSkippableType(part.ref.type) && !canResolveRef(part.ref)
|
|
369903
370116
|
);
|
|
369904
|
-
if (
|
|
370117
|
+
if (hasUnresolvableRef) {
|
|
369905
370118
|
continue;
|
|
369906
370119
|
}
|
|
369907
370120
|
}
|
|
369908
370121
|
try {
|
|
369909
|
-
resolved[key] = resolveEnvValue(
|
|
370122
|
+
resolved[key] = resolveEnvValue(
|
|
370123
|
+
value,
|
|
370124
|
+
resources,
|
|
370125
|
+
secrets,
|
|
370126
|
+
configs,
|
|
370127
|
+
runningContext?.servicePort,
|
|
370128
|
+
runningContext?.serviceEndpoints,
|
|
370129
|
+
runningContext?.currentServicePorts,
|
|
370130
|
+
runningContext?.publicUrls
|
|
370131
|
+
);
|
|
369910
370132
|
} catch (err) {
|
|
369911
370133
|
if (err instanceof MissingSecretError) {
|
|
369912
370134
|
missingSecrets.push({ name: err.secretName, envVar: key });
|
|
@@ -369987,7 +370209,6 @@ function startService(service, resources, secrets, configs, endpointPorts, servi
|
|
|
369987
370209
|
}
|
|
369988
370210
|
};
|
|
369989
370211
|
}
|
|
369990
|
-
var execFileAsync = promisify(execFile);
|
|
369991
370212
|
var InstanceStateManager = class {
|
|
369992
370213
|
stateDir;
|
|
369993
370214
|
statePath;
|
|
@@ -370079,8 +370300,20 @@ var InstanceStateManager = class {
|
|
|
370079
370300
|
releaseLock();
|
|
370080
370301
|
}
|
|
370081
370302
|
}
|
|
370303
|
+
/**
|
|
370304
|
+
* Kill every Specific-owned process (postgres, redis, electric, temporal,
|
|
370305
|
+
* drizzle-gateway) whose command line references this key's data
|
|
370306
|
+
* directory. This is the safety net for when state.json is missing or
|
|
370307
|
+
* incomplete — the port allocator can pick a different block on restart,
|
|
370308
|
+
* so port-based cleanup alone isn't enough. We identify orphans by their
|
|
370309
|
+
* data-dir/config-dir argument, which is stable across runs.
|
|
370310
|
+
*/
|
|
370311
|
+
async sweepKeyDirOrphans() {
|
|
370312
|
+
await killSpecificProcessesReferencing(this.stateDir);
|
|
370313
|
+
}
|
|
370082
370314
|
async cleanStaleState() {
|
|
370083
370315
|
if (!fs6.existsSync(this.statePath)) {
|
|
370316
|
+
await this.sweepKeyDirOrphans();
|
|
370084
370317
|
return false;
|
|
370085
370318
|
}
|
|
370086
370319
|
const releaseLock = await this.acquireLock();
|
|
@@ -370089,6 +370322,7 @@ var InstanceStateManager = class {
|
|
|
370089
370322
|
const state = JSON.parse(content);
|
|
370090
370323
|
if (!this.isProcessRunning(state.owner.pid)) {
|
|
370091
370324
|
await this.killOrphanedProcesses(state);
|
|
370325
|
+
await this.sweepKeyDirOrphans();
|
|
370092
370326
|
fs6.unlinkSync(this.statePath);
|
|
370093
370327
|
return true;
|
|
370094
370328
|
}
|
|
@@ -370118,18 +370352,22 @@ var InstanceStateManager = class {
|
|
|
370118
370352
|
}
|
|
370119
370353
|
}
|
|
370120
370354
|
for (const db of Object.values(state.databases)) {
|
|
370121
|
-
|
|
370122
|
-
|
|
370123
|
-
|
|
370124
|
-
|
|
370125
|
-
|
|
370126
|
-
|
|
370127
|
-
|
|
370128
|
-
|
|
370129
|
-
|
|
370130
|
-
|
|
370131
|
-
|
|
370132
|
-
|
|
370355
|
+
killTrackedProcess(db.pid, db.detached, this.isProcessRunning.bind(this));
|
|
370356
|
+
if (db.port) {
|
|
370357
|
+
await reclaimSpecificOrphanOnPort(db.port);
|
|
370358
|
+
}
|
|
370359
|
+
}
|
|
370360
|
+
for (const aux of Object.values(state.electric ?? {})) {
|
|
370361
|
+
killTrackedProcess(aux.pid, aux.detached, this.isProcessRunning.bind(this));
|
|
370362
|
+
if (aux.port) {
|
|
370363
|
+
await reclaimSpecificOrphanOnPort(aux.port);
|
|
370364
|
+
}
|
|
370365
|
+
}
|
|
370366
|
+
if (state.drizzleGateway) {
|
|
370367
|
+
const dg = state.drizzleGateway;
|
|
370368
|
+
killTrackedProcess(dg.pid, dg.detached, this.isProcessRunning.bind(this));
|
|
370369
|
+
if (dg.port) {
|
|
370370
|
+
await reclaimSpecificOrphanOnPort(dg.port);
|
|
370133
370371
|
}
|
|
370134
370372
|
}
|
|
370135
370373
|
}
|
|
@@ -370200,6 +370438,45 @@ var InstanceStateManager = class {
|
|
|
370200
370438
|
releaseLock();
|
|
370201
370439
|
}
|
|
370202
370440
|
}
|
|
370441
|
+
async registerElectric(databaseName, info) {
|
|
370442
|
+
if (!this.ownsInstances) {
|
|
370443
|
+
throw new Error("Cannot register Electric: not the owner");
|
|
370444
|
+
}
|
|
370445
|
+
const releaseLock = await this.acquireLock();
|
|
370446
|
+
try {
|
|
370447
|
+
const state = this.readState();
|
|
370448
|
+
state.electric = { ...state.electric ?? {}, [databaseName]: info };
|
|
370449
|
+
this.writeStateAtomic(state);
|
|
370450
|
+
} finally {
|
|
370451
|
+
releaseLock();
|
|
370452
|
+
}
|
|
370453
|
+
}
|
|
370454
|
+
async registerDrizzleGateway(info) {
|
|
370455
|
+
if (!this.ownsInstances) {
|
|
370456
|
+
throw new Error("Cannot register Drizzle Gateway: not the owner");
|
|
370457
|
+
}
|
|
370458
|
+
const releaseLock = await this.acquireLock();
|
|
370459
|
+
try {
|
|
370460
|
+
const state = this.readState();
|
|
370461
|
+
state.drizzleGateway = info;
|
|
370462
|
+
this.writeStateAtomic(state);
|
|
370463
|
+
} finally {
|
|
370464
|
+
releaseLock();
|
|
370465
|
+
}
|
|
370466
|
+
}
|
|
370467
|
+
async setPublicUrls(publicUrls) {
|
|
370468
|
+
if (!this.ownsInstances) {
|
|
370469
|
+
throw new Error("Cannot set public URLs: not the owner");
|
|
370470
|
+
}
|
|
370471
|
+
const releaseLock = await this.acquireLock();
|
|
370472
|
+
try {
|
|
370473
|
+
const state = this.readState();
|
|
370474
|
+
state.publicUrls = publicUrls;
|
|
370475
|
+
this.writeStateAtomic(state);
|
|
370476
|
+
} finally {
|
|
370477
|
+
releaseLock();
|
|
370478
|
+
}
|
|
370479
|
+
}
|
|
370203
370480
|
async releaseOwnership() {
|
|
370204
370481
|
if (!this.ownsInstances) {
|
|
370205
370482
|
return;
|
|
@@ -370225,17 +370502,15 @@ var InstanceStateManager = class {
|
|
|
370225
370502
|
fs6.renameSync(tmpPath, this.statePath);
|
|
370226
370503
|
}
|
|
370227
370504
|
};
|
|
370228
|
-
|
|
370505
|
+
function killTrackedProcess(pid, detached, isProcessRunning) {
|
|
370506
|
+
if (!pid || !isProcessRunning(pid)) return;
|
|
370229
370507
|
try {
|
|
370230
|
-
|
|
370231
|
-
"
|
|
370232
|
-
|
|
370233
|
-
|
|
370234
|
-
|
|
370235
|
-
const pid = parseInt(stdout.trim().split("\n")[0], 10);
|
|
370236
|
-
return isNaN(pid) ? void 0 : pid;
|
|
370508
|
+
if (detached) {
|
|
370509
|
+
process.kill(-pid, "SIGKILL");
|
|
370510
|
+
} else {
|
|
370511
|
+
process.kill(pid, "SIGKILL");
|
|
370512
|
+
}
|
|
370237
370513
|
} catch {
|
|
370238
|
-
return void 0;
|
|
370239
370514
|
}
|
|
370240
370515
|
}
|
|
370241
370516
|
var __dirname = path7.dirname(fileURLToPath(import.meta.url));
|
|
@@ -370533,21 +370808,15 @@ async function startElectric(postgres, port, dataDir, options2) {
|
|
|
370533
370808
|
);
|
|
370534
370809
|
const secret = generateRandomString(32);
|
|
370535
370810
|
const host = "127.0.0.1";
|
|
370536
|
-
if (await
|
|
370537
|
-
|
|
370538
|
-
|
|
370539
|
-
await sleep2(100);
|
|
370540
|
-
if (!await checkTcpPort2(host, port)) {
|
|
370541
|
-
freed = true;
|
|
370542
|
-
break;
|
|
370543
|
-
}
|
|
370544
|
-
}
|
|
370811
|
+
if (await isPortInUse(host, port)) {
|
|
370812
|
+
await reclaimSpecificOrphanOnPort(port);
|
|
370813
|
+
const freed = await waitForPortFree(host, port, 3e3);
|
|
370545
370814
|
if (!freed) {
|
|
370546
370815
|
throw new Error(
|
|
370547
|
-
`Electric port ${port} is already in use
|
|
370816
|
+
`Electric port ${port} is already in use by a non-Specific process. Find it with \`lsof -i :${port}\` and kill it, then retry.`
|
|
370548
370817
|
);
|
|
370549
370818
|
}
|
|
370550
|
-
writeLog("electric", `Port ${port} was occupied but
|
|
370819
|
+
writeLog("electric", `Port ${port} was occupied but has been reclaimed`);
|
|
370551
370820
|
}
|
|
370552
370821
|
const storageDir = path9.join(process.cwd(), dataDir, `electric-${postgres.name}`);
|
|
370553
370822
|
fs8.rmSync(storageDir, { recursive: true, force: true });
|
|
@@ -370559,6 +370828,7 @@ async function startElectric(postgres, port, dataDir, options2) {
|
|
|
370559
370828
|
writeLog("electric", `Binary: ${binary.executables["electric"]}`);
|
|
370560
370829
|
const electric = spawn3(binary.executables["electric"], [], {
|
|
370561
370830
|
stdio: ["ignore", "pipe", "pipe"],
|
|
370831
|
+
detached: true,
|
|
370562
370832
|
env: {
|
|
370563
370833
|
...process.env,
|
|
370564
370834
|
DATABASE_URL: postgres.url,
|
|
@@ -370569,16 +370839,18 @@ async function startElectric(postgres, port, dataDir, options2) {
|
|
|
370569
370839
|
ELECTRIC_STORAGE: "MEMORY"
|
|
370570
370840
|
}
|
|
370571
370841
|
});
|
|
370842
|
+
electric.unref();
|
|
370572
370843
|
pipeProcess("electric", electric);
|
|
370573
370844
|
await waitForTcpPort2(host, port);
|
|
370574
370845
|
return {
|
|
370575
370846
|
databaseName: postgres.name,
|
|
370576
370847
|
pid: electric.pid,
|
|
370848
|
+
detached: true,
|
|
370577
370849
|
port,
|
|
370578
370850
|
url: `http://${host}:${port}`,
|
|
370579
370851
|
secret,
|
|
370580
370852
|
async stop() {
|
|
370581
|
-
return killProcess(electric);
|
|
370853
|
+
return killProcess(electric, { detached: true });
|
|
370582
370854
|
}
|
|
370583
370855
|
};
|
|
370584
370856
|
}
|
|
@@ -370595,7 +370867,7 @@ async function waitForTcpPort2(host, port, timeoutMs = 3e4) {
|
|
|
370595
370867
|
}
|
|
370596
370868
|
function checkTcpPort2(host, port) {
|
|
370597
370869
|
return new Promise((resolve52) => {
|
|
370598
|
-
const socket = new
|
|
370870
|
+
const socket = new net3.Socket();
|
|
370599
370871
|
socket.setTimeout(1e3);
|
|
370600
370872
|
socket.on("connect", () => {
|
|
370601
370873
|
socket.destroy();
|
|
@@ -370781,22 +371053,29 @@ async function startDrizzleGateway(postgresInstances, port, configDir, options2)
|
|
|
370781
371053
|
"drizzle-gateway",
|
|
370782
371054
|
`Binary: ${binary.executables["drizzle-gateway"]}`
|
|
370783
371055
|
);
|
|
371056
|
+
if (await isPortInUse(host, port)) {
|
|
371057
|
+
await reclaimSpecificOrphanOnPort(port);
|
|
371058
|
+
await waitForPortFree(host, port, 1e3);
|
|
371059
|
+
}
|
|
370784
371060
|
const drizzleGateway = spawn4(binary.executables["drizzle-gateway"], [], {
|
|
370785
371061
|
stdio: ["ignore", "pipe", "pipe"],
|
|
371062
|
+
detached: true,
|
|
370786
371063
|
env: {
|
|
370787
371064
|
...process.env,
|
|
370788
371065
|
STORE_PATH: drizzleConfigDir,
|
|
370789
371066
|
PORT: String(port)
|
|
370790
371067
|
}
|
|
370791
371068
|
});
|
|
371069
|
+
drizzleGateway.unref();
|
|
370792
371070
|
pipeProcess("drizzle-gateway", drizzleGateway);
|
|
370793
371071
|
await waitForTcpPort3(host, port);
|
|
370794
371072
|
return {
|
|
370795
371073
|
port,
|
|
370796
371074
|
url: `http://${host}:${port}`,
|
|
370797
371075
|
pid: drizzleGateway.pid,
|
|
371076
|
+
detached: true,
|
|
370798
371077
|
async stop() {
|
|
370799
|
-
return killProcess(drizzleGateway);
|
|
371078
|
+
return killProcess(drizzleGateway, { detached: true });
|
|
370800
371079
|
}
|
|
370801
371080
|
};
|
|
370802
371081
|
}
|
|
@@ -370815,7 +371094,7 @@ async function waitForTcpPort3(host, port, timeoutMs = 3e4) {
|
|
|
370815
371094
|
}
|
|
370816
371095
|
function checkTcpPort3(host, port) {
|
|
370817
371096
|
return new Promise((resolve52) => {
|
|
370818
|
-
const socket = new
|
|
371097
|
+
const socket = new net4.Socket();
|
|
370819
371098
|
socket.setTimeout(1e3);
|
|
370820
371099
|
socket.on("connect", () => {
|
|
370821
371100
|
socket.destroy();
|
|
@@ -371149,6 +371428,12 @@ async function startTemporalDevServer(temporals, grpcPort, uiPort, dataDir, onPr
|
|
|
371149
371428
|
}
|
|
371150
371429
|
const host = "127.0.0.1";
|
|
371151
371430
|
const namespaceArgs = temporals.flatMap((t) => ["--namespace", t.name]);
|
|
371431
|
+
for (const p of [grpcPort, uiPort]) {
|
|
371432
|
+
if (await isPortInUse(host, p)) {
|
|
371433
|
+
await reclaimSpecificOrphanOnPort(p);
|
|
371434
|
+
await waitForPortFree(host, p, 1e3);
|
|
371435
|
+
}
|
|
371436
|
+
}
|
|
371152
371437
|
const proc = spawn5(
|
|
371153
371438
|
binary.executables["temporal"],
|
|
371154
371439
|
[
|
|
@@ -371167,12 +371452,14 @@ async function startTemporalDevServer(temporals, grpcPort, uiPort, dataDir, onPr
|
|
|
371167
371452
|
"pretty"
|
|
371168
371453
|
],
|
|
371169
371454
|
{
|
|
371170
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
371455
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
371456
|
+
detached: true
|
|
371171
371457
|
}
|
|
371172
371458
|
);
|
|
371459
|
+
proc.unref();
|
|
371173
371460
|
pipeProcess("temporal", proc);
|
|
371174
371461
|
await waitForTcpPort4(host, grpcPort);
|
|
371175
|
-
const stopServer = () => killProcess(proc);
|
|
371462
|
+
const stopServer = () => killProcess(proc, { detached: true });
|
|
371176
371463
|
const instances = temporals.map((temporal, i) => ({
|
|
371177
371464
|
name: temporal.name,
|
|
371178
371465
|
type: "temporal",
|
|
@@ -371184,6 +371471,7 @@ async function startTemporalDevServer(temporals, grpcPort, uiPort, dataDir, onPr
|
|
|
371184
371471
|
url: `${host}:${grpcPort}`,
|
|
371185
371472
|
uiPort,
|
|
371186
371473
|
pid: i === 0 ? proc.pid : void 0,
|
|
371474
|
+
detached: i === 0 ? true : void 0,
|
|
371187
371475
|
// Only the first instance owns the server lifecycle
|
|
371188
371476
|
stop: i === 0 ? stopServer : async () => {
|
|
371189
371477
|
}
|
|
@@ -371203,7 +371491,7 @@ async function waitForTcpPort4(host, port, timeoutMs = 3e4) {
|
|
|
371203
371491
|
}
|
|
371204
371492
|
function checkTcpPort4(host, port) {
|
|
371205
371493
|
return new Promise((resolve52) => {
|
|
371206
|
-
const socket = new
|
|
371494
|
+
const socket = new net5.Socket();
|
|
371207
371495
|
socket.setTimeout(1e3);
|
|
371208
371496
|
socket.on("connect", () => {
|
|
371209
371497
|
socket.destroy();
|
|
@@ -371305,6 +371593,8 @@ async function startResources(options2) {
|
|
|
371305
371593
|
dbName: instance.dbName,
|
|
371306
371594
|
url: instance.url
|
|
371307
371595
|
};
|
|
371596
|
+
if (instance.pid !== void 0) dbState.pid = instance.pid;
|
|
371597
|
+
if (instance.detached) dbState.detached = true;
|
|
371308
371598
|
if (instance.reshapeEnabled) {
|
|
371309
371599
|
dbState.reshapeEnabled = true;
|
|
371310
371600
|
if (instance.reshapeSearchPath) {
|
|
@@ -371327,7 +371617,7 @@ async function startResources(options2) {
|
|
|
371327
371617
|
startedResources.push(instance);
|
|
371328
371618
|
callbacks.onResourceReady?.(redis.name, instance);
|
|
371329
371619
|
log(`Redis "${redis.name}" ready`);
|
|
371330
|
-
|
|
371620
|
+
const redisState = {
|
|
371331
371621
|
engine: "redis",
|
|
371332
371622
|
port: instance.port,
|
|
371333
371623
|
host: instance.host,
|
|
@@ -371335,7 +371625,10 @@ async function startResources(options2) {
|
|
|
371335
371625
|
password: instance.password,
|
|
371336
371626
|
dbName: instance.dbName,
|
|
371337
371627
|
url: instance.url
|
|
371338
|
-
}
|
|
371628
|
+
};
|
|
371629
|
+
if (instance.pid !== void 0) redisState.pid = instance.pid;
|
|
371630
|
+
if (instance.detached) redisState.detached = true;
|
|
371631
|
+
await stateManager.registerDatabase(redis.name, redisState);
|
|
371339
371632
|
}
|
|
371340
371633
|
for (const storage of storageConfigs) {
|
|
371341
371634
|
if (signal?.cancelled) {
|
|
@@ -371389,6 +371682,8 @@ async function startResources(options2) {
|
|
|
371389
371682
|
dbName: "",
|
|
371390
371683
|
url: instance.url
|
|
371391
371684
|
};
|
|
371685
|
+
if (instance.pid !== void 0) dbState.pid = instance.pid;
|
|
371686
|
+
if (instance.detached) dbState.detached = true;
|
|
371392
371687
|
await stateManager.registerDatabase(instance.name, dbState);
|
|
371393
371688
|
}
|
|
371394
371689
|
}
|
|
@@ -371437,6 +371732,13 @@ async function startResources(options2) {
|
|
|
371437
371732
|
syncUrl: electricInstance.url,
|
|
371438
371733
|
syncSecret: electricInstance.secret
|
|
371439
371734
|
});
|
|
371735
|
+
if (electricInstance.pid !== void 0) {
|
|
371736
|
+
await stateManager.registerElectric(pgName, {
|
|
371737
|
+
port: electricInstance.port,
|
|
371738
|
+
pid: electricInstance.pid,
|
|
371739
|
+
detached: electricInstance.detached
|
|
371740
|
+
});
|
|
371741
|
+
}
|
|
371440
371742
|
callbacks.onElectricReady?.(pgName, electricInstance);
|
|
371441
371743
|
log(`Electric sync for "${pgName}" ready at ${electricInstance.url}`);
|
|
371442
371744
|
}
|
|
@@ -371522,7 +371824,7 @@ var BlockPortAllocator = class _BlockPortAllocator {
|
|
|
371522
371824
|
};
|
|
371523
371825
|
function isPortAvailable(port) {
|
|
371524
371826
|
return new Promise((resolve52) => {
|
|
371525
|
-
const server =
|
|
371827
|
+
const server = net6.createServer();
|
|
371526
371828
|
server.once("error", () => resolve52(false));
|
|
371527
371829
|
server.once("listening", () => {
|
|
371528
371830
|
server.close(() => resolve52(true));
|
|
@@ -371636,7 +371938,7 @@ var TunnelClientImpl = class extends EventEmitter2 {
|
|
|
371636
371938
|
}
|
|
371637
371939
|
}
|
|
371638
371940
|
addPoolConnection() {
|
|
371639
|
-
const remote =
|
|
371941
|
+
const remote = net7.connect({ host: this.tunnelHost, port: this.info.port });
|
|
371640
371942
|
remote.setKeepAlive(true, 3e4);
|
|
371641
371943
|
this.pool.add(remote);
|
|
371642
371944
|
remote.once("data", (firstChunk) => {
|
|
@@ -371651,7 +371953,7 @@ var TunnelClientImpl = class extends EventEmitter2 {
|
|
|
371651
371953
|
remote.on("close", () => this.onIdleClose(remote, errored));
|
|
371652
371954
|
}
|
|
371653
371955
|
pipeToLocal(remote, firstChunk) {
|
|
371654
|
-
const local =
|
|
371956
|
+
const local = net7.connect({ host: "127.0.0.1", port: this.localPort }, () => {
|
|
371655
371957
|
local.write(firstChunk);
|
|
371656
371958
|
remote.pipe(local);
|
|
371657
371959
|
local.pipe(remote);
|
|
@@ -371905,21 +372207,33 @@ var DevEnvironment = class extends TypedEventEmitter {
|
|
|
371905
372207
|
for (const electric of this.electricInstances) {
|
|
371906
372208
|
if (electric.pid) {
|
|
371907
372209
|
try {
|
|
371908
|
-
|
|
372210
|
+
if (electric.detached) {
|
|
372211
|
+
process.kill(-electric.pid, "SIGKILL");
|
|
372212
|
+
} else {
|
|
372213
|
+
process.kill(electric.pid, "SIGKILL");
|
|
372214
|
+
}
|
|
371909
372215
|
} catch {
|
|
371910
372216
|
}
|
|
371911
372217
|
}
|
|
371912
372218
|
}
|
|
371913
372219
|
if (this.drizzleGateway?.pid) {
|
|
371914
372220
|
try {
|
|
371915
|
-
|
|
372221
|
+
if (this.drizzleGateway.detached) {
|
|
372222
|
+
process.kill(-this.drizzleGateway.pid, "SIGKILL");
|
|
372223
|
+
} else {
|
|
372224
|
+
process.kill(this.drizzleGateway.pid, "SIGKILL");
|
|
372225
|
+
}
|
|
371916
372226
|
} catch {
|
|
371917
372227
|
}
|
|
371918
372228
|
}
|
|
371919
372229
|
for (const resource of this.resources.values()) {
|
|
371920
372230
|
if (resource.pid) {
|
|
371921
372231
|
try {
|
|
371922
|
-
|
|
372232
|
+
if (resource.detached) {
|
|
372233
|
+
process.kill(-resource.pid, "SIGKILL");
|
|
372234
|
+
} else {
|
|
372235
|
+
process.kill(resource.pid, "SIGKILL");
|
|
372236
|
+
}
|
|
371923
372237
|
} catch {
|
|
371924
372238
|
}
|
|
371925
372239
|
}
|
|
@@ -372073,21 +372387,33 @@ var DevEnvironment = class extends TypedEventEmitter {
|
|
|
372073
372387
|
for (const electric of this.electricInstances) {
|
|
372074
372388
|
if (electric.pid) {
|
|
372075
372389
|
try {
|
|
372076
|
-
|
|
372390
|
+
if (electric.detached) {
|
|
372391
|
+
process.kill(-electric.pid, "SIGKILL");
|
|
372392
|
+
} else {
|
|
372393
|
+
process.kill(electric.pid, "SIGKILL");
|
|
372394
|
+
}
|
|
372077
372395
|
} catch {
|
|
372078
372396
|
}
|
|
372079
372397
|
}
|
|
372080
372398
|
}
|
|
372081
372399
|
if (this.drizzleGateway?.pid) {
|
|
372082
372400
|
try {
|
|
372083
|
-
|
|
372401
|
+
if (this.drizzleGateway.detached) {
|
|
372402
|
+
process.kill(-this.drizzleGateway.pid, "SIGKILL");
|
|
372403
|
+
} else {
|
|
372404
|
+
process.kill(this.drizzleGateway.pid, "SIGKILL");
|
|
372405
|
+
}
|
|
372084
372406
|
} catch {
|
|
372085
372407
|
}
|
|
372086
372408
|
}
|
|
372087
372409
|
for (const resource of this.resources.values()) {
|
|
372088
372410
|
if (resource.pid) {
|
|
372089
372411
|
try {
|
|
372090
|
-
|
|
372412
|
+
if (resource.detached) {
|
|
372413
|
+
process.kill(-resource.pid, "SIGKILL");
|
|
372414
|
+
} else {
|
|
372415
|
+
process.kill(resource.pid, "SIGKILL");
|
|
372416
|
+
}
|
|
372091
372417
|
} catch {
|
|
372092
372418
|
}
|
|
372093
372419
|
}
|
|
@@ -372301,6 +372627,13 @@ var DevEnvironment = class extends TypedEventEmitter {
|
|
|
372301
372627
|
);
|
|
372302
372628
|
this.drizzleGateway = drizzleGateway;
|
|
372303
372629
|
this.systemLog(`Database viewer ready at ${drizzleGateway.url}`);
|
|
372630
|
+
if (drizzleGateway.pid !== void 0) {
|
|
372631
|
+
await stateManager.registerDrizzleGateway({
|
|
372632
|
+
port: drizzleGateway.port,
|
|
372633
|
+
pid: drizzleGateway.pid,
|
|
372634
|
+
detached: drizzleGateway.detached
|
|
372635
|
+
});
|
|
372636
|
+
}
|
|
372304
372637
|
} catch (err) {
|
|
372305
372638
|
this.systemLog(
|
|
372306
372639
|
`Failed to start database viewer: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -372524,6 +372857,7 @@ Add them to the config block in specific.local`
|
|
|
372524
372857
|
publicUrls.set(svc.name, `localhost:${svc.port}`);
|
|
372525
372858
|
}
|
|
372526
372859
|
}
|
|
372860
|
+
await stateManager.setPublicUrls(Object.fromEntries(publicUrls));
|
|
372527
372861
|
const runningServices = [];
|
|
372528
372862
|
const resolveServiceCwd = (service) => {
|
|
372529
372863
|
if (service.root)
|
|
@@ -373341,7 +373675,7 @@ function trackEvent(event, properties) {
|
|
|
373341
373675
|
event,
|
|
373342
373676
|
properties: {
|
|
373343
373677
|
...properties,
|
|
373344
|
-
cli_version: "0.1.
|
|
373678
|
+
cli_version: "0.1.127",
|
|
373345
373679
|
platform: process.platform,
|
|
373346
373680
|
node_version: process.version,
|
|
373347
373681
|
project_id: getProjectId()
|
|
@@ -373659,7 +373993,7 @@ Valid agents: ${VALID_AGENT_IDS.join(", ")}`
|
|
|
373659
373993
|
}
|
|
373660
373994
|
|
|
373661
373995
|
// src/commands/docs.tsx
|
|
373662
|
-
import { readFileSync as
|
|
373996
|
+
import { readFileSync as readFileSync10, existsSync as existsSync17 } from "fs";
|
|
373663
373997
|
import { spawn as spawn6 } from "child_process";
|
|
373664
373998
|
import { join as join19, dirname as dirname8 } from "path";
|
|
373665
373999
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -373673,7 +374007,7 @@ var BETA_REGISTRY = [
|
|
|
373673
374007
|
];
|
|
373674
374008
|
|
|
373675
374009
|
// src/lib/beta/storage.ts
|
|
373676
|
-
import { readFileSync as
|
|
374010
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, existsSync as existsSync16, mkdirSync as mkdirSync13 } from "fs";
|
|
373677
374011
|
import { join as join18 } from "path";
|
|
373678
374012
|
var BETAS_FILE = ".specific/betas.json";
|
|
373679
374013
|
function loadEnabledBetas(projectDir = process.cwd()) {
|
|
@@ -373682,7 +374016,7 @@ function loadEnabledBetas(projectDir = process.cwd()) {
|
|
|
373682
374016
|
return [];
|
|
373683
374017
|
}
|
|
373684
374018
|
try {
|
|
373685
|
-
const content =
|
|
374019
|
+
const content = readFileSync9(filePath, "utf-8");
|
|
373686
374020
|
const data = JSON.parse(content);
|
|
373687
374021
|
return data.enabled ?? [];
|
|
373688
374022
|
} catch {
|
|
@@ -373787,15 +374121,15 @@ function resolveEmbeddedDoc(path27) {
|
|
|
373787
374121
|
function resolveFilesystemDoc(path27) {
|
|
373788
374122
|
if (!path27) {
|
|
373789
374123
|
const indexPath2 = join19(docsDir, "index.md");
|
|
373790
|
-
return existsSync17(indexPath2) ?
|
|
374124
|
+
return existsSync17(indexPath2) ? readFileSync10(indexPath2, "utf-8") : null;
|
|
373791
374125
|
}
|
|
373792
374126
|
const directPath = join19(docsDir, `${path27}.md`);
|
|
373793
374127
|
if (existsSync17(directPath)) {
|
|
373794
|
-
return
|
|
374128
|
+
return readFileSync10(directPath, "utf-8");
|
|
373795
374129
|
}
|
|
373796
374130
|
const indexPath = join19(docsDir, path27, "index.md");
|
|
373797
374131
|
if (existsSync17(indexPath)) {
|
|
373798
|
-
return
|
|
374132
|
+
return readFileSync10(indexPath, "utf-8");
|
|
373799
374133
|
}
|
|
373800
374134
|
return null;
|
|
373801
374135
|
}
|
|
@@ -374879,11 +375213,13 @@ function DevUI({ instanceKey, tunnelEnabled }) {
|
|
|
374879
375213
|
};
|
|
374880
375214
|
process.on("SIGINT", handleSignal);
|
|
374881
375215
|
process.on("SIGTERM", handleSignal);
|
|
375216
|
+
process.on("SIGHUP", handleSignal);
|
|
374882
375217
|
process.on("uncaughtException", handleCrash);
|
|
374883
375218
|
process.on("unhandledRejection", handleCrash);
|
|
374884
375219
|
return () => {
|
|
374885
375220
|
process.off("SIGINT", handleSignal);
|
|
374886
375221
|
process.off("SIGTERM", handleSignal);
|
|
375222
|
+
process.off("SIGHUP", handleSignal);
|
|
374887
375223
|
process.off("uncaughtException", handleCrash);
|
|
374888
375224
|
process.off("unhandledRejection", handleCrash);
|
|
374889
375225
|
};
|
|
@@ -376585,7 +376921,7 @@ function startSpinner(text) {
|
|
|
376585
376921
|
}
|
|
376586
376922
|
};
|
|
376587
376923
|
}
|
|
376588
|
-
async function execCommand(serviceName, command, instanceKey = "default") {
|
|
376924
|
+
async function execCommand(serviceName, command, instanceKey = "default", options2 = {}) {
|
|
376589
376925
|
if (command.length === 0) {
|
|
376590
376926
|
console.error(
|
|
376591
376927
|
"Error: No command provided. Usage: specific exec <service> -- <command>"
|
|
@@ -376723,11 +377059,32 @@ async function execCommand(serviceName, command, instanceKey = "default") {
|
|
|
376723
377059
|
}
|
|
376724
377060
|
}
|
|
376725
377061
|
}
|
|
377062
|
+
let runningContext;
|
|
377063
|
+
if (existingInstances) {
|
|
377064
|
+
const serviceEndpoints = /* @__PURE__ */ new Map();
|
|
377065
|
+
for (const [name, svcState] of Object.entries(existingInstances.services)) {
|
|
377066
|
+
if (svcState.port !== void 0) {
|
|
377067
|
+
serviceEndpoints.set(name, [
|
|
377068
|
+
{ serviceName: name, endpointName: "default", port: svcState.port }
|
|
377069
|
+
]);
|
|
377070
|
+
}
|
|
377071
|
+
}
|
|
377072
|
+
const selfPort = existingInstances.services[serviceName]?.port;
|
|
377073
|
+
runningContext = { serviceEndpoints };
|
|
377074
|
+
if (selfPort !== void 0) {
|
|
377075
|
+
runningContext.servicePort = selfPort;
|
|
377076
|
+
runningContext.currentServicePorts = /* @__PURE__ */ new Map([["default", selfPort]]);
|
|
377077
|
+
}
|
|
377078
|
+
if (existingInstances.publicUrls) {
|
|
377079
|
+
runningContext.publicUrls = new Map(Object.entries(existingInstances.publicUrls));
|
|
377080
|
+
}
|
|
377081
|
+
}
|
|
377082
|
+
const effectiveEnv = runningContext && service.dev?.env ? { ...service.env, ...service.dev.env } : service.env;
|
|
376726
377083
|
let resolvedEnv;
|
|
376727
377084
|
try {
|
|
376728
377085
|
const secrets = await prepareSecrets(config.secrets);
|
|
376729
377086
|
const configs = await prepareConfigs(config.configs);
|
|
376730
|
-
const result = resolveEnvForExec(
|
|
377087
|
+
const result = resolveEnvForExec(effectiveEnv, resources, secrets, configs, runningContext);
|
|
376731
377088
|
resolvedEnv = result.env;
|
|
376732
377089
|
if (result.missingSecrets.length > 0 || result.missingConfigs.length > 0) {
|
|
376733
377090
|
if (result.missingSecrets.length > 0) {
|
|
@@ -376762,7 +377119,9 @@ async function execCommand(serviceName, command, instanceKey = "default") {
|
|
|
376762
377119
|
process.on("SIGINT", () => handleSignal("SIGINT"));
|
|
376763
377120
|
process.on("SIGTERM", () => handleSignal("SIGTERM"));
|
|
376764
377121
|
let effectiveCwd = process.cwd();
|
|
376765
|
-
if (
|
|
377122
|
+
if (options2.cwd) {
|
|
377123
|
+
effectiveCwd = path21.resolve(process.cwd(), options2.cwd);
|
|
377124
|
+
} else if (service.root) {
|
|
376766
377125
|
effectiveCwd = path21.resolve(process.cwd(), service.root);
|
|
376767
377126
|
} else if (service.build) {
|
|
376768
377127
|
const build = config.builds.find((b) => b.name === service.build.name);
|
|
@@ -377422,7 +377781,7 @@ function compareVersions(a, b) {
|
|
|
377422
377781
|
return 0;
|
|
377423
377782
|
}
|
|
377424
377783
|
async function checkForUpdate() {
|
|
377425
|
-
const currentVersion = "0.1.
|
|
377784
|
+
const currentVersion = "0.1.127";
|
|
377426
377785
|
const response = await fetch(`${BINARIES_BASE_URL}/latest?t=${Date.now()}`);
|
|
377427
377786
|
if (!response.ok) {
|
|
377428
377787
|
throw new Error(`Failed to check for updates: HTTP ${response.status}`);
|
|
@@ -377692,7 +378051,7 @@ async function projectListCommand() {
|
|
|
377692
378051
|
var program = new Command();
|
|
377693
378052
|
var env = "production";
|
|
377694
378053
|
var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
|
|
377695
|
-
program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.
|
|
378054
|
+
program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.127").enablePositionalOptions();
|
|
377696
378055
|
program.command("init").description("Initialize project for use with a coding agent").option("--agent <name...>", "Agents to configure (cursor, claude, codex, other)").addHelpText("after", `
|
|
377697
378056
|
Examples:
|
|
377698
378057
|
$ specific init
|
|
@@ -377722,13 +378081,14 @@ Examples:
|
|
|
377722
378081
|
$ specific deploy --secret db_url=postgres://... --config domain=app.com`).action((options2) => {
|
|
377723
378082
|
deployCommand(options2);
|
|
377724
378083
|
});
|
|
377725
|
-
program.command("exec <service> [args...]").description("Run a one-off command with service environment").option("-k, --key <key>", "Dev environment namespace (auto-detected from git worktree if not specified)").passThroughOptions().addHelpText("after", `
|
|
378084
|
+
program.command("exec <service> [args...]").description("Run a one-off command with service environment").option("-k, --key <key>", "Dev environment namespace (auto-detected from git worktree if not specified)").option("--cwd <path>", "Override working directory (defaults to service.root or build.root)").passThroughOptions().addHelpText("after", `
|
|
377726
378085
|
Examples:
|
|
377727
378086
|
$ specific exec api -- npm run migrate
|
|
377728
|
-
$ specific exec worker -- python manage.py shell
|
|
378087
|
+
$ specific exec worker -- python manage.py shell
|
|
378088
|
+
$ specific exec --cwd . api -- npx expo start`).action(async (service, args, options2) => {
|
|
377729
378089
|
const filteredArgs = args[0] === "--" ? args.slice(1) : args;
|
|
377730
378090
|
const key = options2.key ?? getDefaultKey();
|
|
377731
|
-
await execCommand(service, filteredArgs, key);
|
|
378091
|
+
await execCommand(service, filteredArgs, key, options2.cwd !== void 0 ? { cwd: options2.cwd } : {});
|
|
377732
378092
|
});
|
|
377733
378093
|
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().addHelpText("after", `
|
|
377734
378094
|
Examples:
|