Skip to content

Commit 9ec13d5

Browse files
committed
feat(twilio-run:start): options for ngrok config and named tunnel
1 parent 0599b5f commit 9ec13d5

File tree

4 files changed

+86
-13
lines changed

4 files changed

+86
-13
lines changed

packages/twilio-run/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,9 @@ twilio-run --inspect
151151

152152
# Exposes the Twilio functions via ngrok to share them
153153
twilio-run --ngrok
154+
155+
# Uses a custom project ngrok config and named tunnel to expose the functions via ngrok
156+
twilio-run --ngrok --ngrok-config=./ngrok.yml --ngrok-name=example
154157
```
155158

156159
### `twilio-run deploy`
@@ -318,7 +321,7 @@ Error: What?
318321
at next (/Users/dkundel/dev/twilio-run/node_modules/express/lib/router/route.js:131:14)
319322
```
320323

321-
In general you'll want to use the cleaned-up stack trace since the internals might change throughout time.
324+
In general you'll want to use the cleaned-up stack trace since the internals might change throughout time.
322325

323326

324327

packages/twilio-run/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
"lodash.kebabcase": "^4.1.1",
5959
"lodash.startcase": "^4.4.0",
6060
"log-symbols": "^2.2.0",
61-
"ngrok": "^3.0.1",
61+
"ngrok": "^3.2.7",
6262
"nocache": "^2.1.0",
6363
"normalize.css": "^8.0.1",
6464
"ora": "^3.3.1",
@@ -70,6 +70,7 @@
7070
"type-fest": "^0.15.1",
7171
"window-size": "^1.1.1",
7272
"wrap-ansi": "^5.1.0",
73+
"yaml": "^1.10.0",
7374
"yargs": "^13.2.2"
7475
},
7576
"devDependencies": {

packages/twilio-run/src/commands/start.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,14 @@ export const cliInfo: CliInfo = {
140140
type: 'string',
141141
describe: 'Uses ngrok to create and outfacing url',
142142
},
143+
'ngrok-config': {
144+
type: 'string',
145+
describe: 'Path to custom ngrok config for project specific config.',
146+
},
147+
'ngrok-name': {
148+
type: 'string',
149+
describe: 'Name of ngrok tunnel config.',
150+
},
143151
logs: {
144152
type: 'boolean',
145153
default: true,

packages/twilio-run/src/config/start.ts

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
11
import { EnvironmentVariables } from '@twilio-labs/serverless-api';
22
import dotenv from 'dotenv';
33
import { readFileSync } from 'fs';
4-
import path, { resolve } from 'path';
5-
import { Arguments } from 'yargs';
4+
import path, { resolve, join } from 'path';
5+
import { homedir } from 'os';
6+
import { Arguments, config } from 'yargs';
67
import { ExternalCliOptions, SharedFlags } from '../commands/shared';
78
import { CliInfo } from '../commands/types';
89
import { EnvironmentVariablesWithAuth } from '../types/generic';
910
import { fileExists } from '../utils/fs';
1011
import { getDebugFunction, logger } from '../utils/logger';
1112
import { readSpecializedConfig } from './global';
1213
import { mergeFlagsAndConfig } from './utils/mergeFlagsAndConfig';
14+
import { INgrokOptions } from 'ngrok';
15+
import { parse } from 'yaml';
1316

1417
const debug = getDebugFunction('twilio-run:cli:config');
1518

16-
type NgrokConfig = {
17-
addr: string | number;
18-
subdomain?: string;
19-
};
20-
2119
type InspectInfo = {
2220
hostPort: string;
2321
break: boolean;
@@ -47,6 +45,8 @@ export type StartCliFlags = Arguments<
4745
env?: string;
4846
port: string;
4947
ngrok?: string | boolean;
48+
ngrokConfig?: string;
49+
ngrokName?: string;
5050
logs: boolean;
5151
detailedLogs: boolean;
5252
live: boolean;
@@ -67,12 +67,73 @@ export async function getUrl(cli: StartCliFlags, port: string | number) {
6767
let url = `http://localhost:${port}`;
6868
if (typeof cli.ngrok !== 'undefined') {
6969
debug('Starting ngrok tunnel');
70-
const ngrokConfig: NgrokConfig = { addr: port };
70+
// Setup default ngrok config, setting the protocol and the port number to
71+
// forward to.
72+
const defaultConfig: INgrokOptions = { addr: port, proto: 'http' };
73+
let tunnelConfig = defaultConfig;
74+
let ngrokConfig;
75+
if (typeof cli.ngrokConfig === 'string') {
76+
// If we set a config path then try to load that config. If the config
77+
// fails to load then we'll try to load the default config instead.
78+
const configPath = join(process.cwd(), cli.ngrokConfig);
79+
try {
80+
ngrokConfig = parse(readFileSync(configPath, 'utf-8'));
81+
} catch (err) {
82+
logger.warn(`Could not find ngrok config file at ${configPath}`);
83+
}
84+
}
85+
if (!ngrokConfig) {
86+
// Try to load default config. If there is no default config file, set
87+
// `ngrokConfig` to be an empty object.
88+
const configPath = join(homedir(), '.ngrok2', 'ngrok.yml');
89+
try {
90+
ngrokConfig = parse(readFileSync(configPath, 'utf-8'));
91+
} catch (err) {
92+
ngrokConfig = {};
93+
}
94+
}
95+
if (
96+
typeof cli.ngrokName === 'string' &&
97+
typeof ngrokConfig.tunnels === 'object'
98+
) {
99+
// If we've asked for a named ngrok tunnel and there are available tunnels
100+
// in the config, then set the `tunnelConfig` to the options from the
101+
// config, overriding the addr and proto to the defaults.
102+
tunnelConfig = { ...ngrokConfig.tunnels[cli.ngrokName], ...tunnelConfig };
103+
if (!tunnelConfig) {
104+
// If the config does not include the named tunnel, then set it back to
105+
// the default options.
106+
logger.warn(
107+
`Could not find config for named tunnel "${cli.ngrokName}". Falling back to other options.`
108+
);
109+
tunnelConfig = defaultConfig;
110+
}
111+
}
112+
if (typeof ngrokConfig.authtoken === 'string') {
113+
// If there is an authtoken in the config, add it to the tunnel config.
114+
tunnelConfig.authToken = ngrokConfig.authtoken;
115+
}
71116
if (typeof cli.ngrok === 'string' && cli.ngrok.length > 0) {
72-
ngrokConfig.subdomain = cli.ngrok;
117+
// If we've asked for a custom subdomain, override the tunnel config with
118+
// it.
119+
tunnelConfig.subdomain = cli.ngrok;
120+
}
121+
const ngrok = require('ngrok');
122+
try {
123+
// Try to open the ngrok tunnel.
124+
url = await ngrok.connect(tunnelConfig);
125+
} catch (error) {
126+
// If it fails, it is likely to be because the tunnel config we pass is
127+
// not allowed (e.g. using a custom subdomain without an authtoken). The
128+
// error message from ngrok itself should describe the issue.
129+
logger.warn(error.message);
130+
if (
131+
typeof error.details !== 'undefined' &&
132+
typeof error.details.err !== 'undefined'
133+
) {
134+
logger.warn(error.details.err);
135+
}
73136
}
74-
75-
url = await require('ngrok').connect(ngrokConfig);
76137
debug('ngrok tunnel URL: %s', url);
77138
}
78139

0 commit comments

Comments
 (0)