@jpkme/diff-review 0.1.3 → 0.1.4
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 +22 -6
- package/bin/diff-review.mjs +187 -19
- package/lib/clipboard.mjs +29 -0
- package/lib/config.mjs +233 -0
- package/lib/config.test.mjs +73 -0
- package/lib/extract.mjs +111 -0
- package/lib/git.mjs +202 -0
- package/lib/git.test.mjs +29 -0
- package/lib/json-payload.mjs +31 -0
- package/lib/json-payload.test.mjs +22 -0
- package/lib/output-name.mjs +56 -0
- package/lib/output-name.test.mjs +54 -0
- package/lib/pick-folder.mjs +4 -0
- package/lib/prompt.mjs +154 -0
- package/lib/remote-repo.mjs +182 -0
- package/lib/review.mjs +228 -0
- package/lib/review.test.mjs +160 -0
- package/lib/validate.mjs +221 -0
- package/lib/validate.test.mjs +85 -0
- package/lib/wait-output.mjs +104 -0
- package/package.json +2 -1
- package/web/build/client/_app/immutable/assets/2.Xnfq26gJ.css +1 -0
- package/web/build/client/_app/immutable/assets/2.Xnfq26gJ.css.br +0 -0
- package/web/build/client/_app/immutable/assets/2.Xnfq26gJ.css.gz +0 -0
- package/web/build/client/_app/immutable/chunks/{C5moC2DW.js → BaYDuTXu.js} +36 -29
- package/web/build/client/_app/immutable/chunks/BaYDuTXu.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/BaYDuTXu.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/CAnYe5db.js +1 -0
- package/web/build/client/_app/immutable/chunks/CAnYe5db.js.br +1 -0
- package/web/build/client/_app/immutable/chunks/CAnYe5db.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/Gya4IU1I.js +2 -0
- package/web/build/client/_app/immutable/chunks/Gya4IU1I.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/Gya4IU1I.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/{app.Dei8qxo7.js → app.e96uPVtl.js} +2 -2
- package/web/build/client/_app/immutable/entry/app.e96uPVtl.js.br +0 -0
- package/web/build/client/_app/immutable/entry/app.e96uPVtl.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/start.D-D-7QPy.js +1 -0
- package/web/build/client/_app/immutable/entry/start.D-D-7QPy.js.br +0 -0
- package/web/build/client/_app/immutable/entry/start.D-D-7QPy.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/1.V-g5-sfL.js +1 -0
- package/web/build/client/_app/immutable/nodes/1.V-g5-sfL.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/1.V-g5-sfL.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/2.lcRePHZ5.js +66 -0
- package/web/build/client/_app/immutable/nodes/2.lcRePHZ5.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/2.lcRePHZ5.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/3.GZPWeD9I.js +170 -0
- package/web/build/client/_app/immutable/nodes/3.GZPWeD9I.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/3.GZPWeD9I.js.gz +0 -0
- package/web/build/client/_app/version.json +1 -1
- package/web/build/client/_app/version.json.br +0 -0
- package/web/build/client/_app/version.json.gz +0 -0
- package/web/build/handler.js +3 -3
- package/web/build/index.js +3 -3
- package/web/build/server/chunks/1-DCKScL3A.js +7 -0
- package/web/build/server/chunks/1-DCKScL3A.js.map +1 -0
- package/web/build/server/chunks/2-BpBQQ02B.js +7 -0
- package/web/build/server/chunks/2-BpBQQ02B.js.map +1 -0
- package/web/build/server/chunks/{3-Zc5_qwgV.js → 3-rGkcNCVz.js} +2 -2
- package/web/build/server/chunks/{3-Zc5_qwgV.js.map → 3-rGkcNCVz.js.map} +1 -1
- package/web/build/server/chunks/{handler-BmuqxCCf.js → handler-BaT-QavL.js} +3 -3
- package/web/build/server/chunks/{handler-BmuqxCCf.js.map → handler-BaT-QavL.js.map} +1 -1
- package/web/build/server/chunks/{index.js-41EcOndB.js → index.js-C-K0YDBJ.js} +150 -150
- package/web/build/server/chunks/index.js-C-K0YDBJ.js.map +1 -0
- package/web/build/server/chunks/{manifest.js-KnmnjP9i.js → manifest.js-CDz-Fem4.js} +6 -6
- package/web/build/server/chunks/{manifest.js-KnmnjP9i.js.map → manifest.js-CDz-Fem4.js.map} +1 -1
- package/web/build/server/chunks/{remote-1g4ayag-BgUNMUS8.js → remote-1g4ayag-DXdFe1BG.js} +645 -146
- package/web/build/server/chunks/remote-1g4ayag-DXdFe1BG.js.map +1 -0
- package/web/build/client/_app/immutable/assets/2.Du8E-7-D.css +0 -1
- package/web/build/client/_app/immutable/assets/2.Du8E-7-D.css.br +0 -0
- package/web/build/client/_app/immutable/assets/2.Du8E-7-D.css.gz +0 -0
- package/web/build/client/_app/immutable/chunks/C5moC2DW.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/C5moC2DW.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/WxPrACSs.js +0 -2
- package/web/build/client/_app/immutable/chunks/WxPrACSs.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/WxPrACSs.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/app.Dei8qxo7.js.br +0 -0
- package/web/build/client/_app/immutable/entry/app.Dei8qxo7.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/start.Fzxrn2l4.js +0 -1
- package/web/build/client/_app/immutable/entry/start.Fzxrn2l4.js.br +0 -1
- package/web/build/client/_app/immutable/entry/start.Fzxrn2l4.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/1.Cn_iktkl.js +0 -1
- package/web/build/client/_app/immutable/nodes/1.Cn_iktkl.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/1.Cn_iktkl.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/2.vvYFHTy-.js +0 -66
- package/web/build/client/_app/immutable/nodes/2.vvYFHTy-.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/2.vvYFHTy-.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/3.CeJ7dEsh.js +0 -170
- package/web/build/client/_app/immutable/nodes/3.CeJ7dEsh.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/3.CeJ7dEsh.js.gz +0 -0
- package/web/build/server/chunks/1-Bjgeu1Iz.js +0 -7
- package/web/build/server/chunks/1-Bjgeu1Iz.js.map +0 -1
- package/web/build/server/chunks/2-BvMZU9fP.js +0 -7
- package/web/build/server/chunks/2-BvMZU9fP.js.map +0 -1
- package/web/build/server/chunks/index.js-41EcOndB.js.map +0 -1
- package/web/build/server/chunks/remote-1g4ayag-BgUNMUS8.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { e as error, g as get_request_store, b as stringify_remote_arg, M as MUTATIVE_METHODS, w as with_request_store, c as create_remote_key, n as noop, H as HttpError, S as SvelteKitError, h as handle_error_and_jsonify, _ as __exportAll, i as init_remote_functions } from './index.js-
|
|
2
|
-
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
3
|
-
import { join, resolve } from 'node:path';
|
|
1
|
+
import { e as error, g as get_request_store, b as stringify_remote_arg, M as MUTATIVE_METHODS, w as with_request_store, c as create_remote_key, n as noop, H as HttpError, S as SvelteKitError, h as handle_error_and_jsonify, _ as __exportAll, i as init_remote_functions } from './index.js-C-K0YDBJ.js';
|
|
4
2
|
import { execFileSync, spawnSync } from 'node:child_process';
|
|
3
|
+
import { existsSync, readFileSync, statSync, readdirSync, mkdtempSync, rmSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
4
|
+
import { join, basename, resolve } from 'node:path';
|
|
5
5
|
import { createHash } from 'node:crypto';
|
|
6
6
|
import { tmpdir, platform } from 'node:os';
|
|
7
7
|
import process$1 from 'node:process';
|
|
@@ -957,6 +957,230 @@ Object.defineProperty(query, "live", {
|
|
|
957
957
|
enumerable: true
|
|
958
958
|
});
|
|
959
959
|
|
|
960
|
+
//#region ../lib/git.mjs
|
|
961
|
+
function assertGitRepo(repo) {
|
|
962
|
+
const raw = String(repo || "").trim();
|
|
963
|
+
if (!raw) throw new Error("Indicá la ruta absoluta de un repositorio git.");
|
|
964
|
+
if (!/[\\/]/.test(raw) && !/^[A-Za-z]:/.test(raw)) throw new Error(`"${raw}" no es una ruta. Pegá la ruta absoluta del repo (ej. C:\\tienda-argenta\\tienda-argenta-2).`);
|
|
965
|
+
const resolved = resolve(raw);
|
|
966
|
+
if (!resolved || resolved === ".") throw new Error("Indicá la ruta absoluta de un repositorio git.");
|
|
967
|
+
if (!existsSync(resolved)) throw new Error(`No existe la carpeta: ${resolved}`);
|
|
968
|
+
if (!existsSync(join(resolved, ".git"))) throw new Error(`No es un repositorio git (falta .git): ${resolved}`);
|
|
969
|
+
return resolved;
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Si `cwd` está dentro de un repo git, devuelve la raíz absoluta; si no, null.
|
|
973
|
+
* Acepta subcarpetas (usa `git rev-parse --show-toplevel`).
|
|
974
|
+
*/
|
|
975
|
+
function tryResolveGitRoot(cwd) {
|
|
976
|
+
const raw = String(cwd || "").trim();
|
|
977
|
+
if (!raw) return null;
|
|
978
|
+
const dir = resolve(raw);
|
|
979
|
+
if (!existsSync(dir)) return null;
|
|
980
|
+
try {
|
|
981
|
+
const root = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
982
|
+
cwd: dir,
|
|
983
|
+
encoding: "utf8",
|
|
984
|
+
windowsHide: true,
|
|
985
|
+
stdio: [
|
|
986
|
+
"ignore",
|
|
987
|
+
"pipe",
|
|
988
|
+
"ignore"
|
|
989
|
+
]
|
|
990
|
+
}).trim();
|
|
991
|
+
return root ? resolve(root) : null;
|
|
992
|
+
} catch {
|
|
993
|
+
return null;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
function git(repo, args, { maxBuffer = 64 * 1024 * 1024 } = {}) {
|
|
997
|
+
return execFileSync("git", args, {
|
|
998
|
+
cwd: repo,
|
|
999
|
+
encoding: "utf8",
|
|
1000
|
+
maxBuffer,
|
|
1001
|
+
windowsHide: true
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
function resolveRev(repo, ref) {
|
|
1005
|
+
return git(repo, ["rev-parse", ref]).trim();
|
|
1006
|
+
}
|
|
1007
|
+
function branchTips(repo, branch, base) {
|
|
1008
|
+
return {
|
|
1009
|
+
branchSha: resolveRev(repo, branch),
|
|
1010
|
+
baseSha: resolveRev(repo, base)
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
function fingerprint(branchSha, baseSha) {
|
|
1014
|
+
const a = shortSha(branchSha);
|
|
1015
|
+
const b = shortSha(baseSha);
|
|
1016
|
+
if (!a || !b) return null;
|
|
1017
|
+
return `${a}-${b}`;
|
|
1018
|
+
}
|
|
1019
|
+
function shortSha(sha, n = 10) {
|
|
1020
|
+
const hex = String(sha || "").toLowerCase().replace(/[^a-f0-9]/g, "");
|
|
1021
|
+
return hex.length >= 7 ? hex.slice(0, n) : "";
|
|
1022
|
+
}
|
|
1023
|
+
function commitLog(repo, mergeBase, branch, limit = 25) {
|
|
1024
|
+
try {
|
|
1025
|
+
return git(repo, [
|
|
1026
|
+
"log",
|
|
1027
|
+
"--oneline",
|
|
1028
|
+
"--no-merges",
|
|
1029
|
+
"-n",
|
|
1030
|
+
String(limit),
|
|
1031
|
+
`${mergeBase}..${branch}`
|
|
1032
|
+
]).trim().split("\n").filter(Boolean);
|
|
1033
|
+
} catch {
|
|
1034
|
+
return [];
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
var FILE_LINE_CACHE = /* @__PURE__ */ new Map();
|
|
1038
|
+
var FILE_LINE_CACHE_MAX = 48;
|
|
1039
|
+
function rememberFileLines(key, lines) {
|
|
1040
|
+
if (FILE_LINE_CACHE.has(key)) FILE_LINE_CACHE.delete(key);
|
|
1041
|
+
FILE_LINE_CACHE.set(key, lines);
|
|
1042
|
+
while (FILE_LINE_CACHE.size > FILE_LINE_CACHE_MAX) {
|
|
1043
|
+
const oldest = FILE_LINE_CACHE.keys().next().value;
|
|
1044
|
+
FILE_LINE_CACHE.delete(oldest);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
function showFileLines(repo, rev, path) {
|
|
1048
|
+
const resolved = assertGitRepo(repo);
|
|
1049
|
+
const safeRev = String(rev || "").trim();
|
|
1050
|
+
const safePath = String(path || "").replace(/\\/g, "/").trim();
|
|
1051
|
+
if (!safeRev || !/^[A-Za-z0-9._/\-]+$/.test(safeRev)) throw new Error("Rev inválido.");
|
|
1052
|
+
if (!safePath || safePath.startsWith("/") || safePath.includes("..") || safePath.includes("\0")) throw new Error("Ruta inválida.");
|
|
1053
|
+
const key = `${resolved}\0${safeRev}\0${safePath}`;
|
|
1054
|
+
if (FILE_LINE_CACHE.has(key)) return FILE_LINE_CACHE.get(key);
|
|
1055
|
+
let text;
|
|
1056
|
+
try {
|
|
1057
|
+
text = git(resolved, ["show", `${safeRev}:${safePath}`]);
|
|
1058
|
+
} catch {
|
|
1059
|
+
rememberFileLines(key, null);
|
|
1060
|
+
return null;
|
|
1061
|
+
}
|
|
1062
|
+
if (text.includes("\0")) {
|
|
1063
|
+
rememberFileLines(key, null);
|
|
1064
|
+
return null;
|
|
1065
|
+
}
|
|
1066
|
+
const raw = text.replace(/\r\n/g, "\n");
|
|
1067
|
+
const lines = raw.endsWith("\n") ? raw.slice(0, -1).split("\n") : raw.split("\n");
|
|
1068
|
+
rememberFileLines(key, lines);
|
|
1069
|
+
return lines;
|
|
1070
|
+
}
|
|
1071
|
+
function listBranches(repo) {
|
|
1072
|
+
const names = git(repo, [
|
|
1073
|
+
"for-each-ref",
|
|
1074
|
+
"--sort=-committerdate",
|
|
1075
|
+
"--format=%(refname:short)",
|
|
1076
|
+
"refs/heads",
|
|
1077
|
+
"refs/remotes"
|
|
1078
|
+
]).split("\n").map((s) => s.trim()).filter(Boolean).map((name) => name.replace(/^origin\//, "")).filter((name) => name !== "HEAD" && !name.endsWith("/HEAD"));
|
|
1079
|
+
const unique = [...new Set(names)];
|
|
1080
|
+
const current = git(repo, [
|
|
1081
|
+
"rev-parse",
|
|
1082
|
+
"--abbrev-ref",
|
|
1083
|
+
"HEAD"
|
|
1084
|
+
]).trim();
|
|
1085
|
+
const bases = unique.filter((n) => n === "develop" || n === "main" || n === "master");
|
|
1086
|
+
return {
|
|
1087
|
+
branches: unique,
|
|
1088
|
+
current,
|
|
1089
|
+
defaultBase: bases.includes("develop") ? "develop" : bases.includes("main") ? "main" : bases[0] || "develop"
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
/** URL del remote (origin, o el primero disponible). Vacío si no hay. */
|
|
1093
|
+
function getRemoteUrl(repo, preferred = "origin") {
|
|
1094
|
+
try {
|
|
1095
|
+
const url = git(repo, [
|
|
1096
|
+
"remote",
|
|
1097
|
+
"get-url",
|
|
1098
|
+
preferred
|
|
1099
|
+
]).trim();
|
|
1100
|
+
if (url) return url;
|
|
1101
|
+
} catch {}
|
|
1102
|
+
try {
|
|
1103
|
+
const remotes = git(repo, ["remote"]).split("\n").map((s) => s.trim()).filter(Boolean);
|
|
1104
|
+
if (!remotes.length) return "";
|
|
1105
|
+
return git(repo, [
|
|
1106
|
+
"remote",
|
|
1107
|
+
"get-url",
|
|
1108
|
+
remotes[0]
|
|
1109
|
+
]).trim();
|
|
1110
|
+
} catch {
|
|
1111
|
+
return "";
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
//#endregion
|
|
1115
|
+
//#region ../lib/output-name.mjs
|
|
1116
|
+
/**
|
|
1117
|
+
* Nombre del JSON que escribe el agente.
|
|
1118
|
+
* Preferido: tips cortos + stamp. Legacy sin tips sigue siendo válido al importar.
|
|
1119
|
+
*/
|
|
1120
|
+
/** Prefijo del formato actual (con tips). */
|
|
1121
|
+
var OUTPUT_PREFIX = "diff-report";
|
|
1122
|
+
/** Prefijo legacy (sin tips). */
|
|
1123
|
+
var OUTPUT_PREFIX_LEGACY = "diff-review-output";
|
|
1124
|
+
/**
|
|
1125
|
+
* Válidos:
|
|
1126
|
+
* - diff-review-output.json
|
|
1127
|
+
* - diff-review-output-YYYYMMDD-HHmmss.json
|
|
1128
|
+
* - diff-report_{branchTip}-{baseTip}_YYYYMMDD-HHmmss.json
|
|
1129
|
+
*/
|
|
1130
|
+
var OUTPUT_FILENAME_RE = /^(?:diff-review-output(?:-\d{8}-\d{6})?|diff-report_[0-9a-f]{7,12}-[0-9a-f]{7,12}_\d{8}-\d{6})\.json$/i;
|
|
1131
|
+
/**
|
|
1132
|
+
* @param {Date} [when]
|
|
1133
|
+
* @returns {string} ej. 20260915-143045
|
|
1134
|
+
*/
|
|
1135
|
+
function formatOutputStamp(when = /* @__PURE__ */ new Date()) {
|
|
1136
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
1137
|
+
return `${when.getFullYear()}${pad(when.getMonth() + 1)}${pad(when.getDate())}-${pad(when.getHours())}${pad(when.getMinutes())}${pad(when.getSeconds())}`;
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* @param {{ branchSha?: string, baseSha?: string, stamp?: string, when?: Date } | Date} [input]
|
|
1141
|
+
* @returns {string}
|
|
1142
|
+
* con tips: diff-report_aaaaaaaaaa-bbbbbbbbbb_20260915-143045.json
|
|
1143
|
+
* sin tips: diff-review-output-20260915-143045.json
|
|
1144
|
+
*/
|
|
1145
|
+
function makeOutputFilename(input = {}) {
|
|
1146
|
+
const opts = input instanceof Date ? { when: input } : input || {};
|
|
1147
|
+
const stamp = opts.stamp || formatOutputStamp(opts.when ?? /* @__PURE__ */ new Date());
|
|
1148
|
+
const fp = fingerprint(opts.branchSha, opts.baseSha);
|
|
1149
|
+
if (fp) return `${OUTPUT_PREFIX}_${fp}_${stamp}.json`;
|
|
1150
|
+
return `${OUTPUT_PREFIX_LEGACY}-${stamp}.json`;
|
|
1151
|
+
}
|
|
1152
|
+
function isOutputFilename(name) {
|
|
1153
|
+
return OUTPUT_FILENAME_RE.test(String(name || "").trim());
|
|
1154
|
+
}
|
|
1155
|
+
//#endregion
|
|
1156
|
+
//#region ../lib/json-payload.mjs
|
|
1157
|
+
function tryParseJson(s) {
|
|
1158
|
+
try {
|
|
1159
|
+
return JSON.parse(s);
|
|
1160
|
+
} catch {
|
|
1161
|
+
return null;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
/** Extrae un objeto JSON del texto del modelo (fences, prosa alrededor, envelope). */
|
|
1165
|
+
function decodePayload(text) {
|
|
1166
|
+
const raw = String(text || "").trim();
|
|
1167
|
+
if (!raw) throw new Error("Respuesta vacía.");
|
|
1168
|
+
const fence = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
1169
|
+
if (fence) {
|
|
1170
|
+
const parsed = tryParseJson(fence[1].trim());
|
|
1171
|
+
if (parsed && typeof parsed === "object") return parsed;
|
|
1172
|
+
}
|
|
1173
|
+
const direct = tryParseJson(raw);
|
|
1174
|
+
if (direct && typeof direct === "object") return direct;
|
|
1175
|
+
const start = raw.indexOf("{");
|
|
1176
|
+
const end = raw.lastIndexOf("}");
|
|
1177
|
+
if (start >= 0 && end > start) {
|
|
1178
|
+
const parsed = tryParseJson(raw.slice(start, end + 1));
|
|
1179
|
+
if (parsed && typeof parsed === "object") return parsed;
|
|
1180
|
+
}
|
|
1181
|
+
throw new Error("No se pudo interpretar la respuesta como JSON.");
|
|
1182
|
+
}
|
|
1183
|
+
//#endregion
|
|
960
1184
|
//#region ../lib/config.mjs
|
|
961
1185
|
var DEFAULT_IGNORE = [
|
|
962
1186
|
"**/package-lock.json",
|
|
@@ -1112,150 +1336,14 @@ function capDiff(diff, max = STORE_DIFF_MAX) {
|
|
|
1112
1336
|
if (text.length <= max) return {
|
|
1113
1337
|
diff: text,
|
|
1114
1338
|
diffChars: text.length,
|
|
1115
|
-
truncated: false
|
|
1116
|
-
};
|
|
1117
|
-
return {
|
|
1118
|
-
diff: `${text.slice(0, max)}\n\n… [diff recortado: se guardan ${max} de ${text.length} caracteres]`,
|
|
1119
|
-
diffChars: text.length,
|
|
1120
|
-
truncated: true
|
|
1121
|
-
};
|
|
1122
|
-
}
|
|
1123
|
-
//#endregion
|
|
1124
|
-
//#region ../lib/git.mjs
|
|
1125
|
-
function assertGitRepo(repo) {
|
|
1126
|
-
const raw = String(repo || "").trim();
|
|
1127
|
-
if (!raw) throw new Error("Indicá la ruta absoluta de un repositorio git.");
|
|
1128
|
-
if (!/[\\/]/.test(raw) && !/^[A-Za-z]:/.test(raw)) throw new Error(`"${raw}" no es una ruta. Pegá la ruta absoluta del repo (ej. C:\\tienda-argenta\\tienda-argenta-2).`);
|
|
1129
|
-
const resolved = resolve(raw);
|
|
1130
|
-
if (!resolved || resolved === ".") throw new Error("Indicá la ruta absoluta de un repositorio git.");
|
|
1131
|
-
if (!existsSync(resolved)) throw new Error(`No existe la carpeta: ${resolved}`);
|
|
1132
|
-
if (!existsSync(join(resolved, ".git"))) throw new Error(`No es un repositorio git (falta .git): ${resolved}`);
|
|
1133
|
-
return resolved;
|
|
1134
|
-
}
|
|
1135
|
-
/**
|
|
1136
|
-
* Si `cwd` está dentro de un repo git, devuelve la raíz absoluta; si no, null.
|
|
1137
|
-
* Acepta subcarpetas (usa `git rev-parse --show-toplevel`).
|
|
1138
|
-
*/
|
|
1139
|
-
function tryResolveGitRoot(cwd) {
|
|
1140
|
-
const raw = String(cwd || "").trim();
|
|
1141
|
-
if (!raw) return null;
|
|
1142
|
-
const dir = resolve(raw);
|
|
1143
|
-
if (!existsSync(dir)) return null;
|
|
1144
|
-
try {
|
|
1145
|
-
const root = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
1146
|
-
cwd: dir,
|
|
1147
|
-
encoding: "utf8",
|
|
1148
|
-
windowsHide: true,
|
|
1149
|
-
stdio: [
|
|
1150
|
-
"ignore",
|
|
1151
|
-
"pipe",
|
|
1152
|
-
"ignore"
|
|
1153
|
-
]
|
|
1154
|
-
}).trim();
|
|
1155
|
-
return root ? resolve(root) : null;
|
|
1156
|
-
} catch {
|
|
1157
|
-
return null;
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
function git(repo, args, { maxBuffer = 64 * 1024 * 1024 } = {}) {
|
|
1161
|
-
return execFileSync("git", args, {
|
|
1162
|
-
cwd: repo,
|
|
1163
|
-
encoding: "utf8",
|
|
1164
|
-
maxBuffer,
|
|
1165
|
-
windowsHide: true
|
|
1166
|
-
});
|
|
1167
|
-
}
|
|
1168
|
-
function commitLog(repo, mergeBase, branch, limit = 25) {
|
|
1169
|
-
try {
|
|
1170
|
-
return git(repo, [
|
|
1171
|
-
"log",
|
|
1172
|
-
"--oneline",
|
|
1173
|
-
"--no-merges",
|
|
1174
|
-
"-n",
|
|
1175
|
-
String(limit),
|
|
1176
|
-
`${mergeBase}..${branch}`
|
|
1177
|
-
]).trim().split("\n").filter(Boolean);
|
|
1178
|
-
} catch {
|
|
1179
|
-
return [];
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
var FILE_LINE_CACHE = /* @__PURE__ */ new Map();
|
|
1183
|
-
var FILE_LINE_CACHE_MAX = 48;
|
|
1184
|
-
function rememberFileLines(key, lines) {
|
|
1185
|
-
if (FILE_LINE_CACHE.has(key)) FILE_LINE_CACHE.delete(key);
|
|
1186
|
-
FILE_LINE_CACHE.set(key, lines);
|
|
1187
|
-
while (FILE_LINE_CACHE.size > FILE_LINE_CACHE_MAX) {
|
|
1188
|
-
const oldest = FILE_LINE_CACHE.keys().next().value;
|
|
1189
|
-
FILE_LINE_CACHE.delete(oldest);
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
function showFileLines(repo, rev, path) {
|
|
1193
|
-
const resolved = assertGitRepo(repo);
|
|
1194
|
-
const safeRev = String(rev || "").trim();
|
|
1195
|
-
const safePath = String(path || "").replace(/\\/g, "/").trim();
|
|
1196
|
-
if (!safeRev || !/^[A-Za-z0-9._/\-]+$/.test(safeRev)) throw new Error("Rev inválido.");
|
|
1197
|
-
if (!safePath || safePath.startsWith("/") || safePath.includes("..") || safePath.includes("\0")) throw new Error("Ruta inválida.");
|
|
1198
|
-
const key = `${resolved}\0${safeRev}\0${safePath}`;
|
|
1199
|
-
if (FILE_LINE_CACHE.has(key)) return FILE_LINE_CACHE.get(key);
|
|
1200
|
-
let text;
|
|
1201
|
-
try {
|
|
1202
|
-
text = git(resolved, ["show", `${safeRev}:${safePath}`]);
|
|
1203
|
-
} catch {
|
|
1204
|
-
rememberFileLines(key, null);
|
|
1205
|
-
return null;
|
|
1206
|
-
}
|
|
1207
|
-
if (text.includes("\0")) {
|
|
1208
|
-
rememberFileLines(key, null);
|
|
1209
|
-
return null;
|
|
1210
|
-
}
|
|
1211
|
-
const raw = text.replace(/\r\n/g, "\n");
|
|
1212
|
-
const lines = raw.endsWith("\n") ? raw.slice(0, -1).split("\n") : raw.split("\n");
|
|
1213
|
-
rememberFileLines(key, lines);
|
|
1214
|
-
return lines;
|
|
1215
|
-
}
|
|
1216
|
-
function listBranches(repo) {
|
|
1217
|
-
const names = git(repo, [
|
|
1218
|
-
"for-each-ref",
|
|
1219
|
-
"--sort=-committerdate",
|
|
1220
|
-
"--format=%(refname:short)",
|
|
1221
|
-
"refs/heads",
|
|
1222
|
-
"refs/remotes"
|
|
1223
|
-
]).split("\n").map((s) => s.trim()).filter(Boolean).map((name) => name.replace(/^origin\//, "")).filter((name) => name !== "HEAD" && !name.endsWith("/HEAD"));
|
|
1224
|
-
const unique = [...new Set(names)];
|
|
1225
|
-
const current = git(repo, [
|
|
1226
|
-
"rev-parse",
|
|
1227
|
-
"--abbrev-ref",
|
|
1228
|
-
"HEAD"
|
|
1229
|
-
]).trim();
|
|
1230
|
-
const bases = unique.filter((n) => n === "develop" || n === "main" || n === "master");
|
|
1339
|
+
truncated: false
|
|
1340
|
+
};
|
|
1231
1341
|
return {
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1342
|
+
diff: `${text.slice(0, max)}\n\n… [diff recortado: se guardan ${max} de ${text.length} caracteres]`,
|
|
1343
|
+
diffChars: text.length,
|
|
1344
|
+
truncated: true
|
|
1235
1345
|
};
|
|
1236
1346
|
}
|
|
1237
|
-
/** URL del remote (origin, o el primero disponible). Vacío si no hay. */
|
|
1238
|
-
function getRemoteUrl(repo, preferred = "origin") {
|
|
1239
|
-
try {
|
|
1240
|
-
const url = git(repo, [
|
|
1241
|
-
"remote",
|
|
1242
|
-
"get-url",
|
|
1243
|
-
preferred
|
|
1244
|
-
]).trim();
|
|
1245
|
-
if (url) return url;
|
|
1246
|
-
} catch {}
|
|
1247
|
-
try {
|
|
1248
|
-
const remotes = git(repo, ["remote"]).split("\n").map((s) => s.trim()).filter(Boolean);
|
|
1249
|
-
if (!remotes.length) return "";
|
|
1250
|
-
return git(repo, [
|
|
1251
|
-
"remote",
|
|
1252
|
-
"get-url",
|
|
1253
|
-
remotes[0]
|
|
1254
|
-
]).trim();
|
|
1255
|
-
} catch {
|
|
1256
|
-
return "";
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
1347
|
//#endregion
|
|
1260
1348
|
//#region ../lib/extract.mjs
|
|
1261
1349
|
function extractBranchDiff({ repo, branch, base = "develop" }) {
|
|
@@ -1786,6 +1874,228 @@ function buildReview({ skeleton, payload, notes = [] }) {
|
|
|
1786
1874
|
};
|
|
1787
1875
|
}
|
|
1788
1876
|
//#endregion
|
|
1877
|
+
//#region src/lib/server/pick-file.ts
|
|
1878
|
+
/**
|
|
1879
|
+
* Diálogo nativo para elegir un JSON. Si `defaultDir` existe, abre ahí.
|
|
1880
|
+
*/
|
|
1881
|
+
async function pickJsonFile(defaultDir) {
|
|
1882
|
+
const startDir = defaultDir && existsSync(defaultDir) ? defaultDir : "";
|
|
1883
|
+
if (platform() === "win32") try {
|
|
1884
|
+
return pickWindowsJson(startDir);
|
|
1885
|
+
} catch (err) {
|
|
1886
|
+
console.warn("[diff-review] picker JSON Windows falló, probando rfd/fallback:", err instanceof Error ? err.message : err);
|
|
1887
|
+
}
|
|
1888
|
+
try {
|
|
1889
|
+
return pickJsonWithRfd(startDir);
|
|
1890
|
+
} catch (err) {
|
|
1891
|
+
console.warn("[diff-review] @bindrs/rfd JSON no disponible, usando fallback:", err instanceof Error ? err.message : err);
|
|
1892
|
+
return pickJsonFallback(startDir);
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
function resolveNodeBin$1() {
|
|
1896
|
+
if (process$1.execPath && /node(\.exe)?$/i.test(process$1.execPath)) return process$1.execPath;
|
|
1897
|
+
return "node";
|
|
1898
|
+
}
|
|
1899
|
+
function writeUtf8Bom$1(path, content) {
|
|
1900
|
+
writeFileSync(path, `\uFEFF${content}`, "utf8");
|
|
1901
|
+
}
|
|
1902
|
+
function readPicked(path) {
|
|
1903
|
+
if (!path || !existsSync(path)) return null;
|
|
1904
|
+
return {
|
|
1905
|
+
path,
|
|
1906
|
+
text: readFileSync(path, "utf8")
|
|
1907
|
+
};
|
|
1908
|
+
}
|
|
1909
|
+
/** OpenFileDialog con InitialDirectory (DPI: mejor esfuerzo). */
|
|
1910
|
+
function pickWindowsJson(startDir) {
|
|
1911
|
+
const dir = mkdtempSync(join(tmpdir(), "diff-review-pick-json-"));
|
|
1912
|
+
const script = join(dir, "pick.ps1");
|
|
1913
|
+
const outFile = join(dir, "path.txt");
|
|
1914
|
+
try {
|
|
1915
|
+
writeUtf8Bom$1(script, `
|
|
1916
|
+
$ErrorActionPreference = 'Stop'
|
|
1917
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
1918
|
+
try {
|
|
1919
|
+
Add-Type @'
|
|
1920
|
+
using System;
|
|
1921
|
+
using System.Runtime.InteropServices;
|
|
1922
|
+
public static class DiffReviewDpi {
|
|
1923
|
+
[DllImport("user32.dll")] public static extern IntPtr SetThreadDpiAwarenessContext(IntPtr dpiContext);
|
|
1924
|
+
public static readonly IntPtr PerMonitorV2 = unchecked((IntPtr)(-4));
|
|
1925
|
+
public static void Enable() {
|
|
1926
|
+
try { SetThreadDpiAwarenessContext(PerMonitorV2); } catch { }
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
'@
|
|
1930
|
+
[DiffReviewDpi]::Enable()
|
|
1931
|
+
} catch {}
|
|
1932
|
+
|
|
1933
|
+
$dlg = New-Object System.Windows.Forms.OpenFileDialog
|
|
1934
|
+
$dlg.Title = 'Elegi el JSON de review'
|
|
1935
|
+
$dlg.Filter = 'JSON (*.json)|*.json|Todos (*.*)|*.*'
|
|
1936
|
+
$dlg.FilterIndex = 1
|
|
1937
|
+
$dlg.CheckFileExists = $true
|
|
1938
|
+
$dlg.Multiselect = $false
|
|
1939
|
+
$dir = ${JSON.stringify(startDir)}
|
|
1940
|
+
if ($dir -and (Test-Path -LiteralPath $dir)) { $dlg.InitialDirectory = $dir }
|
|
1941
|
+
if ($dlg.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) { exit 2 }
|
|
1942
|
+
[System.IO.File]::WriteAllText(${JSON.stringify(outFile)}, $dlg.FileName)
|
|
1943
|
+
exit 0
|
|
1944
|
+
`.trim() + "\r\n");
|
|
1945
|
+
const result = spawnSync("powershell.exe", [
|
|
1946
|
+
"-STA",
|
|
1947
|
+
"-NoProfile",
|
|
1948
|
+
"-ExecutionPolicy",
|
|
1949
|
+
"Bypass",
|
|
1950
|
+
"-File",
|
|
1951
|
+
script
|
|
1952
|
+
], {
|
|
1953
|
+
encoding: "utf8",
|
|
1954
|
+
windowsHide: false,
|
|
1955
|
+
stdio: [
|
|
1956
|
+
"ignore",
|
|
1957
|
+
"pipe",
|
|
1958
|
+
"pipe"
|
|
1959
|
+
],
|
|
1960
|
+
timeout: 600 * 1e3
|
|
1961
|
+
});
|
|
1962
|
+
if (result.status === 2) return null;
|
|
1963
|
+
if (result.status !== 0) {
|
|
1964
|
+
const err = (result.stderr || result.stdout || result.error?.message || `exit ${result.status}`).toString().trim();
|
|
1965
|
+
throw new Error(err || "powershell json picker fallo");
|
|
1966
|
+
}
|
|
1967
|
+
if (!existsSync(outFile)) throw new Error("powershell json picker no escribio la ruta");
|
|
1968
|
+
const picked = readPicked(readFileSync(outFile, "utf8").replace(/^\uFEFF/, "").trim());
|
|
1969
|
+
if (!picked) throw new Error("powershell json picker devolvio ruta vacia");
|
|
1970
|
+
return picked;
|
|
1971
|
+
} finally {
|
|
1972
|
+
try {
|
|
1973
|
+
rmSync(dir, {
|
|
1974
|
+
recursive: true,
|
|
1975
|
+
force: true
|
|
1976
|
+
});
|
|
1977
|
+
} catch {}
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
function pickJsonWithRfd(startDir) {
|
|
1981
|
+
const dir = mkdtempSync(join(tmpdir(), "diff-review-rfd-json-"));
|
|
1982
|
+
const worker = join(dir, "pick.mjs");
|
|
1983
|
+
const outFile = join(dir, "path.txt");
|
|
1984
|
+
try {
|
|
1985
|
+
writeFileSync(worker, `import { createRequire } from 'node:module';
|
|
1986
|
+
import { existsSync, writeFileSync } from 'node:fs';
|
|
1987
|
+
import { dirname, join } from 'node:path';
|
|
1988
|
+
|
|
1989
|
+
const require = createRequire(import.meta.url);
|
|
1990
|
+
const outFile = ${JSON.stringify(outFile)};
|
|
1991
|
+
const startDir = ${JSON.stringify(startDir)};
|
|
1992
|
+
|
|
1993
|
+
function findRfdIndex() {
|
|
1994
|
+
const seen = new Set();
|
|
1995
|
+
let dir = process.cwd();
|
|
1996
|
+
for (let i = 0; i < 16; i++) {
|
|
1997
|
+
if (seen.has(dir)) break;
|
|
1998
|
+
seen.add(dir);
|
|
1999
|
+
const indexJs = join(dir, 'node_modules', '@bindrs', 'rfd', 'index.js');
|
|
2000
|
+
if (existsSync(indexJs)) return indexJs;
|
|
2001
|
+
const parent = dirname(dir);
|
|
2002
|
+
if (parent === dir) break;
|
|
2003
|
+
dir = parent;
|
|
2004
|
+
}
|
|
2005
|
+
throw new Error('No se encontro @bindrs/rfd en node_modules');
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
const { AsyncFileDialog } = require(findRfdIndex());
|
|
2009
|
+
let dlg = new AsyncFileDialog()
|
|
2010
|
+
.setTitle('Elegi el JSON de review')
|
|
2011
|
+
.addFilter('JSON', ['json']);
|
|
2012
|
+
if (startDir) dlg = dlg.setDirectory(startDir);
|
|
2013
|
+
const handle = await dlg.pickFile();
|
|
2014
|
+
if (!handle) process.exit(2);
|
|
2015
|
+
writeFileSync(outFile, handle.path(), 'utf8');
|
|
2016
|
+
`, "utf8");
|
|
2017
|
+
const result = spawnSync(resolveNodeBin$1(), [worker], {
|
|
2018
|
+
encoding: "utf8",
|
|
2019
|
+
windowsHide: false,
|
|
2020
|
+
stdio: [
|
|
2021
|
+
"ignore",
|
|
2022
|
+
"pipe",
|
|
2023
|
+
"pipe"
|
|
2024
|
+
],
|
|
2025
|
+
timeout: 600 * 1e3,
|
|
2026
|
+
cwd: process$1.cwd(),
|
|
2027
|
+
env: process$1.env
|
|
2028
|
+
});
|
|
2029
|
+
if (result.status === 2 || result.status === 1) return null;
|
|
2030
|
+
if (result.status !== 0) {
|
|
2031
|
+
const err = (result.stderr || result.stdout || result.error?.message || `exit ${result.status}`).toString().trim();
|
|
2032
|
+
throw new Error(err || "worker rfd json fallo");
|
|
2033
|
+
}
|
|
2034
|
+
if (!existsSync(outFile)) return null;
|
|
2035
|
+
return readPicked(readFileSync(outFile, "utf8").replace(/^\uFEFF/, "").trim());
|
|
2036
|
+
} finally {
|
|
2037
|
+
try {
|
|
2038
|
+
rmSync(dir, {
|
|
2039
|
+
recursive: true,
|
|
2040
|
+
force: true
|
|
2041
|
+
});
|
|
2042
|
+
} catch {}
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
function pickJsonFallback(startDir) {
|
|
2046
|
+
const os = platform();
|
|
2047
|
+
if (os === "darwin") return pickMacJson(startDir);
|
|
2048
|
+
if (os === "linux") return pickLinuxJson(startDir);
|
|
2049
|
+
return null;
|
|
2050
|
+
}
|
|
2051
|
+
function pickMacJson(startDir) {
|
|
2052
|
+
try {
|
|
2053
|
+
const out = execFileSync("osascript", startDir ? ["-e", `POSIX path of (choose file with prompt "Elegi el JSON de review" of type {"public.json","json"} default location POSIX file ${JSON.stringify(startDir + "/")})`] : ["-e", "POSIX path of (choose file with prompt \"Elegi el JSON de review\" of type {\"public.json\",\"json\"})"], {
|
|
2054
|
+
encoding: "utf8",
|
|
2055
|
+
timeout: 600 * 1e3
|
|
2056
|
+
});
|
|
2057
|
+
return readPicked(String(out || "").trim().replace(/\/$/, ""));
|
|
2058
|
+
} catch (err) {
|
|
2059
|
+
if (err.status === 1 || /User canceled|-128/i.test(String(err))) return null;
|
|
2060
|
+
throw new Error("No se pudo abrir el dialogo de archivo: " + (err instanceof Error ? err.message : String(err)));
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
function pickLinuxJson(startDir) {
|
|
2064
|
+
try {
|
|
2065
|
+
const args = [
|
|
2066
|
+
"--file-selection",
|
|
2067
|
+
"--title=Elegi el JSON de review",
|
|
2068
|
+
"--file-filter=*.json"
|
|
2069
|
+
];
|
|
2070
|
+
if (startDir) args.push(`--filename=${join(startDir, "/")}`);
|
|
2071
|
+
const out = execFileSync("zenity", args, {
|
|
2072
|
+
encoding: "utf8",
|
|
2073
|
+
timeout: 600 * 1e3
|
|
2074
|
+
});
|
|
2075
|
+
return readPicked(String(out || "").trim());
|
|
2076
|
+
} catch (zenityErr) {
|
|
2077
|
+
if (zenityErr.status === 1) return null;
|
|
2078
|
+
try {
|
|
2079
|
+
const out = execFileSync("kdialog", startDir ? [
|
|
2080
|
+
"--getopenfilename",
|
|
2081
|
+
startDir,
|
|
2082
|
+
"*.json"
|
|
2083
|
+
] : [
|
|
2084
|
+
"--getopenfilename",
|
|
2085
|
+
".",
|
|
2086
|
+
"*.json"
|
|
2087
|
+
], {
|
|
2088
|
+
encoding: "utf8",
|
|
2089
|
+
timeout: 600 * 1e3
|
|
2090
|
+
});
|
|
2091
|
+
return readPicked(String(out || "").trim());
|
|
2092
|
+
} catch (kErr) {
|
|
2093
|
+
if (kErr.status === 1) return null;
|
|
2094
|
+
throw new Error("No hay dialogo de archivos disponible. Instala zenity o kdialog, o arrastra el JSON.");
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
//#endregion
|
|
1789
2099
|
//#region src/lib/server/pick-folder.ts
|
|
1790
2100
|
/**
|
|
1791
2101
|
* Diálogo nativo de carpetas.
|
|
@@ -2164,9 +2474,140 @@ function loadLaunchRepoInfo() {
|
|
|
2164
2474
|
return null;
|
|
2165
2475
|
}
|
|
2166
2476
|
}
|
|
2477
|
+
/**
|
|
2478
|
+
* JSON dejado por el CLI (env DIFF_REVIEW_IMPORT_FILE). Se consume una sola vez.
|
|
2479
|
+
*/
|
|
2480
|
+
function takePendingImport() {
|
|
2481
|
+
const raw = String(process.env.DIFF_REVIEW_IMPORT_FILE || "").trim();
|
|
2482
|
+
if (!raw) return null;
|
|
2483
|
+
delete process.env.DIFF_REVIEW_IMPORT_FILE;
|
|
2484
|
+
const filePath = resolve(raw);
|
|
2485
|
+
const name = basename(filePath);
|
|
2486
|
+
if (!isOutputFilename(name)) return null;
|
|
2487
|
+
if (!existsSync(filePath)) return null;
|
|
2488
|
+
try {
|
|
2489
|
+
return {
|
|
2490
|
+
filename: name,
|
|
2491
|
+
payload: decodePayload(readFileSync(filePath, "utf8"))
|
|
2492
|
+
};
|
|
2493
|
+
} catch {
|
|
2494
|
+
return null;
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
/**
|
|
2498
|
+
* Busca un JSON de review en la raíz del repo local.
|
|
2499
|
+
* Prefiere `preferredName` si existe; si no, el más reciente que matchee el patrón.
|
|
2500
|
+
*/
|
|
2501
|
+
function detectReviewOutput(repo, preferredName) {
|
|
2502
|
+
const raw = String(repo || "").trim();
|
|
2503
|
+
if (!raw || looksLikeGitUrl(raw)) return null;
|
|
2504
|
+
let root;
|
|
2505
|
+
try {
|
|
2506
|
+
root = assertGitRepo(raw);
|
|
2507
|
+
} catch {
|
|
2508
|
+
return null;
|
|
2509
|
+
}
|
|
2510
|
+
const preferred = String(preferredName || "").trim();
|
|
2511
|
+
if (preferred && isOutputFilename(preferred)) {
|
|
2512
|
+
const preferredPath = join(root, preferred);
|
|
2513
|
+
if (existsSync(preferredPath)) try {
|
|
2514
|
+
return {
|
|
2515
|
+
filename: preferred,
|
|
2516
|
+
path: preferredPath,
|
|
2517
|
+
mtimeMs: statSync(preferredPath).mtimeMs
|
|
2518
|
+
};
|
|
2519
|
+
} catch {}
|
|
2520
|
+
}
|
|
2521
|
+
let best = null;
|
|
2522
|
+
try {
|
|
2523
|
+
for (const name of readdirSync(root)) {
|
|
2524
|
+
if (!isOutputFilename(name)) continue;
|
|
2525
|
+
const full = join(root, name);
|
|
2526
|
+
try {
|
|
2527
|
+
const st = statSync(full);
|
|
2528
|
+
if (!st.isFile()) continue;
|
|
2529
|
+
if (!best || st.mtimeMs > best.mtimeMs) best = {
|
|
2530
|
+
filename: name,
|
|
2531
|
+
path: full,
|
|
2532
|
+
mtimeMs: st.mtimeMs
|
|
2533
|
+
};
|
|
2534
|
+
} catch {}
|
|
2535
|
+
}
|
|
2536
|
+
} catch {
|
|
2537
|
+
return null;
|
|
2538
|
+
}
|
|
2539
|
+
return best;
|
|
2540
|
+
}
|
|
2541
|
+
/** Lee y parsea un JSON de review en la raíz del repo (solo nombres válidos). */
|
|
2542
|
+
function readReviewOutput(repo, filename) {
|
|
2543
|
+
const raw = String(repo || "").trim();
|
|
2544
|
+
const name = basename(String(filename || "").trim());
|
|
2545
|
+
if (!raw || !name || !isOutputFilename(name) || looksLikeGitUrl(raw)) return null;
|
|
2546
|
+
let root;
|
|
2547
|
+
try {
|
|
2548
|
+
root = assertGitRepo(raw);
|
|
2549
|
+
} catch {
|
|
2550
|
+
return null;
|
|
2551
|
+
}
|
|
2552
|
+
const filePath = join(root, name);
|
|
2553
|
+
if (!existsSync(filePath)) return null;
|
|
2554
|
+
try {
|
|
2555
|
+
return {
|
|
2556
|
+
filename: name,
|
|
2557
|
+
payload: decodePayload(readFileSync(filePath, "utf8"))
|
|
2558
|
+
};
|
|
2559
|
+
} catch {
|
|
2560
|
+
return null;
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2167
2563
|
async function pickLocalFolder$1() {
|
|
2168
2564
|
return pickFolder();
|
|
2169
2565
|
}
|
|
2566
|
+
/** Nombre de salida con tips cortos del branch y la base (repo local). */
|
|
2567
|
+
function resolveOutputName(repo, branch, base, stamp) {
|
|
2568
|
+
const outStamp = String(stamp || "").trim() || formatOutputStamp();
|
|
2569
|
+
const raw = String(repo || "").trim();
|
|
2570
|
+
const b = String(branch || "").trim();
|
|
2571
|
+
const baseRef = String(base || "").trim() || "develop";
|
|
2572
|
+
if (!raw || looksLikeGitUrl(raw) || !b) return {
|
|
2573
|
+
filename: makeOutputFilename({ stamp: outStamp }),
|
|
2574
|
+
stamp: outStamp,
|
|
2575
|
+
fingerprint: null
|
|
2576
|
+
};
|
|
2577
|
+
try {
|
|
2578
|
+
const tips = branchTips(assertGitRepo(raw), b, baseRef);
|
|
2579
|
+
const fp = fingerprint(tips.branchSha, tips.baseSha);
|
|
2580
|
+
return {
|
|
2581
|
+
filename: makeOutputFilename({
|
|
2582
|
+
branchSha: tips.branchSha,
|
|
2583
|
+
baseSha: tips.baseSha,
|
|
2584
|
+
stamp: outStamp
|
|
2585
|
+
}),
|
|
2586
|
+
stamp: outStamp,
|
|
2587
|
+
fingerprint: fp,
|
|
2588
|
+
branchSha: tips.branchSha,
|
|
2589
|
+
baseSha: tips.baseSha
|
|
2590
|
+
};
|
|
2591
|
+
} catch {
|
|
2592
|
+
return {
|
|
2593
|
+
filename: makeOutputFilename({ stamp: outStamp }),
|
|
2594
|
+
stamp: outStamp,
|
|
2595
|
+
fingerprint: null
|
|
2596
|
+
};
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
/** Diálogo nativo de JSON; abre en la raíz del repo si es local. */
|
|
2600
|
+
async function pickReviewJsonFile(defaultDir) {
|
|
2601
|
+
const dir = String(defaultDir || "").trim();
|
|
2602
|
+
let start = dir;
|
|
2603
|
+
if (dir && !looksLikeGitUrl(dir)) try {
|
|
2604
|
+
start = assertGitRepo(dir);
|
|
2605
|
+
} catch {
|
|
2606
|
+
start = dir;
|
|
2607
|
+
}
|
|
2608
|
+
else start = "";
|
|
2609
|
+
return pickJsonFile(start || void 0);
|
|
2610
|
+
}
|
|
2170
2611
|
function resolveWorktree(source, repo, branch, base) {
|
|
2171
2612
|
if (source === "url") return ensureRemoteWorktree(repo, branch, base);
|
|
2172
2613
|
return assertGitRepo(repo);
|
|
@@ -2221,11 +2662,16 @@ function hydrateReviewPayload(input) {
|
|
|
2221
2662
|
//#endregion
|
|
2222
2663
|
//#region src/lib/report/git.remote.ts
|
|
2223
2664
|
var git_remote_exports = /* @__PURE__ */ __exportAll({
|
|
2665
|
+
detectOutput: () => detectOutput,
|
|
2224
2666
|
fileLines: () => fileLines,
|
|
2225
2667
|
hydrateFromGit: () => hydrateFromGit,
|
|
2226
2668
|
launchContext: () => launchContext,
|
|
2669
|
+
loadDetectedOutput: () => loadDetectedOutput,
|
|
2227
2670
|
loadRepo: () => loadRepo,
|
|
2228
|
-
|
|
2671
|
+
outputName: () => outputName,
|
|
2672
|
+
pendingImport: () => pendingImport,
|
|
2673
|
+
pickLocalFolder: () => pickLocalFolder,
|
|
2674
|
+
pickReviewFile: () => pickReviewFile
|
|
2229
2675
|
});
|
|
2230
2676
|
function fail(err) {
|
|
2231
2677
|
const message = describeGitError(err) || "No se pudo hablar con git.";
|
|
@@ -2248,6 +2694,46 @@ var launchContext = /* @__PURE__ */ query(async () => {
|
|
|
2248
2694
|
return null;
|
|
2249
2695
|
}
|
|
2250
2696
|
});
|
|
2697
|
+
/** JSON pendiente del CLI (DIFF_REVIEW_IMPORT_FILE); se consume al leer. */
|
|
2698
|
+
var pendingImport = /* @__PURE__ */ query(async () => {
|
|
2699
|
+
try {
|
|
2700
|
+
return takePendingImport();
|
|
2701
|
+
} catch (err) {
|
|
2702
|
+
console.error("[diff-review git] pending import", describeGitError(err), err);
|
|
2703
|
+
return null;
|
|
2704
|
+
}
|
|
2705
|
+
});
|
|
2706
|
+
/** ¿Hay un JSON de review en la raíz del repo local? */
|
|
2707
|
+
var detectOutput = /* @__PURE__ */ query("unchecked", async (input) => {
|
|
2708
|
+
try {
|
|
2709
|
+
return detectReviewOutput(String(input?.repo || "").trim(), input?.preferredName);
|
|
2710
|
+
} catch (err) {
|
|
2711
|
+
console.error("[diff-review git] detect output", describeGitError(err), err);
|
|
2712
|
+
return null;
|
|
2713
|
+
}
|
|
2714
|
+
});
|
|
2715
|
+
/** Nombre sugerido con tips cortos (branch/base) + stamp de sesión. */
|
|
2716
|
+
var outputName = /* @__PURE__ */ query("unchecked", async (input) => {
|
|
2717
|
+
try {
|
|
2718
|
+
return resolveOutputName(String(input?.repo || "").trim(), String(input?.branch || "").trim(), String(input?.base || "develop").trim(), input?.stamp);
|
|
2719
|
+
} catch (err) {
|
|
2720
|
+
console.error("[diff-review git] output name", describeGitError(err), err);
|
|
2721
|
+
const stamp = String(input?.stamp || "").trim() || formatOutputStamp();
|
|
2722
|
+
return {
|
|
2723
|
+
filename: makeOutputFilename({ stamp }),
|
|
2724
|
+
stamp,
|
|
2725
|
+
fingerprint: null
|
|
2726
|
+
};
|
|
2727
|
+
}
|
|
2728
|
+
});
|
|
2729
|
+
/** Lee un JSON detectado en el repo (sin diálogo). */
|
|
2730
|
+
var loadDetectedOutput = /* @__PURE__ */ command("unchecked", async (input) => {
|
|
2731
|
+
try {
|
|
2732
|
+
return readReviewOutput(String(input?.repo || "").trim(), String(input?.filename || "").trim());
|
|
2733
|
+
} catch (err) {
|
|
2734
|
+
fail(err);
|
|
2735
|
+
}
|
|
2736
|
+
});
|
|
2251
2737
|
var pickLocalFolder = /* @__PURE__ */ command("unchecked", async (_input) => {
|
|
2252
2738
|
try {
|
|
2253
2739
|
return await pickLocalFolder$1();
|
|
@@ -2255,6 +2741,19 @@ var pickLocalFolder = /* @__PURE__ */ command("unchecked", async (_input) => {
|
|
|
2255
2741
|
fail(err);
|
|
2256
2742
|
}
|
|
2257
2743
|
});
|
|
2744
|
+
/** Diálogo nativo de JSON; `directory` = raíz del repo local. */
|
|
2745
|
+
var pickReviewFile = /* @__PURE__ */ command("unchecked", async (input) => {
|
|
2746
|
+
try {
|
|
2747
|
+
const picked = await pickReviewJsonFile(input?.directory);
|
|
2748
|
+
if (!picked) return null;
|
|
2749
|
+
return {
|
|
2750
|
+
filename: basename(picked.path),
|
|
2751
|
+
payload: decodePayload(picked.text)
|
|
2752
|
+
};
|
|
2753
|
+
} catch (err) {
|
|
2754
|
+
fail(err);
|
|
2755
|
+
}
|
|
2756
|
+
});
|
|
2258
2757
|
/** Líneas del archivo en el branch revisado, para expandir el contexto oculto de un diff. */
|
|
2259
2758
|
var fileLines = /* @__PURE__ */ query("unchecked", async (input) => loadFileLines({
|
|
2260
2759
|
repo: String(input?.repo || "").trim(),
|
|
@@ -2286,4 +2785,4 @@ for (const [name, fn] of Object.entries(git_remote_exports)) {
|
|
|
2286
2785
|
var _sveltekit_remote_1g4ayag_default = git_remote_exports;
|
|
2287
2786
|
|
|
2288
2787
|
export { _sveltekit_remote_1g4ayag_default as default };
|
|
2289
|
-
//# sourceMappingURL=remote-1g4ayag-
|
|
2788
|
+
//# sourceMappingURL=remote-1g4ayag-DXdFe1BG.js.map
|