Skip to content

Commit 311ed06

Browse files
authored
ci(deploy): move to gha for oidc publishing (#4929)
# Background In order to continue publishing to npm, the org is moving to OIDC tokens. This is setup through GitHub Actions. Which has prompted a conversion from CircleCI to GHA for this repository. In this the two previous PRs, #4918 and #4919, have slowly ported chunks of the dependency workflows over. This PR now provides the deployment support to ship changes from GHA. # Key Changes ## Addition of Deployment Workflow **Concurrency**: One queue per branch. Does not cancel previous runs. The deployment workflow is triggered only on `develop` and `master` commits once the tests have completed. First, the status of the tests is checked before proceeding with any work. If any failures happened, this workflow does not do anything. First, the "Tests" workflow for the associated commit is waited on. About 12 minutes (overhead for network slowness) is allocated, 14 total runtime minutes on the job before it is force killed. If the workflow was successful, the deployment for the branch in question begins. If any other conclusion is found, or none at the end of the timer, the job exits with an error so no deployment occurs. If the branch is `develop`, execution runs autonomously to build and test the package before publishing. Once all is successful, a publish of the next tag is conducted. If the branch is `master`, execution starts with the `prod-hold` job. This is a job that accesses an environment that is defined with "Required Reviewers". Meaning it will not allow the job to continue until someone on that reviewer list has approved it to happen. Once permission is granted, the `prod-deploy` job kicks off. Conducting the same actions as the next release does, but no special tags on the version which makes it `latest` and stable. Once the publish is out, some key information is retrieved as output for the job. Once a successful `prod-deploy` has happened, two jobs kick off. One creates the GitHub Release, which uses the same script as was used in CircleCI. The other sets up NodeJS, waits for the package to be visible on npm if it isn't already, then installs the package globally. After global installation finishes, a few tests of the package that was deployed are run to ensure it is functional. A key part of both workflows, is ensuring the package contents look correct before publishing. To support more expansive testing of the required behavior, a new node script is introduced. Which helps separate the pre-publish and post-publish testing. As well as expands the capability of that test to be more comprehensive. ### New Environment A new environment is introduced, `production-deploy`. This is restricted to only being accessible from the `master` branch. It is configured with required reviewers. So *before* the environment can be accessed in a workflow run, someone from the required reviewer group must manually approve the access in the GitHub Actions UX. This secondary environment is needed since we auto-deploy `next` tags off the `develop` branch. But for `master` with stable deploys, we want to have manual approval before it goes out. Since we can't add the required reviewers to the main environment as that would impact `develop`, this is made to target just stable releases. ## New pre-publish validation script CircleCI is configured with a shell script that did some pre and post deploy validation of the contents. While it is effective for the basics, it was fairly minimal in what it did as a whole. This now has a far more comprehensive suite of checks that runs for this step. The new script pulls key data from the package to build a report. It checks that _all_ contents of the `files` definition exist on the filesystem. It then checks to ensure that the package can be imported in CommonJS. After that, ensuring it is importable (along with all importable files shipped) are capable of that in ESM. Finally, it validates the SRI hashes defined in the sri history are what are computed from the current version. The SRI check only occurs on `master` and `release-` branches. As on `develop` the hash is not updated with every change. This script works by running as a Node ESM script. It pulls key information, does the filesystem check, then links the package to itself. This makes resolving the package pull the linked version on the filesystem instead of the version installed by `@axe-core/webdriverjs` for integration testing. The output of this is logged to the console and compiled into a more easily viewable step summary. The successful output of which can be seen in [a comment on this PR](#4929 (comment)). ## Addition of final tests before deployment of stable One test not added previously, `test_rule_help_version`, which checks for the docs to be live; is now added. As this only impacted stable releases going out, it made sense to reduce the initial scope and introduce this here. The next test added was an explicit SRI validation. This runs on all commits to `master` and `release-*` branches as well as all PRs targeting them. It uses the old validation pathway for quickly getting the check back in place. We can look into what to do with the SRI stuff itself later. ## Removal of CircleCI Publishing Configuration Within this patch, the CircleCI configuration to deploy to NPM is removed since we now want to run only from OIDC on GitHub Actions. The full test suite is not removed with this. It will be removed shortly after this lands when we convert the required checks over to GHA for merging. ## Visual Overviews ### Deploy Workflow <figure><legend>Workflow Control Color Coding</legend> | Color | Element | |-------|---------| | Light Blue - `#e1f5ff` | Start (Trigger) | | Light Red - `#ffe1e1` | End (No Deploy) | | Light Green - `#e1ffe1` | End (Success states) | | Light Yellow - `#fff4e1` | Decision gates & Manual approval | </figure> <figure><legend>Step Color Coding</legend> | Color | Step Name | |-------|-----------| | Blue - `#b3d9ff` | Checkout Code | | Purple - `#d9b3ff` | Install Dependencies | | Pink - `#ffb3d9` | Build Package | | Green - `#b3ffb3` | Validate Package | | Orange - `#ffd9b3` | Publish to NPM | </figure> <details><summary>Click to open diagram</summary> ```mermaid flowchart TD Start([Push to master/develop]) --> WaitTests[wait-for-tests Job] WaitTests --> WT1[Checkout Code] WT1 --> WT2[Wait for Tests Workflow] WT2 --> TestSuccess{Tests Successful?} TestSuccess -->|No| End([End - Tests Failed]) TestSuccess -->|Yes| BranchCheck{Which Branch?} BranchCheck -->|develop| DeployNext[deploy-next Job] BranchCheck -->|master| ProdHold[prod-hold Job] DeployNext --> DN1[Checkout Code] DN1 --> DN2[Install Dependencies] DN2 --> DN3[Build Package<br/>npm prepare & build] DN3 --> DN4[Determine Prerelease Version] DN4 --> DN5[Bump Version] DN5 --> DN6[Validate Package] DN6 --> DN7[Publish to NPM<br/>--tag=next] DN7 --> ValidateNextDeploy[validate-next-deploy Job] ValidateNextDeploy --> VND1[Checkout Code] VND1 --> VND2[Setup Node.js] VND2 --> VND3[Wait for Package on NPM] VND3 --> VND4[Validate Installation of next] VND4 --> EndNext([End - Next Version Validated]) ProdHold --> PH1[Manual Approval Gate] PH1 --> ProdDeploy[prod-deploy Job] ProdDeploy --> PD1[Checkout Code] PD1 --> PD2[Install Dependencies] PD2 --> PD3[Build Package<br/>npm prepare & build] PD3 --> PD4[Validate Package] PD4 --> PD5[Publish to NPM<br/>stable version] PD5 --> PD6[Get Package Data<br/>version & name] PD6 --> CreateRelease[create-github-release Job] PD6 --> ValidateDeploy[validate-deploy Job] CreateRelease --> CR1[Checkout Code] CR1 --> CR2[Install Release Helper<br/>github-release tool] CR2 --> CR3[Download Release Script] CR3 --> CR4[Make Script Executable] CR4 --> CR5[Create GitHub Release] CR5 --> EndRelease([GitHub Release Created]) ValidateDeploy --> VD1[Checkout Code] VD1 --> VD2[Setup Node.js] VD2 --> VD3[Wait for Package on NPM] VD3 --> VD4[Validate Installation of Stable] VD4 --> EndValidate([End - Deploy Validated]) %% Consistent step colors with accessible text style DN1 fill:#b3d9ff,color:#001a33,stroke:#0066cc style PD1 fill:#b3d9ff,color:#001a33,stroke:#0066cc style CR1 fill:#b3d9ff,color:#001a33,stroke:#0066cc style WT1 fill:#b3d9ff,color:#001a33,stroke:#0066cc style VND1 fill:#b3d9ff,color:#001a33,stroke:#0066cc style VD1 fill:#b3d9ff,color:#001a33,stroke:#0066cc style DN2 fill:#d9b3ff,color:#1a0033,stroke:#6600cc style PD2 fill:#d9b3ff,color:#1a0033,stroke:#6600cc style DN3 fill:#ffb3d9,color:#330011,stroke:#cc0066 style PD3 fill:#ffb3d9,color:#330011,stroke:#cc0066 style DN6 fill:#b3ffb3,color:#2200,stroke:#00aa00 style PD4 fill:#b3ffb3,color:#2200,stroke:#00aa00 style DN7 fill:#ffd9b3,color:#331100,stroke:#cc6600 style PD5 fill:#ffd9b3,color:#331100,stroke:#cc6600 %% Decision/Gate styling style Start fill:#e1f5ff,color:#001a33 style End fill:#ffe1e1,color:#330000 style EndNext fill:#e1ffe1,color:#2200 style EndRelease fill:#e1ffe1,color:#2200 style EndValidate fill:#e1ffe1,color:#2200 style PH1 fill:#fff4e1,color:#331100 style TestSuccess fill:#fff4e1,color:#331100 style BranchCheck fill:#fff4e1,color:#331100 ``` </details> ### Validation Script <figure><legend>Color Coding of Scopes</legend> | Color | Scope | |-------|-------| | Dark Gray - `#1a1a1a` | Setup/Teardown | | Blue - `#004d99` | File Existence Check | | Orange/Brown - `#664d00` | CommonJS Compatibility Check | | Purple - `#4d004d` | Importable Check | | Teal - `#004d4d` | SRI Hash Validation | | Gray - `#5c5c5c` | Skipped operations | </figure> <figure><legend>Color Coding of States</legend> | Color | State | |-------|-------| | Green - `#2d5016` | Success | | Red - `#7d1007` | Error/Failure | | Gray - `#5c5c5c` | Skipped | </figure> <details><summary>Click to open diagram</summary> ```mermaid flowchart TD Start([Start Script]) --> Init[Initialize Variables] Init --> FileCheck[File Existence Check] FileCheck --> FileLoop[For each file in pkg.files array] FileLoop --> FileExists{File exists?} FileExists -->|yes| FilePass[✓ Mark as Found] FileExists -->|no| FileFail[✗ Mark as Missing<br/>exitCode++] FilePass --> LinkSetup FileFail --> LinkSetup LinkSetup[Create npm link] -->|success| CJS[CommonJS Compatibility Check] LinkSetup -->|fail| LinkError[Log error and skip remaining checks] CJS --> CJSRequire{Require package successful?} CJSRequire -->|yes| CJSValidate{Validate export type and version exists?} CJSRequire -->|no| CJSFail[✗ CommonJS Failed<br/>exitCode++] CJSValidate -->|yes| CJSPass[✓ CommonJS Compatible] CJSValidate -->|no| CJSFail CJSPass --> Import CJSFail --> Import Import[Importable Check] --> ImportMain{Import main package?} ImportMain -->|success| ValidateMainVersion{Version property exists?} ImportMain -->|fail| ImportMainFail[✗ Not Importable<br/>anyCaught = true] ValidateMainVersion -->|yes| ImportMainPass[✓ Mark as Importable] ValidateMainVersion -->|no| ImportMainFail ImportMainPass --> ImportLoop ImportMainFail --> ImportLoop ImportLoop[For each file in pkg.files] --> FileType{File type?} FileType -->|skip .txt, .d.ts, folders| CheckAnyCaught FileType -->|check resolve| NodeModCheck{Resolves to node_modules?} NodeModCheck -->|yes| ImportNodeFail[✗ Resolves to node_modules<br/>exitCode++] NodeModCheck -->|no| ImportTry{Import successful?} ImportTry -->|yes| CheckVersion{Has version property?} ImportTry -->|no| ImportFileFail[✗ Not Importable<br/>anyCaught = true] CheckVersion -->|yes| ImportPass[✓ Importable] CheckVersion -->|no| ImportFileFail ImportPass --> CheckAnyCaught ImportFileFail --> CheckAnyCaught ImportNodeFail --> CheckAnyCaught CheckAnyCaught{anyCaught true?} -->|yes| ImportExit[exitCode++] CheckAnyCaught -->|no| SRICheck ImportExit --> SRICheck SRICheck[SRI Hash Validation] --> BranchCheck{Branch is master or release-*?} BranchCheck -->|no| SkipSRI[Skip SRI validation] BranchCheck -->|yes| SRILoad[Load sri-history.json<br/>Calculate hashes for axe.js and axe.min.js] SRILoad --> SRICompare{Hash matches expected?} SRICompare -->|yes| SRIPass[✓ Valid SRI] SRICompare -->|no| SRIFail[✗ Invalid SRI<br/>exitCode++] SRIPass --> Cleanup SRIFail --> Cleanup SkipSRI --> Cleanup Cleanup[Unlink npm package] --> End([Exit with exitCode]) LinkError --> CleanupError[Attempt unlink if needed] CleanupError --> End style Start fill:#1a1a1a,stroke:#000,stroke-width:2px,color:#fff style Init fill:#1a1a1a,stroke:#000,stroke-width:2px,color:#fff style End fill:#1a1a1a,stroke:#000,stroke-width:2px,color:#fff style FileCheck fill:#004d99,stroke:#000,stroke-width:2px,color:#fff style FileLoop fill:#004d99,stroke:#000,stroke-width:2px,color:#fff style FileExists fill:#004d99,stroke:#000,stroke-width:2px,color:#fff style FilePass fill:#2d5016,stroke:#000,stroke-width:2px,color:#fff style FileFail fill:#7d1007,stroke:#000,stroke-width:2px,color:#fff style LinkSetup fill:#1a1a1a,stroke:#000,stroke-width:2px,color:#fff style LinkError fill:#7d1007,stroke:#000,stroke-width:2px,color:#fff style CJS fill:#664d00,stroke:#000,stroke-width:2px,color:#fff style CJSRequire fill:#664d00,stroke:#000,stroke-width:2px,color:#fff style CJSValidate fill:#664d00,stroke:#000,stroke-width:2px,color:#fff style CJSPass fill:#2d5016,stroke:#000,stroke-width:2px,color:#fff style CJSFail fill:#7d1007,stroke:#000,stroke-width:2px,color:#fff style Import fill:#4d004d,stroke:#000,stroke-width:2px,color:#fff style ImportMain fill:#4d004d,stroke:#000,stroke-width:2px,color:#fff style ValidateMainVersion fill:#4d004d,stroke:#000,stroke-width:2px,color:#fff style ImportMainPass fill:#2d5016,stroke:#000,stroke-width:2px,color:#fff style ImportLoop fill:#4d004d,stroke:#000,stroke-width:2px,color:#fff style FileType fill:#4d004d,stroke:#000,stroke-width:2px,color:#fff style NodeModCheck fill:#4d004d,stroke:#000,stroke-width:2px,color:#fff style ImportTry fill:#4d004d,stroke:#000,stroke-width:2px,color:#fff style CheckVersion fill:#4d004d,stroke:#000,stroke-width:2px,color:#fff style CheckAnyCaught fill:#4d004d,stroke:#000,stroke-width:2px,color:#fff style ImportExit fill:#4d004d,stroke:#000,stroke-width:2px,color:#fff style ImportPass fill:#2d5016,stroke:#000,stroke-width:2px,color:#fff style ImportMainFail fill:#7d1007,stroke:#000,stroke-width:2px,color:#fff style ImportFileFail fill:#7d1007,stroke:#000,stroke-width:2px,color:#fff style ImportNodeFail fill:#7d1007,stroke:#000,stroke-width:2px,color:#fff style SRICheck fill:#004d4d,stroke:#000,stroke-width:2px,color:#fff style BranchCheck fill:#004d4d,stroke:#000,stroke-width:2px,color:#fff style SRILoad fill:#004d4d,stroke:#000,stroke-width:2px,color:#fff style SRICompare fill:#004d4d,stroke:#000,stroke-width:2px,color:#fff style SRIPass fill:#2d5016,stroke:#000,stroke-width:2px,color:#fff style SRIFail fill:#7d1007,stroke:#000,stroke-width:2px,color:#fff style SkipSRI fill:#5c5c5c,stroke:#000,stroke-width:2px,color:#fff style Cleanup fill:#1a1a1a,stroke:#000,stroke-width:2px,color:#fff style CleanupError fill:#1a1a1a,stroke:#000,stroke-width:2px,color:#fff ``` </details> Fixes: #4912
1 parent a5d3112 commit 311ed06

File tree

15 files changed

+978
-222
lines changed

15 files changed

+978
-222
lines changed

.circleci/config.yml

Lines changed: 0 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -194,19 +194,6 @@ jobs:
194194
- <<: *restore_build
195195
- run: npm run test:jsdom
196196

197-
# Release a "next" version
198-
next_release:
199-
<<: *defaults
200-
<<: *unix_box
201-
steps:
202-
- checkout
203-
- <<: *set_npm_auth
204-
- <<: *restore_dependency_cache_unix
205-
- <<: *restore_build
206-
- run: npm run next-release
207-
- run: .circleci/verify-release.sh
208-
- run: npm publish --tag=next
209-
210197
# Release a "production" version
211198
verify_sri:
212199
<<: *defaults
@@ -218,51 +205,6 @@ jobs:
218205
- <<: *restore_build
219206
- run: npm run sri-validate
220207

221-
# Release a "production" version
222-
release:
223-
<<: *defaults
224-
<<: *unix_box
225-
steps:
226-
- checkout
227-
- <<: *set_npm_auth
228-
- <<: *restore_dependency_cache_unix
229-
- <<: *restore_build
230-
- run: .circleci/verify-release.sh
231-
- run: npm publish
232-
233-
# Create a GitHub release.
234-
github_release:
235-
docker:
236-
- image: cimg/go:1.17.1
237-
steps:
238-
- checkout
239-
- run: go get gopkg.in/aktau/github-release.v0
240-
- run:
241-
name: Download and run GitHub release script
242-
command: |
243-
curl https://raw.githubusercontent.com/dequelabs/attest-release-scripts/develop/src/node-github-release.sh -s -o ./node-github-release.sh
244-
chmod +x ./node-github-release.sh
245-
./node-github-release.sh
246-
247-
# Verify released package has all required files
248-
verify_release:
249-
<<: *defaults
250-
<<: *unix_box
251-
steps:
252-
- checkout
253-
- <<: *restore_dependency_cache_unix
254-
- run: .circleci/verify-release.sh post
255-
256-
# Verify canary released package has all required files
257-
verify_next_release:
258-
<<: *defaults
259-
<<: *unix_box
260-
steps:
261-
- checkout
262-
- <<: *restore_dependency_cache_unix
263-
- run: npm run next-release
264-
- run: .circleci/verify-release.sh post
265-
266208
workflows:
267209
version: 2
268210
build:
@@ -316,61 +258,3 @@ workflows:
316258
only:
317259
- /^release-.+/
318260
- master
319-
# Hold for approval
320-
- hold_release:
321-
type: approval
322-
requires:
323-
- test_chrome
324-
- test_firefox
325-
- test_examples
326-
- test_act
327-
- test_aria_practices
328-
- test_locales
329-
- test_virtual_rules
330-
- build_api_docs
331-
- test_rule_help_version
332-
- test_jsdom
333-
- verify_sri
334-
filters:
335-
branches:
336-
only:
337-
- master
338-
# Run a next release on "develop" commits, but only after the tests pass and dependencies are installed
339-
- next_release:
340-
requires:
341-
- test_chrome
342-
- test_firefox
343-
- test_examples
344-
- test_act
345-
- test_aria_practices
346-
- test_locales
347-
- test_virtual_rules
348-
- build_api_docs
349-
- test_rule_help_version
350-
- test_jsdom
351-
filters:
352-
branches:
353-
only: develop
354-
# Run a production release on "master" commits, but only after the tests pass and dependencies are installed
355-
- release:
356-
requires:
357-
- hold_release
358-
filters:
359-
branches:
360-
only: master
361-
# Verify releases have all required files
362-
- verify_release:
363-
requires:
364-
- release
365-
filters:
366-
branches:
367-
only: master
368-
- verify_next_release:
369-
requires:
370-
- next_release
371-
filters:
372-
branches:
373-
only: develop
374-
- github_release:
375-
requires:
376-
- release

.circleci/verify-release.sh

Lines changed: 0 additions & 65 deletions
This file was deleted.

.eslintignore

Lines changed: 0 additions & 12 deletions
This file was deleted.

.github/actions/install-deps/action.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ outputs:
3434
runs:
3535
using: 'composite'
3636
steps:
37-
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
37+
- name: Setup Node.js
38+
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
3839
with:
3940
registry-url: 'https://registry.npmjs.org'
4041
node-version: ${{ inputs.node-version }}
@@ -68,8 +69,10 @@ runs:
6869
run: npm ci
6970
- name: Start Xvfb
7071
if: ${{ inputs.start-xvfb }}
72+
env:
73+
DISPLAY: ${{ inputs.start-xvfb }}
7174
shell: bash
7275
# This is the same resolution as what CircleCI used.
7376
# Maintaining it for consistency between the environments
7477
# since something may be resolution dependent.
75-
run: Xvfb ${{ inputs.start-xvfb }} -screen 0 1280x1024x24 &
78+
run: Xvfb "$DISPLAY" -screen 0 1280x1024x24 &

.github/bin/determine-version.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/usr/bin/env bash
2+
3+
set -eo pipefail
4+
5+
echo "::group::Determining new prerelease version"
6+
7+
NAME=$(npm pkg get name | tr -d '"')
8+
LATEST_VERSION=$(npm pkg get version | tr -d '"')
9+
10+
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
11+
NEW_VERSION="$LATEST_VERSION-canary.${SHORT_SHA}"
12+
13+
echo "Latest version in package.json: $LATEST_VERSION"
14+
echo "New prerelease version: $NEW_VERSION"
15+
16+
echo "version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
17+
echo "name=$NAME" >> "$GITHUB_OUTPUT"
18+
19+
echo "::endgroup::"

.github/bin/validate-npm-deploy.sh

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/env bash
2+
3+
set -eo pipefail
4+
5+
if [ -z "$PACKAGE_NAME" ] || [ -z "$VERSION" ]; then
6+
echo "::error::PACKAGE_NAME and VERSION environment variables must be set."
7+
exit 1
8+
fi
9+
10+
NPM_ROOT_PATH=$(npm root -g)
11+
12+
npm install -g "${PACKAGE_NAME}@${VERSION}" || {
13+
echo "::error::✗ Failed to install package: ${PACKAGE_NAME}@${VERSION}"
14+
exit 1
15+
}
16+
17+
cd "$NPM_ROOT_PATH" || {
18+
echo "::error::✗ Failed to change directory to global npm root: $NPM_ROOT_PATH"
19+
exit 1
20+
}
21+
22+
node -pe "window={}; document={}; require('${PACKAGE_NAME}');" || {
23+
echo "::error::✗ Failed to import CommonJS module for package: ${PACKAGE_NAME}"
24+
exit 1
25+
}
26+
27+
cd "${NPM_ROOT_PATH}/${PACKAGE_NAME}" || {
28+
echo "::error::✗ Failed to change directory to package path: ${NPM_ROOT_PATH}/${PACKAGE_NAME}"
29+
exit 1
30+
}
31+
32+
types=$(node -pe "require('./package.json').types")
33+
if [ "$types" == "undefined" ]
34+
then
35+
types=$(node -pe "require('./package.json').typings")
36+
fi
37+
if [ "$types" != "undefined" ] && [ ! -f "$types" ]
38+
then
39+
echo "::error::The types file is missing"
40+
exit 1;
41+
fi
42+
echo "Types file '$types' is present in the package"

0 commit comments

Comments
 (0)