Various improvements and fixes to setup-bun (#40)

* Do not save cache on hit
* Support Windows (canary only)
* Support `registry-url` and `scope`
This commit is contained in:
Ashcon Partovi 2023-11-17 15:58:17 -08:00 committed by GitHub
parent 830e319e28
commit a93230df19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 994 additions and 78881 deletions

30
.github/workflows/format.yml vendored Normal file
View File

@ -0,0 +1,30 @@
name: autofix.ci # Must be named this for autofix.ci to work
on:
workflow_dispatch:
pull_request:
push:
branches:
- main
permissions:
contents: read
jobs:
format:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20.x
- name: Install Dependencies
run: npm install
- name: Format
run: |
npm run format
npm run build
- name: Commit
uses: autofix-ci/action@d3e591514b99d0fca6779455ff8338516663f7cc

View File

@ -1,37 +1,48 @@
name: Setup Bun
name: Test
on:
workflow_dispatch:
pull_request:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:
permissions:
contents: read
jobs:
setup-bun:
runs-on: ${{ matrix.os }}
continue-on-error: true
strategy:
matrix:
include:
- os: windows-latest
bun-version: canary
os:
- ubuntu-latest
- macos-latest
bun-version:
- latest
- canary
- "0.5.6"
- "9be68ac2350b965037f408ce4d47c3b9d9a76b63"
- "0.8.1" # last version before 1.0
- "0.x"
- "1.0.0"
- "1.x"
- "1"
- "> 1.0.0"
- "< 2"
# Disable <sha> support for now. This is because Github Artifacts
# expire after 90 days, and we don't have another source of truth yet.
# - "822a00c4d508b54f650933a73ca5f4a3af9a7983" # 1.0.0 commit
steps:
- id: checkout
name: Checkout
uses: actions/checkout@v3
- id: setup-bun
name: Setup Bun
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: ./
with:
bun-version: ${{ matrix.bun-version }}
- id: verify-bun
name: Verify Bun
- name: Run Bun
run: |
bun --version
setup-bun-from-package-json-version:
@ -49,6 +60,6 @@ jobs:
echo "$(jq '. += {"packageManager": "bun@1.0.0"}' package.json)" > package.json
- name: Setup Bun
uses: ./
- name: Verify Bun
- name: Run Bun
run: |
bun --version

View File

@ -10,36 +10,36 @@ Download, install, and setup [Bun](https://bun.sh) in GitHub Actions.
bun-version: latest
```
### Setup custom registry-url and scope (for private packages)
### Using a custom NPM registry
```yaml
- uses: oven-sh/setup-bun@v1
with:
registry-url: "https://npm.pkg.github.com/"
scope: "@foo-bar"
scope: "@foo"
```
After setting up the registry-url and scope, when installing step comes, inject the env to authenticate and install all packages from the private registry
If you need to authenticate with a private registry, you can set the `BUN_AUTH_TOKEN` environment variable.
```yaml
- name: Installing dependencies
- name: Install Dependencies
env:
BUN_AUTH_TOKEN: ${{ secrets.MY_SUPER_SECRET_PAT }}
run: bun i
BUN_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: bun install
```
## Inputs
| Name | Description | Default | Examples |
| -------------- | -------------------------------------------------- | ----------- | ------------------------------- |
| `bun-version` | The version of Bun to download and install. | `latest` | `canary`, `1.0.0`, `<sha>` |
| `bun-version` | The version of Bun to download and install. | `latest` | `canary`, `1.0.0`, `1.0.x` |
| `registry-url` | Registry URL where some private package is stored. | `undefined` | `"https://npm.pkg.github.com/"` |
| `scope` | Scope for private pacakages | `undefined` | `"@foo-bar"`, `"@orgname"` |
| `scope` | Scope for private pacakages. | `undefined` | `"@foo"`, `"@orgname"` |
## Outputs
| Name | Description | Example |
| -------------- | ------------------------------------------ | ------------------------------------------------ |
| `cache-hit` | If the Bun executable was read from cache. | `true` |
| `bun-version` | The output from `bun --version`. | `1.0.0` |
| `bun-revision` | The output from `bun --revision`. | `1.0.0+822a00c4d508b54f650933a73ca5f4a3af9a7983` |
| Name | Description | Example |
| -------------- | ------------------------------------------ | ---------------- |
| `cache-hit` | If the Bun executable was read from cache. | `true` |
| `bun-version` | The output from `bun --version`. | `1.0.0` |
| `bun-revision` | The output from `bun --revision`. | `1.0.0+822a00c4` |

View File

@ -6,20 +6,22 @@ branding:
color: white
inputs:
bun-version:
description: 'The version of Bun to install. (e.g. "latest", "canary", "0.5.6", <sha>)'
description: 'The version of Bun to install. (e.g. "latest", "canary", "1.0.0", "1.0.x", <sha>)'
default: latest
required: false
registry-url:
required: false
description: "Optional registry to set up for auth. Will set the registry in a project level build.toml file, and set up auth to read in from env.BUN_AUTH_TOKEN."
description: "The URL of the package registry to use for installing Bun. Set the $BUN_AUTH_TOKEN environment variable to authenticate with the registry."
scope:
required: false
description: "Optional scope for authenticating against scoped registries."
description: "The scope for authenticating with the package registry."
outputs:
bun-version:
description: The version of Bun that was installed.
bun-revision:
description: The revision of Bun that was installed.
cache-hit:
description: If the version of Bun was cached.
runs:
using: node20
main: dist/action.js
main: dist/index.js

78594
dist/action.js generated vendored

File diff suppressed because one or more lines are too long

69
dist/index.js generated vendored Normal file

File diff suppressed because one or more lines are too long

577
package-lock.json generated Normal file
View File

@ -0,0 +1,577 @@
{
"name": "setup-bun",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "setup-bun",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@actions/cache": "^3.1.4",
"@actions/core": "^1.10.0",
"@actions/exec": "^1.1.1",
"@actions/glob": "^0.4.0",
"@actions/io": "^1.1.2",
"@actions/tool-cache": "^2.0.1"
},
"devDependencies": {
"@types/node": "^20.8.2",
"esbuild": "^0.19.2",
"prettier": "^2.8.4",
"typescript": "^4.9.5"
}
},
"node_modules/@actions/cache": {
"version": "3.2.2",
"license": "MIT",
"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.1.0",
"uuid": "^3.3.3"
}
},
"node_modules/@actions/cache/node_modules/@actions/glob": {
"version": "0.1.2",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.2.6",
"minimatch": "^3.0.4"
}
},
"node_modules/@actions/core": {
"version": "1.10.1",
"license": "MIT",
"dependencies": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
}
},
"node_modules/@actions/core/node_modules/uuid": {
"version": "8.3.2",
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/@actions/exec": {
"version": "1.1.1",
"license": "MIT",
"dependencies": {
"@actions/io": "^1.0.1"
}
},
"node_modules/@actions/glob": {
"version": "0.4.0",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.9.1",
"minimatch": "^3.0.4"
}
},
"node_modules/@actions/http-client": {
"version": "2.1.1",
"license": "MIT",
"dependencies": {
"tunnel": "^0.0.6"
}
},
"node_modules/@actions/io": {
"version": "1.1.3",
"license": "MIT"
},
"node_modules/@actions/tool-cache": {
"version": "2.0.1",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.2.6",
"@actions/exec": "^1.0.0",
"@actions/http-client": "^2.0.1",
"@actions/io": "^1.1.1",
"semver": "^6.1.0",
"uuid": "^3.3.2"
}
},
"node_modules/@azure/abort-controller": {
"version": "1.1.0",
"license": "MIT",
"dependencies": {
"tslib": "^2.2.0"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/@azure/core-auth": {
"version": "1.5.0",
"license": "MIT",
"dependencies": {
"@azure/abort-controller": "^1.0.0",
"@azure/core-util": "^1.1.0",
"tslib": "^2.2.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@azure/core-http": {
"version": "3.0.3",
"license": "MIT",
"dependencies": {
"@azure/abort-controller": "^1.0.0",
"@azure/core-auth": "^1.3.0",
"@azure/core-tracing": "1.0.0-preview.13",
"@azure/core-util": "^1.1.1",
"@azure/logger": "^1.0.0",
"@types/node-fetch": "^2.5.0",
"@types/tunnel": "^0.0.3",
"form-data": "^4.0.0",
"node-fetch": "^2.6.7",
"process": "^0.11.10",
"tslib": "^2.2.0",
"tunnel": "^0.0.6",
"uuid": "^8.3.0",
"xml2js": "^0.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@azure/core-http/node_modules/form-data": {
"version": "4.0.0",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/@azure/core-http/node_modules/uuid": {
"version": "8.3.2",
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/@azure/core-lro": {
"version": "2.5.4",
"license": "MIT",
"dependencies": {
"@azure/abort-controller": "^1.0.0",
"@azure/core-util": "^1.2.0",
"@azure/logger": "^1.0.0",
"tslib": "^2.2.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@azure/core-paging": {
"version": "1.5.0",
"license": "MIT",
"dependencies": {
"tslib": "^2.2.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@azure/core-tracing": {
"version": "1.0.0-preview.13",
"license": "MIT",
"dependencies": {
"@opentelemetry/api": "^1.0.1",
"tslib": "^2.2.0"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/@azure/core-util": {
"version": "1.4.0",
"license": "MIT",
"dependencies": {
"@azure/abort-controller": "^1.0.0",
"tslib": "^2.2.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@azure/logger": {
"version": "1.0.4",
"license": "MIT",
"dependencies": {
"tslib": "^2.2.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@azure/ms-rest-js": {
"version": "2.7.0",
"license": "MIT",
"dependencies": {
"@azure/core-auth": "^1.1.4",
"abort-controller": "^3.0.0",
"form-data": "^2.5.0",
"node-fetch": "^2.6.7",
"tslib": "^1.10.0",
"tunnel": "0.0.6",
"uuid": "^8.3.2",
"xml2js": "^0.5.0"
}
},
"node_modules/@azure/ms-rest-js/node_modules/tslib": {
"version": "1.14.1",
"license": "0BSD"
},
"node_modules/@azure/ms-rest-js/node_modules/uuid": {
"version": "8.3.2",
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/@azure/storage-blob": {
"version": "12.15.0",
"license": "MIT",
"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"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.19.2",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@opentelemetry/api": {
"version": "1.5.0",
"license": "Apache-2.0",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/@types/node": {
"version": "20.9.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.1.tgz",
"integrity": "sha512-HhmzZh5LSJNS5O8jQKpJ/3ZcrrlG6L70hpGqMIAoM9YVD0YBRNWYsfwcXq8VnSjlNpCpgLzMXdiPo+dxcvSmiA==",
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@types/node-fetch": {
"version": "2.6.4",
"license": "MIT",
"dependencies": {
"@types/node": "*",
"form-data": "^3.0.0"
}
},
"node_modules/@types/node-fetch/node_modules/form-data": {
"version": "3.0.1",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/@types/tunnel": {
"version": "0.0.3",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/abort-controller": {
"version": "3.0.0",
"license": "MIT",
"dependencies": {
"event-target-shim": "^5.0.0"
},
"engines": {
"node": ">=6.5"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"license": "MIT"
},
"node_modules/balanced-match": {
"version": "1.0.2",
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"license": "MIT"
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/esbuild": {
"version": "0.19.2",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/android-arm": "0.19.2",
"@esbuild/android-arm64": "0.19.2",
"@esbuild/android-x64": "0.19.2",
"@esbuild/darwin-arm64": "0.19.2",
"@esbuild/darwin-x64": "0.19.2",
"@esbuild/freebsd-arm64": "0.19.2",
"@esbuild/freebsd-x64": "0.19.2",
"@esbuild/linux-arm": "0.19.2",
"@esbuild/linux-arm64": "0.19.2",
"@esbuild/linux-ia32": "0.19.2",
"@esbuild/linux-loong64": "0.19.2",
"@esbuild/linux-mips64el": "0.19.2",
"@esbuild/linux-ppc64": "0.19.2",
"@esbuild/linux-riscv64": "0.19.2",
"@esbuild/linux-s390x": "0.19.2",
"@esbuild/linux-x64": "0.19.2",
"@esbuild/netbsd-x64": "0.19.2",
"@esbuild/openbsd-x64": "0.19.2",
"@esbuild/sunos-x64": "0.19.2",
"@esbuild/win32-arm64": "0.19.2",
"@esbuild/win32-ia32": "0.19.2",
"@esbuild/win32-x64": "0.19.2"
}
},
"node_modules/event-target-shim": {
"version": "5.0.1",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/events": {
"version": "3.3.0",
"license": "MIT",
"engines": {
"node": ">=0.8.x"
}
},
"node_modules/form-data": {
"version": "2.5.1",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 0.12"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/minimatch": {
"version": "3.0.8",
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/node-fetch": {
"version": "2.7.0",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/prettier": {
"version": "2.8.8",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/process": {
"version": "0.11.10",
"license": "MIT",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/sax": {
"version": "1.2.4",
"license": "ISC"
},
"node_modules/semver": {
"version": "6.3.1",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"license": "MIT"
},
"node_modules/tslib": {
"version": "2.6.2",
"license": "0BSD"
},
"node_modules/tunnel": {
"version": "0.0.6",
"license": "MIT",
"engines": {
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
}
},
"node_modules/typescript": {
"version": "4.9.5",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
},
"node_modules/uuid": {
"version": "3.4.0",
"license": "MIT",
"bin": {
"uuid": "bin/uuid"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"license": "BSD-2-Clause"
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/xml2js": {
"version": "0.5.0",
"license": "MIT",
"dependencies": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/xmlbuilder": {
"version": "11.0.1",
"license": "MIT",
"engines": {
"node": ">=4.0"
}
}
}
}

View File

@ -10,16 +10,16 @@
"actions",
"setup"
],
"homepage": "https://bun.sh",
"main": "dist/action.js",
"homepage": "https://bun.sh/",
"main": "dist/index.js",
"repository": "git@github.com:oven-sh/setup-bun.git",
"bugs": "https://github.com/oven-sh/setup-bun/issues",
"license": "MIT",
"author": "xHyroM",
"scripts": {
"format": "prettier --write src *.yml *.json *.md",
"build": "esbuild --target=node16 --outdir=dist --bundle --platform=node --format=cjs src/action.ts",
"start": "bun run build && node dist/action.js"
"build": "esbuild --target=node20 --outdir=dist --bundle --minify --platform=node --format=cjs src/index.ts",
"start": "npm run build && node dist/index.js"
},
"dependencies": {
"@actions/cache": "^3.1.4",

View File

@ -1,42 +1,178 @@
import { tmpdir } from "node:os";
import * as action from "@actions/core";
import setup from "./setup.js";
import { existsSync, readFileSync } from "fs";
import * as path from "path";
import { homedir } from "node:os";
import { join } from "node:path";
import {
mkdirSync,
readdirSync,
symlinkSync,
renameSync,
copyFileSync,
} from "node:fs";
import { addPath, info, warning } from "@actions/core";
import { isFeatureAvailable, restoreCache, saveCache } from "@actions/cache";
import { downloadTool, extractZip } from "@actions/tool-cache";
import { getExecOutput } from "@actions/exec";
import { writeBunfig } from "./bunfig";
if (!process.env.RUNNER_TEMP) {
process.env.RUNNER_TEMP = tmpdir();
export type Input = {
customUrl?: string;
version?: string;
os?: string;
arch?: string;
avx2?: boolean;
profile?: boolean;
scope?: string;
registryUrl?: string;
};
export type Output = {
version: string;
revision: string;
cacheHit: boolean;
};
export default async (options: Input): Promise<Output> => {
const bunfigPath = join(process.cwd(), "bunfig.toml");
writeBunfig(bunfigPath, options);
const url = getDownloadUrl(options);
const cacheEnabled = isCacheEnabled(options);
const binPath = join(homedir(), ".bun", "bin");
try {
mkdirSync(binPath, { recursive: true });
} catch (error) {
if (error.code !== "EEXIST") {
throw error;
}
}
addPath(binPath);
const exe = (name: string) =>
process.platform === "win32" ? `${name}.exe` : name;
const bunPath = join(binPath, exe("bun"));
try {
symlinkSync(bunPath, join(binPath, exe("bunx")));
} catch (error) {
if (error.code !== "EEXIST") {
throw error;
}
}
let revision: string | undefined;
let cacheHit = false;
if (cacheEnabled) {
const cacheRestored = await restoreCache([bunPath], url);
if (cacheRestored) {
revision = await getRevision(bunPath);
if (revision) {
cacheHit = true;
info(`Using a cached version of Bun: ${revision}`);
} else {
warning(
`Found a cached version of Bun: ${revision} (but it appears to be corrupted?)`
);
}
}
}
if (!cacheHit) {
info(`Downloading a new version of Bun: ${url}`);
const zipPath = await downloadTool(url);
const extractedZipPath = await extractZip(zipPath);
const extractedBunPath = await extractBun(extractedZipPath);
try {
renameSync(extractedBunPath, bunPath);
} catch {
// If mv does not work, try to copy the file instead.
// For example: EXDEV: cross-device link not permitted
copyFileSync(extractedBunPath, bunPath);
}
revision = await getRevision(bunPath);
}
if (!revision) {
throw new Error(
"Downloaded a new version of Bun, but failed to check its version? Try again."
);
}
if (cacheEnabled && !cacheHit) {
try {
await saveCache([bunPath], url);
} catch (error) {
warning("Failed to save Bun to cache.");
}
}
const [version] = revision.split("+");
return {
version,
revision,
cacheHit,
};
};
function isCacheEnabled(options: Input): boolean {
const { customUrl, version } = options;
if (customUrl) {
return false;
}
if (!version || /latest|canary|action/i.test(version)) {
return false;
}
return isFeatureAvailable();
}
function readVersionFromPackageJson(): string | undefined {
const { GITHUB_WORKSPACE } = process.env;
if (!GITHUB_WORKSPACE) {
return;
function getDownloadUrl(options: Input): string {
const { customUrl } = options;
if (customUrl) {
return customUrl;
}
const pathToPackageJson = path.join(GITHUB_WORKSPACE, "package.json");
if (!existsSync(pathToPackageJson)) {
return;
}
const { packageManager } = JSON.parse(
readFileSync(pathToPackageJson, "utf8")
) as { packageManager?: string };
return packageManager?.split("bun@")[1];
const { version, os, arch, avx2, profile } = options;
const eversion = encodeURIComponent(version ?? "latest");
const eos = encodeURIComponent(os ?? process.platform);
const earch = encodeURIComponent(arch ?? process.arch);
const eavx2 = encodeURIComponent(avx2 ?? true);
const eprofile = encodeURIComponent(profile ?? false);
const { href } = new URL(
`${eversion}/${eos}/${earch}?avx2=${eavx2}&profile=${eprofile}`,
"https://bun.sh/download/"
);
return href;
}
setup({
version:
readVersionFromPackageJson() || action.getInput("bun-version") || undefined,
customUrl: action.getInput("bun-download-url") || undefined,
registryUrl: action.getInput("registry-url") || undefined,
scope: action.getInput("scope") || undefined,
})
.then(({ version, revision, cacheHit, registryUrl, scope }) => {
action.setOutput("bun-version", version);
action.setOutput("bun-revision", revision);
action.setOutput("cache-hit", cacheHit);
action.setOutput("registry-url", registryUrl);
action.setOutput("scope", scope);
})
.catch((error) => {
action.setFailed(error);
async function extractBun(path: string): Promise<string> {
for (const entry of readdirSync(path, { withFileTypes: true })) {
const { name } = entry;
const entryPath = join(path, name);
if (entry.isFile()) {
if (name === "bun" || name === "bun.exe") {
return entryPath;
}
if (/^bun.*\.zip/.test(name)) {
const extractedPath = await extractZip(entryPath);
return extractBun(extractedPath);
}
}
if (/^bun/.test(name) && entry.isDirectory()) {
return extractBun(entryPath);
}
}
throw new Error("Could not find executable: bun");
}
async function getRevision(exe: string): Promise<string | undefined> {
const revision = await getExecOutput(exe, ["--revision"], {
ignoreReturnCode: true,
});
if (revision.exitCode === 0 && /^\d+\.\d+\.\d+/.test(revision.stdout)) {
return revision.stdout.trim();
}
const version = await getExecOutput(exe, ["--version"], {
ignoreReturnCode: true,
});
if (version.exitCode === 0 && /^\d+\.\d+\.\d+/.test(version.stdout)) {
return version.stdout.trim();
}
return undefined;
}

View File

@ -1,57 +0,0 @@
import { EOL } from "node:os";
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { resolve } from "node:path";
import * as core from "@actions/core";
export function configureAuthentication(registryUrl: string, scope: string) {
const bunfigPath = resolve(process.cwd(), "bunfig.toml");
if (!registryUrl.endsWith("/")) {
registryUrl += "/";
}
writeRegistryToConfigFile({ registryUrl, fileLocation: bunfigPath, scope });
}
type WriteRegistryToConfigFile = {
registryUrl: string;
fileLocation: string;
scope: string;
};
function writeRegistryToConfigFile({
registryUrl,
fileLocation,
scope,
}: WriteRegistryToConfigFile) {
if (scope && scope[0] !== "@") {
scope = "@" + scope;
}
if (scope) {
scope = scope.toLocaleLowerCase();
}
core.info(`Setting auth in ${fileLocation}`);
let newContents = "";
if (existsSync(fileLocation)) {
const curContents = readFileSync(fileLocation, "utf8");
curContents.split(EOL).forEach((line: string) => {
// Add current contents unless they are setting the registry
if (!line.toLowerCase().startsWith(scope)) {
newContents += line + EOL;
}
});
newContents += EOL;
}
const bunRegistryString = `'${scope}' = { token = "$BUN_AUTH_TOKEN", url = "${registryUrl}"}`;
newContents += `[install.scopes]${EOL}${EOL}${bunRegistryString}${EOL}`;
writeFileSync("./bunfig.toml", newContents);
}

50
src/bunfig.ts Normal file
View File

@ -0,0 +1,50 @@
import { EOL } from "node:os";
import { appendFileSync } from "node:fs";
import { info } from "@actions/core";
type BunfigOptions = {
registryUrl?: string;
scope?: string;
};
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) {
return;
}
info(`Writing bunfig.toml to '${path}'.`);
appendFileSync(path, bunfig, {
encoding: "utf8",
});
}

46
src/index.ts Normal file
View File

@ -0,0 +1,46 @@
import { tmpdir } from "node:os";
import { join } from "node:path";
import { existsSync, readFileSync } from "node:fs";
import { getInput, setOutput, setFailed, warning } from "@actions/core";
import runAction from "./action.js";
if (!process.env.RUNNER_TEMP) {
process.env.RUNNER_TEMP = tmpdir();
}
function readVersionFromPackageJson(): string | undefined {
const cwd = process.env.GITHUB_WORKSPACE;
if (!cwd) {
return;
}
const path = join(cwd, "package.json");
try {
if (!existsSync(path)) {
return;
}
const { packageManager } = JSON.parse(readFileSync(path, "utf8"));
if (!packageManager?.startsWith("bun@")) {
return;
}
const [_, version] = packageManager.split("bun@");
return version;
} catch (error) {
const { message } = error as Error;
warning(`Failed to read package.json: ${message}`);
}
}
runAction({
version: getInput("bun-version") || readVersionFromPackageJson() || undefined,
customUrl: getInput("bun-download-url") || undefined,
registryUrl: getInput("registry-url") || undefined,
scope: getInput("scope") || undefined,
})
.then(({ version, revision, cacheHit }) => {
setOutput("bun-version", version);
setOutput("bun-revision", revision);
setOutput("cache-hit", cacheHit);
})
.catch((error) => {
setFailed(error);
});

View File

@ -1,158 +0,0 @@
import { homedir } from "node:os";
import { join } from "node:path";
import { readdir, symlink } from "node:fs/promises";
import * as action from "@actions/core";
import * as cache from "@actions/cache";
import { downloadTool, extractZip } from "@actions/tool-cache";
import { cp, mkdirP, rmRF } from "@actions/io";
import { getExecOutput } from "@actions/exec";
import { configureAuthentication } from "./auth";
export default async (options?: {
version?: string;
customUrl?: string;
scope?: string;
registryUrl?: string;
}): Promise<{
version: string;
revision: string;
cacheHit: boolean;
scope?: string;
registryUrl?: string;
}> => {
const { url, cacheKey } = getDownloadUrl(options);
const cacheEnabled = cacheKey && cache.isFeatureAvailable();
const dir = join(homedir(), ".bun", "bin");
action.addPath(dir);
const path = join(dir, "bun");
let revision: string | undefined;
let cacheHit = false;
if (cacheEnabled) {
const cacheRestored = await cache.restoreCache([path], cacheKey);
if (cacheRestored) {
revision = await verifyBun(path);
if (revision) {
cacheHit = true;
action.info("Using a cached version of Bun.");
} else {
action.warning(
"Found a cached version of Bun, but it appears to be corrupted? Attempting to download a new version."
);
}
}
}
if (!cacheHit) {
action.info(`Downloading a new version of Bun: ${url}`);
const zipPath = await downloadTool(url);
const extractedPath = await extractZip(zipPath);
const exePath = await extractBun(extractedPath);
await mkdirP(dir);
await cp(exePath, path);
await rmRF(exePath);
revision = await verifyBun(path);
}
try {
await symlink(path, join(dir, "bunx"));
} catch (error) {
if (error.code !== "EEXIST") {
throw error;
}
}
if (!revision) {
throw new Error(
"Downloaded a new version of Bun, but failed to check its version? Try again in debug mode."
);
}
if (cacheEnabled) {
try {
await cache.saveCache([path], cacheKey);
} catch (error) {
action.warning("Failed to save Bun to cache.");
}
}
const [version] = revision.split("+");
const { registryUrl, scope } = options;
if (!!registryUrl && !!scope) {
configureAuthentication(registryUrl, scope);
}
return {
version,
revision,
cacheHit,
registryUrl,
scope,
};
};
function getDownloadUrl(options?: {
customUrl?: string;
version?: string;
os?: string;
arch?: string;
avx2?: boolean;
profile?: boolean;
}): {
url: string;
cacheKey: string | null;
} {
if (options?.customUrl) {
return {
url: options.customUrl,
cacheKey: null,
};
}
const release = encodeURIComponent(options?.version ?? "latest");
const os = encodeURIComponent(options?.os ?? process.platform);
const arch = encodeURIComponent(options?.arch ?? process.arch);
const avx2 = encodeURIComponent(options?.avx2 ?? true);
const profile = encodeURIComponent(options?.profile ?? false);
const { href } = new URL(
`${release}/${os}/${arch}?avx2=${avx2}&profile=${profile}`,
"https://bun.sh/download/"
);
return {
url: href,
cacheKey: /^latest|canary|action/i.test(release)
? null
: `bun-${release}-${os}-${arch}-${avx2}-${profile}`,
};
}
async function extractBun(path: string): Promise<string> {
for (const entry of await readdir(path, { withFileTypes: true })) {
const { name } = entry;
const entryPath = join(path, name);
if (entry.isFile()) {
if (name === "bun") {
return entryPath;
}
if (/^bun.*\.zip/.test(name)) {
const extractedPath = await extractZip(entryPath);
return extractBun(extractedPath);
}
}
if (/^bun/.test(name) && entry.isDirectory()) {
return extractBun(entryPath);
}
}
throw new Error("Could not find executable: bun");
}
async function verifyBun(path: string): Promise<string | undefined> {
const revision = await getExecOutput(path, ["--revision"], {
ignoreReturnCode: true,
});
if (revision.exitCode === 0 && /^\d+\.\d+\.\d+/.test(revision.stdout)) {
return revision.stdout.trim();
}
const version = await getExecOutput(path, ["--version"], {
ignoreReturnCode: true,
});
if (version.exitCode === 0 && /^\d+\.\d+\.\d+/.test(version.stdout)) {
return version.stdout.trim();
}
return undefined;
}

View File

@ -5,6 +5,7 @@
"module": "ES2020",
"target": "ES2020",
"moduleResolution": "node",
"skipLibCheck": true
"skipLibCheck": true,
"types": ["node"]
}
}