@hamedb89/localghost 0.1.6 → 0.1.8
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 +55 -6
- package/apps/macos-widget/LocalghostWidget.swift +218 -0
- package/apps/macos-widget/build.sh +53 -0
- package/dist/cli.js +224 -45
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +10 -2
- package/dist/index.js +85 -19
- package/dist/index.js.map +1 -1
- package/dist/vite.d.ts +2 -0
- package/dist/vite.js +76 -21
- package/dist/vite.js.map +1 -1
- package/docs/localghost.1.md +20 -5
- package/docs/macos-widget.md +46 -0
- package/package.json +3 -1
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 existsSync7, readFileSync as readFileSync6, unlinkSync } from "fs";
|
|
5
5
|
import { Command, InvalidArgumentError } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/activity.ts
|
|
@@ -280,6 +280,16 @@ function startCaddy(path) {
|
|
|
280
280
|
stdio: "inherit"
|
|
281
281
|
});
|
|
282
282
|
}
|
|
283
|
+
async function trustCaddy(path) {
|
|
284
|
+
await execa("caddy", ["trust", "--config", path], {
|
|
285
|
+
cwd: dirname3(path),
|
|
286
|
+
stdio: "inherit"
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/context.ts
|
|
291
|
+
import { existsSync as existsSync3 } from "fs";
|
|
292
|
+
import { pathToFileURL } from "url";
|
|
283
293
|
|
|
284
294
|
// src/port.ts
|
|
285
295
|
import { createServer } from "net";
|
|
@@ -308,6 +318,11 @@ async function findAvailablePort(startPort, options = {}) {
|
|
|
308
318
|
}
|
|
309
319
|
|
|
310
320
|
// src/context.ts
|
|
321
|
+
var LOCALGHOST_PROJECT_CONFIG_FILES = [
|
|
322
|
+
"localghost.config.mjs",
|
|
323
|
+
"localghost.config.js",
|
|
324
|
+
"localghost.config.cjs"
|
|
325
|
+
];
|
|
311
326
|
function parsePort(value) {
|
|
312
327
|
if (!value) return void 0;
|
|
313
328
|
const port = Number.parseInt(value, 10);
|
|
@@ -321,6 +336,11 @@ function envDynamicPort() {
|
|
|
321
336
|
if (!value) return void 0;
|
|
322
337
|
return ["1", "true", "yes", "on"].includes(value.toLowerCase());
|
|
323
338
|
}
|
|
339
|
+
function envHttps() {
|
|
340
|
+
const value = process.env.LOCALGHOST_HTTPS;
|
|
341
|
+
if (!value) return void 0;
|
|
342
|
+
return ["1", "true", "yes", "on"].includes(value.toLowerCase());
|
|
343
|
+
}
|
|
324
344
|
function readOptionsFromContext(options) {
|
|
325
345
|
return {
|
|
326
346
|
cwd: options.cwd ?? process.cwd(),
|
|
@@ -338,22 +358,58 @@ function withRuntimePort(entries, requestedPort, port) {
|
|
|
338
358
|
function uniqueHosts(entries) {
|
|
339
359
|
return [...new Set(entries.map((entry) => entry.host))];
|
|
340
360
|
}
|
|
361
|
+
function isAliasableHost(host) {
|
|
362
|
+
return host.includes(".") && !host.startsWith("www.") && !host.includes(":");
|
|
363
|
+
}
|
|
364
|
+
function getDefaultWwwAlias(host) {
|
|
365
|
+
return isAliasableHost(host) ? `www.${host}` : null;
|
|
366
|
+
}
|
|
367
|
+
function addDefaultWwwAliases(entries) {
|
|
368
|
+
const seen = new Set(entries.map((entry) => entry.host));
|
|
369
|
+
const aliases = [];
|
|
370
|
+
for (const entry of entries) {
|
|
371
|
+
const alias = getDefaultWwwAlias(entry.host);
|
|
372
|
+
if (alias && !seen.has(alias)) {
|
|
373
|
+
aliases.push({ host: alias, port: entry.port, target: `127.0.0.1:${entry.port}` });
|
|
374
|
+
seen.add(alias);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return [...entries, ...aliases];
|
|
378
|
+
}
|
|
379
|
+
function defined(input2) {
|
|
380
|
+
return Object.fromEntries(Object.entries(input2).filter(([, value]) => typeof value !== "undefined"));
|
|
381
|
+
}
|
|
382
|
+
async function readProjectConfig(cwd, configFile) {
|
|
383
|
+
if (configFile === false) return {};
|
|
384
|
+
const candidates = configFile ? [configFile] : LOCALGHOST_PROJECT_CONFIG_FILES;
|
|
385
|
+
const path = candidates.map((candidate) => resolveDevHostsPath({ cwd, fileName: candidate }).path).find((candidate) => existsSync3(candidate));
|
|
386
|
+
if (!path) return {};
|
|
387
|
+
const imported = await import(`${pathToFileURL(path).href}?localghost=${Date.now()}`);
|
|
388
|
+
const config = imported.default ?? imported;
|
|
389
|
+
return { config, path };
|
|
390
|
+
}
|
|
341
391
|
async function resolveLocalghostContext(options = {}) {
|
|
342
392
|
const cwd = options.cwd ?? process.cwd();
|
|
343
|
-
const
|
|
393
|
+
const projectConfig = await readProjectConfig(cwd, options.localghostConfig);
|
|
394
|
+
const merged = {
|
|
395
|
+
...projectConfig.config,
|
|
396
|
+
...defined(options)
|
|
397
|
+
};
|
|
398
|
+
const readOptions = readOptionsFromContext({ ...merged, cwd });
|
|
344
399
|
const resolvedPath = resolveDevHostsPath(readOptions);
|
|
345
400
|
const configEntries = readDevHosts(readOptions);
|
|
346
|
-
const requestedPort =
|
|
347
|
-
const dynamicPort =
|
|
348
|
-
const bindHost =
|
|
401
|
+
const requestedPort = merged.port ?? envPort() ?? configEntries[0]?.port ?? 5173;
|
|
402
|
+
const dynamicPort = merged.dynamicPort ?? envDynamicPort() ?? false;
|
|
403
|
+
const bindHost = merged.bindHost ?? "127.0.0.1";
|
|
349
404
|
const probeHost = typeof bindHost === "string" ? bindHost : "127.0.0.1";
|
|
350
405
|
const port = dynamicPort ? await findAvailablePort(requestedPort, { host: probeHost }) : requestedPort;
|
|
351
|
-
const
|
|
406
|
+
const wwwAlias = merged.wwwAlias ?? true;
|
|
407
|
+
const entries = wwwAlias ? addDefaultWwwAliases(withRuntimePort(configEntries, requestedPort, port)) : withRuntimePort(configEntries, requestedPort, port);
|
|
352
408
|
const hosts = uniqueHosts(entries);
|
|
353
|
-
const primaryHost =
|
|
409
|
+
const primaryHost = merged.primaryHost ?? entries.find((entry) => entry.port === port)?.host ?? hosts[0] ?? `${sanitizeProjectName(getProjectName(cwd))}.localhost`;
|
|
354
410
|
return {
|
|
355
411
|
cwd,
|
|
356
|
-
projectName: sanitizeProjectName(
|
|
412
|
+
projectName: sanitizeProjectName(merged.project ?? getProjectName(cwd)),
|
|
357
413
|
readOptions,
|
|
358
414
|
configPath: resolvedPath.path,
|
|
359
415
|
configFileName: resolvedPath.fileName,
|
|
@@ -365,7 +421,9 @@ async function resolveLocalghostContext(options = {}) {
|
|
|
365
421
|
dynamicPort,
|
|
366
422
|
bindHost,
|
|
367
423
|
primaryHost,
|
|
368
|
-
https:
|
|
424
|
+
https: merged.https ?? envHttps() ?? false,
|
|
425
|
+
wwwAlias,
|
|
426
|
+
...projectConfig.path ? { projectConfigPath: projectConfig.path } : {}
|
|
369
427
|
};
|
|
370
428
|
}
|
|
371
429
|
|
|
@@ -490,11 +548,11 @@ async function removeSystemHosts(projectName) {
|
|
|
490
548
|
}
|
|
491
549
|
|
|
492
550
|
// src/init.ts
|
|
493
|
-
import { existsSync as
|
|
551
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
494
552
|
import { join as join5 } from "path";
|
|
495
553
|
function detectPackageManager(cwd = process.cwd()) {
|
|
496
|
-
if (
|
|
497
|
-
if (
|
|
554
|
+
if (existsSync4(join5(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
555
|
+
if (existsSync4(join5(cwd, "yarn.lock"))) return "yarn";
|
|
498
556
|
return "npm";
|
|
499
557
|
}
|
|
500
558
|
function packageRunCommand(packageManager, script) {
|
|
@@ -538,6 +596,7 @@ function updatePackageScripts(packageJsonPath, configFile) {
|
|
|
538
596
|
"localghost:proxy:https": scripts["localghost:proxy:https"] ?? `localghost dev${configFlag} --https`,
|
|
539
597
|
"localghost:run": scripts["localghost:run"] ?? `localghost run${configFlag} --`,
|
|
540
598
|
"localghost:ready": scripts["localghost:ready"] ?? `localghost status${configFlag} --ready`,
|
|
599
|
+
"localghost:trust": scripts["localghost:trust"] ?? `localghost trust${configFlag}`,
|
|
541
600
|
"localghost:ps": scripts["localghost:ps"] ?? "localghost ps",
|
|
542
601
|
"localghost:print": scripts["localghost:print"] ?? `localghost print${configFlag}`,
|
|
543
602
|
"localghost:routes": scripts["localghost:routes"] ?? `localghost routes${configFlag}`,
|
|
@@ -566,7 +625,7 @@ function initLocalghost(options = {}) {
|
|
|
566
625
|
const packageManager = options.packageManager ?? detectPackageManager(cwd);
|
|
567
626
|
const configFile = options.configFile ?? LOCALGHOST_CONFIG_FILE;
|
|
568
627
|
const configPath = join5(cwd, configFile);
|
|
569
|
-
const configExists =
|
|
628
|
+
const configExists = existsSync4(configPath);
|
|
570
629
|
if (configExists && !options.force) {
|
|
571
630
|
return {
|
|
572
631
|
configPath,
|
|
@@ -587,7 +646,7 @@ function initLocalghost(options = {}) {
|
|
|
587
646
|
return {
|
|
588
647
|
configPath,
|
|
589
648
|
configCreated: true,
|
|
590
|
-
...
|
|
649
|
+
...existsSync4(packageJsonPath) ? { packageJsonPath } : {},
|
|
591
650
|
packageJsonChanged,
|
|
592
651
|
packageManager,
|
|
593
652
|
nextSteps: [
|
|
@@ -644,7 +703,7 @@ function formatDomainRoutes(entries, options = {}) {
|
|
|
644
703
|
}
|
|
645
704
|
|
|
646
705
|
// src/state.ts
|
|
647
|
-
import { existsSync as
|
|
706
|
+
import { existsSync as existsSync5 } from "fs";
|
|
648
707
|
import { join as join6 } from "path";
|
|
649
708
|
var LOCALGHOST_STATE_FILE = "ops/local/localghost-state.json";
|
|
650
709
|
function getLocalghostStatePath(cwd = process.cwd()) {
|
|
@@ -652,22 +711,27 @@ function getLocalghostStatePath(cwd = process.cwd()) {
|
|
|
652
711
|
}
|
|
653
712
|
function readLocalghostState(cwd = process.cwd()) {
|
|
654
713
|
const path = getLocalghostStatePath(cwd);
|
|
655
|
-
if (!
|
|
714
|
+
if (!existsSync5(path)) return null;
|
|
656
715
|
return JSON.parse(readTextFile(path));
|
|
657
716
|
}
|
|
658
717
|
function writeLocalghostState(cwd, state) {
|
|
659
718
|
const path = getLocalghostStatePath(cwd);
|
|
660
|
-
writeTextFile(path, `${JSON.stringify({ version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
719
|
+
writeTextFile(path, `${JSON.stringify({ ...state, version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
|
|
661
720
|
`);
|
|
662
721
|
return path;
|
|
663
722
|
}
|
|
723
|
+
function patchLocalghostState(cwd, patch) {
|
|
724
|
+
const current = readLocalghostState(cwd);
|
|
725
|
+
if (!current) return null;
|
|
726
|
+
return writeLocalghostState(cwd, { ...current, ...patch });
|
|
727
|
+
}
|
|
664
728
|
|
|
665
729
|
// src/update-check.ts
|
|
666
|
-
import { existsSync as
|
|
730
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
667
731
|
import { homedir as homedir2 } from "os";
|
|
668
732
|
import { dirname as dirname4, join as join7 } from "path";
|
|
669
733
|
var LOCALGHOST_PACKAGE_NAME = "@hamedb89/localghost";
|
|
670
|
-
var LOCALGHOST_VERSION = "0.1.
|
|
734
|
+
var LOCALGHOST_VERSION = "0.1.8";
|
|
671
735
|
var UPDATE_CHECK_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
672
736
|
var UPDATE_CHECK_NOTIFY_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
673
737
|
var UPDATE_CHECK_TIMEOUT_MS = 900;
|
|
@@ -683,7 +747,7 @@ function getUpdateCheckCachePath(env = process.env) {
|
|
|
683
747
|
return join7(cacheRoot, "localghost", "update-check.json");
|
|
684
748
|
}
|
|
685
749
|
function readCache(path = getUpdateCheckCachePath()) {
|
|
686
|
-
if (!
|
|
750
|
+
if (!existsSync6(path)) return null;
|
|
687
751
|
try {
|
|
688
752
|
return JSON.parse(readFileSync5(path, "utf8"));
|
|
689
753
|
} catch {
|
|
@@ -864,6 +928,15 @@ function parseBooleanLike(value) {
|
|
|
864
928
|
if (["0", "false", "no", "n", "off"].includes(normalized)) return false;
|
|
865
929
|
throw new InvalidArgumentError("Value must be yes or no.");
|
|
866
930
|
}
|
|
931
|
+
function contextOptionsFromCli(options) {
|
|
932
|
+
return {
|
|
933
|
+
cwd: options.cwd,
|
|
934
|
+
...options.project ? { project: options.project } : {},
|
|
935
|
+
...options.config && options.config.length > 0 ? { configFiles: options.config } : {},
|
|
936
|
+
...options.configPattern ? { configPattern: options.configPattern } : {},
|
|
937
|
+
...useHttps(options) ? { https: true } : {}
|
|
938
|
+
};
|
|
939
|
+
}
|
|
867
940
|
function readOptionsFromCli(options) {
|
|
868
941
|
return {
|
|
869
942
|
cwd: options.cwd,
|
|
@@ -880,10 +953,22 @@ async function assertCaddyReady() {
|
|
|
880
953
|
"Localghost will not install it for you. No surprise spells."
|
|
881
954
|
].join("\n"));
|
|
882
955
|
}
|
|
956
|
+
function existingTrustMarkers(cwd) {
|
|
957
|
+
const state = readLocalghostState(cwd);
|
|
958
|
+
return {
|
|
959
|
+
...state?.caddyTrustedAt ? { caddyTrustedAt: state.caddyTrustedAt } : {},
|
|
960
|
+
...state?.caddyTrustPromptedAt ? { caddyTrustPromptedAt: state.caddyTrustPromptedAt } : {}
|
|
961
|
+
};
|
|
962
|
+
}
|
|
883
963
|
function explainHostsPassword() {
|
|
884
964
|
console.log("Localghost may ask for your password to update its managed block in /etc/hosts.");
|
|
885
965
|
console.log("It will only touch the lines between # localghost:start and # localghost:end.");
|
|
886
966
|
}
|
|
967
|
+
function explainTrustPassword() {
|
|
968
|
+
console.log("Localghost can trust Caddy's local HTTPS CA so browsers stop showing local certificate warnings.");
|
|
969
|
+
console.log("macOS may ask for your password to add that local CA to Keychain.");
|
|
970
|
+
console.log("This only affects Caddy's local development certificates on this machine.");
|
|
971
|
+
}
|
|
887
972
|
function useHttps(options) {
|
|
888
973
|
return options.https === true || options.ssl === true;
|
|
889
974
|
}
|
|
@@ -895,10 +980,10 @@ function getSetupCommand(options) {
|
|
|
895
980
|
return `localghost setup${configFlags}${options.https ? " --https" : ""}`;
|
|
896
981
|
}
|
|
897
982
|
function getSetupReadiness(options) {
|
|
898
|
-
const projectName = sanitizeProjectName(options.project ?? getProjectName(options.cwd));
|
|
983
|
+
const projectName = sanitizeProjectName(options.projectName ?? options.project ?? getProjectName(options.cwd));
|
|
899
984
|
const readOptions = readOptionsFromCli(options);
|
|
900
|
-
const entries = readDevHosts(readOptions);
|
|
901
|
-
const configPath = resolveDevHostsPath(readOptions).path;
|
|
985
|
+
const entries = options.entries ?? readDevHosts(readOptions);
|
|
986
|
+
const configPath = options.configPath ?? resolveDevHostsPath(readOptions).path;
|
|
902
987
|
const caddyfilePath = getCaddyfilePath(options.cwd);
|
|
903
988
|
const statePath = getLocalghostStatePath(options.cwd);
|
|
904
989
|
const state = readLocalghostState(options.cwd);
|
|
@@ -923,7 +1008,7 @@ function getSetupReadiness(options) {
|
|
|
923
1008
|
reasons.push(`Could not read ${hostsPath}: ${message}`);
|
|
924
1009
|
}
|
|
925
1010
|
if (!options.ignoreCaddyfile) {
|
|
926
|
-
if (!
|
|
1011
|
+
if (!existsSync7(caddyfilePath)) {
|
|
927
1012
|
reasons.push(`Missing Caddyfile at ${caddyfilePath}.`);
|
|
928
1013
|
} else {
|
|
929
1014
|
const expectedCaddyfile = renderCaddyfile(entries, { https });
|
|
@@ -959,9 +1044,42 @@ async function runSetupFromReadiness(cwd, https, readiness) {
|
|
|
959
1044
|
...hostsResult.tempPath ? { hostsTempPath: hostsResult.tempPath } : {},
|
|
960
1045
|
caddyfilePath,
|
|
961
1046
|
caddyHttps: https,
|
|
1047
|
+
...existingTrustMarkers(cwd),
|
|
962
1048
|
entries: readiness.entries
|
|
963
1049
|
});
|
|
964
1050
|
}
|
|
1051
|
+
function wait(ms) {
|
|
1052
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1053
|
+
}
|
|
1054
|
+
async function runTrust(cwd, caddyfilePath) {
|
|
1055
|
+
await wait(350);
|
|
1056
|
+
try {
|
|
1057
|
+
await trustCaddy(caddyfilePath);
|
|
1058
|
+
} catch {
|
|
1059
|
+
await wait(750);
|
|
1060
|
+
await trustCaddy(caddyfilePath);
|
|
1061
|
+
}
|
|
1062
|
+
patchLocalghostState(cwd, { caddyTrustedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1063
|
+
console.log("Local HTTPS trust is ready.");
|
|
1064
|
+
}
|
|
1065
|
+
async function maybeTrustCaddy(options) {
|
|
1066
|
+
if (!options.https) return;
|
|
1067
|
+
const state = readLocalghostState(options.cwd);
|
|
1068
|
+
if (!options.trust && state?.caddyTrustedAt) return;
|
|
1069
|
+
let shouldTrust = options.trust === true;
|
|
1070
|
+
if (!shouldTrust) {
|
|
1071
|
+
if (state?.caddyTrustPromptedAt || !canPrompt()) return;
|
|
1072
|
+
explainTrustPassword();
|
|
1073
|
+
shouldTrust = await confirm("Trust local HTTPS certificates now?", true);
|
|
1074
|
+
}
|
|
1075
|
+
if (!shouldTrust) {
|
|
1076
|
+
patchLocalghostState(options.cwd, { caddyTrustPromptedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1077
|
+
console.log("Okay. Localghost will still run HTTPS, but the browser may show a certificate warning.");
|
|
1078
|
+
console.log("Run localghost trust when you want to trust Caddy's local CA.");
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
await runTrust(options.cwd, options.caddyfilePath);
|
|
1082
|
+
}
|
|
965
1083
|
function maybePid(pid) {
|
|
966
1084
|
return typeof pid === "number" && Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
967
1085
|
}
|
|
@@ -1074,11 +1192,11 @@ program.command("update").description("Check npm for a newer localghost release"
|
|
|
1074
1192
|
program.command("setup").description("Update /etc/hosts and generate/validate Caddyfile").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", "Generate a local HTTPS Caddy proxy with Caddy local certificates").option("--ssl", "Alias for --https").action(async (options) => {
|
|
1075
1193
|
assertLocalDevelopment("setup");
|
|
1076
1194
|
await assertCaddyReady();
|
|
1077
|
-
const
|
|
1078
|
-
const
|
|
1079
|
-
const
|
|
1080
|
-
const configPath =
|
|
1081
|
-
const entries =
|
|
1195
|
+
const context = await resolveLocalghostContext({ ...contextOptionsFromCli(options), dynamicPort: false });
|
|
1196
|
+
const https = context.https;
|
|
1197
|
+
const projectName = context.projectName;
|
|
1198
|
+
const configPath = context.configPath;
|
|
1199
|
+
const entries = context.entries;
|
|
1082
1200
|
warnAboutLocalMdns(entries);
|
|
1083
1201
|
logDomainRoutes(entries, { https });
|
|
1084
1202
|
explainHostsPassword();
|
|
@@ -1100,6 +1218,7 @@ program.command("setup").description("Update /etc/hosts and generate/validate Ca
|
|
|
1100
1218
|
...hostsResult.tempPath ? { hostsTempPath: hostsResult.tempPath } : {},
|
|
1101
1219
|
caddyfilePath: caddyfile,
|
|
1102
1220
|
caddyHttps: https,
|
|
1221
|
+
...existingTrustMarkers(options.cwd),
|
|
1103
1222
|
entries
|
|
1104
1223
|
});
|
|
1105
1224
|
console.log(`Generated ${caddyfile}`);
|
|
@@ -1107,6 +1226,20 @@ program.command("setup").description("Update /etc/hosts and generate/validate Ca
|
|
|
1107
1226
|
console.log(`State ${statePath}`);
|
|
1108
1227
|
console.log("Setup complete.");
|
|
1109
1228
|
});
|
|
1229
|
+
program.command("trust").description("Trust Caddy's local HTTPS CA for this project's HTTPS proxy").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", "Use HTTPS mode for the Caddyfile").option("--ssl", "Alias for --https").action(async (options) => {
|
|
1230
|
+
assertLocalDevelopment("trust");
|
|
1231
|
+
await assertCaddyReady();
|
|
1232
|
+
const context = await resolveLocalghostContext({ ...contextOptionsFromCli(options), dynamicPort: false });
|
|
1233
|
+
if (!context.https) {
|
|
1234
|
+
throw new Error("Localghost HTTPS is not enabled for this context. Set https: true in localghost.config.mjs or pass --https.");
|
|
1235
|
+
}
|
|
1236
|
+
warnAboutLocalMdns(context.entries);
|
|
1237
|
+
logDomainRoutes(context.entries, { https: true });
|
|
1238
|
+
explainTrustPassword();
|
|
1239
|
+
const caddyfile = await writeCaddyfile(context.entries, options.cwd, { https: true });
|
|
1240
|
+
await validateCaddyfile(caddyfile);
|
|
1241
|
+
await runTrust(options.cwd, caddyfile);
|
|
1242
|
+
});
|
|
1110
1243
|
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) => {
|
|
1111
1244
|
assertLocalDevelopment("reset");
|
|
1112
1245
|
const projectName = sanitizeProjectName(options.project ?? getProjectName(options.cwd));
|
|
@@ -1114,13 +1247,13 @@ program.command("reset").description("Remove Localghost setup state without dele
|
|
|
1114
1247
|
const statePath = getLocalghostStatePath(options.cwd);
|
|
1115
1248
|
explainHostsPassword();
|
|
1116
1249
|
const hostsResult = await removeSystemHosts(projectName);
|
|
1117
|
-
if (
|
|
1250
|
+
if (existsSync7(caddyfilePath)) {
|
|
1118
1251
|
unlinkSync(caddyfilePath);
|
|
1119
1252
|
console.log(`Removed ${caddyfilePath}`);
|
|
1120
1253
|
} else {
|
|
1121
1254
|
console.log(`${caddyfilePath} was not present`);
|
|
1122
1255
|
}
|
|
1123
|
-
if (
|
|
1256
|
+
if (existsSync7(statePath)) {
|
|
1124
1257
|
unlinkSync(statePath);
|
|
1125
1258
|
console.log(`Removed ${statePath}`);
|
|
1126
1259
|
} else {
|
|
@@ -1140,7 +1273,7 @@ program.command("teardown").description("Remove Localghost's managed /etc/hosts
|
|
|
1140
1273
|
const hostsResult = await removeSystemHosts(projectName);
|
|
1141
1274
|
const caddyfilePath = getCaddyfilePath(options.cwd);
|
|
1142
1275
|
let caddyfileRemoved = false;
|
|
1143
|
-
if (options.removeCaddyfile &&
|
|
1276
|
+
if (options.removeCaddyfile && existsSync7(caddyfilePath)) {
|
|
1144
1277
|
unlinkSync(caddyfilePath);
|
|
1145
1278
|
caddyfileRemoved = true;
|
|
1146
1279
|
}
|
|
@@ -1164,10 +1297,17 @@ program.command("teardown").description("Remove Localghost's managed /etc/hosts
|
|
|
1164
1297
|
}
|
|
1165
1298
|
console.log(`State ${statePath}`);
|
|
1166
1299
|
});
|
|
1167
|
-
program.command("status").description("Print Localghost's project-local state file").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("--ready", "Exit non-zero when setup is missing or stale").option("--https", "Check setup readiness for HTTPS mode").option("--ssl", "Alias for --https").option("--json", "Print raw JSON").action((options) => {
|
|
1300
|
+
program.command("status").description("Print Localghost's project-local state file").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("--ready", "Exit non-zero when setup is missing or stale").option("--https", "Check setup readiness for HTTPS mode").option("--ssl", "Alias for --https").option("--json", "Print raw JSON").action(async (options) => {
|
|
1168
1301
|
const state = readLocalghostState(options.cwd);
|
|
1169
1302
|
const statePath = getLocalghostStatePath(options.cwd);
|
|
1170
|
-
const
|
|
1303
|
+
const context = await resolveLocalghostContext({ ...contextOptionsFromCli(options), dynamicPort: false });
|
|
1304
|
+
const readiness = getSetupReadiness({
|
|
1305
|
+
...options,
|
|
1306
|
+
https: context.https,
|
|
1307
|
+
entries: context.entries,
|
|
1308
|
+
configPath: context.configPath,
|
|
1309
|
+
projectName: context.projectName
|
|
1310
|
+
});
|
|
1171
1311
|
if (options.json) {
|
|
1172
1312
|
console.log(JSON.stringify({ state, setup: readiness }, null, 2));
|
|
1173
1313
|
return;
|
|
@@ -1183,6 +1323,8 @@ program.command("status").description("Print Localghost's project-local state fi
|
|
|
1183
1323
|
if (state.hostsPath) console.log(`Hosts: ${state.hostsPath}`);
|
|
1184
1324
|
if (state.caddyfilePath) console.log(`Caddyfile: ${state.caddyfilePath}`);
|
|
1185
1325
|
if (typeof state.caddyHttps === "boolean") console.log(`Mode: ${state.caddyHttps ? "HTTPS" : "HTTP"}`);
|
|
1326
|
+
if (state.caddyTrustedAt) console.log(`HTTPS trust: yes (${state.caddyTrustedAt})`);
|
|
1327
|
+
if (!state.caddyTrustedAt && state.caddyTrustPromptedAt) console.log(`HTTPS trust: not enabled (asked ${state.caddyTrustPromptedAt})`);
|
|
1186
1328
|
if (typeof state.caddyfileRemoved === "boolean") console.log(`Caddyfile removed: ${state.caddyfileRemoved}`);
|
|
1187
1329
|
}
|
|
1188
1330
|
if (readiness.ready) {
|
|
@@ -1198,16 +1340,23 @@ program.command("status").description("Print Localghost's project-local state fi
|
|
|
1198
1340
|
process.exitCode = 1;
|
|
1199
1341
|
}
|
|
1200
1342
|
});
|
|
1201
|
-
program.command("routes").description("Print domain to upstream routes").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("--http", "Print domain URLs with http instead of https").option("--https", "Print domain URLs with https").option("--ssl", "Alias for --https").action((options) => {
|
|
1202
|
-
const
|
|
1203
|
-
warnAboutLocalMdns(entries);
|
|
1204
|
-
console.log(formatDomainRoutes(entries, { https: options.http ? false :
|
|
1343
|
+
program.command("routes").description("Print domain to upstream routes").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("--http", "Print domain URLs with http instead of https").option("--https", "Print domain URLs with https").option("--ssl", "Alias for --https").action(async (options) => {
|
|
1344
|
+
const context = await resolveLocalghostContext({ ...contextOptionsFromCli(options), dynamicPort: false });
|
|
1345
|
+
warnAboutLocalMdns(context.entries);
|
|
1346
|
+
console.log(formatDomainRoutes(context.entries, { https: options.http ? false : context.https }));
|
|
1205
1347
|
});
|
|
1206
|
-
program.command("dev").description("Run the Localghost Caddy proxy after setup").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", "Run a local HTTPS proxy with Caddy local certificates").option("--ssl", "Alias for --https").option("--setup", "Run setup before starting the proxy when setup is missing or stale").action(async (options) => {
|
|
1348
|
+
program.command("dev").description("Run the Localghost Caddy proxy after setup").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", "Run a local HTTPS proxy with Caddy local certificates").option("--ssl", "Alias for --https").option("--setup", "Run setup before starting the proxy when setup is missing or stale").option("--trust", "Trust Caddy's local HTTPS CA before starting the proxy").action(async (options) => {
|
|
1207
1349
|
assertLocalDevelopment("dev");
|
|
1208
1350
|
await assertCaddyReady();
|
|
1209
|
-
const
|
|
1210
|
-
const
|
|
1351
|
+
const context = await resolveLocalghostContext({ ...contextOptionsFromCli(options), dynamicPort: false });
|
|
1352
|
+
const https = context.https;
|
|
1353
|
+
const readiness = getSetupReadiness({
|
|
1354
|
+
...options,
|
|
1355
|
+
https,
|
|
1356
|
+
entries: context.entries,
|
|
1357
|
+
configPath: context.configPath,
|
|
1358
|
+
projectName: context.projectName
|
|
1359
|
+
});
|
|
1211
1360
|
if (!readiness.ready) {
|
|
1212
1361
|
if (!options.setup) {
|
|
1213
1362
|
throw new Error(
|
|
@@ -1233,6 +1382,7 @@ program.command("dev").description("Run the Localghost Caddy proxy after setup")
|
|
|
1233
1382
|
...hostsResult.tempPath ? { hostsTempPath: hostsResult.tempPath } : {},
|
|
1234
1383
|
caddyfilePath,
|
|
1235
1384
|
caddyHttps: https,
|
|
1385
|
+
...existingTrustMarkers(options.cwd),
|
|
1236
1386
|
entries: readiness.entries
|
|
1237
1387
|
});
|
|
1238
1388
|
}
|
|
@@ -1241,6 +1391,17 @@ program.command("dev").description("Run the Localghost Caddy proxy after setup")
|
|
|
1241
1391
|
const caddyfile = await writeCaddyfile(readiness.entries, options.cwd, { https });
|
|
1242
1392
|
await validateCaddyfile(caddyfile);
|
|
1243
1393
|
const caddy = startCaddy(caddyfile);
|
|
1394
|
+
try {
|
|
1395
|
+
await maybeTrustCaddy({
|
|
1396
|
+
cwd: options.cwd,
|
|
1397
|
+
https,
|
|
1398
|
+
caddyfilePath: caddyfile,
|
|
1399
|
+
...typeof options.trust === "boolean" ? { trust: options.trust } : {}
|
|
1400
|
+
});
|
|
1401
|
+
} catch (error) {
|
|
1402
|
+
if (!caddy.killed) caddy.kill("SIGINT");
|
|
1403
|
+
throw error;
|
|
1404
|
+
}
|
|
1244
1405
|
const caddyPid = maybePid(caddy.pid);
|
|
1245
1406
|
const runRecord = registerLocalghostRun({
|
|
1246
1407
|
mode: "dev",
|
|
@@ -1259,20 +1420,27 @@ program.command("dev").description("Run the Localghost Caddy proxy after setup")
|
|
|
1259
1420
|
cleanupRun();
|
|
1260
1421
|
}
|
|
1261
1422
|
});
|
|
1262
|
-
program.command("run").description("Run Caddy and a dev command from the same Localghost context").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("--port <number>", "Initial app port", parsePort2).option("--https", "Run a local HTTPS proxy with Caddy local certificates").option("--ssl", "Alias for --https").option("--setup", "Run setup before starting when setup is missing or stale").option("--dynamic-port [yes|no]", "Use the requested port if free, otherwise continue upward", parseBooleanLike, false).argument("<command...>", "Command to run after --, for example: localghost run -- vite").action(async (command, options) => {
|
|
1423
|
+
program.command("run").description("Run Caddy and a dev command from the same Localghost context").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("--port <number>", "Initial app port", parsePort2).option("--https", "Run a local HTTPS proxy with Caddy local certificates").option("--ssl", "Alias for --https").option("--setup", "Run setup before starting when setup is missing or stale").option("--trust", "Trust Caddy's local HTTPS CA before starting the child command").option("--dynamic-port [yes|no]", "Use the requested port if free, otherwise continue upward", parseBooleanLike, false).argument("<command...>", "Command to run after --, for example: localghost run -- vite").action(async (command, options) => {
|
|
1263
1424
|
assertLocalDevelopment("run");
|
|
1264
1425
|
await assertCaddyReady();
|
|
1265
|
-
const https = useHttps(options);
|
|
1266
1426
|
const context = await resolveLocalghostContext({
|
|
1267
1427
|
cwd: options.cwd,
|
|
1268
1428
|
...options.project ? { project: options.project } : {},
|
|
1269
1429
|
...options.config && options.config.length > 0 ? { configFiles: options.config } : {},
|
|
1270
1430
|
...options.configPattern ? { configPattern: options.configPattern } : {},
|
|
1271
1431
|
...options.port ? { port: options.port } : {},
|
|
1272
|
-
https,
|
|
1432
|
+
...useHttps(options) ? { https: true } : {},
|
|
1273
1433
|
...typeof options.dynamicPort === "boolean" ? { dynamicPort: options.dynamicPort } : {}
|
|
1274
1434
|
});
|
|
1275
|
-
const
|
|
1435
|
+
const https = context.https;
|
|
1436
|
+
const readiness = getSetupReadiness({
|
|
1437
|
+
...options,
|
|
1438
|
+
https,
|
|
1439
|
+
ignoreCaddyfile: true,
|
|
1440
|
+
entries: context.entries,
|
|
1441
|
+
configPath: context.configPath,
|
|
1442
|
+
projectName: context.projectName
|
|
1443
|
+
});
|
|
1276
1444
|
if (!readiness.ready) {
|
|
1277
1445
|
const shouldSetup = options.setup === true || canPrompt() && await confirm("Run caddy:setup now?", true);
|
|
1278
1446
|
if (!shouldSetup) {
|
|
@@ -1298,6 +1466,17 @@ program.command("run").description("Run Caddy and a dev command from the same Lo
|
|
|
1298
1466
|
const caddyExit = caddy.catch((error) => {
|
|
1299
1467
|
if (!caddy.killed) throw error;
|
|
1300
1468
|
});
|
|
1469
|
+
try {
|
|
1470
|
+
await maybeTrustCaddy({
|
|
1471
|
+
cwd: options.cwd,
|
|
1472
|
+
https,
|
|
1473
|
+
caddyfilePath: caddyfile,
|
|
1474
|
+
...typeof options.trust === "boolean" ? { trust: options.trust } : {}
|
|
1475
|
+
});
|
|
1476
|
+
} catch (error) {
|
|
1477
|
+
if (!caddy.killed) caddy.kill("SIGINT");
|
|
1478
|
+
throw error;
|
|
1479
|
+
}
|
|
1301
1480
|
const [binary, ...args] = command;
|
|
1302
1481
|
if (!binary) {
|
|
1303
1482
|
throw new Error("Missing command. Use: localghost run -- vite");
|