Compare commits

...

10 Commits

Author SHA1 Message Date
Mathias Schopmans
3d14baefda chore: prepare v4.7.0 2025-03-31 15:38:21 +02:00
Semanticore Bot
408d8daef1 Release v4.6.1 2025-03-26 10:12:43 +01:00
Mathias Schopmans
9683aa6d44 fix: define user's config as DeepPartial 2025-03-26 10:10:52 +01:00
Mathias Schopmans
375eea2bdd chore(deps): update next to 15.2.4 and eslint to 9.23.0 2025-03-26 10:07:43 +01:00
Mathias Schopmans
3fd8287965 chore: release v4.6.0 2025-03-15 16:42:48 +01:00
Semanticore Bot
39a39d63b3 Release v4.6.0 2025-03-15 16:31:15 +01:00
Mathias Schopmans
61eff6d4fb fix: forward flags to npm run build and adjust README.md 2025-03-14 17:02:16 +01:00
felix.ruf
3efe7b5b61 feat: add strict mode for data building 2025-03-14 17:02:16 +01:00
felix.ruf
c844e6dcbe feat: add configuration options for fuse 2025-03-14 09:25:14 +01:00
Mathias Schopmans
864ab22583 chore: start new 4.6.0-rc.1 release 2025-03-05 16:22:19 +01:00
13 changed files with 1382 additions and 883 deletions

1
.gitignore vendored
View File

@@ -22,6 +22,7 @@
# misc
.DS_Store
.idea/
*.pem
*.tgz

8
.npmignore Normal file
View File

@@ -0,0 +1,8 @@
*.tgz
.idea
.github
/.next/
/out/
/techradar/
/data/about.json
/data/data.json

View File

@@ -1,5 +1,31 @@
# Changelog
## Version v4.6.1 (2025-03-26)
### Fixes
- define user's config as DeepPartial (9683aa6d)
### Chores and tidying
- **deps:** update next to 15.2.4 and eslint to 9.23.0 (375eea2b)
- release v4.6.0 (3fd82879)
## Version v4.6.0 (2025-03-14)
### Features
- add strict mode for data building (3efe7b5b)
- add configuration options for fuse (c844e6dc)
### Fixes
- forward flags to `npm run build` and adjust README.md (61eff6d4)
### Chores and tidying
- start new 4.6.0-rc.1 release (864ab225)
## Version v4.5.0 (2025-03-04)
### Features

View File

