This commit is contained in:
James Brunton 2025-09-03 13:04:08 +00:00 committed by GitHub
commit 938e4f841c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 991 additions and 65 deletions

View File

@ -24,7 +24,7 @@ indent_size = 2
insert_final_newline = false insert_final_newline = false
trim_trailing_whitespace = false trim_trailing_whitespace = false
[{*.js,*.jsx,*.ts,*.tsx}] [{*.js,*.jsx,*.mjs,*.ts,*.tsx}]
indent_size = 2 indent_size = 2
[*.css] [*.css]

View File

@ -147,6 +147,8 @@ jobs:
cache-dependency-path: frontend/package-lock.json cache-dependency-path: frontend/package-lock.json
- name: Install frontend dependencies - name: Install frontend dependencies
run: cd frontend && npm ci run: cd frontend && npm ci
- name: Lint frontend
run: cd frontend && npm run lint
- name: Build frontend - name: Build frontend
run: cd frontend && npm run build run: cd frontend && npm run build
- name: Run frontend tests - name: Run frontend tests

View File

@ -19,5 +19,6 @@
"yzhang.markdown-all-in-one", // Markdown All-in-One extension for enhanced Markdown editing "yzhang.markdown-all-in-one", // Markdown All-in-One extension for enhanced Markdown editing
"stylelint.vscode-stylelint", // Stylelint extension for CSS and SCSS linting "stylelint.vscode-stylelint", // Stylelint extension for CSS and SCSS linting
"redhat.vscode-yaml", // YAML extension for Visual Studio Code "redhat.vscode-yaml", // YAML extension for Visual Studio Code
"dbaeumer.vscode-eslint", // ESLint extension for TypeScript linting
] ]
} }

View File

@ -0,0 +1,31 @@
// @ts-check
import eslint from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';
export default defineConfig(
eslint.configs.recommended,
tseslint.configs.recommended,
{
ignores: [
"dist", // Contains 3rd party code
"public", // Contains 3rd party code
],
},
{
rules: {
"no-empty": "off", // Temporarily disabled until codebase conformant
"no-empty-pattern": "off", // Temporarily disabled until codebase conformant
"no-undef": "off", // Temporarily disabled until codebase conformant
"no-useless-escape": "off", // Temporarily disabled until codebase conformant
"no-case-declarations": "off", // Temporarily disabled until codebase conformant
"prefer-const": "off", // Temporarily disabled until codebase conformant
"@typescript-eslint/ban-ts-comment": "off", // Temporarily disabled until codebase conformant
"@typescript-eslint/no-empty-object-type": "off", // Temporarily disabled until codebase conformant
"@typescript-eslint/no-explicit-any": "off", // Temporarily disabled until codebase conformant
"@typescript-eslint/no-unused-expressions": "off", // Temporarily disabled until codebase conformant
"@typescript-eslint/no-unused-vars": "off", // Temporarily disabled until codebase conformant
},
}
);

File diff suppressed because it is too large Load Diff

View File

@ -40,12 +40,13 @@
"predev": "npm run generate-icons", "predev": "npm run generate-icons",
"dev": "npx tsc --noEmit && vite", "dev": "npx tsc --noEmit && vite",
"prebuild": "npm run generate-icons", "prebuild": "npm run generate-icons",
"lint": "npx eslint",
"build": "npx tsc --noEmit && vite build", "build": "npx tsc --noEmit && vite build",
"preview": "vite preview", "preview": "vite preview",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"generate-licenses": "node scripts/generate-licenses.js", "generate-licenses": "node scripts/generate-licenses.mjs",
"generate-icons": "node scripts/generate-icons.js", "generate-icons": "node scripts/generate-icons.mjs",
"generate-icons:verbose": "node scripts/generate-icons.js --verbose", "generate-icons:verbose": "node scripts/generate-icons.mjs --verbose",
"test": "vitest", "test": "vitest",
"test:watch": "vitest --watch", "test:watch": "vitest --watch",
"test:coverage": "vitest --coverage", "test:coverage": "vitest --coverage",
@ -72,6 +73,7 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.34.0",
"@iconify-json/material-symbols": "^1.2.33", "@iconify-json/material-symbols": "^1.2.33",
"@iconify/utils": "^3.0.1", "@iconify/utils": "^3.0.1",
"@playwright/test": "^1.40.0", "@playwright/test": "^1.40.0",
@ -80,6 +82,7 @@
"@types/react-dom": "^19.1.5", "@types/react-dom": "^19.1.5",
"@vitejs/plugin-react": "^4.5.0", "@vitejs/plugin-react": "^4.5.0",
"@vitest/coverage-v8": "^1.0.0", "@vitest/coverage-v8": "^1.0.0",
"eslint": "^9.34.0",
"jsdom": "^23.0.0", "jsdom": "^23.0.0",
"license-checker": "^25.0.1", "license-checker": "^25.0.1",
"madge": "^8.0.0", "madge": "^8.0.0",
@ -87,7 +90,8 @@
"postcss-cli": "^11.0.1", "postcss-cli": "^11.0.1",
"postcss-preset-mantine": "^1.17.0", "postcss-preset-mantine": "^1.17.0",
"postcss-simple-vars": "^7.0.1", "postcss-simple-vars": "^7.0.1",
"typescript": "^5.8.3", "typescript": "^5.9.2",
"typescript-eslint": "^8.42.0",
"vite": "^6.3.5", "vite": "^6.3.5",
"vitest": "^1.0.0" "vitest": "^1.0.0"
} }

