diff --git a/README.md b/README.md index a52d129..58a661e 100644 --- a/README.md +++ b/README.md @@ -267,4 +267,5 @@ If you want to change core functionality of the radar, you can clone this reposi radar's markdown-files, config.json and about.md in the `data` folder. Run `npm run build:data` to parse the markdown files and create a `data.json` and then run `npm run dev` to start the development server, which will be available at `http://localhost:3000/techradar` or the path -you specified via `basePath`. +you specified via `basePath`. Run `npm run build:data -- --strict` to break the build process +when encountering errors. diff --git a/scripts/buildData.ts b/scripts/buildData.ts index a460d07..2fe49b2 100644 --- a/scripts/buildData.ts +++ b/scripts/buildData.ts @@ -7,6 +7,7 @@ import path from "path"; import nextConfig from "../next.config.js"; import config from "../src/lib/config"; +import ErrorHandler, { ErrorType, TextColor } from "./errorHandler.js"; import Positioner from "./positioner"; import { Flag, Item } from "@/lib/types"; @@ -21,6 +22,7 @@ const quadrants = config.quadrants.map((q, i) => ({ ...q, position: i + 1 })); const quadrantIds = quadrants.map((q) => q.id); const tags = (config as { tags?: string[] }).tags || []; const positioner = new Positioner(size, quadrants, rings); +const errorHandler = new ErrorHandler(quadrants, rings); const marked = new Marked( markedHighlight({ @@ -170,17 +172,25 @@ function postProcessItems(items: Item[]): { const filteredItems = items.filter((item) => { // check if the items' quadrant and ring are valid if (!item.quadrant || !item.ring) { - console.warn(`Item ${item.id} has no quadrant or ring`); + errorHandler.processBuildErrors(ErrorType.NoQuadrant, item.id); return false; } if (!quadrantIds.includes(item.quadrant)) { - console.warn(`Item ${item.id} has invalid quadrant ${item.quadrant}`); + errorHandler.processBuildErrors( + ErrorType.InvalidQuadrant, + item.id, + item.quadrant, + ); return false; } if (!ringIds.includes(item.ring)) { - console.warn(`Item ${item.id} has invalid ring ${item.ring}`); + errorHandler.processBuildErrors( + ErrorType.InvalidRing, + item.id, + item.ring, + ); return false; } @@ -193,6 +203,8 @@ function postProcessItems(items: Item[]): { return true; }); + errorHandler.checkForBuildErrors(); + const releases = getUniqueReleases(filteredItems); const uniqueTags = getUniqueTags(filteredItems); const processedItems = filteredItems.map((item) => { @@ -230,21 +242,30 @@ function postProcessItems(items: Item[]): { return { releases, tags: uniqueTags, items: processedItems }; } -// Parse the data and write radar data to JSON file -parseDirectory(dataPath("radar")).then((items) => { +async function main() { + // Parse the data and write radar data to JSON file + const items = await parseDirectory(dataPath("radar")); const data = postProcessItems(items); if (data.items.length === 0) { - console.error("No valid radar items found."); - console.log("Please check the markdown files in the `radar` directory."); - process.exit(1); + errorHandler.processBuildErrors(ErrorType.NoRadarItems); } + errorHandler.checkForBuildErrors(true); + const json = JSON.stringify(data, null, 2); fs.writeFileSync(dataPath("data.json"), json); -}); -// write about data to JSON file -const about = readMarkdownFile(dataPath("about.md")); -fs.writeFileSync(dataPath("about.json"), JSON.stringify(about, null, 2)); -console.log("ℹ️ Data written to data/data.json and data/about.json"); + // write about data to JSON file + const about = readMarkdownFile(dataPath("about.md")); + fs.writeFileSync(dataPath("about.json"), JSON.stringify(about, null, 2)); + console.log( + "ℹ️ Data written to data/data.json and data/about.json\n\n" + + errorHandler.colorizeBackground( + " Build was successfull ", + TextColor.Green, + ), + ); +} + +main(); diff --git a/scripts/errorHandler.ts b/scripts/errorHandler.ts new file mode 100644 index 0000000..e2db03b --- /dev/null +++ b/scripts/errorHandler.ts @@ -0,0 +1,108 @@ +import { Quadrant, Ring } from "@/lib/types"; + +export enum ErrorType { + NoQuadrant = "Item {0} has no quadrant or ring", + InvalidQuadrant = "Item {0} has invalid quadrant {1}\n\tvalid quadrants are: {2}", + InvalidRing = "Item {0} has invalid ring {1}\n\tvalid rings are: {2}", + NoRadarItems = "No valid radar items found. Please check the markdown files in the `radar` directory.", +} + +export enum TextColor { + Default = 0, + Black, + Red = 31, + Green = 32, + Yellow = 33, + Blue = 34, + Mangenta = 35, + Cyan = 36, + White = 37, +} + +export default class ErrorHandler { + private buildErrors: string[] = []; + private quadrants: Quadrant[]; + private rings: Ring[]; + private isStrict: boolean; + private supportsColor: boolean; + + constructor(quadrants: Quadrant[], rings: Ring[]) { + this.isStrict = process.argv.slice(2).includes("--strict"); + this.supportsColor = process.stdout.isTTY && process.env.TERM !== "dumb"; + this.quadrants = quadrants; + this.rings = rings; + console.log(`ℹ️ Build is${this.isStrict ? "" : " not"} in strict mode\n`); + } + + public processBuildErrors(errorType: ErrorType, ...args: string[]) { + const errorHint = this.getErrorHint(errorType); + const errorMsg = this.formatString( + errorType.toString(), + errorHint ? [...args, errorHint] : args, + ); + this.buildErrors.push(errorMsg); + } + + public checkForBuildErrors(exitOnErr: boolean = false) { + if (this.buildErrors.length > 0) { + console.warn( + this.colorizeBackground( + `There ${this.buildErrors.length > 1 ? "are" : "is"} ${this.buildErrors.length} error${this.buildErrors.length > 1 ? "s" : ""} in your data build`, + TextColor.Red, + ) + + "\n\n" + + this.buildErrors + .map((error, index) => `${index + 1}. ${error}`) + .join("\n") + + "\n", + ); + + if (this.isStrict || exitOnErr) { + process.exit(1); + } + + this.buildErrors = []; + } + } + + private getErrorHint(errorType: ErrorType) { + switch (errorType) { + case ErrorType.InvalidQuadrant: + return this.quadrants.map((quadrant) => quadrant.id).join(", "); + case ErrorType.InvalidRing: + return this.rings.map((ring) => ring.id).join(", "); + default: + break; + } + } + + public colorizeBackground(str: string, backgroundColor: TextColor) { + if (this.supportsColor) { + return `\x1b[${backgroundColor + 10}m${str}\x1b[${TextColor.Default}m`; + } + + return str; + } + + private formatString(msg: string, inserts: string[]) { + return inserts.reduce( + (acc, cur, index) => + acc.replaceAll( + `{${index}}`, + this.colorizeString( + cur, + index === 2 ? TextColor.Green : TextColor.Red, + ), + ), + msg, + ); + } + + private colorizeString(str: string, textColor: TextColor) { + if (this.supportsColor) { + return `\x1b[${textColor}m${str}\x1b[${TextColor.Default}m`; + } + + return str; + } +}