From 7fe070854dceb852284a5939c753e45940cb46b7 Mon Sep 17 00:00:00 2001 From: sugoidogo Date: Tue, 2 Dec 2025 20:19:51 -0800 Subject: [PATCH] redo project structure --- .github/workflows/release.yaml | 23 +- .gitignore | 9 +- LICENCE.md | Bin 0 -> 85364 bytes README.md | 61 +++ package-lock.json | 475 +++++++++++++++++- package.json | 39 +- scripts/compile.ts | 8 +- scripts/test.ts | 54 ++- src/{ => cli}/cli.ts | 50 +- src/{ => cli}/logLevel.ts | 0 src/{ => cli}/spawnSync.ts | 0 tsconfig.json => src/cli/tsconfig.json | 11 +- src/lib/packwiz.ts | 648 +++++++++++++++++++++++++ src/lib/tsconfig.json | 16 + 14 files changed, 1327 insertions(+), 67 deletions(-) create mode 100644 LICENCE.md create mode 100644 README.md rename src/{ => cli}/cli.ts (66%) rename src/{ => cli}/logLevel.ts (100%) rename src/{ => cli}/spawnSync.ts (100%) rename tsconfig.json => src/cli/tsconfig.json (60%) create mode 100644 src/lib/packwiz.ts create mode 100644 src/lib/tsconfig.json diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index a2f7bfd..e28e0df 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -4,10 +4,15 @@ on: push: tags: - "*" - workflow_dispatch: permissions: contents: write + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false jobs: release: @@ -25,10 +30,24 @@ jobs: run: | npm install npm run compile + npm run docs + npm pack + mv packwiz-extras-lib*.tgz packwiz-extras-lib.tgz git log $(git describe --tags --abbrev=0 HEAD~1)..HEAD --oneline > changelog.txt - name: Release uses: softprops/action-gh-release@v2 with: make_latest: true body_path: changelog.txt - files: bin/*/* + files: | + packwiz-extras-lib.tgz + bin/*/* + - name: Setup Pages + uses: actions/configure-pages@v5 + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: './docs' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index a8cd699..79acb33 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ node_modules -bin +test +*.zip +dist .env -modpack.zip -test \ No newline at end of file +*.tgz +docs +bin \ No newline at end of file diff --git a/LICENCE.md b/LICENCE.md new file mode 100644 index 0000000000000000000000000000000000000000..7383dbd0f0b52b676754f118833892ccedbcd895 GIT binary patch literal 85364 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 Fe*w=D###UX literal 0 HcmV?d00001 diff --git a/README.md b/README.md new file mode 100644 index 0000000..db67dc8 --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +# Packwiz Extras + +This is a small helper program/library to implement some missing features from +[packwiz](https://github.com/packwiz/packwiz). +The CLI is licenced under GPLv3 or later. +The library is licenced under LGPLv3 or later. + +## CLI Usage + +``` +Usage: packwiz-extras [options] [command] + +extra utilities for packwiz + +Options: + --pack-file The modpack file to use (default: "pack.toml") + -h, --help display help for command + +Commands: + curseforge|cf [options] manage curseforge files + modrinth|mr [options] manage modrinth files + help [command] display help for command +``` +curseforge commands: +``` +Usage: packwiz-extras curseforge|cf [options] [command] + +manage curseforge files + +Options: + --api-key Your curseforge api key. This is required by curseforge to access their API. Can also be provided via environment variable or .env file + as CF_API_KEY + -h, --help display help for command + +Commands: + detect detect and replace files availible on curseforge + urls cache curseforge download urls to speed up packwiz-installer + help [command] display help for command +``` +modrinth commands: +``` +Usage: packwiz-extras modrinth|mr [options] [command] + +manage modrinth files + +Options: + --api-key Your modrinth api key, optional. + -h, --help display help for command + +Commands: + detect detect and replace files availible on modrinth + merge detect curseforge metafiles which are also availible on modrinth and merge their metadata + help [command] display help for command +``` +## Library Usage +For now, you can get the tarball for this library from the releases. +To add it to your npm project, copy the url and do: +``` +npm install https://github.com/sugoidogo/packwiz-extras/releases/download/x.y.z/packwiz-extras.tgz +``` +Library documentation can be found [online](https://sugoidogo.github.io/packwiz-extras/) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 50cf215..8ff2685 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,27 @@ { - "name": "packwiz-extras-cli", + "name": "packwiz-extras", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { + "name": "packwiz-extras", + "version": "0.2.0", + "license": "LGPL-3.0-or-later", "dependencies": { "commander": "^14.0.2", "confbox": "^0.2.2", - "deno": "^2.5.6", - "dotenv": "^17.2.3", - "packwiz-extras-lib": "https://github.com/sugoidogo/packwiz-extras-lib/releases/download/2.1.0/packwiz-extras-lib.tgz" + "murmur2": "^1.0.2", + "workerless": "https://github.com/sugoidogo/workerless/releases/download/0.1.0/workerless.tgz" }, "devDependencies": { - "@types/node": "^24.10.1" + "@types/node": "^24.10.1", + "@types/web": "^0.0.294", + "bun": "^1.3.3", + "deno": "^2.5.6", + "dotenv": "^17.2.3", + "typedoc": "^0.28.15", + "typescript": "^5.9.3" } }, "node_modules/@deno/darwin-arm64": { @@ -22,6 +31,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -35,6 +45,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -48,6 +59,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -61,6 +73,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -74,6 +87,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -87,12 +101,240 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ] }, + "node_modules/@gerrit0/mini-shiki": { + "version": "3.18.0", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.18.0.tgz", + "integrity": "sha512-zTAG1cXK5Q+T6CBEa8mqEnCx/H9rrpWEn+vhMbWikzmeO2jltY6zVE2m9YCO+xDi+P0vpBrOG1Xgi8AZtlNoUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-oniguruma": "^3.18.0", + "@shikijs/langs": "^3.18.0", + "@shikijs/themes": "^3.18.0", + "@shikijs/types": "^3.18.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@oven/bun-darwin-aarch64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-aarch64/-/bun-darwin-aarch64-1.3.3.tgz", + "integrity": "sha512-eJopQrUk0WR7jViYDC29+Rp50xGvs4GtWOXBeqCoFMzutkkO3CZvHehA4JqnjfWMTSS8toqvRhCSOpOz62Wf9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-darwin-x64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64/-/bun-darwin-x64-1.3.3.tgz", + "integrity": "sha512-xGDePueVFrNgkS+iN0QdEFeRrx2MQ5hQ9ipRFu7N73rgoSSJsFlOKKt2uGZzunczedViIfjYl0ii0K4E9aZ0Ow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-darwin-x64-baseline": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64-baseline/-/bun-darwin-x64-baseline-1.3.3.tgz", + "integrity": "sha512-1ij4wQ9ECLFf1XFry+IFUN+28if40ozDqq6+QtuyOhIwraKzXOlAUbILhRMGvM3ED3yBex2mTwlKpA4Vja/V2g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-linux-aarch64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64/-/bun-linux-aarch64-1.3.3.tgz", + "integrity": "sha512-DabZ3Mt1XcJneWdEEug8l7bCPVvDBRBpjUIpNnRnMFWFnzr8KBEpMcaWTwYOghjXyJdhB4MPKb19MwqyQ+FHAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-aarch64-musl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64-musl/-/bun-linux-aarch64-musl-1.3.3.tgz", + "integrity": "sha512-XWQ3tV/gtZj0wn2AdSUq/tEOKWT4OY+Uww70EbODgrrq00jxuTfq5nnYP6rkLD0M/T5BHJdQRSfQYdIni9vldw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64/-/bun-linux-x64-1.3.3.tgz", + "integrity": "sha512-7eIARtKZKZDtah1aCpQUj/1/zT/zHRR063J6oAxZP9AuA547j5B9OM2D/vi/F4En7Gjk9FPjgPGTSYeqpQDzJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64-baseline": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-baseline/-/bun-linux-x64-baseline-1.3.3.tgz", + "integrity": "sha512-IU8pxhIf845psOv55LqJyL+tSUc6HHMfs6FGhuJcAnyi92j+B1HjOhnFQh9MW4vjoo7do5F8AerXlvk59RGH2w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64-musl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-musl/-/bun-linux-x64-musl-1.3.3.tgz", + "integrity": "sha512-xNSDRPn1yyObKteS8fyQogwsS4eCECswHHgaKM+/d4wy/omZQrXn8ZyGm/ZF9B73UfQytUfbhE7nEnrFq03f0w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64-musl-baseline": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-x64-musl-baseline/-/bun-linux-x64-musl-baseline-1.3.3.tgz", + "integrity": "sha512-JoRTPdAXRkNYouUlJqEncMWUKn/3DiWP03A7weBbtbsKr787gcdNna2YeyQKCb1lIXE4v1k18RM3gaOpQobGIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-windows-x64": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@oven/bun-windows-x64/-/bun-windows-x64-1.3.3.tgz", + "integrity": "sha512-kWqa1LKvDdAIzyfHxo3zGz3HFWbFHDlrNK77hKjUN42ycikvZJ+SHSX76+1OW4G8wmLETX4Jj+4BM1y01DQRIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oven/bun-windows-x64-baseline": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@oven/bun-windows-x64-baseline/-/bun-windows-x64-baseline-1.3.3.tgz", + "integrity": "sha512-u5eZHKq6TPJSE282KyBOicGQ2trkFml0RoUfqkPOJVo7TXGrsGYYzdsugZRnVQY/WEmnxGtBy4T3PAaPqgQViA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.18.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.18.0.tgz", + "integrity": "sha512-15+O2iy+nYU/IdiBIExXuK0JJABa/8tdnRDODBmLhdygQ43aCuipN5N9vTfS8jvkMByHMR09b5jtX2la0CCoOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.18.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.18.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.18.0.tgz", + "integrity": "sha512-Deq7ZoYBtimN0M8pD5RU5TKz7DhUSTPtQOBuJpMxPDDJ+MJ7nT90DEmhDM2V0Nzp6DjfTAd+Z7ibpzr8arWqiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.18.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.18.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.18.0.tgz", + "integrity": "sha512-wzg6vNniXC5J4ChNBJJIZFTWxmrERJMWknehmM++0OAKJqZ41WpnO7PmPOumvMsUaL1SC08Nb/JVdaJd2aTsZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.18.0" + } + }, + "node_modules/@shikijs/types": { + "version": "3.18.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.18.0.tgz", + "integrity": "sha512-YLmpuroH06TpvqRXKR0YqlI0nQ56c8+BO/m9A9ht36WRdxmML4ivUsnpXuJU7PiClLRD2M66ilY2YJ0KE+8q7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/node": { "version": "24.10.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", @@ -103,6 +345,78 @@ "undici-types": "~7.16.0" } }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/web": { + "version": "0.0.294", + "resolved": "https://registry.npmjs.org/@types/web/-/web-0.0.294.tgz", + "integrity": "sha512-prZi2M3jd/lGeOhW9P3txXfn6/nXIJRn+w/aFHz94/dl04riO5V99n+sKSeIciq7FM2XY6wPWoNhNbm6S/AdMQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/bun": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/bun/-/bun-1.3.3.tgz", + "integrity": "sha512-2hJ4ocTZ634/Ptph4lysvO+LbbRZq8fzRvMwX0/CqaLBxrF2UB5D1LdMB8qGcdtCer4/VR9Bx5ORub0yn+yzmw==", + "cpu": [ + "arm64", + "x64" + ], + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "os": [ + "darwin", + "linux", + "win32" + ], + "bin": { + "bun": "bin/bun.exe", + "bunx": "bin/bunx.exe" + }, + "optionalDependencies": { + "@oven/bun-darwin-aarch64": "1.3.3", + "@oven/bun-darwin-x64": "1.3.3", + "@oven/bun-darwin-x64-baseline": "1.3.3", + "@oven/bun-linux-aarch64": "1.3.3", + "@oven/bun-linux-aarch64-musl": "1.3.3", + "@oven/bun-linux-x64": "1.3.3", + "@oven/bun-linux-x64-baseline": "1.3.3", + "@oven/bun-linux-x64-musl": "1.3.3", + "@oven/bun-linux-x64-musl-baseline": "1.3.3", + "@oven/bun-windows-x64": "1.3.3", + "@oven/bun-windows-x64-baseline": "1.3.3" + } + }, "node_modules/commander": { "version": "14.0.2", "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", @@ -122,6 +436,7 @@ "version": "2.5.6", "resolved": "https://registry.npmjs.org/deno/-/deno-2.5.6.tgz", "integrity": "sha512-SSe2gCf41A/uln54H7xdKZFtiDt+reBxYM4IaJLRH/NpEIE5T3VOcLSLHPaJzyiTvZe2xMz1QFcpg4cco5rNsA==", + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -140,6 +455,7 @@ "version": "17.2.3", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -148,23 +464,138 @@ "url": "https://dotenvx.com" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/murmur2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/murmur2/-/murmur2-1.0.2.tgz", "integrity": "sha512-r9dVsfnmykIYovQH9oP2JeZu5/DYa9rZpSQw2FWR/VDoI0oxm/u21NF46bFH5h+x2Xv4q5+jHzsX95N6m0UO3Q==", "license": "MIT" }, - "node_modules/packwiz-extras-lib": { - "version": "2.1.0", - "resolved": "https://github.com/sugoidogo/packwiz-extras-lib/releases/download/2.1.0/packwiz-extras-lib.tgz", - "integrity": "sha512-mnP3x9TB/wN4a5Zs1l8pOBaBlr2B2spwHBdQ2vwrH+dMpzNb4Y/7LkzaBdQe8euQjBe2E9SmTyu71CPevmgeQQ==", - "license": "LGPL-3.0-or-later", - "dependencies": { - "confbox": "^0.2.2", - "murmur2": "^1.0.2", - "workerless": "https://github.com/sugoidogo/workerless/releases/download/0.1.0/workerless.tgz" + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" } }, + "node_modules/typedoc": { + "version": "0.28.15", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.15.tgz", + "integrity": "sha512-mw2/2vTL7MlT+BVo43lOsufkkd2CJO4zeOSuWQQsiXoV2VuEn7f6IZp2jsUDPmBMABpgR0R5jlcJ2OGEFYmkyg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@gerrit0/mini-shiki": "^3.17.0", + "lunr": "^2.3.9", + "markdown-it": "^14.1.0", + "minimatch": "^9.0.5", + "yaml": "^2.8.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 18", + "pnpm": ">= 10" + }, + "peerDependencies": { + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, "node_modules/undici-types": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", @@ -186,6 +617,22 @@ "dependencies": { "web-worker": "^1.5.0" } + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } } } } diff --git a/package.json b/package.json index 68b2682..41e1f23 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,46 @@ { + "name": "packwiz-extras", + "version": "0.2.0", + "description": "ts/js library for node and web for working with packwiz modpacks", + "keywords": [ + "minecraft" + ], + "homepage": "https://github.com/sugoidogo/packwiz-extras#readme", + "bugs": { + "url": "https://github.com/sugoidogo/packwiz-extras/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/sugoidogo/packwiz-extras.git" + }, + "license": "LGPL-3.0-or-later", + "author": "SugoiDogo", "type": "module", + "main": "dist/packwiz.js", + "types": "./dist/packwiz.d.ts", + "files": [ + "dist", + "src" + ], "scripts": { "test": "node scripts/test.ts", - "compile": "node scripts/compile.ts" - }, - "devDependencies": { - "@types/node": "^24.10.1" + "prepack": "tsc -p src/lib/tsconfig.json", + "compile": "node scripts/compile.ts", + "docs": "typedoc src/lib/packwiz.ts" }, "dependencies": { "commander": "^14.0.2", "confbox": "^0.2.2", + "murmur2": "^1.0.2", + "workerless": "https://github.com/sugoidogo/workerless/releases/download/0.1.0/workerless.tgz" + }, + "devDependencies": { + "@types/node": "^24.10.1", + "@types/web": "^0.0.294", + "bun": "^1.3.3", "deno": "^2.5.6", "dotenv": "^17.2.3", - "packwiz-extras-lib": "https://github.com/sugoidogo/packwiz-extras-lib/releases/download/2.1.0/packwiz-extras-lib.tgz" + "typedoc": "^0.28.15", + "typescript": "^5.9.3" } } diff --git a/scripts/compile.ts b/scripts/compile.ts index d44db2a..da544a6 100644 --- a/scripts/compile.ts +++ b/scripts/compile.ts @@ -1,7 +1,7 @@ import spawnSync from "./spawnSync.ts" const deno = true // works, but produces largest executables -const bun = false // has weird issues +const bun = false // error: https://api.curseforge.com/v1/fingerprints: Forbidden const pkg = false // smallest executables but don't work if (deno) { @@ -17,7 +17,7 @@ if (deno) { spawnSync('deno', [ 'compile', '--allow-all', '--target', target, '--output', 'bin/deno/packwiz-extras-' + target, - '--no-check', 'src/cli.ts' + '--no-check', 'src/cli/cli.ts' ], { stdio: 'inherit', shell: true }) } } @@ -37,7 +37,7 @@ if (bun) { 'build', '--compile', '--target', target, '--outfile', 'bin/bun/packwiz-extras-' + target, - 'src/cli.ts' + 'src/cli/cli.ts' ], { stdio: 'inherit', shell: true }) } } @@ -57,6 +57,6 @@ if (pkg) { //'--sea', // doesn't build with this '--compress', 'GZip', //'--compress', 'Brotli', // same size as GZip and the internet says GZip is better - 'src/cli.ts' + 'src/cli/cli.ts' ], { stdio: 'inherit', shell: true }) } \ No newline at end of file diff --git a/scripts/test.ts b/scripts/test.ts index 4197700..b08c799 100644 --- a/scripts/test.ts +++ b/scripts/test.ts @@ -1,13 +1,53 @@ import fs from 'node:fs' import spawnSync from './spawnSync.ts' import download from './downloadProgress.ts' +import { performance } from 'node:perf_hooks' + +const deno = true // primary runtime +const bun = false // error: https://api.curseforge.com/v1/fingerprints: Forbidden +const node = true // supported runtime await download('https://mediafilez.forgecdn.net/files/7223/56/All%20the%20Mods%2010-5.1.zip', 'modpack.zip') +if (fs.existsSync('test')) fs.rmSync('test', { recursive: true }) +const times: any = {} -fs.mkdirSync('test', { recursive: true }) -spawnSync('packwiz', ['curseforge', 'import', '../modpack.zip'], { cwd: 'test' }) -spawnSync('deno', ['run', '--allow-all', 'src/cli.ts', '--pack-file=test/pack.toml', 'cf','detect']) -spawnSync('deno', ['run', '--allow-all', 'src/cli.ts', '--pack-file=test/pack.toml', 'cf','urls']) -spawnSync('deno', ['run', '--allow-all', 'src/cli.ts', '--pack-file=test/pack.toml', 'mr','detect']) -spawnSync('deno', ['run', '--allow-all', 'src/cli.ts', '--pack-file=test/pack.toml', 'mr','merge']) -fs.rmSync('test', { recursive: true }) \ No newline at end of file +if (node) { + fs.mkdirSync('test', { recursive: true }) + spawnSync('packwiz', ['curseforge', 'import', '../modpack.zip'], { cwd: 'test' }) + const start = performance.now() + spawnSync('node', ['src/cli/cli.ts', '--pack-file=test/pack.toml', 'cf', 'detect']) + spawnSync('node', ['src/cli/cli.ts', '--pack-file=test/pack.toml', 'cf', 'urls']) + spawnSync('node', ['src/cli/cli.ts', '--pack-file=test/pack.toml', 'mr', 'detect']) + spawnSync('node', ['src/cli/cli.ts', '--pack-file=test/pack.toml', 'mr', 'merge']) + const end = performance.now() + times['node'] = (end - start) / 1000 + fs.rmSync('test', { recursive: true }) +} + +if (deno) { + fs.mkdirSync('test', { recursive: true }) + spawnSync('packwiz', ['curseforge', 'import', '../modpack.zip'], { cwd: 'test' }) + const start = performance.now() + spawnSync('deno', ['run', '--allow-all', 'src/cli/cli.ts', '--pack-file=test/pack.toml', 'cf', 'detect']) + spawnSync('deno', ['run', '--allow-all', 'src/cli/cli.ts', '--pack-file=test/pack.toml', 'cf', 'urls']) + spawnSync('deno', ['run', '--allow-all', 'src/cli/cli.ts', '--pack-file=test/pack.toml', 'mr', 'detect']) + spawnSync('deno', ['run', '--allow-all', 'src/cli/cli.ts', '--pack-file=test/pack.toml', 'mr', 'merge']) + const end = performance.now() + times['deno'] = (end - start) / 1000 + fs.rmSync('test', { recursive: true }) +} + +if (bun) { + fs.mkdirSync('test', { recursive: true }) + spawnSync('packwiz', ['curseforge', 'import', '../modpack.zip'], { cwd: 'test' }) + const start = performance.now() + spawnSync('bun', ['run', 'src/cli/cli.ts', '--pack-file=test/pack.toml', 'cf', 'detect']) + spawnSync('bun', ['run', 'src/cli/cli.ts', '--pack-file=test/pack.toml', 'cf', 'urls']) + spawnSync('bun', ['run', 'src/cli/cli.ts', '--pack-file=test/pack.toml', 'mr', 'detect']) + spawnSync('bun', ['run', 'src/cli/cli.ts', '--pack-file=test/pack.toml', 'mr', 'merge']) + const end = performance.now() + times['bun'] = (end - start) / 1000 + fs.rmSync('test', { recursive: true }) +} + +console.log(times) \ No newline at end of file diff --git a/src/cli.ts b/src/cli/cli.ts similarity index 66% rename from src/cli.ts rename to src/cli/cli.ts index b93313f..f388891 100644 --- a/src/cli.ts +++ b/src/cli/cli.ts @@ -1,6 +1,6 @@ -import { Command, program } from "commander" +import { program } from "commander" import { config } from "dotenv" -import * as packwiz from 'packwiz-extras-lib' +import * as packwiz from '../lib/packwiz.ts' import * as fs from 'node:fs/promises' import { stringifyTOML } from 'confbox' import spawnSync from "./spawnSync.ts" @@ -10,60 +10,56 @@ async function refresh(packFile: packwiz.Path) { spawnSync('packwiz', ['refresh'], { cwd }) } -async function replace(results: Map) { +async function apply(result:packwiz.Result) { const jobs = [] - for (const [path, mod] of results.entries()) { - jobs.push(fs.rm(path)) - jobs.push(fs.writeFile(path + '.pw.toml', stringifyTOML(mod))) + if (result.delete !== undefined) { + for (const path of result.delete) { + jobs.push(fs.rm(path)) + } } - await Promise.all(jobs) -} - -async function overwrite(results: Map) { - const jobs = [] - for (const [path, mod] of results.entries()) { - jobs.push(fs.writeFile(path, stringifyTOML(mod))) + if (result.write!==undefined) { + for (const [path, data] of Object.entries(result.write)) { + jobs.push(fs.writeFile(path,stringifyTOML(data))) + } } - await Promise.all(jobs) } config({ 'quiet': true }) program.name('packwiz-extras') .description('extra utilities for packwiz') - .option('--pack-file ', 'The modpack file to use (default "pack.toml")', 'pack.toml') + .option('--pack-file ', 'The modpack file to use', 'pack.toml') const curseforge = program.command('curseforge').alias('cf') .description('manage curseforge files') -const cfKey = ['--api-key ', +const cfKey:string[] = ['--api-key ', 'Your curseforge api key. This is required by curseforge to access their API. ' + - 'Can also be provided via environment variable or .env file as CF_API_KEY', - process.env.CF_API_KEY] + 'Can also be provided via environment variable or .env file as CF_API_KEY',] -if (cfKey[2]) { - curseforge.option(cfKey[0], cfKey[1], cfKey[2]) +if (process.env.CF_API_KEY) { + curseforge.option(cfKey[0], cfKey[1]) } else { - curseforge.requiredOption(cfKey[0], cfKey[1], cfKey[2]) + curseforge.requiredOption(cfKey[0], cfKey[1]) } curseforge.command('detect') .description('detect and replace files availible on curseforge') .action(async function () { - const apiKey = curseforge.opts().apiKey + const apiKey = curseforge.opts().apiKey || process.env.CF_API_KEY const packFile = program.opts().packFile const result = await packwiz.cfDetect(packFile, apiKey) - await replace(result) + await apply(result) await refresh(packFile) }) curseforge.command('urls') .description('cache curseforge download urls to speed up packwiz-installer') .action(async function () { - const apiKey = curseforge.opts().apiKey + const apiKey = curseforge.opts().apiKey || process.env.CF_API_KEY const packFile = program.opts().packFile const result = await packwiz.cfUrl(packFile, apiKey) - await overwrite(result) + await apply(result) await refresh(packFile) }) @@ -78,7 +74,7 @@ modrith.command('detect') const apiKey = modrith.opts().apiKey const packFile = program.opts().packFile const result = await packwiz.mrDetect(packFile, apiKey) - await replace(result) + await apply(result) await refresh(packFile) }) @@ -88,7 +84,7 @@ modrith.command('merge') const apiKey = modrith.opts().apiKey const packFile = program.opts().packFile const result = await packwiz.mrMerge(packFile, apiKey) - await overwrite(result) + await apply(result) await refresh(packFile) }) diff --git a/src/logLevel.ts b/src/cli/logLevel.ts similarity index 100% rename from src/logLevel.ts rename to src/cli/logLevel.ts diff --git a/src/spawnSync.ts b/src/cli/spawnSync.ts similarity index 100% rename from src/spawnSync.ts rename to src/cli/spawnSync.ts diff --git a/tsconfig.json b/src/cli/tsconfig.json similarity index 60% rename from tsconfig.json rename to src/cli/tsconfig.json index 82c07c1..8c91052 100644 --- a/tsconfig.json +++ b/src/cli/tsconfig.json @@ -3,10 +3,11 @@ "target": "es2022", "module": "es2022", "moduleResolution": "node", + "lib": ["ESNext"], + "noEmit": true, "allowImportingTsExtensions": true, - "noEmit": true - }, - "include": [ - "src/" - ] + "types": [ + "node" + ] + } } \ No newline at end of file diff --git a/src/lib/packwiz.ts b/src/lib/packwiz.ts new file mode 100644 index 0000000..b2b8760 --- /dev/null +++ b/src/lib/packwiz.ts @@ -0,0 +1,648 @@ +/** + * The hash algorithm used to determine if a file is valid. + * All functions listed must be supported by tools implementing the packwiz pack format. + */ +export type HashFormat = "sha256" | "sha512" | "sha1" | "md5" | "murmur2" +/** + * A relative path using forward slashes. + * Must not reference a file outside the pack root, + * and should not include characters or filenames restricted on common operating systems. + * + * Implementations must support special characters including those that are percent-encoded in URLs, + * such as spaces and square brackets. + * Implementations must guard against path traversal attacks and manually validate paths. + */ +export type Path = string +/** + * Binary hashes should be stored as hexadecimal, + * and case should be ignored during parsing. + * Numeric hashes (e.g. Murmur2) should still be stored as a string, + * to ensure the value is preserved correctly. + */ +export type Hash = string +/** + * A physical Minecraft side. + * Server applies to the dedicated server, + * client applies to the client (and integrated server), + * and both applies to every installation. + */ +export type Side = "both" | "client" | "server" +/** + * An absolute URI compliant with RFC 3986. + * Implementations may need to be more lenient in accepting reserved characters in paths + * due to historical implementation bugs. + * Only the HTTP/HTTPS protocols must be supported, + * other protocols should not be used. + */ +export type Url = string +/** + * The main modpack file for a packwiz modpack. + * This is the first file loaded, + * to allow the modpack downloader to download all the files in the modpack. + */ +export interface Pack { + /** + * The name of the modpack. + * This can be displayed in user interfaces to identify the pack, + * and it does not need to be unique between packs. + */ + "name": string + /** + * The author(s) of the modpack. + * This is output when exporting to the CurseForge pack format, + * and can be displayed in user interfaces. + */ + "author"?: string + /** + * The version of the modpack. + * This is output when exporting to the CurseForge pack format, + * but is not currently used elsewhere by the tools or installer. + * It must not be used for determining if the modpack is outdated. + */ + "version"?: string + /** + * A short description of the modpack. + * This is output when exporting to the Modrinth pack format, + * but is not currently used elsewhere by the tools or installer. + */ + "description"?: string + /** + * A version string identifying the pack format and version of it. + * Currently, this pack format uses version 1.1.0. + * + * If it is not defined, default to "packwiz:1.0.0" + * for backwards-compatibility with packs created before this field was added. + * + * If it is defined: + * + * - All consumers should fail to load the modpack if it does not begin with "packwiz:" + * + * - All consumers should fail to load the modpack if the latter section is not valid semver + * as defined in https://semver.org/spec/v2.0.0.html + * + * - All consumers should fail to load the modpack if the major version is greater than the version they support + * + * - Consumers can suggest updating themselves if the minor version is greater than the version they implement + * + * - Pack tools should suggest and support migration when they support a version newer than this field + */ + "pack-format": string + /** Information about the index file in this modpack. */ + "index": { + /** The path to the file that contains the index. @see {@link Path} */ + "file": Path + /** The hash format for the hash of the index file. @see {@link HashFormat} */ + "hash-format": HashFormat + /** The hash of the index file, as a string. @see {@link Hash} */ + "hash": Hash + } + /** + * The versions of components used by this modpack - usually Minecraft and the mod loader this pack uses. + * The existence of a component implies that it should be installed. + * These values can also be used by tools to determine which versions of mods should be installed. + */ + "versions": { + /** + * The version of Minecraft used by this modpack. + * This should be in the format used by the version.json files. + */ + "minecraft": string + /** + * The version of Forge used by this modpack. + * This version must not include the Minecraft version as a prefix. + */ + "forge"?: string + /** The version of Fabric loader used by this modpack. */ + "fabric"?: string + /** The version of Quilt loader used by this modpack. */ + "quilt"?: string + /** The version of Liteloader used by this modpack. */ + "liteloader"?: string + "neoforge"?: string + }, + /** Additional options */ + "options"?: { + /** + * A list of additional Minecraft versions to accept when installing or updating mods. + * @see https://packwiz.infra.link/tutorials/creating/adding-mods/ + */ + "acceptable-game-versions"?: string[] + /** + * The folder in which new metadata files will be added, + * defaulting to a folder based on the category + * (mods, resourcepacks, etc; if the category is unknown the current directory is used) + */ + "meta-folder"?: string + /** deprecated; aliassed to meta-folder */ + "mods-folder"?: string + /** + * The base folder from which meta-folder will be resolved, + * defaulting to the current directory + * (so you can put all mods/etc in a subfolder while still using the default behaviour) + */ + "meta-folder-base"?: string + /** + * If this is set to true, + * packwiz will not generate hashes of local files, + * to prevent merge conflicts and inconsistent hashes when using git/etc. + * + * - `packwiz refresh --build` can be used in this mode to generate internal hashes + * for distributing the pack with packwiz-installer + */ + "no-internal-hashes"?: boolean + /** + * The folder in which datapacks are to be added; + * specific to the datapack loader mod you use, + * and must be set to add datapacks + * (that are not bundled as mods) + */ + "datapack-folder"?: string + } +} +/** + * The index file of the modpack, + * storing references to every file to be downloaded in the pack. + */ +export interface Index { + /** The default hash format for every file in the index. @see {@link HashFormat} */ + "hash-format": HashFormat + /** + * The files listed in this index. + * If it is not defined, defaults to an empty list. + */ + "files"?: [{ + /** + * The path to the file to be downloaded, + * relative to this index file. + * @see {@link Path} + */ + "file": Path + /** The hash of the specified file, as a string. @see {@link Hash} */ + "hash": Hash + /** + * The name with which this file should be downloaded, + * instead of the filename specified in the path. + * Not compatible with metafile, + * and may not be very well supported. + */ + "alias"?: string + /** + * The hash format for the hash of the specified file. + * Defaults to the hash format specified in the index - + * ideally remove this value if it is equal to the hash format for the index to save space. + * @see {@link HashFormat} + */ + "hash-format"?: HashFormat + /** + * True when this entry points to a .toml metadata file, + * which references a file outside the pack. + */ + "metafile"?: boolean + /** + * When this is set to true, + * the file is not overwritten if it already exists, + * to preserve changes made by a user. + */ + "preserve"?: boolean + }] +} +/** + * A metadata file which references an external file from a URL. + * This allows for side-only mods, + * optional mods and stores metadata to allow finding updates from Modrinth and CurseForge. + * The "mod" terminology is used a lot here, + * but this should work for any file. + */ +export interface Mod { + /** + * The destination path of the mod file, + * relative to this file. + * @see {@link Path} + */ + "filename": Path + /** + * The name of the mod, + * which can be displayed in user interfaces to identify the mod. + * It does not need to be unique between mods, + * although this may cause confusion. + */ + "name": string + /** The side on which this mod should be installed. @see {@link Side} */ + "side"?: Side + /** Information about how to download this mod. */ + "download": { + /** The hash format for the hash of the specified file. @see {@link HashFormat} */ + "hash-format": HashFormat + /** The hash of the specified file, as a string. @see {@link Hash} */ + "hash": Hash + /** The URL to download the mod from. @see {@link URL} */ + "url"?: Url + "mode"?: string + } + /** + * Information about the optional state of this mod. + * When excluded, this indicates that the mod is not optional. + */ + "option"?: { + /** + * Whether or not the mod is optional. + * This can be set to false if you want to keep the description but make the mod required. + */ + "optional": boolean + /** + * If true, the mod will be enabled by default. + * If false, the mod will be disabled by default. + * If a pack format does not support optional mods but it does support disabling mods, + * the mod will be disabled if it defaults to being disabled. + */ + "default"?: boolean + /** + * A description displayed to the user when they select optional mods. + * This should explain why or why not the user should enable the mod. + */ + "description"?: string + } + /** + * Information about how to update the download details of this mod with tools. + * + * If this object does not exist or there are no defined update sources, + * the mod will not be automatically updated. + * + * If there are multiple defined update sources, + * one of them will be chosen. + * The source that is chosen is not defined, + * so it is therefore dependent on the implementation of the tool + * (may not be deterministic, so do not rely on one source being chosen over another). + */ + "update"?: { + /** An update source for updating mods downloaded from CurseForge. */ + "curseforge"?: { + /** + * An integer representing the unique file ID of this mod file. + * This can be used if more metadata needs to be obtained relating to the mod. + */ + "file-id": number + /** + * An integer representing the unique project ID of this mod. + * Updating will retrieve the latest file for this project ID that is valid + * (correct Minecraft version, release channel, modloader, etc.). + */ + "project-id": number + } + /** An update source for updating mods downloaded from Modrinth. */ + "modrinth"?: { + /** + * A string representing the unique mod ID of this mod. + * Updating will retrieve the latest file for this project ID that is valid + * (correct Minecraft version, release channel, modloader, etc.). + */ + "mod-id": string + /** + * A string representing the unique version ID of this file. + * This can be used if more metadata needs to be obtained relating to the mod. + */ + "version": string + } + } +} + +export interface Result { + delete?: Path[] + write?: { [key: Path]: Mod | Index | Pack } +} + +import WorkerlessPool from 'workerless' +import { parseTOML } from 'confbox' + +// copied from https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API/Non-cryptographic_uses_of_subtle_crypto#hashing_a_file +async function sha1(arrayBuffer: BufferSource) { + const hashAsArrayBuffer = await crypto.subtle.digest("SHA-1", arrayBuffer); + const uint8ViewOfHash = new Uint8Array(hashAsArrayBuffer); + // @ts-ignore + if (uint8ViewOfHash.toHex) { + // @ts-ignore + return uint8ViewOfHash.toHex() as string; + } + const hashAsString = Array.from(uint8ViewOfHash) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); + return hashAsString; +} + +async function fetch_toml(url: string, options?: RequestInit) { + const response = await fetch_ok(url, options) + const text = await response.text() + return parseTOML(text) as any +} + +async function fetch_json(url: string, options?: RequestInit) { + const response = await fetch_ok(url, options) + const json = await response.json() + return json as any +} + +async function fetch_bytes(url: string, options?: RequestInit) { + const response = await fetch_ok(url, options) + const bytes = await response.bytes() + return bytes +} + +async function fetch_ok(url: string, options?: RequestInit) { + try { + new URL(url) + } catch { + // @ts-expect-error + const fs = await import('node:fs/promises') + const body = await fs.readFile(url) + return new Response(body) + } + const response = await fetch(url, options) + if (!response.ok) { + throw new Error(`${url}: ${response.statusText}`, { 'cause': response }) + } + return response +} + +function dirname(path: string) { + if (!path.startsWith('http') && !path.startsWith('./')) path = './' + path + const path_segments = path.split('/') + path_segments.pop() + path = path_segments.join('/') + return path +} + +export async function cfDetect(pack_url: string, cfApiKey: string, size_min = 4096): Promise { + console.log('loading pack') + const pack = await fetch_toml(pack_url) as Pack + const pack_dir = dirname(pack_url) + const index_url = `${pack_dir}/${pack.index.file}` + console.log('loading index') + const index = await fetch_toml(index_url) as Index + const index_dir = dirname(index_url) + if (!index.files) { + console.warn(`${index_url} has no files indexed`) + return {} + } + const file_hash_map = new Map() + console.log('hashing files') + const workerlessPool = new WorkerlessPool() + await Promise.all(index.files.map(async file => { + if (file.metafile) return + const file_url = `${index_dir}/${file.file}` + const file_data = await fetch_bytes(file_url) + if (file_data.length < size_min) return + const hash = await workerlessPool.run(async function (buffer: Uint8Array) { + const murmurHash = await import('murmur2').then(module => module.default) + return murmurHash(buffer, 1, true) + }, file_data) + file_hash_map.set(hash, file_url) + })) + workerlessPool.terminate() + console.log('requesting matches from curseforge') + const response = await fetch_json('https://api.curseforge.com/v1/fingerprints', { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'accept': 'application/json', + 'x-api-key': cfApiKey + }, + body: JSON.stringify({ fingerprints: Array.from(file_hash_map.keys()) }) + }) + const result: Result = { delete: [], write: {} } + for (const match of response.data.exactMatches) { + let file_url = file_hash_map.get(match.file.fileFingerprint) + if (!file_url) { + for (const module of match.file.modules) { + file_url = file_hash_map.get(module.fingerprint) + } + if (file_url) { + console.debug("skipping module match, this is probably a false positive", file_url, match.file) + } else { + console.debug("curseforge returned a result we didn't ask for, skipping", match.file) + } + continue + } + for (const hash of match.file.hashes) { + if (hash.algo === 1) { + const mod: Mod = { + "name": match.file.displayName, + "filename": match.file.fileName, + "download": { + "hash-format": "sha1", + "hash": hash.value, + "mode": "metadata:curseforge" + }, + "update": { + "curseforge": { + "file-id": match.file.id, + "project-id": match.file.modId + } + } + } + result.delete.push(file_url) + result.write[match.file.displayName.toLowerCase().replace(' ', '-')] = mod + break + } + } + } + console.log(`found ${result.delete.length} matching files`) + return result +} + +export async function mrDetect(pack_url: string, mrApiKey?: string, size_min = 4096): Promise { + console.log('loading pack') + const pack = await fetch_toml(pack_url) as Pack + const pack_dir = dirname(pack_url) + const index_url = `${pack_dir}/${pack.index.file}` + console.log('loading index') + const index = await fetch_toml(index_url) as Index + const index_dir = dirname(index_url) + if (!index.files) { + console.warn(`${index_url} has no files indexed`) + return {} + } + const file_hash_map = new Map() + console.log('hashing files') + await Promise.all(index.files.map(async file => { + if (file.metafile) return + const file_url = `${index_dir}/${file.file}` + const file_data = await fetch_bytes(file_url) + if (file_data.length < size_min) return + const hash=await sha1(file_data) + file_hash_map.set(hash, file_url) + })) + console.log('requesting matches from modrinth') + const matches = await fetch_json('https://api.modrinth.com/v2/version_files', { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'accept': 'application/json', + 'authorization': mrApiKey + }, + body: JSON.stringify({ + "algorithm": "sha1", + "hashes": Array.from(file_hash_map.keys()) + }) + }) + const project_matches = new Map() + for (const match of Object.values(matches) as any[]) project_matches.set(match.project_id, match) + const projects = await fetch_json(`https://api.modrinth.com/v2/projects?ids=["${Array.from(project_matches.keys()).join('","')}"]`) + + const result: Result = { delete: [], write: {} } + for (const project of projects) { + const match = project_matches.get(project.id) + if (!match) { + console.debug("modrinth returned a project result we didn't ask for, skipping", project) + continue + } + const file_url = file_hash_map.get(match.files[0].hashes.sha1) + if (!file_url) { + console.debug("modrinth returned a file result we didn't ask for, skipping", match) + continue + } + let side: Side = "both" + if (project.client_side === "unsupported") side = "server" + if (project.server_side === "unsupported") side = "client" + const mod: Mod = { + "name": project.title, + "filename": match.files[0].filename, + "side": side, + "download": { + "hash-format": "sha1", + "hash": match.files[0].hashes.sha1, + "url": match.files[0].url + }, + "update": { + "modrinth": { + "version": match.id, + "mod-id": match.project_id + } + } + } + result.delete.push(file_url) + result.write[match.file.displayName.toLowerCase().replace(' ', '-')] = mod + } + console.log(`found ${result.delete.length} matching files`) + return result +} + +export async function cfUrl(pack_url: string, cfApiKey: string): Promise { + console.log('loading pack') + const pack = await fetch_toml(pack_url) as Pack + const pack_dir = dirname(pack_url) + const index_url = `${pack_dir}/${pack.index.file}` + console.log('loading index') + const index = await fetch_toml(index_url) as Index + const index_dir = dirname(index_url) + if (!index.files) { + console.warn(`${index_url} has no files indexed`) + return {} + } + const mod_paths = new Map() + console.log('loading curseforge files') + await Promise.all(index.files.map(async file => { + if(!file.metafile) return + const file_url = `${index_dir}/${file.file}` + const mod = await fetch_toml(file_url) as Mod + if (!mod.update || !mod.update.curseforge) { + return + } + mod_paths.set(mod.update.curseforge['file-id'],{mod,file_url}) + })) + console.log(`requesting ${mod_paths.size} download urls`) + const response = await fetch_json('https://api.curseforge.com/v1/mods/files', { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'accept': 'application/json', + 'x-api-key': cfApiKey + }, + body: JSON.stringify({'fileIds':Array.from(mod_paths.keys())}) + }) + const result: Result = { write: {} } + for (const file of response.data) { + if (!file.downloadUrl) { + console.warn('third party downloads disabled for ' + file.displayName) + continue + } + const { mod, file_url } = mod_paths.get(file.id)! + delete mod.download.mode + mod.download.url = file.downloadUrl + result.write[file_url] = mod + } + console.log(`found ${Object.keys(result.write).length} download urls`) + return result +} + +export async function mrMerge(pack_url: string, mrApiKey?: string): Promise { + console.log('loading pack') + const pack = await fetch_toml(pack_url) as Pack + const pack_dir = dirname(pack_url) + const index_url = `${pack_dir}/${pack.index.file}` + console.log('loading index') + const index = await fetch_toml(index_url) as Index + const index_dir = dirname(index_url) + if (!index.files) { + console.warn(`${index_url} has no files indexed`) + return {} + } + console.log('loading curseforge files') + const file_hash_map:any={} + await Promise.all(index.files.map(async function (file) { + if (!file.metafile) return + const file_url = `${index_dir}/${file.file}` + const mod = await fetch_toml(file_url) as Mod + if (mod.update && mod.update.modrinth) return + if (!file_hash_map[mod.download['hash-format']]) file_hash_map[mod.download['hash-format']] = {} + file_hash_map[mod.download['hash-format']][mod.download.hash]={file_url,mod} + })) + console.log('requesting matches from modrinth') + const project_matches = new Map() + for (const hash_format in file_hash_map) { + const matches = await fetch_json('https://api.modrinth.com/v2/version_files', { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'accept': 'application/json', + 'authorization': mrApiKey + }, + body: JSON.stringify({ + "algorithm": hash_format, + "hashes": Array.from(Object.keys(file_hash_map[hash_format])) + }) + }) + for (const match of Object.values(matches) as any[]) project_matches.set(match.project_id, match) + } + const projects = await fetch_json(`https://api.modrinth.com/v2/projects?ids=["${Array.from(project_matches.keys()).join('","')}"]`) + const result: Result = { write: {} } + for (const project of projects) { + const match = project_matches.get(project.id) + if (!match) { + console.debug("modrinth returned a project result we didn't ask for, skipping", project) + continue + } + let file_url: Path|undefined + let mod: Mod|undefined + for (const hash_format in file_hash_map) { + ({file_url,mod}=file_hash_map[hash_format][match.files[0].hashes[hash_format].toLowerCase().replace('-','')]) + } + if (!file_url || !mod) { + console.debug("modrinth returned a file result we didn't ask for, skipping", match) + continue + } + delete mod.download.mode + mod.download.url = match.files[0].url + let side: Side = "both" + if (project.client_side === "unsupported") side = "server" + if (project.server_side === "unsupported") side = "client" + mod.side = side + if (!mod.update) mod.update = {} + mod.update.modrinth = { + "version": match.id, + "mod-id": match.project_id + } + result.write[file_url] = mod + } + console.log(`found ${Object.keys(result.write).length} matching files`) + return result +} \ No newline at end of file diff --git a/src/lib/tsconfig.json b/src/lib/tsconfig.json new file mode 100644 index 0000000..60a4e6f --- /dev/null +++ b/src/lib/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "es2022", + "module": "es2022", + "moduleResolution": "node", + "lib": ["ESNext"], + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "../../dist", + "types": ["web"] + }, + "include": [ + "packwiz.ts" + ] +} \ No newline at end of file