@abyrd9/harbor-cli 1.0.1 ā 1.1.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.
- package/README.md +248 -37
- package/dist/index.js +219 -10
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,47 +1,58 @@
|
|
|
1
1
|
# Harbor CLI
|
|
2
2
|
|
|
3
|
-
A CLI tool for
|
|
3
|
+
A CLI tool for managing local development services with ease. Harbor helps you orchestrate multiple development services in tmux sessions, perfect for microservices and polyglot projects.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
## ⨠Features
|
|
6
|
+
|
|
7
|
+
- š ļø **Automatic Service Discovery**: Detects project types and suggests appropriate commands
|
|
8
|
+
- š **Flexible Configuration**: Store config in `harbor.json` or `package.json`
|
|
9
|
+
- š **One-Command Launch**: Start all services with a single command
|
|
10
|
+
- š **Dependency Management**: Automatically checks for required system dependencies
|
|
11
|
+
- šÆ **Multi-Language Support**: Works with Node.js, Go, Rust, Python, PHP, Java, and more
|
|
12
|
+
- š„ļø **Tmux Integration**: Professional terminal multiplexing for service management
|
|
7
13
|
|
|
8
14
|
## Installation
|
|
9
15
|
|
|
10
16
|
```bash
|
|
11
|
-
npm
|
|
17
|
+
npm install -g @abyrd9/harbor-cli
|
|
12
18
|
```
|
|
13
19
|
|
|
14
20
|
## Prerequisites
|
|
15
21
|
|
|
16
|
-
|
|
22
|
+
Harbor automatically checks for these required dependencies:
|
|
23
|
+
|
|
24
|
+
- **[tmux](https://github.com/tmux/tmux/wiki/Installing)** - Terminal multiplexer for managing services
|
|
25
|
+
- **[jq](https://stedolan.github.io/jq/download/)** - JSON processor for service management
|
|
17
26
|
|
|
18
|
-
|
|
19
|
-
- [jq](https://stedolan.github.io/jq/download/) (for JSON processing within tmux)
|
|
27
|
+
If missing, Harbor will provide installation instructions.
|
|
20
28
|
|
|
21
29
|
## Quick Start
|
|
22
30
|
|
|
23
|
-
1. Initialize your
|
|
24
|
-
```bash
|
|
25
|
-
harbor dock
|
|
26
|
-
```
|
|
31
|
+
1. **Initialize your project**:
|
|
32
|
+
```bash
|
|
33
|
+
harbor dock
|
|
34
|
+
```
|
|
35
|
+
This scans your directory structure and creates a harbor configuration.
|
|
27
36
|
|
|
28
|
-
2. Add
|
|
29
|
-
```bash
|
|
30
|
-
harbor moor
|
|
31
|
-
```
|
|
37
|
+
2. **Add more services** (optional):
|
|
38
|
+
```bash
|
|
39
|
+
harbor moor
|
|
40
|
+
```
|
|
41
|
+
Scans for new services and adds them to your configuration.
|
|
32
42
|
|
|
33
|
-
3. Launch
|
|
34
|
-
```bash
|
|
35
|
-
harbor launch
|
|
36
|
-
```
|
|
43
|
+
3. **Launch all services**:
|
|
44
|
+
```bash
|
|
45
|
+
harbor launch
|
|
46
|
+
```
|
|
47
|
+
Starts all configured services in organized tmux windows.
|
|
37
48
|
|
|
38
49
|
## Configuration
|
|
39
50
|
|
|
40
|
-
Harbor
|
|
51
|
+
Harbor supports two configuration formats:
|
|
41
52
|
|
|
42
|
-
### harbor.json
|
|
53
|
+
### Option 1: harbor.json
|
|
43
54
|
|
|
44
|
-
|
|
55
|
+
Create a dedicated configuration file:
|
|
45
56
|
|
|
46
57
|
```json
|
|
47
58
|
{
|
|
@@ -57,34 +68,234 @@ Contains your service configurations that are used to launch the services:
|
|
|
57
68
|
"command": "go run ."
|
|
58
69
|
},
|
|
59
70
|
{
|
|
60
|
-
"name": "
|
|
61
|
-
"path": "./
|
|
62
|
-
"command": "
|
|
71
|
+
"name": "database",
|
|
72
|
+
"path": "./db",
|
|
73
|
+
"command": "docker-compose up"
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
"before": [
|
|
77
|
+
{
|
|
78
|
+
"path": "./",
|
|
79
|
+
"command": "echo 'Starting development environment...' && docker-compose up -d"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"path": "./scripts",
|
|
83
|
+
"command": "./setup-dev.sh"
|
|
84
|
+
}
|
|
85
|
+
],
|
|
86
|
+
"after": [
|
|
87
|
+
{
|
|
88
|
+
"path": "./",
|
|
89
|
+
"command": "echo 'Development environment ready!'"
|
|
63
90
|
}
|
|
64
91
|
]
|
|
65
92
|
}
|
|
66
93
|
```
|
|
67
94
|
|
|
95
|
+
### Option 2: package.json
|
|
96
|
+
|
|
97
|
+
Store configuration directly in your `package.json`:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"name": "my-project",
|
|
102
|
+
"version": "1.0.0",
|
|
103
|
+
"harbor": {
|
|
104
|
+
"services": [
|
|
105
|
+
{
|
|
106
|
+
"name": "frontend",
|
|
107
|
+
"path": "./frontend",
|
|
108
|
+
"command": "npm run dev"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "api",
|
|
112
|
+
"path": "./api",
|
|
113
|
+
"command": "go run main.go"
|
|
114
|
+
}
|
|
115
|
+
],
|
|
116
|
+
"before": [
|
|
117
|
+
{
|
|
118
|
+
"path": "./",
|
|
119
|
+
"command": "docker-compose up -d database"
|
|
120
|
+
}
|
|
121
|
+
],
|
|
122
|
+
"after": [
|
|
123
|
+
{
|
|
124
|
+
"path": "./",
|
|
125
|
+
"command": "echo 'All services are running!'"
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
68
132
|
## Commands
|
|
69
133
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
134
|
+
| Command | Description |
|
|
135
|
+
|---------|-------------|
|
|
136
|
+
| `harbor dock` | Initialize harbor configuration by scanning project directories |
|
|
137
|
+
| `harbor moor` | Scan for and add new services to existing configuration |
|
|
138
|
+
| `harbor launch` | Start all services in tmux sessions |
|
|
139
|
+
| `harbor --help` | Show help and available commands |
|
|
140
|
+
| `harbor --version` | Show version information |
|
|
141
|
+
|
|
142
|
+
### Command Options
|
|
143
|
+
|
|
144
|
+
- `-p, --path <path>`: Specify project root path (defaults to `./`)
|
|
145
|
+
|
|
146
|
+
## Supported Project Types
|
|
147
|
+
|
|
148
|
+
Harbor automatically detects and configures services for:
|
|
149
|
+
|
|
150
|
+
- **Node.js**: `package.json` ā `npm run dev`
|
|
151
|
+
- **Go**: `go.mod` ā `go run .`
|
|
152
|
+
- **Rust**: `Cargo.toml` ā `cargo run`
|
|
153
|
+
- **Python**: `requirements.txt` ā `python app.py`
|
|
154
|
+
- **PHP**: `composer.json` ā `php artisan serve`
|
|
155
|
+
- **Java**: `pom.xml`/`build.gradle` ā `mvn spring-boot:run` or `./gradlew bootRun`
|
|
156
|
+
|
|
157
|
+
## How It Works
|
|
158
|
+
|
|
159
|
+
1. **Service Discovery**: Harbor scans your directory for folders containing project files
|
|
160
|
+
2. **Command Detection**: Based on project type, suggests appropriate development commands
|
|
161
|
+
3. **Configuration Generation**: Creates/updates harbor config with discovered services
|
|
162
|
+
4. **Tmux Session**: Launches each service in its own tmux window with proper working directories
|
|
163
|
+
5. **Process Management**: All services run independently with proper I/O handling
|
|
73
164
|
|
|
74
|
-
## Terminal Multiplexer
|
|
165
|
+
## Terminal Multiplexer (Tmux)
|
|
75
166
|
|
|
76
|
-
Harbor uses tmux for
|
|
167
|
+
Harbor uses tmux for professional service management. Essential shortcuts:
|
|
77
168
|
|
|
78
|
-
|
|
79
|
-
- `Ctrl+a
|
|
80
|
-
- `Ctrl+a
|
|
81
|
-
- `Ctrl+a
|
|
82
|
-
- `Ctrl+
|
|
169
|
+
### Session Management
|
|
170
|
+
- `Ctrl+a d` - Detach from session (services continue running)
|
|
171
|
+
- `Ctrl+a :attach` - Reattach to session
|
|
172
|
+
- `Ctrl+a &` - Kill current window
|
|
173
|
+
- `Ctrl+a ?` - Show all keybindings
|
|
174
|
+
|
|
175
|
+
### Window Navigation
|
|
176
|
+
- `Ctrl+a c` - Create new window
|
|
177
|
+
- `Ctrl+a n` - Next window
|
|
178
|
+
- `Ctrl+a p` - Previous window
|
|
179
|
+
- `Ctrl+a 0-9` - Jump to window number
|
|
180
|
+
- `Ctrl+a w` - List all windows
|
|
181
|
+
|
|
182
|
+
### Pane Management
|
|
183
|
+
- `Ctrl+a %` - Split window vertically
|
|
184
|
+
- `Ctrl+a "` - Split window horizontally
|
|
185
|
+
- `Ctrl+a ā/ā/ā/ā` - Navigate between panes
|
|
186
|
+
- `Ctrl+a x` - Close current pane
|
|
187
|
+
|
|
188
|
+
## Advanced Usage
|
|
189
|
+
|
|
190
|
+
### Custom Commands
|
|
191
|
+
Override auto-detected commands by editing your configuration:
|
|
192
|
+
|
|
193
|
+
```json
|
|
194
|
+
{
|
|
195
|
+
"services": [
|
|
196
|
+
{
|
|
197
|
+
"name": "custom-service",
|
|
198
|
+
"path": "./my-service",
|
|
199
|
+
"command": "custom-command --with-flags"
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Before/After Scripts
|
|
206
|
+
Run custom scripts before and after your services start:
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
{
|
|
210
|
+
"before": [
|
|
211
|
+
{
|
|
212
|
+
"path": "./infrastructure",
|
|
213
|
+
"command": "docker-compose up -d"
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"path": "./",
|
|
217
|
+
"command": "npm run setup:dev"
|
|
218
|
+
}
|
|
219
|
+
],
|
|
220
|
+
"after": [
|
|
221
|
+
{
|
|
222
|
+
"path": "./",
|
|
223
|
+
"command": "npm run seed:database"
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"path": "./scripts",
|
|
227
|
+
"command": "./notify-dev-ready.sh"
|
|
228
|
+
}
|
|
229
|
+
]
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**Execution Flow:**
|
|
234
|
+
1. **Before scripts** run sequentially before services start
|
|
235
|
+
2. Services launch in tmux windows
|
|
236
|
+
3. **After scripts** run sequentially after services are ready
|
|
237
|
+
|
|
238
|
+
**Use Cases:**
|
|
239
|
+
- Set up databases or infrastructure
|
|
240
|
+
- Run database migrations or seeds
|
|
241
|
+
- Send notifications when environment is ready
|
|
242
|
+
- Clean up temporary files
|
|
243
|
+
- Run integration tests
|
|
244
|
+
|
|
245
|
+
### Selective Launch
|
|
246
|
+
Currently launches all services. Future versions may support selective service launching.
|
|
247
|
+
|
|
248
|
+
### Environment Variables
|
|
249
|
+
Services inherit your shell environment. Use `.env` files or export variables before running `harbor launch`.
|
|
250
|
+
|
|
251
|
+
## Troubleshooting
|
|
252
|
+
|
|
253
|
+
### Common Issues
|
|
254
|
+
|
|
255
|
+
1. **"Missing required dependencies"**
|
|
256
|
+
- Install tmux and jq as indicated in the error message
|
|
257
|
+
|
|
258
|
+
2. **"No harbor configuration found"**
|
|
259
|
+
- Run `harbor dock` to initialize configuration
|
|
260
|
+
- Ensure you're in the correct project directory
|
|
261
|
+
|
|
262
|
+
3. **Services not starting**
|
|
263
|
+
- Check that commands are correct in your harbor configuration
|
|
264
|
+
- Verify project dependencies are installed in each service directory
|
|
265
|
+
- Check tmux windows for error messages
|
|
266
|
+
|
|
267
|
+
4. **Tmux not responding**
|
|
268
|
+
- Try `tmux kill-session -t harbor` to clean up
|
|
269
|
+
- Restart with `harbor launch`
|
|
270
|
+
|
|
271
|
+
## Examples
|
|
272
|
+
|
|
273
|
+
### Monorepo Setup
|
|
274
|
+
```
|
|
275
|
+
my-project/
|
|
276
|
+
āāā frontend/ (package.json)
|
|
277
|
+
āāā backend/ (go.mod)
|
|
278
|
+
āāā database/ (docker-compose.yml)
|
|
279
|
+
āāā harbor.json
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Polyglot Microservices
|
|
283
|
+
```
|
|
284
|
+
services/
|
|
285
|
+
āāā auth-service/ (Node.js)
|
|
286
|
+
āāā user-service/ (Go)
|
|
287
|
+
āāā payment-service/ (Python)
|
|
288
|
+
āāā notification-service/ (Rust)
|
|
289
|
+
```
|
|
83
290
|
|
|
84
291
|
## Contributing
|
|
85
292
|
|
|
86
|
-
Contributions are welcome! Please feel free to
|
|
293
|
+
Contributions are welcome! Please feel free to:
|
|
294
|
+
|
|
295
|
+
- Report bugs and request features via GitHub Issues
|
|
296
|
+
- Submit pull requests for improvements
|
|
297
|
+
- Help improve documentation
|
|
87
298
|
|
|
88
299
|
## License
|
|
89
300
|
|
|
90
|
-
MIT
|
|
301
|
+
MIT - See LICENSE file for details
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { spawn } from 'node:child_process';
|
|
|
6
6
|
import { chmodSync } from 'node:fs';
|
|
7
7
|
import { readFileSync } from 'node:fs';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import os from 'node:os';
|
|
9
10
|
// Read version from package.json
|
|
10
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
12
|
const __dirname = path.dirname(__filename);
|
|
@@ -24,8 +25,107 @@ const requiredDependencies = [
|
|
|
24
25
|
requiredFor: 'JSON processing in service management',
|
|
25
26
|
},
|
|
26
27
|
];
|
|
28
|
+
function detectOS() {
|
|
29
|
+
const platform = os.platform();
|
|
30
|
+
const arch = os.arch();
|
|
31
|
+
const isWindows = platform === 'win32';
|
|
32
|
+
const isMac = platform === 'darwin';
|
|
33
|
+
const isLinux = platform === 'linux';
|
|
34
|
+
// Check if running in WSL
|
|
35
|
+
let isWSL = false;
|
|
36
|
+
if (isLinux) {
|
|
37
|
+
try {
|
|
38
|
+
const release = fs.readFileSync('/proc/version', 'utf-8');
|
|
39
|
+
isWSL = release.toLowerCase().includes('microsoft') || release.toLowerCase().includes('wsl');
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// If we can't read /proc/version, assume not WSL
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
platform,
|
|
47
|
+
arch,
|
|
48
|
+
isWindows,
|
|
49
|
+
isMac,
|
|
50
|
+
isLinux,
|
|
51
|
+
isWSL,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function getInstallInstructions(dependency, osInfo) {
|
|
55
|
+
const instructions = [];
|
|
56
|
+
if (dependency === 'tmux') {
|
|
57
|
+
if (osInfo.isMac) {
|
|
58
|
+
instructions.push('š macOS:');
|
|
59
|
+
instructions.push(' ⢠Using Homebrew: brew install tmux');
|
|
60
|
+
instructions.push(' ⢠Using MacPorts: sudo port install tmux');
|
|
61
|
+
instructions.push(' ⢠Manual: https://github.com/tmux/tmux/wiki/Installing');
|
|
62
|
+
}
|
|
63
|
+
else if (osInfo.isLinux) {
|
|
64
|
+
if (osInfo.isWSL) {
|
|
65
|
+
instructions.push('š§ WSL/Linux:');
|
|
66
|
+
instructions.push(' ⢠Ubuntu/Debian: sudo apt update && sudo apt install tmux');
|
|
67
|
+
instructions.push(' ⢠CentOS/RHEL: sudo yum install tmux');
|
|
68
|
+
instructions.push(' ⢠Fedora: sudo dnf install tmux');
|
|
69
|
+
instructions.push(' ⢠Arch: sudo pacman -S tmux');
|
|
70
|
+
instructions.push(' ⢠openSUSE: sudo zypper install tmux');
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
instructions.push('š§ Linux:');
|
|
74
|
+
instructions.push(' ⢠Ubuntu/Debian: sudo apt update && sudo apt install tmux');
|
|
75
|
+
instructions.push(' ⢠CentOS/RHEL: sudo yum install tmux');
|
|
76
|
+
instructions.push(' ⢠Fedora: sudo dnf install tmux');
|
|
77
|
+
instructions.push(' ⢠Arch: sudo pacman -S tmux');
|
|
78
|
+
instructions.push(' ⢠openSUSE: sudo zypper install tmux');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (osInfo.isWindows) {
|
|
82
|
+
instructions.push('šŖ Windows:');
|
|
83
|
+
instructions.push(' ⢠Using Chocolatey: choco install tmux');
|
|
84
|
+
instructions.push(' ⢠Using Scoop: scoop install tmux');
|
|
85
|
+
instructions.push(' ⢠Using WSL: Install in WSL and use from there');
|
|
86
|
+
instructions.push(' ⢠Manual: https://github.com/tmux/tmux/wiki/Installing');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else if (dependency === 'jq') {
|
|
90
|
+
if (osInfo.isMac) {
|
|
91
|
+
instructions.push('š macOS:');
|
|
92
|
+
instructions.push(' ⢠Using Homebrew: brew install jq');
|
|
93
|
+
instructions.push(' ⢠Using MacPorts: sudo port install jq');
|
|
94
|
+
instructions.push(' ⢠Using Fink: fink install jq');
|
|
95
|
+
instructions.push(' ⢠Manual: https://jqlang.org/download/');
|
|
96
|
+
}
|
|
97
|
+
else if (osInfo.isLinux) {
|
|
98
|
+
if (osInfo.isWSL) {
|
|
99
|
+
instructions.push('š§ WSL/Linux:');
|
|
100
|
+
instructions.push(' ⢠Ubuntu/Debian: sudo apt update && sudo apt install jq');
|
|
101
|
+
instructions.push(' ⢠CentOS/RHEL: sudo yum install jq');
|
|
102
|
+
instructions.push(' ⢠Fedora: sudo dnf install jq');
|
|
103
|
+
instructions.push(' ⢠Arch: sudo pacman -S jq');
|
|
104
|
+
instructions.push(' ⢠openSUSE: sudo zypper install jq');
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
instructions.push('š§ Linux:');
|
|
108
|
+
instructions.push(' ⢠Ubuntu/Debian: sudo apt update && sudo apt install jq');
|
|
109
|
+
instructions.push(' ⢠CentOS/RHEL: sudo yum install jq');
|
|
110
|
+
instructions.push(' ⢠Fedora: sudo dnf install jq');
|
|
111
|
+
instructions.push(' ⢠Arch: sudo pacman -S jq');
|
|
112
|
+
instructions.push(' ⢠openSUSE: sudo zypper install jq');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else if (osInfo.isWindows) {
|
|
116
|
+
instructions.push('šŖ Windows:');
|
|
117
|
+
instructions.push(' ⢠Using winget: winget install jqlang.jq');
|
|
118
|
+
instructions.push(' ⢠Using Chocolatey: choco install jq');
|
|
119
|
+
instructions.push(' ⢠Using Scoop: scoop install jq');
|
|
120
|
+
instructions.push(' ⢠Using WSL: Install in WSL and use from there');
|
|
121
|
+
instructions.push(' ⢠Manual: https://jqlang.org/download/');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return instructions;
|
|
125
|
+
}
|
|
27
126
|
async function checkDependencies() {
|
|
28
127
|
const missingDeps = [];
|
|
128
|
+
const osInfo = detectOS();
|
|
29
129
|
for (const dep of requiredDependencies) {
|
|
30
130
|
try {
|
|
31
131
|
await new Promise((resolve, reject) => {
|
|
@@ -44,10 +144,19 @@ async function checkDependencies() {
|
|
|
44
144
|
}
|
|
45
145
|
if (missingDeps.length > 0) {
|
|
46
146
|
console.log('ā Missing required dependencies:');
|
|
147
|
+
console.log(`\nš„ļø Detected OS: ${osInfo.platform} ${osInfo.arch}${osInfo.isWSL ? ' (WSL)' : ''}`);
|
|
47
148
|
for (const dep of missingDeps) {
|
|
48
|
-
console.log(`\n${dep.name} (required for ${dep.requiredFor})`);
|
|
49
|
-
|
|
149
|
+
console.log(`\nš¦ ${dep.name} (required for ${dep.requiredFor})`);
|
|
150
|
+
const instructions = getInstallInstructions(dep.name, osInfo);
|
|
151
|
+
if (instructions.length > 0) {
|
|
152
|
+
console.log(' Installation options:');
|
|
153
|
+
instructions.forEach(instruction => console.log(instruction));
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
console.log(` General instructions: ${dep.installMsg}`);
|
|
157
|
+
}
|
|
50
158
|
}
|
|
159
|
+
console.log('\nš” After installing the dependencies, run Harbor again.');
|
|
51
160
|
throw new Error('Please install missing dependencies before continuing');
|
|
52
161
|
}
|
|
53
162
|
}
|
|
@@ -88,6 +197,7 @@ This is typically the first command you'll run in a new project.`)
|
|
|
88
197
|
.option('-p, --path <path>', 'The path to the root of your project', './')
|
|
89
198
|
.action(async (options) => {
|
|
90
199
|
try {
|
|
200
|
+
await checkDependencies();
|
|
91
201
|
const configExists = checkHasHarborConfig();
|
|
92
202
|
if (configExists) {
|
|
93
203
|
console.log('ā Error: Harbor project already initialized');
|
|
@@ -107,20 +217,34 @@ program.command('moor')
|
|
|
107
217
|
.description('Add new services to your harbor configuration')
|
|
108
218
|
.option('-p, --path <path>', 'The path to the root of your project', './')
|
|
109
219
|
.action(async (options) => {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
220
|
+
try {
|
|
221
|
+
await checkDependencies();
|
|
222
|
+
if (!checkHasHarborConfig()) {
|
|
223
|
+
console.log('ā No harbor configuration found');
|
|
224
|
+
console.log('\nTo initialize a new Harbor project, please use:');
|
|
225
|
+
console.log(' harbor dock');
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
await generateDevFile(options.path);
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
console.log('ā Error:', err instanceof Error ? err.message : 'Unknown error');
|
|
114
232
|
process.exit(1);
|
|
115
233
|
}
|
|
116
|
-
await generateDevFile(options.path);
|
|
117
234
|
});
|
|
118
235
|
program.command('launch')
|
|
119
236
|
.description(`Launch your services in the harbor terminal multiplexer (Using tmux)
|
|
120
237
|
|
|
121
238
|
Note: This command will stop any active Caddy processes, including those from other Harbor projects.`)
|
|
122
239
|
.action(async () => {
|
|
123
|
-
|
|
240
|
+
try {
|
|
241
|
+
await checkDependencies();
|
|
242
|
+
await runServices();
|
|
243
|
+
}
|
|
244
|
+
catch (err) {
|
|
245
|
+
console.log('ā Error:', err instanceof Error ? err.message : 'Unknown error');
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
124
248
|
});
|
|
125
249
|
program.parse();
|
|
126
250
|
function fileExists(path) {
|
|
@@ -148,6 +272,36 @@ function validateConfig(config) {
|
|
|
148
272
|
return 'Service path is required';
|
|
149
273
|
}
|
|
150
274
|
}
|
|
275
|
+
// Validate before scripts
|
|
276
|
+
if (config.before && !Array.isArray(config.before)) {
|
|
277
|
+
return 'Before scripts must be an array';
|
|
278
|
+
}
|
|
279
|
+
if (config.before) {
|
|
280
|
+
for (let i = 0; i < config.before.length; i++) {
|
|
281
|
+
const script = config.before[i];
|
|
282
|
+
if (!script.path) {
|
|
283
|
+
return `Before script ${i} must have a path`;
|
|
284
|
+
}
|
|
285
|
+
if (!script.command) {
|
|
286
|
+
return `Before script ${i} must have a command`;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// Validate after scripts
|
|
291
|
+
if (config.after && !Array.isArray(config.after)) {
|
|
292
|
+
return 'After scripts must be an array';
|
|
293
|
+
}
|
|
294
|
+
if (config.after) {
|
|
295
|
+
for (let i = 0; i < config.after.length; i++) {
|
|
296
|
+
const script = config.after[i];
|
|
297
|
+
if (!script.path) {
|
|
298
|
+
return `After script ${i} must have a path`;
|
|
299
|
+
}
|
|
300
|
+
if (!script.command) {
|
|
301
|
+
return `After script ${i} must have a command`;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
151
305
|
return null;
|
|
152
306
|
}
|
|
153
307
|
async function generateDevFile(dirPath) {
|
|
@@ -178,6 +332,8 @@ async function generateDevFile(dirPath) {
|
|
|
178
332
|
writeToPackageJson = true;
|
|
179
333
|
config = {
|
|
180
334
|
services: [],
|
|
335
|
+
before: [],
|
|
336
|
+
after: [],
|
|
181
337
|
};
|
|
182
338
|
console.log('Creating new harbor config in package.json...');
|
|
183
339
|
}
|
|
@@ -185,6 +341,8 @@ async function generateDevFile(dirPath) {
|
|
|
185
341
|
// No package.json, create harbor.json
|
|
186
342
|
config = {
|
|
187
343
|
services: [],
|
|
344
|
+
before: [],
|
|
345
|
+
after: [],
|
|
188
346
|
};
|
|
189
347
|
console.log('Creating new harbor.json...');
|
|
190
348
|
}
|
|
@@ -296,6 +454,41 @@ async function readHarborConfig() {
|
|
|
296
454
|
}
|
|
297
455
|
throw new Error('No harbor configuration found in harbor.json or package.json');
|
|
298
456
|
}
|
|
457
|
+
async function execute(scripts, scriptType) {
|
|
458
|
+
if (!scripts || scripts.length === 0) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
console.log(`\nš Executing ${scriptType} scripts...`);
|
|
462
|
+
for (let i = 0; i < scripts.length; i++) {
|
|
463
|
+
const script = scripts[i];
|
|
464
|
+
console.log(`\nš Running ${scriptType} script ${i + 1}/${scripts.length}: ${script.command}`);
|
|
465
|
+
console.log(` š In directory: ${script.path}`);
|
|
466
|
+
try {
|
|
467
|
+
await new Promise((resolve, reject) => {
|
|
468
|
+
const process = spawn('sh', ['-c', `cd "${script.path}" && ${script.command}`], {
|
|
469
|
+
stdio: 'inherit',
|
|
470
|
+
});
|
|
471
|
+
process.on('close', (code) => {
|
|
472
|
+
if (code === 0) {
|
|
473
|
+
console.log(`ā
${scriptType} script ${i + 1} completed successfully`);
|
|
474
|
+
resolve(null);
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
reject(new Error(`${scriptType} script ${i + 1} exited with code ${code}`));
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
process.on('error', (err) => {
|
|
481
|
+
reject(new Error(`${scriptType} script ${i + 1} failed: ${err.message}`));
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
catch (err) {
|
|
486
|
+
console.error(`ā Error executing ${scriptType} script ${i + 1}:`, err instanceof Error ? err.message : 'Unknown error');
|
|
487
|
+
throw err;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
console.log(`\nā
All ${scriptType} scripts completed successfully`);
|
|
491
|
+
}
|
|
299
492
|
async function runServices() {
|
|
300
493
|
const hasHarborConfig = checkHasHarborConfig();
|
|
301
494
|
if (!hasHarborConfig) {
|
|
@@ -318,6 +511,14 @@ async function runServices() {
|
|
|
318
511
|
console.error('Error reading config:', err);
|
|
319
512
|
process.exit(1);
|
|
320
513
|
}
|
|
514
|
+
// Execute before scripts
|
|
515
|
+
try {
|
|
516
|
+
await execute(config.before || [], 'before');
|
|
517
|
+
}
|
|
518
|
+
catch (err) {
|
|
519
|
+
console.error('ā Before scripts failed, aborting launch');
|
|
520
|
+
process.exit(1);
|
|
521
|
+
}
|
|
321
522
|
// Ensure scripts exist and are executable
|
|
322
523
|
await ensureScriptsExist();
|
|
323
524
|
// Execute the script directly using spawn to handle I/O streams
|
|
@@ -330,12 +531,20 @@ async function runServices() {
|
|
|
330
531
|
console.error(`Error running dev.sh: ${err}`);
|
|
331
532
|
process.exit(1);
|
|
332
533
|
});
|
|
333
|
-
command.on('close', (code) => {
|
|
534
|
+
command.on('close', async (code) => {
|
|
334
535
|
if (code !== 0) {
|
|
335
536
|
console.error(`dev.sh exited with code ${code}`);
|
|
336
537
|
process.exit(1);
|
|
337
538
|
}
|
|
338
|
-
|
|
539
|
+
// Execute after scripts
|
|
540
|
+
try {
|
|
541
|
+
await execute(config.after || [], 'after');
|
|
542
|
+
resolve();
|
|
543
|
+
}
|
|
544
|
+
catch (err) {
|
|
545
|
+
console.error('ā After scripts failed');
|
|
546
|
+
process.exit(1);
|
|
547
|
+
}
|
|
339
548
|
});
|
|
340
549
|
});
|
|
341
550
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abyrd9/harbor-cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "A CLI tool for
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "A CLI tool for orchestrating local development services in tmux sessions. Perfect for microservices and polyglot projects with automatic service discovery and before/after script support.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"harbor": "dist/index.js"
|