From a340212c680fcc0fcb2f5716ba3d48290bdc067e Mon Sep 17 00:00:00 2001 From: Dominik Kundel Date: Mon, 18 May 2020 13:00:21 -0700 Subject: [PATCH 01/10] ci: replace Travis with GitHub Actions To have consistency I'm moving the different CIs slowly to GitHub Actions. It works better with our permissioning models. Long term we'll add publishing to this as well --- .github/workflows/nodejs.yml | 26 ++++++++++++++++++++++++++ .travis.yml | 12 ------------ 2 files changed, 26 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/nodejs.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml new file mode 100644 index 00000000..b8b79c23 --- /dev/null +++ b/.github/workflows/nodejs.yml @@ -0,0 +1,26 @@ +name: Node CI + +on: [pull_request] + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-latest, windows-latest, ubuntu-latest] + node-version: [10, 12, 14] + + steps: + - uses: actions/checkout@v1 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: npm install, build, and test + run: | + npm install + npm run build --if-present + npm run test + env: + CI: true + NODE_ENV: test diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 02427516..00000000 --- a/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -language: node_js - -node_js: - - 'stable' - - '10' - - '12' - -sudo: false - -cache: - directories: - - 'node_modules' \ No newline at end of file From 9bed485855e60366e57caae6d1540d9559d79f6b Mon Sep 17 00:00:00 2001 From: Dominik Kundel Date: Mon, 18 May 2020 13:53:57 -0700 Subject: [PATCH 02/10] fix(server): fix typescript build issue --- src/runtime/server.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/runtime/server.ts b/src/runtime/server.ts index f075d1ac..e74967b6 100644 --- a/src/runtime/server.ts +++ b/src/runtime/server.ts @@ -1,5 +1,6 @@ import { ServerlessFunctionSignature } from '@twilio-labs/serverless-runtime-types/types'; import bodyParser from 'body-parser'; +import chokidar from 'chokidar'; import express, { Express, NextFunction, @@ -7,12 +8,11 @@ import express, { Response as ExpressResponse, } from 'express'; import userAgentMiddleware from 'express-useragent'; -import nocache from 'nocache'; -import { printRouteInfo } from '../printers/start'; -import chokidar from 'chokidar'; import debounce from 'lodash.debounce'; +import nocache from 'nocache'; import path from 'path'; import { StartCliConfig } from '../config/start'; +import { printRouteInfo } from '../printers/start'; import { wrapErrorInHtml } from '../utils/error-html'; import { getDebugFunction } from '../utils/logger'; import { createLogger } from './internal/request-logger'; @@ -47,9 +47,9 @@ export async function createServer( config: StartCliConfig ): Promise { config = { - url: `http://localhost:${port}`, - baseDir: process.cwd(), ...config, + url: config.url || `http://localhost:${port}`, + baseDir: config.baseDir || process.cwd(), }; debug('Starting server with config: %p', config); @@ -95,7 +95,7 @@ export async function createServer( path.join(config.baseDir, '/(assets|static)/**/*'), ], { - ignoreInitial: true + ignoreInitial: true, } ); From dc695c82aca832f814c92e391e04e8cc3540e087 Mon Sep 17 00:00:00 2001 From: Dominik Kundel Date: Mon, 18 May 2020 13:58:27 -0700 Subject: [PATCH 03/10] build(ts): remove incremental builds You can't use incremental builds with the noEmit option so we are dropping it for now --- tsconfig.base.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.base.json b/tsconfig.base.json index a65f9176..e0dbadea 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1,7 +1,7 @@ { "compilerOptions": { /* Basic Options */ - "incremental": true /* Enable incremental compilation */, + "incremental": false /* Enable incremental compilation */, "target": "es2015" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */, "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, "lib": [ From 8166a2c5485ec908e311333989e2466be7ef8a24 Mon Sep 17 00:00:00 2001 From: Dominik Kundel Date: Mon, 18 May 2020 14:04:43 -0700 Subject: [PATCH 04/10] fix(tests): fix ts typing error --- __tests__/runtime/route.test.ts | 84 +++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 35 deletions(-) diff --git a/__tests__/runtime/route.test.ts b/__tests__/runtime/route.test.ts index 9e4d3cdd..2fecb17e 100644 --- a/__tests__/runtime/route.test.ts +++ b/__tests__/runtime/route.test.ts @@ -27,6 +27,10 @@ const { VoiceResponse, MessagingResponse, FaxResponse } = twiml; const mockResponse = (new MockResponse() as unknown) as ExpressResponse; mockResponse.type = jest.fn(() => mockResponse); +function asExpressRequest(req: { query?: {}; body?: {} }): ExpressRequest { + return (req as unknown) as ExpressRequest; +} + describe('handleError function', () => { test('returns string error', () => { const mockRequest = (new MockRequest() as unknown) as ExpressRequest; @@ -101,57 +105,67 @@ describe('handleError function', () => { describe('constructEvent function', () => { test('merges query and body', () => { - const event = constructEvent({ - body: { - Body: 'Hello', - }, - query: { - index: 5, - }, - } as ExpressRequest); + const event = constructEvent( + asExpressRequest({ + body: { + Body: 'Hello', + }, + query: { + index: 5, + }, + }) + ); expect(event).toEqual({ Body: 'Hello', index: 5 }); }); test('overrides query with body', () => { - const event = constructEvent({ - body: { - Body: 'Bye', - }, - query: { - Body: 'Hello', - From: '+123456789', - }, - } as ExpressRequest); + const event = constructEvent( + asExpressRequest({ + body: { + Body: 'Bye', + }, + query: { + Body: 'Hello', + From: '+123456789', + }, + }) + ); expect(event).toEqual({ Body: 'Bye', From: '+123456789' }); }); test('handles empty body', () => { - const event = constructEvent({ - body: {}, - query: { - Body: 'Hello', - From: '+123456789', - }, - } as ExpressRequest); + const event = constructEvent( + asExpressRequest({ + body: {}, + query: { + Body: 'Hello', + From: '+123456789', + }, + }) + ); expect(event).toEqual({ Body: 'Hello', From: '+123456789' }); }); test('handles empty query', () => { - const event = constructEvent({ - body: { - Body: 'Hello', - From: '+123456789', - }, - query: {}, - } as ExpressRequest); + const event = constructEvent( + asExpressRequest({ + body: { + Body: 'Hello', + From: '+123456789', + }, + query: {}, + }) + ); expect(event).toEqual({ Body: 'Hello', From: '+123456789' }); }); test('handles both empty', () => { - const event = constructEvent({ - body: {}, - query: {}, - } as ExpressRequest); + const event = constructEvent( + asExpressRequest({ + body: {}, + query: {}, + }) + ); expect(event).toEqual({}); }); }); From 5c171ae02d96ffcbf0ee82564179e0985f6da93c Mon Sep 17 00:00:00 2001 From: Dominik Kundel Date: Mon, 18 May 2020 14:11:12 -0700 Subject: [PATCH 05/10] chore(deps): remove @types/log-symbols --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 8919c0c3..1387280a 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,6 @@ "@types/express": "^4.17.0", "@types/inquirer": "^6.0.3", "@types/is-ci": "^2.0.0", - "@types/log-symbols": "^3.0.0", "@types/wrap-ansi": "^3.0.0", "body-parser": "^1.18.3", "boxen": "^1.3.0", From 50b76f69ab7afc9f96d1b4c1015a61d06f6dd3fa Mon Sep 17 00:00:00 2001 From: Dominik Kundel Date: Mon, 18 May 2020 14:29:21 -0700 Subject: [PATCH 06/10] chore(npm): disable lock file generation --- .npmrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.npmrc b/.npmrc index e69de29b..9cf94950 100644 --- a/.npmrc +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false \ No newline at end of file From f4319fc9eed5edfc3407d06b261907f13330606a Mon Sep 17 00:00:00 2001 From: Dominik Kundel Date: Mon, 18 May 2020 14:29:51 -0700 Subject: [PATCH 07/10] chore(deps): update ts/jest dependencies --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 1387280a..4fa24012 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "pkg-install": "^1.0.0", "terminal-link": "^1.3.0", "title": "^3.4.1", - "twilio": "^3.33.0", + "twilio": "^3.43.1", "type-fest": "^0.6.0", "window-size": "^1.1.1", "wrap-ansi": "^5.1.0", @@ -103,7 +103,7 @@ "commitizen": "^3.1.1", "cz-conventional-changelog": "^2.1.0", "husky": "^3.0.0", - "jest": "^24.8.0", + "jest": "^26.0.1", "jest-express": "^1.10.1", "lint-staged": "^8.2.1", "listr-silent-renderer": "^1.1.1", @@ -113,8 +113,8 @@ "rimraf": "^2.6.3", "standard-version": "^6.0.1", "supertest": "^3.1.0", - "ts-jest": "^24.0.2", - "typescript": "^3.7.4" + "ts-jest": "^26.0.0", + "typescript": "^3.9.2" }, "files": [ "bin/", From 534df7627a6dd16425fdee418c384b8474acc70b Mon Sep 17 00:00:00 2001 From: Dominik Kundel Date: Mon, 18 May 2020 14:30:40 -0700 Subject: [PATCH 08/10] test(mocks): fix route tests by fixing mock --- __mocks__/twilio.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/__mocks__/twilio.ts b/__mocks__/twilio.ts index d17fb66b..06f30c8d 100644 --- a/__mocks__/twilio.ts +++ b/__mocks__/twilio.ts @@ -1,4 +1,7 @@ -const twilio = jest.genMockFromModule('twilio'); +const actualTwilio = jest.requireActual('twilio'); +const twilio: any = jest.genMockFromModule('twilio'); + +twilio['twiml'] = actualTwilio.twiml; // mock specific functionality From eab870ccb13751f5263f5557f2c565ff372265f6 Mon Sep 17 00:00:00 2001 From: Dominik Kundel Date: Mon, 18 May 2020 14:47:16 -0700 Subject: [PATCH 09/10] test(filesystem): fix tests for windows --- __tests__/templating/filesystem.test.ts | 67 +++++++++++++------------ 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/__tests__/templating/filesystem.test.ts b/__tests__/templating/filesystem.test.ts index 8ea139c6..d5ad89a8 100644 --- a/__tests__/templating/filesystem.test.ts +++ b/__tests__/templating/filesystem.test.ts @@ -5,18 +5,18 @@ jest.mock('pkg-install'); jest.mock('../../src/utils/fs'); jest.mock('../../src/utils/logger'); -import path from 'path'; -import { install } from 'pkg-install'; import { fsHelpers } from '@twilio-labs/serverless-api'; import got from 'got'; +import path, { join } from 'path'; +import { install } from 'pkg-install'; import { mocked } from 'ts-jest/utils'; import { writeFiles } from '../../src/templating/filesystem'; import { downloadFile, fileExists, + mkdir, readFile, writeFile, - mkdir, } from '../../src/utils/fs'; beforeEach(() => { @@ -108,22 +108,22 @@ test('installation with basic functions', async () => { expect(downloadFile).toHaveBeenCalledTimes(3); expect(downloadFile).toHaveBeenCalledWith( 'https://example.com/.env', - 'testing/.env' + join('testing', '.env') ); expect(downloadFile).toHaveBeenCalledWith( 'https://example.com/hello.js', - 'testing/functions/example/hello.js' + join('testing', 'functions', 'example', 'hello.js') ); expect(downloadFile).toHaveBeenCalledWith( 'https://example.com/README.md', - 'testing/readmes/example/hello.md' + join('testing', 'readmes', 'example', 'hello.md') ); expect(mkdir).toHaveBeenCalledTimes(2); - expect(mkdir).toHaveBeenCalledWith('testing/functions/example', { + expect(mkdir).toHaveBeenCalledWith(join('testing', 'functions', 'example'), { recursive: true, }); - expect(mkdir).toHaveBeenCalledWith('testing/readmes/example', { + expect(mkdir).toHaveBeenCalledWith(join('testing', 'readmes', 'example'), { recursive: true, }); }); @@ -165,22 +165,22 @@ test('installation with functions and assets', async () => { expect(downloadFile).toHaveBeenCalledTimes(3); expect(downloadFile).toHaveBeenCalledWith( 'https://example.com/.env', - 'testing/.env' + join('testing', '.env') ); expect(downloadFile).toHaveBeenCalledWith( 'https://example.com/hello.js', - 'testing/functions/example/hello.js' + join('testing', 'functions', 'example', 'hello.js') ); expect(downloadFile).toHaveBeenCalledWith( 'https://example.com/hello.wav', - 'testing/assets/example/hello.wav' + join('testing', 'assets', 'example', 'hello.wav') ); expect(mkdir).toHaveBeenCalledTimes(2); - expect(mkdir).toHaveBeenCalledWith('testing/functions/example', { + expect(mkdir).toHaveBeenCalledWith(join('testing', 'functions', 'example'), { recursive: true, }); - expect(mkdir).toHaveBeenCalledWith('testing/assets/example', { + expect(mkdir).toHaveBeenCalledWith(join('testing', 'assets', 'example'), { recursive: true, }); }); @@ -228,23 +228,23 @@ test('installation with functions and assets and blank namespace', async () => { expect(downloadFile).toHaveBeenCalledTimes(4); expect(downloadFile).toHaveBeenCalledWith( 'https://example.com/.env', - 'testing/.env' + join('testing', '.env') ); expect(downloadFile).toHaveBeenCalledWith( 'https://example.com/hello.js', - 'testing/functions/hello.js' + join('testing', 'functions', 'hello.js') ); expect(downloadFile).toHaveBeenCalledWith( 'https://example.com/hello.wav', - 'testing/assets/hello.wav' + join('testing', 'assets', 'hello.wav') ); expect(downloadFile).toHaveBeenCalledWith( 'https://example.com/README.md', - 'testing/readmes/hello.md' + join('testing', 'readmes', 'hello.md') ); expect(mkdir).toHaveBeenCalledTimes(1); - expect(mkdir).toHaveBeenCalledWith('testing/readmes', { + expect(mkdir).toHaveBeenCalledWith(join('testing', 'readmes'), { recursive: true, }); }); @@ -275,6 +275,7 @@ test('installation with an empty dependency file', async () => { // buffer but depending on inputs `got` can actually return an object. // @ts-ignore mocked(got).mockImplementation(() => + //@ts-ignore Promise.resolve({ body: { dependencies: {} } }) ); @@ -308,7 +309,7 @@ test('installation with an empty dependency file', async () => { expect(downloadFile).toHaveBeenCalledTimes(1); expect(downloadFile).toHaveBeenCalledWith( 'https://example.com/.env', - 'testing/.env' + join('testing', '.env') ); expect(got).toHaveBeenCalledTimes(1); @@ -324,6 +325,7 @@ test('installation with a dependency file', async () => { // buffer but depending on inputs `got` can actually return an object. // @ts-ignore mocked(got).mockImplementation(() => + // @ts-ignore Promise.resolve({ body: { dependencies: { foo: '^1.0.0', got: '^6.9.0' } }, }) @@ -359,7 +361,7 @@ test('installation with a dependency file', async () => { expect(downloadFile).toHaveBeenCalledTimes(1); expect(downloadFile).toHaveBeenCalledWith( 'https://example.com/.env', - 'testing/.env' + join('testing', '.env') ); expect(got).toHaveBeenCalledTimes(1); @@ -382,6 +384,7 @@ test('installation with an existing dot-env file', async () => { // buffer but depending on inputs `got` can actually return an object. // @ts-ignore mocked(got).mockImplementation(() => + // @ts-ignore Promise.resolve({ body: 'HELLO=WORLD\n' }) ); @@ -410,7 +413,7 @@ test('installation with an existing dot-env file', async () => { expect(writeFile).toHaveBeenCalledTimes(1); expect(writeFile).toHaveBeenCalledWith( - 'testing/.env', + join('testing', '.env'), '# Comment\n' + 'FOO=BAR\n' + '\n\n' + @@ -429,7 +432,7 @@ test('installation with overlapping function files throws errors before writing' ); mocked(fileExists).mockImplementation(p => - Promise.resolve(p == 'functions/example/hello.js') + Promise.resolve(p == join('functions', 'example', 'hello.js')) ); await expect( @@ -468,7 +471,7 @@ test('installation with overlapping asset files throws errors before writing', a ); mocked(fileExists).mockImplementation(p => - Promise.resolve(p == 'assets/example/hello.wav') + Promise.resolve(p == join('assets', 'example', 'hello.wav')) ); await expect( @@ -493,7 +496,7 @@ test('installation with overlapping asset files throws errors before writing', a directory: '', }, ], - './', + join('.', path.sep), 'example', 'hello' ) @@ -540,7 +543,7 @@ test('installation with functions and assets in nested directories', async () => directory: '', }, ], - './testing/', + join('.', 'testing'), 'example', 'hello' ); @@ -548,29 +551,29 @@ test('installation with functions and assets in nested directories', async () => expect(downloadFile).toHaveBeenCalledTimes(4); expect(downloadFile).toHaveBeenCalledWith( 'https://example.com/.env', - 'testing/.env' + join('testing', '.env') ); expect(downloadFile).toHaveBeenCalledWith( 'https://example.com/hello.js', - 'testing/functions/example/admin/hello.js' + join('testing', 'functions', 'example', 'admin', 'hello.js') ); expect(downloadFile).toHaveBeenCalledWith( 'https://example.com/README.md', - 'testing/readmes/example/hello.md' + join('testing', 'readmes', 'example', 'hello.md') ); expect(downloadFile).toHaveBeenCalledWith( 'https://example.com/woohoo.jpg', - 'testing/assets/example/success/woohoo.jpg' + join('testing', 'assets', 'example', 'success', 'woohoo.jpg') ); expect(mkdir).toHaveBeenCalledTimes(3); - expect(mkdir).toHaveBeenCalledWith('testing/functions/example', { + expect(mkdir).toHaveBeenCalledWith(join('testing', 'functions', 'example'), { recursive: true, }); - expect(mkdir).toHaveBeenCalledWith('testing/readmes/example', { + expect(mkdir).toHaveBeenCalledWith(join('testing', 'readmes', 'example'), { recursive: true, }); - expect(mkdir).toHaveBeenCalledWith('testing/assets/example', { + expect(mkdir).toHaveBeenCalledWith(join('testing', 'assets', 'example'), { recursive: true, }); }); From 7bf53619b731265cd0b6ac174d9559f44c3e3230 Mon Sep 17 00:00:00 2001 From: Dominik Kundel Date: Fri, 22 May 2020 09:49:22 -0700 Subject: [PATCH 10/10] build(test): enable incremental builds --- tsconfig.test.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tsconfig.test.json b/tsconfig.test.json index 97a33096..05229ab8 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -1,4 +1,6 @@ { "extends": "./tsconfig.base.json", - "compilerOptions": {} + "compilerOptions": { + "incremental": true + } }