aboutsummaryrefslogtreecommitdiff
path: root/packages/bun-lambda/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/bun-lambda/scripts')
-rw-r--r--packages/bun-lambda/scripts/build-layer.ts101
-rw-r--r--packages/bun-lambda/scripts/publish-layer.ts91
2 files changed, 192 insertions, 0 deletions
diff --git a/packages/bun-lambda/scripts/build-layer.ts b/packages/bun-lambda/scripts/build-layer.ts
new file mode 100644
index 000000000..65eeac083
--- /dev/null
+++ b/packages/bun-lambda/scripts/build-layer.ts
@@ -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));
diff --git a/packages/bun-lambda/scripts/publish-layer.ts b/packages/bun-lambda/scripts/publish-layer.ts
new file mode 100644
index 000000000..b7129fc50
--- /dev/null
+++ b/packages/bun-lambda/scripts/publish-layer.ts
@@ -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));