@infomiho/buzz-cli 0.4.0 → 0.6.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 +97 -43
- package/package.json +5 -2
package/dist/cli.mjs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "module";
|
|
2
3
|
import { program } from "commander";
|
|
3
4
|
import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
4
5
|
import { join } from "node:path";
|
|
5
6
|
import { homedir } from "node:os";
|
|
6
7
|
import cliProgress from "cli-progress";
|
|
8
|
+
import { createInterface } from "readline";
|
|
7
9
|
|
|
8
10
|
//#region src/lib.ts
|
|
9
11
|
const CONFIG_PATH = join(homedir(), ".buzz.config.json");
|
|
@@ -78,6 +80,17 @@ async function apiRequest(path, options = {}, { requireAuth = true } = {}) {
|
|
|
78
80
|
if (response.status === 403) throw new ApiError((await response.json()).error || "Permission denied", 403);
|
|
79
81
|
return response;
|
|
80
82
|
}
|
|
83
|
+
const IGNORED_DIRS = [
|
|
84
|
+
".git",
|
|
85
|
+
"node_modules",
|
|
86
|
+
".vscode",
|
|
87
|
+
".idea"
|
|
88
|
+
];
|
|
89
|
+
const IGNORED_FILES = [
|
|
90
|
+
"**/.DS_Store",
|
|
91
|
+
"**/.env",
|
|
92
|
+
"**/.env.*"
|
|
93
|
+
];
|
|
81
94
|
async function createZipBuffer(directory, callbacks) {
|
|
82
95
|
const archiver = await import("archiver");
|
|
83
96
|
return new Promise((resolve, reject) => {
|
|
@@ -89,7 +102,12 @@ async function createZipBuffer(directory, callbacks) {
|
|
|
89
102
|
archive.on("progress", (progress) => {
|
|
90
103
|
callbacks?.onProgress?.(progress.entries.processed, progress.entries.total);
|
|
91
104
|
});
|
|
92
|
-
archive.
|
|
105
|
+
archive.glob("**/*", {
|
|
106
|
+
cwd: directory,
|
|
107
|
+
dot: true,
|
|
108
|
+
skip: IGNORED_DIRS,
|
|
109
|
+
ignore: IGNORED_FILES
|
|
110
|
+
});
|
|
93
111
|
archive.finalize();
|
|
94
112
|
});
|
|
95
113
|
}
|
|
@@ -147,74 +165,91 @@ function createSpinner(message) {
|
|
|
147
165
|
}
|
|
148
166
|
|
|
149
167
|
//#endregion
|
|
150
|
-
//#region src/
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
168
|
+
//#region src/deploy.ts
|
|
169
|
+
function resolveSubdomain(cwd, directory, explicit) {
|
|
170
|
+
if (explicit) return explicit;
|
|
171
|
+
const cwdCname = join(cwd, "CNAME");
|
|
172
|
+
if (existsSync(cwdCname)) return readFileSync(cwdCname, "utf-8").trim();
|
|
173
|
+
const dirCname = join(directory, "CNAME");
|
|
174
|
+
if (existsSync(dirCname)) return readFileSync(dirCname, "utf-8").trim();
|
|
175
|
+
}
|
|
176
|
+
async function packSite(directory, onProgress) {
|
|
154
177
|
if (!existsSync(directory)) throw new CliError(`'${directory}' does not exist`);
|
|
155
178
|
if (!statSync(directory).isDirectory()) throw new CliError(`'${directory}' is not a directory`);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const dirCnamePath = join(directory, "CNAME");
|
|
160
|
-
if (existsSync(dirCnamePath)) subdomain = readFileSync(dirCnamePath, "utf-8").trim();
|
|
161
|
-
}
|
|
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();
|
|
179
|
+
return createZipBuffer(directory, { onProgress });
|
|
180
|
+
}
|
|
181
|
+
async function uploadSite(server, token, zip, subdomain, fetchFn = globalThis.fetch) {
|
|
179
182
|
const boundary = "----BuzzFormBoundary" + Math.random().toString(36).slice(2);
|
|
180
183
|
const header = `--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="site.zip"\r\nContent-Type: application/zip\r\n\r\n`;
|
|
181
184
|
const footer = `\r\n--${boundary}--\r\n`;
|
|
182
185
|
const body = Buffer.concat([
|
|
183
186
|
Buffer.from(header),
|
|
184
|
-
|
|
187
|
+
zip,
|
|
185
188
|
Buffer.from(footer)
|
|
186
189
|
]);
|
|
187
190
|
const headers = {
|
|
188
191
|
"Content-Type": `multipart/form-data; boundary=${boundary}`,
|
|
189
|
-
...authHeaders(
|
|
192
|
+
...authHeaders(token)
|
|
190
193
|
};
|
|
191
194
|
if (subdomain) headers["x-subdomain"] = subdomain;
|
|
192
195
|
let response;
|
|
193
196
|
try {
|
|
194
|
-
response = await
|
|
197
|
+
response = await fetchFn(`${server}/deploy`, {
|
|
195
198
|
method: "POST",
|
|
196
199
|
headers,
|
|
197
200
|
body
|
|
198
201
|
});
|
|
199
202
|
} catch (error) {
|
|
200
|
-
uploadSpinner.stop("✗ Upload failed");
|
|
201
203
|
throw new CliError(`Could not connect to server - ${error instanceof Error ? error.message : error}`);
|
|
202
204
|
}
|
|
203
205
|
const data = await response.json();
|
|
204
206
|
if (response.ok) {
|
|
205
|
-
uploadSpinner.stop("✓ Uploaded");
|
|
206
|
-
console.log(`Deployed to ${data.url}`);
|
|
207
207
|
const deployedSubdomain = new URL(data.url).hostname.split(".")[0];
|
|
208
|
-
|
|
209
|
-
|
|
208
|
+
return {
|
|
209
|
+
url: data.url,
|
|
210
|
+
subdomain: deployedSubdomain
|
|
211
|
+
};
|
|
210
212
|
}
|
|
211
|
-
uploadSpinner.stop("✗ Upload failed");
|
|
212
213
|
if (response.status === 401) throw new CliError("Not authenticated", "Run 'buzz login' first");
|
|
213
214
|
if (response.status === 403) {
|
|
214
|
-
const tip = data.
|
|
215
|
-
throw new CliError(data.
|
|
215
|
+
const tip = data.detail?.includes("owned by another user") ? "Choose a different subdomain with --subdomain <name>" : void 0;
|
|
216
|
+
throw new CliError(data.detail || "Permission denied", tip);
|
|
217
|
+
}
|
|
218
|
+
throw new CliError(data.detail || "Unknown error");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
//#endregion
|
|
222
|
+
//#region src/commands/deploy.ts
|
|
223
|
+
async function deploy(directory, subdomain) {
|
|
224
|
+
const options = getOptions();
|
|
225
|
+
if (!options.token) throw new CliError("Not authenticated", "Run 'buzz login' first");
|
|
226
|
+
subdomain = resolveSubdomain(process.cwd(), directory, subdomain);
|
|
227
|
+
const progressBar = createProgressBar("Zipping");
|
|
228
|
+
let progressStarted = false;
|
|
229
|
+
let zipBuffer;
|
|
230
|
+
try {
|
|
231
|
+
zipBuffer = await packSite(directory, (processed, total) => {
|
|
232
|
+
if (!progressStarted && total > 0) {
|
|
233
|
+
progressBar.start(total, 0);
|
|
234
|
+
progressStarted = true;
|
|
235
|
+
}
|
|
236
|
+
if (progressStarted) progressBar.update(processed);
|
|
237
|
+
});
|
|
238
|
+
} finally {
|
|
239
|
+
if (progressStarted) progressBar.stop();
|
|
240
|
+
}
|
|
241
|
+
console.log(`Compressed to ${formatSize(zipBuffer.length)}`);
|
|
242
|
+
const uploadSpinner = createSpinner("Uploading");
|
|
243
|
+
uploadSpinner.start();
|
|
244
|
+
try {
|
|
245
|
+
const result = await uploadSite(options.server, options.token, zipBuffer, subdomain);
|
|
246
|
+
uploadSpinner.stop("✓ Uploaded");
|
|
247
|
+
console.log(`Deployed to ${result.url}`);
|
|
248
|
+
writeFileSync(join(process.cwd(), "CNAME"), result.subdomain + "\n");
|
|
249
|
+
} catch (error) {
|
|
250
|
+
uploadSpinner.stop("✗ Upload failed");
|
|
251
|
+
throw error;
|
|
216
252
|
}
|
|
217
|
-
throw new CliError(data.error || "Unknown error");
|
|
218
253
|
}
|
|
219
254
|
function registerDeployCommand(program$1) {
|
|
220
255
|
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));
|
|
@@ -240,7 +275,25 @@ function registerListCommand(program$1) {
|
|
|
240
275
|
|
|
241
276
|
//#endregion
|
|
242
277
|
//#region src/commands/delete.ts
|
|
243
|
-
|
|
278
|
+
function confirm(message) {
|
|
279
|
+
const rl = createInterface({
|
|
280
|
+
input: process.stdin,
|
|
281
|
+
output: process.stdout
|
|
282
|
+
});
|
|
283
|
+
return new Promise((resolve) => {
|
|
284
|
+
rl.question(`${message} [y/N] `, (answer) => {
|
|
285
|
+
rl.close();
|
|
286
|
+
resolve(answer.toLowerCase() === "y");
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
async function deleteSite(subdomain, options) {
|
|
291
|
+
if (!options.yes) {
|
|
292
|
+
if (!await confirm(`Delete site '${subdomain}'?`)) {
|
|
293
|
+
console.log("Aborted.");
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
244
297
|
const response = await apiRequest(`/sites/${subdomain}`, { method: "DELETE" });
|
|
245
298
|
if (response.status === 204) {
|
|
246
299
|
console.log(`Deleted ${subdomain}`);
|
|
@@ -250,7 +303,7 @@ async function deleteSite(subdomain) {
|
|
|
250
303
|
throw new CliError((await response.json()).error || "Unknown error");
|
|
251
304
|
}
|
|
252
305
|
function registerDeleteCommand(program$1) {
|
|
253
|
-
program$1.command("delete <subdomain>").description("Delete a deployed site").action(deleteSite);
|
|
306
|
+
program$1.command("delete <subdomain>").description("Delete a deployed site").option("-y, --yes", "Skip confirmation prompt").action(deleteSite);
|
|
254
307
|
}
|
|
255
308
|
|
|
256
309
|
//#endregion
|
|
@@ -429,7 +482,8 @@ function registerCommands(program$1) {
|
|
|
429
482
|
|
|
430
483
|
//#endregion
|
|
431
484
|
//#region src/cli.ts
|
|
432
|
-
|
|
485
|
+
const { version } = createRequire(import.meta.url)("../package.json");
|
|
486
|
+
program.name("buzz").description("CLI for deploying static sites to Buzz hosting").version(version).option("-s, --server <url>", "Server URL (overrides config)").option("-t, --token <token>", "Auth token (overrides config)");
|
|
433
487
|
registerCommands(program);
|
|
434
488
|
async function main() {
|
|
435
489
|
await program.parseAsync();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@infomiho/buzz-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "CLI for deploying static sites to Buzz hosting",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build": "tsdown",
|
|
14
14
|
"dev": "tsdown --watch",
|
|
15
|
+
"test": "vitest run",
|
|
15
16
|
"prepublishOnly": "npm run build"
|
|
16
17
|
},
|
|
17
18
|
"repository": {
|
|
@@ -31,9 +32,11 @@
|
|
|
31
32
|
"@types/archiver": "^7.0.0",
|
|
32
33
|
"@types/cli-progress": "^3.11.6",
|
|
33
34
|
"@types/node": "^22.0.0",
|
|
35
|
+
"jszip": "^3.10.1",
|
|
34
36
|
"publint": "^0.3.16",
|
|
35
37
|
"tsdown": "^0.20.0-beta.4",
|
|
36
|
-
"typescript": "^5.7.0"
|
|
38
|
+
"typescript": "^5.7.0",
|
|
39
|
+
"vitest": "^4.1.1"
|
|
37
40
|
},
|
|
38
41
|
"dependencies": {
|
|
39
42
|
"archiver": "^7.0.0",
|