diff --git a/.console-log-whitelist.json b/.console-log-whitelist.json new file mode 100644 index 0000000000..b065376094 --- /dev/null +++ b/.console-log-whitelist.json @@ -0,0 +1,14 @@ +{ + "files": ["drizzle.config.ts"], + "patterns": [ + "scripts/**", + "**/*.test.ts", + "**/*.test.tsx", + "**/*.spec.ts", + "**/*.spec.tsx", + "**/examples/**", + "e2e/**", + ".github/scripts/**", + "apps/desktop/**" + ] +} diff --git a/.github/workflows/check-console-log.yml b/.github/workflows/check-console-log.yml new file mode 100644 index 0000000000..944c213fc8 --- /dev/null +++ b/.github/workflows/check-console-log.yml @@ -0,0 +1,117 @@ +name: Check Console Log (Warning) + +on: + pull_request: + branches: + - main + - next + +permissions: + contents: read + pull-requests: write + +jobs: + check-console-log: + name: Check for console.log statements (non-blocking) + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + + - name: Install bun + uses: oven-sh/setup-bun@v2 + + - name: Run console.log check + id: check + run: | + OUTPUT=$(bunx tsx scripts/checkConsoleLog.mts 2>&1) + echo "$OUTPUT" + + # Save output to file for later use + echo "$OUTPUT" > /tmp/console-log-output.txt + + # Check if violations were found + if echo "$OUTPUT" | grep -q "Total violations:"; then + echo "has_violations=true" >> $GITHUB_OUTPUT + TOTAL=$(echo "$OUTPUT" | grep -oP "Total violations: \K\d+") + echo "total=$TOTAL" >> $GITHUB_OUTPUT + else + echo "has_violations=false" >> $GITHUB_OUTPUT + fi + + - name: Comment on PR + if: steps.check.outputs.has_violations == 'true' + uses: actions/github-script@v7 + env: + VIOLATION_COUNT: ${{ steps.check.outputs.total }} + with: + script: | + const fs = require('fs'); + const output = fs.readFileSync('/tmp/console-log-output.txt', 'utf8'); + const total = process.env.VIOLATION_COUNT || '0'; + + // Parse violations from output (format: " file:line" followed by " content") + const lines = output.split('\n'); + const violations = []; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + // Match lines like " packages/database/src/client/db.ts:258" + const fileMatch = line.match(/^\s{2}(\S+:\d+)\s*$/); + if (fileMatch) { + const file = fileMatch[1]; + const content = lines[i + 1]?.trim() || ''; + violations.push({ file, content }); + i++; + } + } + + // Build comment body + const maxDisplay = 30; + let body = `## āš ļø Console.log Check Warning\n\n`; + body += `Found **${total}** \`console.log\` statement(s) in this PR.\n\n`; + + if (violations.length > 0) { + body += `
\nšŸ“‹ Click to see violations (${Math.min(violations.length, maxDisplay)} of ${total} shown)\n\n`; + body += `| File | Code |\n|------|------|\n`; + violations.slice(0, maxDisplay).forEach(v => { + const escapedContent = v.content + .substring(0, 60) + .replace(/\|/g, '\\|') + .replace(/`/g, "'"); + body += `| \`${v.file}\` | \`${escapedContent}${v.content.length > 60 ? '...' : ''}\` |\n`; + }); + if (parseInt(total) > maxDisplay) { + body += `\n*...and ${parseInt(total) - maxDisplay} more violations*\n`; + } + body += `\n
\n\n`; + } + + body += `> šŸ’” **Tip:** Remove \`console.log\` or add files to \`.console-log-whitelist.json\`\n`; + body += `> āœ… This check is **non-blocking** and won't prevent merging.`; + + // Find existing comment to update instead of creating duplicates + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + + const botComment = comments.find(c => + c.user.type === 'Bot' && c.body.includes('Console.log Check Warning') + ); + + if (botComment) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body, + }); + } diff --git a/package.json b/package.json index 01a9a7a25a..ae81d49360 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "lint:circular": "npm run lint:circular:main && npm run lint:circular:packages", "lint:circular:main": "dpdm src/**/*.ts --no-warning --no-tree --exit-code circular:1 --no-progress -T true --skip-dynamic-imports circular", "lint:circular:packages": "dpdm packages/**/src/**/*.ts --no-warning --no-tree --exit-code circular:1 --no-progress -T true --skip-dynamic-imports circular", + "lint:console": "tsx scripts/checkConsoleLog.mts", "lint:md": "remark . --silent --output", "lint:mdx": "npm run workflow:mdx && remark \"docs/**/*.mdx\" -r ./.remarkrc.mdx.js --silent --output && eslint \"docs/**/*.mdx\" --quiet --fix", "lint:style": "stylelint \"{src,tests}/**/*.{js,jsx,ts,tsx}\" --fix", diff --git a/scripts/checkConsoleLog.mts b/scripts/checkConsoleLog.mts new file mode 100644 index 0000000000..513bc5777a --- /dev/null +++ b/scripts/checkConsoleLog.mts @@ -0,0 +1,148 @@ +#!/usr/bin/env tsx + +import { execSync } from 'node:child_process'; +import { readFileSync } from 'node:fs'; + +interface WhitelistConfig { + files?: string[]; + patterns?: string[]; +} + +const WHITELIST_PATH = '.console-log-whitelist.json'; + +/** + * Load whitelist configuration + */ +const loadWhitelist = (): WhitelistConfig => { + try { + const content = readFileSync(WHITELIST_PATH, 'utf8'); + return JSON.parse(content); + } catch { + return { files: [], patterns: [] }; + } +}; + +/** + * Check if a file is whitelisted + */ +const isWhitelisted = (filePath: string, whitelist: WhitelistConfig): boolean => { + const normalizedPath = filePath.replaceAll('\\', '/'); + + // Check exact file matches + if (whitelist.files?.some((f) => normalizedPath.includes(f.replaceAll('\\', '/')))) { + return true; + } + + // Check pattern matches (simple glob-like patterns) + if (whitelist.patterns) { + for (const pattern of whitelist.patterns) { + // Escape dots and replace glob patterns + // Use a placeholder for ** to avoid conflicts with single * + let regexPattern = pattern + .replaceAll('.', '\\.') + .replaceAll('**', '\u0000DOUBLESTAR\u0000') + .replaceAll('*', '[^/]*') + .replaceAll('\u0000DOUBLESTAR\u0000', '.*'); + + // If pattern ends with /**, match everything under that directory + // If pattern ends with **, just match everything from that point + const regex = new RegExp(`^${regexPattern}`); + if (regex.test(normalizedPath)) { + return true; + } + } + } + + return false; +}; + +/** + * Main check function + */ +const checkConsoleLogs = () => { + const whitelist = loadWhitelist(); + + console.log('šŸ” Checking for console.log statements...\n'); + + try { + // Search for console.log in TypeScript and JavaScript files + const output = execSync( + `git grep -n "console\\.log" -- "*.ts" "*.tsx" "*.js" "*.jsx" "*.mts" "*.cts" || true`, + { encoding: 'utf8' }, + ); + + if (!output.trim()) { + console.log('āœ… No console.log statements found!'); + return; + } + + const lines = output.trim().split('\n'); + const violations: Array<{ content: string, file: string; line: string; }> = []; + + for (const line of lines) { + // Parse git grep output: filename:lineNumber:content + const match = line.match(/^([^:]+):(\d+):(.+)$/); + if (!match) continue; + + const [, filePath, lineNumber, content] = match; + + // Skip if whitelisted + if (isWhitelisted(filePath, whitelist)) { + continue; + } + + // Skip comments + const trimmedContent = content.trim(); + if (trimmedContent.startsWith('//') || trimmedContent.startsWith('*')) { + continue; + } + + violations.push({ + content: content.trim(), + file: filePath, + line: lineNumber, + }); + } + + if (violations.length === 0) { + console.log('āœ… No console.log violations found (all matches are whitelisted or in comments)!'); + return; + } + + // Report violations as warnings + console.log('āš ļø Found console.log statements in the following files:\n'); + + // Use GitHub Actions annotation format for better visibility in CI + const isCI = process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true'; + + for (const violation of violations) { + if (isCI) { + // GitHub Actions warning annotation format + console.log(`::warning file=${violation.file},line=${violation.line}::console.log found: ${violation.content}`); + } else { + console.log(` ${violation.file}:${violation.line}`); + console.log(` ${violation.content}\n`); + } + } + + console.log(`\nšŸ’” Total violations: ${violations.length}`); + console.log( + `\nšŸ“ To whitelist files, add them to ${WHITELIST_PATH} in the following format:`, + ); + console.log(`{ + "files": ["path/to/file.ts"], + "patterns": ["scripts/**/*.mts", "**/*.test.ts"] +}\n`); + + // Exit with 0 to not block CI, but warnings will still be visible + process.exit(0); + } catch (error: unknown) { + if (error instanceof Error && 'status' in error && error.status !== 0) { + console.error('āŒ Error running git grep:', error.message); + process.exit(1); + } + throw error; + } +}; + +checkConsoleLogs(); diff --git a/src/features/KnowledgeManager/DocumentExplorer/NoteEditorModal.tsx b/src/features/KnowledgeManager/DocumentExplorer/NoteEditorModal.tsx index bc046c4f21..23e02847b2 100644 --- a/src/features/KnowledgeManager/DocumentExplorer/NoteEditorModal.tsx +++ b/src/features/KnowledgeManager/DocumentExplorer/NoteEditorModal.tsx @@ -143,40 +143,20 @@ const NoteEditorModal = memo( const handleOpenChange = (isOpen: boolean) => { // When modal opens, load document content if in edit mode if (isOpen && documentId && editor) { - console.log('[NoteEditorModal] Loading content:', { - cachedEditorDataPreview: cachedEditorData - ? JSON.stringify(cachedEditorData).slice(0, 100) - : null, - cachedEditorDataType: typeof cachedEditorData, - documentId, - documentTitle, - hasCachedEditorData: !!cachedEditorData, - }); - // If editorData is already cached (from list), use it directly if (cachedEditorData) { - console.log('[NoteEditorModal] Using cached editorData', cachedEditorData); setNoteTitle(documentTitle || ''); editor.setDocument('json', JSON.stringify(cachedEditorData)); return; } // Otherwise, fetch full content from API - console.log('[NoteEditorModal] Fetching from API'); documentService .getDocumentById(documentId) .then((doc) => { if (doc && doc.content) { setNoteTitle(doc.title || doc.filename || ''); - console.log('[NoteEditorModal] Fetched doc.editorData:', { - editorDataPreview: doc.editorData - ? JSON.stringify(doc.editorData).slice(0, 100) - : null, - editorDataType: typeof doc.editorData, - hasEditorData: !!doc.editorData, - }); - editor.setDocument('json', doc.editorData); } }) diff --git a/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/index.tsx b/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/index.tsx index 2625f744a9..5c50e923cb 100644 --- a/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/index.tsx +++ b/src/features/KnowledgeManager/FileExplorer/MasonryFileItem/index.tsx @@ -177,19 +177,6 @@ const MasonryFileItem = memo( const isMarkdown = isMarkdownFile(name, fileType); const isNote = isCustomNote(fileType); - // Debug: Log editorData for notes - useEffect(() => { - if (isNote) { - console.log('[MasonryFileItem] Note item:', { - editorDataPreview: editorData ? JSON.stringify(editorData).slice(0, 100) : null, - editorDataType: typeof editorData, - hasEditorData: !!editorData, - id, - name, - }); - } - }, [isNote, id, name, editorData]); - const cardRef = useRef(null); const [isInView, setIsInView] = useState(false); @@ -288,12 +275,6 @@ const MasonryFileItem = memo( )} onClick={() => { if (isNote) { - console.log('[MasonryFileItem] Opening note modal with:', { - editorDataType: typeof editorData, - hasEditorData: !!editorData, - id, - name, - }); setIsNoteModalOpen(true); } else { onOpen(id); @@ -384,7 +365,6 @@ const MasonryFileItem = memo( editorData={editorData} knowledgeBaseId={knowledgeBaseId} onClose={() => { - console.log('[MasonryFileItem] Closing note modal'); setIsNoteModalOpen(false); }} open={isNoteModalOpen} diff --git a/src/features/KnowledgeManager/Header/AddButton.tsx b/src/features/KnowledgeManager/Header/AddButton.tsx index 12ae95acd1..159f3143ae 100644 --- a/src/features/KnowledgeManager/Header/AddButton.tsx +++ b/src/features/KnowledgeManager/Header/AddButton.tsx @@ -36,7 +36,6 @@ const AddButton = ({ knowledgeBaseId }: { knowledgeBaseId?: string }) => { const handleCreateFolder = () => { setIsModalOpen(false); - console.log('create folder'); }; const items = useMemo(