texlite 0.2.1 → 0.3.1
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 +20 -100
- package/README.zh-CN.md +17 -42
- package/dist/client/assets/{GitDialog-BHHxSBn3.js → GitDialog-LVM9oZ6q.js} +1 -1
- package/dist/client/assets/HistoryDialog-BCpa2ath.js +1 -0
- package/dist/client/assets/{LatexEditor-B-0VRUPv.js → LatexEditor-CEQUB9XV.js} +22 -22
- package/dist/client/assets/{PdfPreview-617TWFJN.js → PdfPreview-URQb_Pk6.js} +1 -1
- package/dist/client/assets/{ProjectNavigationDialogs-Cpt2LVEt.js → ProjectNavigationDialogs-Bf4JwsRy.js} +1 -1
- package/dist/client/assets/{SystemMetricsDialog-fkFHVVzJ.js → SystemMetricsDialog-DwnQFYm7.js} +1 -1
- package/dist/client/assets/index-CtVAEMSo.css +1 -0
- package/dist/client/assets/index-DbzKrh7H.js +17 -0
- package/dist/client/assets/index-rBWMBTI-.js +5 -0
- package/dist/client/assets/{plus-DzbLkp6l.js → plus-D_Y3JlVj.js} +1 -1
- package/dist/client/index.html +3 -3
- package/dist/server/app.js +35 -1
- package/dist/server/cli.js +95 -8
- package/dist/server/index.js +3 -1
- package/dist/server/latexFormatter.js +116 -0
- package/dist/server/pm2.js +120 -17
- package/dist/server/runtimePaths.js +1 -1
- package/dist/server/service.js +8 -0
- package/package.json +1 -1
- package/dist/client/assets/HistoryDialog-Dlv57tYf.js +0 -5
- package/dist/client/assets/index-Bc5CmFMl.js +0 -15
- package/dist/client/assets/index-CMqYAbDx.css +0 -1
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{g as F}from"./react-CsO_xvbl.js";function N(S,f){for(var w=0;w<f.length;w++){const m=f[w];if(typeof m!="string"&&!Array.isArray(m)){for(const o in m)if(o!=="default"&&!(o in S)){const r=Object.getOwnPropertyDescriptor(m,o);r&&Object.defineProperty(S,o,r.get?r:{enumerable:!0,get:()=>m[o]})}}}return Object.freeze(Object.defineProperty(S,Symbol.toStringTag,{value:"Module"}))}var R={exports:{}},O;function B(){return O||(O=1,(function(S){var f=function(){this.Diff_Timeout=1,this.Diff_EditCost=4,this.Match_Threshold=.5,this.Match_Distance=1e3,this.Patch_DeleteThreshold=.5,this.Patch_Margin=4,this.Match_MaxBits=32},w=-1,m=1,o=0;f.Diff=function(r,n){return[r,n]},f.prototype.diff_main=function(r,n,e,t){typeof t>"u"&&(this.Diff_Timeout<=0?t=Number.MAX_VALUE:t=new Date().getTime()+this.Diff_Timeout*1e3);var a=t;if(r==null||n==null)throw new Error("Null input. (diff_main)");if(r==n)return r?[new f.Diff(o,r)]:[];typeof e>"u"&&(e=!0);var i=e,s=this.diff_commonPrefix(r,n),h=r.substring(0,s);r=r.substring(s),n=n.substring(s),s=this.diff_commonSuffix(r,n);var l=r.substring(r.length-s);r=r.substring(0,r.length-s),n=n.substring(0,n.length-s);var g=this.diff_compute_(r,n,i,a);return h&&g.unshift(new f.Diff(o,h)),l&&g.push(new f.Diff(o,l)),this.diff_cleanupMerge(g),g},f.prototype.diff_compute_=function(r,n,e,t){var a;if(!r)return[new f.Diff(m,n)];if(!n)return[new f.Diff(w,r)];var i=r.length>n.length?r:n,s=r.length>n.length?n:r,h=i.indexOf(s);if(h!=-1)return a=[new f.Diff(m,i.substring(0,h)),new f.Diff(o,s),new f.Diff(m,i.substring(h+s.length))],r.length>n.length&&(a[0][0]=a[2][0]=w),a;if(s.length==1)return[new f.Diff(w,r),new f.Diff(m,n)];var l=this.diff_halfMatch_(r,n);if(l){var g=l[0],u=l[1],v=l[2],c=l[3],_=l[4],b=this.diff_main(g,v,e,t),M=this.diff_main(u,c,e,t);return b.concat([new f.Diff(o,_)],M)}return e&&r.length>100&&n.length>100?this.diff_lineMode_(r,n,t):this.diff_bisect_(r,n,t)},f.prototype.diff_lineMode_=function(r,n,e){var t=this.diff_linesToChars_(r,n);r=t.chars1,n=t.chars2;var a=t.lineArray,i=this.diff_main(r,n,!1,e);this.diff_charsToLines_(i,a),this.diff_cleanupSemantic(i),i.push(new f.Diff(o,""));for(var s=0,h=0,l=0,g="",u="";s<i.length;){switch(i[s][0]){case m:l++,u+=i[s][1];break;case w:h++,g+=i[s][1];break;case o:if(h>=1&&l>=1){i.splice(s-h-l,h+l),s=s-h-l;for(var v=this.diff_main(g,u,!1,e),c=v.length-1;c>=0;c--)i.splice(s,0,v[c]);s=s+v.length}l=0,h=0,g="",u="";break}s++}return i.pop(),i},f.prototype.diff_bisect_=function(r,n,e){for(var t=r.length,a=n.length,i=Math.ceil((t+a)/2),s=i,h=2*i,l=new Array(h),g=new Array(h),u=0;u<h;u++)l[u]=-1,g[u]=-1;l[s+1]=0,g[s+1]=0;for(var v=t-a,c=v%2!=0,_=0,b=0,M=0,k=0,p=0;p<i&&!(new Date().getTime()>e);p++){for(var D=-p+_;D<=p-b;D+=2){var A=s+D,P;D==-p||D!=p&&l[A-1]<l[A+1]?P=l[A+1]:P=l[A-1]+1;for(var E=P-D;P<t&&E<a&&r.charAt(P)==n.charAt(E);)P++,E++;if(l[A]=P,P>t)b+=2;else if(E>a)_+=2;else if(c){var y=s+v-D;if(y>=0&&y<h&&g[y]!=-1){var d=t-g[y];if(P>=d)return this.diff_bisectSplit_(r,n,P,E,e)}}}for(var I=-p+M;I<=p-k;I+=2){var y=s+I,d;I==-p||I!=p&&g[y-1]<g[y+1]?d=g[y+1]:d=g[y-1]+1;for(var T=d-I;d<t&&T<a&&r.charAt(t-d-1)==n.charAt(a-T-1);)d++,T++;if(g[y]=d,d>t)k+=2;else if(T>a)M+=2;else if(!c){var A=s+v-I;if(A>=0&&A<h&&l[A]!=-1){var P=l[A],E=s+P-A;if(d=t-d,P>=d)return this.diff_bisectSplit_(r,n,P,E,e)}}}}return[new f.Diff(w,r),new f.Diff(m,n)]},f.prototype.diff_bisectSplit_=function(r,n,e,t,a){var i=r.substring(0,e),s=n.substring(0,t),h=r.substring(e),l=n.substring(t),g=this.diff_main(i,s,!1,a),u=this.diff_main(h,l,!1,a);return g.concat(u)},f.prototype.diff_linesToChars_=function(r,n){var e=[],t={};e[0]="";function a(l){for(var g="",u=0,v=-1,c=e.length;v<l.length-1;){v=l.indexOf(`
|
|
2
|
+
`,u),v==-1&&(v=l.length-1);var _=l.substring(u,v+1);(t.hasOwnProperty?t.hasOwnProperty(_):t[_]!==void 0)?g+=String.fromCharCode(t[_]):(c==i&&(_=l.substring(u),v=l.length),g+=String.fromCharCode(c),t[_]=c,e[c++]=_),u=v+1}return g}var i=4e4,s=a(r);i=65535;var h=a(n);return{chars1:s,chars2:h,lineArray:e}},f.prototype.diff_charsToLines_=function(r,n){for(var e=0;e<r.length;e++){for(var t=r[e][1],a=[],i=0;i<t.length;i++)a[i]=n[t.charCodeAt(i)];r[e][1]=a.join("")}},f.prototype.diff_commonPrefix=function(r,n){if(!r||!n||r.charAt(0)!=n.charAt(0))return 0;for(var e=0,t=Math.min(r.length,n.length),a=t,i=0;e<a;)r.substring(i,a)==n.substring(i,a)?(e=a,i=e):t=a,a=Math.floor((t-e)/2+e);return a},f.prototype.diff_commonSuffix=function(r,n){if(!r||!n||r.charAt(r.length-1)!=n.charAt(n.length-1))return 0;for(var e=0,t=Math.min(r.length,n.length),a=t,i=0;e<a;)r.substring(r.length-a,r.length-i)==n.substring(n.length-a,n.length-i)?(e=a,i=e):t=a,a=Math.floor((t-e)/2+e);return a},f.prototype.diff_commonOverlap_=function(r,n){var e=r.length,t=n.length;if(e==0||t==0)return 0;e>t?r=r.substring(e-t):e<t&&(n=n.substring(0,e));var a=Math.min(e,t);if(r==n)return a;for(var i=0,s=1;;){var h=r.substring(a-s),l=n.indexOf(h);if(l==-1)return i;s+=l,(l==0||r.substring(a-s)==n.substring(0,s))&&(i=s,s++)}},f.prototype.diff_halfMatch_=function(r,n){if(this.Diff_Timeout<=0)return null;var e=r.length>n.length?r:n,t=r.length>n.length?n:r;if(e.length<4||t.length*2<e.length)return null;var a=this;function i(b,M,k){for(var p=b.substring(k,k+Math.floor(b.length/4)),D=-1,A="",P,E,y,d;(D=M.indexOf(p,D+1))!=-1;){var I=a.diff_commonPrefix(b.substring(k),M.substring(D)),T=a.diff_commonSuffix(b.substring(0,k),M.substring(0,D));A.length<T+I&&(A=M.substring(D-T,D)+M.substring(D,D+I),P=b.substring(0,k-T),E=b.substring(k+I),y=M.substring(0,D-T),d=M.substring(D+I))}return A.length*2>=b.length?[P,E,y,d,A]:null}var s=i(e,t,Math.ceil(e.length/4)),h=i(e,t,Math.ceil(e.length/2)),l;if(!s&&!h)return null;h?s?l=s[4].length>h[4].length?s:h:l=h:l=s;var g,u,v,c;r.length>n.length?(g=l[0],u=l[1],v=l[2],c=l[3]):(v=l[0],c=l[1],g=l[2],u=l[3]);var _=l[4];return[g,u,v,c,_]},f.prototype.diff_cleanupSemantic=function(r){for(var n=!1,e=[],t=0,a=null,i=0,s=0,h=0,l=0,g=0;i<r.length;)r[i][0]==o?(e[t++]=i,s=l,h=g,l=0,g=0,a=r[i][1]):(r[i][0]==m?l+=r[i][1].length:g+=r[i][1].length,a&&a.length<=Math.max(s,h)&&a.length<=Math.max(l,g)&&(r.splice(e[t-1],0,new f.Diff(w,a)),r[e[t-1]+1][0]=m,t--,t--,i=t>0?e[t-1]:-1,s=0,h=0,l=0,g=0,a=null,n=!0)),i++;for(n&&this.diff_cleanupMerge(r),this.diff_cleanupSemanticLossless(r),i=1;i<r.length;){if(r[i-1][0]==w&&r[i][0]==m){var u=r[i-1][1],v=r[i][1],c=this.diff_commonOverlap_(u,v),_=this.diff_commonOverlap_(v,u);c>=_?(c>=u.length/2||c>=v.length/2)&&(r.splice(i,0,new f.Diff(o,v.substring(0,c))),r[i-1][1]=u.substring(0,u.length-c),r[i+1][1]=v.substring(c),i++):(_>=u.length/2||_>=v.length/2)&&(r.splice(i,0,new f.Diff(o,u.substring(0,_))),r[i-1][0]=m,r[i-1][1]=v.substring(0,v.length-_),r[i+1][0]=w,r[i+1][1]=u.substring(_),i++),i++}i++}},f.prototype.diff_cleanupSemanticLossless=function(r){function n(_,b){if(!_||!b)return 6;var M=_.charAt(_.length-1),k=b.charAt(0),p=M.match(f.nonAlphaNumericRegex_),D=k.match(f.nonAlphaNumericRegex_),A=p&&M.match(f.whitespaceRegex_),P=D&&k.match(f.whitespaceRegex_),E=A&&M.match(f.linebreakRegex_),y=P&&k.match(f.linebreakRegex_),d=E&&_.match(f.blanklineEndRegex_),I=y&&b.match(f.blanklineStartRegex_);return d||I?5:E||y?4:p&&!A&&P?3:A||P?2:p||D?1:0}for(var e=1;e<r.length-1;){if(r[e-1][0]==o&&r[e+1][0]==o){var t=r[e-1][1],a=r[e][1],i=r[e+1][1],s=this.diff_commonSuffix(t,a);if(s){var h=a.substring(a.length-s);t=t.substring(0,t.length-s),a=h+a.substring(0,a.length-s),i=h+i}for(var l=t,g=a,u=i,v=n(t,a)+n(a,i);a.charAt(0)===i.charAt(0);){t+=a.charAt(0),a=a.substring(1)+i.charAt(0),i=i.substring(1);var c=n(t,a)+n(a,i);c>=v&&(v=c,l=t,g=a,u=i)}r[e-1][1]!=l&&(l?r[e-1][1]=l:(r.splice(e-1,1),e--),r[e][1]=g,u?r[e+1][1]=u:(r.splice(e+1,1),e--))}e++}},f.nonAlphaNumericRegex_=/[^a-zA-Z0-9]/,f.whitespaceRegex_=/\s/,f.linebreakRegex_=/[\r\n]/,f.blanklineEndRegex_=/\n\r?\n$/,f.blanklineStartRegex_=/^\r?\n\r?\n/,f.prototype.diff_cleanupEfficiency=function(r){for(var n=!1,e=[],t=0,a=null,i=0,s=!1,h=!1,l=!1,g=!1;i<r.length;)r[i][0]==o?(r[i][1].length<this.Diff_EditCost&&(l||g)?(e[t++]=i,s=l,h=g,a=r[i][1]):(t=0,a=null),l=g=!1):(r[i][0]==w?g=!0:l=!0,a&&(s&&h&&l&&g||a.length<this.Diff_EditCost/2&&s+h+l+g==3)&&(r.splice(e[t-1],0,new f.Diff(w,a)),r[e[t-1]+1][0]=m,t--,a=null,s&&h?(l=g=!0,t=0):(t--,i=t>0?e[t-1]:-1,l=g=!1),n=!0)),i++;n&&this.diff_cleanupMerge(r)},f.prototype.diff_cleanupMerge=function(r){r.push(new f.Diff(o,""));for(var n=0,e=0,t=0,a="",i="",s;n<r.length;)switch(r[n][0]){case m:t++,i+=r[n][1],n++;break;case w:e++,a+=r[n][1],n++;break;case o:e+t>1?(e!==0&&t!==0&&(s=this.diff_commonPrefix(i,a),s!==0&&(n-e-t>0&&r[n-e-t-1][0]==o?r[n-e-t-1][1]+=i.substring(0,s):(r.splice(0,0,new f.Diff(o,i.substring(0,s))),n++),i=i.substring(s),a=a.substring(s)),s=this.diff_commonSuffix(i,a),s!==0&&(r[n][1]=i.substring(i.length-s)+r[n][1],i=i.substring(0,i.length-s),a=a.substring(0,a.length-s))),n-=e+t,r.splice(n,e+t),a.length&&(r.splice(n,0,new f.Diff(w,a)),n++),i.length&&(r.splice(n,0,new f.Diff(m,i)),n++),n++):n!==0&&r[n-1][0]==o?(r[n-1][1]+=r[n][1],r.splice(n,1)):n++,t=0,e=0,a="",i="";break}r[r.length-1][1]===""&&r.pop();var h=!1;for(n=1;n<r.length-1;)r[n-1][0]==o&&r[n+1][0]==o&&(r[n][1].substring(r[n][1].length-r[n-1][1].length)==r[n-1][1]?(r[n][1]=r[n-1][1]+r[n][1].substring(0,r[n][1].length-r[n-1][1].length),r[n+1][1]=r[n-1][1]+r[n+1][1],r.splice(n-1,1),h=!0):r[n][1].substring(0,r[n+1][1].length)==r[n+1][1]&&(r[n-1][1]+=r[n+1][1],r[n][1]=r[n][1].substring(r[n+1][1].length)+r[n+1][1],r.splice(n+1,1),h=!0)),n++;h&&this.diff_cleanupMerge(r)},f.prototype.diff_xIndex=function(r,n){var e=0,t=0,a=0,i=0,s;for(s=0;s<r.length&&(r[s][0]!==m&&(e+=r[s][1].length),r[s][0]!==w&&(t+=r[s][1].length),!(e>n));s++)a=e,i=t;return r.length!=s&&r[s][0]===w?i:i+(n-a)},f.prototype.diff_prettyHtml=function(r){for(var n=[],e=/&/g,t=/</g,a=/>/g,i=/\n/g,s=0;s<r.length;s++){var h=r[s][0],l=r[s][1],g=l.replace(e,"&").replace(t,"<").replace(a,">").replace(i,"¶<br>");switch(h){case m:n[s]='<ins style="background:#e6ffe6;">'+g+"</ins>";break;case w:n[s]='<del style="background:#ffe6e6;">'+g+"</del>";break;case o:n[s]="<span>"+g+"</span>";break}}return n.join("")},f.prototype.diff_text1=function(r){for(var n=[],e=0;e<r.length;e++)r[e][0]!==m&&(n[e]=r[e][1]);return n.join("")},f.prototype.diff_text2=function(r){for(var n=[],e=0;e<r.length;e++)r[e][0]!==w&&(n[e]=r[e][1]);return n.join("")},f.prototype.diff_levenshtein=function(r){for(var n=0,e=0,t=0,a=0;a<r.length;a++){var i=r[a][0],s=r[a][1];switch(i){case m:e+=s.length;break;case w:t+=s.length;break;case o:n+=Math.max(e,t),e=0,t=0;break}}return n+=Math.max(e,t),n},f.prototype.diff_toDelta=function(r){for(var n=[],e=0;e<r.length;e++)switch(r[e][0]){case m:n[e]="+"+encodeURI(r[e][1]);break;case w:n[e]="-"+r[e][1].length;break;case o:n[e]="="+r[e][1].length;break}return n.join(" ").replace(/%20/g," ")},f.prototype.diff_fromDelta=function(r,n){for(var e=[],t=0,a=0,i=n.split(/\t/g),s=0;s<i.length;s++){var h=i[s].substring(1);switch(i[s].charAt(0)){case"+":try{e[t++]=new f.Diff(m,decodeURI(h))}catch{throw new Error("Illegal escape in diff_fromDelta: "+h)}break;case"-":case"=":var l=parseInt(h,10);if(isNaN(l)||l<0)throw new Error("Invalid number in diff_fromDelta: "+h);var g=r.substring(a,a+=l);i[s].charAt(0)=="="?e[t++]=new f.Diff(o,g):e[t++]=new f.Diff(w,g);break;default:if(i[s])throw new Error("Invalid diff operation in diff_fromDelta: "+i[s])}}if(a!=r.length)throw new Error("Delta length ("+a+") does not equal source text length ("+r.length+").");return e},f.prototype.match_main=function(r,n,e){if(r==null||n==null||e==null)throw new Error("Null input. (match_main)");return e=Math.max(0,Math.min(e,r.length)),r==n?0:r.length?r.substring(e,e+n.length)==n?e:this.match_bitap_(r,n,e):-1},f.prototype.match_bitap_=function(r,n,e){if(n.length>this.Match_MaxBits)throw new Error("Pattern too long for this browser.");var t=this.match_alphabet_(n),a=this;function i(P,E){var y=P/n.length,d=Math.abs(e-E);return a.Match_Distance?y+d/a.Match_Distance:d?1:y}var s=this.Match_Threshold,h=r.indexOf(n,e);h!=-1&&(s=Math.min(i(0,h),s),h=r.lastIndexOf(n,e+n.length),h!=-1&&(s=Math.min(i(0,h),s)));var l=1<<n.length-1;h=-1;for(var g,u,v=n.length+r.length,c,_=0;_<n.length;_++){for(g=0,u=v;g<u;)i(_,e+u)<=s?g=u:v=u,u=Math.floor((v-g)/2+g);v=u;var b=Math.max(1,e-u+1),M=Math.min(e+u,r.length)+n.length,k=Array(M+2);k[M+1]=(1<<_)-1;for(var p=M;p>=b;p--){var D=t[r.charAt(p-1)];if(_===0?k[p]=(k[p+1]<<1|1)&D:k[p]=(k[p+1]<<1|1)&D|((c[p+1]|c[p])<<1|1)|c[p+1],k[p]&l){var A=i(_,p-1);if(A<=s)if(s=A,h=p-1,h>e)b=Math.max(1,2*e-h);else break}}if(i(_+1,e)>s)break;c=k}return h},f.prototype.match_alphabet_=function(r){for(var n={},e=0;e<r.length;e++)n[r.charAt(e)]=0;for(var e=0;e<r.length;e++)n[r.charAt(e)]|=1<<r.length-e-1;return n},f.prototype.patch_addContext_=function(r,n){if(n.length!=0){if(r.start2===null)throw Error("patch not initialized");for(var e=n.substring(r.start2,r.start2+r.length1),t=0;n.indexOf(e)!=n.lastIndexOf(e)&&e.length<this.Match_MaxBits-this.Patch_Margin-this.Patch_Margin;)t+=this.Patch_Margin,e=n.substring(r.start2-t,r.start2+r.length1+t);t+=this.Patch_Margin;var a=n.substring(r.start2-t,r.start2);a&&r.diffs.unshift(new f.Diff(o,a));var i=n.substring(r.start2+r.length1,r.start2+r.length1+t);i&&r.diffs.push(new f.Diff(o,i)),r.start1-=a.length,r.start2-=a.length,r.length1+=a.length+i.length,r.length2+=a.length+i.length}},f.prototype.patch_make=function(r,n,e){var t,a;if(typeof r=="string"&&typeof n=="string"&&typeof e>"u")t=r,a=this.diff_main(t,n,!0),a.length>2&&(this.diff_cleanupSemantic(a),this.diff_cleanupEfficiency(a));else if(r&&typeof r=="object"&&typeof n>"u"&&typeof e>"u")a=r,t=this.diff_text1(a);else if(typeof r=="string"&&n&&typeof n=="object"&&typeof e>"u")t=r,a=n;else if(typeof r=="string"&&typeof n=="string"&&e&&typeof e=="object")t=r,a=e;else throw new Error("Unknown call format to patch_make.");if(a.length===0)return[];for(var i=[],s=new f.patch_obj,h=0,l=0,g=0,u=t,v=t,c=0;c<a.length;c++){var _=a[c][0],b=a[c][1];switch(!h&&_!==o&&(s.start1=l,s.start2=g),_){case m:s.diffs[h++]=a[c],s.length2+=b.length,v=v.substring(0,g)+b+v.substring(g);break;case w:s.length1+=b.length,s.diffs[h++]=a[c],v=v.substring(0,g)+v.substring(g+b.length);break;case o:b.length<=2*this.Patch_Margin&&h&&a.length!=c+1?(s.diffs[h++]=a[c],s.length1+=b.length,s.length2+=b.length):b.length>=2*this.Patch_Margin&&h&&(this.patch_addContext_(s,u),i.push(s),s=new f.patch_obj,h=0,u=v,l=g);break}_!==m&&(l+=b.length),_!==w&&(g+=b.length)}return h&&(this.patch_addContext_(s,u),i.push(s)),i},f.prototype.patch_deepCopy=function(r){for(var n=[],e=0;e<r.length;e++){var t=r[e],a=new f.patch_obj;a.diffs=[];for(var i=0;i<t.diffs.length;i++)a.diffs[i]=new f.Diff(t.diffs[i][0],t.diffs[i][1]);a.start1=t.start1,a.start2=t.start2,a.length1=t.length1,a.length2=t.length2,n[e]=a}return n},f.prototype.patch_apply=function(r,n){if(r.length==0)return[n,[]];r=this.patch_deepCopy(r);var e=this.patch_addPadding(r);n=e+n+e,this.patch_splitMax(r);for(var t=0,a=[],i=0;i<r.length;i++){var s=r[i].start2+t,h=this.diff_text1(r[i].diffs),l,g=-1;if(h.length>this.Match_MaxBits?(l=this.match_main(n,h.substring(0,this.Match_MaxBits),s),l!=-1&&(g=this.match_main(n,h.substring(h.length-this.Match_MaxBits),s+h.length-this.Match_MaxBits),(g==-1||l>=g)&&(l=-1))):l=this.match_main(n,h,s),l==-1)a[i]=!1,t-=r[i].length2-r[i].length1;else{a[i]=!0,t=l-s;var u;if(g==-1?u=n.substring(l,l+h.length):u=n.substring(l,g+this.Match_MaxBits),h==u)n=n.substring(0,l)+this.diff_text2(r[i].diffs)+n.substring(l+h.length);else{var v=this.diff_main(h,u,!1);if(h.length>this.Match_MaxBits&&this.diff_levenshtein(v)/h.length>this.Patch_DeleteThreshold)a[i]=!1;else{this.diff_cleanupSemanticLossless(v);for(var c=0,_,b=0;b<r[i].diffs.length;b++){var M=r[i].diffs[b];M[0]!==o&&(_=this.diff_xIndex(v,c)),M[0]===m?n=n.substring(0,l+_)+M[1]+n.substring(l+_):M[0]===w&&(n=n.substring(0,l+_)+n.substring(l+this.diff_xIndex(v,c+M[1].length))),M[0]!==w&&(c+=M[1].length)}}}}}return n=n.substring(e.length,n.length-e.length),[n,a]},f.prototype.patch_addPadding=function(r){for(var n=this.Patch_Margin,e="",t=1;t<=n;t++)e+=String.fromCharCode(t);for(var t=0;t<r.length;t++)r[t].start1+=n,r[t].start2+=n;var a=r[0],i=a.diffs;if(i.length==0||i[0][0]!=o)i.unshift(new f.Diff(o,e)),a.start1-=n,a.start2-=n,a.length1+=n,a.length2+=n;else if(n>i[0][1].length){var s=n-i[0][1].length;i[0][1]=e.substring(i[0][1].length)+i[0][1],a.start1-=s,a.start2-=s,a.length1+=s,a.length2+=s}if(a=r[r.length-1],i=a.diffs,i.length==0||i[i.length-1][0]!=o)i.push(new f.Diff(o,e)),a.length1+=n,a.length2+=n;else if(n>i[i.length-1][1].length){var s=n-i[i.length-1][1].length;i[i.length-1][1]+=e.substring(0,s),a.length1+=s,a.length2+=s}return e},f.prototype.patch_splitMax=function(r){for(var n=this.Match_MaxBits,e=0;e<r.length;e++)if(!(r[e].length1<=n)){var t=r[e];r.splice(e--,1);for(var a=t.start1,i=t.start2,s="";t.diffs.length!==0;){var h=new f.patch_obj,l=!0;for(h.start1=a-s.length,h.start2=i-s.length,s!==""&&(h.length1=h.length2=s.length,h.diffs.push(new f.Diff(o,s)));t.diffs.length!==0&&h.length1<n-this.Patch_Margin;){var g=t.diffs[0][0],u=t.diffs[0][1];g===m?(h.length2+=u.length,i+=u.length,h.diffs.push(t.diffs.shift()),l=!1):g===w&&h.diffs.length==1&&h.diffs[0][0]==o&&u.length>2*n?(h.length1+=u.length,a+=u.length,l=!1,h.diffs.push(new f.Diff(g,u)),t.diffs.shift()):(u=u.substring(0,n-h.length1-this.Patch_Margin),h.length1+=u.length,a+=u.length,g===o?(h.length2+=u.length,i+=u.length):l=!1,h.diffs.push(new f.Diff(g,u)),u==t.diffs[0][1]?t.diffs.shift():t.diffs[0][1]=t.diffs[0][1].substring(u.length))}s=this.diff_text2(h.diffs),s=s.substring(s.length-this.Patch_Margin);var v=this.diff_text1(t.diffs).substring(0,this.Patch_Margin);v!==""&&(h.length1+=v.length,h.length2+=v.length,h.diffs.length!==0&&h.diffs[h.diffs.length-1][0]===o?h.diffs[h.diffs.length-1][1]+=v:h.diffs.push(new f.Diff(o,v))),l||r.splice(++e,0,h)}}},f.prototype.patch_toText=function(r){for(var n=[],e=0;e<r.length;e++)n[e]=r[e];return n.join("")},f.prototype.patch_fromText=function(r){var n=[];if(!r)return n;for(var e=r.split(`
|
|
3
|
+
`),t=0,a=/^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/;t<e.length;){var i=e[t].match(a);if(!i)throw new Error("Invalid patch string: "+e[t]);var s=new f.patch_obj;for(n.push(s),s.start1=parseInt(i[1],10),i[2]===""?(s.start1--,s.length1=1):i[2]=="0"?s.length1=0:(s.start1--,s.length1=parseInt(i[2],10)),s.start2=parseInt(i[3],10),i[4]===""?(s.start2--,s.length2=1):i[4]=="0"?s.length2=0:(s.start2--,s.length2=parseInt(i[4],10)),t++;t<e.length;){var h=e[t].charAt(0);try{var l=decodeURI(e[t].substring(1))}catch{throw new Error("Illegal escape in patch_fromText: "+l)}if(h=="-")s.diffs.push(new f.Diff(w,l));else if(h=="+")s.diffs.push(new f.Diff(m,l));else if(h==" ")s.diffs.push(new f.Diff(o,l));else{if(h=="@")break;if(h!=="")throw new Error('Invalid patch mode "'+h+'" in: '+l)}t++}}return n},f.patch_obj=function(){this.diffs=[],this.start1=null,this.start2=null,this.length1=0,this.length2=0},f.patch_obj.prototype.toString=function(){var r,n;this.length1===0?r=this.start1+",0":this.length1==1?r=this.start1+1:r=this.start1+1+","+this.length1,this.length2===0?n=this.start2+",0":this.length2==1?n=this.start2+1:n=this.start2+1+","+this.length2;for(var e=["@@ -"+r+" +"+n+` @@
|
|
4
|
+
`],t,a=0;a<this.diffs.length;a++){switch(this.diffs[a][0]){case m:t="+";break;case w:t="-";break;case o:t=" ";break}e[a+1]=t+encodeURI(this.diffs[a][1])+`
|
|
5
|
+
`}return e.join("").replace(/%20/g," ")},S.exports=f,S.exports.diff_match_patch=f,S.exports.DIFF_DELETE=w,S.exports.DIFF_INSERT=m,S.exports.DIFF_EQUAL=o})(R)),R.exports}var C=B();const L=F(C),j=N({__proto__:null,default:L},[C]);export{L as d,j as i};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{i as e}from"./index-
|
|
1
|
+
import{i as e}from"./index-DbzKrh7H.js";const i=e("Maximize2",[["polyline",{points:"15 3 21 3 21 9",key:"mznyad"}],["polyline",{points:"9 21 3 21 3 15",key:"1avn1i"}],["line",{x1:"21",x2:"14",y1:"3",y2:"10",key:"ota7mn"}],["line",{x1:"3",x2:"10",y1:"21",y2:"14",key:"1atl0r"}]]);const s=e("Minus",[["path",{d:"M5 12h14",key:"1ays0h"}]]);const y=e("Plus",[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"M12 5v14",key:"s699le"}]]);export{s as M,y as P,i as a};
|
package/dist/client/index.html
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
9
9
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500;600&family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&family=Source+Code+Pro:wght@400;500;600&display=swap" />
|
|
10
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/
|
|
10
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/iosevka@5.2.5/400.css" />
|
|
11
11
|
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
|
12
12
|
<title>TexLite</title>
|
|
13
|
-
<script type="module" crossorigin src="/assets/index-
|
|
13
|
+
<script type="module" crossorigin src="/assets/index-DbzKrh7H.js"></script>
|
|
14
14
|
<link rel="modulepreload" crossorigin href="/assets/react-CsO_xvbl.js">
|
|
15
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
15
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CtVAEMSo.css">
|
|
16
16
|
</head>
|
|
17
17
|
<body>
|
|
18
18
|
<div id="root"></div>
|
package/dist/server/app.js
CHANGED
|
@@ -20,6 +20,7 @@ import { CollaborationService } from "./collaboration.js";
|
|
|
20
20
|
import { ProjectGitService } from "./git.js";
|
|
21
21
|
import { LatexCompletionService } from "./latexCompletion.js";
|
|
22
22
|
import { ProjectHistoryService } from "./history.js";
|
|
23
|
+
import { LatexFormatterService } from "./latexFormatter.js";
|
|
23
24
|
import { buildProjectOutline } from "./projectOutline.js";
|
|
24
25
|
import { replaceProject, searchProject } from "./projectSearch.js";
|
|
25
26
|
import { MetricRegistry } from "./metrics.js";
|
|
@@ -159,6 +160,7 @@ export async function buildApp(config, db, options = {}) {
|
|
|
159
160
|
eventLoopDelay.enable();
|
|
160
161
|
const history = new ProjectHistoryService(config, db);
|
|
161
162
|
const latexCompletions = new LatexCompletionService(config);
|
|
163
|
+
const latexFormatter = new LatexFormatterService();
|
|
162
164
|
const recordHistory = (projectId, userId, reason, paths) => {
|
|
163
165
|
try {
|
|
164
166
|
return history.record(projectId, userId, reason, paths);
|
|
@@ -238,7 +240,7 @@ export async function buildApp(config, db, options = {}) {
|
|
|
238
240
|
allowedEngines: config.allowedEngines,
|
|
239
241
|
allowProjectLatexmkrc: config.allowProjectLatexmkrc
|
|
240
242
|
}));
|
|
241
|
-
app.get("/api/health", async () => ({ ok: true, latexmk: config.latexmk }));
|
|
243
|
+
app.get("/api/health", async () => ({ ok: true, pid: process.pid, latexmk: config.latexmk }));
|
|
242
244
|
app.get("/api/health/metrics", async (request, reply) => {
|
|
243
245
|
if (!requireAdmin(request, reply, db))
|
|
244
246
|
return;
|
|
@@ -1023,6 +1025,38 @@ export async function buildApp(config, db, options = {}) {
|
|
|
1023
1025
|
return apiError(reply, 404, "PROJECT_NOT_FOUND", "项目不存在");
|
|
1024
1026
|
return { files: listProjectFiles(config, id) };
|
|
1025
1027
|
});
|
|
1028
|
+
app.post("/api/projects/:id/format", async (request, reply) => {
|
|
1029
|
+
const user = requireUser(request, reply, db);
|
|
1030
|
+
if (!user)
|
|
1031
|
+
return;
|
|
1032
|
+
const { id } = request.params;
|
|
1033
|
+
const project = accessibleProject(db, id, user);
|
|
1034
|
+
if (!project)
|
|
1035
|
+
return apiError(reply, 404, "PROJECT_NOT_FOUND", "项目不存在");
|
|
1036
|
+
if (!canEdit(project))
|
|
1037
|
+
return apiError(reply, 403, "PROJECT_EDIT_FORBIDDEN", "没有编辑权限");
|
|
1038
|
+
const body = request.body;
|
|
1039
|
+
const filePath = safeRelativePath(typeof body?.path === "string" ? body.path : "");
|
|
1040
|
+
if (!/\.(?:tex|bib|cls|sty)$/i.test(filePath)) {
|
|
1041
|
+
return apiError(reply, 400, "FORMAT_FILE_UNSUPPORTED", "只有 .tex、.bib、.cls 和 .sty 文件支持格式化", { path: filePath });
|
|
1042
|
+
}
|
|
1043
|
+
if (typeof body?.source !== "string")
|
|
1044
|
+
return apiError(reply, 400, "FORMAT_SOURCE_INVALID", "待格式化内容格式不正确");
|
|
1045
|
+
if (Buffer.byteLength(body.source, "utf8") > config.maxUploadBytes) {
|
|
1046
|
+
return apiError(reply, 413, "FILE_TOO_LARGE", `单个文件不能超过 ${Math.floor(config.maxUploadBytes / 1024 / 1024)} MB`, { path: filePath });
|
|
1047
|
+
}
|
|
1048
|
+
try {
|
|
1049
|
+
return await latexFormatter.format(body.source, sourceRoot(config, id));
|
|
1050
|
+
}
|
|
1051
|
+
catch (formatError) {
|
|
1052
|
+
const statusCode = typeof formatError === "object" && formatError !== null && "statusCode" in formatError && typeof formatError.statusCode === "number"
|
|
1053
|
+
? formatError.statusCode : 422;
|
|
1054
|
+
const code = typeof formatError === "object" && formatError !== null && "code" in formatError && typeof formatError.code === "string"
|
|
1055
|
+
? formatError.code : "FORMAT_FAILED";
|
|
1056
|
+
const message = formatError instanceof Error ? formatError.message : "LaTeX formatting failed";
|
|
1057
|
+
return apiError(reply, statusCode, code, message);
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1026
1060
|
app.get("/api/projects/:id/outline", async (request, reply) => {
|
|
1027
1061
|
const user = requireUser(request, reply, db);
|
|
1028
1062
|
if (!user)
|
package/dist/server/cli.js
CHANGED
|
@@ -33,6 +33,7 @@ Commands:
|
|
|
33
33
|
Options:
|
|
34
34
|
--config PATH Use this configuration file instead of TEXLITE_CONFIG or the XDG default.
|
|
35
35
|
--git Make doctor check the optional Git integration as well.
|
|
36
|
+
--json Print status as JSON for scripts instead of the terminal view.
|
|
36
37
|
-h, --help Show this help message.
|
|
37
38
|
-v, --version Print the installed TexLite version.
|
|
38
39
|
`;
|
|
@@ -40,18 +41,23 @@ export function parseArgs(args) {
|
|
|
40
41
|
let command = "help";
|
|
41
42
|
let configPath;
|
|
42
43
|
let checkGit = false;
|
|
44
|
+
let json = false;
|
|
43
45
|
for (let index = 0; index < args.length; index += 1) {
|
|
44
46
|
const argument = args[index];
|
|
45
47
|
if (!argument)
|
|
46
48
|
continue;
|
|
47
49
|
if (argument === "-h" || argument === "--help")
|
|
48
|
-
return { command: "help", configPath, checkGit };
|
|
50
|
+
return { command: "help", configPath, checkGit, json };
|
|
49
51
|
if (argument === "-v" || argument === "--version")
|
|
50
|
-
return { command: "version", configPath, checkGit };
|
|
52
|
+
return { command: "version", configPath, checkGit, json };
|
|
51
53
|
if (argument === "--git") {
|
|
52
54
|
checkGit = true;
|
|
53
55
|
continue;
|
|
54
56
|
}
|
|
57
|
+
if (argument === "--json") {
|
|
58
|
+
json = true;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
55
61
|
if (argument === "--config") {
|
|
56
62
|
const value = args[++index];
|
|
57
63
|
if (!value)
|
|
@@ -72,7 +78,7 @@ export function parseArgs(args) {
|
|
|
72
78
|
throw new Error(`Only one command may be specified (already received ${command})`);
|
|
73
79
|
command = argument;
|
|
74
80
|
}
|
|
75
|
-
return { command, configPath, checkGit };
|
|
81
|
+
return { command, configPath, checkGit, json };
|
|
76
82
|
}
|
|
77
83
|
function output(message) {
|
|
78
84
|
stdout.write(`${message}\n`);
|
|
@@ -136,10 +142,18 @@ async function initialize(options) {
|
|
|
136
142
|
let db = null;
|
|
137
143
|
try {
|
|
138
144
|
if (!fs.existsSync(configPath)) {
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
145
|
+
const configuredSiteName = process.env.TEXLITE_SITE_NAME;
|
|
146
|
+
const siteName = configuredSiteName !== undefined
|
|
147
|
+
? configuredSiteName.trim() || CONFIG_DEFAULTS.siteName
|
|
148
|
+
: rl
|
|
149
|
+
? (await rl.question(`Site name [${CONFIG_DEFAULTS.siteName}]: `)).trim() || CONFIG_DEFAULTS.siteName
|
|
150
|
+
: CONFIG_DEFAULTS.siteName;
|
|
151
|
+
const configuredAdminEmail = process.env.TEXLITE_ADMIN_EMAIL;
|
|
152
|
+
const adminEmail = configuredAdminEmail !== undefined
|
|
153
|
+
? configuredAdminEmail.trim()
|
|
154
|
+
: rl
|
|
155
|
+
? (await rl.question("Administrator contact email (optional): ")).trim()
|
|
156
|
+
: "";
|
|
143
157
|
writeInitialConfig(configPath, siteName, adminEmail);
|
|
144
158
|
output(`Created configuration file: ${configPath}`);
|
|
145
159
|
}
|
|
@@ -240,10 +254,83 @@ async function start(options, restart = false) {
|
|
|
240
254
|
async function status(options) {
|
|
241
255
|
const { config } = await loadValidatedConfig(options);
|
|
242
256
|
const result = await processStatus(config);
|
|
243
|
-
output(JSON.stringify(result, null, 2));
|
|
257
|
+
output(options.json ? JSON.stringify(result, null, 2) : formatProcessStatus(result, terminalColorsEnabled()));
|
|
244
258
|
if (result.status !== "online")
|
|
245
259
|
process.exitCode = 2;
|
|
246
260
|
}
|
|
261
|
+
const ANSI = {
|
|
262
|
+
bold: "\u001b[1m",
|
|
263
|
+
green: "\u001b[32m",
|
|
264
|
+
yellow: "\u001b[33m",
|
|
265
|
+
red: "\u001b[31m",
|
|
266
|
+
gray: "\u001b[90m",
|
|
267
|
+
reset: "\u001b[0m"
|
|
268
|
+
};
|
|
269
|
+
function terminalColorsEnabled() {
|
|
270
|
+
if (Object.hasOwn(process.env, "NO_COLOR"))
|
|
271
|
+
return false;
|
|
272
|
+
if (process.env.FORCE_COLOR && process.env.FORCE_COLOR !== "0")
|
|
273
|
+
return true;
|
|
274
|
+
return Boolean(stdout.isTTY);
|
|
275
|
+
}
|
|
276
|
+
function colorize(value, color, enabled) {
|
|
277
|
+
return enabled ? `${ANSI[color]}${value}${ANSI.reset}` : value;
|
|
278
|
+
}
|
|
279
|
+
function duration(value) {
|
|
280
|
+
if (value === null)
|
|
281
|
+
return "n/a";
|
|
282
|
+
if (value < 60)
|
|
283
|
+
return `${value}s`;
|
|
284
|
+
const minutes = Math.floor(value / 60);
|
|
285
|
+
const seconds = value % 60;
|
|
286
|
+
if (minutes < 60)
|
|
287
|
+
return `${minutes}min ${seconds}s`;
|
|
288
|
+
const hours = Math.floor(minutes / 60);
|
|
289
|
+
const remainingMinutes = minutes % 60;
|
|
290
|
+
if (hours < 24)
|
|
291
|
+
return `${hours}h ${remainingMinutes}min`;
|
|
292
|
+
const days = Math.floor(hours / 24);
|
|
293
|
+
return `${days}d ${hours % 24}h`;
|
|
294
|
+
}
|
|
295
|
+
function statusAppearance(status) {
|
|
296
|
+
switch (status) {
|
|
297
|
+
case "online": return { color: "green", active: "active (running)", health: "healthy" };
|
|
298
|
+
case "unhealthy": return { color: "red", active: "failed (unhealthy)", health: "unhealthy" };
|
|
299
|
+
case "errored": return { color: "red", active: "failed (errored)", health: "unhealthy" };
|
|
300
|
+
case "launching": return { color: "yellow", active: "activating (starting)", health: "starting" };
|
|
301
|
+
case "stopped": return { color: "yellow", active: "inactive (stopped)", health: "not running" };
|
|
302
|
+
case "missing": return { color: "gray", active: "inactive (not managed)", health: "not running" };
|
|
303
|
+
default: return { color: "yellow", active: status, health: "unknown" };
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
export function formatProcessStatus(status, colors = false) {
|
|
307
|
+
const appearance = statusAppearance(status.status);
|
|
308
|
+
const bullet = colorize("●", appearance.color, colors);
|
|
309
|
+
const title = colors ? `${ANSI.bold}TexLite${ANSI.reset}` : "TexLite";
|
|
310
|
+
const active = colorize(appearance.active, appearance.color, colors);
|
|
311
|
+
const health = colorize(appearance.health, appearance.color, colors);
|
|
312
|
+
const loaded = status.pm2Status === "missing"
|
|
313
|
+
? colorize(`not-found (PM2 process: ${status.name})`, "gray", colors)
|
|
314
|
+
: `loaded (PM2 process: ${status.name})`;
|
|
315
|
+
const since = status.startedAt && status.uptimeSeconds !== null
|
|
316
|
+
? ` since ${status.startedAt}; ${duration(status.uptimeSeconds)} ago`
|
|
317
|
+
: "";
|
|
318
|
+
return [
|
|
319
|
+
`${bullet} ${title} - Lightweight collaborative LaTeX editor`,
|
|
320
|
+
` Loaded: ${loaded}`,
|
|
321
|
+
` Active: ${active}${since}`,
|
|
322
|
+
` Health: ${health} (PM2: ${status.pm2Status})`,
|
|
323
|
+
` Main PID: ${status.pid ?? "n/a"}`,
|
|
324
|
+
` Version: ${status.version}`,
|
|
325
|
+
` Restarts: ${status.restarts}`,
|
|
326
|
+
` Listen: ${status.address}`,
|
|
327
|
+
` Config: ${status.configPath}`,
|
|
328
|
+
` Data: ${status.dataDir}`,
|
|
329
|
+
` CWD: ${status.cwd}`,
|
|
330
|
+
` Stdout: ${status.outputLog ?? "n/a"}`,
|
|
331
|
+
` Stderr: ${status.errorLog ?? "n/a"}`
|
|
332
|
+
].join("\n");
|
|
333
|
+
}
|
|
247
334
|
async function stop(options) {
|
|
248
335
|
const { config } = await loadValidatedConfig(options);
|
|
249
336
|
const managed = await stopManaged(config.configPath);
|
package/dist/server/index.js
CHANGED
|
@@ -29,6 +29,8 @@ export async function startServer(configPath) {
|
|
|
29
29
|
}
|
|
30
30
|
app.log.info(`Environment ready: ${environment.map((item) => `${item.name} ${item.version}`).join(", ")}`);
|
|
31
31
|
app.log.info(`${config.siteName} running at http://${config.host}:${config.port}`);
|
|
32
|
+
if (typeof process.send === "function")
|
|
33
|
+
process.send("ready");
|
|
32
34
|
return {
|
|
33
35
|
address: `http://${config.host}:${config.port}`,
|
|
34
36
|
close: async () => {
|
|
@@ -62,7 +64,7 @@ export async function main() {
|
|
|
62
64
|
if (isMainModule()) {
|
|
63
65
|
main().catch((error) => {
|
|
64
66
|
console.error(error instanceof Error ? error.message : error);
|
|
65
|
-
process.
|
|
67
|
+
process.exit(1);
|
|
66
68
|
});
|
|
67
69
|
}
|
|
68
70
|
function isMainModule() {
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
const DEFAULT_COMMAND = "tex-fmt";
|
|
3
|
+
const FORMAT_TIMEOUT_MS = 30_000;
|
|
4
|
+
const MAX_DIAGNOSTIC_BYTES = 16 * 1024;
|
|
5
|
+
/**
|
|
6
|
+
* Invokes the optional host formatter without involving a shell. Keeping
|
|
7
|
+
* this in the server means project-level tex-fmt.toml files are discovered
|
|
8
|
+
* from the project's source directory and the browser never needs a native
|
|
9
|
+
* executable.
|
|
10
|
+
*/
|
|
11
|
+
export class LatexFormatterService {
|
|
12
|
+
command;
|
|
13
|
+
constructor(command = process.env.TEXLITE_TEX_FMT?.trim() || DEFAULT_COMMAND) {
|
|
14
|
+
this.command = command;
|
|
15
|
+
}
|
|
16
|
+
async format(source, cwd) {
|
|
17
|
+
const result = await runCommand(this.command, ["--stdin"], source, cwd, FORMAT_TIMEOUT_MS);
|
|
18
|
+
// tex-fmt is optional like Git, but formatting must never silently switch
|
|
19
|
+
// algorithms: users should know when the host formatter is unavailable.
|
|
20
|
+
if (result.error?.code === "ENOENT")
|
|
21
|
+
throw formatterUnavailable(this.command);
|
|
22
|
+
if (result.error)
|
|
23
|
+
throw formatterError(`无法运行 tex-fmt:${result.error.message}`);
|
|
24
|
+
if (result.status !== 0) {
|
|
25
|
+
const detail = result.stderr.trim() || `退出码 ${result.status ?? "unknown"}`;
|
|
26
|
+
throw formatterError(`tex-fmt 格式化失败:${truncate(detail)}`);
|
|
27
|
+
}
|
|
28
|
+
if (source.trim() && !result.stdout.trim())
|
|
29
|
+
throw formatterError("tex-fmt 未返回格式化后的源码");
|
|
30
|
+
return { formatter: "tex-fmt", formatted: result.stdout };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function formatterError(message) {
|
|
34
|
+
return Object.assign(new Error(message), { code: "FORMAT_FAILED", statusCode: 422 });
|
|
35
|
+
}
|
|
36
|
+
function formatterUnavailable(command) {
|
|
37
|
+
return Object.assign(new Error(`tex-fmt is not installed or cannot be found as "${command}". Install it from https://github.com/wgunderwood/tex-fmt and restart TexLite.`), { code: "FORMATTER_UNAVAILABLE", statusCode: 503 });
|
|
38
|
+
}
|
|
39
|
+
function truncate(value) {
|
|
40
|
+
const bytes = Buffer.byteLength(value, "utf8");
|
|
41
|
+
if (bytes <= MAX_DIAGNOSTIC_BYTES)
|
|
42
|
+
return value;
|
|
43
|
+
return `${Buffer.from(value, "utf8").subarray(0, MAX_DIAGNOSTIC_BYTES).toString("utf8")}…`;
|
|
44
|
+
}
|
|
45
|
+
function runCommand(command, args, input, cwd, timeoutMs) {
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
let child;
|
|
48
|
+
try {
|
|
49
|
+
child = spawn(command, args, {
|
|
50
|
+
cwd,
|
|
51
|
+
env: { ...process.env, LC_ALL: "C" },
|
|
52
|
+
shell: false,
|
|
53
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
resolve({
|
|
58
|
+
status: null,
|
|
59
|
+
stdout: "",
|
|
60
|
+
stderr: "",
|
|
61
|
+
error: error instanceof Error ? Object.assign(error, { code: error.code }) : new Error(String(error))
|
|
62
|
+
});
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const stdout = [];
|
|
66
|
+
const stderr = [];
|
|
67
|
+
let stdoutBytes = 0;
|
|
68
|
+
let stderrBytes = 0;
|
|
69
|
+
let settled = false;
|
|
70
|
+
let timedOut = false;
|
|
71
|
+
let outputTooLarge = false;
|
|
72
|
+
const finish = (result) => {
|
|
73
|
+
if (settled)
|
|
74
|
+
return;
|
|
75
|
+
settled = true;
|
|
76
|
+
clearTimeout(timer);
|
|
77
|
+
resolve({
|
|
78
|
+
...result,
|
|
79
|
+
stdout: Buffer.concat(stdout).toString("utf8"),
|
|
80
|
+
stderr: Buffer.concat(stderr).toString("utf8")
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
const timer = setTimeout(() => {
|
|
84
|
+
timedOut = true;
|
|
85
|
+
child.kill("SIGKILL");
|
|
86
|
+
}, timeoutMs);
|
|
87
|
+
child.stdout.on("data", (chunk) => {
|
|
88
|
+
if (outputTooLarge || stdoutBytes >= 64 * 1024 * 1024)
|
|
89
|
+
return;
|
|
90
|
+
const remaining = 64 * 1024 * 1024 - stdoutBytes;
|
|
91
|
+
const bytes = chunk.byteLength > remaining ? chunk.subarray(0, remaining) : chunk;
|
|
92
|
+
stdout.push(bytes);
|
|
93
|
+
stdoutBytes += bytes.byteLength;
|
|
94
|
+
if (chunk.byteLength > remaining) {
|
|
95
|
+
outputTooLarge = true;
|
|
96
|
+
child.kill("SIGKILL");
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
child.stderr.on("data", (chunk) => {
|
|
100
|
+
if (stderrBytes >= MAX_DIAGNOSTIC_BYTES)
|
|
101
|
+
return;
|
|
102
|
+
const remaining = MAX_DIAGNOSTIC_BYTES - stderrBytes;
|
|
103
|
+
const bytes = chunk.byteLength > remaining ? chunk.subarray(0, remaining) : chunk;
|
|
104
|
+
stderr.push(bytes);
|
|
105
|
+
stderrBytes += bytes.byteLength;
|
|
106
|
+
});
|
|
107
|
+
child.once("error", (error) => finish({ status: null, error }));
|
|
108
|
+
child.once("close", (status) => {
|
|
109
|
+
finish({
|
|
110
|
+
status,
|
|
111
|
+
error: timedOut ? new Error(`命令超时(${timeoutMs}ms)`) : outputTooLarge ? new Error("tex-fmt 输出过大") : null
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
child.stdin.end(input, "utf8");
|
|
115
|
+
});
|
|
116
|
+
}
|
package/dist/server/pm2.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
+
import http from "node:http";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { defaultConfigPath, packageRootDirectory, packageServerEntry } from "./runtimePaths.js";
|
|
5
6
|
let pm2Client = null;
|
|
@@ -39,7 +40,10 @@ function environmentOf(process) {
|
|
|
39
40
|
}
|
|
40
41
|
export function processName(configPath) {
|
|
41
42
|
const absoluteConfigPath = path.resolve(configPath);
|
|
42
|
-
|
|
43
|
+
// Reserve the short conventional name for the standard fallback path only.
|
|
44
|
+
// If XDG_CONFIG_HOME points elsewhere, the absolute path must participate in
|
|
45
|
+
// the name or every isolated/test installation would collide on `texlite`.
|
|
46
|
+
if (absoluteConfigPath === defaultConfigPath({}))
|
|
43
47
|
return "texlite";
|
|
44
48
|
const digest = crypto.createHash("sha256").update(absoluteConfigPath).digest("hex").slice(0, 8);
|
|
45
49
|
return `texlite-${digest}`;
|
|
@@ -76,11 +80,26 @@ function startOptions(config) {
|
|
|
76
80
|
instances: 1,
|
|
77
81
|
autorestart: true,
|
|
78
82
|
watch: false,
|
|
83
|
+
wait_ready: true,
|
|
84
|
+
listen_timeout: 45_000,
|
|
85
|
+
min_uptime: 10_000,
|
|
86
|
+
max_restarts: 5,
|
|
87
|
+
restart_delay: 500,
|
|
79
88
|
max_memory_restart: "512M",
|
|
80
89
|
kill_timeout: 10_000,
|
|
81
90
|
env: environment
|
|
82
91
|
};
|
|
83
92
|
}
|
|
93
|
+
function launch(config) {
|
|
94
|
+
return callback((done) => {
|
|
95
|
+
pm2Client.start(startOptions(config), (error, process) => done(error ?? null, process));
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async function replaceProcess(existing, config) {
|
|
99
|
+
await callback((done) => pm2Client.delete(processId(existing), (error) => done(error ?? null)));
|
|
100
|
+
const started = await launch(config);
|
|
101
|
+
return Array.isArray(started) ? started[0] : started;
|
|
102
|
+
}
|
|
84
103
|
export async function startManaged(config) {
|
|
85
104
|
await connect();
|
|
86
105
|
try {
|
|
@@ -92,13 +111,18 @@ export async function startManaged(config) {
|
|
|
92
111
|
&& environmentOf(process).TEXLITE_CONFIG === absoluteConfigPath);
|
|
93
112
|
if (existing) {
|
|
94
113
|
const status = existing.pm2_env?.status;
|
|
95
|
-
if (status === "
|
|
114
|
+
if (status === "launching")
|
|
96
115
|
return { name, configPath: config.configPath, description: existing };
|
|
97
|
-
|
|
98
|
-
|
|
116
|
+
if (status === "online") {
|
|
117
|
+
const probe = await probeService(config);
|
|
118
|
+
if (probe.ok && probe.pid === existing.pid) {
|
|
119
|
+
return { name, configPath: config.configPath, description: existing };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const refreshed = await replaceProcess(existing, config);
|
|
99
123
|
return { name, configPath: config.configPath, description: refreshed };
|
|
100
124
|
}
|
|
101
|
-
const started = await
|
|
125
|
+
const started = await launch(config);
|
|
102
126
|
const description = Array.isArray(started) ? started[0] : started;
|
|
103
127
|
return { name, configPath: config.configPath, description };
|
|
104
128
|
}
|
|
@@ -136,11 +160,10 @@ export async function restartManaged(config) {
|
|
|
136
160
|
const existing = processes.find((process) => process.name === name
|
|
137
161
|
&& environmentOf(process).TEXLITE_CONFIG === absoluteConfigPath);
|
|
138
162
|
if (!existing) {
|
|
139
|
-
const started = await
|
|
163
|
+
const started = await launch(config);
|
|
140
164
|
return { name, configPath: config.configPath, description: Array.isArray(started) ? started[0] : started };
|
|
141
165
|
}
|
|
142
|
-
|
|
143
|
-
const description = (await list()).find((process) => process.name === name) ?? existing;
|
|
166
|
+
const description = await replaceProcess(existing, config);
|
|
144
167
|
return { name, configPath: config.configPath, description };
|
|
145
168
|
}
|
|
146
169
|
finally {
|
|
@@ -151,12 +174,20 @@ export async function processStatus(config) {
|
|
|
151
174
|
const managed = await managedProcess(config.configPath);
|
|
152
175
|
const description = managed.description;
|
|
153
176
|
const env = description ? description.pm2_env : undefined;
|
|
177
|
+
const pm2Status = env?.status ?? "missing";
|
|
178
|
+
const probe = pm2Status === "online" ? await probeService(config) : { ok: false, pid: null, error: null };
|
|
179
|
+
const healthy = Boolean(description?.pid && probe.ok && probe.pid === description.pid);
|
|
180
|
+
const active = pm2Status === "online" || pm2Status === "launching";
|
|
181
|
+
const startedAt = active && typeof env?.pm_uptime === "number" ? env.pm_uptime : null;
|
|
154
182
|
return {
|
|
155
183
|
name: managed.name,
|
|
156
184
|
configPath: managed.configPath,
|
|
157
|
-
status:
|
|
185
|
+
status: pm2Status === "online" && !healthy ? "unhealthy" : pm2Status,
|
|
186
|
+
pm2Status,
|
|
187
|
+
healthy,
|
|
158
188
|
pid: description?.pid ?? null,
|
|
159
|
-
|
|
189
|
+
startedAt: startedAt === null ? null : new Date(startedAt).toISOString(),
|
|
190
|
+
uptimeSeconds: startedAt === null ? null : Math.max(0, Math.floor((Date.now() - startedAt) / 1000)),
|
|
160
191
|
restarts: env?.restart_time ?? 0,
|
|
161
192
|
version: packageVersion(),
|
|
162
193
|
address: `http://${config.host}:${config.port}`,
|
|
@@ -166,23 +197,95 @@ export async function processStatus(config) {
|
|
|
166
197
|
errorLog: env?.pm_err_log_path ?? null
|
|
167
198
|
};
|
|
168
199
|
}
|
|
169
|
-
/** Wait
|
|
170
|
-
*
|
|
171
|
-
|
|
200
|
+
/** Wait for both PM2 and the HTTP service. The health response PID prevents a
|
|
201
|
+
* foreground `texlite serve` process on the same port from being mistaken for
|
|
202
|
+
* the newly launched managed process. */
|
|
203
|
+
export async function waitForOnline(config, timeoutMs = 50_000) {
|
|
172
204
|
const deadline = Date.now() + timeoutMs;
|
|
173
205
|
let latest = await managedProcess(config.configPath);
|
|
206
|
+
let consecutiveHealthyChecks = 0;
|
|
207
|
+
let healthyPid = null;
|
|
208
|
+
let latestProbe = { ok: false, pid: null, error: null };
|
|
174
209
|
while (Date.now() < deadline) {
|
|
175
210
|
const status = latest.description?.pm2_env?.status;
|
|
176
|
-
if (status === "online")
|
|
177
|
-
|
|
211
|
+
if (status === "online") {
|
|
212
|
+
latestProbe = await probeService(config);
|
|
213
|
+
if (latestProbe.ok && latestProbe.pid === latest.description?.pid) {
|
|
214
|
+
consecutiveHealthyChecks = healthyPid === latestProbe.pid ? consecutiveHealthyChecks + 1 : 1;
|
|
215
|
+
healthyPid = latestProbe.pid;
|
|
216
|
+
if (consecutiveHealthyChecks >= 2)
|
|
217
|
+
return latest;
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
consecutiveHealthyChecks = 0;
|
|
221
|
+
healthyPid = null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
consecutiveHealthyChecks = 0;
|
|
226
|
+
healthyPid = null;
|
|
227
|
+
}
|
|
178
228
|
if (status === "errored" || status === "stopped") {
|
|
179
229
|
throw new Error(`PM2 could not start TexLite (current status: ${status}). Run texlite logs to inspect the logs.`);
|
|
180
230
|
}
|
|
181
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
231
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
182
232
|
latest = await managedProcess(config.configPath);
|
|
183
233
|
}
|
|
184
234
|
const status = latest.description?.pm2_env?.status ?? "missing";
|
|
185
|
-
|
|
235
|
+
const expectedPid = latest.description?.pid ?? "none";
|
|
236
|
+
const probeDetail = latestProbe.pid !== null
|
|
237
|
+
? `health endpoint belongs to PID ${latestProbe.pid}, expected PM2 PID ${expectedPid}`
|
|
238
|
+
: latestProbe.error ?? "health endpoint did not respond";
|
|
239
|
+
throw new Error(`Timed out waiting for TexLite to become healthy at ${healthUrl(config)} (PM2 status: ${status}; ${probeDetail}). Run texlite logs to investigate.`);
|
|
240
|
+
}
|
|
241
|
+
function probeHost(host) {
|
|
242
|
+
if (host === "0.0.0.0")
|
|
243
|
+
return "127.0.0.1";
|
|
244
|
+
if (host === "::" || host === "[::]")
|
|
245
|
+
return "::1";
|
|
246
|
+
return host;
|
|
247
|
+
}
|
|
248
|
+
function healthUrl(config) {
|
|
249
|
+
const host = probeHost(config.host);
|
|
250
|
+
const formattedHost = host.includes(":") ? `[${host}]` : host;
|
|
251
|
+
return `http://${formattedHost}:${config.port}/api/health`;
|
|
252
|
+
}
|
|
253
|
+
function probeService(config, timeoutMs = 1_500) {
|
|
254
|
+
return new Promise((resolve) => {
|
|
255
|
+
let settled = false;
|
|
256
|
+
const finish = (result) => {
|
|
257
|
+
if (settled)
|
|
258
|
+
return;
|
|
259
|
+
settled = true;
|
|
260
|
+
resolve(result);
|
|
261
|
+
};
|
|
262
|
+
const request = http.get(healthUrl(config), { timeout: timeoutMs }, (response) => {
|
|
263
|
+
const chunks = [];
|
|
264
|
+
response.on("data", (chunk) => {
|
|
265
|
+
if (Buffer.concat(chunks).length < 16 * 1024)
|
|
266
|
+
chunks.push(chunk);
|
|
267
|
+
});
|
|
268
|
+
response.once("end", () => {
|
|
269
|
+
if (response.statusCode !== 200) {
|
|
270
|
+
finish({ ok: false, pid: null, error: `health endpoint returned HTTP ${response.statusCode ?? "unknown"}` });
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
try {
|
|
274
|
+
const body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
275
|
+
finish({
|
|
276
|
+
ok: body.ok === true && Number.isInteger(body.pid),
|
|
277
|
+
pid: Number.isInteger(body.pid) ? body.pid : null,
|
|
278
|
+
error: body.ok === true && Number.isInteger(body.pid) ? null : "health endpoint returned an invalid response"
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
finish({ ok: false, pid: null, error: "health endpoint returned invalid JSON" });
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
request.once("timeout", () => request.destroy(new Error("health check timed out")));
|
|
287
|
+
request.once("error", (error) => finish({ ok: false, pid: null, error: error.message }));
|
|
288
|
+
});
|
|
186
289
|
}
|
|
187
290
|
export async function streamLogs(config) {
|
|
188
291
|
await connect();
|
|
@@ -38,5 +38,5 @@ export function packageClientDirectory(moduleUrl = import.meta.url) {
|
|
|
38
38
|
return path.join(packageRootDirectory(moduleUrl), "dist", "client");
|
|
39
39
|
}
|
|
40
40
|
export function packageServerEntry(moduleUrl = import.meta.url) {
|
|
41
|
-
return path.resolve(path.dirname(fileURLToPath(moduleUrl)), "
|
|
41
|
+
return path.resolve(path.dirname(fileURLToPath(moduleUrl)), "service.js");
|
|
42
42
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { main } from "./index.js";
|
|
2
|
+
// PM2 keeps an IPC channel open for managed processes. A failed startup must
|
|
3
|
+
// therefore terminate explicitly: assigning process.exitCode alone can leave
|
|
4
|
+
// an idle process that PM2 incorrectly reports as online.
|
|
5
|
+
main().catch((error) => {
|
|
6
|
+
console.error(error instanceof Error ? error.message : error);
|
|
7
|
+
process.exit(1);
|
|
8
|
+
});
|