mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
Add runtime layer for Bun on AWS Lambda (#2009)
This commit is contained in:
101
packages/bun-lambda/scripts/build-layer.ts
Normal file
101
packages/bun-lambda/scripts/build-layer.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
// HACK: https://github.com/oven-sh/bun/issues/2081
|
||||
process.stdout.getWindowSize = () => [80, 80];
|
||||
process.stderr.getWindowSize = () => [80, 80];
|
||||
|
||||
import { createReadStream, createWriteStream } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { Command, Flags } from "@oclif/core";
|
||||
import JSZip from "jszip";
|
||||
|
||||
export class BuildCommand extends Command {
|
||||
static summary = "Build a custom Lambda layer for Bun.";
|
||||
|
||||
static flags = {
|
||||
arch: Flags.string({
|
||||
description: "The architecture type to support.",
|
||||
options: ["x64", "aarch64"],
|
||||
default: "aarch64",
|
||||
}),
|
||||
release: Flags.string({
|
||||
description: "The release of Bun to install.",
|
||||
default: "latest",
|
||||
}),
|
||||
url: Flags.string({
|
||||
description: "A custom URL to download Bun.",
|
||||
exclusive: ["release"],
|
||||
}),
|
||||
output: Flags.file({
|
||||
exists: false,
|
||||
default: async () => "bun-lambda-layer.zip",
|
||||
}),
|
||||
layer: Flags.string({
|
||||
description: "The name of the Lambda layer.",
|
||||
multiple: true,
|
||||
default: ["bun"],
|
||||
}),
|
||||
region: Flags.string({
|
||||
description: "The region to publish the layer.",
|
||||
multiple: true,
|
||||
default: [],
|
||||
}),
|
||||
public: Flags.boolean({
|
||||
description: "If the layer should be public.",
|
||||
default: false,
|
||||
}),
|
||||
};
|
||||
|
||||
async run() {
|
||||
const result = await this.parse(BuildCommand);
|
||||
const { flags } = result;
|
||||
this.debug("Options:", flags);
|
||||
const { arch, release, url, output } = flags;
|
||||
const { href } = new URL(url ?? `https://bun.sh/download/${release}/linux/${arch}?avx2=true`);
|
||||
this.log("Downloading...", href);
|
||||
const response = await fetch(href, {
|
||||
headers: {
|
||||
"User-Agent": "bun-lambda",
|
||||
},
|
||||
});
|
||||
if (response.url !== href) {
|
||||
this.debug("Redirected URL:", response.url);
|
||||
}
|
||||
this.debug("Response:", response.status, response.statusText);
|
||||
if (!response.ok) {
|
||||
const reason = await response.text();
|
||||
this.error(reason, { exit: 1 });
|
||||
}
|
||||
this.log("Extracting...");
|
||||
const buffer = await response.arrayBuffer();
|
||||
let archive;
|
||||
try {
|
||||
archive = await JSZip.loadAsync(buffer);
|
||||
} catch (cause) {
|
||||
this.debug(cause);
|
||||
this.error("Failed to unzip file:", { exit: 1 });
|
||||
}
|
||||
this.debug("Extracted archive:", Object.keys(archive.files));
|
||||
const bun = archive.filter((_, { dir, name }) => !dir && name.endsWith("bun"))[0];
|
||||
if (!bun) {
|
||||
this.error("Failed to find executable in zip", { exit: 1 });
|
||||
}
|
||||
const cwd = bun.name.split("/")[0];
|
||||
archive = archive.folder(cwd) ?? archive;
|
||||
for (const filename of ["bootstrap", "runtime.ts"]) {
|
||||
const path = join(__dirname, "..", filename);
|
||||
archive.file(filename, createReadStream(path));
|
||||
}
|
||||
this.log("Saving...", output);
|
||||
archive
|
||||
.generateNodeStream({
|
||||
streamFiles: true,
|
||||
compression: "DEFLATE",
|
||||
compressionOptions: {
|
||||
level: 9,
|
||||
},
|
||||
})
|
||||
.pipe(createWriteStream(output));
|
||||
this.log("Saved");
|
||||
}
|
||||
}
|
||||
|
||||
await BuildCommand.run(process.argv.slice(2));
|
||||
91
packages/bun-lambda/scripts/publish-layer.ts
Normal file
91
packages/bun-lambda/scripts/publish-layer.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { BuildCommand } from "./build-layer";
|
||||
|
||||
export class PublishCommand extends BuildCommand {
|
||||
static summary = "Publish a custom Lambda layer for Bun.";
|
||||
|
||||
#aws(args: string[]): string {
|
||||
this.debug("$", "aws", ...args);
|
||||
const { status, stdout, stderr } = spawnSync("aws", args, {
|
||||
stdio: "pipe",
|
||||
});
|
||||
const result = stdout.toString("utf-8").trim();
|
||||
if (status === 0) {
|
||||
return result;
|
||||
}
|
||||
const reason = stderr.toString("utf-8").trim() || result;
|
||||
throw new Error(`aws ${args.join(" ")} exited with ${status}: ${reason}`);
|
||||
}
|
||||
|
||||
async run() {
|
||||
const { flags } = await this.parse(PublishCommand);
|
||||
this.debug("Options:", flags);
|
||||
try {
|
||||
const version = this.#aws(["--version"]);
|
||||
this.debug("AWS CLI:", version);
|
||||
} catch (error) {
|
||||
this.debug(error);
|
||||
this.error(
|
||||
"Install the `aws` CLI to continue: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html",
|
||||
{ exit: 1 },
|
||||
);
|
||||
}
|
||||
const { layer, region, arch, output, public: isPublic } = flags;
|
||||
if (region.includes("*")) {
|
||||
// prettier-ignore
|
||||
const result = this.#aws([
|
||||
"ec2",
|
||||
"describe-regions",
|
||||
"--query", "Regions[].RegionName",
|
||||
"--output", "json"
|
||||
]);
|
||||
region.length = 0;
|
||||
for (const name of JSON.parse(result)) {
|
||||
region.push(name);
|
||||
}
|
||||
} else if (!region.length) {
|
||||
// prettier-ignore
|
||||
region.push(this.#aws([
|
||||
"configure",
|
||||
"get",
|
||||
"region"
|
||||
]));
|
||||
}
|
||||
this.log("Publishing...");
|
||||
for (const regionName of region) {
|
||||
for (const layerName of layer) {
|
||||
// prettier-ignore
|
||||
const result = this.#aws([
|
||||
"lambda",
|
||||
"publish-layer-version",
|
||||
"--layer-name", layerName,
|
||||
"--region", regionName,
|
||||
"--description", "Bun is an incredibly fast JavaScript runtime, bundler, transpiler, and package manager.",
|
||||
"--license-info", "MIT",
|
||||
"--compatible-architectures", arch === "x64" ? "x86_64" : "arm64",
|
||||
"--compatible-runtimes", "provided.al2", "provided",
|
||||
"--zip-file", `fileb://${output}`,
|
||||
"--output", "json",
|
||||
]);
|
||||
const { LayerVersionArn } = JSON.parse(result);
|
||||
this.log("Published", LayerVersionArn);
|
||||
if (isPublic) {
|
||||
// prettier-ignore
|
||||
this.#aws([
|
||||
"lambda",
|
||||
"add-layer-version-permission",
|
||||
"--layer-name", layerName,
|
||||
"--region", regionName,
|
||||
"--version-number", LayerVersionArn.split(":").pop(),
|
||||
"--statement-id", `${layerName}-public`,
|
||||
"--action", "lambda:GetLayerVersion",
|
||||
"--principal", "*",
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.log("Done");
|
||||
}
|
||||
}
|
||||
|
||||
await PublishCommand.run(process.argv.slice(2));
|
||||
Reference in New Issue
Block a user