c4dslbuilder
Version:
A CLI tool designed to compile a folder structure of markdowns and mermaid files into a site, pdf, single file markdown or a collection of markdowns with links - inspired by c4builder
263 lines (262 loc) • 10.9 kB
JavaScript
import Configstore from 'configstore';
import inquirer from 'inquirer';
import chalk from 'chalk';
import path from 'path';
import { CliLogger } from './cli-logger.js';
import * as Constants from '../types/constants.js';
export class ConfigManager {
logger;
constructor(logger = new CliLogger(ConfigManager.name)) {
this.logger = logger;
}
openConfigStore() {
let config;
try {
config = new Configstore(process.cwd().split(path.sep).splice(1).join('_'), {}, {
configPath: path.join(process.cwd(), Constants.CONFIG_FILENAME),
});
}
catch (error) {
this.logger.error('Error accessing config store.', error);
return null;
}
return config;
}
getStoredValue(key) {
const config = this.openConfigStore();
if (!config) {
this.logger.error('Failed to open config store.');
return undefined;
}
try {
const configValue = config.get(key);
if (configValue === undefined) {
this.logger.warn(`Configuration key ${key} not found.`);
return undefined;
}
return configValue;
}
catch (error) {
this.logger.error(`Error retrieving config value for ${key}.`, error);
return undefined;
}
}
printConfigValue(title, value) {
this.logger.log(`${title.padEnd(40)} : ${value}`);
}
/* c8 ignore next 6 -- @preserve -- c8 stubbornly refuses to acknowledge we're testing this */
boolValueToString(value) {
if (value) {
return 'Yes';
}
return 'No';
}
numValueToString(value) {
return value.toString();
}
getPrintValue(value) {
return value?.trim?.() && value !== 'undefined' ? chalk.green(value) : chalk.red('Not set');
}
isValidProjectName(input) {
return (/^[\w\s-]{2,}$/.test(input.trim()) ||
'Project name must be at least 2 characters and only contain letters, numbers, spaces, hyphens, or underscores.');
}
isValidUrl(input) {
if (!input?.trim())
return true;
try {
new URL(input);
return true;
}
catch {
return 'Please enter a valid URL.';
}
}
getStrConfigValue(key) {
const configValue = this.getStoredValue(key);
if (typeof configValue === 'string') {
return configValue;
}
this.logger.info(`Expected string for ${key}, but got ${typeof configValue}`);
return '';
}
getBoolConfigValue(key) {
const configValue = this.getStoredValue(key);
if (typeof configValue === 'boolean') {
return configValue;
}
// cater for string representations of booleans
if (typeof configValue === 'string') {
if (configValue.toLowerCase() === 'true' || configValue.toLowerCase() === 'yes') {
return true;
}
if (configValue.toLowerCase() === 'false' || configValue.toLowerCase() === 'no') {
return false;
}
}
this.logger.info(`Expected boolean for ${key}, but got ${typeof configValue}`);
return false;
}
getNumConfigValue(key) {
const configValue = this.getStoredValue(key);
const num = typeof configValue === 'number' ? configValue : Number(configValue);
if (Number.isNaN(num)) {
this.logger.info(`Expected number for ${key}, but got ${typeof configValue}`);
return 0;
}
return num;
}
setConfigValue(key, value) {
const config = this.openConfigStore();
if (!config) {
this.logger.error('Failed to open config store.');
return;
}
config.set(key, value);
}
deleteConfig(key) {
const config = this.openConfigStore();
if (!config) {
this.logger.error('Failed to open config store.');
return;
}
config.delete(key);
}
resetConfig() {
const config = this.openConfigStore();
if (!config) {
this.logger.error('Failed to open config store.');
return;
}
const projectName = this.getStrConfigValue('projectName');
config.clear();
config.set('projectName', projectName);
this.logger.log(chalk.yellow(`✅ Configuration has been reset for project ${projectName}.`));
}
listConfig() {
this.logger.log(chalk.cyan('Current Configuration\n'));
this.printConfigValue('Project name', this.getPrintValue(this.getStrConfigValue('projectName')));
this.printConfigValue('Homepage Name', this.getPrintValue(this.getStrConfigValue('homepageName')));
this.printConfigValue('Root Folder', this.getPrintValue(this.getStrConfigValue('rootFolder')));
this.printConfigValue('Destination Folder', this.getPrintValue(this.getStrConfigValue('distFolder')));
this.printConfigValue('Structurizr DSL CLI to use', this.getPrintValue(this.getStrConfigValue('dslCli')));
this.printConfigValue('Where Structurizr starts looking for diagrams to extract', this.getPrintValue(this.getStrConfigValue('workspaceDsl')));
this.printConfigValue('Embed Mermaid diagrams?', this.getPrintValue(this.boolValueToString(this.getBoolConfigValue('embedMermaidDiagrams'))));
this.printConfigValue('PDF CSS', this.getPrintValue(this.getStrConfigValue('pdfCss')));
this.printConfigValue('Port Number', this.getPrintValue(this.numValueToString(this.getNumConfigValue('servePort'))));
this.printConfigValue('Repo URL', this.getPrintValue(this.getStrConfigValue('repoName')));
this.printConfigValue('Docsify stylesheet', this.getPrintValue(this.getStrConfigValue('webTheme')));
this.printConfigValue('Enable website search', this.getPrintValue(this.boolValueToString(this.getBoolConfigValue('webSearch'))));
this.printConfigValue('Docsify template', this.getPrintValue(this.getStrConfigValue('docsifyTemplate')));
}
async setConfig() {
this.logger.log(chalk.cyan('Configure your project settings:\n'));
const answers = await inquirer.prompt([
{
type: 'input',
name: 'projectName',
message: 'Project name:',
default: this.getStrConfigValue('projectName'),
validate: this.isValidProjectName.bind(this),
},
{
type: 'input',
name: 'homepageName',
message: 'Homepage name:',
default: this.getStrConfigValue('homepageName') || 'Overview',
},
{
type: 'input',
name: 'rootFolder',
message: 'Root folder:',
default: this.getStrConfigValue('rootFolder') || Constants.DEFAULT_ROOT,
},
{
type: 'input',
name: 'distFolder',
message: 'Destination folder:',
default: this.getStrConfigValue('distFolder') || Constants.DEFAULT_DIST,
},
{
type: 'confirm',
name: 'embedMermaidDiagrams',
message: 'Embed Mermaid diagrams? (Set this to NO / false to replace mermaid diagrams with a link to an image)',
default: this.getBoolConfigValue('embedMermaidDiagrams') || true,
},
{
type: 'list',
name: 'dslCli',
message: 'Which Structurizr CLI would you prefer to use:',
choices: ['structurizr-cli', 'docker'],
default: this.getStrConfigValue('dslCli') || 'structurizr-cli',
},
{
type: 'input',
name: 'workspaceDsl',
message: 'Where should the Structurizr CLI start looking when exporting diagrams:',
default: this.getStrConfigValue('workspaceDsl') || 'workspace.dsl',
},
{
type: 'input',
name: 'pdfCss',
message: 'PDF CSS file path:',
default: this.getStrConfigValue('pdfCss') || '_resources/pdf.css',
},
{
type: 'number',
name: 'servePort',
message: 'Port number:',
default: this.getNumConfigValue('servePort') || 3030,
},
{
type: 'input',
name: 'repoName',
message: 'Repository URL:',
default: this.getStrConfigValue('repoName') || '',
validate: this.isValidUrl.bind(this),
},
{
type: 'input',
name: 'webTheme',
message: 'Website Docsify theme (URL):',
default: this.getStrConfigValue('webTheme') || 'https://unpkg.com/docsify/lib/themes/vue.css',
validate: this.isValidUrl.bind(this),
},
{
type: 'confirm',
name: 'webSearch',
message: 'Enable website search?',
default: this.getBoolConfigValue('webSearch') || true,
},
{
type: 'input',
name: 'docsifyTemplate',
message: 'Local path to a custom Docsify template:',
default: this.getStrConfigValue('docsifyTemplate') || '',
},
]);
Object.entries(answers).forEach(([key, value]) => {
this.setConfigValue(key, String(value));
});
this.logger.log(chalk.green('\n✅ Configuration updated successfully.'));
}
async getAllStoredConfig() {
return {
projectName: this.getStrConfigValue('projectName'),
homepageName: this.getStrConfigValue('homepageName'),
rootFolder: this.getStrConfigValue('rootFolder'),
distFolder: this.getStrConfigValue('distFolder'),
dslCli: this.getStrConfigValue('dslCli') === 'docker' ? 'docker' : 'structurizr-cli',
workspaceDsl: this.getStrConfigValue('workspaceDsl'),
embedMermaidDiagrams: this.getBoolConfigValue('embedMermaidDiagrams'),
pdfCss: this.getStrConfigValue('pdfCss'),
servePort: this.getNumConfigValue('servePort'),
repoName: this.getStrConfigValue('repoName'),
webTheme: this.getStrConfigValue('webTheme'),
webSearch: this.getBoolConfigValue('webSearch'),
docsifyTemplate: this.getStrConfigValue('docsifyTemplate'),
serve: this.getBoolConfigValue('serve'),
generateWebsite: this.getBoolConfigValue('generateWebsite'),
};
}
}