apiblaze 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.
Files changed (3) hide show
  1. package/README.md +54 -0
  2. package/dist/index.js +517 -0
  3. package/package.json +45 -0
package/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # apiblaze
2
+
3
+ CLI for [APIblaze](https://apiblaze.com) — instantly tunnel localhost to your APIblaze projects during development.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g apiblaze
9
+ ```
10
+
11
+ Or run without installing:
12
+
13
+ ```bash
14
+ npx apiblaze dev
15
+ ```
16
+
17
+ ## Requirements
18
+
19
+ - Node.js 18+
20
+
21
+ ## Quick start
22
+
23
+ ```bash
24
+ # 1. Log in to your APIblaze account
25
+ apiblaze login
26
+
27
+ # 2. Start tunneling (default port 3000)
28
+ apiblaze dev
29
+
30
+ # Or specify a different port
31
+ apiblaze dev --port 8080
32
+ ```
33
+
34
+ ## Commands
35
+
36
+ | Command | Description |
37
+ |---|---|
38
+ | `apiblaze login` | Authenticate with your APIblaze account |
39
+ | `apiblaze dev [--port <n>]` | Tunnel localhost projects through Cloudflare |
40
+
41
+ ## How it works
42
+
43
+ `apiblaze dev` automatically:
44
+
45
+ 1. Finds all your APIblaze projects that target `localhost`
46
+ 2. Downloads and starts a [Cloudflare Quick Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/) (no account required)
47
+ 3. Registers the tunnel URL with APIblaze so incoming API requests are forwarded to your machine
48
+ 4. Streams live traffic logs to your terminal in real-time
49
+
50
+ On Ctrl+C the tunnel is cleanly deregistered.
51
+
52
+ ## License
53
+
54
+ MIT
package/dist/index.js ADDED
@@ -0,0 +1,517 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/index.ts
27
+ var import_commander = require("commander");
28
+ var import_chalk4 = __toESM(require("chalk"));
29
+
30
+ // src/types.ts
31
+ var ApiError = class extends Error {
32
+ constructor(status, message) {
33
+ super(message);
34
+ this.status = status;
35
+ this.name = "ApiError";
36
+ }
37
+ };
38
+
39
+ // src/commands/login.ts
40
+ var import_chalk = __toESM(require("chalk"));
41
+ var import_ora = __toESM(require("ora"));
42
+
43
+ // src/lib/auth.ts
44
+ var fs = __toESM(require("fs"));
45
+ var os = __toESM(require("os"));
46
+ var path = __toESM(require("path"));
47
+ var APIBLAZE_DIR = path.join(os.homedir(), ".apiblaze");
48
+ var CREDENTIALS_PATH = path.join(APIBLAZE_DIR, "credentials.json");
49
+ function saveCredentials(creds) {
50
+ fs.mkdirSync(APIBLAZE_DIR, { recursive: true });
51
+ fs.writeFileSync(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), "utf-8");
52
+ fs.chmodSync(CREDENTIALS_PATH, 384);
53
+ }
54
+ function loadCredentials() {
55
+ try {
56
+ const raw = fs.readFileSync(CREDENTIALS_PATH, "utf-8");
57
+ return JSON.parse(raw);
58
+ } catch {
59
+ return null;
60
+ }
61
+ }
62
+ function getAccessToken() {
63
+ const creds = loadCredentials();
64
+ if (!creds) {
65
+ throw new Error("Not authenticated. Run `apiblaze login` first.");
66
+ }
67
+ if (Date.now() >= creds.expiresAt) {
68
+ throw new Error("Session expired. Run `apiblaze login` to re-authenticate.");
69
+ }
70
+ return creds.accessToken;
71
+ }
72
+ function getApiblazeDir() {
73
+ return APIBLAZE_DIR;
74
+ }
75
+
76
+ // src/commands/login.ts
77
+ var AUTH_BASE = "https://auth.apiblaze.com";
78
+ var CLIENT_ID = "emdE4-Pt9LGOAXL5MA1zEQ";
79
+ function openBrowser(url) {
80
+ const { exec } = require("child_process");
81
+ const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "${url}"` : `xdg-open "${url}"`;
82
+ exec(cmd, () => {
83
+ });
84
+ }
85
+ async function requestDeviceCode() {
86
+ const res = await fetch(`${AUTH_BASE}/oauth/device/code`, {
87
+ method: "POST",
88
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
89
+ body: new URLSearchParams({ client_id: CLIENT_ID, scope: "offline_access" })
90
+ });
91
+ if (!res.ok) {
92
+ const body = await res.text();
93
+ throw new Error(`Failed to start device flow: ${res.status} ${body}`);
94
+ }
95
+ return res.json();
96
+ }
97
+ async function pollForToken(deviceCode, intervalSecs) {
98
+ let pollInterval = intervalSecs * 1e3;
99
+ while (true) {
100
+ await new Promise((r) => setTimeout(r, pollInterval));
101
+ const res = await fetch(`${AUTH_BASE}/oauth/token`, {
102
+ method: "POST",
103
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
104
+ body: new URLSearchParams({
105
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
106
+ device_code: deviceCode,
107
+ client_id: CLIENT_ID
108
+ })
109
+ });
110
+ const body = await res.json();
111
+ if (res.ok) {
112
+ return body;
113
+ }
114
+ if (body.error === "authorization_pending") {
115
+ continue;
116
+ } else if (body.error === "slow_down") {
117
+ pollInterval += 5e3;
118
+ } else if (body.error === "expired_token") {
119
+ throw new Error("Device code expired. Run `apiblaze login` again.");
120
+ } else if (body.error === "access_denied") {
121
+ throw new Error("Login was denied or cancelled.");
122
+ } else {
123
+ throw new Error(`Unexpected error during login: ${body.error ?? "unknown"}`);
124
+ }
125
+ }
126
+ }
127
+ async function runLogin() {
128
+ console.log(import_chalk.default.bold("\nLogging in to APIblaze...\n"));
129
+ const deviceAuth = await requestDeviceCode();
130
+ console.log(`${import_chalk.default.cyan("\u2192")} Open this URL in your browser:`);
131
+ console.log(` ${import_chalk.default.bold.underline(deviceAuth.verification_uri_complete ?? deviceAuth.verification_uri)}
132
+ `);
133
+ if (!deviceAuth.verification_uri_complete) {
134
+ console.log(`${import_chalk.default.cyan("\u2192")} Enter code: ${import_chalk.default.bold(deviceAuth.user_code)}
135
+ `);
136
+ }
137
+ openBrowser(deviceAuth.verification_uri_complete ?? deviceAuth.verification_uri);
138
+ const spinner = (0, import_ora.default)("Waiting for authorization...").start();
139
+ const token = await pollForToken(deviceAuth.device_code, deviceAuth.interval);
140
+ saveCredentials({
141
+ accessToken: token.access_token,
142
+ refreshToken: token.refresh_token,
143
+ expiresAt: Date.now() + token.expires_in * 1e3,
144
+ tokenType: token.token_type
145
+ });
146
+ spinner.succeed(import_chalk.default.green("Logged in successfully!"));
147
+ }
148
+
149
+ // src/commands/dev.ts
150
+ var import_chalk3 = __toESM(require("chalk"));
151
+ var import_ora3 = __toESM(require("ora"));
152
+ var import_inquirer = __toESM(require("inquirer"));
153
+
154
+ // src/lib/api.ts
155
+ var API_BASE = "https://adminapioauth.abz.run/1.0.0/prod";
156
+ async function apiFetch(path3, options = {}) {
157
+ const token = getAccessToken();
158
+ const url = `${API_BASE}${path3}`;
159
+ const res = await fetch(url, {
160
+ ...options,
161
+ headers: {
162
+ "Content-Type": "application/json",
163
+ Authorization: `Bearer ${token}`,
164
+ ...options.headers ?? {}
165
+ }
166
+ });
167
+ if (!res.ok) {
168
+ let message = res.statusText;
169
+ try {
170
+ const body = await res.json();
171
+ message = body.message ?? body.error ?? message;
172
+ } catch {
173
+ }
174
+ throw new ApiError(res.status, message);
175
+ }
176
+ if (res.status === 204) {
177
+ return void 0;
178
+ }
179
+ return res.json();
180
+ }
181
+ async function getLocalhostTargets() {
182
+ return apiFetch("/projects/localhost-targets");
183
+ }
184
+ async function putDevTunnel(payload) {
185
+ return apiFetch("/dev-tunnel", {
186
+ method: "PUT",
187
+ body: JSON.stringify(payload)
188
+ });
189
+ }
190
+ async function deleteDevTunnel() {
191
+ return apiFetch("/dev-tunnel", { method: "DELETE" });
192
+ }
193
+
194
+ // src/lib/cloudflared.ts
195
+ var fs2 = __toESM(require("fs"));
196
+ var path2 = __toESM(require("path"));
197
+ var readline = __toESM(require("readline"));
198
+ var import_child_process = require("child_process");
199
+ var import_ora2 = __toESM(require("ora"));
200
+ var TUNNEL_URL_RE = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/;
201
+ var DOWNLOAD_TIMEOUT_MS = 3e4;
202
+ var TUNNEL_START_TIMEOUT_MS = 3e4;
203
+ function getCloudflaredFilename() {
204
+ const platform = process.platform;
205
+ const arch = process.arch;
206
+ if (platform === "darwin" && arch === "arm64") return "cloudflared-darwin-arm64";
207
+ if (platform === "darwin" && arch === "x64") return "cloudflared-darwin-amd64";
208
+ if (platform === "linux" && arch === "arm64") return "cloudflared-linux-arm64";
209
+ if (platform === "linux" && arch === "x64") return "cloudflared-linux-amd64";
210
+ if (platform === "win32" && arch === "x64") return "cloudflared-windows-amd64.exe";
211
+ throw new Error(`Unsupported platform: ${platform} ${arch}`);
212
+ }
213
+ function getCloudflaredBinPath() {
214
+ const filename = process.platform === "win32" ? "cloudflared.exe" : "cloudflared";
215
+ return path2.join(getApiblazeDir(), "bin", filename);
216
+ }
217
+ function isExecutable(filePath) {
218
+ try {
219
+ fs2.accessSync(filePath, fs2.constants.X_OK);
220
+ return true;
221
+ } catch {
222
+ return false;
223
+ }
224
+ }
225
+ async function downloadCloudflared(binPath) {
226
+ const filename = getCloudflaredFilename();
227
+ const url = `https://github.com/cloudflare/cloudflared/releases/latest/download/${filename}`;
228
+ const res = await fetch(url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) });
229
+ if (!res.ok || !res.body) {
230
+ throw new Error(`Failed to download cloudflared: ${res.status} ${res.statusText}`);
231
+ }
232
+ fs2.mkdirSync(path2.dirname(binPath), { recursive: true });
233
+ const tmpPath = `${binPath}.tmp`;
234
+ const writer = fs2.createWriteStream(tmpPath);
235
+ const reader = res.body.getReader();
236
+ try {
237
+ while (true) {
238
+ const { done, value } = await reader.read();
239
+ if (done) break;
240
+ await new Promise((resolve, reject) => {
241
+ writer.write(value, (err) => err ? reject(err) : resolve());
242
+ });
243
+ }
244
+ } finally {
245
+ reader.releaseLock();
246
+ }
247
+ await new Promise((resolve, reject) => {
248
+ writer.end((err) => err ? reject(err) : resolve());
249
+ });
250
+ fs2.renameSync(tmpPath, binPath);
251
+ if (process.platform !== "win32") {
252
+ fs2.chmodSync(binPath, 493);
253
+ }
254
+ }
255
+ async function ensureCloudflared() {
256
+ const binPath = getCloudflaredBinPath();
257
+ if (fs2.existsSync(binPath) && isExecutable(binPath)) {
258
+ return;
259
+ }
260
+ const spinner = (0, import_ora2.default)("Downloading cloudflared...").start();
261
+ try {
262
+ await downloadCloudflared(binPath);
263
+ spinner.succeed("cloudflared downloaded.");
264
+ } catch (err) {
265
+ spinner.fail("Failed to download cloudflared.");
266
+ throw err;
267
+ }
268
+ }
269
+ function spawnCloudflared(port) {
270
+ return new Promise((resolve, reject) => {
271
+ const binPath = getCloudflaredBinPath();
272
+ const proc = (0, import_child_process.spawn)(binPath, ["tunnel", "--url", `http://localhost:${port}`], {
273
+ stdio: ["ignore", "ignore", "pipe"]
274
+ });
275
+ const timer = setTimeout(() => {
276
+ proc.kill();
277
+ reject(new Error("Timed out waiting for cloudflared tunnel URL (30s). Is something already running on the port?"));
278
+ }, TUNNEL_START_TIMEOUT_MS);
279
+ const rl = readline.createInterface({ input: proc.stderr });
280
+ rl.on("line", (line) => {
281
+ const match = line.match(TUNNEL_URL_RE);
282
+ if (match) {
283
+ clearTimeout(timer);
284
+ rl.close();
285
+ resolve({ process: proc, tunnelUrl: match[0] });
286
+ }
287
+ });
288
+ proc.on("error", (err) => {
289
+ clearTimeout(timer);
290
+ reject(new Error(`Failed to start cloudflared: ${err.message}`));
291
+ });
292
+ proc.on("exit", (code) => {
293
+ clearTimeout(timer);
294
+ if (code !== null && code !== 0) {
295
+ reject(new Error(`cloudflared exited with code ${code}`));
296
+ }
297
+ });
298
+ });
299
+ }
300
+ function killCloudflared(proc) {
301
+ try {
302
+ proc.kill("SIGTERM");
303
+ } catch {
304
+ }
305
+ }
306
+
307
+ // src/lib/traffic.ts
308
+ var import_ws = __toESM(require("ws"));
309
+ var import_chalk2 = __toESM(require("chalk"));
310
+ var METHOD_COLORS = {
311
+ GET: import_chalk2.default.cyan,
312
+ POST: import_chalk2.default.green,
313
+ PUT: import_chalk2.default.yellow,
314
+ PATCH: import_chalk2.default.magenta,
315
+ DELETE: import_chalk2.default.red,
316
+ HEAD: import_chalk2.default.blue,
317
+ OPTIONS: import_chalk2.default.gray
318
+ };
319
+ function colorMethod(method) {
320
+ const colorFn = METHOD_COLORS[method.toUpperCase()] ?? import_chalk2.default.white;
321
+ return colorFn(method.padEnd(7));
322
+ }
323
+ function colorStatus(status) {
324
+ const s = String(status);
325
+ if (status >= 500) return import_chalk2.default.red(s);
326
+ if (status >= 400) return import_chalk2.default.yellow(s);
327
+ if (status >= 300) return import_chalk2.default.cyan(s);
328
+ return import_chalk2.default.green(s);
329
+ }
330
+ function colorLatency(latency) {
331
+ const s = `${latency}ms`;
332
+ if (latency < 100) return import_chalk2.default.green(s);
333
+ if (latency < 500) return import_chalk2.default.yellow(s);
334
+ return import_chalk2.default.red(s);
335
+ }
336
+ function formatLogLine(entry) {
337
+ const now = /* @__PURE__ */ new Date();
338
+ const ts = now.toTimeString().slice(0, 8);
339
+ return `${import_chalk2.default.gray(`[${ts}]`)} ${colorMethod(entry.method)} ${import_chalk2.default.white(entry.path)} ${import_chalk2.default.gray("\u2192")} ${colorStatus(entry.status)} ${import_chalk2.default.gray(`(${colorLatency(entry.latency)})`)}`;
340
+ }
341
+ function connectTrafficStream(wsUrl, onEntry) {
342
+ const token = getAccessToken();
343
+ let ws;
344
+ let reconnectCount = 0;
345
+ const MAX_RECONNECTS = 3;
346
+ function connect() {
347
+ ws = new import_ws.default(wsUrl, { headers: { Authorization: `Bearer ${token}` } });
348
+ ws.on("message", (data) => {
349
+ try {
350
+ const entry = JSON.parse(data.toString());
351
+ onEntry(entry);
352
+ } catch {
353
+ }
354
+ });
355
+ ws.on("close", () => {
356
+ if (reconnectCount < MAX_RECONNECTS) {
357
+ reconnectCount++;
358
+ setTimeout(() => connect(), 2e3);
359
+ } else {
360
+ console.warn(import_chalk2.default.yellow("\nTraffic stream disconnected. Stop and restart `apiblaze dev` to reconnect."));
361
+ }
362
+ });
363
+ ws.on("error", (err) => {
364
+ console.warn(import_chalk2.default.yellow(`
365
+ Traffic stream error: ${err.message}`));
366
+ });
367
+ return ws;
368
+ }
369
+ return connect();
370
+ }
371
+
372
+ // src/commands/dev.ts
373
+ async function runDev(options) {
374
+ const creds = loadCredentials();
375
+ if (!creds) {
376
+ console.error(import_chalk3.default.red("Not logged in. Run `apiblaze login` first."));
377
+ process.exit(1);
378
+ }
379
+ let targets;
380
+ {
381
+ const spinner = (0, import_ora3.default)("Fetching your localhost projects...").start();
382
+ try {
383
+ targets = await getLocalhostTargets();
384
+ spinner.stop();
385
+ } catch (err) {
386
+ spinner.fail("Failed to fetch projects.");
387
+ throw err;
388
+ }
389
+ }
390
+ if (targets.length === 0) {
391
+ console.log(import_chalk3.default.yellow("No projects found with localhost targets."));
392
+ console.log("Set a project's upstream target to localhost in your APIblaze dashboard, then try again.");
393
+ process.exit(0);
394
+ }
395
+ let selectedTargets;
396
+ if (targets.length === 1) {
397
+ const { confirmed } = await import_inquirer.default.prompt([{
398
+ type: "confirm",
399
+ name: "confirmed",
400
+ message: `Found 1 project targeting localhost \u2014 tunnel "${import_chalk3.default.bold(targets[0].projectName)}" (${targets[0].tenantName})?`,
401
+ default: true
402
+ }]);
403
+ if (!confirmed) {
404
+ console.log("Aborted.");
405
+ process.exit(0);
406
+ }
407
+ selectedTargets = targets;
408
+ } else {
409
+ const { chosen } = await import_inquirer.default.prompt([{
410
+ type: "checkbox",
411
+ name: "chosen",
412
+ message: `Found ${targets.length} projects targeting localhost \u2014 select which to tunnel:`,
413
+ choices: targets.map((t) => ({
414
+ name: `${import_chalk3.default.bold(t.projectName)} (${t.tenantName}) \u2014 ${t.target}`,
415
+ value: t,
416
+ checked: true
417
+ }))
418
+ }]);
419
+ if (chosen.length === 0) {
420
+ console.log("No projects selected. Aborted.");
421
+ process.exit(0);
422
+ }
423
+ selectedTargets = chosen;
424
+ }
425
+ console.log(
426
+ import_chalk3.default.green(`
427
+ Tunneling ${selectedTargets.length} project(s) to localhost:${options.port}
428
+ `)
429
+ );
430
+ await ensureCloudflared();
431
+ let cfProcess;
432
+ let tunnelUrl;
433
+ {
434
+ const spinner = (0, import_ora3.default)("Starting Cloudflare tunnel...").start();
435
+ try {
436
+ ({ process: cfProcess, tunnelUrl } = await spawnCloudflared(options.port));
437
+ spinner.succeed(`Tunnel active: ${import_chalk3.default.bold.cyan(tunnelUrl)}`);
438
+ } catch (err) {
439
+ spinner.fail("Failed to start cloudflared tunnel.");
440
+ throw err;
441
+ }
442
+ }
443
+ {
444
+ const spinner = (0, import_ora3.default)("Registering tunnel with APIblaze...").start();
445
+ let wsUrl;
446
+ try {
447
+ const result = await putDevTunnel({
448
+ tunnelUrl,
449
+ targets: selectedTargets.map((t) => ({ projectId: t.projectId, tenantId: t.tenantId }))
450
+ });
451
+ wsUrl = result.wsUrl;
452
+ spinner.succeed("Tunnel registered. Proxying traffic.");
453
+ } catch (err) {
454
+ spinner.fail("Failed to register tunnel.");
455
+ killCloudflared(cfProcess);
456
+ throw err;
457
+ }
458
+ let ws;
459
+ console.log("\n" + import_chalk3.default.gray("\u2500".repeat(60)));
460
+ console.log(import_chalk3.default.bold("Live traffic") + import_chalk3.default.gray(" (Ctrl+C to stop)"));
461
+ console.log(import_chalk3.default.gray("\u2500".repeat(60)) + "\n");
462
+ ws = connectTrafficStream(wsUrl, (entry) => {
463
+ console.log(formatLogLine(entry));
464
+ });
465
+ let isCleaningUp = false;
466
+ async function cleanup() {
467
+ if (isCleaningUp) return;
468
+ isCleaningUp = true;
469
+ console.log(import_chalk3.default.gray("\n\nShutting down..."));
470
+ try {
471
+ ws.close();
472
+ } catch {
473
+ }
474
+ await deleteDevTunnel().catch(() => {
475
+ });
476
+ killCloudflared(cfProcess);
477
+ console.log(import_chalk3.default.green("Tunnel stopped."));
478
+ process.exit(0);
479
+ }
480
+ process.on("SIGINT", () => void cleanup());
481
+ process.on("SIGTERM", () => void cleanup());
482
+ await new Promise(() => {
483
+ });
484
+ }
485
+ }
486
+
487
+ // src/index.ts
488
+ var program = new import_commander.Command();
489
+ program.name("apiblaze").description("APIblaze dev tunnel CLI").version("0.1.0");
490
+ program.command("login").description("Authenticate with APIblaze").action(async () => {
491
+ try {
492
+ await runLogin();
493
+ } catch (err) {
494
+ printError(err);
495
+ process.exit(1);
496
+ }
497
+ });
498
+ program.command("dev").description("Start a dev tunnel for your localhost projects").option("-p, --port <number>", "Local port to tunnel", "3000").action(async (opts) => {
499
+ try {
500
+ await runDev({ port: parseInt(opts.port, 10) });
501
+ } catch (err) {
502
+ printError(err);
503
+ process.exit(1);
504
+ }
505
+ });
506
+ function printError(err) {
507
+ if (err instanceof ApiError) {
508
+ console.error(import_chalk4.default.red(`
509
+ API error (${err.status}): ${err.message}`));
510
+ } else if (err instanceof Error) {
511
+ console.error(import_chalk4.default.red(`
512
+ Error: ${err.message}`));
513
+ } else {
514
+ console.error(import_chalk4.default.red("\nUnknown error"));
515
+ }
516
+ }
517
+ program.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "apiblaze",
3
+ "version": "0.1.0",
4
+ "description": "Dev tunnel CLI for APIblaze — route localhost projects through Cloudflare tunnels",
5
+ "keywords": [
6
+ "apiblaze",
7
+ "dev-tunnel",
8
+ "cloudflared",
9
+ "api",
10
+ "proxy"
11
+ ],
12
+ "license": "MIT",
13
+ "author": "APIblaze",
14
+ "bin": {
15
+ "apiblaze": "dist/index.js"
16
+ },
17
+ "main": "./dist/index.js",
18
+ "files": [
19
+ "dist/",
20
+ "README.md"
21
+ ],
22
+ "engines": {
23
+ "node": ">=18.0.0"
24
+ },
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "build:watch": "tsup --watch",
28
+ "typecheck": "tsc --noEmit",
29
+ "prepublishOnly": "npm run build && npm run typecheck"
30
+ },
31
+ "dependencies": {
32
+ "chalk": "^4.1.2",
33
+ "commander": "^11.1.0",
34
+ "inquirer": "^8.2.6",
35
+ "ora": "^5.4.1",
36
+ "ws": "^8.17.0"
37
+ },
38
+ "devDependencies": {
39
+ "@types/inquirer": "^8.2.10",
40
+ "@types/node": "^20.0.0",
41
+ "@types/ws": "^8.5.10",
42
+ "tsup": "^8.0.0",
43
+ "typescript": "^5.4.0"
44
+ }
45
+ }