@jpkme/diff-review 0.1.3 → 0.1.5
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 +328 -18
- package/lib/autocomplete-es.mjs +143 -0
- 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 +44 -0
- package/lib/output-name.test.mjs +37 -0
- package/lib/pick-folder.mjs +4 -0
- package/lib/prompt.mjs +160 -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 +4 -2
- package/schema.json +12 -0
- 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/CO0mUwMJ.js +1 -0
- package/web/build/client/_app/immutable/chunks/CO0mUwMJ.js.br +2 -0
- package/web/build/client/_app/immutable/chunks/CO0mUwMJ.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/{C5moC2DW.js → CdHZDAAo.js} +42 -32
- package/web/build/client/_app/immutable/chunks/CdHZDAAo.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/CdHZDAAo.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/DzqTpz7_.js +2 -0
- package/web/build/client/_app/immutable/chunks/DzqTpz7_.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/DzqTpz7_.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/{app.Dei8qxo7.js → app.BRzz8J51.js} +2 -2
- package/web/build/client/_app/immutable/entry/app.BRzz8J51.js.br +0 -0
- package/web/build/client/_app/immutable/entry/app.BRzz8J51.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/start.BSioRkqK.js +1 -0
- package/web/build/client/_app/immutable/entry/start.BSioRkqK.js.br +0 -0
- package/web/build/client/_app/immutable/entry/start.BSioRkqK.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/1.ws8Khan5.js +1 -0
- package/web/build/client/_app/immutable/nodes/1.ws8Khan5.js.br +1 -0
- package/web/build/client/_app/immutable/nodes/1.ws8Khan5.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/2.CsD5xqRB.js +66 -0
- package/web/build/client/_app/immutable/nodes/2.CsD5xqRB.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/2.CsD5xqRB.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/3.ByTmpJQZ.js +170 -0
- package/web/build/client/_app/immutable/nodes/3.ByTmpJQZ.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/3.ByTmpJQZ.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-qrn24yN5.js +7 -0
- package/web/build/server/chunks/1-qrn24yN5.js.map +1 -0
- package/web/build/server/chunks/2-C7_Loqyn.js +7 -0
- package/web/build/server/chunks/2-C7_Loqyn.js.map +1 -0
- package/web/build/server/chunks/{3-Zc5_qwgV.js → 3-DZ5PBhOc.js} +2 -2
- package/web/build/server/chunks/{3-Zc5_qwgV.js.map → 3-DZ5PBhOc.js.map} +1 -1
- package/web/build/server/chunks/{handler-BmuqxCCf.js → handler-DD4vP3bU.js} +3 -3
- package/web/build/server/chunks/{handler-BmuqxCCf.js.map → handler-DD4vP3bU.js.map} +1 -1
- package/web/build/server/chunks/{index.js-41EcOndB.js → index.js-IhFPHwmp.js} +150 -150
- package/web/build/server/chunks/index.js-IhFPHwmp.js.map +1 -0
- package/web/build/server/chunks/{manifest.js-KnmnjP9i.js → manifest.js-Dk3RuAA5.js} +6 -6
- package/web/build/server/chunks/{manifest.js-KnmnjP9i.js.map → manifest.js-Dk3RuAA5.js.map} +1 -1
- package/web/build/server/chunks/{remote-1g4ayag-BgUNMUS8.js → remote-1g4ayag-BRL-uaKE.js} +632 -146
- package/web/build/server/chunks/remote-1g4ayag-BRL-uaKE.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-IhFPHwmp.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,224 @@ 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 branch/base. Legacy (con stamp o sin tips) sigue válido al importar.
|
|
1119
|
+
*/
|
|
1120
|
+
/** Prefijo del formato actual. */
|
|
1121
|
+
var OUTPUT_PREFIX = "diff-rev";
|
|
1122
|
+
/** Nombre legacy fijo. */
|
|
1123
|
+
var OUTPUT_FILENAME_LEGACY = `diff-review-output.json`;
|
|
1124
|
+
/**
|
|
1125
|
+
* Válidos:
|
|
1126
|
+
* - diff-rev_{branchTip}_{baseTip}.json
|
|
1127
|
+
* - diff-report_{branchTip}-{baseTip}_YYYYMMDD-HHmmss.json (legacy)
|
|
1128
|
+
* - diff-review-output.json / diff-review-output-YYYYMMDD-HHmmss.json (legacy)
|
|
1129
|
+
*/
|
|
1130
|
+
var OUTPUT_FILENAME_RE = /^(?:diff-rev_[0-9a-f]{7,12}_[0-9a-f]{7,12}|diff-report_[0-9a-f]{7,12}-[0-9a-f]{7,12}_\d{8}-\d{6}|diff-review-output(?:-\d{8}-\d{6})?)\.json$/i;
|
|
1131
|
+
/**
|
|
1132
|
+
* @param {{ branchSha?: string, baseSha?: string }} [input]
|
|
1133
|
+
* @returns {string}
|
|
1134
|
+
* con tips: diff-rev_aaaaaaaaaa_bbbbbbbbbb.json
|
|
1135
|
+
* sin tips: diff-review-output.json
|
|
1136
|
+
*/
|
|
1137
|
+
function makeOutputFilename(input = {}) {
|
|
1138
|
+
const opts = input instanceof Date ? {} : input || {};
|
|
1139
|
+
const fp = fingerprint(opts.branchSha, opts.baseSha);
|
|
1140
|
+
if (fp) {
|
|
1141
|
+
const [branchTip, baseTip] = fp.split("-");
|
|
1142
|
+
if (branchTip && baseTip) return `${OUTPUT_PREFIX}_${branchTip}_${baseTip}.json`;
|
|
1143
|
+
}
|
|
1144
|
+
return OUTPUT_FILENAME_LEGACY;
|
|
1145
|
+
}
|
|
1146
|
+
function isOutputFilename(name) {
|
|
1147
|
+
return OUTPUT_FILENAME_RE.test(String(name || "").trim());
|
|
1148
|
+
}
|
|
1149
|
+
//#endregion
|
|
1150
|
+
//#region ../lib/json-payload.mjs
|
|
1151
|
+
function tryParseJson(s) {
|
|
1152
|
+
try {
|
|
1153
|
+
return JSON.parse(s);
|
|
1154
|
+
} catch {
|
|
1155
|
+
return null;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
/** Extrae un objeto JSON del texto del modelo (fences, prosa alrededor, envelope). */
|
|
1159
|
+
function decodePayload(text) {
|
|
1160
|
+
const raw = String(text || "").trim();
|
|
1161
|
+
if (!raw) throw new Error("Respuesta vacía.");
|
|
1162
|
+
const fence = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
1163
|
+
if (fence) {
|
|
1164
|
+
const parsed = tryParseJson(fence[1].trim());
|
|
1165
|
+
if (parsed && typeof parsed === "object") return parsed;
|
|
1166
|
+
}
|
|
1167
|
+
const direct = tryParseJson(raw);
|
|
1168
|
+
if (direct && typeof direct === "object") return direct;
|
|
1169
|
+
const start = raw.indexOf("{");
|
|
1170
|
+
const end = raw.lastIndexOf("}");
|
|
1171
|
+
if (start >= 0 && end > start) {
|
|
1172
|
+
const parsed = tryParseJson(raw.slice(start, end + 1));
|
|
1173
|
+
if (parsed && typeof parsed === "object") return parsed;
|
|
1174
|
+
}
|
|
1175
|
+
throw new Error("No se pudo interpretar la respuesta como JSON.");
|
|
1176
|
+
}
|
|
1177
|
+
//#endregion
|
|
960
1178
|
//#region ../lib/config.mjs
|
|
961
1179
|
var DEFAULT_IGNORE = [
|
|
962
1180
|
"**/package-lock.json",
|
|
@@ -1112,150 +1330,14 @@ function capDiff(diff, max = STORE_DIFF_MAX) {
|
|
|
1112
1330
|
if (text.length <= max) return {
|
|
1113
1331
|
diff: text,
|
|
1114
1332
|
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");
|
|
1333
|
+
truncated: false
|
|
1334
|
+
};
|
|
1231
1335
|
return {
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1336
|
+
diff: `${text.slice(0, max)}\n\n… [diff recortado: se guardan ${max} de ${text.length} caracteres]`,
|
|
1337
|
+
diffChars: text.length,
|
|
1338
|
+
truncated: true
|
|
1235
1339
|
};
|
|
1236
1340
|
}
|
|
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
1341
|
//#endregion
|
|
1260
1342
|
//#region ../lib/extract.mjs
|
|
1261
1343
|
function extractBranchDiff({ repo, branch, base = "develop" }) {
|
|
@@ -1786,6 +1868,228 @@ function buildReview({ skeleton, payload, notes = [] }) {
|
|
|
1786
1868
|
};
|
|
1787
1869
|
}
|
|
1788
1870
|
//#endregion
|
|
1871
|
+
//#region src/lib/server/pick-file.ts
|
|
1872
|
+
/**
|
|
1873
|
+
* Diálogo nativo para elegir un JSON. Si `defaultDir` existe, abre ahí.
|
|
1874
|
+
*/
|
|
1875
|
+
async function pickJsonFile(defaultDir) {
|
|
1876
|
+
const startDir = defaultDir && existsSync(defaultDir) ? defaultDir : "";
|
|
1877
|
+
if (platform() === "win32") try {
|
|
1878
|
+
return pickWindowsJson(startDir);
|
|
1879
|
+
} catch (err) {
|
|
1880
|
+
console.warn("[diff-review] picker JSON Windows falló, probando rfd/fallback:", err instanceof Error ? err.message : err);
|
|
1881
|
+
}
|
|
1882
|
+
try {
|
|
1883
|
+
return pickJsonWithRfd(startDir);
|
|
1884
|
+
} catch (err) {
|
|
1885
|
+
console.warn("[diff-review] @bindrs/rfd JSON no disponible, usando fallback:", err instanceof Error ? err.message : err);
|
|
1886
|
+
return pickJsonFallback(startDir);
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
function resolveNodeBin$1() {
|
|
1890
|
+
if (process$1.execPath && /node(\.exe)?$/i.test(process$1.execPath)) return process$1.execPath;
|
|
1891
|
+
return "node";
|
|
1892
|
+
}
|
|
1893
|
+
function writeUtf8Bom$1(path, content) {
|
|
1894
|
+
writeFileSync(path, `\uFEFF${content}`, "utf8");
|
|
1895
|
+
}
|
|
1896
|
+
function readPicked(path) {
|
|
1897
|
+
if (!path || !existsSync(path)) return null;
|
|
1898
|
+
return {
|
|
1899
|
+
path,
|
|
1900
|
+
text: readFileSync(path, "utf8")
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
/** OpenFileDialog con InitialDirectory (DPI: mejor esfuerzo). */
|
|
1904
|
+
function pickWindowsJson(startDir) {
|
|
1905
|
+
const dir = mkdtempSync(join(tmpdir(), "diff-review-pick-json-"));
|
|
1906
|
+
const script = join(dir, "pick.ps1");
|
|
1907
|
+
const outFile = join(dir, "path.txt");
|
|
1908
|
+
try {
|
|
1909
|
+
writeUtf8Bom$1(script, `
|
|
1910
|
+
$ErrorActionPreference = 'Stop'
|
|
1911
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
1912
|
+
try {
|
|
1913
|
+
Add-Type @'
|
|
1914
|
+
using System;
|
|
1915
|
+
using System.Runtime.InteropServices;
|
|
1916
|
+
public static class DiffReviewDpi {
|
|
1917
|
+
[DllImport("user32.dll")] public static extern IntPtr SetThreadDpiAwarenessContext(IntPtr dpiContext);
|
|
1918
|
+
public static readonly IntPtr PerMonitorV2 = unchecked((IntPtr)(-4));
|
|
1919
|
+
public static void Enable() {
|
|
1920
|
+
try { SetThreadDpiAwarenessContext(PerMonitorV2); } catch { }
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
'@
|
|
1924
|
+
[DiffReviewDpi]::Enable()
|
|
1925
|
+
} catch {}
|
|
1926
|
+
|
|
1927
|
+
$dlg = New-Object System.Windows.Forms.OpenFileDialog
|
|
1928
|
+
$dlg.Title = 'Elegi el JSON de review'
|
|
1929
|
+
$dlg.Filter = 'JSON (*.json)|*.json|Todos (*.*)|*.*'
|
|
1930
|
+
$dlg.FilterIndex = 1
|
|
1931
|
+
$dlg.CheckFileExists = $true
|
|
1932
|
+
$dlg.Multiselect = $false
|
|
1933
|
+
$dir = ${JSON.stringify(startDir)}
|
|
1934
|
+
if ($dir -and (Test-Path -LiteralPath $dir)) { $dlg.InitialDirectory = $dir }
|
|
1935
|
+
if ($dlg.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) { exit 2 }
|
|
1936
|
+
[System.IO.File]::WriteAllText(${JSON.stringify(outFile)}, $dlg.FileName)
|
|
1937
|
+
exit 0
|
|
1938
|
+
`.trim() + "\r\n");
|
|
1939
|
+
const result = spawnSync("powershell.exe", [
|
|
1940
|
+
"-STA",
|
|
1941
|
+
"-NoProfile",
|
|
1942
|
+
"-ExecutionPolicy",
|
|
1943
|
+
"Bypass",
|
|
1944
|
+
"-File",
|
|
1945
|
+
script
|
|
1946
|
+
], {
|
|
1947
|
+
encoding: "utf8",
|
|
1948
|
+
windowsHide: false,
|
|
1949
|
+
stdio: [
|
|
1950
|
+
"ignore",
|
|
1951
|
+
"pipe",
|
|
1952
|
+
"pipe"
|
|
1953
|
+
],
|
|
1954
|
+
timeout: 600 * 1e3
|
|
1955
|
+
});
|
|
1956
|
+
if (result.status === 2) return null;
|
|
1957
|
+
if (result.status !== 0) {
|
|
1958
|
+
const err = (result.stderr || result.stdout || result.error?.message || `exit ${result.status}`).toString().trim();
|
|
1959
|
+
throw new Error(err || "powershell json picker fallo");
|
|
1960
|
+
}
|
|
1961
|
+
if (!existsSync(outFile)) throw new Error("powershell json picker no escribio la ruta");
|
|
1962
|
+
const picked = readPicked(readFileSync(outFile, "utf8").replace(/^\uFEFF/, "").trim());
|
|
1963
|
+
if (!picked) throw new Error("powershell json picker devolvio ruta vacia");
|
|
1964
|
+
return picked;
|
|
1965
|
+
} finally {
|
|
1966
|
+
try {
|
|
1967
|
+
rmSync(dir, {
|
|
1968
|
+
recursive: true,
|
|
1969
|
+
force: true
|
|
1970
|
+
});
|
|
1971
|
+
} catch {}
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
function pickJsonWithRfd(startDir) {
|
|
1975
|
+
const dir = mkdtempSync(join(tmpdir(), "diff-review-rfd-json-"));
|
|
1976
|
+
const worker = join(dir, "pick.mjs");
|
|
1977
|
+
const outFile = join(dir, "path.txt");
|
|
1978
|
+
try {
|
|
1979
|
+
writeFileSync(worker, `import { createRequire } from 'node:module';
|
|
1980
|
+
import { existsSync, writeFileSync } from 'node:fs';
|
|
1981
|
+
import { dirname, join } from 'node:path';
|
|
1982
|
+
|
|
1983
|
+
const require = createRequire(import.meta.url);
|
|
1984
|
+
const outFile = ${JSON.stringify(outFile)};
|
|
1985
|
+
const startDir = ${JSON.stringify(startDir)};
|
|
1986
|
+
|
|
1987
|
+
function findRfdIndex() {
|
|
1988
|
+
const seen = new Set();
|
|
1989
|
+
let dir = process.cwd();
|
|
1990
|
+
for (let i = 0; i < 16; i++) {
|
|
1991
|
+
if (seen.has(dir)) break;
|
|
1992
|
+
seen.add(dir);
|
|
1993
|
+
const indexJs = join(dir, 'node_modules', '@bindrs', 'rfd', 'index.js');
|
|
1994
|
+
if (existsSync(indexJs)) return indexJs;
|
|
1995
|
+
const parent = dirname(dir);
|
|
1996
|
+
if (parent === dir) break;
|
|
1997
|
+
dir = parent;
|
|
1998
|
+
}
|
|
1999
|
+
throw new Error('No se encontro @bindrs/rfd en node_modules');
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
const { AsyncFileDialog } = require(findRfdIndex());
|
|
2003
|
+
let dlg = new AsyncFileDialog()
|
|
2004
|
+
.setTitle('Elegi el JSON de review')
|
|
2005
|
+
.addFilter('JSON', ['json']);
|
|
2006
|
+
if (startDir) dlg = dlg.setDirectory(startDir);
|
|
2007
|
+
const handle = await dlg.pickFile();
|
|
2008
|
+
if (!handle) process.exit(2);
|
|
2009
|
+
writeFileSync(outFile, handle.path(), 'utf8');
|
|
2010
|
+
`, "utf8");
|
|
2011
|
+
const result = spawnSync(resolveNodeBin$1(), [worker], {
|
|
2012
|
+
encoding: "utf8",
|
|
2013
|
+
windowsHide: false,
|
|
2014
|
+
stdio: [
|
|
2015
|
+
"ignore",
|
|
2016
|
+
"pipe",
|
|
2017
|
+
"pipe"
|
|
2018
|
+
],
|
|
2019
|
+
timeout: 600 * 1e3,
|
|
2020
|
+
cwd: process$1.cwd(),
|
|
2021
|
+
env: process$1.env
|
|
2022
|
+
});
|
|
2023
|
+
if (result.status === 2 || result.status === 1) return null;
|
|
2024
|
+
if (result.status !== 0) {
|
|
2025
|
+
const err = (result.stderr || result.stdout || result.error?.message || `exit ${result.status}`).toString().trim();
|
|
2026
|
+
throw new Error(err || "worker rfd json fallo");
|
|
2027
|
+
}
|
|
2028
|
+
if (!existsSync(outFile)) return null;
|
|
2029
|
+
return readPicked(readFileSync(outFile, "utf8").replace(/^\uFEFF/, "").trim());
|
|
2030
|
+
} finally {
|
|
2031
|
+
try {
|
|
2032
|
+
rmSync(dir, {
|
|
2033
|
+
recursive: true,
|
|
2034
|
+
force: true
|
|
2035
|
+
});
|
|
2036
|
+
} catch {}
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
function pickJsonFallback(startDir) {
|
|
2040
|
+
const os = platform();
|
|
2041
|
+
if (os === "darwin") return pickMacJson(startDir);
|
|
2042
|
+
if (os === "linux") return pickLinuxJson(startDir);
|
|
2043
|
+
return null;
|
|
2044
|
+
}
|
|
2045
|
+
function pickMacJson(startDir) {
|
|
2046
|
+
try {
|
|
2047
|
+
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\"})"], {
|
|
2048
|
+
encoding: "utf8",
|
|
2049
|
+
timeout: 600 * 1e3
|
|
2050
|
+
});
|
|
2051
|
+
return readPicked(String(out || "").trim().replace(/\/$/, ""));
|
|
2052
|
+
} catch (err) {
|
|
2053
|
+
if (err.status === 1 || /User canceled|-128/i.test(String(err))) return null;
|
|
2054
|
+
throw new Error("No se pudo abrir el dialogo de archivo: " + (err instanceof Error ? err.message : String(err)));
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
function pickLinuxJson(startDir) {
|
|
2058
|
+
try {
|
|
2059
|
+
const args = [
|
|
2060
|
+
"--file-selection",
|
|
2061
|
+
"--title=Elegi el JSON de review",
|
|
2062
|
+
"--file-filter=*.json"
|
|
2063
|
+
];
|
|
2064
|
+
if (startDir) args.push(`--filename=${join(startDir, "/")}`);
|
|
2065
|
+
const out = execFileSync("zenity", args, {
|
|
2066
|
+
encoding: "utf8",
|
|
2067
|
+
timeout: 600 * 1e3
|
|
2068
|
+
});
|
|
2069
|
+
return readPicked(String(out || "").trim());
|
|
2070
|
+
} catch (zenityErr) {
|
|
2071
|
+
if (zenityErr.status === 1) return null;
|
|
2072
|
+
try {
|
|
2073
|
+
const out = execFileSync("kdialog", startDir ? [
|
|
2074
|
+
"--getopenfilename",
|
|
2075
|
+
startDir,
|
|
2076
|
+
"*.json"
|
|
2077
|
+
] : [
|
|
2078
|
+
"--getopenfilename",
|
|
2079
|
+
".",
|
|
2080
|
+
"*.json"
|
|
2081
|
+
], {
|
|
2082
|
+
encoding: "utf8",
|
|
2083
|
+
timeout: 600 * 1e3
|
|
2084
|
+
});
|
|
2085
|
+
return readPicked(String(out || "").trim());
|
|
2086
|
+
} catch (kErr) {
|
|
2087
|
+
if (kErr.status === 1) return null;
|
|
2088
|
+
throw new Error("No hay dialogo de archivos disponible. Instala zenity o kdialog, o arrastra el JSON.");
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
//#endregion
|
|
1789
2093
|
//#region src/lib/server/pick-folder.ts
|
|
1790
2094
|
/**
|
|
1791
2095
|
* Diálogo nativo de carpetas.
|
|
@@ -2164,9 +2468,135 @@ function loadLaunchRepoInfo() {
|
|
|
2164
2468
|
return null;
|
|
2165
2469
|
}
|
|
2166
2470
|
}
|
|
2471
|
+
/**
|
|
2472
|
+
* JSON dejado por el CLI (env DIFF_REVIEW_IMPORT_FILE). Se consume una sola vez.
|
|
2473
|
+
*/
|
|
2474
|
+
function takePendingImport() {
|
|
2475
|
+
const raw = String(process.env.DIFF_REVIEW_IMPORT_FILE || "").trim();
|
|
2476
|
+
if (!raw) return null;
|
|
2477
|
+
delete process.env.DIFF_REVIEW_IMPORT_FILE;
|
|
2478
|
+
const filePath = resolve(raw);
|
|
2479
|
+
const name = basename(filePath);
|
|
2480
|
+
if (!isOutputFilename(name)) return null;
|
|
2481
|
+
if (!existsSync(filePath)) return null;
|
|
2482
|
+
try {
|
|
2483
|
+
return {
|
|
2484
|
+
filename: name,
|
|
2485
|
+
payload: decodePayload(readFileSync(filePath, "utf8"))
|
|
2486
|
+
};
|
|
2487
|
+
} catch {
|
|
2488
|
+
return null;
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
/**
|
|
2492
|
+
* Busca un JSON de review en la raíz del repo local.
|
|
2493
|
+
* Prefiere `preferredName` si existe; si no, el más reciente que matchee el patrón.
|
|
2494
|
+
*/
|
|
2495
|
+
function detectReviewOutput(repo, preferredName) {
|
|
2496
|
+
const raw = String(repo || "").trim();
|
|
2497
|
+
if (!raw || looksLikeGitUrl(raw)) return null;
|
|
2498
|
+
let root;
|
|
2499
|
+
try {
|
|
2500
|
+
root = assertGitRepo(raw);
|
|
2501
|
+
} catch {
|
|
2502
|
+
return null;
|
|
2503
|
+
}
|
|
2504
|
+
const preferred = String(preferredName || "").trim();
|
|
2505
|
+
if (preferred && isOutputFilename(preferred)) {
|
|
2506
|
+
const preferredPath = join(root, preferred);
|
|
2507
|
+
if (existsSync(preferredPath)) try {
|
|
2508
|
+
return {
|
|
2509
|
+
filename: preferred,
|
|
2510
|
+
path: preferredPath,
|
|
2511
|
+
mtimeMs: statSync(preferredPath).mtimeMs
|
|
2512
|
+
};
|
|
2513
|
+
} catch {}
|
|
2514
|
+
}
|
|
2515
|
+
let best = null;
|
|
2516
|
+
try {
|
|
2517
|
+
for (const name of readdirSync(root)) {
|
|
2518
|
+
if (!isOutputFilename(name)) continue;
|
|
2519
|
+
const full = join(root, name);
|
|
2520
|
+
try {
|
|
2521
|
+
const st = statSync(full);
|
|
2522
|
+
if (!st.isFile()) continue;
|
|
2523
|
+
if (!best || st.mtimeMs > best.mtimeMs) best = {
|
|
2524
|
+
filename: name,
|
|
2525
|
+
path: full,
|
|
2526
|
+
mtimeMs: st.mtimeMs
|
|
2527
|
+
};
|
|
2528
|
+
} catch {}
|
|
2529
|
+
}
|
|
2530
|
+
} catch {
|
|
2531
|
+
return null;
|
|
2532
|
+
}
|
|
2533
|
+
return best;
|
|
2534
|
+
}
|
|
2535
|
+
/** Lee y parsea un JSON de review en la raíz del repo (solo nombres válidos). */
|
|
2536
|
+
function readReviewOutput(repo, filename) {
|
|
2537
|
+
const raw = String(repo || "").trim();
|
|
2538
|
+
const name = basename(String(filename || "").trim());
|
|
2539
|
+
if (!raw || !name || !isOutputFilename(name) || looksLikeGitUrl(raw)) return null;
|
|
2540
|
+
let root;
|
|
2541
|
+
try {
|
|
2542
|
+
root = assertGitRepo(raw);
|
|
2543
|
+
} catch {
|
|
2544
|
+
return null;
|
|
2545
|
+
}
|
|
2546
|
+
const filePath = join(root, name);
|
|
2547
|
+
if (!existsSync(filePath)) return null;
|
|
2548
|
+
try {
|
|
2549
|
+
return {
|
|
2550
|
+
filename: name,
|
|
2551
|
+
payload: decodePayload(readFileSync(filePath, "utf8"))
|
|
2552
|
+
};
|
|
2553
|
+
} catch {
|
|
2554
|
+
return null;
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2167
2557
|
async function pickLocalFolder$1() {
|
|
2168
2558
|
return pickFolder();
|
|
2169
2559
|
}
|
|
2560
|
+
/** Nombre de salida con tips cortos del branch y la base (repo local). */
|
|
2561
|
+
function resolveOutputName(repo, branch, base) {
|
|
2562
|
+
const raw = String(repo || "").trim();
|
|
2563
|
+
const b = String(branch || "").trim();
|
|
2564
|
+
const baseRef = String(base || "").trim() || "develop";
|
|
2565
|
+
if (!raw || looksLikeGitUrl(raw) || !b) return {
|
|
2566
|
+
filename: makeOutputFilename({}),
|
|
2567
|
+
fingerprint: null
|
|
2568
|
+
};
|
|
2569
|
+
try {
|
|
2570
|
+
const tips = branchTips(assertGitRepo(raw), b, baseRef);
|
|
2571
|
+
const fp = fingerprint(tips.branchSha, tips.baseSha);
|
|
2572
|
+
return {
|
|
2573
|
+
filename: makeOutputFilename({
|
|
2574
|
+
branchSha: tips.branchSha,
|
|
2575
|
+
baseSha: tips.baseSha
|
|
2576
|
+
}),
|
|
2577
|
+
fingerprint: fp,
|
|
2578
|
+
branchSha: tips.branchSha,
|
|
2579
|
+
baseSha: tips.baseSha
|
|
2580
|
+
};
|
|
2581
|
+
} catch {
|
|
2582
|
+
return {
|
|
2583
|
+
filename: makeOutputFilename({}),
|
|
2584
|
+
fingerprint: null
|
|
2585
|
+
};
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
/** Diálogo nativo de JSON; abre en la raíz del repo si es local. */
|
|
2589
|
+
async function pickReviewJsonFile(defaultDir) {
|
|
2590
|
+
const dir = String(defaultDir || "").trim();
|
|
2591
|
+
let start = dir;
|
|
2592
|
+
if (dir && !looksLikeGitUrl(dir)) try {
|
|
2593
|
+
start = assertGitRepo(dir);
|
|
2594
|
+
} catch {
|
|
2595
|
+
start = dir;
|
|
2596
|
+
}
|
|
2597
|
+
else start = "";
|
|
2598
|
+
return pickJsonFile(start || void 0);
|
|
2599
|
+
}
|
|
2170
2600
|
function resolveWorktree(source, repo, branch, base) {
|
|
2171
2601
|
if (source === "url") return ensureRemoteWorktree(repo, branch, base);
|
|
2172
2602
|
return assertGitRepo(repo);
|
|
@@ -2221,11 +2651,16 @@ function hydrateReviewPayload(input) {
|
|
|
2221
2651
|
//#endregion
|
|
2222
2652
|
//#region src/lib/report/git.remote.ts
|
|
2223
2653
|
var git_remote_exports = /* @__PURE__ */ __exportAll({
|
|
2654
|
+
detectOutput: () => detectOutput,
|
|
2224
2655
|
fileLines: () => fileLines,
|
|
2225
2656
|
hydrateFromGit: () => hydrateFromGit,
|
|
2226
2657
|
launchContext: () => launchContext,
|
|
2658
|
+
loadDetectedOutput: () => loadDetectedOutput,
|
|
2227
2659
|
loadRepo: () => loadRepo,
|
|
2228
|
-
|
|
2660
|
+
outputName: () => outputName,
|
|
2661
|
+
pendingImport: () => pendingImport,
|
|
2662
|
+
pickLocalFolder: () => pickLocalFolder,
|
|
2663
|
+
pickReviewFile: () => pickReviewFile
|
|
2229
2664
|
});
|
|
2230
2665
|
function fail(err) {
|
|
2231
2666
|
const message = describeGitError(err) || "No se pudo hablar con git.";
|
|
@@ -2248,6 +2683,44 @@ var launchContext = /* @__PURE__ */ query(async () => {
|
|
|
2248
2683
|
return null;
|
|
2249
2684
|
}
|
|
2250
2685
|
});
|
|
2686
|
+
/** JSON pendiente del CLI (DIFF_REVIEW_IMPORT_FILE); se consume al leer. */
|
|
2687
|
+
var pendingImport = /* @__PURE__ */ query(async () => {
|
|
2688
|
+
try {
|
|
2689
|
+
return takePendingImport();
|
|
2690
|
+
} catch (err) {
|
|
2691
|
+
console.error("[diff-review git] pending import", describeGitError(err), err);
|
|
2692
|
+
return null;
|
|
2693
|
+
}
|
|
2694
|
+
});
|
|
2695
|
+
/** ¿Hay un JSON de review en la raíz del repo local? */
|
|
2696
|
+
var detectOutput = /* @__PURE__ */ query("unchecked", async (input) => {
|
|
2697
|
+
try {
|
|
2698
|
+
return detectReviewOutput(String(input?.repo || "").trim(), input?.preferredName);
|
|
2699
|
+
} catch (err) {
|
|
2700
|
+
console.error("[diff-review git] detect output", describeGitError(err), err);
|
|
2701
|
+
return null;
|
|
2702
|
+
}
|
|
2703
|
+
});
|
|
2704
|
+
/** Nombre sugerido con tips cortos (branch/base). */
|
|
2705
|
+
var outputName = /* @__PURE__ */ query("unchecked", async (input) => {
|
|
2706
|
+
try {
|
|
2707
|
+
return resolveOutputName(String(input?.repo || "").trim(), String(input?.branch || "").trim(), String(input?.base || "develop").trim());
|
|
2708
|
+
} catch (err) {
|
|
2709
|
+
console.error("[diff-review git] output name", describeGitError(err), err);
|
|
2710
|
+
return {
|
|
2711
|
+
filename: makeOutputFilename({}),
|
|
2712
|
+
fingerprint: null
|
|
2713
|
+
};
|
|
2714
|
+
}
|
|
2715
|
+
});
|
|
2716
|
+
/** Lee un JSON detectado en el repo (sin diálogo). */
|
|
2717
|
+
var loadDetectedOutput = /* @__PURE__ */ command("unchecked", async (input) => {
|
|
2718
|
+
try {
|
|
2719
|
+
return readReviewOutput(String(input?.repo || "").trim(), String(input?.filename || "").trim());
|
|
2720
|
+
} catch (err) {
|
|
2721
|
+
fail(err);
|
|
2722
|
+
}
|
|
2723
|
+
});
|
|
2251
2724
|
var pickLocalFolder = /* @__PURE__ */ command("unchecked", async (_input) => {
|
|
2252
2725
|
try {
|
|
2253
2726
|
return await pickLocalFolder$1();
|
|
@@ -2255,6 +2728,19 @@ var pickLocalFolder = /* @__PURE__ */ command("unchecked", async (_input) => {
|
|
|
2255
2728
|
fail(err);
|
|
2256
2729
|
}
|
|
2257
2730
|
});
|
|
2731
|
+
/** Diálogo nativo de JSON; `directory` = raíz del repo local. */
|
|
2732
|
+
var pickReviewFile = /* @__PURE__ */ command("unchecked", async (input) => {
|
|
2733
|
+
try {
|
|
2734
|
+
const picked = await pickReviewJsonFile(input?.directory);
|
|
2735
|
+
if (!picked) return null;
|
|
2736
|
+
return {
|
|
2737
|
+
filename: basename(picked.path),
|
|
2738
|
+
payload: decodePayload(picked.text)
|
|
2739
|
+
};
|
|
2740
|
+
} catch (err) {
|
|
2741
|
+
fail(err);
|
|
2742
|
+
}
|
|
2743
|
+
});
|
|
2258
2744
|
/** Líneas del archivo en el branch revisado, para expandir el contexto oculto de un diff. */
|
|
2259
2745
|
var fileLines = /* @__PURE__ */ query("unchecked", async (input) => loadFileLines({
|
|
2260
2746
|
repo: String(input?.repo || "").trim(),
|
|
@@ -2286,4 +2772,4 @@ for (const [name, fn] of Object.entries(git_remote_exports)) {
|
|
|
2286
2772
|
var _sveltekit_remote_1g4ayag_default = git_remote_exports;
|
|
2287
2773
|
|
|
2288
2774
|
export { _sveltekit_remote_1g4ayag_default as default };
|
|
2289
|
-
//# sourceMappingURL=remote-1g4ayag-
|
|
2775
|
+
//# sourceMappingURL=remote-1g4ayag-BRL-uaKE.js.map
|