Automating Lerna Monorepo Publishing with GitHub Actions: Configuration, Versioning, and OIDC Security

Integrating Lerna into GitHub Actions for automated versioning and publishing of monorepo packages requires precise configuration of git history retrieval, conditional logic for release channels, and secure credential management. The process relies heavily on the underlying behavior of semantic-release, which Lerna uses for version calculation, making the integrity of the fetched git history critical for accurate version bumps. Furthermore, recent evolutions in Lerna, specifically version 9.0.0, have introduced support for OIDC (OpenID Connect) trusted publishing, eliminating the need for static npm tokens in CI/CD pipelines. Understanding the interplay between GitHub Actions checkout depth, conventional commits, and Lerna’s publishing mechanisms is essential for establishing a robust, error-free continuous integration workflow.

Git History and Version Calculation Accuracy

The most common failure point when integrating Lerna with GitHub Actions stems from the default behavior of the actions/checkout action. By default, GitHub Actions fetches the repository with --depth=1, which retrieves only the most recent commit without the full git history or tags. Lerna, utilizing semantic-release under the hood, relies on this history to determine which packages have changed since the last release and to calculate the appropriate version bump based on conventional commit messages.

When the git history is shallow, Lerna cannot identify the previous tag. Consequently, it falls back to a default behavior where it assumes all packages have changed. This results in incorrect version bumps, often defaulting to minor or patch increments regardless of the actual commit types, or failing to graduate pre-release versions correctly. In local environments, the full git history is present, allowing Lerna to accurately identify changes since the last specific version (e.g., "Looking for changed packages since v2.1.5"). In GitHub Actions without proper configuration, the output indicates "Assuming all packages changed," leading to inconsistent and erroneous versioning.

To resolve this, the GitHub Actions workflow must be explicitly configured to fetch the entire git history and all tags. This is achieved by setting fetch-depth: 0 in the actions/checkout step. Additionally, a manual git fetch command is often required to ensure all tags are retrieved from the remote origin. This ensures that Lerna has the complete context necessary to differentiate between unchanged packages and those requiring a version bump.

```yaml
- name: "Checkout"
uses: actions/checkout@v2
with:
fetch-depth: 0

  • name: "Fetch Tags"
    run: git fetch --depth=1 origin +refs/tags/:refs/tags/
    ```

Conditional Versioning Strategies

A robust CI/CD pipeline for a monorepo often distinguishes between development builds (beta versions) and production releases (final versions). This distinction is typically managed by branching strategies, such as using a development branch for pre-release builds and a main branch for stable releases. Lerna provides specific flags to handle these scenarios within the versioning command.

When merging into a development branch, the goal is often to generate beta versions. This is accomplished using the --conventional-prerelease flag in conjunction with --preid beta. This instructs Lerna to append a -beta.x suffix to the version numbers of the packages that have changed. Conversely, when merging into the main branch, the objective is to promote these beta versions to final, stable releases. The --conventional-graduate flag serves this purpose, stripping the prerelease identifiers and publishing the versions as stable.

The workflow must also include the --yes flag to automatically confirm the version and publish operations. Without this flag, Lerna would prompt for user confirmation, causing the non-interactive CI runner to hang or fail. The logic can be implemented using a conditional statement in the GitHub Actions workflow script, checking the target branch (github.base_ref) to determine which flags to apply.

bash if [ ${{ github.base_ref }} = development ]; then npx lerna version --conventional-commits --conventional-prerelease --preid beta --yes else npx lerna version --conventional-commits --conventional-graduate --yes fi npx lerna publish from-git --yes

This approach ensures that development merges result in testing versions (e.g., 2.1.6-beta.1) in the registry, while merges to main produce final versions (e.g., 2.2.0). This separation allows teams to test new features in a staging environment before promoting them to production, all automated through the CI pipeline.

Known Issues with Lerna v8.2.1 and Hanging Processes

While configuring the logic and git fetch depth is critical, specific versions of Lerna have introduced regressions that affect the stability of the publishing process. In Lerna version 8.2.1, a notable issue emerged where the GitHub Actions runner would hang after the versioning step completed successfully. The logs would indicate that the version was finished and tags were pushed, but the process would not terminate, requiring manual cancellation of the job.

The error log typically shows:

