molex-ftp-client 2.0.0 → 2.2.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
@@ -1,28 +1,22 @@
1
1
  # molex-ftp-client
2
2
 
3
- Lightweight FTP client built with native Node.js TCP sockets (net module).
3
+ [![npm version](https://img.shields.io/npm/v/molex-ftp-client.svg)](https://www.npmjs.com/package/molex-ftp-client)
4
+ [![npm downloads](https://img.shields.io/npm/dm/molex-ftp-client.svg)](https://www.npmjs.com/package/molex-ftp-client)
5
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
6
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D14-brightgreen.svg)](https://nodejs.org)
7
+ [![Dependencies](https://img.shields.io/badge/dependencies-0-success.svg)](package.json)
8
+
9
+ > **Lightweight FTP client built with native Node.js TCP sockets. Zero dependencies, optimized for performance.**
4
10
 
5
11
  ## Features
6
12
 
7
- - **Zero dependencies** - Uses only native Node.js modules
8
- - **Promise-based API** - Modern async/await support
9
- - **Passive mode** (PASV) for data transfers
10
- - **Debug logging** - Optional verbose logging for troubleshooting
11
- - **Connection keep-alive** - Automatic TCP keep-alive
12
- - **Configurable timeouts** - Prevent hanging connections
13
- - **Event-based** - Listen to FTP responses and events
14
- - ✅ **Upload/download** files with Buffer support
15
- - ✅ **Smart directory management** - Auto-create nested directories
16
- - ✅ **Directory operations** (list, cd, mkdir, pwd, ensureDir)
17
- - ✅ **File operations** (delete, rename, size, exists, modifiedTime)
18
- - ✅ **Robust exists()** - Properly detects both files and directories
19
- - ✅ **Connection validation** - Automatic state checking before operations
20
- - ✅ **Enhanced error messages** - Include context for better debugging
21
- - ✅ **High-performance TCP** - Optimized socket settings (TCP_NODELAY, buffer tuning)
22
- - ✅ **Performance presets** - LOW_LATENCY, HIGH_THROUGHPUT, BALANCED modes
23
- - ✅ **Streaming downloads** - Memory-efficient for large files
24
- - ✅ **Connection statistics** - Track command count and status
25
- - ✅ **Clean architecture** - Modular structure with separation of concerns
13
+ - **Zero dependencies** - Uses only native Node.js modules
14
+ - **Promise-based API** - Modern async/await support
15
+ - **TCP optimizations** - TCP_NODELAY and keep-alive applied by default (~2.5 MB/s transfer speeds)
16
+ - **Auto-create directories** - Upload files to nested paths automatically
17
+ - **Streaming support** - Memory-efficient downloads for large files
18
+ - **Full FTP support** - Upload, download, list, delete, rename, chmod, stat, and more
19
+ - **Debug mode** - See all FTP commands and responses in real-time
26
20
 
27
21
  ## Installation
28
22
 
@@ -35,384 +29,206 @@ npm install molex-ftp-client
35
29
  ```javascript
36
30
  const FTPClient = require('molex-ftp-client');
37
31
 
38
- const client = new FTPClient({
39
- debug: true, // Enable debug logging (default: false)
40
- timeout: 30000, // Command timeout in ms (default: 30000)
41
- keepAlive: true // Enable TCP keep-alive (default: true)
32
+ const client = new FTPClient();
33
+
34
+ await client.connect({
35
+ host: 'ftp.example.com',
36
+ user: 'username',
37
+ password: 'password'
42
38
  });
43
39
 
44
- try {
45
- // Connect to FTP server
46
- await client.connect({
47
- host: 'ftp.example.com',
48
- port: 21,
49
- user: 'username',
50
- password: 'password'
51
- });
52
-
53
- // Upload file
54
- await client.upload('Hello World!', '/remote/path/file.txt');
55
-
56
- // Download file
57
- const data = await client.download('/remote/path/file.txt');
58
- console.log(data.toString());
59
-
60
- // Close connection
61
- await client.close();
62
- } catch (err) {
63
- console.error('FTP Error:', err);
64
- }
40
+ // Upload with auto-directory creation
41
+ await client.upload('Hello World!', '/path/to/file.txt', true);
42
+
43
+ // Download
44
+ const data = await client.download('/path/to/file.txt');
45
+ console.log(data.toString());
46
+
47
+ await client.close();
65
48
  ```
66
49
 
67
50
  ## Constructor Options
68
51
 
69
52
  ```javascript
70
53
  const client = new FTPClient({
71
- debug: false, // Enable debug logging
72
- timeout: 30000, // Command timeout in milliseconds
73
- keepAlive: true, // Enable TCP keep-alive
74
- logger: console.log, // Custom logger function
75
- performancePreset: 'BALANCED', // Performance preset: LOW_LATENCY, HIGH_THROUGHPUT, BALANCED
76
- performance: { // Custom performance tuning (overrides preset)
77
- noDelay: true, // TCP_NODELAY - disable Nagle's algorithm
78
- sendBufferSize: 65536, // SO_SNDBUF in bytes
79
- receiveBufferSize: 65536, // SO_RCVBUF in bytes
80
- keepAlive: true, // TCP keep-alive
81
- keepAliveDelay: 10000 // Keep-alive delay in ms
82
- }
54
+ debug: false, // Enable debug logging
55
+ timeout: 30000, // Command timeout in milliseconds (default: 30000)
56
+ logger: console.log // Custom logger function
83
57
  });
84
58
  ```
85
59
 
86
- ### Performance Presets
87
-
88
- Choose a preset based on your use case:
89
-
90
- - **`LOW_LATENCY`** - Optimized for small files and interactive operations (fastest response)
91
- - **`HIGH_THROUGHPUT`** - Optimized for large file transfers (maximum bandwidth)
92
- - **`BALANCED`** - Good default for most use cases (recommended)
93
-
94
- ```javascript
95
- // For small files like privileges.xml
96
- const client = new FTPClient({ performancePreset: 'LOW_LATENCY' });
97
-
98
- // For large backups or video files
99
- const client = new FTPClient({ performancePreset: 'HIGH_THROUGHPUT' });
100
- ```
101
-
102
- ## API
60
+ ## API Reference
103
61
 
104
- ### Connection
62
+ ### Connection Methods
105
63
 
106
64
  #### `connect(options)`
107
-
108
- Connect to FTP server.
109
-
110
65
  ```javascript
111
66
  await client.connect({
112
- host: 'ftp.example.com', // Required
113
- port: 21, // Default: 21
114
- user: 'username', // Default: 'anonymous'
115
- password: 'password' // Default: 'anonymous@'
67
+ host: 'ftp.example.com', // Required
68
+ port: 21, // Default: 21
69
+ user: 'username', // Default: 'anonymous'
70
+ password: 'password' // Default: 'anonymous@'
116
71
  });
117
72
  ```
118
73
 
119
- Returns: `Promise<void>`
120
-
121
- #### `close()` / `disconnect()`
122
-
123
- Close connection to FTP server.
124
-
74
+ #### `close()`
125
75
  ```javascript
126
76
  await client.close();
127
77
  ```
128
78
 
129
- Returns: `Promise<void>`
130
-
131
- ### File Operations
79
+ ### File Methods
132
80
 
133
81
  #### `upload(data, remotePath, ensureDir)`
134
-
135
- Upload file to server.
136
-
137
82
  ```javascript
138
- // Upload string
139
- await client.upload('Hello World!', '/path/file.txt');
140
-
141
- // Upload Buffer
142
- const buffer = Buffer.from('data');
143
- await client.upload(buffer, '/path/file.bin');
144
-
145
- // Upload with automatic directory creation (recommended for nested paths)
146
- await client.upload('data', '/deep/nested/path/file.txt', true);
83
+ await client.upload('content', '/path/file.txt'); // Basic upload
84
+ await client.upload(buffer, '/path/file.bin'); // Upload Buffer
85
+ await client.upload('content', '/deep/path/file.txt', true); // Auto-create dirs
147
86
  ```
148
87
 
149
- **Parameters:**
150
- - `data` (string|Buffer): File content
151
- - `remotePath` (string): Remote file path
152
- - `ensureDir` (boolean): Create parent directories if needed (default: false)
153
-
154
- Returns: `Promise<void>`
155
-
156
- #### `download(remotePath)`
157
-
158
- Download file from server.
159
-
88
+ #### `download(remotePath)` → `Buffer`
160
89
  ```javascript
161
90
  const data = await client.download('/path/file.txt');
162
- console.log(data.toString()); // Convert Buffer to string
163
91
  ```
164
92
 
165
- Returns: `Promise<Buffer>`
166
-
167
- #### `downloadStream(remotePath, writeStream)`
168
-
169
- Download file as a stream (memory efficient for large files).
170
-
93
+ #### `downloadStream(remotePath, writeStream)` → `number`
94
+ Stream download directly to a writable stream (for saving to disk or processing chunks).
171
95
  ```javascript
172
96
  const fs = require('fs');
173
-
174
- // Stream large file directly to disk
175
- const writeStream = fs.createWriteStream('./local-file.bin');
176
- const bytesTransferred = await client.downloadStream('/large-file.bin', writeStream);
177
- console.log(`Downloaded ${bytesTransferred} bytes`);
178
-
179
- // Stream to any writable stream
180
- const { PassThrough } = require('stream');
181
- const stream = new PassThrough();
182
- stream.on('data', (chunk) => {
183
- console.log(`Received ${chunk.length} bytes`);
184
- });
185
- await client.downloadStream('/file.txt', stream);
97
+ const fileStream = fs.createWriteStream('./local-file.bin');
98
+ const bytes = await client.downloadStream('/remote.bin', fileStream);
99
+ console.log(`Saved ${bytes} bytes to disk`);
186
100
  ```
187
101
 
188
- **Use this for:** Large files where you don't want to buffer everything in memory.
189
-
190
- Returns: `Promise<number>` - Total bytes transferred
191
-
192
102
  #### `delete(path)`
193
-
194
- Delete file.
195
-
196
103
  ```javascript
197
104
  await client.delete('/path/file.txt');
198
105
  ```
199
106
 
200
- Returns: `Promise<void>`
107
+ #### `removeDir(path, recursive)`
108
+ Remove directory, optionally with all contents.
109
+ ```javascript
110
+ await client.removeDir('/path/emptydir'); // Remove empty directory
111
+ await client.removeDir('/path/dir', true); // Delete recursively with contents
112
+ ```
201
113
 
202
114
  #### `rename(from, to)`
203
-
204
- Rename or move file.
205
-
206
115
  ```javascript
207
- await client.rename('/old/path.txt', '/new/path.txt');
116
+ await client.rename('/old.txt', '/new.txt');
208
117
  ```
209
118
 
210
- Returns: `Promise<void>`
211
-
212
- #### `size(path)`
213
-
214
- Get file size in bytes.
215
-
119
+ #### `exists(path)` → `boolean`
216
120
  ```javascript
217
- const bytes = await client.size('/path/file.txt');
218
- console.log(`File size: ${bytes} bytes`);
121
+ const exists = await client.exists('/path/file.txt');
219
122
  ```
220
123
 
221
- Returns: `Promise<number>`
222
-
223
- #### `exists(path)`
224
-
225
- Check if file or directory exists.
226
-
124
+ #### `stat(path)` → `Object`
125
+ Get detailed file/directory information.
227
126
  ```javascript
228
- const fileExists = await client.exists('/path/file.txt');
229
- console.log(fileExists ? 'File exists' : 'File not found');
230
-
231
- const dirExists = await client.exists('/path/to/directory');
232
- console.log(dirExists ? 'Directory exists' : 'Directory not found');
127
+ const info = await client.stat('/path/file.txt');
128
+ // { exists: true, size: 1024, isFile: true, isDirectory: false }
233
129
  ```
234
130
 
235
- Returns: `Promise<boolean>`
236
-
237
- #### `modifiedTime(path)`
238
-
239
- Get file modification time.
131
+ #### `size(path)` → `number`
132
+ ```javascript
133
+ const bytes = await client.size('/path/file.txt');
134
+ ```
240
135
 
136
+ #### `modifiedTime(path)` → `Date`
241
137
  ```javascript
242
138
  const date = await client.modifiedTime('/path/file.txt');
243
- console.log(`Last modified: ${date.toISOString()}`);
244
139
  ```
245
140
 
246
- Returns: `Promise<Date>`
247
-
248
- ### Directory Operations
249
-
250
- #### `list(path)`
251
-
252
- List directory contents.
253
-
141
+ #### `chmod(path, mode)`
142
+ Change file permissions (Unix/Linux servers only).
254
143
  ```javascript
255
- const listing = await client.list('/remote/path');
256
- console.log(listing);
144
+ await client.chmod('/path/file.txt', '755'); // String format
145
+ await client.chmod('/path/script.sh', 0755); // Octal format
257
146
  ```
258
147
 
259
- Returns: `Promise<string>` - Raw directory listing
260
-
261
- #### `cd(path)`
262
-
263
- Change working directory.
148
+ ### Directory Methods
264
149
 
150
+ #### `list(path)` → `string`
151
+ Raw directory listing.
265
152
  ```javascript
266
- await client.cd('/remote/path');
153
+ const listing = await client.list('/path');
267
154
  ```
268
155
 
269
- Returns: `Promise<void>`
270
-
271
- #### `pwd()`
272
-
273
- Get current working directory.
274
-
156
+ #### `listDetailed(path)` → `Array`
157
+ Parsed directory listing with permissions, owner, size, etc.
275
158
  ```javascript
276
- const dir = await client.pwd();
277
- console.log(`Current directory: ${dir}`);
159
+ const files = await client.listDetailed('/path');
160
+ // [
161
+ // { name: 'file.txt', type: 'file', permissions: '-rw-r--r--',
162
+ // owner: 'user', group: 'group', size: 1024, date: 'Jan 15 10:30' },
163
+ // { name: 'subdir', type: 'directory', permissions: 'drwxr-xr-x', ... }
164
+ // ]
278
165
  ```
279
166
 
280
- Returns: `Promise<string>`
281
-
282
167
  #### `mkdir(path)`
283
-
284
- Create directory.
285
-
286
168
  ```javascript
287
- await client.mkdir('/remote/newdir');
169
+ await client.mkdir('/path/newdir');
288
170
  ```
289
171
 
290
- Returns: `Promise<void>`
291
-
292
- #### `ensureDir(dirPath, recursive, isFilePath)`
293
-
294
- Ensure directory exists, creating it (and parent directories) if necessary. Idempotent - safe to call multiple times.
295
-
172
+ #### `cd(path)`
296
173
  ```javascript
297
- // Create nested directories recursively (default)
298
- await client.ensureDir('/deep/nested/path');
299
-
300
- // Create single directory only (will fail if parent doesn't exist)
301
- await client.ensureDir('/newdir', false);
302
-
303
- // Ensure parent directory for a file path
304
- await client.ensureDir('/path/to/file.txt', true, true);
305
-
306
- // Idempotent - no error if directory already exists
307
- await client.ensureDir('/existing/path'); // No error
174
+ await client.cd('/path/to/directory');
308
175
  ```
309
176
 
310
- **Parameters:**
311
- - `dirPath` (string): Directory path or file path to ensure exists
312
- - `recursive` (boolean): Create parent directories if needed (default: true)
313
- - `isFilePath` (boolean): If true, ensures parent directory of file path (default: false)
314
-
315
- **Use case:** Preparing directory structure before multiple file uploads. For single file uploads, use `upload()` with `ensureDir=true`.
316
-
317
- Returns: `Promise<void>`
318
-
319
- ### Utilities
320
-
321
- #### `isConnected()`
322
-
323
- Check if client is connected and authenticated.
324
-
177
+ #### `pwd()` → `string`
325
178
  ```javascript
326
- if (client.isConnected()) {
327
- console.log('Ready to execute commands');
328
- } else {
329
- console.log('Not connected');
330
- }
179
+ const currentDir = await client.pwd();
331
180
  ```
332
181
 
333
- Returns: `boolean`
334
-
335
- #### `getStats()`
182
+ #### `ensureDir(dirPath, recursive, isFilePath)`
183
+ Create directory if it doesn't exist, optionally creating parent directories.
184
+ ```javascript
185
+ await client.ensureDir('/deep/nested/path'); // Create full path
186
+ await client.ensureDir('/path/file.txt', true, true); // Ensure parent dir for file
187
+ ```
336
188
 
337
- Get connection statistics.
189
+ ### Utility Methods
338
190
 
191
+ #### `getState()` → `Object`
192
+ Get current client state for debugging.
339
193
  ```javascript
340
- const stats = client.getStats();
341
- console.log(stats);
194
+ const state = client.getState();
342
195
  // {
343
196
  // connected: true,
344
197
  // authenticated: true,
345
- // commandCount: 5,
346
- // lastCommand: 'LIST .'
198
+ // host: 'ftp.example.com',
199
+ // ...
347
200
  // }
348
201
  ```
349
202
 
350
- Returns: `Object`
351
-
352
203
  #### `setDebug(enabled)`
353
-
354
- Enable or disable debug mode at runtime.
355
-
204
+ Toggle debug mode at runtime.
356
205
  ```javascript
357
- client.setDebug(true); // Enable debug logging
358
- client.setDebug(false); // Disable debug logging
206
+ client.setDebug(true);
359
207
  ```
360
208
 
361
- ## Events
362
-
363
- The client extends EventEmitter and emits the following events:
364
-
365
- ### `connected`
366
-
367
- Fired when TCP connection is established.
368
-
209
+ #### `site(command)` → `Object`
210
+ Execute server-specific SITE commands.
369
211
  ```javascript
370
- client.on('connected', () => {
371
- console.log('Connected to FTP server');
372
- });
212
+ await client.site('CHMOD 755 /path/file.txt'); // Alternative chmod
213
+ const response = await client.site('HELP'); // Get server help
373
214
  ```
374
215
 
375
- ### `response`
376
-
377
- Fired for each FTP response (useful for debugging).
378
-
379
- ```javascript
380
- client.on('response', (line) => {
381
- console.log('FTP:', line);
382
- });
383
- ```
384
-
385
- ### `error`
386
-
387
- Fired on connection errors.
216
+ ## Events
388
217
 
389
218
  ```javascript
390
- client.on('error', (err) => {
391
- console.error('FTP Error:', err);
392
- });
219
+ client.on('connected', () => console.log('TCP connection established'));
220
+ client.on('response', (line) => console.log('FTP:', line));
221
+ client.on('error', (err) => console.error('Error:', err));
222
+ client.on('close', () => console.log('Connection closed'));
393
223
  ```
394
224
 
395
- ### `close`
225
+ ## Debugging
396
226
 
397
- Fired when connection is closed.
398
-
399
- ```javascript
400
- client.on('close', () => {
401
- console.log('Connection closed');
402
- });
403
- ```
404
-
405
- ## Debug Mode
406
-
407
- Enable debug logging to troubleshoot FTP issues:
227
+ Enable debug mode to see all FTP commands and responses:
408
228
 
409
229
  ```javascript
410
230
  const client = new FTPClient({ debug: true });
411
231
 
412
- client.on('response', (line) => {
413
- console.log('FTP Response:', line);
414
- });
415
-
416
232
  await client.connect({ host: 'ftp.example.com', user: 'user', password: 'pass' });
417
233
  // [FTP Debug] Connecting to ftp.example.com:21 as user
418
234
  // [FTP Debug] TCP connection established
@@ -421,147 +237,72 @@ await client.connect({ host: 'ftp.example.com', user: 'user', password: 'pass' }
421
237
  // [FTP Debug] <<< 331 Password required
422
238
  // [FTP Debug] >>> PASS ********
423
239
  // [FTP Debug] <<< 230 Login successful
424
- // [FTP Debug] Authentication successful
425
240
  ```
426
241
 
427
- ## Performance Optimization
428
-
429
- This library implements TCP-level optimizations:
430
-
431
- ### TCP_NODELAY (Nagle's Algorithm)
432
-
433
- By default, the client uses `TCP_NODELAY` which disables Nagle's algorithm. This reduces latency by sending packets immediately instead of buffering them.
434
-
435
- **When to use:**
436
- - Small file transfers
437
- - Interactive operations
438
- - When latency matters more than bandwidth
439
-
440
- **Trade-off:** Slightly more network overhead, but much faster response times.
242
+ ## Performance
441
243
 
442
- ### Buffer Sizing
244
+ TCP optimizations are automatically applied:
245
+ - **TCP_NODELAY** - Disables Nagle's algorithm for lower latency
246
+ - **Keep-alive** - Detects dead connections (10s interval)
443
247
 
444
- The client optimizes socket buffers based on your use case:
248
+ **Typical transfer speeds:** ~2.5 MB/s for 1MB files over standard internet connections.
445
249
 
446
- ```javascript
447
- // Low latency (32KB buffers) - fast for small files
448
- const client = new FTPClient({ performancePreset: 'LOW_LATENCY' });
449
-
450
- // High throughput (128KB buffers) - better for large files
451
- const client = new FTPClient({ performancePreset: 'HIGH_THROUGHPUT' });
452
- ```
453
-
454
- ### Streaming for Large Files
455
-
456
- Use `downloadStream()` for memory-efficient transfers:
250
+ For large files, use `downloadStream()` to save directly to disk without buffering in memory:
457
251
 
458
252
  ```javascript
459
253
  const fs = require('fs');
460
- const writeStream = fs.createWriteStream('./large-backup.zip');
461
- await client.downloadStream('/backup.zip', writeStream);
254
+ const fileStream = fs.createWriteStream('./large-backup.zip');
255
+ const bytes = await client.downloadStream('/backup.zip', fileStream);
256
+ console.log(`Saved ${bytes} bytes to disk`);
462
257
  ```
463
258
 
464
- **Benefits:**
465
- - Constant memory usage (no buffering entire file)
466
- - Start processing data before download completes
467
- - Better for files > 10MB
468
-
469
- ### Performance Comparison
470
-
471
- For a typical privileges.xml (< 100KB):
472
- - **Without optimization:** ~150-200ms
473
- - **With LOW_LATENCY preset:** ~80-120ms
474
- - **40-50% faster** response time
475
-
476
259
  ## Error Handling
477
260
 
478
- All methods return promises and will reject on errors:
479
-
480
261
  ```javascript
481
262
  try {
482
263
  await client.upload('data', '/readonly/file.txt');
483
264
  } catch (err) {
484
265
  if (err.message.includes('FTP Error 550')) {
485
266
  console.error('Permission denied');
486
- } else {
487
- console.error('Upload failed:', err.message);
488
267
  }
489
268
  }
490
269
  ```
491
270
 
492
- ## Complete Example
271
+ ## Example
493
272
 
494
273
  ```javascript
495
274
  const FTPClient = require('molex-ftp-client');
496
275
 
497
- async function backupFile() {
498
- const client = new FTPClient({
499
- debug: true,
500
- timeout: 60000
501
- });
276
+ async function main() {
277
+ const client = new FTPClient({ debug: true });
502
278
 
503
279
  try {
504
- // Connect
505
280
  await client.connect({
506
- host: 'ftp.myserver.com',
507
- port: 21,
281
+ host: 'ftp.example.com',
508
282
  user: 'admin',
509
- password: 'secret123'
283
+ password: 'secret'
510
284
  });
511
285
 
512
- console.log('Current directory:', await client.pwd());
513
-
514
- // Check if file exists
515
- const exists = await client.exists('/backup/data.json');
516
- if (exists) {
517
- // Download existing file
518
- const oldData = await client.download('/backup/data.json');
519
- console.log('Old backup size:', oldData.length, 'bytes');
520
-
521
- // Get modification time
522
- const modTime = await client.modifiedTime('/backup/data.json');
523
- console.log('Last modified:', modTime.toISOString());
524
-
525
- // Rename old backup
526
- await client.rename('/backup/data.json', '/backup/data.old.json');
286
+ // Check file info
287
+ const info = await client.stat('/backup/data.json');
288
+ if (info.exists) {
289
+ console.log(`File size: ${info.size} bytes`);
290
+ const data = await client.download('/backup/data.json');
291
+ console.log('Downloaded:', data.toString());
527
292
  }
528
293
 
529
- // Upload new backup (with automatic directory creation)
530
- const newData = JSON.stringify({ timestamp: Date.now(), data: [1, 2, 3] });
531
- await client.upload(newData, '/backup/data.json', true);
532
- console.log('Backup uploaded successfully');
533
-
534
- // Verify
535
- const size = await client.size('/backup/data.json');
536
- console.log('New backup size:', size, 'bytes');
537
-
538
- // Get stats
539
- const stats = client.getStats();
540
- console.log('Commands executed:', stats.commandCount);
541
-
542
- // Close connection
294
+ // Upload new file
295
+ await client.upload('new data', '/backup/updated.json', true);
296
+
543
297
  await client.close();
544
298
  } catch (err) {
545
- console.error('Backup failed:', err.message);
546
- await client.close();
299
+ console.error('FTP Error:', err.message);
547
300
  }
548
301
  }
549
302
 
550
- backupFile();
303
+ main();
551
304
  ```
552
305
 
553
- ## Architecture
554
-
555
- ```
556
- molex-ftp-client/
557
- ├── index.js # Entry point - exports FTPClient
558
- ├── lib/
559
- │ ├── FTPClient.js # Main class definition & public API
560
- │ ├── connection.js # Connection & authentication logic
561
- │ ├── commands.js # FTP command implementations
562
- │ └── utils.js # Helper functions (parsing, paths)
563
- ```
564
306
  ## License
565
307
 
566
- ISC © Tony Wiedman / MolexWorks
567
-
308
+ ISC License