Skip to content

Commit

Permalink
feat(cli): support init and load config file
Browse files Browse the repository at this point in the history
  • Loading branch information
ysfscream authored and Red-Asuka committed May 17, 2024
1 parent 9789ea4 commit d4e8aed
Show file tree
Hide file tree
Showing 8 changed files with 423 additions and 18 deletions.
3 changes: 3 additions & 0 deletions cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"node": ">=18"
},
"dependencies": {
"@inquirer/prompts": "^5.0.3",
"axios": "^0.27.2",
"cbor": "^9.0.1",
"chalk": "~4.1.2",
Expand All @@ -29,6 +30,7 @@
"compare-versions": "^5.0.1",
"concat-stream": "^2.0.0",
"core-js": "^3.26.0",
"ini": "^4.1.2",
"js-yaml": "^4.1.0",
"json-bigint": "^1.0.0",
"lodash": "^4.17.21",
Expand All @@ -43,6 +45,7 @@
"@faker-js/faker": "^8.1.0",
"@types/concat-stream": "^2.0.0",
"@types/debug": "^4.1.12",
"@types/ini": "^4.1.0",
"@types/js-yaml": "^4.0.5",
"@types/json-bigint": "^1.0.4",
"@types/lodash": "^4.17.1",
Expand Down
23 changes: 23 additions & 0 deletions cli/src/configs/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { join } from 'path'
import { homedir } from 'os'

// Path to user's home directory
const USER_HOME_DIR = homedir()

// Path to the config file
const CONFIG_FILE_PATH = join(USER_HOME_DIR, '.mqttx-cli', 'config')

// Default configuration
const DEFAULT_CONFIG: ConfigModel = {
output: 'text',
mqtt: {
host: 'localhost',
port: 1883,
username: '',
password: '',
},
}

const VALID_OUTPUT_MODES: Array<ConfigModel['output']> = ['text', 'json', 'log']

export { USER_HOME_DIR, CONFIG_FILE_PATH, DEFAULT_CONFIG, VALID_OUTPUT_MODES }
2 changes: 2 additions & 0 deletions cli/src/configs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './load'
export * from './init'
79 changes: 79 additions & 0 deletions cli/src/configs/init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { writeFileSync, mkdirSync } from 'fs'
import { join } from 'path'
import { select, input, password } from '@inquirer/prompts'
import { CONFIG_FILE_PATH, DEFAULT_CONFIG, USER_HOME_DIR } from './common'

/**
* Generates the content of a configuration INI file based on the provided config object.
* @param config - The configuration object containing the necessary properties.
* @returns The generated configuration file content as a string.
*/
const generateConfigContent = (config: ConfigModel): string => {
return `[default]
output = ${config.output}
[mqtt]
host = ${config.mqtt.host}
port = ${config.mqtt.port}
username = ${config.mqtt.username}
password = ${config.mqtt.password}`
}

/**
* Initializes the configuration for MQTTX CLI.
* Creates or updates the configuration file with the provided values.
*/
async function initConfig(): Promise<void> {
const output = (await select({
message: 'Select MQTTX CLI output mode',
choices: [
{ name: 'Text', value: 'text', description: 'Plain text output' },
{ name: 'JSON', value: 'json', description: 'JSON formatted output' },
{ name: 'Log', value: 'log', description: 'Log file output' },
],
default: DEFAULT_CONFIG.output,
})) as ConfigModel['output']

const host = await input({
message: 'Enter the default MQTT broker host',
default: DEFAULT_CONFIG.mqtt.host,
})

const port = await input({
message: 'Enter the default MQTT port',
default: DEFAULT_CONFIG.mqtt.port.toString(),
validate: (input) => !isNaN(parseInt(input, 10)) || 'Port must be a number',
})

const username = await input({
message: 'Enter the default username for MQTT connection authentication',
default: DEFAULT_CONFIG.mqtt.username,
})

const passwordAnswer = await password({
message: 'Enter the default password for MQTT connection authentication',
mask: true,
})

const newConfig: ConfigModel = {
output,
mqtt: {
host,
port: parseInt(port, 10),
username,
password: passwordAnswer,
},
}

try {
mkdirSync(join(USER_HOME_DIR, '.mqttx-cli'), { recursive: true })
writeFileSync(CONFIG_FILE_PATH, generateConfigContent(newConfig))
console.log(`Configuration file created/updated at ${CONFIG_FILE_PATH}`)
} catch (error) {
console.error(
`Unable to create configuration file at ${CONFIG_FILE_PATH}. Please check your permissions and try again.`,
)
}
}

export { initConfig }
49 changes: 49 additions & 0 deletions cli/src/configs/load.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { readFileSync, existsSync } from 'fs'
import ini from 'ini'
import { CONFIG_FILE_PATH, DEFAULT_CONFIG, VALID_OUTPUT_MODES } from './common'

/**
* Parses the content of a config file and returns a ConfigModel object.
* @param content - The content of the config file.
* @returns The parsed ConfigModel object.
* @throws Error if the output mode is invalid.
*/
const parseConfigFile = (content: string): ConfigModel => {
const config = ini.parse(content)
const output = config.default?.output
if (output && !VALID_OUTPUT_MODES.includes(output)) {
throw new Error(`Invalid output mode: ${output}. Valid modes are: ${VALID_OUTPUT_MODES.join(', ')}`)
}

return {
output: config.default?.output || DEFAULT_CONFIG.output,
mqtt: {
host: config.mqtt?.host || DEFAULT_CONFIG.mqtt.host,
port: parseInt(config.mqtt?.port, 10) || DEFAULT_CONFIG.mqtt.port,
username: config.mqtt?.username || DEFAULT_CONFIG.mqtt.username,
password: config.mqtt?.password || DEFAULT_CONFIG.mqtt.password,
},
}
}

/**
* Loads the configuration from a file.
* If the file exists, it reads the content, parses it, and returns the configuration.
* If the file doesn't exist or there is an error parsing the content, it returns the default configuration.
*
* @returns The loaded configuration.
*/
const loadConfig = (): ConfigModel => {
if (existsSync(CONFIG_FILE_PATH)) {
const configFileContent = readFileSync(CONFIG_FILE_PATH, 'utf-8')
try {
return parseConfigFile(configFileContent)
} catch (error) {
console.error((error as Error).message)
console.error('Invalid configuration file. Using default configuration.')
}
}
return DEFAULT_CONFIG
}

export { loadConfig }
9 changes: 9 additions & 0 deletions cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@ import { pub, benchPub, simulatePub } from './lib/pub'
import { sub, benchSub } from './lib/sub'
import ls from './lib/ls'
import { version } from '../package.json'
import { loadConfig, initConfig } from './configs'

export class Commander {
program: Command

constructor() {
const configs = loadConfig()
console.log(configs)
this.program = new Command()
}

Expand All @@ -45,6 +48,12 @@ export class Commander {
await checkUpdate()
})

this.program
.command('init')
.description('Initialize the configuration file.')
.allowUnknownOption(false)
.action(initConfig)

this.program
.command('conn')
.description('Create a connection and connect to MQTT Broker.')
Expand Down
10 changes: 10 additions & 0 deletions cli/src/types/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,16 @@ declare global {
reasonCode: number
length: number
}

interface ConfigModel {
output: 'text' | 'json' | 'log'
mqtt: {
host: string
port: number
username: string
password: string
}
}
}

export {}
Loading

0 comments on commit d4e8aed

Please sign in to comment.