@infomiho/buzz-cli 0.3.0 → 0.4.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.
Files changed (2) hide show
  1. package/dist/cli.mjs +296 -280
  2. package/package.json +3 -1
package/dist/cli.mjs CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { program } from "commander";
3
3
  import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
4
- import { homedir } from "node:os";
5
4
  import { join } from "node:path";
5
+ import { homedir } from "node:os";
6
+ import cliProgress from "cli-progress";
6
7
 
7
- //#region src/cli.ts
8
+ //#region src/lib.ts
8
9
  const CONFIG_PATH = join(homedir(), ".buzz.config.json");
9
10
  const DEFAULT_SERVER = "http://localhost:8080";
10
11
  function loadConfig() {
@@ -22,7 +23,7 @@ function getOptions() {
22
23
  const config = loadConfig();
23
24
  const opts = program.opts();
24
25
  return {
25
- server: opts.server || config.server || DEFAULT_SERVER,
26
+ server: opts.server || process.env.BUZZ_SERVER || config.server || DEFAULT_SERVER,
26
27
  token: opts.token || process.env.BUZZ_TOKEN || config.token
27
28
  };
28
29
  }
@@ -35,7 +36,49 @@ function authHeaders(token) {
35
36
  if (token) return { Authorization: `Bearer ${token}` };
36
37
  return {};
37
38
  }
38
- async function createZipBuffer(directory) {
39
+ var ApiError = class extends Error {
40
+ constructor(message, status, code) {
41
+ super(message);
42
+ this.status = status;
43
+ this.code = code;
44
+ this.name = "ApiError";
45
+ }
46
+ };
47
+ var CliError = class extends Error {
48
+ constructor(message, tip) {
49
+ super(message);
50
+ this.tip = tip;
51
+ this.name = "CliError";
52
+ }
53
+ };
54
+ function handleError(error) {
55
+ if (error instanceof CliError) {
56
+ console.error(`Error: ${error.message}`);
57
+ if (error.tip) console.error(`Tip: ${error.tip}`);
58
+ } else if (error instanceof Error) console.error(`Error: ${error.message}`);
59
+ else console.error(`Error: ${String(error ?? "Unknown error")}`);
60
+ process.exitCode = 1;
61
+ }
62
+ async function apiRequest(path, options = {}, { requireAuth = true } = {}) {
63
+ const opts = getOptions();
64
+ if (requireAuth && !opts.token) throw new CliError("Not authenticated", "Run 'buzz login' first");
65
+ let response;
66
+ try {
67
+ response = await fetch(`${opts.server}${path}`, {
68
+ ...options,
69
+ headers: {
70
+ ...authHeaders(opts.token),
71
+ ...options.headers
72
+ }
73
+ });
74
+ } catch (error) {
75
+ throw new CliError(`Could not connect to server - ${error instanceof Error ? error.message : error}`);
76
+ }
77
+ if (response.status === 401) throw new CliError("Session expired", "Run 'buzz login' to re-authenticate");
78
+ if (response.status === 403) throw new ApiError((await response.json()).error || "Permission denied", 403);
79
+ return response;
80
+ }
81
+ async function createZipBuffer(directory, callbacks) {
39
82
  const archiver = await import("archiver");
40
83
  return new Promise((resolve, reject) => {
41
84
  const archive = archiver.default("zip", { zlib: { level: 9 } });
@@ -43,28 +86,96 @@ async function createZipBuffer(directory) {
43
86
  archive.on("data", (chunk) => chunks.push(chunk));
44
87
  archive.on("end", () => resolve(Buffer.concat(chunks)));
45
88
  archive.on("error", reject);
89
+ archive.on("progress", (progress) => {
90
+ callbacks?.onProgress?.(progress.entries.processed, progress.entries.total);
91
+ });
46
92
  archive.directory(directory, false);
47
93
  archive.finalize();
48
94
  });
49
95
  }
96
+ function isCI() {
97
+ return !process.stdout.isTTY || !!process.env.CI;
98
+ }
99
+ function createProgressBar(task) {
100
+ const ci = isCI();
101
+ return new cliProgress.SingleBar({
102
+ format: `${task} [{bar}] {percentage}% | {value}/{total} files`,
103
+ barCompleteChar: "█",
104
+ barIncompleteChar: "░",
105
+ barsize: 20,
106
+ hideCursor: true,
107
+ clearOnComplete: false,
108
+ noTTYOutput: ci,
109
+ notTTYSchedule: ci ? 2e3 : 100
110
+ });
111
+ }
112
+ function createSpinner(message) {
113
+ const ci = isCI();
114
+ const frames = [
115
+ "⠋",
116
+ "⠙",
117
+ "⠹",
118
+ "⠸",
119
+ "⠼",
120
+ "⠴",
121
+ "⠦",
122
+ "⠧",
123
+ "⠇",
124
+ "⠏"
125
+ ];
126
+ let frameIndex = 0;
127
+ let interval = null;
128
+ return {
129
+ start: () => {
130
+ if (ci) {
131
+ console.log(`${message}...`);
132
+ return;
133
+ }
134
+ process.stdout.write(`${frames[0]} ${message}`);
135
+ interval = setInterval(() => {
136
+ frameIndex = (frameIndex + 1) % frames.length;
137
+ process.stdout.write(`\r${frames[frameIndex]} ${message}`);
138
+ }, 80);
139
+ },
140
+ stop: (finalMessage) => {
141
+ if (interval) {
142
+ clearInterval(interval);
143
+ process.stdout.write(`\r\x1b[K${finalMessage}\n`);
144
+ } else if (ci) console.log(finalMessage);
145
+ }
146
+ };
147
+ }
148
+
149
+ //#endregion
150
+ //#region src/commands/deploy.ts
50
151
  async function deploy(directory, subdomain) {
51
152
  const options = getOptions();
52
- if (!options.token) {
53
- console.error("Error: Not authenticated. Run 'buzz login' first");
54
- process.exit(1);
55
- }
56
- if (!statSync(directory).isDirectory()) {
57
- console.error(`Error: '${directory}' is not a directory`);
58
- process.exit(1);
59
- }
153
+ if (!options.token) throw new CliError("Not authenticated", "Run 'buzz login' first");
154
+ if (!existsSync(directory)) throw new CliError(`'${directory}' does not exist`);
155
+ if (!statSync(directory).isDirectory()) throw new CliError(`'${directory}' is not a directory`);
60
156
  const cwdCnamePath = join(process.cwd(), "CNAME");
61
157
  if (!subdomain && existsSync(cwdCnamePath)) subdomain = readFileSync(cwdCnamePath, "utf-8").trim();
62
158
  else if (!subdomain) {
63
159
  const dirCnamePath = join(directory, "CNAME");
64
160
  if (existsSync(dirCnamePath)) subdomain = readFileSync(dirCnamePath, "utf-8").trim();
65
161
  }
66
- console.log(`Zipping ${directory}...`);
67
- const zipBuffer = await createZipBuffer(directory);
162
+ const progressBar = createProgressBar("Zipping");
163
+ let progressStarted = false;
164
+ let zipBuffer;
165
+ try {
166
+ zipBuffer = await createZipBuffer(directory, { onProgress: (processed, total) => {
167
+ if (!progressStarted && total > 0) {
168
+ progressBar.start(total, 0);
169
+ progressStarted = true;
170
+ }
171
+ if (progressStarted) progressBar.update(processed);
172
+ } });
173
+ } finally {
174
+ if (progressStarted) progressBar.stop();
175
+ }
176
+ console.log(`Compressed to ${formatSize(zipBuffer.length)}`);
177
+ const uploadSpinner = createSpinner("Uploading");
178
+ uploadSpinner.start();
68
179
  const boundary = "----BuzzFormBoundary" + Math.random().toString(36).slice(2);
69
180
  const header = `--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="site.zip"\r\nContent-Type: application/zip\r\n\r\n`;
70
181
  const footer = `\r\n--${boundary}--\r\n`;
@@ -78,96 +189,72 @@ async function deploy(directory, subdomain) {
78
189
  ...authHeaders(options.token)
79
190
  };
80
191
  if (subdomain) headers["x-subdomain"] = subdomain;
192
+ let response;
81
193
  try {
82
- const response = await fetch(`${options.server}/deploy`, {
194
+ response = await fetch(`${options.server}/deploy`, {
83
195
  method: "POST",
84
196
  headers,
85
197
  body
86
198
  });
87
- const data = await response.json();
88
- if (response.ok) {
89
- console.log(`Deployed to ${data.url}`);
90
- const deployedSubdomain = new URL(data.url).hostname.split(".")[0];
91
- writeFileSync(cwdCnamePath, deployedSubdomain + "\n");
92
- } else if (response.status === 401) {
93
- console.error("Error: Not authenticated. Run 'buzz login' first");
94
- process.exit(1);
95
- } else if (response.status === 403) {
96
- console.error(`Error: ${data.error}`);
97
- if (data.error?.includes("owned by another user")) console.error("Tip: Choose a different subdomain with --subdomain <name>");
98
- process.exit(1);
99
- } else {
100
- console.error(`Error: ${data.error || "Unknown error"}`);
101
- process.exit(1);
102
- }
103
199
  } catch (error) {
104
- console.error(`Error: Could not connect to server - ${error instanceof Error ? error.message : error}`);
105
- process.exit(1);
200
+ uploadSpinner.stop("✗ Upload failed");
201
+ throw new CliError(`Could not connect to server - ${error instanceof Error ? error.message : error}`);
202
+ }
203
+ const data = await response.json();
204
+ if (response.ok) {
205
+ uploadSpinner.stop("✓ Uploaded");
206
+ console.log(`Deployed to ${data.url}`);
207
+ const deployedSubdomain = new URL(data.url).hostname.split(".")[0];
208
+ writeFileSync(cwdCnamePath, deployedSubdomain + "\n");
209
+ return;
210
+ }
211
+ uploadSpinner.stop("✗ Upload failed");
212
+ if (response.status === 401) throw new CliError("Not authenticated", "Run 'buzz login' first");
213
+ if (response.status === 403) {
214
+ const tip = data.error?.includes("owned by another user") ? "Choose a different subdomain with --subdomain <name>" : void 0;
215
+ throw new CliError(data.error, tip);
106
216
  }
217
+ throw new CliError(data.error || "Unknown error");
107
218
  }
219
+ function registerDeployCommand(program$1) {
220
+ program$1.command("deploy <directory>").description("Deploy a directory to the server").option("--subdomain <name>", "Subdomain for the site").action((directory, cmdOptions) => deploy(directory, cmdOptions.subdomain));
221
+ }
222
+
223
+ //#endregion
224
+ //#region src/commands/list.ts
108
225
  async function list() {
109
- const options = getOptions();
110
- if (!options.token) {
111
- console.error("Error: Not authenticated. Run 'buzz login' first");
112
- process.exit(1);
226
+ const sites = await (await apiRequest("/sites")).json();
227
+ if (sites.length === 0) {
228
+ console.log("No sites deployed");
229
+ return;
113
230
  }
114
- try {
115
- const response = await fetch(`${options.server}/sites`, { headers: authHeaders(options.token) });
116
- if (response.status === 401) {
117
- console.error("Error: Session expired. Run 'buzz login' to re-authenticate");
118
- process.exit(1);
119
- }
120
- if (response.status === 403) {
121
- console.error("Error: Deploy tokens cannot list sites. Use 'buzz login' for a session token.");
122
- process.exit(1);
123
- }
124
- const sites = await response.json();
125
- if (sites.length === 0) {
126
- console.log("No sites deployed");
127
- return;
128
- }
129
- console.log(`${"NAME".padEnd(24)} ${"CREATED".padEnd(20)} ${"SIZE".padEnd(10)}`);
130
- for (const site of sites) {
131
- const created = site.created.slice(0, 19).replace("T", " ");
132
- console.log(`${site.name.padEnd(24)} ${created.padEnd(20)} ${formatSize(site.size_bytes).padEnd(10)}`);
133
- }
134
- } catch (error) {
135
- console.error(`Error: Could not connect to server - ${error instanceof Error ? error.message : error}`);
136
- process.exit(1);
231
+ console.log(`${"NAME".padEnd(24)} ${"CREATED".padEnd(20)} ${"SIZE".padEnd(10)}`);
232
+ for (const site of sites) {
233
+ const created = site.created.slice(0, 19).replace("T", " ");
234
+ console.log(`${site.name.padEnd(24)} ${created.padEnd(20)} ${formatSize(site.size_bytes).padEnd(10)}`);
137
235
  }
138
236
  }
237
+ function registerListCommand(program$1) {
238
+ program$1.command("list").description("List all deployed sites").action(list);
239
+ }
240
+
241
+ //#endregion
242
+ //#region src/commands/delete.ts
139
243
  async function deleteSite(subdomain) {
140
- const options = getOptions();
141
- if (!options.token) {
142
- console.error("Error: Not authenticated. Run 'buzz login' first");
143
- process.exit(1);
144
- }
145
- try {
146
- const response = await fetch(`${options.server}/sites/${subdomain}`, {
147
- method: "DELETE",
148
- headers: authHeaders(options.token)
149
- });
150
- if (response.status === 204) console.log(`Deleted ${subdomain}`);
151
- else if (response.status === 401) {
152
- console.error("Error: Session expired. Run 'buzz login' to re-authenticate");
153
- process.exit(1);
154
- } else if (response.status === 403) {
155
- const data = await response.json();
156
- console.error(`Error: ${data.error || "You don't have permission to delete this site"}`);
157
- process.exit(1);
158
- } else if (response.status === 404) {
159
- console.error(`Error: Site '${subdomain}' not found`);
160
- process.exit(1);
161
- } else {
162
- const data = await response.json();
163
- console.error(`Error: ${data.error || "Unknown error"}`);
164
- process.exit(1);
165
- }
166
- } catch (error) {
167
- console.error(`Error: Could not connect to server - ${error instanceof Error ? error.message : error}`);
168
- process.exit(1);
244
+ const response = await apiRequest(`/sites/${subdomain}`, { method: "DELETE" });
245
+ if (response.status === 204) {
246
+ console.log(`Deleted ${subdomain}`);
247
+ return;
169
248
  }
249
+ if (response.status === 404) throw new CliError(`Site '${subdomain}' not found`);
250
+ throw new CliError((await response.json()).error || "Unknown error");
251
+ }
252
+ function registerDeleteCommand(program$1) {
253
+ program$1.command("delete <subdomain>").description("Delete a deployed site").action(deleteSite);
170
254
  }
255
+
256
+ //#endregion
257
+ //#region src/commands/config.ts
171
258
  function configCommand(key, value) {
172
259
  const config = loadConfig();
173
260
  if (!key) {
@@ -188,53 +275,71 @@ function configCommand(key, value) {
188
275
  config.server = value;
189
276
  saveConfig(config);
190
277
  console.log(`Server set to ${value}`);
191
- } else {
192
- console.error("Usage: buzz config server <url>");
193
- console.error("Use 'buzz login' to authenticate");
194
- process.exit(1);
278
+ } else throw new CliError("Invalid config command", "Usage: buzz config server <url>");
279
+ }
280
+ function registerConfigCommand(program$1) {
281
+ program$1.command("config [key] [value]").description("View or set configuration (server)").action(configCommand);
282
+ }
283
+
284
+ //#endregion
285
+ //#region src/commands/url.ts
286
+ function url() {
287
+ const cnamePath = join(process.cwd(), "CNAME");
288
+ if (!existsSync(cnamePath)) throw new CliError("No CNAME file found", "Deploy first with: buzz deploy .");
289
+ const subdomain = readFileSync(cnamePath, "utf-8").trim();
290
+ const server = loadConfig().server || DEFAULT_SERVER;
291
+ try {
292
+ const host = new URL(server).hostname;
293
+ console.log(`https://${subdomain}.${host}`);
294
+ } catch {
295
+ console.log(`http://${subdomain}.localhost:8080`);
195
296
  }
196
297
  }
298
+ function registerUrlCommand(program$1) {
299
+ program$1.command("url").description("Show the URL for the current directory").action(url);
300
+ }
301
+
302
+ //#endregion
303
+ //#region src/commands/auth.ts
197
304
  async function login() {
198
305
  const options = getOptions();
306
+ let deviceResponse;
199
307
  try {
200
- const deviceResponse = await fetch(`${options.server}/auth/device`, { method: "POST" });
201
- if (!deviceResponse.ok) {
202
- const data = await deviceResponse.json();
203
- console.error(`Error: ${data.error || "Failed to start login"}`);
204
- process.exit(1);
205
- }
206
- const deviceData = await deviceResponse.json();
207
- console.log(`\nVisit: ${deviceData.verification_uri}`);
208
- console.log(`Enter code: ${deviceData.user_code}\n`);
209
- console.log("Waiting for authorization...");
210
- const interval = (deviceData.interval || 5) * 1e3;
211
- const maxAttempts = Math.ceil((deviceData.expires_in || 900) / (deviceData.interval || 5));
212
- for (let i = 0; i < maxAttempts; i++) {
213
- await new Promise((resolve) => setTimeout(resolve, interval));
214
- const pollData = await (await fetch(`${options.server}/auth/device/poll`, {
308
+ deviceResponse = await fetch(`${options.server}/auth/device`, { method: "POST" });
309
+ } catch (error) {
310
+ throw new CliError(`Could not connect to server - ${error instanceof Error ? error.message : error}`);
311
+ }
312
+ if (!deviceResponse.ok) throw new CliError((await deviceResponse.json()).error || "Failed to start login");
313
+ const deviceData = await deviceResponse.json();
314
+ console.log(`\nVisit: ${deviceData.verification_uri}`);
315
+ console.log(`Enter code: ${deviceData.user_code}\n`);
316
+ console.log("Waiting for authorization...");
317
+ const interval = (deviceData.interval || 5) * 1e3;
318
+ const maxAttempts = Math.ceil((deviceData.expires_in || 900) / (deviceData.interval || 5));
319
+ for (let i = 0; i < maxAttempts; i++) {
320
+ await new Promise((resolve) => setTimeout(resolve, interval));
321
+ let pollResponse;
322
+ try {
323
+ pollResponse = await fetch(`${options.server}/auth/device/poll`, {
215
324
  method: "POST",
216
325
  headers: { "Content-Type": "application/json" },
217
326
  body: JSON.stringify({ device_code: deviceData.device_code })
218
- })).json();
219
- if (pollData.status === "pending") continue;
220
- if (pollData.error) {
221
- console.error(`\nError: ${pollData.error}`);
222
- process.exit(1);
223
- }
224
- if (pollData.status === "complete") {
225
- const config = loadConfig();
226
- config.token = pollData.token;
227
- saveConfig(config);
228
- console.log(`\nLogged in as ${pollData.user.login}`);
229
- return;
230
- }
327
+ });
328
+ } catch (error) {
329
+ throw new CliError(`Could not connect to server - ${error instanceof Error ? error.message : error}`);
330
+ }
331
+ const pollData = await pollResponse.json();
332
+ if (pollData.status === "pending") continue;
333
+ if (pollData.error) throw new CliError(pollData.error);
334
+ if (pollData.status === "complete") {
335
+ const config = loadConfig();
336
+ config.token = pollData.token;
337
+ saveConfig(config);
338
+ console.log(`\nLogged in as ${pollData.user.login}`);
339
+ return;
231
340
  }
232
- console.error("\nLogin timed out");
233
- process.exit(1);
234
- } catch (error) {
235
- console.error(`Error: Could not connect to server - ${error instanceof Error ? error.message : error}`);
236
- process.exit(1);
237
341
  }
342
+ throw new CliError("Login timed out");
238
343
  }
239
344
  async function logout() {
240
345
  const options = getOptions();
@@ -254,171 +359,82 @@ async function logout() {
254
359
  console.log("Logged out");
255
360
  }
256
361
  async function whoami() {
257
- const options = getOptions();
258
- if (!options.token) {
259
- console.log("Not logged in");
260
- console.log("Run 'buzz login' to authenticate");
261
- process.exit(1);
262
- }
263
- try {
264
- const response = await fetch(`${options.server}/auth/me`, { headers: authHeaders(options.token) });
265
- if (response.status === 401) {
266
- console.error("Session expired. Run 'buzz login' to re-authenticate");
267
- process.exit(1);
268
- }
269
- if (!response.ok) {
270
- const data = await response.json();
271
- console.error(`Error: ${data.error || "Unknown error"}`);
272
- process.exit(1);
273
- }
274
- const user = await response.json();
275
- console.log(`Logged in as ${user.login}${user.name ? ` (${user.name})` : ""}`);
276
- } catch (error) {
277
- console.error(`Error: Could not connect to server - ${error instanceof Error ? error.message : error}`);
278
- process.exit(1);
279
- }
362
+ const user = await (await apiRequest("/auth/me")).json();
363
+ console.log(`Logged in as ${user.login}${user.name ? ` (${user.name})` : ""}`);
364
+ }
365
+ function registerAuthCommands(program$1) {
366
+ program$1.command("login").description("Login with GitHub OAuth").action(login);
367
+ program$1.command("logout").description("Logout and clear session").action(logout);
368
+ program$1.command("whoami").description("Show current logged-in user").action(whoami);
280
369
  }
370
+
371
+ //#endregion
372
+ //#region src/commands/tokens.ts
281
373
  async function listTokens() {
282
- const options = getOptions();
283
- if (!options.token) {
284
- console.error("Not logged in. Run 'buzz login' first");
285
- process.exit(1);
374
+ const tokens = await (await apiRequest("/tokens")).json();
375
+ if (tokens.length === 0) {
376
+ console.log("No deployment tokens");
377
+ return;
286
378
  }
287
- try {
288
- const response = await fetch(`${options.server}/tokens`, { headers: authHeaders(options.token) });
289
- if (response.status === 401) {
290
- console.error("Session expired. Run 'buzz login' to re-authenticate");
291
- process.exit(1);
292
- }
293
- if (response.status === 403) {
294
- console.error("Deploy tokens cannot list tokens. Use a session token.");
295
- process.exit(1);
296
- }
297
- if (!response.ok) {
298
- const data = await response.json();
299
- console.error(`Error: ${data.error || "Unknown error"}`);
300
- process.exit(1);
301
- }
302
- const tokens = await response.json();
303
- if (tokens.length === 0) {
304
- console.log("No deployment tokens");
305
- return;
306
- }
307
- console.log(`${"ID".padEnd(18)} ${"NAME".padEnd(20)} ${"SITE".padEnd(20)} ${"LAST USED".padEnd(20)}`);
308
- for (const token of tokens) {
309
- const lastUsed = token.last_used_at ? token.last_used_at.slice(0, 19).replace("T", " ") : "Never";
310
- console.log(`${token.id.padEnd(18)} ${token.name.slice(0, 18).padEnd(20)} ${token.site_name.slice(0, 18).padEnd(20)} ${lastUsed.padEnd(20)}`);
311
- }
312
- } catch (error) {
313
- console.error(`Error: Could not connect to server - ${error instanceof Error ? error.message : error}`);
314
- process.exit(1);
379
+ console.log(`${"ID".padEnd(18)} ${"NAME".padEnd(20)} ${"SITE".padEnd(20)} ${"LAST USED".padEnd(20)}`);
380
+ for (const token of tokens) {
381
+ const lastUsed = token.last_used_at ? token.last_used_at.slice(0, 19).replace("T", " ") : "Never";
382
+ console.log(`${token.id.padEnd(18)} ${token.name.slice(0, 18).padEnd(20)} ${token.site_name.slice(0, 18).padEnd(20)} ${lastUsed.padEnd(20)}`);
315
383
  }
316
384
  }
317
385
  async function createToken(siteName, cmdOptions) {
318
- const options = getOptions();
319
- if (!options.token) {
320
- console.error("Not logged in. Run 'buzz login' first");
321
- process.exit(1);
322
- }
323
- try {
324
- const response = await fetch(`${options.server}/tokens`, {
325
- method: "POST",
326
- headers: {
327
- ...authHeaders(options.token),
328
- "Content-Type": "application/json"
329
- },
330
- body: JSON.stringify({
331
- site_name: siteName,
332
- name: cmdOptions.name || "Deployment token"
333
- })
334
- });
335
- if (response.status === 401) {
336
- console.error("Session expired. Run 'buzz login' to re-authenticate");
337
- process.exit(1);
338
- }
339
- if (response.status === 403) {
340
- const data$1 = await response.json();
341
- console.error(`Error: ${data$1.error}`);
342
- process.exit(1);
343
- }
344
- if (response.status === 404) {
345
- console.error(`Error: Site '${siteName}' not found`);
346
- process.exit(1);
347
- }
348
- if (!response.ok) {
349
- const data$1 = await response.json();
350
- console.error(`Error: ${data$1.error || "Unknown error"}`);
351
- process.exit(1);
352
- }
353
- const data = await response.json();
354
- console.log(`Token created for site '${siteName}':\n`);
355
- console.log(` ${data.token}\n`);
356
- console.log("Save this token - it won't be shown again!");
357
- console.log("\nUse in CI by setting BUZZ_TOKEN environment variable.");
358
- } catch (error) {
359
- console.error(`Error: Could not connect to server - ${error instanceof Error ? error.message : error}`);
360
- process.exit(1);
361
- }
386
+ const response = await apiRequest("/tokens", {
387
+ method: "POST",
388
+ headers: { "Content-Type": "application/json" },
389
+ body: JSON.stringify({
390
+ site_name: siteName,
391
+ name: cmdOptions.name || "Deployment token"
392
+ })
393
+ });
394
+ if (response.status === 404) throw new CliError(`Site '${siteName}' not found`);
395
+ if (!response.ok) throw new CliError((await response.json()).error || "Unknown error");
396
+ const data = await response.json();
397
+ console.log(`Token created for site '${siteName}':\n`);
398
+ console.log(` ${data.token}\n`);
399
+ console.log("Save this token - it won't be shown again!");
400
+ console.log("\nUse in CI by setting BUZZ_TOKEN environment variable.");
362
401
  }
363
402
  async function deleteToken(tokenId) {
364
- const options = getOptions();
365
- if (!options.token) {
366
- console.error("Not logged in. Run 'buzz login' first");
367
- process.exit(1);
368
- }
369
- try {
370
- const response = await fetch(`${options.server}/tokens/${tokenId}`, {
371
- method: "DELETE",
372
- headers: authHeaders(options.token)
373
- });
374
- if (response.status === 204) {
375
- console.log("Token deleted");
376
- return;
377
- }
378
- if (response.status === 401) {
379
- console.error("Session expired. Run 'buzz login' to re-authenticate");
380
- process.exit(1);
381
- }
382
- if (response.status === 404) {
383
- console.error("Token not found");
384
- process.exit(1);
385
- }
386
- const data = await response.json();
387
- console.error(`Error: ${data.error || "Unknown error"}`);
388
- process.exit(1);
389
- } catch (error) {
390
- console.error(`Error: Could not connect to server - ${error instanceof Error ? error.message : error}`);
391
- process.exit(1);
403
+ const response = await apiRequest(`/tokens/${tokenId}`, { method: "DELETE" });
404
+ if (response.status === 204) {
405
+ console.log("Token deleted");
406
+ return;
392
407
  }
408
+ if (response.status === 404) throw new CliError("Token not found");
409
+ throw new CliError((await response.json()).error || "Unknown error");
410
+ }
411
+ function registerTokensCommand(program$1) {
412
+ const tokensCmd = program$1.command("tokens").description("Manage deployment tokens");
413
+ tokensCmd.command("list").description("List your deployment tokens").action(listTokens);
414
+ tokensCmd.command("create <site>").description("Create a deployment token for a site").option("-n, --name <name>", "Token name (for identification)").action(createToken);
415
+ tokensCmd.command("delete <token-id>").description("Delete a deployment token").action(deleteToken);
416
+ }
417
+
418
+ //#endregion
419
+ //#region src/commands/index.ts
420
+ function registerCommands(program$1) {
421
+ registerDeployCommand(program$1);
422
+ registerListCommand(program$1);
423
+ registerDeleteCommand(program$1);
424
+ registerConfigCommand(program$1);
425
+ registerUrlCommand(program$1);
426
+ registerAuthCommands(program$1);
427
+ registerTokensCommand(program$1);
393
428
  }
429
+
430
+ //#endregion
431
+ //#region src/cli.ts
394
432
  program.name("buzz").description("CLI for deploying static sites to Buzz hosting").version("1.0.0").option("-s, --server <url>", "Server URL (overrides config)").option("-t, --token <token>", "Auth token (overrides config)");
395
- program.command("deploy <directory>").description("Deploy a directory to the server").option("--subdomain <name>", "Subdomain for the site").action((directory, cmdOptions) => deploy(directory, cmdOptions.subdomain));
396
- program.command("list").description("List all deployed sites").action(list);
397
- program.command("delete <subdomain>").description("Delete a deployed site").action(deleteSite);
398
- program.command("config [key] [value]").description("View or set configuration (server)").action(configCommand);
399
- program.command("url").description("Show the URL for the current directory").action(() => {
400
- const cnamePath = join(process.cwd(), "CNAME");
401
- if (!existsSync(cnamePath)) {
402
- console.error("No CNAME file found. Deploy first with: buzz deploy .");
403
- process.exit(1);
404
- }
405
- const subdomain = readFileSync(cnamePath, "utf-8").trim();
406
- const server = loadConfig().server || DEFAULT_SERVER;
407
- try {
408
- const host = new URL(server).hostname;
409
- console.log(`https://${subdomain}.${host}`);
410
- } catch {
411
- console.log(`http://${subdomain}.localhost:8080`);
412
- }
413
- });
414
- program.command("login").description("Login with GitHub OAuth").action(login);
415
- program.command("logout").description("Logout and clear session").action(logout);
416
- program.command("whoami").description("Show current logged-in user").action(whoami);
417
- const tokensCmd = program.command("tokens").description("Manage deployment tokens");
418
- tokensCmd.command("list").description("List your deployment tokens").action(listTokens);
419
- tokensCmd.command("create <site>").description("Create a deployment token for a site").option("-n, --name <name>", "Token name (for identification)").action(createToken);
420
- tokensCmd.command("delete <token-id>").description("Delete a deployment token").action(deleteToken);
421
- program.parse();
433
+ registerCommands(program);
434
+ async function main() {
435
+ await program.parseAsync();
436
+ }
437
+ main().catch(handleError);
422
438
 
423
439
  //#endregion
424
440
  export { };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@infomiho/buzz-cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "CLI for deploying static sites to Buzz hosting",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,6 +29,7 @@
29
29
  "license": "MIT",
30
30
  "devDependencies": {
31
31
  "@types/archiver": "^7.0.0",
32
+ "@types/cli-progress": "^3.11.6",
32
33
  "@types/node": "^22.0.0",
33
34
  "publint": "^0.3.16",
34
35
  "tsdown": "^0.20.0-beta.4",
@@ -36,6 +37,7 @@
36
37
  },
37
38
  "dependencies": {
38
39
  "archiver": "^7.0.0",
40
+ "cli-progress": "^3.12.0",
39
41
  "commander": "^13.0.0"
40
42
  },
41
43
  "exports": {