Skip to content
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

feat: Hosting / AWS Lambda / Serverless Framework #338

Closed
wants to merge 29 commits into from
Closed
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b397048
feat: Hosting / AWS Lambda / Serverless Framework
aheissenberger Aug 19, 2024
3680782
fix: optimize text
aheissenberger Aug 19, 2024
04c9f7b
fix: cross import files
aheissenberger Aug 20, 2024
a708791
fix: typo
aheissenberger Aug 20, 2024
e554cb3
docs: add static asset handling in server context
aheissenberger Aug 20, 2024
2d3081a
Merge branch 'main' into aws-lambda
aheissenberger Aug 20, 2024
8488856
fix
aheissenberger Aug 20, 2024
d87fb17
feat: add httip server support to AWS Lambda Serverless
aheissenberger Aug 20, 2024
302102c
fix: liniting
aheissenberger Aug 20, 2024
1139b88
fix: allow to better handle multiple servers
aheissenberger Aug 20, 2024
0309c5e
fix: add rules
aheissenberger Aug 20, 2024
775b4b0
fix: rules & errors
aheissenberger Aug 20, 2024
ea001d2
Merge branch 'main' into aws-lambda
aheissenberger Aug 20, 2024
b5400d4
fix
aheissenberger Aug 20, 2024
dc763df
feat: add support to create templates for .cjs, .mjs
aheissenberger Aug 21, 2024
0ad59aa
feat: add sentry
aheissenberger Aug 21, 2024
e86aac3
fix: add tests
aheissenberger Aug 21, 2024
e507214
fix: lock
aheissenberger Aug 21, 2024
8799f07
Merge branch 'main' into aws-lambda
aheissenberger Aug 21, 2024
6b6a767
fix
aheissenberger Aug 21, 2024
40db7c9
docs: Sentry sourcemap upload
aheissenberger Aug 21, 2024
63c647c
fix: types
aheissenberger Aug 22, 2024
d20ec0f
fix: add sentry react browser
aheissenberger Aug 22, 2024
cb1785e
Merge branch 'main' into aws-lambda
aheissenberger Aug 22, 2024
0514cef
fix merge
aheissenberger Aug 22, 2024
c7f4ab7
fix: optimize sentry for browser
aheissenberger Aug 23, 2024
ee62afb
Merge branch 'main' into aws-lambda
aheissenberger Aug 23, 2024
f434a55
optimize sentry
aheissenberger Aug 23, 2024
499d7db
fix broken tests
aheissenberger Aug 23, 2024
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
16 changes: 16 additions & 0 deletions boilerplates/aws-lambda-hattip/files/$package.json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { addDependency, loadAsJson, type TransformerProps } from "@batijs/core";

export default async function getPackageJson(props: TransformerProps) {
const packageJson = await loadAsJson(props);

return addDependency(packageJson, await import("../package.json").then((x) => x.default), {
devDependencies: [
"@hattip/adapter-aws-lambda",
"@hattip/static",
"@hattip/walk",
"@types/aws-lambda",
"aws-lambda",
],
dependencies: [],
});
}
41 changes: 41 additions & 0 deletions boilerplates/aws-lambda-hattip/files/entry_aws_lambda.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
entry_aws_lambda.ts

This file is the entry point for AWS Lambda

Notes:
* The file name must not have any special characters or dots except for the extension. https://docs.aws.amazon.com/lambda/latest/api/API_CreateFunction.html#API_CreateFunction_RequestSyntax

*/

import * as Sentry from "@sentry/aws-serverless";
//# BATI.has("sentry")
import "@batijs/aws-lambda-serverless/sentry-server.config";

import { existsSync } from "node:fs";
import awsLambdaAdapter from "@hattip/adapter-aws-lambda";
import { walk } from "@hattip/walk";
import type { FileInfo } from "@hattip/walk";
import { createStaticMiddleware } from "@hattip/static";
import { createFileReader } from "@hattip/static/fs";
import hattipHandler from "@batijs/hattip/hattip-entry"; // file is provided by hattip
import type { Handler, APIGatewayProxyResultV2, APIGatewayProxyEventV2 } from "aws-lambda";

