<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged github-actions</title>
<link>https://coderlegion.com/tag/github-actions</link>
<description>Powered by Question2Answer</description>
<item>
<title>Segurança em CI/CD: a importância de estudar, auditar e aprender com pipelines de bons projetos</title>
<link>https://coderlegion.com/33090/seguranca-em-ci-cd-importancia-de-estudar-auditar-aprender-com-pipelines-de-bons-projetos</link>
<description>&lt;p&gt;Pipelines de CI/CD são frequentemente tratados como simples mecanismos de automação: executam testes, compilam aplicações, geram artefatos e realizam deploys. Entretanto, eles também representam uma das superfícies de ataque mais importantes de um ambiente de desenvolvimento moderno.&lt;/p&gt;
&lt;p&gt;Um pipeline possui acesso a repositórios, tokens, credenciais, registries, ambientes de produção, dependências externas e, em muitos casos, permissões para modificar recursos de infraestrutura. Isso significa que uma falha na configuração de um workflow pode ultrapassar os limites do processo de build e comprometer toda a cadeia de entrega de software.&lt;/p&gt;
&lt;p&gt;Por esse motivo, estudar a segurança de pipelines deve fazer parte da rotina de engenharia, assim como revisar código, analisar dependências e avaliar configurações de infraestrutura.&lt;/p&gt;
&lt;p&gt;Mais importante do que simplesmente executar ferramentas de análise é compreender por que determinada configuração representa um risco, em quais condições ela pode ser explorada e quais mecanismos realmente mitigam esse risco.&lt;/p&gt;
&lt;h3 id=&quot;1-por-que-estudar-pipelines-t-o-importante-&quot;&gt;1. Por que estudar pipelines é tão importante?&lt;/h3&gt;
&lt;p&gt;Um pipeline inseguro pode transformar uma alteração aparentemente simples em um vetor de comprometimento.&lt;/p&gt;
&lt;p&gt;Considere alguns exemplos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Um Pull Request pode introduzir código executado em um runner que possui acesso a credenciais.&lt;/li&gt;
&lt;li&gt;Uma interpolação insegura de contextos do GitHub pode permitir injeção de comandos no shell.&lt;/li&gt;
&lt;li&gt;Uma dependência comprometida pode executar código durante a instalação.&lt;/li&gt;
&lt;li&gt;Um token com permissões excessivas pode permitir alterações não autorizadas no repositório.&lt;/li&gt;
&lt;li&gt;Uma publicação sem mecanismos adequados de autorização pode disponibilizar uma versão não aprovada de um pacote.&lt;/li&gt;
&lt;li&gt;Um workflow mal configurado pode executar código de terceiros com privilégios superiores aos necessários.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Esses cenários demonstram que o pipeline não é apenas uma ferramenta operacional. Ele faz parte da infraestrutura de segurança da organização.&lt;/p&gt;
&lt;p&gt;A segurança precisa considerar todo o ciclo de execução: desde o evento que dispara o workflow até as permissões disponíveis, dependências instaladas, artefatos produzidos e operações realizadas.&lt;/p&gt;
&lt;h3 id=&quot;2-utilize-scanners-mas-n-o-terceirize-o-julgamento-t-cnico&quot;&gt;2. Utilize scanners, mas não terceirize o julgamento técnico&lt;/h3&gt;
&lt;p&gt;Existem ferramentas especializadas em identificar configurações inseguras em workflows.&lt;/p&gt;
&lt;p&gt;Algumas opções relevantes que eu utilizo no dia a dia para scans do pipeline:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;zizmor:&lt;/strong&gt; analisa workflows do GitHub Actions e identifica problemas como template injection, permissões inadequadas, uso inseguro de eventos e instalações de dependências fora de manifests.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Checkov:&lt;/strong&gt; realiza análise estática de configurações de infraestrutura como código e também identifica problemas em configurações de CI/CD.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;actionlint:&lt;/strong&gt; valida a sintaxe, expressões, referências e possíveis problemas estruturais em workflows do GitHub Actions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenSSF Scorecard:&lt;/strong&gt; avalia práticas de segurança de projetos e repositórios, incluindo dependências, permissões, manutenção e configuração de workflows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Trivy:&lt;/strong&gt; além de analisar imagens e dependências, consegue avaliar configurações e arquivos de infraestrutura.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Essas ferramentas são importantes porque automatizam a identificação de padrões conhecidos de risco.&lt;/p&gt;
&lt;p&gt;Entretanto, existe uma diferença fundamental entre encontrar uma configuração potencialmente insegura e demonstrar que ela é explorável.&lt;/p&gt;
&lt;p&gt;Um scanner identifica padrões. O engenheiro precisa compreender o contexto.&lt;/p&gt;
&lt;h4 id=&quot;exemplo-template-injection&quot;&gt;Exemplo: template injection&lt;/h4&gt;
&lt;p&gt;Considere:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Check changelog
  run: scripts/check-changelog-frozen.sh &quot;origin/${{ github.base_ref }}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O scanner pode identificar uma possível injeção de comandos porque o contexto é interpolado diretamente no código executado pelo shell.&lt;/p&gt;
&lt;p&gt;O risco existe porque o GitHub Actions realiza a expansão da expressão antes de executar o script.&lt;/p&gt;
&lt;p&gt;Entretanto, é necessário entender a origem do dado.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;github.base_ref&lt;/code&gt; representa a branch de destino do Pull Request, enquanto &lt;code&gt;github.head_ref&lt;/code&gt; representa a branch de origem.&lt;/p&gt;
&lt;p&gt;Um colaborador externo normalmente controla o nome da própria branch, mas não controla livremente a branch de destino.&lt;/p&gt;
&lt;p&gt;Portanto, embora exista um padrão vulnerável, a explorabilidade depende das permissões e das condições do workflow.&lt;/p&gt;
&lt;p&gt;A correção recomendada é transportar o valor por uma variável de ambiente:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Check changelog
  env:
    BASE_REF: ${{ github.base_ref }}
  run: scripts/check-changelog-frozen.sh &quot;origin/$BASE_REF&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O valor passa a ser tratado como dado, em vez de ser inserido diretamente no código interpretado pelo shell.&lt;/p&gt;
&lt;p&gt;Esse exemplo demonstra por que é necessário estudar os findings: o scanner identifica corretamente o padrão, mas a análise de impacto exige conhecimento sobre os contextos do GitHub Actions.&lt;/p&gt;
&lt;h3 id=&quot;3-aprenda-a-diferenciar-vulnerabilidades-reais-de-findings-parcialmente-mitigados&quot;&gt;3. Aprenda a diferenciar vulnerabilidades reais de findings parcialmente mitigados&lt;/h3&gt;
&lt;p&gt;Nem todo alerta representa uma vulnerabilidade diretamente explorável.&lt;/p&gt;
&lt;p&gt;Um exemplo interessante é a instalação de dependências durante o pipeline.&lt;/p&gt;
&lt;p&gt;Considere:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;env:
  CLAUDE_CODE_VERSION: &quot;2.1.296&quot;
  CODEX_VERSION: &quot;0.162.1&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;E posteriormente:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Install CLIs
  run: |
    npm install --global \
      &quot;@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}&quot; \
      &quot;@openai/codex@${CODEX_VERSION}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Um scanner como o zizmor pode identificar o padrão &lt;code&gt;adhoc-packages&lt;/code&gt;, pois os pacotes são instalados globalmente sem um manifesto e lockfile controlando a instalação.&lt;/p&gt;
&lt;p&gt;Entretanto, existe uma particularidade: as versões principais já estão explicitamente fixadas.&lt;/p&gt;
&lt;p&gt;Isso significa que o workflow não está simplesmente instalando a última versão disponível a cada execução.&lt;/p&gt;
&lt;p&gt;Ainda existe uma fragilidade: a árvore completa de dependências não está necessariamente reproduzível, pois as dependências transitivas não são controladas por um lockfile versionado.&lt;/p&gt;
&lt;p&gt;É importante distinguir:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Versão principal fixada: controla qual versão direta será solicitada.&lt;/li&gt;
&lt;li&gt;Manifesto: declara as dependências do projeto.&lt;/li&gt;
&lt;li&gt;Lockfile: registra a resolução das dependências e suas integridades.&lt;/li&gt;
&lt;li&gt;Instalação reproduzível: utiliza a resolução registrada para reconstruir o ambiente.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A ausência de lockfile continua sendo um finding válido, mas não significa automaticamente que existe uma instalação de versão flutuante ou que um pacote esteja comprometido.&lt;/p&gt;
&lt;p&gt;O objetivo da análise não é eliminar alertas indiscriminadamente, mas determinar quais riscos permanecem e quais controles já existem.&lt;/p&gt;
&lt;h3 id=&quot;4-estude-como-projetos-maduros-resolvem-problemas-de-seguran-a&quot;&gt;4. Estude como projetos maduros resolvem problemas de segurança&lt;/h3&gt;
&lt;p&gt;Uma das formas mais eficientes de evoluir na segurança de pipelines é estudar workflows de projetos open source bem mantidos.&lt;/p&gt;
&lt;p&gt;Em vez de começar sempre do zero, investigue como projetos com grande exposição pública estruturam seus processos de CI/CD.&lt;/p&gt;
&lt;p&gt;Alguns exemplos interessantes para estudo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes:&lt;/strong&gt; organização de workflows, testes, automação e governança de contribuições.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Terraform:&lt;/strong&gt; validação, testes, releases e publicação de artefatos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Trivy:&lt;/strong&gt; análise de segurança, integração de scanners e automação de releases.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenTelemetry:&lt;/strong&gt; testes distribuídos, validação de componentes e publicação de versões.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;actions/checkout:&lt;/strong&gt; implementação e manutenção de GitHub Actions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sigstore:&lt;/strong&gt; assinatura, verificação e estabelecimento de confiança em artefatos.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ao estudar esses projetos, não observe apenas quais actions são utilizadas. Investigue as decisões arquiteturais.&lt;/p&gt;
&lt;p&gt;Pergunte:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Como o workflow é disparado?&lt;/li&gt;
&lt;li&gt;Ele aceita Pull Requests de forks?&lt;/li&gt;
&lt;li&gt;Quais permissões são concedidas ao &lt;code&gt;GITHUB_TOKEN&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Existem secrets disponíveis durante a execução?&lt;/li&gt;
&lt;li&gt;Como as dependências são instaladas?&lt;/li&gt;
&lt;li&gt;As GitHub Actions são referenciadas por tags ou hashes SHA completos?&lt;/li&gt;
&lt;li&gt;Como os artefatos são assinados e verificados?&lt;/li&gt;
&lt;li&gt;Como as publicações são autorizadas?&lt;/li&gt;
&lt;li&gt;Existem ambientes protegidos e aprovações humanas?&lt;/li&gt;
&lt;li&gt;Como o projeto separa testes, validação e publicação?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;O objetivo é compreender o motivo de cada decisão, e não simplesmente copiar configurações.&lt;/p&gt;
&lt;p&gt;Uma configuração segura em determinado contexto pode ser inadequada em outro.&lt;/p&gt;
&lt;h3 id=&quot;5-construa-uma-rotina-de-auditoria-de-pipelines&quot;&gt;5. Construa uma rotina de auditoria de pipelines&lt;/h3&gt;
&lt;p&gt;A análise não deve acontecer somente quando um scanner encontra um problema.&lt;/p&gt;
&lt;p&gt;Uma rotina de auditoria pode ser incorporada ao próprio processo de engenharia.&lt;/p&gt;
&lt;p&gt;Uma abordagem prática:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Etapa 1 — Inventário&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Mapear todos os workflows existentes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CI.&lt;/li&gt;
&lt;li&gt;CD.&lt;/li&gt;
&lt;li&gt;Release.&lt;/li&gt;
&lt;li&gt;Publicação de pacotes.&lt;/li&gt;
&lt;li&gt;Provisionamento de infraestrutura.&lt;/li&gt;
&lt;li&gt;Automação de dependências.&lt;/li&gt;
&lt;li&gt;Segurança e compliance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Etapa 2 — Análise automatizada&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Executar ferramentas como:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;zizmor .github/workflows
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;actionlint
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;checkov -d .github/workflows
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;scorecard --repo=github.com/ORG/REPOSITORY
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Os comandos devem ser ajustados à versão e à forma de instalação de cada ferramenta.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Etapa 3 — Investigação dos findings&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Para cada alerta:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Identificar o trecho responsável.&lt;/li&gt;
&lt;li&gt;Entender a regra acionada.&lt;/li&gt;
&lt;li&gt;Identificar a origem dos dados.&lt;/li&gt;
&lt;li&gt;Verificar se existe controle compensatório.&lt;/li&gt;
&lt;li&gt;Avaliar a possibilidade de exploração.&lt;/li&gt;
&lt;li&gt;Determinar o impacto potencial.&lt;/li&gt;
&lt;li&gt;Definir a correção ou justificar uma exceção.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Etapa 4 — Correção&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Priorizar problemas que permitam execução de código não confiável, exposição de credenciais, elevação de privilégios e comprometimento de releases.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Etapa 5 — Prevenção&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Incorporar os scanners ao próprio CI para impedir que configurações inseguras sejam introduzidas novamente.&lt;/p&gt;
&lt;h3 id=&quot;6-crie-um-laborat-rio-utilizando-projetos-reais&quot;&gt;6. Crie um laboratório utilizando projetos reais&lt;/h3&gt;
&lt;p&gt;Uma estratégia especialmente interessante é escolher projetos open source e realizar auditorias independentes em seus workflows.&lt;/p&gt;
&lt;p&gt;Não é necessário encontrar uma vulnerabilidade para que o estudo seja útil.&lt;/p&gt;
&lt;p&gt;O exercício consiste em:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Selecionar um repositório ativo e bem mantido.&lt;/li&gt;
&lt;li&gt;Baixar ou clonar seus workflows.&lt;/li&gt;
&lt;li&gt;Executar os scanners.&lt;/li&gt;
&lt;li&gt;Investigar os findings.&lt;/li&gt;
&lt;li&gt;Consultar a documentação das regras acionadas.&lt;/li&gt;
&lt;li&gt;Comparar a implementação com as recomendações oficiais.&lt;/li&gt;
&lt;li&gt;Identificar controles compensatórios.&lt;/li&gt;
&lt;li&gt;Propor correções.&lt;/li&gt;
&lt;li&gt;Registrar os resultados em um pequeno relatório técnico.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;É possível inclusive comparar diferentes projetos para entender como cada equipe resolve o mesmo problema.&lt;/p&gt;
&lt;p&gt;Por exemplo: como diferentes projetos implementam Trusted Publishing, como controlam dependências de terceiros ou como protegem workflows executados por Pull Requests externos.&lt;/p&gt;
&lt;p&gt;Esse exercício desenvolve uma habilidade importante: sair da posição de alguém que apenas corrige alertas e passar a compreender a arquitetura de segurança do processo de entrega.&lt;/p&gt;
&lt;p&gt;Segurança de CI/CD exige conhecimento sobre GitHub Actions, shell, permissões, autenticação, dependências, infraestrutura e supply chain security.&lt;/p&gt;
&lt;p&gt;É justamente por isso que estudar workflows de projetos maduros é tão valioso: eles oferecem exemplos concretos de decisões de engenharia, problemas reais e estratégias de mitigação que podem ser adaptadas a outros ambientes.&lt;/p&gt;
&lt;h3 id=&quot;conclus-o&quot;&gt;Conclusão&lt;/h3&gt;
&lt;p&gt;Scanners são instrumentos de diagnóstico, não substitutos do conhecimento técnico.&lt;/p&gt;
&lt;p&gt;Executar um scanner e aplicar todas as correções sugeridas sem entender os findings pode gerar configurações desnecessárias, exceções mal justificadas e até novos problemas.&lt;/p&gt;
&lt;p&gt;O caminho mais consistente é combinar análise automatizada, estudo da documentação, investigação técnica e comparação com implementações de projetos reconhecidos.&lt;/p&gt;
&lt;p&gt;Estudar pipelines de bons projetos é uma forma de aprender com decisões de engenharia que já foram colocadas à prova.&lt;/p&gt;
&lt;p&gt;Afinal, um pipeline seguro não é aquele que simplesmente passa em todas as verificações. É aquele cuja arquitetura foi compreendida, cujos riscos são conhecidos e cujos mecanismos de proteção foram implementados de maneira consciente.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/33090/seguranca-em-ci-cd-importancia-de-estudar-auditar-aprender-com-pipelines-de-bons-projetos</guid>
<pubDate>Fri, 30 Oct 2026 12:10:32 +0000</pubDate>
</item>
<item>
<title>Automated PR Reviews with Cline and NVIDIA NIM APIs</title>
<link>https://coderlegion.com/29988/automated-pr-reviews-with-cline-and-nvidia-nim-apis</link>
<description>&lt;p&gt;AI has been evolving at a breakneck pace ever since the first consumer-facing products landed. I've been updating my personal development workflow since day one, keeping my tools and habits aligned with each new wave of progress.&lt;/p&gt;
&lt;p&gt;Over time I've worked with a long list of models and tools.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Models&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ChatGPT&lt;/li&gt;
&lt;li&gt;Claude&lt;/li&gt;
&lt;li&gt;Gemini&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Tools&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cursor&lt;/li&gt;
&lt;li&gt;GitHub Copilot for VS Code&lt;/li&gt;
&lt;li&gt;GitHub Copilot for Xcode&lt;/li&gt;
&lt;li&gt;Xcode Coding Intelligence&lt;/li&gt;
&lt;li&gt;Trae&lt;/li&gt;
&lt;li&gt;VS Code plugins: Cline, Roo Code, …&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;…and the list keeps growing.&lt;/p&gt;
&lt;p&gt;For the last year or so I've been developing with &lt;strong&gt;TRAE / Gemini&lt;/strong&gt; on macOS, and both the IDE and the model served me well. But those tools and models come with two recurring pain points:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Third-party apps&lt;/strong&gt; mean your data sits in someone else's database.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subscription fees&lt;/strong&gt; can add up fast.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The natural answer to both is &lt;strong&gt;local LLMs&lt;/strong&gt;. I tested that route on my &lt;strong&gt;MacBook M1 Pro&lt;/strong&gt; with &lt;strong&gt;LM Studio&lt;/strong&gt;, pulling models locally and wiring them into VS Code / Xcode:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gemma&lt;/li&gt;
&lt;li&gt;Qwen&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;…and a few others.&lt;/p&gt;
&lt;p&gt;The honest result: on my hardware, local models weren't strong enough to beat the cloud subscription experience.&lt;/p&gt;
&lt;p&gt;Then, a few days ago, I discovered that &lt;strong&gt;NVIDIA&lt;/strong&gt; is running open models on its own servers for free at &lt;a rel=&quot;nofollow&quot; href=&quot;https://build.nvidia.com&quot;&gt;build.nvidia.com&lt;/a&gt;. I immediately wanted to try my workflow against that service — first with the &lt;strong&gt;NVIDIA NIM Provider&lt;/strong&gt; extension in VS Code, then side-by-side with &lt;strong&gt;Cline&lt;/strong&gt;. The &lt;strong&gt;Cline + NVIDIA NIM&lt;/strong&gt; combo turned out to be noticeably more stable, so that's what stuck.&lt;/p&gt;
&lt;p&gt;I was about to undergo a major refactor on one of my projects &lt;strong&gt;TaxCalculator&lt;/strong&gt;: turning the Flask/Python monolith (thousands of lines in &lt;code&gt;index.html&lt;/code&gt; plus a tangled frontend) into a sliced, testable, readable, and maintainable structure.&lt;/p&gt;
&lt;p&gt;&amp;lt;Before/after screenshot of the frontend file hierarchy&amp;gt;&lt;/p&gt;
&lt;p&gt;That result genuinely made my day.&lt;/p&gt;
&lt;p&gt;Just before merging the refactor PR, one more question popped into my head: &lt;strong&gt;Cline + NVIDIA NIM&lt;/strong&gt; had carried me this far — could the same duo review my PRs automatically through &lt;strong&gt;GitHub Actions&lt;/strong&gt;?&lt;/p&gt;
&lt;p&gt;Spoiler: yes, it can. Here's the full story, including the bumps along the way.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-automated-pr-review-&quot;&gt;Why Automated PR Review?&lt;/h3&gt;
&lt;p&gt;I'm already constantly reviewing AI-generated code during development and again at PR time. Adding a third pass — an automated PR review bot — is the obvious next step for both &lt;strong&gt;security&lt;/strong&gt; and &lt;strong&gt;robustness&lt;/strong&gt;, right? An LLM-based agent is especially good at catching:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Domain-specific rules&lt;/strong&gt; (Clean Architecture, &lt;code&gt;Decimal&lt;/code&gt; for money, no hardcoded secrets)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Edge cases / null-safety&lt;/strong&gt; issues&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test coverage&lt;/strong&gt; gaps&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API &amp;amp; architecture&lt;/strong&gt; inconsistencies&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cline CLI is ideal for this: it runs headless, integrates with &lt;code&gt;gh&lt;/code&gt;, and supports any OpenAI-compatible API.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;architecture&quot;&gt;Architecture&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;PR opened → GitHub Actions workflow triggers
          → Diff fetch (1 API call, capped by context budget)
          → Cline CLI: read review rules + analyze diff
          → gh pr review --comment to leave inline feedback
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three files do the work:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;File &lt;/th&gt;&lt;th&gt;Role&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;AGENTS.md&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Universal review rules&lt;/strong&gt; (same across every project: language, secrets, dependencies, naming, commits)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;.clinerules/taxcalculator.md&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Project-specific rules&lt;/strong&gt; (Clean Arch boundaries, &lt;code&gt;Decimal&lt;/code&gt;, zero-data-retention, mandatory Flask blueprints, &lt;code&gt;LoggerService&lt;/code&gt;, &lt;code&gt;ConcurrencyService&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;.github/workflows/cline-pr-review.yml&lt;/code&gt; &lt;/td&gt;&lt;td&gt;CI workflow&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Why two rule files?&lt;/strong&gt; So I can copy &lt;code&gt;AGENTS.md&lt;/code&gt; verbatim into other projects. Cline auto-loads anything inside &lt;code&gt;.clinerules/&lt;/code&gt; natively — no extra config needed.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-by-step-setup&quot;&gt;Step-by-Step Setup&lt;/h3&gt;
&lt;h4 id=&quot;1-add-the-secret&quot;&gt;1. Add the secret&lt;/h4&gt;
&lt;p&gt;In the GitHub repo go to &lt;strong&gt;Settings → Secrets and variables → Actions&lt;/strong&gt; and add:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;NVIDIA_API_KEY&lt;/code&gt; = your key from build.nvidia.com&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;2-write-the-workflow-file&quot;&gt;2. Write the workflow file&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;on:
  pull_request:
    types: [opened, ready_for_review, synchronize]

jobs:
  cline-review:
    runs-on: ubuntu-latest
    timeout-minutes: 25
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '24' }
      - run: npm install -g cline

      - name: Authenticate Cline with NVIDIA NIM
        env: { NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }} }
        run: |
          cline auth --provider openai \
            --apikey &quot;$NVIDIA_API_KEY&quot; \
            --baseurl &quot;https://integrate.api.nvidia.com/v1&quot; \
            --modelid &quot;minimaxai/minimax-m3&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-6-problems-i-hit-and-how-i-fixed-them-&quot;&gt;The 6 Problems I Hit (and how I fixed them)&lt;/h3&gt;
