aptunnel 1.0.0 → 1.0.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.
package/README.md CHANGED
@@ -1,6 +1,24 @@
1
- # aptunnel
1
+ <p align="center">
2
+ <h1 align="center">aptunnel</h1>
3
+ <p align="center">Cross-platform Aptible tunnel manager — open, close and monitor multiple database tunnels with short aliases.</p>
4
+ </p>
5
+
6
+ <p align="center">
7
+ <a href="https://www.npmjs.com/package/aptunnel"><img src="https://img.shields.io/npm/v/aptunnel?color=cb3837&label=npm&logo=npm" alt="npm version"></a>
8
+ <a href="https://www.npmjs.com/package/aptunnel"><img src="https://img.shields.io/npm/dm/aptunnel?color=cb3837&logo=npm&label=downloads" alt="npm downloads"></a>
9
+ <a href="https://github.com/Uruba-Software/aptunnel/actions/workflows/test.yml"><img src="https://github.com/Uruba-Software/aptunnel/actions/workflows/test.yml/badge.svg" alt="CI"></a>
10
+ <a href="https://nodejs.org"><img src="https://img.shields.io/node/v/aptunnel?color=339933&logo=node.js&logoColor=white" alt="Node.js version"></a>
11
+ <a href="LICENSE"><img src="https://img.shields.io/npm/l/aptunnel?color=blue" alt="License"></a>
12
+ </p>
13
+
14
+ <p align="center">
15
+ <img src="https://img.shields.io/badge/Linux-supported-FCC624?logo=linux&logoColor=black" alt="Linux">
16
+ <img src="https://img.shields.io/badge/macOS-supported-000000?logo=apple&logoColor=white" alt="macOS">
17
+ <img src="https://img.shields.io/badge/Windows-supported-0078D4?logo=windows&logoColor=white" alt="Windows">
18
+ <img src="https://img.shields.io/badge/WSL-supported-4EAA25?logo=gnubash&logoColor=white" alt="WSL">
19
+ </p>
2
20
 
3
- Cross-platform Aptible tunnel manager. Open, close, and monitor multiple database tunnels with short aliases instead of long Aptible handles.
21
+ ---
4
22
 
5
23
  ```
6
24
  aptunnel dev-db # open tunnel to dev database
@@ -12,7 +30,7 @@ aptunnel status # see what's running
12
30
 
13
31
  ## Requirements
14
32
 
15
- - **Node.js** 18+
33
+ - **[Node.js](https://nodejs.org)** 18+
16
34
  - **[Aptible CLI](https://www.aptible.com/docs/cli)** installed and in your PATH
17
35
 
18
36
  ---
@@ -32,7 +50,7 @@ aptunnel init
32
50
  ```
33
51
 
34
52
  The wizard will:
35
- 1. Verify aptible CLI is installed
53
+ 1. Verify Aptible CLI is installed
36
54
  2. Log you in (supports 2FA)
37
55
  3. Discover all your environments and databases
38
56
  4. Auto-assign ports starting at `55550`
@@ -129,18 +147,18 @@ credentials:
129
147
  email: you@company.com
130
148
 
131
149
  defaults:
132
- environment: ekare-inc-development-gfpkcova
150
+ environment: my-env-development
133
151
  lifetime: 7d
134
152
 
135
153
  environments:
136
- ekare-inc-development-gfpkcova:
154
+ my-env-development:
137
155
  alias: dev
138
156
  databases:
139
- ekaredb-dev:
157
+ mydb-dev:
140
158
  alias: dev-db
141
159
  port: 55554
142
160
  type: postgresql
143
- ekaredb-dev-redis:
161
+ mydb-redis:
144
162
  alias: dev-redis
145
163
  port: 55555
146
164
  type: redis
@@ -154,56 +172,48 @@ tunnel_defaults:
154
172
 
155
173
  ## Shell Completions
156
174
 
