@youtyan/code-viewer 0.2.10 → 0.3.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 +49 -12
- package/dist/code-viewer.js +353 -48
- package/package.json +1 -1
- package/web/app.js +12219 -10860
- package/web/index.html +2 -22
- package/web/style.css +341 -28
package/dist/code-viewer.js
CHANGED
|
@@ -759,6 +759,16 @@ import {
|
|
|
759
759
|
statSync
|
|
760
760
|
} from "node:fs";
|
|
761
761
|
import { join as join2 } from "node:path";
|
|
762
|
+
function normalizeBlameRef(ref, base) {
|
|
763
|
+
const rawRef = ref || "worktree";
|
|
764
|
+
if (base === "worktree" && rawRef !== "worktree") {
|
|
765
|
+
return { base: "HEAD", ref: rawRef };
|
|
766
|
+
}
|
|
767
|
+
if (base === "HEAD" && rawRef === "worktree") {
|
|
768
|
+
return { base: "HEAD", ref: "HEAD" };
|
|
769
|
+
}
|
|
770
|
+
return { base, ref: rawRef };
|
|
771
|
+
}
|
|
762
772
|
function run(args, cwd) {
|
|
763
773
|
return runSync(args, cwd);
|
|
764
774
|
}
|
|
@@ -1063,6 +1073,13 @@ function commitHistory(cwd, options) {
|
|
|
1063
1073
|
const skip = Math.max(0, Math.floor(options.skip) || 0);
|
|
1064
1074
|
const limit = Math.max(1, Math.min(Math.floor(options.limit) || 1, MAX_HISTORY_LIMIT));
|
|
1065
1075
|
const { filterArgs, pathspec, shaTerm } = historyQueryArgs(options.query || "");
|
|
1076
|
+
const pathFilter = (options.path || "").trim();
|
|
1077
|
+
const pathArgs = [];
|
|
1078
|
+
if (pathFilter && !pathFilter.includes("\x00") && !pathFilter.startsWith("-")) {
|
|
1079
|
+
if (!pathFilter.endsWith("/"))
|
|
1080
|
+
pathArgs.push("--follow");
|
|
1081
|
+
pathArgs.push("--", pathFilter);
|
|
1082
|
+
}
|
|
1066
1083
|
const res = run([
|
|
1067
1084
|
"git",
|
|
1068
1085
|
"log",
|
|
@@ -1072,7 +1089,8 @@ function commitHistory(cwd, options) {
|
|
|
1072
1089
|
`--format=${HISTORY_FORMAT}`,
|
|
1073
1090
|
...filterArgs,
|
|
1074
1091
|
verified.stdout.trim(),
|
|
1075
|
-
...pathspec
|
|
1092
|
+
...pathspec,
|
|
1093
|
+
...pathArgs
|
|
1076
1094
|
], cwd);
|
|
1077
1095
|
if (res.code !== 0)
|
|
1078
1096
|
return { commits: [], hasMore: false, error: "git log failed" };
|
|
@@ -1173,6 +1191,130 @@ function numstatZ(args, cwd) {
|
|
|
1173
1191
|
function isToolInternalPath(path) {
|
|
1174
1192
|
return path.split(/[\\/]+/).some((part) => part.toLowerCase() === ".code-viewer");
|
|
1175
1193
|
}
|
|
1194
|
+
function syntheticUncommittedBlameFromWorktree(cwd, path) {
|
|
1195
|
+
const filePath = join2(cwd, path);
|
|
1196
|
+
try {
|
|
1197
|
+
const stat = statSync(filePath);
|
|
1198
|
+
if (!stat.isFile())
|
|
1199
|
+
return { lines: [], commits: {}, error: "not a file" };
|
|
1200
|
+
const text = readFileSync(filePath, "utf8");
|
|
1201
|
+
const normalized = text.replace(/\r\n/g, `
|
|
1202
|
+
`).replace(/\r/g, `
|
|
1203
|
+
`);
|
|
1204
|
+
const lineCount = normalized.length ? normalized.endsWith(`
|
|
1205
|
+
`) ? normalized.length - 1 === 0 ? 1 : normalized.split(`
|
|
1206
|
+
`).length - 1 : normalized.split(`
|
|
1207
|
+
`).length : 1;
|
|
1208
|
+
const lines = [];
|
|
1209
|
+
for (let i = 1;i <= lineCount; i++) {
|
|
1210
|
+
lines.push({ lineNo: i, sha: BLAME_ZERO_SHA, isUncommitted: true });
|
|
1211
|
+
}
|
|
1212
|
+
return {
|
|
1213
|
+
lines,
|
|
1214
|
+
commits: {
|
|
1215
|
+
[BLAME_ZERO_SHA]: {
|
|
1216
|
+
sha: BLAME_ZERO_SHA,
|
|
1217
|
+
author: "Not Committed Yet",
|
|
1218
|
+
authorMail: "",
|
|
1219
|
+
authorTime: 0,
|
|
1220
|
+
summary: "Working tree",
|
|
1221
|
+
isUncommitted: true
|
|
1222
|
+
}
|
|
1223
|
+
},
|
|
1224
|
+
isUntracked: true,
|
|
1225
|
+
isSynthetic: true
|
|
1226
|
+
};
|
|
1227
|
+
} catch (err) {
|
|
1228
|
+
if (typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT") {
|
|
1229
|
+
return { lines: [], commits: {}, error: "file not found" };
|
|
1230
|
+
}
|
|
1231
|
+
return { lines: [], commits: {}, error: "file not readable" };
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
function blame(cwd, options) {
|
|
1235
|
+
const path = options.path;
|
|
1236
|
+
if (!path || path.includes("\x00") || path.startsWith("-")) {
|
|
1237
|
+
return { lines: [], commits: {}, error: "invalid path" };
|
|
1238
|
+
}
|
|
1239
|
+
const normalized = normalizeBlameRef(options.ref, options.base);
|
|
1240
|
+
const args = ["git", "blame", "--porcelain"];
|
|
1241
|
+
if (normalized.base === "HEAD") {
|
|
1242
|
+
if (normalized.ref.startsWith("-") || normalized.ref.includes("\x00"))
|
|
1243
|
+
return { lines: [], commits: {}, error: "invalid ref" };
|
|
1244
|
+
args.push(normalized.ref);
|
|
1245
|
+
}
|
|
1246
|
+
args.push("--", path);
|
|
1247
|
+
const res = run(args, cwd);
|
|
1248
|
+
if (res.code !== 0) {
|
|
1249
|
+
if (normalized.base === "worktree") {
|
|
1250
|
+
return syntheticUncommittedBlameFromWorktree(cwd, path);
|
|
1251
|
+
}
|
|
1252
|
+
return {
|
|
1253
|
+
lines: [],
|
|
1254
|
+
commits: {},
|
|
1255
|
+
error: res.stderr.trim() || "blame failed"
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
const lines = [];
|
|
1259
|
+
const commits = {};
|
|
1260
|
+
const rawLines = res.stdout.split(`
|
|
1261
|
+
`);
|
|
1262
|
+
let i = 0;
|
|
1263
|
+
while (i < rawLines.length) {
|
|
1264
|
+
const headerLine = rawLines[i];
|
|
1265
|
+
if (!headerLine) {
|
|
1266
|
+
i++;
|
|
1267
|
+
continue;
|
|
1268
|
+
}
|
|
1269
|
+
const headerMatch = /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/.exec(headerLine);
|
|
1270
|
+
if (!headerMatch) {
|
|
1271
|
+
i++;
|
|
1272
|
+
continue;
|
|
1273
|
+
}
|
|
1274
|
+
const sha = headerMatch[1];
|
|
1275
|
+
const finalLine = Number(headerMatch[3]);
|
|
1276
|
+
i++;
|
|
1277
|
+
let commit = commits[sha];
|
|
1278
|
+
if (!commit) {
|
|
1279
|
+
commit = {
|
|
1280
|
+
sha,
|
|
1281
|
+
author: "",
|
|
1282
|
+
authorMail: "",
|
|
1283
|
+
authorTime: 0,
|
|
1284
|
+
summary: "",
|
|
1285
|
+
isUncommitted: sha === BLAME_ZERO_SHA
|
|
1286
|
+
};
|
|
1287
|
+
commits[sha] = commit;
|
|
1288
|
+
}
|
|
1289
|
+
while (i < rawLines.length && !rawLines[i].startsWith("\t")) {
|
|
1290
|
+
const metaLine = rawLines[i++];
|
|
1291
|
+
if (!metaLine)
|
|
1292
|
+
continue;
|
|
1293
|
+
const sp = metaLine.indexOf(" ");
|
|
1294
|
+
const key = sp >= 0 ? metaLine.slice(0, sp) : metaLine;
|
|
1295
|
+
const val = sp >= 0 ? metaLine.slice(sp + 1) : "";
|
|
1296
|
+
if (key === "author" && !commit.author)
|
|
1297
|
+
commit.author = val;
|
|
1298
|
+
else if (key === "author-mail" && !commit.authorMail)
|
|
1299
|
+
commit.authorMail = val.replace(/^</, "").replace(/>$/, "");
|
|
1300
|
+
else if (key === "author-time" && !commit.authorTime)
|
|
1301
|
+
commit.authorTime = Number(val) || 0;
|
|
1302
|
+
else if (key === "summary" && !commit.summary)
|
|
1303
|
+
commit.summary = val;
|
|
1304
|
+
}
|
|
1305
|
+
if (i < rawLines.length && rawLines[i].startsWith("\t"))
|
|
1306
|
+
i++;
|
|
1307
|
+
if (Number.isFinite(finalLine) && finalLine > 0) {
|
|
1308
|
+
lines.push({
|
|
1309
|
+
lineNo: finalLine,
|
|
1310
|
+
sha,
|
|
1311
|
+
isUncommitted: sha === BLAME_ZERO_SHA
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
lines.sort((a, b) => a.lineNo - b.lineNo);
|
|
1316
|
+
return { lines, commits };
|
|
1317
|
+
}
|
|
1176
1318
|
function untracked(cwd, path = "") {
|
|
1177
1319
|
const args = ["git", "ls-files", "--others", "--exclude-standard"];
|
|
1178
1320
|
if (path)
|
|
@@ -1509,7 +1651,7 @@ function truncateToNHunks(diffText, n, maxLines = Number.POSITIVE_INFINITY) {
|
|
|
1509
1651
|
lineTruncated
|
|
1510
1652
|
};
|
|
1511
1653
|
}
|
|
1512
|
-
var WORKTREE_RECURSIVE_DEPTH_LIMIT = 32, WORKTREE_RECURSIVE_ENTRY_LIMIT = 50000, DEFAULT_REF_COMMIT_LIMIT = 100, MAX_REF_COMMIT_LIMIT = 500, COMMIT_FORMAT = "%H%x00%s%x00%an%x00%aI", DEFAULT_WORKTREE_OMIT_DIR_NAMES, HISTORY_FORMAT = "%H%x00%s%x00%an%x00%aI%x00%P%x00%b", MAX_HISTORY_LIMIT = 200;
|
|
1654
|
+
var BLAME_ZERO_SHA = "0000000000000000000000000000000000000000", WORKTREE_RECURSIVE_DEPTH_LIMIT = 32, WORKTREE_RECURSIVE_ENTRY_LIMIT = 50000, DEFAULT_REF_COMMIT_LIMIT = 100, MAX_REF_COMMIT_LIMIT = 500, COMMIT_FORMAT = "%H%x00%s%x00%an%x00%aI", DEFAULT_WORKTREE_OMIT_DIR_NAMES, HISTORY_FORMAT = "%H%x00%s%x00%an%x00%aI%x00%P%x00%b", MAX_HISTORY_LIMIT = 200;
|
|
1513
1655
|
var init_git = __esm(() => {
|
|
1514
1656
|
init_runtime();
|
|
1515
1657
|
DEFAULT_WORKTREE_OMIT_DIR_NAMES = [
|
|
@@ -2775,7 +2917,7 @@ __export(exports_skill_cli, {
|
|
|
2775
2917
|
SKILL_HELP: () => SKILL_HELP,
|
|
2776
2918
|
AGENT_SKILL_DIRS: () => AGENT_SKILL_DIRS
|
|
2777
2919
|
});
|
|
2778
|
-
import { cpSync, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "node:fs";
|
|
2920
|
+
import { cpSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2 } from "node:fs";
|
|
2779
2921
|
import { homedir as homedir2 } from "node:os";
|
|
2780
2922
|
import { join as join5, resolve } from "node:path";
|
|
2781
2923
|
function parseAgentList(value) {
|
|
@@ -2831,25 +2973,34 @@ function parseSkillArgs(argv) {
|
|
|
2831
2973
|
}
|
|
2832
2974
|
return { ok: true, args: { kind: "install", agents, global, cwd } };
|
|
2833
2975
|
}
|
|
2976
|
+
function discoverBundledSkills(skillsRoot) {
|
|
2977
|
+
if (!existsSync4(skillsRoot))
|
|
2978
|
+
return [];
|
|
2979
|
+
return readdirSync2(skillsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && existsSync4(join5(skillsRoot, entry.name, "SKILL.md"))).map((entry) => entry.name).sort();
|
|
2980
|
+
}
|
|
2834
2981
|
function installSkill(args, deps) {
|
|
2835
|
-
|
|
2982
|
+
const skills = discoverBundledSkills(deps.skillsRoot);
|
|
2983
|
+
if (skills.length === 0) {
|
|
2836
2984
|
return {
|
|
2837
2985
|
ok: false,
|
|
2838
|
-
error: `bundled
|
|
2986
|
+
error: `no bundled skills found under ${deps.skillsRoot}`
|
|
2839
2987
|
};
|
|
2840
2988
|
}
|
|
2841
2989
|
const base = args.global ? deps.homeDir : resolve(args.cwd ?? deps.projectDir);
|
|
2842
2990
|
const results = [];
|
|
2843
2991
|
for (const agent of args.agents) {
|
|
2844
|
-
const
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2992
|
+
for (const skill of skills) {
|
|
2993
|
+
const sourceDir = join5(deps.skillsRoot, skill);
|
|
2994
|
+
const target = join5(base, AGENT_SKILL_DIRS[agent], "skills", skill);
|
|
2995
|
+
const action = existsSync4(target) ? "updated" : "installed";
|
|
2996
|
+
try {
|
|
2997
|
+
mkdirSync2(target, { recursive: true });
|
|
2998
|
+
cpSync(sourceDir, target, { recursive: true });
|
|
2999
|
+
} catch (error) {
|
|
3000
|
+
return { ok: false, error: String(error) };
|
|
3001
|
+
}
|
|
3002
|
+
results.push({ agent, skill, action, target });
|
|
2851
3003
|
}
|
|
2852
|
-
results.push({ agent, action, target });
|
|
2853
3004
|
}
|
|
2854
3005
|
return { ok: true, results };
|
|
2855
3006
|
}
|
|
@@ -2865,7 +3016,7 @@ function runSkillCli(argv) {
|
|
|
2865
3016
|
return;
|
|
2866
3017
|
}
|
|
2867
3018
|
const result = installSkill(parsed.args, {
|
|
2868
|
-
|
|
3019
|
+
skillsRoot: join5(ROOT, "skills"),
|
|
2869
3020
|
homeDir: homedir2(),
|
|
2870
3021
|
projectDir: process.cwd()
|
|
2871
3022
|
});
|
|
@@ -2874,13 +3025,13 @@ function runSkillCli(argv) {
|
|
|
2874
3025
|
process.exit(1);
|
|
2875
3026
|
}
|
|
2876
3027
|
for (const entry of result.results) {
|
|
2877
|
-
console.log(`${entry.action} (${entry.agent}): ${entry.target}`);
|
|
3028
|
+
console.log(`${entry.action} (${entry.agent}/${entry.skill}): ${entry.target}`);
|
|
2878
3029
|
}
|
|
2879
3030
|
if (result.results.some((entry) => entry.action === "installed")) {
|
|
2880
|
-
console.log("Re-run the same command anytime to update the
|
|
3031
|
+
console.log("Re-run the same command anytime to update the skills.");
|
|
2881
3032
|
}
|
|
2882
3033
|
}
|
|
2883
|
-
var
|
|
3034
|
+
var AGENT_SKILL_DIRS, AGENT_NAMES, SKILL_HELP;
|
|
2884
3035
|
var init_skill_cli = __esm(() => {
|
|
2885
3036
|
init_root();
|
|
2886
3037
|
AGENT_SKILL_DIRS = {
|
|
@@ -2891,15 +3042,16 @@ var init_skill_cli = __esm(() => {
|
|
|
2891
3042
|
agents: ".agents"
|
|
2892
3043
|
};
|
|
2893
3044
|
AGENT_NAMES = Object.keys(AGENT_SKILL_DIRS);
|
|
2894
|
-
SKILL_HELP = `code-viewer skill — manage the bundled agent
|
|
3045
|
+
SKILL_HELP = `code-viewer skill — manage the bundled agent skills
|
|
2895
3046
|
|
|
2896
3047
|
Usage:
|
|
2897
3048
|
code-viewer skill install [--agent <list>] [--global] [--cwd <dir>]
|
|
2898
3049
|
|
|
2899
|
-
Installs
|
|
2900
|
-
skills directory of each selected agent in the current
|
|
2901
|
-
home directory equivalents with --global. Running
|
|
2902
|
-
the files, so the same command also updates an
|
|
3050
|
+
Installs every bundled skill (each SKILL.md directory under the package's
|
|
3051
|
+
skills/) into the skills directory of each selected agent in the current
|
|
3052
|
+
project, or into the home directory equivalents with --global. Running
|
|
3053
|
+
install again overwrites the files, so the same command also updates an
|
|
3054
|
+
existing installation.
|
|
2903
3055
|
|
|
2904
3056
|
Options:
|
|
2905
3057
|
--agent <list> comma separated agents: ${AGENT_NAMES.join(", ")}, or all
|
|
@@ -4006,12 +4158,41 @@ function sanitizeDbUiPrefs(raw) {
|
|
|
4006
4158
|
}
|
|
4007
4159
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
4008
4160
|
}
|
|
4161
|
+
function sanitizeDbUiExpandedTables(raw) {
|
|
4162
|
+
if (!isRecord(raw))
|
|
4163
|
+
return;
|
|
4164
|
+
const out = {};
|
|
4165
|
+
let scopeCount = 0;
|
|
4166
|
+
for (const [scopeRaw, tablesRaw] of Object.entries(raw)) {
|
|
4167
|
+
if (scopeCount >= MAX_DB_UI_EXPANDED_SCOPES)
|
|
4168
|
+
break;
|
|
4169
|
+
const scope = safeObjectKey(scopeRaw);
|
|
4170
|
+
if (!scope)
|
|
4171
|
+
continue;
|
|
4172
|
+
const tables = normalizeStringList(tablesRaw, {
|
|
4173
|
+
maxItems: MAX_DB_UI_TABLES,
|
|
4174
|
+
maxLen: MAX_KEY_LEN,
|
|
4175
|
+
sort: true
|
|
4176
|
+
});
|
|
4177
|
+
if (!tables || tables.length === 0)
|
|
4178
|
+
continue;
|
|
4179
|
+
out[scope] = tables;
|
|
4180
|
+
scopeCount++;
|
|
4181
|
+
}
|
|
4182
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
4183
|
+
}
|
|
4009
4184
|
function sanitizeDbUiState(raw) {
|
|
4010
4185
|
if (!isRecord(raw))
|
|
4011
4186
|
return emptyDbUiState();
|
|
4012
4187
|
const prefs = sanitizeDbUiPrefs(raw.prefs);
|
|
4188
|
+
const expandedTables = sanitizeDbUiExpandedTables(raw.expandedTables);
|
|
4013
4189
|
if (!isRecord(raw.columnWidths)) {
|
|
4014
|
-
|
|
4190
|
+
const out2 = emptyDbUiState();
|
|
4191
|
+
if (expandedTables)
|
|
4192
|
+
out2.expandedTables = expandedTables;
|
|
4193
|
+
if (prefs)
|
|
4194
|
+
out2.prefs = prefs;
|
|
4195
|
+
return out2;
|
|
4015
4196
|
}
|
|
4016
4197
|
const columnWidths = {};
|
|
4017
4198
|
let dbCount = 0;
|
|
@@ -4052,6 +4233,8 @@ function sanitizeDbUiState(raw) {
|
|
|
4052
4233
|
dbCount++;
|
|
4053
4234
|
}
|
|
4054
4235
|
const out = { version: 1, columnWidths };
|
|
4236
|
+
if (expandedTables)
|
|
4237
|
+
out.expandedTables = expandedTables;
|
|
4055
4238
|
if (prefs)
|
|
4056
4239
|
out.prefs = prefs;
|
|
4057
4240
|
return out;
|
|
@@ -4069,12 +4252,30 @@ function mergeDbUiPrefs(current, patch) {
|
|
|
4069
4252
|
}
|
|
4070
4253
|
return Object.keys(next).length > 0 ? next : undefined;
|
|
4071
4254
|
}
|
|
4255
|
+
function mergeDbUiExpandedTables(current, patch) {
|
|
4256
|
+
if (!isRecord(patch))
|
|
4257
|
+
return current;
|
|
4258
|
+
const next = { ...current ?? {} };
|
|
4259
|
+
for (const [scope, tablesRaw] of Object.entries(patch)) {
|
|
4260
|
+
if (tablesRaw === null) {
|
|
4261
|
+
delete next[scope];
|
|
4262
|
+
continue;
|
|
4263
|
+
}
|
|
4264
|
+
next[scope] = tablesRaw;
|
|
4265
|
+
}
|
|
4266
|
+
return sanitizeDbUiExpandedTables(next);
|
|
4267
|
+
}
|
|
4072
4268
|
function mergeDbUiState(current, patch) {
|
|
4073
4269
|
if (!isRecord(patch))
|
|
4074
4270
|
return current;
|
|
4075
4271
|
const mergedPrefs = "prefs" in patch ? mergeDbUiPrefs(current.prefs, patch.prefs) : current.prefs;
|
|
4272
|
+
const mergedExpandedTables = "expandedTables" in patch ? mergeDbUiExpandedTables(current.expandedTables, patch.expandedTables) : current.expandedTables;
|
|
4076
4273
|
if (!isRecord(patch.columnWidths)) {
|
|
4077
4274
|
const merged = { ...current, version: 1 };
|
|
4275
|
+
if (mergedExpandedTables)
|
|
4276
|
+
merged.expandedTables = mergedExpandedTables;
|
|
4277
|
+
else
|
|
4278
|
+
delete merged.expandedTables;
|
|
4078
4279
|
if (mergedPrefs)
|
|
4079
4280
|
merged.prefs = mergedPrefs;
|
|
4080
4281
|
else
|
|
@@ -4114,6 +4315,7 @@ function mergeDbUiState(current, patch) {
|
|
|
4114
4315
|
...current,
|
|
4115
4316
|
...patch,
|
|
4116
4317
|
columnWidths,
|
|
4318
|
+
expandedTables: mergedExpandedTables,
|
|
4117
4319
|
prefs: mergedPrefs,
|
|
4118
4320
|
version: 1
|
|
4119
4321
|
});
|
|
@@ -4145,7 +4347,7 @@ async function patchDbUiState(root, patch) {
|
|
|
4145
4347
|
return { state: next, result: next };
|
|
4146
4348
|
});
|
|
4147
4349
|
}
|
|
4148
|
-
var CODE_VIEWER_DIR2 = ".code-viewer", SETTINGS_FILE_NAME = "settings.json", VIEW_STATE_FILE_NAME = "view-state.json", DB_UI_FILE_NAME = "db-ui.json", MAX_SETTINGS_BYTES = 200000, MAX_VIEW_STATE_BYTES = 1e6, MAX_DB_UI_BYTES = 1e6, MAX_REF_LEN = 1024, MAX_KEY_LEN = 2048, MAX_VIEW_ITEMS = 20000, MAX_DB_UI_DBS = 200, MAX_DB_UI_TABLES = 500, MAX_DB_UI_COLUMNS = 1000, DB_UI_BOOL_PREF_KEYS, settingsStore, viewStateStore, dbUiStore;
|
|
4350
|
+
var CODE_VIEWER_DIR2 = ".code-viewer", SETTINGS_FILE_NAME = "settings.json", VIEW_STATE_FILE_NAME = "view-state.json", DB_UI_FILE_NAME = "db-ui.json", MAX_SETTINGS_BYTES = 200000, MAX_VIEW_STATE_BYTES = 1e6, MAX_DB_UI_BYTES = 1e6, MAX_REF_LEN = 1024, MAX_KEY_LEN = 2048, MAX_VIEW_ITEMS = 20000, MAX_DB_UI_DBS = 200, MAX_DB_UI_TABLES = 500, MAX_DB_UI_COLUMNS = 1000, MAX_DB_UI_EXPANDED_SCOPES = 500, DB_UI_BOOL_PREF_KEYS, settingsStore, viewStateStore, dbUiStore;
|
|
4149
4351
|
var init_state_store = __esm(() => {
|
|
4150
4352
|
init_json_store();
|
|
4151
4353
|
DB_UI_BOOL_PREF_KEYS = ["s3TooltipEnabled", "inferFkRails"];
|
|
@@ -5258,9 +5460,12 @@ function createDockerAdapter(config) {
|
|
|
5258
5460
|
const tableLiteral = table.replace(/'/g, "''");
|
|
5259
5461
|
if (config.kind === "postgresql") {
|
|
5260
5462
|
const schemaLiteral = postgresSchemaLiteral();
|
|
5261
|
-
return `SELECT c.column_name, c.data_type, c.is_nullable, c.column_default, CASE WHEN pk.column_name IS NULL THEN 'NO' ELSE 'YES' END FROM information_schema.columns c LEFT JOIN (SELECT kcu.column_name FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema AND tc.table_name = kcu.table_name WHERE tc.table_schema = ${schemaLiteral} AND tc.table_name = '${tableLiteral}' AND tc.constraint_type = 'PRIMARY KEY') pk ON pk.column_name = c.column_name WHERE c.table_schema = ${schemaLiteral} AND c.table_name = '${tableLiteral}' ORDER BY c.ordinal_position`;
|
|
5463
|
+
return `SELECT c.column_name, c.data_type, c.is_nullable, c.column_default, CASE WHEN pk.column_name IS NULL THEN 'NO' ELSE 'YES' END, COALESCE(d.description, '') FROM information_schema.columns c JOIN pg_namespace n ON n.nspname = c.table_schema JOIN pg_class cls ON cls.relnamespace = n.oid AND cls.relname = c.table_name LEFT JOIN pg_attribute a ON a.attrelid = cls.oid AND a.attname = c.column_name AND a.attnum > 0 AND NOT a.attisdropped LEFT JOIN pg_description d ON d.objoid = cls.oid AND d.objsubid = a.attnum LEFT JOIN (SELECT kcu.column_name FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema AND tc.table_name = kcu.table_name WHERE tc.table_schema = ${schemaLiteral} AND tc.table_name = '${tableLiteral}' AND tc.constraint_type = 'PRIMARY KEY') pk ON pk.column_name = c.column_name WHERE c.table_schema = ${schemaLiteral} AND c.table_name = '${tableLiteral}' ORDER BY c.ordinal_position`;
|
|
5262
5464
|
}
|
|
5263
|
-
return `SELECT column_name, column_type, is_nullable, column_default, column_key FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = '${tableLiteral}' ORDER BY ordinal_position`;
|
|
5465
|
+
return `SELECT column_name, column_type, is_nullable, column_default, column_key, column_comment FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = '${tableLiteral}' ORDER BY ordinal_position`;
|
|
5466
|
+
}
|
|
5467
|
+
function columnCommentFromInfoValue(value) {
|
|
5468
|
+
return value ? value : null;
|
|
5264
5469
|
}
|
|
5265
5470
|
function columnsFromInfoRows(rows) {
|
|
5266
5471
|
if (config.kind === "postgresql") {
|
|
@@ -5269,7 +5474,8 @@ function createDockerAdapter(config) {
|
|
|
5269
5474
|
type: row[1],
|
|
5270
5475
|
nullable: row[2] === "YES",
|
|
5271
5476
|
primaryKey: row[4] === "YES",
|
|
5272
|
-
defaultValue: row[3] === "" ? null : row[3]
|
|
5477
|
+
defaultValue: row[3] === "" ? null : row[3],
|
|
5478
|
+
comment: columnCommentFromInfoValue(row[5])
|
|
5273
5479
|
}));
|
|
5274
5480
|
}
|
|
5275
5481
|
return rows.map((row) => ({
|
|
@@ -5277,7 +5483,8 @@ function createDockerAdapter(config) {
|
|
|
5277
5483
|
type: row[1],
|
|
5278
5484
|
nullable: row[2] === "YES",
|
|
5279
5485
|
primaryKey: row[4] === "PRI",
|
|
5280
|
-
defaultValue: row[3] === "NULL" ? null : row[3]
|
|
5486
|
+
defaultValue: row[3] === "NULL" ? null : row[3],
|
|
5487
|
+
comment: columnCommentFromInfoValue(row[5])
|
|
5281
5488
|
}));
|
|
5282
5489
|
}
|
|
5283
5490
|
async function fetchColumnsAsyncUncached(table, signal) {
|
|
@@ -5376,10 +5583,10 @@ function createDockerAdapter(config) {
|
|
|
5376
5583
|
let sql;
|
|
5377
5584
|
if (config.kind === "postgresql") {
|
|
5378
5585
|
const inList = uncached.map((t) => `'${t.replace(/'/g, "''")}'`).join(",");
|
|
5379
|
-
sql = `SELECT table_name, column_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_schema = ${postgresSchemaLiteral()} AND table_name IN (${inList}) ORDER BY table_name, ordinal_position`;
|
|
5586
|
+
sql = `SELECT c.table_name, c.column_name, c.data_type, c.is_nullable, c.column_default, COALESCE(d.description, '') FROM information_schema.columns c JOIN pg_namespace n ON n.nspname = c.table_schema JOIN pg_class cls ON cls.relnamespace = n.oid AND cls.relname = c.table_name LEFT JOIN pg_attribute a ON a.attrelid = cls.oid AND a.attname = c.column_name AND a.attnum > 0 AND NOT a.attisdropped LEFT JOIN pg_description d ON d.objoid = cls.oid AND d.objsubid = a.attnum WHERE c.table_schema = ${postgresSchemaLiteral()} AND c.table_name IN (${inList}) ORDER BY c.table_name, c.ordinal_position`;
|
|
5380
5587
|
} else {
|
|
5381
5588
|
const inList = uncached.map((t) => `'${t.replace(/'/g, "''")}'`).join(",");
|
|
5382
|
-
sql = `SELECT table_name, column_name, column_type, is_nullable, column_default, column_key FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name IN (${inList}) ORDER BY table_name, ordinal_position`;
|
|
5589
|
+
sql = `SELECT table_name, column_name, column_type, is_nullable, column_default, column_key, column_comment FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name IN (${inList}) ORDER BY table_name, ordinal_position`;
|
|
5383
5590
|
}
|
|
5384
5591
|
try {
|
|
5385
5592
|
const queryResult = await execAsync(sql, signal);
|
|
@@ -5415,7 +5622,8 @@ function createDockerAdapter(config) {
|
|
|
5415
5622
|
type: row[2],
|
|
5416
5623
|
nullable: row[3] === "YES",
|
|
5417
5624
|
primaryKey: pkCols.has(row[1]),
|
|
5418
|
-
defaultValue: row[4] === "" ? null : row[4]
|
|
5625
|
+
defaultValue: row[4] === "" ? null : row[4],
|
|
5626
|
+
comment: columnCommentFromInfoValue(row[5])
|
|
5419
5627
|
}));
|
|
5420
5628
|
} else {
|
|
5421
5629
|
cols = rows.map((row) => ({
|
|
@@ -5423,7 +5631,8 @@ function createDockerAdapter(config) {
|
|
|
5423
5631
|
type: row[2],
|
|
5424
5632
|
nullable: row[3] === "YES",
|
|
5425
5633
|
primaryKey: row[5] === "PRI",
|
|
5426
|
-
defaultValue: row[4] === "NULL" ? null : row[4]
|
|
5634
|
+
defaultValue: row[4] === "NULL" ? null : row[4],
|
|
5635
|
+
comment: columnCommentFromInfoValue(row[6])
|
|
5427
5636
|
}));
|
|
5428
5637
|
}
|
|
5429
5638
|
columnCache.set(tbl, cols);
|
|
@@ -5516,12 +5725,7 @@ function createDockerAdapter(config) {
|
|
|
5516
5725
|
async getFilteredTablePageWithMeta(table, options, signal) {
|
|
5517
5726
|
const id = tableIdentifier(table);
|
|
5518
5727
|
const columnsPromise = tableMetaCache.getColumns(table, () => fetchColumnsAsyncUncached(table, signal));
|
|
5519
|
-
|
|
5520
|
-
try {
|
|
5521
|
-
columns = await columnsPromise;
|
|
5522
|
-
} catch (err) {
|
|
5523
|
-
throw err;
|
|
5524
|
-
}
|
|
5728
|
+
const columns = await columnsPromise;
|
|
5525
5729
|
const columnNames = columns.map((column) => column.name);
|
|
5526
5730
|
const order = buildOrderClause(filterOrderByColumns(options.orderBy, columnNames), config.kind);
|
|
5527
5731
|
const where = buildFilterWhere(filterGroupedColumns(options.grouped, columnNames), config.kind, filterExactColumns(options.exact, columnNames)).where;
|
|
@@ -11597,7 +11801,7 @@ function fileToMeta(file, range, extraQs) {
|
|
|
11597
11801
|
untracked: file.untracked || false
|
|
11598
11802
|
};
|
|
11599
11803
|
}
|
|
11600
|
-
function computePayload(extras, range) {
|
|
11804
|
+
function computePayload(extras, range, pathFilter = "") {
|
|
11601
11805
|
if (isSameWorktreeRange(range)) {
|
|
11602
11806
|
return {
|
|
11603
11807
|
files: [],
|
|
@@ -11613,8 +11817,9 @@ function computePayload(extras, range) {
|
|
|
11613
11817
|
const files = fileMeta(fullArgs, cwd, false);
|
|
11614
11818
|
if (includeUntracked(range, refs2))
|
|
11615
11819
|
files.push(...untrackedMeta(cwd));
|
|
11616
|
-
files.
|
|
11617
|
-
|
|
11820
|
+
const filteredFiles = pathFilter ? files.filter((file) => file.path === pathFilter || file.old_path === pathFilter) : files;
|
|
11821
|
+
filteredFiles.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
11822
|
+
filteredFiles.forEach((file, i) => {
|
|
11618
11823
|
file.order = i + 1;
|
|
11619
11824
|
});
|
|
11620
11825
|
const extraQs = {};
|
|
@@ -11624,7 +11829,7 @@ function computePayload(extras, range) {
|
|
|
11624
11829
|
if (e === "--ignore-blank-lines")
|
|
11625
11830
|
extraQs.ignore_blank = "1";
|
|
11626
11831
|
}
|
|
11627
|
-
const meta =
|
|
11832
|
+
const meta = filteredFiles.map((file) => fileToMeta(file, range, extraQs));
|
|
11628
11833
|
const totals = meta.reduce((acc, file) => {
|
|
11629
11834
|
acc.additions += file.additions || 0;
|
|
11630
11835
|
acc.deletions += file.deletions || 0;
|
|
@@ -11651,9 +11856,12 @@ function handleDiffJson(url) {
|
|
|
11651
11856
|
from: url.searchParams.get("from") || "",
|
|
11652
11857
|
to: url.searchParams.get("to") || ""
|
|
11653
11858
|
};
|
|
11654
|
-
const
|
|
11859
|
+
const path = url.searchParams.get("path") || "";
|
|
11860
|
+
if (path && !safePath(path))
|
|
11861
|
+
return text("invalid path", 400);
|
|
11862
|
+
const key = `${range.from}|${range.to}|${url.searchParams.get("ignore_ws") || ""}|${url.searchParams.get("ignore_blank") || ""}|${path}`;
|
|
11655
11863
|
if (url.searchParams.get("nocache") === "1") {
|
|
11656
|
-
const payload2 = computePayload(extras, range);
|
|
11864
|
+
const payload2 = computePayload(extras, range, path);
|
|
11657
11865
|
const sig = JSON.stringify({ ...payload2, generation: undefined });
|
|
11658
11866
|
const cached2 = metaCache.get(key);
|
|
11659
11867
|
if (!cached2 || cached2.sig !== sig) {
|
|
@@ -11679,7 +11887,7 @@ function handleDiffJson(url) {
|
|
|
11679
11887
|
"Cache-Control": "no-store"
|
|
11680
11888
|
}
|
|
11681
11889
|
});
|
|
11682
|
-
const payload = computePayload(extras, range);
|
|
11890
|
+
const payload = computePayload(extras, range, path);
|
|
11683
11891
|
const body = JSON.stringify(payload);
|
|
11684
11892
|
setTimedCacheEntry(metaCache, key, {
|
|
11685
11893
|
body,
|
|
@@ -12096,15 +12304,109 @@ function handleLog(url) {
|
|
|
12096
12304
|
const ref = url.searchParams.get("ref") || "HEAD";
|
|
12097
12305
|
const skip = Number(url.searchParams.get("skip") || "0");
|
|
12098
12306
|
const limit = Number(url.searchParams.get("limit") || "50");
|
|
12307
|
+
const path = url.searchParams.get("path") || "";
|
|
12308
|
+
if (path && !safePath(path))
|
|
12309
|
+
return text("invalid path", 400);
|
|
12099
12310
|
const result = commitHistory(cwd, {
|
|
12100
12311
|
ref,
|
|
12101
12312
|
skip: Number.isFinite(skip) ? skip : 0,
|
|
12102
12313
|
limit: Number.isFinite(limit) ? limit : 50,
|
|
12103
|
-
query: url.searchParams.get("q") || ""
|
|
12314
|
+
query: url.searchParams.get("q") || "",
|
|
12315
|
+
...path ? { path } : {}
|
|
12104
12316
|
});
|
|
12105
12317
|
if (result.error)
|
|
12106
12318
|
return text(result.error, 400);
|
|
12107
|
-
|
|
12319
|
+
const wantsWorktreeHead = path && skip === 0 && (ref === "worktree" || url.searchParams.get("worktree") === "1");
|
|
12320
|
+
let commits = result.commits;
|
|
12321
|
+
let hasWorktree = false;
|
|
12322
|
+
if (wantsWorktreeHead) {
|
|
12323
|
+
const status = runSync([
|
|
12324
|
+
"git",
|
|
12325
|
+
"-c",
|
|
12326
|
+
"core.quotepath=false",
|
|
12327
|
+
"status",
|
|
12328
|
+
"--porcelain=v1",
|
|
12329
|
+
"-z",
|
|
12330
|
+
"--untracked-files=normal",
|
|
12331
|
+
"--",
|
|
12332
|
+
path
|
|
12333
|
+
], cwd);
|
|
12334
|
+
if (status.code === 0 && status.stdout.length > 0) {
|
|
12335
|
+
const parts = status.stdout.split("\x00").filter(Boolean);
|
|
12336
|
+
if (parts.length > 0) {
|
|
12337
|
+
hasWorktree = true;
|
|
12338
|
+
commits = [
|
|
12339
|
+
{
|
|
12340
|
+
sha: "worktree",
|
|
12341
|
+
subject: "未コミット変更 (Working tree)",
|
|
12342
|
+
author: "",
|
|
12343
|
+
when: "",
|
|
12344
|
+
parents: [],
|
|
12345
|
+
body: ""
|
|
12346
|
+
},
|
|
12347
|
+
...commits
|
|
12348
|
+
];
|
|
12349
|
+
}
|
|
12350
|
+
}
|
|
12351
|
+
}
|
|
12352
|
+
return json2({
|
|
12353
|
+
commits,
|
|
12354
|
+
hasMore: result.hasMore,
|
|
12355
|
+
generation,
|
|
12356
|
+
...hasWorktree ? { hasWorktree: true } : {}
|
|
12357
|
+
});
|
|
12358
|
+
}
|
|
12359
|
+
function blamePathKey(p) {
|
|
12360
|
+
try {
|
|
12361
|
+
const st = statSync3(join13(cwd, p));
|
|
12362
|
+
return `${st.mtimeMs}:${st.size}`;
|
|
12363
|
+
} catch {
|
|
12364
|
+
return "missing";
|
|
12365
|
+
}
|
|
12366
|
+
}
|
|
12367
|
+
function rememberBlame(key, value) {
|
|
12368
|
+
if (blameCache.has(key))
|
|
12369
|
+
blameCache.delete(key);
|
|
12370
|
+
blameCache.set(key, value);
|
|
12371
|
+
while (blameCache.size > BLAME_CACHE_MAX) {
|
|
12372
|
+
const oldest = blameCache.keys().next();
|
|
12373
|
+
if (oldest.done)
|
|
12374
|
+
break;
|
|
12375
|
+
blameCache.delete(oldest.value);
|
|
12376
|
+
}
|
|
12377
|
+
}
|
|
12378
|
+
function handleFileBlame(url) {
|
|
12379
|
+
const path = url.searchParams.get("path") || "";
|
|
12380
|
+
if (!safePath(path))
|
|
12381
|
+
return text("invalid path", 400);
|
|
12382
|
+
const ref = url.searchParams.get("ref") || "worktree";
|
|
12383
|
+
if (!ref || ref.startsWith("-") || ref.includes("\x00"))
|
|
12384
|
+
return text("invalid ref", 400);
|
|
12385
|
+
const rawBase = url.searchParams.get("base");
|
|
12386
|
+
const requestedBase = rawBase === "HEAD" ? "HEAD" : rawBase === "worktree" ? "worktree" : ref === "worktree" ? "worktree" : "HEAD";
|
|
12387
|
+
const normalized = normalizeBlameRef(ref, requestedBase);
|
|
12388
|
+
const { base } = normalized;
|
|
12389
|
+
let cacheKey;
|
|
12390
|
+
if (base === "worktree") {
|
|
12391
|
+
cacheKey = `worktree|${path}|${blamePathKey(path)}`;
|
|
12392
|
+
} else {
|
|
12393
|
+
const resolved = runSync(["git", "rev-parse", "--verify", `${normalized.ref}^{commit}`], cwd);
|
|
12394
|
+
if (resolved.code !== 0)
|
|
12395
|
+
return text("unknown ref", 400);
|
|
12396
|
+
cacheKey = `HEAD|${path}|${resolved.stdout.trim()}`;
|
|
12397
|
+
}
|
|
12398
|
+
const cached = blameCache.get(cacheKey);
|
|
12399
|
+
if (cached) {
|
|
12400
|
+
if (blameCache.has(cacheKey)) {
|
|
12401
|
+
blameCache.delete(cacheKey);
|
|
12402
|
+
blameCache.set(cacheKey, cached);
|
|
12403
|
+
}
|
|
12404
|
+
return json2({ ...cached, base, ref, generation });
|
|
12405
|
+
}
|
|
12406
|
+
const result = blame(cwd, { path, ref: normalized.ref, base });
|
|
12407
|
+
if (!result.error)
|
|
12408
|
+
rememberBlame(cacheKey, result);
|
|
12409
|
+
return json2({ ...result, base, ref, generation });
|
|
12108
12410
|
}
|
|
12109
12411
|
function handleFileDiff(url) {
|
|
12110
12412
|
const path = url.searchParams.get("path") || "";
|
|
@@ -13113,7 +13415,7 @@ function restartWorktreeWatch() {
|
|
|
13113
13415
|
}
|
|
13114
13416
|
worktreeWatch = startScopedWorktreeWatch();
|
|
13115
13417
|
}
|
|
13116
|
-
var WEB_ROOT, VERSION, DEFAULT_ARGS, PREVIEW_HUNKS_DEFAULT = 3, PREVIEW_LINES_DEFAULT = 1200, WATCHED_ASSET_FILES, SIZE_SMALL = 2000, SIZE_MEDIUM = 8000, SIZE_LARGE = 20000, LINE_INDEX_MIN_START = 1e4, LINE_INDEX_MAX_FILE_BYTES, BLOB_LINE_CACHE_MAX_BYTES, MAX_UPLOAD_FILE_BYTES, MAX_UPLOAD_TOTAL_BYTES, MAX_UPLOAD_BODY_BYTES, MAX_UPLOAD_FILES = 50, SAFE_UPLOAD_EXTENSIONS, generation = 1, cwd, cliArgs, listenPort = 0, openAfterStart = false, scopeOmitDirNames, scopeOmitDirCliOverride = null, scopeExcludeNames, scopeWatchLimit, uploadEnabled = true, rgAvailableCache = null, enc, sseClients, sseKeepalives, fileCache, metaCache, fileListCache, lineIndexCache, blobLineIndexCache, blobBytesCache, blobLineCacheBytes = 0, isCodeViewerInternalPath, watchLimitReached = null, server, worktreeWatch = null, shuttingDown = false;
|
|
13418
|
+
var WEB_ROOT, VERSION, DEFAULT_ARGS, PREVIEW_HUNKS_DEFAULT = 3, PREVIEW_LINES_DEFAULT = 1200, WATCHED_ASSET_FILES, SIZE_SMALL = 2000, SIZE_MEDIUM = 8000, SIZE_LARGE = 20000, LINE_INDEX_MIN_START = 1e4, LINE_INDEX_MAX_FILE_BYTES, BLOB_LINE_CACHE_MAX_BYTES, MAX_UPLOAD_FILE_BYTES, MAX_UPLOAD_TOTAL_BYTES, MAX_UPLOAD_BODY_BYTES, MAX_UPLOAD_FILES = 50, SAFE_UPLOAD_EXTENSIONS, generation = 1, cwd, cliArgs, listenPort = 0, openAfterStart = false, scopeOmitDirNames, scopeOmitDirCliOverride = null, scopeExcludeNames, scopeWatchLimit, uploadEnabled = true, rgAvailableCache = null, enc, sseClients, sseKeepalives, fileCache, blameCache, BLAME_CACHE_MAX = 64, metaCache, fileListCache, lineIndexCache, blobLineIndexCache, blobBytesCache, blobLineCacheBytes = 0, isCodeViewerInternalPath, watchLimitReached = null, server, worktreeWatch = null, shuttingDown = false;
|
|
13117
13419
|
var init_preview = __esm(async () => {
|
|
13118
13420
|
init_routes();
|
|
13119
13421
|
init_annotations();
|
|
@@ -13181,6 +13483,7 @@ var init_preview = __esm(async () => {
|
|
|
13181
13483
|
sseClients = new Set;
|
|
13182
13484
|
sseKeepalives = new Map;
|
|
13183
13485
|
fileCache = new Map;
|
|
13486
|
+
blameCache = new Map;
|
|
13184
13487
|
metaCache = new Map;
|
|
13185
13488
|
fileListCache = new Map;
|
|
13186
13489
|
lineIndexCache = new Map;
|
|
@@ -13213,6 +13516,8 @@ var init_preview = __esm(async () => {
|
|
|
13213
13516
|
return handleRefCommits(url);
|
|
13214
13517
|
if (url.pathname === "/_log")
|
|
13215
13518
|
return handleLog(url);
|
|
13519
|
+
if (url.pathname === "/_file_blame")
|
|
13520
|
+
return handleFileBlame(url);
|
|
13216
13521
|
if (url.pathname === "/file_diff")
|
|
13217
13522
|
return handleFileDiff(url);
|
|
13218
13523
|
if (url.pathname === "/file_range")
|