Skip to content

Bun Support #734

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

To maintain a high standard of quality in our releases, before merging every pull request we ask that you've completed the following:

- [ ] Forked the repo and created your branch from `main`
- [X] Forked the repo and created your branch from `main`
- [ ] Made sure tests pass (run `npm it` from the repo root)
- [ ] Expanded test coverage related to your changes:
- [ ] Added and/or updated unit tests (if appropriate)
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/binary-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ jobs:
with:
deno-version: v1.x

- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: v1.1.x

- name: Env
run: |
echo "Event name: ${{ github.event_name }}"
Expand All @@ -55,6 +60,7 @@ jobs:
VER=`python --version`; echo "Python ver: $VER"
VER=`ruby --version`; echo "Ruby ver: $VER"
VER=`deno --version`; echo "Deno ver: $VER"
VER=`bun --version`; echo "Bun ver: $VER"
echo "OS ver: ${{ runner.os }}"

- name: Install
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ jobs:
with:
deno-version: v1.x

- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: v1.1.x

- name: Env
run: |
echo "Event name: ${{ github.event_name }}"
Expand All @@ -55,6 +60,7 @@ jobs:
VER=`python --version`; echo "Python ver: $VER"
VER=`ruby --version`; echo "Ruby ver: $VER"
VER=`deno --version`; echo "Deno ver: $VER"
VER=`bun --version`; echo "Bun ver: $VER"
echo "OS ver: ${{ runner.os }}"

- name: Install
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ test/mock/tmp

# special case in which we're testing local shared file dependency paths
test/mock/dep-warn/**/node_modules/@architect/

#WebStorm
.idea
7 changes: 7 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Architect Sandbox changelog

---
## [6.0.5] 2024-06-29

### Changed

- Added bun support

---

