@carllee1983/dbcli 1.44.1 → 1.45.0
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/rules/dbcli.mdc +30 -3
- package/.cursor/skills/dbcli/reference.md +100 -2
- package/.cursor-plugin/plugin.json +1 -1
- package/.github/skills/dbcli/SKILL.md +30 -3
- package/.github/skills/dbcli/reference.md +100 -2
- package/CHANGELOG.md +12 -0
- package/README.md +13 -0
- package/README.zh-TW.md +13 -0
- package/assets/SKILL.md +30 -3
- package/assets/SKILL.zh-TW.md +26 -3
- package/assets/reference.md +100 -2
- package/dist/agent-core.mjs +15 -4
- package/dist/cli.mjs +1473 -489
- package/dist/core.d.ts +48 -3
- package/dist/core.mjs +336 -85
- package/gemini-extension.json +1 -1
- package/package.json +2 -1
- package/plugins/dbcli-agent/.codex-plugin/plugin.json +1 -1
- package/plugins/dbcli-agent/skills/dbcli/SKILL.md +30 -3
- package/plugins/dbcli-agent/skills/dbcli/reference.md +100 -2
- package/skills/dbcli/SKILL.md +30 -3
- package/skills/dbcli/reference.md +100 -2
- package/.cursor/skills/dbcli/SKILL.md +0 -106
package/dist/cli.mjs
CHANGED
|
@@ -52,7 +52,7 @@ var package_default;
|
|
|
52
52
|
var init_package = __esm(() => {
|
|
53
53
|
package_default = {
|
|
54
54
|
name: "@carllee1983/dbcli",
|
|
55
|
-
version: "1.
|
|
55
|
+
version: "1.45.0",
|
|
56
56
|
description: "Database CLI for AI agents",
|
|
57
57
|
type: "module",
|
|
58
58
|
publishConfig: {
|
|
@@ -125,6 +125,7 @@ var init_package = __esm(() => {
|
|
|
125
125
|
"test:integration": "bun test tests/integration",
|
|
126
126
|
"test:docker": "docker compose -f docker-compose.test.yml up -d --wait && bun test tests/integration/adapters; docker compose -f docker-compose.test.yml down",
|
|
127
127
|
"docs:check": "bun run scripts/check-user-docs.ts",
|
|
128
|
+
"contract:check": "bun run scripts/check-cli-contract.ts",
|
|
128
129
|
"skill:check": "bun run scripts/check-skill-parity.ts",
|
|
129
130
|
"platform:check": "bun run scripts/check-platform-parity.ts",
|
|
130
131
|
"agent-core:check": "bun run scripts/check-agent-core-purity.ts",
|
|
@@ -2409,6 +2410,308 @@ var init_version_check = __esm(() => {
|
|
|
2409
2410
|
STALE_THRESHOLD_MS = 24 * 60 * 60 * 1000;
|
|
2410
2411
|
});
|
|
2411
2412
|
|
|
2413
|
+
// src/agent-core/errors.ts
|
|
2414
|
+
var ConfigError;
|
|
2415
|
+
var init_errors = __esm(() => {
|
|
2416
|
+
ConfigError = class ConfigError extends Error {
|
|
2417
|
+
constructor(message) {
|
|
2418
|
+
super(message);
|
|
2419
|
+
this.name = "ConfigError";
|
|
2420
|
+
if (Error.captureStackTrace)
|
|
2421
|
+
Error.captureStackTrace(this, ConfigError);
|
|
2422
|
+
}
|
|
2423
|
+
};
|
|
2424
|
+
});
|
|
2425
|
+
|
|
2426
|
+
// src/utils/errors.ts
|
|
2427
|
+
var EnvParseError;
|
|
2428
|
+
var init_errors2 = __esm(() => {
|
|
2429
|
+
init_errors();
|
|
2430
|
+
EnvParseError = class EnvParseError extends Error {
|
|
2431
|
+
constructor(message) {
|
|
2432
|
+
super(message);
|
|
2433
|
+
this.name = "EnvParseError";
|
|
2434
|
+
if (Error.captureStackTrace) {
|
|
2435
|
+
Error.captureStackTrace(this, EnvParseError);
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
};
|
|
2439
|
+
});
|
|
2440
|
+
|
|
2441
|
+
// src/core/config-mutation-guard.ts
|
|
2442
|
+
function assertConfigMutationApproved() {
|
|
2443
|
+
if (process.env.DBCLI_AGENT_MODE !== "1")
|
|
2444
|
+
return;
|
|
2445
|
+
throw new ConfigError("Agent mode blocks configuration, permission, and credential changes. A human or administrator must run the approved workflow outside agent mode.");
|
|
2446
|
+
}
|
|
2447
|
+
var init_config_mutation_guard = __esm(() => {
|
|
2448
|
+
init_errors2();
|
|
2449
|
+
});
|
|
2450
|
+
|
|
2451
|
+
// src/core/config-integrity.ts
|
|
2452
|
+
import { createHash, randomUUID } from "crypto";
|
|
2453
|
+
import { chmod, lstat, mkdir, rename, unlink } from "fs/promises";
|
|
2454
|
+
import { dirname, join, resolve } from "path";
|
|
2455
|
+
function integrityPath(storagePath, recordName = INTEGRITY_FILE) {
|
|
2456
|
+
return join(storagePath, recordName);
|
|
2457
|
+
}
|
|
2458
|
+
function hash(content) {
|
|
2459
|
+
return createHash("sha256").update(content).digest("hex");
|
|
2460
|
+
}
|
|
2461
|
+
function protectedPath(storagePath, protectedFileName) {
|
|
2462
|
+
return resolve(join(storagePath, protectedFileName));
|
|
2463
|
+
}
|
|
2464
|
+
function anchorPathFor(protectedFilePath) {
|
|
2465
|
+
const anchorDirectory = process.env.DBCLI_CONFIG_INTEGRITY_ANCHOR_DIR?.trim();
|
|
2466
|
+
if (!anchorDirectory)
|
|
2467
|
+
return null;
|
|
2468
|
+
const key = createHash("sha256").update(protectedFilePath).digest("hex");
|
|
2469
|
+
return join(resolve(anchorDirectory), `${key}.json`);
|
|
2470
|
+
}
|
|
2471
|
+
async function bestEffortSecureMode(path, mode) {
|
|
2472
|
+
try {
|
|
2473
|
+
await chmod(path, mode);
|
|
2474
|
+
} catch {}
|
|
2475
|
+
}
|
|
2476
|
+
async function assertAgentReadableFile(path, description = "config") {
|
|
2477
|
+
if (process.env.DBCLI_AGENT_MODE !== "1")
|
|
2478
|
+
return;
|
|
2479
|
+
try {
|
|
2480
|
+
const fileStat = await lstat(path);
|
|
2481
|
+
if (fileStat.isSymbolicLink() || !fileStat.isFile()) {
|
|
2482
|
+
throw new ConfigError(`Agent mode refuses a non-regular ${description} file: ${path}. Run the human/admin setup workflow to provision it.`);
|
|
2483
|
+
}
|
|
2484
|
+
if ((fileStat.mode & 18) !== 0) {
|
|
2485
|
+
throw new ConfigError(`Agent mode refuses a group/world-writable config: ${path}. Run the human/admin setup workflow to secure it.`);
|
|
2486
|
+
}
|
|
2487
|
+
} catch (error) {
|
|
2488
|
+
if (error instanceof ConfigError)
|
|
2489
|
+
throw error;
|
|
2490
|
+
throw new ConfigError(`Agent mode refuses an unreadable ${description} file: ${path}`);
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
async function writeProtectedFileWithIntegrity(storagePath, protectedFileName, protectedContent, recordName) {
|
|
2494
|
+
await mkdir(storagePath, { recursive: true });
|
|
2495
|
+
const protectedFilePath = protectedPath(storagePath, protectedFileName);
|
|
2496
|
+
const localPath = integrityPath(storagePath, recordName);
|
|
2497
|
+
const anchorPath = anchorPathFor(protectedFilePath);
|
|
2498
|
+
const record = {
|
|
2499
|
+
version: 1,
|
|
2500
|
+
configSha256: hash(protectedContent),
|
|
2501
|
+
updatedAt: new Date().toISOString(),
|
|
2502
|
+
targetPath: protectedFilePath
|
|
2503
|
+
};
|
|
2504
|
+
const recordContent = JSON.stringify(record, null, 2);
|
|
2505
|
+
const suffix = `${process.pid}-${randomUUID()}`;
|
|
2506
|
+
const protectedTemporary = `${protectedFilePath}.tmp-${suffix}`;
|
|
2507
|
+
const localTemporary = `${localPath}.tmp-${suffix}`;
|
|
2508
|
+
const anchorTemporary = anchorPath ? `${anchorPath}.tmp-${suffix}` : null;
|
|
2509
|
+
const published = [];
|
|
2510
|
+
try {
|
|
2511
|
+
const [protectedSnapshot, localSnapshot, anchorSnapshot] = await Promise.all([
|
|
2512
|
+
snapshot(protectedFilePath),
|
|
2513
|
+
snapshot(localPath),
|
|
2514
|
+
anchorPath ? snapshot(anchorPath) : Promise.resolve(null)
|
|
2515
|
+
]);
|
|
2516
|
+
await Bun.write(protectedTemporary, protectedContent);
|
|
2517
|
+
await Bun.write(localTemporary, recordContent);
|
|
2518
|
+
if (anchorPath && anchorTemporary) {
|
|
2519
|
+
await mkdir(dirname(anchorPath), { recursive: true });
|
|
2520
|
+
await Bun.write(anchorTemporary, recordContent);
|
|
2521
|
+
}
|
|
2522
|
+
const publish = async (temporary, previous) => {
|
|
2523
|
+
await rename(temporary, previous.path);
|
|
2524
|
+
published.push(previous);
|
|
2525
|
+
};
|
|
2526
|
+
if (anchorTemporary && anchorPath) {
|
|
2527
|
+
await publish(anchorTemporary, anchorSnapshot ?? { path: anchorPath, content: null });
|
|
2528
|
+
}
|
|
2529
|
+
await publish(localTemporary, localSnapshot);
|
|
2530
|
+
await publish(protectedTemporary, protectedSnapshot);
|
|
2531
|
+
} catch (error) {
|
|
2532
|
+
for (const publishedFile of published.reverse()) {
|
|
2533
|
+
await restore(publishedFile).catch(() => {
|
|
2534
|
+
return;
|
|
2535
|
+
});
|
|
2536
|
+
}
|
|
2537
|
+
throw error;
|
|
2538
|
+
} finally {
|
|
2539
|
+
await unlink(protectedTemporary).catch(() => {
|
|
2540
|
+
return;
|
|
2541
|
+
});
|
|
2542
|
+
await unlink(localTemporary).catch(() => {
|
|
2543
|
+
return;
|
|
2544
|
+
});
|
|
2545
|
+
if (anchorTemporary)
|
|
2546
|
+
await unlink(anchorTemporary).catch(() => {
|
|
2547
|
+
return;
|
|
2548
|
+
});
|
|
2549
|
+
}
|
|
2550
|
+
if (anchorPath) {
|
|
2551
|
+
await bestEffortSecureMode(dirname(anchorPath), 448);
|
|
2552
|
+
await bestEffortSecureMode(anchorPath, 384);
|
|
2553
|
+
}
|
|
2554
|
+
await bestEffortSecureMode(storagePath, 448);
|
|
2555
|
+
await bestEffortSecureMode(protectedFilePath, 384);
|
|
2556
|
+
await bestEffortSecureMode(localPath, 384);
|
|
2557
|
+
}
|
|
2558
|
+
async function snapshot(path) {
|
|
2559
|
+
const file = Bun.file(path);
|
|
2560
|
+
return { path, content: await file.exists() ? await file.text() : null };
|
|
2561
|
+
}
|
|
2562
|
+
async function restore(snapshot2) {
|
|
2563
|
+
if (snapshot2.content === null) {
|
|
2564
|
+
await unlink(snapshot2.path).catch(() => {
|
|
2565
|
+
return;
|
|
2566
|
+
});
|
|
2567
|
+
return;
|
|
2568
|
+
}
|
|
2569
|
+
const temporary = `${snapshot2.path}.rollback-${process.pid}-${randomUUID()}`;
|
|
2570
|
+
await Bun.write(temporary, snapshot2.content);
|
|
2571
|
+
await rename(temporary, snapshot2.path);
|
|
2572
|
+
}
|
|
2573
|
+
async function writeConfigWithIntegrity(storagePath, configContent) {
|
|
2574
|
+
await writeProtectedFileWithIntegrity(storagePath, "config.json", configContent, INTEGRITY_FILE);
|
|
2575
|
+
}
|
|
2576
|
+
async function writeBindingWithIntegrity(projectPath, bindingContent) {
|
|
2577
|
+
await writeProtectedFileWithIntegrity(projectPath, "config.json", bindingContent, ".binding-integrity.json");
|
|
2578
|
+
}
|
|
2579
|
+
async function assertIntegrityRecord(storagePath, configContent, recordName, protectedFileName, description, options = {}) {
|
|
2580
|
+
if (process.env.DBCLI_AGENT_MODE !== "1")
|
|
2581
|
+
return;
|
|
2582
|
+
const configPath = protectedPath(storagePath, protectedFileName);
|
|
2583
|
+
await assertAgentReadableFile(configPath, description);
|
|
2584
|
+
const verifyRecord = async (path, label) => {
|
|
2585
|
+
const file = Bun.file(path);
|
|
2586
|
+
if (!await file.exists()) {
|
|
2587
|
+
throw new ConfigError(`Agent mode refuses a missing ${label}: ${path}`);
|
|
2588
|
+
}
|
|
2589
|
+
let record;
|
|
2590
|
+
try {
|
|
2591
|
+
await assertAgentReadableFile(path, label);
|
|
2592
|
+
record = await file.json();
|
|
2593
|
+
} catch (error) {
|
|
2594
|
+
if (error instanceof ConfigError)
|
|
2595
|
+
throw error;
|
|
2596
|
+
throw new ConfigError(`Agent mode refuses an unreadable ${label}: ${path}`);
|
|
2597
|
+
}
|
|
2598
|
+
if (record.version !== 1 || typeof record.configSha256 !== "string" || record.configSha256 !== hash(configContent) || record.targetPath !== undefined && record.targetPath !== configPath) {
|
|
2599
|
+
throw new ConfigError(`Agent mode detected direct ${description} tampering or an out-of-band edit: ${configPath}`);
|
|
2600
|
+
}
|
|
2601
|
+
};
|
|
2602
|
+
const anchorPath = anchorPathFor(configPath);
|
|
2603
|
+
if (anchorPath)
|
|
2604
|
+
await verifyRecord(anchorPath, "detached config integrity anchor");
|
|
2605
|
+
const localPath = integrityPath(storagePath, recordName);
|
|
2606
|
+
const localExists = await Bun.file(localPath).exists();
|
|
2607
|
+
if (localExists) {
|
|
2608
|
+
await verifyRecord(localPath, "config integrity record");
|
|
2609
|
+
} else if (options.requireRecord && !anchorPath) {
|
|
2610
|
+
throw new ConfigError(`Agent mode refuses a missing config integrity record: ${localPath}`);
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
async function assertConfigIntegrity(storagePath, configContent, options) {
|
|
2614
|
+
await assertIntegrityRecord(storagePath, configContent, INTEGRITY_FILE, "config.json", "config", options);
|
|
2615
|
+
}
|
|
2616
|
+
async function assertBindingIntegrity(projectPath, bindingContent, options) {
|
|
2617
|
+
await assertIntegrityRecord(projectPath, bindingContent, ".binding-integrity.json", "config.json", "project binding", options);
|
|
2618
|
+
}
|
|
2619
|
+
var INTEGRITY_FILE = ".config-integrity.json";
|
|
2620
|
+
var init_config_integrity = __esm(() => {
|
|
2621
|
+
init_errors2();
|
|
2622
|
+
});
|
|
2623
|
+
|
|
2624
|
+
// src/core/config-binding.ts
|
|
2625
|
+
import { createHash as createHash2 } from "crypto";
|
|
2626
|
+
import { mkdir as mkdir2, unlink as unlink2 } from "fs/promises";
|
|
2627
|
+
import { homedir } from "os";
|
|
2628
|
+
import { basename, join as join2, resolve as resolve2 } from "path";
|
|
2629
|
+
function getDbcliConfigHome() {
|
|
2630
|
+
const configuredHome = process.env.DBCLI_CONFIG_HOME?.trim();
|
|
2631
|
+
if (configuredHome)
|
|
2632
|
+
return configuredHome;
|
|
2633
|
+
return join2(process.env.HOME?.trim() || homedir(), ".config", "dbcli");
|
|
2634
|
+
}
|
|
2635
|
+
function isProjectConfigBinding(raw) {
|
|
2636
|
+
if (typeof raw !== "object" || raw === null)
|
|
2637
|
+
return false;
|
|
2638
|
+
const candidate = raw;
|
|
2639
|
+
return candidate.version === 3 && typeof candidate.binding === "object" && candidate.binding !== null && candidate.binding.type === "home-storage" && typeof candidate.binding.storagePath === "string" && candidate.binding.storagePath.length > 0 && typeof candidate.binding.projectPath === "string" && candidate.binding.projectPath.length > 0 && typeof candidate.binding.createdAt === "string" && candidate.binding.createdAt.length > 0;
|
|
2640
|
+
}
|
|
2641
|
+
function getDbcliHomeRoot() {
|
|
2642
|
+
return getDbcliConfigHome();
|
|
2643
|
+
}
|
|
2644
|
+
function getGlobalConfigPath() {
|
|
2645
|
+
return getDbcliHomeRoot();
|
|
2646
|
+
}
|
|
2647
|
+
function isGlobalConfigPath(path) {
|
|
2648
|
+
return resolve2(path) === resolve2(getGlobalConfigPath());
|
|
2649
|
+
}
|
|
2650
|
+
function getProjectStoragePath(projectPath) {
|
|
2651
|
+
const normalizedProjectPath = resolve2(projectPath);
|
|
2652
|
+
const projectName = basename(normalizedProjectPath) || "project";
|
|
2653
|
+
const hash2 = createHash2("sha1").update(normalizedProjectPath).digest("hex").slice(0, 12);
|
|
2654
|
+
return join2(getDbcliHomeRoot(), "projects", `${projectName}-${hash2}`);
|
|
2655
|
+
}
|
|
2656
|
+
async function readProjectBinding(projectPath) {
|
|
2657
|
+
const configFile = Bun.file(join2(projectPath, BINDING_FILE_NAME));
|
|
2658
|
+
if (!await configFile.exists())
|
|
2659
|
+
return null;
|
|
2660
|
+
try {
|
|
2661
|
+
await assertAgentReadableFile(join2(projectPath, BINDING_FILE_NAME), "project binding");
|
|
2662
|
+
const content = await configFile.text();
|
|
2663
|
+
const raw = JSON.parse(content);
|
|
2664
|
+
if (!isProjectConfigBinding(raw))
|
|
2665
|
+
return null;
|
|
2666
|
+
await assertBindingIntegrity(projectPath, content, { requireRecord: true });
|
|
2667
|
+
return raw;
|
|
2668
|
+
} catch (error) {
|
|
2669
|
+
if (error instanceof ConfigError)
|
|
2670
|
+
throw error;
|
|
2671
|
+
return null;
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
async function resolveConfigStoragePath(path) {
|
|
2675
|
+
const binding = await readProjectBinding(path);
|
|
2676
|
+
return binding?.binding.storagePath ?? path;
|
|
2677
|
+
}
|
|
2678
|
+
async function writeProjectBinding(projectPath, storagePath = getProjectStoragePath(projectPath)) {
|
|
2679
|
+
assertConfigMutationApproved();
|
|
2680
|
+
const binding = {
|
|
2681
|
+
version: 3,
|
|
2682
|
+
binding: {
|
|
2683
|
+
type: "home-storage",
|
|
2684
|
+
storagePath,
|
|
2685
|
+
projectPath: resolve2(projectPath),
|
|
2686
|
+
createdAt: new Date().toISOString()
|
|
2687
|
+
}
|
|
2688
|
+
};
|
|
2689
|
+
await mkdir2(projectPath, { recursive: true });
|
|
2690
|
+
await mkdir2(storagePath, { recursive: true });
|
|
2691
|
+
const content = JSON.stringify(binding, null, 2);
|
|
2692
|
+
await writeBindingWithIntegrity(projectPath, content);
|
|
2693
|
+
return binding;
|
|
2694
|
+
}
|
|
2695
|
+
async function migrateLegacyProjectEnvLocal(projectPath, storagePath = getProjectStoragePath(projectPath)) {
|
|
2696
|
+
assertConfigMutationApproved();
|
|
2697
|
+
const projectEnvPath = join2(projectPath, ".env.local");
|
|
2698
|
+
const projectEnvFile = Bun.file(projectEnvPath);
|
|
2699
|
+
if (!await projectEnvFile.exists())
|
|
2700
|
+
return;
|
|
2701
|
+
const storageEnvPath = join2(storagePath, ".env.local");
|
|
2702
|
+
await mkdir2(storagePath, { recursive: true });
|
|
2703
|
+
if (!await Bun.file(storageEnvPath).exists()) {
|
|
2704
|
+
await Bun.file(storageEnvPath).write(await projectEnvFile.text());
|
|
2705
|
+
}
|
|
2706
|
+
await unlink2(projectEnvPath);
|
|
2707
|
+
}
|
|
2708
|
+
var BINDING_FILE_NAME = "config.json";
|
|
2709
|
+
var init_config_binding = __esm(() => {
|
|
2710
|
+
init_config_mutation_guard();
|
|
2711
|
+
init_config_integrity();
|
|
2712
|
+
init_errors2();
|
|
2713
|
+
});
|
|
2714
|
+
|
|
2412
2715
|
// src/utils/config-path.ts
|
|
2413
2716
|
function resolveConfigPath(command, options, fallback = ".dbcli") {
|
|
2414
2717
|
for (let current = command;current; current = current.parent ?? undefined) {
|
|
@@ -2420,11 +2723,25 @@ function resolveConfigPath(command, options, fallback = ".dbcli") {
|
|
|
2420
2723
|
}
|
|
2421
2724
|
}
|
|
2422
2725
|
}
|
|
2726
|
+
if (typeof options?.config === "string" && options.config.length > 0 && options.config !== fallback) {
|
|
2727
|
+
return options.config;
|
|
2728
|
+
}
|
|
2729
|
+
for (let current = command;current; current = current.parent ?? undefined) {
|
|
2730
|
+
const source = current.getOptionValueSource("global");
|
|
2731
|
+
if (source && source !== "default" && current.opts().global === true) {
|
|
2732
|
+
return getGlobalConfigPath();
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
if (options?.global === true)
|
|
2736
|
+
return getGlobalConfigPath();
|
|
2423
2737
|
if (typeof options?.config === "string" && options.config.length > 0) {
|
|
2424
2738
|
return options.config;
|
|
2425
2739
|
}
|
|
2426
2740
|
return fallback;
|
|
2427
2741
|
}
|
|
2742
|
+
var init_config_path = __esm(() => {
|
|
2743
|
+
init_config_binding();
|
|
2744
|
+
});
|
|
2428
2745
|
|
|
2429
2746
|
// resources/lang/en/messages.json
|
|
2430
2747
|
var messages_default;
|
|
@@ -2445,6 +2762,7 @@ var init_messages = __esm(() => {
|
|
|
2445
2762
|
connection_success: "\u2713 Database connection successful",
|
|
2446
2763
|
connection_failed: "\u2717 Database connection failed",
|
|
2447
2764
|
config_saved: "Configuration saved to .dbcli",
|
|
2765
|
+
config_saved_global: "Global configuration saved to {path}",
|
|
2448
2766
|
config_exists_overwrite: "Configuration file .dbcli already exists. Overwrite? (y/n): ",
|
|
2449
2767
|
cancelled: "Cancelled. Configuration not changed.",
|
|
2450
2768
|
skip_test_env_ref: "Skipping connection test in env-ref mode",
|
|
@@ -2728,6 +3046,7 @@ var init_messages2 = __esm(() => {
|
|
|
2728
3046
|
connection_success: "\u2713 \u8CC7\u6599\u5EAB\u9023\u63A5\u6210\u529F",
|
|
2729
3047
|
connection_failed: "\u2717 \u8CC7\u6599\u5EAB\u9023\u63A5\u5931\u6557",
|
|
2730
3048
|
config_saved: "\u914D\u7F6E\u5DF2\u4FDD\u5B58\u81F3 .dbcli",
|
|
3049
|
+
config_saved_global: "\u5168\u57DF\u8A2D\u5B9A\u5DF2\u5132\u5B58\u81F3 {path}",
|
|
2731
3050
|
config_exists_overwrite: "\u914D\u7F6E\u6A94\u6848 .dbcli \u5DF2\u5B58\u5728\u3002\u662F\u5426\u8986\u84CB\uFF1F (y/n)\uFF1A",
|
|
2732
3051
|
cancelled: "\u5DF2\u53D6\u6D88\u3002\u914D\u7F6E\u672A\u66F4\u6539\u3002",
|
|
2733
3052
|
skip_test_env_ref: "\u8DF3\u904E\u9023\u7DDA\u6E2C\u8A66\uFF08\u74B0\u5883\u8B8A\u6578\u53C3\u7167\u6A21\u5F0F\uFF09",
|
|
@@ -3516,7 +3835,7 @@ function getErrorMap() {
|
|
|
3516
3835
|
return overrideErrorMap;
|
|
3517
3836
|
}
|
|
3518
3837
|
var overrideErrorMap;
|
|
3519
|
-
var
|
|
3838
|
+
var init_errors3 = __esm(() => {
|
|
3520
3839
|
init_en();
|
|
3521
3840
|
overrideErrorMap = en_default;
|
|
3522
3841
|
});
|
|
@@ -3618,7 +3937,7 @@ var makeIssue = (params) => {
|
|
|
3618
3937
|
};
|
|
3619
3938
|
}, EMPTY_PATH, INVALID, DIRTY = (value) => ({ status: "dirty", value }), OK = (value) => ({ status: "valid", value }), isAborted = (x) => x.status === "aborted", isDirty = (x) => x.status === "dirty", isValid = (x) => x.status === "valid", isAsync = (x) => typeof Promise !== "undefined" && x instanceof Promise;
|
|
3620
3939
|
var init_parseUtil = __esm(() => {
|
|
3621
|
-
|
|
3940
|
+
init_errors3();
|
|
3622
3941
|
init_en();
|
|
3623
3942
|
EMPTY_PATH = [];
|
|
3624
3943
|
INVALID = Object.freeze({
|
|
@@ -4186,7 +4505,7 @@ var handleResult = (ctx, result) => {
|
|
|
4186
4505
|
}) => custom((data) => data instanceof cls, params), stringType, numberType, nanType, bigIntType, booleanType, dateType, symbolType, undefinedType, nullType, anyType, unknownType, neverType, voidType, arrayType, objectType, strictObjectType, unionType, discriminatedUnionType, intersectionType, tupleType, recordType, mapType, setType, functionType, lazyType, literalType, enumType, nativeEnumType, promiseType, effectsType, optionalType, nullableType, preprocessType, pipelineType, ostring = () => stringType().optional(), onumber = () => numberType().optional(), oboolean = () => booleanType().optional(), coerce, NEVER;
|
|
4187
4506
|
var init_types = __esm(() => {
|
|
4188
4507
|
init_ZodError();
|
|
4189
|
-
|
|
4508
|
+
init_errors3();
|
|
4190
4509
|
init_errorUtil();
|
|
4191
4510
|
init_parseUtil();
|
|
4192
4511
|
init_util();
|
|
@@ -7101,7 +7420,7 @@ __export(exports_external, {
|
|
|
7101
7420
|
BRAND: () => BRAND
|
|
7102
7421
|
});
|
|
7103
7422
|
var init_external = __esm(() => {
|
|
7104
|
-
|
|
7423
|
+
init_errors3();
|
|
7105
7424
|
init_parseUtil();
|
|
7106
7425
|
init_typeAliases();
|
|
7107
7426
|
init_util();
|
|
@@ -7122,7 +7441,7 @@ function validateFormat(value, allowedFormats, commandName) {
|
|
|
7122
7441
|
throw new Error(`Invalid format "${value}" for ${commandName}. Allowed: ${allowed}`);
|
|
7123
7442
|
}
|
|
7124
7443
|
}
|
|
7125
|
-
var EnvRefSchema, StringOrEnvRef, NumberOrEnvRef, OptStringOrEnvRef, OptNumberOrEnvRef, MongoDBConnectionConfigSchema, SqlConnectionConfigSchema, RedisConnectionConfigSchema, ElasticsearchConnectionConfigSchema, ConnectionConfigSchema, PermissionSchema, MetadataSchema, BlacklistConfigSchema, RedisMaskRuleSchema, RedisConfigSchema, AuditRotationConfigSchema, AuditConfigSchema, DbcliConfigSchema, SqlNamedConnectionSchema, MongoDBNamedConnectionSchema, RedisNamedConnectionSchema, ElasticsearchNamedConnectionSchema, NamedConnectionSchema, DbcliConfigV2Schema;
|
|
7444
|
+
var EnvRefSchema, StringOrEnvRef, NumberOrEnvRef, OptStringOrEnvRef, OptNumberOrEnvRef, MongoDBConnectionConfigSchema, SqlConnectionConfigSchema, RedisConnectionConfigSchema, ElasticsearchConnectionConfigSchema, ConnectionConfigSchema, PermissionSchema, EnvironmentLabelSchema, MetadataSchema, BlacklistConfigSchema, RedisMaskRuleSchema, RedisConfigSchema, AuditRotationConfigSchema, AuditConfigSchema, DbcliConfigSchema, SqlNamedConnectionSchema, MongoDBNamedConnectionSchema, RedisNamedConnectionSchema, ElasticsearchNamedConnectionSchema, NamedConnectionSchema, DbcliConfigV2Schema;
|
|
7126
7445
|
var init_validation = __esm(() => {
|
|
7127
7446
|
init_zod();
|
|
7128
7447
|
EnvRefSchema = exports_external.object({
|
|
@@ -7178,6 +7497,7 @@ var init_validation = __esm(() => {
|
|
|
7178
7497
|
ElasticsearchConnectionConfigSchema
|
|
7179
7498
|
]);
|
|
7180
7499
|
PermissionSchema = exports_external.enum(["query-only", "read-write", "data-admin", "admin"]).default("query-only");
|
|
7500
|
+
EnvironmentLabelSchema = exports_external.string().trim().transform((value) => value || undefined).optional();
|
|
7181
7501
|
MetadataSchema = exports_external.object({
|
|
7182
7502
|
createdAt: exports_external.string().datetime().optional(),
|
|
7183
7503
|
version: exports_external.string().default("1.0"),
|
|
@@ -7217,19 +7537,23 @@ var init_validation = __esm(() => {
|
|
|
7217
7537
|
});
|
|
7218
7538
|
SqlNamedConnectionSchema = SqlConnectionConfigSchema.extend({
|
|
7219
7539
|
permission: PermissionSchema,
|
|
7220
|
-
envFile: exports_external.string().optional()
|
|
7540
|
+
envFile: exports_external.string().optional(),
|
|
7541
|
+
environment: EnvironmentLabelSchema
|
|
7221
7542
|
});
|
|
7222
7543
|
MongoDBNamedConnectionSchema = MongoDBConnectionConfigSchema.extend({
|
|
7223
7544
|
permission: PermissionSchema,
|
|
7224
|
-
envFile: exports_external.string().optional()
|
|
7545
|
+
envFile: exports_external.string().optional(),
|
|
7546
|
+
environment: EnvironmentLabelSchema
|
|
7225
7547
|
});
|
|
7226
7548
|
RedisNamedConnectionSchema = RedisConnectionConfigSchema.extend({
|
|
7227
7549
|
permission: PermissionSchema,
|
|
7228
|
-
envFile: exports_external.string().optional()
|
|
7550
|
+
envFile: exports_external.string().optional(),
|
|
7551
|
+
environment: EnvironmentLabelSchema
|
|
7229
7552
|
});
|
|
7230
7553
|
ElasticsearchNamedConnectionSchema = ElasticsearchConnectionConfigSchema.extend({
|
|
7231
7554
|
permission: PermissionSchema,
|
|
7232
|
-
envFile: exports_external.string().optional()
|
|
7555
|
+
envFile: exports_external.string().optional(),
|
|
7556
|
+
environment: EnvironmentLabelSchema
|
|
7233
7557
|
});
|
|
7234
7558
|
NamedConnectionSchema = exports_external.union([
|
|
7235
7559
|
SqlNamedConnectionSchema,
|
|
@@ -7255,34 +7579,6 @@ var init_validation = __esm(() => {
|
|
|
7255
7579
|
});
|
|
7256
7580
|
});
|
|
7257
7581
|
|
|
7258
|
-
// src/agent-core/errors.ts
|
|
7259
|
-
var ConfigError;
|
|
7260
|
-
var init_errors2 = __esm(() => {
|
|
7261
|
-
ConfigError = class ConfigError extends Error {
|
|
7262
|
-
constructor(message) {
|
|
7263
|
-
super(message);
|
|
7264
|
-
this.name = "ConfigError";
|
|
7265
|
-
if (Error.captureStackTrace)
|
|
7266
|
-
Error.captureStackTrace(this, ConfigError);
|
|
7267
|
-
}
|
|
7268
|
-
};
|
|
7269
|
-
});
|
|
7270
|
-
|
|
7271
|
-
// src/utils/errors.ts
|
|
7272
|
-
var EnvParseError;
|
|
7273
|
-
var init_errors3 = __esm(() => {
|
|
7274
|
-
init_errors2();
|
|
7275
|
-
EnvParseError = class EnvParseError extends Error {
|
|
7276
|
-
constructor(message) {
|
|
7277
|
-
super(message);
|
|
7278
|
-
this.name = "EnvParseError";
|
|
7279
|
-
if (Error.captureStackTrace) {
|
|
7280
|
-
Error.captureStackTrace(this, EnvParseError);
|
|
7281
|
-
}
|
|
7282
|
-
}
|
|
7283
|
-
};
|
|
7284
|
-
});
|
|
7285
|
-
|
|
7286
7582
|
// src/agent-core/env-loader.ts
|
|
7287
7583
|
import { readFile } from "fs/promises";
|
|
7288
7584
|
function parseEnvContent(content) {
|
|
@@ -7321,7 +7617,7 @@ async function loadEnvFile(filePath) {
|
|
|
7321
7617
|
}
|
|
7322
7618
|
}
|
|
7323
7619
|
var init_env_loader = __esm(() => {
|
|
7324
|
-
|
|
7620
|
+
init_errors();
|
|
7325
7621
|
});
|
|
7326
7622
|
|
|
7327
7623
|
// src/core/env-loader.ts
|
|
@@ -7329,123 +7625,129 @@ var init_env_loader2 = __esm(() => {
|
|
|
7329
7625
|
init_env_loader();
|
|
7330
7626
|
});
|
|
7331
7627
|
|
|
7332
|
-
// src/
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
function getDbcliHomeRoot() {
|
|
7344
|
-
return DBCLI_HOME_ROOT;
|
|
7345
|
-
}
|
|
7346
|
-
function getProjectStoragePath(projectPath) {
|
|
7347
|
-
const normalizedProjectPath = resolve2(projectPath);
|
|
7348
|
-
const projectName = basename(normalizedProjectPath) || "project";
|
|
7349
|
-
const hash = createHash("sha1").update(normalizedProjectPath).digest("hex").slice(0, 12);
|
|
7350
|
-
return join2(getDbcliHomeRoot(), "projects", `${projectName}-${hash}`);
|
|
7351
|
-
}
|
|
7352
|
-
async function readProjectBinding(projectPath) {
|
|
7353
|
-
const configFile = Bun.file(join2(projectPath, BINDING_FILE_NAME));
|
|
7354
|
-
if (!await configFile.exists())
|
|
7355
|
-
return null;
|
|
7356
|
-
try {
|
|
7357
|
-
const raw = JSON.parse(await configFile.text());
|
|
7358
|
-
return isProjectConfigBinding(raw) ? raw : null;
|
|
7359
|
-
} catch {
|
|
7360
|
-
return null;
|
|
7628
|
+
// src/utils/levenshtein-distance.ts
|
|
7629
|
+
function levenshteinDistance(a, b) {
|
|
7630
|
+
const lenA = a.length;
|
|
7631
|
+
const lenB = b.length;
|
|
7632
|
+
const dp = [];
|
|
7633
|
+
for (let i = 0;i <= lenB; i++) {
|
|
7634
|
+
const row = [];
|
|
7635
|
+
for (let j = 0;j <= lenA; j++) {
|
|
7636
|
+
row[j] = 0;
|
|
7637
|
+
}
|
|
7638
|
+
dp[i] = row;
|
|
7361
7639
|
}
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
}
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
storagePath,
|
|
7373
|
-
projectPath: resolve2(projectPath),
|
|
7374
|
-
createdAt: new Date().toISOString()
|
|
7640
|
+
for (let i = 0;i <= lenB; i++) {
|
|
7641
|
+
const row = dp[i];
|
|
7642
|
+
if (row !== undefined) {
|
|
7643
|
+
row[0] = i;
|
|
7644
|
+
}
|
|
7645
|
+
}
|
|
7646
|
+
for (let j = 0;j <= lenA; j++) {
|
|
7647
|
+
const firstRow = dp[0];
|
|
7648
|
+
if (firstRow !== undefined) {
|
|
7649
|
+
firstRow[j] = j;
|
|
7375
7650
|
}
|
|
7376
|
-
};
|
|
7377
|
-
await mkdir(projectPath, { recursive: true });
|
|
7378
|
-
await mkdir(storagePath, { recursive: true });
|
|
7379
|
-
await Bun.file(join2(projectPath, BINDING_FILE_NAME)).write(JSON.stringify(binding, null, 2));
|
|
7380
|
-
return binding;
|
|
7381
|
-
}
|
|
7382
|
-
async function migrateLegacyProjectEnvLocal(projectPath, storagePath = getProjectStoragePath(projectPath)) {
|
|
7383
|
-
const projectEnvPath = join2(projectPath, ".env.local");
|
|
7384
|
-
const projectEnvFile = Bun.file(projectEnvPath);
|
|
7385
|
-
if (!await projectEnvFile.exists())
|
|
7386
|
-
return;
|
|
7387
|
-
const storageEnvPath = join2(storagePath, ".env.local");
|
|
7388
|
-
await mkdir(storagePath, { recursive: true });
|
|
7389
|
-
if (!await Bun.file(storageEnvPath).exists()) {
|
|
7390
|
-
await Bun.file(storageEnvPath).write(await projectEnvFile.text());
|
|
7391
7651
|
}
|
|
7392
|
-
|
|
7652
|
+
for (let i = 1;i <= lenB; i++) {
|
|
7653
|
+
const currentRow = dp[i];
|
|
7654
|
+
const prevRow = dp[i - 1];
|
|
7655
|
+
if (!currentRow || !prevRow)
|
|
7656
|
+
continue;
|
|
7657
|
+
for (let j = 1;j <= lenA; j++) {
|
|
7658
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
7659
|
+
currentRow[j] = prevRow[j - 1] ?? 0;
|
|
7660
|
+
} else {
|
|
7661
|
+
const sub = (prevRow[j - 1] ?? 0) + 1;
|
|
7662
|
+
const ins = (currentRow[j - 1] ?? 0) + 1;
|
|
7663
|
+
const del = (prevRow[j] ?? 0) + 1;
|
|
7664
|
+
currentRow[j] = Math.min(sub, ins, del);
|
|
7665
|
+
}
|
|
7666
|
+
}
|
|
7667
|
+
}
|
|
7668
|
+
return dp[lenB]?.[lenA] ?? 0;
|
|
7393
7669
|
}
|
|
7394
|
-
var BINDING_FILE_NAME = "config.json", DBCLI_HOME_ROOT;
|
|
7395
|
-
var init_config_binding = __esm(() => {
|
|
7396
|
-
DBCLI_HOME_ROOT = join2(homedir(), ".config", "dbcli");
|
|
7397
|
-
});
|
|
7398
7670
|
|
|
7399
7671
|
// src/core/config-v2.ts
|
|
7400
|
-
import { join as
|
|
7401
|
-
import { mkdir as mkdir2, rename } from "fs/promises";
|
|
7672
|
+
import { join as join4 } from "path";
|
|
7402
7673
|
function detectConfigVersion(raw) {
|
|
7403
7674
|
if (typeof raw === "object" && raw !== null && "version" in raw && raw.version === 2 && "connections" in raw) {
|
|
7404
7675
|
return 2;
|
|
7405
7676
|
}
|
|
7406
7677
|
return 1;
|
|
7407
7678
|
}
|
|
7679
|
+
function assertExplicitProductionSelection(resolved, requestedName) {
|
|
7680
|
+
if (resolved.environment !== "production")
|
|
7681
|
+
return;
|
|
7682
|
+
if (requestedName?.trim() === resolved.name)
|
|
7683
|
+
return;
|
|
7684
|
+
throw new ConfigError(`\u9023\u7DDA '${resolved.name}' \u6A19\u8A18\u70BA production\uFF0C\u5FC5\u9808\u660E\u78BA\u4F7F\u7528 --use ${resolved.name}\uFF08\u6216\u8A2D\u5B9A DBCLI_CONNECTION=${resolved.name}\uFF09\u624D\u80FD\u57F7\u884C\u3002`);
|
|
7685
|
+
}
|
|
7686
|
+
function assertProductionDefaultConfirmation(name2, environment, confirmation) {
|
|
7687
|
+
if (environment !== "production")
|
|
7688
|
+
return;
|
|
7689
|
+
if (confirmation === name2)
|
|
7690
|
+
return;
|
|
7691
|
+
throw new ConfigError(`\u9023\u7DDA '${name2}' \u6A19\u8A18\u70BA production\u3002\u82E5\u8981\u8B8A\u66F4\u9810\u8A2D\u9023\u7DDA\uFF0C\u8ACB\u52A0\u5165 --confirm-production ${name2}\u3002`);
|
|
7692
|
+
}
|
|
7693
|
+
function findSimilarConnectionNames(requestedName, availableNames) {
|
|
7694
|
+
const requested = requestedName.toLowerCase();
|
|
7695
|
+
const distanceLimit = Math.max(2, Math.floor(requested.length / 3));
|
|
7696
|
+
return availableNames.map((name2) => {
|
|
7697
|
+
const candidate = name2.toLowerCase();
|
|
7698
|
+
const distance = levenshteinDistance(requested, candidate);
|
|
7699
|
+
return {
|
|
7700
|
+
name: name2,
|
|
7701
|
+
distance,
|
|
7702
|
+
isSimilar: candidate.includes(requested) || requested.includes(candidate) || distance <= distanceLimit
|
|
7703
|
+
};
|
|
7704
|
+
}).filter(({ isSimilar }) => isSimilar).sort((a, b) => a.distance - b.distance || a.name.localeCompare(b.name)).slice(0, 3).map(({ name: name2 }) => name2);
|
|
7705
|
+
}
|
|
7706
|
+
function connectionNotFoundMessage(requestedName, availableNames) {
|
|
7707
|
+
const suggestions = findSimilarConnectionNames(requestedName, availableNames);
|
|
7708
|
+
const suggestion = suggestions.length > 0 ? ` \u4F60\u662F\u5426\u8981\u4F7F\u7528\uFF1A${suggestions.join("\u3001")}\uFF1F` : "";
|
|
7709
|
+
return `\u9023\u7DDA '${requestedName}' \u4E0D\u5B58\u5728\u3002${suggestion} \u53EF\u7528\u9023\u7DDA\uFF1A${availableNames.join(", ")}`;
|
|
7710
|
+
}
|
|
7408
7711
|
function resolveConnection(config, name2) {
|
|
7409
7712
|
const connectionName = name2 ?? config.default;
|
|
7410
7713
|
const conn = config.connections[connectionName];
|
|
7411
7714
|
if (!conn) {
|
|
7412
|
-
|
|
7413
|
-
throw new ConfigError(`\u9023\u7DDA '${connectionName}' \u4E0D\u5B58\u5728\u3002\u53EF\u7528\u9023\u7DDA\uFF1A${available}`);
|
|
7715
|
+
throw new ConfigError(connectionNotFoundMessage(connectionName, Object.keys(config.connections)));
|
|
7414
7716
|
}
|
|
7415
|
-
const { permission, envFile, ...connectionFields } = conn;
|
|
7717
|
+
const { permission, envFile, environment, ...connectionFields } = conn;
|
|
7416
7718
|
return {
|
|
7417
7719
|
name: connectionName,
|
|
7418
7720
|
connection: connectionFields,
|
|
7419
7721
|
permission,
|
|
7420
|
-
envFile
|
|
7722
|
+
envFile,
|
|
7723
|
+
environment
|
|
7421
7724
|
};
|
|
7422
7725
|
}
|
|
7423
7726
|
async function loadConnectionEnv(resolved, basePath) {
|
|
7424
7727
|
if (resolved.envFile) {
|
|
7425
|
-
const envPath =
|
|
7728
|
+
const envPath = join4(basePath, resolved.envFile);
|
|
7426
7729
|
await loadEnvFile(envPath);
|
|
7427
7730
|
}
|
|
7428
7731
|
}
|
|
7429
7732
|
async function readV2Config(path2) {
|
|
7430
7733
|
const storagePath = await resolveConfigStoragePath(path2);
|
|
7431
|
-
const configPath =
|
|
7734
|
+
const configPath = join4(storagePath, "config.json");
|
|
7432
7735
|
const file = Bun.file(configPath);
|
|
7433
7736
|
if (!await file.exists()) {
|
|
7434
7737
|
throw new ConfigError(`\u627E\u4E0D\u5230 V2 \u8A2D\u5B9A\u6A94\uFF1A${configPath}`);
|
|
7435
7738
|
}
|
|
7739
|
+
await assertAgentReadableFile(configPath);
|
|
7436
7740
|
const content = await file.text();
|
|
7741
|
+
await assertConfigIntegrity(storagePath, content, { requireRecord: true });
|
|
7437
7742
|
const raw = JSON.parse(content);
|
|
7438
7743
|
return DbcliConfigV2Schema.parse(raw);
|
|
7439
7744
|
}
|
|
7440
7745
|
async function writeV2Config(path2, config) {
|
|
7746
|
+
assertConfigMutationApproved();
|
|
7441
7747
|
DbcliConfigV2Schema.parse(config);
|
|
7442
7748
|
const storagePath = await resolveConfigStoragePath(path2);
|
|
7443
|
-
const configPath = join3(storagePath, "config.json");
|
|
7444
|
-
const tmpPath = `${configPath}.tmp`;
|
|
7445
|
-
await mkdir2(storagePath, { recursive: true });
|
|
7446
7749
|
const json = JSON.stringify(config, null, 2);
|
|
7447
|
-
await
|
|
7448
|
-
await rename(tmpPath, configPath);
|
|
7750
|
+
await writeConfigWithIntegrity(storagePath, json);
|
|
7449
7751
|
}
|
|
7450
7752
|
async function patchConnectionSchema(dbcliPath, connectionName, schema, metadataUpdate) {
|
|
7451
7753
|
const storagePath = await resolveConfigStoragePath(dbcliPath);
|
|
@@ -7465,7 +7767,9 @@ async function patchConnectionSchema(dbcliPath, connectionName, schema, metadata
|
|
|
7465
7767
|
}
|
|
7466
7768
|
var init_config_v2 = __esm(() => {
|
|
7467
7769
|
init_validation();
|
|
7468
|
-
|
|
7770
|
+
init_errors2();
|
|
7771
|
+
init_config_mutation_guard();
|
|
7772
|
+
init_config_integrity();
|
|
7469
7773
|
init_env_loader2();
|
|
7470
7774
|
init_config_binding();
|
|
7471
7775
|
});
|
|
@@ -7480,15 +7784,26 @@ function trimAppliedLimit(rows, limit) {
|
|
|
7480
7784
|
}
|
|
7481
7785
|
|
|
7482
7786
|
// src/agent-core/connection-selector.ts
|
|
7787
|
+
function normalizeSelector(value, source) {
|
|
7788
|
+
if (value === undefined)
|
|
7789
|
+
return;
|
|
7790
|
+
const normalized = value.trim();
|
|
7791
|
+
if (normalized === "") {
|
|
7792
|
+
throw new Error(`${source} connection selector cannot be empty`);
|
|
7793
|
+
}
|
|
7794
|
+
return normalized;
|
|
7795
|
+
}
|
|
7483
7796
|
function resolveConnectionSelector(inputs) {
|
|
7484
|
-
|
|
7485
|
-
|
|
7797
|
+
const root = normalizeSelector(inputs.root, "Root");
|
|
7798
|
+
const command = normalizeSelector(inputs.command, "Command");
|
|
7799
|
+
if (root !== undefined && command !== undefined && root !== command) {
|
|
7800
|
+
throw new Error(`Conflicting connection selectors: root value '${root}' does not match command value '${command}'`);
|
|
7486
7801
|
}
|
|
7487
|
-
const explicit =
|
|
7802
|
+
const explicit = command ?? root;
|
|
7488
7803
|
if (explicit !== undefined)
|
|
7489
7804
|
return explicit;
|
|
7490
7805
|
const environment = inputs.environment?.trim();
|
|
7491
|
-
return environment
|
|
7806
|
+
return environment || undefined;
|
|
7492
7807
|
}
|
|
7493
7808
|
function parseConnectionNames(selector) {
|
|
7494
7809
|
const names = selector.split(",").map((name2) => name2.trim());
|
|
@@ -7518,14 +7833,14 @@ function resolveEnvRef(value, fieldName, env = process.env) {
|
|
|
7518
7833
|
return resolved;
|
|
7519
7834
|
}
|
|
7520
7835
|
var init_env_ref = __esm(() => {
|
|
7521
|
-
|
|
7836
|
+
init_errors();
|
|
7522
7837
|
});
|
|
7523
7838
|
|
|
7524
7839
|
// src/agent-core/public.ts
|
|
7525
7840
|
var init_public = __esm(() => {
|
|
7526
7841
|
init_env_loader();
|
|
7527
7842
|
init_env_ref();
|
|
7528
|
-
|
|
7843
|
+
init_errors();
|
|
7529
7844
|
});
|
|
7530
7845
|
|
|
7531
7846
|
// node_modules/lru-cache/dist/esm/node/index.min.js
|
|
@@ -8165,16 +8480,16 @@ var init_index_min = __esm(() => {
|
|
|
8165
8480
|
});
|
|
8166
8481
|
|
|
8167
8482
|
// src/utils/schema-path.ts
|
|
8168
|
-
import { join as
|
|
8483
|
+
import { join as join5 } from "path";
|
|
8169
8484
|
function resolveSchemaPath(dbcliPath, connectionName) {
|
|
8170
8485
|
if (!connectionName)
|
|
8171
|
-
return
|
|
8172
|
-
return
|
|
8486
|
+
return join5(dbcliPath, "schemas");
|
|
8487
|
+
return join5(dbcliPath, "schemas", connectionName);
|
|
8173
8488
|
}
|
|
8174
8489
|
var init_schema_path = () => {};
|
|
8175
8490
|
|
|
8176
8491
|
// src/core/schema-cache.ts
|
|
8177
|
-
import { join as
|
|
8492
|
+
import { join as join6 } from "path";
|
|
8178
8493
|
|
|
8179
8494
|
class SchemaCacheManager {
|
|
8180
8495
|
cache;
|
|
@@ -8200,13 +8515,13 @@ class SchemaCacheManager {
|
|
|
8200
8515
|
}
|
|
8201
8516
|
async initialize() {
|
|
8202
8517
|
try {
|
|
8203
|
-
const indexPath =
|
|
8518
|
+
const indexPath = join6(this.schemaRoot, "index.json");
|
|
8204
8519
|
const indexFile = Bun.file(indexPath);
|
|
8205
8520
|
if (await indexFile.exists()) {
|
|
8206
8521
|
const indexContent = await indexFile.text();
|
|
8207
8522
|
this.index = JSON.parse(indexContent);
|
|
8208
8523
|
}
|
|
8209
|
-
const hotPath =
|
|
8524
|
+
const hotPath = join6(this.schemaRoot, "hot-schemas.json");
|
|
8210
8525
|
const hotFile = Bun.file(hotPath);
|
|
8211
8526
|
if (await hotFile.exists()) {
|
|
8212
8527
|
const hotContent = await hotFile.text();
|
|
@@ -8239,7 +8554,7 @@ class SchemaCacheManager {
|
|
|
8239
8554
|
return null;
|
|
8240
8555
|
}
|
|
8241
8556
|
try {
|
|
8242
|
-
const filePath =
|
|
8557
|
+
const filePath = join6(this.schemaRoot, tableInfo.file);
|
|
8243
8558
|
const file = Bun.file(filePath);
|
|
8244
8559
|
if (!await file.exists()) {
|
|
8245
8560
|
console.error(`Cold table file not found: ${tableInfo.file} for table ${tableName}`);
|
|
@@ -8294,12 +8609,12 @@ var init_schema_cache = __esm(() => {
|
|
|
8294
8609
|
});
|
|
8295
8610
|
|
|
8296
8611
|
// src/core/schema-index.ts
|
|
8297
|
-
import { join as
|
|
8612
|
+
import { join as join7 } from "path";
|
|
8298
8613
|
|
|
8299
8614
|
class SchemaIndexBuilder {
|
|
8300
8615
|
static async loadIndex(dbcliPath, connectionName) {
|
|
8301
8616
|
try {
|
|
8302
|
-
const indexPath =
|
|
8617
|
+
const indexPath = join7(resolveSchemaPath(dbcliPath, connectionName), "index.json");
|
|
8303
8618
|
const file = Bun.file(indexPath);
|
|
8304
8619
|
if (!await file.exists()) {
|
|
8305
8620
|
return null;
|
|
@@ -8346,7 +8661,7 @@ class SchemaIndexBuilder {
|
|
|
8346
8661
|
try {
|
|
8347
8662
|
const schemasDir = resolveSchemaPath(dbcliPath, connectionName);
|
|
8348
8663
|
await this.ensureDir(schemasDir);
|
|
8349
|
-
const indexPath =
|
|
8664
|
+
const indexPath = join7(schemasDir, "index.json");
|
|
8350
8665
|
const indexFile = Bun.file(indexPath);
|
|
8351
8666
|
await indexFile.write(JSON.stringify(index, null, 2));
|
|
8352
8667
|
} catch (error) {
|
|
@@ -8392,7 +8707,7 @@ var exports_schema_loader = {};
|
|
|
8392
8707
|
__export(exports_schema_loader, {
|
|
8393
8708
|
SchemaLayeredLoader: () => SchemaLayeredLoader
|
|
8394
8709
|
});
|
|
8395
|
-
import { join as
|
|
8710
|
+
import { join as join8 } from "path";
|
|
8396
8711
|
|
|
8397
8712
|
class SchemaLayeredLoader {
|
|
8398
8713
|
dbcliPath;
|
|
@@ -8459,7 +8774,7 @@ class SchemaLayeredLoader {
|
|
|
8459
8774
|
}
|
|
8460
8775
|
async ensureDirectories() {
|
|
8461
8776
|
const base = resolveSchemaPath(this.dbcliPath, this.connectionName);
|
|
8462
|
-
const dirs = [base,
|
|
8777
|
+
const dirs = [base, join8(base, "cold")];
|
|
8463
8778
|
for (const dir of dirs) {
|
|
8464
8779
|
try {
|
|
8465
8780
|
const dirFile = Bun.file(dir);
|
|
@@ -8507,7 +8822,7 @@ __export(exports_config, {
|
|
|
8507
8822
|
getGlobalConnectionName: () => getGlobalConnectionName,
|
|
8508
8823
|
configModule: () => configModule
|
|
8509
8824
|
});
|
|
8510
|
-
import { join as
|
|
8825
|
+
import { join as join9 } from "path";
|
|
8511
8826
|
import { mkdir as mkdir3 } from "fs/promises";
|
|
8512
8827
|
function assertNoConnectionSelectorOnV1(connectionName) {
|
|
8513
8828
|
if (connectionName === undefined)
|
|
@@ -8528,7 +8843,7 @@ async function getSchemaIsolationConnectionName(dbcliPath) {
|
|
|
8528
8843
|
const isDirectory = stat?.isDirectory() ?? false;
|
|
8529
8844
|
if (!isDirectory)
|
|
8530
8845
|
return;
|
|
8531
|
-
const configJsonPath =
|
|
8846
|
+
const configJsonPath = join9(storagePath, "config.json");
|
|
8532
8847
|
const configFile = Bun.file(configJsonPath);
|
|
8533
8848
|
if (!await configFile.exists())
|
|
8534
8849
|
return;
|
|
@@ -8576,10 +8891,12 @@ function parseEnvPassword(content) {
|
|
|
8576
8891
|
var _globalConnectionName, DEFAULT_CONFIG, configModule;
|
|
8577
8892
|
var init_config = __esm(() => {
|
|
8578
8893
|
init_validation();
|
|
8579
|
-
|
|
8894
|
+
init_errors2();
|
|
8580
8895
|
init_config_v2();
|
|
8581
8896
|
init_config_binding();
|
|
8582
8897
|
init_public();
|
|
8898
|
+
init_config_mutation_guard();
|
|
8899
|
+
init_config_integrity();
|
|
8583
8900
|
DEFAULT_CONFIG = {
|
|
8584
8901
|
connection: {
|
|
8585
8902
|
system: "postgresql",
|
|
@@ -8607,9 +8924,9 @@ var init_config = __esm(() => {
|
|
|
8607
8924
|
const binding = await readProjectBinding(path2);
|
|
8608
8925
|
const storagePath = await resolveConfigStoragePath(path2);
|
|
8609
8926
|
if (binding) {
|
|
8610
|
-
const storageConfigExists = await Bun.file(
|
|
8927
|
+
const storageConfigExists = await Bun.file(join9(storagePath, "config.json")).exists();
|
|
8611
8928
|
if (!storageConfigExists) {
|
|
8612
|
-
throw new ConfigError(`Bound dbcli config not found: ${
|
|
8929
|
+
throw new ConfigError(`Bound dbcli config not found: ${join9(storagePath, "config.json")}`);
|
|
8613
8930
|
}
|
|
8614
8931
|
}
|
|
8615
8932
|
let isDirectory = false;
|
|
@@ -8620,17 +8937,20 @@ var init_config = __esm(() => {
|
|
|
8620
8937
|
isDirectory = false;
|
|
8621
8938
|
}
|
|
8622
8939
|
if (isDirectory) {
|
|
8623
|
-
const configPath =
|
|
8940
|
+
const configPath = join9(storagePath, "config.json");
|
|
8624
8941
|
const configFile = Bun.file(configPath);
|
|
8625
8942
|
const configExists = await configFile.exists();
|
|
8626
8943
|
if (configExists) {
|
|
8944
|
+
await assertAgentReadableFile(configPath);
|
|
8627
8945
|
const content = await configFile.text();
|
|
8946
|
+
await assertConfigIntegrity(storagePath, content, { requireRecord: true });
|
|
8628
8947
|
const config = JSON.parse(content);
|
|
8629
8948
|
if (detectConfigVersion(config) === 2) {
|
|
8630
8949
|
const v2Config = DbcliConfigV2Schema.parse(config);
|
|
8631
8950
|
const resolved = resolveConnection(v2Config, effectiveConnectionName);
|
|
8951
|
+
assertExplicitProductionSelection(resolved, effectiveConnectionName);
|
|
8632
8952
|
await loadConnectionEnv(resolved, storagePath);
|
|
8633
|
-
const envLocalPath =
|
|
8953
|
+
const envLocalPath = join9(storagePath, ".env.local");
|
|
8634
8954
|
const envLocalFile = Bun.file(envLocalPath);
|
|
8635
8955
|
let legacyPassword = null;
|
|
8636
8956
|
if (await envLocalFile.exists()) {
|
|
@@ -8667,7 +8987,7 @@ var init_config = __esm(() => {
|
|
|
8667
8987
|
console.warn("Warning: Failed to load layered schema cache, falling back to config.json");
|
|
8668
8988
|
}
|
|
8669
8989
|
}
|
|
8670
|
-
|
|
8990
|
+
const parsedConfig = DbcliConfigSchema.parse({
|
|
8671
8991
|
connection: resolvedConnection,
|
|
8672
8992
|
permission: resolved.permission,
|
|
8673
8993
|
schema,
|
|
@@ -8676,10 +8996,15 @@ var init_config = __esm(() => {
|
|
|
8676
8996
|
audit: v2Config.audit,
|
|
8677
8997
|
redis: v2Config.redis
|
|
8678
8998
|
});
|
|
8999
|
+
return {
|
|
9000
|
+
...parsedConfig,
|
|
9001
|
+
effectiveConnectionName: resolved.name,
|
|
9002
|
+
...resolved.environment && { effectiveEnvironment: resolved.environment }
|
|
9003
|
+
};
|
|
8679
9004
|
}
|
|
8680
9005
|
assertNoConnectionSelectorOnV1(effectiveConnectionName);
|
|
8681
9006
|
const resolvedConfig = resolveEnvReferences(config, process.env);
|
|
8682
|
-
const envPath =
|
|
9007
|
+
const envPath = join9(storagePath, ".env.local");
|
|
8683
9008
|
const envFile = Bun.file(envPath);
|
|
8684
9009
|
if (await envFile.exists()) {
|
|
8685
9010
|
const envContent = await envFile.text();
|
|
@@ -8694,12 +9019,18 @@ var init_config = __esm(() => {
|
|
|
8694
9019
|
const file = Bun.file(path2);
|
|
8695
9020
|
const exists = await file.exists();
|
|
8696
9021
|
if (exists) {
|
|
9022
|
+
if (process.env.DBCLI_AGENT_MODE === "1") {
|
|
9023
|
+
throw new ConfigError(`Agent mode refuses legacy single-file config: ${path2}. Run the human/admin migration workflow to move it to V2 home storage first.`);
|
|
9024
|
+
}
|
|
8697
9025
|
assertNoConnectionSelectorOnV1(effectiveConnectionName);
|
|
8698
9026
|
const content = await file.text();
|
|
8699
9027
|
const raw = JSON.parse(content);
|
|
8700
9028
|
const resolved = resolveEnvReferences(raw, process.env);
|
|
8701
9029
|
return DbcliConfigSchema.parse(resolved);
|
|
8702
9030
|
}
|
|
9031
|
+
if (process.env.DBCLI_AGENT_MODE === "1") {
|
|
9032
|
+
throw new ConfigError(`Agent mode refuses a missing config: ${path2}. Run the human/admin setup workflow to provision it.`);
|
|
9033
|
+
}
|
|
8703
9034
|
return { ...DEFAULT_CONFIG };
|
|
8704
9035
|
} catch (error) {
|
|
8705
9036
|
if (error instanceof ConfigError)
|
|
@@ -8741,6 +9072,7 @@ var init_config = __esm(() => {
|
|
|
8741
9072
|
},
|
|
8742
9073
|
async write(path2, config) {
|
|
8743
9074
|
try {
|
|
9075
|
+
assertConfigMutationApproved();
|
|
8744
9076
|
this.validate(config);
|
|
8745
9077
|
const storagePath = await resolveConfigStoragePath(path2);
|
|
8746
9078
|
let isDirectory = false;
|
|
@@ -8754,9 +9086,8 @@ var init_config = __esm(() => {
|
|
|
8754
9086
|
await mkdir3(storagePath, { recursive: true });
|
|
8755
9087
|
const hasEnvReferences = isEnvReference(config.connection.password);
|
|
8756
9088
|
if (hasEnvReferences) {
|
|
8757
|
-
const configPath = join8(storagePath, "config.json");
|
|
8758
9089
|
const configJson = JSON.stringify(config, null, 2);
|
|
8759
|
-
await
|
|
9090
|
+
await writeConfigWithIntegrity(storagePath, configJson);
|
|
8760
9091
|
} else {
|
|
8761
9092
|
const password = config.connection.password;
|
|
8762
9093
|
const configWithoutPassword = {
|
|
@@ -8767,11 +9098,10 @@ var init_config = __esm(() => {
|
|
|
8767
9098
|
}
|
|
8768
9099
|
};
|
|
8769
9100
|
delete configWithoutPassword.connection.password;
|
|
8770
|
-
const configPath = join8(storagePath, "config.json");
|
|
8771
9101
|
const configJson = JSON.stringify(configWithoutPassword, null, 2);
|
|
8772
|
-
await
|
|
9102
|
+
await writeConfigWithIntegrity(storagePath, configJson);
|
|
8773
9103
|
if (password) {
|
|
8774
|
-
const envPath =
|
|
9104
|
+
const envPath = join9(storagePath, ".env.local");
|
|
8775
9105
|
const envContent = `# Database Credentials - DO NOT commit to git
|
|
8776
9106
|
|
|
8777
9107
|
DBCLI_PASSWORD=${password}
|
|
@@ -9888,7 +10218,7 @@ var init_parser = __esm(() => {
|
|
|
9888
10218
|
|
|
9889
10219
|
// src/core/saved-queries/loader.ts
|
|
9890
10220
|
import { readdir } from "fs/promises";
|
|
9891
|
-
import { join as
|
|
10221
|
+
import { join as join10, relative, sep } from "path";
|
|
9892
10222
|
async function loadSnippets(opts) {
|
|
9893
10223
|
const builtin = await walkAndParse(opts.builtinDir, "builtin");
|
|
9894
10224
|
const shared = await walkAndParse(opts.sharedDir, "shared");
|
|
@@ -9955,7 +10285,7 @@ async function collectFiles(root) {
|
|
|
9955
10285
|
async function walk(dir) {
|
|
9956
10286
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
9957
10287
|
for (const e of entries) {
|
|
9958
|
-
const full =
|
|
10288
|
+
const full = join10(dir, e.name);
|
|
9959
10289
|
if (e.isDirectory())
|
|
9960
10290
|
await walk(full);
|
|
9961
10291
|
else
|
|
@@ -9978,23 +10308,23 @@ var init_loader = __esm(() => {
|
|
|
9978
10308
|
});
|
|
9979
10309
|
|
|
9980
10310
|
// src/core/saved-queries/snippet-paths.ts
|
|
9981
|
-
import { join as
|
|
10311
|
+
import { join as join11 } from "path";
|
|
9982
10312
|
function resolveBuiltinDir() {
|
|
9983
10313
|
return packageAssetPath("snippets");
|
|
9984
10314
|
}
|
|
9985
10315
|
function resolveSnippetDirs(workspaceRoot) {
|
|
9986
10316
|
return {
|
|
9987
10317
|
builtinDir: resolveBuiltinDir(),
|
|
9988
|
-
sharedDir:
|
|
9989
|
-
localDir:
|
|
10318
|
+
sharedDir: join11(workspaceRoot, ".dbcli-shared", "queries"),
|
|
10319
|
+
localDir: join11(workspaceRoot, ".dbcli", "queries")
|
|
9990
10320
|
};
|
|
9991
10321
|
}
|
|
9992
10322
|
function snippetKeyToFile(workspaceRoot, key, source) {
|
|
9993
10323
|
const rel = key.replace(/^@/, "") + ".sql";
|
|
9994
10324
|
if (source === "builtin")
|
|
9995
|
-
return
|
|
10325
|
+
return join11(resolveBuiltinDir(), rel);
|
|
9996
10326
|
const dir = source === "shared" ? ".dbcli-shared/queries" : ".dbcli/queries";
|
|
9997
|
-
return
|
|
10327
|
+
return join11(workspaceRoot, dir, rel);
|
|
9998
10328
|
}
|
|
9999
10329
|
var init_snippet_paths = __esm(() => {
|
|
10000
10330
|
init_package_root();
|
|
@@ -10426,6 +10756,7 @@ var init_skill = __esm(() => {
|
|
|
10426
10756
|
init_package_root();
|
|
10427
10757
|
init_esm();
|
|
10428
10758
|
init_context();
|
|
10759
|
+
init_config_path();
|
|
10429
10760
|
REFERENCE_SOURCE_PATH = packageAssetPath("reference.md");
|
|
10430
10761
|
SUPPORTED_PLATFORMS = [
|
|
10431
10762
|
"claude",
|
|
@@ -10469,6 +10800,7 @@ var init_upgrade = __esm(() => {
|
|
|
10469
10800
|
init_esm();
|
|
10470
10801
|
init_colors();
|
|
10471
10802
|
init_version_check();
|
|
10803
|
+
init_config_path();
|
|
10472
10804
|
init_skill();
|
|
10473
10805
|
init_message_loader();
|
|
10474
10806
|
init_package();
|
|
@@ -10639,7 +10971,7 @@ function parseEnvDatabase(env) {
|
|
|
10639
10971
|
return null;
|
|
10640
10972
|
}
|
|
10641
10973
|
var init_env_parser = __esm(() => {
|
|
10642
|
-
|
|
10974
|
+
init_errors2();
|
|
10643
10975
|
});
|
|
10644
10976
|
|
|
10645
10977
|
// node_modules/@inquirer/core/dist/lib/key.js
|
|
@@ -12052,13 +12384,13 @@ var PromisePolyfill;
|
|
|
12052
12384
|
var init_promise_polyfill = __esm(() => {
|
|
12053
12385
|
PromisePolyfill = class PromisePolyfill extends Promise {
|
|
12054
12386
|
static withResolver() {
|
|
12055
|
-
let
|
|
12387
|
+
let resolve4;
|
|
12056
12388
|
let reject;
|
|
12057
12389
|
const promise = new Promise((res, rej) => {
|
|
12058
|
-
|
|
12390
|
+
resolve4 = res;
|
|
12059
12391
|
reject = rej;
|
|
12060
12392
|
});
|
|
12061
|
-
return { promise, resolve:
|
|
12393
|
+
return { promise, resolve: resolve4, reject };
|
|
12062
12394
|
}
|
|
12063
12395
|
};
|
|
12064
12396
|
});
|
|
@@ -12097,7 +12429,7 @@ function createPrompt(view) {
|
|
|
12097
12429
|
});
|
|
12098
12430
|
output.mute();
|
|
12099
12431
|
const screen = new ScreenManager(rl);
|
|
12100
|
-
const { promise, resolve:
|
|
12432
|
+
const { promise, resolve: resolve4, reject } = PromisePolyfill.withResolver();
|
|
12101
12433
|
const cancel = () => reject(new CancelPromptError);
|
|
12102
12434
|
if (signal) {
|
|
12103
12435
|
const abort = () => reject(new AbortPromptError({ cause: signal.reason }));
|
|
@@ -12128,7 +12460,7 @@ function createPrompt(view) {
|
|
|
12128
12460
|
try {
|
|
12129
12461
|
const nextView = view(config, (value) => {
|
|
12130
12462
|
if (effectsSettled) {
|
|
12131
|
-
|
|
12463
|
+
resolve4(value);
|
|
12132
12464
|
} else {
|
|
12133
12465
|
pendingDone = { value };
|
|
12134
12466
|
}
|
|
@@ -12151,7 +12483,7 @@ function createPrompt(view) {
|
|
|
12151
12483
|
if (pendingDone !== null) {
|
|
12152
12484
|
const { value } = pendingDone;
|
|
12153
12485
|
pendingDone = null;
|
|
12154
|
-
|
|
12486
|
+
resolve4(value);
|
|
12155
12487
|
}
|
|
12156
12488
|
});
|
|
12157
12489
|
};
|
|
@@ -12267,7 +12599,7 @@ var init_dist7 = __esm(() => {
|
|
|
12267
12599
|
|
|
12268
12600
|
// src/utils/prompts.ts
|
|
12269
12601
|
async function readLineFromStdin(prompt = "") {
|
|
12270
|
-
return new Promise((
|
|
12602
|
+
return new Promise((resolve4) => {
|
|
12271
12603
|
if (prompt) {
|
|
12272
12604
|
process.stdout.write(prompt);
|
|
12273
12605
|
}
|
|
@@ -12282,12 +12614,12 @@ async function readLineFromStdin(prompt = "") {
|
|
|
12282
12614
|
process.stdin.pause();
|
|
12283
12615
|
process.stdin.removeListener("data", onData);
|
|
12284
12616
|
process.stdin.removeListener("end", onEnd);
|
|
12285
|
-
|
|
12617
|
+
resolve4((lines[0] ?? "").trim());
|
|
12286
12618
|
}
|
|
12287
12619
|
};
|
|
12288
12620
|
const onEnd = () => {
|
|
12289
12621
|
process.stdin.removeListener("data", onData);
|
|
12290
|
-
|
|
12622
|
+
resolve4(data.trim());
|
|
12291
12623
|
};
|
|
12292
12624
|
process.stdin.on("data", onData);
|
|
12293
12625
|
process.stdin.on("end", onEnd);
|
|
@@ -15073,13 +15405,13 @@ var init_adapters = __esm(() => {
|
|
|
15073
15405
|
});
|
|
15074
15406
|
|
|
15075
15407
|
// src/commands/init.ts
|
|
15076
|
-
import { join as
|
|
15077
|
-
import { mkdir as
|
|
15408
|
+
import { join as join14 } from "path";
|
|
15409
|
+
import { mkdir as mkdir5 } from "fs/promises";
|
|
15078
15410
|
async function checkOverwrite(configPath, shouldPrompt, force) {
|
|
15079
15411
|
const storagePath = await resolveConfigStoragePath(configPath);
|
|
15080
15412
|
const fileExists = await Bun.file(configPath).exists();
|
|
15081
|
-
const dirConfigExists = await Bun.file(
|
|
15082
|
-
const storageConfigExists = await Bun.file(
|
|
15413
|
+
const dirConfigExists = await Bun.file(join14(configPath, "config.json")).exists();
|
|
15414
|
+
const storageConfigExists = await Bun.file(join14(storagePath, "config.json")).exists();
|
|
15083
15415
|
if (!fileExists && !dirConfigExists && !storageConfigExists || force)
|
|
15084
15416
|
return true;
|
|
15085
15417
|
if (shouldPrompt) {
|
|
@@ -15094,7 +15426,7 @@ async function checkOverwrite(configPath, shouldPrompt, force) {
|
|
|
15094
15426
|
}
|
|
15095
15427
|
async function handleRemove(configPath, name2) {
|
|
15096
15428
|
const storagePath = await resolveConfigStoragePath(configPath);
|
|
15097
|
-
const configFile = Bun.file(
|
|
15429
|
+
const configFile = Bun.file(join14(storagePath, "config.json"));
|
|
15098
15430
|
if (!await configFile.exists()) {
|
|
15099
15431
|
throw new Error(t("init.config_not_found"));
|
|
15100
15432
|
}
|
|
@@ -15130,7 +15462,7 @@ async function handleRename(configPath, renameArg) {
|
|
|
15130
15462
|
throw new Error(t("init.rename_invalid_format"));
|
|
15131
15463
|
}
|
|
15132
15464
|
const storagePath = await resolveConfigStoragePath(configPath);
|
|
15133
|
-
const configFile = Bun.file(
|
|
15465
|
+
const configFile = Bun.file(join14(storagePath, "config.json"));
|
|
15134
15466
|
if (!await configFile.exists()) {
|
|
15135
15467
|
throw new Error(t("init.config_not_found"));
|
|
15136
15468
|
}
|
|
@@ -15155,10 +15487,11 @@ async function handleRename(configPath, renameArg) {
|
|
|
15155
15487
|
console.log(t_vars("init.connection_renamed", { oldName, newName }));
|
|
15156
15488
|
}
|
|
15157
15489
|
async function writeV2InitConfig(configPath, connectionName, connection, permission, envFile) {
|
|
15158
|
-
const
|
|
15159
|
-
const
|
|
15490
|
+
const globalConfig = isGlobalConfigPath(configPath);
|
|
15491
|
+
const storagePath = globalConfig ? configPath : getProjectStoragePath(configPath);
|
|
15492
|
+
const configJsonPath = join14(storagePath, "config.json");
|
|
15160
15493
|
const configFile = Bun.file(configJsonPath);
|
|
15161
|
-
const projectConfigFile = Bun.file(
|
|
15494
|
+
const projectConfigFile = Bun.file(join14(configPath, "config.json"));
|
|
15162
15495
|
let existingV2 = null;
|
|
15163
15496
|
if (await configFile.exists()) {
|
|
15164
15497
|
const raw = JSON.parse(await configFile.text());
|
|
@@ -15272,9 +15605,11 @@ async function writeV2InitConfig(configPath, connectionName, connection, permiss
|
|
|
15272
15605
|
}
|
|
15273
15606
|
};
|
|
15274
15607
|
await writeV2Config(storagePath, v2Config);
|
|
15275
|
-
|
|
15276
|
-
|
|
15277
|
-
|
|
15608
|
+
if (!globalConfig) {
|
|
15609
|
+
await migrateLegacyProjectEnvLocal(configPath, storagePath);
|
|
15610
|
+
await writeProjectBinding(configPath, storagePath);
|
|
15611
|
+
}
|
|
15612
|
+
console.log(globalConfig ? t_vars("init.config_saved_global", { path: join14(configPath, "config.json") }) : t("init.config_saved"));
|
|
15278
15613
|
}
|
|
15279
15614
|
async function initCommandHandler(options, command) {
|
|
15280
15615
|
const configPath = resolveConfigPath(command);
|
|
@@ -15286,7 +15621,8 @@ async function initCommandHandler(options, command) {
|
|
|
15286
15621
|
await handleRename(configPath, options.rename);
|
|
15287
15622
|
return;
|
|
15288
15623
|
}
|
|
15289
|
-
const
|
|
15624
|
+
const isGlobalConfig = isGlobalConfigPath(configPath);
|
|
15625
|
+
const isV2Init = !!(options.connName || options.envFile || isGlobalConfig);
|
|
15290
15626
|
const connectionName = options.connName || "default";
|
|
15291
15627
|
const existingConfig = await configModule.read(configPath);
|
|
15292
15628
|
const isUsingEnvRefs = options.useEnvRefs;
|
|
@@ -15370,11 +15706,13 @@ async function initCommandHandler(options, command) {
|
|
|
15370
15706
|
await writeV2InitConfig(configPath, connectionName, configForWrite, permission2, options.envFile);
|
|
15371
15707
|
return;
|
|
15372
15708
|
}
|
|
15373
|
-
const storagePath2 = getProjectStoragePath(configPath);
|
|
15374
|
-
await
|
|
15709
|
+
const storagePath2 = isGlobalConfig ? configPath : getProjectStoragePath(configPath);
|
|
15710
|
+
await mkdir5(storagePath2, { recursive: true });
|
|
15375
15711
|
await configModule.write(storagePath2, newConfig2);
|
|
15376
|
-
|
|
15377
|
-
|
|
15712
|
+
if (!isGlobalConfig) {
|
|
15713
|
+
await migrateLegacyProjectEnvLocal(configPath, storagePath2);
|
|
15714
|
+
await writeProjectBinding(configPath, storagePath2);
|
|
15715
|
+
}
|
|
15378
15716
|
console.log(t("init.config_saved"));
|
|
15379
15717
|
return;
|
|
15380
15718
|
}
|
|
@@ -15484,11 +15822,13 @@ async function initCommandHandler(options, command) {
|
|
|
15484
15822
|
await writeV2InitConfig(configPath, connectionName, configForWrite, permission, options.envFile);
|
|
15485
15823
|
return;
|
|
15486
15824
|
}
|
|
15487
|
-
const storagePath = getProjectStoragePath(configPath);
|
|
15488
|
-
await
|
|
15825
|
+
const storagePath = isGlobalConfig ? configPath : getProjectStoragePath(configPath);
|
|
15826
|
+
await mkdir5(storagePath, { recursive: true });
|
|
15489
15827
|
await configModule.write(storagePath, newConfig);
|
|
15490
|
-
|
|
15491
|
-
|
|
15828
|
+
if (!isGlobalConfig) {
|
|
15829
|
+
await migrateLegacyProjectEnvLocal(configPath, storagePath);
|
|
15830
|
+
await writeProjectBinding(configPath, storagePath);
|
|
15831
|
+
}
|
|
15492
15832
|
console.log(t("init.config_saved"));
|
|
15493
15833
|
}
|
|
15494
15834
|
async function handleMongoDBInit(ctx) {
|
|
@@ -15569,11 +15909,14 @@ async function handleMongoDBInit(ctx) {
|
|
|
15569
15909
|
connection: mongoConfig,
|
|
15570
15910
|
permission
|
|
15571
15911
|
});
|
|
15572
|
-
const
|
|
15573
|
-
|
|
15912
|
+
const globalConfig = isGlobalConfigPath(configPath);
|
|
15913
|
+
const storagePath = globalConfig ? configPath : getProjectStoragePath(configPath);
|
|
15914
|
+
await mkdir5(storagePath, { recursive: true });
|
|
15574
15915
|
await configModule.write(storagePath, newConfig);
|
|
15575
|
-
|
|
15576
|
-
|
|
15916
|
+
if (!globalConfig) {
|
|
15917
|
+
await migrateLegacyProjectEnvLocal(configPath, storagePath);
|
|
15918
|
+
await writeProjectBinding(configPath, storagePath);
|
|
15919
|
+
}
|
|
15577
15920
|
console.log(t("init.config_saved"));
|
|
15578
15921
|
}
|
|
15579
15922
|
var VALID_PERMISSIONS, initCommand;
|
|
@@ -15585,6 +15928,7 @@ var init_init = __esm(() => {
|
|
|
15585
15928
|
init_config_v2();
|
|
15586
15929
|
init_prompts();
|
|
15587
15930
|
init_adapters();
|
|
15931
|
+
init_config_path();
|
|
15588
15932
|
init_config_binding();
|
|
15589
15933
|
VALID_PERMISSIONS = ["query-only", "read-write", "data-admin", "admin"];
|
|
15590
15934
|
initCommand = new Command("init").description("Initialize dbcli configuration with .env parsing and interactive prompts").option("--host <host>", "Database host").option("--port <port>", "Database port").option("--user <user>", "Database user").option("--password <password>", "Database password").option("--name <name>", "Database name").option("--system <system>", "Database system (postgresql, mysql, mariadb, mongodb, redis, elasticsearch)").option("--cloud-id <id>", "Elasticsearch Cloud ID").option("--api-key <key>", "Elasticsearch API Key").option("--uri <uri>", "MongoDB connection URI (mongodb://user:pass@host:27017/db?authSource=admin)").option("--auth-source <authSource>", "MongoDB auth database (default: admin when user/password are set)").option("--permission <permission>", "Permission level (query-only, read-write, data-admin, admin)", "query-only").option("--use-env-refs", "Store env var references in config instead of actual values (for CI/CD or multi-env)", false).option("--env-host <var>", "Env var name for host (with --use-env-refs)").option("--env-port <var>", "Env var name for port (with --use-env-refs)").option("--env-user <var>", "Env var name for user (with --use-env-refs)").option("--env-password <var>", "Env var name for password (with --use-env-refs)").option("--env-database <var>", "Env var name for database (with --use-env-refs)").option("--skip-test", "Skip database connection test").option("--no-interactive", "Non-interactive mode (requires all values via flags)").option("--force", "Skip overwrite confirmation if .dbcli exists").option("--conn-name <name>", "Connection name (creates v2 multi-connection config)").option("--env-file <path>", "Path to env file for this connection").option("--remove <name>", "Remove a named connection").option("--rename <names>", "Rename a connection (format: old:new)").action(async (options) => {
|
|
@@ -15776,7 +16120,7 @@ var require_utils = __commonJS((exports, module) => {
|
|
|
15776
16120
|
state[info.set] = info.to;
|
|
15777
16121
|
}
|
|
15778
16122
|
}
|
|
15779
|
-
function
|
|
16123
|
+
function readState2(line) {
|
|
15780
16124
|
let code = codeRegex(true);
|
|
15781
16125
|
let controlChars = code.exec(line);
|
|
15782
16126
|
let state = {};
|
|
@@ -15979,7 +16323,7 @@ var require_utils = __commonJS((exports, module) => {
|
|
|
15979
16323
|
let output = [];
|
|
15980
16324
|
for (let i = 0;i < input.length; i++) {
|
|
15981
16325
|
let line = rewindState(state, input[i]);
|
|
15982
|
-
state =
|
|
16326
|
+
state = readState2(line);
|
|
15983
16327
|
let temp = Object.assign({}, state);
|
|
15984
16328
|
output.push(unwindState(temp, line));
|
|
15985
16329
|
}
|
|
@@ -17672,6 +18016,7 @@ var init_list = __esm(() => {
|
|
|
17672
18016
|
init_adapters();
|
|
17673
18017
|
init_formatters();
|
|
17674
18018
|
init_config();
|
|
18019
|
+
init_config_path();
|
|
17675
18020
|
init_validation();
|
|
17676
18021
|
init_connection_selector();
|
|
17677
18022
|
ALLOWED_FORMATS = ["table", "json"];
|
|
@@ -17853,7 +18198,7 @@ class AtomicFileWriter {
|
|
|
17853
18198
|
}
|
|
17854
18199
|
|
|
17855
18200
|
// src/core/schema-writer.ts
|
|
17856
|
-
import { join as
|
|
18201
|
+
import { join as join15 } from "path";
|
|
17857
18202
|
|
|
17858
18203
|
class SchemaWriter {
|
|
17859
18204
|
dbcliPath;
|
|
@@ -17874,7 +18219,7 @@ class SchemaWriter {
|
|
|
17874
18219
|
hotSchemas[item.table] = tableSchema;
|
|
17875
18220
|
}
|
|
17876
18221
|
}
|
|
17877
|
-
await this.writer.writeJSON(
|
|
18222
|
+
await this.writer.writeJSON(join15(schemaRoot, "hot-schemas.json"), hotSchemas);
|
|
17878
18223
|
const coldGroups = {};
|
|
17879
18224
|
for (const item of mapping.cold) {
|
|
17880
18225
|
if (!coldGroups[item.file]) {
|
|
@@ -17885,10 +18230,10 @@ class SchemaWriter {
|
|
|
17885
18230
|
coldGroups[item.file][item.table] = tableSchema;
|
|
17886
18231
|
}
|
|
17887
18232
|
}
|
|
17888
|
-
const coldDir =
|
|
18233
|
+
const coldDir = join15(schemaRoot, "cold");
|
|
17889
18234
|
await this.ensureDir(coldDir);
|
|
17890
18235
|
for (const [fileName, tables] of Object.entries(coldGroups)) {
|
|
17891
|
-
const filePath =
|
|
18236
|
+
const filePath = join15(schemaRoot, fileName);
|
|
17892
18237
|
await this.writer.writeJSON(filePath, tables);
|
|
17893
18238
|
}
|
|
17894
18239
|
}
|
|
@@ -17928,6 +18273,8 @@ var init_error_recovery = () => {};
|
|
|
17928
18273
|
var init_schema_updater = __esm(() => {
|
|
17929
18274
|
init_concurrent_lock();
|
|
17930
18275
|
init_error_recovery();
|
|
18276
|
+
init_config_mutation_guard();
|
|
18277
|
+
init_config_integrity();
|
|
17931
18278
|
});
|
|
17932
18279
|
|
|
17933
18280
|
// src/core/column-index.ts
|
|
@@ -18432,8 +18779,8 @@ var init_artifact = __esm(() => {
|
|
|
18432
18779
|
});
|
|
18433
18780
|
|
|
18434
18781
|
// src/core/verification/artifact-writer.ts
|
|
18435
|
-
import { mkdir as
|
|
18436
|
-
import { join as
|
|
18782
|
+
import { mkdir as mkdir6, writeFile as writeFile2, link, unlink as unlink4 } from "fs/promises";
|
|
18783
|
+
import { join as join16 } from "path";
|
|
18437
18784
|
function pad(n, len = 2) {
|
|
18438
18785
|
return String(n).padStart(len, "0");
|
|
18439
18786
|
}
|
|
@@ -18450,12 +18797,12 @@ function verificationArtifactFilename(artifact) {
|
|
|
18450
18797
|
return `verification-${timeStamp(artifact.createdAt)}-${shortId(artifact.id)}.json`;
|
|
18451
18798
|
}
|
|
18452
18799
|
async function writeVerificationArtifact(storageDir, artifact) {
|
|
18453
|
-
const dir =
|
|
18454
|
-
await
|
|
18455
|
-
const target =
|
|
18800
|
+
const dir = join16(storageDir, VERIFICATION_DIR_RELATIVE);
|
|
18801
|
+
await mkdir6(dir, { recursive: true });
|
|
18802
|
+
const target = join16(dir, verificationArtifactFilename(artifact));
|
|
18456
18803
|
const tmp = `${target}.${process.pid}.tmp`;
|
|
18457
18804
|
try {
|
|
18458
|
-
await
|
|
18805
|
+
await writeFile2(tmp, JSON.stringify(artifact, null, 2), "utf8");
|
|
18459
18806
|
try {
|
|
18460
18807
|
await link(tmp, target);
|
|
18461
18808
|
} catch (e) {
|
|
@@ -18465,7 +18812,7 @@ async function writeVerificationArtifact(storageDir, artifact) {
|
|
|
18465
18812
|
throw e;
|
|
18466
18813
|
}
|
|
18467
18814
|
} finally {
|
|
18468
|
-
await
|
|
18815
|
+
await unlink4(tmp).catch(() => {});
|
|
18469
18816
|
}
|
|
18470
18817
|
return target;
|
|
18471
18818
|
}
|
|
@@ -18712,8 +19059,8 @@ var init_assert_artifact = __esm(() => {
|
|
|
18712
19059
|
});
|
|
18713
19060
|
|
|
18714
19061
|
// src/core/verification/reader.ts
|
|
18715
|
-
import { lstat, readdir as readdir2, readFile as readFile2 } from "fs/promises";
|
|
18716
|
-
import { join as
|
|
19062
|
+
import { lstat as lstat2, readdir as readdir2, readFile as readFile2 } from "fs/promises";
|
|
19063
|
+
import { join as join17, isAbsolute, resolve as resolve4, sep as sep2 } from "path";
|
|
18717
19064
|
function isArtifactFilename(name2) {
|
|
18718
19065
|
return /^verification-.*\.json$/.test(name2);
|
|
18719
19066
|
}
|
|
@@ -18797,7 +19144,7 @@ function validateVerificationArtifact(value) {
|
|
|
18797
19144
|
return value;
|
|
18798
19145
|
}
|
|
18799
19146
|
async function readVerificationArtifacts(storageRoot) {
|
|
18800
|
-
const storageDir =
|
|
19147
|
+
const storageDir = join17(storageRoot, VERIFICATION_DIR_RELATIVE);
|
|
18801
19148
|
let names;
|
|
18802
19149
|
try {
|
|
18803
19150
|
names = await readdir2(storageDir);
|
|
@@ -18810,9 +19157,9 @@ async function readVerificationArtifacts(storageRoot) {
|
|
|
18810
19157
|
const artifacts = [];
|
|
18811
19158
|
const invalid = [];
|
|
18812
19159
|
for (const filename of names.filter(isArtifactFilename)) {
|
|
18813
|
-
const path4 =
|
|
19160
|
+
const path4 = join17(storageDir, filename);
|
|
18814
19161
|
try {
|
|
18815
|
-
const stats = await
|
|
19162
|
+
const stats = await lstat2(path4);
|
|
18816
19163
|
if (!stats.isFile()) {
|
|
18817
19164
|
throw new Error("artifact path is not a regular file");
|
|
18818
19165
|
}
|
|
@@ -18894,15 +19241,15 @@ function looksLikePath(selector) {
|
|
|
18894
19241
|
}
|
|
18895
19242
|
function findVerificationArtifact(input, selector) {
|
|
18896
19243
|
if (looksLikePath(selector)) {
|
|
18897
|
-
const resolved =
|
|
18898
|
-
const root =
|
|
19244
|
+
const resolved = resolve4(selector);
|
|
19245
|
+
const root = resolve4(input.storageDir);
|
|
18899
19246
|
if (resolved !== root && !resolved.startsWith(root + sep2)) {
|
|
18900
19247
|
throw new VerificationArtifactSelectionError(`Path is outside the verification directory: ${resolved}`);
|
|
18901
19248
|
}
|
|
18902
|
-
const hit = input.artifacts.find((r) =>
|
|
19249
|
+
const hit = input.artifacts.find((r) => resolve4(r.path) === resolved);
|
|
18903
19250
|
if (hit)
|
|
18904
19251
|
return hit;
|
|
18905
|
-
const bad = input.invalid.find((r) =>
|
|
19252
|
+
const bad = input.invalid.find((r) => resolve4(r.path) === resolved);
|
|
18906
19253
|
if (bad) {
|
|
18907
19254
|
throw new VerificationArtifactSelectionError(`Artifact at ${bad.filename} is invalid: ${bad.error}`);
|
|
18908
19255
|
}
|
|
@@ -18939,8 +19286,8 @@ var init_reader = __esm(() => {
|
|
|
18939
19286
|
});
|
|
18940
19287
|
|
|
18941
19288
|
// src/core/verification/retention.ts
|
|
18942
|
-
import { lstat as
|
|
18943
|
-
import { basename as basename3, resolve as
|
|
19289
|
+
import { lstat as lstat3, unlink as unlink5 } from "fs/promises";
|
|
19290
|
+
import { basename as basename3, resolve as resolve5, sep as sep3 } from "path";
|
|
18944
19291
|
function parseOlderThanDays(raw) {
|
|
18945
19292
|
const match = /^(\d+)d$/.exec(raw);
|
|
18946
19293
|
if (!match) {
|
|
@@ -18996,22 +19343,22 @@ function boundError2(message) {
|
|
|
18996
19343
|
return single.length <= 200 ? single : single.slice(0, 199) + "\u2026";
|
|
18997
19344
|
}
|
|
18998
19345
|
function isInsideStorageDir(storageDir, candidatePath) {
|
|
18999
|
-
const base =
|
|
19000
|
-
const resolved =
|
|
19346
|
+
const base = resolve5(storageDir);
|
|
19347
|
+
const resolved = resolve5(candidatePath);
|
|
19001
19348
|
return resolved === base || resolved.startsWith(base + sep3);
|
|
19002
19349
|
}
|
|
19003
19350
|
function hasArtifactFilename(candidatePath) {
|
|
19004
19351
|
return /^verification-.*\.json$/.test(basename3(candidatePath));
|
|
19005
19352
|
}
|
|
19006
19353
|
async function safeDeleteCandidate(storageDir, candidate) {
|
|
19007
|
-
const resolved =
|
|
19354
|
+
const resolved = resolve5(candidate.path);
|
|
19008
19355
|
if (!isInsideStorageDir(storageDir, resolved))
|
|
19009
19356
|
return { ok: false, reason: "outside-storage-dir" };
|
|
19010
19357
|
if (!hasArtifactFilename(resolved))
|
|
19011
19358
|
return { ok: false, reason: "filename-mismatch" };
|
|
19012
19359
|
let stats;
|
|
19013
19360
|
try {
|
|
19014
|
-
stats = await
|
|
19361
|
+
stats = await lstat3(resolved);
|
|
19015
19362
|
} catch (e) {
|
|
19016
19363
|
if (e.code === "ENOENT")
|
|
19017
19364
|
return { ok: false, reason: "missing" };
|
|
@@ -19020,7 +19367,7 @@ async function safeDeleteCandidate(storageDir, candidate) {
|
|
|
19020
19367
|
if (!stats.isFile())
|
|
19021
19368
|
return { ok: false, reason: "not-regular-file" };
|
|
19022
19369
|
try {
|
|
19023
|
-
await
|
|
19370
|
+
await unlink5(resolved);
|
|
19024
19371
|
return { ok: true };
|
|
19025
19372
|
} catch (e) {
|
|
19026
19373
|
if (e.code === "ENOENT")
|
|
@@ -19037,7 +19384,7 @@ async function pruneVerificationArtifacts(storageRoot, criteria, options) {
|
|
|
19037
19384
|
if (criteria.includeInvalid) {
|
|
19038
19385
|
for (const r of read.invalid) {
|
|
19039
19386
|
try {
|
|
19040
|
-
const stats = await
|
|
19387
|
+
const stats = await lstat3(r.path);
|
|
19041
19388
|
invalidMtimes.set(r.path, stats.mtimeMs);
|
|
19042
19389
|
} catch {}
|
|
19043
19390
|
}
|
|
@@ -19290,8 +19637,8 @@ var init_core = __esm(() => {
|
|
|
19290
19637
|
|
|
19291
19638
|
// src/core/audit/lock.ts
|
|
19292
19639
|
import { hostname } from "os";
|
|
19293
|
-
import { dirname as
|
|
19294
|
-
import { mkdir as
|
|
19640
|
+
import { dirname as dirname5 } from "path";
|
|
19641
|
+
import { mkdir as mkdir7, open as open2, rm } from "fs/promises";
|
|
19295
19642
|
|
|
19296
19643
|
class AuditLockManager {
|
|
19297
19644
|
auditFilePath;
|
|
@@ -19320,7 +19667,7 @@ class AuditLockManager {
|
|
|
19320
19667
|
return true;
|
|
19321
19668
|
}
|
|
19322
19669
|
const waitTime = Math.min(backoffMs, LOCK_BACKOFF_MAX_MS);
|
|
19323
|
-
await new Promise((
|
|
19670
|
+
await new Promise((resolve6) => setTimeout(resolve6, waitTime));
|
|
19324
19671
|
backoffMs = Math.min(backoffMs * 1.5, LOCK_BACKOFF_MAX_MS);
|
|
19325
19672
|
}
|
|
19326
19673
|
}
|
|
@@ -19349,7 +19696,7 @@ class AuditLockManager {
|
|
|
19349
19696
|
}
|
|
19350
19697
|
async tryAcquireLock(operationName) {
|
|
19351
19698
|
try {
|
|
19352
|
-
await
|
|
19699
|
+
await mkdir7(dirname5(this.lockPath), { recursive: true });
|
|
19353
19700
|
const lockFile = Bun.file(this.lockPath);
|
|
19354
19701
|
if (await lockFile.exists()) {
|
|
19355
19702
|
const lockContent = await lockFile.json();
|
|
@@ -19367,7 +19714,7 @@ class AuditLockManager {
|
|
|
19367
19714
|
timestamp: Date.now(),
|
|
19368
19715
|
hostname: hostname()
|
|
19369
19716
|
};
|
|
19370
|
-
const handle = await
|
|
19717
|
+
const handle = await open2(this.lockPath, "wx");
|
|
19371
19718
|
try {
|
|
19372
19719
|
await handle.writeFile(JSON.stringify(lockData), "utf8");
|
|
19373
19720
|
} finally {
|
|
@@ -19383,7 +19730,7 @@ var LOCK_RETRY_BUDGET_MS = 200, LOCK_BACKOFF_START_MS = 5, LOCK_BACKOFF_MAX_MS =
|
|
|
19383
19730
|
var init_lock = () => {};
|
|
19384
19731
|
|
|
19385
19732
|
// src/utils/jsonl-rotation.ts
|
|
19386
|
-
import { rename as
|
|
19733
|
+
import { rename as rename3 } from "fs/promises";
|
|
19387
19734
|
function shouldRotate(stats, thresholds, nextLineByteLength) {
|
|
19388
19735
|
const bytesAfter = stats.currentSizeBytes + nextLineByteLength;
|
|
19389
19736
|
const entriesAfter = stats.currentEntryCount + 1;
|
|
@@ -19391,7 +19738,7 @@ function shouldRotate(stats, thresholds, nextLineByteLength) {
|
|
|
19391
19738
|
}
|
|
19392
19739
|
async function rotate(currentPath, previousPath) {
|
|
19393
19740
|
try {
|
|
19394
|
-
await
|
|
19741
|
+
await rename3(currentPath, previousPath);
|
|
19395
19742
|
} catch {}
|
|
19396
19743
|
}
|
|
19397
19744
|
var init_jsonl_rotation = () => {};
|
|
@@ -19402,9 +19749,9 @@ var init_rotation = __esm(() => {
|
|
|
19402
19749
|
});
|
|
19403
19750
|
|
|
19404
19751
|
// src/core/audit/logger.ts
|
|
19405
|
-
import { appendFile, mkdir as
|
|
19406
|
-
import { join as
|
|
19407
|
-
import { randomUUID } from "crypto";
|
|
19752
|
+
import { appendFile, mkdir as mkdir8, readFile as readFile3, stat as stat2 } from "fs/promises";
|
|
19753
|
+
import { join as join18 } from "path";
|
|
19754
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
19408
19755
|
|
|
19409
19756
|
class AuditLogger {
|
|
19410
19757
|
auditDir;
|
|
@@ -19429,8 +19776,8 @@ class AuditLogger {
|
|
|
19429
19776
|
this.enabled = opts.enabled;
|
|
19430
19777
|
this.maxBytes = opts.rotation.maxBytes;
|
|
19431
19778
|
this.maxEntries = opts.rotation.maxEntries;
|
|
19432
|
-
this.auditDir =
|
|
19433
|
-
this.auditFilePath =
|
|
19779
|
+
this.auditDir = join18(opts.storagePath, ".dbcli", "audit");
|
|
19780
|
+
this.auditFilePath = join18(this.auditDir, `${opts.connectionName}.jsonl`);
|
|
19434
19781
|
this.previousFilePath = `${this.auditFilePath}.1`;
|
|
19435
19782
|
this.sessionIdService = opts.sessionIdService;
|
|
19436
19783
|
this.lockManager = opts.lockManager ?? new AuditLockManager(this.auditFilePath);
|
|
@@ -19451,12 +19798,12 @@ class AuditLogger {
|
|
|
19451
19798
|
try {
|
|
19452
19799
|
const sessionId = await this.sessionIdService.resolve();
|
|
19453
19800
|
this.cachedSessionId = sessionId;
|
|
19454
|
-
await
|
|
19801
|
+
await mkdir8(this.auditDir, { recursive: true });
|
|
19455
19802
|
if (!this.writerInitialized) {
|
|
19456
19803
|
await this.syncCountersFromDisk();
|
|
19457
19804
|
this.writerInitialized = true;
|
|
19458
19805
|
}
|
|
19459
|
-
const id =
|
|
19806
|
+
const id = randomUUID2();
|
|
19460
19807
|
const ts = new Date().toISOString();
|
|
19461
19808
|
const enriched = {
|
|
19462
19809
|
...entry,
|
|
@@ -19528,7 +19875,7 @@ class AuditLogger {
|
|
|
19528
19875
|
}
|
|
19529
19876
|
async syncCountersFromDisk() {
|
|
19530
19877
|
try {
|
|
19531
|
-
const s = await
|
|
19878
|
+
const s = await stat2(this.auditFilePath);
|
|
19532
19879
|
this.currentSizeBytes = s.size;
|
|
19533
19880
|
const raw = await readFile3(this.auditFilePath, "utf8");
|
|
19534
19881
|
this.currentEntryCount = raw.split(`
|
|
@@ -19556,17 +19903,17 @@ var init_logger2 = __esm(() => {
|
|
|
19556
19903
|
});
|
|
19557
19904
|
|
|
19558
19905
|
// src/core/audit/session-id.ts
|
|
19559
|
-
import { mkdir as
|
|
19906
|
+
import { mkdir as mkdir9, readFile as readFile4, rename as rename4, stat as stat3, writeFile as writeFile3 } from "fs/promises";
|
|
19560
19907
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
19561
|
-
import { dirname as
|
|
19908
|
+
import { dirname as dirname6, join as join19 } from "path";
|
|
19562
19909
|
function generateSessionId(pid, nowMs) {
|
|
19563
19910
|
const random = randomBytes2(3).toString("hex");
|
|
19564
19911
|
return `${pid}-${nowMs}-${random}`;
|
|
19565
19912
|
}
|
|
19566
19913
|
async function readSessionIdFile(storagePath) {
|
|
19567
|
-
const target =
|
|
19914
|
+
const target = join19(storagePath, LAST_SESSION_ID_RELATIVE);
|
|
19568
19915
|
try {
|
|
19569
|
-
await
|
|
19916
|
+
await stat3(target);
|
|
19570
19917
|
} catch {
|
|
19571
19918
|
return null;
|
|
19572
19919
|
}
|
|
@@ -19586,12 +19933,12 @@ async function readSessionIdFile(storagePath) {
|
|
|
19586
19933
|
}
|
|
19587
19934
|
}
|
|
19588
19935
|
async function writeSessionIdFile(storagePath, payload) {
|
|
19589
|
-
const target =
|
|
19936
|
+
const target = join19(storagePath, LAST_SESSION_ID_RELATIVE);
|
|
19590
19937
|
const tmp = `${target}.tmp`;
|
|
19591
19938
|
try {
|
|
19592
|
-
await
|
|
19593
|
-
await
|
|
19594
|
-
await
|
|
19939
|
+
await mkdir9(dirname6(target), { recursive: true });
|
|
19940
|
+
await writeFile3(tmp, JSON.stringify(payload, null, 2), "utf8");
|
|
19941
|
+
await rename4(tmp, target);
|
|
19595
19942
|
} catch {}
|
|
19596
19943
|
}
|
|
19597
19944
|
|
|
@@ -19686,7 +20033,8 @@ async function getAuditLogger(config, configPath, connectionName) {
|
|
|
19686
20033
|
}
|
|
19687
20034
|
async function writeAuditEntry(config, commandName, options, outcome) {
|
|
19688
20035
|
try {
|
|
19689
|
-
const
|
|
20036
|
+
const connectionName = typeof options.connectionName === "string" && options.connectionName || config.effectiveConnectionName || getGlobalConnectionName() || "default";
|
|
20037
|
+
const logger = await getAuditLogger(config, options.config || ".dbcli", connectionName);
|
|
19690
20038
|
const engine = config.connection?.system || "postgresql";
|
|
19691
20039
|
const target = outcome.target || getOperationTarget(engine, commandName, options, outcome.sql);
|
|
19692
20040
|
let tier = getEngineCapability(engine, commandName).tier;
|
|
@@ -19709,7 +20057,11 @@ async function writeAuditEntry(config, commandName, options, outcome) {
|
|
|
19709
20057
|
...outcome.sql && { redacted_sql: redactSql(outcome.sql) },
|
|
19710
20058
|
...errorMessage && { error: errorMessage },
|
|
19711
20059
|
...outcome.recovery_ref && { recovery_ref: outcome.recovery_ref },
|
|
19712
|
-
metadata:
|
|
20060
|
+
metadata: {
|
|
20061
|
+
...outcome.metadata ?? {},
|
|
20062
|
+
connection_name: connectionName,
|
|
20063
|
+
environment: config.effectiveEnvironment ?? null
|
|
20064
|
+
}
|
|
19713
20065
|
};
|
|
19714
20066
|
const result = await logger.write(entry);
|
|
19715
20067
|
return "success" in result ? result.id : null;
|
|
@@ -19729,49 +20081,6 @@ var init_integration_helper = __esm(() => {
|
|
|
19729
20081
|
_loggers = new Map;
|
|
19730
20082
|
});
|
|
19731
20083
|
|
|
19732
|
-
// src/utils/levenshtein-distance.ts
|
|
19733
|
-
function levenshteinDistance(a, b) {
|
|
19734
|
-
const lenA = a.length;
|
|
19735
|
-
const lenB = b.length;
|
|
19736
|
-
const dp = [];
|
|
19737
|
-
for (let i = 0;i <= lenB; i++) {
|
|
19738
|
-
const row = [];
|
|
19739
|
-
for (let j2 = 0;j2 <= lenA; j2++) {
|
|
19740
|
-
row[j2] = 0;
|
|
19741
|
-
}
|
|
19742
|
-
dp[i] = row;
|
|
19743
|
-
}
|
|
19744
|
-
for (let i = 0;i <= lenB; i++) {
|
|
19745
|
-
const row = dp[i];
|
|
19746
|
-
if (row !== undefined) {
|
|
19747
|
-
row[0] = i;
|
|
19748
|
-
}
|
|
19749
|
-
}
|
|
19750
|
-
for (let j2 = 0;j2 <= lenA; j2++) {
|
|
19751
|
-
const firstRow = dp[0];
|
|
19752
|
-
if (firstRow !== undefined) {
|
|
19753
|
-
firstRow[j2] = j2;
|
|
19754
|
-
}
|
|
19755
|
-
}
|
|
19756
|
-
for (let i = 1;i <= lenB; i++) {
|
|
19757
|
-
const currentRow = dp[i];
|
|
19758
|
-
const prevRow = dp[i - 1];
|
|
19759
|
-
if (!currentRow || !prevRow)
|
|
19760
|
-
continue;
|
|
19761
|
-
for (let j2 = 1;j2 <= lenA; j2++) {
|
|
19762
|
-
if (b.charAt(i - 1) === a.charAt(j2 - 1)) {
|
|
19763
|
-
currentRow[j2] = prevRow[j2 - 1] ?? 0;
|
|
19764
|
-
} else {
|
|
19765
|
-
const sub = (prevRow[j2 - 1] ?? 0) + 1;
|
|
19766
|
-
const ins = (currentRow[j2 - 1] ?? 0) + 1;
|
|
19767
|
-
const del = (prevRow[j2] ?? 0) + 1;
|
|
19768
|
-
currentRow[j2] = Math.min(sub, ins, del);
|
|
19769
|
-
}
|
|
19770
|
-
}
|
|
19771
|
-
}
|
|
19772
|
-
return dp[lenB]?.[lenA] ?? 0;
|
|
19773
|
-
}
|
|
19774
|
-
|
|
19775
20084
|
// src/utils/error-suggester.ts
|
|
19776
20085
|
async function suggestTableName(errorMessage, adapter) {
|
|
19777
20086
|
try {
|
|
@@ -21356,14 +21665,14 @@ var init_render_markdown = __esm(() => {
|
|
|
21356
21665
|
});
|
|
21357
21666
|
|
|
21358
21667
|
// src/core/recovery/last-envelope.ts
|
|
21359
|
-
import { writeFile as
|
|
21360
|
-
import { dirname as
|
|
21361
|
-
import { randomUUID as
|
|
21668
|
+
import { writeFile as writeFile4, readFile as readFile5, rename as rename5, mkdir as mkdir10, stat as stat4 } from "fs/promises";
|
|
21669
|
+
import { dirname as dirname7, join as join20 } from "path";
|
|
21670
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
21362
21671
|
function sanitizeCommandSummary(argv) {
|
|
21363
21672
|
return redactArgv(argv);
|
|
21364
21673
|
}
|
|
21365
|
-
async function writeLastEnvelope(cwd, envelope, argv, now = () => new Date, id =
|
|
21366
|
-
const target =
|
|
21674
|
+
async function writeLastEnvelope(cwd, envelope, argv, now = () => new Date, id = randomUUID3(), auditRef) {
|
|
21675
|
+
const target = join20(cwd, LAST_ENVELOPE_PATH);
|
|
21367
21676
|
const tmp = `${target}.tmp`;
|
|
21368
21677
|
const payload = {
|
|
21369
21678
|
schemaVersion: 1,
|
|
@@ -21375,15 +21684,15 @@ async function writeLastEnvelope(cwd, envelope, argv, now = () => new Date, id =
|
|
|
21375
21684
|
envelope
|
|
21376
21685
|
};
|
|
21377
21686
|
try {
|
|
21378
|
-
await
|
|
21379
|
-
await
|
|
21380
|
-
await
|
|
21687
|
+
await mkdir10(dirname7(target), { recursive: true });
|
|
21688
|
+
await writeFile4(tmp, JSON.stringify(payload, null, 2), "utf8");
|
|
21689
|
+
await rename5(tmp, target);
|
|
21381
21690
|
} catch {}
|
|
21382
21691
|
}
|
|
21383
21692
|
async function readLastEnvelope(cwd) {
|
|
21384
|
-
const target =
|
|
21693
|
+
const target = join20(cwd, LAST_ENVELOPE_PATH);
|
|
21385
21694
|
try {
|
|
21386
|
-
await
|
|
21695
|
+
await stat4(target);
|
|
21387
21696
|
} catch {
|
|
21388
21697
|
return null;
|
|
21389
21698
|
}
|
|
@@ -21395,9 +21704,9 @@ async function readLastEnvelope(cwd) {
|
|
|
21395
21704
|
}
|
|
21396
21705
|
}
|
|
21397
21706
|
async function readLastEnvelopeRaw(cwd) {
|
|
21398
|
-
const target =
|
|
21707
|
+
const target = join20(cwd, LAST_ENVELOPE_PATH);
|
|
21399
21708
|
try {
|
|
21400
|
-
await
|
|
21709
|
+
await stat4(target);
|
|
21401
21710
|
} catch {
|
|
21402
21711
|
return null;
|
|
21403
21712
|
}
|
|
@@ -21420,13 +21729,13 @@ var init_last_envelope = __esm(() => {
|
|
|
21420
21729
|
|
|
21421
21730
|
// src/core/recovery/emit.ts
|
|
21422
21731
|
import { writeFileSync, mkdirSync, renameSync, writeSync } from "fs";
|
|
21423
|
-
import { dirname as
|
|
21424
|
-
import { randomUUID as
|
|
21732
|
+
import { dirname as dirname8, join as join21 } from "path";
|
|
21733
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
21425
21734
|
function emitRecoveryEnvelope(error, ctx, options = {}) {
|
|
21426
21735
|
const envelope = classifyError(error, ctx);
|
|
21427
21736
|
const cwd = options.cwd ?? process.cwd();
|
|
21428
21737
|
const argv = options.argv ?? buildArgvFromProcess();
|
|
21429
|
-
const envelopeId = options.envelopeId ??
|
|
21738
|
+
const envelopeId = options.envelopeId ?? randomUUID4();
|
|
21430
21739
|
writeLastEnvelopeSync(cwd, envelope, argv, envelopeId, options.auditRef);
|
|
21431
21740
|
writeSync(1, renderJson(envelope, { brief: options.brief === true }) + `
|
|
21432
21741
|
`);
|
|
@@ -21437,7 +21746,7 @@ function buildArgvFromProcess() {
|
|
|
21437
21746
|
return ["dbcli", ...userArgs];
|
|
21438
21747
|
}
|
|
21439
21748
|
function writeLastEnvelopeSync(cwd, envelope, argv, id, auditRef) {
|
|
21440
|
-
const target =
|
|
21749
|
+
const target = join21(cwd, LAST_ENVELOPE_PATH);
|
|
21441
21750
|
const tmp = `${target}.tmp`;
|
|
21442
21751
|
const payload = {
|
|
21443
21752
|
schemaVersion: 1,
|
|
@@ -21449,7 +21758,7 @@ function writeLastEnvelopeSync(cwd, envelope, argv, id, auditRef) {
|
|
|
21449
21758
|
envelope
|
|
21450
21759
|
};
|
|
21451
21760
|
try {
|
|
21452
|
-
mkdirSync(
|
|
21761
|
+
mkdirSync(dirname8(target), { recursive: true });
|
|
21453
21762
|
writeFileSync(tmp, JSON.stringify(payload, null, 2), "utf8");
|
|
21454
21763
|
renameSync(tmp, target);
|
|
21455
21764
|
} catch {}
|
|
@@ -22193,8 +22502,8 @@ function renderStep2(r) {
|
|
|
22193
22502
|
var NEXT_SCHEMA_VERSION = 1, STEP_RESULT_SUMMARY_FIELD_CAP = 4096;
|
|
22194
22503
|
|
|
22195
22504
|
// src/core/recovery/next-step-schema.ts
|
|
22196
|
-
import { stat as
|
|
22197
|
-
import { resolve as
|
|
22505
|
+
import { stat as stat5 } from "fs/promises";
|
|
22506
|
+
import { resolve as resolve6 } from "path";
|
|
22198
22507
|
function summarizeZodError(err) {
|
|
22199
22508
|
return err.issues.map((iss) => {
|
|
22200
22509
|
const path4 = iss.path.join(".") || "<root>";
|
|
@@ -22213,9 +22522,9 @@ async function loadStepResultSummary(arg, cwd) {
|
|
|
22213
22522
|
}
|
|
22214
22523
|
let raw;
|
|
22215
22524
|
if (arg.startsWith("@")) {
|
|
22216
|
-
const path4 =
|
|
22525
|
+
const path4 = resolve6(cwd, arg.slice(1));
|
|
22217
22526
|
try {
|
|
22218
|
-
await
|
|
22527
|
+
await stat5(path4);
|
|
22219
22528
|
} catch {
|
|
22220
22529
|
return { ok: false, reason: `--result @<file>: ${path4} not readable.` };
|
|
22221
22530
|
}
|
|
@@ -22880,6 +23189,7 @@ var init_schema = __esm(() => {
|
|
|
22880
23189
|
init_validation();
|
|
22881
23190
|
init_connection_selector();
|
|
22882
23191
|
init_error_suggester();
|
|
23192
|
+
init_config_path();
|
|
22883
23193
|
ALLOWED_FORMATS2 = ["table", "json"];
|
|
22884
23194
|
schemaCommand = new Command().name("schema").description("Display table schema, scan database schema, or refresh existing schema with detected changes").argument("[table]", "Optional: table name to inspect (if omitted, scans all tables)").option("--format <format>", "Output format: table (default) or json", "table").option("--config <path>", "Path to .dbcli config file", ".dbcli").addOption(createConnectionSelectorOption()).option("--refresh", "Refresh schema by detecting changes from database", false).option("--reset", "Clear all existing schema data and re-fetch from database", false).option("--force", "Skip confirmation when updating schema data", false).option("--sample-size <n>", "MongoDB only: number of documents to sample for schema inference (default 50, max 1000). Ignored on SQL connections.").option("--sample-method <method>", 'MongoDB only: "random" (default, uses $sample) or "natural" (uses find().limit()). Ignored on SQL connections.', "random").option("--recovery", "On failure, emit a structured recovery envelope to stdout (suppresses human stderr message)", false).action(schemaAction);
|
|
22885
23195
|
});
|
|
@@ -22937,14 +23247,14 @@ var init_html_formatter = __esm(() => {
|
|
|
22937
23247
|
});
|
|
22938
23248
|
|
|
22939
23249
|
// src/utils/opener.ts
|
|
22940
|
-
import
|
|
23250
|
+
import open3 from "open";
|
|
22941
23251
|
async function openInBrowser(target) {
|
|
22942
23252
|
if (process.env.DBCLI_NO_OPEN === "1" || false) {
|
|
22943
23253
|
console.log(`[Opener] DBCLI_NO_OPEN is set. Skipping browser launch for: ${target}`);
|
|
22944
23254
|
return;
|
|
22945
23255
|
}
|
|
22946
23256
|
try {
|
|
22947
|
-
await
|
|
23257
|
+
await open3(target);
|
|
22948
23258
|
} catch (err) {
|
|
22949
23259
|
console.error(`[Opener] Failed to open ${target}:`, err);
|
|
22950
23260
|
}
|
|
@@ -23369,7 +23679,7 @@ var init_query_size_guard = __esm(() => {
|
|
|
23369
23679
|
// src/commands/query.ts
|
|
23370
23680
|
import crypto3 from "crypto";
|
|
23371
23681
|
import { tmpdir } from "os";
|
|
23372
|
-
import { join as
|
|
23682
|
+
import { join as join22 } from "path";
|
|
23373
23683
|
function requireSqlConnection2(connection) {
|
|
23374
23684
|
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
23375
23685
|
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
@@ -23680,7 +23990,7 @@ async function presentSingleResult(query, options, execution, tableCellLimit) {
|
|
|
23680
23990
|
} : {}
|
|
23681
23991
|
});
|
|
23682
23992
|
if (options.ui) {
|
|
23683
|
-
const tempPath =
|
|
23993
|
+
const tempPath = join22(tmpdir(), `dbcli-query-${Date.now()}.html`);
|
|
23684
23994
|
await Bun.write(tempPath, html);
|
|
23685
23995
|
await openInBrowser(tempPath);
|
|
23686
23996
|
} else {
|
|
@@ -23893,6 +24203,7 @@ var init_query = __esm(() => {
|
|
|
23893
24203
|
init_query_executor();
|
|
23894
24204
|
init_config();
|
|
23895
24205
|
init_blacklist_validator();
|
|
24206
|
+
init_config_path();
|
|
23896
24207
|
init_validation();
|
|
23897
24208
|
init_applied_limit();
|
|
23898
24209
|
init_integration_helper();
|
|
@@ -24335,6 +24646,7 @@ var ALLOWED_FORMATS4;
|
|
|
24335
24646
|
var init_plan = __esm(() => {
|
|
24336
24647
|
init_query_risk_analyzer();
|
|
24337
24648
|
init_config();
|
|
24649
|
+
init_config_path();
|
|
24338
24650
|
init_validation();
|
|
24339
24651
|
init_integration_helper();
|
|
24340
24652
|
ALLOWED_FORMATS4 = ["text", "json"];
|
|
@@ -24438,7 +24750,7 @@ __export(exports_q_mongo, {
|
|
|
24438
24750
|
qMongoBranch: () => qMongoBranch
|
|
24439
24751
|
});
|
|
24440
24752
|
import { tmpdir as tmpdir2 } from "os";
|
|
24441
|
-
import { join as
|
|
24753
|
+
import { join as join23 } from "path";
|
|
24442
24754
|
async function qMongoBranch(snippet, prepared, options, config) {
|
|
24443
24755
|
const collection = options.collection ?? prepared.execHints?.collection;
|
|
24444
24756
|
if (!collection) {
|
|
@@ -24470,7 +24782,7 @@ async function qMongoBranch(snippet, prepared, options, config) {
|
|
|
24470
24782
|
...securityNotification ? { securityNotification } : {}
|
|
24471
24783
|
});
|
|
24472
24784
|
if (options.ui) {
|
|
24473
|
-
const tempPath =
|
|
24785
|
+
const tempPath = join23(tmpdir2(), `dbcli-report-${Date.now()}.html`);
|
|
24474
24786
|
await Bun.write(tempPath, html);
|
|
24475
24787
|
await openInBrowser(tempPath);
|
|
24476
24788
|
} else {
|
|
@@ -24511,7 +24823,7 @@ var init_q_mongo = __esm(() => {
|
|
|
24511
24823
|
// src/commands/q.ts
|
|
24512
24824
|
import crypto4 from "crypto";
|
|
24513
24825
|
import { tmpdir as tmpdir3 } from "os";
|
|
24514
|
-
import { join as
|
|
24826
|
+
import { join as join24 } from "path";
|
|
24515
24827
|
function formatDryRun(input) {
|
|
24516
24828
|
const lines = ["Dry-run preview (no execution):"];
|
|
24517
24829
|
if (input.family === "es") {
|
|
@@ -24612,7 +24924,7 @@ async function qCommand(name2, options, command) {
|
|
|
24612
24924
|
...securityNotification ? { securityNotification } : {}
|
|
24613
24925
|
});
|
|
24614
24926
|
if (options.ui) {
|
|
24615
|
-
const tempPath =
|
|
24927
|
+
const tempPath = join24(tmpdir3(), `dbcli-report-${Date.now()}.html`);
|
|
24616
24928
|
await Bun.write(tempPath, html);
|
|
24617
24929
|
await openInBrowser(tempPath);
|
|
24618
24930
|
} else {
|
|
@@ -24840,6 +25152,7 @@ var init_q = __esm(() => {
|
|
|
24840
25152
|
init_message_loader();
|
|
24841
25153
|
init_adapters();
|
|
24842
25154
|
init_config();
|
|
25155
|
+
init_config_path();
|
|
24843
25156
|
init_blacklist_validator();
|
|
24844
25157
|
init_blacklist();
|
|
24845
25158
|
init_permission_guard();
|
|
@@ -25060,8 +25373,8 @@ var exports_queries_rename = {};
|
|
|
25060
25373
|
__export(exports_queries_rename, {
|
|
25061
25374
|
queriesRename: () => queriesRename
|
|
25062
25375
|
});
|
|
25063
|
-
import { rename as
|
|
25064
|
-
import { dirname as
|
|
25376
|
+
import { rename as rename6, mkdir as mkdir11 } from "fs/promises";
|
|
25377
|
+
import { dirname as dirname9 } from "path";
|
|
25065
25378
|
async function queriesRename(oldName, newName, options = {}) {
|
|
25066
25379
|
if (!oldName.startsWith("@") || !newName.startsWith("@")) {
|
|
25067
25380
|
throw new Error(`Both names must start with '@'`);
|
|
@@ -25082,8 +25395,8 @@ async function queriesRename(oldName, newName, options = {}) {
|
|
|
25082
25395
|
for (const v of local) {
|
|
25083
25396
|
const dst = snippetKeyToFile(cwd, newName, "local");
|
|
25084
25397
|
const dstWithSuffix = preserveEngineSuffix(v.query.file, dst);
|
|
25085
|
-
await
|
|
25086
|
-
await
|
|
25398
|
+
await mkdir11(dirname9(dstWithSuffix), { recursive: true });
|
|
25399
|
+
await rename6(v.query.file, dstWithSuffix);
|
|
25087
25400
|
await rewriteFrontmatterName(dstWithSuffix, newName.slice(1));
|
|
25088
25401
|
console.log(`renamed ${v.query.file} \u2192 ${dstWithSuffix}`);
|
|
25089
25402
|
}
|
|
@@ -25108,8 +25421,8 @@ var exports_queries_copy = {};
|
|
|
25108
25421
|
__export(exports_queries_copy, {
|
|
25109
25422
|
queriesCopy: () => queriesCopy
|
|
25110
25423
|
});
|
|
25111
|
-
import { mkdir as
|
|
25112
|
-
import { dirname as
|
|
25424
|
+
import { mkdir as mkdir12, copyFile } from "fs/promises";
|
|
25425
|
+
import { dirname as dirname10, basename as basename4 } from "path";
|
|
25113
25426
|
async function queriesCopy(src, dst, options = {}) {
|
|
25114
25427
|
if (!src.startsWith("@") || !dst.startsWith("@")) {
|
|
25115
25428
|
throw new Error(`Both names must start with '@'`);
|
|
@@ -25127,7 +25440,7 @@ async function queriesCopy(src, dst, options = {}) {
|
|
|
25127
25440
|
}
|
|
25128
25441
|
for (const v of variants) {
|
|
25129
25442
|
const dstFile = mapEngineSuffix(v.query.file, snippetKeyToFile(cwd, dst, "local"));
|
|
25130
|
-
await
|
|
25443
|
+
await mkdir12(dirname10(dstFile), { recursive: true });
|
|
25131
25444
|
await copyFile(v.query.file, dstFile);
|
|
25132
25445
|
console.log(`copied ${v.query.file} \u2192 ${dstFile}`);
|
|
25133
25446
|
}
|
|
@@ -25145,21 +25458,21 @@ var exports_queries_import = {};
|
|
|
25145
25458
|
__export(exports_queries_import, {
|
|
25146
25459
|
queriesImport: () => queriesImport
|
|
25147
25460
|
});
|
|
25148
|
-
import { stat as
|
|
25149
|
-
import { basename as basename5, join as
|
|
25461
|
+
import { stat as stat6, mkdir as mkdir13, copyFile as copyFile2 } from "fs/promises";
|
|
25462
|
+
import { basename as basename5, join as join25, extname as extname2 } from "path";
|
|
25150
25463
|
async function queriesImport(filePath, options = {}) {
|
|
25151
25464
|
const cwd = options.cwd ?? process.cwd();
|
|
25152
|
-
if (
|
|
25465
|
+
if (extname2(filePath) !== ".sql") {
|
|
25153
25466
|
throw new Error(`Expected .sql file, got ${filePath}`);
|
|
25154
25467
|
}
|
|
25155
|
-
await
|
|
25468
|
+
await stat6(filePath);
|
|
25156
25469
|
const text2 = await Bun.file(filePath).text();
|
|
25157
25470
|
const baseName = options.as ? options.as.replace(/^@/, "") : basename5(filePath, ".sql").replace(/\.(postgres|mysql)$/, "");
|
|
25158
25471
|
const key = "@" + baseName;
|
|
25159
25472
|
parseSavedQuery({ key, file: filePath, source: "local", text: text2 });
|
|
25160
|
-
const targetDir =
|
|
25161
|
-
await
|
|
25162
|
-
const target =
|
|
25473
|
+
const targetDir = join25(cwd, ".dbcli/queries");
|
|
25474
|
+
await mkdir13(targetDir, { recursive: true });
|
|
25475
|
+
const target = join25(targetDir, basename5(filePath));
|
|
25163
25476
|
if (await Bun.file(target).exists()) {
|
|
25164
25477
|
if (!options.force) {
|
|
25165
25478
|
const ok = await dist_default4({ message: `Overwrite ${target}?`, default: false });
|
|
@@ -25180,7 +25493,7 @@ var exports_queries_export = {};
|
|
|
25180
25493
|
__export(exports_queries_export, {
|
|
25181
25494
|
queriesExport: () => queriesExport
|
|
25182
25495
|
});
|
|
25183
|
-
import { writeFile as
|
|
25496
|
+
import { writeFile as writeFile5 } from "fs/promises";
|
|
25184
25497
|
async function queriesExport(name2, options = {}) {
|
|
25185
25498
|
const cwd = options.cwd ?? process.cwd();
|
|
25186
25499
|
const dirs = resolveSnippetDirs(cwd);
|
|
@@ -25201,7 +25514,7 @@ async function queriesExport(name2, options = {}) {
|
|
|
25201
25514
|
}
|
|
25202
25515
|
const text2 = await Bun.file(chosen[0].query.file).text();
|
|
25203
25516
|
if (options.output) {
|
|
25204
|
-
await
|
|
25517
|
+
await writeFile5(options.output, text2, "utf8");
|
|
25205
25518
|
console.log(`wrote ${options.output}`);
|
|
25206
25519
|
} else {
|
|
25207
25520
|
process.stdout.write(text2);
|
|
@@ -25212,12 +25525,12 @@ var init_queries_export = __esm(() => {
|
|
|
25212
25525
|
});
|
|
25213
25526
|
|
|
25214
25527
|
// src/commands/queries.ts
|
|
25215
|
-
import { mkdir as
|
|
25216
|
-
import { dirname as
|
|
25528
|
+
import { mkdir as mkdir14, writeFile as writeFile6 } from "fs/promises";
|
|
25529
|
+
import { dirname as dirname11 } from "path";
|
|
25217
25530
|
import { spawn } from "child_process";
|
|
25218
|
-
async function deriveEngine() {
|
|
25531
|
+
async function deriveEngine(command) {
|
|
25219
25532
|
try {
|
|
25220
|
-
const cfg = await configModule.read(resolveConfigPath(
|
|
25533
|
+
const cfg = await configModule.read(resolveConfigPath(command));
|
|
25221
25534
|
if (cfg.connection) {
|
|
25222
25535
|
return mapSystemToEngine(cfg.connection.system);
|
|
25223
25536
|
}
|
|
@@ -25279,10 +25592,10 @@ function matchesFolded(r, opts) {
|
|
|
25279
25592
|
return false;
|
|
25280
25593
|
return true;
|
|
25281
25594
|
}
|
|
25282
|
-
async function queriesShow(name2, options) {
|
|
25595
|
+
async function queriesShow(name2, options, command) {
|
|
25283
25596
|
const map = await loadSnippets(resolveSnippetDirs(process.cwd()));
|
|
25284
25597
|
try {
|
|
25285
|
-
const engine = await deriveEngine();
|
|
25598
|
+
const engine = await deriveEngine(command);
|
|
25286
25599
|
const snippet = resolveByName(map, name2, engine);
|
|
25287
25600
|
if (options.format === "json") {
|
|
25288
25601
|
console.log(JSON.stringify({ ...snippetToJson(snippet), sql: snippet.query.sqlBody.trim() }, null, 2));
|
|
@@ -25321,8 +25634,8 @@ async function queriesNew(name2, options) {
|
|
|
25321
25634
|
process.exit(1);
|
|
25322
25635
|
return;
|
|
25323
25636
|
}
|
|
25324
|
-
await
|
|
25325
|
-
await
|
|
25637
|
+
await mkdir14(dirname11(file), { recursive: true });
|
|
25638
|
+
await writeFile6(file, scaffold(name2), "utf8");
|
|
25326
25639
|
console.log(`Created ${file}`);
|
|
25327
25640
|
if (source === "shared")
|
|
25328
25641
|
console.log(t("queries.first_run_hint"));
|
|
@@ -25383,7 +25696,7 @@ async function queriesCheck(options) {
|
|
|
25383
25696
|
console.log(`\u2713 ${total} snippets parsed successfully`);
|
|
25384
25697
|
}
|
|
25385
25698
|
}
|
|
25386
|
-
async function queriesSearch(keywords, options) {
|
|
25699
|
+
async function queriesSearch(keywords, options, command) {
|
|
25387
25700
|
const query = keywords.join(" ").trim();
|
|
25388
25701
|
if (!query) {
|
|
25389
25702
|
console.error(t("queries.search_no_keywords"));
|
|
@@ -25408,7 +25721,7 @@ async function queriesSearch(keywords, options) {
|
|
|
25408
25721
|
if (options.engine && options.engine !== "all") {
|
|
25409
25722
|
engineFilter = options.engine;
|
|
25410
25723
|
} else if (!options.engine) {
|
|
25411
|
-
const inferred = await deriveEngineOrNull();
|
|
25724
|
+
const inferred = await deriveEngineOrNull(command);
|
|
25412
25725
|
if (inferred)
|
|
25413
25726
|
engineFilter = inferred;
|
|
25414
25727
|
else
|
|
@@ -25431,9 +25744,9 @@ async function queriesSearch(keywords, options) {
|
|
|
25431
25744
|
}
|
|
25432
25745
|
renderSearchTable(hits, options.includeInternal === true);
|
|
25433
25746
|
}
|
|
25434
|
-
async function deriveEngineOrNull() {
|
|
25747
|
+
async function deriveEngineOrNull(command) {
|
|
25435
25748
|
try {
|
|
25436
|
-
const cfg = await configModule.read(resolveConfigPath(
|
|
25749
|
+
const cfg = await configModule.read(resolveConfigPath(command));
|
|
25437
25750
|
if (cfg.connection) {
|
|
25438
25751
|
return mapSystemToEngine(cfg.connection.system);
|
|
25439
25752
|
}
|
|
@@ -25466,7 +25779,7 @@ function renderSearchTable(hits, includeScore) {
|
|
|
25466
25779
|
for (const r of rows)
|
|
25467
25780
|
console.log(fmt(r));
|
|
25468
25781
|
}
|
|
25469
|
-
async function queriesSuggest(intent, options) {
|
|
25782
|
+
async function queriesSuggest(intent, options, command) {
|
|
25470
25783
|
const map = await loadSnippets(resolveSnippetDirs(process.cwd()));
|
|
25471
25784
|
const folded = [...map.entries()].map(([key, variants]) => foldVariants(key, variants));
|
|
25472
25785
|
const availableIntents = collectIntentPrefixes(folded);
|
|
@@ -25492,7 +25805,7 @@ async function queriesSuggest(intent, options) {
|
|
|
25492
25805
|
if (options.engine && options.engine !== "all") {
|
|
25493
25806
|
engineFilter = options.engine;
|
|
25494
25807
|
} else if (!options.engine) {
|
|
25495
|
-
const inferred = await deriveEngineOrNull();
|
|
25808
|
+
const inferred = await deriveEngineOrNull(command);
|
|
25496
25809
|
if (inferred)
|
|
25497
25810
|
engineFilter = inferred;
|
|
25498
25811
|
else
|
|
@@ -25561,9 +25874,9 @@ function scaffold(name2) {
|
|
|
25561
25874
|
}
|
|
25562
25875
|
async function openInEditor(file) {
|
|
25563
25876
|
const editor = process.env.EDITOR || "vi";
|
|
25564
|
-
await new Promise((
|
|
25877
|
+
await new Promise((resolve7, reject) => {
|
|
25565
25878
|
const child = spawn(editor, [file], { stdio: "inherit" });
|
|
25566
|
-
child.on("exit", (code) => code === 0 ?
|
|
25879
|
+
child.on("exit", (code) => code === 0 ? resolve7() : reject(new Error(`Editor exited with ${code}`)));
|
|
25567
25880
|
});
|
|
25568
25881
|
}
|
|
25569
25882
|
var INTENT_RE_CLI, queriesCommand;
|
|
@@ -25571,6 +25884,7 @@ var init_queries = __esm(() => {
|
|
|
25571
25884
|
init_esm();
|
|
25572
25885
|
init_message_loader();
|
|
25573
25886
|
init_config();
|
|
25887
|
+
init_config_path();
|
|
25574
25888
|
init_saved_queries();
|
|
25575
25889
|
init_fold();
|
|
25576
25890
|
init_search();
|
|
@@ -25580,8 +25894,8 @@ var init_queries = __esm(() => {
|
|
|
25580
25894
|
queriesCommand.command("list").description(t("queries.list_description")).option("--format <type>", "Output format: table, json, csv", "table").option("--tag <tag>", "Filter by tag").option("--engine <engine>", "Filter by engine: postgres | mysql").option("--source <source>", "Filter by source: local | shared").action(async (options) => {
|
|
25581
25895
|
await queriesList(options);
|
|
25582
25896
|
});
|
|
25583
|
-
queriesCommand.command("show <name>").description(t("queries.show_description")).option("--format <type>", "Output format: table, json, csv", "table").action(async (name2, options) => {
|
|
25584
|
-
await queriesShow(name2, options);
|
|
25897
|
+
queriesCommand.command("show <name>").description(t("queries.show_description")).option("--format <type>", "Output format: table, json, csv", "table").action(async (name2, options, command) => {
|
|
25898
|
+
await queriesShow(name2, options, command);
|
|
25585
25899
|
});
|
|
25586
25900
|
queriesCommand.command("new <name>").description(t("queries.new_description")).option("--local", "Create under .dbcli/queries (gitignored) instead of .dbcli-shared/").option("--edit", "Open the created file in $EDITOR").action(async (name2, options) => {
|
|
25587
25901
|
await queriesNew(name2, options);
|
|
@@ -25637,11 +25951,11 @@ var init_queries = __esm(() => {
|
|
|
25637
25951
|
process.exit(1);
|
|
25638
25952
|
}
|
|
25639
25953
|
});
|
|
25640
|
-
queriesCommand.command("search [keywords...]").description(t("queries.search_description")).option("--format <type>", "Output format: table, json", "table").option("--engine <engine>", "Filter: postgres | mysql | redis | elasticsearch | all").option("--source <source>", "Filter: local | shared | builtin | all").option("--limit <n>", "Max results (default 10)").option("--include-internal", "Show ranking score").action(async (keywords, options) => {
|
|
25641
|
-
await queriesSearch(keywords, options);
|
|
25954
|
+
queriesCommand.command("search [keywords...]").description(t("queries.search_description")).option("--format <type>", "Output format: table, json", "table").option("--engine <engine>", "Filter: postgres | mysql | redis | elasticsearch | all").option("--source <source>", "Filter: local | shared | builtin | all").option("--limit <n>", "Max results (default 10)").option("--include-internal", "Show ranking score").action(async (keywords, options, command) => {
|
|
25955
|
+
await queriesSearch(keywords, options, command);
|
|
25642
25956
|
});
|
|
25643
|
-
queriesCommand.command("suggest [intent]").description(t("queries.suggest_description")).option("--format <type>", "Output format: table, json", "table").option("--engine <engine>", "Filter: postgres | mysql | redis | elasticsearch | all").option("--source <source>", "Filter: local | shared | builtin | all").action(async (intent, options) => {
|
|
25644
|
-
await queriesSuggest(intent, options);
|
|
25957
|
+
queriesCommand.command("suggest [intent]").description(t("queries.suggest_description")).option("--format <type>", "Output format: table, json", "table").option("--engine <engine>", "Filter: postgres | mysql | redis | elasticsearch | all").option("--source <source>", "Filter: local | shared | builtin | all").action(async (intent, options, command) => {
|
|
25958
|
+
await queriesSuggest(intent, options, command);
|
|
25645
25959
|
});
|
|
25646
25960
|
});
|
|
25647
25961
|
|
|
@@ -26723,6 +27037,7 @@ var init_dml_plan4 = __esm(() => {
|
|
|
26723
27037
|
init_query_risk_analyzer();
|
|
26724
27038
|
init_config();
|
|
26725
27039
|
init_plan();
|
|
27040
|
+
init_config_path();
|
|
26726
27041
|
init_validation();
|
|
26727
27042
|
init_dml_plan_sql();
|
|
26728
27043
|
init_dml_plan();
|
|
@@ -27003,6 +27318,7 @@ var init_insert = __esm(() => {
|
|
|
27003
27318
|
init_permission_guard();
|
|
27004
27319
|
init_blacklist_validator();
|
|
27005
27320
|
init_blacklist();
|
|
27321
|
+
init_config_path();
|
|
27006
27322
|
init_dml_plan4();
|
|
27007
27323
|
init_integration_helper();
|
|
27008
27324
|
});
|
|
@@ -27305,6 +27621,7 @@ var init_update = __esm(() => {
|
|
|
27305
27621
|
init_permission_guard();
|
|
27306
27622
|
init_blacklist_validator();
|
|
27307
27623
|
init_blacklist();
|
|
27624
|
+
init_config_path();
|
|
27308
27625
|
init_dml_plan4();
|
|
27309
27626
|
init_integration_helper();
|
|
27310
27627
|
});
|
|
@@ -27591,6 +27908,7 @@ var init_delete = __esm(() => {
|
|
|
27591
27908
|
init_permission_guard();
|
|
27592
27909
|
init_blacklist_validator();
|
|
27593
27910
|
init_blacklist();
|
|
27911
|
+
init_config_path();
|
|
27594
27912
|
init_dml_plan4();
|
|
27595
27913
|
init_integration_helper();
|
|
27596
27914
|
});
|
|
@@ -28012,6 +28330,7 @@ var init_export = __esm(() => {
|
|
|
28012
28330
|
init_query_executor();
|
|
28013
28331
|
init_config();
|
|
28014
28332
|
init_prompts();
|
|
28333
|
+
init_config_path();
|
|
28015
28334
|
init_blacklist_validator();
|
|
28016
28335
|
init_applied_limit();
|
|
28017
28336
|
init_integration_helper();
|
|
@@ -28037,15 +28356,15 @@ var init_types6 = __esm(() => {
|
|
|
28037
28356
|
});
|
|
28038
28357
|
|
|
28039
28358
|
// src/core/agent-tasks/task-paths.ts
|
|
28040
|
-
import { join as
|
|
28359
|
+
import { join as join26 } from "path";
|
|
28041
28360
|
function resolveBuiltinDir2() {
|
|
28042
28361
|
return packageAssetPath("tasks");
|
|
28043
28362
|
}
|
|
28044
28363
|
function resolveAgentTaskDirs(workspaceRoot) {
|
|
28045
28364
|
return {
|
|
28046
28365
|
builtinDir: resolveBuiltinDir2(),
|
|
28047
|
-
sharedDir:
|
|
28048
|
-
localDir:
|
|
28366
|
+
sharedDir: join26(workspaceRoot, ".dbcli-shared", "tasks"),
|
|
28367
|
+
localDir: join26(workspaceRoot, ".dbcli", "tasks")
|
|
28049
28368
|
};
|
|
28050
28369
|
}
|
|
28051
28370
|
var init_task_paths = __esm(() => {
|
|
@@ -28192,7 +28511,7 @@ var init_parser2 = __esm(() => {
|
|
|
28192
28511
|
|
|
28193
28512
|
// src/core/agent-tasks/loader.ts
|
|
28194
28513
|
import { readdir as readdir3 } from "fs/promises";
|
|
28195
|
-
import { join as
|
|
28514
|
+
import { join as join27, relative as relative2, sep as sep4 } from "path";
|
|
28196
28515
|
async function loadAgentTasks(opts, flags) {
|
|
28197
28516
|
const errors3 = [];
|
|
28198
28517
|
const builtin = await walkAndParse2(opts.builtinDir, "builtin", errors3);
|
|
@@ -28249,7 +28568,7 @@ async function safeCollectMd(root) {
|
|
|
28249
28568
|
return;
|
|
28250
28569
|
}
|
|
28251
28570
|
for (const e of entries) {
|
|
28252
|
-
const full =
|
|
28571
|
+
const full = join27(dir, e.name);
|
|
28253
28572
|
if (e.isDirectory())
|
|
28254
28573
|
await walk(full);
|
|
28255
28574
|
else
|
|
@@ -28760,9 +29079,19 @@ function getOrInitBlacklist(config) {
|
|
|
28760
29079
|
columns: config.blacklist.columns ? { ...config.blacklist.columns } : {}
|
|
28761
29080
|
};
|
|
28762
29081
|
}
|
|
28763
|
-
async function blacklistList(configPath) {
|
|
29082
|
+
async function blacklistList(configPath, format = "text") {
|
|
28764
29083
|
const config = await configModule.read(configPath);
|
|
28765
29084
|
const blacklist = getOrInitBlacklist(config);
|
|
29085
|
+
const audit = auditBlacklistPatterns(blacklist);
|
|
29086
|
+
if (format === "json") {
|
|
29087
|
+
const result = {
|
|
29088
|
+
tables: blacklist.tables,
|
|
29089
|
+
columns: blacklist.columns,
|
|
29090
|
+
warnings: audit.warnings
|
|
29091
|
+
};
|
|
29092
|
+
console.log(JSON.stringify(result, null, 2));
|
|
29093
|
+
return;
|
|
29094
|
+
}
|
|
28766
29095
|
console.log(t("blacklist.list_title"));
|
|
28767
29096
|
console.log("\u2500".repeat(40));
|
|
28768
29097
|
if (blacklist.tables.length === 0 && Object.keys(blacklist.columns).length === 0) {
|
|
@@ -28781,7 +29110,6 @@ async function blacklistList(configPath) {
|
|
|
28781
29110
|
} else {
|
|
28782
29111
|
console.log(`${t("blacklist.columns_label")}: {}`);
|
|
28783
29112
|
}
|
|
28784
|
-
const audit = auditBlacklistPatterns(blacklist);
|
|
28785
29113
|
for (const w2 of audit.warnings) {
|
|
28786
29114
|
console.error(`\u26A0 blacklist.columns["${w2.collection}"]: '${w2.raw}' is ignored on mongo connections (${w2.reason}).`);
|
|
28787
29115
|
}
|
|
@@ -28871,12 +29199,15 @@ var init_blacklist2 = __esm(() => {
|
|
|
28871
29199
|
init_esm();
|
|
28872
29200
|
init_message_loader();
|
|
28873
29201
|
init_config();
|
|
29202
|
+
init_validation();
|
|
28874
29203
|
VALID_TABLE_NAME = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
28875
29204
|
VALID_COLUMN_NAME = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
28876
29205
|
blacklistCommand = new Command("blacklist").description(t("blacklist.description"));
|
|
28877
|
-
blacklistCommand.command("list").description(t("blacklist.list_title")).option("--config <path>", "Path to .dbcli config file", DEFAULT_CONFIG_PATH).action(async (options) => {
|
|
29206
|
+
blacklistCommand.command("list").description(t("blacklist.list_title")).option("--config <path>", "Path to .dbcli config file", DEFAULT_CONFIG_PATH).option("--format <type>", "Output format: text, json", "text").action(async (options) => {
|
|
28878
29207
|
try {
|
|
28879
|
-
|
|
29208
|
+
const format = options.format || "text";
|
|
29209
|
+
validateFormat(format, ["text", "json"], "blacklist list");
|
|
29210
|
+
await blacklistList(options.config || DEFAULT_CONFIG_PATH, format);
|
|
28880
29211
|
} catch (error) {
|
|
28881
29212
|
console.error(error.message);
|
|
28882
29213
|
process.exit(1);
|
|
@@ -81115,7 +81446,7 @@ ${fence}`;
|
|
|
81115
81446
|
|
|
81116
81447
|
// src/commands/diff.ts
|
|
81117
81448
|
import crypto9 from "crypto";
|
|
81118
|
-
import { resolve as
|
|
81449
|
+
import { resolve as resolve7 } from "path";
|
|
81119
81450
|
function requireSqlConnection8(connection) {
|
|
81120
81451
|
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
81121
81452
|
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
@@ -81142,7 +81473,7 @@ async function expandOrmPaths(inputs) {
|
|
|
81142
81473
|
const expanded = new Set;
|
|
81143
81474
|
for (const path4 of paths) {
|
|
81144
81475
|
if (!hasGlobMagic(path4)) {
|
|
81145
|
-
expanded.add(
|
|
81476
|
+
expanded.add(resolve7(path4));
|
|
81146
81477
|
continue;
|
|
81147
81478
|
}
|
|
81148
81479
|
const matches = await Array.fromAsync(new Bun.Glob(path4).scan({
|
|
@@ -81154,7 +81485,7 @@ async function expandOrmPaths(inputs) {
|
|
|
81154
81485
|
throw new Error(`ORM schema glob matched no files: ${path4}`);
|
|
81155
81486
|
}
|
|
81156
81487
|
for (const match of matches)
|
|
81157
|
-
expanded.add(
|
|
81488
|
+
expanded.add(resolve7(match));
|
|
81158
81489
|
}
|
|
81159
81490
|
return [...expanded].sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
|
|
81160
81491
|
}
|
|
@@ -81490,6 +81821,7 @@ var ALLOWED_FORMATS8, statusCommand;
|
|
|
81490
81821
|
var init_status2 = __esm(() => {
|
|
81491
81822
|
init_esm();
|
|
81492
81823
|
init_config();
|
|
81824
|
+
init_config_path();
|
|
81493
81825
|
init_validation();
|
|
81494
81826
|
ALLOWED_FORMATS8 = ["text", "json"];
|
|
81495
81827
|
statusCommand = new Command("status").description("Show current configuration status (safe for AI agents, no credentials exposed)").option("--format <type>", "Output format: text, json", "json").action(async (options) => {
|
|
@@ -81601,7 +81933,7 @@ var init_collect_snippets = __esm(() => {
|
|
|
81601
81933
|
});
|
|
81602
81934
|
|
|
81603
81935
|
// src/core/inspect/collect-schema-cache.ts
|
|
81604
|
-
import { join as
|
|
81936
|
+
import { join as join28 } from "path";
|
|
81605
81937
|
async function collectSchemaCache(opts) {
|
|
81606
81938
|
const warnings = [];
|
|
81607
81939
|
if (opts.system && !SQL_SYSTEMS2.includes(opts.system)) {
|
|
@@ -81611,7 +81943,7 @@ async function collectSchemaCache(opts) {
|
|
|
81611
81943
|
};
|
|
81612
81944
|
}
|
|
81613
81945
|
const root = resolveSchemaPath(opts.dbcliPath, opts.connectionName);
|
|
81614
|
-
const indexPath =
|
|
81946
|
+
const indexPath = join28(root, "index.json");
|
|
81615
81947
|
const file = Bun.file(indexPath);
|
|
81616
81948
|
if (!await file.exists()) {
|
|
81617
81949
|
return { section: { available: false }, warnings };
|
|
@@ -81693,7 +82025,7 @@ var SAMPLE_SIZE = 10;
|
|
|
81693
82025
|
// src/core/inspect/collect-version.ts
|
|
81694
82026
|
async function collectVersion(adapter, timeoutMs) {
|
|
81695
82027
|
const probe = adapter.getServerVersion().then((v) => typeof v === "string" && v !== "unknown" ? v : null, () => null);
|
|
81696
|
-
const timeout = new Promise((
|
|
82028
|
+
const timeout = new Promise((resolve8) => setTimeout(() => resolve8(null), timeoutMs));
|
|
81697
82029
|
return Promise.race([probe, timeout]);
|
|
81698
82030
|
}
|
|
81699
82031
|
|
|
@@ -81881,7 +82213,7 @@ function buildHints(snap, ctx = {}) {
|
|
|
81881
82213
|
}
|
|
81882
82214
|
|
|
81883
82215
|
// src/core/inspect/collector.ts
|
|
81884
|
-
import { join as
|
|
82216
|
+
import { join as join29 } from "path";
|
|
81885
82217
|
async function collectInspect(opts) {
|
|
81886
82218
|
const warnings = [];
|
|
81887
82219
|
let config = null;
|
|
@@ -81923,7 +82255,7 @@ async function collectInspect(opts) {
|
|
|
81923
82255
|
probeError = err;
|
|
81924
82256
|
return "error";
|
|
81925
82257
|
});
|
|
81926
|
-
const timer = new Promise((
|
|
82258
|
+
const timer = new Promise((resolve8) => setTimeout(() => resolve8("timeout"), probeTimeout));
|
|
81927
82259
|
const outcome = await Promise.race([probe, timer]);
|
|
81928
82260
|
if (outcome === "timeout") {
|
|
81929
82261
|
warnings.push(`probe: timed out after ${probeTimeout}ms`);
|
|
@@ -81965,11 +82297,11 @@ async function collectInspect(opts) {
|
|
|
81965
82297
|
return { ...snapWithoutSuggestions, suggestedCommands, hints, warnings };
|
|
81966
82298
|
}
|
|
81967
82299
|
async function hasConfig(configPath) {
|
|
81968
|
-
if (await Bun.file(
|
|
82300
|
+
if (await Bun.file(join29(configPath, "config.json")).exists())
|
|
81969
82301
|
return true;
|
|
81970
82302
|
if (await Bun.file(configPath).exists()) {
|
|
81971
|
-
const
|
|
81972
|
-
return
|
|
82303
|
+
const stat7 = await Bun.file(configPath).stat().catch(() => null);
|
|
82304
|
+
return stat7?.isFile() === true;
|
|
81973
82305
|
}
|
|
81974
82306
|
return false;
|
|
81975
82307
|
}
|
|
@@ -82132,6 +82464,7 @@ var ALLOWED_FORMATS9, inspectCommand;
|
|
|
82132
82464
|
var init_inspect2 = __esm(() => {
|
|
82133
82465
|
init_esm();
|
|
82134
82466
|
init_message_loader();
|
|
82467
|
+
init_config_path();
|
|
82135
82468
|
init_validation();
|
|
82136
82469
|
init_inspect();
|
|
82137
82470
|
init_config();
|
|
@@ -82311,7 +82644,7 @@ async function runDiagnostic(input) {
|
|
|
82311
82644
|
const indexParams = family === "es" && prepared.execHints?.index ? [prepared.execHints.index] : [];
|
|
82312
82645
|
return input.adapter.execute(prepared.driver.sql, family === "sql" ? prepared.driver.values : indexParams);
|
|
82313
82646
|
})();
|
|
82314
|
-
const timer = new Promise((
|
|
82647
|
+
const timer = new Promise((resolve8) => setTimeout(() => resolve8("timeout"), input.timeoutMs));
|
|
82315
82648
|
let outcome;
|
|
82316
82649
|
let errorMessage2 = null;
|
|
82317
82650
|
try {
|
|
@@ -82617,6 +82950,7 @@ var ALLOWED_FORMATS10, reportCommand;
|
|
|
82617
82950
|
var init_report2 = __esm(() => {
|
|
82618
82951
|
init_esm();
|
|
82619
82952
|
init_message_loader();
|
|
82953
|
+
init_config_path();
|
|
82620
82954
|
init_validation();
|
|
82621
82955
|
init_config();
|
|
82622
82956
|
init_integration_helper();
|
|
@@ -83058,6 +83392,7 @@ var ALLOWED_FORMATS11, guideCommand;
|
|
|
83058
83392
|
var init_guide2 = __esm(() => {
|
|
83059
83393
|
init_esm();
|
|
83060
83394
|
init_message_loader();
|
|
83395
|
+
init_config_path();
|
|
83061
83396
|
init_validation();
|
|
83062
83397
|
init_config();
|
|
83063
83398
|
init_integration_helper();
|
|
@@ -83946,6 +84281,7 @@ var FORMATS, CONFIDENCES;
|
|
|
83946
84281
|
var init_guide_missing_index = __esm(() => {
|
|
83947
84282
|
init_adapters();
|
|
83948
84283
|
init_config();
|
|
84284
|
+
init_config_path();
|
|
83949
84285
|
init_saved_queries();
|
|
83950
84286
|
init_missing_index();
|
|
83951
84287
|
init_parse_sql();
|
|
@@ -84249,6 +84585,7 @@ var init_explain3 = __esm(() => {
|
|
|
84249
84585
|
init_esm();
|
|
84250
84586
|
init_adapters();
|
|
84251
84587
|
init_config();
|
|
84588
|
+
init_config_path();
|
|
84252
84589
|
init_runner2();
|
|
84253
84590
|
init_bulk_runner();
|
|
84254
84591
|
init_explain2();
|
|
@@ -85049,9 +85386,9 @@ function relationBindings(statement) {
|
|
|
85049
85386
|
const bindings = [];
|
|
85050
85387
|
for (const item of statement.from) {
|
|
85051
85388
|
const source = item;
|
|
85052
|
-
const
|
|
85053
|
-
const nullExtendsPrevious =
|
|
85054
|
-
const nullExtendsCurrent =
|
|
85389
|
+
const join30 = typeof source.join === "string" ? source.join.toUpperCase() : "";
|
|
85390
|
+
const nullExtendsPrevious = join30.startsWith("RIGHT") || join30.startsWith("FULL");
|
|
85391
|
+
const nullExtendsCurrent = join30.startsWith("LEFT") || join30.startsWith("FULL");
|
|
85055
85392
|
if (nullExtendsPrevious) {
|
|
85056
85393
|
for (const binding of bindings)
|
|
85057
85394
|
binding.nullExtended = true;
|
|
@@ -86220,6 +86557,7 @@ var init_lint = __esm(() => {
|
|
|
86220
86557
|
init_context2();
|
|
86221
86558
|
init_saved_queries();
|
|
86222
86559
|
init_integration_helper();
|
|
86560
|
+
init_config_path();
|
|
86223
86561
|
FORMATS2 = ["text", "json", "markdown"];
|
|
86224
86562
|
SEVERITIES = ["info", "warn", "error"];
|
|
86225
86563
|
SQL_SYSTEMS5 = ["postgresql", "mysql", "mariadb"];
|
|
@@ -86245,9 +86583,9 @@ var init_lint = __esm(() => {
|
|
|
86245
86583
|
});
|
|
86246
86584
|
|
|
86247
86585
|
// src/core/result-snapshot/fingerprint.ts
|
|
86248
|
-
import { createHash as
|
|
86586
|
+
import { createHash as createHash3 } from "crypto";
|
|
86249
86587
|
function sha256(input) {
|
|
86250
|
-
return
|
|
86588
|
+
return createHash3("sha256").update(input).digest("hex");
|
|
86251
86589
|
}
|
|
86252
86590
|
function isNumeric(values) {
|
|
86253
86591
|
return values.length > 0 && values.every((v) => typeof v === "number");
|
|
@@ -86366,7 +86704,7 @@ var init_serializer = __esm(() => {
|
|
|
86366
86704
|
});
|
|
86367
86705
|
|
|
86368
86706
|
// src/commands/snapshot.ts
|
|
86369
|
-
import { join as
|
|
86707
|
+
import { join as join30 } from "path";
|
|
86370
86708
|
function requireSqlConnection9(connection) {
|
|
86371
86709
|
if (!SQL_SYSTEMS6.includes(connection.system)) {
|
|
86372
86710
|
throw new Error(`snapshot currently supports SQL engines only, got: ${connection.system}`);
|
|
@@ -86379,13 +86717,14 @@ function pad2(n) {
|
|
|
86379
86717
|
function defaultSnapshotPath() {
|
|
86380
86718
|
const d = new Date;
|
|
86381
86719
|
const stamp = `${d.getFullYear()}${pad2(d.getMonth() + 1)}${pad2(d.getDate())}-${pad2(d.getHours())}${pad2(d.getMinutes())}${pad2(d.getSeconds())}`;
|
|
86382
|
-
return
|
|
86720
|
+
return join30(".dbcli", "snapshots", `snap-${stamp}.json`);
|
|
86383
86721
|
}
|
|
86384
86722
|
var ALLOWED_FORMATS12, SQL_SYSTEMS6, snapshotCommand;
|
|
86385
86723
|
var init_snapshot = __esm(() => {
|
|
86386
86724
|
init_esm();
|
|
86387
86725
|
init_adapters();
|
|
86388
86726
|
init_config();
|
|
86727
|
+
init_config_path();
|
|
86389
86728
|
init_validation();
|
|
86390
86729
|
init_blacklist_validator();
|
|
86391
86730
|
init_query_executor();
|
|
@@ -86630,6 +86969,7 @@ var init_assert = __esm(() => {
|
|
|
86630
86969
|
init_esm();
|
|
86631
86970
|
init_adapters();
|
|
86632
86971
|
init_config();
|
|
86972
|
+
init_config_path();
|
|
86633
86973
|
init_validation();
|
|
86634
86974
|
init_blacklist_validator();
|
|
86635
86975
|
init_query_executor();
|
|
@@ -88676,6 +89016,7 @@ var init_verify2 = __esm(() => {
|
|
|
88676
89016
|
init_esm();
|
|
88677
89017
|
init_adapters();
|
|
88678
89018
|
init_config();
|
|
89019
|
+
init_config_path();
|
|
88679
89020
|
init_blacklist_validator();
|
|
88680
89021
|
init_query_executor();
|
|
88681
89022
|
init_query_risk_analyzer();
|
|
@@ -89104,11 +89445,11 @@ var init_envelope_schema = __esm(() => {
|
|
|
89104
89445
|
});
|
|
89105
89446
|
|
|
89106
89447
|
// src/commands/recover.ts
|
|
89107
|
-
import { stat as
|
|
89108
|
-
import { resolve as
|
|
89448
|
+
import { stat as stat7 } from "fs/promises";
|
|
89449
|
+
import { resolve as resolve8 } from "path";
|
|
89109
89450
|
async function resolveApplySource(opts) {
|
|
89110
89451
|
if (opts.from !== undefined) {
|
|
89111
|
-
const path5 =
|
|
89452
|
+
const path5 = resolve8(opts.cwd, opts.from);
|
|
89112
89453
|
let raw;
|
|
89113
89454
|
try {
|
|
89114
89455
|
raw = await Bun.file(path5).text();
|
|
@@ -89128,7 +89469,7 @@ async function resolveApplySource(opts) {
|
|
|
89128
89469
|
}
|
|
89129
89470
|
const saved2 = r3.value;
|
|
89130
89471
|
try {
|
|
89131
|
-
await
|
|
89472
|
+
await stat7(saved2.cwd);
|
|
89132
89473
|
} catch {
|
|
89133
89474
|
throw new RecoverCliError(`--from ${opts.from}: saved cwd '${saved2.cwd}' no longer exists.`, EXIT_CODE.malformed);
|
|
89134
89475
|
}
|
|
@@ -89162,7 +89503,7 @@ async function resolveApplySource(opts) {
|
|
|
89162
89503
|
}
|
|
89163
89504
|
const saved = r.value;
|
|
89164
89505
|
try {
|
|
89165
|
-
await
|
|
89506
|
+
await stat7(saved.cwd);
|
|
89166
89507
|
} catch {
|
|
89167
89508
|
throw new RecoverCliError(`Auto-saved ${LAST_ENVELOPE_PATH} references cwd '${saved.cwd}' that no longer exists.`, EXIT_CODE.malformed);
|
|
89168
89509
|
}
|
|
@@ -89364,13 +89705,13 @@ Verification artifact: ${verificationArtifactPath}`);
|
|
|
89364
89705
|
});
|
|
89365
89706
|
|
|
89366
89707
|
// src/commands/audit.ts
|
|
89367
|
-
import { rm as rm3, stat as
|
|
89368
|
-
import { join as
|
|
89708
|
+
import { rm as rm3, stat as stat8 } from "fs/promises";
|
|
89709
|
+
import { join as join31 } from "path";
|
|
89369
89710
|
async function resolveAuditPaths(configPath, config) {
|
|
89370
89711
|
const storagePath = await resolveConfigStoragePath(configPath);
|
|
89371
89712
|
const connName = config.effectiveConnectionName || getGlobalConnectionName() || "default";
|
|
89372
|
-
const auditDir =
|
|
89373
|
-
const auditFile =
|
|
89713
|
+
const auditDir = join31(storagePath, ".dbcli", "audit");
|
|
89714
|
+
const auditFile = join31(auditDir, `${connName}.jsonl`);
|
|
89374
89715
|
return { auditDir, connectionName: connName, auditFile };
|
|
89375
89716
|
}
|
|
89376
89717
|
function isAuditDisabled(config) {
|
|
@@ -89519,7 +89860,7 @@ function renderHealthTable(h) {
|
|
|
89519
89860
|
`);
|
|
89520
89861
|
}
|
|
89521
89862
|
function readLineFromStdinWithStderrPrompt(prompt) {
|
|
89522
|
-
return new Promise((
|
|
89863
|
+
return new Promise((resolve9) => {
|
|
89523
89864
|
process.stderr.write(prompt);
|
|
89524
89865
|
const chunks = [];
|
|
89525
89866
|
let data = "";
|
|
@@ -89532,12 +89873,12 @@ function readLineFromStdinWithStderrPrompt(prompt) {
|
|
|
89532
89873
|
process.stdin.pause();
|
|
89533
89874
|
process.stdin.removeListener("data", onData);
|
|
89534
89875
|
process.stdin.removeListener("end", onEnd);
|
|
89535
|
-
|
|
89876
|
+
resolve9((lines[0] ?? "").trim());
|
|
89536
89877
|
}
|
|
89537
89878
|
};
|
|
89538
89879
|
const onEnd = () => {
|
|
89539
89880
|
process.stdin.removeListener("data", onData);
|
|
89540
|
-
|
|
89881
|
+
resolve9(data.trim());
|
|
89541
89882
|
};
|
|
89542
89883
|
process.stdin.on("data", onData);
|
|
89543
89884
|
process.stdin.on("end", onEnd);
|
|
@@ -89546,7 +89887,7 @@ function readLineFromStdinWithStderrPrompt(prompt) {
|
|
|
89546
89887
|
}
|
|
89547
89888
|
async function statAuditFile(file) {
|
|
89548
89889
|
try {
|
|
89549
|
-
const s = await
|
|
89890
|
+
const s = await stat8(file);
|
|
89550
89891
|
const entries = (await readEntries(file)).length;
|
|
89551
89892
|
return { entries, size: formatBytes(s.size) };
|
|
89552
89893
|
} catch {
|
|
@@ -89557,6 +89898,7 @@ var ALLOWED_FORMATS18, DEFAULT_TAIL_N = 10, MAX_TAIL_N = 1e4, SHORT_ID_LEN = 8,
|
|
|
89557
89898
|
var init_audit = __esm(() => {
|
|
89558
89899
|
init_esm();
|
|
89559
89900
|
init_message_loader();
|
|
89901
|
+
init_config_path();
|
|
89560
89902
|
init_validation();
|
|
89561
89903
|
init_config();
|
|
89562
89904
|
init_config_binding();
|
|
@@ -89796,8 +90138,62 @@ var init_audit = __esm(() => {
|
|
|
89796
90138
|
});
|
|
89797
90139
|
});
|
|
89798
90140
|
|
|
90141
|
+
// src/utils/runtime-info.ts
|
|
90142
|
+
import { join as join32, normalize, relative as relative3, sep as sep5 } from "path";
|
|
90143
|
+
function normalized(path5) {
|
|
90144
|
+
return normalize(path5).replaceAll("\\", "/");
|
|
90145
|
+
}
|
|
90146
|
+
function isWithin(child, parent) {
|
|
90147
|
+
const rel = relative3(parent, child);
|
|
90148
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep5}`) && !rel.startsWith("/");
|
|
90149
|
+
}
|
|
90150
|
+
function inferRuntimeSource(launcherPath, packageRoot) {
|
|
90151
|
+
const launcher = normalized(launcherPath);
|
|
90152
|
+
const root = normalized(packageRoot);
|
|
90153
|
+
if (launcher.includes("/.bunx/") || launcher.includes("/bunx/") || root.includes("/.bun/install/cache/")) {
|
|
90154
|
+
return "bunx";
|
|
90155
|
+
}
|
|
90156
|
+
if (isWithin(launcher, `${root}/src`) || isWithin(launcher, `${root}/scripts`)) {
|
|
90157
|
+
return "workspace";
|
|
90158
|
+
}
|
|
90159
|
+
if (isWithin(launcher, `${root}/dist`))
|
|
90160
|
+
return "installed";
|
|
90161
|
+
return "unknown";
|
|
90162
|
+
}
|
|
90163
|
+
async function collectRuntimeInfo(packageVersion) {
|
|
90164
|
+
const packageRoot = findPackageRoot();
|
|
90165
|
+
const launcherPath = process.argv[1] ?? "unknown";
|
|
90166
|
+
let packageFileVersion = null;
|
|
90167
|
+
try {
|
|
90168
|
+
const packageFile = Bun.file(join32(packageRoot, "package.json"));
|
|
90169
|
+
if (await packageFile.exists()) {
|
|
90170
|
+
const parsed = await packageFile.json();
|
|
90171
|
+
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
90172
|
+
packageFileVersion = parsed.version;
|
|
90173
|
+
}
|
|
90174
|
+
}
|
|
90175
|
+
} catch {}
|
|
90176
|
+
const versions = process.versions;
|
|
90177
|
+
const runtimeName = versions.bun ? "bun" : versions.node ? "node" : "other";
|
|
90178
|
+
const runtimeVersion = versions[runtimeName] ?? "unknown";
|
|
90179
|
+
return {
|
|
90180
|
+
executablePath: process.execPath,
|
|
90181
|
+
launcherPath,
|
|
90182
|
+
packageRoot,
|
|
90183
|
+
packageVersion,
|
|
90184
|
+
packageFileVersion,
|
|
90185
|
+
runtimeName,
|
|
90186
|
+
runtimeVersion,
|
|
90187
|
+
source: inferRuntimeSource(launcherPath, packageRoot),
|
|
90188
|
+
versionMismatch: packageFileVersion !== null && packageFileVersion !== packageVersion
|
|
90189
|
+
};
|
|
90190
|
+
}
|
|
90191
|
+
var init_runtime_info = __esm(() => {
|
|
90192
|
+
init_package_root();
|
|
90193
|
+
});
|
|
90194
|
+
|
|
89799
90195
|
// src/commands/doctor.ts
|
|
89800
|
-
import { join as
|
|
90196
|
+
import { join as join33 } from "path";
|
|
89801
90197
|
import { resolveSrv as resolveSrv2 } from "dns/promises";
|
|
89802
90198
|
function requireSqlConnection12(connection) {
|
|
89803
90199
|
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
@@ -89805,6 +90201,87 @@ function requireSqlConnection12(connection) {
|
|
|
89805
90201
|
}
|
|
89806
90202
|
return connection;
|
|
89807
90203
|
}
|
|
90204
|
+
function safeSqlIdentifier(value) {
|
|
90205
|
+
return /^[A-Za-z_][A-Za-z0-9_$]*(?:\.[A-Za-z_][A-Za-z0-9_$]*)*$/.test(value) ? value : null;
|
|
90206
|
+
}
|
|
90207
|
+
function boundedSampleCommands(system, table) {
|
|
90208
|
+
if (system === "mongodb") {
|
|
90209
|
+
const collection = shellQuote(table);
|
|
90210
|
+
const query = shellQuote("{}");
|
|
90211
|
+
return {
|
|
90212
|
+
dryRun: `dbcli schema ${collection} --format json`,
|
|
90213
|
+
apply: `dbcli query ${query} --collection ${collection} --limit 100 --format json`
|
|
90214
|
+
};
|
|
90215
|
+
}
|
|
90216
|
+
if (system === "elasticsearch") {
|
|
90217
|
+
const index = shellQuote(table);
|
|
90218
|
+
const query = shellQuote('{"query":{"match_all":{}}}');
|
|
90219
|
+
return {
|
|
90220
|
+
dryRun: `dbcli schema ${index} --format json`,
|
|
90221
|
+
apply: `dbcli query ${query} --collection ${index} --limit 100 --format json`
|
|
90222
|
+
};
|
|
90223
|
+
}
|
|
90224
|
+
const identifier = safeSqlIdentifier(table);
|
|
90225
|
+
if (!identifier) {
|
|
90226
|
+
return {
|
|
90227
|
+
dryRun: `dbcli schema ${shellQuote(table)} --format json`
|
|
90228
|
+
};
|
|
90229
|
+
}
|
|
90230
|
+
const sql = `SELECT * FROM ${identifier} LIMIT 100`;
|
|
90231
|
+
const command = shellQuote(sql);
|
|
90232
|
+
return {
|
|
90233
|
+
dryRun: `dbcli plan ${command} --format json`,
|
|
90234
|
+
apply: `dbcli query ${command} --format json`
|
|
90235
|
+
};
|
|
90236
|
+
}
|
|
90237
|
+
function buildDoctorRemediationPlan(results) {
|
|
90238
|
+
const steps = [];
|
|
90239
|
+
for (const result of results) {
|
|
90240
|
+
if (result.label === "Blacklist completeness" && result.status === "warn") {
|
|
90241
|
+
const prefix = "Consider protecting: ";
|
|
90242
|
+
const candidates = result.message.startsWith(prefix) ? result.message.slice(prefix.length).split(", ").filter(Boolean) : [];
|
|
90243
|
+
for (const candidate of candidates) {
|
|
90244
|
+
const [table, column] = candidate.split(".");
|
|
90245
|
+
if (!table || !column)
|
|
90246
|
+
continue;
|
|
90247
|
+
steps.push({
|
|
90248
|
+
kind: "blacklist-candidate",
|
|
90249
|
+
status: "candidate",
|
|
90250
|
+
rationale: `Sensitive-looking column '${candidate}' is not currently protected.`,
|
|
90251
|
+
dryRun: `dbcli schema ${shellQuote(table)} --format json`,
|
|
90252
|
+
apply: `dbcli blacklist column add ${shellQuote(candidate)}`,
|
|
90253
|
+
requiresHumanConfirmation: true
|
|
90254
|
+
});
|
|
90255
|
+
}
|
|
90256
|
+
}
|
|
90257
|
+
if (result.label === "Schema cache" && result.status === "warn") {
|
|
90258
|
+
steps.push({
|
|
90259
|
+
kind: "schema-refresh",
|
|
90260
|
+
status: "candidate",
|
|
90261
|
+
rationale: result.message,
|
|
90262
|
+
dryRun: "dbcli schema --format json",
|
|
90263
|
+
apply: "dbcli schema --refresh",
|
|
90264
|
+
requiresHumanConfirmation: true
|
|
90265
|
+
});
|
|
90266
|
+
}
|
|
90267
|
+
if (result.label === "Large tables" && result.status === "warn") {
|
|
90268
|
+
const target = result.details?.system ?? "postgresql";
|
|
90269
|
+
const tables = Array.isArray(result.details?.largeTables) ? result.details.largeTables.filter((table) => typeof table === "object" && table !== null && typeof table.name === "string") : [];
|
|
90270
|
+
const candidates = tables.length > 0 ? tables : result.message.replace(/^Large tables:\s*/, "").split(/,\s+(?=[^,]+\s+\([\d.]+M rows\))/).map((entry) => ({ name: entry.replace(/\s+\([\d.]+M rows\)$/, "") }));
|
|
90271
|
+
for (const table of candidates) {
|
|
90272
|
+
const commands = boundedSampleCommands(target, table.name);
|
|
90273
|
+
steps.push({
|
|
90274
|
+
kind: "bounded-sample",
|
|
90275
|
+
status: "candidate",
|
|
90276
|
+
rationale: `${table.name} is large. Review a bounded sample only after confirming blacklist coverage.${commands.apply ? "" : " The identifier needs manual review before a sample query can be generated safely."}`,
|
|
90277
|
+
...commands,
|
|
90278
|
+
requiresHumanConfirmation: true
|
|
90279
|
+
});
|
|
90280
|
+
}
|
|
90281
|
+
}
|
|
90282
|
+
}
|
|
90283
|
+
return steps;
|
|
90284
|
+
}
|
|
89808
90285
|
function resolveSchemaLastUpdated(indexJson, configMetadata) {
|
|
89809
90286
|
if (indexJson && typeof indexJson === "object") {
|
|
89810
90287
|
const idx = indexJson;
|
|
@@ -89869,7 +90346,7 @@ async function collectMongoDoctorResults(config) {
|
|
|
89869
90346
|
results.push(runDoctorChecks.checkLargeTables(collections.map((coll) => ({
|
|
89870
90347
|
name: coll.name,
|
|
89871
90348
|
estimatedRowCount: coll.estimatedRowCount
|
|
89872
|
-
}))));
|
|
90349
|
+
})), "mongodb"));
|
|
89873
90350
|
results.push({
|
|
89874
90351
|
group: "Connection & Data",
|
|
89875
90352
|
label: "Collections",
|
|
@@ -89919,7 +90396,7 @@ async function collectElasticsearchDoctorResults(config) {
|
|
|
89919
90396
|
if (config.blacklistedColumns) {
|
|
89920
90397
|
results.push(runDoctorChecks.checkBlacklistCompleteness(tableColumns, config.blacklistedColumns));
|
|
89921
90398
|
}
|
|
89922
|
-
results.push(runDoctorChecks.checkLargeTables(tables));
|
|
90399
|
+
results.push(runDoctorChecks.checkLargeTables(tables, "elasticsearch"));
|
|
89923
90400
|
const lastUpdated = config.metadata?.schemaLastUpdated ?? null;
|
|
89924
90401
|
results.push(runDoctorChecks.checkSchemaCacheFreshness(lastUpdated));
|
|
89925
90402
|
} catch (error) {
|
|
@@ -89945,9 +90422,11 @@ var init_doctor = __esm(() => {
|
|
|
89945
90422
|
init_message_loader();
|
|
89946
90423
|
init_validation();
|
|
89947
90424
|
init_config_v2();
|
|
90425
|
+
init_config_path();
|
|
89948
90426
|
init_config_binding();
|
|
89949
90427
|
init_package();
|
|
89950
90428
|
init_schema_path();
|
|
90429
|
+
init_runtime_info();
|
|
89951
90430
|
init_config();
|
|
89952
90431
|
init_integration_helper();
|
|
89953
90432
|
ALLOWED_FORMATS19 = ["text", "json"];
|
|
@@ -89968,6 +90447,33 @@ var init_doctor = __esm(() => {
|
|
|
89968
90447
|
"credit_card"
|
|
89969
90448
|
];
|
|
89970
90449
|
runDoctorChecks = {
|
|
90450
|
+
checkRuntime(info) {
|
|
90451
|
+
const fileVersion = info.packageFileVersion ?? "unavailable";
|
|
90452
|
+
const versionMismatch = info.versionMismatch ? ` (bundle/package mismatch: runtime=${info.packageVersion}, package.json=${fileVersion})` : "";
|
|
90453
|
+
return {
|
|
90454
|
+
group: "Environment",
|
|
90455
|
+
label: "Runtime identity",
|
|
90456
|
+
status: info.versionMismatch ? "warn" : "pass",
|
|
90457
|
+
message: `source=${info.source}; runtime=${info.runtimeName} ${info.runtimeVersion}; ` + `executable=${info.executablePath}; launcher=${info.launcherPath}; ` + `package=${info.packageVersion}${versionMismatch}`,
|
|
90458
|
+
details: {
|
|
90459
|
+
source: info.source,
|
|
90460
|
+
runtimeName: info.runtimeName,
|
|
90461
|
+
runtimeVersion: info.runtimeVersion,
|
|
90462
|
+
executablePath: info.executablePath,
|
|
90463
|
+
launcherPath: info.launcherPath,
|
|
90464
|
+
packageRoot: info.packageRoot,
|
|
90465
|
+
packageVersion: info.packageVersion,
|
|
90466
|
+
packageFileVersion: info.packageFileVersion,
|
|
90467
|
+
versionMismatch: info.versionMismatch
|
|
90468
|
+
},
|
|
90469
|
+
...info.versionMismatch && {
|
|
90470
|
+
remediation: {
|
|
90471
|
+
command: "dbcli upgrade",
|
|
90472
|
+
risk: "interactive"
|
|
90473
|
+
}
|
|
90474
|
+
}
|
|
90475
|
+
};
|
|
90476
|
+
},
|
|
89971
90477
|
checkBunVersion(current, required) {
|
|
89972
90478
|
const passes = compareSemver(current, required) >= 0;
|
|
89973
90479
|
return {
|
|
@@ -90003,12 +90509,18 @@ var init_doctor = __esm(() => {
|
|
|
90003
90509
|
}
|
|
90004
90510
|
},
|
|
90005
90511
|
async checkConfigExists(configPath, existsFn) {
|
|
90006
|
-
const exists = existsFn ? await existsFn(configPath) : await Bun.file(configPath).exists() || await Bun.file(
|
|
90512
|
+
const exists = existsFn ? await existsFn(configPath) : await Bun.file(configPath).exists() || await Bun.file(join33(configPath, "config.json")).exists();
|
|
90007
90513
|
return {
|
|
90008
90514
|
group: "Configuration",
|
|
90009
90515
|
label: "Config exists",
|
|
90010
90516
|
status: exists ? "pass" : "error",
|
|
90011
|
-
message: exists ? `Config found: ${configPath}` : `No config found at ${configPath}. Run "dbcli init" first
|
|
90517
|
+
message: exists ? `Config found: ${configPath}` : `No config found at ${configPath}. Run "dbcli init" first.`,
|
|
90518
|
+
...!exists && {
|
|
90519
|
+
remediation: {
|
|
90520
|
+
command: "dbcli init",
|
|
90521
|
+
risk: "interactive"
|
|
90522
|
+
}
|
|
90523
|
+
}
|
|
90012
90524
|
};
|
|
90013
90525
|
},
|
|
90014
90526
|
checkBlacklistCompleteness(tableColumns, blacklistedColumns) {
|
|
@@ -90139,7 +90651,7 @@ var init_doctor = __esm(() => {
|
|
|
90139
90651
|
}
|
|
90140
90652
|
}
|
|
90141
90653
|
},
|
|
90142
|
-
checkLargeTables(tables) {
|
|
90654
|
+
checkLargeTables(tables, system = "postgresql") {
|
|
90143
90655
|
const large = tables.filter((t2) => (t2.estimatedRowCount ?? 0) > 1e6);
|
|
90144
90656
|
if (large.length === 0) {
|
|
90145
90657
|
return {
|
|
@@ -90154,13 +90666,17 @@ var init_doctor = __esm(() => {
|
|
|
90154
90666
|
group: "Connection & Data",
|
|
90155
90667
|
label: "Large tables",
|
|
90156
90668
|
status: "warn",
|
|
90157
|
-
message: `Large tables: ${list}
|
|
90669
|
+
message: `Large tables: ${list}`,
|
|
90670
|
+
details: {
|
|
90671
|
+
system,
|
|
90672
|
+
largeTables: large.map(({ name: name2, estimatedRowCount }) => ({ name: name2, estimatedRowCount }))
|
|
90673
|
+
}
|
|
90158
90674
|
};
|
|
90159
90675
|
},
|
|
90160
90676
|
async checkV2Config(configPath) {
|
|
90161
90677
|
const results = [];
|
|
90162
90678
|
const storagePath = await resolveConfigStoragePath(configPath);
|
|
90163
|
-
const configFile = Bun.file(
|
|
90679
|
+
const configFile = Bun.file(join33(storagePath, "config.json"));
|
|
90164
90680
|
if (!await configFile.exists())
|
|
90165
90681
|
return results;
|
|
90166
90682
|
let raw;
|
|
@@ -90200,7 +90716,7 @@ var init_doctor = __esm(() => {
|
|
|
90200
90716
|
}
|
|
90201
90717
|
for (const [name2, conn] of Object.entries(config.connections)) {
|
|
90202
90718
|
if (conn.envFile) {
|
|
90203
|
-
const envPath =
|
|
90719
|
+
const envPath = join33(storagePath, conn.envFile);
|
|
90204
90720
|
const exists = await Bun.file(envPath).exists();
|
|
90205
90721
|
results.push({
|
|
90206
90722
|
group: "Configuration",
|
|
@@ -90234,7 +90750,7 @@ var init_doctor = __esm(() => {
|
|
|
90234
90750
|
`);
|
|
90235
90751
|
}
|
|
90236
90752
|
};
|
|
90237
|
-
doctorCommand = new Command("doctor").description("Run diagnostic checks on dbcli configuration, environment, and connection").option("--format <type>", "Output format: text, json", "text").action(async (options) => {
|
|
90753
|
+
doctorCommand = new Command("doctor").description("Run diagnostic checks on dbcli configuration, environment, and connection").option("--format <type>", "Output format: text, json", "text").option("--remediation", "Include a non-mutating, human-confirmed remediation plan", false).action(async (options) => {
|
|
90238
90754
|
validateFormat(options.format, ALLOWED_FORMATS19, "doctor");
|
|
90239
90755
|
const logger = getLogger();
|
|
90240
90756
|
const results = [];
|
|
@@ -90242,6 +90758,7 @@ var init_doctor = __esm(() => {
|
|
|
90242
90758
|
const storagePath = await resolveConfigStoragePath(configPath);
|
|
90243
90759
|
const bunVersion = process.versions.bun ?? "unknown";
|
|
90244
90760
|
const requiredBun = package_default.engines?.bun?.replace(">=", "") ?? "1.3.3";
|
|
90761
|
+
results.push(runDoctorChecks.checkRuntime(await collectRuntimeInfo(package_default.version)));
|
|
90245
90762
|
results.push(runDoctorChecks.checkBunVersion(bunVersion, requiredBun));
|
|
90246
90763
|
results.push(await runDoctorChecks.checkLatestVersion(package_default.version));
|
|
90247
90764
|
const configExists = await runDoctorChecks.checkConfigExists(storagePath);
|
|
@@ -90303,13 +90820,13 @@ var init_doctor = __esm(() => {
|
|
|
90303
90820
|
tableColumns.set(t2.name, t2.columns.map((c2) => c2.name));
|
|
90304
90821
|
}
|
|
90305
90822
|
results.push(runDoctorChecks.checkBlacklistCompleteness(tableColumns, blacklistedColumns));
|
|
90306
|
-
results.push(runDoctorChecks.checkLargeTables(tables));
|
|
90823
|
+
results.push(runDoctorChecks.checkLargeTables(tables, config.connection.system));
|
|
90307
90824
|
} catch {
|
|
90308
90825
|
logger.debug("Could not list tables for blacklist/large table check");
|
|
90309
90826
|
}
|
|
90310
90827
|
try {
|
|
90311
90828
|
const schemaConnName = await getSchemaIsolationConnectionName(configPath);
|
|
90312
|
-
const indexPath =
|
|
90829
|
+
const indexPath = join33(resolveSchemaPath(storagePath, schemaConnName), "index.json");
|
|
90313
90830
|
const indexFile = Bun.file(indexPath);
|
|
90314
90831
|
let indexParsed = null;
|
|
90315
90832
|
if (await indexFile.exists()) {
|
|
@@ -90352,7 +90869,8 @@ var init_doctor = __esm(() => {
|
|
|
90352
90869
|
});
|
|
90353
90870
|
} catch {}
|
|
90354
90871
|
if (options.format === "json") {
|
|
90355
|
-
|
|
90872
|
+
const remediation = options.remediation ? buildDoctorRemediationPlan(results) : undefined;
|
|
90873
|
+
console.log(JSON.stringify({ results, hasError, ...remediation && { remediation } }, null, 2));
|
|
90356
90874
|
} else {
|
|
90357
90875
|
console.log(runDoctorChecks.formatTextOutput(results, package_default.version));
|
|
90358
90876
|
}
|
|
@@ -90417,9 +90935,9 @@ function flattenCommandTree(root) {
|
|
|
90417
90935
|
}
|
|
90418
90936
|
|
|
90419
90937
|
// src/commands/completion.ts
|
|
90420
|
-
import { join as
|
|
90938
|
+
import { join as join34 } from "path";
|
|
90421
90939
|
import { homedir as homedir3 } from "os";
|
|
90422
|
-
import { mkdir as
|
|
90940
|
+
import { mkdir as mkdir15 } from "fs/promises";
|
|
90423
90941
|
function resolveHome() {
|
|
90424
90942
|
return process.env.HOME ?? homedir3();
|
|
90425
90943
|
}
|
|
@@ -90669,11 +91187,11 @@ function getInstallPath2(shell) {
|
|
|
90669
91187
|
const home = resolveHome();
|
|
90670
91188
|
switch (shell) {
|
|
90671
91189
|
case "bash":
|
|
90672
|
-
return
|
|
91190
|
+
return join34(home, ".bashrc");
|
|
90673
91191
|
case "zsh":
|
|
90674
|
-
return
|
|
91192
|
+
return join34(home, ".zshrc");
|
|
90675
91193
|
case "fish":
|
|
90676
|
-
return
|
|
91194
|
+
return join34(home, ".config", "fish", "completions", "dbcli.fish");
|
|
90677
91195
|
default:
|
|
90678
91196
|
throw new Error(`Unsupported shell: ${shell}. Supported: bash, zsh, fish`);
|
|
90679
91197
|
}
|
|
@@ -90691,8 +91209,8 @@ function detectShell() {
|
|
|
90691
91209
|
async function installCompletion(shell, script) {
|
|
90692
91210
|
const targetPath = getInstallPath2(shell);
|
|
90693
91211
|
if (shell === "fish") {
|
|
90694
|
-
const dir =
|
|
90695
|
-
await
|
|
91212
|
+
const dir = join34(resolveHome(), ".config", "fish", "completions");
|
|
91213
|
+
await mkdir15(dir, { recursive: true });
|
|
90696
91214
|
await Bun.file(targetPath).write(script);
|
|
90697
91215
|
console.log(colors.success(`\u2713 Fish completion installed to ${targetPath}`));
|
|
90698
91216
|
return;
|
|
@@ -90853,24 +91371,24 @@ var init_types11 = __esm(() => {
|
|
|
90853
91371
|
|
|
90854
91372
|
// src/core/repl/input-classifier.ts
|
|
90855
91373
|
function classifyInput(raw) {
|
|
90856
|
-
const
|
|
90857
|
-
if (
|
|
90858
|
-
return { type: "empty", raw, normalized };
|
|
91374
|
+
const normalized2 = raw.trim();
|
|
91375
|
+
if (normalized2 === "") {
|
|
91376
|
+
return { type: "empty", raw, normalized: normalized2 };
|
|
90859
91377
|
}
|
|
90860
|
-
if (
|
|
90861
|
-
const cmdName = (
|
|
91378
|
+
if (normalized2.startsWith(META_PREFIX)) {
|
|
91379
|
+
const cmdName = (normalized2.split(/\s+/)[0] ?? "").slice(1);
|
|
90862
91380
|
if (metaCommandNames.includes(cmdName)) {
|
|
90863
|
-
return { type: "meta", raw, normalized };
|
|
91381
|
+
return { type: "meta", raw, normalized: normalized2 };
|
|
90864
91382
|
}
|
|
90865
91383
|
}
|
|
90866
|
-
const firstWord = (
|
|
91384
|
+
const firstWord = (normalized2.split(/\s+/)[0] ?? "").toUpperCase();
|
|
90867
91385
|
if (sqlKeywordSet.has(firstWord)) {
|
|
90868
|
-
return { type: "sql", raw, normalized };
|
|
91386
|
+
return { type: "sql", raw, normalized: normalized2 };
|
|
90869
91387
|
}
|
|
90870
|
-
if (
|
|
90871
|
-
return { type: "sql", raw, normalized };
|
|
91388
|
+
if (normalized2.endsWith(SQL_TERMINATOR)) {
|
|
91389
|
+
return { type: "sql", raw, normalized: normalized2 };
|
|
90872
91390
|
}
|
|
90873
|
-
return { type: "command", raw, normalized };
|
|
91391
|
+
return { type: "command", raw, normalized: normalized2 };
|
|
90874
91392
|
}
|
|
90875
91393
|
var META_PREFIX = ".", SQL_TERMINATOR = ";", sqlKeywordSet, metaCommandNames;
|
|
90876
91394
|
var init_input_classifier = __esm(() => {
|
|
@@ -91106,7 +91624,7 @@ function isKnownCommand(name2, commandNames) {
|
|
|
91106
91624
|
|
|
91107
91625
|
// src/core/repl/history-manager.ts
|
|
91108
91626
|
import { existsSync as existsSync2, readFileSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
91109
|
-
import { dirname as
|
|
91627
|
+
import { dirname as dirname12 } from "path";
|
|
91110
91628
|
|
|
91111
91629
|
class HistoryManager {
|
|
91112
91630
|
filePath;
|
|
@@ -91133,7 +91651,7 @@ class HistoryManager {
|
|
|
91133
91651
|
return this.entries;
|
|
91134
91652
|
}
|
|
91135
91653
|
async save() {
|
|
91136
|
-
const dir =
|
|
91654
|
+
const dir = dirname12(this.filePath);
|
|
91137
91655
|
if (!existsSync2(dir)) {
|
|
91138
91656
|
mkdirSync2(dir, { recursive: true });
|
|
91139
91657
|
}
|
|
@@ -91676,7 +92194,7 @@ var init_command_registry = __esm(() => {
|
|
|
91676
92194
|
|
|
91677
92195
|
// src/commands/shell.ts
|
|
91678
92196
|
import { createInterface as createInterface3 } from "readline";
|
|
91679
|
-
import { join as
|
|
92197
|
+
import { join as join35 } from "path";
|
|
91680
92198
|
import { homedir as homedir4 } from "os";
|
|
91681
92199
|
function requireSqlConnection13(connection) {
|
|
91682
92200
|
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
@@ -91866,10 +92384,11 @@ var init_shell3 = __esm(() => {
|
|
|
91866
92384
|
init_adapters();
|
|
91867
92385
|
init_repl_engine();
|
|
91868
92386
|
init_completer();
|
|
92387
|
+
init_config_path();
|
|
91869
92388
|
init_message_loader();
|
|
91870
92389
|
init_es_shell();
|
|
91871
92390
|
import_picocolors5 = __toESM(require_picocolors(), 1);
|
|
91872
|
-
HISTORY_PATH =
|
|
92391
|
+
HISTORY_PATH = join35(homedir4(), ".dbcli_history");
|
|
91873
92392
|
shellCommand = new Command("shell").description("Interactive database shell with auto-completion and syntax highlighting").option("--sql", "SQL-only mode (skip dbcli command parsing)").action(async (options, command) => {
|
|
91874
92393
|
const configPath = resolveConfigPath(command);
|
|
91875
92394
|
await runShell(options, configPath);
|
|
@@ -92480,8 +92999,8 @@ function requireSqlConnection14(connection) {
|
|
|
92480
92999
|
}
|
|
92481
93000
|
return connection;
|
|
92482
93001
|
}
|
|
92483
|
-
async function runDDL(operation, opts) {
|
|
92484
|
-
const configPath = resolveConfigPath(
|
|
93002
|
+
async function runDDL(operation, opts, command) {
|
|
93003
|
+
const configPath = resolveConfigPath(command, opts);
|
|
92485
93004
|
const config = await configModule.read(configPath);
|
|
92486
93005
|
if (!config.connection) {
|
|
92487
93006
|
throw new Error('Run "dbcli init" to configure database connection');
|
|
@@ -92551,8 +93070,9 @@ var init_migrate = __esm(() => {
|
|
|
92551
93070
|
init_adapters();
|
|
92552
93071
|
init_ddl2();
|
|
92553
93072
|
init_ddl_executor();
|
|
93073
|
+
init_config_path();
|
|
92554
93074
|
migrateCommand = new Command("migrate").description(t("migrate.description"));
|
|
92555
|
-
addExecOpts(migrateCommand.command("create <table>").description(t("migrate.create_description")).option("--column <spec...>", 'Column definitions (e.g., "id:serial:pk" "name:varchar(50):not-null")')).action(async (table, opts) => {
|
|
93075
|
+
addExecOpts(migrateCommand.command("create <table>").description(t("migrate.create_description")).option("--column <spec...>", 'Column definitions (e.g., "id:serial:pk" "name:varchar(50):not-null")')).action(async (table, opts, command) => {
|
|
92556
93076
|
try {
|
|
92557
93077
|
const specs = opts.column || [];
|
|
92558
93078
|
if (specs.length === 0) {
|
|
@@ -92560,19 +93080,19 @@ var init_migrate = __esm(() => {
|
|
|
92560
93080
|
process.exit(1);
|
|
92561
93081
|
}
|
|
92562
93082
|
const columns = specs.map(parseColumnSpec);
|
|
92563
|
-
await runDDL({ kind: "createTable", table, columns }, opts);
|
|
93083
|
+
await runDDL({ kind: "createTable", table, columns }, opts, command);
|
|
92564
93084
|
} catch (e) {
|
|
92565
93085
|
handleError(e);
|
|
92566
93086
|
}
|
|
92567
93087
|
});
|
|
92568
|
-
addExecOpts(migrateCommand.command("drop <table>").description(t("migrate.drop_description"))).action(async (table, opts) => {
|
|
93088
|
+
addExecOpts(migrateCommand.command("drop <table>").description(t("migrate.drop_description"))).action(async (table, opts, command) => {
|
|
92569
93089
|
try {
|
|
92570
|
-
await runDDL({ kind: "dropTable", table }, opts);
|
|
93090
|
+
await runDDL({ kind: "dropTable", table }, opts, command);
|
|
92571
93091
|
} catch (e) {
|
|
92572
93092
|
handleError(e);
|
|
92573
93093
|
}
|
|
92574
93094
|
});
|
|
92575
|
-
addExecOpts(migrateCommand.command("add-column <table> <column> <type>").description(t("migrate.add_column_description")).option("--nullable", "Allow NULL values").option("--default <value>", "Default value").option("--unique", "Add UNIQUE constraint")).action(async (table, column, type, opts) => {
|
|
93095
|
+
addExecOpts(migrateCommand.command("add-column <table> <column> <type>").description(t("migrate.add_column_description")).option("--nullable", "Allow NULL values").option("--default <value>", "Default value").option("--unique", "Add UNIQUE constraint")).action(async (table, column, type, opts, command) => {
|
|
92576
93096
|
try {
|
|
92577
93097
|
await runDDL({
|
|
92578
93098
|
kind: "addColumn",
|
|
@@ -92584,19 +93104,19 @@ var init_migrate = __esm(() => {
|
|
|
92584
93104
|
default: opts.default,
|
|
92585
93105
|
unique: opts.unique
|
|
92586
93106
|
}
|
|
92587
|
-
}, opts);
|
|
93107
|
+
}, opts, command);
|
|
92588
93108
|
} catch (e) {
|
|
92589
93109
|
handleError(e);
|
|
92590
93110
|
}
|
|
92591
93111
|
});
|
|
92592
|
-
addExecOpts(migrateCommand.command("drop-column <table> <column>").description(t("migrate.drop_column_description"))).action(async (table, column, opts) => {
|
|
93112
|
+
addExecOpts(migrateCommand.command("drop-column <table> <column>").description(t("migrate.drop_column_description"))).action(async (table, column, opts, command) => {
|
|
92593
93113
|
try {
|
|
92594
|
-
await runDDL({ kind: "dropColumn", table, column }, opts);
|
|
93114
|
+
await runDDL({ kind: "dropColumn", table, column }, opts, command);
|
|
92595
93115
|
} catch (e) {
|
|
92596
93116
|
handleError(e);
|
|
92597
93117
|
}
|
|
92598
93118
|
});
|
|
92599
|
-
addExecOpts(migrateCommand.command("alter-column <table> <column>").description(t("migrate.alter_column_description")).option("--type <type>", "Change column type").option("--rename <name>", "Rename column").option("--set-default <value>", "Set default value").option("--drop-default", "Remove default value").option("--set-nullable", "Allow NULL").option("--drop-nullable", "Disallow NULL")).action(async (table, column, opts) => {
|
|
93119
|
+
addExecOpts(migrateCommand.command("alter-column <table> <column>").description(t("migrate.alter_column_description")).option("--type <type>", "Change column type").option("--rename <name>", "Rename column").option("--set-default <value>", "Set default value").option("--drop-default", "Remove default value").option("--set-nullable", "Allow NULL").option("--drop-nullable", "Disallow NULL")).action(async (table, column, opts, command) => {
|
|
92600
93120
|
try {
|
|
92601
93121
|
await runDDL({
|
|
92602
93122
|
kind: "alterColumn",
|
|
@@ -92610,30 +93130,30 @@ var init_migrate = __esm(() => {
|
|
|
92610
93130
|
setNullable: opts.setNullable,
|
|
92611
93131
|
dropNullable: opts.dropNullable
|
|
92612
93132
|
}
|
|
92613
|
-
}, opts);
|
|
93133
|
+
}, opts, command);
|
|
92614
93134
|
} catch (e) {
|
|
92615
93135
|
handleError(e);
|
|
92616
93136
|
}
|
|
92617
93137
|
});
|
|
92618
|
-
addExecOpts(migrateCommand.command("add-index <table>").description(t("migrate.add_index_description")).requiredOption("--columns <cols>", "Comma-separated column names").option("--unique", "Create unique index").option("--type <type>", "Index type (btree, hash, gin, gist)").option("--name <name>", "Custom index name")).action(async (table, opts) => {
|
|
93138
|
+
addExecOpts(migrateCommand.command("add-index <table>").description(t("migrate.add_index_description")).requiredOption("--columns <cols>", "Comma-separated column names").option("--unique", "Create unique index").option("--type <type>", "Index type (btree, hash, gin, gist)").option("--name <name>", "Custom index name")).action(async (table, opts, command) => {
|
|
92619
93139
|
try {
|
|
92620
93140
|
const columns = opts.columns.split(",").map((c2) => c2.trim());
|
|
92621
93141
|
await runDDL({
|
|
92622
93142
|
kind: "addIndex",
|
|
92623
93143
|
index: { table, columns, unique: opts.unique, type: opts.type, name: opts.name }
|
|
92624
|
-
}, opts);
|
|
93144
|
+
}, opts, command);
|
|
92625
93145
|
} catch (e) {
|
|
92626
93146
|
handleError(e);
|
|
92627
93147
|
}
|
|
92628
93148
|
});
|
|
92629
|
-
addExecOpts(migrateCommand.command("drop-index <index>").description(t("migrate.drop_index_description")).option("--table <table>", "Table name (required for MySQL/MariaDB)")).action(async (indexName, opts) => {
|
|
93149
|
+
addExecOpts(migrateCommand.command("drop-index <index>").description(t("migrate.drop_index_description")).option("--table <table>", "Table name (required for MySQL/MariaDB)")).action(async (indexName, opts, command) => {
|
|
92630
93150
|
try {
|
|
92631
|
-
await runDDL({ kind: "dropIndex", indexName, table: opts.table }, opts);
|
|
93151
|
+
await runDDL({ kind: "dropIndex", indexName, table: opts.table }, opts, command);
|
|
92632
93152
|
} catch (e) {
|
|
92633
93153
|
handleError(e);
|
|
92634
93154
|
}
|
|
92635
93155
|
});
|
|
92636
|
-
addExecOpts(migrateCommand.command("add-constraint <table>").description(t("migrate.add_constraint_description")).option("--fk <column>", "Foreign key column").option("--references <table.column>", "Referenced table.column").option("--on-delete <action>", "ON DELETE action (cascade, set null, restrict, no action)").option("--unique <columns>", "Unique constraint columns (comma-separated)").option("--check <expression>", "Check constraint expression").option("--name <name>", "Custom constraint name")).action(async (table, opts) => {
|
|
93156
|
+
addExecOpts(migrateCommand.command("add-constraint <table>").description(t("migrate.add_constraint_description")).option("--fk <column>", "Foreign key column").option("--references <table.column>", "Referenced table.column").option("--on-delete <action>", "ON DELETE action (cascade, set null, restrict, no action)").option("--unique <columns>", "Unique constraint columns (comma-separated)").option("--check <expression>", "Check constraint expression").option("--name <name>", "Custom constraint name")).action(async (table, opts, command) => {
|
|
92637
93157
|
try {
|
|
92638
93158
|
let type;
|
|
92639
93159
|
let op;
|
|
@@ -92677,35 +93197,35 @@ var init_migrate = __esm(() => {
|
|
|
92677
93197
|
process.exit(1);
|
|
92678
93198
|
return;
|
|
92679
93199
|
}
|
|
92680
|
-
await runDDL(op, opts);
|
|
93200
|
+
await runDDL(op, opts, command);
|
|
92681
93201
|
} catch (e) {
|
|
92682
93202
|
handleError(e);
|
|
92683
93203
|
}
|
|
92684
93204
|
});
|
|
92685
|
-
addExecOpts(migrateCommand.command("drop-constraint <table> <constraint>").description(t("migrate.drop_constraint_description"))).action(async (table, constraintName, opts) => {
|
|
93205
|
+
addExecOpts(migrateCommand.command("drop-constraint <table> <constraint>").description(t("migrate.drop_constraint_description"))).action(async (table, constraintName, opts, command) => {
|
|
92686
93206
|
try {
|
|
92687
|
-
await runDDL({ kind: "dropConstraint", table, constraintName }, opts);
|
|
93207
|
+
await runDDL({ kind: "dropConstraint", table, constraintName }, opts, command);
|
|
92688
93208
|
} catch (e) {
|
|
92689
93209
|
handleError(e);
|
|
92690
93210
|
}
|
|
92691
93211
|
});
|
|
92692
|
-
addExecOpts(migrateCommand.command("add-enum <name> <values...>").description(t("migrate.add_enum_description"))).action(async (name2, values, opts) => {
|
|
93212
|
+
addExecOpts(migrateCommand.command("add-enum <name> <values...>").description(t("migrate.add_enum_description"))).action(async (name2, values, opts, command) => {
|
|
92693
93213
|
try {
|
|
92694
|
-
await runDDL({ kind: "addEnum", definition: { name: name2, values } }, opts);
|
|
93214
|
+
await runDDL({ kind: "addEnum", definition: { name: name2, values } }, opts, command);
|
|
92695
93215
|
} catch (e) {
|
|
92696
93216
|
handleError(e);
|
|
92697
93217
|
}
|
|
92698
93218
|
});
|
|
92699
|
-
addExecOpts(migrateCommand.command("alter-enum <name>").description(t("migrate.alter_enum_description")).requiredOption("--add-value <value>", "Value to add")).action(async (name2, opts) => {
|
|
93219
|
+
addExecOpts(migrateCommand.command("alter-enum <name>").description(t("migrate.alter_enum_description")).requiredOption("--add-value <value>", "Value to add")).action(async (name2, opts, command) => {
|
|
92700
93220
|
try {
|
|
92701
|
-
await runDDL({ kind: "alterEnum", name: name2, addValue: opts.addValue }, opts);
|
|
93221
|
+
await runDDL({ kind: "alterEnum", name: name2, addValue: opts.addValue }, opts, command);
|
|
92702
93222
|
} catch (e) {
|
|
92703
93223
|
handleError(e);
|
|
92704
93224
|
}
|
|
92705
93225
|
});
|
|
92706
|
-
addExecOpts(migrateCommand.command("drop-enum <name>").description(t("migrate.drop_enum_description"))).action(async (name2, opts) => {
|
|
93226
|
+
addExecOpts(migrateCommand.command("drop-enum <name>").description(t("migrate.drop_enum_description"))).action(async (name2, opts, command) => {
|
|
92707
93227
|
try {
|
|
92708
|
-
await runDDL({ kind: "dropEnum", name: name2 }, opts);
|
|
93228
|
+
await runDDL({ kind: "dropEnum", name: name2 }, opts, command);
|
|
92709
93229
|
} catch (e) {
|
|
92710
93230
|
handleError(e);
|
|
92711
93231
|
}
|
|
@@ -92713,12 +93233,13 @@ var init_migrate = __esm(() => {
|
|
|
92713
93233
|
});
|
|
92714
93234
|
|
|
92715
93235
|
// src/commands/use.ts
|
|
92716
|
-
import { join as
|
|
92717
|
-
async function switchDefault(configPath, name2, config) {
|
|
92718
|
-
|
|
92719
|
-
|
|
92720
|
-
throw new ConfigError(
|
|
93236
|
+
import { join as join36 } from "path";
|
|
93237
|
+
async function switchDefault(configPath, name2, config, options = {}) {
|
|
93238
|
+
const connection = config.connections[name2];
|
|
93239
|
+
if (!connection) {
|
|
93240
|
+
throw new ConfigError(connectionNotFoundMessage(name2, Object.keys(config.connections)));
|
|
92721
93241
|
}
|
|
93242
|
+
assertProductionDefaultConfirmation(name2, connection.environment, options.confirmProduction);
|
|
92722
93243
|
const updated = {
|
|
92723
93244
|
...config,
|
|
92724
93245
|
default: name2
|
|
@@ -92731,12 +93252,31 @@ function listConnectionsForDisplay(config) {
|
|
|
92731
93252
|
const host = typeof conn.host === "object" ? `\${${conn.host.$env}}` : conn.host;
|
|
92732
93253
|
const port = typeof conn.port === "object" ? `\${${conn.port.$env}}` : conn.port;
|
|
92733
93254
|
const db = typeof conn.database === "object" ? `\${${conn.database.$env}}` : conn.database;
|
|
92734
|
-
|
|
93255
|
+
const environment = conn.environment ? ` [${conn.environment}]` : "";
|
|
93256
|
+
return `${marker} ${name2.padEnd(12)}${environment} ${conn.system.padEnd(12)} ${host}:${port}/${db}`;
|
|
93257
|
+
});
|
|
93258
|
+
}
|
|
93259
|
+
function listConnectionIdentities(config) {
|
|
93260
|
+
return Object.entries(config.connections).map(([name2, conn]) => {
|
|
93261
|
+
const host = typeof conn.host === "string" && conn.host.length > 0 ? conn.host : null;
|
|
93262
|
+
const database = typeof conn.database === "string" && conn.database.length > 0 ? conn.database : null;
|
|
93263
|
+
return {
|
|
93264
|
+
name: name2,
|
|
93265
|
+
environment: conn.environment ?? null,
|
|
93266
|
+
permission: conn.permission,
|
|
93267
|
+
system: conn.system,
|
|
93268
|
+
server: {
|
|
93269
|
+
host,
|
|
93270
|
+
port: host !== null && typeof conn.port === "number" ? conn.port : null
|
|
93271
|
+
},
|
|
93272
|
+
database,
|
|
93273
|
+
isDefault: name2 === config.default
|
|
93274
|
+
};
|
|
92735
93275
|
});
|
|
92736
93276
|
}
|
|
92737
93277
|
async function ensureV2Config(configPath) {
|
|
92738
93278
|
const storagePath = await resolveConfigStoragePath(configPath);
|
|
92739
|
-
const configFile = Bun.file(
|
|
93279
|
+
const configFile = Bun.file(join36(storagePath, "config.json"));
|
|
92740
93280
|
const legacyFile = Bun.file(configPath);
|
|
92741
93281
|
if (!await configFile.exists() && !await legacyFile.exists()) {
|
|
92742
93282
|
throw new ConfigError(t("init.config_not_found"));
|
|
@@ -92772,19 +93312,30 @@ async function ensureV2Config(configPath) {
|
|
|
92772
93312
|
throw new ConfigError(t("use.requires_v2"));
|
|
92773
93313
|
}
|
|
92774
93314
|
}
|
|
92775
|
-
var useCommand;
|
|
93315
|
+
var ALLOWED_FORMATS20, useCommand;
|
|
92776
93316
|
var init_use = __esm(() => {
|
|
92777
93317
|
init_esm();
|
|
92778
93318
|
init_config_v2();
|
|
92779
93319
|
init_config();
|
|
92780
|
-
|
|
93320
|
+
init_errors2();
|
|
93321
|
+
init_config_path();
|
|
92781
93322
|
init_message_loader();
|
|
92782
93323
|
init_config_binding();
|
|
92783
|
-
|
|
93324
|
+
init_validation();
|
|
93325
|
+
ALLOWED_FORMATS20 = ["text", "json"];
|
|
93326
|
+
useCommand = new Command("use").description("Switch or display the default database connection (v2 config)").argument("[name]", "Connection name to switch to").option("--list", "List all connections").option("--format <format>", "Output format: text (default) or json", "text").option("--confirm-production <name>", "Confirm changing the persisted default to this production connection by repeating its name").action(async (name2, options) => {
|
|
92784
93327
|
try {
|
|
93328
|
+
validateFormat(String(options.format), ALLOWED_FORMATS20, "use");
|
|
92785
93329
|
const configPath = resolveConfigPath(useCommand);
|
|
92786
93330
|
const config = await ensureV2Config(configPath);
|
|
93331
|
+
if (name2 && !options.list && options.format === "json") {
|
|
93332
|
+
throw new ConfigError("--format json is only supported with --list");
|
|
93333
|
+
}
|
|
92787
93334
|
if (options.list || !name2) {
|
|
93335
|
+
if (options.format === "json") {
|
|
93336
|
+
console.log(JSON.stringify({ connections: listConnectionIdentities(config) }, null, 2));
|
|
93337
|
+
return;
|
|
93338
|
+
}
|
|
92788
93339
|
if (!name2) {
|
|
92789
93340
|
console.log(t_vars("use.current", { name: config.default }));
|
|
92790
93341
|
}
|
|
@@ -92794,7 +93345,9 @@ var init_use = __esm(() => {
|
|
|
92794
93345
|
}
|
|
92795
93346
|
return;
|
|
92796
93347
|
}
|
|
92797
|
-
await switchDefault(configPath, name2, config
|
|
93348
|
+
await switchDefault(configPath, name2, config, {
|
|
93349
|
+
confirmProduction: typeof options.confirmProduction === "string" ? options.confirmProduction : undefined
|
|
93350
|
+
});
|
|
92798
93351
|
console.log(t_vars("use.switched", { name: name2 }));
|
|
92799
93352
|
} catch (error) {
|
|
92800
93353
|
if (error instanceof Error) {
|
|
@@ -92881,8 +93434,8 @@ var init_sql_metadata = __esm(() => {
|
|
|
92881
93434
|
});
|
|
92882
93435
|
|
|
92883
93436
|
// src/proxy/events.ts
|
|
92884
|
-
import { appendFile as appendFile2, mkdir as
|
|
92885
|
-
import { dirname as
|
|
93437
|
+
import { appendFile as appendFile2, mkdir as mkdir16, readFile as readFile8, stat as stat9 } from "fs/promises";
|
|
93438
|
+
import { dirname as dirname13 } from "path";
|
|
92886
93439
|
function hasSql(e) {
|
|
92887
93440
|
return e.type === "query_observed" || e.type === "query_completed" || e.type === "query_errored";
|
|
92888
93441
|
}
|
|
@@ -92921,7 +93474,7 @@ class EventWriter {
|
|
|
92921
93474
|
}
|
|
92922
93475
|
async writeInternal(event) {
|
|
92923
93476
|
if (!this.dirEnsured) {
|
|
92924
|
-
await
|
|
93477
|
+
await mkdir16(dirname13(this.path), { recursive: true });
|
|
92925
93478
|
this.dirEnsured = true;
|
|
92926
93479
|
}
|
|
92927
93480
|
if (!this.initialized) {
|
|
@@ -92943,7 +93496,7 @@ class EventWriter {
|
|
|
92943
93496
|
}
|
|
92944
93497
|
async syncCountersFromDisk() {
|
|
92945
93498
|
try {
|
|
92946
|
-
const s = await
|
|
93499
|
+
const s = await stat9(this.path);
|
|
92947
93500
|
this.currentSizeBytes = s.size;
|
|
92948
93501
|
const raw = await readFile8(this.path, "utf8");
|
|
92949
93502
|
this.currentEntryCount = raw.split(`
|
|
@@ -93850,7 +94403,7 @@ function renderAnalysisText(report, top) {
|
|
|
93850
94403
|
}
|
|
93851
94404
|
|
|
93852
94405
|
// src/commands/proxy.ts
|
|
93853
|
-
import { join as
|
|
94406
|
+
import { join as join37 } from "path";
|
|
93854
94407
|
function parseHostPort(value) {
|
|
93855
94408
|
const idx = value.lastIndexOf(":");
|
|
93856
94409
|
if (idx <= 0 || idx === value.length - 1) {
|
|
@@ -93890,7 +94443,7 @@ function resolveProxyConfig(input) {
|
|
|
93890
94443
|
}
|
|
93891
94444
|
async function runProxy(subcommandEngine, options, command) {
|
|
93892
94445
|
try {
|
|
93893
|
-
validateFormat(options.format ?? "text",
|
|
94446
|
+
validateFormat(options.format ?? "text", ALLOWED_FORMATS21, "proxy");
|
|
93894
94447
|
const redact = options.redact ?? "none";
|
|
93895
94448
|
if (!ALLOWED_REDACT.includes(redact)) {
|
|
93896
94449
|
throw new Error(`Invalid --redact "${redact}". Allowed: none, literals`);
|
|
@@ -93913,7 +94466,7 @@ async function runProxy(subcommandEngine, options, command) {
|
|
|
93913
94466
|
target: options.target,
|
|
93914
94467
|
connection
|
|
93915
94468
|
});
|
|
93916
|
-
const eventsPath = options.events ??
|
|
94469
|
+
const eventsPath = options.events ?? join37(".dbcli", "proxy", "events.jsonl");
|
|
93917
94470
|
const slowMs = Number(options.slowMs ?? 1000);
|
|
93918
94471
|
if (!Number.isFinite(slowMs) || slowMs < 0) {
|
|
93919
94472
|
throw new Error(`Invalid --slow-ms "${options.slowMs}". Expected a non-negative number`);
|
|
@@ -93945,12 +94498,12 @@ async function runProxy(subcommandEngine, options, command) {
|
|
|
93945
94498
|
` + `Press Ctrl+C to stop.
|
|
93946
94499
|
`);
|
|
93947
94500
|
}
|
|
93948
|
-
await new Promise((
|
|
94501
|
+
await new Promise((resolve9) => {
|
|
93949
94502
|
const shutdown = () => {
|
|
93950
94503
|
process.removeListener("SIGINT", shutdown);
|
|
93951
94504
|
process.removeListener("SIGTERM", shutdown);
|
|
93952
94505
|
server.stop();
|
|
93953
|
-
|
|
94506
|
+
resolve9();
|
|
93954
94507
|
};
|
|
93955
94508
|
process.on("SIGINT", shutdown);
|
|
93956
94509
|
process.on("SIGTERM", shutdown);
|
|
@@ -93962,7 +94515,7 @@ async function runProxy(subcommandEngine, options, command) {
|
|
|
93962
94515
|
}
|
|
93963
94516
|
}
|
|
93964
94517
|
function addCommonOptions(cmd) {
|
|
93965
|
-
return cmd.option("--listen <host:port>", "Local proxy listen address (required)").option("--target <host:port>", "Upstream DB target (optional when config provides host/port)").option("--events <path>", "Event JSONL path",
|
|
94518
|
+
return cmd.option("--listen <host:port>", "Local proxy listen address (required)").option("--target <host:port>", "Upstream DB target (optional when config provides host/port)").option("--events <path>", "Event JSONL path", join37(".dbcli", "proxy", "events.jsonl")).option("--slow-ms <number>", "Threshold (ms); queries at/above it get slow:true in the event + a terminal warning", "1000").option("--redact <mode>", "SQL redaction: none | literals", "none").option("--format <format>", "Runtime status output: text | json", "text");
|
|
93966
94519
|
}
|
|
93967
94520
|
function parseNonNegInt(value, flag, fallback) {
|
|
93968
94521
|
if (value === undefined)
|
|
@@ -93980,7 +94533,7 @@ async function runAnalyze(options) {
|
|
|
93980
94533
|
const top = parseNonNegInt(options.top, "top", 20);
|
|
93981
94534
|
const slowMs = parseNonNegInt(options.slowMs, "slow-ms", 1000);
|
|
93982
94535
|
const nPlusOne = parseNonNegInt(options.nPlusOne, "n-plus-one", 10);
|
|
93983
|
-
const eventsPath = options.events ??
|
|
94536
|
+
const eventsPath = options.events ?? join37(".dbcli", "proxy", "events.jsonl");
|
|
93984
94537
|
const { events, malformedLines, files } = await readEvents(eventsPath, {
|
|
93985
94538
|
includeRotated: options.includeRotated !== false
|
|
93986
94539
|
});
|
|
@@ -94007,16 +94560,17 @@ async function runAnalyze(options) {
|
|
|
94007
94560
|
process.exit(1);
|
|
94008
94561
|
}
|
|
94009
94562
|
}
|
|
94010
|
-
var SUPPORTED,
|
|
94563
|
+
var SUPPORTED, ALLOWED_FORMATS21, ALLOWED_REDACT, proxyCommand, ANALYZE_FORMATS;
|
|
94011
94564
|
var init_proxy = __esm(() => {
|
|
94012
94565
|
init_esm();
|
|
94013
94566
|
init_config();
|
|
94567
|
+
init_config_path();
|
|
94014
94568
|
init_validation();
|
|
94015
94569
|
init_server();
|
|
94016
94570
|
init_event_reader();
|
|
94017
94571
|
init_analyze();
|
|
94018
94572
|
SUPPORTED = ["mysql", "mariadb", "postgresql"];
|
|
94019
|
-
|
|
94573
|
+
ALLOWED_FORMATS21 = ["text", "json"];
|
|
94020
94574
|
ALLOWED_REDACT = ["none", "literals"];
|
|
94021
94575
|
proxyCommand = new Command().name("proxy").description("Local development observability proxy for MySQL/MariaDB/PostgreSQL (observe-only)");
|
|
94022
94576
|
proxyCommand.enablePositionalOptions();
|
|
@@ -94026,7 +94580,7 @@ var init_proxy = __esm(() => {
|
|
|
94026
94580
|
});
|
|
94027
94581
|
}
|
|
94028
94582
|
ANALYZE_FORMATS = ["json", "text"];
|
|
94029
|
-
proxyCommand.command("analyze").description("Analyze a proxy event log offline (no DB connection)").option("--events <path>", "Event JSONL path",
|
|
94583
|
+
proxyCommand.command("analyze").description("Analyze a proxy event log offline (no DB connection)").option("--events <path>", "Event JSONL path", join37(".dbcli", "proxy", "events.jsonl")).option("--format <format>", "Output format: json | text", "json").option("--top <number>", "Rows shown in text + suggestedCommands depth", "20").option("--slow-ms <number>", "Slow-query threshold (ms) for slowCount", "1000").option("--n-plus-one <number>", "Min repeats per (session,fingerprint) to flag N+1", "10").option("--no-include-rotated", "Do not merge the rotated <events>.1 segment").action(async (options) => {
|
|
94030
94584
|
await runAnalyze(options);
|
|
94031
94585
|
});
|
|
94032
94586
|
addCommonOptions(proxyCommand).action(async (options, command) => {
|
|
@@ -94034,6 +94588,264 @@ var init_proxy = __esm(() => {
|
|
|
94034
94588
|
});
|
|
94035
94589
|
});
|
|
94036
94590
|
|
|
94591
|
+
// src/core/backfill-artifact.ts
|
|
94592
|
+
import { createHash as createHash4 } from "crypto";
|
|
94593
|
+
function isRecord2(value) {
|
|
94594
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
94595
|
+
}
|
|
94596
|
+
function requireIdentifier(value, label) {
|
|
94597
|
+
if (typeof value !== "string" || !/^[A-Za-z_][A-Za-z0-9_$.]*$/.test(value)) {
|
|
94598
|
+
throw new Error(`${label} must be a SQL identifier containing only letters, numbers, _, $, or .`);
|
|
94599
|
+
}
|
|
94600
|
+
return value;
|
|
94601
|
+
}
|
|
94602
|
+
function sqlLiteral(value, targetSystem) {
|
|
94603
|
+
if (value === null)
|
|
94604
|
+
return "NULL";
|
|
94605
|
+
if (typeof value === "string") {
|
|
94606
|
+
if (targetSystem === "mysql" || targetSystem === "mariadb") {
|
|
94607
|
+
const hex = Buffer.from(value, "utf8").toString("hex");
|
|
94608
|
+
return `CONVERT(UNHEX('${hex}') USING utf8mb4)`;
|
|
94609
|
+
}
|
|
94610
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
94611
|
+
}
|
|
94612
|
+
if (typeof value === "number") {
|
|
94613
|
+
if (!Number.isFinite(value))
|
|
94614
|
+
throw new Error("Source rows cannot contain non-finite numbers");
|
|
94615
|
+
return String(value);
|
|
94616
|
+
}
|
|
94617
|
+
if (typeof value === "boolean")
|
|
94618
|
+
return value ? "TRUE" : "FALSE";
|
|
94619
|
+
throw new Error("Source row values must be strings, finite numbers, booleans, or null");
|
|
94620
|
+
}
|
|
94621
|
+
function stripSqlCommentsAndStrings(sql) {
|
|
94622
|
+
let result = "";
|
|
94623
|
+
let index = 0;
|
|
94624
|
+
while (index < sql.length) {
|
|
94625
|
+
const current = sql[index];
|
|
94626
|
+
const next = sql[index + 1];
|
|
94627
|
+
if (current === "-" && next === "-") {
|
|
94628
|
+
index += 2;
|
|
94629
|
+
while (index < sql.length && sql[index] !== `
|
|
94630
|
+
`)
|
|
94631
|
+
index += 1;
|
|
94632
|
+
result += `
|
|
94633
|
+
`;
|
|
94634
|
+
continue;
|
|
94635
|
+
}
|
|
94636
|
+
if (current === "/" && next === "*") {
|
|
94637
|
+
index += 2;
|
|
94638
|
+
while (index < sql.length && !(sql[index] === "*" && sql[index + 1] === "/"))
|
|
94639
|
+
index += 1;
|
|
94640
|
+
index += 2;
|
|
94641
|
+
result += " ";
|
|
94642
|
+
continue;
|
|
94643
|
+
}
|
|
94644
|
+
if (current === "'") {
|
|
94645
|
+
index += 1;
|
|
94646
|
+
while (index < sql.length) {
|
|
94647
|
+
if (sql[index] === "'" && sql[index + 1] === "'") {
|
|
94648
|
+
index += 2;
|
|
94649
|
+
continue;
|
|
94650
|
+
}
|
|
94651
|
+
if (sql[index] === "'") {
|
|
94652
|
+
index += 1;
|
|
94653
|
+
break;
|
|
94654
|
+
}
|
|
94655
|
+
index += 1;
|
|
94656
|
+
}
|
|
94657
|
+
result += " ";
|
|
94658
|
+
continue;
|
|
94659
|
+
}
|
|
94660
|
+
result += current;
|
|
94661
|
+
index += 1;
|
|
94662
|
+
}
|
|
94663
|
+
return result;
|
|
94664
|
+
}
|
|
94665
|
+
function assertReadOnlyVerifyQuery(query) {
|
|
94666
|
+
const normalized2 = stripSqlCommentsAndStrings(query).trim();
|
|
94667
|
+
if (!/^SELECT\b/i.test(normalized2) || /;/.test(normalized2)) {
|
|
94668
|
+
throw new Error("verifyQuery must be a single plain read-only SELECT statement");
|
|
94669
|
+
}
|
|
94670
|
+
if (/\b(?:INSERT|UPDATE|DELETE|MERGE|UPSERT|REPLACE|TRUNCATE|DROP|ALTER|CREATE|GRANT|REVOKE|RENAME|INTO|LOCK|CALL|DO|FOR\s+(?:UPDATE|SHARE|NO\s+KEY\s+UPDATE|KEY\s+SHARE))\b/i.test(normalized2)) {
|
|
94671
|
+
throw new Error("verifyQuery must not contain a write or DDL statement");
|
|
94672
|
+
}
|
|
94673
|
+
return query.trim();
|
|
94674
|
+
}
|
|
94675
|
+
function parseBackfillSourceManifest(raw) {
|
|
94676
|
+
if (!isRecord2(raw))
|
|
94677
|
+
throw new Error("Source manifest must be a JSON object");
|
|
94678
|
+
const table = requireIdentifier(raw.table, "table");
|
|
94679
|
+
if (!Array.isArray(raw.keyColumns) || raw.keyColumns.length === 0) {
|
|
94680
|
+
throw new Error("keyColumns must be a non-empty array");
|
|
94681
|
+
}
|
|
94682
|
+
const keyColumns = raw.keyColumns.map((column) => requireIdentifier(column, "keyColumns entry"));
|
|
94683
|
+
if (new Set(keyColumns).size !== keyColumns.length) {
|
|
94684
|
+
throw new Error("keyColumns must not contain duplicates");
|
|
94685
|
+
}
|
|
94686
|
+
if (!Array.isArray(raw.rows) || raw.rows.length === 0) {
|
|
94687
|
+
throw new Error("rows must be a non-empty array");
|
|
94688
|
+
}
|
|
94689
|
+
if (raw.rows.length > MAX_SOURCE_ROWS) {
|
|
94690
|
+
throw new Error(`rows exceeds the bounded maximum of ${MAX_SOURCE_ROWS}`);
|
|
94691
|
+
}
|
|
94692
|
+
const rows = raw.rows.map((row, index) => {
|
|
94693
|
+
if (!isRecord2(row))
|
|
94694
|
+
throw new Error(`rows[${index}] must be an object`);
|
|
94695
|
+
for (const key of keyColumns) {
|
|
94696
|
+
if (!(key in row))
|
|
94697
|
+
throw new Error(`rows[${index}] is missing key column '${key}'`);
|
|
94698
|
+
}
|
|
94699
|
+
return row;
|
|
94700
|
+
});
|
|
94701
|
+
if (typeof raw.verifyQuery !== "string" || raw.verifyQuery.trim() === "") {
|
|
94702
|
+
throw new Error("verifyQuery must be a non-empty string");
|
|
94703
|
+
}
|
|
94704
|
+
if (typeof raw.expect !== "string" || raw.expect.trim() === "") {
|
|
94705
|
+
throw new Error("expect must be a non-empty string");
|
|
94706
|
+
}
|
|
94707
|
+
if (raw.rollbackHint !== undefined && typeof raw.rollbackHint !== "string") {
|
|
94708
|
+
throw new Error("rollbackHint must be a string when provided");
|
|
94709
|
+
}
|
|
94710
|
+
return {
|
|
94711
|
+
table,
|
|
94712
|
+
keyColumns,
|
|
94713
|
+
rows,
|
|
94714
|
+
verifyQuery: assertReadOnlyVerifyQuery(raw.verifyQuery),
|
|
94715
|
+
expect: raw.expect.trim(),
|
|
94716
|
+
rollbackHint: raw.rollbackHint
|
|
94717
|
+
};
|
|
94718
|
+
}
|
|
94719
|
+
function generateBackfillSql(manifest, targetSystem = "postgresql") {
|
|
94720
|
+
return manifest.rows.map((row, index) => {
|
|
94721
|
+
const setColumns = Object.keys(row).filter((column) => !manifest.keyColumns.includes(column));
|
|
94722
|
+
if (setColumns.length === 0)
|
|
94723
|
+
throw new Error(`rows[${index}] has no non-key columns to update`);
|
|
94724
|
+
const set = setColumns.map((column) => `${requireIdentifier(column, `rows[${index}] column`)} = ${sqlLiteral(row[column], targetSystem)}`).join(", ");
|
|
94725
|
+
const where = manifest.keyColumns.map((column) => {
|
|
94726
|
+
const value = row[column];
|
|
94727
|
+
return value === null ? `${column} IS NULL` : `${column} = ${sqlLiteral(value, targetSystem)}`;
|
|
94728
|
+
}).join(" AND ");
|
|
94729
|
+
return `UPDATE ${manifest.table} SET ${set} WHERE ${where}`;
|
|
94730
|
+
});
|
|
94731
|
+
}
|
|
94732
|
+
function compareBackfillIdentities(source, target) {
|
|
94733
|
+
const differences = [];
|
|
94734
|
+
for (const field of ["environment", "system", "database"]) {
|
|
94735
|
+
if (source[field] !== target[field])
|
|
94736
|
+
differences.push(`${field}: ${String(source[field])} -> ${String(target[field])}`);
|
|
94737
|
+
}
|
|
94738
|
+
if (source.server.host !== target.server.host || source.server.port !== target.server.port) {
|
|
94739
|
+
differences.push(`server: ${source.server.host ?? "unknown"}:${source.server.port ?? "unknown"} -> ${target.server.host ?? "unknown"}:${target.server.port ?? "unknown"}`);
|
|
94740
|
+
}
|
|
94741
|
+
return differences;
|
|
94742
|
+
}
|
|
94743
|
+
function buildBackfillArtifact(input) {
|
|
94744
|
+
if (!SQL_TARGET_SYSTEMS.has(input.targetIdentity.system)) {
|
|
94745
|
+
throw new Error(`Source-to-SQL backfill artifacts require a SQL target connection; '${input.targetIdentity.system}' is not supported`);
|
|
94746
|
+
}
|
|
94747
|
+
const statements = generateBackfillSql(input.manifest, input.targetIdentity.system);
|
|
94748
|
+
const target = input.targetIdentity.name;
|
|
94749
|
+
const targetArg = shellQuote(target);
|
|
94750
|
+
const tableArg = shellQuote(input.manifest.table);
|
|
94751
|
+
const planCommand2 = (sql) => `dbcli --use ${targetArg} plan ${shellQuote(sql)} --format json`;
|
|
94752
|
+
const lastStatement = statements[statements.length - 1];
|
|
94753
|
+
return {
|
|
94754
|
+
schemaVersion: BACKFILL_ARTIFACT_SCHEMA_VERSION,
|
|
94755
|
+
kind: "source-to-sql-backfill",
|
|
94756
|
+
createdAt: (input.now ?? new Date).toISOString(),
|
|
94757
|
+
source: {
|
|
94758
|
+
path: input.sourcePath,
|
|
94759
|
+
sha256: createHash4("sha256").update(input.sourceContent).digest("hex"),
|
|
94760
|
+
rowCount: input.manifest.rows.length
|
|
94761
|
+
},
|
|
94762
|
+
table: input.manifest.table,
|
|
94763
|
+
sourceIdentity: input.sourceIdentity,
|
|
94764
|
+
targetIdentity: input.targetIdentity,
|
|
94765
|
+
identityDiff: compareBackfillIdentities(input.sourceIdentity, input.targetIdentity),
|
|
94766
|
+
statements: statements.map((sql) => ({ sql, planCommand: planCommand2(sql) })),
|
|
94767
|
+
preflight: [
|
|
94768
|
+
`dbcli --use ${targetArg} blacklist list --format json`,
|
|
94769
|
+
`dbcli --use ${targetArg} schema ${tableArg} --format json`,
|
|
94770
|
+
...statements.map(planCommand2)
|
|
94771
|
+
],
|
|
94772
|
+
readBack: {
|
|
94773
|
+
query: input.manifest.verifyQuery,
|
|
94774
|
+
expect: input.manifest.expect,
|
|
94775
|
+
command: `dbcli --use ${targetArg} verify safe-backfill --table ${tableArg} ` + `--query ${shellQuote(lastStatement)} --verify-query ${shellQuote(input.manifest.verifyQuery)} ` + `--expect ${shellQuote(input.manifest.expect)} --after-write --format json`
|
|
94776
|
+
},
|
|
94777
|
+
rollbackHint: input.manifest.rollbackHint ?? "Capture the prior values before applying this artifact; rollback is a separately reviewed UPDATE with its own verify rollback preflight.",
|
|
94778
|
+
execution: {
|
|
94779
|
+
mode: "dry-run",
|
|
94780
|
+
requiresHumanConfirmation: true,
|
|
94781
|
+
note: "This artifact never executes database writes. Review each plan and apply SQL only through an explicitly human-confirmed workflow."
|
|
94782
|
+
}
|
|
94783
|
+
};
|
|
94784
|
+
}
|
|
94785
|
+
var BACKFILL_ARTIFACT_SCHEMA_VERSION = 1, MAX_SOURCE_ROWS = 1000, SQL_TARGET_SYSTEMS;
|
|
94786
|
+
var init_backfill_artifact = __esm(() => {
|
|
94787
|
+
SQL_TARGET_SYSTEMS = new Set(["postgresql", "mysql", "mariadb"]);
|
|
94788
|
+
});
|
|
94789
|
+
|
|
94790
|
+
// src/commands/backfill.ts
|
|
94791
|
+
import { dirname as dirname14, resolve as resolve9 } from "path";
|
|
94792
|
+
import { mkdir as mkdir17 } from "fs/promises";
|
|
94793
|
+
function identityFor(config, name2) {
|
|
94794
|
+
const connection = config.connections[name2];
|
|
94795
|
+
if (!connection) {
|
|
94796
|
+
throw new Error(`Connection '${name2}' was not found. Available connections: ${Object.keys(config.connections).join(", ")}`);
|
|
94797
|
+
}
|
|
94798
|
+
const host = typeof connection.host === "string" && connection.host.length > 0 ? connection.host : null;
|
|
94799
|
+
const database = typeof connection.database === "string" && connection.database.length > 0 ? connection.database : null;
|
|
94800
|
+
return {
|
|
94801
|
+
name: name2,
|
|
94802
|
+
environment: connection.environment ?? null,
|
|
94803
|
+
permission: connection.permission,
|
|
94804
|
+
system: connection.system,
|
|
94805
|
+
server: {
|
|
94806
|
+
host,
|
|
94807
|
+
port: host !== null && typeof connection.port === "number" ? connection.port : null
|
|
94808
|
+
},
|
|
94809
|
+
database
|
|
94810
|
+
};
|
|
94811
|
+
}
|
|
94812
|
+
var backfillCommand;
|
|
94813
|
+
var init_backfill = __esm(() => {
|
|
94814
|
+
init_esm();
|
|
94815
|
+
init_config_v2();
|
|
94816
|
+
init_config_path();
|
|
94817
|
+
init_backfill_artifact();
|
|
94818
|
+
backfillCommand = new Command("backfill").description("Generate reviewable source-to-SQL backfill artifacts; never executes writes");
|
|
94819
|
+
backfillCommand.command("artifact").description("Build a dry-run backfill artifact from a bounded JSON source catalog and two named connections").requiredOption("--source <path>", "JSON source catalog with table, keyColumns, rows, verifyQuery, and expect").requiredOption("--source-use <name>", "Named source connection used to identify the catalog environment").requiredOption("--target-use <name>", "Named target connection for generated SQL").option("--out <path>", "Write artifact JSON to this path").option("--stdout", "Print artifact JSON instead of writing it", false).action(async (options, command) => {
|
|
94820
|
+
const configPath = resolveConfigPath(command);
|
|
94821
|
+
const config = await readV2Config(configPath);
|
|
94822
|
+
const sourcePath = resolve9(options.source);
|
|
94823
|
+
const sourceContent = await Bun.file(sourcePath).text();
|
|
94824
|
+
let raw;
|
|
94825
|
+
try {
|
|
94826
|
+
raw = JSON.parse(sourceContent);
|
|
94827
|
+
} catch {
|
|
94828
|
+
throw new Error(`Source catalog is not valid JSON: ${sourcePath}`);
|
|
94829
|
+
}
|
|
94830
|
+
const artifact2 = buildBackfillArtifact({
|
|
94831
|
+
manifest: parseBackfillSourceManifest(raw),
|
|
94832
|
+
sourcePath,
|
|
94833
|
+
sourceContent,
|
|
94834
|
+
sourceIdentity: identityFor(config, options.sourceUse),
|
|
94835
|
+
targetIdentity: identityFor(config, options.targetUse)
|
|
94836
|
+
});
|
|
94837
|
+
if (options.stdout) {
|
|
94838
|
+
console.log(JSON.stringify(artifact2, null, 2));
|
|
94839
|
+
return;
|
|
94840
|
+
}
|
|
94841
|
+
const out = resolve9(options.out ?? `.dbcli/backfills/${artifact2.source.sha256.slice(0, 12)}.json`);
|
|
94842
|
+
await mkdir17(dirname14(out), { recursive: true });
|
|
94843
|
+
await Bun.write(out, JSON.stringify(artifact2, null, 2) + `
|
|
94844
|
+
`);
|
|
94845
|
+
console.log(JSON.stringify({ path: out, artifact: artifact2 }, null, 2));
|
|
94846
|
+
});
|
|
94847
|
+
});
|
|
94848
|
+
|
|
94037
94849
|
// src/program.ts
|
|
94038
94850
|
var exports_program = {};
|
|
94039
94851
|
__export(exports_program, {
|
|
@@ -94082,7 +94894,7 @@ function hasLongOption(rawArgs, option) {
|
|
|
94082
94894
|
return false;
|
|
94083
94895
|
}
|
|
94084
94896
|
function buildProgram() {
|
|
94085
|
-
const program2 = new Command().name("dbcli").description("Database CLI for AI agents").version(package_default.version).option("--no-color", "Disable colored output").option("-v, --verbose", "Increase verbosity (-v verbose, -vv debug)", (_, prev) => prev + 1, 0).option("-q, --quiet", "Suppress non-essential output").option("--config <path>", "Path to .dbcli config file", ".dbcli").addOption(createConnectionSelectorOption()).enablePositionalOptions();
|
|
94897
|
+
const program2 = new Command().name("dbcli").description("Database CLI for AI agents").version(package_default.version).option("--no-color", "Disable colored output").option("-v, --verbose", "Increase verbosity (-v verbose, -vv debug)", (_, prev) => prev + 1, 0).option("-q, --quiet", "Suppress non-essential output").option("--config <path>", "Path to .dbcli config file", ".dbcli").option("--global", "Use the user-global connection registry (~/.config/dbcli)", false).addOption(createConnectionSelectorOption()).enablePositionalOptions();
|
|
94086
94898
|
program2.addCommand(initCommand);
|
|
94087
94899
|
program2.addCommand(listCommand);
|
|
94088
94900
|
program2.addCommand(schemaCommand);
|
|
@@ -94171,6 +94983,7 @@ function buildProgram() {
|
|
|
94171
94983
|
program2.addCommand(verificationCommand);
|
|
94172
94984
|
program2.addCommand(verifyCommand);
|
|
94173
94985
|
program2.addCommand(proxyCommand);
|
|
94986
|
+
program2.addCommand(backfillCommand);
|
|
94174
94987
|
return program2;
|
|
94175
94988
|
}
|
|
94176
94989
|
var init_program = __esm(() => {
|
|
@@ -94213,6 +95026,7 @@ var init_program = __esm(() => {
|
|
|
94213
95026
|
init_migrate();
|
|
94214
95027
|
init_use();
|
|
94215
95028
|
init_proxy();
|
|
95029
|
+
init_backfill();
|
|
94216
95030
|
init_connection_selector();
|
|
94217
95031
|
init_cli_error();
|
|
94218
95032
|
init_package();
|
|
@@ -94226,9 +95040,121 @@ init_version_check();
|
|
|
94226
95040
|
init_skill();
|
|
94227
95041
|
init_config();
|
|
94228
95042
|
init_connection_selector();
|
|
95043
|
+
init_config_path();
|
|
95044
|
+
|
|
95045
|
+
// src/utils/cli-output.ts
|
|
95046
|
+
var HUMAN_FORMATS = new Set(["text", "table"]);
|
|
95047
|
+
function isMachineReadableCommand(actionCommand, rootCommand) {
|
|
95048
|
+
for (let current = actionCommand;current; current = current.parent ?? undefined) {
|
|
95049
|
+
const options = current.opts();
|
|
95050
|
+
if (options.forAgent === true || options.recovery === true)
|
|
95051
|
+
return true;
|
|
95052
|
+
if (typeof options.format === "string" && !HUMAN_FORMATS.has(options.format))
|
|
95053
|
+
return true;
|
|
95054
|
+
}
|
|
95055
|
+
if (rootCommand) {
|
|
95056
|
+
const options = rootCommand.opts();
|
|
95057
|
+
if (options.forAgent === true || options.recovery === true)
|
|
95058
|
+
return true;
|
|
95059
|
+
if (typeof options.format === "string" && !HUMAN_FORMATS.has(options.format))
|
|
95060
|
+
return true;
|
|
95061
|
+
}
|
|
95062
|
+
return false;
|
|
95063
|
+
}
|
|
95064
|
+
|
|
95065
|
+
// src/utils/update-hint-state.ts
|
|
95066
|
+
import { mkdir as mkdir4, open, rename as rename2, stat, unlink as unlink3, writeFile } from "fs/promises";
|
|
95067
|
+
import { dirname as dirname4, extname, join as join13 } from "path";
|
|
95068
|
+
var MAX_SESSIONS = 32;
|
|
95069
|
+
function defaultCliSessionKey() {
|
|
95070
|
+
const explicit = process.env.DBCLI_SESSION_ID || process.env.TERM_SESSION_ID;
|
|
95071
|
+
if (explicit)
|
|
95072
|
+
return explicit;
|
|
95073
|
+
return `ppid:${process.ppid ?? process.pid}`;
|
|
95074
|
+
}
|
|
95075
|
+
function statePath(configPath) {
|
|
95076
|
+
const looksLikeFile = extname(configPath) !== "" && !configPath.endsWith(".dbcli");
|
|
95077
|
+
const directory = looksLikeFile ? dirname4(configPath) : configPath;
|
|
95078
|
+
return join13(directory, "update-hints.json");
|
|
95079
|
+
}
|
|
95080
|
+
async function withHintStateLock(path3, work) {
|
|
95081
|
+
const lockPath = `${path3}.lock`;
|
|
95082
|
+
for (let attempt = 0;attempt < 20; attempt += 1) {
|
|
95083
|
+
try {
|
|
95084
|
+
const handle = await open(lockPath, "wx");
|
|
95085
|
+
try {
|
|
95086
|
+
return await work();
|
|
95087
|
+
} finally {
|
|
95088
|
+
await handle.close().catch(() => {
|
|
95089
|
+
return;
|
|
95090
|
+
});
|
|
95091
|
+
await unlink3(lockPath).catch(() => {
|
|
95092
|
+
return;
|
|
95093
|
+
});
|
|
95094
|
+
}
|
|
95095
|
+
} catch (error) {
|
|
95096
|
+
const code = error.code;
|
|
95097
|
+
if (code !== "EEXIST")
|
|
95098
|
+
return null;
|
|
95099
|
+
try {
|
|
95100
|
+
const age = Date.now() - (await stat(lockPath)).mtimeMs;
|
|
95101
|
+
if (age > 1e4)
|
|
95102
|
+
await unlink3(lockPath);
|
|
95103
|
+
} catch {}
|
|
95104
|
+
await Bun.sleep(5);
|
|
95105
|
+
}
|
|
95106
|
+
}
|
|
95107
|
+
return null;
|
|
95108
|
+
}
|
|
95109
|
+
async function readState(path3) {
|
|
95110
|
+
try {
|
|
95111
|
+
const file = Bun.file(path3);
|
|
95112
|
+
if (!await file.exists())
|
|
95113
|
+
return { version: 1, sessions: {} };
|
|
95114
|
+
const raw = await file.json();
|
|
95115
|
+
if (raw.version !== 1 || !raw.sessions || typeof raw.sessions !== "object") {
|
|
95116
|
+
return { version: 1, sessions: {} };
|
|
95117
|
+
}
|
|
95118
|
+
return { version: 1, sessions: raw.sessions };
|
|
95119
|
+
} catch {
|
|
95120
|
+
return { version: 1, sessions: {} };
|
|
95121
|
+
}
|
|
95122
|
+
}
|
|
95123
|
+
async function writeState(path3, state) {
|
|
95124
|
+
try {
|
|
95125
|
+
await mkdir4(dirname4(path3), { recursive: true });
|
|
95126
|
+
const temporary = `${path3}.tmp-${process.pid}`;
|
|
95127
|
+
await writeFile(temporary, JSON.stringify(state, null, 2), "utf8");
|
|
95128
|
+
await rename2(temporary, path3);
|
|
95129
|
+
} catch {}
|
|
95130
|
+
}
|
|
95131
|
+
async function claimUpdateHint(configPath, kind, sessionKey, value) {
|
|
95132
|
+
const path3 = statePath(configPath);
|
|
95133
|
+
await mkdir4(dirname4(path3), { recursive: true }).catch(() => {
|
|
95134
|
+
return;
|
|
95135
|
+
});
|
|
95136
|
+
const claimed = await withHintStateLock(path3, async () => {
|
|
95137
|
+
const state = await readState(path3);
|
|
95138
|
+
const current = state.sessions[sessionKey];
|
|
95139
|
+
if (current?.[kind] !== undefined)
|
|
95140
|
+
return false;
|
|
95141
|
+
state.sessions[sessionKey] = {
|
|
95142
|
+
...current ?? {},
|
|
95143
|
+
[kind]: value,
|
|
95144
|
+
updatedAt: new Date().toISOString()
|
|
95145
|
+
};
|
|
95146
|
+
const entries = Object.entries(state.sessions).sort((a, b) => a[1].updatedAt.localeCompare(b[1].updatedAt)).slice(-MAX_SESSIONS);
|
|
95147
|
+
state.sessions = Object.fromEntries(entries);
|
|
95148
|
+
await writeState(path3, state);
|
|
95149
|
+
return true;
|
|
95150
|
+
});
|
|
95151
|
+
return claimed === true;
|
|
95152
|
+
}
|
|
95153
|
+
|
|
95154
|
+
// src/cli.ts
|
|
94229
95155
|
init_program();
|
|
94230
95156
|
init_cli_error();
|
|
94231
|
-
import { join as
|
|
95157
|
+
import { join as join38 } from "path";
|
|
94232
95158
|
import { writeSync as writeSync2 } from "fs";
|
|
94233
95159
|
import { format } from "util";
|
|
94234
95160
|
function installSynchronousRedirectedStdout() {
|
|
@@ -94269,13 +95195,65 @@ function installSynchronousRedirectedStdout() {
|
|
|
94269
95195
|
}
|
|
94270
95196
|
installSynchronousRedirectedStdout();
|
|
94271
95197
|
var _bgVersionCheckResult;
|
|
95198
|
+
var _bgVersionCheckContext = null;
|
|
94272
95199
|
function shouldSkipBackgroundChecks() {
|
|
94273
95200
|
return process.env.DBCLI_NO_UPDATE_CHECK === "1" || process.env.DBCLI_NO_UPDATE_CHECK === "true" || false;
|
|
94274
95201
|
}
|
|
94275
95202
|
var QUIET_OUTPUT_COMMANDS = new Set(["upgrade", "completion"]);
|
|
94276
95203
|
var program2 = buildProgram();
|
|
95204
|
+
function misplacedConnectionSelectorHint() {
|
|
95205
|
+
const rawArgs = process.argv.slice(2);
|
|
95206
|
+
let commandName;
|
|
95207
|
+
let commandIndex = -1;
|
|
95208
|
+
for (let index = 0;index < rawArgs.length; index += 1) {
|
|
95209
|
+
const token = rawArgs[index];
|
|
95210
|
+
if (token === "--")
|
|
95211
|
+
break;
|
|
95212
|
+
if (token === "--config" || token === "--use") {
|
|
95213
|
+
index += 1;
|
|
95214
|
+
continue;
|
|
95215
|
+
}
|
|
95216
|
+
if (token.startsWith("--config=") || token.startsWith("--use=") || token.startsWith("-")) {
|
|
95217
|
+
continue;
|
|
95218
|
+
}
|
|
95219
|
+
commandName = token;
|
|
95220
|
+
commandIndex = index;
|
|
95221
|
+
break;
|
|
95222
|
+
}
|
|
95223
|
+
const command = program2.commands.find((candidate) => candidate.name() === commandName);
|
|
95224
|
+
if (!command || command.options.some((option) => option.long === "--use"))
|
|
95225
|
+
return;
|
|
95226
|
+
const misplacedOptionIndex = rawArgs.findIndex((token, index) => index > commandIndex && (token === "--use" || token.startsWith("--use=")));
|
|
95227
|
+
if (misplacedOptionIndex === -1)
|
|
95228
|
+
return;
|
|
95229
|
+
const commandPath = [command.name()];
|
|
95230
|
+
let currentCommand = command;
|
|
95231
|
+
for (const token of rawArgs.slice(commandIndex + 1, misplacedOptionIndex)) {
|
|
95232
|
+
const child = currentCommand.commands.find((candidate) => candidate.name() === token);
|
|
95233
|
+
if (!child)
|
|
95234
|
+
continue;
|
|
95235
|
+
commandPath.push(child.name());
|
|
95236
|
+
currentCommand = child;
|
|
95237
|
+
}
|
|
95238
|
+
return `Hint: Place --use before the command: dbcli --use <connection> ${commandPath.join(" ")}`;
|
|
95239
|
+
}
|
|
95240
|
+
function configureConnectionSelectorHints(command) {
|
|
95241
|
+
command.configureOutput({
|
|
95242
|
+
outputError: (message, write) => {
|
|
95243
|
+
const hint = /unknown option '--use(?:'|=)/.test(message) ? misplacedConnectionSelectorHint() : undefined;
|
|
95244
|
+
write(hint ? `${message}${hint}
|
|
95245
|
+
` : message);
|
|
95246
|
+
}
|
|
95247
|
+
});
|
|
95248
|
+
for (const child of command.commands)
|
|
95249
|
+
configureConnectionSelectorHints(child);
|
|
95250
|
+
}
|
|
95251
|
+
configureConnectionSelectorHints(program2);
|
|
94277
95252
|
program2.hook("preAction", (thisCommand, actionCommand) => {
|
|
95253
|
+
_bgVersionCheckResult = undefined;
|
|
95254
|
+
_bgVersionCheckContext = null;
|
|
94278
95255
|
const opts = thisCommand.opts();
|
|
95256
|
+
const machineOutput = isMachineReadableCommand(actionCommand, thisCommand);
|
|
94279
95257
|
setGlobalConnectionName(resolveConnectionSelector({
|
|
94280
95258
|
root: opts.use,
|
|
94281
95259
|
command: actionCommand.opts().use,
|
|
@@ -94293,13 +95271,18 @@ program2.hook("preAction", (thisCommand, actionCommand) => {
|
|
|
94293
95271
|
level = 2 /* VERBOSE */;
|
|
94294
95272
|
}
|
|
94295
95273
|
setGlobalLogger(createLogger(level));
|
|
94296
|
-
|
|
94297
|
-
|
|
95274
|
+
const configPath = resolveConfigPath(actionCommand);
|
|
95275
|
+
_bgVersionCheckContext = {
|
|
95276
|
+
configPath,
|
|
95277
|
+
sessionKey: defaultCliSessionKey(),
|
|
95278
|
+
machineOutput
|
|
95279
|
+
};
|
|
95280
|
+
if (!opts.quiet && !QUIET_OUTPUT_COMMANDS.has(actionCommand.name()) && !machineOutput && !shouldSkipBackgroundChecks()) {
|
|
94298
95281
|
(async () => {
|
|
94299
95282
|
try {
|
|
94300
95283
|
let cache = null;
|
|
94301
95284
|
try {
|
|
94302
|
-
const cacheFile = Bun.file(
|
|
95285
|
+
const cacheFile = Bun.file(join38(configPath, "version-check.json"));
|
|
94303
95286
|
if (await cacheFile.exists()) {
|
|
94304
95287
|
cache = await cacheFile.json();
|
|
94305
95288
|
}
|
|
@@ -94313,14 +95296,15 @@ program2.hook("preAction", (thisCommand, actionCommand) => {
|
|
|
94313
95296
|
}
|
|
94314
95297
|
});
|
|
94315
95298
|
program2.hook("postAction", async (thisCommand, actionCommand) => {
|
|
94316
|
-
|
|
95299
|
+
const context = _bgVersionCheckContext;
|
|
95300
|
+
if (_bgVersionCheckResult?.hasUpdate && context && !context.machineOutput && !thisCommand.opts().quiet && !QUIET_OUTPUT_COMMANDS.has(actionCommand.name()) && await claimUpdateHint(context.configPath, "update", context.sessionKey, _bgVersionCheckResult.latestVersion)) {
|
|
94317
95301
|
process.stderr.write(formatUpdateHint(_bgVersionCheckResult.latestVersion) + `
|
|
94318
95302
|
`);
|
|
94319
95303
|
}
|
|
94320
95304
|
const isQuietOutput = QUIET_OUTPUT_COMMANDS.has(actionCommand.name()) || actionCommand.name() === "skill";
|
|
94321
|
-
if (!thisCommand.opts().quiet && !isQuietOutput && !shouldSkipBackgroundChecks()) {
|
|
95305
|
+
if (!thisCommand.opts().quiet && !isQuietOutput && !context?.machineOutput && !shouldSkipBackgroundChecks()) {
|
|
94322
95306
|
const outdatedSkills = await checkSkillUpdates();
|
|
94323
|
-
if (outdatedSkills.length > 0) {
|
|
95307
|
+
if (outdatedSkills.length > 0 && context && await claimUpdateHint(context.configPath, "skill", context.sessionKey, outdatedSkills.slice().sort().join(","))) {
|
|
94324
95308
|
process.stderr.write(formatSkillUpdateReminder(outdatedSkills) + `
|
|
94325
95309
|
`);
|
|
94326
95310
|
}
|