@yangzhe1991/dsh-code-review 0.1.5 → 0.2.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 +13 -0
- package/README.zh.md +13 -0
- package/lib/client.js +323 -4
- package/lib/client.js.map +4 -4
- package/lib/index.js +77 -2
- package/lib/index.js.map +2 -2
- package/lib/types/index.d.ts +18 -2
- package/package.json +3 -2
package/lib/index.js
CHANGED
|
@@ -1,7 +1,82 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
|
|
2
|
+
import { stat, readFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
5
|
+
var DIFF_ROUTE_PATH = "/dsh-code-review/diff";
|
|
6
|
+
var MAX_DIFF_BYTES = 5 * 1024 * 1024;
|
|
7
|
+
function resolveDiffPath(raw) {
|
|
8
|
+
let p = raw.trim();
|
|
9
|
+
if (p === "" || p.includes("\0")) return null;
|
|
10
|
+
if (p.startsWith("~/")) p = join(homedir(), p.slice(2));
|
|
11
|
+
if (!isAbsolute(p)) return null;
|
|
12
|
+
if (!/\.(?:diff|patch)$/i.test(p)) return null;
|
|
13
|
+
return resolve(p);
|
|
14
|
+
}
|
|
15
|
+
async function loadDiffText(absPath) {
|
|
16
|
+
try {
|
|
17
|
+
const st = await stat(absPath);
|
|
18
|
+
if (!st.isFile()) return { ok: false, error: "not-file", status: 400 };
|
|
19
|
+
if (st.size > MAX_DIFF_BYTES) return { ok: false, error: "too-large", status: 413 };
|
|
20
|
+
const text = await readFile(absPath, "utf8");
|
|
21
|
+
return { ok: true, text };
|
|
22
|
+
} catch (err) {
|
|
23
|
+
if (err.code === "ENOENT" || err.code === "ENOTDIR") {
|
|
24
|
+
return { ok: false, error: "not-found", status: 404 };
|
|
25
|
+
}
|
|
26
|
+
return { ok: false, error: "read-error", status: 500 };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function sendJson(res, status, body) {
|
|
30
|
+
const payload = JSON.stringify(body);
|
|
31
|
+
res.writeHead(status, {
|
|
32
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
33
|
+
"Content-Length": Buffer.byteLength(payload),
|
|
34
|
+
"Cache-Control": "no-store",
|
|
35
|
+
"X-Content-Type-Options": "nosniff"
|
|
36
|
+
});
|
|
37
|
+
res.end(payload);
|
|
38
|
+
}
|
|
39
|
+
async function handleDiffRequest(req, res) {
|
|
40
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
41
|
+
sendJson(res, 405, { ok: false, error: "method-not-allowed" });
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
let raw = "";
|
|
45
|
+
try {
|
|
46
|
+
raw = new URL(req.url ?? "/", "http://localhost").searchParams.get("path") ?? "";
|
|
47
|
+
} catch {
|
|
48
|
+
sendJson(res, 400, { ok: false, error: "invalid-path" });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const abs = resolveDiffPath(raw);
|
|
52
|
+
if (abs === null) {
|
|
53
|
+
sendJson(res, 400, { ok: false, error: "invalid-path" });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const result = await loadDiffText(abs);
|
|
57
|
+
if (!result.ok) {
|
|
58
|
+
sendJson(res, result.status, { ok: false, error: result.error });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
sendJson(res, 200, { ok: true, text: result.text });
|
|
62
|
+
}
|
|
63
|
+
function apply(ctx) {
|
|
64
|
+
ctx.inject(["webServer"], (srv) => {
|
|
65
|
+
srv.effect(() => {
|
|
66
|
+
const unregister = srv.webServer.register({
|
|
67
|
+
kind: "exact",
|
|
68
|
+
path: DIFF_ROUTE_PATH,
|
|
69
|
+
handler: handleDiffRequest
|
|
70
|
+
});
|
|
71
|
+
return unregister;
|
|
72
|
+
}, `dsh-code-review: ${DIFF_ROUTE_PATH} diff file route`);
|
|
73
|
+
});
|
|
3
74
|
}
|
|
4
75
|
export {
|
|
5
|
-
|
|
76
|
+
DIFF_ROUTE_PATH,
|
|
77
|
+
MAX_DIFF_BYTES,
|
|
78
|
+
apply,
|
|
79
|
+
loadDiffText,
|
|
80
|
+
resolveDiffPath
|
|
6
81
|
};
|
|
7
82
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * dsh-code-review \u63D2\u4EF6,node \u534A(\u5BBF\u4E3B\u4FA7)\u3002\n *\n * \u672C\
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["/**\n * dsh-code-review \u63D2\u4EF6,node \u534A(\u5BBF\u4E3B\u4FA7)\u3002\n *\n * \u804C\u8D23:\u7ED9\u5BBF\u4E3B Web \u670D\u52A1\u5668\u6CE8\u518C\u4E00\u4E2A\u300C\u672C\u5730 diff \u6587\u4EF6\u8BFB\u53D6\u300D\u8DEF\u7531,\u4F9B\u6D4F\u89C8\u5668\u534A\n * \u7684\u300C\u8DEF\u5F84\u67E5\u770B chip\u300D\u70B9\u51FB\u65F6\u8BFB\u53D6\u4F1A\u8BDD\u91CC\u51FA\u73B0\u7684\u672C\u5730\u8DEF\u5F84\u6307\u5411\u7684 .diff/.patch\n * \u6587\u4EF6\u5185\u5BB9(\u6D4F\u89C8\u5668\u65E0\u6CD5\u76F4\u63A5\u8BFB\u672C\u5730\u6587\u4EF6,\u5FC5\u987B\u7531\u5BBF\u4E3B\u4EE3\u8BFB)\u3002\n *\n * \u5B89\u5168\u8FB9\u754C(\u53EA\u8BFB\u672C\u5730 diff \u6587\u4EF6,\u6700\u5C0F\u66B4\u9732\u9762):\n * - \u4EC5 GET;\u8DEF\u5F84\u5FC5\u987B\u7EDD\u5BF9(\u652F\u6301 ~ \u5C55\u5F00)\u3001\u4E14\u4EE5 .diff/.patch \u7ED3\u5C3E;\n * - \u6587\u4EF6\u5FC5\u987B\u5B58\u5728\u3001\u662F\u666E\u901A\u6587\u4EF6\u3001\u5927\u5C0F \u2264 5MB;\n * - \u53EA\u8FD4\u56DE JSON \u6587\u672C\u5185\u5BB9,\u4E0D\u8BBE CORS(\u4EFB\u4F55\u8DE8\u6E90\u9875\u9762\u90FD\u8BFB\u4E0D\u5230\u54CD\u5E94\u4F53);\n * - \u54CD\u5E94 no-store,\u4E0D\u843D\u4EFB\u4F55\u7F13\u5B58\u3002\n *\n * \u8DEF\u7531\u6CE8\u518C\u4E0E webServer \u5C31\u7EEA\u89E3\u8026:\u7528 ctx.inject \u5EF6\u8FDF\u5230\u670D\u52A1\u53EF\u7528\u65F6\u6267\u884C,\n * webServer \u7F3A\u5931\u7684\u88C5\u914D(\u5982 headless)\u91CC\u56DE\u8C03\u4E0D\u6267\u884C,\u63D2\u4EF6\u884C\u672C\u8EAB\u4E0D\u62A5\u9519\u3002\n */\nimport { stat, readFile } from 'node:fs/promises'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { homedir } from 'node:os'\nimport { isAbsolute, join, resolve } from 'node:path'\n\n/** \u6D4F\u89C8\u5668\u534A\u8BF7\u6C42\u7684\u56FA\u5B9A\u8DEF\u7531\u8DEF\u5F84(\u4E0E\u63D2\u4EF6 bundle \u8DEF\u5F84 /plugins/* \u4E0D\u51B2\u7A81)\u3002 */\nexport const DIFF_ROUTE_PATH = '/dsh-code-review/diff'\n/** \u5355\u6587\u4EF6\u8BFB\u53D6\u4E0A\u9650(5MB,\u9632\u8BEF\u70B9\u8D85\u5927\u6587\u4EF6\u62D6\u57AE\u5BBF\u4E3B)\u3002 */\nexport const MAX_DIFF_BYTES = 5 * 1024 * 1024\n\n// \u2014\u2014 \u6700\u5C0F\u4E0A\u4E0B\u6587\u5F62\u72B6(\u53EA\u53D6\u7528\u5230\u7684 cordis \u6210\u5458,\u4E0D\u5F15\u5165\u5B98\u65B9\u7C7B\u578B\u4F9D\u8D56)\u2014\u2014\n\n/** webServer \u670D\u52A1\u7684\u6700\u5C0F\u5F62\u72B6(register \u6CE8\u518C\u547D\u540D\u8DEF\u7531)\u3002 */\nexport interface WebServerLike {\n register(route: {\n kind: 'exact'\n path: string\n handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>\n }): () => void\n}\n\n/** \u63D2\u4EF6 apply \u6536\u5230\u7684\u5BBF\u4E3B\u4E0A\u4E0B\u6587\u6700\u5C0F\u5F62\u72B6(cordis Context \u7ED3\u6784\u517C\u5BB9)\u3002 */\nexport interface CodeReviewCtx {\n /** \u670D\u52A1\u53EF\u7528\u65F6\u6267\u884C\u56DE\u8C03(webServer \u7F3A\u5931\u65F6\u56DE\u8C03\u4E0D\u6267\u884C,\u4E0D\u963B\u585E\u63D2\u4EF6)\u3002 */\n inject(\n deps: readonly string[],\n callback: (ctx: CodeReviewCtx & { webServer: WebServerLike }) => unknown,\n ): unknown\n /** \u6CE8\u518C\u4E00\u4E2A\u4F5C\u7528\u57DF effect,\u8FD4\u56DE\u7684 disposer \u5728\u63D2\u4EF6\u5378\u8F7D\u65F6\u6267\u884C\u3002 */\n effect(fn: () => void | (() => void), name?: string): unknown\n}\n\n// \u2014\u2014 \u672C\u5730 diff \u6587\u4EF6\u8BFB\u53D6(\u7EAF\u51FD\u6570,\u5355\u6D4B\u8986\u76D6)\u2014\u2014\n\n/** \u8DEF\u5F84\u89E3\u6790\u5931\u8D25\u7684\u539F\u56E0(\u4F9B\u6D4B\u8BD5\u4E0E\u9519\u8BEF\u54CD\u5E94\u4F7F\u7528)\u3002 */\nexport type DiffLoadError = 'invalid-path' | 'not-found' | 'not-file' | 'too-large' | 'read-error'\n\n/** loadDiffText \u7684\u8FD4\u56DE:\u6210\u529F\u5E26\u6587\u672C;\u5931\u8D25\u5E26\u539F\u56E0\u4E0E HTTP \u72B6\u6001\u7801\u3002 */\nexport type DiffLoadResult =\n | { ok: true; text: string }\n | { ok: false; error: DiffLoadError; status: number }\n\n/**\n * \u6821\u9A8C\u5E76\u89C4\u8303\u5316\u6D4F\u89C8\u5668\u4F20\u6765\u7684\u8DEF\u5F84\u5B57\u7B26\u4E32\u3002\n *\n * \u89C4\u5219:trim \u540E\u5FC5\u987B\u975E\u7A7A\u3001\u4E0D\u542B NUL;\u652F\u6301 `~` \u524D\u7F00\u5C55\u5F00\u4E3A home \u76EE\u5F55;\n * \u5C55\u5F00\u540E\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\u4E14\u4EE5 .diff/.patch \u7ED3\u5C3E(\u5927\u5C0F\u5199\u4E0D\u654F\u611F)\u3002\n * \u8FD4\u56DE\u89C4\u8303\u5316\u540E\u7684\u7EDD\u5BF9\u8DEF\u5F84,\u4E0D\u5408\u6CD5\u8FD4\u56DE null\u3002\u4E0D\u89E6\u76D8\u3002\n */\nexport function resolveDiffPath(raw: string): string | null {\n let p = raw.trim()\n if (p === '' || p.includes('\\0')) return null\n if (p.startsWith('~/')) p = join(homedir(), p.slice(2))\n if (!isAbsolute(p)) return null\n if (!/\\.(?:diff|patch)$/i.test(p)) return null\n return resolve(p)\n}\n\n/**\n * \u8BFB\u53D6\u4E00\u4E2A\u5DF2\u6821\u9A8C\u7684\u672C\u5730 diff \u6587\u4EF6\u5185\u5BB9(UTF-8)\u3002\n * \u5931\u8D25\u539F\u56E0\u6620\u5C04:\u4E0D\u5B58\u5728/\u89E3\u5F15\u7528\u5931\u8D25 \u2192 not-found;\u4E0D\u662F\u666E\u901A\u6587\u4EF6 \u2192 not-file;\n * \u8D85\u8FC7\u5927\u5C0F\u4E0A\u9650 \u2192 too-large;\u5176\u4ED6 IO \u9519\u8BEF \u2192 read-error\u3002\n */\nexport async function loadDiffText(absPath: string): Promise<DiffLoadResult> {\n try {\n const st = await stat(absPath)\n if (!st.isFile()) return { ok: false, error: 'not-file', status: 400 }\n if (st.size > MAX_DIFF_BYTES) return { ok: false, error: 'too-large', status: 413 }\n const text = await readFile(absPath, 'utf8')\n return { ok: true, text }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT' || (err as NodeJS.ErrnoException).code === 'ENOTDIR') {\n return { ok: false, error: 'not-found', status: 404 }\n }\n return { ok: false, error: 'read-error', status: 500 }\n }\n}\n\n/** \u53D1\u9001 JSON \u54CD\u5E94(\u7EDF\u4E00\u5934:no-store + nosniff,\u4E0D\u8BBE CORS)\u3002 */\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n const payload = JSON.stringify(body)\n res.writeHead(status, {\n 'Content-Type': 'application/json; charset=utf-8',\n 'Content-Length': Buffer.byteLength(payload),\n 'Cache-Control': 'no-store',\n 'X-Content-Type-Options': 'nosniff',\n })\n res.end(payload)\n}\n\n/**\n * \u8DEF\u7531\u5904\u7406\u5668:\u89E3\u6790 ?path= \u53C2\u6570 \u2192 \u6821\u9A8C \u2192 \u8BFB\u6587\u4EF6 \u2192 \u8FD4\u56DE\n * { ok: true, text } \u6216 { ok: false, error }\u3002\n */\nasync function handleDiffRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n if (req.method !== 'GET' && req.method !== 'HEAD') {\n sendJson(res, 405, { ok: false, error: 'method-not-allowed' })\n return\n }\n let raw = ''\n try {\n raw = new URL(req.url ?? '/', 'http://localhost').searchParams.get('path') ?? ''\n } catch {\n sendJson(res, 400, { ok: false, error: 'invalid-path' })\n return\n }\n const abs = resolveDiffPath(raw)\n if (abs === null) {\n sendJson(res, 400, { ok: false, error: 'invalid-path' })\n return\n }\n const result = await loadDiffText(abs)\n if (!result.ok) {\n sendJson(res, result.status, { ok: false, error: result.error })\n return\n }\n sendJson(res, 200, { ok: true, text: result.text })\n}\n\n/**\n * \u63D2\u4EF6 apply:\u6CE8\u518C\u672C\u5730 diff \u6587\u4EF6\u8BFB\u53D6\u8DEF\u7531\u3002webServer \u53EF\u7528\u65F6\u6267\u884C;\n * \u7F3A\u5931\u65F6\u9759\u9ED8\u8DF3\u8FC7(\u56DE\u8C03\u4E0D\u6267\u884C),\u6D4F\u89C8\u5668\u534A\u7684\u8DEF\u5F84 chip \u4F1A\u663E\u793A\u8BFB\u53D6\u5931\u8D25\u3002\n */\nexport function apply(ctx: CodeReviewCtx): void {\n ctx.inject(['webServer'], (srv) => {\n srv.effect(() => {\n // register \u8FD4\u56DE disposer;\u63D2\u4EF6\u5378\u8F7D/HMR \u91CD\u8F7D\u65F6\u81EA\u52A8\u6458\u9664\u8DEF\u7531\u3002\n const unregister = srv.webServer.register({\n kind: 'exact',\n path: DIFF_ROUTE_PATH,\n handler: handleDiffRequest,\n })\n return unregister\n }, `dsh-code-review: ${DIFF_ROUTE_PATH} diff file route`)\n })\n}\n"],
|
|
5
|
+
"mappings": ";AAgBA,SAAS,MAAM,gBAAgB;AAE/B,SAAS,eAAe;AACxB,SAAS,YAAY,MAAM,eAAe;AAGnC,IAAM,kBAAkB;AAExB,IAAM,iBAAiB,IAAI,OAAO;AAyClC,SAAS,gBAAgB,KAA4B;AAC1D,MAAI,IAAI,IAAI,KAAK;AACjB,MAAI,MAAM,MAAM,EAAE,SAAS,IAAI,EAAG,QAAO;AACzC,MAAI,EAAE,WAAW,IAAI,EAAG,KAAI,KAAK,QAAQ,GAAG,EAAE,MAAM,CAAC,CAAC;AACtD,MAAI,CAAC,WAAW,CAAC,EAAG,QAAO;AAC3B,MAAI,CAAC,qBAAqB,KAAK,CAAC,EAAG,QAAO;AAC1C,SAAO,QAAQ,CAAC;AAClB;AAOA,eAAsB,aAAa,SAA0C;AAC3E,MAAI;AACF,UAAM,KAAK,MAAM,KAAK,OAAO;AAC7B,QAAI,CAAC,GAAG,OAAO,EAAG,QAAO,EAAE,IAAI,OAAO,OAAO,YAAY,QAAQ,IAAI;AACrE,QAAI,GAAG,OAAO,eAAgB,QAAO,EAAE,IAAI,OAAO,OAAO,aAAa,QAAQ,IAAI;AAClF,UAAM,OAAO,MAAM,SAAS,SAAS,MAAM;AAC3C,WAAO,EAAE,IAAI,MAAM,KAAK;AAAA,EAC1B,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,YAAa,IAA8B,SAAS,WAAW;AACzG,aAAO,EAAE,IAAI,OAAO,OAAO,aAAa,QAAQ,IAAI;AAAA,IACtD;AACA,WAAO,EAAE,IAAI,OAAO,OAAO,cAAc,QAAQ,IAAI;AAAA,EACvD;AACF;AAGA,SAAS,SAAS,KAAqB,QAAgB,MAAqB;AAC1E,QAAM,UAAU,KAAK,UAAU,IAAI;AACnC,MAAI,UAAU,QAAQ;AAAA,IACpB,gBAAgB;AAAA,IAChB,kBAAkB,OAAO,WAAW,OAAO;AAAA,IAC3C,iBAAiB;AAAA,IACjB,0BAA0B;AAAA,EAC5B,CAAC;AACD,MAAI,IAAI,OAAO;AACjB;AAMA,eAAe,kBAAkB,KAAsB,KAAoC;AACzF,MAAI,IAAI,WAAW,SAAS,IAAI,WAAW,QAAQ;AACjD,aAAS,KAAK,KAAK,EAAE,IAAI,OAAO,OAAO,qBAAqB,CAAC;AAC7D;AAAA,EACF;AACA,MAAI,MAAM;AACV,MAAI;AACF,UAAM,IAAI,IAAI,IAAI,OAAO,KAAK,kBAAkB,EAAE,aAAa,IAAI,MAAM,KAAK;AAAA,EAChF,QAAQ;AACN,aAAS,KAAK,KAAK,EAAE,IAAI,OAAO,OAAO,eAAe,CAAC;AACvD;AAAA,EACF;AACA,QAAM,MAAM,gBAAgB,GAAG;AAC/B,MAAI,QAAQ,MAAM;AAChB,aAAS,KAAK,KAAK,EAAE,IAAI,OAAO,OAAO,eAAe,CAAC;AACvD;AAAA,EACF;AACA,QAAM,SAAS,MAAM,aAAa,GAAG;AACrC,MAAI,CAAC,OAAO,IAAI;AACd,aAAS,KAAK,OAAO,QAAQ,EAAE,IAAI,OAAO,OAAO,OAAO,MAAM,CAAC;AAC/D;AAAA,EACF;AACA,WAAS,KAAK,KAAK,EAAE,IAAI,MAAM,MAAM,OAAO,KAAK,CAAC;AACpD;AAMO,SAAS,MAAM,KAA0B;AAC9C,MAAI,OAAO,CAAC,WAAW,GAAG,CAAC,QAAQ;AACjC,QAAI,OAAO,MAAM;AAEf,YAAM,aAAa,IAAI,UAAU,SAAS;AAAA,QACxC,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,MACX,CAAC;AACD,aAAO;AAAA,IACT,GAAG,oBAAoB,eAAe,kBAAkB;AAAA,EAC1D,CAAC;AACH;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,2 +1,18 @@
|
|
|
1
|
-
/** dsh-code-review 插件,node
|
|
2
|
-
|
|
1
|
+
/** dsh-code-review 插件,node 半:注册本地 diff 文件读取路由。 */
|
|
2
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
3
|
+
/** webServer 服务的最小形状。 */
|
|
4
|
+
export interface WebServerLike {
|
|
5
|
+
register(route: { kind: 'exact'; path: string; handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void> }): () => void;
|
|
6
|
+
}
|
|
7
|
+
/** 插件 apply 收到的宿主上下文最小形状。 */
|
|
8
|
+
export interface CodeReviewCtx {
|
|
9
|
+
inject(deps: readonly string[], callback: (ctx: CodeReviewCtx & { webServer: WebServerLike }) => unknown): unknown;
|
|
10
|
+
effect(fn: () => void | (() => void), name?: string): unknown;
|
|
11
|
+
}
|
|
12
|
+
export declare const DIFF_ROUTE_PATH: string;
|
|
13
|
+
export declare const MAX_DIFF_BYTES: number;
|
|
14
|
+
export type DiffLoadError = 'invalid-path' | 'not-found' | 'not-file' | 'too-large' | 'read-error';
|
|
15
|
+
export type DiffLoadResult = { ok: true; text: string } | { ok: false; error: DiffLoadError; status: number };
|
|
16
|
+
export declare function resolveDiffPath(raw: string): string | null;
|
|
17
|
+
export declare function loadDiffText(absPath: string): Promise<DiffLoadResult>;
|
|
18
|
+
export declare function apply(ctx: CodeReviewCtx): void;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yangzhe1991/dsh-code-review",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "DSH Web 插件:git diff 一键打开独立双列 Code Review
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "DSH Web 插件:git diff 一键打开独立双列 Code Review 页,支持语法高亮、行内评论与中英双语;对话中的本地 .diff/.patch 路径自动变成一键查看按钮 | DSH web plugin: one-click standalone two-column code review page for git diff with syntax highlighting, inline comments & bilingual UI; local .diff/.patch paths become one-click view buttons",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"react": "^18.2.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
+
"@types/node": "^26.4.1",
|
|
65
66
|
"@types/react": "^18.3.0",
|
|
66
67
|
"esbuild": "^0.25.0",
|
|
67
68
|
"typescript": "^5.6.0"
|