UNPKG

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

180 lines (179 loc) 5.94 kB
import path from 'path'; import { promises as fs } from 'fs'; import fsExtra from 'fs-extra'; import { CliLogger } from './cli-logger.js'; export class SafeFiles { logger; constructor(logger = new CliLogger(SafeFiles.name)) { this.logger = logger; } getFolderName(dir, root, homepage) { return path.resolve(dir) === path.resolve(root) ? homepage : path.parse(dir).base; } async pathExists(filePath) { try { return await fsExtra.pathExists(filePath); } catch (error) { this.logger.error(`Error checking file existence: ${filePath}`, error); return false; } } async createDir(dir) { try { await fsExtra.mkdirp(dir); } catch (error) { this.logger.error(`Error creating directory ${dir}`, error); } } async emptySubFolder(dir) { const resolvedDir = path.resolve(dir); const cwd = process.cwd(); if (!resolvedDir.startsWith(cwd + path.sep)) { this.logger.warn(`Refusing to empty non-subdirectory path: ${resolvedDir}`); return false; } try { await fsExtra.emptyDir(dir); return true; } catch (error) { this.logger.error(`Error emptying directory: ${dir}`, error); return false; } } async copyFile(src, dest) { try { await fsExtra.copy(src, dest); } catch (error) { this.logger.error(`Error copying file from ${src} to ${dest}`, error); } } async removeFile(path) { try { await fs.unlink(path); } catch (error) { this.logger.error(`Error removing file ${path}`, error); } } async accessFile(path) { try { await fs.access(path); } catch (error) { this.logger.error(`Error accessing file ${path}`, error); } } async readDir(dir) { try { const files = await fs.readdir(dir); return files.filter((x) => !x.startsWith('_')); } catch (error) { this.logger.error(`Error reading directory ${dir}`, error); return []; } } async readFileAsString(filePath) { try { const content = await fs.readFile(filePath, 'utf-8'); return content.toString(); } catch (error) { this.logger.error(`Error reading file: ${filePath}`, error); return null; } } async writeFile(filePath, data) { try { await fs.writeFile(filePath, data); } catch (error) { this.logger.error(`Error writing file: ${filePath}`, error); } } async stat(filePath) { try { return await fs.stat(filePath); } catch (error) { this.logger.error(`Error getting stats for file: ${filePath}`, error); return null; } } async ensureDir(dir) { try { await fsExtra.ensureDir(dir); } catch (error) { this.logger.error(`Error ensuring directory: ${dir}`, error); } } async ingestMarkdownFiles(dir, files, treeItem) { const mdFiles = files.filter((x) => path.extname(x).toLowerCase() === '.md'); for (const mdFile of mdFiles) { const fileContents = await this.readFileAsString(path.join(dir, mdFile)); if (fileContents) { treeItem.mdFiles.push({ name: mdFile, content: fileContents }); } } } async ingestMermaidFiles(dir, files, treeItem) { const mmdFiles = files.filter((x) => path.extname(x).toLowerCase() === '.mmd'); for (const mmdFile of mmdFiles) { const fileContents = await this.readFileAsString(path.join(dir, mmdFile)); if (fileContents) { treeItem.mmdFiles.push({ name: mmdFile, content: fileContents.toString(), }); } } } async itemiseTreeFolder(dir, baseFolder, rootFolder, parent, homepageName, tree) { const rootFolderName = this.getFolderName(dir, baseFolder, homepageName); const treeItem = { dir: dir, name: rootFolderName, level: dir.split(path.sep).length - baseFolder.split(path.sep).length, parent: parent, mdFiles: [], mmdFiles: [], descendants: [], }; const files = await this.readDir(dir); for (const file of files) { const filePath = path.join(dir, file); const stats = await this.stat(filePath); if (!stats) continue; if (stats.isDirectory()) { treeItem.descendants.push(file); await this.ensureDir(path.join(baseFolder, dir.replace(rootFolder, ''), file)); const childTreeItem = await this.itemiseTreeFolder(filePath, baseFolder, rootFolder, dir, homepageName, tree); if (childTreeItem) { tree.unshift(childTreeItem); } } } await this.ingestMarkdownFiles(dir, files, treeItem); await this.ingestMermaidFiles(dir, files, treeItem); if (treeItem.mdFiles.length > 0 || treeItem.mmdFiles.length > 0 || treeItem.descendants.length > 0) { return treeItem; } return null; } async generateTree(baseFolder, rootFolder, homepageName) { const tree = []; const rootBranch = await this.itemiseTreeFolder(baseFolder, baseFolder, rootFolder, null, homepageName, tree); if (rootBranch) { tree.unshift(rootBranch); } return tree; } }