feat: support for multiple registries (#109)

* feat: support for multiple registries

* [autofix.ci] apply automated fixes

* refactor: drop unnecessary check

* fix: pass empty array instead undefined

* [autofix.ci] apply automated fixes

* fix: dont add registry set line

* [autofix.ci] apply automated fixes

* feat: few more tests

* feat: add bun types

* docs: explain registries

* feat: save globally

* [autofix.ci] apply automated fixes

* ci: jsr registry test

* ci: add ,

* ci: test

* ci: test

* ci: test

* ci: test

* [autofix.ci] apply automated fixes

* feat: use @iarna/toml

* [autofix.ci] apply automated fixes

* feat: registry parsing

* [autofix.ci] apply automated fixes

* ci: verbose add

* ci: registry install check

* ci: registry install check

* ci: registry install check

* ci: verify registry usage

* docs: registries

* docs: important block

* docs: show table

* docs: show table

* ci: shell

* ci: registry test on linux

* ci: registry test on linux

* build: text lockfile

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Jozef Steinhübl 2025-07-09 12:22:12 +02:00 committed by GitHub
parent 7c641390eb
commit 34f777aec1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 901 additions and 117 deletions

View File

@ -23,10 +23,26 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
tests:
name: Tests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: ./
- name: Install dependencies
run: bun install
- name: Run tests
run: bun test --coverage
setup-bun:
runs-on: ${{ matrix.os }}
continue-on-error: true
needs: [remove-cache]
needs: [remove-cache, tests]
strategy:
matrix:
@ -65,7 +81,7 @@ jobs:
name: setup-bun from (${{ matrix.os }}, ${{ matrix.file.name }})
runs-on: ${{ matrix.os }}
continue-on-error: true
needs: [remove-cache]
needs: [remove-cache, tests]
strategy:
matrix:
os:
@ -130,7 +146,7 @@ jobs:
name: setup-bun from (${{ matrix.os }}, download url)
runs-on: ${{ matrix.os }}
continue-on-error: true
needs: [remove-cache]
needs: [remove-cache, tests]
strategy:
matrix:
@ -153,3 +169,47 @@ jobs:
id: run_bun
run: |
bun --version
test-custom-registries:
name: test installing deps from custom registries (${{ matrix.os }})
runs-on: ${{ matrix.os }}
continue-on-error: true
needs: [remove-cache, tests]
strategy:
matrix:
os:
- ubuntu-latest
steps:
- name: 📥 Checkout
uses: actions/checkout@v4
- name: 🛠️ Setup Bun
uses: ./
id: setup_bun
with:
registries: |
https://registry.npmjs.org
@types:https://registry.yarnpkg.com
- name: ▶️ Install from default registry
run: |
output=$(bun add is-odd --verbose --force 2>&1)
if echo "$output" | grep -q "HTTP/1.1 GET https://registry.npmjs.org/is-odd"; then
echo "Successfully installed from default registry"
else
echo "Failed to install from default registry"
exit 1
fi
- name: ▶️ Install from @types registry
run: |
output=$(bun add @types/bun --verbose --force 2>&1)
if echo "$output" | grep -q "HTTP/1.1 GET https://registry.yarnpkg.com/@types%2fbun"; then
echo "Successfully installed from @types registry"
else
echo "Failed to install from @types registry"
exit 1
fi

View File

@ -18,24 +18,42 @@ Download, install, and setup [Bun](https://bun.sh) in GitHub Actions.
bun-version-file: ".bun-version"
```
### Using a custom NPM registry
## Using custom registries
You can configure multiple package registries using the `registries` input. This supports both default and scoped registries with various authentication methods.
### Registry configuration
```yaml
- uses: oven-sh/setup-bun@v2
with:
registry-url: "https://npm.pkg.github.com/"
scope: "@foo"
```
registries: |
https://registry.npmjs.org/
@myorg:https://npm.pkg.github.com/|$GITHUB_TOKEN
@internal:https://username:$INTERNAL_PASSWORD@registry.internal.com/
If you need to authenticate with a private registry, you can set the `BUN_AUTH_TOKEN` environment variable.
```yaml
- name: Install Dependencies
- name: Install dependencies
env:
BUN_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: bun install --frozen-lockfile
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
INTERNAL_PASSWORD: ${{ secrets.INTERNAL_PASSWORD }}
run: bun install
```
#### Registry format options
| Type | Format |
| ------------------------------------ | --------------------------------------------------------- |
| Default registry | `https://registry.example.com/` |
| Default registry with token | `https://registry.example.com/\|$TOKEN` |
| Scoped registry | `@scope:https://registry.example.com/` |
| Scoped registry with token | `@scope:https://registry.example.com/\|$TOKEN` |
| Scoped registry with URL credentials | `@scope:https://username:$PASSWORD@registry.example.com/` |
> [!IMPORTANT]
> When using authentication, make sure to set the corresponding environment variables in your workflow steps that need access to the registries.
For more information about configuring registries in Bun, see the [official documentation](https://bun.sh/docs/install/registries).
### Override download url
If you need to override the download URL, you can use the `bun-download-url` input.

View File

@ -17,12 +17,23 @@ inputs:
bun-download-url:
description: Override the URL to download Bun from. This skips version resolution and verifying AVX2 support.
required: false
registries:
description: |
List of package registries with authentication support. Format:
- Default registry: https://registry.npmjs.org/
- Default with token: https://registry.npmjs.org/|token
- Scoped registry: @scope:https://registry.example.com/
- Scoped with token: @scope:https://registry.example.com/|token
- Scoped with credentials: @scope:https://user:pass@registry.example.com/
required: false
registry-url:
required: false
description: The URL of the package registry to use for installing Bun. Set the $BUN_AUTH_TOKEN environment variable to authenticate with the registry.
deprecationMessage: "Use 'registries' input instead."
scope:
required: false
description: The scope for authenticating with the package registry.
description: "The scope for authenticating with the package registry."
deprecationMessage: "Use 'registries' input instead."
no-cache:
required: false
type: boolean

View File

@ -3,14 +3,16 @@
"workspaces": {
"": {
"dependencies": {
"@actions/cache": "^3.1.4",
"@actions/core": "^1.10.0",
"@actions/cache": "^4.0.0",
"@actions/core": "^1.11.0",
"@actions/exec": "^1.1.1",
"@actions/glob": "^0.4.0",
"@actions/io": "^1.1.2",
"@actions/tool-cache": "^2.0.1",
"@iarna/toml": "^2.2.5",
},
"devDependencies": {
"@types/bun": "^1.1.13",
"@types/node": "^20.8.2",
"esbuild": "^0.19.2",
"prettier": "^3.4.2",
@ -19,9 +21,9 @@
},
},
"packages": {
"@actions/cache": ["@actions/cache@3.2.4", "", { "dependencies": { "@actions/core": "^1.10.0", "@actions/exec": "^1.0.1", "@actions/glob": "^0.1.0", "@actions/http-client": "^2.1.1", "@actions/io": "^1.0.1", "@azure/abort-controller": "^1.1.0", "@azure/ms-rest-js": "^2.6.0", "@azure/storage-blob": "^12.13.0", "semver": "^6.3.1", "uuid": "^3.3.3" } }, "sha512-RuHnwfcDagtX+37s0ZWy7clbOfnZ7AlDJQ7k/9rzt2W4Gnwde3fa/qjSjVuz4vLcLIpc7fUob27CMrqiWZytYA=="],
"@actions/cache": ["@actions/cache@4.0.3", "", { "dependencies": { "@actions/core": "^1.11.1", "@actions/exec": "^1.0.1", "@actions/glob": "^0.1.0", "@actions/http-client": "^2.1.1", "@actions/io": "^1.0.1", "@azure/abort-controller": "^1.1.0", "@azure/ms-rest-js": "^2.6.0", "@azure/storage-blob": "^12.13.0", "@protobuf-ts/plugin": "^2.9.4", "semver": "^6.3.1" } }, "sha512-SvrqFtYJ7I48A/uXNkoJrnukx5weQv1fGquhs3+4nkByZThBH109KTIqj5x/cGV7JGNvb8dLPVywUOqX1fjiXg=="],
"@actions/core": ["@actions/core@1.10.1", "", { "dependencies": { "@actions/http-client": "^2.0.1", "uuid": "^8.3.2" } }, "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g=="],
"@actions/core": ["@actions/core@1.11.1", "", { "dependencies": { "@actions/exec": "^1.1.1", "@actions/http-client": "^2.0.1" } }, "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A=="],
"@actions/exec": ["@actions/exec@1.1.1", "", { "dependencies": { "@actions/io": "^1.0.1" } }, "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w=="],
@ -53,6 +55,10 @@
"@azure/storage-blob": ["@azure/storage-blob@12.17.0", "", { "dependencies": { "@azure/abort-controller": "^1.0.0", "@azure/core-http": "^3.0.0", "@azure/core-lro": "^2.2.0", "@azure/core-paging": "^1.1.1", "@azure/core-tracing": "1.0.0-preview.13", "@azure/logger": "^1.0.0", "events": "^3.0.0", "tslib": "^2.2.0" } }, "sha512-sM4vpsCpcCApagRW5UIjQNlNylo02my2opgp0Emi8x888hZUvJ3dN69Oq20cEGXkMUWnoCrBaB0zyS3yeB87sQ=="],
"@bufbuild/protobuf": ["@bufbuild/protobuf@2.6.0", "", {}, "sha512-6cuonJVNOIL7lTj5zgo/Rc2bKAo4/GvN+rKCrUj7GdEHRzCk8zKOfFwUsL9nAVk5rSIsRmlgcpLzTRysopEeeg=="],
"@bufbuild/protoplugin": ["@bufbuild/protoplugin@2.6.0", "", { "dependencies": { "@bufbuild/protobuf": "2.6.0", "@typescript/vfs": "^1.5.2", "typescript": "5.4.5" } }, "sha512-mfAwI+4GqUtbw/ddfyolEHaAL86ozRIVlOg2A+SVRbjx1CjsMc1YJO+hBSkt/pqfpR+PmWBbZLstHbXP8KGtMQ=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.19.12", "", { "os": "android", "cpu": "arm" }, "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w=="],
@ -101,14 +107,30 @@
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
"@iarna/toml": ["@iarna/toml@2.2.5", "", {}, "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg=="],
"@opentelemetry/api": ["@opentelemetry/api@1.6.0", "", {}, "sha512-OWlrQAnWn9577PhVgqjUvMr1pg57Bc4jv0iL4w0PRuOSRvq67rvHW9Ie/dZVMvCzhSCB+UxhcY/PmCmFj33Q+g=="],
"@protobuf-ts/plugin": ["@protobuf-ts/plugin@2.11.1", "", { "dependencies": { "@bufbuild/protobuf": "^2.4.0", "@bufbuild/protoplugin": "^2.4.0", "@protobuf-ts/protoc": "^2.11.1", "@protobuf-ts/runtime": "^2.11.1", "@protobuf-ts/runtime-rpc": "^2.11.1", "typescript": "^3.9" }, "bin": { "protoc-gen-ts": "bin/protoc-gen-ts", "protoc-gen-dump": "bin/protoc-gen-dump" } }, "sha512-HyuprDcw0bEEJqkOWe1rnXUP0gwYLij8YhPuZyZk6cJbIgc/Q0IFgoHQxOXNIXAcXM4Sbehh6kjVnCzasElw1A=="],
"@protobuf-ts/protoc": ["@protobuf-ts/protoc@2.11.1", "", { "bin": { "protoc": "protoc.js" } }, "sha512-mUZJaV0daGO6HUX90o/atzQ6A7bbN2RSuHtdwo8SSF2Qoe3zHwa4IHyCN1evftTeHfLmdz+45qo47sL+5P8nyg=="],
"@protobuf-ts/runtime": ["@protobuf-ts/runtime@2.11.1", "", {}, "sha512-KuDaT1IfHkugM2pyz+FwiY80ejWrkH1pAtOBOZFuR6SXEFTsnb/jiQWQ1rCIrcKx2BtyxnxW6BWwsVSA/Ie+WQ=="],
"@protobuf-ts/runtime-rpc": ["@protobuf-ts/runtime-rpc@2.11.1", "", { "dependencies": { "@protobuf-ts/runtime": "^2.11.1" } }, "sha512-4CqqUmNA+/uMz00+d3CYKgElXO9VrEbucjnBFEjqI4GuDrEQ32MaI3q+9qPBvIGOlL4PmHXrzM32vBPWRhQKWQ=="],
"@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="],
"@types/node": ["@types/node@20.12.2", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-zQ0NYO87hyN6Xrclcqp7f8ZbXNbRfoGWNcMvHTPQp9UUrwI0mI7XBz+cu7/W6/VClYo2g63B0cjull/srU7LgQ=="],
"@types/node-fetch": ["@types/node-fetch@2.6.11", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.0" } }, "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g=="],
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
"@types/tunnel": ["@types/tunnel@0.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA=="],
"@typescript/vfs": ["@typescript/vfs@1.6.1", "", { "dependencies": { "debug": "^4.1.1" }, "peerDependencies": { "typescript": "*" } }, "sha512-JwoxboBh7Oz1v38tPbkrZ62ZXNHAk9bJ7c9x0eI5zBfBnBYGhURdbnh7Z4smN/MV48Y5OCcZb58n972UtbazsA=="],
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
@ -117,10 +139,16 @@
"brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
"bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="],
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
"esbuild": ["esbuild@0.19.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="],
@ -137,6 +165,8 @@
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
"prettier": ["prettier@3.4.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ=="],
@ -171,9 +201,9 @@
"@actions/cache/@actions/glob": ["@actions/glob@0.1.2", "", { "dependencies": { "@actions/core": "^1.2.6", "minimatch": "^3.0.4" } }, "sha512-SclLR7Ia5sEqjkJTPs7Sd86maMDw43p769YxBOxvPvEWuPEhpAnBsQfENOpXjFYMmhCqd127bmf+YdvJqVqR4A=="],
"@actions/core/@actions/http-client": ["@actions/http-client@2.2.0", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-q+epW0trjVUUHboliPb4UF9g2msf+w61b32tAkFEwL/IwP0DQWgbCMM0Hbe3e3WXSKz5VcUXbzJQgy8Hkra/Lg=="],
"@actions/glob/@actions/core": ["@actions/core@1.10.1", "", { "dependencies": { "@actions/http-client": "^2.0.1", "uuid": "^8.3.2" } }, "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g=="],
"@actions/core/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
"@actions/tool-cache/@actions/core": ["@actions/core@1.10.1", "", { "dependencies": { "@actions/http-client": "^2.0.1", "uuid": "^8.3.2" } }, "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g=="],
"@actions/tool-cache/@actions/http-client": ["@actions/http-client@2.2.0", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-q+epW0trjVUUHboliPb4UF9g2msf+w61b32tAkFEwL/IwP0DQWgbCMM0Hbe3e3WXSKz5VcUXbzJQgy8Hkra/Lg=="],
@ -193,11 +223,21 @@
"@azure/ms-rest-js/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
"@bufbuild/protoplugin/typescript": ["typescript@5.4.5", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ=="],
"@protobuf-ts/plugin/typescript": ["typescript@3.9.10", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q=="],
"@types/node-fetch/form-data": ["form-data@4.0.0", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "mime-types": "^2.1.12" } }, "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww=="],
"@types/tunnel/@types/node": ["@types/node@20.8.9", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg=="],
"@actions/core/@actions/http-client/undici": ["undici@5.26.5", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-cSb4bPFd5qgR7qr2jYAi0hlX9n5YKK2ONKkLFkxl+v/9BvC0sOpZjBHDBSXc5lWAf5ty9oZdRXytBIHzgUcerw=="],
"@actions/cache/@actions/glob/@actions/core": ["@actions/core@1.10.1", "", { "dependencies": { "@actions/http-client": "^2.0.1", "uuid": "^8.3.2" } }, "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g=="],
"@actions/glob/@actions/core/@actions/http-client": ["@actions/http-client@2.2.0", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-q+epW0trjVUUHboliPb4UF9g2msf+w61b32tAkFEwL/IwP0DQWgbCMM0Hbe3e3WXSKz5VcUXbzJQgy8Hkra/Lg=="],
"@actions/glob/@actions/core/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
"@actions/tool-cache/@actions/core/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
"@actions/tool-cache/@actions/http-client/undici": ["undici@5.26.5", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-cSb4bPFd5qgR7qr2jYAi0hlX9n5YKK2ONKkLFkxl+v/9BvC0sOpZjBHDBSXc5lWAf5ty9oZdRXytBIHzgUcerw=="],
@ -205,8 +245,18 @@
"@azure/core-http/@azure/core-util/@azure/abort-controller": ["@azure/abort-controller@2.1.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-NhzeNm5zu2fPlwGXPUjzsRCRuPx5demaZyNcyNYJDqpa/Sbxzvo/RYt9IwUaAOnDW5+r7J9UOE6f22TQnb9nhQ=="],
"@actions/core/@actions/http-client/undici/@fastify/busboy": ["@fastify/busboy@2.0.0", "", {}, "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ=="],
"@actions/cache/@actions/glob/@actions/core/@actions/http-client": ["@actions/http-client@2.2.0", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-q+epW0trjVUUHboliPb4UF9g2msf+w61b32tAkFEwL/IwP0DQWgbCMM0Hbe3e3WXSKz5VcUXbzJQgy8Hkra/Lg=="],
"@actions/cache/@actions/glob/@actions/core/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
"@actions/glob/@actions/core/@actions/http-client/undici": ["undici@5.26.5", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-cSb4bPFd5qgR7qr2jYAi0hlX9n5YKK2ONKkLFkxl+v/9BvC0sOpZjBHDBSXc5lWAf5ty9oZdRXytBIHzgUcerw=="],
"@actions/tool-cache/@actions/http-client/undici/@fastify/busboy": ["@fastify/busboy@2.0.0", "", {}, "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ=="],
"@actions/cache/@actions/glob/@actions/core/@actions/http-client/undici": ["undici@5.26.5", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-cSb4bPFd5qgR7qr2jYAi0hlX9n5YKK2ONKkLFkxl+v/9BvC0sOpZjBHDBSXc5lWAf5ty9oZdRXytBIHzgUcerw=="],
"@actions/glob/@actions/core/@actions/http-client/undici/@fastify/busboy": ["@fastify/busboy@2.0.0", "", {}, "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ=="],
"@actions/cache/@actions/glob/@actions/core/@actions/http-client/undici/@fastify/busboy": ["@fastify/busboy@2.0.0", "", {}, "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ=="],
}
}

107
dist/setup/index.js generated vendored

File diff suppressed because one or more lines are too long

52
package-lock.json generated
View File

@ -14,9 +14,11 @@
"@actions/exec": "^1.1.1",
"@actions/glob": "^0.4.0",
"@actions/io": "^1.1.2",
"@actions/tool-cache": "^2.0.1"
"@actions/tool-cache": "^2.0.1",
"@iarna/toml": "^2.2.5"
},
"devDependencies": {
"@types/bun": "^1.1.13",
"@types/node": "^20.8.2",
"esbuild": "^0.19.2",
"prettier": "^3.4.2",
@ -271,6 +273,12 @@
"node": ">=12"
}
},
"node_modules/@iarna/toml": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz",
"integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==",
"license": "ISC"
},
"node_modules/@opentelemetry/api": {
"version": "1.5.0",
"license": "Apache-2.0",
@ -348,6 +356,16 @@
"@protobuf-ts/runtime": "^2.9.6"
}
},
"node_modules/@types/bun": {
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.2.18.tgz",
"integrity": "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"bun-types": "1.2.18"
}
},
"node_modules/@types/node": {
"version": "20.9.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.1.tgz",
@ -376,6 +394,17 @@
"node": ">= 6"
}
},
"node_modules/@types/react": {
"version": "19.1.8",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz",
"integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
},
"node_modules/@types/tunnel": {
"version": "0.0.3",
"license": "MIT",
@ -409,6 +438,19 @@
"concat-map": "0.0.1"
}
},
"node_modules/bun-types": {
"version": "1.2.18",
"resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.2.18.tgz",
"integrity": "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
},
"peerDependencies": {
"@types/react": "^19"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"license": "MIT",
@ -423,6 +465,14 @@
"version": "0.0.1",
"license": "MIT"
},
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"license": "MIT",

View File

@ -27,9 +27,11 @@
"@actions/exec": "^1.1.1",
"@actions/glob": "^0.4.0",
"@actions/io": "^1.1.2",
"@actions/tool-cache": "^2.0.1"
"@actions/tool-cache": "^2.0.1",
"@iarna/toml": "^2.2.5"
},
"devDependencies": {
"@types/bun": "^1.1.13",
"@types/node": "^20.8.2",
"esbuild": "^0.19.2",
"prettier": "^3.4.2",

View File

@ -12,9 +12,10 @@ import { addPath, info, warning } from "@actions/core";
import { isFeatureAvailable, restoreCache } from "@actions/cache";
import { downloadTool, extractZip } from "@actions/tool-cache";
import { getExecOutput } from "@actions/exec";
import { writeBunfig } from "./bunfig";
import { writeBunfig, Registry } from "./bunfig";
import { saveState } from "@actions/core";
import { addExtension, retry } from "./utils";
import { cwd } from "node:process";
export type Input = {
customUrl?: string;
@ -23,8 +24,7 @@ export type Input = {
arch?: string;
avx2?: boolean;
profile?: boolean;
scope?: string;
registryUrl?: string;
registries?: Registry[];
noCache?: boolean;
};
@ -44,8 +44,8 @@ export type CacheState = {
};
export default async (options: Input): Promise<Output> => {
const bunfigPath = join(process.cwd(), "bunfig.toml");
writeBunfig(bunfigPath, options);
const bunfigPath = join(cwd(), "bunfig.toml");
writeBunfig(bunfigPath, options.registries);
const url = getDownloadUrl(options);
const cacheEnabled = isCacheEnabled(options);

View File

@ -1,50 +1,81 @@
import { EOL } from "node:os";
import { appendFileSync } from "node:fs";
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { info } from "@actions/core";
import { parse, stringify } from "@iarna/toml";
import { Registry } from "./registry";
type BunfigOptions = {
registryUrl?: string;
scope?: string;
type BunfigConfig = {
install?: {
registry?: {
url: string;
token?: string;
};
scopes?: Record<string, { url: string; token?: string }>;
};
[key: string]: any;
};
export function createBunfig(options: BunfigOptions): string | null {
const { registryUrl, scope } = options;
let url: URL | undefined;
if (registryUrl) {
try {
url = new URL(registryUrl);
} catch {
throw new Error(`Invalid registry-url: ${registryUrl}`);
}
}
let owner: string | undefined;
if (scope) {
owner = scope.startsWith("@")
? scope.toLocaleLowerCase()
: `@${scope.toLocaleLowerCase()}`;
}
if (url && owner) {
return `[install.scopes]${EOL}'${owner}' = { token = "$BUN_AUTH_TOKEN", url = "${url}"}${EOL}`;
}
if (url && !owner) {
return `[install]${EOL}registry = "${url}"${EOL}`;
}
return null;
}
export function writeBunfig(path: string, options: BunfigOptions): void {
const bunfig = createBunfig(options);
if (!bunfig) {
export function writeBunfig(path: string, registries: Registry[]): void {
if (!registries.length) {
return;
}
info(`Writing bunfig.toml to '${path}'.`);
appendFileSync(path, bunfig, {
encoding: "utf8",
let globalRegistryCount = 0;
registries.forEach((registry) => {
try {
new URL(registry.url);
} catch {
throw new Error(`Invalid registry URL: ${registry.url}`);
}
if (!registry.scope) {
globalRegistryCount++;
}
});
if (globalRegistryCount > 1) {
throw new Error("You can't have more than one global registry.");
}
info(`Writing bunfig.toml to '${path}'.`);
let config: BunfigConfig = {};
if (existsSync(path)) {
try {
const content = readFileSync(path, { encoding: "utf-8" });
config = parse(content) as BunfigConfig;
} catch (error) {
info(`Error reading existing bunfig: ${error.message}`);
config = {};
}
}
config.install = config?.install || {};
config.install.scopes = config?.install.scopes || {};
const globalRegistry = registries.find((r) => !r.scope);
if (globalRegistry) {
config.install.registry = {
url: globalRegistry.url,
...(globalRegistry.token ? { token: globalRegistry.token } : {}),
};
}
for (const registry of registries) {
if (registry.scope) {
const scopeName = registry.scope.startsWith("@")
? registry.scope.toLowerCase()
: `@${registry.scope.toLowerCase()}`;
config.install.scopes[scopeName] = {
url: registry.url,
...(registry.token ? { token: registry.token } : {}),
};
}
}
if (Object.keys(config.install.scopes).length === 0) {
delete config.install.scopes;
}
writeFileSync(path, stringify(config), { encoding: "utf8" });
}

View File

@ -2,19 +2,33 @@ import { tmpdir } from "node:os";
import { getInput, setOutput, setFailed, getBooleanInput } from "@actions/core";
import runAction from "./action.js";
import { readVersionFromFile } from "./utils.js";
import { parseRegistries } from "./registry.js";
if (!process.env.RUNNER_TEMP) {
process.env.RUNNER_TEMP = tmpdir();
}
const registries = parseRegistries(getInput("registries"));
// Backwards compatibility for the `registry-url` and `scope` inputs
const registryUrl = getInput("registry-url");
const scope = getInput("scope");
if (registryUrl) {
registries.push({
url: registryUrl,
scope: scope,
token: "$BUN_AUTH_TOKEN",
});
}
runAction({
version:
getInput("bun-version") ||
readVersionFromFile(getInput("bun-version-file")) ||
undefined,
customUrl: getInput("bun-download-url") || undefined,
registryUrl: getInput("registry-url") || undefined,
scope: getInput("scope") || undefined,
registries: registries,
noCache: getBooleanInput("no-cache") || false,
})
.then(({ version, revision, bunPath, url, cacheHit }) => {

62
src/registry.ts Normal file
View File

@ -0,0 +1,62 @@
export type Registry = {
url: string;
scope: string;
token?: string;
};
/**
* Parse registries from the simplified format:
* - Default registry: https://registry.npmjs.org/
* - Default registry with token: https://registry.npmjs.org/|token123
* - With scope and credentials in URL: @myorg:https://username:password@registry.myorg.com/
* - With scope and separate token: @partner:https://registry.partner.com/|basic_token
*/
export function parseRegistries(input: string): Registry[] {
if (!input?.trim()) return [];
return input
.split("\n")
.map((line) => line.trim())
.filter(Boolean)
.map(parseLine)
.filter(Boolean) as Registry[];
}
function parseLine(line: string): Registry | null {
const scopeMatch = line.match(
/^(@[a-z0-9-_.]+|[a-z0-9-_.]+(?=:[a-z]+:\/\/)):(.+)$/i,
);
if (scopeMatch) {
const scope = scopeMatch[1];
const urlPart = scopeMatch[2].trim();
const [url, token] = urlPart.split("|", 2).map((p) => p?.trim());
try {
new URL(url);
return {
url,
scope,
...(token && { token }),
};
} catch (e) {
throw new Error(`Invalid URL in registry configuration: ${url}`);
}
} else {
const [url, token] = line.split("|", 2).map((p) => p?.trim());
try {
new URL(url);
return {
url,
scope: "",
...(token && { token }),
};
} catch (e) {
throw new Error(`Invalid URL in registry configuration: ${url}`);
}
}
}

291
tests/bunfig.spec.ts Normal file
View File

@ -0,0 +1,291 @@
import { afterEach, describe, expect, it } from "bun:test";
import { existsSync, unlinkSync } from "node:fs";
import { writeBunfig } from "../src/bunfig";
import { EOL } from "os";
describe("writeBunfig", () => {
const filePath = "bunfig_test.toml";
async function getFileAndContents() {
const file = Bun.file(filePath);
const contents = (await file.text()).split(EOL);
return { file, contents };
}
afterEach(() => {
if (existsSync(filePath)) unlinkSync(filePath);
console.log(`${filePath} was deleted`);
});
describe("when no bunfig_test.toml file exists", () => {
it("should create a new file with scopes content", async () => {
writeBunfig(filePath, [
{
url: "https://npm.pkg.github.com",
scope: "foo-bar",
token: "$BUN_AUTH_TOKEN",
},
]);
const { file, contents } = await getFileAndContents();
expect(file.exists()).resolves.toBeTrue();
const expectedContents = [
'[install.scopes."@foo-bar"]',
'url = "https://npm.pkg.github.com"',
'token = "$BUN_AUTH_TOKEN"',
"",
];
contents.forEach((content, index) =>
expect(content).toBe(expectedContents[index])
);
expect(contents.length).toBe(expectedContents.length);
});
it("should create a new file with global registry", async () => {
writeBunfig(filePath, [
{
url: "https://npm.pkg.github.com",
scope: "",
},
]);
const { file, contents } = await getFileAndContents();
expect(file.exists()).resolves.toBeTrue();
const expectedContents = [
"[install.registry]",
'url = "https://npm.pkg.github.com"',
"",
];
contents.forEach((content, index) =>
expect(content).toBe(expectedContents[index])
);
expect(contents.length).toBe(expectedContents.length);
});
it("should create a new file with global registry & token", async () => {
writeBunfig(filePath, [
{
url: "https://npm.pkg.github.com",
scope: "",
token: "$BUN_AUTH_TOKEN",
},
]);
const { file, contents } = await getFileAndContents();
expect(file.exists()).resolves.toBeTrue();
const expectedContents = [
"[install.registry]",
'url = "https://npm.pkg.github.com"',
'token = "$BUN_AUTH_TOKEN"',
"",
];
contents.forEach((content, index) =>
expect(content).toBe(expectedContents[index])
);
expect(contents.length).toBe(expectedContents.length);
});
});
describe("when local bunfig_test.toml file exists", () => {
it("and no [install.scopes] exists, should concatenate file correctly", async () => {
const bunfig = `[install]${EOL}optional = true${EOL}${EOL}[install.cache]${EOL}disable = true`;
await Bun.write(filePath, bunfig);
writeBunfig(filePath, [
{
url: "https://npm.pkg.github.com",
scope: "foo-bar",
token: "$BUN_AUTH_TOKEN",
},
]);
const { file, contents } = await getFileAndContents();
expect(file.exists()).resolves.toBeTrue();
const expectedContents = [
"[install]",
"optional = true",
"",
" [install.cache]",
" disable = true",
"",
'[install.scopes."@foo-bar"]',
'url = "https://npm.pkg.github.com"',
'token = "$BUN_AUTH_TOKEN"',
"",
];
contents.forEach((content, index) =>
expect(content).toBe(expectedContents[index])
);
expect(contents.length).toBe(expectedContents.length);
});
it("and [install.scopes] exists and it's not the same registry, should concatenate file correctly", async () => {
const bunfig = `[install]${EOL}optional = true${EOL}${EOL}[install.scopes]${EOL}'@bla-ble' = { token = "$BUN_AUTH_TOKEN", url = "https://npm.pkg.github.com/" }${EOL}${EOL}[install.cache]${EOL}disable = true`;
await Bun.write(filePath, bunfig);
writeBunfig(filePath, [
{
url: "https://npm.pkg.github.com",
scope: "foo-bar",
token: "$BUN_AUTH_TOKEN",
},
]);
const { file, contents } = await getFileAndContents();
expect(file.exists()).resolves.toBeTrue();
const expectedContents = [
"[install]",
"optional = true",
"",
'[install.scopes."@bla-ble"]',
'token = "$BUN_AUTH_TOKEN"',
'url = "https://npm.pkg.github.com/"',
"",
'[install.scopes."@foo-bar"]',
'url = "https://npm.pkg.github.com"',
'token = "$BUN_AUTH_TOKEN"',
"",
" [install.cache]",
" disable = true",
"",
];
contents.forEach((content, index) =>
expect(content).toBe(expectedContents[index])
);
expect(contents.length).toBe(expectedContents.length);
});
it("and [install.scopes] exists and it's the same registry, should concatenate file correctly", async () => {
const bunfig = `[install]${EOL}optional = true${EOL}${EOL}[install.scopes]${EOL}'@foo-bar' = { token = "$BUN_AUTH_TOKEN", url = "https://npm.pkg.github.com/" }${EOL}'@bla-ble' = { token = "$BUN_AUTH_TOKEN", url = "https://npm.pkg.github.com/" }${EOL}${EOL}[install.cache]${EOL}disable = true`;
await Bun.write(filePath, bunfig);
writeBunfig(filePath, [
{
url: "https://npm.pkg.github.com",
scope: "foo-bar",
token: "$BUN_AUTH_TOKEN",
},
]);
const { file, contents } = await getFileAndContents();
expect(file.exists()).resolves.toBeTrue();
const expectedContents = [
"[install]",
"optional = true",
"",
'[install.scopes."@foo-bar"]',
'url = "https://npm.pkg.github.com"',
'token = "$BUN_AUTH_TOKEN"',
"",
'[install.scopes."@bla-ble"]',
'token = "$BUN_AUTH_TOKEN"',
'url = "https://npm.pkg.github.com/"',
"",
" [install.cache]",
" disable = true",
"",
];
contents.forEach((content, index) =>
expect(content).toBe(expectedContents[index])
);
expect(contents.length).toBe(expectedContents.length);
});
it("and [install.scopes] and [install] exists, should concantenate file correctly", async () => {
const bunfig = `[install]${EOL}optional = true${EOL}${EOL}[install.scopes]${EOL}'@foo-bar' = { token = "$BUN_AUTH_TOKEN", url = "https://npm.pkg.github.com/" }${EOL}'@bla-ble' = { token = "$BUN_AUTH_TOKEN", url = "https://npm.pkg.github.com/" }${EOL}${EOL}[install.cache]${EOL}disable = true`;
await Bun.write(filePath, bunfig);
writeBunfig(filePath, [
{
url: "https://npm.pkg.github.com",
scope: "foo-bar",
token: "$BUN_AUTH_TOKEN",
},
{
url: "https://bun.sh",
scope: "",
token: "$BUN_AUTH_TOKEN",
}, // global registry
]);
const { file, contents } = await getFileAndContents();
expect(file.exists()).resolves.toBeTrue();
const expectedContents = [
"[install]",
"optional = true",
"",
'[install.scopes."@foo-bar"]',
'url = "https://npm.pkg.github.com"',
'token = "$BUN_AUTH_TOKEN"',
"",
'[install.scopes."@bla-ble"]',
'token = "$BUN_AUTH_TOKEN"',
'url = "https://npm.pkg.github.com/"',
"",
" [install.cache]",
" disable = true",
"",
" [install.registry]",
' url = "https://bun.sh"',
' token = "$BUN_AUTH_TOKEN"',
"",
];
contents.forEach((content, index) =>
expect(content).toBe(expectedContents[index])
);
expect(contents.length).toBe(expectedContents.length);
});
});
describe("when multiple global registries are provided", () => {
it("should throw an error", () => {
expect(() => {
writeBunfig(filePath, [
{
url: "https://npm.pkg.github.com",
scope: "",
token: "$BUN_AUTH_TOKEN",
},
{
url: "https://bun.sh",
scope: "",
token: "$BUN_AUTH_TOKEN",
},
]);
}).toThrow("You can't have more than one global registry.");
});
});
});

170
tests/registry.spec.ts Normal file
View File

@ -0,0 +1,170 @@
import { describe, expect, it } from "bun:test";
import { parseRegistries } from "../src/registry";
describe("registry", () => {
describe("parseRegistries", () => {
it("should return an empty array for empty input", () => {
expect(parseRegistries("")).toEqual([]);
expect(parseRegistries(" ")).toEqual([]);
expect(parseRegistries(null as any)).toEqual([]);
expect(parseRegistries(undefined as any)).toEqual([]);
});
it("should parse default registry without token", () => {
const input = "https://registry.npmjs.org/";
const result = parseRegistries(input);
expect(result).toEqual([
{
url: "https://registry.npmjs.org/",
scope: "",
},
]);
});
it("should parse default registry with token", () => {
const input = "https://registry.npmjs.org/|npm_token123";
const result = parseRegistries(input);
expect(result).toEqual([
{
url: "https://registry.npmjs.org/",
scope: "",
token: "npm_token123",
},
]);
});
it("should parse scoped registry with URL credentials", () => {
const input = "@myorg:https://username:password@registry.myorg.com/";
const result = parseRegistries(input);
expect(result).toEqual([
{
url: "https://username:password@registry.myorg.com/",
scope: "@myorg",
},
]);
});
it("should parse scoped registry with separate token", () => {
const input = "@partner:https://registry.partner.com/|token_abc123";
const result = parseRegistries(input);
expect(result).toEqual([
{
url: "https://registry.partner.com/",
scope: "@partner",
token: "token_abc123",
},
]);
});
it("should parse multiple registries", () => {
const input = `
https://registry.npmjs.org/
@myorg:https://username:password@registry.myorg.com/
@partner:https://registry.partner.com/|token_abc123
`;
const result = parseRegistries(input);
expect(result).toEqual([
{
url: "https://registry.npmjs.org/",
scope: "",
},
{
url: "https://username:password@registry.myorg.com/",
scope: "@myorg",
},
{
url: "https://registry.partner.com/",
scope: "@partner",
token: "token_abc123",
},
]);
});
it("should handle scope names without @ prefix", () => {
const input = "myorg:https://registry.myorg.com/|token123";
const result = parseRegistries(input);
expect(result).toEqual([
{
url: "https://registry.myorg.com/",
scope: "myorg",
token: "token123",
},
]);
});
it("should throw error for invalid URLs", () => {
expect(() => {
parseRegistries("@myorg:not-a-valid-url");
}).toThrow("Invalid URL in registry configuration: not-a-valid-url");
expect(() => {
parseRegistries("also-not-a-valid-url");
}).toThrow("Invalid URL in registry configuration: also-not-a-valid-url");
});
it("should handle whitespace and empty lines", () => {
const input = `
https://registry.npmjs.org/
@myorg:https://registry.myorg.com/|token123
`;
const result = parseRegistries(input);
expect(result).toEqual([
{
url: "https://registry.npmjs.org/",
scope: "",
},
{
url: "https://registry.myorg.com/",
scope: "@myorg",
token: "token123",
},
]);
});
it("should handle URLs with paths and query parameters", () => {
const input =
"@myorg:https://registry.myorg.com/npm/registry/?timeout=5000|token123";
const result = parseRegistries(input);
expect(result).toEqual([
{
url: "https://registry.myorg.com/npm/registry/?timeout=5000",
scope: "@myorg",
token: "token123",
},
]);
});
it("should correctly handle scopes with hyphens and underscores", () => {
const input = `
@my-org:https://registry.myorg.com/
@my_org:https://registry.myorg.com/
`;
const result = parseRegistries(input);
expect(result).toEqual([
{
url: "https://registry.myorg.com/",
scope: "@my-org",
},
{
url: "https://registry.myorg.com/",
scope: "@my_org",
},
]);
});
});
});

8
tests/tsconfig.json Normal file
View File

@ -0,0 +1,8 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"types": [
"bun"
]
}
}