mbkbucket 3.3.1 → 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 +2 -12
- package/lib/cli.js +178 -512
- package/lib/public/bucketadmin.js +1 -1
- package/lib/src/app.js +37 -50
- package/lib/src/cli/auth-helper.js +24 -103
- package/lib/src/config/index.js +69 -94
- package/lib/src/config/validation.js +14 -30
- package/lib/src/controllers/api.controller.js +85 -176
- package/lib/src/controllers/dashboard.controller.js +12 -23
- package/lib/src/controllers/info.controller.js +17 -20
- package/lib/src/controllers/view.controller.js +23 -70
- package/lib/src/middleware/async-handler.js +1 -16
- package/lib/src/middleware/bucket-resolver.js +7 -13
- package/lib/src/middleware/error-handler.js +6 -14
- package/lib/src/middleware/rate-limiter.js +2 -10
- package/lib/src/middleware/security.js +1 -17
- package/lib/src/routes/api.routes.js +21 -47
- package/lib/src/routes/index.js +1 -9
- package/lib/src/routes/info.routes.js +2 -3
- package/lib/src/routes/view.routes.js +3 -8
- package/lib/src/services/s3.service.js +157 -349
- package/lib/src/utils/errors.js +28 -59
- package/lib/src/utils/helpers.js +124 -70
- package/package.json +6 -1
- /package/{lib/views → views}/bucket.handlebars +0 -0
- /package/{lib/views → views}/bucketadmin_alerts.handlebars +0 -0
- /package/{lib/views → views}/bucketadmin_delete_modal.handlebars +0 -0
- /package/{lib/views → views}/bucketadmin_filelist_skeleton.handlebars +0 -0
- /package/{lib/views → views}/bucketadmin_newfolder_modal.handlebars +0 -0
- /package/{lib/views → views}/bucketadmin_preview_modal.handlebars +0 -0
- /package/{lib/views → views}/bucketadmin_upload_modal.handlebars +0 -0
- /package/{lib/views → views}/bucketadmincontent.handlebars +0 -0
- /package/{lib/views → views}/bucketportal.handlebars +0 -0
- /package/{lib/views → views}/mbkbucket_info.handlebars +0 -0
package/lib/src/app.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import express from "express";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
3
4
|
import { fileURLToPath } from "url";
|
|
4
5
|
import { engine } from "express-handlebars";
|
|
5
6
|
import compression from "compression";
|
|
@@ -8,34 +9,25 @@ import { generalLimiter } from "./middleware/rate-limiter.js";
|
|
|
8
9
|
import { notFoundHandler, globalErrorHandler } from "./middleware/error-handler.js";
|
|
9
10
|
import bucketRoutes from "./routes/index.js";
|
|
10
11
|
import { packageJson } from "./config/index.js";
|
|
11
|
-
import { createLogger } from "
|
|
12
|
+
import { createLogger } from "#logger";
|
|
12
13
|
import { commonHandlebarsHelpers } from "./utils/helpers.js";
|
|
13
14
|
|
|
14
15
|
const debugServer = createLogger('server');
|
|
15
|
-
|
|
16
16
|
const __filename = fileURLToPath(import.meta.url);
|
|
17
17
|
const __dirname = path.dirname(__filename);
|
|
18
18
|
const isDevMode = process.env.NODE_ENV === "dev";
|
|
19
19
|
|
|
20
|
-
/**
|
|
21
|
-
* Creates and configures the Express application.
|
|
22
|
-
* @returns {express.Application}
|
|
23
|
-
*/
|
|
24
20
|
export function createApp() {
|
|
25
21
|
const server = express();
|
|
26
22
|
server.set('trust proxy', 1);
|
|
27
23
|
|
|
28
|
-
// Compression
|
|
29
24
|
server.use(compression());
|
|
30
|
-
|
|
31
|
-
// Body parsing
|
|
32
25
|
server.use(express.json({ limit: '10mb' }));
|
|
33
26
|
server.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
|
34
27
|
|
|
35
|
-
// Static
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
res.set('Cache-Control', dashboardAssetCacheControl);
|
|
28
|
+
// Static dashboard & helper assets
|
|
29
|
+
const serveAsset = (filename) => (_req, res) => {
|
|
30
|
+
res.set('Cache-Control', 'public, max-age=300, must-revalidate');
|
|
39
31
|
res.sendFile(path.join(__dirname, '..', 'public', filename), (err) => {
|
|
40
32
|
if (err) res.status(err.status || 404).end();
|
|
41
33
|
});
|
|
@@ -43,71 +35,66 @@ export function createApp() {
|
|
|
43
35
|
|
|
44
36
|
server.get('/mbkbucket/bucketadmin.css', serveAsset('bucketadmin.css'));
|
|
45
37
|
server.get('/mbkbucket/bucketadmin.js', serveAsset('bucketadmin.js'));
|
|
46
|
-
|
|
47
|
-
// Frontend helper utilities (drop-in for any HTML page)
|
|
48
38
|
server.get('/mbkbucket/mbkbucket-helper.css', serveAsset('mbkbucket-helper.css'));
|
|
49
39
|
server.get('/mbkbucket/mbkbucket-helper.js', serveAsset('mbkbucket-helper.js'));
|
|
50
|
-
|
|
51
|
-
// Helper component demo page (no auth — purely static HTML)
|
|
52
40
|
server.get('/mbkbucket/helper-demo', serveAsset('mbkbucket-helper-demo.html'));
|
|
53
41
|
|
|
54
|
-
|
|
42
|
+
const rootDir = path.resolve(__dirname, "..", "..");
|
|
43
|
+
const libDir = path.resolve(__dirname, "..");
|
|
44
|
+
const cwd = process.cwd();
|
|
45
|
+
|
|
46
|
+
const viewDirs = [
|
|
47
|
+
path.join(rootDir, "views"),
|
|
48
|
+
path.join(libDir, "views"),
|
|
49
|
+
path.join(rootDir, "node_modules", "mbkauthe", "views"),
|
|
50
|
+
path.resolve(cwd, "views"),
|
|
51
|
+
path.resolve(cwd, "node_modules", "mbkauthe", "views"),
|
|
52
|
+
].filter(dir => fs.existsSync(dir));
|
|
53
|
+
|
|
54
|
+
const partialsDir = [
|
|
55
|
+
path.join(rootDir, "views", "templates"),
|
|
56
|
+
path.join(rootDir, "views", "templates", "notice"),
|
|
57
|
+
path.join(rootDir, "views"),
|
|
58
|
+
path.join(rootDir, "views", "partial"),
|
|
59
|
+
path.join(libDir, "views", "templates"),
|
|
60
|
+
path.join(libDir, "views", "templates", "notice"),
|
|
61
|
+
path.join(libDir, "views"),
|
|
62
|
+
path.join(libDir, "views", "partial"),
|
|
63
|
+
path.join(rootDir, "node_modules", "mbkauthe", "views"),
|
|
64
|
+
path.resolve(cwd, "node_modules", "mbkauthe", "views"),
|
|
65
|
+
].filter(dir => fs.existsSync(dir));
|
|
66
|
+
|
|
67
|
+
// Handlebars template engine
|
|
55
68
|
server.engine("handlebars", engine({
|
|
56
69
|
extname: ".handlebars",
|
|
57
70
|
defaultLayout: false,
|
|
58
|
-
partialsDir: [
|
|
59
|
-
path.join(__dirname, "..", "views", "templates"),
|
|
60
|
-
path.join(__dirname, "..", "views", "templates", "notice"),
|
|
61
|
-
path.join(__dirname, "..", "views"),
|
|
62
|
-
path.join(__dirname, "..", "views", "partial"),
|
|
63
|
-
path.join(__dirname, "..", "node_modules", "mbkauthe", "views"),
|
|
64
|
-
path.join(__dirname, "..", "..", "mbkauthe", "views")
|
|
65
|
-
],
|
|
71
|
+
partialsDir: [...new Set(partialsDir)],
|
|
66
72
|
cache: process.env.NODE_ENV === "production",
|
|
67
73
|
helpers: {
|
|
68
74
|
...commonHandlebarsHelpers,
|
|
69
|
-
mbkbucket_cachebuster:
|
|
70
|
-
return "?v=" + packageJson.version;
|
|
71
|
-
}
|
|
75
|
+
mbkbucket_cachebuster: () => `?v=${packageJson.version}`
|
|
72
76
|
}
|
|
73
77
|
}));
|
|
74
78
|
|
|
75
79
|
server.set("view engine", "handlebars");
|
|
76
|
-
server.set("views", [
|
|
77
|
-
path.join(__dirname, "..", "views"),
|
|
78
|
-
path.join(__dirname, "..", "node_modules", "mbkauthe", "views"),
|
|
79
|
-
path.join(__dirname, "..", "..", "mbkauthe", "views")
|
|
80
|
-
]);
|
|
80
|
+
server.set("views", [...new Set(viewDirs)]);
|
|
81
81
|
|
|
82
|
-
// Auth routes
|
|
83
82
|
server.use(mbkautheRoutes);
|
|
84
|
-
|
|
85
|
-
// Rate limiting
|
|
86
83
|
server.use(generalLimiter);
|
|
87
|
-
|
|
88
|
-
// Bucket routes
|
|
89
84
|
server.use(bucketRoutes);
|
|
90
85
|
|
|
91
|
-
// Dev-mode request timing
|
|
92
86
|
if (isDevMode) {
|
|
93
|
-
debugServer("Dev mode is enabled. Starting server in dev mode.");
|
|
94
|
-
|
|
95
87
|
server.use((req, res, next) => {
|
|
96
|
-
const start =
|
|
88
|
+
const start = Date.now();
|
|
97
89
|
res.on('finish', () => {
|
|
98
|
-
|
|
99
|
-
const ms = diff[0] * 1000 + diff[1] / 1e6;
|
|
100
|
-
debugServer("[%s] %s %s %s - %s ms", new Date().toISOString(), req.method, req.originalUrl, res.statusCode, ms.toFixed(3));
|
|
90
|
+
debugServer('%s %s %s (%sms)', req.method, req.originalUrl, res.statusCode, Date.now() - start);
|
|
101
91
|
});
|
|
102
92
|
next();
|
|
103
93
|
});
|
|
104
94
|
|
|
105
|
-
server.get("/",
|
|
106
|
-
res.redirect("/mbkbucket");
|
|
107
|
-
});
|
|
95
|
+
server.get("/", (_req, res) => res.redirect("/mbkbucket"));
|
|
108
96
|
}
|
|
109
97
|
|
|
110
|
-
// Error handlers
|
|
111
98
|
server.use(notFoundHandler);
|
|
112
99
|
server.use(globalErrorHandler);
|
|
113
100
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLI Authentication Helper
|
|
3
|
-
*
|
|
4
3
|
* Handles mbkauthe device-flow login for the mbkbucket CLI.
|
|
5
4
|
* Stores credentials in ~/.mbkbucket/config.json
|
|
6
5
|
*/
|
|
@@ -8,39 +7,27 @@
|
|
|
8
7
|
import fs from 'node:fs';
|
|
9
8
|
import path from 'node:path';
|
|
10
9
|
import os from 'node:os';
|
|
11
|
-
import { createLogger } from '
|
|
10
|
+
import { createLogger } from '#logger';
|
|
12
11
|
|
|
13
12
|
const debugCli = createLogger('cli:auth');
|
|
14
13
|
|
|
15
14
|
export const CONFIG_DIR = path.join(os.homedir(), '.mbkbucket');
|
|
16
15
|
export const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
17
|
-
const POLL_INTERVAL_MS = 5000;
|
|
18
16
|
const MAX_POLL_TIME_MS = 15 * 60 * 1000; // 15 minutes
|
|
17
|
+
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
19
18
|
|
|
20
|
-
/**
|
|
21
|
-
* Read the stored CLI config.
|
|
22
|
-
* @returns {{ token?: string, tokenPrefix?: string, username?: string, serverUrl?: string, profile?: object }}
|
|
23
|
-
*/
|
|
24
19
|
export function readConfig() {
|
|
25
20
|
try {
|
|
26
|
-
|
|
27
|
-
const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');
|
|
28
|
-
return JSON.parse(raw);
|
|
21
|
+
return fs.existsSync(CONFIG_FILE) ? JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8')) : {};
|
|
29
22
|
} catch (err) {
|
|
30
23
|
debugCli('Failed to read config: %s', err.message);
|
|
31
24
|
return {};
|
|
32
25
|
}
|
|
33
26
|
}
|
|
34
27
|
|
|
35
|
-
/**
|
|
36
|
-
* Write config to disk.
|
|
37
|
-
* @param {object} config
|
|
38
|
-
*/
|
|
39
28
|
export function writeConfig(config) {
|
|
40
29
|
try {
|
|
41
|
-
if (!fs.existsSync(CONFIG_DIR)) {
|
|
42
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
43
|
-
}
|
|
30
|
+
if (!fs.existsSync(CONFIG_DIR)) fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
44
31
|
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
45
32
|
} catch (err) {
|
|
46
33
|
console.error(`❌ Failed to write config: ${err.message}`);
|
|
@@ -48,14 +35,9 @@ export function writeConfig(config) {
|
|
|
48
35
|
}
|
|
49
36
|
}
|
|
50
37
|
|
|
51
|
-
/**
|
|
52
|
-
* Clear stored credentials (logout).
|
|
53
|
-
*/
|
|
54
38
|
export function clearConfig() {
|
|
55
39
|
try {
|
|
56
|
-
if (fs.existsSync(CONFIG_FILE))
|
|
57
|
-
fs.unlinkSync(CONFIG_FILE);
|
|
58
|
-
}
|
|
40
|
+
if (fs.existsSync(CONFIG_FILE)) fs.unlinkSync(CONFIG_FILE);
|
|
59
41
|
return true;
|
|
60
42
|
} catch (err) {
|
|
61
43
|
debugCli('Failed to clear config: %s', err.message);
|
|
@@ -63,49 +45,27 @@ export function clearConfig() {
|
|
|
63
45
|
}
|
|
64
46
|
}
|
|
65
47
|
|
|
66
|
-
/**
|
|
67
|
-
* Merge partial config into stored config.
|
|
68
|
-
* @param {object} partial
|
|
69
|
-
*/
|
|
70
48
|
export function updateConfig(partial) {
|
|
71
|
-
const
|
|
72
|
-
const updated = { ...current, ...partial };
|
|
49
|
+
const updated = { ...readConfig(), ...partial };
|
|
73
50
|
writeConfig(updated);
|
|
74
51
|
return updated;
|
|
75
52
|
}
|
|
76
53
|
|
|
77
|
-
/**
|
|
78
|
-
* Check if the user is logged in (has a stored token).
|
|
79
|
-
*/
|
|
80
54
|
export function isLoggedIn() {
|
|
81
|
-
const
|
|
82
|
-
return
|
|
55
|
+
const { token, serverUrl } = readConfig();
|
|
56
|
+
return Boolean(token && serverUrl);
|
|
83
57
|
}
|
|
84
58
|
|
|
85
|
-
/**
|
|
86
|
-
* Get the stored API token.
|
|
87
|
-
*/
|
|
88
59
|
export function getStoredToken() {
|
|
89
60
|
return readConfig().token || null;
|
|
90
61
|
}
|
|
91
62
|
|
|
92
|
-
/**
|
|
93
|
-
* Get the stored server URL.
|
|
94
|
-
*/
|
|
95
63
|
export function getStoredServerUrl() {
|
|
96
64
|
return readConfig().serverUrl || null;
|
|
97
65
|
}
|
|
98
66
|
|
|
99
|
-
/**
|
|
100
|
-
* Perform a device-flow login against an mbkauthe-powered server.
|
|
101
|
-
*
|
|
102
|
-
* @param {{ serverUrl: string, profileKey?: string }} options
|
|
103
|
-
* @returns {Promise<{ token: string, tokenPrefix: string, username: string, profile: object }>}
|
|
104
|
-
*/
|
|
105
67
|
export async function deviceFlowLogin({ serverUrl, profileKey }) {
|
|
106
68
|
const baseUrl = String(serverUrl).replace(/\/+$/, '');
|
|
107
|
-
|
|
108
|
-
// --- Step 1: Request a device code ---
|
|
109
69
|
console.log(`🔑 Requesting device login from ${baseUrl} ...`);
|
|
110
70
|
|
|
111
71
|
let deviceResp;
|
|
@@ -115,53 +75,32 @@ export async function deviceFlowLogin({ serverUrl, profileKey }) {
|
|
|
115
75
|
headers: { 'Content-Type': 'application/json' },
|
|
116
76
|
body: JSON.stringify({
|
|
117
77
|
clientName: `mbkbucket-cli-${os.hostname()}`,
|
|
118
|
-
...(profileKey
|
|
78
|
+
...(profileKey && { profileKey }),
|
|
119
79
|
}),
|
|
120
80
|
});
|
|
121
|
-
|
|
122
81
|
deviceResp = await res.json();
|
|
123
|
-
|
|
124
|
-
if (!deviceResp.success) {
|
|
125
|
-
throw new Error(deviceResp.message || 'Failed to initiate device login');
|
|
126
|
-
}
|
|
82
|
+
if (!deviceResp.success) throw new Error(deviceResp.message || 'Failed to initiate device login');
|
|
127
83
|
} catch (err) {
|
|
128
84
|
if (err.message.includes('Failed to initiate') || err.message.includes('fetch')) {
|
|
129
|
-
throw new Error(
|
|
130
|
-
`Could not reach mbkauthe server at ${baseUrl}/api/cli/device.\n` +
|
|
131
|
-
`Verify the server URL and that CLI_AUTH_ENABLED=true on the server.`
|
|
132
|
-
);
|
|
85
|
+
throw new Error(`Could not reach mbkauthe server at ${baseUrl}/api/cli/device.\nVerify the server URL and that CLI_AUTH_ENABLED=true on the server.`);
|
|
133
86
|
}
|
|
134
87
|
throw err;
|
|
135
88
|
}
|
|
136
89
|
|
|
137
|
-
const {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
deviceCode,
|
|
141
|
-
expiresIn,
|
|
142
|
-
interval,
|
|
143
|
-
profile,
|
|
144
|
-
} = deviceResp;
|
|
145
|
-
|
|
146
|
-
// --- Step 2: Prompt user to approve ---
|
|
147
|
-
console.log('');
|
|
148
|
-
console.log('┌─────────────────────────────────────────────────────────┐');
|
|
90
|
+
const { verificationUrl, userCode, deviceCode, expiresIn, interval, profile } = deviceResp;
|
|
91
|
+
|
|
92
|
+
console.log('\n┌─────────────────────────────────────────────────────────┐');
|
|
149
93
|
console.log('│ │');
|
|
150
|
-
console.log(
|
|
94
|
+
console.log('│ 🌐 Open this URL in your browser to approve: │');
|
|
151
95
|
console.log(`│ ${verificationUrl.padEnd(53)}│`);
|
|
152
|
-
console.log(
|
|
96
|
+
console.log('│ │');
|
|
153
97
|
console.log(`│ 🔢 Or enter this code: ${userCode.padEnd(30)}│`);
|
|
154
98
|
console.log('│ │');
|
|
155
|
-
console.log('
|
|
156
|
-
console.log('');
|
|
99
|
+
console.log('└─────────────────────────────────────────────────────────┘\n');
|
|
157
100
|
|
|
158
|
-
if (profile) {
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
console.log(` Waiting for approval (expires in ${Math.floor(expiresIn / 60)} minutes)...`);
|
|
162
|
-
console.log('');
|
|
101
|
+
if (profile) console.log(` Profile: ${profile.name} (scope: ${profile.scope || 'full'})`);
|
|
102
|
+
console.log(` Waiting for approval (expires in ${Math.floor(expiresIn / 60)} minutes)...\n`);
|
|
163
103
|
|
|
164
|
-
// --- Step 3: Poll for token ---
|
|
165
104
|
const pollInterval = (interval || 5) * 1000;
|
|
166
105
|
const deadline = Date.now() + MAX_POLL_TIME_MS;
|
|
167
106
|
let attempts = 0;
|
|
@@ -190,34 +129,16 @@ export async function deviceFlowLogin({ serverUrl, profileKey }) {
|
|
|
190
129
|
};
|
|
191
130
|
}
|
|
192
131
|
|
|
193
|
-
if (pollBody.status === 'denied')
|
|
194
|
-
|
|
195
|
-
|
|
132
|
+
if (pollBody.status === 'denied') throw new Error('Login request was denied.');
|
|
133
|
+
if (pollBody.status === 'expired') throw new Error('Login request expired. Please try again.');
|
|
134
|
+
if (pollBody.status === 'completed') throw new Error('This login session has already been completed.');
|
|
196
135
|
|
|
197
|
-
if (
|
|
198
|
-
throw new Error('Login request expired. Please try again.');
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (pollBody.status === 'completed') {
|
|
202
|
-
throw new Error('This login session has already been completed.');
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Still pending — show a dot every 5 polls
|
|
206
|
-
if (attempts % 5 === 0) {
|
|
207
|
-
process.stdout.write('.');
|
|
208
|
-
}
|
|
136
|
+
if (attempts % 5 === 0) process.stdout.write('.');
|
|
209
137
|
} catch (err) {
|
|
210
|
-
if (
|
|
211
|
-
throw err;
|
|
212
|
-
}
|
|
213
|
-
// Network errors during poll — retry
|
|
138
|
+
if (['denied', 'expired', 'completed'].some(k => err.message.includes(k))) throw err;
|
|
214
139
|
debugCli('Poll error (will retry): %s', err.message);
|
|
215
140
|
}
|
|
216
141
|
}
|
|
217
142
|
|
|
218
143
|
throw new Error('Login timed out. Please try again.');
|
|
219
144
|
}
|
|
220
|
-
|
|
221
|
-
function sleep(ms) {
|
|
222
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
223
|
-
}
|
package/lib/src/config/index.js
CHANGED
|
@@ -1,133 +1,108 @@
|
|
|
1
1
|
import { createRequire } from "module";
|
|
2
2
|
import dotenv from "dotenv";
|
|
3
3
|
import { parseAndValidateMbkbucketVar, parseAndValidateBucketConnection } from "./validation.js";
|
|
4
|
-
import { createLogger } from "
|
|
4
|
+
import { createLogger } from "#logger";
|
|
5
5
|
|
|
6
6
|
dotenv.config();
|
|
7
7
|
const debugConfig = createLogger('config');
|
|
8
|
-
|
|
9
|
-
// Load package.json from mbkbucket package (not parent project)
|
|
10
8
|
const require = createRequire(import.meta.url);
|
|
9
|
+
|
|
10
|
+
// Load mbkbucket package.json
|
|
11
11
|
let packageJson;
|
|
12
12
|
try {
|
|
13
|
-
|
|
14
|
-
packageJson = require("mbkbucket/package.json");
|
|
13
|
+
packageJson = require("mbkbucket/package.json");
|
|
15
14
|
} catch {
|
|
16
|
-
|
|
17
|
-
packageJson = require("../../../package.json");
|
|
15
|
+
packageJson = require("../../../package.json");
|
|
18
16
|
}
|
|
19
17
|
|
|
20
18
|
// Parent project version
|
|
21
|
-
let appVersion;
|
|
19
|
+
let appVersion = "unknown";
|
|
22
20
|
try {
|
|
23
|
-
|
|
21
|
+
appVersion = require("../../../../package.json")?.version ?? require(`${process.cwd()}/package.json`)?.version ?? "unknown";
|
|
24
22
|
} catch {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
} catch {
|
|
29
|
-
appVersion = "unknown";
|
|
30
|
-
}
|
|
23
|
+
try {
|
|
24
|
+
appVersion = require(`${process.cwd()}/package.json`)?.version ?? "unknown";
|
|
25
|
+
} catch {}
|
|
31
26
|
}
|
|
32
27
|
|
|
33
|
-
function compareVersions(a, b) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
28
|
+
export function compareVersions(a = '', b = '') {
|
|
29
|
+
const left = String(a).split(/[.-]/).map(p => parseInt(p, 10) || 0);
|
|
30
|
+
const right = String(b).split(/[.-]/).map(p => parseInt(p, 10) || 0);
|
|
31
|
+
const len = Math.max(left.length, right.length);
|
|
37
32
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
33
|
+
for (let i = 0; i < len; i++) {
|
|
34
|
+
const diff = (left[i] || 0) - (right[i] || 0);
|
|
35
|
+
if (diff !== 0) return diff;
|
|
36
|
+
}
|
|
37
|
+
return 0;
|
|
43
38
|
}
|
|
44
39
|
|
|
45
|
-
|
|
46
|
-
async function getLatestVersion() {
|
|
40
|
+
export async function getLatestVersion() {
|
|
47
41
|
try {
|
|
48
|
-
const
|
|
49
|
-
if (!
|
|
50
|
-
debugConfig('GitHub API responded with status %s',
|
|
42
|
+
const res = await fetch('https://raw.githubusercontent.com/MIbnEKhalid/mbkbucket/master/package.json');
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
debugConfig('GitHub API responded with status %s', res.status);
|
|
51
45
|
return "0.0.0";
|
|
52
46
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
debugConfig('Error fetching latest version from GitHub: %s', error.message);
|
|
47
|
+
const data = await res.json();
|
|
48
|
+
return data.version;
|
|
49
|
+
} catch (err) {
|
|
50
|
+
debugConfig('Error fetching latest version from GitHub: %s', err.message);
|
|
58
51
|
return null;
|
|
59
52
|
}
|
|
60
53
|
}
|
|
61
54
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
debugConfig('Running latest version (%s).', packageJson.version);
|
|
72
|
-
}
|
|
73
|
-
} catch (error) {
|
|
74
|
-
debugConfig('Failed to check for updates: %s', error.message);
|
|
55
|
+
export async function checkVersion() {
|
|
56
|
+
try {
|
|
57
|
+
const latest = await getLatestVersion();
|
|
58
|
+
if (!latest) return;
|
|
59
|
+
const diff = compareVersions(latest, packageJson.version);
|
|
60
|
+
if (diff > 0) {
|
|
61
|
+
console.log(`[mbkbucket] ⚠️ Update available: v${latest} (current: v${packageJson.version})`);
|
|
62
|
+
} else {
|
|
63
|
+
debugConfig('Running latest version (%s).', packageJson.version);
|
|
75
64
|
}
|
|
65
|
+
} catch (err) {
|
|
66
|
+
debugConfig('Failed to check for updates: %s', err.message);
|
|
67
|
+
}
|
|
76
68
|
}
|
|
77
69
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
debugConfig('Configuration loaded');
|
|
86
|
-
return parsed;
|
|
87
|
-
} catch (error) {
|
|
88
|
-
throw new Error(`[mbkbucket] Configuration Validation Failed:\n - ${error.message}`);
|
|
89
|
-
}
|
|
70
|
+
export function validateConfiguration() {
|
|
71
|
+
try {
|
|
72
|
+
return parseAndValidateMbkbucketVar(process.env.mbkbucketVar);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
throw new Error(`[mbkbucket] Configuration Validation Failed:\n - ${err.message}`);
|
|
75
|
+
}
|
|
90
76
|
}
|
|
91
77
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
} catch (error) {
|
|
105
|
-
throw new Error(`[mbkbucket] BucketConnection validation failed:\n - ${error.message}`);
|
|
106
|
-
}
|
|
78
|
+
export function validateBucketConnection() {
|
|
79
|
+
if (!process.env.BucketConnection) {
|
|
80
|
+
console.warn(`[mbkbucket] ⚠️ BucketConnection not configured. You need to set this environment variable.`);
|
|
81
|
+
console.warn(`[mbkbucket] Example format:\n[mbkbucket] BucketConnection={"r2":{"BUCKET_NAME":"my-bucket","ACCESS_KEY_ID":"key","SECRET_ACCESS_KEY":"secret","ENDPOINT":"https://..."}}`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
parseAndValidateBucketConnection(process.env.BucketConnection);
|
|
86
|
+
debugConfig('BucketConnection validated');
|
|
87
|
+
} catch (err) {
|
|
88
|
+
throw new Error(`[mbkbucket] BucketConnection validation failed:\n - ${err.message}`);
|
|
89
|
+
}
|
|
107
90
|
}
|
|
108
91
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
92
|
+
export function validateAllConfiguration() {
|
|
93
|
+
try {
|
|
94
|
+
validateConfiguration();
|
|
95
|
+
validateBucketConnection();
|
|
96
|
+
return true;
|
|
97
|
+
} catch (err) {
|
|
98
|
+
console.error(err.message);
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
120
101
|
}
|
|
121
102
|
|
|
122
|
-
const mbkbucketVar = validateConfiguration();
|
|
103
|
+
export const mbkbucketVar = validateConfiguration();
|
|
123
104
|
|
|
124
105
|
export {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
mbkbucketVar,
|
|
128
|
-
checkVersion,
|
|
129
|
-
getLatestVersion,
|
|
130
|
-
validateConfiguration,
|
|
131
|
-
validateBucketConnection,
|
|
132
|
-
validateAllConfiguration
|
|
106
|
+
packageJson,
|
|
107
|
+
appVersion
|
|
133
108
|
};
|