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 +51 -43
- package/package.json +9 -1
- package/src/commands/init.js +42 -15
- package/src/commands/login.js +31 -9
- package/src/lib/aptible.js +4 -6
package/README.md
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
150
|
+
environment: my-env-development
|
|
133
151
|
lifetime: 7d
|
|
134
152
|
|
|
135
153
|
environments:
|
|
136
|
-
|
|
154
|
+
my-env-development:
|
|
137
155
|
alias: dev
|
|
138
156
|
databases:
|
|
139
|
-
|
|
157
|
+
mydb-dev:
|
|
140
158
|
alias: dev-db
|
|
141
159
|
port: 55554
|
|
142
160
|
type: postgresql
|
|
143
|
-
|
|
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
|
-
|
|
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
|
-
|
|
171
|
-
```bash
|
|
185
|
+
# Zsh — add to ~/.zshrc
|
|
172
186
|
source <(aptunnel completions zsh)
|
|
173
|
-
```
|
|
174
187
|
|
|
175
|
-
|
|
176
|
-
```bash
|
|
188
|
+
# Fish
|
|
177
189
|
aptunnel completions fish > ~/.config/fish/completions/aptunnel.fish
|
|
178
190
|
```
|
|
179
191
|
|
|
180
|
-
Completions are dynamic —
|
|
192
|
+
Completions are dynamic — your actual database aliases appear in tab-completion.
|
|
181
193
|
|
|
182
194
|
---
|
|
183
195
|
|
|
184
|
-
## Platform
|
|
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
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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 `--
|
|
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`.
|
|
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.
|
|
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/"
|
package/src/commands/init.js
CHANGED
|
@@ -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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
46
|
+
logger.error('Login failed. Please check your credentials.');
|
|
46
47
|
process.exit(1);
|
|
47
48
|
}
|
|
48
|
-
|
|
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
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
}
|
package/src/commands/login.js
CHANGED
|
@@ -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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
}
|
package/src/lib/aptible.js
CHANGED
|
@@ -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
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
|
|
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));
|