@carllee1983/dbcli 8.0.0 → 9.1.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/skills/dbcli/reference.md +2 -2
- package/.cursor-plugin/plugin.json +1 -1
- package/.github/skills/dbcli/reference.md +2 -2
- package/CHANGELOG.md +117 -14
- package/README.dev.md +1 -1
- package/README.md +77 -0
- package/README.zh-TW.md +1 -2
- package/assets/reference.md +2 -2
- package/dist/cli-runtime.mjs +811 -180
- package/dist/cli.mjs +3 -3
- package/dist/core.d.ts +452 -6
- package/dist/core.mjs +220 -19
- package/gemini-extension.json +1 -1
- package/package.json +3 -3
- package/plugins/dbcli-agent/.codex-plugin/plugin.json +1 -1
- package/plugins/dbcli-agent/skills/dbcli/reference.md +2 -2
- package/skills/dbcli/reference.md +2 -2
- package/assets/SKILL.zh-TW.md +0 -564
package/dist/cli-runtime.mjs
CHANGED
|
@@ -2087,7 +2087,7 @@ var package_default;
|
|
|
2087
2087
|
var init_package = __esm(() => {
|
|
2088
2088
|
package_default = {
|
|
2089
2089
|
name: "@carllee1983/dbcli",
|
|
2090
|
-
version: "
|
|
2090
|
+
version: "9.1.0",
|
|
2091
2091
|
description: "Database CLI for AI agents",
|
|
2092
2092
|
type: "module",
|
|
2093
2093
|
publishConfig: {
|
|
@@ -2168,15 +2168,15 @@ var init_package = __esm(() => {
|
|
|
2168
2168
|
"services:check": "bun run scripts/check-test-services.ts",
|
|
2169
2169
|
"docs:check": "bun run scripts/check-user-docs.ts",
|
|
2170
2170
|
"contract:check": "bun run scripts/check-cli-contract.ts",
|
|
2171
|
-
"skill:check": "bun run scripts/check-skill-parity.ts",
|
|
2172
2171
|
"platform:check": "bun run scripts/check-platform-parity.ts",
|
|
2173
2172
|
"agent-core:check": "bun run scripts/check-agent-core-purity.ts",
|
|
2174
2173
|
"core-stdout:check": "bun run scripts/check-core-no-stdout.ts",
|
|
2175
2174
|
"plan:check": "bun run scripts/check-plan-acceptance.ts",
|
|
2176
2175
|
"forgeflow:check": "bun run scripts/check-forgeflow-adoption.ts && bun run scripts/check-forgeflow-handoff.ts",
|
|
2176
|
+
"forgeflow:contract": "bun run scripts/check-forgeflow-contract.ts",
|
|
2177
2177
|
typecheck: "tsc --noEmit --pretty false",
|
|
2178
2178
|
"typecheck:tests": "tsc --noEmit --pretty false -p tsconfig.tests.json",
|
|
2179
|
-
"test:perf": "bun test ./tests/perf/*.bench.ts",
|
|
2179
|
+
"test:perf": "bun test --timeout 30000 ./tests/perf/*.bench.ts",
|
|
2180
2180
|
lint: "eslint src tests scripts --ext .ts --max-warnings=0",
|
|
2181
2181
|
"format:check": "prettier --check .",
|
|
2182
2182
|
format: "prettier --write .",
|
|
@@ -6407,13 +6407,27 @@ function parseStatementTimeoutOption(value) {
|
|
|
6407
6407
|
}
|
|
6408
6408
|
return parsed;
|
|
6409
6409
|
}
|
|
6410
|
+
function isSqliteMemoryTarget(value) {
|
|
6411
|
+
const trimmed = value.trim();
|
|
6412
|
+
if (trimmed.toLowerCase() === ":memory:")
|
|
6413
|
+
return true;
|
|
6414
|
+
if (!trimmed.toLowerCase().startsWith("file:"))
|
|
6415
|
+
return false;
|
|
6416
|
+
const withoutScheme = trimmed.slice("file:".length);
|
|
6417
|
+
const queryStart = withoutScheme.indexOf("?");
|
|
6418
|
+
const path = queryStart === -1 ? withoutScheme : withoutScheme.slice(0, queryStart);
|
|
6419
|
+
if (path.toLowerCase() === ":memory:")
|
|
6420
|
+
return true;
|
|
6421
|
+
const query = queryStart === -1 ? "" : withoutScheme.slice(queryStart + 1);
|
|
6422
|
+
return new URLSearchParams(query).get("mode")?.toLowerCase() === "memory";
|
|
6423
|
+
}
|
|
6410
6424
|
function validateFormat(value, allowedFormats, commandName) {
|
|
6411
6425
|
if (!allowedFormats.includes(value)) {
|
|
6412
6426
|
const allowed = allowedFormats.join(", ");
|
|
6413
6427
|
throw new Error(`Invalid format "${value}" for ${commandName}. Allowed: ${allowed}`);
|
|
6414
6428
|
}
|
|
6415
6429
|
}
|
|
6416
|
-
var EnvRefSchema, StringOrEnvRef, NumberOrEnvRef, OptStringOrEnvRef, OptNumberOrEnvRef, MIN_CONNECTION_TIMEOUT_MS = 100, MAX_CONNECTION_TIMEOUT_MS = 600000, MIN_STATEMENT_TIMEOUT_MS = 0, MAX_STATEMENT_TIMEOUT_MS = 3600000, TimeoutField, MongoDBConnectionConfigSchema, SqlConnectionConfigSchema, RedisConnectionConfigSchema, ElasticsearchConnectionConfigSchema, ConnectionConfigSchema, PermissionSchema, EnvironmentLabelSchema, MetadataSchema, BLACKLIST_KEY_ALIASES, BlacklistConfigSchema, RedisMaskRuleSchema, RedisConfigSchema, DEFAULT_AUDIT_ROTATION, AuditRotationConfigSchema, AuditConfigSchema, DbcliConfigSchema, SqlNamedConnectionSchema, MongoDBNamedConnectionSchema, RedisNamedConnectionSchema, ElasticsearchNamedConnectionSchema, NamedConnectionUnion, NamedConnectionSchema, DbcliConfigV2Schema;
|
|
6430
|
+
var EnvRefSchema, StringOrEnvRef, NumberOrEnvRef, OptStringOrEnvRef, OptNumberOrEnvRef, MIN_CONNECTION_TIMEOUT_MS = 100, MAX_CONNECTION_TIMEOUT_MS = 600000, MIN_STATEMENT_TIMEOUT_MS = 0, MAX_STATEMENT_TIMEOUT_MS = 3600000, TimeoutField, MongoDBConnectionConfigSchema, SqlConnectionConfigSchema, SQLITE_MEMORY_TARGET_MESSAGE, SqliteConnectionConfigSchema, RedisConnectionConfigSchema, ElasticsearchConnectionConfigSchema, ConnectionConfigSchema, PermissionSchema, EnvironmentLabelSchema, MetadataSchema, BLACKLIST_KEY_ALIASES, BlacklistConfigSchema, RedisMaskRuleSchema, RedisConfigSchema, DEFAULT_AUDIT_ROTATION, AuditRotationConfigSchema, AuditConfigSchema, DbcliConfigSchema, SqlNamedConnectionSchema, SqliteNamedConnectionSchema, MongoDBNamedConnectionSchema, RedisNamedConnectionSchema, ElasticsearchNamedConnectionSchema, NamedConnectionUnion, NamedConnectionSchema, DbcliConfigV2Schema;
|
|
6417
6431
|
var init_validation = __esm(() => {
|
|
6418
6432
|
init_zod();
|
|
6419
6433
|
EnvRefSchema = exports_external.object({
|
|
@@ -6450,6 +6464,21 @@ var init_validation = __esm(() => {
|
|
|
6450
6464
|
database: StringOrEnvRef,
|
|
6451
6465
|
...TimeoutField
|
|
6452
6466
|
});
|
|
6467
|
+
SQLITE_MEMORY_TARGET_MESSAGE = "SQLite in-memory databases are refused: a memory target is a different database on every " + "connection, so it cannot be the connection identity the blacklist, audit log and schema " + "cache are scoped to. Point `file` at a database file on disk. See ADR-0038.";
|
|
6468
|
+
SqliteConnectionConfigSchema = exports_external.object({
|
|
6469
|
+
system: exports_external.literal("sqlite"),
|
|
6470
|
+
file: exports_external.union([exports_external.string().min(1), EnvRefSchema]).superRefine((value, ctx) => {
|
|
6471
|
+
if (typeof value === "string" && isSqliteMemoryTarget(value)) {
|
|
6472
|
+
ctx.addIssue({ code: exports_external.ZodIssueCode.custom, message: SQLITE_MEMORY_TARGET_MESSAGE });
|
|
6473
|
+
}
|
|
6474
|
+
}),
|
|
6475
|
+
host: OptStringOrEnvRef,
|
|
6476
|
+
port: exports_external.union([exports_external.number().int(), EnvRefSchema]).optional().default(0),
|
|
6477
|
+
user: OptStringOrEnvRef,
|
|
6478
|
+
password: exports_external.union([exports_external.string(), EnvRefSchema]).optional().default(""),
|
|
6479
|
+
database: OptStringOrEnvRef,
|
|
6480
|
+
...TimeoutField
|
|
6481
|
+
});
|
|
6453
6482
|
RedisConnectionConfigSchema = exports_external.object({
|
|
6454
6483
|
system: exports_external.literal("redis"),
|
|
6455
6484
|
host: StringOrEnvRef,
|
|
@@ -6476,6 +6505,7 @@ var init_validation = __esm(() => {
|
|
|
6476
6505
|
});
|
|
6477
6506
|
ConnectionConfigSchema = exports_external.union([
|
|
6478
6507
|
SqlConnectionConfigSchema,
|
|
6508
|
+
SqliteConnectionConfigSchema,
|
|
6479
6509
|
MongoDBConnectionConfigSchema,
|
|
6480
6510
|
RedisConnectionConfigSchema,
|
|
6481
6511
|
ElasticsearchConnectionConfigSchema
|
|
@@ -6546,6 +6576,11 @@ var init_validation = __esm(() => {
|
|
|
6546
6576
|
envFile: exports_external.string().optional(),
|
|
6547
6577
|
environment: EnvironmentLabelSchema
|
|
6548
6578
|
});
|
|
6579
|
+
SqliteNamedConnectionSchema = SqliteConnectionConfigSchema.extend({
|
|
6580
|
+
permission: PermissionSchema,
|
|
6581
|
+
envFile: exports_external.string().optional(),
|
|
6582
|
+
environment: EnvironmentLabelSchema
|
|
6583
|
+
});
|
|
6549
6584
|
MongoDBNamedConnectionSchema = MongoDBConnectionConfigSchema.extend({
|
|
6550
6585
|
permission: PermissionSchema,
|
|
6551
6586
|
envFile: exports_external.string().optional(),
|
|
@@ -6563,6 +6598,7 @@ var init_validation = __esm(() => {
|
|
|
6563
6598
|
});
|
|
6564
6599
|
NamedConnectionUnion = exports_external.union([
|
|
6565
6600
|
SqlNamedConnectionSchema,
|
|
6601
|
+
SqliteNamedConnectionSchema,
|
|
6566
6602
|
MongoDBNamedConnectionSchema,
|
|
6567
6603
|
RedisNamedConnectionSchema,
|
|
6568
6604
|
ElasticsearchNamedConnectionSchema
|
|
@@ -8816,7 +8852,8 @@ var init_messages = __esm(() => {
|
|
|
8816
8852
|
connection_not_found: "Connection '{name}' does not exist",
|
|
8817
8853
|
cannot_remove_last: "Cannot remove the last connection",
|
|
8818
8854
|
connection_already_exists: "Connection '{name}' already exists",
|
|
8819
|
-
rename_invalid_format: "Usage: --rename <old-name>:<new-name>"
|
|
8855
|
+
rename_invalid_format: "Usage: --rename <old-name>:<new-name>",
|
|
8856
|
+
prompt_sqlite_file: "SQLite database file path: "
|
|
8820
8857
|
},
|
|
8821
8858
|
inspect: {
|
|
8822
8859
|
description: "Read-only snapshot of connection, permission, blacklist, objects, snippets, and suggested next commands.",
|
|
@@ -8985,7 +9022,8 @@ Or use interactive mode: run "dbcli init --use-env-refs" without --no-interactiv
|
|
|
8985
9022
|
env_var_not_defined: `Cannot test connection: environment variable {envKey} is not defined.
|
|
8986
9023
|
Set {envKey} in .env or environment variables.
|
|
8987
9024
|
Hint: run 'export {envKey}=<value>' and retry`,
|
|
8988
|
-
invalid_output_format: "--format {format} is not supported here \u2014 use text or json"
|
|
9025
|
+
invalid_output_format: "--format {format} is not supported here \u2014 use text or json",
|
|
9026
|
+
sqlite_file_required: "A SQLite connection needs a file path. Pass --file <path> or run init interactively."
|
|
8989
9027
|
},
|
|
8990
9028
|
success: {
|
|
8991
9029
|
inserted: "Successfully inserted {count} row(s)",
|
|
@@ -9150,7 +9188,8 @@ var init_messages2 = __esm(() => {
|
|
|
9150
9188
|
connection_not_found: "\u9023\u7DDA '{name}' \u4E0D\u5B58\u5728",
|
|
9151
9189
|
cannot_remove_last: "\u7121\u6CD5\u79FB\u9664\u6700\u5F8C\u4E00\u500B\u9023\u7DDA",
|
|
9152
9190
|
connection_already_exists: "\u9023\u7DDA '{name}' \u5DF2\u5B58\u5728",
|
|
9153
|
-
rename_invalid_format: "\u7528\u6CD5\uFF1A--rename <\u820A\u540D\u7A31>:<\u65B0\u540D\u7A31>"
|
|
9191
|
+
rename_invalid_format: "\u7528\u6CD5\uFF1A--rename <\u820A\u540D\u7A31>:<\u65B0\u540D\u7A31>",
|
|
9192
|
+
prompt_sqlite_file: "SQLite \u8CC7\u6599\u5EAB\u6A94\u6848\u8DEF\u5F91\uFF1A"
|
|
9154
9193
|
},
|
|
9155
9194
|
inspect: {
|
|
9156
9195
|
description: "\u552F\u8B80\u6AA2\u8996\uFF1A\u9023\u7DDA\u3001\u6B0A\u9650\u3001\u9ED1\u540D\u55AE\u3001\u7269\u4EF6\u3001\u7247\u6BB5\u8207\u5EFA\u8B70\u4E0B\u4E00\u6B65\u6307\u4EE4\u3002",
|
|
@@ -9319,7 +9358,8 @@ var init_messages2 = __esm(() => {
|
|
|
9319
9358
|
env_var_not_defined: `\u7121\u6CD5\u6E2C\u8A66\u9023\u7DDA\uFF1A\u74B0\u5883\u8B8A\u6578 {envKey} \u672A\u5B9A\u7FA9\u3002
|
|
9320
9359
|
\u8ACB\u5728 .env \u6216\u74B0\u5883\u8B8A\u6578\u4E2D\u8A2D\u5B9A {envKey}\u3002
|
|
9321
9360
|
\u63D0\u793A\uFF1A\u57F7\u884C 'export {envKey}=<value>' \u5F8C\u91CD\u8A66`,
|
|
9322
|
-
invalid_output_format: "\u7121\u6CD5\u4F7F\u7528 --format {format}\uFF0C\u8ACB\u4F7F\u7528 text \u6216 json"
|
|
9361
|
+
invalid_output_format: "\u7121\u6CD5\u4F7F\u7528 --format {format}\uFF0C\u8ACB\u4F7F\u7528 text \u6216 json",
|
|
9362
|
+
sqlite_file_required: "SQLite \u9023\u7DDA\u9700\u8981\u4E00\u500B\u6A94\u6848\u8DEF\u5F91\u3002\u8ACB\u7528 --file <path>\uFF0C\u6216\u4EE5\u4E92\u52D5\u6A21\u5F0F\u57F7\u884C init\u3002"
|
|
9323
9363
|
},
|
|
9324
9364
|
success: {
|
|
9325
9365
|
inserted: "\u6210\u529F\u63D2\u5165 {count} \u5217",
|
|
@@ -10247,16 +10287,23 @@ var init_redaction = __esm(() => {
|
|
|
10247
10287
|
});
|
|
10248
10288
|
|
|
10249
10289
|
// src/adapters/types.ts
|
|
10250
|
-
var DATABASE_SYSTEMS, ConnectionError;
|
|
10290
|
+
var DATABASE_SYSTEMS, SQL_DATABASE_SYSTEMS, ConnectionError;
|
|
10251
10291
|
var init_types2 = __esm(() => {
|
|
10252
10292
|
DATABASE_SYSTEMS = Object.freeze([
|
|
10253
10293
|
"postgresql",
|
|
10254
10294
|
"mysql",
|
|
10255
10295
|
"mariadb",
|
|
10296
|
+
"sqlite",
|
|
10256
10297
|
"mongodb",
|
|
10257
10298
|
"redis",
|
|
10258
10299
|
"elasticsearch"
|
|
10259
10300
|
]);
|
|
10301
|
+
SQL_DATABASE_SYSTEMS = Object.freeze([
|
|
10302
|
+
"postgresql",
|
|
10303
|
+
"mysql",
|
|
10304
|
+
"mariadb",
|
|
10305
|
+
"sqlite"
|
|
10306
|
+
]);
|
|
10260
10307
|
ConnectionError = class ConnectionError extends Error {
|
|
10261
10308
|
code;
|
|
10262
10309
|
hints;
|
|
@@ -10417,6 +10464,38 @@ var init_capabilities = __esm(() => {
|
|
|
10417
10464
|
...SQL_BASE,
|
|
10418
10465
|
check: cap("limited", "readonly", "SQL-only and strongest on MySQL/MariaDB.")
|
|
10419
10466
|
}),
|
|
10467
|
+
sqlite: Object.freeze({
|
|
10468
|
+
...SQL_BASE,
|
|
10469
|
+
init: cap("supported", "local-write", "Asks for a file path and a permission; the file must already exist."),
|
|
10470
|
+
use: cap("supported", "local-write", "Switches connections and displays the file path."),
|
|
10471
|
+
schemaFullScan: cap("unsupported", "none", "Full schema cache scan is not implemented for SQLite."),
|
|
10472
|
+
lint: cap("unsupported", "none", "Static lint is not enabled for the SQLite dialect yet."),
|
|
10473
|
+
queries: cap("supported", "local-write", "Snippets may declare `engine: sqlite` and bind with `?` placeholders."),
|
|
10474
|
+
insert: cap("supported", "db-write", "Row insert through the shared data-executor path."),
|
|
10475
|
+
update: cap("supported", "db-write", "Row update with a mandatory --where clause."),
|
|
10476
|
+
delete: cap("supported", "db-write", "Row delete with a mandatory --where clause."),
|
|
10477
|
+
export: cap("supported", "local-write", "Exports SELECT results to json, csv or html through the shared query path."),
|
|
10478
|
+
check: cap("unsupported", "none", "Data health check is not enabled for SQLite."),
|
|
10479
|
+
diff: cap("unsupported", "none", "Schema snapshots are not enabled for SQLite."),
|
|
10480
|
+
migrate: cap("unsupported", "none", "SQLite ALTER TABLE covers few operations and a column change is a table rebuild; no DDL generator exists."),
|
|
10481
|
+
shell: cap("unsupported", "none", "The REPL is not enabled for SQLite."),
|
|
10482
|
+
doctor: cap("supported", "readonly", "Checks the file exists, is readable, and is writable when the permission allows writes."),
|
|
10483
|
+
inspect: cap("unsupported", "none", "Inspection collectors are not enabled for SQLite."),
|
|
10484
|
+
report: cap("unsupported", "none", "Report diagnostics query engine-specific statistics views."),
|
|
10485
|
+
guide: cap("unsupported", "none", "Guides depend on engine-specific EXPLAIN output."),
|
|
10486
|
+
explain: cap("unsupported", "none", "EXPLAIN handling is not enabled for SQLite."),
|
|
10487
|
+
plan: cap("unsupported", "none", "Plan capture is not enabled for SQLite."),
|
|
10488
|
+
impactAssess: cap("unsupported", "none", "Impact assessment is not enabled for SQLite."),
|
|
10489
|
+
assert: cap("unsupported", "none", "Assertions are not enabled for SQLite."),
|
|
10490
|
+
snapshot: cap("unsupported", "none", "Result snapshots are not enabled for SQLite."),
|
|
10491
|
+
verify: cap("unsupported", "none", "Write verification is not enabled for SQLite."),
|
|
10492
|
+
semantic: cap("unsupported", "none", "Semantic context work is not enabled for SQLite."),
|
|
10493
|
+
design: cap("unsupported", "none", "Schema design work is not enabled for SQLite."),
|
|
10494
|
+
proxy: cap("not-applicable", "none", "SQLite has no network protocol to proxy."),
|
|
10495
|
+
proxyAnalyze: cap("not-applicable", "none", "There are no SQLite proxy events to analyse."),
|
|
10496
|
+
password: cap("not-applicable", "none", "A SQLite connection has no credential to store or rotate."),
|
|
10497
|
+
queryLimitGuard: cap("limited", "readonly", "Applies the same LIMIT rewrite as the other SQL engines; under query-only the handle is opened read-only, which a database with an unreplayed WAL cannot satisfy.")
|
|
10498
|
+
}),
|
|
10420
10499
|
mysql: Object.freeze(SQL_BASE),
|
|
10421
10500
|
mariadb: Object.freeze(SQL_BASE),
|
|
10422
10501
|
mongodb: Object.freeze({
|
|
@@ -10916,7 +10995,7 @@ var init_encoding = __esm(() => {
|
|
|
10916
10995
|
|
|
10917
10996
|
// src/adapters/identifier-quote.ts
|
|
10918
10997
|
function quoteCharFor(dialect) {
|
|
10919
|
-
return dialect === "postgresql" ? '"' : "`";
|
|
10998
|
+
return dialect === "postgresql" || dialect === "sqlite" ? '"' : "`";
|
|
10920
10999
|
}
|
|
10921
11000
|
function assertQuotable(name, what) {
|
|
10922
11001
|
if (name.length === 0) {
|
|
@@ -11625,6 +11704,208 @@ var init_mysql_adapter = __esm(() => {
|
|
|
11625
11704
|
init_connection_error_message();
|
|
11626
11705
|
});
|
|
11627
11706
|
|
|
11707
|
+
// src/adapters/sqlite-adapter.ts
|
|
11708
|
+
import { Database } from "bun:sqlite";
|
|
11709
|
+
function mapSqliteError(error, file, readonly) {
|
|
11710
|
+
if (error instanceof ConnectionError)
|
|
11711
|
+
return error;
|
|
11712
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11713
|
+
const code = String(error?.code ?? "");
|
|
11714
|
+
if (readonly && (code.startsWith("SQLITE_CANTOPEN") || /unable to open database/i.test(message))) {
|
|
11715
|
+
return new ConnectionError("CONNECTION_REJECTED", `Cannot open '${file}' read-only: the database has an unreplayed write-ahead log, ` + `and replaying it is a write. A query-only connection cannot recover it.`, [
|
|
11716
|
+
"Let the owning application close the database cleanly, which checkpoints the WAL.",
|
|
11717
|
+
"Or connect with a permission above query-only, which opens the file read-write.",
|
|
11718
|
+
`Original driver message: ${message}`
|
|
11719
|
+
]);
|
|
11720
|
+
}
|
|
11721
|
+
if (/readonly database|attempt to write a readonly/i.test(message)) {
|
|
11722
|
+
return new ConnectionError("CONNECTION_REJECTED", `The SQLite connection is open read-only: ${message}`, [
|
|
11723
|
+
"query-only opens the database file read-only, so no write can reach it.",
|
|
11724
|
+
"Raise the connection permission if the write is intended."
|
|
11725
|
+
]);
|
|
11726
|
+
}
|
|
11727
|
+
if (/no such table/i.test(message)) {
|
|
11728
|
+
return new ConnectionError("TABLE_NOT_FOUND", message, [
|
|
11729
|
+
"Run `dbcli list` to see the tables this database holds."
|
|
11730
|
+
]);
|
|
11731
|
+
}
|
|
11732
|
+
if (/no such column/i.test(message)) {
|
|
11733
|
+
return new ConnectionError("COLUMN_NOT_FOUND", message, [
|
|
11734
|
+
"Run `dbcli schema <table>` to see the columns this table holds."
|
|
11735
|
+
]);
|
|
11736
|
+
}
|
|
11737
|
+
if (/syntax error/i.test(message)) {
|
|
11738
|
+
return new ConnectionError("SQL_SYNTAX_ERROR", message, [
|
|
11739
|
+
"Check the statement against SQLite syntax; it differs from PostgreSQL and MySQL."
|
|
11740
|
+
]);
|
|
11741
|
+
}
|
|
11742
|
+
return new ConnectionError("UNKNOWN", message, [`SQLite database: ${file}`]);
|
|
11743
|
+
}
|
|
11744
|
+
|
|
11745
|
+
class SQLiteAdapter {
|
|
11746
|
+
db = null;
|
|
11747
|
+
openedReadonly = true;
|
|
11748
|
+
connected = false;
|
|
11749
|
+
file;
|
|
11750
|
+
constructor(options) {
|
|
11751
|
+
if (!options.file) {
|
|
11752
|
+
throw new ConnectionError("UNKNOWN", "SQLite connection is missing its `file` path", [
|
|
11753
|
+
"Set `file` in the connection configuration to the database file path."
|
|
11754
|
+
]);
|
|
11755
|
+
}
|
|
11756
|
+
this.file = options.file;
|
|
11757
|
+
}
|
|
11758
|
+
async connect() {
|
|
11759
|
+
const target = Bun.file(this.file);
|
|
11760
|
+
if (!await target.exists()) {
|
|
11761
|
+
throw new ConnectionError("ENOTFOUND", `SQLite database file not found: ${this.file}`, [
|
|
11762
|
+
"dbcli never creates a database file; point `file` at one that already exists.",
|
|
11763
|
+
"Check the path in the connection configuration."
|
|
11764
|
+
]);
|
|
11765
|
+
}
|
|
11766
|
+
let header;
|
|
11767
|
+
try {
|
|
11768
|
+
header = await target.slice(0, SQLITE_HEADER.length).text();
|
|
11769
|
+
} catch (error) {
|
|
11770
|
+
throw new ConnectionError("CONNECTION_REJECTED", `SQLite database file is not readable: ${this.file}`, [
|
|
11771
|
+
"Check the file permissions for the user running dbcli.",
|
|
11772
|
+
`Original error: ${error instanceof Error ? error.message : String(error)}`
|
|
11773
|
+
]);
|
|
11774
|
+
}
|
|
11775
|
+
if (target.size > 0 && header !== SQLITE_HEADER) {
|
|
11776
|
+
throw new ConnectionError("CONNECTION_REJECTED", `Not a SQLite database: ${this.file} does not start with the SQLite file header`, ["Point `file` at a SQLite database file."]);
|
|
11777
|
+
}
|
|
11778
|
+
this.connected = true;
|
|
11779
|
+
}
|
|
11780
|
+
async disconnect() {
|
|
11781
|
+
try {
|
|
11782
|
+
this.db?.close();
|
|
11783
|
+
} catch {}
|
|
11784
|
+
this.db = null;
|
|
11785
|
+
this.connected = false;
|
|
11786
|
+
}
|
|
11787
|
+
handle(readonly) {
|
|
11788
|
+
if (!this.connected)
|
|
11789
|
+
requireConnected(null);
|
|
11790
|
+
if (this.db && this.openedReadonly === readonly)
|
|
11791
|
+
return this.db;
|
|
11792
|
+
try {
|
|
11793
|
+
this.db?.close();
|
|
11794
|
+
} catch {}
|
|
11795
|
+
this.db = null;
|
|
11796
|
+
try {
|
|
11797
|
+
this.db = readonly ? new Database(this.file, { readonly: true }) : new Database(this.file, { readwrite: true, create: false });
|
|
11798
|
+
} catch (error) {
|
|
11799
|
+
throw mapSqliteError(error, this.file, readonly);
|
|
11800
|
+
}
|
|
11801
|
+
this.openedReadonly = readonly;
|
|
11802
|
+
return this.db;
|
|
11803
|
+
}
|
|
11804
|
+
async execute(sql, params, options) {
|
|
11805
|
+
const readonly = options?.sqlMode === "native-read-only";
|
|
11806
|
+
const db = this.handle(readonly);
|
|
11807
|
+
try {
|
|
11808
|
+
const statement = db.prepare(sql);
|
|
11809
|
+
try {
|
|
11810
|
+
const columnNames = statement.columnNames;
|
|
11811
|
+
if (columnNames.length === 0) {
|
|
11812
|
+
const changes = statement.run(...params ?? []);
|
|
11813
|
+
return {
|
|
11814
|
+
rows: [],
|
|
11815
|
+
affectedRows: changes.changes,
|
|
11816
|
+
lastInsertId: Number(changes.lastInsertRowid)
|
|
11817
|
+
};
|
|
11818
|
+
}
|
|
11819
|
+
const rows = statement.all(...params ?? []);
|
|
11820
|
+
return {
|
|
11821
|
+
rows,
|
|
11822
|
+
affectedRows: rows.length,
|
|
11823
|
+
rowCount: rows.length,
|
|
11824
|
+
columnNames: [...columnNames]
|
|
11825
|
+
};
|
|
11826
|
+
} finally {
|
|
11827
|
+
statement.finalize();
|
|
11828
|
+
}
|
|
11829
|
+
} catch (error) {
|
|
11830
|
+
throw mapSqliteError(error, this.file, readonly);
|
|
11831
|
+
}
|
|
11832
|
+
}
|
|
11833
|
+
async testConnection() {
|
|
11834
|
+
const result = await this.execute("SELECT 1 as ok", [], {
|
|
11835
|
+
sqlMode: "native-read-only"
|
|
11836
|
+
});
|
|
11837
|
+
return result.rows.length > 0;
|
|
11838
|
+
}
|
|
11839
|
+
async getServerVersion() {
|
|
11840
|
+
const result = await this.execute("SELECT sqlite_version() as version", [], {
|
|
11841
|
+
sqlMode: "native-read-only"
|
|
11842
|
+
});
|
|
11843
|
+
return result.rows[0]?.version ?? "unknown";
|
|
11844
|
+
}
|
|
11845
|
+
async listTables() {
|
|
11846
|
+
const result = await this.execute(`SELECT m.name AS name,
|
|
11847
|
+
m.type AS type,
|
|
11848
|
+
(SELECT COUNT(*) FROM pragma_table_info(m.name)) AS column_count
|
|
11849
|
+
FROM sqlite_master m
|
|
11850
|
+
WHERE m.type IN ('table', 'view')
|
|
11851
|
+
AND m.name NOT LIKE 'sqlite\\_%' ESCAPE '\\'
|
|
11852
|
+
ORDER BY m.name`, [], { sqlMode: "native-read-only" });
|
|
11853
|
+
return result.rows.map((row) => ({
|
|
11854
|
+
name: row.name,
|
|
11855
|
+
columns: [],
|
|
11856
|
+
columnCount: row.column_count,
|
|
11857
|
+
rowCount: 0,
|
|
11858
|
+
estimatedRowCount: 0,
|
|
11859
|
+
tableType: row.type === "view" ? "view" : "table"
|
|
11860
|
+
}));
|
|
11861
|
+
}
|
|
11862
|
+
async getTableSchema(tableName, _options) {
|
|
11863
|
+
const info = await this.execute('SELECT cid, name, type, "notnull", dflt_value, pk FROM pragma_table_info(?)', [tableName], { sqlMode: "native-read-only" });
|
|
11864
|
+
if (info.rows.length === 0) {
|
|
11865
|
+
throw new ConnectionError("TABLE_NOT_FOUND", `Table not found: ${tableName}`, [
|
|
11866
|
+
"Run `dbcli list` to see the tables this database holds."
|
|
11867
|
+
]);
|
|
11868
|
+
}
|
|
11869
|
+
const foreignKeys = await this.execute('SELECT "from", "table", "to" FROM pragma_foreign_key_list(?)', [tableName], { sqlMode: "native-read-only" });
|
|
11870
|
+
const fkByColumn = new Map(foreignKeys.rows.map((fk) => [fk.from, { table: fk.table, column: fk.to ?? "rowid" }]));
|
|
11871
|
+
const ddl = await this.execute("SELECT sql FROM sqlite_master WHERE name = ? AND type = ?", [tableName, "table"], { sqlMode: "native-read-only" });
|
|
11872
|
+
const createSql = ddl.rows[0]?.sql ?? "";
|
|
11873
|
+
const declaresAutoIncrement = /\bAUTOINCREMENT\b/i.test(createSql);
|
|
11874
|
+
const columns = info.rows.map((row) => {
|
|
11875
|
+
const isPrimaryKey = row.pk > 0;
|
|
11876
|
+
const column = {
|
|
11877
|
+
name: row.name,
|
|
11878
|
+
type: row.type || "BLOB",
|
|
11879
|
+
nullable: row.notnull === 0,
|
|
11880
|
+
primaryKey: isPrimaryKey
|
|
11881
|
+
};
|
|
11882
|
+
if (row.dflt_value !== null)
|
|
11883
|
+
column.default = row.dflt_value;
|
|
11884
|
+
const fk = fkByColumn.get(row.name);
|
|
11885
|
+
if (fk)
|
|
11886
|
+
column.foreignKey = fk;
|
|
11887
|
+
if (isPrimaryKey && declaresAutoIncrement && /^INTEGER$/i.test(row.type)) {
|
|
11888
|
+
column.autoIncrement = true;
|
|
11889
|
+
}
|
|
11890
|
+
return column;
|
|
11891
|
+
});
|
|
11892
|
+
const count = await this.execute(`SELECT COUNT(*) AS n FROM ${quoteIdentifier(tableName, "sqlite")}`, [], { sqlMode: "native-read-only" });
|
|
11893
|
+
return {
|
|
11894
|
+
name: tableName,
|
|
11895
|
+
columns,
|
|
11896
|
+
columnCount: columns.length,
|
|
11897
|
+
rowCount: count.rows[0]?.n ?? 0,
|
|
11898
|
+
estimatedRowCount: count.rows[0]?.n ?? 0,
|
|
11899
|
+
tableType: "table"
|
|
11900
|
+
};
|
|
11901
|
+
}
|
|
11902
|
+
}
|
|
11903
|
+
var SQLITE_HEADER = "SQLite format 3\x00";
|
|
11904
|
+
var init_sqlite_adapter = __esm(() => {
|
|
11905
|
+
init_types2();
|
|
11906
|
+
init_sql_adapter_utils();
|
|
11907
|
+
});
|
|
11908
|
+
|
|
11628
11909
|
// src/adapters/server-side-script.ts
|
|
11629
11910
|
function isElasticsearchScriptKey(key, parentKey) {
|
|
11630
11911
|
if (key === "script" || key === "script_fields")
|
|
@@ -14268,6 +14549,8 @@ class AdapterFactory {
|
|
|
14268
14549
|
case "mysql":
|
|
14269
14550
|
case "mariadb":
|
|
14270
14551
|
return new MySQLAdapter(options);
|
|
14552
|
+
case "sqlite":
|
|
14553
|
+
return new SQLiteAdapter(options);
|
|
14271
14554
|
default:
|
|
14272
14555
|
throw new Error(`createSqlAdapter requires a SQL system, got: ${options.system}`);
|
|
14273
14556
|
}
|
|
@@ -14291,6 +14574,7 @@ class AdapterFactory {
|
|
|
14291
14574
|
case "postgresql":
|
|
14292
14575
|
case "mysql":
|
|
14293
14576
|
case "mariadb":
|
|
14577
|
+
case "sqlite":
|
|
14294
14578
|
return AdapterFactory.createSqlAdapter(options);
|
|
14295
14579
|
case "redis":
|
|
14296
14580
|
return AdapterFactory.createRedisAdapter(config);
|
|
@@ -14307,6 +14591,7 @@ class AdapterFactory {
|
|
|
14307
14591
|
case "postgresql":
|
|
14308
14592
|
case "mysql":
|
|
14309
14593
|
case "mariadb":
|
|
14594
|
+
case "sqlite":
|
|
14310
14595
|
return AdapterFactory.createSqlAdapter(options);
|
|
14311
14596
|
case "mongodb":
|
|
14312
14597
|
case "redis":
|
|
@@ -14351,6 +14636,7 @@ class AdapterFactory {
|
|
|
14351
14636
|
var init_factory = __esm(() => {
|
|
14352
14637
|
init_postgresql_adapter();
|
|
14353
14638
|
init_mysql_adapter();
|
|
14639
|
+
init_sqlite_adapter();
|
|
14354
14640
|
init_mongodb_adapter();
|
|
14355
14641
|
init_redis_adapter();
|
|
14356
14642
|
init_elasticsearch_adapter();
|
|
@@ -14722,13 +15008,113 @@ var init_init_mongodb = __esm(() => {
|
|
|
14722
15008
|
VALID_PERMISSIONS = ["query-only", "read-write", "data-admin", "admin"];
|
|
14723
15009
|
});
|
|
14724
15010
|
|
|
15011
|
+
// src/commands/init-sqlite.ts
|
|
15012
|
+
import { mkdir as mkdir7 } from "fs/promises";
|
|
15013
|
+
function isMemoryTarget(value) {
|
|
15014
|
+
const trimmed = value.trim().toLowerCase();
|
|
15015
|
+
if (trimmed === ":memory:")
|
|
15016
|
+
return true;
|
|
15017
|
+
if (!trimmed.startsWith("file:"))
|
|
15018
|
+
return false;
|
|
15019
|
+
const withoutScheme = trimmed.slice("file:".length);
|
|
15020
|
+
const [path = "", query = ""] = withoutScheme.split("?");
|
|
15021
|
+
if (path === ":memory:")
|
|
15022
|
+
return true;
|
|
15023
|
+
return new URLSearchParams(query).get("mode") === "memory";
|
|
15024
|
+
}
|
|
15025
|
+
async function handleSQLiteInit(ctx) {
|
|
15026
|
+
const { options, configPath, connectionName, isV2Init, existingConfig } = ctx;
|
|
15027
|
+
const isInteractive = options.interactive !== false && process.stdin.isTTY;
|
|
15028
|
+
let file = options.file?.trim();
|
|
15029
|
+
while (!file && isInteractive) {
|
|
15030
|
+
const input = (await promptUser.text(t("init.prompt_sqlite_file"))).trim();
|
|
15031
|
+
if (!input)
|
|
15032
|
+
continue;
|
|
15033
|
+
if (isMemoryTarget(input)) {
|
|
15034
|
+
console.error(SQLITE_MEMORY_TARGET_MESSAGE);
|
|
15035
|
+
continue;
|
|
15036
|
+
}
|
|
15037
|
+
file = input;
|
|
15038
|
+
}
|
|
15039
|
+
if (!file) {
|
|
15040
|
+
throw new Error(t("errors.sqlite_file_required"));
|
|
15041
|
+
}
|
|
15042
|
+
if (isMemoryTarget(file)) {
|
|
15043
|
+
throw new Error(SQLITE_MEMORY_TARGET_MESSAGE);
|
|
15044
|
+
}
|
|
15045
|
+
const sqliteConfig = { system: "sqlite", file };
|
|
15046
|
+
let permission = options.permission || "query-only";
|
|
15047
|
+
if (isInteractive && !options.permission) {
|
|
15048
|
+
permission = await promptUser.select(t("init.prompt_permission"), [
|
|
15049
|
+
"query-only",
|
|
15050
|
+
"read-write",
|
|
15051
|
+
"data-admin",
|
|
15052
|
+
"admin"
|
|
15053
|
+
]);
|
|
15054
|
+
}
|
|
15055
|
+
if (!VALID_PERMISSIONS2.includes(permission)) {
|
|
15056
|
+
throw new Error(t_vars("errors.invalid_permission", { permission }));
|
|
15057
|
+
}
|
|
15058
|
+
const canProceed = await checkOverwrite(configPath, isInteractive, !!options.force);
|
|
15059
|
+
if (!canProceed)
|
|
15060
|
+
return;
|
|
15061
|
+
if (!options.skipTest) {
|
|
15062
|
+
console.log(t("init.connection_testing"));
|
|
15063
|
+
const adapter = AdapterFactory.createAdapterWithoutRules(sqliteConfig);
|
|
15064
|
+
try {
|
|
15065
|
+
await adapter.connect();
|
|
15066
|
+
await adapter.testConnection();
|
|
15067
|
+
console.log(t("init.connection_success"));
|
|
15068
|
+
} catch (error) {
|
|
15069
|
+
if (error instanceof ConnectionError) {
|
|
15070
|
+
console.error(t_vars("errors.connection_failed", { message: error.message }));
|
|
15071
|
+
console.error(t("init.connection_hints"));
|
|
15072
|
+
error.hints.forEach((hint) => console.error(` \u2022 ${hint}`));
|
|
15073
|
+
process.exit(1);
|
|
15074
|
+
}
|
|
15075
|
+
throw error;
|
|
15076
|
+
} finally {
|
|
15077
|
+
await adapter.disconnect();
|
|
15078
|
+
}
|
|
15079
|
+
}
|
|
15080
|
+
if (isV2Init) {
|
|
15081
|
+
await writeV2InitConfig(configPath, connectionName, sqliteConfig, permission, options.envFile);
|
|
15082
|
+
return;
|
|
15083
|
+
}
|
|
15084
|
+
const newConfig = configModule.merge(existingConfig, {
|
|
15085
|
+
connection: sqliteConfig,
|
|
15086
|
+
permission
|
|
15087
|
+
});
|
|
15088
|
+
const globalConfig = isGlobalConfigPath(configPath);
|
|
15089
|
+
const storagePath = globalConfig ? configPath : getProjectStoragePath(configPath);
|
|
15090
|
+
await mkdir7(storagePath, { recursive: true });
|
|
15091
|
+
await configModule.write(storagePath, newConfig);
|
|
15092
|
+
if (!globalConfig) {
|
|
15093
|
+
await migrateLegacyProjectEnvLocal(configPath, storagePath);
|
|
15094
|
+
await writeProjectBinding(configPath, storagePath);
|
|
15095
|
+
}
|
|
15096
|
+
console.log(t("init.config_saved"));
|
|
15097
|
+
}
|
|
15098
|
+
var VALID_PERMISSIONS2;
|
|
15099
|
+
var init_init_sqlite = __esm(() => {
|
|
15100
|
+
init_message_loader();
|
|
15101
|
+
init_config();
|
|
15102
|
+
init_prompts();
|
|
15103
|
+
init_adapters();
|
|
15104
|
+
init_validation();
|
|
15105
|
+
init_config_binding();
|
|
15106
|
+
init_init_shared();
|
|
15107
|
+
VALID_PERMISSIONS2 = ["query-only", "read-write", "data-admin", "admin"];
|
|
15108
|
+
});
|
|
15109
|
+
|
|
14725
15110
|
// src/commands/init.ts
|
|
14726
15111
|
var exports_init = {};
|
|
14727
15112
|
__export(exports_init, {
|
|
14728
|
-
initCommand: () => initCommand
|
|
15113
|
+
initCommand: () => initCommand,
|
|
15114
|
+
INIT_SYSTEM_CHOICES: () => INIT_SYSTEM_CHOICES
|
|
14729
15115
|
});
|
|
14730
15116
|
import { join as join11 } from "path";
|
|
14731
|
-
import { mkdir as
|
|
15117
|
+
import { mkdir as mkdir8 } from "fs/promises";
|
|
14732
15118
|
async function handleRemove(configPath, name) {
|
|
14733
15119
|
const storagePath = await resolveConfigStoragePath(configPath);
|
|
14734
15120
|
const configFile = Bun.file(join11(storagePath, "config.json"));
|
|
@@ -14819,18 +15205,22 @@ async function initCommandHandler(options, command) {
|
|
|
14819
15205
|
const systemFromCli = typeof options.system === "string" ? options.system : undefined;
|
|
14820
15206
|
let system = systemFromCli ?? envConfig?.system ?? "postgresql";
|
|
14821
15207
|
if (shouldPrompt && !options.system && !envConfig?.system) {
|
|
14822
|
-
system = await promptUser.select(t("init.select_system"), [
|
|
14823
|
-
"postgresql",
|
|
14824
|
-
"mysql",
|
|
14825
|
-
"mariadb",
|
|
14826
|
-
"mongodb",
|
|
14827
|
-
"redis",
|
|
14828
|
-
"elasticsearch"
|
|
14829
|
-
]);
|
|
15208
|
+
system = await promptUser.select(t("init.select_system"), [...INIT_SYSTEM_CHOICES]);
|
|
14830
15209
|
}
|
|
14831
|
-
if (!
|
|
15210
|
+
if (!DATABASE_SYSTEMS.includes(system)) {
|
|
14832
15211
|
throw new Error(t_vars("errors.invalid_system", { system }));
|
|
14833
15212
|
}
|
|
15213
|
+
if (system === "sqlite") {
|
|
15214
|
+
await handleSQLiteInit({
|
|
15215
|
+
options,
|
|
15216
|
+
configPath,
|
|
15217
|
+
connectionName,
|
|
15218
|
+
isV2Init,
|
|
15219
|
+
existingConfig,
|
|
15220
|
+
shouldPrompt
|
|
15221
|
+
});
|
|
15222
|
+
return;
|
|
15223
|
+
}
|
|
14834
15224
|
const defaults = getDefaultsForSystem(system);
|
|
14835
15225
|
if (system === "mongodb") {
|
|
14836
15226
|
await handleMongoDBInit({
|
|
@@ -14871,7 +15261,7 @@ async function initCommandHandler(options, command) {
|
|
|
14871
15261
|
"admin"
|
|
14872
15262
|
]);
|
|
14873
15263
|
}
|
|
14874
|
-
if (!
|
|
15264
|
+
if (!VALID_PERMISSIONS3.includes(permission2)) {
|
|
14875
15265
|
throw new Error(t_vars("errors.invalid_permission", { permission: permission2 }));
|
|
14876
15266
|
}
|
|
14877
15267
|
const newConfig2 = configModule.merge(existingConfig, {
|
|
@@ -14887,7 +15277,7 @@ async function initCommandHandler(options, command) {
|
|
|
14887
15277
|
return;
|
|
14888
15278
|
}
|
|
14889
15279
|
const storagePath2 = isGlobalConfig ? configPath : getProjectStoragePath(configPath);
|
|
14890
|
-
await
|
|
15280
|
+
await mkdir8(storagePath2, { recursive: true });
|
|
14891
15281
|
await configModule.write(storagePath2, newConfig2);
|
|
14892
15282
|
if (!isGlobalConfig) {
|
|
14893
15283
|
await migrateLegacyProjectEnvLocal(configPath, storagePath2);
|
|
@@ -14928,7 +15318,7 @@ async function initCommandHandler(options, command) {
|
|
|
14928
15318
|
"admin"
|
|
14929
15319
|
]);
|
|
14930
15320
|
}
|
|
14931
|
-
if (!
|
|
15321
|
+
if (!VALID_PERMISSIONS3.includes(permission)) {
|
|
14932
15322
|
throw new Error(t_vars("errors.invalid_permission", { permission }));
|
|
14933
15323
|
}
|
|
14934
15324
|
configForWrite = connection;
|
|
@@ -15006,7 +15396,7 @@ async function initCommandHandler(options, command) {
|
|
|
15006
15396
|
return;
|
|
15007
15397
|
}
|
|
15008
15398
|
const storagePath = isGlobalConfig ? configPath : getProjectStoragePath(configPath);
|
|
15009
|
-
await
|
|
15399
|
+
await mkdir8(storagePath, { recursive: true });
|
|
15010
15400
|
await configModule.write(storagePath, newConfig);
|
|
15011
15401
|
if (!isGlobalConfig) {
|
|
15012
15402
|
await migrateLegacyProjectEnvLocal(configPath, storagePath);
|
|
@@ -15014,7 +15404,7 @@ async function initCommandHandler(options, command) {
|
|
|
15014
15404
|
}
|
|
15015
15405
|
console.log(t("init.config_saved"));
|
|
15016
15406
|
}
|
|
15017
|
-
var
|
|
15407
|
+
var INIT_SYSTEM_CHOICES, VALID_PERMISSIONS3, initCommand;
|
|
15018
15408
|
var init_init = __esm(() => {
|
|
15019
15409
|
init_esm();
|
|
15020
15410
|
init_message_loader();
|
|
@@ -15024,12 +15414,15 @@ var init_init = __esm(() => {
|
|
|
15024
15414
|
init_prompts();
|
|
15025
15415
|
init_redaction();
|
|
15026
15416
|
init_adapters();
|
|
15417
|
+
init_types2();
|
|
15027
15418
|
init_config_path();
|
|
15028
15419
|
init_config_binding();
|
|
15029
15420
|
init_init_shared();
|
|
15030
15421
|
init_init_mongodb();
|
|
15031
|
-
|
|
15032
|
-
|
|
15422
|
+
init_init_sqlite();
|
|
15423
|
+
INIT_SYSTEM_CHOICES = DATABASE_SYSTEMS;
|
|
15424
|
+
VALID_PERMISSIONS3 = ["query-only", "read-write", "data-admin", "admin"];
|
|
15425
|
+
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("--file <path>", "SQLite database file path (the database must already exist)").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) => {
|
|
15033
15426
|
try {
|
|
15034
15427
|
await initCommandHandler(options, initCommand);
|
|
15035
15428
|
} catch (error) {
|
|
@@ -16976,17 +17369,22 @@ var init_formatters = __esm(() => {
|
|
|
16976
17369
|
init_query_result_formatter();
|
|
16977
17370
|
});
|
|
16978
17371
|
|
|
16979
|
-
// src/commands/
|
|
16980
|
-
var exports_list = {};
|
|
16981
|
-
__export(exports_list, {
|
|
16982
|
-
listCommand: () => listCommand
|
|
16983
|
-
});
|
|
17372
|
+
// src/commands/require-sql-connection.ts
|
|
16984
17373
|
function requireSqlConnection(connection) {
|
|
16985
|
-
if (!
|
|
17374
|
+
if (!SQL_DATABASE_SYSTEMS.includes(connection.system)) {
|
|
16986
17375
|
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
16987
17376
|
}
|
|
16988
17377
|
return connection;
|
|
16989
17378
|
}
|
|
17379
|
+
var init_require_sql_connection = __esm(() => {
|
|
17380
|
+
init_types2();
|
|
17381
|
+
});
|
|
17382
|
+
|
|
17383
|
+
// src/commands/list.ts
|
|
17384
|
+
var exports_list = {};
|
|
17385
|
+
__export(exports_list, {
|
|
17386
|
+
listCommand: () => listCommand
|
|
17387
|
+
});
|
|
16990
17388
|
async function listAction(options, command) {
|
|
16991
17389
|
validateFormat(options.format, ALLOWED_FORMATS, "list");
|
|
16992
17390
|
const config = await configModule.read(resolveConfigPath(command, options));
|
|
@@ -17136,6 +17534,7 @@ var init_list = __esm(() => {
|
|
|
17136
17534
|
init_config_path();
|
|
17137
17535
|
init_validation();
|
|
17138
17536
|
init_connection_selector();
|
|
17537
|
+
init_require_sql_connection();
|
|
17139
17538
|
ALLOWED_FORMATS = ["table", "json"];
|
|
17140
17539
|
listCommand = new Command().name("list").description("List all tables in the database with metadata").option("--format <format>", "Output format: table (default) or json", "table").option("--config <path>", "Path to .dbcli config file", ".dbcli").addOption(createConnectionSelectorOption()).option("--include-system", "Elasticsearch only: include system indices whose names start with dot", false).action(listAction);
|
|
17141
17540
|
});
|
|
@@ -17251,7 +17650,7 @@ var init_artifact = __esm(() => {
|
|
|
17251
17650
|
});
|
|
17252
17651
|
|
|
17253
17652
|
// src/core/verification/artifact-writer.ts
|
|
17254
|
-
import { mkdir as
|
|
17653
|
+
import { mkdir as mkdir9, writeFile as writeFile3, link, unlink as unlink5 } from "fs/promises";
|
|
17255
17654
|
import { join as join12 } from "path";
|
|
17256
17655
|
function pad(n, len = 2) {
|
|
17257
17656
|
return String(n).padStart(len, "0");
|
|
@@ -17270,7 +17669,7 @@ function verificationArtifactFilename(artifact) {
|
|
|
17270
17669
|
}
|
|
17271
17670
|
async function writeVerificationArtifact(storageDir, artifact) {
|
|
17272
17671
|
const dir = join12(storageDir, VERIFICATION_DIR_RELATIVE);
|
|
17273
|
-
await
|
|
17672
|
+
await mkdir9(dir, { recursive: true });
|
|
17274
17673
|
const target = join12(dir, verificationArtifactFilename(artifact));
|
|
17275
17674
|
const tmp = `${target}.${process.pid}.tmp`;
|
|
17276
17675
|
try {
|
|
@@ -17843,7 +18242,7 @@ var init_legacy = __esm(() => {
|
|
|
17843
18242
|
|
|
17844
18243
|
// src/core/evidence-receipt/index.ts
|
|
17845
18244
|
import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
|
|
17846
|
-
import { link as link2, lstat as lstat4, mkdir as
|
|
18245
|
+
import { link as link2, lstat as lstat4, mkdir as mkdir10, readFile as readFile3, realpath, unlink as unlink7, writeFile as writeFile4 } from "fs/promises";
|
|
17847
18246
|
import { basename as basename3, dirname as dirname5, relative, resolve as resolve5, sep as sep3 } from "path";
|
|
17848
18247
|
function exact(value, keys, label) {
|
|
17849
18248
|
if (Object.keys(value).length !== keys.length || Object.keys(value).some((key) => !keys.includes(key))) {
|
|
@@ -18123,7 +18522,7 @@ async function writeEvidenceReceipt(workspaceRoot, outputPath, receipt) {
|
|
|
18123
18522
|
const parent = dirname5(requested);
|
|
18124
18523
|
if (!inside(workspace, await existingAncestor(parent)))
|
|
18125
18524
|
throw new EvidenceReceiptValidationError("output path resolves outside the workspace");
|
|
18126
|
-
await
|
|
18525
|
+
await mkdir10(parent, { recursive: true });
|
|
18127
18526
|
const realParent = await realpath(parent);
|
|
18128
18527
|
if (!inside(workspace, realParent))
|
|
18129
18528
|
throw new EvidenceReceiptValidationError("output path resolves outside the workspace");
|
|
@@ -18217,8 +18616,16 @@ var init_evidence_receipt = __esm(() => {
|
|
|
18217
18616
|
});
|
|
18218
18617
|
|
|
18219
18618
|
// src/core/saved-queries/types.ts
|
|
18220
|
-
var SUPPORTED_CHART_TYPES, SavedQueryError;
|
|
18619
|
+
var ENGINE_TAGS, SUPPORTED_CHART_TYPES, SavedQueryError;
|
|
18221
18620
|
var init_types4 = __esm(() => {
|
|
18621
|
+
ENGINE_TAGS = Object.freeze([
|
|
18622
|
+
"postgres",
|
|
18623
|
+
"mysql",
|
|
18624
|
+
"sqlite",
|
|
18625
|
+
"elasticsearch",
|
|
18626
|
+
"redis",
|
|
18627
|
+
"mongodb"
|
|
18628
|
+
]);
|
|
18222
18629
|
SUPPORTED_CHART_TYPES = ["line", "bar", "area", "pie"];
|
|
18223
18630
|
SavedQueryError = class SavedQueryError extends Error {
|
|
18224
18631
|
code;
|
|
@@ -18777,7 +19184,16 @@ function stripCommentsAndStrings2(sql, options = {}) {
|
|
|
18777
19184
|
continue;
|
|
18778
19185
|
}
|
|
18779
19186
|
}
|
|
18780
|
-
if (
|
|
19187
|
+
if (options.dialect === "sqlite" && char === "[") {
|
|
19188
|
+
i++;
|
|
19189
|
+
while (i < sql.length && sql[i] !== "]")
|
|
19190
|
+
i++;
|
|
19191
|
+
if (i < sql.length)
|
|
19192
|
+
i++;
|
|
19193
|
+
result += " ";
|
|
19194
|
+
continue;
|
|
19195
|
+
}
|
|
19196
|
+
if ((mysqlDialect || options.dialect === "sqlite") && char === "`") {
|
|
18781
19197
|
i++;
|
|
18782
19198
|
while (i < sql.length) {
|
|
18783
19199
|
if (sql[i] === "`") {
|
|
@@ -18850,8 +19266,13 @@ function extractFirstKeyword(sql) {
|
|
|
18850
19266
|
}
|
|
18851
19267
|
return firstKeyword;
|
|
18852
19268
|
}
|
|
18853
|
-
function
|
|
19269
|
+
function isDeclaredAdminOnly(keyword) {
|
|
19270
|
+
return DECLARED_ADMIN_ONLY_KEYWORDS.has(keyword.toUpperCase());
|
|
19271
|
+
}
|
|
19272
|
+
function mapKeywordToType(keyword, dialect) {
|
|
18854
19273
|
const upper = keyword.toUpperCase();
|
|
19274
|
+
if (upper === "REPLACE" && dialect === "sqlite")
|
|
19275
|
+
return "REPLACE";
|
|
18855
19276
|
if (upper === "SELECT")
|
|
18856
19277
|
return "SELECT";
|
|
18857
19278
|
if (upper === "SHOW")
|
|
@@ -18881,7 +19302,7 @@ function mapKeywordToType(keyword) {
|
|
|
18881
19302
|
return "UNKNOWN";
|
|
18882
19303
|
}
|
|
18883
19304
|
function isDestructiveOperation(type) {
|
|
18884
|
-
return ["DELETE", "DROP", "ALTER", "TRUNCATE", "CREATE", "GRANT"].includes(type);
|
|
19305
|
+
return ["DELETE", "REPLACE", "DROP", "ALTER", "TRUNCATE", "CREATE", "GRANT"].includes(type);
|
|
18885
19306
|
}
|
|
18886
19307
|
function extractAllKeywords(sql) {
|
|
18887
19308
|
const upper = sql.toUpperCase();
|
|
@@ -18948,8 +19369,10 @@ function determineConfidence(type, _keyword, _sql) {
|
|
|
18948
19369
|
function removeParameterMarkers(sql) {
|
|
18949
19370
|
return sql.replace(/\$\d+/g, "").replace(/\?/g, "");
|
|
18950
19371
|
}
|
|
19372
|
+
var DECLARED_ADMIN_ONLY_KEYWORDS;
|
|
18951
19373
|
var init_sql_analysis = __esm(() => {
|
|
18952
19374
|
init_sql_lexical();
|
|
19375
|
+
DECLARED_ADMIN_ONLY_KEYWORDS = new Set(["PRAGMA", "VACUUM", "REINDEX"]);
|
|
18953
19376
|
});
|
|
18954
19377
|
|
|
18955
19378
|
// src/core/permission/rank.ts
|
|
@@ -18984,11 +19407,12 @@ __export(exports_permission_guard, {
|
|
|
18984
19407
|
SQL_WRITE_OR_DDL_KEYWORDS: () => SQL_WRITE_OR_DDL_KEYWORDS,
|
|
18985
19408
|
SQL_LOCK_CLAUSE: () => SQL_LOCK_CLAUSE,
|
|
18986
19409
|
SQL_DIALECTS: () => SQL_DIALECTS,
|
|
18987
|
-
PermissionError: () => PermissionError
|
|
19410
|
+
PermissionError: () => PermissionError,
|
|
19411
|
+
ConnectionBoundaryError: () => ConnectionBoundaryError
|
|
18988
19412
|
});
|
|
18989
|
-
function classifyStatement(sql) {
|
|
19413
|
+
function classifyStatement(sql, dialect) {
|
|
18990
19414
|
const normalized = normalizeSQL(sql);
|
|
18991
|
-
const stripped = stripCommentsAndStrings2(normalized);
|
|
19415
|
+
const stripped = stripCommentsAndStrings2(normalized, { dialect });
|
|
18992
19416
|
const upper = stripped.toUpperCase();
|
|
18993
19417
|
if (/^\s*ANALYZE\s+SELECT\b/i.test(stripped)) {
|
|
18994
19418
|
return {
|
|
@@ -19001,7 +19425,16 @@ function classifyStatement(sql) {
|
|
|
19001
19425
|
}
|
|
19002
19426
|
const composite = detectCompositePatterns(upper);
|
|
19003
19427
|
const firstKeyword = extractFirstKeyword(stripped);
|
|
19004
|
-
|
|
19428
|
+
if (isDeclaredAdminOnly(firstKeyword)) {
|
|
19429
|
+
return {
|
|
19430
|
+
type: "UNKNOWN",
|
|
19431
|
+
isDangerous: true,
|
|
19432
|
+
keywords: extractAllKeywords(stripped),
|
|
19433
|
+
isComposite: false,
|
|
19434
|
+
confidence: "HIGH"
|
|
19435
|
+
};
|
|
19436
|
+
}
|
|
19437
|
+
const type = mapKeywordToType(firstKeyword, dialect);
|
|
19005
19438
|
return {
|
|
19006
19439
|
type,
|
|
19007
19440
|
isDangerous: isDestructiveOperation(type),
|
|
@@ -19046,8 +19479,21 @@ function escalateHiddenWrite(sql, classification, dialect) {
|
|
|
19046
19479
|
escalatedFrom: hidden
|
|
19047
19480
|
};
|
|
19048
19481
|
}
|
|
19482
|
+
function reachesAnotherDatabase(sql, dialect) {
|
|
19483
|
+
if (dialect !== "sqlite")
|
|
19484
|
+
return false;
|
|
19485
|
+
return stripCommentsAndStrings2(sql, { dialect }).split(";").some((statement) => SQLITE_CROSS_DATABASE.test(statement));
|
|
19486
|
+
}
|
|
19049
19487
|
function checkPermission(sql, permission, dialect) {
|
|
19050
|
-
|
|
19488
|
+
if (reachesAnotherDatabase(sql, dialect)) {
|
|
19489
|
+
return {
|
|
19490
|
+
allowed: false,
|
|
19491
|
+
reason: "ATTACH and DETACH are refused on a SQLite connection: they reach a database file " + "outside the one this connection names, which the blacklist, audit log and schema " + "cache are all scoped to. Configure a second connection instead.",
|
|
19492
|
+
classification: classifyStatement(sql),
|
|
19493
|
+
boundary: true
|
|
19494
|
+
};
|
|
19495
|
+
}
|
|
19496
|
+
const classification = escalateHiddenWrite(sql, classifyStatement(sql, dialect), dialect);
|
|
19051
19497
|
if (permission !== "admin" && containsMultipleStatements(sql, dialect)) {
|
|
19052
19498
|
return {
|
|
19053
19499
|
allowed: false,
|
|
@@ -19133,16 +19579,28 @@ function enforcePermissionForType(type, permission) {
|
|
|
19133
19579
|
}
|
|
19134
19580
|
function enforcePermission(sql, permission, dialect) {
|
|
19135
19581
|
const result = checkPermission(sql, permission, dialect);
|
|
19582
|
+
if (result.boundary) {
|
|
19583
|
+
throw new ConnectionBoundaryError(result.reason, result.classification);
|
|
19584
|
+
}
|
|
19136
19585
|
if (!result.allowed) {
|
|
19137
19586
|
throw new PermissionError(result.reason, result.classification, result.requiredPermission ?? "admin");
|
|
19138
19587
|
}
|
|
19139
19588
|
return result.classification;
|
|
19140
19589
|
}
|
|
19141
|
-
var PermissionError, SQL_DIALECTS, SQL_WRITE_OR_DDL_KEYWORDS, SQL_LOCK_CLAUSE, ESCALATABLE_READ_TYPES, TIER_GRANTS;
|
|
19590
|
+
var ConnectionBoundaryError, PermissionError, SQL_DIALECTS, SQL_WRITE_OR_DDL_KEYWORDS, SQL_LOCK_CLAUSE, ESCALATABLE_READ_TYPES, SQLITE_CROSS_DATABASE, TIER_GRANTS;
|
|
19142
19591
|
var init_permission_guard = __esm(() => {
|
|
19143
19592
|
init_message_loader();
|
|
19144
19593
|
init_sql_analysis();
|
|
19145
19594
|
init_rank();
|
|
19595
|
+
ConnectionBoundaryError = class ConnectionBoundaryError extends Error {
|
|
19596
|
+
classification;
|
|
19597
|
+
constructor(message, classification) {
|
|
19598
|
+
super(message);
|
|
19599
|
+
this.classification = classification;
|
|
19600
|
+
this.name = "ConnectionBoundaryError";
|
|
19601
|
+
Object.setPrototypeOf(this, ConnectionBoundaryError.prototype);
|
|
19602
|
+
}
|
|
19603
|
+
};
|
|
19146
19604
|
PermissionError = class PermissionError extends Error {
|
|
19147
19605
|
classification;
|
|
19148
19606
|
requiredPermission;
|
|
@@ -19154,14 +19612,15 @@ var init_permission_guard = __esm(() => {
|
|
|
19154
19612
|
Object.setPrototypeOf(this, PermissionError.prototype);
|
|
19155
19613
|
}
|
|
19156
19614
|
};
|
|
19157
|
-
SQL_DIALECTS = ["postgresql", "mysql", "mariadb"];
|
|
19615
|
+
SQL_DIALECTS = ["postgresql", "mysql", "mariadb", "sqlite"];
|
|
19158
19616
|
SQL_WRITE_OR_DDL_KEYWORDS = /(?<![.\w])(INSERT|UPDATE|DELETE|MERGE|UPSERT|REPLACE|TRUNCATE|DROP|ALTER|CREATE|GRANT|REVOKE|RENAME|INTO)\b(?!\s*\()/i;
|
|
19159
19617
|
SQL_LOCK_CLAUSE = /\bFOR\s+(?:NO\s+KEY\s+)?UPDATE\b|\bFOR\s+(?:KEY\s+)?SHARE\b/gi;
|
|
19160
19618
|
ESCALATABLE_READ_TYPES = new Set(["SELECT", "EXPLAIN", "DESCRIBE"]);
|
|
19619
|
+
SQLITE_CROSS_DATABASE = /^\s*(ATTACH|DETACH)\b/i;
|
|
19161
19620
|
TIER_GRANTS = [
|
|
19162
19621
|
{ permission: "query-only", types: ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN"] },
|
|
19163
19622
|
{ permission: "read-write", types: ["INSERT", "UPDATE"] },
|
|
19164
|
-
{ permission: "data-admin", types: ["DELETE"] }
|
|
19623
|
+
{ permission: "data-admin", types: ["DELETE", "REPLACE"] }
|
|
19165
19624
|
];
|
|
19166
19625
|
});
|
|
19167
19626
|
|
|
@@ -19311,7 +19770,7 @@ var init_mongodb = __esm(() => {
|
|
|
19311
19770
|
});
|
|
19312
19771
|
// src/core/saved-queries/strategies/index.ts
|
|
19313
19772
|
function engineFamily(engine) {
|
|
19314
|
-
if (engine === "postgres" || engine === "mysql")
|
|
19773
|
+
if (engine === "postgres" || engine === "mysql" || engine === "sqlite")
|
|
19315
19774
|
return "sql";
|
|
19316
19775
|
if (engine === "elasticsearch")
|
|
19317
19776
|
return "es";
|
|
@@ -19470,7 +19929,7 @@ function parseScalar(s) {
|
|
|
19470
19929
|
|
|
19471
19930
|
// src/core/saved-queries/parser.ts
|
|
19472
19931
|
function familyOf(engine) {
|
|
19473
|
-
if (engine === "postgres" || engine === "mysql")
|
|
19932
|
+
if (engine === "postgres" || engine === "mysql" || engine === "sqlite")
|
|
19474
19933
|
return "sql";
|
|
19475
19934
|
if (engine === "elasticsearch")
|
|
19476
19935
|
return "es";
|
|
@@ -19796,13 +20255,14 @@ var init_parser = __esm(() => {
|
|
|
19796
20255
|
init_types4();
|
|
19797
20256
|
MAX_BYTES = 64 * 1024;
|
|
19798
20257
|
VALID_TYPES = ["int", "string", "float", "bool", "date", "datetime"];
|
|
19799
|
-
VALID_ENGINES =
|
|
20258
|
+
VALID_ENGINES = ENGINE_TAGS;
|
|
19800
20259
|
INTENT_RE = /^[a-z][a-z0-9.-]*$/;
|
|
19801
20260
|
ENGINE_DIALECT = {
|
|
19802
20261
|
postgres: "postgresql",
|
|
19803
20262
|
postgresql: "postgresql",
|
|
19804
20263
|
mysql: "mysql",
|
|
19805
|
-
mariadb: "mariadb"
|
|
20264
|
+
mariadb: "mariadb",
|
|
20265
|
+
sqlite: "sqlite"
|
|
19806
20266
|
};
|
|
19807
20267
|
});
|
|
19808
20268
|
|
|
@@ -20045,7 +20505,8 @@ var init_read_only = __esm(() => {
|
|
|
20045
20505
|
DIALECT = {
|
|
20046
20506
|
postgresql: "Postgresql",
|
|
20047
20507
|
mysql: "MySQL",
|
|
20048
|
-
mariadb: "MariaDB"
|
|
20508
|
+
mariadb: "MariaDB",
|
|
20509
|
+
sqlite: "Sqlite"
|
|
20049
20510
|
};
|
|
20050
20511
|
WRITE_STATEMENT_TYPES = new Set([
|
|
20051
20512
|
"insert",
|
|
@@ -20391,7 +20852,8 @@ var init_query_draft = __esm(() => {
|
|
|
20391
20852
|
DIALECT2 = {
|
|
20392
20853
|
postgresql: "Postgresql",
|
|
20393
20854
|
mysql: "MySQL",
|
|
20394
|
-
mariadb: "MariaDB"
|
|
20855
|
+
mariadb: "MariaDB",
|
|
20856
|
+
sqlite: "Sqlite"
|
|
20395
20857
|
};
|
|
20396
20858
|
HASH = /^[a-f0-9]{64}$/;
|
|
20397
20859
|
IDENTIFIER = /^[a-z][a-z0-9-]*$/;
|
|
@@ -21313,6 +21775,8 @@ function mapSystemToEngine(system) {
|
|
|
21313
21775
|
return "postgres";
|
|
21314
21776
|
if (system === "mysql" || system === "mariadb")
|
|
21315
21777
|
return "mysql";
|
|
21778
|
+
if (system === "sqlite")
|
|
21779
|
+
return "sqlite";
|
|
21316
21780
|
if (system === "mongodb")
|
|
21317
21781
|
return "mongodb";
|
|
21318
21782
|
if (system === "elasticsearch")
|
|
@@ -22545,13 +23009,25 @@ class BlacklistValidator {
|
|
|
22545
23009
|
const tables = dedupe2(tableNames);
|
|
22546
23010
|
const blacklistedColumns = tables.length === 0 ? this.manager.getAllBlacklistedColumns() : Array.from(new Set(tables.flatMap((table) => this.manager.getBlacklistedColumns(table))));
|
|
22547
23011
|
if (blacklistedColumns.length === 0) {
|
|
22548
|
-
return {
|
|
23012
|
+
return {
|
|
23013
|
+
filteredRows: rows,
|
|
23014
|
+
omittedColumns: [],
|
|
23015
|
+
cost: NO_MASKING_COST,
|
|
23016
|
+
reachesOmitted: () => false
|
|
23017
|
+
};
|
|
22549
23018
|
}
|
|
23019
|
+
let rowsScanned = 0;
|
|
23020
|
+
let keysScanned = 0;
|
|
23021
|
+
let ruleEvaluations = 0;
|
|
23022
|
+
let pathSplits = 0;
|
|
23023
|
+
let nestedProbeRows = 0;
|
|
23024
|
+
let nestedGlobRows = 0;
|
|
22550
23025
|
const probeNested = blacklistedColumns.some((path2) => path2.includes("."));
|
|
22551
23026
|
const presentColumns = new Set(columnList);
|
|
22552
23027
|
const nestedHeads = new Set;
|
|
22553
23028
|
const collect2 = (record4) => {
|
|
22554
23029
|
for (const key of Object.getOwnPropertyNames(record4)) {
|
|
23030
|
+
keysScanned += 1;
|
|
22555
23031
|
presentColumns.add(key);
|
|
22556
23032
|
if (!probeNested)
|
|
22557
23033
|
continue;
|
|
@@ -22561,6 +23037,7 @@ class BlacklistValidator {
|
|
|
22561
23037
|
}
|
|
22562
23038
|
};
|
|
22563
23039
|
for (const row of rows) {
|
|
23040
|
+
rowsScanned += 1;
|
|
22564
23041
|
if (row === null || typeof row !== "object")
|
|
22565
23042
|
continue;
|
|
22566
23043
|
if (Array.isArray(row)) {
|
|
@@ -22583,6 +23060,7 @@ class BlacklistValidator {
|
|
|
22583
23060
|
}
|
|
22584
23061
|
const omitted = new Set;
|
|
22585
23062
|
for (const path2 of blacklistedColumns) {
|
|
23063
|
+
ruleEvaluations += 1;
|
|
22586
23064
|
const present = presentByFolded.get(foldFieldPath(path2));
|
|
22587
23065
|
if (present !== undefined) {
|
|
22588
23066
|
for (const name of present)
|
|
@@ -22594,8 +23072,17 @@ class BlacklistValidator {
|
|
|
22594
23072
|
continue;
|
|
22595
23073
|
if (!nestedHeads.has(foldFieldPath(path2.slice(0, dot))))
|
|
22596
23074
|
continue;
|
|
23075
|
+
pathSplits += 1;
|
|
22597
23076
|
const segments = path2.split(".").map(foldFieldPath);
|
|
22598
|
-
|
|
23077
|
+
let found = false;
|
|
23078
|
+
for (const row of rows) {
|
|
23079
|
+
nestedProbeRows += 1;
|
|
23080
|
+
if (hasFieldPath(row, segments, PRE_FOLDED)) {
|
|
23081
|
+
found = true;
|
|
23082
|
+
break;
|
|
23083
|
+
}
|
|
23084
|
+
}
|
|
23085
|
+
if (found)
|
|
22599
23086
|
omitted.add(path2);
|
|
22600
23087
|
}
|
|
22601
23088
|
const nestedGlobs = globRules.filter((pattern) => pattern.segments.length > 1);
|
|
@@ -22606,6 +23093,7 @@ class BlacklistValidator {
|
|
|
22606
23093
|
for (const row of rows) {
|
|
22607
23094
|
if (pending.size === 0)
|
|
22608
23095
|
break;
|
|
23096
|
+
nestedGlobRows += 1;
|
|
22609
23097
|
if (row === null || typeof row !== "object")
|
|
22610
23098
|
continue;
|
|
22611
23099
|
collectNestedMatches(row, states, pending, (pattern) => {
|
|
@@ -22643,13 +23131,35 @@ class BlacklistValidator {
|
|
|
22643
23131
|
}
|
|
22644
23132
|
const omittedColumns = Array.from(omitted);
|
|
22645
23133
|
if (omittedColumns.length === 0) {
|
|
22646
|
-
return {
|
|
23134
|
+
return {
|
|
23135
|
+
filteredRows: rows,
|
|
23136
|
+
omittedColumns: [],
|
|
23137
|
+
cost: Object.freeze({
|
|
23138
|
+
rowsScanned,
|
|
23139
|
+
keysScanned,
|
|
23140
|
+
ruleEvaluations,
|
|
23141
|
+
pathSplits,
|
|
23142
|
+
nestedProbeRows,
|
|
23143
|
+
nestedGlobRows,
|
|
23144
|
+
nestedGlobMatches: matchedNested.length
|
|
23145
|
+
}),
|
|
23146
|
+
reachesOmitted: () => false
|
|
23147
|
+
};
|
|
22647
23148
|
}
|
|
22648
23149
|
const removalPaths = matchedNested.length === 0 ? omittedColumns : omittedColumns.filter((path2) => !matchedNested.some((p) => p.raw === path2));
|
|
22649
23150
|
const filteredRows = omitFieldPaths(rows, removalPaths, matchedNested.length > 0 ? { ...FOLD_CASE2, patterns: matchedNested } : FOLD_CASE2);
|
|
22650
23151
|
return {
|
|
22651
23152
|
filteredRows,
|
|
22652
23153
|
omittedColumns,
|
|
23154
|
+
cost: Object.freeze({
|
|
23155
|
+
rowsScanned,
|
|
23156
|
+
keysScanned,
|
|
23157
|
+
ruleEvaluations,
|
|
23158
|
+
pathSplits,
|
|
23159
|
+
nestedProbeRows,
|
|
23160
|
+
nestedGlobRows,
|
|
23161
|
+
nestedGlobMatches: matchedNested.length
|
|
23162
|
+
}),
|
|
22653
23163
|
reachesOmitted: (path2) => {
|
|
22654
23164
|
if (omittedByName(removalPaths, path2))
|
|
22655
23165
|
return true;
|
|
@@ -22677,7 +23187,7 @@ class BlacklistValidator {
|
|
|
22677
23187
|
});
|
|
22678
23188
|
}
|
|
22679
23189
|
}
|
|
22680
|
-
var FOLD_CASE2, PRE_FOLDED;
|
|
23190
|
+
var FOLD_CASE2, PRE_FOLDED, NO_MASKING_COST;
|
|
22681
23191
|
var init_blacklist_validator = __esm(() => {
|
|
22682
23192
|
init_glob();
|
|
22683
23193
|
init_blacklist();
|
|
@@ -22688,6 +23198,15 @@ var init_blacklist_validator = __esm(() => {
|
|
|
22688
23198
|
init_es_index_target();
|
|
22689
23199
|
FOLD_CASE2 = { caseInsensitive: true };
|
|
22690
23200
|
PRE_FOLDED = { caseInsensitive: true, alreadyFolded: true };
|
|
23201
|
+
NO_MASKING_COST = Object.freeze({
|
|
23202
|
+
rowsScanned: 0,
|
|
23203
|
+
keysScanned: 0,
|
|
23204
|
+
ruleEvaluations: 0,
|
|
23205
|
+
pathSplits: 0,
|
|
23206
|
+
nestedProbeRows: 0,
|
|
23207
|
+
nestedGlobRows: 0,
|
|
23208
|
+
nestedGlobMatches: 0
|
|
23209
|
+
});
|
|
22691
23210
|
});
|
|
22692
23211
|
|
|
22693
23212
|
// src/core/health-checker.ts
|
|
@@ -22840,7 +23359,7 @@ var init_core = __esm(() => {
|
|
|
22840
23359
|
// src/core/audit/lock.ts
|
|
22841
23360
|
import { hostname } from "os";
|
|
22842
23361
|
import { dirname as dirname7 } from "path";
|
|
22843
|
-
import { mkdir as
|
|
23362
|
+
import { mkdir as mkdir11, open as open2, rm } from "fs/promises";
|
|
22844
23363
|
|
|
22845
23364
|
class AuditLockManager {
|
|
22846
23365
|
auditFilePath;
|
|
@@ -22898,7 +23417,7 @@ class AuditLockManager {
|
|
|
22898
23417
|
}
|
|
22899
23418
|
async tryAcquireLock(operationName) {
|
|
22900
23419
|
try {
|
|
22901
|
-
await
|
|
23420
|
+
await mkdir11(dirname7(this.lockPath), { recursive: true });
|
|
22902
23421
|
const lockFile = Bun.file(this.lockPath);
|
|
22903
23422
|
if (await lockFile.exists()) {
|
|
22904
23423
|
const lockContent = await lockFile.json();
|
|
@@ -22951,7 +23470,7 @@ var init_rotation = __esm(() => {
|
|
|
22951
23470
|
});
|
|
22952
23471
|
|
|
22953
23472
|
// src/core/audit/logger.ts
|
|
22954
|
-
import { appendFile, mkdir as
|
|
23473
|
+
import { appendFile, mkdir as mkdir12, readFile as readFile4, stat as stat2 } from "fs/promises";
|
|
22955
23474
|
import { join as join21 } from "path";
|
|
22956
23475
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
22957
23476
|
|
|
@@ -23000,7 +23519,7 @@ class AuditLogger {
|
|
|
23000
23519
|
try {
|
|
23001
23520
|
const sessionId = await this.sessionIdService.resolve();
|
|
23002
23521
|
this.cachedSessionId = sessionId;
|
|
23003
|
-
await
|
|
23522
|
+
await mkdir12(this.auditDir, { recursive: true });
|
|
23004
23523
|
if (!this.writerInitialized) {
|
|
23005
23524
|
await this.syncCountersFromDisk();
|
|
23006
23525
|
this.writerInitialized = true;
|
|
@@ -23105,7 +23624,7 @@ var init_logger2 = __esm(() => {
|
|
|
23105
23624
|
});
|
|
23106
23625
|
|
|
23107
23626
|
// src/core/audit/session-id.ts
|
|
23108
|
-
import { mkdir as
|
|
23627
|
+
import { mkdir as mkdir13, readFile as readFile5, rename as rename5, stat as stat3, writeFile as writeFile5 } from "fs/promises";
|
|
23109
23628
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
23110
23629
|
import { dirname as dirname8, join as join22 } from "path";
|
|
23111
23630
|
function generateSessionId(pid, nowMs) {
|
|
@@ -23138,7 +23657,7 @@ async function writeSessionIdFile(storagePath, payload) {
|
|
|
23138
23657
|
const target = join22(storagePath, LAST_SESSION_ID_RELATIVE);
|
|
23139
23658
|
const tmp = `${target}.tmp`;
|
|
23140
23659
|
try {
|
|
23141
|
-
await
|
|
23660
|
+
await mkdir13(dirname8(target), { recursive: true });
|
|
23142
23661
|
await writeFile5(tmp, JSON.stringify(payload, null, 2), "utf8");
|
|
23143
23662
|
await rename5(tmp, target);
|
|
23144
23663
|
} catch {}
|
|
@@ -23259,6 +23778,18 @@ function tokenize2(sql, dialect, backslashEscapes) {
|
|
|
23259
23778
|
}
|
|
23260
23779
|
continue;
|
|
23261
23780
|
}
|
|
23781
|
+
if (dialect === "sqlite" && char === "[") {
|
|
23782
|
+
i++;
|
|
23783
|
+
let value = "";
|
|
23784
|
+
while (i < sql.length && sql[i] !== "]") {
|
|
23785
|
+
value += sql[i];
|
|
23786
|
+
i++;
|
|
23787
|
+
}
|
|
23788
|
+
if (i < sql.length)
|
|
23789
|
+
i++;
|
|
23790
|
+
tokens.push({ kind: "identifier", value, quoted: true });
|
|
23791
|
+
continue;
|
|
23792
|
+
}
|
|
23262
23793
|
if (char === '"' || char === "`") {
|
|
23263
23794
|
const quote = char;
|
|
23264
23795
|
i++;
|
|
@@ -24809,7 +25340,7 @@ var init_render_markdown = __esm(() => {
|
|
|
24809
25340
|
});
|
|
24810
25341
|
|
|
24811
25342
|
// src/core/recovery/last-envelope.ts
|
|
24812
|
-
import { writeFile as writeFile6, readFile as readFile6, rename as rename6, mkdir as
|
|
25343
|
+
import { writeFile as writeFile6, readFile as readFile6, rename as rename6, mkdir as mkdir14, stat as stat4 } from "fs/promises";
|
|
24813
25344
|
import { dirname as dirname9, join as join23 } from "path";
|
|
24814
25345
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
24815
25346
|
function sanitizeCommandSummary(argv) {
|
|
@@ -24828,7 +25359,7 @@ async function writeLastEnvelope(cwd, envelope, argv, now = () => new Date, id =
|
|
|
24828
25359
|
envelope
|
|
24829
25360
|
};
|
|
24830
25361
|
try {
|
|
24831
|
-
await
|
|
25362
|
+
await mkdir14(dirname9(target), { recursive: true });
|
|
24832
25363
|
await writeFile6(tmp, JSON.stringify(payload, null, 2), "utf8");
|
|
24833
25364
|
await rename6(tmp, target);
|
|
24834
25365
|
} catch {}
|
|
@@ -27771,7 +28302,8 @@ var init_write_gate = __esm(() => {
|
|
|
27771
28302
|
PARSER_DIALECTS = {
|
|
27772
28303
|
mysql: "MySQL",
|
|
27773
28304
|
mariadb: "MariaDB",
|
|
27774
|
-
postgresql: "Postgresql"
|
|
28305
|
+
postgresql: "Postgresql",
|
|
28306
|
+
sqlite: "Sqlite"
|
|
27775
28307
|
};
|
|
27776
28308
|
WRITE_TYPES = new Set([
|
|
27777
28309
|
"INSERT",
|
|
@@ -28081,12 +28613,6 @@ function mongoCollectionRefs(query) {
|
|
|
28081
28613
|
return [];
|
|
28082
28614
|
}
|
|
28083
28615
|
}
|
|
28084
|
-
function requireSqlConnection2(connection) {
|
|
28085
|
-
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
28086
|
-
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
28087
|
-
}
|
|
28088
|
-
return connection;
|
|
28089
|
-
}
|
|
28090
28616
|
async function queryCommand(sql, options, command) {
|
|
28091
28617
|
return executeQueryCommand(sql, options, command);
|
|
28092
28618
|
}
|
|
@@ -28438,7 +28964,7 @@ function queryAuditTarget(system, query, options) {
|
|
|
28438
28964
|
async function sqlQueryBranch(query, options, context, fieldSelection) {
|
|
28439
28965
|
const { config } = context;
|
|
28440
28966
|
const effectivePermission = context.effectivePermission ?? config.permission;
|
|
28441
|
-
const adapter = AdapterFactory.createSqlAdapter(
|
|
28967
|
+
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection(config.connection));
|
|
28442
28968
|
let executionError;
|
|
28443
28969
|
try {
|
|
28444
28970
|
await adapter.connect();
|
|
@@ -28705,6 +29231,7 @@ var init_query = __esm(() => {
|
|
|
28705
29231
|
init_query_fanout();
|
|
28706
29232
|
init_elasticsearch2();
|
|
28707
29233
|
init_slow_query_advisory();
|
|
29234
|
+
init_require_sql_connection();
|
|
28708
29235
|
ALLOWED_FORMATS3 = ["table", "json", "csv"];
|
|
28709
29236
|
defaultQueryCommandRuntime = {
|
|
28710
29237
|
loadConfig: (configPath, connectionName) => configModule.read(configPath, connectionName, { loadLayeredSchema: false }),
|
|
@@ -29857,7 +30384,7 @@ class DataExecutor {
|
|
|
29857
30384
|
return this.dbSystem;
|
|
29858
30385
|
}
|
|
29859
30386
|
getQuoteChar() {
|
|
29860
|
-
return this.dbSystem === "mysql" ? "`" : '"';
|
|
30387
|
+
return this.dbSystem === "mysql" || this.dbSystem === "mariadb" ? "`" : '"';
|
|
29861
30388
|
}
|
|
29862
30389
|
checkBlacklist(operation, tableName2) {
|
|
29863
30390
|
this.blacklistValidator?.checkTableBlacklist(operation, tableName2, []);
|
|
@@ -30847,12 +31374,6 @@ __export(exports_insert, {
|
|
|
30847
31374
|
insertCommand: () => insertCommand
|
|
30848
31375
|
});
|
|
30849
31376
|
import crypto5 from "crypto";
|
|
30850
|
-
function requireSqlConnection3(connection) {
|
|
30851
|
-
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
30852
|
-
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
30853
|
-
}
|
|
30854
|
-
return connection;
|
|
30855
|
-
}
|
|
30856
31377
|
async function readStdinJSON() {
|
|
30857
31378
|
try {
|
|
30858
31379
|
if (typeof Bun !== "undefined" && Bun.stdin) {
|
|
@@ -31034,11 +31555,11 @@ async function insertCommand(table, options, command) {
|
|
|
31034
31555
|
await adapter2.disconnect();
|
|
31035
31556
|
}
|
|
31036
31557
|
}
|
|
31037
|
-
const adapter = AdapterFactory.createSqlAdapter(
|
|
31558
|
+
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection(config.connection));
|
|
31038
31559
|
await adapter.connect();
|
|
31039
31560
|
try {
|
|
31040
31561
|
const schema = await adapter.getTableSchema(table);
|
|
31041
|
-
const dbSystem = config.connection.system
|
|
31562
|
+
const dbSystem = config.connection.system;
|
|
31042
31563
|
const blacklistManager = new BlacklistManager(config);
|
|
31043
31564
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
31044
31565
|
const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
|
|
@@ -31115,6 +31636,7 @@ var init_insert = __esm(() => {
|
|
|
31115
31636
|
init_dml_plan();
|
|
31116
31637
|
init_dml_plan4();
|
|
31117
31638
|
init_integration_helper();
|
|
31639
|
+
init_require_sql_connection();
|
|
31118
31640
|
});
|
|
31119
31641
|
|
|
31120
31642
|
// src/commands/update.ts
|
|
@@ -31123,12 +31645,6 @@ __export(exports_update, {
|
|
|
31123
31645
|
updateCommand: () => updateCommand
|
|
31124
31646
|
});
|
|
31125
31647
|
import crypto6 from "crypto";
|
|
31126
|
-
function requireSqlConnection4(connection) {
|
|
31127
|
-
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
31128
|
-
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
31129
|
-
}
|
|
31130
|
-
return connection;
|
|
31131
|
-
}
|
|
31132
31648
|
async function updateCommand(table, options, command) {
|
|
31133
31649
|
let config;
|
|
31134
31650
|
try {
|
|
@@ -31342,7 +31858,7 @@ async function updateCommand(table, options, command) {
|
|
|
31342
31858
|
} catch (error) {
|
|
31343
31859
|
throw new Error(`WHERE clause parsing failed: ${error.message}`);
|
|
31344
31860
|
}
|
|
31345
|
-
const adapter = AdapterFactory.createSqlAdapter(
|
|
31861
|
+
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection(config.connection));
|
|
31346
31862
|
await adapter.connect();
|
|
31347
31863
|
try {
|
|
31348
31864
|
const schema = await adapter.getTableSchema(table);
|
|
@@ -31360,7 +31876,7 @@ async function updateCommand(table, options, command) {
|
|
|
31360
31876
|
await writeAuditEntry(config, "update", options, auditOutcomeForMutation(cancelled, table));
|
|
31361
31877
|
return;
|
|
31362
31878
|
}
|
|
31363
|
-
const dbSystem = config.connection.system
|
|
31879
|
+
const dbSystem = config.connection.system;
|
|
31364
31880
|
const blacklistManager = new BlacklistManager(config);
|
|
31365
31881
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
31366
31882
|
const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
|
|
@@ -31441,6 +31957,7 @@ var init_update = __esm(() => {
|
|
|
31441
31957
|
init_config_path();
|
|
31442
31958
|
init_dml_plan4();
|
|
31443
31959
|
init_integration_helper();
|
|
31960
|
+
init_require_sql_connection();
|
|
31444
31961
|
});
|
|
31445
31962
|
|
|
31446
31963
|
// src/commands/delete.ts
|
|
@@ -31449,12 +31966,6 @@ __export(exports_delete, {
|
|
|
31449
31966
|
deleteCommand: () => deleteCommand
|
|
31450
31967
|
});
|
|
31451
31968
|
import crypto7 from "crypto";
|
|
31452
|
-
function requireSqlConnection5(connection) {
|
|
31453
|
-
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
31454
|
-
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
31455
|
-
}
|
|
31456
|
-
return connection;
|
|
31457
|
-
}
|
|
31458
31969
|
async function deleteCommand(table, options, command) {
|
|
31459
31970
|
let config;
|
|
31460
31971
|
try {
|
|
@@ -31642,7 +32153,7 @@ async function deleteCommand(table, options, command) {
|
|
|
31642
32153
|
} catch (error) {
|
|
31643
32154
|
throw new Error(`WHERE clause parsing failed: ${error.message}`);
|
|
31644
32155
|
}
|
|
31645
|
-
const adapter = AdapterFactory.createSqlAdapter(
|
|
32156
|
+
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection(config.connection));
|
|
31646
32157
|
await adapter.connect();
|
|
31647
32158
|
try {
|
|
31648
32159
|
const schema = await adapter.getTableSchema(table);
|
|
@@ -31660,7 +32171,7 @@ async function deleteCommand(table, options, command) {
|
|
|
31660
32171
|
await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(cancelled, table));
|
|
31661
32172
|
return;
|
|
31662
32173
|
}
|
|
31663
|
-
const dbSystem = config.connection.system
|
|
32174
|
+
const dbSystem = config.connection.system;
|
|
31664
32175
|
const blacklistManager = new BlacklistManager(config);
|
|
31665
32176
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
31666
32177
|
const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
|
|
@@ -31741,6 +32252,7 @@ var init_delete = __esm(() => {
|
|
|
31741
32252
|
init_config_path();
|
|
31742
32253
|
init_dml_plan4();
|
|
31743
32254
|
init_integration_helper();
|
|
32255
|
+
init_require_sql_connection();
|
|
31744
32256
|
});
|
|
31745
32257
|
|
|
31746
32258
|
// src/adapters/elasticsearch/scroll-reader.ts
|
|
@@ -31783,12 +32295,6 @@ __export(exports_export, {
|
|
|
31783
32295
|
buildEsExportRows: () => buildEsExportRows
|
|
31784
32296
|
});
|
|
31785
32297
|
import crypto8 from "crypto";
|
|
31786
|
-
function requireSqlConnection6(connection) {
|
|
31787
|
-
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
31788
|
-
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
31789
|
-
}
|
|
31790
|
-
return connection;
|
|
31791
|
-
}
|
|
31792
32298
|
async function exportCommand(sql, options, command) {
|
|
31793
32299
|
let config;
|
|
31794
32300
|
try {
|
|
@@ -31819,7 +32325,7 @@ async function exportCommand(sql, options, command) {
|
|
|
31819
32325
|
if (options.format === "jsonl") {
|
|
31820
32326
|
throw new Error("--format jsonl is only supported on MongoDB connections");
|
|
31821
32327
|
}
|
|
31822
|
-
const adapter = AdapterFactory.createSqlAdapter(
|
|
32328
|
+
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection(config.connection));
|
|
31823
32329
|
await adapter.connect();
|
|
31824
32330
|
const blacklistValidator = new BlacklistValidator(new BlacklistManager(config));
|
|
31825
32331
|
const executor3 = new QueryExecutor(adapter, config.permission, blacklistValidator, config, {
|
|
@@ -32179,6 +32685,7 @@ var init_export = __esm(() => {
|
|
|
32179
32685
|
init_field_masker();
|
|
32180
32686
|
init_scroll_reader();
|
|
32181
32687
|
init_collection_references();
|
|
32688
|
+
init_require_sql_connection();
|
|
32182
32689
|
SQL_PATTERN = /^\s*(SELECT|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|SHOW|DESCRIBE)\b/i;
|
|
32183
32690
|
});
|
|
32184
32691
|
|
|
@@ -33398,15 +33905,9 @@ import { homedir as homedir2 } from "os";
|
|
|
33398
33905
|
function looksLikeDbcliSkill(content) {
|
|
33399
33906
|
return SKILL_SENTINELS.some((sentinel) => content.includes(sentinel));
|
|
33400
33907
|
}
|
|
33401
|
-
function resolveSkillSource(lang) {
|
|
33402
|
-
if (lang === "zh-TW")
|
|
33403
|
-
return packageAssetPath("SKILL.zh-TW.md");
|
|
33404
|
-
return packageAssetPath("SKILL.md");
|
|
33405
|
-
}
|
|
33406
33908
|
async function skillCommand(program2, options) {
|
|
33407
33909
|
try {
|
|
33408
|
-
const
|
|
33409
|
-
const skillSourcePath = resolveSkillSource(lang);
|
|
33910
|
+
const skillSourcePath = packageAssetPath("SKILL.md");
|
|
33410
33911
|
const skillFile = Bun.file(skillSourcePath);
|
|
33411
33912
|
if (!await skillFile.exists()) {
|
|
33412
33913
|
throw new Error(`Skill source not found: ${skillSourcePath}`);
|
|
@@ -33444,21 +33945,17 @@ async function skillCommand(program2, options) {
|
|
|
33444
33945
|
async function checkSkillUpdates() {
|
|
33445
33946
|
const outdated = [];
|
|
33446
33947
|
try {
|
|
33447
|
-
const
|
|
33448
|
-
|
|
33449
|
-
const sourceFile = Bun.file(resolveSkillSource(lang));
|
|
33450
|
-
if (await sourceFile.exists())
|
|
33451
|
-
sourceContents.push(await sourceFile.text());
|
|
33452
|
-
}
|
|
33453
|
-
if (sourceContents.length === 0)
|
|
33948
|
+
const sourceFile = Bun.file(packageAssetPath("SKILL.md"));
|
|
33949
|
+
if (!await sourceFile.exists())
|
|
33454
33950
|
return [];
|
|
33951
|
+
const sourceContent = await sourceFile.text();
|
|
33455
33952
|
for (const platform of SUPPORTED_PLATFORMS) {
|
|
33456
33953
|
try {
|
|
33457
33954
|
const installPath = getInstallPath(platform);
|
|
33458
33955
|
const installedFile = Bun.file(installPath);
|
|
33459
33956
|
if (await installedFile.exists()) {
|
|
33460
33957
|
const installedContent = await installedFile.text();
|
|
33461
|
-
if (looksLikeDbcliSkill(installedContent) &&
|
|
33958
|
+
if (looksLikeDbcliSkill(installedContent) && installedContent !== sourceContent) {
|
|
33462
33959
|
outdated.push(platform);
|
|
33463
33960
|
}
|
|
33464
33961
|
}
|
|
@@ -33527,15 +34024,15 @@ async function ensureDir(dirPath) {
|
|
|
33527
34024
|
await $`mkdir -p ${dirPath}`.quiet();
|
|
33528
34025
|
} catch {
|
|
33529
34026
|
try {
|
|
33530
|
-
const { mkdir:
|
|
33531
|
-
await
|
|
34027
|
+
const { mkdir: mkdir15 } = await import("fs/promises");
|
|
34028
|
+
await mkdir15(dirPath, { recursive: true });
|
|
33532
34029
|
} catch {
|
|
33533
34030
|
throw new Error(`Cannot create directory: ${dirPath}`);
|
|
33534
34031
|
}
|
|
33535
34032
|
}
|
|
33536
34033
|
}
|
|
33537
34034
|
function registerSkillCommand(program2) {
|
|
33538
|
-
const skill = program2.command("skill").description(t("skill.description")).option("--install <platform>", "Install to platform directory (claude, gemini, antigravity, copilot, cursor, codex, windsurf)").option("--output <path>", "Write skill to file instead of stdout").
|
|
34035
|
+
const skill = program2.command("skill").description(t("skill.description")).option("--install <platform>", "Install to platform directory (claude, gemini, antigravity, copilot, cursor, codex, windsurf)").option("--output <path>", "Write skill to file instead of stdout").action(async (options) => {
|
|
33539
34036
|
try {
|
|
33540
34037
|
await skillCommand(program2, options);
|
|
33541
34038
|
} catch (error) {
|
|
@@ -33584,7 +34081,6 @@ var REFERENCE_SOURCE_PATH, SKILL_SENTINELS, SUPPORTED_PLATFORMS;
|
|
|
33584
34081
|
var init_skill = __esm(() => {
|
|
33585
34082
|
init_message_loader();
|
|
33586
34083
|
init_package_root();
|
|
33587
|
-
init_esm();
|
|
33588
34084
|
init_context();
|
|
33589
34085
|
init_context_v2();
|
|
33590
34086
|
init_config_path();
|
|
@@ -35367,7 +35863,7 @@ var exports_check = {};
|
|
|
35367
35863
|
__export(exports_check, {
|
|
35368
35864
|
checkCommand: () => checkCommand
|
|
35369
35865
|
});
|
|
35370
|
-
function
|
|
35866
|
+
function requireSqlConnection2(connection) {
|
|
35371
35867
|
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
35372
35868
|
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
35373
35869
|
}
|
|
@@ -35379,7 +35875,7 @@ async function checkAction(table, options) {
|
|
|
35379
35875
|
if (!config.connection) {
|
|
35380
35876
|
throw new Error("Database not configured. Run: dbcli init");
|
|
35381
35877
|
}
|
|
35382
|
-
const adapter = AdapterFactory.createSqlAdapter(
|
|
35878
|
+
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection2(config.connection));
|
|
35383
35879
|
await adapter.connect();
|
|
35384
35880
|
try {
|
|
35385
35881
|
const checker = new HealthChecker(adapter);
|
|
@@ -37209,7 +37705,8 @@ var init_ddl = __esm(() => {
|
|
|
37209
37705
|
DIALECT3 = {
|
|
37210
37706
|
mysql: "MySQL",
|
|
37211
37707
|
mariadb: "MariaDB",
|
|
37212
|
-
postgresql: "Postgresql"
|
|
37708
|
+
postgresql: "Postgresql",
|
|
37709
|
+
sqlite: "Sqlite"
|
|
37213
37710
|
};
|
|
37214
37711
|
});
|
|
37215
37712
|
|
|
@@ -37912,7 +38409,7 @@ __export(exports_diff, {
|
|
|
37912
38409
|
ORM_ALIASES: () => ORM_ALIASES
|
|
37913
38410
|
});
|
|
37914
38411
|
import crypto9 from "crypto";
|
|
37915
|
-
function
|
|
38412
|
+
function requireSqlConnection3(connection) {
|
|
37916
38413
|
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
37917
38414
|
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
37918
38415
|
}
|
|
@@ -38079,7 +38576,7 @@ async function diffAction(options) {
|
|
|
38079
38576
|
console.error("Elasticsearch \u4E0D\u652F\u63F4 diff \u6307\u4EE4\uFF1B\u5982\u9700\u8FFD\u8E64 mapping \u8B8A\u5316\uFF0C\u8ACB\u6539\u7528 schema \u4E26\u81EA\u884C\u7248\u672C\u5316 mapping JSON");
|
|
38080
38577
|
process.exit(1);
|
|
38081
38578
|
}
|
|
38082
|
-
const adapter = AdapterFactory.createSqlAdapter(
|
|
38579
|
+
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection3(config.connection));
|
|
38083
38580
|
await adapter.connect();
|
|
38084
38581
|
try {
|
|
38085
38582
|
const tables = await adapter.listTables();
|
|
@@ -38205,9 +38702,11 @@ var init_status2 = __esm(() => {
|
|
|
38205
38702
|
const blacklistTables = config.blacklist?.tables ?? [];
|
|
38206
38703
|
const blacklistColumns = config.blacklist?.columns ?? {};
|
|
38207
38704
|
const columnCount = Object.values(blacklistColumns).reduce((sum, cols) => sum + cols.length, 0);
|
|
38705
|
+
const file = config.connection.file;
|
|
38208
38706
|
const status2 = {
|
|
38209
38707
|
permission: config.permission,
|
|
38210
38708
|
system: config.connection.system,
|
|
38709
|
+
...file !== undefined && { file },
|
|
38211
38710
|
blacklist: {
|
|
38212
38711
|
tables: blacklistTables.length,
|
|
38213
38712
|
columns: columnCount
|
|
@@ -38217,6 +38716,8 @@ var init_status2 = __esm(() => {
|
|
|
38217
38716
|
if (options.format === "text") {
|
|
38218
38717
|
console.log(`Permission: ${status2.permission}`);
|
|
38219
38718
|
console.log(`System: ${status2.system}`);
|
|
38719
|
+
if (file !== undefined)
|
|
38720
|
+
console.log(`File: ${file}`);
|
|
38220
38721
|
console.log(`Blacklist: ${status2.blacklist.tables} table(s), ${status2.blacklist.columns} column(s)`);
|
|
38221
38722
|
console.log(`Version: ${status2.version}`);
|
|
38222
38723
|
} else {
|
|
@@ -39953,7 +40454,8 @@ var init_parse_sql = __esm(() => {
|
|
|
39953
40454
|
DIALECT4 = {
|
|
39954
40455
|
mysql: "MySQL",
|
|
39955
40456
|
mariadb: "MariaDB",
|
|
39956
|
-
postgresql: "Postgresql"
|
|
40457
|
+
postgresql: "Postgresql",
|
|
40458
|
+
sqlite: "Sqlite"
|
|
39957
40459
|
};
|
|
39958
40460
|
});
|
|
39959
40461
|
|
|
@@ -41833,18 +42335,14 @@ __export(exports_doctor, {
|
|
|
41833
42335
|
resolveSchemaLastUpdated: () => resolveSchemaLastUpdated,
|
|
41834
42336
|
renderMongoSamplingLine: () => renderMongoSamplingLine,
|
|
41835
42337
|
doctorCommand: () => doctorCommand,
|
|
42338
|
+
collectSQLiteDoctorResults: () => collectSQLiteDoctorResults,
|
|
41836
42339
|
collectMongoDoctorResults: () => collectMongoDoctorResults,
|
|
41837
42340
|
collectElasticsearchDoctorResults: () => collectElasticsearchDoctorResults,
|
|
41838
42341
|
buildDoctorRemediationPlan: () => buildDoctorRemediationPlan
|
|
41839
42342
|
});
|
|
41840
42343
|
import { join as join37 } from "path";
|
|
41841
42344
|
import { resolveSrv as resolveSrv2 } from "dns/promises";
|
|
41842
|
-
|
|
41843
|
-
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
41844
|
-
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
41845
|
-
}
|
|
41846
|
-
return connection;
|
|
41847
|
-
}
|
|
42345
|
+
import { access, constants } from "fs/promises";
|
|
41848
42346
|
function renderMongoSamplingLine(meta) {
|
|
41849
42347
|
if (!meta.sampleMethod)
|
|
41850
42348
|
return "";
|
|
@@ -42084,6 +42582,46 @@ async function collectElasticsearchDoctorResults(config, runtimeOverrides = {})
|
|
|
42084
42582
|
}
|
|
42085
42583
|
return results;
|
|
42086
42584
|
}
|
|
42585
|
+
async function collectSQLiteDoctorResults(config, runtimeOverrides = {}) {
|
|
42586
|
+
const results = [];
|
|
42587
|
+
const file = config.connection.file ?? "";
|
|
42588
|
+
const fileResults = await runDoctorChecks.checkSQLiteFile(file, config.permission ?? "query-only");
|
|
42589
|
+
results.push(...fileResults);
|
|
42590
|
+
if (fileResults.some((r) => r.status === "error"))
|
|
42591
|
+
return results;
|
|
42592
|
+
const adapter = AdapterFactory.createSqlAdapter(config.connection);
|
|
42593
|
+
try {
|
|
42594
|
+
await adapter.connect();
|
|
42595
|
+
results.push({
|
|
42596
|
+
group: "Connection & Data",
|
|
42597
|
+
label: "Connection",
|
|
42598
|
+
status: "pass",
|
|
42599
|
+
message: `Connected to sqlite ${file}`
|
|
42600
|
+
});
|
|
42601
|
+
try {
|
|
42602
|
+
const tables = await adapter.listTables();
|
|
42603
|
+
const tableColumns = new Map;
|
|
42604
|
+
for (const t2 of tables) {
|
|
42605
|
+
tableColumns.set(t2.name, t2.columns.map((c) => c.name));
|
|
42606
|
+
}
|
|
42607
|
+
if (config.blacklistedColumns) {
|
|
42608
|
+
results.push(runDoctorChecks.checkBlacklistCompleteness(tableColumns, config.blacklistedColumns));
|
|
42609
|
+
}
|
|
42610
|
+
results.push(runDoctorChecks.checkLargeTables(tables, "sqlite"));
|
|
42611
|
+
} catch {}
|
|
42612
|
+
results.push(runDoctorChecks.checkSchemaCacheFreshness(config.metadata?.schemaLastUpdated ?? null));
|
|
42613
|
+
} catch (error) {
|
|
42614
|
+
results.push({
|
|
42615
|
+
group: "Connection & Data",
|
|
42616
|
+
label: "Connection",
|
|
42617
|
+
status: "error",
|
|
42618
|
+
message: `Connection failed: ${error.message}`
|
|
42619
|
+
});
|
|
42620
|
+
} finally {
|
|
42621
|
+
await adapter.disconnect();
|
|
42622
|
+
}
|
|
42623
|
+
return results;
|
|
42624
|
+
}
|
|
42087
42625
|
var ALLOWED_FORMATS15, SENSITIVE_PATTERNS, runDoctorChecks, defaultDoctorCollectorRuntime, doctorCommand;
|
|
42088
42626
|
var init_doctor = __esm(() => {
|
|
42089
42627
|
init_esm();
|
|
@@ -42103,6 +42641,7 @@ var init_doctor = __esm(() => {
|
|
|
42103
42641
|
init_runtime_info();
|
|
42104
42642
|
init_config();
|
|
42105
42643
|
init_integration_helper();
|
|
42644
|
+
init_require_sql_connection();
|
|
42106
42645
|
ALLOWED_FORMATS15 = ["text", "json"];
|
|
42107
42646
|
SENSITIVE_PATTERNS = [
|
|
42108
42647
|
"password",
|
|
@@ -42336,6 +42875,83 @@ var init_doctor = __esm(() => {
|
|
|
42336
42875
|
}
|
|
42337
42876
|
}
|
|
42338
42877
|
},
|
|
42878
|
+
async checkSQLiteFile(file, permission) {
|
|
42879
|
+
const group = "Connection & Data";
|
|
42880
|
+
if (!file) {
|
|
42881
|
+
return [
|
|
42882
|
+
{
|
|
42883
|
+
group,
|
|
42884
|
+
label: "Database file",
|
|
42885
|
+
status: "error",
|
|
42886
|
+
message: 'Connection has no file path \u2014 run "dbcli init --system sqlite"'
|
|
42887
|
+
}
|
|
42888
|
+
];
|
|
42889
|
+
}
|
|
42890
|
+
try {
|
|
42891
|
+
await access(file, constants.F_OK);
|
|
42892
|
+
} catch {
|
|
42893
|
+
return [
|
|
42894
|
+
{
|
|
42895
|
+
group,
|
|
42896
|
+
label: "Database file",
|
|
42897
|
+
status: "error",
|
|
42898
|
+
message: `Database file not found: ${file}`,
|
|
42899
|
+
details: { file }
|
|
42900
|
+
}
|
|
42901
|
+
];
|
|
42902
|
+
}
|
|
42903
|
+
try {
|
|
42904
|
+
await access(file, constants.R_OK);
|
|
42905
|
+
} catch {
|
|
42906
|
+
return [
|
|
42907
|
+
{
|
|
42908
|
+
group,
|
|
42909
|
+
label: "Database file",
|
|
42910
|
+
status: "error",
|
|
42911
|
+
message: `Database file is not readable: ${file}`,
|
|
42912
|
+
details: { file }
|
|
42913
|
+
}
|
|
42914
|
+
];
|
|
42915
|
+
}
|
|
42916
|
+
const results = [
|
|
42917
|
+
{
|
|
42918
|
+
group,
|
|
42919
|
+
label: "Database file",
|
|
42920
|
+
status: "pass",
|
|
42921
|
+
message: `Readable: ${file}`,
|
|
42922
|
+
details: { file }
|
|
42923
|
+
}
|
|
42924
|
+
];
|
|
42925
|
+
if (permission === "query-only") {
|
|
42926
|
+
results.push({
|
|
42927
|
+
group,
|
|
42928
|
+
label: "Database file writable",
|
|
42929
|
+
status: "pass",
|
|
42930
|
+
message: "Not required \u2014 the connection is query-only",
|
|
42931
|
+
details: { file, permission }
|
|
42932
|
+
});
|
|
42933
|
+
return results;
|
|
42934
|
+
}
|
|
42935
|
+
try {
|
|
42936
|
+
await access(file, constants.W_OK);
|
|
42937
|
+
results.push({
|
|
42938
|
+
group,
|
|
42939
|
+
label: "Database file writable",
|
|
42940
|
+
status: "pass",
|
|
42941
|
+
message: `Writable: ${file}`,
|
|
42942
|
+
details: { file, permission }
|
|
42943
|
+
});
|
|
42944
|
+
} catch {
|
|
42945
|
+
results.push({
|
|
42946
|
+
group,
|
|
42947
|
+
label: "Database file writable",
|
|
42948
|
+
status: "error",
|
|
42949
|
+
message: `Database file is not writable, but permission '${permission}' allows writes: ${file}`,
|
|
42950
|
+
details: { file, permission }
|
|
42951
|
+
});
|
|
42952
|
+
}
|
|
42953
|
+
return results;
|
|
42954
|
+
},
|
|
42339
42955
|
checkLargeTables(tables, system = "postgresql") {
|
|
42340
42956
|
const large = tables.filter((t2) => (t2.estimatedRowCount ?? 0) > 1e6);
|
|
42341
42957
|
if (large.length === 0) {
|
|
@@ -42482,13 +43098,18 @@ var init_doctor = __esm(() => {
|
|
|
42482
43098
|
...config,
|
|
42483
43099
|
blacklistedColumns
|
|
42484
43100
|
}));
|
|
43101
|
+
} else if (config.connection.system === "sqlite") {
|
|
43102
|
+
results.push(...await collectSQLiteDoctorResults({
|
|
43103
|
+
...config,
|
|
43104
|
+
blacklistedColumns
|
|
43105
|
+
}));
|
|
42485
43106
|
} else if (config.connection.system === "elasticsearch") {
|
|
42486
43107
|
results.push(...await collectElasticsearchDoctorResults({
|
|
42487
43108
|
...config,
|
|
42488
43109
|
blacklistedColumns
|
|
42489
43110
|
}));
|
|
42490
43111
|
} else {
|
|
42491
|
-
const adapter = AdapterFactory.createSqlAdapter(
|
|
43112
|
+
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection(config.connection));
|
|
42492
43113
|
await adapter.connect();
|
|
42493
43114
|
results.push({
|
|
42494
43115
|
group: "Connection & Data",
|
|
@@ -42804,7 +43425,7 @@ var exports_queries_rename = {};
|
|
|
42804
43425
|
__export(exports_queries_rename, {
|
|
42805
43426
|
queriesRename: () => queriesRename
|
|
42806
43427
|
});
|
|
42807
|
-
import { rename as rename7, mkdir as
|
|
43428
|
+
import { rename as rename7, mkdir as mkdir15 } from "fs/promises";
|
|
42808
43429
|
import { dirname as dirname12 } from "path";
|
|
42809
43430
|
async function queriesRename(oldName, newName, options = {}) {
|
|
42810
43431
|
if (!oldName.startsWith("@") || !newName.startsWith("@")) {
|
|
@@ -42826,7 +43447,7 @@ async function queriesRename(oldName, newName, options = {}) {
|
|
|
42826
43447
|
for (const v of local) {
|
|
42827
43448
|
const dst = snippetKeyToFile(cwd, newName, "local");
|
|
42828
43449
|
const dstWithSuffix = preserveEngineSuffix(v.query.file, dst);
|
|
42829
|
-
await
|
|
43450
|
+
await mkdir15(dirname12(dstWithSuffix), { recursive: true });
|
|
42830
43451
|
await rename7(v.query.file, dstWithSuffix);
|
|
42831
43452
|
await rewriteFrontmatterName(dstWithSuffix, newName.slice(1));
|
|
42832
43453
|
console.log(`renamed ${v.query.file} \u2192 ${dstWithSuffix}`);
|
|
@@ -42852,7 +43473,7 @@ var exports_queries_copy = {};
|
|
|
42852
43473
|
__export(exports_queries_copy, {
|
|
42853
43474
|
queriesCopy: () => queriesCopy
|
|
42854
43475
|
});
|
|
42855
|
-
import { mkdir as
|
|
43476
|
+
import { mkdir as mkdir16, copyFile } from "fs/promises";
|
|
42856
43477
|
import { dirname as dirname13, basename as basename5 } from "path";
|
|
42857
43478
|
async function queriesCopy(src, dst, options = {}) {
|
|
42858
43479
|
if (!src.startsWith("@") || !dst.startsWith("@")) {
|
|
@@ -42871,7 +43492,7 @@ async function queriesCopy(src, dst, options = {}) {
|
|
|
42871
43492
|
}
|
|
42872
43493
|
for (const v of variants) {
|
|
42873
43494
|
const dstFile = mapEngineSuffix(v.query.file, snippetKeyToFile(cwd, dst, "local"));
|
|
42874
|
-
await
|
|
43495
|
+
await mkdir16(dirname13(dstFile), { recursive: true });
|
|
42875
43496
|
await copyFile(v.query.file, dstFile);
|
|
42876
43497
|
console.log(`copied ${v.query.file} \u2192 ${dstFile}`);
|
|
42877
43498
|
}
|
|
@@ -42889,7 +43510,7 @@ var exports_queries_import = {};
|
|
|
42889
43510
|
__export(exports_queries_import, {
|
|
42890
43511
|
queriesImport: () => queriesImport
|
|
42891
43512
|
});
|
|
42892
|
-
import { stat as stat8, mkdir as
|
|
43513
|
+
import { stat as stat8, mkdir as mkdir17, copyFile as copyFile2 } from "fs/promises";
|
|
42893
43514
|
import { basename as basename6, join as join38, extname as extname2 } from "path";
|
|
42894
43515
|
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
42895
43516
|
async function queriesImport(filePath, options = {}) {
|
|
@@ -42903,7 +43524,7 @@ async function queriesImport(filePath, options = {}) {
|
|
|
42903
43524
|
const key = "@" + baseName;
|
|
42904
43525
|
parseSavedQuery({ key, file: filePath, source: "local", text: text4 });
|
|
42905
43526
|
const targetDir = join38(cwd, ".dbcli/queries");
|
|
42906
|
-
await
|
|
43527
|
+
await mkdir17(targetDir, { recursive: true });
|
|
42907
43528
|
const target = join38(targetDir, basename6(filePath));
|
|
42908
43529
|
if (await Bun.file(target).exists()) {
|
|
42909
43530
|
if (!options.force) {
|
|
@@ -42968,7 +43589,7 @@ __export(exports_queries, {
|
|
|
42968
43589
|
queriesCheck: () => queriesCheck,
|
|
42969
43590
|
listSnippetsForEngine: () => listSnippetsForEngine
|
|
42970
43591
|
});
|
|
42971
|
-
import { mkdir as
|
|
43592
|
+
import { mkdir as mkdir18, writeFile as writeFile8 } from "fs/promises";
|
|
42972
43593
|
import { dirname as dirname14 } from "path";
|
|
42973
43594
|
import { spawn } from "child_process";
|
|
42974
43595
|
async function deriveEngine(command) {
|
|
@@ -43089,7 +43710,7 @@ async function queriesNew(name, options) {
|
|
|
43089
43710
|
process.exit(1);
|
|
43090
43711
|
return;
|
|
43091
43712
|
}
|
|
43092
|
-
await
|
|
43713
|
+
await mkdir18(dirname14(file), { recursive: true });
|
|
43093
43714
|
await writeFile8(file, scaffold(name), "utf8");
|
|
43094
43715
|
console.log(`Created ${file}`);
|
|
43095
43716
|
if (source === "shared")
|
|
@@ -43165,7 +43786,7 @@ async function queriesSearch(keywords, options, command) {
|
|
|
43165
43786
|
process.exit(2);
|
|
43166
43787
|
return;
|
|
43167
43788
|
}
|
|
43168
|
-
const allowed = [
|
|
43789
|
+
const allowed = [...ENGINE_TAGS, "all"];
|
|
43169
43790
|
if (options.engine && !allowed.includes(options.engine)) {
|
|
43170
43791
|
console.error(`Unknown engine '${options.engine}'. Allowed: ${allowed.join(", ")}.`);
|
|
43171
43792
|
process.exit(2);
|
|
@@ -43257,7 +43878,7 @@ async function queriesSuggest(intent, options, command) {
|
|
|
43257
43878
|
process.exit(2);
|
|
43258
43879
|
return;
|
|
43259
43880
|
}
|
|
43260
|
-
const allowed = [
|
|
43881
|
+
const allowed = [...ENGINE_TAGS, "all"];
|
|
43261
43882
|
if (options.engine && !allowed.includes(options.engine)) {
|
|
43262
43883
|
console.error(`Unknown engine '${options.engine}'. Allowed: ${allowed.join(", ")}.`);
|
|
43263
43884
|
process.exit(2);
|
|
@@ -43866,7 +44487,8 @@ var init_parse = __esm(() => {
|
|
|
43866
44487
|
DIALECT5 = {
|
|
43867
44488
|
mysql: "MySQL",
|
|
43868
44489
|
mariadb: "MariaDB",
|
|
43869
|
-
postgresql: "Postgresql"
|
|
44490
|
+
postgresql: "Postgresql",
|
|
44491
|
+
sqlite: "Sqlite"
|
|
43870
44492
|
};
|
|
43871
44493
|
});
|
|
43872
44494
|
|
|
@@ -44998,12 +45620,14 @@ var init_not_in_nullable = __esm(() => {
|
|
|
44998
45620
|
"VARIANCE",
|
|
44999
45621
|
"VAR_POP",
|
|
45000
45622
|
"VAR_SAMP"
|
|
45001
|
-
])
|
|
45623
|
+
]),
|
|
45624
|
+
sqlite: new Set([...COMMON_NULL_ON_EMPTY_AGGREGATES, "GROUP_CONCAT", "STRING_AGG"])
|
|
45002
45625
|
};
|
|
45003
45626
|
NON_NULL_AGGREGATES = {
|
|
45004
45627
|
postgresql: new Set(["COUNT", "REGR_COUNT"]),
|
|
45005
45628
|
mysql: new Set(["COUNT", "BIT_AND", "BIT_OR", "BIT_XOR"]),
|
|
45006
|
-
mariadb: new Set(["COUNT", "BIT_AND", "BIT_OR", "BIT_XOR"])
|
|
45629
|
+
mariadb: new Set(["COUNT", "BIT_AND", "BIT_OR", "BIT_XOR"]),
|
|
45630
|
+
sqlite: new Set(["COUNT", "TOTAL"])
|
|
45007
45631
|
};
|
|
45008
45632
|
notInNullableRule = {
|
|
45009
45633
|
name: "not-in-nullable",
|
|
@@ -45866,7 +46490,7 @@ __export(exports_snapshot, {
|
|
|
45866
46490
|
snapshotCommand: () => snapshotCommand
|
|
45867
46491
|
});
|
|
45868
46492
|
import { join as join39 } from "path";
|
|
45869
|
-
function
|
|
46493
|
+
function requireSqlConnection4(connection) {
|
|
45870
46494
|
if (!SQL_SYSTEMS6.includes(connection.system)) {
|
|
45871
46495
|
throw new Error(`snapshot currently supports SQL engines only, got: ${connection.system}`);
|
|
45872
46496
|
}
|
|
@@ -45914,7 +46538,7 @@ var init_snapshot = __esm(() => {
|
|
|
45914
46538
|
const snippets = await loadSnippets(dirs);
|
|
45915
46539
|
sql = resolveByName(snippets, query.slice(1), engine).query.sqlBody;
|
|
45916
46540
|
}
|
|
45917
|
-
const adapter = AdapterFactory.createSqlAdapter(
|
|
46541
|
+
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection4(config.connection));
|
|
45918
46542
|
await adapter.connect();
|
|
45919
46543
|
try {
|
|
45920
46544
|
const blacklistManager = new BlacklistManager(config);
|
|
@@ -46150,7 +46774,7 @@ __export(exports_assert, {
|
|
|
46150
46774
|
assertCommand: () => assertCommand
|
|
46151
46775
|
});
|
|
46152
46776
|
import { basename as basename7 } from "path";
|
|
46153
|
-
function
|
|
46777
|
+
function requireSqlConnection5(connection) {
|
|
46154
46778
|
if (!SQL_SYSTEMS7.includes(connection.system)) {
|
|
46155
46779
|
throw new Error(`assert currently supports SQL engines only, got: ${connection.system}`);
|
|
46156
46780
|
}
|
|
@@ -46205,7 +46829,7 @@ var init_assert = __esm(() => {
|
|
|
46205
46829
|
console.error("Database not configured. Run: dbcli init");
|
|
46206
46830
|
process.exit(1);
|
|
46207
46831
|
}
|
|
46208
|
-
const adapter = AdapterFactory.createSqlAdapter(
|
|
46832
|
+
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection5(config.connection));
|
|
46209
46833
|
await adapter.connect();
|
|
46210
46834
|
let auditRef = null;
|
|
46211
46835
|
let verdict2;
|
|
@@ -47590,7 +48214,7 @@ __export(exports_verify, {
|
|
|
47590
48214
|
verifyCommand: () => verifyCommand,
|
|
47591
48215
|
BUILTIN_VERIFY_SCENARIOS: () => BUILTIN_VERIFY_SCENARIOS
|
|
47592
48216
|
});
|
|
47593
|
-
function
|
|
48217
|
+
function requireSqlConnection6(connection, scenario2) {
|
|
47594
48218
|
if (!SQL_SYSTEMS8.includes(connection.system)) {
|
|
47595
48219
|
throw new Error(`verify ${scenario2} currently supports SQL engines only, got: ${connection.system}`);
|
|
47596
48220
|
}
|
|
@@ -48276,7 +48900,7 @@ async function executeScenario(def, options, command) {
|
|
|
48276
48900
|
console.error("Database not configured. Run: dbcli init");
|
|
48277
48901
|
process.exit(1);
|
|
48278
48902
|
}
|
|
48279
|
-
const adapter = AdapterFactory.createSqlAdapter(
|
|
48903
|
+
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection6(config.connection, def.name));
|
|
48280
48904
|
await adapter.connect();
|
|
48281
48905
|
const runners = def.createRunners({ adapter, config, options, targetTable: input.table }, input);
|
|
48282
48906
|
if (!input.afterWrite) {
|
|
@@ -50151,7 +50775,7 @@ __export(exports_shell, {
|
|
|
50151
50775
|
import { createInterface as createInterface2 } from "readline";
|
|
50152
50776
|
import { join as join40 } from "path";
|
|
50153
50777
|
import { homedir as homedir3 } from "os";
|
|
50154
|
-
function
|
|
50778
|
+
function requireSqlConnection7(connection) {
|
|
50155
50779
|
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
50156
50780
|
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
50157
50781
|
}
|
|
@@ -50196,7 +50820,7 @@ async function runShell(options, configPath) {
|
|
|
50196
50820
|
const connectionOpts = config.connection;
|
|
50197
50821
|
const mongoInner = isMongoDB ? AdapterFactory.createMongoDBAdapter(config) : null;
|
|
50198
50822
|
const redisInner = isRedis ? AdapterFactory.createRedisAdapter(config) : null;
|
|
50199
|
-
const adapter = isMongoDB ? new MongoShellAdapter(mongoInner) : isRedis ? new RedisShellAdapter(redisInner) : AdapterFactory.createSqlAdapter(
|
|
50823
|
+
const adapter = isMongoDB ? new MongoShellAdapter(mongoInner) : isRedis ? new RedisShellAdapter(redisInner) : AdapterFactory.createSqlAdapter(requireSqlConnection7(connectionOpts));
|
|
50200
50824
|
try {
|
|
50201
50825
|
await adapter.connect();
|
|
50202
50826
|
} catch (error) {
|
|
@@ -51011,7 +51635,7 @@ __export(exports_migrate, {
|
|
|
51011
51635
|
runDDL: () => runDDL,
|
|
51012
51636
|
migrateCommand: () => migrateCommand
|
|
51013
51637
|
});
|
|
51014
|
-
function
|
|
51638
|
+
function requireSqlConnection8(connection) {
|
|
51015
51639
|
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
51016
51640
|
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
51017
51641
|
}
|
|
@@ -51035,7 +51659,7 @@ async function runDDL(operation, opts, command) {
|
|
|
51035
51659
|
console.error("\u6B64\u547D\u4EE4\u76EE\u524D\u4E0D\u652F\u63F4 Elasticsearch\uFF1B\u5982\u9700\u5EFA\u7ACB\u7D22\u5F15\u6216\u8ABF\u6574 mapping\uFF0C\u8ACB\u6539\u7528\u5916\u90E8\u5DE5\u5177");
|
|
51036
51660
|
process.exit(1);
|
|
51037
51661
|
}
|
|
51038
|
-
const adapter = AdapterFactory.createSqlAdapter(
|
|
51662
|
+
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection8(config.connection));
|
|
51039
51663
|
const isDryRun = !opts.execute;
|
|
51040
51664
|
if (!isDryRun) {
|
|
51041
51665
|
await adapter.connect();
|
|
@@ -51277,17 +51901,23 @@ async function switchDefault(configPath, name, config, options = {}) {
|
|
|
51277
51901
|
function listConnectionsForDisplay(config) {
|
|
51278
51902
|
return Object.entries(config.connections).map(([name, conn]) => {
|
|
51279
51903
|
const marker = name === config.default ? "*" : " ";
|
|
51904
|
+
const environmentLabel = conn.environment ? ` [${conn.environment}]` : "";
|
|
51905
|
+
const file = conn.file;
|
|
51906
|
+
if (file !== undefined) {
|
|
51907
|
+
const rendered = typeof file === "object" ? `\${${file.$env}}` : file;
|
|
51908
|
+
return `${marker} ${name.padEnd(12)}${environmentLabel} ${conn.system.padEnd(12)} ${rendered}`;
|
|
51909
|
+
}
|
|
51280
51910
|
const host = typeof conn.host === "object" ? `\${${conn.host.$env}}` : conn.host;
|
|
51281
51911
|
const port = typeof conn.port === "object" ? `\${${conn.port.$env}}` : conn.port;
|
|
51282
51912
|
const db = typeof conn.database === "object" ? `\${${conn.database.$env}}` : conn.database;
|
|
51283
|
-
|
|
51284
|
-
return `${marker} ${name.padEnd(12)}${environment} ${conn.system.padEnd(12)} ${host}:${port}/${db}`;
|
|
51913
|
+
return `${marker} ${name.padEnd(12)}${environmentLabel} ${conn.system.padEnd(12)} ${host}:${port}/${db}`;
|
|
51285
51914
|
});
|
|
51286
51915
|
}
|
|
51287
51916
|
function listConnectionIdentities(config) {
|
|
51288
51917
|
return Object.entries(config.connections).map(([name, conn]) => {
|
|
51289
51918
|
const host = typeof conn.host === "string" && conn.host.length > 0 ? conn.host : null;
|
|
51290
51919
|
const database = typeof conn.database === "string" && conn.database.length > 0 ? conn.database : null;
|
|
51920
|
+
const file = conn.file;
|
|
51291
51921
|
return {
|
|
51292
51922
|
name,
|
|
51293
51923
|
environment: conn.environment ?? null,
|
|
@@ -51298,6 +51928,7 @@ function listConnectionIdentities(config) {
|
|
|
51298
51928
|
port: host !== null && typeof conn.port === "number" ? conn.port : null
|
|
51299
51929
|
},
|
|
51300
51930
|
database,
|
|
51931
|
+
...typeof file === "string" && { file },
|
|
51301
51932
|
isDefault: name === config.default
|
|
51302
51933
|
};
|
|
51303
51934
|
});
|
|
@@ -51398,7 +52029,7 @@ var init_config_v2_mutations = __esm(() => {
|
|
|
51398
52029
|
});
|
|
51399
52030
|
|
|
51400
52031
|
// src/core/env-file-writer.ts
|
|
51401
|
-
import { chmod as chmod2, mkdir as
|
|
52032
|
+
import { chmod as chmod2, mkdir as mkdir19, readFile as readFile9, writeFile as writeFile9 } from "fs/promises";
|
|
51402
52033
|
import { dirname as dirname16 } from "path";
|
|
51403
52034
|
function escapeForRegExp(value) {
|
|
51404
52035
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -51411,7 +52042,7 @@ async function upsertEnvVar(envPath, varName, value) {
|
|
|
51411
52042
|
const code = cause.code;
|
|
51412
52043
|
if (code !== "ENOENT")
|
|
51413
52044
|
throw cause;
|
|
51414
|
-
await
|
|
52045
|
+
await mkdir19(dirname16(envPath), { recursive: true });
|
|
51415
52046
|
}
|
|
51416
52047
|
const line = `${varName}="${value}"`;
|
|
51417
52048
|
const existing = new RegExp(`^${escapeForRegExp(varName)}=.*$`, "m");
|
|
@@ -51716,7 +52347,7 @@ var init_sql_metadata = __esm(() => {
|
|
|
51716
52347
|
});
|
|
51717
52348
|
|
|
51718
52349
|
// src/proxy/events.ts
|
|
51719
|
-
import { appendFile as appendFile2, mkdir as
|
|
52350
|
+
import { appendFile as appendFile2, mkdir as mkdir20, readFile as readFile10, stat as stat9 } from "fs/promises";
|
|
51720
52351
|
import { dirname as dirname17 } from "path";
|
|
51721
52352
|
function hasSql(e) {
|
|
51722
52353
|
return e.type === "query_observed" || e.type === "query_completed" || e.type === "query_errored";
|
|
@@ -51756,7 +52387,7 @@ class EventWriter {
|
|
|
51756
52387
|
}
|
|
51757
52388
|
async writeInternal(event) {
|
|
51758
52389
|
if (!this.dirEnsured) {
|
|
51759
|
-
await
|
|
52390
|
+
await mkdir20(dirname17(this.path), { recursive: true });
|
|
51760
52391
|
this.dirEnsured = true;
|
|
51761
52392
|
}
|
|
51762
52393
|
if (!this.initialized) {
|
|
@@ -54168,7 +54799,7 @@ __export(exports_backfill, {
|
|
|
54168
54799
|
backfillCommand: () => backfillCommand
|
|
54169
54800
|
});
|
|
54170
54801
|
import { dirname as dirname18, resolve as resolve12 } from "path";
|
|
54171
|
-
import { mkdir as
|
|
54802
|
+
import { mkdir as mkdir21 } from "fs/promises";
|
|
54172
54803
|
function identityFor(config, name) {
|
|
54173
54804
|
const connection = config.connections[name];
|
|
54174
54805
|
if (!connection) {
|
|
@@ -54218,7 +54849,7 @@ var init_backfill = __esm(() => {
|
|
|
54218
54849
|
return;
|
|
54219
54850
|
}
|
|
54220
54851
|
const out = resolve12(options.out ?? `.dbcli/backfills/${artifact2.source.sha256.slice(0, 12)}.json`);
|
|
54221
|
-
await
|
|
54852
|
+
await mkdir21(dirname18(out), { recursive: true });
|
|
54222
54853
|
await Bun.write(out, JSON.stringify(artifact2, null, 2) + `
|
|
54223
54854
|
`);
|
|
54224
54855
|
console.log(JSON.stringify({ path: out, artifact: artifact2 }, null, 2));
|
|
@@ -54361,7 +54992,7 @@ var init_legacy2 = __esm(() => {
|
|
|
54361
54992
|
|
|
54362
54993
|
// src/core/evidence-pack/index.ts
|
|
54363
54994
|
import { createHash as createHash10, randomUUID as randomUUID6 } from "crypto";
|
|
54364
|
-
import { link as link3, lstat as lstat7, mkdir as
|
|
54995
|
+
import { link as link3, lstat as lstat7, mkdir as mkdir22, realpath as realpath4, unlink as unlink8, writeFile as writeFile10 } from "fs/promises";
|
|
54365
54996
|
import { basename as basename9, dirname as dirname19, relative as relative6, resolve as resolve13, sep as sep8 } from "path";
|
|
54366
54997
|
function isRecord6(value) {
|
|
54367
54998
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -54645,7 +55276,7 @@ async function writeEvidencePack(workspaceRoot, outputPath, pack) {
|
|
|
54645
55276
|
if (!isInside(workspace, existingAncestor2) && existingAncestor2 !== workspace) {
|
|
54646
55277
|
throw new EvidencePackValidationError("output path resolves outside the workspace");
|
|
54647
55278
|
}
|
|
54648
|
-
await
|
|
55279
|
+
await mkdir22(parent, { recursive: true });
|
|
54649
55280
|
const realParent = await realpath4(parent);
|
|
54650
55281
|
if (!isInside(workspace, realParent) && realParent !== workspace) {
|
|
54651
55282
|
throw new EvidencePackValidationError("output path resolves outside the workspace");
|
|
@@ -55290,9 +55921,9 @@ function assessImpact(input) {
|
|
|
55290
55921
|
...nodes.map((node) => node.reference),
|
|
55291
55922
|
...contractResult.metrics.map((metric) => `metric:${metric.name}`)
|
|
55292
55923
|
]);
|
|
55293
|
-
const
|
|
55294
|
-
findings.push(...
|
|
55295
|
-
if (
|
|
55924
|
+
const access2 = dataAccessFindings(change, references, input.dataAccess.value, input.blockedIdentifiers);
|
|
55925
|
+
findings.push(...access2.findings);
|
|
55926
|
+
if (access2.redacted)
|
|
55296
55927
|
addGap(gaps, "REDACTED_PROTECTED_SUBJECT");
|
|
55297
55928
|
}
|
|
55298
55929
|
}
|
|
@@ -56087,7 +56718,7 @@ __export(exports_impact, {
|
|
|
56087
56718
|
impactCommand: () => impactCommand
|
|
56088
56719
|
});
|
|
56089
56720
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
56090
|
-
import { link as link4, lstat as lstat9, mkdir as
|
|
56721
|
+
import { link as link4, lstat as lstat9, mkdir as mkdir23, realpath as realpath6, unlink as unlink9, writeFile as writeFile11 } from "fs/promises";
|
|
56091
56722
|
import { basename as basename10, dirname as dirname20, relative as relative8, resolve as resolve15, sep as sep10 } from "path";
|
|
56092
56723
|
function collectOption3(value, previous) {
|
|
56093
56724
|
return [...previous, value];
|
|
@@ -56308,7 +56939,7 @@ async function resolveOutput(workspaceRoot, outputPath) {
|
|
|
56308
56939
|
const existingAncestor2 = await realExistingAncestor2(parent);
|
|
56309
56940
|
if (!isInside2(workspace, existingAncestor2) && existingAncestor2 !== workspace)
|
|
56310
56941
|
throw new Error("output path must stay inside the workspace");
|
|
56311
|
-
await
|
|
56942
|
+
await mkdir23(parent, { recursive: true });
|
|
56312
56943
|
const realParent = await realpath6(parent);
|
|
56313
56944
|
if (!isInside2(workspace, realParent) && realParent !== workspace)
|
|
56314
56945
|
throw new Error("output path must stay inside the workspace");
|
|
@@ -57178,7 +57809,7 @@ __export(exports_completion, {
|
|
|
57178
57809
|
});
|
|
57179
57810
|
import { join as join46 } from "path";
|
|
57180
57811
|
import { homedir as homedir4 } from "os";
|
|
57181
|
-
import { mkdir as
|
|
57812
|
+
import { mkdir as mkdir24 } from "fs/promises";
|
|
57182
57813
|
function resolveHome() {
|
|
57183
57814
|
return process.env.HOME ?? homedir4();
|
|
57184
57815
|
}
|
|
@@ -57451,7 +58082,7 @@ async function installCompletion(shell, script) {
|
|
|
57451
58082
|
const targetPath = getInstallPath2(shell);
|
|
57452
58083
|
if (shell === "fish") {
|
|
57453
58084
|
const dir = join46(resolveHome(), ".config", "fish", "completions");
|
|
57454
|
-
await
|
|
58085
|
+
await mkdir24(dir, { recursive: true });
|
|
57455
58086
|
await Bun.file(targetPath).write(script);
|
|
57456
58087
|
console.log(colors.success(`\u2713 Fish completion installed to ${targetPath}`));
|
|
57457
58088
|
return;
|