Skip to content

Commit 83a20a6

Browse files
committed
publishing to npm
0 parents  commit 83a20a6

File tree

14 files changed

+1929
-0
lines changed

14 files changed

+1929
-0
lines changed

.github/workflows/push-main.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Push to Main
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches: [main]
7+
8+
jobs:
9+
prerelease:
10+
runs-on: ubuntu-latest
11+
strategy:
12+
matrix:
13+
node: ["18", "20"]
14+
steps:
15+
- uses: actions/checkout@v4
16+
- uses: actions/setup-node@v4
17+
with:
18+
node-version: ${{ matrix.node }}
19+
cache: "yarn"
20+
- run: yarn
21+
- run: yarn build
22+
- if: ${{ matrix.node == '18' }}
23+
uses: scaffoldly/bump-version-action@v1
24+
with:
25+
action: prerelease
26+
version-file: package.json
27+
repo-token: ${{ secrets.GITHUB_TOKEN }}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Release Published
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
jobs:
8+
publish:
9+
runs-on: ubuntu-latest
10+
strategy:
11+
matrix:
12+
node: ["18"]
13+
steps:
14+
- uses: actions/checkout@v4
15+
- uses: actions/setup-node@v4
16+
with:
17+
node-version: ${{ matrix.node }}
18+
registry-url: "https://registry.npmjs.org"
19+
cache: "yarn"
20+
- run: yarn
21+
- run: yarn build
22+
- if: ${{ matrix.node == '18' }}
23+
uses: scaffoldly/bump-version-action@v1
24+
with:
25+
action: postrelease
26+
version-file: package.json
27+
repo-token: ${{ secrets.GITHUB_TOKEN }}
28+
- if: ${{ matrix.node == '18' }}
29+
run: yarn publish --access public
30+
env:
31+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
tsconfig.tsbuildinfo

cli/index.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import packageJson from "../package.json";
2+
import { bootstrap } from "../src/bootstrap";
3+
4+
(async () => {
5+
if (process.argv.includes("--version")) {
6+
console.log(packageJson.version);
7+
return;
8+
}
9+
10+
try {
11+
await bootstrap();
12+
} catch (e) {
13+
if (e instanceof Error) {
14+
console.error(e.message);
15+
} else {
16+
console.error(e);
17+
}
18+
process.exit(1);
19+
}
20+
})();

dist/main.js

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/main.js.LICENSE.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*!
2+
* mime-db
3+
* Copyright(c) 2014 Jonathan Ong
4+
* Copyright(c) 2015-2022 Douglas Christopher Wilson
5+
* MIT Licensed
6+
*/
7+
8+
/*!
9+
* mime-types
10+
* Copyright(c) 2014 Jonathan Ong
11+
* Copyright(c) 2015 Douglas Christopher Wilson
12+
* MIT Licensed
13+
*/

package.json

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"name": "@scaffoldly/awslambda-bootstrap",
3+
"version": "0.1.0",
4+
"description": "Bootstrap Script for AWS Lambda",
5+
"repository": "https://github.com/scaffoldly/awslambda-bootstrap",
6+
"author": "scaffold.ly",
7+
"license": "MIT",
8+
"main": "dist/main.js",
9+
"files": [
10+
"dist/main.js",
11+
"dist/main.js.map"
12+
],
13+
"bin": {
14+
"bootstrap": "dist/main.js"
15+
},
16+
"scripts": {
17+
"build": "webpack",
18+
"start": "node -r ts-node/register -r tsconfig-paths/register cli/index.ts"
19+
},
20+
"engines": {
21+
"node": ">=18"
22+
},
23+
"engineStrict": true,
24+
"keywords": [
25+
"scaffoldly",
26+
"aws",
27+
"lambda"
28+
],
29+
"dependencies": {
30+
"@types/aws-lambda": "^8.10.138",
31+
"@types/node": "18",
32+
"@types/which": "^3.0.3",
33+
"aws-lambda": "^1.0.7",
34+
"axios": "^1.7.2",
35+
"typescript": "5",
36+
"which": "^4.0.0"
37+
},
38+
"devDependencies": {
39+
"source-map": "^0.7.4",
40+
"ts-loader": "^9.5.1",
41+
"ts-node": "^10.9.2",
42+
"tsconfig-paths": "^4.2.0",
43+
"webpack": "^5.91.0",
44+
"webpack-cli": "^5.1.4"
45+
}
46+
}

