Add support for csv + xlsx

This commit is contained in:
2026-07-31 09:17:08 +02:00
parent 4c408d83e9
commit b40a7d340f
6 changed files with 1211 additions and 1 deletions
Generated
+1
View File
@@ -10,6 +10,7 @@
<option name="highlightLevel" value="WARNING" /> <option name="highlightLevel" value="WARNING" />
<option name="transferred" value="true" /> <option name="transferred" value="true" />
</component> </component>
<component name="PhpProjectSharedConfiguration" php_language_level="8.4" />
<component name="PhpStanOptionsConfiguration"> <component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" /> <option name="transferred" value="true" />
</component> </component>
+741
View File
@@ -12,6 +12,7 @@
"@prisma/client": "^6.19.3", "@prisma/client": "^6.19.3",
"docx": "^9.7.1", "docx": "^9.7.1",
"dotenv": "^17.4.2", "dotenv": "^17.4.2",
"exceljs": "^4.4.0",
"express": "^5.2.1", "express": "^5.2.1",
"express-rate-limit": "^8.6.1", "express-rate-limit": "^8.6.1",
"file-type": "^22.0.1", "file-type": "^22.0.1",
@@ -104,6 +105,47 @@
"license": "0BSD", "license": "0BSD",
"optional": true "optional": true
}, },
"node_modules/@fast-csv/format": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz",
"integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==",
"license": "MIT",
"dependencies": {
"@types/node": "^14.0.1",
"lodash.escaperegexp": "^4.1.2",
"lodash.isboolean": "^3.0.3",
"lodash.isequal": "^4.5.0",
"lodash.isfunction": "^3.0.9",
"lodash.isnil": "^4.0.0"
}
},
"node_modules/@fast-csv/format/node_modules/@types/node": {
"version": "14.18.63",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz",
"integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==",
"license": "MIT"
},
"node_modules/@fast-csv/parse": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz",
"integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==",
"license": "MIT",
"dependencies": {
"@types/node": "^14.0.1",
"lodash.escaperegexp": "^4.1.2",
"lodash.groupby": "^4.6.0",
"lodash.isfunction": "^3.0.9",
"lodash.isnil": "^4.0.0",
"lodash.isundefined": "^3.0.1",
"lodash.uniq": "^4.5.0"
}
},
"node_modules/@fast-csv/parse/node_modules/@types/node": {
"version": "14.18.63",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz",
"integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==",
"license": "MIT"
},
"node_modules/@img/colour": { "node_modules/@img/colour": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
@@ -1804,6 +1846,59 @@
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/archiver": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz",
"integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==",
"license": "MIT",
"dependencies": {
"archiver-utils": "^2.1.0",
"async": "^3.2.4",
"buffer-crc32": "^0.2.1",
"readable-stream": "^3.6.0",
"readdir-glob": "^1.1.2",
"tar-stream": "^2.2.0",
"zip-stream": "^4.1.0"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/archiver-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz",
"integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==",
"license": "MIT",
"dependencies": {
"glob": "^7.1.4",
"graceful-fs": "^4.2.0",
"lazystream": "^1.0.0",
"lodash.defaults": "^4.2.0",
"lodash.difference": "^4.5.0",
"lodash.flatten": "^4.4.0",
"lodash.isplainobject": "^4.0.6",
"lodash.union": "^4.6.0",
"normalize-path": "^3.0.0",
"readable-stream": "^2.0.0"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/archiver/node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/argparse": { "node_modules/argparse": {
"version": "1.0.10", "version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@@ -1861,6 +1956,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/base64-js": { "node_modules/base64-js": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -1890,6 +1991,28 @@
"node": ">=10.0.0" "node": ">=10.0.0"
} }
}, },
"node_modules/big-integer": {
"version": "1.6.52",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
"integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==",
"license": "Unlicense",
"engines": {
"node": ">=0.6"
}
},
"node_modules/binary": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz",
"integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==",
"license": "MIT",
"dependencies": {
"buffers": "~0.1.1",
"chainsaw": "~0.1.0"
},
"engines": {
"node": "*"
}
},
"node_modules/binary-extensions": { "node_modules/binary-extensions": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -1902,6 +2025,31 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"license": "MIT",
"dependencies": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
},
"node_modules/bl/node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/bluebird": { "node_modules/bluebird": {
"version": "3.4.7", "version": "3.4.7",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz",
@@ -1945,6 +2093,16 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/brace-expansion": {
"version": "1.1.18",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/braces": { "node_modules/braces": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
@@ -1957,12 +2115,62 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"node_modules/buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/buffer-from": { "node_modules/buffer-from": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/buffer-indexof-polyfill": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz",
"integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==",
"license": "MIT",
"engines": {
"node": ">=0.10"
}
},
"node_modules/buffers": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz",
"integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==",
"engines": {
"node": ">=0.2.0"
}
},
"node_modules/busboy": { "node_modules/busboy": {
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -2094,6 +2302,18 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/chainsaw": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz",
"integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==",
"license": "MIT/X11",
"dependencies": {
"traverse": ">=0.3.0 <0.4"
},
"engines": {
"node": "*"
}
},
"node_modules/chalk": { "node_modules/chalk": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
@@ -2261,6 +2481,41 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/compress-commons": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz",
"integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==",
"license": "MIT",
"dependencies": {
"buffer-crc32": "^0.2.13",
"crc32-stream": "^4.0.2",
"normalize-path": "^3.0.0",
"readable-stream": "^3.6.0"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/compress-commons/node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"license": "MIT"
},
"node_modules/concat-stream": { "node_modules/concat-stream": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
@@ -2367,6 +2622,45 @@
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/crc-32": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
"license": "Apache-2.0",
"bin": {
"crc32": "bin/crc32.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/crc32-stream": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz",
"integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==",
"license": "MIT",
"dependencies": {
"crc-32": "^1.2.0",
"readable-stream": "^3.4.0"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/crc32-stream/node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/croner": { "node_modules/croner": {
"version": "4.1.97", "version": "4.1.97",
"resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz", "resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz",
@@ -2523,6 +2817,15 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/duplexer2": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
"integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==",
"license": "BSD-3-Clause",
"dependencies": {
"readable-stream": "^2.0.2"
}
},
"node_modules/ee-first": { "node_modules/ee-first": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -2565,6 +2868,15 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
"license": "MIT",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/entities": { "node_modules/entities": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz",
@@ -2722,6 +3034,50 @@
"integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/exceljs": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.4.0.tgz",
"integrity": "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==",
"license": "MIT",
"dependencies": {
"archiver": "^5.0.0",
"dayjs": "^1.8.34",
"fast-csv": "^4.3.1",
"jszip": "^3.10.1",
"readable-stream": "^3.6.0",
"saxes": "^5.0.1",
"tmp": "^0.2.0",
"unzipper": "^0.10.11",
"uuid": "^8.3.0"
},
"engines": {
"node": ">=8.3.0"
}
},
"node_modules/exceljs/node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/exceljs/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).",
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/expect-type": { "node_modules/expect-type": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz",
@@ -2833,6 +3189,19 @@
"node": ">=8.0.0" "node": ">=8.0.0"
} }
}, },
"node_modules/fast-csv": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz",
"integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==",
"license": "MIT",
"dependencies": {
"@fast-csv/format": "4.3.5",
"@fast-csv/parse": "4.3.6"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/fast-json-patch": { "node_modules/fast-json-patch": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz",
@@ -3011,6 +3380,18 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"license": "MIT"
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"license": "ISC"
},
"node_modules/fsevents": { "node_modules/fsevents": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -3025,6 +3406,22 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
} }
}, },
"node_modules/fstream": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
"integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
"deprecated": "This package is no longer supported.",
"license": "ISC",
"dependencies": {
"graceful-fs": "^4.1.2",
"inherits": "~2.0.0",
"mkdirp": ">=0.5 0",
"rimraf": "2"
},
"engines": {
"node": ">=0.6"
}
},
"node_modules/function-bind": { "node_modules/function-bind": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -3110,6 +3507,27 @@
"giget": "dist/cli.mjs" "giget": "dist/cli.mjs"
} }
}, },
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob-parent": { "node_modules/glob-parent": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
@@ -3134,6 +3552,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
"node_modules/has-flag": { "node_modules/has-flag": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -3255,6 +3679,17 @@
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
"license": "ISC",
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": { "node_modules/inherits": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
@@ -3389,6 +3824,18 @@
"setimmediate": "^1.0.5" "setimmediate": "^1.0.5"
} }
}, },
"node_modules/lazystream": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz",
"integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==",
"license": "MIT",
"dependencies": {
"readable-stream": "^2.0.5"
},
"engines": {
"node": ">= 0.6.3"
}
},
"node_modules/lie": { "node_modules/lie": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
@@ -3702,12 +4149,97 @@
"uc.micro": "^3.0.0" "uc.micro": "^3.0.0"
} }
}, },
"node_modules/listenercount": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz",
"integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==",
"license": "ISC"
},
"node_modules/lodash": { "node_modules/lodash": {
"version": "4.18.1", "version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
"license": "MIT"
},
"node_modules/lodash.difference": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
"integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==",
"license": "MIT"
},
"node_modules/lodash.escaperegexp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
"integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
"license": "MIT"
},
"node_modules/lodash.flatten": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
"integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==",
"license": "MIT"
},
"node_modules/lodash.groupby": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz",
"integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==",
"license": "MIT"
},
"node_modules/lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
"license": "MIT"
},
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
"license": "MIT"
},
"node_modules/lodash.isfunction": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz",
"integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==",
"license": "MIT"
},
"node_modules/lodash.isnil": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz",
"integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==",
"license": "MIT"
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"license": "MIT"
},
"node_modules/lodash.isundefined": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz",
"integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==",
"license": "MIT"
},
"node_modules/lodash.union": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
"integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==",
"license": "MIT"
},
"node_modules/lodash.uniq": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
"integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
"license": "MIT"
},
"node_modules/lop": { "node_modules/lop": {
"version": "0.4.2", "version": "0.4.2",
"resolved": "https://registry.npmjs.org/lop/-/lop-0.4.2.tgz", "resolved": "https://registry.npmjs.org/lop/-/lop-0.4.2.tgz",
@@ -3890,12 +4422,45 @@
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/mitt": { "node_modules/mitt": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"license": "MIT",
"dependencies": {
"minimist": "^1.2.6"
},
"bin": {
"mkdirp": "bin/cmd.js"
}
},
"node_modules/modern-tar": { "node_modules/modern-tar": {
"version": "0.7.7", "version": "0.7.7",
"resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.7.tgz", "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.7.tgz",
@@ -4731,6 +5296,36 @@
"util-deprecate": "~1.0.1" "util-deprecate": "~1.0.1"
} }
}, },
"node_modules/readdir-glob": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz",
"integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==",
"license": "Apache-2.0",
"dependencies": {
"minimatch": "^5.1.0"
}
},
"node_modules/readdir-glob/node_modules/brace-expansion": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/readdir-glob/node_modules/minimatch": {
"version": "5.1.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
"integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/readdirp": { "node_modules/readdirp": {
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -4755,6 +5350,19 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"license": "ISC",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
}
},
"node_modules/rolldown": { "node_modules/rolldown": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.1.tgz", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.1.tgz",
@@ -4846,6 +5454,18 @@
"node": ">=11.0.0" "node": ">=11.0.0"
} }
}, },
"node_modules/saxes": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz",
"integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==",
"license": "ISC",
"dependencies": {
"xmlchars": "^2.2.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/scheduler": { "node_modules/scheduler": {
"version": "0.27.0", "version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
@@ -5234,6 +5854,36 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"license": "MIT",
"dependencies": {
"bl": "^4.0.3",
"end-of-stream": "^1.4.1",
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1"
},
"engines": {
"node": ">=6"
}
},
"node_modules/tar-stream/node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/tinybench": { "node_modules/tinybench": {
"version": "2.9.0", "version": "2.9.0",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
@@ -5278,6 +5928,15 @@
"node": ">=14.0.0" "node": ">=14.0.0"
} }
}, },
"node_modules/tmp": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz",
"integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==",
"license": "MIT",
"engines": {
"node": ">=14.14"
}
},
"node_modules/to-regex-range": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -5317,6 +5976,15 @@
"url": "https://github.com/sponsors/Borewit" "url": "https://github.com/sponsors/Borewit"
} }
}, },
"node_modules/traverse": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz",
"integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==",
"license": "MIT/X11",
"engines": {
"node": "*"
}
},
"node_modules/tslib": { "node_modules/tslib": {
"version": "1.14.1", "version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
@@ -5445,6 +6113,24 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/unzipper": {
"version": "0.10.14",
"resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz",
"integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==",
"license": "MIT",
"dependencies": {
"big-integer": "^1.6.17",
"binary": "~0.3.0",
"bluebird": "~3.4.1",
"buffer-indexof-polyfill": "~1.0.0",
"duplexer2": "~0.1.4",
"fstream": "^1.0.12",
"graceful-fs": "^4.2.2",
"listenercount": "~1.0.1",
"readable-stream": "~2.3.6",
"setimmediate": "~1.0.4"
}
},
"node_modules/util-deprecate": { "node_modules/util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -5752,6 +6438,12 @@
"node": ">=4.0" "node": ">=4.0"
} }
}, },
"node_modules/xmlchars": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"license": "MIT"
},
"node_modules/y18n": { "node_modules/y18n": {
"version": "5.0.8", "version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
@@ -5787,6 +6479,55 @@
"node": "^20.19.0 || ^22.12.0 || >=23" "node": "^20.19.0 || ^22.12.0 || >=23"
} }
}, },
"node_modules/zip-stream": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz",
"integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==",
"license": "MIT",
"dependencies": {
"archiver-utils": "^3.0.4",
"compress-commons": "^4.1.2",
"readable-stream": "^3.6.0"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/zip-stream/node_modules/archiver-utils": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz",
"integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==",
"license": "MIT",
"dependencies": {
"glob": "^7.2.3",
"graceful-fs": "^4.2.0",
"lazystream": "^1.0.0",
"lodash.defaults": "^4.2.0",
"lodash.difference": "^4.5.0",
"lodash.flatten": "^4.4.0",
"lodash.isplainobject": "^4.0.6",
"lodash.union": "^4.6.0",
"normalize-path": "^3.0.0",
"readable-stream": "^3.6.0"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/zip-stream/node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/zod": { "node_modules/zod": {
"version": "3.25.76", "version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+1
View File
@@ -15,6 +15,7 @@
"@prisma/client": "^6.19.3", "@prisma/client": "^6.19.3",
"docx": "^9.7.1", "docx": "^9.7.1",
"dotenv": "^17.4.2", "dotenv": "^17.4.2",
"exceljs": "^4.4.0",
"express": "^5.2.1", "express": "^5.2.1",
"express-rate-limit": "^8.6.1", "express-rate-limit": "^8.6.1",
"file-type": "^22.0.1", "file-type": "^22.0.1",
+253 -1
View File
@@ -2,9 +2,10 @@ import fs from 'node:fs/promises';
import mammoth from 'mammoth'; import mammoth from 'mammoth';
import puppeteer from 'puppeteer'; import puppeteer from 'puppeteer';
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.mjs'; import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.mjs';
import { Document, Paragraph, TextRun, HeadingLevel, Packer } from 'docx'; import { Document, Paragraph, TextRun, HeadingLevel, Table, TableRow, TableCell, Packer } from 'docx';
import MarkdownIt from 'markdown-it'; import MarkdownIt from 'markdown-it';
import TurndownService from 'turndown'; import TurndownService from 'turndown';
import ExcelJS from 'exceljs';
import { register } from './registry.js'; import { register } from './registry.js';
async function renderHtmlToPdf(html, outputPath) { async function renderHtmlToPdf(html, outputPath) {
@@ -217,6 +218,234 @@ async function convertPdfToDocx(inputPath, outputPath) {
await fs.writeFile(outputPath, buffer); await fs.writeFile(outputPath, buffer);
} }
function decodeHtmlEntities(text) {
return text
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.replace(/&nbsp;/g, ' ')
.replace(/&amp;/g, '&');
}
function stripTags(html) {
return decodeHtmlEntities(html.replace(/<[^>]*>/g, '')).trim();
}
function parseHtmlTableToRows(html) {
const tableMatch = html.match(/<table[^>]*>([\s\S]*?)<\/table>/i);
if (!tableMatch) return [];
const rowMatches = [...tableMatch[1].matchAll(/<tr[^>]*>([\s\S]*?)<\/tr>/gi)];
return rowMatches.map((rowMatch) => {
const cellMatches = [...rowMatch[1].matchAll(/<t[hd][^>]*>([\s\S]*?)<\/t[hd]>/gi)];
return cellMatches.map((cellMatch) => stripTags(cellMatch[1]));
});
}
function rowsToHtmlDocument(rows) {
const body = rows
.map((row) => `<tr>${row.map((cell) => `<td>${escapeHtml(String(cell ?? ''))}</td>`).join('')}</tr>`)
.join('\n');
return `<html><body><table>${body}</table></body></html>`;
}
function escapeMarkdownCell(cell) {
return String(cell ?? '').replace(/\|/g, '\\|').replace(/\r?\n/g, ' ');
}
function rowsToMarkdownTable(rows) {
if (rows.length === 0) return '';
const [header, ...body] = rows;
const headerLine = `| ${header.map(escapeMarkdownCell).join(' | ')} |`;
const separatorLine = `| ${header.map(() => '---').join(' | ')} |`;
const bodyLines = body.map((row) => `| ${row.map(escapeMarkdownCell).join(' | ')} |`);
return [headerLine, separatorLine, ...bodyLines].join('\n') + '\n';
}
function inlineTokensToPlainText(children) {
let text = '';
for (const token of children ?? []) {
if (token.type === 'text' || token.type === 'code_inline') text += token.content;
else if (token.type === 'softbreak' || token.type === 'hardbreak') text += ' ';
}
return text.trim();
}
function parseMarkdownTableToRows(markdown) {
const tokens = new MarkdownIt().parse(markdown, {});
const rows = [];
let currentRow = null;
for (let i = 0; i < tokens.length; i += 1) {
const token = tokens[i];
if (token.type === 'tr_open') {
currentRow = [];
} else if (token.type === 'tr_close') {
if (currentRow) rows.push(currentRow);
currentRow = null;
} else if ((token.type === 'th_open' || token.type === 'td_open') && currentRow) {
currentRow.push(inlineTokensToPlainText(tokens[i + 1]?.children));
}
}
return rows;
}
function parseTxtToRows(text) {
const lines = text.split(/\r?\n/);
while (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();
return lines.map((line) => line.split('\t'));
}
function rowsToTxt(rows) {
return `${rows.map((row) => row.map((cell) => String(cell ?? '')).join('\t')).join('\n')}\n`;
}
async function parseDocxToRows(inputPath) {
const result = await mammoth.convertToHtml({ path: inputPath });
return parseHtmlTableToRows(result.value);
}
async function writeRowsAsDocx(rows, outputPath) {
const table = new Table({
rows: rows.map(
(row) =>
new TableRow({
children: row.map(
(cell) => new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: String(cell ?? '') })] })] })
),
})
),
});
const doc = new Document({ sections: [{ children: [table] }] });
const buffer = await Packer.toBuffer(doc);
await fs.writeFile(outputPath, buffer);
}
async function parsePdfToRows(inputPath) {
const pageTexts = await extractPdfPageTexts(inputPath);
return pageTexts.map((text) => [text]);
}
async function readSpreadsheetRows(inputPath, format) {
const workbook = new ExcelJS.Workbook();
let worksheet;
if (format === 'csv') {
worksheet = await workbook.csv.readFile(inputPath);
} else {
await workbook.xlsx.readFile(inputPath);
[worksheet] = workbook.worksheets;
}
const rows = [];
worksheet.eachRow({ includeEmpty: true }, (row) => {
rows.push(row.values.slice(1).map((value) => value ?? ''));
});
return rows;
}
async function writeSpreadsheetRows(rows, outputPath, format) {
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('Sheet1');
worksheet.addRows(rows);
if (format === 'csv') {
await workbook.csv.writeFile(outputPath);
} else {
await workbook.xlsx.writeFile(outputPath);
}
}
async function convertCsvToXlsx(inputPath, outputPath) {
await writeSpreadsheetRows(await readSpreadsheetRows(inputPath, 'csv'), outputPath, 'xlsx');
}
async function convertXlsxToCsv(inputPath, outputPath) {
await writeSpreadsheetRows(await readSpreadsheetRows(inputPath, 'xlsx'), outputPath, 'csv');
}
async function convertCsvToHtml(inputPath, outputPath) {
await fs.writeFile(outputPath, rowsToHtmlDocument(await readSpreadsheetRows(inputPath, 'csv')));
}
async function convertXlsxToHtml(inputPath, outputPath) {
await fs.writeFile(outputPath, rowsToHtmlDocument(await readSpreadsheetRows(inputPath, 'xlsx')));
}
async function convertHtmlToCsv(inputPath, outputPath) {
const html = await fs.readFile(inputPath, 'utf8');
await writeSpreadsheetRows(parseHtmlTableToRows(html), outputPath, 'csv');
}
async function convertHtmlToXlsx(inputPath, outputPath) {
const html = await fs.readFile(inputPath, 'utf8');
await writeSpreadsheetRows(parseHtmlTableToRows(html), outputPath, 'xlsx');
}
async function convertCsvToMd(inputPath, outputPath) {
await fs.writeFile(outputPath, rowsToMarkdownTable(await readSpreadsheetRows(inputPath, 'csv')));
}
async function convertXlsxToMd(inputPath, outputPath) {
await fs.writeFile(outputPath, rowsToMarkdownTable(await readSpreadsheetRows(inputPath, 'xlsx')));
}
async function convertMdToCsv(inputPath, outputPath) {
const markdown = await fs.readFile(inputPath, 'utf8');
await writeSpreadsheetRows(parseMarkdownTableToRows(markdown), outputPath, 'csv');
}
async function convertMdToXlsx(inputPath, outputPath) {
const markdown = await fs.readFile(inputPath, 'utf8');
await writeSpreadsheetRows(parseMarkdownTableToRows(markdown), outputPath, 'xlsx');
}
async function convertCsvToTxt(inputPath, outputPath) {
await fs.writeFile(outputPath, rowsToTxt(await readSpreadsheetRows(inputPath, 'csv')));
}
async function convertXlsxToTxt(inputPath, outputPath) {
await fs.writeFile(outputPath, rowsToTxt(await readSpreadsheetRows(inputPath, 'xlsx')));
}
async function convertTxtToCsv(inputPath, outputPath) {
const text = await fs.readFile(inputPath, 'utf8');
await writeSpreadsheetRows(parseTxtToRows(text), outputPath, 'csv');
}
async function convertTxtToXlsx(inputPath, outputPath) {
const text = await fs.readFile(inputPath, 'utf8');
await writeSpreadsheetRows(parseTxtToRows(text), outputPath, 'xlsx');
}
async function convertCsvToDocx(inputPath, outputPath) {
await writeRowsAsDocx(await readSpreadsheetRows(inputPath, 'csv'), outputPath);
}
async function convertXlsxToDocx(inputPath, outputPath) {
await writeRowsAsDocx(await readSpreadsheetRows(inputPath, 'xlsx'), outputPath);
}
async function convertDocxToCsv(inputPath, outputPath) {
await writeSpreadsheetRows(await parseDocxToRows(inputPath), outputPath, 'csv');
}
async function convertDocxToXlsx(inputPath, outputPath) {
await writeSpreadsheetRows(await parseDocxToRows(inputPath), outputPath, 'xlsx');
}
async function convertCsvToPdf(inputPath, outputPath) {
await renderHtmlToPdf(rowsToHtmlDocument(await readSpreadsheetRows(inputPath, 'csv')), outputPath);
}
async function convertXlsxToPdf(inputPath, outputPath) {
await renderHtmlToPdf(rowsToHtmlDocument(await readSpreadsheetRows(inputPath, 'xlsx')), outputPath);
}
async function convertPdfToCsv(inputPath, outputPath) {
await writeSpreadsheetRows(await parsePdfToRows(inputPath), outputPath, 'csv');
}
async function convertPdfToXlsx(inputPath, outputPath) {
await writeSpreadsheetRows(await parsePdfToRows(inputPath), outputPath, 'xlsx');
}
export function registerDocumentConverters() { export function registerDocumentConverters() {
register({ family: 'document', sourceFormat: 'docx', targetFormat: 'html', convert: convertDocxToHtml }); register({ family: 'document', sourceFormat: 'docx', targetFormat: 'html', convert: convertDocxToHtml });
register({ family: 'document', sourceFormat: 'txt', targetFormat: 'pdf', convert: convertTxtToPdf }); register({ family: 'document', sourceFormat: 'txt', targetFormat: 'pdf', convert: convertTxtToPdf });
@@ -233,4 +462,27 @@ export function registerDocumentConverters() {
register({ family: 'document', sourceFormat: 'pdf', targetFormat: 'txt', convert: convertPdfToTxt }); register({ family: 'document', sourceFormat: 'pdf', targetFormat: 'txt', convert: convertPdfToTxt });
register({ family: 'document', sourceFormat: 'pdf', targetFormat: 'html', convert: convertPdfToHtml }); register({ family: 'document', sourceFormat: 'pdf', targetFormat: 'html', convert: convertPdfToHtml });
register({ family: 'document', sourceFormat: 'pdf', targetFormat: 'docx', convert: convertPdfToDocx }); register({ family: 'document', sourceFormat: 'pdf', targetFormat: 'docx', convert: convertPdfToDocx });
register({ family: 'document', sourceFormat: 'csv', targetFormat: 'xlsx', convert: convertCsvToXlsx });
register({ family: 'document', sourceFormat: 'xlsx', targetFormat: 'csv', convert: convertXlsxToCsv });
register({ family: 'document', sourceFormat: 'csv', targetFormat: 'html', convert: convertCsvToHtml });
register({ family: 'document', sourceFormat: 'xlsx', targetFormat: 'html', convert: convertXlsxToHtml });
register({ family: 'document', sourceFormat: 'html', targetFormat: 'csv', convert: convertHtmlToCsv });
register({ family: 'document', sourceFormat: 'html', targetFormat: 'xlsx', convert: convertHtmlToXlsx });
register({ family: 'document', sourceFormat: 'csv', targetFormat: 'md', convert: convertCsvToMd });
register({ family: 'document', sourceFormat: 'xlsx', targetFormat: 'md', convert: convertXlsxToMd });
register({ family: 'document', sourceFormat: 'md', targetFormat: 'csv', convert: convertMdToCsv });
register({ family: 'document', sourceFormat: 'md', targetFormat: 'xlsx', convert: convertMdToXlsx });
register({ family: 'document', sourceFormat: 'csv', targetFormat: 'txt', convert: convertCsvToTxt });
register({ family: 'document', sourceFormat: 'xlsx', targetFormat: 'txt', convert: convertXlsxToTxt });
register({ family: 'document', sourceFormat: 'txt', targetFormat: 'csv', convert: convertTxtToCsv });
register({ family: 'document', sourceFormat: 'txt', targetFormat: 'xlsx', convert: convertTxtToXlsx });
register({ family: 'document', sourceFormat: 'csv', targetFormat: 'docx', convert: convertCsvToDocx });
register({ family: 'document', sourceFormat: 'xlsx', targetFormat: 'docx', convert: convertXlsxToDocx });
register({ family: 'document', sourceFormat: 'docx', targetFormat: 'csv', convert: convertDocxToCsv });
register({ family: 'document', sourceFormat: 'docx', targetFormat: 'xlsx', convert: convertDocxToXlsx });
register({ family: 'document', sourceFormat: 'csv', targetFormat: 'pdf', convert: convertCsvToPdf });
register({ family: 'document', sourceFormat: 'xlsx', targetFormat: 'pdf', convert: convertXlsxToPdf });
register({ family: 'document', sourceFormat: 'pdf', targetFormat: 'csv', convert: convertPdfToCsv });
register({ family: 'document', sourceFormat: 'pdf', targetFormat: 'xlsx', convert: convertPdfToXlsx });
} }
+3
View File
@@ -14,6 +14,8 @@ const OUTPUT_MIME_TYPES = {
txt: 'text/plain', txt: 'text/plain',
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
md: 'text/markdown', md: 'text/markdown',
csv: 'text/csv',
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
}; };
export async function detectInputMime(filePath) { export async function detectInputMime(filePath) {
@@ -33,6 +35,7 @@ const UNDETECTABLE_TEXT_FORMATS = {
txt: 'text/plain', txt: 'text/plain',
html: 'text/html', html: 'text/html',
md: 'text/markdown', md: 'text/markdown',
csv: 'text/csv',
}; };
function normalizeFormat(format) { function normalizeFormat(format) {
+212
View File
@@ -0,0 +1,212 @@
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import fs from 'node:fs/promises';
import path from 'node:path';
import os from 'node:os';
import ExcelJS from 'exceljs';
import { PDFDocument, StandardFonts } from 'pdf-lib';
import { registerDocumentConverters } from '../../src/converters/document.js';
import { resolve } from '../../src/converters/registry.js';
import { detectInputMime } from '../../src/mime.js';
let tmpDir;
let csvFixturePath;
let xlsxFixturePath;
beforeAll(async () => {
registerDocumentConverters();
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'converter-spreadsheet-'));
csvFixturePath = path.join(tmpDir, 'fixture.csv');
await fs.writeFile(csvFixturePath, 'Name,Age\nAlice,30\nBob,25\n');
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('Sheet1');
worksheet.addRows([
['Name', 'Age'],
['Alice', 30],
['Bob', 25],
]);
xlsxFixturePath = path.join(tmpDir, 'fixture.xlsx');
await workbook.xlsx.writeFile(xlsxFixturePath);
});
afterAll(async () => {
await fs.rm(tmpDir, { recursive: true, force: true });
});
describe('spreadsheet converters', () => {
it('converts CSV to XLSX', async () => {
const outputPath = path.join(tmpDir, 'from-csv.xlsx');
const entry = resolve('csv', 'xlsx');
await entry.convert(csvFixturePath, outputPath);
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.readFile(outputPath);
const [worksheet] = workbook.worksheets;
expect(worksheet.getRow(2).values.slice(1)).toEqual(['Alice', 30]);
});
it('converts XLSX to CSV', async () => {
const outputPath = path.join(tmpDir, 'from-xlsx.csv');
const entry = resolve('xlsx', 'csv');
await entry.convert(xlsxFixturePath, outputPath);
const csv = await fs.readFile(outputPath, 'utf8');
expect(csv).toContain('Name,Age');
expect(csv).toContain('Alice,30');
});
it('converts CSV to HTML as a table', async () => {
const outputPath = path.join(tmpDir, 'from-csv.html');
const entry = resolve('csv', 'html');
await entry.convert(csvFixturePath, outputPath);
const html = await fs.readFile(outputPath, 'utf8');
expect(html).toContain('<table>');
expect(html).toContain('<td>Alice</td>');
});
it('converts HTML table to CSV', async () => {
const inputPath = path.join(tmpDir, 'fixture-table.html');
await fs.writeFile(
inputPath,
'<html><body><table><tr><td>Name</td><td>Age</td></tr><tr><td>Alice</td><td>30</td></tr></table></body></html>'
);
const outputPath = path.join(tmpDir, 'from-html.csv');
const entry = resolve('html', 'csv');
await entry.convert(inputPath, outputPath);
const csv = await fs.readFile(outputPath, 'utf8');
expect(csv).toContain('Name,Age');
expect(csv).toContain('Alice,30');
});
it('converts CSV to a Markdown table', async () => {
const outputPath = path.join(tmpDir, 'from-csv.md');
const entry = resolve('csv', 'md');
await entry.convert(csvFixturePath, outputPath);
const markdown = await fs.readFile(outputPath, 'utf8');
expect(markdown).toContain('| Name | Age |');
expect(markdown).toContain('| Alice | 30 |');
});
it('converts a Markdown table to CSV', async () => {
const inputPath = path.join(tmpDir, 'fixture-table.md');
await fs.writeFile(inputPath, '| Name | Age |\n| --- | --- |\n| Alice | 30 |\n');
const outputPath = path.join(tmpDir, 'from-md.csv');
const entry = resolve('md', 'csv');
await entry.convert(inputPath, outputPath);
const csv = await fs.readFile(outputPath, 'utf8');
expect(csv).toContain('Name,Age');
expect(csv).toContain('Alice,30');
});
it('converts CSV to tab-delimited TXT', async () => {
const outputPath = path.join(tmpDir, 'from-csv.txt');
const entry = resolve('csv', 'txt');
await entry.convert(csvFixturePath, outputPath);
const text = await fs.readFile(outputPath, 'utf8');
expect(text).toContain('Name\tAge');
expect(text).toContain('Alice\t30');
});
it('converts tab-delimited TXT to CSV', async () => {
const inputPath = path.join(tmpDir, 'fixture.txt');
await fs.writeFile(inputPath, 'Name\tAge\nAlice\t30\n');
const outputPath = path.join(tmpDir, 'from-txt.csv');
const entry = resolve('txt', 'csv');
await entry.convert(inputPath, outputPath);
const csv = await fs.readFile(outputPath, 'utf8');
expect(csv).toContain('Name,Age');
expect(csv).toContain('Alice,30');
});
it('converts CSV to DOCX containing a table', async () => {
const outputPath = path.join(tmpDir, 'from-csv.docx');
const entry = resolve('csv', 'docx');
await entry.convert(csvFixturePath, outputPath);
const mammoth = await import('mammoth');
const result = await mammoth.default.convertToHtml({ path: outputPath });
expect(result.value).toContain('Alice');
expect(result.value).toContain('<table>');
});
it('converts DOCX table to CSV', async () => {
const outputPath = path.join(tmpDir, 'from-docx.csv');
const entry = resolve('docx', 'csv');
await entry.convert(path.join(tmpDir, 'from-csv.docx'), outputPath);
const csv = await fs.readFile(outputPath, 'utf8');
expect(csv).toContain('Alice');
});
it('converts CSV to a valid PDF', async () => {
const outputPath = path.join(tmpDir, 'from-csv.pdf');
const entry = resolve('csv', 'pdf');
await entry.convert(csvFixturePath, outputPath);
const detected = await detectInputMime(outputPath);
expect(detected.mime).toBe('application/pdf');
}, 20000);
it('converts XLSX to HTML as a table', async () => {
const outputPath = path.join(tmpDir, 'from-xlsx.html');
const entry = resolve('xlsx', 'html');
await entry.convert(xlsxFixturePath, outputPath);
const html = await fs.readFile(outputPath, 'utf8');
expect(html).toContain('<td>Bob</td>');
});
it('converts HTML table to XLSX', async () => {
const inputPath = path.join(tmpDir, 'fixture-table-2.html');
await fs.writeFile(
inputPath,
'<html><body><table><tr><td>Name</td><td>Age</td></tr><tr><td>Carol</td><td>40</td></tr></table></body></html>'
);
const outputPath = path.join(tmpDir, 'from-html.xlsx');
const entry = resolve('html', 'xlsx');
await entry.convert(inputPath, outputPath);
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.readFile(outputPath);
const [worksheet] = workbook.worksheets;
expect(worksheet.getRow(2).values.slice(1)).toEqual(['Carol', '40']);
});
it('extracts PDF text into a CSV row (best-effort)', async () => {
const pdfDoc = await PDFDocument.create();
const page = pdfDoc.addPage([600, 400]);
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
page.drawText('Extractable fixture text', { x: 50, y: 350, size: 24, font });
const bytes = await pdfDoc.save();
const inputPath = path.join(tmpDir, 'fixture.pdf');
await fs.writeFile(inputPath, bytes);
const outputPath = path.join(tmpDir, 'from-pdf.csv');
const entry = resolve('pdf', 'csv');
await entry.convert(inputPath, outputPath);
const csv = await fs.readFile(outputPath, 'utf8');
expect(csv).toContain('Extractable fixture text');
});
});