From 55621f2e9cf3e0100ca13c94032b0a38ebb8a250 Mon Sep 17 00:00:00 2001 From: Phil Nash Date: Mon, 24 Aug 2020 13:02:58 +1000 Subject: [PATCH 1/5] feat(twilio-run:start): options for ngrok config and named tunnel --- packages/twilio-run/README.md | 4 +- packages/twilio-run/package.json | 1 + packages/twilio-run/src/commands/start.ts | 8 +++ packages/twilio-run/src/config/start.ts | 83 ++++++++++++++++++++--- 4 files changed, 83 insertions(+), 13 deletions(-) diff --git a/packages/twilio-run/README.md b/packages/twilio-run/README.md index 67b476d2..ce820ded 100644 --- a/packages/twilio-run/README.md +++ b/packages/twilio-run/README.md @@ -152,8 +152,8 @@ twilio-run --inspect # Exposes the Twilio functions via ngrok to share them twilio-run --ngrok -# Exposes the Twilio functions via ngrok using a custom subdomain (requires a paid-for ngrok account) -twilio-run --ngrok=subdomain +# Uses a custom project ngrok config and named tunnel to expose the functions via ngrok +twilio-run --ngrok --ngrok-config=./ngrok.yml --ngrok-name=example ``` ### `twilio-run deploy` diff --git a/packages/twilio-run/package.json b/packages/twilio-run/package.json index b37afe33..0c4d3f57 100644 --- a/packages/twilio-run/package.json +++ b/packages/twilio-run/package.json @@ -73,6 +73,7 @@ "type-fest": "^0.15.1", "window-size": "^1.1.1", "wrap-ansi": "^5.1.0", + "yaml": "^1.10.0", "yargs": "^13.2.2" }, "optionalDependencies": { diff --git a/packages/twilio-run/src/commands/start.ts b/packages/twilio-run/src/commands/start.ts index eead014d..da3a8839 100644 --- a/packages/twilio-run/src/commands/start.ts +++ b/packages/twilio-run/src/commands/start.ts @@ -159,6 +159,14 @@ export const cliInfo: CliInfo = { describe: 'Uses ngrok to create a public url. Pass a string to set the subdomain (requires a paid-for ngrok account).', }, + 'ngrok-config': { + type: 'string', + describe: 'Path to custom ngrok config for project specific config.', + }, + 'ngrok-name': { + type: 'string', + describe: 'Name of ngrok tunnel config.', + }, logs: { type: 'boolean', default: true, diff --git a/packages/twilio-run/src/config/start.ts b/packages/twilio-run/src/config/start.ts index e3c96331..9b8e4c52 100644 --- a/packages/twilio-run/src/config/start.ts +++ b/packages/twilio-run/src/config/start.ts @@ -1,8 +1,9 @@ import { EnvironmentVariables } from '@twilio-labs/serverless-api'; import dotenv from 'dotenv'; import { readFileSync } from 'fs'; -import path, { resolve } from 'path'; -import { Arguments } from 'yargs'; +import path, { resolve, join } from 'path'; +import { homedir } from 'os'; +import { Arguments, config } from 'yargs'; import { ExternalCliOptions, SharedFlags } from '../commands/shared'; import { CliInfo } from '../commands/types'; import { EnvironmentVariablesWithAuth } from '../types/generic'; @@ -10,14 +11,11 @@ import { fileExists } from '../utils/fs'; import { getDebugFunction, logger } from '../utils/logger'; import { readSpecializedConfig } from './global'; import { mergeFlagsAndConfig } from './utils/mergeFlagsAndConfig'; +import { INgrokOptions } from 'ngrok'; +import { parse } from 'yaml'; const debug = getDebugFunction('twilio-run:cli:config'); -type NgrokConfig = { - addr: string | number; - subdomain?: string; -}; - type InspectInfo = { hostPort: string; break: boolean; @@ -47,6 +45,8 @@ export type StartCliFlags = Arguments< env?: string; port: string; ngrok?: string | boolean; + ngrokConfig?: string; + ngrokName?: string; logs: boolean; detailedLogs: boolean; live: boolean; @@ -67,12 +67,73 @@ export async function getUrl(cli: StartCliFlags, port: string | number) { let url = `http://localhost:${port}`; if (typeof cli.ngrok !== 'undefined') { debug('Starting ngrok tunnel'); - const ngrokConfig: NgrokConfig = { addr: port }; + // Setup default ngrok config, setting the protocol and the port number to + // forward to. + const defaultConfig: INgrokOptions = { addr: port, proto: 'http' }; + let tunnelConfig = defaultConfig; + let ngrokConfig; + if (typeof cli.ngrokConfig === 'string') { + // If we set a config path then try to load that config. If the config + // fails to load then we'll try to load the default config instead. + const configPath = join(process.cwd(), cli.ngrokConfig); + try { + ngrokConfig = parse(readFileSync(configPath, 'utf-8')); + } catch (err) { + logger.warn(`Could not find ngrok config file at ${configPath}`); + } + } + if (!ngrokConfig) { + // Try to load default config. If there is no default config file, set + // `ngrokConfig` to be an empty object. + const configPath = join(homedir(), '.ngrok2', 'ngrok.yml'); + try { + ngrokConfig = parse(readFileSync(configPath, 'utf-8')); + } catch (err) { + ngrokConfig = {}; + } + } + if ( + typeof cli.ngrokName === 'string' && + typeof ngrokConfig.tunnels === 'object' + ) { + // If we've asked for a named ngrok tunnel and there are available tunnels + // in the config, then set the `tunnelConfig` to the options from the + // config, overriding the addr and proto to the defaults. + tunnelConfig = { ...ngrokConfig.tunnels[cli.ngrokName], ...tunnelConfig }; + if (!tunnelConfig) { + // If the config does not include the named tunnel, then set it back to + // the default options. + logger.warn( + `Could not find config for named tunnel "${cli.ngrokName}". Falling back to other options.` + ); + tunnelConfig = defaultConfig; + } + } + if (typeof ngrokConfig.authtoken === 'string') { + // If there is an authtoken in the config, add it to the tunnel config. + tunnelConfig.authToken = ngrokConfig.authtoken; + } if (typeof cli.ngrok === 'string' && cli.ngrok.length > 0) { - ngrokConfig.subdomain = cli.ngrok; + // If we've asked for a custom subdomain, override the tunnel config with + // it. + tunnelConfig.subdomain = cli.ngrok; + } + const ngrok = require('ngrok'); + try { + // Try to open the ngrok tunnel. + url = await ngrok.connect(tunnelConfig); + } catch (error) { + // If it fails, it is likely to be because the tunnel config we pass is + // not allowed (e.g. using a custom subdomain without an authtoken). The + // error message from ngrok itself should describe the issue. + logger.warn(error.message); + if ( + typeof error.details !== 'undefined' && + typeof error.details.err !== 'undefined' + ) { + logger.warn(error.details.err); + } } - - url = await require('ngrok').connect(ngrokConfig); debug('ngrok tunnel URL: %s', url); } From 133afb5c2c7075c51f60f054912d5114fe4f8b43 Mon Sep 17 00:00:00 2001 From: Phil Nash Date: Mon, 31 Aug 2020 10:50:35 +1000 Subject: [PATCH 2/5] fix(twilio-run:start): makes readfile async, uses cli.cwd --- packages/twilio-run/src/config/start.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/twilio-run/src/config/start.ts b/packages/twilio-run/src/config/start.ts index 9b8e4c52..4737d073 100644 --- a/packages/twilio-run/src/config/start.ts +++ b/packages/twilio-run/src/config/start.ts @@ -1,9 +1,11 @@ import { EnvironmentVariables } from '@twilio-labs/serverless-api'; import dotenv from 'dotenv'; -import { readFileSync } from 'fs'; +import { readFile } from 'fs'; +import { promisify } from 'util'; +const readFilePromise = promisify(readFile); import path, { resolve, join } from 'path'; import { homedir } from 'os'; -import { Arguments, config } from 'yargs'; +import { Arguments } from 'yargs'; import { ExternalCliOptions, SharedFlags } from '../commands/shared'; import { CliInfo } from '../commands/types'; import { EnvironmentVariablesWithAuth } from '../types/generic'; @@ -75,9 +77,9 @@ export async function getUrl(cli: StartCliFlags, port: string | number) { if (typeof cli.ngrokConfig === 'string') { // If we set a config path then try to load that config. If the config // fails to load then we'll try to load the default config instead. - const configPath = join(process.cwd(), cli.ngrokConfig); + const configPath = join(cli.cwd || process.cwd(), cli.ngrokConfig); try { - ngrokConfig = parse(readFileSync(configPath, 'utf-8')); + ngrokConfig = parse(await readFilePromise(configPath, 'utf-8')); } catch (err) { logger.warn(`Could not find ngrok config file at ${configPath}`); } @@ -87,7 +89,7 @@ export async function getUrl(cli: StartCliFlags, port: string | number) { // `ngrokConfig` to be an empty object. const configPath = join(homedir(), '.ngrok2', 'ngrok.yml'); try { - ngrokConfig = parse(readFileSync(configPath, 'utf-8')); + ngrokConfig = parse(await readFilePromise(configPath, 'utf-8')); } catch (err) { ngrokConfig = {}; } @@ -167,7 +169,7 @@ export async function getEnvironment( if (await fileExists(fullEnvPath)) { try { debug(`Read .env file at "%s"`, fullEnvPath); - const envContent = readFileSync(fullEnvPath, 'utf8'); + const envContent = await readFilePromise(fullEnvPath, 'utf8'); const envValues = dotenv.parse(envContent); for (const [key, val] of Object.entries(envValues)) { env[key] = val; From d662be8d26dd3668ae3462bc2452c50f88e8ab14 Mon Sep 17 00:00:00 2001 From: Phil Nash Date: Mon, 31 Aug 2020 11:11:26 +1000 Subject: [PATCH 3/5] docs(twilio-run): adds docs for ngrok config options --- packages/twilio-run/README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/twilio-run/README.md b/packages/twilio-run/README.md index ce820ded..b062e650 100644 --- a/packages/twilio-run/README.md +++ b/packages/twilio-run/README.md @@ -156,6 +156,28 @@ twilio-run --ngrok twilio-run --ngrok --ngrok-config=./ngrok.yml --ngrok-name=example ``` +#### ngrok + +`twilio-run` lets you open a tunnel using [ngrok](https://ngrok.com/) (see the examples above). By default, just setting the `--ngrok` flag will use a randomly generated subdomain. If you have a paid for ngrok account, you can customise this experience. + +##### Custom ngrok subdomain + +Run the following command: + +``` +twilio-run --ngrok=subdomain +``` + +This will start the `twilio-run` server and also tunnel to it over ngrok via the domain `subdomain.ngrok.io`. + +##### Custom ngrok config + +You can create an [ngrok config file](https://ngrok.com/docs#config-location) which allows you to name tunnels and include other settings for those named tunnels, such as the subdomain, auth, or host headers. + +You can choose to run a named tunnel with `twilio-run` by passing the `--ngrok-name` flag. This will find the named tunnel in your default config, over-ride the `proto` to http and the `addr` to the port your Twilio Functions are running on, otherwise keeping the rest of the settings. + +You can also choose to use a different config file by setting the `--ngrok-config` flag. + ### `twilio-run deploy` Deploys your project to Twilio. It will read dependencies automatically from your `package.json`'s `dependencies` field and install them. It will also upload and set the variables that are specified in your `.env` file. You can point it against a different `.env` file via command-line flags. From 55bd4b48d0b6ef1c0651993620d33ca23dabb155 Mon Sep 17 00:00:00 2001 From: Phil Nash Date: Fri, 16 Apr 2021 10:36:17 +1000 Subject: [PATCH 4/5] chore(twilio-run): updates to the latest ngrok version --- packages/twilio-run/package.json | 2 +- packages/twilio-run/src/config/start.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/twilio-run/package.json b/packages/twilio-run/package.json index 0c4d3f57..e646f164 100644 --- a/packages/twilio-run/package.json +++ b/packages/twilio-run/package.json @@ -77,7 +77,7 @@ "yargs": "^13.2.2" }, "optionalDependencies": { - "ngrok": "^3.3.0" + "ngrok": "^4.0.1" }, "devDependencies": { "@types/cheerio": "^0.22.12", diff --git a/packages/twilio-run/src/config/start.ts b/packages/twilio-run/src/config/start.ts index 4737d073..ac841e91 100644 --- a/packages/twilio-run/src/config/start.ts +++ b/packages/twilio-run/src/config/start.ts @@ -13,7 +13,7 @@ import { fileExists } from '../utils/fs'; import { getDebugFunction, logger } from '../utils/logger'; import { readSpecializedConfig } from './global'; import { mergeFlagsAndConfig } from './utils/mergeFlagsAndConfig'; -import { INgrokOptions } from 'ngrok'; +import { Ngrok } from 'ngrok'; import { parse } from 'yaml'; const debug = getDebugFunction('twilio-run:cli:config'); @@ -71,7 +71,7 @@ export async function getUrl(cli: StartCliFlags, port: string | number) { debug('Starting ngrok tunnel'); // Setup default ngrok config, setting the protocol and the port number to // forward to. - const defaultConfig: INgrokOptions = { addr: port, proto: 'http' }; + const defaultConfig: Ngrok.Options = { addr: port, proto: 'http' }; let tunnelConfig = defaultConfig; let ngrokConfig; if (typeof cli.ngrokConfig === 'string') { From a5985c02d55f2f8c9e59fd83608f910f93362683 Mon Sep 17 00:00:00 2001 From: Phil Nash Date: Fri, 16 Apr 2021 11:14:45 +1000 Subject: [PATCH 5/5] chore(deps): updates twilio in twilio-run and serverless-runtime-types --- packages/serverless-runtime-types/package.json | 2 +- packages/twilio-run/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/serverless-runtime-types/package.json b/packages/serverless-runtime-types/package.json index 0eae7047..fb453de2 100644 --- a/packages/serverless-runtime-types/package.json +++ b/packages/serverless-runtime-types/package.json @@ -26,7 +26,7 @@ "url": "https://github.com/twilio-labs/serverless-toolkit/issues" }, "dependencies": { - "twilio": "^3.33.0" + "twilio": "^3.60.0" }, "devDependencies": { "@types/express": "^4.17.11", diff --git a/packages/twilio-run/package.json b/packages/twilio-run/package.json index e646f164..76c3e933 100644 --- a/packages/twilio-run/package.json +++ b/packages/twilio-run/package.json @@ -69,7 +69,7 @@ "serialize-error": "^7.0.1", "terminal-link": "^1.3.0", "title": "^3.4.1", - "twilio": "^3.43.1", + "twilio": "^3.60.0", "type-fest": "^0.15.1", "window-size": "^1.1.1", "wrap-ansi": "^5.1.0",