blinkhost 0.0.0-stage → 1.0.1

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 (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +132 -2
  3. package/dist/cli.js +565 -0
  4. package/package.json +58 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Blink Authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,133 @@
1
- # Temporary Holding Version
1
+ # ⚡ Blink CLI (`blinkhost`)
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > Instant, zero-signup ephemeral frontend hosting CLI. Deploy static websites in seconds with automatic teardown after 15, 35, or 45 minutes.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/blinkhost.svg)](https://npmjs.org/package/blinkhost)
6
+ [![license](https://img.shields.io/npm/l/blinkhost.svg)](LICENSE)
7
+
8
+ ---
9
+
10
+ ## Features
11
+
12
+ - 🚀 **Zero Signup Required**: No account, no API keys, no credentials needed.
13
+ - ⏳ **Ephemeral by Design**: Choose 15, 35, or 45-minute TTL. Sites automatically self-destruct.
14
+ - 📁 **Auto-Detection**: Automatically finds `dist/`, `build/`, `out/`, `public/`, or current directory.
15
+ - 🔒 **Machine Verified**: Uses hardware machine GUID to enforce 1 active site per machine (preventing abuse while allowing effortless redeployment).
16
+ - 📋 **Auto-Clipboard**: Instantly copies the live URL to your clipboard.
17
+ - 📱 **Mobile QR Code**: Optional `--qr` flag generates a terminal QR code for phone testing.
18
+ - 🛡️ **Safe & Fast**: Only uploads static web assets (rejects server executables).
19
+
20
+ ---
21
+
22
+ ## Quick Start
23
+
24
+ You can run Blink without installing it:
25
+
26
+ ```bash
27
+ npx blinkhost
28
+ ```
29
+
30
+ Or install it globally:
31
+
32
+ ```bash
33
+ npm install -g blinkhost
34
+ ```
35
+
36
+ ---
37
+
38
+ ## Usage
39
+
40
+ ### 1. Basic Deployment
41
+ Run `blink` inside any frontend project (React, Vue, Svelte, Vite, Astro, Next.js static export, or plain HTML):
42
+
43
+ ```bash
44
+ blink
45
+ ```
46
+
47
+ Blink will:
48
+ 1. Auto-detect your build folder (`dist`, `build`, `out`, `public`, etc.).
49
+ 2. Ask you to pick a TTL (15, 35, or 45 minutes) if run interactively.
50
+ 3. Upload your site, display your live URL, and copy it to your clipboard.
51
+
52
+ ### 2. Specify Directory
53
+ ```bash
54
+ blink ./my-app/dist
55
+ ```
56
+
57
+ ### 3. Set Duration Flag
58
+ Skip the interactive prompt by setting the duration:
59
+
60
+ ```bash
61
+ blink -t 35 # 35 minutes
62
+ blink -t 45 # 45 minutes
63
+ ```
64
+
65
+ ### 4. Custom Subdomain Alias
66
+ ```bash
67
+ blink --name my-portfolio
68
+ ```
69
+
70
+ ### 5. Display Mobile QR Code
71
+ ```bash
72
+ blink --qr
73
+ ```
74
+
75
+ ### 6. Auto-Replace Active Site
76
+ If you already have an active deployment and want to overwrite it without the interactive confirmation:
77
+
78
+ ```bash
79
+ blink --replace
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Other Commands
85
+
86
+ ### Check Deployment Status
87
+ View your active site URL, expiration timestamp, and time remaining:
88
+
89
+ ```bash
90
+ blink status
91
+ ```
92
+
93
+ ### Delete Active Deployment Early
94
+ Permanently tear down your active deployment before the timer runs out:
95
+
96
+ ```bash
97
+ blink delete
98
+ # or
99
+ blink destroy
100
+ ```
101
+
102
+ ---
103
+
104
+ ## Environment Variables
105
+
106
+ | Variable | Description | Default |
107
+ | :--- | :--- | :--- |
108
+ | `BLINK_SERVER_URL` | Custom Blink server instance URL | `http://localhost:3000` |
109
+
110
+ ---
111
+
112
+ ## Development
113
+
114
+ ```bash
115
+ # Clone the repository
116
+ git clone https://github.com/Jyothirao2685/blink.git
117
+ cd blink
118
+
119
+ # Install dependencies
120
+ npm install
121
+
122
+ # Build CLI
123
+ npm run build
124
+
125
+ # Link locally for testing
126
+ npm link
127
+ ```
128
+
129
+ ---
130
+
131
+ ## License
132
+
133
+ MIT
package/dist/cli.js ADDED
@@ -0,0 +1,565 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { Command } from "commander";
5
+ import chalk2 from "chalk";
6
+ import ora from "ora";
7
+ import prompts from "prompts";
8
+
9
+ // src/config.ts
10
+ import os from "os";
11
+ import path from "path";
12
+ var CONFIG = {
13
+ // Production server URL or local dev override via BLINK_SERVER_URL
14
+ DEFAULT_SERVER_URL: process.env.BLINK_SERVER_URL || "https://api.restrodash.in",
15
+ // Maximum uncompressed upload size: 50MB
16
+ MAX_UPLOAD_SIZE_BYTES: 50 * 1024 * 1024,
17
+ // Allowed TTL durations in minutes
18
+ ALLOWED_DURATIONS: [15, 35, 45],
19
+ DEFAULT_DURATION: 15,
20
+ // Default directory candidates to look for
21
+ DEFAULT_BUILD_DIRS: ["dist", "build", "out", "public"],
22
+ // Forbidden file extensions on frontend sites
23
+ FORBIDDEN_EXTENSIONS: /* @__PURE__ */ new Set([
24
+ ".exe",
25
+ ".bat",
26
+ ".cmd",
27
+ ".sh",
28
+ ".bin",
29
+ ".elf",
30
+ ".msi",
31
+ ".php",
32
+ ".phtml",
33
+ ".py",
34
+ ".rb",
35
+ ".cgi",
36
+ ".pl"
37
+ ]),
38
+ // Local Blink cache/config directory
39
+ CONFIG_DIR: path.join(os.homedir(), ".blink")
40
+ };
41
+
42
+ // src/machine.ts
43
+ import nodeMachineId from "node-machine-id";
44
+ import crypto from "crypto";
45
+ import os2 from "os";
46
+ import fs from "fs";
47
+ import path2 from "path";
48
+ var { machineId } = nodeMachineId;
49
+ var cachedMachineId = null;
50
+ async function getMachineId() {
51
+ if (cachedMachineId) {
52
+ return cachedMachineId;
53
+ }
54
+ try {
55
+ const id = await machineId();
56
+ cachedMachineId = crypto.createHash("sha256").update(id).digest("hex").substring(0, 32);
57
+ return cachedMachineId;
58
+ } catch {
59
+ const fallbackId = generateFallbackId();
60
+ cachedMachineId = fallbackId;
61
+ return cachedMachineId;
62
+ }
63
+ }
64
+ function generateFallbackId() {
65
+ try {
66
+ if (!fs.existsSync(CONFIG.CONFIG_DIR)) {
67
+ fs.mkdirSync(CONFIG.CONFIG_DIR, { recursive: true });
68
+ }
69
+ const idFile = path2.join(CONFIG.CONFIG_DIR, "machine.id");
70
+ if (fs.existsSync(idFile)) {
71
+ const stored = fs.readFileSync(idFile, "utf8").trim();
72
+ if (stored.length >= 16) return stored;
73
+ }
74
+ const cpus = os2.cpus();
75
+ const cpuModel = cpus.length > 0 ? cpus[0].model : "";
76
+ const totalMem = os2.totalmem().toString();
77
+ const platform = os2.platform();
78
+ const hostname = os2.hostname();
79
+ const hash = crypto.createHash("sha256").update(`${hostname}-${platform}-${cpuModel}-${totalMem}`).digest("hex").substring(0, 32);
80
+ fs.writeFileSync(idFile, hash, "utf8");
81
+ return hash;
82
+ } catch {
83
+ return crypto.randomBytes(16).toString("hex");
84
+ }
85
+ }
86
+
87
+ // src/detector.ts
88
+ import fs2 from "fs";
89
+ import path3 from "path";
90
+ function resolveDeployDirectory(userSpecifiedPath) {
91
+ const cwd = process.cwd();
92
+ let resolvedPath = "";
93
+ if (userSpecifiedPath) {
94
+ resolvedPath = path3.resolve(cwd, userSpecifiedPath);
95
+ if (!fs2.existsSync(resolvedPath)) {
96
+ throw new Error(`Specified path does not exist: ${userSpecifiedPath}`);
97
+ }
98
+ const stat = fs2.statSync(resolvedPath);
99
+ if (!stat.isDirectory()) {
100
+ throw new Error(`Specified path is not a directory: ${userSpecifiedPath}`);
101
+ }
102
+ } else {
103
+ for (const candidate of CONFIG.DEFAULT_BUILD_DIRS) {
104
+ const candidatePath = path3.join(cwd, candidate);
105
+ if (fs2.existsSync(candidatePath) && fs2.statSync(candidatePath).isDirectory()) {
106
+ resolvedPath = candidatePath;
107
+ break;
108
+ }
109
+ }
110
+ if (!resolvedPath) {
111
+ resolvedPath = cwd;
112
+ }
113
+ }
114
+ const { totalFiles, totalSizeBytes, hasIndexHtml } = scanDirectory(resolvedPath);
115
+ if (totalFiles === 0) {
116
+ throw new Error(`Directory is empty: ${resolvedPath}`);
117
+ }
118
+ return {
119
+ targetPath: resolvedPath,
120
+ hasIndexHtml,
121
+ totalFiles,
122
+ totalSizeBytes
123
+ };
124
+ }
125
+ function scanDirectory(dir) {
126
+ let totalFiles = 0;
127
+ let totalSizeBytes = 0;
128
+ let hasIndexHtml = false;
129
+ function walk(currentDir) {
130
+ const entries = fs2.readdirSync(currentDir, { withFileTypes: true });
131
+ for (const entry of entries) {
132
+ if (entry.name === "node_modules" || entry.name === ".git" || entry.name.startsWith(".")) {
133
+ continue;
134
+ }
135
+ const fullPath = path3.join(currentDir, entry.name);
136
+ if (entry.isDirectory()) {
137
+ walk(fullPath);
138
+ } else if (entry.isFile()) {
139
+ totalFiles++;
140
+ const stat = fs2.statSync(fullPath);
141
+ totalSizeBytes += stat.size;
142
+ if (entry.name.toLowerCase() === "index.html" && path3.relative(dir, fullPath) === "index.html") {
143
+ hasIndexHtml = true;
144
+ }
145
+ }
146
+ }
147
+ }
148
+ walk(dir);
149
+ return { totalFiles, totalSizeBytes, hasIndexHtml };
150
+ }
151
+
152
+ // src/packager.ts
153
+ import { createRequire } from "module";
154
+ import fs3 from "fs";
155
+ import path4 from "path";
156
+ var require2 = createRequire(import.meta.url);
157
+ var archiver = require2("archiver");
158
+ async function packageDirectory(directoryPath) {
159
+ return new Promise((resolve, reject) => {
160
+ const chunks = [];
161
+ let totalUncompressedBytes = 0;
162
+ let fileCount = 0;
163
+ const archive = archiver("zip", {
164
+ zlib: { level: 9 }
165
+ // Maximum compression for fast network upload
166
+ });
167
+ archive.on("data", (chunk) => {
168
+ chunks.push(chunk);
169
+ });
170
+ archive.on("error", (err) => {
171
+ reject(err);
172
+ });
173
+ archive.on("end", () => {
174
+ const buffer = Buffer.concat(chunks);
175
+ resolve({
176
+ buffer,
177
+ sizeBytes: buffer.length,
178
+ fileCount
179
+ });
180
+ });
181
+ function addFiles(currentDir, relativeDir = "") {
182
+ const entries = fs3.readdirSync(currentDir, { withFileTypes: true });
183
+ for (const entry of entries) {
184
+ const fullPath = path4.join(currentDir, entry.name);
185
+ const relPath = relativeDir ? `${relativeDir}/${entry.name}` : entry.name;
186
+ if (entry.name === "node_modules" || entry.name === ".git" || entry.name.startsWith(".env") || entry.name === ".DS_Store" || entry.name === "Thumbs.db") {
187
+ continue;
188
+ }
189
+ if (entry.isDirectory()) {
190
+ addFiles(fullPath, relPath);
191
+ } else if (entry.isFile()) {
192
+ const ext = path4.extname(entry.name).toLowerCase();
193
+ if (CONFIG.FORBIDDEN_EXTENSIONS.has(ext)) {
194
+ archive.abort();
195
+ return reject(
196
+ new Error(
197
+ `Security violation: Executable/server-side file detected: "${relPath}". Blink only allows static frontend assets.`
198
+ )
199
+ );
200
+ }
201
+ const stat = fs3.statSync(fullPath);
202
+ totalUncompressedBytes += stat.size;
203
+ if (totalUncompressedBytes > CONFIG.MAX_UPLOAD_SIZE_BYTES) {
204
+ archive.abort();
205
+ return reject(
206
+ new Error(
207
+ `Upload size exceeded 50MB limit (Current: ${(totalUncompressedBytes / (1024 * 1024)).toFixed(1)}MB). Please remove large assets.`
208
+ )
209
+ );
210
+ }
211
+ fileCount++;
212
+ archive.file(fullPath, { name: relPath });
213
+ }
214
+ }
215
+ }
216
+ try {
217
+ addFiles(directoryPath);
218
+ archive.finalize();
219
+ } catch (err) {
220
+ reject(err);
221
+ }
222
+ });
223
+ }
224
+
225
+ // src/api.ts
226
+ var BlinkApi = class {
227
+ constructor(baseUrl) {
228
+ this.baseUrl = baseUrl;
229
+ this.baseUrl = baseUrl.replace(/\/+$/, "");
230
+ }
231
+ baseUrl;
232
+ /**
233
+ * Deploys a packed website archive to the Blink server.
234
+ */
235
+ async deploy(params) {
236
+ const formData = new FormData();
237
+ const blob = new Blob([params.buffer], { type: "application/zip" });
238
+ formData.append("archive", blob, "site.zip");
239
+ formData.append("machineId", params.machineId);
240
+ formData.append("duration", params.duration.toString());
241
+ if (params.alias) {
242
+ formData.append("alias", params.alias);
243
+ }
244
+ if (params.replace) {
245
+ formData.append("replace", "true");
246
+ }
247
+ let response;
248
+ try {
249
+ response = await fetch(`${this.baseUrl}/api/deploy`, {
250
+ method: "POST",
251
+ body: formData,
252
+ headers: {
253
+ "X-Blink-Machine-Id": params.machineId
254
+ }
255
+ });
256
+ } catch (err) {
257
+ throw new Error(`Failed to connect to Blink server at ${this.baseUrl}: ${err.message}`);
258
+ }
259
+ const data = await response.json();
260
+ if (!response.ok) {
261
+ const err = new Error(data.error || "Deployment failed");
262
+ err.code = data.code;
263
+ err.activeSite = data.activeSite;
264
+ throw err;
265
+ }
266
+ return data;
267
+ }
268
+ /**
269
+ * Queries the status of the current active site for this machine.
270
+ */
271
+ async getStatus(machineId2) {
272
+ let response;
273
+ try {
274
+ response = await fetch(`${this.baseUrl}/api/status?machineId=${encodeURIComponent(machineId2)}`, {
275
+ method: "GET",
276
+ headers: {
277
+ "X-Blink-Machine-Id": machineId2
278
+ }
279
+ });
280
+ } catch (err) {
281
+ throw new Error(`Failed to connect to Blink server at ${this.baseUrl}: ${err.message}`);
282
+ }
283
+ const data = await response.json();
284
+ if (!response.ok) {
285
+ throw new Error(data.error || "Failed to fetch status");
286
+ }
287
+ return data;
288
+ }
289
+ /**
290
+ * Deletes the currently active deployment for this machine.
291
+ */
292
+ async deleteCurrentSite(machineId2) {
293
+ let response;
294
+ try {
295
+ response = await fetch(`${this.baseUrl}/api/deploy`, {
296
+ method: "DELETE",
297
+ headers: {
298
+ "Content-Type": "application/json",
299
+ "X-Blink-Machine-Id": machineId2
300
+ },
301
+ body: JSON.stringify({ machineId: machineId2 })
302
+ });
303
+ } catch (err) {
304
+ throw new Error(`Failed to connect to Blink server at ${this.baseUrl}: ${err.message}`);
305
+ }
306
+ const data = await response.json();
307
+ if (!response.ok) {
308
+ throw new Error(data.error || "Failed to delete site");
309
+ }
310
+ return data;
311
+ }
312
+ };
313
+
314
+ // src/ui.ts
315
+ import chalk from "chalk";
316
+ import boxen from "boxen";
317
+ import clipboardy from "clipboardy";
318
+ import qrcode from "qrcode-terminal";
319
+ function renderBanner() {
320
+ console.log(
321
+ chalk.bold.hex("#FF6B6B")("\n \u26A1 BLINK") + chalk.gray(" - Ephemeral Frontend Hosting (Zero-Signup)\n")
322
+ );
323
+ }
324
+ async function copyToClipboard(url) {
325
+ try {
326
+ await clipboardy.write(url);
327
+ return true;
328
+ } catch {
329
+ return false;
330
+ }
331
+ }
332
+ function renderSuccess(data) {
333
+ const expiresDate = new Date(data.expiresAt);
334
+ const formattedTime = expiresDate.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
335
+ const sizeMb = (data.sizeBytes / (1024 * 1024)).toFixed(2);
336
+ const copiedBadge = data.copied ? chalk.greenBright(" \u2714 Copied to clipboard!") : chalk.gray(" (Use Ctrl+C to copy)");
337
+ const content = [
338
+ `${chalk.bold.green("\u2714 Successfully Deployed!")}`,
339
+ "",
340
+ ` ${chalk.bold("URL:")} ${chalk.cyan.underline.bold(data.url)}${copiedBadge}`,
341
+ ` ${chalk.bold("Expires In:")} ${chalk.yellow.bold(`${data.durationMinutes} minutes`)} ${chalk.gray(`(at ${formattedTime})`)}`,
342
+ ` ${chalk.bold("Files:")} ${chalk.white(data.fileCount)} files ${chalk.gray(`(${sizeMb} MB compressed)`)}`,
343
+ "",
344
+ chalk.dim(" Site will automatically self-destruct when time expires.")
345
+ ].join("\n");
346
+ console.log(
347
+ boxen(content, {
348
+ padding: 1,
349
+ margin: 1,
350
+ borderColor: "green",
351
+ borderStyle: "round"
352
+ })
353
+ );
354
+ }
355
+ function renderQrCode(url) {
356
+ console.log(chalk.bold.gray("\n Scan with phone to preview:"));
357
+ qrcode.generate(url, { small: true });
358
+ }
359
+ function renderActiveStatus(data) {
360
+ const expiresDate = new Date(data.expiresAt);
361
+ const formattedTime = expiresDate.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
362
+ const content = [
363
+ `${chalk.bold.cyan("\u25CF Active Blink Deployment")}`,
364
+ "",
365
+ ` ${chalk.bold("URL:")} ${chalk.cyan.underline.bold(data.url)}`,
366
+ ` ${chalk.bold("Site ID:")} ${chalk.gray(data.siteId)}`,
367
+ ` ${chalk.bold("Time Left:")} ${chalk.yellow.bold(`${data.remainingMinutes}m remaining`)} ${chalk.gray(`(until ${formattedTime})`)}`,
368
+ "",
369
+ chalk.dim(" To delete early: ") + chalk.bold("blink delete")
370
+ ].join("\n");
371
+ console.log(
372
+ boxen(content, {
373
+ padding: 1,
374
+ margin: 1,
375
+ borderColor: "cyan",
376
+ borderStyle: "round"
377
+ })
378
+ );
379
+ }
380
+
381
+ // src/cli.ts
382
+ var program = new Command();
383
+ program.name("blink").description("Deploy static frontend websites temporarily without signing up").version("1.0.0").argument("[directory]", "Frontend build directory to deploy (auto-detected if omitted)").option("-t, --time <minutes>", "Expiration time: 15, 35, or 45 mins", (val) => parseInt(val, 10)).option("-n, --name <alias>", 'Custom subdomain alias (e.g. "my-project")').option("--qr", "Display a terminal QR code for phone scanning").option("--replace", "Automatically replace any active deployment without prompting").option("--server <url>", "Custom Blink server URL", CONFIG.DEFAULT_SERVER_URL).action(async (directoryArg, options) => {
384
+ try {
385
+ renderBanner();
386
+ const serverUrl = options.server || CONFIG.DEFAULT_SERVER_URL;
387
+ const api = new BlinkApi(serverUrl);
388
+ const spinner = ora("Detecting frontend directory...").start();
389
+ let detection;
390
+ try {
391
+ detection = resolveDeployDirectory(directoryArg);
392
+ spinner.succeed(
393
+ `Found directory: ${chalk2.cyan(detection.targetPath)} ${chalk2.gray(`(${detection.totalFiles} files)`)}`
394
+ );
395
+ } catch (err) {
396
+ spinner.fail(err.message);
397
+ process.exit(1);
398
+ }
399
+ if (!detection.hasIndexHtml) {
400
+ console.log(
401
+ chalk2.yellow(' \u26A0 Warning: No "index.html" found in the root of the target directory.')
402
+ );
403
+ console.log(chalk2.gray(" Visitors might see a 404 unless your frontend has an index.html.\n"));
404
+ }
405
+ let duration = options.time;
406
+ if (!duration || !CONFIG.ALLOWED_DURATIONS.includes(duration)) {
407
+ if (process.stdout.isTTY) {
408
+ const response = await prompts({
409
+ type: "select",
410
+ name: "selectedDuration",
411
+ message: "Select deployment duration (auto-deleted when expired):",
412
+ choices: [
413
+ { title: "15 minutes (Default)", value: 15 },
414
+ { title: "35 minutes", value: 35 },
415
+ { title: "45 minutes", value: 45 }
416
+ ],
417
+ initial: 0
418
+ });
419
+ if (!response.selectedDuration) {
420
+ console.log(chalk2.gray("\nDeployment cancelled."));
421
+ process.exit(0);
422
+ }
423
+ duration = response.selectedDuration;
424
+ } else {
425
+ duration = CONFIG.DEFAULT_DURATION;
426
+ }
427
+ }
428
+ const machineSpinner = ora("Authenticating machine...").start();
429
+ const machineId2 = await getMachineId();
430
+ machineSpinner.succeed(`Machine verified: ${chalk2.gray(machineId2.substring(0, 12) + "...")}`);
431
+ const packSpinner = ora("Packaging website assets...").start();
432
+ let packageResult;
433
+ try {
434
+ packageResult = await packageDirectory(detection.targetPath);
435
+ packSpinner.succeed(
436
+ `Packaged ${packageResult.fileCount} files (${(packageResult.sizeBytes / (1024 * 1024)).toFixed(2)} MB)`
437
+ );
438
+ } catch (err) {
439
+ packSpinner.fail(err.message);
440
+ process.exit(1);
441
+ }
442
+ const deploySpinner = ora("Deploying to Blink cloud...").start();
443
+ let deployResult;
444
+ let shouldReplace = options.replace || false;
445
+ try {
446
+ deployResult = await api.deploy({
447
+ buffer: packageResult.buffer,
448
+ machineId: machineId2,
449
+ duration,
450
+ alias: options.name,
451
+ replace: shouldReplace
452
+ });
453
+ } catch (err) {
454
+ deploySpinner.stop();
455
+ if (err.code === "ACTIVE_SITE_EXISTS" && err.activeSite) {
456
+ console.log(
457
+ chalk2.yellow(
458
+ `
459
+ \u26A0 You already have an active deployment: ${chalk2.bold.cyan(err.activeSite.url)}`
460
+ )
461
+ );
462
+ if (process.stdout.isTTY) {
463
+ const confirm = await prompts({
464
+ type: "confirm",
465
+ name: "replace",
466
+ message: "Would you like to replace your active deployment?",
467
+ initial: true
468
+ });
469
+ if (confirm.replace) {
470
+ const retrySpinner = ora("Replacing active deployment...").start();
471
+ try {
472
+ deployResult = await api.deploy({
473
+ buffer: packageResult.buffer,
474
+ machineId: machineId2,
475
+ duration,
476
+ alias: options.name,
477
+ replace: true
478
+ });
479
+ retrySpinner.succeed("Replacement complete!");
480
+ } catch (retryErr) {
481
+ retrySpinner.fail(retryErr.message);
482
+ process.exit(1);
483
+ }
484
+ } else {
485
+ console.log(
486
+ chalk2.gray("\nDeployment kept as-is. Run ") + chalk2.bold("blink delete") + chalk2.gray(" to remove it.\n")
487
+ );
488
+ process.exit(0);
489
+ }
490
+ } else {
491
+ console.error(
492
+ chalk2.red(
493
+ "Deployment rejected: active site already running. Use --replace to overwrite.\n"
494
+ )
495
+ );
496
+ process.exit(1);
497
+ }
498
+ } else {
499
+ deploySpinner.fail(err.message);
500
+ process.exit(1);
501
+ }
502
+ }
503
+ if (deployResult) {
504
+ deploySpinner.succeed("Uploaded & configured!");
505
+ const copied = await copyToClipboard(deployResult.url);
506
+ renderSuccess({
507
+ url: deployResult.url,
508
+ expiresAt: deployResult.expiresAt,
509
+ durationMinutes: deployResult.durationMinutes,
510
+ copied,
511
+ fileCount: packageResult.fileCount,
512
+ sizeBytes: packageResult.sizeBytes
513
+ });
514
+ if (options.qr) {
515
+ renderQrCode(deployResult.url);
516
+ }
517
+ }
518
+ } catch (err) {
519
+ console.error(chalk2.red(`
520
+ Error: ${err.message}`));
521
+ process.exit(1);
522
+ }
523
+ });
524
+ program.command("status").description("Check details of your current active Blink deployment").option("--server <url>", "Custom Blink server URL", CONFIG.DEFAULT_SERVER_URL).action(async (options) => {
525
+ try {
526
+ renderBanner();
527
+ const spinner = ora("Checking deployment status...").start();
528
+ const machineId2 = await getMachineId();
529
+ const api = new BlinkApi(options.server || CONFIG.DEFAULT_SERVER_URL);
530
+ const status = await api.getStatus(machineId2);
531
+ spinner.stop();
532
+ if (status.hasActiveSite && status.url && status.siteId && status.expiresAt) {
533
+ renderActiveStatus({
534
+ url: status.url,
535
+ siteId: status.siteId,
536
+ createdAt: status.createdAt || "",
537
+ expiresAt: status.expiresAt,
538
+ remainingMinutes: status.remainingMinutes || 0
539
+ });
540
+ } else {
541
+ console.log(chalk2.gray(" No active deployment found for this machine."));
542
+ console.log(chalk2.gray(" Run ") + chalk2.bold.cyan("blink") + chalk2.gray(" in any project folder to deploy.\n"));
543
+ }
544
+ } catch (err) {
545
+ console.error(chalk2.red(`
546
+ Error: ${err.message}`));
547
+ process.exit(1);
548
+ }
549
+ });
550
+ program.command("delete").alias("destroy").description("Immediately delete your current active Blink deployment").option("--server <url>", "Custom Blink server URL", CONFIG.DEFAULT_SERVER_URL).action(async (options) => {
551
+ try {
552
+ renderBanner();
553
+ const machineId2 = await getMachineId();
554
+ const api = new BlinkApi(options.server || CONFIG.DEFAULT_SERVER_URL);
555
+ const spinner = ora("Deleting active deployment...").start();
556
+ const result = await api.deleteCurrentSite(machineId2);
557
+ spinner.succeed(result.message || "Active deployment deleted successfully.");
558
+ console.log("");
559
+ } catch (err) {
560
+ console.error(chalk2.red(`
561
+ Error: ${err.message}`));
562
+ process.exit(1);
563
+ }
564
+ });
565
+ program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,60 @@
1
1
  {
2
2
  "name": "blinkhost",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.1",
4
+ "description": "Deploy static frontend websites temporarily without signing up.",
5
+ "main": "dist/cli.js",
6
+ "bin": {
7
+ "blink": "./dist/cli.js",
8
+ "blinkhost": "./dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "type": "module",
14
+ "scripts": {
15
+ "build": "tsup",
16
+ "dev": "tsup --watch",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "blink",
21
+ "deploy",
22
+ "frontend",
23
+ "static",
24
+ "temporary",
25
+ "ephemeral",
26
+ "cli"
27
+ ],
28
+ "author": "Jyothirao",
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/Jyothirao2685/blink.git"
33
+ },
34
+ "bugs": {
35
+ "url": "https://github.com/Jyothirao2685/blink/issues"
36
+ },
37
+ "homepage": "https://restrodash.in",
38
+ "dependencies": {
39
+ "archiver": "^7.0.1",
40
+ "boxen": "^8.0.1",
41
+ "chalk": "^5.4.1",
42
+ "clipboardy": "^4.0.0",
43
+ "commander": "^13.1.0",
44
+ "node-machine-id": "^1.1.12",
45
+ "ora": "^8.2.0",
46
+ "prompts": "^2.4.2",
47
+ "qrcode-terminal": "^0.12.0"
48
+ },
49
+ "devDependencies": {
50
+ "@types/archiver": "^6.0.3",
51
+ "@types/node": "^22.13.10",
52
+ "@types/prompts": "^2.4.9",
53
+ "@types/qrcode-terminal": "^0.12.2",
54
+ "tsup": "^9.0.0",
55
+ "typescript": "^5.8.2"
56
+ },
57
+ "overrides": {
58
+ "minimatch": "^11.0.8"
59
+ }
60
+ }