diff --git a/.github/workflows/build-tools-api.yml b/.github/workflows/build-tools-api.yml new file mode 100644 index 00000000..f3d4f1b3 --- /dev/null +++ b/.github/workflows/build-tools-api.yml @@ -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 diff --git a/web/README.md b/web/README.md index 85d188fd..9a37ce5e 100644 --- a/web/README.md +++ b/web/README.md @@ -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` @@ -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`. diff --git a/web/package.json b/web/package.json index beb52a46..78cbe8f8 100644 --- a/web/package.json +++ b/web/package.json @@ -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", diff --git a/web/scripts/generate-tools-api.mjs b/web/scripts/generate-tools-api.mjs new file mode 100644 index 00000000..0f807895 --- /dev/null +++ b/web/scripts/generate-tools-api.mjs @@ -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; +}); \ No newline at end of file