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(