const root = new URL("./dist/client", import.meta.url);
const staticRootExists = existsSync(root);
const files = staticRootExists ? walk(root) : new Map<string, FileInfo>();
const staticMiddleware = staticRootExists
? createStaticMiddleware(files, createFileReader(root), {
urlRoot: "/",
})
: undefined;

const awsHandler = awsLambdaAdapter((ctx) => {
if (hattipHandler === undefined) throw new Error("hattipHandler is undefined");
if (staticMiddleware === undefined) return hattipHandler(ctx);
return staticMiddleware(ctx) || hattipHandler(ctx);
});

export const handler: Handler<APIGatewayProxyEventV2, APIGatewayProxyResultV2> = BATI.has("sentry")
? Sentry.wrapHandler(awsHandler, { captureAllSettledReasons: true })
: awsHandler;
55 changes: 55 additions & 0 deletions boilerplates/aws-lambda-hattip/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"name": "@batijs/aws-lambda-hattip",
"private": true,
"version": "0.0.1",
"description": "",
"type": "module",
"scripts": {
"check-types": "tsc --noEmit",
"build": "bati-compile-boilerplate"
},
"keywords": [],
"author": "",
"license": "MIT",
"devDependencies": {
"@batijs/aws-lambda-serverless": "workspace:*",
"@batijs/compile": "workspace:*",
"@batijs/hattip": "workspace:*",
"@hattip/adapter-aws-lambda": "^0.0.47",
"@hattip/static": "^0.0.47",
"@hattip/walk": "^0.0.47",
"@sentry/aws-serverless": "^8.26.0",
"@sentry/node": "^8.26.0",
"@types/aws-lambda": "^8.10.143",
"@types/node": "^18.19.14",
"aws-lambda": "^1.0.7"
},
"dependencies": {
"@batijs/core": "workspace:*"
},
"files": [
"dist/"
],
"bati": {
"if": {
"flag": {
"$all": [
"hattip",
"aws-lambda-serverless"
]
}
}
},
"exports": {
"./entry_aws_lambda": {
"types": "./dist/types/entry_aws_lambda.d.ts"
}
},
"typesVersions": {
"*": {
"entry_aws_lambda": [
"./dist/types/entry_aws_lambda.d.ts"
]
}
}
}
5 changes: 5 additions & 0 deletions boilerplates/aws-lambda-hattip/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"extends": [
"../tsconfig.base.json"
]
}
14 changes: 14 additions & 0 deletions boilerplates/aws-lambda-hono/files/$package.json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { addDependency, loadAsJson, type TransformerProps } from "@batijs/core";

export default async function getPackageJson(props: TransformerProps) {
const packageJson = await loadAsJson(props);

return addDependency(packageJson, await import("../package.json").then((x) => x.default), {
devDependencies: [
"aws-lambda",
"@types/aws-lambda",
...(props.meta.BATI.has("sentry") ? (["@sentry/aws-serverless"] as const) : []),
],
dependencies: [],
});
}
42 changes: 42 additions & 0 deletions boilerplates/aws-lambda-hono/files/entry_aws_lambda.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
entry_aws_lambda.ts

This file is the entry point for AWS Lambda

Notes:
* The file name must not have any special characters or dots except for the extension. https://docs.aws.amazon.com/lambda/latest/api/API_CreateFunction.html#API_CreateFunction_RequestSyntax

*/

import * as Sentry from "@sentry/aws-serverless";

//# BATI.has("sentry")
import "@batijs/aws-lambda-serverless/sentry-server.config";

import { Hono } from "hono";
import { serveStatic } from "@hono/node-server/serve-static";
import { handle } from "hono/aws-lambda";
import type { LambdaEvent, LambdaContext } from "hono/aws-lambda";
import app from "@batijs/hono/hono-entry"; // file is provided by hono
import type { Handler, APIGatewayProxyResult } from "aws-lambda";

