traforo 0.2.3 → 0.2.4
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/lockfile.d.ts +3 -0
- package/dist/lockfile.js +12 -5
- package/dist/run-tunnel.js +24 -7
- package/package.json +1 -1
package/dist/lockfile.d.ts
CHANGED
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
* Stores one JSON file per active tunnel port in ~/.traforo/{port}.json.
|
|
5
5
|
* Used to detect port conflicts, show tunnel info in error messages,
|
|
6
6
|
* and let agents reuse existing tunnels instead of killing them.
|
|
7
|
+
*
|
|
8
|
+
* Override the lockfile directory with TRAFORO_HOME env var (useful for tests).
|
|
7
9
|
*/
|
|
10
|
+
export declare function getLockfileDir(): string;
|
|
8
11
|
export type LockfileData = {
|
|
9
12
|
tunnelId: string;
|
|
10
13
|
tunnelUrl: string;
|
package/dist/lockfile.js
CHANGED
|
@@ -4,17 +4,22 @@
|
|
|
4
4
|
* Stores one JSON file per active tunnel port in ~/.traforo/{port}.json.
|
|
5
5
|
* Used to detect port conflicts, show tunnel info in error messages,
|
|
6
6
|
* and let agents reuse existing tunnels instead of killing them.
|
|
7
|
+
*
|
|
8
|
+
* Override the lockfile directory with TRAFORO_HOME env var (useful for tests).
|
|
7
9
|
*/
|
|
8
10
|
import fs from 'node:fs';
|
|
9
11
|
import path from 'node:path';
|
|
10
12
|
import os from 'node:os';
|
|
11
|
-
|
|
13
|
+
export function getLockfileDir() {
|
|
14
|
+
return process.env.TRAFORO_HOME ?? path.join(os.homedir(), '.traforo');
|
|
15
|
+
}
|
|
12
16
|
function lockfilePath(port) {
|
|
13
|
-
return path.join(
|
|
17
|
+
return path.join(getLockfileDir(), `${port}.json`);
|
|
14
18
|
}
|
|
15
19
|
export function writeLockfile(port, data) {
|
|
16
20
|
try {
|
|
17
|
-
|
|
21
|
+
const dir = getLockfileDir();
|
|
22
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
18
23
|
fs.writeFileSync(lockfilePath(port), JSON.stringify(data, null, 2) + '\n');
|
|
19
24
|
}
|
|
20
25
|
catch {
|
|
@@ -60,7 +65,9 @@ export function isLockfileStale(lock) {
|
|
|
60
65
|
process.kill(lock.tunnelPid, 0);
|
|
61
66
|
return false;
|
|
62
67
|
}
|
|
63
|
-
catch {
|
|
64
|
-
|
|
68
|
+
catch (error) {
|
|
69
|
+
// ESRCH = no such process (dead). EPERM = process exists but we
|
|
70
|
+
// can't signal it (e.g. PID 1) — treat as alive, not stale.
|
|
71
|
+
return error.code === 'ESRCH';
|
|
65
72
|
}
|
|
66
73
|
}
|
package/dist/run-tunnel.js
CHANGED
|
@@ -6,6 +6,23 @@ import { TunnelClient } from './client.js';
|
|
|
6
6
|
import { writeLockfile, readLockfile, removeLockfile, isLockfileStale, } from './lockfile.js';
|
|
7
7
|
const execPromise = promisify(exec);
|
|
8
8
|
export const CLI_NAME = 'traforo';
|
|
9
|
+
/**
|
|
10
|
+
* Shell-quote an argument array so the suggested command is copy-pasteable.
|
|
11
|
+
* Wraps args in single quotes if they contain shell-special characters.
|
|
12
|
+
*/
|
|
13
|
+
function shellQuote(args) {
|
|
14
|
+
return args
|
|
15
|
+
.map((arg) => {
|
|
16
|
+
if (arg === '')
|
|
17
|
+
return "''";
|
|
18
|
+
// Safe chars that don't need quoting
|
|
19
|
+
if (/^[a-zA-Z0-9._\-/:=@]+$/.test(arg))
|
|
20
|
+
return arg;
|
|
21
|
+
// Wrap in single quotes, escaping any inner single quotes
|
|
22
|
+
return "'" + arg.replace(/'/g, "'\\''") + "'";
|
|
23
|
+
})
|
|
24
|
+
.join(' ');
|
|
25
|
+
}
|
|
9
26
|
const DEFAULT_TUNNEL_ID_BYTES = 10;
|
|
10
27
|
export function createRandomTunnelId({ port }) {
|
|
11
28
|
return `${crypto.randomBytes(DEFAULT_TUNNEL_ID_BYTES).toString('hex')}-${port}`;
|
|
@@ -168,13 +185,13 @@ export async function runTunnel(options) {
|
|
|
168
185
|
// Kill existing process on port if requested
|
|
169
186
|
if (options.kill) {
|
|
170
187
|
await killProcessOnPort(port);
|
|
171
|
-
|
|
172
|
-
// Verify the port actually freed up
|
|
188
|
+
// Verify the port actually freed up before removing the lockfile
|
|
173
189
|
if (await isPortInUse(port, localHost)) {
|
|
174
190
|
console.error(`Error: Port ${port} is still in use after --kill.`);
|
|
175
191
|
console.error(`The process may require elevated permissions to terminate.`);
|
|
176
192
|
process.exit(1);
|
|
177
193
|
}
|
|
194
|
+
removeLockfile(port); // no ownership check — --kill is intentional
|
|
178
195
|
}
|
|
179
196
|
// Pre-flight: detect port conflict before spawning the child process
|
|
180
197
|
if (options.command && options.command.length > 0 && !options.kill) {
|
|
@@ -193,7 +210,7 @@ export async function runTunnel(options) {
|
|
|
193
210
|
console.error(`Error: Port ${port} is already in use\n`);
|
|
194
211
|
console.error(` Tunnel: ${lock.tunnelUrl}`);
|
|
195
212
|
console.error(` ID: ${lock.tunnelId}`);
|
|
196
|
-
console.error(` Command: ${lock.command
|
|
213
|
+
console.error(` Command: ${lock.command ? shellQuote(lock.command) : 'unknown'}`);
|
|
197
214
|
console.error(` Dir: ${lock.cwd}`);
|
|
198
215
|
console.error(` PID: ${lock.tunnelPid}`);
|
|
199
216
|
console.error(` Started: ${lock.startedAt}\n`);
|
|
@@ -206,13 +223,13 @@ export async function runTunnel(options) {
|
|
|
206
223
|
console.error(`Error: Port ${port} is already in use\n`);
|
|
207
224
|
console.error(` Tunnel: ${lock.tunnelUrl}`);
|
|
208
225
|
console.error(` ID: ${lock.tunnelId}`);
|
|
209
|
-
console.error(` Command: ${lock.command
|
|
226
|
+
console.error(` Command: ${lock.command ? shellQuote(lock.command) : 'unknown'}`);
|
|
210
227
|
console.error(` Dir: ${lock.cwd}`);
|
|
211
228
|
console.error(` PID: ${lock.tunnelPid}`);
|
|
212
229
|
console.error(` Started: ${lock.startedAt}\n`);
|
|
213
230
|
console.error(`Use --kill to terminate the existing process and start fresh,`);
|
|
214
231
|
console.error(`or just reuse the tunnel URL above instead:\n`);
|
|
215
|
-
console.error(` traforo -p ${port} --kill -- ${options.command
|
|
232
|
+
console.error(` traforo -p ${port} --kill -- ${shellQuote(options.command)}`);
|
|
216
233
|
process.exit(1);
|
|
217
234
|
}
|
|
218
235
|
}
|
|
@@ -222,7 +239,7 @@ export async function runTunnel(options) {
|
|
|
222
239
|
removeLockfile(port);
|
|
223
240
|
console.error(`Error: Port ${port} is already in use by another process.\n`);
|
|
224
241
|
console.error(`Use --kill to terminate it before starting:`);
|
|
225
|
-
console.error(` traforo -p ${port} --kill -- ${options.command
|
|
242
|
+
console.error(` traforo -p ${port} --kill -- ${shellQuote(options.command)}`);
|
|
226
243
|
process.exit(1);
|
|
227
244
|
}
|
|
228
245
|
}
|
|
@@ -232,7 +249,7 @@ export async function runTunnel(options) {
|
|
|
232
249
|
if (options.command && options.command.length > 0) {
|
|
233
250
|
const cmd = options.command[0];
|
|
234
251
|
const args = options.command.slice(1);
|
|
235
|
-
console.log(`Starting: ${options.command
|
|
252
|
+
console.log(`Starting: ${shellQuote(options.command)}`);
|
|
236
253
|
console.log(`PORT=${port}\n`);
|
|
237
254
|
const spawnedChild = spawn(cmd, args, {
|
|
238
255
|
stdio: 'inherit',
|