@hamedb89/localghost 0.6.1 → 0.6.2
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 +5 -1
- package/dist/cli.js +347 -304
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +142 -107
- package/dist/index.js +1464 -1394
- package/dist/index.js.map +1 -1
- package/dist/{tunnel-BA52DD9e.d.ts → tunnel-DHYPpsX2.d.ts} +1 -1
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +1 -1
- package/dist/vite.js.map +1 -1
- package/docs/ghost-tunnel.md +9 -3
- package/package.json +5 -2
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { existsSync as
|
|
4
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9, unlinkSync } from "fs";
|
|
5
5
|
import { Command, InvalidArgumentError } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/activity.ts
|
|
@@ -480,20 +480,20 @@ function formatDetectedDevServices(services) {
|
|
|
480
480
|
}
|
|
481
481
|
|
|
482
482
|
// src/context.ts
|
|
483
|
-
import { existsSync as
|
|
483
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
484
484
|
import { join as join6 } from "path";
|
|
485
485
|
import { pathToFileURL } from "url";
|
|
486
486
|
|
|
487
487
|
// src/port.ts
|
|
488
488
|
import { createServer } from "net";
|
|
489
489
|
async function isPortAvailable(port, host = "127.0.0.1") {
|
|
490
|
-
return new Promise((
|
|
490
|
+
return new Promise((resolve6) => {
|
|
491
491
|
const server = createServer();
|
|
492
492
|
server.once("error", () => {
|
|
493
|
-
|
|
493
|
+
resolve6(false);
|
|
494
494
|
});
|
|
495
495
|
server.once("listening", () => {
|
|
496
|
-
server.close(() =>
|
|
496
|
+
server.close(() => resolve6(true));
|
|
497
497
|
});
|
|
498
498
|
server.listen(port, host);
|
|
499
499
|
});
|
|
@@ -738,7 +738,7 @@ function createLocalghostRegistry(options = {}) {
|
|
|
738
738
|
};
|
|
739
739
|
}
|
|
740
740
|
|
|
741
|
-
// src/tunnel.ts
|
|
741
|
+
// packages/ghost-tunnel/src/tunnel.ts
|
|
742
742
|
import { domainToASCII } from "url";
|
|
743
743
|
var DEFAULT_GHOST_TUNNEL_SUBDOMAIN = "ghost";
|
|
744
744
|
var DEFAULT_GHOST_TUNNEL_NAMESPACE_TAGS = ["route", "project", "owner"];
|
|
@@ -1089,275 +1089,7 @@ function constructGhostTunnelUrl(input2) {
|
|
|
1089
1089
|
return url.toString();
|
|
1090
1090
|
}
|
|
1091
1091
|
|
|
1092
|
-
// src/
|
|
1093
|
-
var LOCALGHOST_PROJECT_CONFIG_FILES = [
|
|
1094
|
-
"localghost.config.mjs",
|
|
1095
|
-
"localghost.config.js",
|
|
1096
|
-
"localghost.config.cjs"
|
|
1097
|
-
];
|
|
1098
|
-
function parsePort(value) {
|
|
1099
|
-
if (!value) return void 0;
|
|
1100
|
-
const port = Number.parseInt(value, 10);
|
|
1101
|
-
return Number.isInteger(port) && port > 0 && port <= 65535 ? port : void 0;
|
|
1102
|
-
}
|
|
1103
|
-
function envPort() {
|
|
1104
|
-
return parsePort(process.env.LOCALGHOST_PORT) ?? parsePort(process.env.VITE_PORT);
|
|
1105
|
-
}
|
|
1106
|
-
function envDynamicPort() {
|
|
1107
|
-
const value = process.env.LOCALGHOST_DYNAMIC_PORT;
|
|
1108
|
-
if (!value) return void 0;
|
|
1109
|
-
return ["1", "true", "yes", "on"].includes(value.toLowerCase());
|
|
1110
|
-
}
|
|
1111
|
-
function envHttps() {
|
|
1112
|
-
const value = process.env.LOCALGHOST_HTTPS;
|
|
1113
|
-
if (!value) return void 0;
|
|
1114
|
-
return ["1", "true", "yes", "on"].includes(value.toLowerCase());
|
|
1115
|
-
}
|
|
1116
|
-
function getPackageName(cwd) {
|
|
1117
|
-
try {
|
|
1118
|
-
const pkg = JSON.parse(readFileSync5(join6(cwd, "package.json"), "utf8"));
|
|
1119
|
-
return typeof pkg.name === "string" ? pkg.name : void 0;
|
|
1120
|
-
} catch {
|
|
1121
|
-
return void 0;
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
function getPackageOwner(cwd) {
|
|
1125
|
-
const packageName = getPackageName(cwd);
|
|
1126
|
-
if (!packageName?.startsWith("@")) return void 0;
|
|
1127
|
-
return packageName.slice(1).split("/")[0];
|
|
1128
|
-
}
|
|
1129
|
-
function getLocalOwner(cwd) {
|
|
1130
|
-
return sanitizeProjectName(process.env.LOCALGHOST_OWNER ?? getPackageOwner(cwd) ?? process.env.USER ?? process.env.USERNAME ?? "local");
|
|
1131
|
-
}
|
|
1132
|
-
function getRouteName(primaryHost, fallback) {
|
|
1133
|
-
return sanitizeProjectName(primaryHost.split(".")[0] ?? fallback);
|
|
1134
|
-
}
|
|
1135
|
-
function readOptionsFromContext(options) {
|
|
1136
|
-
return {
|
|
1137
|
-
cwd: options.cwd ?? process.cwd(),
|
|
1138
|
-
...options.fileName ? { fileName: options.fileName } : {},
|
|
1139
|
-
...options.configFiles ? { configFiles: options.configFiles } : {},
|
|
1140
|
-
...options.configPattern ? { configPattern: options.configPattern } : {}
|
|
1141
|
-
};
|
|
1142
|
-
}
|
|
1143
|
-
function withRuntimePort(entries, requestedPort, port) {
|
|
1144
|
-
if (requestedPort === port) return entries;
|
|
1145
|
-
const hasRequestedPort = entries.some((entry) => entry.port === requestedPort);
|
|
1146
|
-
if (!hasRequestedPort) return entries;
|
|
1147
|
-
return entries.map((entry) => entry.port === requestedPort ? { ...entry, port, target: `127.0.0.1:${port}` } : entry);
|
|
1148
|
-
}
|
|
1149
|
-
function uniqueHosts(entries) {
|
|
1150
|
-
return [...new Set(entries.map((entry) => entry.host))];
|
|
1151
|
-
}
|
|
1152
|
-
function isAliasableHost(host) {
|
|
1153
|
-
return host.includes(".") && !host.startsWith("www.") && !host.includes(":");
|
|
1154
|
-
}
|
|
1155
|
-
function getDefaultWwwAlias(host) {
|
|
1156
|
-
return isAliasableHost(host) ? `www.${host}` : null;
|
|
1157
|
-
}
|
|
1158
|
-
function addDefaultWwwAliases(entries) {
|
|
1159
|
-
const seen = new Set(entries.map((entry) => entry.host));
|
|
1160
|
-
const aliases = [];
|
|
1161
|
-
for (const entry of entries) {
|
|
1162
|
-
const alias = getDefaultWwwAlias(entry.host);
|
|
1163
|
-
if (alias && !seen.has(alias)) {
|
|
1164
|
-
aliases.push({ host: alias, port: entry.port, target: `127.0.0.1:${entry.port}` });
|
|
1165
|
-
seen.add(alias);
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
return [...entries, ...aliases];
|
|
1169
|
-
}
|
|
1170
|
-
function defined(input2) {
|
|
1171
|
-
return Object.fromEntries(Object.entries(input2).filter(([, value]) => typeof value !== "undefined"));
|
|
1172
|
-
}
|
|
1173
|
-
async function readLocalghostProjectConfig(options = {}) {
|
|
1174
|
-
const cwd = options.cwd ?? process.cwd();
|
|
1175
|
-
if (options.configFile === false) return { config: {} };
|
|
1176
|
-
const candidates = options.configFile ? [options.configFile] : LOCALGHOST_PROJECT_CONFIG_FILES;
|
|
1177
|
-
const path = candidates.map((candidate) => resolveDevHostsPath({ cwd, fileName: candidate }).path).find((candidate) => existsSync4(candidate));
|
|
1178
|
-
if (!path) return { config: {} };
|
|
1179
|
-
const imported = await import(`${pathToFileURL(path).href}?localghost=${Date.now()}`);
|
|
1180
|
-
const config = imported.default ?? imported;
|
|
1181
|
-
return { config, path };
|
|
1182
|
-
}
|
|
1183
|
-
async function resolveLocalghostContext(options = {}) {
|
|
1184
|
-
const cwd = options.cwd ?? process.cwd();
|
|
1185
|
-
const projectConfig = await readLocalghostProjectConfig({
|
|
1186
|
-
cwd,
|
|
1187
|
-
...typeof options.localghostConfig !== "undefined" ? { configFile: options.localghostConfig } : {}
|
|
1188
|
-
});
|
|
1189
|
-
const merged = {
|
|
1190
|
-
...projectConfig.config,
|
|
1191
|
-
...defined(options)
|
|
1192
|
-
};
|
|
1193
|
-
const readOptions = readOptionsFromContext({ ...merged, cwd });
|
|
1194
|
-
const resolvedPath = resolveDevHostsPath(readOptions);
|
|
1195
|
-
const configEntries = readDevHosts(readOptions);
|
|
1196
|
-
const requestedPort = merged.port ?? envPort() ?? configEntries[0]?.port ?? 5173;
|
|
1197
|
-
const dynamicPort = merged.dynamicPort ?? envDynamicPort() ?? true;
|
|
1198
|
-
const autoRepair = merged.autoRepair ?? true;
|
|
1199
|
-
const bindHost = merged.bindHost ?? "127.0.0.1";
|
|
1200
|
-
const probeHost = typeof bindHost === "string" ? bindHost : "127.0.0.1";
|
|
1201
|
-
let port = requestedPort;
|
|
1202
|
-
let releasePort;
|
|
1203
|
-
const reservePort = merged.reservePort ?? false;
|
|
1204
|
-
const instanceKey = merged.instanceKey ?? "run";
|
|
1205
|
-
if (reservePort && dynamicPort) {
|
|
1206
|
-
const registry = createLocalghostRegistry({ cwd, ...merged.registryOwnerToken ? { ownerToken: merged.registryOwnerToken } : {} });
|
|
1207
|
-
const lease = await registry.acquirePort({
|
|
1208
|
-
projectCwd: cwd,
|
|
1209
|
-
instanceKey,
|
|
1210
|
-
startPort: requestedPort,
|
|
1211
|
-
host: probeHost,
|
|
1212
|
-
...options.reservedPorts ? { reservedPorts: options.reservedPorts } : {}
|
|
1213
|
-
});
|
|
1214
|
-
port = lease.port;
|
|
1215
|
-
releasePort = () => registry.releasePort({ projectCwd: cwd, instanceKey });
|
|
1216
|
-
} else if (dynamicPort) {
|
|
1217
|
-
port = await findAvailablePort(requestedPort, { host: probeHost });
|
|
1218
|
-
}
|
|
1219
|
-
const wwwAlias = merged.wwwAlias ?? true;
|
|
1220
|
-
const entries = wwwAlias ? addDefaultWwwAliases(withRuntimePort(configEntries, requestedPort, port)) : withRuntimePort(configEntries, requestedPort, port);
|
|
1221
|
-
const hosts = uniqueHosts(entries);
|
|
1222
|
-
const primaryHost = merged.primaryHost ?? entries.find((entry) => entry.port === port)?.host ?? hosts[0] ?? `${sanitizeProjectName(getProjectName(cwd))}.localhost`;
|
|
1223
|
-
const projectName = sanitizeProjectName(merged.project ?? getProjectName(cwd));
|
|
1224
|
-
const ghostTunnel = resolveGhostTunnelConfig(merged.ghostTunnel, {
|
|
1225
|
-
route: getRouteName(primaryHost, projectName),
|
|
1226
|
-
project: projectName,
|
|
1227
|
-
owner: getLocalOwner(cwd)
|
|
1228
|
-
});
|
|
1229
|
-
return {
|
|
1230
|
-
cwd,
|
|
1231
|
-
projectName,
|
|
1232
|
-
readOptions,
|
|
1233
|
-
configPath: resolvedPath.path,
|
|
1234
|
-
configFileName: resolvedPath.fileName,
|
|
1235
|
-
configEntries,
|
|
1236
|
-
entries,
|
|
1237
|
-
hosts,
|
|
1238
|
-
requestedPort,
|
|
1239
|
-
port,
|
|
1240
|
-
dynamicPort,
|
|
1241
|
-
autoRepair,
|
|
1242
|
-
bindHost,
|
|
1243
|
-
primaryHost,
|
|
1244
|
-
https: merged.https ?? envHttps() ?? false,
|
|
1245
|
-
wwwAlias,
|
|
1246
|
-
ghostTunnel,
|
|
1247
|
-
...projectConfig.path ? { projectConfigPath: projectConfig.path } : {},
|
|
1248
|
-
...releasePort ? { releasePort } : {}
|
|
1249
|
-
};
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
// src/doctor.ts
|
|
1253
|
-
import { execa as execa2 } from "execa";
|
|
1254
|
-
async function checkCaddy() {
|
|
1255
|
-
try {
|
|
1256
|
-
const result = await execa2("caddy", ["version"], { reject: false });
|
|
1257
|
-
const version = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
1258
|
-
return {
|
|
1259
|
-
found: result.exitCode === 0,
|
|
1260
|
-
...version ? { version } : {},
|
|
1261
|
-
installHint: "brew install caddy"
|
|
1262
|
-
};
|
|
1263
|
-
} catch {
|
|
1264
|
-
return {
|
|
1265
|
-
found: false,
|
|
1266
|
-
installHint: "brew install caddy"
|
|
1267
|
-
};
|
|
1268
|
-
}
|
|
1269
|
-
}
|
|
1270
|
-
async function runDoctor(options = {}) {
|
|
1271
|
-
const caddy = await checkCaddy();
|
|
1272
|
-
const cwd = options.cwd ?? process.cwd();
|
|
1273
|
-
const registry = createLocalghostRegistry({ cwd });
|
|
1274
|
-
const data = await registry.read();
|
|
1275
|
-
const now = Date.now();
|
|
1276
|
-
const staleLeases = data.leases.filter((lease) => lease.expiresAt <= now || !isProcessRunning(lease.pid)).map(({ projectCwd, instanceKey, port, pid }) => ({ projectCwd, instanceKey, port, pid }));
|
|
1277
|
-
const allocationsByPort = /* @__PURE__ */ new Map();
|
|
1278
|
-
for (const allocation of data.allocations) {
|
|
1279
|
-
const projects = allocationsByPort.get(allocation.port) ?? [];
|
|
1280
|
-
projects.push(`${allocation.projectCwd}#${allocation.instanceKey}`);
|
|
1281
|
-
allocationsByPort.set(allocation.port, projects);
|
|
1282
|
-
}
|
|
1283
|
-
const duplicateAllocations = [...allocationsByPort.entries()].filter(([, projects]) => projects.length > 1).map(([port, projects]) => ({ port, projects }));
|
|
1284
|
-
let configured;
|
|
1285
|
-
let available;
|
|
1286
|
-
try {
|
|
1287
|
-
const context = await resolveLocalghostContext({
|
|
1288
|
-
cwd,
|
|
1289
|
-
...options.configFiles ? { configFiles: options.configFiles } : {},
|
|
1290
|
-
...options.configPattern ? { configPattern: options.configPattern } : {},
|
|
1291
|
-
dynamicPort: false
|
|
1292
|
-
});
|
|
1293
|
-
configured = context.requestedPort;
|
|
1294
|
-
available = await isPortAvailable(configured);
|
|
1295
|
-
} catch {
|
|
1296
|
-
}
|
|
1297
|
-
const currentProjectCwd = canonicalizeLocalghostProjectCwd(cwd);
|
|
1298
|
-
const currentAllocation = data.allocations.find((allocation) => allocation.projectCwd === currentProjectCwd);
|
|
1299
|
-
return {
|
|
1300
|
-
ok: caddy.found && available !== false && staleLeases.length === 0 && duplicateAllocations.length === 0,
|
|
1301
|
-
caddy,
|
|
1302
|
-
ports: {
|
|
1303
|
-
...configured !== void 0 ? { configured } : {},
|
|
1304
|
-
...available !== void 0 ? { available } : {},
|
|
1305
|
-
registryPath: registry.registryPath,
|
|
1306
|
-
staleLeases,
|
|
1307
|
-
duplicateAllocations,
|
|
1308
|
-
...currentAllocation ? {
|
|
1309
|
-
currentAllocation: {
|
|
1310
|
-
projectCwd: currentAllocation.projectCwd,
|
|
1311
|
-
instanceKey: currentAllocation.instanceKey,
|
|
1312
|
-
port: currentAllocation.port
|
|
1313
|
-
}
|
|
1314
|
-
} : {}
|
|
1315
|
-
}
|
|
1316
|
-
};
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
// src/env.ts
|
|
1320
|
-
function getProductionReason(env = process.env) {
|
|
1321
|
-
if (env.LOCALGHOST_ENV === "production") return "LOCALGHOST_ENV=production";
|
|
1322
|
-
if (env.NODE_ENV === "production") return "NODE_ENV=production";
|
|
1323
|
-
if (env.VERCEL_ENV === "production") return "VERCEL_ENV=production";
|
|
1324
|
-
if (env.NETLIFY === "true" && env.CONTEXT === "production") return "NETLIFY=true and CONTEXT=production";
|
|
1325
|
-
if (env.CF_PAGES_BRANCH && env.CF_PAGES_BRANCH === env.CF_PAGES_PRODUCTION_BRANCH) {
|
|
1326
|
-
return "CF_PAGES_BRANCH matches CF_PAGES_PRODUCTION_BRANCH";
|
|
1327
|
-
}
|
|
1328
|
-
return null;
|
|
1329
|
-
}
|
|
1330
|
-
function assertLocalDevelopment(command, env = process.env) {
|
|
1331
|
-
const reason = getProductionReason(env);
|
|
1332
|
-
if (!reason) return;
|
|
1333
|
-
throw new Error(`Localghost only runs in local development. Refusing \`${command}\` because ${reason}.`);
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
// src/ghost-file.ts
|
|
1337
|
-
var LOCALGHOST_GHOST_TUNNEL_FILE = ".ghosttunnel";
|
|
1338
|
-
function toGhostTunnelOptions(options = {}) {
|
|
1339
|
-
const resolved = typeof options === "string" ? { cwd: options } : options;
|
|
1340
|
-
return {
|
|
1341
|
-
...resolved,
|
|
1342
|
-
fileName: resolved.fileName ?? LOCALGHOST_GHOST_TUNNEL_FILE
|
|
1343
|
-
};
|
|
1344
|
-
}
|
|
1345
|
-
function resolveGhostTunnelPath(options = {}) {
|
|
1346
|
-
return resolveDevHostsPath(toGhostTunnelOptions(options));
|
|
1347
|
-
}
|
|
1348
|
-
function readGhostTunnelEntries(options = {}) {
|
|
1349
|
-
return readDevHosts(toGhostTunnelOptions(options));
|
|
1350
|
-
}
|
|
1351
|
-
function listGhostTunnelEntries(options = {}) {
|
|
1352
|
-
const resolved = resolveGhostTunnelPath(options);
|
|
1353
|
-
if (!resolved.exists) return [];
|
|
1354
|
-
return readGhostTunnelEntries(options);
|
|
1355
|
-
}
|
|
1356
|
-
|
|
1357
|
-
// src/ghost-agent.ts
|
|
1358
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
1359
|
-
|
|
1360
|
-
// src/relay.ts
|
|
1092
|
+
// packages/ghost-tunnel/src/relay.ts
|
|
1361
1093
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
1362
1094
|
import { domainToASCII as domainToASCII2 } from "url";
|
|
1363
1095
|
var DEFAULT_RELAY_ALLOWED_TARGET_HOSTS = ["localhost", "127.0.0.1", "::1"];
|
|
@@ -1463,10 +1195,76 @@ function stripRelayForwardHeaders(headers) {
|
|
|
1463
1195
|
if (lowerName.startsWith("x-localghost-")) continue;
|
|
1464
1196
|
stripped[name] = value;
|
|
1465
1197
|
}
|
|
1466
|
-
return stripped;
|
|
1198
|
+
return stripped;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// packages/ghost-tunnel/src/ghost-file.ts
|
|
1202
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, readdirSync as readdirSync2 } from "fs";
|
|
1203
|
+
import { basename as basename2, resolve as resolve4 } from "path";
|
|
1204
|
+
var LOCALGHOST_GHOST_TUNNEL_FILE = ".ghosttunnel";
|
|
1205
|
+
function getCandidates(options) {
|
|
1206
|
+
const exact = [.../* @__PURE__ */ new Set([...options.fileName ? [options.fileName] : [], ...options.configFiles ?? []])];
|
|
1207
|
+
const pattern = options.configPattern ? readdirSync2(options.cwd ?? process.cwd(), { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name).filter((name) => {
|
|
1208
|
+
const matcher = typeof options.configPattern === "string" ? new RegExp(options.configPattern) : options.configPattern;
|
|
1209
|
+
if (!matcher) return false;
|
|
1210
|
+
matcher.lastIndex = 0;
|
|
1211
|
+
return matcher.test(name);
|
|
1212
|
+
}).sort() : [];
|
|
1213
|
+
if (exact.length > 0 || pattern.length > 0) return [.../* @__PURE__ */ new Set([...exact, ...pattern])];
|
|
1214
|
+
return [LOCALGHOST_GHOST_TUNNEL_FILE];
|
|
1215
|
+
}
|
|
1216
|
+
function parseGhostTunnelEntries(input2, fileName) {
|
|
1217
|
+
const entries = [];
|
|
1218
|
+
input2.split(/\r?\n/).forEach((rawLine, index) => {
|
|
1219
|
+
const line = rawLine.replace(/#.*/, "").trim();
|
|
1220
|
+
if (!line) return;
|
|
1221
|
+
const parts = line.split(/\s+/);
|
|
1222
|
+
const host = parts[0];
|
|
1223
|
+
const portRaw = parts[1];
|
|
1224
|
+
if (!host || !portRaw || parts.length > 2) {
|
|
1225
|
+
throw new Error(`Invalid ${fileName} line ${index + 1}: "${rawLine}"`);
|
|
1226
|
+
}
|
|
1227
|
+
if (!/^(?=.{1,253}$)(?!-)[a-z0-9-]+(\.[a-z0-9-]+)*\.?$/i.test(host)) {
|
|
1228
|
+
throw new Error(`Invalid host on line ${index + 1}: "${host}"`);
|
|
1229
|
+
}
|
|
1230
|
+
const port = Number(portRaw);
|
|
1231
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
1232
|
+
throw new Error(`Invalid port on line ${index + 1}: "${portRaw}"`);
|
|
1233
|
+
}
|
|
1234
|
+
entries.push({ host: host.toLowerCase().replace(/\.$/, ""), port, target: `127.0.0.1:${port}` });
|
|
1235
|
+
});
|
|
1236
|
+
return entries;
|
|
1237
|
+
}
|
|
1238
|
+
function toGhostTunnelOptions(options = {}) {
|
|
1239
|
+
const resolved = typeof options === "string" ? { cwd: options } : options;
|
|
1240
|
+
return {
|
|
1241
|
+
...resolved,
|
|
1242
|
+
fileName: resolved.fileName ?? LOCALGHOST_GHOST_TUNNEL_FILE
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
function resolveGhostTunnelPath(options = {}) {
|
|
1246
|
+
const resolved = toGhostTunnelOptions(options);
|
|
1247
|
+
const cwd = resolved.cwd ?? process.cwd();
|
|
1248
|
+
const searchedFiles = getCandidates(resolved);
|
|
1249
|
+
for (const fileName2 of searchedFiles) {
|
|
1250
|
+
const path = resolve4(cwd, fileName2);
|
|
1251
|
+
if (existsSync4(path)) return { path, fileName: basename2(fileName2), exists: true, searchedFiles };
|
|
1252
|
+
}
|
|
1253
|
+
const fileName = searchedFiles[0] ?? LOCALGHOST_GHOST_TUNNEL_FILE;
|
|
1254
|
+
return { path: resolve4(cwd, fileName), fileName: basename2(fileName), exists: false, searchedFiles };
|
|
1255
|
+
}
|
|
1256
|
+
function readGhostTunnelEntries(options = {}) {
|
|
1257
|
+
const resolved = resolveGhostTunnelPath(options);
|
|
1258
|
+
if (!resolved.exists) throw new Error(`Missing Ghost Tunnel file: ${resolved.path}`);
|
|
1259
|
+
return parseGhostTunnelEntries(readFileSync5(resolved.path, "utf8"), resolved.fileName);
|
|
1260
|
+
}
|
|
1261
|
+
function listGhostTunnelEntries(options = {}) {
|
|
1262
|
+
const resolved = resolveGhostTunnelPath(options);
|
|
1263
|
+
if (!resolved.exists) return [];
|
|
1264
|
+
return readGhostTunnelEntries(options);
|
|
1467
1265
|
}
|
|
1468
1266
|
|
|
1469
|
-
// src/ghost-tunnel-store.ts
|
|
1267
|
+
// packages/ghost-tunnel/src/ghost-tunnel-store.ts
|
|
1470
1268
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1471
1269
|
function base64Encode(value) {
|
|
1472
1270
|
return value.toString("base64");
|
|
@@ -1598,17 +1396,18 @@ function createRedisGhostTunnelStoreFromEnv(input2 = {}) {
|
|
|
1598
1396
|
});
|
|
1599
1397
|
}
|
|
1600
1398
|
|
|
1601
|
-
// src/ghost-agent.ts
|
|
1399
|
+
// packages/ghost-tunnel/src/ghost-agent.ts
|
|
1400
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
1602
1401
|
function isStopped(signal, localSignal) {
|
|
1603
1402
|
return localSignal.aborted || signal?.aborted === true;
|
|
1604
1403
|
}
|
|
1605
1404
|
function wait(ms, signal, localSignal) {
|
|
1606
1405
|
if (isStopped(signal, localSignal)) return Promise.resolve();
|
|
1607
|
-
return new Promise((
|
|
1608
|
-
const timeout = setTimeout(
|
|
1406
|
+
return new Promise((resolve6) => {
|
|
1407
|
+
const timeout = setTimeout(resolve6, ms);
|
|
1609
1408
|
const stop = () => {
|
|
1610
1409
|
clearTimeout(timeout);
|
|
1611
|
-
|
|
1410
|
+
resolve6();
|
|
1612
1411
|
};
|
|
1613
1412
|
signal?.addEventListener("abort", stop, { once: true });
|
|
1614
1413
|
localSignal.addEventListener("abort", stop, { once: true });
|
|
@@ -1746,6 +1545,250 @@ function startGhostTunnelAgent(options) {
|
|
|
1746
1545
|
};
|
|
1747
1546
|
}
|
|
1748
1547
|
|
|
1548
|
+
// src/context.ts
|
|
1549
|
+
var LOCALGHOST_PROJECT_CONFIG_FILES = [
|
|
1550
|
+
"localghost.config.mjs",
|
|
1551
|
+
"localghost.config.js",
|
|
1552
|
+
"localghost.config.cjs"
|
|
1553
|
+
];
|
|
1554
|
+
function parsePort(value) {
|
|
1555
|
+
if (!value) return void 0;
|
|
1556
|
+
const port = Number.parseInt(value, 10);
|
|
1557
|
+
return Number.isInteger(port) && port > 0 && port <= 65535 ? port : void 0;
|
|
1558
|
+
}
|
|
1559
|
+
function envPort() {
|
|
1560
|
+
return parsePort(process.env.LOCALGHOST_PORT) ?? parsePort(process.env.VITE_PORT);
|
|
1561
|
+
}
|
|
1562
|
+
function envDynamicPort() {
|
|
1563
|
+
const value = process.env.LOCALGHOST_DYNAMIC_PORT;
|
|
1564
|
+
if (!value) return void 0;
|
|
1565
|
+
return ["1", "true", "yes", "on"].includes(value.toLowerCase());
|
|
1566
|
+
}
|
|
1567
|
+
function envHttps() {
|
|
1568
|
+
const value = process.env.LOCALGHOST_HTTPS;
|
|
1569
|
+
if (!value) return void 0;
|
|
1570
|
+
return ["1", "true", "yes", "on"].includes(value.toLowerCase());
|
|
1571
|
+
}
|
|
1572
|
+
function getPackageName(cwd) {
|
|
1573
|
+
try {
|
|
1574
|
+
const pkg = JSON.parse(readFileSync6(join6(cwd, "package.json"), "utf8"));
|
|
1575
|
+
return typeof pkg.name === "string" ? pkg.name : void 0;
|
|
1576
|
+
} catch {
|
|
1577
|
+
return void 0;
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
function getPackageOwner(cwd) {
|
|
1581
|
+
const packageName = getPackageName(cwd);
|
|
1582
|
+
if (!packageName?.startsWith("@")) return void 0;
|
|
1583
|
+
return packageName.slice(1).split("/")[0];
|
|
1584
|
+
}
|
|
1585
|
+
function getLocalOwner(cwd) {
|
|
1586
|
+
return sanitizeProjectName(process.env.LOCALGHOST_OWNER ?? getPackageOwner(cwd) ?? process.env.USER ?? process.env.USERNAME ?? "local");
|
|
1587
|
+
}
|
|
1588
|
+
function getRouteName(primaryHost, fallback) {
|
|
1589
|
+
return sanitizeProjectName(primaryHost.split(".")[0] ?? fallback);
|
|
1590
|
+
}
|
|
1591
|
+
function readOptionsFromContext(options) {
|
|
1592
|
+
return {
|
|
1593
|
+
cwd: options.cwd ?? process.cwd(),
|
|
1594
|
+
...options.fileName ? { fileName: options.fileName } : {},
|
|
1595
|
+
...options.configFiles ? { configFiles: options.configFiles } : {},
|
|
1596
|
+
...options.configPattern ? { configPattern: options.configPattern } : {}
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
function withRuntimePort(entries, requestedPort, port) {
|
|
1600
|
+
if (requestedPort === port) return entries;
|
|
1601
|
+
const hasRequestedPort = entries.some((entry) => entry.port === requestedPort);
|
|
1602
|
+
if (!hasRequestedPort) return entries;
|
|
1603
|
+
return entries.map((entry) => entry.port === requestedPort ? { ...entry, port, target: `127.0.0.1:${port}` } : entry);
|
|
1604
|
+
}
|
|
1605
|
+
function uniqueHosts(entries) {
|
|
1606
|
+
return [...new Set(entries.map((entry) => entry.host))];
|
|
1607
|
+
}
|
|
1608
|
+
function isAliasableHost(host) {
|
|
1609
|
+
return host.includes(".") && !host.startsWith("www.") && !host.includes(":");
|
|
1610
|
+
}
|
|
1611
|
+
function getDefaultWwwAlias(host) {
|
|
1612
|
+
return isAliasableHost(host) ? `www.${host}` : null;
|
|
1613
|
+
}
|
|
1614
|
+
function addDefaultWwwAliases(entries) {
|
|
1615
|
+
const seen = new Set(entries.map((entry) => entry.host));
|
|
1616
|
+
const aliases = [];
|
|
1617
|
+
for (const entry of entries) {
|
|
1618
|
+
const alias = getDefaultWwwAlias(entry.host);
|
|
1619
|
+
if (alias && !seen.has(alias)) {
|
|
1620
|
+
aliases.push({ host: alias, port: entry.port, target: `127.0.0.1:${entry.port}` });
|
|
1621
|
+
seen.add(alias);
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
return [...entries, ...aliases];
|
|
1625
|
+
}
|
|
1626
|
+
function defined(input2) {
|
|
1627
|
+
return Object.fromEntries(Object.entries(input2).filter(([, value]) => typeof value !== "undefined"));
|
|
1628
|
+
}
|
|
1629
|
+
async function readLocalghostProjectConfig(options = {}) {
|
|
1630
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1631
|
+
if (options.configFile === false) return { config: {} };
|
|
1632
|
+
const candidates = options.configFile ? [options.configFile] : LOCALGHOST_PROJECT_CONFIG_FILES;
|
|
1633
|
+
const path = candidates.map((candidate) => resolveDevHostsPath({ cwd, fileName: candidate }).path).find((candidate) => existsSync5(candidate));
|
|
1634
|
+
if (!path) return { config: {} };
|
|
1635
|
+
const imported = await import(`${pathToFileURL(path).href}?localghost=${Date.now()}`);
|
|
1636
|
+
const config = imported.default ?? imported;
|
|
1637
|
+
return { config, path };
|
|
1638
|
+
}
|
|
1639
|
+
async function resolveLocalghostContext(options = {}) {
|
|
1640
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1641
|
+
const projectConfig = await readLocalghostProjectConfig({
|
|
1642
|
+
cwd,
|
|
1643
|
+
...typeof options.localghostConfig !== "undefined" ? { configFile: options.localghostConfig } : {}
|
|
1644
|
+
});
|
|
1645
|
+
const merged = {
|
|
1646
|
+
...projectConfig.config,
|
|
1647
|
+
...defined(options)
|
|
1648
|
+
};
|
|
1649
|
+
const readOptions = readOptionsFromContext({ ...merged, cwd });
|
|
1650
|
+
const resolvedPath = resolveDevHostsPath(readOptions);
|
|
1651
|
+
const configEntries = readDevHosts(readOptions);
|
|
1652
|
+
const requestedPort = merged.port ?? envPort() ?? configEntries[0]?.port ?? 5173;
|
|
1653
|
+
const dynamicPort = merged.dynamicPort ?? envDynamicPort() ?? true;
|
|
1654
|
+
const autoRepair = merged.autoRepair ?? true;
|
|
1655
|
+
const bindHost = merged.bindHost ?? "127.0.0.1";
|
|
1656
|
+
const probeHost = typeof bindHost === "string" ? bindHost : "127.0.0.1";
|
|
1657
|
+
let port = requestedPort;
|
|
1658
|
+
let releasePort;
|
|
1659
|
+
const reservePort = merged.reservePort ?? false;
|
|
1660
|
+
const instanceKey = merged.instanceKey ?? "run";
|
|
1661
|
+
if (reservePort && dynamicPort) {
|
|
1662
|
+
const registry = createLocalghostRegistry({ cwd, ...merged.registryOwnerToken ? { ownerToken: merged.registryOwnerToken } : {} });
|
|
1663
|
+
const lease = await registry.acquirePort({
|
|
1664
|
+
projectCwd: cwd,
|
|
1665
|
+
instanceKey,
|
|
1666
|
+
startPort: requestedPort,
|
|
1667
|
+
host: probeHost,
|
|
1668
|
+
...options.reservedPorts ? { reservedPorts: options.reservedPorts } : {}
|
|
1669
|
+
});
|
|
1670
|
+
port = lease.port;
|
|
1671
|
+
releasePort = () => registry.releasePort({ projectCwd: cwd, instanceKey });
|
|
1672
|
+
} else if (dynamicPort) {
|
|
1673
|
+
port = await findAvailablePort(requestedPort, { host: probeHost });
|
|
1674
|
+
}
|
|
1675
|
+
const wwwAlias = merged.wwwAlias ?? true;
|
|
1676
|
+
const entries = wwwAlias ? addDefaultWwwAliases(withRuntimePort(configEntries, requestedPort, port)) : withRuntimePort(configEntries, requestedPort, port);
|
|
1677
|
+
const hosts = uniqueHosts(entries);
|
|
1678
|
+
const primaryHost = merged.primaryHost ?? entries.find((entry) => entry.port === port)?.host ?? hosts[0] ?? `${sanitizeProjectName(getProjectName(cwd))}.localhost`;
|
|
1679
|
+
const projectName = sanitizeProjectName(merged.project ?? getProjectName(cwd));
|
|
1680
|
+
const ghostTunnel = resolveGhostTunnelConfig(merged.ghostTunnel, {
|
|
1681
|
+
route: getRouteName(primaryHost, projectName),
|
|
1682
|
+
project: projectName,
|
|
1683
|
+
owner: getLocalOwner(cwd)
|
|
1684
|
+
});
|
|
1685
|
+
return {
|
|
1686
|
+
cwd,
|
|
1687
|
+
projectName,
|
|
1688
|
+
readOptions,
|
|
1689
|
+
configPath: resolvedPath.path,
|
|
1690
|
+
configFileName: resolvedPath.fileName,
|
|
1691
|
+
configEntries,
|
|
1692
|
+
entries,
|
|
1693
|
+
hosts,
|
|
1694
|
+
requestedPort,
|
|
1695
|
+
port,
|
|
1696
|
+
dynamicPort,
|
|
1697
|
+
autoRepair,
|
|
1698
|
+
bindHost,
|
|
1699
|
+
primaryHost,
|
|
1700
|
+
https: merged.https ?? envHttps() ?? false,
|
|
1701
|
+
wwwAlias,
|
|
1702
|
+
ghostTunnel,
|
|
1703
|
+
...projectConfig.path ? { projectConfigPath: projectConfig.path } : {},
|
|
1704
|
+
...releasePort ? { releasePort } : {}
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
// src/doctor.ts
|
|
1709
|
+
import { execa as execa2 } from "execa";
|
|
1710
|
+
async function checkCaddy() {
|
|
1711
|
+
try {
|
|
1712
|
+
const result = await execa2("caddy", ["version"], { reject: false });
|
|
1713
|
+
const version = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
1714
|
+
return {
|
|
1715
|
+
found: result.exitCode === 0,
|
|
1716
|
+
...version ? { version } : {},
|
|
1717
|
+
installHint: "brew install caddy"
|
|
1718
|
+
};
|
|
1719
|
+
} catch {
|
|
1720
|
+
return {
|
|
1721
|
+
found: false,
|
|
1722
|
+
installHint: "brew install caddy"
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
async function runDoctor(options = {}) {
|
|
1727
|
+
const caddy = await checkCaddy();
|
|
1728
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1729
|
+
const registry = createLocalghostRegistry({ cwd });
|
|
1730
|
+
const data = await registry.read();
|
|
1731
|
+
const now = Date.now();
|
|
1732
|
+
const staleLeases = data.leases.filter((lease) => lease.expiresAt <= now || !isProcessRunning(lease.pid)).map(({ projectCwd, instanceKey, port, pid }) => ({ projectCwd, instanceKey, port, pid }));
|
|
1733
|
+
const allocationsByPort = /* @__PURE__ */ new Map();
|
|
1734
|
+
for (const allocation of data.allocations) {
|
|
1735
|
+
const projects = allocationsByPort.get(allocation.port) ?? [];
|
|
1736
|
+
projects.push(`${allocation.projectCwd}#${allocation.instanceKey}`);
|
|
1737
|
+
allocationsByPort.set(allocation.port, projects);
|
|
1738
|
+
}
|
|
1739
|
+
const duplicateAllocations = [...allocationsByPort.entries()].filter(([, projects]) => projects.length > 1).map(([port, projects]) => ({ port, projects }));
|
|
1740
|
+
let configured;
|
|
1741
|
+
let available;
|
|
1742
|
+
try {
|
|
1743
|
+
const context = await resolveLocalghostContext({
|
|
1744
|
+
cwd,
|
|
1745
|
+
...options.configFiles ? { configFiles: options.configFiles } : {},
|
|
1746
|
+
...options.configPattern ? { configPattern: options.configPattern } : {},
|
|
1747
|
+
dynamicPort: false
|
|
1748
|
+
});
|
|
1749
|
+
configured = context.requestedPort;
|
|
1750
|
+
available = await isPortAvailable(configured);
|
|
1751
|
+
} catch {
|
|
1752
|
+
}
|
|
1753
|
+
const currentProjectCwd = canonicalizeLocalghostProjectCwd(cwd);
|
|
1754
|
+
const currentAllocation = data.allocations.find((allocation) => allocation.projectCwd === currentProjectCwd);
|
|
1755
|
+
return {
|
|
1756
|
+
ok: caddy.found && available !== false && staleLeases.length === 0 && duplicateAllocations.length === 0,
|
|
1757
|
+
caddy,
|
|
1758
|
+
ports: {
|
|
1759
|
+
...configured !== void 0 ? { configured } : {},
|
|
1760
|
+
...available !== void 0 ? { available } : {},
|
|
1761
|
+
registryPath: registry.registryPath,
|
|
1762
|
+
staleLeases,
|
|
1763
|
+
duplicateAllocations,
|
|
1764
|
+
...currentAllocation ? {
|
|
1765
|
+
currentAllocation: {
|
|
1766
|
+
projectCwd: currentAllocation.projectCwd,
|
|
1767
|
+
instanceKey: currentAllocation.instanceKey,
|
|
1768
|
+
port: currentAllocation.port
|
|
1769
|
+
}
|
|
1770
|
+
} : {}
|
|
1771
|
+
}
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
// src/env.ts
|
|
1776
|
+
function getProductionReason(env = process.env) {
|
|
1777
|
+
if (env.LOCALGHOST_ENV === "production") return "LOCALGHOST_ENV=production";
|
|
1778
|
+
if (env.NODE_ENV === "production") return "NODE_ENV=production";
|
|
1779
|
+
if (env.VERCEL_ENV === "production") return "VERCEL_ENV=production";
|
|
1780
|
+
if (env.NETLIFY === "true" && env.CONTEXT === "production") return "NETLIFY=true and CONTEXT=production";
|
|
1781
|
+
if (env.CF_PAGES_BRANCH && env.CF_PAGES_BRANCH === env.CF_PAGES_PRODUCTION_BRANCH) {
|
|
1782
|
+
return "CF_PAGES_BRANCH matches CF_PAGES_PRODUCTION_BRANCH";
|
|
1783
|
+
}
|
|
1784
|
+
return null;
|
|
1785
|
+
}
|
|
1786
|
+
function assertLocalDevelopment(command, env = process.env) {
|
|
1787
|
+
const reason = getProductionReason(env);
|
|
1788
|
+
if (!reason) return;
|
|
1789
|
+
throw new Error(`Localghost only runs in local development. Refusing \`${command}\` because ${reason}.`);
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1749
1792
|
// src/hosts-file.ts
|
|
1750
1793
|
import { writeFileSync as writeFileSync3 } from "fs";
|
|
1751
1794
|
import { tmpdir } from "os";
|
|
@@ -1829,19 +1872,19 @@ async function removeSystemHosts(projectName) {
|
|
|
1829
1872
|
}
|
|
1830
1873
|
|
|
1831
1874
|
// src/init.ts
|
|
1832
|
-
import { existsSync as
|
|
1833
|
-
import { dirname as dirname4, join as join8, resolve as
|
|
1875
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
1876
|
+
import { dirname as dirname4, join as join8, resolve as resolve5 } from "path";
|
|
1834
1877
|
function detectPackageManager(cwd = process.cwd()) {
|
|
1835
|
-
if (
|
|
1836
|
-
if (
|
|
1837
|
-
if (
|
|
1878
|
+
if (existsSync6(join8(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
1879
|
+
if (existsSync6(join8(cwd, "yarn.lock"))) return "yarn";
|
|
1880
|
+
if (existsSync6(join8(cwd, "bun.lock")) || existsSync6(join8(cwd, "bun.lockb"))) return "bun";
|
|
1838
1881
|
return "npm";
|
|
1839
1882
|
}
|
|
1840
1883
|
function isPnpmWorkspaceRoot(cwd = process.cwd()) {
|
|
1841
|
-
const target =
|
|
1884
|
+
const target = resolve5(cwd);
|
|
1842
1885
|
let current = target;
|
|
1843
1886
|
while (true) {
|
|
1844
|
-
if (
|
|
1887
|
+
if (existsSync6(join8(current, "pnpm-workspace.yaml"))) return current === target;
|
|
1845
1888
|
const parent = dirname4(current);
|
|
1846
1889
|
if (parent === current) return false;
|
|
1847
1890
|
current = parent;
|
|
@@ -1865,7 +1908,7 @@ function renderConfig(options) {
|
|
|
1865
1908
|
}
|
|
1866
1909
|
function readPackageJson2(path) {
|
|
1867
1910
|
try {
|
|
1868
|
-
return JSON.parse(
|
|
1911
|
+
return JSON.parse(readFileSync7(path, "utf8"));
|
|
1869
1912
|
} catch {
|
|
1870
1913
|
return null;
|
|
1871
1914
|
}
|
|
@@ -1933,7 +1976,7 @@ function initLocalghost(options = {}) {
|
|
|
1933
1976
|
const packageManager = options.packageManager ?? detectPackageManager(cwd);
|
|
1934
1977
|
const configFile = options.configFile ?? LOCALGHOST_CONFIG_FILE;
|
|
1935
1978
|
const configPath = join8(cwd, configFile);
|
|
1936
|
-
const configExists =
|
|
1979
|
+
const configExists = existsSync6(configPath);
|
|
1937
1980
|
if (configExists && !options.force) {
|
|
1938
1981
|
return {
|
|
1939
1982
|
configPath,
|
|
@@ -1954,7 +1997,7 @@ function initLocalghost(options = {}) {
|
|
|
1954
1997
|
return {
|
|
1955
1998
|
configPath,
|
|
1956
1999
|
configCreated: true,
|
|
1957
|
-
...
|
|
2000
|
+
...existsSync6(packageJsonPath) ? { packageJsonPath } : {},
|
|
1958
2001
|
packageJsonChanged,
|
|
1959
2002
|
packageManager,
|
|
1960
2003
|
nextSteps: [
|
|
@@ -2183,7 +2226,7 @@ function formatGhostTunnel(config, options = {}) {
|
|
|
2183
2226
|
}
|
|
2184
2227
|
|
|
2185
2228
|
// src/state.ts
|
|
2186
|
-
import { existsSync as
|
|
2229
|
+
import { existsSync as existsSync7 } from "fs";
|
|
2187
2230
|
import { join as join9 } from "path";
|
|
2188
2231
|
var LOCALGHOST_STATE_FILE = "ops/local/localghost-state.json";
|
|
2189
2232
|
function getLocalghostStatePath(cwd = process.cwd()) {
|
|
@@ -2191,7 +2234,7 @@ function getLocalghostStatePath(cwd = process.cwd()) {
|
|
|
2191
2234
|
}
|
|
2192
2235
|
function readLocalghostState(cwd = process.cwd()) {
|
|
2193
2236
|
const path = getLocalghostStatePath(cwd);
|
|
2194
|
-
if (!
|
|
2237
|
+
if (!existsSync7(path)) return null;
|
|
2195
2238
|
return JSON.parse(readTextFile(path));
|
|
2196
2239
|
}
|
|
2197
2240
|
function writeLocalghostState(cwd, state) {
|
|
@@ -2207,11 +2250,11 @@ function patchLocalghostState(cwd, patch) {
|
|
|
2207
2250
|
}
|
|
2208
2251
|
|
|
2209
2252
|
// src/update-check.ts
|
|
2210
|
-
import { existsSync as
|
|
2253
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
2211
2254
|
import { homedir as homedir3 } from "os";
|
|
2212
2255
|
import { dirname as dirname5, join as join10 } from "path";
|
|
2213
2256
|
var LOCALGHOST_PACKAGE_NAME = "@hamedb89/localghost";
|
|
2214
|
-
var LOCALGHOST_VERSION = "0.6.
|
|
2257
|
+
var LOCALGHOST_VERSION = "0.6.2";
|
|
2215
2258
|
var UPDATE_CHECK_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2216
2259
|
var UPDATE_CHECK_NOTIFY_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2217
2260
|
var UPDATE_CHECK_TIMEOUT_MS = 900;
|
|
@@ -2227,9 +2270,9 @@ function getUpdateCheckCachePath(env = process.env) {
|
|
|
2227
2270
|
return join10(cacheRoot, "localghost", "update-check.json");
|
|
2228
2271
|
}
|
|
2229
2272
|
function readCache(path = getUpdateCheckCachePath()) {
|
|
2230
|
-
if (!
|
|
2273
|
+
if (!existsSync8(path)) return null;
|
|
2231
2274
|
try {
|
|
2232
|
-
return JSON.parse(
|
|
2275
|
+
return JSON.parse(readFileSync8(path, "utf8"));
|
|
2233
2276
|
} catch {
|
|
2234
2277
|
return null;
|
|
2235
2278
|
}
|
|
@@ -2553,7 +2596,7 @@ function getSetupReadiness(options) {
|
|
|
2553
2596
|
}
|
|
2554
2597
|
const hostsPath = getSystemHostsPath();
|
|
2555
2598
|
try {
|
|
2556
|
-
const hosts =
|
|
2599
|
+
const hosts = readFileSync9(hostsPath, "utf8");
|
|
2557
2600
|
const expectedHostsBlock = renderHostsBlock(projectName, entries).trimEnd();
|
|
2558
2601
|
if (!hosts.includes(expectedHostsBlock)) {
|
|
2559
2602
|
reasons.push(`The Localghost hosts block in ${hostsPath} is missing or stale.`);
|
|
@@ -2563,11 +2606,11 @@ function getSetupReadiness(options) {
|
|
|
2563
2606
|
reasons.push(`Could not read ${hostsPath}: ${message}`);
|
|
2564
2607
|
}
|
|
2565
2608
|
if (!options.ignoreCaddyfile) {
|
|
2566
|
-
if (!
|
|
2609
|
+
if (!existsSync9(caddyfilePath)) {
|
|
2567
2610
|
reasons.push(`Missing Caddyfile at ${caddyfilePath}.`);
|
|
2568
2611
|
} else {
|
|
2569
2612
|
const expectedCaddyfile = renderCaddyfile(entries, { https });
|
|
2570
|
-
const currentCaddyfile =
|
|
2613
|
+
const currentCaddyfile = readFileSync9(caddyfilePath, "utf8");
|
|
2571
2614
|
if (currentCaddyfile !== expectedCaddyfile) {
|
|
2572
2615
|
reasons.push(`Caddyfile at ${caddyfilePath} is stale for ${https ? "HTTPS" : "HTTP"} mode.`);
|
|
2573
2616
|
}
|
|
@@ -2612,7 +2655,7 @@ async function runSetupFromReadiness(cwd, https, readiness) {
|
|
|
2612
2655
|
});
|
|
2613
2656
|
}
|
|
2614
2657
|
function wait2(ms) {
|
|
2615
|
-
return new Promise((
|
|
2658
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
2616
2659
|
}
|
|
2617
2660
|
async function runTrust(cwd, caddyfilePath) {
|
|
2618
2661
|
await wait2(350);
|
|
@@ -3244,13 +3287,13 @@ program.command("reset").description("Remove Localghost setup state without dele
|
|
|
3244
3287
|
const statePath = getLocalghostStatePath(options.cwd);
|
|
3245
3288
|
explainHostsPassword();
|
|
3246
3289
|
const hostsResult = await removeSystemHosts(projectName);
|
|
3247
|
-
if (
|
|
3290
|
+
if (existsSync9(caddyfilePath)) {
|
|
3248
3291
|
unlinkSync(caddyfilePath);
|
|
3249
3292
|
console.log(`Removed ${caddyfilePath}`);
|
|
3250
3293
|
} else {
|
|
3251
3294
|
console.log(`${caddyfilePath} was not present`);
|
|
3252
3295
|
}
|
|
3253
|
-
if (
|
|
3296
|
+
if (existsSync9(statePath)) {
|
|
3254
3297
|
unlinkSync(statePath);
|
|
3255
3298
|
console.log(`Removed ${statePath}`);
|
|
3256
3299
|
} else {
|
|
@@ -3271,7 +3314,7 @@ program.command("teardown").description("Remove Localghost's managed /etc/hosts
|
|
|
3271
3314
|
const hostsResult = await removeSystemHosts(projectName);
|
|
3272
3315
|
const caddyfilePath = getCaddyfilePath(options.cwd);
|
|
3273
3316
|
let caddyfileRemoved = false;
|
|
3274
|
-
if (options.removeCaddyfile &&
|
|
3317
|
+
if (options.removeCaddyfile && existsSync9(caddyfilePath)) {
|
|
3275
3318
|
unlinkSync(caddyfilePath);
|
|
3276
3319
|
caddyfileRemoved = true;
|
|
3277
3320
|
}
|