src/bootstrap.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { ChildProcess, spawn } from "child_process";
2+
import which from "which";
3+
import { routeEvents } from "./routing";
4+
const { _HANDLER, IS_OFFLINE, AWS_LAMBDA_RUNTIME_API } = process.env;
5+
6+
export const bootstrap = async (): Promise<void> => {
7+
if (!AWS_LAMBDA_RUNTIME_API) {
8+
throw new Error("No AWS_LAMBDA_RUNTIME_API specified");
9+
}
10+
11+
if (!_HANDLER) {
12+
throw new Error("No handler specified");
13+
}
14+
15+
const handler = new URL(_HANDLER);
16+
17+
let app: string = handler.protocol.slice(0, -1);
18+
let endpoint = handler.toString();
19+
let childProcess: ChildProcess | undefined = undefined;
20+
21+
if (app !== "http" && app !== "https") {
22+
endpoint = handler.pathname;
23+
try {
24+
await which(app);
25+
} catch (error) {
26+
throw new Error(
27+
`Could not find \`${app}\` in system path ${process.env.PATH}`
28+
);
29+
}
30+
31+
const subcommand = IS_OFFLINE === "true" ? "dev" : "start";
32+
33+
childProcess = spawn(app, [subcommand], {
34+
detached: true,
35+
stdio: "inherit",
36+
});
37+
}
38+
39+
try {
40+
await routeEvents(AWS_LAMBDA_RUNTIME_API, endpoint);
41+
} catch (e) {
42+
if (childProcess) {
43+
childProcess.kill();
44+
}
45+
throw e;
46+
}
47+
};

