Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
35 changes: 20 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,40 @@ 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
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" target="_blank">GitHub</a>
</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