157
- ### Auto-install (detects your shell)
158
-
159
175
  ```bash
160
- aptunnel completions install
176
+ aptunnel completions install # auto-detects your shell
161
177
  ```
162
178
 
163
- ### Manual
179
+ Or manually:
164
180
 
165
- **Bash** — add to `~/.bashrc`:
166
181
  ```bash
182
+ # Bash — add to ~/.bashrc
167
183
  source <(aptunnel completions bash)
168
- ```
169
184
 
170
- **Zsh** — add to `~/.zshrc`:
171
- ```bash
185
+ # Zsh — add to ~/.zshrc
172
186
  source <(aptunnel completions zsh)
173
- ```
174
187
 
175
- **Fish** — copy to completions directory:
176
- ```bash
188
+ # Fish
177
189
  aptunnel completions fish > ~/.config/fish/completions/aptunnel.fish
178
190
  ```
179
191
 
180
- Completions are dynamic — they read your config at runtime so your actual database aliases appear in tab-completion.
192
+ Completions are dynamic — your actual database aliases appear in tab-completion.
181
193
 
182
194
  ---
183
195
 
184
- ## Platform Notes
196
+ ## Platform Support
197
+
198
+ | Platform | Status | Notes |
199
+ |---|---|---|
200
+ | **Linux** | ✅ Full | `lsof`, `ps`, Unix signals |
201
+ | **macOS** | ✅ Full | Same as Linux |
202
+ | **Windows** | ✅ Full | `netstat`, `tasklist`, `taskkill` |
203
+ | **WSL** | ✅ Full | Treated as Linux, `wslview` for browser |
204
+
205
+ **Install Aptible CLI:**
185
206
 
186
- ### macOS
187
- Full support. Install Aptible CLI via Homebrew:
188
207
  ```bash
208
+ # macOS
189
209
  brew install aptible/aptible/aptible
190
- ```
191
210
 
192
- ### Linux
193
- Full support. Install Aptible CLI:
194
- ```bash
211
+ # Linux / WSL
195
212
  curl -s https://toolbelt.aptible.com/install.sh | bash
196
- ```
197
213
 
