@dansyuqri/spark-joy 0.2.1 → 0.2.2
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 +2 -4
- package/dist/app/assets/index-9PHU75Am.js +8 -0
- package/dist/app/assets/{index-ChCdW9F8.css → index-D3yLyUyh.css} +1 -1
- package/dist/app/index.html +2 -2
- package/dist/cli.js +61 -37
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/dist/app/assets/index-_XxERk0m.js +0 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
:root{--bg:#fbfaf8;--panel:#fff;--ink:#1d1d1f;--muted:#6b6b72;--line:#e4e2dd;--accent:#c2410c;--accent-ink:#fff;--add-bg:#e9f8ec;--add-ink:#116329;--del-bg:#fdeceb;--del-ink:#a40e26;--hunk-bg:#f1f0fb;--note-bg:#fff8e5;--done:#15803d;--partial:#b45309;--todo:#9ca3af;--mono:ui-monospace, "SF Mono", Menlo, Consolas, monospace;--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial;--bg:#151518;--panel:#1c1c20;--ink:#ececef;--muted:#9a9aa3;--line:#2e2e34;--accent:#fb923c;--accent-ink:#1a1a1a;--add-bg:#12301c;--add-ink:#7ee2a0;--del-bg:#3a1619;--del-ink:#ff9aa5;--hunk-bg:#23233a;--note-bg:#2f2a18;--done:#4ade80;--partial:#fbbf24;--todo:#6b7280}}*{box-sizing:border-box}body{background:var(--bg);color:var(--ink);margin:0;font:14px/1.5 system-ui,-apple-system,Segoe UI,sans-serif}a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}code,kbd{font-family:var(--mono);font-size:.92em}kbd{border:1px solid var(--line);border-radius:4px;padding:0 4px}button{font:inherit;border:1px solid var(--line);background:var(--panel);color:var(--ink);cursor:pointer;border-radius:6px;padding:6px 12px}button:hover:not(:disabled){border-color:var(--muted)}button:disabled{opacity:.4;cursor:default}button.primary{background:var(--accent);border-color:var(--accent);color:var(--accent-ink);font-weight:600}textarea{font:inherit;border:1px solid var(--line);background:var(--panel);width:100%;min-height:70px;color:var(--ink);resize:vertical;border-radius:6px;padding:8px}.muted{color:var(--muted)}.small{font-size:12px}.warn{color:var(--partial)}.pre{white-space:pre-wrap}.empty{max-width:640px;margin:80px auto;padding:0 16px}.topbar{z-index:10;background:var(--panel);border-bottom:1px solid var(--line);align-items:center;gap:20px;padding:10px 20px;display:flex;position:sticky;top:0}.brand{white-space:nowrap;font-weight:700}.spark{color:var(--accent)}.run{white-space:nowrap;text-overflow:ellipsis;flex:1;min-width:0;overflow:hidden}.tabs{gap:4px;display:flex}.tabs a{color:var(--muted);border-radius:6px;padding:4px 10px}.tabs a.on{background:var(--hunk-bg);color:var(--ink);font-weight:600}.progress{font-variant-numeric:tabular-nums;align-items:center;gap:8px;display:flex}.progress .bar{background:var(--line);border-radius:3px;width:120px;height:6px;overflow:hidden}.progress .bar div{background:var(--done);height:100%;transition:width .2s}.saving{width:70px;color:var(--muted);font-size:12px}.saving.error{color:var(--del-ink)}.status{vertical-align:-2px;justify-content:center;align-self:center;width:1.2em;display:inline-flex}.status.done{color:var(--done)}.status.partial{color:var(--partial)}.status.todo{color:var(--todo)}.badge{border:1px solid var(--line);color:var(--muted);white-space:nowrap;border-radius:10px;margin-left:6px;padding:0 6px;font-size:11px;display:inline-block}.risk{vertical-align:middle;border-radius:50%;width:8px;height:8px;margin-left:8px;display:inline-block}.risk-high{background:var(--del-ink)}.risk-med{background:var(--partial)}.issues{border:1px solid var(--line);background:var(--panel);border-radius:8px;margin:12px 0;padding:8px 12px}.issues.has-errors{border-color:var(--del-ink)}.issues li.error{color:var(--del-ink)}.issues li.warning{color:var(--partial)}.page{max-width:1200px;padding:24px 32px 80px}.overview{max-width:800px;font-size:15px}.strategies{grid-template-columns:repeat(auto-fit,minmax(340px,1fr));gap:16px;display:grid}.strategy{background:var(--panel);border:1px solid var(--line);border-radius:10px;flex-direction:column;padding:16px;display:flex}.strategy.on{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent)}.strategy h3{margin:0 0 4px}.strategy .stats{color:var(--muted);font-size:12px}.chunk-list{flex:1;padding-left:0;list-style:none}.chunk-list .n{min-width:1.6em;color:var(--muted);font-variant-numeric:tabular-nums}.chunk-list li{align-items:center;gap:4px;padding:3px 0;display:flex}.chunk-list li .title{flex:1}.chunk-list .branch{color:var(--muted);font-size:11px;display:block}.strategy .hint{color:var(--muted);margin:10px 0 0;font-size:12px}.size{font-variant-numeric:tabular-nums;color:var(--muted);margin-left:6px;font-size:12px}.features li{margin-bottom:6px}.review{grid-template-columns:300px 1fr;align-items:start;display:grid}.sidebar{border-right:1px solid var(--line);max-height:calc(100vh - 53px);padding:16px 8px 16px 16px;position:sticky;top:53px;overflow:auto}.sidebar ol{margin:8px 0;padding:0;list-style:none}.sidebar li a{color:var(--ink);border-radius:6px;align-items:baseline;gap:6px;padding:5px 8px;display:flex}.sidebar li a:hover{background:var(--hunk-bg);text-decoration:none}.sidebar li.on a{background:var(--hunk-bg);font-weight:600}.sidebar li.skim{border-top:1px dashed var(--line);margin-top:8px;padding-top:8px}.sidebar .n{color:var(--muted);font-variant-numeric:tabular-nums;min-width:1.4em}.sidebar .title{flex:1}.chunk-head h1{margin:0 0 2px;font-size:20px}.chunk-head .intent{max-width:820px;margin:6px 0 0;font-size:15px}.focus{max-width:820px;color:var(--muted);margin:6px 0 0;padding-left:18px}.diff{margin-top:16px}.file{background:var(--panel);border:1px solid var(--line);border-radius:8px;margin-bottom:14px;overflow:hidden}.file>summary{cursor:pointer;border-bottom:1px solid var(--line);align-items:center;gap:4px;padding:8px 12px;display:flex}.file:not([open])>summary{border-bottom:none}.file .path{font-family:var(--mono);font-weight:600}.file .counts{font-family:var(--mono);margin-left:auto;font-size:12px}.add{color:var(--add-ink)}.del{color:var(--del-ink)}.file table{border-collapse:collapse;width:100%;font-family:var(--mono);table-layout:fixed;font-size:12.5px}.file col.no-col{width:52px}.file td.no{text-align:right;color:var(--muted);-webkit-user-select:none;user-select:none;cursor:pointer;vertical-align:top;padding:0 8px}.file td.no:hover{color:var(--accent)}.file td.code{white-space:pre-wrap;word-break:break-all;padding:0 8px}.sign{width:1.2em;color:var(--muted);-webkit-user-select:none;user-select:none;display:inline-block}tr.add td{background:var(--add-bg)}tr.del td{background:var(--del-bg)}tr.meta td{color:var(--muted);font-style:italic}tr.hunk-header td{background:var(--hunk-bg);color:var(--muted);padding:3px 8px}.hid{color:var(--ink);font-weight:700}tr.placeholder td{color:var(--muted);padding:8px;font-family:system-ui,sans-serif}tr.note-row td{background:var(--bg);padding:6px 12px 6px 112px}.line-note{background:var(--note-bg);border:1px solid var(--line);border-radius:6px;padding:8px 10px;font-family:system-ui,sans-serif;font-size:13px}.line-note .text{white-space:pre-wrap;cursor:text}.line-note .actions{gap:6px;margin-top:6px;display:flex}.line-note .actions button{padding:2px 8px;font-size:12px}.chunk-foot{background:var(--bg);border-top:1px solid var(--line);align-items:flex-end;gap:8px;padding:10px 0;display:flex;position:sticky;bottom:0}.chunk-foot textarea{flex:1;height:34px;min-height:34px;padding:6px 8px;transition:height .1s}.chunk-foot textarea:focus,.chunk-foot textarea:not(:placeholder-shown){height:80px}.totals{margin:0 0 8px;font-size:18px}.chunk-refs a{font-variant-numeric:tabular-nums;margin-left:8px;font-size:12px}.features{padding:0;list-style:none}.note-group{background:var(--panel);border:1px solid var(--line);border-radius:8px;margin-bottom:10px;padding:10px 16px}.note-group p{margin:6px 0}.export textarea{min-height:300px;font-family:var(--mono);font-size:12px}@media (width<=900px){.review{grid-template-columns:1fr}.sidebar{border-right:none;border-bottom:1px solid var(--line);max-height:none;position:static}.page{padding:16px}.run{display:none}}.file{scroll-margin-top:64px}.map-page{flex-direction:column;height:calc(100vh - 53px);display:flex}.crumbs{color:var(--muted);padding:10px 20px;font-size:13px}.map-canvas{cursor:grab;touch-action:none;-webkit-user-select:none;user-select:none;flex:1;position:relative;overflow:hidden}.map-canvas:active{cursor:grabbing}.empty-map{padding:20px}.zoom-in{transform-box:fill-box;animation:.22s ease-out zoom-in}@keyframes zoom-in{0%{opacity:0}}.node{cursor:pointer}.node rect{fill:var(--panel);stroke:var(--line);stroke-width:1.2px}.node:hover rect{stroke:var(--accent)}.node.neighbour rect{fill:var(--bg);stroke-dasharray:4 3}.node.neighbour text{opacity:.7}.node text{fill:var(--ink);font-size:13px}.node .title{font-weight:600}.node.file .title{font-family:var(--mono);font-size:12.5px}.node .n{fill:var(--muted);font-weight:400}.node .meta{fill:var(--muted);font-size:11px}.node .status.done{color:var(--done)}.node .status.partial{color:var(--partial)}.node .status.todo{color:var(--todo)}.node .risk-high{fill:var(--del-ink)}.node .risk-med{fill:var(--partial)}.edge path{fill:none;stroke:var(--muted);opacity:.7;transition:opacity .15s}.edge.link path{stroke-dasharray:5 4}.edge.faded{opacity:.15}.arrowhead{fill:var(--muted)}.edge-label rect{fill:var(--bg);stroke:var(--line)}.edge-label text{fill:var(--ink);font-size:11px}
|
|
1
|
+
:root{--bg:#fbfaf8;--panel:#fff;--ink:#1d1d1f;--muted:#6b6b72;--line:#e4e2dd;--accent:#c2410c;--accent-ink:#fff;--add-bg:#e9f8ec;--add-ink:#116329;--del-bg:#fdeceb;--del-ink:#a40e26;--hunk-bg:#f1f0fb;--note-bg:#fff8e5;--done:#15803d;--partial:#b45309;--todo:#9ca3af;--mono:ui-monospace, "SF Mono", Menlo, Consolas, monospace;--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial;--bg:#151518;--panel:#1c1c20;--ink:#ececef;--muted:#9a9aa3;--line:#2e2e34;--accent:#fb923c;--accent-ink:#1a1a1a;--add-bg:#12301c;--add-ink:#7ee2a0;--del-bg:#3a1619;--del-ink:#ff9aa5;--hunk-bg:#23233a;--note-bg:#2f2a18;--done:#4ade80;--partial:#fbbf24;--todo:#6b7280}}*{box-sizing:border-box}body{background:var(--bg);color:var(--ink);margin:0;font:14px/1.5 system-ui,-apple-system,Segoe UI,sans-serif}a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}code,kbd{font-family:var(--mono);font-size:.92em}kbd{border:1px solid var(--line);border-radius:4px;padding:0 4px}button{font:inherit;border:1px solid var(--line);background:var(--panel);color:var(--ink);cursor:pointer;border-radius:6px;padding:6px 12px}button:hover:not(:disabled){border-color:var(--muted)}button:disabled{opacity:.4;cursor:default}button.primary{background:var(--accent);border-color:var(--accent);color:var(--accent-ink);font-weight:600}textarea{font:inherit;border:1px solid var(--line);background:var(--panel);width:100%;min-height:70px;color:var(--ink);resize:vertical;border-radius:6px;padding:8px}.muted{color:var(--muted)}.small{font-size:12px}.warn{color:var(--partial)}.pre{white-space:pre-wrap}.empty{max-width:640px;margin:80px auto;padding:0 16px}.topbar{z-index:10;background:var(--panel);border-bottom:1px solid var(--line);align-items:center;gap:20px;padding:10px 20px;display:flex;position:sticky;top:0}.brand{white-space:nowrap;font-weight:700}.spark{color:var(--accent)}.run{white-space:nowrap;text-overflow:ellipsis;flex:1;min-width:0;overflow:hidden}.tabs{gap:4px;display:flex}.tabs a{color:var(--muted);border-radius:6px;padding:4px 10px}.tabs a.on{background:var(--hunk-bg);color:var(--ink);font-weight:600}.progress{font-variant-numeric:tabular-nums;align-items:center;gap:8px;display:flex}.progress .bar{background:var(--line);border-radius:3px;width:120px;height:6px;overflow:hidden}.progress .bar div{background:var(--done);height:100%;transition:width .2s}.saving{width:70px;color:var(--muted);font-size:12px}.saving.error{color:var(--del-ink)}.status{vertical-align:-2px;justify-content:center;align-self:center;width:1.2em;display:inline-flex}.status.done{color:var(--done)}.status.partial{color:var(--partial)}.status.todo{color:var(--todo)}.badge{border:1px solid var(--line);color:var(--muted);white-space:nowrap;border-radius:10px;margin-left:6px;padding:0 6px;font-size:11px;display:inline-block}.risk{vertical-align:middle;border-radius:50%;width:8px;height:8px;margin-left:8px;display:inline-block}.risk-high{background:var(--del-ink)}.risk-med{background:var(--partial)}.issues{border:1px solid var(--line);background:var(--panel);border-radius:8px;margin:12px 0;padding:8px 12px}.issues.has-errors{border-color:var(--del-ink)}.issues li.error{color:var(--del-ink)}.issues li.warning{color:var(--partial)}.page{max-width:1200px;padding:24px 32px 80px}.overview{max-width:800px;font-size:15px}.strategies{grid-template-columns:repeat(auto-fit,minmax(340px,1fr));gap:16px;display:grid}.strategy{background:var(--panel);border:1px solid var(--line);border-radius:10px;flex-direction:column;padding:16px;display:flex}.strategy.on{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent)}.strategy h3{margin:0 0 4px}.strategy .stats{color:var(--muted);font-size:12px}.chunk-list{flex:1;padding-left:0;list-style:none}.chunk-list .n{min-width:1.6em;color:var(--muted);font-variant-numeric:tabular-nums}.chunk-list li{align-items:center;gap:4px;padding:3px 0;display:flex}.chunk-list li .title{flex:1}.chunk-list .branch{color:var(--muted);font-size:11px;display:block}.strategy .hint{color:var(--muted);margin:10px 0 0;font-size:12px}.size{font-variant-numeric:tabular-nums;color:var(--muted);margin-left:6px;font-size:12px}.features li{margin-bottom:6px}.review{grid-template-columns:300px 1fr;align-items:start;display:grid}.sidebar{border-right:1px solid var(--line);max-height:calc(100vh - 53px);padding:16px 8px 16px 16px;position:sticky;top:53px;overflow:auto}.sidebar ol{margin:8px 0;padding:0;list-style:none}.sidebar li a{color:var(--ink);border-radius:6px;align-items:baseline;gap:6px;padding:5px 8px;display:flex}.sidebar li a:hover{background:var(--hunk-bg);text-decoration:none}.sidebar li.on a{background:var(--hunk-bg);font-weight:600}.sidebar li.skim{border-top:1px dashed var(--line);margin-top:8px;padding-top:8px}.sidebar .n{color:var(--muted);font-variant-numeric:tabular-nums;min-width:1.4em}.sidebar .title{flex:1}.chunk-head h1{margin:0 0 2px;font-size:20px}.chunk-head .intent{max-width:820px;margin:6px 0 0;font-size:15px}.focus{max-width:820px;color:var(--muted);margin:6px 0 0;padding-left:18px}.diff{margin-top:16px}.file{background:var(--panel);border:1px solid var(--line);border-radius:8px;margin-bottom:14px;overflow:hidden}.file>summary{cursor:pointer;border-bottom:1px solid var(--line);align-items:center;gap:4px;padding:8px 12px;display:flex}.file:not([open])>summary{border-bottom:none}.file .path{font-family:var(--mono);font-weight:600}.file .counts{font-family:var(--mono);margin-left:auto;font-size:12px}.add{color:var(--add-ink)}.del{color:var(--del-ink)}.file table{border-collapse:collapse;width:100%;font-family:var(--mono);table-layout:fixed;font-size:12.5px}.file col.no-col{width:52px}.file td.no{text-align:right;color:var(--muted);-webkit-user-select:none;user-select:none;cursor:pointer;vertical-align:top;padding:0 8px}.file td.no:hover{color:var(--accent)}.file td.code{white-space:pre-wrap;word-break:break-all;padding:0 8px}.sign{width:1.2em;color:var(--muted);-webkit-user-select:none;user-select:none;display:inline-block}tr.add td{background:var(--add-bg)}tr.del td{background:var(--del-bg)}tr.meta td{color:var(--muted);font-style:italic}tr.hunk-header td{background:var(--hunk-bg);color:var(--muted);padding:3px 8px}.hid{color:var(--ink);font-weight:700}tr.placeholder td{color:var(--muted);padding:8px;font-family:system-ui,sans-serif}tr.note-row td{background:var(--bg);padding:6px 12px 6px 112px}.line-note{background:var(--note-bg);border:1px solid var(--line);border-radius:6px;padding:8px 10px;font-family:system-ui,sans-serif;font-size:13px}.line-note .text{white-space:pre-wrap;cursor:text}.line-note .actions{gap:6px;margin-top:6px;display:flex}.line-note .actions button{padding:2px 8px;font-size:12px}.chunk-foot{background:var(--bg);border-top:1px solid var(--line);align-items:flex-end;gap:8px;padding:10px 0;display:flex;position:sticky;bottom:0}.chunk-foot textarea{flex:1;height:34px;min-height:34px;padding:6px 8px;transition:height .1s}.chunk-foot textarea:focus,.chunk-foot textarea:not(:placeholder-shown){height:80px}.totals{margin:0 0 8px;font-size:18px}.chunk-refs a{font-variant-numeric:tabular-nums;margin-left:8px;font-size:12px}.features{padding:0;list-style:none}.note-group{background:var(--panel);border:1px solid var(--line);border-radius:8px;margin-bottom:10px;padding:10px 16px}.note-group p{margin:6px 0}.export textarea{min-height:300px;font-family:var(--mono);font-size:12px}@media (width<=900px){.review{grid-template-columns:1fr}.sidebar{border-right:none;border-bottom:1px solid var(--line);max-height:none;position:static}.page{padding:16px}.run{display:none}}.file{scroll-margin-top:64px}.map-page{flex-direction:column;height:calc(100vh - 53px);display:flex}.crumbs{color:var(--muted);padding:10px 20px;font-size:13px}.map-canvas{cursor:grab;touch-action:none;-webkit-user-select:none;user-select:none;flex:1;position:relative;overflow:hidden}.map-canvas:active{cursor:grabbing}.empty-map{padding:20px}.zoom-in{transform-box:fill-box;animation:.22s ease-out zoom-in}@keyframes zoom-in{0%{opacity:0}}.node{cursor:pointer}.node rect{fill:var(--panel);stroke:var(--line);stroke-width:1.2px}.node:hover rect{stroke:var(--accent)}.node.neighbour rect{fill:var(--bg);stroke-dasharray:4 3}.node.neighbour text{opacity:.7}.node text{fill:var(--ink);font-size:13px}.node .title{font-weight:600}.node.file .title{font-family:var(--mono);font-size:12.5px}.node .n{fill:var(--muted);font-weight:400}.node .meta{fill:var(--muted);font-size:11px}.node .status.done{color:var(--done)}.node .status.partial{color:var(--partial)}.node .status.todo{color:var(--todo)}.node .risk-high{fill:var(--del-ink)}.node .risk-med{fill:var(--partial)}.edge path{fill:none;stroke:var(--muted);opacity:.7;transition:opacity .15s}.edge.link path{stroke-dasharray:5 4}.edge.faded{opacity:.15}.arrowhead{fill:var(--muted)}.edge-label rect{fill:var(--bg);stroke:var(--line)}.edge-label text{fill:var(--ink);font-size:11px}tr.gap-row td,tr.foreign-row td{background:var(--hunk-bg);color:var(--muted);padding:2px 8px;font-size:12px}tr.gap-row button{margin-right:6px;padding:1px 8px;font-size:12px}tr.line.expanded td.code,tr.line.expanded td.no{opacity:.75}tr.line.expanded td.no{cursor:default}.file summary .view-file{margin-left:8px;padding:1px 8px;font-size:12px}
|
package/dist/app/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>spark-joy review</title>
|
|
7
7
|
<link rel="icon" href="./favicon.svg" type="image/svg+xml" />
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-9PHU75Am.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-D3yLyUyh.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// cli/index.ts
|
|
2
|
-
import { copyFile, mkdir as mkdir3, mkdtemp, readFile as
|
|
2
|
+
import { copyFile, mkdir as mkdir3, mkdtemp, readFile as readFile5, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
3
3
|
import { homedir, tmpdir } from "os";
|
|
4
|
-
import { join as
|
|
4
|
+
import { join as join5, relative } from "path";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
import { parseArgs } from "util";
|
|
7
7
|
|
|
@@ -667,6 +667,8 @@ function computeLinks(hunks) {
|
|
|
667
667
|
|
|
668
668
|
// cli/lib/git.ts
|
|
669
669
|
import { execFile } from "child_process";
|
|
670
|
+
import { lstat, readFile } from "fs/promises";
|
|
671
|
+
import { join } from "path";
|
|
670
672
|
import { promisify } from "util";
|
|
671
673
|
var exec = promisify(execFile);
|
|
672
674
|
async function git(cwd, args, input, env) {
|
|
@@ -734,31 +736,39 @@ async function generatedByAttr(cwd, files) {
|
|
|
734
736
|
}
|
|
735
737
|
return result;
|
|
736
738
|
}
|
|
739
|
+
async function fileText(root, meta, path) {
|
|
740
|
+
const source = meta.source ?? (meta.working ? "working" : "pr");
|
|
741
|
+
if (source === "working" || source === "uncommitted") {
|
|
742
|
+
if ((await lstat(join(root, path))).isSymbolicLink()) throw new Error(`${path} is a symlink`);
|
|
743
|
+
return readFile(join(root, path), "utf8");
|
|
744
|
+
}
|
|
745
|
+
return git(root, ["show", `${source === "staged" ? "" : meta.headSha}:${path}`]);
|
|
746
|
+
}
|
|
737
747
|
|
|
738
748
|
// cli/lib/runs.ts
|
|
739
|
-
import { mkdir, readFile, readdir, rename, stat, writeFile } from "fs/promises";
|
|
740
|
-
import { join } from "path";
|
|
749
|
+
import { mkdir, readFile as readFile2, readdir, rename, stat, writeFile } from "fs/promises";
|
|
750
|
+
import { join as join2 } from "path";
|
|
741
751
|
import { z as z2 } from "zod";
|
|
742
752
|
var RUNS_DIR = ".spark-joy";
|
|
743
753
|
var slug = (s) => s.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "run";
|
|
744
754
|
function runPaths(root, name) {
|
|
745
|
-
const dir =
|
|
755
|
+
const dir = join2(root, RUNS_DIR, name);
|
|
746
756
|
return {
|
|
747
757
|
name,
|
|
748
758
|
dir,
|
|
749
|
-
hunks:
|
|
750
|
-
meta:
|
|
751
|
-
plan:
|
|
752
|
-
state:
|
|
753
|
-
review:
|
|
754
|
-
links:
|
|
755
|
-
prs:
|
|
759
|
+
hunks: join2(dir, "hunks.json"),
|
|
760
|
+
meta: join2(dir, "meta.json"),
|
|
761
|
+
plan: join2(dir, "plan.json"),
|
|
762
|
+
state: join2(dir, "review-state.json"),
|
|
763
|
+
review: join2(dir, "review.md"),
|
|
764
|
+
links: join2(dir, "links.json"),
|
|
765
|
+
prs: join2(dir, "prs.md")
|
|
756
766
|
};
|
|
757
767
|
}
|
|
758
768
|
async function ensureRunsDir(root) {
|
|
759
|
-
const dir =
|
|
769
|
+
const dir = join2(root, RUNS_DIR);
|
|
760
770
|
await mkdir(dir, { recursive: true });
|
|
761
|
-
await writeFile(
|
|
771
|
+
await writeFile(join2(dir, ".gitignore"), "*\n");
|
|
762
772
|
}
|
|
763
773
|
var exists = (p) => stat(p).then(() => true, () => false);
|
|
764
774
|
async function resolveRun(root, name, branch) {
|
|
@@ -774,11 +784,11 @@ async function resolveRun(root, name, branch) {
|
|
|
774
784
|
return runPaths(root, runs[0].name);
|
|
775
785
|
}
|
|
776
786
|
async function listRuns(root) {
|
|
777
|
-
const dir =
|
|
787
|
+
const dir = join2(root, RUNS_DIR);
|
|
778
788
|
const names = await readdir(dir).catch(() => []);
|
|
779
789
|
const runs = [];
|
|
780
790
|
for (const name of names) {
|
|
781
|
-
const s = await stat(
|
|
791
|
+
const s = await stat(join2(dir, name, "hunks.json")).catch(() => void 0);
|
|
782
792
|
if (s) runs.push({ name, mtime: s.mtimeMs });
|
|
783
793
|
}
|
|
784
794
|
return runs.sort((a, b) => b.mtime - a.mtime);
|
|
@@ -789,7 +799,7 @@ async function writeJson(path, data) {
|
|
|
789
799
|
await rename(tmp, path);
|
|
790
800
|
}
|
|
791
801
|
async function readJson(path) {
|
|
792
|
-
return JSON.parse(await
|
|
802
|
+
return JSON.parse(await readFile2(path, "utf8"));
|
|
793
803
|
}
|
|
794
804
|
var readHunks = async (p) => z2.array(Hunk).parse(await readJson(p.hunks));
|
|
795
805
|
var readMeta = async (p) => Meta.parse(await readJson(p.meta));
|
|
@@ -1040,8 +1050,8 @@ function formatLinks(hunks, links) {
|
|
|
1040
1050
|
|
|
1041
1051
|
// cli/lib/server.ts
|
|
1042
1052
|
import { createServer } from "http";
|
|
1043
|
-
import { readFile as
|
|
1044
|
-
import { extname as extname2, join as
|
|
1053
|
+
import { readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
1054
|
+
import { extname as extname2, join as join3, normalize, sep } from "path";
|
|
1045
1055
|
var TYPES = {
|
|
1046
1056
|
".html": "text/html; charset=utf-8",
|
|
1047
1057
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -1051,6 +1061,7 @@ var TYPES = {
|
|
|
1051
1061
|
".map": "application/json"
|
|
1052
1062
|
};
|
|
1053
1063
|
var MAX_BODY = 20 * 1024 * 1024;
|
|
1064
|
+
var MAX_FILE = 2 * 1024 * 1024;
|
|
1054
1065
|
async function body(req) {
|
|
1055
1066
|
const chunks = [];
|
|
1056
1067
|
let size = 0;
|
|
@@ -1097,6 +1108,19 @@ async function serve(opts) {
|
|
|
1097
1108
|
return send(res, 403, { error: "cross-origin request refused" });
|
|
1098
1109
|
}
|
|
1099
1110
|
if (url.pathname === "/api/run" && req.method === "GET") return send(res, 200, await loadRun(paths));
|
|
1111
|
+
if (url.pathname === "/api/file" && req.method === "GET") {
|
|
1112
|
+
const path = url.searchParams.get("path") ?? "";
|
|
1113
|
+
const [meta, hunks] = await Promise.all([readMeta(paths), readHunks(paths)]);
|
|
1114
|
+
const hs = hunks.filter((h) => h.file === path);
|
|
1115
|
+
if (!hs.length || hs[0].status === "deleted" || hs.some((h) => h.flags.includes("binary")))
|
|
1116
|
+
return send(res, 404, { error: "no text for that file" });
|
|
1117
|
+
const text = await fileText(meta.repoRoot, meta, path).catch(() => void 0);
|
|
1118
|
+
if (text === void 0) return send(res, 404, { error: "no text for that file" });
|
|
1119
|
+
if (text.length > MAX_FILE || text.includes("\0")) return send(res, 404, { error: "file too large or binary" });
|
|
1120
|
+
const lines = text.split("\n");
|
|
1121
|
+
if (lines.at(-1) === "") lines.pop();
|
|
1122
|
+
return send(res, 200, { lines: lines.map((l) => l.replace(/\r$/, "")) });
|
|
1123
|
+
}
|
|
1100
1124
|
if (url.pathname === "/api/state" && req.method === "PUT") {
|
|
1101
1125
|
const state = ReviewState.parse(await body(req));
|
|
1102
1126
|
state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -1113,14 +1137,14 @@ async function serve(opts) {
|
|
|
1113
1137
|
if (url.pathname.startsWith("/api/")) return send(res, 404, { error: "not found" });
|
|
1114
1138
|
if (req.method !== "GET") return send(res, 405, { error: "method not allowed" });
|
|
1115
1139
|
const rel = normalize(decodeURIComponent(url.pathname)).replace(/^([/\\])+/, "");
|
|
1116
|
-
let file =
|
|
1140
|
+
let file = join3(appDir, rel);
|
|
1117
1141
|
if (!file.startsWith(appDir + sep) && file !== appDir) return send(res, 403, { error: "forbidden" });
|
|
1118
1142
|
let data;
|
|
1119
1143
|
try {
|
|
1120
|
-
data = await
|
|
1144
|
+
data = await readFile3(file);
|
|
1121
1145
|
} catch {
|
|
1122
|
-
file =
|
|
1123
|
-
data = await
|
|
1146
|
+
file = join3(appDir, "index.html");
|
|
1147
|
+
data = await readFile3(file);
|
|
1124
1148
|
}
|
|
1125
1149
|
res.writeHead(200, { "content-type": TYPES[extname2(file)] ?? "application/octet-stream" });
|
|
1126
1150
|
res.end(data);
|
|
@@ -1153,8 +1177,8 @@ async function serve(opts) {
|
|
|
1153
1177
|
|
|
1154
1178
|
// cli/lib/stack.ts
|
|
1155
1179
|
import { execFile as execFile2 } from "child_process";
|
|
1156
|
-
import { lstat, readFile as
|
|
1157
|
-
import { join as
|
|
1180
|
+
import { lstat as lstat2, readFile as readFile4, readlink, rm } from "fs/promises";
|
|
1181
|
+
import { join as join4 } from "path";
|
|
1158
1182
|
import { promisify as promisify2 } from "util";
|
|
1159
1183
|
var execP = promisify2(execFile2);
|
|
1160
1184
|
var ZERO = "0".repeat(40);
|
|
@@ -1222,9 +1246,9 @@ async function targetEntry(root, meta, path) {
|
|
|
1222
1246
|
const m = /^(\d+) ([0-9a-f]+) 0\t/.exec(await git(root, ["ls-files", "-s", "-z", "--", path]));
|
|
1223
1247
|
if (m) e = { mode: m[1], sha: m[2] };
|
|
1224
1248
|
} else {
|
|
1225
|
-
const full =
|
|
1226
|
-
const st = await
|
|
1227
|
-
const content = st.isSymbolicLink() ? await readlink(full) : await
|
|
1249
|
+
const full = join4(root, path);
|
|
1250
|
+
const st = await lstat2(full);
|
|
1251
|
+
const content = st.isSymbolicLink() ? await readlink(full) : await readFile4(full);
|
|
1228
1252
|
const mode = st.isSymbolicLink() ? "120000" : st.mode & 73 ? "100755" : "100644";
|
|
1229
1253
|
const sha = st.isSymbolicLink() ? (await git(root, ["hash-object", "-w", "--stdin"], content)).trim() : (await git(root, ["hash-object", "-w", "--", path])).trim();
|
|
1230
1254
|
e = { mode, sha };
|
|
@@ -1330,7 +1354,7 @@ async function createBranches(root, stack, force) {
|
|
|
1330
1354
|
async function checkStack(root, stack, cmd, tmp) {
|
|
1331
1355
|
const results = [];
|
|
1332
1356
|
for (const pr of stack.prs) {
|
|
1333
|
-
const dir =
|
|
1357
|
+
const dir = join4(tmp, `pr-${pr.n}`);
|
|
1334
1358
|
await git(root, ["worktree", "add", "--detach", "--quiet", dir, pr.commit]);
|
|
1335
1359
|
try {
|
|
1336
1360
|
const r = await execP(cmd, { cwd: dir, shell: true, maxBuffer: 64 * 1024 * 1024 }).then(
|
|
@@ -1430,7 +1454,7 @@ async function main(argv, cwd = process.cwd()) {
|
|
|
1430
1454
|
});
|
|
1431
1455
|
const [cmd, ...rest] = positionals;
|
|
1432
1456
|
if (values.version) {
|
|
1433
|
-
const pkg = JSON.parse(await
|
|
1457
|
+
const pkg = JSON.parse(await readFile5(join5(PKG_ROOT, "package.json"), "utf8"));
|
|
1434
1458
|
console.log(pkg.version);
|
|
1435
1459
|
return 0;
|
|
1436
1460
|
}
|
|
@@ -1489,7 +1513,7 @@ async function main(argv, cwd = process.cwd()) {
|
|
|
1489
1513
|
case "serve": {
|
|
1490
1514
|
const p = await run(cwd, values.run);
|
|
1491
1515
|
const port = values.port ? Number(values.port) : 4848;
|
|
1492
|
-
const s = await serve({ paths: p, appDir:
|
|
1516
|
+
const s = await serve({ paths: p, appDir: join5(PKG_ROOT, "dist", "app"), port });
|
|
1493
1517
|
console.log(`spark-joy reviewing ${p.name} at ${s.url} (Ctrl+C to stop)`);
|
|
1494
1518
|
await new Promise((resolve) => process.once("SIGINT", resolve));
|
|
1495
1519
|
await s.close();
|
|
@@ -1524,13 +1548,13 @@ async function main(argv, cwd = process.cwd()) {
|
|
|
1524
1548
|
hunks,
|
|
1525
1549
|
plan: result.plan,
|
|
1526
1550
|
prefix,
|
|
1527
|
-
indexFile:
|
|
1551
|
+
indexFile: join5(p.dir, "split.index")
|
|
1528
1552
|
});
|
|
1529
1553
|
for (const pr of stack.prs)
|
|
1530
1554
|
console.log(`${String(pr.n).padStart(2)} ${pr.branch} ${pr.lines} lines, ${pr.files} files ${pr.chunk.title}`);
|
|
1531
1555
|
console.log(`${stack.prs.length} PRs; the last one matches your changes exactly`);
|
|
1532
1556
|
if (values.check !== void 0) {
|
|
1533
|
-
const tmp = await mkdtemp(
|
|
1557
|
+
const tmp = await mkdtemp(join5(tmpdir(), "spark-joy-check-"));
|
|
1534
1558
|
try {
|
|
1535
1559
|
const results = await checkStack(meta.repoRoot, stack, values.check, tmp);
|
|
1536
1560
|
for (const r of results) {
|
|
@@ -1555,9 +1579,9 @@ nothing created; add --apply to create the branches`);
|
|
|
1555
1579
|
return 0;
|
|
1556
1580
|
}
|
|
1557
1581
|
case "install-skill": {
|
|
1558
|
-
const skill =
|
|
1582
|
+
const skill = join5(PKG_ROOT, "plugin", "skills", "spark-joy", "SKILL.md");
|
|
1559
1583
|
if (values.print) {
|
|
1560
|
-
process.stdout.write(await
|
|
1584
|
+
process.stdout.write(await readFile5(skill, "utf8"));
|
|
1561
1585
|
return 0;
|
|
1562
1586
|
}
|
|
1563
1587
|
const homes = { claude: ".claude", agents: ".agents" };
|
|
@@ -1566,9 +1590,9 @@ nothing created; add --apply to create the branches`);
|
|
|
1566
1590
|
if (bad) throw new Error(`unknown agent ${bad}; use claude or agents`);
|
|
1567
1591
|
const root = values.project ? await repoRoot(cwd) : homedir();
|
|
1568
1592
|
for (const a of agents) {
|
|
1569
|
-
const dir =
|
|
1593
|
+
const dir = join5(root, homes[a], "skills", "spark-joy");
|
|
1570
1594
|
await mkdir3(dir, { recursive: true });
|
|
1571
|
-
await copyFile(skill,
|
|
1595
|
+
await copyFile(skill, join5(dir, "SKILL.md"));
|
|
1572
1596
|
console.log(`installed skill to ${dir}`);
|
|
1573
1597
|
}
|
|
1574
1598
|
console.log('ask your agent to "review this PR in chunks" or "split my changes into PRs"');
|