11import { EnvironmentVariables } from '@twilio-labs/serverless-api' ;
22import dotenv from 'dotenv' ;
33import { 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' ;
67import { ExternalCliOptions , SharedFlags } from '../commands/shared' ;
78import { CliInfo } from '../commands/types' ;
89import { EnvironmentVariablesWithAuth } from '../types/generic' ;
910import { fileExists } from '../utils/fs' ;
1011import { getDebugFunction , logger } from '../utils/logger' ;
1112import { readSpecializedConfig } from './global' ;
1213import { mergeFlagsAndConfig } from './utils/mergeFlagsAndConfig' ;
14+ import { INgrokOptions } from 'ngrok' ;
15+ import { parse } from 'yaml' ;
1316
1417const debug = getDebugFunction ( 'twilio-run:cli:config' ) ;
1518
16- type NgrokConfig = {
17- addr : string | number ;
18- subdomain ?: string ;
19- } ;
20-
2119type 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