@indiekitai/pg-dash 0.3.3 → 0.3.4
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/README.md +88 -11
- package/README.zh-CN.md +87 -10
- package/dist/cli.js +149 -27
- package/dist/cli.js.map +1 -1
- package/dist/mcp.js +225 -8
- package/dist/mcp.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,22 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
# pg-dash
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**The AI-native PostgreSQL health checker.** One command to audit your database, 14 MCP tools for AI-assisted optimization, CI integration for automated checks.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Not another monitoring dashboard — pg-dash is built to fit into your **AI coding workflow**:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Developer writes a migration → CI runs pg-dash check →
|
|
11
|
+
Finds missing indexes → MCP tool suggests fix → PR comment
|
|
12
|
+
```
|
|
8
13
|
|
|
9
14
|
```bash
|
|
10
|
-
|
|
15
|
+
# One-shot health check
|
|
16
|
+
npx @indiekitai/pg-dash check postgres://user:pass@host/db
|
|
17
|
+
|
|
18
|
+
# AI assistant (Claude/Cursor) via MCP
|
|
19
|
+
pg-dash-mcp postgres://user:pass@host/db
|
|
20
|
+
|
|
21
|
+
# CI pipeline with diff
|
|
22
|
+
npx @indiekitai/pg-dash check $DATABASE_URL --ci --diff --format md
|
|
11
23
|
```
|
|
12
24
|
|
|
13
|
-
##
|
|
25
|
+
## Philosophy
|
|
14
26
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
**Developer tools are use-and-go.** You don't stare at a PostgreSQL dashboard all day. You run a check, fix the issues, and move on. pg-dash embraces this:
|
|
28
|
+
|
|
29
|
+
- **Health check** → Find problems, get actionable SQL fixes, done
|
|
30
|
+
- **MCP tools** → Let your AI assistant query and fix your database directly (unique — pganalyze/pgwatch don't have this)
|
|
31
|
+
- **CI integration** → Catch issues automatically on every migration, not when production is on fire
|
|
32
|
+
- **Smart diff** → See what changed since last run, track your progress
|
|
33
|
+
|
|
34
|
+
The Dashboard is there when you need it. But the real power is in the CLI, MCP, and CI.
|
|
35
|
+
|
|
36
|
+
## Why pg-dash?
|
|
37
|
+
|
|
38
|
+
| Tool | Price | Setup | AI-native | CI-ready |
|
|
39
|
+
|------|-------|-------|-----------|----------|
|
|
40
|
+
| pganalyze | $149+/mo | SaaS signup | ❌ | ❌ |
|
|
41
|
+
| Grafana+Prometheus | Free | 3 services | ❌ | ❌ |
|
|
42
|
+
| pgAdmin | Free | Complex UI | ❌ | ❌ |
|
|
43
|
+
| **pg-dash** | **Free** | **One command** | **14 MCP tools** | **`--ci --diff`** |
|
|
21
44
|
|
|
22
45
|
## Features
|
|
23
46
|
|
|
@@ -144,7 +167,61 @@ pg-dash-mcp postgres://user:pass@host/db
|
|
|
144
167
|
PG_DASH_CONNECTION_STRING=postgres://... pg-dash-mcp
|
|
145
168
|
```
|
|
146
169
|
|
|
147
|
-
Available
|
|
170
|
+
### Available Tools (14)
|
|
171
|
+
|
|
172
|
+
| Tool | Description |
|
|
173
|
+
|------|-------------|
|
|
174
|
+
| `pg_dash_overview` | Database overview (version, uptime, size, connections) |
|
|
175
|
+
| `pg_dash_health` | Health advisor report with score, grade, and issues |
|
|
176
|
+
| `pg_dash_tables` | List all tables with sizes and row counts |
|
|
177
|
+
| `pg_dash_table_detail` | Detailed info about a specific table |
|
|
178
|
+
| `pg_dash_activity` | Current database activity (active queries, connections) |
|
|
179
|
+
| `pg_dash_schema_changes` | Recent schema changes |
|
|
180
|
+
| `pg_dash_fix` | Execute a safe fix (VACUUM, ANALYZE, REINDEX, etc.) |
|
|
181
|
+
| `pg_dash_alerts` | Alert history |
|
|
182
|
+
| `pg_dash_explain` | Run EXPLAIN ANALYZE on a SELECT query (read-only) |
|
|
183
|
+
| `pg_dash_batch_fix` | Get batch fix SQL for issues, optionally filtered by category |
|
|
184
|
+
| `pg_dash_slow_queries` | Top slow queries from pg_stat_statements |
|
|
185
|
+
| `pg_dash_table_sizes` | Table sizes with data/index breakdown (top 30) |
|
|
186
|
+
| `pg_dash_export` | Export full health report (JSON or Markdown) |
|
|
187
|
+
| `pg_dash_diff` | Compare current health with last saved snapshot |
|
|
188
|
+
|
|
189
|
+
## CI Integration
|
|
190
|
+
|
|
191
|
+
### GitHub Actions
|
|
192
|
+
|
|
193
|
+
Add `--ci` and `--diff` flags to integrate with CI pipelines:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
# GitHub Actions annotations (::error::, ::warning::)
|
|
197
|
+
pg-dash check postgres://... --ci
|
|
198
|
+
|
|
199
|
+
# Markdown report for PR comments
|
|
200
|
+
pg-dash check postgres://... --ci --format md
|
|
201
|
+
|
|
202
|
+
# Compare with previous run
|
|
203
|
+
pg-dash check postgres://... --diff
|
|
204
|
+
|
|
205
|
+
# All together
|
|
206
|
+
pg-dash check postgres://... --ci --diff --format md
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Sample workflow (`.github/workflows/pg-check.yml`):
|
|
210
|
+
|
|
211
|
+
```yaml
|
|
212
|
+
name: Database Health Check
|
|
213
|
+
on:
|
|
214
|
+
push:
|
|
215
|
+
paths: ['migrations/**', 'prisma/**', 'drizzle/**']
|
|
216
|
+
schedule:
|
|
217
|
+
- cron: '0 8 * * 1' # Weekly Monday 8am
|
|
218
|
+
jobs:
|
|
219
|
+
check:
|
|
220
|
+
runs-on: ubuntu-latest
|
|
221
|
+
steps:
|
|
222
|
+
- uses: actions/checkout@v4
|
|
223
|
+
- run: npx @indiekitai/pg-dash check ${{ secrets.DATABASE_URL }} --ci --diff --format md
|
|
224
|
+
```
|
|
148
225
|
|
|
149
226
|
## Health Checks
|
|
150
227
|
|
package/README.zh-CN.md
CHANGED
|
@@ -2,22 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
# pg-dash
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**AI 原生的 PostgreSQL 健康检查工具。** 一条命令审计数据库,14 个 MCP 工具让 AI 帮你优化,CI 集成自动检查。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
不是又一个监控面板 —— pg-dash 是为 **AI 编程工作流** 设计的:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
开发者写了一个 migration → CI 跑 pg-dash check →
|
|
11
|
+
发现缺失索引 → MCP 工具建议修复 → PR comment
|
|
12
|
+
```
|
|
8
13
|
|
|
9
14
|
```bash
|
|
10
|
-
|
|
15
|
+
# 一次性健康检查
|
|
16
|
+
npx @indiekitai/pg-dash check postgres://user:pass@host/db
|
|
17
|
+
|
|
18
|
+
# AI 助手(Claude/Cursor)通过 MCP 调用
|
|
19
|
+
pg-dash-mcp postgres://user:pass@host/db
|
|
20
|
+
|
|
21
|
+
# CI 流水线 + 差异对比
|
|
22
|
+
npx @indiekitai/pg-dash check $DATABASE_URL --ci --diff --format md
|
|
11
23
|
```
|
|
12
24
|
|
|
25
|
+
## 设计理念
|
|
26
|
+
|
|
27
|
+
**开发者工具就是用完即走的。** 你不会整天盯着 PostgreSQL 监控面板。你跑一次检查,修掉问题,然后继续干活。pg-dash 就是为此设计的:
|
|
28
|
+
|
|
29
|
+
- **健康检查** → 发现问题,拿到可执行的 SQL 修复建议,搞定
|
|
30
|
+
- **MCP 工具** → 让 AI 助手直接查询和修复你的数据库(独一份 —— pganalyze/pgwatch 都没有)
|
|
31
|
+
- **CI 集成** → 每次 migration 自动检查,不要等到生产环境出事
|
|
32
|
+
- **智能 diff** → 看到上次以来的变化,追踪改进进度
|
|
33
|
+
|
|
34
|
+
Dashboard 需要时可以用。但真正的核心能力在 CLI、MCP 和 CI。
|
|
35
|
+
|
|
13
36
|
## 为什么选 pg-dash?
|
|
14
37
|
|
|
15
|
-
| 工具 | 价格 | 部署 |
|
|
16
|
-
|
|
17
|
-
| pganalyze | $149+/月 | SaaS 注册 |
|
|
18
|
-
| Grafana+Prometheus | 免费 | 配置 3 个服务 |
|
|
19
|
-
| pgAdmin | 免费 | 界面复杂 |
|
|
20
|
-
| **pg-dash** | **免费** | **一条命令** |
|
|
38
|
+
| 工具 | 价格 | 部署 | AI 原生 | CI 就绪 |
|
|
39
|
+
|------|------|------|---------|---------|
|
|
40
|
+
| pganalyze | $149+/月 | SaaS 注册 | ❌ | ❌ |
|
|
41
|
+
| Grafana+Prometheus | 免费 | 配置 3 个服务 | ❌ | ❌ |
|
|
42
|
+
| pgAdmin | 免费 | 界面复杂 | ❌ | ❌ |
|
|
43
|
+
| **pg-dash** | **免费** | **一条命令** | **14 个 MCP 工具** | **`--ci --diff`** |
|
|
21
44
|
|
|
22
45
|
## 功能
|
|
23
46
|
|
|
@@ -144,7 +167,61 @@ pg-dash-mcp postgres://user:pass@host/db
|
|
|
144
167
|
PG_DASH_CONNECTION_STRING=postgres://... pg-dash-mcp
|
|
145
168
|
```
|
|
146
169
|
|
|
147
|
-
|
|
170
|
+
### 可用工具(14 个)
|
|
171
|
+
|
|
172
|
+
| 工具 | 描述 |
|
|
173
|
+
|------|------|
|
|
174
|
+
| `pg_dash_overview` | 数据库概览(版本、运行时间、大小、连接数) |
|
|
175
|
+
| `pg_dash_health` | 健康报告(评分、等级、问题列表) |
|
|
176
|
+
| `pg_dash_tables` | 所有表的大小和行数 |
|
|
177
|
+
| `pg_dash_table_detail` | 单个表的详细信息 |
|
|
178
|
+
| `pg_dash_activity` | 当前活动(查询、连接) |
|
|
179
|
+
| `pg_dash_schema_changes` | 最近的 schema 变更 |
|
|
180
|
+
| `pg_dash_fix` | 执行安全修复(VACUUM、ANALYZE、REINDEX 等) |
|
|
181
|
+
| `pg_dash_alerts` | 告警历史 |
|
|
182
|
+
| `pg_dash_explain` | 对 SELECT 查询运行 EXPLAIN ANALYZE(只读) |
|
|
183
|
+
| `pg_dash_batch_fix` | 获取批量修复 SQL,可按类别过滤 |
|
|
184
|
+
| `pg_dash_slow_queries` | pg_stat_statements 中的慢查询 |
|
|
185
|
+
| `pg_dash_table_sizes` | 表大小(数据/索引拆分,前 30) |
|
|
186
|
+
| `pg_dash_export` | 导出完整健康报告(JSON 或 Markdown) |
|
|
187
|
+
| `pg_dash_diff` | 与上次快照对比当前健康状态 |
|
|
188
|
+
|
|
189
|
+
## CI 集成
|
|
190
|
+
|
|
191
|
+
### GitHub Actions
|
|
192
|
+
|
|
193
|
+
使用 `--ci` 和 `--diff` 标志集成到 CI 流水线:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
# GitHub Actions 注解(::error::、::warning::)
|
|
197
|
+
pg-dash check postgres://... --ci
|
|
198
|
+
|
|
199
|
+
# 适合 PR 评论的 Markdown 报告
|
|
200
|
+
pg-dash check postgres://... --ci --format md
|
|
201
|
+
|
|
202
|
+
# 与上次运行对比
|
|
203
|
+
pg-dash check postgres://... --diff
|
|
204
|
+
|
|
205
|
+
# 全部组合
|
|
206
|
+
pg-dash check postgres://... --ci --diff --format md
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
示例工作流(`.github/workflows/pg-check.yml`):
|
|
210
|
+
|
|
211
|
+
```yaml
|
|
212
|
+
name: Database Health Check
|
|
213
|
+
on:
|
|
214
|
+
push:
|
|
215
|
+
paths: ['migrations/**', 'prisma/**', 'drizzle/**']
|
|
216
|
+
schedule:
|
|
217
|
+
- cron: '0 8 * * 1' # 每周一早 8 点
|
|
218
|
+
jobs:
|
|
219
|
+
check:
|
|
220
|
+
runs-on: ubuntu-latest
|
|
221
|
+
steps:
|
|
222
|
+
- uses: actions/checkout@v4
|
|
223
|
+
- run: npx @indiekitai/pg-dash check ${{ secrets.DATABASE_URL }} --ci --diff --format md
|
|
224
|
+
```
|
|
148
225
|
|
|
149
226
|
## 健康检查
|
|
150
227
|
|
package/dist/cli.js
CHANGED
|
@@ -754,6 +754,54 @@ var init_advisor = __esm({
|
|
|
754
754
|
}
|
|
755
755
|
});
|
|
756
756
|
|
|
757
|
+
// src/server/snapshot.ts
|
|
758
|
+
var snapshot_exports = {};
|
|
759
|
+
__export(snapshot_exports, {
|
|
760
|
+
diffSnapshots: () => diffSnapshots2,
|
|
761
|
+
loadSnapshot: () => loadSnapshot,
|
|
762
|
+
saveSnapshot: () => saveSnapshot
|
|
763
|
+
});
|
|
764
|
+
import fs4 from "fs";
|
|
765
|
+
import path4 from "path";
|
|
766
|
+
function saveSnapshot(dataDir, result) {
|
|
767
|
+
fs4.mkdirSync(dataDir, { recursive: true });
|
|
768
|
+
const snapshot = { timestamp: (/* @__PURE__ */ new Date()).toISOString(), result };
|
|
769
|
+
fs4.writeFileSync(path4.join(dataDir, SNAPSHOT_FILE), JSON.stringify(snapshot, null, 2));
|
|
770
|
+
}
|
|
771
|
+
function loadSnapshot(dataDir) {
|
|
772
|
+
const filePath = path4.join(dataDir, SNAPSHOT_FILE);
|
|
773
|
+
if (!fs4.existsSync(filePath)) return null;
|
|
774
|
+
try {
|
|
775
|
+
return JSON.parse(fs4.readFileSync(filePath, "utf-8"));
|
|
776
|
+
} catch {
|
|
777
|
+
return null;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
function diffSnapshots2(prev, current) {
|
|
781
|
+
const prevIds = new Set(prev.issues.map((i) => i.id));
|
|
782
|
+
const currIds = new Set(current.issues.map((i) => i.id));
|
|
783
|
+
const newIssues = current.issues.filter((i) => !prevIds.has(i.id));
|
|
784
|
+
const resolvedIssues = prev.issues.filter((i) => !currIds.has(i.id));
|
|
785
|
+
const unchanged = current.issues.filter((i) => prevIds.has(i.id));
|
|
786
|
+
return {
|
|
787
|
+
scoreDelta: current.score - prev.score,
|
|
788
|
+
previousScore: prev.score,
|
|
789
|
+
currentScore: current.score,
|
|
790
|
+
previousGrade: prev.grade,
|
|
791
|
+
currentGrade: current.grade,
|
|
792
|
+
newIssues,
|
|
793
|
+
resolvedIssues,
|
|
794
|
+
unchanged
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
var SNAPSHOT_FILE;
|
|
798
|
+
var init_snapshot = __esm({
|
|
799
|
+
"src/server/snapshot.ts"() {
|
|
800
|
+
"use strict";
|
|
801
|
+
SNAPSHOT_FILE = "last-check.json";
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
|
|
757
805
|
// src/cli.ts
|
|
758
806
|
import { parseArgs } from "util";
|
|
759
807
|
|
|
@@ -2983,8 +3031,8 @@ async function startServer(opts) {
|
|
|
2983
3031
|
}
|
|
2984
3032
|
|
|
2985
3033
|
// src/cli.ts
|
|
2986
|
-
import
|
|
2987
|
-
import
|
|
3034
|
+
import fs5 from "fs";
|
|
3035
|
+
import path5 from "path";
|
|
2988
3036
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2989
3037
|
process.on("uncaughtException", (err) => {
|
|
2990
3038
|
console.error("Uncaught exception:", err);
|
|
@@ -3018,13 +3066,15 @@ var { values, positionals } = parseArgs({
|
|
|
3018
3066
|
help: { type: "boolean", short: "h" },
|
|
3019
3067
|
version: { type: "boolean", short: "v" },
|
|
3020
3068
|
threshold: { type: "string" },
|
|
3021
|
-
format: { type: "string", short: "f" }
|
|
3069
|
+
format: { type: "string", short: "f" },
|
|
3070
|
+
ci: { type: "boolean", default: false },
|
|
3071
|
+
diff: { type: "boolean", default: false }
|
|
3022
3072
|
}
|
|
3023
3073
|
});
|
|
3024
3074
|
if (values.version) {
|
|
3025
3075
|
try {
|
|
3026
|
-
const __dirname2 =
|
|
3027
|
-
const pkg = JSON.parse(
|
|
3076
|
+
const __dirname2 = path5.dirname(fileURLToPath2(import.meta.url));
|
|
3077
|
+
const pkg = JSON.parse(fs5.readFileSync(path5.resolve(__dirname2, "../package.json"), "utf-8"));
|
|
3028
3078
|
console.log(`pg-dash v${pkg.version}`);
|
|
3029
3079
|
} catch {
|
|
3030
3080
|
console.log("pg-dash v0.1.0");
|
|
@@ -3064,6 +3114,8 @@ Options:
|
|
|
3064
3114
|
--long-query-threshold <min> Long query threshold in minutes (default: 5)
|
|
3065
3115
|
--threshold <score> Health score threshold for check command (default: 70)
|
|
3066
3116
|
-f, --format <fmt> Output format: text|json|md (default: text)
|
|
3117
|
+
--ci Output GitHub Actions compatible annotations
|
|
3118
|
+
--diff Compare with previous run (saves to ~/.pg-dash/last-check.json)
|
|
3067
3119
|
-v, --version Show version
|
|
3068
3120
|
-h, --help Show this help
|
|
3069
3121
|
|
|
@@ -3094,50 +3146,120 @@ if (subcommand === "check") {
|
|
|
3094
3146
|
const connectionString = resolveConnectionString(1);
|
|
3095
3147
|
const threshold = parseInt(values.threshold || "70", 10);
|
|
3096
3148
|
const format = values.format || "text";
|
|
3149
|
+
const ci = values.ci || false;
|
|
3150
|
+
const useDiff = values.diff || false;
|
|
3097
3151
|
const { Pool: Pool2 } = await import("pg");
|
|
3098
3152
|
const { getAdvisorReport: getAdvisorReport2 } = await Promise.resolve().then(() => (init_advisor(), advisor_exports));
|
|
3153
|
+
const { saveSnapshot: saveSnapshot2, loadSnapshot: loadSnapshot2, diffSnapshots: diffSnapshots3 } = await Promise.resolve().then(() => (init_snapshot(), snapshot_exports));
|
|
3154
|
+
const os4 = await import("os");
|
|
3099
3155
|
const pool = new Pool2({ connectionString });
|
|
3156
|
+
const checkDataDir = values["data-dir"] || path5.join(os4.homedir(), ".pg-dash");
|
|
3100
3157
|
try {
|
|
3101
3158
|
const lqt = parseInt(values["long-query-threshold"] || process.env.PG_DASH_LONG_QUERY_THRESHOLD || "5", 10);
|
|
3102
3159
|
const report = await getAdvisorReport2(pool, lqt);
|
|
3160
|
+
let diff = null;
|
|
3161
|
+
if (useDiff) {
|
|
3162
|
+
const prev = loadSnapshot2(checkDataDir);
|
|
3163
|
+
if (prev) {
|
|
3164
|
+
diff = diffSnapshots3(prev.result, report);
|
|
3165
|
+
}
|
|
3166
|
+
saveSnapshot2(checkDataDir, report);
|
|
3167
|
+
}
|
|
3103
3168
|
if (format === "json") {
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
console.log(
|
|
3169
|
+
const output = { ...report };
|
|
3170
|
+
if (diff) output.diff = diff;
|
|
3171
|
+
console.log(JSON.stringify(output, null, 2));
|
|
3172
|
+
} else if (format === "md" || ci && format !== "text") {
|
|
3173
|
+
console.log(`## \u{1F3E5} pg-dash Health Report
|
|
3107
3174
|
`);
|
|
3108
|
-
|
|
3175
|
+
if (diff) {
|
|
3176
|
+
const sign = diff.scoreDelta >= 0 ? "+" : "";
|
|
3177
|
+
console.log(`**Score: ${diff.previousScore} \u2192 ${report.score} (${sign}${diff.scoreDelta})**
|
|
3109
3178
|
`);
|
|
3110
|
-
|
|
3179
|
+
} else {
|
|
3180
|
+
console.log(`**Score: ${report.score}/100 (${report.grade})**
|
|
3111
3181
|
`);
|
|
3112
|
-
|
|
3182
|
+
}
|
|
3183
|
+
console.log(`| Category | Score | Grade | Issues |`);
|
|
3113
3184
|
console.log(`|----------|-------|-------|--------|`);
|
|
3114
3185
|
for (const [cat, b] of Object.entries(report.breakdown)) {
|
|
3115
|
-
console.log(`| ${cat} | ${b.
|
|
3186
|
+
console.log(`| ${cat} | ${b.score} | ${b.grade} | ${b.count} |`);
|
|
3187
|
+
}
|
|
3188
|
+
if (diff) {
|
|
3189
|
+
if (diff.resolvedIssues.length > 0) {
|
|
3190
|
+
console.log(`
|
|
3191
|
+
### \u2705 Resolved (${diff.resolvedIssues.length})`);
|
|
3192
|
+
for (const i of diff.resolvedIssues) console.log(`- ~~${i.title}~~`);
|
|
3193
|
+
}
|
|
3194
|
+
if (diff.newIssues.length > 0) {
|
|
3195
|
+
console.log(`
|
|
3196
|
+
### \u{1F195} New Issues (${diff.newIssues.length})`);
|
|
3197
|
+
for (const i of diff.newIssues) {
|
|
3198
|
+
const icon = i.severity === "critical" ? "\u{1F534}" : i.severity === "warning" ? "\u{1F7E1}" : "\u{1F535}";
|
|
3199
|
+
console.log(`- ${icon} [${i.severity}] ${i.title}`);
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3116
3202
|
}
|
|
3117
3203
|
if (report.issues.length > 0) {
|
|
3118
3204
|
console.log(`
|
|
3119
|
-
### Issues (${report.issues.length})
|
|
3205
|
+
### \u26A0\uFE0F Issues (${report.issues.length})
|
|
3120
3206
|
`);
|
|
3121
3207
|
for (const issue of report.issues) {
|
|
3122
|
-
const
|
|
3123
|
-
console.log(
|
|
3124
|
-
`);
|
|
3125
|
-
console.log(`${issue.description}
|
|
3126
|
-
`);
|
|
3127
|
-
console.log(`**Fix**:
|
|
3128
|
-
\`\`\`sql
|
|
3129
|
-
${issue.fix}
|
|
3130
|
-
\`\`\`
|
|
3131
|
-
`);
|
|
3208
|
+
const sev = issue.severity === "critical" ? "error" : issue.severity === "warning" ? "warning" : "notice";
|
|
3209
|
+
console.log(`- [${sev}] ${issue.title}`);
|
|
3132
3210
|
}
|
|
3133
3211
|
} else {
|
|
3134
3212
|
console.log(`
|
|
3135
3213
|
\u2705 No issues found!`);
|
|
3136
3214
|
}
|
|
3137
|
-
|
|
3215
|
+
if (report.batchFixes.length > 0) {
|
|
3216
|
+
console.log(`
|
|
3217
|
+
### \u{1F527} Batch Fixes
|
|
3218
|
+
`);
|
|
3219
|
+
console.log("```sql");
|
|
3220
|
+
for (const fix of report.batchFixes) {
|
|
3221
|
+
console.log(`-- ${fix.title}`);
|
|
3222
|
+
console.log(fix.sql);
|
|
3223
|
+
}
|
|
3224
|
+
console.log("```");
|
|
3225
|
+
}
|
|
3226
|
+
} else if (ci) {
|
|
3227
|
+
for (const issue of report.issues) {
|
|
3228
|
+
const level = issue.severity === "critical" ? "error" : issue.severity === "warning" ? "warning" : "notice";
|
|
3229
|
+
console.log(`::${level}::${issue.title}: ${issue.description}`);
|
|
3230
|
+
}
|
|
3138
3231
|
console.log(`
|
|
3232
|
+
Health Score: ${report.score}/100 (${report.grade})`);
|
|
3233
|
+
for (const [cat, b] of Object.entries(report.breakdown)) {
|
|
3234
|
+
console.log(` ${cat.padEnd(14)} ${b.grade} (${b.score}/100) \u2014 ${b.count} issue${b.count !== 1 ? "s" : ""}`);
|
|
3235
|
+
}
|
|
3236
|
+
if (diff) {
|
|
3237
|
+
const sign = diff.scoreDelta >= 0 ? "+" : "";
|
|
3238
|
+
console.log(`
|
|
3239
|
+
Score: ${diff.previousScore} \u2192 ${report.score} (${sign}${diff.scoreDelta})`);
|
|
3240
|
+
console.log(`Resolved: ${diff.resolvedIssues.length} issues`);
|
|
3241
|
+
console.log(`New: ${diff.newIssues.length} issues`);
|
|
3242
|
+
}
|
|
3243
|
+
} else {
|
|
3244
|
+
if (diff) {
|
|
3245
|
+
const sign = diff.scoreDelta >= 0 ? "+" : "";
|
|
3246
|
+
console.log(`
|
|
3247
|
+
Score: ${diff.previousScore} \u2192 ${report.score} (${sign}${diff.scoreDelta})
|
|
3248
|
+
`);
|
|
3249
|
+
if (diff.resolvedIssues.length > 0) {
|
|
3250
|
+
console.log(` \u2705 Resolved: ${diff.resolvedIssues.length} issues`);
|
|
3251
|
+
for (const i of diff.resolvedIssues) console.log(` - ${i.title}`);
|
|
3252
|
+
}
|
|
3253
|
+
if (diff.newIssues.length > 0) {
|
|
3254
|
+
console.log(` \u{1F195} New: ${diff.newIssues.length} issues`);
|
|
3255
|
+
for (const i of diff.newIssues) console.log(` - ${i.title}`);
|
|
3256
|
+
}
|
|
3257
|
+
console.log();
|
|
3258
|
+
} else {
|
|
3259
|
+
console.log(`
|
|
3139
3260
|
Health Score: ${report.score}/100 (Grade: ${report.grade})
|
|
3140
3261
|
`);
|
|
3262
|
+
}
|
|
3141
3263
|
for (const [cat, b] of Object.entries(report.breakdown)) {
|
|
3142
3264
|
console.log(` ${cat.padEnd(14)} ${b.grade} (${b.score}/100) \u2014 ${b.count} issue${b.count !== 1 ? "s" : ""}`);
|
|
3143
3265
|
}
|
|
@@ -3161,9 +3283,9 @@ ${issue.fix}
|
|
|
3161
3283
|
}
|
|
3162
3284
|
} else if (subcommand === "schema-diff") {
|
|
3163
3285
|
const connectionString = resolveConnectionString(1);
|
|
3164
|
-
const dataDir = values["data-dir"] ||
|
|
3165
|
-
const schemaDbPath =
|
|
3166
|
-
if (!
|
|
3286
|
+
const dataDir = values["data-dir"] || path5.join((await import("os")).homedir(), ".pg-dash");
|
|
3287
|
+
const schemaDbPath = path5.join(dataDir, "schema.db");
|
|
3288
|
+
if (!fs5.existsSync(schemaDbPath)) {
|
|
3167
3289
|
console.error("No schema tracking data found. Run pg-dash server first to collect schema snapshots.");
|
|
3168
3290
|
process.exit(1);
|
|
3169
3291
|
}
|