src/proxy.ts

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import { APIGatewayProxyResultV2 } from "aws-lambda";
2+
import axios, { AxiosResponseHeaders, RawAxiosResponseHeaders } from "axios";
3+
import net from "net";
4+
import { EndpointRequest, EndpointResponse } from "./types";
5+
6+
function convertHeaders(
7+
headers: RawAxiosResponseHeaders | AxiosResponseHeaders
8+
): { [header: string]: boolean | number | string } | undefined {
9+
if (!headers) {
10+
return undefined;
11+
}
12+
13+
return Object.keys(headers).reduce((acc, key) => {
14+
const value = headers[key];
15+
16+
if (!value) return acc;
17+
18+
if (Array.isArray(value)) {
19+
acc[key] = value.join(", ");
20+
} else if (
21+
typeof value === "string" ||
22+
typeof value === "number" ||
23+
typeof value === "boolean"
24+
) {
25+
acc[key] = value;
26+
}
27+
28+
return acc;
29+
}, {} as { [header: string]: boolean | number | string });
30+
}
31+
32+
const waitForEndpoint = async (
33+
endpoint: string,
34+
deadline: number
35+
): Promise<{ deadline: number }> => {
36+
const start = Date.now();
37+
const url = new URL(endpoint);
38+
const hostname = url.hostname;
39+
const port = parseInt(url.port, 10) || (url.protocol === "https:" ? 443 : 80);
40+
41+
return new Promise((resolve) => {
42+
const socket = new net.Socket();
43+
44+
const onError = () => {
45+
socket.destroy();
46+
return waitForEndpoint(endpoint, deadline - (Date.now() - start)).then(
47+
resolve
48+
);
49+
};
50+
51+
socket.setTimeout(deadline);
52+
socket.once("error", onError);
53+
socket.once("timeout", onError);
54+
55+
socket.connect(port, hostname, () => {
56+
socket.end();
57+
resolve({ deadline: deadline - (Date.now() - start) });
58+
});
59+
});
60+
};
61+
62+
export const endpointProxy = async ({
63+
requestId,
64+
endpoint,
65+
event,
66+
initialDeadline,
67+
}: EndpointRequest): Promise<EndpointResponse> => {
68+
const {
69+
requestContext,
70+
rawPath,
71+
rawQueryString,
72+
headers: rawHeaders,
73+
body: rawBody,
74+
isBase64Encoded,
75+
} = event;
76+
const method = requestContext.http.method;
77+
78+
const { deadline } = await waitForEndpoint(endpoint, initialDeadline);
79+
80+
if (deadline <= 0) {
81+
throw new Error(
82+
`${endpoint.toString()} took longer than ${Math.floor(
83+
initialDeadline / 1000
84+
)} second(s) to start.`
85+
);
86+
}
87+
88+
const url = new URL(rawPath, endpoint);
89+
if (rawQueryString) {
90+
url.search = new URLSearchParams(rawQueryString).toString();
91+
}
92+
93+
const decodedBody =
94+
isBase64Encoded && rawBody ? Buffer.from(rawBody, "base64") : rawBody;
95+
96+
console.log(`Invoking ${method} on ${url.toString()}`);
97+
98+
return axios
99+
.request({
100+
method: method.toLowerCase(),
101+
url: url.toString(),
102+
headers: rawHeaders,
103+
data: decodedBody,
104+
timeout: deadline,
105+
})
106+
.then((response) => {
107+
const { data: rawData, headers: rawHeaders } = response;
108+
109+
const body =
110+
rawData && typeof rawData === "string"
111+
? rawData
112+
: Buffer.from(rawData).toString("base64");
113+
114+
const isBase64Encoded = rawData && typeof rawData !== "string";
115+
116+
const payload: APIGatewayProxyResultV2 = {
117+
statusCode: response.status,
118+
headers: convertHeaders(rawHeaders),
119+
body,
120+
isBase64Encoded,
121+
};
122+
123+
return {
124+
requestId,
125+
payload,
126+
};
127+
});
128+
};
129+
130+
// export const endpointProxy = async (endpoint: URL): Promise<void> => {
131+
// return axios
132+
// .get(
133+
// `http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next`,
134+
// { timeout: 0 }
135+
// )
136+
// .then(({ headers, data }) => {
137+
// const requestId = headers["Lambda-Runtime-Aws-Request-Id"] as string;
138+
// const deadline = Number.parseInt(headers["Lambda-Runtime-Deadline-Ms"]);
139+
140+
// console.log("Received request from Lambda Runtime API", { requestId });
141+
142+
// let event = data as APIGatewayProxyEventV2;
143+
// const {
144+
// requestContext,
145+
// rawPath,
146+
// rawQueryString,
147+
// headers: rawHeaders,
148+
// body: rawBody,
149+
// isBase64Encoded,
150+
// } = event;
151+
// const method = requestContext.http.method;
152+
153+
// const url = new URL(rawPath, endpoint);
154+
// if (rawQueryString) {
155+
// url.search = new URLSearchParams(rawQueryString).toString();
156+
// }
157+
158+
// const decodedBody =
159+
// isBase64Encoded && rawBody ? Buffer.from(rawBody, "base64") : rawBody;
160+
161+
// const request: axios.AxiosRequestConfig<any> = {
162+
// method: method.toLowerCase(),
163+
// url: url.toString(),
164+
// headers: rawHeaders,
165+
// data: decodedBody,
166+
// timeout: deadline,
167+
// };
168+
169+
// return axios
170+
// .request(request)
171+
// .then((response) => ({ requestId, response }));
172+
// })
173+
// .then(({ requestId, response }) => {
174+
// const { data: rawData, headers: rawHeaders } = response;
175+
176+
// const body =
177+
// rawData && typeof rawData === "string"
178+
// ? rawData
179+
// : Buffer.from(rawData).toString("base64");
180+
181+
// const isBase64Encoded = rawData && typeof rawData !== "string";
182+
183+
// const payload: APIGatewayProxyResultV2 = {
184+
// statusCode: response.status,
185+
// headers: convertHeaders(rawHeaders),
186+
// body,
187+
// isBase64Encoded,
188+
// };
189+
190+
// return { requestId, payload };
191+
// })
192+
// .then(({ requestId, payload }) => {
193+
// return axios.post(
194+
// `http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/${requestId}/response`,
195+
// payload
196+
// );
197+
// })
198+
// .then(({ config }) => {
199+
// console.log(`Invocation response successfully sent to ${config.url}`);
200+
// });
201+
// // TODO Error catcher?
202+
// };

0 commit comments

Comments
 (0)