mirror of
https://github.com/lukaszraczylo/shared-actions.git
synced 2026-10-12 04:54:48 +00:00
260 lines
10 KiB
YAML
260 lines
10 KiB
YAML
name: Node Pull Request
|
|
|
|
# Checks for a Node, Vue or Astro project: install without touching the lockfile,
|
|
# then run lint, typecheck, test and build. A script that does not exist is skipped.
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
node-version:
|
|
description: "Node.js version to use"
|
|
required: false
|
|
type: string
|
|
default: "22"
|
|
working-directory:
|
|
description: "Directory that holds package.json"
|
|
required: false
|
|
type: string
|
|
default: "."
|
|
workspace:
|
|
description: "The project is a pnpm workspace. working-directory then holds pnpm-workspace.yaml and pnpm-lock.yaml, and needs no package.json. Dependencies install once there, and scripts run in each of packages."
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
packages:
|
|
description: "With workspace, comma-separated workspace package directories, relative to working-directory."
|
|
required: false
|
|
type: string
|
|
default: ""
|
|
scripts:
|
|
description: "Comma-separated package.json scripts to run, in order. A script that does not exist is skipped."
|
|
required: false
|
|
type: string
|
|
default: "lint,typecheck,test,build"
|
|
pnpm-version:
|
|
description: "pnpm version to use, for a repo whose package.json has no packageManager field. Empty uses the packageManager field, or the newest pnpm."
|
|
required: false
|
|
type: string
|
|
default: ""
|
|
require-tests:
|
|
description: "Fail when package.json has no test script"
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
lfs:
|
|
description: "Enable Git LFS checkout (for repos with large files)"
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
npm-registry-scope:
|
|
description: "Scope served by a private npm registry, for example '@fortawesome'. Used only when npm-registry-url and the npm-registry-token secret are also set."
|
|
required: false
|
|
type: string
|
|
default: ""
|
|
npm-registry-url:
|
|
description: "URL of the private npm registry, for example 'https://npm.fontawesome.com/'. Used only when npm-registry-scope and the npm-registry-token secret are also set."
|
|
required: false
|
|
type: string
|
|
default: ""
|
|
runner:
|
|
description: "Runner label, or JSON object such as {\"group\":\"ci\",\"labels\":[\"linux\"]} to select a runner group. Falls back to vars.SHARED_ACTIONS_RUNNER, then ubuntu-latest."
|
|
required: false
|
|
type: string
|
|
default: ""
|
|
secrets:
|
|
npm-registry-token:
|
|
description: "Auth token for the private npm registry"
|
|
required: false
|
|
|
|
concurrency:
|
|
group: node-pr-${{ github.repository }}-${{ github.event_name }}-${{ github.head_ref || github.ref_name }}-${{ inputs.working-directory }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
pre:
|
|
name: Detect duplicate run
|
|
runs-on: ${{ fromJSON(startsWith(inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest', '{') && (inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest') || format('"{0}"', inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest')) }}
|
|
timeout-minutes: 5
|
|
permissions:
|
|
pull-requests: read
|
|
outputs:
|
|
skip: ${{ steps.dup.outputs.skip }}
|
|
steps:
|
|
# A push to a branch with an open PR also fires pull_request; run only the PR one.
|
|
- name: Skip push run when a PR is open
|
|
id: dup
|
|
uses: actions/github-script@v9
|
|
with:
|
|
script: |
|
|
let skip = false;
|
|
if (context.eventName === 'push') {
|
|
try {
|
|
const { data } = await github.rest.pulls.list({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
head: `${context.repo.owner}:${context.ref.replace('refs/heads/', '')}`,
|
|
state: 'open',
|
|
});
|
|
skip = data.length > 0;
|
|
} catch (e) {
|
|
core.warning(`PR lookup failed, running checks: ${e.message}`);
|
|
}
|
|
}
|
|
core.setOutput('skip', String(skip));
|
|
if (skip) core.notice('Open PR found for this branch; checks run on the pull_request event.');
|
|
|
|
checks:
|
|
name: Checks (${{ inputs.working-directory }})
|
|
needs: pre
|
|
if: needs.pre.outputs.skip != 'true'
|
|
runs-on: ${{ fromJSON(startsWith(inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest', '{') && (inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest') || format('"{0}"', inputs.runner || vars.SHARED_ACTIONS_RUNNER || 'ubuntu-latest')) }}
|
|
timeout-minutes: 30
|
|
permissions:
|
|
contents: read
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
lfs: ${{ inputs.lfs }}
|
|
|
|
- name: Run workflow prepare script
|
|
shell: bash
|
|
run: |
|
|
if [ -f "./workflow-prepare.sh" ]; then
|
|
chmod +x ./workflow-prepare.sh
|
|
./workflow-prepare.sh
|
|
fi
|
|
|
|
- name: Detect the package manager
|
|
id: detect
|
|
working-directory: ${{ inputs.working-directory }}
|
|
env:
|
|
WORKSPACE: ${{ inputs.workspace }}
|
|
PACKAGES: ${{ inputs.packages }}
|
|
run: |
|
|
if [ "$WORKSPACE" = true ]; then
|
|
if [ ! -f pnpm-workspace.yaml ] || [ ! -f pnpm-lock.yaml ]; then
|
|
echo "::error::workspace needs pnpm-workspace.yaml and pnpm-lock.yaml in ${{ inputs.working-directory }}."
|
|
exit 1
|
|
fi
|
|
count=0
|
|
IFS=',' read -r -a packages <<<"$PACKAGES"
|
|
for package in "${packages[@]}"; do
|
|
package=$(echo "$package" | xargs)
|
|
[ -n "$package" ] || continue
|
|
if [ ! -f "$package/package.json" ]; then
|
|
echo "::error::packages lists $package, but ${{ inputs.working-directory }}/$package/package.json does not exist."
|
|
exit 1
|
|
fi
|
|
count=$((count + 1))
|
|
done
|
|
if [ "$count" -eq 0 ]; then
|
|
echo "::error::workspace needs packages: the packages to check."
|
|
exit 1
|
|
fi
|
|
echo "pm=pnpm" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
if [ ! -f package.json ]; then
|
|
echo "::error::No package.json in ${{ inputs.working-directory }}."
|
|
exit 1
|
|
fi
|
|
pm=npm
|
|
if [ -f pnpm-lock.yaml ]; then pm=pnpm
|
|
elif [ -f yarn.lock ]; then pm=yarn
|
|
fi
|
|
echo "pm=$pm" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: ${{ inputs.node-version }}
|
|
|
|
# The npmrc lives in RUNNER_TEMP, not the checkout, so no git command can pick the token up.
|
|
- name: Configure the private npm registry
|
|
env:
|
|
NPM_SCOPE: ${{ inputs.npm-registry-scope }}
|
|
NPM_URL: ${{ inputs.npm-registry-url }}
|
|
NPM_TOKEN: ${{ secrets.npm-registry-token }}
|
|
run: |
|
|
[ -n "$NPM_SCOPE" ] && [ -n "$NPM_URL" ] && [ -n "$NPM_TOKEN" ] || exit 0
|
|
echo "::add-mask::$NPM_TOKEN"
|
|
url="${NPM_URL%/}/"
|
|
npmrc="$RUNNER_TEMP/private-registry.npmrc"
|
|
(umask 077 && printf '%s:registry=%s\n//%s:_authToken=%s\n' "$NPM_SCOPE" "$url" "${url#*://}" "$NPM_TOKEN" > "$npmrc")
|
|
echo "NPM_CONFIG_USERCONFIG=$npmrc" >> "$GITHUB_ENV"
|
|
echo "Using a private npm registry for $NPM_SCOPE."
|
|
|
|
- name: Install dependencies
|
|
working-directory: ${{ inputs.working-directory }}
|
|
env:
|
|
PM: ${{ steps.detect.outputs.pm }}
|
|
PNPM_VERSION: ${{ inputs.pnpm-version }}
|
|
run: |
|
|
# Install without updating the lockfile, so a lockfile that does not match package.json fails.
|
|
case "$PM" in
|
|
pnpm | yarn)
|
|
command -v corepack >/dev/null || npm install --global corepack
|
|
corepack enable "$PM"
|
|
;;
|
|
esac
|
|
if [ "$PM" = pnpm ] && [ -n "$PNPM_VERSION" ]; then
|
|
corepack prepare "pnpm@$PNPM_VERSION" --activate
|
|
fi
|
|
case "$PM" in
|
|
pnpm) pnpm install --frozen-lockfile ;;
|
|
yarn) yarn install --immutable ;;
|
|
npm)
|
|
if [ -f package-lock.json ]; then npm ci; else npm install; fi
|
|
;;
|
|
esac
|
|
|
|
- name: Run scripts
|
|
working-directory: ${{ inputs.working-directory }}
|
|
env:
|
|
PM: ${{ steps.detect.outputs.pm }}
|
|
SCRIPTS: ${{ inputs.scripts }}
|
|
REQUIRE_TESTS: ${{ inputs.require-tests }}
|
|
WORKSPACE: ${{ inputs.workspace }}
|
|
PACKAGES: ${{ inputs.packages }}
|
|
run: |
|
|
# A single project is the one package ".". A workspace is each listed package.
|
|
packages=(.)
|
|
if [ "$WORKSPACE" = true ]; then
|
|
IFS=',' read -r -a packages <<<"$PACKAGES"
|
|
fi
|
|
if [ "$REQUIRE_TESTS" = true ]; then
|
|
for package in "${packages[@]}"; do
|
|
package=$(echo "$package" | xargs)
|
|
[ -n "$package" ] || continue
|
|
if ! jq -e '.scripts.test' "$package/package.json" >/dev/null; then
|
|
echo "::error::$package/package.json has no test script."
|
|
exit 1
|
|
fi
|
|
done
|
|
fi
|
|
ran=()
|
|
IFS=',' read -r -a scripts <<<"$SCRIPTS"
|
|
for script in "${scripts[@]}"; do
|
|
script=$(echo "$script" | xargs)
|
|
[ -n "$script" ] || continue
|
|
for package in "${packages[@]}"; do
|
|
package=$(echo "$package" | xargs)
|
|
[ -n "$package" ] || continue
|
|
where=""
|
|
[ "$package" = . ] || where=" ($package)"
|
|
if jq -e --arg s "$script" '.scripts[$s]' "$package/package.json" >/dev/null; then
|
|
echo "::group::$PM run $script$where"
|
|
(cd "$package" && "$PM" run "$script")
|
|
echo "::endgroup::"
|
|
ran+=("$script$where")
|
|
else
|
|
echo "::notice::No '$script' script$where, skipping."
|
|
fi
|
|
done
|
|
done
|
|
if [ "${#ran[@]}" -eq 0 ]; then
|
|
echo "::warning::None of the scripts ($SCRIPTS) exist in package.json, so nothing was checked."
|
|
fi
|
|
echo "Ran: ${ran[*]:-none} (${PM})" >> "$GITHUB_STEP_SUMMARY"
|