commit 28769020ef299eee6da318c4e0c41d28c1f173a7 Author: pfych Date: Thu Jul 10 07:03:18 2025 +1000 Init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1711c14 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.out +output +node_modules +.idea diff --git a/package.json b/package.json new file mode 100644 index 0000000..99ed454 --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "bms-pack-cli", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "compile-watch": "esbuild --watch src/index.ts --platform=node --bundle --external:esbuild --sourcemap=both --outfile=./.out/build.js > /dev/null 2>&1", + "run-watch": "node --watch ./.out/build.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@types/cli-progress": "^3.11.6", + "@types/lodash": "^4.17.14", + "@types/node": "22.12.0", + "commander": "^13.1.0", + "esbuild": "^0.24.2", + "lodash": "^4.17.21", + "prettier": "3.4.2", + "typescript": "5.7.3" + }, + "dependencies": { + "@types/node-7z": "^2.1.10", + "bms-table-loader": "^2.0.0", + "cli-progress": "^3.12.0", + "glob": "^11.0.1", + "node-7z": "^3.0.0" + }, + "prettier": { + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "all", + "arrowParens": "always", + "printWidth": 80 + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..39a77e3 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,716 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@types/node-7z': + specifier: ^2.1.10 + version: 2.1.10 + bms-table-loader: + specifier: ^2.0.0 + version: 2.0.0 + cli-progress: + specifier: ^3.12.0 + version: 3.12.0 + glob: + specifier: ^11.0.1 + version: 11.0.1 + node-7z: + specifier: ^3.0.0 + version: 3.0.0 + devDependencies: + '@types/cli-progress': + specifier: ^3.11.6 + version: 3.11.6 + '@types/lodash': + specifier: ^4.17.14 + version: 4.17.14 + '@types/node': + specifier: 22.12.0 + version: 22.12.0 + commander: + specifier: ^13.1.0 + version: 13.1.0 + esbuild: + specifier: ^0.24.2 + version: 0.24.2 + lodash: + specifier: ^4.17.21 + version: 4.17.21 + prettier: + specifier: 3.4.2 + version: 3.4.2 + typescript: + specifier: 5.7.3 + version: 5.7.3 + +packages: + + '@esbuild/aix-ppc64@0.24.2': + resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.24.2': + resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.24.2': + resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.24.2': + resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.24.2': + resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.24.2': + resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.24.2': + resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.24.2': + resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.24.2': + resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.24.2': + resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.24.2': + resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.24.2': + resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.24.2': + resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.24.2': + resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.24.2': + resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.24.2': + resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.24.2': + resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.24.2': + resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.24.2': + resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.24.2': + resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.24.2': + resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.24.2': + resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.24.2': + resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.24.2': + resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.24.2': + resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@types/cli-progress@3.11.6': + resolution: {integrity: sha512-cE3+jb9WRlu+uOSAugewNpITJDt1VF8dHOopPO4IABFc3SXYL5WE/+PTz/FCdZRRfIujiWW3n3aMbv1eIGVRWA==} + + '@types/lodash@4.17.14': + resolution: {integrity: sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==} + + '@types/node-7z@2.1.10': + resolution: {integrity: sha512-LdfuQcGAKsLafyM96+F8VekToCuGQnFy9DMM0UdS6f5pEnaP5kAixp3TQPc1NJU4C6IwLwnednktYBjp/z2LRw==} + + '@types/node@22.12.0': + resolution: {integrity: sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + bms-table-loader@2.0.0: + resolution: {integrity: sha512-CFCwo6FLVPeDTL64/xhqAVA1dTim3Qm0nbT9nGtDTEMvlLjW83bE8IswUPzX8SSE+k4UCjJ9vgLveuaQ5KbWHQ==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + cli-progress@3.12.0: + resolution: {integrity: sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==} + engines: {node: '>=4'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@13.1.0: + resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} + engines: {node: '>=18'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + esbuild@0.24.2: + resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} + engines: {node: '>=18'} + hasBin: true + + foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + engines: {node: '>=14'} + + glob@11.0.1: + resolution: {integrity: sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==} + engines: {node: 20 || >=22} + hasBin: true + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@4.0.2: + resolution: {integrity: sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==} + engines: {node: 20 || >=22} + + lodash.defaultsdeep@4.6.1: + resolution: {integrity: sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==} + + lodash.defaultto@4.14.0: + resolution: {integrity: sha512-G6tizqH6rg4P5j32Wy4Z3ZIip7OfG8YWWlPFzUFGcYStH1Ld0l1tWs6NevEQNEDnO1M3NZYjuHuraaFSN5WqeQ==} + + lodash.flattendeep@4.4.0: + resolution: {integrity: sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==} + + lodash.isempty@4.4.0: + resolution: {integrity: sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==} + + lodash.negate@3.0.2: + resolution: {integrity: sha512-JGJYYVslKYC0tRMm/7igfdHulCjoXjoganRNWM8AgS+RXfOvFnPkOveDhPI65F9aAypCX9QEEQoBqWf7Q6uAeA==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + lru-cache@11.0.2: + resolution: {integrity: sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==} + engines: {node: 20 || >=22} + + minimatch@10.0.1: + resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==} + engines: {node: 20 || >=22} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + node-7z@3.0.0: + resolution: {integrity: sha512-KIznWSxIkOYO/vOgKQfJEaXd7rgoFYKZbaurainCEdMhYc7V7mRHX+qdf2HgbpQFcdJL/Q6/XOPrDLoBeTfuZA==} + engines: {node: '>=10'} + + node-fetch@2.6.2: + resolution: {integrity: sha512-aLoxToI6RfZ+0NOjmWAgn9+LEd30YCkJKFSyWacNZdEKTit/ZMcKjGkTRo8uWEsnIb/hfKecNPEbln02PdWbcA==} + engines: {node: 4.x || >=6.0.0} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@2.0.0: + resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} + engines: {node: 20 || >=22} + + prettier@3.4.2: + resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==} + engines: {node: '>=14'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + typescript@5.7.3: + resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + zod@3.24.1: + resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} + +snapshots: + + '@esbuild/aix-ppc64@0.24.2': + optional: true + + '@esbuild/android-arm64@0.24.2': + optional: true + + '@esbuild/android-arm@0.24.2': + optional: true + + '@esbuild/android-x64@0.24.2': + optional: true + + '@esbuild/darwin-arm64@0.24.2': + optional: true + + '@esbuild/darwin-x64@0.24.2': + optional: true + + '@esbuild/freebsd-arm64@0.24.2': + optional: true + + '@esbuild/freebsd-x64@0.24.2': + optional: true + + '@esbuild/linux-arm64@0.24.2': + optional: true + + '@esbuild/linux-arm@0.24.2': + optional: true + + '@esbuild/linux-ia32@0.24.2': + optional: true + + '@esbuild/linux-loong64@0.24.2': + optional: true + + '@esbuild/linux-mips64el@0.24.2': + optional: true + + '@esbuild/linux-ppc64@0.24.2': + optional: true + + '@esbuild/linux-riscv64@0.24.2': + optional: true + + '@esbuild/linux-s390x@0.24.2': + optional: true + + '@esbuild/linux-x64@0.24.2': + optional: true + + '@esbuild/netbsd-arm64@0.24.2': + optional: true + + '@esbuild/netbsd-x64@0.24.2': + optional: true + + '@esbuild/openbsd-arm64@0.24.2': + optional: true + + '@esbuild/openbsd-x64@0.24.2': + optional: true + + '@esbuild/sunos-x64@0.24.2': + optional: true + + '@esbuild/win32-arm64@0.24.2': + optional: true + + '@esbuild/win32-ia32@0.24.2': + optional: true + + '@esbuild/win32-x64@0.24.2': + optional: true + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@types/cli-progress@3.11.6': + dependencies: + '@types/node': 22.12.0 + + '@types/lodash@4.17.14': {} + + '@types/node-7z@2.1.10': + dependencies: + '@types/node': 22.12.0 + + '@types/node@22.12.0': + dependencies: + undici-types: 6.20.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + balanced-match@1.0.2: {} + + bms-table-loader@2.0.0: + dependencies: + node-fetch: 2.6.2 + zod: 3.24.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + cli-progress@3.12.0: + dependencies: + string-width: 4.2.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@13.1.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.0: + dependencies: + ms: 2.1.3 + + eastasianwidth@0.2.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + esbuild@0.24.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.24.2 + '@esbuild/android-arm': 0.24.2 + '@esbuild/android-arm64': 0.24.2 + '@esbuild/android-x64': 0.24.2 + '@esbuild/darwin-arm64': 0.24.2 + '@esbuild/darwin-x64': 0.24.2 + '@esbuild/freebsd-arm64': 0.24.2 + '@esbuild/freebsd-x64': 0.24.2 + '@esbuild/linux-arm': 0.24.2 + '@esbuild/linux-arm64': 0.24.2 + '@esbuild/linux-ia32': 0.24.2 + '@esbuild/linux-loong64': 0.24.2 + '@esbuild/linux-mips64el': 0.24.2 + '@esbuild/linux-ppc64': 0.24.2 + '@esbuild/linux-riscv64': 0.24.2 + '@esbuild/linux-s390x': 0.24.2 + '@esbuild/linux-x64': 0.24.2 + '@esbuild/netbsd-arm64': 0.24.2 + '@esbuild/netbsd-x64': 0.24.2 + '@esbuild/openbsd-arm64': 0.24.2 + '@esbuild/openbsd-x64': 0.24.2 + '@esbuild/sunos-x64': 0.24.2 + '@esbuild/win32-arm64': 0.24.2 + '@esbuild/win32-ia32': 0.24.2 + '@esbuild/win32-x64': 0.24.2 + + foreground-child@3.3.0: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + glob@11.0.1: + dependencies: + foreground-child: 3.3.0 + jackspeak: 4.0.2 + minimatch: 10.0.1 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.0 + + is-fullwidth-code-point@3.0.0: {} + + isexe@2.0.0: {} + + jackspeak@4.0.2: + dependencies: + '@isaacs/cliui': 8.0.2 + + lodash.defaultsdeep@4.6.1: {} + + lodash.defaultto@4.14.0: {} + + lodash.flattendeep@4.4.0: {} + + lodash.isempty@4.4.0: {} + + lodash.negate@3.0.2: {} + + lodash@4.17.21: {} + + lru-cache@11.0.2: {} + + minimatch@10.0.1: + dependencies: + brace-expansion: 2.0.1 + + minipass@7.1.2: {} + + ms@2.1.3: {} + + node-7z@3.0.0: + dependencies: + debug: 4.4.0 + lodash.defaultsdeep: 4.6.1 + lodash.defaultto: 4.14.0 + lodash.flattendeep: 4.4.0 + lodash.isempty: 4.4.0 + lodash.negate: 3.0.2 + normalize-path: 3.0.0 + transitivePeerDependencies: + - supports-color + + node-fetch@2.6.2: {} + + normalize-path@3.0.0: {} + + package-json-from-dist@1.0.1: {} + + path-key@3.1.1: {} + + path-scurry@2.0.0: + dependencies: + lru-cache: 11.0.2 + minipass: 7.1.2 + + prettier@3.4.2: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@4.1.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + typescript@5.7.3: {} + + undici-types@6.20.0: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + zod@3.24.1: {} diff --git a/src/commands/add-source.ts b/src/commands/add-source.ts new file mode 100644 index 0000000..ba5d6c5 --- /dev/null +++ b/src/commands/add-source.ts @@ -0,0 +1,86 @@ +import { glob } from 'glob'; +import { join } from 'path'; +import { configLocation } from '../utils/config'; +import { readFile, writeFile } from 'fs/promises'; +import { SingleBar } from 'cli-progress'; +import { chunk } from 'lodash'; +import { createHash } from 'crypto'; +import { loadChartCache } from '../utils/loadChartCache'; + +const chartCacheName = 'charts.json'; +const chartCacheLocation = join(configLocation, chartCacheName); + +export const addSource = async (path: string) => { + console.log(`Searching for BMS files in ${path}...`); + const bmsFiles = await glob( + `${path}/**/*.{bms,bml,bme,bmson,pms,pml,pme,pmson,dtx}`, + ); + + const chartCache = await loadChartCache(); + + console.log(`Filtering out already scanned files...`); + + const filterBar = new SingleBar({ + format: 'Filtering |{bar}| {percentage}% | ETA: {eta}s | {value}/{total}', + barCompleteChar: '\u2588', + barIncompleteChar: '\u2591', + etaBuffer: 50, + }); + + filterBar.start(bmsFiles.length, 0, { + speed: 'N/A', + }); + + const filesToScan = bmsFiles.filter((path) => { + filterBar.increment(1); + return !chartCache.hashedPaths.includes(path); + }); + + filterBar.stop(); + + console.log(`Hashing files...`); + + const hashBar = new SingleBar({ + format: 'Hashing |{bar}| {percentage}% | ETA: {eta}s | {value}/{total}', + barCompleteChar: '\u2588', + barIncompleteChar: '\u2591', + etaBuffer: 50, + }); + + hashBar.start(filesToScan.length, 0, { + speed: 'N/A', + }); + + const splitFiles = chunk(filesToScan, 100); + for (const files of splitFiles) { + const parsed = await Promise.all( + files.map(async (file) => { + const contents = await readFile(file); + const sha256 = createHash('sha256').update(contents).digest('hex'); + const md5 = createHash('md5').update(contents).digest('hex'); + + hashBar.increment(1); + + return { + path: file, + sha256, + md5, + }; + }), + ); + + for (const data of parsed) { + chartCache.hashes.sha256[data.sha256] = data.path; + chartCache.hashes.md5[data.md5] = data.path; + chartCache.hashedPaths.push(data.path); + } + + await writeFile(chartCacheLocation, JSON.stringify(chartCache, null, 2), { + encoding: 'utf-8', + }); + } + + hashBar.stop(); + + console.log(`Finished scanning BMS files at ${path}`); +}; diff --git a/src/commands/add-table.ts b/src/commands/add-table.ts new file mode 100644 index 0000000..774686e --- /dev/null +++ b/src/commands/add-table.ts @@ -0,0 +1,26 @@ +import { LoadBMSTable } from 'bms-table-loader'; +import { configLocation } from '../utils/config'; +import { writeFile } from 'fs/promises'; +import { join } from 'path'; +import { TableCache } from '../utils/types'; +import { loadTableCache } from '../utils/loadTableCache'; + +const tableCacheName = 'tables.json'; +const tableCacheLocation = join(configLocation, tableCacheName); + +export const addTable = async (url: string) => { + const table = await LoadBMSTable(url); + + let existingTableCache = await loadTableCache(); + const existingTables: TableCache = { + ...existingTableCache, + [table.head.name]: { + url, + data: table, + }, + }; + + await writeFile(tableCacheLocation, JSON.stringify(existingTables, null, 2), { + encoding: 'utf-8', + }); +}; diff --git a/src/commands/compress.ts b/src/commands/compress.ts new file mode 100644 index 0000000..5d9c19d --- /dev/null +++ b/src/commands/compress.ts @@ -0,0 +1,79 @@ +import { loadTableCache } from '../utils/loadTableCache'; +import { loadChartCache } from '../utils/loadChartCache'; +import { groupBy } from 'lodash'; +import { basename, dirname, resolve } from 'path'; +import { cp, mkdir, rm } from 'fs/promises'; +import { SingleBar } from 'cli-progress'; + +export const compress = async (tableName: string, level: string) => { + console.time('Pack Created'); + const tableCache = await loadTableCache(); + const chartCache = await loadChartCache(); + + const table = tableCache[tableName]; + + if (!table) { + console.log(`Table ${tableName} not found in cache`); + return; + } + + const tableData = table.data; + + const chartsGroupedByLevel = groupBy(tableData.body, 'content.level'); + + await mkdir(resolve('output', tableData.head.name), { recursive: true }); + + if (!chartsGroupedByLevel[level]) { + console.log(`No charts found for ${tableData.head.symbol}${level}`); + return; + } + + console.log(`Creating ${tableData.head.symbol}${level} pack...`); + + const chartsFromCache: string[] = []; + for (const chart of chartsGroupedByLevel[level]) { + const cacheEntry = + chartCache.hashes[chart.checksum.type][chart.checksum.value]; + + if (cacheEntry) { + chartsFromCache.push(dirname(cacheEntry)); + } + } + + if (!chartsFromCache.length) { + console.log(`No charts found for ${tableData.head.symbol}${level}`); + } + + const outputPath = resolve( + 'output', + tableData.head.name, + `${tableData.head.symbol}${level}`, + ); + + await mkdir(outputPath, { recursive: true }); + + const copyBar = new SingleBar({ + format: 'Copying |{bar}| {percentage}% | ETA: {eta}s | {value}/{total}', + barCompleteChar: '\u2588', + barIncompleteChar: '\u2591', + etaBuffer: 50, + }); + + copyBar.start(chartsFromCache.length, 0, { + speed: 'N/A', + }); + + await Promise.all( + chartsFromCache.map(async (chart) => { + await cp(chart, resolve(outputPath, basename(chart)), { + recursive: true, + errorOnExist: false, + }); + copyBar.increment(1); + }), + ); + + copyBar.stop(); + + console.timeEnd('Pack Created'); +}; diff --git a/src/commands/list-missing.ts b/src/commands/list-missing.ts new file mode 100644 index 0000000..225f66c --- /dev/null +++ b/src/commands/list-missing.ts @@ -0,0 +1,48 @@ +import { loadChartCache } from '../utils/loadChartCache'; +import { loadTableCache } from '../utils/loadTableCache'; +import { BMSTableEntry } from 'bms-table-loader'; +import { groupBy } from 'lodash'; + +export const listMissing = async (tableName: string) => { + const tableCache = await loadTableCache(); + const chartCache = await loadChartCache(); + + if (!tableCache[tableName]) { + console.log( + `Table ${tableName} not found in cache`, + Object.keys(tableCache), + ); + return; + } + + console.log(`Searching for missing charts from ${tableName}...`); + + const tableMeta = tableCache[tableName].data.head; + let foundMissingCharts: BMSTableEntry[] = []; + const tableCharts: BMSTableEntry[] = tableCache[tableName].data.body; + + for (const chart of tableCharts) { + if (!chartCache.hashes[chart.checksum.type][chart.checksum.value]) { + foundMissingCharts.push(chart); + } + } + + console.log(''); + for (const chart of foundMissingCharts) { + console.log( + `${tableMeta.symbol}${chart.content.level} ${chart.content?.title || 'Unknown Chart'}: ${chart.content?.url || 'No URL provided'}`, + ); + } + console.log(''); + console.log( + `${foundMissingCharts.length}/${tableCharts.length} charts missing from ${tableName}`, + ); + console.log(''); + const missingGroupedByLevel = groupBy(foundMissingCharts, 'content.level'); + const chartsGroupedByLevel = groupBy(tableCharts, 'content.level'); + for (const level of Object.keys(missingGroupedByLevel)) { + console.log( + `${tableMeta.symbol}${level}: ${chartsGroupedByLevel[level].length - missingGroupedByLevel[level].length}/${chartsGroupedByLevel[level].length} (Missing ${missingGroupedByLevel[level].length})`, + ); + } +}; diff --git a/src/commands/list-tables.ts b/src/commands/list-tables.ts new file mode 100644 index 0000000..a2eaa77 --- /dev/null +++ b/src/commands/list-tables.ts @@ -0,0 +1,18 @@ +import { configLocation } from '../utils/config'; +import { readFile } from 'fs/promises'; +import { join } from 'path'; +import { TableCache } from '../utils/types'; + +const tableCacheName = 'tables.json'; +const tableCacheLocation = join(configLocation, tableCacheName); + +export const listTables = async () => { + const existingTableCache = await readFile(tableCacheLocation, 'utf-8'); + const existingTables: TableCache = { + ...JSON.parse(existingTableCache || '{}'), + }; + + for (const table of Object.values(existingTables)) { + console.log(`${table.data.head.name}: ${table.url}`); + } +}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..8043b4a --- /dev/null +++ b/src/index.ts @@ -0,0 +1,51 @@ +import { Command } from 'commander'; +import { mkdirSync } from 'fs'; +import { addTable } from './commands/add-table'; +import { configLocation } from './utils/config'; +import { listTables } from './commands/list-tables'; +import { addSource } from './commands/add-source'; +import { listMissing } from './commands/list-missing'; +import { compress } from './commands/compress'; + +const program = new Command(); + +mkdirSync(configLocation, { recursive: true }); + +program + .name('bms-pack-cli') + .description('CLI to create BMS packs') + .version('0.0.1'); + +program + .command('help') + .description('Show help') + .action(() => program.help()); + +program + .command('add-table') + .argument('url', 'URL of the table') + .description('Add a table to the pack') + .action((url) => addTable(url)); + +program + .command('list-tables') + .description('List tables cached') + .action(() => listTables()); + +program + .command('add-source') + .argument('path', 'Path to the source') + .action((path) => addSource(path)); + +program + .command('list-missing') + .argument('table') + .action((table) => listMissing(table)); + +program + .command('compress') + .argument('table') + .argument('level') + .action((table, level) => compress(table, level)); + +program.parse(); diff --git a/src/utils/config.ts b/src/utils/config.ts new file mode 100644 index 0000000..889bff9 --- /dev/null +++ b/src/utils/config.ts @@ -0,0 +1,6 @@ +import { join } from 'path'; + +export const configLocation = join( + process.env.XDG_CONFIG_HOME || '~/.config', + 'bms-pack-cli', +); diff --git a/src/utils/loadChartCache.ts b/src/utils/loadChartCache.ts new file mode 100644 index 0000000..946f396 --- /dev/null +++ b/src/utils/loadChartCache.ts @@ -0,0 +1,29 @@ +import { ChartCache } from './types'; +import { existsSync } from 'fs'; +import { readFile } from 'fs/promises'; +import { join } from 'path'; +import { configLocation } from './config'; + +const chartCacheName = 'charts.json'; +const chartCacheLocation = join(configLocation, chartCacheName); + +export const loadChartCache = async (): Promise => { + let existingChartCache: string | undefined; + if (existsSync(chartCacheLocation)) { + existingChartCache = await readFile(chartCacheLocation, { + encoding: 'utf-8', + }); + } else { + /// MMMM smelly code .... + // @TODO make this not shit + existingChartCache = JSON.stringify({ + hashedPaths: [], + hashes: { + sha256: {}, + md5: {}, + }, + }); + } + + return JSON.parse(existingChartCache || '{}') as ChartCache; +}; diff --git a/src/utils/loadTableCache.ts b/src/utils/loadTableCache.ts new file mode 100644 index 0000000..8ca3f5f --- /dev/null +++ b/src/utils/loadTableCache.ts @@ -0,0 +1,19 @@ +import { TableCache } from './types'; +import { existsSync } from 'fs'; +import { readFile } from 'fs/promises'; +import { join } from 'path'; +import { configLocation } from './config'; + +const tableCacheName = 'tables.json'; +const tableCacheLocation = join(configLocation, tableCacheName); + +export const loadTableCache = async (): Promise => { + let existingTableCache: string | undefined; + if (existsSync(tableCacheLocation)) { + existingTableCache = await readFile(tableCacheLocation, { + encoding: 'utf-8', + }); + } + + return JSON.parse(existingTableCache || '{}') as TableCache; +}; diff --git a/src/utils/types.ts b/src/utils/types.ts new file mode 100644 index 0000000..155be25 --- /dev/null +++ b/src/utils/types.ts @@ -0,0 +1,11 @@ +import { BMSTable } from 'bms-table-loader'; + +export type TableCache = Record; + +export interface ChartCache { + hashedPaths: string[]; + hashes: { + sha256: Record; + md5: Record; + }; +}