jtcsv 2.1.3 → 2.2.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/LICENSE +1 -1
- package/README.md +60 -341
- package/bin/jtcsv.js +2462 -1372
- package/csv-to-json.js +35 -26
- package/dist/jtcsv.cjs.js +807 -133
- package/dist/jtcsv.cjs.js.map +1 -1
- package/dist/jtcsv.esm.js +800 -134
- package/dist/jtcsv.esm.js.map +1 -1
- package/dist/jtcsv.umd.js +807 -133
- package/dist/jtcsv.umd.js.map +1 -1
- package/errors.js +20 -0
- package/examples/browser-vanilla.html +37 -0
- package/examples/cli-batch-processing.js +38 -0
- package/examples/error-handling.js +324 -0
- package/examples/ndjson-processing.js +434 -0
- package/examples/react-integration.jsx +637 -0
- package/examples/schema-validation.js +640 -0
- package/examples/simple-usage.js +10 -7
- package/examples/typescript-example.ts +486 -0
- package/examples/web-workers-advanced.js +28 -0
- package/index.d.ts +2 -0
- package/json-save.js +2 -1
- package/json-to-csv.js +171 -131
- package/package.json +20 -4
- package/plugins/README.md +41 -467
- package/plugins/express-middleware/README.md +32 -274
- package/plugins/hono/README.md +16 -13
- package/plugins/nestjs/README.md +13 -11
- package/plugins/nextjs-api/README.md +28 -423
- package/plugins/nextjs-api/index.js +1 -2
- package/plugins/nextjs-api/route.js +1 -2
- package/plugins/nuxt/README.md +6 -7
- package/plugins/remix/README.md +9 -9
- package/plugins/sveltekit/README.md +8 -8
- package/plugins/trpc/README.md +8 -5
- package/src/browser/browser-functions.js +33 -3
- package/src/browser/csv-to-json-browser.js +269 -11
- package/src/browser/errors-browser.js +19 -1
- package/src/browser/index.js +39 -5
- package/src/browser/streams.js +393 -0
- package/src/browser/workers/csv-parser.worker.js +20 -2
- package/src/browser/workers/worker-pool.js +507 -447
- package/src/core/plugin-system.js +4 -0
- package/src/engines/fast-path-engine.js +31 -23
- package/src/errors.js +26 -0
- package/src/formats/ndjson-parser.js +54 -5
- package/src/formats/tsv-parser.js +4 -1
- package/src/utils/schema-validator.js +594 -0
- package/src/utils/transform-loader.js +205 -0
- package/src/web-server/index.js +683 -0
- package/stream-csv-to-json.js +16 -87
- package/stream-json-to-csv.js +18 -86
package/bin/jtcsv.js
CHANGED
|
@@ -1,1372 +1,2462 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* jtcsv CLI - Complete Command Line Interface
|
|
5
|
-
*
|
|
6
|
-
* Full-featured command-line interface for JSON↔CSV conversion
|
|
7
|
-
* with streaming, batch processing, and all security features.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const fs = require(
|
|
11
|
-
const path = require(
|
|
12
|
-
const { pipeline } = require(
|
|
13
|
-
const jtcsv = require(
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
${color(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
${color(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
${color(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
${color(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
${color(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
${color(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
${color(
|
|
91
|
-
${color(
|
|
92
|
-
${color(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
${color(
|
|
96
|
-
${color(
|
|
97
|
-
${color(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
${color(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
${color(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
${color(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
${color(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
${color(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
${color(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
${color(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
${color(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
${color(
|
|
132
|
-
|
|
133
|
-
${color(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
${color(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
${color(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
//
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
if (
|
|
806
|
-
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
options.
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
//
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
//
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
//
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* jtcsv CLI - Complete Command Line Interface
|
|
5
|
+
*
|
|
6
|
+
* Full-featured command-line interface for JSON↔CSV conversion
|
|
7
|
+
* with streaming, batch processing, and all security features.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
const path = require("path");
|
|
12
|
+
const { pipeline } = require("stream/promises");
|
|
13
|
+
const jtcsv = require("../index.js");
|
|
14
|
+
const transformLoader = require("../src/utils/transform-loader");
|
|
15
|
+
const schemaValidator = require("../src/utils/schema-validator");
|
|
16
|
+
|
|
17
|
+
const VERSION = require("../package.json").version;
|
|
18
|
+
|
|
19
|
+
// Function to apply transform from JavaScript file
|
|
20
|
+
async function applyTransform(data, transformFile) {
|
|
21
|
+
try {
|
|
22
|
+
const transformPath = path.resolve(process.cwd(), transformFile);
|
|
23
|
+
const transformModule = require(transformPath);
|
|
24
|
+
|
|
25
|
+
// Check if module exports a function
|
|
26
|
+
if (typeof transformModule === "function") {
|
|
27
|
+
return transformModule(data);
|
|
28
|
+
} else if (typeof transformModule.default === "function") {
|
|
29
|
+
return transformModule.default(data);
|
|
30
|
+
} else if (typeof transformModule.transform === "function") {
|
|
31
|
+
return transformModule.transform(data);
|
|
32
|
+
} else {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Transform file must export a function. Found: ${typeof transformModule}`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
} catch (error) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Failed to apply transform from ${transformFile}: ${error.message}`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ANSI colors for terminal output
|
|
45
|
+
const colors = {
|
|
46
|
+
reset: "\x1b[0m",
|
|
47
|
+
bright: "\x1b[1m",
|
|
48
|
+
dim: "\x1b[2m",
|
|
49
|
+
red: "\x1b[31m",
|
|
50
|
+
green: "\x1b[32m",
|
|
51
|
+
yellow: "\x1b[33m",
|
|
52
|
+
blue: "\x1b[34m",
|
|
53
|
+
magenta: "\x1b[35m",
|
|
54
|
+
cyan: "\x1b[36m",
|
|
55
|
+
white: "\x1b[37m",
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function color(text, colorName) {
|
|
59
|
+
return colors[colorName] + text + colors.reset;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function showHelp() {
|
|
63
|
+
console.log(`
|
|
64
|
+
${color("jtcsv CLI v" + VERSION, "cyan")}
|
|
65
|
+
${color("The Complete JSON↔CSV Converter for Node.js", "dim")}
|
|
66
|
+
|
|
67
|
+
${color("USAGE:", "bright")}
|
|
68
|
+
jtcsv [command] [options] [file...]
|
|
69
|
+
|
|
70
|
+
${color("MAIN COMMANDS:", "bright")}
|
|
71
|
+
${color("json-to-csv", "green")} Convert JSON to CSV (alias: json2csv)
|
|
72
|
+
${color("csv-to-json", "green")} Convert CSV to JSON (alias: csv2json)
|
|
73
|
+
${color("ndjson-to-csv", "green")} Convert NDJSON to CSV
|
|
74
|
+
${color("csv-to-ndjson", "green")} Convert CSV to NDJSON
|
|
75
|
+
${color("ndjson-to-json", "green")} Convert NDJSON to JSON array
|
|
76
|
+
${color("json-to-ndjson", "green")} Convert JSON array to NDJSON
|
|
77
|
+
${color("save-json", "yellow")} Save data as JSON file
|
|
78
|
+
${color("save-csv", "yellow")} Save data as CSV file
|
|
79
|
+
${color("stream", "yellow")} Streaming conversion for large files
|
|
80
|
+
${color("batch", "yellow")} Batch process multiple files
|
|
81
|
+
${color("preprocess", "magenta")} Preprocess JSON with deep unwrapping
|
|
82
|
+
${color("unwrap", "magenta")} Flatten nested JSON structures (alias: flatten)
|
|
83
|
+
${color("tui", "magenta")} Launch Terminal User Interface (@jtcsv/tui)
|
|
84
|
+
${color("web", "magenta")} Launch Web Interface (http://localhost:3000)
|
|
85
|
+
${color("help", "blue")} Show this help message
|
|
86
|
+
${color("version", "blue")} Show version information
|
|
87
|
+
|
|
88
|
+
${color("STREAMING SUBCOMMANDS:", "bright")}
|
|
89
|
+
${color("stream json-to-csv", "dim")} Stream JSON to CSV
|
|
90
|
+
${color("stream csv-to-json", "dim")} Stream CSV to JSON
|
|
91
|
+
${color("stream file-to-csv", "dim")} Stream file to CSV
|
|
92
|
+
${color("stream file-to-json", "dim")} Stream file to JSON
|
|
93
|
+
|
|
94
|
+
${color("BATCH SUBCOMMANDS:", "bright")}
|
|
95
|
+
${color("batch json-to-csv", "dim")} Batch convert JSON files to CSV
|
|
96
|
+
${color("batch csv-to-json", "dim")} Batch convert CSV files to JSON
|
|
97
|
+
${color("batch process", "dim")} Process mixed file types
|
|
98
|
+
|
|
99
|
+
${color("EXAMPLES:", "bright")}
|
|
100
|
+
${color("Convert JSON file to CSV:", "dim")}
|
|
101
|
+
jtcsv json-to-csv input.json output.csv --delimiter=,
|
|
102
|
+
|
|
103
|
+
${color("Convert CSV file to JSON:", "dim")}
|
|
104
|
+
jtcsv csv-to-json input.csv output.json --parse-numbers --auto-detect
|
|
105
|
+
|
|
106
|
+
${color("Save data as JSON file:", "dim")}
|
|
107
|
+
jtcsv save-json data.json output.json --pretty
|
|
108
|
+
|
|
109
|
+
${color("Save data as CSV file:", "dim")}
|
|
110
|
+
jtcsv save-csv data.csv output.csv --delimiter=, --transform=transform.js
|
|
111
|
+
|
|
112
|
+
${color("Stream large JSON file to CSV:", "dim")}
|
|
113
|
+
jtcsv stream json-to-csv large.json output.csv --max-records=1000000
|
|
114
|
+
|
|
115
|
+
${color("Stream CSV file to JSON:", "dim")}
|
|
116
|
+
jtcsv stream csv-to-json large.csv output.json --max-rows=500000
|
|
117
|
+
|
|
118
|
+
${color("Preprocess complex JSON:", "dim")}
|
|
119
|
+
jtcsv preprocess complex.json simplified.json --max-depth=3
|
|
120
|
+
|
|
121
|
+
${color("Batch convert JSON files:", "dim")}
|
|
122
|
+
jtcsv batch json-to-csv "data/*.json" "output/" --delimiter=;
|
|
123
|
+
|
|
124
|
+
${color("Launch TUI interface:", "dim")}
|
|
125
|
+
jtcsv tui
|
|
126
|
+
|
|
127
|
+
${color("Launch Web interface:", "dim")}
|
|
128
|
+
jtcsv web --port=3000
|
|
129
|
+
|
|
130
|
+
${color("CONVERSION OPTIONS:", "bright")}
|
|
131
|
+
${color("--delimiter=", "cyan")}CHAR CSV delimiter (default: ;)
|
|
132
|
+
${color("--auto-detect", "cyan")} Auto-detect delimiter (default: true)
|
|
133
|
+
${color("--candidates=", "cyan")}LIST Delimiter candidates (default: ;,\t|)
|
|
134
|
+
${color("--no-headers", "cyan")} Exclude headers from CSV output
|
|
135
|
+
${color("--parse-numbers", "cyan")} Parse numeric values in CSV
|
|
136
|
+
${color("--parse-booleans", "cyan")} Parse boolean values in CSV
|
|
137
|
+
${color("--no-trim", "cyan")} Don't trim whitespace from CSV values
|
|
138
|
+
${color("--no-fast-path", "cyan")} Disable fast-path parser (force quote-aware)
|
|
139
|
+
${color("--fast-path-mode=", "cyan")}MODE Fast path output mode (objects|compact)
|
|
140
|
+
${color("--rename=", "cyan")}JSON Rename columns (JSON map)
|
|
141
|
+
${color("--template=", "cyan")}JSON Column order template (JSON object)
|
|
142
|
+
${color("--no-injection-protection", "cyan")} Disable CSV injection protection
|
|
143
|
+
${color("--no-rfc4180", "cyan")} Disable RFC 4180 compliance
|
|
144
|
+
${color("--max-records=", "cyan")}N Maximum records to process
|
|
145
|
+
${color("--max-rows=", "cyan")}N Maximum rows to process
|
|
146
|
+
${color("--pretty", "cyan")} Pretty print JSON output
|
|
147
|
+
${color("--schema=", "cyan")}JSON JSON schema for validation and formatting
|
|
148
|
+
${color("--transform=", "cyan")}JS Custom transform function (JavaScript file)
|
|
149
|
+
${color("PREPROCESS OPTIONS:", "bright")}
|
|
150
|
+
${color("--max-depth=", "cyan")}N Maximum recursion depth (default: 5)
|
|
151
|
+
${color("--unwrap-arrays", "cyan")} Unwrap arrays to strings
|
|
152
|
+
${color("--stringify-objects", "cyan")} Stringify complex objects
|
|
153
|
+
${color("STREAMING OPTIONS:", "bright")}
|
|
154
|
+
${color("--chunk-size=", "cyan")}N Chunk size in bytes (default: 65536)
|
|
155
|
+
${color("--buffer-size=", "cyan")}N Buffer size in records (default: 1000)
|
|
156
|
+
${color("--add-bom", "cyan")} Add UTF-8 BOM for Excel compatibility
|
|
157
|
+
${color("BATCH OPTIONS:", "bright")}
|
|
158
|
+
${color("--recursive", "cyan")} Process directories recursively
|
|
159
|
+
${color("--pattern=", "cyan")}GLOB File pattern to match
|
|
160
|
+
${color("--output-dir=", "cyan")}DIR Output directory for batch processing
|
|
161
|
+
${color("--overwrite", "cyan")} Overwrite existing files
|
|
162
|
+
${color("--parallel=", "cyan")}N Parallel processing limit (default: 4)
|
|
163
|
+
${color("GENERAL OPTIONS:", "bright")}
|
|
164
|
+
${color("--silent", "cyan")} Suppress all output except errors
|
|
165
|
+
${color("--verbose", "cyan")} Show detailed progress information
|
|
166
|
+
${color("--debug", "cyan")} Show debug information
|
|
167
|
+
${color("--dry-run", "cyan")} Show what would be done without actually doing it
|
|
168
|
+
${color("SECURITY FEATURES:", "bright")}
|
|
169
|
+
• CSV injection protection (enabled by default)
|
|
170
|
+
• Path traversal protection
|
|
171
|
+
• Input validation and sanitization
|
|
172
|
+
• Size limits to prevent DoS attacks
|
|
173
|
+
• Schema validation support
|
|
174
|
+
|
|
175
|
+
${color("PERFORMANCE FEATURES:", "bright")}
|
|
176
|
+
• Streaming for files >100MB
|
|
177
|
+
• Batch processing with parallel execution
|
|
178
|
+
• Memory-efficient preprocessing
|
|
179
|
+
• Configurable buffer sizes
|
|
180
|
+
|
|
181
|
+
${color("LEARN MORE:", "dim")}
|
|
182
|
+
GitHub: https://github.com/Linol-Hamelton/jtcsv
|
|
183
|
+
Issues: https://github.com/Linol-Hamelton/jtcsv/issues
|
|
184
|
+
Documentation: https://github.com/Linol-Hamelton/jtcsv#readme
|
|
185
|
+
`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function showVersion() {
|
|
189
|
+
console.log(`jtcsv v${VERSION}`);
|
|
190
|
+
console.log(`Node.js ${process.version}`);
|
|
191
|
+
console.log(`Platform: ${process.platform} ${process.arch}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ============================================================================
|
|
195
|
+
// CONVERSION FUNCTIONS
|
|
196
|
+
// ============================================================================
|
|
197
|
+
|
|
198
|
+
async function convertJsonToCsv(inputFile, outputFile, options) {
|
|
199
|
+
const startTime = Date.now();
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
// Read input file
|
|
203
|
+
const inputData = await fs.promises.readFile(inputFile, "utf8");
|
|
204
|
+
const jsonData = JSON.parse(inputData);
|
|
205
|
+
|
|
206
|
+
if (!Array.isArray(jsonData)) {
|
|
207
|
+
throw new Error("JSON data must be an array of objects");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!options.silent) {
|
|
211
|
+
console.log(
|
|
212
|
+
color(
|
|
213
|
+
`Converting ${jsonData.length.toLocaleString()} records...`,
|
|
214
|
+
"dim",
|
|
215
|
+
),
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (options.transform) {
|
|
220
|
+
if (!options.silent) {
|
|
221
|
+
console.log(
|
|
222
|
+
color(`Applying transform from: ${options.transform}`, "dim"),
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
transformedData = transformLoader.applyTransform(
|
|
227
|
+
jsonData,
|
|
228
|
+
options.transform,
|
|
229
|
+
);
|
|
230
|
+
if (!options.silent) {
|
|
231
|
+
console.log(
|
|
232
|
+
color(
|
|
233
|
+
`✓ Transform applied to ${transformedData.length} records`,
|
|
234
|
+
"green",
|
|
235
|
+
),
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
} catch (transformError) {
|
|
239
|
+
console.error(
|
|
240
|
+
color(`✗ Transform error: ${transformError.message}`, "red"),
|
|
241
|
+
);
|
|
242
|
+
if (options.debug) {
|
|
243
|
+
console.error(transformError.stack);
|
|
244
|
+
}
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Prepare options for jtcsv
|
|
250
|
+
const jtcsvOptions = {
|
|
251
|
+
delimiter: options.delimiter,
|
|
252
|
+
includeHeaders: options.includeHeaders,
|
|
253
|
+
renameMap: options.renameMap,
|
|
254
|
+
template: options.template,
|
|
255
|
+
maxRecords: options.maxRecords,
|
|
256
|
+
preventCsvInjection: options.preventCsvInjection,
|
|
257
|
+
rfc4180Compliant: options.rfc4180Compliant,
|
|
258
|
+
schema: options.schema, // Add schema option
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// Apply transform function if provided
|
|
262
|
+
let transformedData = jsonData;
|
|
263
|
+
if (options.transform) {
|
|
264
|
+
transformedData = await applyTransform(jsonData, options.transform);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Convert to CSV
|
|
268
|
+
const csvData = jtcsv.jsonToCsv(transformedData, jtcsvOptions);
|
|
269
|
+
|
|
270
|
+
// Write output file
|
|
271
|
+
await fs.promises.writeFile(outputFile, csvData, "utf8");
|
|
272
|
+
|
|
273
|
+
const elapsed = Date.now() - startTime;
|
|
274
|
+
if (!options.silent) {
|
|
275
|
+
console.log(
|
|
276
|
+
color(
|
|
277
|
+
`✓ Converted ${transformedData.length.toLocaleString()} records in ${elapsed}ms`,
|
|
278
|
+
"green",
|
|
279
|
+
),
|
|
280
|
+
);
|
|
281
|
+
console.log(
|
|
282
|
+
color(
|
|
283
|
+
` Output: ${outputFile} (${csvData.length.toLocaleString()} bytes)`,
|
|
284
|
+
"dim",
|
|
285
|
+
),
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
records: transformedData.length,
|
|
291
|
+
bytes: csvData.length,
|
|
292
|
+
time: elapsed,
|
|
293
|
+
};
|
|
294
|
+
} catch (error) {
|
|
295
|
+
console.error(color(`✗ Error: ${error.message}`, "red"));
|
|
296
|
+
if (options.debug) {
|
|
297
|
+
console.error(error.stack);
|
|
298
|
+
}
|
|
299
|
+
process.exit(1);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function convertCsvToJson(inputFile, outputFile, options) {
|
|
304
|
+
const startTime = Date.now();
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
if (!options.silent) {
|
|
308
|
+
console.log(color("Reading CSV file...", "dim"));
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Prepare options for jtcsv
|
|
312
|
+
const jtcsvOptions = {
|
|
313
|
+
delimiter: options.delimiter,
|
|
314
|
+
autoDetect: options.autoDetect,
|
|
315
|
+
candidates: options.candidates,
|
|
316
|
+
hasHeaders: options.hasHeaders,
|
|
317
|
+
renameMap: options.renameMap,
|
|
318
|
+
trim: options.trim,
|
|
319
|
+
parseNumbers: options.parseNumbers,
|
|
320
|
+
parseBooleans: options.parseBooleans,
|
|
321
|
+
maxRows: options.maxRows,
|
|
322
|
+
useFastPath: options.useFastPath,
|
|
323
|
+
fastPathMode: options.fastPathMode,
|
|
324
|
+
schema: options.schema, // Add schema option if supported
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// Read and convert CSV
|
|
328
|
+
const jsonData = await jtcsv.readCsvAsJson(inputFile, jtcsvOptions);
|
|
329
|
+
|
|
330
|
+
// Apply transform if specified
|
|
331
|
+
let transformedData = jsonData;
|
|
332
|
+
if (options.transform) {
|
|
333
|
+
if (!options.silent) {
|
|
334
|
+
console.log(
|
|
335
|
+
color(`Applying transform from: ${options.transform}`, "dim"),
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
transformedData = transformLoader.applyTransform(
|
|
340
|
+
jsonData,
|
|
341
|
+
options.transform,
|
|
342
|
+
);
|
|
343
|
+
if (!options.silent) {
|
|
344
|
+
console.log(
|
|
345
|
+
color(
|
|
346
|
+
`✓ Transform applied to ${transformedData.length} rows`,
|
|
347
|
+
"green",
|
|
348
|
+
),
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
} catch (transformError) {
|
|
352
|
+
console.error(
|
|
353
|
+
color(`✗ Transform error: ${transformError.message}`, "red"),
|
|
354
|
+
);
|
|
355
|
+
if (options.debug) {
|
|
356
|
+
console.error(transformError.stack);
|
|
357
|
+
}
|
|
358
|
+
process.exit(1);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Format JSON
|
|
363
|
+
const jsonOutput = options.pretty
|
|
364
|
+
? JSON.stringify(transformedData, null, 2)
|
|
365
|
+
: JSON.stringify(transformedData);
|
|
366
|
+
|
|
367
|
+
// Write output file
|
|
368
|
+
await fs.promises.writeFile(outputFile, jsonOutput, "utf8");
|
|
369
|
+
|
|
370
|
+
const elapsed = Date.now() - startTime;
|
|
371
|
+
if (!options.silent) {
|
|
372
|
+
console.log(
|
|
373
|
+
color(
|
|
374
|
+
`✓ Converted ${transformedData.length.toLocaleString()} rows in ${elapsed}ms`,
|
|
375
|
+
"green",
|
|
376
|
+
),
|
|
377
|
+
);
|
|
378
|
+
console.log(
|
|
379
|
+
color(
|
|
380
|
+
` Output: ${outputFile} (${jsonOutput.length.toLocaleString()} bytes)`,
|
|
381
|
+
"dim",
|
|
382
|
+
),
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
rows: transformedData.length,
|
|
388
|
+
bytes: jsonOutput.length,
|
|
389
|
+
time: elapsed,
|
|
390
|
+
};
|
|
391
|
+
} catch (error) {
|
|
392
|
+
console.error(color(`✗ Error: ${error.message}`, "red"));
|
|
393
|
+
if (options.debug) {
|
|
394
|
+
console.error(error.stack);
|
|
395
|
+
}
|
|
396
|
+
process.exit(1);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async function saveAsCsv(inputFile, outputFile, options) {
|
|
401
|
+
const startTime = Date.now();
|
|
402
|
+
|
|
403
|
+
try {
|
|
404
|
+
// Read input file
|
|
405
|
+
const inputData = await fs.promises.readFile(inputFile, "utf8");
|
|
406
|
+
|
|
407
|
+
if (!options.silent) {
|
|
408
|
+
console.log(color(`Saving CSV file...`, "dim"));
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Apply transform if specified
|
|
412
|
+
let transformedData = inputData;
|
|
413
|
+
if (options.transform) {
|
|
414
|
+
if (!options.silent) {
|
|
415
|
+
console.log(
|
|
416
|
+
color(`Applying transform from: ${options.transform}`, "dim"),
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
try {
|
|
420
|
+
// Для CSV нужно сначала распарсить, применить трансформацию, затем снова сериализовать
|
|
421
|
+
const parsedData = jtcsv.csvToJson(inputData, {
|
|
422
|
+
delimiter: options.delimiter,
|
|
423
|
+
autoDetect: options.autoDetect,
|
|
424
|
+
hasHeaders: options.hasHeaders,
|
|
425
|
+
trim: options.trim,
|
|
426
|
+
parseNumbers: options.parseNumbers,
|
|
427
|
+
parseBooleans: options.parseBooleans,
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
const transformedJson = transformLoader.applyTransform(
|
|
431
|
+
parsedData,
|
|
432
|
+
options.transform,
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
// Конвертировать обратно в CSV
|
|
436
|
+
transformedData = jtcsv.jsonToCsv(transformedJson, {
|
|
437
|
+
delimiter: options.delimiter,
|
|
438
|
+
includeHeaders: options.includeHeaders,
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
if (!options.silent) {
|
|
442
|
+
console.log(color(`✓ Transform applied`, "green"));
|
|
443
|
+
}
|
|
444
|
+
} catch (transformError) {
|
|
445
|
+
console.error(
|
|
446
|
+
color(`✗ Transform error: ${transformError.message}`, "red"),
|
|
447
|
+
);
|
|
448
|
+
if (options.debug) {
|
|
449
|
+
console.error(transformError.stack);
|
|
450
|
+
}
|
|
451
|
+
process.exit(1);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Write output file
|
|
456
|
+
await fs.promises.writeFile(outputFile, transformedData, "utf8");
|
|
457
|
+
|
|
458
|
+
const elapsed = Date.now() - startTime;
|
|
459
|
+
if (!options.silent) {
|
|
460
|
+
console.log(color(`✓ Saved CSV in ${elapsed}ms`, "green"));
|
|
461
|
+
console.log(color(` Output: ${outputFile}`, "dim"));
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return { time: elapsed };
|
|
465
|
+
} catch (error) {
|
|
466
|
+
console.error(color(`✗ Error: ${error.message}`, "red"));
|
|
467
|
+
if (options.debug) {
|
|
468
|
+
console.error(error.stack);
|
|
469
|
+
}
|
|
470
|
+
process.exit(1);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
async function saveAsJson(inputFile, outputFile, options) {
|
|
475
|
+
const startTime = Date.now();
|
|
476
|
+
|
|
477
|
+
try {
|
|
478
|
+
// Read input file
|
|
479
|
+
const inputData = await fs.promises.readFile(inputFile, "utf8");
|
|
480
|
+
const jsonData = JSON.parse(inputData);
|
|
481
|
+
|
|
482
|
+
if (!options.silent) {
|
|
483
|
+
console.log(
|
|
484
|
+
color(
|
|
485
|
+
`Saving ${Array.isArray(jsonData) ? jsonData.length.toLocaleString() + " records" : "object"}...`,
|
|
486
|
+
"dim",
|
|
487
|
+
),
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Apply transform if specified
|
|
492
|
+
let transformedData = jsonData;
|
|
493
|
+
if (options.transform) {
|
|
494
|
+
if (!options.silent) {
|
|
495
|
+
console.log(
|
|
496
|
+
color(`Applying transform from: ${options.transform}`, "dim"),
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
try {
|
|
500
|
+
transformedData = transformLoader.applyTransform(
|
|
501
|
+
jsonData,
|
|
502
|
+
options.transform,
|
|
503
|
+
);
|
|
504
|
+
if (!options.silent) {
|
|
505
|
+
console.log(color(`✓ Transform applied`, "green"));
|
|
506
|
+
}
|
|
507
|
+
} catch (transformError) {
|
|
508
|
+
console.error(
|
|
509
|
+
color(`✗ Transform error: ${transformError.message}`, "red"),
|
|
510
|
+
);
|
|
511
|
+
if (options.debug) {
|
|
512
|
+
console.error(transformError.stack);
|
|
513
|
+
}
|
|
514
|
+
process.exit(1);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Prepare options for jtcsv
|
|
519
|
+
const jtcsvOptions = {
|
|
520
|
+
prettyPrint: options.pretty,
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
// Save as JSON
|
|
524
|
+
await jtcsv.saveAsJson(transformedData, outputFile, jtcsvOptions);
|
|
525
|
+
|
|
526
|
+
const elapsed = Date.now() - startTime;
|
|
527
|
+
if (!options.silent) {
|
|
528
|
+
console.log(color(`✓ Saved JSON in ${elapsed}ms`, "green"));
|
|
529
|
+
console.log(color(` Output: ${outputFile}`, "dim"));
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return { time: elapsed };
|
|
533
|
+
} catch (error) {
|
|
534
|
+
console.error(color(`✗ Error: ${error.message}`, "red"));
|
|
535
|
+
if (options.debug) {
|
|
536
|
+
console.error(error.stack);
|
|
537
|
+
}
|
|
538
|
+
process.exit(1);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// ============================================================================
|
|
543
|
+
// NDJSON CONVERSION FUNCTIONS
|
|
544
|
+
// ============================================================================
|
|
545
|
+
|
|
546
|
+
async function convertNdjsonToCsv(inputFile, outputFile, options) {
|
|
547
|
+
const startTime = Date.now();
|
|
548
|
+
|
|
549
|
+
try {
|
|
550
|
+
if (!options.silent) {
|
|
551
|
+
console.log(color("Converting NDJSON to CSV...", "dim"));
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Read NDJSON file
|
|
555
|
+
const inputData = await fs.promises.readFile(inputFile, "utf8");
|
|
556
|
+
const jsonData = jtcsv.ndjsonToJson(inputData);
|
|
557
|
+
|
|
558
|
+
if (!options.silent) {
|
|
559
|
+
console.log(
|
|
560
|
+
color(`Parsed ${jsonData.length.toLocaleString()} records from NDJSON`, "dim"),
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Prepare options for jtcsv
|
|
565
|
+
const jtcsvOptions = {
|
|
566
|
+
delimiter: options.delimiter,
|
|
567
|
+
includeHeaders: options.includeHeaders,
|
|
568
|
+
renameMap: options.renameMap,
|
|
569
|
+
template: options.template,
|
|
570
|
+
preventCsvInjection: options.preventCsvInjection,
|
|
571
|
+
rfc4180Compliant: options.rfc4180Compliant,
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
// Convert to CSV
|
|
575
|
+
const csvData = jtcsv.jsonToCsv(jsonData, jtcsvOptions);
|
|
576
|
+
|
|
577
|
+
// Write output file
|
|
578
|
+
await fs.promises.writeFile(outputFile, csvData, "utf8");
|
|
579
|
+
|
|
580
|
+
const elapsed = Date.now() - startTime;
|
|
581
|
+
if (!options.silent) {
|
|
582
|
+
console.log(
|
|
583
|
+
color(
|
|
584
|
+
`✓ Converted ${jsonData.length.toLocaleString()} records in ${elapsed}ms`,
|
|
585
|
+
"green",
|
|
586
|
+
),
|
|
587
|
+
);
|
|
588
|
+
console.log(
|
|
589
|
+
color(
|
|
590
|
+
` Output: ${outputFile} (${csvData.length.toLocaleString()} bytes)`,
|
|
591
|
+
"dim",
|
|
592
|
+
),
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
return {
|
|
597
|
+
records: jsonData.length,
|
|
598
|
+
bytes: csvData.length,
|
|
599
|
+
time: elapsed,
|
|
600
|
+
};
|
|
601
|
+
} catch (error) {
|
|
602
|
+
console.error(color(`✗ Error: ${error.message}`, "red"));
|
|
603
|
+
if (options.debug) {
|
|
604
|
+
console.error(error.stack);
|
|
605
|
+
}
|
|
606
|
+
process.exit(1);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
async function convertCsvToNdjson(inputFile, outputFile, options) {
|
|
611
|
+
const startTime = Date.now();
|
|
612
|
+
|
|
613
|
+
try {
|
|
614
|
+
if (!options.silent) {
|
|
615
|
+
console.log(color("Converting CSV to NDJSON...", "dim"));
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Prepare options for jtcsv
|
|
619
|
+
const jtcsvOptions = {
|
|
620
|
+
delimiter: options.delimiter,
|
|
621
|
+
autoDetect: options.autoDetect,
|
|
622
|
+
candidates: options.candidates,
|
|
623
|
+
hasHeaders: options.hasHeaders,
|
|
624
|
+
renameMap: options.renameMap,
|
|
625
|
+
trim: options.trim,
|
|
626
|
+
parseNumbers: options.parseNumbers,
|
|
627
|
+
parseBooleans: options.parseBooleans,
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
// Read and convert CSV
|
|
631
|
+
const jsonData = await jtcsv.readCsvAsJson(inputFile, jtcsvOptions);
|
|
632
|
+
|
|
633
|
+
// Convert to NDJSON
|
|
634
|
+
const ndjsonData = jtcsv.jsonToNdjson(jsonData);
|
|
635
|
+
|
|
636
|
+
// Write output file
|
|
637
|
+
await fs.promises.writeFile(outputFile, ndjsonData, "utf8");
|
|
638
|
+
|
|
639
|
+
const elapsed = Date.now() - startTime;
|
|
640
|
+
if (!options.silent) {
|
|
641
|
+
console.log(
|
|
642
|
+
color(
|
|
643
|
+
`✓ Converted ${jsonData.length.toLocaleString()} rows in ${elapsed}ms`,
|
|
644
|
+
"green",
|
|
645
|
+
),
|
|
646
|
+
);
|
|
647
|
+
console.log(
|
|
648
|
+
color(
|
|
649
|
+
` Output: ${outputFile} (${ndjsonData.length.toLocaleString()} bytes)`,
|
|
650
|
+
"dim",
|
|
651
|
+
),
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
return {
|
|
656
|
+
rows: jsonData.length,
|
|
657
|
+
bytes: ndjsonData.length,
|
|
658
|
+
time: elapsed,
|
|
659
|
+
};
|
|
660
|
+
} catch (error) {
|
|
661
|
+
console.error(color(`✗ Error: ${error.message}`, "red"));
|
|
662
|
+
if (options.debug) {
|
|
663
|
+
console.error(error.stack);
|
|
664
|
+
}
|
|
665
|
+
process.exit(1);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
async function convertNdjsonToJson(inputFile, outputFile, options) {
|
|
670
|
+
const startTime = Date.now();
|
|
671
|
+
|
|
672
|
+
try {
|
|
673
|
+
if (!options.silent) {
|
|
674
|
+
console.log(color("Converting NDJSON to JSON array...", "dim"));
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Read NDJSON file
|
|
678
|
+
const inputData = await fs.promises.readFile(inputFile, "utf8");
|
|
679
|
+
const jsonData = jtcsv.ndjsonToJson(inputData);
|
|
680
|
+
|
|
681
|
+
// Format JSON
|
|
682
|
+
const jsonOutput = options.pretty
|
|
683
|
+
? JSON.stringify(jsonData, null, 2)
|
|
684
|
+
: JSON.stringify(jsonData);
|
|
685
|
+
|
|
686
|
+
// Write output file
|
|
687
|
+
await fs.promises.writeFile(outputFile, jsonOutput, "utf8");
|
|
688
|
+
|
|
689
|
+
const elapsed = Date.now() - startTime;
|
|
690
|
+
if (!options.silent) {
|
|
691
|
+
console.log(
|
|
692
|
+
color(
|
|
693
|
+
`✓ Converted ${jsonData.length.toLocaleString()} records in ${elapsed}ms`,
|
|
694
|
+
"green",
|
|
695
|
+
),
|
|
696
|
+
);
|
|
697
|
+
console.log(
|
|
698
|
+
color(
|
|
699
|
+
` Output: ${outputFile} (${jsonOutput.length.toLocaleString()} bytes)`,
|
|
700
|
+
"dim",
|
|
701
|
+
),
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return {
|
|
706
|
+
records: jsonData.length,
|
|
707
|
+
bytes: jsonOutput.length,
|
|
708
|
+
time: elapsed,
|
|
709
|
+
};
|
|
710
|
+
} catch (error) {
|
|
711
|
+
console.error(color(`✗ Error: ${error.message}`, "red"));
|
|
712
|
+
if (options.debug) {
|
|
713
|
+
console.error(error.stack);
|
|
714
|
+
}
|
|
715
|
+
process.exit(1);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
async function convertJsonToNdjson(inputFile, outputFile, options) {
|
|
720
|
+
const startTime = Date.now();
|
|
721
|
+
|
|
722
|
+
try {
|
|
723
|
+
if (!options.silent) {
|
|
724
|
+
console.log(color("Converting JSON array to NDJSON...", "dim"));
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Read JSON file
|
|
728
|
+
const inputData = await fs.promises.readFile(inputFile, "utf8");
|
|
729
|
+
const jsonData = JSON.parse(inputData);
|
|
730
|
+
|
|
731
|
+
if (!Array.isArray(jsonData)) {
|
|
732
|
+
throw new Error("JSON data must be an array of objects");
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Convert to NDJSON
|
|
736
|
+
const ndjsonData = jtcsv.jsonToNdjson(jsonData);
|
|
737
|
+
|
|
738
|
+
// Write output file
|
|
739
|
+
await fs.promises.writeFile(outputFile, ndjsonData, "utf8");
|
|
740
|
+
|
|
741
|
+
const elapsed = Date.now() - startTime;
|
|
742
|
+
if (!options.silent) {
|
|
743
|
+
console.log(
|
|
744
|
+
color(
|
|
745
|
+
`✓ Converted ${jsonData.length.toLocaleString()} records in ${elapsed}ms`,
|
|
746
|
+
"green",
|
|
747
|
+
),
|
|
748
|
+
);
|
|
749
|
+
console.log(
|
|
750
|
+
color(
|
|
751
|
+
` Output: ${outputFile} (${ndjsonData.length.toLocaleString()} bytes)`,
|
|
752
|
+
"dim",
|
|
753
|
+
),
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
return {
|
|
758
|
+
records: jsonData.length,
|
|
759
|
+
bytes: ndjsonData.length,
|
|
760
|
+
time: elapsed,
|
|
761
|
+
};
|
|
762
|
+
} catch (error) {
|
|
763
|
+
console.error(color(`✗ Error: ${error.message}`, "red"));
|
|
764
|
+
if (options.debug) {
|
|
765
|
+
console.error(error.stack);
|
|
766
|
+
}
|
|
767
|
+
process.exit(1);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// ============================================================================
|
|
772
|
+
// UNWRAP/FLATTEN FUNCTION
|
|
773
|
+
// ============================================================================
|
|
774
|
+
|
|
775
|
+
async function unwrapJson(inputFile, outputFile, options) {
|
|
776
|
+
const startTime = Date.now();
|
|
777
|
+
|
|
778
|
+
try {
|
|
779
|
+
if (!options.silent) {
|
|
780
|
+
console.log(color("Unwrapping/flattening nested JSON...", "dim"));
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// Read JSON file
|
|
784
|
+
const inputData = await fs.promises.readFile(inputFile, "utf8");
|
|
785
|
+
const jsonData = JSON.parse(inputData);
|
|
786
|
+
|
|
787
|
+
const maxDepth = options.maxDepth || 10;
|
|
788
|
+
const separator = options.flattenPrefix || "_";
|
|
789
|
+
|
|
790
|
+
// Flatten function
|
|
791
|
+
function flattenObject(obj, prefix = "", depth = 0) {
|
|
792
|
+
if (depth >= maxDepth) {
|
|
793
|
+
return { [prefix.slice(0, -1)]: JSON.stringify(obj) };
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const result = {};
|
|
797
|
+
|
|
798
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
799
|
+
const newKey = prefix + key;
|
|
800
|
+
|
|
801
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
802
|
+
Object.assign(result, flattenObject(value, newKey + separator, depth + 1));
|
|
803
|
+
} else if (Array.isArray(value)) {
|
|
804
|
+
// Flatten arrays
|
|
805
|
+
if (options.unwrapArrays) {
|
|
806
|
+
result[newKey] = value.join(", ");
|
|
807
|
+
} else {
|
|
808
|
+
result[newKey] = JSON.stringify(value);
|
|
809
|
+
}
|
|
810
|
+
} else {
|
|
811
|
+
result[newKey] = value;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
return result;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
let unwrappedData;
|
|
819
|
+
if (Array.isArray(jsonData)) {
|
|
820
|
+
unwrappedData = jsonData.map(item => flattenObject(item));
|
|
821
|
+
if (!options.silent) {
|
|
822
|
+
console.log(
|
|
823
|
+
color(`Processing ${jsonData.length.toLocaleString()} records...`, "dim"),
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
} else {
|
|
827
|
+
unwrappedData = flattenObject(jsonData);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// Format JSON
|
|
831
|
+
const jsonOutput = options.pretty
|
|
832
|
+
? JSON.stringify(unwrappedData, null, 2)
|
|
833
|
+
: JSON.stringify(unwrappedData);
|
|
834
|
+
|
|
835
|
+
// Write output file
|
|
836
|
+
await fs.promises.writeFile(outputFile, jsonOutput, "utf8");
|
|
837
|
+
|
|
838
|
+
const elapsed = Date.now() - startTime;
|
|
839
|
+
const recordCount = Array.isArray(unwrappedData) ? unwrappedData.length : 1;
|
|
840
|
+
|
|
841
|
+
if (!options.silent) {
|
|
842
|
+
console.log(
|
|
843
|
+
color(
|
|
844
|
+
`✓ Unwrapped ${recordCount.toLocaleString()} record(s) in ${elapsed}ms`,
|
|
845
|
+
"green",
|
|
846
|
+
),
|
|
847
|
+
);
|
|
848
|
+
console.log(
|
|
849
|
+
color(
|
|
850
|
+
` Output: ${outputFile} (${jsonOutput.length.toLocaleString()} bytes)`,
|
|
851
|
+
"dim",
|
|
852
|
+
),
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
return {
|
|
857
|
+
records: recordCount,
|
|
858
|
+
bytes: jsonOutput.length,
|
|
859
|
+
time: elapsed,
|
|
860
|
+
};
|
|
861
|
+
} catch (error) {
|
|
862
|
+
console.error(color(`✗ Error: ${error.message}`, "red"));
|
|
863
|
+
if (options.debug) {
|
|
864
|
+
console.error(error.stack);
|
|
865
|
+
}
|
|
866
|
+
process.exit(1);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
async function preprocessJson(inputFile, outputFile, options) {
|
|
871
|
+
const startTime = Date.now();
|
|
872
|
+
|
|
873
|
+
try {
|
|
874
|
+
// Read input file
|
|
875
|
+
const inputData = await fs.promises.readFile(inputFile, "utf8");
|
|
876
|
+
const jsonData = JSON.parse(inputData);
|
|
877
|
+
|
|
878
|
+
if (!Array.isArray(jsonData)) {
|
|
879
|
+
throw new Error(
|
|
880
|
+
"JSON data must be an array of objects for preprocessing",
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
if (!options.silent) {
|
|
885
|
+
console.log(
|
|
886
|
+
color(
|
|
887
|
+
`Preprocessing ${jsonData.length.toLocaleString()} records...`,
|
|
888
|
+
"dim",
|
|
889
|
+
),
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// Preprocess data
|
|
894
|
+
const processedData = jtcsv.preprocessData(jsonData);
|
|
895
|
+
|
|
896
|
+
// Apply transform if specified
|
|
897
|
+
let transformedData = processedData;
|
|
898
|
+
if (options.transform) {
|
|
899
|
+
if (!options.silent) {
|
|
900
|
+
console.log(
|
|
901
|
+
color(`Applying transform from: ${options.transform}`, "dim"),
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
try {
|
|
905
|
+
transformedData = transformLoader.applyTransform(
|
|
906
|
+
processedData,
|
|
907
|
+
options.transform,
|
|
908
|
+
);
|
|
909
|
+
if (!options.silent) {
|
|
910
|
+
console.log(
|
|
911
|
+
color(
|
|
912
|
+
`✓ Transform applied to ${transformedData.length} records`,
|
|
913
|
+
"green",
|
|
914
|
+
),
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
} catch (transformError) {
|
|
918
|
+
console.error(
|
|
919
|
+
color(`✗ Transform error: ${transformError.message}`, "red"),
|
|
920
|
+
);
|
|
921
|
+
if (options.debug) {
|
|
922
|
+
console.error(transformError.stack);
|
|
923
|
+
}
|
|
924
|
+
process.exit(1);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// Apply deep unwrap if needed
|
|
929
|
+
if (options.unwrapArrays || options.stringifyObjects) {
|
|
930
|
+
const maxDepth = options.maxDepth || 5;
|
|
931
|
+
transformedData.forEach((item) => {
|
|
932
|
+
for (const key in item) {
|
|
933
|
+
if (item[key] && typeof item[key] === "object") {
|
|
934
|
+
item[key] = jtcsv.deepUnwrap(item[key], 0, maxDepth);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// Format JSON
|
|
941
|
+
const jsonOutput = options.pretty
|
|
942
|
+
? JSON.stringify(transformedData, null, 2)
|
|
943
|
+
: JSON.stringify(transformedData);
|
|
944
|
+
|
|
945
|
+
// Write output file
|
|
946
|
+
await fs.promises.writeFile(outputFile, jsonOutput, "utf8");
|
|
947
|
+
|
|
948
|
+
const elapsed = Date.now() - startTime;
|
|
949
|
+
if (!options.silent) {
|
|
950
|
+
console.log(
|
|
951
|
+
color(
|
|
952
|
+
`✓ Preprocessed ${transformedData.length.toLocaleString()} records in ${elapsed}ms`,
|
|
953
|
+
"green",
|
|
954
|
+
),
|
|
955
|
+
);
|
|
956
|
+
console.log(
|
|
957
|
+
color(
|
|
958
|
+
` Output: ${outputFile} (${jsonOutput.length.toLocaleString()} bytes)`,
|
|
959
|
+
"dim",
|
|
960
|
+
),
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
return {
|
|
965
|
+
records: transformedData.length,
|
|
966
|
+
bytes: jsonOutput.length,
|
|
967
|
+
time: elapsed,
|
|
968
|
+
};
|
|
969
|
+
} catch (error) {
|
|
970
|
+
console.error(color(`✗ Error: ${error.message}`, "red"));
|
|
971
|
+
if (options.debug) {
|
|
972
|
+
console.error(error.stack);
|
|
973
|
+
}
|
|
974
|
+
process.exit(1);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// ============================================================================
|
|
979
|
+
// STREAMING FUNCTIONS
|
|
980
|
+
// ============================================================================
|
|
981
|
+
|
|
982
|
+
async function streamJsonToCsv(inputFile, outputFile, options) {
|
|
983
|
+
const startTime = Date.now();
|
|
984
|
+
let recordCount = 0;
|
|
985
|
+
|
|
986
|
+
try {
|
|
987
|
+
if (!options.silent) {
|
|
988
|
+
console.log(color("Streaming JSON to CSV...", "dim"));
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// Create streams
|
|
992
|
+
const readStream = fs.createReadStream(inputFile, "utf8");
|
|
993
|
+
const writeStream = fs.createWriteStream(outputFile, "utf8");
|
|
994
|
+
|
|
995
|
+
// Add UTF-8 BOM if requested
|
|
996
|
+
if (options.addBOM) {
|
|
997
|
+
writeStream.write("\uFEFF");
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// Parse JSON stream
|
|
1001
|
+
let buffer = "";
|
|
1002
|
+
let isFirstChunk = true;
|
|
1003
|
+
let headersWritten = false;
|
|
1004
|
+
|
|
1005
|
+
readStream.on("data", (chunk) => {
|
|
1006
|
+
buffer += chunk;
|
|
1007
|
+
|
|
1008
|
+
// Try to parse complete JSON objects
|
|
1009
|
+
const lines = buffer.split("\n");
|
|
1010
|
+
buffer = lines.pop() || "";
|
|
1011
|
+
|
|
1012
|
+
for (const line of lines) {
|
|
1013
|
+
if (line.trim()) {
|
|
1014
|
+
try {
|
|
1015
|
+
const obj = JSON.parse(line);
|
|
1016
|
+
// Apply renameMap if provided
|
|
1017
|
+
let finalObj = obj;
|
|
1018
|
+
if (options.renameMap) {
|
|
1019
|
+
finalObj = {};
|
|
1020
|
+
for (const [oldKey, newKey] of Object.entries(
|
|
1021
|
+
options.renameMap,
|
|
1022
|
+
)) {
|
|
1023
|
+
if (oldKey in obj) {
|
|
1024
|
+
finalObj[newKey] = obj[oldKey];
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
// Copy remaining fields
|
|
1028
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1029
|
+
if (!(key in options.renameMap)) {
|
|
1030
|
+
finalObj[key] = value;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
recordCount++;
|
|
1035
|
+
|
|
1036
|
+
// Write headers on first object
|
|
1037
|
+
if (!headersWritten && options.includeHeaders !== false) {
|
|
1038
|
+
const headers = Object.keys(finalObj);
|
|
1039
|
+
writeStream.write(headers.join(options.delimiter || ";") + "\n");
|
|
1040
|
+
headersWritten = true;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// Write CSV row
|
|
1044
|
+
const row =
|
|
1045
|
+
Object.values(finalObj)
|
|
1046
|
+
.map((value) => {
|
|
1047
|
+
const str = String(value);
|
|
1048
|
+
if (
|
|
1049
|
+
str.includes(options.delimiter || ";") ||
|
|
1050
|
+
str.includes('"') ||
|
|
1051
|
+
str.includes("\n")
|
|
1052
|
+
) {
|
|
1053
|
+
return `"${str.replace(/"/g, '""')}"`;
|
|
1054
|
+
}
|
|
1055
|
+
return str;
|
|
1056
|
+
})
|
|
1057
|
+
.join(options.delimiter || ";") + "\n";
|
|
1058
|
+
|
|
1059
|
+
writeStream.write(row);
|
|
1060
|
+
|
|
1061
|
+
// Show progress
|
|
1062
|
+
if (options.verbose && recordCount % 10000 === 0) {
|
|
1063
|
+
process.stdout.write(
|
|
1064
|
+
color(
|
|
1065
|
+
` Processed ${recordCount.toLocaleString()} records\r`,
|
|
1066
|
+
"dim",
|
|
1067
|
+
),
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
} catch (error) {
|
|
1071
|
+
// Skip invalid JSON lines
|
|
1072
|
+
if (options.debug) {
|
|
1073
|
+
console.warn(
|
|
1074
|
+
color(
|
|
1075
|
+
` Warning: Skipping invalid JSON line: ${error.message}`,
|
|
1076
|
+
"yellow",
|
|
1077
|
+
),
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
readStream.on("end", async () => {
|
|
1086
|
+
// Process remaining buffer
|
|
1087
|
+
if (buffer.trim()) {
|
|
1088
|
+
try {
|
|
1089
|
+
const obj = JSON.parse(buffer);
|
|
1090
|
+
|
|
1091
|
+
// Apply renameMap if provided
|
|
1092
|
+
let finalObj = obj;
|
|
1093
|
+
if (options.renameMap) {
|
|
1094
|
+
finalObj = {};
|
|
1095
|
+
for (const [oldKey, newKey] of Object.entries(options.renameMap)) {
|
|
1096
|
+
if (oldKey in obj) {
|
|
1097
|
+
finalObj[newKey] = obj[oldKey];
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
// Copy remaining fields
|
|
1101
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1102
|
+
if (!(key in options.renameMap)) {
|
|
1103
|
+
finalObj[key] = value;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
recordCount++;
|
|
1109
|
+
|
|
1110
|
+
if (!headersWritten && options.includeHeaders !== false) {
|
|
1111
|
+
const headers = Object.keys(finalObj);
|
|
1112
|
+
writeStream.write(headers.join(options.delimiter || ";") + "\n");
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
const row =
|
|
1116
|
+
Object.values(finalObj)
|
|
1117
|
+
.map((value) => {
|
|
1118
|
+
const str = String(value);
|
|
1119
|
+
if (
|
|
1120
|
+
str.includes(options.delimiter || ";") ||
|
|
1121
|
+
str.includes('"') ||
|
|
1122
|
+
str.includes("\n")
|
|
1123
|
+
) {
|
|
1124
|
+
return `"${str.replace(/"/g, '""')}"`;
|
|
1125
|
+
}
|
|
1126
|
+
return str;
|
|
1127
|
+
})
|
|
1128
|
+
.join(options.delimiter || ";") + "\n";
|
|
1129
|
+
|
|
1130
|
+
writeStream.write(row);
|
|
1131
|
+
} catch (error) {
|
|
1132
|
+
// Skip invalid JSON
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
writeStream.end();
|
|
1137
|
+
|
|
1138
|
+
// Wait for write stream to finish
|
|
1139
|
+
await new Promise((resolve) => writeStream.on("finish", resolve));
|
|
1140
|
+
|
|
1141
|
+
const elapsed = Date.now() - startTime;
|
|
1142
|
+
if (!options.silent) {
|
|
1143
|
+
console.log(
|
|
1144
|
+
color(
|
|
1145
|
+
`\n✓ Streamed ${recordCount.toLocaleString()} records in ${elapsed}ms`,
|
|
1146
|
+
"green",
|
|
1147
|
+
),
|
|
1148
|
+
);
|
|
1149
|
+
console.log(color(` Output: ${outputFile}`, "dim"));
|
|
1150
|
+
}
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
readStream.on("error", (error) => {
|
|
1154
|
+
console.error(color(`✗ Stream error: ${error.message}`, "red"));
|
|
1155
|
+
process.exit(1);
|
|
1156
|
+
});
|
|
1157
|
+
|
|
1158
|
+
writeStream.on("error", (error) => {
|
|
1159
|
+
console.error(color(`✗ Write error: ${error.message}`, "red"));
|
|
1160
|
+
process.exit(1);
|
|
1161
|
+
});
|
|
1162
|
+
} catch (error) {
|
|
1163
|
+
console.error(color(`✗ Error: ${error.message}`, "red"));
|
|
1164
|
+
if (options.debug) {
|
|
1165
|
+
console.error(error.stack);
|
|
1166
|
+
}
|
|
1167
|
+
process.exit(1);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
async function streamCsvToJson(inputFile, outputFile, options) {
|
|
1172
|
+
const startTime = Date.now();
|
|
1173
|
+
let rowCount = 0;
|
|
1174
|
+
|
|
1175
|
+
try {
|
|
1176
|
+
if (!options.silent) {
|
|
1177
|
+
console.log(color("Streaming CSV to JSON...", "dim"));
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// Create streams
|
|
1181
|
+
const readStream = fs.createReadStream(inputFile, "utf8");
|
|
1182
|
+
const writeStream = fs.createWriteStream(outputFile, "utf8");
|
|
1183
|
+
|
|
1184
|
+
// Write JSON array opening bracket
|
|
1185
|
+
writeStream.write("[\n");
|
|
1186
|
+
|
|
1187
|
+
let buffer = "";
|
|
1188
|
+
let isFirstRow = true;
|
|
1189
|
+
let headers = [];
|
|
1190
|
+
|
|
1191
|
+
readStream.on("data", (chunk) => {
|
|
1192
|
+
buffer += chunk;
|
|
1193
|
+
|
|
1194
|
+
// Process complete lines
|
|
1195
|
+
const lines = buffer.split("\n");
|
|
1196
|
+
buffer = lines.pop() || "";
|
|
1197
|
+
|
|
1198
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1199
|
+
const line = lines[i].trim();
|
|
1200
|
+
if (!line) continue;
|
|
1201
|
+
|
|
1202
|
+
rowCount++;
|
|
1203
|
+
|
|
1204
|
+
// Parse CSV line
|
|
1205
|
+
const fields = parseCsvLineSimple(line, options.delimiter || ";");
|
|
1206
|
+
|
|
1207
|
+
// First row might be headers
|
|
1208
|
+
if (rowCount === 1 && options.hasHeaders !== false) {
|
|
1209
|
+
headers = fields;
|
|
1210
|
+
continue;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// Create JSON object
|
|
1214
|
+
const obj = {};
|
|
1215
|
+
const fieldCount = Math.min(fields.length, headers.length);
|
|
1216
|
+
|
|
1217
|
+
for (let j = 0; j < fieldCount; j++) {
|
|
1218
|
+
const header = headers[j] || `column${j + 1}`;
|
|
1219
|
+
// Apply renameMap if provided
|
|
1220
|
+
let finalHeader = header;
|
|
1221
|
+
if (options.renameMap && options.renameMap[header]) {
|
|
1222
|
+
finalHeader = options.renameMap[header];
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
let value = fields[j];
|
|
1226
|
+
|
|
1227
|
+
// Parse numbers if enabled
|
|
1228
|
+
if (options.parseNumbers && /^-?\d+(\.\d+)?$/.test(value)) {
|
|
1229
|
+
const num = parseFloat(value);
|
|
1230
|
+
if (!isNaN(num)) {
|
|
1231
|
+
value = num;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
// Parse booleans if enabled
|
|
1236
|
+
if (options.parseBooleans) {
|
|
1237
|
+
const lowerValue = value.toLowerCase();
|
|
1238
|
+
if (lowerValue === "true") value = true;
|
|
1239
|
+
if (lowerValue === "false") value = false;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
obj[finalHeader] = value;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
// Write JSON object
|
|
1246
|
+
const jsonStr = JSON.stringify(obj);
|
|
1247
|
+
if (!isFirstRow) {
|
|
1248
|
+
writeStream.write(",\n");
|
|
1249
|
+
}
|
|
1250
|
+
writeStream.write(" " + jsonStr);
|
|
1251
|
+
isFirstRow = false;
|
|
1252
|
+
|
|
1253
|
+
// Show progress
|
|
1254
|
+
if (options.verbose && rowCount % 10000 === 0) {
|
|
1255
|
+
process.stdout.write(
|
|
1256
|
+
color(` Processed ${rowCount.toLocaleString()} rows\r`, "dim"),
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
readStream.on("end", async () => {
|
|
1263
|
+
// Process remaining buffer
|
|
1264
|
+
if (buffer.trim()) {
|
|
1265
|
+
const fields = parseCsvLineSimple(
|
|
1266
|
+
buffer.trim(),
|
|
1267
|
+
options.delimiter || ";",
|
|
1268
|
+
);
|
|
1269
|
+
|
|
1270
|
+
if (fields.length > 0) {
|
|
1271
|
+
rowCount++;
|
|
1272
|
+
|
|
1273
|
+
// Skip if it's headers
|
|
1274
|
+
if (!(rowCount === 1 && options.hasHeaders !== false)) {
|
|
1275
|
+
const obj = {};
|
|
1276
|
+
const fieldCount = Math.min(fields.length, headers.length);
|
|
1277
|
+
|
|
1278
|
+
for (let j = 0; j < fieldCount; j++) {
|
|
1279
|
+
const header = headers[j] || `column${j + 1}`;
|
|
1280
|
+
// Apply renameMap if provided
|
|
1281
|
+
let finalHeader = header;
|
|
1282
|
+
if (options.renameMap && options.renameMap[header]) {
|
|
1283
|
+
finalHeader = options.renameMap[header];
|
|
1284
|
+
}
|
|
1285
|
+
obj[finalHeader] = fields[j];
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
const jsonStr = JSON.stringify(obj);
|
|
1289
|
+
if (!isFirstRow) {
|
|
1290
|
+
writeStream.write(",\n");
|
|
1291
|
+
}
|
|
1292
|
+
writeStream.write(" " + jsonStr);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
// Write JSON array closing bracket
|
|
1298
|
+
writeStream.write("\n]");
|
|
1299
|
+
writeStream.end();
|
|
1300
|
+
|
|
1301
|
+
// Wait for write stream to finish
|
|
1302
|
+
await new Promise((resolve) => writeStream.on("finish", resolve));
|
|
1303
|
+
|
|
1304
|
+
const elapsed = Date.now() - startTime;
|
|
1305
|
+
if (!options.silent) {
|
|
1306
|
+
console.log(
|
|
1307
|
+
color(
|
|
1308
|
+
`\n✓ Streamed ${(rowCount - (options.hasHeaders !== false ? 1 : 0)).toLocaleString()} rows in ${elapsed}ms`,
|
|
1309
|
+
"green",
|
|
1310
|
+
),
|
|
1311
|
+
);
|
|
1312
|
+
console.log(color(` Output: ${outputFile}`, "dim"));
|
|
1313
|
+
}
|
|
1314
|
+
});
|
|
1315
|
+
|
|
1316
|
+
readStream.on("error", (error) => {
|
|
1317
|
+
console.error(color(`✗ Stream error: ${error.message}`, "red"));
|
|
1318
|
+
process.exit(1);
|
|
1319
|
+
});
|
|
1320
|
+
|
|
1321
|
+
writeStream.on("error", (error) => {
|
|
1322
|
+
console.error(color(`✗ Write error: ${error.message}`, "red"));
|
|
1323
|
+
process.exit(1);
|
|
1324
|
+
});
|
|
1325
|
+
} catch (error) {
|
|
1326
|
+
console.error(color(`✗ Error: ${error.message}`, "red"));
|
|
1327
|
+
if (options.debug) {
|
|
1328
|
+
console.error(error.stack);
|
|
1329
|
+
}
|
|
1330
|
+
process.exit(1);
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
// Simple CSV line parser for streaming
|
|
1335
|
+
function parseCsvLineSimple(line, delimiter) {
|
|
1336
|
+
const fields = [];
|
|
1337
|
+
let currentField = "";
|
|
1338
|
+
let inQuotes = false;
|
|
1339
|
+
|
|
1340
|
+
for (let i = 0; i < line.length; i++) {
|
|
1341
|
+
const char = line[i];
|
|
1342
|
+
|
|
1343
|
+
if (char === '"') {
|
|
1344
|
+
if (inQuotes && i + 1 < line.length && line[i + 1] === '"') {
|
|
1345
|
+
// Escaped quote
|
|
1346
|
+
currentField += '"';
|
|
1347
|
+
i++;
|
|
1348
|
+
} else {
|
|
1349
|
+
// Toggle quotes
|
|
1350
|
+
inQuotes = !inQuotes;
|
|
1351
|
+
}
|
|
1352
|
+
} else if (char === delimiter && !inQuotes) {
|
|
1353
|
+
fields.push(currentField);
|
|
1354
|
+
currentField = "";
|
|
1355
|
+
} else {
|
|
1356
|
+
currentField += char;
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
fields.push(currentField);
|
|
1361
|
+
return fields;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// ============================================================================
|
|
1365
|
+
// BATCH PROCESSING FUNCTIONS
|
|
1366
|
+
// ============================================================================
|
|
1367
|
+
|
|
1368
|
+
async function batchJsonToCsv(inputPattern, outputDir, options) {
|
|
1369
|
+
const startTime = Date.now();
|
|
1370
|
+
|
|
1371
|
+
try {
|
|
1372
|
+
let glob;
|
|
1373
|
+
try {
|
|
1374
|
+
glob = require("glob");
|
|
1375
|
+
} catch (error) {
|
|
1376
|
+
console.error(
|
|
1377
|
+
color(
|
|
1378
|
+
'✗ Error: The "glob" module is required for batch processing',
|
|
1379
|
+
"red",
|
|
1380
|
+
),
|
|
1381
|
+
);
|
|
1382
|
+
console.error(color(" Install it with: npm install glob", "cyan"));
|
|
1383
|
+
console.error(color(" Or update jtcsv: npm update jtcsv", "cyan"));
|
|
1384
|
+
process.exit(1);
|
|
1385
|
+
}
|
|
1386
|
+
const files = glob.sync(inputPattern, {
|
|
1387
|
+
absolute: true,
|
|
1388
|
+
nodir: true,
|
|
1389
|
+
});
|
|
1390
|
+
|
|
1391
|
+
if (files.length === 0) {
|
|
1392
|
+
console.error(
|
|
1393
|
+
color(`✗ No files found matching pattern: ${inputPattern}`, "red"),
|
|
1394
|
+
);
|
|
1395
|
+
process.exit(1);
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
if (!options.silent) {
|
|
1399
|
+
console.log(color(`Found ${files.length} files to process...`, "dim"));
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// Create output directory if it doesn't exist
|
|
1403
|
+
await fs.promises.mkdir(outputDir, { recursive: true });
|
|
1404
|
+
|
|
1405
|
+
const results = [];
|
|
1406
|
+
const parallelLimit = options.parallel || 4;
|
|
1407
|
+
|
|
1408
|
+
// Process files in parallel batches
|
|
1409
|
+
for (let i = 0; i < files.length; i += parallelLimit) {
|
|
1410
|
+
const batch = files.slice(i, i + parallelLimit);
|
|
1411
|
+
const promises = batch.map(async (file) => {
|
|
1412
|
+
const fileName = path.basename(file, ".json");
|
|
1413
|
+
const outputFile = path.join(outputDir, `${fileName}.csv`);
|
|
1414
|
+
|
|
1415
|
+
if (!options.silent && options.verbose) {
|
|
1416
|
+
console.log(color(` Processing: ${file}`, "dim"));
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
try {
|
|
1420
|
+
const result = await convertJsonToCsv(file, outputFile, {
|
|
1421
|
+
...options,
|
|
1422
|
+
silent: true, // Suppress individual file output
|
|
1423
|
+
});
|
|
1424
|
+
|
|
1425
|
+
if (!options.silent) {
|
|
1426
|
+
console.log(
|
|
1427
|
+
color(
|
|
1428
|
+
` ✓ ${fileName}.json → ${fileName}.csv (${result.records} records)`,
|
|
1429
|
+
"green",
|
|
1430
|
+
),
|
|
1431
|
+
);
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
return { file, success: true, ...result };
|
|
1435
|
+
} catch (error) {
|
|
1436
|
+
if (!options.silent) {
|
|
1437
|
+
console.log(color(` ✗ ${fileName}.json: ${error.message}`, "red"));
|
|
1438
|
+
}
|
|
1439
|
+
return { file, success: false, error: error.message };
|
|
1440
|
+
}
|
|
1441
|
+
});
|
|
1442
|
+
|
|
1443
|
+
const batchResults = await Promise.all(promises);
|
|
1444
|
+
results.push(...batchResults);
|
|
1445
|
+
|
|
1446
|
+
if (!options.silent) {
|
|
1447
|
+
const processed = i + batch.length;
|
|
1448
|
+
const percent = Math.round((processed / files.length) * 100);
|
|
1449
|
+
console.log(
|
|
1450
|
+
color(
|
|
1451
|
+
` Progress: ${processed}/${files.length} (${percent}%)`,
|
|
1452
|
+
"dim",
|
|
1453
|
+
),
|
|
1454
|
+
);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
const elapsed = Date.now() - startTime;
|
|
1459
|
+
const successful = results.filter((r) => r.success).length;
|
|
1460
|
+
const totalRecords = results
|
|
1461
|
+
.filter((r) => r.success)
|
|
1462
|
+
.reduce((sum, r) => sum + (r.records || 0), 0);
|
|
1463
|
+
|
|
1464
|
+
if (!options.silent) {
|
|
1465
|
+
console.log(
|
|
1466
|
+
color(`\n✓ Batch processing completed in ${elapsed}ms`, "green"),
|
|
1467
|
+
);
|
|
1468
|
+
console.log(
|
|
1469
|
+
color(` Successful: ${successful}/${files.length} files`, "dim"),
|
|
1470
|
+
);
|
|
1471
|
+
console.log(
|
|
1472
|
+
color(` Total records: ${totalRecords.toLocaleString()}`, "dim"),
|
|
1473
|
+
);
|
|
1474
|
+
console.log(color(` Output directory: ${outputDir}`, "dim"));
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
return {
|
|
1478
|
+
totalFiles: files.length,
|
|
1479
|
+
successful,
|
|
1480
|
+
totalRecords,
|
|
1481
|
+
time: elapsed,
|
|
1482
|
+
results,
|
|
1483
|
+
};
|
|
1484
|
+
} catch (error) {
|
|
1485
|
+
console.error(color(`✗ Batch processing error: ${error.message}`, "red"));
|
|
1486
|
+
if (options.debug) {
|
|
1487
|
+
console.error(error.stack);
|
|
1488
|
+
}
|
|
1489
|
+
process.exit(1);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
async function batchCsvToJson(inputPattern, outputDir, options) {
|
|
1494
|
+
const startTime = Date.now();
|
|
1495
|
+
|
|
1496
|
+
try {
|
|
1497
|
+
let glob;
|
|
1498
|
+
try {
|
|
1499
|
+
glob = require("glob");
|
|
1500
|
+
} catch (error) {
|
|
1501
|
+
console.error(
|
|
1502
|
+
color(
|
|
1503
|
+
'✗ Error: The "glob" module is required for batch processing',
|
|
1504
|
+
"red",
|
|
1505
|
+
),
|
|
1506
|
+
);
|
|
1507
|
+
console.error(color(" Install it with: npm install glob", "cyan"));
|
|
1508
|
+
console.error(color(" Or update jtcsv: npm update jtcsv", "cyan"));
|
|
1509
|
+
process.exit(1);
|
|
1510
|
+
}
|
|
1511
|
+
const files = glob.sync(inputPattern, {
|
|
1512
|
+
absolute: true,
|
|
1513
|
+
nodir: true,
|
|
1514
|
+
});
|
|
1515
|
+
|
|
1516
|
+
if (files.length === 0) {
|
|
1517
|
+
console.error(
|
|
1518
|
+
color(`✗ No files found matching pattern: ${inputPattern}`, "red"),
|
|
1519
|
+
);
|
|
1520
|
+
process.exit(1);
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
if (!options.silent) {
|
|
1524
|
+
console.log(color(`Found ${files.length} files to process...`, "dim"));
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
// Create output directory if it doesn't exist
|
|
1528
|
+
await fs.promises.mkdir(outputDir, { recursive: true });
|
|
1529
|
+
|
|
1530
|
+
const results = [];
|
|
1531
|
+
const parallelLimit = options.parallel || 4;
|
|
1532
|
+
|
|
1533
|
+
// Process files in parallel batches
|
|
1534
|
+
for (let i = 0; i < files.length; i += parallelLimit) {
|
|
1535
|
+
const batch = files.slice(i, i + parallelLimit);
|
|
1536
|
+
const promises = batch.map(async (file) => {
|
|
1537
|
+
const fileName = path.basename(file, ".csv");
|
|
1538
|
+
const outputFile = path.join(outputDir, `${fileName}.json`);
|
|
1539
|
+
|
|
1540
|
+
if (!options.silent && options.verbose) {
|
|
1541
|
+
console.log(color(` Processing: ${file}`, "dim"));
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
try {
|
|
1545
|
+
const result = await convertCsvToJson(file, outputFile, {
|
|
1546
|
+
...options,
|
|
1547
|
+
silent: true, // Suppress individual file output
|
|
1548
|
+
});
|
|
1549
|
+
|
|
1550
|
+
if (!options.silent) {
|
|
1551
|
+
console.log(
|
|
1552
|
+
color(
|
|
1553
|
+
` ✓ ${fileName}.csv → ${fileName}.json (${result.rows} rows)`,
|
|
1554
|
+
"green",
|
|
1555
|
+
),
|
|
1556
|
+
);
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
return { file, success: true, ...result };
|
|
1560
|
+
} catch (error) {
|
|
1561
|
+
if (!options.silent) {
|
|
1562
|
+
console.log(color(` ✗ ${fileName}.csv: ${error.message}`, "red"));
|
|
1563
|
+
}
|
|
1564
|
+
return { file, success: false, error: error.message };
|
|
1565
|
+
}
|
|
1566
|
+
});
|
|
1567
|
+
|
|
1568
|
+
const batchResults = await Promise.all(promises);
|
|
1569
|
+
results.push(...batchResults);
|
|
1570
|
+
|
|
1571
|
+
if (!options.silent) {
|
|
1572
|
+
const processed = i + batch.length;
|
|
1573
|
+
const percent = Math.round((processed / files.length) * 100);
|
|
1574
|
+
console.log(
|
|
1575
|
+
color(
|
|
1576
|
+
` Progress: ${processed}/${files.length} (${percent}%)`,
|
|
1577
|
+
"dim",
|
|
1578
|
+
),
|
|
1579
|
+
);
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
const elapsed = Date.now() - startTime;
|
|
1584
|
+
const successful = results.filter((r) => r.success).length;
|
|
1585
|
+
const totalRows = results
|
|
1586
|
+
.filter((r) => r.success)
|
|
1587
|
+
.reduce((sum, r) => sum + (r.rows || 0), 0);
|
|
1588
|
+
|
|
1589
|
+
if (!options.silent) {
|
|
1590
|
+
console.log(
|
|
1591
|
+
color(`\n✓ Batch processing completed in ${elapsed}ms`, "green"),
|
|
1592
|
+
);
|
|
1593
|
+
console.log(
|
|
1594
|
+
color(` Successful: ${successful}/${files.length} files`, "dim"),
|
|
1595
|
+
);
|
|
1596
|
+
console.log(color(` Total rows: ${totalRows.toLocaleString()}`, "dim"));
|
|
1597
|
+
console.log(color(` Output directory: ${outputDir}`, "dim"));
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
return {
|
|
1601
|
+
totalFiles: files.length,
|
|
1602
|
+
successful,
|
|
1603
|
+
totalRows,
|
|
1604
|
+
time: elapsed,
|
|
1605
|
+
results,
|
|
1606
|
+
};
|
|
1607
|
+
} catch (error) {
|
|
1608
|
+
console.error(color(`✗ Batch processing error: ${error.message}`, "red"));
|
|
1609
|
+
if (options.debug) {
|
|
1610
|
+
console.error(error.stack);
|
|
1611
|
+
}
|
|
1612
|
+
process.exit(1);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
async function batchProcessMixed(inputPattern, outputDir, options) {
|
|
1617
|
+
const startTime = Date.now();
|
|
1618
|
+
|
|
1619
|
+
try {
|
|
1620
|
+
let glob;
|
|
1621
|
+
try {
|
|
1622
|
+
glob = require("glob");
|
|
1623
|
+
} catch (error) {
|
|
1624
|
+
console.error(
|
|
1625
|
+
color(
|
|
1626
|
+
'✗ Error: The "glob" module is required for batch processing',
|
|
1627
|
+
"red",
|
|
1628
|
+
),
|
|
1629
|
+
);
|
|
1630
|
+
console.error(color(" Install it with: npm install glob", "cyan"));
|
|
1631
|
+
console.error(color(" Or update jtcsv: npm update jtcsv", "cyan"));
|
|
1632
|
+
process.exit(1);
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
// Находим все файлы
|
|
1636
|
+
const files = glob.sync(inputPattern, {
|
|
1637
|
+
absolute: true,
|
|
1638
|
+
nodir: true,
|
|
1639
|
+
});
|
|
1640
|
+
|
|
1641
|
+
if (files.length === 0) {
|
|
1642
|
+
console.error(
|
|
1643
|
+
color(`✗ No files found matching pattern: ${inputPattern}`, "red"),
|
|
1644
|
+
);
|
|
1645
|
+
process.exit(1);
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
if (!options.silent) {
|
|
1649
|
+
console.log(color(`Found ${files.length} files to process...`, "dim"));
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
// Создаем выходную директорию
|
|
1653
|
+
await fs.promises.mkdir(outputDir, { recursive: true });
|
|
1654
|
+
|
|
1655
|
+
const results = [];
|
|
1656
|
+
const parallelLimit = options.parallel || 4;
|
|
1657
|
+
|
|
1658
|
+
// Группируем файлы по типу
|
|
1659
|
+
const jsonFiles = files.filter((file) =>
|
|
1660
|
+
file.toLowerCase().endsWith(".json"),
|
|
1661
|
+
);
|
|
1662
|
+
const csvFiles = files.filter((file) =>
|
|
1663
|
+
file.toLowerCase().endsWith(".csv"),
|
|
1664
|
+
);
|
|
1665
|
+
const otherFiles = files.filter(
|
|
1666
|
+
(file) =>
|
|
1667
|
+
!file.toLowerCase().endsWith(".json") &&
|
|
1668
|
+
!file.toLowerCase().endsWith(".csv"),
|
|
1669
|
+
);
|
|
1670
|
+
|
|
1671
|
+
if (otherFiles.length > 0 && !options.silent) {
|
|
1672
|
+
console.log(
|
|
1673
|
+
color(
|
|
1674
|
+
` Warning: Skipping ${otherFiles.length} non-JSON/CSV files`,
|
|
1675
|
+
"yellow",
|
|
1676
|
+
),
|
|
1677
|
+
);
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
// Обрабатываем JSON файлы
|
|
1681
|
+
for (let i = 0; i < jsonFiles.length; i += parallelLimit) {
|
|
1682
|
+
const batch = jsonFiles.slice(i, i + parallelLimit);
|
|
1683
|
+
const promises = batch.map(async (file) => {
|
|
1684
|
+
const fileName = path.basename(file, ".json");
|
|
1685
|
+
const outputFile = path.join(outputDir, `${fileName}.csv`);
|
|
1686
|
+
|
|
1687
|
+
if (!options.silent && options.verbose) {
|
|
1688
|
+
console.log(color(` Processing JSON: ${file}`, "dim"));
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
try {
|
|
1692
|
+
const result = await convertJsonToCsv(file, outputFile, {
|
|
1693
|
+
...options,
|
|
1694
|
+
silent: true,
|
|
1695
|
+
});
|
|
1696
|
+
|
|
1697
|
+
if (!options.silent) {
|
|
1698
|
+
console.log(
|
|
1699
|
+
color(
|
|
1700
|
+
` ✓ ${fileName}.json → ${fileName}.csv (${result.records} records)`,
|
|
1701
|
+
"green",
|
|
1702
|
+
),
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
return { file, type: "json", success: true, ...result };
|
|
1707
|
+
} catch (error) {
|
|
1708
|
+
if (!options.silent) {
|
|
1709
|
+
console.log(color(` ✗ ${fileName}.json: ${error.message}`, "red"));
|
|
1710
|
+
}
|
|
1711
|
+
return { file, type: "json", success: false, error: error.message };
|
|
1712
|
+
}
|
|
1713
|
+
});
|
|
1714
|
+
|
|
1715
|
+
const batchResults = await Promise.all(promises);
|
|
1716
|
+
results.push(...batchResults);
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
// Обрабатываем CSV файлы
|
|
1720
|
+
for (let i = 0; i < csvFiles.length; i += parallelLimit) {
|
|
1721
|
+
const batch = csvFiles.slice(i, i + parallelLimit);
|
|
1722
|
+
const promises = batch.map(async (file) => {
|
|
1723
|
+
const fileName = path.basename(file, ".csv");
|
|
1724
|
+
const outputFile = path.join(outputDir, `${fileName}.json`);
|
|
1725
|
+
|
|
1726
|
+
if (!options.silent && options.verbose) {
|
|
1727
|
+
console.log(color(` Processing CSV: ${file}`, "dim"));
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
try {
|
|
1731
|
+
const result = await convertCsvToJson(file, outputFile, {
|
|
1732
|
+
...options,
|
|
1733
|
+
silent: true,
|
|
1734
|
+
});
|
|
1735
|
+
|
|
1736
|
+
if (!options.silent) {
|
|
1737
|
+
console.log(
|
|
1738
|
+
color(
|
|
1739
|
+
` ✓ ${fileName}.csv → ${fileName}.json (${result.rows} rows)`,
|
|
1740
|
+
"green",
|
|
1741
|
+
),
|
|
1742
|
+
);
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
return { file, type: "csv", success: true, ...result };
|
|
1746
|
+
} catch (error) {
|
|
1747
|
+
if (!options.silent) {
|
|
1748
|
+
console.log(color(` ✗ ${fileName}.csv: ${error.message}`, "red"));
|
|
1749
|
+
}
|
|
1750
|
+
return { file, type: "csv", success: false, error: error.message };
|
|
1751
|
+
}
|
|
1752
|
+
});
|
|
1753
|
+
|
|
1754
|
+
const batchResults = await Promise.all(promises);
|
|
1755
|
+
results.push(...batchResults);
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
const elapsed = Date.now() - startTime;
|
|
1759
|
+
const successful = results.filter((r) => r.success).length;
|
|
1760
|
+
const totalRecords = results
|
|
1761
|
+
.filter((r) => r.success)
|
|
1762
|
+
.reduce((sum, r) => sum + (r.records || r.rows || 0), 0);
|
|
1763
|
+
|
|
1764
|
+
if (!options.silent) {
|
|
1765
|
+
console.log(
|
|
1766
|
+
color(`\n✓ Mixed batch processing completed in ${elapsed}ms`, "green"),
|
|
1767
|
+
);
|
|
1768
|
+
console.log(
|
|
1769
|
+
color(` Successful: ${successful}/${files.length} files`, "dim"),
|
|
1770
|
+
);
|
|
1771
|
+
console.log(
|
|
1772
|
+
color(
|
|
1773
|
+
` JSON files: ${jsonFiles.length}, CSV files: ${csvFiles.length}`,
|
|
1774
|
+
"dim",
|
|
1775
|
+
),
|
|
1776
|
+
);
|
|
1777
|
+
console.log(
|
|
1778
|
+
color(` Total records: ${totalRecords.toLocaleString()}`, "dim"),
|
|
1779
|
+
);
|
|
1780
|
+
console.log(color(` Output directory: ${outputDir}`, "dim"));
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
return {
|
|
1784
|
+
totalFiles: files.length,
|
|
1785
|
+
jsonFiles: jsonFiles.length,
|
|
1786
|
+
csvFiles: csvFiles.length,
|
|
1787
|
+
otherFiles: otherFiles.length,
|
|
1788
|
+
successful,
|
|
1789
|
+
totalRecords,
|
|
1790
|
+
time: elapsed,
|
|
1791
|
+
results,
|
|
1792
|
+
};
|
|
1793
|
+
} catch (error) {
|
|
1794
|
+
console.error(color(`✗ Batch processing error: ${error.message}`, "red"));
|
|
1795
|
+
if (options.debug) {
|
|
1796
|
+
console.error(error.stack);
|
|
1797
|
+
}
|
|
1798
|
+
process.exit(1);
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
// ============================================================================
|
|
1803
|
+
// OPTIONS PARSING
|
|
1804
|
+
// ============================================================================
|
|
1805
|
+
|
|
1806
|
+
function parseOptions(args) {
|
|
1807
|
+
const options = {
|
|
1808
|
+
delimiter: ";",
|
|
1809
|
+
autoDetect: true,
|
|
1810
|
+
candidates: [";", ",", "\t", "|"],
|
|
1811
|
+
hasHeaders: true,
|
|
1812
|
+
includeHeaders: true,
|
|
1813
|
+
renameMap: undefined,
|
|
1814
|
+
template: undefined,
|
|
1815
|
+
trim: true,
|
|
1816
|
+
parseNumbers: false,
|
|
1817
|
+
parseBooleans: false,
|
|
1818
|
+
useFastPath: true,
|
|
1819
|
+
fastPathMode: "objects",
|
|
1820
|
+
preventCsvInjection: true,
|
|
1821
|
+
rfc4180Compliant: true,
|
|
1822
|
+
maxRecords: undefined,
|
|
1823
|
+
maxRows: undefined,
|
|
1824
|
+
maxDepth: 5,
|
|
1825
|
+
pretty: false,
|
|
1826
|
+
silent: false,
|
|
1827
|
+
verbose: false,
|
|
1828
|
+
debug: false,
|
|
1829
|
+
dryRun: false,
|
|
1830
|
+
addBOM: false,
|
|
1831
|
+
unwrapArrays: false,
|
|
1832
|
+
stringifyObjects: false,
|
|
1833
|
+
recursive: false,
|
|
1834
|
+
pattern: "**/*",
|
|
1835
|
+
outputDir: "./output",
|
|
1836
|
+
overwrite: false,
|
|
1837
|
+
parallel: 4,
|
|
1838
|
+
chunkSize: 65536,
|
|
1839
|
+
bufferSize: 1000,
|
|
1840
|
+
schema: undefined,
|
|
1841
|
+
transform: undefined,
|
|
1842
|
+
flattenPrefix: "_",
|
|
1843
|
+
};
|
|
1844
|
+
|
|
1845
|
+
const files = [];
|
|
1846
|
+
|
|
1847
|
+
for (let i = 0; i < args.length; i++) {
|
|
1848
|
+
const arg = args[i];
|
|
1849
|
+
|
|
1850
|
+
if (arg.startsWith("--")) {
|
|
1851
|
+
const [key, value] = arg.slice(2).split("=");
|
|
1852
|
+
|
|
1853
|
+
switch (key) {
|
|
1854
|
+
case "delimiter":
|
|
1855
|
+
options.delimiter = value || ",";
|
|
1856
|
+
options.autoDetect = false;
|
|
1857
|
+
break;
|
|
1858
|
+
case "auto-detect":
|
|
1859
|
+
options.autoDetect = value !== "false";
|
|
1860
|
+
break;
|
|
1861
|
+
case "candidates":
|
|
1862
|
+
options.candidates = value ? value.split(",") : [";", ",", "\t", "|"];
|
|
1863
|
+
break;
|
|
1864
|
+
case "no-headers":
|
|
1865
|
+
options.includeHeaders = false;
|
|
1866
|
+
options.hasHeaders = false;
|
|
1867
|
+
break;
|
|
1868
|
+
case "parse-numbers":
|
|
1869
|
+
options.parseNumbers = true;
|
|
1870
|
+
break;
|
|
1871
|
+
case "parse-booleans":
|
|
1872
|
+
options.parseBooleans = true;
|
|
1873
|
+
break;
|
|
1874
|
+
case "no-trim":
|
|
1875
|
+
options.trim = false;
|
|
1876
|
+
break;
|
|
1877
|
+
case "no-fast-path":
|
|
1878
|
+
options.useFastPath = false;
|
|
1879
|
+
break;
|
|
1880
|
+
case "fast-path":
|
|
1881
|
+
options.useFastPath = value !== "false";
|
|
1882
|
+
break;
|
|
1883
|
+
case "fast-path-mode":
|
|
1884
|
+
options.fastPathMode = value || "objects";
|
|
1885
|
+
if (
|
|
1886
|
+
options.fastPathMode !== "objects" &&
|
|
1887
|
+
options.fastPathMode !== "compact"
|
|
1888
|
+
) {
|
|
1889
|
+
throw new Error("Invalid --fast-path-mode value (objects|compact)");
|
|
1890
|
+
}
|
|
1891
|
+
break;
|
|
1892
|
+
case "rename":
|
|
1893
|
+
try {
|
|
1894
|
+
const jsonStr = value || "{}";
|
|
1895
|
+
const cleanStr = jsonStr
|
|
1896
|
+
.replace(/^'|'$/g, "")
|
|
1897
|
+
.replace(/^"|"$/g, "");
|
|
1898
|
+
options.renameMap = JSON.parse(cleanStr);
|
|
1899
|
+
} catch (e) {
|
|
1900
|
+
throw new Error(`Invalid JSON in --rename option: ${e.message}`);
|
|
1901
|
+
}
|
|
1902
|
+
break;
|
|
1903
|
+
case "template":
|
|
1904
|
+
try {
|
|
1905
|
+
const jsonStr = value || "{}";
|
|
1906
|
+
const cleanStr = jsonStr
|
|
1907
|
+
.replace(/^'|'$/g, "")
|
|
1908
|
+
.replace(/^"|"$/g, "");
|
|
1909
|
+
options.template = JSON.parse(cleanStr);
|
|
1910
|
+
} catch (e) {
|
|
1911
|
+
throw new Error(`Invalid JSON in --template option: ${e.message}`);
|
|
1912
|
+
}
|
|
1913
|
+
break;
|
|
1914
|
+
case "no-injection-protection":
|
|
1915
|
+
options.preventCsvInjection = false;
|
|
1916
|
+
break;
|
|
1917
|
+
case "no-rfc4180":
|
|
1918
|
+
options.rfc4180Compliant = false;
|
|
1919
|
+
break;
|
|
1920
|
+
case "max-records":
|
|
1921
|
+
options.maxRecords = parseInt(value, 10);
|
|
1922
|
+
break;
|
|
1923
|
+
case "max-rows":
|
|
1924
|
+
options.maxRows = parseInt(value, 10);
|
|
1925
|
+
break;
|
|
1926
|
+
case "max-depth":
|
|
1927
|
+
options.maxDepth = parseInt(value, 10) || 5;
|
|
1928
|
+
break;
|
|
1929
|
+
case "pretty":
|
|
1930
|
+
options.pretty = true;
|
|
1931
|
+
break;
|
|
1932
|
+
case "silent":
|
|
1933
|
+
options.silent = true;
|
|
1934
|
+
break;
|
|
1935
|
+
case "verbose":
|
|
1936
|
+
options.verbose = true;
|
|
1937
|
+
break;
|
|
1938
|
+
case "debug":
|
|
1939
|
+
options.debug = true;
|
|
1940
|
+
break;
|
|
1941
|
+
case "dry-run":
|
|
1942
|
+
options.dryRun = true;
|
|
1943
|
+
break;
|
|
1944
|
+
case "add-bom":
|
|
1945
|
+
options.addBOM = true;
|
|
1946
|
+
break;
|
|
1947
|
+
case "unwrap-arrays":
|
|
1948
|
+
options.unwrapArrays = true;
|
|
1949
|
+
break;
|
|
1950
|
+
case "stringify-objects":
|
|
1951
|
+
options.stringifyObjects = true;
|
|
1952
|
+
break;
|
|
1953
|
+
case "recursive":
|
|
1954
|
+
options.recursive = true;
|
|
1955
|
+
break;
|
|
1956
|
+
case "pattern":
|
|
1957
|
+
options.pattern = value || "**/*";
|
|
1958
|
+
break;
|
|
1959
|
+
case "output-dir":
|
|
1960
|
+
options.outputDir = value || "./output";
|
|
1961
|
+
break;
|
|
1962
|
+
case "overwrite":
|
|
1963
|
+
options.overwrite = true;
|
|
1964
|
+
break;
|
|
1965
|
+
case "parallel":
|
|
1966
|
+
options.parallel = parseInt(value, 10) || 4;
|
|
1967
|
+
break;
|
|
1968
|
+
case "chunk-size":
|
|
1969
|
+
options.chunkSize = parseInt(value, 10) || 65536;
|
|
1970
|
+
break;
|
|
1971
|
+
case "buffer-size":
|
|
1972
|
+
options.bufferSize = parseInt(value, 10) || 1000;
|
|
1973
|
+
break;
|
|
1974
|
+
case "schema":
|
|
1975
|
+
try {
|
|
1976
|
+
const jsonStr = value || "{}";
|
|
1977
|
+
const cleanStr = jsonStr
|
|
1978
|
+
.replace(/^'|'$/g, "")
|
|
1979
|
+
.replace(/^"|"$/g, "");
|
|
1980
|
+
options.schema = JSON.parse(cleanStr);
|
|
1981
|
+
} catch (e) {
|
|
1982
|
+
throw new Error(`Invalid JSON in --schema option: ${e.message}`);
|
|
1983
|
+
}
|
|
1984
|
+
break;
|
|
1985
|
+
case "transform":
|
|
1986
|
+
options.transform = value;
|
|
1987
|
+
break;
|
|
1988
|
+
case "port":
|
|
1989
|
+
options.port = parseInt(value, 10) || 3000;
|
|
1990
|
+
break;
|
|
1991
|
+
case "host":
|
|
1992
|
+
options.host = value || 'localhost';
|
|
1993
|
+
break;
|
|
1994
|
+
}
|
|
1995
|
+
} else if (!arg.startsWith("-")) {
|
|
1996
|
+
files.push(arg);
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
return { options, files };
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
// ============================================================================
|
|
2004
|
+
// TUI LAUNCHER
|
|
2005
|
+
// ============================================================================
|
|
2006
|
+
|
|
2007
|
+
async function launchTUI() {
|
|
2008
|
+
try {
|
|
2009
|
+
console.log(color("Launching Terminal User Interface...", "cyan"));
|
|
2010
|
+
console.log(color("Press Ctrl+Q to exit", "dim"));
|
|
2011
|
+
|
|
2012
|
+
const JtcsvTUI = require("@jtcsv/tui");
|
|
2013
|
+
const tui = new JtcsvTUI();
|
|
2014
|
+
tui.start();
|
|
2015
|
+
} catch (error) {
|
|
2016
|
+
if (error.code === "MODULE_NOT_FOUND") {
|
|
2017
|
+
console.error(color("Error: @jtcsv/tui is not installed", "red"));
|
|
2018
|
+
console.log(color("Install it with:", "dim"));
|
|
2019
|
+
console.log(color(" npm install @jtcsv/tui", "cyan"));
|
|
2020
|
+
console.log(color("\nOr use the CLI interface instead:", "dim"));
|
|
2021
|
+
console.log(color(" jtcsv help", "cyan"));
|
|
2022
|
+
} else {
|
|
2023
|
+
console.error(color(`Error: ${error.message}`, "red"));
|
|
2024
|
+
}
|
|
2025
|
+
process.exit(1);
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
async function launchWebUI(options = {}) {
|
|
2030
|
+
try {
|
|
2031
|
+
const webServer = require("../src/web-server");
|
|
2032
|
+
webServer.startServer({
|
|
2033
|
+
port: options.port || 3000,
|
|
2034
|
+
host: options.host || 'localhost'
|
|
2035
|
+
});
|
|
2036
|
+
} catch (error) {
|
|
2037
|
+
console.error(color(`Error: ${error.message}`, "red"));
|
|
2038
|
+
if (options.debug) {
|
|
2039
|
+
console.error(error.stack);
|
|
2040
|
+
}
|
|
2041
|
+
process.exit(1);
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
async function startBasicTUI() {
|
|
2046
|
+
const readline = require("readline");
|
|
2047
|
+
|
|
2048
|
+
const rl = readline.createInterface({
|
|
2049
|
+
input: process.stdin,
|
|
2050
|
+
output: process.stdout,
|
|
2051
|
+
});
|
|
2052
|
+
|
|
2053
|
+
console.clear();
|
|
2054
|
+
console.log(color("╔══════════════════════════════════════╗", "cyan"));
|
|
2055
|
+
console.log(color("║ JTCSV Terminal Interface ║", "cyan"));
|
|
2056
|
+
console.log(color("╚══════════════════════════════════════╝", "cyan"));
|
|
2057
|
+
console.log();
|
|
2058
|
+
console.log(color("Select operation:", "bright"));
|
|
2059
|
+
console.log(" 1. JSON → CSV");
|
|
2060
|
+
console.log(" 2. CSV → JSON");
|
|
2061
|
+
console.log(" 3. Preprocess JSON");
|
|
2062
|
+
console.log(" 4. Batch Processing");
|
|
2063
|
+
console.log(" 5. Exit");
|
|
2064
|
+
console.log();
|
|
2065
|
+
|
|
2066
|
+
rl.question(color("Enter choice (1-5): ", "cyan"), async (choice) => {
|
|
2067
|
+
switch (choice) {
|
|
2068
|
+
case "1":
|
|
2069
|
+
await runJsonToCsvTUI(rl);
|
|
2070
|
+
break;
|
|
2071
|
+
case "2":
|
|
2072
|
+
await runCsvToJsonTUI(rl);
|
|
2073
|
+
break;
|
|
2074
|
+
case "3":
|
|
2075
|
+
console.log(color("Preprocess feature coming soon...", "yellow"));
|
|
2076
|
+
rl.close();
|
|
2077
|
+
break;
|
|
2078
|
+
case "4":
|
|
2079
|
+
console.log(color("Batch processing coming soon...", "yellow"));
|
|
2080
|
+
rl.close();
|
|
2081
|
+
break;
|
|
2082
|
+
case "5":
|
|
2083
|
+
console.log(color("Goodbye!", "green"));
|
|
2084
|
+
rl.close();
|
|
2085
|
+
process.exit(0);
|
|
2086
|
+
break;
|
|
2087
|
+
default:
|
|
2088
|
+
console.log(color("Invalid choice", "red"));
|
|
2089
|
+
rl.close();
|
|
2090
|
+
process.exit(1);
|
|
2091
|
+
}
|
|
2092
|
+
});
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
async function runJsonToCsvTUI(rl) {
|
|
2096
|
+
console.clear();
|
|
2097
|
+
console.log(color("JSON → CSV Conversion", "cyan"));
|
|
2098
|
+
console.log();
|
|
2099
|
+
|
|
2100
|
+
rl.question("Input JSON file: ", (inputFile) => {
|
|
2101
|
+
rl.question("Output CSV file: ", async (outputFile) => {
|
|
2102
|
+
rl.question("Delimiter (default: ;): ", async (delimiter) => {
|
|
2103
|
+
try {
|
|
2104
|
+
console.log(color("\nConverting...", "dim"));
|
|
2105
|
+
|
|
2106
|
+
const result = await convertJsonToCsv(inputFile, outputFile, {
|
|
2107
|
+
delimiter: delimiter || ";",
|
|
2108
|
+
silent: false,
|
|
2109
|
+
});
|
|
2110
|
+
|
|
2111
|
+
console.log(color("\n✓ Conversion complete!", "green"));
|
|
2112
|
+
rl.question("\nPress Enter to continue...", () => {
|
|
2113
|
+
rl.close();
|
|
2114
|
+
startBasicTUI();
|
|
2115
|
+
});
|
|
2116
|
+
} catch (error) {
|
|
2117
|
+
console.error(color(`✗ Error: ${error.message}`, "red"));
|
|
2118
|
+
rl.close();
|
|
2119
|
+
process.exit(1);
|
|
2120
|
+
}
|
|
2121
|
+
});
|
|
2122
|
+
});
|
|
2123
|
+
});
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
async function runCsvToJsonTUI(rl) {
|
|
2127
|
+
console.clear();
|
|
2128
|
+
console.log(color("CSV → JSON Conversion", "cyan"));
|
|
2129
|
+
console.log();
|
|
2130
|
+
|
|
2131
|
+
rl.question("Input CSV file: ", (inputFile) => {
|
|
2132
|
+
rl.question("Output JSON file: ", async (outputFile) => {
|
|
2133
|
+
rl.question("Delimiter (default: ;): ", async (delimiter) => {
|
|
2134
|
+
rl.question("Pretty print? (y/n): ", async (pretty) => {
|
|
2135
|
+
try {
|
|
2136
|
+
console.log(color("\nConverting...", "dim"));
|
|
2137
|
+
|
|
2138
|
+
const result = await convertCsvToJson(inputFile, outputFile, {
|
|
2139
|
+
delimiter: delimiter || ";",
|
|
2140
|
+
pretty: pretty.toLowerCase() === "y",
|
|
2141
|
+
silent: false,
|
|
2142
|
+
});
|
|
2143
|
+
|
|
2144
|
+
console.log(color("\n✓ Conversion complete!", "green"));
|
|
2145
|
+
rl.question("\nPress Enter to continue...", () => {
|
|
2146
|
+
rl.close();
|
|
2147
|
+
startBasicTUI();
|
|
2148
|
+
});
|
|
2149
|
+
} catch (error) {
|
|
2150
|
+
console.error(color(`✗ Error: ${error.message}`, "red"));
|
|
2151
|
+
rl.close();
|
|
2152
|
+
process.exit(1);
|
|
2153
|
+
}
|
|
2154
|
+
});
|
|
2155
|
+
});
|
|
2156
|
+
});
|
|
2157
|
+
});
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
// ============================================================================
|
|
2161
|
+
// MAIN FUNCTION
|
|
2162
|
+
// ============================================================================
|
|
2163
|
+
|
|
2164
|
+
async function main() {
|
|
2165
|
+
const args = process.argv.slice(2);
|
|
2166
|
+
|
|
2167
|
+
if (args.length === 0) {
|
|
2168
|
+
showHelp();
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
const command = args[0].toLowerCase();
|
|
2173
|
+
const { options, files } = parseOptions(args.slice(1));
|
|
2174
|
+
|
|
2175
|
+
// Handle dry run
|
|
2176
|
+
if (options.dryRun) {
|
|
2177
|
+
console.log(color("DRY RUN - No files will be modified", "yellow"));
|
|
2178
|
+
console.log(`Command: ${command}`);
|
|
2179
|
+
console.log(`Files: ${files.join(", ")}`);
|
|
2180
|
+
console.log(`Options:`, options);
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
// Suppress output if silent mode
|
|
2185
|
+
if (options.silent) {
|
|
2186
|
+
console.log = () => {};
|
|
2187
|
+
console.info = () => {};
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
switch (command) {
|
|
2191
|
+
// Main conversion commands
|
|
2192
|
+
case "json-to-csv":
|
|
2193
|
+
case "json2csv":
|
|
2194
|
+
if (files.length < 2) {
|
|
2195
|
+
console.error(color("Error: Input and output files required", "red"));
|
|
2196
|
+
console.log(
|
|
2197
|
+
color("Usage: jtcsv json-to-csv input.json output.csv", "cyan"),
|
|
2198
|
+
);
|
|
2199
|
+
process.exit(1);
|
|
2200
|
+
}
|
|
2201
|
+
await convertJsonToCsv(files[0], files[1], options);
|
|
2202
|
+
break;
|
|
2203
|
+
|
|
2204
|
+
case "csv-to-json":
|
|
2205
|
+
case "csv2json":
|
|
2206
|
+
if (files.length < 2) {
|
|
2207
|
+
console.error(color("Error: Input and output files required", "red"));
|
|
2208
|
+
console.log(
|
|
2209
|
+
color("Usage: jtcsv csv-to-json input.csv output.json", "cyan"),
|
|
2210
|
+
);
|
|
2211
|
+
process.exit(1);
|
|
2212
|
+
}
|
|
2213
|
+
await convertCsvToJson(files[0], files[1], options);
|
|
2214
|
+
break;
|
|
2215
|
+
|
|
2216
|
+
case "save-json":
|
|
2217
|
+
if (files.length < 2) {
|
|
2218
|
+
console.error(color("Error: Input and output files required", "red"));
|
|
2219
|
+
console.log(
|
|
2220
|
+
color("Usage: jtcsv save-json input.json output.json", "cyan"),
|
|
2221
|
+
);
|
|
2222
|
+
process.exit(1);
|
|
2223
|
+
}
|
|
2224
|
+
await saveAsJson(files[0], files[1], options);
|
|
2225
|
+
break;
|
|
2226
|
+
|
|
2227
|
+
case "save-csv":
|
|
2228
|
+
if (files.length < 2) {
|
|
2229
|
+
console.error(color("Error: Input and output files required", "red"));
|
|
2230
|
+
console.log(
|
|
2231
|
+
color("Usage: jtcsv save-csv input.csv output.csv", "cyan"),
|
|
2232
|
+
);
|
|
2233
|
+
process.exit(1);
|
|
2234
|
+
}
|
|
2235
|
+
await saveAsCsv(files[0], files[1], options);
|
|
2236
|
+
break;
|
|
2237
|
+
|
|
2238
|
+
case "preprocess":
|
|
2239
|
+
if (files.length < 2) {
|
|
2240
|
+
console.error(color("Error: Input and output files required", "red"));
|
|
2241
|
+
console.log(
|
|
2242
|
+
color("Usage: jtcsv preprocess input.json output.json", "cyan"),
|
|
2243
|
+
);
|
|
2244
|
+
process.exit(1);
|
|
2245
|
+
}
|
|
2246
|
+
await preprocessJson(files[0], files[1], options);
|
|
2247
|
+
break;
|
|
2248
|
+
|
|
2249
|
+
// NDJSON commands
|
|
2250
|
+
case "ndjson-to-csv":
|
|
2251
|
+
if (files.length < 2) {
|
|
2252
|
+
console.error(color("Error: Input and output files required", "red"));
|
|
2253
|
+
console.log(
|
|
2254
|
+
color("Usage: jtcsv ndjson-to-csv input.ndjson output.csv", "cyan"),
|
|
2255
|
+
);
|
|
2256
|
+
process.exit(1);
|
|
2257
|
+
}
|
|
2258
|
+
await convertNdjsonToCsv(files[0], files[1], options);
|
|
2259
|
+
break;
|
|
2260
|
+
|
|
2261
|
+
case "csv-to-ndjson":
|
|
2262
|
+
if (files.length < 2) {
|
|
2263
|
+
console.error(color("Error: Input and output files required", "red"));
|
|
2264
|
+
console.log(
|
|
2265
|
+
color("Usage: jtcsv csv-to-ndjson input.csv output.ndjson", "cyan"),
|
|
2266
|
+
);
|
|
2267
|
+
process.exit(1);
|
|
2268
|
+
}
|
|
2269
|
+
await convertCsvToNdjson(files[0], files[1], options);
|
|
2270
|
+
break;
|
|
2271
|
+
|
|
2272
|
+
case "ndjson-to-json":
|
|
2273
|
+
if (files.length < 2) {
|
|
2274
|
+
console.error(color("Error: Input and output files required", "red"));
|
|
2275
|
+
console.log(
|
|
2276
|
+
color("Usage: jtcsv ndjson-to-json input.ndjson output.json", "cyan"),
|
|
2277
|
+
);
|
|
2278
|
+
process.exit(1);
|
|
2279
|
+
}
|
|
2280
|
+
await convertNdjsonToJson(files[0], files[1], options);
|
|
2281
|
+
break;
|
|
2282
|
+
|
|
2283
|
+
case "json-to-ndjson":
|
|
2284
|
+
if (files.length < 2) {
|
|
2285
|
+
console.error(color("Error: Input and output files required", "red"));
|
|
2286
|
+
console.log(
|
|
2287
|
+
color("Usage: jtcsv json-to-ndjson input.json output.ndjson", "cyan"),
|
|
2288
|
+
);
|
|
2289
|
+
process.exit(1);
|
|
2290
|
+
}
|
|
2291
|
+
await convertJsonToNdjson(files[0], files[1], options);
|
|
2292
|
+
break;
|
|
2293
|
+
|
|
2294
|
+
// Unwrap/Flatten command
|
|
2295
|
+
case "unwrap":
|
|
2296
|
+
case "flatten":
|
|
2297
|
+
if (files.length < 2) {
|
|
2298
|
+
console.error(color("Error: Input and output files required", "red"));
|
|
2299
|
+
console.log(
|
|
2300
|
+
color("Usage: jtcsv unwrap input.json output.json", "cyan"),
|
|
2301
|
+
);
|
|
2302
|
+
process.exit(1);
|
|
2303
|
+
}
|
|
2304
|
+
await unwrapJson(files[0], files[1], options);
|
|
2305
|
+
break;
|
|
2306
|
+
|
|
2307
|
+
// Streaming commands
|
|
2308
|
+
case "stream":
|
|
2309
|
+
if (args.length < 2) {
|
|
2310
|
+
console.error(
|
|
2311
|
+
color("Error: Streaming mode requires subcommand", "red"),
|
|
2312
|
+
);
|
|
2313
|
+
console.log(
|
|
2314
|
+
color(
|
|
2315
|
+
"Usage: jtcsv stream [json-to-csv|csv-to-json|file-to-csv|file-to-json]",
|
|
2316
|
+
"cyan",
|
|
2317
|
+
),
|
|
2318
|
+
);
|
|
2319
|
+
process.exit(1);
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
const streamCommand = args[1].toLowerCase();
|
|
2323
|
+
// Для stream команд нужно парсить опции начиная с 3-го аргумента (после stream и подкоманды)
|
|
2324
|
+
const streamArgs = args.slice(2);
|
|
2325
|
+
const { options: streamOptions, files: streamFiles } =
|
|
2326
|
+
parseOptions(streamArgs);
|
|
2327
|
+
|
|
2328
|
+
if (streamCommand === "json-to-csv" && streamFiles.length >= 2) {
|
|
2329
|
+
await streamJsonToCsv(streamFiles[0], streamFiles[1], streamOptions);
|
|
2330
|
+
} else if (streamCommand === "csv-to-json" && streamFiles.length >= 2) {
|
|
2331
|
+
await streamCsvToJson(streamFiles[0], streamFiles[1], streamOptions);
|
|
2332
|
+
} else if (streamCommand === "file-to-csv" && streamFiles.length >= 2) {
|
|
2333
|
+
// Use jtcsv streaming API if available
|
|
2334
|
+
try {
|
|
2335
|
+
const readStream = fs.createReadStream(streamFiles[0], "utf8");
|
|
2336
|
+
const writeStream = fs.createWriteStream(streamFiles[1], "utf8");
|
|
2337
|
+
|
|
2338
|
+
if (streamOptions.addBOM) {
|
|
2339
|
+
writeStream.write("\uFEFF");
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
const transformStream = jtcsv.createJsonToCsvStream(streamOptions);
|
|
2343
|
+
await pipeline(readStream, transformStream, writeStream);
|
|
2344
|
+
|
|
2345
|
+
console.log(color("✓ File streamed successfully", "green"));
|
|
2346
|
+
} catch (error) {
|
|
2347
|
+
console.error(color(`✗ Streaming error: ${error.message}`, "red"));
|
|
2348
|
+
process.exit(1);
|
|
2349
|
+
}
|
|
2350
|
+
} else if (streamCommand === "file-to-json" && streamFiles.length >= 2) {
|
|
2351
|
+
// Use jtcsv streaming API if available
|
|
2352
|
+
try {
|
|
2353
|
+
const readStream = fs.createReadStream(streamFiles[0], "utf8");
|
|
2354
|
+
const writeStream = fs.createWriteStream(streamFiles[1], "utf8");
|
|
2355
|
+
|
|
2356
|
+
const transformStream = jtcsv.createCsvToJsonStream(streamOptions);
|
|
2357
|
+
await pipeline(readStream, transformStream, writeStream);
|
|
2358
|
+
|
|
2359
|
+
console.log(color("✓ File streamed successfully", "green"));
|
|
2360
|
+
} catch (error) {
|
|
2361
|
+
console.error(color(`✗ Streaming error: ${error.message}`, "red"));
|
|
2362
|
+
process.exit(1);
|
|
2363
|
+
}
|
|
2364
|
+
} else {
|
|
2365
|
+
console.error(
|
|
2366
|
+
color("Error: Invalid streaming command or missing files", "red"),
|
|
2367
|
+
);
|
|
2368
|
+
process.exit(1);
|
|
2369
|
+
}
|
|
2370
|
+
break;
|
|
2371
|
+
|
|
2372
|
+
// Batch processing commands
|
|
2373
|
+
case "batch":
|
|
2374
|
+
if (args.length < 2) {
|
|
2375
|
+
console.error(color("Error: Batch mode requires subcommand", "red"));
|
|
2376
|
+
console.log(
|
|
2377
|
+
color("Usage: jtcsv batch [json-to-csv|csv-to-json|process]", "cyan"),
|
|
2378
|
+
);
|
|
2379
|
+
process.exit(1);
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2382
|
+
const batchCommand = args[1].toLowerCase();
|
|
2383
|
+
// Для batch команд нужно парсить опции начиная с 3-го аргумента (после batch и подкоманды)
|
|
2384
|
+
const batchArgs = args.slice(2);
|
|
2385
|
+
const { options: batchOptions, files: batchFiles } =
|
|
2386
|
+
parseOptions(batchArgs);
|
|
2387
|
+
|
|
2388
|
+
if (batchCommand === "json-to-csv" && batchFiles.length >= 2) {
|
|
2389
|
+
await batchJsonToCsv(batchFiles[0], batchFiles[1], batchOptions);
|
|
2390
|
+
} else if (batchCommand === "csv-to-json" && batchFiles.length >= 2) {
|
|
2391
|
+
await batchCsvToJson(batchFiles[0], batchFiles[1], batchOptions);
|
|
2392
|
+
} else if (batchCommand === "process" && files.length >= 2) {
|
|
2393
|
+
await batchProcessMixed(files[0], files[1], options);
|
|
2394
|
+
} else {
|
|
2395
|
+
console.error(
|
|
2396
|
+
color("Error: Invalid batch command or missing files", "red"),
|
|
2397
|
+
);
|
|
2398
|
+
process.exit(1);
|
|
2399
|
+
}
|
|
2400
|
+
break;
|
|
2401
|
+
|
|
2402
|
+
// TUI command
|
|
2403
|
+
case "tui":
|
|
2404
|
+
await launchTUI();
|
|
2405
|
+
break;
|
|
2406
|
+
|
|
2407
|
+
// Web UI command
|
|
2408
|
+
case "web":
|
|
2409
|
+
await launchWebUI(options);
|
|
2410
|
+
break;
|
|
2411
|
+
|
|
2412
|
+
// Help and version
|
|
2413
|
+
case "help":
|
|
2414
|
+
case "--help":
|
|
2415
|
+
case "-h":
|
|
2416
|
+
showHelp();
|
|
2417
|
+
break;
|
|
2418
|
+
|
|
2419
|
+
case "version":
|
|
2420
|
+
case "-v":
|
|
2421
|
+
case "--version":
|
|
2422
|
+
showVersion();
|
|
2423
|
+
break;
|
|
2424
|
+
|
|
2425
|
+
default:
|
|
2426
|
+
console.error(color(`Error: Unknown command '${command}'`, "red"));
|
|
2427
|
+
console.log(color("Use jtcsv help for available commands", "cyan"));
|
|
2428
|
+
process.exit(1);
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
// ============================================================================
|
|
2433
|
+
// ERROR HANDLING
|
|
2434
|
+
// ============================================================================
|
|
2435
|
+
|
|
2436
|
+
process.on("uncaughtException", (error) => {
|
|
2437
|
+
console.error(color(`\n✗ Uncaught error: ${error.message}`, "red"));
|
|
2438
|
+
if (process.env.DEBUG) {
|
|
2439
|
+
console.error(error.stack);
|
|
2440
|
+
}
|
|
2441
|
+
process.exit(1);
|
|
2442
|
+
});
|
|
2443
|
+
|
|
2444
|
+
process.on("unhandledRejection", (error) => {
|
|
2445
|
+
console.error(
|
|
2446
|
+
color(`\n✗ Unhandled promise rejection: ${error.message}`, "red"),
|
|
2447
|
+
);
|
|
2448
|
+
if (process.env.DEBUG) {
|
|
2449
|
+
console.error(error.stack);
|
|
2450
|
+
}
|
|
2451
|
+
process.exit(1);
|
|
2452
|
+
});
|
|
2453
|
+
|
|
2454
|
+
// Run main function
|
|
2455
|
+
if (require.main === module) {
|
|
2456
|
+
main().catch((error) => {
|
|
2457
|
+
console.error(color(`\n✗ Fatal error: ${error.message}`, "red"));
|
|
2458
|
+
process.exit(1);
|
|
2459
|
+
});
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
module.exports = { main };
|