@getmonoceros/workbench 1.36.1 → 1.36.3
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 +1064 -880
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -2860,7 +2860,7 @@ function windowsSshPort(name) {
|
|
|
2860
2860
|
for (let i = 0; i < name.length; i++) {
|
|
2861
2861
|
h = h * 31 + name.charCodeAt(i) >>> 0;
|
|
2862
2862
|
}
|
|
2863
|
-
return
|
|
2863
|
+
return 2e4 + h % 2e4;
|
|
2864
2864
|
}
|
|
2865
2865
|
function windowsHostBlock(hostAlias, keyWin, port) {
|
|
2866
2866
|
const head = [
|
|
@@ -4451,6 +4451,33 @@ function generateAgentsMd(input) {
|
|
|
4451
4451
|
"the URL, so they can open it themselves if no bridge is active."
|
|
4452
4452
|
);
|
|
4453
4453
|
lines.push("");
|
|
4454
|
+
lines.push("### Dev servers (so the proxy and LAN can reach them)");
|
|
4455
|
+
lines.push("");
|
|
4456
|
+
lines.push(
|
|
4457
|
+
"A dev server you start must be reachable through the Monoceros proxy",
|
|
4458
|
+
"(and, when the user shares it to their phone/LAN, over the network).",
|
|
4459
|
+
"Configure it so:"
|
|
4460
|
+
);
|
|
4461
|
+
lines.push("");
|
|
4462
|
+
lines.push(
|
|
4463
|
+
"- it **listens on `0.0.0.0`**, not `127.0.0.1` (otherwise the proxy",
|
|
4464
|
+
" cannot reach it);",
|
|
4465
|
+
"- it **accepts the proxy/LAN hostnames** \u2014 Vite `server.allowedHosts`,",
|
|
4466
|
+
" Angular `--allowed-hosts`, CRA `DANGEROUSLY_DISABLE_HOST_CHECK`;",
|
|
4467
|
+
"- it does **not pin the HMR/live-reload socket** to a fixed host or port",
|
|
4468
|
+
" \u2014 let it follow the page URL (e.g. for Vite, do not set",
|
|
4469
|
+
" `server.hmr.clientPort`), so HMR works on `<name>.localhost` and over",
|
|
4470
|
+
" the LAN alike;",
|
|
4471
|
+
"- the **backend is reached via the dev-server proxy** under a relative",
|
|
4472
|
+
" path (Vite `server.proxy`, Angular `proxy.conf.json`, CRA",
|
|
4473
|
+
" `setupProxy.js`) so the browser only ever talks to one origin."
|
|
4474
|
+
);
|
|
4475
|
+
lines.push("");
|
|
4476
|
+
lines.push(
|
|
4477
|
+
"These are dev-server-only settings (a production build ignores them), so",
|
|
4478
|
+
"they are safe to keep."
|
|
4479
|
+
);
|
|
4480
|
+
lines.push("");
|
|
4454
4481
|
}
|
|
4455
4482
|
lines.push("## How to extend this container");
|
|
4456
4483
|
lines.push("");
|
|
@@ -7012,7 +7039,7 @@ async function cleanupDockerObjects(opts) {
|
|
|
7012
7039
|
return { exitCode: rmExit, removedIds: ids };
|
|
7013
7040
|
}
|
|
7014
7041
|
function composeProjectName(root) {
|
|
7015
|
-
return `${path18.basename(root)}_devcontainer
|
|
7042
|
+
return `${path18.basename(root)}_devcontainer`.toLowerCase().replace(/[^a-z0-9_-]/g, "");
|
|
7016
7043
|
}
|
|
7017
7044
|
function assertDevcontainer(root) {
|
|
7018
7045
|
if (!existsSync10(path18.join(root, ".devcontainer"))) {
|
|
@@ -7038,7 +7065,18 @@ async function runComposeAction(buildSubArgs, opts) {
|
|
|
7038
7065
|
const { composeFile, projectName } = resolveCompose(opts.root);
|
|
7039
7066
|
const spawnFn = opts.spawn ?? spawnDockerCompose;
|
|
7040
7067
|
const subArgs = buildSubArgs(opts.service);
|
|
7041
|
-
return spawnFn(
|
|
7068
|
+
return spawnFn(
|
|
7069
|
+
[
|
|
7070
|
+
"-f",
|
|
7071
|
+
composeFile,
|
|
7072
|
+
"-p",
|
|
7073
|
+
projectName,
|
|
7074
|
+
"--profile",
|
|
7075
|
+
DEFERRED_SERVICE_PROFILE,
|
|
7076
|
+
...subArgs
|
|
7077
|
+
],
|
|
7078
|
+
opts.root
|
|
7079
|
+
);
|
|
7042
7080
|
}
|
|
7043
7081
|
async function startDeferredServices(opts) {
|
|
7044
7082
|
if (opts.services.length === 0) return 0;
|
|
@@ -8772,7 +8810,7 @@ var CLI_VERSION;
|
|
|
8772
8810
|
var init_version = __esm({
|
|
8773
8811
|
"src/version.ts"() {
|
|
8774
8812
|
"use strict";
|
|
8775
|
-
CLI_VERSION = true ? "1.36.
|
|
8813
|
+
CLI_VERSION = true ? "1.36.3" : "dev";
|
|
8776
8814
|
}
|
|
8777
8815
|
});
|
|
8778
8816
|
|
|
@@ -9494,6 +9532,7 @@ var init_resolve = __esm({
|
|
|
9494
9532
|
"remove-port",
|
|
9495
9533
|
"port",
|
|
9496
9534
|
"tunnel",
|
|
9535
|
+
"share",
|
|
9497
9536
|
"completion"
|
|
9498
9537
|
];
|
|
9499
9538
|
containerName = (ctx) => listContainerNames(ctx);
|
|
@@ -9633,6 +9672,9 @@ var init_resolve = __esm({
|
|
|
9633
9672
|
"--local-address": { type: "value" }
|
|
9634
9673
|
}
|
|
9635
9674
|
},
|
|
9675
|
+
share: {
|
|
9676
|
+
positionals: [containerName, (ctx) => listAppCandidates(ctx)]
|
|
9677
|
+
},
|
|
9636
9678
|
completion: {
|
|
9637
9679
|
positionals: [() => listShellNames()]
|
|
9638
9680
|
},
|
|
@@ -11941,530 +11983,494 @@ var init_run2 = __esm({
|
|
|
11941
11983
|
}
|
|
11942
11984
|
});
|
|
11943
11985
|
|
|
11944
|
-
// src/
|
|
11945
|
-
import {
|
|
11946
|
-
import
|
|
11947
|
-
|
|
11948
|
-
|
|
11949
|
-
|
|
11950
|
-
|
|
11951
|
-
|
|
11952
|
-
|
|
11953
|
-
|
|
11954
|
-
|
|
11955
|
-
|
|
11956
|
-
|
|
11957
|
-
|
|
11958
|
-
|
|
11959
|
-
|
|
11960
|
-
|
|
11961
|
-
|
|
11962
|
-
|
|
11963
|
-
|
|
11964
|
-
|
|
11965
|
-
|
|
11966
|
-
|
|
11967
|
-
|
|
11968
|
-
|
|
11969
|
-
|
|
11970
|
-
|
|
11971
|
-
});
|
|
11972
|
-
process.exit(exitCode);
|
|
11973
|
-
} catch (err) {
|
|
11974
|
-
consola34.error(err instanceof Error ? err.message : String(err));
|
|
11975
|
-
process.exit(1);
|
|
11976
|
-
}
|
|
11977
|
-
}
|
|
11986
|
+
// src/tunnel/resolve.ts
|
|
11987
|
+
import { existsSync as existsSync20 } from "fs";
|
|
11988
|
+
import path32 from "path";
|
|
11989
|
+
async function resolveTunnelTarget(opts) {
|
|
11990
|
+
const ymlPath = containerConfigPath(opts.name, opts.monocerosHome);
|
|
11991
|
+
if (!existsSync20(ymlPath)) {
|
|
11992
|
+
throw new Error(
|
|
11993
|
+
`No yml profile for '${opts.name}' at ${ymlPath}. Run \`monoceros init ${opts.name}\` first.`
|
|
11994
|
+
);
|
|
11995
|
+
}
|
|
11996
|
+
const parsed = await readConfig(ymlPath);
|
|
11997
|
+
const config = parsed.config;
|
|
11998
|
+
const containerRoot = containerDir(opts.name, opts.monocerosHome);
|
|
11999
|
+
if (!existsSync20(containerRoot)) {
|
|
12000
|
+
throw new Error(
|
|
12001
|
+
`Container '${opts.name}' is not materialised at ${containerRoot}. Run \`monoceros apply ${opts.name}\` first.`
|
|
12002
|
+
);
|
|
12003
|
+
}
|
|
12004
|
+
const composePath = path32.join(containerRoot, ".devcontainer", "compose.yaml");
|
|
12005
|
+
const isCompose = existsSync20(composePath);
|
|
12006
|
+
const parsedTarget = parseTargetArg(opts.target, config);
|
|
12007
|
+
const docker = opts.docker ?? defaultDockerExec;
|
|
12008
|
+
if (isCompose) {
|
|
12009
|
+
return resolveCompose2({
|
|
12010
|
+
name: opts.name,
|
|
12011
|
+
containerRoot,
|
|
12012
|
+
parsedTarget
|
|
11978
12013
|
});
|
|
11979
12014
|
}
|
|
11980
|
-
|
|
11981
|
-
|
|
11982
|
-
|
|
11983
|
-
|
|
11984
|
-
|
|
11985
|
-
|
|
11986
|
-
|
|
11987
|
-
|
|
11988
|
-
|
|
11989
|
-
|
|
11990
|
-
|
|
11991
|
-
|
|
11992
|
-
|
|
11993
|
-
|
|
11994
|
-
|
|
11995
|
-
|
|
11996
|
-
needsProxy = true;
|
|
11997
|
-
const global = await readMonocerosConfig();
|
|
11998
|
-
hostPort = proxyHostPort(global);
|
|
11999
|
-
}
|
|
12000
|
-
deferred = (parsed.config.services ?? []).filter((s) => serviceDefersStart(s.name)).map((s) => s.name);
|
|
12001
|
-
} catch (err) {
|
|
12002
|
-
consola35.warn(
|
|
12003
|
-
`Could not read container yml ahead of start: ${err instanceof Error ? err.message : String(err)}. Skipping Traefik pre-flight.`
|
|
12015
|
+
return resolveImageMode({
|
|
12016
|
+
name: opts.name,
|
|
12017
|
+
containerRoot,
|
|
12018
|
+
parsedTarget,
|
|
12019
|
+
config,
|
|
12020
|
+
docker
|
|
12021
|
+
});
|
|
12022
|
+
}
|
|
12023
|
+
function parseTargetArg(raw, config) {
|
|
12024
|
+
const colon = raw.indexOf(":");
|
|
12025
|
+
if (colon > 0) {
|
|
12026
|
+
const name = raw.slice(0, colon);
|
|
12027
|
+
const port = Number(raw.slice(colon + 1));
|
|
12028
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
12029
|
+
throw new Error(
|
|
12030
|
+
`Invalid target '${raw}'. Use <service>:<port> with a numeric port (1\u201365535), a bare port number, or a configured service name.`
|
|
12004
12031
|
);
|
|
12005
12032
|
}
|
|
12006
|
-
|
|
12007
|
-
|
|
12008
|
-
await ensureProxy({ hostPort });
|
|
12009
|
-
}
|
|
12010
|
-
const exitCode = await runStart({
|
|
12011
|
-
root: containerDir(args.name),
|
|
12012
|
-
silent: true,
|
|
12013
|
-
logger: { info: () => {
|
|
12014
|
-
} }
|
|
12015
|
-
});
|
|
12016
|
-
if (exitCode === 0 && runtimeSupportsBrowserBridge(runtimeVersion)) {
|
|
12017
|
-
spawnBridgeDaemon(containerDir(args.name));
|
|
12018
|
-
}
|
|
12019
|
-
if (exitCode === 0 && deferred.length > 0) {
|
|
12020
|
-
try {
|
|
12021
|
-
const deferExit = await startDeferredServices({
|
|
12022
|
-
root: containerDir(args.name),
|
|
12023
|
-
services: deferred,
|
|
12024
|
-
logger: consola35
|
|
12025
|
-
});
|
|
12026
|
-
if (deferExit !== 0) {
|
|
12027
|
-
consola35.warn(
|
|
12028
|
-
`Deferred service(s) ${deferred.join(", ")} did not start cleanly (exit ${deferExit}).`
|
|
12029
|
-
);
|
|
12030
|
-
}
|
|
12031
|
-
} catch (err) {
|
|
12032
|
-
consola35.warn(
|
|
12033
|
-
`Could not start deferred service(s) ${deferred.join(", ")}: ${err instanceof Error ? err.message : String(err)}`
|
|
12034
|
-
);
|
|
12035
|
-
}
|
|
12036
|
-
}
|
|
12037
|
-
if (exitCode === 0) {
|
|
12038
|
-
consola35.success(`Container '${args.name}' is up.`);
|
|
12039
|
-
}
|
|
12040
|
-
if (args.open && exitCode === 0) {
|
|
12041
|
-
try {
|
|
12042
|
-
await runOpen({ name: args.name, tool: args.open });
|
|
12043
|
-
} catch (err) {
|
|
12044
|
-
consola35.warn(err instanceof Error ? err.message : String(err));
|
|
12045
|
-
}
|
|
12046
|
-
}
|
|
12047
|
-
return exitCode;
|
|
12033
|
+
findConfiguredService(config, name);
|
|
12034
|
+
return { kind: "service", service: name, port };
|
|
12048
12035
|
}
|
|
12049
|
-
|
|
12050
|
-
|
|
12051
|
-
|
|
12052
|
-
"src/commands/start.ts"() {
|
|
12053
|
-
"use strict";
|
|
12054
|
-
init_global();
|
|
12055
|
-
init_io();
|
|
12056
|
-
init_paths();
|
|
12057
|
-
init_bridge_daemon();
|
|
12058
|
-
init_compose();
|
|
12059
|
-
init_catalog();
|
|
12060
|
-
init_open();
|
|
12061
|
-
init_proxy();
|
|
12062
|
-
init_port_check();
|
|
12063
|
-
init_app_control();
|
|
12064
|
-
init_dispatch();
|
|
12065
|
-
startCommand = defineCommand30({
|
|
12066
|
-
meta: {
|
|
12067
|
-
name: "start",
|
|
12068
|
-
group: "run",
|
|
12069
|
-
description: "Bring the named dev-container up. With an <app>, start that app inside it (per its projects/<app>/.monoceros/launch.json); the container is brought up first if needed."
|
|
12070
|
-
},
|
|
12071
|
-
args: {
|
|
12072
|
-
name: {
|
|
12073
|
-
type: "positional",
|
|
12074
|
-
description: "Container name (yml in $MONOCEROS_HOME/container-configs/).",
|
|
12075
|
-
required: true
|
|
12076
|
-
},
|
|
12077
|
-
app: {
|
|
12078
|
-
type: "positional",
|
|
12079
|
-
description: "App to start (a path under projects/ with .monoceros/launch.json). Omit to just bring the container up.",
|
|
12080
|
-
required: false
|
|
12081
|
-
},
|
|
12082
|
-
target: {
|
|
12083
|
-
type: "string",
|
|
12084
|
-
description: `Which launch target to start (defaults to the app's "default" target, or its only one).`
|
|
12085
|
-
},
|
|
12086
|
-
open: {
|
|
12087
|
-
type: "string",
|
|
12088
|
-
description: `After a successful start, open the container in this tool (${OPEN_TOOLS.join("|")}).`
|
|
12089
|
-
}
|
|
12090
|
-
},
|
|
12091
|
-
run({ args }) {
|
|
12092
|
-
if (typeof args.app === "string" && args.app.length > 0) {
|
|
12093
|
-
const app = args.app;
|
|
12094
|
-
const target = typeof args.target === "string" ? args.target : void 0;
|
|
12095
|
-
return dispatch(async () => {
|
|
12096
|
-
if (!await findRunningContainer(args.name)) {
|
|
12097
|
-
const up = await bringContainerUp(args.name, void 0);
|
|
12098
|
-
if (up !== 0) return up;
|
|
12099
|
-
}
|
|
12100
|
-
return runAppCtl(args.name, ctlArgs("start", app, target));
|
|
12101
|
-
});
|
|
12102
|
-
}
|
|
12103
|
-
return dispatch(
|
|
12104
|
-
() => bringContainerUp(
|
|
12105
|
-
args.name,
|
|
12106
|
-
typeof args.open === "string" ? args.open : void 0
|
|
12107
|
-
)
|
|
12108
|
-
);
|
|
12109
|
-
}
|
|
12110
|
-
});
|
|
12036
|
+
const asNumber = Number(raw);
|
|
12037
|
+
if (Number.isInteger(asNumber) && asNumber > 0 && asNumber < 65536) {
|
|
12038
|
+
return { kind: "port", port: asNumber };
|
|
12111
12039
|
}
|
|
12112
|
-
|
|
12113
|
-
|
|
12114
|
-
|
|
12115
|
-
|
|
12116
|
-
|
|
12117
|
-
|
|
12118
|
-
return
|
|
12119
|
-
}
|
|
12120
|
-
function cols(line) {
|
|
12121
|
-
return line.split(" ");
|
|
12040
|
+
const match = findConfiguredService(config, raw);
|
|
12041
|
+
if (match.port === void 0) {
|
|
12042
|
+
throw new Error(
|
|
12043
|
+
`Service '${raw}' declares no port, so tunnel can't know what to forward. Add \`port: <n>\` to the service in the yml and re-apply, or pass one explicitly: \`monoceros tunnel <name> ${raw}:<port>\`.`
|
|
12044
|
+
);
|
|
12045
|
+
}
|
|
12046
|
+
return { kind: "service", service: raw, port: match.port };
|
|
12122
12047
|
}
|
|
12123
|
-
function
|
|
12124
|
-
|
|
12048
|
+
function findConfiguredService(config, name) {
|
|
12049
|
+
const services = config.services.map(resolveService);
|
|
12050
|
+
const match = services.find((s) => s.name === name);
|
|
12051
|
+
if (!match) {
|
|
12052
|
+
const names = services.map((s) => s.name);
|
|
12053
|
+
const list = names.length > 0 ? names.join(", ") : "(none configured)";
|
|
12054
|
+
throw new Error(
|
|
12055
|
+
`Service '${name}' is not configured in this container's yml. Configured services: ${list}. Or pass a port number (e.g. \`monoceros tunnel <name> 8080\`).`
|
|
12056
|
+
);
|
|
12057
|
+
}
|
|
12058
|
+
return match;
|
|
12125
12059
|
}
|
|
12126
|
-
|
|
12127
|
-
const
|
|
12128
|
-
|
|
12129
|
-
|
|
12130
|
-
|
|
12131
|
-
|
|
12132
|
-
|
|
12133
|
-
|
|
12134
|
-
|
|
12135
|
-
try {
|
|
12136
|
-
const parsed = await readConfig(containerConfigPath(name, opts.home));
|
|
12137
|
-
const createOpts = solutionConfigToCreateOptions(parsed.config);
|
|
12138
|
-
configured = true;
|
|
12139
|
-
runtimeVersion = createOpts.runtimeVersion;
|
|
12140
|
-
languages = createOpts.languages;
|
|
12141
|
-
features = Object.keys(createOpts.features ?? {}).map(shortFeatureName2);
|
|
12142
|
-
declaredServices = createOpts.services.map((s) => ({
|
|
12143
|
-
name: s.name,
|
|
12144
|
-
...typeof s.port === "number" ? { port: s.port } : {}
|
|
12145
|
-
}));
|
|
12146
|
-
declaredPorts = createOpts.ports ?? [];
|
|
12147
|
-
} catch {
|
|
12060
|
+
function resolveCompose2(args) {
|
|
12061
|
+
const network = `${composeProjectName(args.containerRoot)}_default`;
|
|
12062
|
+
if (args.parsedTarget.kind === "service") {
|
|
12063
|
+
return {
|
|
12064
|
+
network,
|
|
12065
|
+
targetHost: args.parsedTarget.service,
|
|
12066
|
+
internalPort: args.parsedTarget.port,
|
|
12067
|
+
display: `${args.name}/${args.parsedTarget.service}:${args.parsedTarget.port}`
|
|
12068
|
+
};
|
|
12148
12069
|
}
|
|
12149
|
-
|
|
12150
|
-
|
|
12151
|
-
|
|
12152
|
-
|
|
12153
|
-
|
|
12070
|
+
return {
|
|
12071
|
+
network,
|
|
12072
|
+
targetHost: "workspace",
|
|
12073
|
+
internalPort: args.parsedTarget.port,
|
|
12074
|
+
display: `${args.name}:${args.parsedTarget.port}`
|
|
12154
12075
|
};
|
|
12155
|
-
|
|
12156
|
-
|
|
12157
|
-
|
|
12076
|
+
}
|
|
12077
|
+
async function resolveImageMode(args) {
|
|
12078
|
+
if (args.parsedTarget.kind === "service") {
|
|
12079
|
+
throw new Error(
|
|
12080
|
+
`Service '${args.parsedTarget.service}' is declared in the yml but '${args.name}' is image-mode (no compose.yaml). Services need compose mode \u2014 re-apply with at least one \`services:\` entry to get a compose setup.`
|
|
12081
|
+
);
|
|
12082
|
+
}
|
|
12083
|
+
const ports = args.config.routing?.ports ?? [];
|
|
12084
|
+
if (ports.length > 0) {
|
|
12085
|
+
return {
|
|
12086
|
+
network: PROXY_NETWORK_NAME,
|
|
12087
|
+
targetHost: args.name,
|
|
12088
|
+
internalPort: args.parsedTarget.port,
|
|
12089
|
+
display: `${args.name}:${args.parsedTarget.port}`
|
|
12090
|
+
};
|
|
12091
|
+
}
|
|
12092
|
+
const { network, ip } = await lookupContainerNetwork({
|
|
12093
|
+
containerRoot: args.containerRoot,
|
|
12094
|
+
docker: args.docker
|
|
12095
|
+
});
|
|
12096
|
+
return {
|
|
12097
|
+
network,
|
|
12098
|
+
targetHost: ip,
|
|
12099
|
+
internalPort: args.parsedTarget.port,
|
|
12100
|
+
display: `${args.name}:${args.parsedTarget.port}`
|
|
12101
|
+
};
|
|
12102
|
+
}
|
|
12103
|
+
async function lookupContainerNetwork(args) {
|
|
12104
|
+
const psResult = await args.docker([
|
|
12158
12105
|
"ps",
|
|
12159
|
-
"-
|
|
12160
|
-
"--no-trunc",
|
|
12106
|
+
"-q",
|
|
12161
12107
|
"--filter",
|
|
12162
|
-
`label=devcontainer.local_folder=${
|
|
12163
|
-
"--format",
|
|
12164
|
-
'{{.ID}} {{.State}} {{.Status}} {{.Names}} {{.Label "com.docker.compose.project"}}'
|
|
12108
|
+
`label=devcontainer.local_folder=${args.containerRoot}`
|
|
12165
12109
|
]);
|
|
12166
|
-
|
|
12167
|
-
|
|
12168
|
-
|
|
12169
|
-
|
|
12170
|
-
composeProject = project ?? "";
|
|
12171
|
-
container = {
|
|
12172
|
-
exists: true,
|
|
12173
|
-
running: state === "running",
|
|
12174
|
-
status: status ?? "",
|
|
12175
|
-
dockerName: names ?? ""
|
|
12176
|
-
};
|
|
12177
|
-
}
|
|
12178
|
-
const services = [];
|
|
12179
|
-
if (declaredServices.length > 0) {
|
|
12180
|
-
const live = /* @__PURE__ */ new Map();
|
|
12181
|
-
if (composeProject) {
|
|
12182
|
-
const sps = await docker([
|
|
12183
|
-
"ps",
|
|
12184
|
-
"-a",
|
|
12185
|
-
"--filter",
|
|
12186
|
-
`label=com.docker.compose.project=${composeProject}`,
|
|
12187
|
-
"--format",
|
|
12188
|
-
'{{.Label "com.docker.compose.service"}} {{.State}} {{.Status}}'
|
|
12189
|
-
]);
|
|
12190
|
-
for (const l of nonEmptyLines(sps.stdout)) {
|
|
12191
|
-
const [svc, state, status] = cols(l);
|
|
12192
|
-
if (svc)
|
|
12193
|
-
live.set(svc, { running: state === "running", status: status ?? "" });
|
|
12194
|
-
}
|
|
12195
|
-
}
|
|
12196
|
-
for (const s of declaredServices) {
|
|
12197
|
-
const m = live.get(s.name);
|
|
12198
|
-
services.push({
|
|
12199
|
-
name: s.name,
|
|
12200
|
-
running: m?.running ?? false,
|
|
12201
|
-
status: m?.status ?? "not created",
|
|
12202
|
-
...typeof s.port === "number" ? { port: s.port } : {}
|
|
12203
|
-
});
|
|
12204
|
-
}
|
|
12205
|
-
}
|
|
12206
|
-
const apps = [];
|
|
12207
|
-
for (const app of await listApps(name, opts.home)) {
|
|
12208
|
-
const cfg = await readLaunchConfig(name, app, opts.home);
|
|
12209
|
-
if (!cfg) continue;
|
|
12210
|
-
for (const t of cfg.configurations) {
|
|
12211
|
-
apps.push({
|
|
12212
|
-
app,
|
|
12213
|
-
target: t.name,
|
|
12214
|
-
port: typeof t.port === "number" ? t.port : null,
|
|
12215
|
-
default: t.default === true,
|
|
12216
|
-
running: void 0,
|
|
12217
|
-
pid: null
|
|
12218
|
-
});
|
|
12219
|
-
}
|
|
12110
|
+
if (psResult.exitCode !== 0) {
|
|
12111
|
+
throw new Error(
|
|
12112
|
+
`docker ps failed: ${psResult.stderr.trim() || `exit ${psResult.exitCode}`}`
|
|
12113
|
+
);
|
|
12220
12114
|
}
|
|
12221
|
-
const
|
|
12222
|
-
|
|
12223
|
-
|
|
12224
|
-
|
|
12225
|
-
|
|
12226
|
-
"exec",
|
|
12227
|
-
containerId,
|
|
12228
|
-
"monoceros-ctl",
|
|
12229
|
-
"list",
|
|
12230
|
-
"--json"
|
|
12231
|
-
]);
|
|
12232
|
-
if (res.exitCode === 0) {
|
|
12233
|
-
const byKey = /* @__PURE__ */ new Map();
|
|
12234
|
-
for (const l of nonEmptyLines(res.stdout)) {
|
|
12235
|
-
try {
|
|
12236
|
-
const o = JSON.parse(l);
|
|
12237
|
-
byKey.set(`${o.app}\0${o.target}`, {
|
|
12238
|
-
running: !!o.running,
|
|
12239
|
-
pid: o.pid ?? null
|
|
12240
|
-
});
|
|
12241
|
-
} catch {
|
|
12242
|
-
}
|
|
12243
|
-
}
|
|
12244
|
-
for (const a of apps) {
|
|
12245
|
-
const o = byKey.get(`${a.app}\0${a.target}`);
|
|
12246
|
-
if (o) {
|
|
12247
|
-
a.running = o.running;
|
|
12248
|
-
a.pid = o.pid;
|
|
12249
|
-
} else {
|
|
12250
|
-
a.running = false;
|
|
12251
|
-
}
|
|
12252
|
-
}
|
|
12253
|
-
} else {
|
|
12254
|
-
appStateKnown = false;
|
|
12255
|
-
appStateNote = `could not read run state from the runner - rebuild the runtime image, then \`monoceros upgrade ${name}\``;
|
|
12256
|
-
}
|
|
12115
|
+
const containerId = psResult.stdout.trim().split("\n")[0]?.trim();
|
|
12116
|
+
if (!containerId) {
|
|
12117
|
+
throw new Error(
|
|
12118
|
+
`No running container for '${args.containerRoot}'. Start it with \`monoceros start <name>\` (or open a shell with \`monoceros shell <name>\`) and retry.`
|
|
12119
|
+
);
|
|
12257
12120
|
}
|
|
12258
|
-
|
|
12259
|
-
|
|
12121
|
+
const inspect = await args.docker([
|
|
12122
|
+
"inspect",
|
|
12123
|
+
"--format",
|
|
12124
|
+
"{{json .NetworkSettings.Networks}}",
|
|
12125
|
+
containerId
|
|
12126
|
+
]);
|
|
12127
|
+
if (inspect.exitCode !== 0) {
|
|
12128
|
+
throw new Error(
|
|
12129
|
+
`docker inspect failed: ${inspect.stderr.trim() || `exit ${inspect.exitCode}`}`
|
|
12130
|
+
);
|
|
12260
12131
|
}
|
|
12261
|
-
let
|
|
12132
|
+
let networks = null;
|
|
12262
12133
|
try {
|
|
12263
|
-
|
|
12264
|
-
await readMonocerosConfig({
|
|
12265
|
-
...opts.home ? { monocerosHome: opts.home } : {}
|
|
12266
|
-
})
|
|
12267
|
-
);
|
|
12134
|
+
networks = JSON.parse(inspect.stdout);
|
|
12268
12135
|
} catch {
|
|
12136
|
+
throw new Error(
|
|
12137
|
+
`Unexpected docker inspect output: ${inspect.stdout.slice(0, 200)}`
|
|
12138
|
+
);
|
|
12269
12139
|
}
|
|
12270
|
-
|
|
12271
|
-
|
|
12272
|
-
|
|
12273
|
-
configured,
|
|
12274
|
-
container,
|
|
12275
|
-
services,
|
|
12276
|
-
apps,
|
|
12277
|
-
appStateKnown,
|
|
12278
|
-
...appStateNote ? { appStateNote } : {},
|
|
12279
|
-
ports,
|
|
12280
|
-
builtIn: { languages, features }
|
|
12281
|
-
};
|
|
12282
|
-
}
|
|
12283
|
-
function row(p, marker, name, detail, pad, indent = 2) {
|
|
12284
|
-
const gap = Math.max(2, pad - name.length + 2);
|
|
12285
|
-
return `${" ".repeat(indent)}${marker} ${p.cyan(name)}${" ".repeat(gap)}${detail}`;
|
|
12286
|
-
}
|
|
12287
|
-
function liveMarker(p, running) {
|
|
12288
|
-
if (running === void 0) return " ";
|
|
12289
|
-
return running ? p.green("\u2713") : p.dim("\xB7");
|
|
12290
|
-
}
|
|
12291
|
-
function renderContainer(p, m) {
|
|
12292
|
-
const out = [p.sectionLine(m.name)];
|
|
12293
|
-
if (!m.container.exists) {
|
|
12294
|
-
out.push(
|
|
12295
|
-
row(
|
|
12296
|
-
p,
|
|
12297
|
-
p.dim("\xB7"),
|
|
12298
|
-
"(not created)",
|
|
12299
|
-
p.dim(`run \`monoceros apply ${m.name}\``),
|
|
12300
|
-
14
|
|
12301
|
-
)
|
|
12140
|
+
if (!networks) {
|
|
12141
|
+
throw new Error(
|
|
12142
|
+
`Container ${containerId} reports no networks. Restart it and retry.`
|
|
12302
12143
|
);
|
|
12303
|
-
return out;
|
|
12304
12144
|
}
|
|
12305
|
-
const
|
|
12306
|
-
|
|
12307
|
-
|
|
12308
|
-
|
|
12309
|
-
|
|
12310
|
-
|
|
12311
|
-
|
|
12312
|
-
14
|
|
12313
|
-
)
|
|
12145
|
+
for (const [name, settings] of Object.entries(networks)) {
|
|
12146
|
+
if (settings.IPAddress && settings.IPAddress.length > 0) {
|
|
12147
|
+
return { network: name, ip: settings.IPAddress };
|
|
12148
|
+
}
|
|
12149
|
+
}
|
|
12150
|
+
throw new Error(
|
|
12151
|
+
`Container ${containerId} has no network with a reachable IP. Restart it and retry.`
|
|
12314
12152
|
);
|
|
12315
|
-
return out;
|
|
12316
12153
|
}
|
|
12317
|
-
|
|
12318
|
-
|
|
12319
|
-
|
|
12320
|
-
|
|
12321
|
-
|
|
12322
|
-
|
|
12323
|
-
|
|
12154
|
+
var init_resolve2 = __esm({
|
|
12155
|
+
"src/tunnel/resolve.ts"() {
|
|
12156
|
+
"use strict";
|
|
12157
|
+
init_paths();
|
|
12158
|
+
init_io();
|
|
12159
|
+
init_compose();
|
|
12160
|
+
init_catalog();
|
|
12161
|
+
init_proxy();
|
|
12324
12162
|
}
|
|
12325
|
-
|
|
12163
|
+
});
|
|
12164
|
+
|
|
12165
|
+
// src/tunnel/port-check.ts
|
|
12166
|
+
import { Socket as Socket2 } from "net";
|
|
12167
|
+
async function preflightLocalPort(opts) {
|
|
12168
|
+
const probe = opts.probe ?? realPortProbe2;
|
|
12169
|
+
const result = await probe(opts.port, opts.address);
|
|
12170
|
+
if (result.ok) return;
|
|
12171
|
+
throw new Error(formatLocalPortHeldError(opts.port, opts.address, result));
|
|
12326
12172
|
}
|
|
12327
|
-
function
|
|
12328
|
-
const
|
|
12329
|
-
if (
|
|
12330
|
-
|
|
12331
|
-
|
|
12173
|
+
function formatLocalPortHeldError(port, address, result) {
|
|
12174
|
+
const lines = [];
|
|
12175
|
+
if (result.code === "EADDRINUSE") {
|
|
12176
|
+
lines.push(`Local port ${port} on ${address} is already in use.`);
|
|
12177
|
+
lines.push("");
|
|
12178
|
+
lines.push("Identify the holder, then either stop it or pick a different");
|
|
12179
|
+
lines.push("port for the tunnel:");
|
|
12180
|
+
lines.push("");
|
|
12181
|
+
lines.push(` sudo lsof -iTCP:${port} -sTCP:LISTEN -n -P`);
|
|
12182
|
+
lines.push(` # or: sudo ss -tlnp | grep ":${port}\\b"`);
|
|
12183
|
+
lines.push("");
|
|
12184
|
+
lines.push("Re-run with an explicit local port:");
|
|
12185
|
+
lines.push(` monoceros tunnel \u2026 --local-port=${port + 1}`);
|
|
12186
|
+
} else {
|
|
12187
|
+
lines.push(
|
|
12188
|
+
`Cannot probe local port ${port} on ${address}: ${result.message}`
|
|
12332
12189
|
);
|
|
12333
|
-
|
|
12334
|
-
|
|
12335
|
-
|
|
12190
|
+
lines.push("");
|
|
12191
|
+
lines.push(
|
|
12192
|
+
"Most likely the host network stack (firewall, namespace) is interfering."
|
|
12193
|
+
);
|
|
12194
|
+
lines.push("Try a different local port via `--local-port=<n>`.");
|
|
12336
12195
|
}
|
|
12337
|
-
|
|
12338
|
-
return bits.join(" ");
|
|
12196
|
+
return lines.join("\n");
|
|
12339
12197
|
}
|
|
12340
|
-
|
|
12341
|
-
|
|
12342
|
-
|
|
12343
|
-
|
|
12344
|
-
|
|
12345
|
-
|
|
12346
|
-
|
|
12347
|
-
|
|
12348
|
-
|
|
12198
|
+
var CONNECT_TIMEOUT_MS2, realPortProbe2;
|
|
12199
|
+
var init_port_check2 = __esm({
|
|
12200
|
+
"src/tunnel/port-check.ts"() {
|
|
12201
|
+
"use strict";
|
|
12202
|
+
CONNECT_TIMEOUT_MS2 = 750;
|
|
12203
|
+
realPortProbe2 = (port, address) => {
|
|
12204
|
+
const probeHost = address === "0.0.0.0" ? "127.0.0.1" : address;
|
|
12205
|
+
return new Promise((resolve) => {
|
|
12206
|
+
const socket = new Socket2();
|
|
12207
|
+
let settled = false;
|
|
12208
|
+
const settle = (result) => {
|
|
12209
|
+
if (settled) return;
|
|
12210
|
+
settled = true;
|
|
12211
|
+
socket.destroy();
|
|
12212
|
+
resolve(result);
|
|
12213
|
+
};
|
|
12214
|
+
socket.setTimeout(CONNECT_TIMEOUT_MS2);
|
|
12215
|
+
socket.once("connect", () => {
|
|
12216
|
+
settle({
|
|
12217
|
+
ok: false,
|
|
12218
|
+
code: "EADDRINUSE",
|
|
12219
|
+
message: `another process is listening on ${port}`
|
|
12220
|
+
});
|
|
12221
|
+
});
|
|
12222
|
+
socket.once("timeout", () => {
|
|
12223
|
+
settle({ ok: true });
|
|
12224
|
+
});
|
|
12225
|
+
socket.once("error", (err) => {
|
|
12226
|
+
const code = err.code ?? "UNKNOWN";
|
|
12227
|
+
if (code === "ECONNREFUSED") {
|
|
12228
|
+
settle({ ok: true });
|
|
12229
|
+
} else {
|
|
12230
|
+
settle({ ok: false, code, message: err.message });
|
|
12231
|
+
}
|
|
12232
|
+
});
|
|
12233
|
+
socket.connect(port, probeHost);
|
|
12234
|
+
});
|
|
12235
|
+
};
|
|
12349
12236
|
}
|
|
12350
|
-
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
|
|
12354
|
-
|
|
12355
|
-
|
|
12356
|
-
|
|
12357
|
-
|
|
12358
|
-
|
|
12359
|
-
|
|
12360
|
-
|
|
12361
|
-
|
|
12362
|
-
|
|
12363
|
-
);
|
|
12364
|
-
}
|
|
12237
|
+
});
|
|
12238
|
+
|
|
12239
|
+
// src/tunnel/run.ts
|
|
12240
|
+
import { spawn as spawn14 } from "child_process";
|
|
12241
|
+
import { consola as consola34 } from "consola";
|
|
12242
|
+
function signalNumber(signal) {
|
|
12243
|
+
switch (signal) {
|
|
12244
|
+
case "SIGINT":
|
|
12245
|
+
return 2;
|
|
12246
|
+
case "SIGTERM":
|
|
12247
|
+
return 15;
|
|
12248
|
+
default:
|
|
12249
|
+
return 1;
|
|
12365
12250
|
}
|
|
12366
|
-
return out;
|
|
12367
12251
|
}
|
|
12368
|
-
function
|
|
12369
|
-
|
|
12370
|
-
|
|
12371
|
-
|
|
12372
|
-
|
|
12373
|
-
|
|
12374
|
-
|
|
12252
|
+
async function runTunnel(opts) {
|
|
12253
|
+
const log = opts.logger ?? {
|
|
12254
|
+
info: (m) => consola34.info(m),
|
|
12255
|
+
warn: (m) => consola34.warn(m)
|
|
12256
|
+
};
|
|
12257
|
+
const resolve = opts.resolve ?? resolveTunnelTarget;
|
|
12258
|
+
const resolveArgs3 = {
|
|
12259
|
+
name: opts.name,
|
|
12260
|
+
target: opts.target,
|
|
12261
|
+
...opts.monocerosHome !== void 0 ? { monocerosHome: opts.monocerosHome } : {}
|
|
12262
|
+
};
|
|
12263
|
+
const resolved = await resolve(resolveArgs3);
|
|
12264
|
+
const localPort = opts.localPort ?? resolved.internalPort;
|
|
12265
|
+
const localAddress = opts.localAddress ?? DEFAULT_LOCAL_ADDRESS;
|
|
12266
|
+
validateLocalAddress(localAddress);
|
|
12267
|
+
const preflight = opts.preflight ?? preflightLocalPort;
|
|
12268
|
+
await preflight({ port: localPort, address: localAddress });
|
|
12269
|
+
const dockerSpawn = opts.dockerSpawn ?? defaultDockerSpawn;
|
|
12270
|
+
const installSignalHandler = opts.installSignalHandler ?? installSigintDefault;
|
|
12271
|
+
const dockerArgs = buildDockerArgs({
|
|
12272
|
+
localAddress,
|
|
12273
|
+
localPort,
|
|
12274
|
+
internalPort: resolved.internalPort,
|
|
12275
|
+
network: resolved.network,
|
|
12276
|
+
targetHost: resolved.targetHost
|
|
12277
|
+
});
|
|
12278
|
+
log.info(
|
|
12279
|
+
`Tunnel: ${localAddress}:${localPort} \u2192 ${resolved.display}:${resolved.internalPort} (Ctrl+C to stop)`
|
|
12280
|
+
);
|
|
12281
|
+
const handle = dockerSpawn(dockerArgs);
|
|
12282
|
+
const uninstall = installSignalHandler(() => {
|
|
12283
|
+
});
|
|
12284
|
+
try {
|
|
12285
|
+
const exitCode = await handle.exited;
|
|
12286
|
+
if (exitCode === 130) return 0;
|
|
12287
|
+
return exitCode;
|
|
12288
|
+
} finally {
|
|
12289
|
+
uninstall();
|
|
12375
12290
|
}
|
|
12376
|
-
return out;
|
|
12377
12291
|
}
|
|
12378
|
-
function
|
|
12379
|
-
|
|
12380
|
-
|
|
12381
|
-
|
|
12382
|
-
|
|
12383
|
-
|
|
12384
|
-
|
|
12385
|
-
|
|
12386
|
-
|
|
12387
|
-
|
|
12388
|
-
|
|
12292
|
+
function buildDockerArgs(input) {
|
|
12293
|
+
return [
|
|
12294
|
+
"run",
|
|
12295
|
+
"--rm",
|
|
12296
|
+
"-i",
|
|
12297
|
+
`--network=${input.network}`,
|
|
12298
|
+
"-p",
|
|
12299
|
+
`${input.localAddress}:${input.localPort}:${input.internalPort}`,
|
|
12300
|
+
SOCAT_IMAGE,
|
|
12301
|
+
`TCP-LISTEN:${input.internalPort},fork,reuseaddr`,
|
|
12302
|
+
`TCP:${input.targetHost}:${input.internalPort}`
|
|
12303
|
+
];
|
|
12304
|
+
}
|
|
12305
|
+
function validateLocalAddress(addr) {
|
|
12306
|
+
if (addr === "localhost") return;
|
|
12307
|
+
if (IPV4_RE.test(addr)) {
|
|
12308
|
+
for (const part of addr.split(".")) {
|
|
12309
|
+
const n = Number(part);
|
|
12310
|
+
if (n < 0 || n > 255) {
|
|
12311
|
+
throw new Error(
|
|
12312
|
+
`Invalid --local-address '${addr}': each dotted-quad octet must be 0-255.`
|
|
12313
|
+
);
|
|
12314
|
+
}
|
|
12315
|
+
}
|
|
12316
|
+
return;
|
|
12389
12317
|
}
|
|
12390
|
-
|
|
12318
|
+
throw new Error(
|
|
12319
|
+
`Invalid --local-address '${addr}'. Use 127.0.0.1 (default), 0.0.0.0, or a specific IPv4 address.`
|
|
12320
|
+
);
|
|
12391
12321
|
}
|
|
12392
|
-
|
|
12393
|
-
|
|
12394
|
-
|
|
12395
|
-
|
|
12396
|
-
|
|
12397
|
-
|
|
12398
|
-
|
|
12399
|
-
|
|
12322
|
+
var SOCAT_IMAGE, defaultDockerSpawn, installSigintDefault, DEFAULT_LOCAL_ADDRESS, IPV4_RE;
|
|
12323
|
+
var init_run3 = __esm({
|
|
12324
|
+
"src/tunnel/run.ts"() {
|
|
12325
|
+
"use strict";
|
|
12326
|
+
init_resolve2();
|
|
12327
|
+
init_port_check2();
|
|
12328
|
+
SOCAT_IMAGE = "alpine/socat:1.8.0.3";
|
|
12329
|
+
defaultDockerSpawn = (args) => {
|
|
12330
|
+
const child = spawn14("docker", args, {
|
|
12331
|
+
stdio: "inherit"
|
|
12332
|
+
});
|
|
12333
|
+
const exited = new Promise((resolve, reject) => {
|
|
12334
|
+
child.on("error", reject);
|
|
12335
|
+
child.on("exit", (code, signal) => {
|
|
12336
|
+
if (typeof code === "number") resolve(code);
|
|
12337
|
+
else if (signal) resolve(128 + signalNumber(signal));
|
|
12338
|
+
else resolve(0);
|
|
12339
|
+
});
|
|
12340
|
+
});
|
|
12341
|
+
return {
|
|
12342
|
+
exited,
|
|
12343
|
+
kill: (signal) => {
|
|
12344
|
+
try {
|
|
12345
|
+
child.kill(signal);
|
|
12346
|
+
} catch {
|
|
12347
|
+
}
|
|
12348
|
+
}
|
|
12349
|
+
};
|
|
12350
|
+
};
|
|
12351
|
+
installSigintDefault = (handler) => {
|
|
12352
|
+
process.on("SIGINT", handler);
|
|
12353
|
+
return () => process.off("SIGINT", handler);
|
|
12354
|
+
};
|
|
12355
|
+
DEFAULT_LOCAL_ADDRESS = "127.0.0.1";
|
|
12356
|
+
IPV4_RE = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
12357
|
+
}
|
|
12358
|
+
});
|
|
12359
|
+
|
|
12360
|
+
// src/share/run.ts
|
|
12361
|
+
import os3 from "os";
|
|
12362
|
+
import { consola as consola35 } from "consola";
|
|
12363
|
+
function realHostAddresses() {
|
|
12364
|
+
let ip;
|
|
12365
|
+
for (const list of Object.values(os3.networkInterfaces())) {
|
|
12366
|
+
for (const addr of list ?? []) {
|
|
12367
|
+
if (addr.family === "IPv4" && !addr.internal) {
|
|
12368
|
+
ip = addr.address;
|
|
12369
|
+
break;
|
|
12370
|
+
}
|
|
12371
|
+
}
|
|
12372
|
+
if (ip) break;
|
|
12373
|
+
}
|
|
12374
|
+
const hn = os3.hostname();
|
|
12375
|
+
const mdnsName = hn.endsWith(".local") ? hn : `${hn}.local`;
|
|
12376
|
+
return { ...ip ? { ip } : {}, mdnsName };
|
|
12400
12377
|
}
|
|
12401
|
-
function
|
|
12402
|
-
const
|
|
12403
|
-
|
|
12404
|
-
|
|
12378
|
+
async function runShare(opts) {
|
|
12379
|
+
const log = opts.logger ?? {
|
|
12380
|
+
info: (m) => consola35.info(m),
|
|
12381
|
+
warn: (m) => consola35.warn(m)
|
|
12382
|
+
};
|
|
12383
|
+
const cfg = await readLaunchConfig(opts.name, opts.app, opts.monocerosHome);
|
|
12384
|
+
if (!cfg) {
|
|
12405
12385
|
throw new Error(
|
|
12406
|
-
`No
|
|
12386
|
+
`No launch config for '${opts.app}' (expected projects/${opts.app}/.monoceros/launch.json). Nothing to share.`
|
|
12407
12387
|
);
|
|
12408
12388
|
}
|
|
12409
|
-
const
|
|
12410
|
-
|
|
12411
|
-
|
|
12412
|
-
|
|
12413
|
-
|
|
12414
|
-
|
|
12415
|
-
p,
|
|
12416
|
-
liveMarker(p, t.running),
|
|
12417
|
-
t.target,
|
|
12418
|
-
appTargetDetail(p, m.name, t),
|
|
12419
|
-
pad,
|
|
12420
|
-
2
|
|
12421
|
-
)
|
|
12389
|
+
const ported = cfg.configurations.filter(
|
|
12390
|
+
(t) => typeof t.port === "number"
|
|
12391
|
+
);
|
|
12392
|
+
if (ported.length === 0) {
|
|
12393
|
+
throw new Error(
|
|
12394
|
+
`No target in '${opts.app}' declares a port, so there is nothing to share. Add a \`port\` to a target in its launch.json.`
|
|
12422
12395
|
);
|
|
12423
12396
|
}
|
|
12424
|
-
|
|
12425
|
-
|
|
12426
|
-
|
|
12427
|
-
|
|
12428
|
-
|
|
12429
|
-
|
|
12430
|
-
|
|
12397
|
+
const ports = [...new Set(ported.map((t) => t.port))];
|
|
12398
|
+
const resolve = opts.resolve ?? resolveTunnelTarget;
|
|
12399
|
+
const base = await resolve({
|
|
12400
|
+
name: opts.name,
|
|
12401
|
+
target: String(ports[0]),
|
|
12402
|
+
...opts.monocerosHome !== void 0 ? { monocerosHome: opts.monocerosHome } : {}
|
|
12403
|
+
});
|
|
12404
|
+
const preflight = opts.preflight ?? preflightLocalPort;
|
|
12405
|
+
for (const port of ports) {
|
|
12406
|
+
await preflight({ port, address: SHARE_ADDRESS });
|
|
12407
|
+
}
|
|
12408
|
+
const dockerSpawn = opts.dockerSpawn ?? defaultDockerSpawn;
|
|
12409
|
+
const handles = ports.map(
|
|
12410
|
+
(port) => dockerSpawn(
|
|
12411
|
+
buildDockerArgs({
|
|
12412
|
+
localAddress: SHARE_ADDRESS,
|
|
12413
|
+
localPort: port,
|
|
12414
|
+
internalPort: port,
|
|
12415
|
+
network: base.network,
|
|
12416
|
+
targetHost: base.targetHost
|
|
12417
|
+
})
|
|
12418
|
+
)
|
|
12419
|
+
);
|
|
12420
|
+
const { ip, mdnsName } = (opts.hostAddresses ?? realHostAddresses)();
|
|
12421
|
+
const host = ip ?? mdnsName ?? "<host-ip>";
|
|
12422
|
+
log.info(
|
|
12423
|
+
`Sharing ${opts.name}/${opts.app} on the local network (Ctrl+C to stop):`
|
|
12424
|
+
);
|
|
12425
|
+
for (const t of ported) {
|
|
12426
|
+
log.info(` ${cyan2(t.name)} http://${host}:${t.port}`);
|
|
12427
|
+
}
|
|
12428
|
+
if (mdnsName && ip) {
|
|
12429
|
+
log.info(
|
|
12430
|
+
dim(` also reachable as http://${mdnsName}:<port> where mDNS resolves`)
|
|
12431
12431
|
);
|
|
12432
12432
|
}
|
|
12433
|
-
const
|
|
12434
|
-
|
|
12435
|
-
|
|
12436
|
-
|
|
12437
|
-
|
|
12433
|
+
const installSignalHandler = opts.installSignalHandler ?? installSigintDefault;
|
|
12434
|
+
let stopping = false;
|
|
12435
|
+
const uninstall = installSignalHandler(() => {
|
|
12436
|
+
if (stopping) return;
|
|
12437
|
+
stopping = true;
|
|
12438
|
+
for (const h of handles) h.kill("SIGTERM");
|
|
12439
|
+
});
|
|
12440
|
+
try {
|
|
12441
|
+
const codes = await Promise.all(handles.map((h) => h.exited));
|
|
12442
|
+
const bad = codes.find((c) => c !== 0 && c !== 130);
|
|
12443
|
+
return bad ?? 0;
|
|
12444
|
+
} finally {
|
|
12445
|
+
uninstall();
|
|
12446
|
+
}
|
|
12438
12447
|
}
|
|
12439
|
-
var
|
|
12440
|
-
|
|
12448
|
+
var SHARE_ADDRESS;
|
|
12449
|
+
var init_run4 = __esm({
|
|
12450
|
+
"src/share/run.ts"() {
|
|
12441
12451
|
"use strict";
|
|
12442
|
-
init_io();
|
|
12443
|
-
init_paths();
|
|
12444
|
-
init_global();
|
|
12445
|
-
init_transform();
|
|
12446
12452
|
init_launch_config();
|
|
12447
|
-
|
|
12448
|
-
|
|
12449
|
-
|
|
12453
|
+
init_resolve2();
|
|
12454
|
+
init_port_check2();
|
|
12455
|
+
init_run3();
|
|
12450
12456
|
init_format();
|
|
12457
|
+
SHARE_ADDRESS = "0.0.0.0";
|
|
12451
12458
|
}
|
|
12452
12459
|
});
|
|
12453
12460
|
|
|
12454
|
-
// src/commands/
|
|
12455
|
-
import { defineCommand as
|
|
12456
|
-
|
|
12457
|
-
var
|
|
12458
|
-
|
|
12461
|
+
// src/commands/share.ts
|
|
12462
|
+
import { defineCommand as defineCommand29 } from "citty";
|
|
12463
|
+
import { consola as consola36 } from "consola";
|
|
12464
|
+
var shareCommand;
|
|
12465
|
+
var init_share = __esm({
|
|
12466
|
+
"src/commands/share.ts"() {
|
|
12459
12467
|
"use strict";
|
|
12460
|
-
|
|
12461
|
-
|
|
12462
|
-
init_dispatch();
|
|
12463
|
-
statusCommand = defineCommand31({
|
|
12468
|
+
init_run4();
|
|
12469
|
+
shareCommand = defineCommand29({
|
|
12464
12470
|
meta: {
|
|
12465
|
-
name: "
|
|
12466
|
-
group: "
|
|
12467
|
-
description: "
|
|
12471
|
+
name: "share",
|
|
12472
|
+
group: "discovery",
|
|
12473
|
+
description: "Expose an app's ports to the local network (phone, tablet, other devices) so any device can open it - reached via the host's LAN IP (or `.local` name). Every target in the app's launch.json that declares a port is shared. Foreground: Ctrl+C stops sharing. See ADR 0030."
|
|
12468
12474
|
},
|
|
12469
12475
|
args: {
|
|
12470
12476
|
name: {
|
|
@@ -12474,49 +12480,149 @@ var init_status2 = __esm({
|
|
|
12474
12480
|
},
|
|
12475
12481
|
app: {
|
|
12476
12482
|
type: "positional",
|
|
12477
|
-
description: "
|
|
12478
|
-
required:
|
|
12483
|
+
description: "App to share (a path under projects/ with .monoceros/launch.json). Every target with a `port` is exposed on the LAN.",
|
|
12484
|
+
required: true
|
|
12479
12485
|
}
|
|
12480
12486
|
},
|
|
12481
|
-
run({ args }) {
|
|
12482
|
-
|
|
12483
|
-
|
|
12484
|
-
|
|
12485
|
-
|
|
12486
|
-
|
|
12487
|
-
|
|
12488
|
-
|
|
12489
|
-
block = isApp ? renderApp(model, filter, p) : renderService(model, filter, p);
|
|
12490
|
-
} else {
|
|
12491
|
-
block = renderStatus(model, p);
|
|
12492
|
-
}
|
|
12493
|
-
process.stdout.write(`
|
|
12494
|
-
${block}
|
|
12495
|
-
`);
|
|
12496
|
-
return 0;
|
|
12497
|
-
});
|
|
12487
|
+
async run({ args }) {
|
|
12488
|
+
try {
|
|
12489
|
+
const exitCode = await runShare({ name: args.name, app: args.app });
|
|
12490
|
+
process.exit(exitCode);
|
|
12491
|
+
} catch (err) {
|
|
12492
|
+
consola36.error(err instanceof Error ? err.message : String(err));
|
|
12493
|
+
process.exit(1);
|
|
12494
|
+
}
|
|
12498
12495
|
}
|
|
12499
12496
|
});
|
|
12500
12497
|
}
|
|
12501
12498
|
});
|
|
12502
12499
|
|
|
12503
|
-
// src/commands/
|
|
12504
|
-
import { defineCommand as
|
|
12505
|
-
import { consola as
|
|
12506
|
-
var
|
|
12507
|
-
var
|
|
12508
|
-
"src/commands/
|
|
12500
|
+
// src/commands/shell.ts
|
|
12501
|
+
import { defineCommand as defineCommand30 } from "citty";
|
|
12502
|
+
import { consola as consola37 } from "consola";
|
|
12503
|
+
var shellCommand;
|
|
12504
|
+
var init_shell2 = __esm({
|
|
12505
|
+
"src/commands/shell.ts"() {
|
|
12506
|
+
"use strict";
|
|
12507
|
+
init_paths();
|
|
12508
|
+
init_shell();
|
|
12509
|
+
shellCommand = defineCommand30({
|
|
12510
|
+
meta: {
|
|
12511
|
+
name: "shell",
|
|
12512
|
+
group: "run",
|
|
12513
|
+
description: "Open an interactive bash session inside the named dev-container."
|
|
12514
|
+
},
|
|
12515
|
+
args: {
|
|
12516
|
+
name: {
|
|
12517
|
+
type: "positional",
|
|
12518
|
+
description: "Container name (yml in $MONOCEROS_HOME/container-configs/).",
|
|
12519
|
+
required: true
|
|
12520
|
+
}
|
|
12521
|
+
},
|
|
12522
|
+
async run({ args }) {
|
|
12523
|
+
try {
|
|
12524
|
+
const exitCode = await runShell({
|
|
12525
|
+
root: containerDir(args.name),
|
|
12526
|
+
name: args.name
|
|
12527
|
+
});
|
|
12528
|
+
process.exit(exitCode);
|
|
12529
|
+
} catch (err) {
|
|
12530
|
+
consola37.error(err instanceof Error ? err.message : String(err));
|
|
12531
|
+
process.exit(1);
|
|
12532
|
+
}
|
|
12533
|
+
}
|
|
12534
|
+
});
|
|
12535
|
+
}
|
|
12536
|
+
});
|
|
12537
|
+
|
|
12538
|
+
// src/commands/start.ts
|
|
12539
|
+
import { defineCommand as defineCommand31 } from "citty";
|
|
12540
|
+
import { consola as consola38 } from "consola";
|
|
12541
|
+
async function bringContainerUp(name, openTool) {
|
|
12542
|
+
{
|
|
12543
|
+
const args = { name, open: openTool };
|
|
12544
|
+
let needsProxy = false;
|
|
12545
|
+
let hostPort = 80;
|
|
12546
|
+
let deferred = [];
|
|
12547
|
+
let runtimeVersion;
|
|
12548
|
+
try {
|
|
12549
|
+
const parsed = await readConfig(containerConfigPath(args.name));
|
|
12550
|
+
runtimeVersion = parsed.config.runtimeVersion;
|
|
12551
|
+
if ((parsed.config.routing?.ports ?? []).length > 0) {
|
|
12552
|
+
needsProxy = true;
|
|
12553
|
+
const global = await readMonocerosConfig();
|
|
12554
|
+
hostPort = proxyHostPort(global);
|
|
12555
|
+
}
|
|
12556
|
+
deferred = (parsed.config.services ?? []).filter((s) => serviceDefersStart(s.name)).map((s) => s.name);
|
|
12557
|
+
} catch (err) {
|
|
12558
|
+
consola38.warn(
|
|
12559
|
+
`Could not read container yml ahead of start: ${err instanceof Error ? err.message : String(err)}. Skipping Traefik pre-flight.`
|
|
12560
|
+
);
|
|
12561
|
+
}
|
|
12562
|
+
if (needsProxy) {
|
|
12563
|
+
await preflightHostPort(hostPort);
|
|
12564
|
+
await ensureProxy({ hostPort });
|
|
12565
|
+
}
|
|
12566
|
+
const exitCode = await runStart({
|
|
12567
|
+
root: containerDir(args.name),
|
|
12568
|
+
silent: true,
|
|
12569
|
+
logger: { info: () => {
|
|
12570
|
+
} }
|
|
12571
|
+
});
|
|
12572
|
+
if (exitCode === 0 && runtimeSupportsBrowserBridge(runtimeVersion)) {
|
|
12573
|
+
spawnBridgeDaemon(containerDir(args.name));
|
|
12574
|
+
}
|
|
12575
|
+
if (exitCode === 0 && deferred.length > 0) {
|
|
12576
|
+
try {
|
|
12577
|
+
const deferExit = await startDeferredServices({
|
|
12578
|
+
root: containerDir(args.name),
|
|
12579
|
+
services: deferred,
|
|
12580
|
+
logger: consola38
|
|
12581
|
+
});
|
|
12582
|
+
if (deferExit !== 0) {
|
|
12583
|
+
consola38.warn(
|
|
12584
|
+
`Deferred service(s) ${deferred.join(", ")} did not start cleanly (exit ${deferExit}).`
|
|
12585
|
+
);
|
|
12586
|
+
}
|
|
12587
|
+
} catch (err) {
|
|
12588
|
+
consola38.warn(
|
|
12589
|
+
`Could not start deferred service(s) ${deferred.join(", ")}: ${err instanceof Error ? err.message : String(err)}`
|
|
12590
|
+
);
|
|
12591
|
+
}
|
|
12592
|
+
}
|
|
12593
|
+
if (exitCode === 0) {
|
|
12594
|
+
consola38.success(`Container '${args.name}' is up.`);
|
|
12595
|
+
}
|
|
12596
|
+
if (args.open && exitCode === 0) {
|
|
12597
|
+
try {
|
|
12598
|
+
await runOpen({ name: args.name, tool: args.open });
|
|
12599
|
+
} catch (err) {
|
|
12600
|
+
consola38.warn(err instanceof Error ? err.message : String(err));
|
|
12601
|
+
}
|
|
12602
|
+
}
|
|
12603
|
+
return exitCode;
|
|
12604
|
+
}
|
|
12605
|
+
}
|
|
12606
|
+
var startCommand;
|
|
12607
|
+
var init_start = __esm({
|
|
12608
|
+
"src/commands/start.ts"() {
|
|
12509
12609
|
"use strict";
|
|
12610
|
+
init_global();
|
|
12611
|
+
init_io();
|
|
12510
12612
|
init_paths();
|
|
12613
|
+
init_bridge_daemon();
|
|
12511
12614
|
init_compose();
|
|
12615
|
+
init_catalog();
|
|
12616
|
+
init_open();
|
|
12512
12617
|
init_proxy();
|
|
12618
|
+
init_port_check();
|
|
12513
12619
|
init_app_control();
|
|
12514
12620
|
init_dispatch();
|
|
12515
|
-
|
|
12621
|
+
startCommand = defineCommand31({
|
|
12516
12622
|
meta: {
|
|
12517
|
-
name: "
|
|
12623
|
+
name: "start",
|
|
12518
12624
|
group: "run",
|
|
12519
|
-
description: "
|
|
12625
|
+
description: "Bring the named dev-container up. With an <app>, start that app inside it (per its projects/<app>/.monoceros/launch.json); the container is brought up first if needed."
|
|
12520
12626
|
},
|
|
12521
12627
|
args: {
|
|
12522
12628
|
name: {
|
|
@@ -12526,430 +12632,506 @@ var init_stop = __esm({
|
|
|
12526
12632
|
},
|
|
12527
12633
|
app: {
|
|
12528
12634
|
type: "positional",
|
|
12529
|
-
description: "App to
|
|
12635
|
+
description: "App to start (a path under projects/ with .monoceros/launch.json). Omit to just bring the container up.",
|
|
12530
12636
|
required: false
|
|
12531
12637
|
},
|
|
12532
12638
|
target: {
|
|
12533
12639
|
type: "string",
|
|
12534
|
-
description: `Which launch target to
|
|
12640
|
+
description: `Which launch target to start (defaults to the app's "default" target, or its only one).`
|
|
12535
12641
|
},
|
|
12536
|
-
|
|
12642
|
+
open: {
|
|
12537
12643
|
type: "string",
|
|
12538
|
-
description:
|
|
12644
|
+
description: `After a successful start, open the container in this tool (${OPEN_TOOLS.join("|")}).`
|
|
12539
12645
|
}
|
|
12540
12646
|
},
|
|
12541
12647
|
run({ args }) {
|
|
12542
12648
|
if (typeof args.app === "string" && args.app.length > 0) {
|
|
12543
12649
|
const app = args.app;
|
|
12544
12650
|
const target = typeof args.target === "string" ? args.target : void 0;
|
|
12545
|
-
return dispatch(() =>
|
|
12546
|
-
|
|
12547
|
-
|
|
12548
|
-
|
|
12549
|
-
|
|
12550
|
-
|
|
12551
|
-
|
|
12552
|
-
|
|
12553
|
-
|
|
12554
|
-
|
|
12555
|
-
|
|
12556
|
-
|
|
12557
|
-
|
|
12558
|
-
|
|
12559
|
-
}
|
|
12560
|
-
try {
|
|
12561
|
-
await maybeStopProxy({
|
|
12562
|
-
logger: { info: (msg) => consola36.info(msg) }
|
|
12563
|
-
});
|
|
12564
|
-
} catch (err) {
|
|
12565
|
-
consola36.warn(
|
|
12566
|
-
`Could not tear down the Traefik proxy: ${err instanceof Error ? err.message : String(err)}. Ignored.`
|
|
12567
|
-
);
|
|
12568
|
-
}
|
|
12569
|
-
return exit;
|
|
12570
|
-
});
|
|
12651
|
+
return dispatch(async () => {
|
|
12652
|
+
if (!await findRunningContainer(args.name)) {
|
|
12653
|
+
const up = await bringContainerUp(args.name, void 0);
|
|
12654
|
+
if (up !== 0) return up;
|
|
12655
|
+
}
|
|
12656
|
+
return runAppCtl(args.name, ctlArgs("start", app, target));
|
|
12657
|
+
});
|
|
12658
|
+
}
|
|
12659
|
+
return dispatch(
|
|
12660
|
+
() => bringContainerUp(
|
|
12661
|
+
args.name,
|
|
12662
|
+
typeof args.open === "string" ? args.open : void 0
|
|
12663
|
+
)
|
|
12664
|
+
);
|
|
12571
12665
|
}
|
|
12572
12666
|
});
|
|
12573
12667
|
}
|
|
12574
12668
|
});
|
|
12575
12669
|
|
|
12576
|
-
// src/
|
|
12577
|
-
|
|
12578
|
-
|
|
12579
|
-
|
|
12580
|
-
|
|
12581
|
-
if (!existsSync20(ymlPath)) {
|
|
12582
|
-
throw new Error(
|
|
12583
|
-
`No yml profile for '${opts.name}' at ${ymlPath}. Run \`monoceros init ${opts.name}\` first.`
|
|
12584
|
-
);
|
|
12585
|
-
}
|
|
12586
|
-
const parsed = await readConfig(ymlPath);
|
|
12587
|
-
const config = parsed.config;
|
|
12588
|
-
const containerRoot = containerDir(opts.name, opts.monocerosHome);
|
|
12589
|
-
if (!existsSync20(containerRoot)) {
|
|
12590
|
-
throw new Error(
|
|
12591
|
-
`Container '${opts.name}' is not materialised at ${containerRoot}. Run \`monoceros apply ${opts.name}\` first.`
|
|
12592
|
-
);
|
|
12593
|
-
}
|
|
12594
|
-
const composePath = path32.join(containerRoot, ".devcontainer", "compose.yaml");
|
|
12595
|
-
const isCompose = existsSync20(composePath);
|
|
12596
|
-
const parsedTarget = parseTargetArg(opts.target, config);
|
|
12597
|
-
const docker = opts.docker ?? defaultDockerExec;
|
|
12598
|
-
if (isCompose) {
|
|
12599
|
-
return resolveCompose2({
|
|
12600
|
-
name: opts.name,
|
|
12601
|
-
containerRoot,
|
|
12602
|
-
parsedTarget
|
|
12603
|
-
});
|
|
12604
|
-
}
|
|
12605
|
-
return resolveImageMode({
|
|
12606
|
-
name: opts.name,
|
|
12607
|
-
containerRoot,
|
|
12608
|
-
parsedTarget,
|
|
12609
|
-
config,
|
|
12610
|
-
docker
|
|
12611
|
-
});
|
|
12612
|
-
}
|
|
12613
|
-
function parseTargetArg(raw, config) {
|
|
12614
|
-
const colon = raw.indexOf(":");
|
|
12615
|
-
if (colon > 0) {
|
|
12616
|
-
const name = raw.slice(0, colon);
|
|
12617
|
-
const port = Number(raw.slice(colon + 1));
|
|
12618
|
-
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
12619
|
-
throw new Error(
|
|
12620
|
-
`Invalid target '${raw}'. Use <service>:<port> with a numeric port (1\u201365535), a bare port number, or a configured service name.`
|
|
12621
|
-
);
|
|
12622
|
-
}
|
|
12623
|
-
findConfiguredService(config, name);
|
|
12624
|
-
return { kind: "service", service: name, port };
|
|
12625
|
-
}
|
|
12626
|
-
const asNumber = Number(raw);
|
|
12627
|
-
if (Number.isInteger(asNumber) && asNumber > 0 && asNumber < 65536) {
|
|
12628
|
-
return { kind: "port", port: asNumber };
|
|
12629
|
-
}
|
|
12630
|
-
const match = findConfiguredService(config, raw);
|
|
12631
|
-
if (match.port === void 0) {
|
|
12632
|
-
throw new Error(
|
|
12633
|
-
`Service '${raw}' declares no port, so tunnel can't know what to forward. Add \`port: <n>\` to the service in the yml and re-apply, or pass one explicitly: \`monoceros tunnel <name> ${raw}:<port>\`.`
|
|
12634
|
-
);
|
|
12635
|
-
}
|
|
12636
|
-
return { kind: "service", service: raw, port: match.port };
|
|
12670
|
+
// src/status/index.ts
|
|
12671
|
+
function shortFeatureName2(ref) {
|
|
12672
|
+
const withoutTag = ref.replace(/:[^:/@]+$/, "");
|
|
12673
|
+
const idx = withoutTag.lastIndexOf("/");
|
|
12674
|
+
return idx >= 0 ? withoutTag.slice(idx + 1) : withoutTag;
|
|
12637
12675
|
}
|
|
12638
|
-
function
|
|
12639
|
-
|
|
12640
|
-
const match = services.find((s) => s.name === name);
|
|
12641
|
-
if (!match) {
|
|
12642
|
-
const names = services.map((s) => s.name);
|
|
12643
|
-
const list = names.length > 0 ? names.join(", ") : "(none configured)";
|
|
12644
|
-
throw new Error(
|
|
12645
|
-
`Service '${name}' is not configured in this container's yml. Configured services: ${list}. Or pass a port number (e.g. \`monoceros tunnel <name> 8080\`).`
|
|
12646
|
-
);
|
|
12647
|
-
}
|
|
12648
|
-
return match;
|
|
12676
|
+
function cols(line) {
|
|
12677
|
+
return line.split(" ");
|
|
12649
12678
|
}
|
|
12650
|
-
function
|
|
12651
|
-
|
|
12652
|
-
if (args.parsedTarget.kind === "service") {
|
|
12653
|
-
return {
|
|
12654
|
-
network,
|
|
12655
|
-
targetHost: args.parsedTarget.service,
|
|
12656
|
-
internalPort: args.parsedTarget.port,
|
|
12657
|
-
display: `${args.name}/${args.parsedTarget.service}:${args.parsedTarget.port}`
|
|
12658
|
-
};
|
|
12659
|
-
}
|
|
12660
|
-
return {
|
|
12661
|
-
network,
|
|
12662
|
-
targetHost: "workspace",
|
|
12663
|
-
internalPort: args.parsedTarget.port,
|
|
12664
|
-
display: `${args.name}:${args.parsedTarget.port}`
|
|
12665
|
-
};
|
|
12679
|
+
function nonEmptyLines(s) {
|
|
12680
|
+
return s.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
12666
12681
|
}
|
|
12667
|
-
async function
|
|
12668
|
-
|
|
12669
|
-
|
|
12670
|
-
|
|
12671
|
-
|
|
12672
|
-
|
|
12673
|
-
|
|
12674
|
-
|
|
12675
|
-
|
|
12676
|
-
|
|
12677
|
-
|
|
12678
|
-
|
|
12679
|
-
|
|
12680
|
-
|
|
12682
|
+
async function gatherStatus(name, opts = {}) {
|
|
12683
|
+
const docker = opts.docker ?? realDockerLookup;
|
|
12684
|
+
const root = containerDir(name, opts.home);
|
|
12685
|
+
let runtimeVersion;
|
|
12686
|
+
let languages = [];
|
|
12687
|
+
let features = [];
|
|
12688
|
+
let declaredServices = [];
|
|
12689
|
+
let declaredPorts = [];
|
|
12690
|
+
let configured = false;
|
|
12691
|
+
try {
|
|
12692
|
+
const parsed = await readConfig(containerConfigPath(name, opts.home));
|
|
12693
|
+
const createOpts = solutionConfigToCreateOptions(parsed.config);
|
|
12694
|
+
configured = true;
|
|
12695
|
+
runtimeVersion = createOpts.runtimeVersion;
|
|
12696
|
+
languages = createOpts.languages;
|
|
12697
|
+
features = Object.keys(createOpts.features ?? {}).map(shortFeatureName2);
|
|
12698
|
+
declaredServices = createOpts.services.map((s) => ({
|
|
12699
|
+
name: s.name,
|
|
12700
|
+
...typeof s.port === "number" ? { port: s.port } : {}
|
|
12701
|
+
}));
|
|
12702
|
+
declaredPorts = createOpts.ports ?? [];
|
|
12703
|
+
} catch {
|
|
12681
12704
|
}
|
|
12682
|
-
|
|
12683
|
-
|
|
12684
|
-
|
|
12685
|
-
|
|
12686
|
-
|
|
12687
|
-
network,
|
|
12688
|
-
targetHost: ip,
|
|
12689
|
-
internalPort: args.parsedTarget.port,
|
|
12690
|
-
display: `${args.name}:${args.parsedTarget.port}`
|
|
12705
|
+
let container = {
|
|
12706
|
+
exists: false,
|
|
12707
|
+
running: false,
|
|
12708
|
+
status: "",
|
|
12709
|
+
dockerName: ""
|
|
12691
12710
|
};
|
|
12692
|
-
|
|
12693
|
-
|
|
12694
|
-
const
|
|
12711
|
+
let containerId = "";
|
|
12712
|
+
let composeProject = "";
|
|
12713
|
+
const cps = await docker([
|
|
12695
12714
|
"ps",
|
|
12696
|
-
"-
|
|
12715
|
+
"-a",
|
|
12716
|
+
"--no-trunc",
|
|
12697
12717
|
"--filter",
|
|
12698
|
-
`label=devcontainer.local_folder=${
|
|
12718
|
+
`label=devcontainer.local_folder=${root}`,
|
|
12719
|
+
"--format",
|
|
12720
|
+
'{{.ID}} {{.State}} {{.Status}} {{.Names}} {{.Label "com.docker.compose.project"}}'
|
|
12699
12721
|
]);
|
|
12700
|
-
|
|
12701
|
-
|
|
12702
|
-
|
|
12703
|
-
|
|
12722
|
+
const row2 = nonEmptyLines(cps.stdout)[0];
|
|
12723
|
+
if (row2) {
|
|
12724
|
+
const [id, state, status, names, project] = cols(row2);
|
|
12725
|
+
containerId = id ?? "";
|
|
12726
|
+
composeProject = project ?? "";
|
|
12727
|
+
container = {
|
|
12728
|
+
exists: true,
|
|
12729
|
+
running: state === "running",
|
|
12730
|
+
status: status ?? "",
|
|
12731
|
+
dockerName: names ?? ""
|
|
12732
|
+
};
|
|
12704
12733
|
}
|
|
12705
|
-
const
|
|
12706
|
-
if (
|
|
12707
|
-
|
|
12708
|
-
|
|
12709
|
-
|
|
12734
|
+
const services = [];
|
|
12735
|
+
if (declaredServices.length > 0) {
|
|
12736
|
+
const live = /* @__PURE__ */ new Map();
|
|
12737
|
+
if (composeProject) {
|
|
12738
|
+
const sps = await docker([
|
|
12739
|
+
"ps",
|
|
12740
|
+
"-a",
|
|
12741
|
+
"--filter",
|
|
12742
|
+
`label=com.docker.compose.project=${composeProject}`,
|
|
12743
|
+
"--format",
|
|
12744
|
+
'{{.Label "com.docker.compose.service"}} {{.State}} {{.Status}}'
|
|
12745
|
+
]);
|
|
12746
|
+
for (const l of nonEmptyLines(sps.stdout)) {
|
|
12747
|
+
const [svc, state, status] = cols(l);
|
|
12748
|
+
if (svc)
|
|
12749
|
+
live.set(svc, { running: state === "running", status: status ?? "" });
|
|
12750
|
+
}
|
|
12751
|
+
}
|
|
12752
|
+
for (const s of declaredServices) {
|
|
12753
|
+
const m = live.get(s.name);
|
|
12754
|
+
services.push({
|
|
12755
|
+
name: s.name,
|
|
12756
|
+
running: m?.running ?? false,
|
|
12757
|
+
status: m?.status ?? "not created",
|
|
12758
|
+
...typeof s.port === "number" ? { port: s.port } : {}
|
|
12759
|
+
});
|
|
12760
|
+
}
|
|
12761
|
+
}
|
|
12762
|
+
const apps = [];
|
|
12763
|
+
for (const app of await listApps(name, opts.home)) {
|
|
12764
|
+
const cfg = await readLaunchConfig(name, app, opts.home);
|
|
12765
|
+
if (!cfg) continue;
|
|
12766
|
+
for (const t of cfg.configurations) {
|
|
12767
|
+
apps.push({
|
|
12768
|
+
app,
|
|
12769
|
+
target: t.name,
|
|
12770
|
+
port: typeof t.port === "number" ? t.port : null,
|
|
12771
|
+
default: t.default === true,
|
|
12772
|
+
running: void 0,
|
|
12773
|
+
pid: null
|
|
12774
|
+
});
|
|
12775
|
+
}
|
|
12776
|
+
}
|
|
12777
|
+
const appsSupported = runtimeSupportsAppStatus(runtimeVersion);
|
|
12778
|
+
let appStateKnown = container.running && appsSupported && containerId !== "";
|
|
12779
|
+
let appStateNote;
|
|
12780
|
+
if (appStateKnown) {
|
|
12781
|
+
const res = await docker([
|
|
12782
|
+
"exec",
|
|
12783
|
+
containerId,
|
|
12784
|
+
"monoceros-ctl",
|
|
12785
|
+
"list",
|
|
12786
|
+
"--json"
|
|
12787
|
+
]);
|
|
12788
|
+
if (res.exitCode === 0) {
|
|
12789
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
12790
|
+
for (const l of nonEmptyLines(res.stdout)) {
|
|
12791
|
+
try {
|
|
12792
|
+
const o = JSON.parse(l);
|
|
12793
|
+
byKey.set(`${o.app}\0${o.target}`, {
|
|
12794
|
+
running: !!o.running,
|
|
12795
|
+
pid: o.pid ?? null
|
|
12796
|
+
});
|
|
12797
|
+
} catch {
|
|
12798
|
+
}
|
|
12799
|
+
}
|
|
12800
|
+
for (const a of apps) {
|
|
12801
|
+
const o = byKey.get(`${a.app}\0${a.target}`);
|
|
12802
|
+
if (o) {
|
|
12803
|
+
a.running = o.running;
|
|
12804
|
+
a.pid = o.pid;
|
|
12805
|
+
} else {
|
|
12806
|
+
a.running = false;
|
|
12807
|
+
}
|
|
12808
|
+
}
|
|
12809
|
+
} else {
|
|
12810
|
+
appStateKnown = false;
|
|
12811
|
+
appStateNote = `could not read run state from the runner - rebuild the runtime image, then \`monoceros upgrade ${name}\``;
|
|
12812
|
+
}
|
|
12710
12813
|
}
|
|
12711
|
-
|
|
12712
|
-
"
|
|
12713
|
-
"--format",
|
|
12714
|
-
"{{json .NetworkSettings.Networks}}",
|
|
12715
|
-
containerId
|
|
12716
|
-
]);
|
|
12717
|
-
if (inspect.exitCode !== 0) {
|
|
12718
|
-
throw new Error(
|
|
12719
|
-
`docker inspect failed: ${inspect.stderr.trim() || `exit ${inspect.exitCode}`}`
|
|
12720
|
-
);
|
|
12814
|
+
if (apps.length > 0 && !appStateKnown && !appStateNote) {
|
|
12815
|
+
appStateNote = !container.running ? `start the container to see run state (\`monoceros start ${name}\`)` : `run state needs runtime 1.6.0+ (this one: ${runtimeVersion ?? "unpinned"})`;
|
|
12721
12816
|
}
|
|
12722
|
-
let
|
|
12817
|
+
let hostPort = 80;
|
|
12723
12818
|
try {
|
|
12724
|
-
|
|
12725
|
-
|
|
12726
|
-
|
|
12727
|
-
|
|
12819
|
+
hostPort = proxyHostPort(
|
|
12820
|
+
await readMonocerosConfig({
|
|
12821
|
+
...opts.home ? { monocerosHome: opts.home } : {}
|
|
12822
|
+
})
|
|
12728
12823
|
);
|
|
12824
|
+
} catch {
|
|
12729
12825
|
}
|
|
12730
|
-
|
|
12731
|
-
|
|
12732
|
-
|
|
12826
|
+
const ports = proxyUrlsFor(name, declaredPorts, hostPort);
|
|
12827
|
+
return {
|
|
12828
|
+
name,
|
|
12829
|
+
configured,
|
|
12830
|
+
container,
|
|
12831
|
+
services,
|
|
12832
|
+
apps,
|
|
12833
|
+
appStateKnown,
|
|
12834
|
+
...appStateNote ? { appStateNote } : {},
|
|
12835
|
+
ports,
|
|
12836
|
+
builtIn: { languages, features }
|
|
12837
|
+
};
|
|
12838
|
+
}
|
|
12839
|
+
function row(p, marker, name, detail, pad, indent = 2) {
|
|
12840
|
+
const gap = Math.max(2, pad - name.length + 2);
|
|
12841
|
+
return `${" ".repeat(indent)}${marker} ${p.cyan(name)}${" ".repeat(gap)}${detail}`;
|
|
12842
|
+
}
|
|
12843
|
+
function liveMarker(p, running) {
|
|
12844
|
+
if (running === void 0) return " ";
|
|
12845
|
+
return running ? p.green("\u2713") : p.dim("\xB7");
|
|
12846
|
+
}
|
|
12847
|
+
function renderContainer(p, m) {
|
|
12848
|
+
const out = [p.sectionLine(m.name)];
|
|
12849
|
+
if (!m.container.exists) {
|
|
12850
|
+
out.push(
|
|
12851
|
+
row(
|
|
12852
|
+
p,
|
|
12853
|
+
p.dim("\xB7"),
|
|
12854
|
+
"(not created)",
|
|
12855
|
+
p.dim(`run \`monoceros apply ${m.name}\``),
|
|
12856
|
+
14
|
|
12857
|
+
)
|
|
12733
12858
|
);
|
|
12859
|
+
return out;
|
|
12734
12860
|
}
|
|
12735
|
-
|
|
12736
|
-
|
|
12737
|
-
|
|
12738
|
-
|
|
12739
|
-
|
|
12740
|
-
|
|
12741
|
-
|
|
12861
|
+
const detail = m.container.running ? p.dim(m.container.status || "running") : p.dim(m.container.status || "stopped");
|
|
12862
|
+
out.push(
|
|
12863
|
+
row(
|
|
12864
|
+
p,
|
|
12865
|
+
liveMarker(p, m.container.running),
|
|
12866
|
+
m.container.dockerName,
|
|
12867
|
+
detail,
|
|
12868
|
+
14
|
|
12869
|
+
)
|
|
12742
12870
|
);
|
|
12871
|
+
return out;
|
|
12743
12872
|
}
|
|
12744
|
-
|
|
12745
|
-
|
|
12746
|
-
|
|
12747
|
-
|
|
12748
|
-
|
|
12749
|
-
|
|
12750
|
-
|
|
12751
|
-
init_proxy();
|
|
12873
|
+
function renderServices(p, m) {
|
|
12874
|
+
if (m.services.length === 0) return [];
|
|
12875
|
+
const pad = Math.max(...m.services.map((s) => s.name.length));
|
|
12876
|
+
const out = ["", p.sectionLine("Services")];
|
|
12877
|
+
for (const s of m.services) {
|
|
12878
|
+
const detail = s.running ? p.dim(typeof s.port === "number" ? `running :${s.port}` : "running") : p.dim(s.status || "stopped");
|
|
12879
|
+
out.push(row(p, liveMarker(p, s.running), s.name, detail, pad));
|
|
12752
12880
|
}
|
|
12753
|
-
|
|
12754
|
-
|
|
12755
|
-
// src/tunnel/port-check.ts
|
|
12756
|
-
import { Socket as Socket2 } from "net";
|
|
12757
|
-
async function preflightLocalPort(opts) {
|
|
12758
|
-
const probe = opts.probe ?? realPortProbe2;
|
|
12759
|
-
const result = await probe(opts.port, opts.address);
|
|
12760
|
-
if (result.ok) return;
|
|
12761
|
-
throw new Error(formatLocalPortHeldError(opts.port, opts.address, result));
|
|
12881
|
+
return out;
|
|
12762
12882
|
}
|
|
12763
|
-
function
|
|
12764
|
-
const
|
|
12765
|
-
if (
|
|
12766
|
-
|
|
12767
|
-
|
|
12768
|
-
lines.push("Identify the holder, then either stop it or pick a different");
|
|
12769
|
-
lines.push("port for the tunnel:");
|
|
12770
|
-
lines.push("");
|
|
12771
|
-
lines.push(` sudo lsof -iTCP:${port} -sTCP:LISTEN -n -P`);
|
|
12772
|
-
lines.push(` # or: sudo ss -tlnp | grep ":${port}\\b"`);
|
|
12773
|
-
lines.push("");
|
|
12774
|
-
lines.push("Re-run with an explicit local port:");
|
|
12775
|
-
lines.push(` monoceros tunnel \u2026 --local-port=${port + 1}`);
|
|
12776
|
-
} else {
|
|
12777
|
-
lines.push(
|
|
12778
|
-
`Cannot probe local port ${port} on ${address}: ${result.message}`
|
|
12779
|
-
);
|
|
12780
|
-
lines.push("");
|
|
12781
|
-
lines.push(
|
|
12782
|
-
"Most likely the host network stack (firewall, namespace) is interfering."
|
|
12883
|
+
function appTargetDetail(p, name, t) {
|
|
12884
|
+
const bits = [];
|
|
12885
|
+
if (t.running === true) {
|
|
12886
|
+
bits.push(
|
|
12887
|
+
t.port !== null ? p.dim(`http://${name}-${t.port}.localhost`) : p.dim("running")
|
|
12783
12888
|
);
|
|
12784
|
-
|
|
12889
|
+
if (t.pid !== null) bits.push(p.dim(`pid ${t.pid}`));
|
|
12890
|
+
} else if (t.running === false) {
|
|
12891
|
+
bits.push(p.dim("stopped"));
|
|
12785
12892
|
}
|
|
12786
|
-
|
|
12893
|
+
if (t.default) bits.push(p.dim("(default)"));
|
|
12894
|
+
return bits.join(" ");
|
|
12787
12895
|
}
|
|
12788
|
-
|
|
12789
|
-
|
|
12790
|
-
"
|
|
12791
|
-
|
|
12792
|
-
|
|
12793
|
-
|
|
12794
|
-
|
|
12795
|
-
|
|
12796
|
-
|
|
12797
|
-
let settled = false;
|
|
12798
|
-
const settle = (result) => {
|
|
12799
|
-
if (settled) return;
|
|
12800
|
-
settled = true;
|
|
12801
|
-
socket.destroy();
|
|
12802
|
-
resolve(result);
|
|
12803
|
-
};
|
|
12804
|
-
socket.setTimeout(CONNECT_TIMEOUT_MS2);
|
|
12805
|
-
socket.once("connect", () => {
|
|
12806
|
-
settle({
|
|
12807
|
-
ok: false,
|
|
12808
|
-
code: "EADDRINUSE",
|
|
12809
|
-
message: `another process is listening on ${port}`
|
|
12810
|
-
});
|
|
12811
|
-
});
|
|
12812
|
-
socket.once("timeout", () => {
|
|
12813
|
-
settle({ ok: true });
|
|
12814
|
-
});
|
|
12815
|
-
socket.once("error", (err) => {
|
|
12816
|
-
const code = err.code ?? "UNKNOWN";
|
|
12817
|
-
if (code === "ECONNREFUSED") {
|
|
12818
|
-
settle({ ok: true });
|
|
12819
|
-
} else {
|
|
12820
|
-
settle({ ok: false, code, message: err.message });
|
|
12821
|
-
}
|
|
12822
|
-
});
|
|
12823
|
-
socket.connect(port, probeHost);
|
|
12824
|
-
});
|
|
12825
|
-
};
|
|
12896
|
+
function renderApps(p, m) {
|
|
12897
|
+
if (m.apps.length === 0) return [];
|
|
12898
|
+
const out = ["", p.sectionLine("Apps")];
|
|
12899
|
+
if (m.appStateNote) out.push(` ${p.dim(m.appStateNote)}`);
|
|
12900
|
+
const byApp = /* @__PURE__ */ new Map();
|
|
12901
|
+
for (const a of m.apps) {
|
|
12902
|
+
const list = byApp.get(a.app) ?? [];
|
|
12903
|
+
list.push(a);
|
|
12904
|
+
byApp.set(a.app, list);
|
|
12826
12905
|
}
|
|
12827
|
-
|
|
12828
|
-
|
|
12829
|
-
|
|
12830
|
-
|
|
12831
|
-
|
|
12832
|
-
|
|
12833
|
-
|
|
12834
|
-
|
|
12835
|
-
|
|
12836
|
-
|
|
12837
|
-
|
|
12838
|
-
|
|
12839
|
-
|
|
12906
|
+
for (const [app, targets] of byApp) {
|
|
12907
|
+
out.push(` ${p.cyan(app)}`);
|
|
12908
|
+
const pad = Math.max(...targets.map((t) => t.target.length));
|
|
12909
|
+
for (const t of targets) {
|
|
12910
|
+
out.push(
|
|
12911
|
+
row(
|
|
12912
|
+
p,
|
|
12913
|
+
liveMarker(p, t.running),
|
|
12914
|
+
t.target,
|
|
12915
|
+
appTargetDetail(p, m.name, t),
|
|
12916
|
+
pad,
|
|
12917
|
+
4
|
|
12918
|
+
)
|
|
12919
|
+
);
|
|
12920
|
+
}
|
|
12840
12921
|
}
|
|
12841
|
-
|
|
12842
|
-
|
|
12843
|
-
|
|
12844
|
-
|
|
12845
|
-
|
|
12846
|
-
|
|
12847
|
-
const
|
|
12848
|
-
|
|
12849
|
-
|
|
12850
|
-
target: opts.target,
|
|
12851
|
-
...opts.monocerosHome !== void 0 ? { monocerosHome: opts.monocerosHome } : {}
|
|
12852
|
-
};
|
|
12853
|
-
const resolved = await resolve(resolveArgs3);
|
|
12854
|
-
const localPort = opts.localPort ?? resolved.internalPort;
|
|
12855
|
-
const localAddress = opts.localAddress ?? DEFAULT_LOCAL_ADDRESS;
|
|
12856
|
-
validateLocalAddress(localAddress);
|
|
12857
|
-
const preflight = opts.preflight ?? preflightLocalPort;
|
|
12858
|
-
await preflight({ port: localPort, address: localAddress });
|
|
12859
|
-
const dockerSpawn = opts.dockerSpawn ?? defaultDockerSpawn;
|
|
12860
|
-
const installSignalHandler = opts.installSignalHandler ?? installSigintDefault;
|
|
12861
|
-
const dockerArgs = buildDockerArgs({
|
|
12862
|
-
localAddress,
|
|
12863
|
-
localPort,
|
|
12864
|
-
internalPort: resolved.internalPort,
|
|
12865
|
-
network: resolved.network,
|
|
12866
|
-
targetHost: resolved.targetHost
|
|
12867
|
-
});
|
|
12868
|
-
log.info(
|
|
12869
|
-
`Tunnel: ${localAddress}:${localPort} \u2192 ${resolved.display}:${resolved.internalPort} (Ctrl+C to stop)`
|
|
12870
|
-
);
|
|
12871
|
-
const handle = dockerSpawn(dockerArgs);
|
|
12872
|
-
const uninstall = installSignalHandler(() => {
|
|
12873
|
-
});
|
|
12874
|
-
try {
|
|
12875
|
-
const exitCode = await handle.exited;
|
|
12876
|
-
if (exitCode === 130) return 0;
|
|
12877
|
-
return exitCode;
|
|
12878
|
-
} finally {
|
|
12879
|
-
uninstall();
|
|
12922
|
+
return out;
|
|
12923
|
+
}
|
|
12924
|
+
function renderPorts(p, m) {
|
|
12925
|
+
if (m.ports.length === 0) return [];
|
|
12926
|
+
const pad = Math.max(...m.ports.map((r) => String(r.port).length));
|
|
12927
|
+
const out = ["", p.sectionLine("Ports")];
|
|
12928
|
+
for (const r of m.ports) {
|
|
12929
|
+
const hosts = r.isDefault ? `${m.name}.localhost \xB7 ${m.name}-${r.port}.localhost` : `${m.name}-${r.port}.localhost`;
|
|
12930
|
+
out.push(row(p, " ", String(r.port), p.dim(hosts), pad));
|
|
12880
12931
|
}
|
|
12932
|
+
return out;
|
|
12881
12933
|
}
|
|
12882
|
-
function
|
|
12934
|
+
function renderBuiltIn(p, m) {
|
|
12935
|
+
const lines = [];
|
|
12936
|
+
if (m.builtIn.languages.length > 0)
|
|
12937
|
+
lines.push({ label: "Languages", values: m.builtIn.languages });
|
|
12938
|
+
if (m.builtIn.features.length > 0)
|
|
12939
|
+
lines.push({ label: "Features", values: m.builtIn.features });
|
|
12940
|
+
if (lines.length === 0) return [];
|
|
12941
|
+
const labelWidth = Math.max(...lines.map((l) => l.label.length));
|
|
12942
|
+
const out = ["", `${p.sectionLine("Built in")} ${p.dim("from the yml")}`];
|
|
12943
|
+
for (const l of lines) {
|
|
12944
|
+
out.push(` ${l.label.padEnd(labelWidth)} ${p.cyan(l.values.join(", "))}`);
|
|
12945
|
+
}
|
|
12946
|
+
return out;
|
|
12947
|
+
}
|
|
12948
|
+
function renderStatus(m, p) {
|
|
12883
12949
|
return [
|
|
12884
|
-
|
|
12885
|
-
|
|
12886
|
-
|
|
12887
|
-
|
|
12888
|
-
|
|
12889
|
-
|
|
12890
|
-
SOCAT_IMAGE,
|
|
12891
|
-
`TCP-LISTEN:${input.internalPort},fork,reuseaddr`,
|
|
12892
|
-
`TCP:${input.targetHost}:${input.internalPort}`
|
|
12893
|
-
];
|
|
12950
|
+
...renderContainer(p, m),
|
|
12951
|
+
...renderServices(p, m),
|
|
12952
|
+
...renderApps(p, m),
|
|
12953
|
+
...renderPorts(p, m),
|
|
12954
|
+
...renderBuiltIn(p, m)
|
|
12955
|
+
].join("\n");
|
|
12894
12956
|
}
|
|
12895
|
-
function
|
|
12896
|
-
|
|
12897
|
-
if (
|
|
12898
|
-
|
|
12899
|
-
|
|
12900
|
-
|
|
12901
|
-
|
|
12902
|
-
`Invalid --local-address '${addr}': each dotted-quad octet must be 0-255.`
|
|
12903
|
-
);
|
|
12904
|
-
}
|
|
12905
|
-
}
|
|
12906
|
-
return;
|
|
12957
|
+
function renderApp(m, app, p) {
|
|
12958
|
+
const targets = m.apps.filter((a) => a.app === app);
|
|
12959
|
+
if (targets.length === 0) {
|
|
12960
|
+
const known = [...new Set(m.apps.map((a) => a.app))];
|
|
12961
|
+
throw new Error(
|
|
12962
|
+
`No app "${app}" in ${m.name} (have: ${known.join(", ") || "none"}).`
|
|
12963
|
+
);
|
|
12907
12964
|
}
|
|
12908
|
-
|
|
12909
|
-
|
|
12910
|
-
);
|
|
12965
|
+
const out = [p.sectionLine(app)];
|
|
12966
|
+
if (m.appStateNote) out.push(` ${p.dim(m.appStateNote)}`);
|
|
12967
|
+
const pad = Math.max(...targets.map((t) => t.target.length));
|
|
12968
|
+
for (const t of targets) {
|
|
12969
|
+
out.push(
|
|
12970
|
+
row(
|
|
12971
|
+
p,
|
|
12972
|
+
liveMarker(p, t.running),
|
|
12973
|
+
t.target,
|
|
12974
|
+
appTargetDetail(p, m.name, t),
|
|
12975
|
+
pad,
|
|
12976
|
+
2
|
|
12977
|
+
)
|
|
12978
|
+
);
|
|
12979
|
+
}
|
|
12980
|
+
return out.join("\n");
|
|
12911
12981
|
}
|
|
12912
|
-
|
|
12913
|
-
|
|
12914
|
-
|
|
12982
|
+
function renderService(m, name, p) {
|
|
12983
|
+
const s = m.services.find((svc) => svc.name === name);
|
|
12984
|
+
if (!s) {
|
|
12985
|
+
throw new Error(
|
|
12986
|
+
`No service "${name}" in ${m.name} (have: ${m.services.map((x) => x.name).join(", ") || "none"}).`
|
|
12987
|
+
);
|
|
12988
|
+
}
|
|
12989
|
+
const detail = s.running ? p.dim(typeof s.port === "number" ? `running :${s.port}` : "running") : p.dim(s.status || "stopped");
|
|
12990
|
+
return [
|
|
12991
|
+
p.sectionLine("Services"),
|
|
12992
|
+
row(p, liveMarker(p, s.running), s.name, detail, s.name.length)
|
|
12993
|
+
].join("\n");
|
|
12994
|
+
}
|
|
12995
|
+
var init_status = __esm({
|
|
12996
|
+
"src/status/index.ts"() {
|
|
12915
12997
|
"use strict";
|
|
12916
|
-
|
|
12917
|
-
|
|
12918
|
-
|
|
12919
|
-
|
|
12920
|
-
|
|
12921
|
-
|
|
12922
|
-
|
|
12923
|
-
|
|
12924
|
-
|
|
12925
|
-
|
|
12926
|
-
|
|
12927
|
-
|
|
12928
|
-
|
|
12998
|
+
init_io();
|
|
12999
|
+
init_paths();
|
|
13000
|
+
init_global();
|
|
13001
|
+
init_transform();
|
|
13002
|
+
init_launch_config();
|
|
13003
|
+
init_catalog();
|
|
13004
|
+
init_locate_running();
|
|
13005
|
+
init_dynamic();
|
|
13006
|
+
init_format();
|
|
13007
|
+
}
|
|
13008
|
+
});
|
|
13009
|
+
|
|
13010
|
+
// src/commands/status.ts
|
|
13011
|
+
import { defineCommand as defineCommand32 } from "citty";
|
|
13012
|
+
var statusCommand;
|
|
13013
|
+
var init_status2 = __esm({
|
|
13014
|
+
"src/commands/status.ts"() {
|
|
13015
|
+
"use strict";
|
|
13016
|
+
init_status();
|
|
13017
|
+
init_format();
|
|
13018
|
+
init_dispatch();
|
|
13019
|
+
statusCommand = defineCommand32({
|
|
13020
|
+
meta: {
|
|
13021
|
+
name: "status",
|
|
13022
|
+
group: "run",
|
|
13023
|
+
description: "Show the dev-container at a glance: container, services and apps (up/down), the ports it routes, and what the yml built in. With an <app> (or a service name), narrow to just that."
|
|
13024
|
+
},
|
|
13025
|
+
args: {
|
|
13026
|
+
name: {
|
|
13027
|
+
type: "positional",
|
|
13028
|
+
description: "Container name (yml in $MONOCEROS_HOME/container-configs/).",
|
|
13029
|
+
required: true
|
|
13030
|
+
},
|
|
13031
|
+
app: {
|
|
13032
|
+
type: "positional",
|
|
13033
|
+
description: "Narrow to one app (a path under projects/ with .monoceros/launch.json) or one compose service (e.g. postgres). Omit for the whole stack.",
|
|
13034
|
+
required: false
|
|
13035
|
+
}
|
|
13036
|
+
},
|
|
13037
|
+
run({ args }) {
|
|
13038
|
+
const filter = typeof args.app === "string" ? args.app : void 0;
|
|
13039
|
+
return dispatch(async () => {
|
|
13040
|
+
const model = await gatherStatus(args.name);
|
|
13041
|
+
const p = colorsFor(process.stdout);
|
|
13042
|
+
let block;
|
|
13043
|
+
if (filter) {
|
|
13044
|
+
const isApp = model.apps.some((a) => a.app === filter);
|
|
13045
|
+
block = isApp ? renderApp(model, filter, p) : renderService(model, filter, p);
|
|
13046
|
+
} else {
|
|
13047
|
+
block = renderStatus(model, p);
|
|
13048
|
+
}
|
|
13049
|
+
process.stdout.write(`
|
|
13050
|
+
${block}
|
|
13051
|
+
`);
|
|
13052
|
+
return 0;
|
|
12929
13053
|
});
|
|
12930
|
-
}
|
|
12931
|
-
|
|
12932
|
-
|
|
12933
|
-
|
|
13054
|
+
}
|
|
13055
|
+
});
|
|
13056
|
+
}
|
|
13057
|
+
});
|
|
13058
|
+
|
|
13059
|
+
// src/commands/stop.ts
|
|
13060
|
+
import { defineCommand as defineCommand33 } from "citty";
|
|
13061
|
+
import { consola as consola39 } from "consola";
|
|
13062
|
+
var stopCommand;
|
|
13063
|
+
var init_stop = __esm({
|
|
13064
|
+
"src/commands/stop.ts"() {
|
|
13065
|
+
"use strict";
|
|
13066
|
+
init_paths();
|
|
13067
|
+
init_compose();
|
|
13068
|
+
init_proxy();
|
|
13069
|
+
init_app_control();
|
|
13070
|
+
init_dispatch();
|
|
13071
|
+
stopCommand = defineCommand33({
|
|
13072
|
+
meta: {
|
|
13073
|
+
name: "stop",
|
|
13074
|
+
group: "run",
|
|
13075
|
+
description: "Stop the compose services for the named dev-container. With an <app>, stop that long-running app inside it instead (kills its process group)."
|
|
13076
|
+
},
|
|
13077
|
+
args: {
|
|
13078
|
+
name: {
|
|
13079
|
+
type: "positional",
|
|
13080
|
+
description: "Container name (yml in $MONOCEROS_HOME/container-configs/).",
|
|
13081
|
+
required: true
|
|
13082
|
+
},
|
|
13083
|
+
app: {
|
|
13084
|
+
type: "positional",
|
|
13085
|
+
description: "App to stop (a path under projects/ with .monoceros/launch.json). Omit to stop the container.",
|
|
13086
|
+
required: false
|
|
13087
|
+
},
|
|
13088
|
+
target: {
|
|
13089
|
+
type: "string",
|
|
13090
|
+
description: `Which launch target to stop (defaults to the app's "default" target, or its only one).`
|
|
13091
|
+
},
|
|
13092
|
+
service: {
|
|
13093
|
+
type: "string",
|
|
13094
|
+
description: "Restrict to a single compose service (e.g. postgres). Defaults to all."
|
|
13095
|
+
}
|
|
13096
|
+
},
|
|
13097
|
+
run({ args }) {
|
|
13098
|
+
if (typeof args.app === "string" && args.app.length > 0) {
|
|
13099
|
+
const app = args.app;
|
|
13100
|
+
const target = typeof args.target === "string" ? args.target : void 0;
|
|
13101
|
+
return dispatch(() => runAppCtl(args.name, ctlArgs("stop", app, target)));
|
|
13102
|
+
}
|
|
13103
|
+
return dispatch(async () => {
|
|
13104
|
+
const service = typeof args.service === "string" ? args.service : void 0;
|
|
13105
|
+
const exit = await runStop({
|
|
13106
|
+
root: containerDir(args.name),
|
|
13107
|
+
...service ? { service } : {},
|
|
13108
|
+
logger: { info: () => {
|
|
13109
|
+
} }
|
|
13110
|
+
});
|
|
13111
|
+
if (exit === 0) {
|
|
13112
|
+
consola39.success(
|
|
13113
|
+
service ? `Container '${args.name}' service '${service}' stopped.` : `Container '${args.name}' stopped.`
|
|
13114
|
+
);
|
|
13115
|
+
}
|
|
12934
13116
|
try {
|
|
12935
|
-
|
|
12936
|
-
|
|
13117
|
+
await maybeStopProxy({
|
|
13118
|
+
logger: { info: (msg) => consola39.info(msg) }
|
|
13119
|
+
});
|
|
13120
|
+
} catch (err) {
|
|
13121
|
+
consola39.warn(
|
|
13122
|
+
`Could not tear down the Traefik proxy: ${err instanceof Error ? err.message : String(err)}. Ignored.`
|
|
13123
|
+
);
|
|
12937
13124
|
}
|
|
12938
|
-
|
|
12939
|
-
|
|
12940
|
-
|
|
12941
|
-
|
|
12942
|
-
process.on("SIGINT", handler);
|
|
12943
|
-
return () => process.off("SIGINT", handler);
|
|
12944
|
-
};
|
|
12945
|
-
DEFAULT_LOCAL_ADDRESS = "127.0.0.1";
|
|
12946
|
-
IPV4_RE = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
13125
|
+
return exit;
|
|
13126
|
+
});
|
|
13127
|
+
}
|
|
13128
|
+
});
|
|
12947
13129
|
}
|
|
12948
13130
|
});
|
|
12949
13131
|
|
|
12950
13132
|
// src/commands/tunnel.ts
|
|
12951
|
-
import { defineCommand as
|
|
12952
|
-
import { consola as
|
|
13133
|
+
import { defineCommand as defineCommand34 } from "citty";
|
|
13134
|
+
import { consola as consola40 } from "consola";
|
|
12953
13135
|
function parseLocalPort(raw) {
|
|
12954
13136
|
if (raw === void 0) return void 0;
|
|
12955
13137
|
const n = Number(raw);
|
|
@@ -12965,7 +13147,7 @@ var init_tunnel = __esm({
|
|
|
12965
13147
|
"src/commands/tunnel.ts"() {
|
|
12966
13148
|
"use strict";
|
|
12967
13149
|
init_run3();
|
|
12968
|
-
tunnelCommand =
|
|
13150
|
+
tunnelCommand = defineCommand34({
|
|
12969
13151
|
meta: {
|
|
12970
13152
|
name: "tunnel",
|
|
12971
13153
|
group: "discovery",
|
|
@@ -13002,7 +13184,7 @@ var init_tunnel = __esm({
|
|
|
13002
13184
|
});
|
|
13003
13185
|
process.exit(exitCode);
|
|
13004
13186
|
} catch (err) {
|
|
13005
|
-
|
|
13187
|
+
consola40.error(err instanceof Error ? err.message : String(err));
|
|
13006
13188
|
process.exit(1);
|
|
13007
13189
|
}
|
|
13008
13190
|
}
|
|
@@ -13073,7 +13255,7 @@ var init_prune = __esm({
|
|
|
13073
13255
|
|
|
13074
13256
|
// src/upgrade/index.ts
|
|
13075
13257
|
import { existsSync as existsSync21, promises as fs20 } from "fs";
|
|
13076
|
-
import { consola as
|
|
13258
|
+
import { consola as consola41 } from "consola";
|
|
13077
13259
|
async function fetchRuntimeVersions() {
|
|
13078
13260
|
const tokenUrl = `https://ghcr.io/token?service=ghcr.io&scope=repository:${RUNTIME_REPO}:pull`;
|
|
13079
13261
|
const tokenRes = await fetch(tokenUrl);
|
|
@@ -13111,7 +13293,7 @@ ${yml}`;
|
|
|
13111
13293
|
}
|
|
13112
13294
|
async function runUpgrade(opts) {
|
|
13113
13295
|
const home = opts.monocerosHome ?? monocerosHome();
|
|
13114
|
-
const logger = opts.logger ??
|
|
13296
|
+
const logger = opts.logger ?? consola41;
|
|
13115
13297
|
const fetchVersions = opts.fetchVersions ?? fetchRuntimeVersions;
|
|
13116
13298
|
if (opts.list) {
|
|
13117
13299
|
const versions = await fetchVersions();
|
|
@@ -13257,7 +13439,7 @@ var init_upgrade = __esm({
|
|
|
13257
13439
|
});
|
|
13258
13440
|
|
|
13259
13441
|
// src/commands/upgrade.ts
|
|
13260
|
-
import { defineCommand as
|
|
13442
|
+
import { defineCommand as defineCommand35 } from "citty";
|
|
13261
13443
|
var upgradeCommand;
|
|
13262
13444
|
var init_upgrade2 = __esm({
|
|
13263
13445
|
"src/commands/upgrade.ts"() {
|
|
@@ -13265,7 +13447,7 @@ var init_upgrade2 = __esm({
|
|
|
13265
13447
|
init_upgrade();
|
|
13266
13448
|
init_version();
|
|
13267
13449
|
init_dispatch();
|
|
13268
|
-
upgradeCommand =
|
|
13450
|
+
upgradeCommand = defineCommand35({
|
|
13269
13451
|
meta: {
|
|
13270
13452
|
name: "upgrade",
|
|
13271
13453
|
group: "lifecycle",
|
|
@@ -13307,7 +13489,7 @@ var main_exports = {};
|
|
|
13307
13489
|
__export(main_exports, {
|
|
13308
13490
|
main: () => main
|
|
13309
13491
|
});
|
|
13310
|
-
import { defineCommand as
|
|
13492
|
+
import { defineCommand as defineCommand36 } from "citty";
|
|
13311
13493
|
var main;
|
|
13312
13494
|
var init_main = __esm({
|
|
13313
13495
|
"src/main.ts"() {
|
|
@@ -13340,6 +13522,7 @@ var init_main = __esm({
|
|
|
13340
13522
|
init_remove_repo();
|
|
13341
13523
|
init_remove_service();
|
|
13342
13524
|
init_run2();
|
|
13525
|
+
init_share();
|
|
13343
13526
|
init_shell2();
|
|
13344
13527
|
init_start();
|
|
13345
13528
|
init_status2();
|
|
@@ -13347,7 +13530,7 @@ var init_main = __esm({
|
|
|
13347
13530
|
init_tunnel();
|
|
13348
13531
|
init_upgrade2();
|
|
13349
13532
|
init_version();
|
|
13350
|
-
main =
|
|
13533
|
+
main = defineCommand36({
|
|
13351
13534
|
meta: {
|
|
13352
13535
|
name: "monoceros",
|
|
13353
13536
|
version: CLI_VERSION,
|
|
@@ -13384,6 +13567,7 @@ var init_main = __esm({
|
|
|
13384
13567
|
"remove-port": removePortCommand,
|
|
13385
13568
|
port: portCommand,
|
|
13386
13569
|
tunnel: tunnelCommand,
|
|
13570
|
+
share: shareCommand,
|
|
13387
13571
|
completion: completionCommand,
|
|
13388
13572
|
__complete: __completeCommand,
|
|
13389
13573
|
"__update-check": __updateCheckCommand,
|