@getmonoceros/workbench 1.39.2 → 1.39.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 CHANGED
@@ -9138,7 +9138,7 @@ var CLI_VERSION;
9138
9138
  var init_version = __esm({
9139
9139
  "src/version.ts"() {
9140
9140
  "use strict";
9141
- CLI_VERSION = true ? "1.39.2" : "dev";
9141
+ CLI_VERSION = true ? "1.39.3" : "dev";
9142
9142
  }
9143
9143
  });
9144
9144
 
@@ -9898,7 +9898,8 @@ var init_resolve = __esm({
9898
9898
  }
9899
9899
  },
9900
9900
  share: {
9901
- positionals: [containerName, appCandidates]
9901
+ positionals: [containerName, appCandidates],
9902
+ flags: { "--forward-ports": { type: "value" } }
9902
9903
  },
9903
9904
  completion: {
9904
9905
  positionals: [shellValues]
@@ -13031,8 +13032,8 @@ function renderCaddyfile(sites, certFile, keyFile) {
13031
13032
  }
13032
13033
  function buildCaddyDockerArgs(input) {
13033
13034
  const args = ["run", "--rm", "-i", `--network=${input.network}`];
13034
- for (const port of input.ports) {
13035
- args.push("-p", `${input.localAddress}:${port}:${port}`);
13035
+ for (const { host, container } of input.ports) {
13036
+ args.push("-p", `${input.localAddress}:${host}:${container}`);
13036
13037
  }
13037
13038
  args.push(
13038
13039
  "-v",
@@ -13057,6 +13058,31 @@ import { spawn as spawn14, spawnSync as spawnSync2 } from "child_process";
13057
13058
  import { consola as consola35 } from "consola";
13058
13059
  import { promises as fs22 } from "fs";
13059
13060
  import path34 from "path";
13061
+ function parseForwardPorts(raw) {
13062
+ const out = [];
13063
+ for (const entry2 of raw.split(",").map((s) => s.trim()).filter(Boolean)) {
13064
+ const m = /^(\d+):(\d+)$/.exec(entry2);
13065
+ if (!m) {
13066
+ throw new Error(
13067
+ `Invalid --forward-ports entry '${entry2}': expected host:container (e.g. 15173:5173).`
13068
+ );
13069
+ }
13070
+ const host = Number(m[1]);
13071
+ const container = Number(m[2]);
13072
+ for (const [label, port] of [
13073
+ ["host", host],
13074
+ ["container", container]
13075
+ ]) {
13076
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
13077
+ throw new Error(
13078
+ `Invalid --forward-ports ${label} port '${port}': must be between 1 and 65535.`
13079
+ );
13080
+ }
13081
+ }
13082
+ out.push({ host, container });
13083
+ }
13084
+ return out;
13085
+ }
13060
13086
  function realHostAddresses() {
13061
13087
  let ip;
13062
13088
  for (const list of Object.values(os4.networkInterfaces())) {
@@ -13113,6 +13139,37 @@ async function caTrustDisplayPath(caCertPath, home) {
13113
13139
  const rel = path34.relative(home, caCertPath).split(path34.sep).join("\\");
13114
13140
  return `${prof.homeWin}\\.monoceros\\${rel}`;
13115
13141
  }
13142
+ async function findFreeHostPort(start, probe, taken) {
13143
+ let port = start > 65535 ? 2e4 : start;
13144
+ for (let i = 0; i < 200 && port <= 65535; i++, port++) {
13145
+ if (taken.has(port)) continue;
13146
+ const result = await probe(port, SHARE_ADDRESS);
13147
+ if (result.ok) return port;
13148
+ }
13149
+ return start;
13150
+ }
13151
+ function formatShareCollision(input) {
13152
+ const plural = input.busyHostPorts.length > 1;
13153
+ const cmd = `monoceros share ${input.name} ${input.app} --forward-ports ${input.suggestions.join(",")}`;
13154
+ return [
13155
+ `Cannot share ${input.name}/${input.app}: host port${plural ? "s" : ""} ${input.busyHostPorts.join(", ")} already in use.`,
13156
+ "",
13157
+ "Your IDE forwards the container's ports to 127.0.0.1 (VS Code, Codium",
13158
+ "and JetBrains auto-forward over Remote-SSH, and it cannot be reliably",
13159
+ "turned off). That collides with share, which binds these ports on",
13160
+ "0.0.0.0 to reach other devices.",
13161
+ "",
13162
+ "Resolve it one of two ways:",
13163
+ "",
13164
+ ` 1. In the IDE's PORTS panel, right-click each port -> "Stop Forwarding`,
13165
+ ' Port" (stays gone across reconnects), then re-run share unchanged.',
13166
+ "",
13167
+ " 2. Re-run share and publish the busy ports under different host ports",
13168
+ " (Docker order, host:container):",
13169
+ "",
13170
+ ` ${cmd}`
13171
+ ].join("\n");
13172
+ }
13116
13173
  async function runShare(opts) {
13117
13174
  const log = opts.logger ?? {
13118
13175
  info: (m) => consola35.info(m),
@@ -13133,16 +13190,49 @@ async function runShare(opts) {
13133
13190
  );
13134
13191
  }
13135
13192
  const ports = [...new Set(ported.map((t) => t.port))];
13193
+ const overrides = /* @__PURE__ */ new Map();
13194
+ for (const fp of opts.forwardPorts ?? []) {
13195
+ if (!ports.includes(fp.container)) {
13196
+ throw new Error(
13197
+ `--forward-ports maps container port ${fp.container}, but no shared target uses it. Shared ports: ${ports.join(", ")}.`
13198
+ );
13199
+ }
13200
+ overrides.set(fp.container, fp.host);
13201
+ }
13202
+ const hostPortFor = (container) => overrides.get(container) ?? container;
13203
+ const pairs = ports.map((container) => ({
13204
+ host: hostPortFor(container),
13205
+ container
13206
+ }));
13207
+ const probe = opts.probe ?? realPortProbe2;
13208
+ const busy = [];
13209
+ for (const pair of pairs) {
13210
+ const result = await probe(pair.host, SHARE_ADDRESS);
13211
+ if (!result.ok) busy.push(pair);
13212
+ }
13213
+ if (busy.length > 0) {
13214
+ const taken = new Set(pairs.map((p) => p.host));
13215
+ const suggestions = [];
13216
+ for (const b of busy) {
13217
+ const free = await findFreeHostPort(b.container + 1e4, probe, taken);
13218
+ taken.add(free);
13219
+ suggestions.push(`${free}:${b.container}`);
13220
+ }
13221
+ throw new Error(
13222
+ formatShareCollision({
13223
+ name: opts.name,
13224
+ app: opts.app,
13225
+ busyHostPorts: busy.map((b) => b.host),
13226
+ suggestions
13227
+ })
13228
+ );
13229
+ }
13136
13230
  const resolve = opts.resolve ?? resolveTunnelTarget;
13137
13231
  const base = await resolve({
13138
13232
  name: opts.name,
13139
13233
  target: String(ports[0]),
13140
13234
  ...opts.monocerosHome !== void 0 ? { monocerosHome: opts.monocerosHome } : {}
13141
13235
  });
13142
- const preflight = opts.preflight ?? preflightLocalPort;
13143
- for (const port of ports) {
13144
- await preflight({ port, address: SHARE_ADDRESS });
13145
- }
13146
13236
  const { ip: localIp, mdnsName } = (opts.hostAddresses ?? realHostAddresses)();
13147
13237
  const winLanIp = await (opts.resolveWindowsLanIp ?? resolveWindowsLanIp)();
13148
13238
  const ip = winLanIp ?? localIp;
@@ -13183,7 +13273,7 @@ async function runShare(opts) {
13183
13273
  for (const t of ported) {
13184
13274
  banner.push("", ` ${cyan2(t.name)}`);
13185
13275
  for (const addr of addresses) {
13186
- banner.push(` https://${addr}:${t.port}`);
13276
+ banner.push(` https://${addr}:${hostPortFor(t.port)}`);
13187
13277
  }
13188
13278
  }
13189
13279
  banner.push(
@@ -13199,7 +13289,7 @@ async function runShare(opts) {
13199
13289
  dockerSpawn(
13200
13290
  buildCaddyDockerArgs({
13201
13291
  localAddress: SHARE_ADDRESS,
13202
- ports,
13292
+ ports: pairs,
13203
13293
  network: base.network,
13204
13294
  certDir: tls.certDir,
13205
13295
  caddyfilePath
@@ -13262,11 +13352,21 @@ var init_share = __esm({
13262
13352
  type: "positional",
13263
13353
  description: "App to share (a path under projects/ with .monoceros/launch.json). Every target with a `port` is exposed on the LAN.",
13264
13354
  required: true
13355
+ },
13356
+ "forward-ports": {
13357
+ type: "string",
13358
+ description: "Publish busy container ports under different host ports. Docker `-p` order (host:container), comma-separated: --forward-ports 15173:5173,18000:8000. Use when an IDE already forwards the port to localhost. Unlisted ports keep parity.",
13359
+ required: false
13265
13360
  }
13266
13361
  },
13267
13362
  async run({ args }) {
13268
13363
  try {
13269
- const exitCode = await runShare({ name: args.name, app: args.app });
13364
+ const forwardPorts = args["forward-ports"] ? parseForwardPorts(String(args["forward-ports"])) : void 0;
13365
+ const exitCode = await runShare({
13366
+ name: args.name,
13367
+ app: args.app,
13368
+ ...forwardPorts ? { forwardPorts } : {}
13369
+ });
13270
13370
  process.exit(exitCode);
13271
13371
  } catch (err) {
13272
13372
  consola36.error(err instanceof Error ? err.message : String(err));