Skip to content

Commit d549fbb

Browse files
committed
feat(twilio-run:start): options for ngrok config and named tunnel
1 parent 064f8a5 commit d549fbb

File tree

4 files changed

+83
-13
lines changed

4 files changed

+83
-13
lines changed

packages/twilio-run/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,8 @@ twilio-run --inspect
152152
# Exposes the Twilio functions via ngrok to share them
153153
twilio-run --ngrok
154154

155-
# Exposes the Twilio functions via ngrok using a custom subdomain (requires a paid-for ngrok account)
156-
twilio-run --ngrok=subdomain
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
157157
```
158158

159159
### `twilio-run deploy`

packages/twilio-run/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"type-fest": "^0.15.1",
7474
"window-size": "^1.1.1",
7575
"wrap-ansi": "^5.1.0",
76+
"yaml": "^1.10.0",
7677
"yargs": "^13.2.2"
7778
},
7879
"optionalDependencies": {

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,14 @@ export const cliInfo: CliInfo = {
159159
describe:
160160
'Uses ngrok to create a public url. Pass a string to set the subdomain (requires a paid-for ngrok account).',
161161
},
162+
'ngrok-config': {
163+
type: 'string',
164+
describe: 'Path to custom ngrok config for project specific config.',
165+
},
166+
'ngrok-name': {
167+
type: 'string',
168+
describe: 'Name of ngrok tunnel config.',
169+
},
162170
logs: {
163171
type: 'boolean',
164172
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)