Compare commits

...

1 Commits

Author SHA1 Message Date
Ashcon Partovi
c2ea72c461 Fix bun install -g in Dockerfiles 2024-03-26 15:58:30 -07:00
12 changed files with 192 additions and 9 deletions

21
.vscode/settings.json vendored
View File

@@ -40,7 +40,7 @@
// C++
"lldb.verboseLogging": false,
"cmake.configureOnOpen": false,
"C_Cpp.errorSquiggles": "enabled",
"C_Cpp.errorSquiggles": "enabled",
"[cpp]": {
"editor.defaultFormatter": "xaver.clang-format"
},
@@ -55,7 +55,7 @@
"prettier.enable": true,
"eslint.workingDirectories": ["${workspaceFolder}/packages/bun-types"],
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
@@ -72,12 +72,12 @@
// JSON
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
// Markdown
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
@@ -94,12 +94,17 @@
// TOML
"[toml]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
// YAML
"[yaml]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
// Dockerfile
"[dockerfile]": {
"editor.formatOnSave": false
},
// Files
@@ -148,5 +153,5 @@
"WebKit/WebDriver": true,
"WebKit/WebKitBuild": true,
"WebKit/WebInspectorUI": true
},
}
}

View File

@@ -96,6 +96,9 @@ FROM alpine:3.18
ARG BUN_RUNTIME_TRANSPILER_CACHE_PATH=0
ENV BUN_RUNTIME_TRANSPILER_CACHE_PATH=${BUN_RUNTIME_TRANSPILER_CACHE_PATH}
ARG BUN_INSTALL_BIN=/usr/local/bin
ENV BUN_INSTALL_BIN=${BUN_INSTALL_BIN}
COPY --from=build /usr/local/bin/bun /usr/local/bin/
COPY docker-entrypoint.sh /usr/local/bin/

View File

@@ -62,6 +62,9 @@ FROM debian:bullseye-slim
ARG BUN_RUNTIME_TRANSPILER_CACHE_PATH=0
ENV BUN_RUNTIME_TRANSPILER_CACHE_PATH=${BUN_RUNTIME_TRANSPILER_CACHE_PATH}
ARG BUN_INSTALL_BIN=/usr/local/bin
ENV BUN_INSTALL_BIN=${BUN_INSTALL_BIN}
COPY docker-entrypoint.sh /usr/local/bin
COPY --from=build /usr/local/bin/bun /usr/local/bin/bun

View File

@@ -63,6 +63,9 @@ COPY --from=build /usr/local/bin/bun /usr/local/bin/bun
ARG BUN_RUNTIME_TRANSPILER_CACHE_PATH=0
ENV BUN_RUNTIME_TRANSPILER_CACHE_PATH=${BUN_RUNTIME_TRANSPILER_CACHE_PATH}
ARG BUN_INSTALL_BIN=/usr/local/bin
ENV BUN_INSTALL_BIN=${BUN_INSTALL_BIN}
RUN groupadd bun \
--gid 1000 \
&& useradd bun \

View File

@@ -62,6 +62,9 @@ FROM gcr.io/distroless/base-nossl-debian11
ARG BUN_RUNTIME_TRANSPILER_CACHE_PATH=0
ENV BUN_RUNTIME_TRANSPILER_CACHE_PATH=${BUN_RUNTIME_TRANSPILER_CACHE_PATH}
ARG BUN_INSTALL_BIN=/usr/local/bin
ENV BUN_INSTALL_BIN=${BUN_INSTALL_BIN}
COPY --from=build /usr/local/bin/bun /usr/local/bin/
# Temporarily use the `build`-stage image binaries to create a symlink:

View File

