@simonfestl/husky-cli 0.3.0 → 0.5.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.
@@ -0,0 +1,289 @@
1
+ import { Command } from "commander";
2
+ import { getConfig } from "./config.js";
3
+ import * as readline from "readline";
4
+ export const processCommand = new Command("process")
5
+ .description("Manage processes");
6
+ // Helper: Ensure API is configured
7
+ function ensureConfig() {
8
+ const config = getConfig();
9
+ if (!config.apiUrl) {
10
+ console.error("Error: API URL not configured. Run: husky config set api-url <url>");
11
+ process.exit(1);
12
+ }
13
+ return config;
14
+ }
15
+ // Helper: Prompt for confirmation
16
+ async function confirm(message) {
17
+ const rl = readline.createInterface({
18
+ input: process.stdin,
19
+ output: process.stdout,
20
+ });
21
+ return new Promise((resolve) => {
22
+ rl.question(`${message} (y/N): `, (answer) => {
23
+ rl.close();
24
+ resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
25
+ });
26
+ });
27
+ }
28
+ // Status labels
29
+ const STATUS_LABELS = {
30
+ active: "Active",
31
+ inactive: "Inactive",
32
+ draft: "Draft",
33
+ archived: "Archived",
34
+ };
35
+ // husky process list
36
+ processCommand
37
+ .command("list")
38
+ .description("List all processes")
39
+ .option("--json", "Output as JSON")
40
+ .option("--status <status>", "Filter by status")
41
+ .action(async (options) => {
42
+ const config = ensureConfig();
43
+ try {
44
+ const url = new URL("/api/processes", config.apiUrl);
45
+ if (options.status) {
46
+ url.searchParams.set("status", options.status);
47
+ }
48
+ const res = await fetch(url.toString(), {
49
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
50
+ });
51
+ if (!res.ok) {
52
+ throw new Error(`API error: ${res.status}`);
53
+ }
54
+ const processes = await res.json();
55
+ if (options.json) {
56
+ console.log(JSON.stringify(processes, null, 2));
57
+ }
58
+ else {
59
+ printProcesses(processes);
60
+ }
61
+ }
62
+ catch (error) {
63
+ console.error("Error fetching processes:", error);
64
+ process.exit(1);
65
+ }
66
+ });
67
+ // husky process create <name>
68
+ processCommand
69
+ .command("create <name>")
70
+ .description("Create a new process")
71
+ .option("-d, --description <desc>", "Process description")
72
+ .option("--status <status>", "Initial status")
73
+ .option("--json", "Output as JSON")
74
+ .action(async (name, options) => {
75
+ const config = ensureConfig();
76
+ try {
77
+ const res = await fetch(`${config.apiUrl}/api/processes`, {
78
+ method: "POST",
79
+ headers: {
80
+ "Content-Type": "application/json",
81
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
82
+ },
83
+ body: JSON.stringify({
84
+ name,
85
+ description: options.description,
86
+ status: options.status,
87
+ }),
88
+ });
89
+ if (!res.ok) {
90
+ const errorData = await res.json().catch(() => ({}));
91
+ throw new Error(errorData.error || `API error: ${res.status}`);
92
+ }
93
+ const processData = await res.json();
94
+ if (options.json) {
95
+ console.log(JSON.stringify(processData, null, 2));
96
+ }
97
+ else {
98
+ console.log(`Created process: ${processData.name}`);
99
+ console.log(` ID: ${processData.id}`);
100
+ console.log(` Status: ${processData.status}`);
101
+ }
102
+ }
103
+ catch (error) {
104
+ console.error("Error creating process:", error);
105
+ process.exit(1);
106
+ }
107
+ });
108
+ // husky process get <id>
109
+ processCommand
110
+ .command("get <id>")
111
+ .description("Get process details")
112
+ .option("--json", "Output as JSON")
113
+ .action(async (id, options) => {
114
+ const config = ensureConfig();
115
+ try {
116
+ const res = await fetch(`${config.apiUrl}/api/processes/${id}`, {
117
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
118
+ });
119
+ if (!res.ok) {
120
+ if (res.status === 404) {
121
+ console.error(`Error: Process ${id} not found`);
122
+ }
123
+ else {
124
+ console.error(`Error: API returned ${res.status}`);
125
+ }
126
+ process.exit(1);
127
+ }
128
+ const processData = await res.json();
129
+ if (options.json) {
130
+ console.log(JSON.stringify(processData, null, 2));
131
+ }
132
+ else {
133
+ printProcessDetail(processData);
134
+ }
135
+ }
136
+ catch (error) {
137
+ console.error("Error fetching process:", error);
138
+ process.exit(1);
139
+ }
140
+ });
141
+ // husky process update <id>
142
+ processCommand
143
+ .command("update <id>")
144
+ .description("Update a process")
145
+ .option("-n, --name <name>", "New name")
146
+ .option("-d, --description <desc>", "New description")
147
+ .option("--status <status>", "New status")
148
+ .option("--json", "Output as JSON")
149
+ .action(async (id, options) => {
150
+ const config = ensureConfig();
151
+ // Build update payload with only changed fields
152
+ const updates = {};
153
+ if (options.name)
154
+ updates.name = options.name;
155
+ if (options.description)
156
+ updates.description = options.description;
157
+ if (options.status)
158
+ updates.status = options.status;
159
+ if (Object.keys(updates).length === 0) {
160
+ console.error("Error: No update options provided.");
161
+ console.log("Use -n/--name, -d/--description, or --status");
162
+ process.exit(1);
163
+ }
164
+ try {
165
+ const res = await fetch(`${config.apiUrl}/api/processes/${id}`, {
166
+ method: "PATCH",
167
+ headers: {
168
+ "Content-Type": "application/json",
169
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
170
+ },
171
+ body: JSON.stringify(updates),
172
+ });
173
+ if (!res.ok) {
174
+ if (res.status === 404) {
175
+ console.error(`Error: Process ${id} not found`);
176
+ }
177
+ else {
178
+ const errorData = await res.json().catch(() => ({}));
179
+ console.error(`Error: ${errorData.error || `API returned ${res.status}`}`);
180
+ }
181
+ process.exit(1);
182
+ }
183
+ const processData = await res.json();
184
+ if (options.json) {
185
+ console.log(JSON.stringify(processData, null, 2));
186
+ }
187
+ else {
188
+ console.log(`Process updated successfully`);
189
+ console.log(` Name: ${processData.name}`);
190
+ console.log(` Status: ${processData.status}`);
191
+ const changedFields = Object.keys(updates).join(", ");
192
+ console.log(` Changed: ${changedFields}`);
193
+ }
194
+ }
195
+ catch (error) {
196
+ console.error("Error updating process:", error);
197
+ process.exit(1);
198
+ }
199
+ });
200
+ // husky process delete <id>
201
+ processCommand
202
+ .command("delete <id>")
203
+ .description("Delete a process")
204
+ .option("--force", "Skip confirmation")
205
+ .action(async (id, options) => {
206
+ const config = ensureConfig();
207
+ // Confirm deletion unless --force is provided
208
+ if (!options.force) {
209
+ // First fetch process details to show what will be deleted
210
+ try {
211
+ const getRes = await fetch(`${config.apiUrl}/api/processes/${id}`, {
212
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
213
+ });
214
+ if (!getRes.ok) {
215
+ if (getRes.status === 404) {
216
+ console.error(`Error: Process ${id} not found`);
217
+ }
218
+ else {
219
+ console.error(`Error: API returned ${getRes.status}`);
220
+ }
221
+ process.exit(1);
222
+ }
223
+ const processData = await getRes.json();
224
+ const confirmed = await confirm(`Delete process "${processData.name}" (${id})?`);
225
+ if (!confirmed) {
226
+ console.log("Deletion cancelled.");
227
+ process.exit(0);
228
+ }
229
+ }
230
+ catch (error) {
231
+ console.error("Error fetching process:", error);
232
+ process.exit(1);
233
+ }
234
+ }
235
+ try {
236
+ const res = await fetch(`${config.apiUrl}/api/processes/${id}`, {
237
+ method: "DELETE",
238
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
239
+ });
240
+ if (!res.ok) {
241
+ if (res.status === 404) {
242
+ console.error(`Error: Process ${id} not found`);
243
+ }
244
+ else {
245
+ console.error(`Error: API returned ${res.status}`);
246
+ }
247
+ process.exit(1);
248
+ }
249
+ console.log(`Process deleted`);
250
+ }
251
+ catch (error) {
252
+ console.error("Error deleting process:", error);
253
+ process.exit(1);
254
+ }
255
+ });
256
+ // ============================================
257
+ // OUTPUT FORMATTERS
258
+ // ============================================
259
+ function printProcesses(processes) {
260
+ if (processes.length === 0) {
261
+ console.log("\n No processes found.");
262
+ console.log(" Create one with: husky process create <name>\n");
263
+ return;
264
+ }
265
+ console.log("\n PROCESSES");
266
+ console.log(" " + "-".repeat(70));
267
+ console.log(` ${"ID".padEnd(24)} ${"NAME".padEnd(30)} ${"STATUS".padEnd(12)}`);
268
+ console.log(" " + "-".repeat(70));
269
+ for (const proc of processes) {
270
+ const statusLabel = STATUS_LABELS[proc.status] || proc.status;
271
+ const truncatedName = proc.name.length > 28 ? proc.name.substring(0, 25) + "..." : proc.name;
272
+ console.log(` ${proc.id.padEnd(24)} ${truncatedName.padEnd(30)} ${statusLabel}`);
273
+ }
274
+ console.log(" " + "-".repeat(70));
275
+ console.log(` Total: ${processes.length} process(es)\n`);
276
+ }
277
+ function printProcessDetail(proc) {
278
+ const statusLabel = STATUS_LABELS[proc.status] || proc.status;
279
+ console.log(`\n Process: ${proc.name}`);
280
+ console.log(" " + "=".repeat(60));
281
+ console.log(` ID: ${proc.id}`);
282
+ console.log(` Status: ${statusLabel}`);
283
+ if (proc.description) {
284
+ console.log(` Description: ${proc.description}`);
285
+ }
286
+ console.log(` Created: ${new Date(proc.createdAt).toLocaleString()}`);
287
+ console.log(` Updated: ${new Date(proc.updatedAt).toLocaleString()}`);
288
+ console.log("");
289
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const projectCommand: Command;