@getmonoceros/workbench 1.36.3 → 1.36.4
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/bin.js +102 -55
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -2132,52 +2132,90 @@ async function preflightHostPort(hostPort, opts = {}) {
|
|
|
2132
2132
|
const probe = opts.portProbe ?? realPortProbe;
|
|
2133
2133
|
const result = await probe(hostPort);
|
|
2134
2134
|
if (result.ok) return;
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
function formatHostPortHeldError(hostPort, code, systemMessage) {
|
|
2140
|
-
const isInUse = code === "EADDRINUSE";
|
|
2141
|
-
const lines = [];
|
|
2142
|
-
if (isInUse) {
|
|
2143
|
-
lines.push(`Host port ${hostPort} is already in use by another process.`);
|
|
2144
|
-
lines.push("");
|
|
2145
|
-
lines.push(`Monoceros needs that port for its Traefik proxy (the thing`);
|
|
2146
|
-
lines.push(`that routes <name>.localhost / <name>-<port>.localhost to`);
|
|
2147
|
-
lines.push(`your dev-container). Two ways out:`);
|
|
2148
|
-
lines.push("");
|
|
2149
|
-
lines.push(" 1) Recommended: free the port.");
|
|
2150
|
-
lines.push(" Identify the process holding it:");
|
|
2151
|
-
lines.push(` sudo lsof -iTCP:${hostPort} -sTCP:LISTEN -n -P`);
|
|
2152
|
-
lines.push(` # or: sudo ss -tlnp | grep ":${hostPort}\\b"`);
|
|
2153
|
-
lines.push(" Then stop or reconfigure that service.");
|
|
2154
|
-
lines.push("");
|
|
2155
|
-
lines.push(" 2) Move Monoceros off port 80. Edit (or create)");
|
|
2156
|
-
lines.push(" ~/.monoceros/monoceros-config.yml and add:");
|
|
2157
|
-
lines.push("");
|
|
2158
|
-
lines.push(" schemaVersion: 1");
|
|
2159
|
-
lines.push(" routing:");
|
|
2160
|
-
lines.push(" hostPort: 8080 # any free port");
|
|
2161
|
-
lines.push("");
|
|
2162
|
-
lines.push(" URLs will become http://<name>.localhost:8080/.");
|
|
2163
|
-
lines.push("");
|
|
2164
|
-
lines.push(`Aborting \u2014 re-run after the conflict is resolved.`);
|
|
2165
|
-
} else {
|
|
2166
|
-
lines.push(`Cannot reach host port ${hostPort}: ${systemMessage}`);
|
|
2167
|
-
lines.push("");
|
|
2168
|
-
lines.push(`This is not the typical "port already in use" case \u2014`);
|
|
2169
|
-
lines.push(`Monoceros's pre-flight uses a TCP-connect probe (not a`);
|
|
2170
|
-
lines.push(`bind), so EACCES / privileged-port errors normally don't`);
|
|
2171
|
-
lines.push(`appear here. Most likely something on your host network`);
|
|
2172
|
-
lines.push(`stack (firewall, network namespace, \u2026) is interfering with`);
|
|
2173
|
-
lines.push(`loopback connects.`);
|
|
2174
|
-
lines.push("");
|
|
2175
|
-
lines.push("Workaround: move Monoceros off this port by setting");
|
|
2176
|
-
lines.push("`routing.hostPort` in ~/.monoceros/monoceros-config.yml.");
|
|
2177
|
-
lines.push("");
|
|
2178
|
-
lines.push(`Aborting \u2014 re-run after the issue is resolved.`);
|
|
2135
|
+
if (result.code !== "EADDRINUSE") {
|
|
2136
|
+
throw new Error(
|
|
2137
|
+
formatHostPortHeldError(hostPort, result.code, result.message)
|
|
2138
|
+
);
|
|
2179
2139
|
}
|
|
2180
|
-
|
|
2140
|
+
const live = await containersPublishing(docker, hostPort);
|
|
2141
|
+
if (live.length > 0) {
|
|
2142
|
+
throw new Error(formatLiveContainerError(hostPort, live));
|
|
2143
|
+
}
|
|
2144
|
+
throw new Error(formatLeftoverHolderError(hostPort));
|
|
2145
|
+
}
|
|
2146
|
+
async function containersPublishing(docker, hostPort) {
|
|
2147
|
+
const ps = await docker([
|
|
2148
|
+
"ps",
|
|
2149
|
+
"--filter",
|
|
2150
|
+
`publish=${hostPort}`,
|
|
2151
|
+
"--format",
|
|
2152
|
+
"{{.Names}} ({{.Image}})"
|
|
2153
|
+
]);
|
|
2154
|
+
if (ps.exitCode !== 0) return [];
|
|
2155
|
+
return ps.stdout.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
2156
|
+
}
|
|
2157
|
+
function formatLiveContainerError(hostPort, containers) {
|
|
2158
|
+
const firstName = containers[0].split(" ")[0];
|
|
2159
|
+
return [
|
|
2160
|
+
`Host port ${hostPort} is in use: it is published by a running container.`,
|
|
2161
|
+
"",
|
|
2162
|
+
`Monoceros needs that port for its Traefik proxy. The container(s) holding it:`,
|
|
2163
|
+
...containers.map((c) => ` - ${c}`),
|
|
2164
|
+
"",
|
|
2165
|
+
`Stop or re-map it, then re-run, e.g.:`,
|
|
2166
|
+
"",
|
|
2167
|
+
` docker stop ${firstName}`,
|
|
2168
|
+
"",
|
|
2169
|
+
`Or move Monoceros off the port: set \`routing.hostPort\` in`,
|
|
2170
|
+
`~/.monoceros/monoceros-config.yml.`,
|
|
2171
|
+
"",
|
|
2172
|
+
`Aborting. Re-run after the conflict is resolved.`
|
|
2173
|
+
].join("\n");
|
|
2174
|
+
}
|
|
2175
|
+
function formatLeftoverHolderError(hostPort) {
|
|
2176
|
+
return [
|
|
2177
|
+
`Host port ${hostPort} is in use, but no Docker container in the active`,
|
|
2178
|
+
`engine publishes it.`,
|
|
2179
|
+
"",
|
|
2180
|
+
`This is almost always a leftover Docker port-forwarder (docker-proxy)`,
|
|
2181
|
+
`whose container is already gone. It is left behind when a native Docker`,
|
|
2182
|
+
`daemon is restarted while a container held the port, which is common with`,
|
|
2183
|
+
`dockerd-via-systemd inside WSL (WSL restarts it on every shutdown). The`,
|
|
2184
|
+
`forwarder keeps the port bound with nothing behind it. Two ways out:`,
|
|
2185
|
+
"",
|
|
2186
|
+
` 1) Recommended: reap the leftover by restarting the Docker daemon`,
|
|
2187
|
+
` (harmless, it just clears stale forwarders):`,
|
|
2188
|
+
` WSL / native dockerd: sudo systemctl restart docker`,
|
|
2189
|
+
` Docker Desktop: restart it from its menu`,
|
|
2190
|
+
` Then re-run.`,
|
|
2191
|
+
"",
|
|
2192
|
+
` 2) Or move Monoceros off port ${hostPort}. Edit (or create)`,
|
|
2193
|
+
` ~/.monoceros/monoceros-config.yml and add:`,
|
|
2194
|
+
"",
|
|
2195
|
+
` schemaVersion: 1`,
|
|
2196
|
+
` routing:`,
|
|
2197
|
+
` hostPort: 8080 # any free port`,
|
|
2198
|
+
"",
|
|
2199
|
+
` URLs then become http://<name>.localhost:8080/.`,
|
|
2200
|
+
"",
|
|
2201
|
+
`Aborting. Re-run after the conflict is resolved.`
|
|
2202
|
+
].join("\n");
|
|
2203
|
+
}
|
|
2204
|
+
function formatHostPortHeldError(hostPort, _code, systemMessage) {
|
|
2205
|
+
return [
|
|
2206
|
+
`Cannot reach host port ${hostPort}: ${systemMessage}`,
|
|
2207
|
+
"",
|
|
2208
|
+
`This is not the typical "port already in use" case.`,
|
|
2209
|
+
`Monoceros's pre-flight uses a TCP-connect probe (not a bind), so`,
|
|
2210
|
+
`EACCES / privileged-port errors normally don't appear here. Most`,
|
|
2211
|
+
`likely something on your host network stack (firewall, network`,
|
|
2212
|
+
`namespace, \u2026) is interfering with loopback connects.`,
|
|
2213
|
+
"",
|
|
2214
|
+
"Workaround: move Monoceros off this port by setting",
|
|
2215
|
+
"`routing.hostPort` in ~/.monoceros/monoceros-config.yml.",
|
|
2216
|
+
"",
|
|
2217
|
+
`Aborting. Re-run after the issue is resolved.`
|
|
2218
|
+
].join("\n");
|
|
2181
2219
|
}
|
|
2182
2220
|
var CONNECT_TIMEOUT_MS, realPortProbe;
|
|
2183
2221
|
var init_port_check = __esm({
|
|
@@ -4308,6 +4346,8 @@ var init_transform = __esm({
|
|
|
4308
4346
|
// src/briefing/agents-md.ts
|
|
4309
4347
|
function generateAgentsMd(input) {
|
|
4310
4348
|
const lines = [];
|
|
4349
|
+
const hostPort = input.hostPort ?? 80;
|
|
4350
|
+
const portSuffix = hostPort === 80 ? "" : `:${hostPort}`;
|
|
4311
4351
|
lines.push("# Monoceros Container \u2014 Stack Briefing");
|
|
4312
4352
|
lines.push("");
|
|
4313
4353
|
lines.push(
|
|
@@ -4435,18 +4475,18 @@ function generateAgentsMd(input) {
|
|
|
4435
4475
|
const port = input.ports[i];
|
|
4436
4476
|
if (i === 0) {
|
|
4437
4477
|
lines.push(
|
|
4438
|
-
`- ${port} (default route) \u2192 http://${input.containerName}.localhost`
|
|
4478
|
+
`- ${port} (default route) \u2192 http://${input.containerName}.localhost${portSuffix}`
|
|
4439
4479
|
);
|
|
4440
4480
|
} else {
|
|
4441
4481
|
lines.push(
|
|
4442
|
-
`- ${port} \u2192 http://${input.containerName}-${port}.localhost`
|
|
4482
|
+
`- ${port} \u2192 http://${input.containerName}-${port}.localhost${portSuffix}`
|
|
4443
4483
|
);
|
|
4444
4484
|
}
|
|
4445
4485
|
}
|
|
4446
4486
|
lines.push("");
|
|
4447
4487
|
lines.push(
|
|
4448
4488
|
"To show the user a running app, open it in their host browser with",
|
|
4449
|
-
`\`xdg-open http://${input.containerName}.localhost\` \u2014 Monoceros relays`,
|
|
4489
|
+
`\`xdg-open http://${input.containerName}.localhost${portSuffix}\` \u2014 Monoceros relays`,
|
|
4450
4490
|
"browser-opens from the container to the host machine. Also tell the user",
|
|
4451
4491
|
"the URL, so they can open it themselves if no bridge is active."
|
|
4452
4492
|
);
|
|
@@ -4466,7 +4506,7 @@ function generateAgentsMd(input) {
|
|
|
4466
4506
|
" Angular `--allowed-hosts`, CRA `DANGEROUSLY_DISABLE_HOST_CHECK`;",
|
|
4467
4507
|
"- it does **not pin the HMR/live-reload socket** to a fixed host or port",
|
|
4468
4508
|
" \u2014 let it follow the page URL (e.g. for Vite, do not set",
|
|
4469
|
-
|
|
4509
|
+
` \`server.hmr.clientPort\`), so HMR works on \`<name>.localhost${portSuffix}\` and over`,
|
|
4470
4510
|
" the LAN alike;",
|
|
4471
4511
|
"- the **backend is reached via the dev-server proxy** under a relative",
|
|
4472
4512
|
" path (Vite `server.proxy`, Angular `proxy.conf.json`, CRA",
|
|
@@ -4551,7 +4591,7 @@ function generateAgentsMd(input) {
|
|
|
4551
4591
|
"When you build something that serves on a port (a web app, an API),",
|
|
4552
4592
|
"it must keep running after this session ends. A plain `npm start` (or",
|
|
4553
4593
|
"any foreground start) dies the moment the user exits you or closes the",
|
|
4554
|
-
`terminal, and then \`${input.containerName}.localhost\` returns 502 Bad Gateway.`
|
|
4594
|
+
`terminal, and then \`${input.containerName}.localhost${portSuffix}\` returns 502 Bad Gateway.`
|
|
4555
4595
|
);
|
|
4556
4596
|
lines.push("");
|
|
4557
4597
|
lines.push(
|
|
@@ -4643,7 +4683,7 @@ function formatServiceLine(svc) {
|
|
|
4643
4683
|
}
|
|
4644
4684
|
return `- **${svc.name}** (custom image \`${svc.image}\`) \u2014 reachable at \`${reach}\``;
|
|
4645
4685
|
}
|
|
4646
|
-
function agentsMdInputFromCreateOptions(opts, featureDisplayMap2, manifestLoader) {
|
|
4686
|
+
function agentsMdInputFromCreateOptions(opts, featureDisplayMap2, manifestLoader, hostPort = 80) {
|
|
4647
4687
|
const features = [];
|
|
4648
4688
|
for (const [ref, userOptions] of Object.entries(opts.features ?? {})) {
|
|
4649
4689
|
const manifest = manifestLoader?.(ref);
|
|
@@ -4663,7 +4703,8 @@ function agentsMdInputFromCreateOptions(opts, featureDisplayMap2, manifestLoader
|
|
|
4663
4703
|
services: opts.services,
|
|
4664
4704
|
features,
|
|
4665
4705
|
repos: opts.repos ?? [],
|
|
4666
|
-
ports: opts.ports ?? []
|
|
4706
|
+
ports: opts.ports ?? [],
|
|
4707
|
+
hostPort
|
|
4667
4708
|
};
|
|
4668
4709
|
}
|
|
4669
4710
|
function resolveFeatureLines(ref, userOptions, manifest, featureDisplayMap2) {
|
|
@@ -4929,7 +4970,8 @@ async function writeBriefing(input) {
|
|
|
4929
4970
|
agentsMdInputFromCreateOptions(
|
|
4930
4971
|
input.createOpts,
|
|
4931
4972
|
featureDisplayMap(input.components),
|
|
4932
|
-
manifestLoader
|
|
4973
|
+
manifestLoader,
|
|
4974
|
+
input.hostPort ?? 80
|
|
4933
4975
|
)
|
|
4934
4976
|
);
|
|
4935
4977
|
const claudeBody = generateClaudeMd();
|
|
@@ -8317,7 +8359,12 @@ Fix the value in the env file (or the yml).`
|
|
|
8317
8359
|
}
|
|
8318
8360
|
try {
|
|
8319
8361
|
const components = await loadComponentCatalog();
|
|
8320
|
-
await writeBriefing({
|
|
8362
|
+
await writeBriefing({
|
|
8363
|
+
targetDir,
|
|
8364
|
+
createOpts,
|
|
8365
|
+
components,
|
|
8366
|
+
hostPort: proxyHostPort(globalConfig)
|
|
8367
|
+
});
|
|
8321
8368
|
} catch (err) {
|
|
8322
8369
|
const msg = `briefing files not written: ${err instanceof Error ? err.message : String(err)}`;
|
|
8323
8370
|
(logger.warn ?? logger.info)(msg);
|
|
@@ -8810,7 +8857,7 @@ var CLI_VERSION;
|
|
|
8810
8857
|
var init_version = __esm({
|
|
8811
8858
|
"src/version.ts"() {
|
|
8812
8859
|
"use strict";
|
|
8813
|
-
CLI_VERSION = true ? "1.36.
|
|
8860
|
+
CLI_VERSION = true ? "1.36.4" : "dev";
|
|
8814
8861
|
}
|
|
8815
8862
|
});
|
|
8816
8863
|
|