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/.npmrc b/.npmrc index e69de29b..9cf94950 100644 --- a/.npmrc +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false \ No newline at end of file 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 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 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({}); }); }); 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, }); }); diff --git a/package.json b/package.json index 8919c0c3..4fa24012 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", @@ -74,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", @@ -104,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", @@ -114,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/", 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, } ); 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": [ 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 + } }