@cruxgarden/cli 0.0.6 → 0.0.8

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 (2) hide show
  1. package/lib/commands.js +138 -30
  2. package/package.json +5 -2
package/lib/commands.js CHANGED
@@ -16,23 +16,40 @@ export function showBanner() {
16
16
  // Check if banner was already shown in this shell session
17
17
  if (process.env.CRUX_BANNER_SHOWN) return;
18
18
 
19
- // Custom ASCII art - replace this with your own!
20
- const banner = `
21
- ██████╗██████╗ ██╗ ██╗██╗ ██╗
22
- ██╔════╝██╔══██╗██║ ██║╚██╗██╔╝
23
- ██║ ██████╔╝██║ ██║ ╚███╔╝
24
- ██║ ██╔══██╗██║ ██║ ██╔██╗
25
- ╚██████╗██║ ██║╚██████╔╝██╔╝ ██╗
26
- ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝
27
-
28
- ██████╗ █████╗ ██████╗ ██████╗ ███████╗███╗ ██╗
29
- ██╔════╝ ██╔══██╗██╔══██╗██╔══██╗██╔════╝████╗ ██║
30
- ██║ ███╗███████║██████╔╝██║ ██║█████╗ ██╔██╗ ██║
31
- ██║ ██║██╔══██║██╔══██╗██║ ██║██╔══╝ ██║╚██╗██║
32
- ╚██████╔╝██║ ██║██║ ██║██████╔╝███████╗██║ ╚████║
33
- ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚══════╝╚═╝ ╚═══╝`;
34
-
35
- console.log(chalk.hex(SUCCESS_GREEN)(banner));
19
+ const bannerLines = [
20
+ " ██████╗██████╗ ██╗ ██╗██╗ ██╗",
21
+ "██╔════╝██╔══██╗██║ ██║╚██╗██╔╝",
22
+ "██║ ██████╔╝██║ ██║ ╚███╔╝",
23
+ "██║ ██╔══██╗██║ ██║ ██╔██╗",
24
+ "╚██████╗██║ ██║╚██████╔╝██╔╝ ██╗",
25
+ " ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝",
26
+ "",
27
+ " ██████╗ █████╗ ██████╗ ██████╗ ███████╗███╗ ██╗",
28
+ "██╔════╝ ██╔══██╗██╔══██╗██╔══██╗██╔════╝████╗ ██║",
29
+ "██║ ███╗███████║██████╔╝██║ ██║█████╗ ██╔██╗ ██║",
30
+ "██║ ██║██╔══██║██╔══██╗██║ ██║██╔══╝ ██║╚██╗██║",
31
+ "╚██████╔╝██║ ██║██║ ██║██████╔╝███████╗██║ ╚████║",
32
+ " ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚══════╝╚═╝ ╚═══╝"
33
+ ];
34
+
35
+ // Randomly pick a color from the CLI color palette
36
+ const colors = [
37
+ chalk.hex(SUCCESS_GREEN), // Green
38
+ chalk.cyan, // Cyan
39
+ chalk.yellow, // Yellow
40
+ chalk.magenta, // Magenta
41
+ chalk.blue, // Blue
42
+ chalk.red, // Red
43
+ ];
44
+
45
+ const randomColor = colors[Math.floor(Math.random() * colors.length)];
46
+
47
+ // Print each line with the same random color
48
+ console.log();
49
+ bannerLines.forEach((line) => {
50
+ console.log(randomColor(line));
51
+ });
52
+
36
53
  console.log(chalk.gray(" Nursery Environment - Demo & Trial\n"));
37
54
 
38
55
  // Set environment variable so it persists for this shell session
@@ -89,7 +106,8 @@ function runCommandAsync(command, options = {}) {
89
106
  );
90
107
  reject(error);
91
108
  } else {
92
- resolve(stdout);
109
+ // Return both stdout and stderr since Docker commands often write to stderr
110
+ resolve({ stdout, stderr, combined: stdout + stderr });
93
111
  }
94
112
  });
95
113
 
@@ -117,11 +135,36 @@ function askConfirmation(question) {
117
135
  });
118
136
  }
119
137
 
138
+ function checkDockerAvailable() {
139
+ try {
140
+ execSync("docker info", { stdio: "pipe" });
141
+ return true;
142
+ } catch (error) {
143
+ return false;
144
+ }
145
+ }
146
+
147
+ function ensureDockerRunning() {
148
+ if (!checkDockerAvailable()) {
149
+ console.error(
150
+ chalk.red(
151
+ "\n✗ Docker is not running or not installed.\n",
152
+ ),
153
+ );
154
+ console.log(chalk.gray("Please ensure Docker is installed and running:"));
155
+ console.log(chalk.gray(" • Start Docker Desktop, or"));
156
+ console.log(chalk.gray(" • Start the Docker daemon\n"));
157
+ console.log(chalk.gray("Then try your command again.\n"));
158
+ process.exit(1);
159
+ }
160
+ }
161
+
120
162
  // ============================================================================
