UNPKG

dce-dev-wizard

Version:

Wizard for managing development apps at Harvard DCE.

313 lines (264 loc) 9.3 kB
import fs from 'fs'; import path from 'path'; // Error code regex const errorCodeRegex = /new ErrorWithCode\(\s*(.+),\s*(.+)\s*\)(;|,)/; // Get the project directory const pwd = path.join(process.env.PWD || process.env.CWD || __dirname, '../..'); /*------------------------------------------------------------------------*/ /* -------------------------------- Types ------------------------------- */ /*------------------------------------------------------------------------*/ // An error code and its implementation type ErrorCode = { // Parent code parent: string, // The error code code: string, // The machine-readable name of the error code prop: string, // If true, found in any capacity found: boolean, // Appearances appearances: { // Error message code messageTemplate: string, // File path filePath: string, }[], }; /*------------------------------------------------------------------------*/ /* ------------------------------- Helpers ------------------------------ */ /*------------------------------------------------------------------------*/ /** * Get all paths to the error code types within a folder * @author Gabe Abrams * @param parentFolderPath - the path to the parent folder * @returns an array of paths to the error code types */ const getErrorCodesPaths = (parentFolderPath: string): string[] => { // Get all files in the folder const files = fs.readdirSync(parentFolderPath); // For each item in the folder, recurse if folder, analyze if file const paths: string[] = []; files.forEach((file) => { // Get the full path const fullPath = path.join(parentFolderPath, file); // If folder, recurse const isFolder = ( fs .lstatSync(fullPath) .isDirectory() ); if (isFolder) { // Skip if the "from-server" folder if (file !== 'from-server') { // Recurse paths.push(...getErrorCodesPaths(fullPath)); } } else { // Not folder. Analyze if (file.includes('ErrorCode') && (file.endsWith('.ts') || file.endsWith('.tsx'))) { // Found an error code file paths.push(fullPath); } } }); // Return all accumulated paths return paths; }; /** * Read an error code file and return the error codes within it * @author Gabe Abrams * @param filePath - the path to the error code file * @returns an array of error codes */ const readErrorCodes = (filePath: string): ErrorCode[] => { // Read the file const fileContents = fs.readFileSync(filePath, 'utf8'); // Get the name of the error code file const parent = ( (filePath.split('/').pop() || 'UnknownErrorCodeParent') .replace('.tsx', '') .replace('.ts', '') ); // Split the file into lines const lines = fileContents.split('\n'); // Find all error codes const errorCodes: ErrorCode[] = []; lines.forEach((line, lineNumber) => { // Format of an error code: // DescriptionOfErrorCode = 'CODE', // Check if this line is an error code const matches = line.trim().match(/(\w+) = '(\w+)',/); if (matches) { // Found an error code const name = matches[1]; const code = matches[2]; // Add to error codes errorCodes.push({ parent, code, found: false, prop: name, appearances: [], }); } }); // Return all error codes return errorCodes; }; /*------------------------------------------------------------------------*/ /* -------------------------------- Main -------------------------------- */ /*------------------------------------------------------------------------*/ /*----------------------------------------*/ /* ---------- Parse Error Codes --------- */ /*----------------------------------------*/ console.log('\nUpdating error docs...'); // Get error code file paths const serverPaths = getErrorCodesPaths(path.join(pwd, '/server/src')); const clientPaths = getErrorCodesPaths(path.join(pwd, '/client/src')); const errorCodePaths = serverPaths.concat(clientPaths); // Read each and process the enums const errorCodes: ErrorCode[] = []; errorCodePaths.forEach((filePath) => { errorCodes.push(...readErrorCodes(filePath)); }); /*----------------------------------------*/ /* -------- Validate Error Codes -------- */ /*----------------------------------------*/ // Display fatal error if duplicate error codes exist const errorCodeSet = new Set<string>(); errorCodes.forEach((errorCode) => { if (errorCodeSet.has(errorCode.code)) { console.error(`Duplicate error code defined: ${errorCode.code} in ${errorCode.parent}. Fatal error. Exiting!`); process.exit(1); } }); /*----------------------------------------*/ /* ---------- Find Appearances ---------- */ /*----------------------------------------*/ /** * Recursively look for appearances of error codes * @author Gabe Abrams * @param folderPath - the path to the folder to search */ const findAppearances = (folderPath: string) => { // Format of an error appearance: // new ErrorWithCode( // `Message template with ${templateVariables} in it perhaps`, // ErrorCodeParent.ErrorCodePropName, // ); // Get all files in the folder const files = fs.readdirSync(folderPath); // For each item in the folder, recurse if folder, analyze if file files.forEach((file) => { // Get the full path const fullPath = path.join(folderPath, file); // If folder, recurse const isFolder = ( fs .lstatSync(fullPath) .isDirectory() ); if (isFolder) { // Recurse findAppearances(fullPath); } else { // Not folder. Analyze if ( (file.endsWith('.ts') || file.endsWith('.tsx')) && !errorCodePaths.includes(fullPath) ) { // Read the file let fileContents = fs.readFileSync(fullPath, 'utf8'); // Check if found errorCodes.find((errorCode) => { if ( fileContents.includes(`${errorCode.parent}.${errorCode.prop},`) || fileContents.includes(`${errorCode.parent}.${errorCode.prop};`) || fileContents.includes(`${errorCode.parent}.${errorCode.prop} `) || fileContents.includes(`${errorCode.parent}.${errorCode.prop})`) || fileContents.includes(`${errorCode.parent}.${errorCode.prop}\n`) ) { errorCode.found = true; } }); // Check if this line is an error appearance let matches = fileContents.trim().match(errorCodeRegex); while (matches) { // Found an error appearance const messageTemplate = matches[1]; const parentAndProp = matches[2]; // Find the error code const errorCode = errorCodes.find((errorCode) => { return ( `${errorCode.parent}.${errorCode.prop},` === parentAndProp.trim() ); }); if (errorCode) { // Add the appearance errorCode.appearances.push({ messageTemplate, filePath: fullPath, }); } // Remove this match fileContents = fileContents.replace(matches[0], ''); matches = fileContents.trim().match(errorCodeRegex); } } } }); }; // Add appearances in each folder findAppearances(path.join(pwd, '/server/src')); findAppearances(path.join(pwd, '/client/src')); /*------------------------------------------------------------------------*/ /* ----------------------------- Build Docs ----------------------------- */ /*------------------------------------------------------------------------*/ let docsMD = ''; /*----------------------------------------*/ /* -------------- Metadata -------------- */ /*----------------------------------------*/ const pkg = require(path.join(pwd, 'package.json')); if (!pkg) { console.log('package.json not found'); process.exit(1); } const projectName = pkg.name || 'This Project'; /*----------------------------------------*/ /* ---------------- Intro --------------- */ /*----------------------------------------*/ docsMD = `# Error Cheatsheet for ${projectName}\n`; docsMD += 'This is an auto-generated document that outlines the friendly error codes that users may encounter when using this app.\n\n'; /*----------------------------------------*/ /* ---------------- Table --------------- */ /*----------------------------------------*/ // Header docsMD += '\n| Code | Internal Name | Associated Message |\n'; docsMD += '| -------- | -------- | -------- |\n'; // Error codes errorCodes.forEach((err) => { // Skip ones that are not in use if (!err.found) { return; } let associatedMessage = ( err .appearances .map((appearance) => { return `<div>${appearance.messageTemplate.replace('`', '\'')}<div style="font-size: 80%">${appearance.filePath.replace(pwd, '')}</div></div>`; }) .join('<br>') ); if (err.appearances.length === 0) { associatedMessage = 'No additional information'; } docsMD += `| ${err.code} | ${err.prop} | ${associatedMessage} |\n`; }); // Finish fs.writeFileSync( path.join(pwd, 'docs/ErrorCodeDocs.md'), docsMD, 'utf-8', ); console.log('Success! Written to docs/ErrorCodeDocs.md');