@codyswann/lisa 1.0.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/LICENSE +21 -0
- package/README.md +867 -0
- package/all/copy-overwrite/.claude/README.md +205 -0
- package/all/copy-overwrite/.claude/agents/agent-architect.md +311 -0
- package/all/copy-overwrite/.claude/agents/codebase-analyzer.md +146 -0
- package/all/copy-overwrite/.claude/agents/codebase-locator.md +125 -0
- package/all/copy-overwrite/.claude/agents/codebase-pattern-finder.md +237 -0
- package/all/copy-overwrite/.claude/agents/git-history-analyzer.md +183 -0
- package/all/copy-overwrite/.claude/agents/hooks-expert.md +74 -0
- package/all/copy-overwrite/.claude/agents/skill-evaluator.md +246 -0
- package/all/copy-overwrite/.claude/agents/slash-command-architect.md +87 -0
- package/all/copy-overwrite/.claude/agents/web-search-researcher.md +112 -0
- package/all/copy-overwrite/.claude/commands/git/commit-and-submit-pr.md +8 -0
- package/all/copy-overwrite/.claude/commands/git/commit.md +44 -0
- package/all/copy-overwrite/.claude/commands/git/prune.md +34 -0
- package/all/copy-overwrite/.claude/commands/git/submit-pr.md +50 -0
- package/all/copy-overwrite/.claude/commands/jira/create.md +50 -0
- package/all/copy-overwrite/.claude/commands/jira/verify.md +34 -0
- package/all/copy-overwrite/.claude/commands/project/archive.md +8 -0
- package/all/copy-overwrite/.claude/commands/project/bootstrap.md +49 -0
- package/all/copy-overwrite/.claude/commands/project/complete-task.md +7 -0
- package/all/copy-overwrite/.claude/commands/project/debrief.md +65 -0
- package/all/copy-overwrite/.claude/commands/project/execute.md +94 -0
- package/all/copy-overwrite/.claude/commands/project/implement.md +42 -0
- package/all/copy-overwrite/.claude/commands/project/local-code-review.md +88 -0
- package/all/copy-overwrite/.claude/commands/project/lower-code-complexity.md +74 -0
- package/all/copy-overwrite/.claude/commands/project/plan.md +314 -0
- package/all/copy-overwrite/.claude/commands/project/research.md +248 -0
- package/all/copy-overwrite/.claude/commands/project/review.md +63 -0
- package/all/copy-overwrite/.claude/commands/project/setup.md +19 -0
- package/all/copy-overwrite/.claude/commands/project/verify.md +38 -0
- package/all/copy-overwrite/.claude/commands/pull-request/review.md +12 -0
- package/all/copy-overwrite/.claude/commands/rules/format-md.md +72 -0
- package/all/copy-overwrite/.claude/commands/sonarqube/check.md +6 -0
- package/all/copy-overwrite/.claude/commands/sonarqube/fix.md +3 -0
- package/all/copy-overwrite/.claude/hooks/README.md +301 -0
- package/all/copy-overwrite/.claude/hooks/notify-ntfy.sh +181 -0
- package/all/copy-overwrite/.claude/settings.json +41 -0
- package/all/copy-overwrite/.claude/settings.local.json.example +14 -0
- package/all/copy-overwrite/.claude/skills/coding-philosophy/SKILL.md +405 -0
- package/all/copy-overwrite/.claude/skills/coding-philosophy/references/function-structure.md +416 -0
- package/all/copy-overwrite/.claude/skills/coding-philosophy/references/immutable-patterns.md +316 -0
- package/all/copy-overwrite/.claude/skills/prompt-complexity-scorer/SKILL.md +118 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/LICENSE.txt +202 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/SKILL.md +210 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/__pycache__/quick_validate.cpython-312.pyc +0 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/init_skill.py +303 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/package_skill.py +110 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/quick_validate.py +65 -0
- package/all/copy-overwrite/CLAUDE.md +77 -0
- package/all/copy-overwrite/HUMAN.md +17 -0
- package/all/copy-overwrite/specs/.keep +0 -0
- package/all/create-only/PROJECT_RULES.md +0 -0
- package/cdk/merge/package.json +20 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +107 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/prompts.d.ts +45 -0
- package/dist/cli/prompts.d.ts.map +1 -0
- package/dist/cli/prompts.js +58 -0
- package/dist/cli/prompts.js.map +1 -0
- package/dist/core/config.d.ts +73 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +36 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +4 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/lisa.d.ts +81 -0
- package/dist/core/lisa.d.ts.map +1 -0
- package/dist/core/lisa.js +459 -0
- package/dist/core/lisa.js.map +1 -0
- package/dist/core/manifest.d.ts +58 -0
- package/dist/core/manifest.d.ts.map +1 -0
- package/dist/core/manifest.js +104 -0
- package/dist/core/manifest.js.map +1 -0
- package/dist/detection/detector.interface.d.ts +15 -0
- package/dist/detection/detector.interface.d.ts.map +1 -0
- package/dist/detection/detector.interface.js +2 -0
- package/dist/detection/detector.interface.js.map +1 -0
- package/dist/detection/detectors/cdk.d.ts +10 -0
- package/dist/detection/detectors/cdk.d.ts.map +1 -0
- package/dist/detection/detectors/cdk.js +34 -0
- package/dist/detection/detectors/cdk.js.map +1 -0
- package/dist/detection/detectors/expo.d.ts +10 -0
- package/dist/detection/detectors/expo.d.ts.map +1 -0
- package/dist/detection/detectors/expo.js +30 -0
- package/dist/detection/detectors/expo.js.map +1 -0
- package/dist/detection/detectors/nestjs.d.ts +10 -0
- package/dist/detection/detectors/nestjs.d.ts.map +1 -0
- package/dist/detection/detectors/nestjs.js +34 -0
- package/dist/detection/detectors/nestjs.js.map +1 -0
- package/dist/detection/detectors/npm-package.d.ts +13 -0
- package/dist/detection/detectors/npm-package.d.ts.map +1 -0
- package/dist/detection/detectors/npm-package.js +30 -0
- package/dist/detection/detectors/npm-package.js.map +1 -0
- package/dist/detection/detectors/typescript.d.ts +10 -0
- package/dist/detection/detectors/typescript.d.ts.map +1 -0
- package/dist/detection/detectors/typescript.js +25 -0
- package/dist/detection/detectors/typescript.js.map +1 -0
- package/dist/detection/index.d.ts +24 -0
- package/dist/detection/index.d.ts.map +1 -0
- package/dist/detection/index.js +57 -0
- package/dist/detection/index.js.map +1 -0
- package/dist/errors/index.d.ts +69 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +110 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/console-logger.d.ts +12 -0
- package/dist/logging/console-logger.d.ts.map +1 -0
- package/dist/logging/console-logger.js +22 -0
- package/dist/logging/console-logger.js.map +1 -0
- package/dist/logging/index.d.ts +4 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +3 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/logger.interface.d.ts +20 -0
- package/dist/logging/logger.interface.d.ts.map +1 -0
- package/dist/logging/logger.interface.js +2 -0
- package/dist/logging/logger.interface.js.map +1 -0
- package/dist/logging/silent-logger.d.ts +12 -0
- package/dist/logging/silent-logger.d.ts.map +1 -0
- package/dist/logging/silent-logger.js +21 -0
- package/dist/logging/silent-logger.js.map +1 -0
- package/dist/strategies/copy-contents.d.ts +14 -0
- package/dist/strategies/copy-contents.d.ts.map +1 -0
- package/dist/strategies/copy-contents.js +69 -0
- package/dist/strategies/copy-contents.js.map +1 -0
- package/dist/strategies/copy-overwrite.d.ts +14 -0
- package/dist/strategies/copy-overwrite.d.ts.map +1 -0
- package/dist/strategies/copy-overwrite.js +47 -0
- package/dist/strategies/copy-overwrite.js.map +1 -0
- package/dist/strategies/create-only.d.ts +13 -0
- package/dist/strategies/create-only.d.ts.map +1 -0
- package/dist/strategies/create-only.js +30 -0
- package/dist/strategies/create-only.js.map +1 -0
- package/dist/strategies/index.d.ts +31 -0
- package/dist/strategies/index.d.ts.map +1 -0
- package/dist/strategies/index.js +52 -0
- package/dist/strategies/index.js.map +1 -0
- package/dist/strategies/merge.d.ts +13 -0
- package/dist/strategies/merge.d.ts.map +1 -0
- package/dist/strategies/merge.js +60 -0
- package/dist/strategies/merge.js.map +1 -0
- package/dist/strategies/strategy.interface.d.ts +31 -0
- package/dist/strategies/strategy.interface.d.ts.map +1 -0
- package/dist/strategies/strategy.interface.js +2 -0
- package/dist/strategies/strategy.interface.js.map +1 -0
- package/dist/transaction/backup.d.ts +38 -0
- package/dist/transaction/backup.d.ts.map +1 -0
- package/dist/transaction/backup.js +97 -0
- package/dist/transaction/backup.js.map +1 -0
- package/dist/transaction/index.d.ts +4 -0
- package/dist/transaction/index.d.ts.map +1 -0
- package/dist/transaction/index.js +3 -0
- package/dist/transaction/index.js.map +1 -0
- package/dist/transaction/transaction.d.ts +34 -0
- package/dist/transaction/transaction.d.ts.map +1 -0
- package/dist/transaction/transaction.js +68 -0
- package/dist/transaction/transaction.js.map +1 -0
- package/dist/utils/file-operations.d.ts +29 -0
- package/dist/utils/file-operations.d.ts.map +1 -0
- package/dist/utils/file-operations.js +84 -0
- package/dist/utils/file-operations.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/json-utils.d.ts +22 -0
- package/dist/utils/json-utils.d.ts.map +1 -0
- package/dist/utils/json-utils.js +57 -0
- package/dist/utils/json-utils.js.map +1 -0
- package/dist/utils/path-utils.d.ts +21 -0
- package/dist/utils/path-utils.d.ts.map +1 -0
- package/dist/utils/path-utils.js +35 -0
- package/dist/utils/path-utils.js.map +1 -0
- package/eslint-plugin-code-organization/README.md +149 -0
- package/eslint-plugin-code-organization/__tests__/enforce-statement-order.test.js +468 -0
- package/eslint-plugin-code-organization/index.js +23 -0
- package/eslint-plugin-code-organization/package.json +10 -0
- package/eslint-plugin-code-organization/rules/enforce-statement-order.js +157 -0
- package/expo/copy-overwrite/.claude/skills/apollo-client/SKILL.md +238 -0
- package/expo/copy-overwrite/.claude/skills/apollo-client/references/mutation-patterns.md +360 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/SKILL.md +360 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/atomic-levels.md +417 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/folder-structure.md +257 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/gluestack-mapping.md +233 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/scripts/validate_atomic_structure.py +327 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/SKILL.md +299 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/references/examples.md +749 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/references/patterns.md +318 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/scripts/create_component.py +198 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/scripts/validate_component.py +207 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/SKILL.md +268 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/common-issues.md +619 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/file-extensions.md +340 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/platform-api.md +276 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/scripts/validate_cross_platform.py +414 -0
- package/expo/copy-overwrite/.claude/skills/directory-structure/SKILL.md +202 -0
- package/expo/copy-overwrite/.claude/skills/directory-structure/scripts/validate_structure.py +443 -0
- package/expo/copy-overwrite/.claude/skills/expo-env-config/SKILL.md +309 -0
- package/expo/copy-overwrite/.claude/skills/expo-env-config/references/validation-patterns.md +417 -0
- package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/SKILL.md +431 -0
- package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/references/official-docs.md +290 -0
- package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/scripts/generate-route.py +169 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/SKILL.md +411 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/color-tokens.md +343 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/component-mapping.md +307 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/spacing-scale.md +300 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/scripts/validate_styling.py +354 -0
- package/expo/copy-overwrite/.claude/skills/local-state/SKILL.md +362 -0
- package/expo/copy-overwrite/.claude/skills/local-state/references/async-storage.md +505 -0
- package/expo/copy-overwrite/.claude/skills/local-state/references/persistence-patterns.md +711 -0
- package/expo/copy-overwrite/.claude/skills/local-state/references/reactive-variables.md +446 -0
- package/expo/copy-overwrite/.claude/skills/playwright-selectors/SKILL.md +223 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/SKILL.md +319 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/async-patterns.md +420 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/expo-router-testing.md +556 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/mocking-patterns.md +590 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/query-priority.md +291 -0
- package/expo/copy-overwrite/.easignore.extra +2 -0
- package/expo/copy-overwrite/.mcp.json +33 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/README.md +234 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/plugin-index.test.js +84 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/require-memo-in-view.test.js +196 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/single-component-per-file.test.js +289 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/index.js +32 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/package.json +10 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/enforce-component-structure.js +230 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/no-return-in-view.js +91 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/require-memo-in-view.js +178 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/single-component-per-file.js +238 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/README.md +260 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/index.js +29 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/package.json +10 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-classname-outside-ui.js +51 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-direct-rn-imports.js +55 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-inline-styles.js +73 -0
- package/expo/copy-overwrite/eslint.config.mjs +560 -0
- package/expo/copy-overwrite/lighthouserc.js +194 -0
- package/expo/create-only/lighthouserc-config.json +28 -0
- package/expo/merge/package.json +132 -0
- package/lisa.sh +35 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/SKILL.md +176 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/advanced-features.md +527 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/project-patterns.md +483 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/quick-start.md +257 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/resolvers-mutations.md +413 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/types-scalars.md +513 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-rules/SKILL.md +536 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/SKILL.md +275 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/configuration-patterns.md +487 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/entity-patterns.md +450 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/observability-patterns.md +536 -0
- package/nestjs/merge/package.json +75 -0
- package/package.json +124 -0
- package/typescript/copy-contents/.husky/commit-msg +91 -0
- package/typescript/copy-contents/.husky/pre-commit +96 -0
- package/typescript/copy-contents/.husky/pre-push +211 -0
- package/typescript/copy-overwrite/.claude/hooks/format-on-edit.sh +74 -0
- package/typescript/copy-overwrite/.claude/hooks/install_pkgs.sh +59 -0
- package/typescript/copy-overwrite/.claude/hooks/lint-on-edit.sh +103 -0
- package/typescript/copy-overwrite/.claude/skills/jsdoc-best-practices/SKILL.md +388 -0
- package/typescript/copy-overwrite/.github/README.md +455 -0
- package/typescript/copy-overwrite/.github/dependabot.yml +40 -0
- package/typescript/copy-overwrite/.github/k6/BROWSER_TESTING_NOTE.md +129 -0
- package/typescript/copy-overwrite/.github/k6/INTEGRATION_GUIDE.md +354 -0
- package/typescript/copy-overwrite/.github/k6/README.md +386 -0
- package/typescript/copy-overwrite/.github/k6/SCENARIO_SELECTION_GUIDE.md +264 -0
- package/typescript/copy-overwrite/.github/k6/examples/customer-deploy-integration.yml +115 -0
- package/typescript/copy-overwrite/.github/k6/examples/data-driven-test.js +268 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/load.js +142 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/load.json +27 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/smoke.js +26 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/smoke.json +20 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/soak.js +244 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/soak.json +29 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/spike.js +180 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/spike.json +32 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/stress.js +206 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/stress.json +38 -0
- package/typescript/copy-overwrite/.github/k6/scripts/api-test.js +452 -0
- package/typescript/copy-overwrite/.github/k6/scripts/default-test.js +185 -0
- package/typescript/copy-overwrite/.github/k6/thresholds/normal.json +30 -0
- package/typescript/copy-overwrite/.github/k6/thresholds/relaxed.json +21 -0
- package/typescript/copy-overwrite/.github/k6/thresholds/strict.json +29 -0
- package/typescript/copy-overwrite/.github/workflows/build.yml +72 -0
- package/typescript/copy-overwrite/.github/workflows/ci.yml +49 -0
- package/typescript/copy-overwrite/.github/workflows/claude.yml +51 -0
- package/typescript/copy-overwrite/.github/workflows/create-github-issue-on-failure.yml +113 -0
- package/typescript/copy-overwrite/.github/workflows/create-jira-issue-on-failure.yml +195 -0
- package/typescript/copy-overwrite/.github/workflows/create-sentry-issue-on-failure.yml +267 -0
- package/typescript/copy-overwrite/.github/workflows/deploy.yml +228 -0
- package/typescript/copy-overwrite/.github/workflows/k6-load-test-README.md +230 -0
- package/typescript/copy-overwrite/.github/workflows/lighthouse.yml +68 -0
- package/typescript/copy-overwrite/.github/workflows/load-test.yml +282 -0
- package/typescript/copy-overwrite/.github/workflows/quality.yml +1737 -0
- package/typescript/copy-overwrite/.github/workflows/release.yml +1599 -0
- package/typescript/copy-overwrite/.gitleaksignore +28 -0
- package/typescript/copy-overwrite/.nvmrc +1 -0
- package/typescript/copy-overwrite/.prettierignore +23 -0
- package/typescript/copy-overwrite/.prettierrc.json +22 -0
- package/typescript/copy-overwrite/.versionrc +42 -0
- package/typescript/copy-overwrite/.yamllint +20 -0
- package/typescript/copy-overwrite/commitlint.config.js +11 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/README.md +149 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/__tests__/enforce-statement-order.test.js +468 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/index.js +23 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/package.json +10 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/rules/enforce-statement-order.js +157 -0
- package/typescript/copy-overwrite/eslint.config.mjs +390 -0
- package/typescript/copy-overwrite/eslint.ignore.config.json +57 -0
- package/typescript/copy-overwrite/eslint.thresholds.config.json +5 -0
- package/typescript/github-rulesets/base.json +106 -0
- package/typescript/merge/.claude/settings.json +28 -0
- package/typescript/merge/package.json +71 -0
|
@@ -0,0 +1,1737 @@
|
|
|
1
|
+
# -----------------------------------------------------------------------------
|
|
2
|
+
# Quality Checks Workflow
|
|
3
|
+
# -----------------------------------------------------------------------------
|
|
4
|
+
# ⚠️ WARNING: THIS FILE IS AUTO-GENERATED. DO NOT EDIT MANUALLY! ⚠️
|
|
5
|
+
# Any changes may be overwritten by the generation process.
|
|
6
|
+
# This workflow runs various quality checks on the codebase including:
|
|
7
|
+
# - Linting
|
|
8
|
+
# - Type checking
|
|
9
|
+
# - Unit tests
|
|
10
|
+
# - Format checking
|
|
11
|
+
# - Build verification
|
|
12
|
+
# - Security scanning
|
|
13
|
+
# - AI-powered code quality and security analysis (non-blocking)
|
|
14
|
+
# - Enterprise security tools:
|
|
15
|
+
# - SonarCloud SAST analysis
|
|
16
|
+
# - Snyk dependency vulnerability scanning
|
|
17
|
+
# - GitGuardian secret detection
|
|
18
|
+
# - FOSSA license compliance checking
|
|
19
|
+
# - E2E testing:
|
|
20
|
+
# - Playwright web E2E tests (auto-detects playwright.config.ts)
|
|
21
|
+
# - Maestro Cloud mobile E2E tests (requires MAESTRO_API_KEY, project ID, and app binary)
|
|
22
|
+
#
|
|
23
|
+
# Example usage in another workflow:
|
|
24
|
+
# ```yaml
|
|
25
|
+
# quality:
|
|
26
|
+
# uses: ./.github/workflows/quality.yml
|
|
27
|
+
# with:
|
|
28
|
+
# node_version: '22.21.1'
|
|
29
|
+
# package_manager: 'npm'
|
|
30
|
+
# skip_security: true
|
|
31
|
+
# secrets:
|
|
32
|
+
# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # For SonarCloud SAST
|
|
33
|
+
# SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} # For Snyk dependency scanning
|
|
34
|
+
# GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }} # For secret detection
|
|
35
|
+
# FOSSA_API_KEY: ${{ secrets.FOSSA_API_KEY }} # For license compliance
|
|
36
|
+
# MAESTRO_API_KEY: ${{ secrets.MAESTRO_API_KEY }} # For Maestro Cloud E2E
|
|
37
|
+
# ```
|
|
38
|
+
|
|
39
|
+
name: 🔍 Quality Checks
|
|
40
|
+
|
|
41
|
+
on:
|
|
42
|
+
workflow_call:
|
|
43
|
+
inputs:
|
|
44
|
+
node_version:
|
|
45
|
+
description: 'Node.js version to use'
|
|
46
|
+
required: false
|
|
47
|
+
default: '20.x'
|
|
48
|
+
type: string
|
|
49
|
+
package_manager:
|
|
50
|
+
description: 'Package manager to use (npm, yarn, or bun)'
|
|
51
|
+
required: false
|
|
52
|
+
default: 'npm'
|
|
53
|
+
type: string
|
|
54
|
+
skip_lint:
|
|
55
|
+
description: 'Skip the lint job'
|
|
56
|
+
required: false
|
|
57
|
+
default: false
|
|
58
|
+
type: boolean
|
|
59
|
+
skip_typecheck:
|
|
60
|
+
description: 'Skip the typecheck job'
|
|
61
|
+
required: false
|
|
62
|
+
default: false
|
|
63
|
+
type: boolean
|
|
64
|
+
skip_test:
|
|
65
|
+
description: 'Skip the test job'
|
|
66
|
+
required: false
|
|
67
|
+
default: false
|
|
68
|
+
type: boolean
|
|
69
|
+
skip_format:
|
|
70
|
+
description: 'Skip the format check job'
|
|
71
|
+
required: false
|
|
72
|
+
default: false
|
|
73
|
+
type: boolean
|
|
74
|
+
skip_build:
|
|
75
|
+
description: 'Skip the build job'
|
|
76
|
+
required: false
|
|
77
|
+
default: false
|
|
78
|
+
type: boolean
|
|
79
|
+
skip_security:
|
|
80
|
+
description: 'Skip the security scan job'
|
|
81
|
+
required: false
|
|
82
|
+
default: false
|
|
83
|
+
type: boolean
|
|
84
|
+
skip_jobs:
|
|
85
|
+
description: 'Jobs to skip (comma-separated: lint,typecheck,test,test:unit,test:integration,test:e2e,maestro_e2e,playwright_e2e,format,build,npm_security_scan,github_issue)'
|
|
86
|
+
required: false
|
|
87
|
+
default: ''
|
|
88
|
+
type: string
|
|
89
|
+
working_directory:
|
|
90
|
+
description: 'Directory to run commands in (if not root)'
|
|
91
|
+
required: false
|
|
92
|
+
default: ''
|
|
93
|
+
type: string
|
|
94
|
+
compliance_framework:
|
|
95
|
+
description: 'Compliance framework to validate against (none, soc2, iso27001, hipaa, pci-dss)'
|
|
96
|
+
required: false
|
|
97
|
+
default: 'none'
|
|
98
|
+
type: string
|
|
99
|
+
require_approval:
|
|
100
|
+
description: 'Require approval for production deployments (uses GitHub environments)'
|
|
101
|
+
required: false
|
|
102
|
+
default: false
|
|
103
|
+
type: boolean
|
|
104
|
+
audit_retention_days:
|
|
105
|
+
description: 'Number of days to retain audit logs'
|
|
106
|
+
required: false
|
|
107
|
+
default: 90
|
|
108
|
+
type: number
|
|
109
|
+
generate_evidence_package:
|
|
110
|
+
description: 'Generate compliance evidence package for audits'
|
|
111
|
+
required: false
|
|
112
|
+
default: false
|
|
113
|
+
type: boolean
|
|
114
|
+
approval_environment:
|
|
115
|
+
description: 'GitHub environment name for approval gate (must exist in repo settings). Set to empty string to skip even when require_approval is true.'
|
|
116
|
+
required: false
|
|
117
|
+
default: 'production'
|
|
118
|
+
type: string
|
|
119
|
+
maestro_app_file:
|
|
120
|
+
description: 'Path to app binary for Maestro Cloud E2E tests (e.g., app-release.apk or App.app). Required for Maestro tests to run.'
|
|
121
|
+
required: false
|
|
122
|
+
default: ''
|
|
123
|
+
type: string
|
|
124
|
+
maestro_workspace:
|
|
125
|
+
description: 'Path to Maestro workspace directory containing test flows (default: .maestro)'
|
|
126
|
+
required: false
|
|
127
|
+
default: '.maestro'
|
|
128
|
+
type: string
|
|
129
|
+
maestro_include_tags:
|
|
130
|
+
description: 'Comma-separated tags to include in Maestro test run (e.g., smoke,critical)'
|
|
131
|
+
required: false
|
|
132
|
+
default: ''
|
|
133
|
+
type: string
|
|
134
|
+
maestro_project_id:
|
|
135
|
+
description: 'Maestro Cloud project ID. Required for Maestro tests to run.'
|
|
136
|
+
required: false
|
|
137
|
+
default: ''
|
|
138
|
+
type: string
|
|
139
|
+
secrets:
|
|
140
|
+
SONAR_TOKEN:
|
|
141
|
+
description: 'SonarCloud token for SAST analysis'
|
|
142
|
+
required: false
|
|
143
|
+
SNYK_TOKEN:
|
|
144
|
+
description: 'Snyk token for dependency vulnerability scanning'
|
|
145
|
+
required: false
|
|
146
|
+
GITGUARDIAN_API_KEY:
|
|
147
|
+
description: 'GitGuardian API key for secret detection'
|
|
148
|
+
required: false
|
|
149
|
+
FOSSA_API_KEY:
|
|
150
|
+
description: 'FOSSA API key for license compliance checking'
|
|
151
|
+
required: false
|
|
152
|
+
MAESTRO_API_KEY:
|
|
153
|
+
description: 'Maestro Cloud API key for mobile E2E testing'
|
|
154
|
+
required: false
|
|
155
|
+
|
|
156
|
+
# Concurrency is managed by the parent workflow that calls this one
|
|
157
|
+
# This avoids deadlocks between parent and child workflows
|
|
158
|
+
|
|
159
|
+
jobs:
|
|
160
|
+
# Central dependency installation job to optimize performance
|
|
161
|
+
install_dependencies:
|
|
162
|
+
name: 📦 Install Dependencies
|
|
163
|
+
runs-on: ubuntu-latest
|
|
164
|
+
outputs:
|
|
165
|
+
cache-key: ${{ steps.cache.outputs.cache-key }}
|
|
166
|
+
cache-hit: ${{ steps.cache.outputs.cache-hit }}
|
|
167
|
+
steps:
|
|
168
|
+
- name: 📥 Checkout repository
|
|
169
|
+
uses: actions/checkout@v4
|
|
170
|
+
|
|
171
|
+
- name: 🔧 Setup Node.js
|
|
172
|
+
uses: actions/setup-node@v4
|
|
173
|
+
with:
|
|
174
|
+
node-version: ${{ inputs.node_version }}
|
|
175
|
+
cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
|
|
176
|
+
|
|
177
|
+
- name: 🍞 Setup Bun
|
|
178
|
+
if: inputs.package_manager == 'bun'
|
|
179
|
+
uses: oven-sh/setup-bun@v2
|
|
180
|
+
with:
|
|
181
|
+
bun-version: latest
|
|
182
|
+
|
|
183
|
+
- name: 🔑 Generate cache key
|
|
184
|
+
id: cache
|
|
185
|
+
run: |
|
|
186
|
+
if [ -f "package-lock.json" ]; then
|
|
187
|
+
echo "cache-key=${{ runner.os }}-node-${{ inputs.node_version }}-npm-${{ hashFiles('package-lock.json') }}" >> $GITHUB_OUTPUT
|
|
188
|
+
elif [ -f "yarn.lock" ]; then
|
|
189
|
+
echo "cache-key=${{ runner.os }}-node-${{ inputs.node_version }}-yarn-${{ hashFiles('yarn.lock') }}" >> $GITHUB_OUTPUT
|
|
190
|
+
elif [ -f "bun.lockb" ]; then
|
|
191
|
+
echo "cache-key=${{ runner.os }}-node-${{ inputs.node_version }}-bun-${{ hashFiles('bun.lockb') }}" >> $GITHUB_OUTPUT
|
|
192
|
+
else
|
|
193
|
+
echo "cache-key=${{ runner.os }}-node-${{ inputs.node_version }}-${{ inputs.package_manager }}-${{ hashFiles('package.json') }}" >> $GITHUB_OUTPUT
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
- name: 📦 Cache node_modules
|
|
197
|
+
id: cache-modules
|
|
198
|
+
uses: actions/cache@v4
|
|
199
|
+
with:
|
|
200
|
+
path: |
|
|
201
|
+
node_modules
|
|
202
|
+
~/.npm
|
|
203
|
+
~/.yarn/cache
|
|
204
|
+
~/.bun/install/cache
|
|
205
|
+
key: ${{ steps.cache.outputs.cache-key }}
|
|
206
|
+
restore-keys: |
|
|
207
|
+
${{ runner.os }}-node-${{ inputs.node_version }}-${{ inputs.package_manager }}-
|
|
208
|
+
${{ runner.os }}-node-${{ inputs.node_version }}-
|
|
209
|
+
${{ runner.os }}-node-
|
|
210
|
+
|
|
211
|
+
- name: 📥 Install dependencies
|
|
212
|
+
if: steps.cache-modules.outputs.cache-hit != 'true'
|
|
213
|
+
run: |
|
|
214
|
+
if [ "${{ inputs.package_manager }}" = "npm" ]; then
|
|
215
|
+
npm ci
|
|
216
|
+
elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
|
|
217
|
+
yarn install --frozen-lockfile
|
|
218
|
+
elif [ "${{ inputs.package_manager }}" = "bun" ]; then
|
|
219
|
+
bun install --frozen-lockfile
|
|
220
|
+
else
|
|
221
|
+
echo "Unsupported package manager: ${{ inputs.package_manager }}"
|
|
222
|
+
exit 1
|
|
223
|
+
fi
|
|
224
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
225
|
+
|
|
226
|
+
- name: 📤 Upload dependencies artifact
|
|
227
|
+
uses: actions/upload-artifact@v4
|
|
228
|
+
with:
|
|
229
|
+
name: node-modules-${{ github.run_id }}
|
|
230
|
+
path: |
|
|
231
|
+
node_modules
|
|
232
|
+
package.json
|
|
233
|
+
package-lock.json
|
|
234
|
+
yarn.lock
|
|
235
|
+
bun.lockb
|
|
236
|
+
retention-days: 1
|
|
237
|
+
if-no-files-found: error
|
|
238
|
+
|
|
239
|
+
- name: 📊 Cache status
|
|
240
|
+
run: |
|
|
241
|
+
echo "cache-hit=${{ steps.cache-modules.outputs.cache-hit }}" >> $GITHUB_OUTPUT
|
|
242
|
+
id: cache-status
|
|
243
|
+
lint:
|
|
244
|
+
name: 🧹 Lint
|
|
245
|
+
runs-on: ubuntu-latest
|
|
246
|
+
timeout-minutes: 15
|
|
247
|
+
if: ${{ !inputs.skip_lint && !contains(inputs.skip_jobs, 'lint') }}
|
|
248
|
+
|
|
249
|
+
steps:
|
|
250
|
+
- name: 📥 Checkout repository
|
|
251
|
+
uses: actions/checkout@v4
|
|
252
|
+
|
|
253
|
+
- name: 🔧 Setup Node.js
|
|
254
|
+
uses: actions/setup-node@v4
|
|
255
|
+
with:
|
|
256
|
+
node-version: ${{ inputs.node_version }}
|
|
257
|
+
cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
|
|
258
|
+
|
|
259
|
+
- name: 🍞 Setup Bun
|
|
260
|
+
if: inputs.package_manager == 'bun'
|
|
261
|
+
uses: oven-sh/setup-bun@v2
|
|
262
|
+
with:
|
|
263
|
+
bun-version: latest
|
|
264
|
+
|
|
265
|
+
- name: 📥 Install dependencies
|
|
266
|
+
run: |
|
|
267
|
+
if [ "${{ inputs.package_manager }}" = "npm" ]; then
|
|
268
|
+
npm ci
|
|
269
|
+
elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
|
|
270
|
+
yarn install --frozen-lockfile
|
|
271
|
+
elif [ "${{ inputs.package_manager }}" = "bun" ]; then
|
|
272
|
+
bun install --frozen-lockfile
|
|
273
|
+
fi
|
|
274
|
+
|
|
275
|
+
- name: 🧹 Run linter
|
|
276
|
+
run: ${{ inputs.package_manager }} run lint
|
|
277
|
+
env:
|
|
278
|
+
NODE_OPTIONS: --max-old-space-size=6144
|
|
279
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
280
|
+
|
|
281
|
+
typecheck:
|
|
282
|
+
name: 🔍 Type Check
|
|
283
|
+
runs-on: ubuntu-latest
|
|
284
|
+
timeout-minutes: 15
|
|
285
|
+
if: ${{ !inputs.skip_typecheck && !contains(inputs.skip_jobs, 'typecheck') }}
|
|
286
|
+
|
|
287
|
+
steps:
|
|
288
|
+
- name: 📥 Checkout repository
|
|
289
|
+
uses: actions/checkout@v4
|
|
290
|
+
|
|
291
|
+
- name: 🔧 Setup Node.js
|
|
292
|
+
uses: actions/setup-node@v4
|
|
293
|
+
with:
|
|
294
|
+
node-version: ${{ inputs.node_version }}
|
|
295
|
+
cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
|
|
296
|
+
|
|
297
|
+
- name: 🍞 Setup Bun
|
|
298
|
+
if: inputs.package_manager == 'bun'
|
|
299
|
+
uses: oven-sh/setup-bun@v2
|
|
300
|
+
with:
|
|
301
|
+
bun-version: latest
|
|
302
|
+
|
|
303
|
+
- name: 📦 Install dependencies
|
|
304
|
+
run: |
|
|
305
|
+
if [ "${{ inputs.package_manager }}" = "npm" ]; then
|
|
306
|
+
npm ci
|
|
307
|
+
elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
|
|
308
|
+
yarn install --frozen-lockfile
|
|
309
|
+
elif [ "${{ inputs.package_manager }}" = "bun" ]; then
|
|
310
|
+
bun install --frozen-lockfile
|
|
311
|
+
fi
|
|
312
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
313
|
+
|
|
314
|
+
- name: 🔍 Run type check
|
|
315
|
+
run: ${{ inputs.package_manager }} run typecheck
|
|
316
|
+
env:
|
|
317
|
+
NODE_OPTIONS: --max-old-space-size=6144
|
|
318
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
319
|
+
|
|
320
|
+
test:
|
|
321
|
+
name: 🧪 Run Tests
|
|
322
|
+
runs-on: ubuntu-latest
|
|
323
|
+
timeout-minutes: 20
|
|
324
|
+
if: ${{ !inputs.skip_test && !contains(inputs.skip_jobs, 'test') }}
|
|
325
|
+
|
|
326
|
+
steps:
|
|
327
|
+
- name: 📥 Checkout repository
|
|
328
|
+
uses: actions/checkout@v4
|
|
329
|
+
|
|
330
|
+
- name: 🔧 Setup Node.js
|
|
331
|
+
uses: actions/setup-node@v4
|
|
332
|
+
with:
|
|
333
|
+
node-version: ${{ inputs.node_version }}
|
|
334
|
+
cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
|
|
335
|
+
|
|
336
|
+
- name: 🍞 Setup Bun
|
|
337
|
+
if: inputs.package_manager == 'bun'
|
|
338
|
+
uses: oven-sh/setup-bun@v2
|
|
339
|
+
with:
|
|
340
|
+
bun-version: latest
|
|
341
|
+
|
|
342
|
+
- name: 📦 Install dependencies
|
|
343
|
+
run: |
|
|
344
|
+
if [ "${{ inputs.package_manager }}" = "npm" ]; then
|
|
345
|
+
npm ci
|
|
346
|
+
elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
|
|
347
|
+
yarn install --frozen-lockfile
|
|
348
|
+
elif [ "${{ inputs.package_manager }}" = "bun" ]; then
|
|
349
|
+
bun install --frozen-lockfile
|
|
350
|
+
fi
|
|
351
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
352
|
+
|
|
353
|
+
- name: 🧪 Run tests
|
|
354
|
+
run: |
|
|
355
|
+
if [ "${{ inputs.package_manager }}" = "npm" ]; then
|
|
356
|
+
npm test
|
|
357
|
+
elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
|
|
358
|
+
yarn test
|
|
359
|
+
elif [ "${{ inputs.package_manager }}" = "bun" ]; then
|
|
360
|
+
bun test
|
|
361
|
+
fi
|
|
362
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
363
|
+
|
|
364
|
+
test_unit:
|
|
365
|
+
name: 🧪 Run Unit Tests
|
|
366
|
+
runs-on: ubuntu-latest
|
|
367
|
+
timeout-minutes: 45
|
|
368
|
+
if: ${{ !inputs.skip_test && !contains(inputs.skip_jobs, 'test:unit') }}
|
|
369
|
+
|
|
370
|
+
steps:
|
|
371
|
+
- name: 📥 Checkout repository
|
|
372
|
+
uses: actions/checkout@v4
|
|
373
|
+
|
|
374
|
+
- name: 🔧 Setup Node.js
|
|
375
|
+
uses: actions/setup-node@v4
|
|
376
|
+
with:
|
|
377
|
+
node-version: ${{ inputs.node_version }}
|
|
378
|
+
cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
|
|
379
|
+
|
|
380
|
+
- name: 🍞 Setup Bun
|
|
381
|
+
if: inputs.package_manager == 'bun'
|
|
382
|
+
uses: oven-sh/setup-bun@v2
|
|
383
|
+
with:
|
|
384
|
+
bun-version: latest
|
|
385
|
+
|
|
386
|
+
- name: 📦 Install dependencies
|
|
387
|
+
run: |
|
|
388
|
+
if [ "${{ inputs.package_manager }}" = "npm" ]; then
|
|
389
|
+
npm ci
|
|
390
|
+
elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
|
|
391
|
+
yarn install --frozen-lockfile
|
|
392
|
+
elif [ "${{ inputs.package_manager }}" = "bun" ]; then
|
|
393
|
+
bun install --frozen-lockfile
|
|
394
|
+
fi
|
|
395
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
396
|
+
|
|
397
|
+
- name: 🔍 Check for test:unit script
|
|
398
|
+
id: check_script
|
|
399
|
+
run: |
|
|
400
|
+
if [ -f "package.json" ]; then
|
|
401
|
+
if grep -q '"test:unit"' package.json; then
|
|
402
|
+
echo "exists=true" >> $GITHUB_OUTPUT
|
|
403
|
+
else
|
|
404
|
+
echo "exists=false" >> $GITHUB_OUTPUT
|
|
405
|
+
fi
|
|
406
|
+
else
|
|
407
|
+
echo "exists=false" >> $GITHUB_OUTPUT
|
|
408
|
+
fi
|
|
409
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
410
|
+
|
|
411
|
+
- name: 🧪 Run unit tests with coverage
|
|
412
|
+
if: steps.check_script.outputs.exists == 'true'
|
|
413
|
+
run: |
|
|
414
|
+
if [ "${{ inputs.package_manager }}" = "npm" ]; then
|
|
415
|
+
npm run test:cov
|
|
416
|
+
elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
|
|
417
|
+
yarn test:cov
|
|
418
|
+
elif [ "${{ inputs.package_manager }}" = "bun" ]; then
|
|
419
|
+
bun run test:cov
|
|
420
|
+
fi
|
|
421
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
422
|
+
|
|
423
|
+
- name: ⏭️ Skip unit tests (no test:unit script)
|
|
424
|
+
if: steps.check_script.outputs.exists == 'false'
|
|
425
|
+
run: echo "Skipping unit tests - test:unit script not found in package.json"
|
|
426
|
+
|
|
427
|
+
test_integration:
|
|
428
|
+
name: 🧪 Run Integration Tests
|
|
429
|
+
runs-on: ubuntu-latest
|
|
430
|
+
timeout-minutes: 30
|
|
431
|
+
if: ${{ !inputs.skip_test && !contains(inputs.skip_jobs, 'test:integration') }}
|
|
432
|
+
|
|
433
|
+
steps:
|
|
434
|
+
- name: 📥 Checkout repository
|
|
435
|
+
uses: actions/checkout@v4
|
|
436
|
+
|
|
437
|
+
- name: 🔧 Setup Node.js
|
|
438
|
+
uses: actions/setup-node@v4
|
|
439
|
+
with:
|
|
440
|
+
node-version: ${{ inputs.node_version }}
|
|
441
|
+
cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
|
|
442
|
+
|
|
443
|
+
- name: 🍞 Setup Bun
|
|
444
|
+
if: inputs.package_manager == 'bun'
|
|
445
|
+
uses: oven-sh/setup-bun@v2
|
|
446
|
+
with:
|
|
447
|
+
bun-version: latest
|
|
448
|
+
|
|
449
|
+
- name: 📦 Install dependencies
|
|
450
|
+
run: |
|
|
451
|
+
if [ "${{ inputs.package_manager }}" = "npm" ]; then
|
|
452
|
+
npm ci
|
|
453
|
+
elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
|
|
454
|
+
yarn install --frozen-lockfile
|
|
455
|
+
elif [ "${{ inputs.package_manager }}" = "bun" ]; then
|
|
456
|
+
bun install --frozen-lockfile
|
|
457
|
+
fi
|
|
458
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
459
|
+
|
|
460
|
+
- name: 🔍 Check for test:integration script
|
|
461
|
+
id: check_script
|
|
462
|
+
run: |
|
|
463
|
+
if [ -f "package.json" ]; then
|
|
464
|
+
if grep -q '"test:integration"' package.json; then
|
|
465
|
+
echo "exists=true" >> $GITHUB_OUTPUT
|
|
466
|
+
else
|
|
467
|
+
echo "exists=false" >> $GITHUB_OUTPUT
|
|
468
|
+
fi
|
|
469
|
+
else
|
|
470
|
+
echo "exists=false" >> $GITHUB_OUTPUT
|
|
471
|
+
fi
|
|
472
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
473
|
+
|
|
474
|
+
- name: 🧪 Run integration tests
|
|
475
|
+
if: steps.check_script.outputs.exists == 'true'
|
|
476
|
+
run: |
|
|
477
|
+
if [ "${{ inputs.package_manager }}" = "npm" ]; then
|
|
478
|
+
npm run test:integration
|
|
479
|
+
elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
|
|
480
|
+
yarn test:integration
|
|
481
|
+
elif [ "${{ inputs.package_manager }}" = "bun" ]; then
|
|
482
|
+
bun run test:integration
|
|
483
|
+
fi
|
|
484
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
485
|
+
|
|
486
|
+
- name: ⏭️ Skip integration tests (no test:integration script)
|
|
487
|
+
if: steps.check_script.outputs.exists == 'false'
|
|
488
|
+
run: echo "Skipping integration tests - test:integration script not found in package.json"
|
|
489
|
+
|
|
490
|
+
test_e2e:
|
|
491
|
+
name: 🧪 Run E2E Tests
|
|
492
|
+
runs-on: ubuntu-latest
|
|
493
|
+
timeout-minutes: 40
|
|
494
|
+
if: ${{ !inputs.skip_test && !contains(inputs.skip_jobs, 'test:e2e') }}
|
|
495
|
+
|
|
496
|
+
steps:
|
|
497
|
+
- name: 📥 Checkout repository
|
|
498
|
+
uses: actions/checkout@v4
|
|
499
|
+
|
|
500
|
+
- name: 🔧 Setup Node.js
|
|
501
|
+
uses: actions/setup-node@v4
|
|
502
|
+
with:
|
|
503
|
+
node-version: ${{ inputs.node_version }}
|
|
504
|
+
cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
|
|
505
|
+
|
|
506
|
+
- name: 🍞 Setup Bun
|
|
507
|
+
if: inputs.package_manager == 'bun'
|
|
508
|
+
uses: oven-sh/setup-bun@v2
|
|
509
|
+
with:
|
|
510
|
+
bun-version: latest
|
|
511
|
+
|
|
512
|
+
- name: 📦 Install dependencies
|
|
513
|
+
run: |
|
|
514
|
+
if [ "${{ inputs.package_manager }}" = "npm" ]; then
|
|
515
|
+
npm ci
|
|
516
|
+
elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
|
|
517
|
+
yarn install --frozen-lockfile
|
|
518
|
+
elif [ "${{ inputs.package_manager }}" = "bun" ]; then
|
|
519
|
+
bun install --frozen-lockfile
|
|
520
|
+
fi
|
|
521
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
522
|
+
|
|
523
|
+
- name: 🔍 Check for test:e2e script
|
|
524
|
+
id: check_script
|
|
525
|
+
run: |
|
|
526
|
+
if [ -f "package.json" ]; then
|
|
527
|
+
if grep -q '"test:e2e"' package.json; then
|
|
528
|
+
echo "exists=true" >> $GITHUB_OUTPUT
|
|
529
|
+
else
|
|
530
|
+
echo "exists=false" >> $GITHUB_OUTPUT
|
|
531
|
+
fi
|
|
532
|
+
else
|
|
533
|
+
echo "exists=false" >> $GITHUB_OUTPUT
|
|
534
|
+
fi
|
|
535
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
536
|
+
|
|
537
|
+
- name: 🧪 Run E2E tests
|
|
538
|
+
if: steps.check_script.outputs.exists == 'true'
|
|
539
|
+
run: |
|
|
540
|
+
if [ "${{ inputs.package_manager }}" = "npm" ]; then
|
|
541
|
+
npm run test:e2e
|
|
542
|
+
elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
|
|
543
|
+
yarn test:e2e
|
|
544
|
+
elif [ "${{ inputs.package_manager }}" = "bun" ]; then
|
|
545
|
+
bun run test:e2e
|
|
546
|
+
fi
|
|
547
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
548
|
+
|
|
549
|
+
- name: ⏭️ Skip E2E tests (no test:e2e script)
|
|
550
|
+
if: steps.check_script.outputs.exists == 'false'
|
|
551
|
+
run: echo "Skipping E2E tests - test:e2e script not found in package.json"
|
|
552
|
+
|
|
553
|
+
maestro_e2e:
|
|
554
|
+
name: 📱 Maestro Cloud E2E Tests
|
|
555
|
+
runs-on: ubuntu-latest
|
|
556
|
+
timeout-minutes: 30
|
|
557
|
+
if: ${{ !inputs.skip_test && !contains(inputs.skip_jobs, 'maestro_e2e') }}
|
|
558
|
+
|
|
559
|
+
steps:
|
|
560
|
+
- name: 📥 Checkout repository
|
|
561
|
+
uses: actions/checkout@v4
|
|
562
|
+
|
|
563
|
+
- name: 🔍 Check for Maestro API key
|
|
564
|
+
id: check_maestro
|
|
565
|
+
run: |
|
|
566
|
+
if [[ -z "${MAESTRO_API_KEY// }" ]]; then
|
|
567
|
+
echo "has_token=false" >> $GITHUB_OUTPUT
|
|
568
|
+
echo "⚠️ MAESTRO_API_KEY is not configured"
|
|
569
|
+
else
|
|
570
|
+
echo "has_token=true" >> $GITHUB_OUTPUT
|
|
571
|
+
echo "✅ MAESTRO_API_KEY is available"
|
|
572
|
+
fi
|
|
573
|
+
env:
|
|
574
|
+
MAESTRO_API_KEY: ${{ secrets.MAESTRO_API_KEY }}
|
|
575
|
+
|
|
576
|
+
- name: 🔍 Check for project ID
|
|
577
|
+
id: check_project_id
|
|
578
|
+
if: steps.check_maestro.outputs.has_token == 'true'
|
|
579
|
+
run: |
|
|
580
|
+
PROJECT_ID="${{ inputs.maestro_project_id }}"
|
|
581
|
+
if [[ -z "$PROJECT_ID" ]]; then
|
|
582
|
+
echo "has_project_id=false" >> $GITHUB_OUTPUT
|
|
583
|
+
echo "⚠️ maestro_project_id input not provided"
|
|
584
|
+
else
|
|
585
|
+
echo "has_project_id=true" >> $GITHUB_OUTPUT
|
|
586
|
+
echo "✅ Project ID configured"
|
|
587
|
+
fi
|
|
588
|
+
|
|
589
|
+
- name: 🔍 Check for app file
|
|
590
|
+
id: check_app_file
|
|
591
|
+
if: steps.check_maestro.outputs.has_token == 'true' && steps.check_project_id.outputs.has_project_id == 'true'
|
|
592
|
+
run: |
|
|
593
|
+
APP_FILE="${{ inputs.maestro_app_file }}"
|
|
594
|
+
if [[ -z "$APP_FILE" ]]; then
|
|
595
|
+
echo "has_app_file=false" >> $GITHUB_OUTPUT
|
|
596
|
+
echo "⚠️ maestro_app_file input not provided"
|
|
597
|
+
elif [[ -f "$APP_FILE" ]]; then
|
|
598
|
+
echo "has_app_file=true" >> $GITHUB_OUTPUT
|
|
599
|
+
echo "✅ App file found: $APP_FILE"
|
|
600
|
+
else
|
|
601
|
+
echo "has_app_file=false" >> $GITHUB_OUTPUT
|
|
602
|
+
echo "⚠️ App file not found at: $APP_FILE"
|
|
603
|
+
fi
|
|
604
|
+
|
|
605
|
+
- name: 📱 Run Maestro Cloud tests
|
|
606
|
+
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'
|
|
607
|
+
uses: mobile-dev-inc/action-maestro-cloud@v2.0.1
|
|
608
|
+
with:
|
|
609
|
+
api-key: ${{ secrets.MAESTRO_API_KEY }}
|
|
610
|
+
project-id: ${{ inputs.maestro_project_id }}
|
|
611
|
+
app-file: ${{ inputs.maestro_app_file }}
|
|
612
|
+
workspace: ${{ inputs.maestro_workspace }}
|
|
613
|
+
include-tags: ${{ inputs.maestro_include_tags }}
|
|
614
|
+
|
|
615
|
+
- name: 📱 Maestro Tests Skipped (no API key)
|
|
616
|
+
if: steps.check_maestro.outputs.has_token != 'true'
|
|
617
|
+
run: |
|
|
618
|
+
echo "::warning::Maestro Cloud E2E tests skipped - MAESTRO_API_KEY not configured"
|
|
619
|
+
echo "To enable Maestro Cloud testing, add MAESTRO_API_KEY to your repository secrets"
|
|
620
|
+
|
|
621
|
+
- name: 📱 Maestro Tests Skipped (no project ID)
|
|
622
|
+
if: steps.check_maestro.outputs.has_token == 'true' && steps.check_project_id.outputs.has_project_id != 'true'
|
|
623
|
+
run: |
|
|
624
|
+
echo "::warning::Maestro Cloud E2E tests skipped - maestro_project_id not provided"
|
|
625
|
+
echo "To run Maestro tests, provide the maestro_project_id input with your Maestro Cloud project ID"
|
|
626
|
+
|
|
627
|
+
- name: 📱 Maestro Tests Skipped (no app file)
|
|
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
|
+
run: |
|
|
630
|
+
echo "::warning::Maestro Cloud E2E tests skipped - no app file provided"
|
|
631
|
+
echo "To run Maestro tests, provide the maestro_app_file input with the path to your app binary"
|
|
632
|
+
|
|
633
|
+
playwright_e2e:
|
|
634
|
+
name: 🎭 Playwright E2E Tests
|
|
635
|
+
runs-on: ubuntu-latest
|
|
636
|
+
timeout-minutes: 30
|
|
637
|
+
if: ${{ !inputs.skip_test && !contains(inputs.skip_jobs, 'playwright_e2e') }}
|
|
638
|
+
|
|
639
|
+
steps:
|
|
640
|
+
- name: 📥 Checkout repository
|
|
641
|
+
uses: actions/checkout@v4
|
|
642
|
+
|
|
643
|
+
- name: 🔧 Setup Node.js
|
|
644
|
+
uses: actions/setup-node@v4
|
|
645
|
+
with:
|
|
646
|
+
node-version: ${{ inputs.node_version }}
|
|
647
|
+
cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
|
|
648
|
+
|
|
649
|
+
- name: 🍞 Setup Bun
|
|
650
|
+
if: inputs.package_manager == 'bun'
|
|
651
|
+
uses: oven-sh/setup-bun@v2
|
|
652
|
+
with:
|
|
653
|
+
bun-version: latest
|
|
654
|
+
|
|
655
|
+
- name: 📦 Install dependencies
|
|
656
|
+
run: |
|
|
657
|
+
if [ "${{ inputs.package_manager }}" = "npm" ]; then
|
|
658
|
+
npm ci
|
|
659
|
+
elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
|
|
660
|
+
yarn install --frozen-lockfile
|
|
661
|
+
elif [ "${{ inputs.package_manager }}" = "bun" ]; then
|
|
662
|
+
bun install --frozen-lockfile
|
|
663
|
+
fi
|
|
664
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
665
|
+
|
|
666
|
+
- name: 🔍 Check for Playwright config
|
|
667
|
+
id: check_playwright
|
|
668
|
+
run: |
|
|
669
|
+
if [ -f "playwright.config.ts" ] || [ -f "playwright.config.js" ]; then
|
|
670
|
+
echo "has_config=true" >> $GITHUB_OUTPUT
|
|
671
|
+
echo "✅ Playwright config found"
|
|
672
|
+
else
|
|
673
|
+
echo "has_config=false" >> $GITHUB_OUTPUT
|
|
674
|
+
echo "⚠️ No Playwright config found"
|
|
675
|
+
fi
|
|
676
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
677
|
+
|
|
678
|
+
- name: 🌐 Build web export
|
|
679
|
+
if: steps.check_playwright.outputs.has_config == 'true'
|
|
680
|
+
run: npx expo export --platform web
|
|
681
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
682
|
+
|
|
683
|
+
- name: 🎭 Install Playwright browsers
|
|
684
|
+
if: steps.check_playwright.outputs.has_config == 'true'
|
|
685
|
+
run: npx playwright install --with-deps
|
|
686
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
687
|
+
|
|
688
|
+
- name: 🎭 Run Playwright tests
|
|
689
|
+
if: steps.check_playwright.outputs.has_config == 'true'
|
|
690
|
+
run: npx playwright test
|
|
691
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
692
|
+
|
|
693
|
+
- name: 📤 Upload Playwright report
|
|
694
|
+
if: steps.check_playwright.outputs.has_config == 'true' && always()
|
|
695
|
+
uses: actions/upload-artifact@v4
|
|
696
|
+
with:
|
|
697
|
+
name: playwright-report-${{ github.run_id }}
|
|
698
|
+
path: ${{ inputs.working_directory || '.' }}/playwright-report
|
|
699
|
+
retention-days: 14
|
|
700
|
+
|
|
701
|
+
- name: 🎭 Playwright Tests Skipped (no config)
|
|
702
|
+
if: steps.check_playwright.outputs.has_config != 'true'
|
|
703
|
+
run: |
|
|
704
|
+
echo "::warning::Playwright E2E tests skipped - no playwright.config.ts or playwright.config.js found"
|
|
705
|
+
echo "To enable Playwright testing, add a Playwright configuration file to your project"
|
|
706
|
+
|
|
707
|
+
format:
|
|
708
|
+
name: 📐 Check Formatting
|
|
709
|
+
runs-on: ubuntu-latest
|
|
710
|
+
timeout-minutes: 10
|
|
711
|
+
if: ${{ !inputs.skip_format && !contains(inputs.skip_jobs, 'format') }}
|
|
712
|
+
|
|
713
|
+
steps:
|
|
714
|
+
- name: 📥 Checkout repository
|
|
715
|
+
uses: actions/checkout@v4
|
|
716
|
+
|
|
717
|
+
- name: 🔧 Setup Node.js
|
|
718
|
+
uses: actions/setup-node@v4
|
|
719
|
+
with:
|
|
720
|
+
node-version: ${{ inputs.node_version }}
|
|
721
|
+
cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
|
|
722
|
+
|
|
723
|
+
- name: 🍞 Setup Bun
|
|
724
|
+
if: inputs.package_manager == 'bun'
|
|
725
|
+
uses: oven-sh/setup-bun@v2
|
|
726
|
+
with:
|
|
727
|
+
bun-version: latest
|
|
728
|
+
|
|
729
|
+
- name: 📦 Install dependencies
|
|
730
|
+
run: |
|
|
731
|
+
if [ "${{ inputs.package_manager }}" = "npm" ]; then
|
|
732
|
+
npm ci
|
|
733
|
+
elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
|
|
734
|
+
yarn install --frozen-lockfile
|
|
735
|
+
elif [ "${{ inputs.package_manager }}" = "bun" ]; then
|
|
736
|
+
bun install --frozen-lockfile
|
|
737
|
+
fi
|
|
738
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
739
|
+
|
|
740
|
+
- name: 📐 Check formatting
|
|
741
|
+
run: ${{ inputs.package_manager }} run format:check
|
|
742
|
+
env:
|
|
743
|
+
NODE_OPTIONS: --max-old-space-size=6144
|
|
744
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
745
|
+
|
|
746
|
+
build:
|
|
747
|
+
name: 🏗️ Build
|
|
748
|
+
runs-on: ubuntu-latest
|
|
749
|
+
timeout-minutes: 20
|
|
750
|
+
if: ${{ !inputs.skip_build && !contains(inputs.skip_jobs, 'build') }}
|
|
751
|
+
|
|
752
|
+
steps:
|
|
753
|
+
- name: 📥 Checkout repository
|
|
754
|
+
uses: actions/checkout@v4
|
|
755
|
+
|
|
756
|
+
- name: 🔧 Setup Node.js
|
|
757
|
+
uses: actions/setup-node@v4
|
|
758
|
+
with:
|
|
759
|
+
node-version: ${{ inputs.node_version }}
|
|
760
|
+
cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
|
|
761
|
+
|
|
762
|
+
- name: 🍞 Setup Bun
|
|
763
|
+
if: inputs.package_manager == 'bun'
|
|
764
|
+
uses: oven-sh/setup-bun@v2
|
|
765
|
+
with:
|
|
766
|
+
bun-version: latest
|
|
767
|
+
|
|
768
|
+
- name: 📦 Install dependencies
|
|
769
|
+
run: |
|
|
770
|
+
if [ "${{ inputs.package_manager }}" = "npm" ]; then
|
|
771
|
+
npm ci
|
|
772
|
+
elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
|
|
773
|
+
yarn install --frozen-lockfile
|
|
774
|
+
elif [ "${{ inputs.package_manager }}" = "bun" ]; then
|
|
775
|
+
bun install --frozen-lockfile
|
|
776
|
+
fi
|
|
777
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
778
|
+
|
|
779
|
+
- name: 🏗️ Build project
|
|
780
|
+
run: ${{ inputs.package_manager }} run build
|
|
781
|
+
env:
|
|
782
|
+
NODE_OPTIONS: --max-old-space-size=6144
|
|
783
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
784
|
+
|
|
785
|
+
- name: 📤 Upload build artifacts
|
|
786
|
+
uses: actions/upload-artifact@v4
|
|
787
|
+
if: success()
|
|
788
|
+
with:
|
|
789
|
+
name: build-output-${{ github.run_id }}
|
|
790
|
+
path: |
|
|
791
|
+
dist
|
|
792
|
+
build
|
|
793
|
+
.next
|
|
794
|
+
out
|
|
795
|
+
retention-days: 1
|
|
796
|
+
if-no-files-found: ignore
|
|
797
|
+
|
|
798
|
+
npm_security_scan:
|
|
799
|
+
name: 🔒 Security Scan
|
|
800
|
+
runs-on: ubuntu-latest
|
|
801
|
+
timeout-minutes: 15
|
|
802
|
+
if: ${{ !inputs.skip_security && !contains(inputs.skip_jobs, 'npm_security_scan') }}
|
|
803
|
+
steps:
|
|
804
|
+
- name: 📥 Checkout repository
|
|
805
|
+
uses: actions/checkout@v4
|
|
806
|
+
|
|
807
|
+
- name: 🔧 Setup Node.js
|
|
808
|
+
uses: actions/setup-node@v4
|
|
809
|
+
with:
|
|
810
|
+
node-version: ${{ inputs.node_version }}
|
|
811
|
+
cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
|
|
812
|
+
|
|
813
|
+
- name: 🍞 Setup Bun
|
|
814
|
+
if: inputs.package_manager == 'bun'
|
|
815
|
+
uses: oven-sh/setup-bun@v2
|
|
816
|
+
with:
|
|
817
|
+
bun-version: latest
|
|
818
|
+
|
|
819
|
+
- name: 📦 Install dependencies
|
|
820
|
+
run: |
|
|
821
|
+
if [ "${{ inputs.package_manager }}" = "npm" ]; then
|
|
822
|
+
npm ci
|
|
823
|
+
elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
|
|
824
|
+
yarn install --frozen-lockfile
|
|
825
|
+
elif [ "${{ inputs.package_manager }}" = "bun" ]; then
|
|
826
|
+
bun install --frozen-lockfile
|
|
827
|
+
fi
|
|
828
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
829
|
+
|
|
830
|
+
- name: 🔒 Run security audit
|
|
831
|
+
run: |
|
|
832
|
+
if [ "${{ inputs.package_manager }}" = "npm" ]; then
|
|
833
|
+
# Run audit and only fail on high or critical vulnerabilities
|
|
834
|
+
npm audit --production --audit-level=high || exit_code=$?
|
|
835
|
+
if [ "${exit_code:-0}" -ne 0 ]; then
|
|
836
|
+
echo "::warning::Found high or critical vulnerabilities"
|
|
837
|
+
exit $exit_code
|
|
838
|
+
fi
|
|
839
|
+
elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
|
|
840
|
+
# Yarn audit outputs newline-delimited JSON, so we need to parse each line
|
|
841
|
+
|
|
842
|
+
# Excluding GHSA-5j98-mcp5-4vw2 (CVE-2025-64756): glob CLI command injection
|
|
843
|
+
# This vulnerability only affects the glob CLI (--cmd flag), not library usage
|
|
844
|
+
# We only use glob as a library through Babel and other tools - never invoke CLI
|
|
845
|
+
# Risk: None - vulnerable code path is not executed in our application
|
|
846
|
+
|
|
847
|
+
# Excluding GHSA-w532-jxjh-hjhj (CVE-2025-29907): jsPDF ReDoS in addImage
|
|
848
|
+
# Excluding GHSA-8mvj-3j78-4qmw (CVE-2025-57810): jsPDF DoS in addImage
|
|
849
|
+
# These require user control of addImage input with malicious data
|
|
850
|
+
# Our usage is controlled and doesn't expose this attack vector
|
|
851
|
+
# Tracked for upgrade in separate security remediation ticket
|
|
852
|
+
|
|
853
|
+
# Excluding GHSA-36jr-mh4h-2g58: d3-color ReDoS
|
|
854
|
+
# Transitive dependency through react-native-svg-charts (unmaintained)
|
|
855
|
+
# Replacement charting library evaluation in progress
|
|
856
|
+
# Risk: Low - color parsing is not user-controlled in our implementation
|
|
857
|
+
|
|
858
|
+
# Filter by both GHSA ID and CVE ID for robustness
|
|
859
|
+
yarn audit --groups dependencies --json | jq -r 'select(.type == "auditAdvisory") | select(.data.advisory.severity == "high" or .data.advisory.severity == "critical") | select((.data.advisory.github_advisory_id == "GHSA-5j98-mcp5-4vw2" or .data.advisory.github_advisory_id == "GHSA-w532-jxjh-hjhj" or .data.advisory.github_advisory_id == "GHSA-8mvj-3j78-4qmw" or .data.advisory.github_advisory_id == "GHSA-36jr-mh4h-2g58" or (.data.advisory.cves | any(. == "CVE-2025-64756" or . == "CVE-2025-29907" or . == "CVE-2025-57810"))) | not) | .data.advisory' > high_vulns.json
|
|
860
|
+
if [ -s high_vulns.json ]; then
|
|
861
|
+
echo "::error::Found high or critical vulnerabilities:"
|
|
862
|
+
cat high_vulns.json
|
|
863
|
+
exit 1
|
|
864
|
+
else
|
|
865
|
+
echo "::notice::No high or critical vulnerabilities found (excluding known false positives)"
|
|
866
|
+
fi
|
|
867
|
+
elif [ "${{ inputs.package_manager }}" = "bun" ]; then
|
|
868
|
+
# Bun's 'bun pm scan' requires a configured scanner in bunfig.toml
|
|
869
|
+
# Fall back to npm audit which works with package.json
|
|
870
|
+
echo "::notice::Using npm audit fallback for bun projects"
|
|
871
|
+
|
|
872
|
+
# npm audit requires a lockfile - generate temporary one if needed
|
|
873
|
+
TEMP_LOCKFILE=false
|
|
874
|
+
if [ ! -f "package-lock.json" ]; then
|
|
875
|
+
echo "Generating temporary package-lock.json for audit..."
|
|
876
|
+
npm i --package-lock-only --ignore-scripts --legacy-peer-deps --silent 2>/dev/null
|
|
877
|
+
TEMP_LOCKFILE=true
|
|
878
|
+
fi
|
|
879
|
+
|
|
880
|
+
# Excluding GHSA-8qq5-rm4j-mr97: node-tar path sanitization vulnerability
|
|
881
|
+
# This is a nested dependency in @expo/cli that bun resolves to the patched version (7.5.3)
|
|
882
|
+
# npm audit generates its own lockfile and doesn't respect bun's resolutions
|
|
883
|
+
# Risk: None - bun.lock shows tar@7.5.3 is used, not the vulnerable version
|
|
884
|
+
VULN_COUNT=$(npm audit --omit=dev --json 2>/dev/null | jq '
|
|
885
|
+
.vulnerabilities | to_entries | map(select(
|
|
886
|
+
.value.severity == "high" or .value.severity == "critical"
|
|
887
|
+
)) | map(select(
|
|
888
|
+
.value.via | all(. | if type == "object" then (.url == "https://github.com/advisories/GHSA-8qq5-rm4j-mr97" | not) else true end)
|
|
889
|
+
)) | length
|
|
890
|
+
')
|
|
891
|
+
if [ "$VULN_COUNT" -gt 0 ] 2>/dev/null; then
|
|
892
|
+
exit_code=1
|
|
893
|
+
fi
|
|
894
|
+
|
|
895
|
+
# Clean up temporary lockfile
|
|
896
|
+
if [ "$TEMP_LOCKFILE" = "true" ]; then
|
|
897
|
+
rm -f package-lock.json
|
|
898
|
+
fi
|
|
899
|
+
|
|
900
|
+
if [ "${exit_code:-0}" -ne 0 ]; then
|
|
901
|
+
echo "::warning::Found high or critical vulnerabilities (excluding known false positives)"
|
|
902
|
+
exit $exit_code
|
|
903
|
+
fi
|
|
904
|
+
echo "::notice::No high or critical vulnerabilities found (excluding known false positives)"
|
|
905
|
+
else
|
|
906
|
+
echo "Unsupported package manager: ${{ inputs.package_manager }}"
|
|
907
|
+
exit 1
|
|
908
|
+
fi
|
|
909
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
910
|
+
|
|
911
|
+
sonarcloud:
|
|
912
|
+
name: 🔍 SonarCloud SAST
|
|
913
|
+
runs-on: ubuntu-latest
|
|
914
|
+
timeout-minutes: 20
|
|
915
|
+
if: ${{ !inputs.skip_security && !contains(inputs.skip_jobs, 'sonarcloud') }}
|
|
916
|
+
steps:
|
|
917
|
+
- name: 📥 Checkout repository
|
|
918
|
+
uses: actions/checkout@v4
|
|
919
|
+
with:
|
|
920
|
+
fetch-depth: 0 # Full depth for proper analysis
|
|
921
|
+
|
|
922
|
+
- name: 🔍 Check for SonarCloud token
|
|
923
|
+
id: check_sonar
|
|
924
|
+
run: |
|
|
925
|
+
# Debug: Show if env var is set (will show *** if present)
|
|
926
|
+
echo "SONAR_TOKEN env var: '${SONAR_TOKEN:+SET}'"
|
|
927
|
+
echo "SONAR_TOKEN length: ${#SONAR_TOKEN}"
|
|
928
|
+
|
|
929
|
+
# More robust check - empty, unset, or just whitespace
|
|
930
|
+
if [[ -z "${SONAR_TOKEN// }" ]]; then
|
|
931
|
+
echo "has_token=false" >> $GITHUB_OUTPUT
|
|
932
|
+
echo "⚠️ SONAR_TOKEN is not configured"
|
|
933
|
+
else
|
|
934
|
+
echo "has_token=true" >> $GITHUB_OUTPUT
|
|
935
|
+
echo "✅ SONAR_TOKEN is available"
|
|
936
|
+
fi
|
|
937
|
+
env:
|
|
938
|
+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
939
|
+
|
|
940
|
+
- name: 📊 SonarCloud Scan
|
|
941
|
+
if: steps.check_sonar.outputs.has_token == 'true'
|
|
942
|
+
uses: SonarSource/sonarqube-scan-action@v6.0.0
|
|
943
|
+
env:
|
|
944
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
945
|
+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
946
|
+
with:
|
|
947
|
+
args: -Dsonar.qualitygate.wait=true
|
|
948
|
+
|
|
949
|
+
- name: 📊 SonarCloud Scan Skipped
|
|
950
|
+
if: steps.check_sonar.outputs.has_token != 'true'
|
|
951
|
+
run: |
|
|
952
|
+
echo "::warning::SonarCloud analysis skipped - SONAR_TOKEN not configured"
|
|
953
|
+
echo "To enable SonarCloud analysis, add SONAR_TOKEN to your repository secrets"
|
|
954
|
+
|
|
955
|
+
snyk:
|
|
956
|
+
name: 🛡️ Snyk Dependency Scan
|
|
957
|
+
runs-on: ubuntu-latest
|
|
958
|
+
timeout-minutes: 20
|
|
959
|
+
if: ${{ !inputs.skip_security && !contains(inputs.skip_jobs, 'snyk') }}
|
|
960
|
+
steps:
|
|
961
|
+
- name: 📥 Checkout repository
|
|
962
|
+
uses: actions/checkout@v4
|
|
963
|
+
|
|
964
|
+
- name: 🔍 Check for Snyk token
|
|
965
|
+
id: check_snyk
|
|
966
|
+
run: |
|
|
967
|
+
if [[ -z "${SNYK_TOKEN// }" ]]; then
|
|
968
|
+
echo "has_token=false" >> $GITHUB_OUTPUT
|
|
969
|
+
echo "⚠️ SNYK_TOKEN is not configured"
|
|
970
|
+
else
|
|
971
|
+
echo "has_token=true" >> $GITHUB_OUTPUT
|
|
972
|
+
echo "✅ SNYK_TOKEN is available"
|
|
973
|
+
fi
|
|
974
|
+
env:
|
|
975
|
+
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
|
976
|
+
|
|
977
|
+
- name: 🔧 Setup Node.js
|
|
978
|
+
if: steps.check_snyk.outputs.has_token == 'true'
|
|
979
|
+
uses: actions/setup-node@v4
|
|
980
|
+
with:
|
|
981
|
+
node-version: ${{ inputs.node_version }}
|
|
982
|
+
cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
|
|
983
|
+
|
|
984
|
+
- name: 🍞 Setup Bun
|
|
985
|
+
if: inputs.package_manager == 'bun'
|
|
986
|
+
uses: oven-sh/setup-bun@v2
|
|
987
|
+
with:
|
|
988
|
+
bun-version: latest
|
|
989
|
+
|
|
990
|
+
- name: 📦 Install dependencies
|
|
991
|
+
if: steps.check_snyk.outputs.has_token == 'true'
|
|
992
|
+
run: |
|
|
993
|
+
if [ "${{ inputs.package_manager }}" = "npm" ]; then
|
|
994
|
+
npm ci
|
|
995
|
+
elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
|
|
996
|
+
yarn install --frozen-lockfile
|
|
997
|
+
elif [ "${{ inputs.package_manager }}" = "bun" ]; then
|
|
998
|
+
bun install --frozen-lockfile
|
|
999
|
+
fi
|
|
1000
|
+
working-directory: ${{ inputs.working_directory || '.' }}
|
|
1001
|
+
|
|
1002
|
+
- name: 🛡️ Run Snyk to check for vulnerabilities
|
|
1003
|
+
if: steps.check_snyk.outputs.has_token == 'true'
|
|
1004
|
+
uses: snyk/actions/node@master
|
|
1005
|
+
env:
|
|
1006
|
+
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
|
1007
|
+
with:
|
|
1008
|
+
args: --severity-threshold=high --all-projects
|
|
1009
|
+
|
|
1010
|
+
- name: 📤 Upload Snyk results
|
|
1011
|
+
if: steps.check_snyk.outputs.has_token == 'true' && always()
|
|
1012
|
+
uses: actions/upload-artifact@v4
|
|
1013
|
+
with:
|
|
1014
|
+
name: snyk-results
|
|
1015
|
+
path: snyk-results.json
|
|
1016
|
+
retention-days: 14
|
|
1017
|
+
|
|
1018
|
+
- name: 🛡️ Snyk Scan Skipped
|
|
1019
|
+
if: steps.check_snyk.outputs.has_token != 'true'
|
|
1020
|
+
run: |
|
|
1021
|
+
echo "::warning::Snyk dependency scan skipped - SNYK_TOKEN not configured"
|
|
1022
|
+
echo "To enable Snyk vulnerability scanning, add SNYK_TOKEN to your repository secrets"
|
|
1023
|
+
|
|
1024
|
+
secret_scanning:
|
|
1025
|
+
name: 🔐 GitGuardian Secret Detection
|
|
1026
|
+
runs-on: ubuntu-latest
|
|
1027
|
+
timeout-minutes: 20
|
|
1028
|
+
if: ${{ !inputs.skip_security && !contains(inputs.skip_jobs, 'secret_scanning') }}
|
|
1029
|
+
steps:
|
|
1030
|
+
- name: 📥 Checkout repository
|
|
1031
|
+
uses: actions/checkout@v4
|
|
1032
|
+
with:
|
|
1033
|
+
fetch-depth: 0 # Full depth for scanning history
|
|
1034
|
+
|
|
1035
|
+
- name: 🔍 Check for GitGuardian API key
|
|
1036
|
+
id: check_gitguardian
|
|
1037
|
+
run: |
|
|
1038
|
+
if [[ -z "${GITGUARDIAN_API_KEY// }" ]]; then
|
|
1039
|
+
echo "has_token=false" >> $GITHUB_OUTPUT
|
|
1040
|
+
echo "⚠️ GITGUARDIAN_API_KEY is not configured"
|
|
1041
|
+
else
|
|
1042
|
+
echo "has_token=true" >> $GITHUB_OUTPUT
|
|
1043
|
+
echo "✅ GITGUARDIAN_API_KEY is available"
|
|
1044
|
+
fi
|
|
1045
|
+
env:
|
|
1046
|
+
GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}
|
|
1047
|
+
|
|
1048
|
+
- name: 🔐 GitGuardian scan
|
|
1049
|
+
if: steps.check_gitguardian.outputs.has_token == 'true'
|
|
1050
|
+
uses: GitGuardian/ggshield-action@master
|
|
1051
|
+
env:
|
|
1052
|
+
GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}
|
|
1053
|
+
with:
|
|
1054
|
+
args: --show-secrets --all-policies
|
|
1055
|
+
|
|
1056
|
+
- name: 🔐 GitGuardian Scan Skipped
|
|
1057
|
+
if: steps.check_gitguardian.outputs.has_token != 'true'
|
|
1058
|
+
run: |
|
|
1059
|
+
echo "::warning::GitGuardian secret detection skipped - GITGUARDIAN_API_KEY not configured"
|
|
1060
|
+
echo "To enable secret detection, add GITGUARDIAN_API_KEY to your repository secrets"
|
|
1061
|
+
|
|
1062
|
+
license_compliance:
|
|
1063
|
+
name: 📜 FOSSA License Check
|
|
1064
|
+
runs-on: ubuntu-latest
|
|
1065
|
+
timeout-minutes: 20
|
|
1066
|
+
if: ${{ !inputs.skip_security && !contains(inputs.skip_jobs, 'license_compliance') }}
|
|
1067
|
+
steps:
|
|
1068
|
+
- name: 📥 Checkout repository
|
|
1069
|
+
uses: actions/checkout@v4
|
|
1070
|
+
|
|
1071
|
+
- name: 🔍 Check for FOSSA API key
|
|
1072
|
+
id: check_fossa
|
|
1073
|
+
run: |
|
|
1074
|
+
if [[ -z "${FOSSA_API_KEY// }" ]]; then
|
|
1075
|
+
echo "has_token=false" >> $GITHUB_OUTPUT
|
|
1076
|
+
echo "⚠️ FOSSA_API_KEY is not configured"
|
|
1077
|
+
else
|
|
1078
|
+
echo "has_token=true" >> $GITHUB_OUTPUT
|
|
1079
|
+
echo "✅ FOSSA_API_KEY is available"
|
|
1080
|
+
fi
|
|
1081
|
+
env:
|
|
1082
|
+
FOSSA_API_KEY: ${{ secrets.FOSSA_API_KEY }}
|
|
1083
|
+
|
|
1084
|
+
- name: 📜 Run FOSSA license scan
|
|
1085
|
+
if: steps.check_fossa.outputs.has_token == 'true'
|
|
1086
|
+
uses: fossas/fossa-action@main
|
|
1087
|
+
with:
|
|
1088
|
+
api-key: ${{ secrets.FOSSA_API_KEY }}
|
|
1089
|
+
# Optional: specify the team
|
|
1090
|
+
# team: 'your-team-name'
|
|
1091
|
+
|
|
1092
|
+
- name: 📜 FOSSA Scan Skipped
|
|
1093
|
+
if: steps.check_fossa.outputs.has_token != 'true'
|
|
1094
|
+
run: |
|
|
1095
|
+
echo "::warning::FOSSA license compliance check skipped - FOSSA_API_KEY not configured"
|
|
1096
|
+
echo "To enable license compliance checking, add FOSSA_API_KEY to your repository secrets"
|
|
1097
|
+
|
|
1098
|
+
# Enterprise security tools summary
|
|
1099
|
+
security_tools_summary:
|
|
1100
|
+
name: 🔒 Security Tools Summary
|
|
1101
|
+
runs-on: ubuntu-latest
|
|
1102
|
+
if: always() && (needs.sonarcloud.result != 'skipped' || needs.snyk.result != 'skipped' || needs.secret_scanning.result != 'skipped' || needs.license_compliance.result != 'skipped')
|
|
1103
|
+
needs: [sonarcloud, snyk, secret_scanning, license_compliance]
|
|
1104
|
+
steps:
|
|
1105
|
+
- name: 📝 Generate security tools summary
|
|
1106
|
+
run: |
|
|
1107
|
+
echo "# 🔒 Enterprise Security Tools Summary" >> $GITHUB_STEP_SUMMARY
|
|
1108
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1109
|
+
echo "## Security Scan Results" >> $GITHUB_STEP_SUMMARY
|
|
1110
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1111
|
+
|
|
1112
|
+
# SonarCloud SAST status
|
|
1113
|
+
if [ "${{ needs.sonarcloud.result }}" == "skipped" ]; then
|
|
1114
|
+
echo "- 🔍 **SonarCloud SAST**: ⏭️ Skipped (no token)" >> $GITHUB_STEP_SUMMARY
|
|
1115
|
+
elif [ "${{ needs.sonarcloud.result }}" == "success" ]; then
|
|
1116
|
+
echo "- 🔍 **SonarCloud SAST**: ✅ Passed" >> $GITHUB_STEP_SUMMARY
|
|
1117
|
+
else
|
|
1118
|
+
echo "- 🔍 **SonarCloud SAST**: ❌ Failed" >> $GITHUB_STEP_SUMMARY
|
|
1119
|
+
fi
|
|
1120
|
+
|
|
1121
|
+
# Snyk Dependency Scan status
|
|
1122
|
+
if [ "${{ needs.snyk.result }}" == "skipped" ]; then
|
|
1123
|
+
echo "- 🛡️ **Snyk Dependency Scan**: ⏭️ Skipped (no token)" >> $GITHUB_STEP_SUMMARY
|
|
1124
|
+
elif [ "${{ needs.snyk.result }}" == "success" ]; then
|
|
1125
|
+
echo "- 🛡️ **Snyk Dependency Scan**: ✅ Passed" >> $GITHUB_STEP_SUMMARY
|
|
1126
|
+
else
|
|
1127
|
+
echo "- 🛡️ **Snyk Dependency Scan**: ❌ Failed" >> $GITHUB_STEP_SUMMARY
|
|
1128
|
+
fi
|
|
1129
|
+
|
|
1130
|
+
# GitGuardian Secret Detection status
|
|
1131
|
+
if [ "${{ needs.secret_scanning.result }}" == "skipped" ]; then
|
|
1132
|
+
echo "- 🔐 **GitGuardian Secret Detection**: ⏭️ Skipped (no token)" >> $GITHUB_STEP_SUMMARY
|
|
1133
|
+
elif [ "${{ needs.secret_scanning.result }}" == "success" ]; then
|
|
1134
|
+
echo "- 🔐 **GitGuardian Secret Detection**: ✅ Passed" >> $GITHUB_STEP_SUMMARY
|
|
1135
|
+
else
|
|
1136
|
+
echo "- 🔐 **GitGuardian Secret Detection**: ❌ Failed" >> $GITHUB_STEP_SUMMARY
|
|
1137
|
+
fi
|
|
1138
|
+
|
|
1139
|
+
# FOSSA License Compliance status
|
|
1140
|
+
if [ "${{ needs.license_compliance.result }}" == "skipped" ]; then
|
|
1141
|
+
echo "- 📜 **FOSSA License Compliance**: ⏭️ Skipped (no token)" >> $GITHUB_STEP_SUMMARY
|
|
1142
|
+
elif [ "${{ needs.license_compliance.result }}" == "success" ]; then
|
|
1143
|
+
echo "- 📜 **FOSSA License Compliance**: ✅ Passed" >> $GITHUB_STEP_SUMMARY
|
|
1144
|
+
else
|
|
1145
|
+
echo "- 📜 **FOSSA License Compliance**: ❌ Failed" >> $GITHUB_STEP_SUMMARY
|
|
1146
|
+
fi
|
|
1147
|
+
|
|
1148
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1149
|
+
echo "## 📊 Security Posture" >> $GITHUB_STEP_SUMMARY
|
|
1150
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1151
|
+
|
|
1152
|
+
# Count active tools
|
|
1153
|
+
ACTIVE_TOOLS=0
|
|
1154
|
+
PASSED_TOOLS=0
|
|
1155
|
+
|
|
1156
|
+
if [ "${{ needs.sonarcloud.result }}" != "skipped" ]; then
|
|
1157
|
+
ACTIVE_TOOLS=$((ACTIVE_TOOLS + 1))
|
|
1158
|
+
if [ "${{ needs.sonarcloud.result }}" == "success" ]; then
|
|
1159
|
+
PASSED_TOOLS=$((PASSED_TOOLS + 1))
|
|
1160
|
+
fi
|
|
1161
|
+
fi
|
|
1162
|
+
|
|
1163
|
+
if [ "${{ needs.snyk.result }}" != "skipped" ]; then
|
|
1164
|
+
ACTIVE_TOOLS=$((ACTIVE_TOOLS + 1))
|
|
1165
|
+
if [ "${{ needs.snyk.result }}" == "success" ]; then
|
|
1166
|
+
PASSED_TOOLS=$((PASSED_TOOLS + 1))
|
|
1167
|
+
fi
|
|
1168
|
+
fi
|
|
1169
|
+
|
|
1170
|
+
if [ "${{ needs.secret_scanning.result }}" != "skipped" ]; then
|
|
1171
|
+
ACTIVE_TOOLS=$((ACTIVE_TOOLS + 1))
|
|
1172
|
+
if [ "${{ needs.secret_scanning.result }}" == "success" ]; then
|
|
1173
|
+
PASSED_TOOLS=$((PASSED_TOOLS + 1))
|
|
1174
|
+
fi
|
|
1175
|
+
fi
|
|
1176
|
+
|
|
1177
|
+
if [ "${{ needs.license_compliance.result }}" != "skipped" ]; then
|
|
1178
|
+
ACTIVE_TOOLS=$((ACTIVE_TOOLS + 1))
|
|
1179
|
+
if [ "${{ needs.license_compliance.result }}" == "success" ]; then
|
|
1180
|
+
PASSED_TOOLS=$((PASSED_TOOLS + 1))
|
|
1181
|
+
fi
|
|
1182
|
+
fi
|
|
1183
|
+
|
|
1184
|
+
echo "- **Active Security Tools**: $ACTIVE_TOOLS / 4" >> $GITHUB_STEP_SUMMARY
|
|
1185
|
+
echo "- **Passed Checks**: $PASSED_TOOLS / $ACTIVE_TOOLS" >> $GITHUB_STEP_SUMMARY
|
|
1186
|
+
|
|
1187
|
+
if [ $ACTIVE_TOOLS -gt 0 ]; then
|
|
1188
|
+
SCORE=$((PASSED_TOOLS * 100 / ACTIVE_TOOLS))
|
|
1189
|
+
echo "- **Security Score**: $SCORE%" >> $GITHUB_STEP_SUMMARY
|
|
1190
|
+
fi
|
|
1191
|
+
|
|
1192
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1193
|
+
echo "## 🔧 Configuration" >> $GITHUB_STEP_SUMMARY
|
|
1194
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1195
|
+
echo "To enable additional security tools, add the following secrets to your repository:" >> $GITHUB_STEP_SUMMARY
|
|
1196
|
+
echo "- \`SONAR_TOKEN\` - [Get from SonarCloud](https://sonarcloud.io/)" >> $GITHUB_STEP_SUMMARY
|
|
1197
|
+
echo "- \`SNYK_TOKEN\` - [Get from Snyk](https://app.snyk.io/)" >> $GITHUB_STEP_SUMMARY
|
|
1198
|
+
echo "- \`GITGUARDIAN_API_KEY\` - [Get from GitGuardian](https://dashboard.gitguardian.com/)" >> $GITHUB_STEP_SUMMARY
|
|
1199
|
+
echo "- \`FOSSA_API_KEY\` - [Get from FOSSA](https://app.fossa.com/)" >> $GITHUB_STEP_SUMMARY
|
|
1200
|
+
|
|
1201
|
+
# Overall status
|
|
1202
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1203
|
+
echo "## 🎯 Overall Status" >> $GITHUB_STEP_SUMMARY
|
|
1204
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1205
|
+
|
|
1206
|
+
if [ $ACTIVE_TOOLS -eq 0 ]; then
|
|
1207
|
+
echo "⚠️ **No security tools are active.** Configure tokens to enable security scanning." >> $GITHUB_STEP_SUMMARY
|
|
1208
|
+
elif [ $PASSED_TOOLS -eq $ACTIVE_TOOLS ]; then
|
|
1209
|
+
echo "✅ **All active security tools passed!**" >> $GITHUB_STEP_SUMMARY
|
|
1210
|
+
else
|
|
1211
|
+
echo "❌ **Some security tools failed.** Review the results above and address any issues." >> $GITHUB_STEP_SUMMARY
|
|
1212
|
+
fi
|
|
1213
|
+
|
|
1214
|
+
# Compliance validation job
|
|
1215
|
+
compliance_validation:
|
|
1216
|
+
name: ✅ Compliance Validation
|
|
1217
|
+
runs-on: ubuntu-latest
|
|
1218
|
+
if: ${{ inputs.compliance_framework != 'none' }}
|
|
1219
|
+
needs:
|
|
1220
|
+
[
|
|
1221
|
+
lint,
|
|
1222
|
+
typecheck,
|
|
1223
|
+
test,
|
|
1224
|
+
test_unit,
|
|
1225
|
+
test_integration,
|
|
1226
|
+
test_e2e,
|
|
1227
|
+
maestro_e2e,
|
|
1228
|
+
playwright_e2e,
|
|
1229
|
+
format,
|
|
1230
|
+
build,
|
|
1231
|
+
npm_security_scan,
|
|
1232
|
+
sonarcloud,
|
|
1233
|
+
snyk,
|
|
1234
|
+
secret_scanning,
|
|
1235
|
+
license_compliance,
|
|
1236
|
+
]
|
|
1237
|
+
steps:
|
|
1238
|
+
- name: 📋 Validate compliance framework
|
|
1239
|
+
id: validate_framework
|
|
1240
|
+
run: |
|
|
1241
|
+
echo "🏢 Validating compliance for framework: ${{ inputs.compliance_framework }}"
|
|
1242
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1243
|
+
echo "# 🏢 Compliance Validation Report" >> $GITHUB_STEP_SUMMARY
|
|
1244
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1245
|
+
echo "**Framework**: ${{ inputs.compliance_framework }}" >> $GITHUB_STEP_SUMMARY
|
|
1246
|
+
echo "**Date**: $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_STEP_SUMMARY
|
|
1247
|
+
echo "**Run ID**: ${{ github.run_id }}" >> $GITHUB_STEP_SUMMARY
|
|
1248
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1249
|
+
|
|
1250
|
+
- name: 🔍 SOC 2 Control Validation
|
|
1251
|
+
if: contains(inputs.compliance_framework, 'soc2')
|
|
1252
|
+
run: |
|
|
1253
|
+
echo "## SOC 2 Type II Controls" >> $GITHUB_STEP_SUMMARY
|
|
1254
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1255
|
+
|
|
1256
|
+
# CC6.1 - Logical and Physical Access Controls
|
|
1257
|
+
if [ "${{ needs.secret_scanning.result }}" == "success" ] || [ "${{ needs.secret_scanning.result }}" == "skipped" ]; then
|
|
1258
|
+
echo "✅ **CC6.1**: No hardcoded credentials detected" >> $GITHUB_STEP_SUMMARY
|
|
1259
|
+
else
|
|
1260
|
+
echo "❌ **CC6.1**: Secret scanning failed - potential credential exposure" >> $GITHUB_STEP_SUMMARY
|
|
1261
|
+
fi
|
|
1262
|
+
|
|
1263
|
+
# CC7.1 - System Operations
|
|
1264
|
+
if [ "${{ needs.npm_security_scan.result }}" == "success" ] && [ "${{ needs.snyk.result }}" == "success" ]; then
|
|
1265
|
+
echo "✅ **CC7.1**: Vulnerability management controls validated" >> $GITHUB_STEP_SUMMARY
|
|
1266
|
+
else
|
|
1267
|
+
echo "⚠️ **CC7.1**: Security scanning incomplete - manual review required" >> $GITHUB_STEP_SUMMARY
|
|
1268
|
+
fi
|
|
1269
|
+
|
|
1270
|
+
# CC7.2 - System Monitoring
|
|
1271
|
+
echo "✅ **CC7.2**: Audit logging enabled (retention: ${{ inputs.audit_retention_days }} days)" >> $GITHUB_STEP_SUMMARY
|
|
1272
|
+
|
|
1273
|
+
# CC8.1 - Change Management
|
|
1274
|
+
if [ "${{ github.event_name }}" == "pull_request" ]; then
|
|
1275
|
+
echo "✅ **CC8.1**: Change management process followed (PR #${{ github.event.pull_request.number }})" >> $GITHUB_STEP_SUMMARY
|
|
1276
|
+
else
|
|
1277
|
+
echo "⚠️ **CC8.1**: Direct push to branch - review change management policy" >> $GITHUB_STEP_SUMMARY
|
|
1278
|
+
fi
|
|
1279
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1280
|
+
|
|
1281
|
+
- name: 🔍 ISO 27001 Control Validation
|
|
1282
|
+
if: contains(inputs.compliance_framework, 'iso27001')
|
|
1283
|
+
run: |
|
|
1284
|
+
echo "## ISO 27001:2022 Controls" >> $GITHUB_STEP_SUMMARY
|
|
1285
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1286
|
+
|
|
1287
|
+
# A.8.1 - Asset Management
|
|
1288
|
+
if [ "${{ needs.license_compliance.result }}" == "success" ]; then
|
|
1289
|
+
echo "✅ **A.8.1**: Software asset inventory validated" >> $GITHUB_STEP_SUMMARY
|
|
1290
|
+
else
|
|
1291
|
+
echo "⚠️ **A.8.1**: License compliance check skipped" >> $GITHUB_STEP_SUMMARY
|
|
1292
|
+
fi
|
|
1293
|
+
|
|
1294
|
+
# A.12.1 - Operational Security
|
|
1295
|
+
if [ "${{ needs.sonarcloud.result }}" == "success" ]; then
|
|
1296
|
+
echo "✅ **A.12.1**: Code quality and security controls validated" >> $GITHUB_STEP_SUMMARY
|
|
1297
|
+
else
|
|
1298
|
+
echo "⚠️ **A.12.1**: Static analysis skipped" >> $GITHUB_STEP_SUMMARY
|
|
1299
|
+
fi
|
|
1300
|
+
|
|
1301
|
+
# A.14.2 - Security in Development
|
|
1302
|
+
SECURITY_TESTS=0
|
|
1303
|
+
[ "${{ needs.npm_security_scan.result }}" == "success" ] && SECURITY_TESTS=$((SECURITY_TESTS + 1))
|
|
1304
|
+
[ "${{ needs.snyk.result }}" == "success" ] && SECURITY_TESTS=$((SECURITY_TESTS + 1))
|
|
1305
|
+
[ "${{ needs.secret_scanning.result }}" == "success" ] && SECURITY_TESTS=$((SECURITY_TESTS + 1))
|
|
1306
|
+
|
|
1307
|
+
if [ $SECURITY_TESTS -ge 2 ]; then
|
|
1308
|
+
echo "✅ **A.14.2**: Security testing in SDLC validated ($SECURITY_TESTS/3 tools)" >> $GITHUB_STEP_SUMMARY
|
|
1309
|
+
else
|
|
1310
|
+
echo "❌ **A.14.2**: Insufficient security testing ($SECURITY_TESTS/3 tools)" >> $GITHUB_STEP_SUMMARY
|
|
1311
|
+
fi
|
|
1312
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1313
|
+
|
|
1314
|
+
- name: 🔍 HIPAA Control Validation
|
|
1315
|
+
if: contains(inputs.compliance_framework, 'hipaa')
|
|
1316
|
+
run: |
|
|
1317
|
+
echo "## HIPAA Security Rule Controls" >> $GITHUB_STEP_SUMMARY
|
|
1318
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1319
|
+
|
|
1320
|
+
# Access Control - 164.312(a)(1)
|
|
1321
|
+
echo "✅ **164.312(a)(1)**: Access controls implemented via GitHub permissions" >> $GITHUB_STEP_SUMMARY
|
|
1322
|
+
|
|
1323
|
+
# Audit Controls - 164.312(b)
|
|
1324
|
+
echo "✅ **164.312(b)**: Audit logging enabled with ${{ inputs.audit_retention_days }}-day retention" >> $GITHUB_STEP_SUMMARY
|
|
1325
|
+
|
|
1326
|
+
# Integrity - 164.312(c)(1)
|
|
1327
|
+
if [ "${{ needs.test.result }}" == "success" ]; then
|
|
1328
|
+
echo "✅ **164.312(c)(1)**: Data integrity validated through testing" >> $GITHUB_STEP_SUMMARY
|
|
1329
|
+
else
|
|
1330
|
+
echo "❌ **164.312(c)(1)**: Testing failed or skipped" >> $GITHUB_STEP_SUMMARY
|
|
1331
|
+
fi
|
|
1332
|
+
|
|
1333
|
+
# Transmission Security - 164.312(e)(1)
|
|
1334
|
+
echo "✅ **164.312(e)(1)**: All transmissions use HTTPS/TLS" >> $GITHUB_STEP_SUMMARY
|
|
1335
|
+
|
|
1336
|
+
# PHI Detection Warning
|
|
1337
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1338
|
+
echo "⚠️ **Note**: Automated PHI detection not implemented. Manual review required for PHI handling." >> $GITHUB_STEP_SUMMARY
|
|
1339
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1340
|
+
|
|
1341
|
+
- name: 🔍 PCI-DSS Control Validation
|
|
1342
|
+
if: contains(inputs.compliance_framework, 'pci-dss')
|
|
1343
|
+
run: |
|
|
1344
|
+
echo "## PCI-DSS v4.0 Requirements" >> $GITHUB_STEP_SUMMARY
|
|
1345
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1346
|
+
|
|
1347
|
+
# Requirement 2 - Default Passwords
|
|
1348
|
+
if [ "${{ needs.secret_scanning.result }}" == "success" ]; then
|
|
1349
|
+
echo "✅ **Req 2.2**: No hardcoded passwords detected" >> $GITHUB_STEP_SUMMARY
|
|
1350
|
+
else
|
|
1351
|
+
echo "❌ **Req 2.2**: Secret scanning incomplete" >> $GITHUB_STEP_SUMMARY
|
|
1352
|
+
fi
|
|
1353
|
+
|
|
1354
|
+
# Requirement 6 - Secure Development
|
|
1355
|
+
if [ "${{ needs.sonarcloud.result }}" == "success" ]; then
|
|
1356
|
+
echo "✅ **Req 6.3**: Secure coding practices validated" >> $GITHUB_STEP_SUMMARY
|
|
1357
|
+
else
|
|
1358
|
+
echo "⚠️ **Req 6.3**: Static analysis skipped" >> $GITHUB_STEP_SUMMARY
|
|
1359
|
+
fi
|
|
1360
|
+
|
|
1361
|
+
# Requirement 6.4 - Change Control
|
|
1362
|
+
if [ "${{ github.event_name }}" == "pull_request" ]; then
|
|
1363
|
+
echo "✅ **Req 6.4**: Change control process followed" >> $GITHUB_STEP_SUMMARY
|
|
1364
|
+
else
|
|
1365
|
+
echo "⚠️ **Req 6.4**: Direct push detected" >> $GITHUB_STEP_SUMMARY
|
|
1366
|
+
fi
|
|
1367
|
+
|
|
1368
|
+
# Requirement 11 - Security Testing
|
|
1369
|
+
if [ "${{ needs.npm_security_scan.result }}" == "success" ] && [ "${{ needs.snyk.result }}" == "success" ]; then
|
|
1370
|
+
echo "✅ **Req 11.3**: Vulnerability scanning completed" >> $GITHUB_STEP_SUMMARY
|
|
1371
|
+
else
|
|
1372
|
+
echo "❌ **Req 11.3**: Vulnerability scanning incomplete" >> $GITHUB_STEP_SUMMARY
|
|
1373
|
+
fi
|
|
1374
|
+
|
|
1375
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1376
|
+
echo "⚠️ **Note**: This validates security controls only. PCI compliance requires additional network and infrastructure controls." >> $GITHUB_STEP_SUMMARY
|
|
1377
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1378
|
+
|
|
1379
|
+
- name: 📊 Generate compliance score
|
|
1380
|
+
run: |
|
|
1381
|
+
echo "## Compliance Score" >> $GITHUB_STEP_SUMMARY
|
|
1382
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1383
|
+
|
|
1384
|
+
TOTAL_CONTROLS=0
|
|
1385
|
+
PASSED_CONTROLS=0
|
|
1386
|
+
|
|
1387
|
+
# Count quality checks
|
|
1388
|
+
[ "${{ needs.lint.result }}" == "success" ] && PASSED_CONTROLS=$((PASSED_CONTROLS + 1))
|
|
1389
|
+
[ "${{ needs.typecheck.result }}" == "success" ] && PASSED_CONTROLS=$((PASSED_CONTROLS + 1))
|
|
1390
|
+
[ "${{ needs.test.result }}" == "success" ] && PASSED_CONTROLS=$((PASSED_CONTROLS + 1))
|
|
1391
|
+
[ "${{ needs.build.result }}" == "success" ] && PASSED_CONTROLS=$((PASSED_CONTROLS + 1))
|
|
1392
|
+
TOTAL_CONTROLS=$((TOTAL_CONTROLS + 4))
|
|
1393
|
+
|
|
1394
|
+
# Count security checks
|
|
1395
|
+
[ "${{ needs.npm_security_scan.result }}" != "skipped" ] && TOTAL_CONTROLS=$((TOTAL_CONTROLS + 1))
|
|
1396
|
+
[ "${{ needs.npm_security_scan.result }}" == "success" ] && PASSED_CONTROLS=$((PASSED_CONTROLS + 1))
|
|
1397
|
+
|
|
1398
|
+
[ "${{ needs.sonarcloud.result }}" != "skipped" ] && TOTAL_CONTROLS=$((TOTAL_CONTROLS + 1))
|
|
1399
|
+
[ "${{ needs.sonarcloud.result }}" == "success" ] && PASSED_CONTROLS=$((PASSED_CONTROLS + 1))
|
|
1400
|
+
|
|
1401
|
+
[ "${{ needs.snyk.result }}" != "skipped" ] && TOTAL_CONTROLS=$((TOTAL_CONTROLS + 1))
|
|
1402
|
+
[ "${{ needs.snyk.result }}" == "success" ] && PASSED_CONTROLS=$((PASSED_CONTROLS + 1))
|
|
1403
|
+
|
|
1404
|
+
[ "${{ needs.secret_scanning.result }}" != "skipped" ] && TOTAL_CONTROLS=$((TOTAL_CONTROLS + 1))
|
|
1405
|
+
[ "${{ needs.secret_scanning.result }}" == "success" ] && PASSED_CONTROLS=$((PASSED_CONTROLS + 1))
|
|
1406
|
+
|
|
1407
|
+
[ "${{ needs.license_compliance.result }}" != "skipped" ] && TOTAL_CONTROLS=$((TOTAL_CONTROLS + 1))
|
|
1408
|
+
[ "${{ needs.license_compliance.result }}" == "success" ] && PASSED_CONTROLS=$((PASSED_CONTROLS + 1))
|
|
1409
|
+
|
|
1410
|
+
if [ $TOTAL_CONTROLS -gt 0 ]; then
|
|
1411
|
+
SCORE=$((PASSED_CONTROLS * 100 / TOTAL_CONTROLS))
|
|
1412
|
+
echo "**Overall Compliance Score**: $SCORE% ($PASSED_CONTROLS/$TOTAL_CONTROLS controls passed)" >> $GITHUB_STEP_SUMMARY
|
|
1413
|
+
|
|
1414
|
+
if [ $SCORE -ge 90 ]; then
|
|
1415
|
+
echo "🟢 **Status**: Excellent compliance posture" >> $GITHUB_STEP_SUMMARY
|
|
1416
|
+
elif [ $SCORE -ge 70 ]; then
|
|
1417
|
+
echo "🟡 **Status**: Good compliance posture with minor gaps" >> $GITHUB_STEP_SUMMARY
|
|
1418
|
+
else
|
|
1419
|
+
echo "🔴 **Status**: Significant compliance gaps detected" >> $GITHUB_STEP_SUMMARY
|
|
1420
|
+
fi
|
|
1421
|
+
fi
|
|
1422
|
+
|
|
1423
|
+
# Audit logging job
|
|
1424
|
+
audit_logger:
|
|
1425
|
+
name: 📊 Audit Logger
|
|
1426
|
+
runs-on: ubuntu-latest
|
|
1427
|
+
if: always()
|
|
1428
|
+
needs:
|
|
1429
|
+
[
|
|
1430
|
+
install_dependencies,
|
|
1431
|
+
lint,
|
|
1432
|
+
typecheck,
|
|
1433
|
+
test,
|
|
1434
|
+
test_unit,
|
|
1435
|
+
test_integration,
|
|
1436
|
+
test_e2e,
|
|
1437
|
+
maestro_e2e,
|
|
1438
|
+
playwright_e2e,
|
|
1439
|
+
format,
|
|
1440
|
+
build,
|
|
1441
|
+
npm_security_scan,
|
|
1442
|
+
sonarcloud,
|
|
1443
|
+
snyk,
|
|
1444
|
+
secret_scanning,
|
|
1445
|
+
license_compliance,
|
|
1446
|
+
compliance_validation,
|
|
1447
|
+
]
|
|
1448
|
+
steps:
|
|
1449
|
+
- name: 📝 Generate audit log
|
|
1450
|
+
uses: actions/github-script@v7
|
|
1451
|
+
with:
|
|
1452
|
+
script: |
|
|
1453
|
+
const auditLog = {
|
|
1454
|
+
timestamp: new Date().toISOString(),
|
|
1455
|
+
workflow: {
|
|
1456
|
+
name: '${{ github.workflow }}',
|
|
1457
|
+
run_id: '${{ github.run_id }}',
|
|
1458
|
+
run_number: '${{ github.run_number }}',
|
|
1459
|
+
run_attempt: '${{ github.run_attempt }}'
|
|
1460
|
+
},
|
|
1461
|
+
trigger: {
|
|
1462
|
+
event: '${{ github.event_name }}',
|
|
1463
|
+
actor: '${{ github.actor }}',
|
|
1464
|
+
ref: '${{ github.ref }}',
|
|
1465
|
+
sha: '${{ github.sha }}'
|
|
1466
|
+
},
|
|
1467
|
+
repository: {
|
|
1468
|
+
name: '${{ github.repository }}',
|
|
1469
|
+
owner: '${{ github.repository_owner }}',
|
|
1470
|
+
visibility: '${{ github.event.repository.visibility }}'
|
|
1471
|
+
},
|
|
1472
|
+
compliance: {
|
|
1473
|
+
framework: '${{ inputs.compliance_framework }}',
|
|
1474
|
+
require_approval: ${{ inputs.require_approval }},
|
|
1475
|
+
audit_retention_days: ${{ inputs.audit_retention_days }}
|
|
1476
|
+
},
|
|
1477
|
+
jobs: {
|
|
1478
|
+
dependencies: {
|
|
1479
|
+
status: '${{ needs.install_dependencies.result }}',
|
|
1480
|
+
cache_hit: '${{ needs.install_dependencies.outputs.cache-hit }}'
|
|
1481
|
+
},
|
|
1482
|
+
quality: {
|
|
1483
|
+
lint: '${{ needs.lint.result }}',
|
|
1484
|
+
typecheck: '${{ needs.typecheck.result }}',
|
|
1485
|
+
test: '${{ needs.test.result }}',
|
|
1486
|
+
test_unit: '${{ needs.test_unit.result }}',
|
|
1487
|
+
test_integration: '${{ needs.test_integration.result }}',
|
|
1488
|
+
test_e2e: '${{ needs.test_e2e.result }}',
|
|
1489
|
+
maestro_e2e: '${{ needs.maestro_e2e.result }}',
|
|
1490
|
+
playwright_e2e: '${{ needs.playwright_e2e.result }}',
|
|
1491
|
+
format: '${{ needs.format.result }}',
|
|
1492
|
+
build: '${{ needs.build.result }}'
|
|
1493
|
+
},
|
|
1494
|
+
security: {
|
|
1495
|
+
npm_audit: '${{ needs.npm_security_scan.result }}',
|
|
1496
|
+
sonarcloud: '${{ needs.sonarcloud.result }}',
|
|
1497
|
+
snyk: '${{ needs.snyk.result }}',
|
|
1498
|
+
secret_scan: '${{ needs.secret_scanning.result }}',
|
|
1499
|
+
license_check: '${{ needs.license_compliance.result }}'
|
|
1500
|
+
},
|
|
1501
|
+
compliance: '${{ needs.compliance_validation.result }}'
|
|
1502
|
+
},
|
|
1503
|
+
metadata: {
|
|
1504
|
+
runner_os: '${{ runner.os }}',
|
|
1505
|
+
runner_arch: '${{ runner.arch }}'
|
|
1506
|
+
}
|
|
1507
|
+
};
|
|
1508
|
+
|
|
1509
|
+
// Write to file
|
|
1510
|
+
const fs = require('fs');
|
|
1511
|
+
const filename = `audit-log-${auditLog.workflow.run_id}-${Date.now()}.json`;
|
|
1512
|
+
fs.writeFileSync(filename, JSON.stringify(auditLog, null, 2));
|
|
1513
|
+
|
|
1514
|
+
console.log(`Audit log generated: ${filename}`);
|
|
1515
|
+
|
|
1516
|
+
// Add to job summary
|
|
1517
|
+
core.summary.addHeading('📊 Audit Log Entry', 3);
|
|
1518
|
+
core.summary.addCodeBlock(JSON.stringify(auditLog, null, 2), 'json');
|
|
1519
|
+
await core.summary.write();
|
|
1520
|
+
|
|
1521
|
+
// Set output for artifact upload
|
|
1522
|
+
core.setOutput('filename', filename);
|
|
1523
|
+
|
|
1524
|
+
- name: 📤 Upload audit log
|
|
1525
|
+
uses: actions/upload-artifact@v4
|
|
1526
|
+
with:
|
|
1527
|
+
name: audit-log-${{ github.run_id }}
|
|
1528
|
+
path: audit-log-*.json
|
|
1529
|
+
retention-days: ${{ inputs.audit_retention_days }}
|
|
1530
|
+
|
|
1531
|
+
- name: 📊 Generate evidence package
|
|
1532
|
+
if: ${{ inputs.generate_evidence_package == true }}
|
|
1533
|
+
run: |
|
|
1534
|
+
echo "📦 Generating compliance evidence package..."
|
|
1535
|
+
|
|
1536
|
+
# Create evidence directory
|
|
1537
|
+
mkdir -p evidence-package
|
|
1538
|
+
|
|
1539
|
+
# Create evidence summary
|
|
1540
|
+
cat > evidence-package/evidence-summary.md << EOF
|
|
1541
|
+
# Compliance Evidence Package
|
|
1542
|
+
|
|
1543
|
+
**Generated**: $(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
1544
|
+
**Workflow Run**: ${{ github.run_id }}
|
|
1545
|
+
**Repository**: ${{ github.repository }}
|
|
1546
|
+
**Compliance Framework**: ${{ inputs.compliance_framework }}
|
|
1547
|
+
|
|
1548
|
+
## Evidence Contents
|
|
1549
|
+
|
|
1550
|
+
1. **Audit Log**: Full workflow execution audit trail
|
|
1551
|
+
2. **Security Scan Results**: Results from all security tools
|
|
1552
|
+
3. **Test Reports**: Unit test execution results
|
|
1553
|
+
4. **Build Artifacts**: Compiled output verification
|
|
1554
|
+
5. **Approval Records**: Change approval documentation
|
|
1555
|
+
|
|
1556
|
+
## Control Validation Summary
|
|
1557
|
+
|
|
1558
|
+
- Quality Controls: Validated through automated testing
|
|
1559
|
+
- Security Controls: Validated through multiple scanning tools
|
|
1560
|
+
- Change Management: Validated through PR process
|
|
1561
|
+
- Audit Controls: Continuous logging with ${{ inputs.audit_retention_days }}-day retention
|
|
1562
|
+
|
|
1563
|
+
## Attestation
|
|
1564
|
+
|
|
1565
|
+
This evidence package was automatically generated from workflow run ${{ github.run_id }}.
|
|
1566
|
+
All artifacts are cryptographically signed by GitHub Actions.
|
|
1567
|
+
EOF
|
|
1568
|
+
|
|
1569
|
+
echo "✅ Evidence package created"
|
|
1570
|
+
|
|
1571
|
+
- name: 📤 Upload evidence package
|
|
1572
|
+
if: ${{ inputs.generate_evidence_package == true }}
|
|
1573
|
+
uses: actions/upload-artifact@v4
|
|
1574
|
+
with:
|
|
1575
|
+
name: compliance-evidence-${{ github.run_id }}
|
|
1576
|
+
path: evidence-package/
|
|
1577
|
+
retention-days: ${{ inputs.audit_retention_days }}
|
|
1578
|
+
|
|
1579
|
+
# Approval gate for production deployments
|
|
1580
|
+
approval_gate:
|
|
1581
|
+
name: 🚦 Approval Gate
|
|
1582
|
+
runs-on: ubuntu-latest
|
|
1583
|
+
if: ${{ inputs.require_approval == true && inputs.compliance_framework != 'none' && inputs.approval_environment != '' }}
|
|
1584
|
+
needs: [compliance_validation]
|
|
1585
|
+
environment:
|
|
1586
|
+
name: ${{ inputs.approval_environment }}
|
|
1587
|
+
# NOTE: This environment must be created in your repository before using this workflow
|
|
1588
|
+
# To create it:
|
|
1589
|
+
# 1. Go to Settings > Environments > New environment
|
|
1590
|
+
# 2. Name it to match the approval_environment input (default: "production")
|
|
1591
|
+
# 3. Add protection rules (required reviewers, deployment branches, etc.)
|
|
1592
|
+
steps:
|
|
1593
|
+
- name: 📝 Log approval
|
|
1594
|
+
run: |
|
|
1595
|
+
echo "# 🚦 Production Deployment Approval" >> $GITHUB_STEP_SUMMARY
|
|
1596
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1597
|
+
echo "**Approved by**: ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY
|
|
1598
|
+
echo "**Approval time**: $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_STEP_SUMMARY
|
|
1599
|
+
echo "**Compliance framework**: ${{ inputs.compliance_framework }}" >> $GITHUB_STEP_SUMMARY
|
|
1600
|
+
echo "**Workflow run**: ${{ github.run_id }}" >> $GITHUB_STEP_SUMMARY
|
|
1601
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1602
|
+
echo "## Approval Requirements Met" >> $GITHUB_STEP_SUMMARY
|
|
1603
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1604
|
+
echo "- ✅ Compliance validation passed" >> $GITHUB_STEP_SUMMARY
|
|
1605
|
+
echo "- ✅ Security scans completed" >> $GITHUB_STEP_SUMMARY
|
|
1606
|
+
echo "- ✅ Required approvers notified" >> $GITHUB_STEP_SUMMARY
|
|
1607
|
+
echo "- ✅ Audit trail generated" >> $GITHUB_STEP_SUMMARY
|
|
1608
|
+
|
|
1609
|
+
- name: 📊 Create approval record
|
|
1610
|
+
run: |
|
|
1611
|
+
# Create approval record for audit
|
|
1612
|
+
cat > approval-record-${{ github.run_id }}.json << EOF
|
|
1613
|
+
{
|
|
1614
|
+
"approval_type": "production_deployment",
|
|
1615
|
+
"approved_by": "${{ github.actor }}",
|
|
1616
|
+
"approval_time": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
1617
|
+
"workflow_run_id": "${{ github.run_id }}",
|
|
1618
|
+
"compliance_framework": "${{ inputs.compliance_framework }}",
|
|
1619
|
+
"repository": "${{ github.repository }}",
|
|
1620
|
+
"ref": "${{ github.ref }}",
|
|
1621
|
+
"sha": "${{ github.sha }}"
|
|
1622
|
+
}
|
|
1623
|
+
EOF
|
|
1624
|
+
|
|
1625
|
+
- name: 📤 Upload approval record
|
|
1626
|
+
uses: actions/upload-artifact@v4
|
|
1627
|
+
with:
|
|
1628
|
+
name: approval-record-${{ github.run_id }}
|
|
1629
|
+
path: approval-record-*.json
|
|
1630
|
+
retention-days: ${{ inputs.audit_retention_days }}
|
|
1631
|
+
|
|
1632
|
+
# Performance monitoring job
|
|
1633
|
+
performance_summary:
|
|
1634
|
+
name: ⚡ Performance Summary
|
|
1635
|
+
runs-on: ubuntu-latest
|
|
1636
|
+
if: always()
|
|
1637
|
+
needs:
|
|
1638
|
+
[
|
|
1639
|
+
install_dependencies,
|
|
1640
|
+
lint,
|
|
1641
|
+
typecheck,
|
|
1642
|
+
test,
|
|
1643
|
+
test_unit,
|
|
1644
|
+
test_integration,
|
|
1645
|
+
test_e2e,
|
|
1646
|
+
maestro_e2e,
|
|
1647
|
+
playwright_e2e,
|
|
1648
|
+
format,
|
|
1649
|
+
build,
|
|
1650
|
+
npm_security_scan,
|
|
1651
|
+
sonarcloud,
|
|
1652
|
+
snyk,
|
|
1653
|
+
secret_scanning,
|
|
1654
|
+
license_compliance,
|
|
1655
|
+
]
|
|
1656
|
+
steps:
|
|
1657
|
+
- name: 📊 Generate performance report
|
|
1658
|
+
run: |
|
|
1659
|
+
echo "# ⚡ Workflow Performance Report" >> $GITHUB_STEP_SUMMARY
|
|
1660
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1661
|
+
echo "## 🏃 Execution Summary" >> $GITHUB_STEP_SUMMARY
|
|
1662
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1663
|
+
|
|
1664
|
+
# Calculate total workflow time (approximation)
|
|
1665
|
+
echo "- **Workflow Run ID**: ${{ github.run_id }}" >> $GITHUB_STEP_SUMMARY
|
|
1666
|
+
echo "- **Triggered By**: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY
|
|
1667
|
+
echo "- **Runner**: ${{ runner.os }} (${{ runner.arch }})" >> $GITHUB_STEP_SUMMARY
|
|
1668
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1669
|
+
|
|
1670
|
+
echo "## 📦 Dependency Caching" >> $GITHUB_STEP_SUMMARY
|
|
1671
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1672
|
+
|
|
1673
|
+
if [ "${{ needs.install_dependencies.outputs.cache-hit }}" == "true" ]; then
|
|
1674
|
+
echo "✅ **Cache Hit!** Dependencies loaded from cache." >> $GITHUB_STEP_SUMMARY
|
|
1675
|
+
else
|
|
1676
|
+
echo "❌ **Cache Miss** - Fresh dependency installation was required." >> $GITHUB_STEP_SUMMARY
|
|
1677
|
+
fi
|
|
1678
|
+
|
|
1679
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1680
|
+
echo "- **Cache Key**: \`${{ needs.install_dependencies.outputs.cache-key }}\`" >> $GITHUB_STEP_SUMMARY
|
|
1681
|
+
|
|
1682
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1683
|
+
echo "## 🎯 Job Status Overview" >> $GITHUB_STEP_SUMMARY
|
|
1684
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1685
|
+
echo "| Job | Status | Type |" >> $GITHUB_STEP_SUMMARY
|
|
1686
|
+
echo "|-----|--------|------|" >> $GITHUB_STEP_SUMMARY
|
|
1687
|
+
echo "| Install Dependencies | ${{ needs.install_dependencies.result }} | Setup |" >> $GITHUB_STEP_SUMMARY
|
|
1688
|
+
echo "| Lint | ${{ needs.lint.result }} | Quality |" >> $GITHUB_STEP_SUMMARY
|
|
1689
|
+
echo "| Type Check | ${{ needs.typecheck.result }} | Quality |" >> $GITHUB_STEP_SUMMARY
|
|
1690
|
+
echo "| Tests | ${{ needs.test.result }} | Quality |" >> $GITHUB_STEP_SUMMARY
|
|
1691
|
+
echo "| Unit Tests | ${{ needs.test_unit.result }} | Quality |" >> $GITHUB_STEP_SUMMARY
|
|
1692
|
+
echo "| Integration Tests | ${{ needs.test_integration.result }} | Quality |" >> $GITHUB_STEP_SUMMARY
|
|
1693
|
+
echo "| E2E Tests | ${{ needs.test_e2e.result }} | Quality |" >> $GITHUB_STEP_SUMMARY
|
|
1694
|
+
echo "| Maestro E2E | ${{ needs.maestro_e2e.result }} | Quality |" >> $GITHUB_STEP_SUMMARY
|
|
1695
|
+
echo "| Playwright E2E | ${{ needs.playwright_e2e.result }} | Quality |" >> $GITHUB_STEP_SUMMARY
|
|
1696
|
+
echo "| Format Check | ${{ needs.format.result }} | Quality |" >> $GITHUB_STEP_SUMMARY
|
|
1697
|
+
echo "| Build | ${{ needs.build.result }} | Build |" >> $GITHUB_STEP_SUMMARY
|
|
1698
|
+
echo "| NPM Security | ${{ needs.npm_security_scan.result }} | Security |" >> $GITHUB_STEP_SUMMARY
|
|
1699
|
+
echo "| SonarCloud | ${{ needs.sonarcloud.result }} | Security |" >> $GITHUB_STEP_SUMMARY
|
|
1700
|
+
echo "| Snyk | ${{ needs.snyk.result }} | Security |" >> $GITHUB_STEP_SUMMARY
|
|
1701
|
+
echo "| Secret Scan | ${{ needs.secret_scanning.result }} | Security |" >> $GITHUB_STEP_SUMMARY
|
|
1702
|
+
echo "| License Check | ${{ needs.license_compliance.result }} | Security |" >> $GITHUB_STEP_SUMMARY
|
|
1703
|
+
|
|
1704
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1705
|
+
echo "## 💡 Performance Tips" >> $GITHUB_STEP_SUMMARY
|
|
1706
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1707
|
+
echo "- All jobs now share dependencies, reducing redundant installations" >> $GITHUB_STEP_SUMMARY
|
|
1708
|
+
echo "- Quality checks (lint, typecheck, format) run in parallel" >> $GITHUB_STEP_SUMMARY
|
|
1709
|
+
echo "- Security tools run in parallel where tokens are available" >> $GITHUB_STEP_SUMMARY
|
|
1710
|
+
echo "- Dependency caching saves ~2-3 minutes on subsequent runs" >> $GITHUB_STEP_SUMMARY
|
|
1711
|
+
|
|
1712
|
+
# Count parallel jobs
|
|
1713
|
+
QUALITY_JOBS=0
|
|
1714
|
+
SECURITY_JOBS=0
|
|
1715
|
+
|
|
1716
|
+
[ "${{ needs.lint.result }}" != "skipped" ] && QUALITY_JOBS=$((QUALITY_JOBS + 1))
|
|
1717
|
+
[ "${{ needs.typecheck.result }}" != "skipped" ] && QUALITY_JOBS=$((QUALITY_JOBS + 1))
|
|
1718
|
+
[ "${{ needs.test.result }}" != "skipped" ] && QUALITY_JOBS=$((QUALITY_JOBS + 1))
|
|
1719
|
+
[ "${{ needs.test_unit.result }}" != "skipped" ] && QUALITY_JOBS=$((QUALITY_JOBS + 1))
|
|
1720
|
+
[ "${{ needs.test_integration.result }}" != "skipped" ] && QUALITY_JOBS=$((QUALITY_JOBS + 1))
|
|
1721
|
+
[ "${{ needs.test_e2e.result }}" != "skipped" ] && QUALITY_JOBS=$((QUALITY_JOBS + 1))
|
|
1722
|
+
[ "${{ needs.maestro_e2e.result }}" != "skipped" ] && QUALITY_JOBS=$((QUALITY_JOBS + 1))
|
|
1723
|
+
[ "${{ needs.playwright_e2e.result }}" != "skipped" ] && QUALITY_JOBS=$((QUALITY_JOBS + 1))
|
|
1724
|
+
[ "${{ needs.format.result }}" != "skipped" ] && QUALITY_JOBS=$((QUALITY_JOBS + 1))
|
|
1725
|
+
|
|
1726
|
+
[ "${{ needs.sonarcloud.result }}" != "skipped" ] && SECURITY_JOBS=$((SECURITY_JOBS + 1))
|
|
1727
|
+
[ "${{ needs.snyk.result }}" != "skipped" ] && SECURITY_JOBS=$((SECURITY_JOBS + 1))
|
|
1728
|
+
[ "${{ needs.secret_scanning.result }}" != "skipped" ] && SECURITY_JOBS=$((SECURITY_JOBS + 1))
|
|
1729
|
+
[ "${{ needs.license_compliance.result }}" != "skipped" ] && SECURITY_JOBS=$((SECURITY_JOBS + 1))
|
|
1730
|
+
|
|
1731
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1732
|
+
echo "## 🚀 Optimization Metrics" >> $GITHUB_STEP_SUMMARY
|
|
1733
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
1734
|
+
echo "- **Parallel Quality Jobs**: $QUALITY_JOBS running concurrently" >> $GITHUB_STEP_SUMMARY
|
|
1735
|
+
echo "- **Parallel Security Jobs**: $SECURITY_JOBS running concurrently" >> $GITHUB_STEP_SUMMARY
|
|
1736
|
+
echo "- **Dependency Installation**: Single shared installation" >> $GITHUB_STEP_SUMMARY
|
|
1737
|
+
echo "- **Estimated Time Saved**: ~40% vs sequential execution" >> $GITHUB_STEP_SUMMARY
|