critique 0.0.1 → 0.0.3
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/AGENTS.md +7 -2
- package/CHANGELOG.md +9 -0
- package/bun.lock +9 -0
- package/package.json +3 -1
- package/src/cli.tsx +293 -80
- package/src/diff.tsx +223 -992
- package/src/example.tsx +931 -14
- package/src/highlight.tsx +2 -6
package/AGENTS.md
CHANGED
|
@@ -6,7 +6,6 @@ IMPORTANT! before starting every task ALWAYS read opentui docs with `curl -s htt
|
|
|
6
6
|
|
|
7
7
|
ALWAYS!
|
|
8
8
|
|
|
9
|
-
|
|
10
9
|
## bun
|
|
11
10
|
|
|
12
11
|
NEVER run bun run index.tsx. You cannot directly run the tui app. it will hang. instead ask me to do so.
|
|
@@ -24,6 +23,7 @@ Try to never use useEffect if possible. usually you can move logic directly in e
|
|
|
24
23
|
## Rules
|
|
25
24
|
|
|
26
25
|
- keep types as close as possible to rayacst
|
|
26
|
+
- if you need Node.js apis import the namesapce and not the named exports: `import fs from 'fs'` and not `import { writeFileSync } from 'fs'`
|
|
27
27
|
- DO NOT use as any. instead try to understand how to fix the types in other ways
|
|
28
28
|
- to implement compound components like `List.Item` first define the type of List, using a interface, then use : to implement it and add compound components later using . and omitting the props types given they are already typed by the interface, here is an example
|
|
29
29
|
- DO NOT use console.log. only use logger.log instead
|
|
@@ -53,7 +53,12 @@ https://gitchamber.com/repos/repos/vercel/ai/main/files
|
|
|
53
53
|
|
|
54
54
|
use gitchamber to read the .md files using curl
|
|
55
55
|
|
|
56
|
-
|
|
57
56
|
## researching opentui patterns
|
|
58
57
|
|
|
59
58
|
you can read more examples of opentui react code using gitchamber by listing and reading files from the correct endpoint: https://gitchamber.com/repos/sst/opentui/main/files?glob=packages/react/examples/**
|
|
59
|
+
|
|
60
|
+
## changelog
|
|
61
|
+
|
|
62
|
+
after any meaningful change update CHANGELOG.md with the version number and the list of changes made. in concise bullet points
|
|
63
|
+
|
|
64
|
+
before updating the changelog bump the package.json version field first. NEVER do major bumps. NEVER publish yourself
|
package/CHANGELOG.md
ADDED
package/bun.lock
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"": {
|
|
5
5
|
"name": "react",
|
|
6
6
|
"dependencies": {
|
|
7
|
+
"@clack/prompts": "^1.0.0-alpha.6",
|
|
7
8
|
"@opentui/core": "^0.1.26",
|
|
8
9
|
"@opentui/react": "^0.1.26",
|
|
9
10
|
"@shikijs/langs": "^3.13.0",
|
|
@@ -23,6 +24,10 @@
|
|
|
23
24
|
"packages": {
|
|
24
25
|
"@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="],
|
|
25
26
|
|
|
27
|
+
"@clack/core": ["@clack/core@1.0.0-alpha.6", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-eG5P45+oShFG17u9I1DJzLkXYB1hpUgTLi32EfsMjSHLEqJUR8BOBCVFkdbUX2g08eh/HCi6UxNGpPhaac1LAA=="],
|
|
28
|
+
|
|
29
|
+
"@clack/prompts": ["@clack/prompts@1.0.0-alpha.6", "", { "dependencies": { "@clack/core": "1.0.0-alpha.6", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-75NCtYOgDHVBE2nLdKPTDYOaESxO0GLAKC7INREp5VbS988Xua1u+588VaGlcvXiLc/kSwc25Cd+4PeTSpY6QQ=="],
|
|
30
|
+
|
|
26
31
|
"@dimforge/rapier2d-simd-compat": ["@dimforge/rapier2d-simd-compat@0.17.3", "", {}, "sha512-bijvwWz6NHsNj5e5i1vtd3dU2pDhthSaTUZSh14DUGGKJfw8eMnlWZsxwHBxB/a3AXVNDjL9abuHw1k9FGR+jg=="],
|
|
27
32
|
|
|
28
33
|
"@jimp/core": ["@jimp/core@1.6.0", "", { "dependencies": { "@jimp/file-ops": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "await-to-js": "^3.0.0", "exif-parser": "^0.1.12", "file-type": "^16.0.0", "mime": "3" } }, "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w=="],
|
|
@@ -225,6 +230,8 @@
|
|
|
225
230
|
|
|
226
231
|
"peek-readable": ["peek-readable@4.1.0", "", {}, "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="],
|
|
227
232
|
|
|
233
|
+
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
|
234
|
+
|
|
228
235
|
"pixelmatch": ["pixelmatch@5.3.0", "", { "dependencies": { "pngjs": "^6.0.0" }, "bin": { "pixelmatch": "bin/pixelmatch" } }, "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q=="],
|
|
229
236
|
|
|
230
237
|
"planck": ["planck@1.4.2", "", { "peerDependencies": { "stage-js": "^1.0.0-alpha.12" } }, "sha512-mNbhnV3g8X2rwGxzcesjmN8BDA6qfXgQxXVMkWau9MCRlQY0RLNEkyHlVp6yFy/X6qrzAXyNONCnZ1cGDLrNew=="],
|
|
@@ -261,6 +268,8 @@
|
|
|
261
268
|
|
|
262
269
|
"simple-xml-to-json": ["simple-xml-to-json@1.2.3", "", {}, "sha512-kWJDCr9EWtZ+/EYYM5MareWj2cRnZGF93YDNpH4jQiHB+hBIZnfPFSQiVMzZOdk+zXWqTZ/9fTeQNu2DqeiudA=="],
|
|
263
270
|
|
|
271
|
+
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
|
|
272
|
+
|
|
264
273
|
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
|
|
265
274
|
|
|
266
275
|
"stage-js": ["stage-js@1.0.0-alpha.17", "", {}, "sha512-AzlMO+t51v6cFvKZ+Oe9DJnL1OXEH5s9bEy6di5aOrUpcP7PCzI/wIeXF0u3zg0L89gwnceoKxrLId0ZpYnNXw=="],
|
package/package.json
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
"name": "critique",
|
|
3
3
|
"module": "src/diff.tsx",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.0.
|
|
5
|
+
"version": "0.0.3",
|
|
6
6
|
"private": false,
|
|
7
7
|
"bin": "./src/cli.tsx",
|
|
8
8
|
"scripts": {
|
|
9
9
|
"cli": "bun --watch src/cli.tsx",
|
|
10
|
+
"prepublishonly": "tsc",
|
|
10
11
|
"example": "bun --watch src/example.tsx"
|
|
11
12
|
},
|
|
12
13
|
"devDependencies": {
|
|
@@ -14,6 +15,7 @@
|
|
|
14
15
|
"typescript": "^5.9.3"
|
|
15
16
|
},
|
|
16
17
|
"dependencies": {
|
|
18
|
+
"@clack/prompts": "^1.0.0-alpha.6",
|
|
17
19
|
"@opentui/core": "^0.1.26",
|
|
18
20
|
"@opentui/react": "^0.1.26",
|
|
19
21
|
"@shikijs/langs": "^3.13.0",
|
package/src/cli.tsx
CHANGED
|
@@ -1,17 +1,129 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { cac } from "cac";
|
|
3
|
-
import { parsePatch } from "diff";
|
|
4
|
-
import { render, useOnResize, useTerminalDimensions } from "@opentui/react";
|
|
5
|
-
import * as React from "react";
|
|
6
|
-
import { execSync } from "child_process";
|
|
7
3
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
render,
|
|
5
|
+
useKeyboard,
|
|
6
|
+
useOnResize,
|
|
7
|
+
useRenderer,
|
|
8
|
+
useTerminalDimensions,
|
|
9
|
+
} from "@opentui/react";
|
|
10
|
+
import * as React from "react";
|
|
11
|
+
import { exec, execSync } from "child_process";
|
|
12
|
+
import { promisify } from "util";
|
|
13
|
+
import { MacOSScrollAccel } from "@opentui/core";
|
|
14
|
+
import fs from "fs";
|
|
15
|
+
import { tmpdir } from "os";
|
|
16
|
+
import { join } from "path";
|
|
17
|
+
import * as p from "@clack/prompts";
|
|
18
|
+
|
|
19
|
+
const execAsync = promisify(exec);
|
|
12
20
|
|
|
13
21
|
const cli = cac("critique");
|
|
14
22
|
|
|
23
|
+
class ScrollAcceleration {
|
|
24
|
+
public multiplier: number = 1;
|
|
25
|
+
private macosAccel: MacOSScrollAccel;
|
|
26
|
+
constructor() {
|
|
27
|
+
this.macosAccel = new MacOSScrollAccel();
|
|
28
|
+
}
|
|
29
|
+
tick(delta: number) {
|
|
30
|
+
return this.macosAccel.tick(delta) * this.multiplier;
|
|
31
|
+
}
|
|
32
|
+
reset() {
|
|
33
|
+
this.macosAccel.reset();
|
|
34
|
+
// this.multiplier = 1;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface AppProps {
|
|
39
|
+
parsedFiles: Array<{
|
|
40
|
+
oldFileName?: string;
|
|
41
|
+
newFileName?: string;
|
|
42
|
+
hunks: any[];
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function App({ parsedFiles }: AppProps) {
|
|
47
|
+
const { width: initialWidth } = useTerminalDimensions();
|
|
48
|
+
const [width, setWidth] = React.useState(initialWidth);
|
|
49
|
+
const [scrollAcceleration] = React.useState(() => new ScrollAcceleration());
|
|
50
|
+
|
|
51
|
+
useOnResize(
|
|
52
|
+
React.useCallback((newWidth: number) => {
|
|
53
|
+
setWidth(newWidth);
|
|
54
|
+
}, []),
|
|
55
|
+
);
|
|
56
|
+
const useSplitView = width >= 100;
|
|
57
|
+
|
|
58
|
+
const renderer = useRenderer();
|
|
59
|
+
|
|
60
|
+
useKeyboard((key) => {
|
|
61
|
+
if (key.name === "z" && key.ctrl) {
|
|
62
|
+
renderer.console.toggle();
|
|
63
|
+
}
|
|
64
|
+
if (key.option) {
|
|
65
|
+
console.log(key);
|
|
66
|
+
if (key.eventType === "release") {
|
|
67
|
+
scrollAcceleration.multiplier = 1;
|
|
68
|
+
} else {
|
|
69
|
+
scrollAcceleration.multiplier = 10;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const { FileEditPreviewTitle, FileEditPreview } = require("./diff.tsx");
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<box
|
|
78
|
+
key={String(useSplitView)}
|
|
79
|
+
style={{ flexDirection: "column", height: "100%", padding: 1 }}
|
|
80
|
+
>
|
|
81
|
+
<scrollbox
|
|
82
|
+
scrollAcceleration={scrollAcceleration}
|
|
83
|
+
style={{
|
|
84
|
+
flexGrow: 1,
|
|
85
|
+
rootOptions: {
|
|
86
|
+
backgroundColor: "transparent",
|
|
87
|
+
border: false,
|
|
88
|
+
},
|
|
89
|
+
scrollbarOptions: {
|
|
90
|
+
showArrows: false,
|
|
91
|
+
trackOptions: {
|
|
92
|
+
foregroundColor: "#4a4a4a",
|
|
93
|
+
backgroundColor: "transparent",
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
}}
|
|
97
|
+
focused
|
|
98
|
+
>
|
|
99
|
+
<box style={{ flexDirection: "column" }}>
|
|
100
|
+
{parsedFiles.map((file, idx) => (
|
|
101
|
+
<box
|
|
102
|
+
key={idx}
|
|
103
|
+
style={{
|
|
104
|
+
flexDirection: "column",
|
|
105
|
+
marginBottom: idx < parsedFiles.length - 1 ? 2 : 0,
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
<FileEditPreviewTitle
|
|
109
|
+
filePath={file.newFileName || file.oldFileName || "unknown"}
|
|
110
|
+
hunks={file.hunks}
|
|
111
|
+
/>
|
|
112
|
+
<box paddingTop={1} />
|
|
113
|
+
<FileEditPreview
|
|
114
|
+
hunks={file.hunks}
|
|
115
|
+
paddingLeft={0}
|
|
116
|
+
splitView={useSplitView}
|
|
117
|
+
filePath={file.newFileName || file.oldFileName || ""}
|
|
118
|
+
/>
|
|
119
|
+
</box>
|
|
120
|
+
))}
|
|
121
|
+
</box>
|
|
122
|
+
</scrollbox>
|
|
123
|
+
</box>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
15
127
|
cli
|
|
16
128
|
.command(
|
|
17
129
|
"[ref]",
|
|
@@ -20,95 +132,196 @@ cli
|
|
|
20
132
|
.option("--staged", "Show staged changes")
|
|
21
133
|
.option("--commit <ref>", "Show changes from a specific commit")
|
|
22
134
|
.action(async (ref, options) => {
|
|
23
|
-
let gitDiff: string;
|
|
24
|
-
|
|
25
135
|
try {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
136
|
+
const gitCommand = (() => {
|
|
137
|
+
if (options.staged) return "git diff --cached --no-prefix";
|
|
138
|
+
if (options.commit) return `git show ${options.commit} --no-prefix`;
|
|
139
|
+
if (ref) return `git show ${ref} --no-prefix`;
|
|
140
|
+
return "git diff --no-prefix";
|
|
141
|
+
})();
|
|
142
|
+
|
|
143
|
+
const [{ stdout: gitDiff }, diffModule, { parsePatch }] =
|
|
144
|
+
await Promise.all([
|
|
145
|
+
execAsync(gitCommand, { encoding: "utf-8" }),
|
|
146
|
+
import("./diff.tsx"),
|
|
147
|
+
import("diff"),
|
|
148
|
+
]);
|
|
149
|
+
|
|
150
|
+
if (!gitDiff.trim()) {
|
|
151
|
+
console.log("No changes to display");
|
|
152
|
+
process.exit(0);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const parsedFiles = parsePatch(gitDiff);
|
|
156
|
+
|
|
157
|
+
if (parsedFiles.length === 0) {
|
|
158
|
+
console.log("No changes to display");
|
|
159
|
+
process.exit(0);
|
|
34
160
|
}
|
|
161
|
+
|
|
162
|
+
const { ErrorBoundary } = diffModule;
|
|
163
|
+
|
|
164
|
+
await render(
|
|
165
|
+
React.createElement(
|
|
166
|
+
ErrorBoundary,
|
|
167
|
+
null,
|
|
168
|
+
React.createElement(App, { parsedFiles }),
|
|
169
|
+
),
|
|
170
|
+
);
|
|
35
171
|
} catch (error) {
|
|
36
172
|
console.error("Error getting git diff:", error);
|
|
37
173
|
process.exit(1);
|
|
38
174
|
}
|
|
175
|
+
});
|
|
39
176
|
|
|
40
|
-
|
|
41
|
-
|
|
177
|
+
cli
|
|
178
|
+
.command("difftool <local> <remote>", "Git difftool integration")
|
|
179
|
+
.action(async (local: string, remote: string) => {
|
|
180
|
+
if (!process.stdout.isTTY) {
|
|
181
|
+
execSync(`git diff --no-ext-diff "${local}" "${remote}"`, {
|
|
182
|
+
stdio: "inherit",
|
|
183
|
+
});
|
|
42
184
|
process.exit(0);
|
|
43
185
|
}
|
|
44
186
|
|
|
45
|
-
|
|
187
|
+
try {
|
|
188
|
+
const [localContent, remoteContent, diffModule, { structuredPatch }] =
|
|
189
|
+
await Promise.all([
|
|
190
|
+
fs.readFileSync(local, "utf-8"),
|
|
191
|
+
fs.readFileSync(remote, "utf-8"),
|
|
192
|
+
import("./diff.tsx"),
|
|
193
|
+
import("diff"),
|
|
194
|
+
]);
|
|
46
195
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
196
|
+
const patch = structuredPatch(
|
|
197
|
+
local,
|
|
198
|
+
remote,
|
|
199
|
+
localContent,
|
|
200
|
+
remoteContent,
|
|
201
|
+
"",
|
|
202
|
+
"",
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
if (patch.hunks.length === 0) {
|
|
206
|
+
console.log("No changes to display");
|
|
207
|
+
process.exit(0);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const { ErrorBoundary } = diffModule;
|
|
211
|
+
|
|
212
|
+
await render(
|
|
213
|
+
React.createElement(
|
|
214
|
+
ErrorBoundary,
|
|
215
|
+
null,
|
|
216
|
+
React.createElement(App, { parsedFiles: [patch] }),
|
|
217
|
+
),
|
|
218
|
+
);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.error("Error displaying diff:", error);
|
|
221
|
+
process.exit(1);
|
|
50
222
|
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
cli
|
|
226
|
+
.command(
|
|
227
|
+
"pick <branch>",
|
|
228
|
+
"Pick files from another branch to apply to HEAD (experimental)",
|
|
229
|
+
)
|
|
230
|
+
.action(async (branch: string) => {
|
|
231
|
+
try {
|
|
232
|
+
const { stdout: currentBranch } = await execAsync(
|
|
233
|
+
"git branch --show-current",
|
|
234
|
+
);
|
|
235
|
+
const current = currentBranch.trim();
|
|
236
|
+
|
|
237
|
+
if (current === branch) {
|
|
238
|
+
p.log.error("Cannot pick from the same branch");
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
51
241
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
242
|
+
const { stdout: branchExists } = await execAsync(
|
|
243
|
+
`git rev-parse --verify ${branch}`,
|
|
244
|
+
{ encoding: "utf-8" },
|
|
245
|
+
).catch(() => ({ stdout: "" }));
|
|
55
246
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
247
|
+
if (!branchExists.trim()) {
|
|
248
|
+
p.log.error(`Branch "${branch}" does not exist`);
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const { stdout: diffOutput } = await execAsync(
|
|
253
|
+
`git diff --name-only HEAD ${branch}`,
|
|
254
|
+
{ encoding: "utf-8" },
|
|
60
255
|
);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
/>
|
|
95
|
-
<box paddingTop={1} />
|
|
96
|
-
<FileEditPreview
|
|
97
|
-
hunks={file.hunks}
|
|
98
|
-
paddingLeft={0}
|
|
99
|
-
splitView={useSplitView}
|
|
100
|
-
/>
|
|
101
|
-
</box>
|
|
102
|
-
))}
|
|
103
|
-
</box>
|
|
104
|
-
</scrollbox>
|
|
105
|
-
</box>
|
|
256
|
+
|
|
257
|
+
const files = diffOutput
|
|
258
|
+
.trim()
|
|
259
|
+
.split("\n")
|
|
260
|
+
.filter((f) => f);
|
|
261
|
+
|
|
262
|
+
if (files.length === 0) {
|
|
263
|
+
p.log.info("No differences found between branches");
|
|
264
|
+
process.exit(0);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const selectedFiles = await p.autocompleteMultiselect({
|
|
268
|
+
message: `Select files to pick from "${branch}":`,
|
|
269
|
+
options: files.map((file) => ({
|
|
270
|
+
value: file,
|
|
271
|
+
label: file,
|
|
272
|
+
})),
|
|
273
|
+
required: false,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
if (p.isCancel(selectedFiles)) {
|
|
277
|
+
p.cancel("Operation cancelled.");
|
|
278
|
+
process.exit(0);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (!selectedFiles || selectedFiles.length === 0) {
|
|
282
|
+
p.log.info("No files selected");
|
|
283
|
+
process.exit(0);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const { stdout: patchData } = await execAsync(
|
|
287
|
+
`git diff HEAD ${branch} -- ${selectedFiles.join(" ")}`,
|
|
288
|
+
{ encoding: "utf-8" },
|
|
106
289
|
);
|
|
107
|
-
}
|
|
108
290
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
291
|
+
const patchFile = join(tmpdir(), `critique-pick-${Date.now()}.patch`);
|
|
292
|
+
fs.writeFileSync(patchFile, patchData);
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
execSync(`git apply --3way "${patchFile}"`, { stdio: "pipe" });
|
|
296
|
+
} catch {
|
|
297
|
+
execSync(`git apply "${patchFile}"`, { stdio: "inherit" });
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
fs.unlinkSync(patchFile);
|
|
301
|
+
|
|
302
|
+
const { stdout: conflictFiles } = await execAsync(
|
|
303
|
+
"git diff --name-only --diff-filter=U",
|
|
304
|
+
{ encoding: "utf-8" },
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
const conflicts = conflictFiles
|
|
308
|
+
.trim()
|
|
309
|
+
.split("\n")
|
|
310
|
+
.filter((f) => f);
|
|
311
|
+
|
|
312
|
+
if (conflicts.length > 0) {
|
|
313
|
+
p.log.warn(`Applied with conflicts in ${conflicts.length} file(s):`);
|
|
314
|
+
conflicts.forEach((file) => p.log.message(` - ${file}`));
|
|
315
|
+
} else {
|
|
316
|
+
p.log.success(`Applied changes from ${selectedFiles.length} file(s)`);
|
|
317
|
+
}
|
|
318
|
+
process.exit(0);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
p.log.error(
|
|
321
|
+
`Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
322
|
+
);
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
112
325
|
});
|
|
113
326
|
|
|
114
327
|
cli.help();
|