﻿<feed xmlns="http://www.w3.org/2005/Atom">
  <title type="text" xml:lang="en">Lefthook</title>
  <link type="application/atom+xml" href="https://d.moonfire.us/tags/lefthook/atom.xml" rel="self" />
  <link type="text/html" href="https://d.moonfire.us/tags/lefthook/" rel="alternate" />
  <updated>2026-03-16T17:43:08Z</updated>
  <id>https://d.moonfire.us/tags/lefthook/</id>
  <author>
    <name>D. Moonfire</name>
  </author>
  <rights>Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International</rights>
  <entry>
    <title>Using Nitride - Introduction</title>
    <link rel="alternate" href="https://d.moonfire.us/blog/2025/06/07/using-nitride-introduction/" />
    <updated>2025-06-07T05:00:00Z</updated>
    <id>https://d.moonfire.us/blog/2025/06/07/using-nitride-introduction/</id>
    <category term="development" scheme="https://d.moonfire.us/categories/" label="Development" />
    <category term="mfgames-nitride" scheme="https://d.moonfire.us/tags/" label="MfGames.Nitride" />
    <category term="bitrot" scheme="https://d.moonfire.us/tags/" label="Bitrot" />
    <category term="nixos" scheme="https://d.moonfire.us/tags/" label="NixOS" />
    <category term="direnv" scheme="https://d.moonfire.us/tags/" label="direnv" />
    <category term="lefthook" scheme="https://d.moonfire.us/tags/" label="Lefthook" />
    <category term="editorconfig" scheme="https://d.moonfire.us/tags/" label="EditorConfig" />
    <category term="conventional-commits" scheme="https://d.moonfire.us/tags/" label="Conventional Commits" />
    <category term="gemini" scheme="https://d.moonfire.us/tags/" label="Gemini" />
    <summary type="html">I'm going to start a long, erratic journey to update my publisher website. Along the way, I'm going to document how to create a MfGames.Nitride website from the ground up, along with a lot of little asides about reasoning or purpose.
</summary>
    <content type="html">&lt;p&gt;One drawback of having a lot of irons in the fire is occasionally projects that should not have been forgotten are and they &lt;a href="/tags/bitrot/"&gt;bitrow&lt;/a&gt;. A good example is the &lt;a href="https://typewriter.press"&gt;Typewriter Press&lt;/a&gt; and it's associated reflected websites:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://sassy.typewriter.press"&gt;Sassy&lt;/a&gt; - Romance novels&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dusty.typewriter.press"&gt;Dusty&lt;/a&gt; - Historical fiction&lt;/li&gt;
