@@ -3,14 +3,15 @@ import {
3
3
CommanderError ,
4
4
createCommand ,
5
5
InvalidArgumentError ,
6
+ Option ,
6
7
} from '@commander-js/extra-typings' ;
7
8
import { readFile , writeFile } from 'fs/promises' ;
8
9
import { MermaidChart } from '@mermaidchart/sdk' ;
9
10
10
11
import confirm from '@inquirer/confirm' ;
11
12
import input from '@inquirer/input' ;
12
13
import select , { Separator } from '@inquirer/select' ;
13
- import { type Config , defaultConfigPath , readConfig , writeConfig } from './config.js' ;
14
+ import { defaultConfigPath , readConfig , writeConfig , optionNameMap } from './config.js' ;
14
15
import { type Cache , link , type LinkOptions , pull , push } from './methods.js' ;
15
16
import { processMarkdown } from './remark.js' ;
16
17
@@ -25,32 +26,40 @@ import { processMarkdown } from './remark.js';
25
26
*/
26
27
type CommonOptions = ReturnType < ReturnType < typeof createCommanderCommand > [ 'opts' ] > ;
27
28
28
- async function createClient ( options : CommonOptions , config ?: Config ) {
29
- if ( config === undefined ) {
30
- try {
31
- config = await readConfig ( options [ 'config' ] ) ;
32
- } catch ( error ) {
33
- if (
34
- error instanceof Error &&
35
- 'errno' in error &&
36
- ( error as NodeJS . ErrnoException ) . code === 'ENOENT' &&
37
- options [ 'config' ] === defaultConfigPath ( )
38
- ) {
39
- config = { } ;
40
- } else {
41
- throw new InvalidArgumentError (
42
- `Failed to load config file ${ options [ 'config' ] } due to: ${ error } ` ,
43
- ) ;
44
- }
29
+ /**
30
+ * Reads the config file from the given `--config <configPath>` argument, ignoring
31
+ * ENONET errors if `ignoreENONET` is `true`.
32
+ *
33
+ * @param configPath - The path to the config file.
34
+ * @param ignoreENONET - Whether to ignore ENONET errors.
35
+ * @throws {@link InvalidArgumentError }
36
+ * Thrown if:
37
+ * - The config file exists, but is not a valid TOML file.
38
+ * - The config file does not exist, and `ignoreENONET` is `false`.
39
+ */
40
+ async function readConfigFromConfigArg ( configPath : string , ignoreENONET : boolean = false ) {
41
+ try {
42
+ return await readConfig ( configPath ) ;
43
+ } catch ( error ) {
44
+ if (
45
+ error instanceof Error &&
46
+ 'errno' in error &&
47
+ ( error as NodeJS . ErrnoException ) . code === 'ENOENT' &&
48
+ ignoreENONET
49
+ ) {
50
+ return { } ;
45
51
}
52
+ throw new InvalidArgumentError ( `Failed to load config file ${ configPath } due to: ${ error } ` ) ;
46
53
}
54
+ }
47
55
56
+ async function createClient ( options : CommonOptions ) {
48
57
const client = new MermaidChart ( {
49
58
clientID : '018bf0ff-7e8c-7952-ab4e-a7bd7c7f54f3' ,
50
- baseURL : new URL ( config . base_url ?? 'https://mermaidchart.com' ) . toString ( ) ,
59
+ baseURL : new URL ( options . baseUrl ) . toString ( ) ,
51
60
} ) ;
52
61
53
- if ( config . auth_token === undefined ) {
62
+ if ( options . authToken === undefined ) {
54
63
throw new CommanderError (
55
64
/*exitCode=*/ 1 ,
56
65
'ENEEDAUTH' ,
@@ -59,7 +68,7 @@ async function createClient(options: CommonOptions, config?: Config) {
59
68
}
60
69
61
70
try {
62
- await client . setAccessToken ( config . auth_token ) ;
71
+ await client . setAccessToken ( options . authToken ) ;
63
72
} catch ( error ) {
64
73
if ( error instanceof Error && error . message . includes ( '401' ) ) {
65
74
throw new CommanderError (
@@ -91,32 +100,22 @@ function login() {
91
100
. description ( 'Login to a Mermaid Chart account' )
92
101
. action ( async ( _options , command ) => {
93
102
const optsWithGlobals = command . optsWithGlobals < CommonOptions > ( ) ;
94
- let config ;
95
- try {
96
- config = await readConfig ( optsWithGlobals [ 'config' ] ) ;
97
- } catch ( error ) {
98
- if (
99
- error instanceof Error &&
100
- 'errno' in error &&
101
- ( error as NodeJS . ErrnoException ) . code === 'ENOENT'
102
- ) {
103
- config = { } ;
104
- } else {
105
- throw error ;
106
- }
107
- }
108
103
109
- const baseURL = new URL ( config . base_url ?? 'https://mermaidchart.com' ) . toString ( ) ;
104
+ // empty if default config file doesn't exist
105
+ const config = await readConfigFromConfigArg (
106
+ optsWithGlobals [ 'config' ] ,
107
+ /*ignoreENONET=*/ true ,
108
+ ) ;
110
109
111
110
const client = new MermaidChart ( {
112
111
clientID : '018bf0ff-7e8c-7952-ab4e-a7bd7c7f54f3' ,
113
- baseURL : baseURL ,
112
+ baseURL : optsWithGlobals . baseUrl ,
114
113
} ) ;
115
114
116
115
const answer = await input ( {
117
116
message : `Enter your API token. You can generate one at ${ new URL (
118
117
'/app/user/settings' ,
119
- baseURL ,
118
+ optsWithGlobals . baseUrl ,
120
119
) } `,
121
120
async validate ( key ) {
122
121
try {
@@ -150,7 +149,7 @@ function logout() {
150
149
await writeConfig ( optsWithGlobals [ 'config' ] , config ) ;
151
150
152
151
try {
153
- const user = await ( await createClient ( optsWithGlobals , config ) ) . getUser ( ) ;
152
+ const user = await ( await createClient ( optsWithGlobals ) ) . getUser ( ) ;
154
153
console . log ( `API token for ${ user . emailAddress } removed from ${ optsWithGlobals [ 'config' ] } ` ) ;
155
154
} catch ( error ) {
156
155
// API token might have been expired
@@ -295,7 +294,43 @@ export function createCommanderCommand() {
295
294
'-c, --config <config_file>' ,
296
295
'The path to the config file to use.' ,
297
296
defaultConfigPath ( ) ,
298
- ) ;
297
+ )
298
+ . addOption (
299
+ new Option (
300
+ '--base-url <base_url>' ,
301
+ 'The base URL of the Mermaid Chart instance to use.' ,
302
+ ) . default ( 'https://mermaidchart.com' ) ,
303
+ )
304
+ . addOption ( new Option ( '--auth-token <auth_token>' , 'The Mermaid Chart API token to use.' ) )
305
+ . hook ( 'preSubcommand' , async ( command , actionCommand ) => {
306
+ const configPath = command . getOptionValue ( 'config' ) ;
307
+ /**
308
+ * config file is allowed to not exist if:
309
+ * - the user is running the `login` command, we'll create a new config file if it doesn't exist
310
+ */
311
+ const ignoreENONET = configPath === defaultConfigPath ( ) || actionCommand . name ( ) === 'login' ;
312
+
313
+ const config = await readConfigFromConfigArg ( configPath , ignoreENONET ) ;
314
+ for ( const key in config ) {
315
+ if ( ! ( key in optionNameMap ) ) {
316
+ console . warn ( `Warning: Ignoring unrecognized config key: ${ key } in ${ configPath } ` ) ;
317
+ continue ;
318
+ }
319
+ const optionCommanderName = optionNameMap [ key as keyof typeof optionNameMap ] ;
320
+ // config values only override default/implied values
321
+ if (
322
+ [ undefined , 'default' , 'implied' ] . includes (
323
+ command . getOptionValueSource ( optionCommanderName ) ,
324
+ )
325
+ ) {
326
+ command . setOptionValueWithSource (
327
+ optionNameMap [ key as keyof typeof optionNameMap ] ,
328
+ config [ key ] ,
329
+ 'config' ,
330
+ ) ;
331
+ }
332
+ }
333
+ } ) ;
299
334
300
335
return program
301
336
. addCommand ( whoami ( ) )
0 commit comments