@codyswann/lisa 1.51.4 → 1.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2049 +0,0 @@
1
- # This file is managed by Lisa.
2
- # Do not edit directly — changes will be overwritten on the next `lisa` run.
3
- # -----------------------------------------------------------------------------
4
- # Quality Checks Workflow
5
- # -----------------------------------------------------------------------------
6
- # ⚠️ WARNING: THIS FILE IS AUTO-GENERATED. DO NOT EDIT MANUALLY! ⚠️
7
- # Any changes may be overwritten by the generation process.
8
- # This workflow runs various quality checks on the codebase including:
9
- # - Linting
10
- # - Type checking
11
- # - Unit tests
12
- # - Format checking
13
- # - Build verification
14
- # - Security scanning
15
- # - AI-powered code quality and security analysis (non-blocking)
16
- # - Enterprise security tools:
17
- # - SonarCloud SAST analysis
18
- # - Snyk dependency vulnerability scanning
19
- # - GitGuardian secret detection
20
- # - FOSSA license compliance checking
21
- # - OWASP ZAP DAST baseline scanning
22
- # - E2E testing:
23
- # - Playwright web E2E tests (auto-detects playwright.config.ts)
24
- # - Maestro Cloud mobile E2E tests (requires MAESTRO_API_KEY, project ID, and app binary)
25
- #
26
- # Example usage in another workflow:
27
- # ```yaml
28
- # quality:
29
- # uses: ./.github/workflows/quality.yml
30
- # with:
31
- # node_version: '22.21.1'
32
- # package_manager: 'npm'
33
- # skip_jobs: 'npm_security_scan,sonarcloud,snyk'
34
- # secrets:
35
- # SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # For SonarCloud SAST
36
- # SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} # For Snyk dependency scanning
37
- # GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }} # For secret detection
38
- # FOSSA_API_KEY: ${{ secrets.FOSSA_API_KEY }} # For license compliance
39
- # MAESTRO_API_KEY: ${{ secrets.MAESTRO_API_KEY }} # For Maestro Cloud E2E
40
- # ```
41
-
42
- name: 🔍 Quality Checks
43
-
44
- on:
45
- workflow_call:
46
- inputs:
47
- node_version:
48
- description: 'Node.js version to use'
49
- required: false
50
- default: '22.21.1'
51
- type: string
52
- package_manager:
53
- description: 'Package manager to use (npm, yarn, or bun)'
54
- required: false
55
- default: 'npm'
56
- type: string
57
- skip_jobs:
58
- description: 'Jobs to skip (comma-separated: lint,lint_slow,typecheck,test,test:unit,test:integration,test:e2e,maestro_e2e,playwright_e2e,format,build,dead_code,sg_scan,npm_security_scan,zap_baseline,github_issue)'
59
- required: false
60
- default: ''
61
- type: string
62
- zap_target_url:
63
- description: 'Target URL for OWASP ZAP baseline scan (leave empty to skip ZAP)'
64
- required: false
65
- default: ''
66
- type: string
67
- zap_rules_file:
68
- description: 'Path to ZAP rules configuration file'
69
- required: false
70
- default: '.zap/baseline.conf'
71
- type: string
72
- working_directory:
73
- description: 'Directory to run commands in (if not root)'
74
- required: false
75
- default: ''
76
- type: string
77
- compliance_framework:
78
- description: 'Compliance framework to validate against (none, soc2, iso27001, hipaa, pci-dss)'
79
- required: false
80
- default: 'none'
81
- type: string
82
- require_approval:
83
- description: 'Require approval for production deployments (uses GitHub environments)'
84
- required: false
85
- default: false
86
- type: boolean
87
- audit_retention_days:
88
- description: 'Number of days to retain audit logs'
89
- required: false
90
- default: 90
91
- type: number
92
- generate_evidence_package:
93
- description: 'Generate compliance evidence package for audits'
94
- required: false
95
- default: false
96
- type: boolean
97
- approval_environment:
98
- description: 'GitHub environment name for approval gate (must exist in repo settings). Set to empty string to skip even when require_approval is true.'
99
- required: false
100
- default: 'production'
101
- type: string
102
- maestro_app_file:
103
- description: 'Path to app binary for Maestro Cloud E2E tests (e.g., app-release.apk or App.app). Required for Maestro tests to run.'
104
- required: false
105
- default: ''
106
- type: string
107
- maestro_workspace:
108
- description: 'Path to Maestro workspace directory containing test flows (default: .maestro)'
109
- required: false
110
- default: '.maestro'
111
- type: string
112
- maestro_include_tags:
113
- description: 'Comma-separated tags to include in Maestro test run (e.g., smoke,critical)'
114
- required: false
115
- default: ''
116
- type: string
117
- maestro_project_id:
118
- description: 'Maestro Cloud project ID. Required for Maestro tests to run.'
119
- required: false
120
- default: ''
121
- type: string
122
- secrets:
123
- SONAR_TOKEN:
124
- description: 'SonarCloud token for SAST analysis'
125
- required: false
126
- SNYK_TOKEN:
127
- description: 'Snyk token for dependency vulnerability scanning'
128
- required: false
129
- GITGUARDIAN_API_KEY:
130
- description: 'GitGuardian API key for secret detection'
131
- required: false
132
- FOSSA_API_KEY:
133
- description: 'FOSSA API key for license compliance checking'
134
- required: false
135
- MAESTRO_API_KEY:
136
- description: 'Maestro Cloud API key for mobile E2E testing'
137
- required: false
138
-
139
- # Concurrency is managed by the parent workflow that calls this one
140
- # This avoids deadlocks between parent and child workflows
141
-
142
- jobs:
143
- # Central dependency installation job to optimize performance
144
- install_dependencies:
145
- name: 📦 Install Dependencies
146
- runs-on: ubuntu-latest
147
- outputs:
148
- cache-key: ${{ steps.cache.outputs.cache-key }}
149
- cache-hit: ${{ steps.cache.outputs.cache-hit }}
150
- steps:
151
- - name: 📥 Checkout repository
152
- uses: actions/checkout@v4
153
-
154
- - name: 🔧 Setup Node.js
155
- uses: actions/setup-node@v4
156
- with:
157
- node-version: ${{ inputs.node_version }}
158
- cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
159
-
160
- - name: 🍞 Setup Bun
161
- if: inputs.package_manager == 'bun'
162
- uses: oven-sh/setup-bun@v2
163
- with:
164
- bun-version: '1.3.8'
165
-
166
- - name: 🔑 Generate cache key
167
- id: cache
168
- run: |
169
- if [ -f "package-lock.json" ]; then
170
- echo "cache-key=${{ runner.os }}-node-${{ inputs.node_version }}-npm-${{ hashFiles('package-lock.json') }}" >> $GITHUB_OUTPUT
171
- elif [ -f "yarn.lock" ]; then
172
- echo "cache-key=${{ runner.os }}-node-${{ inputs.node_version }}-yarn-${{ hashFiles('yarn.lock') }}" >> $GITHUB_OUTPUT
173
- elif [ -f "bun.lockb" ]; then
174
- echo "cache-key=${{ runner.os }}-node-${{ inputs.node_version }}-bun-${{ hashFiles('bun.lockb') }}" >> $GITHUB_OUTPUT
175
- else
176
- echo "cache-key=${{ runner.os }}-node-${{ inputs.node_version }}-${{ inputs.package_manager }}-${{ hashFiles('package.json') }}" >> $GITHUB_OUTPUT
177
- fi
178
-
179
- - name: 📦 Cache node_modules
180
- id: cache-modules
181
- uses: actions/cache@v4
182
- with:
183
- path: |
184
- node_modules
185
- ~/.npm
186
- ~/.yarn/cache
187
- ~/.bun/install/cache
188
- key: ${{ steps.cache.outputs.cache-key }}
189
- restore-keys: |
190
- ${{ runner.os }}-node-${{ inputs.node_version }}-${{ inputs.package_manager }}-
191
- ${{ runner.os }}-node-${{ inputs.node_version }}-
192
- ${{ runner.os }}-node-
193
-
194
- - name: 📥 Install dependencies
195
- if: steps.cache-modules.outputs.cache-hit != 'true'
196
- run: |
197
- if [ "${{ inputs.package_manager }}" = "npm" ]; then
198
- npm ci
199
- elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
200
- yarn install --frozen-lockfile
201
- elif [ "${{ inputs.package_manager }}" = "bun" ]; then
202
- bun install --frozen-lockfile
203
- else
204
- echo "Unsupported package manager: ${{ inputs.package_manager }}"
205
- exit 1
206
- fi
207
- working-directory: ${{ inputs.working_directory || '.' }}
208
-
209
- - name: 📤 Upload dependencies artifact
210
- uses: actions/upload-artifact@v4
211
- with:
212
- name: node-modules-${{ github.run_id }}
213
- path: |
214
- node_modules
215
- package.json
216
- package-lock.json
217
- yarn.lock
218
- bun.lockb
219
- retention-days: 1
220
- if-no-files-found: error
221
-
222
- - name: 📊 Cache status
223
- run: |
224
- echo "cache-hit=${{ steps.cache-modules.outputs.cache-hit }}" >> $GITHUB_OUTPUT
225
- id: cache-status
226
- lint:
227
- name: 🧹 Lint
228
- runs-on: ubuntu-latest
229
- timeout-minutes: 15
230
- if: ${{ !contains(inputs.skip_jobs, 'lint') }}
231
-
232
- steps:
233
- - name: 📥 Checkout repository
234
- uses: actions/checkout@v4
235
-
236
- - name: 🔧 Setup Node.js
237
- uses: actions/setup-node@v4
238
- with:
239
- node-version: ${{ inputs.node_version }}
240
- cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
241
-
242
- - name: 🍞 Setup Bun
243
- if: inputs.package_manager == 'bun'
244
- uses: oven-sh/setup-bun@v2
245
- with:
246
- bun-version: '1.3.8'
247
-
248
- - name: 📥 Install dependencies
249
- run: |
250
- if [ "${{ inputs.package_manager }}" = "npm" ]; then
251
- npm ci
252
- elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
253
- yarn install --frozen-lockfile
254
- elif [ "${{ inputs.package_manager }}" = "bun" ]; then
255
- bun install --frozen-lockfile
256
- fi
257
-
258
- - name: 🧹 Run linter
259
- run: ${{ inputs.package_manager }} run lint
260
- env:
261
- NODE_OPTIONS: --max-old-space-size=6144
262
- working-directory: ${{ inputs.working_directory || '.' }}
263
-
264
- lint_slow:
265
- name: 🐢 Slow Lint Rules
266
- runs-on: ubuntu-latest
267
- timeout-minutes: 20
268
- if: ${{ !contains(inputs.skip_jobs, 'lint_slow') }}
269
-
270
- steps:
271
- - name: 📥 Checkout repository
272
- uses: actions/checkout@v4
273
-
274
- - name: 🔧 Setup Node.js
275
- uses: actions/setup-node@v4
276
- with:
277
- node-version: ${{ inputs.node_version }}
278
- cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
279
-
280
- - name: 🍞 Setup Bun
281
- if: inputs.package_manager == 'bun'
282
- uses: oven-sh/setup-bun@v2
283
- with:
284
- bun-version: '1.3.8'
285
-
286
- - name: 📥 Install dependencies
287
- run: |
288
- if [ "${{ inputs.package_manager }}" = "npm" ]; then
289
- npm ci
290
- elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
291
- yarn install --frozen-lockfile
292
- elif [ "${{ inputs.package_manager }}" = "bun" ]; then
293
- bun install --frozen-lockfile
294
- fi
295
- working-directory: ${{ inputs.working_directory || '.' }}
296
-
297
- - name: 🐢 Run slow lint rules
298
- run: ${{ inputs.package_manager }} run lint:slow
299
- env:
300
- NODE_OPTIONS: --max-old-space-size=6144
301
- working-directory: ${{ inputs.working_directory || '.' }}
302
-
303
- typecheck:
304
- name: 🔍 Type Check
305
- runs-on: ubuntu-latest
306
- timeout-minutes: 15
307
- if: ${{ !contains(inputs.skip_jobs, 'typecheck') }}
308
-
309
- steps:
310
- - name: 📥 Checkout repository
311
- uses: actions/checkout@v4
312
-
313
- - name: 🔧 Setup Node.js
314
- uses: actions/setup-node@v4
315
- with:
316
- node-version: ${{ inputs.node_version }}
317
- cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
318
-
319
- - name: 🍞 Setup Bun
320
- if: inputs.package_manager == 'bun'
321
- uses: oven-sh/setup-bun@v2
322
- with:
323
- bun-version: '1.3.8'
324
-
325
- - name: 📦 Install dependencies
326
- run: |
327
- if [ "${{ inputs.package_manager }}" = "npm" ]; then
328
- npm ci
329
- elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
330
- yarn install --frozen-lockfile
331
- elif [ "${{ inputs.package_manager }}" = "bun" ]; then
332
- bun install --frozen-lockfile
333
- fi
334
- working-directory: ${{ inputs.working_directory || '.' }}
335
-
336
- - name: 🔍 Run type check
337
- run: ${{ inputs.package_manager }} run typecheck
338
- env:
339
- NODE_OPTIONS: --max-old-space-size=6144
340
- working-directory: ${{ inputs.working_directory || '.' }}
341
-
342
- test:
343
- name: 🧪 Run Tests
344
- runs-on: ubuntu-latest
345
- timeout-minutes: 20
346
- if: ${{ !contains(inputs.skip_jobs, 'test') }}
347
-
348
- steps:
349
- - name: 📥 Checkout repository
350
- uses: actions/checkout@v4
351
-
352
- - name: 🔧 Setup Node.js
353
- uses: actions/setup-node@v4
354
- with:
355
- node-version: ${{ inputs.node_version }}
356
- cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
357
-
358
- - name: 🍞 Setup Bun
359
- if: inputs.package_manager == 'bun'
360
- uses: oven-sh/setup-bun@v2
361
- with:
362
- bun-version: '1.3.8'
363
-
364
- - name: 📦 Install dependencies
365
- run: |
366
- if [ "${{ inputs.package_manager }}" = "npm" ]; then
367
- npm ci
368
- elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
369
- yarn install --frozen-lockfile
370
- elif [ "${{ inputs.package_manager }}" = "bun" ]; then
371
- bun install --frozen-lockfile
372
- fi
373
- working-directory: ${{ inputs.working_directory || '.' }}
374
-
375
- - name: 🧪 Run tests
376
- run: |
377
- if [ "${{ inputs.package_manager }}" = "npm" ]; then
378
- npm test
379
- elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
380
- yarn test
381
- elif [ "${{ inputs.package_manager }}" = "bun" ]; then
382
- bun run test
383
- fi
384
- working-directory: ${{ inputs.working_directory || '.' }}
385
-
386
- test_unit:
387
- name: 🧪 Run Unit Tests
388
- runs-on: ubuntu-latest
389
- timeout-minutes: 45
390
- if: ${{ !contains(inputs.skip_jobs, 'test:unit') }}
391
-
392
- steps:
393
- - name: 📥 Checkout repository
394
- uses: actions/checkout@v4
395
-
396
- - name: 🔧 Setup Node.js
397
- uses: actions/setup-node@v4
398
- with:
399
- node-version: ${{ inputs.node_version }}
400
- cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
401
-
402
- - name: 🍞 Setup Bun
403
- if: inputs.package_manager == 'bun'
404
- uses: oven-sh/setup-bun@v2
405
- with:
406
- bun-version: '1.3.8'
407
-
408
- - name: 📦 Install dependencies
409
- run: |
410
- if [ "${{ inputs.package_manager }}" = "npm" ]; then
411
- npm ci
412
- elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
413
- yarn install --frozen-lockfile
414
- elif [ "${{ inputs.package_manager }}" = "bun" ]; then
415
- bun install --frozen-lockfile
416
- fi
417
- working-directory: ${{ inputs.working_directory || '.' }}
418
-
419
- - name: 🔍 Check for test:unit script
420
- id: check_script
421
- run: |
422
- if [ -f "package.json" ]; then
423
- if grep -q '"test:unit"' package.json; then
424
- echo "exists=true" >> $GITHUB_OUTPUT
425
- else
426
- echo "exists=false" >> $GITHUB_OUTPUT
427
- fi
428
- else
429
- echo "exists=false" >> $GITHUB_OUTPUT
430
- fi
431
- working-directory: ${{ inputs.working_directory || '.' }}
432
-
433
- - name: 🧪 Run unit tests with coverage
434
- if: steps.check_script.outputs.exists == 'true'
435
- run: |
436
- if [ "${{ inputs.package_manager }}" = "npm" ]; then
437
- npm run test:cov
438
- elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
439
- yarn test:cov
440
- elif [ "${{ inputs.package_manager }}" = "bun" ]; then
441
- bun run test:cov
442
- fi
443
- working-directory: ${{ inputs.working_directory || '.' }}
444
-
445
- - name: ⏭️ Skip unit tests (no test:unit script)
446
- if: steps.check_script.outputs.exists == 'false'
447
- run: echo "Skipping unit tests - test:unit script not found in package.json"
448
-
449
- test_integration:
450
- name: 🧪 Run Integration Tests
451
- runs-on: ubuntu-latest
452
- timeout-minutes: 30
453
- if: ${{ !contains(inputs.skip_jobs, 'test:integration') }}
454
-
455
- steps:
456
- - name: 📥 Checkout repository
457
- uses: actions/checkout@v4
458
-
459
- - name: 🔧 Setup Node.js
460
- uses: actions/setup-node@v4
461
- with:
462
- node-version: ${{ inputs.node_version }}
463
- cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
464
-
465
- - name: 🍞 Setup Bun
466
- if: inputs.package_manager == 'bun'
467
- uses: oven-sh/setup-bun@v2
468
- with:
469
- bun-version: '1.3.8'
470
-
471
- - name: 📦 Install dependencies
472
- run: |
473
- if [ "${{ inputs.package_manager }}" = "npm" ]; then
474
- npm ci
475
- elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
476
- yarn install --frozen-lockfile
477
- elif [ "${{ inputs.package_manager }}" = "bun" ]; then
478
- bun install --frozen-lockfile
479
- fi
480
- working-directory: ${{ inputs.working_directory || '.' }}
481
-
482
- - name: 🔍 Check for test:integration script
483
- id: check_script
484
- run: |
485
- if [ -f "package.json" ]; then
486
- if grep -q '"test:integration"' package.json; then
487
- echo "exists=true" >> $GITHUB_OUTPUT
488
- else
489
- echo "exists=false" >> $GITHUB_OUTPUT
490
- fi
491
- else
492
- echo "exists=false" >> $GITHUB_OUTPUT
493
- fi
494
- working-directory: ${{ inputs.working_directory || '.' }}
495
-
496
- - name: 🧪 Run integration tests
497
- if: steps.check_script.outputs.exists == 'true'
498
- run: |
499
- if [ "${{ inputs.package_manager }}" = "npm" ]; then
500
- npm run test:integration
501
- elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
502
- yarn test:integration
503
- elif [ "${{ inputs.package_manager }}" = "bun" ]; then
504
- bun run test:integration
505
- fi
506
- working-directory: ${{ inputs.working_directory || '.' }}
507
-
508
- - name: ⏭️ Skip integration tests (no test:integration script)
509
- if: steps.check_script.outputs.exists == 'false'
510
- run: echo "Skipping integration tests - test:integration script not found in package.json"
511
-
512
- test_e2e:
513
- name: 🧪 Run E2E Tests
514
- runs-on: ubuntu-latest
515
- timeout-minutes: 40
516
- if: ${{ !contains(inputs.skip_jobs, 'test:e2e') }}
517
-
518
- steps:
519
- - name: 📥 Checkout repository
520
- uses: actions/checkout@v4
521
-
522
- - name: 🔧 Setup Node.js
523
- uses: actions/setup-node@v4
524
- with:
525
- node-version: ${{ inputs.node_version }}
526
- cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
527
-
528
- - name: 🍞 Setup Bun
529
- if: inputs.package_manager == 'bun'
530
- uses: oven-sh/setup-bun@v2
531
- with:
532
- bun-version: '1.3.8'
533
-
534
- - name: 📦 Install dependencies
535
- run: |
536
- if [ "${{ inputs.package_manager }}" = "npm" ]; then
537
- npm ci
538
- elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
539
- yarn install --frozen-lockfile
540
- elif [ "${{ inputs.package_manager }}" = "bun" ]; then
541
- bun install --frozen-lockfile
542
- fi
543
- working-directory: ${{ inputs.working_directory || '.' }}
544
-
545
- - name: 🔍 Check for test:e2e script
546
- id: check_script
547
- run: |
548
- if [ -f "package.json" ]; then
549
- if grep -q '"test:e2e"' package.json; then
550
- echo "exists=true" >> $GITHUB_OUTPUT
551
- else
552
- echo "exists=false" >> $GITHUB_OUTPUT
553
- fi
554
- else
555
- echo "exists=false" >> $GITHUB_OUTPUT
556
- fi
557
- working-directory: ${{ inputs.working_directory || '.' }}
558
-
559
- - name: 🧪 Run E2E tests
560
- if: steps.check_script.outputs.exists == 'true'
561
- run: |
562
- if [ "${{ inputs.package_manager }}" = "npm" ]; then
563
- npm run test:e2e
564
- elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
565
- yarn test:e2e
566
- elif [ "${{ inputs.package_manager }}" = "bun" ]; then
567
- bun run test:e2e
568
- fi
569
- working-directory: ${{ inputs.working_directory || '.' }}
570
-
571
- - name: ⏭️ Skip E2E tests (no test:e2e script)
572
- if: steps.check_script.outputs.exists == 'false'
573
- run: echo "Skipping E2E tests - test:e2e script not found in package.json"
574
-
575
- maestro_e2e:
576
- name: 📱 Maestro Cloud E2E Tests
577
- runs-on: ubuntu-latest
578
- timeout-minutes: 30
579
- if: ${{ !contains(inputs.skip_jobs, 'maestro_e2e') }}
580
-
581
- steps:
582
- - name: 📥 Checkout repository
583
- uses: actions/checkout@v4
584
-
585
- - name: 🔍 Check for Maestro API key
586
- id: check_maestro
587
- run: |
588
- if [[ -z "${MAESTRO_API_KEY// }" ]]; then
589
- echo "has_token=false" >> $GITHUB_OUTPUT
590
- echo "⚠️ MAESTRO_API_KEY is not configured"
591
- else
592
- echo "has_token=true" >> $GITHUB_OUTPUT
593
- echo "✅ MAESTRO_API_KEY is available"
594
- fi
595
- env:
596
- MAESTRO_API_KEY: ${{ secrets.MAESTRO_API_KEY }}
597
-
598
- - name: 🔍 Check for project ID
599
- id: check_project_id
600
- if: steps.check_maestro.outputs.has_token == 'true'
601
- run: |
602
- PROJECT_ID="${{ inputs.maestro_project_id }}"
603
- if [[ -z "$PROJECT_ID" ]]; then
604
- echo "has_project_id=false" >> $GITHUB_OUTPUT
605
- echo "⚠️ maestro_project_id input not provided"
606
- else
607
- echo "has_project_id=true" >> $GITHUB_OUTPUT
608
- echo "✅ Project ID configured"
609
- fi
610
-
611
- - name: 🔍 Check for app file
612
- id: check_app_file
613
- if: steps.check_maestro.outputs.has_token == 'true' && steps.check_project_id.outputs.has_project_id == 'true'
614
- run: |
615
- APP_FILE="${{ inputs.maestro_app_file }}"
616
- if [[ -z "$APP_FILE" ]]; then
617
- echo "has_app_file=false" >> $GITHUB_OUTPUT
618
- echo "⚠️ maestro_app_file input not provided"
619
- elif [[ -f "$APP_FILE" ]]; then
620
- echo "has_app_file=true" >> $GITHUB_OUTPUT
621
- echo "✅ App file found: $APP_FILE"
622
- else
623
- echo "has_app_file=false" >> $GITHUB_OUTPUT
624
- echo "⚠️ App file not found at: $APP_FILE"
625
- fi
626
-
627
- - name: 📱 Run Maestro Cloud tests
628
- if: steps.check_maestro.outputs.has_token == 'true' && steps.check_project_id.outputs.has_project_id == 'true' && steps.check_app_file.outputs.has_app_file == 'true'
629
- uses: mobile-dev-inc/action-maestro-cloud@v2.0.1
630
- with:
631
- api-key: ${{ secrets.MAESTRO_API_KEY }}
632
- project-id: ${{ inputs.maestro_project_id }}
633
- app-file: ${{ inputs.maestro_app_file }}
634
- workspace: ${{ inputs.maestro_workspace }}
635
- include-tags: ${{ inputs.maestro_include_tags }}
636
-
637
- - name: 📱 Maestro Tests Skipped (no API key)
638
- if: steps.check_maestro.outputs.has_token != 'true'
639
- run: |
640
- echo "::warning::Maestro Cloud E2E tests skipped - MAESTRO_API_KEY not configured"
641
- echo "To enable Maestro Cloud testing, add MAESTRO_API_KEY to your repository secrets"
642
-
643
- - name: 📱 Maestro Tests Skipped (no project ID)
644
- if: steps.check_maestro.outputs.has_token == 'true' && steps.check_project_id.outputs.has_project_id != 'true'
645
- run: |
646
- echo "::warning::Maestro Cloud E2E tests skipped - maestro_project_id not provided"
647
- echo "To run Maestro tests, provide the maestro_project_id input with your Maestro Cloud project ID"
648
-
649
- - name: 📱 Maestro Tests Skipped (no app file)
650
- if: steps.check_maestro.outputs.has_token == 'true' && steps.check_project_id.outputs.has_project_id == 'true' && steps.check_app_file.outputs.has_app_file != 'true'
651
- run: |
652
- echo "::warning::Maestro Cloud E2E tests skipped - no app file provided"
653
- echo "To run Maestro tests, provide the maestro_app_file input with the path to your app binary"
654
-
655
- playwright_e2e:
656
- name: 🎭 Playwright E2E Tests
657
- runs-on: ubuntu-latest
658
- timeout-minutes: 30
659
- if: ${{ !contains(inputs.skip_jobs, 'playwright_e2e') }}
660
-
661
- steps:
662
- - name: 📥 Checkout repository
663
- uses: actions/checkout@v4
664
-
665
- - name: 🔧 Setup Node.js
666
- uses: actions/setup-node@v4
667
- with:
668
- node-version: ${{ inputs.node_version }}
669
- cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
670
-
671
- - name: 🍞 Setup Bun
672
- if: inputs.package_manager == 'bun'
673
- uses: oven-sh/setup-bun@v2
674
- with:
675
- bun-version: '1.3.8'
676
-
677
- - name: 📦 Install dependencies
678
- run: |
679
- if [ "${{ inputs.package_manager }}" = "npm" ]; then
680
- npm ci
681
- elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
682
- yarn install --frozen-lockfile
683
- elif [ "${{ inputs.package_manager }}" = "bun" ]; then
684
- bun install --frozen-lockfile
685
- fi
686
- working-directory: ${{ inputs.working_directory || '.' }}
687
-
688
- - name: 🔍 Check for Playwright config
689
- id: check_playwright
690
- run: |
691
- if [ -f "playwright.config.ts" ] || [ -f "playwright.config.js" ]; then
692
- echo "has_config=true" >> $GITHUB_OUTPUT
693
- echo "✅ Playwright config found"
694
- else
695
- echo "has_config=false" >> $GITHUB_OUTPUT
696
- echo "⚠️ No Playwright config found"
697
- fi
698
- working-directory: ${{ inputs.working_directory || '.' }}
699
-
700
- - name: 🌐 Build web export
701
- if: steps.check_playwright.outputs.has_config == 'true'
702
- run: npx expo export --platform web
703
- working-directory: ${{ inputs.working_directory || '.' }}
704
-
705
- - name: 🎭 Install Playwright browsers
706
- if: steps.check_playwright.outputs.has_config == 'true'
707
- run: npx playwright install --with-deps
708
- working-directory: ${{ inputs.working_directory || '.' }}
709
-
710
- - name: 🎭 Run Playwright tests
711
- if: steps.check_playwright.outputs.has_config == 'true'
712
- run: npx playwright test
713
- working-directory: ${{ inputs.working_directory || '.' }}
714
-
715
- - name: 📤 Upload Playwright report
716
- if: steps.check_playwright.outputs.has_config == 'true' && always()
717
- uses: actions/upload-artifact@v4
718
- with:
719
- name: playwright-report-${{ github.run_id }}
720
- path: ${{ inputs.working_directory || '.' }}/playwright-report
721
- retention-days: 14
722
-
723
- - name: 🎭 Playwright Tests Skipped (no config)
724
- if: steps.check_playwright.outputs.has_config != 'true'
725
- run: |
726
- echo "::warning::Playwright E2E tests skipped - no playwright.config.ts or playwright.config.js found"
727
- echo "To enable Playwright testing, add a Playwright configuration file to your project"
728
-
729
- format:
730
- name: 📐 Check Formatting
731
- runs-on: ubuntu-latest
732
- timeout-minutes: 10
733
- if: ${{ !contains(inputs.skip_jobs, 'format') }}
734
-
735
- steps:
736
- - name: 📥 Checkout repository
737
- uses: actions/checkout@v4
738
-
739
- - name: 🔧 Setup Node.js
740
- uses: actions/setup-node@v4
741
- with:
742
- node-version: ${{ inputs.node_version }}
743
- cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
744
-
745
- - name: 🍞 Setup Bun
746
- if: inputs.package_manager == 'bun'
747
- uses: oven-sh/setup-bun@v2
748
- with:
749
- bun-version: '1.3.8'
750
-
751
- - name: 📦 Install dependencies
752
- run: |
753
- if [ "${{ inputs.package_manager }}" = "npm" ]; then
754
- npm ci
755
- elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
756
- yarn install --frozen-lockfile
757
- elif [ "${{ inputs.package_manager }}" = "bun" ]; then
758
- bun install --frozen-lockfile
759
- fi
760
- working-directory: ${{ inputs.working_directory || '.' }}
761
-
762
- - name: 📐 Check formatting
763
- run: ${{ inputs.package_manager }} run format:check
764
- env:
765
- NODE_OPTIONS: --max-old-space-size=6144
766
- working-directory: ${{ inputs.working_directory || '.' }}
767
-
768
- build:
769
- name: 🏗️ Build
770
- runs-on: ubuntu-latest
771
- timeout-minutes: 20
772
- if: ${{ !contains(inputs.skip_jobs, 'build') }}
773
-
774
- steps:
775
- - name: 📥 Checkout repository
776
- uses: actions/checkout@v4
777
-
778
- - name: 🔧 Setup Node.js
779
- uses: actions/setup-node@v4
780
- with:
781
- node-version: ${{ inputs.node_version }}
782
- cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
783
-
784
- - name: 🍞 Setup Bun
785
- if: inputs.package_manager == 'bun'
786
- uses: oven-sh/setup-bun@v2
787
- with:
788
- bun-version: '1.3.8'
789
-
790
- - name: 📦 Install dependencies
791
- run: |
792
- if [ "${{ inputs.package_manager }}" = "npm" ]; then
793
- npm ci
794
- elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
795
- yarn install --frozen-lockfile
796
- elif [ "${{ inputs.package_manager }}" = "bun" ]; then
797
- bun install --frozen-lockfile
798
- fi
799
- working-directory: ${{ inputs.working_directory || '.' }}
800
-
801
- - name: 🏗️ Build project
802
- run: ${{ inputs.package_manager }} run build
803
- env:
804
- NODE_OPTIONS: --max-old-space-size=6144
805
- working-directory: ${{ inputs.working_directory || '.' }}
806
-
807
- - name: 📤 Upload build artifacts
808
- uses: actions/upload-artifact@v4
809
- if: success()
810
- with:
811
- name: build-output-${{ github.run_id }}
812
- path: |
813
- dist
814
- build
815
- .next
816
- out
817
- retention-days: 1
818
- if-no-files-found: ignore
819
-
820
- dead_code:
821
- name: 🗑️ Dead Code Detection
822
- runs-on: ubuntu-latest
823
- timeout-minutes: 10
824
- if: ${{ !contains(inputs.skip_jobs, 'dead_code') }}
825
-
826
- steps:
827
- - name: 📥 Checkout repository
828
- uses: actions/checkout@v4
829
-
830
- - name: 🔧 Setup Node.js
831
- uses: actions/setup-node@v4
832
- with:
833
- node-version: ${{ inputs.node_version }}
834
- cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
835
-
836
- - name: 🍞 Setup Bun
837
- if: inputs.package_manager == 'bun'
838
- uses: oven-sh/setup-bun@v2
839
- with:
840
- bun-version: '1.3.8'
841
-
842
- - name: 📦 Install dependencies
843
- run: |
844
- if [ "${{ inputs.package_manager }}" = "npm" ]; then
845
- npm ci
846
- elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
847
- yarn install --frozen-lockfile
848
- elif [ "${{ inputs.package_manager }}" = "bun" ]; then
849
- bun install --frozen-lockfile
850
- fi
851
- working-directory: ${{ inputs.working_directory || '.' }}
852
-
853
- - name: 🗑️ Run dead code detection (knip)
854
- run: ${{ inputs.package_manager }} run knip
855
- working-directory: ${{ inputs.working_directory || '.' }}
856
-
857
- sg_scan:
858
- name: 🔎 AST Grep Scan
859
- runs-on: ubuntu-latest
860
- timeout-minutes: 10
861
- if: ${{ !contains(inputs.skip_jobs, 'sg_scan') }}
862
-
863
- steps:
864
- - name: 📥 Checkout repository
865
- uses: actions/checkout@v4
866
-
867
- - name: 🔧 Setup Node.js
868
- uses: actions/setup-node@v4
869
- with:
870
- node-version: ${{ inputs.node_version }}
871
- cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
872
-
873
- - name: 🍞 Setup Bun
874
- if: inputs.package_manager == 'bun'
875
- uses: oven-sh/setup-bun@v2
876
- with:
877
- bun-version: '1.3.8'
878
-
879
- - name: 📦 Install dependencies
880
- run: |
881
- if [ "${{ inputs.package_manager }}" = "npm" ]; then
882
- npm ci
883
- elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
884
- yarn install --frozen-lockfile
885
- elif [ "${{ inputs.package_manager }}" = "bun" ]; then
886
- bun install --frozen-lockfile
887
- fi
888
- working-directory: ${{ inputs.working_directory || '.' }}
889
-
890
- - name: 🔍 Check for sgconfig.yml
891
- id: check_config
892
- run: |
893
- if [ -f "sgconfig.yml" ]; then
894
- echo "has_config=true" >> $GITHUB_OUTPUT
895
- else
896
- echo "has_config=false" >> $GITHUB_OUTPUT
897
- fi
898
- working-directory: ${{ inputs.working_directory || '.' }}
899
-
900
- - name: 🔍 Check for ast-grep rules
901
- id: check_rules
902
- if: steps.check_config.outputs.has_config == 'true'
903
- run: |
904
- RULES_DIR="ast-grep/rules"
905
- if [ -d "$RULES_DIR" ]; then
906
- RULE_COUNT=$(find "$RULES_DIR" -name "*.yml" -o -name "*.yaml" 2>/dev/null | grep -v ".gitkeep" | wc -l | tr -d ' ')
907
- if [ "$RULE_COUNT" -gt 0 ]; then
908
- echo "has_rules=true" >> $GITHUB_OUTPUT
909
- echo "rule_count=$RULE_COUNT" >> $GITHUB_OUTPUT
910
- else
911
- echo "has_rules=false" >> $GITHUB_OUTPUT
912
- fi
913
- else
914
- echo "has_rules=false" >> $GITHUB_OUTPUT
915
- fi
916
- working-directory: ${{ inputs.working_directory || '.' }}
917
-
918
- - name: 🔎 Run ast-grep scan
919
- if: steps.check_config.outputs.has_config == 'true' && steps.check_rules.outputs.has_rules == 'true'
920
- run: ${{ inputs.package_manager }} run sg:scan
921
- working-directory: ${{ inputs.working_directory || '.' }}
922
-
923
- - name: ⏭️ AST Grep Skipped (no config)
924
- if: steps.check_config.outputs.has_config != 'true'
925
- run: |
926
- echo "::warning::ast-grep scan skipped - no sgconfig.yml found"
927
- echo "To enable ast-grep scanning, add sgconfig.yml to your project root"
928
-
929
- - name: ⏭️ AST Grep Skipped (no rules)
930
- if: steps.check_config.outputs.has_config == 'true' && steps.check_rules.outputs.has_rules != 'true'
931
- run: |
932
- echo "::warning::ast-grep scan skipped - no rules defined in ast-grep/rules/"
933
- echo "To enable ast-grep scanning, add rule YAML files to ast-grep/rules/"
934
-
935
- npm_security_scan:
936
- name: 🔒 Security Scan
937
- runs-on: ubuntu-latest
938
- timeout-minutes: 15
939
- if: ${{ !contains(inputs.skip_jobs, 'npm_security_scan') }}
940
- steps:
941
- - name: 📥 Checkout repository
942
- uses: actions/checkout@v4
943
-
944
- - name: 🔧 Setup Node.js
945
- uses: actions/setup-node@v4
946
- with:
947
- node-version: ${{ inputs.node_version }}
948
- cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
949
-
950
- - name: 🍞 Setup Bun
951
- if: inputs.package_manager == 'bun'
952
- uses: oven-sh/setup-bun@v2
953
- with:
954
- bun-version: '1.3.8'
955
-
956
- - name: 📦 Install dependencies
957
- run: |
958
- if [ "${{ inputs.package_manager }}" = "npm" ]; then
959
- npm ci
960
- elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
961
- yarn install --frozen-lockfile
962
- elif [ "${{ inputs.package_manager }}" = "bun" ]; then
963
- bun install --frozen-lockfile
964
- fi
965
- working-directory: ${{ inputs.working_directory || '.' }}
966
-
967
- - name: 📋 Load audit exclusions
968
- id: audit_exclusions
969
- run: |
970
- GHSA_IDS=""
971
- CVE_IDS=""
972
- for config_file in audit.ignore.config.json audit.ignore.local.json; do
973
- if [ -f "$config_file" ]; then
974
- FILE_IDS=$(jq -r '.exclusions[].id' "$config_file" 2>/dev/null)
975
- if [ -n "$FILE_IDS" ]; then
976
- GHSA_IDS="$GHSA_IDS $FILE_IDS"
977
- fi
978
- FILE_CVES=$(jq -r '.exclusions[] | select(.cve != null) | .cve' "$config_file" 2>/dev/null)
979
- if [ -n "$FILE_CVES" ]; then
980
- CVE_IDS="$CVE_IDS $FILE_CVES"
981
- fi
982
- fi
983
- done
984
- GHSA_IDS=$(echo "$GHSA_IDS" | tr ' ' '\n' | sort -u | grep -v '^$' | tr '\n' ' ')
985
- CVE_IDS=$(echo "$CVE_IDS" | tr ' ' '\n' | sort -u | grep -v '^$' | tr '\n' ' ')
986
- echo "ghsa_ids=$GHSA_IDS" >> $GITHUB_OUTPUT
987
- echo "cve_ids=$CVE_IDS" >> $GITHUB_OUTPUT
988
- echo "Loaded GHSA exclusions: $GHSA_IDS"
989
- echo "Loaded CVE exclusions: $CVE_IDS"
990
- working-directory: ${{ inputs.working_directory || '.' }}
991
-
992
- - name: 🔒 Run security audit
993
- run: |
994
- GHSA_IDS="${{ steps.audit_exclusions.outputs.ghsa_ids }}"
995
- CVE_IDS="${{ steps.audit_exclusions.outputs.cve_ids }}"
996
-
997
- if [ "${{ inputs.package_manager }}" = "npm" ]; then
998
- # Build jq exclusion filter for npm audit GHSA IDs
999
- NPM_EXCLUDE_FILTER=""
1000
- for _id in $GHSA_IDS; do
1001
- if [ -n "$NPM_EXCLUDE_FILTER" ]; then
1002
- NPM_EXCLUDE_FILTER="$NPM_EXCLUDE_FILTER or . == \"$_id\""
1003
- else
1004
- NPM_EXCLUDE_FILTER=". == \"$_id\""
1005
- fi
1006
- done
1007
-
1008
- AUDIT_JSON=$(npm audit --production --json 2>/dev/null || true)
1009
- if [ -n "$NPM_EXCLUDE_FILTER" ]; then
1010
- UNFIXED_HIGH=$(echo "$AUDIT_JSON" | jq "[.vulnerabilities | to_entries[] | select(.value.severity == \"high\" or .value.severity == \"critical\") | .value.via[] | select(type == \"object\") | .url | ltrimstr(\"https://github.com/advisories/\")] | unique | map(select($NPM_EXCLUDE_FILTER | not)) | length")
1011
- else
1012
- UNFIXED_HIGH=$(echo "$AUDIT_JSON" | jq '[.vulnerabilities | to_entries[] | select(.value.severity == "high" or .value.severity == "critical") | .value.via[] | select(type == "object") | .url | ltrimstr("https://github.com/advisories/")] | unique | length')
1013
- fi
1014
- if [ "$UNFIXED_HIGH" -gt 0 ]; then
1015
- echo "::warning::Found high or critical vulnerabilities (after excluding known false positives)"
1016
- npm audit --production --audit-level=high || true
1017
- exit 1
1018
- fi
1019
- echo "::notice::No high or critical vulnerabilities found (excluding known false positives)"
1020
- elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
1021
- # Build jq filter for GHSA IDs
1022
- GHSA_FILTER=""
1023
- for _id in $GHSA_IDS; do
1024
- if [ -n "$GHSA_FILTER" ]; then
1025
- GHSA_FILTER="$GHSA_FILTER or .data.advisory.github_advisory_id == \"$_id\""
1026
- else
1027
- GHSA_FILTER=".data.advisory.github_advisory_id == \"$_id\""
1028
- fi
1029
- done
1030
-
1031
- # Build jq filter for CVE IDs
1032
- CVE_FILTER=""
1033
- for _cve in $CVE_IDS; do
1034
- if [ -n "$CVE_FILTER" ]; then
1035
- CVE_FILTER="$CVE_FILTER or . == \"$_cve\""
1036
- else
1037
- CVE_FILTER=". == \"$_cve\""
1038
- fi
1039
- done
1040
-
1041
- # Combine GHSA and CVE filters
1042
- COMBINED_FILTER=""
1043
- if [ -n "$GHSA_FILTER" ] && [ -n "$CVE_FILTER" ]; then
1044
- COMBINED_FILTER="($GHSA_FILTER or (.data.advisory.cves | any($CVE_FILTER)))"
1045
- elif [ -n "$GHSA_FILTER" ]; then
1046
- COMBINED_FILTER="($GHSA_FILTER)"
1047
- elif [ -n "$CVE_FILTER" ]; then
1048
- COMBINED_FILTER="((.data.advisory.cves | any($CVE_FILTER)))"
1049
- fi
1050
-
1051
- if [ -n "$COMBINED_FILTER" ]; then
1052
- yarn audit --groups dependencies --json | jq -r "select(.type == \"auditAdvisory\") | select(.data.advisory.severity == \"high\" or .data.advisory.severity == \"critical\") | select(($COMBINED_FILTER) | not) | .data.advisory" > high_vulns.json
1053
- else
1054
- yarn audit --groups dependencies --json | jq -r 'select(.type == "auditAdvisory") | select(.data.advisory.severity == "high" or .data.advisory.severity == "critical") | .data.advisory' > high_vulns.json
1055
- fi
1056
-
1057
- if [ -s high_vulns.json ]; then
1058
- echo "::error::Found high or critical vulnerabilities:"
1059
- cat high_vulns.json
1060
- exit 1
1061
- else
1062
- echo "::notice::No high or critical vulnerabilities found (excluding known false positives)"
1063
- fi
1064
- elif [ "${{ inputs.package_manager }}" = "bun" ]; then
1065
- # Build --ignore flags dynamically from exclusion list
1066
- BUN_IGNORE_FLAGS=""
1067
- for _id in $GHSA_IDS; do
1068
- BUN_IGNORE_FLAGS="$BUN_IGNORE_FLAGS --ignore $_id"
1069
- done
1070
-
1071
- if ! bun audit --audit-level=high $BUN_IGNORE_FLAGS; then
1072
- echo "::warning::Found high or critical vulnerabilities"
1073
- exit 1
1074
- fi
1075
- echo "::notice::No high or critical vulnerabilities found"
1076
- else
1077
- echo "Unsupported package manager: ${{ inputs.package_manager }}"
1078
- exit 1
1079
- fi
1080
- working-directory: ${{ inputs.working_directory || '.' }}
1081
-
1082
- sonarcloud:
1083
- name: 🔍 SonarCloud SAST
1084
- runs-on: ubuntu-latest
1085
- timeout-minutes: 20
1086
- if: ${{ !contains(inputs.skip_jobs, 'sonarcloud') }}
1087
- steps:
1088
- - name: 📥 Checkout repository
1089
- uses: actions/checkout@v4
1090
- with:
1091
- fetch-depth: 0 # Full depth for proper analysis
1092
-
1093
- - name: 🔍 Check for SonarCloud token
1094
- id: check_sonar
1095
- run: |
1096
- # Debug: Show if env var is set (will show *** if present)
1097
- echo "SONAR_TOKEN env var: '${SONAR_TOKEN:+SET}'"
1098
- echo "SONAR_TOKEN length: ${#SONAR_TOKEN}"
1099
-
1100
- # More robust check - empty, unset, or just whitespace
1101
- if [[ -z "${SONAR_TOKEN// }" ]]; then
1102
- echo "has_token=false" >> $GITHUB_OUTPUT
1103
- echo "⚠️ SONAR_TOKEN is not configured"
1104
- else
1105
- echo "has_token=true" >> $GITHUB_OUTPUT
1106
- echo "✅ SONAR_TOKEN is available"
1107
- fi
1108
- env:
1109
- SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
1110
-
1111
- - name: 📊 SonarCloud Scan
1112
- if: steps.check_sonar.outputs.has_token == 'true'
1113
- id: sonar_scan
1114
- uses: SonarSource/sonarqube-scan-action@v6.0.0
1115
- env:
1116
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
1117
- SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
1118
- with:
1119
- args: -Dsonar.qualitygate.wait=true
1120
- continue-on-error: true
1121
-
1122
- - name: 🔍 Validate SonarCloud results
1123
- if: steps.check_sonar.outputs.has_token == 'true'
1124
- env:
1125
- SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
1126
- run: |
1127
- OUTCOME="${{ steps.sonar_scan.outcome }}"
1128
-
1129
- if [ "$OUTCOME" = "success" ]; then
1130
- echo "✅ SonarCloud scan and quality gate passed"
1131
- exit 0
1132
- fi
1133
-
1134
- if [ "$OUTCOME" = "failure" ]; then
1135
- REPORT_FILE=".scannerwork/report-task.txt"
1136
-
1137
- if [ ! -f "$REPORT_FILE" ]; then
1138
- echo "::error::SonarCloud scan failed and report-task.txt is missing; failing build."
1139
- exit 1
1140
- fi
1141
-
1142
- # Extract serverUrl and ceTaskId from report-task.txt (key=value format)
1143
- SERVER_URL=$(grep -E '^serverUrl=' "$REPORT_FILE" | cut -d= -f2-)
1144
- CE_TASK_ID=$(grep -E '^ceTaskId=' "$REPORT_FILE" | cut -d= -f2-)
1145
-
1146
- if [ -z "$SERVER_URL" ] || [ -z "$CE_TASK_ID" ]; then
1147
- echo "::error::Could not extract serverUrl or ceTaskId from report-task.txt; failing build."
1148
- exit 1
1149
- fi
1150
-
1151
- # Query SonarCloud CE task API to get the actual error message
1152
- TASK_JSON=$(curl -s -u "$SONAR_TOKEN:" "$SERVER_URL/api/ce/task?id=$CE_TASK_ID")
1153
- ERROR_MSG=$(echo "$TASK_JSON" | jq -r '.task.errorMessage // ""')
1154
-
1155
- # Only allow line-limit quota errors to pass
1156
- if echo "$ERROR_MSG" | grep -qi "maximum allowed lines limit"; then
1157
- echo "::warning::SonarCloud scan failed due to organization line-limit quota; not blocking build."
1158
- echo "ℹ️ Contact your SonarCloud organization admin to increase or manage line limits."
1159
- exit 0
1160
- fi
1161
-
1162
- echo "::error::SonarCloud scan failed: $ERROR_MSG"
1163
- exit 1
1164
- fi
1165
-
1166
- - name: 📊 SonarCloud Scan Skipped
1167
- if: steps.check_sonar.outputs.has_token != 'true'
1168
- run: |
1169
- echo "::warning::SonarCloud analysis skipped - SONAR_TOKEN not configured"
1170
- echo "To enable SonarCloud analysis, add SONAR_TOKEN to your repository secrets"
1171
-
1172
- snyk:
1173
- name: 🛡️ Snyk Dependency Scan
1174
- runs-on: ubuntu-latest
1175
- timeout-minutes: 20
1176
- if: ${{ !contains(inputs.skip_jobs, 'snyk') }}
1177
- steps:
1178
- - name: 📥 Checkout repository
1179
- uses: actions/checkout@v4
1180
-
1181
- - name: 🔍 Check for Snyk token
1182
- id: check_snyk
1183
- run: |
1184
- if [[ -z "${SNYK_TOKEN// }" ]]; then
1185
- echo "has_token=false" >> $GITHUB_OUTPUT
1186
- echo "⚠️ SNYK_TOKEN is not configured"
1187
- else
1188
- echo "has_token=true" >> $GITHUB_OUTPUT
1189
- echo "✅ SNYK_TOKEN is available"
1190
- fi
1191
- env:
1192
- SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
1193
-
1194
- - name: 🔧 Setup Node.js
1195
- if: steps.check_snyk.outputs.has_token == 'true'
1196
- uses: actions/setup-node@v4
1197
- with:
1198
- node-version: ${{ inputs.node_version }}
1199
- cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
1200
-
1201
- - name: 🍞 Setup Bun
1202
- if: inputs.package_manager == 'bun'
1203
- uses: oven-sh/setup-bun@v2
1204
- with:
1205
- bun-version: '1.3.8'
1206
-
1207
- - name: 📦 Install dependencies
1208
- if: steps.check_snyk.outputs.has_token == 'true'
1209
- run: |
1210
- if [ "${{ inputs.package_manager }}" = "npm" ]; then
1211
- npm ci
1212
- elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
1213
- yarn install --frozen-lockfile
1214
- elif [ "${{ inputs.package_manager }}" = "bun" ]; then
1215
- bun install --frozen-lockfile
1216
- fi
1217
- working-directory: ${{ inputs.working_directory || '.' }}
1218
-
1219
- - name: 🛡️ Run Snyk to check for vulnerabilities
1220
- if: steps.check_snyk.outputs.has_token == 'true'
1221
- uses: snyk/actions/node@master
1222
- env:
1223
- SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
1224
- with:
1225
- args: --severity-threshold=high --all-projects
1226
-
1227
- - name: 📤 Upload Snyk results
1228
- if: steps.check_snyk.outputs.has_token == 'true' && always()
1229
- uses: actions/upload-artifact@v4
1230
- with:
1231
- name: snyk-results
1232
- path: snyk-results.json
1233
- retention-days: 14
1234
-
1235
- - name: 🛡️ Snyk Scan Skipped
1236
- if: steps.check_snyk.outputs.has_token != 'true'
1237
- run: |
1238
- echo "::warning::Snyk dependency scan skipped - SNYK_TOKEN not configured"
1239
- echo "To enable Snyk vulnerability scanning, add SNYK_TOKEN to your repository secrets"
1240
-
1241
- secret_scanning:
1242
- name: 🔐 GitGuardian Secret Detection
1243
- runs-on: ubuntu-latest
1244
- timeout-minutes: 20
1245
- if: ${{ !contains(inputs.skip_jobs, 'secret_scanning') }}
1246
- steps:
1247
- - name: 📥 Checkout repository
1248
- uses: actions/checkout@v4
1249
- with:
1250
- fetch-depth: 0 # Full depth for scanning history
1251
-
1252
- - name: 🔍 Check for GitGuardian API key
1253
- id: check_gitguardian
1254
- run: |
1255
- if [[ -z "${GITGUARDIAN_API_KEY// }" ]]; then
1256
- echo "has_token=false" >> $GITHUB_OUTPUT
1257
- echo "⚠️ GITGUARDIAN_API_KEY is not configured"
1258
- else
1259
- echo "has_token=true" >> $GITHUB_OUTPUT
1260
- echo "✅ GITGUARDIAN_API_KEY is available"
1261
- fi
1262
- env:
1263
- GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}
1264
-
1265
- - name: 🔐 GitGuardian scan
1266
- if: steps.check_gitguardian.outputs.has_token == 'true'
1267
- uses: GitGuardian/ggshield-action@master
1268
- env:
1269
- GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}
1270
- with:
1271
- args: --show-secrets --all-policies
1272
-
1273
- - name: 🔐 GitGuardian Scan Skipped
1274
- if: steps.check_gitguardian.outputs.has_token != 'true'
1275
- run: |
1276
- echo "::warning::GitGuardian secret detection skipped - GITGUARDIAN_API_KEY not configured"
1277
- echo "To enable secret detection, add GITGUARDIAN_API_KEY to your repository secrets"
1278
-
1279
- license_compliance:
1280
- name: 📜 FOSSA License Check
1281
- runs-on: ubuntu-latest
1282
- timeout-minutes: 20
1283
- if: ${{ !contains(inputs.skip_jobs, 'license_compliance') }}
1284
- steps:
1285
- - name: 📥 Checkout repository
1286
- uses: actions/checkout@v4
1287
-
1288
- - name: 🔍 Check for FOSSA API key
1289
- id: check_fossa
1290
- run: |
1291
- if [[ -z "${FOSSA_API_KEY// }" ]]; then
1292
- echo "has_token=false" >> $GITHUB_OUTPUT
1293
- echo "⚠️ FOSSA_API_KEY is not configured"
1294
- else
1295
- echo "has_token=true" >> $GITHUB_OUTPUT
1296
- echo "✅ FOSSA_API_KEY is available"
1297
- fi
1298
- env:
1299
- FOSSA_API_KEY: ${{ secrets.FOSSA_API_KEY }}
1300
-
1301
- - name: 📜 Run FOSSA license scan
1302
- if: steps.check_fossa.outputs.has_token == 'true'
1303
- uses: fossas/fossa-action@main
1304
- with:
1305
- api-key: ${{ secrets.FOSSA_API_KEY }}
1306
- # Optional: specify the team
1307
- # team: 'your-team-name'
1308
-
1309
- - name: 📜 FOSSA Scan Skipped
1310
- if: steps.check_fossa.outputs.has_token != 'true'
1311
- run: |
1312
- echo "::warning::FOSSA license compliance check skipped - FOSSA_API_KEY not configured"
1313
- echo "To enable license compliance checking, add FOSSA_API_KEY to your repository secrets"
1314
-
1315
- zap_baseline:
1316
- name: 🕷️ OWASP ZAP Baseline
1317
- runs-on: ubuntu-latest
1318
- timeout-minutes: 20
1319
- if: ${{ !contains(inputs.skip_jobs, 'zap_baseline') && inputs.zap_target_url != '' }}
1320
- steps:
1321
- - name: 📥 Checkout repository
1322
- uses: actions/checkout@v4
1323
-
1324
- - name: 🔧 Setup Node.js
1325
- uses: actions/setup-node@v4
1326
- with:
1327
- node-version: ${{ inputs.node_version }}
1328
- cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
1329
-
1330
- - name: 🍞 Setup Bun
1331
- if: inputs.package_manager == 'bun'
1332
- uses: oven-sh/setup-bun@v2
1333
- with:
1334
- bun-version: '1.3.8'
1335
-
1336
- - name: 📦 Install dependencies
1337
- run: |
1338
- if [ "${{ inputs.package_manager }}" = "npm" ]; then
1339
- npm ci
1340
- elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
1341
- yarn install --frozen-lockfile
1342
- elif [ "${{ inputs.package_manager }}" = "bun" ]; then
1343
- bun install --frozen-lockfile
1344
- fi
1345
- working-directory: ${{ inputs.working_directory || '.' }}
1346
-
1347
- - name: 🔍 Check for ZAP rules file
1348
- id: check_rules
1349
- run: |
1350
- if [ -f "${{ inputs.zap_rules_file }}" ]; then
1351
- echo "has_rules=true" >> $GITHUB_OUTPUT
1352
- else
1353
- echo "has_rules=false" >> $GITHUB_OUTPUT
1354
- fi
1355
- working-directory: ${{ inputs.working_directory || '.' }}
1356
-
1357
- - name: 🕷️ Run ZAP baseline scan
1358
- uses: zaproxy/action-baseline@v0.14.0
1359
- with:
1360
- target: ${{ inputs.zap_target_url }}
1361
- rules_file_name: ${{ steps.check_rules.outputs.has_rules == 'true' && inputs.zap_rules_file || '' }}
1362
- fail_action: true
1363
- allow_issue_writing: false
1364
- artifact_name: 'zap-report'
1365
-
1366
- - name: 📤 Upload ZAP report
1367
- if: always()
1368
- uses: actions/upload-artifact@v4
1369
- with:
1370
- name: zap-baseline-report-${{ github.run_id }}
1371
- path: |
1372
- zap-report.html
1373
- zap-report.json
1374
- zap-report.md
1375
- retention-days: 14
1376
-
1377
- # Enterprise security tools summary
1378
- security_tools_summary:
1379
- name: 🔒 Security Tools Summary
1380
- runs-on: ubuntu-latest
1381
- if: always() && (needs.sonarcloud.result != 'skipped' || needs.snyk.result != 'skipped' || needs.secret_scanning.result != 'skipped' || needs.license_compliance.result != 'skipped' || needs.zap_baseline.result != 'skipped')
1382
- needs: [sonarcloud, snyk, secret_scanning, license_compliance, zap_baseline]
1383
- steps:
1384
- - name: 📝 Generate security tools summary
1385
- run: |
1386
- echo "# 🔒 Enterprise Security Tools Summary" >> $GITHUB_STEP_SUMMARY
1387
- echo "" >> $GITHUB_STEP_SUMMARY
1388
- echo "## Security Scan Results" >> $GITHUB_STEP_SUMMARY
1389
- echo "" >> $GITHUB_STEP_SUMMARY
1390
-
1391
- # SonarCloud SAST status
1392
- if [ "${{ needs.sonarcloud.result }}" == "skipped" ]; then
1393
- echo "- 🔍 **SonarCloud SAST**: ⏭️ Skipped (no token)" >> $GITHUB_STEP_SUMMARY
1394
- elif [ "${{ needs.sonarcloud.result }}" == "success" ]; then
1395
- echo "- 🔍 **SonarCloud SAST**: ✅ Passed" >> $GITHUB_STEP_SUMMARY
1396
- else
1397
- echo "- 🔍 **SonarCloud SAST**: ❌ Failed" >> $GITHUB_STEP_SUMMARY
1398
- fi
1399
-
1400
- # Snyk Dependency Scan status
1401
- if [ "${{ needs.snyk.result }}" == "skipped" ]; then
1402
- echo "- 🛡️ **Snyk Dependency Scan**: ⏭️ Skipped (no token)" >> $GITHUB_STEP_SUMMARY
1403
- elif [ "${{ needs.snyk.result }}" == "success" ]; then
1404
- echo "- 🛡️ **Snyk Dependency Scan**: ✅ Passed" >> $GITHUB_STEP_SUMMARY
1405
- else
1406
- echo "- 🛡️ **Snyk Dependency Scan**: ❌ Failed" >> $GITHUB_STEP_SUMMARY
1407
- fi
1408
-
1409
- # GitGuardian Secret Detection status
1410
- if [ "${{ needs.secret_scanning.result }}" == "skipped" ]; then
1411
- echo "- 🔐 **GitGuardian Secret Detection**: ⏭️ Skipped (no token)" >> $GITHUB_STEP_SUMMARY
1412
- elif [ "${{ needs.secret_scanning.result }}" == "success" ]; then
1413
- echo "- 🔐 **GitGuardian Secret Detection**: ✅ Passed" >> $GITHUB_STEP_SUMMARY
1414
- else
1415
- echo "- 🔐 **GitGuardian Secret Detection**: ❌ Failed" >> $GITHUB_STEP_SUMMARY
1416
- fi
1417
-
1418
- # FOSSA License Compliance status
1419
- if [ "${{ needs.license_compliance.result }}" == "skipped" ]; then
1420
- echo "- 📜 **FOSSA License Compliance**: ⏭️ Skipped (no token)" >> $GITHUB_STEP_SUMMARY
1421
- elif [ "${{ needs.license_compliance.result }}" == "success" ]; then
1422
- echo "- 📜 **FOSSA License Compliance**: ✅ Passed" >> $GITHUB_STEP_SUMMARY
1423
- else
1424
- echo "- 📜 **FOSSA License Compliance**: ❌ Failed" >> $GITHUB_STEP_SUMMARY
1425
- fi
1426
-
1427
- # OWASP ZAP Baseline status
1428
- if [ "${{ needs.zap_baseline.result }}" == "skipped" ]; then
1429
- echo "- 🕷️ **OWASP ZAP Baseline**: ⏭️ Skipped (no target URL)" >> $GITHUB_STEP_SUMMARY
1430
- elif [ "${{ needs.zap_baseline.result }}" == "success" ]; then
1431
- echo "- 🕷️ **OWASP ZAP Baseline**: ✅ Passed" >> $GITHUB_STEP_SUMMARY
1432
- else
1433
- echo "- 🕷️ **OWASP ZAP Baseline**: ❌ Failed" >> $GITHUB_STEP_SUMMARY
1434
- fi
1435
-
1436
- echo "" >> $GITHUB_STEP_SUMMARY
1437
- echo "## 📊 Security Posture" >> $GITHUB_STEP_SUMMARY
1438
- echo "" >> $GITHUB_STEP_SUMMARY
1439
-
1440
- # Count active tools
1441
- ACTIVE_TOOLS=0
1442
- PASSED_TOOLS=0
1443
-
1444
- if [ "${{ needs.sonarcloud.result }}" != "skipped" ]; then
1445
- ACTIVE_TOOLS=$((ACTIVE_TOOLS + 1))
1446
- if [ "${{ needs.sonarcloud.result }}" == "success" ]; then
1447
- PASSED_TOOLS=$((PASSED_TOOLS + 1))
1448
- fi
1449
- fi
1450
-
1451
- if [ "${{ needs.snyk.result }}" != "skipped" ]; then
1452
- ACTIVE_TOOLS=$((ACTIVE_TOOLS + 1))
1453
- if [ "${{ needs.snyk.result }}" == "success" ]; then
1454
- PASSED_TOOLS=$((PASSED_TOOLS + 1))
1455
- fi
1456
- fi
1457
-
1458
- if [ "${{ needs.secret_scanning.result }}" != "skipped" ]; then
1459
- ACTIVE_TOOLS=$((ACTIVE_TOOLS + 1))
1460
- if [ "${{ needs.secret_scanning.result }}" == "success" ]; then
1461
- PASSED_TOOLS=$((PASSED_TOOLS + 1))
1462
- fi
1463
- fi
1464
-
1465
- if [ "${{ needs.license_compliance.result }}" != "skipped" ]; then
1466
- ACTIVE_TOOLS=$((ACTIVE_TOOLS + 1))
1467
- if [ "${{ needs.license_compliance.result }}" == "success" ]; then
1468
- PASSED_TOOLS=$((PASSED_TOOLS + 1))
1469
- fi
1470
- fi
1471
-
1472
- if [ "${{ needs.zap_baseline.result }}" != "skipped" ]; then
1473
- ACTIVE_TOOLS=$((ACTIVE_TOOLS + 1))
1474
- if [ "${{ needs.zap_baseline.result }}" == "success" ]; then
1475
- PASSED_TOOLS=$((PASSED_TOOLS + 1))
1476
- fi
1477
- fi
1478
-
1479
- echo "- **Active Security Tools**: $ACTIVE_TOOLS / 5" >> $GITHUB_STEP_SUMMARY
1480
- echo "- **Passed Checks**: $PASSED_TOOLS / $ACTIVE_TOOLS" >> $GITHUB_STEP_SUMMARY
1481
-
1482
- if [ $ACTIVE_TOOLS -gt 0 ]; then
1483
- SCORE=$((PASSED_TOOLS * 100 / ACTIVE_TOOLS))
1484
- echo "- **Security Score**: $SCORE%" >> $GITHUB_STEP_SUMMARY
1485
- fi
1486
-
1487
- echo "" >> $GITHUB_STEP_SUMMARY
1488
- echo "## 🔧 Configuration" >> $GITHUB_STEP_SUMMARY
1489
- echo "" >> $GITHUB_STEP_SUMMARY
1490
- echo "To enable additional security tools, add the following secrets to your repository:" >> $GITHUB_STEP_SUMMARY
1491
- echo "- \`SONAR_TOKEN\` - [Get from SonarCloud](https://sonarcloud.io/)" >> $GITHUB_STEP_SUMMARY
1492
- echo "- \`SNYK_TOKEN\` - [Get from Snyk](https://app.snyk.io/)" >> $GITHUB_STEP_SUMMARY
1493
- echo "- \`GITGUARDIAN_API_KEY\` - [Get from GitGuardian](https://dashboard.gitguardian.com/)" >> $GITHUB_STEP_SUMMARY
1494
- echo "- \`FOSSA_API_KEY\` - [Get from FOSSA](https://app.fossa.com/)" >> $GITHUB_STEP_SUMMARY
1495
-
1496
- # Overall status
1497
- echo "" >> $GITHUB_STEP_SUMMARY
1498
- echo "## 🎯 Overall Status" >> $GITHUB_STEP_SUMMARY
1499
- echo "" >> $GITHUB_STEP_SUMMARY
1500
-
1501
- if [ $ACTIVE_TOOLS -eq 0 ]; then
1502
- echo "⚠️ **No security tools are active.** Configure tokens to enable security scanning." >> $GITHUB_STEP_SUMMARY
1503
- elif [ $PASSED_TOOLS -eq $ACTIVE_TOOLS ]; then
1504
- echo "✅ **All active security tools passed!**" >> $GITHUB_STEP_SUMMARY
1505
- else
1506
- echo "❌ **Some security tools failed.** Review the results above and address any issues." >> $GITHUB_STEP_SUMMARY
1507
- fi
1508
-
1509
- # Compliance validation job
1510
- compliance_validation:
1511
- name: ✅ Compliance Validation
1512
- runs-on: ubuntu-latest
1513
- if: ${{ inputs.compliance_framework != 'none' }}
1514
- needs:
1515
- [
1516
- lint,
1517
- typecheck,
1518
- test,
1519
- test_unit,
1520
- test_integration,
1521
- test_e2e,
1522
- maestro_e2e,
1523
- playwright_e2e,
1524
- format,
1525
- build,
1526
- dead_code,
1527
- sg_scan,
1528
- npm_security_scan,
1529
- sonarcloud,
1530
- snyk,
1531
- secret_scanning,
1532
- license_compliance,
1533
- zap_baseline,
1534
- ]
1535
- steps:
1536
- - name: 📋 Validate compliance framework
1537
- id: validate_framework
1538
- run: |
1539
- echo "🏢 Validating compliance for framework: ${{ inputs.compliance_framework }}"
1540
- echo "" >> $GITHUB_STEP_SUMMARY
1541
- echo "# 🏢 Compliance Validation Report" >> $GITHUB_STEP_SUMMARY
1542
- echo "" >> $GITHUB_STEP_SUMMARY
1543
- echo "**Framework**: ${{ inputs.compliance_framework }}" >> $GITHUB_STEP_SUMMARY
1544
- echo "**Date**: $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_STEP_SUMMARY
1545
- echo "**Run ID**: ${{ github.run_id }}" >> $GITHUB_STEP_SUMMARY
1546
- echo "" >> $GITHUB_STEP_SUMMARY
1547
-
1548
- - name: 🔍 SOC 2 Control Validation
1549
- if: contains(inputs.compliance_framework, 'soc2')
1550
- run: |
1551
- echo "## SOC 2 Type II Controls" >> $GITHUB_STEP_SUMMARY
1552
- echo "" >> $GITHUB_STEP_SUMMARY
1553
-
1554
- # CC6.1 - Logical and Physical Access Controls
1555
- if [ "${{ needs.secret_scanning.result }}" == "success" ] || [ "${{ needs.secret_scanning.result }}" == "skipped" ]; then
1556
- echo "✅ **CC6.1**: No hardcoded credentials detected" >> $GITHUB_STEP_SUMMARY
1557
- else
1558
- echo "❌ **CC6.1**: Secret scanning failed - potential credential exposure" >> $GITHUB_STEP_SUMMARY
1559
- fi
1560
-
1561
- # CC7.1 - System Operations
1562
- if [ "${{ needs.npm_security_scan.result }}" == "success" ] && [ "${{ needs.snyk.result }}" == "success" ]; then
1563
- echo "✅ **CC7.1**: Vulnerability management controls validated" >> $GITHUB_STEP_SUMMARY
1564
- else
1565
- echo "⚠️ **CC7.1**: Security scanning incomplete - manual review required" >> $GITHUB_STEP_SUMMARY
1566
- fi
1567
-
1568
- # CC7.2 - System Monitoring
1569
- echo "✅ **CC7.2**: Audit logging enabled (retention: ${{ inputs.audit_retention_days }} days)" >> $GITHUB_STEP_SUMMARY
1570
-
1571
- # CC8.1 - Change Management
1572
- if [ "${{ github.event_name }}" == "pull_request" ]; then
1573
- echo "✅ **CC8.1**: Change management process followed (PR #${{ github.event.pull_request.number }})" >> $GITHUB_STEP_SUMMARY
1574
- else
1575
- echo "⚠️ **CC8.1**: Direct push to branch - review change management policy" >> $GITHUB_STEP_SUMMARY
1576
- fi
1577
- echo "" >> $GITHUB_STEP_SUMMARY
1578
-
1579
- - name: 🔍 ISO 27001 Control Validation
1580
- if: contains(inputs.compliance_framework, 'iso27001')
1581
- run: |
1582
- echo "## ISO 27001:2022 Controls" >> $GITHUB_STEP_SUMMARY
1583
- echo "" >> $GITHUB_STEP_SUMMARY
1584
-
1585
- # A.8.1 - Asset Management
1586
- if [ "${{ needs.license_compliance.result }}" == "success" ]; then
1587
- echo "✅ **A.8.1**: Software asset inventory validated" >> $GITHUB_STEP_SUMMARY
1588
- else
1589
- echo "⚠️ **A.8.1**: License compliance check skipped" >> $GITHUB_STEP_SUMMARY
1590
- fi
1591
-
1592
- # A.12.1 - Operational Security
1593
- if [ "${{ needs.sonarcloud.result }}" == "success" ]; then
1594
- echo "✅ **A.12.1**: Code quality and security controls validated" >> $GITHUB_STEP_SUMMARY
1595
- else
1596
- echo "⚠️ **A.12.1**: Static analysis skipped" >> $GITHUB_STEP_SUMMARY
1597
- fi
1598
-
1599
- # A.14.2 - Security in Development
1600
- SECURITY_TESTS=0
1601
- [ "${{ needs.npm_security_scan.result }}" == "success" ] && SECURITY_TESTS=$((SECURITY_TESTS + 1))
1602
- [ "${{ needs.snyk.result }}" == "success" ] && SECURITY_TESTS=$((SECURITY_TESTS + 1))
1603
- [ "${{ needs.secret_scanning.result }}" == "success" ] && SECURITY_TESTS=$((SECURITY_TESTS + 1))
1604
-
1605
- if [ $SECURITY_TESTS -ge 2 ]; then
1606
- echo "✅ **A.14.2**: Security testing in SDLC validated ($SECURITY_TESTS/3 tools)" >> $GITHUB_STEP_SUMMARY
1607
- else
1608
- echo "❌ **A.14.2**: Insufficient security testing ($SECURITY_TESTS/3 tools)" >> $GITHUB_STEP_SUMMARY
1609
- fi
1610
- echo "" >> $GITHUB_STEP_SUMMARY
1611
-
1612
- - name: 🔍 HIPAA Control Validation
1613
- if: contains(inputs.compliance_framework, 'hipaa')
1614
- run: |
1615
- echo "## HIPAA Security Rule Controls" >> $GITHUB_STEP_SUMMARY
1616
- echo "" >> $GITHUB_STEP_SUMMARY
1617
-
1618
- # Access Control - 164.312(a)(1)
1619
- echo "✅ **164.312(a)(1)**: Access controls implemented via GitHub permissions" >> $GITHUB_STEP_SUMMARY
1620
-
1621
- # Audit Controls - 164.312(b)
1622
- echo "✅ **164.312(b)**: Audit logging enabled with ${{ inputs.audit_retention_days }}-day retention" >> $GITHUB_STEP_SUMMARY
1623
-
1624
- # Integrity - 164.312(c)(1)
1625
- if [ "${{ needs.test.result }}" == "success" ]; then
1626
- echo "✅ **164.312(c)(1)**: Data integrity validated through testing" >> $GITHUB_STEP_SUMMARY
1627
- else
1628
- echo "❌ **164.312(c)(1)**: Testing failed or skipped" >> $GITHUB_STEP_SUMMARY
1629
- fi
1630
-
1631
- # Transmission Security - 164.312(e)(1)
1632
- echo "✅ **164.312(e)(1)**: All transmissions use HTTPS/TLS" >> $GITHUB_STEP_SUMMARY
1633
-
1634
- # PHI Detection Warning
1635
- echo "" >> $GITHUB_STEP_SUMMARY
1636
- echo "⚠️ **Note**: Automated PHI detection not implemented. Manual review required for PHI handling." >> $GITHUB_STEP_SUMMARY
1637
- echo "" >> $GITHUB_STEP_SUMMARY
1638
-
1639
- - name: 🔍 PCI-DSS Control Validation
1640
- if: contains(inputs.compliance_framework, 'pci-dss')
1641
- run: |
1642
- echo "## PCI-DSS v4.0 Requirements" >> $GITHUB_STEP_SUMMARY
1643
- echo "" >> $GITHUB_STEP_SUMMARY
1644
-
1645
- # Requirement 2 - Default Passwords
1646
- if [ "${{ needs.secret_scanning.result }}" == "success" ]; then
1647
- echo "✅ **Req 2.2**: No hardcoded passwords detected" >> $GITHUB_STEP_SUMMARY
1648
- else
1649
- echo "❌ **Req 2.2**: Secret scanning incomplete" >> $GITHUB_STEP_SUMMARY
1650
- fi
1651
-
1652
- # Requirement 6 - Secure Development
1653
- if [ "${{ needs.sonarcloud.result }}" == "success" ]; then
1654
- echo "✅ **Req 6.3**: Secure coding practices validated" >> $GITHUB_STEP_SUMMARY
1655
- else
1656
- echo "⚠️ **Req 6.3**: Static analysis skipped" >> $GITHUB_STEP_SUMMARY
1657
- fi
1658
-
1659
- # Requirement 6.4 - Change Control
1660
- if [ "${{ github.event_name }}" == "pull_request" ]; then
1661
- echo "✅ **Req 6.4**: Change control process followed" >> $GITHUB_STEP_SUMMARY
1662
- else
1663
- echo "⚠️ **Req 6.4**: Direct push detected" >> $GITHUB_STEP_SUMMARY
1664
- fi
1665
-
1666
- # Requirement 11 - Security Testing
1667
- if [ "${{ needs.npm_security_scan.result }}" == "success" ] && [ "${{ needs.snyk.result }}" == "success" ]; then
1668
- echo "✅ **Req 11.3**: Vulnerability scanning completed" >> $GITHUB_STEP_SUMMARY
1669
- else
1670
- echo "❌ **Req 11.3**: Vulnerability scanning incomplete" >> $GITHUB_STEP_SUMMARY
1671
- fi
1672
-
1673
- echo "" >> $GITHUB_STEP_SUMMARY
1674
- echo "⚠️ **Note**: This validates security controls only. PCI compliance requires additional network and infrastructure controls." >> $GITHUB_STEP_SUMMARY
1675
- echo "" >> $GITHUB_STEP_SUMMARY
1676
-
1677
- - name: 📊 Generate compliance score
1678
- run: |
1679
- echo "## Compliance Score" >> $GITHUB_STEP_SUMMARY
1680
- echo "" >> $GITHUB_STEP_SUMMARY
1681
-
1682
- TOTAL_CONTROLS=0
1683
- PASSED_CONTROLS=0
1684
-
1685
- # Count quality checks
1686
- [ "${{ needs.lint.result }}" == "success" ] && PASSED_CONTROLS=$((PASSED_CONTROLS + 1))
1687
- [ "${{ needs.typecheck.result }}" == "success" ] && PASSED_CONTROLS=$((PASSED_CONTROLS + 1))
1688
- [ "${{ needs.test.result }}" == "success" ] && PASSED_CONTROLS=$((PASSED_CONTROLS + 1))
1689
- [ "${{ needs.build.result }}" == "success" ] && PASSED_CONTROLS=$((PASSED_CONTROLS + 1))
1690
- TOTAL_CONTROLS=$((TOTAL_CONTROLS + 4))
1691
-
1692
- # Count security checks
1693
- [ "${{ needs.npm_security_scan.result }}" != "skipped" ] && TOTAL_CONTROLS=$((TOTAL_CONTROLS + 1))
1694
- [ "${{ needs.npm_security_scan.result }}" == "success" ] && PASSED_CONTROLS=$((PASSED_CONTROLS + 1))
1695
-
1696
- [ "${{ needs.sonarcloud.result }}" != "skipped" ] && TOTAL_CONTROLS=$((TOTAL_CONTROLS + 1))
1697
- [ "${{ needs.sonarcloud.result }}" == "success" ] && PASSED_CONTROLS=$((PASSED_CONTROLS + 1))
1698
-
1699
- [ "${{ needs.snyk.result }}" != "skipped" ] && TOTAL_CONTROLS=$((TOTAL_CONTROLS + 1))
1700
- [ "${{ needs.snyk.result }}" == "success" ] && PASSED_CONTROLS=$((PASSED_CONTROLS + 1))
1701
-
1702
- [ "${{ needs.secret_scanning.result }}" != "skipped" ] && TOTAL_CONTROLS=$((TOTAL_CONTROLS + 1))
1703
- [ "${{ needs.secret_scanning.result }}" == "success" ] && PASSED_CONTROLS=$((PASSED_CONTROLS + 1))
1704
-
1705
- [ "${{ needs.license_compliance.result }}" != "skipped" ] && TOTAL_CONTROLS=$((TOTAL_CONTROLS + 1))
1706
- [ "${{ needs.license_compliance.result }}" == "success" ] && PASSED_CONTROLS=$((PASSED_CONTROLS + 1))
1707
-
1708
- if [ $TOTAL_CONTROLS -gt 0 ]; then
1709
- SCORE=$((PASSED_CONTROLS * 100 / TOTAL_CONTROLS))
1710
- echo "**Overall Compliance Score**: $SCORE% ($PASSED_CONTROLS/$TOTAL_CONTROLS controls passed)" >> $GITHUB_STEP_SUMMARY
1711
-
1712
- if [ $SCORE -ge 90 ]; then
1713
- echo "🟢 **Status**: Excellent compliance posture" >> $GITHUB_STEP_SUMMARY
1714
- elif [ $SCORE -ge 70 ]; then
1715
- echo "🟡 **Status**: Good compliance posture with minor gaps" >> $GITHUB_STEP_SUMMARY
1716
- else
1717
- echo "🔴 **Status**: Significant compliance gaps detected" >> $GITHUB_STEP_SUMMARY
1718
- fi
1719
- fi
1720
-
1721
- # Audit logging job
1722
- audit_logger:
1723
- name: 📊 Audit Logger
1724
- runs-on: ubuntu-latest
1725
- if: always()
1726
- needs:
1727
- [
1728
- install_dependencies,
1729
- lint,
1730
- typecheck,
1731
- test,
1732
- test_unit,
1733
- test_integration,
1734
- test_e2e,
1735
- maestro_e2e,
1736
- playwright_e2e,
1737
- format,
1738
- build,
1739
- dead_code,
1740
- sg_scan,
1741
- npm_security_scan,
1742
- sonarcloud,
1743
- snyk,
1744
- secret_scanning,
1745
- license_compliance,
1746
- zap_baseline,
1747
- compliance_validation,
1748
- ]
1749
- steps:
1750
- - name: 📝 Generate audit log
1751
- uses: actions/github-script@v7
1752
- with:
1753
- script: |
1754
- const auditLog = {
1755
- timestamp: new Date().toISOString(),
1756
- workflow: {
1757
- name: '${{ github.workflow }}',
1758
- run_id: '${{ github.run_id }}',
1759
- run_number: '${{ github.run_number }}',
1760
- run_attempt: '${{ github.run_attempt }}'
1761
- },
1762
- trigger: {
1763
- event: '${{ github.event_name }}',
1764
- actor: '${{ github.actor }}',
1765
- ref: '${{ github.ref }}',
1766
- sha: '${{ github.sha }}'
1767
- },
1768
- repository: {
1769
- name: '${{ github.repository }}',
1770
- owner: '${{ github.repository_owner }}',
1771
- visibility: '${{ github.event.repository.visibility }}'
1772
- },
1773
- compliance: {
1774
- framework: '${{ inputs.compliance_framework }}',
1775
- require_approval: ${{ inputs.require_approval }},
1776
- audit_retention_days: ${{ inputs.audit_retention_days }}
1777
- },
1778
- jobs: {
1779
- dependencies: {
1780
- status: '${{ needs.install_dependencies.result }}',
1781
- cache_hit: '${{ needs.install_dependencies.outputs.cache-hit }}'
1782
- },
1783
- quality: {
1784
- lint: '${{ needs.lint.result }}',
1785
- typecheck: '${{ needs.typecheck.result }}',
1786
- test: '${{ needs.test.result }}',
1787
- test_unit: '${{ needs.test_unit.result }}',
1788
- test_integration: '${{ needs.test_integration.result }}',
1789
- test_e2e: '${{ needs.test_e2e.result }}',
1790
- maestro_e2e: '${{ needs.maestro_e2e.result }}',
1791
- playwright_e2e: '${{ needs.playwright_e2e.result }}',
1792
- format: '${{ needs.format.result }}',
1793
- build: '${{ needs.build.result }}',
1794
- sg_scan: '${{ needs.sg_scan.result }}'
1795
- },
1796
- security: {
1797
- npm_audit: '${{ needs.npm_security_scan.result }}',
1798
- sonarcloud: '${{ needs.sonarcloud.result }}',
1799
- snyk: '${{ needs.snyk.result }}',
1800
- secret_scan: '${{ needs.secret_scanning.result }}',
1801
- license_check: '${{ needs.license_compliance.result }}',
1802
- zap_baseline: '${{ needs.zap_baseline.result }}'
1803
- },
1804
- compliance: '${{ needs.compliance_validation.result }}'
1805
- },
1806
- metadata: {
1807
- runner_os: '${{ runner.os }}',
1808
- runner_arch: '${{ runner.arch }}'
1809
- }
1810
- };
1811
-
1812
- // Write to file
1813
- const fs = require('fs');
1814
- const filename = `audit-log-${auditLog.workflow.run_id}-${Date.now()}.json`;
1815
- fs.writeFileSync(filename, JSON.stringify(auditLog, null, 2));
1816
-
1817
- console.log(`Audit log generated: ${filename}`);
1818
-
1819
- // Add to job summary
1820
- core.summary.addHeading('📊 Audit Log Entry', 3);
1821
- core.summary.addCodeBlock(JSON.stringify(auditLog, null, 2), 'json');
1822
- await core.summary.write();
1823
-
1824
- // Set output for artifact upload
1825
- core.setOutput('filename', filename);
1826
-
1827
- - name: 📤 Upload audit log
1828
- uses: actions/upload-artifact@v4
1829
- with:
1830
- name: audit-log-${{ github.run_id }}
1831
- path: audit-log-*.json
1832
- retention-days: ${{ inputs.audit_retention_days }}
1833
-
1834
- - name: 📊 Generate evidence package
1835
- if: ${{ inputs.generate_evidence_package == true }}
1836
- run: |
1837
- echo "📦 Generating compliance evidence package..."
1838
-
1839
- # Create evidence directory
1840
- mkdir -p evidence-package
1841
-
1842
- # Create evidence summary
1843
- cat > evidence-package/evidence-summary.md << EOF
1844
- # Compliance Evidence Package
1845
-
1846
- **Generated**: $(date -u +%Y-%m-%dT%H:%M:%SZ)
1847
- **Workflow Run**: ${{ github.run_id }}
1848
- **Repository**: ${{ github.repository }}
1849
- **Compliance Framework**: ${{ inputs.compliance_framework }}
1850
-
1851
- ## Evidence Contents
1852
-
1853
- 1. **Audit Log**: Full workflow execution audit trail
1854
- 2. **Security Scan Results**: Results from all security tools
1855
- 3. **Test Reports**: Unit test execution results
1856
- 4. **Build Artifacts**: Compiled output verification
1857
- 5. **Approval Records**: Change approval documentation
1858
-
1859
- ## Control Validation Summary
1860
-
1861
- - Quality Controls: Validated through automated testing
1862
- - Security Controls: Validated through multiple scanning tools
1863
- - Change Management: Validated through PR process
1864
- - Audit Controls: Continuous logging with ${{ inputs.audit_retention_days }}-day retention
1865
-
1866
- ## Attestation
1867
-
1868
- This evidence package was automatically generated from workflow run ${{ github.run_id }}.
1869
- All artifacts are cryptographically signed by GitHub Actions.
1870
- EOF
1871
-
1872
- echo "✅ Evidence package created"
1873
-
1874
- - name: 📤 Upload evidence package
1875
- if: ${{ inputs.generate_evidence_package == true }}
1876
- uses: actions/upload-artifact@v4
1877
- with:
1878
- name: compliance-evidence-${{ github.run_id }}
1879
- path: evidence-package/
1880
- retention-days: ${{ inputs.audit_retention_days }}
1881
-
1882
- # Approval gate for production deployments
1883
- approval_gate:
1884
- name: 🚦 Approval Gate
1885
- runs-on: ubuntu-latest
1886
- if: ${{ inputs.require_approval == true && inputs.compliance_framework != 'none' && inputs.approval_environment != '' }}
1887
- needs: [compliance_validation]
1888
- environment:
1889
- name: ${{ inputs.approval_environment }}
1890
- # NOTE: This environment must be created in your repository before using this workflow
1891
- # To create it:
1892
- # 1. Go to Settings > Environments > New environment
1893
- # 2. Name it to match the approval_environment input (default: "production")
1894
- # 3. Add protection rules (required reviewers, deployment branches, etc.)
1895
- steps:
1896
- - name: 📝 Log approval
1897
- run: |
1898
- echo "# 🚦 Production Deployment Approval" >> $GITHUB_STEP_SUMMARY
1899
- echo "" >> $GITHUB_STEP_SUMMARY
1900
- echo "**Approved by**: ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY
1901
- echo "**Approval time**: $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_STEP_SUMMARY
1902
- echo "**Compliance framework**: ${{ inputs.compliance_framework }}" >> $GITHUB_STEP_SUMMARY
1903
- echo "**Workflow run**: ${{ github.run_id }}" >> $GITHUB_STEP_SUMMARY
1904
- echo "" >> $GITHUB_STEP_SUMMARY
1905
- echo "## Approval Requirements Met" >> $GITHUB_STEP_SUMMARY
1906
- echo "" >> $GITHUB_STEP_SUMMARY
1907
- echo "- ✅ Compliance validation passed" >> $GITHUB_STEP_SUMMARY
1908
- echo "- ✅ Security scans completed" >> $GITHUB_STEP_SUMMARY
1909
- echo "- ✅ Required approvers notified" >> $GITHUB_STEP_SUMMARY
1910
- echo "- ✅ Audit trail generated" >> $GITHUB_STEP_SUMMARY
1911
-
1912
- - name: 📊 Create approval record
1913
- run: |
1914
- # Create approval record for audit
1915
- cat > approval-record-${{ github.run_id }}.json << EOF
1916
- {
1917
- "approval_type": "production_deployment",
1918
- "approved_by": "${{ github.actor }}",
1919
- "approval_time": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
1920
- "workflow_run_id": "${{ github.run_id }}",
1921
- "compliance_framework": "${{ inputs.compliance_framework }}",
1922
- "repository": "${{ github.repository }}",
1923
- "ref": "${{ github.ref }}",
1924
- "sha": "${{ github.sha }}"
1925
- }
1926
- EOF
1927
-
1928
- - name: 📤 Upload approval record
1929
- uses: actions/upload-artifact@v4
1930
- with:
1931
- name: approval-record-${{ github.run_id }}
1932
- path: approval-record-*.json
1933
- retention-days: ${{ inputs.audit_retention_days }}
1934
-
1935
- # Performance monitoring job
1936
- performance_summary:
1937
- name: ⚡ Performance Summary
1938
- runs-on: ubuntu-latest
1939
- if: always()
1940
- needs:
1941
- [
1942
- install_dependencies,
1943
- lint,
1944
- typecheck,
1945
- test,
1946
- test_unit,
1947
- test_integration,
1948
- test_e2e,
1949
- maestro_e2e,
1950
- playwright_e2e,
1951
- format,
1952
- build,
1953
- dead_code,
1954
- sg_scan,
1955
- npm_security_scan,
1956
- sonarcloud,
1957
- snyk,
1958
- secret_scanning,
1959
- license_compliance,
1960
- zap_baseline,
1961
- ]
1962
- steps:
1963
- - name: 📊 Generate performance report
1964
- run: |
1965
- echo "# ⚡ Workflow Performance Report" >> $GITHUB_STEP_SUMMARY
1966
- echo "" >> $GITHUB_STEP_SUMMARY
1967
- echo "## 🏃 Execution Summary" >> $GITHUB_STEP_SUMMARY
1968
- echo "" >> $GITHUB_STEP_SUMMARY
1969
-
1970
- # Calculate total workflow time (approximation)
1971
- echo "- **Workflow Run ID**: ${{ github.run_id }}" >> $GITHUB_STEP_SUMMARY
1972
- echo "- **Triggered By**: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY
1973
- echo "- **Runner**: ${{ runner.os }} (${{ runner.arch }})" >> $GITHUB_STEP_SUMMARY
1974
- echo "" >> $GITHUB_STEP_SUMMARY
1975
-
1976
- echo "## 📦 Dependency Caching" >> $GITHUB_STEP_SUMMARY
1977
- echo "" >> $GITHUB_STEP_SUMMARY
1978
-
1979
- if [ "${{ needs.install_dependencies.outputs.cache-hit }}" == "true" ]; then
1980
- echo "✅ **Cache Hit!** Dependencies loaded from cache." >> $GITHUB_STEP_SUMMARY
1981
- else
1982
- echo "❌ **Cache Miss** - Fresh dependency installation was required." >> $GITHUB_STEP_SUMMARY
1983
- fi
1984
-
1985
- echo "" >> $GITHUB_STEP_SUMMARY
1986
- echo "- **Cache Key**: \`${{ needs.install_dependencies.outputs.cache-key }}\`" >> $GITHUB_STEP_SUMMARY
1987
-
1988
- echo "" >> $GITHUB_STEP_SUMMARY
1989
- echo "## 🎯 Job Status Overview" >> $GITHUB_STEP_SUMMARY
1990
- echo "" >> $GITHUB_STEP_SUMMARY
1991
- echo "| Job | Status | Type |" >> $GITHUB_STEP_SUMMARY
1992
- echo "|-----|--------|------|" >> $GITHUB_STEP_SUMMARY
1993
- echo "| Install Dependencies | ${{ needs.install_dependencies.result }} | Setup |" >> $GITHUB_STEP_SUMMARY
1994
- echo "| Lint | ${{ needs.lint.result }} | Quality |" >> $GITHUB_STEP_SUMMARY
1995
- echo "| Type Check | ${{ needs.typecheck.result }} | Quality |" >> $GITHUB_STEP_SUMMARY
1996
- echo "| Tests | ${{ needs.test.result }} | Quality |" >> $GITHUB_STEP_SUMMARY
1997
- echo "| Unit Tests | ${{ needs.test_unit.result }} | Quality |" >> $GITHUB_STEP_SUMMARY
1998
- echo "| Integration Tests | ${{ needs.test_integration.result }} | Quality |" >> $GITHUB_STEP_SUMMARY
1999
- echo "| E2E Tests | ${{ needs.test_e2e.result }} | Quality |" >> $GITHUB_STEP_SUMMARY
2000
- echo "| Maestro E2E | ${{ needs.maestro_e2e.result }} | Quality |" >> $GITHUB_STEP_SUMMARY
2001
- echo "| Playwright E2E | ${{ needs.playwright_e2e.result }} | Quality |" >> $GITHUB_STEP_SUMMARY
2002
- echo "| Format Check | ${{ needs.format.result }} | Quality |" >> $GITHUB_STEP_SUMMARY
2003
- echo "| Build | ${{ needs.build.result }} | Build |" >> $GITHUB_STEP_SUMMARY
2004
- echo "| Dead Code | ${{ needs.dead_code.result }} | Quality |" >> $GITHUB_STEP_SUMMARY
2005
- echo "| AST Grep | ${{ needs.sg_scan.result }} | Quality |" >> $GITHUB_STEP_SUMMARY
2006
- echo "| NPM Security | ${{ needs.npm_security_scan.result }} | Security |" >> $GITHUB_STEP_SUMMARY
2007
- echo "| SonarCloud | ${{ needs.sonarcloud.result }} | Security |" >> $GITHUB_STEP_SUMMARY
2008
- echo "| Snyk | ${{ needs.snyk.result }} | Security |" >> $GITHUB_STEP_SUMMARY
2009
- echo "| Secret Scan | ${{ needs.secret_scanning.result }} | Security |" >> $GITHUB_STEP_SUMMARY
2010
- echo "| License Check | ${{ needs.license_compliance.result }} | Security |" >> $GITHUB_STEP_SUMMARY
2011
- echo "| ZAP Baseline | ${{ needs.zap_baseline.result }} | Security |" >> $GITHUB_STEP_SUMMARY
2012
-
2013
- echo "" >> $GITHUB_STEP_SUMMARY
2014
- echo "## 💡 Performance Tips" >> $GITHUB_STEP_SUMMARY
2015
- echo "" >> $GITHUB_STEP_SUMMARY
2016
- echo "- All jobs now share dependencies, reducing redundant installations" >> $GITHUB_STEP_SUMMARY
2017
- echo "- Quality checks (lint, typecheck, format) run in parallel" >> $GITHUB_STEP_SUMMARY
2018
- echo "- Security tools run in parallel where tokens are available" >> $GITHUB_STEP_SUMMARY
2019
- echo "- Dependency caching saves ~2-3 minutes on subsequent runs" >> $GITHUB_STEP_SUMMARY
2020
-
2021
- # Count parallel jobs
2022
- QUALITY_JOBS=0
2023
- SECURITY_JOBS=0
2024
-
2025
- [ "${{ needs.lint.result }}" != "skipped" ] && QUALITY_JOBS=$((QUALITY_JOBS + 1))
2026
- [ "${{ needs.typecheck.result }}" != "skipped" ] && QUALITY_JOBS=$((QUALITY_JOBS + 1))
2027
- [ "${{ needs.test.result }}" != "skipped" ] && QUALITY_JOBS=$((QUALITY_JOBS + 1))
2028
- [ "${{ needs.test_unit.result }}" != "skipped" ] && QUALITY_JOBS=$((QUALITY_JOBS + 1))
2029
- [ "${{ needs.test_integration.result }}" != "skipped" ] && QUALITY_JOBS=$((QUALITY_JOBS + 1))
2030
- [ "${{ needs.test_e2e.result }}" != "skipped" ] && QUALITY_JOBS=$((QUALITY_JOBS + 1))
2031
- [ "${{ needs.maestro_e2e.result }}" != "skipped" ] && QUALITY_JOBS=$((QUALITY_JOBS + 1))
2032
- [ "${{ needs.playwright_e2e.result }}" != "skipped" ] && QUALITY_JOBS=$((QUALITY_JOBS + 1))
2033
- [ "${{ needs.format.result }}" != "skipped" ] && QUALITY_JOBS=$((QUALITY_JOBS + 1))
2034
- [ "${{ needs.dead_code.result }}" != "skipped" ] && QUALITY_JOBS=$((QUALITY_JOBS + 1))
2035
- [ "${{ needs.sg_scan.result }}" != "skipped" ] && QUALITY_JOBS=$((QUALITY_JOBS + 1))
2036
-
2037
- [ "${{ needs.sonarcloud.result }}" != "skipped" ] && SECURITY_JOBS=$((SECURITY_JOBS + 1))
2038
- [ "${{ needs.snyk.result }}" != "skipped" ] && SECURITY_JOBS=$((SECURITY_JOBS + 1))
2039
- [ "${{ needs.secret_scanning.result }}" != "skipped" ] && SECURITY_JOBS=$((SECURITY_JOBS + 1))
2040
- [ "${{ needs.license_compliance.result }}" != "skipped" ] && SECURITY_JOBS=$((SECURITY_JOBS + 1))
2041
- [ "${{ needs.zap_baseline.result }}" != "skipped" ] && SECURITY_JOBS=$((SECURITY_JOBS + 1))
2042
-
2043
- echo "" >> $GITHUB_STEP_SUMMARY
2044
- echo "## 🚀 Optimization Metrics" >> $GITHUB_STEP_SUMMARY
2045
- echo "" >> $GITHUB_STEP_SUMMARY
2046
- echo "- **Parallel Quality Jobs**: $QUALITY_JOBS running concurrently" >> $GITHUB_STEP_SUMMARY
2047
- echo "- **Parallel Security Jobs**: $SECURITY_JOBS running concurrently" >> $GITHUB_STEP_SUMMARY
2048
- echo "- **Dependency Installation**: Single shared installation" >> $GITHUB_STEP_SUMMARY
2049
- echo "- **Estimated Time Saved**: ~40% vs sequential execution" >> $GITHUB_STEP_SUMMARY