Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
6680a43
docs: update blog posts and API key management articles
Coding-Dev-Tools Aug 9, 2026
133c52a
cowork-bot: ci: SHA-pin all actions and remove silent-failure trap on…
cowork-bot Aug 10, 2026
c26a315
fix: replace broken bare pip install commands with git+https:// paths…
Coding-Dev-Tools Aug 10, 2026
4de801f
fix(ci): install pytest explicitly since editable install fails on th…
Coding-Dev-Tools Aug 15, 2026
cb9eef5
fix(html): resolve mojibake encoding and structural validation errors
Coding-Dev-Tools Aug 16, 2026
f44e95a
fix(html): URL-encode SVG favicons across 58 files and fix structural…
Coding-Dev-Tools Aug 16, 2026
8e927d4
fix(html): comprehensive SVG favicon encoding and structural dedup
Coding-Dev-Tools Aug 16, 2026
85a34ee
fix(html): close unclosed divs in docs.html and about.html, remove st…
Coding-Dev-Tools Aug 16, 2026
3ff9490
fix(ci): correct html5validator-action input and fix 6 blog HTML stru…
Coding-Dev-Tools Aug 16, 2026
5bcf0af
fix(html): resolve 34 HTML validation errors across 28 blog posts and…
Coding-Dev-Tools Aug 16, 2026
6d06c93
fix: close unclosed <ol> tags in blog posts - Fix </ul> mismatched cl…
Coding-Dev-Tools Aug 18, 2026
cb54dfa
fix: close remaining unclosed <ol> tags in blog posts
Coding-Dev-Tools Aug 18, 2026
19384d2
fix: close unclosed <ol> tags in 12 blog posts
Coding-Dev-Tools Aug 18, 2026
2326ae3
fix: close unclosed <main> tag in before-you-deploy blog post
Coding-Dev-Tools Aug 18, 2026
be3176e
Merge branch 'main' into cowork/blog-apiauth-articles-20260809
Coding-Dev-Tools Oct 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 21 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +11,44 @@ jobs:
name: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Basic sanity check
run: echo "Repository structure OK"
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.12'
- name: Install dependencies
run: pip install pytest
- name: Run test suite
run: python -m pytest tests/ -q

linkcheck:
name: Link Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.12'
- name: Install dependencies
run: pip install -e . || true
run: pip install -e . || pip install requests
- name: Run link checker
run: python .hermes/linkcheck.py --exit-code || true
run: python .hermes/linkcheck.py --exit-code --verbose .

html-validate:
name: HTML Validation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Validate HTML
uses: Cyb3r-Jak3/html5validator-action@v7.2.0
uses: Cyb3r-Jak3/html5validator-action@6f62a310ddc3c79a97ec2ba0f85253a5ea356688 # v7.2.0
with:
root: .
ignore: "ja,verbose"
continue-on-error: true
blacklist: "ja"

deploy:
name: Deploy to GitHub Pages
needs: [test, linkcheck, html-validate]
needs: [test, linkcheck]
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
Expand All @@ -53,13 +58,13 @@ jobs:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Pages
uses: actions/configure-pages@v4
uses: actions/configure-pages@1f0c5cde4bc74cd7e1254d0cb4de8d49e9068c7d # v4.0.0
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1
with:
path: .
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
2 changes: 1 addition & 1 deletion 404.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<meta name="description" content="The page you're looking for doesn't exist. Browse our CLI tools, blog, or documentation.">
<meta name="robots" content="noindex">
<link rel="canonical" href="https://coding-dev-tools.github.io/devforge/404.html">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>?</text></svg>">
<link rel="icon" href="data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%20100%20100%27%3E%3Ctext%20y%3D%27.9em%27%20font-size%3D%2790%27%3E%3F%3C%2Ftext%3E%3C%2Fsvg%3E">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
Expand Down
54 changes: 54 additions & 0 deletions _archive/html-fix-20260817/_fix_html.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env python3
"""Fix systematic HTML validation errors in devforge blog posts."""
import os
import re
from pathlib import Path

BLOG_DIR = Path("blog")
fixed_files = []

def fix_file(path: Path) -> bool:
content = path.read_text(encoding="utf-8")
original = content

# Fix 1: Reversed closing tags </pre></code> -> </code></pre>
content = content.replace("</pre></code>", "</code></pre>")

