r2put 1.0.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/README.md ADDED
@@ -0,0 +1,146 @@
1
+ # r2put
2
+
3
+ A futuristic CLI tool for uploading files to Cloudflare R2 with animated progress bars and a polished terminal UI.
4
+
5
+ ## Features
6
+
7
+ - Elegant futuristic TUI with animated progress bars
8
+ - Real-time transfer rate and ETA display
9
+ - Timestamped log messages with typing animation
10
+ - Presigned download URL generation on successful upload
11
+ - Built on React Ink for smooth terminal rendering
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install r2put
17
+ # or
18
+ pnpm add r2put
19
+ # or
20
+ yarn add r2put
21
+ ```
22
+
23
+ ## Prerequisites
24
+
25
+ Set the following environment variables for R2 authentication:
26
+
27
+ ```bash
28
+ export CLOUDFLARE_ACCOUNT_ID="your-account-id"
29
+ export R2_ACCESS_KEY_ID="your-access-key-id"
30
+ export R2_SECRET_ACCESS_KEY="your-secret-access-key"
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ ```bash
36
+ r2put --file <path> --bucket <name> [options]
37
+ ```
38
+
39
+ ### Options
40
+
41
+ | Option | Short | Description | Required |
42
+ |--------|-------|-------------|----------|
43
+ | `--file` | `-f` | Path to file to upload | Yes |
44
+ | `--bucket` | `-b` | Target R2 bucket name | Yes |
45
+ | `--key` | `-k` | Custom object key (defaults to filename) | No |
46
+ | `--region` | `-r` | Region hint for display (default: WNAM) | No |
47
+ | `--help` | `-h` | Show help message | No |
48
+
49
+ ### Examples
50
+
51
+ Upload a file with default object key:
52
+
53
+ ```bash
54
+ r2put --file ./data.bin --bucket production-v4
55
+ ```
56
+
57
+ Upload with custom object key:
58
+
59
+ ```bash
60
+ r2put -f ./local-image.png -b assets -k images/hero.png
61
+ ```
62
+
63
+ Specify region for display:
64
+
65
+ ```bash
66
+ r2put --file ./backup.tar.gz --bucket backups --region ENAM
67
+ ```
68
+
69
+ ## Output
70
+
71
+ The CLI displays:
72
+
73
+ - **Header**: Cloudflare R2 branding
74
+ - **File Info**: Filename and size
75
+ - **Progress Bar**: Animated upload progress with percentage
76
+ - **Stats**: Transfer rate and estimated time remaining
77
+ - **Log Messages**: Timestamped status updates
78
+ - **Footer**: Bucket, encryption, and region info
79
+ - **Result**: Object path and presigned download URL on completion
80
+
81
+ ## Example Output
82
+
83
+ ```
84
+ C L O U D F L A R E R 2
85
+ ULTRA-LOW LATENCY OBJECT STORAGE
86
+
87
+ › Uploading: neural-net-weights.bin [4.2 GB]
88
+
89
+ ✔ COMPLETE 95%
90
+ ████████████████████████████████████████░░░░░░░░░░░░░░░░░░░░
91
+
92
+ TRANSFER RATE: 842.4 MB/S EST. TIME: 4.2S
93
+
94
+ [13:53:02] › Initializing secure handshake with R2-E1...
95
+ [13:53:02] › Payload delivered. Starting edge-layer optimization.
96
+ [13:53:05] ✔ Deployment finalized. Resource available at edge node.
97
+
98
+ ◘ BUCKET ○ ENCRYPTION ◈ REGION
99
+ production-v4 AES-256-GCM WNAM (EDGE)
100
+
101
+ ╔═══════════════════════════════════════════════════════════╗
102
+ ║ UPLOAD COMPLETE ║
103
+ ║ ║
104
+ ║ OBJECT PATH ║
105
+ ║ production-v4/neural-net-weights.bin ║
106
+ ║ ║
107
+ ║ PRESIGNED DOWNLOAD URL ║
108
+ ║ https://...r2.cloudflarestorage.com/... ║
109
+ ║ ║
110
+ ╚═══════════════════════════════════════════════════════════╝
111
+ ```
112
+
113
+ ## Environment Variables
114
+
115
+ | Variable | Description |
116
+ |----------|-------------|
117
+ | `CLOUDFLARE_ACCOUNT_ID` | Your Cloudflare Account ID |
118
+ | `R2_ACCESS_KEY_ID` | R2 API Access Key ID |
119
+ | `R2_SECRET_ACCESS_KEY` | R2 API Secret Access Key |
120
+
121
+ ## Development
122
+
123
+ ```bash
124
+ # Install dependencies
125
+ pnpm install
126
+
127
+ # Build the CLI
128
+ pnpm build
129
+
130
+ # Watch for changes
131
+ pnpm dev
132
+
133
+ # Type check
134
+ pnpm typecheck
135
+ ```
136
+
137
+ ## Dependencies
138
+
139
+ - [@cfkit/r2](../r2) - Cloudflare R2 API wrapper
140
+ - [ink](https://github.com/vadimdemedes/ink) - React for CLI
141
+ - [ink-spinner](https://github.com/vadimdemedes/ink-spinner) - Spinner component
142
+
143
+ ## License
144
+
145
+ MIT
146
+
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/cli.js ADDED
@@ -0,0 +1,480 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from 'util';
3
+ import { existsSync, statSync, readFileSync } from 'fs';
4
+ import { basename } from 'path';
5
+ import { render, useApp, Box, Text, useStdout } from 'ink';
6
+ import { useState, useCallback, useEffect, useRef } from 'react';
7
+ import { R2Client } from '@cfkit/r2';
8
+ import { jsx, jsxs } from 'react/jsx-runtime';
9
+
10
+ // src/theme.ts
11
+ var theme = {
12
+ /**
13
+ * Primary accent color used throughout the UI.
14
+ * Supported values: standard terminal colors like 'cyan', 'blue', 'magenta', 'green', etc.
15
+ * or hex colors like '#ff6600'
16
+ */
17
+ primaryColor: "#ff6600",
18
+ /**
19
+ * Success color used for completed states and checkmarks.
20
+ * A warm lime green that harmonizes with the Cloudflare orange.
21
+ */
22
+ successColor: "#66cc33"
23
+ };
24
+ function Header() {
25
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
26
+ /* @__PURE__ */ jsx(Box, { justifyContent: "center", children: /* @__PURE__ */ jsx(Text, { bold: true, color: theme.primaryColor, children: "C L O U D F L A R E R 2" }) }),
27
+ /* @__PURE__ */ jsx(Box, { justifyContent: "center", children: /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: true, children: "ULTRA-LOW LATENCY OBJECT STORAGE" }) })
28
+ ] });
29
+ }
30
+ function formatFileSize(bytes) {
31
+ if (bytes >= 1024 * 1024 * 1024) {
32
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
33
+ }
34
+ if (bytes >= 1024 * 1024) {
35
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
36
+ }
37
+ if (bytes >= 1024) {
38
+ return `${(bytes / 1024).toFixed(1)} KB`;
39
+ }
40
+ return `${bytes} B`;
41
+ }
42
+ function FileInfo({ filename, fileSize, contentType }) {
43
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
44
+ /* @__PURE__ */ jsxs(Box, { children: [
45
+ /* @__PURE__ */ jsx(Text, { color: theme.primaryColor, children: "\u203A " }),
46
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Uploading: " }),
47
+ /* @__PURE__ */ jsx(Text, { children: filename })
48
+ ] }),
49
+ /* @__PURE__ */ jsxs(Box, { marginLeft: 2, gap: 1, children: [
50
+ /* @__PURE__ */ jsx(Box, { borderStyle: "round", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: formatFileSize(fileSize) }) }),
51
+ /* @__PURE__ */ jsx(Box, { borderStyle: "round", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: contentType }) })
52
+ ] })
53
+ ] });
54
+ }
55
+ var FILLED_CHAR = "\u2588";
56
+ var EMPTY_CHAR = "\u2591";
57
+ var PADDING = 1;
58
+ function ProgressBar({ percentage, status }) {
59
+ const { stdout } = useStdout();
60
+ const [animationFrame, setAnimationFrame] = useState(0);
61
+ const terminalWidth = stdout?.columns || 80;
62
+ const barWidth = Math.max(20, terminalWidth - PADDING * 2);
63
+ useEffect(() => {
64
+ if (status !== "uploading") return;
65
+ const interval = setInterval(() => {
66
+ setAnimationFrame((prev) => (prev + 1) % 4);
67
+ }, 150);
68
+ return () => clearInterval(interval);
69
+ }, [status]);
70
+ const clampedPercentage = Math.min(100, Math.max(0, percentage));
71
+ const filledWidth = Math.round(clampedPercentage / 100 * barWidth);
72
+ const emptyWidth = barWidth - filledWidth;
73
+ const filled = FILLED_CHAR.repeat(filledWidth);
74
+ const empty = EMPTY_CHAR.repeat(emptyWidth);
75
+ const statusLabel = status === "complete" ? "TRANSFER COMPLETE" : "UPLOADING";
76
+ const statusColor = status === "complete" ? theme.successColor : theme.primaryColor;
77
+ const spinnerChars = ["\u25D0", "\u25D3", "\u25D1", "\u25D2"];
78
+ const spinner = status === "uploading" ? spinnerChars[animationFrame] : "\u2714";
79
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
80
+ /* @__PURE__ */ jsxs(Box, { children: [
81
+ /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
82
+ spinner,
83
+ " "
84
+ ] }),
85
+ /* @__PURE__ */ jsx(Text, { color: statusColor, bold: true, children: statusLabel }),
86
+ /* @__PURE__ */ jsx(Box, { flexGrow: 1 }),
87
+ /* @__PURE__ */ jsxs(Text, { color: statusColor, bold: true, children: [
88
+ Math.round(clampedPercentage),
89
+ "%"
90
+ ] })
91
+ ] }),
92
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
93
+ /* @__PURE__ */ jsx(Text, { color: theme.primaryColor, children: filled }),
94
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: empty })
95
+ ] })
96
+ ] });
97
+ }
98
+ function formatTransferRate(bytesPerSecond) {
99
+ if (bytesPerSecond >= 1024 * 1024 * 1024) {
100
+ return `${(bytesPerSecond / (1024 * 1024 * 1024)).toFixed(1)} GB/S`;
101
+ }
102
+ if (bytesPerSecond >= 1024 * 1024) {
103
+ return `${(bytesPerSecond / (1024 * 1024)).toFixed(1)} MB/S`;
104
+ }
105
+ if (bytesPerSecond >= 1024) {
106
+ return `${(bytesPerSecond / 1024).toFixed(1)} KB/S`;
107
+ }
108
+ return `${bytesPerSecond} B/S`;
109
+ }
110
+ function formatTime(seconds) {
111
+ if (seconds < 1) return "< 1S";
112
+ if (seconds < 60) return `${Math.round(seconds)}S`;
113
+ if (seconds < 3600) {
114
+ const mins2 = Math.floor(seconds / 60);
115
+ const secs = Math.round(seconds % 60);
116
+ return `${mins2}M ${secs}S`;
117
+ }
118
+ const hours = Math.floor(seconds / 3600);
119
+ const mins = Math.floor(seconds % 3600 / 60);
120
+ return `${hours}H ${mins}M`;
121
+ }
122
+ function Stats({ transferRate, estimatedTime }) {
123
+ return /* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", marginY: 0, children: [
124
+ /* @__PURE__ */ jsxs(Box, { borderStyle: "round", borderColor: "gray", paddingX: 1, children: [
125
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "TRANSFER RATE: " }),
126
+ /* @__PURE__ */ jsx(Text, { color: "white", children: formatTransferRate(transferRate) })
127
+ ] }),
128
+ /* @__PURE__ */ jsxs(Box, { borderStyle: "round", borderColor: "gray", paddingX: 1, children: [
129
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "EST. TIME: " }),
130
+ /* @__PURE__ */ jsx(Text, { color: "white", children: formatTime(estimatedTime) })
131
+ ] })
132
+ ] });
133
+ }
134
+ function BucketInfo({ bucket, region }) {
135
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
136
+ /* @__PURE__ */ jsxs(Box, { children: [
137
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u25D8 BUCKET: " }),
138
+ /* @__PURE__ */ jsx(Text, { color: theme.primaryColor, children: bucket })
139
+ ] }),
140
+ /* @__PURE__ */ jsxs(Box, { children: [
141
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u25C8 REGION: " }),
142
+ /* @__PURE__ */ jsx(Text, { color: theme.primaryColor, children: region })
143
+ ] })
144
+ ] });
145
+ }
146
+ function AnimatedLog({ entry, isLatest }) {
147
+ const skipAnimation = entry.type === "success" || entry.type === "error";
148
+ const initialChars = skipAnimation ? entry.message.length : isLatest ? 0 : entry.message.length;
149
+ const [displayedChars, setDisplayedChars] = useState(initialChars);
150
+ const messageRef = useRef(entry.message);
151
+ useEffect(() => {
152
+ if (messageRef.current !== entry.message) {
153
+ messageRef.current = entry.message;
154
+ setDisplayedChars(skipAnimation ? entry.message.length : 0);
155
+ }
156
+ }, [entry.message, skipAnimation]);
157
+ useEffect(() => {
158
+ if (!isLatest && displayedChars < entry.message.length) {
159
+ setDisplayedChars(entry.message.length);
160
+ }
161
+ }, [isLatest, displayedChars, entry.message.length]);
162
+ useEffect(() => {
163
+ if (skipAnimation || !isLatest || displayedChars >= entry.message.length) return;
164
+ const timeout = setTimeout(() => {
165
+ setDisplayedChars((prev) => Math.min(prev + 3, entry.message.length));
166
+ }, 15);
167
+ return () => clearTimeout(timeout);
168
+ }, [isLatest, displayedChars, entry.message.length, skipAnimation]);
169
+ const displayedMessage = entry.message.slice(0, displayedChars);
170
+ const prefix = entry.type === "success" ? "\u2714" : "\u203A";
171
+ const prefixColor = entry.type === "success" ? theme.successColor : entry.type === "error" ? "red" : theme.primaryColor;
172
+ const showCursor = isLatest && !skipAnimation && displayedChars < entry.message.length;
173
+ return /* @__PURE__ */ jsxs(Box, { children: [
174
+ /* @__PURE__ */ jsxs(Text, { color: "gray", dimColor: true, children: [
175
+ "[",
176
+ entry.timestamp,
177
+ "] "
178
+ ] }),
179
+ /* @__PURE__ */ jsxs(Text, { color: prefixColor, children: [
180
+ prefix,
181
+ " "
182
+ ] }),
183
+ /* @__PURE__ */ jsx(Text, { children: displayedMessage }),
184
+ showCursor && /* @__PURE__ */ jsx(Text, { color: theme.primaryColor, children: "\u258C" })
185
+ ] });
186
+ }
187
+ function LogMessages({ logs, bucket, region }) {
188
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 0, paddingY: 1, paddingLeft: 1, borderStyle: "round", borderColor: "gray", children: [
189
+ /* @__PURE__ */ jsx(BucketInfo, { bucket, region }),
190
+ logs.map((entry, index) => /* @__PURE__ */ jsx(
191
+ AnimatedLog,
192
+ {
193
+ entry,
194
+ isLatest: index === logs.length - 1
195
+ },
196
+ `${entry.timestamp}-${index}`
197
+ )),
198
+ logs.length === 0 && /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: true, children: "Waiting for connection..." })
199
+ ] });
200
+ }
201
+ function SuccessResult({ objectKey, bucket, downloadUrl }) {
202
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
203
+ /* @__PURE__ */ jsxs(Box, { children: [
204
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u203A OBJECT PATH: " }),
205
+ /* @__PURE__ */ jsxs(Text, { color: theme.primaryColor, children: [
206
+ bucket,
207
+ "/",
208
+ objectKey
209
+ ] })
210
+ ] }),
211
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
212
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u203A DOWNLOAD URL:" }),
213
+ /* @__PURE__ */ jsx(Text, { color: theme.primaryColor, children: downloadUrl })
214
+ ] })
215
+ ] });
216
+ }
217
+ function getTimestamp() {
218
+ const now = /* @__PURE__ */ new Date();
219
+ return now.toLocaleTimeString("en-US", {
220
+ hour12: false,
221
+ hour: "2-digit",
222
+ minute: "2-digit",
223
+ second: "2-digit"
224
+ });
225
+ }
226
+ function getMimeType(filename) {
227
+ const ext = filename.split(".").pop()?.toLowerCase() || "";
228
+ const mimeTypes = {
229
+ "jpg": "image/jpeg",
230
+ "jpeg": "image/jpeg",
231
+ "png": "image/png",
232
+ "gif": "image/gif",
233
+ "webp": "image/webp",
234
+ "svg": "image/svg+xml",
235
+ "pdf": "application/pdf",
236
+ "json": "application/json",
237
+ "txt": "text/plain",
238
+ "html": "text/html",
239
+ "css": "text/css",
240
+ "js": "application/javascript",
241
+ "ts": "application/typescript",
242
+ "zip": "application/zip",
243
+ "tar": "application/x-tar",
244
+ "gz": "application/gzip",
245
+ "bin": "application/octet-stream",
246
+ "pkg": "application/octet-stream",
247
+ "exe": "application/octet-stream",
248
+ "mp4": "video/mp4",
249
+ "mp3": "audio/mpeg",
250
+ "wav": "audio/wav"
251
+ };
252
+ return mimeTypes[ext] || "application/octet-stream";
253
+ }
254
+ function App({ config, credentials }) {
255
+ const { exit } = useApp();
256
+ const [status, setStatus] = useState("initializing");
257
+ const [percentage, setPercentage] = useState(0);
258
+ const [transferRate, setTransferRate] = useState(0);
259
+ const [estimatedTime, setEstimatedTime] = useState(0);
260
+ const [logs, setLogs] = useState([]);
261
+ const [downloadUrl, setDownloadUrl] = useState("");
262
+ const [error, setError] = useState(null);
263
+ const [fileSize, setFileSize] = useState(0);
264
+ const addLog = useCallback((message, type = "info") => {
265
+ setLogs((prev) => [...prev, { timestamp: getTimestamp(), message, type }]);
266
+ }, []);
267
+ useEffect(() => {
268
+ async function performUpload() {
269
+ try {
270
+ const stats = statSync(config.file);
271
+ setFileSize(stats.size);
272
+ addLog("Initializing secure handshake with R2-E1...");
273
+ await new Promise((resolve) => setTimeout(resolve, 500));
274
+ const r2 = new R2Client({
275
+ accountId: credentials.accountId,
276
+ accessKeyId: credentials.accessKeyId,
277
+ secretAccessKey: credentials.secretAccessKey
278
+ });
279
+ const bucket = r2.bucket(config.bucket);
280
+ addLog("Payload delivered. Starting edge-layer optimization.");
281
+ setStatus("uploading");
282
+ const fileBuffer = readFileSync(config.file);
283
+ const contentType = getMimeType(config.key);
284
+ const fileContent = new Blob([fileBuffer], { type: contentType });
285
+ const startTime = Date.now();
286
+ let uploadedBytes = 0;
287
+ const chunkSize = Math.max(stats.size / 20, 1024 * 1024);
288
+ const progressInterval = setInterval(() => {
289
+ uploadedBytes = Math.min(uploadedBytes + chunkSize, stats.size);
290
+ const elapsed = (Date.now() - startTime) / 1e3;
291
+ const rate = uploadedBytes / elapsed;
292
+ const remaining = (stats.size - uploadedBytes) / rate;
293
+ setPercentage(uploadedBytes / stats.size * 100);
294
+ setTransferRate(rate);
295
+ setEstimatedTime(remaining);
296
+ if (uploadedBytes >= stats.size) {
297
+ clearInterval(progressInterval);
298
+ }
299
+ }, 100);
300
+ await bucket.uploadFile(config.key, fileContent, {
301
+ contentType,
302
+ metadata: {
303
+ "uploaded-at": (/* @__PURE__ */ new Date()).toISOString(),
304
+ "original-filename": config.key
305
+ }
306
+ });
307
+ clearInterval(progressInterval);
308
+ setPercentage(100);
309
+ setTransferRate(stats.size / ((Date.now() - startTime) / 1e3));
310
+ setEstimatedTime(0);
311
+ addLog("Transfer complete. Resource available at edge node.", "success");
312
+ const result = await bucket.presignedDownloadUrl(config.key, {
313
+ expiresIn: 3600
314
+ // 1 hour
315
+ });
316
+ setDownloadUrl(result.url);
317
+ setStatus("complete");
318
+ setTimeout(() => {
319
+ exit();
320
+ }, 100);
321
+ } catch (err) {
322
+ const errorMessage = err instanceof Error ? err.message : "Unknown error occurred";
323
+ setError(errorMessage);
324
+ setStatus("error");
325
+ addLog(`Upload failed: ${errorMessage}`, "error");
326
+ setTimeout(() => {
327
+ exit();
328
+ }, 100);
329
+ }
330
+ }
331
+ performUpload();
332
+ }, [config, credentials, addLog, exit]);
333
+ if (error) {
334
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
335
+ /* @__PURE__ */ jsx(Header, {}),
336
+ /* @__PURE__ */ jsx(Box, { borderStyle: "single", borderColor: "red", padding: 1, children: /* @__PURE__ */ jsxs(Text, { color: "red", children: [
337
+ "Error: ",
338
+ error
339
+ ] }) })
340
+ ] });
341
+ }
342
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
343
+ /* @__PURE__ */ jsx(Header, {}),
344
+ /* @__PURE__ */ jsx(
345
+ FileInfo,
346
+ {
347
+ filename: config.key,
348
+ fileSize,
349
+ contentType: getMimeType(config.key)
350
+ }
351
+ ),
352
+ /* @__PURE__ */ jsx(
353
+ ProgressBar,
354
+ {
355
+ percentage,
356
+ status: status === "complete" ? "complete" : "uploading"
357
+ }
358
+ ),
359
+ (status === "uploading" || status === "complete") && /* @__PURE__ */ jsx(
360
+ Stats,
361
+ {
362
+ transferRate,
363
+ estimatedTime
364
+ }
365
+ ),
366
+ /* @__PURE__ */ jsx(LogMessages, { logs, bucket: config.bucket, region: config.region }),
367
+ status === "complete" && downloadUrl && /* @__PURE__ */ jsx(
368
+ SuccessResult,
369
+ {
370
+ objectKey: config.key,
371
+ bucket: config.bucket,
372
+ downloadUrl
373
+ }
374
+ )
375
+ ] });
376
+ }
377
+ var HELP_TEXT = `
378
+ CLOUDFLARE R2 UPLOAD CLI
379
+
380
+ Upload files to Cloudflare R2 with style.
381
+
382
+ USAGE
383
+ $ r2put --file <path> --bucket <name> [options]
384
+
385
+ OPTIONS
386
+ -f, --file Path to file to upload (required)
387
+ -b, --bucket Target R2 bucket name (required)
388
+ -k, --key Custom object key (defaults to filename)
389
+ -r, --region Region hint for display (default: WNAM)
390
+ -h, --help Show this help message
391
+
392
+ ENVIRONMENT
393
+ CLOUDFLARE_ACCOUNT_ID Cloudflare Account ID
394
+ R2_ACCESS_KEY_ID R2 Access Key ID
395
+ R2_SECRET_ACCESS_KEY R2 Secret Access Key
396
+
397
+ EXAMPLE
398
+ $ r2put --file ./data.bin --bucket production-v4
399
+ `;
400
+ function showHelp() {
401
+ console.log(HELP_TEXT);
402
+ process.exit(0);
403
+ }
404
+ function showError(message) {
405
+ console.error(`
406
+ ERROR: ${message}
407
+ `);
408
+ console.error(" Run with --help for usage information.\n");
409
+ process.exit(1);
410
+ }
411
+ function getCredentials() {
412
+ const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
413
+ const accessKeyId = process.env.R2_ACCESS_KEY_ID;
414
+ const secretAccessKey = process.env.R2_SECRET_ACCESS_KEY;
415
+ if (!accountId) {
416
+ showError("Missing CLOUDFLARE_ACCOUNT_ID environment variable");
417
+ }
418
+ if (!accessKeyId) {
419
+ showError("Missing R2_ACCESS_KEY_ID environment variable");
420
+ }
421
+ if (!secretAccessKey) {
422
+ showError("Missing R2_SECRET_ACCESS_KEY environment variable");
423
+ }
424
+ return {
425
+ accountId,
426
+ accessKeyId,
427
+ secretAccessKey
428
+ };
429
+ }
430
+ function parseCliArgs() {
431
+ const { values } = parseArgs({
432
+ options: {
433
+ file: { type: "string", short: "f" },
434
+ bucket: { type: "string", short: "b" },
435
+ key: { type: "string", short: "k" },
436
+ region: { type: "string", short: "r", default: "WNAM" },
437
+ help: { type: "boolean", short: "h" }
438
+ },
439
+ strict: true,
440
+ allowPositionals: false
441
+ });
442
+ if (values.help) {
443
+ showHelp();
444
+ }
445
+ if (!values.file) {
446
+ showError("Missing required argument: --file");
447
+ }
448
+ if (!values.bucket) {
449
+ showError("Missing required argument: --bucket");
450
+ }
451
+ const filePath = values.file;
452
+ if (!existsSync(filePath)) {
453
+ showError(`File not found: ${filePath}`);
454
+ }
455
+ const stats = statSync(filePath);
456
+ if (!stats.isFile()) {
457
+ showError(`Not a file: ${filePath}`);
458
+ }
459
+ const key = values.key || basename(filePath);
460
+ return {
461
+ file: filePath,
462
+ bucket: values.bucket,
463
+ key,
464
+ region: values.region || "WNAM"
465
+ };
466
+ }
467
+ async function main() {
468
+ const config = parseCliArgs();
469
+ const credentials = getCredentials();
470
+ const { waitUntilExit } = render(
471
+ /* @__PURE__ */ jsx(App, { config, credentials })
472
+ );
473
+ await waitUntilExit();
474
+ }
475
+ main().catch((error) => {
476
+ console.error("Fatal error:", error);
477
+ process.exit(1);
478
+ });
479
+ //# sourceMappingURL=cli.js.map
480
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/theme.ts","../src/components/Header.tsx","../src/components/FileInfo.tsx","../src/components/ProgressBar.tsx","../src/components/Stats.tsx","../src/components/BucketInfo.tsx","../src/components/LogMessages.tsx","../src/components/SuccessResult.tsx","../src/App.tsx","../src/cli.tsx"],"names":["jsxs","Box","jsx","Text","mins","useState","useEffect","statSync"],"mappings":"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","file":"cli.js","sourcesContent":["/**\n * Global theme configuration for the CLI.\n * Change the colors here to apply throughout the application.\n */\nexport const theme = {\n /**\n * Primary accent color used throughout the UI.\n * Supported values: standard terminal colors like 'cyan', 'blue', 'magenta', 'green', etc.\n * or hex colors like '#ff6600'\n */\n primaryColor: '#ff6600',\n \n /**\n * Success color used for completed states and checkmarks.\n * A warm lime green that harmonizes with the Cloudflare orange.\n */\n successColor: '#66cc33'\n} as const\n\nexport type ThemeColor = typeof theme.primaryColor\n\n","import React from 'react'\nimport { Box, Text } from 'ink'\nimport { theme } from '../theme.js'\n\nexport function Header(): React.ReactElement {\n return (\n <Box flexDirection=\"column\" marginBottom={1}>\n <Box justifyContent=\"center\">\n <Text bold color={theme.primaryColor}>\n C L O U D F L A R E R 2\n </Text>\n </Box>\n <Box justifyContent=\"center\">\n <Text color=\"gray\" dimColor>\n ULTRA-LOW LATENCY OBJECT STORAGE\n </Text>\n </Box>\n </Box>\n )\n}\n\n","import React from 'react'\nimport { Box, Text } from 'ink'\nimport { theme } from '../theme.js'\n\ninterface FileInfoProps {\n filename: string\n fileSize: number\n contentType: string\n}\n\nfunction formatFileSize(bytes: number): string {\n if (bytes >= 1024 * 1024 * 1024) {\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`\n }\n if (bytes >= 1024 * 1024) {\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n }\n if (bytes >= 1024) {\n return `${(bytes / 1024).toFixed(1)} KB`\n }\n return `${bytes} B`\n}\n\nexport function FileInfo({ filename, fileSize, contentType }: FileInfoProps): React.ReactElement {\n return (\n <Box flexDirection=\"column\" marginY={1}>\n <Box>\n <Text color={theme.primaryColor}>{'› '}</Text>\n <Text bold>Uploading: </Text>\n <Text>{filename}</Text>\n </Box>\n <Box marginLeft={2} gap={1}>\n <Box borderStyle=\"round\" borderColor=\"gray\" paddingX={1}>\n <Text color=\"gray\">{formatFileSize(fileSize)}</Text>\n </Box>\n <Box borderStyle=\"round\" borderColor=\"gray\" paddingX={1}>\n <Text color=\"gray\">{contentType}</Text>\n </Box>\n </Box>\n </Box>\n )\n}\n\n","import React, { useState, useEffect } from 'react'\nimport { Box, Text, useStdout } from 'ink'\nimport { theme } from '../theme.js'\n\ninterface ProgressBarProps {\n percentage: number\n status: 'uploading' | 'complete'\n}\n\nconst FILLED_CHAR = '█'\nconst EMPTY_CHAR = '░'\nconst PADDING = 1 // Account for box padding on each side\n\nexport function ProgressBar({ percentage, status }: ProgressBarProps): React.ReactElement {\n const { stdout } = useStdout()\n const [animationFrame, setAnimationFrame] = useState(0)\n \n // Get terminal width, default to 80 if not available\n const terminalWidth = stdout?.columns || 80\n // Calculate bar width: terminal width minus padding\n const barWidth = Math.max(20, terminalWidth - (PADDING * 2))\n \n // Animate the progress bar edge during upload\n useEffect(() => {\n if (status !== 'uploading') return\n \n const interval = setInterval(() => {\n setAnimationFrame((prev) => (prev + 1) % 4)\n }, 150)\n \n return () => clearInterval(interval)\n }, [status])\n\n const clampedPercentage = Math.min(100, Math.max(0, percentage))\n const filledWidth = Math.round((clampedPercentage / 100) * barWidth)\n const emptyWidth = barWidth - filledWidth\n\n const filled = FILLED_CHAR.repeat(filledWidth)\n const empty = EMPTY_CHAR.repeat(emptyWidth)\n\n const statusLabel = status === 'complete' ? 'TRANSFER COMPLETE' : 'UPLOADING'\n const statusColor = status === 'complete' ? theme.successColor : theme.primaryColor\n \n // Animated spinner characters\n const spinnerChars = ['◐', '◓', '◑', '◒']\n const spinner = status === 'uploading' ? spinnerChars[animationFrame] : '✔'\n\n return (\n <Box flexDirection=\"column\" marginY={1}>\n <Box>\n <Text color=\"gray\">{spinner} </Text>\n <Text color={statusColor} bold>{statusLabel}</Text>\n <Box flexGrow={1} />\n <Text color={statusColor} bold>{Math.round(clampedPercentage)}%</Text>\n </Box>\n <Box marginTop={1}>\n <Text color={theme.primaryColor}>{filled}</Text>\n <Text color=\"gray\">{empty}</Text>\n </Box>\n </Box>\n )\n}\n\n","import React from 'react'\nimport { Box, Text } from 'ink'\n\ninterface StatsProps {\n transferRate: number // bytes per second\n estimatedTime: number // seconds remaining\n}\n\nfunction formatTransferRate(bytesPerSecond: number): string {\n if (bytesPerSecond >= 1024 * 1024 * 1024) {\n return `${(bytesPerSecond / (1024 * 1024 * 1024)).toFixed(1)} GB/S`\n }\n if (bytesPerSecond >= 1024 * 1024) {\n return `${(bytesPerSecond / (1024 * 1024)).toFixed(1)} MB/S`\n }\n if (bytesPerSecond >= 1024) {\n return `${(bytesPerSecond / 1024).toFixed(1)} KB/S`\n }\n return `${bytesPerSecond} B/S`\n}\n\nfunction formatTime(seconds: number): string {\n if (seconds < 1) return '< 1S'\n if (seconds < 60) return `${Math.round(seconds)}S`\n if (seconds < 3600) {\n const mins = Math.floor(seconds / 60)\n const secs = Math.round(seconds % 60)\n return `${mins}M ${secs}S`\n }\n const hours = Math.floor(seconds / 3600)\n const mins = Math.floor((seconds % 3600) / 60)\n return `${hours}H ${mins}M`\n}\n\nexport function Stats({ transferRate, estimatedTime }: StatsProps): React.ReactElement {\n return (\n <Box justifyContent=\"space-between\" marginY={0}>\n <Box borderStyle=\"round\" borderColor=\"gray\" paddingX={1}>\n <Text color=\"gray\">TRANSFER RATE: </Text>\n <Text color=\"white\">{formatTransferRate(transferRate)}</Text>\n </Box>\n <Box borderStyle=\"round\" borderColor=\"gray\" paddingX={1}>\n <Text color=\"gray\">EST. TIME: </Text>\n <Text color=\"white\">{formatTime(estimatedTime)}</Text>\n </Box>\n </Box>\n )\n}\n\n","import React from 'react'\nimport { Box, Text } from 'ink'\nimport { theme } from '../theme.js'\n\ninterface BucketInfoProps {\n bucket: string\n region: string\n}\n\nexport function BucketInfo({ bucket, region }: BucketInfoProps): React.ReactElement {\n return (\n <Box flexDirection=\"column\" marginBottom={1}>\n <Box>\n <Text color=\"gray\">◘ BUCKET: </Text>\n <Text color={theme.primaryColor}>{bucket}</Text>\n </Box>\n <Box>\n <Text color=\"gray\">◈ REGION: </Text>\n <Text color={theme.primaryColor}>{region}</Text>\n </Box>\n </Box>\n )\n}\n\n","import React, { useState, useEffect, useRef } from 'react'\nimport { Box, Text } from 'ink'\nimport type { LogEntry } from '../types.js'\nimport { theme } from '../theme.js'\nimport { BucketInfo } from './BucketInfo.js'\n\ninterface LogMessagesProps {\n logs: LogEntry[]\n bucket: string\n region: string\n}\n\ninterface AnimatedLogProps {\n entry: LogEntry\n isLatest: boolean\n}\n\nfunction AnimatedLog({ entry, isLatest }: AnimatedLogProps): React.ReactElement {\n // Skip animation for success/error messages - show them immediately\n const skipAnimation = entry.type === 'success' || entry.type === 'error'\n const initialChars = skipAnimation ? entry.message.length : (isLatest ? 0 : entry.message.length)\n \n const [displayedChars, setDisplayedChars] = useState(initialChars)\n const messageRef = useRef(entry.message)\n \n // Reset animation when message changes\n useEffect(() => {\n if (messageRef.current !== entry.message) {\n messageRef.current = entry.message\n setDisplayedChars(skipAnimation ? entry.message.length : 0)\n }\n }, [entry.message, skipAnimation])\n\n // When this entry is no longer the latest, show full message immediately\n useEffect(() => {\n if (!isLatest && displayedChars < entry.message.length) {\n setDisplayedChars(entry.message.length)\n }\n }, [isLatest, displayedChars, entry.message.length])\n\n useEffect(() => {\n if (skipAnimation || !isLatest || displayedChars >= entry.message.length) return\n\n const timeout = setTimeout(() => {\n setDisplayedChars((prev) => Math.min(prev + 3, entry.message.length))\n }, 15)\n\n return () => clearTimeout(timeout)\n }, [isLatest, displayedChars, entry.message.length, skipAnimation])\n\n const displayedMessage = entry.message.slice(0, displayedChars)\n const prefix = entry.type === 'success' ? '✔' : '›'\n const prefixColor = entry.type === 'success' ? theme.successColor : entry.type === 'error' ? 'red' : theme.primaryColor\n\n const showCursor = isLatest && !skipAnimation && displayedChars < entry.message.length\n\n return (\n <Box>\n <Text color=\"gray\" dimColor>[{entry.timestamp}] </Text>\n <Text color={prefixColor}>{prefix} </Text>\n <Text>{displayedMessage}</Text>\n {showCursor && <Text color={theme.primaryColor}>▌</Text>}\n </Box>\n )\n}\n\nexport function LogMessages({ logs, bucket, region }: LogMessagesProps): React.ReactElement {\n return (\n <Box flexDirection=\"column\" marginY={0} paddingY={1} paddingLeft={1} borderStyle=\"round\" borderColor=\"gray\">\n <BucketInfo bucket={bucket} region={region} />\n {logs.map((entry, index) => (\n <AnimatedLog\n key={`${entry.timestamp}-${index}`}\n entry={entry}\n isLatest={index === logs.length - 1}\n />\n ))}\n {logs.length === 0 && (\n <Text color=\"gray\" dimColor>Waiting for connection...</Text>\n )}\n </Box>\n )\n}\n\n","import React from 'react'\nimport { Box, Text } from 'ink'\nimport { theme } from '../theme.js'\n\ninterface SuccessResultProps {\n objectKey: string\n bucket: string\n downloadUrl: string\n}\n\nexport function SuccessResult({ objectKey, bucket, downloadUrl }: SuccessResultProps): React.ReactElement {\n return (\n <Box flexDirection=\"column\" marginTop={1}>\n <Box>\n <Text color=\"gray\">› OBJECT PATH: </Text>\n <Text color={theme.primaryColor}>{bucket}/{objectKey}</Text>\n </Box>\n <Box flexDirection=\"column\">\n <Text color=\"gray\">› DOWNLOAD URL:</Text>\n <Text color={theme.primaryColor}>{downloadUrl}</Text>\n </Box>\n </Box>\n )\n}\n","import React, { useState, useEffect, useCallback } from 'react'\nimport { Box, Text, useApp } from 'ink'\nimport { readFileSync, statSync } from 'node:fs'\nimport { R2Client } from '@cfkit/r2'\nimport {\n Header,\n FileInfo,\n ProgressBar,\n Stats,\n LogMessages,\n SuccessResult\n} from './components/index.js'\nimport type { CliConfig, R2Credentials, LogEntry, UploadStatus } from './types.js'\n\ninterface AppProps {\n config: CliConfig\n credentials: R2Credentials\n}\n\nfunction getTimestamp(): string {\n const now = new Date()\n return now.toLocaleTimeString('en-US', {\n hour12: false,\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit'\n })\n}\n\nfunction getMimeType(filename: string): string {\n const ext = filename.split('.').pop()?.toLowerCase() || ''\n const mimeTypes: Record<string, string> = {\n 'jpg': 'image/jpeg',\n 'jpeg': 'image/jpeg',\n 'png': 'image/png',\n 'gif': 'image/gif',\n 'webp': 'image/webp',\n 'svg': 'image/svg+xml',\n 'pdf': 'application/pdf',\n 'json': 'application/json',\n 'txt': 'text/plain',\n 'html': 'text/html',\n 'css': 'text/css',\n 'js': 'application/javascript',\n 'ts': 'application/typescript',\n 'zip': 'application/zip',\n 'tar': 'application/x-tar',\n 'gz': 'application/gzip',\n 'bin': 'application/octet-stream',\n 'pkg': 'application/octet-stream',\n 'exe': 'application/octet-stream',\n 'mp4': 'video/mp4',\n 'mp3': 'audio/mpeg',\n 'wav': 'audio/wav'\n }\n return mimeTypes[ext] || 'application/octet-stream'\n}\n\nexport function App({ config, credentials }: AppProps): React.ReactElement {\n const { exit } = useApp()\n \n const [status, setStatus] = useState<UploadStatus>('initializing')\n const [percentage, setPercentage] = useState(0)\n const [transferRate, setTransferRate] = useState(0)\n const [estimatedTime, setEstimatedTime] = useState(0)\n const [logs, setLogs] = useState<LogEntry[]>([])\n const [downloadUrl, setDownloadUrl] = useState('')\n const [error, setError] = useState<string | null>(null)\n const [fileSize, setFileSize] = useState(0)\n\n const addLog = useCallback((message: string, type: LogEntry['type'] = 'info') => {\n setLogs((prev) => [...prev, { timestamp: getTimestamp(), message, type }])\n }, [])\n\n useEffect(() => {\n async function performUpload() {\n try {\n // Get file stats\n const stats = statSync(config.file)\n setFileSize(stats.size)\n\n addLog('Initializing secure handshake with R2-E1...')\n await new Promise((resolve) => setTimeout(resolve, 500))\n\n // Initialize R2 client\n const r2 = new R2Client({\n accountId: credentials.accountId,\n accessKeyId: credentials.accessKeyId,\n secretAccessKey: credentials.secretAccessKey\n })\n\n const bucket = r2.bucket(config.bucket)\n\n addLog('Payload delivered. Starting edge-layer optimization.')\n setStatus('uploading')\n\n // Read file content and convert Buffer to Blob for cross-platform compatibility\n const fileBuffer = readFileSync(config.file)\n const contentType = getMimeType(config.key)\n const fileContent = new Blob([fileBuffer], { type: contentType })\n\n // Simulate progress for visual effect\n // In a real implementation, you'd track actual upload progress\n const startTime = Date.now()\n let uploadedBytes = 0\n const chunkSize = Math.max(stats.size / 20, 1024 * 1024) // 5% chunks or 1MB min\n\n const progressInterval = setInterval(() => {\n uploadedBytes = Math.min(uploadedBytes + chunkSize, stats.size)\n const elapsed = (Date.now() - startTime) / 1000\n const rate = uploadedBytes / elapsed\n const remaining = (stats.size - uploadedBytes) / rate\n\n setPercentage((uploadedBytes / stats.size) * 100)\n setTransferRate(rate)\n setEstimatedTime(remaining)\n\n if (uploadedBytes >= stats.size) {\n clearInterval(progressInterval)\n }\n }, 100)\n\n // Perform actual upload\n await bucket.uploadFile(config.key, fileContent, {\n contentType,\n metadata: {\n 'uploaded-at': new Date().toISOString(),\n 'original-filename': config.key\n }\n })\n\n clearInterval(progressInterval)\n setPercentage(100)\n setTransferRate(stats.size / ((Date.now() - startTime) / 1000))\n setEstimatedTime(0)\n\n addLog('Transfer complete. Resource available at edge node.', 'success')\n\n // Generate presigned download URL\n const result = await bucket.presignedDownloadUrl(config.key, {\n expiresIn: 3600 // 1 hour\n })\n\n setDownloadUrl(result.url)\n setStatus('complete')\n\n // Exit after a short delay to let the user see the result\n setTimeout(() => {\n exit()\n }, 100)\n\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred'\n setError(errorMessage)\n setStatus('error')\n addLog(`Upload failed: ${errorMessage}`, 'error')\n \n setTimeout(() => {\n exit()\n }, 100)\n }\n }\n\n performUpload()\n }, [config, credentials, addLog, exit])\n\n if (error) {\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Header />\n <Box borderStyle=\"single\" borderColor=\"red\" padding={1}>\n <Text color=\"red\">Error: {error}</Text>\n </Box>\n </Box>\n )\n }\n\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Header />\n \n <FileInfo\n filename={config.key}\n fileSize={fileSize}\n contentType={getMimeType(config.key)}\n />\n \n <ProgressBar\n percentage={percentage}\n status={status === 'complete' ? 'complete' : 'uploading'}\n />\n \n {(status === 'uploading' || status === 'complete') && (\n <Stats\n transferRate={transferRate}\n estimatedTime={estimatedTime}\n />\n )}\n \n <LogMessages logs={logs} bucket={config.bucket} region={config.region} />\n \n {status === 'complete' && downloadUrl && (\n <SuccessResult\n objectKey={config.key}\n bucket={config.bucket}\n downloadUrl={downloadUrl}\n />\n )}\n </Box>\n )\n}\n\n","import { parseArgs } from 'node:util'\nimport { existsSync, statSync } from 'node:fs'\nimport { basename } from 'node:path'\nimport { render } from 'ink'\nimport { App } from './App.js'\nimport type { CliConfig, R2Credentials } from './types.js'\n\nconst HELP_TEXT = `\n CLOUDFLARE R2 UPLOAD CLI\n \n Upload files to Cloudflare R2 with style.\n\n USAGE\n $ r2put --file <path> --bucket <name> [options]\n\n OPTIONS\n -f, --file Path to file to upload (required)\n -b, --bucket Target R2 bucket name (required)\n -k, --key Custom object key (defaults to filename)\n -r, --region Region hint for display (default: WNAM)\n -h, --help Show this help message\n\n ENVIRONMENT\n CLOUDFLARE_ACCOUNT_ID Cloudflare Account ID\n R2_ACCESS_KEY_ID R2 Access Key ID\n R2_SECRET_ACCESS_KEY R2 Secret Access Key\n\n EXAMPLE\n $ r2put --file ./data.bin --bucket production-v4\n`\n\nfunction showHelp(): void {\n console.log(HELP_TEXT)\n process.exit(0)\n}\n\nfunction showError(message: string): void {\n console.error(`\\n ERROR: ${message}\\n`)\n console.error(' Run with --help for usage information.\\n')\n process.exit(1)\n}\n\nfunction getCredentials(): R2Credentials {\n const accountId = process.env.CLOUDFLARE_ACCOUNT_ID\n const accessKeyId = process.env.R2_ACCESS_KEY_ID\n const secretAccessKey = process.env.R2_SECRET_ACCESS_KEY\n\n if (!accountId) {\n showError('Missing CLOUDFLARE_ACCOUNT_ID environment variable')\n }\n if (!accessKeyId) {\n showError('Missing R2_ACCESS_KEY_ID environment variable')\n }\n if (!secretAccessKey) {\n showError('Missing R2_SECRET_ACCESS_KEY environment variable')\n }\n\n return {\n accountId: accountId!,\n accessKeyId: accessKeyId!,\n secretAccessKey: secretAccessKey!\n }\n}\n\nfunction parseCliArgs(): CliConfig {\n const { values } = parseArgs({\n options: {\n file: { type: 'string', short: 'f' },\n bucket: { type: 'string', short: 'b' },\n key: { type: 'string', short: 'k' },\n region: { type: 'string', short: 'r', default: 'WNAM' },\n help: { type: 'boolean', short: 'h' }\n },\n strict: true,\n allowPositionals: false\n })\n\n if (values.help) {\n showHelp()\n }\n\n if (!values.file) {\n showError('Missing required argument: --file')\n }\n\n if (!values.bucket) {\n showError('Missing required argument: --bucket')\n }\n\n const filePath = values.file!\n \n if (!existsSync(filePath)) {\n showError(`File not found: ${filePath}`)\n }\n\n const stats = statSync(filePath)\n if (!stats.isFile()) {\n showError(`Not a file: ${filePath}`)\n }\n\n const key = values.key || basename(filePath)\n\n return {\n file: filePath,\n bucket: values.bucket!,\n key,\n region: values.region || 'WNAM'\n }\n}\n\nasync function main(): Promise<void> {\n const config = parseCliArgs()\n const credentials = getCredentials()\n\n const { waitUntilExit } = render(\n <App config={config} credentials={credentials} />\n )\n\n await waitUntilExit()\n}\n\nmain().catch((error) => {\n console.error('Fatal error:', error)\n process.exit(1)\n})\n\n"]}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "r2put",
3
+ "version": "1.0.0",
4
+ "description": "Futuristic CLI tool for uploading files to Cloudflare R2",
5
+ "type": "module",
6
+ "main": "./dist/cli.js",
7
+ "bin": {
8
+ "r2put": "./dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md"
13
+ ],
14
+ "keywords": [
15
+ "cloudflare",
16
+ "r2",
17
+ "s3",
18
+ "storage",
19
+ "cli",
20
+ "upload"
21
+ ],
22
+ "author": "",
23
+ "license": "MIT",
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "dependencies": {
28
+ "ink": "^5.2.0",
29
+ "ink-link": "^4.1.0",
30
+ "ink-spinner": "^5.0.0",
31
+ "react": "^18.3.1",
32
+ "@cfkit/r2": "1.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^24.7.1",
36
+ "@types/react": "^18.3.0",
37
+ "tsup": "^8.0.0",
38
+ "tsx": "^4.19.0",
39
+ "typescript": "^5.9.3"
40
+ },
41
+ "scripts": {
42
+ "build": "tsup",
43
+ "dev": "tsup --watch",
44
+ "typecheck": "tsc --noEmit",
45
+ "preview": "tsx src/dev-preview.tsx",
46
+ "lint": "echo 'No linting configured'",
47
+ "test": "echo 'No tests configured'",
48
+ "clean": "rimraf dist .turbo"
49
+ }
50
+ }