Skip to content

⬆️ Update dependencies: eslint packages (major) #936

⬆️ Update dependencies: eslint packages (major)

⬆️ Update dependencies: eslint packages (major) #936

Workflow file for this run

name: CI
on:
push:
branches:
- master
tags-ignore:
- "**"
pull_request:
branches:
- "**"
jobs:
# If the "invalid" or "skip ci" labels are added to the Pull Request, do not run CI.
# see https://github.com/sounisi5011/npm-packages/blob/2a5ca2de696eeb8b40a38de90580441c4c6c96e0/.github/workflows/ci.yaml#L12-L52
if-run-ci:
# see https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
# see https://stackoverflow.com/a/67532120/4907315
if: >-
${{
! (
contains(github.event.pull_request.labels.*.name, 'invalid')
|| contains(github.event.pull_request.labels.*.name, 'skip ci')
)
}}
runs-on: ubuntu-latest
steps:
- name: Check GitHub API rate limit
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# https://shogo82148.github.io/blog/2020/10/23/github-bot-using-actions/
# https://stackoverflow.com/a/59352240/4907315
# https://docs.github.com/ja/actions/learn-github-actions/workflow-commands-for-github-actions
# https://qiita.com/ryo0301/items/de8ce43fe61ede66f80a
# https://stedolan.github.io/jq/manual/v1.6/#keys,keys_unsorted
# https://qiita.com/richmikan@github/items/2aee77ae13bee2c648f4
run: |
readonly DATE_FORMAT='+%Y/%m/%d %T %Z'
RATE_LIMIT_JSON="$(gh api 'rate_limit')"
echo "${RATE_LIMIT_JSON}" | jq -r '.resources | keys_unsorted[]' | while read -r type; do
echo '::group::' "${type}"
echo "${RATE_LIMIT_JSON}" | jq ".resources.${type}"
echo "::notice::Reset time is $(date -ud "@$(echo "${RATE_LIMIT_JSON}" | jq -r ".resources.${type}.reset")" "${DATE_FORMAT}")"
echo '::endgroup::'
done
core_remaining="$(echo "${RATE_LIMIT_JSON}" | jq -r ".resources.core.remaining")"
if [ "${core_remaining}" == 0 ]; then
core_reset="$(echo "${RATE_LIMIT_JSON}" | jq -r ".resources.core.reset")"
echo "::error::Rate limit reached. Please wait until $(date -ud "@${core_reset}" "${DATE_FORMAT}")"
exit 1
elif [ "${core_remaining}" -lt 10 ]; then
echo "::warning::The rate limit is approaching: core.remaining=${core_remaining}"
fi
detect-supported-node:
needs: if-run-ci
runs-on: ubuntu-latest
outputs:
versions-json: ${{ steps.detector.outputs.versions-json }}
latest-commit-diff: ${{ steps.latest-commit-diff.outputs.result }}
steps:
- name: Checkout
uses: actions/checkout@v3
- id: detector
uses: sounisi5011/npm-packages@actions/get-nodejs-versions-array-v0
- name: Get the diff from the latest commit where the GitHub Actions succeeded
id: latest-commit-diff
uses: actions/github-script@v6
with:
result-encoding: string
# see https://stackoverflow.com/a/74868904
script: |
const { inspect } = require('util');
async function fetchActionsSucceededSha1List({ workflowName, ...variables }) {
// Use the GraphQL API instead of the REST API.
// This allows us to save the rate limits of the REST API.
// Instead, this step consumes the GraphQL API rate limit, but since few jobs use the GraphQL API, this is probably not a problem.
const query = `query($owner: String!, $repo: String!, $pullRequest: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $pullRequest) {
commits(last: 250) {
nodes {
url
commit {
oid
authoredDate
committedDate
checkSuites(first: 100) {
nodes {
conclusion
workflowRun {
workflow { name }
}
}
}
}
}
}
}
}
}`;
const result = await github.graphql(query, variables);
console.log(`::group::fetch result\n${JSON.stringify(result, null, 2)}\n::endgroup::`);
return (
result.repository.pullRequest.commits.nodes
.filter(pullRequestCommit =>
pullRequestCommit.commit.checkSuites.nodes.some(checkSuite =>
checkSuite.workflowRun?.workflow.name === workflowName
&& checkSuite.conclusion === 'SUCCESS'
)
)
.map(pullRequestCommit => ({
sha1: pullRequestCommit.commit.oid,
date: Math.max(
new Date(pullRequestCommit.commit.authoredDate).getTime(),
new Date(pullRequestCommit.commit.committedDate).getTime(),
),
}))
.sort((a, b) => b.date - a.date)
.map(({ sha1 }) => sha1)
);
}
try {
const baseSha1 = context.payload.pull_request?.base.sha;
console.log('::group::baseSha1\n', baseSha1, '\n::endgroup::');
if (!baseSha1) return '/';
// see https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables
const workflowNameOrFilepath = process.env.GITHUB_WORKFLOW ?? context.workflow;
const variables = {
owner: context.repo.owner,
repo: context.repo.repo,
pullRequest: context.issue.number ?? Number(/^refs\/pull\/(\d+)(?:\/|$)/.exec(context.ref)?.[1]),
workflowName: workflowNameOrFilepath,
};
console.log('::group::variables\n', variables, '\n::endgroup::');
const isFirstCommit = !context.payload.before;
const actionsSucceededSha1List = isFirstCommit || !Number.isInteger(variables.pullRequest)
? null
: await fetchActionsSucceededSha1List(variables);
console.log('::group::actionsSucceededSha1List\n', actionsSucceededSha1List, '\n::endgroup::');
try {
console.log('::group::Get file differences');
const latestSha1 = actionsSucceededSha1List?.[0] ?? baseSha1;
await exec.exec('git fetch --no-tags --prune --no-recurse-submodules --depth=1 origin', [latestSha1]);
const { stdout: diffResult } = await exec.getExecOutput('git diff --name-only', [latestSha1]);
return diffResult;
} finally {
console.log('::endgroup::');
}
} catch (error) {
core.error(inspect(error));
console.log(`::group::context\n${JSON.stringify({ ...context, issue: context.issue, repo: context.repo }, null, 2)}\n::endgroup::`);
return '/';
}
lint:
needs: if-run-ci
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Reconfigure git to use HTTP authentication
# see https://stackoverflow.com/a/69634516
# see https://github.com/actions/setup-node/issues/214#issuecomment-810829250
shell: bash
run: |
git config --global \
'url.https://${{ secrets.GITHUB_TOKEN }}@github.com/.insteadOf' \
'ssh://git@github.com/'
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 14.x
- name: Enable Corepack (Automatically setup a package manager for Node.js)
run: |
corepack enable
corepack enable npm
- name: Show node and package manager version
shell: bash
run: |
echo node "$(node --version)"
echo pnpm "$(pnpm --version)"
echo "pnpm-store-path=$(pnpm store path --silent)" >> "${GITHUB_ENV}"
- name: Cache pnpm
uses: actions/cache@v3
with:
key: node-cache-${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
path: ${{ env.pnpm-store-path }}
- name: Install dependencies
run: pnpm install
- name: Run linter
run: pnpm run test --pre-test-only
unit-test:
needs: detect-supported-node
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
node-version: ${{ fromJson(needs.detect-supported-node.outputs.versions-json) }}
runs-on: ${{ matrix.os }}
steps:
- name: Determine whether to skip the test
shell: python
env:
LATEST_COMMIT_DIFF: ${{ needs.detect-supported-node.outputs.latest-commit-diff }}
IGNORE_FILES: |
.github/workflows/check-if-changelog-is-updated.yaml
.github/workflows/pr-auto-approve.yaml
.github/workflows/update-license-year.yaml
.template/readme.njk
.vscode/settings.json
.codeclimate.yml
.renovaterc.json5
CHANGELOG.md
CONTRIBUTING.md
lefthook.yml
LICENSE
README.md
run: |
import os
import random
def str_to_set(lines: str):
return set(lines.strip('\n').split('\n'))
def export_var(name: str, value: str):
with open(os.environ.get('GITHUB_ENV'), 'a') as f:
# see https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings
delimiter = ""
while (delimiter in name) or (delimiter in value):
delimiter = f"ghadelimiter_{hex(random.getrandbits(255))}"
f.write(f"{name}<<{delimiter}\n{value}\n{delimiter}\n")
diff_files = str_to_set(os.environ.get('LATEST_COMMIT_DIFF'))
ignore_files = str_to_set(os.environ.get('IGNORE_FILES'))
if ('/' in diff_files) or (0 < len(diff_files - ignore_files - {''})):
export_var('run-test', '1')
- uses: actions/checkout@v3
if: ${{ env.run-test }}
- name: Reconfigure git to use HTTP authentication
if: ${{ env.run-test }}
# see https://stackoverflow.com/a/69634516
# see https://github.com/actions/setup-node/issues/214#issuecomment-810829250
shell: bash
run: |
git config --global \
'url.https://${{ secrets.GITHUB_TOKEN }}@github.com/.insteadOf' \
'ssh://git@github.com/'
- name: Install Node.js
if: ${{ env.run-test }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Enable Corepack (Automatically setup a package manager for Node.js)
if: ${{ env.run-test }}
shell: bash
run: |
# Corepack v0.14 is the earliest version that can use the environment variable COREPACK_ENABLE_STRICT.
# see https://github.com/nodejs/corepack/blob/v0.14.0/CHANGELOG.md#0140-2022-09-02
# In addition, this version supports Node.js 14.14.0 or later.
# see https://github.com/nodejs/corepack/pull/227
readonly COREPACK_MIN_VERSION='0.14'
exec_with_debug() {
node - "$@" << 'EOS'
// These characters cannot be wrapped in double quotes:
// - !
// - $
// - \
// - `
const cannotInQuotChars = /!$\\`/;
const quotedArgs = process.argv.slice(2)
.map(arg => {
const wrapApos = `'${arg.replace(new RegExp(`'[^"${cannotInQuotChars.source}]*`, 'g'), `'"$&"'`)}'`.replace(/^''|''$/g, ``);
const wrapQuot = `"${arg.replace(/"[^']*/g, `"'$&'"`)}"`.replace(/^""|""$/g, ``);
return arg === `` || /[ !"$&'()*;<>?[\]\\`{|}~]/.test(arg) ? (
new RegExp(`[${cannotInQuotChars.source}]`).test(arg) || wrapApos.length <= wrapQuot.length
? wrapApos
: wrapQuot
) : arg
})
.join(' ');
// The "[command]" prefix may cause the display to shift, so use it with the "setTimeout" function.
setTimeout(() => {
process.stderr.write(`[command]${quotedArgs}`);
setTimeout(() => process.stderr.write("\n"), 1);
}, 1);
EOS
"$@"
}
version_lte() {
local op1 op2 i max_i
# see https://genzouw.com/entry/2019/12/17/120057/1831/
# see https://www.shellcheck.net/wiki/SC2206
IFS='.' read -r -a op1 <<< "$1"
IFS='.' read -r -a op2 <<< "$2"
max_i="${#op1[@]}"
if [[ "${#op2[@]}" -lt "${max_i}" ]]; then
max_i="${#op2[@]}"
fi
for ((i=0; i<"${max_i}"; i++)); do
if [[ "${op1[i]}" -lt "${op2[i]}" ]]; then
return 0
elif [[ "${op2[i]}" -lt "${op1[i]}" ]]; then
return 1
fi
done
return 0
}
# On Windows we can't use the CLI we installed just by running the "npm install --global ..." command.
# This function allows use of the installed CLI.
npm_install_global() {
exec_with_debug npm install --global --force "$1"
# Clear the bash command cache.
# If the package is overwritten, the path to the command may change.
# To track this, remove the command cache.
exec_with_debug hash -r
if [[ '${{ runner.os }}' == 'Windows' ]]; then
# Windows installs global packages to a directory that has lower priority than the default node install so we also need to edit $PATH
# see https://github.com/vercel/turbo/pull/1632/files#diff-b92a3120126a9ffe46d7d5ec3a8496ef1eac951db09e1972fac7c78438e36c42R69
local -r globalPath="$(exec_with_debug npm config get prefix)"
echo "${globalPath}"
local -r unixLikeGlobalPath="$(exec_with_debug cygpath --unix "${globalPath}")"
echo "${unixLikeGlobalPath}"
if [[ "${PATH}" != "${unixLikeGlobalPath}:"* ]]; then
echo "[command]echo '${globalPath}' >> \$GITHUB_PATH"
echo "${globalPath}" >> "${GITHUB_PATH}"
echo "[command]PATH='${unixLikeGlobalPath}':\$PATH"
PATH="${unixLikeGlobalPath}:${PATH}"
fi
fi
}
is_packageManager_enabled_by_corepack() {
local -r targetPackageManagerCLI="$1"
local -r packageManagerVersion="$2"
# If the package manager CLI does not exist, Corepack is not enabled and should return false
! type "${targetPackageManagerCLI}" >/dev/null 2>&1 && return 1
# If the "npm/yarn/pnpm --version" command fails, Corepack has disabled this package manager and should return false
local targetPackageManagerVersion
targetPackageManagerVersion="$("${targetPackageManagerCLI}" --version 2>/dev/null)" || return 1
[[ "${targetPackageManagerVersion}" = "${packageManagerVersion%%+*}" ]]
}
is_packageManager_disabled_by_corepack() {
local -r targetPackageManagerCLI="$1"
local -r packageManagerName="$2"
# If the package manager CLI does not exist, Corepack is not enabled and should return false
! type "${targetPackageManagerCLI}" >/dev/null 2>&1 && return 1
# If the "npm/yarn/pnpm --version" command succeeds, Corepack has not disabled this package manager and should return false
local errorMessage
errorMessage="$("${targetPackageManagerCLI}" --version 2>&1)" && return 1
echo "${errorMessage}" | grep -qE "\bThis project is configured to use ${packageManagerName}\b"
}
# Note: On Windows, the `npm ls --global corepack` command cannot be used to detect the builtin Corepack.
# So, use this complex conditional expression.
corepack_enabled() {
local -r packageManager="$(< "${GITHUB_WORKSPACE}/package.json" jq --raw-output '.packageManager')"
if [[ "${packageManager}" == 'npm@'* ]]; then
is_packageManager_enabled_by_corepack npm "${packageManager#*@}" \
&& is_packageManager_disabled_by_corepack yarn "${packageManager%%@*}" \
&& is_packageManager_disabled_by_corepack pnpm "${packageManager%%@*}"
elif [[ "${packageManager}" == 'yarn@'* ]]; then
is_packageManager_enabled_by_corepack yarn "${packageManager#*@}" \
&& is_packageManager_disabled_by_corepack npm "${packageManager%%@*}" \
&& is_packageManager_disabled_by_corepack pnpm "${packageManager%%@*}"
elif [[ "${packageManager}" == 'pnpm@'* ]]; then
is_packageManager_enabled_by_corepack pnpm "${packageManager#*@}" \
&& is_packageManager_disabled_by_corepack npm "${packageManager%%@*}" \
&& is_packageManager_disabled_by_corepack yarn "${packageManager%%@*}"
else
# see https://stackoverflow.com/a/23550347
>&2 echo "Unsupported package manager specification: '${packageManager}'"
exit 1
fi
}
if type corepack >/dev/null 2>&1; then
if version_lte "${COREPACK_MIN_VERSION}" "$(corepack --version)"; then
exec_with_debug corepack --version
echo '::group::Try enable Corepack'
else
echo "::group::Old Corepack is detected ( corepack@$(corepack --version 2>/dev/null || echo '[Execution failed; Unknown version]') ). Update this"
npm_install_global "corepack@${COREPACK_MIN_VERSION}"
echo '::endgroup::'
exec_with_debug corepack --version
echo '::group::Enable Corepack'
fi
exec_with_debug corepack enable
exec_with_debug corepack enable npm
echo '::endgroup::'
fi
# If Corepack is not available, install it manually.
# Note: Corepack is already installed on GitHub Actions.
# But it does not manage npm versions.
# To manage npm, Corepack must be installed via npm, which is builtin to the installed Node.js.
if ! corepack_enabled; then
echo '::warning::Failed to enable Corepack'
echo '::group::Install Corepack manually'
if type corepack >/dev/null 2>&1; then
# Disable the built-in Corepack in GitHub Actions.
# If enabled, problems will occur when the yarn command is executed.
exec_with_debug corepack disable
fi
npm_install_global "corepack@${COREPACK_MIN_VERSION}"
echo '::endgroup::'
exec_with_debug corepack --version
echo '::group::Enable Corepack'
exec_with_debug corepack enable
exec_with_debug corepack enable npm
echo '::endgroup::'
fi
- name: Show node and package manager version
if: ${{ env.run-test }}
shell: bash
run: |
echo node "$(node --version)"
echo pnpm "$(pnpm --version)"
echo "pnpm-store-path=$(pnpm store path --silent)" >> "${GITHUB_ENV}"
- name: Cache pnpm
if: ${{ env.run-test }}
uses: actions/cache@v3
with:
key: node-cache-${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
path: ${{ env.pnpm-store-path }}
- name: Install dependencies
if: ${{ env.run-test }}
run: pnpm install
- name: Run unit test
if: ${{ env.run-test }}
run: pnpm run test --test-only
# Successfully complete this job when all jobs have been completed.
# Only by checking this job, it is possible to determine if CI is complete or not.
# So we can simplify our GitHub status check configuration.
# see https://github.com/orgs/community/discussions/26822
# see https://github.com/sounisi5011/npm-packages/blob/2a5ca2de696eeb8b40a38de90580441c4c6c96e0/.github/workflows/ci.yaml#L482-L498
complete:
name: Complete CI
needs: [lint, unit-test]
if:
${{ always() && github.event.pull_request }}
# This job is required only for Pull Requests.
# It does not need to be run on other branches.
runs-on: ubuntu-latest
steps:
- name: Check all job status
# see https://docs.github.com/en/actions/learn-github-actions/contexts#needs-context
# see https://docs.github.com/en/actions/learn-github-actions/expressions#contains
# see https://stackoverflow.com/a/67532120/4907315
if: >-
${{
contains(needs.*.result, 'failure')
|| contains(needs.*.result, 'cancelled')
|| contains(needs.*.result, 'skipped')
}}
run: exit 1