diff --git a/.github/workflows/api-build.yml b/.github/workflows/api-build.yml deleted file mode 100644 index 841c732..0000000 --- a/.github/workflows/api-build.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: API Build - -on: - workflow_call: - inputs: - app_path: - description: "Path to the API app (e.g., apps/api)" - required: true - type: string - app_name: - description: "Name of the app for display" - required: false - type: string - default: "API" - node_version: - description: "Node.js version to use" - required: false - type: string - default: "24" - run_lint: - description: "Whether to run lint" - required: false - type: boolean - default: true - run_test: - description: "Whether to run tests" - required: false - type: boolean - default: true - build_command: - description: "Build command to run" - required: false - type: string - default: "build" - use_filter: - description: "Whether to use filter for installation" - required: false - type: boolean - default: false - -jobs: - build: - name: Build ${{ inputs.app_name }} - runs-on: ubuntu-latest - - steps: - - name: Setup - uses: sisques-labs/workflows/.github/actions/setup@main - with: - node_version: ${{ inputs.node_version }} - - - name: Install dependencies - uses: sisques-labs/workflows/.github/actions/install@main - with: - app_path: ${{ inputs.app_path }} - use_filter: ${{ inputs.use_filter }} - frozen_lockfile: "true" - - - name: Lint code - if: ${{ inputs.run_lint }} - run: pnpm --filter=./${{ inputs.app_path }}... lint - - - name: Run unit tests - if: ${{ inputs.run_test }} - run: pnpm --filter=./${{ inputs.app_path }}... test - - - name: Build the app - run: pnpm --filter=./${{ inputs.app_path }}... ${{ inputs.build_command }} diff --git a/.github/workflows/node-ci.yml b/.github/workflows/node-ci.yml new file mode 100644 index 0000000..410bc1e --- /dev/null +++ b/.github/workflows/node-ci.yml @@ -0,0 +1,162 @@ +name: Node CI + +on: + workflow_call: + inputs: + app_path: + description: 'Path to the app/package (e.g., apps/web). Use "." for root' + required: false + type: string + default: "." + node_version: + description: "Node.js version to use" + required: true + type: string + use_filter: + description: "Whether to use filter for installation" + required: false + type: boolean + default: false + run_lint: + description: "Whether to run lint" + required: false + type: boolean + default: true + lint_command: + description: "Lint command to run (without the leading pnpm)" + required: false + type: string + default: "lint" + run_test: + description: "Whether to run unit tests" + required: false + type: boolean + default: true + test_command: + description: "Unit test command to run (without the leading pnpm)" + required: false + type: string + default: "test" + run_build: + description: "Whether to build" + required: false + type: boolean + default: true + build_command: + description: "Build command to run (without the leading pnpm)" + required: false + type: string + default: "build" + extra_check_command: + description: "Optional extra pnpm command that runs as its own parallel job (e.g. a codegen-sync check, a type-check, or a service-less test suite). Skipped when empty." + required: false + type: string + default: "" + env_vars: + description: "Extra environment variables applied to every job, as newline-separated KEY=VALUE pairs (e.g. non-secret values a test suite needs). Empty by default." + required: false + type: string + default: "" + +jobs: + # Deliberately four independent jobs with no `needs:` between them: lint, + # unit tests, build, and the optional extra check all only need `install`, + # so they run fully in parallel instead of behind one another. A consumer + # workflow that also has DB-backed e2e/integration jobs should give those + # no `needs: ci` either, unless they actually consume this workflow's + # build output — most don't, they run straight from source. + lint: + if: inputs.run_lint + name: Lint + runs-on: ubuntu-latest + steps: + - name: Load extra env vars + if: inputs.env_vars != '' + run: echo "${{ inputs.env_vars }}" >> "$GITHUB_ENV" + + - name: Setup + uses: sisques-labs/workflows/.github/actions/setup@main + with: + node_version: ${{ inputs.node_version }} + + - name: Install dependencies + uses: sisques-labs/workflows/.github/actions/install@main + with: + app_path: ${{ inputs.app_path }} + use_filter: ${{ inputs.use_filter }} + frozen_lockfile: "true" + + - name: Lint + run: pnpm ${{ inputs.lint_command }} + + test: + if: inputs.run_test + name: Unit tests + runs-on: ubuntu-latest + steps: + - name: Load extra env vars + if: inputs.env_vars != '' + run: echo "${{ inputs.env_vars }}" >> "$GITHUB_ENV" + + - name: Setup + uses: sisques-labs/workflows/.github/actions/setup@main + with: + node_version: ${{ inputs.node_version }} + + - name: Install dependencies + uses: sisques-labs/workflows/.github/actions/install@main + with: + app_path: ${{ inputs.app_path }} + use_filter: ${{ inputs.use_filter }} + frozen_lockfile: "true" + + - name: Test + run: pnpm ${{ inputs.test_command }} + + build: + if: inputs.run_build + name: Build + runs-on: ubuntu-latest + steps: + - name: Load extra env vars + if: inputs.env_vars != '' + run: echo "${{ inputs.env_vars }}" >> "$GITHUB_ENV" + + - name: Setup + uses: sisques-labs/workflows/.github/actions/setup@main + with: + node_version: ${{ inputs.node_version }} + + - name: Install dependencies + uses: sisques-labs/workflows/.github/actions/install@main + with: + app_path: ${{ inputs.app_path }} + use_filter: ${{ inputs.use_filter }} + frozen_lockfile: "true" + + - name: Build + run: pnpm ${{ inputs.build_command }} + + extra_check: + if: inputs.extra_check_command != '' + name: Extra check + runs-on: ubuntu-latest + steps: + - name: Load extra env vars + if: inputs.env_vars != '' + run: echo "${{ inputs.env_vars }}" >> "$GITHUB_ENV" + + - name: Setup + uses: sisques-labs/workflows/.github/actions/setup@main + with: + node_version: ${{ inputs.node_version }} + + - name: Install dependencies + uses: sisques-labs/workflows/.github/actions/install@main + with: + app_path: ${{ inputs.app_path }} + use_filter: ${{ inputs.use_filter }} + frozen_lockfile: "true" + + - name: Run + run: pnpm ${{ inputs.extra_check_command }} diff --git a/.github/workflows/web-build.yml b/.github/workflows/web-build.yml deleted file mode 100644 index 6e93b88..0000000 --- a/.github/workflows/web-build.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: Web Build - -on: - workflow_call: - inputs: - app_path: - description: "Path to the web app (e.g., apps/web)" - required: true - type: string - app_name: - description: "Name of the app for display" - required: false - type: string - default: "Web App" - node_version: - description: "Node.js version to use" - required: false - type: string - default: "24" - run_lint: - description: "Whether to run lint" - required: false - type: boolean - default: true - run_test: - description: "Whether to run tests" - required: false - type: boolean - default: true - build_command: - description: "Build command to run" - required: false - type: string - default: "build" - use_filter: - description: "Whether to use filter for installation" - required: false - type: boolean - default: false - -jobs: - build: - name: Build ${{ inputs.app_name }} - runs-on: ubuntu-latest - - steps: - - name: Setup - uses: sisques-labs/workflows/.github/actions/setup@main - with: - node_version: ${{ inputs.node_version }} - - - name: Install dependencies - uses: sisques-labs/workflows/.github/actions/install@main - with: - app_path: ${{ inputs.app_path }} - use_filter: ${{ inputs.use_filter }} - frozen_lockfile: "true" - - - name: Lint code - if: ${{ inputs.run_lint }} - run: pnpm --filter=./${{ inputs.app_path }}... lint - - - name: Run unit tests - if: ${{ inputs.run_test }} - run: pnpm --filter=./${{ inputs.app_path }}... test - - - name: Build the app - run: pnpm --filter=./${{ inputs.app_path }}... ${{ inputs.build_command }} diff --git a/README.md b/README.md index 63898fe..19d5d14 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,7 @@ This repository contains reusable GitHub Actions workflows and composite actions ``` .github/ ├── workflows/ # Reusable workflows -│ ├── web-build.yml # Web application build workflow -│ ├── api-build.yml # API build workflow +│ ├── node-ci.yml # Lint, test, and build a Node.js app as parallel jobs │ ├── node-release.yml # Node.js release workflow with semantic-release │ ├── release-train.yml # Automatic alpha/beta/stable release train │ ├── docker-release.yml # Version bump + Docker build & publish @@ -36,7 +35,7 @@ This repository contains reusable GitHub Actions workflows and composite actions To use a reusable workflow in another project, create a workflow file in your project's `.github/workflows/` directory and reference the workflow using the `uses` keyword. -**Example: Using the Web Build workflow** +**Example: Using the Node CI workflow** Create `.github/workflows/ci.yml` in your project: @@ -44,30 +43,21 @@ Create `.github/workflows/ci.yml` in your project: name: CI on: - push: - branches: [main, develop] pull_request: - branches: [main, develop] jobs: - build-web: - uses: sisques-labs/workflows/.github/workflows/web-build.yml@main + ci: + uses: sisques-labs/workflows/.github/workflows/node-ci.yml@main with: - app_path: "apps/web" - app_name: "My Web App" node_version: "24" - run_lint: true - run_test: true - build_command: "build" - secrets: inherit # Required if the workflow needs secrets ``` **Key Points:** -- Use `uses: sisques-labs/workflows/.github/workflows/web-build.yml@main` to reference the workflow +- Use `uses: sisques-labs/workflows/.github/workflows/node-ci.yml@main` to reference the workflow - Replace `@main` with the branch/tag you want to use (e.g., `@v1.0.0` for versioned releases) - All inputs are passed via the `with:` section -- Use `secrets: inherit` to pass secrets from your repository to the reusable workflow +- Use `secrets: inherit` only if the workflow you're calling actually needs secrets (`node-ci.yml` doesn't) ### Using Composite Actions @@ -140,106 +130,68 @@ If you have multiple apps in a monorepo, you can create separate jobs for each: name: CI on: - push: - branches: [main] pull_request: - branches: [main] jobs: - build-web: - uses: sisques-labs/workflows/.github/workflows/web-build.yml@main + ci-web: + uses: sisques-labs/workflows/.github/workflows/node-ci.yml@main with: app_path: "apps/web" - app_name: "Web App" - - build-admin: - uses: sisques-labs/workflows/.github/workflows/web-build.yml@main - with: - app_path: "apps/admin" - app_name: "Admin App" + node_version: "24" - build-api: - uses: sisques-labs/workflows/.github/workflows/api-build.yml@main + ci-api: + uses: sisques-labs/workflows/.github/workflows/node-ci.yml@main with: app_path: "apps/api" - app_name: "API" -``` - -## Reusable Workflows - -### Web Build - -Builds a Next.js or web application with optional linting and testing. - -**Usage:** - -```yaml -name: Web Build - -on: - pull_request: - paths: - - "apps/web/**" - branches: [main, dev] - -jobs: - build: - uses: sisques-labs/workflows/.github/workflows/web-build.yml@main - with: - app_path: "apps/web" - app_name: "Web App" node_version: "24" - run_lint: true - run_test: true - build_command: "build" ``` -**Inputs:** - -- `app_path` (required): Path to the web app (e.g., `apps/web`) -- `app_name` (optional, default: `"Web App"`): Name of the app for display -- `node_version` (optional, default: `"24"`): Node.js version to use -- `run_lint` (optional, default: `true`): Whether to run lint -- `run_test` (optional, default: `true`): Whether to run tests -- `build_command` (optional, default: `"build"`): Build command to run (e.g., `build`, `build:prod`) -- `use_filter` (optional, default: `false`): Whether to use filter for installation +## Reusable Workflows -### API Build +### Node CI -Builds a NestJS or API application with optional linting and testing. +Lints, unit-tests, and builds a Node.js app. Unlike a single sequential job, +`lint`, `test`, `build`, and the optional `extra_check` run as **four +independent jobs with no `needs:` between them** — they only need `install`, +so they all start at once instead of queuing behind each other. **Usage:** ```yaml -name: API Build +name: CI on: pull_request: - paths: - - "apps/api/**" - branches: [main, dev] jobs: - build: - uses: sisques-labs/workflows/.github/workflows/api-build.yml@main + ci: + uses: sisques-labs/workflows/.github/workflows/node-ci.yml@main with: - app_path: "apps/api" - app_name: "API" - node_version: "24" - run_lint: true - run_test: true - build_command: "build" + node_version: "22" + test_command: "test:coverage" # only if it differs from the default "test" + extra_check_command: "gen:topics:check" # optional, runs as its own parallel job ``` **Inputs:** -- `app_path` (required): Path to the API app (e.g., `apps/api`) -- `app_name` (optional, default: `"API"`): Name of the app for display -- `node_version` (optional, default: `"24"`): Node.js version to use -- `run_lint` (optional, default: `true`): Whether to run lint -- `run_test` (optional, default: `true`): Whether to run tests -- `build_command` (optional, default: `"build"`): Build command to run (e.g., `build`, `build:prod`) +- `app_path` (optional, default: `"."`): Path to the app/package (e.g., `apps/web`) +- `node_version` (required): Node.js version to use - `use_filter` (optional, default: `false`): Whether to use filter for installation +- `run_lint` / `lint_command` (optional, default: `true` / `"lint"`) +- `run_test` / `test_command` (optional, default: `true` / `"test"`) +- `run_build` / `build_command` (optional, default: `true` / `"build"`) +- `extra_check_command` (optional, default: `""`): an extra `pnpm ` that runs as its own parallel job when non-empty — e.g. a codegen-sync check, a `tsc --noEmit` type check, or a service-less test suite (`test:e2e` for an app with no DB dependency). Free-form, so it's whatever the consumer repo needs; there's exactly one slot, not a list. +- `env_vars` (optional, default: `""`): extra environment variables applied to every job, as newline-separated `KEY=VALUE` pairs — for non-secret values a test suite needs (e.g. a mocked login). Use repository/environment secrets instead for anything sensitive. + +**What this deliberately does NOT cover:** DB-backed `e2e`/`integration` jobs +(Postgres services, migrations, seed data). Those differ too much between +consumers (DB engine/version, env var names, extra setup steps) to fit a +generic reusable workflow without turning it into a pile of pass-through +inputs. Define those as ordinary jobs alongside the `ci:` job in the +consumer's own `ci.yml` — with **no `needs: ci`** unless they genuinely +consume `node-ci.yml`'s build output (most e2e/integration suites run +straight from source and only need `install`, so gating them behind lint/ +test/build just adds wall-clock time for no reason). ### Node Release @@ -726,29 +678,21 @@ on: branches: [main] jobs: - build-web: - uses: sisques-labs/workflows/.github/workflows/web-build.yml@main + ci-web: + uses: sisques-labs/workflows/.github/workflows/node-ci.yml@main with: app_path: "apps/web" - app_name: "Web App" node_version: "24" - run_lint: true - run_test: true - build_command: "build" - build-api: - uses: sisques-labs/workflows/.github/workflows/api-build.yml@main + ci-api: + uses: sisques-labs/workflows/.github/workflows/node-ci.yml@main with: app_path: "apps/api" - app_name: "API" node_version: "24" - run_lint: true - run_test: true - build_command: "build" release: if: github.ref == 'refs/heads/main' && github.event_name == 'push' - needs: [build-web, build-api] + needs: [ci-web, ci-api] uses: sisques-labs/workflows/.github/workflows/node-release.yml@main secrets: inherit with: