Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
fix(html): resolve 34 HTML validation errors across 28 blog posts and…
… remove continue-on-error silent-failure trap

- Escape unescaped < in shell redirects (< seed.sql, << EOF) inside <pre><code> blocks
- Fix reversed closing tags (</pre></code> -> </code></pre>)
- Fix mismatched list closers (</ol> -> </ul>)
- Fix unclosed divs, stray </main>, duplicate article-waitlist wrappers
- Move <style> from body into <head> in new-cli-features-may-18-2026.html
- Fix unclosed <span class=cmd> nesting in clean-up-react-dead-code.html
- Remove continue-on-error: true from html-validate job (silent-failure trap)
- 32 tests pass
  • Loading branch information
Coding-Dev-Tools committed Aug 16, 2026
commit 5bcf0afb2c208f0639ee167b45eabda5e266da9a
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ jobs:
html-validate:
name: HTML Validation
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Validate HTML
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}")
6 changes: 3 additions & 3 deletions blog/api-key-management-from-terminal.html
Original file line number Diff line number Diff line change
Expand Up @@ -232,14 +232,14 @@ <h2>7. Export for CI/CD</h2>

<p>Other export formats:</p>

<div class="cmd-block"># Export as dotenv file
<div class="cmd-block"><pre><code># Export as dotenv file
$ apiauth export --format dotenv --output .env.prod

# Export as JSON for deployment tools
$ apiauth export --format json

# Export as shell exports for Docker
$ apiauth export --format shell</code></pre>
$ apiauth export --format shell</code></pre></div>

<p>For GitHub Actions, add a step to your workflow:</p>

Expand Down Expand Up @@ -295,7 +295,7 @@ <h2>Real-World Scenario: Compromised Key Response</h2>
<li>Ran <code>apiauth generate api-key --service api-gateway</code> — a new key was created</li>
<li>Ran <code>apiauth export --format github-actions</code> — the new key was exported to update CI secrets</li>
<li>Verified with <code>apiauth list</code> that the revoked key still appears in the audit trail for compliance</li>
</ol>
</ul>
Comment thread
Coding-Dev-Tools marked this conversation as resolved.
Outdated
<p>
Total time: under 60 seconds. No frantic Slack messages, no searching through .env files, no wondering if the key was rotated everywhere.
</p>
Expand Down
4 changes: 2 additions & 2 deletions blog/apiauth-verify-api-keys-runtime-import-revocation.html
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ <h2>How Verify Works Under the Hood</h2>
<li>It compares the hash against every stored key entry.</li>
<li>If a match is found, it checks the <code>revoked</code> flag and the <code>expires_at</code> timestamp.</li>
<li>It returns the status: valid, revoked, expired, or not found.</li>
</ol>
</ul>

<div class="callout green">
<p><strong>Zero plaintext storage.</strong> The keystore never stores the raw key value. Verification is always hash-based — the same pattern password systems have used for decades, now applied to API keys.</p>
Expand Down Expand Up @@ -349,7 +349,7 @@ <h4>Pattern 4: Batch Import + Verify for Key Migration</h4>
else
echo " ✗ $name import failed (status: $STATUS)"
fi
done < legacy_keys.env</code></pre>
done &lt; legacy_keys.env</code></pre>
</div>

<h2>The Verify vs. Lookup Decision</h2>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,7 @@ <h3>From dotenv to APIAuth</h3>
--name "$name" \
--service "migrated-from-env" \
--expiry-days 365
done < .env.production
done &lt; .env.production

# Verify everything imported
apiauth stats
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ <h2>CI/CD Integration: GitHub Actions</h2>
<li>GitHub marks the PR as "Checks failed"</li>
<li>The <code>migrate</code> step generates <code>MIGRATION.md</code></li>
<li>The migration guide is uploaded as an artifact for the PR author</li>
</ol>
</ul>

<div class="callout orange">
<p><strong>Intentional breaking changes are sometimes necessary.</strong> When you <em>do</em> need a breaking change (v1 to v2 upgrade), generate the migration guide and attach it to your PR description. Reviewers can verify the migration path instead of hunting through the diff. The CI gate ensures every breaking change is documented &mdash; it doesn't prevent them.</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ <h3>What makes it different</h3>
<li><strong>Migration guide generation.</strong> When you bump your API from v1 to v2, consumers need to know what changed and how to adapt. API Contract Guardian auto-generates markdown migration guides from the spec diff — breaking changes, action items, and per-change severity. No other tool in this comparison does this.</li>
<li><strong>CI-first design.</strong> The <code>check</code> command exits with a non-zero code when breaking changes are detected. It integrates with GitHub Actions, GitLab CI, and any CI system in one line. Git branch comparison is built-in, not an afterthought.</li>
<li><strong>Per-change severity.</strong> Not every breaking change is equally severe. Removing an endpoint is critical; adding a required property to an optional schema is a warning. API Contract Guardian lets you configure severity per change category, so your CI pipeline can distinguish "block the merge" from "warn and allow."</li>
</ol>
</ul>

<h3>Breaking changes detected</h3>

Expand Down
2 changes: 1 addition & 1 deletion blog/autonomous-ai-experiment.html
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ <h3>The Development Cycle</h3>
<li><strong>Engineer</strong> picks up a coding task, writes code, runs tests, and pushes</li>
<li><strong>Researcher</strong> validates the approach, checks competitors, suggests improvements</li>
<li><strong>Marketer</strong> documents features, writes tutorials, updates the landing page</li>
</ol>
</ul>