# Fix 2: Unescaped < inside <code> blocks (but not HTML tags)
# Match < followed by space or common shell operators inside code contexts
# Only escape < that are NOT part of HTML tags (no / or letter immediately after)
def escape_lt_in_code(match):
inner = match.group(1)
# Escape bare < that aren't already &lt; and aren't HTML tags
inner = re.sub(r'<(?!\s*/?\s*[a-zA-Z]|&lt;|\s)', '&lt;', inner)
return f"<code>{inner}</code>"

content = re.sub(r'<code>(.*?)</code>', escape_lt_in_code, content, flags=re.DOTALL)

# Fix 3: Stray </ol> that should be </ul> (when preceded by <ul>)
# Look for <ul>...<li>...</li>...</ol> pattern
content = re.sub(
r'(<ul[^>]*>.*?</li>\s*)</ol>',
r'\1</ul>',
content,
flags=re.DOTALL
)

# Fix 4: Missing </div> before </main> - add closing div if unclosed
# This is tricky; we'll handle specific known files

if content != original:
path.write_text(content, encoding="utf-8")
return True
return False

# Process all HTML files in blog/
count = 0
for html_file in sorted(BLOG_DIR.glob("*.html")):
if fix_file(html_file):
fixed_files.append(html_file.name)
count += 1

print(f"Fixed {count} files:")
for f in fixed_files:
print(f" - {f}")
67 changes: 67 additions & 0 deletions _archive/html-fix-20260817/_fix_html_redirects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
"""Fix remaining unescaped < in code/pre blocks and structural issues."""
import re
from pathlib import Path

BLOG_DIR = Path("blog")
fixed_files = []

def fix_file(path: Path) -> bool:
content = path.read_text(encoding="utf-8")
original = content
name = path.name

# Fix: Inside <pre><code>...</code></pre>, escape < that are followed by
# space (shell redirects like < seed.sql, < legacy_keys.env)
# but preserve < that start HTML tags (<span, </code, etc.)
def escape_shell_redirects_in_pre(match):
inner = match.group(1)
# Escape < followed by space (shell redirect pattern)
inner = re.sub(r'< ', '&lt; ', inner)
# Escape < followed by digit (less-than comparison like < 10)
inner = re.sub(r'<(\d)', r'&lt;\1', inner)
# Escape << (heredoc) that isn't already escaped
inner = inner.replace('<<', '&lt;&lt;')
# Fix double-escaping from above
inner = inner.replace('&lt;&lt;&lt;', '&lt;&lt;')
return f'<pre><code>{inner}</code></pre>'

content = re.sub(r'<pre><code>(.*?)</code></pre>', escape_shell_redirects_in_pre, content, flags=re.DOTALL)

# Fix: Inside <div class="cmd-block">, escape < followed by space
def escape_in_cmd_block(match):
inner = match.group(1)
inner = re.sub(r'< ', '&lt; ', inner)
return f'<div class="cmd-block">{inner}</div>'
content = re.sub(r'<div class="cmd-block">(.*?)</div>', escape_in_cmd_block, content, flags=re.DOTALL)

# Fix: deadcode-fail-ci-on-dead-code.html line 353 - GitHub Actions expression
# < steps.threshold.outputs.threshold should be &lt; in HTML context
if name == "deadcode-fail-ci-on-dead-code.html":
content = content.replace(
'if: steps.scan.outputs.count < steps.threshold.outputs.threshold - 10',
'if: steps.scan.outputs.count &lt; steps.threshold.outputs.threshold - 10'
)

# Fix: click-to-mcp-three-distribution-channels.html - stray </div>
if name == "click-to-mcp-three-distribution-channels.html":
# Check around line 245 for the stray </div>
lines = content.split('\n')
# The issue is likely an extra </div> that doesn't match any opening
# Let's look at the structure more carefully - skip for now, it may be
# a false positive from the validator after our other fixes

if content != original:
path.write_text(content, encoding="utf-8")
return True
return False

count = 0
for html_file in sorted(BLOG_DIR.glob("*.html")):
if fix_file(html_file):
fixed_files.append(html_file.name)
count += 1

print(f"Fixed {count} files:")
for f in fixed_files:
print(f" - {f}")
101 changes: 101 additions & 0 deletions _archive/html-fix-20260817/_fix_html_structural.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/usr/bin/env python3
"""Fix remaining structural HTML validation errors in devforge blog posts."""
import re
from pathlib import Path

BLOG_DIR = Path("blog")
fixed_files = []

def fix_file(path: Path) -> bool:
content = path.read_text(encoding="utf-8")
original = content
name = path.name

