difit 0.0.5 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -10
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.test.d.ts +1 -0
- package/dist/cli/index.test.js +676 -0
- package/dist/cli/utils.d.ts +1 -0
- package/dist/cli/utils.js +12 -1
- package/dist/cli/utils.test.d.ts +1 -0
- package/dist/cli/utils.test.js +214 -0
- package/dist/client/assets/index-CGpOyJJl.js +178 -0
- package/dist/client/assets/index-CpclbaYk.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/server/git-diff-tui.d.ts +2 -0
- package/dist/server/git-diff-tui.js +95 -0
- package/dist/server/git-diff.d.ts +10 -0
- package/dist/server/git-diff.js +23 -5
- package/dist/server/git-diff.test.d.ts +1 -0
- package/dist/server/git-diff.test.js +292 -0
- package/dist/server/server.d.ts +12 -0
- package/dist/server/server.js +8 -58
- package/dist/server/server.test.d.ts +1 -0
- package/dist/server/server.test.js +382 -0
- package/dist/tui/App.d.ts +8 -0
- package/dist/tui/App.js +92 -0
- package/dist/tui/components/DiffViewer.d.ts +9 -0
- package/dist/tui/components/DiffViewer.js +88 -0
- package/dist/tui/components/FileList.d.ts +8 -0
- package/dist/tui/components/FileList.js +48 -0
- package/dist/tui/components/SideBySideDiffViewer.d.ts +9 -0
- package/dist/tui/components/SideBySideDiffViewer.js +237 -0
- package/dist/tui/components/StatusBar.d.ts +8 -0
- package/dist/tui/components/StatusBar.js +23 -0
- package/dist/tui/utils/parseDiff.d.ts +2 -0
- package/dist/tui/utils/parseDiff.js +68 -0
- package/dist/types/diff.d.ts +35 -0
- package/dist/utils/fileUtils.d.ts +12 -0
- package/dist/utils/fileUtils.js +21 -0
- package/package.json +1 -1
- package/dist/client/assets/index-W2UC55JC.css +0 -1
- package/dist/client/assets/index-hiGBtmpa.js +0 -142
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/*! tailwindcss v4.1.11 | MIT License | https://tailwindcss.com */@layer properties{@supports ((-webkit-hyphens:none) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-yellow-800:oklch(47.6% .114 61.907);--color-yellow-900:oklch(42.1% .095 57.708);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--font-weight-medium:500;--font-weight-semibold:600;--radius-md:.375rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-github-bg-primary:#0d1117;--color-github-bg-secondary:#161b22;--color-github-bg-tertiary:#21262d;--color-github-border:#30363d;--color-github-text-primary:#f0f6fc;--color-github-text-secondary:#8b949e;--color-github-text-muted:#6e7681;--color-github-accent:#238636;--color-github-danger:#da3633;--color-github-warning:#d29922;--color-diff-addition-bg:#0d4429;--color-diff-deletion-bg:#67060c}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::-moz-placeholder{opacity:1}::placeholder{opacity:1}@supports (not (-webkit-appearance:-apple-pay-button)) or (contain-intrinsic-size:1px){::-moz-placeholder{color:currentColor}::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::-moz-placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.absolute{position:absolute}.relative{position:relative}.static{position:static}.sticky{position:sticky}.top-0{top:calc(var(--spacing)*0)}.top-1\/2{top:50%}.left-3{left:calc(var(--spacing)*3)}.z-10{z-index:10}.m-0{margin:calc(var(--spacing)*0)}.m-2{margin:calc(var(--spacing)*2)}.mx-3{margin-inline:calc(var(--spacing)*3)}.mx-4{margin-inline:calc(var(--spacing)*4)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.inline{display:inline}.h-4{height:calc(var(--spacing)*4)}.h-full{height:100%}.h-screen{height:100vh}.min-h-\[20px\]{min-height:20px}.min-h-\[60px\]{min-height:60px}.w-1{width:calc(var(--spacing)*1)}.w-1\/2{width:50%}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-\[50px\]{width:50px}.w-\[60px\]{width:60px}.w-full{width:100%}.min-w-0{min-width:calc(var(--spacing)*0)}.flex-1{flex:1}.flex-shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.cursor-col-resize{cursor:col-resize}.cursor-pointer{cursor:pointer}.resize{resize:both}.resize-none{resize:none}.resize-y{resize:vertical}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:calc(var(--spacing)*1)}.gap-1\.5{gap:calc(var(--spacing)*1.5)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-none{--tw-border-style:none;border-style:none}.border-\[var\(--border-muted\)\]{border-color:var(--border-muted)}.border-blue-600\/50{border-color:#155dfc80}@supports (color:color-mix(in lab,red,red)){.border-blue-600\/50{border-color:color-mix(in oklab,var(--color-blue-600)50%,transparent)}}.border-github-accent{border-color:var(--color-github-accent)}.border-github-border{border-color:var(--color-github-border)}.border-github-text-muted{border-color:var(--color-github-text-muted)}.border-gray-500{border-color:var(--color-gray-500)}.border-gray-600\/50{border-color:#4a556580}@supports (color:color-mix(in lab,red,red)){.border-gray-600\/50{border-color:color-mix(in oklab,var(--color-gray-600)50%,transparent)}}.border-green-600\/50{border-color:#00a54480}@supports (color:color-mix(in lab,red,red)){.border-green-600\/50{border-color:color-mix(in oklab,var(--color-green-600)50%,transparent)}}.border-red-600\/50{border-color:#e4001480}@supports (color:color-mix(in lab,red,red)){.border-red-600\/50{border-color:color-mix(in oklab,var(--color-red-600)50%,transparent)}}.border-yellow-400\/30{border-color:#fac8004d}@supports (color:color-mix(in lab,red,red)){.border-yellow-400\/30{border-color:color-mix(in oklab,var(--color-yellow-400)30%,transparent)}}.border-yellow-600\/50{border-color:#cd890080}@supports (color:color-mix(in lab,red,red)){.border-yellow-600\/50{border-color:color-mix(in oklab,var(--color-yellow-600)50%,transparent)}}.border-l-yellow-400{border-left-color:var(--color-yellow-400)}.bg-\[var\(--bg-secondary\)\]{background-color:var(--bg-secondary)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-blue-700\/40{background-color:#1447e666}@supports (color:color-mix(in lab,red,red)){.bg-blue-700\/40{background-color:color-mix(in oklab,var(--color-blue-700)40%,transparent)}}.bg-diff-addition-bg{background-color:var(--color-diff-addition-bg)}.bg-diff-deletion-bg{background-color:var(--color-diff-deletion-bg)}.bg-github-accent{background-color:var(--color-github-accent)}.bg-github-bg-primary{background-color:var(--color-github-bg-primary)}.bg-github-bg-secondary{background-color:var(--color-github-bg-secondary)}.bg-github-bg-tertiary{background-color:var(--color-github-bg-tertiary)}.bg-github-border{background-color:var(--color-github-border)}.bg-github-warning\/20{background-color:#d2992233}@supports (color:color-mix(in lab,red,red)){.bg-github-warning\/20{background-color:color-mix(in oklab,var(--color-github-warning)20%,transparent)}}.bg-gray-600{background-color:var(--color-gray-600)}.bg-gray-700\/40{background-color:#36415366}@supports (color:color-mix(in lab,red,red)){.bg-gray-700\/40{background-color:color-mix(in oklab,var(--color-gray-700)40%,transparent)}}.bg-green-100\/10{background-color:#dcfce71a}@supports (color:color-mix(in lab,red,red)){.bg-green-100\/10{background-color:color-mix(in oklab,var(--color-green-100)10%,transparent)}}.bg-green-700\/40{background-color:#00813866}@supports (color:color-mix(in lab,red,red)){.bg-green-700\/40{background-color:color-mix(in oklab,var(--color-green-700)40%,transparent)}}.bg-red-100\/10{background-color:#ffe2e21a}@supports (color:color-mix(in lab,red,red)){.bg-red-100\/10{background-color:color-mix(in oklab,var(--color-red-100)10%,transparent)}}.bg-red-700\/40{background-color:#bf000f66}@supports (color:color-mix(in lab,red,red)){.bg-red-700\/40{background-color:color-mix(in oklab,var(--color-red-700)40%,transparent)}}.bg-transparent{background-color:#0000}.bg-yellow-500\/10{background-color:#edb2001a}@supports (color:color-mix(in lab,red,red)){.bg-yellow-500\/10{background-color:color-mix(in oklab,var(--color-yellow-500)10%,transparent)}}.bg-yellow-700\/40{background-color:#a3610066}@supports (color:color-mix(in lab,red,red)){.bg-yellow-700\/40{background-color:color-mix(in oklab,var(--color-yellow-700)40%,transparent)}}.bg-yellow-800\/30{background-color:#874b004d}@supports (color:color-mix(in lab,red,red)){.bg-yellow-800\/30{background-color:color-mix(in oklab,var(--color-yellow-800)30%,transparent)}}.bg-yellow-900\/20{background-color:#733e0a33}@supports (color:color-mix(in lab,red,red)){.bg-yellow-900\/20{background-color:color-mix(in oklab,var(--color-yellow-900)20%,transparent)}}.p-0{padding:calc(var(--spacing)*0)}.p-1{padding:calc(var(--spacing)*1)}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-3{padding:calc(var(--spacing)*3)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-1\.5{padding-inline:calc(var(--spacing)*1.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-5{padding-inline:calc(var(--spacing)*5)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.pr-3{padding-right:calc(var(--spacing)*3)}.pl-9{padding-left:calc(var(--spacing)*9)}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-5{--tw-leading:calc(var(--spacing)*5);line-height:calc(var(--spacing)*5)}.leading-6{--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.break-all{word-break:break-all}.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-blue-200{color:var(--color-blue-200)}.text-github-accent{color:var(--color-github-accent)}.text-github-danger{color:var(--color-github-danger)}.text-github-text-muted{color:var(--color-github-text-muted)}.text-github-text-primary{color:var(--color-github-text-primary)}.text-github-text-secondary{color:var(--color-github-text-secondary)}.text-github-warning{color:var(--color-github-warning)}.text-gray-200{color:var(--color-gray-200)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-green-200{color:var(--color-green-200)}.text-red-200{color:var(--color-red-200)}.text-white{color:var(--color-white)}.text-yellow-100{color:var(--color-yellow-100)}.text-yellow-200{color:var(--color-yellow-200)}.italic{font-style:italic}.line-through{text-decoration-line:line-through}.placeholder-github-text-muted::-moz-placeholder{color:var(--color-github-text-muted)}.placeholder-github-text-muted::placeholder{color:var(--color-github-text-muted)}.opacity-70{opacity:.7}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}@media (hover:hover){.hover\:border-blue-500:hover{border-color:var(--color-blue-500)}.hover\:border-github-accent\/50:hover{border-color:#23863680}@supports (color:color-mix(in lab,red,red)){.hover\:border-github-accent\/50:hover{border-color:color-mix(in oklab,var(--color-github-accent)50%,transparent)}}.hover\:border-gray-500:hover{border-color:var(--color-gray-500)}.hover\:border-green-500:hover{border-color:var(--color-green-500)}.hover\:border-red-500:hover{border-color:var(--color-red-500)}.hover\:border-yellow-500:hover{border-color:var(--color-yellow-500)}.hover\:bg-blue-600\/50:hover{background-color:#155dfc80}@supports (color:color-mix(in lab,red,red)){.hover\:bg-blue-600\/50:hover{background-color:color-mix(in oklab,var(--color-blue-600)50%,transparent)}}.hover\:bg-github-bg-tertiary:hover{background-color:var(--color-github-bg-tertiary)}.hover\:bg-github-text-muted:hover{background-color:var(--color-github-text-muted)}.hover\:bg-gray-500:hover{background-color:var(--color-gray-500)}.hover\:bg-gray-600\/50:hover{background-color:#4a556580}@supports (color:color-mix(in lab,red,red)){.hover\:bg-gray-600\/50:hover{background-color:color-mix(in oklab,var(--color-gray-600)50%,transparent)}}.hover\:bg-green-600\/50:hover{background-color:#00a54480}@supports (color:color-mix(in lab,red,red)){.hover\:bg-green-600\/50:hover{background-color:color-mix(in oklab,var(--color-green-600)50%,transparent)}}.hover\:bg-red-600\/50:hover{background-color:#e4001480}@supports (color:color-mix(in lab,red,red)){.hover\:bg-red-600\/50:hover{background-color:color-mix(in oklab,var(--color-red-600)50%,transparent)}}.hover\:bg-yellow-600\/50:hover{background-color:#cd890080}@supports (color:color-mix(in lab,red,red)){.hover\:bg-yellow-600\/50:hover{background-color:color-mix(in oklab,var(--color-yellow-600)50%,transparent)}}.hover\:bg-yellow-800\/30:hover{background-color:#874b004d}@supports (color:color-mix(in lab,red,red)){.hover\:bg-yellow-800\/30:hover{background-color:color-mix(in oklab,var(--color-yellow-800)30%,transparent)}}.hover\:text-github-text-primary:hover{color:var(--color-github-text-primary)}.hover\:text-white:hover{color:var(--color-white)}.hover\:opacity-80:hover{opacity:.8}}.focus\:min-h-\[80px\]:focus{min-height:80px}.focus\:border-blue-600:focus{border-color:var(--color-blue-600)}.focus\:border-github-accent:focus{border-color:var(--color-github-accent)}.focus\:border-yellow-500:focus{border-color:var(--color-yellow-500)}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-blue-600\/30:focus{--tw-ring-color:#155dfc4d}@supports (color:color-mix(in lab,red,red)){.focus\:ring-blue-600\/30:focus{--tw-ring-color:color-mix(in oklab,var(--color-blue-600)30%,transparent)}}.focus\:ring-yellow-500:focus{--tw-ring-color:var(--color-yellow-500)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:opacity-50:disabled{opacity:.5}.\[\&_code\]\:\!bg-transparent code{background-color:#0000!important}.\[\&_code\]\:text-inherit code{color:inherit}.\[\&_pre\]\:m-0 pre{margin:calc(var(--spacing)*0)}.\[\&_pre\]\:\!bg-transparent pre{background-color:#0000!important}.\[\&_pre\]\:p-0 pre{padding:calc(var(--spacing)*0)}.\[\&_pre\]\:text-inherit pre{color:inherit}}html,body{color:#f0f6fc;background-color:#0d1117;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Noto Sans,Helvetica,Arial,sans-serif;font-size:14px;line-height:1.5}button{cursor:pointer}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}
|
package/dist/client/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>ReviewIt - Git Diff Viewer</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-CGpOyJJl.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CpclbaYk.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import simpleGit from 'simple-git';
|
|
2
|
+
import { validateDiffArguments, createCommitRangeString } from '../cli/utils.js';
|
|
3
|
+
export async function loadGitDiff(targetCommitish, baseCommitish) {
|
|
4
|
+
// Validate arguments
|
|
5
|
+
const validation = validateDiffArguments(targetCommitish, baseCommitish);
|
|
6
|
+
if (!validation.valid) {
|
|
7
|
+
throw new Error(validation.error);
|
|
8
|
+
}
|
|
9
|
+
const git = simpleGit();
|
|
10
|
+
let diff;
|
|
11
|
+
// Handle target special chars (base is always a regular commit)
|
|
12
|
+
if (targetCommitish === 'working') {
|
|
13
|
+
// Show unstaged changes (working vs staged)
|
|
14
|
+
diff = await git.diff(['--name-status']);
|
|
15
|
+
}
|
|
16
|
+
else if (targetCommitish === 'staged') {
|
|
17
|
+
// Show staged changes against base commit
|
|
18
|
+
diff = await git.diff(['--cached', baseCommitish, '--name-status']);
|
|
19
|
+
}
|
|
20
|
+
else if (targetCommitish === '.') {
|
|
21
|
+
// Show all uncommitted changes against base commit
|
|
22
|
+
diff = await git.diff([baseCommitish, '--name-status']);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
// Both are regular commits: standard commit-to-commit comparison
|
|
26
|
+
diff = await git.diff([
|
|
27
|
+
createCommitRangeString(baseCommitish, targetCommitish),
|
|
28
|
+
'--name-status',
|
|
29
|
+
]);
|
|
30
|
+
if (!diff.trim()) {
|
|
31
|
+
// Try without parent (for initial commit)
|
|
32
|
+
const diffInitial = await git.diff([targetCommitish, '--name-status']);
|
|
33
|
+
if (!diffInitial.trim()) {
|
|
34
|
+
throw new Error('No changes found in this commit');
|
|
35
|
+
}
|
|
36
|
+
diff = diffInitial;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const fileChanges = diff
|
|
40
|
+
.split('\n')
|
|
41
|
+
.filter((line) => line.trim())
|
|
42
|
+
.map((line) => {
|
|
43
|
+
const [status, ...pathParts] = line.split('\t');
|
|
44
|
+
const path = pathParts.join('\t');
|
|
45
|
+
return { status, path };
|
|
46
|
+
});
|
|
47
|
+
// Get diff for each file individually
|
|
48
|
+
const fileDiffs = await Promise.all(fileChanges.map(async ({ status, path }) => {
|
|
49
|
+
let fileDiff = '';
|
|
50
|
+
// Handle individual file diffs (base is always a regular commit)
|
|
51
|
+
if (targetCommitish === 'working') {
|
|
52
|
+
// Show unstaged changes (working vs staged)
|
|
53
|
+
fileDiff = await git.diff(['--', path]);
|
|
54
|
+
}
|
|
55
|
+
else if (targetCommitish === 'staged') {
|
|
56
|
+
// Show staged changes against base commit
|
|
57
|
+
fileDiff = await git.diff(['--cached', baseCommitish, '--', path]);
|
|
58
|
+
}
|
|
59
|
+
else if (targetCommitish === '.') {
|
|
60
|
+
// Show all uncommitted changes against base commit
|
|
61
|
+
fileDiff = await git.diff([baseCommitish, '--', path]);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
try {
|
|
65
|
+
// Both are regular commits: standard commit-to-commit comparison
|
|
66
|
+
fileDiff = await git.diff([
|
|
67
|
+
createCommitRangeString(baseCommitish, targetCommitish),
|
|
68
|
+
'--',
|
|
69
|
+
path,
|
|
70
|
+
]);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// For new files or if parent doesn't exist
|
|
74
|
+
fileDiff = await git.diff([targetCommitish, '--', path]);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const lines = fileDiff.split('\n');
|
|
78
|
+
let additions = 0;
|
|
79
|
+
let deletions = 0;
|
|
80
|
+
lines.forEach((line) => {
|
|
81
|
+
if (line.startsWith('+') && !line.startsWith('+++'))
|
|
82
|
+
additions++;
|
|
83
|
+
if (line.startsWith('-') && !line.startsWith('---'))
|
|
84
|
+
deletions++;
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
path,
|
|
88
|
+
status: status,
|
|
89
|
+
diff: fileDiff,
|
|
90
|
+
additions,
|
|
91
|
+
deletions,
|
|
92
|
+
};
|
|
93
|
+
}));
|
|
94
|
+
return fileDiffs;
|
|
95
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { DiffResponse } from '../types/diff.js';
|
|
2
|
+
export declare class GitDiffParser {
|
|
3
|
+
private git;
|
|
4
|
+
constructor(repoPath?: string);
|
|
5
|
+
parseDiff(commitish: string, ignoreWhitespace?: boolean): Promise<DiffResponse>;
|
|
6
|
+
private parseUnifiedDiff;
|
|
7
|
+
private parseFileBlock;
|
|
8
|
+
private parseChunks;
|
|
9
|
+
validateCommit(commitish: string): Promise<boolean>;
|
|
10
|
+
}
|
package/dist/server/git-diff.js
CHANGED
|
@@ -3,12 +3,25 @@ export class GitDiffParser {
|
|
|
3
3
|
constructor(repoPath = process.cwd()) {
|
|
4
4
|
this.git = simpleGit(repoPath);
|
|
5
5
|
}
|
|
6
|
-
async parseDiff(commitish) {
|
|
6
|
+
async parseDiff(commitish, ignoreWhitespace = false) {
|
|
7
7
|
try {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
let resolvedCommit;
|
|
9
|
+
let diffArgs;
|
|
10
|
+
if (commitish === '.') {
|
|
11
|
+
// Show diff between HEAD and working directory (uncommitted changes)
|
|
12
|
+
resolvedCommit = 'Working Directory (uncommitted changes)';
|
|
13
|
+
diffArgs = ['HEAD'];
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
// Resolve commitish to actual commit hash
|
|
17
|
+
resolvedCommit = await this.git.revparse([commitish]);
|
|
18
|
+
diffArgs = [`${commitish}^`, commitish];
|
|
19
|
+
}
|
|
20
|
+
if (ignoreWhitespace) {
|
|
21
|
+
diffArgs.push('-w');
|
|
22
|
+
}
|
|
23
|
+
const diffSummary = await this.git.diffSummary(diffArgs);
|
|
24
|
+
const diffRaw = await this.git.diff(diffArgs);
|
|
12
25
|
const files = await this.parseUnifiedDiff(diffRaw, diffSummary.files);
|
|
13
26
|
return {
|
|
14
27
|
commit: resolvedCommit,
|
|
@@ -116,6 +129,11 @@ export class GitDiffParser {
|
|
|
116
129
|
}
|
|
117
130
|
async validateCommit(commitish) {
|
|
118
131
|
try {
|
|
132
|
+
if (commitish === '.') {
|
|
133
|
+
// For working directory, just check if we're in a git repo
|
|
134
|
+
await this.git.status();
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
119
137
|
await this.git.show([commitish, '--name-only']);
|
|
120
138
|
return true;
|
|
121
139
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { GitDiffParser } from './git-diff';
|
|
3
|
+
// Mock simple-git
|
|
4
|
+
vi.mock('simple-git', () => ({
|
|
5
|
+
simpleGit: vi.fn(() => ({
|
|
6
|
+
revparse: vi.fn(),
|
|
7
|
+
diffSummary: vi.fn(),
|
|
8
|
+
diff: vi.fn(),
|
|
9
|
+
})),
|
|
10
|
+
}));
|
|
11
|
+
// Mock child_process
|
|
12
|
+
vi.mock('child_process', async (importOriginal) => {
|
|
13
|
+
const actual = (await importOriginal());
|
|
14
|
+
return {
|
|
15
|
+
...actual,
|
|
16
|
+
execSync: vi.fn(),
|
|
17
|
+
execFileSync: vi.fn(),
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
// Mock fs
|
|
21
|
+
vi.mock('fs', async (importOriginal) => {
|
|
22
|
+
const actual = (await importOriginal());
|
|
23
|
+
return {
|
|
24
|
+
...actual,
|
|
25
|
+
readFileSync: vi.fn(),
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
describe('GitDiffParser', () => {
|
|
29
|
+
let parser;
|
|
30
|
+
let mockExecFileSync;
|
|
31
|
+
let mockReadFileSync;
|
|
32
|
+
beforeEach(async () => {
|
|
33
|
+
parser = new GitDiffParser('/test/repo');
|
|
34
|
+
vi.clearAllMocks();
|
|
35
|
+
// Get mocked functions
|
|
36
|
+
const childProcess = await import('child_process');
|
|
37
|
+
const fs = await import('fs');
|
|
38
|
+
mockExecFileSync = childProcess.execFileSync;
|
|
39
|
+
mockReadFileSync = fs.readFileSync;
|
|
40
|
+
});
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
vi.restoreAllMocks();
|
|
43
|
+
});
|
|
44
|
+
describe('getBlobContent', () => {
|
|
45
|
+
it('reads from filesystem for working directory', async () => {
|
|
46
|
+
const mockBuffer = Buffer.from('test content');
|
|
47
|
+
mockReadFileSync.mockReturnValue(mockBuffer);
|
|
48
|
+
const result = await parser.getBlobContent('test.txt', 'working');
|
|
49
|
+
expect(mockReadFileSync).toHaveBeenCalledWith('test.txt');
|
|
50
|
+
expect(result).toBe(mockBuffer);
|
|
51
|
+
});
|
|
52
|
+
it('reads from filesystem for "." ref', async () => {
|
|
53
|
+
const mockBuffer = Buffer.from('test content');
|
|
54
|
+
mockReadFileSync.mockReturnValue(mockBuffer);
|
|
55
|
+
const result = await parser.getBlobContent('test.txt', '.');
|
|
56
|
+
expect(mockReadFileSync).toHaveBeenCalledWith('test.txt');
|
|
57
|
+
expect(result).toBe(mockBuffer);
|
|
58
|
+
});
|
|
59
|
+
it('uses git show for staged files', async () => {
|
|
60
|
+
const mockBuffer = Buffer.from('staged content');
|
|
61
|
+
mockExecFileSync.mockReturnValue(mockBuffer);
|
|
62
|
+
const result = await parser.getBlobContent('test.txt', 'staged');
|
|
63
|
+
expect(mockExecFileSync).toHaveBeenCalledWith('git', ['show', ':test.txt'], {
|
|
64
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
65
|
+
});
|
|
66
|
+
expect(result).toBe(mockBuffer);
|
|
67
|
+
});
|
|
68
|
+
it('uses git cat-file for git refs', async () => {
|
|
69
|
+
const blobHash = 'abc123def456';
|
|
70
|
+
const mockBuffer = Buffer.from('git content');
|
|
71
|
+
mockExecFileSync
|
|
72
|
+
.mockReturnValueOnce(blobHash + '\n') // First call for rev-parse
|
|
73
|
+
.mockReturnValueOnce(mockBuffer); // Second call for cat-file
|
|
74
|
+
const result = await parser.getBlobContent('test.txt', 'HEAD');
|
|
75
|
+
expect(mockExecFileSync).toHaveBeenCalledWith('git', ['rev-parse', 'HEAD:test.txt'], {
|
|
76
|
+
encoding: 'utf8',
|
|
77
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
78
|
+
});
|
|
79
|
+
expect(mockExecFileSync).toHaveBeenCalledWith('git', ['cat-file', 'blob', blobHash], {
|
|
80
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
81
|
+
});
|
|
82
|
+
expect(result).toBe(mockBuffer);
|
|
83
|
+
});
|
|
84
|
+
it('handles file size limit errors', async () => {
|
|
85
|
+
const error = new Error('maxBuffer exceeded');
|
|
86
|
+
mockExecFileSync.mockImplementation(() => {
|
|
87
|
+
throw error;
|
|
88
|
+
});
|
|
89
|
+
await expect(parser.getBlobContent('large-file.jpg', 'HEAD')).rejects.toThrow('Image file large-file.jpg is too large to display (over 10MB limit)');
|
|
90
|
+
});
|
|
91
|
+
it('handles ENOBUFS errors', async () => {
|
|
92
|
+
const error = new Error('ENOBUFS: buffer overflow');
|
|
93
|
+
mockExecFileSync.mockImplementation(() => {
|
|
94
|
+
throw error;
|
|
95
|
+
});
|
|
96
|
+
await expect(parser.getBlobContent('large-file.jpg', 'HEAD')).rejects.toThrow('Image file large-file.jpg is too large to display (over 10MB limit)');
|
|
97
|
+
});
|
|
98
|
+
it('handles general git errors', async () => {
|
|
99
|
+
const error = new Error('fatal: Path does not exist');
|
|
100
|
+
mockExecFileSync.mockImplementation(() => {
|
|
101
|
+
throw error;
|
|
102
|
+
});
|
|
103
|
+
await expect(parser.getBlobContent('missing.txt', 'HEAD')).rejects.toThrow('Failed to get blob content for missing.txt at HEAD: fatal: Path does not exist');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe('parseFileBlock with binary files', () => {
|
|
107
|
+
it('parses added binary file correctly', () => {
|
|
108
|
+
const diffLines = [
|
|
109
|
+
'diff --git a/image.jpg b/image.jpg',
|
|
110
|
+
'new file mode 100644',
|
|
111
|
+
'index 0000000..abc123',
|
|
112
|
+
'--- /dev/null',
|
|
113
|
+
'+++ b/image.jpg',
|
|
114
|
+
'Binary files /dev/null and b/image.jpg differ',
|
|
115
|
+
];
|
|
116
|
+
const summary = {
|
|
117
|
+
insertions: 0,
|
|
118
|
+
deletions: 0,
|
|
119
|
+
};
|
|
120
|
+
// Access private method for testing
|
|
121
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
122
|
+
expect(result).toEqual({
|
|
123
|
+
path: 'image.jpg',
|
|
124
|
+
oldPath: undefined,
|
|
125
|
+
status: 'added',
|
|
126
|
+
additions: 0,
|
|
127
|
+
deletions: 0,
|
|
128
|
+
chunks: [], // Binary files should have empty chunks
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
it('parses deleted binary file correctly', () => {
|
|
132
|
+
const diffLines = [
|
|
133
|
+
'diff --git a/old-image.png b/old-image.png',
|
|
134
|
+
'deleted file mode 100644',
|
|
135
|
+
'index abc123..0000000',
|
|
136
|
+
'--- a/old-image.png',
|
|
137
|
+
'+++ /dev/null',
|
|
138
|
+
'Binary files a/old-image.png and /dev/null differ',
|
|
139
|
+
];
|
|
140
|
+
const summary = {
|
|
141
|
+
insertions: 0,
|
|
142
|
+
deletions: 0,
|
|
143
|
+
};
|
|
144
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
145
|
+
expect(result).toEqual({
|
|
146
|
+
path: 'old-image.png',
|
|
147
|
+
oldPath: undefined,
|
|
148
|
+
status: 'deleted',
|
|
149
|
+
additions: 0,
|
|
150
|
+
deletions: 0,
|
|
151
|
+
chunks: [],
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
it('parses modified binary file correctly', () => {
|
|
155
|
+
const diffLines = [
|
|
156
|
+
'diff --git a/photo.jpg b/photo.jpg',
|
|
157
|
+
'index abc123..def456 100644',
|
|
158
|
+
'--- a/photo.jpg',
|
|
159
|
+
'+++ b/photo.jpg',
|
|
160
|
+
'Binary files a/photo.jpg and b/photo.jpg differ',
|
|
161
|
+
];
|
|
162
|
+
const summary = {
|
|
163
|
+
insertions: 0,
|
|
164
|
+
deletions: 0,
|
|
165
|
+
};
|
|
166
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
167
|
+
expect(result).toEqual({
|
|
168
|
+
path: 'photo.jpg',
|
|
169
|
+
oldPath: undefined,
|
|
170
|
+
status: 'modified',
|
|
171
|
+
additions: 0,
|
|
172
|
+
deletions: 0,
|
|
173
|
+
chunks: [],
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
it('parses renamed binary file correctly', () => {
|
|
177
|
+
const diffLines = [
|
|
178
|
+
'diff --git a/old-name.gif b/new-name.gif',
|
|
179
|
+
'similarity index 100%',
|
|
180
|
+
'rename from old-name.gif',
|
|
181
|
+
'rename to new-name.gif',
|
|
182
|
+
];
|
|
183
|
+
const summary = {
|
|
184
|
+
insertions: 0,
|
|
185
|
+
deletions: 0,
|
|
186
|
+
};
|
|
187
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
188
|
+
expect(result).toEqual({
|
|
189
|
+
path: 'new-name.gif',
|
|
190
|
+
oldPath: 'old-name.gif',
|
|
191
|
+
status: 'renamed',
|
|
192
|
+
additions: 0,
|
|
193
|
+
deletions: 0,
|
|
194
|
+
chunks: [],
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
it('handles non-binary files normally', () => {
|
|
198
|
+
const diffLines = [
|
|
199
|
+
'diff --git a/script.js b/script.js',
|
|
200
|
+
'index abc123..def456 100644',
|
|
201
|
+
'--- a/script.js',
|
|
202
|
+
'+++ b/script.js',
|
|
203
|
+
'@@ -1,3 +1,4 @@',
|
|
204
|
+
' console.log("hello");',
|
|
205
|
+
'+console.log("world");',
|
|
206
|
+
' // end',
|
|
207
|
+
];
|
|
208
|
+
const summary = {
|
|
209
|
+
insertions: 1,
|
|
210
|
+
deletions: 0,
|
|
211
|
+
};
|
|
212
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
213
|
+
expect(result).toEqual({
|
|
214
|
+
path: 'script.js',
|
|
215
|
+
oldPath: undefined,
|
|
216
|
+
status: 'added',
|
|
217
|
+
additions: 1,
|
|
218
|
+
deletions: 0,
|
|
219
|
+
chunks: expect.any(Array), // Should have parsed chunks
|
|
220
|
+
});
|
|
221
|
+
// Verify chunks were parsed
|
|
222
|
+
expect(result.chunks).toHaveLength(1);
|
|
223
|
+
expect(result.chunks[0].header).toBe('@@ -1,3 +1,4 @@');
|
|
224
|
+
});
|
|
225
|
+
it('detects added files using /dev/null indicator', () => {
|
|
226
|
+
const diffLines = [
|
|
227
|
+
'diff --git a/new-file.txt b/new-file.txt',
|
|
228
|
+
'index 0000000..abc123 100644',
|
|
229
|
+
'--- /dev/null',
|
|
230
|
+
'+++ b/new-file.txt',
|
|
231
|
+
'@@ -0,0 +1,2 @@',
|
|
232
|
+
'+line 1',
|
|
233
|
+
'+line 2',
|
|
234
|
+
];
|
|
235
|
+
const summary = {
|
|
236
|
+
insertions: 2,
|
|
237
|
+
deletions: 0,
|
|
238
|
+
};
|
|
239
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
240
|
+
expect(result.status).toBe('added');
|
|
241
|
+
});
|
|
242
|
+
it('detects deleted files using /dev/null indicator', () => {
|
|
243
|
+
const diffLines = [
|
|
244
|
+
'diff --git a/deleted-file.txt b/deleted-file.txt',
|
|
245
|
+
'index abc123..0000000 100644',
|
|
246
|
+
'--- a/deleted-file.txt',
|
|
247
|
+
'+++ /dev/null',
|
|
248
|
+
'@@ -1,2 +0,0 @@',
|
|
249
|
+
'-line 1',
|
|
250
|
+
'-line 2',
|
|
251
|
+
];
|
|
252
|
+
const summary = {
|
|
253
|
+
insertions: 0,
|
|
254
|
+
deletions: 2,
|
|
255
|
+
};
|
|
256
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
257
|
+
expect(result.status).toBe('deleted');
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
describe('File status detection improvements', () => {
|
|
261
|
+
it('prioritizes new file mode over other indicators', () => {
|
|
262
|
+
const diffLines = [
|
|
263
|
+
'diff --git a/test.txt b/test.txt',
|
|
264
|
+
'new file mode 100644',
|
|
265
|
+
'index 0000000..abc123',
|
|
266
|
+
'--- a/test.txt', // This might confuse simple parsers
|
|
267
|
+
'+++ b/test.txt',
|
|
268
|
+
];
|
|
269
|
+
const summary = {
|
|
270
|
+
insertions: 5,
|
|
271
|
+
deletions: 0,
|
|
272
|
+
};
|
|
273
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
274
|
+
expect(result.status).toBe('added');
|
|
275
|
+
});
|
|
276
|
+
it('prioritizes deleted file mode over other indicators', () => {
|
|
277
|
+
const diffLines = [
|
|
278
|
+
'diff --git a/test.txt b/test.txt',
|
|
279
|
+
'deleted file mode 100644',
|
|
280
|
+
'index abc123..0000000',
|
|
281
|
+
'--- a/test.txt',
|
|
282
|
+
'+++ b/test.txt', // This might confuse simple parsers
|
|
283
|
+
];
|
|
284
|
+
const summary = {
|
|
285
|
+
insertions: 0,
|
|
286
|
+
deletions: 5,
|
|
287
|
+
};
|
|
288
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
289
|
+
expect(result.status).toBe('deleted');
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface ServerOptions {
|
|
2
|
+
commitish: string;
|
|
3
|
+
preferredPort?: number;
|
|
4
|
+
openBrowser?: boolean;
|
|
5
|
+
mode?: string;
|
|
6
|
+
ignoreWhitespace?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function startServer(options: ServerOptions): Promise<{
|
|
9
|
+
port: number;
|
|
10
|
+
url: string;
|
|
11
|
+
}>;
|
|
12
|
+
export {};
|
package/dist/server/server.js
CHANGED
|
@@ -5,12 +5,11 @@ import open from 'open';
|
|
|
5
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
6
|
const __dirname = dirname(__filename);
|
|
7
7
|
import { GitDiffParser } from './git-diff.js';
|
|
8
|
-
import { CommentStore } from './comment-store.js';
|
|
9
8
|
export async function startServer(options) {
|
|
10
9
|
const app = express();
|
|
11
10
|
const parser = new GitDiffParser();
|
|
12
|
-
const commentStore = new CommentStore();
|
|
13
11
|
let diffData = null;
|
|
12
|
+
let currentIgnoreWhitespace = options.ignoreWhitespace || false;
|
|
14
13
|
app.use(express.json());
|
|
15
14
|
app.use((_req, res, next) => {
|
|
16
15
|
res.header('Access-Control-Allow-Origin', 'http://localhost:*');
|
|
@@ -22,63 +21,14 @@ export async function startServer(options) {
|
|
|
22
21
|
if (!isValidCommit) {
|
|
23
22
|
throw new Error(`Invalid or non-existent commit: ${options.commitish}`);
|
|
24
23
|
}
|
|
25
|
-
diffData = await parser.parseDiff(options.commitish);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const comments = await commentStore.getComments();
|
|
32
|
-
res.json(comments);
|
|
33
|
-
});
|
|
34
|
-
app.post('/api/comments', async (req, res) => {
|
|
35
|
-
try {
|
|
36
|
-
const { file, line, body } = req.body;
|
|
37
|
-
if (!file || typeof line !== 'number' || !body) {
|
|
38
|
-
return res.status(400).json({ error: 'Missing required fields: file, line, body' });
|
|
39
|
-
}
|
|
40
|
-
const comment = await commentStore.addComment(file, line, body);
|
|
41
|
-
res.json(comment);
|
|
42
|
-
}
|
|
43
|
-
catch (error) {
|
|
44
|
-
res.status(500).json({ error: 'Failed to save comment' });
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
app.post('/api/comments/:id/prompt', async (req, res) => {
|
|
48
|
-
try {
|
|
49
|
-
const comments = await commentStore.getComments();
|
|
50
|
-
const comment = comments.find((c) => c.id === req.params.id);
|
|
51
|
-
if (!comment) {
|
|
52
|
-
return res.status(404).json({ error: 'Comment not found' });
|
|
53
|
-
}
|
|
54
|
-
const targetFile = diffData.files.find((f) => f.path === comment.file);
|
|
55
|
-
if (!targetFile) {
|
|
56
|
-
return res.status(404).json({ error: 'File not found in diff' });
|
|
57
|
-
}
|
|
58
|
-
let diffContent = '';
|
|
59
|
-
for (const chunk of targetFile.chunks) {
|
|
60
|
-
diffContent += chunk.header + '\n';
|
|
61
|
-
for (const line of chunk.lines) {
|
|
62
|
-
const prefix = line.type === 'add' ? '+' : line.type === 'delete' ? '-' : ' ';
|
|
63
|
-
diffContent += prefix + line.content + '\n';
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
const prompt = commentStore.generatePrompt(comment, diffContent);
|
|
67
|
-
res.json({ prompt });
|
|
68
|
-
}
|
|
69
|
-
catch (error) {
|
|
70
|
-
res.status(500).json({ error: 'Failed to generate prompt' });
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
app.post('/api/comments/all/prompt', async (_, res) => {
|
|
74
|
-
try {
|
|
75
|
-
const comments = await commentStore.getComments();
|
|
76
|
-
const prompt = commentStore.generateAllCommentsPrompt(comments, diffData.files);
|
|
77
|
-
res.json({ prompt });
|
|
78
|
-
}
|
|
79
|
-
catch (error) {
|
|
80
|
-
res.status(500).json({ error: 'Failed to generate all comments prompt' });
|
|
24
|
+
diffData = await parser.parseDiff(options.commitish, currentIgnoreWhitespace);
|
|
25
|
+
app.get('/api/diff', async (req, res) => {
|
|
26
|
+
const ignoreWhitespace = req.query.ignoreWhitespace === 'true';
|
|
27
|
+
if (ignoreWhitespace !== currentIgnoreWhitespace) {
|
|
28
|
+
currentIgnoreWhitespace = ignoreWhitespace;
|
|
29
|
+
diffData = await parser.parseDiff(options.commitish, ignoreWhitespace);
|
|
81
30
|
}
|
|
31
|
+
res.json({ ...diffData, ignoreWhitespace });
|
|
82
32
|
});
|
|
83
33
|
// Always runs in production mode when distributed as a CLI tool
|
|
84
34
|
const isProduction = process.env.NODE_ENV === 'production' || process.env.NODE_ENV !== 'development';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|