From 8f774ff8458200ecb3fc51057f4f43f5ed7a2c94 Mon Sep 17 00:00:00 2001 From: Gabriel Rufino Date: Sat, 14 Mar 2026 13:24:35 -0300 Subject: [PATCH 1/4] feat: support npm, yarn and pnpm package managers - Add `package-manager` input (defaults to `npm`) - Use `corepack enable` to handle package manager binaries - Add input validation using `@actalog/expect@v1` - Update all internal commands to be dynamic based on the selected manager - Update CI workflow to test all managers via matrix - Update README with new documentation and usage examples --- .github/workflows/ci.yml | 7 ++++++- README.md | 12 ++++++++++++ action.yml | 41 ++++++++++++++++++++++++++++++++-------- 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 51ccffe..3fb08fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,10 @@ jobs: name: action path: action.yml test: - name: 'Test: default usage' + name: 'Test: package manager ${{ matrix.package-manager }}' + strategy: + matrix: + package-manager: [npm, yarn, pnpm] runs-on: ubuntu-latest needs: - upload-action @@ -29,6 +32,8 @@ jobs: repository: gabrielrufino/node-template - uses: actions/download-artifact@v4 - uses: ./action/ + with: + package-manager: ${{ matrix.package-manager }} test-using-except: name: "Test: using 'except'" runs-on: ubuntu-latest diff --git a/README.md b/README.md index 9efafdd..dc29ef5 100644 --- a/README.md +++ b/README.md @@ -24,4 +24,16 @@ jobs: steps: - uses: actions/checkout@v4 - uses: actalog/node-ci@v3 + with: + package-manager: pnpm # optional, default is npm ``` + +## Inputs + +| Name | Description | Default | +| ----------------- | -------------------------------------------------- | ------- | +| `node-version` | Node.js version | `24` | +| `package-manager` | Package manager to use (`npm`, `yarn`, or `pnpm`) | `npm` | +| `except` | Scripts to skip (comma-separated: `build`, `lint`) | `` | +| `github-token` | GitHub Token (for SonarCloud and PR comments) | `` | +| `sonar-token` | SonarCloud Token | `` | diff --git a/action.yml b/action.yml index 63f68ee..b994d77 100644 --- a/action.yml +++ b/action.yml @@ -14,39 +14,63 @@ inputs: node-version: required: false default: 24 + package-manager: + required: false + default: npm + description: 'The package manager to use (npm, yarn, or pnpm)' sonar-token: required: false runs: using: composite steps: + - uses: actalog/expect@v1 + with: + type: enum + value: ${{ inputs.package-manager }} + options: npm,yarn,pnpm - uses: actions/setup-node@v6 with: node-version: ${{ inputs.node-version }} - cache: npm + cache: ${{ inputs.package-manager }} + - run: corepack enable + shell: bash + - id: package-manager + run: | + if [ "${{ inputs.package-manager }}" == "yarn" ]; then + echo "pkg_cmd=yarn" >> $GITHUB_OUTPUT + echo "install_cmd=yarn install --immutable || yarn install" >> $GITHUB_OUTPUT + elif [ "${{ inputs.package-manager }}" == "pnpm" ]; then + echo "pkg_cmd=pnpm" >> $GITHUB_OUTPUT + echo "install_cmd=pnpm install --frozen-lockfile || pnpm install" >> $GITHUB_OUTPUT + else + echo "pkg_cmd=npm" >> $GITHUB_OUTPUT + echo "install_cmd=npm ci || npm install" >> $GITHUB_OUTPUT + fi + shell: bash - id: scripts run: | scripts=$(cat package.json | jq -c '.scripts | keys') echo "scripts=$scripts" >> $GITHUB_OUTPUT shell: bash - - run: time (npm ci || npm install) + - run: time (${{ steps.package-manager.outputs.install_cmd }}) shell: bash - id: build if: ${{ contains(steps.scripts.outputs.scripts, 'build') && !contains(inputs.except, 'build') }} - run: time npm run build + run: time ${{ steps.package-manager.outputs.pkg_cmd }} run build shell: bash - id: lint if: ${{ contains(steps.scripts.outputs.scripts, 'lint') && !contains(inputs.except, 'lint') }} - run: time npm run lint + run: time ${{ steps.package-manager.outputs.pkg_cmd }} run lint shell: bash - id: test if: ${{ contains(steps.scripts.outputs.scripts, 'test') && !contains(inputs.except, 'test') }} - run: npm test + run: ${{ steps.package-manager.outputs.pkg_cmd }} test shell: bash - - run: npm run test:e2e + - run: ${{ steps.package-manager.outputs.pkg_cmd }} run test:e2e if: ${{ contains(steps.scripts.outputs.scripts, 'test:e2e') && !contains(inputs.except, 'test:e2e') }} shell: bash - - run: npm run test:cov + - run: ${{ steps.package-manager.outputs.pkg_cmd }} run test:cov if: ${{ contains(steps.scripts.outputs.scripts, 'test:cov') && !contains(inputs.except, 'test') }} shell: bash - if: ${{ inputs.sonar-token && inputs.github-token }} @@ -71,7 +95,7 @@ runs: { echo 'outdated<> $GITHUB_OUTPUT shell: bash @@ -108,4 +132,5 @@ runs: * **Runner OS:** ${{ runner.os }} * **Runner Arch:** ${{ runner.arch }} * **Node version:** ${{ inputs.node-version }} + * **Package manager:** ${{ inputs.package-manager }} continue-on-error: true From 7ec8de1fc58e565bb110362036e55e89c7bf75f6 Mon Sep 17 00:00:00 2001 From: Gabriel Rufino Date: Sat, 14 Mar 2026 13:39:14 -0300 Subject: [PATCH 2/4] fix: split setup-node into two steps to support corepack-based caching - Install Node.js first to ensure `corepack` is available in the environment - Enable `corepack` before configuring the package manager cache - Run `setup-node` a second time to handle caching now that shims are in PATH - Add explanatory comments for the dual-step approach and ensure version consistency --- action.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/action.yml b/action.yml index b994d77..78c0682 100644 --- a/action.yml +++ b/action.yml @@ -29,12 +29,18 @@ runs: type: enum value: ${{ inputs.package-manager }} options: npm,yarn,pnpm + # First call: Install Node.js so that 'corepack' is available in the environment - uses: actions/setup-node@v6 with: node-version: ${{ inputs.node-version }} - cache: ${{ inputs.package-manager }} - run: corepack enable shell: bash + # Second call: Configure cache. This must happen AFTER 'corepack enable' + # so that the caching logic can find the pnpm/yarn executables in the PATH. + - uses: actions/setup-node@v6 + with: + node-version: ${{ inputs.node-version }} + cache: ${{ inputs.package-manager }} - id: package-manager run: | if [ "${{ inputs.package-manager }}" == "yarn" ]; then From a22fb020b42a0c320e34f5008c6aa43a0ff24e5e Mon Sep 17 00:00:00 2001 From: Gabriel Rufino Date: Sat, 14 Mar 2026 13:45:44 -0300 Subject: [PATCH 3/4] fix: make package manager caching optional based on lock file existence - Use `hashFiles` to check for `pnpm-lock.yaml`, `yarn.lock`, or `package-lock.json` - Only enable the `cache` input in `setup-node` if the corresponding file exists - Prevent workflow failures when the selected manager's lock file is missing - Simplify logic using a ternary-like expression directly in the YAML --- action.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/action.yml b/action.yml index 78c0682..c1b1830 100644 --- a/action.yml +++ b/action.yml @@ -40,7 +40,14 @@ runs: - uses: actions/setup-node@v6 with: node-version: ${{ inputs.node-version }} - cache: ${{ inputs.package-manager }} + # Only enable caching if the corresponding lock file exists + cache: >- + ${{ + (inputs.package-manager == 'pnpm' && hashFiles('pnpm-lock.yaml') != '') && 'pnpm' || + (inputs.package-manager == 'yarn' && hashFiles('yarn.lock') != '') && 'yarn' || + (inputs.package-manager == 'npm' && hashFiles('package-lock.json') != '') && 'npm' || + '' + }} - id: package-manager run: | if [ "${{ inputs.package-manager }}" == "yarn" ]; then From 76ed63b37e8e66a12b32982f380e65d86832d6fa Mon Sep 17 00:00:00 2001 From: Gabriel Rufino Date: Sat, 14 Mar 2026 13:55:06 -0300 Subject: [PATCH 4/4] feat: add step duration and install info to PR comments - Calculate execution time for install, build, lint, and test steps - Add 'Install' step and 'Duration' column to the PR summary table - Use manual timestamp calculation to provide duration in seconds - Format and align the summary table in `action.yml` for better readability --- action.yml | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/action.yml b/action.yml index c1b1830..4c48daf 100644 --- a/action.yml +++ b/action.yml @@ -66,19 +66,32 @@ runs: scripts=$(cat package.json | jq -c '.scripts | keys') echo "scripts=$scripts" >> $GITHUB_OUTPUT shell: bash - - run: time (${{ steps.package-manager.outputs.install_cmd }}) + - id: install + run: | + start=$(date +%s) + ${{ steps.package-manager.outputs.install_cmd }} + echo "duration=$(( $(date +%s) - start ))s" >> $GITHUB_OUTPUT shell: bash - id: build if: ${{ contains(steps.scripts.outputs.scripts, 'build') && !contains(inputs.except, 'build') }} - run: time ${{ steps.package-manager.outputs.pkg_cmd }} run build + run: | + start=$(date +%s) + ${{ steps.package-manager.outputs.pkg_cmd }} run build + echo "duration=$(( $(date +%s) - start ))s" >> $GITHUB_OUTPUT shell: bash - id: lint if: ${{ contains(steps.scripts.outputs.scripts, 'lint') && !contains(inputs.except, 'lint') }} - run: time ${{ steps.package-manager.outputs.pkg_cmd }} run lint + run: | + start=$(date +%s) + ${{ steps.package-manager.outputs.pkg_cmd }} run lint + echo "duration=$(( $(date +%s) - start ))s" >> $GITHUB_OUTPUT shell: bash - id: test if: ${{ contains(steps.scripts.outputs.scripts, 'test') && !contains(inputs.except, 'test') }} - run: ${{ steps.package-manager.outputs.pkg_cmd }} test + run: | + start=$(date +%s) + ${{ steps.package-manager.outputs.pkg_cmd }} test + echo "duration=$(( $(date +%s) - start ))s" >> $GITHUB_OUTPUT shell: bash - run: ${{ steps.package-manager.outputs.pkg_cmd }} run test:e2e if: ${{ contains(steps.scripts.outputs.scripts, 'test:e2e') && !contains(inputs.except, 'test:e2e') }} @@ -124,11 +137,12 @@ runs: ## Steps - | Step | Status | - | --------- | -------------------------- | - | **Build** | ${{ steps.build.outcome }} | - | **Lint** | ${{ steps.lint.outcome }} | - | **Test** | ${{ steps.test.outcome }} | + | Step | Status | Duration | + | :--- | :--- | :--- | + | **Install** | ${{ steps.install.outcome }} | ${{ steps.install.outputs.duration }} | + | **Build** | ${{ steps.build.outcome }} | ${{ steps.build.outputs.duration || '-' }} | + | **Lint** | ${{ steps.lint.outcome }} | ${{ steps.lint.outputs.duration || '-' }} | + | **Test** | ${{ steps.test.outcome }} | ${{ steps.test.outputs.duration || '-' }} | ## Statistics