mbkbucket 3.3.0 → 3.3.2
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/index.js +8 -18
- package/lib/cli.js +642 -0
- package/{public → lib/public}/bucketadmin.js +1 -1
- package/{src → lib/src}/app.js +37 -50
- package/{src → lib/src}/cli/auth-helper.js +24 -103
- package/lib/src/config/index.js +108 -0
- package/{src → lib/src}/config/validation.js +14 -30
- package/{src → lib/src}/controllers/api.controller.js +85 -176
- package/{src → lib/src}/controllers/dashboard.controller.js +12 -23
- package/{src → lib/src}/controllers/info.controller.js +17 -20
- package/{src → lib/src}/controllers/view.controller.js +23 -70
- package/{src → lib/src}/middleware/async-handler.js +1 -16
- package/{src → lib/src}/middleware/bucket-resolver.js +7 -13
- package/{src → lib/src}/middleware/error-handler.js +6 -14
- package/{src → lib/src}/middleware/rate-limiter.js +2 -10
- package/{src → lib/src}/middleware/security.js +1 -17
- package/lib/src/routes/api.routes.js +40 -0
- package/{src → lib/src}/routes/index.js +1 -9
- package/{src → lib/src}/routes/info.routes.js +2 -3
- package/{src → lib/src}/routes/view.routes.js +3 -8
- package/{src → lib/src}/services/s3.service.js +157 -349
- package/lib/src/utils/errors.js +45 -0
- package/lib/src/utils/helpers.js +251 -0
- package/package.json +18 -3
- package/.github/PACKAGE.md +0 -19
- package/.github/workflows/publish.yml +0 -88
- package/cli.js +0 -977
- package/index.d.ts +0 -444
- package/lib/index.js +0 -17
- package/src/config/index.js +0 -133
- package/src/routes/api.routes.js +0 -66
- package/src/utils/errors.js +0 -76
- package/src/utils/helpers.js +0 -197
- package/test/config-validation.test.js +0 -116
- /package/{public → lib/public}/bucketadmin.css +0 -0
- /package/{public → lib/public}/mbkbucket-helper-demo.html +0 -0
- /package/{public → lib/public}/mbkbucket-helper.css +0 -0
- /package/{public → lib/public}/mbkbucket-helper.js +0 -0
- /package/{src → lib/src}/utils/logger.js +0 -0
package/cli.js
DELETED
|
@@ -1,977 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* mbkbucket CLI — Manage S3/R2 buckets from the command line.
|
|
5
|
-
*
|
|
6
|
-
* mbkbucket login --server <url> Authenticate via device flow
|
|
7
|
-
* mbkbucket logout Clear stored credentials
|
|
8
|
-
* mbkbucket whoami Show login status
|
|
9
|
-
* mbkbucket list [prefix] List files and folders
|
|
10
|
-
* mbkbucket upload <file> [key] Upload a file
|
|
11
|
-
* mbkbucket download <key> [dest] Download a file
|
|
12
|
-
* mbkbucket delete <key> Delete a file
|
|
13
|
-
* mbkbucket delete-folder <prefix> Recursively delete a folder
|
|
14
|
-
* mbkbucket info <key> Show file metadata
|
|
15
|
-
* mbkbucket signed-url <key> Generate a pre-signed URL
|
|
16
|
-
*
|
|
17
|
-
* Before running, set these env vars (or use a .env file in cwd):
|
|
18
|
-
* BucketConnection={"bucketname":{...}}
|
|
19
|
-
* APP_NAME=your-app-name
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import dotenv from 'dotenv';
|
|
23
|
-
import path from 'node:path';
|
|
24
|
-
import fs from 'node:fs';
|
|
25
|
-
import os from 'node:os';
|
|
26
|
-
|
|
27
|
-
// Load .env from cwd first so S3 service can pick up BucketConnection etc.
|
|
28
|
-
dotenv.config();
|
|
29
|
-
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
// Parse CLI arguments
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
|
-
const args = process.argv.slice(2);
|
|
34
|
-
|
|
35
|
-
function findFlag(name) {
|
|
36
|
-
const long = `--${name}`;
|
|
37
|
-
const short = `-${name[0]}`;
|
|
38
|
-
for (let i = 0; i < args.length; i++) {
|
|
39
|
-
if (args[i] === long || args[i] === short) {
|
|
40
|
-
return args[i + 1] !== undefined && !args[i + 1].startsWith('-') ? args[i + 1] : true;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return undefined;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function hasFlag(name) {
|
|
47
|
-
return args.includes(`--${name}`) || args.includes(`-${name[0]}`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function getPositionalArgs() {
|
|
51
|
-
const positional = [];
|
|
52
|
-
for (let i = 0; i < args.length; i++) {
|
|
53
|
-
if (args[i].startsWith('--') || args[i].startsWith('-')) {
|
|
54
|
-
// Skip flag and its value
|
|
55
|
-
if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
|
|
56
|
-
const long = args[i].replace(/^-+/, '');
|
|
57
|
-
// Only skip value for flags that take one (not boolean flags like --help)
|
|
58
|
-
if (['server', 'app', 'bucket', 'profile-key', 'expires'].includes(long)) {
|
|
59
|
-
i++;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
} else {
|
|
63
|
-
positional.push(args[i]);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return positional;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const command = getPositionalArgs()[0] || 'help';
|
|
70
|
-
const cmdArgs = getPositionalArgs().slice(1);
|
|
71
|
-
|
|
72
|
-
// ---------------------------------------------------------------------------
|
|
73
|
-
// CLI Auth helpers (lazy-loaded because they don't import S3)
|
|
74
|
-
// ---------------------------------------------------------------------------
|
|
75
|
-
import {
|
|
76
|
-
readConfig,
|
|
77
|
-
clearConfig,
|
|
78
|
-
updateConfig,
|
|
79
|
-
writeConfig,
|
|
80
|
-
isLoggedIn,
|
|
81
|
-
deviceFlowLogin,
|
|
82
|
-
CONFIG_FILE,
|
|
83
|
-
CONFIG_DIR,
|
|
84
|
-
} from './src/cli/auth-helper.js';
|
|
85
|
-
|
|
86
|
-
// ---------------------------------------------------------------------------
|
|
87
|
-
// Help text
|
|
88
|
-
// ---------------------------------------------------------------------------
|
|
89
|
-
function showHelp() {
|
|
90
|
-
console.log(`
|
|
91
|
-
mbkbucket — S3/R2 bucket CLI
|
|
92
|
-
|
|
93
|
-
USAGE:
|
|
94
|
-
mbkbucket <command> [options] [arguments]
|
|
95
|
-
|
|
96
|
-
COMMANDS:
|
|
97
|
-
login Authenticate with mbkauthe device flow
|
|
98
|
-
logout Clear stored credentials
|
|
99
|
-
whoami Show current login status
|
|
100
|
-
|
|
101
|
-
config Show all config values
|
|
102
|
-
config get <key> Get a specific config value
|
|
103
|
-
config set <k> <v> Set a config value
|
|
104
|
-
config unset <key> Remove a config value
|
|
105
|
-
config path Show config file path
|
|
106
|
-
config reset Reset to demo values
|
|
107
|
-
config edit Open config file in editor
|
|
108
|
-
|
|
109
|
-
list [prefix] List files and folders
|
|
110
|
-
upload <file> [key] Upload a file or folder
|
|
111
|
-
download <key> [dest] Download a file or folder
|
|
112
|
-
delete <key> Delete a file
|
|
113
|
-
delete-folder <prefix> Recursively delete a folder
|
|
114
|
-
info <key> Show file metadata
|
|
115
|
-
signed-url <key> Generate a pre-signed download URL
|
|
116
|
-
|
|
117
|
-
GLOBAL OPTIONS:
|
|
118
|
-
--app, -a <name> Override APP_NAME for key prefixing
|
|
119
|
-
--bucket, -b <name> Override default bucket name
|
|
120
|
-
--help, -h Show this help
|
|
121
|
-
|
|
122
|
-
LOGIN OPTIONS:
|
|
123
|
-
--server <url> mbkauthe server URL (required for login)
|
|
124
|
-
--profile-key <k> API token profile key
|
|
125
|
-
|
|
126
|
-
SIGNED-URL OPTIONS:
|
|
127
|
-
--expires <sec> Expiration in seconds (default: 3600)
|
|
128
|
-
|
|
129
|
-
ENVIRONMENT:
|
|
130
|
-
BucketConnection JSON mapping of bucket name → S3 credentials
|
|
131
|
-
APP_NAME Application name for key prefix isolation
|
|
132
|
-
PORT Server port (default: 3004)
|
|
133
|
-
`);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// ---------------------------------------------------------------------------
|
|
137
|
-
// S3 helper — conditionally imports S3 service after env is set up
|
|
138
|
-
// ---------------------------------------------------------------------------
|
|
139
|
-
let _s3Module = null;
|
|
140
|
-
async function getS3() {
|
|
141
|
-
if (_s3Module) return _s3Module;
|
|
142
|
-
// Set APP_NAME from CLI flag before importing
|
|
143
|
-
const appFlag = findFlag('app');
|
|
144
|
-
if (appFlag && typeof appFlag === 'string') {
|
|
145
|
-
process.env.APP_NAME = appFlag;
|
|
146
|
-
}
|
|
147
|
-
_s3Module = await import('./src/services/s3.service.js');
|
|
148
|
-
return _s3Module;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// ---------------------------------------------------------------------------
|
|
152
|
-
// Formatting helpers
|
|
153
|
-
// ---------------------------------------------------------------------------
|
|
154
|
-
function formatBytes(bytes) {
|
|
155
|
-
if (!bytes || bytes === 0) return '0 B';
|
|
156
|
-
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
157
|
-
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
158
|
-
return `${(bytes / Math.pow(1024, i)).toFixed(i > 0 ? 1 : 0)} ${units[i]}`;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function formatDate(iso) {
|
|
162
|
-
try {
|
|
163
|
-
return new Date(iso).toLocaleString();
|
|
164
|
-
} catch {
|
|
165
|
-
return iso || '—';
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// ---------------------------------------------------------------------------
|
|
170
|
-
// Command implementations
|
|
171
|
-
// ---------------------------------------------------------------------------
|
|
172
|
-
|
|
173
|
-
async function cmdLogin() {
|
|
174
|
-
// Use --server flag, or fall back to stored config
|
|
175
|
-
let serverUrl = findFlag('server');
|
|
176
|
-
const storedCfg = readConfig();
|
|
177
|
-
|
|
178
|
-
if (!serverUrl || typeof serverUrl !== 'string') {
|
|
179
|
-
if (storedCfg.serverUrl) {
|
|
180
|
-
serverUrl = storedCfg.serverUrl;
|
|
181
|
-
console.log(`ℹ️ Using stored server: ${serverUrl}`);
|
|
182
|
-
} else {
|
|
183
|
-
console.error('❌ --server <url> is required for login.');
|
|
184
|
-
console.error(' Example: mbkbucket login --server https://myapp.example.com');
|
|
185
|
-
console.error('');
|
|
186
|
-
console.error('💡 Tip: Set it once with:');
|
|
187
|
-
console.error(' mbkbucket config set serverUrl https://your-server.com');
|
|
188
|
-
console.error(' Then just run: mbkbucket login');
|
|
189
|
-
process.exit(1);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Use --profile-key flag, or fall back to stored config
|
|
194
|
-
let profileKey = findFlag('profile-key');
|
|
195
|
-
if (!profileKey && storedCfg.profileKey) {
|
|
196
|
-
profileKey = storedCfg.profileKey;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Always save the server URL so the user doesn't have to retype it
|
|
200
|
-
updateConfig({ serverUrl });
|
|
201
|
-
|
|
202
|
-
try {
|
|
203
|
-
const result = await deviceFlowLogin({
|
|
204
|
-
serverUrl,
|
|
205
|
-
profileKey: typeof profileKey === 'string' ? profileKey : undefined,
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
updateConfig({
|
|
209
|
-
token: result.token,
|
|
210
|
-
tokenPrefix: result.tokenPrefix,
|
|
211
|
-
username: result.username,
|
|
212
|
-
serverUrl,
|
|
213
|
-
profile: result.profile || null,
|
|
214
|
-
loggedInAt: new Date().toISOString(),
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
console.log('');
|
|
218
|
-
console.log('✅ Credentials saved. You are now logged in.');
|
|
219
|
-
console.log(` Run "mbkbucket whoami" to verify.`);
|
|
220
|
-
} catch (err) {
|
|
221
|
-
const msg = err.message || '';
|
|
222
|
-
console.error(`❌ Login failed: ${msg}`);
|
|
223
|
-
|
|
224
|
-
// Give actionable guidance based on the error
|
|
225
|
-
if (msg.includes('profileKey') || msg.includes('profileId')) {
|
|
226
|
-
console.error('');
|
|
227
|
-
console.error('💡 An API token profile is required. You need to:');
|
|
228
|
-
console.error(' 1. Create an API token profile in the mbkauthe admin panel');
|
|
229
|
-
console.error(' 2. Run: mbkbucket login --profile-key <your-profile-key>');
|
|
230
|
-
console.error('');
|
|
231
|
-
console.error(' Or save it for future use:');
|
|
232
|
-
console.error(' mbkbucket config set profileKey <your-profile-key>');
|
|
233
|
-
} else if (msg.includes('fetch') || msg.includes('ECONNREFUSED') || msg.includes('ENOTFOUND')) {
|
|
234
|
-
console.error('');
|
|
235
|
-
console.error('💡 Could not reach the server. Check:');
|
|
236
|
-
console.error(' 1. Is the server running?');
|
|
237
|
-
console.error(` 2. Is "${serverUrl}" the correct URL?`);
|
|
238
|
-
console.error(' 3. Is CLI_AUTH_ENABLED=true on the server?');
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
console.error('');
|
|
242
|
-
console.error(` Server URL saved. Run "mbkbucket login" to retry.`);
|
|
243
|
-
process.exit(1);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function cmdLogout() {
|
|
248
|
-
if (!isLoggedIn()) {
|
|
249
|
-
console.log('ℹ️ Not logged in.');
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
const cfg = readConfig();
|
|
253
|
-
clearConfig();
|
|
254
|
-
console.log(`👋 Logged out. Goodbye, ${cfg.username || 'user'}!`);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function cmdWhoami() {
|
|
258
|
-
if (!isLoggedIn()) {
|
|
259
|
-
console.log('ℹ️ Not logged in. Run "mbkbucket login --server <url>" to authenticate.');
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
const cfg = readConfig();
|
|
263
|
-
console.log(`✅ Logged in as: ${cfg.username}`);
|
|
264
|
-
console.log(` Server: ${cfg.serverUrl}`);
|
|
265
|
-
console.log(` Token: ${cfg.tokenPrefix}...`);
|
|
266
|
-
if (cfg.profile) {
|
|
267
|
-
console.log(` Profile: ${cfg.profile.name} (scope: ${cfg.profile.scope || 'full'})`);
|
|
268
|
-
}
|
|
269
|
-
console.log(` Since: ${formatDate(cfg.loggedInAt)}`);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
async function cmdList() {
|
|
273
|
-
const prefix = cmdArgs[0] || '';
|
|
274
|
-
const bucketName = findFlag('bucket');
|
|
275
|
-
|
|
276
|
-
try {
|
|
277
|
-
const s3 = await getS3();
|
|
278
|
-
const result = await s3.listfiles(prefix, { bucketName, delimiter: '/' });
|
|
279
|
-
|
|
280
|
-
if (!result.Contents || result.Contents.length === 0) {
|
|
281
|
-
console.log(`📭 No files found${prefix ? ` with prefix "${prefix}"` : ''}.`);
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
console.log('');
|
|
286
|
-
console.log(`📁 Files${prefix ? ` matching "${prefix}"` : ''} (${result.KeyCount || result.Contents.length} items):`);
|
|
287
|
-
console.log('─'.repeat(80));
|
|
288
|
-
|
|
289
|
-
// Show CommonPrefixes (folders) first
|
|
290
|
-
if (result.CommonPrefixes && result.CommonPrefixes.length > 0) {
|
|
291
|
-
for (const cp of result.CommonPrefixes) {
|
|
292
|
-
const folderName = cp.Prefix.replace(/\/$/, '').split('/').pop() || cp.Prefix;
|
|
293
|
-
console.log(` 📂 ${folderName}/`);
|
|
294
|
-
}
|
|
295
|
-
if (result.Contents.length > 0) console.log('');
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
for (const obj of result.Contents) {
|
|
299
|
-
const name = obj.Key || '';
|
|
300
|
-
const size = formatBytes(obj.Size);
|
|
301
|
-
const modified = formatDate(obj.LastModified);
|
|
302
|
-
const displayName = name.length > 60 ? '...' + name.slice(-57) : name;
|
|
303
|
-
console.log(` ${displayName.padEnd(62)} ${size.padStart(10)} ${modified}`);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
console.log('─'.repeat(80));
|
|
307
|
-
if (result.hasMore) {
|
|
308
|
-
console.log(` ... more files available (use --prefix to narrow down)`);
|
|
309
|
-
}
|
|
310
|
-
console.log('');
|
|
311
|
-
} catch (err) {
|
|
312
|
-
console.error(`❌ List failed: ${err.message}`);
|
|
313
|
-
process.exit(1);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
async function cmdUpload() {
|
|
318
|
-
const filePath = cmdArgs[0];
|
|
319
|
-
if (!filePath) {
|
|
320
|
-
console.error('❌ Usage: mbkbucket upload <file> [key]');
|
|
321
|
-
console.error(' Example: mbkbucket upload ./photo.jpg');
|
|
322
|
-
console.error(' Example: mbkbucket upload ./photo.jpg images/photo.jpg');
|
|
323
|
-
process.exit(1);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const resolvedPath = path.resolve(filePath);
|
|
327
|
-
const isDir = fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isDirectory();
|
|
328
|
-
|
|
329
|
-
if (isDir) {
|
|
330
|
-
await uploadFolder(resolvedPath);
|
|
331
|
-
} else {
|
|
332
|
-
await uploadSingleFile(resolvedPath);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
async function uploadSingleFile(filePath) {
|
|
337
|
-
const bucketName = findFlag('bucket');
|
|
338
|
-
const customKey = cmdArgs[1] || null;
|
|
339
|
-
|
|
340
|
-
if (!fs.existsSync(filePath)) {
|
|
341
|
-
console.error(`❌ File not found: ${filePath}`);
|
|
342
|
-
process.exit(1);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
const stat = fs.statSync(filePath);
|
|
346
|
-
const fileName = path.basename(filePath);
|
|
347
|
-
const key = customKey || fileName;
|
|
348
|
-
|
|
349
|
-
// Determine MIME type from extension
|
|
350
|
-
const ext = path.extname(fileName).slice(1).toLowerCase();
|
|
351
|
-
const mimeMap = {
|
|
352
|
-
'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'png': 'image/png',
|
|
353
|
-
'gif': 'image/gif', 'webp': 'image/webp', 'svg': 'image/svg+xml',
|
|
354
|
-
'mp4': 'video/mp4', 'webm': 'video/webm', 'mp3': 'audio/mpeg',
|
|
355
|
-
'wav': 'audio/wav', 'pdf': 'application/pdf', 'json': 'application/json',
|
|
356
|
-
'js': 'application/javascript', 'css': 'text/css', 'html': 'text/html',
|
|
357
|
-
'txt': 'text/plain', 'md': 'text/markdown', 'csv': 'text/csv',
|
|
358
|
-
'zip': 'application/zip', 'gz': 'application/gzip',
|
|
359
|
-
};
|
|
360
|
-
const contentType = mimeMap[ext] || 'application/octet-stream';
|
|
361
|
-
|
|
362
|
-
console.log(`⬆️ Uploading: ${fileName} (${formatBytes(stat.size)}) → ${key}`);
|
|
363
|
-
|
|
364
|
-
try {
|
|
365
|
-
const s3 = await getS3();
|
|
366
|
-
const buffer = fs.readFileSync(filePath);
|
|
367
|
-
const result = await s3.uploadFile(key, buffer, contentType, { bucketName });
|
|
368
|
-
console.log(`✅ Uploaded: ${result.key} (${formatBytes(result.fileSize)})`);
|
|
369
|
-
} catch (err) {
|
|
370
|
-
console.error(`❌ Upload failed: ${err.message}`);
|
|
371
|
-
process.exit(1);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
async function uploadFolder(folderPath) {
|
|
376
|
-
const bucketName = findFlag('bucket');
|
|
377
|
-
const prefix = cmdArgs[1] || path.basename(folderPath);
|
|
378
|
-
|
|
379
|
-
// Recursively collect all files
|
|
380
|
-
const files = walkDir(folderPath);
|
|
381
|
-
|
|
382
|
-
if (files.length === 0) {
|
|
383
|
-
console.log('📭 No files found in folder.');
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
console.log(`📁 Uploading folder: ${folderPath} (${files.length} files) → ${prefix}/`);
|
|
388
|
-
console.log('');
|
|
389
|
-
|
|
390
|
-
let uploaded = 0;
|
|
391
|
-
let failed = 0;
|
|
392
|
-
|
|
393
|
-
for (const file of files) {
|
|
394
|
-
const relativePath = path.relative(folderPath, file).replace(/\\/g, '/');
|
|
395
|
-
const key = `${prefix}/${relativePath}`;
|
|
396
|
-
const stat = fs.statSync(file);
|
|
397
|
-
const ext = path.extname(file).slice(1).toLowerCase();
|
|
398
|
-
const mimeMap = {
|
|
399
|
-
'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'png': 'image/png',
|
|
400
|
-
'gif': 'image/gif', 'webp': 'image/webp', 'svg': 'image/svg+xml',
|
|
401
|
-
};
|
|
402
|
-
const contentType = mimeMap[ext] || 'application/octet-stream';
|
|
403
|
-
|
|
404
|
-
try {
|
|
405
|
-
const s3 = await getS3();
|
|
406
|
-
const buffer = fs.readFileSync(file);
|
|
407
|
-
await s3.uploadFile(key, buffer, contentType, { bucketName });
|
|
408
|
-
console.log(` ✅ ${relativePath}`);
|
|
409
|
-
uploaded++;
|
|
410
|
-
} catch (err) {
|
|
411
|
-
console.error(` ❌ ${relativePath}: ${err.message}`);
|
|
412
|
-
failed++;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
console.log('');
|
|
417
|
-
console.log(`Done: ${uploaded} uploaded, ${failed} failed.`);
|
|
418
|
-
if (failed > 0) process.exit(1);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
function walkDir(dir) {
|
|
422
|
-
const results = [];
|
|
423
|
-
const list = fs.readdirSync(dir);
|
|
424
|
-
for (const item of list) {
|
|
425
|
-
const fullPath = path.join(dir, item);
|
|
426
|
-
if (fs.statSync(fullPath).isDirectory()) {
|
|
427
|
-
results.push(...walkDir(fullPath));
|
|
428
|
-
} else {
|
|
429
|
-
results.push(fullPath);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
return results;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
async function cmdDownload() {
|
|
436
|
-
const key = cmdArgs[0];
|
|
437
|
-
if (!key) {
|
|
438
|
-
console.error('❌ Usage: mbkbucket download <key> [destination]');
|
|
439
|
-
process.exit(1);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
const dest = cmdArgs[1] || path.basename(key);
|
|
443
|
-
const bucketName = findFlag('bucket');
|
|
444
|
-
|
|
445
|
-
try {
|
|
446
|
-
const s3 = await getS3();
|
|
447
|
-
|
|
448
|
-
// Check if it's a folder (key ends with / or has children)
|
|
449
|
-
const isFolderLike = key.endsWith('/');
|
|
450
|
-
if (isFolderLike) {
|
|
451
|
-
await downloadFolder(s3, key, dest, bucketName);
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
// Single file download
|
|
456
|
-
console.log(`⬇️ Downloading: ${key} → ${dest}`);
|
|
457
|
-
const result = await s3.downloadFile(key, { bucketName });
|
|
458
|
-
|
|
459
|
-
if (result.notModified) {
|
|
460
|
-
console.log('ℹ️ File not modified.');
|
|
461
|
-
return;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// Read the body stream
|
|
465
|
-
const body = await streamToBuffer(result.Body);
|
|
466
|
-
const destDir = path.dirname(path.resolve(dest));
|
|
467
|
-
if (!fs.existsSync(destDir)) {
|
|
468
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
469
|
-
}
|
|
470
|
-
fs.writeFileSync(path.resolve(dest), body);
|
|
471
|
-
|
|
472
|
-
console.log(`✅ Downloaded: ${dest} (${formatBytes(body.length)})`);
|
|
473
|
-
} catch (err) {
|
|
474
|
-
console.error(`❌ Download failed: ${err.message}`);
|
|
475
|
-
process.exit(1);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
async function downloadFolder(s3, prefix, destDir, bucketName) {
|
|
480
|
-
// List all files under the prefix
|
|
481
|
-
const allFiles = [];
|
|
482
|
-
let token = null;
|
|
483
|
-
const resolvedDest = path.resolve(destDir);
|
|
484
|
-
|
|
485
|
-
do {
|
|
486
|
-
const result = await s3.listfiles(prefix, { bucketName, continuationToken: token });
|
|
487
|
-
if (result.Contents) {
|
|
488
|
-
for (const obj of result.Contents) {
|
|
489
|
-
if (obj.Key && obj.Key !== prefix) {
|
|
490
|
-
allFiles.push(obj.Key);
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
token = result.IsTruncated ? result.NextContinuationToken : null;
|
|
495
|
-
} while (token);
|
|
496
|
-
|
|
497
|
-
if (allFiles.length === 0) {
|
|
498
|
-
console.log(`📭 No files found with prefix "${prefix}".`);
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
console.log(`📁 Downloading ${allFiles.length} files to ${resolvedDest}/`);
|
|
503
|
-
console.log('');
|
|
504
|
-
|
|
505
|
-
let downloaded = 0;
|
|
506
|
-
let failed = 0;
|
|
507
|
-
|
|
508
|
-
for (const fileKey of allFiles) {
|
|
509
|
-
const relativePath = fileKey.startsWith(prefix) ? fileKey.slice(prefix.length) : fileKey;
|
|
510
|
-
const filePath = path.join(resolvedDest, relativePath);
|
|
511
|
-
|
|
512
|
-
try {
|
|
513
|
-
const result = await s3.downloadFile(fileKey, { bucketName });
|
|
514
|
-
if (!result.notModified) {
|
|
515
|
-
const body = await streamToBuffer(result.Body);
|
|
516
|
-
const fileDir = path.dirname(filePath);
|
|
517
|
-
if (!fs.existsSync(fileDir)) fs.mkdirSync(fileDir, { recursive: true });
|
|
518
|
-
fs.writeFileSync(filePath, body);
|
|
519
|
-
}
|
|
520
|
-
console.log(` ✅ ${relativePath}`);
|
|
521
|
-
downloaded++;
|
|
522
|
-
} catch (err) {
|
|
523
|
-
console.error(` ❌ ${relativePath}: ${err.message}`);
|
|
524
|
-
failed++;
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
console.log('');
|
|
529
|
-
console.log(`Done: ${downloaded} downloaded, ${failed} failed.`);
|
|
530
|
-
if (failed > 0) process.exit(1);
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
async function streamToBuffer(stream) {
|
|
534
|
-
if (Buffer.isBuffer(stream)) return stream;
|
|
535
|
-
if (stream?.transformToWeb) {
|
|
536
|
-
// AWS SDK v3 sometimes returns Node.js Readable
|
|
537
|
-
const chunks = [];
|
|
538
|
-
for await (const chunk of stream) {
|
|
539
|
-
chunks.push(chunk);
|
|
540
|
-
}
|
|
541
|
-
return Buffer.concat(chunks);
|
|
542
|
-
}
|
|
543
|
-
// Web ReadableStream
|
|
544
|
-
const reader = stream.getReader();
|
|
545
|
-
const chunks = [];
|
|
546
|
-
while (true) {
|
|
547
|
-
const { done, value } = await reader.read();
|
|
548
|
-
if (done) break;
|
|
549
|
-
chunks.push(value);
|
|
550
|
-
}
|
|
551
|
-
return Buffer.concat(chunks);
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
async function cmdDelete() {
|
|
555
|
-
const key = cmdArgs[0];
|
|
556
|
-
if (!key) {
|
|
557
|
-
console.error('❌ Usage: mbkbucket delete <key>');
|
|
558
|
-
process.exit(1);
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
const bucketName = findFlag('bucket');
|
|
562
|
-
|
|
563
|
-
try {
|
|
564
|
-
const s3 = await getS3();
|
|
565
|
-
console.log(`🗑️ Deleting: ${key}`);
|
|
566
|
-
const result = await s3.deleteFile(key, bucketName);
|
|
567
|
-
console.log(`✅ Deleted: ${result.key}`);
|
|
568
|
-
} catch (err) {
|
|
569
|
-
console.error(`❌ Delete failed: ${err.message}`);
|
|
570
|
-
process.exit(1);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
async function cmdDeleteFolder() {
|
|
575
|
-
const prefix = cmdArgs[0];
|
|
576
|
-
if (!prefix) {
|
|
577
|
-
console.error('❌ Usage: mbkbucket delete-folder <prefix>');
|
|
578
|
-
process.exit(1);
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
const bucketName = findFlag('bucket');
|
|
582
|
-
const cleanedPrefix = String(prefix).replace(/^\/+/, '').replace(/\/+$/, '');
|
|
583
|
-
|
|
584
|
-
console.log(`⚠️ This will delete ALL files under "${cleanedPrefix}/".`);
|
|
585
|
-
console.log(` Are you sure? Type "yes" to confirm:`);
|
|
586
|
-
|
|
587
|
-
// Simple confirmation
|
|
588
|
-
const confirmed = await promptLine();
|
|
589
|
-
if (confirmed.trim().toLowerCase() !== 'yes') {
|
|
590
|
-
console.log('Cancelled.');
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
try {
|
|
595
|
-
const s3 = await getS3();
|
|
596
|
-
console.log(`🗑️ Deleting folder: ${cleanedPrefix}/`);
|
|
597
|
-
const result = await s3.deleteFolder(cleanedPrefix, bucketName);
|
|
598
|
-
console.log(`✅ Deleted ${result.deletedCount} file(s) under "${cleanedPrefix}/".`);
|
|
599
|
-
} catch (err) {
|
|
600
|
-
console.error(`❌ Delete folder failed: ${err.message}`);
|
|
601
|
-
process.exit(1);
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
async function cmdInfo() {
|
|
606
|
-
const key = cmdArgs[0];
|
|
607
|
-
if (!key) {
|
|
608
|
-
console.error('❌ Usage: mbkbucket info <key>');
|
|
609
|
-
process.exit(1);
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
const bucketName = findFlag('bucket');
|
|
613
|
-
|
|
614
|
-
try {
|
|
615
|
-
const s3 = await getS3();
|
|
616
|
-
const meta = await s3.getFileMetadata(key, bucketName);
|
|
617
|
-
|
|
618
|
-
if (!meta.exists) {
|
|
619
|
-
console.log(`❌ File not found: ${key}`);
|
|
620
|
-
process.exit(1);
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
console.log('');
|
|
624
|
-
console.log(`📄 ${meta.key}`);
|
|
625
|
-
console.log('─'.repeat(60));
|
|
626
|
-
console.log(` Size: ${formatBytes(meta.ContentLength)}`);
|
|
627
|
-
console.log(` Type: ${meta.ContentType || 'unknown'}`);
|
|
628
|
-
console.log(` Last Modified: ${formatDate(meta.LastModified)}`);
|
|
629
|
-
console.log(` ETag: ${meta.ETag || '—'}`);
|
|
630
|
-
console.log(` Cache-Control: ${meta.CacheControl || '—'}`);
|
|
631
|
-
if (meta.Metadata && Object.keys(meta.Metadata).length > 0) {
|
|
632
|
-
console.log(' Metadata:');
|
|
633
|
-
for (const [k, v] of Object.entries(meta.Metadata)) {
|
|
634
|
-
console.log(` ${k}: ${v}`);
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
console.log('');
|
|
638
|
-
} catch (err) {
|
|
639
|
-
console.error(`❌ Info failed: ${err.message}`);
|
|
640
|
-
process.exit(1);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
// ---------------------------------------------------------------------------
|
|
645
|
-
// Config command
|
|
646
|
-
// ---------------------------------------------------------------------------
|
|
647
|
-
|
|
648
|
-
const DEMO_CONFIG = {
|
|
649
|
-
token: 'mbk_demo_token_replace_with_real_one_from_login',
|
|
650
|
-
tokenPrefix: 'mbk_demo',
|
|
651
|
-
username: 'demo-user',
|
|
652
|
-
serverUrl: 'https://your-app.example.com',
|
|
653
|
-
profile: {
|
|
654
|
-
name: 'Default CLI Profile',
|
|
655
|
-
scope: 'read-write',
|
|
656
|
-
allowedApps: ['mbkbucket'],
|
|
657
|
-
},
|
|
658
|
-
loggedInAt: new Date().toISOString(),
|
|
659
|
-
};
|
|
660
|
-
|
|
661
|
-
const VALID_CONFIG_KEYS = ['token', 'tokenPrefix', 'username', 'serverUrl', 'profile', 'profileKey', 'loggedInAt'];
|
|
662
|
-
|
|
663
|
-
async function cmdConfig() {
|
|
664
|
-
const sub = cmdArgs[0];
|
|
665
|
-
const key = cmdArgs[1];
|
|
666
|
-
const value = cmdArgs[2];
|
|
667
|
-
|
|
668
|
-
switch (sub) {
|
|
669
|
-
case 'get':
|
|
670
|
-
return cmdConfigGet(key);
|
|
671
|
-
case 'set':
|
|
672
|
-
return cmdConfigSet(key, value);
|
|
673
|
-
case 'unset':
|
|
674
|
-
case 'del':
|
|
675
|
-
case 'delete':
|
|
676
|
-
return cmdConfigUnset(key);
|
|
677
|
-
case 'path':
|
|
678
|
-
return cmdConfigPath();
|
|
679
|
-
case 'reset':
|
|
680
|
-
return cmdConfigReset();
|
|
681
|
-
case 'edit':
|
|
682
|
-
await cmdConfigEdit();
|
|
683
|
-
break;
|
|
684
|
-
default:
|
|
685
|
-
cmdConfigShow();
|
|
686
|
-
break;
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
function cmdConfigShow() {
|
|
691
|
-
const cfg = readConfig();
|
|
692
|
-
const entries = Object.keys(cfg).filter(k => !k.startsWith('_'));
|
|
693
|
-
|
|
694
|
-
if (entries.length === 0) {
|
|
695
|
-
console.log('📭 No config values set.');
|
|
696
|
-
console.log(` File: ${CONFIG_FILE}`);
|
|
697
|
-
console.log(' Run "mbkbucket config reset" to populate demo values.');
|
|
698
|
-
return;
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
console.log('');
|
|
702
|
-
console.log(`📋 Config (${CONFIG_FILE}):`);
|
|
703
|
-
console.log('─'.repeat(60));
|
|
704
|
-
|
|
705
|
-
for (const k of entries.sort()) {
|
|
706
|
-
const v = cfg[k];
|
|
707
|
-
const display = typeof v === 'object' && v !== null
|
|
708
|
-
? JSON.stringify(v)
|
|
709
|
-
: String(v);
|
|
710
|
-
|
|
711
|
-
// Mask the token for security
|
|
712
|
-
if (k === 'token' && display.length > 12) {
|
|
713
|
-
console.log(` ${k.padEnd(14)} ${display.slice(0, 8)}...${display.slice(-4)}`);
|
|
714
|
-
} else {
|
|
715
|
-
const truncated = display.length > 55 ? display.slice(0, 52) + '...' : display;
|
|
716
|
-
console.log(` ${k.padEnd(14)} ${truncated}`);
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
console.log('─'.repeat(60));
|
|
721
|
-
console.log(' Run "mbkbucket config set <key> <value>" to change a value.');
|
|
722
|
-
console.log(' Run "mbkbucket config edit" to open in your editor.');
|
|
723
|
-
console.log('');
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
function cmdConfigGet(key) {
|
|
727
|
-
if (!key) {
|
|
728
|
-
console.error('❌ Usage: mbkbucket config get <key>');
|
|
729
|
-
console.error(' Valid keys: ' + VALID_CONFIG_KEYS.join(', '));
|
|
730
|
-
process.exit(1);
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
const cfg = readConfig();
|
|
734
|
-
if (!(key in cfg)) {
|
|
735
|
-
console.error(`❌ Key not found: ${key}`);
|
|
736
|
-
console.error(' Valid keys: ' + VALID_CONFIG_KEYS.join(', '));
|
|
737
|
-
process.exit(1);
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
const v = cfg[key];
|
|
741
|
-
if (typeof v === 'object' && v !== null) {
|
|
742
|
-
console.log(JSON.stringify(v, null, 2));
|
|
743
|
-
} else {
|
|
744
|
-
console.log(String(v));
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
function cmdConfigSet(key, value) {
|
|
749
|
-
if (!key || value === undefined) {
|
|
750
|
-
console.error('❌ Usage: mbkbucket config set <key> <value>');
|
|
751
|
-
console.error(' Valid keys: ' + VALID_CONFIG_KEYS.join(', '));
|
|
752
|
-
console.error(' Example: mbkbucket config set serverUrl https://myapp.example.com');
|
|
753
|
-
process.exit(1);
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
if (!VALID_CONFIG_KEYS.includes(key)) {
|
|
757
|
-
console.error(`❌ Invalid key: ${key}`);
|
|
758
|
-
console.error(' Valid keys: ' + VALID_CONFIG_KEYS.join(', '));
|
|
759
|
-
process.exit(1);
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
// Parse JSON if it looks like an object/array
|
|
763
|
-
let parsed = value;
|
|
764
|
-
if ((value.startsWith('{') || value.startsWith('[')) &&
|
|
765
|
-
(value.endsWith('}') || value.endsWith(']'))) {
|
|
766
|
-
try {
|
|
767
|
-
parsed = JSON.parse(value);
|
|
768
|
-
} catch {
|
|
769
|
-
// Keep as string if parse fails
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
updateConfig({ [key]: parsed });
|
|
774
|
-
console.log(`✅ Set ${key} = ${typeof parsed === 'object' ? JSON.stringify(parsed) : parsed}`);
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
function cmdConfigUnset(key) {
|
|
778
|
-
if (!key) {
|
|
779
|
-
console.error('❌ Usage: mbkbucket config unset <key>');
|
|
780
|
-
console.error(' Valid keys: ' + VALID_CONFIG_KEYS.join(', '));
|
|
781
|
-
process.exit(1);
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
const cfg = readConfig();
|
|
785
|
-
if (!(key in cfg)) {
|
|
786
|
-
console.error(`❌ Key not found: ${key}`);
|
|
787
|
-
process.exit(1);
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
delete cfg[key];
|
|
791
|
-
writeConfig(cfg);
|
|
792
|
-
console.log(`🗑️ Removed: ${key}`);
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
function cmdConfigPath() {
|
|
796
|
-
console.log(`📁 Config directory: ${CONFIG_DIR}`);
|
|
797
|
-
console.log(`📄 Config file: ${CONFIG_FILE}`);
|
|
798
|
-
if (fs.existsSync(CONFIG_FILE)) {
|
|
799
|
-
const stat = fs.statSync(CONFIG_FILE);
|
|
800
|
-
console.log(` Size: ${formatBytes(stat.size)}`);
|
|
801
|
-
console.log(` Modified: ${formatDate(stat.mtime.toISOString())}`);
|
|
802
|
-
}
|
|
803
|
-
if (!isLoggedIn()) {
|
|
804
|
-
console.log('');
|
|
805
|
-
console.log('💡 Tip: Run "mbkbucket login --server <url>" to authenticate.');
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
async function cmdConfigReset() {
|
|
810
|
-
console.log('⚠️ This will overwrite your current config with demo values.');
|
|
811
|
-
console.log(' Are you sure? Type "yes" to confirm:');
|
|
812
|
-
|
|
813
|
-
const confirmed = await promptLine();
|
|
814
|
-
if (confirmed.trim().toLowerCase() !== 'yes') {
|
|
815
|
-
console.log('Cancelled.');
|
|
816
|
-
return;
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
writeConfig({ ...DEMO_CONFIG });
|
|
820
|
-
console.log('✅ Config reset to demo values.');
|
|
821
|
-
console.log(' Run "mbkbucket config" to view.');
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
async function cmdConfigEdit() {
|
|
825
|
-
const editor = process.env.EDITOR || process.env.VISUAL || 'notepad';
|
|
826
|
-
|
|
827
|
-
if (!fs.existsSync(CONFIG_DIR)) {
|
|
828
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
829
|
-
}
|
|
830
|
-
if (!fs.existsSync(CONFIG_FILE)) {
|
|
831
|
-
writeConfig({ ...DEMO_CONFIG });
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
console.log(`✏️ Opening ${CONFIG_FILE} with ${editor}...`);
|
|
835
|
-
|
|
836
|
-
const { spawn } = await import('node:child_process');
|
|
837
|
-
const child = spawn(editor, [CONFIG_FILE], {
|
|
838
|
-
stdio: 'inherit',
|
|
839
|
-
shell: true,
|
|
840
|
-
});
|
|
841
|
-
|
|
842
|
-
return new Promise((resolve) => {
|
|
843
|
-
child.on('exit', (code) => {
|
|
844
|
-
if (code === 0) {
|
|
845
|
-
console.log('✅ Editor closed.');
|
|
846
|
-
} else {
|
|
847
|
-
console.error(`⚠️ Editor exited with code ${code}.`);
|
|
848
|
-
}
|
|
849
|
-
resolve();
|
|
850
|
-
});
|
|
851
|
-
|
|
852
|
-
child.on('error', (err) => {
|
|
853
|
-
console.error(`❌ Could not open editor (${editor}): ${err.message}`);
|
|
854
|
-
console.error(' Set the EDITOR environment variable to your preferred editor.');
|
|
855
|
-
console.error(` Config file location: ${CONFIG_FILE}`);
|
|
856
|
-
resolve();
|
|
857
|
-
});
|
|
858
|
-
});
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
async function cmdSignedUrl() {
|
|
862
|
-
const key = cmdArgs[0];
|
|
863
|
-
if (!key) {
|
|
864
|
-
console.error('❌ Usage: mbkbucket signed-url <key> [--expires <seconds>]');
|
|
865
|
-
process.exit(1);
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
const bucketName = findFlag('bucket');
|
|
869
|
-
const expires = parseInt(findFlag('expires'), 10) || 3600;
|
|
870
|
-
|
|
871
|
-
try {
|
|
872
|
-
const s3 = await getS3();
|
|
873
|
-
console.log(`🔗 Generating signed URL (expires in ${expires}s)...`);
|
|
874
|
-
const result = await s3.generateSignedUrl(key, 'getObject', expires, bucketName);
|
|
875
|
-
console.log('');
|
|
876
|
-
console.log(result.url || result);
|
|
877
|
-
} catch (err) {
|
|
878
|
-
console.error(`❌ Signed URL failed: ${err.message}`);
|
|
879
|
-
process.exit(1);
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
// ---------------------------------------------------------------------------
|
|
884
|
-
// Prompt helper
|
|
885
|
-
// ---------------------------------------------------------------------------
|
|
886
|
-
function promptLine() {
|
|
887
|
-
return new Promise((resolve) => {
|
|
888
|
-
const { stdin, stdout } = process;
|
|
889
|
-
stdin.resume();
|
|
890
|
-
stdout.write('> ');
|
|
891
|
-
stdin.once('data', (data) => {
|
|
892
|
-
stdin.pause();
|
|
893
|
-
resolve(data.toString().trim());
|
|
894
|
-
});
|
|
895
|
-
});
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
// ---------------------------------------------------------------------------
|
|
899
|
-
// Command router
|
|
900
|
-
// ---------------------------------------------------------------------------
|
|
901
|
-
async function main() {
|
|
902
|
-
// Global flags
|
|
903
|
-
if (hasFlag('help')) {
|
|
904
|
-
showHelp();
|
|
905
|
-
return;
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
// Set APP_NAME from flag before any S3 import
|
|
909
|
-
const appFlag = findFlag('app');
|
|
910
|
-
if (appFlag && typeof appFlag === 'string') {
|
|
911
|
-
process.env.APP_NAME = appFlag;
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
const bucketFlag = findFlag('bucket');
|
|
915
|
-
if (bucketFlag && typeof bucketFlag === 'string') {
|
|
916
|
-
process.env.MBKAUTHE_BUCKET = bucketFlag;
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
switch (command) {
|
|
920
|
-
case 'login':
|
|
921
|
-
await cmdLogin();
|
|
922
|
-
break;
|
|
923
|
-
case 'logout':
|
|
924
|
-
cmdLogout();
|
|
925
|
-
break;
|
|
926
|
-
case 'whoami':
|
|
927
|
-
cmdWhoami();
|
|
928
|
-
break;
|
|
929
|
-
case 'list':
|
|
930
|
-
case 'ls':
|
|
931
|
-
await cmdList();
|
|
932
|
-
break;
|
|
933
|
-
case 'upload':
|
|
934
|
-
case 'up':
|
|
935
|
-
await cmdUpload();
|
|
936
|
-
break;
|
|
937
|
-
case 'download':
|
|
938
|
-
case 'dl':
|
|
939
|
-
await cmdDownload();
|
|
940
|
-
break;
|
|
941
|
-
case 'delete':
|
|
942
|
-
case 'rm':
|
|
943
|
-
await cmdDelete();
|
|
944
|
-
break;
|
|
945
|
-
case 'delete-folder':
|
|
946
|
-
case 'rmdir':
|
|
947
|
-
await cmdDeleteFolder();
|
|
948
|
-
break;
|
|
949
|
-
case 'info':
|
|
950
|
-
case 'stat':
|
|
951
|
-
await cmdInfo();
|
|
952
|
-
break;
|
|
953
|
-
case 'signed-url':
|
|
954
|
-
case 'sign':
|
|
955
|
-
await cmdSignedUrl();
|
|
956
|
-
break;
|
|
957
|
-
case 'config':
|
|
958
|
-
case 'cfg':
|
|
959
|
-
await cmdConfig();
|
|
960
|
-
break;
|
|
961
|
-
case 'help':
|
|
962
|
-
case '--help':
|
|
963
|
-
case '-h':
|
|
964
|
-
showHelp();
|
|
965
|
-
break;
|
|
966
|
-
default:
|
|
967
|
-
console.error(`❌ Unknown command: ${command}`);
|
|
968
|
-
console.error(' Run "mbkbucket --help" for usage information.');
|
|
969
|
-
process.exit(1);
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
main().catch((err) => {
|
|
974
|
-
console.error(`❌ Unexpected error: ${err.message}`);
|
|
975
|
-
if (process.env.DEBUG) console.error(err);
|
|
976
|
-
process.exit(1);
|
|
977
|
-
});
|