difit 1.1.8 → 1.1.10
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 +1 -0
- package/dist/cli/index.js +41 -6
- package/dist/cli/utils.d.ts +4 -0
- package/dist/cli/utils.js +22 -2
- package/dist/client/assets/index-CSVORGii.css +1 -0
- package/dist/client/assets/index-C_j9y-F8.js +187 -0
- package/dist/client/assets/{prism-java-BnGaC8QK.js → prism-java-Be0vSAlS.js} +1 -1
- package/dist/client/assets/{prism-php-BghhkkC7.js → prism-php-Duzkne60.js} +1 -1
- package/dist/client/assets/{prism-ruby-CubCUQWk.js → prism-ruby-BEvvSO-t.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/git-diff.js +4 -0
- package/dist/server/server.d.ts +3 -0
- package/dist/server/server.js +23 -11
- package/dist/server/server.test.js +0 -87
- package/dist/tui/App.d.ts +1 -0
- package/dist/tui/App.js +4 -4
- package/dist/tui/components/StatusBar.d.ts +1 -1
- package/dist/tui/components/StatusBar.js +1 -1
- package/dist/types/diff.d.ts +2 -0
- package/package.json +3 -1
- package/dist/client/assets/index-BtXScDaj.js +0 -187
- package/dist/client/assets/index-CUdTIauN.css +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{g as u}from"./index-
|
|
1
|
+
import{g as u}from"./index-C_j9y-F8.js";function p(t,a){for(var r=0;r<a.length;r++){const e=a[r];if(typeof e!="string"&&!Array.isArray(e)){for(const s in e)if(s!=="default"&&!(s in t)){const n=Object.getOwnPropertyDescriptor(e,s);n&&Object.defineProperty(t,s,n.get?n:{enumerable:!0,get:()=>e[s]})}}}return Object.freeze(Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}))}var o={},i;function d(){return i||(i=1,function(t){var a=/\b(?:abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|exports|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|module|native|new|non-sealed|null|open|opens|package|permits|private|protected|provides|public|record(?!\s*[(){}[\]<>=%~.:,;?+\-*/&|^])|requires|return|sealed|short|static|strictfp|super|switch|synchronized|this|throw|throws|to|transient|transitive|try|uses|var|void|volatile|while|with|yield)\b/,r=/(?:[a-z]\w*\s*\.\s*)*(?:[A-Z]\w*\s*\.\s*)*/.source,e={pattern:RegExp(/(^|[^\w.])/.source+r+/[A-Z](?:[\d_A-Z]*[a-z]\w*)?\b/.source),lookbehind:!0,inside:{namespace:{pattern:/^[a-z]\w*(?:\s*\.\s*[a-z]\w*)*(?:\s*\.)?/,inside:{punctuation:/\./}},punctuation:/\./}};t.languages.java=t.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"/,lookbehind:!0,greedy:!0},"class-name":[e,{pattern:RegExp(/(^|[^\w.])/.source+r+/[A-Z]\w*(?=\s+\w+\s*[;,=()]|\s*(?:\[[\s,]*\]\s*)?::\s*new\b)/.source),lookbehind:!0,inside:e.inside},{pattern:RegExp(/(\b(?:class|enum|extends|implements|instanceof|interface|new|record|throws)\s+)/.source+r+/[A-Z]\w*\b/.source),lookbehind:!0,inside:e.inside}],keyword:a,function:[t.languages.clike.function,{pattern:/(::\s*)[a-z_]\w*/,lookbehind:!0}],number:/\b0b[01][01_]*L?\b|\b0x(?:\.[\da-f_p+-]+|[\da-f_]+(?:\.[\da-f_p+-]+)?)\b|(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?\d[\d_]*)?[dfl]?/i,operator:{pattern:/(^|[^.])(?:<<=?|>>>?=?|->|--|\+\+|&&|\|\||::|[?:~]|[-+*/%&|^!=<>]=?)/m,lookbehind:!0},constant:/\b[A-Z][A-Z_\d]+\b/}),t.languages.insertBefore("java","string",{"triple-quoted-string":{pattern:/"""[ \t]*[\r\n](?:(?:"|"")?(?:\\.|[^"\\]))*"""/,greedy:!0,alias:"string"},char:{pattern:/'(?:\\.|[^'\\\r\n]){1,6}'/,greedy:!0}}),t.languages.insertBefore("java","class-name",{annotation:{pattern:/(^|[^.])@\w+(?:\s*\.\s*\w+)*/,lookbehind:!0,alias:"punctuation"},generics:{pattern:/<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&))*>)*>)*>)*>/,inside:{"class-name":e,keyword:a,punctuation:/[<>(),.:]/,operator:/[?&|]/}},import:[{pattern:RegExp(/(\bimport\s+)/.source+r+/(?:[A-Z]\w*|\*)(?=\s*;)/.source),lookbehind:!0,inside:{namespace:e.inside.namespace,punctuation:/\./,operator:/\*/,"class-name":/\w+/}},{pattern:RegExp(/(\bimport\s+static\s+)/.source+r+/(?:\w+|\*)(?=\s*;)/.source),lookbehind:!0,alias:"static",inside:{namespace:e.inside.namespace,static:/\b\w+$/,punctuation:/\./,operator:/\*/,"class-name":/\w+/}}],namespace:{pattern:RegExp(/(\b(?:exports|import(?:\s+static)?|module|open|opens|package|provides|requires|to|transitive|uses|with)\s+)(?!<keyword>)[a-z]\w*(?:\.[a-z]\w*)*\.?/.source.replace(/<keyword>/g,function(){return a.source})),lookbehind:!0,inside:{punctuation:/\./}}})}(Prism)),o}var c=d();const l=u(c),w=p({__proto__:null,default:l},[c]);export{w as p};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{g as c}from"./index-
|
|
1
|
+
import{g as c}from"./index-C_j9y-F8.js";function f(e,r){for(var n=0;n<r.length;n++){const t=r[n];if(typeof t!="string"&&!Array.isArray(t)){for(const a in t)if(a!=="default"&&!(a in e)){const i=Object.getOwnPropertyDescriptor(t,a);i&&Object.defineProperty(e,a,i.get?i:{enumerable:!0,get:()=>t[a]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}var u={},d;function g(){return d||(d=1,function(e){var r=/\/\*[\s\S]*?\*\/|\/\/.*|#(?!\[).*/,n=[{pattern:/\b(?:false|true)\b/i,alias:"boolean"},{pattern:/(::\s*)\b[a-z_]\w*\b(?!\s*\()/i,greedy:!0,lookbehind:!0},{pattern:/(\b(?:case|const)\s+)\b[a-z_]\w*(?=\s*[;=])/i,greedy:!0,lookbehind:!0},/\b(?:null)\b/i,/\b[A-Z_][A-Z0-9_]*\b(?!\s*\()/],t=/\b0b[01]+(?:_[01]+)*\b|\b0o[0-7]+(?:_[0-7]+)*\b|\b0x[\da-f]+(?:_[\da-f]+)*\b|(?:\b\d+(?:_\d+)*\.?(?:\d+(?:_\d+)*)?|\B\.\d+)(?:e[+-]?\d+)?/i,a=/<?=>|\?\?=?|\.{3}|\??->|[!=]=?=?|::|\*\*=?|--|\+\+|&&|\|\||<<|>>|[?~]|[/^|%*&<>.+-]=?/,i=/[{}\[\](),:;]/;e.languages.php={delimiter:{pattern:/\?>$|^<\?(?:php(?=\s)|=)?/i,alias:"important"},comment:r,variable:/\$+(?:\w+\b|(?=\{))/,package:{pattern:/(namespace\s+|use\s+(?:function\s+)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,lookbehind:!0,inside:{punctuation:/\\/}},"class-name-definition":{pattern:/(\b(?:class|enum|interface|trait)\s+)\b[a-z_]\w*(?!\\)\b/i,lookbehind:!0,alias:"class-name"},"function-definition":{pattern:/(\bfunction\s+)[a-z_]\w*(?=\s*\()/i,lookbehind:!0,alias:"function"},keyword:[{pattern:/(\(\s*)\b(?:array|bool|boolean|float|int|integer|object|string)\b(?=\s*\))/i,alias:"type-casting",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)\b(?:array(?!\s*\()|bool|callable|(?:false|null)(?=\s*\|)|float|int|iterable|mixed|object|self|static|string)\b(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b(?:array(?!\s*\()|bool|callable|(?:false|null)(?=\s*\|)|float|int|iterable|mixed|never|object|self|static|string|void)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/\b(?:array(?!\s*\()|bool|float|int|iterable|mixed|object|string|void)\b/i,alias:"type-declaration",greedy:!0},{pattern:/(\|\s*)(?:false|null)\b|\b(?:false|null)(?=\s*\|)/i,alias:"type-declaration",greedy:!0,lookbehind:!0},{pattern:/\b(?:parent|self|static)(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(\byield\s+)from\b/i,lookbehind:!0},/\bclass\b/i,{pattern:/((?:^|[^\s>:]|(?:^|[^-])>|(?:^|[^:]):)\s*)\b(?:abstract|and|array|as|break|callable|case|catch|clone|const|continue|declare|default|die|do|echo|else|elseif|empty|enddeclare|endfor|endforeach|endif|endswitch|endwhile|enum|eval|exit|extends|final|finally|fn|for|foreach|function|global|goto|if|implements|include|include_once|instanceof|insteadof|interface|isset|list|match|namespace|never|new|or|parent|print|private|protected|public|readonly|require|require_once|return|self|static|switch|throw|trait|try|unset|use|var|while|xor|yield|__halt_compiler)\b/i,lookbehind:!0}],"argument-name":{pattern:/([(,]\s*)\b[a-z_]\w*(?=\s*:(?!:))/i,lookbehind:!0},"class-name":[{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self|\s+static))\s+|\bcatch\s*\()\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/(\|\s*)\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/\b[a-z_]\w*(?!\\)\b(?=\s*\|)/i,greedy:!0},{pattern:/(\|\s*)(?:\\?\b[a-z_]\w*)+\b/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(?:\\?\b[a-z_]\w*)+\b(?=\s*\|)/i,alias:"class-name-fully-qualified",greedy:!0,inside:{punctuation:/\\/}},{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self\b|\s+static\b))\s+|\bcatch\s*\()(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*\$)/i,alias:"type-declaration",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-declaration"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*::)/i,alias:["class-name-fully-qualified","static-context"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/([(,?]\s*)[a-z_]\w*(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-hint"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b[a-z_]\w*(?!\\)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:["class-name-fully-qualified","return-type"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:n,function:{pattern:/(^|[^\\\w])\\?[a-z_](?:[\w\\]*\w)?(?=\s*\()/i,lookbehind:!0,inside:{punctuation:/\\/}},property:{pattern:/(->\s*)\w+/,lookbehind:!0},number:t,operator:a,punctuation:i};var l={pattern:/\{\$(?:\{(?:\{[^{}]+\}|[^{}]+)\}|[^{}])+\}|(^|[^\\{])\$+(?:\w+(?:\[[^\r\n\[\]]+\]|->\w+)?)/,lookbehind:!0,inside:e.languages.php},o=[{pattern:/<<<'([^']+)'[\r\n](?:.*[\r\n])*?\1;/,alias:"nowdoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<'[^']+'|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<'?|[';]$/}}}},{pattern:/<<<(?:"([^"]+)"[\r\n](?:.*[\r\n])*?\1;|([a-z_]\w*)[\r\n](?:.*[\r\n])*?\2;)/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<(?:"[^"]+"|[a-z_]\w*)|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<"?|[";]$/}},interpolation:l}},{pattern:/`(?:\\[\s\S]|[^\\`])*`/,alias:"backtick-quoted-string",greedy:!0},{pattern:/'(?:\\[\s\S]|[^\\'])*'/,alias:"single-quoted-string",greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,alias:"double-quoted-string",greedy:!0,inside:{interpolation:l}}];e.languages.insertBefore("php","variable",{string:o,attribute:{pattern:/#\[(?:[^"'\/#]|\/(?![*/])|\/\/.*$|#(?!\[).*$|\/\*(?:[^*]|\*(?!\/))*\*\/|"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*')+\](?=\s*[a-z$#])/im,greedy:!0,inside:{"attribute-content":{pattern:/^(#\[)[\s\S]+(?=\]$)/,lookbehind:!0,inside:{comment:r,string:o,"attribute-class-name":[{pattern:/([^:]|^)\b[a-z_]\w*(?!\\)\b/i,alias:"class-name",greedy:!0,lookbehind:!0},{pattern:/([^:]|^)(?:\\?\b[a-z_]\w*)+/i,alias:["class-name","class-name-fully-qualified"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:n,number:t,operator:a,punctuation:i}},delimiter:{pattern:/^#\[|\]$/,alias:"punctuation"}}}}),e.hooks.add("before-tokenize",function(s){if(/<\?/.test(s.code)){var p=/<\?(?:[^"'/#]|\/(?![*/])|("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|(?:\/\/|#(?!\[))(?:[^?\n\r]|\?(?!>))*(?=$|\?>|[\r\n])|#\[|\/\*(?:[^*]|\*(?!\/))*(?:\*\/|$))*?(?:\?>|$)/g;e.languages["markup-templating"].buildPlaceholders(s,"php",p)}}),e.hooks.add("after-tokenize",function(s){e.languages["markup-templating"].tokenizePlaceholders(s,"php")})}(Prism)),u}var b=g();const y=c(b),m=f({__proto__:null,default:y},[b]);export{m as p};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{g as l}from"./index-
|
|
1
|
+
import{g as l}from"./index-C_j9y-F8.js";function d(e,r){for(var n=0;n<r.length;n++){const t=r[n];if(typeof t!="string"&&!Array.isArray(t)){for(const i in t)if(i!=="default"&&!(i in e)){const s=Object.getOwnPropertyDescriptor(t,i);s&&Object.defineProperty(e,i,s.get?s:{enumerable:!0,get:()=>t[i]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}var a={},o;function g(){return o||(o=1,function(e){e.languages.ruby=e.languages.extend("clike",{comment:{pattern:/#.*|^=begin\s[\s\S]*?^=end/m,greedy:!0},"class-name":{pattern:/(\b(?:class|module)\s+|\bcatch\s+\()[\w.\\]+|\b[A-Z_]\w*(?=\s*\.\s*new\b)/,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:BEGIN|END|alias|and|begin|break|case|class|def|define_method|defined|do|each|else|elsif|end|ensure|extend|for|if|in|include|module|new|next|nil|not|or|prepend|private|protected|public|raise|redo|require|rescue|retry|return|self|super|then|throw|undef|unless|until|when|while|yield)\b/,operator:/\.{2,3}|&\.|===|<?=>|[!=]?~|(?:&&|\|\||<<|>>|\*\*|[+\-*/%<>!^&|=])=?|[?:]/,punctuation:/[(){}[\].,;]/}),e.languages.insertBefore("ruby","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}});var r={pattern:/((?:^|[^\\])(?:\\{2})*)#\{(?:[^{}]|\{[^{}]*\})*\}/,lookbehind:!0,inside:{content:{pattern:/^(#\{)[\s\S]+(?=\}$)/,lookbehind:!0,inside:e.languages.ruby},delimiter:{pattern:/^#\{|\}$/,alias:"punctuation"}}};delete e.languages.ruby.function;var n="(?:"+[/([^a-zA-Z0-9\s{(\[<=])(?:(?!\1)[^\\]|\\[\s\S])*\1/.source,/\((?:[^()\\]|\\[\s\S]|\((?:[^()\\]|\\[\s\S])*\))*\)/.source,/\{(?:[^{}\\]|\\[\s\S]|\{(?:[^{}\\]|\\[\s\S])*\})*\}/.source,/\[(?:[^\[\]\\]|\\[\s\S]|\[(?:[^\[\]\\]|\\[\s\S])*\])*\]/.source,/<(?:[^<>\\]|\\[\s\S]|<(?:[^<>\\]|\\[\s\S])*>)*>/.source].join("|")+")",t=/(?:"(?:\\.|[^"\\\r\n])*"|(?:\b[a-zA-Z_]\w*|[^\s\0-\x7F]+)[?!]?|\$.)/.source;e.languages.insertBefore("ruby","keyword",{"regex-literal":[{pattern:RegExp(/%r/.source+n+/[egimnosux]{0,6}/.source),greedy:!0,inside:{interpolation:r,regex:/[\s\S]+/}},{pattern:/(^|[^/])\/(?!\/)(?:\[[^\r\n\]]+\]|\\.|[^[/\\\r\n])+\/[egimnosux]{0,6}(?=\s*(?:$|[\r\n,.;})#]))/,lookbehind:!0,greedy:!0,inside:{interpolation:r,regex:/[\s\S]+/}}],variable:/[@$]+[a-zA-Z_]\w*(?:[?!]|\b)/,symbol:[{pattern:RegExp(/(^|[^:]):/.source+t),lookbehind:!0,greedy:!0},{pattern:RegExp(/([\r\n{(,][ \t]*)/.source+t+/(?=:(?!:))/.source),lookbehind:!0,greedy:!0}],"method-definition":{pattern:/(\bdef\s+)\w+(?:\s*\.\s*\w+)?/,lookbehind:!0,inside:{function:/\b\w+$/,keyword:/^self\b/,"class-name":/^\w+/,punctuation:/\./}}}),e.languages.insertBefore("ruby","string",{"string-literal":[{pattern:RegExp(/%[qQiIwWs]?/.source+n),greedy:!0,inside:{interpolation:r,string:/[\s\S]+/}},{pattern:/("|')(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|(?!\1)[^\\#\r\n])*\1/,greedy:!0,inside:{interpolation:r,string:/[\s\S]+/}},{pattern:/<<[-~]?([a-z_]\w*)[\r\n](?:.*[\r\n])*?[\t ]*\1/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<[-~]?[a-z_]\w*|\b[a-z_]\w*$/i,inside:{symbol:/\b\w+/,punctuation:/^<<[-~]?/}},interpolation:r,string:/[\s\S]+/}},{pattern:/<<[-~]?'([a-z_]\w*)'[\r\n](?:.*[\r\n])*?[\t ]*\1/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<[-~]?'[a-z_]\w*'|\b[a-z_]\w*$/i,inside:{symbol:/\b\w+/,punctuation:/^<<[-~]?'|'$/}},string:/[\s\S]+/}}],"command-literal":[{pattern:RegExp(/%x/.source+n),greedy:!0,inside:{interpolation:r,command:{pattern:/[\s\S]+/,alias:"string"}}},{pattern:/`(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|[^\\`#\r\n])*`/,greedy:!0,inside:{interpolation:r,command:{pattern:/[\s\S]+/,alias:"string"}}}]}),delete e.languages.ruby.string,e.languages.insertBefore("ruby","number",{builtin:/\b(?:Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Fixnum|Float|Hash|IO|Integer|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|Stat|String|Struct|Symbol|TMS|Thread|ThreadGroup|Time|TrueClass)\b/,constant:/\b[A-Z][A-Z0-9_]*(?:[?!]|\b)/}),e.languages.rb=e.languages.ruby}(Prism)),a}var u=g();const p=l(u),b=d({__proto__:null,default:p},[u]);export{b as p};
|
package/dist/client/index.html
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
<link rel="icon" type="image/png" sizes="512x512" href="/icon-512x512.png" />
|
|
11
11
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
12
12
|
<title>ReviewIt - Git Diff Viewer</title>
|
|
13
|
-
<script type="module" crossorigin src="/assets/index-
|
|
14
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
13
|
+
<script type="module" crossorigin src="/assets/index-C_j9y-F8.js"></script>
|
|
14
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CSVORGii.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
17
17
|
<div id="root"></div>
|
package/dist/server/git-diff.js
CHANGED
|
@@ -42,6 +42,9 @@ export class GitDiffParser {
|
|
|
42
42
|
if (ignoreWhitespace) {
|
|
43
43
|
diffArgs.push('-w');
|
|
44
44
|
}
|
|
45
|
+
// Ignore external diff-tools to unify output.
|
|
46
|
+
// https://github.com/yoshiko-pg/reviewit/issues/19
|
|
47
|
+
diffArgs.push('--no-ext-diff', '--color=never');
|
|
45
48
|
// Add --color=never to ensure plain text output without ANSI escape sequences
|
|
46
49
|
const diffSummary = await this.git.diffSummary(diffArgs);
|
|
47
50
|
const diffRaw = await this.git.diff(['--color=never', ...diffArgs]);
|
|
@@ -49,6 +52,7 @@ export class GitDiffParser {
|
|
|
49
52
|
return {
|
|
50
53
|
commit: resolvedCommit,
|
|
51
54
|
files,
|
|
55
|
+
isEmpty: files.length === 0,
|
|
52
56
|
};
|
|
53
57
|
}
|
|
54
58
|
catch (error) {
|
package/dist/server/server.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ interface ServerOptions {
|
|
|
2
2
|
targetCommitish: string;
|
|
3
3
|
baseCommitish: string;
|
|
4
4
|
preferredPort?: number;
|
|
5
|
+
host?: string;
|
|
5
6
|
openBrowser?: boolean;
|
|
6
7
|
mode?: string;
|
|
7
8
|
ignoreWhitespace?: boolean;
|
|
@@ -9,5 +10,7 @@ interface ServerOptions {
|
|
|
9
10
|
export declare function startServer(options: ServerOptions): Promise<{
|
|
10
11
|
port: number;
|
|
11
12
|
url: string;
|
|
13
|
+
isEmpty?: boolean;
|
|
14
|
+
server?: any;
|
|
12
15
|
}>;
|
|
13
16
|
export {};
|
package/dist/server/server.js
CHANGED
|
@@ -10,6 +10,7 @@ export async function startServer(options) {
|
|
|
10
10
|
const parser = new GitDiffParser();
|
|
11
11
|
let diffData = null;
|
|
12
12
|
let currentIgnoreWhitespace = options.ignoreWhitespace || false;
|
|
13
|
+
const diffMode = options.mode || 'side-by-side';
|
|
13
14
|
app.use(express.json());
|
|
14
15
|
app.use(express.text()); // For sendBeacon text/plain requests
|
|
15
16
|
app.use((_req, res, next) => {
|
|
@@ -29,7 +30,7 @@ export async function startServer(options) {
|
|
|
29
30
|
currentIgnoreWhitespace = ignoreWhitespace;
|
|
30
31
|
diffData = await parser.parseDiff(options.targetCommitish, options.baseCommitish, ignoreWhitespace);
|
|
31
32
|
}
|
|
32
|
-
res.json({ ...diffData, ignoreWhitespace });
|
|
33
|
+
res.json({ ...diffData, ignoreWhitespace, mode: diffMode });
|
|
33
34
|
});
|
|
34
35
|
// Store comments for final output
|
|
35
36
|
let finalComments = [];
|
|
@@ -134,8 +135,18 @@ export async function startServer(options) {
|
|
|
134
135
|
`);
|
|
135
136
|
});
|
|
136
137
|
}
|
|
137
|
-
const { port, url } = await startServerWithFallback(app, options.preferredPort || 3000);
|
|
138
|
-
|
|
138
|
+
const { port, url, server } = await startServerWithFallback(app, options.preferredPort || 3000, options.host || '127.0.0.1');
|
|
139
|
+
// Security warning for non-localhost binding
|
|
140
|
+
if (options.host && options.host !== '127.0.0.1' && options.host !== 'localhost') {
|
|
141
|
+
console.warn('\n⚠️ WARNING: Server is accessible from external network!');
|
|
142
|
+
console.warn(` Binding to: ${options.host}:${port}`);
|
|
143
|
+
console.warn(' Make sure this is intended and your network is secure.\n');
|
|
144
|
+
}
|
|
145
|
+
// Check if diff is empty and skip browser opening
|
|
146
|
+
if (diffData.isEmpty) {
|
|
147
|
+
// Don't open browser if no differences found
|
|
148
|
+
}
|
|
149
|
+
else if (options.openBrowser) {
|
|
139
150
|
try {
|
|
140
151
|
await open(url);
|
|
141
152
|
}
|
|
@@ -143,17 +154,18 @@ export async function startServer(options) {
|
|
|
143
154
|
console.warn('Failed to open browser automatically');
|
|
144
155
|
}
|
|
145
156
|
}
|
|
146
|
-
return { port, url };
|
|
157
|
+
return { port, url, isEmpty: diffData.isEmpty, server };
|
|
147
158
|
}
|
|
148
|
-
async function startServerWithFallback(app, preferredPort) {
|
|
159
|
+
async function startServerWithFallback(app, preferredPort, host) {
|
|
149
160
|
return new Promise((resolve, reject) => {
|
|
150
161
|
// express's listen() method uses listen() method in node:net Server instance internally
|
|
151
162
|
// https://expressjs.com/en/5x/api.html#app.listen
|
|
152
163
|
// so, an error will be an instance of NodeJS.ErrnoException
|
|
153
|
-
app.listen(preferredPort,
|
|
154
|
-
const
|
|
164
|
+
const server = app.listen(preferredPort, host, (err) => {
|
|
165
|
+
const displayHost = host === '0.0.0.0' ? 'localhost' : host;
|
|
166
|
+
const url = `http://${displayHost}:${preferredPort}`;
|
|
155
167
|
if (!err) {
|
|
156
|
-
resolve({ port: preferredPort, url });
|
|
168
|
+
resolve({ port: preferredPort, url, server });
|
|
157
169
|
return;
|
|
158
170
|
}
|
|
159
171
|
// Handling errors when failed to launch a server
|
|
@@ -161,9 +173,9 @@ async function startServerWithFallback(app, preferredPort) {
|
|
|
161
173
|
// Try another port until it succeeds
|
|
162
174
|
case 'EADDRINUSE': {
|
|
163
175
|
console.log(`Port ${preferredPort} is busy, trying ${preferredPort + 1}...`);
|
|
164
|
-
return startServerWithFallback(app, preferredPort + 1)
|
|
165
|
-
.then(({ port, url }) => {
|
|
166
|
-
resolve({ port, url });
|
|
176
|
+
return startServerWithFallback(app, preferredPort + 1, host)
|
|
177
|
+
.then(({ port, url, server }) => {
|
|
178
|
+
resolve({ port, url, server });
|
|
167
179
|
})
|
|
168
180
|
.catch(reject);
|
|
169
181
|
}
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
// Set environment variable to skip fetch mocking
|
|
3
|
-
process.env.VITEST_SERVER_TEST = 'true';
|
|
4
2
|
import { startServer } from './server.js';
|
|
5
|
-
// Add fetch polyfill for Node.js test environment
|
|
6
|
-
const { fetch } = await import('undici');
|
|
7
|
-
globalThis.fetch = fetch;
|
|
8
3
|
// Mock GitDiffParser
|
|
9
4
|
vi.mock('./git-diff.js', () => ({
|
|
10
5
|
GitDiffParser: vi.fn().mockImplementation(() => ({
|
|
@@ -25,7 +20,6 @@ vi.mock('./git-diff.js', () => ({
|
|
|
25
20
|
stats: { additions: 10, deletions: 5 },
|
|
26
21
|
isEmpty: false,
|
|
27
22
|
}),
|
|
28
|
-
getBlobContent: vi.fn().mockResolvedValue(Buffer.from('mock image data')),
|
|
29
23
|
})),
|
|
30
24
|
}));
|
|
31
25
|
describe('Server Integration Tests', () => {
|
|
@@ -298,85 +292,4 @@ describe('Server Integration Tests', () => {
|
|
|
298
292
|
expect(response.headers.get('Access-Control-Allow-Headers')).toBe('Origin, X-Requested-With, Content-Type, Accept');
|
|
299
293
|
});
|
|
300
294
|
});
|
|
301
|
-
describe('Blob API endpoints', () => {
|
|
302
|
-
let port;
|
|
303
|
-
beforeEach(async () => {
|
|
304
|
-
const result = await startServer({
|
|
305
|
-
targetCommitish: 'HEAD',
|
|
306
|
-
baseCommitish: 'HEAD^',
|
|
307
|
-
preferredPort: 9060,
|
|
308
|
-
});
|
|
309
|
-
servers.push(result.server);
|
|
310
|
-
port = result.port;
|
|
311
|
-
});
|
|
312
|
-
it('GET /api/blob/* returns file content for images', async () => {
|
|
313
|
-
const response = await fetch(`http://localhost:${port}/api/blob/image.jpg?ref=HEAD`);
|
|
314
|
-
expect(response.ok).toBe(true);
|
|
315
|
-
expect(response.headers.get('Content-Type')).toBe('image/jpeg');
|
|
316
|
-
expect(response.headers.get('Cache-Control')).toBe('no-cache, no-store, must-revalidate');
|
|
317
|
-
expect(response.headers.get('Pragma')).toBe('no-cache');
|
|
318
|
-
expect(response.headers.get('Expires')).toBe('0');
|
|
319
|
-
const buffer = await response.arrayBuffer();
|
|
320
|
-
expect(buffer.byteLength).toBeGreaterThan(0);
|
|
321
|
-
});
|
|
322
|
-
it('sets correct content type for different image formats', async () => {
|
|
323
|
-
const testCases = [
|
|
324
|
-
{ filename: 'photo.jpg', expectedType: 'image/jpeg' },
|
|
325
|
-
{ filename: 'photo.jpeg', expectedType: 'image/jpeg' },
|
|
326
|
-
{ filename: 'logo.png', expectedType: 'image/png' },
|
|
327
|
-
{ filename: 'animation.gif', expectedType: 'image/gif' },
|
|
328
|
-
{ filename: 'bitmap.bmp', expectedType: 'image/bmp' },
|
|
329
|
-
{ filename: 'vector.svg', expectedType: 'image/svg+xml' },
|
|
330
|
-
{ filename: 'modern.webp', expectedType: 'image/webp' },
|
|
331
|
-
{ filename: 'favicon.ico', expectedType: 'image/x-icon' },
|
|
332
|
-
{ filename: 'photo.tiff', expectedType: 'image/tiff' },
|
|
333
|
-
{ filename: 'photo.tif', expectedType: 'image/tiff' },
|
|
334
|
-
{ filename: 'modern.avif', expectedType: 'image/avif' },
|
|
335
|
-
{ filename: 'mobile.heic', expectedType: 'image/heic' },
|
|
336
|
-
{ filename: 'camera.heif', expectedType: 'image/heif' },
|
|
337
|
-
];
|
|
338
|
-
for (const { filename, expectedType } of testCases) {
|
|
339
|
-
const response = await fetch(`http://localhost:${port}/api/blob/${filename}?ref=HEAD`);
|
|
340
|
-
expect(response.headers.get('Content-Type')).toBe(expectedType);
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
it('sets default content type for unknown extensions', async () => {
|
|
344
|
-
const response = await fetch(`http://localhost:${port}/api/blob/unknown.xyz?ref=HEAD`);
|
|
345
|
-
expect(response.ok).toBe(true);
|
|
346
|
-
expect(response.headers.get('Content-Type')).toBe('application/octet-stream');
|
|
347
|
-
});
|
|
348
|
-
it('handles different git refs correctly', async () => {
|
|
349
|
-
const testRefs = ['HEAD', 'main', 'feature-branch', 'abc123'];
|
|
350
|
-
for (const ref of testRefs) {
|
|
351
|
-
const response = await fetch(`http://localhost:${port}/api/blob/image.jpg?ref=${ref}`);
|
|
352
|
-
expect(response.ok).toBe(true);
|
|
353
|
-
}
|
|
354
|
-
});
|
|
355
|
-
it('defaults to HEAD when no ref is provided', async () => {
|
|
356
|
-
const response = await fetch(`http://localhost:${port}/api/blob/image.jpg`);
|
|
357
|
-
expect(response.ok).toBe(true);
|
|
358
|
-
// Should use HEAD as default ref
|
|
359
|
-
});
|
|
360
|
-
it('handles file not found errors', async () => {
|
|
361
|
-
// Skip this test as mocking GitDiffParser in an already running server is complex
|
|
362
|
-
// The error handling is already covered by the actual implementation
|
|
363
|
-
});
|
|
364
|
-
it('handles large file errors appropriately', async () => {
|
|
365
|
-
// Skip this test as mocking GitDiffParser in an already running server is complex
|
|
366
|
-
// The error handling is already covered by the actual implementation
|
|
367
|
-
});
|
|
368
|
-
it('handles special characters in file paths', async () => {
|
|
369
|
-
const specialPaths = [
|
|
370
|
-
'folder/image with spaces.jpg',
|
|
371
|
-
'folder/image-with-dashes.png',
|
|
372
|
-
'folder/image_with_underscores.gif',
|
|
373
|
-
'folder/ιμαγε.jpg', // Unicode characters
|
|
374
|
-
];
|
|
375
|
-
for (const path of specialPaths) {
|
|
376
|
-
const encodedPath = encodeURIComponent(path);
|
|
377
|
-
const response = await fetch(`http://localhost:${port}/api/blob/${encodedPath}?ref=HEAD`);
|
|
378
|
-
expect(response.ok).toBe(true);
|
|
379
|
-
}
|
|
380
|
-
});
|
|
381
|
-
});
|
|
382
295
|
});
|
package/dist/tui/App.d.ts
CHANGED
package/dist/tui/App.js
CHANGED
|
@@ -5,12 +5,12 @@ import FileList from './components/FileList.js';
|
|
|
5
5
|
import DiffViewer from './components/DiffViewer.js';
|
|
6
6
|
import SideBySideDiffViewer from './components/SideBySideDiffViewer.js';
|
|
7
7
|
import StatusBar from './components/StatusBar.js';
|
|
8
|
-
const App = ({ targetCommitish, baseCommitish }) => {
|
|
8
|
+
const App = ({ targetCommitish, baseCommitish, mode }) => {
|
|
9
9
|
const [files, setFiles] = useState([]);
|
|
10
10
|
const [selectedFileIndex, setSelectedFileIndex] = useState(0);
|
|
11
11
|
const [loading, setLoading] = useState(true);
|
|
12
12
|
const [error, setError] = useState(null);
|
|
13
|
-
const [viewMode, setViewMode] = useState('side-by-side');
|
|
13
|
+
const [viewMode, setViewMode] = useState(mode === 'inline' ? 'inline' : 'side-by-side');
|
|
14
14
|
const { exit } = useApp();
|
|
15
15
|
const loadDiff = async () => {
|
|
16
16
|
setLoading(true);
|
|
@@ -49,7 +49,7 @@ const App = ({ targetCommitish, baseCommitish }) => {
|
|
|
49
49
|
setViewMode('side-by-side');
|
|
50
50
|
}
|
|
51
51
|
if (input === 'd') {
|
|
52
|
-
setViewMode('
|
|
52
|
+
setViewMode('inline');
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
else {
|
|
@@ -84,7 +84,7 @@ const App = ({ targetCommitish, baseCommitish }) => {
|
|
|
84
84
|
React.createElement(Box, { flexGrow: 1, flexDirection: "column" }, viewMode === 'list' ? (React.createElement(FileList, { files: files, selectedIndex: selectedFileIndex })) : viewMode === 'side-by-side' ? (React.createElement(SideBySideDiffViewer, { files: files, initialFileIndex: selectedFileIndex, onBack: () => setViewMode('list') })) : (React.createElement(DiffViewer, { files: files, initialFileIndex: selectedFileIndex, onBack: () => setViewMode('list') }))),
|
|
85
85
|
React.createElement(Box, { borderStyle: "single", paddingX: 1 },
|
|
86
86
|
React.createElement(Text, { dimColor: true }, viewMode === 'list'
|
|
87
|
-
? '↑/↓ or j/k: navigate | Enter/Space: side-by-side | d:
|
|
87
|
+
? '↑/↓ or j/k: navigate | Enter/Space: side-by-side | d: inline diff | r: reload | q: quit'
|
|
88
88
|
: viewMode === 'side-by-side'
|
|
89
89
|
? 'Tab: next file | Shift+Tab: prev | ↑/↓ or j/k: scroll | ESC/b: list | r: reload | q: quit'
|
|
90
90
|
: 'Tab: next | Shift+Tab: prev | ↑/↓ or j/k: scroll | ESC/b: list | r: reload | q: quit'))));
|
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
interface StatusBarProps {
|
|
3
3
|
commitish: string;
|
|
4
4
|
totalFiles: number;
|
|
5
|
-
currentMode: 'list' | '
|
|
5
|
+
currentMode: 'list' | 'inline' | 'side-by-side';
|
|
6
6
|
}
|
|
7
7
|
declare const StatusBar: React.FC<StatusBarProps>;
|
|
8
8
|
export default StatusBar;
|
package/dist/types/diff.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "difit",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.10",
|
|
4
4
|
"description": "A lightweight command-line tool that spins up a local web server to display Git commit diffs in a GitHub-like Files changed view",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -36,11 +36,13 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@octokit/rest": "^22.0.0",
|
|
38
38
|
"commander": "^14.0.0",
|
|
39
|
+
"dracula-prism": "^2.1.16",
|
|
39
40
|
"express": "^5.1.0",
|
|
40
41
|
"ink": "^6.0.1",
|
|
41
42
|
"lucide-react": "^0.525.0",
|
|
42
43
|
"open": "^10.1.2",
|
|
43
44
|
"prism-react-renderer": "^2.4.1",
|
|
45
|
+
"prism-themes": "^1.9.0",
|
|
44
46
|
"prismjs": "^1.30.0",
|
|
45
47
|
"react": "^19.1.0",
|
|
46
48
|
"react-dom": "^19.1.0",
|