chore(ci): Audit documentation for changes to influxdb3 CLI. Configures a GitHub release workflow to generate release notes and run the audit documentation script.
parent
9a4721aa40
commit
c74060210b
|
@ -18,20 +18,37 @@ on:
|
|||
description: 'Version to audit (use "local" for running containers)'
|
||||
required: false
|
||||
default: 'local'
|
||||
create_issue:
|
||||
description: 'Create GitHub issue with audit results'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
schedule:
|
||||
# Run weekly on Mondays at 9 AM UTC
|
||||
# Note: This only runs API audits for distributed products
|
||||
# CLI audits for core/enterprise run via the release workflow
|
||||
- cron: '0 9 * * 1'
|
||||
|
||||
jobs:
|
||||
audit-cli:
|
||||
name: Audit CLI Documentation
|
||||
runs-on: ubuntu-latest
|
||||
if: contains(fromJSON('["core", "enterprise", "all-monolith"]'), github.event.inputs.product)
|
||||
# Only run for manual triggers, not scheduled runs (which are for distributed products)
|
||||
if: github.event_name == 'workflow_dispatch' && contains(fromJSON('["core", "enterprise", "all-monolith"]'), github.event.inputs.product)
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Set up Docker
|
||||
if: github.event.inputs.version == 'local'
|
||||
run: |
|
||||
|
@ -44,9 +61,9 @@ jobs:
|
|||
VERSION="${{ github.event.inputs.version }}"
|
||||
|
||||
if [ "$PRODUCT" == "all-monolith" ]; then
|
||||
./helper-scripts/influxdb3-monolith/audit-cli-documentation.sh both $VERSION
|
||||
node ./helper-scripts/influxdb3-monolith/audit-cli-documentation.js both $VERSION
|
||||
else
|
||||
./helper-scripts/influxdb3-monolith/audit-cli-documentation.sh $PRODUCT $VERSION
|
||||
node ./helper-scripts/influxdb3-monolith/audit-cli-documentation.js $PRODUCT $VERSION
|
||||
fi
|
||||
|
||||
- name: Upload CLI audit reports
|
||||
|
@ -62,11 +79,23 @@ jobs:
|
|||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const product = '${{ github.event.inputs.product }}';
|
||||
const version = '${{ github.event.inputs.version }}';
|
||||
let product = '${{ github.event.inputs.product }}';
|
||||
let version = '${{ github.event.inputs.version }}';
|
||||
|
||||
// Handle scheduled runs (no inputs)
|
||||
if (github.event_name === 'schedule') {
|
||||
product = 'both';
|
||||
version = 'local';
|
||||
}
|
||||
|
||||
// Read audit report
|
||||
const reportPath = `helper-scripts/output/cli-audit/documentation-audit-${product}-${version}.md`;
|
||||
|
||||
if (!fs.existsSync(reportPath)) {
|
||||
console.log(`Audit report not found at ${reportPath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const report = fs.readFileSync(reportPath, 'utf8');
|
||||
|
||||
// Create issue
|
||||
|
@ -75,7 +104,7 @@ jobs:
|
|||
repo: context.repo.repo,
|
||||
title: `CLI Documentation Audit - ${product} ${version}`,
|
||||
body: report,
|
||||
labels: ['documentation', 'cli-audit', product]
|
||||
labels: ['documentation', 'cli-audit', product === 'both' ? 'core-enterprise' : product]
|
||||
});
|
||||
|
||||
audit-api:
|
||||
|
|
|
@ -0,0 +1,266 @@
|
|||
name: InfluxDB 3 Release Documentation
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
product:
|
||||
description: 'Product being released'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- core
|
||||
- enterprise
|
||||
- both
|
||||
version:
|
||||
description: 'Version being released (e.g., 3.0.0)'
|
||||
required: true
|
||||
type: string
|
||||
previous_version:
|
||||
description: 'Previous version for comparison (e.g., 2.9.0)'
|
||||
required: true
|
||||
type: string
|
||||
dry_run:
|
||||
description: 'Dry run (do not create PRs or issues)'
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
jobs:
|
||||
generate-release-notes:
|
||||
name: Generate Release Notes
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
release_notes_generated: ${{ steps.generate.outputs.generated }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Generate release notes
|
||||
id: generate
|
||||
run: |
|
||||
echo "Generating release notes for ${{ github.event.inputs.product }} v${{ github.event.inputs.version }}"
|
||||
|
||||
# TODO: Call the actual generate-release-notes script when it exists
|
||||
# node ./helper-scripts/influxdb3-monolith/generate-release-notes.js \
|
||||
# --product ${{ github.event.inputs.product }} \
|
||||
# --version ${{ github.event.inputs.version }} \
|
||||
# --previous ${{ github.event.inputs.previous_version }}
|
||||
|
||||
# For now, create a placeholder
|
||||
mkdir -p helper-scripts/output/release-notes
|
||||
echo "# Release Notes for ${{ github.event.inputs.product }} v${{ github.event.inputs.version }}" > helper-scripts/output/release-notes/release-notes-${{ github.event.inputs.product }}-${{ github.event.inputs.version }}.md
|
||||
echo "Generated: true" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Upload release notes
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-notes-${{ github.event.inputs.product }}-${{ github.event.inputs.version }}
|
||||
path: helper-scripts/output/release-notes/
|
||||
retention-days: 30
|
||||
|
||||
audit-cli-documentation:
|
||||
name: Audit CLI Documentation
|
||||
needs: generate-release-notes
|
||||
runs-on: ubuntu-latest
|
||||
if: needs.generate-release-notes.outputs.release_notes_generated == 'true'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Pull Docker images for version
|
||||
run: |
|
||||
VERSION="${{ github.event.inputs.version }}"
|
||||
PRODUCT="${{ github.event.inputs.product }}"
|
||||
|
||||
if [ "$PRODUCT" == "both" ]; then
|
||||
docker pull influxdb:${VERSION}-core || true
|
||||
docker pull influxdb:${VERSION}-enterprise || true
|
||||
else
|
||||
docker pull influxdb:${VERSION}-${PRODUCT} || true
|
||||
fi
|
||||
|
||||
- name: Run CLI audit
|
||||
run: |
|
||||
PRODUCT="${{ github.event.inputs.product }}"
|
||||
VERSION="${{ github.event.inputs.version }}"
|
||||
|
||||
node ./helper-scripts/influxdb3-monolith/audit-cli-documentation.js $PRODUCT $VERSION
|
||||
|
||||
- name: Upload CLI audit reports
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cli-audit-release-${{ github.event.inputs.product }}-${{ github.event.inputs.version }}
|
||||
path: helper-scripts/output/cli-audit/
|
||||
retention-days: 90
|
||||
|
||||
create-documentation-pr:
|
||||
name: Create Documentation PR
|
||||
needs: [generate-release-notes, audit-cli-documentation]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.inputs.dry_run != 'true'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts/
|
||||
|
||||
- name: Create release branch
|
||||
run: |
|
||||
BRANCH="release-docs-${{ github.event.inputs.product }}-${{ github.event.inputs.version }}"
|
||||
git checkout -b $BRANCH
|
||||
echo "BRANCH=$BRANCH" >> $GITHUB_ENV
|
||||
|
||||
- name: Copy release notes to docs
|
||||
run: |
|
||||
# TODO: Copy release notes to appropriate documentation location
|
||||
echo "Release notes would be copied here"
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: ${{ env.BRANCH }}
|
||||
title: "docs: Release documentation for ${{ github.event.inputs.product }} v${{ github.event.inputs.version }}"
|
||||
body: |
|
||||
## Release Documentation Update
|
||||
|
||||
This PR contains documentation updates for **${{ github.event.inputs.product }} v${{ github.event.inputs.version }}**
|
||||
|
||||
### Included Updates:
|
||||
- [ ] Release notes
|
||||
- [ ] Version updates
|
||||
- [ ] CLI documentation audit results
|
||||
|
||||
### Artifacts:
|
||||
- [Release Notes](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
||||
- [CLI Audit Report](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
||||
|
||||
### Manual Review Needed:
|
||||
Please review the CLI audit report for any missing or outdated documentation that needs to be updated.
|
||||
|
||||
---
|
||||
*This PR was automatically generated by the release workflow.*
|
||||
labels: |
|
||||
documentation
|
||||
release
|
||||
${{ github.event.inputs.product }}
|
||||
draft: true
|
||||
|
||||
create-audit-issue:
|
||||
name: Create CLI Audit Issue
|
||||
needs: audit-cli-documentation
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.inputs.dry_run != 'true'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Download audit report
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: cli-audit-release-${{ github.event.inputs.product }}-${{ github.event.inputs.version }}
|
||||
path: audit-report/
|
||||
|
||||
- name: Create issue from audit
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const product = '${{ github.event.inputs.product }}';
|
||||
const version = '${{ github.event.inputs.version }}';
|
||||
|
||||
// Find and read the audit report
|
||||
const files = fs.readdirSync('audit-report');
|
||||
const auditFile = files.find(f => f.includes('documentation-audit'));
|
||||
|
||||
if (!auditFile) {
|
||||
console.log('No audit report found');
|
||||
return;
|
||||
}
|
||||
|
||||
const report = fs.readFileSync(`audit-report/${auditFile}`, 'utf8');
|
||||
|
||||
// Check if there are any issues to report
|
||||
const hasMissingOptions = report.includes('⚠️ Missing from docs');
|
||||
const hasExtraOptions = report.includes('ℹ️ Documented but not in CLI');
|
||||
|
||||
if (hasMissingOptions || hasExtraOptions) {
|
||||
// Create issue
|
||||
await github.rest.issues.create({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
title: `CLI Documentation Updates Needed - ${product} v${version}`,
|
||||
body: `## CLI Documentation Audit Results
|
||||
|
||||
The following documentation issues were found during the release of **${product} v${version}**:
|
||||
|
||||
${report}
|
||||
|
||||
### Action Items:
|
||||
- [ ] Review and update documentation for commands with missing options
|
||||
- [ ] Remove documentation for deprecated options
|
||||
- [ ] Verify all examples work with the new version
|
||||
- [ ] Update any version-specific content
|
||||
|
||||
---
|
||||
*This issue was automatically generated during the release process.*`,
|
||||
labels: ['documentation', 'cli-audit', 'release', product],
|
||||
milestone: version // Assumes milestone exists for version
|
||||
});
|
||||
|
||||
console.log('Created issue for CLI documentation updates');
|
||||
} else {
|
||||
console.log('No documentation issues found - skipping issue creation');
|
||||
}
|
||||
|
||||
summary:
|
||||
name: Release Summary
|
||||
needs: [generate-release-notes, audit-cli-documentation, create-documentation-pr, create-audit-issue]
|
||||
runs-on: ubuntu-latest
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
- name: Generate summary
|
||||
run: |
|
||||
echo "# Release Documentation Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## Release Information" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Product**: ${{ github.event.inputs.product }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Version**: ${{ github.event.inputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Previous Version**: ${{ github.event.inputs.previous_version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Dry Run**: ${{ github.event.inputs.dry_run }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
echo "## Workflow Results" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Step | Status |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Generate Release Notes | ${{ needs.generate-release-notes.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| CLI Documentation Audit | ${{ needs.audit-cli-documentation.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Create Documentation PR | ${{ needs.create-documentation-pr.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Create Audit Issue | ${{ needs.create-audit-issue.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then
|
||||
echo "**Note**: This was a dry run. No PRs or issues were created." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
|
@ -0,0 +1,61 @@
|
|||
name: Trigger Documentation Update on Release
|
||||
|
||||
on:
|
||||
# Can be triggered by external workflows using repository_dispatch
|
||||
repository_dispatch:
|
||||
types: [influxdb3-release]
|
||||
|
||||
# Can also be triggered via GitHub API
|
||||
# Example:
|
||||
# curl -X POST \
|
||||
# -H "Authorization: token $GITHUB_TOKEN" \
|
||||
# -H "Accept: application/vnd.github.v3+json" \
|
||||
# https://api.github.com/repos/influxdata/docs-v2/dispatches \
|
||||
# -d '{"event_type":"influxdb3-release","client_payload":{"product":"core","version":"3.0.0","previous_version":"2.9.0"}}'
|
||||
|
||||
jobs:
|
||||
trigger-release-workflow:
|
||||
name: Trigger Release Documentation
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Validate payload
|
||||
run: |
|
||||
if [ -z "${{ github.event.client_payload.product }}" ]; then
|
||||
echo "Error: product is required in client_payload"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${{ github.event.client_payload.version }}" ]; then
|
||||
echo "Error: version is required in client_payload"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${{ github.event.client_payload.previous_version }}" ]; then
|
||||
echo "Error: previous_version is required in client_payload"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Received release notification:"
|
||||
echo "Product: ${{ github.event.client_payload.product }}"
|
||||
echo "Version: ${{ github.event.client_payload.version }}"
|
||||
echo "Previous Version: ${{ github.event.client_payload.previous_version }}"
|
||||
|
||||
- name: Trigger release documentation workflow
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
await github.rest.actions.createWorkflowDispatch({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
workflow_id: 'influxdb3-release.yml',
|
||||
ref: 'master',
|
||||
inputs: {
|
||||
product: '${{ github.event.client_payload.product }}',
|
||||
version: '${{ github.event.client_payload.version }}',
|
||||
previous_version: '${{ github.event.client_payload.previous_version }}',
|
||||
dry_run: '${{ github.event.client_payload.dry_run || 'false' }}'
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Successfully triggered release documentation workflow');
|
|
@ -0,0 +1,277 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Apply CLI documentation patches generated by audit-cli-documentation.js
|
||||
* Usage: node apply-cli-patches.js [core|enterprise|both] [--dry-run]
|
||||
*/
|
||||
|
||||
import { promises as fs } from 'fs';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { process } from 'node:process';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Color codes
|
||||
const Colors = {
|
||||
RED: '\x1b[0;31m',
|
||||
GREEN: '\x1b[0;32m',
|
||||
YELLOW: '\x1b[1;33m',
|
||||
BLUE: '\x1b[0;34m',
|
||||
NC: '\x1b[0m', // No Color
|
||||
};
|
||||
|
||||
async function fileExists(path) {
|
||||
try {
|
||||
await fs.access(path);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureDir(dir) {
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
}
|
||||
|
||||
async function extractFrontmatter(content) {
|
||||
const lines = content.split('\n');
|
||||
if (lines[0] !== '---') return { frontmatter: null, content };
|
||||
|
||||
const frontmatterLines = [];
|
||||
let i = 1;
|
||||
while (i < lines.length && lines[i] !== '---') {
|
||||
frontmatterLines.push(lines[i]);
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i >= lines.length) return { frontmatter: null, content };
|
||||
|
||||
const frontmatterText = frontmatterLines.join('\n');
|
||||
const remainingContent = lines.slice(i + 1).join('\n');
|
||||
|
||||
return { frontmatter: frontmatterText, content: remainingContent };
|
||||
}
|
||||
|
||||
async function getActualDocumentationPath(docPath, projectRoot) {
|
||||
// Check if the documentation file exists and has a source field
|
||||
const fullPath = join(projectRoot, docPath);
|
||||
|
||||
if (await fileExists(fullPath)) {
|
||||
const content = await fs.readFile(fullPath, 'utf8');
|
||||
const { frontmatter } = await extractFrontmatter(content);
|
||||
|
||||
if (frontmatter) {
|
||||
// Look for source: field in frontmatter
|
||||
const sourceMatch = frontmatter.match(/^source:\s*(.+)$/m);
|
||||
if (sourceMatch) {
|
||||
const sourcePath = sourceMatch[1].trim();
|
||||
return sourcePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return docPath;
|
||||
}
|
||||
|
||||
async function applyPatches(product, dryRun = false) {
|
||||
const patchDir = join(
|
||||
dirname(__dirname),
|
||||
'output',
|
||||
'cli-audit',
|
||||
'patches',
|
||||
product
|
||||
);
|
||||
const projectRoot = join(__dirname, '..', '..');
|
||||
|
||||
console.log(
|
||||
`${Colors.BLUE}📋 Applying CLI documentation patches for ${product}${Colors.NC}`
|
||||
);
|
||||
if (dryRun) {
|
||||
console.log(
|
||||
`${Colors.YELLOW}🔍 DRY RUN - No files will be created${Colors.NC}`
|
||||
);
|
||||
}
|
||||
console.log();
|
||||
|
||||
// Check if patch directory exists
|
||||
if (!(await fileExists(patchDir))) {
|
||||
console.log(`${Colors.YELLOW}No patches found for ${product}.${Colors.NC}`);
|
||||
console.log("Run 'yarn audit:cli' first to generate patches.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Read all patch files
|
||||
const patchFiles = await fs.readdir(patchDir);
|
||||
const mdFiles = patchFiles.filter((f) => f.endsWith('.md'));
|
||||
|
||||
if (mdFiles.length === 0) {
|
||||
console.log(
|
||||
`${Colors.YELLOW}No patch files found in ${patchDir}${Colors.NC}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Found ${mdFiles.length} patch file(s) to apply:\n`);
|
||||
|
||||
// Map patch files to their destination
|
||||
const baseCliPath = `content/influxdb3/${product}/reference/cli/influxdb3`;
|
||||
const commandToFile = {
|
||||
'create-database.md': `${baseCliPath}/create/database.md`,
|
||||
'create-token.md': `${baseCliPath}/create/token/_index.md`,
|
||||
'create-token-admin.md': `${baseCliPath}/create/token/admin.md`,
|
||||
'create-trigger.md': `${baseCliPath}/create/trigger.md`,
|
||||
'create-table.md': `${baseCliPath}/create/table.md`,
|
||||
'create-last_cache.md': `${baseCliPath}/create/last_cache.md`,
|
||||
'create-distinct_cache.md': `${baseCliPath}/create/distinct_cache.md`,
|
||||
'show-databases.md': `${baseCliPath}/show/databases.md`,
|
||||
'show-tokens.md': `${baseCliPath}/show/tokens.md`,
|
||||
'delete-database.md': `${baseCliPath}/delete/database.md`,
|
||||
'delete-table.md': `${baseCliPath}/delete/table.md`,
|
||||
'query.md': `${baseCliPath}/query.md`,
|
||||
'write.md': `${baseCliPath}/write.md`,
|
||||
};
|
||||
|
||||
let applied = 0;
|
||||
let skipped = 0;
|
||||
|
||||
for (const patchFile of mdFiles) {
|
||||
const destinationPath = commandToFile[patchFile];
|
||||
|
||||
if (!destinationPath) {
|
||||
console.log(
|
||||
`${Colors.YELLOW}⚠️ Unknown patch file: ${patchFile}${Colors.NC}`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the actual documentation path (handles source: frontmatter)
|
||||
const actualPath = await getActualDocumentationPath(
|
||||
destinationPath,
|
||||
projectRoot
|
||||
);
|
||||
const fullDestPath = join(projectRoot, actualPath);
|
||||
const patchPath = join(patchDir, patchFile);
|
||||
|
||||
// Check if destination already exists
|
||||
if (await fileExists(fullDestPath)) {
|
||||
console.log(
|
||||
`${Colors.YELLOW}⏭️ Skipping${Colors.NC} ${patchFile} - destination already exists:`
|
||||
);
|
||||
console.log(` ${actualPath}`);
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dryRun) {
|
||||
console.log(`${Colors.BLUE}🔍 Would create${Colors.NC} ${actualPath}`);
|
||||
console.log(` from patch: ${patchFile}`);
|
||||
if (actualPath !== destinationPath) {
|
||||
console.log(` (resolved from: ${destinationPath})`);
|
||||
}
|
||||
applied++;
|
||||
} else {
|
||||
try {
|
||||
// Ensure destination directory exists
|
||||
await ensureDir(dirname(fullDestPath));
|
||||
|
||||
// Copy patch to destination
|
||||
const content = await fs.readFile(patchPath, 'utf8');
|
||||
|
||||
// Update the menu configuration based on product
|
||||
let updatedContent = content;
|
||||
if (product === 'enterprise') {
|
||||
updatedContent = content
|
||||
.replace('influxdb3/core/tags:', 'influxdb3/enterprise/tags:')
|
||||
.replace(
|
||||
'influxdb3_core_reference:',
|
||||
'influxdb3_enterprise_reference:'
|
||||
);
|
||||
}
|
||||
|
||||
await fs.writeFile(fullDestPath, updatedContent);
|
||||
|
||||
console.log(`${Colors.GREEN}✅ Created${Colors.NC} ${actualPath}`);
|
||||
console.log(` from patch: ${patchFile}`);
|
||||
if (actualPath !== destinationPath) {
|
||||
console.log(` (resolved from: ${destinationPath})`);
|
||||
}
|
||||
applied++;
|
||||
} catch (error) {
|
||||
console.log(
|
||||
`${Colors.RED}❌ Error${Colors.NC} creating ${actualPath}:`
|
||||
);
|
||||
console.log(` ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log();
|
||||
console.log(`${Colors.BLUE}Summary:${Colors.NC}`);
|
||||
console.log(`- Patches ${dryRun ? 'would be' : ''} applied: ${applied}`);
|
||||
console.log(`- Files skipped (already exist): ${skipped}`);
|
||||
console.log(`- Total patch files: ${mdFiles.length}`);
|
||||
|
||||
if (!dryRun && applied > 0) {
|
||||
console.log();
|
||||
console.log(
|
||||
`${Colors.GREEN}✨ Success!${Colors.NC} Created ${applied} new ` +
|
||||
'documentation file(s).'
|
||||
);
|
||||
console.log();
|
||||
console.log('Next steps:');
|
||||
console.log('1. Review the generated files and customize the content');
|
||||
console.log('2. Add proper examples with placeholders');
|
||||
console.log('3. Update descriptions and add any missing options');
|
||||
console.log('4. Run tests: yarn test:links');
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const product =
|
||||
args.find((arg) => ['core', 'enterprise', 'both'].includes(arg)) || 'both';
|
||||
const dryRun = args.includes('--dry-run');
|
||||
|
||||
if (args.includes('--help') || args.includes('-h')) {
|
||||
console.log(
|
||||
'Usage: node apply-cli-patches.js [core|enterprise|both] [--dry-run]'
|
||||
);
|
||||
console.log();
|
||||
console.log('Options:');
|
||||
console.log(
|
||||
' --dry-run Show what would be done without creating files'
|
||||
);
|
||||
console.log();
|
||||
console.log('Examples:');
|
||||
console.log(
|
||||
' node apply-cli-patches.js # Apply patches for both products'
|
||||
);
|
||||
console.log(
|
||||
' node apply-cli-patches.js core --dry-run # Preview core patches'
|
||||
);
|
||||
console.log(
|
||||
' node apply-cli-patches.js enterprise # Apply enterprise patches'
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
try {
|
||||
if (product === 'both') {
|
||||
await applyPatches('core', dryRun);
|
||||
console.log();
|
||||
await applyPatches('enterprise', dryRun);
|
||||
} else {
|
||||
await applyPatches(product, dryRun);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`${Colors.RED}Error:${Colors.NC}`, error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run if called directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
main();
|
||||
}
|
|
@ -0,0 +1,960 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Audit CLI documentation against current CLI help output
|
||||
* Usage: node audit-cli-documentation.js [core|enterprise|both] [version]
|
||||
* Example: node audit-cli-documentation.js core 3.2.0
|
||||
*/
|
||||
|
||||
import { spawn } from 'child_process';
|
||||
import { promises as fs } from 'fs';
|
||||
import { homedir } from 'os';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Color codes
|
||||
const Colors = {
|
||||
RED: '\x1b[0;31m',
|
||||
GREEN: '\x1b[0;32m',
|
||||
YELLOW: '\x1b[1;33m',
|
||||
BLUE: '\x1b[0;34m',
|
||||
NC: '\x1b[0m', // No Color
|
||||
};
|
||||
|
||||
class CLIDocAuditor {
|
||||
constructor(product = 'both', version = 'local') {
|
||||
this.product = product;
|
||||
this.version = version;
|
||||
this.outputDir = join(dirname(__dirname), 'output', 'cli-audit');
|
||||
|
||||
// Token paths - check environment variables first (Docker Compose), then fall back to local files
|
||||
const coreTokenEnv = process.env.INFLUXDB3_CORE_TOKEN;
|
||||
const enterpriseTokenEnv = process.env.INFLUXDB3_ENTERPRISE_TOKEN;
|
||||
|
||||
if (coreTokenEnv && this.fileExists(coreTokenEnv)) {
|
||||
// Running in Docker Compose with secrets
|
||||
this.coreTokenFile = coreTokenEnv;
|
||||
this.enterpriseTokenFile = enterpriseTokenEnv;
|
||||
} else {
|
||||
// Running locally
|
||||
this.coreTokenFile = join(homedir(), '.env.influxdb3-core-admin-token');
|
||||
this.enterpriseTokenFile = join(
|
||||
homedir(),
|
||||
'.env.influxdb3-enterprise-admin-token'
|
||||
);
|
||||
}
|
||||
|
||||
// Commands to extract help for
|
||||
this.mainCommands = [
|
||||
'create',
|
||||
'delete',
|
||||
'disable',
|
||||
'enable',
|
||||
'query',
|
||||
'show',
|
||||
'test',
|
||||
'update',
|
||||
'write',
|
||||
];
|
||||
this.subcommands = [
|
||||
'create database',
|
||||
'create token admin',
|
||||
'create token',
|
||||
'create trigger',
|
||||
'create last_cache',
|
||||
'create distinct_cache',
|
||||
'create table',
|
||||
'show databases',
|
||||
'show tokens',
|
||||
'show system',
|
||||
'delete database',
|
||||
'delete table',
|
||||
'delete trigger',
|
||||
'update database',
|
||||
'test wal_plugin',
|
||||
'test schedule_plugin',
|
||||
];
|
||||
|
||||
// Map for command tracking during option parsing
|
||||
this.commandOptionsMap = {};
|
||||
}
|
||||
|
||||
async fileExists(path) {
|
||||
try {
|
||||
await fs.access(path);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async ensureDir(dir) {
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
}
|
||||
|
||||
async loadTokens() {
|
||||
let coreToken = null;
|
||||
let enterpriseToken = null;
|
||||
|
||||
try {
|
||||
if (await this.fileExists(this.coreTokenFile)) {
|
||||
const stat = await fs.stat(this.coreTokenFile);
|
||||
if (stat.size > 0) {
|
||||
coreToken = (await fs.readFile(this.coreTokenFile, 'utf8')).trim();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Token file doesn't exist or can't be read
|
||||
}
|
||||
|
||||
try {
|
||||
if (await this.fileExists(this.enterpriseTokenFile)) {
|
||||
const stat = await fs.stat(this.enterpriseTokenFile);
|
||||
if (stat.size > 0) {
|
||||
enterpriseToken = (
|
||||
await fs.readFile(this.enterpriseTokenFile, 'utf8')
|
||||
).trim();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Token file doesn't exist or can't be read
|
||||
}
|
||||
|
||||
return { coreToken, enterpriseToken };
|
||||
}
|
||||
|
||||
runCommand(cmd, args = []) {
|
||||
return new Promise((resolve) => {
|
||||
const child = spawn(cmd, args, { encoding: 'utf8' });
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
child.stdout.on('data', (data) => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
|
||||
child.stderr.on('data', (data) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
|
||||
child.on('close', (code) => {
|
||||
resolve({ code, stdout, stderr });
|
||||
});
|
||||
|
||||
child.on('error', (err) => {
|
||||
resolve({ code: 1, stdout: '', stderr: err.message });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async extractCurrentCLI(product, outputFile) {
|
||||
process.stdout.write(
|
||||
`Extracting current CLI help from influxdb3-${product}...`
|
||||
);
|
||||
|
||||
await this.loadTokens();
|
||||
|
||||
if (this.version === 'local') {
|
||||
const containerName = `influxdb3-${product}`;
|
||||
|
||||
// Check if container is running
|
||||
const { code, stdout } = await this.runCommand('docker', [
|
||||
'ps',
|
||||
'--format',
|
||||
'{{.Names}}',
|
||||
]);
|
||||
if (code !== 0 || !stdout.includes(containerName)) {
|
||||
console.log(` ${Colors.RED}✗${Colors.NC}`);
|
||||
console.log(`Error: Container ${containerName} is not running.`);
|
||||
console.log(`Start it with: docker compose up -d influxdb3-${product}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract comprehensive help
|
||||
let fileContent = '';
|
||||
|
||||
// Main help
|
||||
const mainHelp = await this.runCommand('docker', [
|
||||
'exec',
|
||||
containerName,
|
||||
'influxdb3',
|
||||
'--help',
|
||||
]);
|
||||
fileContent += mainHelp.code === 0 ? mainHelp.stdout : mainHelp.stderr;
|
||||
|
||||
// Extract all subcommand help
|
||||
for (const cmd of this.mainCommands) {
|
||||
fileContent += `\n\n===== influxdb3 ${cmd} --help =====\n`;
|
||||
const cmdHelp = await this.runCommand('docker', [
|
||||
'exec',
|
||||
containerName,
|
||||
'influxdb3',
|
||||
cmd,
|
||||
'--help',
|
||||
]);
|
||||
fileContent += cmdHelp.code === 0 ? cmdHelp.stdout : cmdHelp.stderr;
|
||||
}
|
||||
|
||||
// Extract detailed subcommand help
|
||||
for (const subcmd of this.subcommands) {
|
||||
fileContent += `\n\n===== influxdb3 ${subcmd} --help =====\n`;
|
||||
const cmdParts = [
|
||||
'exec',
|
||||
containerName,
|
||||
'influxdb3',
|
||||
...subcmd.split(' '),
|
||||
'--help',
|
||||
];
|
||||
const subcmdHelp = await this.runCommand('docker', cmdParts);
|
||||
fileContent +=
|
||||
subcmdHelp.code === 0 ? subcmdHelp.stdout : subcmdHelp.stderr;
|
||||
}
|
||||
|
||||
await fs.writeFile(outputFile, fileContent);
|
||||
console.log(` ${Colors.GREEN}✓${Colors.NC}`);
|
||||
} else {
|
||||
// Use specific version image
|
||||
const image = `influxdb:${this.version}-${product}`;
|
||||
|
||||
process.stdout.write(`Extracting CLI help from ${image}...`);
|
||||
|
||||
// Pull image if needed
|
||||
const pullResult = await this.runCommand('docker', ['pull', image]);
|
||||
if (pullResult.code !== 0) {
|
||||
console.log(` ${Colors.RED}✗${Colors.NC}`);
|
||||
console.log(`Error: Failed to pull image ${image}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract help from specific version
|
||||
let fileContent = '';
|
||||
|
||||
// Main help
|
||||
const mainHelp = await this.runCommand('docker', [
|
||||
'run',
|
||||
'--rm',
|
||||
image,
|
||||
'influxdb3',
|
||||
'--help',
|
||||
]);
|
||||
fileContent += mainHelp.code === 0 ? mainHelp.stdout : mainHelp.stderr;
|
||||
|
||||
// Extract subcommand help
|
||||
for (const cmd of this.mainCommands) {
|
||||
fileContent += `\n\n===== influxdb3 ${cmd} --help =====\n`;
|
||||
const cmdHelp = await this.runCommand('docker', [
|
||||
'run',
|
||||
'--rm',
|
||||
image,
|
||||
'influxdb3',
|
||||
cmd,
|
||||
'--help',
|
||||
]);
|
||||
fileContent += cmdHelp.code === 0 ? cmdHelp.stdout : cmdHelp.stderr;
|
||||
}
|
||||
|
||||
await fs.writeFile(outputFile, fileContent);
|
||||
console.log(` ${Colors.GREEN}✓${Colors.NC}`);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async parseCLIHelp(helpFile, parsedFile) {
|
||||
const content = await fs.readFile(helpFile, 'utf8');
|
||||
const lines = content.split('\n');
|
||||
|
||||
let output = '# CLI Commands and Options\n\n';
|
||||
let currentCommand = '';
|
||||
let inOptions = false;
|
||||
|
||||
for (const line of lines) {
|
||||
// Detect command headers
|
||||
if (line.startsWith('===== influxdb3') && line.endsWith('--help =====')) {
|
||||
currentCommand = line
|
||||
.replace('===== ', '')
|
||||
.replace(' --help =====', '')
|
||||
.trim();
|
||||
output += `## ${currentCommand}\n\n`;
|
||||
inOptions = false;
|
||||
// Initialize options list for this command
|
||||
this.commandOptionsMap[currentCommand] = [];
|
||||
}
|
||||
// Detect options sections
|
||||
else if (line.trim() === 'Options:') {
|
||||
output += '### Options:\n\n';
|
||||
inOptions = true;
|
||||
}
|
||||
// Parse option lines
|
||||
else if (inOptions && /^\s*-/.test(line)) {
|
||||
// Extract option and description
|
||||
const optionMatch = line.match(/--[a-z][a-z0-9-]*/);
|
||||
const shortMatch = line.match(/\s-[a-zA-Z],/);
|
||||
|
||||
if (optionMatch) {
|
||||
const option = optionMatch[0];
|
||||
const shortOption = shortMatch
|
||||
? shortMatch[0].replace(/[,\s]/g, '')
|
||||
: null;
|
||||
|
||||
// Extract description by removing option parts
|
||||
let description = line.replace(/^\s*-[^\s]*\s*/, '');
|
||||
description = description.replace(/^\s*--[^\s]*\s*/, '').trim();
|
||||
|
||||
if (shortOption) {
|
||||
output += `- \`${shortOption}, ${option}\`: ${description}\n`;
|
||||
} else {
|
||||
output += `- \`${option}\`: ${description}\n`;
|
||||
}
|
||||
|
||||
// Store option with its command context
|
||||
if (currentCommand && option) {
|
||||
this.commandOptionsMap[currentCommand].push(option);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Reset options flag for new sections
|
||||
else if (/^[A-Z][a-z]+:$/.test(line.trim())) {
|
||||
inOptions = false;
|
||||
}
|
||||
}
|
||||
|
||||
await fs.writeFile(parsedFile, output);
|
||||
}
|
||||
|
||||
findDocsPath(product) {
|
||||
if (product === 'core') {
|
||||
return 'content/influxdb3/core/reference/cli/influxdb3';
|
||||
} else if (product === 'enterprise') {
|
||||
return 'content/influxdb3/enterprise/reference/cli/influxdb3';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
async extractCommandHelp(content, command) {
|
||||
// Find the section for this specific command in the CLI help
|
||||
const lines = content.split('\n');
|
||||
let inCommand = false;
|
||||
let helpText = [];
|
||||
const commandHeader = `===== influxdb3 ${command} --help =====`;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (lines[i] === commandHeader) {
|
||||
inCommand = true;
|
||||
continue;
|
||||
}
|
||||
if (inCommand && lines[i].startsWith('===== influxdb3')) {
|
||||
break;
|
||||
}
|
||||
if (inCommand) {
|
||||
helpText.push(lines[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return helpText.join('\n').trim();
|
||||
}
|
||||
|
||||
async generateDocumentationTemplate(command, helpText) {
|
||||
// Parse the help text to extract description and options
|
||||
const lines = helpText.split('\n');
|
||||
let description = '';
|
||||
let usage = '';
|
||||
let options = [];
|
||||
let inOptions = false;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
|
||||
if (i === 0 && !line.startsWith('Usage:') && line.trim()) {
|
||||
description = line.trim();
|
||||
}
|
||||
if (line.startsWith('Usage:')) {
|
||||
usage = line.replace('Usage:', '').trim();
|
||||
}
|
||||
if (line.trim() === 'Options:') {
|
||||
inOptions = true;
|
||||
continue;
|
||||
}
|
||||
if (inOptions && /^\s*-/.test(line)) {
|
||||
const optionMatch = line.match(/--([a-z][a-z0-9-]*)/);
|
||||
const shortMatch = line.match(/\s-([a-zA-Z]),/);
|
||||
if (optionMatch) {
|
||||
const optionName = optionMatch[1];
|
||||
const shortOption = shortMatch ? shortMatch[1] : null;
|
||||
let optionDesc = line
|
||||
.replace(/^\s*-[^\s]*\s*/, '')
|
||||
.replace(/^\s*--[^\s]*\s*/, '')
|
||||
.trim();
|
||||
|
||||
options.push({
|
||||
name: optionName,
|
||||
short: shortOption,
|
||||
description: optionDesc,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate markdown template
|
||||
let template = `---
|
||||
title: influxdb3 ${command}
|
||||
description: >
|
||||
The \`influxdb3 ${command}\` command ${description.toLowerCase()}.
|
||||
influxdb3/core/tags: [cli]
|
||||
menu:
|
||||
influxdb3_core_reference:
|
||||
parent: influxdb3 cli
|
||||
weight: 201
|
||||
---
|
||||
|
||||
# influxdb3 ${command}
|
||||
|
||||
${description}
|
||||
|
||||
## Usage
|
||||
|
||||
\`\`\`bash
|
||||
${usage || `influxdb3 ${command} [OPTIONS]`}
|
||||
\`\`\`
|
||||
|
||||
`;
|
||||
|
||||
if (options.length > 0) {
|
||||
template += `## Options
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
`;
|
||||
|
||||
for (const opt of options) {
|
||||
const optionDisplay = opt.short
|
||||
? `\`-${opt.short}\`, \`--${opt.name}\``
|
||||
: `\`--${opt.name}\``;
|
||||
template += `| ${optionDisplay} | ${opt.description} |\n`;
|
||||
}
|
||||
}
|
||||
|
||||
template += `
|
||||
## Examples
|
||||
|
||||
### Example 1: Basic usage
|
||||
|
||||
{{% code-placeholders "PLACEHOLDER1|PLACEHOLDER2" %}}
|
||||
\`\`\`bash
|
||||
influxdb3 ${command} --example PLACEHOLDER1
|
||||
\`\`\`
|
||||
{{% /code-placeholders %}}
|
||||
|
||||
Replace the following:
|
||||
|
||||
- {{% code-placeholder-key %}}\`PLACEHOLDER1\`{{% /code-placeholder-key %}}: Description of placeholder
|
||||
`;
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
async extractFrontmatter(content) {
|
||||
const lines = content.split('\n');
|
||||
if (lines[0] !== '---') return { frontmatter: null, content };
|
||||
|
||||
const frontmatterLines = [];
|
||||
let i = 1;
|
||||
while (i < lines.length && lines[i] !== '---') {
|
||||
frontmatterLines.push(lines[i]);
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i >= lines.length) return { frontmatter: null, content };
|
||||
|
||||
const frontmatterText = frontmatterLines.join('\n');
|
||||
const remainingContent = lines.slice(i + 1).join('\n');
|
||||
|
||||
return { frontmatter: frontmatterText, content: remainingContent };
|
||||
}
|
||||
|
||||
async getActualContentPath(filePath) {
|
||||
// Get the actual content path, resolving source fields
|
||||
try {
|
||||
const content = await fs.readFile(filePath, 'utf8');
|
||||
const { frontmatter } = await this.extractFrontmatter(content);
|
||||
|
||||
if (frontmatter) {
|
||||
const sourceMatch = frontmatter.match(/^source:\s*(.+)$/m);
|
||||
if (sourceMatch) {
|
||||
let sourcePath = sourceMatch[1].trim();
|
||||
// Handle relative paths from project root
|
||||
if (sourcePath.startsWith('/shared/')) {
|
||||
sourcePath = `content${sourcePath}`;
|
||||
}
|
||||
return sourcePath;
|
||||
}
|
||||
}
|
||||
return null; // No source field found
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async parseDocumentedOptions(filePath) {
|
||||
// Parse a documentation file to extract all documented options
|
||||
try {
|
||||
const content = await fs.readFile(filePath, 'utf8');
|
||||
const options = [];
|
||||
|
||||
// Look for options in various patterns:
|
||||
// 1. Markdown tables with option columns
|
||||
// 2. Option lists with backticks
|
||||
// 3. Code examples with --option flags
|
||||
|
||||
// Pattern 1: Markdown tables (| Option | Description |)
|
||||
const tableMatches = content.match(/\|\s*`?--[a-z][a-z0-9-]*`?\s*\|/gi);
|
||||
if (tableMatches) {
|
||||
for (const match of tableMatches) {
|
||||
const option = match.match(/--[a-z][a-z0-9-]*/i);
|
||||
if (option) {
|
||||
options.push(option[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pattern 2: Backtick-enclosed options in text
|
||||
const backtickMatches = content.match(/`--[a-z][a-z0-9-]*`/gi);
|
||||
if (backtickMatches) {
|
||||
for (const match of backtickMatches) {
|
||||
const option = match.replace(/`/g, '');
|
||||
options.push(option);
|
||||
}
|
||||
}
|
||||
|
||||
// Pattern 3: Options in code blocks
|
||||
const codeBlockMatches = content.match(/```[\s\S]*?```/g);
|
||||
if (codeBlockMatches) {
|
||||
for (const block of codeBlockMatches) {
|
||||
const blockOptions = block.match(/--[a-z][a-z0-9-]*/gi);
|
||||
if (blockOptions) {
|
||||
options.push(...blockOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pattern 4: Environment variable mappings (INFLUXDB3_* to --option)
|
||||
const envMatches = content.match(
|
||||
/\|\s*`INFLUXDB3_[^`]*`\s*\|\s*`--[a-z][a-z0-9-]*`\s*\|/gi
|
||||
);
|
||||
if (envMatches) {
|
||||
for (const match of envMatches) {
|
||||
const option = match.match(/--[a-z][a-z0-9-]*/);
|
||||
if (option) {
|
||||
options.push(option[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove duplicates and return sorted
|
||||
return [...new Set(options)].sort();
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async auditDocs(product, cliFile, auditFile) {
|
||||
const docsPath = this.findDocsPath(product);
|
||||
const sharedPath = 'content/shared/influxdb3-cli';
|
||||
const patchDir = join(this.outputDir, 'patches', product);
|
||||
await this.ensureDir(patchDir);
|
||||
|
||||
let output = `# CLI Documentation Audit - ${product}\n`;
|
||||
output += `Generated: ${new Date().toISOString()}\n\n`;
|
||||
|
||||
// GitHub base URL for edit links
|
||||
const githubBase = 'https://github.com/influxdata/docs-v2/edit/master';
|
||||
const githubNewBase = 'https://github.com/influxdata/docs-v2/new/master';
|
||||
|
||||
// VSCode links for local editing
|
||||
const vscodeBase = 'vscode://file';
|
||||
const projectRoot = join(__dirname, '..', '..');
|
||||
|
||||
// Check for missing documentation
|
||||
output += '## Missing Documentation\n\n';
|
||||
|
||||
let missingCount = 0;
|
||||
const missingDocs = [];
|
||||
|
||||
// Map commands to expected documentation files
|
||||
const commandToFile = {
|
||||
'create database': 'create/database.md',
|
||||
'create token': 'create/token/_index.md',
|
||||
'create token admin': 'create/token/admin.md',
|
||||
'create trigger': 'create/trigger.md',
|
||||
'create table': 'create/table.md',
|
||||
'create last_cache': 'create/last_cache.md',
|
||||
'create distinct_cache': 'create/distinct_cache.md',
|
||||
'show databases': 'show/databases.md',
|
||||
'show tokens': 'show/tokens.md',
|
||||
'delete database': 'delete/database.md',
|
||||
'delete table': 'delete/table.md',
|
||||
query: 'query.md',
|
||||
write: 'write.md',
|
||||
};
|
||||
|
||||
// Extract commands from CLI help
|
||||
const content = await fs.readFile(cliFile, 'utf8');
|
||||
const lines = content.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('===== influxdb3') && line.endsWith('--help =====')) {
|
||||
const command = line
|
||||
.replace('===== influxdb3 ', '')
|
||||
.replace(' --help =====', '');
|
||||
|
||||
if (commandToFile[command]) {
|
||||
const expectedFile = commandToFile[command];
|
||||
const productFile = join(docsPath, expectedFile);
|
||||
const sharedFile = join(sharedPath, expectedFile);
|
||||
|
||||
const productExists = await this.fileExists(productFile);
|
||||
const sharedExists = await this.fileExists(sharedFile);
|
||||
|
||||
let needsContent = false;
|
||||
let targetPath = null;
|
||||
let stubPath = null;
|
||||
|
||||
if (!productExists && !sharedExists) {
|
||||
// Completely missing
|
||||
needsContent = true;
|
||||
targetPath = productFile;
|
||||
} else if (productExists) {
|
||||
// Check if it has a source field pointing to missing content
|
||||
const actualPath = await this.getActualContentPath(productFile);
|
||||
if (actualPath && !(await this.fileExists(actualPath))) {
|
||||
needsContent = true;
|
||||
targetPath = actualPath;
|
||||
stubPath = productFile;
|
||||
}
|
||||
} else if (sharedExists) {
|
||||
// Shared file exists, check if it has content
|
||||
const actualPath = await this.getActualContentPath(sharedFile);
|
||||
if (actualPath && !(await this.fileExists(actualPath))) {
|
||||
needsContent = true;
|
||||
targetPath = actualPath;
|
||||
stubPath = sharedFile;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsContent && targetPath) {
|
||||
const githubNewUrl = `${githubNewBase}/${targetPath}`;
|
||||
const localPath = join(projectRoot, targetPath);
|
||||
|
||||
output += `- **Missing**: Documentation for \`influxdb3 ${command}\`\n`;
|
||||
if (stubPath) {
|
||||
output += ` - Stub exists at: \`${stubPath}\`\n`;
|
||||
output += ` - Content needed at: \`${targetPath}\`\n`;
|
||||
} else {
|
||||
output += ` - Expected: \`${targetPath}\` or \`${sharedFile}\`\n`;
|
||||
}
|
||||
output += ` - [Create on GitHub](${githubNewUrl})\n`;
|
||||
output += ` - Local: \`${localPath}\`\n`;
|
||||
|
||||
// Generate documentation template
|
||||
const helpText = await this.extractCommandHelp(content, command);
|
||||
const docTemplate = await this.generateDocumentationTemplate(
|
||||
command,
|
||||
helpText
|
||||
);
|
||||
|
||||
// Save patch file
|
||||
const patchFileName = `${command.replace(/ /g, '-')}.md`;
|
||||
const patchFile = join(patchDir, patchFileName);
|
||||
await fs.writeFile(patchFile, docTemplate);
|
||||
|
||||
output += ` - **Template generated**: \`${patchFile}\`\n`;
|
||||
|
||||
missingDocs.push({ command, file: targetPath, patchFile });
|
||||
missingCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (missingCount === 0) {
|
||||
output += 'No missing documentation files detected.\n';
|
||||
} else {
|
||||
output += `\n### Quick Actions\n\n`;
|
||||
output += `Copy and paste these commands to create missing documentation:\n\n`;
|
||||
output += `\`\`\`bash\n`;
|
||||
for (const doc of missingDocs) {
|
||||
const relativePatch = join(
|
||||
'helper-scripts/output/cli-audit/patches',
|
||||
product,
|
||||
`${doc.command.replace(/ /g, '-')}.md`
|
||||
);
|
||||
output += `# Create ${doc.command} documentation\n`;
|
||||
output += `mkdir -p $(dirname ${doc.file})\n`;
|
||||
output += `cp ${relativePatch} ${doc.file}\n\n`;
|
||||
}
|
||||
output += `\`\`\`\n`;
|
||||
}
|
||||
|
||||
output += '\n';
|
||||
|
||||
// Check for outdated options in existing docs
|
||||
output += '## Existing Documentation Review\n\n';
|
||||
|
||||
// Parse CLI help first to populate commandOptionsMap
|
||||
const parsedFile = join(
|
||||
this.outputDir,
|
||||
`parsed-cli-${product}-${this.version}.md`
|
||||
);
|
||||
await this.parseCLIHelp(cliFile, parsedFile);
|
||||
|
||||
// For each command, check if documentation exists and compare content
|
||||
const existingDocs = [];
|
||||
for (const [command, expectedFile] of Object.entries(commandToFile)) {
|
||||
const productFile = join(docsPath, expectedFile);
|
||||
const sharedFile = join(sharedPath, expectedFile);
|
||||
|
||||
let docFile = null;
|
||||
let actualContentFile = null;
|
||||
|
||||
// Find the documentation file
|
||||
if (await this.fileExists(productFile)) {
|
||||
docFile = productFile;
|
||||
// Check if it's a stub with source field
|
||||
const actualPath = await this.getActualContentPath(productFile);
|
||||
actualContentFile = actualPath
|
||||
? join(projectRoot, actualPath)
|
||||
: join(projectRoot, productFile);
|
||||
} else if (await this.fileExists(sharedFile)) {
|
||||
docFile = sharedFile;
|
||||
actualContentFile = join(projectRoot, sharedFile);
|
||||
}
|
||||
|
||||
if (docFile && (await this.fileExists(actualContentFile))) {
|
||||
const githubEditUrl = `${githubBase}/${docFile}`;
|
||||
const localPath = join(projectRoot, docFile);
|
||||
const vscodeUrl = `${vscodeBase}/${localPath}`;
|
||||
|
||||
// Get CLI options for this command
|
||||
const cliOptions = this.commandOptionsMap[`influxdb3 ${command}`] || [];
|
||||
|
||||
// Parse documentation content to find documented options
|
||||
const documentedOptions =
|
||||
await this.parseDocumentedOptions(actualContentFile);
|
||||
|
||||
// Find missing options (in CLI but not in docs)
|
||||
const missingOptions = cliOptions.filter(
|
||||
(opt) => !documentedOptions.includes(opt)
|
||||
);
|
||||
|
||||
// Find extra options (in docs but not in CLI)
|
||||
const extraOptions = documentedOptions.filter(
|
||||
(opt) => !cliOptions.includes(opt)
|
||||
);
|
||||
|
||||
existingDocs.push({
|
||||
command,
|
||||
file: docFile,
|
||||
actualContentFile: actualContentFile.replace(
|
||||
join(projectRoot, ''),
|
||||
''
|
||||
),
|
||||
githubUrl: githubEditUrl,
|
||||
localPath,
|
||||
vscodeUrl,
|
||||
cliOptions,
|
||||
documentedOptions,
|
||||
missingOptions,
|
||||
extraOptions,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (existingDocs.length > 0) {
|
||||
output += 'Review these existing documentation files for accuracy:\n\n';
|
||||
|
||||
for (const doc of existingDocs) {
|
||||
output += `### \`influxdb3 ${doc.command}\`\n`;
|
||||
output += `- **File**: \`${doc.file}\`\n`;
|
||||
if (doc.actualContentFile !== doc.file) {
|
||||
output += `- **Content**: \`${doc.actualContentFile}\`\n`;
|
||||
}
|
||||
output += `- [Edit on GitHub](${doc.githubUrl})\n`;
|
||||
output += `- [Open in VS Code](${doc.vscodeUrl})\n`;
|
||||
output += `- **Local**: \`${doc.localPath}\`\n`;
|
||||
|
||||
// Show option analysis
|
||||
if (doc.missingOptions.length > 0) {
|
||||
output += `- **⚠️ Missing from docs** (${doc.missingOptions.length} options):\n`;
|
||||
for (const option of doc.missingOptions.sort()) {
|
||||
output += ` - \`${option}\`\n`;
|
||||
}
|
||||
}
|
||||
|
||||
if (doc.extraOptions.length > 0) {
|
||||
output += `- **ℹ️ Documented but not in CLI** (${doc.extraOptions.length} options):\n`;
|
||||
for (const option of doc.extraOptions.sort()) {
|
||||
output += ` - \`${option}\`\n`;
|
||||
}
|
||||
}
|
||||
|
||||
if (doc.missingOptions.length === 0 && doc.extraOptions.length === 0) {
|
||||
output += `- **✅ Options match** (${doc.cliOptions.length} options)\n`;
|
||||
}
|
||||
|
||||
if (doc.cliOptions.length > 0) {
|
||||
output += `- **All CLI Options** (${doc.cliOptions.length}):\n`;
|
||||
const uniqueOptions = [...new Set(doc.cliOptions)].sort();
|
||||
for (const option of uniqueOptions) {
|
||||
const status = doc.missingOptions.includes(option) ? '❌' : '✅';
|
||||
output += ` - ${status} \`${option}\`\n`;
|
||||
}
|
||||
}
|
||||
output += '\n';
|
||||
}
|
||||
}
|
||||
|
||||
output += '\n## Summary\n';
|
||||
output += `- Missing documentation files: ${missingCount}\n`;
|
||||
output += `- Existing documentation files: ${existingDocs.length}\n`;
|
||||
output += `- Generated templates: ${missingCount}\n`;
|
||||
output += '- Options are grouped by command for easier review\n\n';
|
||||
|
||||
output += '## Automation Suggestions\n\n';
|
||||
output +=
|
||||
'1. **Use generated templates**: Check the `patches` directory for pre-filled documentation templates\n';
|
||||
output +=
|
||||
'2. **Batch creation**: Use the shell commands above to quickly create all missing files\n';
|
||||
output +=
|
||||
'3. **CI Integration**: Add this audit to your CI pipeline to catch missing docs early\n';
|
||||
output +=
|
||||
'4. **Auto-PR**: Create a GitHub Action that runs this audit and opens PRs for missing docs\n\n';
|
||||
|
||||
await fs.writeFile(auditFile, output);
|
||||
console.log(`📄 Audit complete: ${auditFile}`);
|
||||
|
||||
if (missingCount > 0) {
|
||||
console.log(
|
||||
`📝 Generated ${missingCount} documentation templates in: ${patchDir}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async run() {
|
||||
console.log(
|
||||
`${Colors.BLUE}🔍 InfluxDB 3 CLI Documentation Audit${Colors.NC}`
|
||||
);
|
||||
console.log('=======================================');
|
||||
console.log(`Product: ${this.product}`);
|
||||
console.log(`Version: ${this.version}`);
|
||||
console.log();
|
||||
|
||||
// Ensure output directory exists
|
||||
await this.ensureDir(this.outputDir);
|
||||
|
||||
if (this.product === 'core') {
|
||||
const cliFile = join(
|
||||
this.outputDir,
|
||||
`current-cli-core-${this.version}.txt`
|
||||
);
|
||||
const auditFile = join(
|
||||
this.outputDir,
|
||||
`documentation-audit-core-${this.version}.md`
|
||||
);
|
||||
|
||||
if (await this.extractCurrentCLI('core', cliFile)) {
|
||||
await this.auditDocs('core', cliFile, auditFile);
|
||||
}
|
||||
} else if (this.product === 'enterprise') {
|
||||
const cliFile = join(
|
||||
this.outputDir,
|
||||
`current-cli-enterprise-${this.version}.txt`
|
||||
);
|
||||
const auditFile = join(
|
||||
this.outputDir,
|
||||
`documentation-audit-enterprise-${this.version}.md`
|
||||
);
|
||||
|
||||
if (await this.extractCurrentCLI('enterprise', cliFile)) {
|
||||
await this.auditDocs('enterprise', cliFile, auditFile);
|
||||
}
|
||||
} else if (this.product === 'both') {
|
||||
// Core
|
||||
const cliFileCore = join(
|
||||
this.outputDir,
|
||||
`current-cli-core-${this.version}.txt`
|
||||
);
|
||||
const auditFileCore = join(
|
||||
this.outputDir,
|
||||
`documentation-audit-core-${this.version}.md`
|
||||
);
|
||||
|
||||
if (await this.extractCurrentCLI('core', cliFileCore)) {
|
||||
await this.auditDocs('core', cliFileCore, auditFileCore);
|
||||
}
|
||||
|
||||
// Enterprise
|
||||
const cliFileEnt = join(
|
||||
this.outputDir,
|
||||
`current-cli-enterprise-${this.version}.txt`
|
||||
);
|
||||
const auditFileEnt = join(
|
||||
this.outputDir,
|
||||
`documentation-audit-enterprise-${this.version}.md`
|
||||
);
|
||||
|
||||
if (await this.extractCurrentCLI('enterprise', cliFileEnt)) {
|
||||
await this.auditDocs('enterprise', cliFileEnt, auditFileEnt);
|
||||
}
|
||||
} else {
|
||||
console.error(`Error: Invalid product '${this.product}'`);
|
||||
console.error(
|
||||
'Usage: node audit-cli-documentation.js [core|enterprise|both] [version]'
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log();
|
||||
console.log(
|
||||
`${Colors.GREEN}✅ CLI documentation audit complete!${Colors.NC}`
|
||||
);
|
||||
console.log();
|
||||
console.log('Next steps:');
|
||||
console.log(`1. Review the audit reports in: ${this.outputDir}`);
|
||||
console.log('2. Update missing documentation files');
|
||||
console.log('3. Verify options match current CLI behavior');
|
||||
console.log('4. Update examples and usage patterns');
|
||||
}
|
||||
}
|
||||
|
||||
// Main execution
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const product = args[0] || 'both';
|
||||
const version = args[1] || 'local';
|
||||
|
||||
// Validate product
|
||||
if (!['core', 'enterprise', 'both'].includes(product)) {
|
||||
console.error(`Error: Invalid product '${product}'`);
|
||||
console.error(
|
||||
'Usage: node audit-cli-documentation.js [core|enterprise|both] [version]'
|
||||
);
|
||||
console.error('Example: node audit-cli-documentation.js core 3.2.0');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const auditor = new CLIDocAuditor(product, version);
|
||||
await auditor.run();
|
||||
}
|
||||
|
||||
// Run if called directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
main().catch((err) => {
|
||||
console.error('Error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export { CLIDocAuditor };
|
|
@ -1,316 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Audit CLI documentation against current CLI help output
|
||||
# Usage: ./audit-cli-documentation.sh [core|enterprise|both] [version]
|
||||
# Example: ./audit-cli-documentation.sh core 3.2.0
|
||||
|
||||
set -e
|
||||
|
||||
# Color codes
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Parse arguments
|
||||
PRODUCT=${1:-both}
|
||||
VERSION=${2:-local}
|
||||
|
||||
echo -e "${BLUE}🔍 InfluxDB 3 CLI Documentation Audit${NC}"
|
||||
echo "======================================="
|
||||
echo "Product: $PRODUCT"
|
||||
echo "Version: $VERSION"
|
||||
echo ""
|
||||
|
||||
# Set up output directory
|
||||
OUTPUT_DIR="helper-scripts/output/cli-audit"
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# Load tokens from secret files
|
||||
load_tokens() {
|
||||
SECRET_CORE_FILE="$HOME/.env.influxdb3-core-admin-token"
|
||||
SECRET_ENT_FILE="$HOME/.env.influxdb3-enterprise-admin-token"
|
||||
|
||||
if [ -f "$SECRET_CORE_FILE" ] && [ -s "$SECRET_CORE_FILE" ]; then
|
||||
INFLUXDB3_CORE_TOKEN=$(cat "$SECRET_CORE_FILE")
|
||||
fi
|
||||
if [ -f "$SECRET_ENT_FILE" ] && [ -s "$SECRET_ENT_FILE" ]; then
|
||||
INFLUXDB3_ENTERPRISE_TOKEN=$(cat "$SECRET_ENT_FILE")
|
||||
fi
|
||||
}
|
||||
|
||||
# Get current CLI help for a product
|
||||
extract_current_cli() {
|
||||
local product=$1
|
||||
local output_file=$2
|
||||
|
||||
load_tokens
|
||||
|
||||
if [ "$VERSION" == "local" ]; then
|
||||
local container_name="influxdb3-${product}"
|
||||
|
||||
echo -n "Extracting current CLI help from ${container_name}..."
|
||||
|
||||
# Check if container is running
|
||||
if ! docker ps --format '{{.Names}}' | grep -q "^${container_name}$"; then
|
||||
echo -e " ${RED}✗${NC}"
|
||||
echo "Error: Container ${container_name} is not running."
|
||||
echo "Start it with: docker compose up -d influxdb3-${product}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Extract comprehensive help
|
||||
docker exec "${container_name}" influxdb3 --help > "$output_file" 2>&1
|
||||
|
||||
# Extract all subcommand help
|
||||
for cmd in create delete disable enable query show test update write; do
|
||||
echo "" >> "$output_file"
|
||||
echo "===== influxdb3 $cmd --help =====" >> "$output_file"
|
||||
docker exec "${container_name}" influxdb3 $cmd --help >> "$output_file" 2>&1 || true
|
||||
done
|
||||
|
||||
# Extract detailed subcommand help
|
||||
local subcommands=(
|
||||
"create database"
|
||||
"create token admin"
|
||||
"create token"
|
||||
"create trigger"
|
||||
"create last_cache"
|
||||
"create distinct_cache"
|
||||
"create table"
|
||||
"show databases"
|
||||
"show tokens"
|
||||
"show system"
|
||||
"delete database"
|
||||
"delete table"
|
||||
"delete trigger"
|
||||
"update database"
|
||||
"test wal_plugin"
|
||||
"test schedule_plugin"
|
||||
)
|
||||
|
||||
for subcmd in "${subcommands[@]}"; do
|
||||
echo "" >> "$output_file"
|
||||
echo "===== influxdb3 $subcmd --help =====" >> "$output_file"
|
||||
docker exec "${container_name}" influxdb3 $subcmd --help >> "$output_file" 2>&1 || true
|
||||
done
|
||||
|
||||
echo -e " ${GREEN}✓${NC}"
|
||||
else
|
||||
# Use specific version image
|
||||
local image="influxdb:${VERSION}-${product}"
|
||||
|
||||
echo -n "Extracting CLI help from ${image}..."
|
||||
|
||||
if ! docker pull "${image}" > /dev/null 2>&1; then
|
||||
echo -e " ${RED}✗${NC}"
|
||||
echo "Error: Failed to pull image ${image}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Extract help from specific version
|
||||
docker run --rm "${image}" influxdb3 --help > "$output_file" 2>&1
|
||||
|
||||
# Extract subcommand help
|
||||
for cmd in create delete disable enable query show test update write; do
|
||||
echo "" >> "$output_file"
|
||||
echo "===== influxdb3 $cmd --help =====" >> "$output_file"
|
||||
docker run --rm "${image}" influxdb3 $cmd --help >> "$output_file" 2>&1 || true
|
||||
done
|
||||
|
||||
echo -e " ${GREEN}✓${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Parse CLI help to extract structured information
|
||||
parse_cli_help() {
|
||||
local help_file=$1
|
||||
local parsed_file=$2
|
||||
|
||||
echo "# CLI Commands and Options" > "$parsed_file"
|
||||
echo "" >> "$parsed_file"
|
||||
|
||||
local current_command=""
|
||||
local in_options=false
|
||||
|
||||
while IFS= read -r line; do
|
||||
# Detect command headers
|
||||
if echo "$line" | grep -q "^===== influxdb3.*--help ====="; then
|
||||
current_command=$(echo "$line" | sed 's/^===== //' | sed 's/ --help =====//')
|
||||
echo "## $current_command" >> "$parsed_file"
|
||||
echo "" >> "$parsed_file"
|
||||
in_options=false
|
||||
# Detect options sections
|
||||
elif echo "$line" | grep -q "^Options:"; then
|
||||
echo "### Options:" >> "$parsed_file"
|
||||
echo "" >> "$parsed_file"
|
||||
in_options=true
|
||||
# Parse option lines
|
||||
elif [ "$in_options" = true ] && echo "$line" | grep -qE "^\s*-"; then
|
||||
# Extract option and description
|
||||
option=$(echo "$line" | grep -oE '\-\-[a-z][a-z0-9-]*' | head -1)
|
||||
short_option=$(echo "$line" | grep -oE '\s-[a-zA-Z],' | sed 's/[, ]//g')
|
||||
description=$(echo "$line" | sed 's/^[[:space:]]*-[^[:space:]]*[[:space:]]*//' | sed 's/^[[:space:]]*--[^[:space:]]*[[:space:]]*//')
|
||||
|
||||
if [ -n "$option" ]; then
|
||||
if [ -n "$short_option" ]; then
|
||||
echo "- \`$short_option, $option\`: $description" >> "$parsed_file"
|
||||
else
|
||||
echo "- \`$option\`: $description" >> "$parsed_file"
|
||||
fi
|
||||
fi
|
||||
# Reset options flag for new sections
|
||||
elif echo "$line" | grep -qE "^[A-Z][a-z]+:$"; then
|
||||
in_options=false
|
||||
fi
|
||||
done < "$help_file"
|
||||
}
|
||||
|
||||
# Find documentation files for a product
|
||||
find_docs() {
|
||||
local product=$1
|
||||
|
||||
case "$product" in
|
||||
"core")
|
||||
echo "content/influxdb3/core/reference/cli/influxdb3"
|
||||
;;
|
||||
"enterprise")
|
||||
echo "content/influxdb3/enterprise/reference/cli/influxdb3"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Audit documentation against CLI
|
||||
audit_docs() {
|
||||
local product=$1
|
||||
local cli_file=$2
|
||||
local audit_file=$3
|
||||
|
||||
local docs_path=$(find_docs "$product")
|
||||
local shared_path="content/shared/influxdb3-cli"
|
||||
|
||||
echo "# CLI Documentation Audit - $product" > "$audit_file"
|
||||
echo "Generated: $(date)" >> "$audit_file"
|
||||
echo "" >> "$audit_file"
|
||||
|
||||
# Check for missing documentation
|
||||
echo "## Missing Documentation" >> "$audit_file"
|
||||
echo "" >> "$audit_file"
|
||||
|
||||
local missing_count=0
|
||||
|
||||
# Extract commands from CLI help
|
||||
grep "^===== influxdb3.*--help =====" "$cli_file" | while read -r line; do
|
||||
local command=$(echo "$line" | sed 's/^===== influxdb3 //' | sed 's/ --help =====//')
|
||||
local expected_file=""
|
||||
|
||||
# Map command to expected documentation file
|
||||
case "$command" in
|
||||
"create database") expected_file="create/database.md" ;;
|
||||
"create token") expected_file="create/token/_index.md" ;;
|
||||
"create token admin") expected_file="create/token/admin.md" ;;
|
||||
"create trigger") expected_file="create/trigger.md" ;;
|
||||
"create table") expected_file="create/table.md" ;;
|
||||
"create last_cache") expected_file="create/last_cache.md" ;;
|
||||
"create distinct_cache") expected_file="create/distinct_cache.md" ;;
|
||||
"show databases") expected_file="show/databases.md" ;;
|
||||
"show tokens") expected_file="show/tokens.md" ;;
|
||||
"delete database") expected_file="delete/database.md" ;;
|
||||
"delete table") expected_file="delete/table.md" ;;
|
||||
"query") expected_file="query.md" ;;
|
||||
"write") expected_file="write.md" ;;
|
||||
*) continue ;;
|
||||
esac
|
||||
|
||||
if [ -n "$expected_file" ]; then
|
||||
# Check both product-specific and shared docs
|
||||
local product_file="$docs_path/$expected_file"
|
||||
local shared_file="$shared_path/$expected_file"
|
||||
|
||||
if [ ! -f "$product_file" ] && [ ! -f "$shared_file" ]; then
|
||||
echo "- **Missing**: Documentation for \`influxdb3 $command\`" >> "$audit_file"
|
||||
echo " - Expected: \`$product_file\` or \`$shared_file\`" >> "$audit_file"
|
||||
missing_count=$((missing_count + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$missing_count" -eq 0 ]; then
|
||||
echo "No missing documentation files detected." >> "$audit_file"
|
||||
fi
|
||||
|
||||
echo "" >> "$audit_file"
|
||||
|
||||
# Check for outdated options in existing docs
|
||||
echo "## Potentially Outdated Documentation" >> "$audit_file"
|
||||
echo "" >> "$audit_file"
|
||||
|
||||
local outdated_count=0
|
||||
|
||||
# This would require more sophisticated parsing of markdown files
|
||||
# For now, we'll note this as a manual review item
|
||||
echo "**Manual Review Needed**: Compare the following CLI options with existing documentation:" >> "$audit_file"
|
||||
echo "" >> "$audit_file"
|
||||
|
||||
# Extract all options from CLI help
|
||||
grep -E "^\s*(-[a-zA-Z],?\s*)?--[a-z][a-z0-9-]*" "$cli_file" | sort -u | while read -r option_line; do
|
||||
local option=$(echo "$option_line" | grep -oE '\--[a-z][a-z0-9-]*')
|
||||
if [ -n "$option" ]; then
|
||||
echo "- \`$option\`" >> "$audit_file"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "" >> "$audit_file"
|
||||
echo "## Summary" >> "$audit_file"
|
||||
echo "- Missing documentation files: $missing_count" >> "$audit_file"
|
||||
echo "- Manual review recommended for option accuracy" >> "$audit_file"
|
||||
echo "" >> "$audit_file"
|
||||
|
||||
echo "📄 Audit complete: $audit_file"
|
||||
}
|
||||
|
||||
# Main execution
|
||||
case "$PRODUCT" in
|
||||
"core")
|
||||
CLI_FILE="$OUTPUT_DIR/current-cli-core-${VERSION}.txt"
|
||||
AUDIT_FILE="$OUTPUT_DIR/documentation-audit-core-${VERSION}.md"
|
||||
|
||||
extract_current_cli "core" "$CLI_FILE"
|
||||
audit_docs "core" "$CLI_FILE" "$AUDIT_FILE"
|
||||
;;
|
||||
"enterprise")
|
||||
CLI_FILE="$OUTPUT_DIR/current-cli-enterprise-${VERSION}.txt"
|
||||
AUDIT_FILE="$OUTPUT_DIR/documentation-audit-enterprise-${VERSION}.md"
|
||||
|
||||
extract_current_cli "enterprise" "$CLI_FILE"
|
||||
audit_docs "enterprise" "$CLI_FILE" "$AUDIT_FILE"
|
||||
;;
|
||||
"both")
|
||||
# Core
|
||||
CLI_FILE_CORE="$OUTPUT_DIR/current-cli-core-${VERSION}.txt"
|
||||
AUDIT_FILE_CORE="$OUTPUT_DIR/documentation-audit-core-${VERSION}.md"
|
||||
|
||||
extract_current_cli "core" "$CLI_FILE_CORE"
|
||||
audit_docs "core" "$CLI_FILE_CORE" "$AUDIT_FILE_CORE"
|
||||
|
||||
# Enterprise
|
||||
CLI_FILE_ENT="$OUTPUT_DIR/current-cli-enterprise-${VERSION}.txt"
|
||||
AUDIT_FILE_ENT="$OUTPUT_DIR/documentation-audit-enterprise-${VERSION}.md"
|
||||
|
||||
extract_current_cli "enterprise" "$CLI_FILE_ENT"
|
||||
audit_docs "enterprise" "$CLI_FILE_ENT" "$AUDIT_FILE_ENT"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 [core|enterprise|both] [version]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}✅ CLI documentation audit complete!${NC}"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo "1. Review the audit reports in: $OUTPUT_DIR"
|
||||
echo "2. Update missing documentation files"
|
||||
echo "3. Verify options match current CLI behavior"
|
||||
echo "4. Update examples and usage patterns"
|
|
@ -64,7 +64,12 @@
|
|||
"test:links:telegraf": "node cypress/support/run-e2e-specs.js --spec \"cypress/e2e/content/article-links.cy.js\" content/telegraf/**/*.{md,html}",
|
||||
"test:links:shared": "node cypress/support/run-e2e-specs.js --spec \"cypress/e2e/content/article-links.cy.js\" content/shared/**/*.{md,html}",
|
||||
"test:links:api-docs": "node cypress/support/run-e2e-specs.js --spec \"cypress/e2e/content/article-links.cy.js\" /influxdb3/core/api/,/influxdb3/enterprise/api/,/influxdb3/cloud-dedicated/api/,/influxdb3/cloud-dedicated/api/v1/,/influxdb/cloud-dedicated/api/v1/,/influxdb/cloud-dedicated/api/management/,/influxdb3/cloud-dedicated/api/management/",
|
||||
"test:shortcode-examples": "node cypress/support/run-e2e-specs.js --spec \"cypress/e2e/content/article-links.cy.js\" content/example.md"
|
||||
"test:shortcode-examples": "node cypress/support/run-e2e-specs.js --spec \"cypress/e2e/content/article-links.cy.js\" content/example.md",
|
||||
"audit:cli": "node ./helper-scripts/influxdb3-monolith/audit-cli-documentation.js both local",
|
||||
"audit:cli:3core": "node ./helper-scripts/influxdb3-monolith/audit-cli-documentation.js core local",
|
||||
"audit:cli:3ent": "node ./helper-scripts/influxdb3-monolith/audit-cli-documentation.js enterprise local",
|
||||
"audit:cli:apply": "node ./helper-scripts/influxdb3-monolith/apply-cli-patches.js both",
|
||||
"audit:cli:apply:dry": "node ./helper-scripts/influxdb3-monolith/apply-cli-patches.js both --dry-run"
|
||||
},
|
||||
"type": "module",
|
||||
"browserslist": [
|
||||
|
|
Loading…
Reference in New Issue