From 7bebf6f370c7fbfab3f1431741a48a8cdaaf552c Mon Sep 17 00:00:00 2001 From: Dominik Kundel Date: Tue, 26 Mar 2024 00:52:08 -0700 Subject: [PATCH 1/3] fix(@twilio/runtime-handler): add cross-fork logger instance for local dev (#459) Co-authored-by: Victor A <33580233+victoray@users.noreply.github.com> --- .changeset/forty-snails-rescue.md | 5 +++ .../dev-runtime/internal/crossForkLogger.ts | 35 +++++++++++++++++++ .../dev-runtime/internal/functionRunner.ts | 8 +++++ .../runtime-handler/src/dev-runtime/route.ts | 23 +++++++++++- 4 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 .changeset/forty-snails-rescue.md create mode 100644 packages/runtime-handler/src/dev-runtime/internal/crossForkLogger.ts diff --git a/.changeset/forty-snails-rescue.md b/.changeset/forty-snails-rescue.md new file mode 100644 index 00000000..def1040e --- /dev/null +++ b/.changeset/forty-snails-rescue.md @@ -0,0 +1,5 @@ +--- +'@twilio/runtime-handler': patch +--- + +Fix error messages in local development diff --git a/packages/runtime-handler/src/dev-runtime/internal/crossForkLogger.ts b/packages/runtime-handler/src/dev-runtime/internal/crossForkLogger.ts new file mode 100644 index 00000000..f6f18325 --- /dev/null +++ b/packages/runtime-handler/src/dev-runtime/internal/crossForkLogger.ts @@ -0,0 +1,35 @@ +import { LoggerInstance } from '../types'; + +export class CrossForkLogger implements LoggerInstance { + constructor() {} + + debug(msg: string) { + this.sendLog('debug', msg); + } + + info(msg: string) { + this.sendLog('info', msg); + } + + warn(msg: string, title: string = '') { + this.sendLog('warn', msg, title); + } + + error(msg: string, title: string = '') { + this.sendLog('error', msg, title); + } + + log(msg: string, level: number) { + this.sendLog('log', msg, level); + } + + private sendLog(level: keyof LoggerInstance, ...args: (string | number)[]) { + process.send && + process.send({ + crossForkLogMessage: { + level, + args: args, + }, + }); + } +} diff --git a/packages/runtime-handler/src/dev-runtime/internal/functionRunner.ts b/packages/runtime-handler/src/dev-runtime/internal/functionRunner.ts index 10a7d944..d54a0f86 100644 --- a/packages/runtime-handler/src/dev-runtime/internal/functionRunner.ts +++ b/packages/runtime-handler/src/dev-runtime/internal/functionRunner.ts @@ -7,6 +7,7 @@ import { isTwiml, } from '../route'; import { ServerConfig, Headers } from '../types'; +import { CrossForkLogger } from './crossForkLogger'; import { Response } from './response'; import { setRoutes } from './route-cache'; @@ -61,12 +62,19 @@ const handleSuccess = (responseObject?: string | number | boolean | object) => { } }; +process.on('uncaughtException', (err, origin) => { + if (process.send) { + process.send({ err: serializeError(err) }); + } +}); + process.on( 'message', ({ functionPath, event, config, path }: FunctionRunnerOptions) => { try { setRoutes(config.routes); constructGlobalScope(config); + config.logger = new CrossForkLogger(); let context = constructContext(config, path); sendDebugMessage('Context for %s: %p', path, context); context = augmentContextWithOptionals(config, context); diff --git a/packages/runtime-handler/src/dev-runtime/route.ts b/packages/runtime-handler/src/dev-runtime/route.ts index 008329b8..4dfdad31 100644 --- a/packages/runtime-handler/src/dev-runtime/route.ts +++ b/packages/runtime-handler/src/dev-runtime/route.ts @@ -26,7 +26,7 @@ import { import { Reply } from './internal/functionRunner'; import { Response } from './internal/response'; import * as Runtime from './internal/runtime'; -import { ServerConfig } from './types'; +import { LoggerInstance, ServerConfig } from './types'; import debug from './utils/debug'; import { wrapErrorInHtml } from './utils/error-html'; import { getCodeLocation } from './utils/getCodeLocation'; @@ -303,25 +303,46 @@ export function functionPathToRoute( reply, debugMessage, debugArgs = [], + crossForkLogMessage, }: { err?: Error | number | string; reply?: Reply; debugMessage?: string; debugArgs?: any[]; + crossForkLogMessage?: { + level: keyof LoggerInstance; + args: [string] | [string, number] | [string, string]; + }; }) => { if (debugMessage) { log(debugMessage, ...debugArgs); return; } + + if (crossForkLogMessage) { + if ( + config.logger && + typeof config.logger[crossForkLogMessage.level] === 'function' + ) { + config.logger[crossForkLogMessage.level]( + // @ts-ignore + ...crossForkLogMessage.args + ); + } + return; + } + if (err) { const error = deserializeError(err); handleError(error, req, res, functionPath); } + if (reply) { res.status(reply.statusCode); res.set(reply.headers); res.send(reply.body); } + forked.kill(); } ); From 34de0811d9a6980c69f01be9d03251c2f2496b4b Mon Sep 17 00:00:00 2001 From: Phil Nash Date: Tue, 26 Mar 2024 19:26:55 +1100 Subject: [PATCH 2/3] fix(runtime-handler): don't check for exact Content-Type matches (#392) * fix(runtime-handler): don't check for exact Content-Type matches Content-Type headers may have the media type followed by a semicolon, followed by some parameters, for example "charset=UTF-8". This fix ensures that only the start of the Content-Type header matches application/json when stringifying the body to JSON. Fixes #391 * chore: add changeset --------- Co-authored-by: Victor Ayogu --- .changeset/friendly-turtles-pretend.md | 5 +++++ .../dev-runtime/internal/response.test.ts | 18 ++++++++++++++++++ .../src/dev-runtime/internal/response.ts | 15 ++++++++++----- 3 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 .changeset/friendly-turtles-pretend.md diff --git a/.changeset/friendly-turtles-pretend.md b/.changeset/friendly-turtles-pretend.md new file mode 100644 index 00000000..03d29d63 --- /dev/null +++ b/.changeset/friendly-turtles-pretend.md @@ -0,0 +1,5 @@ +--- +'@twilio/runtime-handler': patch +--- + +Don't check for exact Content-Type matches diff --git a/packages/runtime-handler/__tests__/dev-runtime/internal/response.test.ts b/packages/runtime-handler/__tests__/dev-runtime/internal/response.test.ts index 8a54e45a..aee87042 100644 --- a/packages/runtime-handler/__tests__/dev-runtime/internal/response.test.ts +++ b/packages/runtime-handler/__tests__/dev-runtime/internal/response.test.ts @@ -292,3 +292,21 @@ test('serializes a response with content type set to application/json', () => { 'Set-Cookie': [], }); }); + +test('serializes a response with content type set to application/json with a charset', () => { + const response = new Response(); + response.setBody({ url: 'https://dkundel.com' }); + response.setStatusCode(200); + response.appendHeader('Content-Type', 'application/json; charset=UTF-8'); + + const serialized = response.serialize(); + + expect(serialized.body).toEqual( + JSON.stringify({ url: 'https://dkundel.com' }) + ); + expect(serialized.statusCode).toEqual(200); + expect(serialized.headers).toEqual({ + 'Content-Type': 'application/json; charset=UTF-8', + 'Set-Cookie': [], + }); +}); diff --git a/packages/runtime-handler/src/dev-runtime/internal/response.ts b/packages/runtime-handler/src/dev-runtime/internal/response.ts index 20ec8285..5f418883 100644 --- a/packages/runtime-handler/src/dev-runtime/internal/response.ts +++ b/packages/runtime-handler/src/dev-runtime/internal/response.ts @@ -79,7 +79,7 @@ export class Response implements TwilioResponse { this.headers[COOKIE_HEADER] = newHeaderValue; } } else { - this.headers[COOKIE_HEADER] = Array.isArray(value) ? value: [value]; + this.headers[COOKIE_HEADER] = Array.isArray(value) ? value : [value]; } } else { const existingValue = this.headers[key]; @@ -133,12 +133,17 @@ export class Response implements TwilioResponse { } serialize() { + const contentType = this.headers['Content-Type']; + let body = this.body; + if ( + typeof contentType === 'string' && + contentType.startsWith('application/json') + ) { + body = JSON.stringify(body); + } return { statusCode: this.statusCode, - body: - this.headers['Content-Type'] === 'application/json' - ? JSON.stringify(this.body) - : this.body, + body: body, headers: this.headers, }; } From 3868a78d8175a622766560caceb86f647a3c34d5 Mon Sep 17 00:00:00 2001 From: Dominik Kundel Date: Tue, 26 Mar 2024 01:32:28 -0700 Subject: [PATCH 3/3] fix(twilio-run): turn off fork-process in inspect mode (#462) * fix(twilio-run): turn off fork-process in inspect mode * Update start.test.ts --------- Co-authored-by: makserik <15821542+makserik@users.noreply.github.com> Co-authored-by: Victor Ayogu --- .changeset/warm-moles-learn.md | 6 ++++++ packages/twilio-run/__tests__/config/start.test.ts | 14 ++++++++++++++ packages/twilio-run/src/config/start.ts | 6 ++++++ 3 files changed, 26 insertions(+) create mode 100644 .changeset/warm-moles-learn.md diff --git a/.changeset/warm-moles-learn.md b/.changeset/warm-moles-learn.md new file mode 100644 index 00000000..b3651dd1 --- /dev/null +++ b/.changeset/warm-moles-learn.md @@ -0,0 +1,6 @@ +--- +'twilio-run': patch +'@twilio-labs/plugin-serverless': patch +--- + +Fix of debug/inspect mode by disabling fork process diff --git a/packages/twilio-run/__tests__/config/start.test.ts b/packages/twilio-run/__tests__/config/start.test.ts index 6ded1a0d..0c269bcf 100644 --- a/packages/twilio-run/__tests__/config/start.test.ts +++ b/packages/twilio-run/__tests__/config/start.test.ts @@ -285,4 +285,18 @@ describe('getConfigFromCli', () => { expect(startConfig.baseDir).toEqual(path.resolve(config.cwd)); } }); + + test('turns off fork process if inspect is enabled', async () => { + require('../../src/config/utils/package-json').__setPackageJson({}); + const config = { + dir: './other_dir', + inspect: '', + } as unknown as StartCliFlags; + if (config.dir) { + const startConfig = await getConfigFromCli(config); + expect(startConfig.baseDir).toEqual(path.resolve(config.dir)); + expect(startConfig.forkProcess).toEqual(false); + expect(startConfig.inspect).not.toEqual(undefined); + } + }); }); diff --git a/packages/twilio-run/src/config/start.ts b/packages/twilio-run/src/config/start.ts index 5f5179b3..2d5df6be 100644 --- a/packages/twilio-run/src/config/start.ts +++ b/packages/twilio-run/src/config/start.ts @@ -186,6 +186,12 @@ export async function getConfigFromCli( config.assetsFolderName = cli.assetsFolder; config.functionsFolderName = cli.functionsFolder; config.forkProcess = cli.forkProcess; + + if (typeof config.inspect !== 'undefined') { + debug('Disabling fork-process in inspect mode.'); + config.forkProcess = false; + } + config.pkgJson = pkgJson; return config;