portless 0.2.0 → 0.2.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.
Files changed (2) hide show
  1. package/dist/cli.js +59 -7
  2. package/package.json +18 -15
package/dist/cli.js CHANGED
@@ -11,7 +11,7 @@ import chalk from "chalk";
11
11
  import * as fs from "fs";
12
12
  import * as path from "path";
13
13
  import * as readline from "readline";
14
- import { spawn, spawnSync } from "child_process";
14
+ import { execSync, spawn, spawnSync } from "child_process";
15
15
 
16
16
  // src/cli-utils.ts
17
17
  import * as net from "net";
@@ -85,6 +85,18 @@ function readProxyPort() {
85
85
  return DEFAULT_PROXY_PORT;
86
86
  }
87
87
  }
88
+ function findPidOnPort(port) {
89
+ try {
90
+ const output = execSync(`lsof -ti tcp:${port} -sTCP:LISTEN`, {
91
+ encoding: "utf-8",
92
+ timeout: 5e3
93
+ });
94
+ const pid = parseInt(output.trim().split("\n")[0], 10);
95
+ return isNaN(pid) ? null : pid;
96
+ } catch {
97
+ return null;
98
+ }
99
+ }
88
100
  async function waitForProxy(proxyPort, maxAttempts = 20, intervalMs = 250) {
89
101
  const port = proxyPort ?? readProxyPort();
90
102
  for (let i = 0; i < maxAttempts; i++) {
@@ -199,11 +211,40 @@ function startProxyServer(proxyPort) {
199
211
  }
200
212
  async function stopProxy() {
201
213
  const pidPath = store.pidPath;
214
+ const proxyPort = readProxyPort();
202
215
  if (!fs.existsSync(pidPath)) {
203
- console.log(chalk.yellow("Proxy is not running."));
216
+ if (await isProxyRunning(proxyPort)) {
217
+ console.log(chalk.yellow(`PID file is missing but port ${proxyPort} is still in use.`));
218
+ const pid = findPidOnPort(proxyPort);
219
+ if (pid !== null) {
220
+ try {
221
+ process.kill(pid, "SIGTERM");
222
+ try {
223
+ fs.unlinkSync(PROXY_PORT_PATH);
224
+ } catch {
225
+ }
226
+ console.log(chalk.green(`Killed process ${pid}. Proxy stopped.`));
227
+ } catch (err) {
228
+ if (isErrnoException(err) && err.code === "EPERM") {
229
+ console.error(chalk.red("Permission denied. The proxy runs as root."));
230
+ console.log(chalk.blue("Use: sudo portless proxy stop"));
231
+ } else {
232
+ const message = err instanceof Error ? err.message : String(err);
233
+ console.error(chalk.red("Failed to stop proxy:"), message);
234
+ }
235
+ }
236
+ } else if (process.getuid?.() !== 0) {
237
+ console.error(chalk.red("Permission denied. The proxy likely runs as root."));
238
+ console.log(chalk.blue("Use: sudo portless proxy stop"));
239
+ } else {
240
+ console.error(chalk.red(`Could not identify the process on port ${proxyPort}.`));
241
+ console.log(chalk.blue(`Try: sudo kill "$(lsof -ti tcp:${proxyPort})"`));
242
+ }
243
+ } else {
244
+ console.log(chalk.yellow("Proxy is not running."));
245
+ }
204
246
  return;
205
247
  }
206
- const proxyPort = readProxyPort();
207
248
  try {
208
249
  const pid = parseInt(fs.readFileSync(pidPath, "utf-8"), 10);
209
250
  if (isNaN(pid)) {
@@ -326,6 +367,13 @@ portless
326
367
  }
327
368
  async function main() {
328
369
  const args = process.argv.slice(2);
370
+ const isNpx = process.env.npm_command === "exec" && !process.env.npm_lifecycle_event;
371
+ const isPnpmDlx = !!process.env.PNPM_SCRIPT_SRC_DIR && !process.env.npm_lifecycle_event;
372
+ if (isNpx || isPnpmDlx) {
373
+ console.error(chalk.red("Error: portless should not be run via npx or pnpm dlx."));
374
+ console.log(chalk.blue("Install globally: npm install -g portless"));
375
+ process.exit(1);
376
+ }
329
377
  const skipPortless = process.env.PORTLESS === "0" || process.env.PORTLESS === "skip";
330
378
  if (skipPortless && args.length >= 2 && args[0] !== "proxy") {
331
379
  spawnCommand(args.slice(1));
@@ -338,6 +386,10 @@ ${chalk.bold("portless")} - Replace port numbers with stable, named .localhost U
338
386
  Eliminates port conflicts, memorizing port numbers, and cookie/storage
339
387
  clashes by giving each dev server a stable .localhost URL.
340
388
 
389
+ ${chalk.bold("Install:")}
390
+ ${chalk.cyan("npm install -g portless")}
391
+ Do NOT add portless as a project dependency.
392
+
341
393
  ${chalk.bold("Usage:")}
342
394
  ${chalk.cyan("sudo portless proxy")} Start the proxy (run once, keep open)
343
395
  ${chalk.cyan("sudo portless proxy --port 8080")} Start the proxy on a custom port
@@ -346,9 +398,9 @@ ${chalk.bold("Usage:")}
346
398
  ${chalk.cyan("portless list")} Show active routes
347
399
 
348
400
  ${chalk.bold("Examples:")}
349
- sudo portless proxy # Start proxy in terminal 1
350
- portless myapp next dev # Terminal 2 -> http://myapp.localhost
351
- portless api.myapp pnpm start # Terminal 3 -> http://api.myapp.localhost
401
+ sudo portless proxy # Start proxy in terminal 1
402
+ portless myapp next dev # Terminal 2 -> http://myapp.localhost
403
+ portless api.myapp pnpm start # Terminal 3 -> http://api.myapp.localhost
352
404
 
353
405
  ${chalk.bold("In package.json:")}
354
406
  {
@@ -374,7 +426,7 @@ ${chalk.bold("Skip portless:")}
374
426
  process.exit(0);
375
427
  }
376
428
  if (args[0] === "--version" || args[0] === "-v") {
377
- console.log("0.2.0");
429
+ console.log("0.2.2");
378
430
  process.exit(0);
379
431
  }
380
432
  if (args[0] === "list") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portless",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Replace port numbers with stable, named .localhost URLs. For humans and agents.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,6 +17,7 @@
17
17
  "files": [
18
18
  "dist"
19
19
  ],
20
+ "packageManager": "pnpm@9.15.4",
20
21
  "engines": {
21
22
  "node": ">=20"
22
23
  },
@@ -24,6 +25,21 @@
24
25
  "darwin",
25
26
  "linux"
26
27
  ],
28
+ "scripts": {
29
+ "build": "tsup",
30
+ "dev": "tsup --watch",
31
+ "format": "prettier --write .",
32
+ "format:check": "prettier --check .",
33
+ "lint": "eslint src/",
34
+ "lint:fix": "eslint src/ --fix",
35
+ "prepublishOnly": "pnpm build",
36
+ "pretest": "pnpm build",
37
+ "test": "vitest run",
38
+ "test:coverage": "vitest run --coverage",
39
+ "test:watch": "vitest",
40
+ "typecheck": "tsc --noEmit",
41
+ "prepare": "husky"
42
+ },
27
43
  "keywords": [
28
44
  "local",
29
45
  "development",
@@ -65,18 +81,5 @@
65
81
  "prettier --write"
66
82
  ],
67
83
  "*.{json,md,yml,yaml}": "prettier --write"
68
- },
69
- "scripts": {
70
- "build": "tsup",
71
- "dev": "tsup --watch",
72
- "format": "prettier --write .",
73
- "format:check": "prettier --check .",
74
- "lint": "eslint src/",
75
- "lint:fix": "eslint src/ --fix",
76
- "pretest": "pnpm build",
77
- "test": "vitest run",
78
- "test:coverage": "vitest run --coverage",
79
- "test:watch": "vitest",
80
- "typecheck": "tsc --noEmit"
81
84
  }
82
- }
85
+ }