diff --git a/apps/web/.vscode/settings.json b/apps/web/.vscode/settings.json index 20801b7a2..2c6b64a26 100644 --- a/apps/web/.vscode/settings.json +++ b/apps/web/.vscode/settings.json @@ -9,5 +9,8 @@ }, "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[jsonc]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" } } diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index f9a14cd2b..d2b8aefb9 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -28,5 +28,6 @@ "allowJs": true }, "exclude": ["node_modules", "dist", ".next", "public"], - "include": ["next-env.d.ts", "globals.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"] + "include": ["next-env.d.ts", "globals.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "references": [{ "path": "../../packages/server-shared" }] } diff --git a/packages/server-shared/package.json b/packages/server-shared/package.json index 35faaa858..5252ece4d 100644 --- a/packages/server-shared/package.json +++ b/packages/server-shared/package.json @@ -5,6 +5,7 @@ "module": "src/index.ts", "private": false, "scripts": { + "prebuild": "tsx scripts/type-import-check.ts", "build": "tsc --build", "dev": "tsc --watch", "lint": "biome check", @@ -63,12 +64,13 @@ } }, "dependencies": { - "@buster/vitest-config": "workspace:*", - "@buster/typescript-config": "workspace:*", "@buster/database": "workspace:*", + "@buster/typescript-config": "workspace:*", + "@buster/vitest-config": "workspace:*", "zod": "^3.0.0" }, "devDependencies": { + "tsx": "^4.20.3", "vitest": "catalog:" } } diff --git a/packages/server-shared/scripts/type-import-check.ts b/packages/server-shared/scripts/type-import-check.ts new file mode 100644 index 000000000..cd9e25d06 --- /dev/null +++ b/packages/server-shared/scripts/type-import-check.ts @@ -0,0 +1,165 @@ +import { readFileSync, readdirSync, statSync } from 'node:fs'; +import { join, relative } from 'node:path'; + +// ANSI color codes for output +const colors = { + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + reset: '\x1b[0m', +}; + +interface ImportViolation { + file: string; + line: number; + lineContent: string; +} + +function getAllTypeScriptFiles(dir: string): string[] { + const files: string[] = []; + + function traverse(currentDir: string) { + const entries = readdirSync(currentDir); + + for (const entry of entries) { + const fullPath = join(currentDir, entry); + const stat = statSync(fullPath); + + if (stat.isDirectory()) { + // Skip test directories and node_modules + if (!entry.includes('test') && entry !== 'node_modules') { + traverse(fullPath); + } + } else if ( + entry.endsWith('.ts') && + !entry.endsWith('.test.ts') && + !entry.endsWith('.spec.ts') + ) { + files.push(fullPath); + } + } + } + + traverse(dir); + return files; +} + +function checkFileForViolations(filePath: string): ImportViolation[] { + const content = readFileSync(filePath, 'utf-8'); + const lines = content.split('\n'); + const violations: ImportViolation[] = []; + + // Regex patterns for different import styles + const patterns = { + // import { something } from '@buster/database' + namedImport: /^import\s+\{[^}]+\}\s+from\s+['"]@buster\/database['"]/, + // import something from '@buster/database' + defaultImport: /^import\s+(?!type\s+)\w+\s+from\s+['"]@buster\/database['"]/, + // import * as something from '@buster/database' + namespaceImport: /^import\s+\*\s+as\s+\w+\s+from\s+['"]@buster\/database['"]/, + // Correct type import pattern + typeImport: /^import\s+type\s+(\{[^}]+\}|\w+|\*\s+as\s+\w+)\s+from\s+['"]@buster\/database['"]/, + }; + + lines.forEach((line, index) => { + const trimmedLine = line.trim(); + + // Skip comments and empty lines + if (trimmedLine.startsWith('//') || trimmedLine.length === 0) { + return; + } + + // Check if this line imports from @buster/database + if (trimmedLine.includes('@buster/database')) { + // Check if it's a type import + if (!patterns.typeImport.test(trimmedLine)) { + // Check if it's any other kind of import from @buster/database + if ( + patterns.namedImport.test(trimmedLine) || + patterns.defaultImport.test(trimmedLine) || + patterns.namespaceImport.test(trimmedLine) + ) { + violations.push({ + file: filePath, + line: index + 1, + lineContent: trimmedLine, + }); + } + } + } + }); + + return violations; +} + +function main() { + console.log('🔍 Checking for non-type imports from @buster/database...\n'); + + const srcDir = join(process.cwd(), 'src'); + + try { + const files = getAllTypeScriptFiles(srcDir); + console.log(`Found ${files.length} TypeScript files to check.\n`); + + let totalViolations = 0; + const allViolations: ImportViolation[] = []; + + for (const file of files) { + const violations = checkFileForViolations(file); + if (violations.length > 0) { + totalViolations += violations.length; + allViolations.push(...violations); + } + } + + if (totalViolations === 0) { + console.log( + `${colors.green}✅ All imports from @buster/database are type-only imports!${colors.reset}` + ); + process.exit(0); + } else { + console.log(`${colors.red}❌ Found ${totalViolations} violation(s):${colors.reset}\n`); + + // Group violations by file + const violationsByFile = allViolations.reduce( + (acc, violation) => { + const relPath = relative(process.cwd(), violation.file); + if (!acc[relPath]) { + acc[relPath] = []; + } + acc[relPath].push(violation); + return acc; + }, + {} as Record + ); + + // Display violations + for (const [file, violations] of Object.entries(violationsByFile)) { + console.log(`${colors.yellow}${file}:${colors.reset}`); + for (const violation of violations) { + console.log( + ` Line ${violation.line}: ${colors.red}${violation.lineContent}${colors.reset}` + ); + const fixedLine = violation.lineContent.replace(/^import\s+/, 'import type '); + console.log(` ${colors.green}Fix:${colors.reset} ${fixedLine}\n`); + } + } + + console.log( + `${colors.red}⚠️ Fix these imports to use 'import type' syntax to avoid build errors.${colors.reset}` + ); + process.exit(1); + } + } catch (error) { + if (error instanceof Error && error.message.includes('ENOENT')) { + console.error( + `${colors.red}Error: src directory not found. Make sure you're running this from the package root.${colors.reset}` + ); + } else { + console.error(`${colors.red}Error:${colors.reset}`, error); + } + process.exit(1); + } +} + +main(); diff --git a/packages/server-shared/tsconfig.json b/packages/server-shared/tsconfig.json index 835b1826e..02356e4a6 100644 --- a/packages/server-shared/tsconfig.json +++ b/packages/server-shared/tsconfig.json @@ -6,5 +6,8 @@ "tsBuildInfoFile": "dist/.cache/tsbuildinfo.json" }, "include": ["src"], - "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"] + "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"], + "references": [ + { "path": "../database" } // Reference other projects + ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 59f25cab6..4b82bcc1e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -798,16 +798,13 @@ importers: '@buster/vitest-config': specifier: workspace:* version: link:../vitest-config - drizzle-zod: - specifier: ^0.8.2 - version: 0.8.2(drizzle-orm@0.44.2(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(mysql2@3.14.1)(pg@8.16.3)(postgres@3.4.7))(zod@3.25.1) zod: specifier: ^3.0.0 version: 3.25.1 devDependencies: - tsup: - specifier: 'catalog:' - version: 8.5.0(jiti@2.4.2)(postcss@8.5.6)(tsx@4.20.3)(typescript@5.8.3)(yaml@2.8.0) + tsx: + specifier: ^4.20.3 + version: 4.20.3 vitest: specifier: 'catalog:' version: 3.2.4(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@24.0.10)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(msw@2.10.3(@types/node@24.0.10)(typescript@5.8.3))(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) @@ -6732,12 +6729,6 @@ packages: sqlite3: optional: true - drizzle-zod@0.8.2: - resolution: {integrity: sha512-9Do/16OjFFNrQDZgvMtxtDDwKWbFOxUAIwNPKX98SfxrP8H18vhN1BvNXbhelLcdgCE7GEaXDJqBjMExSkhpkA==} - peerDependencies: - drizzle-orm: '>=0.36.0' - zod: ^3.25.1 - dt-sql-parser@4.3.1: resolution: {integrity: sha512-WlFB9of+ChwWtc5M222jHGIpzqHx51szLe/11GAwwbA+4hRaVkMpWMf2bbYj4i855edSoTQ52zyLJVOpe+4OVg==} engines: {node: '>=18'} @@ -18496,11 +18487,6 @@ snapshots: pg: 8.16.3 postgres: 3.4.7 - drizzle-zod@0.8.2(drizzle-orm@0.44.2(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(mysql2@3.14.1)(pg@8.16.3)(postgres@3.4.7))(zod@3.25.1): - dependencies: - drizzle-orm: 0.44.2(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(mysql2@3.14.1)(pg@8.16.3)(postgres@3.4.7) - zod: 3.25.1 - dt-sql-parser@4.3.1(antlr4ng-cli@1.0.7): dependencies: antlr4-c3: 3.3.7(antlr4ng-cli@1.0.7)