Horm Codes

Talking About Monorepos and Nx at TDC Conference

Check out my talk at TDC 2024 with title Using NX to Manage Services Inside Monorepo:

You can also access slides directly at slides.horm.codes/tdc-2024. Fun fact, the slide repository uses Nx by itself (see here for more details).

Resources

Here are some code snippets that can be useful when using Nx…

Continuous Integration in GitHub Actions:

Workflow example:

name: CI

on:
  pull_request:

env:
  NODE_VERSION: 20

jobs:
  lint:
    name: Lint Code
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'yarn'

      - run: yarn install --frozen-lockfile --prefer-offline

      - run: yarn nx affected -t lint --base=origin/main --parallel=3

  unit_tests:
    runs-on: ubuntu-latest
    name: Run unit tests
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'yarn'

      - run: yarn install --frozen-lockfile --prefer-offline

      - run: yarn nx affected -t test --base=origin/main

Continuous Deployment in GitHub Actions:

Workflow example:

name: CD

on:
  push:
    branches:
      - main

env:
  NODE_VERSION: 20

jobs:
  deploy:
    runs-on: ubuntu-latest
    name: Deploy affected services to dev
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'yarn'

      - run: yarn install --frozen-lockfile --prefer-offline

      - name: Find last successful workflow run
        uses: nrwl/nx-set-shas@v4

      - run: yarn nx affected -t deploy

Publishing Libraries to GitHub Maven Registry

Nx config:

{
  // ... 
  "release": {
    "projects": [
      "lib",
      "api-client",
      "api-rest-models"
    ],
    "projectsRelationship": "fixed",
    "version": {
      "conventionalCommits": true,
      "git": {
        "commit": false
      }
    },
    "changelog": {
      "git": {
        "commit": false
      },
      "workspaceChangelog": {
        "createRelease": "github"
      }
    }
  }
}

Release script:

const {
  releaseChangelog,
  releasePublish,
  releaseVersion
} = require("nx/release");
const fs = require("fs");
const yargs = require('yargs');

const gradlePropsPath = "./gradle.properties";

(async () => {
  const options = await yargs
    .version(false) // don't use the default meaning of version in yargs
    .option('dryRun', {
      alias: 'd',
      description:
        'Whether or not to perform a dry-run of the release process, defaults to false',
      type: 'boolean',
      default: false,
    })
    .parseAsync();


  const {
    workspaceVersion,
    projectsVersionData
  } = await releaseVersion({ dryRun: options.dryRun });

  if (typeof workspaceVersion !== "string") {
    console.warn("No new version detected. Exiting...");
    process.exit(0);
    return;
  }

  await releaseChangelog({
    versionData: projectsVersionData,
    version: workspaceVersion,
    dryRun: options.dryRun,
  });

  if (options.dryRun) {
    console.warn("Dry-run mode enabled. Exiting...");
    process.exit(0);
    return
  }

  const publishStatus = await releasePublish({ dryRun: options.dryRun });

  process.exit(publishStatus);
})();

Workflow example:

name: CD

on:
  push:
    branches:
      - main

env:
  NODE_VERSION: 20

jobs:
  publish_libs:
    runs-on: ubuntu-latest
    name: Publish libraries
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'yarn'

      - run: yarn install --frozen-lockfile --prefer-offline

      - name: Define Git user
        run: |
          git config user.name github-actions
          git config user.email github-actions@github.com

      - run: node ./tools/scripts/release.js
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITHUB_ACTOR: ${{ github.actor }}