@@ -45,7 +45,7 @@ file like the following and adapt to your needs:
"version": "1.0.0",
"license": "MIT",
"scripts": {
"build": "techradar build",
"build": "techradar build --strict",
"serve": "techradar serve"
},
"dependencies": {
@@ -58,6 +58,8 @@ Run `npm install` to install the dependencies and run `npm run build` to create
This will also create a basic bootstrap of all required files, including the `config.json` and
the `about.md` if they do not exist yet.
Note: The `--strict` flag will break the build process if there are any errors in the markdown files. If you do not care about errors, you can remove the `--strict` flag.
### Step 2: Change logo and the favicon
Place your `logo.svg` and `favicon.ico` in the `public` folder next to the `package.json`.
@@ -71,13 +73,14 @@ file as `logoFile` inside the `config.json`. e.g. `"logoFile": "acme-logo.png"`
Open the `config.json` file and configure the radar to your needs.
| Attribute | Description |
| --------- | ------------------------------------------------------------------------------------------------------------------------------ |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------ |
| basePath | Set if hosting under a sub-path, otherwise set it to `/`. Default is `/techradar` |
| baseUrl | Set to the full URL, where the radar will be hosted. Will be used for sitemap.xml. `https://www.aoe.com/techradar` |
| logoFile | (optional) Filepath in public folder. Default is `logo.svg` |
| jsFile | (optional) Filepath in public folder or URL to enable include of custom script |
| toggles | (optional) Modify the behaviour and contents of the radar. See config below. |
| sections | (optional) Modify the order of sections (`radar`, `tags`, `list`) |
| fuzzySearch | (optional) Modify the fuse.js options (https://www.fusejs.io/api/options.html) |
| colors | A map of colors for the radar. Can be any valid CSS color value |
| quadrants | Config of the 4 quadrants of the radar. See config below. |
| rings | Config of the rings of the radar. See config below. |
@@ -103,6 +106,10 @@ Open the `config.json` file and configure the radar to your needs.
An array with of `radar`, `tags`, `list` in order you want them to appear on the page.
#### `config.fuzzySearch`
An object that represents the fuse.js options, which is used to search the radar.
#### `config.quadrants`
| Attribute | Description |
@@ -262,4 +269,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.

View File

@@ -11,6 +11,7 @@ const SOURCE_DIR = path.join(CWD, "node_modules", "aoe_technology_radar");
const HASH_FILE = path.join(BUILDER_DIR, "hash");
const PARAMETER = process.argv[2]; // "build" or "serve"
const FLAGS = process.argv.slice(3).join(" ");
function info(message) {
console.log(`\x1b[32m${message}\x1b[0m`);
@@ -146,7 +147,9 @@ try {
}
info("Building data");
execSync("npm run build:data", { stdio: "inherit" });
execSync(`npm run build:data -- ${FLAGS}`, {
stdio: "inherit",
});
if (PARAMETER === "serve") {
info("Starting techradar");

View File

@@ -137,5 +137,11 @@
"pageSearch": "Search",
"searchPlaceholder": "What are you looking for?",
"metaDescription": ""
},
"fuzzySearch": {
"threshold": 0.4,
"distance": 600,
"ignoreLocation": false,
"includeScore": true
}
}

1972
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
{
"name": "aoe_technology_radar",
"version": "4.5.0",
"version": "4.7.0-rc.1",
"bin": {
"techradar": "./bin/techradar.js"
"techradar": "bin/techradar.js"
},
"scripts": {
"dev": "next dev",
@@ -16,23 +16,23 @@
"postinstall": "npm run build:icons"
},
"devDependencies": {
"@commitlint/cli": "^19.7.1",
"@commitlint/config-conventional": "^19.7.1",
"@commitlint/cli": "^19.8.0",
"@commitlint/config-conventional": "^19.8.0",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/node": "^22",
"@types/react": "^19",
"@types/react-dom": "^19",
"clsx": "^2.1.1",
"eslint": "^9.21.0",
"eslint-config-next": "15.2.1",
"eslint": "^9.23.0",
"eslint-config-next": "15.2.4",
"fuse.js": "^7.1.0",
"gray-matter": "^4.0.3",
"highlight.js": "^11.11.1",
"husky": "^9.1.7",
"lint-staged": "^15.4.3",
"lint-staged": "^15.5.0",
"marked": "^15.0.7",
"marked-highlight": "^2.2.1",
"next": "15.2.1",
"next": "15.2.4",
"postcss-nested": "^7.0.2",
"postcss-preset-env": "^10.1.5",
"prettier": "^3.5.3",

View File

@@ -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 };
}
async function main() {
// Parse the data and write radar data to JSON file
parseDirectory(dataPath("radar")).then((items) => {
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");
console.log(
" Data written to data/data.json and data/about.json\n\n" +
errorHandler.colorizeBackground(
" Build was successfull ",
TextColor.Green,
),
);
}
main();

108
scripts/errorHandler.ts Normal file
View File

@@ -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;
}
}

View File

@@ -1,8 +1,17 @@
import defaultConfig from "../../data/config.default.json";
import _userConfig from "../../data/config.json";
const userConfig = _userConfig as typeof defaultConfig;
const config = { ...defaultConfig, ...userConfig };
type DeepPartial<T> = T extends object
? {
[P in keyof T]?: DeepPartial<T[P]>;
}
: T;
type Config = typeof defaultConfig;
type UserConfig = DeepPartial<typeof defaultConfig>;
const userConfig = _userConfig as UserConfig;
const config = { ...defaultConfig, ...userConfig } as Config;
if (userConfig.colors)
config.colors = { ...defaultConfig.colors, ...userConfig.colors };
@@ -13,4 +22,10 @@ if (userConfig.labels)
if (userConfig.toggles)
config.toggles = { ...defaultConfig.toggles, ...userConfig.toggles };
if (userConfig.fuzzySearch)
config.fuzzySearch = {
...defaultConfig.fuzzySearch,
...userConfig.fuzzySearch,
};
export default config;

View File

@@ -46,6 +46,10 @@ export function getFlag(flag: Flag) {
return config.flags[flag];
}
export const getFuzzySearchConfig = () => {
return config.fuzzySearch;
};
export function getRings(): Ring[] {
return config.rings;
}

View File

@@ -6,6 +6,7 @@ import { useCallback, useMemo } from "react";
import { Filter } from "@/components/Filter/Filter";
import { ItemList } from "@/components/ItemList/ItemList";
import { getItems, getLabel } from "@/lib/data";
import { getFuzzySearchConfig } from "@/lib/data";
import { formatTitle } from "@/lib/format";
import { CustomPage } from "@/pages/_app";
@@ -32,9 +33,7 @@ const Overview: CustomPage = () => {
const { items, index } = useMemo(() => {
const items = getItems().filter((item) => !ring || item.ring === ring);
const index = new Fuse(items, {
threshold: 0.4,
distance: 600,
includeScore: true,
...getFuzzySearchConfig(),
keys: [
{
name: "title",