difit 2.0.10 → 2.1.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.ja.md +192 -0
- package/README.ko.md +192 -0
- package/README.md +61 -44
- package/README.zh.md +192 -0
- package/dist/cli/index.js +50 -0
- package/dist/cli/index.test.js +141 -12
- package/dist/cli/utils.js +18 -3
- package/dist/cli/utils.test.js +10 -1
- package/dist/client/assets/index-BtavrLIu.css +1 -0
- package/dist/client/assets/index-Bx2n4Aep.js +210 -0
- package/dist/client/assets/{prism-csharp-68c6WkNx.js → prism-csharp-BTkEzOdP.js} +1 -1
- package/dist/client/assets/{prism-java-C8EIlB8E.js → prism-java-B6gV82l4.js} +1 -1
- package/dist/client/assets/{prism-php-DHZyM8JV.js → prism-php-gnpy0VQF.js} +1 -1
- package/dist/client/assets/prism-protobuf-DiQ_z8B5.js +1 -0
- package/dist/client/assets/{prism-ruby-MnFNFfyf.js → prism-ruby-CMkpRodx.js} +1 -1
- package/dist/client/assets/{prism-solidity-CIeB0O-m.js → prism-solidity-BDXCWkss.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/file-watcher.d.ts +23 -0
- package/dist/server/file-watcher.js +236 -0
- package/dist/server/file-watcher.test.d.ts +1 -0
- package/dist/server/file-watcher.test.js +225 -0
- package/dist/server/git-diff.d.ts +2 -0
- package/dist/server/git-diff.js +47 -4
- package/dist/server/git-diff.test.js +209 -0
- package/dist/server/server.d.ts +5 -2
- package/dist/server/server.js +66 -16
- package/dist/server/server.test.js +3 -3
- package/dist/types/watch.d.ts +30 -0
- package/dist/types/watch.js +8 -0
- package/package.json +3 -2
- package/dist/client/assets/index-DMBW6MaM.css +0 -1
- package/dist/client/assets/index-EuLpHPLj.js +0 -200
|
@@ -1 +1 @@
|
|
|
1
|
-
import{g as U}from"./index-
|
|
1
|
+
import{g as U}from"./index-Bx2n4Aep.js";function W(r,t){for(var e=0;e<t.length;e++){const a=t[e];if(typeof a!="string"&&!Array.isArray(a)){for(const s in a)if(s!=="default"&&!(s in r)){const u=Object.getOwnPropertyDescriptor(a,s);u&&Object.defineProperty(r,s,u.get?u:{enumerable:!0,get:()=>a[s]})}}}return Object.freeze(Object.defineProperty(r,Symbol.toStringTag,{value:"Module"}))}var I={},O;function Z(){return O||(O=1,function(r){function t(n,l){return n.replace(/<<(\d+)>>/g,function(f,M){return"(?:"+l[+M]+")"})}function e(n,l,f){return RegExp(t(n,l),"")}function a(n,l){for(var f=0;f<l;f++)n=n.replace(/<<self>>/g,function(){return"(?:"+n+")"});return n.replace(/<<self>>/g,"[^\\s\\S]")}var s={type:"bool byte char decimal double dynamic float int long object sbyte short string uint ulong ushort var void",typeDeclaration:"class enum interface record struct",contextual:"add alias and ascending async await by descending from(?=\\s*(?:\\w|$)) get global group into init(?=\\s*;) join let nameof not notnull on or orderby partial remove select set unmanaged value when where with(?=\\s*{)",other:"abstract as base break case catch checked const continue default delegate do else event explicit extern finally fixed for foreach goto if implicit in internal is lock namespace new null operator out override params private protected public readonly ref return sealed sizeof stackalloc static switch this throw try typeof unchecked unsafe using virtual volatile while yield"};function u(n){return"\\b(?:"+n.trim().replace(/ /g,"|")+")\\b"}var v=u(s.typeDeclaration),d=RegExp(u(s.type+" "+s.typeDeclaration+" "+s.contextual+" "+s.other)),T=u(s.typeDeclaration+" "+s.contextual+" "+s.other),z=u(s.type+" "+s.typeDeclaration+" "+s.other),g=a(/<(?:[^<>;=+\-*/%&|^]|<<self>>)*>/.source,2),h=a(/\((?:[^()]|<<self>>)*\)/.source,2),i=/@?\b[A-Za-z_]\w*\b/.source,b=t(/<<0>>(?:\s*<<1>>)?/.source,[i,g]),c=t(/(?!<<0>>)<<1>>(?:\s*\.\s*<<1>>)*/.source,[T,b]),y=/\[\s*(?:,\s*)*\]/.source,A=t(/<<0>>(?:\s*(?:\?\s*)?<<1>>)*(?:\s*\?)?/.source,[c,y]),K=t(/[^,()<>[\];=+\-*/%&|^]|<<0>>|<<1>>|<<2>>/.source,[g,h,y]),q=t(/\(<<0>>+(?:,<<0>>+)+\)/.source,[K]),p=t(/(?:<<0>>|<<1>>)(?:\s*(?:\?\s*)?<<2>>)*(?:\s*\?)?/.source,[q,c,y]),o={keyword:d,punctuation:/[<>()?,.:[\]]/},k=/'(?:[^\r\n'\\]|\\.|\\[Uux][\da-fA-F]{1,8})'/.source,w=/"(?:\\.|[^\\"\r\n])*"/.source,F=/@"(?:""|\\[\s\S]|[^\\"])*"(?!")/.source;r.languages.csharp=r.languages.extend("clike",{string:[{pattern:e(/(^|[^$\\])<<0>>/.source,[F]),lookbehind:!0,greedy:!0},{pattern:e(/(^|[^@$\\])<<0>>/.source,[w]),lookbehind:!0,greedy:!0}],"class-name":[{pattern:e(/(\busing\s+static\s+)<<0>>(?=\s*;)/.source,[c]),lookbehind:!0,inside:o},{pattern:e(/(\busing\s+<<0>>\s*=\s*)<<1>>(?=\s*;)/.source,[i,p]),lookbehind:!0,inside:o},{pattern:e(/(\busing\s+)<<0>>(?=\s*=)/.source,[i]),lookbehind:!0},{pattern:e(/(\b<<0>>\s+)<<1>>/.source,[v,b]),lookbehind:!0,inside:o},{pattern:e(/(\bcatch\s*\(\s*)<<0>>/.source,[c]),lookbehind:!0,inside:o},{pattern:e(/(\bwhere\s+)<<0>>/.source,[i]),lookbehind:!0},{pattern:e(/(\b(?:is(?:\s+not)?|as)\s+)<<0>>/.source,[A]),lookbehind:!0,inside:o},{pattern:e(/\b<<0>>(?=\s+(?!<<1>>|with\s*\{)<<2>>(?:\s*[=,;:{)\]]|\s+(?:in|when)\b))/.source,[p,z,i]),inside:o}],keyword:d,number:/(?:\b0(?:x[\da-f_]*[\da-f]|b[01_]*[01])|(?:\B\.\d+(?:_+\d+)*|\b\d+(?:_+\d+)*(?:\.\d+(?:_+\d+)*)?)(?:e[-+]?\d+(?:_+\d+)*)?)(?:[dflmu]|lu|ul)?\b/i,operator:/>>=?|<<=?|[-=]>|([-+&|])\1|~|\?\?=?|[-+*/%&|^!=<>]=?/,punctuation:/\?\.?|::|[{}[\];(),.:]/}),r.languages.insertBefore("csharp","number",{range:{pattern:/\.\./,alias:"operator"}}),r.languages.insertBefore("csharp","punctuation",{"named-parameter":{pattern:e(/([(,]\s*)<<0>>(?=\s*:)/.source,[i]),lookbehind:!0,alias:"punctuation"}}),r.languages.insertBefore("csharp","class-name",{namespace:{pattern:e(/(\b(?:namespace|using)\s+)<<0>>(?:\s*\.\s*<<0>>)*(?=\s*[;{])/.source,[i]),lookbehind:!0,inside:{punctuation:/\./}},"type-expression":{pattern:e(/(\b(?:default|sizeof|typeof)\s*\(\s*(?!\s))(?:[^()\s]|\s(?!\s)|<<0>>)*(?=\s*\))/.source,[h]),lookbehind:!0,alias:"class-name",inside:o},"return-type":{pattern:e(/<<0>>(?=\s+(?:<<1>>\s*(?:=>|[({]|\.\s*this\s*\[)|this\s*\[))/.source,[p,c]),inside:o,alias:"class-name"},"constructor-invocation":{pattern:e(/(\bnew\s+)<<0>>(?=\s*[[({])/.source,[p]),lookbehind:!0,inside:o,alias:"class-name"},"generic-method":{pattern:e(/<<0>>\s*<<1>>(?=\s*\()/.source,[i,g]),inside:{function:e(/^<<0>>/.source,[i]),generic:{pattern:RegExp(g),alias:"class-name",inside:o}}},"type-list":{pattern:e(/\b((?:<<0>>\s+<<1>>|record\s+<<1>>\s*<<5>>|where\s+<<2>>)\s*:\s*)(?:<<3>>|<<4>>|<<1>>\s*<<5>>|<<6>>)(?:\s*,\s*(?:<<3>>|<<4>>|<<6>>))*(?=\s*(?:where|[{;]|=>|$))/.source,[v,b,i,p,d.source,h,/\bnew\s*\(\s*\)/.source]),lookbehind:!0,inside:{"record-arguments":{pattern:e(/(^(?!new\s*\()<<0>>\s*)<<1>>/.source,[b,h]),lookbehind:!0,greedy:!0,inside:r.languages.csharp},keyword:d,"class-name":{pattern:RegExp(p),greedy:!0,inside:o},punctuation:/[,()]/}},preprocessor:{pattern:/(^[\t ]*)#.*/m,lookbehind:!0,alias:"property",inside:{directive:{pattern:/(#)\b(?:define|elif|else|endif|endregion|error|if|line|nullable|pragma|region|undef|warning)\b/,lookbehind:!0,alias:"keyword"}}}});var x=w+"|"+k,E=t(/\/(?![*/])|\/\/[^\r\n]*[\r\n]|\/\*(?:[^*]|\*(?!\/))*\*\/|<<0>>/.source,[x]),S=a(t(/[^"'/()]|<<0>>|\(<<self>>*\)/.source,[E]),2),_=/\b(?:assembly|event|field|method|module|param|property|return|type)\b/.source,N=t(/<<0>>(?:\s*\(<<1>>*\))?/.source,[c,S]);r.languages.insertBefore("csharp","class-name",{attribute:{pattern:e(/((?:^|[^\s\w>)?])\s*\[\s*)(?:<<0>>\s*:\s*)?<<1>>(?:\s*,\s*<<1>>)*(?=\s*\])/.source,[_,N]),lookbehind:!0,greedy:!0,inside:{target:{pattern:e(/^<<0>>(?=\s*:)/.source,[_]),alias:"keyword"},"attribute-arguments":{pattern:e(/\(<<0>>*\)/.source,[S]),inside:r.languages.csharp},"class-name":{pattern:RegExp(c),inside:{punctuation:/\./}},punctuation:/[:,]/}}});var m=/:[^}\r\n]+/.source,C=a(t(/[^"'/()]|<<0>>|\(<<self>>*\)/.source,[E]),2),$=t(/\{(?!\{)(?:(?![}:])<<0>>)*<<1>>?\}/.source,[C,m]),R=a(t(/[^"'/()]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|<<0>>|\(<<self>>*\)/.source,[x]),2),D=t(/\{(?!\{)(?:(?![}:])<<0>>)*<<1>>?\}/.source,[R,m]);function j(n,l){return{interpolation:{pattern:e(/((?:^|[^{])(?:\{\{)*)<<0>>/.source,[n]),lookbehind:!0,inside:{"format-string":{pattern:e(/(^\{(?:(?![}:])<<0>>)*)<<1>>(?=\}$)/.source,[l,m]),lookbehind:!0,inside:{punctuation:/^:/}},punctuation:/^\{|\}$/,expression:{pattern:/[\s\S]+/,alias:"language-csharp",inside:r.languages.csharp}}},string:/[\s\S]+/}}r.languages.insertBefore("csharp","string",{"interpolation-string":[{pattern:e(/(^|[^\\])(?:\$@|@\$)"(?:""|\\[\s\S]|\{\{|<<0>>|[^\\{"])*"/.source,[$]),lookbehind:!0,greedy:!0,inside:j($,C)},{pattern:e(/(^|[^@\\])\$"(?:\\.|\{\{|<<0>>|[^\\"{])*"/.source,[D]),lookbehind:!0,greedy:!0,inside:j(D,R)}],char:{pattern:RegExp(k),greedy:!0}}),r.languages.dotnet=r.languages.cs=r.languages.csharp}(Prism)),I}var B=Z();const G=U(B),J=W({__proto__:null,default:G},[B]);export{J as p};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{g as u}from"./index-
|
|
1
|
+
import{g as u}from"./index-Bx2n4Aep.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-Bx2n4Aep.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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(e){var s=/\b(?:bool|bytes|double|s?fixed(?:32|64)|float|[su]?int(?:32|64)|string)\b/;e.languages.protobuf=e.languages.extend("clike",{"class-name":[{pattern:/(\b(?:enum|extend|message|service)\s+)[A-Za-z_]\w*(?=\s*\{)/,lookbehind:!0},{pattern:/(\b(?:rpc\s+\w+|returns)\s*\(\s*(?:stream\s+)?)\.?[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*(?=\s*\))/,lookbehind:!0}],keyword:/\b(?:enum|extend|extensions|import|message|oneof|option|optional|package|public|repeated|required|reserved|returns|rpc(?=\s+\w)|service|stream|syntax|to)\b(?!\s*=\s*\d)/,function:/\b[a-z_]\w*(?=\s*\()/i}),e.languages.insertBefore("protobuf","operator",{map:{pattern:/\bmap<\s*[\w.]+\s*,\s*[\w.]+\s*>(?=\s+[a-z_]\w*\s*[=;])/i,alias:"class-name",inside:{punctuation:/[<>.,]/,builtin:s}},builtin:s,"positional-class-name":{pattern:/(?:\b|\B\.)[a-z_]\w*(?:\.[a-z_]\w*)*(?=\s+[a-z_]\w*\s*[=;])/i,alias:"class-name",inside:{punctuation:/\./}},annotation:{pattern:/(\[\s*)[a-z_]\w*(?=\s*=)/i,lookbehind:!0}})})(Prism);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{g as l}from"./index-
|
|
1
|
+
import{g as l}from"./index-Bx2n4Aep.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};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{g as u}from"./index-
|
|
1
|
+
import{g as u}from"./index-Bx2n4Aep.js";function d(t,n){for(var i=0;i<n.length;i++){const e=n[i];if(typeof e!="string"&&!Array.isArray(e)){for(const r in e)if(r!=="default"&&!(r in t)){const s=Object.getOwnPropertyDescriptor(e,r);s&&Object.defineProperty(t,r,s.get?s:{enumerable:!0,get:()=>e[r]})}}}return Object.freeze(Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}))}var o={},a;function c(){return a||(a=1,Prism.languages.solidity=Prism.languages.extend("clike",{"class-name":{pattern:/(\b(?:contract|enum|interface|library|new|struct|using)\s+)(?!\d)[\w$]+/,lookbehind:!0},keyword:/\b(?:_|anonymous|as|assembly|assert|break|calldata|case|constant|constructor|continue|contract|default|delete|do|else|emit|enum|event|external|for|from|function|if|import|indexed|inherited|interface|internal|is|let|library|mapping|memory|modifier|new|payable|pragma|private|public|pure|require|returns?|revert|selfdestruct|solidity|storage|struct|suicide|switch|this|throw|using|var|view|while)\b/,operator:/=>|->|:=|=:|\*\*|\+\+|--|\|\||&&|<<=?|>>=?|[-+*/%^&|<>!=]=?|[~?]/}),Prism.languages.insertBefore("solidity","keyword",{builtin:/\b(?:address|bool|byte|u?int(?:8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?|string|bytes(?:[1-9]|[12]\d|3[0-2])?)\b/}),Prism.languages.insertBefore("solidity","number",{version:{pattern:/([<>]=?|\^)\d+\.\d+\.\d+\b/,lookbehind:!0,alias:"number"}}),Prism.languages.sol=Prism.languages.solidity),o}var l=c();const m=u(l),y=d({__proto__:null,default:m},[l]);export{y as p};
|
package/dist/client/index.html
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
<link rel="icon" href="/favicon-white.svg" media="(prefers-color-scheme: dark)" />
|
|
8
8
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
9
9
|
<title>difit - Git Diff Viewer</title>
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-Bx2n4Aep.js"></script>
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BtavrLIu.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<div id="root"></div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type Response } from 'express';
|
|
2
|
+
import { DiffMode } from '../types/watch.js';
|
|
3
|
+
export declare class FileWatcherService {
|
|
4
|
+
private subscriptions;
|
|
5
|
+
private clients;
|
|
6
|
+
private debounceTimer;
|
|
7
|
+
private config;
|
|
8
|
+
private git;
|
|
9
|
+
constructor();
|
|
10
|
+
start(diffMode: DiffMode, watchPath: string, debounceMs?: number, onCacheInvalidate?: () => void): Promise<void>;
|
|
11
|
+
private setupWatchers;
|
|
12
|
+
private shouldIgnoreEvent;
|
|
13
|
+
private matchesPattern;
|
|
14
|
+
private isRelevantGitFile;
|
|
15
|
+
private debouncedBroadcast;
|
|
16
|
+
stop(): Promise<void>;
|
|
17
|
+
addClient(res: Response): void;
|
|
18
|
+
removeClient(res: Response): void;
|
|
19
|
+
private broadcastChange;
|
|
20
|
+
private sendToClient;
|
|
21
|
+
private determineChangeType;
|
|
22
|
+
private isGitignored;
|
|
23
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
import { subscribe } from '@parcel/watcher';
|
|
3
|
+
import { simpleGit } from 'simple-git';
|
|
4
|
+
import { DiffMode } from '../types/watch.js';
|
|
5
|
+
const MODE_WATCH_CONFIGS = {
|
|
6
|
+
[DiffMode.DEFAULT]: {
|
|
7
|
+
watchPaths: ['.git'],
|
|
8
|
+
ignore: ['.git/objects/**', '.git/refs/**', 'node_modules/**'],
|
|
9
|
+
},
|
|
10
|
+
[DiffMode.WORKING]: {
|
|
11
|
+
watchPaths: ['.', '.git'],
|
|
12
|
+
ignore: ['.git/objects/**', '.git/refs/**', 'node_modules/**'],
|
|
13
|
+
},
|
|
14
|
+
[DiffMode.STAGED]: {
|
|
15
|
+
watchPaths: ['.git'],
|
|
16
|
+
ignore: ['.git/objects/**', '.git/refs/**'],
|
|
17
|
+
},
|
|
18
|
+
[DiffMode.DOT]: {
|
|
19
|
+
watchPaths: ['.', '.git'],
|
|
20
|
+
ignore: [
|
|
21
|
+
'.git/objects/**',
|
|
22
|
+
'.git/refs/**',
|
|
23
|
+
'.git/FETCH_HEAD',
|
|
24
|
+
'.git/ORIG_HEAD',
|
|
25
|
+
'.git/logs/**',
|
|
26
|
+
'node_modules/**',
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
[DiffMode.SPECIFIC]: {
|
|
30
|
+
watchPaths: [], // No watching for specific commits
|
|
31
|
+
ignore: [],
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
export class FileWatcherService {
|
|
35
|
+
subscriptions = [];
|
|
36
|
+
clients = [];
|
|
37
|
+
debounceTimer = null;
|
|
38
|
+
config = null;
|
|
39
|
+
git = null;
|
|
40
|
+
constructor() { }
|
|
41
|
+
async start(diffMode, watchPath, debounceMs = 300, onCacheInvalidate) {
|
|
42
|
+
this.config = { watchPath, diffMode, debounceMs, onCacheInvalidate };
|
|
43
|
+
// Stop existing watchers
|
|
44
|
+
await this.stop();
|
|
45
|
+
// No watching for specific commit comparisons
|
|
46
|
+
if (diffMode === DiffMode.SPECIFIC) {
|
|
47
|
+
console.log('🔍 File watching disabled (specific commit comparison)');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const modeConfig = MODE_WATCH_CONFIGS[diffMode];
|
|
51
|
+
// Initialize git instance for .gitignore checking
|
|
52
|
+
this.git = simpleGit(watchPath);
|
|
53
|
+
try {
|
|
54
|
+
await this.setupWatchers(modeConfig, watchPath);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
console.error('❌ Failed to start file watcher:', error);
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async setupWatchers(modeConfig, basePath) {
|
|
62
|
+
for (const watchPath of modeConfig.watchPaths) {
|
|
63
|
+
const fullPath = join(basePath, watchPath);
|
|
64
|
+
try {
|
|
65
|
+
const subscription = (await subscribe(fullPath, async (err, events) => {
|
|
66
|
+
if (err) {
|
|
67
|
+
console.error(`Watch error for ${watchPath}:`, err);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// Filter out ignored files and check for relevant changes
|
|
71
|
+
const relevantEvents = [];
|
|
72
|
+
for (const event of events) {
|
|
73
|
+
if (this.shouldIgnoreEvent(event.path, modeConfig.ignore)) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
// For working directory watching, also apply .gitignore patterns
|
|
77
|
+
if (watchPath === '.' && (await this.isGitignored(event.path, basePath))) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
// For git directory watching, only care about specific files
|
|
81
|
+
if (watchPath === '.git') {
|
|
82
|
+
const fileName = event.path.replace(/.*[/\\]/, '');
|
|
83
|
+
const isRelevantGitFile = this.isRelevantGitFile(fileName, this.config?.diffMode);
|
|
84
|
+
if (!isRelevantGitFile) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
relevantEvents.push(event);
|
|
89
|
+
}
|
|
90
|
+
if (relevantEvents.length > 0) {
|
|
91
|
+
this.debouncedBroadcast();
|
|
92
|
+
}
|
|
93
|
+
}, {
|
|
94
|
+
ignore: modeConfig.ignore,
|
|
95
|
+
}));
|
|
96
|
+
this.subscriptions.push(subscription);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.warn(`⚠️ Could not watch ${fullPath}:`, error);
|
|
100
|
+
// Continue with other watchers even if one fails
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
shouldIgnoreEvent(filePath, ignorePatterns) {
|
|
105
|
+
return ignorePatterns.some((pattern) => {
|
|
106
|
+
// Handle negation patterns (e.g., "!.git/index")
|
|
107
|
+
if (pattern.startsWith('!')) {
|
|
108
|
+
const positivePattern = pattern.slice(1);
|
|
109
|
+
return !this.matchesPattern(filePath, positivePattern);
|
|
110
|
+
}
|
|
111
|
+
return this.matchesPattern(filePath, pattern);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
matchesPattern(filePath, pattern) {
|
|
115
|
+
// Simple glob pattern matching
|
|
116
|
+
const regex = pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*');
|
|
117
|
+
return new RegExp(regex).test(filePath);
|
|
118
|
+
}
|
|
119
|
+
isRelevantGitFile(fileName, diffMode) {
|
|
120
|
+
if (!diffMode)
|
|
121
|
+
return false;
|
|
122
|
+
switch (diffMode) {
|
|
123
|
+
case DiffMode.DEFAULT:
|
|
124
|
+
case DiffMode.DOT:
|
|
125
|
+
return fileName === 'HEAD';
|
|
126
|
+
case DiffMode.WORKING:
|
|
127
|
+
case DiffMode.STAGED:
|
|
128
|
+
return fileName === 'index' || fileName === 'HEAD';
|
|
129
|
+
default:
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
debouncedBroadcast() {
|
|
134
|
+
if (this.debounceTimer) {
|
|
135
|
+
clearTimeout(this.debounceTimer);
|
|
136
|
+
}
|
|
137
|
+
const debounceMs = this.config?.debounceMs || 300;
|
|
138
|
+
this.debounceTimer = setTimeout(() => {
|
|
139
|
+
// Invalidate cache before broadcasting change
|
|
140
|
+
if (this.config?.onCacheInvalidate) {
|
|
141
|
+
this.config.onCacheInvalidate();
|
|
142
|
+
}
|
|
143
|
+
this.broadcastChange();
|
|
144
|
+
}, debounceMs);
|
|
145
|
+
}
|
|
146
|
+
async stop() {
|
|
147
|
+
// Clear debounce timer
|
|
148
|
+
if (this.debounceTimer) {
|
|
149
|
+
clearTimeout(this.debounceTimer);
|
|
150
|
+
this.debounceTimer = null;
|
|
151
|
+
}
|
|
152
|
+
// Unsubscribe from all watchers
|
|
153
|
+
await Promise.all(this.subscriptions.map(async (subscription) => {
|
|
154
|
+
try {
|
|
155
|
+
await subscription.unsubscribe();
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
console.warn('Error unsubscribing from file watcher:', error);
|
|
159
|
+
}
|
|
160
|
+
}));
|
|
161
|
+
this.subscriptions = [];
|
|
162
|
+
// Clear clients
|
|
163
|
+
this.clients = [];
|
|
164
|
+
}
|
|
165
|
+
addClient(res) {
|
|
166
|
+
this.clients.push(res);
|
|
167
|
+
// Send initial connection event
|
|
168
|
+
this.sendToClient(res, {
|
|
169
|
+
type: 'connected',
|
|
170
|
+
diffMode: this.config?.diffMode || DiffMode.DEFAULT,
|
|
171
|
+
changeType: 'file',
|
|
172
|
+
timestamp: new Date().toISOString(),
|
|
173
|
+
message: `Connected to file watcher (${this.config?.diffMode} mode)`,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
removeClient(res) {
|
|
177
|
+
const index = this.clients.indexOf(res);
|
|
178
|
+
if (index > -1) {
|
|
179
|
+
this.clients.splice(index, 1);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
broadcastChange() {
|
|
183
|
+
if (this.clients.length === 0 || !this.config) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const changeType = this.determineChangeType();
|
|
187
|
+
const event = {
|
|
188
|
+
type: 'reload',
|
|
189
|
+
diffMode: this.config.diffMode,
|
|
190
|
+
changeType,
|
|
191
|
+
timestamp: new Date().toISOString(),
|
|
192
|
+
message: `Changes detected in ${this.config.diffMode} mode`,
|
|
193
|
+
};
|
|
194
|
+
this.clients.forEach((client) => {
|
|
195
|
+
this.sendToClient(client, event);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
sendToClient(client, event) {
|
|
199
|
+
try {
|
|
200
|
+
client.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
console.error('Failed to send event to client:', error);
|
|
204
|
+
this.removeClient(client);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
determineChangeType() {
|
|
208
|
+
if (!this.config)
|
|
209
|
+
return 'file';
|
|
210
|
+
switch (this.config.diffMode) {
|
|
211
|
+
case DiffMode.DEFAULT:
|
|
212
|
+
case DiffMode.DOT:
|
|
213
|
+
return 'commit'; // .git/HEAD changes indicate new commits
|
|
214
|
+
case DiffMode.STAGED:
|
|
215
|
+
return 'staging'; // .git/index changes
|
|
216
|
+
case DiffMode.WORKING:
|
|
217
|
+
return 'file'; // Both file and staging changes, default to file
|
|
218
|
+
default:
|
|
219
|
+
return 'file';
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async isGitignored(filePath, basePath) {
|
|
223
|
+
if (!this.git)
|
|
224
|
+
return false;
|
|
225
|
+
// Get relative path from base directory
|
|
226
|
+
const relativePath = filePath.replace(basePath + '/', '');
|
|
227
|
+
try {
|
|
228
|
+
const result = await this.git.checkIgnore([relativePath]);
|
|
229
|
+
return result.length > 0;
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
// checkIgnore throws an error when no files are ignored
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { DiffMode } from '../types/watch.js';
|
|
3
|
+
import { FileWatcherService } from './file-watcher.js';
|
|
4
|
+
// Mock @parcel/watcher
|
|
5
|
+
vi.mock('@parcel/watcher', () => ({
|
|
6
|
+
subscribe: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
// Mock simple-git
|
|
9
|
+
vi.mock('simple-git', () => ({
|
|
10
|
+
simpleGit: vi.fn(() => ({
|
|
11
|
+
checkIgnore: vi.fn(),
|
|
12
|
+
})),
|
|
13
|
+
}));
|
|
14
|
+
const { subscribe } = await import('@parcel/watcher');
|
|
15
|
+
const { simpleGit } = await import('simple-git');
|
|
16
|
+
describe('FileWatcherService', () => {
|
|
17
|
+
let fileWatcher;
|
|
18
|
+
let mockResponse;
|
|
19
|
+
let mockSubscription;
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
vi.clearAllMocks();
|
|
22
|
+
fileWatcher = new FileWatcherService();
|
|
23
|
+
// Mock Express Response
|
|
24
|
+
mockResponse = {
|
|
25
|
+
write: vi.fn(),
|
|
26
|
+
};
|
|
27
|
+
// Mock subscription
|
|
28
|
+
mockSubscription = {
|
|
29
|
+
unsubscribe: vi.fn().mockResolvedValue(undefined),
|
|
30
|
+
};
|
|
31
|
+
vi.mocked(subscribe).mockResolvedValue(mockSubscription);
|
|
32
|
+
});
|
|
33
|
+
describe('start', () => {
|
|
34
|
+
it('should start watching for DEFAULT mode', async () => {
|
|
35
|
+
await fileWatcher.start(DiffMode.DEFAULT, '/test/path', 300);
|
|
36
|
+
expect(subscribe).toHaveBeenCalledWith('/test/path/.git', expect.any(Function), {
|
|
37
|
+
ignore: ['.git/objects/**', '.git/refs/**', 'node_modules/**'],
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
it('should start watching for WORKING mode', async () => {
|
|
41
|
+
await fileWatcher.start(DiffMode.WORKING, '/test/path', 300);
|
|
42
|
+
expect(subscribe).toHaveBeenCalledTimes(2);
|
|
43
|
+
expect(subscribe).toHaveBeenCalledWith('/test/path', expect.any(Function), {
|
|
44
|
+
ignore: ['.git/objects/**', '.git/refs/**', 'node_modules/**'],
|
|
45
|
+
});
|
|
46
|
+
expect(subscribe).toHaveBeenCalledWith('/test/path/.git', expect.any(Function), {
|
|
47
|
+
ignore: ['.git/objects/**', '.git/refs/**', 'node_modules/**'],
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
it('should start watching for DOT mode', async () => {
|
|
51
|
+
await fileWatcher.start(DiffMode.DOT, '/test/path', 300);
|
|
52
|
+
expect(subscribe).toHaveBeenCalledTimes(2);
|
|
53
|
+
expect(subscribe).toHaveBeenCalledWith('/test/path', expect.any(Function), {
|
|
54
|
+
ignore: [
|
|
55
|
+
'.git/objects/**',
|
|
56
|
+
'.git/refs/**',
|
|
57
|
+
'.git/FETCH_HEAD',
|
|
58
|
+
'.git/ORIG_HEAD',
|
|
59
|
+
'.git/logs/**',
|
|
60
|
+
'node_modules/**',
|
|
61
|
+
],
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
it('should not start watching for SPECIFIC mode', async () => {
|
|
65
|
+
await fileWatcher.start(DiffMode.SPECIFIC, '/test/path', 300);
|
|
66
|
+
expect(subscribe).not.toHaveBeenCalled();
|
|
67
|
+
});
|
|
68
|
+
it('should initialize git instance for gitignore checking', async () => {
|
|
69
|
+
await fileWatcher.start(DiffMode.DOT, '/test/path', 300);
|
|
70
|
+
expect(simpleGit).toHaveBeenCalledWith('/test/path');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
describe('stop', () => {
|
|
74
|
+
it('should unsubscribe all watchers', async () => {
|
|
75
|
+
await fileWatcher.start(DiffMode.WORKING, '/test/path', 300);
|
|
76
|
+
await fileWatcher.stop();
|
|
77
|
+
expect(mockSubscription.unsubscribe).toHaveBeenCalledTimes(2);
|
|
78
|
+
});
|
|
79
|
+
it('should clear clients', async () => {
|
|
80
|
+
fileWatcher.addClient(mockResponse);
|
|
81
|
+
await fileWatcher.stop();
|
|
82
|
+
// After stop, adding client should work normally (clients array should be cleared)
|
|
83
|
+
expect(() => fileWatcher.addClient(mockResponse)).not.toThrow();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe('client management', () => {
|
|
87
|
+
it('should add and remove clients', () => {
|
|
88
|
+
const mockResponse1 = { write: vi.fn() };
|
|
89
|
+
const mockResponse2 = { write: vi.fn() };
|
|
90
|
+
fileWatcher.addClient(mockResponse1);
|
|
91
|
+
fileWatcher.addClient(mockResponse2);
|
|
92
|
+
// Should send connected event to new clients
|
|
93
|
+
expect(mockResponse1.write).toHaveBeenCalledWith(expect.stringContaining('"type":"connected"'));
|
|
94
|
+
expect(mockResponse2.write).toHaveBeenCalledWith(expect.stringContaining('"type":"connected"'));
|
|
95
|
+
fileWatcher.removeClient(mockResponse1);
|
|
96
|
+
// mockResponse1 should be removed, but this is tested indirectly
|
|
97
|
+
// by checking that only one client receives broadcasts
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
describe('gitignore checking', () => {
|
|
101
|
+
it('should check gitignore correctly', async () => {
|
|
102
|
+
const mockGit = {
|
|
103
|
+
checkIgnore: vi.fn().mockResolvedValue(['ignored-file.txt']),
|
|
104
|
+
};
|
|
105
|
+
vi.mocked(simpleGit).mockReturnValue(mockGit);
|
|
106
|
+
await fileWatcher.start(DiffMode.DOT, '/test/path', 300);
|
|
107
|
+
// Get the callback function passed to subscribe
|
|
108
|
+
const subscribeCall = vi.mocked(subscribe).mock.calls[0];
|
|
109
|
+
const callback = subscribeCall[1];
|
|
110
|
+
// Simulate file change event
|
|
111
|
+
const mockEvents = [
|
|
112
|
+
{ path: '/test/path/ignored-file.txt', type: 'update' },
|
|
113
|
+
{ path: '/test/path/normal-file.txt', type: 'update' },
|
|
114
|
+
];
|
|
115
|
+
await callback(null, mockEvents);
|
|
116
|
+
expect(mockGit.checkIgnore).toHaveBeenCalledWith(['ignored-file.txt']);
|
|
117
|
+
expect(mockGit.checkIgnore).toHaveBeenCalledWith(['normal-file.txt']);
|
|
118
|
+
});
|
|
119
|
+
it('should handle gitignore check errors gracefully', async () => {
|
|
120
|
+
const mockGit = {
|
|
121
|
+
checkIgnore: vi.fn().mockRejectedValue(new Error('No ignored files')),
|
|
122
|
+
};
|
|
123
|
+
vi.mocked(simpleGit).mockReturnValue(mockGit);
|
|
124
|
+
await fileWatcher.start(DiffMode.DOT, '/test/path', 300);
|
|
125
|
+
const subscribeCall = vi.mocked(subscribe).mock.calls[0];
|
|
126
|
+
const callback = subscribeCall[1];
|
|
127
|
+
const mockEvents = [{ path: '/test/path/some-file.txt', type: 'update' }];
|
|
128
|
+
// Should not throw when gitignore check fails
|
|
129
|
+
await expect(callback(null, mockEvents)).resolves.not.toThrow();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
describe('file filtering', () => {
|
|
133
|
+
beforeEach(async () => {
|
|
134
|
+
const mockGit = {
|
|
135
|
+
checkIgnore: vi.fn().mockRejectedValue(new Error('No ignored files')),
|
|
136
|
+
};
|
|
137
|
+
vi.mocked(simpleGit).mockReturnValue(mockGit);
|
|
138
|
+
await fileWatcher.start(DiffMode.DEFAULT, '/test/path', 300);
|
|
139
|
+
fileWatcher.addClient(mockResponse);
|
|
140
|
+
});
|
|
141
|
+
it('should filter relevant git files for DEFAULT mode', async () => {
|
|
142
|
+
const subscribeCall = vi.mocked(subscribe).mock.calls[0];
|
|
143
|
+
const callback = subscribeCall[1];
|
|
144
|
+
const mockEvents = [
|
|
145
|
+
{ path: '/test/path/.git/HEAD', type: 'update' },
|
|
146
|
+
{ path: '/test/path/.git/index', type: 'update' },
|
|
147
|
+
{ path: '/test/path/.git/objects/abc123', type: 'update' },
|
|
148
|
+
];
|
|
149
|
+
await callback(null, mockEvents);
|
|
150
|
+
// Wait for debounce period
|
|
151
|
+
await new Promise((resolve) => setTimeout(resolve, 350));
|
|
152
|
+
// Should only broadcast for HEAD file in DEFAULT mode
|
|
153
|
+
expect(mockResponse.write).toHaveBeenCalledWith(expect.stringContaining('"type":"reload"'));
|
|
154
|
+
});
|
|
155
|
+
it('should filter out ignored patterns', async () => {
|
|
156
|
+
const subscribeCall = vi.mocked(subscribe).mock.calls[0];
|
|
157
|
+
const callback = subscribeCall[1];
|
|
158
|
+
const mockEvents = [
|
|
159
|
+
{ path: '/test/path/.git/objects/abc123', type: 'update' },
|
|
160
|
+
{ path: '/test/path/.git/refs/heads/main', type: 'update' },
|
|
161
|
+
{ path: '/test/path/node_modules/package/file.js', type: 'update' },
|
|
162
|
+
];
|
|
163
|
+
await callback(null, mockEvents);
|
|
164
|
+
// Should not broadcast for ignored files
|
|
165
|
+
expect(mockResponse.write).not.toHaveBeenCalledWith(expect.stringContaining('"type":"reload"'));
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
describe('debounce functionality', () => {
|
|
169
|
+
beforeEach(async () => {
|
|
170
|
+
const mockGit = {
|
|
171
|
+
checkIgnore: vi.fn().mockRejectedValue(new Error('No ignored files')),
|
|
172
|
+
};
|
|
173
|
+
vi.mocked(simpleGit).mockReturnValue(mockGit);
|
|
174
|
+
await fileWatcher.start(DiffMode.DEFAULT, '/test/path', 100); // Short debounce for testing
|
|
175
|
+
fileWatcher.addClient(mockResponse);
|
|
176
|
+
});
|
|
177
|
+
it('should debounce multiple file changes', async () => {
|
|
178
|
+
const subscribeCall = vi.mocked(subscribe).mock.calls[0];
|
|
179
|
+
const callback = subscribeCall[1];
|
|
180
|
+
const mockEvents = [{ path: '/test/path/.git/HEAD', type: 'update' }];
|
|
181
|
+
// Trigger multiple events quickly
|
|
182
|
+
await callback(null, mockEvents);
|
|
183
|
+
await callback(null, mockEvents);
|
|
184
|
+
await callback(null, mockEvents);
|
|
185
|
+
// Wait for debounce period
|
|
186
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
187
|
+
// Should only send one reload event due to debouncing
|
|
188
|
+
const reloadCalls = vi
|
|
189
|
+
.mocked(mockResponse.write)
|
|
190
|
+
.mock.calls.filter((call) => call[0].toString().includes('"type":"reload"'));
|
|
191
|
+
expect(reloadCalls).toHaveLength(1);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
describe('change type determination', () => {
|
|
195
|
+
it('should determine correct change type for each mode', async () => {
|
|
196
|
+
const modes = [
|
|
197
|
+
{ mode: DiffMode.DEFAULT, expectedType: 'commit' },
|
|
198
|
+
{ mode: DiffMode.DOT, expectedType: 'commit' },
|
|
199
|
+
{ mode: DiffMode.STAGED, expectedType: 'staging' },
|
|
200
|
+
{ mode: DiffMode.WORKING, expectedType: 'file' },
|
|
201
|
+
];
|
|
202
|
+
for (const { mode, expectedType } of modes) {
|
|
203
|
+
const mockGit = {
|
|
204
|
+
checkIgnore: vi.fn().mockRejectedValue(new Error('No ignored files')),
|
|
205
|
+
};
|
|
206
|
+
vi.mocked(simpleGit).mockReturnValue(mockGit);
|
|
207
|
+
await fileWatcher.start(mode, '/test/path', 300);
|
|
208
|
+
const mockClient = { write: vi.fn() };
|
|
209
|
+
fileWatcher.addClient(mockClient);
|
|
210
|
+
const subscribeCall = vi
|
|
211
|
+
.mocked(subscribe)
|
|
212
|
+
.mock.calls.find((call) => call[0].includes('.git'));
|
|
213
|
+
if (subscribeCall) {
|
|
214
|
+
const callback = subscribeCall[1];
|
|
215
|
+
const mockEvents = [{ path: '/test/path/.git/HEAD', type: 'update' }];
|
|
216
|
+
await callback(null, mockEvents);
|
|
217
|
+
await new Promise((resolve) => setTimeout(resolve, 350)); // Wait for debounce
|
|
218
|
+
expect(mockClient.write).toHaveBeenCalledWith(expect.stringContaining(`"changeType":"${expectedType}"`));
|
|
219
|
+
}
|
|
220
|
+
await fileWatcher.stop();
|
|
221
|
+
vi.clearAllMocks();
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|