@simonfestl/husky-cli 1.15.0 → 1.16.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/dist/commands/interactive/auth.d.ts +6 -0
- package/dist/commands/interactive/auth.js +227 -0
- package/dist/commands/interactive/brain.d.ts +6 -0
- package/dist/commands/interactive/brain.js +356 -0
- package/dist/commands/interactive/chat.d.ts +6 -0
- package/dist/commands/interactive/chat.js +367 -0
- package/dist/commands/interactive/infra.d.ts +6 -0
- package/dist/commands/interactive/infra.js +243 -0
- package/dist/commands/interactive/pr.d.ts +6 -0
- package/dist/commands/interactive/pr.js +323 -0
- package/dist/commands/interactive/preview.d.ts +6 -0
- package/dist/commands/interactive/preview.js +238 -0
- package/dist/commands/interactive/tools.d.ts +6 -0
- package/dist/commands/interactive/tools.js +375 -0
- package/dist/commands/interactive/worker.d.ts +6 -0
- package/dist/commands/interactive/worker.js +196 -0
- package/dist/commands/interactive.js +59 -15
- package/dist/lib/permissions.js +28 -1
- package/package.json +1 -1
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive Mode: Pull Request Module
|
|
3
|
+
*
|
|
4
|
+
* Provides menu-based PR management and automation.
|
|
5
|
+
*/
|
|
6
|
+
import { select, input, confirm } from "@inquirer/prompts";
|
|
7
|
+
import { pressEnterToContinue, truncate } from "./utils.js";
|
|
8
|
+
import { spawnSync } from "child_process";
|
|
9
|
+
/**
|
|
10
|
+
* Safe command execution using spawnSync
|
|
11
|
+
*/
|
|
12
|
+
function runGh(args) {
|
|
13
|
+
const result = spawnSync("gh", args, {
|
|
14
|
+
encoding: "utf-8",
|
|
15
|
+
timeout: 60000,
|
|
16
|
+
});
|
|
17
|
+
return {
|
|
18
|
+
success: result.status === 0,
|
|
19
|
+
stdout: result.stdout || "",
|
|
20
|
+
stderr: result.stderr || "",
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export async function prMenu() {
|
|
24
|
+
console.log("\n PULL REQUESTS");
|
|
25
|
+
console.log(" " + "-".repeat(50));
|
|
26
|
+
console.log(" PR management and automation");
|
|
27
|
+
console.log("");
|
|
28
|
+
const menuItems = [
|
|
29
|
+
{ name: "📋 List PRs", value: "list" },
|
|
30
|
+
{ name: "🔍 View PR", value: "view" },
|
|
31
|
+
{ name: "✅ Check PR Status", value: "status" },
|
|
32
|
+
{ name: "📝 Create PR", value: "create" },
|
|
33
|
+
{ name: "🔀 Merge PR", value: "merge" },
|
|
34
|
+
{ name: "💬 Add Comment", value: "comment" },
|
|
35
|
+
{ name: "👀 Review PR", value: "review" },
|
|
36
|
+
{ name: "← Back", value: "back" },
|
|
37
|
+
];
|
|
38
|
+
const choice = await select({
|
|
39
|
+
message: "PR Action:",
|
|
40
|
+
choices: menuItems,
|
|
41
|
+
});
|
|
42
|
+
switch (choice) {
|
|
43
|
+
case "list":
|
|
44
|
+
await listPRs();
|
|
45
|
+
break;
|
|
46
|
+
case "view":
|
|
47
|
+
await viewPR();
|
|
48
|
+
break;
|
|
49
|
+
case "status":
|
|
50
|
+
await checkStatus();
|
|
51
|
+
break;
|
|
52
|
+
case "create":
|
|
53
|
+
await createPR();
|
|
54
|
+
break;
|
|
55
|
+
case "merge":
|
|
56
|
+
await mergePR();
|
|
57
|
+
break;
|
|
58
|
+
case "comment":
|
|
59
|
+
await addComment();
|
|
60
|
+
break;
|
|
61
|
+
case "review":
|
|
62
|
+
await reviewPR();
|
|
63
|
+
break;
|
|
64
|
+
case "back":
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function listPRs() {
|
|
69
|
+
try {
|
|
70
|
+
console.log("\n Fetching PRs...\n");
|
|
71
|
+
const result = runGh(["pr", "list", "--json", "number,title,state,author,createdAt,url,isDraft", "--limit", "20"]);
|
|
72
|
+
if (!result.success) {
|
|
73
|
+
console.error("\n Error listing PRs. Is 'gh' CLI installed and authenticated?");
|
|
74
|
+
console.error(` ${result.stderr}\n`);
|
|
75
|
+
await pressEnterToContinue();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const prs = JSON.parse(result.stdout).map((pr) => ({
|
|
79
|
+
...pr,
|
|
80
|
+
draft: pr.isDraft,
|
|
81
|
+
author: typeof pr.author === "object" ? pr.author.login : pr.author,
|
|
82
|
+
}));
|
|
83
|
+
console.log(" PULL REQUESTS");
|
|
84
|
+
console.log(" " + "-".repeat(80));
|
|
85
|
+
if (prs.length === 0) {
|
|
86
|
+
console.log(" No open PRs found.");
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
console.log(` ${"#".padEnd(6)} ${"TITLE".padEnd(40)} ${"AUTHOR".padEnd(15)} ${"STATE"}`);
|
|
90
|
+
console.log(" " + "-".repeat(80));
|
|
91
|
+
for (const pr of prs) {
|
|
92
|
+
const draftIcon = pr.draft ? "📝" : "";
|
|
93
|
+
console.log(` ${String(pr.number).padEnd(6)} ${truncate(pr.title, 38).padEnd(40)} ${truncate(pr.author, 13).padEnd(15)} ${pr.state} ${draftIcon}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
console.log("");
|
|
97
|
+
await pressEnterToContinue();
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
console.error("\n Error listing PRs:", error.message);
|
|
101
|
+
await pressEnterToContinue();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function viewPR() {
|
|
105
|
+
try {
|
|
106
|
+
const prNumber = await input({
|
|
107
|
+
message: "PR number:",
|
|
108
|
+
validate: (v) => {
|
|
109
|
+
if (!v)
|
|
110
|
+
return "PR number is required";
|
|
111
|
+
if (isNaN(parseInt(v)))
|
|
112
|
+
return "Must be a number";
|
|
113
|
+
return true;
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
console.log(`\n Fetching PR #${prNumber}...\n`);
|
|
117
|
+
const result = runGh(["pr", "view", prNumber]);
|
|
118
|
+
if (!result.success) {
|
|
119
|
+
console.error("\n Error viewing PR:", result.stderr);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
console.log(result.stdout);
|
|
123
|
+
}
|
|
124
|
+
await pressEnterToContinue();
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
console.error("\n Error viewing PR:", error.message);
|
|
128
|
+
await pressEnterToContinue();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function checkStatus() {
|
|
132
|
+
try {
|
|
133
|
+
const prNumber = await input({
|
|
134
|
+
message: "PR number:",
|
|
135
|
+
validate: (v) => {
|
|
136
|
+
if (!v)
|
|
137
|
+
return "PR number is required";
|
|
138
|
+
if (isNaN(parseInt(v)))
|
|
139
|
+
return "Must be a number";
|
|
140
|
+
return true;
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
console.log(`\n Checking status of PR #${prNumber}...\n`);
|
|
144
|
+
const result = runGh(["pr", "checks", prNumber]);
|
|
145
|
+
console.log(" PR CHECKS");
|
|
146
|
+
console.log(" " + "-".repeat(60));
|
|
147
|
+
if (!result.success) {
|
|
148
|
+
console.error(" Error:", result.stderr);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
console.log(result.stdout);
|
|
152
|
+
}
|
|
153
|
+
await pressEnterToContinue();
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
console.error("\n Error checking PR status:", error.message);
|
|
157
|
+
await pressEnterToContinue();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async function createPR() {
|
|
161
|
+
try {
|
|
162
|
+
const title = await input({
|
|
163
|
+
message: "PR title:",
|
|
164
|
+
validate: (v) => (v.length > 0 ? true : "Title is required"),
|
|
165
|
+
});
|
|
166
|
+
const body = await input({
|
|
167
|
+
message: "PR description (optional):",
|
|
168
|
+
default: "",
|
|
169
|
+
});
|
|
170
|
+
const draft = await confirm({
|
|
171
|
+
message: "Create as draft?",
|
|
172
|
+
default: false,
|
|
173
|
+
});
|
|
174
|
+
console.log("\n Creating PR...\n");
|
|
175
|
+
const args = ["pr", "create", "--title", title];
|
|
176
|
+
if (body) {
|
|
177
|
+
args.push("--body", body);
|
|
178
|
+
}
|
|
179
|
+
if (draft) {
|
|
180
|
+
args.push("--draft");
|
|
181
|
+
}
|
|
182
|
+
const result = runGh(args);
|
|
183
|
+
if (!result.success) {
|
|
184
|
+
console.error("\n Error creating PR:", result.stderr);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
console.log(" PR created successfully!");
|
|
188
|
+
console.log(` ${result.stdout.trim()}`);
|
|
189
|
+
console.log("");
|
|
190
|
+
}
|
|
191
|
+
await pressEnterToContinue();
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
console.error("\n Error creating PR:", error.message);
|
|
195
|
+
await pressEnterToContinue();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async function mergePR() {
|
|
199
|
+
try {
|
|
200
|
+
const prNumber = await input({
|
|
201
|
+
message: "PR number to merge:",
|
|
202
|
+
validate: (v) => {
|
|
203
|
+
if (!v)
|
|
204
|
+
return "PR number is required";
|
|
205
|
+
if (isNaN(parseInt(v)))
|
|
206
|
+
return "Must be a number";
|
|
207
|
+
return true;
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
const mergeMethod = await select({
|
|
211
|
+
message: "Merge method:",
|
|
212
|
+
choices: [
|
|
213
|
+
{ name: "Squash and merge", value: "squash" },
|
|
214
|
+
{ name: "Create merge commit", value: "merge" },
|
|
215
|
+
{ name: "Rebase and merge", value: "rebase" },
|
|
216
|
+
],
|
|
217
|
+
});
|
|
218
|
+
const deleteBranch = await confirm({
|
|
219
|
+
message: "Delete branch after merge?",
|
|
220
|
+
default: true,
|
|
221
|
+
});
|
|
222
|
+
const confirmed = await confirm({
|
|
223
|
+
message: `Are you sure you want to merge PR #${prNumber}?`,
|
|
224
|
+
default: false,
|
|
225
|
+
});
|
|
226
|
+
if (!confirmed) {
|
|
227
|
+
console.log("\n Cancelled.\n");
|
|
228
|
+
await pressEnterToContinue();
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
console.log(`\n Merging PR #${prNumber}...\n`);
|
|
232
|
+
const args = ["pr", "merge", prNumber, `--${mergeMethod}`];
|
|
233
|
+
if (deleteBranch) {
|
|
234
|
+
args.push("--delete-branch");
|
|
235
|
+
}
|
|
236
|
+
const result = runGh(args);
|
|
237
|
+
if (!result.success) {
|
|
238
|
+
console.error("\n Error merging PR:", result.stderr);
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
console.log(" PR merged successfully!\n");
|
|
242
|
+
}
|
|
243
|
+
await pressEnterToContinue();
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
console.error("\n Error merging PR:", error.message);
|
|
247
|
+
await pressEnterToContinue();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async function addComment() {
|
|
251
|
+
try {
|
|
252
|
+
const prNumber = await input({
|
|
253
|
+
message: "PR number:",
|
|
254
|
+
validate: (v) => {
|
|
255
|
+
if (!v)
|
|
256
|
+
return "PR number is required";
|
|
257
|
+
if (isNaN(parseInt(v)))
|
|
258
|
+
return "Must be a number";
|
|
259
|
+
return true;
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
const comment = await input({
|
|
263
|
+
message: "Comment:",
|
|
264
|
+
validate: (v) => (v.length > 0 ? true : "Comment is required"),
|
|
265
|
+
});
|
|
266
|
+
console.log("\n Adding comment...\n");
|
|
267
|
+
const result = runGh(["pr", "comment", prNumber, "--body", comment]);
|
|
268
|
+
if (!result.success) {
|
|
269
|
+
console.error("\n Error adding comment:", result.stderr);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
console.log(" Comment added successfully!\n");
|
|
273
|
+
}
|
|
274
|
+
await pressEnterToContinue();
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
console.error("\n Error adding comment:", error.message);
|
|
278
|
+
await pressEnterToContinue();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
async function reviewPR() {
|
|
282
|
+
try {
|
|
283
|
+
const prNumber = await input({
|
|
284
|
+
message: "PR number to review:",
|
|
285
|
+
validate: (v) => {
|
|
286
|
+
if (!v)
|
|
287
|
+
return "PR number is required";
|
|
288
|
+
if (isNaN(parseInt(v)))
|
|
289
|
+
return "Must be a number";
|
|
290
|
+
return true;
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
const reviewType = await select({
|
|
294
|
+
message: "Review type:",
|
|
295
|
+
choices: [
|
|
296
|
+
{ name: "✅ Approve", value: "approve" },
|
|
297
|
+
{ name: "💬 Comment", value: "comment" },
|
|
298
|
+
{ name: "🔄 Request changes", value: "request-changes" },
|
|
299
|
+
],
|
|
300
|
+
});
|
|
301
|
+
const body = await input({
|
|
302
|
+
message: "Review message (optional):",
|
|
303
|
+
default: "",
|
|
304
|
+
});
|
|
305
|
+
console.log("\n Submitting review...\n");
|
|
306
|
+
const args = ["pr", "review", prNumber, `--${reviewType}`];
|
|
307
|
+
if (body) {
|
|
308
|
+
args.push("--body", body);
|
|
309
|
+
}
|
|
310
|
+
const result = runGh(args);
|
|
311
|
+
if (!result.success) {
|
|
312
|
+
console.error("\n Error submitting review:", result.stderr);
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
console.log(" Review submitted successfully!\n");
|
|
316
|
+
}
|
|
317
|
+
await pressEnterToContinue();
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
console.error("\n Error submitting review:", error.message);
|
|
321
|
+
await pressEnterToContinue();
|
|
322
|
+
}
|
|
323
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive Mode: Preview Module
|
|
3
|
+
*
|
|
4
|
+
* Provides menu-based PR preview deployment management.
|
|
5
|
+
*/
|
|
6
|
+
import { select, input, confirm } from "@inquirer/prompts";
|
|
7
|
+
import { ensureConfig, pressEnterToContinue, truncate, formatDate } from "./utils.js";
|
|
8
|
+
export async function previewMenu() {
|
|
9
|
+
const config = ensureConfig();
|
|
10
|
+
console.log("\n PREVIEW DEPLOYMENTS");
|
|
11
|
+
console.log(" " + "-".repeat(50));
|
|
12
|
+
console.log(" PR preview deployment management");
|
|
13
|
+
console.log("");
|
|
14
|
+
const menuItems = [
|
|
15
|
+
{ name: "📋 List Previews", value: "list" },
|
|
16
|
+
{ name: "🔍 Preview Status", value: "status" },
|
|
17
|
+
{ name: "🚀 Deploy Preview", value: "deploy" },
|
|
18
|
+
{ name: "🗑️ Delete Preview", value: "delete" },
|
|
19
|
+
{ name: "🧹 Cleanup Old", value: "cleanup" },
|
|
20
|
+
{ name: "← Back", value: "back" },
|
|
21
|
+
];
|
|
22
|
+
const choice = await select({
|
|
23
|
+
message: "Preview Action:",
|
|
24
|
+
choices: menuItems,
|
|
25
|
+
});
|
|
26
|
+
switch (choice) {
|
|
27
|
+
case "list":
|
|
28
|
+
await listPreviews(config);
|
|
29
|
+
break;
|
|
30
|
+
case "status":
|
|
31
|
+
await previewStatus(config);
|
|
32
|
+
break;
|
|
33
|
+
case "deploy":
|
|
34
|
+
await deployPreview(config);
|
|
35
|
+
break;
|
|
36
|
+
case "delete":
|
|
37
|
+
await deletePreview(config);
|
|
38
|
+
break;
|
|
39
|
+
case "cleanup":
|
|
40
|
+
await cleanupPreviews(config);
|
|
41
|
+
break;
|
|
42
|
+
case "back":
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function listPreviews(config) {
|
|
47
|
+
try {
|
|
48
|
+
const res = await fetch(`${config.apiUrl}/api/previews`, {
|
|
49
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
50
|
+
});
|
|
51
|
+
if (!res.ok) {
|
|
52
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
53
|
+
await pressEnterToContinue();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const previews = await res.json();
|
|
57
|
+
console.log("\n PREVIEW DEPLOYMENTS");
|
|
58
|
+
console.log(" " + "-".repeat(80));
|
|
59
|
+
if (previews.length === 0) {
|
|
60
|
+
console.log(" No preview deployments found.");
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
console.log(` ${"PR #".padEnd(8)} ${"STATUS".padEnd(12)} ${"URL".padEnd(45)} ${"UPDATED"}`);
|
|
64
|
+
console.log(" " + "-".repeat(80));
|
|
65
|
+
for (const preview of previews) {
|
|
66
|
+
const statusIcon = preview.status === "active" ? "🟢" : preview.status === "building" ? "🔄" : "🔴";
|
|
67
|
+
console.log(` ${String(preview.prNumber).padEnd(8)} ${statusIcon} ${preview.status.padEnd(9)} ${truncate(preview.url, 43).padEnd(45)} ${formatDate(preview.updatedAt)}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
console.log("");
|
|
71
|
+
await pressEnterToContinue();
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
console.error("\n Error fetching previews:", error);
|
|
75
|
+
await pressEnterToContinue();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function previewStatus(config) {
|
|
79
|
+
try {
|
|
80
|
+
const prNumber = await input({
|
|
81
|
+
message: "PR number:",
|
|
82
|
+
validate: (v) => {
|
|
83
|
+
if (!v)
|
|
84
|
+
return "PR number is required";
|
|
85
|
+
if (isNaN(parseInt(v)))
|
|
86
|
+
return "Must be a number";
|
|
87
|
+
return true;
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
const res = await fetch(`${config.apiUrl}/api/previews/pr/${prNumber}`, {
|
|
91
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
92
|
+
});
|
|
93
|
+
if (!res.ok) {
|
|
94
|
+
if (res.status === 404) {
|
|
95
|
+
console.log(`\n No preview found for PR #${prNumber}\n`);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
99
|
+
}
|
|
100
|
+
await pressEnterToContinue();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const preview = await res.json();
|
|
104
|
+
console.log("\n PREVIEW STATUS");
|
|
105
|
+
console.log(" " + "-".repeat(50));
|
|
106
|
+
console.log(` PR: #${preview.prNumber}`);
|
|
107
|
+
console.log(` Status: ${preview.status}`);
|
|
108
|
+
console.log(` URL: ${preview.url}`);
|
|
109
|
+
console.log(` Created: ${formatDate(preview.createdAt)}`);
|
|
110
|
+
console.log(` Updated: ${formatDate(preview.updatedAt)}`);
|
|
111
|
+
console.log("");
|
|
112
|
+
await pressEnterToContinue();
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
console.error("\n Error fetching preview status:", error);
|
|
116
|
+
await pressEnterToContinue();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async function deployPreview(config) {
|
|
120
|
+
try {
|
|
121
|
+
const prNumber = await input({
|
|
122
|
+
message: "PR number to deploy:",
|
|
123
|
+
validate: (v) => {
|
|
124
|
+
if (!v)
|
|
125
|
+
return "PR number is required";
|
|
126
|
+
if (isNaN(parseInt(v)))
|
|
127
|
+
return "Must be a number";
|
|
128
|
+
return true;
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
const confirmed = await confirm({
|
|
132
|
+
message: `Deploy preview for PR #${prNumber}?`,
|
|
133
|
+
default: true,
|
|
134
|
+
});
|
|
135
|
+
if (!confirmed) {
|
|
136
|
+
console.log("\n Cancelled.\n");
|
|
137
|
+
await pressEnterToContinue();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
console.log(`\n Deploying preview for PR #${prNumber}...\n`);
|
|
141
|
+
const res = await fetch(`${config.apiUrl}/api/previews/deploy`, {
|
|
142
|
+
method: "POST",
|
|
143
|
+
headers: {
|
|
144
|
+
"Content-Type": "application/json",
|
|
145
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
146
|
+
},
|
|
147
|
+
body: JSON.stringify({ prNumber: parseInt(prNumber) }),
|
|
148
|
+
});
|
|
149
|
+
if (!res.ok) {
|
|
150
|
+
const error = await res.text();
|
|
151
|
+
console.error(`\n Error: ${error}\n`);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
const data = await res.json();
|
|
155
|
+
console.log(" Preview deployment started!");
|
|
156
|
+
if (data.url) {
|
|
157
|
+
console.log(` URL: ${data.url}`);
|
|
158
|
+
}
|
|
159
|
+
console.log("");
|
|
160
|
+
}
|
|
161
|
+
await pressEnterToContinue();
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
console.error("\n Error deploying preview:", error);
|
|
165
|
+
await pressEnterToContinue();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async function deletePreview(config) {
|
|
169
|
+
try {
|
|
170
|
+
const prNumber = await input({
|
|
171
|
+
message: "PR number to delete preview for:",
|
|
172
|
+
validate: (v) => {
|
|
173
|
+
if (!v)
|
|
174
|
+
return "PR number is required";
|
|
175
|
+
if (isNaN(parseInt(v)))
|
|
176
|
+
return "Must be a number";
|
|
177
|
+
return true;
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
const confirmed = await confirm({
|
|
181
|
+
message: `Delete preview for PR #${prNumber}?`,
|
|
182
|
+
default: false,
|
|
183
|
+
});
|
|
184
|
+
if (!confirmed) {
|
|
185
|
+
console.log("\n Cancelled.\n");
|
|
186
|
+
await pressEnterToContinue();
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
console.log(`\n Deleting preview for PR #${prNumber}...\n`);
|
|
190
|
+
const res = await fetch(`${config.apiUrl}/api/previews/pr/${prNumber}`, {
|
|
191
|
+
method: "DELETE",
|
|
192
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
193
|
+
});
|
|
194
|
+
if (!res.ok) {
|
|
195
|
+
const error = await res.text();
|
|
196
|
+
console.error(`\n Error: ${error}\n`);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
console.log(" Preview deleted successfully!\n");
|
|
200
|
+
}
|
|
201
|
+
await pressEnterToContinue();
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
console.error("\n Error deleting preview:", error);
|
|
205
|
+
await pressEnterToContinue();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
async function cleanupPreviews(config) {
|
|
209
|
+
try {
|
|
210
|
+
const confirmed = await confirm({
|
|
211
|
+
message: "Clean up old preview deployments?",
|
|
212
|
+
default: false,
|
|
213
|
+
});
|
|
214
|
+
if (!confirmed) {
|
|
215
|
+
console.log("\n Cancelled.\n");
|
|
216
|
+
await pressEnterToContinue();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
console.log("\n Cleaning up old previews...\n");
|
|
220
|
+
const res = await fetch(`${config.apiUrl}/api/previews/cleanup`, {
|
|
221
|
+
method: "POST",
|
|
222
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
223
|
+
});
|
|
224
|
+
if (!res.ok) {
|
|
225
|
+
const error = await res.text();
|
|
226
|
+
console.error(`\n Error: ${error}\n`);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
const data = await res.json();
|
|
230
|
+
console.log(` Cleanup complete! Removed: ${data.removed || 0} previews\n`);
|
|
231
|
+
}
|
|
232
|
+
await pressEnterToContinue();
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
console.error("\n Error cleaning up:", error);
|
|
236
|
+
await pressEnterToContinue();
|
|
237
|
+
}
|
|
238
|
+
}
|