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.
Files changed (39) hide show
  1. package/index.js +8 -18
  2. package/lib/cli.js +642 -0
  3. package/{public → lib/public}/bucketadmin.js +1 -1
  4. package/{src → lib/src}/app.js +37 -50
  5. package/{src → lib/src}/cli/auth-helper.js +24 -103
  6. package/lib/src/config/index.js +108 -0
  7. package/{src → lib/src}/config/validation.js +14 -30
  8. package/{src → lib/src}/controllers/api.controller.js +85 -176
  9. package/{src → lib/src}/controllers/dashboard.controller.js +12 -23
  10. package/{src → lib/src}/controllers/info.controller.js +17 -20
  11. package/{src → lib/src}/controllers/view.controller.js +23 -70
  12. package/{src → lib/src}/middleware/async-handler.js +1 -16
  13. package/{src → lib/src}/middleware/bucket-resolver.js +7 -13
  14. package/{src → lib/src}/middleware/error-handler.js +6 -14
  15. package/{src → lib/src}/middleware/rate-limiter.js +2 -10
  16. package/{src → lib/src}/middleware/security.js +1 -17
  17. package/lib/src/routes/api.routes.js +40 -0
  18. package/{src → lib/src}/routes/index.js +1 -9
  19. package/{src → lib/src}/routes/info.routes.js +2 -3
  20. package/{src → lib/src}/routes/view.routes.js +3 -8
  21. package/{src → lib/src}/services/s3.service.js +157 -349
  22. package/lib/src/utils/errors.js +45 -0
  23. package/lib/src/utils/helpers.js +251 -0
  24. package/package.json +18 -3
  25. package/.github/PACKAGE.md +0 -19
  26. package/.github/workflows/publish.yml +0 -88
  27. package/cli.js +0 -977
  28. package/index.d.ts +0 -444
  29. package/lib/index.js +0 -17
  30. package/src/config/index.js +0 -133
  31. package/src/routes/api.routes.js +0 -66
  32. package/src/utils/errors.js +0 -76
  33. package/src/utils/helpers.js +0 -197
  34. package/test/config-validation.test.js +0 -116
  35. /package/{public → lib/public}/bucketadmin.css +0 -0
  36. /package/{public → lib/public}/mbkbucket-helper-demo.html +0 -0
  37. /package/{public → lib/public}/mbkbucket-helper.css +0 -0
  38. /package/{public → lib/public}/mbkbucket-helper.js +0 -0
  39. /package/{src → lib/src}/utils/logger.js +0 -0
@@ -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 "./utils/logger.js";
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 assets (CSS/JS for dashboard)
36
- const dashboardAssetCacheControl = 'public, max-age=300, must-revalidate';
37
- const serveAsset = (filename) => (req, res) => {
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
- // Handlebars configuration
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: function () {
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 = process.hrtime();
88
+ const start = Date.now();
97
89
  res.on('finish', () => {
98
- const diff = process.hrtime(start);
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("/", async (req, res) => {
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 '../utils/logger.js';
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
- if (!fs.existsSync(CONFIG_FILE)) return {};
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 current = readConfig();
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 cfg = readConfig();
82
- return !!(cfg.token && cfg.serverUrl);
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 ? { 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
- verificationUrl,
139
- userCode,
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(`│ 🌐 Open this URL in your browser to approve: │`);
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
- console.log(` Profile: ${profile.name} (scope: ${profile.scope || 'full'})`);
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
- throw new Error('Login request was denied.');
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 (pollBody.status === 'expired') {
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 (err.message.includes('denied') || err.message.includes('expired') || err.message.includes('completed')) {
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
- }
@@ -0,0 +1,108 @@
1
+ import { createRequire } from "module";
2
+ import dotenv from "dotenv";
3
+ import { parseAndValidateMbkbucketVar, parseAndValidateBucketConnection } from "./validation.js";
4
+ import { createLogger } from "#logger";
5
+
6
+ dotenv.config();
7
+ const debugConfig = createLogger('config');
8
+ const require = createRequire(import.meta.url);
9
+
10
+ // Load mbkbucket package.json
11
+ let packageJson;
12
+ try {
13
+ packageJson = require("mbkbucket/package.json");
14
+ } catch {
15
+ packageJson = require("../../../package.json");
16
+ }
17
+
18
+ // Parent project version
19
+ let appVersion = "unknown";
20
+ try {
21
+ appVersion = require("../../../../package.json")?.version ?? require(`${process.cwd()}/package.json`)?.version ?? "unknown";
22
+ } catch {
23
+ try {
24
+ appVersion = require(`${process.cwd()}/package.json`)?.version ?? "unknown";
25
+ } catch {}
26
+ }
27
+
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);
32
+
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;
38
+ }
39
+
40
+ export async function getLatestVersion() {
41
+ try {
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);
45
+ return "0.0.0";
46
+ }
47
+ const data = await res.json();
48
+ return data.version;
49
+ } catch (err) {
50
+ debugConfig('Error fetching latest version from GitHub: %s', err.message);
51
+ return null;
52
+ }
53
+ }
54
+
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);
64
+ }
65
+ } catch (err) {
66
+ debugConfig('Failed to check for updates: %s', err.message);
67
+ }
68
+ }
69
+
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
+ }
76
+ }
77
+
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
+ }
90
+ }
91
+
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
+ }
101
+ }
102
+
103
+ export const mbkbucketVar = validateConfiguration();
104
+
105
+ export {
106
+ packageJson,
107
+ appVersion
108
+ };
@@ -1,20 +1,18 @@
1
1
  const TRUE_VALUES = new Set(['true', 't', '1', 'yes', 'y', 'on']);
