diff options
Diffstat (limited to 'packages/bun-lambda/scripts')
-rw-r--r-- | packages/bun-lambda/scripts/build-layer.ts | 101 | ||||
-rw-r--r-- | packages/bun-lambda/scripts/publish-layer.ts | 91 |
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)); |