@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.
Files changed (3) hide show
  1. package/README.md +248 -37
  2. package/dist/index.js +219 -10
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,47 +1,58 @@
1
1
  # Harbor CLI
2
2
 
3
- A CLI tool for those small side projects that only run a few services. Harbor allows you to:
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
- 1. šŸ› ļø Define your services in a configuration file
6
- 2. šŸš€ Launch your services in a tmux session
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 i -g @abyrd9/harbor-cli
17
+ npm install -g @abyrd9/harbor-cli
12
18
  ```
13
19
 
14
20
  ## Prerequisites
15
21
 
16
- Before using Harbor, make sure you have the following installed:
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
- - [tmux](https://github.com/tmux/tmux/wiki/Installing) (for terminal multiplexing)
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 development environment:
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 new services to your configuration:
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 your services:
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 uses a configuration file to manage your services:
51
+ Harbor supports two configuration formats:
41
52
 
42
- ### harbor.json
53
+ ### Option 1: harbor.json
43
54
 
44
- Contains your service configurations that are used to launch the services:
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": "dashboard",
61
- "path": "./vite-frontend",
62
- "command": "npx drizzle-kit studio"
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
- - `harbor dock`: Generate a new harbor.json file
71
- - `harbor moor`: Add new services to your harbor.json file
72
- - `harbor launch`: Start all services defined in your harbor.json file in a tmux session
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 managing your services. Some useful shortcuts:
167
+ Harbor uses tmux for professional service management. Essential shortcuts:
77
168
 
78
- - `Ctrl+a d`: Detach from session
79
- - `Ctrl+a c`: Create new window
80
- - `Ctrl+a n`: Next window
81
- - `Ctrl+a p`: Previous window
82
- - `Ctrl+q`: Quit session
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 submit a Pull Request.
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
- console.log(`Install instructions: ${dep.installMsg}`);
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
- if (!checkHasHarborConfig()) {
111
- console.log('āŒ No harbor configuration found');
112
- console.log('\nTo initialize a new Harbor project, please use:');
113
- console.log(' harbor dock');
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
- await runServices();
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
- resolve();
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.0.1",
4
- "description": "A CLI tool for managing simple local development services with tmux sessions",
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"