@hamedb89/localghost 0.1.13 → 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 +30 -4
- package/dist/cli.js +657 -146
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +117 -3
- package/dist/index.js +399 -40
- package/dist/index.js.map +1 -1
- package/dist/vite.d.ts +1 -0
- package/dist/vite.js +231 -19
- package/dist/vite.js.map +1 -1
- package/docs/flows.md +3 -1
- package/docs/ghost-tunnel.md +4 -1
- package/docs/localghost.1.md +35 -6
- package/package.json +1 -1
package/dist/vite.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ type LocalGhostPluginOptions = {
|
|
|
17
17
|
localghostConfig?: string | false;
|
|
18
18
|
wwwAlias?: boolean;
|
|
19
19
|
ghostTunnel?: GhostTunnelOptions;
|
|
20
|
+
registryOwnerToken?: string;
|
|
20
21
|
verbose?: boolean;
|
|
21
22
|
};
|
|
22
23
|
declare function localGhostPlugin(options?: LocalGhostPluginOptions): Plugin;
|
package/dist/vite.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/vite.ts
|
|
2
2
|
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
3
|
-
import { normalize, resolve as
|
|
3
|
+
import { normalize as normalize2, resolve as resolve3 } from "path";
|
|
4
4
|
import { spawn } from "child_process";
|
|
5
5
|
import { emitKeypressEvents } from "readline";
|
|
6
6
|
|
|
@@ -80,6 +80,7 @@ function registerLocalghostRun(input2, path = getLocalghostActivityPath()) {
|
|
|
80
80
|
...input2.configPath ? { configPath: input2.configPath } : {},
|
|
81
81
|
...input2.caddyfilePath ? { caddyfilePath: input2.caddyfilePath } : {},
|
|
82
82
|
...input2.caddyPid ? { caddyPid: input2.caddyPid } : {},
|
|
83
|
+
...input2.caddyPgid ? { caddyPgid: input2.caddyPgid } : {},
|
|
83
84
|
...input2.childPid ? { childPid: input2.childPid } : {},
|
|
84
85
|
...input2.childCommand ? { childCommand: input2.childCommand } : {},
|
|
85
86
|
...typeof input2.https === "boolean" ? { https: input2.https } : {},
|
|
@@ -229,25 +230,30 @@ function getProjectName(cwd = process.cwd()) {
|
|
|
229
230
|
}
|
|
230
231
|
}
|
|
231
232
|
function sanitizeProjectName(value) {
|
|
232
|
-
const
|
|
233
|
+
const sanitized = value.replace(/[^\w.-]+/g, "-");
|
|
234
|
+
let start = 0;
|
|
235
|
+
let end = sanitized.length;
|
|
236
|
+
while (start < end && sanitized.charCodeAt(start) === 45) start += 1;
|
|
237
|
+
while (end > start && sanitized.charCodeAt(end - 1) === 45) end -= 1;
|
|
238
|
+
const projectName = sanitized.slice(start, end);
|
|
233
239
|
return projectName || "app";
|
|
234
240
|
}
|
|
235
241
|
|
|
236
242
|
// src/context.ts
|
|
237
243
|
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
238
|
-
import { join as
|
|
244
|
+
import { join as join4 } from "path";
|
|
239
245
|
import { pathToFileURL } from "url";
|
|
240
246
|
|
|
241
247
|
// src/port.ts
|
|
242
248
|
import { createServer } from "net";
|
|
243
249
|
async function isPortAvailable(port, host = "127.0.0.1") {
|
|
244
|
-
return new Promise((
|
|
250
|
+
return new Promise((resolve4) => {
|
|
245
251
|
const server = createServer();
|
|
246
252
|
server.once("error", () => {
|
|
247
|
-
|
|
253
|
+
resolve4(false);
|
|
248
254
|
});
|
|
249
255
|
server.once("listening", () => {
|
|
250
|
-
server.close(() =>
|
|
256
|
+
server.close(() => resolve4(true));
|
|
251
257
|
});
|
|
252
258
|
server.listen(port, host);
|
|
253
259
|
});
|
|
@@ -264,6 +270,188 @@ async function findAvailablePort(startPort, options = {}) {
|
|
|
264
270
|
throw new Error(`No available port found from ${startPort} to ${startPort + maxAttempts - 1}.`);
|
|
265
271
|
}
|
|
266
272
|
|
|
273
|
+
// src/registry.ts
|
|
274
|
+
import { randomUUID } from "crypto";
|
|
275
|
+
import { mkdir, open, readFile, rename, rm, stat, unlink, writeFile } from "fs/promises";
|
|
276
|
+
import { homedir as homedir2 } from "os";
|
|
277
|
+
import { join as join3, normalize, resolve as resolve2 } from "path";
|
|
278
|
+
var LOCALGHOST_REGISTRY_FILE = "registry.json";
|
|
279
|
+
var LOCALGHOST_REGISTRY_LOCK_FILE = "registry.lock";
|
|
280
|
+
function defaultProcessRunning(pid) {
|
|
281
|
+
if (pid <= 0) return false;
|
|
282
|
+
try {
|
|
283
|
+
process.kill(pid, 0);
|
|
284
|
+
return true;
|
|
285
|
+
} catch (error) {
|
|
286
|
+
return error.code === "EPERM";
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function getLocalghostRegistryRoot(env = process.env) {
|
|
290
|
+
return resolve2(env.LOCALGHOST_HOME || join3(homedir2(), ".localghost"));
|
|
291
|
+
}
|
|
292
|
+
function canonicalizeLocalghostProjectCwd(cwd = process.cwd()) {
|
|
293
|
+
return normalize(resolve2(cwd));
|
|
294
|
+
}
|
|
295
|
+
function emptyRegistry() {
|
|
296
|
+
return { version: 1, allocations: [], leases: [] };
|
|
297
|
+
}
|
|
298
|
+
function leaseKey(projectCwd, instanceKey) {
|
|
299
|
+
return `${projectCwd}\0${instanceKey}`;
|
|
300
|
+
}
|
|
301
|
+
function validRegistry(value) {
|
|
302
|
+
if (!value || typeof value !== "object") return false;
|
|
303
|
+
const candidate = value;
|
|
304
|
+
return candidate.version === 1 && Array.isArray(candidate.allocations) && Array.isArray(candidate.leases);
|
|
305
|
+
}
|
|
306
|
+
function pruneRegistry(registry, now, isRunning) {
|
|
307
|
+
registry.leases = registry.leases.filter((lease) => lease.expiresAt > now && isRunning(lease.pid));
|
|
308
|
+
}
|
|
309
|
+
async function readJson(path) {
|
|
310
|
+
try {
|
|
311
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
312
|
+
} catch (error) {
|
|
313
|
+
if (error.code === "ENOENT") return void 0;
|
|
314
|
+
return void 0;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function createLocalghostRegistry(options = {}) {
|
|
318
|
+
const root = resolve2(options.stateRoot ?? getLocalghostRegistryRoot());
|
|
319
|
+
const registryPath = join3(root, LOCALGHOST_REGISTRY_FILE);
|
|
320
|
+
const lockPath = join3(root, LOCALGHOST_REGISTRY_LOCK_FILE);
|
|
321
|
+
const cwd = canonicalizeLocalghostProjectCwd(options.cwd);
|
|
322
|
+
const now = options.now ?? Date.now;
|
|
323
|
+
const pid = options.pid ?? process.pid;
|
|
324
|
+
const ownerToken = options.ownerToken ?? randomUUID();
|
|
325
|
+
const isRunning = options.isProcessRunning ?? defaultProcessRunning;
|
|
326
|
+
const availabilityCheck = options.availabilityCheck ?? isPortAvailable;
|
|
327
|
+
const lockTimeoutMs = options.lockTimeoutMs ?? 5e3;
|
|
328
|
+
const lockRetryMs = options.lockRetryMs ?? 25;
|
|
329
|
+
const lockStaleMs = options.lockStaleMs ?? 3e4;
|
|
330
|
+
async function readRegistry() {
|
|
331
|
+
const value = await readJson(registryPath);
|
|
332
|
+
return validRegistry(value) ? value : emptyRegistry();
|
|
333
|
+
}
|
|
334
|
+
async function writeRegistry(registry) {
|
|
335
|
+
await mkdir(root, { recursive: true });
|
|
336
|
+
const temporaryPath = join3(root, `.registry.${process.pid}.${randomUUID()}.tmp`);
|
|
337
|
+
await writeFile(temporaryPath, `${JSON.stringify(registry, null, 2)}
|
|
338
|
+
`, { mode: 384 });
|
|
339
|
+
await rename(temporaryPath, registryPath);
|
|
340
|
+
}
|
|
341
|
+
async function lock() {
|
|
342
|
+
await mkdir(root, { recursive: true });
|
|
343
|
+
const deadline = now() + lockTimeoutMs;
|
|
344
|
+
const token = randomUUID();
|
|
345
|
+
while (true) {
|
|
346
|
+
try {
|
|
347
|
+
const handle = await open(lockPath, "wx", 384);
|
|
348
|
+
await handle.writeFile(`${JSON.stringify({ pid, createdAt: now(), token })}
|
|
349
|
+
`);
|
|
350
|
+
await handle.close();
|
|
351
|
+
return async () => {
|
|
352
|
+
const current = await readJson(lockPath);
|
|
353
|
+
if (current?.token === token) await unlink(lockPath).catch(() => void 0);
|
|
354
|
+
};
|
|
355
|
+
} catch (error) {
|
|
356
|
+
if (error.code !== "EEXIST") throw error;
|
|
357
|
+
const lockInfo = await readJson(lockPath);
|
|
358
|
+
let stale = false;
|
|
359
|
+
if (lockInfo && typeof lockInfo.pid === "number") {
|
|
360
|
+
stale = !isRunning(lockInfo.pid) && now() - lockInfo.createdAt >= 0;
|
|
361
|
+
} else {
|
|
362
|
+
try {
|
|
363
|
+
stale = now() - (await stat(lockPath)).mtimeMs > lockStaleMs;
|
|
364
|
+
} catch {
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (stale) {
|
|
369
|
+
await rm(lockPath, { force: true }).catch(() => void 0);
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
if (now() >= deadline) throw new Error(`Timed out waiting for Localghost registry lock: ${lockPath}`);
|
|
373
|
+
await new Promise((resolveDelay) => setTimeout(resolveDelay, lockRetryMs));
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
async function withLock(operation) {
|
|
378
|
+
const releaseLock = await lock();
|
|
379
|
+
try {
|
|
380
|
+
const registry = await readRegistry();
|
|
381
|
+
pruneRegistry(registry, now(), isRunning);
|
|
382
|
+
return await operation(registry);
|
|
383
|
+
} finally {
|
|
384
|
+
await releaseLock();
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
root,
|
|
389
|
+
registryPath,
|
|
390
|
+
lockPath,
|
|
391
|
+
ownerToken,
|
|
392
|
+
read: readRegistry,
|
|
393
|
+
async prune() {
|
|
394
|
+
const releaseLock = await lock();
|
|
395
|
+
try {
|
|
396
|
+
const registry = await readRegistry();
|
|
397
|
+
const before = registry.leases.length;
|
|
398
|
+
pruneRegistry(registry, now(), isRunning);
|
|
399
|
+
await writeRegistry(registry);
|
|
400
|
+
return { removedLeases: before - registry.leases.length };
|
|
401
|
+
} finally {
|
|
402
|
+
await releaseLock();
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
async acquirePort(acquireOptions) {
|
|
406
|
+
const projectCwd = canonicalizeLocalghostProjectCwd(acquireOptions.projectCwd ?? cwd);
|
|
407
|
+
if (!acquireOptions.instanceKey) throw new Error("instanceKey is required");
|
|
408
|
+
return withLock(async (registry) => {
|
|
409
|
+
const key = leaseKey(projectCwd, acquireOptions.instanceKey);
|
|
410
|
+
const existing = registry.allocations.find((entry2) => leaseKey(entry2.projectCwd, entry2.instanceKey) === key);
|
|
411
|
+
const reserved = new Set(acquireOptions.reservedPorts ?? []);
|
|
412
|
+
const activePorts = new Set(registry.leases.map((lease2) => lease2.port));
|
|
413
|
+
const port = existing?.port;
|
|
414
|
+
const ownsActiveLease = registry.leases.some((lease2) => lease2.port === port && leaseKey(lease2.projectCwd, lease2.instanceKey) === key && lease2.ownerToken === ownerToken);
|
|
415
|
+
const reusable = port !== void 0 && !reserved.has(port) && (!activePorts.has(port) || ownsActiveLease) && (ownsActiveLease || await availabilityCheck(port, acquireOptions.host));
|
|
416
|
+
let selectedPort = reusable ? port : void 0;
|
|
417
|
+
if (selectedPort === void 0) {
|
|
418
|
+
const startPort = acquireOptions.startPort ?? 3e3;
|
|
419
|
+
const maxAttempts = acquireOptions.maxAttempts ?? 50;
|
|
420
|
+
for (let offset = 0; offset < maxAttempts; offset += 1) {
|
|
421
|
+
const candidate = startPort + offset;
|
|
422
|
+
if (reserved.has(candidate) || activePorts.has(candidate)) continue;
|
|
423
|
+
if (await availabilityCheck(candidate, acquireOptions.host)) {
|
|
424
|
+
selectedPort = candidate;
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (selectedPort === void 0) throw new Error(`No available registry port found from ${startPort} to ${startPort + maxAttempts - 1}.`);
|
|
429
|
+
}
|
|
430
|
+
const timestamp = now();
|
|
431
|
+
const entry = existing ?? { projectCwd, instanceKey: acquireOptions.instanceKey, port: selectedPort, updatedAt: timestamp };
|
|
432
|
+
entry.port = selectedPort;
|
|
433
|
+
entry.updatedAt = timestamp;
|
|
434
|
+
if (!existing) registry.allocations.push(entry);
|
|
435
|
+
registry.leases = registry.leases.filter((lease2) => leaseKey(lease2.projectCwd, lease2.instanceKey) !== key);
|
|
436
|
+
const lease = { projectCwd, instanceKey: acquireOptions.instanceKey, port: selectedPort, pid, acquiredAt: timestamp, expiresAt: timestamp + (acquireOptions.leaseTtlMs ?? 30 * 60 * 1e3), ownerToken };
|
|
437
|
+
registry.leases.push(lease);
|
|
438
|
+
await writeRegistry(registry);
|
|
439
|
+
return lease;
|
|
440
|
+
});
|
|
441
|
+
},
|
|
442
|
+
async releasePort(releaseOptions) {
|
|
443
|
+
const projectCwd = canonicalizeLocalghostProjectCwd(releaseOptions.projectCwd ?? cwd);
|
|
444
|
+
return withLock(async (registry) => {
|
|
445
|
+
const key = leaseKey(projectCwd, releaseOptions.instanceKey);
|
|
446
|
+
const before = registry.leases.length;
|
|
447
|
+
registry.leases = registry.leases.filter((lease) => leaseKey(lease.projectCwd, lease.instanceKey) !== key || lease.ownerToken !== ownerToken);
|
|
448
|
+
if (registry.leases.length !== before) await writeRegistry(registry);
|
|
449
|
+
return registry.leases.length !== before;
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
267
455
|
// src/tunnel.ts
|
|
268
456
|
import { domainToASCII } from "url";
|
|
269
457
|
var DEFAULT_GHOST_TUNNEL_SUBDOMAIN = "ghost";
|
|
@@ -641,7 +829,7 @@ function envHttps() {
|
|
|
641
829
|
}
|
|
642
830
|
function getPackageName(cwd) {
|
|
643
831
|
try {
|
|
644
|
-
const pkg = JSON.parse(readFileSync3(
|
|
832
|
+
const pkg = JSON.parse(readFileSync3(join4(cwd, "package.json"), "utf8"));
|
|
645
833
|
return typeof pkg.name === "string" ? pkg.name : void 0;
|
|
646
834
|
} catch {
|
|
647
835
|
return void 0;
|
|
@@ -724,7 +912,24 @@ async function resolveLocalghostContext(options = {}) {
|
|
|
724
912
|
const autoRepair = merged.autoRepair ?? true;
|
|
725
913
|
const bindHost = merged.bindHost ?? "127.0.0.1";
|
|
726
914
|
const probeHost = typeof bindHost === "string" ? bindHost : "127.0.0.1";
|
|
727
|
-
|
|
915
|
+
let port = requestedPort;
|
|
916
|
+
let releasePort;
|
|
917
|
+
const reservePort = merged.reservePort ?? false;
|
|
918
|
+
const instanceKey = merged.instanceKey ?? "run";
|
|
919
|
+
if (reservePort && dynamicPort) {
|
|
920
|
+
const registry = createLocalghostRegistry({ cwd, ...merged.registryOwnerToken ? { ownerToken: merged.registryOwnerToken } : {} });
|
|
921
|
+
const lease = await registry.acquirePort({
|
|
922
|
+
projectCwd: cwd,
|
|
923
|
+
instanceKey,
|
|
924
|
+
startPort: requestedPort,
|
|
925
|
+
host: probeHost,
|
|
926
|
+
...options.reservedPorts ? { reservedPorts: options.reservedPorts } : {}
|
|
927
|
+
});
|
|
928
|
+
port = lease.port;
|
|
929
|
+
releasePort = () => registry.releasePort({ projectCwd: cwd, instanceKey });
|
|
930
|
+
} else if (dynamicPort) {
|
|
931
|
+
port = await findAvailablePort(requestedPort, { host: probeHost });
|
|
932
|
+
}
|
|
728
933
|
const wwwAlias = merged.wwwAlias ?? true;
|
|
729
934
|
const entries = wwwAlias ? addDefaultWwwAliases(withRuntimePort(configEntries, requestedPort, port)) : withRuntimePort(configEntries, requestedPort, port);
|
|
730
935
|
const hosts = uniqueHosts(entries);
|
|
@@ -753,7 +958,8 @@ async function resolveLocalghostContext(options = {}) {
|
|
|
753
958
|
https: merged.https ?? envHttps() ?? false,
|
|
754
959
|
wwwAlias,
|
|
755
960
|
ghostTunnel,
|
|
756
|
-
...projectConfig.path ? { projectConfigPath: projectConfig.path } : {}
|
|
961
|
+
...projectConfig.path ? { projectConfigPath: projectConfig.path } : {},
|
|
962
|
+
...releasePort ? { releasePort } : {}
|
|
757
963
|
};
|
|
758
964
|
}
|
|
759
965
|
|
|
@@ -806,7 +1012,7 @@ function writeTextFile(path, value) {
|
|
|
806
1012
|
// src/hosts-file.ts
|
|
807
1013
|
import { writeFileSync as writeFileSync3 } from "fs";
|
|
808
1014
|
import { tmpdir } from "os";
|
|
809
|
-
import { join as
|
|
1015
|
+
import { join as join5 } from "path";
|
|
810
1016
|
import { execa as execa2 } from "execa";
|
|
811
1017
|
function escapeRegExp(value) {
|
|
812
1018
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -842,7 +1048,7 @@ ${block}`;
|
|
|
842
1048
|
}
|
|
843
1049
|
async function writeSystemHostsFile(hostsPath, next, projectName) {
|
|
844
1050
|
const sanitizedProjectName = sanitizeProjectName(projectName);
|
|
845
|
-
const tempPath =
|
|
1051
|
+
const tempPath = join5(tmpdir(), `localghost-${sanitizedProjectName}-hosts`);
|
|
846
1052
|
writeFileSync3(tempPath, next, "utf8");
|
|
847
1053
|
if (process.env.LOCALGHOST_HOSTS_PATH) {
|
|
848
1054
|
writeFileSync3(hostsPath, next, "utf8");
|
|
@@ -899,10 +1105,10 @@ async function ask(question, defaultValue) {
|
|
|
899
1105
|
|
|
900
1106
|
// src/state.ts
|
|
901
1107
|
import { existsSync as existsSync4 } from "fs";
|
|
902
|
-
import { join as
|
|
1108
|
+
import { join as join6 } from "path";
|
|
903
1109
|
var LOCALGHOST_STATE_FILE = "ops/local/localghost-state.json";
|
|
904
1110
|
function getLocalghostStatePath(cwd = process.cwd()) {
|
|
905
|
-
return
|
|
1111
|
+
return join6(cwd, LOCALGHOST_STATE_FILE);
|
|
906
1112
|
}
|
|
907
1113
|
function readLocalghostState(cwd = process.cwd()) {
|
|
908
1114
|
const path = getLocalghostStatePath(cwd);
|
|
@@ -917,7 +1123,7 @@ function writeLocalghostState(cwd, state) {
|
|
|
917
1123
|
}
|
|
918
1124
|
|
|
919
1125
|
// src/caddy.ts
|
|
920
|
-
import { dirname as dirname3, join as
|
|
1126
|
+
import { dirname as dirname3, join as join7 } from "path";
|
|
921
1127
|
import { execa as execa3 } from "execa";
|
|
922
1128
|
function shouldShowCaddyLogs() {
|
|
923
1129
|
return ["1", "true", "yes", "on"].includes((process.env.LOCALGHOST_CADDY_VERBOSE ?? "").toLowerCase());
|
|
@@ -935,7 +1141,7 @@ function groupByPort(entries) {
|
|
|
935
1141
|
return groups;
|
|
936
1142
|
}
|
|
937
1143
|
function getCaddyfilePath(cwd = process.cwd()) {
|
|
938
|
-
return
|
|
1144
|
+
return join7(cwd, "ops/local/Caddyfile");
|
|
939
1145
|
}
|
|
940
1146
|
function renderCaddyfile(entries, options = {}) {
|
|
941
1147
|
const groups = groupByPort(entries);
|
|
@@ -1073,12 +1279,12 @@ function getConfigWatchFiles(options) {
|
|
|
1073
1279
|
const readOptions = readOptionsFromPlugin(options);
|
|
1074
1280
|
const cwd = readOptions.cwd ?? process.cwd();
|
|
1075
1281
|
const resolvedPath = resolveDevHostsPath(readOptions);
|
|
1076
|
-
const candidatePaths = getConfigFileCandidates(readOptions).map((fileName) =>
|
|
1077
|
-
const projectConfigPaths = options.localghostConfig === false ? [] : options.localghostConfig ? [
|
|
1282
|
+
const candidatePaths = getConfigFileCandidates(readOptions).map((fileName) => resolve3(cwd, fileName));
|
|
1283
|
+
const projectConfigPaths = options.localghostConfig === false ? [] : options.localghostConfig ? [resolve3(cwd, options.localghostConfig)] : ["localghost.config.mjs", "localghost.config.js", "localghost.config.cjs"].map((fileName) => resolve3(cwd, fileName));
|
|
1078
1284
|
return [.../* @__PURE__ */ new Set([...candidatePaths, resolvedPath.path, ...projectConfigPaths])];
|
|
1079
1285
|
}
|
|
1080
1286
|
function normalizeWatchPath(filePath) {
|
|
1081
|
-
return
|
|
1287
|
+
return normalize2(resolve3(filePath));
|
|
1082
1288
|
}
|
|
1083
1289
|
function renderConfig(hosts, port) {
|
|
1084
1290
|
return [
|
|
@@ -1094,7 +1300,7 @@ function defaultHost(cwd) {
|
|
|
1094
1300
|
}
|
|
1095
1301
|
function getPackageOwner2(cwd) {
|
|
1096
1302
|
try {
|
|
1097
|
-
const pkg = JSON.parse(readFileSync5(
|
|
1303
|
+
const pkg = JSON.parse(readFileSync5(resolve3(cwd, "package.json"), "utf8"));
|
|
1098
1304
|
if (typeof pkg.name === "string" && pkg.name.startsWith("@")) {
|
|
1099
1305
|
return pkg.name.slice(1).split("/")[0];
|
|
1100
1306
|
}
|
|
@@ -1219,10 +1425,15 @@ async function ensureLocalghostContext(options, vitePort, https) {
|
|
|
1219
1425
|
writeTextFile(resolved.path, renderConfig(hosts, vitePort));
|
|
1220
1426
|
console.log(`Created ${resolved.path}`);
|
|
1221
1427
|
}
|
|
1428
|
+
const wrapperManagedPort = Boolean(process.env.LOCALGHOST_PORT);
|
|
1222
1429
|
const context = await resolveLocalghostContext({
|
|
1223
1430
|
...options,
|
|
1224
1431
|
cwd,
|
|
1225
1432
|
port: vitePort,
|
|
1433
|
+
reservePort: !wrapperManagedPort,
|
|
1434
|
+
instanceKey: "vite",
|
|
1435
|
+
registryOwnerToken: options.registryOwnerToken ?? `${process.pid}:vite:${cwd}`,
|
|
1436
|
+
...wrapperManagedPort ? { dynamicPort: false } : {},
|
|
1226
1437
|
...typeof https === "boolean" ? { https } : {}
|
|
1227
1438
|
});
|
|
1228
1439
|
if (!hasReadySetup(cwd, context.entries, resolved.path, context.https)) {
|
|
@@ -1420,6 +1631,7 @@ function localGhostPlugin(options = {}) {
|
|
|
1420
1631
|
const cleanup = () => {
|
|
1421
1632
|
cleanupActivity();
|
|
1422
1633
|
cleanupGhostMenu?.();
|
|
1634
|
+
void resolvedContext?.releasePort?.();
|
|
1423
1635
|
};
|
|
1424
1636
|
server.httpServer?.once("close", cleanup);
|
|
1425
1637
|
process.once("exit", cleanup);
|