@hamedb89/localghost 0.1.15 → 0.2.0
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/README.md +12 -2
- package/dist/cli.js +169 -27
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +40 -4
- package/dist/index.js +83 -6
- package/dist/index.js.map +1 -1
- package/dist/vite.js +13 -0
- package/dist/vite.js.map +1 -1
- package/docs/localghost.1.md +6 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -93,6 +93,8 @@ Check whether the machine is ready:
|
|
|
93
93
|
localghost doctor
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
+
Doctor also reports occupied configured ports, stale registry leases, and port allocations shared by multiple Localghost instances. Use `--json` for agent-readable output.
|
|
97
|
+
|
|
96
98
|
Prepare `/etc/hosts` and the local Caddyfile:
|
|
97
99
|
|
|
98
100
|
```sh
|
|
@@ -111,6 +113,14 @@ Repair stale hosts, Caddy configuration, or setup state:
|
|
|
111
113
|
localghost repair
|
|
112
114
|
```
|
|
113
115
|
|
|
116
|
+
If an existing project port is occupied or was allocated incorrectly, ask Localghost to choose and remember the next available port:
|
|
117
|
+
|
|
118
|
+
```sh
|
|
119
|
+
localghost repair --reallocate-port
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
This does not edit `.localghost`; it records a stable runtime allocation in `~/.localghost` and regenerates the managed hosts and Caddy state from that allocation. To remove expired or dead leases as part of the repair, add `--prune-registry`.
|
|
123
|
+
|
|
114
124
|
Run only the local proxy:
|
|
115
125
|
|
|
116
126
|
```sh
|
|
@@ -584,9 +594,9 @@ The app bundle is written to `dist/LocalghostWidget.app`.
|
|
|
584
594
|
```sh
|
|
585
595
|
localghost [--cwd path] [--dry-run]
|
|
586
596
|
localghost init [--write-scripts] [--config file] [--host host] [--port port]
|
|
587
|
-
localghost doctor
|
|
597
|
+
localghost doctor [--cwd path] [--config file] [--config-pattern regex] [--json]
|
|
588
598
|
localghost setup [--project name] [--config file] [--config-pattern regex] [--https|--ssl]
|
|
589
|
-
localghost repair [--project name] [--config file] [--config-pattern regex] [--https|--ssl] [--trust]
|
|
599
|
+
localghost repair [--project name] [--config file] [--config-pattern regex] [--https|--ssl] [--trust] [--reallocate-port] [--prune-registry]
|
|
590
600
|
localghost trust [--project name] [--config file] [--config-pattern regex] [--https|--ssl]
|
|
591
601
|
localghost reset [--project name]
|
|
592
602
|
localghost teardown [--project name] [--remove-caddyfile]
|
package/dist/cli.js
CHANGED
|
@@ -86,6 +86,7 @@ function registerLocalghostRun(input2, path = getLocalghostActivityPath()) {
|
|
|
86
86
|
...input2.configPath ? { configPath: input2.configPath } : {},
|
|
87
87
|
...input2.caddyfilePath ? { caddyfilePath: input2.caddyfilePath } : {},
|
|
88
88
|
...input2.caddyPid ? { caddyPid: input2.caddyPid } : {},
|
|
89
|
+
...input2.caddyPgid ? { caddyPgid: input2.caddyPgid } : {},
|
|
89
90
|
...input2.childPid ? { childPid: input2.childPid } : {},
|
|
90
91
|
...input2.childCommand ? { childCommand: input2.childCommand } : {},
|
|
91
92
|
...typeof input2.https === "boolean" ? { https: input2.https } : {},
|
|
@@ -336,7 +337,8 @@ async function validateCaddyfile(path) {
|
|
|
336
337
|
function startCaddy(path) {
|
|
337
338
|
return execa("caddy", ["run", "--config", path], {
|
|
338
339
|
cwd: dirname3(path),
|
|
339
|
-
stdio: caddyStdio()
|
|
340
|
+
stdio: caddyStdio(),
|
|
341
|
+
detached: process.platform !== "win32"
|
|
340
342
|
});
|
|
341
343
|
}
|
|
342
344
|
function stopCaddyProcesses(pids, killProcess = (pid, signal) => process.kill(pid, signal)) {
|
|
@@ -628,6 +630,18 @@ function createLocalghostRegistry(options = {}) {
|
|
|
628
630
|
lockPath,
|
|
629
631
|
ownerToken,
|
|
630
632
|
read: readRegistry,
|
|
633
|
+
async prune() {
|
|
634
|
+
const releaseLock = await lock();
|
|
635
|
+
try {
|
|
636
|
+
const registry = await readRegistry();
|
|
637
|
+
const before = registry.leases.length;
|
|
638
|
+
pruneRegistry(registry, now(), isRunning);
|
|
639
|
+
await writeRegistry(registry);
|
|
640
|
+
return { removedLeases: before - registry.leases.length };
|
|
641
|
+
} finally {
|
|
642
|
+
await releaseLock();
|
|
643
|
+
}
|
|
644
|
+
},
|
|
631
645
|
async acquirePort(acquireOptions) {
|
|
632
646
|
const projectCwd = canonicalizeLocalghostProjectCwd(acquireOptions.projectCwd ?? cwd);
|
|
633
647
|
if (!acquireOptions.instanceKey) throw new Error("instanceKey is required");
|
|
@@ -1207,11 +1221,52 @@ async function checkCaddy() {
|
|
|
1207
1221
|
};
|
|
1208
1222
|
}
|
|
1209
1223
|
}
|
|
1210
|
-
async function runDoctor() {
|
|
1224
|
+
async function runDoctor(options = {}) {
|
|
1211
1225
|
const caddy = await checkCaddy();
|
|
1226
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1227
|
+
const registry = createLocalghostRegistry({ cwd });
|
|
1228
|
+
const data = await registry.read();
|
|
1229
|
+
const now = Date.now();
|
|
1230
|
+
const staleLeases = data.leases.filter((lease) => lease.expiresAt <= now || !isProcessRunning(lease.pid)).map(({ projectCwd, instanceKey, port, pid }) => ({ projectCwd, instanceKey, port, pid }));
|
|
1231
|
+
const allocationsByPort = /* @__PURE__ */ new Map();
|
|
1232
|
+
for (const allocation of data.allocations) {
|
|
1233
|
+
const projects = allocationsByPort.get(allocation.port) ?? [];
|
|
1234
|
+
projects.push(`${allocation.projectCwd}#${allocation.instanceKey}`);
|
|
1235
|
+
allocationsByPort.set(allocation.port, projects);
|
|
1236
|
+
}
|
|
1237
|
+
const duplicateAllocations = [...allocationsByPort.entries()].filter(([, projects]) => projects.length > 1).map(([port, projects]) => ({ port, projects }));
|
|
1238
|
+
let configured;
|
|
1239
|
+
let available;
|
|
1240
|
+
try {
|
|
1241
|
+
const context = await resolveLocalghostContext({
|
|
1242
|
+
cwd,
|
|
1243
|
+
...options.configFiles ? { configFiles: options.configFiles } : {},
|
|
1244
|
+
...options.configPattern ? { configPattern: options.configPattern } : {},
|
|
1245
|
+
dynamicPort: false
|
|
1246
|
+
});
|
|
1247
|
+
configured = context.requestedPort;
|
|
1248
|
+
available = await isPortAvailable(configured);
|
|
1249
|
+
} catch {
|
|
1250
|
+
}
|
|
1251
|
+
const currentProjectCwd = canonicalizeLocalghostProjectCwd(cwd);
|
|
1252
|
+
const currentAllocation = data.allocations.find((allocation) => allocation.projectCwd === currentProjectCwd);
|
|
1212
1253
|
return {
|
|
1213
|
-
ok: caddy.found,
|
|
1214
|
-
caddy
|
|
1254
|
+
ok: caddy.found && available !== false && staleLeases.length === 0 && duplicateAllocations.length === 0,
|
|
1255
|
+
caddy,
|
|
1256
|
+
ports: {
|
|
1257
|
+
...configured !== void 0 ? { configured } : {},
|
|
1258
|
+
...available !== void 0 ? { available } : {},
|
|
1259
|
+
registryPath: registry.registryPath,
|
|
1260
|
+
staleLeases,
|
|
1261
|
+
duplicateAllocations,
|
|
1262
|
+
...currentAllocation ? {
|
|
1263
|
+
currentAllocation: {
|
|
1264
|
+
projectCwd: currentAllocation.projectCwd,
|
|
1265
|
+
instanceKey: currentAllocation.instanceKey,
|
|
1266
|
+
port: currentAllocation.port
|
|
1267
|
+
}
|
|
1268
|
+
} : {}
|
|
1269
|
+
}
|
|
1215
1270
|
};
|
|
1216
1271
|
}
|
|
1217
1272
|
|
|
@@ -1868,7 +1923,8 @@ Use \`localghost dev\` only when the Caddy proxy should run without starting the
|
|
|
1868
1923
|
- \`localghost repair\`: repair managed hosts and Caddy setup.
|
|
1869
1924
|
- \`localghost ps --json\`: inspect Localghost-managed repositories, instances, and ports.
|
|
1870
1925
|
- \`localghost routes\`: inspect hostname-to-port routing.
|
|
1871
|
-
- \`localghost doctor\`: check machine prerequisites.
|
|
1926
|
+
- \`localghost doctor\`: check machine prerequisites, ports, and registry state.
|
|
1927
|
+
- \`localghost repair --reallocate-port\`: move an occupied project port to a stable available port.
|
|
1872
1928
|
|
|
1873
1929
|
## Configuration
|
|
1874
1930
|
|
|
@@ -2012,7 +2068,7 @@ import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as rea
|
|
|
2012
2068
|
import { homedir as homedir3 } from "os";
|
|
2013
2069
|
import { dirname as dirname4, join as join10 } from "path";
|
|
2014
2070
|
var LOCALGHOST_PACKAGE_NAME = "@hamedb89/localghost";
|
|
2015
|
-
var LOCALGHOST_VERSION = "0.
|
|
2071
|
+
var LOCALGHOST_VERSION = "0.2.0";
|
|
2016
2072
|
var UPDATE_CHECK_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2017
2073
|
var UPDATE_CHECK_NOTIFY_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2018
2074
|
var UPDATE_CHECK_TIMEOUT_MS = 900;
|
|
@@ -2176,6 +2232,27 @@ ${message}`);
|
|
|
2176
2232
|
|
|
2177
2233
|
// src/cli.ts
|
|
2178
2234
|
import { execa as execa4 } from "execa";
|
|
2235
|
+
|
|
2236
|
+
// src/process.ts
|
|
2237
|
+
function signalManagedProcessPid(pid, signal, killProcess = (value, processSignal) => process.kill(value, processSignal)) {
|
|
2238
|
+
if (typeof pid !== "number" || !Number.isInteger(pid) || pid < 1) return false;
|
|
2239
|
+
try {
|
|
2240
|
+
killProcess(process.platform === "win32" ? pid : -pid, signal);
|
|
2241
|
+
return true;
|
|
2242
|
+
} catch (error) {
|
|
2243
|
+
if (error instanceof Error && "code" in error && error.code === "ESRCH") return false;
|
|
2244
|
+
throw error;
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
function signalManagedProcess(child, signal) {
|
|
2248
|
+
if (process.platform === "win32") {
|
|
2249
|
+
if (!child.killed) child.kill(signal);
|
|
2250
|
+
return true;
|
|
2251
|
+
}
|
|
2252
|
+
return signalManagedProcessPid(child.pid, signal);
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
// src/cli.ts
|
|
2179
2256
|
function warnAboutLocalMdns(entries) {
|
|
2180
2257
|
const localHosts = findLocalMdnsHosts(entries);
|
|
2181
2258
|
if (localHosts.length > 0) {
|
|
@@ -2255,10 +2332,18 @@ async function assertCaddyReady() {
|
|
|
2255
2332
|
}
|
|
2256
2333
|
function cleanManagedCaddyProcesses() {
|
|
2257
2334
|
const runs = listLocalghostRuns();
|
|
2258
|
-
const
|
|
2259
|
-
const
|
|
2335
|
+
const legacyPids = runs.flatMap((run) => run.caddyPid && !run.caddyPgid ? [run.caddyPid] : []);
|
|
2336
|
+
const managedPgid = runs.flatMap((run) => run.caddyPgid ? [run.caddyPgid] : []);
|
|
2337
|
+
const legacyResult = stopCaddyProcesses(legacyPids);
|
|
2338
|
+
const managedResult = stopCaddyProcesses(managedPgid, signalManagedProcessPid);
|
|
2339
|
+
const result = {
|
|
2340
|
+
stopped: [...legacyResult.stopped, ...managedResult.stopped],
|
|
2341
|
+
alreadyExited: [...legacyResult.alreadyExited, ...managedResult.alreadyExited],
|
|
2342
|
+
failed: [...legacyResult.failed, ...managedResult.failed]
|
|
2343
|
+
};
|
|
2260
2344
|
for (const run of runs) {
|
|
2261
|
-
|
|
2345
|
+
const caddyIdentity = run.caddyPgid ?? run.caddyPid;
|
|
2346
|
+
if (caddyIdentity && (result.stopped.includes(caddyIdentity) || result.alreadyExited.includes(caddyIdentity))) {
|
|
2262
2347
|
unregisterLocalghostRun(run.id);
|
|
2263
2348
|
}
|
|
2264
2349
|
}
|
|
@@ -2468,6 +2553,16 @@ async function waitForServicePorts(entries, timeoutMs = 1e4) {
|
|
|
2468
2553
|
}
|
|
2469
2554
|
return false;
|
|
2470
2555
|
}
|
|
2556
|
+
async function waitForPortsToBeAvailable(entries, timeoutMs = 1e4) {
|
|
2557
|
+
const deadline = Date.now() + timeoutMs;
|
|
2558
|
+
const ports = [...new Set(entries.map((entry) => entry.port))];
|
|
2559
|
+
while (Date.now() < deadline) {
|
|
2560
|
+
const availability = await Promise.all(ports.map((port) => isPortAvailable(port)));
|
|
2561
|
+
if (availability.every(Boolean)) return true;
|
|
2562
|
+
await wait2(50);
|
|
2563
|
+
}
|
|
2564
|
+
return false;
|
|
2565
|
+
}
|
|
2471
2566
|
async function runDetectedServices(options) {
|
|
2472
2567
|
assertLocalDevelopment("run");
|
|
2473
2568
|
await assertCaddyReady();
|
|
@@ -2508,6 +2603,7 @@ async function runDetectedServices(options) {
|
|
|
2508
2603
|
const children = runtimeServices.map((service) => execa4(service.command[0], service.command.slice(1), {
|
|
2509
2604
|
cwd: service.cwd,
|
|
2510
2605
|
stdio: "inherit",
|
|
2606
|
+
detached: process.platform !== "win32",
|
|
2511
2607
|
env: {
|
|
2512
2608
|
...process.env,
|
|
2513
2609
|
LOCALGHOST_PORT: String(service.port),
|
|
@@ -2524,6 +2620,7 @@ async function runDetectedServices(options) {
|
|
|
2524
2620
|
configPath: options.configPath,
|
|
2525
2621
|
caddyfilePath: caddyfile,
|
|
2526
2622
|
...caddyPid ? { caddyPid } : {},
|
|
2623
|
+
...caddyPid ? { caddyPgid: caddyPid } : {},
|
|
2527
2624
|
childCommand: ["services", ...runtimeServices.map((service) => service.name)],
|
|
2528
2625
|
https: options.https,
|
|
2529
2626
|
dynamicPort: options.dynamicPort,
|
|
@@ -2543,10 +2640,13 @@ async function runDetectedServices(options) {
|
|
|
2543
2640
|
await processExit;
|
|
2544
2641
|
} finally {
|
|
2545
2642
|
for (const child of children) {
|
|
2546
|
-
|
|
2643
|
+
signalManagedProcess(child, "SIGINT");
|
|
2547
2644
|
}
|
|
2548
|
-
|
|
2645
|
+
signalManagedProcess(caddy, "SIGINT");
|
|
2549
2646
|
await Promise.allSettled([caddyExit, ...children]);
|
|
2647
|
+
if (!await waitForPortsToBeAvailable(entries)) {
|
|
2648
|
+
console.warn("Localghost: timed out waiting for service ports to be released.");
|
|
2649
|
+
}
|
|
2550
2650
|
cleanupRun();
|
|
2551
2651
|
}
|
|
2552
2652
|
} finally {
|
|
@@ -2674,8 +2774,17 @@ program.command("init").description("Create a .localghost config for this projec
|
|
|
2674
2774
|
program.command("guide").description("Explain the recommended Localghost workflow to humans or agents").option("--agent", "Print the agent-oriented workflow guide").option("--json", "Print the guide as JSON").action((options) => {
|
|
2675
2775
|
console.log(formatLocalghostAgentGuide(options.json ? "json" : "text"));
|
|
2676
2776
|
});
|
|
2677
|
-
program.command("doctor").description("Check machine prerequisites").action(async () => {
|
|
2678
|
-
const result = await runDoctor(
|
|
2777
|
+
program.command("doctor").description("Check machine prerequisites, ports, and Localghost registry state").option("--cwd <path>", "Project directory", process.cwd()).option("--config <file>", "Config file to inspect. Can be repeated.", collect, []).option("--config-pattern <regex>", "Regex for config filenames in the project root").option("--json", "Print raw JSON").action(async (options) => {
|
|
2778
|
+
const result = await runDoctor({
|
|
2779
|
+
cwd: options.cwd,
|
|
2780
|
+
...options.config && options.config.length > 0 ? { configFiles: options.config } : {},
|
|
2781
|
+
...options.configPattern ? { configPattern: options.configPattern } : {}
|
|
2782
|
+
});
|
|
2783
|
+
if (options.json) {
|
|
2784
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2785
|
+
if (!result.ok) process.exitCode = 1;
|
|
2786
|
+
return;
|
|
2787
|
+
}
|
|
2679
2788
|
if (result.caddy.found) {
|
|
2680
2789
|
console.log(`Caddy: ${result.caddy.version ?? "found"}`);
|
|
2681
2790
|
} else {
|
|
@@ -2683,6 +2792,17 @@ program.command("doctor").description("Check machine prerequisites").action(asyn
|
|
|
2683
2792
|
console.log(`Run: ${result.caddy.installHint}`);
|
|
2684
2793
|
console.log("Localghost will not install it for you. No surprise spells.");
|
|
2685
2794
|
}
|
|
2795
|
+
if (result.ports.configured === void 0) {
|
|
2796
|
+
console.log("Port: could not resolve project configuration.");
|
|
2797
|
+
} else {
|
|
2798
|
+
console.log(`Port ${result.ports.configured}: ${result.ports.available ? "available" : "occupied"}`);
|
|
2799
|
+
}
|
|
2800
|
+
if (result.ports.staleLeases.length > 0) {
|
|
2801
|
+
console.log(`Registry: ${result.ports.staleLeases.length} stale lease(s); run localghost repair --prune-registry.`);
|
|
2802
|
+
}
|
|
2803
|
+
for (const duplicate of result.ports.duplicateAllocations) {
|
|
2804
|
+
console.log(`Registry: port ${duplicate.port} is allocated to ${duplicate.projects.join(", ")}.`);
|
|
2805
|
+
}
|
|
2686
2806
|
if (!result.ok) {
|
|
2687
2807
|
process.exitCode = 1;
|
|
2688
2808
|
}
|
|
@@ -2789,11 +2909,20 @@ program.command("trust").description("Trust Caddy's local HTTPS CA for this proj
|
|
|
2789
2909
|
await validateCaddyfile(caddyfile);
|
|
2790
2910
|
await runTrust(options.cwd, caddyfile);
|
|
2791
2911
|
});
|
|
2792
|
-
program.command("repair").description("Reconcile stale
|
|
2912
|
+
program.command("repair").description("Reconcile stale setup, ports, registry state, and optional HTTPS trust").option("--project <name>", "Managed /etc/hosts block name").option("--cwd <path>", "Project directory", process.cwd()).option("--config <file>", "Config file to look for. Can be repeated.", collect, []).option("--config-pattern <regex>", "Regex for config filenames in the project root").option("--https", "Repair an HTTPS Caddy setup").option("--ssl", "Alias for --https").option("--trust", "Re-run Caddy's local HTTPS trust step").option("--reallocate-port", "Persist a stable replacement for an occupied port").option("--prune-registry", "Remove expired or dead registry leases").action(async (options) => {
|
|
2793
2913
|
assertLocalDevelopment("repair");
|
|
2794
2914
|
printLocalghostBanner();
|
|
2795
2915
|
await assertCaddyReady();
|
|
2796
|
-
const
|
|
2916
|
+
const registry = createLocalghostRegistry({ cwd: options.cwd });
|
|
2917
|
+
if (options.pruneRegistry) {
|
|
2918
|
+
const result = await registry.prune();
|
|
2919
|
+
console.log(`Pruned ${result.removedLeases} stale registry lease${result.removedLeases === 1 ? "" : "s"}.`);
|
|
2920
|
+
}
|
|
2921
|
+
const context = await resolveLocalghostContext({
|
|
2922
|
+
...contextOptionsFromCli(options),
|
|
2923
|
+
dynamicPort: options.reallocatePort ? true : false,
|
|
2924
|
+
...options.reallocatePort ? { reservePort: true, instanceKey: "run" } : {}
|
|
2925
|
+
});
|
|
2797
2926
|
const readiness = getSetupReadiness({
|
|
2798
2927
|
...options,
|
|
2799
2928
|
https: context.https,
|
|
@@ -2806,14 +2935,21 @@ program.command("repair").description("Reconcile stale hosts, Caddyfile, setup s
|
|
|
2806
2935
|
}
|
|
2807
2936
|
warnAboutLocalMdns(context.entries);
|
|
2808
2937
|
logDomainRoutes(context.entries, { https: context.https, ghostTunnel: context.ghostTunnel });
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2938
|
+
try {
|
|
2939
|
+
await runSetupFromReadiness(options.cwd, context.https, readiness);
|
|
2940
|
+
if (options.trust) {
|
|
2941
|
+
await runTrust(options.cwd, readiness.caddyfilePath);
|
|
2942
|
+
}
|
|
2943
|
+
if (context.port !== context.requestedPort) {
|
|
2944
|
+
console.log(`Reallocated port ${context.requestedPort} -> ${context.port}.`);
|
|
2945
|
+
}
|
|
2946
|
+
console.log(`Repaired hosts: ${getSystemHostsPath()}`);
|
|
2947
|
+
console.log(`Repaired Caddyfile: ${readiness.caddyfilePath}`);
|
|
2948
|
+
console.log(`Repaired state: ${readiness.statePath}`);
|
|
2949
|
+
console.log("Repair complete.");
|
|
2950
|
+
} finally {
|
|
2951
|
+
await context.releasePort?.();
|
|
2952
|
+
}
|
|
2817
2953
|
});
|
|
2818
2954
|
program.command("reset").description("Remove Localghost setup state without deleting .localghost").option("--project <name>", "Managed /etc/hosts block name").option("--cwd <path>", "Project directory", process.cwd()).action(async (options) => {
|
|
2819
2955
|
assertLocalDevelopment("reset");
|
|
@@ -2969,7 +3105,7 @@ program.command("dev").description("Run the Localghost Caddy proxy, repairing st
|
|
|
2969
3105
|
...typeof options.trust === "boolean" ? { trust: options.trust } : {}
|
|
2970
3106
|
});
|
|
2971
3107
|
} catch (error) {
|
|
2972
|
-
|
|
3108
|
+
signalManagedProcess(caddy, "SIGINT");
|
|
2973
3109
|
throw error;
|
|
2974
3110
|
}
|
|
2975
3111
|
const caddyPid = maybePid(caddy.pid);
|
|
@@ -2980,6 +3116,7 @@ program.command("dev").description("Run the Localghost Caddy proxy, repairing st
|
|
|
2980
3116
|
configPath: readiness.configPath,
|
|
2981
3117
|
caddyfilePath: caddyfile,
|
|
2982
3118
|
...caddyPid ? { caddyPid } : {},
|
|
3119
|
+
...caddyPid ? { caddyPgid: caddyPid } : {},
|
|
2983
3120
|
https,
|
|
2984
3121
|
entries: readiness.entries
|
|
2985
3122
|
});
|
|
@@ -3049,7 +3186,7 @@ program.command("run").description("Run Caddy and a dev command from the same Lo
|
|
|
3049
3186
|
...typeof options.trust === "boolean" ? { trust: options.trust } : {}
|
|
3050
3187
|
});
|
|
3051
3188
|
} catch (error) {
|
|
3052
|
-
|
|
3189
|
+
signalManagedProcess(caddy, "SIGINT");
|
|
3053
3190
|
throw error;
|
|
3054
3191
|
}
|
|
3055
3192
|
const [binary, ...args] = command;
|
|
@@ -3059,6 +3196,7 @@ program.command("run").description("Run Caddy and a dev command from the same Lo
|
|
|
3059
3196
|
const child = execa4(binary, args, {
|
|
3060
3197
|
cwd: options.cwd,
|
|
3061
3198
|
stdio: "inherit",
|
|
3199
|
+
detached: process.platform !== "win32",
|
|
3062
3200
|
env: {
|
|
3063
3201
|
...process.env,
|
|
3064
3202
|
LOCALGHOST_PORT: String(context.port),
|
|
@@ -3075,6 +3213,7 @@ program.command("run").description("Run Caddy and a dev command from the same Lo
|
|
|
3075
3213
|
configPath: context.configPath,
|
|
3076
3214
|
caddyfilePath: caddyfile,
|
|
3077
3215
|
...caddyPid ? { caddyPid } : {},
|
|
3216
|
+
...caddyPid ? { caddyPgid: caddyPid } : {},
|
|
3078
3217
|
...childPid ? { childPid } : {},
|
|
3079
3218
|
childCommand: command,
|
|
3080
3219
|
https,
|
|
@@ -3085,10 +3224,10 @@ program.command("run").description("Run Caddy and a dev command from the same Lo
|
|
|
3085
3224
|
});
|
|
3086
3225
|
const cleanupRun = registerCleanup(runRecord.id);
|
|
3087
3226
|
const stopCaddy = () => {
|
|
3088
|
-
|
|
3227
|
+
signalManagedProcess(caddy, "SIGINT");
|
|
3089
3228
|
};
|
|
3090
3229
|
const stopChild = () => {
|
|
3091
|
-
|
|
3230
|
+
signalManagedProcess(child, "SIGINT");
|
|
3092
3231
|
};
|
|
3093
3232
|
try {
|
|
3094
3233
|
await Promise.race([child, caddyExit]);
|
|
@@ -3096,6 +3235,9 @@ program.command("run").description("Run Caddy and a dev command from the same Lo
|
|
|
3096
3235
|
stopChild();
|
|
3097
3236
|
stopCaddy();
|
|
3098
3237
|
await Promise.allSettled([child, caddyExit]);
|
|
3238
|
+
if (!await waitForPortsToBeAvailable(context.entries)) {
|
|
3239
|
+
console.warn("Localghost: timed out waiting for service ports to be released.");
|
|
3240
|
+
}
|
|
3099
3241
|
cleanupRun();
|
|
3100
3242
|
await context.releasePort?.();
|
|
3101
3243
|
}
|