@getmonoceros/workbench 1.28.0 → 1.29.1

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
@@ -5175,7 +5175,7 @@ var init_transform = __esm({
5175
5175
  // src/devcontainer/ssh-attach.ts
5176
5176
  import { spawn as spawn4 } from "child_process";
5177
5177
  import { promises as fs10 } from "fs";
5178
- import { existsSync as existsSync8 } from "fs";
5178
+ import { existsSync as existsSync8, readFileSync as readFileSync4 } from "fs";
5179
5179
  import os2 from "os";
5180
5180
  import path13 from "path";
5181
5181
  function sshHomeDir(home) {
@@ -5275,6 +5275,155 @@ ${existing}` : prefix;
5275
5275
  await fs10.chmod(configPath, 384).catch(() => {
5276
5276
  });
5277
5277
  }
5278
+ function runCapture(cmd, args) {
5279
+ return new Promise((resolve, reject) => {
5280
+ const child = spawn4(cmd, args, {
5281
+ stdio: ["ignore", "pipe", "ignore"]
5282
+ });
5283
+ let stdout = "";
5284
+ child.stdout.on("data", (c) => {
5285
+ stdout += c.toString();
5286
+ });
5287
+ child.on("error", reject);
5288
+ child.on("exit", (code) => resolve({ stdout, exitCode: code ?? 0 }));
5289
+ });
5290
+ }
5291
+ function isWsl() {
5292
+ try {
5293
+ if (process.env.WSL_DISTRO_NAME) return true;
5294
+ const rel = readFileSync4(
5295
+ "/proc/sys/kernel/osrelease",
5296
+ "utf8"
5297
+ ).toLowerCase();
5298
+ return rel.includes("microsoft") || rel.includes("wsl");
5299
+ } catch {
5300
+ return false;
5301
+ }
5302
+ }
5303
+ async function resolveWindowsProfile() {
5304
+ try {
5305
+ const r = await runCapture("cmd.exe", ["/c", "echo %USERPROFILE%"]);
5306
+ const homeWin = r.stdout.replace(/\r/g, "").trim();
5307
+ if (r.exitCode !== 0 || !homeWin || homeWin.includes("%USERPROFILE%")) {
5308
+ return null;
5309
+ }
5310
+ const w = await runCapture("wslpath", ["-u", homeWin]);
5311
+ const homeWsl = w.stdout.trim();
5312
+ const user = homeWin.split("\\").pop() ?? "";
5313
+ if (w.exitCode !== 0 || !homeWsl || !user) return null;
5314
+ return { homeWsl, homeWin, user };
5315
+ } catch {
5316
+ return null;
5317
+ }
5318
+ }
5319
+ async function realLockWindowsKey(winKeyPath, user) {
5320
+ await runCapture("icacls.exe", [
5321
+ winKeyPath,
5322
+ "/inheritance:r",
5323
+ "/grant:r",
5324
+ `${user}:R`
5325
+ ]);
5326
+ }
5327
+ function resolveWindowsDeps(d) {
5328
+ return {
5329
+ isWsl: d?.isWsl ?? isWsl,
5330
+ resolveProfile: d?.resolveProfile ?? resolveWindowsProfile,
5331
+ lockKey: d?.lockKey ?? realLockWindowsKey
5332
+ };
5333
+ }
5334
+ function windowsHostBlock(hostAlias, keyWin) {
5335
+ return [
5336
+ `Host ${hostAlias}`,
5337
+ ` User node`,
5338
+ ` IdentityFile ${keyWin}`,
5339
+ ` IdentitiesOnly yes`,
5340
+ ` StrictHostKeyChecking no`,
5341
+ ` UserKnownHostsFile NUL`,
5342
+ ` ProxyCommand docker exec -i ${hostAlias} socat - TCP:127.0.0.1:22`
5343
+ ].join("\n");
5344
+ }
5345
+ function escapeRegExp2(s) {
5346
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5347
+ }
5348
+ function blockMarkers(hostAlias) {
5349
+ return {
5350
+ begin: `# >>> monoceros ${hostAlias} >>>`,
5351
+ end: `# <<< monoceros ${hostAlias} <<<`
5352
+ };
5353
+ }
5354
+ async function upsertMarkedBlock(configPath, hostAlias, body) {
5355
+ const { begin, end } = blockMarkers(hostAlias);
5356
+ const section = `${begin}
5357
+ ${body}
5358
+ ${end}`;
5359
+ await fs10.mkdir(path13.dirname(configPath), { recursive: true });
5360
+ let existing = "";
5361
+ try {
5362
+ existing = await fs10.readFile(configPath, "utf8");
5363
+ } catch {
5364
+ existing = "";
5365
+ }
5366
+ const re = new RegExp(`${escapeRegExp2(begin)}[\\s\\S]*?${escapeRegExp2(end)}`);
5367
+ if (re.test(existing)) {
5368
+ await fs10.writeFile(configPath, existing.replace(re, section));
5369
+ return;
5370
+ }
5371
+ const sep = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
5372
+ await fs10.writeFile(configPath, `${existing}${sep}${section}
5373
+ `);
5374
+ }
5375
+ async function removeMarkedBlock(configPath, hostAlias) {
5376
+ let existing = "";
5377
+ try {
5378
+ existing = await fs10.readFile(configPath, "utf8");
5379
+ } catch {
5380
+ return;
5381
+ }
5382
+ const { begin, end } = blockMarkers(hostAlias);
5383
+ const re = new RegExp(
5384
+ `\\n?${escapeRegExp2(begin)}[\\s\\S]*?${escapeRegExp2(end)}\\n?`,
5385
+ "g"
5386
+ );
5387
+ await fs10.writeFile(
5388
+ configPath,
5389
+ existing.replace(re, "\n").replace(/\n{3,}/g, "\n\n")
5390
+ );
5391
+ }
5392
+ async function setupWindowsBridge(name, hostAlias, privateKey, deps, logger) {
5393
+ if (!deps.isWsl()) return;
5394
+ const profile = await deps.resolveProfile();
5395
+ if (!profile) {
5396
+ logger.warn(
5397
+ "WSL detected but the Windows user profile could not be resolved; skipping the Windows SSH bridge."
5398
+ );
5399
+ return;
5400
+ }
5401
+ const monoDir = path13.join(profile.homeWsl, ".ssh", "monoceros");
5402
+ await fs10.mkdir(monoDir, { recursive: true });
5403
+ await fs10.copyFile(privateKey, path13.join(monoDir, name));
5404
+ const keyWin = `${profile.homeWin}\\.ssh\\monoceros\\${name}`;
5405
+ await upsertMarkedBlock(
5406
+ path13.join(profile.homeWsl, ".ssh", "config"),
5407
+ hostAlias,
5408
+ windowsHostBlock(hostAlias, keyWin)
5409
+ );
5410
+ await deps.lockKey(keyWin, profile.user);
5411
+ logger.info(
5412
+ `Windows SSH bridge ready: \`ssh ${hostAlias}\` (Codium/Gateway too).`
5413
+ );
5414
+ }
5415
+ async function removeWindowsBridge(name, hostAlias, deps) {
5416
+ if (!deps.isWsl()) return;
5417
+ const profile = await deps.resolveProfile();
5418
+ if (!profile) return;
5419
+ await fs10.rm(path13.join(profile.homeWsl, ".ssh", "monoceros", name), {
5420
+ force: true
5421
+ });
5422
+ await removeMarkedBlock(
5423
+ path13.join(profile.homeWsl, ".ssh", "config"),
5424
+ hostAlias
5425
+ );
5426
+ }
5278
5427
  async function setupSshAttach(opts) {
5279
5428
  const hostAlias = `monoceros-${opts.name}`;
5280
5429
  const logger = opts.logger ?? { info: () => {
@@ -5300,11 +5449,32 @@ async function setupSshAttach(opts) {
5300
5449
  configEntryContent(opts.name, hostAlias, keys.privateKey, proxyScript)
5301
5450
  );
5302
5451
  await ensureInclude(userSshDir, opts.home);
5452
+ try {
5453
+ await setupWindowsBridge(
5454
+ opts.name,
5455
+ hostAlias,
5456
+ keys.privateKey,
5457
+ resolveWindowsDeps(opts.windows),
5458
+ logger
5459
+ );
5460
+ } catch (err) {
5461
+ logger.warn(
5462
+ `Windows SSH bridge skipped: ${err instanceof Error ? err.message : String(err)}`
5463
+ );
5464
+ }
5303
5465
  return { hostAlias, configured: true };
5304
5466
  }
5305
- async function removeSshAttach(home, name) {
5467
+ async function removeSshAttach(home, name, windows) {
5306
5468
  await fs10.rm(sshProxyScriptPath(home, name), { force: true });
5307
5469
  await fs10.rm(sshConfigEntryPath(home, name), { force: true });
5470
+ try {
5471
+ await removeWindowsBridge(
5472
+ name,
5473
+ `monoceros-${name}`,
5474
+ resolveWindowsDeps(windows)
5475
+ );
5476
+ } catch {
5477
+ }
5308
5478
  }
5309
5479
  var realKeygen;
5310
5480
  var init_ssh_attach = __esm({
@@ -6381,13 +6551,13 @@ var init_runtime_pull_hint = __esm({
6381
6551
 
6382
6552
  // src/devcontainer/cli.ts
6383
6553
  import { spawn as spawn5 } from "child_process";
6384
- import { readFileSync as readFileSync4 } from "fs";
6554
+ import { readFileSync as readFileSync5 } from "fs";
6385
6555
  import { createRequire } from "module";
6386
6556
  import path16 from "path";
6387
6557
  function devcontainerCliPath() {
6388
6558
  if (cachedBinaryPath) return cachedBinaryPath;
6389
6559
  const pkgJsonPath = require_.resolve("@devcontainers/cli/package.json");
6390
- const pkg = JSON.parse(readFileSync4(pkgJsonPath, "utf8"));
6560
+ const pkg = JSON.parse(readFileSync5(pkgJsonPath, "utf8"));
6391
6561
  const binEntry = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.devcontainer ?? "";
6392
6562
  if (!binEntry) {
6393
6563
  throw new Error("Could not resolve @devcontainers/cli bin entry.");
@@ -7612,7 +7782,7 @@ var init_apply = __esm({
7612
7782
 
7613
7783
  // src/devcontainer/browser-bridge.ts
7614
7784
  import { spawn as spawn9 } from "child_process";
7615
- import { existsSync as existsSync11, promises as fsp5, readFileSync as readFileSync5 } from "fs";
7785
+ import { existsSync as existsSync11, promises as fsp5, readFileSync as readFileSync6 } from "fs";
7616
7786
  import http from "http";
7617
7787
  import path20 from "path";
7618
7788
  function parseCallbackTarget(authUrl) {
@@ -7715,7 +7885,7 @@ exit 0
7715
7885
  if (!existsSync11(urlFile)) return;
7716
7886
  let content = "";
7717
7887
  try {
7718
- content = readFileSync5(urlFile, "utf8");
7888
+ content = readFileSync6(urlFile, "utf8");
7719
7889
  } catch {
7720
7890
  return;
7721
7891
  }
@@ -7808,11 +7978,23 @@ function resolveOnPath(bin) {
7808
7978
  }
7809
7979
  return null;
7810
7980
  }
7811
- function resolveEditorBinary(tool) {
7981
+ async function resolveEditorBinary(tool) {
7812
7982
  if (resolveOnPath(tool.bin)) return tool.bin;
7813
7983
  if (process.platform === "darwin" && existsSync13(tool.macAppBin)) {
7814
7984
  return tool.macAppBin;
7815
7985
  }
7986
+ if (isWsl()) {
7987
+ const candidates = [`/mnt/c/Program Files/${tool.winInstallSubpath}`];
7988
+ const profile = await resolveWindowsProfile();
7989
+ if (profile) {
7990
+ candidates.push(
7991
+ `${profile.homeWsl}/AppData/Local/Programs/${tool.winInstallSubpath}`
7992
+ );
7993
+ }
7994
+ for (const candidate of candidates) {
7995
+ if (existsSync13(candidate)) return candidate;
7996
+ }
7997
+ }
7816
7998
  return null;
7817
7999
  }
7818
8000
  function realLaunch(bin, args) {
@@ -7854,7 +8036,7 @@ async function runOpen(opts) {
7854
8036
  );
7855
8037
  }
7856
8038
  const resolve = opts.binResolver ?? resolveEditorBinary;
7857
- const bin = resolve(editor);
8039
+ const bin = await resolve(editor);
7858
8040
  if (!bin) {
7859
8041
  const where = process.platform === "darwin" ? " on PATH or in /Applications" : " on PATH";
7860
8042
  throw new Error(
@@ -7880,12 +8062,14 @@ var init_open = __esm({
7880
8062
  label: "VS Code",
7881
8063
  bin: "code",
7882
8064
  macAppBin: "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
7883
- setupHint: `install the Remote-SSH extension and run "Shell Command: Install 'code' command in PATH"`
8065
+ winInstallSubpath: "Microsoft VS Code/bin/code",
8066
+ setupHint: `install the Remote-SSH extension; on macOS also run "Shell Command: Install 'code' command in PATH"`
7884
8067
  },
7885
8068
  codium: {
7886
8069
  label: "VS Codium",
7887
8070
  bin: "codium",
7888
8071
  macAppBin: "/Applications/VSCodium.app/Contents/Resources/app/bin/codium",
8072
+ winInstallSubpath: "VSCodium/bin/codium",
7889
8073
  setupHint: 'install the "Open Remote - SSH" extension (the codium CLI ships with the app)'
7890
8074
  }
7891
8075
  };
@@ -7898,7 +8082,7 @@ var CLI_VERSION;
7898
8082
  var init_version = __esm({
7899
8083
  "src/version.ts"() {
7900
8084
  "use strict";
7901
- CLI_VERSION = true ? "1.28.0" : "dev";
8085
+ CLI_VERSION = true ? "1.29.1" : "dev";
7902
8086
  }
7903
8087
  });
7904
8088