diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 63acb7fc21..0000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,458 +0,0 @@ -version: 2.1 - -defaults: &defaults - working_directory: ~/axe-core - -unix_box: &unix_box - docker: - - image: cimg/node:22.16-browsers - -unix_nightly_box: &unix_nightly_box - docker: - - image: cimg/node:lts-browsers - -orbs: - puppeteer: threetreeslight/puppeteer@0.1.2 - browser-tools: circleci/browser-tools@1.5.1 - -set_npm_auth: &set_npm_auth - run: npm config set "//registry.npmjs.org/:_authToken" $NPM_AUTH - -restore_dependency_cache_unix: &restore_dependency_cache_unix - restore_cache: - name: Restore NPM Cache - keys: - - v9-cache-unix-{{ checksum "package-lock.json" }} - -restore_build: &restore_build - restore_cache: - name: Restore Axe.js Cache - keys: - - v9-cache-build-<< pipeline.git.revision >> - -commands: - browser-tools-job: - steps: - - browser-tools/install-browser-tools - -jobs: - # Fetch and cache dependencies. - dependencies_unix: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - run: - name: Skip Install If Cache Exists - command: | - if [ -d "node_modules" ]; then - echo "node_modules exist" - circleci step halt - else - echo "node_modules does not exist" - fi - - browser-tools-job - - <<: *set_npm_auth - - run: npm ci - - run: npx browser-driver-manager install chromedriver --verbose - - save_cache: - key: v9-cache-unix-{{ checksum "package-lock.json" }} - paths: - - node_modules - - # Build and cache built files - build_unix: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - run: npm run prepare - - run: npm run build - - save_cache: - key: v9-cache-build-<< pipeline.git.revision >> - paths: - - axe.js - - axe.min.js - - # Run ESLINT - lint: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - run: npm run eslint - - # Run the test suite. - test_chrome: - <<: *defaults - <<: *unix_box - steps: - - checkout - - browser-tools-job - - <<: *restore_dependency_cache_unix - - run: npx browser-driver-manager install chromedriver --verbose - - <<: *restore_build - - run: npm run test -- --browsers Chrome - - run: npm run test:integration:chrome - - test_firefox: - <<: *defaults - <<: *unix_box - steps: - - checkout - - browser-tools-job - - <<: *restore_dependency_cache_unix - - <<: *restore_build - - run: npm run test -- --browsers Firefox - - run: npm run test:integration:firefox - - # Run examples under `doc/examples` - test_examples: - <<: *defaults - <<: *unix_box - steps: - - checkout - - browser-tools-job - - <<: *restore_dependency_cache_unix - - run: npx browser-driver-manager install chromedriver --verbose - - <<: *restore_build - - run: npm run test:examples - - # Run ACT test cases - test_act: - <<: *defaults - <<: *unix_box - steps: - - checkout - - browser-tools-job - - <<: *restore_dependency_cache_unix - - run: npx browser-driver-manager install chromedriver --verbose - - <<: *restore_build - - run: npm run test:act - - # Run ARIA practices test cases - test_aria_practices: - <<: *defaults - <<: *unix_box - steps: - - checkout - - browser-tools-job - - <<: *restore_dependency_cache_unix - - run: npx browser-driver-manager install chromedriver --verbose - - <<: *restore_build - - run: npm run test:apg - - # Test locale files - test_locales: - <<: *defaults - <<: *unix_box - steps: - - checkout - - browser-tools-job - - <<: *restore_dependency_cache_unix - - run: npx browser-driver-manager install chromedriver --verbose - - <<: *restore_build - - run: npm run test:locales - - # Test virtual rules - test_virtual_rules: - <<: *defaults - <<: *unix_box - steps: - - checkout - - browser-tools-job - - <<: *restore_dependency_cache_unix - - run: npx browser-driver-manager install chromedriver --verbose - - <<: *restore_build - - run: npm run test:virtual-rules - - # Run the test suite for nightly builds. - test_nightly_browsers: - <<: *defaults - <<: *unix_nightly_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - run: sudo apt-get update -y - - <<: *restore_build - - run: - name: Install Chrome and ChromeDriver Beta - command: npx browser-driver-manager install chrome=beta chromedriver=beta --verbose - - run: - name: Install Firefox Nightly - command: | - # Assumes Firefox >= 135; in earlier versions, this resolves to a .tar.bz2 instead - wget -O firefox-nightly.tar.xz "https://download.mozilla.org/?product=firefox-nightly-latest-ssl&os=linux64&lang=en-US" - tar xf firefox-nightly.tar.xz - - run: - name: Set Environment Variable - command: echo "export FIREFOX_NIGHTLY_BIN=$(pwd)/firefox/firefox-bin" >> $BASH_ENV - - run: npm run test -- --browsers Chrome,FirefoxNightly - - # Run the test suite for nightly builds. - test_nightly_act: - <<: *defaults - <<: *unix_nightly_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - browser-tools-job - # install ACT rules - # install first as for some reason installing a single package - # also re-installs all repo dependencies as well - - run: npm install w3c/wcag-act-rules#main - - run: npx browser-driver-manager install chromedriver --verbose - - <<: *restore_build - - run: npm run test:act - - # Run the test suite for nightly builds. - test_nightly_aria_practices: - <<: *defaults - <<: *unix_nightly_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - browser-tools-job - # install ARIA practices - # install first as for some reason installing a single package - # also re-installs all repo dependencies as well - - run: npm install w3c/aria-practices#main - - run: npx browser-driver-manager install chromedriver --verbose - - <<: *restore_build - - run: npm run test:apg - - # Test api docs can be built - build_api_docs: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - run: npm run api-docs - - # Test newest axe-core version rule help docs are active (only on - # master prs) - test_rule_help_version: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - run: npm run test:rule-help-version - - # Test jsdom API - test_jsdom: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - <<: *restore_build - - run: npm run test:jsdom - - # Release a "next" version - next_release: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *set_npm_auth - - <<: *restore_dependency_cache_unix - - <<: *restore_build - - run: npm run next-release - - run: .circleci/verify-release.sh - - run: npm publish --tag=next - - # Release a "production" version - verify_sri: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *set_npm_auth - - <<: *restore_dependency_cache_unix - - <<: *restore_build - - run: npm run sri-validate - - # Release a "production" version - release: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *set_npm_auth - - <<: *restore_dependency_cache_unix - - <<: *restore_build - - run: .circleci/verify-release.sh - - run: npm publish - - # Create a GitHub release. - github_release: - docker: - - image: cimg/go:1.17.1 - steps: - - checkout - - run: go get gopkg.in/aktau/github-release.v0 - - run: - name: Download and run GitHub release script - command: | - curl https://raw.githubusercontent.com/dequelabs/attest-release-scripts/develop/src/node-github-release.sh -s -o ./node-github-release.sh - chmod +x ./node-github-release.sh - ./node-github-release.sh - - # Verify released package has all required files - verify_release: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - run: .circleci/verify-release.sh post - - # Verify canary released package has all required files - verify_next_release: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - run: npm run next-release - - run: .circleci/verify-release.sh post - -workflows: - version: 2 - build: - jobs: - # install deps - - dependencies_unix - - build_unix: - requires: - - dependencies_unix - # Run linting - - lint: - requires: - - dependencies_unix - # Run tests on all commits, but after installing dependencies - - test_chrome: - requires: - - build_unix - - test_firefox: - requires: - - build_unix - - test_examples: - requires: - - build_unix - - test_act: - requires: - - build_unix - - test_aria_practices: - requires: - - build_unix - - test_locales: - requires: - - build_unix - - test_virtual_rules: - requires: - - build_unix - - build_api_docs: - requires: - - build_unix - - test_rule_help_version: - requires: - - build_unix - - test_jsdom: - requires: - - build_unix - # Verify the sri history is correct - - verify_sri: - requires: - - build_unix - filters: - branches: - only: - - /^release-.+/ - - master - # Hold for approval - - hold_release: - type: approval - requires: - - test_chrome - - test_firefox - - test_examples - - test_act - - test_aria_practices - - test_locales - - test_virtual_rules - - build_api_docs - - test_rule_help_version - - test_jsdom - - verify_sri - filters: - branches: - only: - - master - # Run a next release on "develop" commits, but only after the tests pass and dependencies are installed - - next_release: - requires: - - test_chrome - - test_firefox - - test_examples - - test_act - - test_aria_practices - - test_locales - - test_virtual_rules - - build_api_docs - - test_rule_help_version - - test_jsdom - filters: - branches: - only: develop - # Run a production release on "master" commits, but only after the tests pass and dependencies are installed - - release: - requires: - - hold_release - filters: - branches: - only: master - # Verify releases have all required files - - verify_release: - requires: - - release - filters: - branches: - only: master - - verify_next_release: - requires: - - next_release - filters: - branches: - only: develop - - github_release: - requires: - - release - nightly: - triggers: - - schedule: - # run at 00:00 UTC every day - cron: '0 0 * * *' - filters: - branches: - only: - - develop - jobs: - - dependencies_unix - - build_unix: - requires: - - dependencies_unix - - test_nightly_browsers: - requires: - - build_unix - - test_nightly_act: - requires: - - build_unix - - test_nightly_aria_practices: - requires: - - build_unix diff --git a/.circleci/verify-release.sh b/.circleci/verify-release.sh deleted file mode 100755 index 93a350f06c..0000000000 --- a/.circleci/verify-release.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/bash - -set -e - -# Verifying the release can fail due to race condition of -# npm not publishing the package before we try to install -# it -function wait_for_publish() { - echo "Installing $1@$2" - - set +e - for i in {1..10}; do - npm install "$1@$2" 2> /dev/null - if [ $? -eq 0 ]; then - echo "Successfully installed" - set -e - return - else - echo "Retrying..." - sleep 10 - fi - done - - echo "Unable to install. Exiting..." - exit 1 -} - -if [ -n "$1" ] && [ "$1" == "post" ] -then - # verify the released npm package in another dir as we can't - # install a package with the same name - version=$(node -pe "require('./package.json').version") - name=$(node -pe "require('./package.json').name") - - mkdir "verify-release-$version" - cd "verify-release-$version" - npm init -y - - wait_for_publish "$name" "$version" - - node -pe "window={}; document={}; require('$name')" - - cd "node_modules/${name}" -else - # verify main file exists - main=$(node -pe "require('./package.json').main") - node -pe "window={}; document={}; require('./$main')" -fi - -# Test if typescript file exists (if declared) -# -# Note: because we are using node to read the package.json, the -# variable gets set to the string `undefined` if the property -# does not exists, rather than an empty variable. -types=$(node -pe "require('./package.json').types") -if [ "$types" == "undefined" ] -then - types=$(node -pe "require('./package.json').typings") -fi - -if [ "$types" != "undefined" ] && [ ! -f "$types" ] -then - echo "types file missing" - exit 1; -fi \ No newline at end of file diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index df541335e0..0000000000 --- a/.eslintignore +++ /dev/null @@ -1,12 +0,0 @@ -**/node_modules/* -**/tmp/* - -build/tasks/aria-supported.js - -doc/api/* -doc/examples/jest_react/*.js - -lib/core/imports/*.js -lib/core/utils/uuid.js -axe.js -axe.min.js diff --git a/.github/actions/install-deps/action.yml b/.github/actions/install-deps/action.yml new file mode 100644 index 0000000000..b7d220b7f6 --- /dev/null +++ b/.github/actions/install-deps/action.yml @@ -0,0 +1,78 @@ +name: 'Install Dependencies' +description: 'Install OS and Project dependencies' + +inputs: + node-version: + description: 'Node.js version to install' + required: false + start-xvfb: + description: 'If provided, this is the display number to run xvfb on. Should be in `:N` format, e.g., `:99`.' + required: false + nightly: + description: 'If true, installs the nightly versions of browsers.' + required: false +outputs: + chrome-path: + description: 'Path to the installed Chrome binary' + value: ${{ steps.setup-chrome.outputs.chrome-path }} + firefox-path: + description: 'Path to the installed Firefox binary' + value: ${{ steps.setup-firefox.outputs.firefox-path }} + chromedriver-path: + description: 'Path to the installed ChromeDriver binary' + value: ${{ steps.setup-chrome.outputs.chromedriver-path }} + chrome-version: + description: 'Version of the installed Chrome binary' + value: ${{ steps.setup-chrome.outputs.chrome-version }} + chromedriver-version: + description: 'Version of the installed ChromeDriver binary' + value: ${{ steps.setup-chrome.outputs.chromedriver-version }} + firefox-version: + description: 'Version of the installed Firefox binary' + value: ${{ steps.setup-firefox.outputs.firefox-version }} + +runs: + using: 'composite' + steps: + - name: Setup Node.js + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + with: + registry-url: 'https://registry.npmjs.org' + node-version: ${{ inputs.node-version }} + node-version-file: ${{ inputs.node-version == '' && '.nvmrc' || '' }} + cache: npm + - name: Fix Chrome Sandbox Permissions + shell: bash + run: | + sudo chown root:root /opt/google/chrome/chrome-sandbox + sudo chmod 4755 /opt/google/chrome/chrome-sandbox + - name: Install Xvfb + shell: bash + if: ${{ inputs.start-xvfb }} + run: | + sudo apt-get update + sudo apt-get install -y xvfb x11-xserver-utils + - name: Install Google Chrome for Testing + id: setup-chrome + uses: browser-actions/setup-chrome@b94431e051d1c52dcbe9a7092a4f10f827795416 # v2.1.0 + with: + chrome-version: ${{ inputs.nightly == 'true' && 'beta' || 'stable' }} + install-chromedriver: true + install-dependencies: true + - name: Install Firefox + id: setup-firefox + uses: browser-actions/setup-firefox@5914774dda97099441f02628f8d46411fcfbd208 # v1.7.0 + with: + firefox-version: ${{ inputs.nightly == 'true' && 'latest-nightly' || 'latest' }} + - name: Install Project Dependencies + shell: bash + run: npm ci + - name: Start Xvfb + if: ${{ inputs.start-xvfb }} + env: + DISPLAY: ${{ inputs.start-xvfb }} + shell: bash + # This is the same resolution as what CircleCI used. + # Maintaining it for consistency between the environments + # since something may be resolution dependent. + run: Xvfb "$DISPLAY" -screen 0 1280x1024x24 & diff --git a/.github/bin/determine-version.sh b/.github/bin/determine-version.sh new file mode 100755 index 0000000000..076e7a77b8 --- /dev/null +++ b/.github/bin/determine-version.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -eo pipefail + +echo "::group::Determining new prerelease version" + +NAME=$(npm pkg get name | tr -d '"') +LATEST_VERSION=$(npm pkg get version | tr -d '"') + +SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7) +NEW_VERSION="$LATEST_VERSION-canary.${SHORT_SHA}" + +echo "Latest version in package.json: $LATEST_VERSION" +echo "New prerelease version: $NEW_VERSION" + +echo "version=$NEW_VERSION" >> "$GITHUB_OUTPUT" +echo "name=$NAME" >> "$GITHUB_OUTPUT" + +echo "::endgroup::" diff --git a/.github/bin/validate-npm-deploy.sh b/.github/bin/validate-npm-deploy.sh new file mode 100755 index 0000000000..0e2029b5f2 --- /dev/null +++ b/.github/bin/validate-npm-deploy.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +set -eo pipefail + +if [ -z "$PACKAGE_NAME" ] || [ -z "$VERSION" ]; then + echo "::error::PACKAGE_NAME and VERSION environment variables must be set." + exit 1 +fi + +NPM_ROOT_PATH=$(npm root -g) + +npm install -g "${PACKAGE_NAME}@${VERSION}" || { + echo "::error::✗ Failed to install package: ${PACKAGE_NAME}@${VERSION}" + exit 1 +} + +cd "$NPM_ROOT_PATH" || { + echo "::error::✗ Failed to change directory to global npm root: $NPM_ROOT_PATH" + exit 1 +} + +node -pe "window={}; document={}; require('${PACKAGE_NAME}');" || { + echo "::error::✗ Failed to import CommonJS module for package: ${PACKAGE_NAME}" + exit 1 +} + +cd "${NPM_ROOT_PATH}/${PACKAGE_NAME}" || { + echo "::error::✗ Failed to change directory to package path: ${NPM_ROOT_PATH}/${PACKAGE_NAME}" + exit 1 +} + +types=$(node -pe "require('./package.json').types") +if [ "$types" == "undefined" ] +then + types=$(node -pe "require('./package.json').typings") +fi +if [ "$types" != "undefined" ] && [ ! -f "$types" ] +then + echo "::error::The types file is missing" + exit 1; +fi +echo "Types file '$types' is present in the package" diff --git a/.github/bin/validate-package.mjs b/.github/bin/validate-package.mjs new file mode 100755 index 0000000000..c8afd16de8 --- /dev/null +++ b/.github/bin/validate-package.mjs @@ -0,0 +1,441 @@ +#!/usr/bin/env node + +/** + * @fileoverview Validates the package before publishing. + * This script performs several checks to ensure the package + * is correctly set up, including: + * - Verifying the existence of files listed in `package.json`'s `files` array. + * - Ensuring the package can be imported using both ESM `import` and CommonJS `require()`. + * - Validating Subresource Integrity (SRI) hashes for the built files. + * + * The script generates a summary report compatible with + * GitHub Actions, providing detailed feedback on each + * validation step. + * + * Running this script locally has a few implications to be + * aware of: + * 1. It links and unlinks the package globally. So this + * could impact other workspaces where current links are used. + * 2. To test the step summary, set the `GITHUB_STEP_SUMMARY` + * environment variable to a file path. If this file does not + * exist, it will be created. + */ + +import { resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { createRequire } from 'node:module'; +import { access, appendFile, readFile } from 'node:fs/promises'; +import { execSync } from 'node:child_process'; +import pkg from '../../package.json' with { type: 'json' }; + +const isDebug = process.env.DEBUG === 'true'; +const repoRoot = resolve(import.meta.dirname, '..', '..'); +/** + * Start the exit code at 0 for a successful run. If any checks + * fail, we increment by 1 for each failure. When every check is done, + * we exit with the final exit code. + * + * This means our exit code informs us of how many failures happened. + * + * For anyone unfamiliar with exit codes in shell programs, + * an exit code of `0` means success, and any non-zero exit code + * means failure. + * + * Special note as well, in theory this _could_ go above `255`, + * causing the actual exit code to wrap around back to `0` and + * keep counting. But, if we have that many checks in here down + * the road then all the validation will need a major refactor. + */ +let exitCode = 0; +const missing = []; +const summaryFile = process.env.GITHUB_STEP_SUMMARY; +let summary = `# Package Validation + +
note: gives firefox to load (document.stylesheets), other browsers are fine. + // In Firefox, there is a delay between adding the element and it appearing in + // document.styleSheets, so we poll until we see it there. + const timeoutAt = Date.now() + 500; + const interval = setInterval(function () { + const isLoaded = Array.from(doc.styleSheets).some( + sheet => sheet.ownerNode === style + ); + if (isLoaded) { + clearInterval(interval); + resolve(); + } else if (Date.now() > timeoutAt) { + clearInterval(interval); + reject( + new Error( + 'Added