@carllee1983/dbcli 1.44.0 → 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 +26 -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.d.ts +7 -1
- package/dist/agent-core.mjs +29 -8
- package/dist/cli.mjs +2045 -11756
- package/dist/core.d.ts +48 -3
- package/dist/core.mjs +348 -89
- 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/core.mjs
CHANGED
|
@@ -753,16 +753,16 @@ var init_index_min = __esm(() => {
|
|
|
753
753
|
});
|
|
754
754
|
|
|
755
755
|
// src/utils/schema-path.ts
|
|
756
|
-
import { join as
|
|
756
|
+
import { join as join6 } from "path";
|
|
757
757
|
function resolveSchemaPath(dbcliPath, connectionName) {
|
|
758
758
|
if (!connectionName)
|
|
759
|
-
return
|
|
760
|
-
return
|
|
759
|
+
return join6(dbcliPath, "schemas");
|
|
760
|
+
return join6(dbcliPath, "schemas", connectionName);
|
|
761
761
|
}
|
|
762
762
|
var init_schema_path = () => {};
|
|
763
763
|
|
|
764
764
|
// src/core/schema-cache.ts
|
|
765
|
-
import { join as
|
|
765
|
+
import { join as join7 } from "path";
|
|
766
766
|
|
|
767
767
|
class SchemaCacheManager {
|
|
768
768
|
cache;
|
|
@@ -788,13 +788,13 @@ class SchemaCacheManager {
|
|
|
788
788
|
}
|
|
789
789
|
async initialize() {
|
|
790
790
|
try {
|
|
791
|
-
const indexPath =
|
|
791
|
+
const indexPath = join7(this.schemaRoot, "index.json");
|
|
792
792
|
const indexFile = Bun.file(indexPath);
|
|
793
793
|
if (await indexFile.exists()) {
|
|
794
794
|
const indexContent = await indexFile.text();
|
|
795
795
|
this.index = JSON.parse(indexContent);
|
|
796
796
|
}
|
|
797
|
-
const hotPath =
|
|
797
|
+
const hotPath = join7(this.schemaRoot, "hot-schemas.json");
|
|
798
798
|
const hotFile = Bun.file(hotPath);
|
|
799
799
|
if (await hotFile.exists()) {
|
|
800
800
|
const hotContent = await hotFile.text();
|
|
@@ -827,7 +827,7 @@ class SchemaCacheManager {
|
|
|
827
827
|
return null;
|
|
828
828
|
}
|
|
829
829
|
try {
|
|
830
|
-
const filePath =
|
|
830
|
+
const filePath = join7(this.schemaRoot, tableInfo.file);
|
|
831
831
|
const file = Bun.file(filePath);
|
|
832
832
|
if (!await file.exists()) {
|
|
833
833
|
console.error(`Cold table file not found: ${tableInfo.file} for table ${tableName}`);
|
|
@@ -882,12 +882,12 @@ var init_schema_cache = __esm(() => {
|
|
|
882
882
|
});
|
|
883
883
|
|
|
884
884
|
// src/core/schema-index.ts
|
|
885
|
-
import { join as
|
|
885
|
+
import { join as join8 } from "path";
|
|
886
886
|
|
|
887
887
|
class SchemaIndexBuilder {
|
|
888
888
|
static async loadIndex(dbcliPath, connectionName) {
|
|
889
889
|
try {
|
|
890
|
-
const indexPath =
|
|
890
|
+
const indexPath = join8(resolveSchemaPath(dbcliPath, connectionName), "index.json");
|
|
891
891
|
const file = Bun.file(indexPath);
|
|
892
892
|
if (!await file.exists()) {
|
|
893
893
|
return null;
|
|
@@ -934,7 +934,7 @@ class SchemaIndexBuilder {
|
|
|
934
934
|
try {
|
|
935
935
|
const schemasDir = resolveSchemaPath(dbcliPath, connectionName);
|
|
936
936
|
await this.ensureDir(schemasDir);
|
|
937
|
-
const indexPath =
|
|
937
|
+
const indexPath = join8(schemasDir, "index.json");
|
|
938
938
|
const indexFile = Bun.file(indexPath);
|
|
939
939
|
await indexFile.write(JSON.stringify(index, null, 2));
|
|
940
940
|
} catch (error) {
|
|
@@ -980,7 +980,7 @@ var exports_schema_loader = {};
|
|
|
980
980
|
__export(exports_schema_loader, {
|
|
981
981
|
SchemaLayeredLoader: () => SchemaLayeredLoader
|
|
982
982
|
});
|
|
983
|
-
import { join as
|
|
983
|
+
import { join as join9 } from "path";
|
|
984
984
|
|
|
985
985
|
class SchemaLayeredLoader {
|
|
986
986
|
dbcliPath;
|
|
@@ -1047,7 +1047,7 @@ class SchemaLayeredLoader {
|
|
|
1047
1047
|
}
|
|
1048
1048
|
async ensureDirectories() {
|
|
1049
1049
|
const base = resolveSchemaPath(this.dbcliPath, this.connectionName);
|
|
1050
|
-
const dirs = [base,
|
|
1050
|
+
const dirs = [base, join9(base, "cold")];
|
|
1051
1051
|
for (const dir of dirs) {
|
|
1052
1052
|
try {
|
|
1053
1053
|
const dirFile = Bun.file(dir);
|
|
@@ -2584,13 +2584,13 @@ var PromisePolyfill;
|
|
|
2584
2584
|
var init_promise_polyfill = __esm(() => {
|
|
2585
2585
|
PromisePolyfill = class PromisePolyfill extends Promise {
|
|
2586
2586
|
static withResolver() {
|
|
2587
|
-
let
|
|
2587
|
+
let resolve3;
|
|
2588
2588
|
let reject;
|
|
2589
2589
|
const promise = new Promise((res, rej) => {
|
|
2590
|
-
|
|
2590
|
+
resolve3 = res;
|
|
2591
2591
|
reject = rej;
|
|
2592
2592
|
});
|
|
2593
|
-
return { promise, resolve:
|
|
2593
|
+
return { promise, resolve: resolve3, reject };
|
|
2594
2594
|
}
|
|
2595
2595
|
};
|
|
2596
2596
|
});
|
|
@@ -2629,7 +2629,7 @@ function createPrompt(view) {
|
|
|
2629
2629
|
});
|
|
2630
2630
|
output.mute();
|
|
2631
2631
|
const screen = new ScreenManager(rl);
|
|
2632
|
-
const { promise, resolve:
|
|
2632
|
+
const { promise, resolve: resolve3, reject } = PromisePolyfill.withResolver();
|
|
2633
2633
|
const cancel = () => reject(new CancelPromptError);
|
|
2634
2634
|
if (signal) {
|
|
2635
2635
|
const abort = () => reject(new AbortPromptError({ cause: signal.reason }));
|
|
@@ -2660,7 +2660,7 @@ function createPrompt(view) {
|
|
|
2660
2660
|
try {
|
|
2661
2661
|
const nextView = view(config, (value) => {
|
|
2662
2662
|
if (effectsSettled) {
|
|
2663
|
-
|
|
2663
|
+
resolve3(value);
|
|
2664
2664
|
} else {
|
|
2665
2665
|
pendingDone = { value };
|
|
2666
2666
|
}
|
|
@@ -2683,7 +2683,7 @@ function createPrompt(view) {
|
|
|
2683
2683
|
if (pendingDone !== null) {
|
|
2684
2684
|
const { value } = pendingDone;
|
|
2685
2685
|
pendingDone = null;
|
|
2686
|
-
|
|
2686
|
+
resolve3(value);
|
|
2687
2687
|
}
|
|
2688
2688
|
});
|
|
2689
2689
|
};
|
|
@@ -8538,7 +8538,7 @@ var require_lib2 = __commonJS((exports) => {
|
|
|
8538
8538
|
return matches;
|
|
8539
8539
|
};
|
|
8540
8540
|
exports.analyse = analyse;
|
|
8541
|
-
var detectFile = (filepath, opts = {}) => new Promise((
|
|
8541
|
+
var detectFile = (filepath, opts = {}) => new Promise((resolve3, reject) => {
|
|
8542
8542
|
let fd;
|
|
8543
8543
|
const fs = (0, node_1.default)();
|
|
8544
8544
|
const handler = (err, buffer) => {
|
|
@@ -8548,7 +8548,7 @@ var require_lib2 = __commonJS((exports) => {
|
|
|
8548
8548
|
if (err) {
|
|
8549
8549
|
reject(err);
|
|
8550
8550
|
} else if (buffer) {
|
|
8551
|
-
|
|
8551
|
+
resolve3((0, exports.detect)(buffer));
|
|
8552
8552
|
} else {
|
|
8553
8553
|
reject(new Error("No error and no buffer received"));
|
|
8554
8554
|
}
|
|
@@ -12320,7 +12320,7 @@ import { spawn, spawnSync } from "child_process";
|
|
|
12320
12320
|
import { readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
12321
12321
|
import path2 from "path";
|
|
12322
12322
|
import os from "os";
|
|
12323
|
-
import { randomUUID as
|
|
12323
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
12324
12324
|
function sanitizeAffix(affix) {
|
|
12325
12325
|
if (!affix)
|
|
12326
12326
|
return "";
|
|
@@ -12355,12 +12355,12 @@ class ExternalEditor {
|
|
|
12355
12355
|
}
|
|
12356
12356
|
runAsync(callback) {
|
|
12357
12357
|
this.createTempFile();
|
|
12358
|
-
const promise = new Promise((
|
|
12358
|
+
const promise = new Promise((resolve3, reject) => {
|
|
12359
12359
|
try {
|
|
12360
12360
|
const editorProcess = spawn(this.editor.bin, this.editor.args.concat([this.tempFile]), { stdio: "inherit" });
|
|
12361
12361
|
editorProcess.on("exit", (code) => {
|
|
12362
12362
|
this.lastExitStatus = code;
|
|
12363
|
-
|
|
12363
|
+
resolve3();
|
|
12364
12364
|
});
|
|
12365
12365
|
} catch (launchError) {
|
|
12366
12366
|
reject(new LaunchEditorError(launchError));
|
|
@@ -12389,7 +12389,7 @@ class ExternalEditor {
|
|
|
12389
12389
|
createTempFile() {
|
|
12390
12390
|
try {
|
|
12391
12391
|
const baseDir = this.fileOptions.dir ?? os.tmpdir();
|
|
12392
|
-
const id =
|
|
12392
|
+
const id = randomUUID3();
|
|
12393
12393
|
const prefix = sanitizeAffix(this.fileOptions.prefix);
|
|
12394
12394
|
const postfix = sanitizeAffix(this.fileOptions.postfix);
|
|
12395
12395
|
const filename = `${prefix}${id}${postfix}`;
|
|
@@ -13658,6 +13658,7 @@ var messages_default = {
|
|
|
13658
13658
|
connection_success: "\u2713 Database connection successful",
|
|
13659
13659
|
connection_failed: "\u2717 Database connection failed",
|
|
13660
13660
|
config_saved: "Configuration saved to .dbcli",
|
|
13661
|
+
config_saved_global: "Global configuration saved to {path}",
|
|
13661
13662
|
config_exists_overwrite: "Configuration file .dbcli already exists. Overwrite? (y/n): ",
|
|
13662
13663
|
cancelled: "Cancelled. Configuration not changed.",
|
|
13663
13664
|
skip_test_env_ref: "Skipping connection test in env-ref mode",
|
|
@@ -13937,6 +13938,7 @@ var messages_default2 = {
|
|
|
13937
13938
|
connection_success: "\u2713 \u8CC7\u6599\u5EAB\u9023\u63A5\u6210\u529F",
|
|
13938
13939
|
connection_failed: "\u2717 \u8CC7\u6599\u5EAB\u9023\u63A5\u5931\u6557",
|
|
13939
13940
|
config_saved: "\u914D\u7F6E\u5DF2\u4FDD\u5B58\u81F3 .dbcli",
|
|
13941
|
+
config_saved_global: "\u5168\u57DF\u8A2D\u5B9A\u5DF2\u5132\u5B58\u81F3 {path}",
|
|
13940
13942
|
config_exists_overwrite: "\u914D\u7F6E\u6A94\u6848 .dbcli \u5DF2\u5B58\u5728\u3002\u662F\u5426\u8986\u84CB\uFF1F (y/n)\uFF1A",
|
|
13941
13943
|
cancelled: "\u5DF2\u53D6\u6D88\u3002\u914D\u7F6E\u672A\u66F4\u6539\u3002",
|
|
13942
13944
|
skip_test_env_ref: "\u8DF3\u904E\u9023\u7DDA\u6E2C\u8A66\uFF08\u74B0\u5883\u8B8A\u6578\u53C3\u7167\u6A21\u5F0F\uFF09",
|
|
@@ -17549,12 +17551,205 @@ class SessionIdService {
|
|
|
17549
17551
|
}
|
|
17550
17552
|
|
|
17551
17553
|
// src/core/config-binding.ts
|
|
17552
|
-
import { createHash } from "crypto";
|
|
17553
|
-
import { mkdir as
|
|
17554
|
+
import { createHash as createHash2 } from "crypto";
|
|
17555
|
+
import { mkdir as mkdir5, unlink as unlink2 } from "fs/promises";
|
|
17554
17556
|
import { homedir } from "os";
|
|
17555
|
-
import { basename, join as
|
|
17557
|
+
import { basename, join as join4, resolve as resolve2 } from "path";
|
|
17558
|
+
|
|
17559
|
+
// src/agent-core/errors.ts
|
|
17560
|
+
class ConfigError extends Error {
|
|
17561
|
+
constructor(message) {
|
|
17562
|
+
super(message);
|
|
17563
|
+
this.name = "ConfigError";
|
|
17564
|
+
if (Error.captureStackTrace)
|
|
17565
|
+
Error.captureStackTrace(this, ConfigError);
|
|
17566
|
+
}
|
|
17567
|
+
}
|
|
17568
|
+
// src/core/config-mutation-guard.ts
|
|
17569
|
+
function assertConfigMutationApproved() {
|
|
17570
|
+
if (process.env.DBCLI_AGENT_MODE !== "1")
|
|
17571
|
+
return;
|
|
17572
|
+
throw new ConfigError("Agent mode blocks configuration, permission, and credential changes. A human or administrator must run the approved workflow outside agent mode.");
|
|
17573
|
+
}
|
|
17574
|
+
|
|
17575
|
+
// src/core/config-integrity.ts
|
|
17576
|
+
import { createHash, randomUUID as randomUUID2 } from "crypto";
|
|
17577
|
+
import { chmod, lstat, mkdir as mkdir4, rename as rename3, unlink } from "fs/promises";
|
|
17578
|
+
import { dirname as dirname3, join as join3, resolve } from "path";
|
|
17579
|
+
var INTEGRITY_FILE = ".config-integrity.json";
|
|
17580
|
+
function integrityPath(storagePath, recordName = INTEGRITY_FILE) {
|
|
17581
|
+
return join3(storagePath, recordName);
|
|
17582
|
+
}
|
|
17583
|
+
function hash(content) {
|
|
17584
|
+
return createHash("sha256").update(content).digest("hex");
|
|
17585
|
+
}
|
|
17586
|
+
function protectedPath(storagePath, protectedFileName) {
|
|
17587
|
+
return resolve(join3(storagePath, protectedFileName));
|
|
17588
|
+
}
|
|
17589
|
+
function anchorPathFor(protectedFilePath) {
|
|
17590
|
+
const anchorDirectory = process.env.DBCLI_CONFIG_INTEGRITY_ANCHOR_DIR?.trim();
|
|
17591
|
+
if (!anchorDirectory)
|
|
17592
|
+
return null;
|
|
17593
|
+
const key = createHash("sha256").update(protectedFilePath).digest("hex");
|
|
17594
|
+
return join3(resolve(anchorDirectory), `${key}.json`);
|
|
17595
|
+
}
|
|
17596
|
+
async function bestEffortSecureMode(path, mode) {
|
|
17597
|
+
try {
|
|
17598
|
+
await chmod(path, mode);
|
|
17599
|
+
} catch {}
|
|
17600
|
+
}
|
|
17601
|
+
async function assertAgentReadableFile(path, description = "config") {
|
|
17602
|
+
if (process.env.DBCLI_AGENT_MODE !== "1")
|
|
17603
|
+
return;
|
|
17604
|
+
try {
|
|
17605
|
+
const fileStat = await lstat(path);
|
|
17606
|
+
if (fileStat.isSymbolicLink() || !fileStat.isFile()) {
|
|
17607
|
+
throw new ConfigError(`Agent mode refuses a non-regular ${description} file: ${path}. Run the human/admin setup workflow to provision it.`);
|
|
17608
|
+
}
|
|
17609
|
+
if ((fileStat.mode & 18) !== 0) {
|
|
17610
|
+
throw new ConfigError(`Agent mode refuses a group/world-writable config: ${path}. Run the human/admin setup workflow to secure it.`);
|
|
17611
|
+
}
|
|
17612
|
+
} catch (error) {
|
|
17613
|
+
if (error instanceof ConfigError)
|
|
17614
|
+
throw error;
|
|
17615
|
+
throw new ConfigError(`Agent mode refuses an unreadable ${description} file: ${path}`);
|
|
17616
|
+
}
|
|
17617
|
+
}
|
|
17618
|
+
async function writeProtectedFileWithIntegrity(storagePath, protectedFileName, protectedContent, recordName) {
|
|
17619
|
+
await mkdir4(storagePath, { recursive: true });
|
|
17620
|
+
const protectedFilePath = protectedPath(storagePath, protectedFileName);
|
|
17621
|
+
const localPath = integrityPath(storagePath, recordName);
|
|
17622
|
+
const anchorPath = anchorPathFor(protectedFilePath);
|
|
17623
|
+
const record = {
|
|
17624
|
+
version: 1,
|
|
17625
|
+
configSha256: hash(protectedContent),
|
|
17626
|
+
updatedAt: new Date().toISOString(),
|
|
17627
|
+
targetPath: protectedFilePath
|
|
17628
|
+
};
|
|
17629
|
+
const recordContent = JSON.stringify(record, null, 2);
|
|
17630
|
+
const suffix = `${process.pid}-${randomUUID2()}`;
|
|
17631
|
+
const protectedTemporary = `${protectedFilePath}.tmp-${suffix}`;
|
|
17632
|
+
const localTemporary = `${localPath}.tmp-${suffix}`;
|
|
17633
|
+
const anchorTemporary = anchorPath ? `${anchorPath}.tmp-${suffix}` : null;
|
|
17634
|
+
const published = [];
|
|
17635
|
+
try {
|
|
17636
|
+
const [protectedSnapshot, localSnapshot, anchorSnapshot] = await Promise.all([
|
|
17637
|
+
snapshot(protectedFilePath),
|
|
17638
|
+
snapshot(localPath),
|
|
17639
|
+
anchorPath ? snapshot(anchorPath) : Promise.resolve(null)
|
|
17640
|
+
]);
|
|
17641
|
+
await Bun.write(protectedTemporary, protectedContent);
|
|
17642
|
+
await Bun.write(localTemporary, recordContent);
|
|
17643
|
+
if (anchorPath && anchorTemporary) {
|
|
17644
|
+
await mkdir4(dirname3(anchorPath), { recursive: true });
|
|
17645
|
+
await Bun.write(anchorTemporary, recordContent);
|
|
17646
|
+
}
|
|
17647
|
+
const publish = async (temporary, previous) => {
|
|
17648
|
+
await rename3(temporary, previous.path);
|
|
17649
|
+
published.push(previous);
|
|
17650
|
+
};
|
|
17651
|
+
if (anchorTemporary && anchorPath) {
|
|
17652
|
+
await publish(anchorTemporary, anchorSnapshot ?? { path: anchorPath, content: null });
|
|
17653
|
+
}
|
|
17654
|
+
await publish(localTemporary, localSnapshot);
|
|
17655
|
+
await publish(protectedTemporary, protectedSnapshot);
|
|
17656
|
+
} catch (error) {
|
|
17657
|
+
for (const publishedFile of published.reverse()) {
|
|
17658
|
+
await restore(publishedFile).catch(() => {
|
|
17659
|
+
return;
|
|
17660
|
+
});
|
|
17661
|
+
}
|
|
17662
|
+
throw error;
|
|
17663
|
+
} finally {
|
|
17664
|
+
await unlink(protectedTemporary).catch(() => {
|
|
17665
|
+
return;
|
|
17666
|
+
});
|
|
17667
|
+
await unlink(localTemporary).catch(() => {
|
|
17668
|
+
return;
|
|
17669
|
+
});
|
|
17670
|
+
if (anchorTemporary)
|
|
17671
|
+
await unlink(anchorTemporary).catch(() => {
|
|
17672
|
+
return;
|
|
17673
|
+
});
|
|
17674
|
+
}
|
|
17675
|
+
if (anchorPath) {
|
|
17676
|
+
await bestEffortSecureMode(dirname3(anchorPath), 448);
|
|
17677
|
+
await bestEffortSecureMode(anchorPath, 384);
|
|
17678
|
+
}
|
|
17679
|
+
await bestEffortSecureMode(storagePath, 448);
|
|
17680
|
+
await bestEffortSecureMode(protectedFilePath, 384);
|
|
17681
|
+
await bestEffortSecureMode(localPath, 384);
|
|
17682
|
+
}
|
|
17683
|
+
async function snapshot(path) {
|
|
17684
|
+
const file = Bun.file(path);
|
|
17685
|
+
return { path, content: await file.exists() ? await file.text() : null };
|
|
17686
|
+
}
|
|
17687
|
+
async function restore(snapshot2) {
|
|
17688
|
+
if (snapshot2.content === null) {
|
|
17689
|
+
await unlink(snapshot2.path).catch(() => {
|
|
17690
|
+
return;
|
|
17691
|
+
});
|
|
17692
|
+
return;
|
|
17693
|
+
}
|
|
17694
|
+
const temporary = `${snapshot2.path}.rollback-${process.pid}-${randomUUID2()}`;
|
|
17695
|
+
await Bun.write(temporary, snapshot2.content);
|
|
17696
|
+
await rename3(temporary, snapshot2.path);
|
|
17697
|
+
}
|
|
17698
|
+
async function writeConfigWithIntegrity(storagePath, configContent) {
|
|
17699
|
+
await writeProtectedFileWithIntegrity(storagePath, "config.json", configContent, INTEGRITY_FILE);
|
|
17700
|
+
}
|
|
17701
|
+
async function writeBindingWithIntegrity(projectPath, bindingContent) {
|
|
17702
|
+
await writeProtectedFileWithIntegrity(projectPath, "config.json", bindingContent, ".binding-integrity.json");
|
|
17703
|
+
}
|
|
17704
|
+
async function assertIntegrityRecord(storagePath, configContent, recordName, protectedFileName, description, options = {}) {
|
|
17705
|
+
if (process.env.DBCLI_AGENT_MODE !== "1")
|
|
17706
|
+
return;
|
|
17707
|
+
const configPath = protectedPath(storagePath, protectedFileName);
|
|
17708
|
+
await assertAgentReadableFile(configPath, description);
|
|
17709
|
+
const verifyRecord = async (path, label) => {
|
|
17710
|
+
const file = Bun.file(path);
|
|
17711
|
+
if (!await file.exists()) {
|
|
17712
|
+
throw new ConfigError(`Agent mode refuses a missing ${label}: ${path}`);
|
|
17713
|
+
}
|
|
17714
|
+
let record;
|
|
17715
|
+
try {
|
|
17716
|
+
await assertAgentReadableFile(path, label);
|
|
17717
|
+
record = await file.json();
|
|
17718
|
+
} catch (error) {
|
|
17719
|
+
if (error instanceof ConfigError)
|
|
17720
|
+
throw error;
|
|
17721
|
+
throw new ConfigError(`Agent mode refuses an unreadable ${label}: ${path}`);
|
|
17722
|
+
}
|
|
17723
|
+
if (record.version !== 1 || typeof record.configSha256 !== "string" || record.configSha256 !== hash(configContent) || record.targetPath !== undefined && record.targetPath !== configPath) {
|
|
17724
|
+
throw new ConfigError(`Agent mode detected direct ${description} tampering or an out-of-band edit: ${configPath}`);
|
|
17725
|
+
}
|
|
17726
|
+
};
|
|
17727
|
+
const anchorPath = anchorPathFor(configPath);
|
|
17728
|
+
if (anchorPath)
|
|
17729
|
+
await verifyRecord(anchorPath, "detached config integrity anchor");
|
|
17730
|
+
const localPath = integrityPath(storagePath, recordName);
|
|
17731
|
+
const localExists = await Bun.file(localPath).exists();
|
|
17732
|
+
if (localExists) {
|
|
17733
|
+
await verifyRecord(localPath, "config integrity record");
|
|
17734
|
+
} else if (options.requireRecord && !anchorPath) {
|
|
17735
|
+
throw new ConfigError(`Agent mode refuses a missing config integrity record: ${localPath}`);
|
|
17736
|
+
}
|
|
17737
|
+
}
|
|
17738
|
+
async function assertConfigIntegrity(storagePath, configContent, options) {
|
|
17739
|
+
await assertIntegrityRecord(storagePath, configContent, INTEGRITY_FILE, "config.json", "config", options);
|
|
17740
|
+
}
|
|
17741
|
+
async function assertBindingIntegrity(projectPath, bindingContent, options) {
|
|
17742
|
+
await assertIntegrityRecord(projectPath, bindingContent, ".binding-integrity.json", "config.json", "project binding", options);
|
|
17743
|
+
}
|
|
17744
|
+
|
|
17745
|
+
// src/core/config-binding.ts
|
|
17556
17746
|
var BINDING_FILE_NAME = "config.json";
|
|
17557
|
-
|
|
17747
|
+
function getDbcliConfigHome() {
|
|
17748
|
+
const configuredHome = process.env.DBCLI_CONFIG_HOME?.trim();
|
|
17749
|
+
if (configuredHome)
|
|
17750
|
+
return configuredHome;
|
|
17751
|
+
return join4(process.env.HOME?.trim() || homedir(), ".config", "dbcli");
|
|
17752
|
+
}
|
|
17558
17753
|
function isProjectConfigBinding(raw) {
|
|
17559
17754
|
if (typeof raw !== "object" || raw === null)
|
|
17560
17755
|
return false;
|
|
@@ -17562,22 +17757,35 @@ function isProjectConfigBinding(raw) {
|
|
|
17562
17757
|
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;
|
|
17563
17758
|
}
|
|
17564
17759
|
function getDbcliHomeRoot() {
|
|
17565
|
-
return
|
|
17760
|
+
return getDbcliConfigHome();
|
|
17761
|
+
}
|
|
17762
|
+
function getGlobalConfigPath() {
|
|
17763
|
+
return getDbcliHomeRoot();
|
|
17764
|
+
}
|
|
17765
|
+
function isGlobalConfigPath(path) {
|
|
17766
|
+
return resolve2(path) === resolve2(getGlobalConfigPath());
|
|
17566
17767
|
}
|
|
17567
17768
|
function getProjectStoragePath(projectPath) {
|
|
17568
|
-
const normalizedProjectPath =
|
|
17769
|
+
const normalizedProjectPath = resolve2(projectPath);
|
|
17569
17770
|
const projectName = basename(normalizedProjectPath) || "project";
|
|
17570
|
-
const
|
|
17571
|
-
return
|
|
17771
|
+
const hash2 = createHash2("sha1").update(normalizedProjectPath).digest("hex").slice(0, 12);
|
|
17772
|
+
return join4(getDbcliHomeRoot(), "projects", `${projectName}-${hash2}`);
|
|
17572
17773
|
}
|
|
17573
17774
|
async function readProjectBinding(projectPath) {
|
|
17574
|
-
const configFile = Bun.file(
|
|
17775
|
+
const configFile = Bun.file(join4(projectPath, BINDING_FILE_NAME));
|
|
17575
17776
|
if (!await configFile.exists())
|
|
17576
17777
|
return null;
|
|
17577
17778
|
try {
|
|
17578
|
-
|
|
17579
|
-
|
|
17580
|
-
|
|
17779
|
+
await assertAgentReadableFile(join4(projectPath, BINDING_FILE_NAME), "project binding");
|
|
17780
|
+
const content = await configFile.text();
|
|
17781
|
+
const raw = JSON.parse(content);
|
|
17782
|
+
if (!isProjectConfigBinding(raw))
|
|
17783
|
+
return null;
|
|
17784
|
+
await assertBindingIntegrity(projectPath, content, { requireRecord: true });
|
|
17785
|
+
return raw;
|
|
17786
|
+
} catch (error) {
|
|
17787
|
+
if (error instanceof ConfigError)
|
|
17788
|
+
throw error;
|
|
17581
17789
|
return null;
|
|
17582
17790
|
}
|
|
17583
17791
|
}
|
|
@@ -17586,18 +17794,20 @@ async function resolveConfigStoragePath(path) {
|
|
|
17586
17794
|
return binding?.binding.storagePath ?? path;
|
|
17587
17795
|
}
|
|
17588
17796
|
async function writeProjectBinding(projectPath, storagePath = getProjectStoragePath(projectPath)) {
|
|
17797
|
+
assertConfigMutationApproved();
|
|
17589
17798
|
const binding = {
|
|
17590
17799
|
version: 3,
|
|
17591
17800
|
binding: {
|
|
17592
17801
|
type: "home-storage",
|
|
17593
17802
|
storagePath,
|
|
17594
|
-
projectPath:
|
|
17803
|
+
projectPath: resolve2(projectPath),
|
|
17595
17804
|
createdAt: new Date().toISOString()
|
|
17596
17805
|
}
|
|
17597
17806
|
};
|
|
17598
|
-
await
|
|
17599
|
-
await
|
|
17600
|
-
|
|
17807
|
+
await mkdir5(projectPath, { recursive: true });
|
|
17808
|
+
await mkdir5(storagePath, { recursive: true });
|
|
17809
|
+
const content = JSON.stringify(binding, null, 2);
|
|
17810
|
+
await writeBindingWithIntegrity(projectPath, content);
|
|
17601
17811
|
return binding;
|
|
17602
17812
|
}
|
|
17603
17813
|
|
|
@@ -21628,6 +21838,7 @@ var ConnectionConfigSchema = exports_external.union([
|
|
|
21628
21838
|
ElasticsearchConnectionConfigSchema
|
|
21629
21839
|
]);
|
|
21630
21840
|
var PermissionSchema = exports_external.enum(["query-only", "read-write", "data-admin", "admin"]).default("query-only");
|
|
21841
|
+
var EnvironmentLabelSchema = exports_external.string().trim().transform((value) => value || undefined).optional();
|
|
21631
21842
|
var MetadataSchema = exports_external.object({
|
|
21632
21843
|
createdAt: exports_external.string().datetime().optional(),
|
|
21633
21844
|
version: exports_external.string().default("1.0"),
|
|
@@ -21667,19 +21878,23 @@ var DbcliConfigSchema = exports_external.object({
|
|
|
21667
21878
|
});
|
|
21668
21879
|
var SqlNamedConnectionSchema = SqlConnectionConfigSchema.extend({
|
|
21669
21880
|
permission: PermissionSchema,
|
|
21670
|
-
envFile: exports_external.string().optional()
|
|
21881
|
+
envFile: exports_external.string().optional(),
|
|
21882
|
+
environment: EnvironmentLabelSchema
|
|
21671
21883
|
});
|
|
21672
21884
|
var MongoDBNamedConnectionSchema = MongoDBConnectionConfigSchema.extend({
|
|
21673
21885
|
permission: PermissionSchema,
|
|
21674
|
-
envFile: exports_external.string().optional()
|
|
21886
|
+
envFile: exports_external.string().optional(),
|
|
21887
|
+
environment: EnvironmentLabelSchema
|
|
21675
21888
|
});
|
|
21676
21889
|
var RedisNamedConnectionSchema = RedisConnectionConfigSchema.extend({
|
|
21677
21890
|
permission: PermissionSchema,
|
|
21678
|
-
envFile: exports_external.string().optional()
|
|
21891
|
+
envFile: exports_external.string().optional(),
|
|
21892
|
+
environment: EnvironmentLabelSchema
|
|
21679
21893
|
});
|
|
21680
21894
|
var ElasticsearchNamedConnectionSchema = ElasticsearchConnectionConfigSchema.extend({
|
|
21681
21895
|
permission: PermissionSchema,
|
|
21682
|
-
envFile: exports_external.string().optional()
|
|
21896
|
+
envFile: exports_external.string().optional(),
|
|
21897
|
+
environment: EnvironmentLabelSchema
|
|
21683
21898
|
});
|
|
21684
21899
|
var NamedConnectionSchema = exports_external.union([
|
|
21685
21900
|
SqlNamedConnectionSchema,
|
|
@@ -21704,16 +21919,8 @@ var DbcliConfigV2Schema = exports_external.object({
|
|
|
21704
21919
|
path: ["default"]
|
|
21705
21920
|
});
|
|
21706
21921
|
|
|
21707
|
-
// src/agent-core/errors.ts
|
|
21708
|
-
class ConfigError extends Error {
|
|
21709
|
-
constructor(message) {
|
|
21710
|
-
super(message);
|
|
21711
|
-
this.name = "ConfigError";
|
|
21712
|
-
if (Error.captureStackTrace)
|
|
21713
|
-
Error.captureStackTrace(this, ConfigError);
|
|
21714
|
-
}
|
|
21715
|
-
}
|
|
21716
21922
|
// src/agent-core/env-loader.ts
|
|
21923
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
21717
21924
|
function parseEnvContent(content) {
|
|
21718
21925
|
const entries = [];
|
|
21719
21926
|
for (const line of content.split(`
|
|
@@ -21734,64 +21941,94 @@ function parseEnvContent(content) {
|
|
|
21734
21941
|
return entries;
|
|
21735
21942
|
}
|
|
21736
21943
|
async function loadEnvFile(filePath) {
|
|
21737
|
-
|
|
21738
|
-
|
|
21739
|
-
|
|
21740
|
-
|
|
21944
|
+
let content;
|
|
21945
|
+
try {
|
|
21946
|
+
content = await readFile3(filePath, "utf8");
|
|
21947
|
+
} catch (cause) {
|
|
21948
|
+
const code = cause.code;
|
|
21949
|
+
if (code === "ENOENT" || code === "EISDIR") {
|
|
21950
|
+
throw new ConfigError(`\u627E\u4E0D\u5230 env \u6A94\u6848\uFF1A${filePath}`);
|
|
21951
|
+
}
|
|
21952
|
+
throw cause;
|
|
21953
|
+
}
|
|
21954
|
+
for (const [key, value] of parseEnvContent(content)) {
|
|
21741
21955
|
if (process.env[key] === undefined)
|
|
21742
21956
|
process.env[key] = value;
|
|
21743
21957
|
}
|
|
21744
21958
|
}
|
|
21745
21959
|
// src/core/config-v2.ts
|
|
21746
|
-
import { join as
|
|
21747
|
-
import { mkdir as mkdir5, rename as rename3 } from "fs/promises";
|
|
21960
|
+
import { join as join5 } from "path";
|
|
21748
21961
|
function detectConfigVersion(raw) {
|
|
21749
21962
|
if (typeof raw === "object" && raw !== null && "version" in raw && raw.version === 2 && "connections" in raw) {
|
|
21750
21963
|
return 2;
|
|
21751
21964
|
}
|
|
21752
21965
|
return 1;
|
|
21753
21966
|
}
|
|
21967
|
+
function assertExplicitProductionSelection(resolved, requestedName) {
|
|
21968
|
+
if (resolved.environment !== "production")
|
|
21969
|
+
return;
|
|
21970
|
+
if (requestedName?.trim() === resolved.name)
|
|
21971
|
+
return;
|
|
21972
|
+
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`);
|
|
21973
|
+
}
|
|
21974
|
+
function findSimilarConnectionNames(requestedName, availableNames) {
|
|
21975
|
+
const requested = requestedName.toLowerCase();
|
|
21976
|
+
const distanceLimit = Math.max(2, Math.floor(requested.length / 3));
|
|
21977
|
+
return availableNames.map((name) => {
|
|
21978
|
+
const candidate = name.toLowerCase();
|
|
21979
|
+
const distance = levenshteinDistance(requested, candidate);
|
|
21980
|
+
return {
|
|
21981
|
+
name,
|
|
21982
|
+
distance,
|
|
21983
|
+
isSimilar: candidate.includes(requested) || requested.includes(candidate) || distance <= distanceLimit
|
|
21984
|
+
};
|
|
21985
|
+
}).filter(({ isSimilar }) => isSimilar).sort((a, b) => a.distance - b.distance || a.name.localeCompare(b.name)).slice(0, 3).map(({ name }) => name);
|
|
21986
|
+
}
|
|
21987
|
+
function connectionNotFoundMessage(requestedName, availableNames) {
|
|
21988
|
+
const suggestions = findSimilarConnectionNames(requestedName, availableNames);
|
|
21989
|
+
const suggestion = suggestions.length > 0 ? ` \u4F60\u662F\u5426\u8981\u4F7F\u7528\uFF1A${suggestions.join("\u3001")}\uFF1F` : "";
|
|
21990
|
+
return `\u9023\u7DDA '${requestedName}' \u4E0D\u5B58\u5728\u3002${suggestion} \u53EF\u7528\u9023\u7DDA\uFF1A${availableNames.join(", ")}`;
|
|
21991
|
+
}
|
|
21754
21992
|
function resolveConnection(config, name) {
|
|
21755
21993
|
const connectionName = name ?? config.default;
|
|
21756
21994
|
const conn = config.connections[connectionName];
|
|
21757
21995
|
if (!conn) {
|
|
21758
|
-
|
|
21759
|
-
throw new ConfigError(`\u9023\u7DDA '${connectionName}' \u4E0D\u5B58\u5728\u3002\u53EF\u7528\u9023\u7DDA\uFF1A${available}`);
|
|
21996
|
+
throw new ConfigError(connectionNotFoundMessage(connectionName, Object.keys(config.connections)));
|
|
21760
21997
|
}
|
|
21761
|
-
const { permission, envFile, ...connectionFields } = conn;
|
|
21998
|
+
const { permission, envFile, environment, ...connectionFields } = conn;
|
|
21762
21999
|
return {
|
|
21763
22000
|
name: connectionName,
|
|
21764
22001
|
connection: connectionFields,
|
|
21765
22002
|
permission,
|
|
21766
|
-
envFile
|
|
22003
|
+
envFile,
|
|
22004
|
+
environment
|
|
21767
22005
|
};
|
|
21768
22006
|
}
|
|
21769
22007
|
async function loadConnectionEnv(resolved, basePath) {
|
|
21770
22008
|
if (resolved.envFile) {
|
|
21771
|
-
const envPath =
|
|
22009
|
+
const envPath = join5(basePath, resolved.envFile);
|
|
21772
22010
|
await loadEnvFile(envPath);
|
|
21773
22011
|
}
|
|
21774
22012
|
}
|
|
21775
22013
|
async function readV2Config(path) {
|
|
21776
22014
|
const storagePath = await resolveConfigStoragePath(path);
|
|
21777
|
-
const configPath =
|
|
22015
|
+
const configPath = join5(storagePath, "config.json");
|
|
21778
22016
|
const file = Bun.file(configPath);
|
|
21779
22017
|
if (!await file.exists()) {
|
|
21780
22018
|
throw new ConfigError(`\u627E\u4E0D\u5230 V2 \u8A2D\u5B9A\u6A94\uFF1A${configPath}`);
|
|
21781
22019
|
}
|
|
22020
|
+
await assertAgentReadableFile(configPath);
|
|
21782
22021
|
const content = await file.text();
|
|
22022
|
+
await assertConfigIntegrity(storagePath, content, { requireRecord: true });
|
|
21783
22023
|
const raw = JSON.parse(content);
|
|
21784
22024
|
return DbcliConfigV2Schema.parse(raw);
|
|
21785
22025
|
}
|
|
21786
22026
|
async function writeV2Config(path, config) {
|
|
22027
|
+
assertConfigMutationApproved();
|
|
21787
22028
|
DbcliConfigV2Schema.parse(config);
|
|
21788
22029
|
const storagePath = await resolveConfigStoragePath(path);
|
|
21789
|
-
const configPath = join4(storagePath, "config.json");
|
|
21790
|
-
const tmpPath = `${configPath}.tmp`;
|
|
21791
|
-
await mkdir5(storagePath, { recursive: true });
|
|
21792
22030
|
const json = JSON.stringify(config, null, 2);
|
|
21793
|
-
await
|
|
21794
|
-
await rename3(tmpPath, configPath);
|
|
22031
|
+
await writeConfigWithIntegrity(storagePath, json);
|
|
21795
22032
|
}
|
|
21796
22033
|
function listConnections(config) {
|
|
21797
22034
|
return Object.entries(config.connections).map(([name, conn]) => {
|
|
@@ -21809,7 +22046,7 @@ function listConnections(config) {
|
|
|
21809
22046
|
}
|
|
21810
22047
|
|
|
21811
22048
|
// src/core/config.ts
|
|
21812
|
-
import { join as
|
|
22049
|
+
import { join as join10 } from "path";
|
|
21813
22050
|
import { mkdir as mkdir6 } from "fs/promises";
|
|
21814
22051
|
// src/agent-core/env-ref.ts
|
|
21815
22052
|
function resolveEnvRef(value, fieldName, env = process.env) {
|
|
@@ -21892,9 +22129,9 @@ var configModule = {
|
|
|
21892
22129
|
const binding = await readProjectBinding(path);
|
|
21893
22130
|
const storagePath = await resolveConfigStoragePath(path);
|
|
21894
22131
|
if (binding) {
|
|
21895
|
-
const storageConfigExists = await Bun.file(
|
|
22132
|
+
const storageConfigExists = await Bun.file(join10(storagePath, "config.json")).exists();
|
|
21896
22133
|
if (!storageConfigExists) {
|
|
21897
|
-
throw new ConfigError(`Bound dbcli config not found: ${
|
|
22134
|
+
throw new ConfigError(`Bound dbcli config not found: ${join10(storagePath, "config.json")}`);
|
|
21898
22135
|
}
|
|
21899
22136
|
}
|
|
21900
22137
|
let isDirectory = false;
|
|
@@ -21905,17 +22142,20 @@ var configModule = {
|
|
|
21905
22142
|
isDirectory = false;
|
|
21906
22143
|
}
|
|
21907
22144
|
if (isDirectory) {
|
|
21908
|
-
const configPath =
|
|
22145
|
+
const configPath = join10(storagePath, "config.json");
|
|
21909
22146
|
const configFile = Bun.file(configPath);
|
|
21910
22147
|
const configExists = await configFile.exists();
|
|
21911
22148
|
if (configExists) {
|
|
22149
|
+
await assertAgentReadableFile(configPath);
|
|
21912
22150
|
const content = await configFile.text();
|
|
22151
|
+
await assertConfigIntegrity(storagePath, content, { requireRecord: true });
|
|
21913
22152
|
const config = JSON.parse(content);
|
|
21914
22153
|
if (detectConfigVersion(config) === 2) {
|
|
21915
22154
|
const v2Config = DbcliConfigV2Schema.parse(config);
|
|
21916
22155
|
const resolved = resolveConnection(v2Config, effectiveConnectionName);
|
|
22156
|
+
assertExplicitProductionSelection(resolved, effectiveConnectionName);
|
|
21917
22157
|
await loadConnectionEnv(resolved, storagePath);
|
|
21918
|
-
const envLocalPath =
|
|
22158
|
+
const envLocalPath = join10(storagePath, ".env.local");
|
|
21919
22159
|
const envLocalFile = Bun.file(envLocalPath);
|
|
21920
22160
|
let legacyPassword = null;
|
|
21921
22161
|
if (await envLocalFile.exists()) {
|
|
@@ -21952,7 +22192,7 @@ var configModule = {
|
|
|
21952
22192
|
console.warn("Warning: Failed to load layered schema cache, falling back to config.json");
|
|
21953
22193
|
}
|
|
21954
22194
|
}
|
|
21955
|
-
|
|
22195
|
+
const parsedConfig = DbcliConfigSchema.parse({
|
|
21956
22196
|
connection: resolvedConnection,
|
|
21957
22197
|
permission: resolved.permission,
|
|
21958
22198
|
schema,
|
|
@@ -21961,10 +22201,15 @@ var configModule = {
|
|
|
21961
22201
|
audit: v2Config.audit,
|
|
21962
22202
|
redis: v2Config.redis
|
|
21963
22203
|
});
|
|
22204
|
+
return {
|
|
22205
|
+
...parsedConfig,
|
|
22206
|
+
effectiveConnectionName: resolved.name,
|
|
22207
|
+
...resolved.environment && { effectiveEnvironment: resolved.environment }
|
|
22208
|
+
};
|
|
21964
22209
|
}
|
|
21965
22210
|
assertNoConnectionSelectorOnV1(effectiveConnectionName);
|
|
21966
22211
|
const resolvedConfig = resolveEnvReferences(config, process.env);
|
|
21967
|
-
const envPath =
|
|
22212
|
+
const envPath = join10(storagePath, ".env.local");
|
|
21968
22213
|
const envFile = Bun.file(envPath);
|
|
21969
22214
|
if (await envFile.exists()) {
|
|
21970
22215
|
const envContent = await envFile.text();
|
|
@@ -21979,12 +22224,18 @@ var configModule = {
|
|
|
21979
22224
|
const file = Bun.file(path);
|
|
21980
22225
|
const exists = await file.exists();
|
|
21981
22226
|
if (exists) {
|
|
22227
|
+
if (process.env.DBCLI_AGENT_MODE === "1") {
|
|
22228
|
+
throw new ConfigError(`Agent mode refuses legacy single-file config: ${path}. Run the human/admin migration workflow to move it to V2 home storage first.`);
|
|
22229
|
+
}
|
|
21982
22230
|
assertNoConnectionSelectorOnV1(effectiveConnectionName);
|
|
21983
22231
|
const content = await file.text();
|
|
21984
22232
|
const raw = JSON.parse(content);
|
|
21985
22233
|
const resolved = resolveEnvReferences(raw, process.env);
|
|
21986
22234
|
return DbcliConfigSchema.parse(resolved);
|
|
21987
22235
|
}
|
|
22236
|
+
if (process.env.DBCLI_AGENT_MODE === "1") {
|
|
22237
|
+
throw new ConfigError(`Agent mode refuses a missing config: ${path}. Run the human/admin setup workflow to provision it.`);
|
|
22238
|
+
}
|
|
21988
22239
|
return { ...DEFAULT_CONFIG };
|
|
21989
22240
|
} catch (error) {
|
|
21990
22241
|
if (error instanceof ConfigError)
|
|
@@ -22026,6 +22277,7 @@ var configModule = {
|
|
|
22026
22277
|
},
|
|
22027
22278
|
async write(path, config) {
|
|
22028
22279
|
try {
|
|
22280
|
+
assertConfigMutationApproved();
|
|
22029
22281
|
this.validate(config);
|
|
22030
22282
|
const storagePath = await resolveConfigStoragePath(path);
|
|
22031
22283
|
let isDirectory = false;
|
|
@@ -22039,9 +22291,8 @@ var configModule = {
|
|
|
22039
22291
|
await mkdir6(storagePath, { recursive: true });
|
|
22040
22292
|
const hasEnvReferences = isEnvReference(config.connection.password);
|
|
22041
22293
|
if (hasEnvReferences) {
|
|
22042
|
-
const configPath = join9(storagePath, "config.json");
|
|
22043
22294
|
const configJson = JSON.stringify(config, null, 2);
|
|
22044
|
-
await
|
|
22295
|
+
await writeConfigWithIntegrity(storagePath, configJson);
|
|
22045
22296
|
} else {
|
|
22046
22297
|
const password = config.connection.password;
|
|
22047
22298
|
const configWithoutPassword = {
|
|
@@ -22052,11 +22303,10 @@ var configModule = {
|
|
|
22052
22303
|
}
|
|
22053
22304
|
};
|
|
22054
22305
|
delete configWithoutPassword.connection.password;
|
|
22055
|
-
const configPath = join9(storagePath, "config.json");
|
|
22056
22306
|
const configJson = JSON.stringify(configWithoutPassword, null, 2);
|
|
22057
|
-
await
|
|
22307
|
+
await writeConfigWithIntegrity(storagePath, configJson);
|
|
22058
22308
|
if (password) {
|
|
22059
|
-
const envPath =
|
|
22309
|
+
const envPath = join10(storagePath, ".env.local");
|
|
22060
22310
|
const envContent = `# Database Credentials - DO NOT commit to git
|
|
22061
22311
|
|
|
22062
22312
|
DBCLI_PASSWORD=${password}
|
|
@@ -22278,7 +22528,8 @@ async function getAuditLogger(config, configPath, connectionName) {
|
|
|
22278
22528
|
}
|
|
22279
22529
|
async function writeAuditEntry(config, commandName, options, outcome) {
|
|
22280
22530
|
try {
|
|
22281
|
-
const
|
|
22531
|
+
const connectionName = typeof options.connectionName === "string" && options.connectionName || config.effectiveConnectionName || getGlobalConnectionName() || "default";
|
|
22532
|
+
const logger = await getAuditLogger(config, options.config || ".dbcli", connectionName);
|
|
22282
22533
|
const engine = config.connection?.system || "postgresql";
|
|
22283
22534
|
const target = outcome.target || getOperationTarget(engine, commandName, options, outcome.sql);
|
|
22284
22535
|
let tier = getEngineCapability(engine, commandName).tier;
|
|
@@ -22301,7 +22552,11 @@ async function writeAuditEntry(config, commandName, options, outcome) {
|
|
|
22301
22552
|
...outcome.sql && { redacted_sql: redactSql(outcome.sql) },
|
|
22302
22553
|
...errorMessage && { error: errorMessage },
|
|
22303
22554
|
...outcome.recovery_ref && { recovery_ref: outcome.recovery_ref },
|
|
22304
|
-
metadata:
|
|
22555
|
+
metadata: {
|
|
22556
|
+
...outcome.metadata ?? {},
|
|
22557
|
+
connection_name: connectionName,
|
|
22558
|
+
environment: config.effectiveEnvironment ?? null
|
|
22559
|
+
}
|
|
22305
22560
|
};
|
|
22306
22561
|
const result = await logger.write(entry);
|
|
22307
22562
|
return "success" in result ? result.id : null;
|
|
@@ -22604,7 +22859,7 @@ function inferColumnType(value) {
|
|
|
22604
22859
|
}
|
|
22605
22860
|
// src/utils/prompts.ts
|
|
22606
22861
|
async function readLineFromStdin(prompt = "") {
|
|
22607
|
-
return new Promise((
|
|
22862
|
+
return new Promise((resolve3) => {
|
|
22608
22863
|
if (prompt) {
|
|
22609
22864
|
process.stdout.write(prompt);
|
|
22610
22865
|
}
|
|
@@ -22619,12 +22874,12 @@ async function readLineFromStdin(prompt = "") {
|
|
|
22619
22874
|
process.stdin.pause();
|
|
22620
22875
|
process.stdin.removeListener("data", onData);
|
|
22621
22876
|
process.stdin.removeListener("end", onEnd);
|
|
22622
|
-
|
|
22877
|
+
resolve3((lines[0] ?? "").trim());
|
|
22623
22878
|
}
|
|
22624
22879
|
};
|
|
22625
22880
|
const onEnd = () => {
|
|
22626
22881
|
process.stdin.removeListener("data", onData);
|
|
22627
|
-
|
|
22882
|
+
resolve3(data.trim());
|
|
22628
22883
|
};
|
|
22629
22884
|
process.stdin.on("data", onData);
|
|
22630
22885
|
process.stdin.on("end", onEnd);
|
|
@@ -22991,13 +23246,14 @@ Parameters:`);
|
|
|
22991
23246
|
init_schema_loader();
|
|
22992
23247
|
|
|
22993
23248
|
// src/core/config-v2-mutations.ts
|
|
22994
|
-
import { join as
|
|
23249
|
+
import { join as join11 } from "path";
|
|
22995
23250
|
var SQL_SYSTEMS = ["postgresql", "mysql", "mariadb"];
|
|
22996
23251
|
function envVarNameFor(connName, field) {
|
|
22997
23252
|
const slug = connName.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
22998
23253
|
return `DBCLI_${slug}_${field.toUpperCase()}`;
|
|
22999
23254
|
}
|
|
23000
23255
|
async function writeConnectionSecret(projectPath, connName, field, value) {
|
|
23256
|
+
assertConfigMutationApproved();
|
|
23001
23257
|
if (value.includes(`
|
|
23002
23258
|
`) || value.includes("\r")) {
|
|
23003
23259
|
throw new Error("secret value \u4E0D\u53EF\u5305\u542B\u63DB\u884C\u5B57\u5143");
|
|
@@ -23008,7 +23264,7 @@ async function writeConnectionSecret(projectPath, connName, field, value) {
|
|
|
23008
23264
|
throw new Error(`\u9023\u7DDA '${connName}' \u4E0D\u5B58\u5728`);
|
|
23009
23265
|
const storagePath = await resolveConfigStoragePath(projectPath);
|
|
23010
23266
|
const envFile = conn.envFile ?? `.env.${connName}`;
|
|
23011
|
-
const envPath =
|
|
23267
|
+
const envPath = join11(storagePath, envFile);
|
|
23012
23268
|
const varName = envVarNameFor(connName, field);
|
|
23013
23269
|
let content = "";
|
|
23014
23270
|
const file = Bun.file(envPath);
|
|
@@ -23252,7 +23508,10 @@ export {
|
|
|
23252
23508
|
migrateV1ToV2,
|
|
23253
23509
|
loadConnectionEnv,
|
|
23254
23510
|
listConnections,
|
|
23511
|
+
isGlobalConfigPath,
|
|
23255
23512
|
getProjectStoragePath,
|
|
23513
|
+
getGlobalConfigPath,
|
|
23514
|
+
getDbcliConfigHome,
|
|
23256
23515
|
envVarNameFor,
|
|
23257
23516
|
detectConfigVersion,
|
|
23258
23517
|
SchemaLayeredLoader,
|