1+ name : CI/CD Pipeline
2+
3+ on :
4+ push :
5+ branches : [main, development]
6+ pull_request :
7+ branches : [main, development]
8+ workflow_dispatch :
9+ inputs :
10+ release_type :
11+ description : ' Release type (leave empty for no release)'
12+ required : false
13+ type : choice
14+ options :
15+ - ' '
16+ - ' patch'
17+ - ' minor'
18+ - ' major'
19+
20+ jobs :
21+ lint :
22+ name : Lint
23+ runs-on : ubuntu-latest
24+ steps :
25+ - name : Checkout
26+ uses : actions/checkout@v4
27+
28+ - name : Setup and install dependencies
29+ uses : ./.github/actions/setup-deps
30+
31+ - name : Run ESLint
32+ run : npm run lint
33+
34+ test-js :
35+ name : Test JavaScript/TypeScript (Node ${{ matrix.node-version }})
36+ runs-on : ubuntu-latest
37+ strategy :
38+ matrix :
39+ node-version : [20, 22]
40+ steps :
41+ - name : Checkout
42+ uses : actions/checkout@v4
43+
44+ - name : Setup and install dependencies
45+ uses : ./.github/actions/setup-deps
46+ with :
47+ node-version : ${{ matrix.node-version }}
48+
49+ - name : Run tests
50+ run : npm test
51+
52+ - name : Upload coverage
53+ uses : codecov/codecov-action@v4
54+ if : always()
55+ with :
56+ files : ./coverage/lcov.info
57+ fail_ci_if_error : false
58+
59+ test-ios-module :
60+ name : Validate iOS Module
61+ runs-on : macos-latest
62+ steps :
63+ - name : Checkout
64+ uses : actions/checkout@v4
65+
66+ - name : Validate Swift syntax
67+ run : |
68+ echo "Validating iOS Swift files..."
69+ # Check that Swift files have valid syntax
70+ for file in ios/*.swift; do
71+ if [ -f "$file" ]; then
72+ echo "Checking $file..."
73+ swiftc -parse -target arm64-apple-ios13.0 -sdk $(xcrun --sdk iphonesimulator --show-sdk-path) "$file" 2>&1 | head -20
74+ fi
75+ done
76+ echo "✅ iOS Swift files validated!"
77+
78+ - name : Check test files exist
79+ run : |
80+ echo "iOS test files:"
81+ ls -la ios/Tests/*.swift | wc -l
82+ echo "test files found"
83+
84+ build :
85+ name : Build Package
86+ runs-on : ubuntu-latest
87+ needs : [lint, test-js]
88+ steps :
89+ - name : Checkout
90+ uses : actions/checkout@v4
91+
92+ - name : Setup and install dependencies
93+ uses : ./.github/actions/setup-deps
94+
95+ - name : Cache build artifacts
96+ uses : ./.github/actions/cache-build
97+ id : build-cache
98+
99+ - name : Build package
100+ if : steps.build-cache.outputs.cache-hit != 'true'
101+ run : npm run build
102+
103+ - name : Verify build exists
104+ run : |
105+ if [ ! -d "build" ]; then
106+ echo "Build failed - running build again"
107+ npm run build
108+ fi
109+
110+ - name : Check build output
111+ run : |
112+ if [ ! -d "build" ]; then
113+ echo "Build directory not found"
114+ exit 1
115+ fi
116+ if [ ! -f "build/index.js" ]; then
117+ echo "build/index.js not found"
118+ exit 1
119+ fi
120+ if [ ! -f "build/index.d.ts" ]; then
121+ echo "build/index.d.ts not found"
122+ exit 1
123+ fi
124+
125+ - name : Pack module for testing
126+ run : npm pack
127+
128+ - name : Upload package artifact
129+ uses : actions/upload-artifact@v4
130+ with :
131+ name : npm-package
132+ path : expo-focus-menu-*.tgz
133+
134+ release-approval :
135+ name : Release Approval Check
136+ runs-on : ubuntu-latest
137+ needs : [build, test-ios-module]
138+ if : github.event_name == 'workflow_dispatch' && github.event.inputs.release_type != ''
139+ outputs :
140+ should_release : ${{ steps.check.outputs.should_release }}
141+ steps :
142+ - name : Check release conditions
143+ id : check
144+ run : |
145+ if [[ "${{ github.ref }}" == "refs/heads/main" || "${{ github.ref }}" == "refs/heads/development" ]]; then
146+ echo "should_release=true" >> $GITHUB_OUTPUT
147+ echo "✅ Release approved for branch: ${{ github.ref }}"
148+ echo "Release type: ${{ github.event.inputs.release_type }}"
149+ else
150+ echo "should_release=false" >> $GITHUB_OUTPUT
151+ echo "❌ Releases only allowed from main or development branches"
152+ exit 1
153+ fi
154+
155+ publish-npm :
156+ name : Publish to npm
157+ runs-on : ubuntu-latest
158+ needs : release-approval
159+ if : needs.release-approval.outputs.should_release == 'true'
160+ permissions :
161+ contents : write
162+ id-token : write
163+ steps :
164+ - name : Checkout
165+ uses : actions/checkout@v4
166+ with :
167+ token : ${{ secrets.GITHUB_TOKEN }}
168+ fetch-depth : 0 # Needed for version bumping
169+
170+ - name : Setup and install dependencies
171+ uses : ./.github/actions/setup-deps
172+
173+ - name : Setup Node.js for publishing
174+ uses : actions/setup-node@v4
175+ with :
176+ node-version : 20
177+ registry-url : ' https://registry.npmjs.org'
178+
179+ - name : Build package
180+ run : npm run build
181+
182+ - name : Configure Git
183+ run : |
184+ git config --global user.name "github-actions[bot]"
185+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
186+
187+ - name : Bump version
188+ id : bump-version
189+ run : |
190+ npm version ${{ github.event.inputs.release_type }} -m "chore: release v%s"
191+ VERSION=$(node -p "require('./package.json').version")
192+ echo "version=$VERSION" >> $GITHUB_OUTPUT
193+ echo "New version: $VERSION"
194+
195+ - name : Push version bump
196+ run : git push --follow-tags
197+
198+ - name : Create .npmrc
199+ run : |
200+ echo "//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}" > ~/.npmrc
201+
202+ - name : Publish to npm (with provenance)
203+ run : npm publish --access public --provenance
204+ env :
205+ NODE_AUTH_TOKEN : ${{ secrets.NPM_TOKEN }}
206+
207+ - name : Create GitHub Release
208+ run : |
209+ gh release create v${{ steps.bump-version.outputs.version }} \
210+ --title "Release v${{ steps.bump-version.outputs.version }}" \
211+ --notes "See [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/main/CHANGELOG.md) for details." \
212+ --verify-tag
213+ env :
214+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
215+
216+ publish-github-packages :
217+ name : Publish to GitHub Packages
218+ runs-on : ubuntu-latest
219+ needs : release-approval
220+ if : needs.release-approval.outputs.should_release == 'true'
221+ permissions :
222+ contents : read
223+ packages : write
224+ steps :
225+ - name : Checkout
226+ uses : actions/checkout@v4
227+
228+ - name : Setup and install dependencies
229+ uses : ./.github/actions/setup-deps
230+
231+ - name : Setup Node.js for GitHub Packages
232+ uses : actions/setup-node@v4
233+ with :
234+ node-version : 20
235+ registry-url : ' https://npm.pkg.github.com'
236+ scope : ' @${{ github.repository_owner }}'
237+
238+ - name : Build package
239+ run : npm run build
240+
241+ - name : Configure package for GitHub Packages
242+ run : |
243+ # Create a modified package.json with scoped name for GitHub Packages
244+ node -e "
245+ const pkg = require('./package.json');
246+ pkg.name = '@${{ github.repository_owner }}/' + pkg.name;
247+ pkg.publishConfig = {
248+ registry: 'https://npm.pkg.github.com'
249+ };
250+ require('fs').writeFileSync('package.json', JSON.stringify(pkg, null, 2));
251+ "
252+ echo "Package name updated for GitHub Packages"
253+ cat package.json | head -5
254+
255+ - name : Publish to GitHub Packages
256+ run : npm publish
257+ env :
258+ NODE_AUTH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
259+
260+ summary :
261+ name : Pipeline Summary
262+ runs-on : ubuntu-latest
263+ if : always()
264+ needs : [lint, test-js, test-ios-module, build, publish-npm, publish-github-packages]
265+ steps :
266+ - name : Summary
267+ run : |
268+ echo "## Pipeline Summary" >> $GITHUB_STEP_SUMMARY
269+ echo "" >> $GITHUB_STEP_SUMMARY
270+
271+ # Check each job status
272+ if [[ "${{ needs.lint.result }}" == "success" ]]; then
273+ echo "✅ Lint: Passed" >> $GITHUB_STEP_SUMMARY
274+ else
275+ echo "❌ Lint: ${{ needs.lint.result }}" >> $GITHUB_STEP_SUMMARY
276+ fi
277+
278+ if [[ "${{ needs.test-js.result }}" == "success" ]]; then
279+ echo "✅ Tests: Passed" >> $GITHUB_STEP_SUMMARY
280+ else
281+ echo "❌ Tests: ${{ needs.test-js.result }}" >> $GITHUB_STEP_SUMMARY
282+ fi
283+
284+ if [[ "${{ needs.test-ios-module.result }}" == "success" ]]; then
285+ echo "✅ iOS Module: Validated" >> $GITHUB_STEP_SUMMARY
286+ else
287+ echo "❌ iOS Module: ${{ needs.test-ios-module.result }}" >> $GITHUB_STEP_SUMMARY
288+ fi
289+
290+ if [[ "${{ needs.build.result }}" == "success" ]]; then
291+ echo "✅ Build: Successful" >> $GITHUB_STEP_SUMMARY
292+ else
293+ echo "❌ Build: ${{ needs.build.result }}" >> $GITHUB_STEP_SUMMARY
294+ fi
295+
296+ # Check if this was a release
297+ if [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ github.event.inputs.release_type }}" != "" ]]; then
298+ echo "" >> $GITHUB_STEP_SUMMARY
299+ echo "### Release Status" >> $GITHUB_STEP_SUMMARY
300+
301+ if [[ "${{ needs.publish-npm.result }}" == "success" ]]; then
302+ echo "✅ Published to npm" >> $GITHUB_STEP_SUMMARY
303+ elif [[ "${{ needs.publish-npm.result }}" == "skipped" ]]; then
304+ echo "⏭️ npm publish skipped (no release requested)" >> $GITHUB_STEP_SUMMARY
305+ else
306+ echo "❌ npm publish: ${{ needs.publish-npm.result }}" >> $GITHUB_STEP_SUMMARY
307+ fi
308+
309+ if [[ "${{ needs.publish-github-packages.result }}" == "success" ]]; then
310+ echo "✅ Published to GitHub Packages" >> $GITHUB_STEP_SUMMARY
311+ elif [[ "${{ needs.publish-github-packages.result }}" == "skipped" ]]; then
312+ echo "⏭️ GitHub Packages publish skipped" >> $GITHUB_STEP_SUMMARY
313+ else
314+ echo "❌ GitHub Packages publish: ${{ needs.publish-github-packages.result }}" >> $GITHUB_STEP_SUMMARY
315+ fi
316+ fi
0 commit comments