<p>The CEO prioritized based on a product roadmap, escalating stalled issues and reassigning as needed. No human intervened in any code decision.</p>

Expand Down
6 changes: 1 addition & 5 deletions blog/before-you-deploy-config-drift-and-cost.html
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,7 @@ <h2>Next Steps</h2>
<li><a href="../start-here.html">Browse all 11 tools</a> in the DevForge suite</li>
</ul>

</main>

<div class="article-waitlist"><div class="article-waitlist">
<div class="article-waitlist">
Comment thread
Coding-Dev-Tools marked this conversation as resolved.
<h3>Get Notified About New Tutorials</h3>
<p>DevOps guides, release notes, and product updates — no spam, unsubscribe anytime.</p>
<div class="form-row">
Expand All @@ -193,8 +191,6 @@ <h3>Get Notified About New Tutorials</h3>
</div>
</div>

</main>

<footer>
<span>© 2026 DevForge. Built autonomously.</span>
<a href="https://github.com/Coding-Dev-Tools">GitHub</a>
Expand Down
4 changes: 2 additions & 2 deletions blog/ci-cd-python-cli-tools-guide.html
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ <h2>Putting It All Together</h2>
<li><strong>Config drift check</strong> — <a href="https://github.com/Coding-Dev-Tools/configdrift">configdrift diff</a> validates configs</li>
<li><strong>Deployment diff preview</strong> — <a href="https://github.com/Coding-Dev-Tools/deploydiff">deploydiff</a> estimates cost impact</li>
<li><strong>Tag triggers PyPI publish</strong> — zero-touch release</li>
</ol>
</ul>

<p>Every tool — from <a href="https://github.com/Coding-Dev-Tools/json2sql">json2sql</a> to <a href="https://github.com/Coding-Dev-Tools/schemaforge">SchemaForge</a> to <a href="https://github.com/Coding-Dev-Tools/click-to-mcp">click-to-mcp</a> — follows this exact pipeline. It's the same CI whether you're building a small utility or a complex multi-format converter.</p>

Expand All @@ -241,7 +241,7 @@ <h2>Quick Wins Checklist</h2>
<li><input type="checkbox" disabled> Add <a href="https://github.com/Coding-Dev-Tools/configdrift">config drift detection</a> if your CLI uses config files</li>
<li><input type="checkbox" disabled> Set up automated PyPI publishing on release tags</li>
<li><input type="checkbox" disabled> Add version bump automation with conventional commits</li>
</ol>
</ul>

<p>The key insight: <strong>invest in CI early</strong>. Every minute spent on pipeline setup saves hours of debugging "works on my machine" issues later. Our 11-tool suite runs 4,000+ CI checks per week — and we haven't had a single undetected regression since launch.</p>

Expand Down
4 changes: 2 additions & 2 deletions blog/clean-up-react-dead-code.html
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ <h2>What We'll Build</h2>
<li>Interpreting the output (what's real vs. false positives)</li>
<li>Removing the dead code safely</li>
<li>Setting up DeadCode in CI to prevent backsliding</li>
</ol>
</ul>

<hr>

Expand Down Expand Up @@ -214,7 +214,7 @@ <h3>Batch removal with verification</h3>
<pre><code><span class="cmd">deadcode scan -p</span> . <span class="flag">--exclude</span> node_modules,.next <span class="flag">--json</span> > deadcode-results.json

<span class="comment"># Then process with jq or a script to generate delete commands</span>
<span class="cmd">cat deadcode-results.json | jq -r '.[] | select(.severity=="high") | .file' | xargs rm</code></pre>
<span class="cmd">cat deadcode-results.json | jq -r '.[] | select(.severity=="high") | .file' | xargs rm</span></code></pre>

<div class="tip">
<strong>Tip:</strong> The <code>--allow-list</code> flag lets you suppress findings you've consciously decided to keep (e.g., a publicly exported utility you plan to deprecate over two releases). Run with <code>--generate-allow-list</code> to create an initial baseline, then review and trim it.
Expand Down
1 change: 0 additions & 1 deletion blog/click-to-mcp-three-distribution-channels.html
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,6 @@ <h3>Get DevForge Updates</h3>
</div>
<div class="form-msg" id="wl-msg">&#10003; You're on the list.</div>
</form>
</div>

<p style="text-align:center;font-size:0.85rem;color:#666;">
Star us on <a href="https://github.com/Coding-Dev-Tools" style="color:#6366f1;">GitHub</a> &middot;
Expand Down
4 changes: 2 additions & 2 deletions blog/datamorph-validate-data-schema-ci-pipeline.html
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,13 @@ <h3>Two Validation Modes</h3>
<pre><code>datamorph validate data.csv
# Checks: file is readable, format is detected, columns are consistent
# No schema file needed — uses inferred schema from the data itself
# Good for: "is this file even parseable?"</pre></code>
# Good for: "is this file even parseable?"</code></pre>

<p><strong>With a schema file</strong> (schema validation):</p>
<pre><code>datamorph validate data.csv --schema expected-schema.json
# Checks: all expected fields present, types match, no unexpected fields (in strict mode)
# Requires a schema file generated by `datamorph schema`
# Good for: "does this file still have the shape we expect?"</pre></code>
# Good for: "does this file still have the shape we expect?"</code></pre>

<h3>Strict Mode: Catch Type Mismatches</h3>

Expand Down
Loading
Loading