From abc92cbea6fc597d2cd37c891349df5c07ae6c8d Mon Sep 17 00:00:00 2001 From: Adam Hamrick Date: Fri, 9 Oct 2026 11:41:32 -0400 Subject: [PATCH 1/2] feat: asdf -> mise --- .changeset/migrate-asdf-to-mise.md | 34 ++++ .github/renovate.json | 6 + .github/workflows/changesets-check.yml | 2 - .github/workflows/pull-request-main.yml | 4 - .github/workflows/push-main.yml | 4 - .github/workflows/reusable-docusaurus.yml | 12 +- .../workflows/solidity-review-artifacts.yml | 3 + .tool-versions | 3 - CONTRIBUTING.md | 79 +++++++++ README.md | 57 +------ actions/ci-lint-ts/action.yml | 12 +- actions/ci-prettier/action.yml | 19 ++- actions/ci-test-sol/action.yml | 12 +- actions/ci-test-ts/action.yml | 12 +- .../action.yml | 12 +- actions/cicd-changesets/action.yml | 14 +- actions/setup-nodejs/README.md | 30 +++- actions/setup-nodejs/action.yml | 74 +++++---- .../setup-nodejs/scripts/resolve-versions.sh | 157 ++++++++++++++++++ .../scripts/resolve-versions.test.sh | 135 +++++++++++++++ actions/signed-commits/README.md | 15 +- apps/actions-dependencies-updater/README.md | 6 +- mise.toml | 28 ++++ .../reusable-docusaurus.yml | 12 +- 24 files changed, 587 insertions(+), 155 deletions(-) create mode 100644 .changeset/migrate-asdf-to-mise.md delete mode 100644 .tool-versions create mode 100644 CONTRIBUTING.md create mode 100755 actions/setup-nodejs/scripts/resolve-versions.sh create mode 100755 actions/setup-nodejs/scripts/resolve-versions.test.sh create mode 100644 mise.toml diff --git a/.changeset/migrate-asdf-to-mise.md b/.changeset/migrate-asdf-to-mise.md new file mode 100644 index 000000000..9bf7b77bd --- /dev/null +++ b/.changeset/migrate-asdf-to-mise.md @@ -0,0 +1,34 @@ +--- +"setup-nodejs": minor +"ci-lint-ts": minor +"ci-test-ts": minor +"ci-test-sol": minor +"ci-prettier": minor +"cicd-build-publish-artifacts-ts": minor +"cicd-changesets": minor +"reusable-docusaurus": minor +"changesets-signed-commits": patch +--- + +Migrate version management from asdf/`.tool-versions` to +[mise](https://mise.jdx.dev/): versions are pinned in the repo root +`mise.toml`. + +- `setup-nodejs` reads node and pnpm versions from a mise file (`mise.toml`, + `.mise.toml`, `.config/mise/config.toml`) first, falling back to + `.tool-versions`/`.nvmrc`, so downstream repos can migrate independently. A + new `node-version` input takes precedence over `node-version-file`, which + now defaults to auto-detect instead of `.tool-versions`. When a mise config + is present, `mise install` also provisions every pinned tool (e.g. + `shellcheck`) for later steps. +- `ci-lint-ts`, `ci-test-ts`, `ci-test-sol`, `ci-prettier`, + `cicd-build-publish-artifacts-ts`, `cicd-changesets`, and + `reusable-docusaurus` default `node-version-file` to auto-detect and + `pnpm-version` to the version pinned in the repo's `mise.toml` or + `.tool-versions` (fallback `^12.0.0`). `ci-prettier`'s fallback node version + bumped from `^20.16.0` to `^26.0.0` and its default pnpm moved from `^9.0.0` + to the file-driven version. +- `signed-commits` README examples now pass `node-version-file: mise.toml`. + +NOTE for consumers: repos pinning versions via `.tool-versions` keep working +unchanged. Pin versions in a `mise.toml` to pick up the new behavior. diff --git a/.github/renovate.json b/.github/renovate.json index 081b52131..e2edd7130 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -15,6 +15,12 @@ "(^|/)action\\.ya?ml$" ] }, + "mise": { + "fileMatch": [ + "(^|/)\\.?mise(\\.work)?\\.toml$", + "(^|/)\\.config/mise/config\\.toml$" + ] + }, "packageRules": [ { "groupName": "minor-patch dependencies grouped by manager", diff --git a/.github/workflows/changesets-check.yml b/.github/workflows/changesets-check.yml index 27609fe06..ec118a9da 100644 --- a/.github/workflows/changesets-check.yml +++ b/.github/workflows/changesets-check.yml @@ -67,8 +67,6 @@ jobs: - name: Setup pnpm uses: ./actions/setup-nodejs - with: - pnpm-version: "^12.0.0" - name: Dry-run changeset version env: diff --git a/.github/workflows/pull-request-main.yml b/.github/workflows/pull-request-main.yml index b4571458e..81add03b8 100644 --- a/.github/workflows/pull-request-main.yml +++ b/.github/workflows/pull-request-main.yml @@ -24,7 +24,6 @@ jobs: with: # general inputs checkout-repo: "false" - pnpm-version: "^12.0.0" ci-prettier: runs-on: ubuntu-latest @@ -41,7 +40,6 @@ jobs: with: # general inputs checkout-repo: "false" - pnpm-version: "^12.0.0" ci-lint-misc: runs-on: ubuntu-latest @@ -128,7 +126,6 @@ jobs: with: # general inputs checkout-repo: "false" - pnpm-version: "^12.0.0" ci-build-artifacts: runs-on: ubuntu-latest @@ -145,7 +142,6 @@ jobs: with: # general inputs checkout-repo: "false" - pnpm-version: "^12.0.0" - name: Check for diff in built artifacts run: | diff --git a/.github/workflows/push-main.yml b/.github/workflows/push-main.yml index e4c3d9d01..6a218c760 100644 --- a/.github/workflows/push-main.yml +++ b/.github/workflows/push-main.yml @@ -21,7 +21,6 @@ jobs: with: # general inputs checkout-repo: "false" - pnpm-version: "^12.0.0" ci-prettier: runs-on: ubuntu-latest @@ -38,7 +37,6 @@ jobs: with: # general inputs checkout-repo: "false" - pnpm-version: "^12.0.0" ci-test: runs-on: ubuntu-latest @@ -55,7 +53,6 @@ jobs: with: # general inputs checkout-repo: "false" - pnpm-version: "^12.0.0" cicd-build-publish-artifacts-dev: runs-on: ubuntu-latest @@ -77,7 +74,6 @@ jobs: uses: ./actions/cicd-build-publish-artifacts-ts with: checkout-repo: "false" - pnpm-version: "^12.0.0" cd-release: runs-on: ubuntu-latest diff --git a/.github/workflows/reusable-docusaurus.yml b/.github/workflows/reusable-docusaurus.yml index b833edfd3..fbab9e640 100644 --- a/.github/workflows/reusable-docusaurus.yml +++ b/.github/workflows/reusable-docusaurus.yml @@ -31,15 +31,19 @@ on: inputs: # nodejs inputs node-version-file: - description: "path to .tool-versions file for node version" + description: + "path to a version file for node (mise.toml, .tool-versions, + .nvmrc); empty auto-detects" required: false type: string - default: ".tool-versions" + default: "" pnpm-version: - description: "the pnpm version to use" + description: + "pnpm version; empty reads it from mise.toml/.tool-versions, falling + back to ^12.0.0" required: false type: string - default: "^12.0.0" + default: "" # docs-specific inputs build-command: description: "command to build the documentation (run from repository root, use pnpm -C to specify directory)" diff --git a/.github/workflows/solidity-review-artifacts.yml b/.github/workflows/solidity-review-artifacts.yml index 0600785c3..a2c34b512 100644 --- a/.github/workflows/solidity-review-artifacts.yml +++ b/.github/workflows/solidity-review-artifacts.yml @@ -216,6 +216,7 @@ jobs: uses: smartcontractkit/.github/actions/setup-nodejs@0b8d877a32bfa479b62ce70c1f6d2493a7c93fed # v0.0.0 with: package-json-directory: ${{ inputs.contracts_directory }} + node-version-file: "" pnpm-version: ^12.0.0 - name: Create code coverage directory @@ -326,6 +327,7 @@ jobs: uses: smartcontractkit/.github/actions/setup-nodejs@0b8d877a32bfa479b62ce70c1f6d2493a7c93fed # v0.0.0 with: package-json-directory: ${{ inputs.contracts_directory }} + node-version-file: "" pnpm-version: ^12.0.0 - name: Install Foundry @@ -491,6 +493,7 @@ jobs: uses: smartcontractkit/.github/actions/setup-nodejs@0b8d877a32bfa479b62ce70c1f6d2493a7c93fed # v0.0.0 with: package-json-directory: ${{ inputs.contracts_directory }} + node-version-file: "" pnpm-version: ^12.0.0 - name: Setup git top level directory diff --git a/.tool-versions b/.tool-versions deleted file mode 100644 index 954b0a31d..000000000 --- a/.tool-versions +++ /dev/null @@ -1,3 +0,0 @@ -nodejs 26.10.0 -pnpm 12.9.1 -shellcheck 0.11.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..460a16071 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,79 @@ +# Contributing + +This document covers setting up this repository, creating and modifying actions, +and how versioning works. + +- [Setup](#setup) + - [Dependencies](#dependencies) + - [Post-Install](#post-install) + - [Common Tasks](#common-tasks) +- [New Actions](#new-actions) +- [Existing Actions](#existing-actions) +- [Versioning](#versioning) + +## Setup + +### Dependencies + +- [`mise`](https://mise.jdx.dev/) - manages the `node`, `pnpm`, and `shellcheck` + versions pinned in the repo root [`mise.toml`](mise.toml) + +### Post-Install + +- `mise install` - will install versions as per the `mise.toml` file +- `mise run install` - install all npm dependencies (`pnpm install`) and the git + pre-commit hook (`pnpm lefthook install`) + - `pnpm lefthook run pre-commit` - to run the pre-commit hook manually + +### Common Tasks + +Common operations are available as mise tasks, runnable from anywhere in the +repo: + +| Task | Description | +| ------------------------- | ---------------------------------------------- | +| `mise run lint` | Lint all projects | +| `mise run build` | Build all projects | +| `mise run test` | Test all projects | +| `mise run format` | Format all files with prettier | +| `mise run workflows:sync` | Sync reusable workflows into .github/workflows | + +These delegate to the pnpm scripts in the root `package.json`, which can also be +invoked directly (`pnpm run lint`, etc.). + +## New Actions + +1. Generate the Action boilerplate + ```sh + pnpm nx generate nx-chainlink:create-gh-action + ``` +2. Make your changes +3. Add a changeset for your new action (`pnpm changeset`) +4. Commit and open a PR + +## Existing Actions + +1. Modify the action as needed +2. Build the action if it is written in JS/TS +3. Add a changeset (`pnpm changeset`) +4. Commit and open a PR + +## Versioning + +Actions are versioned through an automated process managed by +[`changesets`](https://github.com/changesets/changesets). The process is as +follows: + +1. You merge a change with a changeset file (in the `.changeset` directory) + 1. Created through invoking `pnpm changeset` + 2. Or through [`gocs`](https://github.com/smartcontractkit/gocs) + (`go install github.com/smartcontractkit/gocs/cmd/gocs@latest`) +2. A "Version packages" pull request will open or update + ([ex](https://github.com/smartcontractkit/.github/pull/540)). This PR will + "consume" the changesets present in the default branch by: + 1. Deleting the changeset files + 2. Adding the changeset content to the respective changelogs + 3. Bump the versions in the `package.json` according to the changeset + (patch/minor/major) +3. The "Version packages" PR gets merged, and the git tags for the actions' + versions will be created during CICD. diff --git a/README.md b/README.md index d9ac07316..71d306ef5 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,6 @@ network of repositories. - [Action Versions](#action-versions) - [Automated Updates](#automated-updates) - [Contributing](#contributing) - - [Setup](#setup) - - [New Actions](#new-actions) - - [Existing Actions](#existing-actions) - - [Versioning](#versioning) - [Example Usage](#example-usage) ## Using Actions @@ -44,57 +40,8 @@ or patch versions are released for the major version you are pinned to. ## Contributing -### Setup - -#### Dependencies - -- [`asdf`](https://asdf-vm.com/) -- [`pnpm`](https://pnpm.io/) -- `nodejs` - -#### Post-Install - -- `asdf install` - will install versions as per the `.tool-versions` file -- `pnpm install` - install all npm dependencies as required -- `pnpm lefthook install` - install the git pre-commit hook for formatting - - `pnpm lefthook run pre-commit` - to run the pre-commit hook manually - -### New Actions - -1. Generate the Action boilerplate - ```sh - pnpm nx generate nx-chainlink:create-gh-action - ``` -2. Make your changes -3. Add a changeset for your new action (`pnpm changeset`) -4. Commit and open a PR - -### Existing Actions - -1. Modify the action as needed -2. Build the action if it is written in JS/TS -3. Add a changeset (`pnpm changeset`) -4. Commit and open a PR - -### Versioning - -Actions are versioned through an automated process managed by -[`changesets`](https://github.com/changesets/changesets). The process is as -follows: - -1. You merge a change with a changeset file (in the `.changeset` directory) - 1. Created through invoking `pnpm changeset` - 2. Or through [`gocs`](https://github.com/smartcontractkit/gocs) - (`go install github.com/smartcontractkit/gocs/cmd/gocs@latest`) -2. A "Version packages" pull request will open or update - ([ex](https://github.com/smartcontractkit/.github/pull/540)). This PR will - "consume" the changesets present in the default branch by: - 1. Deleting the changeset files - 2. Adding the changeset content to the respective changelogs - 3. Bump the versions in the `package.json` according to the changeset - (patch/minor/major) -3. The "Version packages" PR gets merged, and the git tags for the actions' - versions will be created during CICD. +See [CONTRIBUTING.md](CONTRIBUTING.md) for how to set up this repository, create +and modify actions, and how versioning works. ## Example Usage diff --git a/actions/ci-lint-ts/action.yml b/actions/ci-lint-ts/action.yml index 307cf4f5c..de5ac135a 100644 --- a/actions/ci-lint-ts/action.yml +++ b/actions/ci-lint-ts/action.yml @@ -21,13 +21,17 @@ inputs: default: main # nodejs inputs node-version-file: - description: "" + description: + "path to a version file for node (mise.toml, .tool-versions, .nvmrc); + empty auto-detects" required: false - default: ".tool-versions" + default: "" pnpm-version: - description: "" + description: + "pnpm version; empty reads it from mise.toml/.tool-versions, falling back + to ^12.0.0" required: false - default: "^12.0.0" + default: "" runs: using: composite diff --git a/actions/ci-prettier/action.yml b/actions/ci-prettier/action.yml index e968b0fb4..cd7bf6a27 100644 --- a/actions/ci-prettier/action.yml +++ b/actions/ci-prettier/action.yml @@ -18,18 +18,22 @@ inputs: default: "0" # nodejs inputs node-version-file: - description: "" + description: + "path to a version file for node (mise.toml, .tool-versions, .nvmrc); + empty auto-detects" required: false - default: ".tool-versions" + default: "" nodejs-version: description: - "NodeJS version used only if the node-version-file isn't provided" + "NodeJS version written to node-version-file when that file doesn't exist" required: false - default: "^20.16.0" + default: "^26.0.0" pnpm-version: - description: "" + description: + "pnpm version; empty reads it from mise.toml/.tool-versions, falling back + to ^12.0.0" required: false - default: "^9.0.0" + default: "" runs: using: composite @@ -40,7 +44,8 @@ runs: with: fetch-depth: ${{ inputs.checkout-repo-fetch-depth }} - - name: Create .tool-versions file if it doesn't exist + - name: Create version file if it doesn't exist + if: inputs.node-version-file != '' shell: bash run: | if [ ! -f "${{ inputs.node-version-file }}" ]; then diff --git a/actions/ci-test-sol/action.yml b/actions/ci-test-sol/action.yml index 896c1f04d..c4866e186 100644 --- a/actions/ci-test-sol/action.yml +++ b/actions/ci-test-sol/action.yml @@ -42,13 +42,17 @@ inputs: default: "--report lcov" # nodejs inputs node-version-file: - description: "" + description: + "path to a version file for node (mise.toml, .tool-versions, .nvmrc); + empty auto-detects" required: false - default: ".tool-versions" + default: "" pnpm-version: - description: "" + description: + "pnpm version; empty reads it from mise.toml/.tool-versions, falling back + to ^12.0.0" required: false - default: "^12.0.0" + default: "" runs: using: composite diff --git a/actions/ci-test-ts/action.yml b/actions/ci-test-ts/action.yml index 807dbd983..61aefbaa5 100644 --- a/actions/ci-test-ts/action.yml +++ b/actions/ci-test-ts/action.yml @@ -21,13 +21,17 @@ inputs: default: "false" # nodejs inputs node-version-file: - description: "" + description: + "path to a version file for node (mise.toml, .tool-versions, .nvmrc); + empty auto-detects" required: false - default: ".tool-versions" + default: "" pnpm-version: - description: "" + description: + "pnpm version; empty reads it from mise.toml/.tool-versions, falling back + to ^12.0.0" required: false - default: "^12.0.0" + default: "" runs: using: composite diff --git a/actions/cicd-build-publish-artifacts-ts/action.yml b/actions/cicd-build-publish-artifacts-ts/action.yml index 6bc302c2d..a24b3003a 100644 --- a/actions/cicd-build-publish-artifacts-ts/action.yml +++ b/actions/cicd-build-publish-artifacts-ts/action.yml @@ -48,13 +48,17 @@ inputs: required: false # nodejs inputs node-version-file: - description: "" + description: + "path to a version file for node (mise.toml, .tool-versions, .nvmrc); + empty auto-detects" required: false - default: ".tool-versions" + default: "" pnpm-version: - description: "" + description: + "pnpm version; empty reads it from mise.toml/.tool-versions, falling back + to ^12.0.0" required: false - default: "^12.0.0" + default: "" # foundry inputs setup-foundry: description: "" diff --git a/actions/cicd-changesets/action.yml b/actions/cicd-changesets/action.yml index a229be06f..dc908ce99 100644 --- a/actions/cicd-changesets/action.yml +++ b/actions/cicd-changesets/action.yml @@ -84,13 +84,17 @@ inputs: default: "" # nodejs inputs (package-manager: pnpm only) node-version-file: - description: "" + description: + "path to a version file for node (mise.toml, .tool-versions, .nvmrc); + empty auto-detects" required: false - default: ".tool-versions" + default: "" pnpm-version: - description: "" + description: + "pnpm version; empty reads it from mise.toml/.tool-versions, falling back + to ^12.0.0" required: false - default: "^12.0.0" + default: "" pnpm-use-cache: description: "Use cache for pnpm" required: false @@ -241,7 +245,7 @@ runs: # It does not read mise.toml, which is where our TypeScript repos pin an # exact bun, so the script resolves that case to a plain version instead. # `bun-version` still wins when set, the same way setup-nodejs lets - # pnpm-version override .tool-versions. + # pnpm-version override the version file. - name: Resolve bun version id: bun-version if: inputs.package-manager == 'bun' diff --git a/actions/setup-nodejs/README.md b/actions/setup-nodejs/README.md index c6a73d899..585a4d4ed 100644 --- a/actions/setup-nodejs/README.md +++ b/actions/setup-nodejs/README.md @@ -2,12 +2,34 @@ Sets up nodejs and pnpm. +## Determining node version + +The action will determine what version to use through the following means: + +1. If `node-version` was explicitly passed, it will use that +2. If not passed, it will check the `node-version-file` input: + - For a mise file (`mise.toml`, `.mise.toml`, or `.config/mise/config.toml`), + it reads the `node` version from the `[tools]` table itself + - For `.tool-versions` or `.nvmrc`, the file is forwarded to `setup-node`, + which parses it natively +3. If `node-version-file` is not passed, it auto-detects: a mise file first, + then `.tool-versions` or `.nvmrc` +4. If no file is found, `setup-node` falls back to its own default + ## Determining pnpm version The action will determine what version to use through the following means: 1. If `pnpm-version` was explicitly passed, it will use that -2. If not passed, it will attempt to pull the version from the `.tool-versions` - file -3. If this file doesn't exist, or doesn't declare `pnpm` then it will default to - `^10.0.0`. +2. If not passed, it will attempt to pull the version from the same mise file as + node (`mise.toml`, `.mise.toml`, or `.config/mise/config.toml`) +3. If not found there, it will pull the version from a `.tool-versions` file +4. If this file doesn't exist, or doesn't declare `pnpm` then it will default to + `^12.0.0` + +## mise + +If a mise config file is present, the action also runs +[`mise install`](https://mise.jdx.dev/) so every tool pinned in `mise.toml` +(e.g. `shellcheck`) is available to subsequent steps — the same toolchain a +developer gets locally. Repos without a mise config skip this entirely. diff --git a/actions/setup-nodejs/action.yml b/actions/setup-nodejs/action.yml index 7db5c0c0b..45b352068 100644 --- a/actions/setup-nodejs/action.yml +++ b/actions/setup-nodejs/action.yml @@ -2,14 +2,27 @@ name: Setup nodejs description: Setup Nodejs and Pnpm with caching inputs: + node-version: + description: + The node version to use. This value takes precedence over the + node-version-file input. Leave it empty to resolve the version from a file + instead. + required: false node-version-file: - description: The node version + description: | + Path to a file the node version is read from when `node-version` is + empty. setup-node reads `.tool-versions`, `.nvmrc`, and `package.json` + natively, so those are forwarded to it unchanged. It does not understand + `mise.toml`, so mise files (`mise.toml`, `.mise.toml`, + `.config/mise/config.toml`) are read by this action and passed to + setup-node as a version instead. Leave it empty (the default) to + auto-detect: a mise file first, then `.tool-versions` or `.nvmrc`, and + setup-node's own default if no file is found. required: false - default: .tool-versions pnpm-version: description: - The pnpm version to use. This value takes precedence over the - pnpm-version-file input. Pseudo-default is ^12.0.0. + The pnpm version to use. This value takes precedence over the version + pinned in mise.toml or .tool-versions. Pseudo-default is ^12.0.0. required: false use-cache: description: Enable github actions cache @@ -40,46 +53,39 @@ inputs: runs: using: composite steps: - - name: Install node - uses: actions/setup-node@v6 - with: - node-version-file: ${{ inputs.node-version-file }} - registry-url: ${{ inputs.registry-url }} - - - name: Get pnpm version - id: pnpm-version + - name: Detect mise config + id: mise-config shell: bash - env: - PNPM_VERSION_INPUT: ${{ inputs.pnpm-version }} - PNPM_FALLBACK_VERSION: "^12.0.0" - DOT_TOOL_VERSIONS: ".tool-versions" run: | - echo "Attempting to determine pnpm version to use..." - - if [[ ! -z "${PNPM_VERSION_INPUT}" ]]; then - echo "Using pnpm version from input: ${PNPM_VERSION_INPUT}" - echo "pnpm-version=${PNPM_VERSION_INPUT}" | tee -a "${GITHUB_OUTPUT}" - exit 0 + if [[ -f mise.toml || -f .mise.toml || -f .config/mise/config.toml ]]; then + echo "found=true" | tee -a "${GITHUB_OUTPUT}" fi - if [[ -f "${DOT_TOOL_VERSIONS}" ]]; then - echo "Found ${DOT_TOOL_VERSIONS} file, checking for pnpm version..." - PNPM_VERSION_FROM_FILE=$(grep '^pnpm ' "${DOT_TOOL_VERSIONS}" | cut -d' ' -f2-) - fi + - name: Install mise tools + if: steps.mise-config.outputs.found == 'true' + uses: jdx/mise-action@c37c93293d6b742fc901e1406b8f764f6fb19dac # v2 - if [[ -z "${PNPM_VERSION_FROM_FILE}" ]]; then - echo "::info:: No pnpm version found in .tool-versions file (or it may not exist). Using default version." - echo "pnpm-version=${PNPM_FALLBACK_VERSION}" | tee -a "${GITHUB_OUTPUT}" - exit 0 - fi + - name: Resolve versions + id: versions + shell: bash + env: + NODE_VERSION: ${{ inputs.node-version }} + NODE_VERSION_FILE: ${{ inputs.node-version-file }} + PNPM_VERSION: ${{ inputs.pnpm-version }} + PNPM_FALLBACK: "^12.0.0" + run: "${GITHUB_ACTION_PATH}/scripts/resolve-versions.sh" - echo "Found pnpm version in file. Using: ${PNPM_VERSION_FROM_FILE}" - echo "pnpm-version=${PNPM_VERSION_FROM_FILE}" | tee -a "${GITHUB_OUTPUT}" + - name: Install node + uses: actions/setup-node@v6 + with: + node-version: ${{ steps.versions.outputs.node-version }} + node-version-file: ${{ steps.versions.outputs.node-version-file }} + registry-url: ${{ inputs.registry-url }} - name: Install pnpm uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 with: - version: ${{ steps.pnpm-version.outputs.pnpm-version }} + version: ${{ steps.versions.outputs.pnpm-version }} run_install: false - name: Get pnpm store directory diff --git a/actions/setup-nodejs/scripts/resolve-versions.sh b/actions/setup-nodejs/scripts/resolve-versions.sh new file mode 100755 index 000000000..87e7b4929 --- /dev/null +++ b/actions/setup-nodejs/scripts/resolve-versions.sh @@ -0,0 +1,157 @@ +#!/usr/bin/env bash +# +# Resolve the node and pnpm versions setup-nodejs should install. +# +# setup-node reads .tool-versions, .nvmrc, and package.json natively via its +# node-version-file input, so those are forwarded to it unchanged. It does not +# understand mise.toml, which is where our repos pin an exact node and pnpm, +# so this script reads mise files itself and emits plain versions. +# +# Inputs (env): +# NODE_VERSION explicit node version; wins over everything, no file is read +# NODE_VERSION_FILE path to a node version file; auto-detect when empty +# PNPM_VERSION explicit pnpm version; wins over every file +# PNPM_FALLBACK pnpm version to use when no pin is found +# +# Outputs (GITHUB_OUTPUT): +# node-version plain version for setup-node's node-version input +# (empty when a file is forwarded) +# node-version-file pass to setup-node's node-version-file input (empty +# when a version is set) +# pnpm-version pnpm version to install, never empty +# +# Exactly one of node-version / node-version-file is non-empty, or both are +# empty to let setup-node fall back to its own default. + +set -euo pipefail + +NODE_VERSION="${NODE_VERSION:-}" +NODE_VERSION_FILE="${NODE_VERSION_FILE:-}" +PNPM_VERSION="${PNPM_VERSION:-}" +PNPM_FALLBACK="${PNPM_FALLBACK:-}" + +# mise config file names, in the order mise itself prefers them. +MISE_PATHS=(mise.toml .mise.toml .config/mise/config.toml) +# Files setup-node reads on its own. package.json is omitted deliberately: +# setup-node already reads it when given no file at all. +FORWARDABLE_PATHS=(.tool-versions .nvmrc) + +# Print the entry of a mise [tools] table for the given tool, in any of the +# forms mise accepts: a bare string, an array of versions, or a table with a +# version key. +mise_tool_line() { + local file="$1" tool="$2" + awk -v tool="$tool" ' + /^[[:space:]]*\[/ { tools = ($0 ~ /^[[:space:]]*\[tools\][[:space:]]*$/); next } + $0 ~ ("^[[:space:]]*(\"" tool "\"|" tool ")[[:space:]]*=") { if (tools) { print; exit } } + ' "$file" +} + +# Reduce a mise tool entry to a bare version: drop the key, a trailing comment, +# the punctuation of all three forms, an inner `version =`, and any extra +# versions of the array form. +mise_tool_version() { + local raw + raw=$(mise_tool_line "$1" "$2") + raw="${raw#*=}" + raw="${raw%%#*}" + raw=$(printf '%s' "${raw}" | tr -d "\"'[]{} ") + raw="${raw##*=}" + printf '%s' "${raw%%,*}" +} + +is_mise_file() { + case "$(basename "$1")" in + mise.toml | .mise.toml | config.toml) return 0 ;; + *) return 1 ;; + esac +} + +first_existing() { + local candidate + for candidate in "$@"; do + if [[ -f "${candidate}" ]]; then + printf '%s' "${candidate}" + return + fi + done +} + +# Pick the mise file to read tools from: an explicitly named one, or +# auto-detect. Both node and pnpm are read from the same file. +mise_file="" +if [[ -n "${NODE_VERSION_FILE}" ]]; then + is_mise_file "${NODE_VERSION_FILE}" && mise_file="${NODE_VERSION_FILE}" +else + mise_file=$(first_existing "${MISE_PATHS[@]}") +fi + +out_node_version="" +out_node_file="" + +if [[ -n "${NODE_VERSION}" ]]; then + out_node_version="${NODE_VERSION}" + echo "Using node version from the node-version input: ${out_node_version}" +else + if [[ -n "${mise_file}" ]]; then + if [[ ! -f "${mise_file}" ]]; then + echo "::error::node-version-file '${mise_file}' does not exist" + exit 1 + fi + out_node_version=$(mise_tool_version "${mise_file}" node) + if [[ -n "${out_node_version}" ]]; then + echo "Using node version from ${mise_file}: ${out_node_version}" + elif [[ -n "${NODE_VERSION_FILE}" ]]; then + # An explicitly named mise file must pin node. An auto-detected one that + # does not is skipped, so the other files still get a chance. + echo "::error::node-version-file '${mise_file}' does not pin a node version" + exit 1 + fi + fi + + # No mise pin: hand a file setup-node understands to setup-node. + if [[ -z "${out_node_version}" ]]; then + if [[ -n "${NODE_VERSION_FILE}" ]]; then + out_node_file="${NODE_VERSION_FILE}" + else + out_node_file=$(first_existing "${FORWARDABLE_PATHS[@]}") + fi + if [[ -n "${out_node_file}" ]]; then + echo "Forwarding ${out_node_file} to setup-node" + else + echo "No node pin found; setup-node will use its default" + fi + fi +fi + +out_pnpm="" + +if [[ -n "${PNPM_VERSION}" ]]; then + out_pnpm="${PNPM_VERSION}" + echo "Using pnpm version from the pnpm-version input: ${out_pnpm}" +else + if [[ -n "${mise_file}" ]]; then + out_pnpm=$(mise_tool_version "${mise_file}" pnpm) + if [[ -n "${out_pnpm}" ]]; then + echo "Using pnpm version from ${mise_file}: ${out_pnpm}" + fi + fi + + if [[ -z "${out_pnpm}" && -f ".tool-versions" ]]; then + out_pnpm=$(grep '^pnpm ' ".tool-versions" | cut -d' ' -f2- || true) + if [[ -n "${out_pnpm}" ]]; then + echo "Using pnpm version from .tool-versions: ${out_pnpm}" + fi + fi + + if [[ -z "${out_pnpm}" ]]; then + echo "No pnpm version pin found. Using default version." + out_pnpm="${PNPM_FALLBACK}" + fi +fi + +{ + echo "node-version=${out_node_version}" + echo "node-version-file=${out_node_file}" + echo "pnpm-version=${out_pnpm}" +} | tee -a "${GITHUB_OUTPUT}" diff --git a/actions/setup-nodejs/scripts/resolve-versions.test.sh b/actions/setup-nodejs/scripts/resolve-versions.test.sh new file mode 100755 index 000000000..8b9ac5cb3 --- /dev/null +++ b/actions/setup-nodejs/scripts/resolve-versions.test.sh @@ -0,0 +1,135 @@ +#!/usr/bin/env bash +# +# Tests for resolve-versions.sh. Run from this directory: +# ./resolve-versions.test.sh + +set -uo pipefail + +SCRIPT="$(cd "$(dirname "$0")" && pwd)/resolve-versions.sh" +passed=0 +failed=0 + +# run_test_case \ +# \ +# [NODE_VERSION] [NODE_VERSION_FILE] [PNPM_VERSION] +# +# The setup commands run inside a scratch repo so each case sees only the files +# it creates. +run_test_case() { + local name="$1" setup="$2" want_node_version="$3" want_node_file="$4" + local want_pnpm="$5" want_exit="$6" + local node_version="${7:-}" node_version_file="${8:-}" pnpm_version="${9:-}" + local dir output rc got_node_version got_node_file got_pnpm github_output + + dir=$(mktemp -d) + github_output=$(mktemp) + (cd "${dir}" && eval "${setup}") >/dev/null + + output=$(cd "${dir}" && env \ + GITHUB_OUTPUT="${github_output}" \ + NODE_VERSION="${node_version}" \ + NODE_VERSION_FILE="${node_version_file}" \ + PNPM_VERSION="${pnpm_version}" \ + PNPM_FALLBACK="^12.0.0" \ + "${SCRIPT}" 2>&1) + rc=$? + + got_node_version=$(grep "^node-version=" "${github_output}" | cut -d= -f2- | tr -d '\n') + got_node_file=$(grep "^node-version-file=" "${github_output}" | cut -d= -f2- | tr -d '\n') + got_pnpm=$(grep "^pnpm-version=" "${github_output}" | cut -d= -f2- | tr -d '\n') + + if [[ "${got_node_version}" == "${want_node_version}" && + "${got_node_file}" == "${want_node_file}" && + "${got_pnpm}" == "${want_pnpm}" && + "${rc}" == "${want_exit}" ]]; then + echo "PASS ${name}" + passed=$((passed + 1)) + else + echo "FAIL ${name}" + echo " expected: node-version='${want_node_version}' node-version-file='${want_node_file}' pnpm-version='${want_pnpm}' exit=${want_exit}" + echo " got: node-version='${got_node_version}' node-version-file='${got_node_file}' pnpm-version='${got_pnpm}' exit=${rc}" + echo " output: ${output}" + failed=$((failed + 1)) + fi + + rm -rf "${dir}" "${github_output}" +} + +# --- mise files: read here, emitted as a version --------------------------- +run_test_case "mise.toml bare strings" \ + 'printf "[tools]\nnode = \"26.10.0\"\npnpm = \"12.9.1\"\n" > mise.toml' \ + "26.10.0" "" "12.9.1" 0 +run_test_case "mise.toml table form" \ + 'printf "[tools]\nnode = { version = \"26.10.0\" }\n" > mise.toml' \ + "26.10.0" "" "^12.0.0" 0 +run_test_case "mise.toml array form" \ + 'printf "[tools]\nnode = [\"26.10.0\", \"26.9.0\"]\n" > mise.toml' \ + "26.10.0" "" "^12.0.0" 0 +run_test_case "mise.toml single quotes and comment" \ + "printf '[tools]\nnode = '\\''26.10.0'\\'' # pinned\n' > mise.toml" \ + "26.10.0" "" "^12.0.0" 0 +run_test_case "mise.toml quoted keys" \ + 'printf "[tools]\n\"node\" = \"26.10.0\"\n\"pnpm\" = \"12.9.1\"\n" > mise.toml' \ + "26.10.0" "" "12.9.1" 0 +run_test_case ".mise.toml" \ + 'printf "[tools]\nnode = \"26.10.0\"\n" > .mise.toml' \ + "26.10.0" "" "^12.0.0" 0 +run_test_case ".config/mise/config.toml" \ + 'mkdir -p .config/mise; printf "[tools]\nnode = \"26.10.0\"\n" > .config/mise/config.toml' \ + "26.10.0" "" "^12.0.0" 0 +run_test_case "mise.toml node outside [tools] ignored" \ + 'printf "[tasks]\nnode = \"echo hi\"\n" > mise.toml; printf "nodejs 26.10.0\n" > .tool-versions' \ + "" ".tool-versions" "^12.0.0" 0 +run_test_case "mise.toml pnpm outside [tools] ignored" \ + 'printf "[tasks]\npnpm = \"echo hi\"\n" > mise.toml' \ + "" "" "^12.0.0" 0 +run_test_case "node from mise, pnpm from .tool-versions" \ + 'printf "[tools]\nnode = \"26.10.0\"\n" > mise.toml; printf "pnpm 12.9.1\n" > .tool-versions' \ + "26.10.0" "" "12.9.1" 0 + +# --- files setup-node reads: forwarded ------------------------------------ +run_test_case "auto-detect .tool-versions" \ + 'printf "nodejs 26.10.0\npnpm 12.9.1\n" > .tool-versions' \ + "" ".tool-versions" "12.9.1" 0 +run_test_case "auto-detect .nvmrc" \ + 'printf "26.10.0\n" > .nvmrc' \ + "" ".nvmrc" "^12.0.0" 0 +run_test_case "mise preferred over .tool-versions" \ + 'printf "[tools]\nnode = \"26.10.0\"\npnpm = \"12.9.1\"\n" > mise.toml; printf "nodejs 22\npnpm 10\n" > .tool-versions' \ + "26.10.0" "" "12.9.1" 0 +run_test_case "package.json left to setup-node" \ + 'printf "{\"engines\":{\"node\":\">=26\"}}\n" > package.json' \ + "" "" "^12.0.0" 0 +run_test_case "no pin at all" \ + 'printf "hi\n" > README.md' \ + "" "" "^12.0.0" 0 +run_test_case "tool-versions without pnpm falls back" \ + 'printf "nodejs 26.10.0\n" > .tool-versions' \ + "" ".tool-versions" "^12.0.0" 0 + +# --- explicit inputs and explicit files ----------------------------------- +run_test_case "node-version input wins" \ + 'printf "[tools]\nnode = \"26.10.0\"\n" > mise.toml' \ + "24.0.0" "" "^12.0.0" 0 "24.0.0" +run_test_case "pnpm-version input wins" \ + 'printf "[tools]\npnpm = \"12.9.1\"\n" > mise.toml' \ + "" "" "10.0.0" 0 "" "" "10.0.0" +run_test_case "explicit .tool-versions" \ + 'printf "nodejs 26.10.0\npnpm 12.9.1\n" > .tool-versions' \ + "" ".tool-versions" "12.9.1" 0 "" ".tool-versions" +run_test_case "explicit .nvmrc" \ + 'printf "26.10.0\n" > .nvmrc' \ + "" ".nvmrc" "^12.0.0" 0 "" ".nvmrc" +run_test_case "explicit mise file in subdir" \ + 'mkdir -p sub; printf "[tools]\nnode = \"26.10.0\"\npnpm = \"12.9.1\"\n" > sub/mise.toml' \ + "26.10.0" "" "12.9.1" 0 "" "sub/mise.toml" +run_test_case "explicit mise file without node fails" \ + 'printf "[tools]\npnpm = \"12.9.1\"\n" > mise.toml' \ + "" "" "" 1 "" "mise.toml" +run_test_case "explicit mise file missing fails" \ + 'true' \ + "" "" "" 1 "" "sub/mise.toml" + +echo +echo "passed: ${passed}, failed: ${failed}" +[[ "${failed}" -eq 0 ]] diff --git a/actions/signed-commits/README.md b/actions/signed-commits/README.md index 6c15da711..297096347 100644 --- a/actions/signed-commits/README.md +++ b/actions/signed-commits/README.md @@ -77,8 +77,7 @@ jobs: - name: Setup Node.js 20 uses: smartcontractkit/.github/actions/setup-nodejs@main with: - pnpm-version: "^10.0.0" - node-version-file: .tool-versions + node-version-file: mise.toml run-install: true use-cache: true @@ -121,8 +120,7 @@ jobs: - name: Setup Node.js 20 uses: smartcontractkit/.github/actions/setup-nodejs@ with: - pnpm-version: "^10.0.0" - node-version-file: .tool-versions + node-version-file: mise.toml run-install: true use-cache: true @@ -188,8 +186,7 @@ jobs: - name: Setup Node.js 20 uses: smartcontractkit/.github/actions/setup-nodejs@ with: - pnpm-version: "^10.0.0" - node-version-file: .tool-versions + node-version-file: mise.toml run-install: true use-cache: true @@ -236,8 +233,7 @@ jobs: - name: Setup Node.js 20 uses: smartcontractkit/.github/actions/setup-nodejs@main with: - pnpm-version: "^10.0.0" - node-version-file: .tool-versions + node-version-file: mise.toml run-install: true use-cache: true @@ -320,8 +316,7 @@ jobs: - name: Setup Node.js 20 uses: smartcontractkit/.github/actions/setup-nodejs@main with: - pnpm-version: "^10.0.0" - node-version-file: .tool-versions + node-version-file: mise.toml run-install: true use-cache: true diff --git a/apps/actions-dependencies-updater/README.md b/apps/actions-dependencies-updater/README.md index 08d5621e1..bf455167f 100644 --- a/apps/actions-dependencies-updater/README.md +++ b/apps/actions-dependencies-updater/README.md @@ -11,12 +11,12 @@ This tool has two main purposes: 1. Install and setup tooling - - [`pnpm`](https://pnpm.io/) - - [`asdf`](https://asdf-vm.com/), then - [`nodejs`](https://github.com/asdf-vm/asdf-nodejs). + - [`mise`](https://mise.jdx.dev/) - node and pnpm versions are pinned in the + repo root `mise.toml` 2. Install Dependencies ```bash + mise install pnpm install ``` 3. Generate a Github Personal Access Token with repo access diff --git a/mise.toml b/mise.toml new file mode 100644 index 000000000..fceb817f6 --- /dev/null +++ b/mise.toml @@ -0,0 +1,28 @@ +[tools] +node = "26.10.0" +pnpm = "12.9.1" +shellcheck = "0.11.0" + +[tasks.install] +description = "Install tools, npm dependencies, and git hooks" +run = ["mise install", "pnpm install", "pnpm lefthook install"] + +[tasks.lint] +description = "Lint all projects" +run = "pnpm run lint" + +[tasks.build] +description = "Build all projects" +run = "pnpm run build" + +[tasks.test] +description = "Test all projects" +run = "pnpm run test" + +[tasks.format] +description = "Format all files with prettier" +run = "pnpm run prettier:write" + +[tasks."workflows:sync"] +description = "Sync reusable workflows into .github/workflows" +run = "pnpm run workflows:fix" diff --git a/workflows/reusable-docusaurus/reusable-docusaurus.yml b/workflows/reusable-docusaurus/reusable-docusaurus.yml index b2e6ba154..be6fcca04 100644 --- a/workflows/reusable-docusaurus/reusable-docusaurus.yml +++ b/workflows/reusable-docusaurus/reusable-docusaurus.yml @@ -27,15 +27,19 @@ on: inputs: # nodejs inputs node-version-file: - description: "path to .tool-versions file for node version" + description: + "path to a version file for node (mise.toml, .tool-versions, + .nvmrc); empty auto-detects" required: false type: string - default: ".tool-versions" + default: "" pnpm-version: - description: "the pnpm version to use" + description: + "pnpm version; empty reads it from mise.toml/.tool-versions, falling + back to ^12.0.0" required: false type: string - default: "^12.0.0" + default: "" # docs-specific inputs build-command: description: "command to build the documentation (run from repository root, use pnpm -C to specify directory)" From 39cab7e42e0b01ae4eba011e4ee967362a942ab2 Mon Sep 17 00:00:00 2001 From: Adam Hamrick Date: Fri, 9 Oct 2026 11:59:56 -0400 Subject: [PATCH 2/2] fix: version hardening --- .changeset/migrate-asdf-to-mise.md | 14 +++++++++----- actions/setup-nodejs/README.md | 18 ++++++++++++++---- actions/setup-nodejs/action.yml | 11 ++++++++++- .../scripts/resolve-versions.test.sh | 3 +++ 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/.changeset/migrate-asdf-to-mise.md b/.changeset/migrate-asdf-to-mise.md index 9bf7b77bd..5ab361e2b 100644 --- a/.changeset/migrate-asdf-to-mise.md +++ b/.changeset/migrate-asdf-to-mise.md @@ -16,11 +16,15 @@ Migrate version management from asdf/`.tool-versions` to - `setup-nodejs` reads node and pnpm versions from a mise file (`mise.toml`, `.mise.toml`, `.config/mise/config.toml`) first, falling back to - `.tool-versions`/`.nvmrc`, so downstream repos can migrate independently. A - new `node-version` input takes precedence over `node-version-file`, which - now defaults to auto-detect instead of `.tool-versions`. When a mise config - is present, `mise install` also provisions every pinned tool (e.g. - `shellcheck`) for later steps. + `.tool-versions`/`.nvmrc`, so downstream repos can migrate independently. + Consumer repos are not required to use either: repos with no version file + now get setup-node's default node and pnpm `^12.0.0` instead of failing (the + old action always passed `.tool-versions` to setup-node, which errors when + the file is missing). A new `node-version` input takes precedence over + `node-version-file`, which now defaults to auto-detect instead of + `.tool-versions`. When a mise config is present, `mise install` also + provisions every pinned tool (e.g. `shellcheck`) for later steps; opt out + with the new `mise-install` input. - `ci-lint-ts`, `ci-test-ts`, `ci-test-sol`, `ci-prettier`, `cicd-build-publish-artifacts-ts`, `cicd-changesets`, and `reusable-docusaurus` default `node-version-file` to auto-detect and diff --git a/actions/setup-nodejs/README.md b/actions/setup-nodejs/README.md index 585a4d4ed..14c2ceb3e 100644 --- a/actions/setup-nodejs/README.md +++ b/actions/setup-nodejs/README.md @@ -2,6 +2,10 @@ Sets up nodejs and pnpm. +Version resolution works whether the repo pins its toolchain with +[mise](https://mise.jdx.dev/), asdf, or nothing at all. No version file is +required. + ## Determining node version The action will determine what version to use through the following means: @@ -10,11 +14,15 @@ The action will determine what version to use through the following means: 2. If not passed, it will check the `node-version-file` input: - For a mise file (`mise.toml`, `.mise.toml`, or `.config/mise/config.toml`), it reads the `node` version from the `[tools]` table itself - - For `.tool-versions` or `.nvmrc`, the file is forwarded to `setup-node`, - which parses it natively + - For `.tool-versions`, `.nvmrc`, or `package.json`, the file is forwarded to + `setup-node`, which parses it natively 3. If `node-version-file` is not passed, it auto-detects: a mise file first, then `.tool-versions` or `.nvmrc` -4. If no file is found, `setup-node` falls back to its own default +4. If no file is found, `setup-node` falls back to its own default (the runner's + preinstalled node) + +Repos with both a mise file and a `.tool-versions`/`.nvmrc` resolve from the +mise file. ## Determining pnpm version @@ -32,4 +40,6 @@ The action will determine what version to use through the following means: If a mise config file is present, the action also runs [`mise install`](https://mise.jdx.dev/) so every tool pinned in `mise.toml` (e.g. `shellcheck`) is available to subsequent steps — the same toolchain a -developer gets locally. Repos without a mise config skip this entirely. +developer gets locally. Repos without a mise config skip this entirely. Set the +`mise-install` input to `false` to skip provisioning; version resolution is +unaffected by that input. diff --git a/actions/setup-nodejs/action.yml b/actions/setup-nodejs/action.yml index 45b352068..fdc1f9d9e 100644 --- a/actions/setup-nodejs/action.yml +++ b/actions/setup-nodejs/action.yml @@ -24,6 +24,13 @@ inputs: The pnpm version to use. This value takes precedence over the version pinned in mise.toml or .tool-versions. Pseudo-default is ^12.0.0. required: false + mise-install: + description: + "Run `mise install` to provision every tool pinned in the repo's mise.toml + (e.g. node, pnpm, shellcheck) for later steps. Set to false to skip + provisioning. Version resolution is unaffected by this input." + required: true + default: "true" use-cache: description: Enable github actions cache required: true @@ -62,7 +69,9 @@ runs: fi - name: Install mise tools - if: steps.mise-config.outputs.found == 'true' + if: + steps.mise-config.outputs.found == 'true' && inputs.mise-install == + 'true' uses: jdx/mise-action@c37c93293d6b742fc901e1406b8f764f6fb19dac # v2 - name: Resolve versions diff --git a/actions/setup-nodejs/scripts/resolve-versions.test.sh b/actions/setup-nodejs/scripts/resolve-versions.test.sh index 8b9ac5cb3..4da46e772 100755 --- a/actions/setup-nodejs/scripts/resolve-versions.test.sh +++ b/actions/setup-nodejs/scripts/resolve-versions.test.sh @@ -83,6 +83,9 @@ run_test_case "mise.toml node outside [tools] ignored" \ run_test_case "mise.toml pnpm outside [tools] ignored" \ 'printf "[tasks]\npnpm = \"echo hi\"\n" > mise.toml' \ "" "" "^12.0.0" 0 +run_test_case "env-only mise.toml yields all defaults" \ + 'printf "[env]\nFOO = \"bar\"\n" > mise.toml' \ + "" "" "^12.0.0" 0 run_test_case "node from mise, pnpm from .tool-versions" \ 'printf "[tools]\nnode = \"26.10.0\"\n" > mise.toml; printf "pnpm 12.9.1\n" > .tool-versions' \ "26.10.0" "" "12.9.1" 0