Skip to content

Commit bfee1ca

Browse files
feat: support all sam local cli options
1 parent 8eb596f commit bfee1ca

9 files changed

+277
-35
lines changed

.editorconfig

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# EditorConfig is awesome: https://EditorConfig.org
2+
3+
# top-most EditorConfig file
4+
root = true
5+
6+
# Unix-style newlines with a newline ending every file
7+
[*]
8+
indent_style = space
9+
indent_size = 2
10+
end_of_line = lf
11+
charset = utf-8
12+
trim_trailing_whitespace = true
13+
insert_final_newline = true
14+

lib/SAMLocal.ts

+10-20
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,27 @@
66

77
import { spawn } from 'child_process';
88

9+
import { getCLIOptionArgs } from './SamLocalCLIOptions';
10+
import { SAMLocalCLIOptions, SAMLocalType } from './types';
911
import { createDeferred } from './utils';
1012

11-
interface SAMLocalOptions {
12-
// https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-local-start-api.html
13-
warmContainers?: 'EAGER' | 'LAZY';
13+
export interface SAMLocalOptions {
1414
onData?: (data: any) => void;
1515
onError?: (data: any) => void;
16+
cliOptions: SAMLocalCLIOptions;
1617
}
1718
export interface SAMLocal {
1819
kill: () => void;
1920
}
2021

2122
export async function createSAMLocal(
22-
type: 'sdk' | 'api',
23+
type: SAMLocalType,
2324
cwd: string,
24-
port: number,
25-
options: SAMLocalOptions = {}
25+
options: SAMLocalOptions = {cliOptions: {}},
2626
): Promise<SAMLocal> {
27-
const {warmContainers, onData, onError} = options
28-
const sdkSpawnArgs = [
29-
'local', 'start-lambda',
30-
'--port', `${port}`,
31-
'--region', 'local',
32-
];
33-
const apiSpawnArgs = [
34-
'local', 'start-api',
35-
'--port', `${port}`,
36-
...(warmContainers ? ['--warm-containers',warmContainers] : []),
37-
];
27+
const {onData, onError, cliOptions} = options;
28+
const typeArg = type === 'sdk' ? 'start-lambda' : 'start-api';
29+
const spawnArgs = ['local', typeArg, ...getCLIOptionArgs(type, cliOptions)];
3830
const defer = createDeferred();
3931
let started = false;
4032

@@ -46,9 +38,7 @@ export async function createSAMLocal(
4638
}
4739
}
4840

49-
const process = spawn('sam', type === 'sdk' ? sdkSpawnArgs : apiSpawnArgs, {
50-
cwd,
51-
});
41+
const process = spawn('sam', spawnArgs, { cwd });
5242

5343
process.on('exit', () => {
5444
defer.resolve();

lib/SAMLocalCLIOptions.ts

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import {paramCase} from 'change-case'
2+
import { SAMLocalCLIOptions, SAMLocalType } from "./types"
3+
import { isNotEmptyString, isFinite} from "./utils"
4+
5+
const cliOptionKeys = [
6+
'host', 'port', 'template', 'envVars', 'parameterOverrides',
7+
'debugPort', 'debuggerPath', 'debugArgs', 'warmContainers',
8+
'debugFunction', 'dockerVolumeBasedir', 'dockerNetwork',
9+
'containerEnvVars', 'logFile', 'layerCacheBasedir',
10+
'skipPullImage', 'forceImageBuild', 'profile',
11+
'region', 'configFile', 'configEnv', 'debug',
12+
]
13+
14+
const isLocalStartLambdaCLIArgKey = (key: string) => {
15+
return cliOptionKeys.indexOf(key) > -1
16+
}
17+
const isLocalStartAPICLIArgKey = (key: string) => {
18+
return [...cliOptionKeys, 'staticDir'].indexOf(key) > -1
19+
}
20+
21+
export const getCLIOptionArgs = (
22+
type: SAMLocalType,
23+
options: SAMLocalCLIOptions,
24+
): string[] => {
25+
const args = Object.keys(options).reduce((prev, key) => {
26+
const validKey = type === 'api'
27+
? isLocalStartAPICLIArgKey(key)
28+
: isLocalStartLambdaCLIArgKey(key)
29+
if (!validKey) {
30+
console.warn(`Unknown option: ${key}. It is ignored.`)
31+
return prev
32+
}
33+
34+
const value = options[key as keyof SAMLocalCLIOptions]
35+
const paramArgs =
36+
isNotEmptyString(value) ? [`--${paramCase(key)}`, value]
37+
: isFinite(value) ? [`--${paramCase(key)}`, value+'']
38+
:/*typeof value === 'boolean'*/ [`--${paramCase(key)}`]
39+
return [...prev, ...paramArgs]
40+
}, [] as string[])
41+
42+
return args
43+
}

lib/generateAppModel.ts

+12-9
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
unzipToLocation,
1515
} from './utils';
1616
import { createSAMLocal, SAMLocal } from './SAMLocal';
17-
import { ConfigLambda, SAMTemplate } from './types';
17+
import { ConfigLambda, SAMLocalAPICLIOptions, SAMTemplate } from './types';
1818

1919
/**
2020
* Wrapper that generates a serverless application model (SAM) for lambda inputs
@@ -50,13 +50,15 @@ interface Props {
5050
cwd: string;
5151
onData?: (data: any) => void;
5252
onError?: (data: any) => void;
53+
cliOptions?: SAMLocalAPICLIOptions;
5354
}
5455

5556
export async function generateSAM({
5657
lambdas,
5758
cwd,
5859
onData,
5960
onError,
61+
cliOptions={},
6062
}: Props): Promise<SAM> {
6163
const _tmpDir = tmpDir({ unsafeCleanup: true });
6264
const workdir = _tmpDir.name;
@@ -111,20 +113,21 @@ export async function generateSAM({
111113
fs.writeFileSync(path.join(workdir, 'template.yml'), yaml(SAMTemplate));
112114

113115
let SAM: SAMLocal;
116+
let host: string;
114117
let port: number;
115118
let client: AWSLambda;
119+
let region: string;
116120

117121
async function start() {
118-
// Get free port
119-
port = await getPort();
120-
SAM = await createSAMLocal('api', workdir, port, {
122+
port = cliOptions.port || await getPort();
123+
host = cliOptions.host || '127.0.0.1'
124+
region = cliOptions.region || 'local'
125+
SAM = await createSAMLocal('api', workdir, {
121126
onData,
122127
onError,
128+
cliOptions: { ...cliOptions, port, host, region },
123129
});
124-
client = new AWSLambda({
125-
endpoint: `http://localhost:${port}`,
126-
region: 'local',
127-
});
130+
client = new AWSLambda({ endpoint: `http://${host}:${port}`, region });
128131
}
129132

130133
async function stop() {
@@ -176,7 +179,7 @@ export async function generateSAM({
176179
path: string,
177180
{ headers } = {}
178181
) => {
179-
return fetch(`http://localhost:${port}${path}`, { headers });
182+
return fetch(`http://${host}:${port}${path}`, { headers });
180183
};
181184

182185
return {

lib/generateProxyModel.ts

+10-6
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import getPort from 'get-port';
1212
import http from 'http';
1313
import { URL } from 'url';
1414

15-
import { SAMTemplate } from './types';
15+
import { SAMLocalLambadCLIOptions, SAMTemplate } from './types';
1616
import { getLocalIpAddressFromHost, unzipToLocation } from './utils';
1717
import { createSAMLocal, SAMLocal } from './SAMLocal';
1818

@@ -28,6 +28,7 @@ interface Props {
2828
runtime?: 'nodejs12.x' | 'nodejs14.x';
2929
onData?: (data: any) => void;
3030
onError?: (data: any) => void;
31+
cliOptions?: SAMLocalLambadCLIOptions;
3132
}
3233

3334
interface SendRequestEventProps {
@@ -48,6 +49,7 @@ export async function generateProxySAM({
4849
runtime = 'nodejs12.x',
4950
onData,
5051
onError,
52+
cliOptions={},
5153
}: Props): Promise<SAM> {
5254
const _tmpDir = tmpDir({ unsafeCleanup: true });
5355
const workdir = _tmpDir.name;
@@ -82,8 +84,10 @@ export async function generateProxySAM({
8284
fs.writeFileSync(path.join(workdir, 'template.yml'), yaml(SAMTemplate));
8385

8486
let SAM: SAMLocal;
87+
let host: string;
8588
let port: number;
8689
let client: AWSLambda;
90+
let region: string;
8791
let portProxyConfig: number;
8892
// Simple HTTP server to serve the proxy config
8993
// https://stackoverflow.com/a/44188852/831465
@@ -95,14 +99,14 @@ export async function generateProxySAM({
9599
async function start() {
96100
// Initialize SAM
97101
port = await getPort();
98-
SAM = await createSAMLocal('sdk', workdir, port, {
102+
host = cliOptions.host || '127.0.0.1'
103+
region = cliOptions.region || 'local'
104+
SAM = await createSAMLocal('sdk', workdir, {
99105
onData,
100106
onError,
107+
cliOptions: { ...cliOptions, port, host, region },
101108
});
102-
client = new AWSLambda({
103-
endpoint: `http://localhost:${port}`,
104-
region: 'local',
105-
});
109+
client = new AWSLambda({ endpoint: `http://${host}:${port}`, region });
106110

107111
// Initialize Proxy Config Server
108112
portProxyConfig = await getPort();

lib/types.ts

+44
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,47 @@ export interface ConfigLambda {
4141
environment?: Record<string, string>;
4242
memorySize?: number;
4343
}
44+
45+
export type SAMLocalType = 'sdk' | 'api'
46+
47+
type WarmContainersOptions = 'EAGER' | 'LAZY'
48+
49+
export interface SAMLocalCLICommonOptions {
50+
host?: string
51+
port?: number
52+
template?: string
53+
envVars?: string
54+
parameterOverrides?: string
55+
debugPort?: string
56+
debuggerPath?: string
57+
debugArgs?: string
58+
warmContainers?: WarmContainersOptions
59+
debugFunction?: string
60+
dockerVolumeBasedir?: string
61+
dockerNetwork?: string
62+
containerEnvVars?: string
63+
logFile?: string
64+
layerCacheBasedir?: string
65+
skipPullImage?: boolean
66+
forceImageBuild?: boolean
67+
profile?: string
68+
region?: string
69+
configFile?: string
70+
configEnv?: string
71+
debug?: boolean
72+
// help?: boolean
73+
}
74+
75+
export interface SAMLocalLambadCLIOptions extends SAMLocalCLICommonOptions {
76+
// https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-local-start-lambda.html
77+
// This type is the same as SAMLocalCLICommonOptions
78+
}
79+
80+
export interface SAMLocalAPICLIOptions extends SAMLocalCLICommonOptions {
81+
// https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-local-start-api.html
82+
staticDir?: string
83+
}
84+
85+
export type SAMLocalCLIOptions =
86+
| SAMLocalLambadCLIOptions
87+
| SAMLocalAPICLIOptions

lib/utils/index.ts

+8
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,12 @@ export function normalizeCloudFrontHeaders(
139139
return result;
140140
}
141141

142+
export const isNotEmptyString = (a: any): a is string => {
143+
return typeof a === 'string' && !!a
144+
}
145+
146+
export const isFinite = (a: any): a is number => {
147+
return Number.isFinite(a)
148+
}
149+
142150
export { Deferred, createDeferred } from './deferred';

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
},
1313
"dependencies": {
1414
"aws-sdk": "^2.771.0",
15+
"change-case": "^4.1.2",
1516
"get-port": "^5.1.1",
1617
"node-fetch": "^2.6.1",
1718
"tmp": "^0.2.1",

0 commit comments

Comments
 (0)