jtcsv 2.2.8 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/README.md +204 -115
  2. package/bin/jtcsv.ts +2612 -0
  3. package/browser.d.ts +142 -0
  4. package/dist/benchmark.js +446 -0
  5. package/dist/benchmark.js.map +1 -0
  6. package/dist/bin/jtcsv.js +1940 -0
  7. package/dist/bin/jtcsv.js.map +1 -0
  8. package/dist/csv-to-json.js +1262 -0
  9. package/dist/csv-to-json.js.map +1 -0
  10. package/dist/errors.js +291 -0
  11. package/dist/errors.js.map +1 -0
  12. package/dist/eslint.config.js +147 -0
  13. package/dist/eslint.config.js.map +1 -0
  14. package/dist/index-core.js +95 -0
  15. package/dist/index-core.js.map +1 -0
  16. package/dist/index.js +93 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/json-save.js +229 -0
  19. package/dist/json-save.js.map +1 -0
  20. package/dist/json-to-csv.js +576 -0
  21. package/dist/json-to-csv.js.map +1 -0
  22. package/dist/jtcsv-core.cjs.js +1736 -0
  23. package/dist/jtcsv-core.cjs.js.map +1 -0
  24. package/dist/jtcsv-core.esm.js +1708 -0
  25. package/dist/jtcsv-core.esm.js.map +1 -0
  26. package/dist/jtcsv-core.umd.js +1742 -0
  27. package/dist/jtcsv-core.umd.js.map +1 -0
  28. package/dist/jtcsv-full.cjs.js +2241 -0
  29. package/dist/jtcsv-full.cjs.js.map +1 -0
  30. package/dist/jtcsv-full.esm.js +2209 -0
  31. package/dist/jtcsv-full.esm.js.map +1 -0
  32. package/dist/jtcsv-full.umd.js +2247 -0
  33. package/dist/jtcsv-full.umd.js.map +1 -0
  34. package/dist/jtcsv-workers.esm.js +768 -0
  35. package/dist/jtcsv-workers.esm.js.map +1 -0
  36. package/dist/jtcsv-workers.umd.js +782 -0
  37. package/dist/jtcsv-workers.umd.js.map +1 -0
  38. package/dist/jtcsv.cjs.js +1996 -2048
  39. package/dist/jtcsv.cjs.js.map +1 -1
  40. package/dist/jtcsv.esm.js +1992 -2048
  41. package/dist/jtcsv.esm.js.map +1 -1
  42. package/dist/jtcsv.umd.js +2157 -2209
  43. package/dist/jtcsv.umd.js.map +1 -1
  44. package/dist/plugins/express-middleware/index.js +350 -0
  45. package/dist/plugins/express-middleware/index.js.map +1 -0
  46. package/dist/plugins/fastify-plugin/index.js +315 -0
  47. package/dist/plugins/fastify-plugin/index.js.map +1 -0
  48. package/dist/plugins/hono/index.js +111 -0
  49. package/dist/plugins/hono/index.js.map +1 -0
  50. package/dist/plugins/nestjs/index.js +112 -0
  51. package/dist/plugins/nestjs/index.js.map +1 -0
  52. package/dist/plugins/nuxt/index.js +53 -0
  53. package/dist/plugins/nuxt/index.js.map +1 -0
  54. package/dist/plugins/remix/index.js +133 -0
  55. package/dist/plugins/remix/index.js.map +1 -0
  56. package/dist/plugins/sveltekit/index.js +155 -0
  57. package/dist/plugins/sveltekit/index.js.map +1 -0
  58. package/dist/plugins/trpc/index.js +136 -0
  59. package/dist/plugins/trpc/index.js.map +1 -0
  60. package/dist/run-demo.js +49 -0
  61. package/dist/run-demo.js.map +1 -0
  62. package/dist/src/browser/browser-functions.js +193 -0
  63. package/dist/src/browser/browser-functions.js.map +1 -0
  64. package/dist/src/browser/core.js +123 -0
  65. package/dist/src/browser/core.js.map +1 -0
  66. package/dist/src/browser/csv-to-json-browser.js +353 -0
  67. package/dist/src/browser/csv-to-json-browser.js.map +1 -0
  68. package/dist/src/browser/errors-browser.js +219 -0
  69. package/dist/src/browser/errors-browser.js.map +1 -0
  70. package/dist/src/browser/extensions/plugins.js +106 -0
  71. package/dist/src/browser/extensions/plugins.js.map +1 -0
  72. package/dist/src/browser/extensions/workers.js +66 -0
  73. package/dist/src/browser/extensions/workers.js.map +1 -0
  74. package/dist/src/browser/index.js +140 -0
  75. package/dist/src/browser/index.js.map +1 -0
  76. package/dist/src/browser/json-to-csv-browser.js +225 -0
  77. package/dist/src/browser/json-to-csv-browser.js.map +1 -0
  78. package/dist/src/browser/streams.js +340 -0
  79. package/dist/src/browser/streams.js.map +1 -0
  80. package/dist/src/browser/workers/csv-parser.worker.js +264 -0
  81. package/dist/src/browser/workers/csv-parser.worker.js.map +1 -0
  82. package/dist/src/browser/workers/worker-pool.js +338 -0
  83. package/dist/src/browser/workers/worker-pool.js.map +1 -0
  84. package/dist/src/core/delimiter-cache.js +196 -0
  85. package/dist/src/core/delimiter-cache.js.map +1 -0
  86. package/dist/src/core/node-optimizations.js +279 -0
  87. package/dist/src/core/node-optimizations.js.map +1 -0
  88. package/dist/src/core/plugin-system.js +399 -0
  89. package/dist/src/core/plugin-system.js.map +1 -0
  90. package/dist/src/core/transform-hooks.js +348 -0
  91. package/dist/src/core/transform-hooks.js.map +1 -0
  92. package/dist/src/engines/fast-path-engine-new.js +262 -0
  93. package/dist/src/engines/fast-path-engine-new.js.map +1 -0
  94. package/dist/src/engines/fast-path-engine.js +671 -0
  95. package/dist/src/engines/fast-path-engine.js.map +1 -0
  96. package/dist/src/errors.js +18 -0
  97. package/dist/src/errors.js.map +1 -0
  98. package/dist/src/formats/ndjson-parser.js +332 -0
  99. package/dist/src/formats/ndjson-parser.js.map +1 -0
  100. package/dist/src/formats/tsv-parser.js +230 -0
  101. package/dist/src/formats/tsv-parser.js.map +1 -0
  102. package/dist/src/index-with-plugins.js +259 -0
  103. package/dist/src/index-with-plugins.js.map +1 -0
  104. package/dist/src/types/index.js +3 -0
  105. package/dist/src/types/index.js.map +1 -0
  106. package/dist/src/utils/bom-utils.js +267 -0
  107. package/dist/src/utils/bom-utils.js.map +1 -0
  108. package/dist/src/utils/encoding-support.js +77 -0
  109. package/dist/src/utils/encoding-support.js.map +1 -0
  110. package/dist/src/utils/schema-validator.js +609 -0
  111. package/dist/src/utils/schema-validator.js.map +1 -0
  112. package/dist/src/utils/transform-loader.js +281 -0
  113. package/dist/src/utils/transform-loader.js.map +1 -0
  114. package/dist/src/utils/validators.js +40 -0
  115. package/dist/src/utils/validators.js.map +1 -0
  116. package/dist/src/utils/zod-adapter.js +144 -0
  117. package/dist/src/utils/zod-adapter.js.map +1 -0
  118. package/dist/src/web-server/index.js +648 -0
  119. package/dist/src/web-server/index.js.map +1 -0
  120. package/dist/src/workers/csv-multithreaded.js +211 -0
  121. package/dist/src/workers/csv-multithreaded.js.map +1 -0
  122. package/dist/src/workers/csv-parser.worker.js +179 -0
  123. package/dist/src/workers/csv-parser.worker.js.map +1 -0
  124. package/dist/src/workers/worker-pool.js +228 -0
  125. package/dist/src/workers/worker-pool.js.map +1 -0
  126. package/dist/stream-csv-to-json.js +665 -0
  127. package/dist/stream-csv-to-json.js.map +1 -0
  128. package/dist/stream-json-to-csv.js +389 -0
  129. package/dist/stream-json-to-csv.js.map +1 -0
  130. package/examples/advanced/conditional-transformations.ts +446 -0
  131. package/examples/advanced/csv-parser.worker.ts +89 -0
  132. package/examples/advanced/nested-objects-example.ts +306 -0
  133. package/examples/advanced/performance-optimization.ts +504 -0
  134. package/examples/advanced/run-demo-server.ts +116 -0
  135. package/examples/advanced/web-worker-usage.html +874 -0
  136. package/examples/async-multithreaded-example.ts +335 -0
  137. package/examples/cli-advanced-usage.md +290 -0
  138. package/examples/{cli-batch-processing.js → cli-batch-processing.ts} +38 -38
  139. package/examples/{cli-tool.js → cli-tool.ts} +5 -8
  140. package/examples/{error-handling.js → error-handling.ts} +356 -324
  141. package/examples/{express-api.js → express-api.ts} +161 -164
  142. package/examples/{large-dataset-example.js → large-dataset-example.ts} +201 -182
  143. package/examples/{ndjson-processing.js → ndjson-processing.ts} +456 -434
  144. package/examples/{plugin-excel-exporter.js → plugin-excel-exporter.ts} +6 -7
  145. package/examples/react-integration.tsx +637 -0
  146. package/examples/{schema-validation.js → schema-validation.ts} +2 -2
  147. package/examples/simple-usage.ts +194 -0
  148. package/examples/{streaming-example.js → streaming-example.ts} +12 -12
  149. package/index.d.ts +187 -18
  150. package/package.json +75 -81
  151. package/plugins.d.ts +37 -0
  152. package/schema.d.ts +103 -0
  153. package/src/browser/browser-functions.ts +402 -0
  154. package/src/browser/core.ts +152 -0
  155. package/src/browser/csv-to-json-browser.d.ts +3 -0
  156. package/src/browser/csv-to-json-browser.ts +494 -0
  157. package/src/browser/{errors-browser.js → errors-browser.ts} +305 -197
  158. package/src/browser/extensions/plugins.ts +93 -0
  159. package/src/browser/extensions/workers.ts +39 -0
  160. package/src/browser/globals.d.ts +5 -0
  161. package/src/browser/index.ts +192 -0
  162. package/src/browser/json-to-csv-browser.d.ts +3 -0
  163. package/src/browser/json-to-csv-browser.ts +338 -0
  164. package/src/browser/streams.ts +403 -0
  165. package/src/browser/workers/{csv-parser.worker.js → csv-parser.worker.ts} +3 -3
  166. package/src/browser/workers/{worker-pool.js → worker-pool.ts} +51 -30
  167. package/src/core/delimiter-cache.ts +320 -0
  168. package/src/core/{node-optimizations.js → node-optimizations.ts} +448 -407
  169. package/src/core/plugin-system.ts +588 -0
  170. package/src/core/transform-hooks.ts +566 -0
  171. package/src/engines/{fast-path-engine-new.js → fast-path-engine-new.ts} +11 -2
  172. package/src/engines/{fast-path-engine.js → fast-path-engine.ts} +79 -53
  173. package/src/errors.ts +1 -0
  174. package/src/formats/{ndjson-parser.js → ndjson-parser.ts} +24 -16
  175. package/src/formats/{tsv-parser.js → tsv-parser.ts} +18 -17
  176. package/src/{index-with-plugins.js → index-with-plugins.ts} +381 -357
  177. package/src/types/index.ts +275 -0
  178. package/src/utils/bom-utils.ts +373 -0
  179. package/src/utils/encoding-support.ts +155 -0
  180. package/src/utils/{schema-validator.js → schema-validator.ts} +814 -589
  181. package/src/utils/transform-loader.ts +389 -0
  182. package/src/utils/validators.ts +35 -0
  183. package/src/utils/zod-adapter.ts +280 -0
  184. package/src/web-server/{index.js → index.ts} +19 -19
  185. package/src/workers/csv-multithreaded.ts +310 -0
  186. package/src/workers/csv-parser.worker.ts +227 -0
  187. package/src/workers/worker-pool.ts +409 -0
  188. package/bin/jtcsv.js +0 -2462
  189. package/csv-to-json.js +0 -688
  190. package/errors.js +0 -208
  191. package/examples/simple-usage.js +0 -282
  192. package/index.js +0 -68
  193. package/json-save.js +0 -254
  194. package/json-to-csv.js +0 -526
  195. package/plugins/README.md +0 -91
  196. package/plugins/express-middleware/README.md +0 -64
  197. package/plugins/express-middleware/example.js +0 -136
  198. package/plugins/express-middleware/index.d.ts +0 -114
  199. package/plugins/express-middleware/index.js +0 -360
  200. package/plugins/express-middleware/package.json +0 -52
  201. package/plugins/fastify-plugin/index.js +0 -406
  202. package/plugins/fastify-plugin/package.json +0 -55
  203. package/plugins/hono/README.md +0 -28
  204. package/plugins/hono/index.d.ts +0 -12
  205. package/plugins/hono/index.js +0 -36
  206. package/plugins/hono/package.json +0 -35
  207. package/plugins/nestjs/README.md +0 -35
  208. package/plugins/nestjs/index.d.ts +0 -25
  209. package/plugins/nestjs/index.js +0 -77
  210. package/plugins/nestjs/package.json +0 -37
  211. package/plugins/nextjs-api/README.md +0 -57
  212. package/plugins/nextjs-api/examples/ConverterComponent.jsx +0 -386
  213. package/plugins/nextjs-api/examples/api-convert.js +0 -69
  214. package/plugins/nextjs-api/index.js +0 -387
  215. package/plugins/nextjs-api/package.json +0 -63
  216. package/plugins/nextjs-api/route.js +0 -371
  217. package/plugins/nuxt/README.md +0 -24
  218. package/plugins/nuxt/index.js +0 -21
  219. package/plugins/nuxt/package.json +0 -35
  220. package/plugins/nuxt/runtime/composables/useJtcsv.js +0 -6
  221. package/plugins/nuxt/runtime/plugin.js +0 -6
  222. package/plugins/remix/README.md +0 -26
  223. package/plugins/remix/index.d.ts +0 -16
  224. package/plugins/remix/index.js +0 -62
  225. package/plugins/remix/package.json +0 -35
  226. package/plugins/sveltekit/README.md +0 -28
  227. package/plugins/sveltekit/index.d.ts +0 -17
  228. package/plugins/sveltekit/index.js +0 -54
  229. package/plugins/sveltekit/package.json +0 -33
  230. package/plugins/trpc/README.md +0 -25
  231. package/plugins/trpc/index.d.ts +0 -7
  232. package/plugins/trpc/index.js +0 -32
  233. package/plugins/trpc/package.json +0 -34
  234. package/src/browser/browser-functions.js +0 -219
  235. package/src/browser/csv-to-json-browser.js +0 -700
  236. package/src/browser/index.js +0 -113
  237. package/src/browser/json-to-csv-browser.js +0 -309
  238. package/src/browser/streams.js +0 -393
  239. package/src/core/delimiter-cache.js +0 -186
  240. package/src/core/plugin-system.js +0 -476
  241. package/src/core/transform-hooks.js +0 -350
  242. package/src/errors.js +0 -26
  243. package/src/utils/transform-loader.js +0 -205
  244. package/stream-csv-to-json.js +0 -542
  245. package/stream-json-to-csv.js +0 -464
  246. /package/examples/{web-workers-advanced.js → web-workers-advanced.ts} +0 -0
