@infomiho/buzz-cli 0.1.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.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,239 @@
1
+ #!/usr/bin/env node
2
+ import { program } from "commander";
3
+ import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import { join } from "node:path";
6
+ const CONFIG_PATH = join(homedir(), ".buzz.config.json");
7
+ const DEFAULT_SERVER = "http://localhost:8080";
8
+ function loadConfig() {
9
+ if (existsSync(CONFIG_PATH)) {
10
+ try {
11
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
12
+ }
13
+ catch {
14
+ return {};
15
+ }
16
+ }
17
+ return {};
18
+ }
19
+ function saveConfig(config) {
20
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
21
+ }
22
+ function getOptions() {
23
+ const config = loadConfig();
24
+ const opts = program.opts();
25
+ return {
26
+ server: opts.server || config.server || DEFAULT_SERVER,
27
+ token: opts.token || config.token,
28
+ };
29
+ }
30
+ function formatSize(bytes) {
31
+ if (bytes < 1024)
32
+ return `${bytes} B`;
33
+ if (bytes < 1024 ** 2)
34
+ return `${(bytes / 1024).toFixed(1)} KB`;
35
+ return `${(bytes / 1024 ** 2).toFixed(1)} MB`;
36
+ }
37
+ function authHeaders(token) {
38
+ if (token) {
39
+ return { Authorization: `Bearer ${token}` };
40
+ }
41
+ return {};
42
+ }
43
+ async function createZipBuffer(directory) {
44
+ const archiver = await import("archiver");
45
+ return new Promise((resolve, reject) => {
46
+ const archive = archiver.default("zip", { zlib: { level: 9 } });
47
+ const chunks = [];
48
+ archive.on("data", (chunk) => chunks.push(chunk));
49
+ archive.on("end", () => resolve(Buffer.concat(chunks)));
50
+ archive.on("error", reject);
51
+ archive.directory(directory, false);
52
+ archive.finalize();
53
+ });
54
+ }
55
+ async function deploy(directory, subdomain) {
56
+ const options = getOptions();
57
+ const stat = statSync(directory);
58
+ if (!stat.isDirectory()) {
59
+ console.error(`Error: '${directory}' is not a directory`);
60
+ process.exit(1);
61
+ }
62
+ // Check for CNAME file if no subdomain specified
63
+ const cnamePath = join(directory, "CNAME");
64
+ if (!subdomain && existsSync(cnamePath)) {
65
+ subdomain = readFileSync(cnamePath, "utf-8").trim();
66
+ }
67
+ console.log(`Zipping ${directory}...`);
68
+ const zipBuffer = await createZipBuffer(directory);
69
+ const boundary = "----BuzzFormBoundary" + Math.random().toString(36).slice(2);
70
+ const header = `--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="site.zip"\r\nContent-Type: application/zip\r\n\r\n`;
71
+ const footer = `\r\n--${boundary}--\r\n`;
72
+ const body = Buffer.concat([
73
+ Buffer.from(header),
74
+ zipBuffer,
75
+ Buffer.from(footer),
76
+ ]);
77
+ const headers = {
78
+ "Content-Type": `multipart/form-data; boundary=${boundary}`,
79
+ ...authHeaders(options.token),
80
+ };
81
+ if (subdomain) {
82
+ headers["x-subdomain"] = subdomain;
83
+ }
84
+ try {
85
+ const response = await fetch(`${options.server}/deploy`, {
86
+ method: "POST",
87
+ headers,
88
+ body,
89
+ });
90
+ const data = await response.json();
91
+ if (response.ok) {
92
+ console.log(`Deployed to ${data.url}`);
93
+ // Save subdomain to CNAME file
94
+ const deployedSubdomain = new URL(data.url).hostname.split(".")[0];
95
+ writeFileSync(cnamePath, deployedSubdomain + "\n");
96
+ }
97
+ else {
98
+ console.error(`Error: ${data.error || "Unknown error"}`);
99
+ process.exit(1);
100
+ }
101
+ }
102
+ catch (error) {
103
+ console.error(`Error: Could not connect to server - ${error instanceof Error ? error.message : error}`);
104
+ process.exit(1);
105
+ }
106
+ }
107
+ async function list() {
108
+ const options = getOptions();
109
+ try {
110
+ const response = await fetch(`${options.server}/sites`, {
111
+ headers: authHeaders(options.token),
112
+ });
113
+ if (response.status === 401) {
114
+ console.error("Error: Unauthorized - check your token");
115
+ process.exit(1);
116
+ }
117
+ const sites = await response.json();
118
+ if (sites.length === 0) {
119
+ console.log("No sites deployed");
120
+ return;
121
+ }
122
+ console.log(`${"NAME".padEnd(24)} ${"CREATED".padEnd(20)} ${"SIZE".padEnd(10)}`);
123
+ for (const site of sites) {
124
+ const created = site.created.slice(0, 19).replace("T", " ");
125
+ console.log(`${site.name.padEnd(24)} ${created.padEnd(20)} ${formatSize(site.size_bytes).padEnd(10)}`);
126
+ }
127
+ }
128
+ catch (error) {
129
+ console.error(`Error: Could not connect to server - ${error instanceof Error ? error.message : error}`);
130
+ process.exit(1);
131
+ }
132
+ }
133
+ async function deleteSite(subdomain) {
134
+ const options = getOptions();
135
+ try {
136
+ const response = await fetch(`${options.server}/sites/${subdomain}`, {
137
+ method: "DELETE",
138
+ headers: authHeaders(options.token),
139
+ });
140
+ if (response.status === 204) {
141
+ console.log(`Deleted ${subdomain}`);
142
+ }
143
+ else if (response.status === 401) {
144
+ console.error("Error: Unauthorized - check your token");
145
+ process.exit(1);
146
+ }
147
+ else if (response.status === 404) {
148
+ console.error(`Error: Site '${subdomain}' not found`);
149
+ process.exit(1);
150
+ }
151
+ else {
152
+ const data = await response.json();
153
+ console.error(`Error: ${data.error || "Unknown error"}`);
154
+ process.exit(1);
155
+ }
156
+ }
157
+ catch (error) {
158
+ console.error(`Error: Could not connect to server - ${error instanceof Error ? error.message : error}`);
159
+ process.exit(1);
160
+ }
161
+ }
162
+ function configCommand(key, value) {
163
+ const config = loadConfig();
164
+ if (!key) {
165
+ // Show current config
166
+ if (Object.keys(config).length === 0) {
167
+ console.log("No configuration set");
168
+ console.log(`\nConfig file: ${CONFIG_PATH}`);
169
+ console.log("\nUsage:");
170
+ console.log(" buzz config server <url> Set server URL");
171
+ console.log(" buzz config token <token> Set auth token");
172
+ return;
173
+ }
174
+ console.log("Current configuration:");
175
+ if (config.server)
176
+ console.log(` server: ${config.server}`);
177
+ if (config.token)
178
+ console.log(` token: ${config.token.slice(0, 8)}...`);
179
+ console.log(`\nConfig file: ${CONFIG_PATH}`);
180
+ return;
181
+ }
182
+ if (key === "server" && value) {
183
+ config.server = value;
184
+ saveConfig(config);
185
+ console.log(`Server set to ${value}`);
186
+ }
187
+ else if (key === "token" && value) {
188
+ config.token = value;
189
+ saveConfig(config);
190
+ console.log("Token saved");
191
+ }
192
+ else {
193
+ console.error("Usage: buzz config <server|token> <value>");
194
+ process.exit(1);
195
+ }
196
+ }
197
+ program
198
+ .name("buzz")
199
+ .description("CLI for deploying static sites to Buzz hosting")
200
+ .version("1.0.0")
201
+ .option("-s, --server <url>", "Server URL (overrides config)")
202
+ .option("-t, --token <token>", "Auth token (overrides config)");
203
+ program
204
+ .command("deploy <directory> [subdomain]")
205
+ .description("Deploy a directory to the server")
206
+ .action(deploy);
207
+ program
208
+ .command("list")
209
+ .description("List all deployed sites")
210
+ .action(list);
211
+ program
212
+ .command("delete <subdomain>")
213
+ .description("Delete a deployed site")
214
+ .action(deleteSite);
215
+ program
216
+ .command("config [key] [value]")
217
+ .description("View or set configuration (server, token)")
218
+ .action(configCommand);
219
+ program
220
+ .command("url")
221
+ .description("Show the URL for the current directory")
222
+ .action(() => {
223
+ const cnamePath = join(process.cwd(), "CNAME");
224
+ if (!existsSync(cnamePath)) {
225
+ console.error("No CNAME file found. Deploy first with: buzz deploy .");
226
+ process.exit(1);
227
+ }
228
+ const subdomain = readFileSync(cnamePath, "utf-8").trim();
229
+ const config = loadConfig();
230
+ const server = config.server || DEFAULT_SERVER;
231
+ try {
232
+ const host = new URL(server).hostname;
233
+ console.log(`https://${subdomain}.${host}`);
234
+ }
235
+ catch {
236
+ console.log(`http://${subdomain}.localhost:8080`);
237
+ }
238
+ });
239
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@infomiho/buzz-cli",
3
+ "version": "0.1.0",
4
+ "description": "CLI for deploying static sites to Buzz hosting",
5
+ "type": "module",
6
+ "bin": {
7
+ "buzz": "./dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "dev": "tsc --watch",
15
+ "prepublishOnly": "npm run build"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/infomiho/buzz-static-hosting.git"
20
+ },
21
+ "homepage": "https://github.com/infomiho/buzz-static-hosting#readme",
22
+ "keywords": [
23
+ "static-site",
24
+ "hosting",
25
+ "deploy",
26
+ "cli"
27
+ ],
28
+ "author": "",
29
+ "license": "MIT",
30
+ "devDependencies": {
31
+ "@types/archiver": "^7.0.0",
32
+ "@types/node": "^22.0.0",
33
+ "typescript": "^5.7.0"
34
+ },
35
+ "dependencies": {
36
+ "archiver": "^7.0.0",
37
+ "commander": "^13.0.0"
38
+ }
39
+ }