postgresai 0.11.0-alpha.5 → 0.11.0-alpha.7

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/bin/postgres-ai.js +268 -5
  2. package/package.json +1 -1
@@ -191,12 +191,166 @@ program
191
191
  const code = await runCompose(["run", "--rm", "sources-generator"]);
192
192
  if (code !== 0) process.exitCode = code;
193
193
  });
194
- program.command("update").description("update project").action(stub("update"));
194
+ program
195
+ .command("update")
196
+ .description("update project")
197
+ .action(async () => {
198
+ const { exec } = require("child_process");
199
+ const util = require("util");
200
+ const execPromise = util.promisify(exec);
201
+ const fs = require("fs");
202
+ const path = require("path");
203
+
204
+ console.log("Updating PostgresAI monitoring stack...\n");
205
+
206
+ try {
207
+ // Check if we're in a git repo
208
+ const gitDir = path.resolve(process.cwd(), ".git");
209
+ if (!fs.existsSync(gitDir)) {
210
+ console.error("Not a git repository. Cannot update.");
211
+ process.exitCode = 1;
212
+ return;
213
+ }
214
+
215
+ // Fetch latest changes
216
+ console.log("Fetching latest changes...");
217
+ await execPromise("git fetch origin");
218
+
219
+ // Check current branch
220
+ const { stdout: branch } = await execPromise("git rev-parse --abbrev-ref HEAD");
221
+ const currentBranch = branch.trim();
222
+ console.log(`Current branch: ${currentBranch}`);
223
+
224
+ // Pull latest changes
225
+ console.log("Pulling latest changes...");
226
+ const { stdout: pullOut } = await execPromise("git pull origin " + currentBranch);
227
+ console.log(pullOut);
228
+
229
+ // Update Docker images
230
+ console.log("\nUpdating Docker images...");
231
+ const code = await runCompose(["pull"]);
232
+
233
+ if (code === 0) {
234
+ console.log("\n✓ Update completed successfully");
235
+ console.log("\nTo apply updates, restart services:");
236
+ console.log(" postgres-ai restart");
237
+ } else {
238
+ console.error("\n✗ Docker image update failed");
239
+ process.exitCode = 1;
240
+ }
241
+ } catch (error) {
242
+ console.error(`Update failed: ${error.message}`);
243
+ process.exitCode = 1;
244
+ }
245
+ });
195
246
  program
196
247
  .command("reset [service]")
197
248
  .description("reset all or specific service")
