Skip to content

Commit d92c4c5

Browse files
committed
feat(#OFAWEB-254): add eslint-js-prefix action for DOM selector convention enforcement
- ESLint 9 plugin with require-js-prefix rule - Composite action with framework auto-detection (React, Vue, Cypress, Playwright) - CI workflow with unit and integration tests - Release workflow with conventional commits and semantic versioning - Extract bash logic into separate scripts (scripts/*.sh) - Add framework auto-detection as visible step (React, Vue, Cypress) - Merge user patterns with auto-detected patterns - Split docs: README.md overview + docs/eslint-js-prefix.md details - Simplify ESLint rule with clearer comments - Add .gitignore
1 parent 76d67dc commit d92c4c5

22 files changed

Lines changed: 1596 additions & 0 deletions

File tree

.github/workflows/release.yml

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
inputs:
8+
bump-type:
9+
description: 'Version bump type'
10+
required: true
11+
default: 'patch'
12+
type: choice
13+
options:
14+
- patch
15+
- minor
16+
- major
17+
18+
permissions:
19+
contents: write
20+
21+
jobs:
22+
release:
23+
name: Create Release
24+
runs-on: ubuntu-latest
25+
steps:
26+
- name: Checkout
27+
uses: actions/checkout@v6
28+
with:
29+
fetch-depth: 0
30+
token: ${{ secrets.GITHUB_TOKEN }}
31+
32+
- name: Get latest tag
33+
id: latest-tag
34+
run: |
35+
# Get the latest semver tag
36+
LATEST=$(git tag -l 'v*.*.*' | sort -V | tail -n1)
37+
if [[ -z "$LATEST" ]]; then
38+
LATEST="v0.0.0"
39+
fi
40+
echo "tag=$LATEST" >> $GITHUB_OUTPUT
41+
echo "Latest tag: $LATEST"
42+
43+
- name: Determine bump type from commits
44+
id: bump-type
45+
if: github.event_name == 'push'
46+
run: |
47+
LATEST_TAG="${{ steps.latest-tag.outputs.tag }}"
48+
49+
# Get commits since last tag
50+
if [[ "$LATEST_TAG" == "v0.0.0" ]]; then
51+
COMMITS=$(git log --oneline)
52+
else
53+
COMMITS=$(git log --oneline ${LATEST_TAG}..HEAD)
54+
fi
55+
56+
echo "Commits since $LATEST_TAG:"
57+
echo "$COMMITS"
58+
echo ""
59+
60+
# Determine bump type from conventional commits
61+
if echo "$COMMITS" | grep -qE '^[a-f0-9]+ (feat|fix|docs|style|refactor|perf|test|chore)(\(.+\))?!:|BREAKING CHANGE'; then
62+
BUMP="major"
63+
elif echo "$COMMITS" | grep -qE '^[a-f0-9]+ feat(\(.+\))?:'; then
64+
BUMP="minor"
65+
elif echo "$COMMITS" | grep -qE '^[a-f0-9]+ (fix|docs|style|refactor|perf|test|chore)(\(.+\))?:'; then
66+
BUMP="patch"
67+
else
68+
# Default to patch for non-conventional commits
69+
BUMP="patch"
70+
fi
71+
72+
echo "Determined bump type: $BUMP"
73+
echo "type=$BUMP" >> $GITHUB_OUTPUT
74+
75+
- name: Set bump type
76+
id: final-bump
77+
run: |
78+
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
79+
echo "type=${{ inputs.bump-type }}" >> $GITHUB_OUTPUT
80+
else
81+
echo "type=${{ steps.bump-type.outputs.type }}" >> $GITHUB_OUTPUT
82+
fi
83+
84+
- name: Calculate new version
85+
id: new-version
86+
run: |
87+
CURRENT="${{ steps.latest-tag.outputs.tag }}"
88+
BUMP="${{ steps.final-bump.outputs.type }}"
89+
90+
# Remove 'v' prefix
91+
VERSION=${CURRENT#v}
92+
93+
# Split into parts
94+
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
95+
96+
case $BUMP in
97+
major)
98+
MAJOR=$((MAJOR + 1))
99+
MINOR=0
100+
PATCH=0
101+
;;
102+
minor)
103+
MINOR=$((MINOR + 1))
104+
PATCH=0
105+
;;
106+
patch)
107+
PATCH=$((PATCH + 1))
108+
;;
109+
esac
110+
111+
NEW_VERSION="v${MAJOR}.${MINOR}.${PATCH}"
112+
MAJOR_TAG="v${MAJOR}"
113+
114+
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
115+
echo "major-tag=$MAJOR_TAG" >> $GITHUB_OUTPUT
116+
echo "New version: $NEW_VERSION (major tag: $MAJOR_TAG)"
117+
118+
- name: Generate changelog
119+
id: changelog
120+
run: |
121+
LATEST_TAG="${{ steps.latest-tag.outputs.tag }}"
122+
NEW_VERSION="${{ steps.new-version.outputs.version }}"
123+
124+
echo "## What's Changed in $NEW_VERSION" > changelog.md
125+
echo "" >> changelog.md
126+
127+
# Get commits since last tag
128+
if [[ "$LATEST_TAG" == "v0.0.0" ]]; then
129+
COMMITS=$(git log --pretty=format:"- %s (%h)" --reverse)
130+
else
131+
COMMITS=$(git log --pretty=format:"- %s (%h)" --reverse ${LATEST_TAG}..HEAD)
132+
fi
133+
134+
# Categorize commits
135+
FEATURES=$(echo "$COMMITS" | grep -E "^- feat" || true)
136+
FIXES=$(echo "$COMMITS" | grep -E "^- fix" || true)
137+
OTHER=$(echo "$COMMITS" | grep -vE "^- (feat|fix)" || true)
138+
139+
if [[ -n "$FEATURES" ]]; then
140+
echo "### Features" >> changelog.md
141+
echo "$FEATURES" | sed 's/^- feat[^:]*: /- /' >> changelog.md
142+
echo "" >> changelog.md
143+
fi
144+
145+
if [[ -n "$FIXES" ]]; then
146+
echo "### Bug Fixes" >> changelog.md
147+
echo "$FIXES" | sed 's/^- fix[^:]*: /- /' >> changelog.md
148+
echo "" >> changelog.md
149+
fi
150+
151+
if [[ -n "$OTHER" ]]; then
152+
echo "### Other Changes" >> changelog.md
153+
echo "$OTHER" >> changelog.md
154+
echo "" >> changelog.md
155+
fi
156+
157+
echo "" >> changelog.md
158+
echo "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${LATEST_TAG}...${NEW_VERSION}" >> changelog.md
159+
160+
cat changelog.md
161+
162+
- name: Create exact version tag
163+
run: |
164+
NEW_VERSION="${{ steps.new-version.outputs.version }}"
165+
git config user.name "github-actions[bot]"
166+
git config user.email "github-actions[bot]@users.noreply.github.com"
167+
git tag -a "$NEW_VERSION" -m "Release $NEW_VERSION"
168+
git push origin "$NEW_VERSION"
169+
170+
- name: Update major version tag
171+
run: |
172+
MAJOR_TAG="${{ steps.new-version.outputs.major-tag }}"
173+
git config user.name "github-actions[bot]"
174+
git config user.email "github-actions[bot]@users.noreply.github.com"
175+
git tag -fa "$MAJOR_TAG" -m "Update $MAJOR_TAG to ${{ steps.new-version.outputs.version }}"
176+
git push origin "$MAJOR_TAG" --force
177+
178+
- name: Create GitHub Release
179+
uses: softprops/action-gh-release@v2
180+
with:
181+
tag_name: ${{ steps.new-version.outputs.version }}
182+
name: ${{ steps.new-version.outputs.version }}
183+
body_path: changelog.md
184+
draft: false
185+
prerelease: false
186+
env:
187+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
188+
189+
- name: Summary
190+
run: |
191+
echo "## 🚀 Release Created" >> $GITHUB_STEP_SUMMARY
192+
echo "" >> $GITHUB_STEP_SUMMARY
193+
echo "- **Version**: ${{ steps.new-version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
194+
echo "- **Major Tag**: ${{ steps.new-version.outputs.major-tag }}" >> $GITHUB_STEP_SUMMARY
195+
echo "- **Bump Type**: ${{ steps.final-bump.outputs.type }}" >> $GITHUB_STEP_SUMMARY
196+
echo "" >> $GITHUB_STEP_SUMMARY
197+
echo "### Usage" >> $GITHUB_STEP_SUMMARY
198+
echo "" >> $GITHUB_STEP_SUMMARY
199+
echo "\`\`\`yaml" >> $GITHUB_STEP_SUMMARY
200+
echo "# Recommended: Use major version tag" >> $GITHUB_STEP_SUMMARY
201+
echo "uses: ${{ github.repository }}/actions/eslint-js-prefix@${{ steps.new-version.outputs.major-tag }}" >> $GITHUB_STEP_SUMMARY
202+
echo "" >> $GITHUB_STEP_SUMMARY
203+
echo "# Pinned: Use exact version" >> $GITHUB_STEP_SUMMARY
204+
echo "uses: ${{ github.repository }}/actions/eslint-js-prefix@${{ steps.new-version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
205+
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY

.github/workflows/test.yml

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# =============================================================================
2+
# Test Workflow
3+
# =============================================================================
4+
#
5+
# Runs unit tests and integration tests for all actions in this repository.
6+
# Triggered on every push and pull request.
7+
#
8+
# =============================================================================
9+
10+
name: Test
11+
12+
on:
13+
push:
14+
pull_request:
15+
16+
jobs:
17+
# ---------------------------------------------------------------------------
18+
# Unit Tests - Test the ESLint plugin directly
19+
# ---------------------------------------------------------------------------
20+
unit-tests:
21+
name: Unit Tests
22+
runs-on: ubuntu-latest
23+
steps:
24+
- uses: actions/checkout@v6
25+
26+
- uses: actions/setup-node@v6
27+
with:
28+
node-version: '24'
29+
30+
- name: Run plugin tests
31+
working-directory: actions/eslint-js-prefix/plugin
32+
run: |
33+
npm install
34+
npm test
35+
36+
# ---------------------------------------------------------------------------
37+
# Integration Tests - Test the full GitHub Action
38+
# ---------------------------------------------------------------------------
39+
integration-tests:
40+
name: Integration Tests
41+
runs-on: ubuntu-latest
42+
strategy:
43+
fail-fast: false
44+
matrix:
45+
fixture:
46+
- name: project-clean
47+
expected-violations: 0
48+
should-fail: false
49+
- name: project-violations
50+
expected-violations: 5
51+
should-fail: true
52+
steps:
53+
- uses: actions/checkout@v6
54+
55+
- name: Run action on ${{ matrix.fixture.name }}
56+
id: lint
57+
uses: ./actions/eslint-js-prefix
58+
continue-on-error: true
59+
with:
60+
working-directory: tests/fixtures/eslint-js-prefix/${{ matrix.fixture.name }}
61+
62+
- name: Verify results
63+
run: |
64+
VIOLATIONS="${{ steps.lint.outputs.violations }}"
65+
EXPECTED="${{ matrix.fixture.expected-violations }}"
66+
SHOULD_FAIL="${{ matrix.fixture.should-fail }}"
67+
OUTCOME="${{ steps.lint.outcome }}"
68+
69+
echo "Violations: $VIOLATIONS (expected: $EXPECTED)"
70+
echo "Outcome: $OUTCOME (should fail: $SHOULD_FAIL)"
71+
72+
# Check violation count
73+
if [[ "$VIOLATIONS" != "$EXPECTED" ]]; then
74+
echo "❌ Wrong violation count"
75+
exit 1
76+
fi
77+
78+
# Check exit status matches expectation
79+
if [[ "$SHOULD_FAIL" == "true" && "$OUTCOME" != "failure" ]]; then
80+
echo "❌ Action should have failed"
81+
exit 1
82+
fi
83+
if [[ "$SHOULD_FAIL" == "false" && "$OUTCOME" != "success" ]]; then
84+
echo "❌ Action should have succeeded"
85+
exit 1
86+
fi
87+
88+
echo "✅ Test passed"
89+
90+
# ---------------------------------------------------------------------------
91+
# Framework Detection Test - Verify auto-detection works
92+
# ---------------------------------------------------------------------------
93+
framework-detection:
94+
name: Framework Detection
95+
runs-on: ubuntu-latest
96+
steps:
97+
- uses: actions/checkout@v6
98+
99+
- name: Test with React project (auto-detect on)
100+
uses: ./actions/eslint-js-prefix
101+
with:
102+
working-directory: tests/fixtures/eslint-js-prefix/project-clean
103+
# auto-detect: true (default) - should detect React and allow data-testid
104+
105+
# ---------------------------------------------------------------------------
106+
# Final Check - All tests must pass
107+
# ---------------------------------------------------------------------------
108+
all-tests-passed:
109+
name: All Tests Passed
110+
needs: [unit-tests, integration-tests, framework-detection]
111+
runs-on: ubuntu-latest
112+
steps:
113+
- run: echo "✅ All tests passed!"

.gitignore

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Dependencies
2+
node_modules/
3+
package-lock.json
4+
5+
# Build output
6+
dist/
7+
build/
8+
9+
# IDE
10+
.idea/
11+
.vscode/
12+
*.swp
13+
*.swo
14+
15+
# OS
16+
.DS_Store
17+
Thumbs.db
18+
19+
# Logs
20+
*.log
21+
npm-debug.log*
22+
23+
# Test coverage
24+
coverage/

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
### Added
11+
- `eslint-js-prefix` action for enforcing js- prefix convention on DOM selectors
12+
- ESLint plugin with `require-js-prefix` rule
13+
- Framework auto-detection for React, Vue, Cypress, and Playwright
14+
- CI workflow with unit tests and integration tests
15+
- Release workflow with conventional commits support
16+
- Test fixtures for clean and violation scenarios

0 commit comments

Comments
 (0)