Skip to content

Commit 47051de

Browse files
authored
fix(runtime-handler): add warning for optional context vars (#317)
1 parent 6b5bcf4 commit 47051de

File tree

5 files changed

+144
-4
lines changed

5 files changed

+144
-4
lines changed

Diff for: packages/runtime-handler/src/dev-runtime/internal/functionRunner.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { ServerlessCallback } from '@twilio-labs/serverless-runtime-types/types';
22
import { serializeError } from 'serialize-error';
3-
import { constructContext, constructGlobalScope, isTwiml } from '../route';
3+
import {
4+
augmentContextWithOptionals,
5+
constructContext,
6+
constructGlobalScope,
7+
isTwiml,
8+
} from '../route';
49
import { ServerConfig } from '../types';
510
import { Response } from './response';
611
import { setRoutes } from './route-cache';
@@ -62,8 +67,9 @@ process.on(
6267
try {
6368
setRoutes(config.routes);
6469
constructGlobalScope(config);
65-
const context = constructContext(config, path);
70+
let context = constructContext(config, path);
6671
sendDebugMessage('Context for %s: %p', path, context);
72+
context = augmentContextWithOptionals(config, context);
6773
sendDebugMessage('Event for %s: %o', path, event);
6874
let run_timings: { start: [number, number]; end: [number, number] } = {
6975
start: [0, 0],

Diff for: packages/runtime-handler/src/dev-runtime/route.ts

+65-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
TwilioClientOptions,
77
TwilioPackage,
88
} from '@twilio-labs/serverless-runtime-types/types';
9+
import chalk from 'chalk';
910
import { fork } from 'child_process';
1011
import {
1112
NextFunction,
@@ -23,6 +24,7 @@ import * as Runtime from './internal/runtime';
2324
import { ServerConfig } from './types';
2425
import debug from './utils/debug';
2526
import { wrapErrorInHtml } from './utils/error-html';
27+
import { getCodeLocation } from './utils/getCodeLocation';
2628
import { requireFromProject } from './utils/requireFromProject';
2729
import { cleanUpStackTrace } from './utils/stack-trace/clean-up';
2830

@@ -39,6 +41,60 @@ export function constructEvent<T extends {} = {}>(req: ExpressRequest): T {
3941
return { ...req.query, ...req.body };
4042
}
4143

44+
export function augmentContextWithOptionals(
45+
config: ServerConfig,
46+
context: Context
47+
): Context<{
48+
ACCOUNT_SID?: string;
49+
AUTH_TOKEN?: string;
50+
DOMAIN_NAME: string;
51+
PATH: string;
52+
SERVICE_SID: string | undefined;
53+
ENVIRONMENT_SID: string | undefined;
54+
[key: string]: string | undefined | Function;
55+
}> {
56+
log('Adding getters with warnings to optional properties');
57+
if (typeof context.SERVICE_SID === 'undefined') {
58+
let _serviceSid: string | undefined = undefined;
59+
Object.defineProperty(context, 'SERVICE_SID', {
60+
get: () => {
61+
if (_serviceSid === undefined) {
62+
console.warn(
63+
chalk`{bold.yellow WARNING} at ${getCodeLocation({
64+
relativeFrom: config.baseDir,
65+
offset: 1,
66+
})} The SERVICE_SID variable is undefined by default in local development. This variable will be autopopulated when your Functions get deployed. Learn more at: https://twil.io/toolkit-variables`
67+
);
68+
}
69+
return _serviceSid;
70+
},
71+
set: (value: string) => {
72+
_serviceSid = value;
73+
},
74+
});
75+
}
76+
if (typeof context.ENVIRONMENT_SID === 'undefined') {
77+
let _environmentSid: string | undefined = undefined;
78+
Object.defineProperty(context, 'ENVIRONMENT_SID', {
79+
get: () => {
80+
if (_environmentSid === undefined) {
81+
console.warn(
82+
chalk`{bold.yellow WARNING} at ${getCodeLocation({
83+
relativeFrom: config.baseDir,
84+
offset: 1,
85+
})}: The ENVIRONMENT_SID variable is undefined by default in local development. This variable will be autopopulated when your Functions get deployed. Learn more at: https://twil.io/toolkit-variables`
86+
);
87+
}
88+
return _environmentSid;
89+
},
90+
set: (value: string) => {
91+
_environmentSid = value;
92+
},
93+
});
94+
}
95+
return context;
96+
}
97+
4298
export function constructContext<T extends {} = {}>(
4399
{ url, env, logger, baseDir }: ServerConfig,
44100
functionPath: string
@@ -74,7 +130,15 @@ export function constructContext<T extends {} = {}>(
74130
}
75131
const DOMAIN_NAME = url.replace(/^https?:\/\//, '');
76132
const PATH = functionPath;
77-
return { PATH, DOMAIN_NAME, ...env, getTwilioClient };
133+
const context = {
134+
PATH,
135+
DOMAIN_NAME,
136+
SERVICE_SID: undefined,
137+
ENVIRONMENT_SID: undefined,
138+
...env,
139+
getTwilioClient,
140+
};
141+
return context;
78142
}
79143

80144
export function constructGlobalScope(config: ServerConfig): void {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import os from 'os';
2+
import path from 'path';
3+
4+
export type CodeLocation =
5+
| {
6+
line: null;
7+
filePath: null;
8+
column: null;
9+
toString(): 'unknown';
10+
}
11+
| {
12+
line: number;
13+
column: number;
14+
filePath: string;
15+
toString(): string;
16+
};
17+
18+
export const UNKNOWN_LOCATION = {
19+
line: null,
20+
filePath: null,
21+
column: null,
22+
toString: (): 'unknown' => {
23+
return 'unknown';
24+
},
25+
};
26+
27+
const STACK_TRACE_REGEX = /.*\((?<fullFilePath>.*):(?<line>\d+):(?<column>\d+)\)$/i;
28+
29+
export function getCodeLocation(
30+
options: { relativeFrom?: string; offset?: number } = {}
31+
): CodeLocation {
32+
options = { relativeFrom: undefined, offset: 0, ...options };
33+
const fullStackTrace = new Error().stack;
34+
if (typeof fullStackTrace !== 'string') {
35+
return UNKNOWN_LOCATION;
36+
}
37+
38+
const stackLines = fullStackTrace.split(os.EOL);
39+
// add two to the offset because the first line is "Error" and the next is the location of this function.
40+
const locationLine = stackLines[(options.offset || 0) + 2];
41+
42+
if (!locationLine) {
43+
return UNKNOWN_LOCATION;
44+
}
45+
46+
const match = STACK_TRACE_REGEX.exec(locationLine);
47+
if (match === null) {
48+
return UNKNOWN_LOCATION;
49+
}
50+
51+
const line = parseInt(match.groups?.line || '0', 0);
52+
const column = parseInt(match.groups?.column || '0', 0);
53+
let filePath = match.groups?.fullFilePath || 'unknown';
54+
55+
if (options.relativeFrom) {
56+
filePath = path.relative(options.relativeFrom, filePath);
57+
}
58+
59+
const stringVersion = `${filePath}:${line}:${column}`;
60+
61+
return {
62+
line,
63+
column,
64+
filePath,
65+
toString: () => stringVersion,
66+
};
67+
}

Diff for: packages/serverless-runtime-types/types.d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ export type RuntimeInstance = {
4646
export type Context<T = {}> = {
4747
getTwilioClient(options?: TwilioClientOptions): twilio.Twilio;
4848
DOMAIN_NAME: string;
49+
PATH: string;
50+
SERVICE_SID: string | undefined;
51+
ENVIRONMENT_SID: string | undefined;
4952
} & T;
5053

5154
export type ServerlessCallback = (

Diff for: packages/twilio-run/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
"license": "MIT",
3737
"dependencies": {
3838
"@twilio-labs/serverless-api": "^5.2.0",
39-
"@twilio-labs/serverless-runtime-types": "^2.1.0",
39+
"@twilio-labs/serverless-runtime-types": "2.1.0-rc.0",
4040
"@types/express": "4.17.7",
4141
"@types/inquirer": "^6.0.3",
4242
"@types/is-ci": "^2.0.0",

0 commit comments

Comments
 (0)