&lt;li&gt;&lt;a href="https://broken.typewriter.press"&gt;Broken&lt;/a&gt; - Fantasy including contemporary&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Eventually, when I start publishing my sci-fi stories, they would go under &amp;ldquo;Electric&amp;rdquo;. I call them &amp;ldquo;reflected&amp;rdquo; sites because they are just filtered versions of the main Typewriter site with some slightly different branding. That way, if I wrote more romance, I could put them there and folks could go to the romance-only site or jump over to the main one to see that I have no single genre (much like I don't have a single genre for my writing).&lt;/p&gt;
&lt;p&gt;However, it's been years since I've updated those sites and they are beginning to creak underneath their age. Not to mention, I haven't included the last two books I published on them and that is humiliating for me.&lt;/p&gt;
&lt;p&gt;So, I decided to switch the sites over to &lt;a href="/tags/mfgames-nitride/"&gt;MfGames.Nitride&lt;/a&gt; and document the process.&lt;/p&gt;
&lt;h2&gt;Series&lt;/h2&gt;
&lt;p&gt;This is going to be broken into multiple posts, mostly because I know I'm going to get distracted around the fifteenth of the month when I switch to writing, but also to keep these topic-focused.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/07/using-nitride-introduction/"&gt;2025-06-07 Using Nitride - Introduction&lt;/a&gt; - I'm going to start a long, erratic journey to update my publisher website. Along the way, I'm going to document how to create a MfGames.Nitride website from the ground up, along with a lot of little asides about reasoning or purpose.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/08/using-nitride-pipelines/"&gt;2025-06-08 Using Nitride - Pipelines&lt;/a&gt; - The first major concept of Nitride is the idea of a &amp;quot;pipeline&amp;quot; which are the largest units of working in the system. This shows a relatively complex setup of pipelines that will be used to generate the website.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/09/using-nitride-entities/"&gt;2025-06-09 Using Nitride - Entities&lt;/a&gt; - The continued adventures of creating the Typewriter website using MfGames.Nitride, a C# static site generator. The topic of today is the Entity class and how Nitride uses an ECS (Entity-Component-System) to generate pages.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/10/using-nitride-markdown/"&gt;2025-06-10 Using Nitride - Markdown&lt;/a&gt; - Examples and explanations of converting Markdown to HTML using MfGames.Nitride and MarkDig.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/12/using-nitride-front-matter/"&gt;2025-06-12 Using Nitride - Front Matter&lt;/a&gt; - How to use Nitride to take the front matter from pages and add them as a component into the Entity class.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conventions&lt;/h2&gt;
&lt;p&gt;My usual conventions when giving file names is to use &lt;code&gt;//&lt;/code&gt; for the Git repository root. So, &lt;code&gt;//flake.nix&lt;/code&gt; means the &lt;code&gt;flake.nix&lt;/code&gt; in the root level of the project. Also, I'll add a trailing &lt;code&gt;/&lt;/code&gt; when I'm talking specifically about a directory, such as &lt;code&gt;//node_modules/&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;NixOS Flakes&lt;/h2&gt;
&lt;p&gt;Since I'm going to be creating a new site instead of converting the existing, I can easily start from the beginning. That usually means starting with a NixOS flake which is my preferred way of writing.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-nix"&gt;# //flake.nix
{
  inputs = {
    nixpkgs.url = &amp;quot;nixpkgs/nixos-25.05&amp;quot;;
    flake-utils.url = &amp;quot;github:numtide/flake-utils&amp;quot;;
    mfgames-project-setup.url = &amp;quot;git+https://src.mfgames.com/nixos-contrib/mfgames-project-setup-flake.git&amp;quot;;
  };

  outputs = inputs @ { self, nixpkgs, flake-utils, mfgames-project-setup }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
        project-config = mfgames-project-setup.lib.mkConfig {
          inherit system;
          pkgs = nixpkgs.legacyPackages.${system};
          dotnet.enable = true;
          prettier.proseWrap = &amp;quot;never&amp;quot;;
        };
      in
      {
        devShell = pkgs.mkShell {
          packages = [
            # Development
            pkgs.dotnet-sdk
            pkgs.nodejs_20

            # Local Serving
            pkgs.miniserve
            pkgs.agate
          ]
          ++ project-config.packages;

          shellHook = project-config.shellHook;
        };
      });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There really isn't much to this other than my use of &lt;a href="https://mfgames.com/mfgames-project-setup-flake/"&gt;mfgames-project-setup-flake&lt;/a&gt;. This is a flake that sets up a lot of the common elements I use: a consistent &lt;code&gt;.editorconfig&lt;/code&gt;, &lt;code&gt;treefmt&lt;/code&gt; for formatting various parts of the system, &lt;code&gt;lefthook&lt;/code&gt; for setting up &lt;a href="/tags/conventional-commits/"&gt;conventional commits&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And naturally, I have to set up &lt;a href="/tags/direnv/"&gt;direnv&lt;/a&gt; because I want to set up my environment as soon as I change into the directory.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# //.envrc
use flake || use nix

dotenv_if_exists .env

PATH_add $PWD/node_modules/.bin
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I'm adding the &lt;code&gt;node_modules&lt;/code&gt; path because I know I'm going to be using &lt;code&gt;webpack&lt;/code&gt; as part of this site.&lt;/p&gt;
&lt;p&gt;Once those two files are in, then I can get my basic setup.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;$ git add flake.nix .envrc
$ direnv allow
direnv: nix-direnv: Renewed cache
nixago: updating repository files
nixago: '.conform.yaml' link updated
nixago: '/.conform.yaml' added to .gitignore
nixago: '.editorconfig' copy created
nixago: 'lefthook.yml' link updated
Error: unknown shorthand flag: 'a' in -a
Error: unknown shorthand flag: 'a' in -a
nixago: '/lefthook.yml' added to .gitignore
nixago: '.prettierrc.json' link updated
nixago: '/.prettierrc.json' added to .gitignore
nixago: 'treefmt.toml' link updated
nixago: '/treefmt.toml' added to .gitignore
mfgames-project-setup: '/.direnv/' added to .gitignore
sync hooks: ✔️ (commit-msg, pre-commit)
direnv: export +AR +AS +AWS_ACCESS_KEY_ID +AWS_BUCKET +AWS_ENDPOINT +AWS_REGION +AWS_SECRET_ACCESS_KEY +CC +CONFIG_SHELL +CXX +DOTNET_CLI_TELEMETRY_OPTOUT +DOTNET_NOLOGO +DOTNET_SKIP_FIRST_TIME_EXPERIENCE +DOTNET_SKIP_WORKLOAD_INTEGRITY_CHECK +HOST_PATH +IN_NIX_SHELL +LD +MSBUILDALWAYSOVERWRITEREADONLYFILES +MSBUILDTERMINALLOGGER +NIX_BINTOOLS +NIX_BINTOOLS_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu +NIX_BUILD_CORES +NIX_CC +NIX_CC_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu +NIX_CFLAGS_COMPILE +NIX_ENFORCE_NO_NATIVE +NIX_HARDENING_ENABLE +NIX_LDFLAGS +NIX_STORE +NM +NODE_PATH +OBJCOPY +OBJDUMP +RANLIB +READELF +SIZE +SOURCE_DATE_EPOCH +STRINGS +STRIP +__structuredAttrs +buildInputs +buildPhase +builder +cmakeFlags +configureFlags +depsBuildBuild +depsBuildBuildPropagated +depsBuildTarget +depsBuildTargetPropagated +depsHostHost +depsHostHostPropagated +depsTargetTarget +depsTargetTargetPropagated +doCheck +doInstallCheck +dontAddDisableDepTrack +mesonFlags +name +nativeBuildInputs +out +outputs +patches +phases +preferLocalBuild +propagatedBuildInputs +propagatedNativeBuildInputs +shell +shellHook +stdenv +strictDeps +system ~PATH ~XDG_DATA_DIRS
$ l
total 64K
drwxr-xr-x  5 dmoonfire users 4.0K Jun  7 11:10 .
drwxrwxr-x 18 dmoonfire users 4.0K Dec 26 08:08 ..
lrwxrwxrwx  1 dmoonfire users   56 Jun  7 11:10 .conform.yaml -&amp;gt; /nix/store/pckycri07q2ah90swk6hf21ilsi59a4s-conform.yaml
drwxr-xr-x  4 dmoonfire users 4.0K Jun  7 11:10 .direnv
-rw-r--r--  1 dmoonfire users 5.2K Jun  7 11:10 .editorconfig
-rw-------  1 dmoonfire users  259 Jun  7 10:40 .env
-rw-r--r--  1 dmoonfire users  13K Jun  7 11:10 flake.lock
-rw-r--r--  1 dmoonfire users  983 Jun  7 11:03 flake.nix
drwxrwxr-x  8 dmoonfire users 4.0K Jun  7 11:10 .git
-rw-r--r--  1 dmoonfire users  139 Jun  7 11:10 .gitignore
lrwxrwxrwx  1 dmoonfire users   56 Jun  7 11:10 lefthook.yml -&amp;gt; /nix/store/ihdb9vnc6jp535m43ndas79s4p9viyjn-lefthook.yml
lrwxrwxrwx  1 dmoonfire users   59 Jun  7 11:10 .prettierrc.json -&amp;gt; /nix/store/mjdm21r27r4n43aq92qkvmdcvy0l2qrc-prettierrc.json
-rw-r--r--  1 dmoonfire users 1.2K Jun  7 10:40 README.md
lrwxrwxrwx  1 dmoonfire users   56 Jun  7 11:10 treefmt.toml -&amp;gt; /nix/store/4lk6rmb2khs5l3yn91rah9y4c6zmxybf-treefmt.toml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Probably one of my favorite things is that it also sets up the initial &lt;code&gt;.gitignore&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;$ cat .gitignore
# nixago: ignore-linked-files
/treefmt.toml
/.prettierrc.json
/lefthook.yml
/.conform.yaml
# mfgames-project-setup: ignore-files
/.direnv/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Overall, using that just cuts out that first hour of starting up a new project. And it lets me evolve my changes to style and different tools by simply running &lt;code&gt;nix flake update&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Directory Layout&lt;/h2&gt;
&lt;p&gt;Related to using a flake to setup a lot of the boilerplate, I have &lt;a href="//d.moonfire.us/garden/project-layout/"&gt;written my thoughts&lt;/a&gt; on setting up polyglot projects such as this in a consistent manner. I think it is a relatively short digital garden plot, but there are a couple applicable things.&lt;/p&gt;
&lt;p&gt;Source code goes into &lt;code&gt;//src/&lt;/code&gt; and the output is going into &lt;code&gt;//build&lt;/code&gt; (which has an entry in &lt;code&gt;.gitignore&lt;/code&gt;). One of the artifacts of my &lt;a href="/tags/wordpress/"&gt;WordPress&lt;/a&gt; days is that I have static pages, which I put into &lt;code&gt;//src/pages/&lt;/code&gt;, and blog posts which go into &lt;code&gt;//src/posts/&lt;/code&gt;. There is some magic that goes on with posts, but that's a different post.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;$ find src
src
src/pages
src/pages/typewriter
src/pages/typewriter/index.md
src/posts
src/posts/2025-07-01-website-redesign.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I put an extra layer of directories under &lt;code&gt;//src/pages/&lt;/code&gt; for a site &amp;ldquo;slug&amp;rdquo; to identify which site the page will go on. In this case, I'm going to have &lt;code&gt;typewriter&lt;/code&gt; for the main site, &lt;code&gt;sassy&lt;/code&gt;, &lt;code&gt;dusty&lt;/code&gt;, &lt;code&gt;broken&lt;/code&gt;, and &lt;code&gt;electric&lt;/code&gt;. There will also be a &lt;code&gt;common&lt;/code&gt; for pages that are shared across all pages such as the contact page.&lt;/p&gt;
&lt;p&gt;For output, those same slugs are going to be used. I'm intending to have this output both HTML and Gemtext pages for HTTPS and &lt;a href="/tags/gemini/"&gt;Gemini&lt;/a&gt; respectively. I know not a lot of people use Gemini these days, but I still think it is useful and there is a certain minimalism to it that appeals to me.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;find build
build
build/typewriter
build/typewriter/html
build/typewriter/gemtext
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Just&lt;/h2&gt;
&lt;p&gt;Another part of my standard project layout is using &lt;a href="/tags/just/"&gt;Just&lt;/a&gt; to handle the build system. There are a number of reasons, but I'm going to be dealing with Node programs, such as &lt;code&gt;webpack&lt;/code&gt;, and also .NET projects for the build. There is also CLI executables for hosting locally (&lt;code&gt;miniserv&lt;/code&gt; and &lt;code&gt;agate&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;I made a conscious decision not to write wrappers for Node and the other CLIs into Nitride. There is the ability to execute, but I don't want to tightly tie the site generator to a specific tool. Not to mention, most of the time, I want to set them up in different tabs to watch them so I want a more &amp;ldquo;generic&amp;rdquo; task runner than the ones built into Node or .NET which have some biases I've struggled with.&lt;/p&gt;
&lt;p&gt;Just is something that is self-contained, doesn't make a lot of assumptions, and doesn't drag an entire ecosystem in with it.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-just"&gt;# //Justfile
set dotenv-load

_default:
    just --choose

# Cleans up the project without removing the local .env
clean:
    git clean -xfd --exclude .env

# Builds all the websites
build: build-typewriter

# Builds the typewriter.press website
build-typewriter:
    echo I did something

# Serves the Typewriter website
serve-typewriter-html:
    miniserve --index index.html build/typewriter/html

# Serves the Typewriter Gemini pod
serve-typewriter-gemtext:
    mkdir -p .cache/agate
    agate --content build/typewriter/gemtext --certs .cache/agate --hostname localhost
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Naturally, &lt;code&gt;.cache/&lt;/code&gt; has to be added to the &lt;code&gt;.gitignore&lt;/code&gt; file. I use &lt;code&gt;.cache&lt;/code&gt; from the project layout plot.&lt;/p&gt;
&lt;h2&gt;What's Next&lt;/h2&gt;
&lt;p&gt;Now that we have the basic boilerplate for a new project, time to start adding .NET into the mix.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/07/using-nitride-introduction/"&gt;2025-06-07 Using Nitride - Introduction&lt;/a&gt; - I'm going to start a long, erratic journey to update my publisher website. Along the way, I'm going to document how to create a MfGames.Nitride website from the ground up, along with a lot of little asides about reasoning or purpose.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/08/using-nitride-pipelines/"&gt;2025-06-08 Using Nitride - Pipelines&lt;/a&gt; - The first major concept of Nitride is the idea of a &amp;quot;pipeline&amp;quot; which are the largest units of working in the system. This shows a relatively complex setup of pipelines that will be used to generate the website.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/09/using-nitride-entities/"&gt;2025-06-09 Using Nitride - Entities&lt;/a&gt; - The continued adventures of creating the Typewriter website using MfGames.Nitride, a C# static site generator. The topic of today is the Entity class and how Nitride uses an ECS (Entity-Component-System) to generate pages.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/10/using-nitride-markdown/"&gt;2025-06-10 Using Nitride - Markdown&lt;/a&gt; - Examples and explanations of converting Markdown to HTML using MfGames.Nitride and MarkDig.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/12/using-nitride-front-matter/"&gt;2025-06-12 Using Nitride - Front Matter&lt;/a&gt; - How to use Nitride to take the front matter from pages and add them as a component into the Entity class.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Semantic Release and Woodpecker CI</title>
    <link rel="alternate" href="https://d.moonfire.us/blog/2022/08/07/semantic-release-and-woodpecker-ci/" />
    <updated>2022-08-07T05:00:00Z</updated>
    <id>https://d.moonfire.us/blog/2022/08/07/semantic-release-and-woodpecker-ci/</id>
    <category term="development" scheme="https://d.moonfire.us/categories/" label="Development" />
    <category term="semantic-release" scheme="https://d.moonfire.us/tags/" label="Semantic Release" />
    <category term="woodpecker-ci" scheme="https://d.moonfire.us/tags/" label="Woodpecker CI" />
    <category term="gitlab" scheme="https://d.moonfire.us/tags/" label="Gitlab" />
    <category term="gitea" scheme="https://d.moonfire.us/tags/" label="Gitea" />
    <category term="conventional-commits" scheme="https://d.moonfire.us/tags/" label="Conventional Commits" />
    <category term="gitversion" scheme="https://d.moonfire.us/tags/" label="GitVersion" />
    <category term="lefthook" scheme="https://d.moonfire.us/tags/" label="Lefthook" />
    <category term="sourcehut" scheme="https://d.moonfire.us/tags/" label="Sourcehut" />
    <category term="fedran" scheme="https://d.moonfire.us/tags/" label="Fedran" />
    <category term="project-layout" scheme="https://d.moonfire.us/tags/" label="Project Layout" />
    <summary type="html">In my migration from GitLab to Gitea, I've started moving my CI/CD server over to Woodpecker. Here is some of the struggles I've done through in the process of getting it to work.
</summary>
    <content type="html">&lt;p&gt;With the recent drama of &lt;a href="/tags/gitlab/"&gt;GitLab&lt;/a&gt;, both with the CI/CD changes and then more recent possible threat of deleting old repositories, I continue my migration to a local &lt;a href="/tags/gitea/"&gt;Gitea&lt;/a&gt; instance, &lt;a href="https://src.mfgames.com/"&gt;https://src.mfgames.com/&lt;/a&gt; for the bulk of my code and writing.&lt;/p&gt;
&lt;p&gt;For the most part, migrating is just a matter of shuffling data. I have a &lt;em&gt;lot&lt;/em&gt; of repositories, both active and inactive, and it will take me months to move them over. Plus I haven't decided if I'm going to purge them from my GitLab account so there is a single source of truth or just mirror back to them.&lt;/p&gt;
&lt;p&gt;Currently, the most difficult task was figuring out how to handle the build processing. I've mentioned previously that I use &lt;a href="/tags/conventional-commits/"&gt;Conventional Commits&lt;/a&gt; and &lt;a href="/tags/semantic-release/"&gt;Semantic Release&lt;/a&gt; fairly heavily. I've branched out a little from there using &lt;a href="/tags/lefthook/"&gt;Lefthook&lt;/a&gt; and my &lt;a href="/garden/project-layout/"&gt;project layout&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Currently, the CI does the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Build the project&lt;/li&gt;
&lt;li&gt;Test various conditions including valid commit messages&lt;/li&gt;
&lt;li&gt;If the commits indicate a new build:
&lt;ol&gt;
&lt;li&gt;Tag it&lt;/li&gt;
&lt;li&gt;Build the release version&lt;/li&gt;
&lt;li&gt;Create a release on Gitea&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This changes over time, but it is the basic pattern.&lt;/p&gt;
&lt;h1&gt;Tags and Git Depth&lt;/h1&gt;
&lt;p&gt;Woodpecker does not automatically download the needed tags for &lt;code&gt;semantic-release&lt;/code&gt; (and &lt;a href="/tags/gitversion/"&gt;GitVersion&lt;/a&gt;). This means that the &lt;code&gt;.woodpecker.yml&lt;/code&gt; file needs to include tags.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml"&gt;clone:
    git:
        image: woodpeckerci/plugin-git
        settings:
            tags: true
pipeline:
    # The pipeline elements
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unike GitLab, which only limits to the last ten commits, it appears that Woodpecker &lt;a href="https://woodpecker-ci.org/plugins/plugin-git"&gt;downloads the full repository&lt;/a&gt; by default which is also needed by GitVersion because it calculates every version. Not entirely sure about &lt;code&gt;semantic-release&lt;/code&gt; logging indicates it doesn't need the full repository, just enough back to find a version.&lt;/p&gt;
&lt;h1&gt;Building and Testing&lt;/h1&gt;
&lt;p&gt;To support task branches, I have a basic build and test code that runs on pushes and pull requests. This lets me identify bugs earlier and catch typos with my commits.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml"&gt;build:
    image: registry.gitlab.com/dmoonfire/nix-flake-docker:latest
    commands:
        - nix develop --command scripts/build.sh
    when:
        # We need both &amp;quot;tag&amp;quot; for the next section.
        event: [push, pull_request, tag]
        tag: v*

test:
    image: registry.gitlab.com/dmoonfire/nix-flake-docker:latest
    commands:
        - nix develop --command scripts/test.sh
    when:
        event: [push, pull_request]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From the build tasks, you can see that I'm using my current project layout which uses scripts in the &lt;code&gt;scripts/&lt;/code&gt; folder instead of &lt;code&gt;npm run&lt;/code&gt; or &lt;code&gt;dotnet run&lt;/code&gt;. This is to make it easier to work with polyglot plus works around the issue that I need to use &lt;code&gt;nix develop&lt;/code&gt; to get into my reproducible environment since the Docker image doesn't automatically do that. This is because both Gitlab and Woodpecker use the image which bypasses initialization files and I couldn't have it run &lt;code&gt;direnv allow&lt;/code&gt; automatically to set up environment variables.&lt;/p&gt;
&lt;p&gt;One thing that is missing is that Woodpecker doesn't have a clean mechanism for temporary build artifacts. I can't upload the build files and then download them so I can see the final results. Instead, I have to script it out or use a S3 plugin.&lt;/p&gt;
&lt;h1&gt;Building on Versions&lt;/h1&gt;
&lt;p&gt;With most cases, I build the release version of the project when the conventional commits indicate that there is a new version (&lt;code&gt;feat&lt;/code&gt; and &lt;code&gt;fix&lt;/code&gt;). This is an additional pipeline that comes after the &lt;code&gt;test:&lt;/code&gt; line.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml"&gt;release-main:
    image: registry.gitlab.com/dmoonfire/nix-flake-docker:latest
    commands:
        - export DRONE=&amp;quot;true&amp;quot; # Required to convince `env-ci`
        # semantic-release needs this locally
        - git branch $DRONE_BRANCH origin/$DRONE_BRANCH
        - nix develop --command scripts/release.sh
    secrets:
        - gitea_token
        - git_credentials
    when:
        event: push
        branch: main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are a number of things in this block that took me a while. The first is the &lt;code&gt;event&lt;/code&gt; and &lt;code&gt;branch&lt;/code&gt;. We only do releases on the &lt;code&gt;main&lt;/code&gt; (I'm still moving away from &lt;code&gt;master&lt;/code&gt; as racist language).&lt;/p&gt;
&lt;p&gt;The second is the &lt;code&gt;export DRONE&lt;/code&gt; line. At the time I set this up, &lt;a href="https://www.npmjs.com/package/env-ci"&gt;env-ci&lt;/a&gt; wasn't aware of Woodpecker, but it was &lt;a href="https://github.com/woodpecker-ci/woodpecker/pull/1035"&gt;recently added&lt;/a&gt; thanks to 6543 on the Woodpecker Matrix channel, &lt;code&gt;#woodpecker-ci:matrix.org&lt;/code&gt;. I don't know when the latest &lt;code&gt;semantic-release&lt;/code&gt; will have it, but it shouldn't be needed soon, if not already.&lt;/p&gt;
&lt;p&gt;The third is the &lt;code&gt;git branch&lt;/code&gt; line in the above script. Woodpecker creates a detached head, as does Gitlab. But when it doesn't do is also create a local branch for the one being created. This causes a problem because the release process appears to &amp;ldquo;jump&amp;rdquo; to the branch to figure out the changes between the detached head (the commit being built) and the actual branch.&lt;/p&gt;
&lt;p&gt;Finally, we have the secrets. &lt;code&gt;semantic-release&lt;/code&gt; automatically picks up $GITEA_TOKEN for the release process but also needs $GIT_CREDENTIALS to verify Git access.&lt;/p&gt;
&lt;p&gt;The token is easy, that is what given by Gitea for the user.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/semantic-release/semantic-release/blob/master/docs/usage/ci-configuration.md"&gt;GIT_CREDENTIALS&lt;/a&gt; is slightly harder, it is a colon-separate tuple of the user name and the Gitea access token. From observations, the Gitea is basically just a bunch of URL-safe characters, so the URL-escaping isn't needed in my case (you need to URL-escape the left and right of the colon but not the colon itself).&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;$ export GITEA_TOKEN=9fc6d72c72e4b149f07491a0b2d3ec9215d57caf
$ export GIT_CREDENTIALS=&amp;quot;dmoonfire:$GITEA_TOKEN&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These need to be set on a per-project basis since Woodpecker, unlike GitLab and &lt;a href="/tags/sourcehut/"&gt;sourcehut&lt;/a&gt;, there doesn't appear to be a good way of having a shared set of secrets for projects (GitLab has organization/group level secrets, sourcehut has the secret storage). This means I have to set the same GITEA_TOKEN and GIT_CREDENTIALS for all 80+ of my &lt;a href="/tags/fedran/"&gt;Fedran&lt;/a&gt; repositories†.&lt;/p&gt;
&lt;p&gt;† Woodpecker has a CLI, &lt;code&gt;woodpecker-cli&lt;/code&gt; which will let me automate that. I will use that.&lt;/p&gt;
&lt;h1&gt;Create Release on Tag&lt;/h1&gt;
&lt;p&gt;One thing I'm moving toward is creating a release entry on the forge. Since this happens after the build process and Woodpecker uses a different Docker image, I need it to be a post-release event so I hang it off the tagging process instead of the push to &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;6543 came to my rescue again with this one, so that is why there are those two &amp;ldquo;tag&amp;rdquo; elements in the script above. I also have a new stanza for the release process:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-yaml"&gt;release-gitea:
    image: plugins/gitea-release
    settings:
        base_url: https://src.mfgames.com
        files:
            - &amp;quot;*.pdf&amp;quot;
            - &amp;quot;*.epub&amp;quot;
        api_key:
            from_secret: gitea_token
    when:
        event: tag
        tag: v*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If I didn't have the &lt;code&gt;event:&lt;/code&gt; and &lt;code&gt;tag:&lt;/code&gt; in the &lt;code&gt;build:&lt;/code&gt; stanza, it wasn't working for the tags. This caused me some difficulties because I usually treat hashes as being unordered, but Woodpecker uses file order for processing pipelines. So, I needed to have the &lt;code&gt;build:&lt;/code&gt; target build the file (with the correct version because it was tagged) and then &lt;code&gt;release-gitea:&lt;/code&gt; to use that output for the release process. The &lt;code&gt;test:&lt;/code&gt; and &lt;code&gt;release-main:&lt;/code&gt; are skipped because they don't have those events listed.&lt;/p&gt;
&lt;p&gt;In addition, secrets are handled differently when done as a parameter for a plugin. That is why I have the &lt;code&gt;from_secret:&lt;/code&gt; element in the above script. This inconsistency threw me for a few days.&lt;/p&gt;
&lt;h1&gt;Putting it Together&lt;/h1&gt;
&lt;p&gt;If you want to see the final version, check out &lt;a href="https://src.mfgames.com/dmoonfire-garden/project-layout/src/branch/main/.woodpecker.yml"&gt;this example&lt;/a&gt; which has my current version as a single file.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;I'm happy to move over to Woodpecker (you know, except for the cost of hosting) both because of the control and the challenge. I also don't have a need for speed, so if it takes a while to get through the queue, I'm okay.&lt;/p&gt;
</content>
  </entry>
</feed>