2
2
  const FALSE_VALUES = new Set(['false', 'f', '0', 'no', 'n', 'off']);
3
3
 
4
- function normalizeBooleanLike(value) {
4
+ export function normalizeBooleanLike(value) {
5
5
  if (typeof value === 'boolean') return value;
6
6
  if (typeof value === 'string') {
7
- const normalized = value.trim().toLowerCase();
8
- if (TRUE_VALUES.has(normalized)) return true;
9
- if (FALSE_VALUES.has(normalized)) return false;
7
+ const norm = value.trim().toLowerCase();
8
+ if (TRUE_VALUES.has(norm)) return true;
9
+ if (FALSE_VALUES.has(norm)) return false;
10
10
  }
11
11
  return value;
12
12
  }
13
13
 
14
14
  export function parseAndValidateMbkbucketVar(rawValue) {
15
- const errors = [];
16
15
  let parsed;
17
-
18
16
  try {
19
17
  parsed = rawValue ? JSON.parse(rawValue) : {};
20
18
  } catch {
@@ -25,14 +23,10 @@ export function parseAndValidateMbkbucketVar(rawValue) {
25
23
  throw new Error('mbkbucketVar must be a valid object');
26
24
  }
27
25
 
28
- const defaults = {
29
- p_view_inline: true,
30
- publiView_enabled: false
31
- };
32
-
26
+ const defaults = { p_view_inline: true, publiView_enabled: false };
33
27
  for (const [key, defaultVal] of Object.entries(defaults)) {
34
- const current = parsed[key];
35
- if (current === undefined || (typeof current === 'string' && current.trim() === '')) {
28
+ const val = parsed[key];
29
+ if (val === undefined || (typeof val === 'string' && val.trim() === '')) {
36
30
  parsed[key] = defaultVal;
37
31
  }
38
32
  }
@@ -40,24 +34,16 @@ export function parseAndValidateMbkbucketVar(rawValue) {
40
34
  parsed.p_view_inline = normalizeBooleanLike(parsed.p_view_inline);
41
35
  parsed.publiView_enabled = normalizeBooleanLike(parsed.publiView_enabled);
42
36
 
43
- if (typeof parsed.p_view_inline !== 'boolean') {
44
- errors.push('mbkbucketVar.p_view_inline must be a boolean if provided');
45
- }
46
- if (typeof parsed.publiView_enabled !== 'boolean') {
47
- errors.push('mbkbucketVar.publiView_enabled must be a boolean if provided');
48
- }
49
-
50
- if (errors.length) {
51
- throw new Error(errors.join(' | '));
52
- }
37
+ const errors = [];
38
+ if (typeof parsed.p_view_inline !== 'boolean') errors.push('mbkbucketVar.p_view_inline must be a boolean if provided');
39
+ if (typeof parsed.publiView_enabled !== 'boolean') errors.push('mbkbucketVar.publiView_enabled must be a boolean if provided');
40
+ if (errors.length) throw new Error(errors.join(' | '));
53
41
 
54
42
  return parsed;
55
43
  }
56
44
 
57
45
  export function parseAndValidateBucketConnection(rawValue) {
58
- if (!rawValue) {
59
- return null;
60
- }
46
+ if (!rawValue) return null;
61
47
 
62
48
  let parsed;
63
49
  try {
@@ -66,7 +52,7 @@ export function parseAndValidateBucketConnection(rawValue) {
66
52
  throw new Error('BucketConnection is not valid JSON');
67
53
  }
68
54
 
69
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed) || Object.keys(parsed).length === 0) {
55
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed) || !Object.keys(parsed).length) {
70
56
  throw new Error('BucketConnection must be a non-empty object');
71
57
  }
72
58
 
@@ -77,7 +63,7 @@ export function parseAndValidateBucketConnection(rawValue) {
77
63
  throw new Error(`Bucket '${bucketName}' configuration must be an object, not a string. Remove quotes around the inner JSON object.`);
78
64
  }
79
65
 
80
- const missingFields = requiredFields.filter((field) => !config[field]);
66
+ const missingFields = requiredFields.filter(field => !config[field]);
81
67
  if (missingFields.length) {
82
68
  throw new Error(`Bucket '${bucketName}' is missing required fields: ${missingFields.join(', ')}`);
83
69
  }
@@ -85,5 +71,3 @@ export function parseAndValidateBucketConnection(rawValue) {
85
71
 
86
72
  return parsed;
87
73
  }
88
-
89
- export { normalizeBooleanLike };