@abyrd9/harbor-cli 0.0.3 → 0.1.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/dist/index.js CHANGED
@@ -90,37 +90,61 @@ if (process.argv.length <= 2) {
90
90
  }
91
91
  program.command('dock')
92
92
  .description(`Prepares your development environment by creating both:
93
- - harbor.json configuration file
94
- - Caddyfile for reverse proxy
93
+ - harbor.json configuration file (or harbor field in package.json)
94
+ - Caddyfile for reverse proxy (if needed)
95
95
 
96
96
  This is typically the first command you'll run in a new project.`)
97
97
  .option('-p, --path <path>', 'The path to the root of your project', './')
98
98
  .action(async (options) => {
99
- const caddyFileExists = fileExists('Caddyfile');
100
- const configFileExists = fileExists('harbor.json');
101
- if (caddyFileExists || configFileExists) {
102
- console.log('❌ Error: Harbor project already initialized');
103
- if (caddyFileExists) {
104
- console.log(' - Caddyfile already exists');
99
+ try {
100
+ const caddyFileExists = fileExists('Caddyfile');
101
+ const configExists = checkHasHarborConfig();
102
+ if (caddyFileExists || configExists) {
103
+ 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.');
111
+ process.exit(1);
105
112
  }
106
- if (configFileExists) {
107
- console.log(' - harbor.json already exists');
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.');
108
133
  }
109
- console.log('\nTo reinitialize, please remove these files first.');
134
+ }
135
+ catch (err) {
136
+ console.log('❌ Error:', err instanceof Error ? err.message : 'Unknown error');
110
137
  process.exit(1);
111
138
  }
112
- await generateDevFile(options.path);
113
- await generateCaddyFile();
114
- console.log('✨ Environment successfully prepared and anchored!');
115
139
  });
116
140
  program.command('moor')
117
- .description('Add new services to your harbor.json configuration file')
141
+ .description('Add new services to your harbor configuration')
118
142
  .option('-p, --path <path>', 'The path to the root of your project', './')
119
143
  .action(async (options) => {
120
- if (!fileExists('harbor.json')) {
121
- console.log('❌ No harbor.json configuration found');
144
+ if (!checkHasHarborConfig()) {
145
+ console.log('❌ No harbor configuration found');
122
146
  console.log('\nTo initialize a new Harbor project, please use:');
123
- console.log(' harbor anchor');
147
+ console.log(' harbor dock');
124
148
  process.exit(1);
125
149
  }
126
150
  await generateDevFile(options.path);
@@ -130,10 +154,10 @@ program.command('anchor')
130
154
 
131
155
  Note: This command will stop any active Caddy processes, including those from other Harbor projects.`)
132
156
  .action(async () => {
133
- if (!fileExists('harbor.json')) {
134
- console.log('❌ No harbor.json configuration found');
157
+ if (!checkHasHarborConfig()) {
158
+ console.log('❌ No harbor configuration found');
135
159
  console.log('\nTo initialize a new Harbor project, please use:');
136
- console.log(' harbor anchor');
160
+ console.log(' harbor dock');
137
161
  process.exit(1);
138
162
  }
139
163
  await generateCaddyFile();
@@ -178,28 +202,63 @@ function validateConfig(config) {
178
202
  }
179
203
  async function generateDevFile(dirPath) {
180
204
  let config;
205
+ let writeToPackageJson = false;
181
206
  try {
182
- const existing = await fs.promises.readFile('harbor.json', 'utf-8');
183
- config = JSON.parse(existing);
184
- console.log('Found existing harbor.json, scanning for new services...');
185
- }
186
- catch (err) {
187
- if (err.code !== 'ENOENT') {
188
- console.error('Error reading harbor.json:', err);
189
- process.exit(1);
207
+ // First try to read from harbor.json
208
+ try {
209
+ const existing = await fs.promises.readFile('harbor.json', 'utf-8');
210
+ config = JSON.parse(existing);
211
+ console.log('Found existing harbor.json, scanning for new services...');
190
212
  }
191
- // Initialize new config with defaults
192
- config = {
193
- domain: 'localhost',
194
- useSudo: false,
195
- services: [],
196
- };
197
- console.log('Creating new harbor.json...');
198
- }
199
- // Create a map of existing services for easy lookup
200
- const existing = new Set(config.services.map(s => s.name));
201
- let newServicesAdded = false;
202
- try {
213
+ catch (err) {
214
+ if (err.code !== 'ENOENT') {
215
+ throw new Error(`Error reading harbor.json: ${err instanceof Error ? err.message : 'Unknown error'}`);
216
+ }
217
+ // If harbor.json doesn't exist, try package.json
218
+ try {
219
+ const packageData = await fs.promises.readFile('package.json', 'utf-8');
220
+ const packageJson = JSON.parse(packageData);
221
+ if (packageJson.harbor) {
222
+ config = packageJson.harbor;
223
+ writeToPackageJson = true;
224
+ console.log('Found existing harbor config in package.json, scanning for new services...');
225
+ }
226
+ else if (fileExists('package.json')) {
227
+ // If package.json exists but no harbor config, use it
228
+ writeToPackageJson = true;
229
+ config = {
230
+ domain: 'localhost',
231
+ useSudo: false,
232
+ services: [],
233
+ };
234
+ console.log('Creating new harbor config in package.json...');
235
+ }
236
+ else {
237
+ // No package.json, create harbor.json
238
+ config = {
239
+ domain: 'localhost',
240
+ useSudo: false,
241
+ services: [],
242
+ };
243
+ console.log('Creating new harbor.json...');
244
+ }
245
+ }
246
+ catch (err) {
247
+ if (err.code !== 'ENOENT') {
248
+ throw new Error(`Error reading package.json: ${err instanceof Error ? err.message : 'Unknown error'}`);
249
+ }
250
+ // No package.json, create harbor.json
251
+ config = {
252
+ domain: 'localhost',
253
+ useSudo: false,
254
+ services: [],
255
+ };
256
+ console.log('Creating new harbor.json...');
257
+ }
258
+ }
259
+ // Create a map of existing services for easy lookup
260
+ const existing = new Set(config.services.map(s => s.name));
261
+ let newServicesAdded = false;
203
262
  const folders = await fs.promises.readdir(dirPath, { withFileTypes: true });
204
263
  for (const folder of folders) {
205
264
  if (folder.isDirectory()) {
@@ -232,40 +291,87 @@ async function generateDevFile(dirPath) {
232
291
  }
233
292
  if (!newServicesAdded) {
234
293
  console.log('No new services found to add, feel free to add them manually');
235
- return;
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;
236
313
  }
237
314
  const validationError = validateConfig(config);
238
315
  if (validationError) {
239
- console.log(`❌ Invalid harbor.json configuration: ${validationError}`);
240
- process.exit(1);
316
+ throw new Error(`Invalid harbor configuration: ${validationError}`);
317
+ }
318
+ if (writeToPackageJson) {
319
+ // Update package.json
320
+ const packageData = await fs.promises.readFile('package.json', 'utf-8');
321
+ const packageJson = JSON.parse(packageData);
322
+ packageJson.harbor = config;
323
+ await fs.promises.writeFile('package.json', JSON.stringify(packageJson, null, 2), 'utf-8');
324
+ console.log('\npackage.json updated successfully with harbor configuration');
325
+ }
326
+ else {
327
+ // Write to harbor.json
328
+ await fs.promises.writeFile('harbor.json', JSON.stringify(config, null, 2), 'utf-8');
329
+ console.log('\nharbor.json created successfully');
241
330
  }
242
- await fs.promises.writeFile('harbor.json', JSON.stringify(config, null, 2), 'utf-8');
243
- console.log('\nharbor.json updated successfully');
244
331
  console.log('\nImportant:');
245
332
  console.log(' - Update the \'Port\' field for each service to match its actual port or leave blank to ignore in the Caddyfile');
246
333
  console.log(' - Verify the auto-detected commands are correct for your services');
334
+ return true;
247
335
  }
248
336
  catch (err) {
249
- console.error('Error processing directory:', err);
250
- process.exit(1);
337
+ throw new Error(`Error processing directory: ${err instanceof Error ? err.message : 'Unknown error'}`);
251
338
  }
252
339
  }
253
340
  async function readHarborConfig() {
341
+ // First try to read from harbor.json
254
342
  try {
255
343
  const data = await fs.promises.readFile('harbor.json', 'utf-8');
256
344
  const config = JSON.parse(data);
257
345
  const validationError = validateConfig(config);
258
346
  if (validationError) {
259
- throw new Error(`Invalid configuration: ${validationError}`);
347
+ throw new Error(`Invalid configuration in harbor.json: ${validationError}`);
260
348
  }
261
349
  return config;
262
350
  }
263
351
  catch (err) {
264
- if (err.code === 'ENOENT') {
265
- throw new Error('harbor.json not found');
352
+ if (err.code !== 'ENOENT') {
353
+ throw err;
354
+ }
355
+ }
356
+ // If harbor.json doesn't exist, try package.json
357
+ try {
358
+ const packageData = await fs.promises.readFile('package.json', 'utf-8');
359
+ const packageJson = JSON.parse(packageData);
360
+ if (packageJson.harbor) {
361
+ const config = packageJson.harbor;
362
+ const validationError = validateConfig(config);
363
+ if (validationError) {
364
+ throw new Error(`Invalid configuration in package.json harbor field: ${validationError}`);
365
+ }
366
+ return config;
367
+ }
368
+ }
369
+ catch (err) {
370
+ if (err.code !== 'ENOENT') {
371
+ throw err;
266
372
  }
267
- throw err;
268
373
  }
374
+ throw new Error('No harbor configuration found in harbor.json or package.json');
269
375
  }
270
376
  async function stopCaddy() {
271
377
  try {
@@ -322,11 +428,11 @@ async function generateCaddyFile() {
322
428
  }
323
429
  }
324
430
  async function runServices() {
325
- // Check for required files
326
- if (!fileExists('harbor.json')) {
327
- console.log('❌ No harbor.json configuration found');
431
+ const hasHarborConfig = checkHasHarborConfig();
432
+ if (!hasHarborConfig) {
433
+ console.log('❌ No harbor configuration found');
328
434
  console.log('\nTo initialize a new Harbor project, please use:');
329
- console.log(' harbor anchor');
435
+ console.log(' harbor dock');
330
436
  process.exit(1);
331
437
  }
332
438
  // Load and validate config
@@ -414,3 +520,18 @@ async function ensureScriptsExist() {
414
520
  throw err;
415
521
  }
416
522
  }
523
+ function checkHasHarborConfig() {
524
+ // Check for harbor.json
525
+ if (fileExists('harbor.json')) {
526
+ return true;
527
+ }
528
+ // Check for harbor config in package.json
529
+ try {
530
+ const packageData = fs.readFileSync(`${process.cwd()}/package.json`, 'utf-8');
531
+ const packageJson = JSON.parse(packageData);
532
+ return !!packageJson.harbor;
533
+ }
534
+ catch {
535
+ return false;
536
+ }
537
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abyrd9/harbor-cli",
3
- "version": "0.0.3",
3
+ "version": "0.1.0",
4
4
  "description": "A CLI tool for managing local development services with automatic subdomain routing",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,8 +14,8 @@
14
14
  "check": "bunx npm-check -u",
15
15
  "build": "tsc",
16
16
  "prepare": "bun run build",
17
- "start": "node dist/index.js",
18
- "harbor": "node dist/index.js",
17
+ "start": "bun dist/index.js",
18
+ "harbor": "bun dist/index.js",
19
19
  "release": "bash release.sh"
20
20
  },
21
21
  "keywords": [
@@ -40,12 +40,30 @@
40
40
  "node": ">=18.0.0"
41
41
  },
42
42
  "dependencies": {
43
- "@commander-js/extra-typings": "^12.1.0"
43
+ "@commander-js/extra-typings": "^13.1.0"
44
44
  },
45
45
  "devDependencies": {
46
- "@types/node": "^22.10.2",
46
+ "@types/node": "^22.13.1",
47
47
  "bun-types": "latest",
48
- "typescript": "^5.3.3"
48
+ "typescript": "^5.7.3"
49
49
  },
50
- "main": "index.js"
50
+ "main": "index.js",
51
+ "harbor": {
52
+ "domain": "localhost",
53
+ "useSudo": false,
54
+ "services": [
55
+ {
56
+ "name": "vite-project",
57
+ "path": "test-services/vite-project",
58
+ "subdomain": "vite-project",
59
+ "command": "npm run dev"
60
+ },
61
+ {
62
+ "name": "go-api",
63
+ "path": "test-services/go-api",
64
+ "subdomain": "go-api",
65
+ "command": "go run ."
66
+ }
67
+ ]
68
+ }
51
69
  }
package/scripts/dev.sh CHANGED
@@ -6,6 +6,17 @@ if tmux has-session -t local-dev-test 2>/dev/null; then
6
6
  tmux kill-session -t local-dev-test
7
7
  fi
8
8
 
9
+ # Function to get harbor config
10
+ get_harbor_config() {
11
+ if [ -f "harbor.json" ]; then
12
+ cat harbor.json
13
+ elif [ -f "package.json" ]; then
14
+ jq '.harbor' package.json
15
+ else
16
+ echo "{}"
17
+ fi
18
+ }
19
+
9
20
  # Start a new tmux session named 'local-dev-test' and rename the initial window
10
21
  tmux new-session -d -s local-dev-test
11
22
 
@@ -70,7 +81,7 @@ tmux rename-window -t local-dev-test:0 'Terminal'
70
81
 
71
82
  # Check if any services need Caddy
72
83
  needs_caddy=false
73
- jq -c '.services[]' harbor.json | while read service; do
84
+ get_harbor_config | jq -c '.services[]' | while read service; do
74
85
  subdomain=$(echo $service | jq -r '.subdomain // empty')
75
86
  port=$(echo $service | jq -r '.port // empty')
76
87
  if [ ! -z "$subdomain" ] && [ ! -z "$port" ]; then
@@ -89,8 +100,8 @@ if [ "$needs_caddy" = true ]; then
89
100
  window_index=2 # Start services from index 2
90
101
  fi
91
102
 
92
- # Create windows dynamically based on harbor.json
93
- jq -c '.services[]' harbor.json | while read service; do
103
+ # Create windows dynamically based on harbor config
104
+ get_harbor_config | jq -c '.services[]' | while read service; do
94
105
  name=$(echo $service | jq -r '.name')
95
106
  path=$(echo $service | jq -r '.path')
96
107
  command=$(echo $service | jq -r '.command')