@@ -3,14 +3,15 @@ import yargs, { Options } from "yargs";
33import {
44 DefaultFlavor ,
55 FilecoinFlavorName ,
6- DefaultOptionsByName
6+ DefaultOptionsByName ,
7+ FlavorName
78} from "@ganache/flavors" ;
89import {
910 Base ,
1011 Definitions ,
1112 YargsPrimitiveCliTypeStrings
1213} from "@ganache/options" ;
13- import { Command , Argv } from "./types" ;
14+ import { FlavorCommand , StartArgs , GanacheArgs } from "./types" ;
1415import chalk from "chalk" ;
1516import { EOL } from "os" ;
1617import marked from "marked" ;
@@ -86,9 +87,11 @@ function processOption(
8687 // the types held within each array
8788 const { cliType } = optionObj ;
8889 const array = cliType && cliType . startsWith ( "array:" ) ; // e.g. array:string or array:number
89- const type = ( array
90- ? cliType . slice ( 6 ) // remove the "array:" part
91- : cliType ) as YargsPrimitiveCliTypeStrings ;
90+ const type = (
91+ array
92+ ? cliType . slice ( 6 ) // remove the "array:" part
93+ : cliType
94+ ) as YargsPrimitiveCliTypeStrings ;
9295
9396 const options : Options = {
9497 group,
@@ -127,9 +130,9 @@ function applyDefaults(
127130 const group = `${ category [ 0 ] . toUpperCase ( ) } ${ category . slice (
128131 1
129132 ) } :` as GroupType ;
130- const categoryObj = ( flavorDefaults [
133+ const categoryObj = flavorDefaults [
131134 category
132- ] as unknown ) as Definitions < Base . Config > ;
135+ ] as unknown as Definitions < Base . Config > ;
133136 const state = { } ;
134137 for ( const option in categoryObj ) {
135138 const optionObj = categoryObj [ option ] ;
@@ -146,13 +149,18 @@ function applyDefaults(
146149 }
147150}
148151
149- export default function ( version : string , isDocker : boolean ) {
152+ export default function (
153+ version : string ,
154+ isDocker : boolean ,
155+ rawArgs = process . argv . slice ( 2 )
156+ ) {
150157 const versionUsageOutputText = chalk `{hex("${
151158 TruffleColors . porsche
152159 } ").bold ${ center ( version ) } }`;
153- let args = yargs
154- // disable dot-notation because yargs just can't coerce args properly...
155- // ...on purpose! https://github.com/yargs/yargs/issues/1021#issuecomment-352324693
160+
161+ // disable dot-notation because yargs just can't coerce args properly...
162+ // ...on purpose! https://github.com/yargs/yargs/issues/1021#issuecomment-352324693
163+ yargs
156164 . parserConfiguration ( { "dot-notation" : false } )
157165 . strict ( )
158166 . usage ( versionUsageOutputText )
@@ -168,7 +176,7 @@ export default function (version: string, isDocker: boolean) {
168176 let flavor : keyof typeof DefaultOptionsByName ;
169177 for ( flavor in DefaultOptionsByName ) {
170178 const flavorDefaults = DefaultOptionsByName [ flavor ] ;
171- let command : Command ;
179+ let command : FlavorCommand ;
172180 let defaultPort : number ;
173181 switch ( flavor ) {
174182 // since "ethereum" is the DefaultFlavor we don't need a `case` for it
@@ -185,7 +193,7 @@ export default function (version: string, isDocker: boolean) {
185193 defaultPort = 8545 ;
186194 }
187195
188- args = args . command (
196+ yargs . command (
189197 command ,
190198 chalk `Use the {bold ${ flavor } } flavor of Ganache` ,
191199 flavorArgs => {
@@ -219,32 +227,116 @@ export default function (version: string, isDocker: boolean) {
219227 }
220228
221229 return true ;
230+ } )
231+ . option ( "detach" , {
232+ description : highlight (
233+ "Run Ganache in detached (daemon) mode." +
234+ EOL +
235+ "See `ganache instances --help` for information on managing detached instances."
236+ ) ,
237+ type : "boolean" ,
238+ alias : [ "D" , "😈" ]
222239 } ) ;
240+ } ,
241+ parsedArgs => {
242+ parsedArgs . action = parsedArgs . detach ? "start-detached" : "start" ;
223243 }
224244 ) ;
225245 }
226246
227- args = args
247+ yargs
248+ . command (
249+ "instances" ,
250+ highlight (
251+ "Manage instances of Ganache running in detached mode." +
252+ EOL +
253+ "(Ganache can be run in detached mode by providing the `--detach` flag)"
254+ ) ,
255+ _yargs => {
256+ _yargs
257+ . command (
258+ "list" ,
259+ "List instances running in detached mode" ,
260+ _ => { } ,
261+ listArgs => {
262+ listArgs . action = "list" ;
263+ }
264+ )
265+ . command (
266+ "stop <name>" ,
267+ "Stop the instance specified by <name>" ,
268+ stopArgs => {
269+ stopArgs . positional ( "name" , { type : "string" } ) ;
270+ } ,
271+ stopArgs => {
272+ stopArgs . action = "stop" ;
273+ }
274+ )
275+ . version ( false ) ;
276+ }
277+ )
228278 . showHelpOnFail ( false , "Specify -? or --help for available options" )
229279 . alias ( "help" , "?" )
230280 . wrap ( wrapWidth )
231281 . version ( version ) ;
232282
233- const parsedArgs = args . argv ;
234- const finalArgs = {
235- flavor : parsedArgs . _ . length > 0 ? parsedArgs . _ [ 0 ] : DefaultFlavor
236- } as Argv ;
237- for ( let key in parsedArgs ) {
238- // split on the first "."
239- const [ group , option ] = key . split ( / \. ( .+ ) / ) ;
240- // only copy namespaced/group keys
241- if ( option ) {
242- if ( ! finalArgs [ group ] ) {
243- finalArgs [ group ] = { } ;
283+ const parsedArgs = yargs . parse ( rawArgs ) ;
284+
285+ let finalArgs : GanacheArgs ;
286+ if ( parsedArgs . action === "stop" ) {
287+ finalArgs = {
288+ action : "stop" ,
289+ name : parsedArgs . name as string
290+ } ;
291+ } else if ( parsedArgs . action === "list" ) {
292+ finalArgs = { action : "list" } ;
293+ } else if (
294+ parsedArgs . action === "start" ||
295+ parsedArgs . action === "start-detached"
296+ ) {
297+ const action = parsedArgs . action ;
298+ const flavor = ( parsedArgs . _ . length > 0
299+ ? parsedArgs . _ [ 0 ]
300+ : DefaultFlavor ) as any as FlavorName ;
301+
302+ finalArgs = {
303+ flavor,
304+ action,
305+ ...( expandArgs ( parsedArgs ) as Omit <
306+ StartArgs < FlavorName > ,
307+ "flavor" | "action"
308+ > )
309+ } ;
310+ } else {
311+ throw new Error ( `Unknown action: ${ parsedArgs . action } ` ) ;
312+ }
313+
314+ return finalArgs ;
315+ }
316+
317+ /**
318+ * Expands the arguments into an object including only namespaced keys from the
319+ * `args` argument.
320+ * @param {object } args to be expanded
321+ * @returns {object } with the expanded arguments
322+ */
323+ export function expandArgs ( args : object ) : object {
324+ const namespacedArgs = { } ;
325+
326+ for ( const key in args ) {
327+ // ignore keys that are kebab-cased - they will be duplicated as camelCase
328+ if ( key . indexOf ( "-" ) === - 1 ) {
329+ // split on the first "."
330+ const [ namespace , option ] = key . split ( / \. ( .+ ) / ) ;
331+ // only copy namespaced/group keys, and ignore keys with kebab cases
332+ if ( option ) {
333+ if ( ! namespacedArgs [ namespace ] ) {
334+ namespacedArgs [ namespace ] = { } ;
335+ }
336+ namespacedArgs [ namespace ] [ option ] = args [ key ] ;
244337 }
245- finalArgs [ group ] [ option ] = parsedArgs [ key ] ;
246338 }
247339 }
248340
249- return finalArgs ;
341+ return namespacedArgs ;
250342}
0 commit comments