diff --git a/.github/workflows/doc-style-checker.yml b/.github/workflows/doc-style-checker.yml new file mode 100644 index 0000000..c704b7d --- /dev/null +++ b/.github/workflows/doc-style-checker.yml @@ -0,0 +1,362 @@ +name: Doc Style Checker + +on: + workflow_call: + inputs: + repository: + description: "The repository to check out" + required: true + type: string + path: + type: string + description: "The startPath pointing to the folder containing documentation" + required: false + default: "." + pull_request_number: + type: string + description: "The pull request number to check out" + required: true + base_sha: + type: string + description: "The base sha to diff against" + required: true + head_sha: + type: string + description: "The head sha to comment against" + required: true + secrets: + GEMINI_API_KEY: + description: "Google Gemini API key" + required: true + GEMINI_API_KEY_2: + description: "Optional second Google Gemini API key" + required: false + GEMINI_API_KEY_3: + description: "Optional third Google Gemini API key" + required: false + ACTION_TOKEN: + description: "GitHub token for posting comments" + required: true + workflow_dispatch: # Add this for manual testing + inputs: + test_mode: + description: "Run in test mode" + default: true + type: boolean + +jobs: + doc-style-check: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + repository: ${{ inputs.repository }} + ref: ${{ inputs.head_sha }} + fetch-depth: 100 + path: content-repo + + - name: Get changed documentation files + id: changed-files + working-directory: ./content-repo + run: | + echo "Getting changed files between ${{ inputs.base_sha }} and ${{ inputs.head_sha }}" + CHANGED_FILES=$(git diff --name-only ${{ inputs.base_sha }} ${{ inputs.head_sha }} | grep -E '\.(adoc|md)$' || true) + echo "Changed documentation files:" + echo "$CHANGED_FILES" + echo "files<> $GITHUB_OUTPUT + echo "$CHANGED_FILES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Process changed documentation files + if: steps.changed-files.outputs.files != '' + working-directory: ./content-repo + run: | + mkdir -p /tmp/doc-processing + echo "Processing files..." + + echo '${{ steps.changed-files.outputs.files }}' | while IFS= read -r file; do + if [[ -n "$file" && -f "$file" ]]; then + echo "Preparing: $file" + SAFE_NAME=$(echo "$(basename "$file")" | sed 's/[^a-zA-Z0-9._-]/_/g') + + # Create a simple JSON file with proper escaping + python3 -c " + import json + import sys + + filename = '$file' + + try: + with open(filename, 'r', encoding='utf-8') as f: + content = f.read() + + data = { + 'filename': filename, + 'content': content + } + + with open('/tmp/doc-processing/${SAFE_NAME}.meta', 'w') as f: + json.dump(data, f) + + print(f'βœ“ Prepared {filename}') + except Exception as e: + print(f'βœ— Error processing {filename}: {e}') + sys.exit(1) + " + fi + done + + - name: Setup Node.js + if: steps.changed-files.outputs.files != '' + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Run Doc Style Checker + if: steps.changed-files.outputs.files != '' + run: | + cat > check_docs.js << 'EOF' + const fs = require('fs'); + const path = require('path'); + const https = require('https'); + + async function checkDocStyle(content, filename) { + console.log(`πŸ“€ Sending request for: ${filename}`); + console.log(`πŸ“ Content preview: ${content.substring(0, 100)}...`); + console.log(`πŸ“ Content length: ${content.length}`); + + const data = JSON.stringify({ + content: content, + filename: filename + }); + + console.log(`πŸ“¦ Request data size: ${data.length} bytes`); + + const options = { + hostname: 'doc-style-checker.vercel.app', + path: '/api/check-style', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': data.length, + 'User-Agent': 'GitHub-Actions-Doc-Checker/1.0' + } + }; + + return new Promise((resolve, reject) => { + const req = https.request(options, (res) => { + let responseData = ''; + res.on('data', (chunk) => { + responseData += chunk; + }); + res.on('end', () => { + try { + console.log(`πŸ“¨ Response status: ${res.statusCode}`); + console.log(`πŸ“¨ Response headers:`, JSON.stringify(res.headers)); + console.log(`πŸ“¨ Response body preview: ${responseData.substring(0, 500)}...`); + + if (res.statusCode !== 200) { + console.error(`❌ Non-200 status code: ${res.statusCode}`); + console.error(`❌ Full response: ${responseData}`); + throw new Error(`HTTP ${res.statusCode}: ${responseData}`); + } + const result = JSON.parse(responseData); + console.log(`βœ… Successfully parsed response for ${filename}`); + resolve(result); + } catch (e) { + console.error('❌ Response parsing error:', e.message); + console.error('❌ Raw response:', responseData); + reject(e); + } + }); + }); + + req.on('error', (e) => { + console.error(`❌ Request error for ${filename}:`, e.message); + reject(e); + }); + + req.setTimeout(45000, () => { + console.error(`⏰ Request timeout for ${filename}`); + req.destroy(); + reject(new Error('Request timeout after 45 seconds')); + }); + + console.log(`πŸš€ Sending request to API...`); + req.write(data); + req.end(); + }); + } + + async function processAllFiles() { + const processingDir = '/tmp/doc-processing'; + const results = {}; + + if (!fs.existsSync(processingDir)) { + console.log('No processing directory found'); + return results; + } + + const metaFiles = fs.readdirSync(processingDir).filter(f => f.endsWith('.meta')); + console.log(`Found ${metaFiles.length} file(s) to process`); + + for (const metaFile of metaFiles) { + const metaPath = path.join(processingDir, metaFile); + + try { + const metadata = JSON.parse(fs.readFileSync(metaPath, 'utf8')); + console.log(`πŸ” Processing: ${metadata.filename}`); + console.log(`πŸ“„ Content preview: "${metadata.content.substring(0, 150)}..."`); + console.log(`πŸ“ Content length: ${metadata.content.length} characters`); + console.log(`πŸ”€ Content type: ${typeof metadata.content}`); + + const result = await checkDocStyle(metadata.content, metadata.filename); + results[metadata.filename] = result; + + const issueCount = result.issues ? result.issues.length : 0; + console.log(`βœ… ${metadata.filename}: ${issueCount} issue(s) found`); + + await new Promise(resolve => setTimeout(resolve, 3000)); + + } catch (error) { + console.error(`❌ Error processing ${metaFile}:`, error.message); + console.error(`❌ Error stack:`, error.stack); + const filename = metaFile.replace('.meta', ''); + results[filename] = { error: error.message }; + } + } + + return results; + } + + processAllFiles() + .then(results => { + fs.writeFileSync('/tmp/style-check-results.json', JSON.stringify(results, null, 2)); + console.log('πŸ“Š Style check completed'); + }) + .catch(error => { + console.error('❌ Style check failed:', error); + process.exit(1); + }); + EOF + + node check_docs.js + + - name: Post PR Comment + if: steps.changed-files.outputs.files != '' + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.ACTION_TOKEN }} + script: | + const fs = require('fs'); + + let results = {}; + try { + const resultsData = fs.readFileSync('/tmp/style-check-results.json', 'utf8'); + results = JSON.parse(resultsData); + } catch (error) { + console.log('No results to process'); + return; + } + + console.log(`Processing results for ${Object.keys(results).length} files`); + + let comment = '## πŸ“ Doc Style Checker Results\n\n'; + let hasIssues = false; + let totalIssues = 0; + + for (const [filename, result] of Object.entries(results)) { + if (result.error) { + comment += `### ❌ ${filename}\n**Error:** \`${result.error}\`\n\n`; + continue; + } + + if (!result.issues || result.issues.length === 0) { + comment += `### βœ… ${filename}\nNo style issues found!\n\n`; + continue; + } + + // Process the nested structure from your API + let fileIssues = []; + result.issues.forEach(categoryGroup => { + if (categoryGroup.issues && Array.isArray(categoryGroup.issues)) { + categoryGroup.issues.forEach(issue => { + fileIssues.push({ + category: categoryGroup.category, + ...issue + }); + }); + } + }); + + if (fileIssues.length === 0) { + comment += `### βœ… ${filename}\nNo style issues found!\n\n`; + continue; + } + + hasIssues = true; + totalIssues += fileIssues.length; + comment += `### πŸ“‹ ${filename}\n**${fileIssues.length} issue${fileIssues.length > 1 ? 's' : ''} found**\n\n`; + + // Group by category + const byCategory = {}; + fileIssues.forEach(issue => { + const cat = issue.category || 'General'; + if (!byCategory[cat]) byCategory[cat] = []; + byCategory[cat].push(issue); + }); + + for (const [category, issues] of Object.entries(byCategory)) { + comment += `#### ${category}\n\n`; + + issues.forEach((issue, i) => { + comment += `
\nIssue ${i + 1}\n\n`; + + if (issue.problem) { + comment += `**Problem:** ${issue.problem}\n\n`; + } + + if (issue.problematicText) { + comment += `**Text:**\n\`\`\`\n${issue.problematicText}\n\`\`\`\n\n`; + } + + if (issue.location) { + comment += `**Location:** ${issue.location}\n\n`; + } + + if (issue.suggestion) { + comment += `**Suggestion:** ${issue.suggestion}\n\n`; + } + + if (issue.guideline) { + comment += `**Guideline:** ${issue.guideline}\n\n`; + } + + comment += `
\n\n`; + }); + } + } + + if (hasIssues) { + const summary = `πŸ” **Summary:** ${totalIssues} issue${totalIssues > 1 ? 's' : ''} found across ${Object.keys(results).length} file${Object.keys(results).length > 1 ? 's' : ''}\n\n`; + comment = comment.replace('## πŸ“ Doc Style Checker Results\n\n', `## πŸ“ Doc Style Checker Results\n\n${summary}`); + } else if (Object.keys(results).length > 0) { + comment += 'πŸŽ‰ **All documentation looks great!** No style issues found.\n\n'; + } + + comment += '---\n*Automated by [Doc Style Checker](https://doc-style-checker.vercel.app/) β€’ Couchbase Documentation Style Guide*'; + + try { + const [owner, repo] = '${{ inputs.repository }}'.split('/'); + await github.rest.issues.createComment({ + issue_number: parseInt('${{ inputs.pull_request_number }}'), + owner: owner, + repo: repo, + body: comment + }); + console.log('βœ… Comment posted successfully'); + } catch (error) { + console.error('❌ Failed to post comment:', error); + throw error; + } \ No newline at end of file diff --git a/.github/workflows/docs-review.yml b/.github/workflows/docs-review.yml new file mode 100644 index 0000000..536a6a4 --- /dev/null +++ b/.github/workflows/docs-review.yml @@ -0,0 +1,19 @@ +name: Documentation Review +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + doc-style-check: + uses: couchbaselabs/docs-runner/.github/workflows/doc-style-checker.yml@fi-docs-style-checker + with: + repository: ${{ github.repository }} + path: "." + pull_request_number: ${{ github.event.pull_request.number }} + base_sha: ${{ github.event.pull_request.base.sha }} + head_sha: ${{ github.event.pull_request.head.sha }} + secrets: + GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} + GEMINI_API_KEY_2: ${{ secrets.GEMINI_API_KEY_2 }} + GEMINI_API_KEY_3: ${{ secrets.GEMINI_API_KEY_3 }} + ACTION_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/fi-test.yml b/.github/workflows/fi-test.yml deleted file mode 100644 index ff778e3..0000000 --- a/.github/workflows/fi-test.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Test workflow for FI - -on: - workflow_call: - workflow_dispatch: - -jobs: - test: - runs-on: ubuntu-latest - - steps: - - name: test - run: | - echo worlld - - diff --git a/test-doc.adoc b/test-doc.adoc new file mode 100644 index 0000000..6a8409b --- /dev/null +++ b/test-doc.adoc @@ -0,0 +1,110 @@ +Data Modelling + +couchbase Sync Gateway’s data model; for secure cloud-to-edge synchronization of enterprise data. + +introduction + +This page includes guidance and constraints relating to the design of data buckets and documents that you want to replicate using Sync Gateway. They do not necessarily align with constraints on the local storage and use of such documents. + +property naming + +You can use an underscore prefix (_, ASCII _) for property naming, but your name cannot match any of the Document system properties reserved by Sync Gateway: + +_sync + +_id + +_rev + +_deleted + +_attachments + +_revisions + +_exp + +_purged + +_removed + +Any document that matches the reserved property names listed will be rejected by Sync Gateway — see Example 1 for the error details. + +Example 1. Property prefix error message +text +Copy +"{"error":"Bad Request","reason":"user defined top level properties beginning with '_' are not allowed in document body"}" +Where it applies +This rule applies to writes performed through: + +Couchbase Lite SDKs + +Sync Gateway REST APIs + +Couchbase Server SDKs when shared bucket access is enabled. + +When you might encounter the error +You may encounter the error in the following deployment situations: + +In Mobile-to-Web Data Sync with Node.js Server SDK and Ottoman.js (the Node.js ODM for Couchbase), where the rule conflicts with the _type property that is automatically added by Ottoman.js. + +A suggested workaround in this scenario is to fork the Ottoman.js library, perform a search-replace for the _type property and replace it without a leading underscore. + +For versions 2.x of Sync Gateway, you can encounter the following error: + +In Mobile-to-Web Data Sync with Field-level Encryption enabled, because the rule conflicts with the default field encryption format. + +How to avoid the error +You should change any top-level user properties that have a key with a leading underscore , by either: + +Renaming them to remove the underscore, or, + +Wrapping them inside another object with a key that doesn’t have a leading underscore. + +Document Structure +Couchbase’s unit of data is a document, this is the NOSQL equivalent of a row or record. + +Documents are stored as a key-value pair, which comprises a unique and immutable key, the Id, and a value representing the users' data (a JSON-object or binary blob). + +Key +The document key, the Id, is: + +A UTF-8 string with no spaces, although it may contain special characters, such as (, %, /, ", and _ + +No longer than 250 bytes + +Unique within the bucket + +Automatically generated (as a UUID) or be set by the user or application when saved + +Immutable; that is, once saved the Id cannot be changed. + +Value +The document value is either: + +A JSON value, termed a Document. + +This JSON object is a collection of key/value pairs. The values may be numbers, strings, arrays, or even nested objects. As a result, documents can represent complex data structures in a readily parsable and self-organizing manner. + +a binary object (also known as a blob or attachment) + +These attachments provide a means to store large media files or any other non-textual data. Couchbase Lite supports attachments of unlimited size, although the Sync Gateway imposes a 20MB limit for attachments synced to it. + +Document Attributes +Each Document has the following attributes: + +A document ID + +A current revision ID (which changes when the document is updated) + +A history of past revision IDs (usually linear, but will form a branching tree if the document has or has had conflicts) + +A body in the form of a JSON object (a set of key/value pairs) + +Zero or more named binary attachments + +Document Change History +Couchbase Lite tracks the change history of every document as a series of revisions, like version control systems such as Git or Subversion. Its main purpose is to enable the replicator to determine which data to sync and any conflicts arising. + +Each document change is assigned a unique revision ID. The IDs of past revisions are available. The content of past revisions may be available if the revision was created locally and the database has not yet been compacted. +