@@ -1,4 +1,4 @@
import { gc as bunGC, unsafe, which } from "bun";
import { gc as bunGC, spawnSync, unsafe, which } from "bun";
import { describe, test, expect, afterAll, beforeAll } from "bun:test";
import { readlink, readFile } from "fs/promises";
import { isAbsolute } from "path";
@@ -345,6 +345,24 @@ export function dockerExe(): string | null {
return which("docker") || which("podman") || null;
}
export function isDocker() {
const docker = dockerExe();
if (!docker) {
return false;
}
const { exitCode, stderr } = spawnSync({
cmd: [docker, "info"],
stderr: "pipe",
});
if (exitCode !== 0 || /cannot connect/i.test(stderr.toString())) {
return false;
}
return true;
}
export async function waitForPort(port: number, timeout: number = 60_000): Promise<void> {
let deadline = Date.now() + Math.max(1, timeout);
let error: unknown;

View File

@@ -0,0 +1,6 @@
FROM oven/bun
RUN bun install -g cowsay \
&& cowsay "Hello, World!" \
# FIXME: 'node': No such file or directory
# `bun install -g` could change shebang from node to bun?
|| true

View File

@@ -0,0 +1,2 @@
FROM oven/bun
RUN bun --revision

View File

@@ -0,0 +1,5 @@
FROM oven/bun
RUN bun install -g cowsay \
&& which cowsay \
&& bun uninstall -g cowsay \
&& which cowsay && echo "FAIL" || echo "PASS"

View File

@@ -0,0 +1,2 @@
FROM oven/bun
RUN bun --version

View File

@@ -0,0 +1,2 @@
FROM oven/bun
RUN bunx cowsay "Hello, World!"

View File

@@ -0,0 +1,131 @@
import { isLinux, isDocker, dockerExe } from "harness";
import { readFileSync, writeFileSync, mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, basename, dirname, extname } from "node:path";
import { spawn } from "bun";
import { describe, test, expect, beforeAll } from "bun:test";
const dockerPath = join(import.meta.dir, "..", "..", "..", "dockerhub");
const randomId = Math.random().toString(36).slice(2, 8);
const baseImage = `bun-test-${randomId}`;
const timeout = 5 * 60 * 1000; // 5 minutes
describe.if(isLinux && isDocker())("Dockerfile", async () => {
describe.each(["debian", "debian-slim", "alpine"])("%s", tag => {
beforeAll(async () => {
await buildBaseImage(tag);
});
test(
"bun --version",
async () => {
const { stderr } = await buildCustomImage("bun-version.dockerfile", tag);
expect(stderr).toContain(Bun.version);
},
{ timeout },
);
test(
"bun --revision",
async () => {
const { stderr } = await buildCustomImage("bun-revision.dockerfile", tag);
expect(stderr).toContain(Bun.revision.slice(0, 7));
},
{ timeout },
);
test(
"bun install -g cowsay",
async () => {
const { stderr } = await buildCustomImage("bun-install-g-cowsay.dockerfile", tag);
expect(stderr).toContain("No such file or directory");
},
{ timeout },
);
test(
"bun uninstall -g cowsay",
async () => {
const { stderr } = await buildCustomImage("bun-uninstall-g-cowsay.dockerfile", tag);
expect(stderr).toContain("PASS");
},
{ timeout },
);
test(
"bunx cowsay",
async () => {
const { stderr } = await buildCustomImage("bunx-cowsay.dockerfile", tag);
expect(stderr).toContain('"Hello, World!"');
},
{ timeout },
);
});
});
async function buildBaseImage(tag: string): Promise<void> {
const image = `${baseImage}:${tag}`;
// Build using the Dockerhub image as the base
{
const { exited, stderr } = spawn({
cwd: join(dockerPath, tag),
cmd: [dockerExe()!, "build", "--progress=plain", "-t", image, "."],
stderr: "pipe",
stdout: "pipe",
});
if ((await exited) !== 0) {
throw new Error(`Failed to build base Docker image: '${image}'`, {
cause: await Bun.readableStreamToText(stderr),
});
}
}
// Change the bun binary to use the current bun binary
{
const { execPath } = process;
const tmp = mkdtempSync(join(tmpdir(), "bun-docker-"));
const dockerfilePath = join(tmp, "Dockerfile");
writeFileSync(
dockerfilePath,
`FROM bun:${tag}
COPY ${basename(execPath)} /usr/local/bin/bun`,
);
const { exited, stderr } = spawn({
cwd: dirname(execPath),
cmd: [dockerExe()!, "build", "-f", dockerfilePath, "-t", image, "."],
stderr: "pipe",
});
if ((await exited) !== 0) {
throw new Error(`Failed to build base Docker image: '${image}'`, {
cause: await Bun.readableStreamToText(stderr),
});
}
}
}
async function buildCustomImage(
file: string,
tag: string,
): Promise<{
stdout: string;
stderr: string;
}> {
const image = `${baseImage}:${basename(file, extname(file))}-${tag}`;
const dockerfile = readFileSync(join(import.meta.dir, file), "utf8");
const tmp = mkdtempSync(join(tmpdir(), "bun-docker-"));
writeFileSync(join(tmp, "Dockerfile"), dockerfile.replace(/FROM oven\/bun/, `FROM ${baseImage}:${tag}`));
const { exited, stdout, stderr } = spawn({
cwd: tmp,
cmd: [dockerExe()!, "build", "--progress=plain", "--no-cache", "-t", image, "."],
stderr: "pipe",
stdout: "pipe",
});
if ((await exited) !== 0) {
throw new Error(`Failed to build custom Docker image: '${image}'`, {
cause: Bun.readableStreamToText(stderr),
});
}
return {
stdout: await Bun.readableStreamToText(stdout),
stderr: await Bun.readableStreamToText(stderr),
};
}