🌐 AI搜索 & 代理 主页
Skip to content

Commit 18e166b

Browse files
author
Nik Samokhvalov
committed
feat(cli): add init dry-run and print-sql
- Add --print-sql and --dry-run options - Redact passwords by default (override with --show-secrets)
1 parent 74de286 commit 18e166b

File tree

3 files changed

+60
-0
lines changed

3 files changed

+60
-0
lines changed

cli/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,20 @@ Optional permissions (RDS/self-managed extras from the root `README.md`) are ena
6767
npx postgresai init postgresql://admin@host:5432/dbname --skip-optional-permissions
6868
```
6969

70+
### Print SQL / dry run
71+
72+
To see what SQL would be executed (passwords redacted by default):
73+
74+
```bash
75+
npx postgresai init postgresql://admin@host:5432/dbname --print-sql
76+
```
77+
78+
To print SQL and exit without applying anything:
79+
80+
```bash
81+
npx postgresai init postgresql://admin@host:5432/dbname --dry-run
82+
```
83+
7084
## Quick start
7185

7286
### Authentication

cli/bin/postgres-ai.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ program
129129
.option("--monitoring-user <name>", "Monitoring role name to create/update", "postgres_ai_mon")
130130
.option("--password <password>", "Monitoring role password (overrides PGAI_MON_PASSWORD)")
131131
.option("--skip-optional-permissions", "Skip optional permissions (RDS/self-managed extras)", false)
132+
.option("--print-sql", "Print SQL steps before applying (passwords redacted by default)", false)
133+
.option("--show-secrets", "When printing SQL, do not redact secrets (DANGEROUS)", false)
134+
.option("--dry-run", "Print SQL steps and exit without applying changes", false)
132135
.action(async (conn: string | undefined, opts: {
133136
dbUrl?: string;
134137
host?: string;
@@ -139,6 +142,9 @@ program
139142
monitoringUser: string;
140143
password?: string;
141144
skipOptionalPermissions?: boolean;
145+
printSql?: boolean;
146+
showSecrets?: boolean;
147+
dryRun?: boolean;
142148
}) => {
143149
let adminConn;
144150
try {
@@ -165,6 +171,8 @@ program
165171
console.log(`Monitoring user: ${opts.monitoringUser}`);
166172
console.log(`Optional permissions: ${includeOptionalPermissions ? "enabled" : "skipped"}`);
167173

174+
const shouldPrintSql = !!opts.printSql || !!opts.dryRun;
175+
168176
// Use native pg client instead of requiring psql to be installed
169177
const { Client } = require("pg");
170178
const client = new Client(adminConn.clientConfig);
@@ -210,6 +218,30 @@ program
210218
roleExists,
211219
});
212220

221+
if (shouldPrintSql) {
222+
const redact = !opts.showSecrets;
223+
const redactPasswords = (sql: string): string => {
224+
if (!redact) return sql;
225+
// Replace PASSWORD '<literal>' (handles doubled quotes inside).
226+
return sql.replace(/password\s+'(?:''|[^'])*'/gi, "password '<redacted>'");
227+
};
228+
229+
console.log("\n--- SQL plan ---");
230+
for (const step of plan.steps) {
231+
console.log(`\n-- ${step.name}${step.optional ? " (optional)" : ""}`);
232+
console.log(redactPasswords(step.sql));
233+
}
234+
console.log("\n--- end SQL plan ---\n");
235+
if (redact) {
236+
console.log("Note: passwords are redacted in the printed SQL (use --show-secrets to print them).");
237+
}
238+
}
239+
240+
if (opts.dryRun) {
241+
console.log("✓ dry-run completed (no changes were applied)");
242+
return;
243+
}
244+
213245
const { applied, skippedOptional } = await applyInitPlan({ client, plan });
214246

215247
console.log("✓ init completed");

cli/test/init.test.cjs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,18 @@ test("resolveAdminConnection rejects invalid psql-like port", () => {
8787
);
8888
});
8989

90+
test("print-sql redaction regex matches password literal with embedded quotes", async () => {
91+
const plan = await init.buildInitPlan({
92+
database: "mydb",
93+
monitoringUser: "postgres_ai_mon",
94+
monitoringPassword: "pa'ss",
95+
includeOptionalPermissions: false,
96+
roleExists: false,
97+
});
98+
const step = plan.steps.find((s) => s.name === "create monitoring user");
99+
assert.ok(step);
100+
const redacted = step.sql.replace(/password\\s+'(?:''|[^'])*'/gi, "password '<redacted>'");
101+
assert.match(redacted, /password '<redacted>'/i);
102+
});
103+
90104

0 commit comments

Comments
 (0)