198
- ### Windows
199
- Supported with some limitations:
200
- - Process management uses `taskkill` and `wmic` instead of Unix signals
201
- - Port detection uses `netstat` instead of `lsof`
202
- - File permissions use `icacls` for the credentials file
203
- - Requires Node.js 18+ for Windows
204
-
205
- ### WSL (Windows Subsystem for Linux)
206
- Treated as Linux. Browser opening uses `wslview` if available, otherwise falls back to `cmd.exe /c start`.
214
+ # Windows
215
+ # Download from https://www.aptible.com/docs/cli
216
+ ```
207
217
 
208
218
  ---
209
219
 
@@ -221,15 +231,15 @@ Pressing **Ctrl+C** while aptunnel is running closes all open tunnels before exi
221
231
 
222
232
  ## Troubleshooting
223
233
 
224
- **"Aptible CLI not found"** — Install aptible and make sure it's in your PATH. Run `aptible version` to verify.
234
+ **"Aptible CLI not found"** — Install the [Aptible CLI](https://www.aptible.com/docs/cli) and make sure it's in your PATH. Run `aptible version` to verify.
225
235
 
226
236
  **"Token expired"** — Run `aptunnel login`. aptunnel will attempt auto-relogin on tunnel failures, but a fresh login is the cleanest fix.
227
237
 
228
- **"Port already in use"** — Another process is on that port. Use `--force` to kill it or `--port=<N>` to use a different port.
238
+ **"Port already in use"** — Another process is on that port. Use `--port=<N>` to use a different port or update it with `aptunnel config --set-port dev-db <N>`.
229
239
 
230
240
  **"Config file is corrupted"** — Delete `~/.aptunnel/config.yaml` and re-run `aptunnel init`.
231
241
 
232
- **Tunnel fails silently** — Check the log file: `cat /tmp/aptunnel-<alias>.log`. Set `DEBUG=1` for verbose output from aptunnel.
242
+ **Tunnel fails silently** — Check the log file: `cat /tmp/aptunnel-<alias>.log`.
233
243
 
234
244
  ---
235
245
 
@@ -237,11 +247,9 @@ Pressing **Ctrl+C** while aptunnel is running closes all open tunnels before exi
237
247
 
238
248
  1. Fork the repo
239
249
  2. Create a branch: `git checkout -b my-feature`
240
- 3. Make your changes
250
+ 3. Make your changes and add tests
241
251
  4. Open a pull request against `main`
242
252
 
243
- Please keep PRs focused. One feature or fix per PR.
244
-
245
253
  ---
246
254
 
247
255
  ## License
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aptunnel",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Cross-platform Aptible tunnel manager — multi-tunnel, auto-discovery, background process management",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,6 +17,14 @@
17
17
  "test:all": "node --test test/lib/platform.test.js test/lib/config-manager.test.js test/lib/process-manager.test.js test/lib/aptible.test.js test/lib/completions.test.js test/commands/config.test.js test/commands/help.test.js test/commands/status.test.js test/integration/cli.test.js",
18
18
  "test:watch": "node --test --watch test/lib/platform.test.js test/lib/config-manager.test.js test/lib/process-manager.test.js test/lib/aptible.test.js test/lib/completions.test.js test/commands/config.test.js test/commands/help.test.js test/commands/status.test.js"
19
19
  },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/Uruba-Software/aptunnel.git"
23
+ },
24
+ "homepage": "https://github.com/Uruba-Software/aptunnel#readme",
25
+ "bugs": {
26
+ "url": "https://github.com/Uruba-Software/aptunnel/issues"
27
+ },
20
28
  "files": [
21
29
  "bin/",
22
30
  "src/"
@@ -37,15 +37,16 @@ export async function runInit(args) {
37
37
  console.log('');
38
38
 
39
39
  // 4. Login (interactive — handles 2FA via stdio: inherit)
40
- // Close readline BEFORE spawning aptible so stdin is fully available to the child
40
+ // Do NOT show a spinner here: aptible may prompt for a 2FA OTP code and the
41
+ // spinner output would hide that prompt. Print a plain line instead.
41
42
  closeRL();
42
- const spinner = (await import('ora')).default({ text: 'Logging in to Aptible…' }).start();
43
+ console.log('Logging in to Aptible… (enter 2FA code if prompted)');
43
44
  const ok = await login({ email, password });
44
45
  if (!ok) {
45
- spinner.fail('Login failed. Please check your credentials.');
46
+ logger.error('Login failed. Please check your credentials.');
46
47
  process.exit(1);
47
48
  }
48
- spinner.succeed('Logged in successfully.');
49
+ logger.success('Logged in successfully.');
49
50
  console.log('');
50
51
 
51
52
  // 5. Discover environments
@@ -283,18 +284,44 @@ function ask(prompt) {
283
284
  }
284
285
 
285
286
  function askSecret(prompt) {
287
+ // Close the shared readline so we can take over stdin directly.
288
+ // Monkey-patching rl._writeToOutput (private API) is unreliable across
289
+ // Node.js versions — reading raw characters is the portable alternative.
290
+ if (_rl) { _rl.close(); _rl = null; }
291
+
286
292
  return new Promise((resolve) => {
287
- const rl = getRL();
288
293
  process.stdout.write(prompt);
289
- rl._writeToOutput = function(str) {
290
- if (!this.stdoutMuted) this.output.write(str);
291
- };
292
- rl.stdoutMuted = true;
293
- rl.question('', (answer) => {
294
- rl.stdoutMuted = false;
295
- rl._writeToOutput = function(str) { this.output.write(str); };
296
- process.stdout.write('\n');
297
- resolve(answer);
298
- });
294
+
295
+ const chars = [];
296
+ process.stdin.resume();
297
+ process.stdin.setEncoding('utf8');
298
+
299
+ const isTTY = !!process.stdin.isTTY;
300
+ if (isTTY) process.stdin.setRawMode(true);
301
+
302
+ function onData(data) {
303
+ for (const char of data) {
304
+ if (char === '\r' || char === '\n') {
305
+ process.stdin.removeListener('data', onData);
306
+ if (isTTY) process.stdin.setRawMode(false);
307
+ // Pause so Node.js stops consuming keystrokes that belong to child
308
+ // processes (e.g. aptible login waiting for a 2FA OTP).
309
+ process.stdin.pause();
310
+ process.stdout.write('\n');
311
+ resolve(chars.join(''));
312
+ return;
313
+ } else if (char === '\u0003') {
314
+ // Ctrl+C
315
+ process.stdout.write('\n');
316
+ process.exit(0);
317
+ } else if (char === '\u007f' || char === '\b') {
318
+ if (chars.length > 0) chars.pop();
319
+ } else if (char >= ' ') {
320
+ chars.push(char);
321
+ }
322
+ }
323
+ }
324
+
325
+ process.stdin.on('data', onData);
299
326
  });
300
327
  }
@@ -103,16 +103,38 @@ function ask(prompt) {
103
103
  }
104
104
 
105
105
  function askSecret(prompt) {
106
+ if (_rl) { _rl.close(); _rl = null; }
107
+
106
108
  return new Promise((resolve) => {
107
- const rl = getRL();
108
109
  process.stdout.write(prompt);
109
- rl._writeToOutput = function(str) { if (!this.stdoutMuted) this.output.write(str); };
110
- rl.stdoutMuted = true;
111
- rl.question('', (answer) => {
112
- rl.stdoutMuted = false;
113
- rl._writeToOutput = function(str) { this.output.write(str); };
114
- process.stdout.write('\n');
115
- resolve(answer);
116
- });
110
+
111
+ const chars = [];
112
+ process.stdin.resume();
113
+ process.stdin.setEncoding('utf8');
114
+
115
+ const isTTY = !!process.stdin.isTTY;
116
+ if (isTTY) process.stdin.setRawMode(true);
117
+
118
+ function onData(data) {
119
+ for (const char of data) {
120
+ if (char === '\r' || char === '\n') {
121
+ process.stdin.removeListener('data', onData);
122
+ if (isTTY) process.stdin.setRawMode(false);
123
+ process.stdin.pause();
124
+ process.stdout.write('\n');
125
+ resolve(chars.join(''));
126
+ return;
127
+ } else if (char === '\u0003') {
128
+ process.stdout.write('\n');
129
+ process.exit(0);
130
+ } else if (char === '\u007f' || char === '\b') {
131
+ if (chars.length > 0) chars.pop();
132
+ } else if (char >= ' ') {
133
+ chars.push(char);
134
+ }
135
+ }
136
+ }
137
+
138
+ process.stdin.on('data', onData);
117
139
  });
118
140
  }
@@ -58,12 +58,10 @@ export function login({ email, password, lifetime = '7d', otp } = {}) {
58
58
  if (password) args.push(`--password=${password}`);
59
59
  if (otp) args.push(`--otp=${otp}`);
60
60
 
61
- // Resume stdin so aptible can receive input (e.g. 2FA OTP prompt).
62
- // readline.close() pauses process.stdin; we must unpause it before handing
63
- // it to a child process, otherwise the child's read() never returns.
64
- process.stdin.resume();
65
-
66
- // stdio: 'inherit' is critical — aptible prompts for 2FA interactively
61
+ // stdio: 'inherit' aptible reads from fd 0 directly at OS level,
62
+ // independent of Node.js stream state. Do NOT call process.stdin.resume()
63
+ // here: flowing mode with no listener would consume the user's 2FA keystrokes
64
+ // before aptible gets them.
67
65
  const child = spawn('aptible', args, { stdio: 'inherit', ...SHELL_OPT });
68
66
 
69
67
  child.on('close', (code) => resolve(code === 0));