@@ -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
import { createRequire } from 'node:module' ;
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,31 +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
- }
41
- throw new InvalidArgumentError (
42
- `Failed to load config file ${ options [ 'config' ] } due to: ${ error } ` ,
43
- ) ;
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 { } ;
44
51
}
52
+ throw new InvalidArgumentError ( `Failed to load config file ${ configPath } due to: ${ error } ` ) ;
45
53
}
54
+ }
46
55
56
+ async function createClient ( options : CommonOptions ) {
47
57
const client = new MermaidChart ( {
48
58
clientID : '018bf0ff-7e8c-7952-ab4e-a7bd7c7f54f3' ,
49
- baseURL : new URL ( config . base_url ?? 'https://mermaidchart.com' ) . toString ( ) ,
59
+ baseURL : options . baseUrl ,
50
60
} ) ;
51
61
52
- if ( config . auth_token === undefined ) {
62
+ if ( options . authToken === undefined ) {
53
63
throw new CommanderError (
54
64
/*exitCode=*/ 1 ,
55
65
'ENEEDAUTH' ,
@@ -58,7 +68,7 @@ async function createClient(options: CommonOptions, config?: Config) {
58
68
}
59
69
60
70
try {
61
- await client . setAccessToken ( config . auth_token ) ;
71
+ await client . setAccessToken ( options . authToken ) ;
62
72
} catch ( error ) {
63
73
if ( error instanceof Error && error . message . includes ( '401' ) ) {
64
74
throw new CommanderError (
@@ -90,32 +100,22 @@ function login() {
90
100
. description ( 'Login to a Mermaid Chart account' )
91
101
. action ( async ( _options , command ) => {
92
102
const optsWithGlobals = command . optsWithGlobals < CommonOptions > ( ) ;
93
- let config ;
94
- try {
95
- config = await readConfig ( optsWithGlobals [ 'config' ] ) ;
96
- } catch ( error ) {
97
- if (
98
- error instanceof Error &&
99
- 'errno' in error &&
100
- ( error as NodeJS . ErrnoException ) . code === 'ENOENT'
101
- ) {
102
- config = { } ;
103
- } else {
104
- throw error ;
105
- }
106
- }
107
103
108
- 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
+ ) ;
109
109
110
110
const client = new MermaidChart ( {
111
111
clientID : '018bf0ff-7e8c-7952-ab4e-a7bd7c7f54f3' ,
112
- baseURL : baseURL ,
112
+ baseURL : optsWithGlobals . baseUrl ,
113
113
} ) ;
114
114
115
115
const answer = await input ( {
116
116
message : `Enter your API token. You can generate one at ${ new URL (
117
117
'/app/user/settings' ,
118
- baseURL ,
118
+ optsWithGlobals . baseUrl ,
119
119
) } `,
120
120
async validate ( key ) {
121
121
try {
@@ -149,7 +149,7 @@ function logout() {
149
149
await writeConfig ( optsWithGlobals [ 'config' ] , config ) ;
150
150
151
151
try {
152
- const user = await ( await createClient ( optsWithGlobals , config ) ) . getUser ( ) ;
152
+ const user = await ( await createClient ( optsWithGlobals ) ) . getUser ( ) ;
153
153
console . log ( `API token for ${ user . emailAddress } removed from ${ optsWithGlobals [ 'config' ] } ` ) ;
154
154
} catch ( error ) {
155
155
// API token might have been expired
@@ -297,7 +297,46 @@ export function createCommanderCommand() {
297
297
'-c, --config <config_file>' ,
298
298
'The path to the config file to use.' ,
299
299
defaultConfigPath ( ) ,
300
- ) ;
300
+ )
301
+ . addOption (
302
+ new Option ( '--base-url <base_url>' , 'The base URL of the Mermaid Chart instance to use.' )
303
+ . default ( 'https://mermaidchart.com' )
304
+ . env ( 'MERMAID_CHART_BASE_URL' ) ,
305
+ )
306
+ . addOption (
307
+ new Option ( '--auth-token <auth_token>' , 'The Mermaid Chart API token to use.' ) . env (
308
+ 'MERMAID_CHART_AUTH_TOKEN' ,
309
+ ) ,
310
+ )
311
+ . hook ( 'preSubcommand' , async ( command , actionCommand ) => {
312
+ const configPath = command . getOptionValue ( 'config' ) ;
313
+ /**
314
+ * config file is allowed to not exist if:
315
+ * - the user is running the `login` command, we'll create a new config file if it doesn't exist
316
+ */
317
+ const ignoreENONET = configPath === defaultConfigPath ( ) || actionCommand . name ( ) === 'login' ;
318
+
319
+ const config = await readConfigFromConfigArg ( configPath , ignoreENONET ) ;
320
+ for ( const key in config ) {
321
+ if ( ! ( key in optionNameMap ) ) {
322
+ console . warn ( `Warning: Ignoring unrecognized config key: ${ key } in ${ configPath } ` ) ;
323
+ continue ;
324
+ }
325
+ const optionCommanderName = optionNameMap [ key as keyof typeof optionNameMap ] ;
326
+ // config values only override default/implied values
327
+ if (
328
+ [ undefined , 'default' , 'implied' ] . includes (
329
+ command . getOptionValueSource ( optionCommanderName ) ,
330
+ )
331
+ ) {
332
+ command . setOptionValueWithSource (
333
+ optionNameMap [ key as keyof typeof optionNameMap ] ,
334
+ config [ key ] ,
335
+ 'config' ,
336
+ ) ;
337
+ }
338
+ }
339
+ } ) ;
301
340
302
341
return program
303
342
. addCommand ( whoami ( ) )
0 commit comments