1
1
import { EnvironmentVariables } from '@twilio-labs/serverless-api' ;
2
2
import dotenv from 'dotenv' ;
3
3
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' ;
6
7
import { ExternalCliOptions , SharedFlags } from '../commands/shared' ;
7
8
import { CliInfo } from '../commands/types' ;
8
9
import { EnvironmentVariablesWithAuth } from '../types/generic' ;
9
10
import { fileExists } from '../utils/fs' ;
10
11
import { getDebugFunction , logger } from '../utils/logger' ;
11
12
import { readSpecializedConfig } from './global' ;
12
13
import { mergeFlagsAndConfig } from './utils/mergeFlagsAndConfig' ;
14
+ import { INgrokOptions } from 'ngrok' ;
15
+ import { parse } from 'yaml' ;
13
16
14
17
const debug = getDebugFunction ( 'twilio-run:cli:config' ) ;
15
18
16
- type NgrokConfig = {
17
- addr : string | number ;
18
- subdomain ?: string ;
19
- } ;
20
-
21
19
type InspectInfo = {
22
20
hostPort : string ;
23
21
break : boolean ;
@@ -47,6 +45,8 @@ export type StartCliFlags = Arguments<
47
45
env ?: string ;
48
46
port : string ;
49
47
ngrok ?: string | boolean ;
48
+ ngrokConfig ?: string ;
49
+ ngrokName ?: string ;
50
50
logs : boolean ;
51
51
detailedLogs : boolean ;
52
52
live : boolean ;
@@ -67,12 +67,73 @@ export async function getUrl(cli: StartCliFlags, port: string | number) {
67
67
let url = `http://localhost:${ port } ` ;
68
68
if ( typeof cli . ngrok !== 'undefined' ) {
69
69
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
+ }
71
116
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
+ }
73
136
}
74
-
75
- url = await require ( 'ngrok' ) . connect ( ngrokConfig ) ;
76
137
debug ( 'ngrok tunnel URL: %s' , url ) ;
77
138
}
78
139
0 commit comments