@getmonoceros/workbench 1.36.0 → 1.36.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +1073 -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"))) {
|
|
@@ -8772,7 +8799,7 @@ var CLI_VERSION;
|
|
|
8772
8799
|
var init_version = __esm({
|
|
8773
8800
|
"src/version.ts"() {
|
|
8774
8801
|
"use strict";
|
|
8775
|
-
CLI_VERSION = true ? "1.36.
|
|
8802
|
+
CLI_VERSION = true ? "1.36.2" : "dev";
|
|
8776
8803
|
}
|
|
8777
8804
|
});
|
|
8778
8805
|
|
|
@@ -9324,6 +9351,22 @@ async function listAppCandidates(ctx) {
|
|
|
9324
9351
|
return [];
|
|
9325
9352
|
}
|
|
9326
9353
|
}
|
|
9354
|
+
async function listAppOrServiceCandidates(ctx) {
|
|
9355
|
+
const name = containerNameFromCtx(ctx);
|
|
9356
|
+
if (!name) return [];
|
|
9357
|
+
const apps = await listApps(name, ctx.opts.monocerosHome).catch(() => []);
|
|
9358
|
+
let services = [];
|
|
9359
|
+
try {
|
|
9360
|
+
const parsed = await readConfig(
|
|
9361
|
+
containerConfigPath(name, ctx.opts.monocerosHome)
|
|
9362
|
+
);
|
|
9363
|
+
services = solutionConfigToCreateOptions(parsed.config).services.map(
|
|
9364
|
+
(s) => s.name
|
|
9365
|
+
);
|
|
9366
|
+
} catch {
|
|
9367
|
+
}
|
|
9368
|
+
return [.../* @__PURE__ */ new Set([...apps, ...services])].sort();
|
|
9369
|
+
}
|
|
9327
9370
|
async function listTargetCandidates(ctx) {
|
|
9328
9371
|
const name = containerNameFromCtx(ctx);
|
|
9329
9372
|
const app = positionalFromCtx(ctx, 1);
|
|
@@ -9437,6 +9480,8 @@ var init_resolve = __esm({
|
|
|
9437
9480
|
"src/completion/resolve.ts"() {
|
|
9438
9481
|
"use strict";
|
|
9439
9482
|
init_paths();
|
|
9483
|
+
init_io();
|
|
9484
|
+
init_transform();
|
|
9440
9485
|
init_launch_config();
|
|
9441
9486
|
init_components();
|
|
9442
9487
|
init_manifest();
|
|
@@ -9476,6 +9521,7 @@ var init_resolve = __esm({
|
|
|
9476
9521
|
"remove-port",
|
|
9477
9522
|
"port",
|
|
9478
9523
|
"tunnel",
|
|
9524
|
+
"share",
|
|
9479
9525
|
"completion"
|
|
9480
9526
|
];
|
|
9481
9527
|
containerName = (ctx) => listContainerNames(ctx);
|
|
@@ -9543,7 +9589,9 @@ var init_resolve = __esm({
|
|
|
9543
9589
|
"--target": { type: "value", values: (ctx) => listTargetCandidates(ctx) }
|
|
9544
9590
|
}
|
|
9545
9591
|
},
|
|
9546
|
-
status: {
|
|
9592
|
+
status: {
|
|
9593
|
+
positionals: [containerName, (ctx) => listAppOrServiceCandidates(ctx)]
|
|
9594
|
+
},
|
|
9547
9595
|
"list-apps": { positionals: [containerName] },
|
|
9548
9596
|
"add-language": {
|
|
9549
9597
|
positionals: [containerName, () => listLanguageNames()]
|
|
@@ -9613,6 +9661,9 @@ var init_resolve = __esm({
|
|
|
9613
9661
|
"--local-address": { type: "value" }
|
|
9614
9662
|
}
|
|
9615
9663
|
},
|
|
9664
|
+
share: {
|
|
9665
|
+
positionals: [containerName, (ctx) => listAppCandidates(ctx)]
|
|
9666
|
+
},
|
|
9616
9667
|
completion: {
|
|
9617
9668
|
positionals: [() => listShellNames()]
|
|
9618
9669
|
},
|
|
@@ -11921,530 +11972,494 @@ var init_run2 = __esm({
|
|
|
11921
11972
|
}
|
|
11922
11973
|
});
|
|
11923
11974
|
|
|
11924
|
-
// src/
|
|
11925
|
-
import {
|
|
11926
|
-
import
|
|
11927
|
-
|
|
11928
|
-
|
|
11929
|
-
|
|
11930
|
-
|
|
11931
|
-
|
|
11932
|
-
|
|
11933
|
-
|
|
11934
|
-
|
|
11935
|
-
|
|
11936
|
-
|
|
11937
|
-
|
|
11938
|
-
|
|
11939
|
-
|
|
11940
|
-
|
|
11941
|
-
|
|
11942
|
-
|
|
11943
|
-
|
|
11944
|
-
|
|
11945
|
-
|
|
11946
|
-
|
|
11947
|
-
|
|
11948
|
-
|
|
11949
|
-
|
|
11950
|
-
|
|
11951
|
-
});
|
|
11952
|
-
process.exit(exitCode);
|
|
11953
|
-
} catch (err) {
|
|
11954
|
-
consola34.error(err instanceof Error ? err.message : String(err));
|
|
11955
|
-
process.exit(1);
|
|
11956
|
-
}
|
|
11957
|
-
}
|
|
11975
|
+
// src/tunnel/resolve.ts
|
|
11976
|
+
import { existsSync as existsSync20 } from "fs";
|
|
11977
|
+
import path32 from "path";
|
|
11978
|
+
async function resolveTunnelTarget(opts) {
|
|
11979
|
+
const ymlPath = containerConfigPath(opts.name, opts.monocerosHome);
|
|
11980
|
+
if (!existsSync20(ymlPath)) {
|
|
11981
|
+
throw new Error(
|
|
11982
|
+
`No yml profile for '${opts.name}' at ${ymlPath}. Run \`monoceros init ${opts.name}\` first.`
|
|
11983
|
+
);
|
|
11984
|
+
}
|
|
11985
|
+
const parsed = await readConfig(ymlPath);
|
|
11986
|
+
const config = parsed.config;
|
|
11987
|
+
const containerRoot = containerDir(opts.name, opts.monocerosHome);
|
|
11988
|
+
if (!existsSync20(containerRoot)) {
|
|
11989
|
+
throw new Error(
|
|
11990
|
+
`Container '${opts.name}' is not materialised at ${containerRoot}. Run \`monoceros apply ${opts.name}\` first.`
|
|
11991
|
+
);
|
|
11992
|
+
}
|
|
11993
|
+
const composePath = path32.join(containerRoot, ".devcontainer", "compose.yaml");
|
|
11994
|
+
const isCompose = existsSync20(composePath);
|
|
11995
|
+
const parsedTarget = parseTargetArg(opts.target, config);
|
|
11996
|
+
const docker = opts.docker ?? defaultDockerExec;
|
|
11997
|
+
if (isCompose) {
|
|
11998
|
+
return resolveCompose2({
|
|
11999
|
+
name: opts.name,
|
|
12000
|
+
containerRoot,
|
|
12001
|
+
parsedTarget
|
|
11958
12002
|
});
|
|
11959
12003
|
}
|
|
11960
|
-
|
|
11961
|
-
|
|
11962
|
-
|
|
11963
|
-
|
|
11964
|
-
|
|
11965
|
-
|
|
11966
|
-
|
|
11967
|
-
|
|
11968
|
-
|
|
11969
|
-
|
|
11970
|
-
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
|
|
11974
|
-
|
|
11975
|
-
|
|
11976
|
-
needsProxy = true;
|
|
11977
|
-
const global = await readMonocerosConfig();
|
|
11978
|
-
hostPort = proxyHostPort(global);
|
|
11979
|
-
}
|
|
11980
|
-
deferred = (parsed.config.services ?? []).filter((s) => serviceDefersStart(s.name)).map((s) => s.name);
|
|
11981
|
-
} catch (err) {
|
|
11982
|
-
consola35.warn(
|
|
11983
|
-
`Could not read container yml ahead of start: ${err instanceof Error ? err.message : String(err)}. Skipping Traefik pre-flight.`
|
|
12004
|
+
return resolveImageMode({
|
|
12005
|
+
name: opts.name,
|
|
12006
|
+
containerRoot,
|
|
12007
|
+
parsedTarget,
|
|
12008
|
+
config,
|
|
12009
|
+
docker
|
|
12010
|
+
});
|
|
12011
|
+
}
|
|
12012
|
+
function parseTargetArg(raw, config) {
|
|
12013
|
+
const colon = raw.indexOf(":");
|
|
12014
|
+
if (colon > 0) {
|
|
12015
|
+
const name = raw.slice(0, colon);
|
|
12016
|
+
const port = Number(raw.slice(colon + 1));
|
|
12017
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
12018
|
+
throw new Error(
|
|
12019
|
+
`Invalid target '${raw}'. Use <service>:<port> with a numeric port (1\u201365535), a bare port number, or a configured service name.`
|
|
11984
12020
|
);
|
|
11985
12021
|
}
|
|
11986
|
-
|
|
11987
|
-
|
|
11988
|
-
await ensureProxy({ hostPort });
|
|
11989
|
-
}
|
|
11990
|
-
const exitCode = await runStart({
|
|
11991
|
-
root: containerDir(args.name),
|
|
11992
|
-
silent: true,
|
|
11993
|
-
logger: { info: () => {
|
|
11994
|
-
} }
|
|
11995
|
-
});
|
|
11996
|
-
if (exitCode === 0 && runtimeSupportsBrowserBridge(runtimeVersion)) {
|
|
11997
|
-
spawnBridgeDaemon(containerDir(args.name));
|
|
11998
|
-
}
|
|
11999
|
-
if (exitCode === 0 && deferred.length > 0) {
|
|
12000
|
-
try {
|
|
12001
|
-
const deferExit = await startDeferredServices({
|
|
12002
|
-
root: containerDir(args.name),
|
|
12003
|
-
services: deferred,
|
|
12004
|
-
logger: consola35
|
|
12005
|
-
});
|
|
12006
|
-
if (deferExit !== 0) {
|
|
12007
|
-
consola35.warn(
|
|
12008
|
-
`Deferred service(s) ${deferred.join(", ")} did not start cleanly (exit ${deferExit}).`
|
|
12009
|
-
);
|
|
12010
|
-
}
|
|
12011
|
-
} catch (err) {
|
|
12012
|
-
consola35.warn(
|
|
12013
|
-
`Could not start deferred service(s) ${deferred.join(", ")}: ${err instanceof Error ? err.message : String(err)}`
|
|
12014
|
-
);
|
|
12015
|
-
}
|
|
12016
|
-
}
|
|
12017
|
-
if (exitCode === 0) {
|
|
12018
|
-
consola35.success(`Container '${args.name}' is up.`);
|
|
12019
|
-
}
|
|
12020
|
-
if (args.open && exitCode === 0) {
|
|
12021
|
-
try {
|
|
12022
|
-
await runOpen({ name: args.name, tool: args.open });
|
|
12023
|
-
} catch (err) {
|
|
12024
|
-
consola35.warn(err instanceof Error ? err.message : String(err));
|
|
12025
|
-
}
|
|
12026
|
-
}
|
|
12027
|
-
return exitCode;
|
|
12022
|
+
findConfiguredService(config, name);
|
|
12023
|
+
return { kind: "service", service: name, port };
|
|
12028
12024
|
}
|
|
12029
|
-
|
|
12030
|
-
|
|
12031
|
-
|
|
12032
|
-
"src/commands/start.ts"() {
|
|
12033
|
-
"use strict";
|
|
12034
|
-
init_global();
|
|
12035
|
-
init_io();
|
|
12036
|
-
init_paths();
|
|
12037
|
-
init_bridge_daemon();
|
|
12038
|
-
init_compose();
|
|
12039
|
-
init_catalog();
|
|
12040
|
-
init_open();
|
|
12041
|
-
init_proxy();
|
|
12042
|
-
init_port_check();
|
|
12043
|
-
init_app_control();
|
|
12044
|
-
init_dispatch();
|
|
12045
|
-
startCommand = defineCommand30({
|
|
12046
|
-
meta: {
|
|
12047
|
-
name: "start",
|
|
12048
|
-
group: "run",
|
|
12049
|
-
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."
|
|
12050
|
-
},
|
|
12051
|
-
args: {
|
|
12052
|
-
name: {
|
|
12053
|
-
type: "positional",
|
|
12054
|
-
description: "Container name (yml in $MONOCEROS_HOME/container-configs/).",
|
|
12055
|
-
required: true
|
|
12056
|
-
},
|
|
12057
|
-
app: {
|
|
12058
|
-
type: "positional",
|
|
12059
|
-
description: "App to start (a path under projects/ with .monoceros/launch.json). Omit to just bring the container up.",
|
|
12060
|
-
required: false
|
|
12061
|
-
},
|
|
12062
|
-
target: {
|
|
12063
|
-
type: "string",
|
|
12064
|
-
description: `Which launch target to start (defaults to the app's "default" target, or its only one).`
|
|
12065
|
-
},
|
|
12066
|
-
open: {
|
|
12067
|
-
type: "string",
|
|
12068
|
-
description: `After a successful start, open the container in this tool (${OPEN_TOOLS.join("|")}).`
|
|
12069
|
-
}
|
|
12070
|
-
},
|
|
12071
|
-
run({ args }) {
|
|
12072
|
-
if (typeof args.app === "string" && args.app.length > 0) {
|
|
12073
|
-
const app = args.app;
|
|
12074
|
-
const target = typeof args.target === "string" ? args.target : void 0;
|
|
12075
|
-
return dispatch(async () => {
|
|
12076
|
-
if (!await findRunningContainer(args.name)) {
|
|
12077
|
-
const up = await bringContainerUp(args.name, void 0);
|
|
12078
|
-
if (up !== 0) return up;
|
|
12079
|
-
}
|
|
12080
|
-
return runAppCtl(args.name, ctlArgs("start", app, target));
|
|
12081
|
-
});
|
|
12082
|
-
}
|
|
12083
|
-
return dispatch(
|
|
12084
|
-
() => bringContainerUp(
|
|
12085
|
-
args.name,
|
|
12086
|
-
typeof args.open === "string" ? args.open : void 0
|
|
12087
|
-
)
|
|
12088
|
-
);
|
|
12089
|
-
}
|
|
12090
|
-
});
|
|
12025
|
+
const asNumber = Number(raw);
|
|
12026
|
+
if (Number.isInteger(asNumber) && asNumber > 0 && asNumber < 65536) {
|
|
12027
|
+
return { kind: "port", port: asNumber };
|
|
12091
12028
|
}
|
|
12092
|
-
|
|
12093
|
-
|
|
12094
|
-
|
|
12095
|
-
|
|
12096
|
-
|
|
12097
|
-
|
|
12098
|
-
return
|
|
12099
|
-
}
|
|
12100
|
-
function cols(line) {
|
|
12101
|
-
return line.split(" ");
|
|
12029
|
+
const match = findConfiguredService(config, raw);
|
|
12030
|
+
if (match.port === void 0) {
|
|
12031
|
+
throw new Error(
|
|
12032
|
+
`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>\`.`
|
|
12033
|
+
);
|
|
12034
|
+
}
|
|
12035
|
+
return { kind: "service", service: raw, port: match.port };
|
|
12102
12036
|
}
|
|
12103
|
-
function
|
|
12104
|
-
|
|
12037
|
+
function findConfiguredService(config, name) {
|
|
12038
|
+
const services = config.services.map(resolveService);
|
|
12039
|
+
const match = services.find((s) => s.name === name);
|
|
12040
|
+
if (!match) {
|
|
12041
|
+
const names = services.map((s) => s.name);
|
|
12042
|
+
const list = names.length > 0 ? names.join(", ") : "(none configured)";
|
|
12043
|
+
throw new Error(
|
|
12044
|
+
`Service '${name}' is not configured in this container's yml. Configured services: ${list}. Or pass a port number (e.g. \`monoceros tunnel <name> 8080\`).`
|
|
12045
|
+
);
|
|
12046
|
+
}
|
|
12047
|
+
return match;
|
|
12105
12048
|
}
|
|
12106
|
-
|
|
12107
|
-
const
|
|
12108
|
-
|
|
12109
|
-
|
|
12110
|
-
|
|
12111
|
-
|
|
12112
|
-
|
|
12113
|
-
|
|
12114
|
-
|
|
12115
|
-
try {
|
|
12116
|
-
const parsed = await readConfig(containerConfigPath(name, opts.home));
|
|
12117
|
-
const createOpts = solutionConfigToCreateOptions(parsed.config);
|
|
12118
|
-
configured = true;
|
|
12119
|
-
runtimeVersion = createOpts.runtimeVersion;
|
|
12120
|
-
languages = createOpts.languages;
|
|
12121
|
-
features = Object.keys(createOpts.features ?? {}).map(shortFeatureName2);
|
|
12122
|
-
declaredServices = createOpts.services.map((s) => ({
|
|
12123
|
-
name: s.name,
|
|
12124
|
-
...typeof s.port === "number" ? { port: s.port } : {}
|
|
12125
|
-
}));
|
|
12126
|
-
declaredPorts = createOpts.ports ?? [];
|
|
12127
|
-
} catch {
|
|
12049
|
+
function resolveCompose2(args) {
|
|
12050
|
+
const network = `${composeProjectName(args.containerRoot)}_default`;
|
|
12051
|
+
if (args.parsedTarget.kind === "service") {
|
|
12052
|
+
return {
|
|
12053
|
+
network,
|
|
12054
|
+
targetHost: args.parsedTarget.service,
|
|
12055
|
+
internalPort: args.parsedTarget.port,
|
|
12056
|
+
display: `${args.name}/${args.parsedTarget.service}:${args.parsedTarget.port}`
|
|
12057
|
+
};
|
|
12128
12058
|
}
|
|
12129
|
-
|
|
12130
|
-
|
|
12131
|
-
|
|
12132
|
-
|
|
12133
|
-
|
|
12059
|
+
return {
|
|
12060
|
+
network,
|
|
12061
|
+
targetHost: "workspace",
|
|
12062
|
+
internalPort: args.parsedTarget.port,
|
|
12063
|
+
display: `${args.name}:${args.parsedTarget.port}`
|
|
12134
12064
|
};
|
|
12135
|
-
|
|
12136
|
-
|
|
12137
|
-
|
|
12065
|
+
}
|
|
12066
|
+
async function resolveImageMode(args) {
|
|
12067
|
+
if (args.parsedTarget.kind === "service") {
|
|
12068
|
+
throw new Error(
|
|
12069
|
+
`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.`
|
|
12070
|
+
);
|
|
12071
|
+
}
|
|
12072
|
+
const ports = args.config.routing?.ports ?? [];
|
|
12073
|
+
if (ports.length > 0) {
|
|
12074
|
+
return {
|
|
12075
|
+
network: PROXY_NETWORK_NAME,
|
|
12076
|
+
targetHost: args.name,
|
|
12077
|
+
internalPort: args.parsedTarget.port,
|
|
12078
|
+
display: `${args.name}:${args.parsedTarget.port}`
|
|
12079
|
+
};
|
|
12080
|
+
}
|
|
12081
|
+
const { network, ip } = await lookupContainerNetwork({
|
|
12082
|
+
containerRoot: args.containerRoot,
|
|
12083
|
+
docker: args.docker
|
|
12084
|
+
});
|
|
12085
|
+
return {
|
|
12086
|
+
network,
|
|
12087
|
+
targetHost: ip,
|
|
12088
|
+
internalPort: args.parsedTarget.port,
|
|
12089
|
+
display: `${args.name}:${args.parsedTarget.port}`
|
|
12090
|
+
};
|
|
12091
|
+
}
|
|
12092
|
+
async function lookupContainerNetwork(args) {
|
|
12093
|
+
const psResult = await args.docker([
|
|
12138
12094
|
"ps",
|
|
12139
|
-
"-
|
|
12140
|
-
"--no-trunc",
|
|
12095
|
+
"-q",
|
|
12141
12096
|
"--filter",
|
|
12142
|
-
`label=devcontainer.local_folder=${
|
|
12143
|
-
"--format",
|
|
12144
|
-
'{{.ID}} {{.State}} {{.Status}} {{.Names}} {{.Label "com.docker.compose.project"}}'
|
|
12097
|
+
`label=devcontainer.local_folder=${args.containerRoot}`
|
|
12145
12098
|
]);
|
|
12146
|
-
|
|
12147
|
-
|
|
12148
|
-
|
|
12149
|
-
|
|
12150
|
-
composeProject = project ?? "";
|
|
12151
|
-
container = {
|
|
12152
|
-
exists: true,
|
|
12153
|
-
running: state === "running",
|
|
12154
|
-
status: status ?? "",
|
|
12155
|
-
dockerName: names ?? ""
|
|
12156
|
-
};
|
|
12157
|
-
}
|
|
12158
|
-
const services = [];
|
|
12159
|
-
if (declaredServices.length > 0) {
|
|
12160
|
-
const live = /* @__PURE__ */ new Map();
|
|
12161
|
-
if (composeProject) {
|
|
12162
|
-
const sps = await docker([
|
|
12163
|
-
"ps",
|
|
12164
|
-
"-a",
|
|
12165
|
-
"--filter",
|
|
12166
|
-
`label=com.docker.compose.project=${composeProject}`,
|
|
12167
|
-
"--format",
|
|
12168
|
-
'{{.Label "com.docker.compose.service"}} {{.State}} {{.Status}}'
|
|
12169
|
-
]);
|
|
12170
|
-
for (const l of nonEmptyLines(sps.stdout)) {
|
|
12171
|
-
const [svc, state, status] = cols(l);
|
|
12172
|
-
if (svc)
|
|
12173
|
-
live.set(svc, { running: state === "running", status: status ?? "" });
|
|
12174
|
-
}
|
|
12175
|
-
}
|
|
12176
|
-
for (const s of declaredServices) {
|
|
12177
|
-
const m = live.get(s.name);
|
|
12178
|
-
services.push({
|
|
12179
|
-
name: s.name,
|
|
12180
|
-
running: m?.running ?? false,
|
|
12181
|
-
status: m?.status ?? "not created",
|
|
12182
|
-
...typeof s.port === "number" ? { port: s.port } : {}
|
|
12183
|
-
});
|
|
12184
|
-
}
|
|
12185
|
-
}
|
|
12186
|
-
const apps = [];
|
|
12187
|
-
for (const app of await listApps(name, opts.home)) {
|
|
12188
|
-
const cfg = await readLaunchConfig(name, app, opts.home);
|
|
12189
|
-
if (!cfg) continue;
|
|
12190
|
-
for (const t of cfg.configurations) {
|
|
12191
|
-
apps.push({
|
|
12192
|
-
app,
|
|
12193
|
-
target: t.name,
|
|
12194
|
-
port: typeof t.port === "number" ? t.port : null,
|
|
12195
|
-
default: t.default === true,
|
|
12196
|
-
running: void 0,
|
|
12197
|
-
pid: null
|
|
12198
|
-
});
|
|
12199
|
-
}
|
|
12099
|
+
if (psResult.exitCode !== 0) {
|
|
12100
|
+
throw new Error(
|
|
12101
|
+
`docker ps failed: ${psResult.stderr.trim() || `exit ${psResult.exitCode}`}`
|
|
12102
|
+
);
|
|
12200
12103
|
}
|
|
12201
|
-
const
|
|
12202
|
-
|
|
12203
|
-
|
|
12204
|
-
|
|
12205
|
-
|
|
12206
|
-
"exec",
|
|
12207
|
-
containerId,
|
|
12208
|
-
"monoceros-ctl",
|
|
12209
|
-
"list",
|
|
12210
|
-
"--json"
|
|
12211
|
-
]);
|
|
12212
|
-
if (res.exitCode === 0) {
|
|
12213
|
-
const byKey = /* @__PURE__ */ new Map();
|
|
12214
|
-
for (const l of nonEmptyLines(res.stdout)) {
|
|
12215
|
-
try {
|
|
12216
|
-
const o = JSON.parse(l);
|
|
12217
|
-
byKey.set(`${o.app}\0${o.target}`, {
|
|
12218
|
-
running: !!o.running,
|
|
12219
|
-
pid: o.pid ?? null
|
|
12220
|
-
});
|
|
12221
|
-
} catch {
|
|
12222
|
-
}
|
|
12223
|
-
}
|
|
12224
|
-
for (const a of apps) {
|
|
12225
|
-
const o = byKey.get(`${a.app}\0${a.target}`);
|
|
12226
|
-
if (o) {
|
|
12227
|
-
a.running = o.running;
|
|
12228
|
-
a.pid = o.pid;
|
|
12229
|
-
} else {
|
|
12230
|
-
a.running = false;
|
|
12231
|
-
}
|
|
12232
|
-
}
|
|
12233
|
-
} else {
|
|
12234
|
-
appStateKnown = false;
|
|
12235
|
-
appStateNote = `could not read run state from the runner - rebuild the runtime image, then \`monoceros upgrade ${name}\``;
|
|
12236
|
-
}
|
|
12104
|
+
const containerId = psResult.stdout.trim().split("\n")[0]?.trim();
|
|
12105
|
+
if (!containerId) {
|
|
12106
|
+
throw new Error(
|
|
12107
|
+
`No running container for '${args.containerRoot}'. Start it with \`monoceros start <name>\` (or open a shell with \`monoceros shell <name>\`) and retry.`
|
|
12108
|
+
);
|
|
12237
12109
|
}
|
|
12238
|
-
|
|
12239
|
-
|
|
12110
|
+
const inspect = await args.docker([
|
|
12111
|
+
"inspect",
|
|
12112
|
+
"--format",
|
|
12113
|
+
"{{json .NetworkSettings.Networks}}",
|
|
12114
|
+
containerId
|
|
12115
|
+
]);
|
|
12116
|
+
if (inspect.exitCode !== 0) {
|
|
12117
|
+
throw new Error(
|
|
12118
|
+
`docker inspect failed: ${inspect.stderr.trim() || `exit ${inspect.exitCode}`}`
|
|
12119
|
+
);
|
|
12240
12120
|
}
|
|
12241
|
-
let
|
|
12121
|
+
let networks = null;
|
|
12242
12122
|
try {
|
|
12243
|
-
|
|
12244
|
-
await readMonocerosConfig({
|
|
12245
|
-
...opts.home ? { monocerosHome: opts.home } : {}
|
|
12246
|
-
})
|
|
12247
|
-
);
|
|
12123
|
+
networks = JSON.parse(inspect.stdout);
|
|
12248
12124
|
} catch {
|
|
12125
|
+
throw new Error(
|
|
12126
|
+
`Unexpected docker inspect output: ${inspect.stdout.slice(0, 200)}`
|
|
12127
|
+
);
|
|
12249
12128
|
}
|
|
12250
|
-
|
|
12251
|
-
|
|
12252
|
-
|
|
12253
|
-
configured,
|
|
12254
|
-
container,
|
|
12255
|
-
services,
|
|
12256
|
-
apps,
|
|
12257
|
-
appStateKnown,
|
|
12258
|
-
...appStateNote ? { appStateNote } : {},
|
|
12259
|
-
ports,
|
|
12260
|
-
builtIn: { languages, features }
|
|
12261
|
-
};
|
|
12262
|
-
}
|
|
12263
|
-
function row(p, marker, name, detail, pad, indent = 2) {
|
|
12264
|
-
const gap = Math.max(2, pad - name.length + 2);
|
|
12265
|
-
return `${" ".repeat(indent)}${marker} ${p.cyan(name)}${" ".repeat(gap)}${detail}`;
|
|
12266
|
-
}
|
|
12267
|
-
function liveMarker(p, running) {
|
|
12268
|
-
if (running === void 0) return " ";
|
|
12269
|
-
return running ? p.green("\u2713") : p.dim("\xB7");
|
|
12270
|
-
}
|
|
12271
|
-
function renderContainer(p, m) {
|
|
12272
|
-
const out = [p.sectionLine(m.name)];
|
|
12273
|
-
if (!m.container.exists) {
|
|
12274
|
-
out.push(
|
|
12275
|
-
row(
|
|
12276
|
-
p,
|
|
12277
|
-
p.dim("\xB7"),
|
|
12278
|
-
"(not created)",
|
|
12279
|
-
p.dim(`run \`monoceros apply ${m.name}\``),
|
|
12280
|
-
14
|
|
12281
|
-
)
|
|
12129
|
+
if (!networks) {
|
|
12130
|
+
throw new Error(
|
|
12131
|
+
`Container ${containerId} reports no networks. Restart it and retry.`
|
|
12282
12132
|
);
|
|
12283
|
-
return out;
|
|
12284
12133
|
}
|
|
12285
|
-
const
|
|
12286
|
-
|
|
12287
|
-
|
|
12288
|
-
|
|
12289
|
-
|
|
12290
|
-
|
|
12291
|
-
|
|
12292
|
-
14
|
|
12293
|
-
)
|
|
12134
|
+
for (const [name, settings] of Object.entries(networks)) {
|
|
12135
|
+
if (settings.IPAddress && settings.IPAddress.length > 0) {
|
|
12136
|
+
return { network: name, ip: settings.IPAddress };
|
|
12137
|
+
}
|
|
12138
|
+
}
|
|
12139
|
+
throw new Error(
|
|
12140
|
+
`Container ${containerId} has no network with a reachable IP. Restart it and retry.`
|
|
12294
12141
|
);
|
|
12295
|
-
return out;
|
|
12296
12142
|
}
|
|
12297
|
-
|
|
12298
|
-
|
|
12299
|
-
|
|
12300
|
-
|
|
12301
|
-
|
|
12302
|
-
|
|
12303
|
-
|
|
12143
|
+
var init_resolve2 = __esm({
|
|
12144
|
+
"src/tunnel/resolve.ts"() {
|
|
12145
|
+
"use strict";
|
|
12146
|
+
init_paths();
|
|
12147
|
+
init_io();
|
|
12148
|
+
init_compose();
|
|
12149
|
+
init_catalog();
|
|
12150
|
+
init_proxy();
|
|
12304
12151
|
}
|
|
12305
|
-
|
|
12152
|
+
});
|
|
12153
|
+
|
|
12154
|
+
// src/tunnel/port-check.ts
|
|
12155
|
+
import { Socket as Socket2 } from "net";
|
|
12156
|
+
async function preflightLocalPort(opts) {
|
|
12157
|
+
const probe = opts.probe ?? realPortProbe2;
|
|
12158
|
+
const result = await probe(opts.port, opts.address);
|
|
12159
|
+
if (result.ok) return;
|
|
12160
|
+
throw new Error(formatLocalPortHeldError(opts.port, opts.address, result));
|
|
12306
12161
|
}
|
|
12307
|
-
function
|
|
12308
|
-
const
|
|
12309
|
-
if (
|
|
12310
|
-
|
|
12311
|
-
|
|
12162
|
+
function formatLocalPortHeldError(port, address, result) {
|
|
12163
|
+
const lines = [];
|
|
12164
|
+
if (result.code === "EADDRINUSE") {
|
|
12165
|
+
lines.push(`Local port ${port} on ${address} is already in use.`);
|
|
12166
|
+
lines.push("");
|
|
12167
|
+
lines.push("Identify the holder, then either stop it or pick a different");
|
|
12168
|
+
lines.push("port for the tunnel:");
|
|
12169
|
+
lines.push("");
|
|
12170
|
+
lines.push(` sudo lsof -iTCP:${port} -sTCP:LISTEN -n -P`);
|
|
12171
|
+
lines.push(` # or: sudo ss -tlnp | grep ":${port}\\b"`);
|
|
12172
|
+
lines.push("");
|
|
12173
|
+
lines.push("Re-run with an explicit local port:");
|
|
12174
|
+
lines.push(` monoceros tunnel \u2026 --local-port=${port + 1}`);
|
|
12175
|
+
} else {
|
|
12176
|
+
lines.push(
|
|
12177
|
+
`Cannot probe local port ${port} on ${address}: ${result.message}`
|
|
12312
12178
|
);
|
|
12313
|
-
|
|
12314
|
-
|
|
12315
|
-
|
|
12179
|
+
lines.push("");
|
|
12180
|
+
lines.push(
|
|
12181
|
+
"Most likely the host network stack (firewall, namespace) is interfering."
|
|
12182
|
+
);
|
|
12183
|
+
lines.push("Try a different local port via `--local-port=<n>`.");
|
|
12316
12184
|
}
|
|
12317
|
-
|
|
12318
|
-
return bits.join(" ");
|
|
12185
|
+
return lines.join("\n");
|
|
12319
12186
|
}
|
|
12320
|
-
|
|
12321
|
-
|
|
12322
|
-
|
|
12323
|
-
|
|
12324
|
-
|
|
12325
|
-
|
|
12326
|
-
|
|
12327
|
-
|
|
12328
|
-
|
|
12187
|
+
var CONNECT_TIMEOUT_MS2, realPortProbe2;
|
|
12188
|
+
var init_port_check2 = __esm({
|
|
12189
|
+
"src/tunnel/port-check.ts"() {
|
|
12190
|
+
"use strict";
|
|
12191
|
+
CONNECT_TIMEOUT_MS2 = 750;
|
|
12192
|
+
realPortProbe2 = (port, address) => {
|
|
12193
|
+
const probeHost = address === "0.0.0.0" ? "127.0.0.1" : address;
|
|
12194
|
+
return new Promise((resolve) => {
|
|
12195
|
+
const socket = new Socket2();
|
|
12196
|
+
let settled = false;
|
|
12197
|
+
const settle = (result) => {
|
|
12198
|
+
if (settled) return;
|
|
12199
|
+
settled = true;
|
|
12200
|
+
socket.destroy();
|
|
12201
|
+
resolve(result);
|
|
12202
|
+
};
|
|
12203
|
+
socket.setTimeout(CONNECT_TIMEOUT_MS2);
|
|
12204
|
+
socket.once("connect", () => {
|
|
12205
|
+
settle({
|
|
12206
|
+
ok: false,
|
|
12207
|
+
code: "EADDRINUSE",
|
|
12208
|
+
message: `another process is listening on ${port}`
|
|
12209
|
+
});
|
|
12210
|
+
});
|
|
12211
|
+
socket.once("timeout", () => {
|
|
12212
|
+
settle({ ok: true });
|
|
12213
|
+
});
|
|
12214
|
+
socket.once("error", (err) => {
|
|
12215
|
+
const code = err.code ?? "UNKNOWN";
|
|
12216
|
+
if (code === "ECONNREFUSED") {
|
|
12217
|
+
settle({ ok: true });
|
|
12218
|
+
} else {
|
|
12219
|
+
settle({ ok: false, code, message: err.message });
|
|
12220
|
+
}
|
|
12221
|
+
});
|
|
12222
|
+
socket.connect(port, probeHost);
|
|
12223
|
+
});
|
|
12224
|
+
};
|
|
12329
12225
|
}
|
|
12330
|
-
|
|
12331
|
-
|
|
12332
|
-
|
|
12333
|
-
|
|
12334
|
-
|
|
12335
|
-
|
|
12336
|
-
|
|
12337
|
-
|
|
12338
|
-
|
|
12339
|
-
|
|
12340
|
-
|
|
12341
|
-
|
|
12342
|
-
|
|
12343
|
-
);
|
|
12344
|
-
}
|
|
12226
|
+
});
|
|
12227
|
+
|
|
12228
|
+
// src/tunnel/run.ts
|
|
12229
|
+
import { spawn as spawn14 } from "child_process";
|
|
12230
|
+
import { consola as consola34 } from "consola";
|
|
12231
|
+
function signalNumber(signal) {
|
|
12232
|
+
switch (signal) {
|
|
12233
|
+
case "SIGINT":
|
|
12234
|
+
return 2;
|
|
12235
|
+
case "SIGTERM":
|
|
12236
|
+
return 15;
|
|
12237
|
+
default:
|
|
12238
|
+
return 1;
|
|
12345
12239
|
}
|
|
12346
|
-
return out;
|
|
12347
12240
|
}
|
|
12348
|
-
function
|
|
12349
|
-
|
|
12350
|
-
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
|
|
12354
|
-
|
|
12241
|
+
async function runTunnel(opts) {
|
|
12242
|
+
const log = opts.logger ?? {
|
|
12243
|
+
info: (m) => consola34.info(m),
|
|
12244
|
+
warn: (m) => consola34.warn(m)
|
|
12245
|
+
};
|
|
12246
|
+
const resolve = opts.resolve ?? resolveTunnelTarget;
|
|
12247
|
+
const resolveArgs3 = {
|
|
12248
|
+
name: opts.name,
|
|
12249
|
+
target: opts.target,
|
|
12250
|
+
...opts.monocerosHome !== void 0 ? { monocerosHome: opts.monocerosHome } : {}
|
|
12251
|
+
};
|
|
12252
|
+
const resolved = await resolve(resolveArgs3);
|
|
12253
|
+
const localPort = opts.localPort ?? resolved.internalPort;
|
|
12254
|
+
const localAddress = opts.localAddress ?? DEFAULT_LOCAL_ADDRESS;
|
|
12255
|
+
validateLocalAddress(localAddress);
|
|
12256
|
+
const preflight = opts.preflight ?? preflightLocalPort;
|
|
12257
|
+
await preflight({ port: localPort, address: localAddress });
|
|
12258
|
+
const dockerSpawn = opts.dockerSpawn ?? defaultDockerSpawn;
|
|
12259
|
+
const installSignalHandler = opts.installSignalHandler ?? installSigintDefault;
|
|
12260
|
+
const dockerArgs = buildDockerArgs({
|
|
12261
|
+
localAddress,
|
|
12262
|
+
localPort,
|
|
12263
|
+
internalPort: resolved.internalPort,
|
|
12264
|
+
network: resolved.network,
|
|
12265
|
+
targetHost: resolved.targetHost
|
|
12266
|
+
});
|
|
12267
|
+
log.info(
|
|
12268
|
+
`Tunnel: ${localAddress}:${localPort} \u2192 ${resolved.display}:${resolved.internalPort} (Ctrl+C to stop)`
|
|
12269
|
+
);
|
|
12270
|
+
const handle = dockerSpawn(dockerArgs);
|
|
12271
|
+
const uninstall = installSignalHandler(() => {
|
|
12272
|
+
});
|
|
12273
|
+
try {
|
|
12274
|
+
const exitCode = await handle.exited;
|
|
12275
|
+
if (exitCode === 130) return 0;
|
|
12276
|
+
return exitCode;
|
|
12277
|
+
} finally {
|
|
12278
|
+
uninstall();
|
|
12355
12279
|
}
|
|
12356
|
-
return out;
|
|
12357
12280
|
}
|
|
12358
|
-
function
|
|
12359
|
-
|
|
12360
|
-
|
|
12361
|
-
|
|
12362
|
-
|
|
12363
|
-
|
|
12364
|
-
|
|
12365
|
-
|
|
12366
|
-
|
|
12367
|
-
|
|
12368
|
-
|
|
12281
|
+
function buildDockerArgs(input) {
|
|
12282
|
+
return [
|
|
12283
|
+
"run",
|
|
12284
|
+
"--rm",
|
|
12285
|
+
"-i",
|
|
12286
|
+
`--network=${input.network}`,
|
|
12287
|
+
"-p",
|
|
12288
|
+
`${input.localAddress}:${input.localPort}:${input.internalPort}`,
|
|
12289
|
+
SOCAT_IMAGE,
|
|
12290
|
+
`TCP-LISTEN:${input.internalPort},fork,reuseaddr`,
|
|
12291
|
+
`TCP:${input.targetHost}:${input.internalPort}`
|
|
12292
|
+
];
|
|
12293
|
+
}
|
|
12294
|
+
function validateLocalAddress(addr) {
|
|
12295
|
+
if (addr === "localhost") return;
|
|
12296
|
+
if (IPV4_RE.test(addr)) {
|
|
12297
|
+
for (const part of addr.split(".")) {
|
|
12298
|
+
const n = Number(part);
|
|
12299
|
+
if (n < 0 || n > 255) {
|
|
12300
|
+
throw new Error(
|
|
12301
|
+
`Invalid --local-address '${addr}': each dotted-quad octet must be 0-255.`
|
|
12302
|
+
);
|
|
12303
|
+
}
|
|
12304
|
+
}
|
|
12305
|
+
return;
|
|
12369
12306
|
}
|
|
12370
|
-
|
|
12307
|
+
throw new Error(
|
|
12308
|
+
`Invalid --local-address '${addr}'. Use 127.0.0.1 (default), 0.0.0.0, or a specific IPv4 address.`
|
|
12309
|
+
);
|
|
12371
12310
|
}
|
|
12372
|
-
|
|
12373
|
-
|
|
12374
|
-
|
|
12375
|
-
|
|
12376
|
-
|
|
12377
|
-
|
|
12378
|
-
|
|
12379
|
-
|
|
12311
|
+
var SOCAT_IMAGE, defaultDockerSpawn, installSigintDefault, DEFAULT_LOCAL_ADDRESS, IPV4_RE;
|
|
12312
|
+
var init_run3 = __esm({
|
|
12313
|
+
"src/tunnel/run.ts"() {
|
|
12314
|
+
"use strict";
|
|
12315
|
+
init_resolve2();
|
|
12316
|
+
init_port_check2();
|
|
12317
|
+
SOCAT_IMAGE = "alpine/socat:1.8.0.3";
|
|
12318
|
+
defaultDockerSpawn = (args) => {
|
|
12319
|
+
const child = spawn14("docker", args, {
|
|
12320
|
+
stdio: "inherit"
|
|
12321
|
+
});
|
|
12322
|
+
const exited = new Promise((resolve, reject) => {
|
|
12323
|
+
child.on("error", reject);
|
|
12324
|
+
child.on("exit", (code, signal) => {
|
|
12325
|
+
if (typeof code === "number") resolve(code);
|
|
12326
|
+
else if (signal) resolve(128 + signalNumber(signal));
|
|
12327
|
+
else resolve(0);
|
|
12328
|
+
});
|
|
12329
|
+
});
|
|
12330
|
+
return {
|
|
12331
|
+
exited,
|
|
12332
|
+
kill: (signal) => {
|
|
12333
|
+
try {
|
|
12334
|
+
child.kill(signal);
|
|
12335
|
+
} catch {
|
|
12336
|
+
}
|
|
12337
|
+
}
|
|
12338
|
+
};
|
|
12339
|
+
};
|
|
12340
|
+
installSigintDefault = (handler) => {
|
|
12341
|
+
process.on("SIGINT", handler);
|
|
12342
|
+
return () => process.off("SIGINT", handler);
|
|
12343
|
+
};
|
|
12344
|
+
DEFAULT_LOCAL_ADDRESS = "127.0.0.1";
|
|
12345
|
+
IPV4_RE = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
12346
|
+
}
|
|
12347
|
+
});
|
|
12348
|
+
|
|
12349
|
+
// src/share/run.ts
|
|
12350
|
+
import os3 from "os";
|
|
12351
|
+
import { consola as consola35 } from "consola";
|
|
12352
|
+
function realHostAddresses() {
|
|
12353
|
+
let ip;
|
|
12354
|
+
for (const list of Object.values(os3.networkInterfaces())) {
|
|
12355
|
+
for (const addr of list ?? []) {
|
|
12356
|
+
if (addr.family === "IPv4" && !addr.internal) {
|
|
12357
|
+
ip = addr.address;
|
|
12358
|
+
break;
|
|
12359
|
+
}
|
|
12360
|
+
}
|
|
12361
|
+
if (ip) break;
|
|
12362
|
+
}
|
|
12363
|
+
const hn = os3.hostname();
|
|
12364
|
+
const mdnsName = hn.endsWith(".local") ? hn : `${hn}.local`;
|
|
12365
|
+
return { ...ip ? { ip } : {}, mdnsName };
|
|
12380
12366
|
}
|
|
12381
|
-
function
|
|
12382
|
-
const
|
|
12383
|
-
|
|
12384
|
-
|
|
12367
|
+
async function runShare(opts) {
|
|
12368
|
+
const log = opts.logger ?? {
|
|
12369
|
+
info: (m) => consola35.info(m),
|
|
12370
|
+
warn: (m) => consola35.warn(m)
|
|
12371
|
+
};
|
|
12372
|
+
const cfg = await readLaunchConfig(opts.name, opts.app, opts.monocerosHome);
|
|
12373
|
+
if (!cfg) {
|
|
12385
12374
|
throw new Error(
|
|
12386
|
-
`No
|
|
12375
|
+
`No launch config for '${opts.app}' (expected projects/${opts.app}/.monoceros/launch.json). Nothing to share.`
|
|
12387
12376
|
);
|
|
12388
12377
|
}
|
|
12389
|
-
const
|
|
12390
|
-
|
|
12391
|
-
|
|
12392
|
-
|
|
12393
|
-
|
|
12394
|
-
|
|
12395
|
-
p,
|
|
12396
|
-
liveMarker(p, t.running),
|
|
12397
|
-
t.target,
|
|
12398
|
-
appTargetDetail(p, m.name, t),
|
|
12399
|
-
pad,
|
|
12400
|
-
2
|
|
12401
|
-
)
|
|
12378
|
+
const ported = cfg.configurations.filter(
|
|
12379
|
+
(t) => typeof t.port === "number"
|
|
12380
|
+
);
|
|
12381
|
+
if (ported.length === 0) {
|
|
12382
|
+
throw new Error(
|
|
12383
|
+
`No target in '${opts.app}' declares a port, so there is nothing to share. Add a \`port\` to a target in its launch.json.`
|
|
12402
12384
|
);
|
|
12403
12385
|
}
|
|
12404
|
-
|
|
12405
|
-
|
|
12406
|
-
|
|
12407
|
-
|
|
12408
|
-
|
|
12409
|
-
|
|
12410
|
-
|
|
12386
|
+
const ports = [...new Set(ported.map((t) => t.port))];
|
|
12387
|
+
const resolve = opts.resolve ?? resolveTunnelTarget;
|
|
12388
|
+
const base = await resolve({
|
|
12389
|
+
name: opts.name,
|
|
12390
|
+
target: String(ports[0]),
|
|
12391
|
+
...opts.monocerosHome !== void 0 ? { monocerosHome: opts.monocerosHome } : {}
|
|
12392
|
+
});
|
|
12393
|
+
const preflight = opts.preflight ?? preflightLocalPort;
|
|
12394
|
+
for (const port of ports) {
|
|
12395
|
+
await preflight({ port, address: SHARE_ADDRESS });
|
|
12396
|
+
}
|
|
12397
|
+
const dockerSpawn = opts.dockerSpawn ?? defaultDockerSpawn;
|
|
12398
|
+
const handles = ports.map(
|
|
12399
|
+
(port) => dockerSpawn(
|
|
12400
|
+
buildDockerArgs({
|
|
12401
|
+
localAddress: SHARE_ADDRESS,
|
|
12402
|
+
localPort: port,
|
|
12403
|
+
internalPort: port,
|
|
12404
|
+
network: base.network,
|
|
12405
|
+
targetHost: base.targetHost
|
|
12406
|
+
})
|
|
12407
|
+
)
|
|
12408
|
+
);
|
|
12409
|
+
const { ip, mdnsName } = (opts.hostAddresses ?? realHostAddresses)();
|
|
12410
|
+
const host = ip ?? mdnsName ?? "<host-ip>";
|
|
12411
|
+
log.info(
|
|
12412
|
+
`Sharing ${opts.name}/${opts.app} on the local network (Ctrl+C to stop):`
|
|
12413
|
+
);
|
|
12414
|
+
for (const t of ported) {
|
|
12415
|
+
log.info(` ${cyan2(t.name)} http://${host}:${t.port}`);
|
|
12416
|
+
}
|
|
12417
|
+
if (mdnsName && ip) {
|
|
12418
|
+
log.info(
|
|
12419
|
+
dim(` also reachable as http://${mdnsName}:<port> where mDNS resolves`)
|
|
12411
12420
|
);
|
|
12412
12421
|
}
|
|
12413
|
-
const
|
|
12414
|
-
|
|
12415
|
-
|
|
12416
|
-
|
|
12417
|
-
|
|
12422
|
+
const installSignalHandler = opts.installSignalHandler ?? installSigintDefault;
|
|
12423
|
+
let stopping = false;
|
|
12424
|
+
const uninstall = installSignalHandler(() => {
|
|
12425
|
+
if (stopping) return;
|
|
12426
|
+
stopping = true;
|
|
12427
|
+
for (const h of handles) h.kill("SIGTERM");
|
|
12428
|
+
});
|
|
12429
|
+
try {
|
|
12430
|
+
const codes = await Promise.all(handles.map((h) => h.exited));
|
|
12431
|
+
const bad = codes.find((c) => c !== 0 && c !== 130);
|
|
12432
|
+
return bad ?? 0;
|
|
12433
|
+
} finally {
|
|
12434
|
+
uninstall();
|
|
12435
|
+
}
|
|
12418
12436
|
}
|
|
12419
|
-
var
|
|
12420
|
-
|
|
12437
|
+
var SHARE_ADDRESS;
|
|
12438
|
+
var init_run4 = __esm({
|
|
12439
|
+
"src/share/run.ts"() {
|
|
12421
12440
|
"use strict";
|
|
12422
|
-
init_io();
|
|
12423
|
-
init_paths();
|
|
12424
|
-
init_global();
|
|
12425
|
-
init_transform();
|
|
12426
12441
|
init_launch_config();
|
|
12427
|
-
|
|
12428
|
-
|
|
12429
|
-
|
|
12442
|
+
init_resolve2();
|
|
12443
|
+
init_port_check2();
|
|
12444
|
+
init_run3();
|
|
12430
12445
|
init_format();
|
|
12446
|
+
SHARE_ADDRESS = "0.0.0.0";
|
|
12431
12447
|
}
|
|
12432
12448
|
});
|
|
12433
12449
|
|
|
12434
|
-
// src/commands/
|
|
12435
|
-
import { defineCommand as
|
|
12436
|
-
|
|
12437
|
-
var
|
|
12438
|
-
|
|
12450
|
+
// src/commands/share.ts
|
|
12451
|
+
import { defineCommand as defineCommand29 } from "citty";
|
|
12452
|
+
import { consola as consola36 } from "consola";
|
|
12453
|
+
var shareCommand;
|
|
12454
|
+
var init_share = __esm({
|
|
12455
|
+
"src/commands/share.ts"() {
|
|
12439
12456
|
"use strict";
|
|
12440
|
-
|
|
12441
|
-
|
|
12442
|
-
init_dispatch();
|
|
12443
|
-
statusCommand = defineCommand31({
|
|
12457
|
+
init_run4();
|
|
12458
|
+
shareCommand = defineCommand29({
|
|
12444
12459
|
meta: {
|
|
12445
|
-
name: "
|
|
12446
|
-
group: "
|
|
12447
|
-
description: "
|
|
12460
|
+
name: "share",
|
|
12461
|
+
group: "discovery",
|
|
12462
|
+
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."
|
|
12448
12463
|
},
|
|
12449
12464
|
args: {
|
|
12450
12465
|
name: {
|
|
@@ -12454,49 +12469,149 @@ var init_status2 = __esm({
|
|
|
12454
12469
|
},
|
|
12455
12470
|
app: {
|
|
12456
12471
|
type: "positional",
|
|
12457
|
-
description: "
|
|
12458
|
-
required:
|
|
12472
|
+
description: "App to share (a path under projects/ with .monoceros/launch.json). Every target with a `port` is exposed on the LAN.",
|
|
12473
|
+
required: true
|
|
12459
12474
|
}
|
|
12460
12475
|
},
|
|
12461
|
-
run({ args }) {
|
|
12462
|
-
|
|
12463
|
-
|
|
12464
|
-
|
|
12465
|
-
|
|
12466
|
-
|
|
12467
|
-
|
|
12468
|
-
|
|
12469
|
-
block = isApp ? renderApp(model, filter, p) : renderService(model, filter, p);
|
|
12470
|
-
} else {
|
|
12471
|
-
block = renderStatus(model, p);
|
|
12472
|
-
}
|
|
12473
|
-
process.stdout.write(`
|
|
12474
|
-
${block}
|
|
12475
|
-
`);
|
|
12476
|
-
return 0;
|
|
12477
|
-
});
|
|
12476
|
+
async run({ args }) {
|
|
12477
|
+
try {
|
|
12478
|
+
const exitCode = await runShare({ name: args.name, app: args.app });
|
|
12479
|
+
process.exit(exitCode);
|
|
12480
|
+
} catch (err) {
|
|
12481
|
+
consola36.error(err instanceof Error ? err.message : String(err));
|
|
12482
|
+
process.exit(1);
|
|
12483
|
+
}
|
|
12478
12484
|
}
|
|
12479
12485
|
});
|
|
12480
12486
|
}
|
|
12481
12487
|
});
|
|
12482
12488
|
|
|
12483
|
-
// src/commands/
|
|
12484
|
-
import { defineCommand as
|
|
12485
|
-
import { consola as
|
|
12486
|
-
var
|
|
12487
|
-
var
|
|
12488
|
-
"src/commands/
|
|
12489
|
+
// src/commands/shell.ts
|
|
12490
|
+
import { defineCommand as defineCommand30 } from "citty";
|
|
12491
|
+
import { consola as consola37 } from "consola";
|
|
12492
|
+
var shellCommand;
|
|
12493
|
+
var init_shell2 = __esm({
|
|
12494
|
+
"src/commands/shell.ts"() {
|
|
12495
|
+
"use strict";
|
|
12496
|
+
init_paths();
|
|
12497
|
+
init_shell();
|
|
12498
|
+
shellCommand = defineCommand30({
|
|
12499
|
+
meta: {
|
|
12500
|
+
name: "shell",
|
|
12501
|
+
group: "run",
|
|
12502
|
+
description: "Open an interactive bash session inside the named dev-container."
|
|
12503
|
+
},
|
|
12504
|
+
args: {
|
|
12505
|
+
name: {
|
|
12506
|
+
type: "positional",
|
|
12507
|
+
description: "Container name (yml in $MONOCEROS_HOME/container-configs/).",
|
|
12508
|
+
required: true
|
|
12509
|
+
}
|
|
12510
|
+
},
|
|
12511
|
+
async run({ args }) {
|
|
12512
|
+
try {
|
|
12513
|
+
const exitCode = await runShell({
|
|
12514
|
+
root: containerDir(args.name),
|
|
12515
|
+
name: args.name
|
|
12516
|
+
});
|
|
12517
|
+
process.exit(exitCode);
|
|
12518
|
+
} catch (err) {
|
|
12519
|
+
consola37.error(err instanceof Error ? err.message : String(err));
|
|
12520
|
+
process.exit(1);
|
|
12521
|
+
}
|
|
12522
|
+
}
|
|
12523
|
+
});
|
|
12524
|
+
}
|
|
12525
|
+
});
|
|
12526
|
+
|
|
12527
|
+
// src/commands/start.ts
|
|
12528
|
+
import { defineCommand as defineCommand31 } from "citty";
|
|
12529
|
+
import { consola as consola38 } from "consola";
|
|
12530
|
+
async function bringContainerUp(name, openTool) {
|
|
12531
|
+
{
|
|
12532
|
+
const args = { name, open: openTool };
|
|
12533
|
+
let needsProxy = false;
|
|
12534
|
+
let hostPort = 80;
|
|
12535
|
+
let deferred = [];
|
|
12536
|
+
let runtimeVersion;
|
|
12537
|
+
try {
|
|
12538
|
+
const parsed = await readConfig(containerConfigPath(args.name));
|
|
12539
|
+
runtimeVersion = parsed.config.runtimeVersion;
|
|
12540
|
+
if ((parsed.config.routing?.ports ?? []).length > 0) {
|
|
12541
|
+
needsProxy = true;
|
|
12542
|
+
const global = await readMonocerosConfig();
|
|
12543
|
+
hostPort = proxyHostPort(global);
|
|
12544
|
+
}
|
|
12545
|
+
deferred = (parsed.config.services ?? []).filter((s) => serviceDefersStart(s.name)).map((s) => s.name);
|
|
12546
|
+
} catch (err) {
|
|
12547
|
+
consola38.warn(
|
|
12548
|
+
`Could not read container yml ahead of start: ${err instanceof Error ? err.message : String(err)}. Skipping Traefik pre-flight.`
|
|
12549
|
+
);
|
|
12550
|
+
}
|
|
12551
|
+
if (needsProxy) {
|
|
12552
|
+
await preflightHostPort(hostPort);
|
|
12553
|
+
await ensureProxy({ hostPort });
|
|
12554
|
+
}
|
|
12555
|
+
const exitCode = await runStart({
|
|
12556
|
+
root: containerDir(args.name),
|
|
12557
|
+
silent: true,
|
|
12558
|
+
logger: { info: () => {
|
|
12559
|
+
} }
|
|
12560
|
+
});
|
|
12561
|
+
if (exitCode === 0 && runtimeSupportsBrowserBridge(runtimeVersion)) {
|
|
12562
|
+
spawnBridgeDaemon(containerDir(args.name));
|
|
12563
|
+
}
|
|
12564
|
+
if (exitCode === 0 && deferred.length > 0) {
|
|
12565
|
+
try {
|
|
12566
|
+
const deferExit = await startDeferredServices({
|
|
12567
|
+
root: containerDir(args.name),
|
|
12568
|
+
services: deferred,
|
|
12569
|
+
logger: consola38
|
|
12570
|
+
});
|
|
12571
|
+
if (deferExit !== 0) {
|
|
12572
|
+
consola38.warn(
|
|
12573
|
+
`Deferred service(s) ${deferred.join(", ")} did not start cleanly (exit ${deferExit}).`
|
|
12574
|
+
);
|
|
12575
|
+
}
|
|
12576
|
+
} catch (err) {
|
|
12577
|
+
consola38.warn(
|
|
12578
|
+
`Could not start deferred service(s) ${deferred.join(", ")}: ${err instanceof Error ? err.message : String(err)}`
|
|
12579
|
+
);
|
|
12580
|
+
}
|
|
12581
|
+
}
|
|
12582
|
+
if (exitCode === 0) {
|
|
12583
|
+
consola38.success(`Container '${args.name}' is up.`);
|
|
12584
|
+
}
|
|
12585
|
+
if (args.open && exitCode === 0) {
|
|
12586
|
+
try {
|
|
12587
|
+
await runOpen({ name: args.name, tool: args.open });
|
|
12588
|
+
} catch (err) {
|
|
12589
|
+
consola38.warn(err instanceof Error ? err.message : String(err));
|
|
12590
|
+
}
|
|
12591
|
+
}
|
|
12592
|
+
return exitCode;
|
|
12593
|
+
}
|
|
12594
|
+
}
|
|
12595
|
+
var startCommand;
|
|
12596
|
+
var init_start = __esm({
|
|
12597
|
+
"src/commands/start.ts"() {
|
|
12489
12598
|
"use strict";
|
|
12599
|
+
init_global();
|
|
12600
|
+
init_io();
|
|
12490
12601
|
init_paths();
|
|
12602
|
+
init_bridge_daemon();
|
|
12491
12603
|
init_compose();
|
|
12604
|
+
init_catalog();
|
|
12605
|
+
init_open();
|
|
12492
12606
|
init_proxy();
|
|
12607
|
+
init_port_check();
|
|
12493
12608
|
init_app_control();
|
|
12494
12609
|
init_dispatch();
|
|
12495
|
-
|
|
12610
|
+
startCommand = defineCommand31({
|
|
12496
12611
|
meta: {
|
|
12497
|
-
name: "
|
|
12612
|
+
name: "start",
|
|
12498
12613
|
group: "run",
|
|
12499
|
-
description: "
|
|
12614
|
+
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."
|
|
12500
12615
|
},
|
|
12501
12616
|
args: {
|
|
12502
12617
|
name: {
|
|
@@ -12506,430 +12621,506 @@ var init_stop = __esm({
|
|
|
12506
12621
|
},
|
|
12507
12622
|
app: {
|
|
12508
12623
|
type: "positional",
|
|
12509
|
-
description: "App to
|
|
12624
|
+
description: "App to start (a path under projects/ with .monoceros/launch.json). Omit to just bring the container up.",
|
|
12510
12625
|
required: false
|
|
12511
12626
|
},
|
|
12512
12627
|
target: {
|
|
12513
12628
|
type: "string",
|
|
12514
|
-
description: `Which launch target to
|
|
12629
|
+
description: `Which launch target to start (defaults to the app's "default" target, or its only one).`
|
|
12515
12630
|
},
|
|
12516
|
-
|
|
12631
|
+
open: {
|
|
12517
12632
|
type: "string",
|
|
12518
|
-
description:
|
|
12633
|
+
description: `After a successful start, open the container in this tool (${OPEN_TOOLS.join("|")}).`
|
|
12519
12634
|
}
|
|
12520
12635
|
},
|
|
12521
12636
|
run({ args }) {
|
|
12522
12637
|
if (typeof args.app === "string" && args.app.length > 0) {
|
|
12523
12638
|
const app = args.app;
|
|
12524
12639
|
const target = typeof args.target === "string" ? args.target : void 0;
|
|
12525
|
-
return dispatch(() =>
|
|
12526
|
-
|
|
12527
|
-
|
|
12528
|
-
|
|
12529
|
-
|
|
12530
|
-
|
|
12531
|
-
|
|
12532
|
-
|
|
12533
|
-
|
|
12534
|
-
|
|
12535
|
-
|
|
12536
|
-
|
|
12537
|
-
|
|
12538
|
-
|
|
12539
|
-
}
|
|
12540
|
-
try {
|
|
12541
|
-
await maybeStopProxy({
|
|
12542
|
-
logger: { info: (msg) => consola36.info(msg) }
|
|
12543
|
-
});
|
|
12544
|
-
} catch (err) {
|
|
12545
|
-
consola36.warn(
|
|
12546
|
-
`Could not tear down the Traefik proxy: ${err instanceof Error ? err.message : String(err)}. Ignored.`
|
|
12547
|
-
);
|
|
12548
|
-
}
|
|
12549
|
-
return exit;
|
|
12550
|
-
});
|
|
12640
|
+
return dispatch(async () => {
|
|
12641
|
+
if (!await findRunningContainer(args.name)) {
|
|
12642
|
+
const up = await bringContainerUp(args.name, void 0);
|
|
12643
|
+
if (up !== 0) return up;
|
|
12644
|
+
}
|
|
12645
|
+
return runAppCtl(args.name, ctlArgs("start", app, target));
|
|
12646
|
+
});
|
|
12647
|
+
}
|
|
12648
|
+
return dispatch(
|
|
12649
|
+
() => bringContainerUp(
|
|
12650
|
+
args.name,
|
|
12651
|
+
typeof args.open === "string" ? args.open : void 0
|
|
12652
|
+
)
|
|
12653
|
+
);
|
|
12551
12654
|
}
|
|
12552
12655
|
});
|
|
12553
12656
|
}
|
|
12554
12657
|
});
|
|
12555
12658
|
|
|
12556
|
-
// src/
|
|
12557
|
-
|
|
12558
|
-
|
|
12559
|
-
|
|
12560
|
-
|
|
12561
|
-
if (!existsSync20(ymlPath)) {
|
|
12562
|
-
throw new Error(
|
|
12563
|
-
`No yml profile for '${opts.name}' at ${ymlPath}. Run \`monoceros init ${opts.name}\` first.`
|
|
12564
|
-
);
|
|
12565
|
-
}
|
|
12566
|
-
const parsed = await readConfig(ymlPath);
|
|
12567
|
-
const config = parsed.config;
|
|
12568
|
-
const containerRoot = containerDir(opts.name, opts.monocerosHome);
|
|
12569
|
-
if (!existsSync20(containerRoot)) {
|
|
12570
|
-
throw new Error(
|
|
12571
|
-
`Container '${opts.name}' is not materialised at ${containerRoot}. Run \`monoceros apply ${opts.name}\` first.`
|
|
12572
|
-
);
|
|
12573
|
-
}
|
|
12574
|
-
const composePath = path32.join(containerRoot, ".devcontainer", "compose.yaml");
|
|
12575
|
-
const isCompose = existsSync20(composePath);
|
|
12576
|
-
const parsedTarget = parseTargetArg(opts.target, config);
|
|
12577
|
-
const docker = opts.docker ?? defaultDockerExec;
|
|
12578
|
-
if (isCompose) {
|
|
12579
|
-
return resolveCompose2({
|
|
12580
|
-
name: opts.name,
|
|
12581
|
-
containerRoot,
|
|
12582
|
-
parsedTarget
|
|
12583
|
-
});
|
|
12584
|
-
}
|
|
12585
|
-
return resolveImageMode({
|
|
12586
|
-
name: opts.name,
|
|
12587
|
-
containerRoot,
|
|
12588
|
-
parsedTarget,
|
|
12589
|
-
config,
|
|
12590
|
-
docker
|
|
12591
|
-
});
|
|
12592
|
-
}
|
|
12593
|
-
function parseTargetArg(raw, config) {
|
|
12594
|
-
const colon = raw.indexOf(":");
|
|
12595
|
-
if (colon > 0) {
|
|
12596
|
-
const name = raw.slice(0, colon);
|
|
12597
|
-
const port = Number(raw.slice(colon + 1));
|
|
12598
|
-
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
12599
|
-
throw new Error(
|
|
12600
|
-
`Invalid target '${raw}'. Use <service>:<port> with a numeric port (1\u201365535), a bare port number, or a configured service name.`
|
|
12601
|
-
);
|
|
12602
|
-
}
|
|
12603
|
-
findConfiguredService(config, name);
|
|
12604
|
-
return { kind: "service", service: name, port };
|
|
12605
|
-
}
|
|
12606
|
-
const asNumber = Number(raw);
|
|
12607
|
-
if (Number.isInteger(asNumber) && asNumber > 0 && asNumber < 65536) {
|
|
12608
|
-
return { kind: "port", port: asNumber };
|
|
12609
|
-
}
|
|
12610
|
-
const match = findConfiguredService(config, raw);
|
|
12611
|
-
if (match.port === void 0) {
|
|
12612
|
-
throw new Error(
|
|
12613
|
-
`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>\`.`
|
|
12614
|
-
);
|
|
12615
|
-
}
|
|
12616
|
-
return { kind: "service", service: raw, port: match.port };
|
|
12659
|
+
// src/status/index.ts
|
|
12660
|
+
function shortFeatureName2(ref) {
|
|
12661
|
+
const withoutTag = ref.replace(/:[^:/@]+$/, "");
|
|
12662
|
+
const idx = withoutTag.lastIndexOf("/");
|
|
12663
|
+
return idx >= 0 ? withoutTag.slice(idx + 1) : withoutTag;
|
|
12617
12664
|
}
|
|
12618
|
-
function
|
|
12619
|
-
|
|
12620
|
-
const match = services.find((s) => s.name === name);
|
|
12621
|
-
if (!match) {
|
|
12622
|
-
const names = services.map((s) => s.name);
|
|
12623
|
-
const list = names.length > 0 ? names.join(", ") : "(none configured)";
|
|
12624
|
-
throw new Error(
|
|
12625
|
-
`Service '${name}' is not configured in this container's yml. Configured services: ${list}. Or pass a port number (e.g. \`monoceros tunnel <name> 8080\`).`
|
|
12626
|
-
);
|
|
12627
|
-
}
|
|
12628
|
-
return match;
|
|
12665
|
+
function cols(line) {
|
|
12666
|
+
return line.split(" ");
|
|
12629
12667
|
}
|
|
12630
|
-
function
|
|
12631
|
-
|
|
12632
|
-
if (args.parsedTarget.kind === "service") {
|
|
12633
|
-
return {
|
|
12634
|
-
network,
|
|
12635
|
-
targetHost: args.parsedTarget.service,
|
|
12636
|
-
internalPort: args.parsedTarget.port,
|
|
12637
|
-
display: `${args.name}/${args.parsedTarget.service}:${args.parsedTarget.port}`
|
|
12638
|
-
};
|
|
12639
|
-
}
|
|
12640
|
-
return {
|
|
12641
|
-
network,
|
|
12642
|
-
targetHost: "workspace",
|
|
12643
|
-
internalPort: args.parsedTarget.port,
|
|
12644
|
-
display: `${args.name}:${args.parsedTarget.port}`
|
|
12645
|
-
};
|
|
12668
|
+
function nonEmptyLines(s) {
|
|
12669
|
+
return s.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
12646
12670
|
}
|
|
12647
|
-
async function
|
|
12648
|
-
|
|
12649
|
-
|
|
12650
|
-
|
|
12651
|
-
|
|
12652
|
-
|
|
12653
|
-
|
|
12654
|
-
|
|
12655
|
-
|
|
12656
|
-
|
|
12657
|
-
|
|
12658
|
-
|
|
12659
|
-
|
|
12660
|
-
|
|
12671
|
+
async function gatherStatus(name, opts = {}) {
|
|
12672
|
+
const docker = opts.docker ?? realDockerLookup;
|
|
12673
|
+
const root = containerDir(name, opts.home);
|
|
12674
|
+
let runtimeVersion;
|
|
12675
|
+
let languages = [];
|
|
12676
|
+
let features = [];
|
|
12677
|
+
let declaredServices = [];
|
|
12678
|
+
let declaredPorts = [];
|
|
12679
|
+
let configured = false;
|
|
12680
|
+
try {
|
|
12681
|
+
const parsed = await readConfig(containerConfigPath(name, opts.home));
|
|
12682
|
+
const createOpts = solutionConfigToCreateOptions(parsed.config);
|
|
12683
|
+
configured = true;
|
|
12684
|
+
runtimeVersion = createOpts.runtimeVersion;
|
|
12685
|
+
languages = createOpts.languages;
|
|
12686
|
+
features = Object.keys(createOpts.features ?? {}).map(shortFeatureName2);
|
|
12687
|
+
declaredServices = createOpts.services.map((s) => ({
|
|
12688
|
+
name: s.name,
|
|
12689
|
+
...typeof s.port === "number" ? { port: s.port } : {}
|
|
12690
|
+
}));
|
|
12691
|
+
declaredPorts = createOpts.ports ?? [];
|
|
12692
|
+
} catch {
|
|
12661
12693
|
}
|
|
12662
|
-
|
|
12663
|
-
|
|
12664
|
-
|
|
12665
|
-
|
|
12666
|
-
|
|
12667
|
-
network,
|
|
12668
|
-
targetHost: ip,
|
|
12669
|
-
internalPort: args.parsedTarget.port,
|
|
12670
|
-
display: `${args.name}:${args.parsedTarget.port}`
|
|
12694
|
+
let container = {
|
|
12695
|
+
exists: false,
|
|
12696
|
+
running: false,
|
|
12697
|
+
status: "",
|
|
12698
|
+
dockerName: ""
|
|
12671
12699
|
};
|
|
12672
|
-
|
|
12673
|
-
|
|
12674
|
-
const
|
|
12700
|
+
let containerId = "";
|
|
12701
|
+
let composeProject = "";
|
|
12702
|
+
const cps = await docker([
|
|
12675
12703
|
"ps",
|
|
12676
|
-
"-
|
|
12704
|
+
"-a",
|
|
12705
|
+
"--no-trunc",
|
|
12677
12706
|
"--filter",
|
|
12678
|
-
`label=devcontainer.local_folder=${
|
|
12707
|
+
`label=devcontainer.local_folder=${root}`,
|
|
12708
|
+
"--format",
|
|
12709
|
+
'{{.ID}} {{.State}} {{.Status}} {{.Names}} {{.Label "com.docker.compose.project"}}'
|
|
12679
12710
|
]);
|
|
12680
|
-
|
|
12681
|
-
|
|
12682
|
-
|
|
12683
|
-
|
|
12711
|
+
const row2 = nonEmptyLines(cps.stdout)[0];
|
|
12712
|
+
if (row2) {
|
|
12713
|
+
const [id, state, status, names, project] = cols(row2);
|
|
12714
|
+
containerId = id ?? "";
|
|
12715
|
+
composeProject = project ?? "";
|
|
12716
|
+
container = {
|
|
12717
|
+
exists: true,
|
|
12718
|
+
running: state === "running",
|
|
12719
|
+
status: status ?? "",
|
|
12720
|
+
dockerName: names ?? ""
|
|
12721
|
+
};
|
|
12684
12722
|
}
|
|
12685
|
-
const
|
|
12686
|
-
if (
|
|
12687
|
-
|
|
12688
|
-
|
|
12689
|
-
|
|
12723
|
+
const services = [];
|
|
12724
|
+
if (declaredServices.length > 0) {
|
|
12725
|
+
const live = /* @__PURE__ */ new Map();
|
|
12726
|
+
if (composeProject) {
|
|
12727
|
+
const sps = await docker([
|
|
12728
|
+
"ps",
|
|
12729
|
+
"-a",
|
|
12730
|
+
"--filter",
|
|
12731
|
+
`label=com.docker.compose.project=${composeProject}`,
|
|
12732
|
+
"--format",
|
|
12733
|
+
'{{.Label "com.docker.compose.service"}} {{.State}} {{.Status}}'
|
|
12734
|
+
]);
|
|
12735
|
+
for (const l of nonEmptyLines(sps.stdout)) {
|
|
12736
|
+
const [svc, state, status] = cols(l);
|
|
12737
|
+
if (svc)
|
|
12738
|
+
live.set(svc, { running: state === "running", status: status ?? "" });
|
|
12739
|
+
}
|
|
12740
|
+
}
|
|
12741
|
+
for (const s of declaredServices) {
|
|
12742
|
+
const m = live.get(s.name);
|
|
12743
|
+
services.push({
|
|
12744
|
+
name: s.name,
|
|
12745
|
+
running: m?.running ?? false,
|
|
12746
|
+
status: m?.status ?? "not created",
|
|
12747
|
+
...typeof s.port === "number" ? { port: s.port } : {}
|
|
12748
|
+
});
|
|
12749
|
+
}
|
|
12750
|
+
}
|
|
12751
|
+
const apps = [];
|
|
12752
|
+
for (const app of await listApps(name, opts.home)) {
|
|
12753
|
+
const cfg = await readLaunchConfig(name, app, opts.home);
|
|
12754
|
+
if (!cfg) continue;
|
|
12755
|
+
for (const t of cfg.configurations) {
|
|
12756
|
+
apps.push({
|
|
12757
|
+
app,
|
|
12758
|
+
target: t.name,
|
|
12759
|
+
port: typeof t.port === "number" ? t.port : null,
|
|
12760
|
+
default: t.default === true,
|
|
12761
|
+
running: void 0,
|
|
12762
|
+
pid: null
|
|
12763
|
+
});
|
|
12764
|
+
}
|
|
12765
|
+
}
|
|
12766
|
+
const appsSupported = runtimeSupportsAppStatus(runtimeVersion);
|
|
12767
|
+
let appStateKnown = container.running && appsSupported && containerId !== "";
|
|
12768
|
+
let appStateNote;
|
|
12769
|
+
if (appStateKnown) {
|
|
12770
|
+
const res = await docker([
|
|
12771
|
+
"exec",
|
|
12772
|
+
containerId,
|
|
12773
|
+
"monoceros-ctl",
|
|
12774
|
+
"list",
|
|
12775
|
+
"--json"
|
|
12776
|
+
]);
|
|
12777
|
+
if (res.exitCode === 0) {
|
|
12778
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
12779
|
+
for (const l of nonEmptyLines(res.stdout)) {
|
|
12780
|
+
try {
|
|
12781
|
+
const o = JSON.parse(l);
|
|
12782
|
+
byKey.set(`${o.app}\0${o.target}`, {
|
|
12783
|
+
running: !!o.running,
|
|
12784
|
+
pid: o.pid ?? null
|
|
12785
|
+
});
|
|
12786
|
+
} catch {
|
|
12787
|
+
}
|
|
12788
|
+
}
|
|
12789
|
+
for (const a of apps) {
|
|
12790
|
+
const o = byKey.get(`${a.app}\0${a.target}`);
|
|
12791
|
+
if (o) {
|
|
12792
|
+
a.running = o.running;
|
|
12793
|
+
a.pid = o.pid;
|
|
12794
|
+
} else {
|
|
12795
|
+
a.running = false;
|
|
12796
|
+
}
|
|
12797
|
+
}
|
|
12798
|
+
} else {
|
|
12799
|
+
appStateKnown = false;
|
|
12800
|
+
appStateNote = `could not read run state from the runner - rebuild the runtime image, then \`monoceros upgrade ${name}\``;
|
|
12801
|
+
}
|
|
12690
12802
|
}
|
|
12691
|
-
|
|
12692
|
-
"
|
|
12693
|
-
"--format",
|
|
12694
|
-
"{{json .NetworkSettings.Networks}}",
|
|
12695
|
-
containerId
|
|
12696
|
-
]);
|
|
12697
|
-
if (inspect.exitCode !== 0) {
|
|
12698
|
-
throw new Error(
|
|
12699
|
-
`docker inspect failed: ${inspect.stderr.trim() || `exit ${inspect.exitCode}`}`
|
|
12700
|
-
);
|
|
12803
|
+
if (apps.length > 0 && !appStateKnown && !appStateNote) {
|
|
12804
|
+
appStateNote = !container.running ? `start the container to see run state (\`monoceros start ${name}\`)` : `run state needs runtime 1.6.0+ (this one: ${runtimeVersion ?? "unpinned"})`;
|
|
12701
12805
|
}
|
|
12702
|
-
let
|
|
12806
|
+
let hostPort = 80;
|
|
12703
12807
|
try {
|
|
12704
|
-
|
|
12705
|
-
|
|
12706
|
-
|
|
12707
|
-
|
|
12808
|
+
hostPort = proxyHostPort(
|
|
12809
|
+
await readMonocerosConfig({
|
|
12810
|
+
...opts.home ? { monocerosHome: opts.home } : {}
|
|
12811
|
+
})
|
|
12708
12812
|
);
|
|
12813
|
+
} catch {
|
|
12709
12814
|
}
|
|
12710
|
-
|
|
12711
|
-
|
|
12712
|
-
|
|
12815
|
+
const ports = proxyUrlsFor(name, declaredPorts, hostPort);
|
|
12816
|
+
return {
|
|
12817
|
+
name,
|
|
12818
|
+
configured,
|
|
12819
|
+
container,
|
|
12820
|
+
services,
|
|
12821
|
+
apps,
|
|
12822
|
+
appStateKnown,
|
|
12823
|
+
...appStateNote ? { appStateNote } : {},
|
|
12824
|
+
ports,
|
|
12825
|
+
builtIn: { languages, features }
|
|
12826
|
+
};
|
|
12827
|
+
}
|
|
12828
|
+
function row(p, marker, name, detail, pad, indent = 2) {
|
|
12829
|
+
const gap = Math.max(2, pad - name.length + 2);
|
|
12830
|
+
return `${" ".repeat(indent)}${marker} ${p.cyan(name)}${" ".repeat(gap)}${detail}`;
|
|
12831
|
+
}
|
|
12832
|
+
function liveMarker(p, running) {
|
|
12833
|
+
if (running === void 0) return " ";
|
|
12834
|
+
return running ? p.green("\u2713") : p.dim("\xB7");
|
|
12835
|
+
}
|
|
12836
|
+
function renderContainer(p, m) {
|
|
12837
|
+
const out = [p.sectionLine(m.name)];
|
|
12838
|
+
if (!m.container.exists) {
|
|
12839
|
+
out.push(
|
|
12840
|
+
row(
|
|
12841
|
+
p,
|
|
12842
|
+
p.dim("\xB7"),
|
|
12843
|
+
"(not created)",
|
|
12844
|
+
p.dim(`run \`monoceros apply ${m.name}\``),
|
|
12845
|
+
14
|
|
12846
|
+
)
|
|
12713
12847
|
);
|
|
12848
|
+
return out;
|
|
12714
12849
|
}
|
|
12715
|
-
|
|
12716
|
-
|
|
12717
|
-
|
|
12718
|
-
|
|
12719
|
-
|
|
12720
|
-
|
|
12721
|
-
|
|
12850
|
+
const detail = m.container.running ? p.dim(m.container.status || "running") : p.dim(m.container.status || "stopped");
|
|
12851
|
+
out.push(
|
|
12852
|
+
row(
|
|
12853
|
+
p,
|
|
12854
|
+
liveMarker(p, m.container.running),
|
|
12855
|
+
m.container.dockerName,
|
|
12856
|
+
detail,
|
|
12857
|
+
14
|
|
12858
|
+
)
|
|
12722
12859
|
);
|
|
12860
|
+
return out;
|
|
12723
12861
|
}
|
|
12724
|
-
|
|
12725
|
-
|
|
12726
|
-
|
|
12727
|
-
|
|
12728
|
-
|
|
12729
|
-
|
|
12730
|
-
|
|
12731
|
-
init_proxy();
|
|
12862
|
+
function renderServices(p, m) {
|
|
12863
|
+
if (m.services.length === 0) return [];
|
|
12864
|
+
const pad = Math.max(...m.services.map((s) => s.name.length));
|
|
12865
|
+
const out = ["", p.sectionLine("Services")];
|
|
12866
|
+
for (const s of m.services) {
|
|
12867
|
+
const detail = s.running ? p.dim(typeof s.port === "number" ? `running :${s.port}` : "running") : p.dim(s.status || "stopped");
|
|
12868
|
+
out.push(row(p, liveMarker(p, s.running), s.name, detail, pad));
|
|
12732
12869
|
}
|
|
12733
|
-
|
|
12734
|
-
|
|
12735
|
-
// src/tunnel/port-check.ts
|
|
12736
|
-
import { Socket as Socket2 } from "net";
|
|
12737
|
-
async function preflightLocalPort(opts) {
|
|
12738
|
-
const probe = opts.probe ?? realPortProbe2;
|
|
12739
|
-
const result = await probe(opts.port, opts.address);
|
|
12740
|
-
if (result.ok) return;
|
|
12741
|
-
throw new Error(formatLocalPortHeldError(opts.port, opts.address, result));
|
|
12870
|
+
return out;
|
|
12742
12871
|
}
|
|
12743
|
-
function
|
|
12744
|
-
const
|
|
12745
|
-
if (
|
|
12746
|
-
|
|
12747
|
-
|
|
12748
|
-
lines.push("Identify the holder, then either stop it or pick a different");
|
|
12749
|
-
lines.push("port for the tunnel:");
|
|
12750
|
-
lines.push("");
|
|
12751
|
-
lines.push(` sudo lsof -iTCP:${port} -sTCP:LISTEN -n -P`);
|
|
12752
|
-
lines.push(` # or: sudo ss -tlnp | grep ":${port}\\b"`);
|
|
12753
|
-
lines.push("");
|
|
12754
|
-
lines.push("Re-run with an explicit local port:");
|
|
12755
|
-
lines.push(` monoceros tunnel \u2026 --local-port=${port + 1}`);
|
|
12756
|
-
} else {
|
|
12757
|
-
lines.push(
|
|
12758
|
-
`Cannot probe local port ${port} on ${address}: ${result.message}`
|
|
12759
|
-
);
|
|
12760
|
-
lines.push("");
|
|
12761
|
-
lines.push(
|
|
12762
|
-
"Most likely the host network stack (firewall, namespace) is interfering."
|
|
12872
|
+
function appTargetDetail(p, name, t) {
|
|
12873
|
+
const bits = [];
|
|
12874
|
+
if (t.running === true) {
|
|
12875
|
+
bits.push(
|
|
12876
|
+
t.port !== null ? p.dim(`http://${name}-${t.port}.localhost`) : p.dim("running")
|
|
12763
12877
|
);
|
|
12764
|
-
|
|
12878
|
+
if (t.pid !== null) bits.push(p.dim(`pid ${t.pid}`));
|
|
12879
|
+
} else if (t.running === false) {
|
|
12880
|
+
bits.push(p.dim("stopped"));
|
|
12765
12881
|
}
|
|
12766
|
-
|
|
12882
|
+
if (t.default) bits.push(p.dim("(default)"));
|
|
12883
|
+
return bits.join(" ");
|
|
12767
12884
|
}
|
|
12768
|
-
|
|
12769
|
-
|
|
12770
|
-
"
|
|
12771
|
-
|
|
12772
|
-
|
|
12773
|
-
|
|
12774
|
-
|
|
12775
|
-
|
|
12776
|
-
|
|
12777
|
-
let settled = false;
|
|
12778
|
-
const settle = (result) => {
|
|
12779
|
-
if (settled) return;
|
|
12780
|
-
settled = true;
|
|
12781
|
-
socket.destroy();
|
|
12782
|
-
resolve(result);
|
|
12783
|
-
};
|
|
12784
|
-
socket.setTimeout(CONNECT_TIMEOUT_MS2);
|
|
12785
|
-
socket.once("connect", () => {
|
|
12786
|
-
settle({
|
|
12787
|
-
ok: false,
|
|
12788
|
-
code: "EADDRINUSE",
|
|
12789
|
-
message: `another process is listening on ${port}`
|
|
12790
|
-
});
|
|
12791
|
-
});
|
|
12792
|
-
socket.once("timeout", () => {
|
|
12793
|
-
settle({ ok: true });
|
|
12794
|
-
});
|
|
12795
|
-
socket.once("error", (err) => {
|
|
12796
|
-
const code = err.code ?? "UNKNOWN";
|
|
12797
|
-
if (code === "ECONNREFUSED") {
|
|
12798
|
-
settle({ ok: true });
|
|
12799
|
-
} else {
|
|
12800
|
-
settle({ ok: false, code, message: err.message });
|
|
12801
|
-
}
|
|
12802
|
-
});
|
|
12803
|
-
socket.connect(port, probeHost);
|
|
12804
|
-
});
|
|
12805
|
-
};
|
|
12885
|
+
function renderApps(p, m) {
|
|
12886
|
+
if (m.apps.length === 0) return [];
|
|
12887
|
+
const out = ["", p.sectionLine("Apps")];
|
|
12888
|
+
if (m.appStateNote) out.push(` ${p.dim(m.appStateNote)}`);
|
|
12889
|
+
const byApp = /* @__PURE__ */ new Map();
|
|
12890
|
+
for (const a of m.apps) {
|
|
12891
|
+
const list = byApp.get(a.app) ?? [];
|
|
12892
|
+
list.push(a);
|
|
12893
|
+
byApp.set(a.app, list);
|
|
12806
12894
|
}
|
|
12807
|
-
|
|
12808
|
-
|
|
12809
|
-
|
|
12810
|
-
|
|
12811
|
-
|
|
12812
|
-
|
|
12813
|
-
|
|
12814
|
-
|
|
12815
|
-
|
|
12816
|
-
|
|
12817
|
-
|
|
12818
|
-
|
|
12819
|
-
|
|
12895
|
+
for (const [app, targets] of byApp) {
|
|
12896
|
+
out.push(` ${p.cyan(app)}`);
|
|
12897
|
+
const pad = Math.max(...targets.map((t) => t.target.length));
|
|
12898
|
+
for (const t of targets) {
|
|
12899
|
+
out.push(
|
|
12900
|
+
row(
|
|
12901
|
+
p,
|
|
12902
|
+
liveMarker(p, t.running),
|
|
12903
|
+
t.target,
|
|
12904
|
+
appTargetDetail(p, m.name, t),
|
|
12905
|
+
pad,
|
|
12906
|
+
4
|
|
12907
|
+
)
|
|
12908
|
+
);
|
|
12909
|
+
}
|
|
12820
12910
|
}
|
|
12821
|
-
|
|
12822
|
-
|
|
12823
|
-
|
|
12824
|
-
|
|
12825
|
-
|
|
12826
|
-
|
|
12827
|
-
const
|
|
12828
|
-
|
|
12829
|
-
|
|
12830
|
-
target: opts.target,
|
|
12831
|
-
...opts.monocerosHome !== void 0 ? { monocerosHome: opts.monocerosHome } : {}
|
|
12832
|
-
};
|
|
12833
|
-
const resolved = await resolve(resolveArgs3);
|
|
12834
|
-
const localPort = opts.localPort ?? resolved.internalPort;
|
|
12835
|
-
const localAddress = opts.localAddress ?? DEFAULT_LOCAL_ADDRESS;
|
|
12836
|
-
validateLocalAddress(localAddress);
|
|
12837
|
-
const preflight = opts.preflight ?? preflightLocalPort;
|
|
12838
|
-
await preflight({ port: localPort, address: localAddress });
|
|
12839
|
-
const dockerSpawn = opts.dockerSpawn ?? defaultDockerSpawn;
|
|
12840
|
-
const installSignalHandler = opts.installSignalHandler ?? installSigintDefault;
|
|
12841
|
-
const dockerArgs = buildDockerArgs({
|
|
12842
|
-
localAddress,
|
|
12843
|
-
localPort,
|
|
12844
|
-
internalPort: resolved.internalPort,
|
|
12845
|
-
network: resolved.network,
|
|
12846
|
-
targetHost: resolved.targetHost
|
|
12847
|
-
});
|
|
12848
|
-
log.info(
|
|
12849
|
-
`Tunnel: ${localAddress}:${localPort} \u2192 ${resolved.display}:${resolved.internalPort} (Ctrl+C to stop)`
|
|
12850
|
-
);
|
|
12851
|
-
const handle = dockerSpawn(dockerArgs);
|
|
12852
|
-
const uninstall = installSignalHandler(() => {
|
|
12853
|
-
});
|
|
12854
|
-
try {
|
|
12855
|
-
const exitCode = await handle.exited;
|
|
12856
|
-
if (exitCode === 130) return 0;
|
|
12857
|
-
return exitCode;
|
|
12858
|
-
} finally {
|
|
12859
|
-
uninstall();
|
|
12911
|
+
return out;
|
|
12912
|
+
}
|
|
12913
|
+
function renderPorts(p, m) {
|
|
12914
|
+
if (m.ports.length === 0) return [];
|
|
12915
|
+
const pad = Math.max(...m.ports.map((r) => String(r.port).length));
|
|
12916
|
+
const out = ["", p.sectionLine("Ports")];
|
|
12917
|
+
for (const r of m.ports) {
|
|
12918
|
+
const hosts = r.isDefault ? `${m.name}.localhost \xB7 ${m.name}-${r.port}.localhost` : `${m.name}-${r.port}.localhost`;
|
|
12919
|
+
out.push(row(p, " ", String(r.port), p.dim(hosts), pad));
|
|
12860
12920
|
}
|
|
12921
|
+
return out;
|
|
12861
12922
|
}
|
|
12862
|
-
function
|
|
12923
|
+
function renderBuiltIn(p, m) {
|
|
12924
|
+
const lines = [];
|
|
12925
|
+
if (m.builtIn.languages.length > 0)
|
|
12926
|
+
lines.push({ label: "Languages", values: m.builtIn.languages });
|
|
12927
|
+
if (m.builtIn.features.length > 0)
|
|
12928
|
+
lines.push({ label: "Features", values: m.builtIn.features });
|
|
12929
|
+
if (lines.length === 0) return [];
|
|
12930
|
+
const labelWidth = Math.max(...lines.map((l) => l.label.length));
|
|
12931
|
+
const out = ["", `${p.sectionLine("Built in")} ${p.dim("from the yml")}`];
|
|
12932
|
+
for (const l of lines) {
|
|
12933
|
+
out.push(` ${l.label.padEnd(labelWidth)} ${p.cyan(l.values.join(", "))}`);
|
|
12934
|
+
}
|
|
12935
|
+
return out;
|
|
12936
|
+
}
|
|
12937
|
+
function renderStatus(m, p) {
|
|
12863
12938
|
return [
|
|
12864
|
-
|
|
12865
|
-
|
|
12866
|
-
|
|
12867
|
-
|
|
12868
|
-
|
|
12869
|
-
|
|
12870
|
-
SOCAT_IMAGE,
|
|
12871
|
-
`TCP-LISTEN:${input.internalPort},fork,reuseaddr`,
|
|
12872
|
-
`TCP:${input.targetHost}:${input.internalPort}`
|
|
12873
|
-
];
|
|
12939
|
+
...renderContainer(p, m),
|
|
12940
|
+
...renderServices(p, m),
|
|
12941
|
+
...renderApps(p, m),
|
|
12942
|
+
...renderPorts(p, m),
|
|
12943
|
+
...renderBuiltIn(p, m)
|
|
12944
|
+
].join("\n");
|
|
12874
12945
|
}
|
|
12875
|
-
function
|
|
12876
|
-
|
|
12877
|
-
if (
|
|
12878
|
-
|
|
12879
|
-
|
|
12880
|
-
|
|
12881
|
-
|
|
12882
|
-
`Invalid --local-address '${addr}': each dotted-quad octet must be 0-255.`
|
|
12883
|
-
);
|
|
12884
|
-
}
|
|
12885
|
-
}
|
|
12886
|
-
return;
|
|
12946
|
+
function renderApp(m, app, p) {
|
|
12947
|
+
const targets = m.apps.filter((a) => a.app === app);
|
|
12948
|
+
if (targets.length === 0) {
|
|
12949
|
+
const known = [...new Set(m.apps.map((a) => a.app))];
|
|
12950
|
+
throw new Error(
|
|
12951
|
+
`No app "${app}" in ${m.name} (have: ${known.join(", ") || "none"}).`
|
|
12952
|
+
);
|
|
12887
12953
|
}
|
|
12888
|
-
|
|
12889
|
-
|
|
12890
|
-
);
|
|
12954
|
+
const out = [p.sectionLine(app)];
|
|
12955
|
+
if (m.appStateNote) out.push(` ${p.dim(m.appStateNote)}`);
|
|
12956
|
+
const pad = Math.max(...targets.map((t) => t.target.length));
|
|
12957
|
+
for (const t of targets) {
|
|
12958
|
+
out.push(
|
|
12959
|
+
row(
|
|
12960
|
+
p,
|
|
12961
|
+
liveMarker(p, t.running),
|
|
12962
|
+
t.target,
|
|
12963
|
+
appTargetDetail(p, m.name, t),
|
|
12964
|
+
pad,
|
|
12965
|
+
2
|
|
12966
|
+
)
|
|
12967
|
+
);
|
|
12968
|
+
}
|
|
12969
|
+
return out.join("\n");
|
|
12891
12970
|
}
|
|
12892
|
-
|
|
12893
|
-
|
|
12894
|
-
|
|
12971
|
+
function renderService(m, name, p) {
|
|
12972
|
+
const s = m.services.find((svc) => svc.name === name);
|
|
12973
|
+
if (!s) {
|
|
12974
|
+
throw new Error(
|
|
12975
|
+
`No service "${name}" in ${m.name} (have: ${m.services.map((x) => x.name).join(", ") || "none"}).`
|
|
12976
|
+
);
|
|
12977
|
+
}
|
|
12978
|
+
const detail = s.running ? p.dim(typeof s.port === "number" ? `running :${s.port}` : "running") : p.dim(s.status || "stopped");
|
|
12979
|
+
return [
|
|
12980
|
+
p.sectionLine("Services"),
|
|
12981
|
+
row(p, liveMarker(p, s.running), s.name, detail, s.name.length)
|
|
12982
|
+
].join("\n");
|
|
12983
|
+
}
|
|
12984
|
+
var init_status = __esm({
|
|
12985
|
+
"src/status/index.ts"() {
|
|
12895
12986
|
"use strict";
|
|
12896
|
-
|
|
12897
|
-
|
|
12898
|
-
|
|
12899
|
-
|
|
12900
|
-
|
|
12901
|
-
|
|
12902
|
-
|
|
12903
|
-
|
|
12904
|
-
|
|
12905
|
-
|
|
12906
|
-
|
|
12907
|
-
|
|
12908
|
-
|
|
12987
|
+
init_io();
|
|
12988
|
+
init_paths();
|
|
12989
|
+
init_global();
|
|
12990
|
+
init_transform();
|
|
12991
|
+
init_launch_config();
|
|
12992
|
+
init_catalog();
|
|
12993
|
+
init_locate_running();
|
|
12994
|
+
init_dynamic();
|
|
12995
|
+
init_format();
|
|
12996
|
+
}
|
|
12997
|
+
});
|
|
12998
|
+
|
|
12999
|
+
// src/commands/status.ts
|
|
13000
|
+
import { defineCommand as defineCommand32 } from "citty";
|
|
13001
|
+
var statusCommand;
|
|
13002
|
+
var init_status2 = __esm({
|
|
13003
|
+
"src/commands/status.ts"() {
|
|
13004
|
+
"use strict";
|
|
13005
|
+
init_status();
|
|
13006
|
+
init_format();
|
|
13007
|
+
init_dispatch();
|
|
13008
|
+
statusCommand = defineCommand32({
|
|
13009
|
+
meta: {
|
|
13010
|
+
name: "status",
|
|
13011
|
+
group: "run",
|
|
13012
|
+
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."
|
|
13013
|
+
},
|
|
13014
|
+
args: {
|
|
13015
|
+
name: {
|
|
13016
|
+
type: "positional",
|
|
13017
|
+
description: "Container name (yml in $MONOCEROS_HOME/container-configs/).",
|
|
13018
|
+
required: true
|
|
13019
|
+
},
|
|
13020
|
+
app: {
|
|
13021
|
+
type: "positional",
|
|
13022
|
+
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.",
|
|
13023
|
+
required: false
|
|
13024
|
+
}
|
|
13025
|
+
},
|
|
13026
|
+
run({ args }) {
|
|
13027
|
+
const filter = typeof args.app === "string" ? args.app : void 0;
|
|
13028
|
+
return dispatch(async () => {
|
|
13029
|
+
const model = await gatherStatus(args.name);
|
|
13030
|
+
const p = colorsFor(process.stdout);
|
|
13031
|
+
let block;
|
|
13032
|
+
if (filter) {
|
|
13033
|
+
const isApp = model.apps.some((a) => a.app === filter);
|
|
13034
|
+
block = isApp ? renderApp(model, filter, p) : renderService(model, filter, p);
|
|
13035
|
+
} else {
|
|
13036
|
+
block = renderStatus(model, p);
|
|
13037
|
+
}
|
|
13038
|
+
process.stdout.write(`
|
|
13039
|
+
${block}
|
|
13040
|
+
`);
|
|
13041
|
+
return 0;
|
|
12909
13042
|
});
|
|
12910
|
-
}
|
|
12911
|
-
|
|
12912
|
-
|
|
12913
|
-
|
|
13043
|
+
}
|
|
13044
|
+
});
|
|
13045
|
+
}
|
|
13046
|
+
});
|
|
13047
|
+
|
|
13048
|
+
// src/commands/stop.ts
|
|
13049
|
+
import { defineCommand as defineCommand33 } from "citty";
|
|
13050
|
+
import { consola as consola39 } from "consola";
|
|
13051
|
+
var stopCommand;
|
|
13052
|
+
var init_stop = __esm({
|
|
13053
|
+
"src/commands/stop.ts"() {
|
|
13054
|
+
"use strict";
|
|
13055
|
+
init_paths();
|
|
13056
|
+
init_compose();
|
|
13057
|
+
init_proxy();
|
|
13058
|
+
init_app_control();
|
|
13059
|
+
init_dispatch();
|
|
13060
|
+
stopCommand = defineCommand33({
|
|
13061
|
+
meta: {
|
|
13062
|
+
name: "stop",
|
|
13063
|
+
group: "run",
|
|
13064
|
+
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)."
|
|
13065
|
+
},
|
|
13066
|
+
args: {
|
|
13067
|
+
name: {
|
|
13068
|
+
type: "positional",
|
|
13069
|
+
description: "Container name (yml in $MONOCEROS_HOME/container-configs/).",
|
|
13070
|
+
required: true
|
|
13071
|
+
},
|
|
13072
|
+
app: {
|
|
13073
|
+
type: "positional",
|
|
13074
|
+
description: "App to stop (a path under projects/ with .monoceros/launch.json). Omit to stop the container.",
|
|
13075
|
+
required: false
|
|
13076
|
+
},
|
|
13077
|
+
target: {
|
|
13078
|
+
type: "string",
|
|
13079
|
+
description: `Which launch target to stop (defaults to the app's "default" target, or its only one).`
|
|
13080
|
+
},
|
|
13081
|
+
service: {
|
|
13082
|
+
type: "string",
|
|
13083
|
+
description: "Restrict to a single compose service (e.g. postgres). Defaults to all."
|
|
13084
|
+
}
|
|
13085
|
+
},
|
|
13086
|
+
run({ args }) {
|
|
13087
|
+
if (typeof args.app === "string" && args.app.length > 0) {
|
|
13088
|
+
const app = args.app;
|
|
13089
|
+
const target = typeof args.target === "string" ? args.target : void 0;
|
|
13090
|
+
return dispatch(() => runAppCtl(args.name, ctlArgs("stop", app, target)));
|
|
13091
|
+
}
|
|
13092
|
+
return dispatch(async () => {
|
|
13093
|
+
const service = typeof args.service === "string" ? args.service : void 0;
|
|
13094
|
+
const exit = await runStop({
|
|
13095
|
+
root: containerDir(args.name),
|
|
13096
|
+
...service ? { service } : {},
|
|
13097
|
+
logger: { info: () => {
|
|
13098
|
+
} }
|
|
13099
|
+
});
|
|
13100
|
+
if (exit === 0) {
|
|
13101
|
+
consola39.success(
|
|
13102
|
+
service ? `Container '${args.name}' service '${service}' stopped.` : `Container '${args.name}' stopped.`
|
|
13103
|
+
);
|
|
13104
|
+
}
|
|
12914
13105
|
try {
|
|
12915
|
-
|
|
12916
|
-
|
|
13106
|
+
await maybeStopProxy({
|
|
13107
|
+
logger: { info: (msg) => consola39.info(msg) }
|
|
13108
|
+
});
|
|
13109
|
+
} catch (err) {
|
|
13110
|
+
consola39.warn(
|
|
13111
|
+
`Could not tear down the Traefik proxy: ${err instanceof Error ? err.message : String(err)}. Ignored.`
|
|
13112
|
+
);
|
|
12917
13113
|
}
|
|
12918
|
-
|
|
12919
|
-
|
|
12920
|
-
|
|
12921
|
-
|
|
12922
|
-
process.on("SIGINT", handler);
|
|
12923
|
-
return () => process.off("SIGINT", handler);
|
|
12924
|
-
};
|
|
12925
|
-
DEFAULT_LOCAL_ADDRESS = "127.0.0.1";
|
|
12926
|
-
IPV4_RE = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
13114
|
+
return exit;
|
|
13115
|
+
});
|
|
13116
|
+
}
|
|
13117
|
+
});
|
|
12927
13118
|
}
|
|
12928
13119
|
});
|
|
12929
13120
|
|
|
12930
13121
|
// src/commands/tunnel.ts
|
|
12931
|
-
import { defineCommand as
|
|
12932
|
-
import { consola as
|
|
13122
|
+
import { defineCommand as defineCommand34 } from "citty";
|
|
13123
|
+
import { consola as consola40 } from "consola";
|
|
12933
13124
|
function parseLocalPort(raw) {
|
|
12934
13125
|
if (raw === void 0) return void 0;
|
|
12935
13126
|
const n = Number(raw);
|
|
@@ -12945,7 +13136,7 @@ var init_tunnel = __esm({
|
|
|
12945
13136
|
"src/commands/tunnel.ts"() {
|
|
12946
13137
|
"use strict";
|
|
12947
13138
|
init_run3();
|
|
12948
|
-
tunnelCommand =
|
|
13139
|
+
tunnelCommand = defineCommand34({
|
|
12949
13140
|
meta: {
|
|
12950
13141
|
name: "tunnel",
|
|
12951
13142
|
group: "discovery",
|
|
@@ -12982,7 +13173,7 @@ var init_tunnel = __esm({
|
|
|
12982
13173
|
});
|
|
12983
13174
|
process.exit(exitCode);
|
|
12984
13175
|
} catch (err) {
|
|
12985
|
-
|
|
13176
|
+
consola40.error(err instanceof Error ? err.message : String(err));
|
|
12986
13177
|
process.exit(1);
|
|
12987
13178
|
}
|
|
12988
13179
|
}
|
|
@@ -13053,7 +13244,7 @@ var init_prune = __esm({
|
|
|
13053
13244
|
|
|
13054
13245
|
// src/upgrade/index.ts
|
|
13055
13246
|
import { existsSync as existsSync21, promises as fs20 } from "fs";
|
|
13056
|
-
import { consola as
|
|
13247
|
+
import { consola as consola41 } from "consola";
|
|
13057
13248
|
async function fetchRuntimeVersions() {
|
|
13058
13249
|
const tokenUrl = `https://ghcr.io/token?service=ghcr.io&scope=repository:${RUNTIME_REPO}:pull`;
|
|
13059
13250
|
const tokenRes = await fetch(tokenUrl);
|
|
@@ -13091,7 +13282,7 @@ ${yml}`;
|
|
|
13091
13282
|
}
|
|
13092
13283
|
async function runUpgrade(opts) {
|
|
13093
13284
|
const home = opts.monocerosHome ?? monocerosHome();
|
|
13094
|
-
const logger = opts.logger ??
|
|
13285
|
+
const logger = opts.logger ?? consola41;
|
|
13095
13286
|
const fetchVersions = opts.fetchVersions ?? fetchRuntimeVersions;
|
|
13096
13287
|
if (opts.list) {
|
|
13097
13288
|
const versions = await fetchVersions();
|
|
@@ -13237,7 +13428,7 @@ var init_upgrade = __esm({
|
|
|
13237
13428
|
});
|
|
13238
13429
|
|
|
13239
13430
|
// src/commands/upgrade.ts
|
|
13240
|
-
import { defineCommand as
|
|
13431
|
+
import { defineCommand as defineCommand35 } from "citty";
|
|
13241
13432
|
var upgradeCommand;
|
|
13242
13433
|
var init_upgrade2 = __esm({
|
|
13243
13434
|
"src/commands/upgrade.ts"() {
|
|
@@ -13245,7 +13436,7 @@ var init_upgrade2 = __esm({
|
|
|
13245
13436
|
init_upgrade();
|
|
13246
13437
|
init_version();
|
|
13247
13438
|
init_dispatch();
|
|
13248
|
-
upgradeCommand =
|
|
13439
|
+
upgradeCommand = defineCommand35({
|
|
13249
13440
|
meta: {
|
|
13250
13441
|
name: "upgrade",
|
|
13251
13442
|
group: "lifecycle",
|
|
@@ -13287,7 +13478,7 @@ var main_exports = {};
|
|
|
13287
13478
|
__export(main_exports, {
|
|
13288
13479
|
main: () => main
|
|
13289
13480
|
});
|
|
13290
|
-
import { defineCommand as
|
|
13481
|
+
import { defineCommand as defineCommand36 } from "citty";
|
|
13291
13482
|
var main;
|
|
13292
13483
|
var init_main = __esm({
|
|
13293
13484
|
"src/main.ts"() {
|
|
@@ -13320,6 +13511,7 @@ var init_main = __esm({
|
|
|
13320
13511
|
init_remove_repo();
|
|
13321
13512
|
init_remove_service();
|
|
13322
13513
|
init_run2();
|
|
13514
|
+
init_share();
|
|
13323
13515
|
init_shell2();
|
|
13324
13516
|
init_start();
|
|
13325
13517
|
init_status2();
|
|
@@ -13327,7 +13519,7 @@ var init_main = __esm({
|
|
|
13327
13519
|
init_tunnel();
|
|
13328
13520
|
init_upgrade2();
|
|
13329
13521
|
init_version();
|
|
13330
|
-
main =
|
|
13522
|
+
main = defineCommand36({
|
|
13331
13523
|
meta: {
|
|
13332
13524
|
name: "monoceros",
|
|
13333
13525
|
version: CLI_VERSION,
|
|
@@ -13364,6 +13556,7 @@ var init_main = __esm({
|
|
|
13364
13556
|
"remove-port": removePortCommand,
|
|
13365
13557
|
port: portCommand,
|
|
13366
13558
|
tunnel: tunnelCommand,
|
|
13559
|
+
share: shareCommand,
|
|
13367
13560
|
completion: completionCommand,
|
|
13368
13561
|
__complete: __completeCommand,
|
|
13369
13562
|
"__update-check": __updateCheckCommand,
|