## [6.0.5] 2024-04-29
Expand Down
2 changes: 1 addition & 1 deletion src/invoke-lambda/env/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module.exports = function createLambdaContext (params) {
let functionVersion = '$LATEST'
let invokedFunctionArn = 'sandbox'

if (runtime.startsWith('node') || runtime.startsWith('deno')) {
if (runtime.startsWith('node') || runtime.startsWith('deno') || runtime.startsWith('bun')) {
let lambdaContext = {
awsRequestId: makeRequestId(),
functionName,
Expand Down
1 change: 1 addition & 0 deletions src/invoke-lambda/exec/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ function getRuntime ({ config, handlerModuleSystem }) {
if (handlerModuleSystem === 'esm') return 'node-esm'
return 'node'
}
else if (run.startsWith('bun')) return 'bun'
else if (run.startsWith('deno')) return 'deno'
else if (run.startsWith('ruby')) return 'ruby'
else if (run.startsWith('python')) return 'python'
Expand Down
2 changes: 1 addition & 1 deletion src/invoke-lambda/exec/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ let handlers
module.exports = function () {
if (handlers) return handlers
handlers = {}
let runtimes = [ 'deno.mjs', 'node.js', 'node-esm.mjs', 'python.py', 'ruby.rb' ]
let runtimes = [ 'bun.mjs', 'deno.mjs', 'node.js', 'node-esm.mjs', 'python.py', 'ruby.rb' ]
runtimes.forEach(runtime => {
try {
let name = runtime.split('.')[0]
Expand Down
68 changes: 68 additions & 0 deletions src/invoke-lambda/exec/runtimes/bun.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/* eslint @stylistic/js/semi: [ 'error', 'always' ] */
/* global Bun */
const { __ARC_CONFIG__, __ARC_CONTEXT__, AWS_LAMBDA_RUNTIME_API: runtimeAPI } = Bun.env.toObject();
const url = p => runtimeAPI + '/2018-06-01/runtime/' + p;
const headers = { 'content-type': 'application/json; charset=utf-8' };

(async function main () {
try {
const { handlerFile, handlerMethod } = JSON.parse(__ARC_CONFIG__);
const context = JSON.parse(__ARC_CONTEXT__);
Bun.env.delete('__ARC_CONFIG__');
Bun.env.delete('__ARC_CONTEXT__');

const isPromise = obj => obj && typeof obj.then === 'function';

async function run () {
const next = url('invocation/next');
const response = await fetch(next);
const event = await response.json();

const requestID = response.headers.get('Lambda-Runtime-Aws-Request-Id') || response.headers.get('lambda-runtime-aws-request-id');
const errorEndpoint = url('invocation/' + requestID + '/error');
const responseEndpoint = url('invocation/' + requestID + '/response');

const file = 'file://' + handlerFile;
const mod = await import(file);
const handler = mod[handlerMethod];

async function callback (err, result) {
if (err) {
console.log(err);
const errorMessage = err.message || '(unknown error)';
const errorType = err.name || '(unknown error type)';
const stackTrace = err.stack ? err.stack.split('\n') : undefined;
const body = JSON.stringify({ errorMessage, errorType, stackTrace });
await fetch(errorEndpoint, { method: 'POST', headers, body });
}
else {
const options = { method: 'POST', headers };
if (result) options.body = JSON.stringify(result);
await fetch(responseEndpoint, options);
}
}
try {
const response = handler(event, context, callback);
if (isPromise(response)) {
response.then(result => callback(null, result)).catch(callback);
}
}
catch (err) {
callback(err);
}
}
await run();
}
catch (err) {
(async function initError () {
const unknown = 'Unknown init error';
console.log('Lambda init error:', err || unknown);
const initErrorEndpoint = url('init/error');
const errorMessage = err.message || unknown;
const errorType = err.name || 'Unknown init error type';
const stackTrace = err.stack ? err.stack.split('\n') : undefined;
const body = JSON.stringify({ errorMessage, errorType, stackTrace });
await fetch(initErrorEndpoint, { method: 'POST', headers, body });
})();
}
})();
4 changes: 4 additions & 0 deletions src/lib/runtime-eval.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ let pyCommandCache
* Provides platform-specific runtime-specific commands for child_process spawns
*/
module.exports = {
bun: (script) => ({
command: 'bun',
args: [ 'run', '-e', script ],
}),
deno: function (script) {
return {
command: 'deno',
Expand Down
4 changes: 3 additions & 1 deletion src/sandbox/check-runtimes/version-check.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ module.exports = function runtimeVersionCheck (params) {
function check (configured, localRuntimes) {
let alias = aliases[configured.toLowerCase()]
let runtime = alias ? runtimes[alias][0] : configured
let major = ver => ver.split('.')[0]
let major = ver => {
return ver.split('.')[0]
}
let minor = ver => ver.split('.')[1]
if (runtime.startsWith('nodejs')) {
let runtimeVer = runtimeVersions[runtime].wildcard
Expand Down
9 changes: 7 additions & 2 deletions test/mock/multi-handler/app.arc
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@
multi-handler

@http
# Bun
get /bun/index.js
get /bun/index.ts
get /bun/index.tsx

# Deno
get /deno/index.js
get /deno/mod.js
get /deno/index.ts

get /deno/mod.ts
get /deno/index.tsx
get /deno/mod.tsx
Expand All @@ -17,4 +22,4 @@ get /node/esm/index.js
get /node/esm/index.mjs

@arc
runtime nodejs20.x # The Deno functions have config files
runtime nodejs20.x # The Deno and Bun functions have config files
2 changes: 2 additions & 0 deletions test/mock/multi-handler/src/http/get-bun-index_js/config.arc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@aws
runtime bun
10 changes: 10 additions & 0 deletions test/mock/multi-handler/src/http/get-bun-index_js/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export async function handler (req) {
let body = req
body.message = 'Hello from get /bun/index.js'
const response = {
statusCode: 200,
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body)
}
return response
}
2 changes: 2 additions & 0 deletions test/mock/multi-handler/src/http/get-bun-index_ts/config.arc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@aws
runtime bun
10 changes: 10 additions & 0 deletions test/mock/multi-handler/src/http/get-bun-index_ts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export async function handler (req: any) {
let body = req
body.message = 'Hello from get /bun/index.ts'
const response = {
statusCode: 200,
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body)
};
return response;
}
2 changes: 2 additions & 0 deletions test/mock/multi-handler/src/http/get-bun-index_tsx/config.arc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@aws
runtime bun
10 changes: 10 additions & 0 deletions test/mock/multi-handler/src/http/get-bun-index_tsx/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export async function handler (req: any) {
let body = req
body.message = 'Hello from get /bun/index.tsx'
const response = {
statusCode: 200,
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body)
};
return response;
}
1 change: 1 addition & 0 deletions test/mock/normal/app.arc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ get /python3.11
get /python3.8
get /ruby3.2
get /deno
get /bun
# Path
get /get-p-c/:param/*
get /get-c/*
Expand Down
5 changes: 5 additions & 0 deletions test/mock/normal/src/http/get-bun/config.arc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@aws
runtime bun
timeout 10
# concurrency 1
# memory 1152
10 changes: 10 additions & 0 deletions test/mock/normal/src/http/get-bun/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export async function handler (req: any) {
let body = req
body.message = 'Hello from get /bun (running bun)'
const response = {
statusCode: 200,
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body)
};
return response;
}
16 changes: 14 additions & 2 deletions test/unit/src/invoke-lambda/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ let { credentials: creds } = require(join(cwd, 'test', 'utils'))

let runtimes = {
asap: 0,
bun: 0,
deno: 0,
node: 0,
python: 0,
Expand All @@ -21,6 +22,7 @@ let exec = (lambda, params, callback) => {
let { runtime } = lambda.config
let run
if (runtime.startsWith('node')) run = 'node'
if (runtime.startsWith('bun')) run = 'bun'
if (runtime.startsWith('deno')) run = 'deno'
if (runtime.startsWith('python')) run = 'python'
if (runtime.startsWith('ruby')) run = 'ruby'
Expand Down Expand Up @@ -60,7 +62,7 @@ test('Get inventory', t => {
})

test('Test runtime invocations', t => {
t.plan(21)
t.plan(24)
let lambda

lambda = get.http('get /')
Expand Down Expand Up @@ -125,6 +127,15 @@ test('Test runtime invocations', t => {
t.equals(timeout, 10000, 'deno ran with correct timeout')
t.deepEqual(result, event, 'deno received event')
})

lambda = get.http('get /bun')
invoke({ lambda, ...params }, (err, result) => {
if (err) t.end(err)
let { options, timeout } = execPassedParams
t.equals(options.cwd, lambda.src, 'bun passed correct path')
t.equals(timeout, 10000, 'bun ran with correct timeout')
t.deepEqual(result, event, 'bun received event')
})
})

// This test will still hit the node call counter at least once
Expand Down Expand Up @@ -184,9 +195,10 @@ test('Test ASAP invocation', t => {
})

test('Verify call counts from runtime invocations', t => {
t.plan(5)
t.plan(6)
t.equals(runtimes.asap, 1, 'ASAP called correct number of times')
t.equals(runtimes.deno, 1, 'Deno called correct number of times')
t.equals(runtimes.deno, 1, 'Bun called correct number of times')
t.equals(runtimes.node, 5, 'Node called correct number of times')
t.equals(runtimes.python, 2, 'Python called correct number of times')
t.equals(runtimes.ruby, 1, 'Ruby called correct number of times')
Expand Down