@simonfestl/husky-cli 0.5.0 → 0.5.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 +21 -25
- package/dist/commands/config.d.ts +1 -0
- package/dist/commands/config.js +10 -3
- package/dist/commands/idea.js +9 -7
- package/dist/commands/interactive/changelog.d.ts +1 -0
- package/dist/commands/interactive/changelog.js +398 -0
- package/dist/commands/interactive/departments.d.ts +1 -0
- package/dist/commands/interactive/departments.js +242 -0
- package/dist/commands/interactive/ideas.d.ts +1 -0
- package/dist/commands/interactive/ideas.js +311 -0
- package/dist/commands/interactive/jules-sessions.d.ts +1 -0
- package/dist/commands/interactive/jules-sessions.js +460 -0
- package/dist/commands/interactive/processes.d.ts +1 -0
- package/dist/commands/interactive/processes.js +271 -0
- package/dist/commands/interactive/projects.d.ts +1 -0
- package/dist/commands/interactive/projects.js +297 -0
- package/dist/commands/interactive/roadmaps.d.ts +1 -0
- package/dist/commands/interactive/roadmaps.js +650 -0
- package/dist/commands/interactive/strategy.d.ts +1 -0
- package/dist/commands/interactive/strategy.js +790 -0
- package/dist/commands/interactive/tasks.d.ts +1 -0
- package/dist/commands/interactive/tasks.js +415 -0
- package/dist/commands/interactive/utils.d.ts +15 -0
- package/dist/commands/interactive/utils.js +54 -0
- package/dist/commands/interactive/vm-sessions.d.ts +1 -0
- package/dist/commands/interactive/vm-sessions.js +319 -0
- package/dist/commands/interactive/workflows.d.ts +1 -0
- package/dist/commands/interactive/workflows.js +442 -0
- package/dist/commands/interactive.js +150 -1135
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -251,39 +251,35 @@ npm link
|
|
|
251
251
|
|
|
252
252
|
## Publishing / Release
|
|
253
253
|
|
|
254
|
-
The CLI is automatically published to npm via GitHub Actions using OIDC Trusted Publishing.
|
|
255
|
-
|
|
256
254
|
### Publishing a New Version
|
|
257
255
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
256
|
+
```bash
|
|
257
|
+
cd packages/cli
|
|
258
|
+
|
|
259
|
+
# 1. Bump version
|
|
260
|
+
npm version patch # or minor/major
|
|
263
261
|
|
|
264
|
-
2.
|
|
265
|
-
|
|
266
|
-
git add .
|
|
267
|
-
git commit -m "chore(cli): bump version to x.x.x"
|
|
268
|
-
git push origin main
|
|
269
|
-
```
|
|
262
|
+
# 2. Publish to npm (opens browser for authentication)
|
|
263
|
+
npm publish --access public
|
|
270
264
|
|
|
271
|
-
3.
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
265
|
+
# 3. Commit and push
|
|
266
|
+
git add .
|
|
267
|
+
git commit -m "chore(cli): release vX.X.X"
|
|
268
|
+
git push origin main
|
|
269
|
+
```
|
|
275
270
|
|
|
276
|
-
###
|
|
271
|
+
### Updating on Other Devices
|
|
277
272
|
|
|
278
|
-
|
|
279
|
-
|
|
273
|
+
```bash
|
|
274
|
+
# Update to latest version
|
|
275
|
+
npm update -g @simonfestl/husky-cli
|
|
280
276
|
|
|
281
|
-
|
|
277
|
+
# Or reinstall
|
|
278
|
+
npm install -g @simonfestl/husky-cli
|
|
282
279
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
- Workflow: `publish.yml`
|
|
280
|
+
# Check installed version
|
|
281
|
+
husky --version
|
|
282
|
+
```
|
|
287
283
|
|
|
288
284
|
## Changelog
|
|
289
285
|
|
package/dist/commands/config.js
CHANGED
|
@@ -4,13 +4,14 @@ import { join } from "path";
|
|
|
4
4
|
import { homedir } from "os";
|
|
5
5
|
const CONFIG_DIR = join(homedir(), ".husky");
|
|
6
6
|
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
7
|
-
// API Key validation - must be at least 16 characters
|
|
7
|
+
// API Key validation - must be at least 16 characters, alphanumeric + common key chars (base64, JWT, etc.)
|
|
8
8
|
function validateApiKey(key) {
|
|
9
9
|
if (key.length < 16) {
|
|
10
10
|
return { valid: false, error: "API key must be at least 16 characters long" };
|
|
11
11
|
}
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
// Allow: letters, numbers, dashes, underscores, dots, plus, slash, equals (base64/JWT compatible)
|
|
13
|
+
if (!/^[a-zA-Z0-9_\-\.+/=]+$/.test(key)) {
|
|
14
|
+
return { valid: false, error: "API key contains invalid characters" };
|
|
14
15
|
}
|
|
15
16
|
return { valid: true };
|
|
16
17
|
}
|
|
@@ -45,6 +46,12 @@ function saveConfig(config) {
|
|
|
45
46
|
}
|
|
46
47
|
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
47
48
|
}
|
|
49
|
+
// Helper to set a single config value (used by interactive mode)
|
|
50
|
+
export function setConfig(key, value) {
|
|
51
|
+
const config = getConfig();
|
|
52
|
+
config[key] = value;
|
|
53
|
+
saveConfig(config);
|
|
54
|
+
}
|
|
48
55
|
export const configCommand = new Command("config")
|
|
49
56
|
.description("Manage CLI configuration");
|
|
50
57
|
// husky config set <key> <value>
|
package/dist/commands/idea.js
CHANGED
|
@@ -293,10 +293,12 @@ function printIdeas(ideas) {
|
|
|
293
293
|
console.log(` ${"ID".padEnd(24)} ${"TITLE".padEnd(30)} ${"STATUS".padEnd(12)} CATEGORY`);
|
|
294
294
|
console.log(" " + "─".repeat(70));
|
|
295
295
|
for (const idea of ideas) {
|
|
296
|
-
const statusIcon = getStatusIcon(idea.status);
|
|
297
|
-
const
|
|
296
|
+
const statusIcon = getStatusIcon(idea.status || "draft");
|
|
297
|
+
const title = idea.title || "(untitled)";
|
|
298
|
+
const truncatedTitle = title.length > 28 ? title.substring(0, 25) + "..." : title;
|
|
298
299
|
const category = idea.category || "-";
|
|
299
|
-
|
|
300
|
+
const status = idea.status || "draft";
|
|
301
|
+
console.log(` ${idea.id.padEnd(24)} ${truncatedTitle.padEnd(30)} ${statusIcon} ${status.padEnd(10)} ${category}`);
|
|
300
302
|
}
|
|
301
303
|
// Summary by status
|
|
302
304
|
const draftCount = ideas.filter((i) => i.status === "draft").length;
|
|
@@ -309,10 +311,10 @@ function printIdeas(ideas) {
|
|
|
309
311
|
console.log("");
|
|
310
312
|
}
|
|
311
313
|
function printIdeaDetail(idea) {
|
|
312
|
-
console.log(`\n Idea: ${idea.title}`);
|
|
314
|
+
console.log(`\n Idea: ${idea.title || "(untitled)"}`);
|
|
313
315
|
console.log(" " + "─".repeat(50));
|
|
314
316
|
console.log(` ID: ${idea.id}`);
|
|
315
|
-
console.log(` Status: ${idea.status}`);
|
|
317
|
+
console.log(` Status: ${idea.status || "draft"}`);
|
|
316
318
|
if (idea.category) {
|
|
317
319
|
console.log(` Category: ${idea.category}`);
|
|
318
320
|
}
|
|
@@ -320,8 +322,8 @@ function printIdeaDetail(idea) {
|
|
|
320
322
|
console.log(` Description:`);
|
|
321
323
|
console.log(` ${idea.description}`);
|
|
322
324
|
}
|
|
323
|
-
console.log(` Created: ${new Date(idea.createdAt).toLocaleString()}`);
|
|
324
|
-
console.log(` Updated: ${new Date(idea.updatedAt).toLocaleString()}`);
|
|
325
|
+
console.log(` Created: ${idea.createdAt ? new Date(idea.createdAt).toLocaleString() : "-"}`);
|
|
326
|
+
console.log(` Updated: ${idea.updatedAt ? new Date(idea.updatedAt).toLocaleString() : "-"}`);
|
|
325
327
|
console.log("");
|
|
326
328
|
}
|
|
327
329
|
function getStatusIcon(status) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function changelogMenu(): Promise<void>;
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import { select, input, confirm } from "@inquirer/prompts";
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import { ensureConfig, pressEnterToContinue, truncate, formatDate } from "./utils.js";
|
|
4
|
+
const TYPE_LABELS = {
|
|
5
|
+
feature: "New Features",
|
|
6
|
+
fix: "Bug Fixes",
|
|
7
|
+
breaking: "Breaking Changes",
|
|
8
|
+
docs: "Documentation",
|
|
9
|
+
chore: "Maintenance",
|
|
10
|
+
};
|
|
11
|
+
const TYPE_ICONS = {
|
|
12
|
+
feature: "[NEW]",
|
|
13
|
+
fix: "[FIX]",
|
|
14
|
+
breaking: "[!!!]",
|
|
15
|
+
docs: "[DOC]",
|
|
16
|
+
chore: "[CHG]",
|
|
17
|
+
};
|
|
18
|
+
export async function changelogMenu() {
|
|
19
|
+
const config = ensureConfig();
|
|
20
|
+
const menuItems = [
|
|
21
|
+
{ name: "List changelogs", value: "list" },
|
|
22
|
+
{ name: "View changelog", value: "view" },
|
|
23
|
+
{ name: "Generate changelog", value: "generate" },
|
|
24
|
+
{ name: "Publish changelog", value: "publish" },
|
|
25
|
+
{ name: "Delete changelog", value: "delete" },
|
|
26
|
+
{ name: "Back to main menu", value: "back" },
|
|
27
|
+
];
|
|
28
|
+
const choice = await select({
|
|
29
|
+
message: "Changelog:",
|
|
30
|
+
choices: menuItems,
|
|
31
|
+
});
|
|
32
|
+
switch (choice) {
|
|
33
|
+
case "list":
|
|
34
|
+
await listChangelogs(config);
|
|
35
|
+
break;
|
|
36
|
+
case "view":
|
|
37
|
+
await viewChangelog(config);
|
|
38
|
+
break;
|
|
39
|
+
case "generate":
|
|
40
|
+
await generateChangelog(config);
|
|
41
|
+
break;
|
|
42
|
+
case "publish":
|
|
43
|
+
await publishChangelog(config);
|
|
44
|
+
break;
|
|
45
|
+
case "delete":
|
|
46
|
+
await deleteChangelog(config);
|
|
47
|
+
break;
|
|
48
|
+
case "back":
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function fetchChangelogs(config, projectId) {
|
|
53
|
+
const url = new URL("/api/changelogs", config.apiUrl);
|
|
54
|
+
if (projectId) {
|
|
55
|
+
url.searchParams.set("projectId", projectId);
|
|
56
|
+
}
|
|
57
|
+
const res = await fetch(url.toString(), {
|
|
58
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
59
|
+
});
|
|
60
|
+
if (!res.ok)
|
|
61
|
+
throw new Error(`API returned ${res.status}`);
|
|
62
|
+
return res.json();
|
|
63
|
+
}
|
|
64
|
+
async function fetchProjects(config) {
|
|
65
|
+
const res = await fetch(`${config.apiUrl}/api/projects`, {
|
|
66
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
67
|
+
});
|
|
68
|
+
if (!res.ok)
|
|
69
|
+
throw new Error(`API returned ${res.status}`);
|
|
70
|
+
return res.json();
|
|
71
|
+
}
|
|
72
|
+
async function selectChangelog(config, message) {
|
|
73
|
+
const changelogs = await fetchChangelogs(config);
|
|
74
|
+
if (changelogs.length === 0) {
|
|
75
|
+
console.log("\n No changelogs found.\n");
|
|
76
|
+
await pressEnterToContinue();
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const choices = changelogs.map((c) => ({
|
|
80
|
+
name: `[${c.status}] ${c.version} (${c.entries.length} entries) - ${formatDate(c.releaseDate)}`,
|
|
81
|
+
value: c.id,
|
|
82
|
+
}));
|
|
83
|
+
choices.push({ name: "Cancel", value: "__cancel__" });
|
|
84
|
+
const changelogId = await select({ message, choices });
|
|
85
|
+
if (changelogId === "__cancel__")
|
|
86
|
+
return null;
|
|
87
|
+
// Fetch full changelog
|
|
88
|
+
const res = await fetch(`${config.apiUrl}/api/changelogs/${changelogId}`, {
|
|
89
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
90
|
+
});
|
|
91
|
+
if (!res.ok)
|
|
92
|
+
return null;
|
|
93
|
+
return res.json();
|
|
94
|
+
}
|
|
95
|
+
async function listChangelogs(config) {
|
|
96
|
+
try {
|
|
97
|
+
const changelogs = await fetchChangelogs(config);
|
|
98
|
+
console.log("\n CHANGELOGS");
|
|
99
|
+
console.log(" " + "-".repeat(70));
|
|
100
|
+
if (changelogs.length === 0) {
|
|
101
|
+
console.log(" No changelogs found.");
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
for (const cl of changelogs) {
|
|
105
|
+
const statusIcon = cl.status === "published" ? "[OK]" : "[DRAFT]";
|
|
106
|
+
console.log(` ${statusIcon} ${cl.version.padEnd(15)} ${formatDate(cl.releaseDate).padEnd(12)} ${cl.entries.length} entries`);
|
|
107
|
+
console.log(` ID: ${cl.id}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
console.log("");
|
|
111
|
+
await pressEnterToContinue();
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
console.error("\n Error fetching changelogs:", error);
|
|
115
|
+
await pressEnterToContinue();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function viewChangelog(config) {
|
|
119
|
+
try {
|
|
120
|
+
const changelog = await selectChangelog(config, "Select changelog to view:");
|
|
121
|
+
if (!changelog)
|
|
122
|
+
return;
|
|
123
|
+
console.log(`\n Changelog: ${changelog.version}`);
|
|
124
|
+
console.log(" " + "-".repeat(60));
|
|
125
|
+
console.log(` ID: ${changelog.id}`);
|
|
126
|
+
console.log(` Status: ${changelog.status}`);
|
|
127
|
+
console.log(` Date: ${formatDate(changelog.releaseDate)}`);
|
|
128
|
+
console.log(` Entries: ${changelog.entries.length}`);
|
|
129
|
+
if (changelog.entries.length > 0) {
|
|
130
|
+
// Group by type
|
|
131
|
+
const byType = {};
|
|
132
|
+
for (const entry of changelog.entries) {
|
|
133
|
+
if (!byType[entry.type]) {
|
|
134
|
+
byType[entry.type] = [];
|
|
135
|
+
}
|
|
136
|
+
byType[entry.type].push(entry);
|
|
137
|
+
}
|
|
138
|
+
console.log("\n Changes:");
|
|
139
|
+
const typeOrder = ["breaking", "feature", "fix", "docs", "chore"];
|
|
140
|
+
for (const type of typeOrder) {
|
|
141
|
+
const entries = byType[type];
|
|
142
|
+
if (entries && entries.length > 0) {
|
|
143
|
+
const icon = TYPE_ICONS[type] || "[?]";
|
|
144
|
+
const label = TYPE_LABELS[type] || type;
|
|
145
|
+
console.log(`\n ${icon} ${label}`);
|
|
146
|
+
for (const entry of entries) {
|
|
147
|
+
const prStr = entry.prNumber ? ` (#${entry.prNumber})` : "";
|
|
148
|
+
console.log(` - ${truncate(entry.title, 55)}${prStr}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
console.log("");
|
|
154
|
+
await pressEnterToContinue();
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
console.error("\n Error viewing changelog:", error);
|
|
158
|
+
await pressEnterToContinue();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async function generateChangelog(config) {
|
|
162
|
+
try {
|
|
163
|
+
// Select project
|
|
164
|
+
const projects = await fetchProjects(config);
|
|
165
|
+
if (projects.length === 0) {
|
|
166
|
+
console.log("\n No projects found. Create a project first.\n");
|
|
167
|
+
await pressEnterToContinue();
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const projectChoices = projects.map((p) => ({
|
|
171
|
+
name: p.name,
|
|
172
|
+
value: p.id,
|
|
173
|
+
}));
|
|
174
|
+
projectChoices.push({ name: "Cancel", value: "__cancel__" });
|
|
175
|
+
const projectId = await select({ message: "Select project:", choices: projectChoices });
|
|
176
|
+
if (projectId === "__cancel__")
|
|
177
|
+
return;
|
|
178
|
+
const version = await input({
|
|
179
|
+
message: "Version (e.g., 1.0.0):",
|
|
180
|
+
validate: (v) => (v.length > 0 ? true : "Version required"),
|
|
181
|
+
});
|
|
182
|
+
const since = await input({
|
|
183
|
+
message: "Git ref to start from (tag, commit, or empty for last 50 commits):",
|
|
184
|
+
});
|
|
185
|
+
const until = await input({
|
|
186
|
+
message: "Git ref to end at (default: HEAD):",
|
|
187
|
+
default: "HEAD",
|
|
188
|
+
});
|
|
189
|
+
console.log("\n Collecting git commits...\n");
|
|
190
|
+
const commits = getGitCommits(since || undefined, until);
|
|
191
|
+
if (commits.length === 0) {
|
|
192
|
+
console.log(" No commits found in the specified range.\n");
|
|
193
|
+
await pressEnterToContinue();
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
console.log(` Found ${commits.length} commits.`);
|
|
197
|
+
const proceed = await confirm({
|
|
198
|
+
message: "Generate changelog with AI?",
|
|
199
|
+
default: true,
|
|
200
|
+
});
|
|
201
|
+
if (!proceed) {
|
|
202
|
+
console.log("\n Cancelled.\n");
|
|
203
|
+
await pressEnterToContinue();
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
console.log("\n Generating changelog with AI... This may take a moment.\n");
|
|
207
|
+
const res = await fetch(`${config.apiUrl}/api/changelogs/generate`, {
|
|
208
|
+
method: "POST",
|
|
209
|
+
headers: {
|
|
210
|
+
"Content-Type": "application/json",
|
|
211
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
212
|
+
},
|
|
213
|
+
body: JSON.stringify({
|
|
214
|
+
projectId,
|
|
215
|
+
version,
|
|
216
|
+
since: since || undefined,
|
|
217
|
+
until,
|
|
218
|
+
commits,
|
|
219
|
+
}),
|
|
220
|
+
});
|
|
221
|
+
if (!res.ok) {
|
|
222
|
+
const error = await res.json().catch(() => ({}));
|
|
223
|
+
console.error(`\n Error: ${error.error || `API returned ${res.status}`}\n`);
|
|
224
|
+
await pressEnterToContinue();
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const data = await res.json();
|
|
228
|
+
const changelog = data.changelog;
|
|
229
|
+
console.log(` ✓ Changelog ${changelog.version} generated!`);
|
|
230
|
+
console.log(` ID: ${changelog.id}`);
|
|
231
|
+
console.log(` Entries: ${changelog.entries.length}`);
|
|
232
|
+
console.log(` Status: ${changelog.status}`);
|
|
233
|
+
if (changelog.entries.length > 0) {
|
|
234
|
+
console.log("\n Entries:");
|
|
235
|
+
for (const entry of changelog.entries.slice(0, 5)) {
|
|
236
|
+
const icon = TYPE_ICONS[entry.type] || "[?]";
|
|
237
|
+
console.log(` ${icon} ${truncate(entry.title, 50)}`);
|
|
238
|
+
}
|
|
239
|
+
if (changelog.entries.length > 5) {
|
|
240
|
+
console.log(` ... and ${changelog.entries.length - 5} more`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
console.log("");
|
|
244
|
+
await pressEnterToContinue();
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
console.error("\n Error generating changelog:", error);
|
|
248
|
+
await pressEnterToContinue();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
async function publishChangelog(config) {
|
|
252
|
+
try {
|
|
253
|
+
const changelogs = await fetchChangelogs(config);
|
|
254
|
+
const draftChangelogs = changelogs.filter((c) => c.status === "draft");
|
|
255
|
+
if (draftChangelogs.length === 0) {
|
|
256
|
+
console.log("\n No draft changelogs to publish.\n");
|
|
257
|
+
await pressEnterToContinue();
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const choices = draftChangelogs.map((c) => ({
|
|
261
|
+
name: `${c.version} (${c.entries.length} entries)`,
|
|
262
|
+
value: c.id,
|
|
263
|
+
}));
|
|
264
|
+
choices.push({ name: "Cancel", value: "__cancel__" });
|
|
265
|
+
const changelogId = await select({ message: "Select changelog to publish:", choices });
|
|
266
|
+
if (changelogId === "__cancel__")
|
|
267
|
+
return;
|
|
268
|
+
const changelog = draftChangelogs.find((c) => c.id === changelogId);
|
|
269
|
+
const confirmed = await confirm({
|
|
270
|
+
message: `Publish changelog ${changelog?.version}?`,
|
|
271
|
+
default: true,
|
|
272
|
+
});
|
|
273
|
+
if (!confirmed) {
|
|
274
|
+
console.log("\n Cancelled.\n");
|
|
275
|
+
await pressEnterToContinue();
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const res = await fetch(`${config.apiUrl}/api/changelogs/${changelogId}`, {
|
|
279
|
+
method: "PATCH",
|
|
280
|
+
headers: {
|
|
281
|
+
"Content-Type": "application/json",
|
|
282
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
283
|
+
},
|
|
284
|
+
body: JSON.stringify({
|
|
285
|
+
status: "published",
|
|
286
|
+
releaseDate: new Date().toISOString(),
|
|
287
|
+
}),
|
|
288
|
+
});
|
|
289
|
+
if (!res.ok) {
|
|
290
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
291
|
+
await pressEnterToContinue();
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
console.log(`\n ✓ Changelog ${changelog?.version} published!\n`);
|
|
295
|
+
await pressEnterToContinue();
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
console.error("\n Error publishing changelog:", error);
|
|
299
|
+
await pressEnterToContinue();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
async function deleteChangelog(config) {
|
|
303
|
+
try {
|
|
304
|
+
const changelog = await selectChangelog(config, "Select changelog to delete:");
|
|
305
|
+
if (!changelog)
|
|
306
|
+
return;
|
|
307
|
+
const confirmed = await confirm({
|
|
308
|
+
message: `Delete changelog ${changelog.version}? This cannot be undone.`,
|
|
309
|
+
default: false,
|
|
310
|
+
});
|
|
311
|
+
if (!confirmed) {
|
|
312
|
+
console.log("\n Cancelled.\n");
|
|
313
|
+
await pressEnterToContinue();
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const res = await fetch(`${config.apiUrl}/api/changelogs/${changelog.id}`, {
|
|
317
|
+
method: "DELETE",
|
|
318
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
319
|
+
});
|
|
320
|
+
if (!res.ok) {
|
|
321
|
+
console.error(`\n Error: API returned ${res.status}\n`);
|
|
322
|
+
await pressEnterToContinue();
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
console.log(`\n ✓ Changelog deleted.\n`);
|
|
326
|
+
await pressEnterToContinue();
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
console.error("\n Error deleting changelog:", error);
|
|
330
|
+
await pressEnterToContinue();
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
// Git helpers
|
|
334
|
+
function getGitCommits(since, until) {
|
|
335
|
+
try {
|
|
336
|
+
let range = "";
|
|
337
|
+
if (since && until) {
|
|
338
|
+
range = `${since}..${until}`;
|
|
339
|
+
}
|
|
340
|
+
else if (since) {
|
|
341
|
+
range = `${since}..HEAD`;
|
|
342
|
+
}
|
|
343
|
+
else if (until) {
|
|
344
|
+
range = until;
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
range = "HEAD~50..HEAD";
|
|
348
|
+
}
|
|
349
|
+
const format = "%H|%h|%s|%b|%an|%aI";
|
|
350
|
+
const output = execSync(`git log ${range} --format="${format}" --no-merges`, { encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 });
|
|
351
|
+
if (!output.trim()) {
|
|
352
|
+
return [];
|
|
353
|
+
}
|
|
354
|
+
const commits = [];
|
|
355
|
+
const lines = output.trim().split("\n");
|
|
356
|
+
let currentCommit = [];
|
|
357
|
+
for (const line of lines) {
|
|
358
|
+
if (/^[a-f0-9]{40}\|/.test(line)) {
|
|
359
|
+
if (currentCommit.length > 0) {
|
|
360
|
+
parseCommitLine(currentCommit.join("\n"), commits);
|
|
361
|
+
}
|
|
362
|
+
currentCommit = [line];
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
currentCommit.push(line);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (currentCommit.length > 0) {
|
|
369
|
+
parseCommitLine(currentCommit.join("\n"), commits);
|
|
370
|
+
}
|
|
371
|
+
return commits;
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
return [];
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
function parseCommitLine(line, commits) {
|
|
378
|
+
const parts = line.split("|");
|
|
379
|
+
if (parts.length >= 6) {
|
|
380
|
+
const hash = parts[0];
|
|
381
|
+
const shortHash = parts[1];
|
|
382
|
+
const message = parts[2];
|
|
383
|
+
const body = parts[3];
|
|
384
|
+
const author = parts[4];
|
|
385
|
+
const date = parts[5];
|
|
386
|
+
const prMatch = message.match(/\(#(\d+)\)/);
|
|
387
|
+
const prNumber = prMatch ? prMatch[1] : undefined;
|
|
388
|
+
commits.push({
|
|
389
|
+
hash,
|
|
390
|
+
shortHash,
|
|
391
|
+
message,
|
|
392
|
+
body,
|
|
393
|
+
author,
|
|
394
|
+
date,
|
|
395
|
+
prNumber,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function departmentsMenu(): Promise<void>;
|