type Bindings = {
event: LambdaEvent;
lambdaContext: LambdaContext;
};

const lambdaApp = new Hono<{ Bindings: Bindings }>();

lambdaApp.use(
"/*",
serveStatic({
root: `./dist/client/`,
}),
);

lambdaApp.route("/", app!);
const awsHandler = handle(lambdaApp);

export const handler: Handler<LambdaEvent, APIGatewayProxyResult> = BATI.has("sentry")
? Sentry.wrapHandler(awsHandler, { captureAllSettledReasons: true })
: awsHandler;
54 changes: 54 additions & 0 deletions boilerplates/aws-lambda-hono/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"name": "@batijs/aws-lambda-hono",
"private": true,
"version": "0.0.1",
"description": "",
"type": "module",
"scripts": {
"check-types": "tsc --noEmit",
"build": "bati-compile-boilerplate"
},
"keywords": [],
"author": "",
"license": "MIT",
"devDependencies": {
"@batijs/aws-lambda-serverless": "workspace:*",
"@batijs/compile": "workspace:*",
"@batijs/hono": "workspace:*",
"@hono/node-server": "^1.12.1",
"@hono/sentry": "^1.2.0",
"@sentry/aws-serverless": "^8.26.0",
"@types/aws-lambda": "^8.10.143",
"@types/node": "^18.19.14",
"aws-lambda": "^1.0.7",
"hono": "^4.5.6"
},
"dependencies": {
"@batijs/core": "workspace:*"
},
"files": [
"dist/"
],
"bati": {
"if": {
"flag": {
"$all": [
"hono",
"aws-lambda-serverless"
]
}
}
},
"exports": {
"./entry_aws_lambda": {
"types": "./dist/types/entry_aws_lambda.d.ts"
}
},
"typesVersions": {
"*": {
"entry_aws_lambda": [
"./dist/types/entry_aws_lambda.d.ts"
]
}
}
}
5 changes: 5 additions & 0 deletions boilerplates/aws-lambda-hono/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"extends": [
"../tsconfig.base.json"
]
}
34 changes: 34 additions & 0 deletions boilerplates/aws-lambda-serverless/files/$.env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { appendToEnv, type TransformerProps } from "@batijs/core";

export default async function getServerlessEnv(props: TransformerProps) {
if (!props.meta.BATI.has("sentry")) return undefined;
const sentryOrg = process.env.TEST_SENTRY_ORG;
const sentryProject = process.env.TEST_SENTRY_PROJECT;
const sentryAuthToken = process.env.TEST_SENTRY_AUTH_TOKEN;
const sentryDNS = process.env.TEST_SENTRY_DSN;

let envContent = await props.readfile?.();

envContent = appendToEnv(envContent, "SENTRY_ORG", sentryOrg ?? "", "Sentry Org. Used for Upload of Source Maps");
envContent = appendToEnv(
envContent,
"SENTRY_PROJECT",
sentryProject ?? "",
"Sentry Project Slug. Used for Upload of Source Maps",
);
envContent = appendToEnv(
envContent,
"SENTRY_AUTH_TOKEN",
sentryAuthToken ?? "",
"Sentry Auth Token. Used for Upload of Source Maps",
);
envContent = appendToEnv(envContent, "SENTRY_DSN", sentryDNS ?? "", "Sentry DNS. Used for Error Reporting");
envContent = appendToEnv(
envContent,
"SENTRY_PROFILER_BINARY_PATH",
process.env.SENTRY_PROFILER_BINARY_PATH ?? "",
"SENTRY_PROFILER_BINARY_PATH. Needed for location of profiler binary",
);

return envContent;
}
aheissenberger marked this conversation as resolved.
Show resolved Hide resolved
89 changes: 89 additions & 0 deletions boilerplates/aws-lambda-serverless/files/$README.md.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { loadReadme, type TransformerProps } from "@batijs/core";

