Compare commits
10 Commits
2c245afa1f
...
3d14baefda
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d14baefda | ||
|
|
408d8daef1 | ||
|
|
9683aa6d44 | ||
|
|
375eea2bdd | ||
|
|
3fd8287965 | ||
|
|
39a39d63b3 | ||
|
|
61eff6d4fb | ||
|
|
3efe7b5b61 | ||
|
|
c844e6dcbe | ||
|
|
864ab22583 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -22,6 +22,7 @@
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.idea/
|
||||
*.pem
|
||||
*.tgz
|
||||
|
||||
|
||||
8
.npmignore
Normal file
8
.npmignore
Normal file
@@ -0,0 +1,8 @@
|
||||
*.tgz
|
||||
.idea
|
||||
.github
|
||||
/.next/
|
||||
/out/
|
||||
/techradar/
|
||||
/data/about.json
|
||||
/data/data.json
|
||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -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
|
||||
|
||||
48
README.md
48
README.md
@@ -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`.
|
||||
@@ -70,24 +72,25 @@ 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`) |
|
||||
| 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. |
|
||||
| flags | Config of the flags of the radar. See config below |
|
||||
| chart | If you hava a lot of items, you can increase the `size` to scale down the radar |
|
||||
| social | Social links in the footer. See config below |
|
||||
| imprint | URL to the legal information |
|
||||
| labels | Configure the labels to change the texts and labels of the radar |
|
||||
| tags | (optional) Use to render only items, which contain at least one of the specified tags. e.g `["frontend", "backend"]` |
|
||||
| editUrl | (optional) If set, an edit button will be shown next to the revision.<br/> You can use placeholders for `{id}` and `{release}` |
|
||||
| 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. |
|
||||
| flags | Config of the flags of the radar. See config below |
|
||||
| chart | If you hava a lot of items, you can increase the `size` to scale down the radar |
|
||||
| social | Social links in the footer. See config below |
|
||||
| imprint | URL to the legal information |
|
||||
| labels | Configure the labels to change the texts and labels of the radar |
|
||||
| tags | (optional) Use to render only items, which contain at least one of the specified tags. e.g `["frontend", "backend"]` |
|
||||
| editUrl | (optional) If set, an edit button will be shown next to the revision.<br/> You can use placeholders for `{id}` and `{release}` |
|
||||
|
||||
#### `config.toggles`
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
1972
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
|
||||
108
scripts/errorHandler.ts
Normal file
108
scripts/errorHandler.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user