View File

@ -1,6 +0,0 @@
module.exports = {
plugins: [
require('@tailwindcss/postcss'),
require('autoprefixer'),
],
};

View File

@ -0,0 +1,9 @@
import postcss from '@tailwindcss/postcss';
import autoprefixer from 'autoprefixer';
module.exports = {
plugins: [
postcss,
autoprefixer,
],
};

View File

@ -1,8 +1,12 @@
#!/usr/bin/env node #!/usr/bin/env node
const { icons } = require('@iconify-json/material-symbols'); import { icons } from '@iconify-json/material-symbols';
const fs = require('fs'); import fs from 'fs';
const path = require('path'); import { fileURLToPath } from 'url';
import path, { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Check for verbose flag // Check for verbose flag
const isVerbose = process.argv.includes('--verbose') || process.argv.includes('-v'); const isVerbose = process.argv.includes('--verbose') || process.argv.includes('-v');
@ -19,27 +23,27 @@ const debug = (message) => {
function scanForUsedIcons() { function scanForUsedIcons() {
const usedIcons = new Set(); const usedIcons = new Set();
const srcDir = path.join(__dirname, '..', 'src'); const srcDir = path.join(__dirname, '..', 'src');
info('🔍 Scanning codebase for LocalIcon usage...'); info('🔍 Scanning codebase for LocalIcon usage...');
if (!fs.existsSync(srcDir)) { if (!fs.existsSync(srcDir)) {
console.error('❌ Source directory not found:', srcDir); console.error('❌ Source directory not found:', srcDir);
process.exit(1); process.exit(1);
} }
// Recursively scan all .tsx and .ts files // Recursively scan all .tsx and .ts files
function scanDirectory(dir) { function scanDirectory(dir) {
const files = fs.readdirSync(dir); const files = fs.readdirSync(dir);
files.forEach(file => { files.forEach(file => {
const filePath = path.join(dir, file); const filePath = path.join(dir, file);
const stat = fs.statSync(filePath); const stat = fs.statSync(filePath);
if (stat.isDirectory()) { if (stat.isDirectory()) {
scanDirectory(filePath); scanDirectory(filePath);
} else if (file.endsWith('.tsx') || file.endsWith('.ts')) { } else if (file.endsWith('.tsx') || file.endsWith('.ts')) {
const content = fs.readFileSync(filePath, 'utf8'); const content = fs.readFileSync(filePath, 'utf8');
// Match LocalIcon usage: <LocalIcon icon="icon-name" ...> // Match LocalIcon usage: <LocalIcon icon="icon-name" ...>
const localIconMatches = content.match(/<LocalIcon\s+[^>]*icon="([^"]+)"/g); const localIconMatches = content.match(/<LocalIcon\s+[^>]*icon="([^"]+)"/g);
if (localIconMatches) { if (localIconMatches) {
@ -51,7 +55,7 @@ function scanForUsedIcons() {
} }
}); });
} }
// Match old material-symbols-rounded spans: <span className="material-symbols-rounded">icon-name</span> // Match old material-symbols-rounded spans: <span className="material-symbols-rounded">icon-name</span>
const spanMatches = content.match(/<span[^>]*className="[^"]*material-symbols-rounded[^"]*"[^>]*>([^<]+)<\/span>/g); const spanMatches = content.match(/<span[^>]*className="[^"]*material-symbols-rounded[^"]*"[^>]*>([^<]+)<\/span>/g);
if (spanMatches) { if (spanMatches) {
@ -64,7 +68,7 @@ function scanForUsedIcons() {
} }
}); });
} }
// Match Icon component usage: <Icon icon="material-symbols:icon-name" ...> // Match Icon component usage: <Icon icon="material-symbols:icon-name" ...>
const iconMatches = content.match(/<Icon\s+[^>]*icon="material-symbols:([^"]+)"/g); const iconMatches = content.match(/<Icon\s+[^>]*icon="material-symbols:([^"]+)"/g);
if (iconMatches) { if (iconMatches) {
@ -79,12 +83,12 @@ function scanForUsedIcons() {
} }
}); });
} }
scanDirectory(srcDir); scanDirectory(srcDir);
const iconArray = Array.from(usedIcons).sort(); const iconArray = Array.from(usedIcons).sort();
info(`📋 Found ${iconArray.length} unique icons across codebase`); info(`📋 Found ${iconArray.length} unique icons across codebase`);
return iconArray; return iconArray;
} }
@ -102,7 +106,7 @@ async function main() {
const existingSet = JSON.parse(fs.readFileSync(outputPath, 'utf8')); const existingSet = JSON.parse(fs.readFileSync(outputPath, 'utf8'));
const existingIcons = Object.keys(existingSet.icons || {}).sort(); const existingIcons = Object.keys(existingSet.icons || {}).sort();
const currentIcons = [...usedIcons].sort(); const currentIcons = [...usedIcons].sort();
if (JSON.stringify(existingIcons) === JSON.stringify(currentIcons)) { if (JSON.stringify(existingIcons) === JSON.stringify(currentIcons)) {
needsRegeneration = false; needsRegeneration = false;
info(`✅ Icon set already up-to-date (${usedIcons.length} icons, ${Math.round(fs.statSync(outputPath).size / 1024)}KB)`); info(`✅ Icon set already up-to-date (${usedIcons.length} icons, ${Math.round(fs.statSync(outputPath).size / 1024)}KB)`);
@ -122,7 +126,7 @@ async function main() {
// Dynamic import of ES module // Dynamic import of ES module
const { getIcons } = await import('@iconify/utils'); const { getIcons } = await import('@iconify/utils');
// Extract only our used icons from the full set // Extract only our used icons from the full set
const extractedIcons = getIcons(icons, usedIcons); const extractedIcons = getIcons(icons, usedIcons);
@ -155,7 +159,7 @@ async function main() {
// Generate TypeScript types // Generate TypeScript types
const typesContent = `// Auto-generated icon types const typesContent = `// Auto-generated icon types
// This file is automatically generated by scripts/generate-icons.js // This file is automatically generated by scripts/generate-icons.mjs
// Do not edit manually - changes will be overwritten // Do not edit manually - changes will be overwritten
export type MaterialSymbolIcon = ${usedIcons.map(icon => `'${icon}'`).join(' | ')}; export type MaterialSymbolIcon = ${usedIcons.map(icon => `'${icon}'`).join(' | ')};
@ -183,4 +187,4 @@ export default iconSet;
main().catch(error => { main().catch(error => {
console.error('❌ Script failed:', error); console.error('❌ Script failed:', error);
process.exit(1); process.exit(1);
}); });

View File

@ -1,8 +1,12 @@
#!/usr/bin/env node #!/usr/bin/env node
const { execSync } = require('child_process'); import { execSync } from 'child_process';
const fs = require('fs'); import fs from 'fs';
const path = require('path'); import { fileURLToPath } from 'url';
import path, { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
/** /**
* Generate 3rd party licenses for frontend dependencies * Generate 3rd party licenses for frontend dependencies

View File

@ -130,7 +130,7 @@ export class PDFExportService {
newDoc.setModificationDate(new Date()); newDoc.setModificationDate(new Date());
const pdfBytes = await newDoc.save(); const pdfBytes = await newDoc.save();
return new Blob([pdfBytes], { type: 'application/pdf' }); return new Blob([pdfBytes as BlobPart], { type: 'application/pdf' });
} }
/** /**
@ -176,7 +176,7 @@ export class PDFExportService {
newDoc.setModificationDate(new Date()); newDoc.setModificationDate(new Date());
const pdfBytes = await newDoc.save(); const pdfBytes = await newDoc.save();
return new Blob([pdfBytes], { type: 'application/pdf' }); return new Blob([pdfBytes as BlobPart], { type: 'application/pdf' });
} }

View File

@ -12,8 +12,8 @@ import {
conversionDiscovery, conversionDiscovery,
type ConversionEndpoint type ConversionEndpoint
} from '../helpers/conversionEndpointDiscovery'; } from '../helpers/conversionEndpointDiscovery';
import * as path from 'path'; import path from 'path';
import * as fs from 'fs'; import fs from 'fs';
// Test configuration // Test configuration
const BASE_URL = process.env.BASE_URL || 'http://localhost:5173'; const BASE_URL = process.env.BASE_URL || 'http://localhost:5173';
@ -239,7 +239,6 @@ async function testConversion(page: Page, conversion: ConversionEndpoint) {
// Save and verify file is not empty // Save and verify file is not empty
const path = await download.path(); const path = await download.path();
if (path) { if (path) {
const fs = require('fs');
const stats = fs.statSync(path); const stats = fs.statSync(path);
expect(stats.size).toBeGreaterThan(0); expect(stats.size).toBeGreaterThan(0);