/
function.ts
124 lines (109 loc) · 3.78 KB
/
function.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import * as lambda from '@aws-cdk/aws-lambda';
import * as cdk from '@aws-cdk/core';
import * as fs from 'fs';
import * as path from 'path';
import { Bundling, ParcelBaseOptions } from './bundling';
import { PackageJsonManager } from './package-json-manager';
import { nodeMajorVersion, parseStackTrace } from './util';
/**
* Properties for a NodejsFunction
*/
export interface NodejsFunctionProps extends lambda.FunctionOptions, ParcelBaseOptions {
/**
* Path to the entry file (JavaScript or TypeScript).
*
* @default - Derived from the name of the defining file and the construct's id.
* If the `NodejsFunction` is defined in `stack.ts` with `my-handler` as id
* (`new NodejsFunction(this, 'my-handler')`), the construct will look at `stack.my-handler.ts`
* and `stack.my-handler.js`.
*/
readonly entry?: string;
/**
* The name of the exported handler in the entry file.
*
* @default handler
*/
readonly handler?: string;
/**
* The runtime environment. Only runtimes of the Node.js family are
* supported.
*
* @default - `NODEJS_12_X` if `process.versions.node` >= '12.0.0',
* `NODEJS_10_X` otherwise.
*/
readonly runtime?: lambda.Runtime;
}
/**
* A Node.js Lambda function bundled using Parcel
*/
export class NodejsFunction extends lambda.Function {
constructor(scope: cdk.Construct, id: string, props: NodejsFunctionProps = {}) {
if (props.runtime && props.runtime.family !== lambda.RuntimeFamily.NODEJS) {
throw new Error('Only `NODEJS` runtimes are supported.');
}
// Entry and defaults
const entry = findEntry(id, props.entry);
const handler = props.handler ?? 'handler';
const defaultRunTime = nodeMajorVersion() >= 12
? lambda.Runtime.NODEJS_12_X
: lambda.Runtime.NODEJS_10_X;
const runtime = props.runtime ?? defaultRunTime;
// Look for the closest package.json starting in the directory of the entry
// file. We need to restore it after bundling.
const packageJsonManager = new PackageJsonManager(path.dirname(entry));
try {
super(scope, id, {
...props,
runtime,
code: Bundling.parcel({
entry,
runtime,
...props,
}),
handler: `index.${handler}`,
});
} finally {
// We can only restore after the code has been bound to the function
packageJsonManager.restore();
}
}
}
/**
* Searches for an entry file. Preference order is the following:
* 1. Given entry file
* 2. A .ts file named as the defining file with id as suffix (defining-file.id.ts)
* 3. A .js file name as the defining file with id as suffix (defining-file.id.js)
*/
function findEntry(id: string, entry?: string): string {
if (entry) {
if (!/\.(js|ts)$/.test(entry)) {
throw new Error('Only JavaScript or TypeScript entry files are supported.');
}
if (!fs.existsSync(entry)) {
throw new Error(`Cannot find entry file at ${entry}`);
}
return entry;
}
const definingFile = findDefiningFile();
const extname = path.extname(definingFile);
const tsHandlerFile = definingFile.replace(new RegExp(`${extname}$`), `.${id}.ts`);
if (fs.existsSync(tsHandlerFile)) {
return tsHandlerFile;
}
const jsHandlerFile = definingFile.replace(new RegExp(`${extname}$`), `.${id}.js`);
if (fs.existsSync(jsHandlerFile)) {
return jsHandlerFile;
}
throw new Error('Cannot find entry file.');
}
/**
* Finds the name of the file where the `NodejsFunction` is defined
*/
function findDefiningFile(): string {
const stackTrace = parseStackTrace();
const functionIndex = stackTrace.findIndex(s => /NodejsFunction/.test(s.methodName || ''));
if (functionIndex === -1 || !stackTrace[functionIndex + 1]) {
throw new Error('Cannot find defining file.');
}
return stackTrace[functionIndex + 1].file;
}