121
163
  // Nursery Environment Commands
122
164
  // ============================================================================
123
165
 
124
166
  export async function startNursery(options) {
167
+ ensureDockerRunning();
125
168
  const spinner = ora("Starting Crux Garden Nursery environment...").start();
126
169
 
127
170
  try {
@@ -144,12 +187,21 @@ export async function startNursery(options) {
144
187
  spinner.text =
145
188
  "Starting nursery services (api, postgres, redis)...";
146
189
  await runCommandAsync(
147
- "docker-compose -f docker-compose.nursery.yml up -d --remove-orphans && docker rm cruxgarden-nursery-migrations 2>/dev/null || true",
190
+ "docker-compose -f docker-compose.nursery.yml up -d --remove-orphans",
148
191
  {
149
192
  silent: true,
150
193
  },
151
194
  );
152
195
 
196
+ // Clean up the migrations container if it exists (cross-platform)
197
+ await runCommandAsync(
198
+ "docker rm cruxgarden-nursery-migrations",
199
+ {
200
+ silent: true,
201
+ ignoreError: true,
202
+ },
203
+ );
204
+
153
205
  spinner.succeed("Crux Garden Nursery environment started!");
154
206
  console.log(
155
207
  chalk.hex(SUCCESS_GREEN)("\n✓ API running on:"),
@@ -198,6 +250,7 @@ export async function startNursery(options) {
198
250
  }
199
251
 
200
252
  export async function stopNursery() {
253
+ ensureDockerRunning();
201
254
  const spinner = ora("Stopping Crux Garden Nursery environment...").start();
202
255
  await runCommandAsync("docker-compose -f docker-compose.nursery.yml down", {
203
256
  silent: true,
@@ -212,6 +265,7 @@ export async function stopNursery() {
212
265
  }
213
266
 
214
267
  export async function logsNursery(options) {
268
+ ensureDockerRunning();
215
269
  if (options.follow) {
216
270
  console.log(
217
271
  chalk.gray("Following nursery logs (press Ctrl+C to exit)...\n"),
@@ -236,6 +290,7 @@ export async function logsNursery(options) {
236
290
  }
237
291
 
238
292
  export async function cleanNursery() {
293
+ ensureDockerRunning();
239
294
  console.log(
240
295
  chalk.yellow(
241
296
  "\n⚠️ This will delete all nursery containers and volumes (including data)!\n",
@@ -273,6 +328,7 @@ export async function cleanNursery() {
273
328
  }
274
329
 
275
330
  export async function purgeNursery() {
331
+ ensureDockerRunning();
276
332
  console.log(
277
333
  chalk.yellow(
278
334
  "\n⚠️ This will delete ALL nursery resources (containers, volumes, AND images)!\n",
@@ -316,24 +372,62 @@ export async function purgeNursery() {
316
372
  }
317
373
 
318
374
  export async function updateNursery() {
375
+ ensureDockerRunning();
319
376
  const spinner = ora(
320
- "Downloading latest Crux Garden API image from ghcr.io...",
377
+ "Checking for latest Crux Garden API image from ghcr.io...",
321
378
  ).start();
322
379
 
323
- await runCommandAsync("docker-compose -f docker-compose.nursery.yml pull", {
324
- silent: true,
325
- });
380
+ try {
381
+ // Get the current local image digest before pulling
382
+ const beforeResult = await runCommandAsync(
383
+ "docker images --digests --format '{{.Digest}}' ghcr.io/cruxgarden/api:latest",
384
+ { silent: true, ignoreError: true }
385
+ );
386
+ const digestBefore = beforeResult?.combined?.trim() || beforeResult?.stdout?.trim() || "";
326
387
 
327
- spinner.succeed("Latest nursery image downloaded!");
328
- console.log(
329
- chalk.gray("Run"),
330
- chalk.cyan("crux nursery restart"),
331
- chalk.gray("to use the new image."),
332
- );
388
+ // Pull latest images
389
+ spinner.text = "Pulling latest images...";
390
+ await runCommandAsync("docker-compose -f docker-compose.nursery.yml pull", {
391
+ silent: true,
392
+ });
393
+
394
+ // Get the image digest after pulling
395
+ const afterResult = await runCommandAsync(
396
+ "docker images --digests --format '{{.Digest}}' ghcr.io/cruxgarden/api:latest",
397
+ { silent: true, ignoreError: true }
398
+ );
399
+ const digestAfter = afterResult?.combined?.trim() || afterResult?.stdout?.trim() || "";
400
+
401
+ // Compare digests to see if image was updated
402
+ if (digestBefore && digestAfter && digestBefore === digestAfter) {
403
+ spinner.succeed("Nursery image is already up-to-date!");
404
+ console.log(
405
+ chalk.gray("\nYou're running the latest version."),
406
+ );
407
+ } else if (digestAfter) {
408
+ spinner.succeed("Latest nursery image downloaded!");
409
+ console.log(
410
+ chalk.gray("\nRun"),
411
+ chalk.cyan("crux nursery restart"),
412
+ chalk.gray("to use the new image."),
413
+ );
414
+ } else {
415
+ spinner.succeed("Images pulled successfully!");
416
+ console.log(
417
+ chalk.gray("\nRun"),
418
+ chalk.cyan("crux nursery restart"),
419
+ chalk.gray("to use the latest images."),
420
+ );
421
+ }
422
+ } catch (error) {
423
+ spinner.fail("Failed to update images");
424
+ throw error;
425
+ }
333
426
  console.log();
334
427
  }
335
428
 
336
429
  export async function resetNursery() {
430
+ ensureDockerRunning();
337
431
  console.log(
338
432
  chalk.yellow(
339
433
  "\n⚠️ This will delete all nursery data and start fresh with the latest image!\n",
@@ -371,9 +465,18 @@ export async function resetNursery() {
371
465
  // Start fresh
372
466
  spinner.text = "Starting fresh nursery environment...";
373
467
  await runCommandAsync(
374
- "docker-compose -f docker-compose.nursery.yml up -d --remove-orphans && docker rm cruxgarden-nursery-migrations 2>/dev/null || true",
468
+ "docker-compose -f docker-compose.nursery.yml up -d --remove-orphans",
469
+ {
470
+ silent: true,
471
+ },
472
+ );
473
+
474
+ // Clean up the migrations container if it exists (cross-platform)
475
+ await runCommandAsync(
476
+ "docker rm cruxgarden-nursery-migrations",
375
477
  {
376
478
  silent: true,
479
+ ignoreError: true,
377
480
  },
378
481
  );
379
482
 
@@ -406,12 +509,14 @@ export async function restartNursery() {
406
509
  }
407
510
 
408
511
  export async function statusNursery() {
512
+ ensureDockerRunning();
409
513
  console.log(chalk.bold("\nCrux Garden Nursery Environment Status:\n"));
410
514
  runCommand("docker-compose -f docker-compose.nursery.yml ps");
411
515
  console.log();
412
516
  }
413
517
 
414
518
  export async function connectNurseryDb() {
519
+ ensureDockerRunning();
415
520
  console.log(chalk.gray("Connecting to Nursery PostgreSQL...\n"));
416
521
  runCommand(
417
522
  "docker exec -it cruxgarden-nursery-postgres psql -U cruxgarden -d cruxgarden",
@@ -419,16 +524,19 @@ export async function connectNurseryDb() {
419
524
  }
420
525
 
421
526
  export async function connectNurseryRedis() {
527
+ ensureDockerRunning();
422
528
  console.log(chalk.gray("Connecting to Nursery Redis...\n"));
423
529
  runCommand("docker exec -it cruxgarden-nursery-redis redis-cli");
424
530
  }
425
531
 
426
532
  export async function connectNurseryApi() {
533
+ ensureDockerRunning();
427
534
  console.log(chalk.gray("Opening shell in Nursery API container...\n"));
428
535
  runCommand("docker exec -it cruxgarden-nursery-api sh");
429
536
  }
430
537
 
431
538
  export async function stopNurseryDb() {
539
+ ensureDockerRunning();
432
540
  const spinner = ora("Stopping nursery database services...").start();
433
541
  await runCommandAsync(
434
542
  "docker-compose -f docker-compose.nursery.yml stop postgres redis",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cruxgarden/cli",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "Crux Garden CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,7 +19,10 @@
19
19
  "docker:nursery:db:stop": "cd docker && docker-compose -f docker-compose.nursery.yml stop postgres redis",
20
20
  "docker:nursery:redis:connect": "docker exec -it cruxgarden-nursery-redis redis-cli",
21
21
  "docker:nursery:db:connect": "docker exec -it cruxgarden-nursery-postgres psql -U cruxgarden -d cruxgarden",
22
- "docker:nursery:api:connect": "docker exec -it cruxgarden-nursery-api sh"
22
+ "docker:nursery:api:connect": "docker exec -it cruxgarden-nursery-api sh",
23
+ "publish:patch": "npm version patch && npm publish && git push && git push --tags",
24
+ "publish:minor": "npm version minor && npm publish && git push && git push --tags",
25
+ "publish:major": "npm version major && npm publish && git push && git push --tags"
23
26
  },
24
27
  "engines": {
25
28
  "node": ">=18.0.0"