@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.
- package/dist/utils/ignore-patterns.d.ts.map +1 -1
- package/dist/utils/ignore-patterns.js +7 -1
- package/dist/utils/ignore-patterns.js.map +1 -1
- package/package.json +2 -2
- package/typescript/deletions.json +3 -1
- package/typescript/copy-overwrite/.github/workflows/quality.yml +0 -2049
- package/typescript/copy-overwrite/.github/workflows/release.yml +0 -1593
|
@@ -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
|