# Fix 1: Unescaped < followed by space inside <pre><code> blocks
# Pattern: <code>...< ...</code> where < is not part of an HTML tag or entity
def escape_lt_in_pre_code(match):
inner = match.group(1)
# Escape < that are followed by space (shell redirection like `< seed.sql`)
# but NOT < that start HTML tags or entities
inner = re.sub(r'<(?!\s*/?\s*[a-zA-Z/]|&lt;|&gt;|&amp;|#)', '&lt;', inner)
return f'<pre><code>{inner}</code></pre>'
content = re.sub(r'<pre><code>(.*?)</code></pre>', escape_lt_in_pre_code, content, flags=re.DOTALL)

# Fix 2: api-key-management-from-terminal.html - unclosed div.cmd-block with stray </code></pre>
if name == "api-key-management-from-terminal.html":
# The cmd-block div has raw text without <pre><code> wrapper but ends with </code></pre>
old = ' <div class="cmd-block"># Export as dotenv file\n$ apiauth export --format dotenv --output .env.prod\n\n# Export as JSON for deployment tools\n$ apiauth export --format json\n\n# Export as shell exports for Docker\n$ apiauth export --format shell</code></pre>'
new = ' <div class="cmd-block"><pre><code># Export as dotenv file\n$ apiauth export --format dotenv --output .env.prod\n\n# Export as JSON for deployment tools\n$ apiauth export --format json\n\n# Export as shell exports for Docker\n$ apiauth export --format shell</code></pre></div>'
content = content.replace(old, new)

# Fix 3: before-you-deploy-config-drift-and-cost.html - duplicate </main> and unclosed div
if name == "before-you-deploy-config-drift-and-cost.html":
# Remove the stray second </main> and fix the duplicate article-waitlist div
old = '</main>\n\n <div class="article-waitlist"><div class="article-waitlist">'
new = ' <div class="article-waitlist">'
content = content.replace(old, new)
# Remove the extra </main> after the waitlist div
old2 = ' </div>\n\n</main>\n\n<footer>'
new2 = ' </div>\n\n<footer>'
content = content.replace(old2, new2)

# Fix 4: click-to-mcp-three-distribution-channels.html - stray </div>
if name == "click-to-mcp-three-distribution-channels.html":
# Line 245 has stray </div> - check context
pass # Will verify after other fixes

# Fix 5: new-cli-features-may-18-2026.html - <style> in body
if name == "new-cli-features-may-18-2026.html":
# Move <style> block into <head> or wrap in proper location
# For now, move it before </head> if possible, otherwise leave as-is
# since this is a cosmetic issue and the page renders fine
style_match = re.search(r'(<style>.*?</style>)', content, re.DOTALL)
head_close = content.find('</head>')
if style_match and head_close > 0:
style_block = style_match.group(1)
# Only move if style is currently after </head>
if style_match.start() > head_close:
content = content.replace(style_block, '', 1)
content = content.replace('</head>', style_block + '\n</head>', 1)

# Fix 6: clean-up-react-dead-code.html - code nesting with spans
if name == "clean-up-react-dead-code.html":
# The issue is </code> closing a span-nested code improperly
# Line 217: <span class="cmd">cat ... | xargs rm</code></pre>
# Should be: <span class="cmd">cat ... | xargs rm</span></code></pre>
old = '<span class="cmd">cat deadcode-results.json | jq -r \'[] | select(.severity=="high") | .file\' | xargs rm</code></pre>'
new = '<span class="cmd">cat deadcode-results.json | jq -r \'[] | select(.severity=="high") | .file\' | xargs rm</span></code></pre>'
content = content.replace(old, new)
# Also try without escaped quotes
old2 = "<span class=\"cmd\">cat deadcode-results.json | jq -r '.[] | select(.severity==\"high\") | .file' | xargs rm</code></pre>"
new2 = "<span class=\"cmd\">cat deadcode-results.json | jq -r '.[] | select(.severity==\"high\") | .file' | xargs rm</span></code></pre>"
content = content.replace(old2, new2)

# Fix 7: deploydiff-rollback-commands - <<EOF heredoc escaping
if name == "deploydiff-rollback-commands-terraform-cloudformation.html":
# Already fixed < to &lt; but need to check <<EOF pattern
# The line should be: echo "ROLLBACK_COMMANDS&lt;&lt;EOF"
content = content.replace('&lt;<EOF', '&lt;&lt;EOF')
# Also fix any remaining </code> nesting issues around line 308
# Check for reversed tags
content = content.replace('</pre></code>', '</code></pre>')