198
- .action(stub("reset"));
199
- program.command("clean").description("cleanup artifacts").action(stub("clean"));
249
+ .action(async (service) => {
250
+ const readline = require("readline");
251
+ const rl = readline.createInterface({
252
+ input: process.stdin,
253
+ output: process.stdout,
254
+ });
255
+
256
+ const question = (prompt) => new Promise((resolve) => rl.question(prompt, resolve));
257
+
258
+ try {
259
+ if (service) {
260
+ // Reset specific service
261
+ console.log(`\nThis will stop '${service}', remove its volume, and restart it.`);
262
+ console.log("All data for this service will be lost!\n");
263
+
264
+ const answer = await question("Continue? (y/N): ");
265
+ if (answer.toLowerCase() !== "y") {
266
+ console.log("Cancelled");
267
+ rl.close();
268
+ return;
269
+ }
270
+
271
+ console.log(`\nStopping ${service}...`);
272
+ await runCompose(["stop", service]);
273
+
274
+ console.log(`Removing volume for ${service}...`);
275
+ await runCompose(["rm", "-f", "-v", service]);
276
+
277
+ console.log(`Restarting ${service}...`);
278
+ const code = await runCompose(["up", "-d", service]);
279
+
280
+ if (code === 0) {
281
+ console.log(`\n✓ Service '${service}' has been reset`);
282
+ } else {
283
+ console.error(`\n✗ Failed to restart '${service}'`);
284
+ process.exitCode = 1;
285
+ }
286
+ } else {
287
+ // Reset all services
288
+ console.log("\nThis will stop all services and remove all data!");
289
+ console.log("Volumes, networks, and containers will be deleted.\n");
290
+
291
+ const answer = await question("Continue? (y/N): ");
292
+ if (answer.toLowerCase() !== "y") {
293
+ console.log("Cancelled");
294
+ rl.close();
295
+ return;
296
+ }
297
+
298
+ console.log("\nStopping services and removing data...");
299
+ const downCode = await runCompose(["down", "-v"]);
300
+
301
+ if (downCode === 0) {
302
+ console.log("✓ Environment reset completed - all containers and data removed");
303
+ } else {
304
+ console.error("✗ Reset failed");
305
+ process.exitCode = 1;
306
+ }
307
+ }
308
+
309
+ rl.close();
310
+ } catch (error) {
311
+ rl.close();
312
+ console.error(`Reset failed: ${error.message}`);
313
+ process.exitCode = 1;
314
+ }
315
+ });
316
+ program
317
+ .command("clean")
318
+ .description("cleanup artifacts")
319
+ .action(async () => {
320
+ const { exec } = require("child_process");
321
+ const util = require("util");
322
+ const execPromise = util.promisify(exec);
323
+
324
+ console.log("Cleaning up Docker resources...\n");
325
+
326
+ try {
327
+ // Remove stopped containers
328
+ const { stdout: containers } = await execPromise("docker ps -aq --filter 'status=exited'");
329
+ if (containers.trim()) {
330
+ await execPromise(`docker rm ${containers.trim().split('\n').join(' ')}`);
331
+ console.log("✓ Removed stopped containers");
332
+ } else {
333
+ console.log("✓ No stopped containers to remove");
334
+ }
335
+
336
+ // Remove unused volumes
337
+ const { stdout: volumeOut } = await execPromise("docker volume prune -f");
338
+ console.log("✓ Removed unused volumes");
339
+
340
+ // Remove unused networks
341
+ const { stdout: networkOut } = await execPromise("docker network prune -f");
342
+ console.log("✓ Removed unused networks");
343
+
344
+ // Remove dangling images
345
+ const { stdout: imageOut } = await execPromise("docker image prune -f");
346
+ console.log("✓ Removed dangling images");
347
+
348
+ console.log("\nCleanup completed");
349
+ } catch (error) {
350
+ console.error(`Error during cleanup: ${error.message}`);
351
+ process.exitCode = 1;
352
+ }
353
+ });
200
354
  program
201
355
  .command("shell <service>")
202
356
  .description("open service shell")
@@ -330,7 +484,69 @@ program
330
484
  program
331
485
  .command("test-instance <name>")
332
486
  .description("test instance connectivity")
