@infomiho/buzz-cli 0.4.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.
- package/dist/cli.mjs +80 -42
- package/package.json +4 -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");
|
|
@@ -147,74 +149,91 @@ function createSpinner(message) {
|
|
|
147
149
|
}
|
|
148
150
|
|
|
149
151
|
//#endregion
|
|
150
|
-
//#region src/
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
//#region src/deploy.ts
|
|
153
|
+
function resolveSubdomain(cwd, directory, explicit) {
|
|
154
|
+
if (explicit) return explicit;
|
|
155
|
+
const cwdCname = join(cwd, "CNAME");
|
|
156
|
+
if (existsSync(cwdCname)) return readFileSync(cwdCname, "utf-8").trim();
|
|
157
|
+
const dirCname = join(directory, "CNAME");
|
|
158
|
+
if (existsSync(dirCname)) return readFileSync(dirCname, "utf-8").trim();
|
|
159
|
+
}
|
|
160
|
+
async function packSite(directory, onProgress) {
|
|
154
161
|
if (!existsSync(directory)) throw new CliError(`'${directory}' does not exist`);
|
|
155
162
|
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();
|
|
163
|
+
return createZipBuffer(directory, { onProgress });
|
|
164
|
+
}
|
|
165
|
+
async function uploadSite(server, token, zip, subdomain, fetchFn = globalThis.fetch) {
|
|
179
166
|
const boundary = "----BuzzFormBoundary" + Math.random().toString(36).slice(2);
|
|
180
167
|
const header = `--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="site.zip"\r\nContent-Type: application/zip\r\n\r\n`;
|
|
181
168
|
const footer = `\r\n--${boundary}--\r\n`;
|
|
182
169
|
const body = Buffer.concat([
|
|
183
170
|
Buffer.from(header),
|
|
184
|
-
|
|
171
|
+
zip,
|
|
185
172
|
Buffer.from(footer)
|
|
186
173
|
]);
|
|
187
174
|
const headers = {
|
|
188
175
|
"Content-Type": `multipart/form-data; boundary=${boundary}`,
|
|
189
|
-
...authHeaders(
|
|
176
|
+
...authHeaders(token)
|
|
190
177
|
};
|
|
191
178
|
if (subdomain) headers["x-subdomain"] = subdomain;
|
|
192
179
|
let response;
|
|
193
180
|
try {
|
|
194
|
-
response = await
|
|
181
|
+
response = await fetchFn(`${server}/deploy`, {
|
|
195
182
|
method: "POST",
|
|
196
183
|
headers,
|
|
197
184
|
body
|
|
198
185
|
});
|
|
199
186
|
} catch (error) {
|
|
200
|
-
uploadSpinner.stop("✗ Upload failed");
|
|
201
187
|
throw new CliError(`Could not connect to server - ${error instanceof Error ? error.message : error}`);
|
|
202
188
|
}
|
|
203
189
|
const data = await response.json();
|
|
204
190
|
if (response.ok) {
|
|
205
|
-
uploadSpinner.stop("✓ Uploaded");
|
|
206
|
-
console.log(`Deployed to ${data.url}`);
|
|
207
191
|
const deployedSubdomain = new URL(data.url).hostname.split(".")[0];
|
|
208
|
-
|
|
209
|
-
|
|
192
|
+
return {
|
|
193
|
+
url: data.url,
|
|
194
|
+
subdomain: deployedSubdomain
|
|
195
|
+
};
|
|
210
196
|
}
|
|
211
|
-
uploadSpinner.stop("✗ Upload failed");
|
|
212
197
|
if (response.status === 401) throw new CliError("Not authenticated", "Run 'buzz login' first");
|
|
213
198
|
if (response.status === 403) {
|
|
214
|
-
const tip = data.
|
|
215
|
-
throw new CliError(data.
|
|
199
|
+
const tip = data.detail?.includes("owned by another user") ? "Choose a different subdomain with --subdomain <name>" : void 0;
|
|
200
|
+
throw new CliError(data.detail || "Permission denied", tip);
|
|
201
|
+
}
|
|
202
|
+
throw new CliError(data.detail || "Unknown error");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
//#endregion
|
|
206
|
+
//#region src/commands/deploy.ts
|
|
207
|
+
async function deploy(directory, subdomain) {
|
|
208
|
+
const options = getOptions();
|
|
209
|
+
if (!options.token) throw new CliError("Not authenticated", "Run 'buzz login' first");
|
|
210
|
+
subdomain = resolveSubdomain(process.cwd(), directory, subdomain);
|
|
211
|
+
const progressBar = createProgressBar("Zipping");
|
|
212
|
+
let progressStarted = false;
|
|
213
|
+
let zipBuffer;
|
|
214
|
+
try {
|
|
215
|
+
zipBuffer = await packSite(directory, (processed, total) => {
|
|
216
|
+
if (!progressStarted && total > 0) {
|
|
217
|
+
progressBar.start(total, 0);
|
|
218
|
+
progressStarted = true;
|
|
219
|
+
}
|
|
220
|
+
if (progressStarted) progressBar.update(processed);
|
|
221
|
+
});
|
|
222
|
+
} finally {
|
|
223
|
+
if (progressStarted) progressBar.stop();
|
|
224
|
+
}
|
|
225
|
+
console.log(`Compressed to ${formatSize(zipBuffer.length)}`);
|
|
226
|
+
const uploadSpinner = createSpinner("Uploading");
|
|
227
|
+
uploadSpinner.start();
|
|
228
|
+
try {
|
|
229
|
+
const result = await uploadSite(options.server, options.token, zipBuffer, subdomain);
|
|
230
|
+
uploadSpinner.stop("✓ Uploaded");
|
|
231
|
+
console.log(`Deployed to ${result.url}`);
|
|
232
|
+
writeFileSync(join(process.cwd(), "CNAME"), result.subdomain + "\n");
|
|
233
|
+
} catch (error) {
|
|
234
|
+
uploadSpinner.stop("✗ Upload failed");
|
|
235
|
+
throw error;
|
|
216
236
|
}
|
|
217
|
-
throw new CliError(data.error || "Unknown error");
|
|
218
237
|
}
|
|
219
238
|
function registerDeployCommand(program$1) {
|
|
220
239
|
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 +259,25 @@ function registerListCommand(program$1) {
|
|
|
240
259
|
|
|
241
260
|
//#endregion
|
|
242
261
|
//#region src/commands/delete.ts
|
|
243
|
-
|
|
262
|
+
function confirm(message) {
|
|
263
|
+
const rl = createInterface({
|
|
264
|
+
input: process.stdin,
|
|
265
|
+
output: process.stdout
|
|
266
|
+
});
|
|
267
|
+
return new Promise((resolve) => {
|
|
268
|
+
rl.question(`${message} [y/N] `, (answer) => {
|
|
269
|
+
rl.close();
|
|
270
|
+
resolve(answer.toLowerCase() === "y");
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
async function deleteSite(subdomain, options) {
|
|
275
|
+
if (!options.yes) {
|
|
276
|
+
if (!await confirm(`Delete site '${subdomain}'?`)) {
|
|
277
|
+
console.log("Aborted.");
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
244
281
|
const response = await apiRequest(`/sites/${subdomain}`, { method: "DELETE" });
|
|
245
282
|
if (response.status === 204) {
|
|
246
283
|
console.log(`Deleted ${subdomain}`);
|
|
@@ -250,7 +287,7 @@ async function deleteSite(subdomain) {
|
|
|
250
287
|
throw new CliError((await response.json()).error || "Unknown error");
|
|
251
288
|
}
|
|
252
289
|
function registerDeleteCommand(program$1) {
|
|
253
|
-
program$1.command("delete <subdomain>").description("Delete a deployed site").action(deleteSite);
|
|
290
|
+
program$1.command("delete <subdomain>").description("Delete a deployed site").option("-y, --yes", "Skip confirmation prompt").action(deleteSite);
|
|
254
291
|
}
|
|
255
292
|
|
|
256
293
|
//#endregion
|
|
@@ -429,7 +466,8 @@ function registerCommands(program$1) {
|
|
|
429
466
|
|
|
430
467
|
//#endregion
|
|
431
468
|
//#region src/cli.ts
|
|
432
|
-
|
|
469
|
+
const { version } = createRequire(import.meta.url)("../package.json");
|
|
470
|
+
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
471
|
registerCommands(program);
|
|
434
472
|
async function main() {
|
|
435
473
|
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.5.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": {
|
|
@@ -33,7 +34,8 @@
|
|
|
33
34
|
"@types/node": "^22.0.0",
|
|
34
35
|
"publint": "^0.3.16",
|
|
35
36
|
"tsdown": "^0.20.0-beta.4",
|
|
36
|
-
"typescript": "^5.7.0"
|
|
37
|
+
"typescript": "^5.7.0",
|
|
38
|
+
"vitest": "^4.1.1"
|
|
37
39
|
},
|
|
38
40
|
"dependencies": {
|
|
39
41
|
"archiver": "^7.0.0",
|