&lt;h4 id=&quot;1-node-20-deprecation&quot;&gt;1. Node 20 deprecation&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Error:&lt;/strong&gt; &lt;code&gt;Node.js 20 is deprecated... being forced to run on Node.js 24&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; &lt;code&gt;node-version: '24'&lt;/code&gt; (Cline CLI also requires Node 22.15+ for TLS trust store).&lt;/p&gt;
&lt;h4 id=&quot;2-unknown-option---base-url-&quot;&gt;2. &lt;code&gt;unknown option '--base-url'&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Error:&lt;/strong&gt; cline CLI does not accept dashed flags for this option.&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Drop the dash: &lt;code&gt;--baseurl &quot;https://integrate.api.nvidia.com/v1&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;3-auth-quick-setup-requires---modelid&quot;&gt;3. &lt;code&gt;auth quick setup requires --modelid&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Error:&lt;/strong&gt; quick-setup mode forces you to pick a model.&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Add &lt;code&gt;--modelid &quot;minimaxai/minimax-m3&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;4-429-too-many-requests-rate-limit-&quot;&gt;4. &lt;code&gt;429 Too Many Requests&lt;/code&gt; (rate limit)&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; NVIDIA's free tier caps you at &lt;strong&gt;40 requests per minute&lt;/strong&gt;. Every time Cline reads a slice of the diff, that's one request to the LLM. A large diff made Cline fetch it in several chunks, passing the 40 limit in under a minute — as a result the API responded with HTTP 429 and stopped serving.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fix — two layers:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(a) Diff pre-preparation&lt;/strong&gt; in the workflow, not in Cline:&lt;br&gt;
Fetch the full diff once, cap it by a budget derived from the model's context window, and derive the file list locally. Cline then just &lt;code&gt;cat&lt;/code&gt;s the prepared files — one read, one request.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Prepare PR diff
  env:
    MODEL_CONTEXT_WINDOW: &quot;128000&quot;   # token limit of the model
    DIFF_BUDGET_RATIO: &quot;0.7&quot;         # share of context for the diff
    DIFF_TOKENS_PER_LINE: &quot;3&quot;        # avg token density of a unified diff
  run: |
    # Fetch the diff once. Each subsequent read by Cline would be another
    # LLM call, burning our 40 RPM budget.
    gh pr diff &quot;$PR&quot; &amp;gt; /tmp/diff_full.txt
    TOTAL_LINES=$(wc -l &amp;lt; /tmp/diff_full.txt)

    # Derive the line cap from the model's context window. This is NOT a
    # magic number — change MODEL_CONTEXT_WINDOW to retarget a different model.
    MAX_LINES=$(awk -v ctx=&quot;$MODEL_CONTEXT_WINDOW&quot; -v ratio=&quot;$DIFF_BUDGET_RATIO&quot; \
                   -v tpl=&quot;$DIFF_TOKENS_PER_LINE&quot; \
                   'BEGIN { printf &quot;%d&quot;, ctx * ratio / tpl }')

    # Truncate to the cap. Anything beyond is dropped with a GitHub Actions
    # warning annotation so reviewers see why.
    if [ &quot;$TOTAL_LINES&quot; -le &quot;$MAX_LINES&quot; ]; then
      cp /tmp/diff_full.txt /tmp/diff.txt
    else
      head -&quot;$MAX_LINES&quot; /tmp/diff_full.txt &amp;gt; /tmp/diff.txt
      echo &quot;::warning::Diff truncated from $TOTAL_LINES to $MAX_LINES lines&quot;
    fi

    # Build the file list locally from 'diff --git' headers — no extra API call.
    grep '^diff --git' /tmp/diff_full.txt | sed -E 's|^diff --git a/(.+) b/.*|\1|' \
      &amp;gt; /tmp/diff_summary.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;(b) Strict prompt budget&lt;/strong&gt; — max 5 tool calls, &lt;code&gt;gh pr diff&lt;/code&gt; banned:&lt;br&gt;
Without these rules, an LLM agent will happily re-fetch the diff, read extra files for &quot;context&quot;, and burn through your rate budget. We tell Cline exactly what we want it to do, in what order, and how many steps it's allowed to take.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- Do NOT call 'gh pr diff' again.
- Make at most 5 tool calls total.
- Read /tmp/diff_summary.txt and /tmp/diff.txt via 'cat'.
- Post all review comments in ONE 'gh pr review --comment' call.
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;5-unknown-flag---stat&quot;&gt;5. &lt;code&gt;unknown flag: --stat&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Error:&lt;/strong&gt; &lt;code&gt;gh pr diff --stat&lt;/code&gt; does not exist in this &lt;code&gt;gh&lt;/code&gt; version (only &lt;code&gt;--name-only&lt;/code&gt; and &lt;code&gt;--patch&lt;/code&gt;).&lt;br&gt;
&lt;strong&gt;What we wanted it for:&lt;/strong&gt; the &quot;X files changed, +Y −Z&quot; summary you see on the PR page. We needed that list so Cline could see the affected files before reading the diff.&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Extract the file list locally from the &lt;code&gt;diff --git a/... b/...&lt;/code&gt; headers in the diff itself. No extra API call, just text processing:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;grep '^diff --git' /tmp/diff_full.txt | sed -E 's|^diff --git a/(.+) b/.*|\1|'
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;6-retries-duplicate-findings&quot;&gt;6. Retries duplicate findings&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; On a transient 429 or socket drop, naively adding &lt;code&gt;|| sleep 60 &amp;amp;&amp;amp; retry&lt;/code&gt; seems safe. In practice it isn't — Cline is invoked as a fresh CLI process each time, so the retry restarts from scratch: re-reads the diff, re-reads context, and posts &lt;strong&gt;duplicate review comments&lt;/strong&gt; on the same PR.&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; No retry. Run Cline once, fail loudly on failure. If a future change makes retries necessary, persist Cline's intermediate state to disk between attempts so a retry can resume.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# No retry — Cline must complete in one session.
cline --auto-approve true &quot;$PROMPT&quot; \
  || (echo '::error::Cline review failed'; exit 1)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;how-we-pick-the-dynamic-line-limit&quot;&gt;How We Pick the Dynamic Line Limit&lt;/h3&gt;
