@kadi.build/file-manager 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -202,7 +202,27 @@ Configuration is loaded from multiple sources (in priority order):
202
202
 
203
203
  1. Constructor options
204
204
  2. Environment variables (prefixed with `KADI_`)
205
- 3. `.env` file
205
+ 3. `config.yml` file (walks up from CWD — looks for `file-manager:` section)
206
+ 4. `.env` file (walks up from CWD — legacy fallback)
207
+
208
+ ### config.yml
209
+
210
+ Place a `config.yml` in your project root (or any parent directory):
211
+
212
+ ```yaml
213
+ file-manager:
214
+ local_root: ./
215
+ default_upload_directory: ./uploads
216
+ default_download_directory: ./downloads
217
+ default_temp_directory: ./temp
218
+ max_file_size: 1073741824
219
+ watch_debounce_ms: 100
220
+ watch_max_watchers: 10
221
+ compression_format: zip
222
+ compression_level: 6
223
+ ```
224
+
225
+ ### Environment Variables (overrides)
206
226
 
207
227
  | Variable | Description | Default |
208
228
  |----------|-------------|---------|
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kadi.build/file-manager",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Complete local and remote file management with watching and compression",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -38,6 +38,9 @@
38
38
  "ssh2": "^1.15.0",
39
39
  "ssh2-sftp-client": "^9.1.0"
40
40
  },
41
+ "optionalDependencies": {
42
+ "js-yaml": "^4.1.0"
43
+ },
41
44
  "devDependencies": {
42
45
  "fs-extra": "^11.2.0"
43
46
  },
@@ -3,12 +3,28 @@
3
3
  *
4
4
  * Handles configuration for file operations only.
5
5
  * Simplified from the monolith ConfigManager - no tunnel/server config.
6
+ *
7
+ * Configuration resolution order (later wins):
8
+ * 1. Built-in defaults
9
+ * 2. Walk-up config.yml → "file-manager" section
10
+ * 3. Walk-up .env file → fallback when no config.yml found
11
+ * 4. Environment variables (KADI_<KEY> or bare <KEY>)
12
+ * 5. Passed options (constructor/load)
6
13
  */
7
14
 
8
15
  import { promises as fs } from 'fs';
16
+ import { readFileSync, existsSync } from 'fs';
9
17
  import path from 'path';
10
18
  import os from 'os';
11
19
 
20
+ let yaml;
21
+ try {
22
+ const m = await import('js-yaml');
23
+ yaml = m.default || m;
24
+ } catch {
25
+ yaml = null;
26
+ }
27
+
12
28
  class ConfigManager {
13
29
  constructor() {
14
30
  this.config = {};
@@ -72,14 +88,11 @@ class ConfigManager {
72
88
  // Apply defaults
73
89
  this.config = { ...this.defaults };
74
90
 
75
- // Load from .env file if it exists
76
- try {
77
- const envPath = path.join(process.cwd(), '.env');
78
- const envContent = await fs.readFile(envPath, 'utf8');
79
- this._parseEnvContent(envContent);
80
- } catch (error) {
81
- // .env file not found, that's fine
82
- }
91
+ // Load from config.yml (walk-up discovery) "file-manager" section
92
+ this._loadFromConfigYml();
93
+
94
+ // Load from .env file if it exists (walk-up fallback)
95
+ await this._loadFromEnvFile();
83
96
 
84
97
  // Load from environment variables
85
98
  this._loadFromEnvironment();
@@ -106,6 +119,65 @@ class ConfigManager {
106
119
  return this;
107
120
  }
108
121
 
122
+ /**
123
+ * Walk up from CWD looking for config.yml and load the "file-manager" section.
124
+ */
125
+ _loadFromConfigYml() {
126
+ if (!yaml) return;
127
+
128
+ const configPath = this._walkUpFind('config.yml');
129
+ if (!configPath) return;
130
+
131
+ try {
132
+ const parsed = yaml.load(readFileSync(configPath, 'utf8'));
133
+ const section = parsed && parsed['file-manager'];
134
+ if (!section || typeof section !== 'object') return;
135
+
136
+ // Map YAML snake_case keys to UPPER_CASE config keys
137
+ for (const [ymlKey, ymlVal] of Object.entries(section)) {
138
+ const upperKey = ymlKey.toUpperCase();
139
+ if (this.defaults.hasOwnProperty(upperKey)) {
140
+ this.config[upperKey] = this._parseValue(String(ymlVal), this.defaults[upperKey]);
141
+ }
142
+ }
143
+ } catch {
144
+ // config.yml parse failure — continue with other sources
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Walk up from CWD looking for a .env file and load it.
150
+ */
151
+ async _loadFromEnvFile() {
152
+ const envPath = this._walkUpFind('.env');
153
+ if (!envPath) return;
154
+
155
+ try {
156
+ const envContent = await fs.readFile(envPath, 'utf8');
157
+ this._parseEnvContent(envContent);
158
+ } catch {
159
+ // .env file not found or unreadable, that's fine
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Walk up from startDir looking for a filename.
165
+ * @param {string} filename
166
+ * @param {string} [startDir]
167
+ * @returns {string|null}
168
+ */
169
+ _walkUpFind(filename, startDir) {
170
+ let dir = startDir || process.cwd();
171
+ while (true) {
172
+ const candidate = path.join(dir, filename);
173
+ if (existsSync(candidate)) return candidate;
174
+ const parent = path.dirname(dir);
175
+ if (parent === dir) break;
176
+ dir = parent;
177
+ }
178
+ return null;
179
+ }
180
+
109
181
  _parseEnvContent(content) {
110
182
  const lines = content.split('\n');
111
183
  for (const line of lines) {