coderemote 0.3.0 → 0.3.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.
Files changed (2) hide show
  1. package/dist/coderemote.js +52 -15
  2. package/package.json +1 -1
@@ -4,6 +4,7 @@
4
4
  import { execFile as execFile5, spawn as spawn3 } from "node:child_process";
5
5
  import { randomBytes } from "node:crypto";
6
6
  import fs8 from "node:fs";
7
+ import net2 from "node:net";
7
8
  import os4 from "node:os";
8
9
  import path7 from "node:path";
9
10
  import { fileURLToPath } from "node:url";
@@ -1478,7 +1479,7 @@ function parse(out) {
1478
1479
  return JSON.parse(out.slice(out.indexOf("{")));
1479
1480
  }
1480
1481
  function machineName(description) {
1481
- return (description || "").replace(/^(coderemote|vsremote)\s+/, "").replace(/\.local$/, "") || null;
1482
+ return (description || "").replace(/^(coderemote|vsremote)\s+/, "").replace(/\.local\b/, "") || null;
1482
1483
  }
1483
1484
  function machine(tunnel, current) {
1484
1485
  return {
@@ -1587,7 +1588,7 @@ function installCommand(installedAt) {
1587
1588
  // package.json
1588
1589
  var package_default = {
1589
1590
  name: "coderemote",
1590
- version: "0.3.0",
1591
+ version: "0.3.1",
1591
1592
  description: "Use your computer from your iPhone: files, git and a real terminal over a secure Microsoft Dev Tunnel. No SSH, no port forwarding, no VPN.",
1592
1593
  keywords: [
1593
1594
  "remote",
@@ -1935,12 +1936,23 @@ function loadConfig() {
1935
1936
  cfg2.token = randomBytes(32).toString("base64url");
1936
1937
  saveConfig(cfg2);
1937
1938
  }
1939
+ if (cfg2.tunnelId && !cfg2.tunnels) {
1940
+ cfg2.tunnels = { [DEFAULT_PORT]: cfg2.tunnelId };
1941
+ saveConfig(cfg2);
1942
+ }
1938
1943
  return cfg2;
1939
1944
  }
1940
1945
  function saveConfig(cfg2) {
1941
1946
  fs8.mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
1942
1947
  fs8.writeFileSync(CONFIG_FILE, JSON.stringify(cfg2, null, 2), { mode: 384 });
1943
1948
  }
1949
+ function portFree(port2) {
1950
+ return new Promise((resolve) => {
1951
+ const probe = net2.createServer();
1952
+ probe.once("error", () => resolve(false));
1953
+ probe.listen(port2, "127.0.0.1", () => probe.close(() => resolve(true)));
1954
+ });
1955
+ }
1944
1956
  function listen(token, port2, root2, tunnelToken, preview, status2) {
1945
1957
  const server = createAgentServer({ token, root: root2, tunnelToken, preview, tunnelStatus: status2 });
1946
1958
  return new Promise((resolve, reject) => {
@@ -2009,20 +2021,22 @@ function applyAccess(tunnelId, anonymous2) {
2009
2021
  return anonymous2 ? devtunnel("access", "create", tunnelId, "--anonymous") : devtunnel("access", "reset", tunnelId);
2010
2022
  }
2011
2023
  async function ensureTunnel(cfg2, port2, anonymous2) {
2012
- if (cfg2.tunnelId) {
2024
+ const known = cfg2.tunnels?.[port2];
2025
+ if (known) {
2013
2026
  const [updated, access] = await Promise.all([
2014
- devtunnel("update", cfg2.tunnelId, "--expiration", "30d", "--add-labels", MACHINE_LABEL),
2015
- applyAccess(cfg2.tunnelId, anonymous2)
2027
+ devtunnel("update", known, "--expiration", "30d", "--add-labels", MACHINE_LABEL),
2028
+ applyAccess(known, anonymous2)
2016
2029
  ]);
2017
2030
  if (updated.ok) {
2018
2031
  if (!access.ok) throw new Error(`devtunnel access failed:
2019
2032
  ${access.out}`);
2020
- return cfg2.tunnelId;
2033
+ return known;
2021
2034
  }
2022
2035
  if (!/not found/i.test(updated.out)) throw new Error(`devtunnel update failed:
2023
2036
  ${updated.out}`);
2024
2037
  }
2025
- const createArgs = ["create", "--expiration", "30d", "-d", `coderemote ${os4.hostname()}`, "--labels", MACHINE_LABEL, "--json"];
2038
+ const name = port2 === DEFAULT_PORT ? os4.hostname() : `${os4.hostname()} :${port2}`;
2039
+ const createArgs = ["create", "--expiration", "30d", "-d", `coderemote ${name}`, "--labels", MACHINE_LABEL, "--json"];
2026
2040
  if (anonymous2) createArgs.push("--allow-anonymous");
2027
2041
  const created = await devtunnel(...createArgs);
2028
2042
  if (!created.ok) throw new Error(`devtunnel create failed:
@@ -2033,7 +2047,8 @@ ${created.out}`);
2033
2047
  const portRes = await devtunnel("port", "create", tunnelId, "-p", String(port2), "--protocol", "http");
2034
2048
  if (!portRes.ok) throw new Error(`devtunnel port create failed:
2035
2049
  ${portRes.out}`);
2036
- cfg2.tunnelId = tunnelId;
2050
+ cfg2.tunnels = { ...cfg2.tunnels, [port2]: tunnelId };
2051
+ if (port2 === DEFAULT_PORT) cfg2.tunnelId = tunnelId;
2037
2052
  saveConfig(cfg2);
2038
2053
  return tunnelId;
2039
2054
  }
@@ -2131,7 +2146,8 @@ var USAGE = `Usage: coderemote <command> [options]
2131
2146
 
2132
2147
  Options:
2133
2148
  --root DIR folder to share (default: current folder)
2134
- --port N local port (default: ${DEFAULT_PORT})
2149
+ --port N local port (default: the first free one from ${DEFAULT_PORT}; each port
2150
+ keeps its own tunnel URL, so a folder pairs once per port)
2135
2151
  --allow-anonymous up: skip the sign-in, the pairing token alone grants access
2136
2152
  (alias --no-auth)
2137
2153
  --web up: also run \`code tunnel\` and print a vscode.dev link that opens
@@ -2149,11 +2165,32 @@ var root = path7.resolve(flag("--root") ?? process.cwd());
2149
2165
  var cfg = loadConfig();
2150
2166
  var registry = new AgentRegistry(path7.join(CONFIG_DIR, "agents"));
2151
2167
  async function start({ tunnel }) {
2152
- const running = registry.list().find((a) => a.port === port);
2153
- if (running) {
2154
- console.error(`An agent already runs on port ${port} for ${running.root} (pid ${running.pid}).`);
2155
- console.error("Stop it with `coderemote stop --all`, or pick another port with --port.");
2156
- process.exit(1);
2168
+ const agents = registry.list();
2169
+ if (flag("--port")) {
2170
+ const running = agents.find((a) => a.port === port);
2171
+ if (running) {
2172
+ console.error(`An agent already runs on port ${port} for ${running.root} (pid ${running.pid}).`);
2173
+ console.error("Stop it with `coderemote stop --all`, or pick another port with --port.");
2174
+ process.exit(1);
2175
+ }
2176
+ } else {
2177
+ const sharing = agents.find((a) => a.root === root);
2178
+ if (sharing) {
2179
+ console.error(`${root} is already shared on port ${sharing.port} (pid ${sharing.pid}).`);
2180
+ console.error("Stop it with `coderemote stop`, or start another one with --port.");
2181
+ process.exit(1);
2182
+ }
2183
+ const taken = async (p) => agents.some((a) => a.port === p) || !await portFree(p);
2184
+ const remembered = cfg.folderPorts?.[root];
2185
+ if (remembered && !await taken(remembered)) port = remembered;
2186
+ else {
2187
+ const claimed = new Set(Object.values(cfg.folderPorts ?? {}));
2188
+ while (claimed.has(port) || await taken(port)) port++;
2189
+ }
2190
+ if (port !== remembered) {
2191
+ cfg.folderPorts = { ...cfg.folderPorts, [root]: port };
2192
+ saveConfig(cfg);
2193
+ }
2157
2194
  }
2158
2195
  let server;
2159
2196
  let tunnelId;
@@ -2193,7 +2230,7 @@ async function start({ tunnel }) {
2193
2230
  account = user;
2194
2231
  spinner.succeed();
2195
2232
  spinner.start("Connecting to the relay");
2196
- const known = cfg.tunnelId;
2233
+ const known = cfg.tunnels?.[port];
2197
2234
  let hosting = known ? hostTunnel(known, port) : null;
2198
2235
  hosting?.catch(() => {
2199
2236
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coderemote",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Use your computer from your iPhone: files, git and a real terminal over a secure Microsoft Dev Tunnel. No SSH, no port forwarding, no VPN.",
5
5
  "keywords": [
6
6
  "remote",