@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.
- package/dist/cli.mjs +296 -280
- 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/
|
|
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
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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
|
|
110
|
-
if (
|
|
111
|
-
console.
|
|
112
|
-
|
|
226
|
+
const sites = await (await apiRequest("/sites")).json();
|
|
227
|
+
if (sites.length === 0) {
|
|
228
|
+
console.log("No sites deployed");
|
|
229
|
+
return;
|
|
113
230
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
141
|
-
if (
|
|
142
|
-
console.
|
|
143
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
})
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
|
283
|
-
if (
|
|
284
|
-
console.
|
|
285
|
-
|
|
374
|
+
const tokens = await (await apiRequest("/tokens")).json();
|
|
375
|
+
if (tokens.length === 0) {
|
|
376
|
+
console.log("No deployment tokens");
|
|
377
|
+
return;
|
|
286
378
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
|
365
|
-
if (
|
|
366
|
-
console.
|
|
367
|
-
|
|
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
|
|
396
|
-
|
|
397
|
-
program.
|
|
398
|
-
|
|
399
|
-
|
|
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
|
+
"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": {
|