@devrouter/cli 0.0.43 → 0.0.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/devrouter.js +114 -35
- package/package.json +1 -1
- package/upgrade-prompts/0.0.44.md +30 -0
package/dist/devrouter.js
CHANGED
|
@@ -2509,7 +2509,7 @@ function loadRepoConfig(repoPath) {
|
|
|
2509
2509
|
const config = parseConfig(parsed ?? {}, configPath);
|
|
2510
2510
|
const requiredVersion = config.devrouter?.version;
|
|
2511
2511
|
if (requiredVersion && !hasWarnedVersionMismatch) {
|
|
2512
|
-
const cliVersion = true ? "0.0.
|
|
2512
|
+
const cliVersion = true ? "0.0.44" : "0.0.0-dev";
|
|
2513
2513
|
if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
|
|
2514
2514
|
hasWarnedVersionMismatch = true;
|
|
2515
2515
|
process.stderr.write(
|
|
@@ -5964,6 +5964,9 @@ function parseCertificateDnsHosts(pem) {
|
|
|
5964
5964
|
function isHostCoveredByCertificateHost(host, certificateHost) {
|
|
5965
5965
|
const normalizedHost = normalizeHost(host);
|
|
5966
5966
|
const normalizedCertificateHost = normalizeHost(certificateHost);
|
|
5967
|
+
if (normalizedHost === normalizedCertificateHost) {
|
|
5968
|
+
return true;
|
|
5969
|
+
}
|
|
5967
5970
|
if (normalizedCertificateHost.startsWith("*.")) {
|
|
5968
5971
|
if (normalizedCertificateHost === "*.localhost") {
|
|
5969
5972
|
return false;
|
|
@@ -5975,7 +5978,7 @@ function isHostCoveredByCertificateHost(host, certificateHost) {
|
|
|
5975
5978
|
const wildcardPart = normalizedHost.slice(0, normalizedHost.length - suffix.length);
|
|
5976
5979
|
return wildcardPart.length > 0 && !wildcardPart.includes(".");
|
|
5977
5980
|
}
|
|
5978
|
-
return
|
|
5981
|
+
return false;
|
|
5979
5982
|
}
|
|
5980
5983
|
function findUncoveredCertificateHosts(requiredHosts, certificateHosts) {
|
|
5981
5984
|
const normalizedRequired = normalizeUniqueHosts(requiredHosts);
|
|
@@ -5986,22 +5989,54 @@ function findUncoveredCertificateHosts(requiredHosts, certificateHosts) {
|
|
|
5986
5989
|
)
|
|
5987
5990
|
);
|
|
5988
5991
|
}
|
|
5989
|
-
function
|
|
5992
|
+
function compactTLSCertificateHosts(hosts) {
|
|
5993
|
+
const normalizedHosts = normalizeUniqueHosts(hosts);
|
|
5994
|
+
const wildcardHosts = new Set(normalizedHosts.filter((host) => host.startsWith("*.")));
|
|
5995
|
+
const siblingGroups = /* @__PURE__ */ new Map();
|
|
5996
|
+
const compacted = new Set(wildcardHosts);
|
|
5997
|
+
for (const host of normalizedHosts) {
|
|
5998
|
+
if (host.startsWith("*.")) {
|
|
5999
|
+
continue;
|
|
6000
|
+
}
|
|
6001
|
+
const labels = host.split(".");
|
|
6002
|
+
if (labels.length < 3 || labels.at(-1) !== "localhost") {
|
|
6003
|
+
compacted.add(host);
|
|
6004
|
+
continue;
|
|
6005
|
+
}
|
|
6006
|
+
const suffix = labels.slice(1).join(".");
|
|
6007
|
+
const siblings = siblingGroups.get(suffix) ?? [];
|
|
6008
|
+
siblings.push(host);
|
|
6009
|
+
siblingGroups.set(suffix, siblings);
|
|
6010
|
+
}
|
|
6011
|
+
for (const [suffix, siblings] of siblingGroups) {
|
|
6012
|
+
const wildcard = `*.${suffix}`;
|
|
6013
|
+
if (siblings.length > 1 || wildcardHosts.has(wildcard)) {
|
|
6014
|
+
compacted.add(wildcard);
|
|
6015
|
+
continue;
|
|
6016
|
+
}
|
|
6017
|
+
compacted.add(siblings[0]);
|
|
6018
|
+
}
|
|
6019
|
+
const selectedWildcards = Array.from(compacted).filter((host) => host.startsWith("*."));
|
|
6020
|
+
return normalizeUniqueHosts(Array.from(compacted)).filter(
|
|
6021
|
+
(host) => host.startsWith("*.") || !selectedWildcards.some((wildcard) => isHostCoveredByCertificateHost(host, wildcard))
|
|
6022
|
+
);
|
|
6023
|
+
}
|
|
6024
|
+
function readCurrentCertificateHosts(options = {}) {
|
|
5990
6025
|
if (!import_node_fs16.default.existsSync(CERT_FILE)) {
|
|
5991
6026
|
return [];
|
|
5992
6027
|
}
|
|
5993
|
-
const pem = import_node_fs16.default.readFileSync(CERT_FILE, "utf-8");
|
|
5994
|
-
return parseCertificateDnsHosts(pem);
|
|
5995
|
-
}
|
|
5996
|
-
function currentCertificateHostsOrEmpty() {
|
|
5997
6028
|
try {
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6029
|
+
const pem = import_node_fs16.default.readFileSync(CERT_FILE, "utf-8");
|
|
6030
|
+
return parseCertificateDnsHosts(pem);
|
|
6031
|
+
} catch (error) {
|
|
6032
|
+
if (options.replaceMalformed) {
|
|
6033
|
+
return [];
|
|
6034
|
+
}
|
|
6035
|
+
throw error;
|
|
6001
6036
|
}
|
|
6002
6037
|
}
|
|
6003
6038
|
function buildDesiredTLSCertificateHosts(requestedHosts, existingCertificateHosts) {
|
|
6004
|
-
return
|
|
6039
|
+
return compactTLSCertificateHosts([
|
|
6005
6040
|
...DEFAULT_TLS_CERT_HOSTS,
|
|
6006
6041
|
...existingCertificateHosts,
|
|
6007
6042
|
...requestedHosts
|
|
@@ -6009,35 +6044,75 @@ function buildDesiredTLSCertificateHosts(requestedHosts, existingCertificateHost
|
|
|
6009
6044
|
}
|
|
6010
6045
|
function getTLSHostCoverage(hosts) {
|
|
6011
6046
|
const requiredHosts = normalizeUniqueHosts([...DEFAULT_TLS_CERT_HOSTS, ...hosts]);
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6047
|
+
return withFileLockSync(
|
|
6048
|
+
TLS_CERTIFICATE_LOCK_FILE,
|
|
6049
|
+
{ activity: "TLS certificate inspection", waitMs: TLS_CERTIFICATE_LOCK_WAIT_MS },
|
|
6050
|
+
() => {
|
|
6051
|
+
const certificateHosts = readCurrentCertificateHosts();
|
|
6052
|
+
const uncoveredHosts = findUncoveredCertificateHosts(requiredHosts, certificateHosts);
|
|
6053
|
+
return {
|
|
6054
|
+
requiredHosts,
|
|
6055
|
+
certificateHosts,
|
|
6056
|
+
uncoveredHosts
|
|
6057
|
+
};
|
|
6058
|
+
}
|
|
6059
|
+
);
|
|
6019
6060
|
}
|
|
6020
6061
|
async function applyTLSCertificate(options, installTrust) {
|
|
6021
6062
|
ensureRouterFiles();
|
|
6022
|
-
const
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6063
|
+
const result = withFileLockSync(
|
|
6064
|
+
TLS_CERTIFICATE_LOCK_FILE,
|
|
6065
|
+
{
|
|
6066
|
+
activity: "TLS certificate refresh",
|
|
6067
|
+
target: options.repoPath,
|
|
6068
|
+
waitMs: TLS_CERTIFICATE_LOCK_WAIT_MS
|
|
6069
|
+
},
|
|
6070
|
+
() => {
|
|
6071
|
+
const alreadyEnabled = isTLSEnabled();
|
|
6072
|
+
if (installTrust) {
|
|
6073
|
+
ensureMkcert();
|
|
6074
|
+
runOrThrow("mkcert", ["-install"]);
|
|
6075
|
+
} else {
|
|
6076
|
+
getMkcertRootCAPath({ repoPath: options.repoPath });
|
|
6077
|
+
}
|
|
6078
|
+
const existingCertificateHosts = readCurrentCertificateHosts({
|
|
6079
|
+
replaceMalformed: installTrust
|
|
6080
|
+
});
|
|
6081
|
+
const desiredHosts = buildDesiredTLSCertificateHosts(
|
|
6082
|
+
options.hosts ?? [],
|
|
6083
|
+
existingCertificateHosts
|
|
6084
|
+
);
|
|
6085
|
+
let certificateHosts = [];
|
|
6086
|
+
let uncoveredHosts = [];
|
|
6087
|
+
for (let attempt = 1; attempt <= TLS_CERTIFICATE_WRITE_ATTEMPTS; attempt += 1) {
|
|
6088
|
+
runOrThrow("mkcert", [
|
|
6089
|
+
"-cert-file",
|
|
6090
|
+
CERT_FILE,
|
|
6091
|
+
"-key-file",
|
|
6092
|
+
CERT_KEY_FILE,
|
|
6093
|
+
...desiredHosts
|
|
6094
|
+
]);
|
|
6095
|
+
certificateHosts = readCurrentCertificateHosts();
|
|
6096
|
+
uncoveredHosts = findUncoveredCertificateHosts(desiredHosts, certificateHosts);
|
|
6097
|
+
if (uncoveredHosts.length === 0) {
|
|
6098
|
+
break;
|
|
6099
|
+
}
|
|
6100
|
+
}
|
|
6101
|
+
if (uncoveredHosts.length > 0) {
|
|
6102
|
+
throw new Error(
|
|
6103
|
+
`mkcert did not produce certificate coverage for host(s): ${uncoveredHosts.join(", ")} after ${TLS_CERTIFICATE_WRITE_ATTEMPTS} attempts`
|
|
6104
|
+
);
|
|
6105
|
+
}
|
|
6106
|
+
setTLSEnabled(true);
|
|
6107
|
+
refreshHostRoutesDynamicFile();
|
|
6108
|
+
return { alreadyEnabled, hosts: certificateHosts };
|
|
6109
|
+
}
|
|
6026
6110
|
);
|
|
6027
|
-
if (installTrust) {
|
|
6028
|
-
ensureMkcert();
|
|
6029
|
-
runOrThrow("mkcert", ["-install"]);
|
|
6030
|
-
} else {
|
|
6031
|
-
getMkcertRootCAPath({ repoPath: options.repoPath });
|
|
6032
|
-
}
|
|
6033
|
-
runOrThrow("mkcert", ["-cert-file", CERT_FILE, "-key-file", CERT_KEY_FILE, ...desiredHosts]);
|
|
6034
|
-
setTLSEnabled(true);
|
|
6035
|
-
refreshHostRoutesDynamicFile();
|
|
6036
6111
|
const routerContainer = await findContainerByName("devrouter-traefik");
|
|
6037
6112
|
if (routerContainer && await isContainerRunning("devrouter-traefik")) {
|
|
6038
6113
|
startRouterStack();
|
|
6039
6114
|
}
|
|
6040
|
-
return
|
|
6115
|
+
return result;
|
|
6041
6116
|
}
|
|
6042
6117
|
async function installTLS(options = {}) {
|
|
6043
6118
|
return applyTLSCertificate(options, true);
|
|
@@ -6079,7 +6154,7 @@ Run: ${tlsSetupCommand(options.repoPath)}`
|
|
|
6079
6154
|
);
|
|
6080
6155
|
}
|
|
6081
6156
|
}
|
|
6082
|
-
var import_node_child_process8, import_node_crypto6, import_node_fs16, import_node_path15, DEFAULT_TLS_CERT_HOSTS;
|
|
6157
|
+
var import_node_child_process8, import_node_crypto6, import_node_fs16, import_node_path15, DEFAULT_TLS_CERT_HOSTS, TLS_CERTIFICATE_LOCK_FILE, TLS_CERTIFICATE_LOCK_WAIT_MS, TLS_CERTIFICATE_WRITE_ATTEMPTS;
|
|
6083
6158
|
var init_tls = __esm({
|
|
6084
6159
|
"src/core/tls.ts"() {
|
|
6085
6160
|
"use strict";
|
|
@@ -6088,9 +6163,13 @@ var init_tls = __esm({
|
|
|
6088
6163
|
import_node_fs16 = __toESM(require("fs"));
|
|
6089
6164
|
import_node_path15 = __toESM(require("path"));
|
|
6090
6165
|
init_docker();
|
|
6166
|
+
init_file_lock();
|
|
6091
6167
|
init_host_routes();
|
|
6092
6168
|
init_router();
|
|
6093
6169
|
DEFAULT_TLS_CERT_HOSTS = ["localhost", "*.localhost"];
|
|
6170
|
+
TLS_CERTIFICATE_LOCK_FILE = import_node_path15.default.join(DEVROUTER_HOME, "tls-certificate.lock");
|
|
6171
|
+
TLS_CERTIFICATE_LOCK_WAIT_MS = 6e4;
|
|
6172
|
+
TLS_CERTIFICATE_WRITE_ATTEMPTS = 2;
|
|
6094
6173
|
}
|
|
6095
6174
|
});
|
|
6096
6175
|
|
|
@@ -8074,7 +8153,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
8074
8153
|
const config = runtimeConfig.config;
|
|
8075
8154
|
loadedConfig = config;
|
|
8076
8155
|
loadedWorkspace = runtimeConfig.workspace;
|
|
8077
|
-
const cliVersion = true ? "0.0.
|
|
8156
|
+
const cliVersion = true ? "0.0.44" : "0.0.0-dev";
|
|
8078
8157
|
const configVersion = config.devrouter?.version;
|
|
8079
8158
|
if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
|
|
8080
8159
|
addCheck(checks, {
|
|
@@ -14280,7 +14359,7 @@ var init_version = __esm({
|
|
|
14280
14359
|
|
|
14281
14360
|
// src/cli.ts
|
|
14282
14361
|
var import_commander = require("commander");
|
|
14283
|
-
var CLI_VERSION = true ? "0.0.
|
|
14362
|
+
var CLI_VERSION = true ? "0.0.44" : "0.0.0-dev";
|
|
14284
14363
|
var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
|
|
14285
14364
|
function withErrorHandling(action2) {
|
|
14286
14365
|
return async (...args) => {
|
package/package.json
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Upgrade to devrouter 0.0.44
|
|
2
|
+
|
|
3
|
+
Shared local TLS certificate refresh now serializes inspection, generation,
|
|
4
|
+
verification, and route-configuration refresh across parallel worktrees.
|
|
5
|
+
|
|
6
|
+
1. Install `@devrouter/cli@0.0.44` on the host and bump `.devrouter.yml` to
|
|
7
|
+
`devrouter.version: 0.0.44`.
|
|
8
|
+
2. Keep using `devrouter ensure <checkout>` for primary and linked checkouts.
|
|
9
|
+
Do not run `mkcert` manually for individual worktree hosts; Devrouter now
|
|
10
|
+
preserves and compacts shared certificate coverage under its own lock.
|
|
11
|
+
3. Do not remove the shared certificate or lock as routine cleanup. Routine
|
|
12
|
+
refresh still fails closed on a malformed certificate; the explicit
|
|
13
|
+
repository-scoped setup command printed by Devrouter replaces that generated
|
|
14
|
+
certificate while holding the same machine-global lock.
|
|
15
|
+
|
|
16
|
+
Verification:
|
|
17
|
+
|
|
18
|
+
- Run `devrouter -V --repo <checkout>` and confirm the installed CLI and local
|
|
19
|
+
repository report `0.0.44`.
|
|
20
|
+
- Start two linked checkouts concurrently with `devrouter ensure`; require both
|
|
21
|
+
namespaced HTTPS routes to pass readiness and remain covered afterward.
|
|
22
|
+
- Run `devrouter doctor --repo <checkout>` and confirm no TLS host-coverage
|
|
23
|
+
warning remains.
|
|
24
|
+
|
|
25
|
+
Report template:
|
|
26
|
+
|
|
27
|
+
- CLI/config version: `0.0.44`
|
|
28
|
+
- Parallel worktree routes: `<passed or details>`
|
|
29
|
+
- TLS host coverage: `<passed or details>`
|
|
30
|
+
- Remaining blockers: `<none or details>`
|