@abyrd9/harbor-cli 0.1.1 → 1.0.0

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
@@ -3,8 +3,7 @@
3
3
  A CLI tool for those small side projects that only run a few services. Harbor allows you to:
4
4
 
5
5
  1. 🛠️ Define your services in a configuration file
6
- 2. 🔄 Generate a Caddyfile to reverse proxy certain services to subdomains
7
- 3. 🚀 Launch your services in a tmux session with Caddy and your services automatically proxied
6
+ 2. 🚀 Launch your services in a tmux session
8
7
 
9
8
  ## Installation
10
9
 
@@ -16,7 +15,6 @@ npm i -g @abyrd9/harbor-cli
16
15
 
17
16
  Before using Harbor, make sure you have the following installed:
18
17
 
19
- - [Caddy](https://caddyserver.com/docs/install) (for reverse proxy)
20
18
  - [tmux](https://github.com/tmux/tmux/wiki/Installing) (for terminal multiplexing)
21
19
  - [jq](https://stedolan.github.io/jq/download/) (for JSON processing within tmux)
22
20
 
@@ -32,74 +30,45 @@ harbor dock
32
30
  harbor moor
33
31
  ```
34
32
 
35
- 3. Update your Caddyfile:
36
- ```bash
37
- harbor anchor
38
- ```
39
-
40
- 4. Launch your services:
33
+ 3. Launch your services:
41
34
  ```bash
42
35
  harbor launch
43
36
  ```
44
37
 
45
38
  ## Configuration
46
39
 
47
- Harbor uses two main configuration files:
40
+ Harbor uses a configuration file to manage your services:
48
41
 
49
42
  ### harbor.json
50
43
 
51
- Contains your service configurations that are used to generate the Caddyfile and launch the services:
44
+ Contains your service configurations that are used to launch the services:
52
45
 
53
46
  ```json
54
47
  {
55
- "domain": "localhost",
56
48
  "services": [
57
49
  {
58
50
  "name": "frontend",
59
51
  "path": "./vite-frontend",
60
- "command": "npm run dev",
61
- "port": 3000,
62
- "subdomain": "app"
52
+ "command": "npm run dev"
63
53
  },
64
54
  {
65
55
  "name": "api",
66
56
  "path": "./go-api",
67
- "command": "go run .",
68
- "port": 8080,
69
- "subdomain": "api"
57
+ "command": "go run ."
70
58
  },
71
59
  {
72
60
  "name": "dashboard",
73
61
  "path": "./vite-frontend",
74
- "command": "npx drizzle-kit studio",
62
+ "command": "npx drizzle-kit studio"
75
63
  }
76
64
  ]
77
65
  }
78
66
  ```
79
67
 
80
- > Note: The dashboard service is a bit special. This is a drizzle studio instance to view your database. There's no subdomain value and no port declared because it typically runs at `local.drizzle.studio`. This will still be running and viewable in your tmux session, but it won't be automatically proxied.
81
-
82
- ### Caddyfile
83
-
84
- Automatically generated reverse proxy configuration:
85
-
86
- ```caddy
87
- api.localhost {
88
- reverse_proxy localhost:8080
89
- }
90
-
91
- app.localhost {
92
- reverse_proxy localhost:3000
93
- }
94
- ```
95
-
96
- > Note: Some services may be tricky with Caddy and getting HMR to work or whatever other problems. Feel free to tinker with the Caddyfile or the subdomain values in the harbor config file in order to get things running. Maybe in the future there will be examples for common cases.
97
-
98
68
  ## Commands
99
69
 
100
70
  - `harbor dock`: Generate a new harbor.json file
101
71
  - `harbor moor`: Add new services to your harbor.json file
102
- - `harbor anchor`: Update your Caddyfile from the current harbor.json file
103
72
  - `harbor launch`: Start all services defined in your harbor.json file in a tmux session
104
73
 
105
74
  ## Terminal Multiplexer
package/dist/index.js CHANGED
@@ -11,12 +11,6 @@ const __filename = fileURLToPath(import.meta.url);
11
11
  const __dirname = path.dirname(__filename);
12
12
  const packageJson = JSON.parse(readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
13
13
  const requiredDependencies = [
14
- {
15
- name: 'Caddy',
16
- command: 'caddy version',
17
- installMsg: 'https://caddyserver.com/docs/install',
18
- requiredFor: 'reverse proxy functionality',
19
- },
20
14
  {
21
15
  name: 'tmux',
22
16
  command: 'tmux -V',
@@ -73,13 +67,11 @@ program
73
67
  .description(`A CLI tool for managing your project's local development services
74
68
 
75
69
  Harbor helps you manage multiple local development services with ease.
76
- It provides a simple way to configure and run your services with automatic
77
- subdomain routing through Caddy reverse proxy.
70
+ It provides a simple way to configure and run your services in tmux sessions.
78
71
 
79
72
  Available Commands:
80
73
  dock Initialize a new Harbor project
81
74
  moor Add new services to your configuration
82
- anchor Generate Caddy reverse proxy configuration
83
75
  launch Start all services in tmux sessions`)
84
76
  .version(packageJson.version)
85
77
  .action(async () => await checkDependencies())
@@ -89,48 +81,22 @@ if (process.argv.length <= 2) {
89
81
  program.help();
90
82
  }
91
83
  program.command('dock')
92
- .description(`Prepares your development environment by creating both:
84
+ .description(`Prepares your development environment by creating a harbor configuration file
93
85
  - harbor.json configuration file (or harbor field in package.json)
94
- - Caddyfile for reverse proxy (if needed)
95
86
 
96
87
  This is typically the first command you'll run in a new project.`)
97
88
  .option('-p, --path <path>', 'The path to the root of your project', './')
98
89
  .action(async (options) => {
99
90
  try {
100
- const caddyFileExists = fileExists('Caddyfile');
101
91
  const configExists = checkHasHarborConfig();
102
- if (caddyFileExists || configExists) {
92
+ if (configExists) {
103
93
  console.log('❌ Error: Harbor project already initialized');
104
- if (caddyFileExists) {
105
- console.log(' - Caddyfile already exists');
106
- }
107
- if (configExists) {
108
- console.log(' - Harbor configuration already exists');
109
- }
110
- console.log('\nTo reinitialize, please remove these files first.');
94
+ console.log(' - Harbor configuration already exists');
95
+ console.log('\nTo reinitialize, please remove the configuration first.');
111
96
  process.exit(1);
112
97
  }
113
- const servicesAdded = await generateDevFile(options.path);
114
- // Only try to generate Caddyfile if services were actually added
115
- if (servicesAdded) {
116
- try {
117
- await generateCaddyFile();
118
- console.log('✨ Environment successfully prepared and anchored!');
119
- }
120
- catch (err) {
121
- if (err instanceof Error && err.message.includes('No harbor configuration found')) {
122
- // This is expected if no services were added
123
- console.log('✨ Environment prepared! No services configured yet.');
124
- }
125
- else {
126
- console.log('❌ Error generating Caddyfile:', err instanceof Error ? err.message : 'Unknown error');
127
- process.exit(1);
128
- }
129
- }
130
- }
131
- else {
132
- console.log('✨ Environment prepared! No services configured yet.');
133
- }
98
+ await generateDevFile(options.path);
99
+ console.log('✨ Environment prepared!');
134
100
  }
135
101
  catch (err) {
136
102
  console.log('❌ Error:', err instanceof Error ? err.message : 'Unknown error');
@@ -149,19 +115,6 @@ program.command('moor')
149
115
  }
150
116
  await generateDevFile(options.path);
151
117
  });
152
- program.command('anchor')
153
- .description(`Add new services to your Caddyfile
154
-
155
- Note: This command will stop any active Caddy processes, including those from other Harbor projects.`)
156
- .action(async () => {
157
- if (!checkHasHarborConfig()) {
158
- console.log('❌ No harbor configuration found');
159
- console.log('\nTo initialize a new Harbor project, please use:');
160
- console.log(' harbor dock');
161
- process.exit(1);
162
- }
163
- await generateCaddyFile();
164
- });
165
118
  program.command('launch')
166
119
  .description(`Launch your services in the harbor terminal multiplexer (Using tmux)
167
120
 
@@ -184,9 +137,6 @@ function isProjectDirectory(dirPath) {
184
137
  });
185
138
  }
186
139
  function validateConfig(config) {
187
- if (!config.domain) {
188
- return 'Domain is required';
189
- }
190
140
  if (!Array.isArray(config.services)) {
191
141
  return 'Services must be an array';
192
142
  }
@@ -227,8 +177,6 @@ async function generateDevFile(dirPath) {
227
177
  // If package.json exists but no harbor config, use it
228
178
  writeToPackageJson = true;
229
179
  config = {
230
- domain: 'localhost',
231
- useSudo: false,
232
180
  services: [],
233
181
  };
234
182
  console.log('Creating new harbor config in package.json...');
@@ -236,8 +184,6 @@ async function generateDevFile(dirPath) {
236
184
  else {
237
185
  // No package.json, create harbor.json
238
186
  config = {
239
- domain: 'localhost',
240
- useSudo: false,
241
187
  services: [],
242
188
  };
243
189
  console.log('Creating new harbor.json...');
@@ -249,8 +195,6 @@ async function generateDevFile(dirPath) {
249
195
  }
250
196
  // No package.json, create harbor.json
251
197
  config = {
252
- domain: 'localhost',
253
- useSudo: false,
254
198
  services: [],
255
199
  };
256
200
  console.log('Creating new harbor.json...');
@@ -268,7 +212,6 @@ async function generateDevFile(dirPath) {
268
212
  const service = {
269
213
  name: folder.name,
270
214
  path: folderPath,
271
- subdomain: folder.name.toLowerCase().replace(/\s+/g, ''),
272
215
  };
273
216
  // Try to determine default command based on project type
274
217
  if (fs.existsSync(path.join(folderPath, 'package.json'))) {
@@ -291,25 +234,6 @@ async function generateDevFile(dirPath) {
291
234
  }
292
235
  if (!newServicesAdded) {
293
236
  console.log('No new services found to add, feel free to add them manually');
294
- // Still write the initial config even if no services were found
295
- const validationError = validateConfig(config);
296
- if (validationError) {
297
- throw new Error(`Invalid harbor configuration: ${validationError}`);
298
- }
299
- if (writeToPackageJson) {
300
- // Update package.json
301
- const packageData = await fs.promises.readFile('package.json', 'utf-8');
302
- const packageJson = JSON.parse(packageData);
303
- packageJson.harbor = config;
304
- await fs.promises.writeFile('package.json', JSON.stringify(packageJson, null, 2), 'utf-8');
305
- console.log('\npackage.json updated successfully with harbor configuration');
306
- }
307
- else {
308
- // Write to harbor.json
309
- await fs.promises.writeFile('harbor.json', JSON.stringify(config, null, 2), 'utf-8');
310
- console.log('\nharbor.json created successfully');
311
- }
312
- return false;
313
237
  }
314
238
  const validationError = validateConfig(config);
315
239
  if (validationError) {
@@ -329,7 +253,6 @@ async function generateDevFile(dirPath) {
329
253
  console.log('\nharbor.json created successfully');
330
254
  }
331
255
  console.log('\nImportant:');
332
- console.log(' - Update the \'Port\' field for each service to match its actual port or leave blank to ignore in the Caddyfile');
333
256
  console.log(' - Verify the auto-detected commands are correct for your services');
334
257
  return true;
335
258
  }
@@ -373,60 +296,6 @@ async function readHarborConfig() {
373
296
  }
374
297
  throw new Error('No harbor configuration found in harbor.json or package.json');
375
298
  }
376
- async function stopCaddy() {
377
- try {
378
- console.log('\n⚠️ Stopping any existing Caddy processes...');
379
- console.log(' This will interrupt any active Harbor or Caddy services\n');
380
- // Try to kill any existing Caddy processes
381
- await new Promise((resolve) => {
382
- const isWindows = process.platform === 'win32';
383
- const killCommand = isWindows ? 'taskkill /F /IM caddy.exe' : 'pkill caddy';
384
- const childProcess = spawn('sh', ['-c', killCommand]);
385
- childProcess.on('close', () => {
386
- // It's okay if there was no process to kill (code 1)
387
- resolve();
388
- });
389
- });
390
- // Give it a moment to fully release ports
391
- await new Promise(resolve => setTimeout(resolve, 1000));
392
- }
393
- catch (err) {
394
- // Ignore errors as the process might not exist
395
- }
396
- }
397
- async function generateCaddyFile() {
398
- try {
399
- const config = await readHarborConfig();
400
- let caddyfileContent = '';
401
- // Check if any services need a Caddyfile
402
- const needsCaddyfile = config.services.some(svc => svc.port && svc.subdomain);
403
- if (!needsCaddyfile) {
404
- // If no services need a Caddyfile, remove it if it exists
405
- if (fileExists('Caddyfile')) {
406
- await fs.promises.unlink('Caddyfile');
407
- console.log('Removed Caddyfile as no services require subdomains');
408
- }
409
- return;
410
- }
411
- for (const svc of config.services) {
412
- if (!svc.port || !svc.subdomain) {
413
- continue;
414
- }
415
- const serverName = `${svc.subdomain}.${config.domain}`;
416
- caddyfileContent += `${serverName} {\n`;
417
- caddyfileContent += ` reverse_proxy localhost:${svc.port}\n`;
418
- caddyfileContent += "}\n\n";
419
- }
420
- await fs.promises.writeFile('Caddyfile', caddyfileContent, 'utf-8');
421
- // Stop existing Caddy process before proceeding
422
- await stopCaddy();
423
- console.log('Caddyfile generated successfully');
424
- }
425
- catch (err) {
426
- console.error('Error generating Caddyfile:', err);
427
- process.exit(1);
428
- }
429
- }
430
299
  async function runServices() {
431
300
  const hasHarborConfig = checkHasHarborConfig();
432
301
  if (!hasHarborConfig) {
@@ -449,18 +318,6 @@ async function runServices() {
449
318
  console.error('Error reading config:', err);
450
319
  process.exit(1);
451
320
  }
452
- // Check if any services need a Caddyfile
453
- const needsCaddyfile = config.services.some(svc => svc.port && svc.subdomain);
454
- if (needsCaddyfile && !fileExists('Caddyfile')) {
455
- console.log('❌ No Caddyfile found, but some services require subdomains');
456
- console.log('\nTo generate the Caddyfile:');
457
- console.log(' harbor anchor');
458
- process.exit(1);
459
- }
460
- // Stop any existing Caddy process if we need it
461
- if (needsCaddyfile) {
462
- await stopCaddy();
463
- }
464
321
  // Ensure scripts exist and are executable
465
322
  await ensureScriptsExist();
466
323
  // Execute the script directly using spawn to handle I/O streams
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@abyrd9/harbor-cli",
3
- "version": "0.1.1",
4
- "description": "A CLI tool for managing local development services with automatic subdomain routing",
3
+ "version": "1.0.0",
4
+ "description": "A CLI tool for managing simple local development services with tmux sessions",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "harbor": "dist/index.js"
@@ -21,8 +21,6 @@
21
21
  "keywords": [
22
22
  "cli",
23
23
  "development",
24
- "proxy",
25
- "caddy",
26
24
  "tmux",
27
25
  "local-development"
28
26
  ],
@@ -49,22 +47,16 @@
49
47
  },
50
48
  "main": "index.js",
51
49
  "harbor": {
52
- "domain": "localhost",
53
- "useSudo": true,
54
50
  "services": [
55
51
  {
56
52
  "name": "vite-project",
57
53
  "path": "test-services/vite-project",
58
- "subdomain": "vite-project",
59
- "command": "npm run dev",
60
- "port": 5173
54
+ "command": "npm run dev"
61
55
  },
62
56
  {
63
57
  "name": "go-api",
64
58
  "path": "test-services/go-api",
65
- "subdomain": "go-api",
66
- "command": "go run .",
67
- "port": 8080
59
+ "command": "go run ."
68
60
  }
69
61
  ]
70
62
  }
package/scripts/dev.sh CHANGED
@@ -79,36 +79,8 @@ tmux set-option -g 'status-format[0]' ''
79
79
  echo "Creating window for interactive shell"
80
80
  tmux rename-window -t local-dev-test:0 'Terminal'
81
81
 
82
- # Check if any services need Caddy
83
- needs_caddy=false
84
- echo "Checking for services that need Caddy..."
85
- if get_harbor_config | jq -e '.services[] | select(.subdomain != null and .port != null) | true' > /dev/null; then
86
- needs_caddy=true
87
- echo "Found services that need Caddy"
88
- fi
89
- echo "needs_caddy value: $needs_caddy"
90
-
91
82
  window_index=1 # Start from index 1
92
83
 
93
- # Create a new window for Caddy if needed
94
- if [ "$needs_caddy" = true ]; then
95
- echo "Creating window for Caddy"
96
- tmux new-window -t local-dev-test:1 -n 'Caddy'
97
-
98
- # Check if useSudo is true in harbor config
99
- use_sudo=$(get_harbor_config | jq -r '.useSudo // false')
100
- echo "useSudo value: $use_sudo"
101
- if [ "$use_sudo" = "true" ]; then
102
- echo "Running Caddy with sudo"
103
- tmux send-keys -t local-dev-test:1 'sudo caddy run' C-m
104
- else
105
- echo "Running Caddy without sudo"
106
- tmux send-keys -t local-dev-test:1 'caddy run' C-m
107
- fi
108
-
109
- window_index=2 # Start services from index 2
110
- fi
111
-
112
84
  # Create windows dynamically based on harbor config
113
85
  get_harbor_config | jq -c '.services[]' | while read service; do
114
86
  name=$(echo $service | jq -r '.name')