This commit is contained in:
Ashcon Partovi 2023-11-17 12:37:18 -08:00
parent 830e319e28
commit 8f31cd5980
13 changed files with 403 additions and 78879 deletions

28
.github/workflows/format.yml vendored Normal file
View file

@ -0,0 +1,28 @@
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 build
- name: Commit
uses: autofix-ci/action@d3e591514b99d0fca6779455ff8338516663f7cc

View file

@ -1,37 +1,40 @@
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:
test:
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"
- "1.0.0"
- "1.x"
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:

View file

@ -10,22 +10,22 @@ 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
@ -34,12 +34,12 @@ After setting up the registry-url and scope, when installing step comes, inject
| -------------- | -------------------------------------------------- | ----------- | ------------------------------- |
| `bun-version` | The version of Bun to download and install. | `latest` | `canary`, `1.0.0`, `<sha>` |
| `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,18 +6,20 @@ 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:

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

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,176 @@
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,
readFileSync,
} 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 bunPath = join(binPath, "bun");
try {
symlinkSync(bunPath, join(binPath, "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"]
}
}