@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 +177 -56
- package/package.json +25 -7
- package/scripts/dev.sh +14 -3
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
107
|
-
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
121
|
-
console.log('❌ No harbor
|
|
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
|
|
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 (!
|
|
134
|
-
console.log('❌ No harbor
|
|
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
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
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
|
-
|
|
240
|
-
|
|
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
|
-
|
|
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
|
|
265
|
-
throw
|
|
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
|
-
|
|
326
|
-
if (!
|
|
327
|
-
console.log('❌ No harbor
|
|
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
|
|
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
|
+
"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": "
|
|
18
|
-
"harbor": "
|
|
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": "^
|
|
43
|
+
"@commander-js/extra-typings": "^13.1.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@types/node": "^22.
|
|
46
|
+
"@types/node": "^22.13.1",
|
|
47
47
|
"bun-types": "latest",
|
|
48
|
-
"typescript": "^5.
|
|
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[]'
|
|
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
|
|
93
|
-
jq -c '.services[]'
|
|
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')
|