&lt;p&gt;The cap is &lt;strong&gt;not a magic number&lt;/strong&gt;. It's derived from three constants the workflow exposes as environment variables:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Variable &lt;/th&gt;&lt;th&gt;Default &lt;/th&gt;&lt;th&gt;Why&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;MODEL_CONTEXT_WINDOW&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;128000&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Token limit of the model you call&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;DIFF_BUDGET_RATIO&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;0.7&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Share of the context reserved for the diff&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;DIFF_TOKENS_PER_LINE&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;3&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Average token density of a unified diff&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The formula:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;max_lines = context_window × diff_budget_ratio ÷ diff_tokens_per_line
         = 128_000 × 0.7 ÷ 3
         ≈ 29_866 lines
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So a typical PR with fewer than ~30k diff lines is reviewed in full. Anything larger is truncated and the workflow emits a &lt;code&gt;::warning::&lt;/code&gt; annotation explaining the cap.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why not &quot;unlimited&quot;?&lt;/strong&gt; Three real upper bounds apply, and any of them can break the run:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Model context window&lt;/strong&gt; — the diff, system prompt, rules, and the agent's output all share one token budget. Overflow = truncated reasoning or broken output.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workflow timeout&lt;/strong&gt; — the job-level &lt;code&gt;timeout-minutes&lt;/code&gt; caps the total runtime. A multi-hundred-thousand-line diff cannot finish reviewing inside the window.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rate limit&lt;/strong&gt; — free NVIDIA NIM caps at 40 RPM. A giant diff turns into many LLM calls if the agent reads it in pieces.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Trade-offs you can tune:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DIFF_BUDGET_RATIO = 0.9&lt;/code&gt; → bigger diff, less room for Cline's reasoning/output.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DIFF_BUDGET_RATIO = 0.5&lt;/code&gt; → smaller diff, larger safety margin.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;timeout-minutes&lt;/code&gt; → increase for very large diffs, decrease for faster feedback.&lt;/li&gt;
&lt;li&gt;Switch models → set &lt;code&gt;MODEL_CONTEXT_WINDOW&lt;/code&gt; (e.g. &lt;code&gt;32000&lt;/code&gt;) and the cap recomputes automatically.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;result-a-real-pr-review&quot;&gt;Result: A Real PR Review&lt;/h3&gt;
&lt;p&gt;On a real PR, the workflow produced output like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;🚨 &lt;strong&gt;Critical&lt;/strong&gt;: duplicate block in &lt;code&gt;report_service._create_trades_from_lists&lt;/code&gt;, hard-coded strings, &lt;code&gt;remember=True&lt;/code&gt; cookie leaking across sessions&lt;/li&gt;
&lt;li&gt;⚠️ &lt;strong&gt;Important&lt;/strong&gt;: privacy/data-retention violation (&lt;code&gt;cvs_hash&lt;/code&gt; persisted server-side), missing integration tests under &lt;code&gt;tests/unit/parsers/&lt;/code&gt;, architecture inconsistencies&lt;/li&gt;
&lt;li&gt;✨ &lt;strong&gt;Nits&lt;/strong&gt;: quote style inconsistency, &lt;code&gt;NonJSON&lt;/code&gt; typo, multi-line logs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In other words: &lt;strong&gt;universal rules + project-specific rules + single-shot review + dynamic diff budget&lt;/strong&gt; combined into a real, value-adding automated review running on a free tier.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;real-example-taxcalculator-refactor-pr-13-&quot;&gt;Real Example: TaxCalculator Refactor (PR #13)&lt;/h3&gt;
&lt;p&gt;Here's an actual PR review generated by the workflow on my &lt;strong&gt;TaxCalculator&lt;/strong&gt; project — a large refactor turning a Flask monolith into Clean Architecture boundaries.&lt;/p&gt;
&lt;h4 id=&quot;pr-review-eraydiler-taxcalculator-13&quot;&gt;PR Review — eraydiler/TaxCalculator#13&lt;/h4&gt;
&lt;p&gt;Overall the PR is a large refactor that improves structure (Clean Architecture boundaries, partials, JS modules, tests). However, several &lt;strong&gt;blocking issues&lt;/strong&gt; need attention before merging.&lt;/p&gt;
&lt;h5 id=&quot;-blocking-privacy-zero-data-retention-claim-is-violated&quot;&gt;🔴 Blocking — Privacy: zero-data-retention claim is violated&lt;/h5&gt;
&lt;p&gt;&lt;code&gt;ARCHITECTURE.md&lt;/code&gt; (and &lt;code&gt;README.md&lt;/code&gt;) explicitly promise &lt;em&gt;&quot;No user report data (CSV content, tax summary) is persisted on the server. All report data lives exclusively in the clients IndexedDB.&quot;&lt;/em&gt; The diff contradicts this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;src/models/report_task.py&lt;/code&gt; stores &lt;code&gt;csv_content&lt;/code&gt; on the model:&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;self.csv_content: Optional[str] = task_data.get(&quot;csv_content&quot;)
...
&quot;csv_content&quot;: self.csv_content,
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/repositories/mongo_task_repository.py&lt;/code&gt; writes &lt;code&gt;csv_content&lt;/code&gt; to MongoDB:&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if csv_content is not None:
    update_data[&quot;csv_content&quot;] = csv_content
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/blueprints/auth.py&lt;/code&gt; (&lt;code&gt;download_csv&lt;/code&gt;) reads &lt;code&gt;task.csv_content&lt;/code&gt; from MongoDB and serves it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The project-specific rule &lt;strong&gt;Privacy: Server must NOT persist CSV report content or tax summaries&lt;/strong&gt; is being broken by this change. Either:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Remove &lt;code&gt;csv_content&lt;/code&gt; from &lt;code&gt;ReportTask&lt;/code&gt; and persist &lt;em&gt;only&lt;/em&gt; &lt;code&gt;csv_hash&lt;/code&gt; (the documented design), and make the client responsible for retaining the CSV in IndexedDB for re-download — &lt;strong&gt;or&lt;/strong&gt; —&lt;/li&gt;
&lt;li&gt;Update &lt;code&gt;ARCHITECTURE.md&lt;/code&gt; / &lt;code&gt;README.md&lt;/code&gt; to reflect the actual policy, and get explicit sign-off, since this is a documented user-facing privacy guarantee.&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id=&quot;-logic-security&quot;&gt;🟡 Logic / Security&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;src/blueprints/auth.py&lt;/code&gt;: the upload endpoint computes &lt;code&gt;csv_hash&lt;/code&gt; for integrity but never compares the stored hash against a client-supplied hash on download — the integrity check described in &lt;code&gt;ARCHITECTURE.md&lt;/code&gt; is one-directional only. If you keep &lt;code&gt;csv_content&lt;/code&gt; in Mongo, at least verify hash equality before serving.&lt;/li&gt;
&lt;li&gt;The webhook signature path uses &lt;code&gt;X-Signature&lt;/code&gt; header; confirm this matches Lemon Squeezy's actual header name (&lt;code&gt;X-Signature&lt;/code&gt; is correct for Lemon Squeezy — OK, but worth a comment).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tests/unit/test_webhook_idempotency.py&lt;/code&gt; patches &lt;code&gt;src.app.payment_service.user_repository&lt;/code&gt; but the production code accesses the repo via the service, not the module-level attribute. Verify the patch target; otherwise this test passes without exercising the real path.&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;-tests&quot;&gt;🟡 Tests&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;New tests added (good), but no unit test exists for the refactored &lt;code&gt;mongo_task_repository&lt;/code&gt; &lt;code&gt;create_task_metadata&lt;/code&gt; / &lt;code&gt;update_task_status&lt;/code&gt; paths. Add coverage under &lt;code&gt;tests/unit/&lt;/code&gt; for both the success path and the privacy rule (assert that no &lt;code&gt;csv_content&lt;/code&gt; is written when the zero-retention policy is enforced).&lt;/li&gt;
&lt;li&gt;The download-race-condition test should also assert that &lt;code&gt;csv_content&lt;/code&gt; is &lt;strong&gt;never&lt;/strong&gt; written by the server, to lock in the privacy guarantee.&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;-minor-style&quot;&gt;🟢 Minor / Style&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tests/integration/test_end_to_end.py&lt;/code&gt; still contains &lt;code&gt;print(...)&lt;/code&gt; debug noise (&lt;code&gt;print(&quot;\nRow differences found:&quot;)&lt;/code&gt; etc.). Replace with &lt;code&gt;LoggerService&lt;/code&gt; per the logging rule.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/blueprints/auth.py&lt;/code&gt; returns &lt;code&gt;{&quot;error&quot;: &quot;Geçersiz dosya formatı...&quot;}&lt;/code&gt; — user-facing strings must be English unless localization is added; this is Turkish. Same applies to any other tr-string errors in the diff.&lt;/li&gt;
&lt;li&gt;Commit messages are clean and descriptive — good.&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;-looks-good&quot;&gt;✅ Looks good&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Decimal&lt;/code&gt; is used consistently for monetary values (no &lt;code&gt;float&lt;/code&gt; introduced for tax/money).&lt;/li&gt;
&lt;li&gt;Blueprints live under &lt;code&gt;src/blueprints/&lt;/code&gt;, not inline in &lt;code&gt;app.py&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ConcurrencyService&lt;/code&gt; is used for parallel work.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LoggerService&lt;/code&gt; is used for errors/info.&lt;/li&gt;
&lt;li&gt;JS is split into focused ES modules with proper single-responsibility boundaries.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Please address the &lt;strong&gt;Blocking&lt;/strong&gt; privacy item first — it directly violates a documented user-facing promise and a project-specific review rule.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;lessons-learned&quot;&gt;Lessons Learned&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;LLM agents are not cheap&lt;/strong&gt; — even 40 RPM is tight, careful budgeting is essential.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CLI flags drift across versions&lt;/strong&gt; — always check &lt;code&gt;--help&lt;/code&gt; before writing the workflow.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Batch in the workflow, not in the agent&lt;/strong&gt; — pre-preparation saves API calls.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don't retry blindly&lt;/strong&gt; — a fresh Cline process restarts from scratch and duplicates findings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep universal rules separate from project-specific rules&lt;/strong&gt; — reusable vs portable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Derive magic numbers from real constraints&lt;/strong&gt; — context window, timeout, rate limit — instead of guessing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Real PR reviews catch real problems&lt;/strong&gt; — the privacy violation in TaxCalculator#13 would have been missed by manual review alone.&lt;/li&gt;
&lt;/ol&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29988/automated-pr-reviews-with-cline-and-nvidia-nim-apis</guid>
<pubDate>Tue, 06 Oct 2026 11:57:57 +0000</pubDate>
</item>
<item>
<title>Who Can Change Your npm Release Tags?</title>
<link>https://coderlegion.com/29397/who-can-change-your-npm-release-tags</link>
<description>&lt;h2 id=&quot;who-should-be-allowed-to-change-your-npm-release-tags-&quot;&gt;&lt;strong&gt;Who Should Be Allowed to Change Your npm Release Tags?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Your CI/CD pipeline may already be able to &lt;strong&gt;publish an npm package&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;But here’s the more important question:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Should every workflow that can publish also be allowed to change &lt;code&gt;latest&lt;/code&gt;, &lt;code&gt;next&lt;/code&gt;, or &lt;code&gt;beta&lt;/code&gt; tags?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;GitHub’s recent npm trusted publishing update makes this separation much clearer.&lt;/p&gt;
&lt;p&gt;Workflows can now receive &lt;strong&gt;explicit permission&lt;/strong&gt; to manage npm distribution tags using &lt;strong&gt;short-lived OIDC credentials&lt;/strong&gt; instead of keeping long-lived automation tokens around.&lt;/p&gt;
&lt;h3 id=&quot;why-this-matters&quot;&gt;Why this matters&lt;/h3&gt;
&lt;p&gt;Release tags can directly affect &lt;strong&gt;which version users install&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Changing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;latest
next
beta
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;may look like a small metadata change, but in practice it can influence production adoption, prerelease testing, and package distribution.&lt;/p&gt;
&lt;p&gt;That means tag management deserves its &lt;strong&gt;own permission boundary&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;what-development-teams-should-review&quot;&gt;What development teams should review&lt;/h3&gt;
&lt;p&gt;Before changing anything in production, I’d check these first:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Which workflow actually needs to manage release tags?&lt;/li&gt;
&lt;li&gt;Does that workflow really need broader publishing permissions too?&lt;/li&gt;
&lt;li&gt;Can the operation be handled through &lt;strong&gt;OIDC-based trusted publishing&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;Have you tested tag updates before removing existing credentials?&lt;/li&gt;
&lt;li&gt;Are old automation tokens still required anywhere else?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A good principle here is:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Give automation only the permissions it actually needs.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The tag-management permission is &lt;strong&gt;off by default&lt;/strong&gt;, which is a sensible security choice.&lt;/p&gt;
&lt;p&gt;And importantly, moving to short-lived credentials does not remove the need for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;release testing&lt;/li&gt;
&lt;li&gt;permission reviews&lt;/li&gt;
&lt;li&gt;CI/CD controls&lt;/li&gt;
&lt;li&gt;rollback planning&lt;/li&gt;
&lt;li&gt;software supply-chain security&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;discussion&quot;&gt;Discussion&lt;/h3&gt;
&lt;p&gt;I think this is a good example of where &lt;strong&gt;least privilege&lt;/strong&gt; becomes practical, not theoretical.&lt;/p&gt;
&lt;p&gt;Publishing a package and deciding which release becomes &lt;code&gt;latest&lt;/code&gt; are related actions—but they are not necessarily the same responsibility.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How are you handling this in your npm workflows today?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Do you keep publishing and tag management in one workflow, or would you separate them?&lt;/p&gt;
&lt;p&gt; #NPM #GitHubActions #DevSecOps #CICD #OIDC #SoftwareEngineering #DevOps #SoftwareSupplyChain #PackageManagement #OpenSource #DeveloperTools #Automation #NodeJS #WaqasAhmad&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29397/who-can-change-your-npm-release-tags</guid>
<pubDate>Thu, 01 Oct 2026 11:37:54 +0000</pubDate>
</item>
<item>
<title># The Revolution in Protocol Security: How It Actually Gets Used</title>
<link>https://coderlegion.com/24974/the-revolution-in-protocol-security-how-it-actually-gets-used</link>
<description>&lt;p&gt;&lt;strong&gt;A follow-up. The previous article described what we are building. This one describes how it works in practice - and why the shift from academic tool to industrial platform matters.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;In a previous article, I described the architecture of a protocol analyzer we are building: automatic repair, sixteen attacker models, GNN-guided search, modular crate design. That was the &quot;what.&quot; This article is the &quot;how&quot; - how this tool gets used, what it changes in practice, and why we believe it represents a shift from laboratory instrument to industrial platform.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-problem-with-existing-tools-is-not-only-capability---it-is-also-access&quot;&gt;The problem with existing tools is not only capability - it is also access&lt;/h3&gt;
&lt;p&gt;ProVerif and Tamarin are remarkable achievements in formal methods. They are also laboratory instruments by design. You install them on a research machine, write a model in their DSL, run analysis in a terminal, read the output. This workflow is fine for a PhD student. It does not work for a security engineer at a company that ships TLS implementations.&lt;/p&gt;
&lt;p&gt;The gap concerns where these tools live in the development lifecycle, not what they can prove. Existing tools sit outside CI pipelines, outside IDEs, outside deployment workflows. They are consulted - if at all - after a protocol is designed, before it ships. Rarely during development, rarely after deployment.&lt;/p&gt;
&lt;p&gt;We are building something that lives inside the lifecycle.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;ci-cd-integration-verify-on-every-commit&quot;&gt;CI/CD integration: verify on every commit&lt;/h3&gt;
&lt;p&gt;The analyzer ships as a GitHub Action. Every time a protocol description changes - a new message flow, a modified key exchange, an added authentication step - the analyzer runs automatically. It checks whether the change introduces a vulnerability. If it does, the action fails with a concrete attack trace and a proposed fix.&lt;/p&gt;
&lt;p&gt;This is not a theoretical capability. The action is designed to run in minutes on standard CI hardware, not hours on a research cluster. The performance targets we described in the previous article - 10x to 50x speedup, sub-ten-minute analysis for real-world protocols - exist to make this practical. A tool that takes six hours to analyze a protocol cannot run in CI. One that takes six minutes can.&lt;/p&gt;
&lt;p&gt;Beyond GitHub Actions, the analyzer provides a CLI that integrates with any CI system - GitLab CI, Jenkins, Buildkite. The output is machine-readable: SARIF for security scanners, JSON for custom pipelines, JUnit XML for test reporting. Protocol verification becomes another test suite in the pipeline.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;ide-integration-find-vulnerabilities-while-you-write&quot;&gt;IDE integration: find vulnerabilities while you write&lt;/h3&gt;
&lt;p&gt;The analyzer ships a Language Server Protocol (LSP) implementation. In a VS Code editor, this means: syntax highlighting for the protocol DSL, inline error reporting as you type, hover documentation for security properties, and one-click analysis from the editor.&lt;/p&gt;
&lt;p&gt;The experience is analogous to writing Rust with rust-analyzer: the tool understands what you are writing and gives feedback in real time. When you define a key exchange step, the analyzer checks whether the key material is properly authenticated. When you add a new message, it checks whether the freshness guarantees hold. The vulnerability is flagged before you commit the code - not after.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;real-time-analysis-the-daemon-and-the-sniffer&quot;&gt;Real-time analysis: the daemon and the sniffer&lt;/h3&gt;
&lt;p&gt;This is where the tool stops being a verifier and becomes a platform.&lt;/p&gt;
&lt;p&gt;The analyzer runs as a background daemon with a gRPC API. Any service in the infrastructure can send a protocol description and receive analysis results. But the daemon does more than wait for requests - it listens to live traffic.&lt;/p&gt;
&lt;p&gt;The sniffer module captures network traffic (PCAP), reconstructs protocol sessions from packet streams, and feeds them to the symbolic engine in real time. When a TLS handshake happens on the network, the analyzer checks whether that handshake - with its specific cipher suite, extensions, and parameters - satisfies the security properties. When an unusual pattern appears - a repeated nonce, an unexpected message order, a downgrade attempt - it flags the anomaly.&lt;/p&gt;
&lt;p&gt;This is live verification - analysis that runs while the protocol is executing, not after the fact.&lt;/p&gt;
&lt;p&gt;The real-time constraint changes the engineering at its core. Traditional protocol analysis explores the full state space - every possible interleaving, every attacker action. That takes hours. Real-time analysis uses a bounded model: explore the state space up to depth N, report what you find, continue monitoring. The GNN-guided search from our previous article is critical here - it prioritizes the most promising exploration paths, finding attacks faster in bounded time.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;web-interface-collaboration-not-just-output&quot;&gt;Web interface: collaboration, not just output&lt;/h3&gt;
&lt;p&gt;The analyzer ships a web UI. This is a requirement for adoption, not a luxury.&lt;/p&gt;
&lt;p&gt;Security review is a collaborative process. An analyst finds an attack, a protocol designer evaluates the proposed fix, a manager decides whether the risk is acceptable. In existing tools, this workflow happens outside the tool: screenshots in Slack, traces in emails, decisions in meetings.&lt;/p&gt;
&lt;p&gt;The web UI makes the analysis itself collaborative. Attack graphs are rendered in 3D - navigable, zoomable, inspectable. Repair proposals are presented side-by-side with the original protocol, with the differences highlighted. Reviewers can comment, approve, or reject fixes. The entire security review happens inside the platform.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;converters-meeting-people-where-they-are&quot;&gt;Converters: meeting people where they are&lt;/h3&gt;
&lt;p&gt;The analyzer accepts input from ProVerif, Tamarin, Scyther, Avispa, CryptoVerif, and Maude descriptions. This is an adoption strategy, not a convenience feature.&lt;/p&gt;
&lt;p&gt;Thousands of protocols are already modeled in these languages. Researchers and engineers who have invested years in ProVerif models should not have to rewrite them. They import their existing description, run the analyzer, and get the same output plus repair proposals plus real-time monitoring plus CI integration.&lt;/p&gt;
&lt;p&gt;The converters are imperfect - each source language has constructs that do not map cleanly. But they handle the common case well enough to lower the barrier from &quot;I should try this tool&quot; to &quot;I just tried this tool.&quot;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;platform-not-tool&quot;&gt;Platform, not tool&lt;/h3&gt;
&lt;p&gt;Let us be explicit about what we are building. A tool does one thing from the command line. A platform integrates into workflows, provides APIs, runs continuously, and collaborates with other systems.&lt;/p&gt;
&lt;p&gt;The protocol analyzer we are building has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A CLI for direct use and scripting&lt;/li&gt;
&lt;li&gt;A GitHub Action for CI/CD&lt;/li&gt;
&lt;li&gt;An LSP server for IDE integration&lt;/li&gt;
&lt;li&gt;A gRPC API for programmatic access&lt;/li&gt;
&lt;li&gt;A daemon for continuous monitoring&lt;/li&gt;
&lt;li&gt;A sniffer for real-time traffic analysis&lt;/li&gt;
&lt;li&gt;A web UI for collaborative review&lt;/li&gt;
&lt;li&gt;WASM bindings for browser-based playground&lt;/li&gt;
&lt;li&gt;Converters from five existing tools&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a platform. The symbolic analysis core - the part described in the previous article - is the engine. Everything else is the interface between that engine and the world where protocols are built, deployed, and attacked.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-revolution-is-practical-not-technical&quot;&gt;The revolution is practical, not technical&lt;/h3&gt;
&lt;p&gt;The technical contributions matter: automatic repair, GNN-guided search, incremental closure, sixteen attacker models. But the reason they matter is that they enable a new capability - protocol verification that runs in CI, in your IDE, on your network, in real time.&lt;/p&gt;
&lt;p&gt;ProVerif and Tamarin proved that symbolic protocol analysis is possible. We are trying to prove that it is practical - practical enough to run on every commit, practical enough to catch vulnerabilities while you type, practical enough to monitor live traffic.&lt;/p&gt;
&lt;p&gt;That is the shift: from laboratory instrument to industrial platform. From &quot;analyze after design&quot; to &quot;verify during development and after deployment.&quot; From a tool that finds attacks to a platform that finds them, fixes them, and watches for them.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;status&quot;&gt;Status&lt;/h3&gt;
&lt;p&gt;The CI/CD action, the LSP server, the daemon, and the sniffer are in active development. The web UI has working prototypes. The converters handle common cases. The core engine - described in the previous article - is functional and stable.&lt;/p&gt;
&lt;p&gt;This project is under active development. The platform described here is being built, not shipped. But the direction is clear, and the architecture supports it.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;This project is under active development. This article describes the planned platform capabilities of a tool still being built. The previous article covers the core engine architecture; this one covers the interfaces between that engine and the development lifecycle.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Follow the project:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Sup4ikX&quot;&gt;github.com/Sup4ikX&lt;/a&gt; - a preview release will be published there as soon as the first production version is ready.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24974/the-revolution-in-protocol-security-how-it-actually-gets-used</guid>
<pubDate>Thu, 20 Aug 2026 07:46:14 +0000</pubDate>
</item>
<item>
<title>Automatically update GitHub Action versions</title>
<link>https://coderlegion.com/21577/automatically-update-github-action-versions</link>
<description>&lt;p&gt;You don't notice your GitHub Actions versions until you start getting warnings about things like &quot;Node 20 is no longer supported&quot;. When you think about it, GitHub Actions are &lt;em&gt;yet another&lt;/em&gt; dependency that needs to be kept up to date and present supply chain risks.&lt;/p&gt;
&lt;h3 id=&quot;get-dependabot-to-do-the-work&quot;&gt;Get Dependabot to do the work&lt;/h3&gt;
&lt;p&gt;The good news is, you can get Dependabot to keep your GitHub Actions up to date for you. You can add instructions for this to your &lt;code&gt;.github/dependabot.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;version: 2
updates:
  - package-ecosystem: &quot;github-actions&quot;
    directory: &quot;/&quot;
    schedule:
      interval: &quot;weekly&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In my case I already had configuration for my npm dependencies:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;version: 2
updates:
  - package-ecosystem: &quot;npm&quot;
    directory: &quot;/&quot;
    schedule:
      interval: &quot;weekly&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But it's trivial to add multiple updates to the same file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;version: 2
updates:
  - package-ecosystem: &quot;npm&quot;
    directory: &quot;/&quot;
    schedule:
      interval: &quot;weekly&quot;

  - package-ecosystem: &quot;github-actions&quot;
    directory: &quot;/&quot;
    schedule:
      interval: &quot;weekly&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;automatic-pull-requests&quot;&gt;Automatic pull requests&lt;/h3&gt;
&lt;p&gt;When you first commit this file, you'll notice pull requests start appearing for your review.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=973758743445030684&quot; alt=&quot;Dependabot pull request to update a GitHub Action version&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Setup pnpm cache
    uses: actions/cache@v4 (-)
    uses: actions/cache@v5 (+)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a simple example of how you can use Dependabot to keep your GitHub Actions up to date. You can find more information about Dependabot in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/about-dependabot-version-updates&quot;&gt;GitHub documentation&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21577/automatically-update-github-action-versions</guid>
<pubDate>Mon, 29 Jun 2026 14:49:47 +0000</pubDate>
</item>
<item>
<title>Setting GitHub as a trusted publisher for npm</title>
<link>https://coderlegion.com/18956/setting-github-as-a-trusted-publisher-for-npm</link>
<description>&lt;p&gt;So, stuff happened and &lt;strong&gt;npm&lt;/strong&gt; has been updated to reduce the volume of stuff happening. In a world of SBOMs, SLSA, and supply chain attacks, it's time to get serious about publishing packages. In this case, that means using the new &lt;em&gt;Trusted Publisher&lt;/em&gt; feature to connect GitHub (or GitLab) to &lt;strong&gt;npm&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;set-the-trusted-publisher-on-npm&quot;&gt;Set the trusted publisher on npm&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Sign into &lt;a rel=&quot;nofollow&quot; href=&quot;https://npmjs.com&quot;&gt;npm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Select the package you want to set up, for example &lt;code&gt;astro-accelerator-utils&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;em&gt;Settings&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;In the &lt;em&gt;Trusted Publishers&lt;/em&gt; section, select your provider, in my case it's &lt;strong&gt;GitHub&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Enter you repository information:&lt;ul&gt;
&lt;li&gt;Organization or user name, for example &lt;code&gt;Steve-Fenton&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Repository name, for example &lt;code&gt;astro-accelerator-utils&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The result should be that &lt;code&gt;Steve-Fenton/astro-accelerator-utils&lt;/code&gt; matches your repo in GitHub&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Provide the workflow file name&lt;ul&gt;
&lt;li&gt;This should match the workflow that will publish the package, in my case &lt;code&gt;build-astro.yml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The file must be in &lt;code&gt;.github/workflows/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Set up connection&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you use environments, you can optionally limit publishing by environment.&lt;/p&gt;
&lt;h3 id=&quot;check-you-github-action&quot;&gt;Check you GitHub Action&lt;/h3&gt;
&lt;p&gt;In your permissions section, you need to allow &lt;code&gt;id-token&lt;/code&gt; to be written.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;permissions:
    id-token: write
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can then use the &lt;code&gt;npm publish&lt;/code&gt; step in your workflow.&lt;/p&gt;
&lt;p&gt;I conditionally publish based on the version number, so I only publish when the version number changes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Publish if version has been updated
    env:
        NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
    run: |
        PACKAGE_NAME=$(node -p &quot;require('./package.json').name&quot;)
        LOCAL_VERSION=$(node -p &quot;require('./package.json').version&quot;)
        REMOTE_VERSION=$(npm view $PACKAGE_NAME version || echo &quot;0.0.0&quot;)

        if [ &quot;$LOCAL_VERSION&quot; != &quot;$REMOTE_VERSION&quot; ] &amp;amp;&amp;amp; [ &quot;$(printf '%s\n%s' &quot;$REMOTE_VERSION&quot; &quot;$LOCAL_VERSION&quot; | sort -V | tail -n1)&quot; = &quot;$LOCAL_VERSION&quot; ]; then
        echo &quot;Local version $LOCAL_VERSION is higher than remote version $REMOTE_VERSION. Publishing...&quot;
        echo &quot;//registry.npmjs.org/:_authToken=$NPM_AUTH_TOKEN&quot; &amp;gt; ~/.npmrc
        npm publish --access public
        else
        echo &quot;Version $LOCAL_VERSION is not newer than $REMOTE_VERSION. Skipping publish.&quot;
        fi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a more secure way to publish npm packages, but it's also easier because you don't need to keep updating tokens and secrets.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18956/setting-github-as-a-trusted-publisher-for-npm</guid>
<pubDate>Tue, 26 May 2026 06:29:10 +0000</pubDate>
</item>
<item>
<title>Stop Rewriting CI/CD: Reusable GitHub Actions for Maven Projects</title>
<link>https://coderlegion.com/16030/stop-rewriting-ci-cd-reusable-github-actions-for-maven-projects</link>
<description>&lt;h2 id=&quot;stop-rewriting-ci-cd-pipelines-for-every-maven-project&quot;&gt;Stop Rewriting CI/CD Pipelines for Every Maven Project&lt;/h2&gt;
&lt;p&gt;If you’ve worked on multiple Java projects, you’ve probably run into this:&lt;/p&gt;
&lt;p&gt;Every repository has its own version of a CI/CD pipeline.&lt;/p&gt;
&lt;p&gt;Slightly different YAML.&lt;br&gt;
Slightly different setup steps.&lt;br&gt;
Slightly different ways of doing the same thing.&lt;/p&gt;
&lt;p&gt;And somehow… none of them are reusable.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-real-problem&quot;&gt;The Real Problem&lt;/h3&gt;
&lt;p&gt;It’s not that CI/CD is hard.&lt;/p&gt;
&lt;p&gt;It’s that it’s repetitive.&lt;/p&gt;
&lt;p&gt;You end up doing the same things over and over:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Set up Java  &lt;/li&gt;
&lt;li&gt;Cache Maven dependencies  &lt;/li&gt;
&lt;li&gt;Run tests  &lt;/li&gt;
&lt;li&gt;Build artifacts  &lt;/li&gt;
&lt;li&gt;Handle versioning  &lt;/li&gt;
&lt;li&gt;Publish or release  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then copy that workflow into another repo…&lt;br&gt;
…and tweak it just enough to break consistency.&lt;/p&gt;
&lt;p&gt;This is exactly the kind of problem automation should solve — yet we keep duplicating it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-different-approach&quot;&gt;A Different Approach&lt;/h3&gt;
&lt;p&gt;Instead of treating each pipeline as a one-off YAML file, I started thinking:&lt;/p&gt;
&lt;p&gt;What if CI/CD steps were modular and reusable — just like code?&lt;/p&gt;
&lt;p&gt;That’s where NERV-Actions came from.&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/your-repo-link&quot;&gt;https://github.com/your-repo-link&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-nerv-actions-is&quot;&gt;What NERV-Actions Is&lt;/h3&gt;
&lt;p&gt;NERV-Actions is a collection of reusable GitHub Actions designed for Maven-based projects.&lt;/p&gt;
&lt;p&gt;The idea is simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Break CI/CD into small, composable actions  &lt;/li&gt;
&lt;li&gt;Reuse them across repositories  &lt;/li&gt;
&lt;li&gt;Keep pipelines consistent without rewriting everything  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# repeated in every repo
- uses: actions/setup-java@v4
- run: mvn clean install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You plug in reusable building blocks that already handle those concerns.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-this-matters-1&quot;&gt;Why This Matters&lt;/h3&gt;
&lt;p&gt;Reusable workflows aren’t just about convenience — they solve real scaling problems.&lt;/p&gt;
&lt;p&gt;When you define things once and reuse them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You reduce duplication  &lt;/li&gt;
&lt;li&gt;You avoid inconsistencies across repos  &lt;/li&gt;
&lt;li&gt;You make updates easier (change once, apply everywhere)  &lt;/li&gt;
&lt;li&gt;You spend less time debugging YAML  &lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;design-philosophy&quot;&gt;Design Philosophy&lt;/h3&gt;
&lt;p&gt;I didn’t try to build a one-size-fits-all pipeline.&lt;/p&gt;
&lt;p&gt;Instead, NERV-Actions focuses on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Composable units → mix only what you need  &lt;/li&gt;
&lt;li&gt;Sensible defaults → minimal setup required  &lt;/li&gt;
&lt;li&gt;Consistency → same patterns across projects  &lt;/li&gt;
&lt;li&gt;Extensibility → override when necessary  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s closer to building blocks than a framework.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;example-use-case&quot;&gt;Example Use Case&lt;/h3&gt;
&lt;p&gt;For a typical Maven project, your pipeline usually boils down to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Run tests  &lt;/li&gt;
&lt;li&gt;Build artifacts  &lt;/li&gt;
&lt;li&gt;Publish packages  &lt;/li&gt;
&lt;li&gt;Create releases  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With reusable actions, you don’t redefine these steps every time — you just assemble them.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-not-just-copy-paste-&quot;&gt;Why Not Just Copy-Paste?&lt;/h3&gt;
&lt;p&gt;Because copy-paste doesn’t scale.&lt;/p&gt;
&lt;p&gt;It works for 2–3 repos.&lt;br&gt;
It breaks down at 10+.&lt;/p&gt;
&lt;p&gt;Suddenly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fixes don’t propagate  &lt;/li&gt;
&lt;li&gt;Pipelines drift  &lt;/li&gt;
&lt;li&gt;Debugging becomes inconsistent  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Reusable actions solve that by centralizing the logic.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;still-evolving&quot;&gt;Still Evolving&lt;/h3&gt;
&lt;p&gt;This isn’t meant to be a perfect CI/CD solution.&lt;/p&gt;
&lt;p&gt;It’s a practical attempt to reduce the friction I kept running into across projects.&lt;/p&gt;
&lt;p&gt;If you’re dealing with the same repetition in GitHub Actions, this might be useful:&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/czetsuyatech/nerv-actions&quot;&gt;https://github.com/czetsuyatech/nerv-actions&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;curious-about-your-setup&quot;&gt;Curious About Your Setup&lt;/h3&gt;
&lt;p&gt;Are you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;copying workflows between repos?  &lt;/li&gt;
&lt;li&gt;using reusable workflows heavily?  &lt;/li&gt;
&lt;li&gt;or building your own internal action libraries?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Would be interesting to hear how others are handling this.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/16030/stop-rewriting-ci-cd-reusable-github-actions-for-maven-projects</guid>
<pubDate>Tue, 28 Apr 2026 01:40:16 +0000</pubDate>
</item>
<item>
<title>Weather Service Project (Part 1): Building the Data Collector (Python, GitHub Actions or Netlify)</title>
<link>https://coderlegion.com/10040/weather-service-project-part-building-the-data-collector-python-github-actions-netlify</link>
<description>&lt;p&gt;As I mentioned in a previous post, one of my goals with Datalaria is to get my hands dirty with projects that allow me to learn and connect different technologies in the data world. Today, we begin a series dedicated to one of those projects: the creation of a &lt;strong&gt;complete global weather service&lt;/strong&gt;, from data collection to visualization and prediction, all serverless and using free tools.&lt;/p&gt;
&lt;p&gt;In this first installment, we will focus on the &lt;strong&gt;heart of the system: the backend data collector&lt;/strong&gt;. We'll see how to build a &quot;robot&quot; that works for us 24/7, connecting to an external API, saving structured information, and doing all this automatically and for free. Let's dive in!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://datalaria.com/en/posts/app_openweather_part1_backend/AI_App_Weather_Image.png&quot; alt=&quot;Conceptual image of Weather Service&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-first-step-talking-to-the-openweathermap-api&quot;&gt;The First Step: Talking to the OpenWeatherMap API&lt;/h4&gt;
&lt;p&gt;Every weather service needs a data source. I chose &lt;a rel=&quot;nofollow&quot; href=&quot;https://openweathermap.org/&quot;&gt;OpenWeatherMap&lt;/a&gt; for its popularity and generous free plan. The initial process is straightforward:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Register&lt;/strong&gt;: Create an account on their website.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Get the API Key&lt;/strong&gt;: Generate a unique key that will identify us in each call. It's like our &quot;key&quot; to access their data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Store the Key&lt;/strong&gt;: &lt;strong&gt;Never&lt;/strong&gt; directly in the code! We'll discuss this further below.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With the key in hand (or almost!), I wrote a first &lt;code&gt;test_clima.py&lt;/code&gt; script to test the connection using Python's fantastic &lt;code&gt;requests&lt;/code&gt; library:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import requests

API_KEY = &quot;YOUR_API_KEY_HERE&quot; # Temporarily! We'll use Secrets later
CITY = &quot;Madrid&quot;
URL = f&quot;[https://api.openweathermap.org/data/2.5/weather?q=](https://api.openweathermap.org/data/2.5/weather?q=){CITY}&amp;amp;appid={API_KEY}&amp;amp;units=metric&amp;amp;lang=es&quot;

try:
    response = requests.get(URL)
    response.raise_for_status() # Raises an exception for HTTP errors (4xx or 5xx)
    data = response.json()
    print(f&quot;Temperature in {CITY}: {data['main']['temp']}°C&quot;)
except requests.exceptions.RequestException as e:
    print(f&quot;Error connecting to the API: {e}&quot;)
except KeyError as e:
    print(f&quot;Unexpected API response, key missing: {e}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;First Obstacle Overcome (with Patience):&lt;/strong&gt; When I first ran it, I got a 401 Unauthorized error!  It turns out that OpenWeatherMap API Keys can take a few hours to activate after being generated. The lesson: sometimes, the solution is simply to wait. ⏳&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-database-why-csv-and-not-sql-&quot;&gt;The &quot;Database&quot;: Why CSV and Not SQL?&lt;/h4&gt;
&lt;p&gt;With data flowing, I needed to store it. I could have set up an SQL database (PostgreSQL, MySQL...), but that would involve complexity, a server (cost), and for this project, it was overkill.&lt;/p&gt;
&lt;p&gt;I opted for radical simplicity: &lt;strong&gt;CSV (Comma Separated Values) files&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Advantages&lt;/strong&gt;: Easy to read and write with Python, perfectly versionable with Git (we can track changes), and sufficient for the initial data volume we'd be handling.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Key Logic&lt;/strong&gt;: I needed to append a new row to each city's file daily, but only write the header (&lt;code&gt;date_time&lt;/code&gt;, &lt;code&gt;city&lt;/code&gt;, &lt;code&gt;temperature_c&lt;/code&gt;, etc.) the first time. Python's native &lt;code&gt;csv&lt;/code&gt; library and &lt;code&gt;os.path.exists&lt;/code&gt; make this trivial:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import csv
import os
from datetime import datetime

# ... (code to fetch API data for a city) ...

now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
data_row = [now, city, temperature, ...] # List with the data
header = ['date_time', 'city', 'temperature_c', ...] # List with column names
file_name = f&quot;data/{city}.csv&quot; # We'll create a 'data' folder

# Ensure the 'data' folder exists
os.makedirs(os.path.dirname(file_name), exist_ok=True)

is_new_file = not os.path.exists(file_name)

try:
    with open(file_name, mode='a', newline='', encoding='utf-8') as f:
        writer = csv.writer(f)
        if is_new_file:
            writer.writerow(header) # Write header ONLY if new file
        writer.writerow(data_row) # Append the new data row
    print(f&quot;Data saved for {city}&quot;)
except IOError as e:
    print(f&quot;Error writing to {file_name}: {e}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-automation-robot-github-actions-to-the-rescue&quot;&gt;The Automation Robot: GitHub Actions to the Rescue&lt;/h4&gt;
&lt;p&gt;Here comes the magic: how to make this script run daily without having a server constantly on? The answer is &lt;strong&gt;GitHub Actions&lt;/strong&gt;, the automation engine integrated into GitHub. It's like having a small robot working for us for free.&lt;/p&gt;
&lt;h5 id=&quot;security-first-never-upload-your-api-key-&quot;&gt;Security First: Never Upload Your API Key!&lt;/h5&gt;
&lt;p&gt;The biggest mistake would be to upload &lt;code&gt;registrar_clima.py&lt;/code&gt; with the &lt;code&gt;API_KEY&lt;/code&gt; written directly in the code. Anyone could see it on GitHub.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Solution&lt;/strong&gt;: Use GitHub's &lt;strong&gt;Repository Secrets&lt;/strong&gt;.&lt;ol&gt;
&lt;li&gt;Go to &lt;code&gt;Settings &amp;gt; Secrets and variables &amp;gt; Actions&lt;/code&gt; in your GitHub repository.&lt;/li&gt;
&lt;li&gt;Create a new secret named &lt;code&gt;OPENWEATHER_API_KEY&lt;/code&gt; and paste your key there.&lt;/li&gt;
&lt;li&gt;In your Python script, read the key securely using &lt;code&gt;os.environ.get(&quot;OPENWEATHER_API_KEY&quot;)&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;the-robot-s-brain-the-github-workflows-update-weather-yml-file&quot;&gt;The Robot's Brain: The &lt;code&gt;.github/workflows/update-weather.yml&lt;/code&gt; File&lt;/h5&gt;
&lt;p&gt;This YAML file tells GitHub Actions what to do and when:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: Daily Weather Data Update

on:
  workflow_dispatch: # Allows manual triggering from GitHub
  push:
    branches: [ main ] # Triggers if changes are pushed to the main branch
  schedule:
    - cron: '0 6 * * *' # The key: triggers daily at 06:00 UTC

jobs:
  update_data:
    runs-on: ubuntu-latest # Use a free Linux virtual machine
    steps:
      - name: Checkout repository code
        uses: actions/checkout@v4 # Downloads our code

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.10' # Or your preferred version

      - name: Install necessary dependencies
        run: pip install -r requirements.txt # Reads requirements.txt and installs requests, etc.

      - name: Execute data collection script
        run: python registrar_clima.py # Our main script!
        env:
          OPENWEATHER_API_KEY: ${{ secrets.OPENWEATHER_API_KEY }} # Securely injects the secret

      - name: Save new data to repository (Commit &amp;amp; Push)
        run: |
          git config user.name 'github-actions[bot]' # Identifies the 'bot'
          git config user.email 'github-actions[bot]@users.noreply.github.com'
          git add data/*.csv # Adds ONLY the modified CSV files in the 'data' folder
          # Check if there are changes before committing to avoid empty commits
          git diff --staged --quiet || git commit -m &quot;Automated weather data update &quot;
          git push # Pushes changes to the repository
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Automatic token to allow the push
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;This last step is crucial!&lt;/strong&gt; The Action itself acts as a user, performing &lt;code&gt;git add&lt;/code&gt;, &lt;code&gt;git commit&lt;/code&gt;, and &lt;code&gt;git push&lt;/code&gt; of the CSV files that the Python script has just modified. This way, the updated data is saved in our repository every day.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-serverless-alternative-deployment-and-automation-with-netlify&quot;&gt;The Serverless Alternative: Deployment and Automation with Netlify&lt;/h4&gt;
&lt;p&gt;While GitHub Actions is a fantastic automation tool, for this project I decided to explore an alternative even more integrated with the &quot;serverless&quot; concept: &lt;strong&gt;Netlify&lt;/strong&gt;. Netlify not only allows us to deploy our static frontend (like GitHub Pages) but also offers serverless functions and, crucially for our backend, &lt;strong&gt;scheduled functions (or Cron Jobs)&lt;/strong&gt;.&lt;/p&gt;
&lt;h5 id=&quot;deploying-the-static-frontend-with-netlify&quot;&gt;Deploying the Static Frontend with Netlify&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Connect Your Repository&lt;/strong&gt;: The process is incredibly simple. Log in to Netlify, click &quot;Add new site,&quot; and select &quot;Import an existing project.&quot; Connect with your GitHub account and choose your Weather Service project repository.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Basic Configuration&lt;/strong&gt;: Netlify will automatically detect your project. Ensure that the &quot;Build command&quot; is empty (as it's a static site with no build process) and that the &quot;Publish directory&quot; is the root of your repository (&lt;code&gt;./&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Continuous Deployment&lt;/strong&gt;: Netlify will automatically configure continuous deployment. Every time you &lt;code&gt;git push&lt;/code&gt; to your &lt;code&gt;main&lt;/code&gt; branch (or whichever branch you've configured), Netlify will rebuild and deploy your site.&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id=&quot;automating-the-backend-with-netlify-functions-and-cron-jobs-&quot;&gt;Automating the Backend with Netlify Functions (and Cron Jobs)&lt;/h5&gt;
&lt;p&gt;This is where Netlify Serverless Functions shine for our data collector. Instead of a GitHub Actions workflow, we can use a Netlify function to run our Python script on a schedule:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Project Structure&lt;/strong&gt;: Create a &lt;code&gt;netlify/functions/&lt;/code&gt; folder at the root of your project. Inside, you can have a Python file like &lt;code&gt;collect_weather.py&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dependency Management&lt;/strong&gt;: You'll need a &lt;code&gt;requirements.txt&lt;/code&gt; file at the root of your project for Netlify to install Python dependencies (&lt;code&gt;requests&lt;/code&gt;, &lt;code&gt;pandas&lt;/code&gt;, &lt;code&gt;scikit-learn&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;netlify.toml&lt;/code&gt; Configuration&lt;/strong&gt;: This file at your project's root is crucial for defining your functions and their schedules:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;[build]
  publish = &quot;.&quot; # Directory where your index.html is located
  command = &quot;&quot; # No build command needed for a static site

[functions]
  directory = &quot;netlify/functions&quot; # Where your functions are located
  node_bundler = &quot;esbuild&quot; # For JS/TS functions. Netlify will detect Python.

[[edge_functions]] # For scheduling a function (requires Netlify Edge Functions)
  function = &quot;collect_weather&quot; # The name of your function (without the .py extension)
  path = &quot;/.netlify/functions/collect_weather&quot; # The function path (can be different)
  schedule = &quot;@daily&quot; # Or use a cron string like &quot;0 6 * * *&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Python Function (&lt;code&gt;netlify/functions/collect_weather.py&lt;/code&gt;)&lt;/strong&gt;: This function will encapsulate the logic of your &lt;code&gt;registrar_clima.py&lt;/code&gt;. Netlify will execute it in a Python environment.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# netlify/functions/collect_weather.py
import json
import requests
import os
import time
from datetime import datetime
import csv

# ... (all your registrar_clima.py script code goes here) ...

def handler(event, context):
    # The main call to your data collection logic would go here
    # This is a simplified example
    try:
        # Your logic to fetch and save data, generate CSVs/JSONs
            
        # For this project, the GitHub Actions approach for the backend
        # that directly commits to the repo is still simpler
        # for CSV storage. Netlify would be ideal for the frontend
        # and functions for real-time APIs or lightweight predictions.

        print(&quot;Netlify function for weather collection executed.&quot;)
        return {
            &quot;statusCode&quot;: 200,
            &quot;body&quot;: json.dumps({&quot;message&quot;: &quot;Backend logic would run here.&quot;}),
        }
    except Exception as e:
        return {
            &quot;statusCode&quot;: 500,
            &quot;body&quot;: json.dumps({&quot;error&quot;: str(e)}),
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Environment Variables in Netlify&lt;/strong&gt;: For the &lt;code&gt;OPENWEATHER_API_KEY&lt;/code&gt;, go to &lt;code&gt;Site settings &amp;gt; Build &amp;amp; deploy &amp;gt; Environment variables&lt;/code&gt; and add your key there.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Important Consideration&lt;/strong&gt;: For the Netlify function to persist changes directly to your GitHub repository (like committing the CSVs), you would need a more advanced setup. To maintain simplicity and direct storage in the Git repository with automatic CSV commits, the &lt;strong&gt;GitHub Actions solution remains the most straightforward and efficient for the data collector backend in this specific case&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-scaling-problem-and-the-necessary-architectural-pivot-&quot;&gt;The Scaling Problem (and the Necessary Architectural Pivot)&lt;/h4&gt;
&lt;p&gt;My initial idea was to monitor about 1000 cities and store everything in a single &lt;code&gt;weather_data.csv&lt;/code&gt; file. I did a quick calculation: 1000 cities &lt;em&gt; ~200 bytes/day &lt;/em&gt; 365 days * 3 years... over 200 MB! &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why is this a problem?&lt;/strong&gt; Because the frontend (our dashboard, which we'll see in the next post) runs in the user's browser. It would have to download that &lt;em&gt;entire&lt;/em&gt; 200 MB just to display the graph for &lt;em&gt;one&lt;/em&gt; city. Totally unacceptable in terms of performance. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Architectural Solution:&lt;/strong&gt; Switch to a &lt;strong&gt;&quot;one file per entity&quot;&lt;/strong&gt; strategy.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We create a &lt;code&gt;data/&lt;/code&gt; folder.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;registrar_clima.py&lt;/code&gt; script now generates (or appends data to) one CSV file per city: &lt;code&gt;data/Madrid.csv&lt;/code&gt;, &lt;code&gt;data/Leon.csv&lt;/code&gt;, &lt;code&gt;data/Tokyo.csv&lt;/code&gt;, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This way, when the user wants to see the weather for Leon, the frontend will only download the &lt;code&gt;data/Leon.csv&lt;/code&gt; file, which will be just a few kilobytes. Instant loading! ✨&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Second Scaling Obstacle (API Limits):&lt;/strong&gt; OpenWeatherMap, in its free plan, allows about 60 calls per minute. My loop to get data for 155 cities (my current list) would make these calls too quickly.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Vital Solution:&lt;/strong&gt; Add &lt;code&gt;import time&lt;/code&gt; at the beginning of the Python script and &lt;code&gt;time.sleep(1.1)&lt;/code&gt; at the end of the &lt;code&gt;for city in cities:&lt;/code&gt; loop. This introduces a pause of slightly more than 1 second between each API call, ensuring we stay below the limit and avoid being blocked. &lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;conclusion-part-1-&quot;&gt;Conclusion (Part 1)&lt;/h4&gt;
&lt;p&gt;We've got the foundation! We've built a robust and automated system that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Connects to an external API securely.&lt;/li&gt;
&lt;li&gt;Processes and stores historical data for multiple entities (cities).&lt;/li&gt;
&lt;li&gt;Runs daily, at no cost, thanks to GitHub Actions.&lt;/li&gt;
&lt;li&gt;Is designed to scale efficiently.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the next post, we'll put on our frontend developer hats and build the interactive dashboard that will allow any user to explore this data with dynamic graphs. Don't miss it!&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;references-and-links-of-interest-&quot;&gt;References and Links of Interest:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Complete Web Service&lt;/strong&gt;: See the live project in action here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/apps/weather/&quot;&gt;https://datalaria.com/apps/weather/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Project GitHub Repository&lt;/strong&gt;: Explore the source code and project structure: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Dalaez/app_weather&quot;&gt;https://github.com/Dalaez/app_weather&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenWeatherMap&lt;/strong&gt;: Weather API documentation: &lt;a rel=&quot;nofollow&quot; href=&quot;https://openweathermap.org/api&quot;&gt;https://openweathermap.org/api&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Python Requests&lt;/strong&gt;: Documentation for the HTTP requests library: &lt;a rel=&quot;nofollow&quot; href=&quot;https://requests.readthedocs.io/en/master/&quot;&gt;https://requests.readthedocs.io/en/master/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Actions&lt;/strong&gt;: Official GitHub Actions guide: &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.github.com/en/actions&quot;&gt;https://docs.github.com/en/actions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Netlify&lt;/strong&gt;: Official Netlify website: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.netlify.com/&quot;&gt;https://www.netlify.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/10040/weather-service-project-part-building-the-data-collector-python-github-actions-netlify</guid>
<pubDate>Tue, 20 Jan 2026 06:48:57 +0000</pubDate>
</item>
<item>
<title>Hello World to GitHub Actions: Your First Automated Workflow</title>
<link>https://coderlegion.com/8878/hello-world-to-github-actions-your-first-automated-workflow</link>
<description>&lt;p&gt;Hey there! So you're building a React app with TypeScript and Vite, and you keep hearing people talk about CI/CD and GitHub Actions. What's all the fuss about? Let me break it down for you in plain English.&lt;/p&gt;
&lt;h3 id=&quot;what-s-github-actions-anyway-&quot;&gt;What's GitHub Actions Anyway?&lt;/h3&gt;
&lt;p&gt;Think of GitHub Actions as your automation buddy that lives in your GitHub repo. You tell it &quot;hey, whenever I push code, run my tests and make sure nothing broke,&quot; and it just... does it. Automatically. While you grab coffee(or chai) or work on something else.&lt;/p&gt;
&lt;p&gt;Pretty cool, right? Here's what makes it awesome:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It's automatic&lt;/strong&gt; - No more remembering to run tests before you push. Your automation buddy handles it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It's consistent&lt;/strong&gt; - Everyone's code gets tested the same way, every single time. No more &quot;but it works on my machine!&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It's free&lt;/strong&gt; - GitHub gives you a generous amount of free minutes every month. For most personal projects, you won't pay a dime.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It's built-in&lt;/strong&gt; - Everything happens right in GitHub. No need to sign up for yet another service.&lt;/p&gt;
&lt;h3 id=&quot;the-basics-don-t-worry-it-s-simple-&quot;&gt;The Basics (Don't Worry, It's Simple!)&lt;/h3&gt;
&lt;p&gt;Before we jump into code, let's get familiar with a few terms:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Workflows&lt;/strong&gt; are like recipes. They're files that tell GitHub Actions what to do. You write them in a language called YAML (it's just a fancy text format, nothing scary).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Events&lt;/strong&gt; are triggers. &quot;When someone pushes code&quot; or &quot;when someone opens a pull request&quot; - these are events that kick off your workflow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Jobs&lt;/strong&gt; are the big tasks in your recipe. Like &quot;build the app&quot; or &quot;run tests.&quot; Each job runs on its own fresh computer in the cloud.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Steps&lt;/strong&gt; are the small tasks inside a job. Things like &quot;install dependencies&quot; or &quot;run the linter.&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Actions&lt;/strong&gt; are pre-made shortcuts. Instead of writing commands to set up Node.js from scratch, you can use an action someone else already made. It's like using a package from npm, but for your workflows.&lt;/p&gt;
&lt;h3 id=&quot;let-s-build-your-first-workflow-&quot;&gt;Let's Build Your First Workflow!&lt;/h3&gt;
&lt;p&gt;Alright, enough theory. Let's make something real. We're going to create a workflow that checks your code every time you push or open a pull request.&lt;/p&gt;
&lt;h4 id=&quot;creating-the-file&quot;&gt;Creating the File&lt;/h4&gt;
&lt;p&gt;First, make a new folder in your project: &lt;code&gt;.github/workflows/&lt;/code&gt;. Yes, it starts with a dot - that's on purpose!&lt;/p&gt;
&lt;p&gt;Inside that folder, create a file called &lt;code&gt;ci.yml&lt;/code&gt;. Here's what goes in it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name: CI Pipeline

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]

permissions:
  contents: read

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run linter
        run: npm run lint
      
      - name: Type check
        run: npm run type-check
      
      - name: Build project
        run: npm run build
      
      - name: Run tests
        run: npm test
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;what-s-all-this-mean-&quot;&gt;What's All This Mean?&lt;/h4&gt;
&lt;p&gt;Let me walk you through it:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;name: CI Pipeline&lt;/strong&gt; - Just a friendly name for your workflow. You'll see it in the GitHub interface.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;on:&lt;/strong&gt; - This is where you say &quot;run this when...&quot; In our case, we're saying &quot;run when someone pushes to main or develop, OR when someone opens a pull request.&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;permissions:&lt;/strong&gt; - This is important! We're telling GitHub &quot;hey, this workflow only needs permission to read our code, nothing else.&quot; It's like giving someone a library card instead of the keys to the building. Good security practice!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;jobs:&lt;/strong&gt; - Here's where the actual work happens. We've got one job called &quot;build-and-test.&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;runs-on: ubuntu-latest&lt;/strong&gt; - This means &quot;run on a computer with Ubuntu Linux.&quot; GitHub provides this for free!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;steps:&lt;/strong&gt; - These are the actual commands that run, one after another:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First, we grab the code&lt;/li&gt;
&lt;li&gt;Then we install Node.js&lt;/li&gt;
&lt;li&gt;Install our dependencies&lt;/li&gt;
&lt;li&gt;Run the linter to check code style&lt;/li&gt;
&lt;li&gt;Check for TypeScript errors&lt;/li&gt;
&lt;li&gt;Build the project&lt;/li&gt;
&lt;li&gt;Run our tests&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;setting-up-your-package-json&quot;&gt;Setting Up Your package.json&lt;/h4&gt;
&lt;p&gt;Your &lt;code&gt;package.json&lt;/code&gt; needs to have these scripts for the workflow to work:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;scripts&quot;: {
    &quot;dev&quot;: &quot;vite&quot;,
    &quot;build&quot;: &quot;tsc &amp;amp;&amp;amp; vite build&quot;,
    &quot;lint&quot;: &quot;eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0&quot;,
    &quot;type-check&quot;: &quot;tsc --noEmit&quot;,
    &quot;test&quot;: &quot;vitest run&quot;,
    &quot;preview&quot;: &quot;vite preview&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Don't have ESLint or Vitest set up yet? No worries - you can skip those steps in your workflow for now and add them later.&lt;/p&gt;
&lt;h3 id=&quot;want-to-deploy-automatically-too-&quot;&gt;Want to Deploy Automatically Too?&lt;/h3&gt;
&lt;p&gt;Once your tests pass, you might want to deploy your app. Here's a workflow that deploys to GitHub Pages:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name: Deploy to GitHub Pages

on:
  push:
    branches: [ main ]

permissions:
  contents: read
  pages: write
  id-token: write

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build project
        run: npm run build
      
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: ./dist
  
  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This one's a bit different - it has TWO jobs. The second job (deploy) waits for the first one (build) to finish successfully. That &lt;code&gt;needs: build&lt;/code&gt; line is what makes that happen.&lt;/p&gt;
&lt;h3 id=&quot;making-it-faster-with-caching&quot;&gt;Making It Faster with Caching&lt;/h3&gt;
&lt;p&gt;Want your workflows to run faster? Good news - the &lt;code&gt;actions/setup-node@v4&lt;/code&gt; action we're using already caches your npm packages when you add &lt;code&gt;cache: 'npm'&lt;/code&gt;. That means the second time your workflow runs, it won't have to download all your dependencies again. Pretty smart!&lt;/p&gt;
&lt;h3 id=&quot;keeping-secrets-secret&quot;&gt;Keeping Secrets... Secret&lt;/h3&gt;
&lt;p&gt;Sometimes you need API keys or passwords in your builds. DON'T put them directly in your workflow file - that's like posting your house keys on Instagram.&lt;/p&gt;
&lt;p&gt;Instead, use GitHub's secrets feature:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to your repo's Settings&lt;/li&gt;
&lt;li&gt;Click on &quot;Secrets and variables&quot; then &quot;Actions&quot;&lt;/li&gt;
&lt;li&gt;Click &quot;New repository secret&quot;&lt;/li&gt;
&lt;li&gt;Add your secret (like &lt;code&gt;VITE_API_KEY&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then use it in your workflow like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Build project
  run: npm run build
  env:
    VITE_API_KEY: ${{ secrets.VITE_API_KEY }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;GitHub will hide it in the logs automatically. Nice!&lt;/p&gt;
&lt;h3 id=&quot;checking-if-it-s-working&quot;&gt;Checking If It's Working&lt;/h3&gt;
&lt;p&gt;After you push your workflow file to GitHub:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Click the &quot;Actions&quot; tab in your repo&lt;/li&gt;
&lt;li&gt;You'll see your workflow running (or done running)&lt;/li&gt;
&lt;li&gt;Click on it to see all the details&lt;/li&gt;
&lt;li&gt;If something breaks, click on the red X to see what went wrong&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The logs tell you EXACTLY what happened at each step. It's super helpful for debugging.&lt;/p&gt;
&lt;h3 id=&quot;understanding-workflow-permissions&quot;&gt;Understanding Workflow Permissions&lt;/h3&gt;
&lt;p&gt;Quick important thing - see that &lt;code&gt;permissions&lt;/code&gt; block we added? That's like setting up proper door locks for your house. &lt;/p&gt;
&lt;p&gt;By default, workflows might have way more permissions than they need. We're being explicit and saying &quot;this workflow only needs to READ the code&quot; with &lt;code&gt;contents: read&lt;/code&gt;. If later you need to do something like comment on pull requests, you'd add &lt;code&gt;pull-requests: write&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It's just a good habit to be clear about what your workflow can and can't do. Your future self (and your team) will thank you!&lt;/p&gt;
&lt;h3 id=&quot;some-quick-tips&quot;&gt;Some Quick Tips&lt;/h3&gt;
&lt;p&gt;Here are a few things that'll make your life easier:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Set permissions explicitly&lt;/strong&gt; - Like we just talked about, always add that &lt;code&gt;permissions&lt;/code&gt; block. Start with &lt;code&gt;contents: read&lt;/code&gt; and only add more when you need it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use version tags&lt;/strong&gt; - Notice how we use &lt;code&gt;@v4&lt;/code&gt; in our actions? That's better than using &lt;code&gt;@main&lt;/code&gt; because it won't suddenly change on you. Your workflows stay stable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;npm ci&lt;/code&gt; not &lt;code&gt;npm install&lt;/code&gt;&lt;/strong&gt; - In automated environments, &lt;code&gt;npm ci&lt;/code&gt; is faster and more reliable. It's designed specifically for CI/CD.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Run jobs in parallel&lt;/strong&gt; - If you have multiple jobs that don't depend on each other, they'll run at the same time. Saves time!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Add a badge to your README&lt;/strong&gt; - In the Actions tab, you can grab a badge that shows if your build is passing. It looks professional!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Test different Node versions&lt;/strong&gt; - You can use something called a matrix to test on Node 18, 20, and 21 all at once. Here's how:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;strategy:
  matrix:
    node-version: [18, 20, 21]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Protect your main branch&lt;/strong&gt; - In your repo settings, you can require that all checks pass before merging. No more broken builds!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Separate your workflows&lt;/strong&gt; - Keep your testing workflow separate from your deployment workflow. Makes things easier to understand and manage.&lt;/p&gt;
&lt;h3 id=&quot;when-things-go-wrong&quot;&gt;When Things Go Wrong&lt;/h3&gt;
&lt;p&gt;Don't panic! Here are common issues and fixes:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Workflow not running?&lt;/strong&gt; - Check that your YAML file is in &lt;code&gt;.github/workflows/&lt;/code&gt; and that you don't have any syntax errors. YAML is picky about spaces and indentation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Works on your computer but not in CI?&lt;/strong&gt; - Make sure all your dependencies are listed in &lt;code&gt;package.json&lt;/code&gt;. Also check that you're not relying on environment variables that only exist on your machine.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Running out of build minutes?&lt;/strong&gt; - Public repos get unlimited minutes! Private repos have limits. Check your usage in Settings → Billing.&lt;/p&gt;
&lt;h3 id=&quot;what-s-next-&quot;&gt;What's Next?&lt;/h3&gt;
&lt;p&gt;You now know the basics! Here are some cool things to explore when you're ready:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Browse the GitHub Marketplace for actions other people have made&lt;/li&gt;
&lt;li&gt;Set up notifications to Slack or Discord when builds fail&lt;/li&gt;
&lt;li&gt;Add code coverage reporting&lt;/li&gt;
&lt;li&gt;Create custom actions for your specific needs&lt;/li&gt;
&lt;li&gt;Try matrix builds to test across multiple environments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key is to start simple - get your basic testing pipeline working - then add more features as you need them. Don't try to do everything at once!&lt;/p&gt;
&lt;h3 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h3&gt;
&lt;p&gt;GitHub Actions might seem a bit intimidating at first, but once you get the hang of it, you'll wonder how you lived without it. No more forgetting to run tests. No more &quot;oops, I broke main.&quot; Just push your code and let your automation buddy do its thing.&lt;/p&gt;
&lt;p&gt;Start with the simple CI workflow we built together, make sure it works, and then gradually add more automation as you get comfortable. Before you know it, you'll be the GitHub Actions expert on your team!&lt;/p&gt;
&lt;p&gt;Happy automating! &lt;/p&gt;
</description>
<category>GitHub Actions</category>
<guid isPermaLink="true">https://coderlegion.com/8878/hello-world-to-github-actions-your-first-automated-workflow</guid>
<pubDate>Thu, 25 Dec 2025 06:23:00 +0000</pubDate>
</item>
<item>
<title>CI/CD That Ships Itself: Our GitHub Actions Blueprint</title>
<link>https://coderlegion.com/8322/ci-cd-that-ships-itself-our-github-actions-blueprint</link>
<description>&lt;h2 id=&quot;ci-cd-that-ships-itself-our-github-actions-blueprint&quot;&gt;CI/CD That Ships Itself: Our GitHub Actions Blueprint&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;(Pocket Portfolio — 12 Weeks of Shipping)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Fast releases are only useful if they are consistent and safe.&lt;br&gt;
This post breaks down Pocket Portfolio’s CI/CD pipeline on GitHub Actions: how we lint, type-check, test against Firebase emulators, run Playwright smoke tests, and then canary-deploy to the Edge.&lt;/p&gt;
&lt;p&gt;The goal is simple: every commit proves it can ship itself.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;pipeline-overview&quot;&gt;Pipeline Overview&lt;/h3&gt;
&lt;p&gt;Stages executed per pull request and on main:&lt;/p&gt;
&lt;p&gt;1) Lint and TypeCheck&lt;br&gt;
2) Build with reproducible caching&lt;br&gt;
3) Firebase emulator tests (unit and integration)&lt;br&gt;
4) Playwright UI smoke tests&lt;br&gt;
5) Artifact capture (build, coverage, screenshots)&lt;br&gt;
6) Canary deploy to &lt;code&gt;next.pocketportfolio.app&lt;/code&gt; with manual approval&lt;br&gt;
7) Post-deploy health checks and auto-rollback&lt;/p&gt;
&lt;p&gt;We use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub Actions matrix to cover Node versions and Next.js runtime modes  &lt;/li&gt;
&lt;li&gt;Project-level concurrency to avoid clobbering environments  &lt;/li&gt;
&lt;li&gt;Required checks enforced by branch protection rules&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;repository-structure-relevant-parts-&quot;&gt;Repository Structure (relevant parts)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;
.github/
workflows/
ci.yml
deploy-canary.yml
apps/
web/                  # Next.js 14 app
packages/
ui/                   # shared components
config/               # ESLint, TS config
tests/
e2e/                  # Playwright specs
seeds/                # Firebase test data

&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;workflow-1-ci-pr-and-main-&quot;&gt;Workflow 1: CI (PR and main)&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;./github/workflows/ci.yml&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: CI

on:
  pull_request:
    branches: [ main ]
  push:
    branches: [ main ]

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

jobs:
  setup:
    name: Setup
    runs-on: ubuntu-latest
    outputs:
      node: ${{ steps.matrix.outputs.node }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - uses: pnpm/action-setup@v4
        with:
          version: 9
      - name: Install
        run: pnpm install --frozen-lockfile

  lint_typecheck:
    name: Lint and TypeCheck
    needs: setup
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - uses: pnpm/action-setup@v4
        with:
          version: 9
      - run: pnpm install --frozen-lockfile
      - run: pnpm eslint .
      - run: pnpm tsc -p tsconfig.json --noEmit

  build:
    name: Build
    needs: [setup, lint_typecheck]
    runs-on: ubuntu-latest
    env:
      NEXT_TELEMETRY_DISABLED: 1
      NODE_ENV: production
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - uses: pnpm/action-setup@v4
        with:
          version: 9
      - run: pnpm install --frozen-lockfile
      - name: Build web
        run: pnpm --filter ./apps/web build
      - name: Upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: web-build
          path: apps/web/.next

  test_emulators:
    name: Firebase Emulator Tests
    needs: build
    runs-on: ubuntu-latest
    services:
      firestore:
        image: docker.io/bitnami/firebase-emulator:latest
        ports:
          - 4000:4000
          - 8080:8080
          - 9099:9099
          - 9199:9199
    env:
      FIREBASE_AUTH_EMULATOR_HOST: localhost:9099
      FIRESTORE_EMULATOR_HOST: localhost:8080
      GCLOUD_PROJECT: test-project
      NODE_ENV: test
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - uses: pnpm/action-setup@v4
        with:
          version: 9
      - run: pnpm install --frozen-lockfile
      - name: Seed test data
        run: pnpm tsx tests/seeds/seed.ts
      - name: Unit and integration tests
        run: pnpm test -- --reporter=junit --coverage
      - name: Upload coverage
        uses: actions/upload-artifact@v4
        with:
          name: coverage
          path: coverage

  e2e_smoke:
    name: Playwright Smoke (headless)
    needs: test_emulators
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - uses: pnpm/action-setup@v4
        with:
          version: 9
      - run: pnpm install --frozen-lockfile
      - name: Install browsers
        run: pnpm exec playwright install --with-deps
      - name: Run smoke tests
        env:
          BASE_URL: http://localhost:3000
        run: |
          pnpm --filter ./apps/web dev &amp;amp; sleep 8
          pnpm exec playwright test tests/e2e/smoke.spec.ts --reporter=line
      - name: Upload Playwright artifacts
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: playwright-artifacts
          path: |
            playwright-report
            test-results
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What this does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cancels superseded runs automatically&lt;/li&gt;
&lt;li&gt;Builds once and reuses artifacts&lt;/li&gt;
&lt;li&gt;Spins Firebase emulators for realistic tests&lt;/li&gt;
&lt;li&gt;Runs Playwright in headless mode against a local dev server&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;workflow-2-canary-deploy-to-edge&quot;&gt;Workflow 2: Canary Deploy to Edge&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;./github/workflows/deploy-canary.yml&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: Deploy Canary

on:
  workflow_run:
    workflows: [&quot;CI&quot;]
    types: [completed]
    branches: [ main ]

permissions:
  contents: read
  deployments: write
  id-token: write

concurrency:
  group: deploy-canary
  cancel-in-progress: false

jobs:
  gate:
    name: Require green CI
    runs-on: ubuntu-latest
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    steps:
      - run: echo &quot;CI passed, proceeding to deploy.&quot;

  deploy:
    name: Deploy to next.pocketportfolio.app
    needs: gate
    runs-on: ubuntu-latest
    environment:
      name: canary
      url: https://next.pocketportfolio.app
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - uses: pnpm/action-setup@v4
        with:
          version: 9
      - run: pnpm install --frozen-lockfile
      - name: Build
        run: pnpm --filter ./apps/web build
      - name: Vercel Deploy (Edge)
        uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          working-directory: ./apps/web
          scope: ${{ secrets.VERCEL_ORG_ID }}
          prod: false
      - name: Health check
        run: |
          curl -fsSL https://next.pocketportfolio.app/api/health || exit 1
          curl -fsSL https://next.pocketportfolio.app/api/health-price || exit 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;workflow_run&lt;/code&gt; ensures deploy runs only after CI succeeds on &lt;code&gt;main&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Environment protection rules can require manual approval inside GitHub before the &lt;code&gt;deploy&lt;/code&gt; job starts.&lt;/li&gt;
&lt;li&gt;We target Edge by configuring Vercel project functions to run on the Edge runtime.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;health-checks-and-auto-rollback&quot;&gt;Health Checks and Auto-Rollback&lt;/h3&gt;
&lt;p&gt;We keep deploys safe with a simple pattern:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;After deployment, curl health and quote endpoints&lt;/li&gt;
&lt;li&gt;If any return non-2xx, mark the job failed&lt;/li&gt;
&lt;li&gt;A failing canary keeps production untouched&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Optional rollback step (if you use Vercel Protected Branches):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;      - name: Roll back canary to previous deployment
        if: failure()
        run: |
          echo &quot;Rolling back...&quot;
          # Example: pin to previous successful alias or commit sha via Vercel CLI
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;speed-and-stability-techniques&quot;&gt;Speed and Stability Techniques&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Caching:&lt;/strong&gt; &lt;code&gt;actions/setup-node&lt;/code&gt; with &lt;code&gt;cache: pnpm&lt;/code&gt; cuts install time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Concurrency:&lt;/strong&gt; single deploy job at a time prevents environment drift.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Artifacts:&lt;/strong&gt; store build output and Playwright results for debugging.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Separate concerns:&lt;/strong&gt; CI proves code is safe; deploy workflow just ships.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Emulators:&lt;/strong&gt; Firebase emulator suite catches integration issues locally and in CI.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;failure-modes-we-handle&quot;&gt;Failure Modes We Handle&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Stuck third-party provider: Playwright tests stub provider responses for critical flows.&lt;/li&gt;
&lt;li&gt;Flaky UI timing: Playwright uses test id selectors and realistic waits.&lt;/li&gt;
&lt;li&gt;Race conditions: matrix and concurrency settings keep builds deterministic.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;minimal-smoke-spec-example&quot;&gt;Minimal Smoke Spec Example&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;tests/e2e/smoke.spec.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { test, expect } from '@playwright/test';

test('dashboard loads and shows health badge', async ({ page }) =&amp;gt; {
  await page.goto('/');
  await expect(page.getByTestId('health-badge')).toHaveText(/healthy|degraded/);
});

test('price endpoint responds', async ({ request }) =&amp;gt; {
  const res = await request.get('/api/health-price');
  expect(res.ok()).toBeTruthy();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-to-measure&quot;&gt;What To Measure&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;CI total duration (target: under 8 minutes for PRs)&lt;/li&gt;
&lt;li&gt;Flake rate (target: below 1 percent over 30 days)&lt;/li&gt;
&lt;li&gt;Canary failure rate and mean time to recovery&lt;/li&gt;
&lt;li&gt;Playwright runtime and retries per suite&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;assets&quot;&gt;Assets&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Workflow diagram (Setup -&amp;gt; Lint -&amp;gt; Build -&amp;gt; Emulators -&amp;gt; Playwright -&amp;gt; Canary Deploy)&lt;/li&gt;
&lt;li&gt;Screenshots of GitHub Checks and Environment approvals&lt;/li&gt;
&lt;li&gt;Example Playwright report&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;links&quot;&gt;Links&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Actions runs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/PocketPortfolio/Financialprofilenetwork/actions&quot;&gt;https://github.com/PocketPortfolio/Financialprofilenetwork/actions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;App: &lt;a rel=&quot;nofollow&quot; href=&quot;https://pocketportfolio.app&quot;&gt;https://pocketportfolio.app&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;tl-dr&quot;&gt;TL;DR&lt;/h3&gt;
&lt;p&gt;Every commit runs the same path production will run: build, test, verify, deploy, and prove it is healthy.&lt;br&gt;
If any step fails, the release stops itself. That is CI/CD that ships itself.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/8322/ci-cd-that-ships-itself-our-github-actions-blueprint</guid>
<pubDate>Tue, 09 Dec 2025 12:06:26 +0000</pubDate>
</item>
<item>
<title>From Code Push to Docker Hub: CI/CD with GitHub Actions</title>
<link>https://coderlegion.com/6405/from-code-push-to-docker-hub-ci-cd-with-github-actions</link>
<description>&lt;p&gt;Imagine you’re running a bakery. Every morning, you bring fresh ingredients (your new code). But instead of kneading, baking, packaging, and delivering each loaf by hand, you want a &lt;strong&gt;conveyor belt&lt;/strong&gt; that handles everything the moment the ingredients arrive.&lt;/p&gt;
&lt;p&gt;That conveyor belt is your &lt;strong&gt;CI/CD pipeline&lt;/strong&gt;.&lt;br&gt;
Each time you &lt;code&gt;git push&lt;/code&gt;, it builds, tests, packages, and ships your product automatically.&lt;/p&gt;
&lt;p&gt;In this article, we’ll create that conveyor belt for your Node.js app using &lt;code&gt;GitHub Actions and Docker&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=14288477006382423933&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;why-automate-docker-builds-&quot;&gt;Why Automate Docker Builds?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Why It Matters&lt;/strong&gt;&lt;br&gt;
Manual builds are slow, repetitive, and error-prone. Automation brings consistency and peace of mind.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Explain Like a Friend&lt;/strong&gt;&lt;br&gt;
Think of manually assembling furniture for every order — you’ll eventually forget screws or swap panels. A factory line ensures the same result every single time. CI/CD is that factory line for software.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real-World Example&lt;/strong&gt;&lt;br&gt;
A small dev team pushes hotfixes daily. Without automation, they rebuild locally, tag manually, and sometimes push to the wrong repo. With GitHub Actions, every push triggers a clean, predictable build → tag → push pipeline.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Action Step&lt;/strong&gt;&lt;br&gt;
Write down every manual command you currently run to deploy your Docker image. That’s your “to-automate” checklist.&lt;/p&gt;
&lt;h3 id=&quot;prerequisites-accounts-secrets-amp-permissions&quot;&gt;Prerequisites: Accounts, Secrets &amp;amp; Permissions&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Why It Matters&lt;/strong&gt;&lt;br&gt;
Your automation can’t log in or deploy if it doesn’t have the right keys.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Explanation&lt;/strong&gt;&lt;br&gt;
Think of GitHub and Docker Hub as two warehouses. Your CI/CD robot needs secure keys to both: one to collect the code, the other to store the built product (your image).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real-World Example&lt;/strong&gt;&lt;br&gt;
In the demo, the author logs into Docker Hub using encrypted secrets:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DOCKER_HUB_USERNAME&lt;/li&gt;
&lt;li&gt;DOCKER_HUB_TOKEN&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These secrets are stored safely in &lt;strong&gt;GitHub → Settings → Secrets → Actions&lt;/strong&gt; never in your source code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Action Steps&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create or log into your Docker Hub account.&lt;/li&gt;
&lt;li&gt;Generate a personal access token (instead of using your password).&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In GitHub, go to Settings → Secrets → Actions, and add:&lt;/p&gt;
&lt;p&gt;DOCKER_HUB_USERNAME&lt;/p&gt;
&lt;p&gt;DOCKER_HUB_TOKEN&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;writing-the-workflow-main-yaml-step-by-step&quot;&gt;Writing the Workflow (main.yaml) — Step by Step&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Why It Matters&lt;/strong&gt;&lt;br&gt;
The workflow file is your recipe — the step-by-step plan your robot will follow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Explanation&lt;/strong&gt;&lt;br&gt;
In &lt;code&gt;.github/workflows/main.yaml&lt;/code&gt;, you define:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When to run: (trigger event, like push)&lt;/li&gt;
&lt;li&gt;What to run: (the jobs and steps)&lt;/li&gt;
&lt;li&gt;How to run: (what tools or actions to use)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Key Parts&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;on: push&lt;/code&gt;: “Start the oven when new ingredients arrive.”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;jobs&lt;/code&gt;: “Each job is a workstation — baking, packaging, shipping.”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;actions/checkout@v3&lt;/code&gt;: “Fetch ingredients from the storage (repo).”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;docker/login-action@v2&lt;/code&gt;: “Unlock the warehouse with your keycard (secret).”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;docker/build-push-action@v3&lt;/code&gt;: “Bake and ship the final product.”&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Example Workflow&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name: Build and Push Docker Image

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_HUB_USERNAME }}
          password: ${{ secrets.DOCKER_HUB_TOKEN }}

      - uses: docker/build-push-action@v3
        with:
          context: .
          push: true
          tags: yourname/app:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Common Pitfalls&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Wrong &lt;strong&gt;Dockerfile path&lt;/strong&gt; → build fails.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Missing or misnamed secrets → login fails silently.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Forgot &lt;code&gt;push: true&lt;/code&gt; → image never uploads.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;✅ Action Step&lt;/strong&gt;&lt;br&gt;
Commit your file to &lt;code&gt;.github/workflows/main.yaml&lt;/code&gt; and push it. Your pipeline is ready.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;build-amp-push-watching-it-run&quot;&gt;Build &amp;amp; Push: Watching It Run&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Why It Matters&lt;/strong&gt;&lt;br&gt;
Seeing it work builds confidence that your automation truly works.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Explanation&lt;/strong&gt;&lt;br&gt;
Push your code, and watch:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;GitHub detects the push.&lt;/li&gt;
&lt;li&gt;Actions triggers the job.&lt;/li&gt;
&lt;li&gt;The workflow checks out code, logs in to Docker Hub, builds, and pushes the image.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Real-World Example&lt;/strong&gt;&lt;br&gt;
In the demo, after pushing a small code change, the author refreshed Docker Hub and saw the new image tagged &lt;code&gt;latest&lt;/code&gt;. No manual typing, no Docker commands.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Action Steps&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Edit a small file (e.g., README).&lt;/li&gt;
&lt;li&gt;Run git push.&lt;/li&gt;
&lt;li&gt;Open GitHub → Actions tab → watch logs.&lt;/li&gt;
&lt;li&gt;Open Docker Hub → confirm the new tag.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Counterpoints &amp;amp; Limitations&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Private repos or registries&lt;/strong&gt;: Require additional permissions or tokens.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multi-service setups&lt;/strong&gt;: Need multiple jobs or build matrix strategies.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Large images&lt;/strong&gt;: Consider caching layers for faster builds.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security&lt;/strong&gt;: Always scan base images and protect secrets carefully.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;mini-case-study-solo-developer-to-one-click-deployment-&quot;&gt;Mini Case Study: “Solo Developer to One-Click Deployment”&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Before&lt;/strong&gt;:&lt;br&gt;
X, a solo Node.js developer, manually built and pushed Docker images every update — often forgetting tags or pushing the wrong version.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;After GitHub Actions&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Added main.yaml&lt;/li&gt;
&lt;li&gt;Configured Docker Hub secrets&lt;/li&gt;
&lt;li&gt;Now, every git push builds + pushes automatically&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;He cut his deployment time from 10 minutes to under 1, and never mistakes again.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;conclusion-from-kitchen-chaos-to-conveyor-belt&quot;&gt;Conclusion — From Kitchen Chaos to Conveyor Belt&lt;/h3&gt;
&lt;p&gt;We began with a bakery. You don’t want to bake, pack, and ship every loaf by hand. You want a conveyor belt that works reliably.&lt;/p&gt;
&lt;p&gt;GitHub Actions and Docker give you that belt. As soon as you push code, your automation handles the rest — repeatable, consistent, hands-off.&lt;/p&gt;
&lt;p&gt;Take this workflow file, plug in your secrets, push a change, and watch your pipeline come alive.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/6405/from-code-push-to-docker-hub-ci-cd-with-github-actions</guid>
<pubDate>Tue, 14 Oct 2025 05:04:53 +0000</pubDate>
</item>
<item>
<title>Automate your open-source project's builds &amp; deployments with CI/CD using GitHub Actions!</title>
<link>https://coderlegion.com/5883/automate-your-open-source-projects-builds-deployments-with-ci-cd-using-github-actions</link>
<description>&lt;h3 id=&quot;building-ci-cd-pipelines-without-losing-your-mind-&quot;&gt;Building CI/CD Pipelines (Without Losing Your Mind)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Beginner Level&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Hey everyone!  As members of the open-source community, we're all passionate about building awesome things – from libraries that power the internet to tools that make developers’ lives easier. But sometimes, getting those amazing contributions into production can feel… well, a bit like herding cats. That's where CI/CD pipelines come in!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://semaphore.io/wp-content/uploads/2022/05/cicd-pipeline-introduction-1024x422-1.jpg&quot; alt=&quot;CICD Pipeline&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What &lt;em&gt;is&lt;/em&gt; a CI/CD Pipeline, Anyway? (Don't worry, it's not as scary as it sounds)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;CI/CD stands for Continuous Integration and Continuous Delivery/Deployment.  Think of it as an automated assembly line for your code. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Continuous Integration (CI):&lt;/strong&gt; Whenever someone commits code to your repository, the CI system automatically pulls that code, builds it, runs tests, and checks for integration issues. It's like constantly merging small updates instead of waiting for a massive, potentially disastrous, merge at the end.  This helps catch bugs early and keeps your codebase healthy.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Continuous Delivery (CD):&lt;/strong&gt; Takes the CI step a little further by automating the process of releasing your code to a staging or testing environment. It ensures that your software is always in a deployable state. &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Continuous Deployment (also CD):&lt;/strong&gt;  The ultimate goal! Automates the entire release process, including deploying code directly to production after passing all tests.  This allows for faster feedback loops and quicker delivery of value to users.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Why Should We Care? (Besides Saving Our Sanity)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For open-source projects, a solid CI/CD pipeline offers huge benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Faster Feedback:&lt;/strong&gt; Developers get instant notifications when their code breaks something.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reduced Risk:&lt;/strong&gt; Automated tests catch bugs before they reach users.  (Nobody likes a surprise bug!)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Increased Confidence:&lt;/strong&gt; Knowing your code is consistently tested and deployed gives you peace of mind.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easier Collaboration:&lt;/strong&gt; Everyone benefits from clear, automated workflows. &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;More Time for Innovation:&lt;/strong&gt; Less time spent on manual deployment means more time coding!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Building Your Own Pipeline&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Okay, enough theory. Let's dive into how to build a CI/CD pipeline.  There are tons of tools out there – Jenkins, GitLab CI, GitHub Actions, CircleCI, Azure DevOps… the list goes on!  Don’t feel overwhelmed; you don't need to master &lt;em&gt;everything&lt;/em&gt; at once.&lt;/p&gt;
&lt;p&gt;I'll focus on &lt;strong&gt;GitHub Actions&lt;/strong&gt; here, as it's widely used and integrates seamlessly with GitHub repositories (which is probably where your awesome open-source projects live). It also has a generous free tier – perfect for getting started!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Here’s a basic workflow example:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Create a &lt;code&gt;.github/workflows&lt;/code&gt; directory in your repository.&lt;/strong&gt; This is where you'll store your workflow files.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create a YAML file (e.g., &lt;code&gt;main.yml&lt;/code&gt;) inside the directory.&lt;/strong&gt; This file defines the steps of your pipeline.  Here’s an example:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: CI/CD Pipeline

on:
  push:
    branches:
      - main  # Trigger the workflow on pushes to the 'main' branch

jobs:
  build:
    runs-on: ubuntu-latest  # Use a Linux runner

    steps:
      - uses: actions/checkout@v3 # Checkout the code from the repository

      - name: Set up Node.js 16
        uses: actions/setup-node@v3
        with:
          node-version: 16

      - name: Install dependencies
        run: npm install

      - name: Run tests
        run: npm test  # Replace with your testing command (e.g., pytest, rspec)

      - name: Build the project
        run: npm run build # Replace with your build command

      - name: Deploy to staging (optional)
        if: github.ref == 'refs/heads/main'
        run: |
          # Add your deployment commands here - e.g., using SSH, cloud provider CLI tools etc.
          echo &quot;Deploying to staging...&quot; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Let’s break down that YAML:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt;: A descriptive name for your workflow.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;on&lt;/code&gt;:  Specifies when the workflow should run (in this case, on pushes to the &lt;code&gt;main&lt;/code&gt; branch).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jobs&lt;/code&gt;: Defines a set of tasks to be executed in parallel or sequentially. Here we have one job called &lt;code&gt;build&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;runs-on&lt;/code&gt;: Specifies the virtual environment where your job will run.  &lt;code&gt;ubuntu-latest&lt;/code&gt; is a common choice.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;steps&lt;/code&gt;: A list of individual tasks to perform within the job. Each step uses pre-built actions from the GitHub Actions marketplace (like &lt;code&gt;actions/checkout@v3&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Important Notes and Tips:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Actions Marketplace:&lt;/strong&gt; The GitHub Actions marketplace has &lt;em&gt;tons&lt;/em&gt; of community-created actions for everything from linting your code to sending Slack notifications on build failures.  Explore it!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secrets Management:&lt;/strong&gt; Don't hardcode API keys or passwords in your workflow files! Use GitHub Secrets (Settings -&amp;gt; Secrets -&amp;gt; Actions) instead. It's much safer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testing is Key:&lt;/strong&gt; Write good tests! They are the foundation of a robust CI/CD pipeline.  Aim for high test coverage. &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Branching Strategy:&lt;/strong&gt;  Consider adopting a branching strategy like Gitflow to streamline your development process. &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Notifications:&lt;/strong&gt; Configure notifications (e.g., Slack, email) to be alerted about build failures and deployments.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Troubleshooting: Because Things &lt;em&gt;Always&lt;/em&gt; Go Wrong&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Expect things to break! Your first pipeline won’t be perfect. Here are a few common troubleshooting tips:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Check the logs:&lt;/strong&gt; The GitHub Actions UI provides detailed logs for each step of your workflow.  Read them carefully!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Permissions:&lt;/strong&gt; Make sure your workflow has the necessary permissions to access your repository and deploy to your target environment. &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dependencies:&lt;/strong&gt; Double-check that all dependencies are installed correctly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don't Panic&lt;/strong&gt;: Seriously, it happens to everyone. Debugging is part of the process!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Resources to Explore&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub Actions Documentation:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.github.com/en/actions&quot;&gt;https://docs.github.com/en/actions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitLab CI Documentation:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.gitlab.com/ee/ci/&quot;&gt;https://docs.gitlab.com/ee/ci/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CircleCI Documentation:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://circleci.com/docs/&quot;&gt;https://circleci.com/docs/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The Takeaway&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Building a CI/CD pipeline is an investment in the future of your open-source project. It might seem daunting at first, but it’s worth the effort.  By automating your build, test, and deployment processes, you can free up time to focus on what truly matters – writing awesome code!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Now go forth and automate! And if you have any questions or tips of your own, share them in the comments below!&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(P.S. If you accidentally deploy a broken version of your project to production, just blame the bot.  It's always a good strategy.) &lt;/strong&gt;&lt;/p&gt;
</description>
<category>CI/CD Pipelines</category>
<guid isPermaLink="true">https://coderlegion.com/5883/automate-your-open-source-projects-builds-deployments-with-ci-cd-using-github-actions</guid>
<pubDate>Thu, 02 Oct 2025 07:00:54 +0000</pubDate>
</item>
<item>
<title>Enhancing Git Security and Workflow: A Comprehensive Guide to Signed Commits and a Linear History</title>
<link>https://coderlegion.com/5118/enhancing-git-security-and-workflow-comprehensive-guide-signed-commits-linear-history</link>
<description>&lt;p&gt;In the world of version control, maintaining a secure and clean project history is crucial, especially for collaborative and open-source projects. This guide breaks down two essential best practices—&lt;strong&gt;signed commits&lt;/strong&gt; and a &lt;strong&gt;linear history&lt;/strong&gt;—and provides detailed steps on how to implement them.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-the-power-of-signed-commits&quot;&gt;1. The Power of Signed Commits&lt;/h3&gt;
&lt;p&gt;A signed commit is cryptographically verified to have been made by the person to whom it is attributed. This is a critical security measure in modern software development. It prevents malicious actors from impersonating trusted contributors and provides an undeniable audit trail.&lt;/p&gt;
&lt;h4 id=&quot;how-it-works&quot;&gt;&lt;strong&gt;How It Works&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Signed commits rely on asymmetric cryptography using a public/private key pair. You use your private key to digitally sign your commits, and others can use your publicly available key to verify the signature.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Private Key:&lt;/strong&gt; A secret key stored securely on your local machine, often protected by a strong passphrase. You use this to create a digital signature for each commit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Public Key:&lt;/strong&gt; This key is derived from your private key and can be shared freely. You upload it to your GitHub account.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When you push a signed commit, GitHub uses your public key to verify that the signature on the commit was created by your private key. If the signatures match, the commit gets a green &quot;&lt;strong&gt;Verified&lt;/strong&gt;&quot; badge, guaranteeing non-repudiation.&lt;/p&gt;
&lt;h4 id=&quot;how-to-implement-signed-commits&quot;&gt;&lt;strong&gt;How to Implement Signed Commits&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Follow these steps to generate a GPG key, add it to GitHub, and configure Git to automatically sign your commits.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Install GPG:&lt;/strong&gt;&lt;br&gt;
Ensure you have GPG (GNU Privacy Guard) installed on your machine.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;macOS:&lt;/strong&gt; &lt;code&gt;brew install gpg&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Windows:&lt;/strong&gt; Download from the &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.gnupg.org/download/index.html&quot;&gt;GnuPG website&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Linux (Debian/Ubuntu):&lt;/strong&gt; &lt;code&gt;sudo apt-get install gpg&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Generate a GPG Key:&lt;/strong&gt;&lt;br&gt;
Use this command to generate a new key. Follow the prompts to select the key type, size, and set a strong passphrase.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --full-generate-key
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Export Your Public Key:&lt;/strong&gt;&lt;br&gt;
First, list your secret keys to get your key ID.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --list-secret-keys --keyid-format=long
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your key ID will be a 16-character string in the output (e.g., &lt;code&gt;3AA5C34371567BD2&lt;/code&gt;). Then, export the public key.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gpg --armor --export [your-key-ID]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Copy the full output, including the &lt;code&gt;-----BEGIN PGP PUBLIC KEY BLOCK-----&lt;/code&gt; and &lt;code&gt;-----END PGP PUBLIC KEY BLOCK-----&lt;/code&gt; lines.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add the Key to GitHub:&lt;/strong&gt;&lt;br&gt;
Go to your GitHub &lt;code&gt;Settings &amp;gt; SSH and GPG keys&lt;/code&gt; and click &quot;&lt;strong&gt;New GPG key&lt;/strong&gt;.&quot; Paste the public key you just copied and save. Your account is now ready to verify signed commits.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configure Git:&lt;/strong&gt;&lt;br&gt;
Tell Git which key to use for signing and enable automatic commit signing globally.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Tell Git about your signing key
git config --global user.signingkey [your-key-ID]

# Automatically sign all commits
git config --global commit.gpgsign true
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;From now on, every commit you make will be automatically signed. If you prefer to sign commits on a per-commit basis, you can use the &lt;code&gt;-S&lt;/code&gt; flag: &lt;code&gt;git commit -S -m &quot;Your commit message&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; For GitHub Actions, you cannot use a local GPG key, as the runner is a temporary virtual machine. Instead, GitHub automatically signs commits made by the &lt;code&gt;github-actions[bot]&lt;/code&gt; user via the GitHub API, providing a verified commit for your automated workflows.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-the-discipline-of-a-linear-history&quot;&gt;2. The Discipline of a Linear History&lt;/h3&gt;
&lt;p&gt;In addition to securing individual commits, maintaining a clean project history is vital for team collaboration. A &lt;strong&gt;linear history&lt;/strong&gt; ensures your Git commits follow a single, straight line without the usual branching and merging. It makes a project’s history incredibly easy to read and manage.&lt;/p&gt;
&lt;h4 id=&quot;how-it-s-achieved-the-rebase-workflow&quot;&gt;&lt;strong&gt;How It's Achieved: The Rebase Workflow&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;A linear history is primarily achieved by using &lt;code&gt;git rebase&lt;/code&gt; instead of &lt;code&gt;git merge&lt;/code&gt;. This rewrites your branch's history to place your commits on top of the latest version of the main branch.&lt;/p&gt;
&lt;p&gt;Here is a typical rebase workflow for a feature branch:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Update your local &lt;code&gt;main&lt;/code&gt; branch:&lt;/strong&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git checkout main
git pull
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Move your feature branch's commits on top of &lt;code&gt;main&lt;/code&gt;:&lt;/strong&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git checkout my-feature-branch
git rebase main
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you encounter conflicts during the rebase, resolve them, add the changes (&lt;code&gt;git add .&lt;/code&gt;), and then run &lt;code&gt;git rebase --continue&lt;/code&gt;. Once the rebase is complete, your feature branch will have a clean, linear history, and you can push your changes.&lt;/p&gt;
&lt;h4 id=&quot;interactive-rebase-for-cleaning-up-commits&quot;&gt;&lt;strong&gt;Interactive Rebase for Cleaning Up Commits&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Use &lt;code&gt;git rebase -i&lt;/code&gt; to clean up your commit history before pushing. This is perfect for condensing multiple work-in-progress commits into a single, meaningful commit.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Rebase the last 5 commits on your current branch
git rebase -i HEAD~5
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;pushing-rebased-commits&quot;&gt;&lt;strong&gt;Pushing Rebased Commits&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Because &lt;code&gt;git rebase&lt;/code&gt; rewrites history, a standard &lt;code&gt;git push&lt;/code&gt; will fail. You must use a force push, but the safest way is with the &lt;code&gt;--force-with-lease&lt;/code&gt; flag. This prevents you from accidentally overwriting someone else's work.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git push --force-with-lease
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;merging-in-a-linear-workflow&quot;&gt;&lt;strong&gt;Merging in a Linear Workflow&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;In a linear workflow, you still use a &quot;merge&quot; action, but you do it via pull requests on platforms like GitHub, which offer specific types that avoid creating a merge commit:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fast-Forward Merge:&lt;/strong&gt; If your feature branch is a direct extension of the &lt;code&gt;main&lt;/code&gt; branch, Git simply moves the &lt;code&gt;main&lt;/code&gt; pointer forward.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Squash and Merge:&lt;/strong&gt; This combines all your commits into a single, new commit on the &lt;code&gt;main&lt;/code&gt; branch, which is great for keeping the main branch history clean.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rebase and Merge:&lt;/strong&gt; This performs a rebase and then a fast-forward merge, preserving all of your individual commits in a clean, linear order.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-enforcing-best-practices-with-branch-protection-rules&quot;&gt;3. Enforcing Best Practices with Branch Protection Rules&lt;/h3&gt;
&lt;p&gt;Once you understand these concepts, you can enforce them using GitHub's &lt;strong&gt;branch protection rules&lt;/strong&gt;. These rules are a powerful administrative tool for ensuring the integrity of your key branches (like &lt;code&gt;main&lt;/code&gt; or &lt;code&gt;develop&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;To configure them, navigate to &lt;code&gt;Settings &amp;gt; Branches&lt;/code&gt; in your GitHub repository and click &quot;&lt;strong&gt;Add rule&lt;/strong&gt;.&quot;&lt;/p&gt;
&lt;h4 id=&quot;the-most-useful-rules&quot;&gt;&lt;strong&gt;The Most Useful Rules&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Require signed commits:&lt;/strong&gt; This prevents anyone from merging a pull request that contains an unsigned commit. It's a non-negotiable step for maintaining a secure project.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Require linear history:&lt;/strong&gt; This prevents the use of standard merge commits, forcing all merges to be a rebase or squash. It's an essential setting for maintaining a clean history.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Restrict push access:&lt;/strong&gt; This allows you to specify that only certain users or teams (like administrators) can push directly to a protected branch, forcing all other changes to go through a pull request.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Restrict who can create and delete branches:&lt;/strong&gt; This provides a safety net against accidental or malicious deletions by preventing unauthorized users from creating branches that match a protection rule or from deleting a critical branch.&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Secure Coding Practices</category>
<guid isPermaLink="true">https://coderlegion.com/5118/enhancing-git-security-and-workflow-comprehensive-guide-signed-commits-linear-history</guid>
<pubDate>Mon, 01 Sep 2025 12:00:38 +0000</pubDate>
</item>
<item>
<title>GitHub workflows tips and tricks</title>
<link>https://coderlegion.com/5073/github-workflows-tips-and-tricks</link>
<description>&lt;p&gt;I've quite a lengthy experience with GitHub workflows, but not up to the point where I can claim I'm an expert. However, I recently developed a new workflow, and it prompted me to write this post. Feel free to add your own.&lt;/p&gt;
&lt;h3 id=&quot;what-are-github-workflows-&quot;&gt;What are GitHub workflows?&lt;/h3&gt;
&lt;p&gt;&amp;gt;A &lt;strong&gt;workflow&lt;/strong&gt; is a configurable automated process that will run one or more jobs. Workflows are defined by a YAML file checked in to your repository and will run when triggered by an event in your repository, or they can be triggered manually or at a defined schedule.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&amp;gt;Workflows are defined in the &lt;code&gt;.github/workflows&lt;/code&gt; directory in a repository. A repository can have multiple workflows, each of which can perform a different set of tasks, such as:&lt;/p&gt;
&lt;p&gt;&amp;gt;&lt;em&gt; Building and testing pull requests&lt;br&gt;
&amp;gt;&lt;/em&gt; Deploying your application every time a release is created&lt;br&gt;
&amp;gt;* Adding a label whenever a new issue is opened&lt;/p&gt;
&lt;p&gt;&amp;gt;-- &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.github.com/en/actions/concepts/workflows-and-actions/workflows&quot;&gt;About workflows&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As such, a workflow is comparable to a Jenkins job, implemented in YAML instead of XML. Yet, the main difference is that the workflow configuration is &lt;strong&gt;stored inside the code repository&lt;/strong&gt;, contrary to a legacy Jenkins job configuration. It doesn't look like much, but it allows using source code management approaches, including versioning and rolling back. Years ago, I worked for a customer whose DevOps team implemented a Puppet-based approach to reconstruct Jenkins jobs so the Puppet config could be stored in Git. It was a huge effort!&lt;/p&gt;
&lt;p&gt;Note that GitHub was quite late to the party. Before it implemented workflows, I used a third-party service called &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.travis-ci.com/&quot;&gt;Travis CI&lt;/a&gt; that worked the same way. I switched mainly because Travis became paid and secondarily because workflows are natively part of GitHub.&lt;/p&gt;
&lt;p&gt; Tip: &lt;strong&gt;Use the correct semantics&lt;/strong&gt;: they are called &lt;em&gt;GitHub workflows&lt;/em&gt;. GitHub Actions are entirely different.&lt;/p&gt;
&lt;h3 id=&quot;github-actions&quot;&gt;GitHub Actions&lt;/h3&gt;
&lt;p&gt;GitHub workflows are composed of &lt;em&gt;jobs&lt;/em&gt;, which themselves are composed of &lt;em&gt;steps&lt;/em&gt;. Steps reference &lt;em&gt;run&lt;/em&gt; commands, and a couple of optional parameters, including a name.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;jobs:
  metrics:
    runs-on: my-image                              #1
    steps:
      - name: Install dependencies via Poetry      #2
        run: poetry install                        #3
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;OCI image on which the workflow runs&lt;/li&gt;
&lt;li&gt;Step name&lt;/li&gt;
&lt;li&gt;Step command&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;GitHub Actions are reusable components that offer alternatives to repeating the same command over and over.&lt;/p&gt;
&lt;p&gt;&amp;gt;An &lt;strong&gt;action&lt;/strong&gt; is a pre-defined, reusable set of jobs or code that performs specific tasks within a &lt;strong&gt;workflow&lt;/strong&gt;, reducing the amount of repetitive code you write in your workflow files. Actions can perform tasks such as:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&amp;gt;&lt;em&gt; Pulling your Git repository from GitHub&lt;br&gt;
&amp;gt;&lt;/em&gt; Setting up the correct toolchain for your build environment&lt;br&gt;
&amp;gt;* Setting up authentication for your cloud provider&lt;/p&gt;
&lt;p&gt;&amp;gt;You can write your own actions, or you can find actions to use in your workflows in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/marketplace?type=actions&quot;&gt;GitHub Marketplace&lt;/a&gt;.&lt;br&gt;
&amp;gt;For example, instead of executing &lt;code&gt;git checkout&lt;/code&gt;, you can use the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/marketplace/actions/checkout&quot;&gt;checkout action&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&amp;gt;-- &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.github.com/en/actions/get-started/understand-github-actions#actions&quot;&gt;GitHub Action&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;jobs:
  metrics:
    runs-on: my-image
    steps:
      - uses: actions/checkout@v5                  #1
        with:                                      #2
          fetch-depth: 0
          submodules: recursive
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Reference a GitHub Action&lt;/li&gt;
&lt;li&gt;Action parameters&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I'm a big fan of not reinventing the wheel. GitHub Actions play a big role in that.&lt;/p&gt;
&lt;p&gt; Tip: &lt;strong&gt;Prefer GitHub Actions over ad-hoc commands&lt;/strong&gt; when possible.&lt;/p&gt;
&lt;h3 id=&quot;choosing-the-right-action&quot;&gt;Choosing the right Action&lt;/h3&gt;
&lt;p&gt;The GitHub Marketplace hosts &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/marketplace?type=actions&quot;&gt;plenty of actions&lt;/a&gt;. Actions are listed by categories. You can filter by creator type and sort by popularity.&lt;/p&gt;
&lt;p&gt;When choosing an action, you should exert the same care as when bringing in any other dependency. Here are a couple of criteria to help you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vendor: prefer verified creators&lt;/li&gt;
&lt;li&gt;License type: Open Source vs. closed source. With the former, check the exact terms; the Apache v2 license is very different from the GPL one.&lt;/li&gt;
&lt;li&gt;Pricing for commercial actions&lt;/li&gt;
&lt;li&gt;Check the source code repository of actions that display it and check the following information:&lt;br&gt;
&lt;strong&gt; Inception date&lt;br&gt;
&lt;/strong&gt; Activity, including the repartition of commits per committer, the number of open issues, the mean time for fixing issues, etc.&lt;br&gt;
** Documentation (reference material, tutorials, how-to guides, and explanations)&lt;/li&gt;
&lt;li&gt;Community, if any&lt;/li&gt;
&lt;li&gt;Support&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; Tip: &lt;strong&gt;Be as careful in choosing a GitHub Action as in choosing any other dependency.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;know-your-actions&quot;&gt;Know your Actions&lt;/h3&gt;
&lt;p&gt;As the previous tip, this one stems from a more generic one—I speak from experience. I was developing a workflow to package a Java application. I was heavily using the workflow, and the job had to download the dependencies at every run.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-java@v4                #1
        with:
          distribution: temurin
          java-version: 21
      - uses: actions/cache@v4                     #2
        with:
          path: ~/.m2/repository                   #3
          key: ${{ platform }}-maven--${{ hashFiles('**/pom.xml') }} #4
          restore-keys: |
            ${{ runner.os }}-maven-                #4
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Install a JDK&lt;/li&gt;
&lt;li&gt;Set up a cache. The cache is generic and can cache files across runs.&lt;/li&gt;
&lt;li&gt;Cache the local Maven repository&lt;/li&gt;
&lt;li&gt;Cache dependencies. The key uses the runner OS, which doesn't change, and the POM's hash. Hence, the step creates a new cache each time the POM changes.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Yet, reading the documentation, I realized I could achieve the same in a much more straightforward way:&lt;/p&gt;
&lt;p&gt;&amp;gt;The action has a built-in functionality for caching and restoring dependencies. It uses toolkit/cache under hood for caching dependencies but requires less configuration settings. Supported package managers are gradle, maven and sbt. The format of the used cache key is &lt;code&gt;setup-java-${{ platform }}-${{ packageManager }}-${{ fileHash }}&lt;/code&gt;, where the hash is based on the following files:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&amp;gt;* maven: &lt;code&gt;**/pom.xml&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&amp;gt;-- &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/marketplace/actions/setup-java-jdk#caching-packages-dependencies&quot;&gt;Caching packages dependencies&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Thus, I replaced the above snippet with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 21
          cache: maven                             #1
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;This is it&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt; Tip: &lt;strong&gt;Thoroughly read the documentation of actions.&lt;/strong&gt; Spending one hour on the documentation can help you save man-days of effort.&lt;/p&gt;
&lt;h3 id=&quot;pin-your-dependency-version&quot;&gt;Pin your dependency version&lt;/h3&gt;
&lt;p&gt;This section also takes generic advice and applies it to GitHub workflows' version. You find versions in at least two places: the OCI image and GitHub Actions' version.&lt;/p&gt;
&lt;p&gt;You may have noticed the &lt;code&gt;runs-on: ubuntu-latest&lt;/code&gt; in the previous snippets. At the moment of this writing, it points to &lt;code&gt;ubuntu-24.04&lt;/code&gt;. As time passes, it will point to the latest version, with different libraries' versions. It could cause a failure during the workflow execution, or worse, change the output in ways you wouldn't notice until it's too late. You'd better set a specific version. Check &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/actions/runner-images[all&quot;&gt;https://github.com/actions/runner-images[all&lt;/a&gt; available images].&lt;/p&gt;
&lt;p&gt;Actions' versioning works differently, as they point to a GitHub repo's reference: either a tag or a commit SHA. While the OCI image is in GitHub's hands, the action is the responsibility of its provider. It means anybody with commit rights could change the content of an underlying tag. If you take security seriously, you &lt;strong&gt;must&lt;/strong&gt; pin to a commit.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@08c6903             #1
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Pin to a specific commit&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt; Tip: Take security seriously and &lt;strong&gt;pin actions to a specific commit.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;use-the-job-summary&quot;&gt;Use the job summary&lt;/h3&gt;
&lt;p&gt;Each of your workflow steps probably outputs lots of logs. In a regular workflow, the overall amount of log lines can be quite huge. Some of them might be useful, some of them might not be, but because there's only a standard output and standard error, everything ends up in the same place. Yet, you may want to focus on specific information, &lt;em&gt;e.g.&lt;/em&gt;, the number of tests run or the percentage of code coverage, in case you don't send these data somewhere else. GitHub makes it straightforward to add the data you want in the job summary.&lt;/p&gt;
&lt;p&gt;&amp;gt;You can set some custom Markdown for each job so that it will be displayed on the summary page of a workflow run. You can use job summaries to display and group unique content, such as test result summaries, so that someone viewing the result of a workflow run doesn't need to go into the logs to see important information related to the run, such as failures.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&amp;gt;Job summaries support GitHub flavored Markdown, and you can add your Markdown content for a step to the &lt;code&gt;GITHUB_STEP_SUMMARY&lt;/code&gt; environment file. &lt;code&gt;GITHUB_STEP_SUMMARY&lt;/code&gt; is unique for each step in a job.&lt;/p&gt;
&lt;p&gt;&amp;gt;-- &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands#adding-a-job-summary&quot;&gt;Adding a job summary&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here's a &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/apache/arrow-adbc/blob/apache-arrow-adbc-19/.github/workflows/packaging.yml#L224-L228&quot;&gt;sample&lt;/a&gt; from Apache Arrow:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Show inputs
  run: |
    echo &quot;upload_artifacts: ${{ inputs.upload_artifacts }}&quot; &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY
    echo &quot;schedule: ${{ github.event.schedule }}&quot; &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY
    echo &quot;ref: ${{ github.ref }}&quot; &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/apache/arrow-adbc/actions/runs/17181739408#summary-48745051897&quot;&gt;result&lt;/a&gt; is:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=37561915331864553&quot; alt=&quot;GitHub summary sample&quot;&gt;&lt;/p&gt;
&lt;p&gt; Tip: &lt;strong&gt;Use GitHub job summary&lt;/strong&gt; to enhance developer experience.&lt;/p&gt;
&lt;h3 id=&quot;understand-workflows-lifecycle&quot;&gt;Understand workflows' lifecycle&lt;/h3&gt;
&lt;p&gt;Workflows run steps sequentially. A failing step cancels all remaining steps, and the workflow ends up in a &lt;code&gt;Failure&lt;/code&gt; state. In the example above, it means we won't get any testing summary at all if any of the subsequent step fails. It's possible to bypass this default behavior by using the counterintuitive &lt;code&gt;if&lt;/code&gt; condition.&lt;/p&gt;
&lt;p&gt;&amp;gt;You can use the following status check functions as expressions in &lt;code&gt;if&lt;/code&gt; conditionals. A default status check of &lt;code&gt;success()&lt;/code&gt; is applied unless you include one of these functions.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&amp;gt;-- &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.github.com/en/actions/reference/workflows-and-actions/expressions#status-check-functions&quot;&gt;Status check functions&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;GitHub uses &lt;code&gt;success()&lt;/code&gt; by default, but options include &lt;code&gt;always()&lt;/code&gt;, &lt;code&gt;cancelled()&lt;/code&gt;, and &lt;code&gt;failure()&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Show inputs
  if: ${{ always() }}                              #1
  run: |
    echo &quot;upload_artifacts: ${{ inputs.upload_artifacts }}&quot; &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY
    echo &quot;schedule: ${{ github.event.schedule }}&quot; &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY
    echo &quot;ref: ${{ github.ref }}&quot; &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Always run the step, regardless of whether the previous steps were successful or not&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now imagine the following sequence:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Step that may fail
  run: whatever
- name: Execute unit tests
  run: ./mvnw -B test                              #1
- name: Test Summary
  if: ${{ always() }}
  uses: test-summary/action@31493c7                #2
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Running unit tests also generates JUnit reports&lt;/li&gt;
&lt;li&gt;Use the generated JUnit reports to write a step summary&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If the first step fails, the summary executes regardless of whether tests were run. If they didn't, it would fail. To avoid this, we must refine the condition further so that the summary step only runs if the unit test step did.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Step that may fail
  run: whatever
- name: Execute unit tests
  id: test                                         #1
  run: ./mvnw -B test
- name: Test Summary
  if: ${{ always() &amp;amp;&amp;amp; steps.test.conclusion == 'success'}} #2
  uses: test-summary/action@31493c7
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Set the step's id&lt;/li&gt;
&lt;li&gt;Run only if the &lt;code&gt;test&lt;/code&gt; step runs successfully&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It's only a simple example, but it offers interesting options.&lt;/p&gt;
&lt;p&gt; Tip: &lt;strong&gt;Know workflows' lifecycle&lt;/strong&gt; and use it to your advantage.&lt;/p&gt;
&lt;h3 id=&quot;test-locally&quot;&gt;Test locally&lt;/h3&gt;
&lt;p&gt;Lots of organizations enforce strict GitHub rules. The most widespread one is that you can't push to &lt;code&gt;master&lt;/code&gt;: every commit must go through a pull request. While it makes sense for established projects, it prevents starting projects from iterating fast while retaining the commit history. In the context of developing GitHub workflows, it's definitely crippling.&lt;/p&gt;
&lt;p&gt;Comes the act project:&lt;/p&gt;
&lt;p&gt;&amp;gt;&quot;Think globally, act locally&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&amp;gt;Run your GitHub Actions locally! Why would you want to do this? Two reasons:&lt;/p&gt;
&lt;p&gt;&amp;gt;&lt;em&gt; &lt;strong&gt;Fast Feedback&lt;/strong&gt; - Rather than having to commit/push every time you want to test out the changes you are making to your &lt;code&gt;.github/workflows/&lt;/code&gt; files (or for any changes to embedded GitHub actions), you can use act to run the actions locally. The environment variables and filesystem are all configured to match what GitHub provides.&lt;br&gt;
&amp;gt;&lt;/em&gt; &lt;strong&gt;Local Task Runner&lt;/strong&gt; - I love make. However, I also hate repeating myself. With &lt;code&gt;act&lt;/code&gt;, you can use the GitHub Actions defined in your &lt;code&gt;.github/workflows/&lt;/code&gt; to replace your &lt;code&gt;Makefile&lt;/code&gt;!&lt;/p&gt;
&lt;p&gt;&amp;gt;-- &lt;a rel=&quot;nofollow&quot; href=&quot;https://nektosact.com/&quot;&gt;Introduction to &lt;code&gt;act&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;act&lt;/code&gt; has a GitHub CLI integration that triggers the workflow according to the correct event. Let's emulate a push:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gh act push
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here's the output excerpt when images and actions have already been downloaded:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;INFO[0000] Using docker host 'unix:///var/run/docker.sock', and daemon socket 'unix:///var/run/docker.sock'
[workflow.yml/test] ⭐ Run Set up job
[workflow.yml/test]   Start image=catthehacker/ubuntu:act-22.04
[workflow.yml/test]     docker pull image=catthehacker/ubuntu:act-22.04 platform= username= forcePull=true
[workflow.yml/test]     docker create image=catthehacker/ubuntu:act-22.04 platform= entrypoint=[&quot;tail&quot; &quot;-f&quot; &quot;/dev/null&quot;] cmd=[] network=&quot;host&quot;
[workflow.yml/test]     docker run image=catthehacker/ubuntu:act-22.04 platform= entrypoint=[&quot;tail&quot; &quot;-f&quot; &quot;/dev/null&quot;] cmd=[] network=&quot;host&quot;
[workflow.yml/test]     docker exec cmd=[node --no-warnings -e console.log(process.execPath)] user= workdir=
[workflow.yml/test]   ✅  Success - Set up job
[workflow.yml/test]   ☁  git clone 'https://github.com/actions/setup-java' # ref=v4
[workflow.yml/test] ⭐ Run Main actions/checkout@v4
[workflow.yml/test]     docker cp src=/Users/nico/projects/act-sample/. dst=/Users/nico/projects/act-sample
[workflow.yml/test]   ✅  Success - Main actions/checkout@v4 [9.211777902s]
[workflow.yml/test] ⭐ Run Main Setup JDK
[workflow.yml/test]     docker cp src=/home/nico/.cache/act/actions-setup-java@v4/ dst=/var/run/act/actions/actions-setup-java@v4/
[workflow.yml/test]     docker exec cmd=[/opt/acttoolcache/node/18.20.8/x64/bin/node /var/run/act/actions/actions-setup-java@v4/dist/setup/index.js] user= workdir=
[workflow.yml/test]   ❓  ::group::Installed distributions
| Resolved Java 21.0.8+9.0.LTS from tool-cache
| Setting Java 21.0.8+9.0.LTS as the default
| Creating toolchains.xml for JDK version 21 from temurin
| Writing to /root/.m2/toolchains.xml
|
| Java configuration:
|   Distribution: temurin
|   Version: 21.0.8+9.0.LTS
|   Path: /opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/21.0.8-9.0.LTS/x64
|
[workflow.yml/test]   ❓  ::endgroup::
[workflow.yml/test]   ❓ add-matcher /run/act/actions/actions-setup-java@v4/.github/java.json
| Creating settings.xml with server-id: github
| Writing to /root/.m2/settings.xml
[workflow.yml/test]   ⚙  ***
| Cache Size: ~50 MB (52710428 B)
| [command]/usr/bin/tar -xf /tmp/c8ef4803-85c1-4867-ac2d-442dbce79755/cache.tzst -P -C /Users/nico/projects/act-sample --use-compress-program unzstd
| Cache restored successfully
| Cache restored from key: setup-java-linux-x64-maven-ef0e54e9035c18b60db7ea0af5e2f0c4cc5445dd6a2a2a672b91e14f14e7e4c2
[workflow.yml/test]   ✅  Success - Main Setup JDK [2.514565962s]
[workflow.yml/test]   ⚙  ::set-env:: JAVA_HOME=/opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/21.0.8-9.0.LTS/x64
[workflow.yml/test]   ⚙  ::set-env:: JAVA_HOME_21_X64=/opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/21.0.8-9.0.LTS/x64
[workflow.yml/test]   ⚙  ::set-output:: distribution=Temurin-Hotspot
[workflow.yml/test]   ⚙  ::set-output:: path=/opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/21.0.8-9.0.LTS/x64
[workflow.yml/test]   ⚙  ::set-output:: version=21.0.8+9.0.LTS
[workflow.yml/test]   ⚙  ::set-output:: cache-hit=false
[workflow.yml/test]   ⚙  ::add-path:: /opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/21.0.8-9.0.LTS/x64/bin
[workflow.yml/test] ⭐ Run Main Run &quot;fast&quot; tests
[workflow.yml/test]     docker exec cmd=[bash -e /var/run/act/workflow/2] user= workdir=
...
[workflow.yml/test]   ✅  Success - Main Run &quot;fast&quot; tests [20.920776212s]
[workflow.yml/test] ⭐ Run Post Setup JDK
[workflow.yml/test]     docker exec cmd=[/opt/acttoolcache/node/18.20.8/x64/bin/node /var/run/act/actions/actions-setup-java@v4/dist/cleanup/index.js] user= workdir=
| [command]/usr/bin/tar --posix -cf cache.tzst --exclude cache.tzst -P -C /Users/nico/projects/act-sample --files-from manifest.txt --use-compress-program zstdmt
| Cache Size: ~50 MB (52701753 B)
| Cache saved successfully
| Cache saved with the key: setup-java-Linux-x64-maven-ef0e54e9035c18b60db7ea0af5e2f0c4cc5445dd6a2a2a672b91e14f14e7e4c2
[workflow.yml/test]   ✅  Success - Post Setup JDK [1.01412383s]
[workflow.yml/test] ⭐ Run Complete job
[workflow.yml/test] Cleaning up container for job test
[workflow.yml/test]   ✅  Success - Complete job
[workflow.yml/test]   Job succeeded
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I could write a couple of posts on &lt;code&gt;act&lt;/code&gt;; I'll leave you to read the documentation.&lt;/p&gt;
&lt;p&gt; Tip: &lt;strong&gt;Test your workflows locally&lt;/strong&gt; to reduce your feedback cycle time.&lt;/p&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Use the correct semantics, differentiate between workflows and actions.&lt;/li&gt;
&lt;li&gt;Prefer GitHub Actions over ad-hoc commands.&lt;/li&gt;
&lt;li&gt;Be as careful in choosing a GitHub Action as in choosing any other dependency.&lt;/li&gt;
&lt;li&gt;Thoroughly read the documentation of each action you use.&lt;/li&gt;
&lt;li&gt;Pin actions to a specific commit.&lt;/li&gt;
&lt;li&gt;Use GitHub job summary to enhance developer experience.&lt;/li&gt;
&lt;li&gt;Know workflows' lifecycle.&lt;/li&gt;
&lt;li&gt;Test your workflows locally.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;To go further:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.github.com/en/actions/concepts/workflows-and-actions/workflows&quot;&gt;About workflows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.github.com/en/actions/get-started/understand-github-actions#actions&quot;&gt;GitHub Action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/marketplace?type=actions&quot;&gt;GitHub Action Marketplace&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/actions/runner-images&quot;&gt;Runner images&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands#adding-a-job-summary&quot;&gt;Adding a job summary&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.github.com/en/actions/reference/workflows-and-actions/expressions#status-check-functions&quot;&gt;Status check functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://nektosact.com/&quot;&gt;Introduction to act&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a rel=&quot;nofollow&quot; href=&quot;https://blog.frankel.ch/github-workflows-tips-tricks/&quot;&gt;A Java Geek&lt;/a&gt; on August 24&lt;sup&gt;th&lt;/sup&gt;, 2025&lt;/em&gt;&lt;/p&gt;
</description>
<category>CI/CD Pipelines</category>
<guid isPermaLink="true">https://coderlegion.com/5073/github-workflows-tips-and-tricks</guid>
<pubDate>Thu, 28 Aug 2025 06:54:30 +0000</pubDate>
</item>
<item>
<title>Deploy your application on Vercel with GitHub Actions</title>
<link>https://coderlegion.com/4878/deploy-your-application-on-vercel-with-github-actions</link>
<description>&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=5202709586993786117&quot; alt=&quot;&quot;&gt;&lt;br&gt;
A very common CI/CD pattern for frontend apps is: &lt;strong&gt;build in CI and deploy to production automatically when the code reaches&lt;/strong&gt; &lt;code&gt;main&lt;/code&gt;.&lt;br&gt;
If you use Vercel, you can do this with a &lt;strong&gt;short and reliable&lt;/strong&gt; GitHub Actions workflow that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;downloads your project's configuration and variables from Vercel,&lt;/li&gt;
&lt;li&gt;builds the app in CI,&lt;/li&gt;
&lt;li&gt;and deploys the already built artifacts to &lt;strong&gt;production&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this article, we'll walk through it step by step and I'll give you some tips to help you understand &lt;strong&gt;what each line does&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;purpose-of-the-workflow&quot;&gt;Purpose of the workflow&lt;/h4&gt;
&lt;p&gt;Every time there is a push to &lt;code&gt;main&lt;/code&gt;, the following should be executed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pull config&lt;/strong&gt; from Vercel (includes &lt;strong&gt;production&lt;/strong&gt; environment variables).&lt;/li&gt;
&lt;li&gt;Reproducible &lt;strong&gt;build&lt;/strong&gt; in CI.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deploy&lt;/strong&gt; that artifact (without rebuilding in Vercel).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This will give you &lt;strong&gt;predictable&lt;/strong&gt; deployments that are easy to audit and debug.&lt;/p&gt;
&lt;h4 id=&quot;general-workflow-structure&quot;&gt;General workflow structure&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: Vercel PRO deployment
on:
  push:
    branches:
      - main
jobs:
  deploy:
    name: deploy
    runs-on: ubuntu-latest
    steps:
      - id: checkout-step
        name: Checkout code
        uses: actions/checkout@v4
      - id: install-vercel-cli-step
        name: Install Vercel CLI
        run: npm install --global vercel@latest
      - id: pull-vercel-env-info-step
        name: Pull Vercel Environment Information
        run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN
          }}
      - id: build-vercel-artifacts-step
        name: Build Project Artifacts
        run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
      - id: deploy-vercel-artifacts-step
        name: Deploy Project Artifacts to Vercel
        run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;br&gt;
Create the secret &lt;code&gt;VERCEL_TOKEN&lt;/code&gt; in your repository (Settings → Secrets and variables → Actions).&lt;br&gt;
Either commit &lt;code&gt;.vercel/project.json&lt;/code&gt;, or provide &lt;code&gt;VERCEL_ORG_ID&lt;/code&gt; and &lt;code&gt;VERCEL_PROJECT_ID&lt;/code&gt; as secrets when you do vercel pull.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;step-by-step&quot;&gt;Step by step&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;on: push (to main)&lt;/strong&gt;&lt;br&gt;
Each push to &lt;code&gt;main&lt;/code&gt; triggers the production pipeline. If you prefer to only deploy when merging by PR, simply avoid pushing directly to &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;uses: actions/checkout@v4&lt;/strong&gt;&lt;br&gt;
Download your code to the runner so that the following tools (Vercel CLI) have it available.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;npm install --global vercel@latest&lt;/strong&gt;&lt;br&gt;
Install the Vercel CLI.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; to avoid surprises, you can set version: vercel@xx (or whatever you use on your team).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;vercel pull --yes --environment=production&lt;/strong&gt;&lt;br&gt;
Bring the project configuration and production environment variables to &lt;code&gt;.vercel/&lt;/code&gt; on the runner.&lt;br&gt;
In CI there are no interactive questions, so we use &lt;code&gt;--yes&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.vercel/project.json&lt;/code&gt; in the repository or&lt;/li&gt;
&lt;li&gt;&lt;code&gt;VERCEL_ORG_ID&lt;/code&gt; and &lt;code&gt;VERCEL_PROJECT_ID&lt;/code&gt; as variables/secrets.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;vercel build --prod&lt;/strong&gt;&lt;br&gt;
Builds in CI using your framework (Next.js, SvelteKit, etc.) or what is defined in &lt;code&gt;vercel.json&lt;/code&gt;.&lt;br&gt;
The result is stored in &lt;code&gt;.vercel/output&lt;/code&gt; and is exactly what you are going to deploy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;vercel deploy --prebuilt --prod&lt;/strong&gt;&lt;br&gt;
Uploads the already built artifact to production.&lt;br&gt;
With &lt;code&gt;--prebuilt&lt;/code&gt;, Vercel does not rebuild: what you tested in CI is what your user will see.&lt;/p&gt;
&lt;h4 id=&quot;-final-result&quot;&gt;✅ Final result&lt;/h4&gt;
&lt;p&gt;With this workflow, each merge to &lt;code&gt;main&lt;/code&gt; runs a clean build and deploys that same artifact to production in Vercel. You get:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;reproducible builds,&lt;/li&gt;
&lt;li&gt;faster deploys,&lt;/li&gt;
&lt;li&gt;and clear CI logs for debugging.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;prefer-to-configure-it-visually-&quot;&gt;Prefer to configure it visually?&lt;/h4&gt;
&lt;p&gt;Writing workflows by hand gives you total control, but sometimes you want speed with built-in validations.&lt;/p&gt;
&lt;p&gt;With &lt;strong&gt;OctoLab&lt;/strong&gt;, you can set up this same pipeline visually: choose actions, fill in fields, and the YAML is generated instantly, ready to commit.&lt;/p&gt;
&lt;p&gt;Try it out: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.octolab.app/templates/vercel-pro-deployment&quot;&gt;https://www.octolab.app/templates/vercel-pro-deployment&lt;/a&gt;&lt;/p&gt;
&lt;h4 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;This workflow provides you with a clean &lt;strong&gt;CI → production&lt;/strong&gt; flow on every push to &lt;code&gt;main&lt;/code&gt;, using Vercel's recommended approach: &lt;strong&gt;pull → build → deploy&lt;/strong&gt;. It's easy to adapt to previews, monorepos, or strict reproducibility (fixed Node and CLI). &lt;/p&gt;
&lt;p&gt;Use it as a base and evolve it with caching, notifications, or environment gates as your team grows.&lt;/p&gt;
</description>
<category>Getting Started</category>
<guid isPermaLink="true">https://coderlegion.com/4878/deploy-your-application-on-vercel-with-github-actions</guid>
<pubDate>Wed, 20 Aug 2025 17:26:29 +0000</pubDate>
</item>
<item>
<title>Unlocking AI-Native Development: Key Highlights from GitHub Galaxy 2025</title>
<link>https://coderlegion.com/4561/unlocking-ai-native-development-key-highlights-from-github-galaxy-2025</link>
<description>&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;br&gt;
Recently, GitHub hosted Galaxy 2025, an all-virtual event spotlighting the future of &lt;strong&gt;AI-native software development&lt;/strong&gt;. This gathering offered a firsthand look at how GitHub is redefining developer workflows — from enterprise-grade security to seamless AI-powered collaboration.&lt;/p&gt;
&lt;hr&gt;
&lt;h5 id=&quot;highlights-from-galaxy-2025&quot;&gt;Highlights from Galaxy 2025&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keynote: Where AI Meets Enterprise Excellence&lt;/strong&gt;&lt;br&gt;
GitHub emphasized AI-native and &lt;strong&gt;agentic experiences&lt;/strong&gt;, underscoring the importance of &lt;strong&gt;developer choice&lt;/strong&gt; and &lt;strong&gt;robust security&lt;/strong&gt; in modern software development ([GitHub Galaxy][2]).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hands-On Secure Workflows&lt;/strong&gt;&lt;br&gt;
The event spotlighted secure DevOps workflows in action — showcasing tools that streamline collaboration while addressing vulnerabilities at scale.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Product Roadmap &amp;amp; GitHub Vision&lt;/strong&gt;&lt;br&gt;
Attendees gained insights into GitHub’s strategic vision and upcoming features designed to empower teams with AI-powered toolchains and automation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Agentic AI &amp;amp; Developer Flow&lt;/strong&gt;&lt;br&gt;
Topics such as agentic AI, maintaining developer focus, and enhancing productivity at scale were central to discussions — critical as organizations strive to innovate faster than ever ([GitHub Universe][3], [X (formerly Twitter)][4]).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h5 id=&quot;why-it-matters-for-your-organization&quot;&gt;Why It Matters for Your Organization&lt;/h5&gt;
&lt;p&gt;GitHub Galaxy 2025 wasn’t just an event—it was a glimpse into the future of software development. Whether you're leading development teams or shaping digital strategies, the insights shared are a blueprint for staying competitive and secure in an AI-forward world.&lt;/p&gt;
&lt;hr&gt;
&lt;h5 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h5&gt;
&lt;p&gt;The Galaxy logo itself—dynamic and charged with energy—reflects GitHub’s bold direction toward AI-native development. Let this be your inspiration to explore how AI-first workflows and enterprise-grade security can elevate your development lifecycle.&lt;/p&gt;
&lt;p&gt;For more info, check out this link:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://galaxy.github.com/&quot;&gt;https://galaxy.github.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Let’s stay connected:&lt;/p&gt;
&lt;p&gt;Instagram: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.instagram.com/angular_development/&quot;&gt;https://www.instagram.com/angular_development/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Facebook: &lt;a rel=&quot;nofollow&quot; href=&quot;https://m.facebook.com/learnangular2plus/&quot;&gt;https://m.facebook.com/learnangular2plus/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Threads: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.threads.net/@angular_development&quot;&gt;https://www.threads.net/@angular_development&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Medium: &lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/@eraoftech&quot;&gt;https://medium.com/@eraoftech&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;coderlegion: &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/user/Sunny&quot;&gt;https://coderlegion.com/user/Sunny&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Quora: &lt;a rel=&quot;nofollow&quot; href=&quot;https://neweraofcoding.quora.com/&quot;&gt;https://neweraofcoding.quora.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;YouTube: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.youtube.com/@neweraofcoding&quot;&gt;https://www.youtube.com/@neweraofcoding&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;LinkedIn: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/company/infowebtech/&quot;&gt;https://www.linkedin.com/company/infowebtech/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Hashnode: &lt;a rel=&quot;nofollow&quot; href=&quot;https://neweraofcoding.hashnode.dev/&quot;&gt;https://neweraofcoding.hashnode.dev/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/angulardevelopment/&quot;&gt;https://github.com/angulardevelopment/&lt;/a&gt; | &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/sunny7899/&quot;&gt;sunny7899&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;BlueSky: &lt;a rel=&quot;nofollow&quot; href=&quot;https://bsky.app/profile/neweraofcoding.bsky.social&quot;&gt;https://bsky.app/profile/neweraofcoding.bsky.social&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Substack Newsletter: &lt;a rel=&quot;nofollow&quot; href=&quot;https://codeforweb.substack.com/&quot;&gt;https://codeforweb.substack.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Pinterest: &lt;a rel=&quot;nofollow&quot; href=&quot;https://in.pinterest.com/tech_nerd_life/&quot;&gt;https://in.pinterest.com/tech_nerd_life/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;http://dev.to/&quot;&gt;dev.to&lt;/a&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/sunny7899&quot;&gt;https://dev.to/sunny7899&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Looking for web dev trainings: &lt;a rel=&quot;nofollow&quot; href=&quot;https://beginner-to-pro-training.vercel.app/&quot;&gt;https://beginner-to-pro-training.vercel.app/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Software development services: &lt;a rel=&quot;nofollow&quot; href=&quot;https://infowebtechnologies.vercel.app/&quot;&gt;https://infowebtechnologies.vercel.app/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Contribution to the web development community: &lt;a rel=&quot;nofollow&quot; href=&quot;https://code-for-next-generation.vercel.app/&quot;&gt;https://code-for-next-generation.vercel.app/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Book a session: &lt;a rel=&quot;nofollow&quot; href=&quot;https://topmate.io/softwaredev&quot;&gt;https://topmate.io/softwaredev&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Telegram Channel: &lt;a rel=&quot;nofollow&quot; href=&quot;https://t.me/neweraofcoding&quot;&gt;https://t.me/neweraofcoding&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Slack Community: &lt;a rel=&quot;nofollow&quot; href=&quot;https://join.slack.com/t/neweraofcoding/shared_invite/zt-x8vm1in4-9_VmsFrkKkG2ZFlGHRFI5Q&quot;&gt;Invite&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Discord Community: &lt;a rel=&quot;nofollow&quot; href=&quot;http://discord.gg/Nuc9YRngHz&quot;&gt;http://discord.gg/Nuc9YRngHz&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Buy me a coffee on Ko-fi: &lt;a rel=&quot;nofollow&quot; href=&quot;https://ko-fi.com/softwaredev&quot;&gt;https://ko-fi.com/softwaredev&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Ebooks: &lt;a rel=&quot;nofollow&quot; href=&quot;https://apexsunshine.gumroad.com/&quot;&gt;https://apexsunshine.gumroad.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Thank you for being a part of the community. Happy coding!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/4561/unlocking-ai-native-development-key-highlights-from-github-galaxy-2025</guid>
<pubDate>Sun, 10 Aug 2025 19:31:41 +0000</pubDate>
</item>
<item>
<title>The Ultimate Guide to Mastering Git Commands: Everything You Need to Know</title>
<link>https://coderlegion.com/1196/the-ultimate-guide-to-mastering-git-commands-everything-you-need-to-know</link>
<description>&lt;h2 id=&quot;the-ultimate-guide-to-mastering-git-commands-everything-you-need-to-know&quot;&gt;&lt;strong&gt;The Ultimate Guide to Mastering Git Commands: Everything You Need to Know&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Git is the backbone of modern software development, enabling developers to track changes, collaborate seamlessly, and manage codebases with ease. Whether you're a beginner or an experienced developer, understanding Git commands is essential for efficient version control. In this &lt;strong&gt;comprehensive guide&lt;/strong&gt;, we’ll dive deep into &lt;strong&gt;every Git command&lt;/strong&gt;, explaining their purpose, usage, and best practices. By the end of this post, you'll be equipped with the knowledge to master Git like a pro! &lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-setting-up-git-the-first-step-in-your-journey&quot;&gt;&lt;strong&gt;1. Setting Up Git: The First Step in Your Journey&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Before you start using Git, it's crucial to configure it properly. These initial steps ensure that your commits are correctly attributed and that Git behaves the way you want it to.&lt;/p&gt;
&lt;h4 id=&quot;-git-config-configuring-git-settings&quot;&gt;️ &lt;strong&gt;&lt;code&gt;git config&lt;/code&gt;&lt;/strong&gt;: Configuring Git Settings&lt;/h4&gt;
&lt;p&gt;This command allows you to set up user-specific configurations such as your name, email, and other preferences.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git config --global user.name &quot;Your Name&quot;
git config --global user.email &quot;*Emails are not allowed*&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;:&lt;br&gt;
These commands set your username and email globally across all repositories on your machine. This information will be attached to every commit you make.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Additional Tips&lt;/strong&gt;:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To check your current configuration, use:  &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git config --list
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;You can also set repository-specific configurations by omitting the &lt;code&gt;--global&lt;/code&gt; flag.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-creating-and-initializing-repositories-where-it-all-begins&quot;&gt;&lt;strong&gt;2. Creating and Initializing Repositories: Where It All Begins&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id=&quot;git-init-initializing-a-new-repository&quot;&gt;&lt;strong&gt;&lt;code&gt;git init&lt;/code&gt;&lt;/strong&gt;: Initializing a New Repository&lt;/h4&gt;
&lt;p&gt;This command creates a new Git repository in your current directory. It sets up a &lt;code&gt;.git&lt;/code&gt; folder where all the version control data will be stored.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git init
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;:&lt;br&gt;
Use this command when you’re starting a new project from scratch. It initializes a Git repository, allowing you to start tracking changes immediately.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;:&lt;br&gt;
If you have a project folder called &lt;code&gt;my-project&lt;/code&gt;, navigate to it and run &lt;code&gt;git init&lt;/code&gt;. Now, Git is ready to track changes in your project!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;git-clone-cloning-an-existing-repository&quot;&gt;&lt;strong&gt;&lt;code&gt;git clone&lt;/code&gt;&lt;/strong&gt;: Cloning an Existing Repository&lt;/h4&gt;
&lt;p&gt;If you want to work on an existing project hosted on a remote server (like GitHub), you can use the &lt;code&gt;git clone&lt;/code&gt; command.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/username/repo.git
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;:&lt;br&gt;
This command downloads the entire repository, including its history, to your local machine. It’s perfect for collaborating on open-source projects or working on team projects.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;:&lt;br&gt;
Suppose you want to contribute to a project on GitHub. You can clone the repository using the URL provided by the platform.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-checking-the-status-stay-updated-with-your-work&quot;&gt;&lt;strong&gt;3. Checking the Status: Stay Updated with Your Work&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id=&quot;git-status-understanding-the-state-of-your-repository&quot;&gt;&lt;strong&gt;&lt;code&gt;git status&lt;/code&gt;&lt;/strong&gt;: Understanding the State of Your Repository&lt;/h4&gt;
&lt;p&gt;This command provides a snapshot of your working directory and staging area. It tells you which files are modified, staged, or untracked.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git status
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;:&lt;br&gt;
Use this command to understand what’s happening in your repository. It helps you identify which files need to be committed or staged.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;:&lt;br&gt;
After making changes to your code, running &lt;code&gt;git status&lt;/code&gt; will show you which files have been modified and whether they’re staged for commit.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-tracking-and-staging-files-preparing-for-commit&quot;&gt;&lt;strong&gt;4. Tracking and Staging Files: Preparing for Commit&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id=&quot;git-add-adding-files-to-the-staging-area&quot;&gt;&lt;strong&gt;&lt;code&gt;git add&lt;/code&gt;&lt;/strong&gt;: Adding Files to the Staging Area&lt;/h4&gt;
&lt;p&gt;Before committing changes, you need to stage them using the &lt;code&gt;git add&lt;/code&gt; command. This moves files from the working directory to the staging area.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git add &amp;lt;file-name&amp;gt;       # Add a specific file
git add .                 # Add all files in the current directory
git add -u                # Add only modified or deleted files (ignores new files)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;:&lt;br&gt;
This command prepares your changes for the next commit. It’s like telling Git, “Hey, I want these changes to be part of my next snapshot.”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;:&lt;br&gt;
If you’ve made changes to &lt;code&gt;index.html&lt;/code&gt; and &lt;code&gt;style.css&lt;/code&gt;, you can stage both files using &lt;code&gt;git add index.html style.css&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-5-committing-changes-saving-your-work&quot;&gt;✅ &lt;strong&gt;5. Committing Changes: Saving Your Work&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id=&quot;git-commit-finalizing-your-changes&quot;&gt;&lt;strong&gt;&lt;code&gt;git commit&lt;/code&gt;&lt;/strong&gt;: Finalizing Your Changes&lt;/h4&gt;
&lt;p&gt;Once your changes are staged, you can commit them to the repository. A commit is like a snapshot of your project at a specific point in time.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git commit -m &quot;Commit message&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;:&lt;br&gt;
This command saves your changes to the repository with a descriptive message. The commit message should explain what changes were made and why.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;:&lt;br&gt;
After staging your changes, you can commit them with a message like:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git commit -m &quot;Added login functionality&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pro Tip&lt;/strong&gt;:&lt;br&gt;
Use &lt;code&gt;git commit -am &quot;message&quot;&lt;/code&gt; to stage and commit all tracked files in one step.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-6-viewing-the-commit-history-tracking-your-progress&quot;&gt;⏳ &lt;strong&gt;6. Viewing the Commit History: Tracking Your Progress&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id=&quot;-git-log-exploring-your-commit-history&quot;&gt;️ &lt;strong&gt;&lt;code&gt;git log&lt;/code&gt;&lt;/strong&gt;: Exploring Your Commit History&lt;/h4&gt;
&lt;p&gt;This command displays the commit history of your repository, showing who made each commit, when it was made, and the associated message.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git log
git log --oneline          # Display a concise summary
git log --author=&quot;name&quot;    # Filter commits by author
git log --since=&quot;date&quot;     # Show commits after a specific date
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;:&lt;br&gt;
Use this command to review the history of your project. It’s especially useful for debugging or understanding how the project evolved over time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;:&lt;br&gt;
Running &lt;code&gt;git log --oneline&lt;/code&gt; will give you a quick overview of recent commits, showing only the commit hash and message.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;7-branching-and-merging-managing-parallel-workflows&quot;&gt;&lt;strong&gt;7. Branching and Merging: Managing Parallel Workflows&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id=&quot;git-branch-managing-branches&quot;&gt;&lt;strong&gt;&lt;code&gt;git branch&lt;/code&gt;&lt;/strong&gt;: Managing Branches&lt;/h4&gt;
&lt;p&gt;Branches allow you to work on different features or bug fixes simultaneously without affecting the main codebase.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git branch                  # List all branches
git branch &amp;lt;branch-name&amp;gt;    # Create a new branch
git branch -d &amp;lt;branch-name&amp;gt; # Delete a branch
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;:&lt;br&gt;
Branches are essential for collaboration. They let multiple developers work on different parts of a project without interfering with each other.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;:&lt;br&gt;
To create a new branch for a feature, use:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git branch feature-login
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;-git-merge-combining-branches&quot;&gt;↔️ &lt;strong&gt;&lt;code&gt;git merge&lt;/code&gt;&lt;/strong&gt;: Combining Branches&lt;/h4&gt;
&lt;p&gt;Once you’ve completed work on a branch, you can merge it back into the main branch (usually &lt;code&gt;main&lt;/code&gt; or &lt;code&gt;master&lt;/code&gt;).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git merge &amp;lt;branch-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;:&lt;br&gt;
This command integrates changes from one branch into another. It’s commonly used to merge feature branches into the main branch.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;:&lt;br&gt;
After finishing work on the &lt;code&gt;feature-login&lt;/code&gt; branch, you can merge it into &lt;code&gt;main&lt;/code&gt; using:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git checkout main
git merge feature-login
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;git-rebase-reapplying-commits&quot;&gt;&lt;strong&gt;&lt;code&gt;git rebase&lt;/code&gt;&lt;/strong&gt;: Reapplying Commits&lt;/h4&gt;
&lt;p&gt;Rebasing allows you to move or combine a sequence of commits to a new base commit.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git rebase &amp;lt;base&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;:&lt;br&gt;
Rebasing is useful for keeping a clean, linear commit history. It avoids unnecessary merge commits.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;:&lt;br&gt;
If you’re working on a feature branch and want to incorporate the latest changes from &lt;code&gt;main&lt;/code&gt;, you can rebase your branch:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git checkout feature-login
git rebase main
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;8-remote-repository-management-collaborating-with-others&quot;&gt;&lt;strong&gt;8. Remote Repository Management: Collaborating with Others&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id=&quot;git-remote-managing-remote-repositories&quot;&gt;&lt;strong&gt;&lt;code&gt;git remote&lt;/code&gt;&lt;/strong&gt;: Managing Remote Repositories&lt;/h4&gt;
&lt;p&gt;Remote repositories are hosted on platforms like GitHub, GitLab, or Bitbucket. They allow you to share your code with others.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git remote -v               # List all remotes
git remote add &amp;lt;remote-name&amp;gt; &amp;lt;url&amp;gt;  # Add a new remote
git remote remove &amp;lt;remote-name&amp;gt;     # Remove a remote
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;:&lt;br&gt;
This command helps you connect your local repository to a remote repository, enabling collaboration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;:&lt;br&gt;
To add a remote repository called &lt;code&gt;origin&lt;/code&gt;, use:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git remote add origin https://github.com/username/repo.git
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;git-fetch-fetching-changes-from-remote&quot;&gt;&lt;strong&gt;&lt;code&gt;git fetch&lt;/code&gt;&lt;/strong&gt;: Fetching Changes from Remote&lt;/h4&gt;
&lt;p&gt;This command retrieves updates from a remote repository but doesn’t merge them into your local branch.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git fetch &amp;lt;remote-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;:&lt;br&gt;
Use this command to see what changes have been made on the remote repository without affecting your local work.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;git-pull-fetching-and-merging-changes&quot;&gt;&lt;strong&gt;&lt;code&gt;git pull&lt;/code&gt;&lt;/strong&gt;: Fetching and Merging Changes&lt;/h4&gt;
&lt;p&gt;This command fetches changes from the remote repository and merges them into your current branch.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git pull &amp;lt;remote-name&amp;gt; &amp;lt;branch-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;:&lt;br&gt;
It’s a combination of &lt;code&gt;git fetch&lt;/code&gt; and &lt;code&gt;git merge&lt;/code&gt;. Use it to update your local branch with the latest changes from the remote.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;git-push-pushing-changes-to-remote&quot;&gt;&lt;strong&gt;&lt;code&gt;git push&lt;/code&gt;&lt;/strong&gt;: Pushing Changes to Remote&lt;/h4&gt;
&lt;p&gt;After committing changes locally, you can push them to the remote repository.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git push &amp;lt;remote-name&amp;gt; &amp;lt;branch-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;:&lt;br&gt;
This command uploads your local commits to the remote repository, making them available to others.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;:&lt;br&gt;
To push your &lt;code&gt;main&lt;/code&gt; branch to the &lt;code&gt;origin&lt;/code&gt; remote, use:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git push origin main
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-9-tagging-marking-important-milestones&quot;&gt;️ &lt;strong&gt;9. Tagging: Marking Important Milestones&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id=&quot;-git-tag-creating-tags&quot;&gt;️ &lt;strong&gt;&lt;code&gt;git tag&lt;/code&gt;&lt;/strong&gt;: Creating Tags&lt;/h4&gt;
&lt;p&gt;Tags are used to mark specific points in your repository’s history, usually for releases.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git tag &amp;lt;tag-name&amp;gt;                     # Create a lightweight tag
git tag -a &amp;lt;tag-name&amp;gt; -m &quot;Tag message&quot; # Create an annotated tag
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;:&lt;br&gt;
Tags are useful for marking versions of your project (e.g., &lt;code&gt;v1.0&lt;/code&gt;, &lt;code&gt;v2.0&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;:&lt;br&gt;
To create a tag for version 1.0, use:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git tag -a v1.0 -m &quot;Version 1.0 release&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;10-resolving-conflicts-handling-merge-issues&quot;&gt;&lt;strong&gt;10. Resolving Conflicts: Handling Merge Issues&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id=&quot;git-diff-identifying-differences&quot;&gt;&lt;strong&gt;&lt;code&gt;git diff&lt;/code&gt;&lt;/strong&gt;: Identifying Differences&lt;/h4&gt;
&lt;p&gt;This command shows differences between commits, branches, or the working directory.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git diff
git diff --staged
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;:&lt;br&gt;
Use this command to identify conflicts or differences between files.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;git-merge---abort-aborting-a-merge&quot;&gt;&lt;strong&gt;&lt;code&gt;git merge --abort&lt;/code&gt;&lt;/strong&gt;: Aborting a Merge&lt;/h4&gt;
&lt;p&gt;If a merge results in conflicts, you can abort the merge process.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git merge --abort
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;:&lt;br&gt;
This command cancels the merge and restores the branch to its previous state.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;11-undoing-changes-fixing-mistakes&quot;&gt;&lt;strong&gt;11. Undoing Changes: Fixing Mistakes&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id=&quot;git-reset-resetting-changes&quot;&gt;&lt;strong&gt;&lt;code&gt;git reset&lt;/code&gt;&lt;/strong&gt;: Resetting Changes&lt;/h4&gt;
&lt;p&gt;This command resets the current HEAD to a specified state.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git reset --hard HEAD~1  # Discard the last commit
git reset --soft HEAD~1  # Keep changes in the working directory
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;:&lt;br&gt;
Use this command to undo commits or reset your repository to a previous state.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;git-revert-reverting-commits&quot;&gt;&lt;strong&gt;&lt;code&gt;git revert&lt;/code&gt;&lt;/strong&gt;: Reverting Commits&lt;/h4&gt;
&lt;p&gt;This command creates a new commit that undoes changes introduced by a previous commit.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git revert &amp;lt;commit-hash&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;:&lt;br&gt;
Use this command to safely undo changes without altering the commit history.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;12-cleaning-up-removing-untracked-files&quot;&gt;&lt;strong&gt;12. Cleaning Up: Removing Untracked Files&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id=&quot;git-clean-removing-untracked-files&quot;&gt;&lt;strong&gt;&lt;code&gt;git clean&lt;/code&gt;&lt;/strong&gt;: Removing Untracked Files&lt;/h4&gt;
&lt;p&gt;This command removes untracked files from your working directory.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clean -fd
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;:&lt;br&gt;
Use this command to clean up your working directory by removing files that aren’t being tracked by Git.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;13-searching-finding-specific-code&quot;&gt;&lt;strong&gt;13. Searching: Finding Specific Code&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id=&quot;git-grep-searching-for-strings&quot;&gt;&lt;strong&gt;&lt;code&gt;git grep&lt;/code&gt;&lt;/strong&gt;: Searching for Strings&lt;/h4&gt;
&lt;p&gt;This command searches for specific strings within your repository.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git grep &quot;search-term&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;:&lt;br&gt;
Use this command to quickly find specific code snippets or text within your project.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;14-stashing-changes-temporarily-saving-work&quot;&gt;&lt;strong&gt;14. Stashing Changes: Temporarily Saving Work&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id=&quot;git-stash-stashing-changes&quot;&gt;&lt;strong&gt;&lt;code&gt;git stash&lt;/code&gt;&lt;/strong&gt;: Stashing Changes&lt;/h4&gt;
&lt;p&gt;This command temporarily saves changes that aren’t ready to be committed.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git stash
git stash list
git stash apply
git stash pop
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;:&lt;br&gt;
Use this command to save your work in progress without committing it, allowing you to switch branches or fix urgent issues.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;15-ignoring-files-keeping-your-repository-clean&quot;&gt;&lt;strong&gt;15. Ignoring Files: Keeping Your Repository Clean&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id=&quot;-gitignore-ignoring-files&quot;&gt;&lt;strong&gt;&lt;code&gt;.gitignore&lt;/code&gt;&lt;/strong&gt;: Ignoring Files&lt;/h4&gt;
&lt;p&gt;Create a &lt;code&gt;.gitignore&lt;/code&gt; file to specify files or directories that Git should ignore.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Purpose&lt;/strong&gt;:&lt;br&gt;
Use this file to prevent unnecessary files (like logs, temporary files, or dependencies) from being tracked by Git.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;conclusion-mastering-git-commands&quot;&gt;&lt;strong&gt;Conclusion: Mastering Git Commands&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Git is a powerful tool that empowers developers to manage complex projects efficiently. By mastering these commands, you’ll be able to track changes, collaborate with others, and maintain a clean, organized codebase. Remember, practice makes perfect—experiment with these commands in your own projects to become a Git expert! &lt;/p&gt;
&lt;hr&gt;
&lt;p&gt; &lt;strong&gt;Happy Coding!&lt;/strong&gt; &lt;/p&gt;
</description>
<category>Web Dev</category>
<guid isPermaLink="true">https://coderlegion.com/1196/the-ultimate-guide-to-mastering-git-commands-everything-you-need-to-know</guid>
<pubDate>Fri, 21 Feb 2025 14:45:30 +0000</pubDate>
</item>
<item>
<title>Ever wondered how to automate your development workflow effortlessly?</title>
<link>https://coderlegion.com/1139/ever-wondered-how-to-automate-your-development-workflow-effortlessly</link>
<description>&lt;p&gt;CI/CD refers to the combined practices of continuous integration (CI) and continuous delivery (CD), or  deployment, which aims to streamline and accelerate the software development lifecycle.&lt;/p&gt;
&lt;p&gt;It's a method that allows software development and IT teams to deliver code changes more frequently and reliably.&lt;/p&gt;
&lt;p&gt;CI/CD is often referred to as a &quot;CI/CD pipeline&quot; and is supported by development and operations teams working together in an agile way.&lt;/p&gt;
&lt;h2 id=&quot;wondering-how-to-bring-ci-cd-to-your-github-project-&quot;&gt;Wondering how to bring CI/CD to your GitHub project?&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;GitHub Actions is the key to making it happen!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;CI/CD can be made easier using GitHub Actions.&lt;/p&gt;
&lt;p&gt;GitHub Actions is a CI/CD tool that automates software development workflows directly within your GitHub repository. &lt;/p&gt;
&lt;p&gt;It allows you to automate tasks across pull requests and issues.&lt;/p&gt;
&lt;p&gt;GitHub Actions is designed to simplify workflows with flexible automation and easy-to-use CI/CD capabilities.&lt;/p&gt;
&lt;p&gt;It brings continuous integration (CI) and continuous delivery (CD) directly to the GitHub flow.&lt;/p&gt;
&lt;h3 id=&quot;you-ll-have-to-know-a-few-core-concepts-before-working-with-github-actions-&quot;&gt;You’ll have to know a few core concepts before working with GitHub Actions:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Workflows&lt;/strong&gt;: Automated processes comprised of jobs triggered by events; defined in YAML files within the .github/workflows directory.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Events&lt;/strong&gt;: Triggers that start a workflow, such as creating a branch or opening a pull request.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Jobs&lt;/strong&gt;: Tasks executed in a workflow, which can run in parallel.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Actions&lt;/strong&gt;: Reusable code packages that perform complex tasks within workflows, like sending notifications. They can be custom or from the GitHub Marketplace.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Runners&lt;/strong&gt;: The servers that execute your workflows.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can tailor and build GitHub Actions to automate any tasks you need.&lt;/p&gt;
&lt;p&gt;You’ll have to know some basic YAML to start your GitHub Actions journey.&lt;/p&gt;
&lt;p&gt;However, you don’t have to know it by heart.&lt;/p&gt;
&lt;p&gt;You can refer to the documentation and do it on your own. &lt;/p&gt;
&lt;p&gt;The only thing you’ll have to remember is how it works.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you ask me, &quot;Have you worked with GitHub Actions?&quot;—my answer is a definite &quot;Yes!&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;GitHub Actions will make your life easier when you are maintaining any open-source projects.&lt;/p&gt;
&lt;p&gt;It will reduce your workload 10x when you have automated the CI/CD through GitHub Actions.&lt;/p&gt;
&lt;p&gt;I have used GitHub Actions for my open-source project CampX. It made my maintenance job easier.&lt;/p&gt;
&lt;p&gt;You can use GitHub Actions for any job that you have to carry out in your open-source project; it can be a simple or complex job.&lt;/p&gt;
&lt;h3 id=&quot;i-ve-used-github-actions-&quot;&gt;I’ve used GitHub Actions:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;To greet the contributor when they create their first issue on my project.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To add labels to the issues and pull requests created.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To validate the PRs, ensuring they’ve filled in all the necessary details in the PR template.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are just simple tasks, but you’ll save so much time automating them using GitHub Actions.&lt;/p&gt;
&lt;p&gt;You can use GitHub Actions to check if code changes work properly or if they break the website—and so much more.&lt;/p&gt;
&lt;p&gt;If you feel too lazy to create a GitHub action from scratch, the GitHub Marketplace has thousands of GitHub actions ready to use.&lt;/p&gt;
&lt;p&gt;Here are some examples of the GitHub Actions workflows that I used:&lt;/p&gt;
&lt;p&gt;The below workflow file is what I used for greeting the contributors.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://media.licdn.com/dms/image/v2/D4D12AQH3f_Gdx71kcQ/article-inline_image-shrink_1000_1488/B4DZUFDka1GkAU-/0/1739546584902?e=1745452800&amp;amp;v=beta&amp;amp;t=_3slwo7pBxD6jTplbyElW9bN_5N9FzFNFADXJIWdYts&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The below workflow file is what I used for labelling the issues and PRs.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://media.licdn.com/dms/image/v2/D4D12AQHkA7tEVQEtOg/article-inline_image-shrink_1500_2232/B4DZUFEK40G8AU-/0/1739546742502?e=1745452800&amp;amp;v=beta&amp;amp;t=HOct2xlbuDqq9TSJmf-ohXgtHOTsmBXukggbIzs0hDQ&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The below workflow file is what I used for validating the PRs.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://media.licdn.com/dms/image/v2/D4D12AQG3ZNq0k0Z-Mw/article-inline_image-shrink_1500_2232/B4DZUFEaIAHwAY-/0/1739546808430?e=1745452800&amp;amp;v=beta&amp;amp;t=SxzqFKkr5w_bfBhkTimVu95a_ziAJS7tha8uAKakCFU&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;GitHub Actions is a game-changer for automating workflows and simplifying CI/CD. &lt;/p&gt;
&lt;p&gt;Whether you're managing an open-source project or streamlining deployments, it saves time and effort.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Have you tried GitHub Actions in your projects? Let me know how it's helped you or what you're looking to automate next!&lt;/strong&gt;&lt;/p&gt;
</description>
<category>CI/CD Pipelines</category>
<guid isPermaLink="true">https://coderlegion.com/1139/ever-wondered-how-to-automate-your-development-workflow-effortlessly</guid>
<pubDate>Tue, 18 Feb 2025 11:39:33 +0000</pubDate>
</item>
</channel>
</rss>