@youtyan/code-viewer 0.8.8 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -17
- package/dist/code-viewer.js +716 -138
- package/package.json +1 -1
- package/skills/code-viewer-query/SKILL.md +3 -1
- package/web/app.js +304 -89
- package/web/style.css +10 -4
package/dist/code-viewer.js
CHANGED
|
@@ -1127,7 +1127,7 @@ var init_name_pattern = __esm(() => {
|
|
|
1127
1127
|
|
|
1128
1128
|
// web-src/server/runtime.ts
|
|
1129
1129
|
import { spawn, spawnSync } from "node:child_process";
|
|
1130
|
-
import { createReadStream, promises as fs } from "node:fs";
|
|
1130
|
+
import { createReadStream, promises as fs, statSync as statSync2 } from "node:fs";
|
|
1131
1131
|
import {
|
|
1132
1132
|
createServer
|
|
1133
1133
|
} from "node:http";
|
|
@@ -1278,10 +1278,20 @@ function appendProcessError(stderr, err) {
|
|
|
1278
1278
|
return `${stderr}${stderr ? `
|
|
1279
1279
|
` : ""}${err.message}`;
|
|
1280
1280
|
}
|
|
1281
|
+
function assertReadableRegularFile(path) {
|
|
1282
|
+
const stats = statSync2(path);
|
|
1283
|
+
if (stats.isFile())
|
|
1284
|
+
return;
|
|
1285
|
+
const error = new Error(`not a regular file: ${path}`);
|
|
1286
|
+
error.code = stats.isDirectory() ? "EISDIR" : "EINVAL";
|
|
1287
|
+
throw error;
|
|
1288
|
+
}
|
|
1281
1289
|
function fileReadableStream(path) {
|
|
1290
|
+
assertReadableRegularFile(path);
|
|
1282
1291
|
return Readable.toWeb(createReadStream(path));
|
|
1283
1292
|
}
|
|
1284
1293
|
function fileByteRangeResponseBody(path, start, endInclusive) {
|
|
1294
|
+
assertReadableRegularFile(path);
|
|
1285
1295
|
return Readable.toWeb(createReadStream(path, { start, end: endInclusive }));
|
|
1286
1296
|
}
|
|
1287
1297
|
async function readFileTextRange(path, start, endExclusive) {
|
|
@@ -1415,7 +1425,7 @@ import {
|
|
|
1415
1425
|
readFileSync,
|
|
1416
1426
|
readlinkSync,
|
|
1417
1427
|
realpathSync as realpathSync2,
|
|
1418
|
-
statSync as
|
|
1428
|
+
statSync as statSync3
|
|
1419
1429
|
} from "node:fs";
|
|
1420
1430
|
import { open, stat } from "node:fs/promises";
|
|
1421
1431
|
import { dirname as dirname3, join as join4, posix, relative as relative2 } from "node:path";
|
|
@@ -2060,7 +2070,7 @@ function isGitInternalPath(path) {
|
|
|
2060
2070
|
function syntheticUncommittedBlameFromWorktree(cwd, path) {
|
|
2061
2071
|
const filePath = join4(cwd, path);
|
|
2062
2072
|
try {
|
|
2063
|
-
const stat2 =
|
|
2073
|
+
const stat2 = statSync3(filePath);
|
|
2064
2074
|
if (!stat2.isFile())
|
|
2065
2075
|
return { lines: [], commits: {}, error: "not a file" };
|
|
2066
2076
|
const text = readFileSync(filePath, "utf8");
|
|
@@ -2250,7 +2260,7 @@ function resolveWorktreeSymlinkTarget(cwd, full) {
|
|
|
2250
2260
|
let symlink_target_type = "missing";
|
|
2251
2261
|
if (realpathWithinRepo(cwd, full, false) !== null) {
|
|
2252
2262
|
try {
|
|
2253
|
-
const stat2 =
|
|
2263
|
+
const stat2 = statSync3(full);
|
|
2254
2264
|
symlink_target_type = stat2.isDirectory() ? "tree" : stat2.isFile() ? "blob" : "missing";
|
|
2255
2265
|
} catch {
|
|
2256
2266
|
symlink_target_type = "missing";
|
|
@@ -3982,7 +3992,7 @@ __export(exports_file_cli, {
|
|
|
3982
3992
|
FILE_DEFAULT_HISTORY_LIMIT: () => FILE_DEFAULT_HISTORY_LIMIT,
|
|
3983
3993
|
FILE_AGENT_HELP: () => FILE_AGENT_HELP
|
|
3984
3994
|
});
|
|
3985
|
-
import { existsSync as existsSync3, readFileSync as readFileSync4, realpathSync as realpathSync4, statSync as
|
|
3995
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, realpathSync as realpathSync4, statSync as statSync4 } from "node:fs";
|
|
3986
3996
|
import { join as join6, relative as relative3 } from "node:path";
|
|
3987
3997
|
function validatePath(value) {
|
|
3988
3998
|
return validateRepoRelativePathValue(value, "--path");
|
|
@@ -4381,7 +4391,7 @@ async function readShowTextAsync(root, command) {
|
|
|
4381
4391
|
};
|
|
4382
4392
|
}
|
|
4383
4393
|
try {
|
|
4384
|
-
const stat2 =
|
|
4394
|
+
const stat2 = statSync4(full);
|
|
4385
4395
|
if (!stat2.isFile()) {
|
|
4386
4396
|
return { code: 1, stdout: "", stderr: "not a file" };
|
|
4387
4397
|
}
|
|
@@ -10531,6 +10541,38 @@ var init_sql_capture = __esm(() => {
|
|
|
10531
10541
|
storage = new AsyncLocalStorage;
|
|
10532
10542
|
});
|
|
10533
10543
|
|
|
10544
|
+
// web-src/server/database/adapters/table-meta-cache.ts
|
|
10545
|
+
function createTableMetaCache(now = () => Date.now()) {
|
|
10546
|
+
const columns = new Map;
|
|
10547
|
+
const rowCounts = new Map;
|
|
10548
|
+
async function readThrough(store, table, ttlMs, load) {
|
|
10549
|
+
const cached = store.get(table);
|
|
10550
|
+
if (cached && cached.expires > now())
|
|
10551
|
+
return cached.value;
|
|
10552
|
+
const value = await load();
|
|
10553
|
+
store.set(table, { value, expires: now() + ttlMs });
|
|
10554
|
+
return value;
|
|
10555
|
+
}
|
|
10556
|
+
return {
|
|
10557
|
+
getColumns(table, load) {
|
|
10558
|
+
return readThrough(columns, table, COLUMNS_TTL_MS, load);
|
|
10559
|
+
},
|
|
10560
|
+
getRowCount(table, load) {
|
|
10561
|
+
return readThrough(rowCounts, table, ROWCOUNT_TTL_MS, load);
|
|
10562
|
+
},
|
|
10563
|
+
invalidate(table) {
|
|
10564
|
+
if (table) {
|
|
10565
|
+
columns.delete(table);
|
|
10566
|
+
rowCounts.delete(table);
|
|
10567
|
+
return;
|
|
10568
|
+
}
|
|
10569
|
+
columns.clear();
|
|
10570
|
+
rowCounts.clear();
|
|
10571
|
+
}
|
|
10572
|
+
};
|
|
10573
|
+
}
|
|
10574
|
+
var COLUMNS_TTL_MS = 30000, ROWCOUNT_TTL_MS = 15000;
|
|
10575
|
+
|
|
10534
10576
|
// web-src/server/database/adapters/docker.ts
|
|
10535
10577
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
10536
10578
|
import mysql from "mysql2/promise";
|
|
@@ -10966,39 +11008,6 @@ function buildTableSelectList(columns, kind) {
|
|
|
10966
11008
|
});
|
|
10967
11009
|
return hasSpatialColumn ? parts.join(", ") : "*";
|
|
10968
11010
|
}
|
|
10969
|
-
function createTableMetaCache(now = () => Date.now()) {
|
|
10970
|
-
const columns = new Map;
|
|
10971
|
-
const rowCounts = new Map;
|
|
10972
|
-
return {
|
|
10973
|
-
async getColumns(table, fetch2) {
|
|
10974
|
-
const cached = columns.get(table);
|
|
10975
|
-
const current = now();
|
|
10976
|
-
if (cached && cached.expires > current)
|
|
10977
|
-
return cached.value;
|
|
10978
|
-
const value = await fetch2();
|
|
10979
|
-
columns.set(table, { value, expires: now() + COLUMNS_TTL_MS });
|
|
10980
|
-
return value;
|
|
10981
|
-
},
|
|
10982
|
-
async getRowCount(table, fetch2) {
|
|
10983
|
-
const cached = rowCounts.get(table);
|
|
10984
|
-
const current = now();
|
|
10985
|
-
if (cached && cached.expires > current)
|
|
10986
|
-
return cached.value;
|
|
10987
|
-
const value = await fetch2();
|
|
10988
|
-
rowCounts.set(table, { value, expires: now() + ROWCOUNT_TTL_MS });
|
|
10989
|
-
return value;
|
|
10990
|
-
},
|
|
10991
|
-
invalidate(table) {
|
|
10992
|
-
if (table) {
|
|
10993
|
-
columns.delete(table);
|
|
10994
|
-
rowCounts.delete(table);
|
|
10995
|
-
return;
|
|
10996
|
-
}
|
|
10997
|
-
columns.clear();
|
|
10998
|
-
rowCounts.clear();
|
|
10999
|
-
}
|
|
11000
|
-
};
|
|
11001
|
-
}
|
|
11002
11011
|
function observeBackgroundRejection(promise) {
|
|
11003
11012
|
promise.catch(() => {
|
|
11004
11013
|
return;
|
|
@@ -11618,7 +11627,7 @@ async function openDockerAdapterAsync(serviceName, kind, env, cwd, overrideDatab
|
|
|
11618
11627
|
...kind === "postgresql" && schema ? { schema } : {}
|
|
11619
11628
|
});
|
|
11620
11629
|
}
|
|
11621
|
-
var createPgPoolImpl = (config) => new pg.Pool(config), createMysqlPoolImpl = (config) => mysql.createPool(config),
|
|
11630
|
+
var createPgPoolImpl = (config) => new pg.Pool(config), createMysqlPoolImpl = (config) => mysql.createPool(config), 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";
|
|
11622
11631
|
var init_docker = __esm(() => {
|
|
11623
11632
|
init_mutate();
|
|
11624
11633
|
init_sql_snapshot();
|
|
@@ -12956,7 +12965,8 @@ var init_source_meta = __esm(() => {
|
|
|
12956
12965
|
cts: "typescript",
|
|
12957
12966
|
kts: "kotlin",
|
|
12958
12967
|
cxx: "cpp",
|
|
12959
|
-
hxx: "cpp"
|
|
12968
|
+
hxx: "cpp",
|
|
12969
|
+
gd: "gdscript"
|
|
12960
12970
|
};
|
|
12961
12971
|
TEXT_SOURCE_EXTENSIONS = new Set([
|
|
12962
12972
|
...Object.keys(EXT_TO_LANG),
|
|
@@ -13859,6 +13869,72 @@ var init_s3 = __esm(() => {
|
|
|
13859
13869
|
s3DockerCurlTimeoutMs = DEFAULT_S3_DOCKER_CURL_TIMEOUT_MS;
|
|
13860
13870
|
});
|
|
13861
13871
|
|
|
13872
|
+
// web-src/server/database/adapters/sqlite-introspection.ts
|
|
13873
|
+
function sqliteTableInfoSql(table) {
|
|
13874
|
+
return `PRAGMA table_info(${sanitizeIdentifier(table)})`;
|
|
13875
|
+
}
|
|
13876
|
+
function sqliteIndexListSql(table) {
|
|
13877
|
+
return `PRAGMA index_list(${sanitizeIdentifier(table)})`;
|
|
13878
|
+
}
|
|
13879
|
+
function sqliteIndexInfoSql(index) {
|
|
13880
|
+
return `PRAGMA index_info(${sanitizeIdentifier(index)})`;
|
|
13881
|
+
}
|
|
13882
|
+
function sqliteForeignKeyListSql(table) {
|
|
13883
|
+
return `PRAGMA foreign_key_list(${sanitizeIdentifier(table)})`;
|
|
13884
|
+
}
|
|
13885
|
+
function sqliteColumnFromPragmaRow(row) {
|
|
13886
|
+
return {
|
|
13887
|
+
name: row.name,
|
|
13888
|
+
type: row.type || "TEXT",
|
|
13889
|
+
nullable: row.notnull === 0,
|
|
13890
|
+
primaryKey: row.pk > 0,
|
|
13891
|
+
defaultValue: row.dflt_value
|
|
13892
|
+
};
|
|
13893
|
+
}
|
|
13894
|
+
function sqliteTableInfoFromRow(row) {
|
|
13895
|
+
return {
|
|
13896
|
+
name: row.name,
|
|
13897
|
+
type: row.type,
|
|
13898
|
+
rowCount: null
|
|
13899
|
+
};
|
|
13900
|
+
}
|
|
13901
|
+
function sqliteRowCountUnionSql(tables) {
|
|
13902
|
+
return tables.map((table) => `SELECT ${escapeSqlString(table)} AS tbl, COUNT(*) AS cnt FROM ${sanitizeIdentifier(table)}`).join(" UNION ALL ");
|
|
13903
|
+
}
|
|
13904
|
+
function sqliteRowCountSql(table) {
|
|
13905
|
+
return `SELECT COUNT(*) AS cnt FROM ${sanitizeIdentifier(table)}`;
|
|
13906
|
+
}
|
|
13907
|
+
function assertReadonlySqliteStatement(sql) {
|
|
13908
|
+
const upper = sql.trim().toUpperCase();
|
|
13909
|
+
if (!SQLITE_READONLY_FIRST_WORDS.has(upper.split(/\s/)[0])) {
|
|
13910
|
+
throw new Error("Only SELECT, PRAGMA, EXPLAIN, and WITH queries are allowed");
|
|
13911
|
+
}
|
|
13912
|
+
if (SQLITE_BLOCKED_KEYWORDS_RE.test(upper)) {
|
|
13913
|
+
throw new Error("Query contains a disallowed statement keyword");
|
|
13914
|
+
}
|
|
13915
|
+
}
|
|
13916
|
+
function stripTrailingSemicolon(sql) {
|
|
13917
|
+
return sql.trim().replace(/;\s*$/, "");
|
|
13918
|
+
}
|
|
13919
|
+
var SQLITE_INTROSPECTION_SQL, SQLITE_READONLY_FIRST_WORDS, SQLITE_BLOCKED_KEYWORDS_RE;
|
|
13920
|
+
var init_sqlite_introspection = __esm(() => {
|
|
13921
|
+
init_sql_utils();
|
|
13922
|
+
SQLITE_INTROSPECTION_SQL = {
|
|
13923
|
+
listTables: "SELECT name, type FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%' ORDER BY name",
|
|
13924
|
+
listIndexes: "SELECT name, tbl_name FROM sqlite_master WHERE type = 'index' AND name NOT LIKE 'sqlite_%' ORDER BY name",
|
|
13925
|
+
listForeignKeyTables: "SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' AND sql NOT LIKE '%VIRTUAL%' ORDER BY name",
|
|
13926
|
+
createStatement: "SELECT sql FROM sqlite_master WHERE name = ?",
|
|
13927
|
+
triggers: "SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND tbl_name = ?"
|
|
13928
|
+
};
|
|
13929
|
+
SQLITE_READONLY_FIRST_WORDS = new Set([
|
|
13930
|
+
"SELECT",
|
|
13931
|
+
"PRAGMA",
|
|
13932
|
+
"EXPLAIN",
|
|
13933
|
+
"WITH"
|
|
13934
|
+
]);
|
|
13935
|
+
SQLITE_BLOCKED_KEYWORDS_RE = /\b(INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|ATTACH|DETACH|REPLACE|VACUUM|REINDEX|LOAD_EXTENSION)\b/;
|
|
13936
|
+
});
|
|
13937
|
+
|
|
13862
13938
|
// web-src/server/database/adapters/sqlite.ts
|
|
13863
13939
|
function safePrepare(db, sql) {
|
|
13864
13940
|
const stmt = db.prepare(sql);
|
|
@@ -13885,14 +13961,8 @@ function queryRowsToResult(rows, columns) {
|
|
|
13885
13961
|
};
|
|
13886
13962
|
}
|
|
13887
13963
|
function queryColumns(db, table) {
|
|
13888
|
-
const rows = db.prepare(
|
|
13889
|
-
return rows.map(
|
|
13890
|
-
name: row.name,
|
|
13891
|
-
type: row.type || "TEXT",
|
|
13892
|
-
nullable: row.notnull === 0,
|
|
13893
|
-
primaryKey: row.pk > 0,
|
|
13894
|
-
defaultValue: row.dflt_value
|
|
13895
|
-
}));
|
|
13964
|
+
const rows = db.prepare(sqliteTableInfoSql(table)).all();
|
|
13965
|
+
return rows.map(sqliteColumnFromPragmaRow);
|
|
13896
13966
|
}
|
|
13897
13967
|
function wrapDbWithSqlCapture(rawDb) {
|
|
13898
13968
|
return new Proxy(rawDb, {
|
|
@@ -13923,12 +13993,8 @@ function createSqliteAdapter(rawDb, openRawWriteDb) {
|
|
|
13923
13993
|
model: "sql",
|
|
13924
13994
|
capabilities: { snapshot: true },
|
|
13925
13995
|
getTables() {
|
|
13926
|
-
const rows = db.prepare(
|
|
13927
|
-
return rows.map(
|
|
13928
|
-
name: row.name,
|
|
13929
|
-
type: row.type,
|
|
13930
|
-
rowCount: null
|
|
13931
|
-
}));
|
|
13996
|
+
const rows = db.prepare(SQLITE_INTROSPECTION_SQL.listTables).all();
|
|
13997
|
+
return rows.map(sqliteTableInfoFromRow);
|
|
13932
13998
|
},
|
|
13933
13999
|
async getTablesAsync() {
|
|
13934
14000
|
return this.getTables();
|
|
@@ -13940,10 +14006,10 @@ function createSqliteAdapter(rawDb, openRawWriteDb) {
|
|
|
13940
14006
|
return this.getColumns(table);
|
|
13941
14007
|
},
|
|
13942
14008
|
getIndexes() {
|
|
13943
|
-
const rows = db.prepare(
|
|
14009
|
+
const rows = db.prepare(SQLITE_INTROSPECTION_SQL.listIndexes).all();
|
|
13944
14010
|
return rows.map((row) => {
|
|
13945
|
-
const info = db.prepare(
|
|
13946
|
-
const indexList = db.prepare(
|
|
14011
|
+
const info = db.prepare(sqliteIndexInfoSql(row.name)).all();
|
|
14012
|
+
const indexList = db.prepare(sqliteIndexListSql(row.tbl_name)).all();
|
|
13947
14013
|
const entry = indexList.find((i) => i.name === row.name);
|
|
13948
14014
|
return {
|
|
13949
14015
|
name: row.name,
|
|
@@ -13957,11 +14023,11 @@ function createSqliteAdapter(rawDb, openRawWriteDb) {
|
|
|
13957
14023
|
return this.getIndexes();
|
|
13958
14024
|
},
|
|
13959
14025
|
getForeignKeys() {
|
|
13960
|
-
const tables = db.prepare(
|
|
14026
|
+
const tables = db.prepare(SQLITE_INTROSPECTION_SQL.listForeignKeyTables).all();
|
|
13961
14027
|
const fks = [];
|
|
13962
14028
|
for (const t of tables) {
|
|
13963
14029
|
try {
|
|
13964
|
-
const rows = db.prepare(
|
|
14030
|
+
const rows = db.prepare(sqliteForeignKeyListSql(t.name)).all();
|
|
13965
14031
|
for (const row of rows) {
|
|
13966
14032
|
fks.push({
|
|
13967
14033
|
fromTable: t.name,
|
|
@@ -13988,7 +14054,7 @@ function createSqliteAdapter(rawDb, openRawWriteDb) {
|
|
|
13988
14054
|
return this.getColumnsMulti(tables);
|
|
13989
14055
|
},
|
|
13990
14056
|
getTableRowCount(table) {
|
|
13991
|
-
const row = db.prepare(
|
|
14057
|
+
const row = db.prepare(sqliteRowCountSql(table)).get();
|
|
13992
14058
|
return row?.cnt ?? 0;
|
|
13993
14059
|
},
|
|
13994
14060
|
async getTableRowCountAsync(table) {
|
|
@@ -13998,16 +14064,14 @@ function createSqliteAdapter(rawDb, openRawWriteDb) {
|
|
|
13998
14064
|
const result = new Map;
|
|
13999
14065
|
if (tables.length === 0)
|
|
14000
14066
|
return result;
|
|
14001
|
-
const parts = tables.map((t) => `SELECT '${t.replace(/'/g, "''")}' AS tbl, COUNT(*) AS cnt FROM ${sanitizeIdentifier(t)}`);
|
|
14002
|
-
const sql = parts.join(" UNION ALL ");
|
|
14003
14067
|
try {
|
|
14004
|
-
const rows = db.prepare(
|
|
14068
|
+
const rows = db.prepare(sqliteRowCountUnionSql(tables)).all();
|
|
14005
14069
|
for (const row of rows) {
|
|
14006
14070
|
result.set(row.tbl, row.cnt);
|
|
14007
14071
|
}
|
|
14008
14072
|
} catch {
|
|
14009
14073
|
for (const t of tables) {
|
|
14010
|
-
const row = db.prepare(
|
|
14074
|
+
const row = db.prepare(sqliteRowCountSql(t)).get();
|
|
14011
14075
|
result.set(t, row?.cnt ?? 0);
|
|
14012
14076
|
}
|
|
14013
14077
|
}
|
|
@@ -14059,17 +14123,8 @@ function createSqliteAdapter(rawDb, openRawWriteDb) {
|
|
|
14059
14123
|
};
|
|
14060
14124
|
},
|
|
14061
14125
|
executeReadonlyQuery(sql, params, maxRows = 1000) {
|
|
14062
|
-
|
|
14063
|
-
const
|
|
14064
|
-
const firstWord = upper.split(/\s/)[0];
|
|
14065
|
-
if (firstWord !== "SELECT" && firstWord !== "PRAGMA" && firstWord !== "EXPLAIN" && firstWord !== "WITH") {
|
|
14066
|
-
throw new Error("Only SELECT, PRAGMA, EXPLAIN, and WITH queries are allowed");
|
|
14067
|
-
}
|
|
14068
|
-
const BLOCKED_RE = /\b(INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|ATTACH|DETACH|REPLACE|VACUUM|REINDEX|LOAD_EXTENSION)\b/;
|
|
14069
|
-
if (BLOCKED_RE.test(upper)) {
|
|
14070
|
-
throw new Error("Query contains a disallowed statement keyword");
|
|
14071
|
-
}
|
|
14072
|
-
const limited = trimmed.replace(/;\s*$/, "");
|
|
14126
|
+
assertReadonlySqliteStatement(sql);
|
|
14127
|
+
const limited = stripTrailingSemicolon(sql);
|
|
14073
14128
|
const wrappedSql = `SELECT * FROM (${limited}) LIMIT ${maxRows + 1}`;
|
|
14074
14129
|
let rows;
|
|
14075
14130
|
try {
|
|
@@ -14100,14 +14155,14 @@ function createSqliteAdapter(rawDb, openRawWriteDb) {
|
|
|
14100
14155
|
return this.executeReadonlyQuery(sql, params, maxRows);
|
|
14101
14156
|
},
|
|
14102
14157
|
getCreateStatement(table) {
|
|
14103
|
-
const row = db.prepare(
|
|
14158
|
+
const row = db.prepare(SQLITE_INTROSPECTION_SQL.createStatement).get(table);
|
|
14104
14159
|
return row?.sql ?? "";
|
|
14105
14160
|
},
|
|
14106
14161
|
async getCreateStatementAsync(table) {
|
|
14107
14162
|
return this.getCreateStatement(table);
|
|
14108
14163
|
},
|
|
14109
14164
|
getTriggers(table) {
|
|
14110
|
-
const rows = db.prepare(
|
|
14165
|
+
const rows = db.prepare(SQLITE_INTROSPECTION_SQL.triggers).all(table);
|
|
14111
14166
|
return rows.map((row) => ({ name: row.name, sql: row.sql ?? "" }));
|
|
14112
14167
|
},
|
|
14113
14168
|
async getTriggersAsync(table) {
|
|
@@ -14187,6 +14242,7 @@ var init_sqlite = __esm(() => {
|
|
|
14187
14242
|
init_sql_utils();
|
|
14188
14243
|
init_sqlite_driver();
|
|
14189
14244
|
init_sql_capture();
|
|
14245
|
+
init_sqlite_introspection();
|
|
14190
14246
|
sqliteAdapterFactory = {
|
|
14191
14247
|
async open(path) {
|
|
14192
14248
|
const DbClass = await loadSqliteClass();
|
|
@@ -14219,13 +14275,13 @@ import {
|
|
|
14219
14275
|
openSync,
|
|
14220
14276
|
readSync,
|
|
14221
14277
|
realpathSync as realpathSync5,
|
|
14222
|
-
statSync as
|
|
14278
|
+
statSync as statSync5
|
|
14223
14279
|
} from "node:fs";
|
|
14224
14280
|
import { lstat, open as open2, readdir, readFile as readFile2, stat as stat2 } from "node:fs/promises";
|
|
14225
14281
|
import { basename, join as join9, relative as relative4 } from "node:path";
|
|
14226
14282
|
function isSqliteFile(fullPath) {
|
|
14227
14283
|
try {
|
|
14228
|
-
const stat3 =
|
|
14284
|
+
const stat3 = statSync5(fullPath);
|
|
14229
14285
|
if (!stat3.isFile() || stat3.size < 16)
|
|
14230
14286
|
return false;
|
|
14231
14287
|
const buf = Buffer.alloc(16);
|
|
@@ -14423,6 +14479,8 @@ function dbKindDisplayName(kind) {
|
|
|
14423
14479
|
return "MySQL";
|
|
14424
14480
|
case "sqlite":
|
|
14425
14481
|
return "SQLite";
|
|
14482
|
+
case "d1":
|
|
14483
|
+
return "Cloudflare D1";
|
|
14426
14484
|
}
|
|
14427
14485
|
}
|
|
14428
14486
|
function detectDbKindFromContainerPort(port) {
|
|
@@ -15849,6 +15907,310 @@ function asAsyncDoc(source) {
|
|
|
15849
15907
|
};
|
|
15850
15908
|
}
|
|
15851
15909
|
|
|
15910
|
+
// web-src/server/database/adapters/d1.ts
|
|
15911
|
+
function isD1InternalName(name) {
|
|
15912
|
+
return D1_INTERNAL_NAME_RE.test(name);
|
|
15913
|
+
}
|
|
15914
|
+
function isD1HttpError(err) {
|
|
15915
|
+
return err instanceof D1HttpError;
|
|
15916
|
+
}
|
|
15917
|
+
function d1ErrorMessage(envelope, status, sql) {
|
|
15918
|
+
const first = envelope.errors?.find((entry) => entry?.message);
|
|
15919
|
+
const detail = first?.message?.replace(/\s+/g, " ").trim().slice(0, 240);
|
|
15920
|
+
const statement = sql.replace(/\s+/g, " ").trim().slice(0, 160);
|
|
15921
|
+
return `${detail || `D1 HTTP ${status}`} (sql: ${statement})`;
|
|
15922
|
+
}
|
|
15923
|
+
function toDbValue(value) {
|
|
15924
|
+
if (value === null || value === undefined)
|
|
15925
|
+
return null;
|
|
15926
|
+
if (typeof value === "number" || typeof value === "string")
|
|
15927
|
+
return value;
|
|
15928
|
+
if (typeof value === "boolean")
|
|
15929
|
+
return value;
|
|
15930
|
+
if (Array.isArray(value)) {
|
|
15931
|
+
return value.every((byte) => typeof byte === "number") ? new Uint8Array(value) : JSON.stringify(value);
|
|
15932
|
+
}
|
|
15933
|
+
if (typeof value === "object")
|
|
15934
|
+
return JSON.stringify(value);
|
|
15935
|
+
return String(value);
|
|
15936
|
+
}
|
|
15937
|
+
function parseRawResult(result) {
|
|
15938
|
+
const columns = Array.isArray(result?.results?.columns) ? result.results.columns.map((name) => String(name)) : [];
|
|
15939
|
+
const rawRows = Array.isArray(result?.results?.rows) ? result.results.rows : [];
|
|
15940
|
+
const rows = rawRows.map((row) => Array.isArray(row) ? row.map(toDbValue) : []);
|
|
15941
|
+
return {
|
|
15942
|
+
columns,
|
|
15943
|
+
columnTypes: columns.map(() => "TEXT"),
|
|
15944
|
+
rows,
|
|
15945
|
+
rowCount: rows.length
|
|
15946
|
+
};
|
|
15947
|
+
}
|
|
15948
|
+
async function d1Fetch(url, init, signal) {
|
|
15949
|
+
if (signal?.aborted)
|
|
15950
|
+
throw new D1HttpError(503, "D1 request aborted");
|
|
15951
|
+
const controller = new AbortController;
|
|
15952
|
+
const onParentAbort = () => controller.abort();
|
|
15953
|
+
signal?.addEventListener("abort", onParentAbort, { once: true });
|
|
15954
|
+
const timer = setTimeout(() => controller.abort(), d1RequestTimeoutMs);
|
|
15955
|
+
try {
|
|
15956
|
+
const d1FetchImpl = d1FetchOverride ?? globalThis.fetch;
|
|
15957
|
+
return await d1FetchImpl(url, { ...init, signal: controller.signal });
|
|
15958
|
+
} catch (err) {
|
|
15959
|
+
if (signal?.aborted)
|
|
15960
|
+
throw new D1HttpError(503, "D1 request aborted");
|
|
15961
|
+
if (controller.signal.aborted) {
|
|
15962
|
+
throw new D1HttpError(503, `D1 request timed out after ${d1RequestTimeoutMs}ms`);
|
|
15963
|
+
}
|
|
15964
|
+
throw new D1HttpError(503, `D1 request failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
15965
|
+
} finally {
|
|
15966
|
+
clearTimeout(timer);
|
|
15967
|
+
signal?.removeEventListener("abort", onParentAbort);
|
|
15968
|
+
}
|
|
15969
|
+
}
|
|
15970
|
+
function createD1Adapter(config) {
|
|
15971
|
+
const baseUrl = (config.apiBaseUrl || DEFAULT_D1_API_BASE_URL).replace(/\/$/, "");
|
|
15972
|
+
const rawUrl = `${baseUrl}/accounts/${encodeURIComponent(config.accountId)}/d1/database/${encodeURIComponent(config.databaseId)}/raw`;
|
|
15973
|
+
const tableMetaCache = createTableMetaCache();
|
|
15974
|
+
async function runSql(sql, params = [], signal) {
|
|
15975
|
+
if (!config.apiToken) {
|
|
15976
|
+
throw new D1HttpError(401, "D1 API token is missing. Reopen the connection dialog and enter the API token again (credentials are never stored in the repository).");
|
|
15977
|
+
}
|
|
15978
|
+
recordSql(sql);
|
|
15979
|
+
const res = await d1Fetch(rawUrl, {
|
|
15980
|
+
method: "POST",
|
|
15981
|
+
headers: {
|
|
15982
|
+
"Content-Type": "application/json",
|
|
15983
|
+
Authorization: `Bearer ${config.apiToken}`
|
|
15984
|
+
},
|
|
15985
|
+
body: JSON.stringify({ sql, params })
|
|
15986
|
+
}, signal);
|
|
15987
|
+
let envelope;
|
|
15988
|
+
try {
|
|
15989
|
+
envelope = await res.json();
|
|
15990
|
+
} catch {
|
|
15991
|
+
throw new D1HttpError(res.status, `D1 HTTP ${res.status}: invalid JSON`);
|
|
15992
|
+
}
|
|
15993
|
+
if (!res.ok || envelope.success === false) {
|
|
15994
|
+
const status = res.ok ? 400 : res.status;
|
|
15995
|
+
throw new D1HttpError(status, d1ErrorMessage(envelope, status, sql));
|
|
15996
|
+
}
|
|
15997
|
+
return parseRawResult(envelope.result?.[0]);
|
|
15998
|
+
}
|
|
15999
|
+
async function runSqlRecords(sql, params = [], signal) {
|
|
16000
|
+
const result = await runSql(sql, params, signal);
|
|
16001
|
+
return result.rows.map((row) => {
|
|
16002
|
+
const record = {};
|
|
16003
|
+
result.columns.forEach((column, index) => {
|
|
16004
|
+
record[column] = row[index] ?? null;
|
|
16005
|
+
});
|
|
16006
|
+
return record;
|
|
16007
|
+
});
|
|
16008
|
+
}
|
|
16009
|
+
async function loadColumns(table, signal) {
|
|
16010
|
+
const rows = await runSqlRecords(sqliteTableInfoSql(table), [], signal);
|
|
16011
|
+
return rows.map(sqliteColumnFromPragmaRow);
|
|
16012
|
+
}
|
|
16013
|
+
function getColumnsAsync(table, signal) {
|
|
16014
|
+
return tableMetaCache.getColumns(table, () => loadColumns(table, signal));
|
|
16015
|
+
}
|
|
16016
|
+
async function countRows(table, signal) {
|
|
16017
|
+
const result = await runSql(sqliteRowCountSql(table), [], signal);
|
|
16018
|
+
return Number(result.rows[0]?.[0] ?? 0);
|
|
16019
|
+
}
|
|
16020
|
+
async function selectPage(table, columns, options, signal) {
|
|
16021
|
+
const order = buildOrderClause(filterOrderByColumns(options.orderBy, columns.map((column) => column.name)));
|
|
16022
|
+
const whereClause = options.where ? ` WHERE ${options.where}` : "";
|
|
16023
|
+
return runSql(`SELECT * FROM ${sanitizeIdentifier(table)}${whereClause}${order} LIMIT ? OFFSET ?`, [...options.params ?? [], options.limit, options.offset], signal);
|
|
16024
|
+
}
|
|
16025
|
+
const adapter = {
|
|
16026
|
+
kind: "d1",
|
|
16027
|
+
model: "sql",
|
|
16028
|
+
capabilities: { snapshot: true },
|
|
16029
|
+
async getTablesAsync(signal) {
|
|
16030
|
+
const rows = await runSqlRecords(SQLITE_INTROSPECTION_SQL.listTables, [], signal);
|
|
16031
|
+
return rows.filter((row) => !isD1InternalName(row.name)).map(sqliteTableInfoFromRow);
|
|
16032
|
+
},
|
|
16033
|
+
getColumnsAsync,
|
|
16034
|
+
async getColumnsMultiAsync(tables, signal) {
|
|
16035
|
+
const entries = await Promise.all(tables.map(async (table) => [table, await getColumnsAsync(table, signal)]));
|
|
16036
|
+
return new Map(entries);
|
|
16037
|
+
},
|
|
16038
|
+
async getIndexesAsync(signal) {
|
|
16039
|
+
const indexes = await runSqlRecords(SQLITE_INTROSPECTION_SQL.listIndexes, [], signal);
|
|
16040
|
+
const described = await Promise.all(indexes.filter((index) => !isD1InternalName(index.name) && !isD1InternalName(index.tbl_name)).map(async (index) => {
|
|
16041
|
+
try {
|
|
16042
|
+
const [info, indexList] = await Promise.all([
|
|
16043
|
+
runSqlRecords(sqliteIndexInfoSql(index.name), [], signal),
|
|
16044
|
+
runSqlRecords(sqliteIndexListSql(index.tbl_name), [], signal)
|
|
16045
|
+
]);
|
|
16046
|
+
const entry = indexList.find((row) => row.name === index.name);
|
|
16047
|
+
return {
|
|
16048
|
+
name: index.name,
|
|
16049
|
+
table: index.tbl_name,
|
|
16050
|
+
columns: info.map((row) => row.name),
|
|
16051
|
+
unique: entry ? Number(entry.unique) === 1 : false
|
|
16052
|
+
};
|
|
16053
|
+
} catch (err) {
|
|
16054
|
+
if (isAbortLikeError(err, signal))
|
|
16055
|
+
throw err;
|
|
16056
|
+
return null;
|
|
16057
|
+
}
|
|
16058
|
+
}));
|
|
16059
|
+
return described.filter((index) => index !== null);
|
|
16060
|
+
},
|
|
16061
|
+
async getForeignKeysAsync(signal) {
|
|
16062
|
+
const tables = await runSqlRecords(SQLITE_INTROSPECTION_SQL.listForeignKeyTables, [], signal);
|
|
16063
|
+
const perTable = await Promise.all(tables.filter((table) => !isD1InternalName(table.name)).map(async (table) => {
|
|
16064
|
+
try {
|
|
16065
|
+
const rows = await runSqlRecords(sqliteForeignKeyListSql(table.name), [], signal);
|
|
16066
|
+
return rows.map((row) => ({
|
|
16067
|
+
fromTable: table.name,
|
|
16068
|
+
fromColumn: row.from,
|
|
16069
|
+
toTable: row.table,
|
|
16070
|
+
toColumn: row.to
|
|
16071
|
+
}));
|
|
16072
|
+
} catch (err) {
|
|
16073
|
+
if (isAbortLikeError(err, signal))
|
|
16074
|
+
throw err;
|
|
16075
|
+
return [];
|
|
16076
|
+
}
|
|
16077
|
+
}));
|
|
16078
|
+
return perTable.flat();
|
|
16079
|
+
},
|
|
16080
|
+
getTableRowCountAsync(table, signal) {
|
|
16081
|
+
return tableMetaCache.getRowCount(table, () => countRows(table, signal));
|
|
16082
|
+
},
|
|
16083
|
+
async getTableRowCountsAsync(tables, signal) {
|
|
16084
|
+
const result = new Map;
|
|
16085
|
+
const countable = tables.filter((table) => !isD1InternalName(table));
|
|
16086
|
+
if (countable.length === 0)
|
|
16087
|
+
return result;
|
|
16088
|
+
try {
|
|
16089
|
+
const rows = await runSqlRecords(sqliteRowCountUnionSql(countable), [], signal);
|
|
16090
|
+
for (const row of rows)
|
|
16091
|
+
result.set(String(row.tbl), Number(row.cnt));
|
|
16092
|
+
return result;
|
|
16093
|
+
} catch {
|
|
16094
|
+
for (const table of countable) {
|
|
16095
|
+
try {
|
|
16096
|
+
result.set(table, await countRows(table, signal));
|
|
16097
|
+
} catch (err) {
|
|
16098
|
+
if (isAbortLikeError(err, signal))
|
|
16099
|
+
throw err;
|
|
16100
|
+
}
|
|
16101
|
+
}
|
|
16102
|
+
return result;
|
|
16103
|
+
}
|
|
16104
|
+
},
|
|
16105
|
+
async getTablePageAsync(table, options, signal) {
|
|
16106
|
+
const columns = await getColumnsAsync(table, signal);
|
|
16107
|
+
return selectPage(table, columns, options, signal);
|
|
16108
|
+
},
|
|
16109
|
+
async getTablePageWithMeta(table, options, signal) {
|
|
16110
|
+
const columns = await getColumnsAsync(table, signal);
|
|
16111
|
+
const [page, totalRows] = await Promise.all([
|
|
16112
|
+
selectPage(table, columns, options, signal),
|
|
16113
|
+
countRows(table, signal)
|
|
16114
|
+
]);
|
|
16115
|
+
return {
|
|
16116
|
+
columns,
|
|
16117
|
+
rows: page.rows,
|
|
16118
|
+
rowCount: page.rowCount,
|
|
16119
|
+
totalRows
|
|
16120
|
+
};
|
|
16121
|
+
},
|
|
16122
|
+
async getFilteredTablePageWithMeta(table, options, signal) {
|
|
16123
|
+
const columns = await getColumnsAsync(table, signal);
|
|
16124
|
+
const columnNames = columns.map((column) => column.name);
|
|
16125
|
+
const filter = buildFilterWhere(filterGroupedColumns(options.grouped, columnNames), "sqlite", filterExactColumns(options.exact, columnNames));
|
|
16126
|
+
const whereClause = filter.where ? ` WHERE ${filter.where}` : "";
|
|
16127
|
+
const [page, countResult] = await Promise.all([
|
|
16128
|
+
selectPage(table, columns, { ...options, where: filter.where, params: filter.params }, signal),
|
|
16129
|
+
runSql(`SELECT COUNT(*) AS cnt FROM ${sanitizeIdentifier(table)}${whereClause}`, filter.params, signal)
|
|
16130
|
+
]);
|
|
16131
|
+
return {
|
|
16132
|
+
columns,
|
|
16133
|
+
rows: page.rows,
|
|
16134
|
+
rowCount: page.rowCount,
|
|
16135
|
+
totalRows: Number(countResult.rows[0]?.[0] ?? 0)
|
|
16136
|
+
};
|
|
16137
|
+
},
|
|
16138
|
+
async executeReadonlyQueryAsync(sql, params, maxRows = 1000, signal) {
|
|
16139
|
+
assertReadonlySqliteStatement(sql);
|
|
16140
|
+
const limited = stripTrailingSemicolon(sql);
|
|
16141
|
+
let result;
|
|
16142
|
+
try {
|
|
16143
|
+
result = await runSql(`SELECT * FROM (${limited}) LIMIT ${maxRows + 1}`, params, signal);
|
|
16144
|
+
} catch (wrapErr) {
|
|
16145
|
+
try {
|
|
16146
|
+
result = await runSql(`${limited} LIMIT ${maxRows + 1}`, params, signal);
|
|
16147
|
+
} catch {
|
|
16148
|
+
throw wrapErr;
|
|
16149
|
+
}
|
|
16150
|
+
}
|
|
16151
|
+
return result.rows.length > maxRows ? { ...result, rows: result.rows.slice(0, maxRows), rowCount: maxRows } : result;
|
|
16152
|
+
},
|
|
16153
|
+
invalidateTableMetaCache(table) {
|
|
16154
|
+
tableMetaCache.invalidate(table);
|
|
16155
|
+
},
|
|
16156
|
+
async getCreateStatementAsync(table, signal) {
|
|
16157
|
+
const rows = await runSqlRecords(SQLITE_INTROSPECTION_SQL.createStatement, [table], signal);
|
|
16158
|
+
return rows[0]?.sql ?? "";
|
|
16159
|
+
},
|
|
16160
|
+
async getTriggersAsync(table, signal) {
|
|
16161
|
+
const rows = await runSqlRecords(SQLITE_INTROSPECTION_SQL.triggers, [table], signal);
|
|
16162
|
+
return rows.map((row) => ({ name: row.name, sql: row.sql ?? "" }));
|
|
16163
|
+
},
|
|
16164
|
+
close() {},
|
|
16165
|
+
async* iterateForSnapshot(table, signal) {
|
|
16166
|
+
const columns = await adapter.getColumnsAsync(table, signal);
|
|
16167
|
+
const colNames = columns.map((column) => column.name);
|
|
16168
|
+
const pkColumns = columns.filter((column) => column.primaryKey).map((column) => column.name);
|
|
16169
|
+
let offset = 0;
|
|
16170
|
+
let rowIndex = 0;
|
|
16171
|
+
for (;; ) {
|
|
16172
|
+
if (signal?.aborted)
|
|
16173
|
+
return;
|
|
16174
|
+
const result = await adapter.getTablePageAsync(table, { offset, limit: SQL_SNAPSHOT_BATCH_SIZE }, signal);
|
|
16175
|
+
if (result.rows.length === 0)
|
|
16176
|
+
return;
|
|
16177
|
+
for (const row of result.rows) {
|
|
16178
|
+
yield {
|
|
16179
|
+
keyJson: buildRowKeyJson(pkColumns, colNames, row, rowIndex),
|
|
16180
|
+
rowHash: computeRowHash(colNames, row),
|
|
16181
|
+
payloadJson: rowToPayloadJson(colNames, row)
|
|
16182
|
+
};
|
|
16183
|
+
rowIndex++;
|
|
16184
|
+
}
|
|
16185
|
+
offset += result.rows.length;
|
|
16186
|
+
if (result.rows.length < SQL_SNAPSHOT_BATCH_SIZE)
|
|
16187
|
+
return;
|
|
16188
|
+
}
|
|
16189
|
+
},
|
|
16190
|
+
async listSnapshotContainers() {
|
|
16191
|
+
const tables = await adapter.getTablesAsync();
|
|
16192
|
+
return tables.filter((table) => table.type === "table").map((table) => ({ id: table.name, label: table.name }));
|
|
16193
|
+
}
|
|
16194
|
+
};
|
|
16195
|
+
return adapter;
|
|
16196
|
+
}
|
|
16197
|
+
var DEFAULT_D1_API_BASE_URL = "https://api.cloudflare.com/client/v4", D1_INTERNAL_NAME_RE, DEFAULT_D1_REQUEST_TIMEOUT_MS = 20000, d1RequestTimeoutMs, d1FetchOverride = null, D1HttpError;
|
|
16198
|
+
var init_d1 = __esm(() => {
|
|
16199
|
+
init_sql_snapshot();
|
|
16200
|
+
init_sql_utils();
|
|
16201
|
+
init_sql_capture();
|
|
16202
|
+
init_sqlite_introspection();
|
|
16203
|
+
D1_INTERNAL_NAME_RE = /^_cf_/i;
|
|
16204
|
+
d1RequestTimeoutMs = DEFAULT_D1_REQUEST_TIMEOUT_MS;
|
|
16205
|
+
D1HttpError = class D1HttpError extends Error {
|
|
16206
|
+
status;
|
|
16207
|
+
constructor(status, message) {
|
|
16208
|
+
super(message);
|
|
16209
|
+
this.status = status;
|
|
16210
|
+
}
|
|
16211
|
+
};
|
|
16212
|
+
});
|
|
16213
|
+
|
|
15852
16214
|
// web-src/server/database/adapters/dynamodb.ts
|
|
15853
16215
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
15854
16216
|
import { createHash as createHash5, createHmac as createHmac2 } from "node:crypto";
|
|
@@ -16387,6 +16749,136 @@ var init_connection_pool = __esm(() => {
|
|
|
16387
16749
|
pool = new Map;
|
|
16388
16750
|
});
|
|
16389
16751
|
|
|
16752
|
+
// web-src/server/database/credential-store.ts
|
|
16753
|
+
function isKeychainAvailable() {
|
|
16754
|
+
if (keychainEnabledOverride !== null)
|
|
16755
|
+
return keychainEnabledOverride;
|
|
16756
|
+
if (false)
|
|
16757
|
+
;
|
|
16758
|
+
return process.platform === "darwin";
|
|
16759
|
+
}
|
|
16760
|
+
function accountFor(cwd, connectionId) {
|
|
16761
|
+
return `${cwd}#${connectionId}`;
|
|
16762
|
+
}
|
|
16763
|
+
function quoteSecurityArg(value) {
|
|
16764
|
+
if (hasControlCharacter(value))
|
|
16765
|
+
return null;
|
|
16766
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
16767
|
+
}
|
|
16768
|
+
async function runSecurityAsync(opts) {
|
|
16769
|
+
if (!keychainSpawnOverride && false) {}
|
|
16770
|
+
const spawn3 = keychainSpawnOverride ?? spawnCollectAsync;
|
|
16771
|
+
const result = await spawn3({
|
|
16772
|
+
command: SECURITY_COMMAND,
|
|
16773
|
+
args: opts.args,
|
|
16774
|
+
...opts.input === undefined ? {} : { input: opts.input },
|
|
16775
|
+
timeoutMs: KEYCHAIN_TIMEOUT_MS,
|
|
16776
|
+
abortMessage: "keychain access aborted",
|
|
16777
|
+
timeoutMessage: `keychain access timed out after ${KEYCHAIN_TIMEOUT_MS}ms`,
|
|
16778
|
+
rejectOnError: false
|
|
16779
|
+
});
|
|
16780
|
+
return {
|
|
16781
|
+
stdout: result.stdout.toString("utf8"),
|
|
16782
|
+
stderr: result.stderr.toString("utf8"),
|
|
16783
|
+
code: result.code
|
|
16784
|
+
};
|
|
16785
|
+
}
|
|
16786
|
+
function warnKeychain(action, detail) {
|
|
16787
|
+
console.warn(`[code-viewer] keychain ${action} failed: ${detail.replace(/\s+/g, " ").trim().slice(0, 200)}`);
|
|
16788
|
+
}
|
|
16789
|
+
async function saveConnectionSecretsAsync(cwd, connectionId, secrets) {
|
|
16790
|
+
if (!isKeychainAvailable())
|
|
16791
|
+
return false;
|
|
16792
|
+
if (Object.keys(secrets).length === 0) {
|
|
16793
|
+
return deleteConnectionSecretsAsync(cwd, connectionId);
|
|
16794
|
+
}
|
|
16795
|
+
const account = quoteSecurityArg(accountFor(cwd, connectionId));
|
|
16796
|
+
const label = quoteSecurityArg(`code-viewer: ${connectionId}`);
|
|
16797
|
+
if (!account || !label) {
|
|
16798
|
+
warnKeychain("save", "connection id or path contains control characters");
|
|
16799
|
+
return false;
|
|
16800
|
+
}
|
|
16801
|
+
const payload = Buffer.from(JSON.stringify(secrets), "utf8").toString("base64");
|
|
16802
|
+
try {
|
|
16803
|
+
const result = await runSecurityAsync({
|
|
16804
|
+
args: ["-i"],
|
|
16805
|
+
input: `add-generic-password -U -s "${KEYCHAIN_SERVICE}" -a ${account} -l ${label} -w "${payload}"
|
|
16806
|
+
`
|
|
16807
|
+
});
|
|
16808
|
+
if (result.code !== 0) {
|
|
16809
|
+
warnKeychain("save", result.stderr || `exit ${result.code}`);
|
|
16810
|
+
return false;
|
|
16811
|
+
}
|
|
16812
|
+
return true;
|
|
16813
|
+
} catch (err) {
|
|
16814
|
+
warnKeychain("save", err instanceof Error ? err.message : String(err));
|
|
16815
|
+
return false;
|
|
16816
|
+
}
|
|
16817
|
+
}
|
|
16818
|
+
async function loadConnectionSecretsAsync(cwd, connectionId) {
|
|
16819
|
+
if (!isKeychainAvailable())
|
|
16820
|
+
return null;
|
|
16821
|
+
const account = accountFor(cwd, connectionId);
|
|
16822
|
+
if (hasControlCharacter(account))
|
|
16823
|
+
return null;
|
|
16824
|
+
try {
|
|
16825
|
+
const result = await runSecurityAsync({
|
|
16826
|
+
args: [
|
|
16827
|
+
"find-generic-password",
|
|
16828
|
+
"-s",
|
|
16829
|
+
KEYCHAIN_SERVICE,
|
|
16830
|
+
"-a",
|
|
16831
|
+
account,
|
|
16832
|
+
"-w"
|
|
16833
|
+
]
|
|
16834
|
+
});
|
|
16835
|
+
if (result.code === ERR_SEC_ITEM_NOT_FOUND)
|
|
16836
|
+
return null;
|
|
16837
|
+
if (result.code !== 0) {
|
|
16838
|
+
warnKeychain("read", result.stderr || `exit ${result.code}`);
|
|
16839
|
+
return null;
|
|
16840
|
+
}
|
|
16841
|
+
const decoded = Buffer.from(result.stdout.trim(), "base64").toString("utf8");
|
|
16842
|
+
const parsed = JSON.parse(decoded);
|
|
16843
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
16844
|
+
return null;
|
|
16845
|
+
}
|
|
16846
|
+
const secrets = {};
|
|
16847
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
16848
|
+
if (typeof value === "string")
|
|
16849
|
+
secrets[key] = value;
|
|
16850
|
+
}
|
|
16851
|
+
return Object.keys(secrets).length > 0 ? secrets : null;
|
|
16852
|
+
} catch (err) {
|
|
16853
|
+
warnKeychain("read", err instanceof Error ? err.message : String(err));
|
|
16854
|
+
return null;
|
|
16855
|
+
}
|
|
16856
|
+
}
|
|
16857
|
+
async function deleteConnectionSecretsAsync(cwd, connectionId) {
|
|
16858
|
+
if (!isKeychainAvailable())
|
|
16859
|
+
return false;
|
|
16860
|
+
const account = accountFor(cwd, connectionId);
|
|
16861
|
+
if (hasControlCharacter(account))
|
|
16862
|
+
return false;
|
|
16863
|
+
try {
|
|
16864
|
+
const result = await runSecurityAsync({
|
|
16865
|
+
args: ["delete-generic-password", "-s", KEYCHAIN_SERVICE, "-a", account]
|
|
16866
|
+
});
|
|
16867
|
+
if (result.code !== 0 && result.code !== ERR_SEC_ITEM_NOT_FOUND) {
|
|
16868
|
+
warnKeychain("delete", result.stderr || `exit ${result.code}`);
|
|
16869
|
+
return false;
|
|
16870
|
+
}
|
|
16871
|
+
return true;
|
|
16872
|
+
} catch (err) {
|
|
16873
|
+
warnKeychain("delete", err instanceof Error ? err.message : String(err));
|
|
16874
|
+
return false;
|
|
16875
|
+
}
|
|
16876
|
+
}
|
|
16877
|
+
var SECURITY_COMMAND = "/usr/bin/security", KEYCHAIN_SERVICE = "code-viewer", KEYCHAIN_TIMEOUT_MS = 5000, ERR_SEC_ITEM_NOT_FOUND = 44, keychainEnabledOverride = null, keychainSpawnOverride = null;
|
|
16878
|
+
var init_credential_store = __esm(() => {
|
|
16879
|
+
init_spawn_runner();
|
|
16880
|
+
});
|
|
16881
|
+
|
|
16390
16882
|
// web-src/server/database/connections-store.ts
|
|
16391
16883
|
import { randomUUID } from "node:crypto";
|
|
16392
16884
|
import { chmod } from "node:fs/promises";
|
|
@@ -16394,15 +16886,45 @@ import { join as join12 } from "node:path";
|
|
|
16394
16886
|
function secretKey(cwd, id) {
|
|
16395
16887
|
return `${cwd}\x00${id}`;
|
|
16396
16888
|
}
|
|
16889
|
+
async function hydrateFromKeychainAsync(cwd, connections) {
|
|
16890
|
+
if (!isKeychainAvailable())
|
|
16891
|
+
return;
|
|
16892
|
+
await Promise.all(connections.map((connection) => {
|
|
16893
|
+
const key = secretKey(cwd, connection.id);
|
|
16894
|
+
if (runtimeSecrets.has(key))
|
|
16895
|
+
return;
|
|
16896
|
+
const inFlight = keychainLookups.get(key);
|
|
16897
|
+
if (inFlight)
|
|
16898
|
+
return inFlight;
|
|
16899
|
+
const lookup = loadConnectionSecretsAsync(cwd, connection.id).then((stored) => {
|
|
16900
|
+
if (stored)
|
|
16901
|
+
runtimeSecrets.set(key, extractSecrets(stored));
|
|
16902
|
+
}).catch(() => {
|
|
16903
|
+
keychainLookups.delete(key);
|
|
16904
|
+
});
|
|
16905
|
+
keychainLookups.set(key, lookup);
|
|
16906
|
+
return lookup;
|
|
16907
|
+
}));
|
|
16908
|
+
}
|
|
16397
16909
|
function extractSecrets(value) {
|
|
16398
|
-
|
|
16399
|
-
|
|
16400
|
-
|
|
16401
|
-
|
|
16402
|
-
|
|
16403
|
-
|
|
16404
|
-
|
|
16405
|
-
|
|
16910
|
+
const secrets = {};
|
|
16911
|
+
for (const field of RUNTIME_ONLY_FIELDS) {
|
|
16912
|
+
if (typeof value[field] === "string")
|
|
16913
|
+
secrets[field] = value[field];
|
|
16914
|
+
}
|
|
16915
|
+
return secrets;
|
|
16916
|
+
}
|
|
16917
|
+
function preservedSecrets(input, existing) {
|
|
16918
|
+
if (!existing)
|
|
16919
|
+
return {};
|
|
16920
|
+
const preserved = {};
|
|
16921
|
+
const source = existing;
|
|
16922
|
+
for (const field of PRESERVED_SECRET_FIELDS) {
|
|
16923
|
+
if (input[field] === undefined && field in source) {
|
|
16924
|
+
preserved[field] = source[field];
|
|
16925
|
+
}
|
|
16926
|
+
}
|
|
16927
|
+
return preserved;
|
|
16406
16928
|
}
|
|
16407
16929
|
function withRuntimeSecrets(cwd, connection) {
|
|
16408
16930
|
return {
|
|
@@ -16496,6 +17018,19 @@ function sanitizeConnection(raw) {
|
|
|
16496
17018
|
password: requiredString(input.password)
|
|
16497
17019
|
};
|
|
16498
17020
|
}
|
|
17021
|
+
if (input.kind === "d1") {
|
|
17022
|
+
const accountId = requiredString(input.accountId, 128).trim();
|
|
17023
|
+
const databaseId = requiredString(input.databaseId, 128).trim();
|
|
17024
|
+
if (!accountId || !databaseId)
|
|
17025
|
+
return null;
|
|
17026
|
+
return {
|
|
17027
|
+
...base,
|
|
17028
|
+
kind: "d1",
|
|
17029
|
+
accountId,
|
|
17030
|
+
databaseId,
|
|
17031
|
+
apiToken: requiredString(input.apiToken)
|
|
17032
|
+
};
|
|
17033
|
+
}
|
|
16499
17034
|
if (input.kind === "s3" || input.kind === "dynamodb") {
|
|
16500
17035
|
const endpoint = validEndpoint(input.endpoint);
|
|
16501
17036
|
const region = requiredString(input.region).trim();
|
|
@@ -16514,16 +17049,19 @@ function sanitizeConnection(raw) {
|
|
|
16514
17049
|
}
|
|
16515
17050
|
return null;
|
|
16516
17051
|
}
|
|
17052
|
+
function assertConnectionCredentials(connection) {
|
|
17053
|
+
const missing = !connection || (connection.kind === "postgresql" || connection.kind === "mysql") && !connection.user || (connection.kind === "s3" || connection.kind === "dynamodb") && !connection.accessKeyId || connection.kind === "d1" && !connection.apiToken;
|
|
17054
|
+
if (!connection || missing) {
|
|
17055
|
+
throw new Error("invalid datastore connection");
|
|
17056
|
+
}
|
|
17057
|
+
return connection;
|
|
17058
|
+
}
|
|
16517
17059
|
function validateDatastoreConnection(raw, fallbackId = "connection:0000000000000000") {
|
|
16518
17060
|
const input = raw && typeof raw === "object" ? raw : {};
|
|
16519
|
-
|
|
17061
|
+
return assertConnectionCredentials(sanitizeConnection({
|
|
16520
17062
|
...input,
|
|
16521
17063
|
id: typeof input.id === "string" && input.id ? input.id : fallbackId
|
|
16522
|
-
});
|
|
16523
|
-
if (!connection || (connection.kind === "postgresql" || connection.kind === "mysql") && !connection.user || (connection.kind === "s3" || connection.kind === "dynamodb") && !connection.accessKeyId) {
|
|
16524
|
-
throw new Error("invalid datastore connection");
|
|
16525
|
-
}
|
|
16526
|
-
return connection;
|
|
17064
|
+
}));
|
|
16527
17065
|
}
|
|
16528
17066
|
function sanitizeState(raw) {
|
|
16529
17067
|
if (!raw || typeof raw !== "object")
|
|
@@ -16551,7 +17089,9 @@ async function protectFile(cwd) {
|
|
|
16551
17089
|
});
|
|
16552
17090
|
}
|
|
16553
17091
|
async function loadDatastoreConnections(cwd) {
|
|
16554
|
-
|
|
17092
|
+
const { connections } = await store.load(cwd);
|
|
17093
|
+
await hydrateFromKeychainAsync(cwd, connections);
|
|
17094
|
+
return connections.map((connection) => withRuntimeSecrets(cwd, connection));
|
|
16555
17095
|
}
|
|
16556
17096
|
async function findDatastoreConnection(cwd, id) {
|
|
16557
17097
|
return (await loadDatastoreConnections(cwd)).find((entry) => entry.id === id) ?? null;
|
|
@@ -16563,17 +17103,12 @@ async function saveDatastoreConnection(cwd, raw) {
|
|
|
16563
17103
|
const result = await store.update(cwd, (state) => {
|
|
16564
17104
|
const storedExisting = state.connections.find((entry) => entry.id === id);
|
|
16565
17105
|
const existing = storedExisting ? withRuntimeSecrets(cwd, storedExisting) : undefined;
|
|
16566
|
-
const merged = sanitizeConnection({
|
|
17106
|
+
const merged = assertConnectionCredentials(sanitizeConnection({
|
|
16567
17107
|
...existing ?? {},
|
|
16568
17108
|
...input,
|
|
16569
17109
|
id,
|
|
16570
|
-
|
|
16571
|
-
|
|
16572
|
-
sessionToken: input.sessionToken === undefined && existing && "sessionToken" in existing ? existing.sessionToken : input.sessionToken
|
|
16573
|
-
});
|
|
16574
|
-
if (!merged || (merged.kind === "postgresql" || merged.kind === "mysql") && !merged.user || (merged.kind === "s3" || merged.kind === "dynamodb") && !merged.accessKeyId) {
|
|
16575
|
-
throw new Error("invalid datastore connection");
|
|
16576
|
-
}
|
|
17110
|
+
...preservedSecrets(input, existing)
|
|
17111
|
+
}));
|
|
16577
17112
|
const connections = state.connections.filter((entry) => entry.id !== id);
|
|
16578
17113
|
if (!storedExisting && connections.length >= MAX_CONNECTIONS2) {
|
|
16579
17114
|
throw new Error("too many datastore connections");
|
|
@@ -16584,7 +17119,10 @@ async function saveDatastoreConnection(cwd, raw) {
|
|
|
16584
17119
|
result: merged
|
|
16585
17120
|
};
|
|
16586
17121
|
});
|
|
16587
|
-
|
|
17122
|
+
const secrets = extractSecrets(result);
|
|
17123
|
+
runtimeSecrets.set(secretKey(cwd, result.id), secrets);
|
|
17124
|
+
keychainLookups.set(secretKey(cwd, result.id), Promise.resolve());
|
|
17125
|
+
await saveConnectionSecretsAsync(cwd, result.id, secrets);
|
|
16588
17126
|
await protectFile(cwd);
|
|
16589
17127
|
return result;
|
|
16590
17128
|
}
|
|
@@ -16597,8 +17135,10 @@ async function deleteDatastoreConnection(cwd, id) {
|
|
|
16597
17135
|
};
|
|
16598
17136
|
});
|
|
16599
17137
|
runtimeSecrets.delete(secretKey(cwd, id));
|
|
17138
|
+
keychainLookups.delete(secretKey(cwd, id));
|
|
17139
|
+
const secretsRemoved = isKeychainAvailable() ? await deleteConnectionSecretsAsync(cwd, id) : true;
|
|
16600
17140
|
await protectFile(cwd);
|
|
16601
|
-
return deleted;
|
|
17141
|
+
return { deleted, secretsRemoved };
|
|
16602
17142
|
}
|
|
16603
17143
|
function connectionToFileInfo(connection) {
|
|
16604
17144
|
return {
|
|
@@ -16611,25 +17151,33 @@ function connectionToFileInfo(connection) {
|
|
|
16611
17151
|
};
|
|
16612
17152
|
}
|
|
16613
17153
|
function publicConnection(connection) {
|
|
16614
|
-
const {
|
|
16615
|
-
|
|
16616
|
-
|
|
16617
|
-
username: _username,
|
|
16618
|
-
accessKeyId: _accessKeyId,
|
|
16619
|
-
...withoutPassword
|
|
16620
|
-
} = connection;
|
|
16621
|
-
const {
|
|
16622
|
-
secretAccessKey: _secret,
|
|
16623
|
-
sessionToken: _token,
|
|
16624
|
-
...safe
|
|
16625
|
-
} = withoutPassword;
|
|
17154
|
+
const safe = { ...connection };
|
|
17155
|
+
for (const field of RUNTIME_ONLY_FIELDS)
|
|
17156
|
+
delete safe[field];
|
|
16626
17157
|
return safe;
|
|
16627
17158
|
}
|
|
16628
|
-
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;
|
|
17159
|
+
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, RUNTIME_ONLY_FIELDS, PRESERVED_SECRET_FIELDS, runtimeSecrets, keychainLookups, store;
|
|
16629
17160
|
var init_connections_store = __esm(() => {
|
|
16630
17161
|
init_json_store();
|
|
17162
|
+
init_credential_store();
|
|
16631
17163
|
MAX_JSON_BYTES = 256 * 1024;
|
|
17164
|
+
RUNTIME_ONLY_FIELDS = [
|
|
17165
|
+
"user",
|
|
17166
|
+
"username",
|
|
17167
|
+
"accessKeyId",
|
|
17168
|
+
"password",
|
|
17169
|
+
"secretAccessKey",
|
|
17170
|
+
"sessionToken",
|
|
17171
|
+
"apiToken"
|
|
17172
|
+
];
|
|
17173
|
+
PRESERVED_SECRET_FIELDS = [
|
|
17174
|
+
"password",
|
|
17175
|
+
"secretAccessKey",
|
|
17176
|
+
"sessionToken",
|
|
17177
|
+
"apiToken"
|
|
17178
|
+
];
|
|
16632
17179
|
runtimeSecrets = new Map;
|
|
17180
|
+
keychainLookups = new Map;
|
|
16633
17181
|
store = createJsonFileStore({
|
|
16634
17182
|
filePath: connectionsFilePath,
|
|
16635
17183
|
empty: emptyState,
|
|
@@ -16654,7 +17202,7 @@ function escapeLikeTerm(term) {
|
|
|
16654
17202
|
return term.replace(/=/g, "==").replace(/%/g, "=%").replace(/_/g, "=_");
|
|
16655
17203
|
}
|
|
16656
17204
|
async function searchTableAsync(adapter, table, columns, term, maxHits, includeNonText, pkColumns, signal) {
|
|
16657
|
-
const kind = adapter.kind;
|
|
17205
|
+
const kind = adapter.kind === "d1" ? "sqlite" : adapter.kind;
|
|
16658
17206
|
const searchCols = includeNonText ? columns.filter((c) => c.type.toUpperCase() !== "BLOB" && c.type.toUpperCase() !== "BYTEA") : columns.filter((c) => isTextLikeType(c.type));
|
|
16659
17207
|
if (searchCols.length === 0)
|
|
16660
17208
|
return [];
|
|
@@ -17023,6 +17571,9 @@ function handleError(prefix, action, err, signal) {
|
|
|
17023
17571
|
if (isDockerComposeServiceUnavailableError(err)) {
|
|
17024
17572
|
return textError(message, err.status);
|
|
17025
17573
|
}
|
|
17574
|
+
if (isD1HttpError(err)) {
|
|
17575
|
+
return textError(message, err.status);
|
|
17576
|
+
}
|
|
17026
17577
|
if (isFilesystemAccessError(err)) {
|
|
17027
17578
|
return textError(`failed to ${action}`, 500);
|
|
17028
17579
|
}
|
|
@@ -17030,6 +17581,7 @@ function handleError(prefix, action, err, signal) {
|
|
|
17030
17581
|
}
|
|
17031
17582
|
var DEFAULT_MAX_DOCKER_ADAPTER_CACHE = 8, DEFAULT_DOCKER_ADAPTER_IDLE_MS, MAX_LOGGED_ERROR_BODY = 500, logQueue;
|
|
17032
17583
|
var init_handle_shared = __esm(() => {
|
|
17584
|
+
init_d1();
|
|
17033
17585
|
init_docker_utils();
|
|
17034
17586
|
init_connections_store();
|
|
17035
17587
|
init_discovery();
|
|
@@ -19201,6 +19753,10 @@ function ensureInit() {
|
|
|
19201
19753
|
initialized = true;
|
|
19202
19754
|
}
|
|
19203
19755
|
async function getAdapter(r, _cwd, signal) {
|
|
19756
|
+
if (r.d1) {
|
|
19757
|
+
const connection = r.d1;
|
|
19758
|
+
return dockerAdapterCache.getOrOpenAsync(r.dbId, () => createD1Adapter(connection));
|
|
19759
|
+
}
|
|
19204
19760
|
if (r.saved) {
|
|
19205
19761
|
const cacheKey = r.schema ? `${r.dbId}\x00schema=${r.schema}` : r.dbId;
|
|
19206
19762
|
return dockerAdapterCache.getOrOpenAsync(cacheKey, () => createSqlCliAdapter({ ...r.saved, schema: r.schema }));
|
|
@@ -19256,6 +19812,9 @@ async function resolveDb(cwd, dbParam, omitDirNames, schemaParam, signal) {
|
|
|
19256
19812
|
const connection = await findDatastoreConnection(cwd, dbParam);
|
|
19257
19813
|
if (!connection)
|
|
19258
19814
|
return textError("datastore connection not found", 404);
|
|
19815
|
+
if (connection.kind === "d1") {
|
|
19816
|
+
return { resolved: dbParam, dbId: dbParam, d1: connection };
|
|
19817
|
+
}
|
|
19259
19818
|
if (connection.kind !== "postgresql" && connection.kind !== "mysql") {
|
|
19260
19819
|
return textError(`${connection.kind} must use its datastore routes`, 400);
|
|
19261
19820
|
}
|
|
@@ -20465,7 +21024,7 @@ async function handleDbUiGet(cwd) {
|
|
|
20465
21024
|
return jsonLoadResponse(() => loadDbUiState(cwd), "db UI", "failed to load db UI state");
|
|
20466
21025
|
}
|
|
20467
21026
|
function closeSavedConnection(id, kind) {
|
|
20468
|
-
if (kind === "postgresql" || kind === "mysql") {
|
|
21027
|
+
if (kind === "postgresql" || kind === "mysql" || kind === "d1") {
|
|
20469
21028
|
dockerAdapterCache.close(id);
|
|
20470
21029
|
dockerAdapterCache.closePrefix(`${id}\x00`);
|
|
20471
21030
|
return;
|
|
@@ -20497,9 +21056,9 @@ async function handleConnections(cwd, req) {
|
|
|
20497
21056
|
const existing = await findDatastoreConnection(cwd, id);
|
|
20498
21057
|
if (!existing)
|
|
20499
21058
|
return textError("datastore connection not found", 404);
|
|
20500
|
-
await deleteDatastoreConnection(cwd, id);
|
|
21059
|
+
const { secretsRemoved } = await deleteDatastoreConnection(cwd, id);
|
|
20501
21060
|
closeSavedConnection(id, existing.kind);
|
|
20502
|
-
return json({ ok: true });
|
|
21061
|
+
return json({ ok: true, secretsRemoved });
|
|
20503
21062
|
}
|
|
20504
21063
|
async function probeDatastoreConnection(connection, signal) {
|
|
20505
21064
|
if (connection.kind === "postgresql" || connection.kind === "mysql") {
|
|
@@ -20511,6 +21070,15 @@ async function probeDatastoreConnection(connection, signal) {
|
|
|
20511
21070
|
}
|
|
20512
21071
|
return;
|
|
20513
21072
|
}
|
|
21073
|
+
if (connection.kind === "d1") {
|
|
21074
|
+
const adapter2 = createD1Adapter(connection);
|
|
21075
|
+
try {
|
|
21076
|
+
await adapter2.getTablesAsync(signal);
|
|
21077
|
+
} finally {
|
|
21078
|
+
adapter2.close();
|
|
21079
|
+
}
|
|
21080
|
+
return;
|
|
21081
|
+
}
|
|
20514
21082
|
if (connection.kind === "redis") {
|
|
20515
21083
|
const adapter2 = createRedisAdapter(connection);
|
|
20516
21084
|
try {
|
|
@@ -20830,6 +21398,7 @@ async function handleDatabaseRoute(req, url, cwd, omitDirNames, sideEffectAllowe
|
|
|
20830
21398
|
var initialized = false, dockerAdapterCache, DEFAULT_DB_FILE_DISCOVERY_DEPS, MAX_SCHEMA_NAME_LEN2 = 1024, MAX_COLUMN_VALUE_PAIRS = 64, MAX_FILTER_COLUMN_LEN = 128, MAX_FILTER_VALUE_LEN = 4096, DB_QUERY_DEFAULT_MAX_ROWS = 1000, DB_QUERY_HARD_CAP_MAX_ROWS = 1e4, EXPORT_MAX_ROWS = 1e5, MAX_TABS_BODY_BYTES = 1e6, MAX_DB_UI_BODY_BYTES = 1e6, MAX_SNAPSHOT_TABLES = 512, MAX_SNAPSHOT_TABLE_NAME_LEN = 1024, searchJobs, snapshotJobs, DOCKER_CLOSE_REGISTRY, SNAPSHOT_DOCKER_SOURCE_REGISTRY;
|
|
20831
21399
|
var init_handle = __esm(() => {
|
|
20832
21400
|
init_state_store();
|
|
21401
|
+
init_d1();
|
|
20833
21402
|
init_docker();
|
|
20834
21403
|
init_docker_utils();
|
|
20835
21404
|
init_dynamodb();
|
|
@@ -20899,7 +21468,7 @@ var init_handle = __esm(() => {
|
|
|
20899
21468
|
});
|
|
20900
21469
|
|
|
20901
21470
|
// web-src/server/doctor.ts
|
|
20902
|
-
import { accessSync as accessSync2, constants as constants2, readFileSync as readFileSync6, statSync as
|
|
21471
|
+
import { accessSync as accessSync2, constants as constants2, readFileSync as readFileSync6, statSync as statSync6 } from "node:fs";
|
|
20903
21472
|
import { dirname as dirname5, join as join16, relative as relative6 } from "node:path";
|
|
20904
21473
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
20905
21474
|
function statusWorse(a, b) {
|
|
@@ -21092,7 +21661,7 @@ async function checkSqlite(cwd) {
|
|
|
21092
21661
|
async function trySnapshotDbOpen(cwd) {
|
|
21093
21662
|
const dbPath = join16(cwd, SNAPSHOT_DB_REL);
|
|
21094
21663
|
try {
|
|
21095
|
-
|
|
21664
|
+
statSync6(dbPath);
|
|
21096
21665
|
} catch {
|
|
21097
21666
|
return { kind: "skipped" };
|
|
21098
21667
|
}
|
|
@@ -21121,7 +21690,7 @@ function checkSnapshotStore(cwd) {
|
|
|
21121
21690
|
dirDetail = `${dir} (writable)`;
|
|
21122
21691
|
} catch {
|
|
21123
21692
|
try {
|
|
21124
|
-
|
|
21693
|
+
statSync6(dir);
|
|
21125
21694
|
dirStatus = "error";
|
|
21126
21695
|
dirDetail = `${dir} (not writable)`;
|
|
21127
21696
|
dirHint = "Snapshot creation will fail until the directory is writable. " + "Check filesystem permissions on the .code-viewer directory.";
|
|
@@ -21131,7 +21700,7 @@ function checkSnapshotStore(cwd) {
|
|
|
21131
21700
|
}
|
|
21132
21701
|
let dbDetail = dbPath;
|
|
21133
21702
|
try {
|
|
21134
|
-
const stat3 =
|
|
21703
|
+
const stat3 = statSync6(dbPath);
|
|
21135
21704
|
dbDetail = `${dbPath} (${stat3.size.toLocaleString()} bytes)`;
|
|
21136
21705
|
} catch {
|
|
21137
21706
|
dbDetail = `${dbPath} (not created yet — created on first snapshot)`;
|
|
@@ -24535,7 +25104,7 @@ import {
|
|
|
24535
25104
|
readFileSync as readFileSync8,
|
|
24536
25105
|
realpathSync as realpathSync7,
|
|
24537
25106
|
renameSync,
|
|
24538
|
-
statSync as
|
|
25107
|
+
statSync as statSync7,
|
|
24539
25108
|
unlinkSync as unlinkSync2,
|
|
24540
25109
|
watch,
|
|
24541
25110
|
writeFileSync as writeFileSync2
|
|
@@ -25055,7 +25624,7 @@ function worktreeFileMetadata(path, knownSize) {
|
|
|
25055
25624
|
if (!full)
|
|
25056
25625
|
return {};
|
|
25057
25626
|
try {
|
|
25058
|
-
const stat3 =
|
|
25627
|
+
const stat3 = statSync7(full);
|
|
25059
25628
|
return {
|
|
25060
25629
|
size: knownSize ?? stat3.size,
|
|
25061
25630
|
created_at: isoDate(stat3.birthtimeMs),
|
|
@@ -25080,7 +25649,7 @@ async function directoryMetadata(target, path) {
|
|
|
25080
25649
|
if (!full)
|
|
25081
25650
|
return {};
|
|
25082
25651
|
try {
|
|
25083
|
-
const stat3 =
|
|
25652
|
+
const stat3 = statSync7(full);
|
|
25084
25653
|
return {
|
|
25085
25654
|
created_at: isoDate(stat3.birthtimeMs),
|
|
25086
25655
|
updated_at: isoDate(stat3.mtimeMs)
|
|
@@ -25357,7 +25926,7 @@ async function handleLog(url) {
|
|
|
25357
25926
|
}
|
|
25358
25927
|
function blamePathKey(p) {
|
|
25359
25928
|
try {
|
|
25360
|
-
const st =
|
|
25929
|
+
const st = statSync7(join20(cwd, p));
|
|
25361
25930
|
return `${st.mtimeMs}:${st.size}`;
|
|
25362
25931
|
} catch {
|
|
25363
25932
|
return "missing";
|
|
@@ -25512,7 +26081,7 @@ async function handleFileDiff(url) {
|
|
|
25512
26081
|
}
|
|
25513
26082
|
function worktreeLineIndexSignature(full) {
|
|
25514
26083
|
try {
|
|
25515
|
-
const stat3 =
|
|
26084
|
+
const stat3 = statSync7(full);
|
|
25516
26085
|
return `size:${stat3.size}|mtime:${stat3.mtimeMs}|ctime:${stat3.ctimeMs}|ino:${stat3.ino || 0}`;
|
|
25517
26086
|
} catch {
|
|
25518
26087
|
return null;
|
|
@@ -25528,7 +26097,7 @@ async function getWorktreeLineIndex(full) {
|
|
|
25528
26097
|
lineIndexCache.set(full, cached);
|
|
25529
26098
|
return cached.index;
|
|
25530
26099
|
}
|
|
25531
|
-
const stat3 =
|
|
26100
|
+
const stat3 = statSync7(full);
|
|
25532
26101
|
if (stat3.size > LINE_INDEX_MAX_FILE_BYTES)
|
|
25533
26102
|
return null;
|
|
25534
26103
|
const index = await buildLineOffsetIndexFromStream(fileReadableStream(full), stat3.size);
|
|
@@ -25660,6 +26229,8 @@ async function handleFileRange(url) {
|
|
|
25660
26229
|
const full = safeWorktreePath2(path);
|
|
25661
26230
|
if (!full)
|
|
25662
26231
|
return text("no file", 404);
|
|
26232
|
+
if (await rawFileSize(path, ref) == null)
|
|
26233
|
+
return text("no file", 404);
|
|
25663
26234
|
const responseGeneration = generation;
|
|
25664
26235
|
const result = await collectIndexedWorktreeLineRange(full, start, end);
|
|
25665
26236
|
const body = {
|
|
@@ -25808,7 +26379,8 @@ async function rawFileSize(path, ref) {
|
|
|
25808
26379
|
if (!full)
|
|
25809
26380
|
return null;
|
|
25810
26381
|
try {
|
|
25811
|
-
|
|
26382
|
+
const stats = statSync7(full);
|
|
26383
|
+
return stats.isFile() ? stats.size : null;
|
|
25812
26384
|
} catch {
|
|
25813
26385
|
return null;
|
|
25814
26386
|
}
|
|
@@ -25869,7 +26441,7 @@ async function handleUploadFiles(req) {
|
|
|
25869
26441
|
const realDir = safeOpenWorktreePath(dir);
|
|
25870
26442
|
if (!realDir)
|
|
25871
26443
|
return text("not found", 404);
|
|
25872
|
-
const stats =
|
|
26444
|
+
const stats = statSync7(realDir);
|
|
25873
26445
|
if (!stats.isDirectory())
|
|
25874
26446
|
return text("not a directory", 400);
|
|
25875
26447
|
const files = form.getAll("files").filter((item) => item instanceof File);
|
|
@@ -26118,7 +26690,7 @@ async function handleOpenPath(req) {
|
|
|
26118
26690
|
const target = safeOpenWorktreePath(targetPath);
|
|
26119
26691
|
if (!target)
|
|
26120
26692
|
return text("not found", 404);
|
|
26121
|
-
const stats =
|
|
26693
|
+
const stats = statSync7(target);
|
|
26122
26694
|
if (!stats.isDirectory())
|
|
26123
26695
|
return text("not a directory", 400);
|
|
26124
26696
|
openOsPath(target);
|
|
@@ -26156,7 +26728,7 @@ async function handleTrashPath(req) {
|
|
|
26156
26728
|
return text("not found", 404);
|
|
26157
26729
|
let changedPaths;
|
|
26158
26730
|
try {
|
|
26159
|
-
const stats =
|
|
26731
|
+
const stats = statSync7(originalFullPath);
|
|
26160
26732
|
if (!stats.isDirectory())
|
|
26161
26733
|
changedPaths = [path];
|
|
26162
26734
|
} catch {}
|
|
@@ -26209,7 +26781,7 @@ async function handleCreateDirectory(req) {
|
|
|
26209
26781
|
const parent = safeOpenWorktreePath(dir);
|
|
26210
26782
|
if (!parent)
|
|
26211
26783
|
return text("not found", 404);
|
|
26212
|
-
const stats =
|
|
26784
|
+
const stats = statSync7(parent);
|
|
26213
26785
|
if (!stats.isDirectory())
|
|
26214
26786
|
return text("not a directory", 400);
|
|
26215
26787
|
const targetPath = dir ? `${dir}/${name}` : name;
|
|
@@ -26259,7 +26831,7 @@ async function handleRestoreTrash(req) {
|
|
|
26259
26831
|
return text(restored.error || "undo failed", 409);
|
|
26260
26832
|
let changedPaths;
|
|
26261
26833
|
try {
|
|
26262
|
-
const stats =
|
|
26834
|
+
const stats = statSync7(worktreePath(originalPath));
|
|
26263
26835
|
if (!stats.isDirectory())
|
|
26264
26836
|
changedPaths = [originalPath];
|
|
26265
26837
|
} catch {}
|
|
@@ -27025,6 +27597,12 @@ data: ${watchLimitReached}
|
|
|
27025
27597
|
root: cwd,
|
|
27026
27598
|
started_at: new Date().toISOString()
|
|
27027
27599
|
});
|
|
27600
|
+
process.on("uncaughtException", (error) => {
|
|
27601
|
+
console.error("[code-viewer] uncaught exception (server kept running):", error);
|
|
27602
|
+
});
|
|
27603
|
+
process.on("unhandledRejection", (reason) => {
|
|
27604
|
+
console.error("[code-viewer] unhandled rejection (server kept running):", reason);
|
|
27605
|
+
});
|
|
27028
27606
|
process.on("exit", () => {
|
|
27029
27607
|
removeServerRegistry(cwd, process.pid);
|
|
27030
27608
|
closeSseClients();
|