{"schemaVersion":"1.0","type":"TechArticle","types":["Article","TechArticle"],"slug":"gitops-for-technical-writers-continuous-publishing-with-the-zyvop-cli-and-github-actions-b8f7d","url":"https://zyvop.com/gitops-for-technical-writers-continuous-publishing-with-the-zyvop-cli-and-github-actions-b8f7d","title":"GitOps for Technical Writers: Continuous Publishing with the ZyVop CLI and GitHub Actions","subtitle":"How to treat technical articles like production software—versioned in Git, reviewed in pull requests, and continuously deployed across Dev.to, Hashnode, Medium, and Bluesky on every git push.","tldr":"Learn how to build an automated continuous deployment pipeline for technical writing using GitOps, the ZyVop CLI, and GitHub Actions.","keywords":["gitops","GitHub Actions","cicd","automation","DevOps"],"entities":["Sanju Singh","gitops","GitHub Actions","cicd","automation","DevOps","ZyVOP"],"keyTakeaways":["Your content stays in your hands: You own the raw Markdown files, version history, and branch reviews in your repository.","You write where you are productive: No more pasting between browser tabs. Stay in your terminal, IDE, and Git workflow.","Continuous syndication happens automatically: One git push simultaneously publishes your post across Dev.to, Hashnode, Medium, and Bluesky, while maintaining your canonical SEO ranking."],"headings":["1. System Architecture: The End-to-End Pipeline","Key Architectural Tenets:","2. The Anatomy of a Headless Markdown Post","Frontmatter Schema Reference","3. Inside the ZyVop CLI: AST Parsing &amp; Token Authentication","How the CLI Parses and Dispatches","The AST Code Fence Protection Challenge","4. Setting Up the Production GitHub Actions Workflow","File: .github/workflows/publish.yml","5. Securing the Pipeline with Personal Access Tokens","Steps to Configure:","6. Engineering Best Practices for Repository Structure","Recommended Repository Layout:","Pre-Merge Quality Gates (Pull Request Workflow)","7. Summary &amp; Getting Started"],"outboundLinks":[],"contentText":"For software engineers, writing code and writing technical articles should feel like the same discipline. Both require structural hierarchy, precise syntax, logical proofs, and iterative refinement. Yet the developer experience of publishing an article has historically diverged from our software development lifecycle. Traditional Publishing: Local Editor (Markdown) ──&gt; Copy/Paste ──&gt; Web CMS Dashboard ──&gt; Manual Formatting ──&gt; Publish ──&gt; Repeat for 4 Platforms GitOps Publishing: Local Editor (Markdown) ──&gt; git commit &amp; push ──&gt; GitHub Actions (CI/CD) ──&gt; ZyVop API ──&gt; Automated Multi-Platform Fanout When writing in a proprietary browser-based CMS, we surrender the tools we rely on daily: No Real Version Control: Revisions are stored in proprietary database snapshots rather than immutable Git commits with atomic diffs and clear commit messages. No Peer Review Infrastructure: Collaboration happens via clunky comment sidebars instead of standard GitHub Pull Requests, branch previews, and automated linting. Context Switching: We are forced out of our configured local environments (Neovim, VS Code, Helix) into browser textareas with fragile clipboard handling. Manual Multi-Platform Duplication: Distributing an article to Dev.to, Hashnode, Medium, and Bluesky means manually copy-pasting Markdown, re-uploading cover images, re-tagging, and hoping canonical URLs were configured correctly to avoid search engine penalties. To solve this, we applied the principles of GitOps and Continuous Delivery to technical blogging. By combining the ZyVop CLI, headless REST/GraphQL APIs, and GitHub Actions, you can manage your blog as an open-source repository and automate the entire lifecycle from local Markdown file to globally distributed, SEO-canonicalized publication. 1. System Architecture: The End-to-End Pipeline At its core, GitOps publishing treats a directory of Markdown files as the single source of truth for your published technical content. A git push to your repository's main branch acts as the deployment trigger. Here is the architectural lifecycle of an article moving through the pipeline: sequenceDiagram autonumber actor Dev as Developer (Local IDE) participant Git as GitHub Repository participant CI as GitHub Actions Runner participant API as ZyVop Public REST API participant DB as PostgreSQL Database participant Queue as BullMQ Job Queue participant Worker as Background Workers participant Ext as Dev.to / Hashnode / Medium / Bluesky Dev-&gt;&gt;Git: git push origin main (posts/microservices.md) Git-&gt;&gt;CI: Trigger workflow on push (paths: 'posts/**.md') CI-&gt;&gt;CI: Checkout repository &amp; detect modified files CI-&gt;&gt;API: POST /api/v1/articles (Bearer Token + Markdown Payload) API-&gt;&gt;API: Validate Token &amp; Parse YAML Frontmatter AST API-&gt;&gt;DB: Upsert Post Entity &amp; Commit Canonical Metadata API-&gt;&gt;Queue: Enqueue syndication job (payload: postId) API--&gt;&gt;CI: 201 Created (post slug &amp; live URL) CI--&gt;&gt;Dev: GitHub Workflow Succeeded Queue-&gt;&gt;Worker: Consume syndication task par Fan-out Distribution Worker-&gt;&gt;Ext: Dev.to API (with canonical_url) Worker-&gt;&gt;Ext: Hashnode GraphQL (with isRepublished) Worker-&gt;&gt;Ext: Medium API (with canonicalUrl) Worker-&gt;&gt;Ext: Bluesky AT Protocol (with RichText facets) endKey Architectural Tenets: Decoupled Synchronous Validation: The GitHub Actions runner communicates with the fast REST endpoint, which validates frontmatter, stores the post in PostgreSQL, and returns within ~250ms. Asynchronous Multi-Platform Fan-Out: External platform APIs (which often experience variable latency or strict rate limits) are handled by dedicated background BullMQ workers. This guarantees that slow third-party networks never fail or block your CI/CD build. Deterministic Canonical SEO: The root post URL is automatically computed and injected into the metadata headers of all syndication targets, ensuring Google and Bing attribute domain authority to your primary source. 2. The Anatomy of a Headless Markdown Post In a GitOps workflow, your Markdown files must declare both their content and their deployment configuration. We use standard YAML frontmatter parsed at the AST level via gray-matter. Here is an example production post configuration (posts/distributed-queues.md): --- title: \"The Anatomy of a Resilient Distributed Task Queue\" subtitle: \"Deep dive into NestJS, Fastify, BullMQ, and Redis worker processes\" excerpt: \"Learn how to architect high-throughput asynchronous job pipelines that survive network partitions and node crashes.\" category: backend tags: - typescript - architecture - redis - devops canonical_url: https://myblog.com/posts/distributed-queues cover_image: https://assets.myblog.com/covers/task-queue.webp status: PUBLISHED generate_toc: true cross_post: devto: true hashnode: true medium: true bluesky: true --- # Introduction When designing scalable web architectures, separating synchronous request-response cycles from background task execution is essential...Frontmatter Schema Reference Field Type Description title string (Required) The primary headline of the article. subtitle string (Optional) Secondary description or tagline. excerpt string (Optional) Short summary used for RSS feeds, newsletter preheaders, and preview cards. tags string[] Up to 5 category tags (automatically mapped across syndication platforms). canonical_url string (Optional) Custom origin URL if you are syndicating from a personal standalone domain. status PUBLISHED | DRAFT When set to DRAFT, the post is created without triggering public feeds or syndication. generate_toc boolean Automatically calculates heading levels (&lt;h2&gt;, &lt;h3&gt;) and renders a floating Table of Contents. cross_post object Boolean flags dictating which downstream syndication adapters should run. 3. Inside the ZyVop CLI: AST Parsing &amp; Token Authentication The ZyVop CLI was designed with two modes of execution: Interactive Developer Mode: For local terminal testing with real-time spinners (ora), colored diff logs (picocolors), and session validation. Headless CI/CD Mode: For non-interactive runners utilizing Personal Access Tokens (zv_...) passed via environment variables. How the CLI Parses and Dispatches When you execute npx zyvop publish ./posts/my-article.md, the CLI performs the following operations: import fs from \"node:fs\"; import path from \"node:path\"; import matter from \"gray-matter\"; import { marked } from \"marked\"; import { publishArticleRestApi } from \"../api.js\"; export async function publishCommand(filePath, options) { const resolvedPath = path.resolve(process.cwd(), filePath); const rawFile = fs.readFileSync(resolvedPath, \"utf-8\"); // 1. Extract frontmatter and raw Markdown AST const parsed = matter(rawFile); const frontmatter = parsed.data || {}; const content = parsed.content || \"\"; // 2. Resolve authentication credentials const token = process.env.ZYVOP_TOKEN || options.token; const endpoint = options.endpoint || \"https://api.zyvop.com\"; // 3. Dispatch to the Headless REST Endpoint if (token.startsWith(\"zv_\")) { const post = await publishArticleRestApi(rawFile, token, endpoint); console.log(`✅ Live URL: ${post.url}`); return; } }The AST Code Fence Protection Challenge One significant technical hurdle when converting Markdown for multi-platform delivery is nested code fences. If an article includes Markdown tutorials illustrating triple-backtick fences (```), naive Markdown parsers misinterpret closing boundaries. Furthermore, platforms like Dev.to's Forem engine treat nested triple-backticks as Liquid template syntax errors, throwing unhandled exceptions such as \"Unknown tag 'endraw'\". The cross-posting engine dynamically calculates the fence depth: this.turndown.addRule('fencedCodeBlock', { filter: ['pre'], replacement: (_content: any, node: any) =&gt; { const code = node.querySelector ? node.querySelector('code') : null; const text = code ? code.textContent : node.textContent; // Dynamically increase fence length if the content contains triple backticks let fence = '```'; while (text.includes(fence)) { fence += '`'; } return `\\n\\n${fence}${lang}\\n${text}\\n${fence}\\n\\n`; }, });This ensures that your code snippets—no matter how complex or nested—remain syntactically intact across all platforms. 4. Setting Up the Production GitHub Actions Workflow To achieve true GitOps publishing, we don't want to re-publish every single post on every commit. We only want to publish new or modified Markdown files in the current push. Here is the production-ready GitHub Actions workflow. File: .github/workflows/publish.yml name: Continuous Publishing (GitOps) on: push: branches: - main paths: - 'posts/**.md' workflow_dispatch: # Allows manual trigger from the GitHub Actions UI concurrency: group: publishing-${{ github.ref }} cancel-in-progress: false jobs: publish-articles: name: Validate &amp; Publish to ZyVop runs-on: ubuntu-latest steps: - name: 📥 Checkout Repository uses: actions/checkout@v4 with: fetch-depth: 2 # Fetch the previous commit for accurate git diffing - name: ⚙️ Setup Node.js Runtime uses: actions/setup-node@v4 with: node-version: 20 cache: 'npm' - name: 🔍 Detect Changed Markdown Posts id: changed-files run: | # If initial commit or forced push, fallback to all posts if [ \"${{ github.event.before }}\" = \"0000000000000000000000000000000000000000\" ]; then FILES=$(git ls-files 'posts/*.md') else FILES=$(git diff --name-only --diff-filter=ACMR ${{ github.event.before }} ${{ github.sha }} | grep '^posts/.*\\.md$' || true) fi if [ -z \"$FILES\" ]; then echo \"No Markdown files modified.\" echo \"has_changes=false\" &gt;&gt; $GITHUB_OUTPUT else echo \"Files to publish:\" echo \"$FILES\" # Format files into a space-separated list FILES_CLEAN=$(echo \"$FILES\" | tr '\\n' ' ') echo \"files=$FILES_CLEAN\" &gt;&gt; $GITHUB_OUTPUT echo \"has_changes=true\" &gt;&gt; $GITHUB_OUTPUT fi - name: 🚀 Run ZyVop CLI Publisher if: steps.changed-files.outputs.has_changes == 'true' env: ZYVOP_TOKEN: ${{ secrets.ZYVOP_TOKEN }} run: | for file in ${{ steps.changed-files.outputs.files }}; do if [ -f \"$file\" ]; then echo \"──────────────────────────────────────────────\" echo \"📦 Deploying: $file\" npx zyvop publish \"$file\" fi done - name: 📊 Summary Report if: steps.changed-files.outputs.has_changes == 'true' run: | echo \"### 🚀 GitOps Publishing Complete\" &gt;&gt; $GITHUB_STEP_SUMMARY echo \"The following articles were verified and deployed:\" &gt;&gt; $GITHUB_STEP_SUMMARY for file in ${{ steps.changed-files.outputs.files }}; do echo \"- \\`$file\\`\" &gt;&gt; $GITHUB_STEP_SUMMARY done5. Securing the Pipeline with Personal Access Tokens Authentication in CI/CD pipelines requires zero interactive prompts. ZyVop uses cryptographically hashed Developer Personal Access Tokens (zv_live_...). flowchart LR subgraph Local [\"1. Developer Machine\"] PAT[\"Generate Token in Settings&lt;br/&gt;&lt;code&gt;zv_live_9f8c...&lt;/code&gt;\"] end subgraph GH [\"2. GitHub Repository\"] Secret[\"Encrypted Secret&lt;br/&gt;&lt;code&gt;ZYVOP_TOKEN&lt;/code&gt;\"] end subgraph Runner [\"3. GitHub Actions\"] Env[\"Injected Environment&lt;br/&gt;&lt;code&gt;process.env.ZYVOP_TOKEN&lt;/code&gt;\"] CLI[\"ZyVop CLI\"] end subgraph Server [\"4. ZyVop API\"] Auth[\"Validate Scopes &amp; Rate Limits\"] Publish[\"Publish Post\"] end PAT --&gt;|Add to Settings &gt; Secrets| Secret Secret --&gt;|Inject at Runtime| Env Env --&gt; CLI CLI --&gt;|Authorization: Bearer zv_...| Auth Auth --&gt; PublishSteps to Configure: Log in to your Dashboard and navigate to Settings &gt; Developer API. Click Generate New Token, assign a descriptive name (e.g., github-actions-blog), and copy the generated key. In your GitHub repository, navigate to Settings &gt; Secrets and variables &gt; Actions. Click New repository secret, set the name to ZYVOP_TOKEN, and paste the token string. 6. Engineering Best Practices for Repository Structure When managing your publication as code, organizing your directory structure helps maintain readability and simplifies pre-commit validations. Recommended Repository Layout: my-tech-blog/ ├── .github/ │ └── workflows/ │ ├── publish.yml # Automated deployment pipeline │ └── lint.yml # Pre-merge validation (Markdownlint, CSpell) ├── .markdownlint.json # Style consistency rules ├── posts/ │ ├── 2026-08-20-distributed-queues.md │ ├── 2026-08-22-at-protocol-internals.md │ └── drafts/ │ └── upcoming-raft-consensus.md ├── static/ │ └── diagrams/ │ └── queue-architecture.png └── README.md Pre-Merge Quality Gates (Pull Request Workflow) Before an article is merged into main, you can enforce the same quality checks you use on software projects: Markdown Linting (markdownlint-cli2): Ensures heading hierarchies are semantically correct (e.g., single h1, no skipped header levels). Spell Checking (cspell): Catches typographical errors and unknown terminology before publication. Link Validation (lychee): Verifies that all outbound references, documentation links, and image URLs are reachable and return HTTP 200. # .github/workflows/lint.yml name: Content Verification on: pull_request: paths: - 'posts/**.md' jobs: verify: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Check Markdown formatting uses: DavidAnson/markdownlint-cli2-action@v16 with: globs: 'posts/**/*.md' - name: Check spelling uses: streetsidesoftware/cspell-action@v6 with: files: 'posts/**/*.md'7. Summary &amp; Getting Started By shifting technical blogging to a GitOps workflow: Your content stays in your hands: You own the raw Markdown files, version history, and branch reviews in your repository. You write where you are productive: No more pasting between browser tabs. Stay in your terminal, IDE, and Git workflow. Continuous syndication happens automatically: One git push simultaneously publishes your post across Dev.to, Hashnode, Medium, and Bluesky, while maintaining your canonical SEO ranking. To test publishing an article directly from your terminal: # 1. Login to your account npx zyvop login # 2. Test publishing any local Markdown file npx zyvop publish ./posts/my-article.md","contentHash":"sha256:18d0e3f4c2e35122752ceed81394a93d6a11e04b9eafb624ebd340d5e9ec8b20","authorName":"Sanju Singh","authorUrl":"https://zyvop.com/author/sanjay687","authorSameAs":[],"category":null,"tags":["gitops","GitHub Actions","cicd","automation","DevOps"],"audience":"Software engineers and developers building applications with gitops","tone":"Instructional, practical, code-first","readingTimeMinutes":9,"wordCount":1876,"faqs":null,"primaryTopic":"gitops","publishedAt":"2026-08-23T06:41:01.253Z","updatedAt":"2026-08-27T21:30:00.237Z","canonicalUrl":"https://zyvop.com/gitops-for-technical-writers-continuous-publishing-with-the-zyvop-cli-and-github-actions-b8f7d"}