postgresai 0.15.0-dev.2 → 0.15.0-dev.3

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.
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- import { Command } from "commander";
3
+ import { Command, Option } from "commander";
4
4
  import pkg from "../package.json";
5
5
  import * as config from "../lib/config";
6
6
  import * as yaml from "js-yaml";
@@ -343,7 +343,8 @@ function writeReportFiles(reports: Record<string, any>, outputPath: string): voi
343
343
  for (const [checkId, report] of Object.entries(reports)) {
344
344
  const filePath = path.join(outputPath, `${checkId}.json`);
345
345
  fs.writeFileSync(filePath, JSON.stringify(report, null, 2), "utf8");
346
- console.log(`✓ ${checkId}: ${filePath}`);
346
+ const title = report.checkTitle || checkId;
347
+ console.log(`✓ ${checkId} ${title}: ${filePath}`);
347
348
  }
348
349
  }
349
350
 
@@ -1925,8 +1926,8 @@ program
1925
1926
  }
1926
1927
  }
1927
1928
 
1928
- // Output JSON to stdout
1929
- if (shouldPrintJson) {
1929
+ // Output JSON to stdout (unless --output is specified, in which case files are written instead)
1930
+ if (shouldPrintJson && !outputPath) {
1930
1931
  console.log(JSON.stringify(reports, null, 2));
1931
1932
  }
1932
1933
 
@@ -4198,11 +4199,11 @@ reports
4198
4199
  .command("list")
4199
4200
  .description("list checkup reports")
4200
4201
  .option("--project-id <id>", "filter by project id", (v: string) => parseInt(v, 10))
4201
- .option("--status <status>", "filter by status (e.g., completed)")
4202
- .option("--limit <n>", "max number of reports to return (default: 20)", (v: string) => parseInt(v, 10))
4202
+ .addOption(new Option("--status <status>", "filter by status (e.g., completed)").hideHelp())
4203
+ .option("--limit <n>", "max number of reports to return (default: 20, max: 100)", (v: string) => { const n = parseInt(v, 10); return Number.isNaN(n) ? 20 : Math.max(1, Math.min(n, 100)); })
4203
4204
  .option("--before <date>", "show reports created before this date (YYYY-MM-DD, DD.MM.YYYY, etc.)")
4204
4205
  .option("--all", "fetch all reports (paginated automatically)")
4205
- .option("--debug", "enable debug output")
4206
+ .addOption(new Option("--debug", "enable debug output").hideHelp())
4206
4207
  .option("--json", "output raw JSON")
4207
4208
  .action(async (opts: { projectId?: number; status?: string; limit?: number; before?: string; all?: boolean; debug?: boolean; json?: boolean }) => {
4208
4209
  const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching reports...");
@@ -4260,7 +4261,7 @@ reports
4260
4261
  .description("list files of a checkup report (metadata only, no content)")
4261
4262
  .option("--type <type>", "filter by file type: json, md")
4262
4263
  .option("--check-id <id>", "filter by check ID (e.g., H002)")
4263
- .option("--debug", "enable debug output")
4264
+ .addOption(new Option("--debug", "enable debug output").hideHelp())
4264
4265
  .option("--json", "output raw JSON")
4265
4266
  .action(async (reportId: string | undefined, opts: { type?: "json" | "md"; checkId?: string; debug?: boolean; json?: boolean }) => {
4266
4267
  const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching report files...");
@@ -4317,7 +4318,7 @@ reports
4317
4318
  .option("--check-id <id>", "filter by check ID (e.g., H002)")
4318
4319
  .option("--formatted", "render markdown with ANSI styling (experimental)")
4319
4320
  .option("-o, --output <dir>", "save files to directory (uses original filenames)")
4320
- .option("--debug", "enable debug output")
4321
+ .addOption(new Option("--debug", "enable debug output").hideHelp())
4321
4322
  .option("--json", "output raw JSON")
4322
4323
  .action(async (reportId: string | undefined, opts: { type?: "json" | "md"; checkId?: string; formatted?: boolean; output?: string; debug?: boolean; json?: boolean }) => {
4323
4324
  const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching report data...");
@@ -13064,7 +13064,7 @@ var {
13064
13064
  // package.json
13065
13065
  var package_default = {
13066
13066
  name: "postgresai",
13067
- version: "0.15.0-dev.2",
13067
+ version: "0.15.0-dev.3",
13068
13068
  description: "postgres_ai CLI",
13069
13069
  license: "Apache-2.0",
13070
13070
  private: false,
@@ -15892,7 +15892,7 @@ var Result = import_lib.default.Result;
15892
15892
  var TypeOverrides = import_lib.default.TypeOverrides;
15893
15893
  var defaults = import_lib.default.defaults;
15894
15894
  // package.json
15895
- var version = "0.15.0-dev.2";
15895
+ var version = "0.15.0-dev.3";
15896
15896
  var package_default2 = {
15897
15897
  name: "postgresai",
15898
15898
  version,
@@ -29543,7 +29543,8 @@ function writeReportFiles(reports, outputPath) {
29543
29543
  for (const [checkId, report] of Object.entries(reports)) {
29544
29544
  const filePath = path5.join(outputPath, `${checkId}.json`);
29545
29545
  fs5.writeFileSync(filePath, JSON.stringify(report, null, 2), "utf8");
29546
- console.log(`\u2713 ${checkId}: ${filePath}`);
29546
+ const title = report.checkTitle || checkId;
29547
+ console.log(`\u2713 ${checkId} ${title}: ${filePath}`);
29547
29548
  }
29548
29549
  }
29549
29550
  function printUploadSummary(summary, projectWasGenerated, useStderr, reports) {
@@ -30780,7 +30781,7 @@ Usage: postgresai checkup ${checkId} postgresql://user@host:5432/dbname
30780
30781
  }
30781
30782
  }
30782
30783
  }
30783
- if (shouldPrintJson) {
30784
+ if (shouldPrintJson && !outputPath) {
30784
30785
  console.log(JSON.stringify(reports, null, 2));
30785
30786
  }
30786
30787
  const hadOutput = shouldPrintJson || shouldConvertMarkdown || outputPath || uploadSummary;
@@ -32591,7 +32592,10 @@ issues.command("update-action-item <actionItemId>").description("update an actio
32591
32592
  }
32592
32593
  });
32593
32594
  var reports = program2.command("reports").description("checkup reports management");
32594
- reports.command("list").description("list checkup reports").option("--project-id <id>", "filter by project id", (v) => parseInt(v, 10)).option("--status <status>", "filter by status (e.g., completed)").option("--limit <n>", "max number of reports to return (default: 20)", (v) => parseInt(v, 10)).option("--before <date>", "show reports created before this date (YYYY-MM-DD, DD.MM.YYYY, etc.)").option("--all", "fetch all reports (paginated automatically)").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (opts) => {
32595
+ reports.command("list").description("list checkup reports").option("--project-id <id>", "filter by project id", (v) => parseInt(v, 10)).addOption(new Option("--status <status>", "filter by status (e.g., completed)").hideHelp()).option("--limit <n>", "max number of reports to return (default: 20, max: 100)", (v) => {
32596
+ const n = parseInt(v, 10);
32597
+ return Number.isNaN(n) ? 20 : Math.max(1, Math.min(n, 100));
32598
+ }).option("--before <date>", "show reports created before this date (YYYY-MM-DD, DD.MM.YYYY, etc.)").option("--all", "fetch all reports (paginated automatically)").addOption(new Option("--debug", "enable debug output").hideHelp()).option("--json", "output raw JSON").action(async (opts) => {
32595
32599
  const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching reports...");
32596
32600
  try {
32597
32601
  const rootOpts = program2.opts();
@@ -32640,7 +32644,7 @@ reports.command("list").description("list checkup reports").option("--project-id
32640
32644
  process.exitCode = 1;
32641
32645
  }
32642
32646
  });
32643
- reports.command("files [reportId]").description("list files of a checkup report (metadata only, no content)").option("--type <type>", "filter by file type: json, md").option("--check-id <id>", "filter by check ID (e.g., H002)").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (reportId, opts) => {
32647
+ reports.command("files [reportId]").description("list files of a checkup report (metadata only, no content)").option("--type <type>", "filter by file type: json, md").option("--check-id <id>", "filter by check ID (e.g., H002)").addOption(new Option("--debug", "enable debug output").hideHelp()).option("--json", "output raw JSON").action(async (reportId, opts) => {
32644
32648
  const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching report files...");
32645
32649
  try {
32646
32650
  const rootOpts = program2.opts();
@@ -32686,7 +32690,7 @@ reports.command("files [reportId]").description("list files of a checkup report
32686
32690
  process.exitCode = 1;
32687
32691
  }
32688
32692
  });
32689
- reports.command("data [reportId]").description("get checkup report file data (includes content)").option("--type <type>", "filter by file type: json, md").option("--check-id <id>", "filter by check ID (e.g., H002)").option("--formatted", "render markdown with ANSI styling (experimental)").option("-o, --output <dir>", "save files to directory (uses original filenames)").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (reportId, opts) => {
32693
+ reports.command("data [reportId]").description("get checkup report file data (includes content)").option("--type <type>", "filter by file type: json, md").option("--check-id <id>", "filter by check ID (e.g., H002)").option("--formatted", "render markdown with ANSI styling (experimental)").option("-o, --output <dir>", "save files to directory (uses original filenames)").addOption(new Option("--debug", "enable debug output").hideHelp()).option("--json", "output raw JSON").action(async (reportId, opts) => {
32690
32694
  const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching report data...");
32691
32695
  try {
32692
32696
  const rootOpts = program2.opts();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresai",
3
- "version": "0.15.0-dev.2",
3
+ "version": "0.15.0-dev.3",
4
4
  "description": "postgres_ai CLI",
5
5
  "license": "Apache-2.0",
6
6
  "private": false,
@@ -1098,6 +1098,34 @@ describe("CLI tests", () => {
1098
1098
  expect(r.stderr).not.toMatch(/connection string required/i);
1099
1099
  expect(r.stderr).not.toMatch(/unknown option/i);
1100
1100
  });
1101
+
1102
+ // Tests for --output flag behavior (suppresses stdout when specified)
1103
+ test("checkup --output option is recognized", () => {
1104
+ const r = runCli(["checkup", "postgresql://test:test@localhost:5432/test", "--no-upload", "--output", "/tmp/test-output"]);
1105
+ // Connection will fail, but option parsing should succeed
1106
+ expect(r.stderr).not.toMatch(/unknown option/i);
1107
+ expect(r.stderr).not.toMatch(/did you mean/i);
1108
+ });
1109
+
1110
+ test("checkup --json --output should NOT output JSON to stdout (writes to files only)", () => {
1111
+ // This is a behavioral test - when --output is specified along with --json,
1112
+ // JSON should only be written to files, not to stdout.
1113
+ // We verify by checking the help text describes this behavior
1114
+ const r = runCli(["checkup", "--help"]);
1115
+ expect(r.status).toBe(0);
1116
+ expect(r.stdout).toMatch(/--output/);
1117
+ expect(r.stdout).toMatch(/--json/);
1118
+ });
1119
+
1120
+ test("checkup --output creates directory if it doesn't exist", () => {
1121
+ const env = { XDG_CONFIG_HOME: "/tmp/postgresai-test-empty-config" };
1122
+ // Use a temp directory that might not exist
1123
+ const tempDir = `/tmp/postgresai-test-output-${Date.now()}`;
1124
+ const r = runCli(["checkup", "postgresql://test:test@localhost:5432/test", "--no-upload", "--json", "--output", tempDir], env);
1125
+ // Connection will fail, but directory creation should be attempted before connection
1126
+ // The error should be about connection, not about directory
1127
+ expect(r.stderr).not.toMatch(/Failed to create output directory/i);
1128
+ });
1101
1129
  });
1102
1130
 
1103
1131
  // Tests for checkup-api module
@@ -299,6 +299,90 @@ describe("CLI reports command group", () => {
299
299
  }
300
300
  });
301
301
 
302
+ test("reports list --limit caps at 100", async () => {
303
+ const api = await startFakeApi();
304
+ try {
305
+ await runCliAsync(
306
+ ["reports", "list", "--limit", "200"],
307
+ isolatedEnv({
308
+ PGAI_API_KEY: "test-key",
309
+ PGAI_API_BASE_URL: api.baseUrl,
310
+ })
311
+ );
312
+
313
+ const req = api.requests.find((x) =>
314
+ x.pathname.endsWith("/checkup_reports")
315
+ );
316
+ expect(req).toBeTruthy();
317
+ expect(req!.searchParams.limit).toBe("100");
318
+ } finally {
319
+ api.stop();
320
+ }
321
+ });
322
+
323
+ test("reports list --limit below cap passes through", async () => {
324
+ const api = await startFakeApi();
325
+ try {
326
+ await runCliAsync(
327
+ ["reports", "list", "--limit", "50"],
328
+ isolatedEnv({
329
+ PGAI_API_KEY: "test-key",
330
+ PGAI_API_BASE_URL: api.baseUrl,
331
+ })
332
+ );
333
+
334
+ const req = api.requests.find((x) =>
335
+ x.pathname.endsWith("/checkup_reports")
336
+ );
337
+ expect(req).toBeTruthy();
338
+ expect(req!.searchParams.limit).toBe("50");
339
+ } finally {
340
+ api.stop();
341
+ }
342
+ });
343
+
344
+ test("reports list --limit with invalid value falls back to default", async () => {
345
+ const api = await startFakeApi();
346
+ try {
347
+ await runCliAsync(
348
+ ["reports", "list", "--limit", "abc"],
349
+ isolatedEnv({
350
+ PGAI_API_KEY: "test-key",
351
+ PGAI_API_BASE_URL: api.baseUrl,
352
+ })
353
+ );
354
+
355
+ const req = api.requests.find((x) =>
356
+ x.pathname.endsWith("/checkup_reports")
357
+ );
358
+ expect(req).toBeTruthy();
359
+ expect(req!.searchParams.limit).toBe("20");
360
+ } finally {
361
+ api.stop();
362
+ }
363
+ });
364
+
365
+ test("reports list --limit with negative value clamps to 1", async () => {
366
+ const api = await startFakeApi();
367
+ try {
368
+ await runCliAsync(
369
+ ["reports", "list", "--limit", "-5"],
370
+ isolatedEnv({
371
+ PGAI_API_KEY: "test-key",
372
+ PGAI_API_BASE_URL: api.baseUrl,
373
+ })
374
+ );
375
+
376
+ const req = api.requests.find((x) =>
377
+ x.pathname.endsWith("/checkup_reports")
378
+ );
379
+ expect(req).toBeTruthy();
380
+ expect(req!.searchParams.limit).toBe("1");
381
+ } finally {
382
+ api.stop();
383
+ }
384
+ });
385
+
302
386
  test("reports files succeeds against a fake API", async () => {
303
387
  const api = await startFakeApi();
304
388
  try {