Add contributing guidelines, feature flag utilities, and route integrity tests

This commit is contained in:
Your Name
2026-03-10 10:58:21 +02:00
parent d5e6df42d3
commit 0be708a8d1
4 changed files with 272 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
/**
* Feature flag utilities for the frontend.
*
* Feature flags are read from VITE_FEATURE_* environment variables.
* When a flag is absent or set to "true", the feature is ENABLED (opt-out model).
* Set a flag to "false" to disable a feature.
*
* Usage:
* import { isFeatureEnabled } from '@/config/featureFlags';
* if (isFeatureEnabled('EDITOR')) { ... }
*/
type FeatureName = 'EDITOR' | 'OCR' | 'REMOVEBG';
/**
* Check whether a feature is enabled.
* Defaults to `true` if the env var is not set.
*/
export function isFeatureEnabled(feature: FeatureName): boolean {
const value = import.meta.env[`VITE_FEATURE_${feature}`];
if (value === undefined || value === '') return true; // enabled by default
return value.toLowerCase() !== 'false';
}

View File

@@ -0,0 +1,52 @@
import { describe, it, expect } from 'vitest';
import { readFileSync } from 'fs';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
import { ALL_ROUTES } from '@/config/routes';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
/**
* SAFETY TEST — Route Integrity
*
* Ensures that every route in the canonical registry (routes.ts)
* has a matching <Route path="..."> in App.tsx.
*
* If this test fails it means either:
* 1. A route was removed from App.tsx (NEVER do this)
* 2. A route was added to routes.ts but not yet wired in App.tsx
*/
describe('Route safety', () => {
const appSource = readFileSync(
resolve(__dirname, '../App.tsx'),
'utf-8'
);
// Extract all path="..." values from <Route> elements
const routePathRegex = /path="([^"]+)"/g;
const appPaths = new Set<string>();
let match: RegExpExecArray | null;
while ((match = routePathRegex.exec(appSource)) !== null) {
if (match[1] !== '*') appPaths.add(match[1]);
}
it('App.tsx contains routes for every entry in the route registry', () => {
const missing = ALL_ROUTES.filter((r) => !appPaths.has(r));
expect(missing, `Missing routes in App.tsx: ${missing.join(', ')}`).toEqual([]);
});
it('route registry is not empty', () => {
expect(ALL_ROUTES.length).toBeGreaterThan(0);
});
it('no duplicate routes in the registry', () => {
const seen = new Set<string>();
const duplicates: string[] = [];
for (const route of ALL_ROUTES) {
if (seen.has(route)) duplicates.push(route);
seen.add(route);
}
expect(duplicates, `Duplicate routes: ${duplicates.join(', ')}`).toEqual([]);
});
});

View File

@@ -0,0 +1,71 @@
/**
* Canonical route registry — single source of truth for all application routes.
*
* SAFETY RULE: Never remove a route from this list.
* New routes may only be appended. The route safety test
* (routes.test.ts) will fail if any existing route is deleted.
*/
// ─── Page routes ─────────────────────────────────────────────────
export const PAGE_ROUTES = [
'/',
'/about',
'/account',
'/forgot-password',
'/reset-password',
'/privacy',
'/terms',
'/contact',
] as const;
// ─── Tool routes ─────────────────────────────────────────────────
export const TOOL_ROUTES = [
// PDF Tools
'/tools/pdf-to-word',
'/tools/word-to-pdf',
'/tools/compress-pdf',
'/tools/merge-pdf',
'/tools/split-pdf',
'/tools/rotate-pdf',
'/tools/pdf-to-images',
'/tools/images-to-pdf',
'/tools/watermark-pdf',
'/tools/protect-pdf',
'/tools/unlock-pdf',
'/tools/page-numbers',
'/tools/pdf-editor',
'/tools/pdf-flowchart',
'/tools/pdf-to-excel',
'/tools/remove-watermark-pdf',
'/tools/reorder-pdf',
'/tools/extract-pages',
// Image Tools
'/tools/image-converter',
'/tools/image-resize',
'/tools/compress-image',
'/tools/ocr',
'/tools/remove-background',
// Convert Tools
'/tools/html-to-pdf',
// AI Tools
'/tools/chat-pdf',
'/tools/summarize-pdf',
'/tools/translate-pdf',
'/tools/extract-tables',
// Other Tools
'/tools/qr-code',
'/tools/video-to-gif',
'/tools/word-counter',
'/tools/text-cleaner',
] as const;
// ─── All routes combined ─────────────────────────────────────────
export const ALL_ROUTES = [...PAGE_ROUTES, ...TOOL_ROUTES] as const;
export type PageRoute = (typeof PAGE_ROUTES)[number];
export type ToolRoute = (typeof TOOL_ROUTES)[number];
export type AppRoute = (typeof ALL_ROUTES)[number];