@@ -0,0 +1,1940 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const readline = __importStar(require("readline"));
40
+ const { pipeline } = require('stream/promises');
41
+ const jtcsv = __importStar(require("../index"));
42
+ const transformLoader = require('../src/utils/transform-loader');
43
+ const schemaValidator = require('../src/utils/schema-validator');
44
+ const VERSION = require('../package.json').version;
45
+ async function applyTransform(data, transformFile) {
46
+ try {
47
+ const transformPath = path.resolve(process.cwd(), transformFile);
48
+ const transformModule = require(transformPath);
49
+ if (typeof transformModule === 'function') {
50
+ return transformModule(data);
51
+ }
52
+ else if (typeof transformModule.default === 'function') {
53
+ return transformModule.default(data);
54
+ }
55
+ else if (typeof transformModule.transform === 'function') {
56
+ return transformModule.transform(data);
57
+ }
58
+ else {
59
+ throw new Error(`Transform file must export a function. Found: ${typeof transformModule}`);
60
+ }
61
+ }
62
+ catch (error) {
63
+ throw new Error(`Failed to apply transform from ${transformFile}: ${error.message}`);
64
+ }
65
+ }
66
+ const colors = {
67
+ reset: '\x1b[0m',
68
+ bright: '\x1b[1m',
69
+ dim: '\x1b[2m',
70
+ red: '\x1b[31m',
71
+ green: '\x1b[32m',
72
+ yellow: '\x1b[33m',
73
+ blue: '\x1b[34m',
74
+ magenta: '\x1b[35m',
75
+ cyan: '\x1b[36m',
76
+ white: '\x1b[37m'
77
+ };
78
+ function color(text, colorName) {
79
+ return colors[colorName] + text + colors.reset;
80
+ }
81
+ function showHelp() {
82
+ console.log(`
83
+ ${color('jtcsv CLI v' + VERSION, 'cyan')}
84
+ ${color('The Complete JSON<->CSV Converter for Node.js', 'dim')}
85
+
86
+ ${color('USAGE:', 'bright')}
87
+ jtcsv [command] [options] [file...]
88
+
89
+ ${color('MAIN COMMANDS:', 'bright')}
90
+ ${color('json-to-csv', 'green')} Convert JSON to CSV (alias: json2csv)
91
+ ${color('csv-to-json', 'green')} Convert CSV to JSON (alias: csv2json)
92
+ ${color('ndjson-to-csv', 'green')} Convert NDJSON to CSV
93
+ ${color('csv-to-ndjson', 'green')} Convert CSV to NDJSON
94
+ ${color('ndjson-to-json', 'green')} Convert NDJSON to JSON array
95
+ ${color('json-to-ndjson', 'green')} Convert JSON array to NDJSON
96
+ ${color('save-json', 'yellow')} Save data as JSON file
97
+ ${color('save-csv', 'yellow')} Save data as CSV file
98
+ ${color('stream', 'yellow')} Streaming conversion for large files
99
+ ${color('batch', 'yellow')} Batch process multiple files
100
+ ${color('preprocess', 'magenta')} Preprocess JSON with deep unwrapping
101
+ ${color('unwrap', 'magenta')} Flatten nested JSON structures (alias: flatten)
102
+ ${color('tui', 'magenta')} Launch Terminal User Interface (@jtcsv/tui)
103
+ ${color('web', 'magenta')} Launch Web Interface (http://localhost:3000)
104
+ ${color('help', 'blue')} Show this help message
105
+ ${color('version', 'blue')} Show version information
106
+
107
+ ${color('STREAMING SUBCOMMANDS:', 'bright')}
108
+ ${color('stream json-to-csv', 'dim')} Stream JSON to CSV
109
+ ${color('stream csv-to-json', 'dim')} Stream CSV to JSON
110
+ ${color('stream file-to-csv', 'dim')} Stream file to CSV
111
+ ${color('stream file-to-json', 'dim')} Stream file to JSON
112
+
113
+ ${color('BATCH SUBCOMMANDS:', 'bright')}
114
+ ${color('batch json-to-csv', 'dim')} Batch convert JSON files to CSV
115
+ ${color('batch csv-to-json', 'dim')} Batch convert CSV files to JSON
116
+ ${color('batch process', 'dim')} Process mixed file types
117
+
118
+ ${color('EXAMPLES:', 'bright')}
119
+ ${color('Convert JSON file to CSV:', 'dim')}
120
+ jtcsv json-to-csv input.json output.csv --delimiter=,
121
+
122
+ ${color('Convert CSV file to JSON:', 'dim')}
123
+ jtcsv csv-to-json input.csv output.json --parse-numbers --auto-detect
124
+
125
+ ${color('Save data as JSON file:', 'dim')}
126
+ jtcsv save-json data.json output.json --pretty
127
+
128
+ ${color('Save data as CSV file:', 'dim')}
129
+ jtcsv save-csv data.csv output.csv --delimiter=, --transform=transform.js
130
+
131
+ ${color('Stream large JSON file to CSV:', 'dim')}
132
+ jtcsv stream json-to-csv large.json output.csv --max-records=1000000
133
+
134
+ ${color('Stream CSV file to JSON:', 'dim')}
135
+ jtcsv stream csv-to-json large.csv output.json --max-rows=500000
136
+
137
+ ${color('Preprocess complex JSON:', 'dim')}
138
+ jtcsv preprocess complex.json simplified.json --max-depth=3
139
+
140
+ ${color('Batch convert JSON files:', 'dim')}
141
+ jtcsv batch json-to-csv "data/*.json" "output/" --delimiter=;
142
+
143
+ ${color('Launch TUI interface:', 'dim')}
144
+ jtcsv tui
145
+
146
+ ${color('Launch Web interface:', 'dim')}
147
+ jtcsv web --port=3000
148
+
149
+ ${color('CONVERSION OPTIONS:', 'bright')}
150
+ ${color('--delimiter=', 'cyan')}CHAR CSV delimiter (default: ;)
151
+ ${color('--auto-detect', 'cyan')} Auto-detect delimiter (default: true)
152
+ ${color('--candidates=', 'cyan')}LIST Delimiter candidates (default: ;,\t|)
153
+ ${color('--no-headers', 'cyan')} Exclude headers from CSV output
154
+ ${color('--parse-numbers', 'cyan')} Parse numeric values in CSV
155
+ ${color('--parse-booleans', 'cyan')} Parse boolean values in CSV
156
+ ${color('--no-trim', 'cyan')} Don't trim whitespace from CSV values
157
+ ${color('--no-fast-path', 'cyan')} Disable fast-path parser (force quote-aware)
158
+ ${color('--fast-path-mode=', 'cyan')}MODE Fast path output mode (objects|compact)
159
+ ${color('--repair-row-shifts', 'cyan')} Repair shifted rows with trailing empty fields
160
+ ${color('--normalize-quotes', 'cyan')} Normalize excessive quotes in parsed fields
161
+ ${color('--rename=', 'cyan')}JSON Rename columns (JSON map)
162
+ ${color('--template=', 'cyan')}JSON Column order template (JSON object)
163
+ ${color('--no-injection-protection', 'cyan')} Disable CSV injection protection
164
+ ${color('--no-rfc4180', 'cyan')} Disable RFC 4180 compliance
165
+ ${color('--max-records=', 'cyan')}N Maximum records to process
166
+ ${color('--max-rows=', 'cyan')}N Maximum rows to process
167
+ ${color('--pretty', 'cyan')} Pretty print JSON output
168
+ ${color('--schema=', 'cyan')}JSON JSON schema for validation and formatting
169
+ ${color('--transform=', 'cyan')}JS Custom transform function (JavaScript file)
170
+ ${color('PREPROCESS OPTIONS:', 'bright')}
171
+ ${color('--max-depth=', 'cyan')}N Maximum recursion depth (default: 5)
172
+ ${color('--flatten', 'cyan')} Flatten nested objects into dot notation
173
+ ${color('--flatten-separator=', 'cyan')}CHAR Separator for flattened keys (default: .)
174
+ ${color('--flatten-max-depth=', 'cyan')}N Maximum flattening depth (default: 3)
175
+ ${color('--array-handling=', 'cyan')}MODE Array handling: stringify|join|expand (default: stringify)
176
+ ${color('--unwrap-arrays', 'cyan')} Unwrap arrays to strings
177
+ ${color('--stringify-objects', 'cyan')} Stringify complex objects
178
+ ${color('STREAMING OPTIONS:', 'bright')}
179
+ ${color('--chunk-size=', 'cyan')}N Chunk size in bytes (default: 65536)
180
+ ${color('--buffer-size=', 'cyan')}N Buffer size in records (default: 1000)
181
+ ${color('--add-bom', 'cyan')} Add UTF-8 BOM for Excel compatibility
182
+ ${color('BATCH OPTIONS:', 'bright')}
183
+ ${color('--recursive', 'cyan')} Process directories recursively
184
+ ${color('--pattern=', 'cyan')}GLOB File pattern to match
185
+ ${color('--output-dir=', 'cyan')}DIR Output directory for batch processing
186
+ ${color('--overwrite', 'cyan')} Overwrite existing files
187
+ ${color('--parallel=', 'cyan')}N Parallel processing limit (default: 4)
188
+ ${color('GENERAL OPTIONS:', 'bright')}
189
+ ${color('--silent', 'cyan')} Suppress all output except errors
190
+ ${color('--verbose', 'cyan')} Show detailed progress information
191
+ ${color('--debug', 'cyan')} Show debug information
192
+ ${color('--dry-run', 'cyan')} Show what would be done without actually doing it
193
+ ${color('SECURITY FEATURES:', 'bright')}
194
+ - CSV injection protection (enabled by default)
195
+ - Path traversal protection
196
+ - Input validation and sanitization
197
+ - Size limits to prevent DoS attacks
198
+ - Schema validation support
199
+
200
+ ${color('PERFORMANCE FEATURES:', 'bright')}
201
+ - Streaming for files >100MB
202
+ - Batch processing with parallel execution
203
+ - Memory-efficient preprocessing
204
+ - Configurable buffer sizes
205
+
206
+ ${color('LEARN MORE:', 'dim')}
207
+ GitHub: https://github.com/Linol-Hamelton/jtcsv
208
+ Issues: https://github.com/Linol-Hamelton/jtcsv/issues
209
+ Documentation: https://github.com/Linol-Hamelton/jtcsv#readme
210
+ `);
211
+ }
212
+ function showVersion() {
213
+ console.log(`jtcsv v${VERSION}`);
214
+ console.log(`Node.js ${process.version}`);
215
+ console.log(`Platform: ${process.platform} ${process.arch}`);
216
+ }
217
+ async function readStdin() {
218
+ return new Promise((resolve, reject) => {
219
+ let data = '';
220
+ process.stdin.setEncoding('utf8');
221
+ process.stdin.on('data', (chunk) => {
222
+ data += chunk;
223
+ });
224
+ process.stdin.on('end', () => resolve(data));
225
+ process.stdin.on('error', reject);
226
+ });
227
+ }
228
+ async function convertJsonToCsv(inputFile, outputFile, options) {
229
+ const startTime = Date.now();
230
+ const useStdin = inputFile === '-';
231
+ const useStdout = outputFile === '-';
232
+ const shouldLog = !options.silent && !useStdout;
233
+ try {
234
+ const inputData = useStdin
235
+ ? await readStdin()
236
+ : await fs.promises.readFile(inputFile, 'utf8');
237
+ const jsonData = JSON.parse(inputData);
238
+ if (!Array.isArray(jsonData)) {
239
+ throw new Error('JSON data must be an array of objects');
240
+ }
241
+ if (shouldLog) {
242
+ console.log(color(`Converting ${jsonData.length.toLocaleString()} records...`, 'dim'));
243
+ }
244
+ if (options.transform) {
245
+ if (shouldLog) {
246
+ console.log(color(`Applying transform from: ${options.transform}`, 'dim'));
247
+ }
248
+ let transformedData;
249
+ try {
250
+ transformedData = transformLoader.applyTransform(jsonData, options.transform);
251
+ if (shouldLog) {
252
+ console.log(color(`✓ Transform applied to ${transformedData.length} records`, 'green'));
253
+ }
254
+ }
255
+ catch (transformError) {
256
+ console.error(color(`✗ Transform error: ${transformError.message}`, 'red'));
257
+ if (options.debug) {
258
+ console.error(transformError.stack);
259
+ }
260
+ process.exit(1);
261
+ }
262
+ }
263
+ const jtcsvOptions = {
264
+ delimiter: options.delimiter,
265
+ includeHeaders: options.includeHeaders,
266
+ renameMap: options.renameMap,
267
+ template: options.template,
268
+ maxRecords: options.maxRecords,
269
+ preventCsvInjection: options.preventCsvInjection,
270
+ rfc4180Compliant: options.rfc4180Compliant,
271
+ normalizeQuotes: options.normalizeQuotes,
272
+ schema: options.schema,
273
+ flatten: options.flatten,
274
+ flattenSeparator: options.flattenSeparator,
275
+ flattenMaxDepth: options.flattenMaxDepth,
276
+ arrayHandling: options.arrayHandling
277
+ };
278
+ let transformedData = jsonData;
279
+ if (options.transform) {
280
+ transformedData = await applyTransform(jsonData, options.transform);
281
+ }
282
+ const csvData = jtcsv.jsonToCsv(transformedData, jtcsvOptions);
283
+ if (useStdout) {
284
+ process.stdout.write(csvData);
285
+ }
286
+ else {
287
+ await fs.promises.writeFile(outputFile, csvData, 'utf8');
288
+ }
289
+ const elapsed = Date.now() - startTime;
290
+ if (shouldLog) {
291
+ console.log(color(`✓ Converted ${transformedData.length.toLocaleString()} records in ${elapsed}ms`, 'green'));
292
+ if (!useStdout) {
293
+ console.log(color(` Output: ${outputFile} (${csvData.length.toLocaleString()} bytes)`, 'dim'));
294
+ }
295
+ }
296
+ return {
297
+ records: transformedData.length,
298
+ bytes: csvData.length,
299
+ time: elapsed
300
+ };
301
+ }
302
+ catch (error) {
303
+ console.error(color(`✗ Error: ${error.message}`, 'red'));
304
+ if (options.debug) {
305
+ console.error(error.stack);
306
+ }
307
+ process.exit(1);
308
+ }
309
+ }
310
+ async function convertCsvToJson(inputFile, outputFile, options) {
311
+ const startTime = Date.now();
312
+ const useStdin = inputFile === '-';
313
+ const useStdout = outputFile === '-';
314
+ const shouldLog = !options.silent && !useStdout;
315
+ try {
316
+ if (shouldLog) {
317
+ console.log(color('Reading CSV file...', 'dim'));
318
+ }
319
+ const jtcsvOptions = {
320
+ delimiter: options.delimiter,
321
+ autoDetect: options.autoDetect,
322
+ candidates: options.candidates,
323
+ hasHeaders: options.hasHeaders,
324
+ renameMap: options.renameMap,
325
+ trim: options.trim,
326
+ parseNumbers: options.parseNumbers,
327
+ parseBooleans: options.parseBooleans,
328
+ maxRows: options.maxRows,
329
+ useFastPath: options.useFastPath,
330
+ fastPathMode: options.fastPathMode,
331
+ repairRowShifts: options.repairRowShifts,
332
+ normalizeQuotes: options.normalizeQuotes,
333
+ schema: options.schema
334
+ };
335
+ let jsonData;
336
+ if (useStdin) {
337
+ const csvContent = await readStdin();
338
+ jsonData = jtcsv.csvToJson(csvContent, jtcsvOptions);
339
+ }
340
+ else {
341
+ jsonData = await jtcsv.readCsvAsJson(inputFile, jtcsvOptions);
342
+ }
343
+ let transformedData = jsonData;
344
+ if (options.transform) {
345
+ if (shouldLog) {
346
+ console.log(color(`Applying transform from: ${options.transform}`, 'dim'));
347
+ }
348
+ try {
349
+ transformedData = transformLoader.applyTransform(jsonData, options.transform);
350
+ if (shouldLog) {
351
+ console.log(color(`✓ Transform applied to ${transformedData.length} rows`, 'green'));
352
+ }
353
+ }
354
+ catch (transformError) {
355
+ console.error(color(`✗ Transform error: ${transformError.message}`, 'red'));
356
+ if (options.debug) {
357
+ console.error(transformError.stack);
358
+ }
359
+ process.exit(1);
360
+ }
361
+ }
362
+ const jsonOutput = options.pretty
363
+ ? JSON.stringify(transformedData, null, 2)
364
+ : JSON.stringify(transformedData);
365
+ if (useStdout) {
366
+ process.stdout.write(jsonOutput);
367
+ }
368
+ else {
369
+ await fs.promises.writeFile(outputFile, jsonOutput, 'utf8');
370
+ }
371
+ const elapsed = Date.now() - startTime;
372
+ if (shouldLog) {
373
+ console.log(color(`✓ Converted ${transformedData.length.toLocaleString()} rows in ${elapsed}ms`, 'green'));
374
+ if (!useStdout) {
375
+ console.log(color(` Output: ${outputFile} (${jsonOutput.length.toLocaleString()} bytes)`, 'dim'));
376
+ }
377
+ }
378
+ return {
379
+ rows: transformedData.length,
380
+ bytes: jsonOutput.length,
381
+ time: elapsed
382
+ };
383
+ }
384
+ catch (error) {
385
+ console.error(color(`✗ Error: ${error.message}`, 'red'));
386
+ if (options.debug) {
387
+ console.error(error.stack);
388
+ }
389
+ process.exit(1);
390
+ }
391
+ }
392
+ async function saveAsCsv(inputFile, outputFile, options) {
393
+ const startTime = Date.now();
394
+ try {
395
+ const inputData = await fs.promises.readFile(inputFile, 'utf8');
396
+ if (!options.silent) {
397
+ console.log(color('Saving CSV file...', 'dim'));
398
+ }
399
+ let transformedData = inputData;
400
+ if (options.transform) {
401
+ if (!options.silent) {
402
+ console.log(color(`Applying transform from: ${options.transform}`, 'dim'));
403
+ }
404
+ try {
405
+ const parsedData = jtcsv.csvToJson(inputData, {
406
+ delimiter: options.delimiter,
407
+ autoDetect: options.autoDetect,
408
+ hasHeaders: options.hasHeaders,
409
+ trim: options.trim,
410
+ parseNumbers: options.parseNumbers,
411
+ parseBooleans: options.parseBooleans,
412
+ repairRowShifts: options.repairRowShifts,
413
+ normalizeQuotes: options.normalizeQuotes
414
+ });
415
+ const transformedJson = transformLoader.applyTransform(parsedData, options.transform);
416
+ transformedData = jtcsv.jsonToCsv(transformedJson, {
417
+ delimiter: options.delimiter,
418
+ includeHeaders: options.includeHeaders,
419
+ normalizeQuotes: options.normalizeQuotes
420
+ });
421
+ if (!options.silent) {
422
+ console.log(color('✓ Transform applied', 'green'));
423
+ }
424
+ }
425
+ catch (transformError) {
426
+ console.error(color(`✗ Transform error: ${transformError.message}`, 'red'));
427
+ if (options.debug) {
428
+ console.error(transformError.stack);
429
+ }
430
+ process.exit(1);
431
+ }
432
+ }
433
+ await fs.promises.writeFile(outputFile, transformedData, 'utf8');
434
+ const elapsed = Date.now() - startTime;
435
+ if (!options.silent) {
436
+ console.log(color(`✓ Saved CSV in ${elapsed}ms`, 'green'));
437
+ console.log(color(` Output: ${outputFile}`, 'dim'));
438
+ }
439
+ return { time: elapsed };
440
+ }
441
+ catch (error) {
442
+ console.error(color(`✗ Error: ${error.message}`, 'red'));
443
+ if (options.debug) {
444
+ console.error(error.stack);
445
+ }
446
+ process.exit(1);
447
+ }
448
+ }
449
+ async function saveAsJson(inputFile, outputFile, options) {
450
+ const startTime = Date.now();
451
+ try {
452
+ const inputData = await fs.promises.readFile(inputFile, 'utf8');
453
+ const jsonData = JSON.parse(inputData);
454
+ if (!options.silent) {
455
+ console.log(color(`Saving ${Array.isArray(jsonData) ? jsonData.length.toLocaleString() + ' records' : 'object'}...`, 'dim'));
456
+ }
457
+ let transformedData = jsonData;
458
+ if (options.transform) {
459
+ if (!options.silent) {
460
+ console.log(color(`Applying transform from: ${options.transform}`, 'dim'));
461
+ }
462
+ try {
463
+ transformedData = transformLoader.applyTransform(jsonData, options.transform);
464
+ if (!options.silent) {
465
+ console.log(color('✓ Transform applied', 'green'));
466
+ }
467
+ }
468
+ catch (transformError) {
469
+ console.error(color(`✗ Transform error: ${transformError.message}`, 'red'));
470
+ if (options.debug) {
471
+ console.error(transformError.stack);
472
+ }
473
+ process.exit(1);
474
+ }
475
+ }
476
+ const jtcsvOptions = {
477
+ prettyPrint: options.pretty
478
+ };
479
+ await jtcsv.saveAsJson(transformedData, outputFile, jtcsvOptions);
480
+ const elapsed = Date.now() - startTime;
481
+ if (!options.silent) {
482
+ console.log(color(`✓ Saved JSON in ${elapsed}ms`, 'green'));
483
+ console.log(color(` Output: ${outputFile}`, 'dim'));
484
+ }
485
+ return { time: elapsed };
486
+ }
487
+ catch (error) {
488
+ console.error(color(`✗ Error: ${error.message}`, 'red'));
489
+ if (options.debug) {
490
+ console.error(error.stack);
491
+ }
492
+ process.exit(1);
493
+ }
494
+ }
495
+ async function convertNdjsonToCsv(inputFile, outputFile, options) {
496
+ const startTime = Date.now();
497
+ try {
498
+ if (!options.silent) {
499
+ console.log(color('Converting NDJSON to CSV...', 'dim'));
500
+ }
501
+ const inputData = await fs.promises.readFile(inputFile, 'utf8');
502
+ const jsonData = jtcsv.ndjsonToJson(inputData);
503
+ if (!options.silent) {
504
+ console.log(color(`Parsed ${jsonData.length.toLocaleString()} records from NDJSON`, 'dim'));
505
+ }
506
+ const jtcsvOptions = {
507
+ delimiter: options.delimiter,
508
+ includeHeaders: options.includeHeaders,
509
+ renameMap: options.renameMap,
510
+ template: options.template,
511
+ preventCsvInjection: options.preventCsvInjection,
512
+ rfc4180Compliant: options.rfc4180Compliant,
513
+ normalizeQuotes: options.normalizeQuotes
514
+ };
515
+ const csvData = jtcsv.jsonToCsv(jsonData, jtcsvOptions);
516
+ await fs.promises.writeFile(outputFile, csvData, 'utf8');
517
+ const elapsed = Date.now() - startTime;
518
+ if (!options.silent) {
519
+ console.log(color(`✓ Converted ${jsonData.length.toLocaleString()} records in ${elapsed}ms`, 'green'));
520
+ console.log(color(` Output: ${outputFile} (${csvData.length.toLocaleString()} bytes)`, 'dim'));
521
+ }
522
+ return {
523
+ records: jsonData.length,
524
+ bytes: csvData.length,
525
+ time: elapsed
526
+ };
527
+ }
528
+ catch (error) {
529
+ console.error(color(`✗ Error: ${error.message}`, 'red'));
530
+ if (options.debug) {
531
+ console.error(error.stack);
532
+ }
533
+ process.exit(1);
534
+ }
535
+ }
536
+ async function convertCsvToNdjson(inputFile, outputFile, options) {
537
+ const startTime = Date.now();
538
+ try {
539
+ if (!options.silent) {
540
+ console.log(color('Converting CSV to NDJSON...', 'dim'));
541
+ }
542
+ const jtcsvOptions = {
543
+ delimiter: options.delimiter,
544
+ autoDetect: options.autoDetect,
545
+ candidates: options.candidates,
546
+ hasHeaders: options.hasHeaders,
547
+ renameMap: options.renameMap,
548
+ trim: options.trim,
549
+ parseNumbers: options.parseNumbers,
550
+ parseBooleans: options.parseBooleans
551
+ };
552
+ const jsonData = await jtcsv.readCsvAsJson(inputFile, jtcsvOptions);
553
+ const ndjsonData = jtcsv.jsonToNdjson(jsonData);
554
+ await fs.promises.writeFile(outputFile, ndjsonData, 'utf8');
555
+ const elapsed = Date.now() - startTime;
556
+ if (!options.silent) {
557
+ console.log(color(`✓ Converted ${jsonData.length.toLocaleString()} rows in ${elapsed}ms`, 'green'));
558
+ console.log(color(` Output: ${outputFile} (${ndjsonData.length.toLocaleString()} bytes)`, 'dim'));
559
+ }
560
+ return {
561
+ rows: jsonData.length,
562
+ bytes: ndjsonData.length,
563
+ time: elapsed
564
+ };
565
+ }
566
+ catch (error) {
567
+ console.error(color(`✗ Error: ${error.message}`, 'red'));
568
+ if (options.debug) {
569
+ console.error(error.stack);
570
+ }
571
+ process.exit(1);
572
+ }
573
+ }
574
+ async function convertNdjsonToJson(inputFile, outputFile, options) {
575
+ const startTime = Date.now();
576
+ try {
577
+ if (!options.silent) {
578
+ console.log(color('Converting NDJSON to JSON array...', 'dim'));
579
+ }
580
+ const inputData = await fs.promises.readFile(inputFile, 'utf8');
581
+ const jsonData = jtcsv.ndjsonToJson(inputData);
582
+ const jsonOutput = options.pretty
583
+ ? JSON.stringify(jsonData, null, 2)
584
+ : JSON.stringify(jsonData);
585
+ await fs.promises.writeFile(outputFile, jsonOutput, 'utf8');
586
+ const elapsed = Date.now() - startTime;
587
+ if (!options.silent) {
588
+ console.log(color(`✓ Converted ${jsonData.length.toLocaleString()} records in ${elapsed}ms`, 'green'));
589
+ console.log(color(` Output: ${outputFile} (${jsonOutput.length.toLocaleString()} bytes)`, 'dim'));
590
+ }
591
+ return {
592
+ records: jsonData.length,
593
+ bytes: jsonOutput.length,
594
+ time: elapsed
595
+ };
596
+ }
597
+ catch (error) {
598
+ console.error(color(`✗ Error: ${error.message}`, 'red'));
599
+ if (options.debug) {
600
+ console.error(error.stack);
601
+ }
602
+ process.exit(1);
603
+ }
604
+ }
605
+ async function convertJsonToNdjson(inputFile, outputFile, options) {
606
+ const startTime = Date.now();
607
+ try {
608
+ if (!options.silent) {
609
+ console.log(color('Converting JSON array to NDJSON...', 'dim'));
610
+ }
611
+ const inputData = await fs.promises.readFile(inputFile, 'utf8');
612
+ const jsonData = JSON.parse(inputData);
613
+ if (!Array.isArray(jsonData)) {
614
+ throw new Error('JSON data must be an array of objects');
615
+ }
616
+ const ndjsonData = jtcsv.jsonToNdjson(jsonData);
617
+ await fs.promises.writeFile(outputFile, ndjsonData, 'utf8');
618
+ const elapsed = Date.now() - startTime;
619
+ if (!options.silent) {
620
+ console.log(color(`✓ Converted ${jsonData.length.toLocaleString()} records in ${elapsed}ms`, 'green'));
621
+ console.log(color(` Output: ${outputFile} (${ndjsonData.length.toLocaleString()} bytes)`, 'dim'));
622
+ }
623
+ return {
624
+ records: jsonData.length,
625
+ bytes: ndjsonData.length,
626
+ time: elapsed
627
+ };
628
+ }
629
+ catch (error) {
630
+ console.error(color(`✗ Error: ${error.message}`, 'red'));
631
+ if (options.debug) {
632
+ console.error(error.stack);
633
+ }
634
+ process.exit(1);
635
+ }
636
+ }
637
+ async function unwrapJson(inputFile, outputFile, options) {
638
+ const startTime = Date.now();
639
+ try {
640
+ if (!options.silent) {
641
+ console.log(color('Unwrapping/flattening nested JSON...', 'dim'));
642
+ }
643
+ const inputData = await fs.promises.readFile(inputFile, 'utf8');
644
+ const jsonData = JSON.parse(inputData);
645
+ const maxDepth = options.maxDepth || 10;
646
+ const separator = options.flattenPrefix || '_';
647
+ const flattenObject = (obj, prefix = '', depth = 0) => {
648
+ if (depth >= maxDepth) {
649
+ return { [prefix.slice(0, -1)]: JSON.stringify(obj) };
650
+ }
651
+ const result = {};
652
+ for (const [key, value] of Object.entries(obj)) {
653
+ const newKey = prefix + key;
654
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
655
+ Object.assign(result, flattenObject(value, newKey + separator, depth + 1));
656
+ }
657
+ else if (Array.isArray(value)) {
658
+ if (options.unwrapArrays) {
659
+ result[newKey] = value.join(', ');
660
+ }
661
+ else {
662
+ result[newKey] = JSON.stringify(value);
663
+ }
664
+ }
665
+ else {
666
+ result[newKey] = value;
667
+ }
668
+ }
669
+ return result;
670
+ };
671
+ let unwrappedData;
672
+ if (Array.isArray(jsonData)) {
673
+ unwrappedData = jsonData.map(item => flattenObject(item));
674
+ if (!options.silent) {
675
+ console.log(color(`Processing ${jsonData.length.toLocaleString()} records...`, 'dim'));
676
+ }
677
+ }
678
+ else {
679
+ unwrappedData = flattenObject(jsonData);
680
+ }
681
+ const jsonOutput = options.pretty
682
+ ? JSON.stringify(unwrappedData, null, 2)
683
+ : JSON.stringify(unwrappedData);
684
+ await fs.promises.writeFile(outputFile, jsonOutput, 'utf8');
685
+ const elapsed = Date.now() - startTime;
686
+ const recordCount = Array.isArray(unwrappedData) ? unwrappedData.length : 1;
687
+ if (!options.silent) {
688
+ console.log(color(`✓ Unwrapped ${recordCount.toLocaleString()} record(s) in ${elapsed}ms`, 'green'));
689
+ console.log(color(` Output: ${outputFile} (${jsonOutput.length.toLocaleString()} bytes)`, 'dim'));
690
+ }
691
+ return {
692
+ records: recordCount,
693
+ bytes: jsonOutput.length,
694
+ time: elapsed
695
+ };
696
+ }
697
+ catch (error) {
698
+ console.error(color(`✗ Error: ${error.message}`, 'red'));
699
+ if (options.debug) {
700
+ console.error(error.stack);
701
+ }
702
+ process.exit(1);
703
+ }
704
+ }
705
+ async function preprocessJson(inputFile, outputFile, options) {
706
+ const startTime = Date.now();
707
+ try {
708
+ const inputData = await fs.promises.readFile(inputFile, 'utf8');
709
+ const jsonData = JSON.parse(inputData);
710
+ if (!Array.isArray(jsonData)) {
711
+ throw new Error('JSON data must be an array of objects for preprocessing');
712
+ }
713
+ if (!options.silent) {
714
+ console.log(color(`Preprocessing ${jsonData.length.toLocaleString()} records...`, 'dim'));
715
+ }
716
+ const processedData = jtcsv.preprocessData(jsonData);
717
+ let transformedData = processedData;
718
+ if (options.transform) {
719
+ if (!options.silent) {
720
+ console.log(color(`Applying transform from: ${options.transform}`, 'dim'));
721
+ }
722
+ try {
723
+ transformedData = transformLoader.applyTransform(processedData, options.transform);
724
+ if (!options.silent) {
725
+ console.log(color(`✓ Transform applied to ${transformedData.length} records`, 'green'));
726
+ }
727
+ }
728
+ catch (transformError) {
729
+ console.error(color(`✗ Transform error: ${transformError.message}`, 'red'));
730
+ if (options.debug) {
731
+ console.error(transformError.stack);
732
+ }
733
+ process.exit(1);
734
+ }
735
+ }
736
+ if (options.unwrapArrays || options.stringifyObjects) {
737
+ const maxDepth = options.maxDepth || 5;
738
+ transformedData.forEach((item) => {
739
+ for (const key in item) {
740
+ if (item[key] && typeof item[key] === 'object') {
741
+ item[key] = jtcsv.deepUnwrap(item[key]);
742
+ }
743
+ }
744
+ });
745
+ }
746
+ const jsonOutput = options.pretty
747
+ ? JSON.stringify(transformedData, null, 2)
748
+ : JSON.stringify(transformedData);
749
+ await fs.promises.writeFile(outputFile, jsonOutput, 'utf8');
750
+ const elapsed = Date.now() - startTime;
751
+ if (!options.silent) {
752
+ console.log(color(`✓ Preprocessed ${transformedData.length.toLocaleString()} records in ${elapsed}ms`, 'green'));
753
+ console.log(color(` Output: ${outputFile} (${jsonOutput.length.toLocaleString()} bytes)`, 'dim'));
754
+ }
755
+ return {
756
+ records: transformedData.length,
757
+ bytes: jsonOutput.length,
758
+ time: elapsed
759
+ };
760
+ }
761
+ catch (error) {
762
+ console.error(color(`✗ Error: ${error.message}`, 'red'));
763
+ if (options.debug) {
764
+ console.error(error.stack);
765
+ }
766
+ process.exit(1);
767
+ }
768
+ }
769
+ async function streamJsonToCsv(inputFile, outputFile, options) {
770
+ const startTime = Date.now();
771
+ let recordCount = 0;
772
+ try {
773
+ if (!options.silent) {
774
+ console.log(color('Streaming JSON to CSV...', 'dim'));
775
+ }
776
+ const readStream = fs.createReadStream(inputFile, 'utf8');
777
+ const writeStream = fs.createWriteStream(outputFile, 'utf8');
778
+ if (options.addBOM) {
779
+ writeStream.write('\uFEFF');
780
+ }
781
+ let buffer = '';
782
+ const isFirstChunk = true;
783
+ let headersWritten = false;
784
+ readStream.on('data', (chunk) => {
785
+ buffer += chunk;
786
+ const lines = buffer.split('\n');
787
+ buffer = lines.pop() || '';
788
+ for (const line of lines) {
789
+ if (line.trim()) {
790
+ try {
791
+ const obj = JSON.parse(line);
792
+ let finalObj = obj;
793
+ if (options.renameMap) {
794
+ const renameMap = options.renameMap;
795
+ finalObj = {};
796
+ for (const [oldKey, newKey] of Object.entries(renameMap)) {
797
+ if (oldKey in obj) {
798
+ finalObj[newKey] = obj[oldKey];
799
+ }
800
+ }
801
+ for (const [key, value] of Object.entries(obj)) {
802
+ if (!(key in renameMap)) {
803
+ finalObj[key] = value;
804
+ }
805
+ }
806
+ }
807
+ recordCount++;
808
+ if (!headersWritten && options.includeHeaders !== false) {
809
+ const headers = Object.keys(finalObj);
810
+ writeStream.write(headers.join(options.delimiter || ';') + '\n');
811
+ headersWritten = true;
812
+ }
813
+ const row = Object.values(finalObj)
814
+ .map((value) => {
815
+ const str = String(value);
816
+ if (str.includes(options.delimiter || ';') ||
817
+ str.includes('"') ||
818
+ str.includes('\n')) {
819
+ return `"${str.replace(/"/g, '""')}"`;
820
+ }
821
+ return str;
822
+ })
823
+ .join(options.delimiter || ';') + '\n';
824
+ writeStream.write(row);
825
+ if (options.verbose && recordCount % 10000 === 0) {
826
+ process.stdout.write(color(` Processed ${recordCount.toLocaleString()} records\r`, 'dim'));
827
+ }
828
+ }
829
+ catch (error) {
830
+ if (options.debug) {
831
+ console.warn(color(` Warning: Skipping invalid JSON line: ${error.message}`, 'yellow'));
832
+ }
833
+ }
834
+ }
835
+ }
836
+ });
837
+ readStream.on('end', async () => {
838
+ if (buffer.trim()) {
839
+ try {
840
+ const obj = JSON.parse(buffer);
841
+ let finalObj = obj;
842
+ if (options.renameMap) {
843
+ const renameMap = options.renameMap;
844
+ finalObj = {};
845
+ for (const [oldKey, newKey] of Object.entries(renameMap)) {
846
+ if (oldKey in obj) {
847
+ finalObj[newKey] = obj[oldKey];
848
+ }
849
+ }
850
+ for (const [key, value] of Object.entries(obj)) {
851
+ if (!(key in renameMap)) {
852
+ finalObj[key] = value;
853
+ }
854
+ }
855
+ }
856
+ recordCount++;
857
+ if (!headersWritten && options.includeHeaders !== false) {
858
+ const headers = Object.keys(finalObj);
859
+ writeStream.write(headers.join(options.delimiter || ';') + '\n');
860
+ }
861
+ const row = Object.values(finalObj)
862
+ .map((value) => {
863
+ const str = String(value);
864
+ if (str.includes(options.delimiter || ';') ||
865
+ str.includes('"') ||
866
+ str.includes('\n')) {
867
+ return `"${str.replace(/"/g, '""')}"`;
868
+ }
869
+ return str;
870
+ })
871
+ .join(options.delimiter || ';') + '\n';
872
+ writeStream.write(row);
873
+ }
874
+ catch (error) {
875
+ }
876
+ }
877
+ writeStream.end();
878
+ await new Promise((resolve) => writeStream.on('finish', () => resolve()));
879
+ const elapsed = Date.now() - startTime;
880
+ if (!options.silent) {
881
+ console.log(color(`\n✓ Streamed ${recordCount.toLocaleString()} records in ${elapsed}ms`, 'green'));
882
+ console.log(color(` Output: ${outputFile}`, 'dim'));
883
+ }
884
+ });
885
+ readStream.on('error', (error) => {
886
+ console.error(color(`✗ Stream error: ${error.message}`, 'red'));
887
+ process.exit(1);
888
+ });
889
+ writeStream.on('error', (error) => {
890
+ console.error(color(`✗ Write error: ${error.message}`, 'red'));
891
+ process.exit(1);
892
+ });
893
+ }
894
+ catch (error) {
895
+ console.error(color(`✗ Error: ${error.message}`, 'red'));
896
+ if (options.debug) {
897
+ console.error(error.stack);
898
+ }
899
+ process.exit(1);
900
+ }
901
+ }
902
+ async function streamCsvToJson(inputFile, outputFile, options) {
903
+ const startTime = Date.now();
904
+ let rowCount = 0;
905
+ try {
906
+ if (!options.silent) {
907
+ console.log(color('Streaming CSV to JSON...', 'dim'));
908
+ }
909
+ const readStream = fs.createReadStream(inputFile, 'utf8');
910
+ const writeStream = fs.createWriteStream(outputFile, 'utf8');
911
+ writeStream.write('[\n');
912
+ let buffer = '';
913
+ let isFirstRow = true;
914
+ let headers = [];
915
+ readStream.on('data', (chunk) => {
916
+ buffer += chunk;
917
+ const lines = buffer.split('\n');
918
+ buffer = lines.pop() || '';
919
+ for (let i = 0; i < lines.length; i++) {
920
+ const line = lines[i].trim();
921
+ if (!line) {
922
+ continue;
923
+ }
924
+ rowCount++;
925
+ const fields = parseCsvLineSimple(line, options.delimiter || ';');
926
+ if (rowCount === 1 && options.hasHeaders !== false) {
927
+ headers = fields;
928
+ continue;
929
+ }
930
+ const obj = {};
931
+ const fieldCount = Math.min(fields.length, headers.length);
932
+ for (let j = 0; j < fieldCount; j++) {
933
+ const header = headers[j] || `column${j + 1}`;
934
+ let finalHeader = header;
935
+ if (options.renameMap && options.renameMap[header]) {
936
+ finalHeader = options.renameMap[header];
937
+ }
938
+ let value = fields[j];
939
+ if (options.parseNumbers) {
940
+ const trimmed = value.trim();
941
+ const firstChar = trimmed.charAt(0);
942
+ if ((firstChar >= '0' && firstChar <= '9') || firstChar === '-' || firstChar === '.') {
943
+ const num = parseFloat(trimmed);
944
+ if (!isNaN(num) && isFinite(num)) {
945
+ if (String(num) === trimmed || (trimmed.includes('.') && !isNaN(Number(trimmed)))) {
946
+ value = num;
947
+ }
948
+ }
949
+ }
950
+ }
951
+ if (options.parseBooleans) {
952
+ const lowerValue = value.toLowerCase();
953
+ if (lowerValue === 'true') {
954
+ value = true;
955
+ }
956
+ if (lowerValue === 'false') {
957
+ value = false;
958
+ }
959
+ }
960
+ obj[finalHeader] = value;
961
+ }
962
+ const jsonStr = JSON.stringify(obj);
963
+ if (!isFirstRow) {
964
+ writeStream.write(',\n');
965
+ }
966
+ writeStream.write(' ' + jsonStr);
967
+ isFirstRow = false;
968
+ if (options.verbose && rowCount % 10000 === 0) {
969
+ process.stdout.write(color(` Processed ${rowCount.toLocaleString()} rows\r`, 'dim'));
970
+ }
971
+ }
972
+ });
973
+ readStream.on('end', async () => {
974
+ if (buffer.trim()) {
975
+ const fields = parseCsvLineSimple(buffer.trim(), options.delimiter || ';');
976
+ if (fields.length > 0) {
977
+ rowCount++;
978
+ if (!(rowCount === 1 && options.hasHeaders !== false)) {
979
+ const obj = {};
980
+ const fieldCount = Math.min(fields.length, headers.length);
981
+ for (let j = 0; j < fieldCount; j++) {
982
+ const header = headers[j] || `column${j + 1}`;
983
+ let finalHeader = header;
984
+ if (options.renameMap && options.renameMap[header]) {
985
+ finalHeader = options.renameMap[header];
986
+ }
987
+ obj[finalHeader] = fields[j];
988
+ }
989
+ const jsonStr = JSON.stringify(obj);
990
+ if (!isFirstRow) {
991
+ writeStream.write(',\n');
992
+ }
993
+ writeStream.write(' ' + jsonStr);
994
+ }
995
+ }
996
+ }
997
+ writeStream.write('\n]');
998
+ writeStream.end();
999
+ await new Promise((resolve) => writeStream.on('finish', () => resolve()));
1000
+ const elapsed = Date.now() - startTime;
1001
+ if (!options.silent) {
1002
+ console.log(color(`\n✓ Streamed ${(rowCount - (options.hasHeaders !== false ? 1 : 0)).toLocaleString()} rows in ${elapsed}ms`, 'green'));
1003
+ console.log(color(` Output: ${outputFile}`, 'dim'));
1004
+ }
1005
+ });
1006
+ readStream.on('error', (error) => {
1007
+ console.error(color(`✗ Stream error: ${error.message}`, 'red'));
1008
+ process.exit(1);
1009
+ });
1010
+ writeStream.on('error', (error) => {
1011
+ console.error(color(`✗ Write error: ${error.message}`, 'red'));
1012
+ process.exit(1);
1013
+ });
1014
+ }
1015
+ catch (error) {
1016
+ console.error(color(`✗ Error: ${error.message}`, 'red'));
1017
+ if (options.debug) {
1018
+ console.error(error.stack);
1019
+ }
1020
+ process.exit(1);
1021
+ }
1022
+ }
1023
+ function parseCsvLineSimple(line, delimiter) {
1024
+ const fields = [];
1025
+ let currentField = '';
1026
+ let inQuotes = false;
1027
+ for (let i = 0; i < line.length; i++) {
1028
+ const char = line[i];
1029
+ if (char === '"') {
1030
+ if (inQuotes && i + 1 < line.length && line[i + 1] === '"') {
1031
+ currentField += '"';
1032
+ i++;
1033
+ }
1034
+ else {
1035
+ inQuotes = !inQuotes;
1036
+ }
1037
+ }
1038
+ else if (char === delimiter && !inQuotes) {
1039
+ fields.push(currentField);
1040
+ currentField = '';
1041
+ }
1042
+ else {
1043
+ currentField += char;
1044
+ }
1045
+ }
1046
+ fields.push(currentField);
1047
+ return fields;
1048
+ }
1049
+ async function batchJsonToCsv(inputPattern, outputDir, options) {
1050
+ const startTime = Date.now();
1051
+ try {
1052
+ let glob;
1053
+ try {
1054
+ glob = require('glob');
1055
+ }
1056
+ catch (error) {
1057
+ console.error(color('✗ Error: The "glob" module is required for batch processing', 'red'));
1058
+ console.error(color(' Install it with: npm install glob', 'cyan'));
1059
+ console.error(color(' Or update jtcsv: npm update jtcsv', 'cyan'));
1060
+ process.exit(1);
1061
+ }
1062
+ const files = glob.sync(inputPattern, {
1063
+ absolute: true,
1064
+ nodir: true
1065
+ });
1066
+ if (files.length === 0) {
1067
+ console.error(color(`✗ No files found matching pattern: ${inputPattern}`, 'red'));
1068
+ process.exit(1);
1069
+ }
1070
+ if (!options.silent) {
1071
+ console.log(color(`Found ${files.length} files to process...`, 'dim'));
1072
+ }
1073
+ await fs.promises.mkdir(outputDir, { recursive: true });
1074
+ const results = [];
1075
+ const parallelLimit = options.parallel || 4;
1076
+ for (let i = 0; i < files.length; i += parallelLimit) {
1077
+ const batch = files.slice(i, i + parallelLimit);
1078
+ const promises = batch.map(async (file) => {
1079
+ const fileName = path.basename(file, '.json');
1080
+ const outputFile = path.join(outputDir, `${fileName}.csv`);
1081
+ if (!options.silent && options.verbose) {
1082
+ console.log(color(` Processing: ${file}`, 'dim'));
1083
+ }
1084
+ try {
1085
+ const result = await convertJsonToCsv(file, outputFile, {
1086
+ ...options,
1087
+ silent: true
1088
+ });
1089
+ if (!options.silent) {
1090
+ console.log(color(` ✓ ${fileName}.json → ${fileName}.csv (${result.records} records)`, 'green'));
1091
+ }
1092
+ return { file, success: true, ...result };
1093
+ }
1094
+ catch (error) {
1095
+ if (!options.silent) {
1096
+ console.log(color(` ✗ ${fileName}.json: ${error.message}`, 'red'));
1097
+ }
1098
+ return { file, success: false, error: error.message };
1099
+ }
1100
+ });
1101
+ const batchResults = await Promise.all(promises);
1102
+ results.push(...batchResults);
1103
+ if (!options.silent) {
1104
+ const processed = i + batch.length;
1105
+ const percent = Math.round((processed / files.length) * 100);
1106
+ console.log(color(` Progress: ${processed}/${files.length} (${percent}%)`, 'dim'));
1107
+ }
1108
+ }
1109
+ const elapsed = Date.now() - startTime;
1110
+ const successful = results.filter((r) => r.success).length;
1111
+ const totalRecords = results
1112
+ .filter((r) => r.success)
1113
+ .reduce((sum, r) => sum + (r.records || 0), 0);
1114
+ if (!options.silent) {
1115
+ console.log(color(`\n✓ Batch processing completed in ${elapsed}ms`, 'green'));
1116
+ console.log(color(` Successful: ${successful}/${files.length} files`, 'dim'));
1117
+ console.log(color(` Total records: ${totalRecords.toLocaleString()}`, 'dim'));
1118
+ console.log(color(` Output directory: ${outputDir}`, 'dim'));
1119
+ }
1120
+ return {
1121
+ totalFiles: files.length,
1122
+ successful,
1123
+ totalRecords,
1124
+ time: elapsed,
1125
+ results
1126
+ };
1127
+ }
1128
+ catch (error) {
1129
+ console.error(color(`✗ Batch processing error: ${error.message}`, 'red'));
1130
+ if (options.debug) {
1131
+ console.error(error.stack);
1132
+ }
1133
+ process.exit(1);
1134
+ }
1135
+ }
1136
+ async function batchCsvToJson(inputPattern, outputDir, options) {
1137
+ const startTime = Date.now();
1138
+ try {
1139
+ let glob;
1140
+ try {
1141
+ glob = require('glob');
1142
+ }
1143
+ catch (error) {
1144
+ console.error(color('✗ Error: The "glob" module is required for batch processing', 'red'));
1145
+ console.error(color(' Install it with: npm install glob', 'cyan'));
1146
+ console.error(color(' Or update jtcsv: npm update jtcsv', 'cyan'));
1147
+ process.exit(1);
1148
+ }
1149
+ const files = glob.sync(inputPattern, {
1150
+ absolute: true,
1151
+ nodir: true
1152
+ });
1153
+ if (files.length === 0) {
1154
+ console.error(color(`✗ No files found matching pattern: ${inputPattern}`, 'red'));
1155
+ process.exit(1);
1156
+ }
1157
+ if (!options.silent) {
1158
+ console.log(color(`Found ${files.length} files to process...`, 'dim'));
1159
+ }
1160
+ await fs.promises.mkdir(outputDir, { recursive: true });
1161
+ const results = [];
1162
+ const parallelLimit = options.parallel || 4;
1163
+ for (let i = 0; i < files.length; i += parallelLimit) {
1164
+ const batch = files.slice(i, i + parallelLimit);
1165
+ const promises = batch.map(async (file) => {
1166
+ const fileName = path.basename(file, '.csv');
1167
+ const outputFile = path.join(outputDir, `${fileName}.json`);
1168
+ if (!options.silent && options.verbose) {
1169
+ console.log(color(` Processing: ${file}`, 'dim'));
1170
+ }
1171
+ try {
1172
+ const result = await convertCsvToJson(file, outputFile, {
1173
+ ...options,
1174
+ silent: true
1175
+ });
1176
+ if (!options.silent) {
1177
+ console.log(color(` ✓ ${fileName}.csv → ${fileName}.json (${result.rows} rows)`, 'green'));
1178
+ }
1179
+ return { file, success: true, ...result };
1180
+ }
1181
+ catch (error) {
1182
+ if (!options.silent) {
1183
+ console.log(color(` ✗ ${fileName}.csv: ${error.message}`, 'red'));
1184
+ }
1185
+ return { file, success: false, error: error.message };
1186
+ }
1187
+ });
1188
+ const batchResults = await Promise.all(promises);
1189
+ results.push(...batchResults);
1190
+ if (!options.silent) {
1191
+ const processed = i + batch.length;
1192
+ const percent = Math.round((processed / files.length) * 100);
1193
+ console.log(color(` Progress: ${processed}/${files.length} (${percent}%)`, 'dim'));
1194
+ }
1195
+ }
1196
+ const elapsed = Date.now() - startTime;
1197
+ const successful = results.filter((r) => r.success).length;
1198
+ const totalRows = results
1199
+ .filter((r) => r.success)
1200
+ .reduce((sum, r) => sum + (r.rows || 0), 0);
1201
+ if (!options.silent) {
1202
+ console.log(color(`\n✓ Batch processing completed in ${elapsed}ms`, 'green'));
1203
+ console.log(color(` Successful: ${successful}/${files.length} files`, 'dim'));
1204
+ console.log(color(` Total rows: ${totalRows.toLocaleString()}`, 'dim'));
1205
+ console.log(color(` Output directory: ${outputDir}`, 'dim'));
1206
+ }
1207
+ return {
1208
+ totalFiles: files.length,
1209
+ successful,
1210
+ totalRows,
1211
+ time: elapsed,
1212
+ results
1213
+ };
1214
+ }
1215
+ catch (error) {
1216
+ console.error(color(`✗ Batch processing error: ${error.message}`, 'red'));
1217
+ if (options.debug) {
1218
+ console.error(error.stack);
1219
+ }
1220
+ process.exit(1);
1221
+ }
1222
+ }
1223
+ async function batchProcessMixed(inputPattern, outputDir, options) {
1224
+ const startTime = Date.now();
1225
+ try {
1226
+ let glob;
1227
+ try {
1228
+ glob = require('glob');
1229
+ }
1230
+ catch (error) {
1231
+ console.error(color('✗ Error: The "glob" module is required for batch processing', 'red'));
1232
+ console.error(color(' Install it with: npm install glob', 'cyan'));
1233
+ console.error(color(' Or update jtcsv: npm update jtcsv', 'cyan'));
1234
+ process.exit(1);
1235
+ }
1236
+ const files = glob.sync(inputPattern, {
1237
+ absolute: true,
1238
+ nodir: true
1239
+ });
1240
+ if (files.length === 0) {
1241
+ console.error(color(`✗ No files found matching pattern: ${inputPattern}`, 'red'));
1242
+ process.exit(1);
1243
+ }
1244
+ if (!options.silent) {
1245
+ console.log(color(`Found ${files.length} files to process...`, 'dim'));
1246
+ }
1247
+ await fs.promises.mkdir(outputDir, { recursive: true });
1248
+ const results = [];
1249
+ const parallelLimit = options.parallel || 4;
1250
+ const jsonFiles = files.filter((file) => file.toLowerCase().endsWith('.json'));
1251
+ const csvFiles = files.filter((file) => file.toLowerCase().endsWith('.csv'));
1252
+ const otherFiles = files.filter((file) => !file.toLowerCase().endsWith('.json') &&
1253
+ !file.toLowerCase().endsWith('.csv'));
1254
+ if (otherFiles.length > 0 && !options.silent) {
1255
+ console.log(color(` Warning: Skipping ${otherFiles.length} non-JSON/CSV files`, 'yellow'));
1256
+ }
1257
+ for (let i = 0; i < jsonFiles.length; i += parallelLimit) {
1258
+ const batch = jsonFiles.slice(i, i + parallelLimit);
1259
+ const promises = batch.map(async (file) => {
1260
+ const fileName = path.basename(file, '.json');
1261
+ const outputFile = path.join(outputDir, `${fileName}.csv`);
1262
+ if (!options.silent && options.verbose) {
1263
+ console.log(color(` Processing JSON: ${file}`, 'dim'));
1264
+ }
1265
+ try {
1266
+ const result = await convertJsonToCsv(file, outputFile, {
1267
+ ...options,
1268
+ silent: true
1269
+ });
1270
+ if (!options.silent) {
1271
+ console.log(color(` ✓ ${fileName}.json → ${fileName}.csv (${result.records} records)`, 'green'));
1272
+ }
1273
+ return { file, type: 'json', success: true, ...result };
1274
+ }
1275
+ catch (error) {
1276
+ if (!options.silent) {
1277
+ console.log(color(` ✗ ${fileName}.json: ${error.message}`, 'red'));
1278
+ }
1279
+ return { file, type: 'json', success: false, error: error.message };
1280
+ }
1281
+ });
1282
+ const batchResults = await Promise.all(promises);
1283
+ results.push(...batchResults);
1284
+ }
1285
+ for (let i = 0; i < csvFiles.length; i += parallelLimit) {
1286
+ const batch = csvFiles.slice(i, i + parallelLimit);
1287
+ const promises = batch.map(async (file) => {
1288
+ const fileName = path.basename(file, '.csv');
1289
+ const outputFile = path.join(outputDir, `${fileName}.json`);
1290
+ if (!options.silent && options.verbose) {
1291
+ console.log(color(` Processing CSV: ${file}`, 'dim'));
1292
+ }
1293
+ try {
1294
+ const result = await convertCsvToJson(file, outputFile, {
1295
+ ...options,
1296
+ silent: true
1297
+ });
1298
+ if (!options.silent) {
1299
+ console.log(color(` ✓ ${fileName}.csv → ${fileName}.json (${result.rows} rows)`, 'green'));
1300
+ }
1301
+ return { file, type: 'csv', success: true, ...result };
1302
+ }
1303
+ catch (error) {
1304
+ if (!options.silent) {
1305
+ console.log(color(` ✗ ${fileName}.csv: ${error.message}`, 'red'));
1306
+ }
1307
+ return { file, type: 'csv', success: false, error: error.message };
1308
+ }
1309
+ });
1310
+ const batchResults = await Promise.all(promises);
1311
+ results.push(...batchResults);
1312
+ }
1313
+ const elapsed = Date.now() - startTime;
1314
+ const successful = results.filter((r) => r.success).length;
1315
+ const totalRecords = results
1316
+ .filter((r) => r.success)
1317
+ .reduce((sum, r) => sum + (r.records || r.rows || 0), 0);
1318
+ if (!options.silent) {
1319
+ console.log(color(`\n✓ Mixed batch processing completed in ${elapsed}ms`, 'green'));
1320
+ console.log(color(` Successful: ${successful}/${files.length} files`, 'dim'));
1321
+ console.log(color(` JSON files: ${jsonFiles.length}, CSV files: ${csvFiles.length}`, 'dim'));
1322
+ console.log(color(` Total records: ${totalRecords.toLocaleString()}`, 'dim'));
1323
+ console.log(color(` Output directory: ${outputDir}`, 'dim'));
1324
+ }
1325
+ return {
1326
+ totalFiles: files.length,
1327
+ jsonFiles: jsonFiles.length,
1328
+ csvFiles: csvFiles.length,
1329
+ otherFiles: otherFiles.length,
1330
+ successful,
1331
+ totalRecords,
1332
+ time: elapsed,
1333
+ results
1334
+ };
1335
+ }
1336
+ catch (error) {
1337
+ console.error(color(`✗ Batch processing error: ${error.message}`, 'red'));
1338
+ if (options.debug) {
1339
+ console.error(error.stack);
1340
+ }
1341
+ process.exit(1);
1342
+ }
1343
+ }
1344
+ function parseOptions(args) {
1345
+ const options = {
1346
+ delimiter: ';',
1347
+ autoDetect: true,
1348
+ candidates: [';', ',', '\t', '|'],
1349
+ hasHeaders: true,
1350
+ includeHeaders: true,
1351
+ renameMap: undefined,
1352
+ template: undefined,
1353
+ trim: true,
1354
+ parseNumbers: false,
1355
+ parseBooleans: false,
1356
+ useFastPath: true,
1357
+ fastPathMode: 'objects',
1358
+ repairRowShifts: true,
1359
+ normalizeQuotes: true,
1360
+ preventCsvInjection: true,
1361
+ rfc4180Compliant: true,
1362
+ maxRecords: undefined,
1363
+ maxRows: undefined,
1364
+ maxDepth: 5,
1365
+ pretty: false,
1366
+ silent: false,
1367
+ verbose: false,
1368
+ debug: false,
1369
+ dryRun: false,
1370
+ addBOM: false,
1371
+ unwrapArrays: false,
1372
+ stringifyObjects: false,
1373
+ recursive: false,
1374
+ pattern: '**/*',
1375
+ outputDir: './output',
1376
+ patternProvided: false,
1377
+ outputDirProvided: false,
1378
+ overwrite: false,
1379
+ parallel: 4,
1380
+ chunkSize: 65536,
1381
+ bufferSize: 1000,
1382
+ schema: undefined,
1383
+ transform: undefined,
1384
+ flattenPrefix: '_',
1385
+ flatten: false,
1386
+ flattenSeparator: '.',
1387
+ flattenMaxDepth: 3,
1388
+ arrayHandling: 'stringify',
1389
+ port: 3000,
1390
+ host: 'localhost'
1391
+ };
1392
+ const files = [];
1393
+ for (let i = 0; i < args.length; i++) {
1394
+ const arg = args[i];
1395
+ if (arg.startsWith('--')) {
1396
+ const [key, value] = arg.slice(2).split('=');
1397
+ switch (key) {
1398
+ case 'delimiter':
1399
+ options.delimiter = value || ',';
1400
+ options.autoDetect = false;
1401
+ break;
1402
+ case 'auto-detect':
1403
+ options.autoDetect = value !== 'false';
1404
+ break;
1405
+ case 'candidates':
1406
+ options.candidates = value ? value.split(',') : [';', ',', '\t', '|'];
1407
+ break;
1408
+ case 'no-headers':
1409
+ options.includeHeaders = false;
1410
+ options.hasHeaders = false;
1411
+ break;
1412
+ case 'parse-numbers':
1413
+ options.parseNumbers = true;
1414
+ break;
1415
+ case 'parse-booleans':
1416
+ options.parseBooleans = true;
1417
+ break;
1418
+ case 'no-trim':
1419
+ options.trim = false;
1420
+ break;
1421
+ case 'no-fast-path':
1422
+ options.useFastPath = false;
1423
+ break;
1424
+ case 'fast-path':
1425
+ options.useFastPath = value !== 'false';
1426
+ break;
1427
+ case 'fast-path-mode':
1428
+ options.fastPathMode = value || 'objects';
1429
+ if (options.fastPathMode !== 'objects' &&
1430
+ options.fastPathMode !== 'compact') {
1431
+ throw new Error('Invalid --fast-path-mode value (objects|compact)');
1432
+ }
1433
+ break;
1434
+ case 'repair-row-shifts':
1435
+ options.repairRowShifts = true;
1436
+ break;
1437
+ case 'normalize-quotes':
1438
+ options.normalizeQuotes = true;
1439
+ break;
1440
+ case 'rename':
1441
+ try {
1442
+ const jsonStr = value || '{}';
1443
+ const cleanStr = jsonStr
1444
+ .replace(/^'|'$/g, '')
1445
+ .replace(/^"|"$/g, '');
1446
+ options.renameMap = JSON.parse(cleanStr);
1447
+ }
1448
+ catch (e) {
1449
+ throw new Error(`Invalid JSON in --rename option: ${e.message}`);
1450
+ }
1451
+ break;
1452
+ case 'template':
1453
+ try {
1454
+ const jsonStr = value || '{}';
1455
+ const cleanStr = jsonStr
1456
+ .replace(/^'|'$/g, '')
1457
+ .replace(/^"|"$/g, '');
1458
+ options.template = JSON.parse(cleanStr);
1459
+ }
1460
+ catch (e) {
1461
+ throw new Error(`Invalid JSON in --template option: ${e.message}`);
1462
+ }
1463
+ break;
1464
+ case 'no-injection-protection':
1465
+ options.preventCsvInjection = false;
1466
+ break;
1467
+ case 'no-rfc4180':
1468
+ options.rfc4180Compliant = false;
1469
+ break;
1470
+ case 'max-records':
1471
+ options.maxRecords = parseInt(value, 10);
1472
+ break;
1473
+ case 'max-rows':
1474
+ options.maxRows = parseInt(value, 10);
1475
+ break;
1476
+ case 'max-depth':
1477
+ options.maxDepth = parseInt(value, 10) || 5;
1478
+ break;
1479
+ case 'pretty':
1480
+ options.pretty = true;
1481
+ break;
1482
+ case 'flatten':
1483
+ options.flatten = true;
1484
+ break;
1485
+ case 'flatten-separator':
1486
+ options.flattenSeparator = value || '.';
1487
+ break;
1488
+ case 'flatten-max-depth':
1489
+ options.flattenMaxDepth = parseInt(value, 10) || 3;
1490
+ break;
1491
+ case 'array-handling':
1492
+ options.arrayHandling = value || 'stringify';
1493
+ if (!['stringify', 'join', 'expand'].includes(options.arrayHandling)) {
1494
+ throw new Error('Invalid --array-handling value (stringify|join|expand)');
1495
+ }
1496
+ break;
1497
+ case 'silent':
1498
+ options.silent = true;
1499
+ break;
1500
+ case 'verbose':
1501
+ options.verbose = true;
1502
+ break;
1503
+ case 'debug':
1504
+ options.debug = true;
1505
+ break;
1506
+ case 'dry-run':
1507
+ options.dryRun = true;
1508
+ break;
1509
+ case 'add-bom':
1510
+ options.addBOM = true;
1511
+ break;
1512
+ case 'unwrap-arrays':
1513
+ options.unwrapArrays = true;
1514
+ break;
1515
+ case 'stringify-objects':
1516
+ options.stringifyObjects = true;
1517
+ break;
1518
+ case 'recursive':
1519
+ options.recursive = true;
1520
+ break;
1521
+ case 'pattern':
1522
+ options.pattern = value || '**/*';
1523
+ options.patternProvided = true;
1524
+ break;
1525
+ case 'output-dir':
1526
+ options.outputDir = value || './output';
1527
+ options.outputDirProvided = true;
1528
+ break;
1529
+ case 'overwrite':
1530
+ options.overwrite = true;
1531
+ break;
1532
+ case 'parallel':
1533
+ options.parallel = parseInt(value, 10) || 4;
1534
+ break;
1535
+ case 'chunk-size':
1536
+ options.chunkSize = parseInt(value, 10) || 65536;
1537
+ break;
1538
+ case 'buffer-size':
1539
+ options.bufferSize = parseInt(value, 10) || 1000;
1540
+ break;
1541
+ case 'schema':
1542
+ try {
1543
+ const jsonStr = value || '{}';
1544
+ const cleanStr = jsonStr
1545
+ .replace(/^'|'$/g, '')
1546
+ .replace(/^"|"$/g, '');
1547
+ options.schema = JSON.parse(cleanStr);
1548
+ }
1549
+ catch (e) {
1550
+ throw new Error(`Invalid JSON in --schema option: ${e.message}`);
1551
+ }
1552
+ break;
1553
+ case 'transform':
1554
+ options.transform = value;
1555
+ break;
1556
+ case 'port':
1557
+ options.port = parseInt(value, 10) || 3000;
1558
+ break;
1559
+ case 'host':
1560
+ options.host = value || 'localhost';
1561
+ break;
1562
+ }
1563
+ }
1564
+ else if (arg === '-' || !arg.startsWith('-')) {
1565
+ files.push(arg);
1566
+ }
1567
+ }
1568
+ return { options, files };
1569
+ }
1570
+ async function launchTUI() {
1571
+ try {
1572
+ console.log(color('Launching Terminal User Interface...', 'cyan'));
1573
+ console.log(color('Press Ctrl+Q to exit', 'dim'));
1574
+ const JtcsvTUI = require('@jtcsv/tui');
1575
+ const tui = new JtcsvTUI();
1576
+ tui.start();
1577
+ }
1578
+ catch (error) {
1579
+ if (error.code === 'MODULE_NOT_FOUND') {
1580
+ console.error(color('Error: @jtcsv/tui is not installed', 'red'));
1581
+ console.log(color('Install it with:', 'dim'));
1582
+ console.log(color(' npm install @jtcsv/tui', 'cyan'));
1583
+ console.log(color('\nOr use the CLI interface instead:', 'dim'));
1584
+ console.log(color(' jtcsv help', 'cyan'));
1585
+ }
1586
+ else {
1587
+ console.error(color(`Error: ${error.message}`, 'red'));
1588
+ }
1589
+ process.exit(1);
1590
+ }
1591
+ }
1592
+ async function launchWebUI(options = {}) {
1593
+ try {
1594
+ const webServer = require('../src/web-server');
1595
+ webServer.startServer({
1596
+ port: options.port || 3000,
1597
+ host: options.host || 'localhost'
1598
+ });
1599
+ }
1600
+ catch (error) {
1601
+ console.error(color(`Error: ${error.message}`, 'red'));
1602
+ if (options.debug) {
1603
+ console.error(error.stack);
1604
+ }
1605
+ process.exit(1);
1606
+ }
1607
+ }
1608
+ async function startBasicTUI() {
1609
+ const rl = readline.createInterface({
1610
+ input: process.stdin,
1611
+ output: process.stdout
1612
+ });
1613
+ console.clear();
1614
+ console.log(color('╔══════════════════════════════════════╗', 'cyan'));
1615
+ console.log(color('║ JTCSV Terminal Interface ║', 'cyan'));
1616
+ console.log(color('╚══════════════════════════════════════╝', 'cyan'));
1617
+ console.log();
1618
+ console.log(color('Select operation:', 'bright'));
1619
+ console.log(' 1. JSON → CSV');
1620
+ console.log(' 2. CSV → JSON');
1621
+ console.log(' 3. Preprocess JSON');
1622
+ console.log(' 4. Batch Processing');
1623
+ console.log(' 5. Exit');
1624
+ console.log();
1625
+ rl.question(color('Enter choice (1-5): ', 'cyan'), async (choice) => {
1626
+ switch (choice) {
1627
+ case '1':
1628
+ await runJsonToCsvTUI(rl);
1629
+ break;
1630
+ case '2':
1631
+ await runCsvToJsonTUI(rl);
1632
+ break;
1633
+ case '3':
1634
+ console.log(color('Preprocess feature coming soon...', 'yellow'));
1635
+ rl.close();
1636
+ break;
1637
+ case '4':
1638
+ console.log(color('Batch processing coming soon...', 'yellow'));
1639
+ rl.close();
1640
+ break;
1641
+ case '5':
1642
+ console.log(color('Goodbye!', 'green'));
1643
+ rl.close();
1644
+ process.exit(0);
1645
+ break;
1646
+ default:
1647
+ console.log(color('Invalid choice', 'red'));
1648
+ rl.close();
1649
+ process.exit(1);
1650
+ }
1651
+ });
1652
+ }
1653
+ async function runJsonToCsvTUI(rl) {
1654
+ console.clear();
1655
+ console.log(color('JSON → CSV Conversion', 'cyan'));
1656
+ console.log();
1657
+ rl.question('Input JSON file: ', (inputFile) => {
1658
+ rl.question('Output CSV file: ', async (outputFile) => {
1659
+ rl.question('Delimiter (default: ;): ', async (delimiter) => {
1660
+ try {
1661
+ console.log(color('\nConverting...', 'dim'));
1662
+ const result = await convertJsonToCsv(inputFile, outputFile, {
1663
+ delimiter: delimiter || ';',
1664
+ silent: false
1665
+ });
1666
+ console.log(color('\n✓ Conversion complete!', 'green'));
1667
+ rl.question('\nPress Enter to continue...', () => {
1668
+ rl.close();
1669
+ startBasicTUI();
1670
+ });
1671
+ }
1672
+ catch (error) {
1673
+ console.error(color(`✗ Error: ${error.message}`, 'red'));
1674
+ rl.close();
1675
+ process.exit(1);
1676
+ }
1677
+ });
1678
+ });
1679
+ });
1680
+ }
1681
+ async function runCsvToJsonTUI(rl) {
1682
+ console.clear();
1683
+ console.log(color('CSV → JSON Conversion', 'cyan'));
1684
+ console.log();
1685
+ rl.question('Input CSV file: ', (inputFile) => {
1686
+ rl.question('Output JSON file: ', async (outputFile) => {
1687
+ rl.question('Delimiter (default: ;): ', async (delimiter) => {
1688
+ rl.question('Pretty print? (y/n): ', async (pretty) => {
1689
+ try {
1690
+ console.log(color('\nConverting...', 'dim'));
1691
+ const result = await convertCsvToJson(inputFile, outputFile, {
1692
+ delimiter: delimiter || ';',
1693
+ pretty: pretty.toLowerCase() === 'y',
1694
+ silent: false
1695
+ });
1696
+ console.log(color('\n✓ Conversion complete!', 'green'));
1697
+ rl.question('\nPress Enter to continue...', () => {
1698
+ rl.close();
1699
+ startBasicTUI();
1700
+ });
1701
+ }
1702
+ catch (error) {
1703
+ console.error(color(`✗ Error: ${error.message}`, 'red'));
1704
+ rl.close();
1705
+ process.exit(1);
1706
+ }
1707
+ });
1708
+ });
1709
+ });
1710
+ });
1711
+ }
1712
+ async function main() {
1713
+ const args = process.argv.slice(2);
1714
+ if (args.length === 0) {
1715
+ showHelp();
1716
+ return;
1717
+ }
1718
+ const command = args[0].toLowerCase();
1719
+ const { options, files } = parseOptions(args.slice(1));
1720
+ if (options.dryRun) {
1721
+ console.log(color('DRY RUN - No files will be modified', 'yellow'));
1722
+ console.log(`Command: ${command}`);
1723
+ console.log(`Files: ${files.join(', ')}`);
1724
+ console.log('Options:', options);
1725
+ return;
1726
+ }
1727
+ if (options.silent) {
1728
+ console.log = () => { };
1729
+ console.info = () => { };
1730
+ }
1731
+ switch (command) {
1732
+ case 'json-to-csv':
1733
+ case 'json2csv':
1734
+ if (files.length < 2) {
1735
+ console.error(color('Error: Input and output files required', 'red'));
1736
+ console.log(color('Usage: jtcsv json-to-csv input.json output.csv', 'cyan'));
1737
+ process.exit(1);
1738
+ }
1739
+ await convertJsonToCsv(files[0], files[1], options);
1740
+ break;
1741
+ case 'csv-to-json':
1742
+ case 'csv2json':
1743
+ if (files.length < 2) {
1744
+ console.error(color('Error: Input and output files required', 'red'));
1745
+ console.log(color('Usage: jtcsv csv-to-json input.csv output.json', 'cyan'));
1746
+ process.exit(1);
1747
+ }
1748
+ await convertCsvToJson(files[0], files[1], options);
1749
+ break;
1750
+ case 'save-json':
1751
+ if (files.length < 2) {
1752
+ console.error(color('Error: Input and output files required', 'red'));
1753
+ console.log(color('Usage: jtcsv save-json input.json output.json', 'cyan'));
1754
+ process.exit(1);
1755
+ }
1756
+ await saveAsJson(files[0], files[1], options);
1757
+ break;
1758
+ case 'save-csv':
1759
+ if (files.length < 2) {
1760
+ console.error(color('Error: Input and output files required', 'red'));
1761
+ console.log(color('Usage: jtcsv save-csv input.csv output.csv', 'cyan'));
1762
+ process.exit(1);
1763
+ }
1764
+ await saveAsCsv(files[0], files[1], options);
1765
+ break;
1766
+ case 'preprocess':
1767
+ if (files.length < 2) {
1768
+ console.error(color('Error: Input and output files required', 'red'));
1769
+ console.log(color('Usage: jtcsv preprocess input.json output.json', 'cyan'));
1770
+ process.exit(1);
1771
+ }
1772
+ await preprocessJson(files[0], files[1], options);
1773
+ break;
1774
+ case 'ndjson-to-csv':
1775
+ if (files.length < 2) {
1776
+ console.error(color('Error: Input and output files required', 'red'));
1777
+ console.log(color('Usage: jtcsv ndjson-to-csv input.ndjson output.csv', 'cyan'));
1778
+ process.exit(1);
1779
+ }
1780
+ await convertNdjsonToCsv(files[0], files[1], options);
1781
+ break;
1782
+ case 'csv-to-ndjson':
1783
+ if (files.length < 2) {
1784
+ console.error(color('Error: Input and output files required', 'red'));
1785
+ console.log(color('Usage: jtcsv csv-to-ndjson input.csv output.ndjson', 'cyan'));
1786
+ process.exit(1);
1787
+ }
1788
+ await convertCsvToNdjson(files[0], files[1], options);
1789
+ break;
1790
+ case 'ndjson-to-json':
1791
+ if (files.length < 2) {
1792
+ console.error(color('Error: Input and output files required', 'red'));
1793
+ console.log(color('Usage: jtcsv ndjson-to-json input.ndjson output.json', 'cyan'));
1794
+ process.exit(1);
1795
+ }
1796
+ await convertNdjsonToJson(files[0], files[1], options);
1797
+ break;
1798
+ case 'json-to-ndjson':
1799
+ if (files.length < 2) {
1800
+ console.error(color('Error: Input and output files required', 'red'));
1801
+ console.log(color('Usage: jtcsv json-to-ndjson input.json output.ndjson', 'cyan'));
1802
+ process.exit(1);
1803
+ }
1804
+ await convertJsonToNdjson(files[0], files[1], options);
1805
+ break;
1806
+ case 'unwrap':
1807
+ case 'flatten':
1808
+ if (files.length < 2) {
1809
+ console.error(color('Error: Input and output files required', 'red'));
1810
+ console.log(color('Usage: jtcsv unwrap input.json output.json', 'cyan'));
1811
+ process.exit(1);
1812
+ }
1813
+ await unwrapJson(files[0], files[1], options);
1814
+ break;
1815
+ case 'stream':
1816
+ if (args.length < 2) {
1817
+ console.error(color('Error: Streaming mode requires subcommand', 'red'));
1818
+ console.log(color('Usage: jtcsv stream [json-to-csv|csv-to-json|file-to-csv|file-to-json]', 'cyan'));
1819
+ process.exit(1);
1820
+ }
1821
+ const streamCommand = args[1].toLowerCase();
1822
+ const streamArgs = args.slice(2);
1823
+ const { options: streamOptions, files: streamFiles } = parseOptions(streamArgs);
1824
+ if (streamCommand === 'json-to-csv' && streamFiles.length >= 2) {
1825
+ await streamJsonToCsv(streamFiles[0], streamFiles[1], streamOptions);
1826
+ }
1827
+ else if (streamCommand === 'csv-to-json' && streamFiles.length >= 2) {
1828
+ await streamCsvToJson(streamFiles[0], streamFiles[1], streamOptions);
1829
+ }
1830
+ else if (streamCommand === 'file-to-csv' && streamFiles.length >= 2) {
1831
+ try {
1832
+ const readStream = fs.createReadStream(streamFiles[0], 'utf8');
1833
+ const writeStream = fs.createWriteStream(streamFiles[1], 'utf8');
1834
+ if (streamOptions.addBOM) {
1835
+ writeStream.write('\uFEFF');
1836
+ }
1837
+ const transformStream = jtcsv.createJsonToCsvStream(streamOptions);
1838
+ await pipeline(readStream, transformStream, writeStream);
1839
+ console.log(color('✓ File streamed successfully', 'green'));
1840
+ }
1841
+ catch (error) {
1842
+ console.error(color(`✗ Streaming error: ${error.message}`, 'red'));
1843
+ process.exit(1);
1844
+ }
1845
+ }
1846
+ else if (streamCommand === 'file-to-json' && streamFiles.length >= 2) {
1847
+ try {
1848
+ const readStream = fs.createReadStream(streamFiles[0], 'utf8');
1849
+ const writeStream = fs.createWriteStream(streamFiles[1], 'utf8');
1850
+ const transformStream = jtcsv.createCsvToJsonStream(streamOptions);
1851
+ await pipeline(readStream, transformStream, writeStream);
1852
+ console.log(color('✓ File streamed successfully', 'green'));
1853
+ }
1854
+ catch (error) {
1855
+ console.error(color(`✗ Streaming error: ${error.message}`, 'red'));
1856
+ process.exit(1);
1857
+ }
1858
+ }
1859
+ else {
1860
+ console.error(color('Error: Invalid streaming command or missing files', 'red'));
1861
+ process.exit(1);
1862
+ }
1863
+ break;
1864
+ case 'batch':
1865
+ if (args.length < 2) {
1866
+ console.error(color('Error: Batch mode requires subcommand', 'red'));
1867
+ console.log(color('Usage: jtcsv batch [json-to-csv|csv-to-json|process]', 'cyan'));
1868
+ process.exit(1);
1869
+ }
1870
+ const batchCommand = args[1].toLowerCase();
1871
+ const batchArgs = args.slice(2);
1872
+ const { options: batchOptions, files: batchFiles } = parseOptions(batchArgs);
1873
+ const inputPattern = batchFiles[0]
1874
+ || (batchOptions.patternProvided ? batchOptions.pattern : undefined);
1875
+ const outputDir = batchFiles[1]
1876
+ || (batchOptions.outputDirProvided ? batchOptions.outputDir : undefined);
1877
+ if (!inputPattern || !outputDir) {
1878
+ console.error(color('Error: Batch mode requires input pattern and output directory', 'red'));
1879
+ console.log(color('Usage: jtcsv batch [json-to-csv|csv-to-json|process] "<pattern>" "<outputDir>"', 'cyan'));
1880
+ console.log(color('Or: jtcsv batch <subcommand> --pattern="<pattern>" --output-dir="<outputDir>"', 'cyan'));
1881
+ process.exit(1);
1882
+ }
1883
+ if (batchCommand === 'json-to-csv') {
1884
+ await batchJsonToCsv(inputPattern, outputDir, batchOptions);
1885
+ }
1886
+ else if (batchCommand === 'csv-to-json') {
1887
+ await batchCsvToJson(inputPattern, outputDir, batchOptions);
1888
+ }
1889
+ else if (batchCommand === 'process') {
1890
+ await batchProcessMixed(inputPattern, outputDir, batchOptions);
1891
+ }
1892
+ else {
1893
+ console.error(color('Error: Invalid batch command or missing files', 'red'));
1894
+ process.exit(1);
1895
+ }
1896
+ break;
1897
+ case 'tui':
1898
+ await launchTUI();
1899
+ break;
1900
+ case 'web':
1901
+ await launchWebUI(options);
1902
+ break;
1903
+ case 'help':
1904
+ case '--help':
1905
+ case '-h':
1906
+ showHelp();
1907
+ break;
1908
+ case 'version':
1909
+ case '-v':
1910
+ case '--version':
1911
+ showVersion();
1912
+ break;
1913
+ default:
1914
+ console.error(color(`Error: Unknown command '${command}'`, 'red'));
1915
+ console.log(color('Use jtcsv help for available commands', 'cyan'));
1916
+ process.exit(1);
1917
+ }
1918
+ }
1919
+ process.on('uncaughtException', (error) => {
1920
+ console.error(color(`\n✗ Uncaught error: ${error.message}`, 'red'));
1921
+ if (process.env.DEBUG) {
1922
+ console.error(error.stack);
1923
+ }
1924
+ process.exit(1);
1925
+ });
1926
+ process.on('unhandledRejection', (error) => {
1927
+ console.error(color(`\n✗ Unhandled promise rejection: ${error.message}`, 'red'));
1928
+ if (process.env.DEBUG) {
1929
+ console.error(error.stack);
1930
+ }
1931
+ process.exit(1);
1932
+ });
1933
+ if (require.main === module) {
1934
+ main().catch((error) => {
1935
+ console.error(color(`\n✗ Fatal error: ${error.message}`, 'red'));
1936
+ process.exit(1);
1937
+ });
1938
+ }
1939
+ module.exports = { main };
1940
+ //# sourceMappingURL=jtcsv.js.map