When Git Becomes Your Blog Editor: Automated Grammar Checking for Tech Writers
¡Hola amigos! Ever sent an important email to notice that embarrassing typo right after hitting send? Now imagine doing that with your technical documentation that thousands of developers might read. Or submitting a PR with working code, only to have someone comment “typo in line 47” and nothing else? Or starting your own blog and… ¡Qué horror!
I know this is still not cybersecurity, but despacio, we will get there. We have to set up our blog first.
Why we want to do this
You know what’s funny about starting a tech blog? You spend hours perfecting your code examples, making sure every technical detail is spot-on, and then someone comments “you wrote ‘recieve’ instead of ‘receive’ in the first paragraph.” ¡Ay, caramba! We don’t want this to happen
After writing my first couple of blog posts about my journey from verification to cybersecurity, I noticed I was spending almost as much time proofreading as writing. And still, those sneaky typos kept slipping through!
I decided to do what any reasonable developer would: automate the problem away.
Since I’m planning to write more about my cybersecurity adventures (aventuras, as we say in Spanish), I needed something better than just staring at my screen hoping to catch all typos. And being the tech person I am, I thought - why not make Git do the work?
Here’s what we’re building today:
- A GitHub Action that catches typos before they go public
- A local pre-merge hook for immediate feedback
- A bilingual setup because, well, you’ve seen my Spanish learning attempts in these posts
How are we going to do this
Let’s break this down into manageable pieces. And no, I won’t make a cooking analogy here - I’m actually writing this while hungry, and that would just be cruel. 🥘
Setting Up GitHub Actions
First, let’s create our workflow structure. Remember, good organization is like good code - it makes life easier later:
# cd to you project direcrory
mkdir -p .github/workflows
# Create and edit the workflow file
vim .github/workflows/quality-check.yml
Then, edit the workflow configuration
# Name of the workflow - this will appear in GitHub Actions tab
name: Spell and Grammar Check
# Define when this workflow should run
on:
pull_request:
# Only run on PRs targeting the develop branch
branches: [ develop ]
# Specify which pull request actions should trigger this
# opened: when PR is created
# synchronize: when new commits are pushed to the PR
# reopened: when a closed PR is reopened
types: [opened, synchronize, reopened]
# Define the jobs that this workflow will run
jobs:
# Job name is 'spellcheck'
spellcheck:
# Use the latest Ubuntu runner provided by GitHub
runs-on: ubuntu-latest
# List of steps to execute in this job
steps:
# Step 1: Check out the repository code
- uses: actions/checkout@v3
# Step 2: Set up Node.js environment
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '18' # Specify Node.js version
# Step 3: Install cspell globally
- name: Install cspell
run: npm install -g cspell
# Step 4: Install write-good globally
- name: Install write-good
run: npm install -g write-good
# Install dependencies including the Spanish dictionary
- name: Install dependencies
run: |
npm install
npm install --save-dev @cspell/dict-es-es
npm install --save-dev textlint textlint-rule-write-good textlint-rule-no-dead-link \
textlint-rule-terminology textlint-rule-period-in-list-item \
textlint-rule-no-start-duplicated-conjunction @textlint/textlint-plugin-markdown
pip install proselint
# Step 5: Run the spell checker
- name: Run spell check
run: |
echo "Running spell check..."
# Check all markdown, mdx, and txt files
# Exit with error (exit 1) if spell check fails
cspell "**/*.{md,mdx,txt}" || exit 1
# Step 6: Run the grammar checker
- name: Run textlint
run: |
echo "Running textlint check..."
find . -type f -name "*.md" -o -name "*.mdx" -o -name "*.txt" | while read file; do
echo "Checking $file"
npx textlint "$file" || true
done
- name: Run proselint
run: |
echo "Running proselint check..."
find . -type f -name "*.md" -o -name "*.mdx" -o -name "*.txt" | while read file; do
echo "Checking $file"
proselint "$file" || true
done
Then, create a customized cspell configuration file to customize spell checker
npm init -y
npm install --save-dev cspell
Then, create cspell.json
{
"version": "0.2",
"language": "en",
"words": [],
"flagWords": [],
"ignorePaths": [
"node_modules/**"
]
}
Testing if this works (As we use to say it trust but verify)
As any verification engineer would tell you (and boy, do we love telling people this), if you haven’t tested it, it doesn’t work! Let’s intentionally break stuff - it’s the only way to be sure our checks are working.
- First, make sure to push the workflow file to repository (it’s okay to do this on develop branch)
git add .github/workflows/quality-check.yml git commit -m "Add spell check workflow" git push origin your-current-branch
- Create a test branch with some intentional spelling mistakes:
# Create and checkout a new branch git checkout -b test-spellcheck # Create a test markdown file with some spelling mistakes echo "This is a testt documment with misspelled wurds." > test-post.md
- Then push this branch
git add test-post.md git commit -m "Add test post with spelling mistakes" git push origin test-spellcheck
- Now, on GitHub repository
- Click on “Pull requests”
- Click “New pull request”
- Set “base” to develop
- Set “compare” to test-spellcheck
- Create the pull request
- After creating the PR, you can check the workflow results
- Go to the PR page
- Look for the “Checks” tab or scroll down to see the GitHub Actions status
- Click on the “Details” link next to the spell check workflow to see the full output
Local merging spell check
While GitHub Actions are great, waiting for the cloud to tell you about typos is like waiting for your code to compile - ain’t nobody got time for that! Let’s set up local checks.
We want to set this spell check in a way that when we do merge locally, it’s invoked and merging won’t be executed until this check passes.
For this, we will need to use Git hooks, as Git Actions works only for remote/cloud checks.
-
First, install all the required packages
# Install cspell npm install --save-dev cspell @cspell/dict-es-es # Install textlint and common rules npm install --save-dev \ textlint \ textlint-rule-write-good \ textlint-rule-no-dead-link \ textlint-rule-terminology \ textlint-rule-period-in-list-item \ textlint-rule-no-start-duplicated-conjunction \ @textlint/textlint-plugin-markdown # Install proselint pip install proselint
-
Create the hooks directory if it doesn’t exist
mkdir -p .git/hooks
-
Create pre-merge-commit hook and make it executable
vim .git/hooks/pre-merge-commit
Now , copy the following into the hook
#!/bin/bash # This script runs automatically when you try to merge branches # Get the name of the current branch we're on # git rev-parse --abbrev-ref HEAD returns the short name of the current branch CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) # The branch we want to protect with spell checking # Change this if you want to protect a different branch TARGET_BRANCH="develop" # Only run the checks when we're merging into the target branch (develop) if [ "$CURRENT_BRANCH" = "$TARGET_BRANCH" ]; then echo "Running spell and grammar checks before merge..." # Get a list of all markdown files that are staged for commit # git diff --name-only : List changed files # --cached : Look at staged files # --diff-filter=ACM : Only look at Added, Changed, or Modified files # grep -E '\.(md|mdx|txt)$' : Only look at markdown and text files FILES=$(git diff --name-only --cached --diff-filter=ACM | grep -E '\.(md|mdx|txt)$') # If we found any markdown files to check if [ -n "$FILES" ]; then # Run spell check using cspell # The '!' means "if the command fails" # npx ensures we use the project's installed version of cspell if ! npx cspell $FILES; then echo "❌ Spell check failed! Please fix the spelling errors before merging." # Exit with error code 1 to prevent the merge exit 1 fi # Run grammar check using write-good # We run this even if spell check fails # '|| true' means the script continues even if write-good finds issues echo "Grammar check (warnings):" for file in $FILES; do npx write-good "$file" || true done # If we got here, spell check passed echo "✅ Spell check passed!" echo "⚠️ Review any grammar warnings above" else # No markdown files were changed echo "No markdown files to check." fi fi # Exit successfully - allow the merge to proceed exit 0
chmod +x .git/hooks/pre-merge-commit
-
Let’s create a
.textlintrc
file{ "plugins": [ "@textlint/markdown" ], "rules": { "write-good": { "passive": true, "tooWordy": true, "weasel": true, "adverb": true }, "terminology": { "defaultTerms": true }, "period-in-list-item": true, "no-start-duplicated-conjunction": { "interval": 2 }, "no-dead-link": { "ignore": [ "mailto:", "http://localhost" ] } } }
-
When you try to merge locally
git checkout develop git merge test-spellcheck
Updates: Making it Más Interesante
Updating cspell configuration file: Because We Speak Spanglish
Remember how I mentioned I’m learning Spanish after that trip to Madrid? Well, might as well make our spell checker bilingual too. Who knows, maybe it’ll help with my Spanish blog posts in the future (yes, that’s coming - próximamente).
{
// Version of the config file format
"version": "0.2",
// List of languages to use for spell checking
"language": "en,es",
// Enable mixing languages within the same file
"enableFiletypes": ["*"],
"enabledLanguageIds": ["*"],
"allowCompoundWords": true,
// Import Spanish dictionary
"import": [
"@cspell/dict-es-es/cspell-ext.json"
],
// Words to always consider correct
"words": [
"CySecRoad" // Your project name
// Add your custom words here
"ciberseguridad",
"cybersecurity",
"pentesting",
"hacking"
],
// Dictionaries to load
"dictionaries": [
"en_US", // American English
"es", // Spanish
"es-ES", // Spanish (Spain)
"companies", // Company names
"softwareTerms", // Programming terms
"misc" // Miscellaneous terms
],
// Words to always consider incorrect
"flagWords": [
"irregardless",
"alot"
],
// Ignore files and folders
"ignorePaths": [
"node_modules/**",
".git/**",
"public/**",
"dist/**"
],
// File patterns to check
"files": [
"**/*.md",
"**/*.mdx",
"**/*.txt"
],
// Allow acronyms that are uppercase
"allowCompoundWords": true,
// Maximum number of problems to report
"maxNumberOfProblems": 1000,
// Maximum word length
"maxDuplicateProblems": 100,
// Minimum word length to check
"minWordLength": 3,
// No more language-specific overrides since we want mixed language support
"languageSettings": [
{
"languageId": "*",
"locale": "*",
"allowCompoundWords": true,
"enabledLanguageIds": ["en", "es"]
}
]
}
Running spell check locally and overruling the merge checks
Local check: For the Impatient Developers
Sometimes you just want to check things quickly without going through the whole merge process. Here’s how:
# Check a single file
npx cspell path/to/your/file.md
# Check multiple specific files
npx cspell file1.md file2.md
# Check all markdown files in a directory
npx cspell "**/*.md"
To check now
# Run textlint
npx textlint your-file.md
# Run proselint
proselint --json your-file.md
proselint your-file.md
# Run both
npx textlint your-file.md && proselint your-file.md
Overruling the merge checks: The Nuclear Option
Sometimes you need to merge something urgently, even with typos. Like when it’s Friday at 4:55 PM and you really want to start your weekend. (I’m not judging, we’ve all been there!) Here’s how to do it:
# --no-verify flag skips all pre-merge hooks
git merge --no-verify branch-name
Remember, with great power comes great responsibility. Use this like you’d use sudo - only when you really need to!
Pro Tips from a Reformed Typo Offender
- Add your technical terms to cspell.json immediately - trust me, you don’t want to fix “cybersecurity” 50 times
- Run checks locally before pushing - it’s faster than waiting for GitHub Actions to tell you about that typo you already knew about
- Don’t blindly trust the grammar checker - sometimes “incorrect” grammar is more readable
- Keep your custom word list somewhere safe - rebuilding it after a fresh clone isn’t fun
¡Y eso es todo, mis amigos!
Remember, perfect grammar won’t fix broken code, but it might make your readers focus on what you’re actually trying to say. And isn’t that the whole point of writing?
Happy coding, y que tus pull requests sean siempre aprobados!1 🚀
P.S. If you found any typos in this post… well, clearly I need to update my cspell.json
! 😉
-
And let your pull requests be always approved ↩︎