1
1
#! /usr/bin/env node
2
- const { join, resolve} = require ( 'path' ) ;
2
+ const { Command} = require ( 'commander' )
3
+ const { join, resolve} = require ( 'path' )
3
4
4
5
const ExitCode = {
5
6
MissingArgument : 2 ,
@@ -26,14 +27,17 @@ const ExitCode = {
26
27
const resolveRelativeAndRequirePaths = ( moduleNameOrPath ) => [
27
28
resolve ( moduleNameOrPath ) ,
28
29
...require . resolve . paths ( moduleNameOrPath ) . map ( dir => join ( dir , moduleNameOrPath ) )
29
- ] ;
30
+ ]
30
31
31
32
/**
32
33
* Attempts to require the items in `possiblePaths` in order
33
34
* and check for the presence of an exported `run` function.
34
35
* The first module found is returned.
35
36
*
36
37
* @param {string[] } possiblePaths
38
+ * @param {Options } opts the options from `parseArguments`
39
+ * @param {NodeRequire } [_require] the require to use for --register option,
40
+ * by default the regular `require` is used.
37
41
* @returns {RunnableModule }
38
42
*
39
43
* @throws {
@@ -45,43 +49,91 @@ const resolveRelativeAndRequirePaths = (moduleNameOrPath) => [
45
49
*
46
50
* @see resolveRelativeAndRequirePaths
47
51
*/
48
- const requireRunnable = ( possiblePaths ) => {
49
- const errors = [ ] ;
50
- let exitCode = ExitCode . ModuleNotFound ;
52
+ const requireRunnable = (
53
+ possiblePaths , opts , _require = require
54
+ ) => {
55
+ for ( const hook of opts . require ) {
56
+ _require ( hook )
57
+ }
58
+
59
+ const errors = [ ]
60
+ let exitCode = ExitCode . ModuleNotFound
51
61
for ( const candidate of possiblePaths ) {
52
62
try {
53
- const required = require ( candidate ) ;
63
+ const required = _require ( candidate )
54
64
if ( typeof required . run !== 'function' ) {
55
- errors . push ( `'${ candidate } ' is a module but has no export named 'run'` ) ;
56
- exitCode = ExitCode . InvalidModuleExport ;
57
- continue ;
65
+ errors . push ( `'${ candidate } ' is a module but has no export named 'run'` )
66
+ exitCode = ExitCode . InvalidModuleExport
67
+ continue
58
68
}
59
- return required ;
69
+ return required
60
70
} catch ( err ) {
61
- errors . push ( err . message ) ;
71
+ errors . push ( err . message )
62
72
}
63
73
}
64
- console . error ( 'No runnable module found:' ) ;
65
- errors . forEach ( err => console . error ( err ) ) ;
66
- process . exit ( exitCode ) ;
67
- } ;
74
+ console . error ( 'No runnable module found:' )
75
+ errors . forEach ( err => console . error ( err ) )
76
+ process . exit ( exitCode )
77
+ }
78
+
79
+ /**
80
+ * Available CLI options for runex.
81
+ *
82
+ * Usage information: `npx runex -h|--help`
83
+ *
84
+ * @typedef {{
85
+ * require: string[]
86
+ * }} Options
87
+ */
88
+
89
+ /**
90
+ * Collects all distinct values, order is not persisted
91
+ *
92
+ * @param {string } value
93
+ * @param {string[] } prev
94
+ * @returns {string[] }
95
+ */
96
+ const collectDistinct = ( value , prev ) => [ ...new Set ( prev ) . add ( value ) . values ( ) ]
97
+
98
+ /**
99
+ *
100
+ * @param {Command } commander
101
+ * @param {number } code
102
+ * @returns {Function<never> }
103
+ */
104
+ const exitWithUsage = ( commander , code ) => ( ) => {
105
+ commander . outputHelp ( )
106
+ process . exit ( code )
107
+ }
68
108
69
109
/**
70
110
* Parses a list of commend line arguments.
71
111
*
72
112
* If you are invoking it make sure to slice/remove anything that's not relevant for `runex`.
73
113
*
74
114
* @param {string[] } argv the relevant part of `process.argv`
75
- * @returns {{args: string[], moduleNameOrPath: string} }
115
+ * @returns {{args: string[], moduleNameOrPath: string, opts: Options } }
76
116
*
77
117
* @throws {ExitCode.MissingArgument } (exits) in case missing argument for module
78
118
*/
79
- const parseArguments = ( [ moduleNameOrPath , ...args ] ) => {
119
+ const parseArguments = ( argv ) => {
120
+ const commander = new Command ( '[npx] runex' ) ;
121
+ const exitOnMissingArgument = exitWithUsage ( commander , ExitCode . MissingArgument )
122
+ commander . usage ( '[options] runnable [args]' )
123
+ . option (
124
+ '-r, --require <module>' , '0..n modules for node to require' , collectDistinct , [ ]
125
+ )
126
+ . exitOverride ( exitOnMissingArgument )
127
+ /** @see https://github.com/tj/commander.js/issues/512 */
128
+ . parse ( [ null , '' , ...argv ] )
129
+ const opts = commander . opts ( ) ;
130
+ const [ moduleNameOrPath , ...args ] = commander . args
131
+
80
132
if ( moduleNameOrPath === undefined ) {
81
- console . error ( 'Missing argument: You need to specify the module to run' ) ;
82
- process . exit ( ExitCode . MissingArgument ) ;
133
+ console . error ( 'Missing argument: You need to specify the module to run.' )
134
+ exitOnMissingArgument ( ) ;
83
135
}
84
- return { moduleNameOrPath, args } ;
136
+ return { args , moduleNameOrPath, opts }
85
137
}
86
138
87
139
/**
@@ -91,31 +143,40 @@ const parseArguments = ([moduleNameOrPath, ...args]) => {
91
143
* if you pass a your own value, you have to take care of it.
92
144
*
93
145
* @param {RunnableModule } runnable the module to "execute"
94
- * @param {{args: any[]} } [runArgs] the arguments to pass to `runnable.run`,
146
+ * @param {{args: any[], opts: Options } } [runArgs] the arguments to pass to `runnable.run`,
95
147
* by default they are parsed from `process.argv`
96
148
*
97
149
* @see parseArguments
98
150
*/
99
- const run = ( runnable , { args} = parseArguments ( process . argv . slice ( 2 ) ) ) => {
151
+ const run = (
152
+ runnable , { args} = parseArguments ( process . argv . slice ( 2 ) )
153
+ ) => {
100
154
return new Promise ( resolve => {
101
155
resolve ( runnable . run ( ...args ) )
102
156
} ) . catch ( err => {
103
- console . error ( err ) ;
104
- process . exit ( ExitCode . ExportThrows ) ;
105
- } ) ;
157
+ console . error ( err )
158
+ process . exit ( ExitCode . ExportThrows )
159
+ } )
106
160
}
107
161
108
162
if ( require . main === module ) {
109
- const { moduleNameOrPath, args} = parseArguments ( process . argv . slice ( 2 ) ) ;
110
- run ( requireRunnable ( resolveRelativeAndRequirePaths ( moduleNameOrPath ) ) , { args} )
163
+ const p = parseArguments ( process . argv . slice ( 2 ) )
164
+ const runnable = requireRunnable (
165
+ resolveRelativeAndRequirePaths ( p . moduleNameOrPath ) ,
166
+ p . opts
167
+ )
168
+ run ( runnable , p )
111
169
. then ( value => {
112
- if ( value ) console . log ( value ) ;
113
- } ) ;
170
+ if ( value !== undefined ) console . log ( value )
171
+ } )
114
172
} else {
115
173
module . exports = {
174
+ collectDistinct,
116
175
ExitCode,
176
+ exitWithUsage,
117
177
parseArguments,
118
- resolveModule : requireRunnable ,
178
+ requireRunnable,
179
+ resolveRelativeAndRequirePaths,
119
180
run
120
181
}
121
182
}
0 commit comments