# Fix 8: datamorph-validate-data-schema-ci-pipeline.html - already fixed by script
# Verify the </code></pre> order is correct
if name == "datamorph-validate-data-schema-ci-pipeline.html":
content = content.replace('</pre></code>', '</code></pre>')

if content != original:
path.write_text(content, encoding="utf-8")
return True
return False

count = 0
for html_file in sorted(BLOG_DIR.glob("*.html")):
if fix_file(html_file):
fixed_files.append(html_file.name)
count += 1

print(f"Fixed {count} files:")
for f in fixed_files:
print(f" - {f}")
11 changes: 1 addition & 10 deletions about.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"sameAs": ["https://github.com/Coding-Dev-Tools"]
}
</script>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚒</text></svg>">
<link rel="icon" href="data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%20100%20100%27%3E%3Ctext%20y%3D%27.9em%27%20font-size%3D%2790%27%3E%E2%9A%92%3C%2Ftext%3E%3C%2Fsvg%3E">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
Expand Down Expand Up @@ -133,15 +133,6 @@
<a href="https://github.com/Coding-Dev-Tools">
</div>

<a href="index.html" class="logo">Revenue<span>Holdings</span></a>
<div class="links">
<a href="quickstart.html">Get Started</a>
<a href="docs.html">Docs</a>
<a href="pricing.html">Pricing</a>
<a href="alternatives.html">Alternatives</a>
<a href="releases.html">Changelog</a>
<a href="blog.html">Blog</a>
<a href="about.html" class="active">About</a>
</div>
</nav>

Expand Down
2 changes: 1 addition & 1 deletion alternatives.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<link rel="canonical" href="https://coding-dev-tools.github.io/devforge/alternatives.html">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚒</text></svg>">
<link rel="icon" href="data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%20100%20100%27%3E%3Ctext%20y%3D%27.9em%27%20font-size%3D%2790%27%3E%E2%9A%92%3C%2Ftext%3E%3C%2Fsvg%3E">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<script type="application/ld+json">
{
Expand Down
5 changes: 1 addition & 4 deletions blog.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,11 @@

<meta name="description" content="Updates, release notes, and stories from DevForge — Open-source developer tools. CI/CD guides, API contract testing, DevOps tips, tool comparisons, technical deep-dives, and hands-on tutorials — 66 articles and growing.">

<meta name="description" content="Updates, release notes, and stories from DevForge — Open-source developer tools. CI/CD guides, API contract testing, DevOps tips, tool comparisons, technical deep-dives, and hands-on tutorials — 66 articles and growing.">

<meta name="keywords" content="developer tools, Open-source, CLI tools, DevForge, CI/CD, DevOps, API contracts, infrastructure diff, config drift, release notes, tutorial, tool comparison, OpenAPI diff, white-label AI agent, MCP server directory">
<meta property="og:title" content="DevForge — Blog">
<meta property="og:description" content="Product updates, CI/CD guides, DevOps deep-dives, and tool comparisons from Open-source developer tools. 66 articles: API contract testing, DataMorph batch conversion, API key mgmt, env sync, API mock server & advanced patterns, infra cost, JSON to SQL, config drift, MCP launch, dead code, Envault guide, OpenAPI comparison, CI safety net, production guide, schema conversion, and more.">

<title>Blog — DevForge</title>
<meta name="description" content="Updates, release notes, and stories from DevForge — the autonomous AI devtools company. CI/CD guides, API contract testing, DevOps tips, tool comparisons, and hands-on tutorials.">
<meta name="keywords" content="developer tools, autonomous AI, CLI tools, DevForge, CI/CD, DevOps, API contracts, infrastructure diff, config drift, release notes, tutorial, tool comparison, OpenAPI diff">
<meta property="og:title" content="DevForge — Blog">
<meta property="og:description" content="Product updates, CI/CD guides, DevOps deep-dives, and tool comparisons from the autonomous AI devtools company. 5 articles: tutorials, guides, comparisons, and announcements.">
Expand All @@ -33,7 +30,7 @@
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<link rel="canonical" href="https://coding-dev-tools.github.io/devforge/blog.html">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>âš’</text></svg>">
<link rel="icon" href="data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%20100%20100%27%3E%3Ctext%20y%3D%27.9em%27%20font-size%3D%2790%27%3E%C3%A2%C5%A1%E2%80%99%3C%2Ftext%3E%3C%2Fsvg%3E">
<link rel="alternate" type="application/atom+xml" href="feed.xml" title="DevForge Blog">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
Expand Down
Loading
Loading