From 29f994279f339ee99fab02c594a37077cb98272c Mon Sep 17 00:00:00 2001 From: nlf Date: Thu, 12 Jan 2023 11:55:06 -0800 Subject: [PATCH] chore: add node release integration workflows (#6049) --- .github/workflows/node-integration.yml | 456 ++++++++++++++++++++++ .github/workflows/release-integration.yml | 45 +++ 2 files changed, 501 insertions(+) create mode 100644 .github/workflows/node-integration.yml create mode 100644 .github/workflows/release-integration.yml diff --git a/.github/workflows/node-integration.yml b/.github/workflows/node-integration.yml new file mode 100644 index 0000000000000..c90a5e681dc37 --- /dev/null +++ b/.github/workflows/node-integration.yml @@ -0,0 +1,456 @@ +name: nodejs integration + +on: + workflow_call: + inputs: + nodeVersion: + description: 'nodejs version' + required: true + type: string + default: nightly + npmVersion: + description: 'npm version' + required: true + type: string + default: git + + workflow_dispatch: + inputs: + nodeVersion: + description: 'nodejs version' + required: true + type: string + default: nightly + npmVersion: + description: 'npm version' + required: true + type: string + default: git + +# used by setup-ccache-action's post hook to cleanup old caches +permissions: + actions: write + +jobs: + build-nodejs: + name: build nodejs@${{ inputs.nodeVersion }} npm@${{ inputs.npmVersion }} + runs-on: ubuntu-latest + outputs: + nodeVersion: ${{ steps.build-nodejs.outputs.nodeVersion }} + steps: + - name: setup ccache + uses: Chocobo1/setup-ccache-action@v1 + with: + override_cache_key: nodejs-${{ inputs.nodeVersion }} + - name: build nodejs + id: build-nodejs + run: | + echo "::group::setup" + set -eo pipefail + npmDir="${RUNNER_TEMP}/npm" + sourceDir="${RUNNER_TEMP}/src" + targetDir="${RUNNER_TEMP}/build" + npmFile="${RUNNER_TEMP}/npm.tgz" + sourceFile="${RUNNER_TEMP}/source.tgz" + targetFile="${RUNNER_TEMP}/build.tgz" + echo "::endgroup::" + + echo "::group::finding nodejs version matching ${{ inputs.nodeVersion }}" + if [[ "${{ inputs.nodeVersion }}" == "nightly" ]]; then + nodeVersion=$(curl -sSL https://nodejs.org/download/nightly/index.json | jq -r .[0].version) + nodeUrl="https://nodejs.org/download/nightly/${nodeVersion}/node-${nodeVersion}.tar.gz" + else + nodeVersion=$(curl -sSL https://nodejs.org/download/release/index.json | jq -r 'map(select(.version | test("^v${{ inputs.nodeVersion }}"))) | .[0].version') + if [[ -z "$nodeVersion" ]]; then + echo "::error ::unable to find released nodejs version matching: ${{ inputs.nodeVersion }}" + exit 1 + fi + nodeUrl="https://nodejs.org/download/release/${nodeVersion}/node-${nodeVersion}.tar.gz" + fi + echo "nodeVersion=${nodeVersion}" >> $GITHUB_OUTPUT + echo "::endgroup::" + + echo "::group::extracting source from $nodeUrl" + mkdir -p "$sourceDir" + curl -sSL "$nodeUrl" | tar xz -C "$sourceDir" --strip=1 + echo "::endgroup::" + + echo "::group::cloning npm" + mkdir -p "$npmDir" + git clone https://github.com/npm/cli.git "$npmDir" + npmVersion=$(cat "${npmDir}/package.json" | jq -r '"\(.version)-git"') + echo "::endgroup::" + + if [[ "${{ inputs.npmVersion }}" != "git" ]]; then + echo "::group::checking out npm@${{ inputs.npmVersion }}" + pushd "$npmDir" >/dev/null + npmVersion=$(curl -sSL https://registry.npmjs.org/npm |\ + jq -r '.time | to_entries | map(select(.key | test("^${{ inputs.npmVersion }}"))) | sort_by(.value | split(".") | "\(.[0])Z" | fromdate) | .[-1].key') + npmGitHead=$(git show-ref --tags "v${npmVersion}" | cut -d' ' -f1) + git reset --hard "$npmGitHead" + popd >/dev/null + echo "::endgroup::" + fi + + echo "::group::packing npm release $npmVersion" + pushd "$npmDir" >/dev/null + node . run resetdeps + npmtarball="$(node . pack --loglevel=silent --json | jq -r .[0].filename)" + tar czf "$npmFile" -C "$npmDir" . + popd >/dev/null + echo "npm=$npmFile" >> $GITHUB_OUTPUT + echo "::endgroup::" + + echo "::group::updating nodejs bundled npm" + rm -rf "${sourceDir}/deps/npm" + mkdir -p "${sourceDir}/deps/npm" + tar xfz "${npmDir}/${npmtarball}" -C "${sourceDir}/deps/npm" --strip=1 + echo "::endgroup::" + + echo "::group::packing nodejs source" + tar cfz "$sourceFile" -C "$sourceDir" . + echo "source=$sourceFile" >> $GITHUB_OUTPUT + echo "::endgroup::" + + echo "::group::building nodejs" + mkdir -p "$targetDir" + pushd "$sourceDir" >/dev/null + ./configure --prefix="$targetDir" + make -j4 install + popd >/dev/null + echo "::endgroup::" + + echo "::group::packing nodejs build" + tar cfz "$targetFile" -C "$targetDir" . + echo "build=$targetFile" >> $GITHUB_OUTPUT + echo "::endgroup::" + - name: upload artifacts + uses: actions/upload-artifact@v3 + with: + name: nodejs-${{ steps.build-nodejs.outputs.nodeVersion }} + path: | + ${{ steps.build-nodejs.outputs.source }} + ${{ steps.build-nodejs.outputs.build }} + ${{ steps.build-nodejs.outputs.npm }} + + test-nodejs: + name: test nodejs + runs-on: ubuntu-latest + needs: + - build-nodejs + steps: + - name: setup ccache + uses: Chocobo1/setup-ccache-action@v1 + with: + override_cache_key: nodejs-${{ inputs.nodeVersion }} + - name: download artifacts + uses: actions/download-artifact@v3 + with: + name: nodejs-${{ needs.build-nodejs.outputs.nodeVersion }} + - name: test nodejs + run: | + set -e + tar xf source.tgz + ./configure + make -j4 test-only + + test-npm: + name: test npm + runs-on: ubuntu-latest + needs: + - build-nodejs + steps: + - name: download artifacts + uses: actions/download-artifact@v3 + with: + name: nodejs-${{ needs.build-nodejs.outputs.nodeVersion }} + path: ${{ runner.temp }} + - name: install nodejs ${{ needs.build-nodejs.outputs.nodeVersion }} + id: install + run: | + set -e + mkdir -p "${RUNNER_TEMP}/node" + tar xf "${RUNNER_TEMP}/build.tgz" -C "${RUNNER_TEMP}/node" + + mkdir -p "${RUNNER_TEMP}/npm" + tar xf "${RUNNER_TEMP}/npm.tgz" -C "${RUNNER_TEMP}/npm" + + echo "${RUNNER_TEMP}/node/bin" >> $GITHUB_PATH + echo "cache=$(npm config get cache)" >> $GITHUB_OUTPUT + - name: setup npm cache + uses: actions/cache@v3 + with: + path: ${{ steps.install.outputs.cache }} + key: npm-tests + - run: node --version + - run: npm --version + - name: test npm + run: | + echo "::group::setup" + set -e + cd "${RUNNER_TEMP}/npm" + echo "::endgroup::" + + echo "::group::npm run resetdeps" + node . run resetdeps + echo "::endgroup::" + + echo "::group::npm link" + node . link + echo "::endgroup::" + + STEPEXIT=0 + FINALEXIT=0 + + set +e + echo "::group::npm test" + node . test --ignore-scripts + STEPEXIT=$? + if [[ $STEPEXIT -ne 0 ]]; then + echo "::warning ::npm test failed, exit: $STEPEXIT" + FINALEXIT=STEPEXIT + fi + echo "::endgroup::" + + for workspace in $(npm query .workspace | jq -r .[].name); do + echo "::group::npm test -w $workspace" + node . test -w $workspace --if-present --ignore-scripts + STEPEXIT=$? + if [[ $STEPEXIT -ne 0 ]]; then + echo "::warning ::npm test -w $workspace failed, exit: $STEPEXIT" + FINALEXIT=STEPEXIT + fi + echo "::endgroup::" + done + + exit $FINALEXIT + + generate-matrix: + name: generate matrix + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.generate-matrix.outputs.matrix }} + steps: + - name: install dependencies + run: | + npm install --no-save --no-audit --no-fund citgm npm-package-arg + - name: generate matrix + id: generate-matrix + uses: actions/github-script@v6 + with: + script: | + const { execSync } = require('child_process') + const npa = require('npm-package-arg') + + const lookup = require('./node_modules/citgm/lib/lookup.json') + + const matchesKeyword = (value) => { + const keywords = ['ubuntu', 'debian', 'linux', 'x86', '>=11', '>=12', '>=16', '>=17'] + if (value === true || + (typeof value === 'string' && keywords.includes(value)) || + (Array.isArray(value) && keywords.some((keyword) => value.includes(keyword) || value.includes(true)))) { + return true + } + + return false + } + + // this is a manually updated list of packages that we know currently fail + const knownFailures = [ + 'body-parser', // json parsing error difference + 'clinic', // unknown, lots of failures + 'ember-cli', // timeout in nodejs ci, one failing test for us that timed out + 'express', // body-parser is what actually fails, it's used in a test + 'https-proxy-agent', // looks ssl related + 'node-gyp', // one test consistently times out + 'resolve', // compares results to require.resolve and fails, also missing inspector/promises + 'serialport', // esbuild barfs on node 20.0.0-pre + 'undici', // test failure in node >=19, unable to root cause + 'uuid', // tests that crypto.getRandomValues throws but it doesn't + 'weak', // doesn't seem to build in node >12 + ] + + // this is a manually updated list of packages that are flaky + const supplementalFlaky = [ + 'pino', // flaky test test/transport/core.test.js:401 + 'tough-cookie', // race condition test/node_util_fallback_test.js:87 + ] + + const matrix = [] + for (const package in lookup) { + const meta = lookup[package] + + // we omit npm from the matrix because its tests are run as their own job + if (matchesKeyword(meta.skip) || meta.yarn === true || package === 'npm') { + continue + } + + const install_flags = ['--no-audit', '--no-fund'] + if (meta.install) { + install_flags.push(meta.install.slice(1)) + } + const context = JSON.parse(execSync(`npm show ${package} --json`)) + const test = meta.scripts ? meta.scripts.map((script) => `npm run ${script}`) : ['npm test'] + + const repo = npa(meta.repo || context.repository.url).hosted + const details = {} + if (meta.useGitClone) { + details.repo = repo.https() + } else { + if (meta.ignoreGitHead) { + details.url = repo.tarball() + } else { + details.url = repo.tarball({ committish: context.gitHead }) + } + } + + const env = { ...meta.envVar } + matrix.push({ + package, + version: context.version, + env, + install_flags: install_flags.join(' '), + commands: [...test], + flaky: matchesKeyword(meta.flaky) || supplementalFlaky.includes(package), + knownFailure: knownFailures.includes(package), + ...details, + }) + } + core.setOutput('matrix', matrix) + + citgm: + name: citgm - ${{ matrix.package }}@${{ matrix.version }} ${{ matrix.flaky && '(flaky)' || '' }} ${{ matrix.knownFailure && '(known failure)' || '' }} + runs-on: ubuntu-latest + needs: + - build-nodejs + - generate-matrix + strategy: + fail-fast: false + matrix: + include: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} + steps: + - name: download artifacts + uses: actions/download-artifact@v3 + with: + name: nodejs-${{ needs.build-nodejs.outputs.nodeVersion }} + path: ${{ runner.temp }} + - name: install nodejs ${{ needs.build-nodejs.outputs.nodeVersion }} + id: install + run: | + set -e + mkdir -p "${RUNNER_TEMP}/node" + tar xf "${RUNNER_TEMP}/build.tgz" -C "${RUNNER_TEMP}/node" + echo "nodedir=${RUNNER_TEMP}/node" >> $GITHUB_OUTPUT + + echo "${RUNNER_TEMP}/node/bin" >> $GITHUB_PATH + echo "cache=$(npm config get cache)" >> $GITHUB_OUTPUT + - name: setup npm cache + uses: actions/cache@v3 + with: + path: ${{ steps.install.outputs.cache }} + key: npm-${{ matrix.package }} + - run: node --version + - run: npm --version + - name: download source + id: download + run: | + set -eo pipefail + TARGET_DIR="${RUNNER_TEMP}/${{ matrix.package }}" + mkdir -p "$TARGET_DIR" + echo "target=$TARGET_DIR" >> $GITHUB_OUTPUT + + if [[ -n "${{ matrix.repo }}" ]]; then + export GIT_TERMINAL_PROMPT=0 + export GIT_SSH_COMMAND="ssh -oBatchMode=yes" + git clone --recurse-submodules "${{ matrix.repo }}" "$TARGET_DIR" + elif [[ -n "${{ matrix.url }}" ]]; then + curl -sSL "${{ matrix.url }}" | tar xz -C "$TARGET_DIR" --strip=1 + fi + + if [[ -f "${TARGET_DIR}/package-lock.json" ]]; then + lockfileVersion=$(cat "${TARGET_DIR}/package-lock.json" | jq .lockfileVersion) + echo "lockfileVersion=$lockfileVersion" >> $GITHUB_OUTPUT + fi + - name: npm install ${{ matrix.install_flags }} + id: npm-install + working-directory: ${{ steps.download.outputs.target }} + run: | + set +e + npm install --nodedir="${{steps.install.outputs.nodedir }}" ${{ matrix.install_flags }} + exitcode=$? + if [[ $exitcode -ne 0 && "${{ matrix.knownFailure }}" == "true" ]]; then + echo "::warning ::npm install failed, exit $exitcode" + echo "failed=true" >> $GITHUB_OUTPUT + exit 0 + fi + exit $exitcode + - name: verify lockfileVersion unchanged + working-directory: ${{ steps.download.outputs.target }} + if: ${{ steps.download.outputs.lockfileVersion && steps.npm-install.outputs.failed != 'true' }} + run: | + if [[ -f "package-lock.json" ]]; then + newLockfileVersion=$(cat "package-lock.json" | jq .lockfileVersion) + if [[ "$newLockfileVersion" -ne "${{ steps.download.outputs.lockfileVersion }}" ]]; then + logMessage="lockfileVersion changed from ${{ steps.download.outputs.lockfileVersion }} to $newLockfileVersion" + if [[ "${{ steps.download.outputs.lockfileVersion }}" -eq 1 ]]; then + echo "::warning ::$logMessage" + else + echo "::error ::$logMessage" + exit 1 + fi + fi + fi + - name: npm ls + working-directory: ${{ steps.download.outputs.target }} + if: ${{ steps.npm-install.outputs.failed != 'true' }} + run: | + npm ls + - name: ${{ join(matrix.commands, ' && ') }} + env: ${{ matrix.env }} + working-directory: ${{ steps.download.outputs.target }} + if: ${{ steps.npm-install.outputs.failed != 'true' }} + run: | + set +e + FINALEXIT=0 + STEPEXIT=0 + + export npm_config_nodedir="${{ steps.install.outputs.nodedir }}" + + # inlining some patches to make tests run + if [[ "${{ matrix.package }}" == "undici" ]]; then + sed -i.bak 's/--experimental-wasm-simd //' package.json + rm -f package.json.bak + sed -i.bak 's/--experimental-wasm-simd//' .taprc + rm -f .taprc.bak + fi + + commandCount=$(echo '${{ toJSON(matrix.commands) }}' | jq -r 'length') + if [[ $commandCount -eq 1 ]]; then + COMMAND=$(echo '${{ toJSON(matrix.commands) }}' | jq -r '.[0]') + $COMMAND + STEPEXIT=$? + if [[ $STEPEXIT -ne 0 ]]; then + if [[ "${{ matrix.flaky }}" == "true" || "${{ matrix.knownFailure }}" == "true" ]]; then + echo "::warning ::$COMMAND failed, exit $STEPEXIT" + exit 0 + fi + fi + exit $STEPEXIT + fi + + for row in $(echo '${{ toJSON(matrix.commands) }}' | jq -r '.[] | @base64'); do + COMMAND=$(echo "$row" | base64 --decode) + echo "::group::$COMMAND" + $COMMAND + STEPEXIT=$? + if [[ $STEPEXIT -ne 0 ]]; then + if [[ "${{ matrix.flaky }}" == "true" || "${{ matrix.knownFailure }}" == "true" ]]; then + echo "::warning ::$COMMAND failed, exit: $STEPEXIT" + else + FINALEXIT=$STEPEXIT + echo "::error ::$COMMAND failed, exit: $STEPEXIT" + fi + fi + echo "::endgroup::" + done + exit $FINALEXIT diff --git a/.github/workflows/release-integration.yml b/.github/workflows/release-integration.yml new file mode 100644 index 0000000000000..07c68a50a2dc4 --- /dev/null +++ b/.github/workflows/release-integration.yml @@ -0,0 +1,45 @@ +name: release integration + +on: + release: + types: + - published + workflow_dispatch: + inputs: + npmVersion: + description: npm version to test + type: string + required: true + +jobs: + npm-version: + env: + npmVersion: ${{ inputs.npmVersion || github.ref_name }} + name: determine npm version + if: ${{ inputs.npmVersion || startsWith(github.ref_name, 'v') }} + runs-on: ubuntu-latest + outputs: + npmVersion: ${{ steps.version.outputs.npmVersion }} + steps: + - name: clean npm version + id: version + run: | + npmVersion="${{ env.npmVersion }}" + npmVersion="${npmVersion/#v}" + echo "npmVersion=${npmVersion}" >> $GITHUB_OUTPUT + node-integration: + name: nodejs@${{ matrix.nodeVersion }} integration + strategy: + fail-fast: false + matrix: + nodeVersion: + - 18 + - 19 + - nightly + needs: + - npm-version + uses: ./.github/workflows/node-integration.yml + with: + nodeVersion: ${{ matrix.nodeVersion }} + npmVersion: ${{ needs.npm-version.outputs.npmVersion }} +