Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/build-tools-api.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Build Aggregated Tools API

on:
push:
branches:
- main

jobs:
build_tools_api:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: "24"
cache: "npm"
cache-dependency-path: web/package-lock.json

- name: Install dependencies
run: npm ci
working-directory: web

- name: Generate aggregated API
run: npm run build:api
working-directory: web

- name: Upload tools API artifact
uses: actions/upload-artifact@v4
with:
name: tools-api-json
path: web/public/api/tools.json
if-no-files-found: error
11 changes: 11 additions & 0 deletions web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ The app starts with Vite (typically at `http://localhost:5173`).

- `npm run dev`: Start local dev server with HMR
- `npm run build`: Build production assets into `dist/`
- `npm run build:api`: Generate aggregated tools API JSON at `public/api/tools.json`
- `npm run preview`: Preview the production build locally
- `npm run lint`: Run ESLint on JS/JSX files
- `npm run format-json:check`: Check formatting for `../quality-tools/*.json`
Expand Down Expand Up @@ -65,6 +66,16 @@ This means:
- New/updated JSON files in `quality-tools/` are available to the UI at build time.
- Tool IDs in URLs are based on filenames (stored as `_filename` in loader output).

### Aggregated JSON API

The build process also creates a static aggregated API payload from all files in `../quality-tools`:

- Build artifact path: `dist/api/tools.json`
- URL on deployed docs site: `.../api/tools.json`
- Payload keys: `generatedAt`, `source`, `count`, `files`, `tools`

Because this file is generated during `npm run build`, the deployed docs always include an up-to-date machine-readable API of the catalogue.

## Styling System

- Tailwind CSS v4 is enabled via `@import "tailwindcss"` in `src/index.css`.
Expand Down
3 changes: 2 additions & 1 deletion web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"build": "npm run build:api && vite build",
"build:api": "node ./scripts/generate-tools-api.mjs",
"lint": "eslint .",
"format-json:check": "prettier --check ../quality-tools",
"format-json:fix": "prettier --write ../quality-tools",
Expand Down
55 changes: 55 additions & 0 deletions web/scripts/generate-tools-api.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(__dirname, '..', '..');
const sourceDir = path.join(repoRoot, 'quality-tools');
const targetDir = path.join(repoRoot, 'web', 'public', 'api');
const targetFile = path.join(targetDir, 'tools.json');

const readToolFiles = async () => {
const entries = await fs.readdir(sourceDir, { withFileTypes: true });
const jsonFiles = entries
.filter((entry) => entry.isFile() && entry.name.endsWith('.json'))
.map((entry) => entry.name)
.sort((a, b) => a.localeCompare(b));

const tools = await Promise.all(
jsonFiles.map(async (filename) => {
const filePath = path.join(sourceDir, filename);
const fileContent = await fs.readFile(filePath, 'utf-8');
const parsed = JSON.parse(fileContent);

return {
...parsed,
_filename: filename.replace(/\.json$/i, ''),
};
}),
);

return { tools, files: jsonFiles };
};

const writeApiFile = async () => {
const { tools, files } = await readToolFiles();

const payload = {
generatedAt: new Date().toISOString(),
source: 'quality-tools',
count: tools.length,
files,
tools,
};

await fs.mkdir(targetDir, { recursive: true });
await fs.writeFile(targetFile, `${JSON.stringify(payload, null, 2)}\n`, 'utf-8');

console.log(`Generated ${targetFile} with ${tools.length} tools.`);
};

writeApiFile().catch((error) => {
console.error('Failed to generate aggregated tools API JSON.');
console.error(error);
process.exitCode = 1;
});
Loading