@youtyan/code-viewer 0.6.0 → 0.6.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 +14 -11
- package/dist/code-viewer.js +411 -65
- package/package.json +1 -1
- package/skills/code-viewer-snapshot/SKILL.md +6 -0
- package/web/app.js +1104 -564
- package/web/index.html +7 -3
- package/web/style.css +153 -5
package/dist/code-viewer.js
CHANGED
|
@@ -3765,6 +3765,93 @@ Default (non --json) output:
|
|
|
3765
3765
|
]);
|
|
3766
3766
|
});
|
|
3767
3767
|
|
|
3768
|
+
// web-src/core/routes.ts
|
|
3769
|
+
function assertNever(value) {
|
|
3770
|
+
throw new Error(`unhandled route: ${JSON.stringify(value)}`);
|
|
3771
|
+
}
|
|
3772
|
+
function formatLineTarget(line) {
|
|
3773
|
+
return typeof line === "number" ? String(line) : `${line.start}-${line.end}`;
|
|
3774
|
+
}
|
|
3775
|
+
function buildRoute(route) {
|
|
3776
|
+
switch (route.screen) {
|
|
3777
|
+
case "repo": {
|
|
3778
|
+
const params = new URLSearchParams;
|
|
3779
|
+
if (route.ref && route.ref !== "worktree")
|
|
3780
|
+
params.set("ref", route.ref);
|
|
3781
|
+
if (route.path)
|
|
3782
|
+
params.set("path", route.path);
|
|
3783
|
+
const qs = params.toString();
|
|
3784
|
+
return `/${qs ? `?${qs}` : ""}`;
|
|
3785
|
+
}
|
|
3786
|
+
case "file":
|
|
3787
|
+
if (route.view === "blob") {
|
|
3788
|
+
return "/file?path=" + encodeURIComponent(route.path) + "&target=" + encodeURIComponent(route.ref || "worktree") + "&view=blob" + (route.preview ? "&preview=1" : "") + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "") + (route.virtual === "off" ? "&virtual=off" : "");
|
|
3789
|
+
}
|
|
3790
|
+
if (route.view === "blame") {
|
|
3791
|
+
const ref = route.ref || "worktree";
|
|
3792
|
+
return "/file?path=" + encodeURIComponent(route.path) + "&target=" + encodeURIComponent(ref) + "&view=blame" + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "");
|
|
3793
|
+
}
|
|
3794
|
+
if (route.view === "history") {
|
|
3795
|
+
return "/file?path=" + encodeURIComponent(route.path) + "&target=" + encodeURIComponent(route.ref || "worktree") + "&view=history" + (route.commit ? `&commit=${encodeURIComponent(route.commit)}` : "") + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "");
|
|
3796
|
+
}
|
|
3797
|
+
return "/file?path=" + encodeURIComponent(route.path) + "&ref=" + encodeURIComponent(route.ref || "worktree") + "&from=" + encodeURIComponent(route.range.from || "") + "&to=" + encodeURIComponent(route.range.to || "worktree") + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "") + (route.virtual === "off" ? "&virtual=off" : "");
|
|
3798
|
+
case "diff":
|
|
3799
|
+
return "/todif?from=" + encodeURIComponent(route.range.from || "") + "&to=" + encodeURIComponent(route.range.to || "worktree") + (route.path ? `&path=${encodeURIComponent(route.path)}` : "") + (route.line ? `&line=${encodeURIComponent(formatLineTarget(route.line))}` : "");
|
|
3800
|
+
case "help": {
|
|
3801
|
+
const params = new URLSearchParams;
|
|
3802
|
+
if (route.lang && route.lang !== "en")
|
|
3803
|
+
params.set("lang", route.lang);
|
|
3804
|
+
if (route.section && route.section !== "overview")
|
|
3805
|
+
params.set("section", route.section);
|
|
3806
|
+
const qs = params.toString();
|
|
3807
|
+
return `/help${qs ? `?${qs}` : ""}`;
|
|
3808
|
+
}
|
|
3809
|
+
case "history": {
|
|
3810
|
+
const params = new URLSearchParams;
|
|
3811
|
+
if (route.ref && route.ref !== "HEAD")
|
|
3812
|
+
params.set("ref", route.ref);
|
|
3813
|
+
if (route.commit)
|
|
3814
|
+
params.set("commit", route.commit);
|
|
3815
|
+
const qs = params.toString();
|
|
3816
|
+
return `/history${qs ? `?${qs}` : ""}`;
|
|
3817
|
+
}
|
|
3818
|
+
case "database": {
|
|
3819
|
+
const params = new URLSearchParams;
|
|
3820
|
+
if (route.db)
|
|
3821
|
+
params.set("db", route.db);
|
|
3822
|
+
if (route.schema)
|
|
3823
|
+
params.set("schema", route.schema);
|
|
3824
|
+
if (route.table)
|
|
3825
|
+
params.set("table", route.table);
|
|
3826
|
+
if (route.tab)
|
|
3827
|
+
params.set("tab", route.tab);
|
|
3828
|
+
if (route.diffBefore)
|
|
3829
|
+
params.set("diffBefore", route.diffBefore);
|
|
3830
|
+
if (route.diffAfter)
|
|
3831
|
+
params.set("diffAfter", route.diffAfter);
|
|
3832
|
+
const qs = params.toString();
|
|
3833
|
+
return `/database${qs ? `?${qs}` : ""}`;
|
|
3834
|
+
}
|
|
3835
|
+
case "unknown":
|
|
3836
|
+
return "/todif?from=" + encodeURIComponent(route.range.from || "") + "&to=" + encodeURIComponent(route.range.to || "worktree");
|
|
3837
|
+
default:
|
|
3838
|
+
return assertNever(route);
|
|
3839
|
+
}
|
|
3840
|
+
}
|
|
3841
|
+
var SPA_PATHS, APP_ENTRY_PATHS;
|
|
3842
|
+
var init_routes = __esm(() => {
|
|
3843
|
+
SPA_PATHS = [
|
|
3844
|
+
"/todif",
|
|
3845
|
+
"/todiff",
|
|
3846
|
+
"/file",
|
|
3847
|
+
"/help",
|
|
3848
|
+
"/history",
|
|
3849
|
+
"/database",
|
|
3850
|
+
"/doctor"
|
|
3851
|
+
];
|
|
3852
|
+
APP_ENTRY_PATHS = ["/", "/index.html"];
|
|
3853
|
+
});
|
|
3854
|
+
|
|
3768
3855
|
// web-src/server/query-cli.ts
|
|
3769
3856
|
var exports_query_cli = {};
|
|
3770
3857
|
__export(exports_query_cli, {
|
|
@@ -5149,6 +5236,18 @@ function buildSnapshotPollCommand(serverUrl, db, schema) {
|
|
|
5149
5236
|
const schemaArg = schema ? ` --schema ${shellSingleQuote(schema)}` : "";
|
|
5150
5237
|
return `${cli} snapshot list --db ${shellSingleQuote(db)}${schemaArg} --json`;
|
|
5151
5238
|
}
|
|
5239
|
+
function buildSnapshotDiffUrl(serverUrl, dbId, schema, beforeId, afterId) {
|
|
5240
|
+
const path = buildRoute({
|
|
5241
|
+
screen: "database",
|
|
5242
|
+
db: dbId,
|
|
5243
|
+
schema,
|
|
5244
|
+
tab: "snapshot",
|
|
5245
|
+
diffBefore: beforeId,
|
|
5246
|
+
diffAfter: afterId,
|
|
5247
|
+
range: { from: "", to: "" }
|
|
5248
|
+
});
|
|
5249
|
+
return new URL(path, serverUrl).toString();
|
|
5250
|
+
}
|
|
5152
5251
|
function buildDiffRowsCommand(serverUrl, before, after, table) {
|
|
5153
5252
|
const cli = `code-viewer query --server ${shellSingleQuote(serverUrl)}`;
|
|
5154
5253
|
return `${cli} diff rows --before ${shellSingleQuote(before)} ` + `--after ${shellSingleQuote(after)} --table ${shellSingleQuote(table)} --json`;
|
|
@@ -5301,6 +5400,7 @@ async function runDiffTables(serverUrl, command) {
|
|
|
5301
5400
|
const body = await requestJson(serverUrl, `/_db/snapshot/diff/tables${qs}`, "GET", undefined, "diff tables");
|
|
5302
5401
|
const enriched = {
|
|
5303
5402
|
...body,
|
|
5403
|
+
diffUrl: buildSnapshotDiffUrl(serverUrl, body.dbId, body.schema, body.beforeId, body.afterId),
|
|
5304
5404
|
tables: body.tables.map((t) => ({
|
|
5305
5405
|
...t,
|
|
5306
5406
|
diffRowsCommand: buildDiffRowsCommand(serverUrl, body.beforeId, body.afterId, t.tableName)
|
|
@@ -5312,8 +5412,10 @@ async function runDiffTables(serverUrl, command) {
|
|
|
5312
5412
|
}
|
|
5313
5413
|
if (!enriched.tables.length) {
|
|
5314
5414
|
console.log("no tables in diff");
|
|
5415
|
+
console.log(`# view in browser: ${enriched.diffUrl}`);
|
|
5315
5416
|
return;
|
|
5316
5417
|
}
|
|
5418
|
+
console.log(`# view in browser: ${enriched.diffUrl}`);
|
|
5317
5419
|
for (const t of enriched.tables) {
|
|
5318
5420
|
const cov = t.coverage === "both" ? "" : ` (${t.coverage})`;
|
|
5319
5421
|
console.log(`${t.tableName} +${t.insertedCount} ~${t.updatedCount} -${t.deletedCount} =${t.unchangedCount}${cov}`);
|
|
@@ -5570,10 +5672,13 @@ no separate stored diff entity, so you always pass both snapshot ids.
|
|
|
5570
5672
|
code-viewer query snapshot list --db app.db --json
|
|
5571
5673
|
|
|
5572
5674
|
5. View the diff (per-table summary, then per-row detail). diff tables
|
|
5573
|
-
prints
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
the
|
|
5675
|
+
prints a "# view in browser: <url>" hint up top (opens the same diff in
|
|
5676
|
+
the human's browser, Database > Snapshot tab), then each per-table
|
|
5677
|
+
summary line plus a paste-safe "# diff rows: ..." hint right below it.
|
|
5678
|
+
--json adds the same browser link as a diffUrl field and a
|
|
5679
|
+
diffRowsCommand field on each tables[] element, so you can hand the
|
|
5680
|
+
human a direct link or drill into row detail without rebuilding the
|
|
5681
|
+
command yourself:
|
|
5577
5682
|
code-viewer query diff tables --before snap-abc123 --after snap-def456 --json
|
|
5578
5683
|
code-viewer query diff rows --before snap-abc123 --after snap-def456 \\
|
|
5579
5684
|
--table users --json
|
|
@@ -5760,10 +5865,13 @@ object bytes (text-shaped objects are previewable via \`s3 text\`).
|
|
|
5760
5865
|
- diff tables: human-readable lines (default) plus a paste-safe
|
|
5761
5866
|
"# diff rows: code-viewer query --server '<url>' diff rows --before '<id>'
|
|
5762
5867
|
--after '<id>' --table '<table>' --json" comment line right below each table,
|
|
5763
|
-
so AI/human can drill into row detail without rebuilding the command.
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5868
|
+
so AI/human can drill into row detail without rebuilding the command. A
|
|
5869
|
+
"# view in browser: <url>" hint is printed once up top — opens the same
|
|
5870
|
+
before/after comparison in the human's browser (Database > Snapshot tab).
|
|
5871
|
+
With --json the full /_db/snapshot/diff/tables payload is emitted, the
|
|
5872
|
+
top-level diffUrl field carries the same browser link, and each tables[]
|
|
5873
|
+
element gains an additive diffRowsCommand field with the same literal.
|
|
5874
|
+
server URL / snapshot ids / table names are POSIX single-quoted.
|
|
5767
5875
|
- snapshot create: prints "snapshot started" immediately with the snapshotId.
|
|
5768
5876
|
The no-wait output also includes a paste-safe poll command that pins
|
|
5769
5877
|
--server '<url>' and single-quotes db/schema so AI/human paste does not
|
|
@@ -5843,6 +5951,7 @@ object bytes (text-shaped objects are previewable via \`s3 text\`).
|
|
|
5843
5951
|
server only scans text-like columns (faster, cheaper).
|
|
5844
5952
|
`, VALUE_FLAGS2, BOOL_FLAGS2, DEFAULT_SEARCH_TIMEOUT_SEC = 60, DEFAULT_SNAPSHOT_WAIT_TIMEOUT_SEC = 120, REDIS_ACTION_ALLOWLIST, NON_REDIS_SUBCOMMAND_ALLOWLIST, ES_ACTION_ALLOWLIST, S3_ACTION_ALLOWLIST;
|
|
5845
5953
|
var init_query_cli = __esm(() => {
|
|
5954
|
+
init_routes();
|
|
5846
5955
|
init_cli_helpers();
|
|
5847
5956
|
init_cli_helpers();
|
|
5848
5957
|
VALUE_FLAGS2 = new Set([
|
|
@@ -10039,6 +10148,11 @@ var init_source_meta = __esm(() => {
|
|
|
10039
10148
|
"conf",
|
|
10040
10149
|
"env",
|
|
10041
10150
|
"properties",
|
|
10151
|
+
"rules",
|
|
10152
|
+
"rule",
|
|
10153
|
+
"prompt",
|
|
10154
|
+
"prompts",
|
|
10155
|
+
"instructions",
|
|
10042
10156
|
"gitignore",
|
|
10043
10157
|
"dockerignore",
|
|
10044
10158
|
"editorconfig",
|
|
@@ -11775,6 +11889,15 @@ function parseDockerDbId(dbId) {
|
|
|
11775
11889
|
return null;
|
|
11776
11890
|
return { serviceName: rest, relDir: "", database };
|
|
11777
11891
|
}
|
|
11892
|
+
function canonicalizeDockerDbId(dbId) {
|
|
11893
|
+
const parsed = parseDockerDbId(dbId);
|
|
11894
|
+
if (!parsed)
|
|
11895
|
+
return null;
|
|
11896
|
+
const database = parsed.database ? `:${parsed.database}` : "";
|
|
11897
|
+
if (!parsed.relDir)
|
|
11898
|
+
return `docker:${parsed.serviceName}${database}`;
|
|
11899
|
+
return `docker:${parsed.serviceName}@${encodeURIComponent(parsed.relDir)}${database}`;
|
|
11900
|
+
}
|
|
11778
11901
|
async function findDockerServiceByDbIdAsync(cwd, dbId, kind, omitDirNames, signal) {
|
|
11779
11902
|
const parsed = parseDockerDbId(dbId);
|
|
11780
11903
|
if (!parsed)
|
|
@@ -12194,6 +12317,9 @@ function sanitizeSettings(raw) {
|
|
|
12194
12317
|
const annotationPanelOpen = optionalBoolean(raw.annotationPanelOpen);
|
|
12195
12318
|
if (annotationPanelOpen !== undefined)
|
|
12196
12319
|
out.annotationPanelOpen = annotationPanelOpen;
|
|
12320
|
+
const annotationPanelWidth = optionalNumber(raw.annotationPanelWidth, 260, 720);
|
|
12321
|
+
if (annotationPanelWidth !== undefined)
|
|
12322
|
+
out.annotationPanelWidth = annotationPanelWidth;
|
|
12197
12323
|
const annotationFollow = optionalBoolean(raw.annotationFollow);
|
|
12198
12324
|
if (annotationFollow !== undefined)
|
|
12199
12325
|
out.annotationFollow = annotationFollow;
|
|
@@ -14175,6 +14301,9 @@ async function getStoreDb(cwd) {
|
|
|
14175
14301
|
try {
|
|
14176
14302
|
storeDb.exec("ALTER TABLE snapshots ADD COLUMN schema_name TEXT");
|
|
14177
14303
|
} catch {}
|
|
14304
|
+
try {
|
|
14305
|
+
storeDb.exec("ALTER TABLE snapshot_tables ADD COLUMN revision_id TEXT");
|
|
14306
|
+
} catch {}
|
|
14178
14307
|
return storeDb;
|
|
14179
14308
|
}
|
|
14180
14309
|
function makeId2(prefix) {
|
|
@@ -14183,32 +14312,163 @@ function makeId2(prefix) {
|
|
|
14183
14312
|
function hashPayload(payloadJson) {
|
|
14184
14313
|
return createHash5("sha256").update(payloadJson).digest("hex");
|
|
14185
14314
|
}
|
|
14315
|
+
function hashLengthPrefixed(hasher, value) {
|
|
14316
|
+
hasher.update(`${Buffer.byteLength(value, "utf8")}:`);
|
|
14317
|
+
hasher.update(value);
|
|
14318
|
+
hasher.update(`
|
|
14319
|
+
`);
|
|
14320
|
+
}
|
|
14321
|
+
function deleteOrphanPayloads(db) {
|
|
14322
|
+
db.prepare(`DELETE FROM snapshot_payloads
|
|
14323
|
+
WHERE NOT EXISTS (
|
|
14324
|
+
SELECT 1 FROM snapshot_rows
|
|
14325
|
+
WHERE snapshot_rows.payload_hash = snapshot_payloads.payload_hash
|
|
14326
|
+
)
|
|
14327
|
+
AND NOT EXISTS (
|
|
14328
|
+
SELECT 1 FROM snapshot_table_revision_rows
|
|
14329
|
+
WHERE snapshot_table_revision_rows.payload_hash = snapshot_payloads.payload_hash
|
|
14330
|
+
)`).run();
|
|
14331
|
+
}
|
|
14332
|
+
function dockerDbIdFilterValues(dbId) {
|
|
14333
|
+
const values = [dbId];
|
|
14334
|
+
const canonical = canonicalizeDockerDbId(dbId);
|
|
14335
|
+
if (canonical)
|
|
14336
|
+
values.push(canonical);
|
|
14337
|
+
const parsed = parseDockerDbId(dbId);
|
|
14338
|
+
if (parsed?.relDir) {
|
|
14339
|
+
const database = parsed.database ? `:${parsed.database}` : "";
|
|
14340
|
+
values.push(`docker:${parsed.serviceName}@${parsed.relDir}${database}`);
|
|
14341
|
+
}
|
|
14342
|
+
return [...new Set(values)];
|
|
14343
|
+
}
|
|
14344
|
+
function getSnapshotScopeRow(db, snapshotId) {
|
|
14345
|
+
const row = db.prepare("SELECT db_id, COALESCE(schema_name, 'public') AS schema_name FROM snapshots WHERE id = ?").get(snapshotId);
|
|
14346
|
+
if (!row)
|
|
14347
|
+
throw new Error(`snapshot not found: ${snapshotId}`);
|
|
14348
|
+
return { dbId: row.db_id, schema: row.schema_name };
|
|
14349
|
+
}
|
|
14186
14350
|
async function createSnapshot(cwd, dbId, kind, tables, note, schema) {
|
|
14187
14351
|
const db = await getStoreDb(cwd);
|
|
14188
14352
|
const id = makeId2("snap");
|
|
14189
|
-
|
|
14353
|
+
const storedDbId = canonicalizeDockerDbId(dbId) ?? dbId;
|
|
14354
|
+
db.prepare("INSERT INTO snapshots (id, db_id, schema_name, kind, note, created_at, status) VALUES (?, ?, ?, ?, ?, ?, ?)").run(id, storedDbId, schema ?? null, kind, note, new Date().toISOString(), "running");
|
|
14190
14355
|
for (const t of tables) {
|
|
14191
14356
|
db.prepare("INSERT INTO snapshot_tables (snapshot_id, table_name) VALUES (?, ?)").run(id, t);
|
|
14192
14357
|
}
|
|
14193
14358
|
return id;
|
|
14194
14359
|
}
|
|
14195
|
-
async function
|
|
14360
|
+
async function beginSnapshotTableRevision(cwd, snapshotId, tableName, pkColumns) {
|
|
14196
14361
|
const db = await getStoreDb(cwd);
|
|
14197
|
-
const
|
|
14198
|
-
const
|
|
14362
|
+
const id = makeId2("rev");
|
|
14363
|
+
const scope = getSnapshotScopeRow(db, snapshotId);
|
|
14364
|
+
db.exec("BEGIN");
|
|
14365
|
+
try {
|
|
14366
|
+
db.prepare("INSERT OR IGNORE INTO snapshot_tables (snapshot_id, table_name) VALUES (?, ?)").run(snapshotId, tableName);
|
|
14367
|
+
db.prepare(`INSERT INTO snapshot_table_revisions
|
|
14368
|
+
(id, source_snapshot_id, db_id, schema_name, table_name, row_count, table_hash, hash_version, pk_columns_json, status, created_at)
|
|
14369
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, snapshotId, scope.dbId, scope.schema, tableName, 0, "", SNAPSHOT_TABLE_HASH_VERSION, JSON.stringify(pkColumns), "building", new Date().toISOString());
|
|
14370
|
+
db.exec("COMMIT");
|
|
14371
|
+
} catch (err) {
|
|
14372
|
+
try {
|
|
14373
|
+
db.exec("ROLLBACK");
|
|
14374
|
+
} catch {}
|
|
14375
|
+
throw err;
|
|
14376
|
+
}
|
|
14377
|
+
return id;
|
|
14378
|
+
}
|
|
14379
|
+
async function addSnapshotTableRows(cwd, revisionId, rows) {
|
|
14380
|
+
if (rows.length === 0)
|
|
14381
|
+
return;
|
|
14382
|
+
const db = await getStoreDb(cwd);
|
|
14383
|
+
const insertRow = db.prepare("INSERT OR IGNORE INTO snapshot_table_revision_rows (revision_id, row_key_hash, row_key_json, row_hash, payload_hash) VALUES (?, ?, ?, ?, ?)");
|
|
14199
14384
|
const insertPayload = db.prepare("INSERT OR IGNORE INTO snapshot_payloads (payload_hash, payload_json) VALUES (?, ?)");
|
|
14200
|
-
const updateTable = db.prepare("UPDATE snapshot_tables SET row_count = ?, table_hash = ?, pk_columns_json = ? WHERE snapshot_id = ? AND table_name = ?");
|
|
14201
14385
|
db.exec("BEGIN");
|
|
14202
14386
|
try {
|
|
14203
14387
|
for (const row of rows) {
|
|
14204
14388
|
const rowKeyHash = createHash5("sha256").update(row.rowKeyJson).digest("hex");
|
|
14205
14389
|
const payloadHash = hashPayload(row.payloadJson);
|
|
14206
|
-
|
|
14207
|
-
insertRow.run(snapshotId, tableName, rowKeyHash, row.rowKeyJson, row.rowHash, payloadHash);
|
|
14390
|
+
insertRow.run(revisionId, rowKeyHash, row.rowKeyJson, row.rowHash, payloadHash);
|
|
14208
14391
|
insertPayload.run(payloadHash, row.payloadJson);
|
|
14209
14392
|
}
|
|
14210
|
-
|
|
14211
|
-
|
|
14393
|
+
db.exec("COMMIT");
|
|
14394
|
+
} catch (err) {
|
|
14395
|
+
try {
|
|
14396
|
+
db.exec("ROLLBACK");
|
|
14397
|
+
} catch {}
|
|
14398
|
+
throw err;
|
|
14399
|
+
}
|
|
14400
|
+
}
|
|
14401
|
+
function computeRevisionTableHash(db, revisionId) {
|
|
14402
|
+
const hasher = createHash5("sha256");
|
|
14403
|
+
hashLengthPrefixed(hasher, `snapshot-table-v${SNAPSHOT_TABLE_HASH_VERSION}`);
|
|
14404
|
+
let rowCount = 0;
|
|
14405
|
+
let last;
|
|
14406
|
+
for (;; ) {
|
|
14407
|
+
const rows = last ? db.prepare(`SELECT row_key_hash, row_key_json, row_hash, payload_hash
|
|
14408
|
+
FROM snapshot_table_revision_rows
|
|
14409
|
+
WHERE revision_id = ?
|
|
14410
|
+
AND (row_key_hash > ? OR (row_key_hash = ? AND row_key_json > ?))
|
|
14411
|
+
ORDER BY row_key_hash, row_key_json
|
|
14412
|
+
LIMIT ?`).all(revisionId, last.row_key_hash, last.row_key_hash, last.row_key_json, SNAPSHOT_REVISION_HASH_BATCH_SIZE) : db.prepare(`SELECT row_key_hash, row_key_json, row_hash, payload_hash
|
|
14413
|
+
FROM snapshot_table_revision_rows
|
|
14414
|
+
WHERE revision_id = ?
|
|
14415
|
+
ORDER BY row_key_hash, row_key_json
|
|
14416
|
+
LIMIT ?`).all(revisionId, SNAPSHOT_REVISION_HASH_BATCH_SIZE);
|
|
14417
|
+
if (rows.length === 0)
|
|
14418
|
+
break;
|
|
14419
|
+
for (const row of rows) {
|
|
14420
|
+
hashLengthPrefixed(hasher, row.row_key_hash);
|
|
14421
|
+
hashLengthPrefixed(hasher, row.row_key_json);
|
|
14422
|
+
hashLengthPrefixed(hasher, row.row_hash);
|
|
14423
|
+
hashLengthPrefixed(hasher, row.payload_hash);
|
|
14424
|
+
rowCount++;
|
|
14425
|
+
last = {
|
|
14426
|
+
row_key_hash: row.row_key_hash,
|
|
14427
|
+
row_key_json: row.row_key_json
|
|
14428
|
+
};
|
|
14429
|
+
}
|
|
14430
|
+
if (rows.length < SNAPSHOT_REVISION_HASH_BATCH_SIZE)
|
|
14431
|
+
break;
|
|
14432
|
+
}
|
|
14433
|
+
return {
|
|
14434
|
+
rowCount,
|
|
14435
|
+
tableHash: `${SNAPSHOT_TABLE_HASH_PREFIX}${hasher.digest("hex")}`
|
|
14436
|
+
};
|
|
14437
|
+
}
|
|
14438
|
+
async function finalizeSnapshotTableRevision(cwd, snapshotId, tableName, revisionId) {
|
|
14439
|
+
const db = await getStoreDb(cwd);
|
|
14440
|
+
const revision = db.prepare(`SELECT db_id, schema_name, table_name, pk_columns_json
|
|
14441
|
+
FROM snapshot_table_revisions
|
|
14442
|
+
WHERE id = ?`).get(revisionId);
|
|
14443
|
+
if (!revision)
|
|
14444
|
+
throw new Error(`snapshot table revision not found: ${revisionId}`);
|
|
14445
|
+
const { rowCount, tableHash } = computeRevisionTableHash(db, revisionId);
|
|
14446
|
+
const existing = db.prepare(`SELECT id
|
|
14447
|
+
FROM snapshot_table_revisions
|
|
14448
|
+
WHERE id != ?
|
|
14449
|
+
AND db_id = ?
|
|
14450
|
+
AND schema_name = ?
|
|
14451
|
+
AND table_name = ?
|
|
14452
|
+
AND hash_version = ?
|
|
14453
|
+
AND table_hash = ?
|
|
14454
|
+
AND row_count = ?
|
|
14455
|
+
AND pk_columns_json = ?
|
|
14456
|
+
AND status = 'done'
|
|
14457
|
+
ORDER BY created_at ASC
|
|
14458
|
+
LIMIT 1`).get(revisionId, revision.db_id, revision.schema_name, revision.table_name, SNAPSHOT_TABLE_HASH_VERSION, tableHash, rowCount, revision.pk_columns_json);
|
|
14459
|
+
const adoptedRevisionId = existing?.id ?? revisionId;
|
|
14460
|
+
db.exec("BEGIN");
|
|
14461
|
+
try {
|
|
14462
|
+
if (existing) {
|
|
14463
|
+
db.prepare("DELETE FROM snapshot_table_revisions WHERE id = ?").run(revisionId);
|
|
14464
|
+
} else {
|
|
14465
|
+
db.prepare(`UPDATE snapshot_table_revisions
|
|
14466
|
+
SET row_count = ?, table_hash = ?, status = 'done', source_snapshot_id = NULL
|
|
14467
|
+
WHERE id = ?`).run(rowCount, tableHash, revisionId);
|
|
14468
|
+
}
|
|
14469
|
+
db.prepare(`UPDATE snapshot_tables
|
|
14470
|
+
SET row_count = ?, table_hash = ?, pk_columns_json = ?, revision_id = ?
|
|
14471
|
+
WHERE snapshot_id = ? AND table_name = ?`).run(rowCount, tableHash, revision.pk_columns_json, adoptedRevisionId, snapshotId, tableName);
|
|
14212
14472
|
db.exec("COMMIT");
|
|
14213
14473
|
} catch (err) {
|
|
14214
14474
|
try {
|
|
@@ -14220,7 +14480,18 @@ async function addSnapshotTableData(cwd, snapshotId, tableName, pkColumns, rows)
|
|
|
14220
14480
|
async function finalizeSnapshot(cwd, snapshotId, error) {
|
|
14221
14481
|
const db = await getStoreDb(cwd);
|
|
14222
14482
|
if (error) {
|
|
14223
|
-
db.
|
|
14483
|
+
db.exec("BEGIN");
|
|
14484
|
+
try {
|
|
14485
|
+
db.prepare("DELETE FROM snapshot_table_revisions WHERE source_snapshot_id = ? AND status = 'building'").run(snapshotId);
|
|
14486
|
+
deleteOrphanPayloads(db);
|
|
14487
|
+
db.prepare("UPDATE snapshots SET status = 'error', error_message = ? WHERE id = ?").run(error, snapshotId);
|
|
14488
|
+
db.exec("COMMIT");
|
|
14489
|
+
} catch (err) {
|
|
14490
|
+
try {
|
|
14491
|
+
db.exec("ROLLBACK");
|
|
14492
|
+
} catch {}
|
|
14493
|
+
throw err;
|
|
14494
|
+
}
|
|
14224
14495
|
} else {
|
|
14225
14496
|
db.prepare("UPDATE snapshots SET status = 'done' WHERE id = ?").run(snapshotId);
|
|
14226
14497
|
}
|
|
@@ -14230,8 +14501,9 @@ async function listSnapshots(cwd, dbId, schema) {
|
|
|
14230
14501
|
const conditions = [];
|
|
14231
14502
|
const params = [];
|
|
14232
14503
|
if (dbId) {
|
|
14233
|
-
|
|
14234
|
-
|
|
14504
|
+
const dbIdValues = dockerDbIdFilterValues(dbId);
|
|
14505
|
+
conditions.push(dbIdValues.length === 1 ? "db_id = ?" : `db_id IN (${dbIdValues.map(() => "?").join(", ")})`);
|
|
14506
|
+
params.push(...dbIdValues);
|
|
14235
14507
|
}
|
|
14236
14508
|
if (schema !== undefined) {
|
|
14237
14509
|
conditions.push("COALESCE(schema_name, 'public') = ?");
|
|
@@ -14264,11 +14536,14 @@ async function deleteSnapshot(cwd, snapshotId) {
|
|
|
14264
14536
|
db.exec("BEGIN");
|
|
14265
14537
|
try {
|
|
14266
14538
|
db.prepare("DELETE FROM snapshots WHERE id = ?").run(snapshotId);
|
|
14267
|
-
db.prepare(`DELETE FROM
|
|
14268
|
-
WHERE
|
|
14269
|
-
|
|
14270
|
-
|
|
14271
|
-
|
|
14539
|
+
db.prepare(`DELETE FROM snapshot_table_revisions
|
|
14540
|
+
WHERE status = 'done'
|
|
14541
|
+
AND NOT EXISTS (
|
|
14542
|
+
SELECT 1 FROM snapshot_tables
|
|
14543
|
+
WHERE snapshot_tables.revision_id = snapshot_table_revisions.id
|
|
14544
|
+
)`).run();
|
|
14545
|
+
db.prepare("DELETE FROM snapshot_table_revisions WHERE source_snapshot_id = ? AND status = 'building'").run(snapshotId);
|
|
14546
|
+
deleteOrphanPayloads(db);
|
|
14272
14547
|
db.exec("COMMIT");
|
|
14273
14548
|
} catch (err) {
|
|
14274
14549
|
try {
|
|
@@ -14278,15 +14553,22 @@ async function deleteSnapshot(cwd, snapshotId) {
|
|
|
14278
14553
|
}
|
|
14279
14554
|
}
|
|
14280
14555
|
function getSnapshotScope(db, snapshotId) {
|
|
14281
|
-
|
|
14282
|
-
|
|
14283
|
-
|
|
14284
|
-
|
|
14556
|
+
return getSnapshotScopeRow(db, snapshotId);
|
|
14557
|
+
}
|
|
14558
|
+
async function getSnapshotScopeById(cwd, snapshotId) {
|
|
14559
|
+
const db = await getStoreDb(cwd);
|
|
14560
|
+
const scope = getSnapshotScope(db, snapshotId);
|
|
14561
|
+
return {
|
|
14562
|
+
dbId: canonicalizeDockerDbId(scope.dbId) ?? scope.dbId,
|
|
14563
|
+
schema: scope.schema
|
|
14564
|
+
};
|
|
14285
14565
|
}
|
|
14286
14566
|
function assertSameSnapshotScope(db, beforeId, afterId) {
|
|
14287
14567
|
const before = getSnapshotScope(db, beforeId);
|
|
14288
14568
|
const after = getSnapshotScope(db, afterId);
|
|
14289
|
-
|
|
14569
|
+
const beforeDbId = canonicalizeDockerDbId(before.dbId) ?? before.dbId;
|
|
14570
|
+
const afterDbId = canonicalizeDockerDbId(after.dbId) ?? after.dbId;
|
|
14571
|
+
if (beforeDbId !== afterDbId || before.schema !== after.schema) {
|
|
14290
14572
|
throw new Error(`cannot compare snapshots from different database/schema (${before.dbId}:${before.schema} vs ${after.dbId}:${after.schema})`);
|
|
14291
14573
|
}
|
|
14292
14574
|
}
|
|
@@ -14338,16 +14620,16 @@ async function computeDiffTables(cwd, beforeId, afterId) {
|
|
|
14338
14620
|
continue;
|
|
14339
14621
|
}
|
|
14340
14622
|
const insertedCount = db.prepare(`SELECT COUNT(*) AS cnt
|
|
14341
|
-
FROM
|
|
14342
|
-
LEFT JOIN
|
|
14623
|
+
FROM snapshot_rows_resolved a
|
|
14624
|
+
LEFT JOIN snapshot_rows_resolved b ON b.snapshot_id = ? AND b.table_name = ? AND b.row_key_hash = a.row_key_hash
|
|
14343
14625
|
WHERE a.snapshot_id = ? AND a.table_name = ? AND b.row_key_hash IS NULL`).get(beforeId, table, afterId, table).cnt;
|
|
14344
14626
|
const deletedCount = db.prepare(`SELECT COUNT(*) AS cnt
|
|
14345
|
-
FROM
|
|
14346
|
-
LEFT JOIN
|
|
14627
|
+
FROM snapshot_rows_resolved b
|
|
14628
|
+
LEFT JOIN snapshot_rows_resolved a ON a.snapshot_id = ? AND a.table_name = ? AND a.row_key_hash = b.row_key_hash
|
|
14347
14629
|
WHERE b.snapshot_id = ? AND b.table_name = ? AND a.row_key_hash IS NULL`).get(afterId, table, beforeId, table).cnt;
|
|
14348
14630
|
const updatedCount = db.prepare(`SELECT COUNT(*) AS cnt
|
|
14349
|
-
FROM
|
|
14350
|
-
INNER JOIN
|
|
14631
|
+
FROM snapshot_rows_resolved b
|
|
14632
|
+
INNER JOIN snapshot_rows_resolved a ON a.snapshot_id = ? AND a.table_name = ? AND a.row_key_hash = b.row_key_hash
|
|
14351
14633
|
WHERE b.snapshot_id = ? AND b.table_name = ? AND b.row_hash != a.row_hash`).get(afterId, table, beforeId, table).cnt;
|
|
14352
14634
|
const unchangedCount = b.row_count - deletedCount - updatedCount;
|
|
14353
14635
|
results.push({
|
|
@@ -14373,8 +14655,8 @@ async function computeDiffRows(cwd, beforeId, afterId, table, offset = 0, limit
|
|
|
14373
14655
|
assertSameSnapshotScope(db, beforeId, afterId);
|
|
14374
14656
|
const allDiffRows = [];
|
|
14375
14657
|
const inserted = db.prepare(`SELECT a.row_key_json, a.payload_hash
|
|
14376
|
-
FROM
|
|
14377
|
-
LEFT JOIN
|
|
14658
|
+
FROM snapshot_rows_resolved a
|
|
14659
|
+
LEFT JOIN snapshot_rows_resolved b ON b.snapshot_id = ? AND b.table_name = ? AND b.row_key_hash = a.row_key_hash
|
|
14378
14660
|
WHERE a.snapshot_id = ? AND a.table_name = ? AND b.row_key_hash IS NULL
|
|
14379
14661
|
ORDER BY a.row_key_json`).all(beforeId, table, afterId, table);
|
|
14380
14662
|
for (const r of inserted) {
|
|
@@ -14386,8 +14668,8 @@ async function computeDiffRows(cwd, beforeId, afterId, table, offset = 0, limit
|
|
|
14386
14668
|
});
|
|
14387
14669
|
}
|
|
14388
14670
|
const deleted = db.prepare(`SELECT b.row_key_json, b.payload_hash
|
|
14389
|
-
FROM
|
|
14390
|
-
LEFT JOIN
|
|
14671
|
+
FROM snapshot_rows_resolved b
|
|
14672
|
+
LEFT JOIN snapshot_rows_resolved a ON a.snapshot_id = ? AND a.table_name = ? AND a.row_key_hash = b.row_key_hash
|
|
14391
14673
|
WHERE b.snapshot_id = ? AND b.table_name = ? AND a.row_key_hash IS NULL
|
|
14392
14674
|
ORDER BY b.row_key_json`).all(afterId, table, beforeId, table);
|
|
14393
14675
|
for (const r of deleted) {
|
|
@@ -14399,8 +14681,8 @@ async function computeDiffRows(cwd, beforeId, afterId, table, offset = 0, limit
|
|
|
14399
14681
|
});
|
|
14400
14682
|
}
|
|
14401
14683
|
const updated = db.prepare(`SELECT b.row_key_json, b.payload_hash AS before_ph, a.payload_hash AS after_ph
|
|
14402
|
-
FROM
|
|
14403
|
-
INNER JOIN
|
|
14684
|
+
FROM snapshot_rows_resolved b
|
|
14685
|
+
INNER JOIN snapshot_rows_resolved a ON a.snapshot_id = ? AND a.table_name = ? AND a.row_key_hash = b.row_key_hash
|
|
14404
14686
|
WHERE b.snapshot_id = ? AND b.table_name = ? AND b.row_hash != a.row_hash
|
|
14405
14687
|
ORDER BY b.row_key_json`).all(afterId, table, beforeId, table);
|
|
14406
14688
|
for (const r of updated) {
|
|
@@ -14454,10 +14736,45 @@ CREATE TABLE IF NOT EXISTS snapshot_tables (
|
|
|
14454
14736
|
row_count INTEGER NOT NULL DEFAULT 0,
|
|
14455
14737
|
table_hash TEXT NOT NULL DEFAULT '',
|
|
14456
14738
|
pk_columns_json TEXT NOT NULL DEFAULT '[]',
|
|
14739
|
+
revision_id TEXT,
|
|
14457
14740
|
PRIMARY KEY (snapshot_id, table_name),
|
|
14458
|
-
FOREIGN KEY (snapshot_id) REFERENCES snapshots(id) ON DELETE CASCADE
|
|
14741
|
+
FOREIGN KEY (snapshot_id) REFERENCES snapshots(id) ON DELETE CASCADE,
|
|
14742
|
+
FOREIGN KEY (revision_id) REFERENCES snapshot_table_revisions(id)
|
|
14743
|
+
);
|
|
14744
|
+
|
|
14745
|
+
CREATE TABLE IF NOT EXISTS snapshot_table_revisions (
|
|
14746
|
+
id TEXT PRIMARY KEY,
|
|
14747
|
+
source_snapshot_id TEXT,
|
|
14748
|
+
db_id TEXT NOT NULL,
|
|
14749
|
+
schema_name TEXT NOT NULL,
|
|
14750
|
+
table_name TEXT NOT NULL,
|
|
14751
|
+
row_count INTEGER NOT NULL DEFAULT 0,
|
|
14752
|
+
table_hash TEXT NOT NULL DEFAULT '',
|
|
14753
|
+
hash_version INTEGER NOT NULL DEFAULT 2,
|
|
14754
|
+
pk_columns_json TEXT NOT NULL DEFAULT '[]',
|
|
14755
|
+
status TEXT NOT NULL DEFAULT 'building',
|
|
14756
|
+
created_at TEXT NOT NULL
|
|
14459
14757
|
);
|
|
14460
14758
|
|
|
14759
|
+
CREATE INDEX IF NOT EXISTS idx_snapshot_table_revisions_lookup
|
|
14760
|
+
ON snapshot_table_revisions(db_id, schema_name, table_name, hash_version, table_hash, row_count, pk_columns_json, status);
|
|
14761
|
+
|
|
14762
|
+
CREATE TABLE IF NOT EXISTS snapshot_table_revision_rows (
|
|
14763
|
+
revision_id TEXT NOT NULL,
|
|
14764
|
+
row_key_hash TEXT NOT NULL,
|
|
14765
|
+
row_key_json TEXT NOT NULL,
|
|
14766
|
+
row_hash TEXT NOT NULL,
|
|
14767
|
+
payload_hash TEXT NOT NULL,
|
|
14768
|
+
PRIMARY KEY (revision_id, row_key_hash),
|
|
14769
|
+
FOREIGN KEY (revision_id) REFERENCES snapshot_table_revisions(id) ON DELETE CASCADE
|
|
14770
|
+
);
|
|
14771
|
+
|
|
14772
|
+
CREATE INDEX IF NOT EXISTS idx_snapshot_table_revision_rows_revision_key
|
|
14773
|
+
ON snapshot_table_revision_rows(revision_id, row_key_hash);
|
|
14774
|
+
|
|
14775
|
+
CREATE INDEX IF NOT EXISTS idx_snapshot_table_revision_rows_payload_hash
|
|
14776
|
+
ON snapshot_table_revision_rows(payload_hash);
|
|
14777
|
+
|
|
14461
14778
|
CREATE TABLE IF NOT EXISTS snapshot_rows (
|
|
14462
14779
|
snapshot_id TEXT NOT NULL,
|
|
14463
14780
|
table_name TEXT NOT NULL,
|
|
@@ -14475,14 +14792,40 @@ CREATE TABLE IF NOT EXISTS snapshot_payloads (
|
|
|
14475
14792
|
);
|
|
14476
14793
|
|
|
14477
14794
|
-- deleteSnapshot の orphan cleanup (snapshot_payloads を残さない) は
|
|
14478
|
-
--
|
|
14479
|
-
-- snapshot_payloads 件数 ×
|
|
14795
|
+
-- legacy rows と revision rows の payload_hash で逆引きする。index が無いと
|
|
14796
|
+
-- snapshot_payloads 件数 × rows 全件の相関スキャンになる。
|
|
14480
14797
|
CREATE INDEX IF NOT EXISTS idx_snapshot_rows_payload_hash
|
|
14481
14798
|
ON snapshot_rows(payload_hash);
|
|
14482
14799
|
|
|
14483
|
-
|
|
14800
|
+
DROP VIEW IF EXISTS snapshot_rows_resolved;
|
|
14801
|
+
|
|
14802
|
+
CREATE VIEW snapshot_rows_resolved AS
|
|
14803
|
+
SELECT
|
|
14804
|
+
st.snapshot_id AS snapshot_id,
|
|
14805
|
+
st.table_name AS table_name,
|
|
14806
|
+
rr.row_key_hash AS row_key_hash,
|
|
14807
|
+
rr.row_key_json AS row_key_json,
|
|
14808
|
+
rr.row_hash AS row_hash,
|
|
14809
|
+
rr.payload_hash AS payload_hash
|
|
14810
|
+
FROM snapshot_tables st
|
|
14811
|
+
INNER JOIN snapshot_table_revision_rows rr
|
|
14812
|
+
ON rr.revision_id = st.revision_id
|
|
14813
|
+
WHERE st.revision_id IS NOT NULL
|
|
14814
|
+
UNION ALL
|
|
14815
|
+
SELECT
|
|
14816
|
+
snapshot_id,
|
|
14817
|
+
table_name,
|
|
14818
|
+
row_key_hash,
|
|
14819
|
+
row_key_json,
|
|
14820
|
+
row_hash,
|
|
14821
|
+
payload_hash
|
|
14822
|
+
FROM snapshot_rows;
|
|
14823
|
+
|
|
14824
|
+
`, storeDb = null, storeDbPath = null, SNAPSHOT_TABLE_HASH_VERSION = 2, SNAPSHOT_TABLE_HASH_PREFIX, SNAPSHOT_REVISION_HASH_BATCH_SIZE = 1000;
|
|
14484
14825
|
var init_snapshot_store = __esm(() => {
|
|
14826
|
+
init_discovery();
|
|
14485
14827
|
init_sqlite_driver();
|
|
14828
|
+
SNAPSHOT_TABLE_HASH_PREFIX = `v${SNAPSHOT_TABLE_HASH_VERSION}:`;
|
|
14486
14829
|
});
|
|
14487
14830
|
|
|
14488
14831
|
// web-src/server/database/sources/types.ts
|
|
@@ -14505,7 +14848,6 @@ async function runSnapshot(cwd, source, dbId, containers, note, onProgress, opti
|
|
|
14505
14848
|
const container = containers[i];
|
|
14506
14849
|
throwIfAborted(options.signal, "snapshot cancelled");
|
|
14507
14850
|
onProgress?.({ container, done: false, index: i, total });
|
|
14508
|
-
const collected = [];
|
|
14509
14851
|
let pkColumns = [];
|
|
14510
14852
|
if (snapshotSource.model === "sql") {
|
|
14511
14853
|
try {
|
|
@@ -14517,16 +14859,28 @@ async function runSnapshot(cwd, source, dbId, containers, note, onProgress, opti
|
|
|
14517
14859
|
}
|
|
14518
14860
|
}
|
|
14519
14861
|
throwIfAborted(options.signal, "snapshot cancelled");
|
|
14862
|
+
const revisionId = await beginSnapshotTableRevision(cwd, snapshotId, container, pkColumns);
|
|
14863
|
+
const pendingRows = [];
|
|
14864
|
+
const flushRows = async () => {
|
|
14865
|
+
if (pendingRows.length === 0)
|
|
14866
|
+
return;
|
|
14867
|
+
const rows = pendingRows.splice(0, pendingRows.length);
|
|
14868
|
+
await addSnapshotTableRows(cwd, revisionId, rows);
|
|
14869
|
+
};
|
|
14520
14870
|
for await (const item of snapshotSource.iterateForSnapshot(container, options.signal)) {
|
|
14521
14871
|
throwIfAborted(options.signal, "snapshot cancelled");
|
|
14522
|
-
|
|
14872
|
+
pendingRows.push({
|
|
14523
14873
|
rowKeyJson: item.keyJson,
|
|
14524
14874
|
rowHash: item.rowHash,
|
|
14525
14875
|
payloadJson: item.payloadJson
|
|
14526
14876
|
});
|
|
14877
|
+
if (pendingRows.length >= SNAPSHOT_FLUSH_THRESHOLD) {
|
|
14878
|
+
await flushRows();
|
|
14879
|
+
}
|
|
14527
14880
|
}
|
|
14528
14881
|
throwIfAborted(options.signal, "snapshot cancelled");
|
|
14529
|
-
await
|
|
14882
|
+
await flushRows();
|
|
14883
|
+
await finalizeSnapshotTableRevision(cwd, snapshotId, container, revisionId);
|
|
14530
14884
|
}
|
|
14531
14885
|
await finalizeSnapshot(cwd, snapshotId);
|
|
14532
14886
|
onProgress?.({ container: "", done: true, index: total, total });
|
|
@@ -15821,7 +16175,14 @@ async function handleDiffTables(cwd, url) {
|
|
|
15821
16175
|
return textError("missing before or after parameter", 400);
|
|
15822
16176
|
try {
|
|
15823
16177
|
const tables = await computeDiffTables(cwd, beforeId, afterId);
|
|
15824
|
-
|
|
16178
|
+
const scope = await getSnapshotScopeById(cwd, beforeId);
|
|
16179
|
+
return json({
|
|
16180
|
+
beforeId,
|
|
16181
|
+
afterId,
|
|
16182
|
+
dbId: scope.dbId,
|
|
16183
|
+
schema: scope.schema,
|
|
16184
|
+
tables
|
|
16185
|
+
});
|
|
15825
16186
|
} catch (err) {
|
|
15826
16187
|
return handleError("database", "compute diff", err);
|
|
15827
16188
|
}
|
|
@@ -17288,21 +17649,6 @@ function normalizeNewDirectoryName(name) {
|
|
|
17288
17649
|
return trimmed;
|
|
17289
17650
|
}
|
|
17290
17651
|
|
|
17291
|
-
// web-src/core/routes.ts
|
|
17292
|
-
var SPA_PATHS, APP_ENTRY_PATHS;
|
|
17293
|
-
var init_routes = __esm(() => {
|
|
17294
|
-
SPA_PATHS = [
|
|
17295
|
-
"/todif",
|
|
17296
|
-
"/todiff",
|
|
17297
|
-
"/file",
|
|
17298
|
-
"/help",
|
|
17299
|
-
"/history",
|
|
17300
|
-
"/database",
|
|
17301
|
-
"/doctor"
|
|
17302
|
-
];
|
|
17303
|
-
APP_ENTRY_PATHS = ["/", "/index.html"];
|
|
17304
|
-
});
|
|
17305
|
-
|
|
17306
17652
|
// web-src/server/cache.ts
|
|
17307
17653
|
import { lstatSync as lstatSync3 } from "node:fs";
|
|
17308
17654
|
import { join as join14 } from "node:path";
|