@youtyan/code-viewer 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/dist/code-viewer.js +1206 -352
- package/package.json +8 -2
- package/web/app.js +1428 -860
- package/web/style.css +65 -0
- package/web/vendor/THIRD_PARTY_NOTICES.txt +1181 -83
package/dist/code-viewer.js
CHANGED
|
@@ -10340,6 +10340,42 @@ function throwIfAborted(signal, message = "operation aborted") {
|
|
|
10340
10340
|
if (signal?.aborted)
|
|
10341
10341
|
throw abortError(message);
|
|
10342
10342
|
}
|
|
10343
|
+
function waitForAbortableResource(promise, signal, dispose, message = "operation aborted") {
|
|
10344
|
+
const disposeSafely = (resource) => {
|
|
10345
|
+
try {
|
|
10346
|
+
dispose(resource);
|
|
10347
|
+
} catch {}
|
|
10348
|
+
};
|
|
10349
|
+
if (!signal)
|
|
10350
|
+
return promise;
|
|
10351
|
+
if (signal.aborted) {
|
|
10352
|
+
promise.then(disposeSafely, () => {
|
|
10353
|
+
return;
|
|
10354
|
+
});
|
|
10355
|
+
return Promise.reject(abortError(message));
|
|
10356
|
+
}
|
|
10357
|
+
return new Promise((resolve2, reject) => {
|
|
10358
|
+
let aborted = false;
|
|
10359
|
+
const onAbort = () => {
|
|
10360
|
+
aborted = true;
|
|
10361
|
+
signal.removeEventListener("abort", onAbort);
|
|
10362
|
+
reject(abortError(message));
|
|
10363
|
+
};
|
|
10364
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
10365
|
+
promise.then((resource) => {
|
|
10366
|
+
signal.removeEventListener("abort", onAbort);
|
|
10367
|
+
if (aborted) {
|
|
10368
|
+
disposeSafely(resource);
|
|
10369
|
+
return;
|
|
10370
|
+
}
|
|
10371
|
+
resolve2(resource);
|
|
10372
|
+
}, (error) => {
|
|
10373
|
+
signal.removeEventListener("abort", onAbort);
|
|
10374
|
+
if (!aborted)
|
|
10375
|
+
reject(error);
|
|
10376
|
+
});
|
|
10377
|
+
});
|
|
10378
|
+
}
|
|
10343
10379
|
function isAbortLikeError(err, signal, extraTokens = []) {
|
|
10344
10380
|
if (signal?.aborted)
|
|
10345
10381
|
return true;
|
|
@@ -10796,6 +10832,8 @@ var init_sql_capture = __esm(() => {
|
|
|
10796
10832
|
|
|
10797
10833
|
// web-src/server/database/adapters/docker.ts
|
|
10798
10834
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
10835
|
+
import mysql from "mysql2/promise";
|
|
10836
|
+
import pg from "pg";
|
|
10799
10837
|
function dockerDatabasesCacheKey(serviceName, kind, cwd) {
|
|
10800
10838
|
return `${serviceName}\x00${kind}\x00${cwd}`;
|
|
10801
10839
|
}
|
|
@@ -10821,55 +10859,211 @@ function setDockerSchemasCache(key, value, ttlMs, now = Date.now()) {
|
|
|
10821
10859
|
function fallbackDockerDatabases(defaultDb) {
|
|
10822
10860
|
return defaultDb ? [defaultDb] : [];
|
|
10823
10861
|
}
|
|
10824
|
-
function
|
|
10862
|
+
function buildExecInvocation(config, sql) {
|
|
10863
|
+
if (!config.containerName) {
|
|
10864
|
+
throw new Error("direct SQL connections use the built-in driver");
|
|
10865
|
+
}
|
|
10825
10866
|
if (config.kind === "postgresql") {
|
|
10826
|
-
return
|
|
10867
|
+
return {
|
|
10868
|
+
args: [
|
|
10869
|
+
dockerCommand(),
|
|
10870
|
+
"exec",
|
|
10871
|
+
"-i",
|
|
10872
|
+
"-e",
|
|
10873
|
+
`PGPASSWORD=${config.password}`,
|
|
10874
|
+
config.containerName,
|
|
10875
|
+
"psql",
|
|
10876
|
+
"-U",
|
|
10877
|
+
config.user,
|
|
10878
|
+
"-d",
|
|
10879
|
+
config.database,
|
|
10880
|
+
"-X",
|
|
10881
|
+
"-q",
|
|
10882
|
+
"-t",
|
|
10883
|
+
"-A",
|
|
10884
|
+
"-F",
|
|
10885
|
+
PG_FIELD_SEPARATOR,
|
|
10886
|
+
"-R",
|
|
10887
|
+
PG_RECORD_SEPARATOR,
|
|
10888
|
+
"-v",
|
|
10889
|
+
"ON_ERROR_STOP=1",
|
|
10890
|
+
"-c",
|
|
10891
|
+
sql
|
|
10892
|
+
],
|
|
10893
|
+
env: process.env
|
|
10894
|
+
};
|
|
10895
|
+
}
|
|
10896
|
+
return {
|
|
10897
|
+
args: [
|
|
10827
10898
|
dockerCommand(),
|
|
10828
10899
|
"exec",
|
|
10829
10900
|
"-i",
|
|
10830
10901
|
"-e",
|
|
10831
|
-
`
|
|
10902
|
+
`MYSQL_PWD=${config.password}`,
|
|
10832
10903
|
config.containerName,
|
|
10833
|
-
"
|
|
10834
|
-
"-
|
|
10904
|
+
"mysql",
|
|
10905
|
+
"-u",
|
|
10835
10906
|
config.user,
|
|
10836
|
-
"-d",
|
|
10837
10907
|
config.database,
|
|
10838
|
-
"
|
|
10839
|
-
"-
|
|
10840
|
-
"-
|
|
10841
|
-
"-A",
|
|
10842
|
-
"-F",
|
|
10843
|
-
"\t",
|
|
10844
|
-
"-R",
|
|
10845
|
-
PG_RECORD_SEPARATOR,
|
|
10846
|
-
"-v",
|
|
10847
|
-
"ON_ERROR_STOP=1",
|
|
10848
|
-
"-c",
|
|
10908
|
+
"--batch",
|
|
10909
|
+
"--default-character-set=utf8mb4",
|
|
10910
|
+
"-e",
|
|
10849
10911
|
sql
|
|
10850
|
-
]
|
|
10912
|
+
],
|
|
10913
|
+
env: process.env
|
|
10914
|
+
};
|
|
10915
|
+
}
|
|
10916
|
+
function sqlDriverValue(value, nullValue) {
|
|
10917
|
+
if (value === null || value === undefined)
|
|
10918
|
+
return nullValue;
|
|
10919
|
+
if (value instanceof Date)
|
|
10920
|
+
return value.toISOString();
|
|
10921
|
+
if (value instanceof Uint8Array)
|
|
10922
|
+
return Buffer.from(value).toString("hex");
|
|
10923
|
+
if (typeof value === "object")
|
|
10924
|
+
return JSON.stringify(value);
|
|
10925
|
+
return String(value);
|
|
10926
|
+
}
|
|
10927
|
+
function pgDriverResult(raw) {
|
|
10928
|
+
const results = Array.isArray(raw) ? raw : [raw];
|
|
10929
|
+
const reversed = [...results].reverse();
|
|
10930
|
+
const result = reversed.find((item) => !!item && typeof item === "object" && Array.isArray(item.fields) && item.fields.length > 0) ?? reversed.find((item) => !!item && typeof item === "object" && Array.isArray(item.rows) && item.rows.length > 0) ?? raw;
|
|
10931
|
+
if (!result || typeof result !== "object")
|
|
10932
|
+
return { columns: [], rows: [] };
|
|
10933
|
+
const fields = Array.isArray(result.fields) ? result.fields : [];
|
|
10934
|
+
const rows = Array.isArray(result.rows) ? result.rows : [];
|
|
10935
|
+
return {
|
|
10936
|
+
columns: fields.map((field) => String(field.name ?? "")),
|
|
10937
|
+
rows: rows.map((row) => row.map((value) => sqlDriverValue(value, "\\N")))
|
|
10938
|
+
};
|
|
10939
|
+
}
|
|
10940
|
+
function mysqlDriverResult(raw) {
|
|
10941
|
+
if (!Array.isArray(raw) || raw.length < 2)
|
|
10942
|
+
return { columns: [], rows: [] };
|
|
10943
|
+
const [allRows, allFields] = raw;
|
|
10944
|
+
let rows = allRows;
|
|
10945
|
+
let fields = allFields;
|
|
10946
|
+
if (Array.isArray(allFields) && allFields.length > 0 && Array.isArray(allFields[0])) {
|
|
10947
|
+
const fieldSets = allFields;
|
|
10948
|
+
const rowSets = Array.isArray(allRows) ? allRows : [];
|
|
10949
|
+
let index = -1;
|
|
10950
|
+
for (let i = fieldSets.length - 1;i >= 0; i--) {
|
|
10951
|
+
if (Array.isArray(fieldSets[i])) {
|
|
10952
|
+
index = i;
|
|
10953
|
+
break;
|
|
10954
|
+
}
|
|
10955
|
+
}
|
|
10956
|
+
fields = index >= 0 ? fieldSets[index] : [];
|
|
10957
|
+
rows = index >= 0 ? rowSets[index] : [];
|
|
10851
10958
|
}
|
|
10852
|
-
|
|
10853
|
-
|
|
10854
|
-
|
|
10855
|
-
"
|
|
10856
|
-
"
|
|
10857
|
-
|
|
10858
|
-
|
|
10859
|
-
|
|
10860
|
-
|
|
10861
|
-
|
|
10862
|
-
|
|
10863
|
-
|
|
10864
|
-
|
|
10865
|
-
|
|
10866
|
-
|
|
10867
|
-
|
|
10959
|
+
const fieldList = Array.isArray(fields) ? fields.filter(Boolean) : [];
|
|
10960
|
+
const rowList = Array.isArray(rows) ? rows : [];
|
|
10961
|
+
return {
|
|
10962
|
+
columns: fieldList.map((field) => String(field.name ?? "")),
|
|
10963
|
+
rows: rowList.map((row) => row.map((value) => sqlDriverValue(value, "NULL")))
|
|
10964
|
+
};
|
|
10965
|
+
}
|
|
10966
|
+
function createSqlDriverExecutor(config) {
|
|
10967
|
+
if (!config.host || !config.port) {
|
|
10968
|
+
throw new Error("direct SQL connection requires host and port");
|
|
10969
|
+
}
|
|
10970
|
+
if (config.kind === "postgresql") {
|
|
10971
|
+
const pool2 = createPgPoolImpl({
|
|
10972
|
+
host: config.host,
|
|
10973
|
+
port: config.port,
|
|
10974
|
+
user: config.user,
|
|
10975
|
+
password: config.password,
|
|
10976
|
+
database: config.database,
|
|
10977
|
+
ssl: config.tls,
|
|
10978
|
+
max: 4,
|
|
10979
|
+
connectionTimeoutMillis: 1e4,
|
|
10980
|
+
query_timeout: 1e4
|
|
10981
|
+
});
|
|
10982
|
+
return {
|
|
10983
|
+
async exec(sql, signal) {
|
|
10984
|
+
throwIfAborted(signal, "query aborted");
|
|
10985
|
+
const client = await waitForAbortableResource(pool2.connect(), signal, (lateClient) => lateClient.release(true), "query aborted");
|
|
10986
|
+
let released = false;
|
|
10987
|
+
const abort = () => {
|
|
10988
|
+
if (released)
|
|
10989
|
+
return;
|
|
10990
|
+
released = true;
|
|
10991
|
+
client.release(true);
|
|
10992
|
+
};
|
|
10993
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
10994
|
+
try {
|
|
10995
|
+
throwIfAborted(signal, "query aborted");
|
|
10996
|
+
return pgDriverResult(await client.query({ text: sql, rowMode: "array" }));
|
|
10997
|
+
} catch (error) {
|
|
10998
|
+
if (signal?.aborted)
|
|
10999
|
+
throw abortError("query aborted");
|
|
11000
|
+
throw error;
|
|
11001
|
+
} finally {
|
|
11002
|
+
signal?.removeEventListener("abort", abort);
|
|
11003
|
+
if (!released)
|
|
11004
|
+
client.release();
|
|
11005
|
+
}
|
|
11006
|
+
},
|
|
11007
|
+
close() {
|
|
11008
|
+
pool2.end().catch(() => {
|
|
11009
|
+
return;
|
|
11010
|
+
});
|
|
11011
|
+
}
|
|
11012
|
+
};
|
|
11013
|
+
}
|
|
11014
|
+
const pool = createMysqlPoolImpl({
|
|
11015
|
+
host: config.host,
|
|
11016
|
+
port: config.port,
|
|
11017
|
+
user: config.user,
|
|
11018
|
+
password: config.password,
|
|
11019
|
+
database: config.database,
|
|
11020
|
+
ssl: config.tls ? {} : undefined,
|
|
11021
|
+
waitForConnections: true,
|
|
11022
|
+
connectionLimit: 4,
|
|
11023
|
+
connectTimeout: 1e4,
|
|
11024
|
+
multipleStatements: true,
|
|
11025
|
+
rowsAsArray: true,
|
|
11026
|
+
supportBigNumbers: true,
|
|
11027
|
+
bigNumberStrings: true,
|
|
11028
|
+
dateStrings: true
|
|
11029
|
+
});
|
|
11030
|
+
return {
|
|
11031
|
+
async exec(sql, signal) {
|
|
11032
|
+
throwIfAborted(signal, "query aborted");
|
|
11033
|
+
const connection = await waitForAbortableResource(pool.getConnection(), signal, (lateConnection) => lateConnection.destroy(), "query aborted");
|
|
11034
|
+
let released = false;
|
|
11035
|
+
const abort = () => {
|
|
11036
|
+
if (released)
|
|
11037
|
+
return;
|
|
11038
|
+
released = true;
|
|
11039
|
+
connection.destroy();
|
|
11040
|
+
};
|
|
11041
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
11042
|
+
try {
|
|
11043
|
+
throwIfAborted(signal, "query aborted");
|
|
11044
|
+
return mysqlDriverResult(await connection.query({ sql, rowsAsArray: true, timeout: 1e4 }));
|
|
11045
|
+
} catch (error) {
|
|
11046
|
+
if (signal?.aborted)
|
|
11047
|
+
throw abortError("query aborted");
|
|
11048
|
+
throw error;
|
|
11049
|
+
} finally {
|
|
11050
|
+
signal?.removeEventListener("abort", abort);
|
|
11051
|
+
if (!released)
|
|
11052
|
+
connection.release();
|
|
11053
|
+
}
|
|
11054
|
+
},
|
|
11055
|
+
close() {
|
|
11056
|
+
pool.end().catch(() => {
|
|
11057
|
+
return;
|
|
11058
|
+
});
|
|
11059
|
+
}
|
|
11060
|
+
};
|
|
10868
11061
|
}
|
|
10869
11062
|
function execInContainer(config, sql, timeoutMs = 1e4) {
|
|
10870
|
-
const args =
|
|
11063
|
+
const { args, env } = buildExecInvocation(config, sql);
|
|
10871
11064
|
const proc = spawnSyncImpl2(args[0], args.slice(1), {
|
|
10872
11065
|
encoding: "utf8",
|
|
11066
|
+
env,
|
|
10873
11067
|
timeout: timeoutMs,
|
|
10874
11068
|
stdio: ["ignore", "pipe", "pipe"]
|
|
10875
11069
|
});
|
|
@@ -10895,11 +11089,12 @@ async function readStreamText(stream) {
|
|
|
10895
11089
|
text += decoder.decode();
|
|
10896
11090
|
return text;
|
|
10897
11091
|
}
|
|
10898
|
-
async function execWithBunSpawn(spawnFn, args, timeoutMs, signal) {
|
|
11092
|
+
async function execWithBunSpawn(spawnFn, args, env, timeoutMs, signal) {
|
|
10899
11093
|
throwIfAborted(signal, "query aborted");
|
|
10900
11094
|
let proc;
|
|
10901
11095
|
try {
|
|
10902
11096
|
proc = spawnFn(args, {
|
|
11097
|
+
env,
|
|
10903
11098
|
stdin: "ignore",
|
|
10904
11099
|
stdout: "pipe",
|
|
10905
11100
|
stderr: "pipe"
|
|
@@ -10946,10 +11141,11 @@ async function execWithBunSpawn(spawnFn, args, timeoutMs, signal) {
|
|
|
10946
11141
|
signal?.removeEventListener("abort", abort);
|
|
10947
11142
|
}
|
|
10948
11143
|
}
|
|
10949
|
-
function execWithNodeSpawn(args, timeoutMs, signal) {
|
|
11144
|
+
function execWithNodeSpawn(args, env, timeoutMs, signal) {
|
|
10950
11145
|
return spawnTextAsync({
|
|
10951
11146
|
command: args[0],
|
|
10952
11147
|
args: args.slice(1),
|
|
11148
|
+
env,
|
|
10953
11149
|
timeoutMs,
|
|
10954
11150
|
signal,
|
|
10955
11151
|
killSignal: "SIGKILL",
|
|
@@ -10966,10 +11162,11 @@ async function execInContainerAsync(config, sql, timeoutMs = 1e4, signal) {
|
|
|
10966
11162
|
throwIfDockerCommandUnavailableResult(result);
|
|
10967
11163
|
return result;
|
|
10968
11164
|
}
|
|
10969
|
-
const args =
|
|
11165
|
+
const { args, env } = buildExecInvocation(config, sql);
|
|
10970
11166
|
const bunSpawn = globalThis.Bun?.spawn;
|
|
10971
|
-
result = bunSpawn ? await execWithBunSpawn(bunSpawn, args, timeoutMs, signal) : await execWithNodeSpawn(args, timeoutMs, signal);
|
|
10972
|
-
|
|
11167
|
+
result = bunSpawn ? await execWithBunSpawn(bunSpawn, args, env, timeoutMs, signal) : await execWithNodeSpawn(args, env, timeoutMs, signal);
|
|
11168
|
+
if (config.containerName)
|
|
11169
|
+
throwIfDockerCommandUnavailableResult(result);
|
|
10973
11170
|
return result;
|
|
10974
11171
|
}
|
|
10975
11172
|
function stripFinalLineBreak(text) {
|
|
@@ -11029,14 +11226,14 @@ function decodeMysqlBatchField(value) {
|
|
|
11029
11226
|
}
|
|
11030
11227
|
return out;
|
|
11031
11228
|
}
|
|
11032
|
-
function splitTsvLine(line, decodeFields) {
|
|
11033
|
-
const fields = line.split(
|
|
11229
|
+
function splitTsvLine(line, decodeFields, fieldSeparator = "\t") {
|
|
11230
|
+
const fields = line.split(fieldSeparator);
|
|
11034
11231
|
return decodeFields ? fields.map(decodeMysqlBatchField) : fields;
|
|
11035
11232
|
}
|
|
11036
11233
|
function stripFinalRecordSeparator(text, recordSeparator) {
|
|
11037
11234
|
return text.endsWith(recordSeparator) ? text.slice(0, -recordSeparator.length) : text;
|
|
11038
11235
|
}
|
|
11039
|
-
function parseTsvOutput(stdout, hasHeader, recordSeparator) {
|
|
11236
|
+
function parseTsvOutput(stdout, hasHeader, recordSeparator, fieldSeparator) {
|
|
11040
11237
|
const text = recordSeparator ? stripFinalRecordSeparator(stripFinalLineBreak(stdout), recordSeparator) : stripFinalLineBreak(stdout);
|
|
11041
11238
|
if (text.length === 0)
|
|
11042
11239
|
return { columns: [], rows: [] };
|
|
@@ -11044,11 +11241,11 @@ function parseTsvOutput(stdout, hasHeader, recordSeparator) {
|
|
|
11044
11241
|
if (lines.length === 0)
|
|
11045
11242
|
return { columns: [], rows: [] };
|
|
11046
11243
|
if (hasHeader) {
|
|
11047
|
-
const columns = splitTsvLine(lines[0], true);
|
|
11048
|
-
const rows2 = lines.slice(1).map((line) => splitTsvLine(line, true));
|
|
11244
|
+
const columns = splitTsvLine(lines[0], true, fieldSeparator);
|
|
11245
|
+
const rows2 = lines.slice(1).map((line) => splitTsvLine(line, true, fieldSeparator));
|
|
11049
11246
|
return { columns, rows: rows2 };
|
|
11050
11247
|
}
|
|
11051
|
-
const rows = lines.map((line) => splitTsvLine(line, false));
|
|
11248
|
+
const rows = lines.map((line) => splitTsvLine(line, false, fieldSeparator));
|
|
11052
11249
|
return { columns: [], rows };
|
|
11053
11250
|
}
|
|
11054
11251
|
function isMysqlSpatialType(type) {
|
|
@@ -11107,13 +11304,18 @@ function observeBackgroundRejection(promise) {
|
|
|
11107
11304
|
});
|
|
11108
11305
|
return promise;
|
|
11109
11306
|
}
|
|
11110
|
-
function
|
|
11307
|
+
function createSqlCliAdapter(config) {
|
|
11308
|
+
const driver = config.containerName ? null : createSqlDriverExecutor(config);
|
|
11111
11309
|
async function execAsync(sql, signal) {
|
|
11310
|
+
if (driver) {
|
|
11311
|
+
recordSql(sql);
|
|
11312
|
+
return driver.exec(sql, signal);
|
|
11313
|
+
}
|
|
11112
11314
|
const result = await execInContainerAsync(config, sql, 1e4, signal);
|
|
11113
11315
|
if (result.code !== 0) {
|
|
11114
11316
|
throw new Error(result.stderr.trim() || "query failed");
|
|
11115
11317
|
}
|
|
11116
|
-
return parseTsvOutput(result.stdout, config.kind === "mysql", config.kind === "postgresql" ? PG_RECORD_SEPARATOR : undefined);
|
|
11318
|
+
return parseTsvOutput(result.stdout, config.kind === "mysql", config.kind === "postgresql" ? PG_RECORD_SEPARATOR : undefined, config.kind === "postgresql" ? PG_FIELD_SEPARATOR : undefined);
|
|
11117
11319
|
}
|
|
11118
11320
|
function toDbValue(val) {
|
|
11119
11321
|
if (val === "NULL" || val === "\\N")
|
|
@@ -11190,15 +11392,16 @@ function createDockerAdapter(config) {
|
|
|
11190
11392
|
async getTablesAsync(signal) {
|
|
11191
11393
|
let sql;
|
|
11192
11394
|
if (config.kind === "postgresql") {
|
|
11193
|
-
sql = `SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = ${postgresSchemaLiteral()} ORDER BY table_name`;
|
|
11395
|
+
sql = `SELECT t.table_name, t.table_type, COALESCE(obj_description(cls.oid, 'pg_class'), '') FROM information_schema.tables t JOIN pg_namespace n ON n.nspname = t.table_schema JOIN pg_class cls ON cls.relnamespace = n.oid AND cls.relname = t.table_name WHERE t.table_schema = ${postgresSchemaLiteral()} ORDER BY t.table_name`;
|
|
11194
11396
|
} else {
|
|
11195
|
-
sql = `SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = DATABASE() ORDER BY table_name`;
|
|
11397
|
+
sql = `SELECT table_name, table_type, table_comment FROM information_schema.tables WHERE table_schema = DATABASE() ORDER BY table_name`;
|
|
11196
11398
|
}
|
|
11197
11399
|
const result = await execAsync(sql, signal);
|
|
11198
11400
|
return result.rows.map((row) => ({
|
|
11199
11401
|
name: row[0],
|
|
11200
11402
|
type: row[1] === "VIEW" ? "view" : "table",
|
|
11201
|
-
rowCount: null
|
|
11403
|
+
rowCount: null,
|
|
11404
|
+
comment: row[2] || null
|
|
11202
11405
|
}));
|
|
11203
11406
|
},
|
|
11204
11407
|
async getColumnsAsync(table, signal) {
|
|
@@ -11211,11 +11414,11 @@ function createDockerAdapter(config) {
|
|
|
11211
11414
|
},
|
|
11212
11415
|
async getIndexesAsync(signal) {
|
|
11213
11416
|
let sql;
|
|
11214
|
-
const INDEX_COL_SEP = "\
|
|
11417
|
+
const INDEX_COL_SEP = "\x1D";
|
|
11215
11418
|
if (config.kind === "postgresql") {
|
|
11216
|
-
sql = `SELECT i.relname, t.relname, CASE WHEN ix.indisunique THEN '1' ELSE '0' END, COALESCE(string_agg(a.attname, E'\\
|
|
11419
|
+
sql = `SELECT i.relname, t.relname, CASE WHEN ix.indisunique THEN '1' ELSE '0' END, COALESCE(string_agg(a.attname, E'\\x1d' ORDER BY k.ord), '') FROM pg_index ix JOIN pg_class i ON i.oid = ix.indexrelid JOIN pg_class t ON t.oid = ix.indrelid JOIN pg_namespace n ON n.oid = t.relnamespace LEFT JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS k(attnum, ord) ON true LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum WHERE n.nspname = ${postgresSchemaLiteral()} AND i.relname NOT LIKE 'pg_%' GROUP BY i.relname, t.relname, ix.indisunique ORDER BY t.relname, i.relname`;
|
|
11217
11420
|
} else {
|
|
11218
|
-
sql = `SELECT index_name, table_name, IF(MAX(non_unique) = 0, '1', '0'), GROUP_CONCAT(column_name ORDER BY seq_in_index SEPARATOR '\
|
|
11421
|
+
sql = `SELECT index_name, table_name, IF(MAX(non_unique) = 0, '1', '0'), GROUP_CONCAT(column_name ORDER BY seq_in_index SEPARATOR '\x1D') FROM information_schema.statistics WHERE table_schema = DATABASE() GROUP BY index_name, table_name ORDER BY table_name, index_name`;
|
|
11219
11422
|
}
|
|
11220
11423
|
const result = await execAsync(sql, signal);
|
|
11221
11424
|
return result.rows.map((row) => ({
|
|
@@ -11544,6 +11747,7 @@ function createDockerAdapter(config) {
|
|
|
11544
11747
|
close() {
|
|
11545
11748
|
columnCache.clear();
|
|
11546
11749
|
tableMetaCache.invalidate();
|
|
11750
|
+
driver?.close();
|
|
11547
11751
|
},
|
|
11548
11752
|
async* iterateForSnapshot(table, signal) {
|
|
11549
11753
|
const columns = await adapter.getColumnsAsync(table, signal);
|
|
@@ -11615,7 +11819,7 @@ async function listDockerDatabasesAsync(serviceName, kind, env, cwd, signal) {
|
|
|
11615
11819
|
return fallback;
|
|
11616
11820
|
return setDockerDatabasesCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
11617
11821
|
}
|
|
11618
|
-
const parsed = parseTsvOutput(result.stdout, kind === "mysql", kind === "postgresql" ? PG_RECORD_SEPARATOR : undefined);
|
|
11822
|
+
const parsed = parseTsvOutput(result.stdout, kind === "mysql", kind === "postgresql" ? PG_RECORD_SEPARATOR : undefined, kind === "postgresql" ? PG_FIELD_SEPARATOR : undefined);
|
|
11619
11823
|
const dbs = parsed.rows.map((r) => r[0]).filter(Boolean);
|
|
11620
11824
|
const value = dbs.length > 0 ? dbs : fallbackDockerDatabases(defaultDb);
|
|
11621
11825
|
return setDockerDatabasesCache(cacheKey, value, value.length > 0 ? DOCKER_DATABASES_POSITIVE_TTL_MS : DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
@@ -11655,7 +11859,7 @@ async function fetchPostgresSchemasViaContainerAsync(config, cacheKey, now, sign
|
|
|
11655
11859
|
if (result.code !== 0) {
|
|
11656
11860
|
return setDockerSchemasCache(cacheKey, ["public"], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
11657
11861
|
}
|
|
11658
|
-
const parsed = parseTsvOutput(result.stdout, false, PG_RECORD_SEPARATOR);
|
|
11862
|
+
const parsed = parseTsvOutput(result.stdout, false, PG_RECORD_SEPARATOR, PG_FIELD_SEPARATOR);
|
|
11659
11863
|
const schemas = parsed.rows.map((r) => r[0]).filter(Boolean);
|
|
11660
11864
|
const value = schemas.length > 0 ? schemas : ["public"];
|
|
11661
11865
|
return setDockerSchemasCache(cacheKey, value, DOCKER_DATABASES_POSITIVE_TTL_MS, now);
|
|
@@ -11713,7 +11917,7 @@ async function openDockerAdapterAsync(serviceName, kind, env, cwd, overrideDatab
|
|
|
11713
11917
|
...kind === "postgresql" && schema ? { schema } : {}
|
|
11714
11918
|
});
|
|
11715
11919
|
}
|
|
11716
|
-
var COLUMNS_TTL_MS = 30000, ROWCOUNT_TTL_MS = 15000, DOCKER_DATABASES_POSITIVE_TTL_MS = 15000, DOCKER_DATABASES_NEGATIVE_TTL_MS = 3000, dockerDatabasesCache, dockerSchemasCache, spawnSyncImpl2, PG_RECORD_SEPARATOR = "\x1E", MYSQL_SPATIAL_TYPES, SUPABASE_LOCAL_DB_USER = "postgres", SUPABASE_LOCAL_DB_PASSWORD = "postgres", SUPABASE_LOCAL_DB_NAME = "postgres";
|
|
11920
|
+
var createPgPoolImpl = (config) => new pg.Pool(config), createMysqlPoolImpl = (config) => mysql.createPool(config), COLUMNS_TTL_MS = 30000, ROWCOUNT_TTL_MS = 15000, DOCKER_DATABASES_POSITIVE_TTL_MS = 15000, DOCKER_DATABASES_NEGATIVE_TTL_MS = 3000, dockerDatabasesCache, dockerSchemasCache, spawnSyncImpl2, PG_RECORD_SEPARATOR = "\x1E", PG_FIELD_SEPARATOR = "\x1F", MYSQL_SPATIAL_TYPES, createDockerAdapter, SUPABASE_LOCAL_DB_USER = "postgres", SUPABASE_LOCAL_DB_PASSWORD = "postgres", SUPABASE_LOCAL_DB_NAME = "postgres";
|
|
11717
11921
|
var init_docker = __esm(() => {
|
|
11718
11922
|
init_mutate();
|
|
11719
11923
|
init_sql_snapshot();
|
|
@@ -11735,6 +11939,7 @@ var init_docker = __esm(() => {
|
|
|
11735
11939
|
"geometrycollection",
|
|
11736
11940
|
"geomcollection"
|
|
11737
11941
|
]);
|
|
11942
|
+
createDockerAdapter = createSqlCliAdapter;
|
|
11738
11943
|
});
|
|
11739
11944
|
|
|
11740
11945
|
// web-src/server/database/adapters/elasticsearch.ts
|
|
@@ -11743,8 +11948,13 @@ __export(exports_elasticsearch, {
|
|
|
11743
11948
|
quoteCurlConfigString: () => quoteCurlConfigString,
|
|
11744
11949
|
openElasticsearchAdapterAsync: () => openElasticsearchAdapterAsync,
|
|
11745
11950
|
isReadOnlyEsPath: () => isReadOnlyEsPath,
|
|
11746
|
-
|
|
11951
|
+
createElasticsearchAdapter: () => createElasticsearchAdapter,
|
|
11952
|
+
canonicalizeEsSnapshotContainer: () => canonicalizeEsSnapshotContainer,
|
|
11953
|
+
__setEsFetchForTest: () => __setEsFetchForTest
|
|
11747
11954
|
});
|
|
11955
|
+
function __setEsFetchForTest(fetchForTest) {
|
|
11956
|
+
esFetchImpl = fetchForTest ?? globalThis.fetch;
|
|
11957
|
+
}
|
|
11748
11958
|
function isReadOnlyEsPath(rawPath) {
|
|
11749
11959
|
const path = rawPath.split("?")[0].replace(/\/+$/, "");
|
|
11750
11960
|
const segments = path.split("/").filter(Boolean);
|
|
@@ -11764,13 +11974,11 @@ function quoteCurlConfigString(value) {
|
|
|
11764
11974
|
}
|
|
11765
11975
|
function buildEsRequestInvocation(config, method, path, body) {
|
|
11766
11976
|
const hasPassword = !!config.password;
|
|
11767
|
-
const
|
|
11768
|
-
const
|
|
11977
|
+
const endpoint = config.endpoint?.replace(/\/$/, "") || "http://localhost:9200";
|
|
11978
|
+
const url = `${endpoint}${path.startsWith("/") ? "" : "/"}${path}`;
|
|
11979
|
+
const curlConfig = hasPassword ? `user = ${quoteCurlConfigString(`${config.username || "elastic"}:${config.password}`)}
|
|
11769
11980
|
` : undefined;
|
|
11770
|
-
const
|
|
11771
|
-
"exec",
|
|
11772
|
-
"-i",
|
|
11773
|
-
config.containerName,
|
|
11981
|
+
const curlArgs = [
|
|
11774
11982
|
"curl",
|
|
11775
11983
|
"-s",
|
|
11776
11984
|
"-S",
|
|
@@ -11786,13 +11994,64 @@ __ES_STATUS__:%{http_code}
|
|
|
11786
11994
|
...hasPassword ? ["-K", "-"] : [],
|
|
11787
11995
|
...body !== undefined ? ["--data-binary", JSON.stringify(body)] : []
|
|
11788
11996
|
];
|
|
11789
|
-
|
|
11997
|
+
if (!config.containerName) {
|
|
11998
|
+
throw new Error("direct Elasticsearch connections use the built-in HTTP client");
|
|
11999
|
+
}
|
|
12000
|
+
return {
|
|
12001
|
+
command: dockerCommand(),
|
|
12002
|
+
args: ["exec", "-i", config.containerName, ...curlArgs],
|
|
12003
|
+
input: curlConfig
|
|
12004
|
+
};
|
|
11790
12005
|
}
|
|
11791
12006
|
async function execEsRequestAsync(config, method, path, body, timeoutMs = 15000, signal) {
|
|
11792
12007
|
throwIfAborted(signal, "elasticsearch request aborted");
|
|
12008
|
+
if (!config.containerName) {
|
|
12009
|
+
const endpoint = config.endpoint?.replace(/\/$/, "") || "http://localhost:9200";
|
|
12010
|
+
const url = `${endpoint}${path.startsWith("/") ? "" : "/"}${path}`;
|
|
12011
|
+
const controller = new AbortController;
|
|
12012
|
+
let timedOut = false;
|
|
12013
|
+
const timer = setTimeout(() => {
|
|
12014
|
+
timedOut = true;
|
|
12015
|
+
controller.abort();
|
|
12016
|
+
}, timeoutMs);
|
|
12017
|
+
const abort = () => controller.abort();
|
|
12018
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
12019
|
+
try {
|
|
12020
|
+
const headers = new Headers({ "Content-Type": "application/json" });
|
|
12021
|
+
if (config.password) {
|
|
12022
|
+
headers.set("Authorization", `Basic ${Buffer.from(`${config.username || "elastic"}:${config.password}`).toString("base64")}`);
|
|
12023
|
+
}
|
|
12024
|
+
const response = await esFetchImpl(url, {
|
|
12025
|
+
method,
|
|
12026
|
+
headers,
|
|
12027
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
12028
|
+
signal: controller.signal,
|
|
12029
|
+
redirect: "error"
|
|
12030
|
+
});
|
|
12031
|
+
const text = await response.text();
|
|
12032
|
+
return {
|
|
12033
|
+
code: 0,
|
|
12034
|
+
stdout: `${text}
|
|
12035
|
+
__ES_STATUS__:${response.status}
|
|
12036
|
+
`,
|
|
12037
|
+
stderr: ""
|
|
12038
|
+
};
|
|
12039
|
+
} catch (error) {
|
|
12040
|
+
if (signal?.aborted)
|
|
12041
|
+
throw new Error("elasticsearch request aborted");
|
|
12042
|
+
return {
|
|
12043
|
+
code: 1,
|
|
12044
|
+
stdout: "",
|
|
12045
|
+
stderr: timedOut ? `elasticsearch request timed out after ${timeoutMs}ms` : error instanceof Error ? error.message : String(error)
|
|
12046
|
+
};
|
|
12047
|
+
} finally {
|
|
12048
|
+
clearTimeout(timer);
|
|
12049
|
+
signal?.removeEventListener("abort", abort);
|
|
12050
|
+
}
|
|
12051
|
+
}
|
|
11793
12052
|
const invocation = buildEsRequestInvocation(config, method, path, body);
|
|
11794
12053
|
const result = await spawnTextAsync({
|
|
11795
|
-
command:
|
|
12054
|
+
command: invocation.command,
|
|
11796
12055
|
args: invocation.args,
|
|
11797
12056
|
env: process.env,
|
|
11798
12057
|
input: invocation.input,
|
|
@@ -11802,7 +12061,8 @@ async function execEsRequestAsync(config, method, path, body, timeoutMs = 15000,
|
|
|
11802
12061
|
timeoutMessage: `elasticsearch request timed out after ${timeoutMs}ms`,
|
|
11803
12062
|
rejectOnError: false
|
|
11804
12063
|
});
|
|
11805
|
-
|
|
12064
|
+
if (config.containerName)
|
|
12065
|
+
throwIfDockerCommandUnavailableResult(result);
|
|
11806
12066
|
return result;
|
|
11807
12067
|
}
|
|
11808
12068
|
function parseEsResponse(stdout) {
|
|
@@ -11829,7 +12089,7 @@ function createElasticsearchAdapter(config) {
|
|
|
11829
12089
|
async function callJsonAsync(method, path, body, label, signal) {
|
|
11830
12090
|
const r = await execEsRequestAsync(config, method, path, body, 15000, signal);
|
|
11831
12091
|
if (r.code !== 0) {
|
|
11832
|
-
throw new Error(r.stderr.trim() || `${label}:
|
|
12092
|
+
throw new Error(r.stderr.trim() || `${label}: request failed`);
|
|
11833
12093
|
}
|
|
11834
12094
|
const { status, body: text } = parseEsResponse(r.stdout);
|
|
11835
12095
|
if (status < 200 || status >= 300) {
|
|
@@ -11914,7 +12174,7 @@ function createElasticsearchAdapter(config) {
|
|
|
11914
12174
|
}
|
|
11915
12175
|
const r = await execEsRequestAsync(config, "GET", `/${encodeURIComponent(opts.index)}/_doc/${encodeURIComponent(opts.id)}`, undefined, 15000, opts.signal);
|
|
11916
12176
|
if (r.code !== 0) {
|
|
11917
|
-
throw new Error(r.stderr.trim() ||
|
|
12177
|
+
throw new Error(r.stderr.trim() || "_doc: request failed");
|
|
11918
12178
|
}
|
|
11919
12179
|
const { status, body: text } = parseEsResponse(r.stdout);
|
|
11920
12180
|
if (status === 404) {
|
|
@@ -12013,7 +12273,7 @@ function createElasticsearchAdapter(config) {
|
|
|
12013
12273
|
const r = await execEsRequestAsync(config, input.method, input.path, input.body, 15000, signal);
|
|
12014
12274
|
const elapsedMs = Date.now() - start;
|
|
12015
12275
|
if (r.code !== 0) {
|
|
12016
|
-
throw new Error(r.stderr.trim() ||
|
|
12276
|
+
throw new Error(r.stderr.trim() || "query: request failed");
|
|
12017
12277
|
}
|
|
12018
12278
|
const { status, body: text } = parseEsResponse(r.stdout);
|
|
12019
12279
|
let body = text;
|
|
@@ -12099,10 +12359,11 @@ async function openElasticsearchAdapterAsync(serviceName, env, cwd, signal) {
|
|
|
12099
12359
|
const password = env.ELASTIC_PASSWORD || "";
|
|
12100
12360
|
return createElasticsearchAdapter({ containerName, password });
|
|
12101
12361
|
}
|
|
12102
|
-
var ES_QUERY_ALLOWED_SUBPATHS, ES_DEFAULT_SIZE = 200;
|
|
12362
|
+
var esFetchImpl, ES_QUERY_ALLOWED_SUBPATHS, ES_DEFAULT_SIZE = 200;
|
|
12103
12363
|
var init_elasticsearch = __esm(() => {
|
|
12104
12364
|
init_docker_utils();
|
|
12105
12365
|
init_spawn_runner();
|
|
12366
|
+
esFetchImpl = globalThis.fetch;
|
|
12106
12367
|
ES_QUERY_ALLOWED_SUBPATHS = new Set([
|
|
12107
12368
|
"_search",
|
|
12108
12369
|
"_count",
|
|
@@ -12119,9 +12380,14 @@ var exports_redis = {};
|
|
|
12119
12380
|
__export(exports_redis, {
|
|
12120
12381
|
openRedisExplorerAsync: () => openRedisExplorerAsync,
|
|
12121
12382
|
createRedisAdapter: () => createRedisAdapter,
|
|
12122
|
-
canonicalizeRedisSnapshotContainer: () => canonicalizeRedisSnapshotContainer
|
|
12383
|
+
canonicalizeRedisSnapshotContainer: () => canonicalizeRedisSnapshotContainer,
|
|
12384
|
+
__setRedisClientFactoryForTest: () => __setRedisClientFactoryForTest
|
|
12123
12385
|
});
|
|
12124
12386
|
import { createHash as createHash3 } from "node:crypto";
|
|
12387
|
+
import { createClient } from "@redis/client";
|
|
12388
|
+
function __setRedisClientFactoryForTest(factory) {
|
|
12389
|
+
createRedisClientImpl = factory ?? ((options) => createClient(options));
|
|
12390
|
+
}
|
|
12125
12391
|
function canonicalizeRedisSnapshotContainer(container) {
|
|
12126
12392
|
const { db, pattern } = parseSnapshotContainer(container);
|
|
12127
12393
|
return JSON.stringify({ db, pattern });
|
|
@@ -12139,20 +12405,25 @@ function parseSnapshotContainer(container) {
|
|
|
12139
12405
|
}
|
|
12140
12406
|
function buildRedisCliInvocation(config, args) {
|
|
12141
12407
|
const hasPassword = !!config.password;
|
|
12142
|
-
const dockerArgs = [
|
|
12143
|
-
"exec",
|
|
12144
|
-
"-i",
|
|
12145
|
-
...hasPassword ? ["-e", "REDISCLI_AUTH"] : [],
|
|
12146
|
-
config.containerName,
|
|
12147
|
-
"redis-cli",
|
|
12148
|
-
"-3",
|
|
12149
|
-
...args
|
|
12150
|
-
];
|
|
12151
12408
|
const spawnEnv = hasPassword ? { ...process.env, REDISCLI_AUTH: config.password } : process.env;
|
|
12152
|
-
|
|
12409
|
+
if (!config.containerName) {
|
|
12410
|
+
throw new Error("direct Redis connections use the built-in driver");
|
|
12411
|
+
}
|
|
12412
|
+
return {
|
|
12413
|
+
args: [
|
|
12414
|
+
"exec",
|
|
12415
|
+
"-i",
|
|
12416
|
+
...hasPassword ? ["-e", "REDISCLI_AUTH"] : [],
|
|
12417
|
+
config.containerName,
|
|
12418
|
+
"redis-cli",
|
|
12419
|
+
"-3",
|
|
12420
|
+
...args
|
|
12421
|
+
],
|
|
12422
|
+
env: spawnEnv
|
|
12423
|
+
};
|
|
12153
12424
|
}
|
|
12154
|
-
async function
|
|
12155
|
-
throwIfAborted(signal, "redis
|
|
12425
|
+
async function execRedisCliProcessAsync(config, args, timeoutMs = 1e4, signal) {
|
|
12426
|
+
throwIfAborted(signal, "redis request aborted");
|
|
12156
12427
|
const invocation = buildRedisCliInvocation(config, args);
|
|
12157
12428
|
const result = await spawnTextAsync({
|
|
12158
12429
|
command: dockerCommand(),
|
|
@@ -12160,13 +12431,115 @@ async function execRedisCliAsync(config, args, timeoutMs = 1e4, signal) {
|
|
|
12160
12431
|
env: invocation.env,
|
|
12161
12432
|
timeoutMs,
|
|
12162
12433
|
signal,
|
|
12163
|
-
abortMessage: "redis
|
|
12164
|
-
timeoutMessage: `redis
|
|
12434
|
+
abortMessage: "redis request aborted",
|
|
12435
|
+
timeoutMessage: `redis request timed out after ${timeoutMs}ms`,
|
|
12165
12436
|
rejectOnError: false
|
|
12166
12437
|
});
|
|
12167
|
-
|
|
12438
|
+
if (config.containerName)
|
|
12439
|
+
throwIfDockerCommandUnavailableResult(result);
|
|
12168
12440
|
return result;
|
|
12169
12441
|
}
|
|
12442
|
+
function redisReplyText(reply) {
|
|
12443
|
+
if (reply === null || reply === undefined)
|
|
12444
|
+
return "";
|
|
12445
|
+
if (reply instanceof Uint8Array)
|
|
12446
|
+
return Buffer.from(reply).toString("utf8");
|
|
12447
|
+
if (Array.isArray(reply))
|
|
12448
|
+
return JSON.stringify(reply);
|
|
12449
|
+
if (typeof reply === "object")
|
|
12450
|
+
return JSON.stringify(reply);
|
|
12451
|
+
return String(reply);
|
|
12452
|
+
}
|
|
12453
|
+
function createRedisDriverExecutor(config) {
|
|
12454
|
+
if (!config.host || !config.port) {
|
|
12455
|
+
throw new Error("direct Redis connection requires host and port");
|
|
12456
|
+
}
|
|
12457
|
+
const clients = new Map;
|
|
12458
|
+
function clientForDatabase(database) {
|
|
12459
|
+
const existing = clients.get(database);
|
|
12460
|
+
if (existing)
|
|
12461
|
+
return existing;
|
|
12462
|
+
const socket = config.tls ? {
|
|
12463
|
+
host: config.host,
|
|
12464
|
+
port: config.port,
|
|
12465
|
+
tls: true,
|
|
12466
|
+
connectTimeout: 1e4
|
|
12467
|
+
} : {
|
|
12468
|
+
host: config.host,
|
|
12469
|
+
port: config.port,
|
|
12470
|
+
connectTimeout: 1e4
|
|
12471
|
+
};
|
|
12472
|
+
const client = createRedisClientImpl({
|
|
12473
|
+
username: config.username || undefined,
|
|
12474
|
+
password: config.password || undefined,
|
|
12475
|
+
database,
|
|
12476
|
+
socket
|
|
12477
|
+
});
|
|
12478
|
+
client.on("error", () => {});
|
|
12479
|
+
const entry = { client, connected: client.connect() };
|
|
12480
|
+
clients.set(database, entry);
|
|
12481
|
+
return entry;
|
|
12482
|
+
}
|
|
12483
|
+
return {
|
|
12484
|
+
async exec(args, timeoutMs = 1e4, signal) {
|
|
12485
|
+
throwIfAborted(signal, "redis request aborted");
|
|
12486
|
+
let database = 0;
|
|
12487
|
+
let command = args;
|
|
12488
|
+
if (args[0] === "-n" && args.length >= 3) {
|
|
12489
|
+
database = Number(args[1]) || 0;
|
|
12490
|
+
command = args.slice(2);
|
|
12491
|
+
}
|
|
12492
|
+
const entry = clientForDatabase(database);
|
|
12493
|
+
let connectDisposed = false;
|
|
12494
|
+
const disposeConnectingClient = () => {
|
|
12495
|
+
if (connectDisposed)
|
|
12496
|
+
return;
|
|
12497
|
+
connectDisposed = true;
|
|
12498
|
+
if (clients.get(database) === entry)
|
|
12499
|
+
clients.delete(database);
|
|
12500
|
+
entry.client.destroy();
|
|
12501
|
+
};
|
|
12502
|
+
const abortConnect = () => disposeConnectingClient();
|
|
12503
|
+
signal?.addEventListener("abort", abortConnect, { once: true });
|
|
12504
|
+
const timeoutController = new AbortController;
|
|
12505
|
+
let timedOut = false;
|
|
12506
|
+
const timer = setTimeout(() => {
|
|
12507
|
+
timedOut = true;
|
|
12508
|
+
timeoutController.abort();
|
|
12509
|
+
}, timeoutMs);
|
|
12510
|
+
const abort = () => timeoutController.abort();
|
|
12511
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
12512
|
+
let connected = false;
|
|
12513
|
+
try {
|
|
12514
|
+
await waitForAbortableResource(entry.connected.then(() => entry.client), signal, disposeConnectingClient, "redis request aborted");
|
|
12515
|
+
connected = true;
|
|
12516
|
+
signal?.removeEventListener("abort", abortConnect);
|
|
12517
|
+
throwIfAborted(signal, "redis request aborted");
|
|
12518
|
+
const reply = await entry.client.withAbortSignal(timeoutController.signal).sendCommand(command);
|
|
12519
|
+
return { stdout: redisReplyText(reply), stderr: "", code: 0 };
|
|
12520
|
+
} catch (error) {
|
|
12521
|
+
if (!connected)
|
|
12522
|
+
disposeConnectingClient();
|
|
12523
|
+
if (signal?.aborted)
|
|
12524
|
+
throw abortError("redis request aborted");
|
|
12525
|
+
return {
|
|
12526
|
+
stdout: "",
|
|
12527
|
+
stderr: timedOut ? `redis request timed out after ${timeoutMs}ms` : error instanceof Error ? error.message : String(error),
|
|
12528
|
+
code: 1
|
|
12529
|
+
};
|
|
12530
|
+
} finally {
|
|
12531
|
+
clearTimeout(timer);
|
|
12532
|
+
signal?.removeEventListener("abort", abortConnect);
|
|
12533
|
+
signal?.removeEventListener("abort", abort);
|
|
12534
|
+
}
|
|
12535
|
+
},
|
|
12536
|
+
close() {
|
|
12537
|
+
for (const { client } of clients.values())
|
|
12538
|
+
client.destroy();
|
|
12539
|
+
clients.clear();
|
|
12540
|
+
}
|
|
12541
|
+
};
|
|
12542
|
+
}
|
|
12170
12543
|
function parseInfoKeyspace(stdout) {
|
|
12171
12544
|
const counts = new Map;
|
|
12172
12545
|
for (const line of stdout.split(/\r?\n/)) {
|
|
@@ -12201,6 +12574,8 @@ function decodeHexItem(hex) {
|
|
|
12201
12574
|
return { binaryBase64: buf.toString("base64") };
|
|
12202
12575
|
}
|
|
12203
12576
|
function createRedisAdapter(config) {
|
|
12577
|
+
const driver = config.containerName ? null : createRedisDriverExecutor(config);
|
|
12578
|
+
const execRedisCliAsync = (_config, args, timeoutMs = 1e4, signal) => driver ? driver.exec(args, timeoutMs, signal) : execRedisCliProcessAsync(config, args, timeoutMs, signal);
|
|
12204
12579
|
function parseDatabasesResult(result) {
|
|
12205
12580
|
if (result.code !== 0) {
|
|
12206
12581
|
throw new Error(result.stderr.trim() || "INFO keyspace failed");
|
|
@@ -12691,7 +13066,9 @@ function createRedisAdapter(config) {
|
|
|
12691
13066
|
deleteKeyAsync,
|
|
12692
13067
|
iterateForSnapshot,
|
|
12693
13068
|
listSnapshotContainers,
|
|
12694
|
-
close() {
|
|
13069
|
+
close() {
|
|
13070
|
+
driver?.close();
|
|
13071
|
+
}
|
|
12695
13072
|
};
|
|
12696
13073
|
}
|
|
12697
13074
|
async function openRedisExplorerAsync(serviceName, env, cwd, signal) {
|
|
@@ -12699,7 +13076,7 @@ async function openRedisExplorerAsync(serviceName, env, cwd, signal) {
|
|
|
12699
13076
|
const password = env.REDIS_PASSWORD || "";
|
|
12700
13077
|
return createRedisAdapter({ containerName, password });
|
|
12701
13078
|
}
|
|
12702
|
-
var DEFAULT_DATABASES = 16, REDIS_STRING_BYTE_LIMIT = 65536, REDIS_COLLECTION_LIMIT = 200, SCAN_WITH_TYPES_LUA = `local s = redis.call('SCAN', ARGV[1], 'MATCH', ARGV[2], 'COUNT', ARGV[3]); local types = {}; for i, k in ipairs(s[2]) do types[i] = redis.call('TYPE', k).ok end; return cjson.encode({cursor=s[1], keys=s[2], types=types})`, LUA_TOHEX_PRELUDE = `local function tohex(s) local t = {} for i = 1, #s do t[i] = string.format('%02x', string.byte(s, i)) end return table.concat(t) end`, TYPE_OR_STRING_LUA, LUA_HEX_KEY_PRELUDE = `local function tohex(s) local t = {} for i = 1, #s do t[i] = string.format('%02x', string.byte(s, i)) end return table.concat(t) end local function fromhex(h) local b = {} for i = 1, #h, 2 do b[#b+1] = string.char(tonumber(string.sub(h, i, i+1), 16)) end return table.concat(b) end`, SCAN_HEX_KEYS_LUA;
|
|
13079
|
+
var createRedisClientImpl = (options) => createClient(options), DEFAULT_DATABASES = 16, REDIS_STRING_BYTE_LIMIT = 65536, REDIS_COLLECTION_LIMIT = 200, SCAN_WITH_TYPES_LUA = `local s = redis.call('SCAN', ARGV[1], 'MATCH', ARGV[2], 'COUNT', ARGV[3]); local types = {}; for i, k in ipairs(s[2]) do types[i] = redis.call('TYPE', k).ok end; return cjson.encode({cursor=s[1], keys=s[2], types=types})`, LUA_TOHEX_PRELUDE = `local function tohex(s) local t = {} for i = 1, #s do t[i] = string.format('%02x', string.byte(s, i)) end return table.concat(t) end`, TYPE_OR_STRING_LUA, LUA_HEX_KEY_PRELUDE = `local function tohex(s) local t = {} for i = 1, #s do t[i] = string.format('%02x', string.byte(s, i)) end return table.concat(t) end local function fromhex(h) local b = {} for i = 1, #h, 2 do b[#b+1] = string.char(tonumber(string.sub(h, i, i+1), 16)) end return table.concat(b) end`, SCAN_HEX_KEYS_LUA;
|
|
12703
13080
|
var init_redis = __esm(() => {
|
|
12704
13081
|
init_docker_utils();
|
|
12705
13082
|
init_spawn_runner();
|
|
@@ -15779,185 +16156,33 @@ function asAsyncDoc(source) {
|
|
|
15779
16156
|
};
|
|
15780
16157
|
}
|
|
15781
16158
|
|
|
15782
|
-
// web-src/server/database/
|
|
15783
|
-
|
|
15784
|
-
|
|
16159
|
+
// web-src/server/database/adapters/dynamodb.ts
|
|
16160
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
16161
|
+
import { createHash as createHash5, createHmac as createHmac2 } from "node:crypto";
|
|
16162
|
+
function createDynamoDbRequestDeadline() {
|
|
16163
|
+
const timeoutMs = dynamoDbRequestTimeoutMs;
|
|
16164
|
+
return { expiresAt: Date.now() + timeoutMs, timeoutMs };
|
|
15785
16165
|
}
|
|
15786
|
-
function
|
|
15787
|
-
|
|
15788
|
-
|
|
15789
|
-
for (const [key, entry] of pool) {
|
|
15790
|
-
if (entry.lastUsed < oldestTime) {
|
|
15791
|
-
oldestTime = entry.lastUsed;
|
|
15792
|
-
oldestKey = key;
|
|
15793
|
-
}
|
|
15794
|
-
}
|
|
15795
|
-
if (oldestKey) {
|
|
15796
|
-
const entry = pool.get(oldestKey);
|
|
15797
|
-
if (entry) {
|
|
15798
|
-
clearTimeout(entry.timer);
|
|
15799
|
-
try {
|
|
15800
|
-
entry.adapter.close();
|
|
15801
|
-
} catch {}
|
|
15802
|
-
pool.delete(oldestKey);
|
|
15803
|
-
}
|
|
15804
|
-
}
|
|
16166
|
+
function createDynamoDbDockerCurlDeadline() {
|
|
16167
|
+
const timeoutMs = dynamoDbDockerCurlTimeoutMs;
|
|
16168
|
+
return { expiresAt: Date.now() + timeoutMs, timeoutMs };
|
|
15805
16169
|
}
|
|
15806
|
-
function
|
|
15807
|
-
|
|
15808
|
-
entry.timer = setTimeout(() => {
|
|
15809
|
-
const current = pool.get(key);
|
|
15810
|
-
if (current === entry) {
|
|
15811
|
-
try {
|
|
15812
|
-
current.adapter.close();
|
|
15813
|
-
} catch {}
|
|
15814
|
-
pool.delete(key);
|
|
15815
|
-
}
|
|
15816
|
-
}, IDLE_TIMEOUT_MS);
|
|
16170
|
+
function createDynamoDbTransportDeadline(config) {
|
|
16171
|
+
return config.dockerContainerName ? createDynamoDbDockerCurlDeadline() : createDynamoDbRequestDeadline();
|
|
15817
16172
|
}
|
|
15818
|
-
|
|
15819
|
-
|
|
15820
|
-
throw new Error("No adapter factory configured");
|
|
15821
|
-
}
|
|
15822
|
-
const existing = pool.get(resolvedPath);
|
|
15823
|
-
if (existing) {
|
|
15824
|
-
existing.lastUsed = Date.now();
|
|
15825
|
-
scheduleEviction(resolvedPath, existing);
|
|
15826
|
-
return existing.adapter;
|
|
15827
|
-
}
|
|
15828
|
-
if (pool.size >= MAX_CONNECTIONS) {
|
|
15829
|
-
evictOldest();
|
|
15830
|
-
}
|
|
15831
|
-
const adapter = await factory.open(resolvedPath);
|
|
15832
|
-
const entry = {
|
|
15833
|
-
adapter,
|
|
15834
|
-
path: resolvedPath,
|
|
15835
|
-
lastUsed: Date.now(),
|
|
15836
|
-
timer: setTimeout(() => {
|
|
15837
|
-
return;
|
|
15838
|
-
}, 0)
|
|
15839
|
-
};
|
|
15840
|
-
pool.set(resolvedPath, entry);
|
|
15841
|
-
scheduleEviction(resolvedPath, entry);
|
|
15842
|
-
return adapter;
|
|
16173
|
+
function dynamoDbTimeoutError(deadline) {
|
|
16174
|
+
return new DynamoDbHttpError(503, `DynamoDB request timed out after ${deadline?.timeoutMs ?? dynamoDbRequestTimeoutMs}ms`);
|
|
15843
16175
|
}
|
|
15844
|
-
function
|
|
15845
|
-
|
|
15846
|
-
|
|
15847
|
-
|
|
15848
|
-
clearTimeout(entry.timer);
|
|
15849
|
-
try {
|
|
15850
|
-
entry.adapter.close();
|
|
15851
|
-
} catch {}
|
|
15852
|
-
pool.delete(resolvedPath);
|
|
15853
|
-
return true;
|
|
16176
|
+
function remainingDynamoDbTimeoutMs(deadline) {
|
|
16177
|
+
if (!deadline)
|
|
16178
|
+
return dynamoDbRequestTimeoutMs;
|
|
16179
|
+
return Math.max(0, deadline.expiresAt - Date.now());
|
|
15854
16180
|
}
|
|
15855
|
-
|
|
15856
|
-
|
|
15857
|
-
IDLE_TIMEOUT_MS = 5 * 60 * 1000;
|
|
15858
|
-
pool = new Map;
|
|
15859
|
-
});
|
|
15860
|
-
|
|
15861
|
-
// web-src/server/database/global-search.ts
|
|
15862
|
-
function isTextLikeType(type) {
|
|
15863
|
-
const upper = type.toUpperCase();
|
|
15864
|
-
return upper.includes("CHAR") || upper.includes("TEXT") || upper.includes("VARCHAR") || upper.includes("CLOB") || upper.includes("STRING") || upper === "JSON" || upper === "JSONB" || upper === "XML" || upper === "UUID";
|
|
16181
|
+
function hmac2(key, value) {
|
|
16182
|
+
return createHmac2("sha256", key).update(value, "utf8").digest();
|
|
15865
16183
|
}
|
|
15866
|
-
function
|
|
15867
|
-
return
|
|
15868
|
-
}
|
|
15869
|
-
async function searchTableAsync(adapter, table, columns, term, maxHits, includeNonText, pkColumns, signal) {
|
|
15870
|
-
const kind = adapter.kind;
|
|
15871
|
-
const searchCols = includeNonText ? columns.filter((c) => c.type.toUpperCase() !== "BLOB" && c.type.toUpperCase() !== "BYTEA") : columns.filter((c) => isTextLikeType(c.type));
|
|
15872
|
-
if (searchCols.length === 0)
|
|
15873
|
-
return [];
|
|
15874
|
-
const escapedTerm = escapeLikeTerm(term);
|
|
15875
|
-
const tbl = sanitizeIdentifier(table, kind);
|
|
15876
|
-
const hits = [];
|
|
15877
|
-
const db = asAsync(adapter);
|
|
15878
|
-
for (const col of searchCols) {
|
|
15879
|
-
if (signal?.aborted)
|
|
15880
|
-
break;
|
|
15881
|
-
if (hits.length >= maxHits)
|
|
15882
|
-
break;
|
|
15883
|
-
const colId = sanitizeIdentifier(col.name, kind);
|
|
15884
|
-
const castCol = kind === "mysql" ? `CAST(${colId} AS CHAR)` : `CAST(${colId} AS TEXT)`;
|
|
15885
|
-
let sql;
|
|
15886
|
-
const remaining = maxHits - hits.length;
|
|
15887
|
-
if (kind === "sqlite") {
|
|
15888
|
-
sql = `SELECT * FROM ${tbl} WHERE ${castCol} LIKE ? ESCAPE '='`;
|
|
15889
|
-
} else {
|
|
15890
|
-
const likeVal = escapeSqlString(`%${escapedTerm}%`);
|
|
15891
|
-
sql = `SELECT * FROM ${tbl} WHERE ${castCol} LIKE ${likeVal} ESCAPE '='`;
|
|
15892
|
-
}
|
|
15893
|
-
try {
|
|
15894
|
-
const params = kind === "sqlite" ? [`%${escapedTerm}%`] : undefined;
|
|
15895
|
-
const result = await db.readonlyQuery(sql, params, remaining, signal);
|
|
15896
|
-
for (const row of result.rows) {
|
|
15897
|
-
const colIdx = result.columns.indexOf(col.name);
|
|
15898
|
-
const valueRaw = colIdx >= 0 ? serializeDbValue(row[colIdx]) : null;
|
|
15899
|
-
const valueStr = valueRaw == null ? "" : String(valueRaw);
|
|
15900
|
-
const preview = valueStr.length > 200 ? `${valueStr.slice(0, 200)}...` : valueStr;
|
|
15901
|
-
let rowKeyJson;
|
|
15902
|
-
if (pkColumns.length > 0) {
|
|
15903
|
-
const keyObj = {};
|
|
15904
|
-
for (const pk of pkColumns) {
|
|
15905
|
-
const pkIdx = result.columns.indexOf(pk);
|
|
15906
|
-
if (pkIdx >= 0)
|
|
15907
|
-
keyObj[pk] = serializeDbValue(row[pkIdx]);
|
|
15908
|
-
}
|
|
15909
|
-
rowKeyJson = JSON.stringify(keyObj);
|
|
15910
|
-
}
|
|
15911
|
-
hits.push({
|
|
15912
|
-
table,
|
|
15913
|
-
column: col.name,
|
|
15914
|
-
rowKeyJson,
|
|
15915
|
-
valuePreview: preview,
|
|
15916
|
-
rowPreview: serializeDbRow(row)
|
|
15917
|
-
});
|
|
15918
|
-
}
|
|
15919
|
-
} catch (err) {
|
|
15920
|
-
if (isAbortLikeError(err, signal))
|
|
15921
|
-
throw err;
|
|
15922
|
-
}
|
|
15923
|
-
}
|
|
15924
|
-
return hits;
|
|
15925
|
-
}
|
|
15926
|
-
function getPrimaryKeyColumnsFromColumns(columns) {
|
|
15927
|
-
return columns.filter((c) => c.primaryKey).map((c) => c.name);
|
|
15928
|
-
}
|
|
15929
|
-
var init_global_search = __esm(() => {
|
|
15930
|
-
init_serialize();
|
|
15931
|
-
init_sql_utils();
|
|
15932
|
-
});
|
|
15933
|
-
|
|
15934
|
-
// web-src/server/database/adapters/dynamodb.ts
|
|
15935
|
-
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
15936
|
-
import { createHash as createHash5, createHmac as createHmac2 } from "node:crypto";
|
|
15937
|
-
function createDynamoDbRequestDeadline() {
|
|
15938
|
-
const timeoutMs = dynamoDbRequestTimeoutMs;
|
|
15939
|
-
return { expiresAt: Date.now() + timeoutMs, timeoutMs };
|
|
15940
|
-
}
|
|
15941
|
-
function createDynamoDbDockerCurlDeadline() {
|
|
15942
|
-
const timeoutMs = dynamoDbDockerCurlTimeoutMs;
|
|
15943
|
-
return { expiresAt: Date.now() + timeoutMs, timeoutMs };
|
|
15944
|
-
}
|
|
15945
|
-
function createDynamoDbTransportDeadline(config) {
|
|
15946
|
-
return config.dockerContainerName ? createDynamoDbDockerCurlDeadline() : createDynamoDbRequestDeadline();
|
|
15947
|
-
}
|
|
15948
|
-
function dynamoDbTimeoutError(deadline) {
|
|
15949
|
-
return new DynamoDbHttpError(503, `DynamoDB request timed out after ${deadline?.timeoutMs ?? dynamoDbRequestTimeoutMs}ms`);
|
|
15950
|
-
}
|
|
15951
|
-
function remainingDynamoDbTimeoutMs(deadline) {
|
|
15952
|
-
if (!deadline)
|
|
15953
|
-
return dynamoDbRequestTimeoutMs;
|
|
15954
|
-
return Math.max(0, deadline.expiresAt - Date.now());
|
|
15955
|
-
}
|
|
15956
|
-
function hmac2(key, value) {
|
|
15957
|
-
return createHmac2("sha256", key).update(value, "utf8").digest();
|
|
15958
|
-
}
|
|
15959
|
-
function sha2562(value) {
|
|
15960
|
-
return createHash5("sha256").update(value, "utf8").digest("hex");
|
|
16184
|
+
function sha2562(value) {
|
|
16185
|
+
return createHash5("sha256").update(value, "utf8").digest("hex");
|
|
15961
16186
|
}
|
|
15962
16187
|
function amzDate2(date = new Date) {
|
|
15963
16188
|
const iso = date.toISOString().replace(/[:-]|\.\d{3}/g, "");
|
|
@@ -16393,6 +16618,416 @@ var init_dynamodb = __esm(() => {
|
|
|
16393
16618
|
dynamoDbDockerCurlTimeoutMs = DEFAULT_DYNAMODB_DOCKER_CURL_TIMEOUT_MS;
|
|
16394
16619
|
});
|
|
16395
16620
|
|
|
16621
|
+
// web-src/server/database/connection-pool.ts
|
|
16622
|
+
function setAdapterFactory(f) {
|
|
16623
|
+
factory = f;
|
|
16624
|
+
}
|
|
16625
|
+
function evictOldest() {
|
|
16626
|
+
let oldestKey = null;
|
|
16627
|
+
let oldestTime = Infinity;
|
|
16628
|
+
for (const [key, entry] of pool) {
|
|
16629
|
+
if (entry.lastUsed < oldestTime) {
|
|
16630
|
+
oldestTime = entry.lastUsed;
|
|
16631
|
+
oldestKey = key;
|
|
16632
|
+
}
|
|
16633
|
+
}
|
|
16634
|
+
if (oldestKey) {
|
|
16635
|
+
const entry = pool.get(oldestKey);
|
|
16636
|
+
if (entry) {
|
|
16637
|
+
clearTimeout(entry.timer);
|
|
16638
|
+
try {
|
|
16639
|
+
entry.adapter.close();
|
|
16640
|
+
} catch {}
|
|
16641
|
+
pool.delete(oldestKey);
|
|
16642
|
+
}
|
|
16643
|
+
}
|
|
16644
|
+
}
|
|
16645
|
+
function scheduleEviction(key, entry) {
|
|
16646
|
+
clearTimeout(entry.timer);
|
|
16647
|
+
entry.timer = setTimeout(() => {
|
|
16648
|
+
const current = pool.get(key);
|
|
16649
|
+
if (current === entry) {
|
|
16650
|
+
try {
|
|
16651
|
+
current.adapter.close();
|
|
16652
|
+
} catch {}
|
|
16653
|
+
pool.delete(key);
|
|
16654
|
+
}
|
|
16655
|
+
}, IDLE_TIMEOUT_MS);
|
|
16656
|
+
}
|
|
16657
|
+
async function getConnection(resolvedPath) {
|
|
16658
|
+
if (!factory) {
|
|
16659
|
+
throw new Error("No adapter factory configured");
|
|
16660
|
+
}
|
|
16661
|
+
const existing = pool.get(resolvedPath);
|
|
16662
|
+
if (existing) {
|
|
16663
|
+
existing.lastUsed = Date.now();
|
|
16664
|
+
scheduleEviction(resolvedPath, existing);
|
|
16665
|
+
return existing.adapter;
|
|
16666
|
+
}
|
|
16667
|
+
if (pool.size >= MAX_CONNECTIONS) {
|
|
16668
|
+
evictOldest();
|
|
16669
|
+
}
|
|
16670
|
+
const adapter = await factory.open(resolvedPath);
|
|
16671
|
+
const entry = {
|
|
16672
|
+
adapter,
|
|
16673
|
+
path: resolvedPath,
|
|
16674
|
+
lastUsed: Date.now(),
|
|
16675
|
+
timer: setTimeout(() => {
|
|
16676
|
+
return;
|
|
16677
|
+
}, 0)
|
|
16678
|
+
};
|
|
16679
|
+
pool.set(resolvedPath, entry);
|
|
16680
|
+
scheduleEviction(resolvedPath, entry);
|
|
16681
|
+
return adapter;
|
|
16682
|
+
}
|
|
16683
|
+
function closeConnection(resolvedPath) {
|
|
16684
|
+
const entry = pool.get(resolvedPath);
|
|
16685
|
+
if (!entry)
|
|
16686
|
+
return false;
|
|
16687
|
+
clearTimeout(entry.timer);
|
|
16688
|
+
try {
|
|
16689
|
+
entry.adapter.close();
|
|
16690
|
+
} catch {}
|
|
16691
|
+
pool.delete(resolvedPath);
|
|
16692
|
+
return true;
|
|
16693
|
+
}
|
|
16694
|
+
var MAX_CONNECTIONS = 8, IDLE_TIMEOUT_MS, pool, factory = null;
|
|
16695
|
+
var init_connection_pool = __esm(() => {
|
|
16696
|
+
IDLE_TIMEOUT_MS = 5 * 60 * 1000;
|
|
16697
|
+
pool = new Map;
|
|
16698
|
+
});
|
|
16699
|
+
|
|
16700
|
+
// web-src/server/database/connections-store.ts
|
|
16701
|
+
import { randomUUID } from "node:crypto";
|
|
16702
|
+
import { chmod } from "node:fs/promises";
|
|
16703
|
+
import { join as join11 } from "node:path";
|
|
16704
|
+
function secretKey(cwd, id) {
|
|
16705
|
+
return `${cwd}\x00${id}`;
|
|
16706
|
+
}
|
|
16707
|
+
function extractSecrets(value) {
|
|
16708
|
+
return {
|
|
16709
|
+
...typeof value.user === "string" ? { user: value.user } : {},
|
|
16710
|
+
...typeof value.username === "string" ? { username: value.username } : {},
|
|
16711
|
+
...typeof value.accessKeyId === "string" ? { accessKeyId: value.accessKeyId } : {},
|
|
16712
|
+
...typeof value.password === "string" ? { password: value.password } : {},
|
|
16713
|
+
...typeof value.secretAccessKey === "string" ? { secretAccessKey: value.secretAccessKey } : {},
|
|
16714
|
+
...typeof value.sessionToken === "string" ? { sessionToken: value.sessionToken } : {}
|
|
16715
|
+
};
|
|
16716
|
+
}
|
|
16717
|
+
function withRuntimeSecrets(cwd, connection) {
|
|
16718
|
+
return {
|
|
16719
|
+
...connection,
|
|
16720
|
+
...runtimeSecrets.get(secretKey(cwd, connection.id)) ?? {}
|
|
16721
|
+
};
|
|
16722
|
+
}
|
|
16723
|
+
function connectionsFilePath(root) {
|
|
16724
|
+
return join11(root, ".code-viewer", CONNECTIONS_FILE_NAME);
|
|
16725
|
+
}
|
|
16726
|
+
function emptyState() {
|
|
16727
|
+
return { version: 1, connections: [] };
|
|
16728
|
+
}
|
|
16729
|
+
function requiredString(value, maxLength = MAX_VALUE_LENGTH) {
|
|
16730
|
+
return typeof value === "string" && value.length <= maxLength ? value : "";
|
|
16731
|
+
}
|
|
16732
|
+
function optionalString2(value, maxLength = MAX_VALUE_LENGTH) {
|
|
16733
|
+
const normalized = requiredString(value, maxLength);
|
|
16734
|
+
return normalized || undefined;
|
|
16735
|
+
}
|
|
16736
|
+
function validPort(value) {
|
|
16737
|
+
const port = Number(value);
|
|
16738
|
+
return Number.isInteger(port) && port >= 1 && port <= 65535 ? port : null;
|
|
16739
|
+
}
|
|
16740
|
+
function validEndpoint(value) {
|
|
16741
|
+
const raw = requiredString(value, MAX_VALUE_LENGTH);
|
|
16742
|
+
if (!raw)
|
|
16743
|
+
return null;
|
|
16744
|
+
try {
|
|
16745
|
+
const url = new URL(raw);
|
|
16746
|
+
if (url.protocol !== "http:" && url.protocol !== "https:" || url.username || url.password) {
|
|
16747
|
+
return null;
|
|
16748
|
+
}
|
|
16749
|
+
return url.toString().replace(/\/$/, "");
|
|
16750
|
+
} catch {
|
|
16751
|
+
return null;
|
|
16752
|
+
}
|
|
16753
|
+
}
|
|
16754
|
+
function sanitizeConnection(raw) {
|
|
16755
|
+
if (!raw || typeof raw !== "object")
|
|
16756
|
+
return null;
|
|
16757
|
+
const input = raw;
|
|
16758
|
+
const id = requiredString(input.id, 80);
|
|
16759
|
+
const name = requiredString(input.name, MAX_NAME_LENGTH).trim();
|
|
16760
|
+
if (!/^connection:[a-f0-9-]{16,64}$/.test(id) || !name)
|
|
16761
|
+
return null;
|
|
16762
|
+
const base = { id, name };
|
|
16763
|
+
if (input.kind === "postgresql" || input.kind === "mysql") {
|
|
16764
|
+
const host = requiredString(input.host, MAX_HOST_LENGTH).trim();
|
|
16765
|
+
const port = validPort(input.port);
|
|
16766
|
+
const user = requiredString(input.user).trim();
|
|
16767
|
+
const database = requiredString(input.database).trim();
|
|
16768
|
+
if (!host || !port || !database)
|
|
16769
|
+
return null;
|
|
16770
|
+
return {
|
|
16771
|
+
...base,
|
|
16772
|
+
kind: input.kind,
|
|
16773
|
+
host,
|
|
16774
|
+
port,
|
|
16775
|
+
user,
|
|
16776
|
+
password: requiredString(input.password),
|
|
16777
|
+
database,
|
|
16778
|
+
...optionalString2(input.schema)?.trim() ? { schema: optionalString2(input.schema)?.trim() } : {},
|
|
16779
|
+
tls: input.tls === true
|
|
16780
|
+
};
|
|
16781
|
+
}
|
|
16782
|
+
if (input.kind === "redis") {
|
|
16783
|
+
const host = requiredString(input.host, MAX_HOST_LENGTH).trim();
|
|
16784
|
+
const port = validPort(input.port);
|
|
16785
|
+
if (!host || !port)
|
|
16786
|
+
return null;
|
|
16787
|
+
return {
|
|
16788
|
+
...base,
|
|
16789
|
+
kind: "redis",
|
|
16790
|
+
host,
|
|
16791
|
+
port,
|
|
16792
|
+
...optionalString2(input.username)?.trim() ? { username: optionalString2(input.username)?.trim() } : {},
|
|
16793
|
+
password: requiredString(input.password),
|
|
16794
|
+
tls: input.tls === true
|
|
16795
|
+
};
|
|
16796
|
+
}
|
|
16797
|
+
if (input.kind === "elasticsearch") {
|
|
16798
|
+
const endpoint = validEndpoint(input.endpoint);
|
|
16799
|
+
if (!endpoint)
|
|
16800
|
+
return null;
|
|
16801
|
+
return {
|
|
16802
|
+
...base,
|
|
16803
|
+
kind: "elasticsearch",
|
|
16804
|
+
endpoint,
|
|
16805
|
+
...optionalString2(input.username)?.trim() ? { username: optionalString2(input.username)?.trim() } : {},
|
|
16806
|
+
password: requiredString(input.password)
|
|
16807
|
+
};
|
|
16808
|
+
}
|
|
16809
|
+
if (input.kind === "s3" || input.kind === "dynamodb") {
|
|
16810
|
+
const endpoint = validEndpoint(input.endpoint);
|
|
16811
|
+
const region = requiredString(input.region).trim();
|
|
16812
|
+
const accessKeyId = requiredString(input.accessKeyId).trim();
|
|
16813
|
+
if (!endpoint || !region)
|
|
16814
|
+
return null;
|
|
16815
|
+
return {
|
|
16816
|
+
...base,
|
|
16817
|
+
kind: input.kind,
|
|
16818
|
+
endpoint,
|
|
16819
|
+
region,
|
|
16820
|
+
accessKeyId,
|
|
16821
|
+
secretAccessKey: requiredString(input.secretAccessKey),
|
|
16822
|
+
...optionalString2(input.sessionToken) ? { sessionToken: optionalString2(input.sessionToken) } : {}
|
|
16823
|
+
};
|
|
16824
|
+
}
|
|
16825
|
+
return null;
|
|
16826
|
+
}
|
|
16827
|
+
function validateDatastoreConnection(raw, fallbackId = "connection:0000000000000000") {
|
|
16828
|
+
const input = raw && typeof raw === "object" ? raw : {};
|
|
16829
|
+
const connection = sanitizeConnection({
|
|
16830
|
+
...input,
|
|
16831
|
+
id: typeof input.id === "string" && input.id ? input.id : fallbackId
|
|
16832
|
+
});
|
|
16833
|
+
if (!connection || (connection.kind === "postgresql" || connection.kind === "mysql") && !connection.user || (connection.kind === "s3" || connection.kind === "dynamodb") && !connection.accessKeyId) {
|
|
16834
|
+
throw new Error("invalid datastore connection");
|
|
16835
|
+
}
|
|
16836
|
+
return connection;
|
|
16837
|
+
}
|
|
16838
|
+
function sanitizeState(raw) {
|
|
16839
|
+
if (!raw || typeof raw !== "object")
|
|
16840
|
+
return emptyState();
|
|
16841
|
+
const input = raw;
|
|
16842
|
+
if (input.version !== 1 || !Array.isArray(input.connections)) {
|
|
16843
|
+
return emptyState();
|
|
16844
|
+
}
|
|
16845
|
+
const seen = new Set;
|
|
16846
|
+
const connections = [];
|
|
16847
|
+
for (const candidate of input.connections) {
|
|
16848
|
+
if (connections.length >= MAX_CONNECTIONS2)
|
|
16849
|
+
break;
|
|
16850
|
+
const connection = sanitizeConnection(candidate);
|
|
16851
|
+
if (!connection || seen.has(connection.id))
|
|
16852
|
+
continue;
|
|
16853
|
+
seen.add(connection.id);
|
|
16854
|
+
connections.push(connection);
|
|
16855
|
+
}
|
|
16856
|
+
return { version: 1, connections };
|
|
16857
|
+
}
|
|
16858
|
+
async function protectFile(cwd) {
|
|
16859
|
+
await chmod(connectionsFilePath(cwd), 384).catch(() => {
|
|
16860
|
+
return;
|
|
16861
|
+
});
|
|
16862
|
+
}
|
|
16863
|
+
async function loadDatastoreConnections(cwd) {
|
|
16864
|
+
return (await store.load(cwd)).connections.map((connection) => withRuntimeSecrets(cwd, connection));
|
|
16865
|
+
}
|
|
16866
|
+
async function findDatastoreConnection(cwd, id) {
|
|
16867
|
+
return (await loadDatastoreConnections(cwd)).find((entry) => entry.id === id) ?? null;
|
|
16868
|
+
}
|
|
16869
|
+
async function saveDatastoreConnection(cwd, raw) {
|
|
16870
|
+
const input = raw && typeof raw === "object" ? raw : {};
|
|
16871
|
+
const requestedId = typeof input.id === "string" ? input.id : "";
|
|
16872
|
+
const id = requestedId || `connection:${randomUUID()}`;
|
|
16873
|
+
const result = await store.update(cwd, (state) => {
|
|
16874
|
+
const storedExisting = state.connections.find((entry) => entry.id === id);
|
|
16875
|
+
const existing = storedExisting ? withRuntimeSecrets(cwd, storedExisting) : undefined;
|
|
16876
|
+
const merged = sanitizeConnection({
|
|
16877
|
+
...existing ?? {},
|
|
16878
|
+
...input,
|
|
16879
|
+
id,
|
|
16880
|
+
password: input.password === undefined && existing && "password" in existing ? existing.password : input.password,
|
|
16881
|
+
secretAccessKey: input.secretAccessKey === undefined && existing && "secretAccessKey" in existing ? existing.secretAccessKey : input.secretAccessKey,
|
|
16882
|
+
sessionToken: input.sessionToken === undefined && existing && "sessionToken" in existing ? existing.sessionToken : input.sessionToken
|
|
16883
|
+
});
|
|
16884
|
+
if (!merged || (merged.kind === "postgresql" || merged.kind === "mysql") && !merged.user || (merged.kind === "s3" || merged.kind === "dynamodb") && !merged.accessKeyId) {
|
|
16885
|
+
throw new Error("invalid datastore connection");
|
|
16886
|
+
}
|
|
16887
|
+
const connections = state.connections.filter((entry) => entry.id !== id);
|
|
16888
|
+
if (!storedExisting && connections.length >= MAX_CONNECTIONS2) {
|
|
16889
|
+
throw new Error("too many datastore connections");
|
|
16890
|
+
}
|
|
16891
|
+
connections.push(merged);
|
|
16892
|
+
return {
|
|
16893
|
+
state: { version: 1, connections },
|
|
16894
|
+
result: merged
|
|
16895
|
+
};
|
|
16896
|
+
});
|
|
16897
|
+
runtimeSecrets.set(secretKey(cwd, result.id), extractSecrets(result));
|
|
16898
|
+
await protectFile(cwd);
|
|
16899
|
+
return result;
|
|
16900
|
+
}
|
|
16901
|
+
async function deleteDatastoreConnection(cwd, id) {
|
|
16902
|
+
const deleted = await store.update(cwd, (state) => {
|
|
16903
|
+
const connections = state.connections.filter((entry) => entry.id !== id);
|
|
16904
|
+
return {
|
|
16905
|
+
state: { version: 1, connections },
|
|
16906
|
+
result: connections.length !== state.connections.length
|
|
16907
|
+
};
|
|
16908
|
+
});
|
|
16909
|
+
runtimeSecrets.delete(secretKey(cwd, id));
|
|
16910
|
+
await protectFile(cwd);
|
|
16911
|
+
return deleted;
|
|
16912
|
+
}
|
|
16913
|
+
function connectionToFileInfo(connection) {
|
|
16914
|
+
return {
|
|
16915
|
+
id: connection.id,
|
|
16916
|
+
path: "saved connection",
|
|
16917
|
+
name: connection.name,
|
|
16918
|
+
sizeBytes: 0,
|
|
16919
|
+
kind: connection.kind,
|
|
16920
|
+
savedConnection: true
|
|
16921
|
+
};
|
|
16922
|
+
}
|
|
16923
|
+
function publicConnection(connection) {
|
|
16924
|
+
const {
|
|
16925
|
+
password: _password,
|
|
16926
|
+
user: _user,
|
|
16927
|
+
username: _username,
|
|
16928
|
+
accessKeyId: _accessKeyId,
|
|
16929
|
+
...withoutPassword
|
|
16930
|
+
} = connection;
|
|
16931
|
+
const {
|
|
16932
|
+
secretAccessKey: _secret,
|
|
16933
|
+
sessionToken: _token,
|
|
16934
|
+
...safe
|
|
16935
|
+
} = withoutPassword;
|
|
16936
|
+
return safe;
|
|
16937
|
+
}
|
|
16938
|
+
var CONNECTIONS_FILE_NAME = "datastore-connections.json", MAX_CONNECTIONS2 = 64, MAX_JSON_BYTES, MAX_NAME_LENGTH = 120, MAX_HOST_LENGTH = 253, MAX_VALUE_LENGTH = 4096, runtimeSecrets, store;
|
|
16939
|
+
var init_connections_store = __esm(() => {
|
|
16940
|
+
init_json_store();
|
|
16941
|
+
MAX_JSON_BYTES = 256 * 1024;
|
|
16942
|
+
runtimeSecrets = new Map;
|
|
16943
|
+
store = createJsonFileStore({
|
|
16944
|
+
filePath: connectionsFilePath,
|
|
16945
|
+
empty: emptyState,
|
|
16946
|
+
sanitize: sanitizeState,
|
|
16947
|
+
maxBytes: MAX_JSON_BYTES,
|
|
16948
|
+
backupSuffix: "bak",
|
|
16949
|
+
sizeErrorMessage: "datastore connections state too large",
|
|
16950
|
+
serialize: (state) => `${JSON.stringify({
|
|
16951
|
+
version: 1,
|
|
16952
|
+
connections: state.connections.map(publicConnection)
|
|
16953
|
+
}, null, 2)}
|
|
16954
|
+
`
|
|
16955
|
+
});
|
|
16956
|
+
});
|
|
16957
|
+
|
|
16958
|
+
// web-src/server/database/global-search.ts
|
|
16959
|
+
function isTextLikeType(type) {
|
|
16960
|
+
const upper = type.toUpperCase();
|
|
16961
|
+
return upper.includes("CHAR") || upper.includes("TEXT") || upper.includes("VARCHAR") || upper.includes("CLOB") || upper.includes("STRING") || upper === "JSON" || upper === "JSONB" || upper === "XML" || upper === "UUID";
|
|
16962
|
+
}
|
|
16963
|
+
function escapeLikeTerm(term) {
|
|
16964
|
+
return term.replace(/=/g, "==").replace(/%/g, "=%").replace(/_/g, "=_");
|
|
16965
|
+
}
|
|
16966
|
+
async function searchTableAsync(adapter, table, columns, term, maxHits, includeNonText, pkColumns, signal) {
|
|
16967
|
+
const kind = adapter.kind;
|
|
16968
|
+
const searchCols = includeNonText ? columns.filter((c) => c.type.toUpperCase() !== "BLOB" && c.type.toUpperCase() !== "BYTEA") : columns.filter((c) => isTextLikeType(c.type));
|
|
16969
|
+
if (searchCols.length === 0)
|
|
16970
|
+
return [];
|
|
16971
|
+
const escapedTerm = escapeLikeTerm(term);
|
|
16972
|
+
const tbl = sanitizeIdentifier(table, kind);
|
|
16973
|
+
const hits = [];
|
|
16974
|
+
const db = asAsync(adapter);
|
|
16975
|
+
for (const col of searchCols) {
|
|
16976
|
+
if (signal?.aborted)
|
|
16977
|
+
break;
|
|
16978
|
+
if (hits.length >= maxHits)
|
|
16979
|
+
break;
|
|
16980
|
+
const colId = sanitizeIdentifier(col.name, kind);
|
|
16981
|
+
const castCol = kind === "mysql" ? `CAST(${colId} AS CHAR)` : `CAST(${colId} AS TEXT)`;
|
|
16982
|
+
let sql;
|
|
16983
|
+
const remaining = maxHits - hits.length;
|
|
16984
|
+
if (kind === "sqlite") {
|
|
16985
|
+
sql = `SELECT * FROM ${tbl} WHERE ${castCol} LIKE ? ESCAPE '='`;
|
|
16986
|
+
} else {
|
|
16987
|
+
const likeVal = escapeSqlString(`%${escapedTerm}%`);
|
|
16988
|
+
sql = `SELECT * FROM ${tbl} WHERE ${castCol} LIKE ${likeVal} ESCAPE '='`;
|
|
16989
|
+
}
|
|
16990
|
+
try {
|
|
16991
|
+
const params = kind === "sqlite" ? [`%${escapedTerm}%`] : undefined;
|
|
16992
|
+
const result = await db.readonlyQuery(sql, params, remaining, signal);
|
|
16993
|
+
for (const row of result.rows) {
|
|
16994
|
+
const colIdx = result.columns.indexOf(col.name);
|
|
16995
|
+
const valueRaw = colIdx >= 0 ? serializeDbValue(row[colIdx]) : null;
|
|
16996
|
+
const valueStr = valueRaw == null ? "" : String(valueRaw);
|
|
16997
|
+
const preview = valueStr.length > 200 ? `${valueStr.slice(0, 200)}...` : valueStr;
|
|
16998
|
+
let rowKeyJson;
|
|
16999
|
+
if (pkColumns.length > 0) {
|
|
17000
|
+
const keyObj = {};
|
|
17001
|
+
for (const pk of pkColumns) {
|
|
17002
|
+
const pkIdx = result.columns.indexOf(pk);
|
|
17003
|
+
if (pkIdx >= 0)
|
|
17004
|
+
keyObj[pk] = serializeDbValue(row[pkIdx]);
|
|
17005
|
+
}
|
|
17006
|
+
rowKeyJson = JSON.stringify(keyObj);
|
|
17007
|
+
}
|
|
17008
|
+
hits.push({
|
|
17009
|
+
table,
|
|
17010
|
+
column: col.name,
|
|
17011
|
+
rowKeyJson,
|
|
17012
|
+
valuePreview: preview,
|
|
17013
|
+
rowPreview: serializeDbRow(row)
|
|
17014
|
+
});
|
|
17015
|
+
}
|
|
17016
|
+
} catch (err) {
|
|
17017
|
+
if (isAbortLikeError(err, signal))
|
|
17018
|
+
throw err;
|
|
17019
|
+
}
|
|
17020
|
+
}
|
|
17021
|
+
return hits;
|
|
17022
|
+
}
|
|
17023
|
+
function getPrimaryKeyColumnsFromColumns(columns) {
|
|
17024
|
+
return columns.filter((c) => c.primaryKey).map((c) => c.name);
|
|
17025
|
+
}
|
|
17026
|
+
var init_global_search = __esm(() => {
|
|
17027
|
+
init_serialize();
|
|
17028
|
+
init_sql_utils();
|
|
17029
|
+
});
|
|
17030
|
+
|
|
16396
17031
|
// web-src/server/database/handle-shared.ts
|
|
16397
17032
|
function createDockerAdapterCache(maxEntries = DEFAULT_MAX_DOCKER_ADAPTER_CACHE, idleMs = DEFAULT_DOCKER_ADAPTER_IDLE_MS) {
|
|
16398
17033
|
const cache = new Map;
|
|
@@ -16642,6 +17277,24 @@ async function resolveDockerExplorerAsync(cwd, dbParam, kind, cache, openFn, omi
|
|
|
16642
17277
|
}
|
|
16643
17278
|
return { dbId: dbParam, explorer };
|
|
16644
17279
|
}
|
|
17280
|
+
async function resolveDatastoreExplorerAsync(cwd, dbParam, kind, cache, openDocker, openSaved, omitDirNames, signal) {
|
|
17281
|
+
if (dbParam?.startsWith("connection:")) {
|
|
17282
|
+
const connection = await findDatastoreConnection(cwd, dbParam);
|
|
17283
|
+
if (!connection || connection.kind !== kind) {
|
|
17284
|
+
return textError(`${kind} connection not found`, 404);
|
|
17285
|
+
}
|
|
17286
|
+
try {
|
|
17287
|
+
const explorer = await waitForCallerAbort(cache.getOrOpenAsync(dbParam, () => openSaved(connection)), signal, `${kind} open aborted`);
|
|
17288
|
+
return { dbId: dbParam, explorer };
|
|
17289
|
+
} catch (err) {
|
|
17290
|
+
if (isAbortLikeError(err, signal)) {
|
|
17291
|
+
return textError(`${kind} open aborted`, 503);
|
|
17292
|
+
}
|
|
17293
|
+
throw err;
|
|
17294
|
+
}
|
|
17295
|
+
}
|
|
17296
|
+
return resolveDockerExplorerAsync(cwd, dbParam, kind, cache, openDocker, omitDirNames, signal);
|
|
17297
|
+
}
|
|
16645
17298
|
async function dispatchRoutes(req, url, routes, sideEffectAllowed, wrap = (res) => res, handleRouteError) {
|
|
16646
17299
|
if (!Object.prototype.hasOwnProperty.call(routes, url.pathname))
|
|
16647
17300
|
return null;
|
|
@@ -16688,6 +17341,7 @@ function handleError(prefix, action, err, signal) {
|
|
|
16688
17341
|
var DEFAULT_MAX_DOCKER_ADAPTER_CACHE = 8, DEFAULT_DOCKER_ADAPTER_IDLE_MS, MAX_LOGGED_ERROR_BODY = 500, logQueue;
|
|
16689
17342
|
var init_handle_shared = __esm(() => {
|
|
16690
17343
|
init_docker_utils();
|
|
17344
|
+
init_connections_store();
|
|
16691
17345
|
init_discovery();
|
|
16692
17346
|
DEFAULT_DOCKER_ADAPTER_IDLE_MS = 5 * 60 * 1000;
|
|
16693
17347
|
logQueue = Promise.resolve();
|
|
@@ -16703,7 +17357,12 @@ function closeDynamoDbAdapter(dbId) {
|
|
|
16703
17357
|
dynamoDbAdapterCache.close(dbId);
|
|
16704
17358
|
}
|
|
16705
17359
|
function resolveDynamoDb(cwd, dbParam, signal, omitDirNames) {
|
|
16706
|
-
return
|
|
17360
|
+
return resolveDatastoreExplorerAsync(cwd, dbParam, "dynamodb", dynamoDbAdapterCache, (info) => openDynamoDbExplorerAsync(info), (connection) => {
|
|
17361
|
+
if (connection.kind !== "dynamodb") {
|
|
17362
|
+
throw new Error("invalid DynamoDB connection");
|
|
17363
|
+
}
|
|
17364
|
+
return createDynamoDbAdapter(connection);
|
|
17365
|
+
}, omitDirNames, signal);
|
|
16707
17366
|
}
|
|
16708
17367
|
function dynamoDbErrorResponse(err, action, signal) {
|
|
16709
17368
|
if (isAbortLikeError(err, signal)) {
|
|
@@ -16944,7 +17603,12 @@ function closeElasticsearchAdapter(dbId) {
|
|
|
16944
17603
|
esAdapterCache.close(dbId);
|
|
16945
17604
|
}
|
|
16946
17605
|
function resolveEs(cwd, dbParam, signal, omitDirNames) {
|
|
16947
|
-
return
|
|
17606
|
+
return resolveDatastoreExplorerAsync(cwd, dbParam, "elasticsearch", esAdapterCache, (info) => openElasticsearchAdapterAsync(info.serviceName, info.env, info.composeDir), (connection) => {
|
|
17607
|
+
if (connection.kind !== "elasticsearch") {
|
|
17608
|
+
throw new Error("invalid Elasticsearch connection");
|
|
17609
|
+
}
|
|
17610
|
+
return createElasticsearchAdapter(connection);
|
|
17611
|
+
}, omitDirNames, signal);
|
|
16948
17612
|
}
|
|
16949
17613
|
async function handleIndices(req, cwd, url, omitDirNames) {
|
|
16950
17614
|
const r = await resolveEs(cwd, url.searchParams.get("db"), req.signal, omitDirNames);
|
|
@@ -17189,7 +17853,11 @@ function closeRedisAdapter(dbId) {
|
|
|
17189
17853
|
redisAdapterCache.close(dbId);
|
|
17190
17854
|
}
|
|
17191
17855
|
function resolveRedis(cwd, dbParam, signal, omitDirNames) {
|
|
17192
|
-
return
|
|
17856
|
+
return resolveDatastoreExplorerAsync(cwd, dbParam, "redis", redisAdapterCache, (info) => openRedisExplorerAsync(info.serviceName, info.env, info.composeDir), (connection) => {
|
|
17857
|
+
if (connection.kind !== "redis")
|
|
17858
|
+
throw new Error("invalid Redis connection");
|
|
17859
|
+
return createRedisAdapter(connection);
|
|
17860
|
+
}, omitDirNames, signal);
|
|
17193
17861
|
}
|
|
17194
17862
|
async function handleDatabases(req, cwd, url, omitDirNames) {
|
|
17195
17863
|
const r = await resolveRedis(cwd, url.searchParams.get("db"), req.signal, omitDirNames);
|
|
@@ -17381,7 +18049,11 @@ function closeS3Adapter(dbId) {
|
|
|
17381
18049
|
s3AdapterCache.close(dbId);
|
|
17382
18050
|
}
|
|
17383
18051
|
function resolveS3(cwd, dbParam, signal, omitDirNames) {
|
|
17384
|
-
return
|
|
18052
|
+
return resolveDatastoreExplorerAsync(cwd, dbParam, "s3", s3AdapterCache, (info) => openS3ExplorerAsync(info), (connection) => {
|
|
18053
|
+
if (connection.kind !== "s3")
|
|
18054
|
+
throw new Error("invalid S3 connection");
|
|
18055
|
+
return createS3Adapter(connection);
|
|
18056
|
+
}, omitDirNames, signal);
|
|
17385
18057
|
}
|
|
17386
18058
|
function validateBucket(value) {
|
|
17387
18059
|
if (!value)
|
|
@@ -17831,11 +18503,11 @@ var init_handle_s3 = __esm(() => {
|
|
|
17831
18503
|
});
|
|
17832
18504
|
|
|
17833
18505
|
// web-src/server/database/query-history.ts
|
|
17834
|
-
import { join as
|
|
18506
|
+
import { join as join12 } from "node:path";
|
|
17835
18507
|
function historyFilePath(root) {
|
|
17836
|
-
return
|
|
18508
|
+
return join12(root, CODE_VIEWER_DIR3, HISTORY_FILE_NAME);
|
|
17837
18509
|
}
|
|
17838
|
-
function
|
|
18510
|
+
function emptyState2() {
|
|
17839
18511
|
return { version: 1, entries: [] };
|
|
17840
18512
|
}
|
|
17841
18513
|
function serializeHistoryState(state) {
|
|
@@ -17845,14 +18517,14 @@ function serializeHistoryState(state) {
|
|
|
17845
18517
|
};
|
|
17846
18518
|
let content = `${JSON.stringify(normalized, null, 2)}
|
|
17847
18519
|
`;
|
|
17848
|
-
while (normalized.entries.length > 1 && Buffer.byteLength(content, "utf8") >
|
|
18520
|
+
while (normalized.entries.length > 1 && Buffer.byteLength(content, "utf8") > MAX_JSON_BYTES2) {
|
|
17849
18521
|
normalized.entries.pop();
|
|
17850
18522
|
content = `${JSON.stringify(normalized, null, 2)}
|
|
17851
18523
|
`;
|
|
17852
18524
|
}
|
|
17853
18525
|
return content;
|
|
17854
18526
|
}
|
|
17855
|
-
function
|
|
18527
|
+
function optionalString3(value, maxLen) {
|
|
17856
18528
|
if (typeof value !== "string")
|
|
17857
18529
|
return;
|
|
17858
18530
|
if (!value || value.length > maxLen || value.includes("\x00"))
|
|
@@ -17883,16 +18555,16 @@ function sanitizeEntry(raw) {
|
|
|
17883
18555
|
if (!raw || typeof raw !== "object")
|
|
17884
18556
|
return null;
|
|
17885
18557
|
const entry = raw;
|
|
17886
|
-
const id =
|
|
17887
|
-
const dbId =
|
|
17888
|
-
const sql =
|
|
18558
|
+
const id = optionalString3(entry.id, MAX_ID_LEN);
|
|
18559
|
+
const dbId = optionalString3(entry.dbId, MAX_DB_ID_LEN);
|
|
18560
|
+
const sql = optionalString3(entry.sql, MAX_SQL_LEN);
|
|
17889
18561
|
if (!id || !dbId || !sql)
|
|
17890
18562
|
return null;
|
|
17891
18563
|
const columns = Array.isArray(entry.columns) ? entry.columns.filter((col) => typeof col === "string" && !!col).map((col) => col.slice(0, MAX_COLUMN_LEN)).slice(0, MAX_COLUMNS) : [];
|
|
17892
18564
|
const rowsPreview = sanitizeRows(entry.rowsPreview);
|
|
17893
|
-
const schema =
|
|
17894
|
-
const title =
|
|
17895
|
-
const body =
|
|
18565
|
+
const schema = optionalString3(entry.schema, MAX_SCHEMA_LEN);
|
|
18566
|
+
const title = optionalString3(entry.title, MAX_TEXT_LEN);
|
|
18567
|
+
const body = optionalString3(entry.body, MAX_TEXT_LEN);
|
|
17896
18568
|
return {
|
|
17897
18569
|
id,
|
|
17898
18570
|
dbId,
|
|
@@ -17906,17 +18578,17 @@ function sanitizeEntry(raw) {
|
|
|
17906
18578
|
savedRows: finiteNumber(entry.savedRows) ?? rowsPreview.length,
|
|
17907
18579
|
truncated: typeof entry.truncated === "boolean" ? entry.truncated : false,
|
|
17908
18580
|
elapsedMs: finiteNumber(entry.elapsedMs) ?? 0,
|
|
17909
|
-
executedAt:
|
|
18581
|
+
executedAt: optionalString3(entry.executedAt, 64) ?? new Date(0).toISOString(),
|
|
17910
18582
|
executedBy: entry.executedBy === "ai" ? "ai" : "user",
|
|
17911
18583
|
source: entry.source === "cli" ? "cli" : "browser"
|
|
17912
18584
|
};
|
|
17913
18585
|
}
|
|
17914
18586
|
function sanitizeHistoryState(raw) {
|
|
17915
18587
|
if (!raw || typeof raw !== "object")
|
|
17916
|
-
return
|
|
18588
|
+
return emptyState2();
|
|
17917
18589
|
const entriesRaw = raw.entries;
|
|
17918
18590
|
if (!Array.isArray(entriesRaw))
|
|
17919
|
-
return
|
|
18591
|
+
return emptyState2();
|
|
17920
18592
|
const entries = [];
|
|
17921
18593
|
for (const entry of entriesRaw) {
|
|
17922
18594
|
if (entries.length >= MAX_ENTRIES2)
|
|
@@ -17955,7 +18627,7 @@ function deleteQueryHistoryEntry(state, id) {
|
|
|
17955
18627
|
}
|
|
17956
18628
|
function clearQueryHistory(state, dbId, schema) {
|
|
17957
18629
|
if (!dbId)
|
|
17958
|
-
return
|
|
18630
|
+
return emptyState2();
|
|
17959
18631
|
return {
|
|
17960
18632
|
version: 1,
|
|
17961
18633
|
entries: state.entries.filter((e) => {
|
|
@@ -17967,14 +18639,14 @@ function clearQueryHistory(state, dbId, schema) {
|
|
|
17967
18639
|
})
|
|
17968
18640
|
};
|
|
17969
18641
|
}
|
|
17970
|
-
var CODE_VIEWER_DIR3 = ".code-viewer", HISTORY_FILE_NAME = "query-history.json", MAX_ENTRIES2 = 200, MAX_PREVIEW_ROWS = 100,
|
|
18642
|
+
var CODE_VIEWER_DIR3 = ".code-viewer", HISTORY_FILE_NAME = "query-history.json", MAX_ENTRIES2 = 200, MAX_PREVIEW_ROWS = 100, MAX_JSON_BYTES2 = 1e6, MAX_ID_LEN = 128, MAX_DB_ID_LEN = 2048, MAX_SCHEMA_LEN = 512, MAX_SQL_LEN = 64000, MAX_TEXT_LEN = 64000, MAX_COLUMN_LEN = 512, MAX_COLUMNS = 500, historyStore;
|
|
17971
18643
|
var init_query_history = __esm(() => {
|
|
17972
18644
|
init_json_store();
|
|
17973
18645
|
historyStore = createJsonFileStore({
|
|
17974
18646
|
filePath: historyFilePath,
|
|
17975
|
-
empty:
|
|
18647
|
+
empty: emptyState2,
|
|
17976
18648
|
sanitize: sanitizeHistoryState,
|
|
17977
|
-
maxBytes:
|
|
18649
|
+
maxBytes: MAX_JSON_BYTES2,
|
|
17978
18650
|
backupSuffix: "corrupt",
|
|
17979
18651
|
serialize: serializeHistoryState
|
|
17980
18652
|
});
|
|
@@ -17983,9 +18655,9 @@ var init_query_history = __esm(() => {
|
|
|
17983
18655
|
// web-src/server/database/snapshot-store.ts
|
|
17984
18656
|
import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
|
|
17985
18657
|
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
17986
|
-
import { join as
|
|
18658
|
+
import { join as join13 } from "node:path";
|
|
17987
18659
|
async function getStoreDb(cwd) {
|
|
17988
|
-
const dbPath =
|
|
18660
|
+
const dbPath = join13(cwd, CODE_VIEWER_DIR4, SNAPSHOT_DB_NAME);
|
|
17989
18661
|
if (storeDb && storeDbPath === dbPath)
|
|
17990
18662
|
return storeDb;
|
|
17991
18663
|
if (storeDb) {
|
|
@@ -17993,7 +18665,7 @@ async function getStoreDb(cwd) {
|
|
|
17993
18665
|
storeDb.close();
|
|
17994
18666
|
} catch {}
|
|
17995
18667
|
}
|
|
17996
|
-
mkdirSync3(
|
|
18668
|
+
mkdirSync3(join13(cwd, CODE_VIEWER_DIR4), { recursive: true });
|
|
17997
18669
|
const DbClass = await loadSqliteClass();
|
|
17998
18670
|
storeDb = new DbClass(dbPath);
|
|
17999
18671
|
storeDbPath = dbPath;
|
|
@@ -18599,11 +19271,11 @@ var init_snapshot_runner = __esm(() => {
|
|
|
18599
19271
|
});
|
|
18600
19272
|
|
|
18601
19273
|
// web-src/server/database/tabs-store.ts
|
|
18602
|
-
import { join as
|
|
19274
|
+
import { join as join14 } from "node:path";
|
|
18603
19275
|
function tabsFilePath(root) {
|
|
18604
|
-
return
|
|
19276
|
+
return join14(root, CODE_VIEWER_DIR5, TABS_FILE_NAME);
|
|
18605
19277
|
}
|
|
18606
|
-
function
|
|
19278
|
+
function emptyState3() {
|
|
18607
19279
|
return { version: 1, tabs: [], activeTabId: null };
|
|
18608
19280
|
}
|
|
18609
19281
|
function isValidCssSize(s) {
|
|
@@ -18725,10 +19397,10 @@ function sanitizeDynamodb(v) {
|
|
|
18725
19397
|
}
|
|
18726
19398
|
function sanitize(input) {
|
|
18727
19399
|
if (!input || typeof input !== "object")
|
|
18728
|
-
return
|
|
19400
|
+
return emptyState3();
|
|
18729
19401
|
const obj = input;
|
|
18730
19402
|
if (obj.version !== 1)
|
|
18731
|
-
return
|
|
19403
|
+
return emptyState3();
|
|
18732
19404
|
const rawTabs = Array.isArray(obj.tabs) ? obj.tabs : [];
|
|
18733
19405
|
const seenIds = new Set;
|
|
18734
19406
|
const tabs = [];
|
|
@@ -18796,7 +19468,7 @@ async function loadTabsAsync(cwd) {
|
|
|
18796
19468
|
async function saveTabsAsync(cwd, state) {
|
|
18797
19469
|
return tabsStore.save(cwd, state);
|
|
18798
19470
|
}
|
|
18799
|
-
var CODE_VIEWER_DIR5 = ".code-viewer", TABS_FILE_NAME = "tabs.json", MAX_TABS = 64,
|
|
19471
|
+
var CODE_VIEWER_DIR5 = ".code-viewer", TABS_FILE_NAME = "tabs.json", MAX_TABS = 64, MAX_JSON_BYTES3 = 1e6, MAX_SQL_DRAFT_LEN = 16000, MAX_ES_QUERY_LEN = 16000, MAX_TAB_ID_LEN = 128, MAX_DB_ID_LEN2 = 2048, MAX_SCHEMA_NAME_LEN = 512, MAX_TABLE_NAME_LEN2 = 512, MAX_REDIS_KEY_LEN = 1024, MAX_REDIS_KEY_FILTER_LEN = 512, MAX_INDEX_NAME_LEN = 256, MAX_S3_BUCKET_LEN = 256, MAX_S3_KEY_LEN = 2048, MAX_S3_QUERY_LEN = 2048, MAX_DYNAMODB_TABLE_LEN = 255, MAX_DYNAMODB_EXPRESSION_LEN = 4096, MAX_DYNAMODB_ATTRIBUTE_VALUES_LEN = 16000, MAX_DYNAMODB_ITEM_KEY_LEN = 4096, MAX_CSS_SIZE_LEN = 16, VALID_VIEWS, tabsStore;
|
|
18800
19472
|
var init_tabs_store = __esm(() => {
|
|
18801
19473
|
init_json_store();
|
|
18802
19474
|
VALID_VIEWS = new Set([
|
|
@@ -18809,9 +19481,9 @@ var init_tabs_store = __esm(() => {
|
|
|
18809
19481
|
]);
|
|
18810
19482
|
tabsStore = createJsonFileStore({
|
|
18811
19483
|
filePath: tabsFilePath,
|
|
18812
|
-
empty:
|
|
19484
|
+
empty: emptyState3,
|
|
18813
19485
|
sanitize,
|
|
18814
|
-
maxBytes:
|
|
19486
|
+
maxBytes: MAX_JSON_BYTES3,
|
|
18815
19487
|
backupSuffix: "bak",
|
|
18816
19488
|
sizeErrorMessage: "tabs state too large"
|
|
18817
19489
|
});
|
|
@@ -18839,6 +19511,10 @@ function ensureInit() {
|
|
|
18839
19511
|
initialized = true;
|
|
18840
19512
|
}
|
|
18841
19513
|
async function getAdapter(r, _cwd, signal) {
|
|
19514
|
+
if (r.saved) {
|
|
19515
|
+
const cacheKey = r.schema ? `${r.dbId}\x00schema=${r.schema}` : r.dbId;
|
|
19516
|
+
return dockerAdapterCache.getOrOpenAsync(cacheKey, () => createSqlCliAdapter({ ...r.saved, schema: r.schema }));
|
|
19517
|
+
}
|
|
18842
19518
|
if (r.docker) {
|
|
18843
19519
|
const docker = r.docker;
|
|
18844
19520
|
const cacheKey = r.schema ? `${r.dbId}\x00schema=${r.schema}` : r.dbId;
|
|
@@ -18886,6 +19562,24 @@ async function resolveSupabaseSchema(info, requestedSchema, signal) {
|
|
|
18886
19562
|
async function resolveDb(cwd, dbParam, omitDirNames, schemaParam, signal) {
|
|
18887
19563
|
if (!dbParam)
|
|
18888
19564
|
return textError("missing db parameter", 400);
|
|
19565
|
+
if (dbParam.startsWith("connection:")) {
|
|
19566
|
+
const connection = await findDatastoreConnection(cwd, dbParam);
|
|
19567
|
+
if (!connection)
|
|
19568
|
+
return textError("datastore connection not found", 404);
|
|
19569
|
+
if (connection.kind !== "postgresql" && connection.kind !== "mysql") {
|
|
19570
|
+
return textError(`${connection.kind} must use its datastore routes`, 400);
|
|
19571
|
+
}
|
|
19572
|
+
const requestedSchema = normalizeSchemaParam(schemaParam);
|
|
19573
|
+
if (requestedSchema instanceof Response)
|
|
19574
|
+
return requestedSchema;
|
|
19575
|
+
const schema = connection.kind === "postgresql" ? requestedSchema || connection.schema || "public" : undefined;
|
|
19576
|
+
return {
|
|
19577
|
+
resolved: dbParam,
|
|
19578
|
+
dbId: dbParam,
|
|
19579
|
+
saved: connection,
|
|
19580
|
+
...schema ? { schema } : {}
|
|
19581
|
+
};
|
|
19582
|
+
}
|
|
18889
19583
|
if (dbParam.startsWith("supabase:")) {
|
|
18890
19584
|
const parsed = parseSupabaseDbId(dbParam);
|
|
18891
19585
|
if (!parsed)
|
|
@@ -18993,10 +19687,11 @@ async function expandDockerServicesForFiles(dockerServices, listDockerDatabases,
|
|
|
18993
19687
|
}
|
|
18994
19688
|
async function createDbFilesResponse(cwd, omitDirNames, signal, deps = DEFAULT_DB_FILE_DISCOVERY_DEPS) {
|
|
18995
19689
|
ensureInit();
|
|
18996
|
-
const [sqliteSettled, dockerSettled, supabaseSettled] = await Promise.allSettled([
|
|
19690
|
+
const [sqliteSettled, dockerSettled, supabaseSettled, connectionsSettled] = await Promise.allSettled([
|
|
18997
19691
|
deps.discoverSqliteFiles(cwd, omitDirNames, signal),
|
|
18998
19692
|
deps.discoverDockerDatabases(cwd, omitDirNames, signal),
|
|
18999
|
-
deps.discoverSupabaseCliProjects(cwd, omitDirNames, signal)
|
|
19693
|
+
deps.discoverSupabaseCliProjects(cwd, omitDirNames, signal),
|
|
19694
|
+
(deps.loadConnections ?? loadDatastoreConnections)(cwd)
|
|
19000
19695
|
]);
|
|
19001
19696
|
if (sqliteSettled.status === "rejected") {
|
|
19002
19697
|
throw sqliteSettled.reason;
|
|
@@ -19004,11 +19699,15 @@ async function createDbFilesResponse(cwd, omitDirNames, signal, deps = DEFAULT_D
|
|
|
19004
19699
|
if (supabaseSettled.status === "rejected") {
|
|
19005
19700
|
throw supabaseSettled.reason;
|
|
19006
19701
|
}
|
|
19702
|
+
if (connectionsSettled.status === "rejected") {
|
|
19703
|
+
throw connectionsSettled.reason;
|
|
19704
|
+
}
|
|
19007
19705
|
if (dockerSettled.status === "rejected" && isAbortLikeError(dockerSettled.reason, signal)) {
|
|
19008
19706
|
throw dockerSettled.reason;
|
|
19009
19707
|
}
|
|
19010
19708
|
const sqliteFiles = sqliteSettled.value;
|
|
19011
19709
|
const supabaseProjects = supabaseSettled.value;
|
|
19710
|
+
const savedConnections = connectionsSettled.value;
|
|
19012
19711
|
const dockerServices = dockerSettled.status === "fulfilled" ? dockerSettled.value : [];
|
|
19013
19712
|
const dockerErrors = [];
|
|
19014
19713
|
if (dockerSettled.status === "rejected") {
|
|
@@ -19027,7 +19726,8 @@ async function createDbFilesResponse(cwd, omitDirNames, signal, deps = DEFAULT_D
|
|
|
19027
19726
|
kind: "sqlite"
|
|
19028
19727
|
})),
|
|
19029
19728
|
...dockerEntries.map(toFileInfo),
|
|
19030
|
-
...supabaseProjects.map(toFileInfo)
|
|
19729
|
+
...supabaseProjects.map(toFileInfo),
|
|
19730
|
+
...savedConnections.map(connectionToFileInfo)
|
|
19031
19731
|
],
|
|
19032
19732
|
...dockerTruncated ? { truncated: true } : {},
|
|
19033
19733
|
...dockerErrors.length > 0 ? { dockerError: dockerErrors.join("; ") } : {}
|
|
@@ -19052,6 +19752,26 @@ async function createDbSchemasResponse(cwd, dbParam, schemaParam, omitDirNames,
|
|
|
19052
19752
|
};
|
|
19053
19753
|
return { ok: true, value: body2 };
|
|
19054
19754
|
}
|
|
19755
|
+
if (r.saved?.kind === "postgresql") {
|
|
19756
|
+
try {
|
|
19757
|
+
const adapter = await getAdapter(r, cwd, signal);
|
|
19758
|
+
const { result, executedSql: executedSql2 } = await captureSql(() => adapter.executeReadonlyQueryAsync("SELECT schema_name FROM information_schema.schemata ORDER BY schema_name", undefined, 1000, signal));
|
|
19759
|
+
return {
|
|
19760
|
+
ok: true,
|
|
19761
|
+
value: {
|
|
19762
|
+
dbId: r.dbId,
|
|
19763
|
+
schemas: result.rows.map((row) => ({ name: String(row[0]) })),
|
|
19764
|
+
selectedSchema: r.schema,
|
|
19765
|
+
executedSql: executedSql2
|
|
19766
|
+
}
|
|
19767
|
+
};
|
|
19768
|
+
} catch (err) {
|
|
19769
|
+
return {
|
|
19770
|
+
ok: false,
|
|
19771
|
+
response: handleError("database", "list schemas", err, signal)
|
|
19772
|
+
};
|
|
19773
|
+
}
|
|
19774
|
+
}
|
|
19055
19775
|
if (!r.docker || r.docker.kind !== "postgresql") {
|
|
19056
19776
|
const body2 = { dbId: r.dbId, schemas: [] };
|
|
19057
19777
|
return { ok: true, value: body2 };
|
|
@@ -20054,6 +20774,118 @@ async function handleTabsPut(cwd, req) {
|
|
|
20054
20774
|
async function handleDbUiGet(cwd) {
|
|
20055
20775
|
return jsonLoadResponse(() => loadDbUiState(cwd), "db UI", "failed to load db UI state");
|
|
20056
20776
|
}
|
|
20777
|
+
function closeSavedConnection(id, kind) {
|
|
20778
|
+
if (kind === "postgresql" || kind === "mysql") {
|
|
20779
|
+
dockerAdapterCache.close(id);
|
|
20780
|
+
dockerAdapterCache.closePrefix(`${id}\x00`);
|
|
20781
|
+
return;
|
|
20782
|
+
}
|
|
20783
|
+
DOCKER_CLOSE_REGISTRY[kind]?.(id);
|
|
20784
|
+
}
|
|
20785
|
+
async function handleConnections(cwd, req) {
|
|
20786
|
+
if (req.method === "GET") {
|
|
20787
|
+
const connections = await loadDatastoreConnections(cwd);
|
|
20788
|
+
return json({ connections: connections.map(publicConnection) });
|
|
20789
|
+
}
|
|
20790
|
+
const body = await parseBoundedJsonBody(req, 65536, "connection payload too large");
|
|
20791
|
+
if (body instanceof Response)
|
|
20792
|
+
return body;
|
|
20793
|
+
if (req.method === "PUT") {
|
|
20794
|
+
try {
|
|
20795
|
+
const connection = await saveDatastoreConnection(cwd, body);
|
|
20796
|
+
closeSavedConnection(connection.id, connection.kind);
|
|
20797
|
+
return json({ connection: publicConnection(connection) });
|
|
20798
|
+
} catch (err) {
|
|
20799
|
+
const message = err instanceof Error ? err.message : "invalid datastore connection";
|
|
20800
|
+
return textError(message, message === "too many datastore connections" ? 409 : 400);
|
|
20801
|
+
}
|
|
20802
|
+
}
|
|
20803
|
+
const id = body && typeof body === "object" && "id" in body ? body.id : undefined;
|
|
20804
|
+
if (typeof id !== "string" || !id.startsWith("connection:")) {
|
|
20805
|
+
return textError("invalid datastore connection id", 400);
|
|
20806
|
+
}
|
|
20807
|
+
const existing = await findDatastoreConnection(cwd, id);
|
|
20808
|
+
if (!existing)
|
|
20809
|
+
return textError("datastore connection not found", 404);
|
|
20810
|
+
await deleteDatastoreConnection(cwd, id);
|
|
20811
|
+
closeSavedConnection(id, existing.kind);
|
|
20812
|
+
return json({ ok: true });
|
|
20813
|
+
}
|
|
20814
|
+
async function probeDatastoreConnection(connection, signal) {
|
|
20815
|
+
if (connection.kind === "postgresql" || connection.kind === "mysql") {
|
|
20816
|
+
const adapter2 = createSqlCliAdapter(connection);
|
|
20817
|
+
try {
|
|
20818
|
+
await adapter2.getTablesAsync(signal);
|
|
20819
|
+
} finally {
|
|
20820
|
+
adapter2.close();
|
|
20821
|
+
}
|
|
20822
|
+
return;
|
|
20823
|
+
}
|
|
20824
|
+
if (connection.kind === "redis") {
|
|
20825
|
+
const adapter2 = createRedisAdapter(connection);
|
|
20826
|
+
try {
|
|
20827
|
+
await adapter2.listDatabasesAsync(signal);
|
|
20828
|
+
} finally {
|
|
20829
|
+
adapter2.close();
|
|
20830
|
+
}
|
|
20831
|
+
return;
|
|
20832
|
+
}
|
|
20833
|
+
if (connection.kind === "elasticsearch") {
|
|
20834
|
+
const adapter2 = createElasticsearchAdapter(connection);
|
|
20835
|
+
try {
|
|
20836
|
+
await adapter2.listIndicesAsync(signal);
|
|
20837
|
+
} finally {
|
|
20838
|
+
adapter2.close();
|
|
20839
|
+
}
|
|
20840
|
+
return;
|
|
20841
|
+
}
|
|
20842
|
+
if (connection.kind === "s3") {
|
|
20843
|
+
const adapter2 = createS3Adapter(connection);
|
|
20844
|
+
try {
|
|
20845
|
+
await adapter2.listBuckets(signal);
|
|
20846
|
+
} finally {
|
|
20847
|
+
adapter2.close();
|
|
20848
|
+
}
|
|
20849
|
+
return;
|
|
20850
|
+
}
|
|
20851
|
+
if (connection.kind !== "dynamodb") {
|
|
20852
|
+
throw new Error("invalid datastore connection");
|
|
20853
|
+
}
|
|
20854
|
+
const adapter = createDynamoDbAdapter(connection);
|
|
20855
|
+
try {
|
|
20856
|
+
await adapter.listTablesAsync({ limit: 1, signal });
|
|
20857
|
+
} finally {
|
|
20858
|
+
adapter.close();
|
|
20859
|
+
}
|
|
20860
|
+
}
|
|
20861
|
+
async function handleConnectionTest(cwd, req) {
|
|
20862
|
+
const body = await parseBoundedJsonBody(req, 65536, "connection payload too large");
|
|
20863
|
+
if (body instanceof Response)
|
|
20864
|
+
return body;
|
|
20865
|
+
const input = body && typeof body === "object" ? body : {};
|
|
20866
|
+
let existing = null;
|
|
20867
|
+
if (typeof input.id === "string") {
|
|
20868
|
+
existing = await findDatastoreConnection(cwd, input.id);
|
|
20869
|
+
if (!existing)
|
|
20870
|
+
return textError("datastore connection not found", 404);
|
|
20871
|
+
}
|
|
20872
|
+
try {
|
|
20873
|
+
const connection = validateDatastoreConnection({
|
|
20874
|
+
...existing ?? {},
|
|
20875
|
+
...input
|
|
20876
|
+
});
|
|
20877
|
+
await probeDatastoreConnection(connection, req.signal);
|
|
20878
|
+
return json({ ok: true });
|
|
20879
|
+
} catch (err) {
|
|
20880
|
+
if (isAbortLikeError(err, req.signal)) {
|
|
20881
|
+
return textError("connection test aborted", 503);
|
|
20882
|
+
}
|
|
20883
|
+
if (err instanceof Error && err.message === "invalid datastore connection") {
|
|
20884
|
+
return textError(err.message, 400);
|
|
20885
|
+
}
|
|
20886
|
+
return textError("connection failed", 400);
|
|
20887
|
+
}
|
|
20888
|
+
}
|
|
20057
20889
|
async function handleDbUiPatch(cwd, req) {
|
|
20058
20890
|
const body = await parseBoundedJsonBody(req, MAX_DB_UI_BODY_BYTES, "db UI body too large");
|
|
20059
20891
|
if (body instanceof Response)
|
|
@@ -20074,6 +20906,12 @@ async function handleClose(cwd, req, omitDirNames) {
|
|
|
20074
20906
|
return body;
|
|
20075
20907
|
if (!body.db)
|
|
20076
20908
|
return textError("missing db", 400);
|
|
20909
|
+
if (body.db.startsWith("connection:")) {
|
|
20910
|
+
const connection = await findDatastoreConnection(cwd, body.db);
|
|
20911
|
+
if (connection)
|
|
20912
|
+
closeSavedConnection(body.db, connection.kind);
|
|
20913
|
+
return json({ ok: true });
|
|
20914
|
+
}
|
|
20077
20915
|
if (body.db.startsWith("docker:")) {
|
|
20078
20916
|
const parsed = parseDockerDbId(body.db);
|
|
20079
20917
|
if (!parsed)
|
|
@@ -20174,6 +21012,16 @@ async function handleDatabaseRoute(req, url, cwd, omitDirNames, sideEffectAllowe
|
|
|
20174
21012
|
methods: ["GET"],
|
|
20175
21013
|
handler: () => handleFiles(cwd, omitDirNames, req.signal)
|
|
20176
21014
|
},
|
|
21015
|
+
"/_db/connections": {
|
|
21016
|
+
methods: ["GET", "PUT", "DELETE"],
|
|
21017
|
+
sideEffect: (requestMethod) => requestMethod !== "GET",
|
|
21018
|
+
handler: () => handleConnections(cwd, req)
|
|
21019
|
+
},
|
|
21020
|
+
"/_db/connections/test": {
|
|
21021
|
+
methods: ["POST"],
|
|
21022
|
+
sideEffect: true,
|
|
21023
|
+
handler: () => handleConnectionTest(cwd, req)
|
|
21024
|
+
},
|
|
20177
21025
|
"/_db/schemas": {
|
|
20178
21026
|
methods: ["GET"],
|
|
20179
21027
|
handler: () => handleSchemas(cwd, url, omitDirNames, req.signal)
|
|
@@ -20294,9 +21142,14 @@ var init_handle = __esm(() => {
|
|
|
20294
21142
|
init_state_store();
|
|
20295
21143
|
init_docker();
|
|
20296
21144
|
init_docker_utils();
|
|
21145
|
+
init_dynamodb();
|
|
21146
|
+
init_elasticsearch();
|
|
21147
|
+
init_redis();
|
|
21148
|
+
init_s3();
|
|
20297
21149
|
init_sql_capture();
|
|
20298
21150
|
init_sqlite();
|
|
20299
21151
|
init_connection_pool();
|
|
21152
|
+
init_connections_store();
|
|
20300
21153
|
init_discovery();
|
|
20301
21154
|
init_global_search();
|
|
20302
21155
|
init_handle_dynamodb();
|
|
@@ -20314,7 +21167,8 @@ var init_handle = __esm(() => {
|
|
|
20314
21167
|
discoverSqliteFiles: discoverSqliteFilesAsync,
|
|
20315
21168
|
discoverDockerDatabases: discoverDockerDatabasesAsync,
|
|
20316
21169
|
listDockerDatabases: listDockerDatabasesAsync,
|
|
20317
|
-
discoverSupabaseCliProjects: discoverSupabaseCliProjectsAsync
|
|
21170
|
+
discoverSupabaseCliProjects: discoverSupabaseCliProjectsAsync,
|
|
21171
|
+
loadConnections: loadDatastoreConnections
|
|
20318
21172
|
};
|
|
20319
21173
|
searchJobs = new Map;
|
|
20320
21174
|
snapshotJobs = new Map;
|
|
@@ -20356,7 +21210,7 @@ var init_handle = __esm(() => {
|
|
|
20356
21210
|
|
|
20357
21211
|
// web-src/server/doctor.ts
|
|
20358
21212
|
import { accessSync as accessSync2, constants as constants2, readFileSync as readFileSync6, statSync as statSync5 } from "node:fs";
|
|
20359
|
-
import { dirname as dirname4, join as
|
|
21213
|
+
import { dirname as dirname4, join as join15, relative as relative5 } from "node:path";
|
|
20360
21214
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
20361
21215
|
function statusWorse(a, b) {
|
|
20362
21216
|
const rank = { ok: 0, warn: 1, error: 2 };
|
|
@@ -20450,7 +21304,7 @@ function findCodeViewerPackageJson() {
|
|
|
20450
21304
|
cursor = dirname4(process.argv[1] || ".");
|
|
20451
21305
|
}
|
|
20452
21306
|
for (let depth = 0;depth < 8; depth += 1) {
|
|
20453
|
-
const candidate =
|
|
21307
|
+
const candidate = join15(cursor, "package.json");
|
|
20454
21308
|
try {
|
|
20455
21309
|
const raw = readFileSync6(candidate, "utf8");
|
|
20456
21310
|
const pkg = JSON.parse(raw);
|
|
@@ -20546,7 +21400,7 @@ async function checkSqlite(cwd) {
|
|
|
20546
21400
|
return { id: "sqlite", title: "SQLite driver", rows };
|
|
20547
21401
|
}
|
|
20548
21402
|
async function trySnapshotDbOpen(cwd) {
|
|
20549
|
-
const dbPath =
|
|
21403
|
+
const dbPath = join15(cwd, SNAPSHOT_DB_REL);
|
|
20550
21404
|
try {
|
|
20551
21405
|
statSync5(dbPath);
|
|
20552
21406
|
} catch {
|
|
@@ -20567,7 +21421,7 @@ async function trySnapshotDbOpen(cwd) {
|
|
|
20567
21421
|
}
|
|
20568
21422
|
}
|
|
20569
21423
|
function checkSnapshotStore(cwd) {
|
|
20570
|
-
const dbPath =
|
|
21424
|
+
const dbPath = join15(cwd, SNAPSHOT_DB_REL);
|
|
20571
21425
|
const dir = dirname4(dbPath);
|
|
20572
21426
|
let dirStatus = "ok";
|
|
20573
21427
|
let dirDetail = dir;
|
|
@@ -21571,7 +22425,7 @@ function normalizeNewDirectoryName(name) {
|
|
|
21571
22425
|
|
|
21572
22426
|
// web-src/server/cache.ts
|
|
21573
22427
|
import { lstatSync as lstatSync3 } from "node:fs";
|
|
21574
|
-
import { join as
|
|
22428
|
+
import { join as join16 } from "node:path";
|
|
21575
22429
|
function cacheFresh(cached, now = Date.now(), ttlMs = CACHE_TTL_MS) {
|
|
21576
22430
|
return !!cached && now - cached.storedAt <= ttlMs;
|
|
21577
22431
|
}
|
|
@@ -21586,7 +22440,7 @@ function setTimedCacheEntry(cache, key, value, now = Date.now(), maxEntries = MA
|
|
|
21586
22440
|
}
|
|
21587
22441
|
function worktreeFileSignature(path, cwd) {
|
|
21588
22442
|
try {
|
|
21589
|
-
const stats = lstatSync3(
|
|
22443
|
+
const stats = lstatSync3(join16(cwd, path));
|
|
21590
22444
|
const inode = "ino" in stats ? stats.ino : 0;
|
|
21591
22445
|
return `state:file|size:${stats.size}|mtime:${stats.mtimeMs}|ctime:${stats.ctimeMs}|ino:${inode}`;
|
|
21592
22446
|
} catch {
|
|
@@ -21632,12 +22486,12 @@ function startDevAssetReload(options) {
|
|
|
21632
22486
|
var init_dev_assets = () => {};
|
|
21633
22487
|
|
|
21634
22488
|
// web-src/server/journal.ts
|
|
21635
|
-
import { join as
|
|
22489
|
+
import { join as join17 } from "node:path";
|
|
21636
22490
|
function dailyJournalFilePath(root) {
|
|
21637
|
-
return
|
|
22491
|
+
return join17(root, CODE_VIEWER_DIR, DAILY_JOURNAL_FILE_NAME);
|
|
21638
22492
|
}
|
|
21639
22493
|
function journalTasksFilePath(root) {
|
|
21640
|
-
return
|
|
22494
|
+
return join17(root, CODE_VIEWER_DIR, JOURNAL_TASKS_FILE_NAME);
|
|
21641
22495
|
}
|
|
21642
22496
|
function emptyDailyJournalState() {
|
|
21643
22497
|
return { version: 1, entries: [] };
|
|
@@ -21650,7 +22504,7 @@ function makeJournalId(prefix) {
|
|
|
21650
22504
|
const time = Date.now().toString(36);
|
|
21651
22505
|
return `${prefix}-${time}${random}`;
|
|
21652
22506
|
}
|
|
21653
|
-
function
|
|
22507
|
+
function optionalString4(value, maxLen) {
|
|
21654
22508
|
if (typeof value !== "string")
|
|
21655
22509
|
return;
|
|
21656
22510
|
if (value.includes("\x00"))
|
|
@@ -21676,12 +22530,12 @@ function normalizeJournalEntry(raw) {
|
|
|
21676
22530
|
if (!raw || typeof raw !== "object")
|
|
21677
22531
|
return null;
|
|
21678
22532
|
const entry = raw;
|
|
21679
|
-
const id =
|
|
22533
|
+
const id = optionalString4(entry.id, 128);
|
|
21680
22534
|
const date = isIsoDate(entry.date) ? entry.date : undefined;
|
|
21681
22535
|
const body = optionalBody(entry.body, JOURNAL_ENTRY_BODY_MAX_BYTES);
|
|
21682
22536
|
if (!id || !date || body === undefined)
|
|
21683
22537
|
return null;
|
|
21684
|
-
const title =
|
|
22538
|
+
const title = optionalString4(entry.title, JOURNAL_TITLE_MAX_CHARS);
|
|
21685
22539
|
return {
|
|
21686
22540
|
id,
|
|
21687
22541
|
date,
|
|
@@ -21689,8 +22543,8 @@ function normalizeJournalEntry(raw) {
|
|
|
21689
22543
|
body,
|
|
21690
22544
|
labels: normalizeJournalLabels(entry.labels),
|
|
21691
22545
|
source: normalizeSource(entry.source),
|
|
21692
|
-
created_at:
|
|
21693
|
-
updated_at:
|
|
22546
|
+
created_at: optionalString4(entry.created_at, 64) ?? new Date(0).toISOString(),
|
|
22547
|
+
updated_at: optionalString4(entry.updated_at, 64) ?? new Date(0).toISOString()
|
|
21694
22548
|
};
|
|
21695
22549
|
}
|
|
21696
22550
|
function normalizeDailyJournalState(raw) {
|
|
@@ -21714,13 +22568,13 @@ function normalizeTaskNote(raw) {
|
|
|
21714
22568
|
if (!raw || typeof raw !== "object")
|
|
21715
22569
|
return null;
|
|
21716
22570
|
const note = raw;
|
|
21717
|
-
const id =
|
|
22571
|
+
const id = optionalString4(note.id, 128);
|
|
21718
22572
|
const body = optionalBody(note.body, JOURNAL_TASK_NOTE_MAX_BYTES);
|
|
21719
22573
|
if (!id || body === undefined)
|
|
21720
22574
|
return null;
|
|
21721
22575
|
return {
|
|
21722
22576
|
id,
|
|
21723
|
-
at:
|
|
22577
|
+
at: optionalString4(note.at, 64) ?? new Date(0).toISOString(),
|
|
21724
22578
|
body,
|
|
21725
22579
|
source: normalizeSource(note.source)
|
|
21726
22580
|
};
|
|
@@ -21729,9 +22583,9 @@ function normalizeTaskClaim(raw) {
|
|
|
21729
22583
|
if (!raw || typeof raw !== "object")
|
|
21730
22584
|
return;
|
|
21731
22585
|
const claim = raw;
|
|
21732
|
-
const by =
|
|
21733
|
-
const claimedAt =
|
|
21734
|
-
const leaseExpiresAt =
|
|
22586
|
+
const by = optionalString4(claim.by, 128);
|
|
22587
|
+
const claimedAt = optionalString4(claim.claimed_at, 64);
|
|
22588
|
+
const leaseExpiresAt = optionalString4(claim.lease_expires_at, 64);
|
|
21735
22589
|
if (!by || !claimedAt || !leaseExpiresAt)
|
|
21736
22590
|
return;
|
|
21737
22591
|
return {
|
|
@@ -21744,8 +22598,8 @@ function normalizeJournalTask(raw) {
|
|
|
21744
22598
|
if (!raw || typeof raw !== "object")
|
|
21745
22599
|
return null;
|
|
21746
22600
|
const task = raw;
|
|
21747
|
-
const id =
|
|
21748
|
-
const title =
|
|
22601
|
+
const id = optionalString4(task.id, 128);
|
|
22602
|
+
const title = optionalString4(task.title, JOURNAL_TITLE_MAX_CHARS);
|
|
21749
22603
|
if (!id || !title)
|
|
21750
22604
|
return null;
|
|
21751
22605
|
const status = isJournalTaskStatus(task.status) ? task.status : "todo";
|
|
@@ -21753,8 +22607,8 @@ function normalizeJournalTask(raw) {
|
|
|
21753
22607
|
const body = optionalBody(task.body, JOURNAL_TASK_BODY_MAX_BYTES) ?? "";
|
|
21754
22608
|
const dueDate = isIsoDate(task.due_date) ? task.due_date : undefined;
|
|
21755
22609
|
const sourceDate = isIsoDate(task.source_date) ? task.source_date : undefined;
|
|
21756
|
-
const journalEntryId =
|
|
21757
|
-
const completedAt =
|
|
22610
|
+
const journalEntryId = optionalString4(task.journal_entry_id, 128);
|
|
22611
|
+
const completedAt = optionalString4(task.completed_at, 64);
|
|
21758
22612
|
const notes = Array.isArray(task.notes) ? task.notes.slice(0, MAX_NOTES_PER_TASK).map(normalizeTaskNote).filter((note) => note !== null) : [];
|
|
21759
22613
|
const claim = normalizeTaskClaim(task.claim);
|
|
21760
22614
|
return {
|
|
@@ -21764,8 +22618,8 @@ function normalizeJournalTask(raw) {
|
|
|
21764
22618
|
status,
|
|
21765
22619
|
priority,
|
|
21766
22620
|
labels: normalizeJournalLabels(task.labels),
|
|
21767
|
-
created_at:
|
|
21768
|
-
updated_at:
|
|
22621
|
+
created_at: optionalString4(task.created_at, 64) ?? new Date(0).toISOString(),
|
|
22622
|
+
updated_at: optionalString4(task.updated_at, 64) ?? new Date(0).toISOString(),
|
|
21769
22623
|
...dueDate ? { due_date: dueDate } : {},
|
|
21770
22624
|
...sourceDate ? { source_date: sourceDate } : {},
|
|
21771
22625
|
...journalEntryId ? { journal_entry_id: journalEntryId } : {},
|
|
@@ -21846,7 +22700,7 @@ function addDailyJournalEntry(state, input, now, makeId3 = makeJournalId) {
|
|
|
21846
22700
|
const valid = validateEntryInput(input);
|
|
21847
22701
|
if (valid.ok === false)
|
|
21848
22702
|
return valid;
|
|
21849
|
-
const title =
|
|
22703
|
+
const title = optionalString4(input.title, JOURNAL_TITLE_MAX_CHARS);
|
|
21850
22704
|
const entry = {
|
|
21851
22705
|
id: makeId3("j"),
|
|
21852
22706
|
date: input.date,
|
|
@@ -21877,7 +22731,7 @@ function updateDailyJournalEntry(state, id, patch, now) {
|
|
|
21877
22731
|
next.date = patch.date;
|
|
21878
22732
|
}
|
|
21879
22733
|
if (patch.title !== undefined) {
|
|
21880
|
-
const title =
|
|
22734
|
+
const title = optionalString4(patch.title, JOURNAL_TITLE_MAX_CHARS);
|
|
21881
22735
|
if (title)
|
|
21882
22736
|
next.title = title;
|
|
21883
22737
|
else
|
|
@@ -21910,7 +22764,7 @@ function deleteDailyJournalEntry(state, id) {
|
|
|
21910
22764
|
};
|
|
21911
22765
|
}
|
|
21912
22766
|
function validateTaskInput(input) {
|
|
21913
|
-
if (!
|
|
22767
|
+
if (!optionalString4(input.title, JOURNAL_TITLE_MAX_CHARS))
|
|
21914
22768
|
return { ok: false, error: "title is required" };
|
|
21915
22769
|
if (input.body !== undefined && optionalBody(input.body, JOURNAL_TASK_BODY_MAX_BYTES) === undefined)
|
|
21916
22770
|
return { ok: false, error: "body is too large" };
|
|
@@ -21955,7 +22809,7 @@ function addJournalTask(state, input, now, makeId3 = makeJournalId) {
|
|
|
21955
22809
|
const status = input.status || anchor?.status || "todo";
|
|
21956
22810
|
const task = {
|
|
21957
22811
|
id: makeId3("t"),
|
|
21958
|
-
title:
|
|
22812
|
+
title: optionalString4(input.title, JOURNAL_TITLE_MAX_CHARS) || "Untitled task",
|
|
21959
22813
|
body: input.body || "",
|
|
21960
22814
|
status,
|
|
21961
22815
|
priority: input.priority || "p2",
|
|
@@ -21980,7 +22834,7 @@ function updateJournalTask(state, id, patch, now) {
|
|
|
21980
22834
|
return { ok: false, error: "task not found" };
|
|
21981
22835
|
const next = { ...task, updated_at: now };
|
|
21982
22836
|
if (patch.title !== undefined) {
|
|
21983
|
-
const title =
|
|
22837
|
+
const title = optionalString4(patch.title, JOURNAL_TITLE_MAX_CHARS);
|
|
21984
22838
|
if (!title)
|
|
21985
22839
|
return { ok: false, error: "title is required" };
|
|
21986
22840
|
next.title = title;
|
|
@@ -22022,7 +22876,7 @@ function updateJournalTask(state, id, patch, now) {
|
|
|
22022
22876
|
return { ok: false, error: "source date must be YYYY-MM-DD" };
|
|
22023
22877
|
}
|
|
22024
22878
|
if (patch.journal_entry_id !== undefined) {
|
|
22025
|
-
const journalEntryId =
|
|
22879
|
+
const journalEntryId = optionalString4(patch.journal_entry_id, 128);
|
|
22026
22880
|
if (journalEntryId)
|
|
22027
22881
|
next.journal_entry_id = journalEntryId;
|
|
22028
22882
|
else
|
|
@@ -22072,10 +22926,10 @@ function linkGithubIssueTask(state, input, now, makeId3 = makeJournalId) {
|
|
|
22072
22926
|
if (!Number.isInteger(input.issue_number) || input.issue_number < 1) {
|
|
22073
22927
|
return { ok: false, error: "issue number must be a positive integer" };
|
|
22074
22928
|
}
|
|
22075
|
-
const title =
|
|
22076
|
-
const repo =
|
|
22077
|
-
const issueUrl =
|
|
22078
|
-
const memoLabel =
|
|
22929
|
+
const title = optionalString4(input.title, JOURNAL_TITLE_MAX_CHARS) || `GitHub issue #${input.issue_number}`;
|
|
22930
|
+
const repo = optionalString4(input.repo, 120);
|
|
22931
|
+
const issueUrl = optionalString4(input.url, 240);
|
|
22932
|
+
const memoLabel = optionalString4(input.memo_label, 80);
|
|
22079
22933
|
const requiredLabels = githubIssueRequiredLabels(input.issue_number, repo, input.labels);
|
|
22080
22934
|
const linkLabel = journalIssueLabel(input.issue_number);
|
|
22081
22935
|
const repoLabel = journalIssueRepoLabel(repo);
|
|
@@ -22153,7 +23007,7 @@ function claimJournalTask(state, id, input, now) {
|
|
|
22153
23007
|
ok: false,
|
|
22154
23008
|
error: "only todo or expired doing tasks can be claimed"
|
|
22155
23009
|
};
|
|
22156
|
-
const by =
|
|
23010
|
+
const by = optionalString4(input.by, 128) || "ai";
|
|
22157
23011
|
const wipLimit = input.wip_limit;
|
|
22158
23012
|
if (wipLimit !== undefined && wipLimit > 0) {
|
|
22159
23013
|
const activeDoing = state.tasks.filter((item) => {
|
|
@@ -22198,7 +23052,7 @@ function completeJournalTask(state, id, input, now, makeId3 = makeJournalId) {
|
|
|
22198
23052
|
const activeClaim = task.claim && Number.isFinite(Date.parse(task.claim.lease_expires_at)) && Date.parse(task.claim.lease_expires_at) > nowMs;
|
|
22199
23053
|
if (!activeClaim)
|
|
22200
23054
|
return { ok: false, error: "task must be claimed before completion" };
|
|
22201
|
-
const by =
|
|
23055
|
+
const by = optionalString4(input.by, 128);
|
|
22202
23056
|
if (!by)
|
|
22203
23057
|
return { ok: false, error: "task completion requires claim owner" };
|
|
22204
23058
|
if (task.claim?.by !== by)
|
|
@@ -22270,7 +23124,7 @@ var init_journal2 = __esm(() => {
|
|
|
22270
23124
|
|
|
22271
23125
|
// web-src/server/search-service.ts
|
|
22272
23126
|
import { existsSync as existsSync7, lstatSync as lstatSync4, readFileSync as readFileSync7, realpathSync as realpathSync5 } from "node:fs";
|
|
22273
|
-
import { join as
|
|
23127
|
+
import { join as join18, relative as relative6 } from "node:path";
|
|
22274
23128
|
async function rgAvailableAsync(cwd) {
|
|
22275
23129
|
if (rgAvailableCache !== null)
|
|
22276
23130
|
return rgAvailableCache;
|
|
@@ -22299,7 +23153,7 @@ function safeWorktreePath(env, path) {
|
|
|
22299
23153
|
return null;
|
|
22300
23154
|
if (isGitInternalPath(path))
|
|
22301
23155
|
return null;
|
|
22302
|
-
const full =
|
|
23156
|
+
const full = join18(env.cwd, path);
|
|
22303
23157
|
if (!existsSync7(full))
|
|
22304
23158
|
return null;
|
|
22305
23159
|
let realCwd;
|
|
@@ -22491,7 +23345,7 @@ var init_search_service = __esm(() => {
|
|
|
22491
23345
|
|
|
22492
23346
|
// web-src/server/mcp.ts
|
|
22493
23347
|
import { readFileSync as readFileSync8 } from "node:fs";
|
|
22494
|
-
import { join as
|
|
23348
|
+
import { join as join19 } from "node:path";
|
|
22495
23349
|
function defaultMcpTools(options = {}) {
|
|
22496
23350
|
return [
|
|
22497
23351
|
{
|
|
@@ -23965,7 +24819,7 @@ var init_mcp = __esm(() => {
|
|
|
23965
24819
|
init_search_cli();
|
|
23966
24820
|
init_search_service();
|
|
23967
24821
|
init_status_cli();
|
|
23968
|
-
PACKAGE_VERSION = JSON.parse(readFileSync8(
|
|
24822
|
+
PACKAGE_VERSION = JSON.parse(readFileSync8(join19(ROOT, "package.json"), "utf8")).version;
|
|
23969
24823
|
MCP_SERVER_INFO = {
|
|
23970
24824
|
name: "code-viewer",
|
|
23971
24825
|
title: "code-viewer",
|
|
@@ -24061,7 +24915,7 @@ import {
|
|
|
24061
24915
|
writeFileSync as writeFileSync2
|
|
24062
24916
|
} from "node:fs";
|
|
24063
24917
|
import { homedir as homedir3 } from "node:os";
|
|
24064
|
-
import { basename as basename3, dirname as dirname5, extname as extname2, join as
|
|
24918
|
+
import { basename as basename3, dirname as dirname5, extname as extname2, join as join20, relative as relative7 } from "node:path";
|
|
24065
24919
|
function parseCli() {
|
|
24066
24920
|
const rest = [];
|
|
24067
24921
|
for (let i = 2;i < process.argv.length; i++) {
|
|
@@ -24183,7 +25037,7 @@ Examples:
|
|
|
24183
25037
|
}
|
|
24184
25038
|
function warnIfLegacyConfigPresent() {
|
|
24185
25039
|
try {
|
|
24186
|
-
if (existsSync8(
|
|
25040
|
+
if (existsSync8(join20(cwd, ".code-viewer.json"))) {
|
|
24187
25041
|
console.warn("[code-viewer] .code-viewer.json is no longer used; configure scope and upload from Viewer Settings instead. The file can be safely removed.");
|
|
24188
25042
|
}
|
|
24189
25043
|
} catch {}
|
|
@@ -24290,7 +25144,7 @@ function staticFile(pathname) {
|
|
|
24290
25144
|
const spec = map[pathname];
|
|
24291
25145
|
if (!spec)
|
|
24292
25146
|
return null;
|
|
24293
|
-
const full =
|
|
25147
|
+
const full = join20(WEB_ROOT, spec[0]);
|
|
24294
25148
|
if (!existsSync8(full))
|
|
24295
25149
|
return text("not found", 404);
|
|
24296
25150
|
return new Response(readFileSync9(full), {
|
|
@@ -24541,7 +25395,7 @@ function safeWorktreePath2(path) {
|
|
|
24541
25395
|
return safeWorktreePath(currentSearchEnv(), path);
|
|
24542
25396
|
}
|
|
24543
25397
|
function worktreePath(path) {
|
|
24544
|
-
return
|
|
25398
|
+
return join20(cwd, path);
|
|
24545
25399
|
}
|
|
24546
25400
|
function safeOpenWorktreePath(path) {
|
|
24547
25401
|
if (path === "") {
|
|
@@ -24830,7 +25684,7 @@ async function handleLog(url) {
|
|
|
24830
25684
|
}
|
|
24831
25685
|
function blamePathKey(p) {
|
|
24832
25686
|
try {
|
|
24833
|
-
const st = statSync6(
|
|
25687
|
+
const st = statSync6(join20(cwd, p));
|
|
24834
25688
|
return `${st.mtimeMs}:${st.size}`;
|
|
24835
25689
|
} catch {
|
|
24836
25690
|
return "missing";
|
|
@@ -25363,7 +26217,7 @@ async function handleUploadFiles(req) {
|
|
|
25363
26217
|
total += file.size;
|
|
25364
26218
|
if (total > MAX_UPLOAD_TOTAL_BYTES)
|
|
25365
26219
|
return text("upload too large", 413);
|
|
25366
|
-
const target =
|
|
26220
|
+
const target = join20(realDir, safeName);
|
|
25367
26221
|
if (relative7(realDir, dirname5(target)) !== "")
|
|
25368
26222
|
return text("invalid filename", 400);
|
|
25369
26223
|
if (existsSync8(target))
|
|
@@ -25485,9 +26339,9 @@ function triggerUpdate(changedPaths) {
|
|
|
25485
26339
|
sendSse("update", data);
|
|
25486
26340
|
}
|
|
25487
26341
|
function moveMacPathIntoTrash(path) {
|
|
25488
|
-
const trashDir =
|
|
26342
|
+
const trashDir = join20(homedir3(), ".Trash");
|
|
25489
26343
|
const base = basename3(path) || "code-viewer-trash-item";
|
|
25490
|
-
const target =
|
|
26344
|
+
const target = join20(trashDir, `${base}-${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}`);
|
|
25491
26345
|
try {
|
|
25492
26346
|
mkdirSync4(trashDir, { recursive: true });
|
|
25493
26347
|
renameSync(path, target);
|
|
@@ -25529,7 +26383,7 @@ async function restoreTrashPath(originalPath, trashPath) {
|
|
|
25529
26383
|
if (!existsSync8(trashPath))
|
|
25530
26384
|
return { ok: false, error: "trash item not found" };
|
|
25531
26385
|
try {
|
|
25532
|
-
const trashRoot =
|
|
26386
|
+
const trashRoot = join20(homedir3(), ".Trash");
|
|
25533
26387
|
const trashRelative = relative7(trashRoot, trashPath);
|
|
25534
26388
|
if (trashRelative === "" || trashRelative.startsWith("..") || trashRelative.startsWith("/") || trashRelative.startsWith("\\"))
|
|
25535
26389
|
return { ok: false, error: "invalid trash handle" };
|
|
@@ -25679,7 +26533,7 @@ async function handleCreateDirectory(req) {
|
|
|
25679
26533
|
const targetPath = dir ? `${dir}/${name}` : name;
|
|
25680
26534
|
if (!safeRepoPath(targetPath) || isGitInternalPath(targetPath))
|
|
25681
26535
|
return text("invalid target", 400);
|
|
25682
|
-
const target =
|
|
26536
|
+
const target = join20(parent, name);
|
|
25683
26537
|
if (existsSync8(target))
|
|
25684
26538
|
return text("already exists", 409);
|
|
25685
26539
|
try {
|
|
@@ -26277,8 +27131,8 @@ var init_preview = __esm(async () => {
|
|
|
26277
27131
|
init_server_registry();
|
|
26278
27132
|
init_state_store();
|
|
26279
27133
|
init_worktree_watcher();
|
|
26280
|
-
WEB_ROOT =
|
|
26281
|
-
VERSION = JSON.parse(readFileSync9(
|
|
27134
|
+
WEB_ROOT = join20(ROOT, "web");
|
|
27135
|
+
VERSION = JSON.parse(readFileSync9(join20(ROOT, "package.json"), "utf8")).version;
|
|
26282
27136
|
DEFAULT_ARGS = ["HEAD"];
|
|
26283
27137
|
WATCHED_ASSET_FILES = ["index.html", "style.css", "app.js"];
|
|
26284
27138
|
LINE_INDEX_MAX_FILE_BYTES = 256 * 1024 * 1024;
|