newpr 1.0.3 → 1.0.5
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/package.json
CHANGED
package/src/stack/publish.ts
CHANGED
|
@@ -46,9 +46,9 @@ export async function publishStack(input: PublishInput): Promise<StackPublishRes
|
|
|
46
46
|
? `[${order}/${total}] ${gc.pr_title}`
|
|
47
47
|
: `[Stack ${order}/${total}] ${gc.group_id}`;
|
|
48
48
|
|
|
49
|
-
const
|
|
49
|
+
const placeholder = buildPlaceholderBody(gc.group_id, order, total, pr_meta);
|
|
50
50
|
|
|
51
|
-
const prResult = await Bun.$`gh pr create --repo ${ghRepo} --base ${prBase} --head ${gc.branch_name} --title ${title} --body ${
|
|
51
|
+
const prResult = await Bun.$`gh pr create --repo ${ghRepo} --base ${prBase} --head ${gc.branch_name} --title ${title} --body ${placeholder} --draft`.quiet().nothrow();
|
|
52
52
|
|
|
53
53
|
if (prResult.exitCode === 0) {
|
|
54
54
|
const prUrl = prResult.stdout.toString().trim();
|
|
@@ -68,29 +68,84 @@ export async function publishStack(input: PublishInput): Promise<StackPublishRes
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
await updatePrBodies(ghRepo, prs, pr_meta);
|
|
72
|
+
|
|
71
73
|
return { branches, prs };
|
|
72
74
|
}
|
|
73
75
|
|
|
74
|
-
function
|
|
76
|
+
async function updatePrBodies(ghRepo: string, prs: PrInfo[], prMeta: PrMeta): Promise<void> {
|
|
77
|
+
if (prs.length === 0) return;
|
|
78
|
+
|
|
79
|
+
for (let i = 0; i < prs.length; i++) {
|
|
80
|
+
const pr = prs[i]!;
|
|
81
|
+
const body = buildFullBody(pr, i, prs, prMeta);
|
|
82
|
+
|
|
83
|
+
await Bun.$`gh pr edit ${pr.number} --repo ${ghRepo} --body ${body}`.quiet().nothrow();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function buildPlaceholderBody(
|
|
75
88
|
groupId: string,
|
|
76
89
|
order: number,
|
|
77
90
|
total: number,
|
|
78
|
-
_execResult: StackExecResult,
|
|
79
91
|
prMeta: PrMeta,
|
|
80
92
|
): string {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const lines = [
|
|
85
|
-
`> This is part of a stacked PR chain created by [newpr](${prMeta.pr_url})`,
|
|
86
|
-
`>`,
|
|
87
|
-
`> **Stack order**: ${order}/${total}`,
|
|
88
|
-
`> **${prevPr}** | **${nextPr}**`,
|
|
93
|
+
return [
|
|
94
|
+
`> This is part of a stacked PR chain created by [newpr](https://github.com/jiwonMe/newpr).`,
|
|
95
|
+
`> Stack order: ${order}/${total} — body will be updated with links shortly.`,
|
|
89
96
|
``,
|
|
90
97
|
`## ${groupId}`,
|
|
91
98
|
``,
|
|
92
99
|
`*From PR #${prMeta.pr_number}: ${prMeta.pr_title}*`,
|
|
93
|
-
];
|
|
100
|
+
].join("\n");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function buildFullBody(
|
|
104
|
+
current: PrInfo,
|
|
105
|
+
index: number,
|
|
106
|
+
allPrs: PrInfo[],
|
|
107
|
+
prMeta: PrMeta,
|
|
108
|
+
): string {
|
|
109
|
+
const total = allPrs.length;
|
|
110
|
+
const order = index + 1;
|
|
111
|
+
|
|
112
|
+
const stackTable = allPrs.map((pr, i) => {
|
|
113
|
+
const num = i + 1;
|
|
114
|
+
const isCurrent = i === index;
|
|
115
|
+
const marker = isCurrent ? "👉" : statusEmoji(i, index);
|
|
116
|
+
const link = `[#${pr.number}](${pr.url})`;
|
|
117
|
+
const titleText = pr.title.replace(/^\[\d+\/\d+\]\s*/, "");
|
|
118
|
+
return `| ${marker} | ${num}/${total} | ${link} | ${titleText} |`;
|
|
119
|
+
}).join("\n");
|
|
120
|
+
|
|
121
|
+
const prev = index > 0
|
|
122
|
+
? `⬅️ Previous: [#${allPrs[index - 1]!.number}](${allPrs[index - 1]!.url})`
|
|
123
|
+
: "⬅️ Previous: base branch";
|
|
124
|
+
const next = index < total - 1
|
|
125
|
+
? `➡️ Next: [#${allPrs[index + 1]!.number}](${allPrs[index + 1]!.url})`
|
|
126
|
+
: "➡️ Next: top of stack";
|
|
127
|
+
|
|
128
|
+
return [
|
|
129
|
+
`> **Stack ${order}/${total}** — This PR is part of a stacked PR chain created by [newpr](https://github.com/jiwonMe/newpr).`,
|
|
130
|
+
`> Source: #${prMeta.pr_number} ${prMeta.pr_title}`,
|
|
131
|
+
``,
|
|
132
|
+
`### 📚 Stack Navigation`,
|
|
133
|
+
``,
|
|
134
|
+
`| | Order | PR | Title |`,
|
|
135
|
+
`|---|---|---|---|`,
|
|
136
|
+
stackTable,
|
|
137
|
+
``,
|
|
138
|
+
`${prev} | ${next}`,
|
|
139
|
+
``,
|
|
140
|
+
`---`,
|
|
141
|
+
``,
|
|
142
|
+
`## ${current.group_id}`,
|
|
143
|
+
``,
|
|
144
|
+
`*From PR [#${prMeta.pr_number}](${prMeta.pr_url}): ${prMeta.pr_title}*`,
|
|
145
|
+
].join("\n");
|
|
146
|
+
}
|
|
94
147
|
|
|
95
|
-
|
|
148
|
+
function statusEmoji(prIndex: number, currentIndex: number): string {
|
|
149
|
+
if (prIndex < currentIndex) return "✅";
|
|
150
|
+
return "⬜";
|
|
96
151
|
}
|
package/src/web/client/App.tsx
CHANGED
|
@@ -202,6 +202,7 @@ export function App() {
|
|
|
202
202
|
onTabChange={handleTabChange}
|
|
203
203
|
onReanalyze={(prUrl: string) => { analysis.start(prUrl); }}
|
|
204
204
|
enabledPlugins={features.enabledPlugins}
|
|
205
|
+
onTrackAnalysis={bgAnalyses.track}
|
|
205
206
|
/>
|
|
206
207
|
)}
|
|
207
208
|
{analysis.phase === "error" && (
|
|
@@ -51,6 +51,7 @@ export function ResultsScreen({
|
|
|
51
51
|
onTabChange,
|
|
52
52
|
onReanalyze,
|
|
53
53
|
enabledPlugins,
|
|
54
|
+
onTrackAnalysis,
|
|
54
55
|
}: {
|
|
55
56
|
data: NewprOutput;
|
|
56
57
|
onBack: () => void;
|
|
@@ -61,6 +62,7 @@ export function ResultsScreen({
|
|
|
61
62
|
onTabChange?: (tab: string) => void;
|
|
62
63
|
onReanalyze?: (prUrl: string) => void;
|
|
63
64
|
enabledPlugins?: string[];
|
|
65
|
+
onTrackAnalysis?: (analysisSessionId: string, prUrl: string) => void;
|
|
64
66
|
}) {
|
|
65
67
|
const { meta, summary } = data;
|
|
66
68
|
const [tab, setTab] = useState<TabValue>(getInitialTab);
|
|
@@ -274,7 +276,7 @@ export function ResultsScreen({
|
|
|
274
276
|
/>
|
|
275
277
|
</TabsContent>
|
|
276
278
|
<TabsContent value="stack">
|
|
277
|
-
<StackPanel sessionId={sessionId} />
|
|
279
|
+
<StackPanel sessionId={sessionId} onTrackAnalysis={onTrackAnalysis} />
|
|
278
280
|
</TabsContent>
|
|
279
281
|
<TabsContent value="slides">
|
|
280
282
|
<SlidesPanel data={data} sessionId={sessionId} />
|
|
@@ -133,7 +133,11 @@ function applyServerState(server: ServerStackState): Partial<StackState> {
|
|
|
133
133
|
};
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
|
|
136
|
+
interface UseStackOptions {
|
|
137
|
+
onTrackAnalysis?: (analysisSessionId: string, prUrl: string) => void;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function useStack(sessionId: string | null | undefined, options?: UseStackOptions) {
|
|
137
141
|
const [state, setState] = useState<StackState>({
|
|
138
142
|
phase: "idle",
|
|
139
143
|
error: null,
|
|
@@ -212,8 +216,9 @@ export function useStack(sessionId: string | null | undefined) {
|
|
|
212
216
|
});
|
|
213
217
|
|
|
214
218
|
es.onerror = () => {
|
|
215
|
-
es.
|
|
216
|
-
|
|
219
|
+
if (es.readyState === EventSource.CLOSED) {
|
|
220
|
+
eventSourceRef.current = null;
|
|
221
|
+
}
|
|
217
222
|
};
|
|
218
223
|
}, []);
|
|
219
224
|
|
|
@@ -253,11 +258,29 @@ export function useStack(sessionId: string | null | undefined) {
|
|
|
253
258
|
const data = await res.json();
|
|
254
259
|
if (!res.ok) throw new Error(data.error ?? "Publishing failed");
|
|
255
260
|
|
|
261
|
+
const publishResult = data.publish_result as PublishResultData;
|
|
262
|
+
|
|
256
263
|
setState((s) => ({
|
|
257
264
|
...s,
|
|
258
265
|
phase: "done",
|
|
259
|
-
publishResult
|
|
266
|
+
publishResult,
|
|
260
267
|
}));
|
|
268
|
+
|
|
269
|
+
if (options?.onTrackAnalysis && publishResult?.prs?.length > 0) {
|
|
270
|
+
for (const pr of publishResult.prs) {
|
|
271
|
+
try {
|
|
272
|
+
const analysisRes = await fetch("/api/analysis", {
|
|
273
|
+
method: "POST",
|
|
274
|
+
headers: { "Content-Type": "application/json" },
|
|
275
|
+
body: JSON.stringify({ pr: pr.url }),
|
|
276
|
+
});
|
|
277
|
+
const analysisData = await analysisRes.json() as { sessionId?: string };
|
|
278
|
+
if (analysisData.sessionId) {
|
|
279
|
+
options.onTrackAnalysis(analysisData.sessionId, pr.url);
|
|
280
|
+
}
|
|
281
|
+
} catch {}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
261
284
|
} catch (err) {
|
|
262
285
|
setState((s) => ({
|
|
263
286
|
...s,
|
|
@@ -265,7 +288,7 @@ export function useStack(sessionId: string | null | undefined) {
|
|
|
265
288
|
error: err instanceof Error ? err.message : String(err),
|
|
266
289
|
}));
|
|
267
290
|
}
|
|
268
|
-
}, [sessionId]);
|
|
291
|
+
}, [sessionId, options]);
|
|
269
292
|
|
|
270
293
|
const reset = useCallback(() => {
|
|
271
294
|
eventSourceRef.current?.close();
|
|
@@ -68,8 +68,13 @@ function PipelineTimeline({ phase }: { phase: StackPhase }) {
|
|
|
68
68
|
);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
interface StackPanelProps {
|
|
72
|
+
sessionId?: string | null;
|
|
73
|
+
onTrackAnalysis?: (analysisSessionId: string, prUrl: string) => void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function StackPanel({ sessionId, onTrackAnalysis }: StackPanelProps) {
|
|
77
|
+
const stack = useStack(sessionId, { onTrackAnalysis });
|
|
73
78
|
|
|
74
79
|
if (stack.phase === "idle") {
|
|
75
80
|
return (
|