Skip to content

Commit

Permalink
ci(github actions): auto-detect the version of Node.js used in unit t…
Browse files Browse the repository at this point in the history
…ests (#675)

* ci(github actions): auto-detect the version of Node.js used in unit tests

    Automatically detects the version of Node.js on which to run unit tests based on the value of the "engines.node" field.

* test(./actions/get-nodejs-versions-array): add test for `src/semver-range.ts` file

* fix(./actions/get-nodejs-versions-array): fix `specifiedMaxMajorVersion()` function

* fix(./actions/get-nodejs-versions-array): modify the text of error messages

* test(./actions/get-nodejs-versions-array): add more tests for the `specifiedMaxMajorVersion()` function

    Added test for regular expression to parse major version.

* test(./actions/get-nodejs-versions-array): add test for `src/utils.ts` file

* refactor(./actions/get-nodejs-versions-array): reduce lines in `specifiedMaxMajorVersion()` function

    Code Climate reported:
    + Function `specifiedMaxMajorVersion` has 27 lines of code (exceeds 25 allowed). Consider refactoring.

* refactor(./actions/get-nodejs-versions-array): use `import type ...` statements instead of `import ...` statements

* test(./actions/get-nodejs-versions-array): add test for `src/main.ts` file

    Added test if it terminates successfully.

* test(./actions/get-nodejs-versions-array): fix the test to not use existing environment variables

* test(./actions/get-nodejs-versions-array): use `require('node:fs').constants` instead of `require('node:fs/promises').constants`

    `require('node:fs/promises').constants` is available in Node.js 16.17.0 and above.
    Not available in Node.js 16.0.0.
    see https://nodejs.org/docs/latest-v16.x/api/fs.html#fspromisesconstants

* test(./actions/get-nodejs-versions-array): rename `tests/tmp` directory to `tests/.temp`

    This directory name is one of the names that Vitest will exclude without configuration.
    see https://vitest.dev/config/#exclude

* test(./actions/get-nodejs-versions-array): add more tests for `src/main.ts` file

    Added tests for when this fails.

* refactor(./actions/get-nodejs-versions-array): reduce lines and Cognitive Complexity within the `run` function

    Code Climate reported:
    + Function `run` has 63 lines of code (exceeds 25 allowed). Consider refactoring.
    + Function `run` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.

* ci(code climate): ignore `*.test.ts` files

* refactor(./actions/get-nodejs-versions-array): reduce Cognitive Complexity within the `getVersionSpecList` function

    Code Climate reported:
    + Function `getVersionSpecList` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.

* ci(./actions/get-nodejs-versions-array): create a release tag containing only actions directly under the repository at release time

    Until now, users had to use long action names like this:

    ```yaml
    steps:
      - uses: sounisi5011/npm-packages/actions/get-nodejs-versions-array@get-nodejs-versions-array-action-v0
    ```

    However, by creating this release tag, users can now use short action names like this:

    ```yaml
    steps:
      - uses: sounisi5011/npm-packages@actions/get-nodejs-versions-array-v0
    ```

* ci(./actions/get-nodejs-versions-array): fix `.github/workflows/publish.sh` files

    + Use the same message in all Git tags
    + Fixed the text in the `README.md` file
    + Fixed shell scripts based on shellcheck report

* fix(./actions/get-nodejs-versions-array): modify error message when file does not exist

* fix(./actions/get-nodejs-versions-array): use `graceful-fs` instead of the builtin `node:fs` module

    Use `graceful-fs` to avoid `EMFILE` and `ENFILE` errors.
    see https://github.com/isaacs/node-graceful-fs/blob/v4.2.10/graceful-fs.js#L109-L127

* perf(./actions/get-nodejs-versions-array): reduce file size of generated files

    + `dist/index.js`
        200945 bytes -> 176276 bytes

* chore: ESLint, Prettier, and dprint should ignore the `.temp/` directory

* build(./actions/get-nodejs-versions-array): no need to pass the environment variable `TS_NODE_PROJECT` to the `@vercel/ncc`

* chore(git): ignore `.temp/` directories

* test(./actions/get-nodejs-versions-array): automatically create `tests/.temp/` directories

    No need to add this directory to Git.

* docs(./actions/get-nodejs-versions-array): fix the `description` field in the `package.json` file

* ci(./actions/get-nodejs-versions-array): fix `.github/workflows/publish.sh` files - create `package.json` files required for ESM

* chore(./actions/get-nodejs-versions-array): update dependencies

* build(./actions/get-nodejs-versions-array): fix `tsconfig.json`

* build(./actions/get-nodejs-versions-array): fix `tsconfig.json`

* test(./actions/get-nodejs-versions-array): remove `@sounisi5011/ts-utils-is-property-accessible` from dependencies

* chore(eslint): disable "vitest/max-expects" rules

    see https://github.com/veritem/eslint-plugin-vitest/blob/v0.0.54/docs/rules/max-expects.md

    We do not think we need to limit the number of `expect()`.

* chore(deps): update dependency "eslint-plugin-vitest"
  • Loading branch information
sounisi5011 authored Mar 4, 2023
1 parent 2a5ca2d commit a40259b
Show file tree
Hide file tree
Showing 28 changed files with 2,367 additions and 32 deletions.
4 changes: 4 additions & 0 deletions .codeclimate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ exclude_patterns:
- "**/scripts/*.js"
# Jest configuration file ( https://jestjs.io/ )
- "**/jest.config.js"
# Test files
- "**/*.test.ts"
- "**/*.test.cts"
- "**/*.test.mts"
# Test files for tsd ( https://www.npmjs.com/package/tsd )
- "**/*.test-d.ts"
- "**/*.test-d.cts"
Expand Down
1 change: 1 addition & 0 deletions .dprint-js.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"**/protocol-buffers/*_pb.js",
"**/protocol-buffers/*_pb.d.ts",
"**/runkit-example.js",
"**/.temp/",
"**/coverage/",
"**/node_modules/",
"**/dist/",
Expand Down
1 change: 1 addition & 0 deletions .dprint.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"**/protocol-buffers/*_pb.js",
"**/protocol-buffers/*_pb.d.ts",
"**/runkit-example.js",
"**/.temp/",
"**/coverage/",
"**/node_modules/",
"**/dist/",
Expand Down
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
**/protocol-buffers/*_pb.js
**/protocol-buffers/*_pb.d.ts
**/runkit-example.js
.temp/
coverage/
node_modules/
dist/
Expand Down
8 changes: 8 additions & 0 deletions .eslintrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ rules:
- devDependencies:
- "*"
- ".*"
- "**/*.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"
- "**/*.test-d.{ts,cts,mts}"
- "**/tests/**"
import/order:
Expand Down Expand Up @@ -160,6 +161,13 @@ overrides:
jest/prefer-todo: error
jest/require-top-level-describe: error
jest/valid-title: error
- files:
- "**/*.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"
extends:
- plugin:vitest/all
rules:
# We do not think we need to limit the number of `expect()`
vitest/max-expects: off
- files: "**/examples/**"
rules:
node/no-missing-require: off
Expand Down
42 changes: 19 additions & 23 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -374,48 +374,44 @@ jobs:
else
echo '::notice::No uncommitted changes by build'
fi
detect-supported-node:
needs: if-run-ci
runs-on: ubuntu-latest
outputs:
versions-json: ${{ steps.detector.outputs.versions-json }}
steps:
- uses: actions/checkout@v3
- id: detector
uses: ./actions/get-nodejs-versions-array
unit-test:
needs: pre-build
needs: [detect-supported-node, pre-build]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version:
- 12.17.0 # minimum version supported by pnpm v6 - https://github.com/pnpm/pnpm/releases/tag/v6.0.0
- 12.20.0 # minimum version that supports ECMAScript modules - https://nodejs.org/api/esm.html#modules-ecmascript-modules
- 12.x
- 14.1.0
node-version: ${{ fromJson(needs.detect-supported-node.outputs.versions-json) }}
exclude:
- node-version: 14.0.0
# When trying install dependencies via pnpm in Node.js 14.0.0, pnpm throws the following error:
# Error [ERR_STREAM_PREMATURE_CLOSE]: Premature close
# This problem started occurring around 2022-06-27.
# It is probably an issue with the npm registry, as it also started occurring with older pnpm.
- 14.13.0 # minimum version that supports ECMAScript modules - https://nodejs.org/api/esm.html#modules-ecmascript-modules
- 14.13.1 # minimum version that supports "node:" import - https://nodejs.org/api/esm.html#node-imports
- 14.18.0 # minimum version where the "require()" function supports "node:" import - https://nodejs.org/api/modules.html#core-modules
- 14.x
- 15.0.0
- 15.x
- 16.0.0
- 16.x
- 17.0.0
- 17.x
- 18.0.0
- 18.x
include:
# Corepack 0.11.0 and later only supports Node.js 14.14.0 and later
# see https://github.com/nodejs/corepack/pull/227
- node-version: 12.17.0
- node-version: 12.17.0 # minimum version supported by pnpm v6 - https://github.com/pnpm/pnpm/releases/tag/v6.0.0
corepack-version-range: "<0.11"
- node-version: 12.20.0
- node-version: 12.20.0 # minimum version that supports ECMAScript modules - https://nodejs.org/api/esm.html#modules-ecmascript-modules
corepack-version-range: "<0.11"
- node-version: 12.x
corepack-version-range: "<0.11"
- node-version: 14.1.0
- node-version: 14.1.0 # 14.0.0 excluded, so add the next version 14.1.0
corepack-version-range: "<0.11"
- node-version: 14.13.0
- node-version: 14.13.0 # minimum version that supports ECMAScript modules - https://nodejs.org/api/esm.html#modules-ecmascript-modules
corepack-version-range: "<0.11"
- node-version: 14.13.1
- node-version: 14.13.1 # minimum version that supports "node:" import - https://nodejs.org/api/esm.html#node-imports
corepack-version-range: "<0.11"
- node-version: 14.18.0 # minimum version where the "require()" function supports "node:" import - https://nodejs.org/api/modules.html#core-modules
steps:
- uses: actions/checkout@v3
- name: Download installed dependencies
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Temporary directory
.temp/

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# Ultra Runner cache
.ultra.cache.json

.temp/
coverage/
node_modules/
dist/
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,5 @@

| Package | Version |
|-|-|
| [`get-nodejs-versions-array-action`](./actions/get-nodejs-versions-array) | [`v0.0.1`](https://github.com/sounisi5011/npm-packages/tree/get-nodejs-versions-array-action-v0.0.1/actions/get-nodejs-versions-array) |
| [`monorepo-workspace-submodules-finder-action`](./actions/monorepo-workspace-submodules-finder) | [`v1.3.2`](https://github.com/sounisi5011/npm-packages/tree/monorepo-workspace-submodules-finder-action-v1.3.2/actions/monorepo-workspace-submodules-finder) |
1 change: 1 addition & 0 deletions actions/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
!dist/
*/dist/package.json
121 changes: 121 additions & 0 deletions actions/get-nodejs-versions-array/.github/workflows/publish.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#!/bin/bash

git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'
git remote add gh-token "https://${GITHUB_TOKEN}@github.com/sounisi5011/npm-packages.git"

update_git_tag() {
local tag_name="$1"
local tag_message="$2"
git tag -d "${tag_name}" || true
git push origin :"${tag_name}" || true
git tag -a "${tag_name}" -m "${tag_message}"
git push origin "${tag_name}"
}

# shellcheck disable=SC2154
readonly tagMessage="Release ${matrix_package_name_with_scope}@${outputs_major}.${outputs_minor}.${outputs_patch}"

# shellcheck disable=SC2154
update_git_tag "${matrix_package_name_without_scope}-v${outputs_major}" "${tagMessage}"
update_git_tag "${matrix_package_name_without_scope}-v${outputs_major}.${outputs_minor}" "${tagMessage}"

# Try to create a commit having an action directly under the repository.
# This allows users to use this action without having to specify a directory name.
GIT_ROOT_PATH="$(git rev-parse --show-toplevel)"
GIT_RELEASE_COMMIT_REF="$(git rev-parse HEAD)"
PKG_ROOT_DIRNAME="$(realpath --relative-to="${GIT_ROOT_PATH}" "${PWD}")"
readonly GIT_ROOT_PATH GIT_RELEASE_COMMIT_REF PKG_ROOT_DIRNAME
readonly GITHUB_URL_PREFIX='https://github.com/sounisi5011/npm-packages'
readonly TAG_NAME_PREFIX='actions/get-nodejs-versions-array'
readonly latestTagName="${TAG_NAME_PREFIX}-latest"

cd "${GIT_ROOT_PATH}" || ! echo "[!] Move to '${GIT_ROOT_PATH}' failed"
echo '::group::$' git checkout "refs/tags/${latestTagName}"
if git checkout "refs/tags/${latestTagName}"; then
echo '::endgroup::'

echo '::group::$' git merge --no-commit "${GIT_RELEASE_COMMIT_REF}"
# Tries to merge in order to keep a Git history.
# However, the merge content is not used here.
# Therefore, we disable automatic commit.
git merge --no-commit "${GIT_RELEASE_COMMIT_REF}" || true
echo '::endgroup::'
else
echo '::endgroup::'
echo '[!] This action has not yet been published'
# shellcheck disable=SC2154
git switch -c "action-only--${outputs_tag_name}"
fi

echo '::group::Delete everything except the ".git" directory'
find "${GIT_ROOT_PATH}" -maxdepth 1 -mindepth 1 -type d -name '.git' -prune -o -print0 | xargs -0 rm -rf
ls -lhpa
echo '::endgroup::'

echo '::group::Restore only files required for custom actions'
git restore --source="${GIT_RELEASE_COMMIT_REF}" "${PKG_ROOT_DIRNAME}"/{action.yaml,dist}
mv "${GIT_ROOT_PATH}/${PKG_ROOT_DIRNAME}"/* "${GIT_ROOT_PATH}/"
ls -lhpa
echo '::endgroup::'

echo '::group::Create package.json file'
# This JavaScript action is written in ECMAScript modules, so this file is required.
echo '{"type":"module"}' > "${GIT_ROOT_PATH}/package.json"
cat "${GIT_ROOT_PATH}/package.json"
echo '::endgroup::'

echo '::group::Create README.md file'
{
echo '> **Note**'
echo '> This place is a copy in [the `'"${PKG_ROOT_DIRNAME}"'` directory] of [the commit specified by the '"${outputs_tag_name}"' tag]'
# shellcheck disable=SC2016
echo '> If you want to contribute, please move to [the `main` branch]!'
echo
# shellcheck disable=SC2016
echo '[the `main` branch]:' "${GITHUB_URL_PREFIX}/tree/main/${PKG_ROOT_DIRNAME}"
echo '[the commit specified by the '"${outputs_tag_name}"' tag]:' "${GITHUB_URL_PREFIX}/tree/${outputs_tag_name}"
echo '[the `'"${PKG_ROOT_DIRNAME}"'` directory]:' "${GITHUB_URL_PREFIX}/tree/${outputs_tag_name}/${PKG_ROOT_DIRNAME}"
} > "${GIT_ROOT_PATH}/README.md"
cat "${GIT_ROOT_PATH}/README.md"
echo '::endgroup::'

echo '::group::Create CHANGELOG.md file'
{
echo "Please see ${GITHUB_URL_PREFIX}/blob/${outputs_tag_name}/${PKG_ROOT_DIRNAME}/CHANGELOG.md"
} > "${GIT_ROOT_PATH}/CHANGELOG.md"
cat "${GIT_ROOT_PATH}/CHANGELOG.md"
echo '::endgroup::'

echo '::group::Create LICENSE file'
licenseFilename="${PKG_ROOT_DIRNAME}/LICENSE"
# see https://stackoverflow.com/a/444317
# see https://stackoverflow.com/a/4709925
if git ls-tree -r --name-only --full-name "${GIT_RELEASE_COMMIT_REF}" | grep -qxF "${licenseFilename}"; then
:
elif git ls-tree --name-only --full-name "${GIT_RELEASE_COMMIT_REF}" | grep -qxF 'LICENSE'; then
licenseFilename='LICENSE'
else
licenseFilename=''
fi
if [ -z "${licenseFilename}" ]; then
echo "::warning::LICENSE file not found in release commit ( ${GIT_RELEASE_COMMIT_REF} )"
else
{
echo "Please see ${GITHUB_URL_PREFIX}/blob/${outputs_tag_name}/${licenseFilename}"
} > "${GIT_ROOT_PATH}/LICENSE"
cat "${GIT_ROOT_PATH}/LICENSE"
fi
echo '::endgroup::'

echo '::group::Add commit'
git add --all
git commit --reuse-message="${GIT_RELEASE_COMMIT_REF}" --no-verify
echo '::endgroup::'

echo '::group::Add Git Tags'
update_git_tag "${latestTagName}" "${tagMessage}"
update_git_tag "${TAG_NAME_PREFIX}-v${outputs_major}" "${tagMessage}"
update_git_tag "${TAG_NAME_PREFIX}-v${outputs_major}.${outputs_minor}" "${tagMessage}"
update_git_tag "${TAG_NAME_PREFIX}-v${outputs_major}.${outputs_minor}.${outputs_patch}" "${tagMessage}"
echo '::endgroup::'
12 changes: 12 additions & 0 deletions actions/get-nodejs-versions-array/.lintstagedrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// @ts-check

const path = require('path');

const baseConfig = require('../../.lintstagedrc.cjs');

/** @type {import('../../.lintstagedrc.cjs').LintStagedConfig} */
module.exports = async filenames => [
...(await baseConfig(filenames)),
'pnpm build-with-cache',
`git add ${path.resolve(__dirname, 'dist')}`,
];
21 changes: 21 additions & 0 deletions actions/get-nodejs-versions-array/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2023 sounisi5011

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
68 changes: 68 additions & 0 deletions actions/get-nodejs-versions-array/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# get-nodejs-versions-array-action

Get an array of Node.js versions supported by your repository.

Detects supported Node.js by reading [the `engines.node` field in `package.json` file] in your repository.

[the `engines.node` field in `package.json` file]: https://docs.npmjs.com/cli/configuring-npm/package-json#engines

## Outputs

### `versions-json`

A JSON string. This value is an array of semver strings, meaning Node.js version.

For example, if [the `engines.node` field][the `engines.node` field in `package.json` file] has the following value:

```json
{
"engines": {
"node": "^14.18 || 16 || >=18"
}
}
```

the content of this output will be JSON like this:

```json
[
"14.18.0",
"14.x",
"16.0.0",
"16.x",
"18.0.0",
"18.x"
]
```

> **Note**
> The version range `^14.18 || 16 || >=18` in this example also includes Node.js 19, 20, and higher versions.
> However, the maximum version explicitly specified is `18`.
> Therefore, the output will include versions up to 18.
## Example usage

```yaml
jobs:
detect-supported-node:
runs-on: ubuntu-latest
outputs:
versions-json: ${{ steps.detector.outputs.versions-json }}
steps:
- name: Checkout
uses: actions/checkout@v3
- id: detector
uses: sounisi5011/npm-packages/actions/get-nodejs-versions-array@get-nodejs-versions-array-action-v0
unit-test:
needs: detect-supported-node
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version: ${{ fromJson(needs.detect-supported-node.outputs.versions-json) }}
steps:
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
```
11 changes: 11 additions & 0 deletions actions/get-nodejs-versions-array/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: Get Node.js versions array
author: sounisi5011
description: |
Get an array of Node.js versions supported by your repository
outputs:
versions-json:
description: |
A JSON string. This value is an array of semver strings, meaning Node.js version.
runs:
using: node16
main: dist/index.js
1 change: 1 addition & 0 deletions actions/get-nodejs-versions-array/dist/index.js

Large diffs are not rendered by default.

Loading

0 comments on commit a40259b

Please sign in to comment.