diff --git a/.github/workflows/md-extension-autofix.yml b/.github/workflows/md-extension-autofix.yml new file mode 100644 index 0000000000..269c5aec91 --- /dev/null +++ b/.github/workflows/md-extension-autofix.yml @@ -0,0 +1,156 @@ +name: MD Extension Auto-Fix + +on: + pull_request: + types: [opened, synchronize] + branches: + - dev + +concurrency: + group: md-extension-autofix-${{ github.event.pull_request.number }} + +jobs: + md-extension-autofix: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Check if triggered by bot commit + id: bot-check + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + COMMIT=$(gh api repos/${{ github.repository }}/commits/${{ github.event.pull_request.head.sha }} \ + --jq '{message: .commit.message}') + MESSAGE=$(echo "$COMMIT" | jq -r '.message') + echo "Latest commit message: $MESSAGE" + if echo "$MESSAGE" | grep -qE '^fix\(docs\): add missing \.md extension'; then + echo "Skipping: commit is from md-extension-autofix workflow" + echo "skip=true" >> "$GITHUB_OUTPUT" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + + - name: Checkout PR branch + if: steps.bot-check.outputs.skip != 'true' + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.base.ref }} + fetch-depth: 0 + + + - name: Configure git identity + if: steps.bot-check.outputs.skip != 'true' + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Get changed files in docs/ + id: changed-files + if: steps.bot-check.outputs.skip != 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_NUMBER=${{ github.event.pull_request.number }} + CHANGED=$(gh pr diff "$PR_NUMBER" --name-only | grep -E '^docs/' || true) + if [ -z "$CHANGED" ]; then + echo "No docs/ files changed" + echo "count=0" >> "$GITHUB_OUTPUT" + else + echo "$CHANGED" > /tmp/changed-files.txt + echo "count=$(echo "$CHANGED" | wc -l | tr -d ' ')" >> "$GITHUB_OUTPUT" + fi + + - name: Run md-extension-autofix + id: autofix + if: steps.changed-files.outputs.count > 0 + run: | + chmod +x scripts/md-extension-autofix.sh + SUMMARY=$(./scripts/md-extension-autofix.sh /tmp/changed-files.txt) + echo "$SUMMARY" > /tmp/md-extension-summary.json + RENAMED_COUNT=$(echo "$SUMMARY" | jq '.renamed | length') + SKIPPED_COUNT=$(echo "$SUMMARY" | jq '.skipped | length') + echo "renamed=$RENAMED_COUNT" >> "$GITHUB_OUTPUT" + echo "skipped=$SKIPPED_COUNT" >> "$GITHUB_OUTPUT" + echo "Renamed: $RENAMED_COUNT, Skipped: $SKIPPED_COUNT" + + - name: Commit renamed files + id: commit + if: steps.autofix.outputs.renamed > 0 + run: | + if git diff --quiet && git diff --staged --quiet; then + echo "committed=false" >> "$GITHUB_OUTPUT" + else + git add -A docs/ + git commit -m "fix(docs): add missing .md extension to renamed files" + echo "committed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Push fixes + if: steps.commit.outputs.committed == 'true' + continue-on-error: true + env: + VALE_TOKEN: ${{ secrets.VALE_TOKEN }} + run: | + git remote set-url origin "https://x-access-token:${VALE_TOKEN}@github.com/${{ github.repository }}.git" + git push + + - name: Post PR comment + if: steps.changed-files.outputs.count > 0 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_NUMBER=${{ github.event.pull_request.number }} + REPO=${{ github.repository }} + + RENAMED_COUNT=0 + SKIPPED_COUNT=0 + if [ -f /tmp/md-extension-summary.json ]; then + RENAMED_COUNT=$(jq '.renamed | length' /tmp/md-extension-summary.json) + SKIPPED_COUNT=$(jq '.skipped | length' /tmp/md-extension-summary.json) + fi + + # Nothing to report — exit silently + if [ "$RENAMED_COUNT" -eq 0 ] && [ "$SKIPPED_COUNT" -eq 0 ]; then + exit 0 + fi + + { + if [ "$RENAMED_COUNT" -gt 0 ]; then + echo "**Auto-fix: Missing \`.md\` extensions added**" + echo "" + echo "The following files were missing a \`.md\` extension and would not have appeared on the docs site. They've been renamed automatically:" + echo "" + echo "| Original filename | Renamed to |" + echo "|---|---|" + jq -r '.renamed[] | "| `\(.from)` | `\(.to | split("/") | last)` |"' /tmp/md-extension-summary.json + echo "" + echo "Links to these files in other pages have been updated. Please review the changes in the commit above." + fi + + if [ "$SKIPPED_COUNT" -gt 0 ]; then + if [ "$RENAMED_COUNT" -gt 0 ]; then echo ""; fi + echo "**Action needed: Possible missing \`.md\` extension**" + echo "" + echo "The following files were added to a docs directory without a \`.md\` extension, but couldn't be auto-renamed:" + echo "" + echo "| File | Reason |" + echo "|---|---|" + jq -r '.skipped[] | "| `\(.file)` | \(.reason) |"' /tmp/md-extension-summary.json + echo "" + echo "If these are markdown pages, add the \`.md\` extension and push again." + fi + } > /tmp/md-extension-comment.md + + # Delete previous comments from this workflow + COMMENT_IDS=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" \ + --jq '[.[] | select( + .user.login == "github-actions[bot]" and + (.body | (contains("Auto-fix: Missing `.md` extensions") or contains("Action needed: Possible missing `.md` extension"))) + ) | .id] | .[]' 2>/dev/null || true) + for ID in $COMMENT_IDS; do + gh api "repos/${REPO}/issues/comments/${ID}" -X DELETE 2>/dev/null || true + done + + gh pr comment "$PR_NUMBER" --repo "$REPO" --body-file /tmp/md-extension-comment.md diff --git a/docs/accessanalyzer/11.6/admin/jobs/instantjobs/fs_sdd_delete b/docs/accessanalyzer/11.6/admin/jobs/instantjobs/fs_sdd_delete.md similarity index 61% rename from docs/accessanalyzer/11.6/admin/jobs/instantjobs/fs_sdd_delete rename to docs/accessanalyzer/11.6/admin/jobs/instantjobs/fs_sdd_delete.md index e7acec3d49..1169360a09 100644 --- a/docs/accessanalyzer/11.6/admin/jobs/instantjobs/fs_sdd_delete +++ b/docs/accessanalyzer/11.6/admin/jobs/instantjobs/fs_sdd_delete.md @@ -1,5 +1,10 @@ +--- +title: "FS_SDD_DELETE Job" +description: "FS_SDD_DELETE Job" +sidebar_position: 45 +--- -# FS_SDD_DELETE Instant Job +# FS_SDD_DELETE Job The FS_SDD_DELETE instant job deletes Sensitive Data Discovery (SDD) data from the Tier 1 database for specified criteria, hosts, or combinations thereof. This job is available in the Instant Job Library under the File System library. @@ -7,34 +12,36 @@ The FS_SDD_DELETE instant job deletes Sensitive Data Discovery (SDD) data from t ## Runtime Details -- **Dependencies**: The 0.Collection Job Group must be successfully run before running this job +- **Dependencies**: Run the 0.Collection Job Group successfully before running this job - **Target Hosts**: None (select Local host) -- **Scheduling**: Can be run as desired, typically on an ad-hoc basis -- **History Retention**: Not supported and must be turned off +- **Scheduling**: Run as needed, typically on an ad-hoc basis +- **History Retention**: Not supported. Turn off history retention before running this job. - **Multi-console Support**: Not supported -- **Additional Notes**: This job performs permanent data deletion with no undo capability. All analysis tasks are disabled by default to prevent accidental data loss. +- **Additional Notes**: This job performs permanent data deletion with no undo capability. Access Analyzer disables all analysis tasks by default to prevent accidental data loss. -The FS_SDD_DELETE instant job provides a controlled method for removing Sensitive Data Discovery data from your Tier 1 database. Use this job to clean up SDD data for specific criteria, remove data associated with decommissioned hosts, or delete specific host-and-criteria combinations. Because this job permanently deletes data with no recovery option, all analysis tasks are disabled by default as a safety measure. +The FS_SDD_DELETE instant job provides a controlled method for removing Sensitive Data Discovery data from your Tier 1 database. Use this job to delete SDD data for specific criteria, remove data associated with decommissioned hosts, or delete specific host-and-criteria combinations. Because this job permanently deletes data with no recovery option, Access Analyzer disables all analysis tasks by default as a safety measure. ## Analysis Tasks for the FS_SDD_DELETE Job To see the analysis tasks for this job, navigate to **Jobs > Instant Job Library > File System > FS_SDD_DELETE** and select the **Analysis Tasks** tab. -> **WARNING**: This job permanently deletes data from the database. This action cannot be undone. All analysis tasks are disabled by default to prevent accidental data loss. Carefully review the data to be deleted before enabling and running any analysis task. +:::warning +This job permanently deletes data from the database. You can't undo this action. Access Analyzer disables all analysis tasks by default to prevent accidental data loss. Carefully review the data to be deleted before enabling and running any analysis task. +::: The following analysis tasks are available for the FS_SDD_DELETE job: -- **Delete Criteria** – Deletes all SDD data for specified criteria from all hosts. Use this task when you want to remove all occurrences of specific criteria across your entire environment. +- **Delete Criteria** – Deletes all SDD data for specified criteria from all hosts. Use this task to remove all occurrences of specific criteria across your entire environment. - **Delete Host** – Deletes all SDD data related to a specific host. Use this task when decommissioning a host or removing all SDD data associated with a particular system. -- **Remove Host & Criteria** – Deletes all SDD data for a specific host and criteria combination. Use this task for targeted removal of SDD data for a specific criterion on a specific host. +- **Remove Host & Criteria** – Deletes all SDD data for a specific host and criteria combination. Use this task to remove SDD data for a specific criterion on a specific host. ### Configuring the Analysis Tasks -Each analysis task requires manually populating temporary database tables before execution. Follow these steps to configure and run an analysis task: +Each analysis task requires manually populating temporary database tables before execution: 1. Open SQL Server Management Studio and connect to your Tier 1 database. 2. Determine which analysis task you need to run based on the data you want to delete. -3. Populate the required temporary table(s): +3. Populate the required temporary tables: - For **Delete Criteria**: Populate the `#Criteria` temporary table with the criteria names you want to delete - For **Delete Host**: Populate the `#hosts` temporary table with the host names you want to delete - For **Remove Host & Criteria**: Populate both the `#hosts` and `#Criteria` temporary tables with the specific host and criteria combinations @@ -43,5 +50,5 @@ Each analysis task requires manually populating temporary database tables before 6. Right-click the appropriate analysis task and select **Enable**. 7. Review the enabled task to verify it will delete the correct data. 8. Run the job by clicking **Run Job** and selecting **Local** as the target host. -9. After the job completes, verify the data has been deleted as expected. +9. After the job completes, verify the job deleted the data as expected. 10. Disable the analysis task to prevent accidental future deletions. diff --git a/docs/accessanalyzer/12.0/admin/jobs/instantjobs/fs_sdd_delete.md b/docs/accessanalyzer/12.0/admin/jobs/instantjobs/fs_sdd_delete.md index 6e0a7de4f4..1788f04a9d 100644 --- a/docs/accessanalyzer/12.0/admin/jobs/instantjobs/fs_sdd_delete.md +++ b/docs/accessanalyzer/12.0/admin/jobs/instantjobs/fs_sdd_delete.md @@ -1,5 +1,10 @@ +--- +title: "FS_SDD_DELETE Job" +description: "FS_SDD_DELETE Job" +sidebar_position: 45 +--- -# FS_SDD_DELETE Instant Job +# FS_SDD_DELETE Job The FS_SDD_DELETE instant job deletes Sensitive Data Discovery (SDD) data from the Tier 1 database for specified criteria, hosts, or combinations thereof. This job is available in the Instant Job Library under the File System library. @@ -7,26 +12,28 @@ The FS_SDD_DELETE instant job deletes Sensitive Data Discovery (SDD) data from t ## Runtime Details -- **Dependencies**: The 0.Collection Job Group must be successfully run before running this job +- **Dependencies**: Run the 0.Collection Job Group successfully before running this job - **Target Hosts**: None (select Local host) -- **Scheduling**: Can be run as desired, typically on an ad-hoc basis -- **History Retention**: Not supported and must be turned off +- **Scheduling**: Run as needed, typically on an ad-hoc basis +- **History Retention**: Not supported. Turn off history retention before running this job. - **Multi-console Support**: Not supported -- **Additional Notes**: This job performs permanent data deletion with no undo capability. All analysis tasks are disabled by default to prevent accidental data loss. +- **Additional Notes**: This job performs permanent data deletion with no undo capability. Access Analyzer disables all analysis tasks by default to prevent accidental data loss. -The FS_SDD_DELETE instant job provides a controlled method for removing Sensitive Data Discovery data from your Tier 1 database. Use this job to clean up SDD data for specific criteria, remove data associated with decommissioned hosts, or delete specific host-and-criteria combinations. Because this job permanently deletes data with no recovery option, all analysis tasks are disabled by default as a safety measure. +The FS_SDD_DELETE instant job provides a controlled method for removing Sensitive Data Discovery data from your Tier 1 database. Use this job to delete SDD data for specific criteria, remove data associated with decommissioned hosts, or delete specific host-and-criteria combinations. Because this job permanently deletes data with no recovery option, Access Analyzer disables all analysis tasks by default as a safety measure. ## Analysis Tasks for the FS_SDD_DELETE Job To see the analysis tasks for this job, navigate to **Jobs > Instant Job Library > File System > FS_SDD_DELETE** and select the **Analysis Tasks** tab. -> **WARNING**: This job permanently deletes data from the database. This action can't be undone. All analysis tasks are disabled by default to prevent accidental data loss. Carefully review the data to be deleted before enabling and running any analysis task. +:::warning +This job permanently deletes data from the database. You can't undo this action. Access Analyzer disables all analysis tasks by default to prevent accidental data loss. Carefully review the data to be deleted before enabling and running any analysis task. +::: The following analysis tasks are available for the FS_SDD_DELETE job: -- **Delete Criteria** – Deletes all SDD data for specified criteria from all hosts. Use this task when you want to remove all occurrences of specific criteria across your entire environment. +- **Delete Criteria** – Deletes all SDD data for specified criteria from all hosts. Use this task to remove all occurrences of specific criteria across your entire environment. - **Delete Host** – Deletes all SDD data related to a specific host. Use this task when decommissioning a host or removing all SDD data associated with a particular system. -- **Remove Host & Criteria** – Deletes all SDD data for a specific host and criteria combination. Use this task for targeted removal of SDD data for a specific criterion on a specific host. +- **Remove Host & Criteria** – Deletes all SDD data for a specific host and criteria combination. Use this task to remove SDD data for a specific criterion on a specific host. ### Configuring the Analysis Tasks @@ -43,5 +50,5 @@ Each analysis task requires manually populating temporary database tables before 6. Right-click the appropriate analysis task and select **Enable**. 7. Review the enabled task to verify it will delete the correct data. 8. Run the job by clicking **Run Job** and selecting **Local** as the target host. -9. After the job completes, verify the data has been deleted as expected. +9. After the job completes, verify the job deleted the data as expected. 10. Disable the analysis task to prevent accidental future deletions. diff --git a/docs/kb/accessanalyzer/file-system-and-sensitive-data-discovery/delete-sdd-matches.md b/docs/kb/accessanalyzer/file-system-and-sensitive-data-discovery/delete-sdd-matches.md index 806ab1ba79..4c2b6e2944 100644 --- a/docs/kb/accessanalyzer/file-system-and-sensitive-data-discovery/delete-sdd-matches.md +++ b/docs/kb/accessanalyzer/file-system-and-sensitive-data-discovery/delete-sdd-matches.md @@ -50,8 +50,10 @@ SET MatchData = NULL, ### Removing All SDD Matches Use the `FS_SDD_DELETE` instant job to remove SDD matches entirely: -- **NAA v12.0:** [InstantJobs\FS_SDD_DELETE](https://docs.netwrix.com/docs/accessanalyzer/12_0/admin/jobs/instantjobs/fs_sdd_delete) -- **NAA v11.6:** [InstantJobs\FS_SDD_DELETE](https://docs.netwrix.com/docs/accessanalyzer/11_6/admin/jobs/instantjobs/fs_sdd_delete) +- **Access Analyzer 12.0:** [FS_SDD_DELETE Job](https://docs.netwrix.com/docs/accessanalyzer/12_0/admin/jobs/instantjobs/fs_sdd_delete) +- **Access Analyzer 11.6:** [FS_SDD_DELETE Job](https://docs.netwrix.com/docs/accessanalyzer/11_6/admin/jobs/instantjobs/fs_sdd_delete) -## Related Link +## Related Links - [Configure the (SEEK) File System Scan Query](https://docs.netwrix.com/docs/accessanalyzer/12_0/solutions/filesystem/collection/seek_system_scans#configure-the-seek-file-system-scan-query) +- [FS_SDD_DELETE Job — Access Analyzer 12.0](https://docs.netwrix.com/docs/accessanalyzer/12_0/admin/jobs/instantjobs/fs_sdd_delete) +- [FS_SDD_DELETE Job — Access Analyzer 11.6](https://docs.netwrix.com/docs/accessanalyzer/11_6/admin/jobs/instantjobs/fs_sdd_delete) diff --git a/scripts/md-extension-autofix.sh b/scripts/md-extension-autofix.sh new file mode 100644 index 0000000000..faa6bd1b89 --- /dev/null +++ b/scripts/md-extension-autofix.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash +# md-extension-autofix.sh — detect and fix missing .md extensions in docs/ files +# Usage: md-extension-autofix.sh +# Output: JSON summary to stdout +set -euo pipefail + +# Extensions that are intentionally non-markdown — never rename these +IGNORE_EXTENSIONS=("html" "htm" "py" "ps1" "plantuml" "tmp" "DS_Store" "png" "jpg" "jpeg" "webp" "gif" "svg" "pdf" "zip" "json" "js" "mjs" "ts" "sh" "yml" "yaml" "css" "txt" "xml" "csv" "lock") + +has_extension() { + local file="$1" + local basename + basename=$(basename "$file") + [[ "$basename" == *.* ]] +} + +is_ignored_extension() { + local file="$1" + local basename ext + basename=$(basename "$file") + ext="${basename##*.}" + if [ "$ext" = "$basename" ]; then + return 1 # no extension + fi + for ignored in "${IGNORE_EXTENSIONS[@]}"; do + if [ "$ext" = "$ignored" ]; then + return 0 + fi + done + return 1 +} + +is_markdown_content() { + local file="$1" + local has_frontmatter=0 + local has_heading=0 + if head -10 "$file" | grep -qE '^---'; then + has_frontmatter=1 + fi + if grep -qE '^#{1,6} ' "$file"; then + has_heading=1 + fi + [ "$has_frontmatter" -eq 1 ] && [ "$has_heading" -eq 1 ] +} + +rewrite_links_in_docs() { + local old_basename="$1" + local new_basename="$2" + local expr1="s|](\([^)]*\)${old_basename})|](\1${new_basename})|g" + local expr2="s|](\([^)#]*\)${old_basename}#|](\1${new_basename}#|g" + # macOS BSD sed requires `sed -i ''`; GNU sed (Linux/CI) uses `sed -i` + if [[ "$(uname -s)" == "Darwin" ]]; then + find docs/ -name '*.md' -exec sed -i '' -e "$expr1" -e "$expr2" {} + + else + find docs/ -name '*.md' -exec sed -i -e "$expr1" -e "$expr2" {} + + fi +} + +# Allow sourcing for tests +case "${1:-}" in + --test) + return 0 2>/dev/null || exit 0 + ;; +esac + +CHANGED_FILES_LIST="${1:?Usage: md-extension-autofix.sh }" + +if [ ! -f "$CHANGED_FILES_LIST" ]; then + echo '{"renamed": [], "skipped": []}' + exit 0 +fi + +RENAMED_FROM=() +RENAMED_TO=() +SKIPPED_FILES=() +SKIP_REASONS=() + +while IFS= read -r file; do + # Only process files inside docs/ + [[ "$file" == docs/* ]] || continue + + # Skip deleted files + [ -f "$file" ] || continue + + # Skip files that already have an extension + has_extension "$file" && continue + + # Skip files with a known non-markdown extension + is_ignored_extension "$file" && continue + + new_file="${file}.md" + + # Skip if destination already exists + if [ -f "$new_file" ]; then + SKIPPED_FILES+=("$file") + SKIP_REASONS+=("$(basename "$new_file") already exists in the same folder") + continue + fi + + # Skip if content doesn't look like markdown + if ! is_markdown_content "$file"; then + SKIPPED_FILES+=("$file") + SKIP_REASONS+=("Content doesn't look like markdown — please check and rename manually if needed") + continue + fi + + # Rename and rewrite links + # Use mv + git add instead of git mv so it works for both tracked and untracked files + mv "$file" "$new_file" + git add "$new_file" + git rm --cached "$file" 2>/dev/null || true + rewrite_links_in_docs "$(basename "$file")" "$(basename "$new_file")" + + RENAMED_FROM+=("$file") + RENAMED_TO+=("$new_file") + +done < "$CHANGED_FILES_LIST" + +# Output JSON summary +RENAMED_JSON="[" +for i in "${!RENAMED_FROM[@]}"; do + [ "$i" -gt 0 ] && RENAMED_JSON+="," + RENAMED_JSON+="{\"from\": \"${RENAMED_FROM[$i]}\", \"to\": \"${RENAMED_TO[$i]}\"}" +done +RENAMED_JSON+="]" + +SKIPPED_JSON="[" +for i in "${!SKIPPED_FILES[@]}"; do + [ "$i" -gt 0 ] && SKIPPED_JSON+="," + SKIPPED_JSON+="{\"file\": \"${SKIPPED_FILES[$i]}\", \"reason\": \"${SKIP_REASONS[$i]}\"}" +done +SKIPPED_JSON+="]" + +echo "{\"renamed\": ${RENAMED_JSON}, \"skipped\": ${SKIPPED_JSON}}" diff --git a/scripts/test-md-extension-autofix.sh b/scripts/test-md-extension-autofix.sh new file mode 100644 index 0000000000..29d6bd3eef --- /dev/null +++ b/scripts/test-md-extension-autofix.sh @@ -0,0 +1,155 @@ +#!/usr/bin/env bash +# test-md-extension-autofix.sh — unit tests for md-extension-autofix.sh +set -euo pipefail + +# Source just the functions (--test mode skips the main script logic) +source "$(dirname "$0")/md-extension-autofix.sh" --test + +PASS=0 +FAIL=0 + +assert_true() { + local label="$1" + local result="$2" + if [ "$result" = "0" ]; then + PASS=$((PASS + 1)) + else + FAIL=$((FAIL + 1)) + echo "FAIL (expected true): $label" + fi +} + +assert_false() { + local label="$1" + local result="$2" + if [ "$result" != "0" ]; then + PASS=$((PASS + 1)) + else + FAIL=$((FAIL + 1)) + echo "FAIL (expected false): $label" + fi +} + +# ---- has_extension ---- + +has_extension "docs/kb/some-article" && R=$? || R=$? +assert_false "no extension: docs/kb/some-article" "$R" + +has_extension "docs/kb/some-article.md" && R=$? || R=$? +assert_true "has extension: .md" "$R" + +has_extension "docs/kb/.DS_Store" && R=$? || R=$? +assert_true "has extension: .DS_Store (hidden file with ext)" "$R" + +has_extension "docs/kb/script.ps1" && R=$? || R=$? +assert_true "has extension: .ps1" "$R" + +# ---- is_ignored_extension ---- + +is_ignored_extension "docs/kb/article.ps1" && R=$? || R=$? +assert_true "ignored: .ps1" "$R" + +is_ignored_extension "docs/kb/diagram.plantuml" && R=$? || R=$? +assert_true "ignored: .plantuml" "$R" + +is_ignored_extension "docs/kb/advisory.html" && R=$? || R=$? +assert_true "ignored: .html" "$R" + +is_ignored_extension "docs/kb/no-extension" && R=$? || R=$? +assert_false "not ignored: no extension" "$R" + +is_ignored_extension "docs/kb/article.md" && R=$? || R=$? +assert_false "not ignored: .md" "$R" + +# ---- is_markdown_content ---- + +TMPDIR_TEST=$(mktemp -d) + +# File with both frontmatter and heading → markdown +cat > "$TMPDIR_TEST/good.md" <<'EOF' +--- +title: Test +--- + +# My Heading + +Some content here. +EOF +is_markdown_content "$TMPDIR_TEST/good.md" && R=$? || R=$? +assert_true "markdown: has frontmatter and heading" "$R" + +# File with only heading, no frontmatter → not markdown +cat > "$TMPDIR_TEST/no-frontmatter" <<'EOF' +# My Heading + +Some content here. +EOF +is_markdown_content "$TMPDIR_TEST/no-frontmatter" && R=$? || R=$? +assert_false "not markdown: heading only, no frontmatter" "$R" + +# File with only frontmatter, no heading → not markdown +cat > "$TMPDIR_TEST/no-heading" <<'EOF' +--- +title: Test +--- + +Some content here with no heading. +EOF +is_markdown_content "$TMPDIR_TEST/no-heading" && R=$? || R=$? +assert_false "not markdown: frontmatter only, no heading" "$R" + +# Empty file → not markdown +touch "$TMPDIR_TEST/empty" +is_markdown_content "$TMPDIR_TEST/empty" && R=$? || R=$? +assert_false "not markdown: empty file" "$R" + +# PowerShell script → not markdown +cat > "$TMPDIR_TEST/script.ps1" <<'EOF' +param([string]$AgentPath) +Write-Host "Installing agent..." +Start-Process $AgentPath +EOF +is_markdown_content "$TMPDIR_TEST/script.ps1" && R=$? || R=$? +assert_false "not markdown: powershell script" "$R" + +rm -rf "$TMPDIR_TEST" + +# ---- rewrite_links_in_docs ---- + +TMPDIR_LINKS=$(mktemp -d) +mkdir -p "$TMPDIR_LINKS/docs/product/1.0" + +# File that links to the old extensionless path +cat > "$TMPDIR_LINKS/docs/product/1.0/overview.md" <<'EOF' +--- +title: Overview +--- + +# Overview + +See [delete matches](delete-sdd-matches) for details. +Also see [this page](../other/delete-sdd-matches) and [anchored](delete-sdd-matches#section). +EOF + +# Run rewrite from inside the temp dir +(cd "$TMPDIR_LINKS" && rewrite_links_in_docs "delete-sdd-matches" "delete-sdd-matches.md") + +# Check plain link was rewritten +grep -q '](delete-sdd-matches.md)' "$TMPDIR_LINKS/docs/product/1.0/overview.md" && R=0 || R=1 +assert_true "link rewrite: plain link updated" "$R" + +# Check anchored link was rewritten +grep -q '](delete-sdd-matches.md#section)' "$TMPDIR_LINKS/docs/product/1.0/overview.md" && R=0 || R=1 +assert_true "link rewrite: anchored link updated" "$R" + +# Check relative path link was rewritten +grep -q '](../other/delete-sdd-matches.md)' "$TMPDIR_LINKS/docs/product/1.0/overview.md" && R=0 || R=1 +assert_true "link rewrite: relative path link updated" "$R" + +rm -rf "$TMPDIR_LINKS" + +echo "" +echo "Results: $PASS passed, $FAIL failed" +if [ "$FAIL" -gt 0 ]; then + exit 1 +fi