333
- .action(stub("test-instance"));
487
+ .action(async (name) => {
488
+ const fs = require("fs");
489
+ const path = require("path");
490
+ const { exec } = require("child_process");
491
+ const util = require("util");
492
+ const execPromise = util.promisify(exec);
493
+
494
+ const instancesPath = path.resolve(process.cwd(), "instances.yml");
495
+ if (!fs.existsSync(instancesPath)) {
496
+ console.error("instances.yml not found");
497
+ process.exitCode = 1;
498
+ return;
499
+ }
500
+
501
+ const content = fs.readFileSync(instancesPath, "utf8");
502
+ const lines = content.split(/\r?\n/);
503
+ let connStr = "";
504
+ let foundInstance = false;
505
+
506
+ for (let i = 0; i < lines.length; i++) {
507
+ const nameLine = lines[i].match(/^-[\t ]*name:[\t ]*(.+)$/);
508
+ if (nameLine && nameLine[1].trim() === name) {
509
+ foundInstance = true;
510
+ // Look for conn_str in next lines
511
+ for (let j = i + 1; j < lines.length && j < i + 15; j++) {
512
+ const connLine = lines[j].match(/^[\t ]*conn_str:[\t ]*(.+)$/);
513
+ if (connLine) {
514
+ connStr = connLine[1].trim();
515
+ break;
516
+ }
517
+ // Stop at next instance
518
+ if (lines[j].match(/^-[\t ]*name:/)) break;
519
+ }
520
+ break;
521
+ }
522
+ }
523
+
524
+ if (!foundInstance) {
525
+ console.error(`Instance '${name}' not found`);
526
+ process.exitCode = 1;
527
+ return;
528
+ }
529
+
530
+ if (!connStr) {
531
+ console.error(`Connection string not found for instance '${name}'`);
532
+ process.exitCode = 1;
533
+ return;
534
+ }
535
+
536
+ console.log(`Testing connection to '${name}'...`);
537
+
538
+ try {
539
+ const { stdout, stderr } = await execPromise(
540
+ `psql "${connStr}" -c "SELECT version();" --no-psqlrc`,
541
+ { timeout: 10000, env: { ...process.env, PAGER: 'cat' } }
542
+ );
543
+ console.log(`✓ Connection successful`);
544
+ console.log(stdout.trim());
545
+ } catch (error) {
546
+ console.error(`✗ Connection failed: ${error.message}`);
547
+ process.exitCode = 1;
548
+ }
549
+ });
334
550
 
335
551
  // API key and grafana
336
552
  program
@@ -400,7 +616,54 @@ program
400
616
  program
401
617
  .command("generate-grafana-password")
402
618
  .description("generate Grafana password")
403
- .action(stub("generate-grafana-password"));
619
+ .action(async () => {
620
+ const fs = require("fs");
621
+ const path = require("path");
622
+ const { exec } = require("child_process");
623
+ const util = require("util");
624
+ const execPromise = util.promisify(exec);
625
+
626
+ const cfgPath = path.resolve(process.cwd(), ".pgwatch-config");
627
+
628
+ try {
629
+ // Generate secure password using openssl
630
+ const { stdout: password } = await execPromise(
631
+ "openssl rand -base64 12 | tr -d '\n'"
632
+ );
633
+ const newPassword = password.trim();
634
+
635
+ if (!newPassword) {
636
+ console.error("Failed to generate password");
637
+ process.exitCode = 1;
638
+ return;
639
+ }
640
+
641
+ // Read existing config
642
+ let config = "";
643
+ if (fs.existsSync(cfgPath)) {
644
+ config = fs.readFileSync(cfgPath, "utf8");
645
+ }
646
+
647
+ // Update or add grafana_password
648
+ const lines = config.split(/\r?\n/).filter((l) => !/^grafana_password=/.test(l));
649
+ lines.push(`grafana_password=${newPassword}`);
650
+
651
+ // Write back
652
+ fs.writeFileSync(cfgPath, lines.filter(Boolean).join("\n") + "\n", "utf8");
653
+
654
+ console.log("✓ New Grafana password generated and saved");
655
+ console.log("\nNew credentials:");
656
+ console.log(" URL: http://localhost:3000");
657
+ console.log(" Username: monitor");
658
+ console.log(` Password: ${newPassword}`);
659
+ console.log("\nRestart Grafana to apply:");
660
+ console.log(" postgres-ai restart grafana");
661
+ } catch (error) {
662
+ console.error(`Failed to generate password: ${error.message}`);
663
+ console.error("\nNote: This command requires 'openssl' to be installed");
664
+ process.exitCode = 1;
665
+ }
666
+ });
404
667
  program
405
668
  .command("show-grafana-credentials")
406
669
  .description("show Grafana credentials")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresai",
3
- "version": "0.11.0-alpha.5",
3
+ "version": "0.11.0-alpha.7",
4
4
  "description": "PostgresAI CLI (Node.js)",
5
5
  "license": "MIT",
6
6
  "private": false,