text lerna info getChangelogConfig Successfully resolved preset "conventional-changelog-angular" lerna info git Pushing tags... lerna success version finished Error: The operation was canceled.

This issue was not present in the previous version, 8.2.0. Users experiencing this hang were advised to revert to Lerna v8.2.0 as a temporary workaround. This highlights the importance of pinning Lerna versions in CI workflows to avoid unexpected regressions from minor updates. The environment in which this issue was reported included Node.js 20.8.1, npm 10.1.0, and Lerna ^8.1.8 resolving to 8.2.1, running on Ubuntu 24.04.2 LTS.

OIDC Trusted Publishing in Lerna v9.0.0

A significant advancement in secure publishing workflows was introduced in Lerna v9.0.0 with the addition of support for OIDC (OpenID Connect) trusted publishing. Traditionally, publishing packages to npm required storing a static npm token in the GitHub Actions secrets. This approach poses security risks, as the token is a fixed credential that, if compromised, provides permanent access to the user's npm account.

OIDC trusted publishing eliminates the need for static tokens by leveraging the identity of the CI/CD environment itself. The package on the npm side is configured to accept publications only from a specific trusted environment, such as a GitHub Actions workflow. During the publish step, the CI environment retrieves a short-lived OIDC token from the identity provider (GitHub) and uses it to authenticate with npm. This token is valid only for the duration of the workflow run and is tied to the specific repository and environment, significantly reducing the attack surface.

For Lerna v9.0.0 and later, no additional configuration within Lerna itself is required to support OIDC. The workflow simply needs to follow npm's official guidance for configuring OIDC in GitHub Actions. This includes setting up the appropriate permissions in the GitHub Actions workflow to read the ID token and ensuring the npm package is configured to trust the GitHub repository. This feature represents a major step forward in securing the software supply chain for monorepos managed by Lerna.

Workflow Configuration Example

Combining these elements, a robust GitHub Actions workflow for publishing a Lerna monorepo involves careful orchestration of checkout, environment setup, and conditional execution. The workflow should be triggered on pull request closures for specific branches (e.g., development and main). Branch protection rules should be configured to prevent merges if the CI checks fail, ensuring that only valid, tested code reaches the registry.

The following configuration demonstrates the integration of full git history fetching, conditional versioning logic, and the execution of Lerna commands.

```yaml
name: Publish

on:
pull_request:
types: [closed]
branches:
- development
- main

jobs:
publish:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- name: "Checkout"
uses: actions/checkout@v2
with:
fetch-depth: 0

  - name: "Fetch Tags"
    run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*

  - name: "Use NodeJS 14"
    uses: actions/setup-node@v2
    with:
      node-version: '14'

  - name: "Configure Git User"
    run: |
      git config user.name "${{ github.actor }}"
      git config user.email "${{ github.actor}}@users.noreply.github.com"

  - name: "Version and Publish"
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    run: |
      if [ ${{ github.base_ref }} = development ]; then
        npx lerna version --conventional-commits --conventional-prerelease --preid beta --yes
      else
        npx lerna version --conventional-commits --conventional-graduate --yes
      fi
      npx lerna publish from-git --yes

```

This workflow ensures that the git history is fully available for Lerna to make accurate versioning decisions. It distinguishes between beta and final releases based on the target branch, and it uses the --yes flag to automate the process. For teams using Lerna v9.0.0+, this workflow can be further secured by removing the need for static npm tokens in favor of OIDC trusted publishing.

Conclusion

Automating the publishing of Lerna-managed monorepos via GitHub Actions requires a deep understanding of both git internals and Lerna's versioning mechanics. The primary pitfall lies in the shallow cloning behavior of GitHub Actions, which must be corrected by fetching the full history and tags to ensure accurate version calculations. By implementing conditional logic based on branch targets, teams can seamlessly manage the lifecycle of packages from beta pre-releases to final stable versions. Furthermore, the adoption of Lerna v9.0.0 and its OIDC trusted publishing capabilities marks a significant evolution in secure, automated software distribution, removing the reliance on static credentials and enhancing the integrity of the publishing pipeline.

Sources

  1. Lerna Issue #4165: Publish hang in v8.2.1
  2. Lerna Issue #2542: Lerna with GitHub Actions checkout depth
  3. Lerna Documentation: OIDC Trusted Publishing
  4. Automatic Versioning in a Lerna Monorepo using GitHub Actions

Related Posts