export default async function getReadme(props: TransformerProps) {
const content = await loadReadme(props);

//language=Markdown
const docs =
`
## AWS Lambda Serverless Framework

The deployment requires a default AWS profile to be [set up](https://www.serverless.com/framework/docs/providers/aws/guide/credentials) on your local machine.
To stay within the limit of an AWS Lambda deployment (max. 50MB), all external dependencies are bundled with esbuild and static assets only used by the client \`dist/client/assets\` are uploaded to S3.

### Setup

1. Change the project name (custom.project) in the \`serverless.yml\` file. **This is required to create a worldwide unique S3 bucket!**.
2. Stage can be changed in the \`serverless.yml\` file or by adding the option \`--stage prod\`.
3. The region can be changed in the \`serverless.yml\` .

### Deploy

\`pnpm deploy:aws\`

The url of the CloudFront distribution is printed with \`pnpm sls info --verbose\` - see \`CloudFrontDistributionUrl: \`.


### Remove

\`pnpm sls remove\`

### Logs

\`pnpm sls logs -f vikeapp\`

### Resources

The following resources are created:
* S3 bucket for the static files (only \`dist/client/assets\` is uploaded!)
* CloudFront distribution for the static files and the Lambda function
* Lambda function with the Node.js runtime and the entry file \`hono-entry_aws_lambda.ts\`
* Default Stage: dev

### Static Assets in Lambda

The static assets are served by the Lambda function. The path is defined in the \`hono-entry_aws_lambda.ts\` file.

If you need to access any static assets in your server code which are not public, you can use one of the following methods:
1) Use \`import data from './data.json'\` in the Lambda function. This will embed the data in the Lambda function.
2) Use \`import data from './data.docx' assert { type: 'docx' }\` in the Lambda function and add the [copy loader](https://esbuild.github.io/content-types/#copy) to \`esbuild.config.cjs\`.
3) Uncomment the \`copyPlugin\` in \`esbuild-plugins.cjs\`, adapt the configuration to the location of your data folder.

**Example Code:**
\`\`\`typescript
// code snipplet from server/create-todo-handler.ts and server/data/users.bin
const pp = path.join(path.dirname(fileURLToPath(import.meta.url)), "data/users.bin");
console.log(pp);
const usersData = fs.readFileSync(pp, "utf-8");
const users = JSON.parse(usersData);
\`\`\`

**Example Configuration:**
\`\`\`js
const copyPlugin = copy({
assets: [{
from: ['./server/data/*'],
to: ['data']
}]
})
\`\`\`

` +
(props.meta.BATI.has("sentry")
? `
### Sentry

[Sentry is integrated](https://docs.sentry.io/platforms/javascript/guides/aws-lambda/install/esm-npm/) in the Lambda function.
The build process will automatically upload the source maps to Sentry.

### Configuration

The environment variable \`SENTRY_DSN\` and some other sentry variables must be set in\`.env\`.
Any further configuration can be done in the \`sentry-server.config.mjs\` file.
`
: "");

content.addTodo(docs);

return content.finalize();
}
29 changes: 29 additions & 0 deletions boilerplates/aws-lambda-serverless/files/$package.json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { addDependency, loadAsJson, setScripts, type TransformerProps } from "@batijs/core";

export default async function getPackageJson(props: TransformerProps) {
const packageJson = await loadAsJson(props);

setScripts(packageJson, {
"deploy:aws": {
value: "cross-env NODE_ENV=production sls deploy && pnpm sls info --verbose | grep CloudFrontDistributionUrl",
precedence: 20,
warnIfReplaced: true,
},
});

return addDependency(packageJson, await import("../package.json").then((x) => x.default), {
devDependencies: [
"serverless",
"serverless-cloudfront-invalidate",
"serverless-esbuild",
"serverless-s3-sync",
"serverless-scriptable-plugin",
"esbuild-plugin-copy",
//@ts-ignore
...(props.meta.BATI.has("sentry")
? ["@sentry/aws-serverless", "@sentry/profiling-node", "@sentry/esbuild-plugin"]
: []),
magne4000 marked this conversation as resolved.
Show resolved Hide resolved
],
dependencies: [],
});
}
Loading
Loading