Skip to content

Commit b286ef4

Browse files
jskupsikclaudeamcclain
authored
Added deploy scripts, dependabot, consolidated codeql into ci script (#4261)
Adds GitHub Actions workflows for CI and deployment, replacing/consolidating the previous CodeQL-only setup: - **`ci.yml`** — New unified CI workflow that runs linting and CodeQL analysis on PRs and pushes to `develop`. Consolidates the old standalone `codeql-analysis.yml` (deleted) into this single workflow, upgrading actions from v1/v2 to v3/v6. - **`deploySnapshot.yml`** — Publishes SNAPSHOT builds to npm (tagged `next`) on every push to `develop`. Supports optional version override via manual dispatch. Appends a timestamp to ensure unique snapshot versions. - **`deployRelease.yml`** — Manually triggered release workflow (from `master`). Validates semver input, enforces sequential versioning, publishes to npm, tags the commit, and creates a GitHub Release with auto-generated notes. Includes hotfix support for patching older versions from non-master branches. - **`dependabot.yml`** — Enables weekly Dependabot updates for both GitHub Actions and npm dependencies. - **`.npmrc`** — Switched FontAwesome Pro registry from the internal mirror (`repo.xh.io`) to the official FontAwesome registry (`npm.fontawesome.com`). All workflows inject a `FONTAWESOME_PACKAGE_TOKEN` secret for authentication. --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Anselm McClain <atm@xh.io>
1 parent a824678 commit b286ef4

13 files changed

Lines changed: 555 additions & 246 deletions

.github/dependabot.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "github-actions"
4+
directory: "/"
5+
schedule:
6+
interval: "weekly"
7+
- package-ecosystem: "npm"
8+
directory: "/"
9+
schedule:
10+
interval: "weekly"

.github/workflows/ci.yml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# CI — Validates linting and runs CodeQL analysis on PRs and pushes to develop.
2+
#
3+
# This workflow does not publish artifacts. For snapshot and release publishing,
4+
# see Deploy Snapshot and Deploy Release.
5+
6+
name: CI
7+
8+
on:
9+
push:
10+
branches: [ "develop" ]
11+
pull_request:
12+
branches: [ "develop" ]
13+
schedule:
14+
- cron: '23 10 * * 4'
15+
16+
jobs:
17+
lint:
18+
# No need / benefit to run lint on a schedule.
19+
if: github.event_name != 'schedule'
20+
21+
runs-on: ubuntu-latest
22+
permissions:
23+
contents: read
24+
25+
steps:
26+
- uses: actions/checkout@v6
27+
28+
- name: Setup Node.js
29+
uses: actions/setup-node@v4
30+
with:
31+
node-version-file: '.nvmrc'
32+
33+
- name: Configure Font Awesome registry auth
34+
env:
35+
FONTAWESOME_PACKAGE_TOKEN: ${{ secrets.FONTAWESOME_PACKAGE_TOKEN }}
36+
run: echo "//npm.fontawesome.com/:_authToken=$FONTAWESOME_PACKAGE_TOKEN" >> .npmrc
37+
38+
- name: Install dependencies
39+
run: yarn install --frozen-lockfile
40+
41+
- name: Lint
42+
run: yarn lint
43+
44+
codeql:
45+
46+
runs-on: ubuntu-latest
47+
permissions:
48+
actions: read
49+
contents: read
50+
security-events: write
51+
52+
steps:
53+
- uses: actions/checkout@v6
54+
55+
- name: Initialize CodeQL
56+
uses: github/codeql-action/init@v3
57+
with:
58+
languages: javascript
59+
60+
- name: Autobuild
61+
uses: github/codeql-action/autobuild@v3
62+
63+
- name: Perform CodeQL Analysis
64+
uses: github/codeql-action/analyze@v3

.github/workflows/codeql-analysis.yml

Lines changed: 0 additions & 70 deletions
This file was deleted.
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# Deploy Release — Publishes a numbered release to npm.
2+
#
3+
# Manually triggered from the master branch. Validates the version input, publishes to npm,
4+
# tags the commit, and creates a GitHub release with auto-generated notes. For snapshot
5+
# builds, see Deploy Snapshot.
6+
7+
name: Deploy Release
8+
9+
on:
10+
workflow_dispatch:
11+
inputs:
12+
xhReleaseVersion:
13+
description: 'Release Version'
14+
required: true
15+
type: string
16+
isHotfix:
17+
description: 'As hotfix. Check when releasing a hotfix to a version other than the latest.'
18+
required: true
19+
default: false
20+
type: boolean
21+
22+
jobs:
23+
build:
24+
# Guards against accidental release from develop. Requires master for standard
25+
# releases and a branch other than master (or develop) when isHotfix is set.
26+
if: github.ref != 'refs/heads/develop' && ((github.ref == 'refs/heads/master') != inputs.isHotfix)
27+
28+
runs-on: ubuntu-latest
29+
permissions:
30+
contents: write
31+
32+
steps:
33+
- uses: actions/checkout@v6
34+
with:
35+
fetch-depth: 0
36+
fetch-tags: true
37+
38+
- name: Validate release version
39+
env:
40+
VERSION: ${{ inputs.xhReleaseVersion }}
41+
IS_HOTFIX: ${{ inputs.isHotfix }}
42+
run: |
43+
# Must be semver (X.Y.Z) with no leading zeros.
44+
if [[ ! "$VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
45+
echo "::error::Invalid version '$VERSION'. Must be semver with no leading zeros (e.g. 82.0.0)."
46+
exit 1
47+
fi
48+
49+
# Must not duplicate an existing release
50+
if git tag -l "v$VERSION" | grep -q .; then
51+
echo "::error::Tag v$VERSION already exists. This version has already been released."
52+
exit 1
53+
fi
54+
55+
# Strict version validation — the new version must be exactly one
56+
# increment from the latest relevant tag and hotfix cannot be latest.
57+
LATEST=$(git tag -l 'v*' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sed 's/^v//' | sort -t. -k1,1n -k2,2n -k3,3n | tail -1)
58+
if [ -z "$LATEST" ]; then
59+
echo "::error::No existing release tags found. Cannot validate version."
60+
exit 1
61+
fi
62+
LATEST_MAJOR=$(echo "$LATEST" | cut -d. -f1)
63+
LATEST_MINOR=$(echo "$LATEST" | cut -d. -f2)
64+
LATEST_PATCH=$(echo "$LATEST" | cut -d. -f3)
65+
66+
# The three versions that would be valid as a standard (non-hotfix) release.
67+
NEXT_MAJOR="$(( LATEST_MAJOR + 1 )).0.0"
68+
NEXT_MINOR="${LATEST_MAJOR}.$(( LATEST_MINOR + 1 )).0"
69+
NEXT_PATCH="${LATEST_MAJOR}.${LATEST_MINOR}.$(( LATEST_PATCH + 1 ))"
70+
71+
if [ "$IS_HOTFIX" = "true" ]; then
72+
# A hotfix must NOT be a standard next-release version.
73+
if [ "$VERSION" = "$NEXT_MAJOR" ] || [ "$VERSION" = "$NEXT_MINOR" ] || [ "$VERSION" = "$NEXT_PATCH" ]; then
74+
echo "::error::Hotfix version $VERSION matches a standard release increment (latest is v$LATEST). Use a standard release instead."
75+
exit 1
76+
fi
77+
78+
NEW_MAJOR=$(echo "$VERSION" | cut -d. -f1)
79+
NEW_MINOR=$(echo "$VERSION" | cut -d. -f2)
80+
81+
# Validate against the highest tags for this major version.
82+
MAX_MINOR=$(git tag -l "v${NEW_MAJOR}.*" | grep -E "^v${NEW_MAJOR}\.[0-9]+\.[0-9]+$" | sed 's/^v//' | cut -d. -f2 | sort -n | tail -1)
83+
if [ -z "$MAX_MINOR" ]; then
84+
echo "::error::No existing tags found for major version ${NEW_MAJOR}. Cannot validate hotfix."
85+
exit 1
86+
fi
87+
88+
# Allowed: next minor bump for this major.
89+
ALLOWED_MINOR="${NEW_MAJOR}.$(( MAX_MINOR + 1 )).0"
90+
91+
# Only offer a patch bump if tags exist for this specific MAJOR.MINOR.
92+
MAX_PATCH=$(git tag -l "v${NEW_MAJOR}.${NEW_MINOR}.*" | grep -E "^v${NEW_MAJOR}\.${NEW_MINOR}\.[0-9]+$" | sed 's/^v//' | cut -d. -f3 | sort -n | tail -1)
93+
if [ -n "$MAX_PATCH" ]; then
94+
ALLOWED_PATCH="${NEW_MAJOR}.${NEW_MINOR}.$(( MAX_PATCH + 1 ))"
95+
fi
96+
97+
if [ "$VERSION" != "$ALLOWED_MINOR" ] && [ "$VERSION" != "${ALLOWED_PATCH:-}" ]; then
98+
ALLOWED="$ALLOWED_MINOR"
99+
[ -n "${ALLOWED_PATCH:-}" ] && ALLOWED="$ALLOWED or $ALLOWED_PATCH"
100+
echo "::error::Hotfix version $VERSION is not a valid next version. Allowed: $ALLOWED."
101+
exit 1
102+
fi
103+
else
104+
# Standard release: must be exactly one increment from the latest tag.
105+
if [ "$VERSION" != "$NEXT_MAJOR" ] && [ "$VERSION" != "$NEXT_MINOR" ] && [ "$VERSION" != "$NEXT_PATCH" ]; then
106+
echo "::error::Version $VERSION is not a valid next version (latest is v$LATEST). Allowed: $NEXT_MAJOR, $NEXT_MINOR, or $NEXT_PATCH."
107+
exit 1
108+
fi
109+
fi
110+
111+
- name: Setup Node.js
112+
uses: actions/setup-node@v4
113+
with:
114+
node-version-file: '.nvmrc'
115+
registry-url: 'https://registry.npmjs.org'
116+
117+
- name: Configure Font Awesome registry auth
118+
env:
119+
FONTAWESOME_PACKAGE_TOKEN: ${{ secrets.FONTAWESOME_PACKAGE_TOKEN }}
120+
run: echo "//npm.fontawesome.com/:_authToken=$FONTAWESOME_PACKAGE_TOKEN" >> .npmrc
121+
122+
- name: Install dependencies
123+
run: yarn install --frozen-lockfile
124+
125+
- name: Yarn lint
126+
run: yarn lint:all
127+
128+
- name: Set release version in package.json
129+
env:
130+
VERSION: ${{ inputs.xhReleaseVersion }}
131+
run: npm version --no-git-tag-version --new-version "$VERSION"
132+
133+
- name: Publish release to npm
134+
env:
135+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
136+
run: npm publish
137+
138+
- name: Tag release
139+
env:
140+
VERSION: ${{ inputs.xhReleaseVersion }}
141+
run: |
142+
git tag "v$VERSION"
143+
git push origin "v$VERSION"
144+
145+
- name: Create GitHub release
146+
env:
147+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
148+
VERSION: ${{ inputs.xhReleaseVersion }}
149+
IS_HOTFIX: ${{ inputs.isHotfix }}
150+
run: |
151+
LATEST_FLAG="--latest"
152+
if [ "$IS_HOTFIX" = "true" ]; then
153+
LATEST_FLAG="--latest=false"
154+
fi
155+
gh release create "v$VERSION" \
156+
--title "v$VERSION" \
157+
--generate-notes \
158+
"$LATEST_FLAG"
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Deploy Snapshot — Publishes a SNAPSHOT build to npm on every push to develop.
2+
#
3+
# Snapshots are mutable development builds (version from package.json, e.g. 82.0.0-SNAPSHOT).
4+
# They are published with the `next` dist-tag so they don't affect `latest`. For numbered
5+
# releases, see Deploy Release.
6+
7+
name: Deploy Snapshot
8+
9+
on:
10+
push:
11+
branches: [ "develop" ]
12+
workflow_dispatch:
13+
inputs:
14+
xhSnapshotVersion:
15+
description: '(Optional) Snapshot version to override default in package.json. Note that the suffix "-SNAPSHOT" will be automatically appended if not present.'
16+
required: false
17+
default: ''
18+
type: string
19+
20+
concurrency:
21+
group: deploy-snapshot
22+
cancel-in-progress: true
23+
24+
jobs:
25+
build:
26+
27+
runs-on: ubuntu-latest
28+
permissions:
29+
contents: read
30+
31+
steps:
32+
- uses: actions/checkout@v6
33+
34+
- name: Setup Node.js
35+
uses: actions/setup-node@v4
36+
with:
37+
node-version-file: '.nvmrc'
38+
registry-url: 'https://registry.npmjs.org'
39+
40+
- name: Configure Font Awesome registry auth
41+
env:
42+
FONTAWESOME_PACKAGE_TOKEN: ${{ secrets.FONTAWESOME_PACKAGE_TOKEN }}
43+
run: echo "//npm.fontawesome.com/:_authToken=$FONTAWESOME_PACKAGE_TOKEN" >> .npmrc
44+
45+
- name: Install dependencies
46+
run: yarn install --frozen-lockfile
47+
48+
- name: Yarn lint
49+
run: yarn lint:all
50+
51+
- name: Override snapshot version in package.json
52+
if: inputs.xhSnapshotVersion != ''
53+
env:
54+
VERSION: ${{ inputs.xhSnapshotVersion }}
55+
run: |
56+
if [[ ! "$VERSION" == *-SNAPSHOT ]]; then
57+
VERSION="$VERSION-SNAPSHOT"
58+
fi
59+
npm version --no-git-tag-version --new-version "$VERSION"
60+
echo "Updated version to SNAPSHOT: $VERSION"
61+
62+
- name: Validate SNAPSHOT version
63+
run: |
64+
VERSION=$(node -p "require('./package.json').version")
65+
TIME=$(node -p "new Date().getTime()")
66+
if [[ ! "$VERSION" == *-SNAPSHOT ]]; then
67+
echo "::error::'$VERSION' is not a SNAPSHOT version. Aborting."
68+
exit 1
69+
fi
70+
npm version --no-git-tag-version --new-version "$VERSION.$TIME"
71+
echo "Validated snapshot version: $VERSION.$TIME"
72+
73+
- name: Publish snapshot to npm
74+
env:
75+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
76+
run: npm publish --tag next

.npmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
@fortawesome:registry=https://repo.xh.io/repository/npm-fontawesome/
1+
@fortawesome:registry=https://npm.fontawesome.com/

0 commit comments

Comments
 (0)