Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): support init and load config file #1656

Merged
merged 1 commit into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading