mirror of
https://github.com/BradGroux/veritas-kanban.git
synced 2026-08-28 02:44:59 +00:00
592 lines
21 KiB
YAML
592 lines
21 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
branches: [main]
|
|
types: [opened, synchronize, reopened, labeled, unlabeled]
|
|
schedule:
|
|
- cron: '0 8 * * *'
|
|
workflow_dispatch:
|
|
inputs:
|
|
test_scope:
|
|
description: Unit-test verification tier
|
|
required: true
|
|
default: focused
|
|
type: choice
|
|
options:
|
|
- focused
|
|
- full
|
|
base_sha:
|
|
description: Optional base commit for a focused run (defaults to HEAD^)
|
|
required: false
|
|
type: string
|
|
|
|
permissions:
|
|
actions: read
|
|
contents: read
|
|
checks: read
|
|
pull-requests: read
|
|
|
|
concurrency:
|
|
group: >-
|
|
${{
|
|
format(
|
|
'{0}-{1}-{2}',
|
|
github.workflow,
|
|
github.ref,
|
|
github.event_name == 'pull_request' &&
|
|
contains(fromJSON('["labeled","unlabeled"]'), github.event.action) &&
|
|
github.event.label.name != 'ci:full' &&
|
|
format('cosmetic-{0}', github.run_id) ||
|
|
'authoritative'
|
|
)
|
|
}}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
NODE_VERSION: '22'
|
|
|
|
jobs:
|
|
# ─── Deterministic Test Scope ───────────────────────────────────
|
|
select-tests:
|
|
name: Select Test Scope
|
|
if: >-
|
|
github.event_name != 'pull_request' ||
|
|
!contains(fromJSON('["labeled","unlabeled"]'), github.event.action) ||
|
|
github.event.label.name == 'ci:full'
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
scope: ${{ steps.scope.outputs.scope }}
|
|
packages: ${{ steps.scope.outputs.packages }}
|
|
diff_range: ${{ steps.scope.outputs.diff_range }}
|
|
reason: ${{ steps.scope.outputs.reason }}
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
|
|
- name: Verify CI scope controls
|
|
run: >-
|
|
node --test
|
|
scripts/check-actions-pinned.test.mjs
|
|
scripts/check-delivery-cadence.test.mjs
|
|
scripts/check-security-gates.test.mjs
|
|
scripts/select-ci-test-scope.test.mjs
|
|
scripts/verify-full-suite-job-evidence.test.mjs
|
|
|
|
- name: Guard delivery cadence
|
|
run: node scripts/check-delivery-cadence.mjs
|
|
|
|
- name: Guard immutable GitHub Actions references
|
|
run: node scripts/check-actions-pinned.mjs
|
|
|
|
- name: Guard continuous security gates
|
|
run: node scripts/check-security-gates.mjs
|
|
|
|
- name: Find reviewed full-suite evidence
|
|
id: reviewed_full
|
|
if: github.event_name == 'push'
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
reviewed_full=false
|
|
reviewed_pr=
|
|
reviewed_head=
|
|
reviewed_mode=
|
|
associated_prs="$(
|
|
gh api \
|
|
-H 'Accept: application/vnd.github+json' \
|
|
"repos/${GITHUB_REPOSITORY}/commits/${GITHUB_SHA}/pulls" \
|
|
2>/dev/null ||
|
|
printf '[]'
|
|
)"
|
|
merged_pr="$(
|
|
jq -c \
|
|
--arg merge_sha "$GITHUB_SHA" \
|
|
'[.[] | select(
|
|
.merged_at != null and
|
|
.base.ref == "main" and
|
|
.merge_commit_sha == $merge_sha
|
|
)] | first // empty' \
|
|
<<<"$associated_prs"
|
|
)"
|
|
|
|
if [[ -n "$merged_pr" ]]; then
|
|
reviewed_pr="$(jq -r '.number' <<<"$merged_pr")"
|
|
reviewed_head="$(jq -r '.head.sha' <<<"$merged_pr")"
|
|
check_runs="$(
|
|
gh api \
|
|
-H 'Accept: application/vnd.github+json' \
|
|
"repos/${GITHUB_REPOSITORY}/commits/${reviewed_head}/check-runs?per_page=100" \
|
|
2>/dev/null ||
|
|
printf '{"check_runs":[]}'
|
|
)"
|
|
|
|
full_suite_check_id="$(
|
|
jq -r '
|
|
[
|
|
.check_runs[] |
|
|
select(
|
|
.name == "Workspace Unit Tests" and
|
|
.status == "completed" and
|
|
.conclusion == "success" and
|
|
.app.slug == "github-actions"
|
|
)
|
|
] |
|
|
sort_by(.completed_at) |
|
|
last |
|
|
.id // empty
|
|
' <<<"$check_runs"
|
|
)"
|
|
full_suite_job="$(
|
|
if [[ -n "$full_suite_check_id" ]]; then
|
|
gh api \
|
|
-H 'Accept: application/vnd.github+json' \
|
|
"repos/${GITHUB_REPOSITORY}/actions/jobs/${full_suite_check_id}" \
|
|
2>/dev/null ||
|
|
printf '{}'
|
|
else
|
|
printf '{}'
|
|
fi
|
|
)"
|
|
|
|
if node scripts/verify-full-suite-job-evidence.mjs <<<"$full_suite_job"; then
|
|
if git cat-file -e "${reviewed_head}^{commit}" 2>/dev/null ||
|
|
git fetch --no-tags origin "$reviewed_head"; then
|
|
if git merge-base --is-ancestor "$reviewed_head" "$GITHUB_SHA"; then
|
|
reviewed_full=true
|
|
reviewed_mode=ancestor
|
|
elif [[ "$(git rev-parse "${reviewed_head}^{tree}")" == \
|
|
"$(git rev-parse "${GITHUB_SHA}^{tree}")" ]]; then
|
|
reviewed_full=true
|
|
reviewed_mode=identical-tree
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
{
|
|
echo "reviewed_full=$reviewed_full"
|
|
echo "reviewed_pr=$reviewed_pr"
|
|
echo "reviewed_head=$reviewed_head"
|
|
echo "reviewed_mode=$reviewed_mode"
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Select verification tier
|
|
id: scope
|
|
shell: bash
|
|
env:
|
|
CI_EVENT_NAME: ${{ github.event_name }}
|
|
CI_MANUAL_SCOPE: ${{ inputs.test_scope || '' }}
|
|
CI_PR_LABELS: ${{ toJSON(github.event.pull_request.labels.*.name) }}
|
|
CI_REVIEWED_FULL: ${{ steps.reviewed_full.outputs.reviewed_full || 'false' }}
|
|
CI_REVIEWED_PR: ${{ steps.reviewed_full.outputs.reviewed_pr || '' }}
|
|
CI_REVIEWED_MODE: ${{ steps.reviewed_full.outputs.reviewed_mode || '' }}
|
|
PR_BASE_SHA: ${{ github.event.pull_request.base.sha || '' }}
|
|
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || '' }}
|
|
PUSH_BEFORE_SHA: ${{ github.event.before || '' }}
|
|
DISPATCH_BASE_SHA: ${{ inputs.base_sha || '' }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
case "$CI_EVENT_NAME" in
|
|
pull_request)
|
|
CI_BASE_SHA="$PR_BASE_SHA"
|
|
CI_HEAD_SHA="$PR_HEAD_SHA"
|
|
;;
|
|
push)
|
|
CI_BASE_SHA="$PUSH_BEFORE_SHA"
|
|
CI_HEAD_SHA="$GITHUB_SHA"
|
|
if [[ "$CI_BASE_SHA" =~ ^0+$ ]]; then
|
|
CI_BASE_SHA="$(git rev-parse "${GITHUB_SHA}^")"
|
|
fi
|
|
;;
|
|
workflow_dispatch)
|
|
CI_HEAD_SHA="$GITHUB_SHA"
|
|
if [[ -n "$DISPATCH_BASE_SHA" ]]; then
|
|
if [[ ! "$DISPATCH_BASE_SHA" =~ ^[0-9a-fA-F]{7,40}$ ]]; then
|
|
echo "::error::base_sha must be a 7-40 character hexadecimal commit ID"
|
|
exit 1
|
|
fi
|
|
CI_BASE_SHA="$(git rev-parse --verify "${DISPATCH_BASE_SHA}^{commit}")"
|
|
else
|
|
CI_BASE_SHA="$(git rev-parse "${GITHUB_SHA}^")"
|
|
fi
|
|
;;
|
|
schedule)
|
|
CI_BASE_SHA="$GITHUB_SHA"
|
|
CI_HEAD_SHA="$GITHUB_SHA"
|
|
;;
|
|
*)
|
|
echo "::error::Unsupported CI event: $CI_EVENT_NAME"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
export CI_BASE_SHA CI_HEAD_SHA
|
|
node scripts/select-ci-test-scope.mjs
|
|
|
|
# ─── Lint & Type Check ───────────────────────────────────────────
|
|
lint-and-typecheck:
|
|
name: Lint & Type Check
|
|
if: >-
|
|
github.event_name != 'pull_request' ||
|
|
!contains(fromJSON('["labeled","unlabeled"]'), github.event.action) ||
|
|
github.event.label.name == 'ci:full'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
|
|
|
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
cache: pnpm
|
|
|
|
- name: Check pnpm settings location
|
|
run: node scripts/check-pnpm-settings.mjs
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Build shared (dependency for typecheck)
|
|
run: pnpm --filter @veritas-kanban/shared build
|
|
|
|
- name: Lint
|
|
run: pnpm lint
|
|
|
|
- name: Enforce lint warning budget
|
|
run: pnpm lint:budget
|
|
|
|
- name: Check permission coverage
|
|
run: node scripts/check-permission-coverage.mjs
|
|
|
|
- name: Type check all packages
|
|
run: pnpm typecheck
|
|
|
|
# ─── Focused Related Tests ──────────────────────────────────────
|
|
test-changed:
|
|
name: Changed Tests
|
|
needs: select-tests
|
|
if: >-
|
|
always() &&
|
|
(
|
|
github.event_name != 'pull_request' ||
|
|
!contains(fromJSON('["labeled","unlabeled"]'), github.event.action) ||
|
|
github.event.label.name == 'ci:full'
|
|
)
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Require a successful scope decision
|
|
env:
|
|
SELECTOR_RESULT: ${{ needs.select-tests.result }}
|
|
SELECTED_SCOPE: ${{ needs.select-tests.outputs.scope }}
|
|
SELECTED_PACKAGES: ${{ needs.select-tests.outputs.packages }}
|
|
run: |
|
|
if [[ "$SELECTOR_RESULT" != "success" ]]; then
|
|
echo "::error::Select Test Scope did not complete successfully"
|
|
exit 1
|
|
fi
|
|
if [[ ! "$SELECTED_SCOPE" =~ ^(none|focused|full)$ ]]; then
|
|
echo "::error::Select Test Scope returned an invalid scope"
|
|
exit 1
|
|
fi
|
|
if [[ "$SELECTED_SCOPE" == "focused" && -z "$SELECTED_PACKAGES" ]]; then
|
|
echo "::error::Focused scope requires at least one workspace"
|
|
exit 1
|
|
fi
|
|
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
if: needs.select-tests.outputs.scope == 'focused'
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
|
if: needs.select-tests.outputs.scope == 'focused'
|
|
|
|
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
|
if: needs.select-tests.outputs.scope == 'focused'
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
cache: pnpm
|
|
|
|
- name: Install dependencies
|
|
if: needs.select-tests.outputs.scope == 'focused'
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Build shared test dependency
|
|
if: needs.select-tests.outputs.scope == 'focused'
|
|
run: pnpm --filter @veritas-kanban/shared build
|
|
|
|
- name: Run related tests in affected workspaces
|
|
if: needs.select-tests.outputs.scope == 'focused'
|
|
env:
|
|
DIFF_RANGE: ${{ needs.select-tests.outputs.diff_range }}
|
|
SELECTED_PACKAGES: ${{ needs.select-tests.outputs.packages }}
|
|
VERITAS_DISABLE_WATCHERS: '1'
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
IFS=',' read -r -a packages <<< "$SELECTED_PACKAGES"
|
|
executed_packages=()
|
|
{
|
|
echo "### Changed Tests"
|
|
echo
|
|
echo "- Diff range: \`$DIFF_RANGE\`"
|
|
echo "- Selected workspaces: \`$SELECTED_PACKAGES\`"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
for package_name in "${packages[@]}"; do
|
|
related_files=()
|
|
while IFS= read -r changed_file; do
|
|
related_files+=("./${changed_file#"$package_name/"}")
|
|
done < <(
|
|
git diff --name-only --diff-filter=ACMR "$DIFF_RANGE" -- "$package_name/"
|
|
)
|
|
|
|
if (( ${#related_files[@]} == 0 )); then
|
|
continue
|
|
fi
|
|
|
|
case "$package_name" in
|
|
server|web|cli|mcp)
|
|
package_filter="@veritas-kanban/${package_name}"
|
|
extra_args=()
|
|
if [[ "$package_name" == "web" ]]; then
|
|
extra_args+=(--testTimeout 15000)
|
|
fi
|
|
;;
|
|
desktop)
|
|
package_filter="@veritas-kanban/desktop"
|
|
extra_args=(--config vitest.config.ts)
|
|
;;
|
|
*)
|
|
echo "::error::Unknown selected workspace: $package_name"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
pnpm --filter "$package_filter" exec vitest related \
|
|
--run \
|
|
--maxWorkers=4 \
|
|
--passWithNoTests \
|
|
"${extra_args[@]}" \
|
|
"${related_files[@]}"
|
|
executed_packages+=("$package_name")
|
|
done
|
|
|
|
{
|
|
if (( ${#executed_packages[@]} > 0 )); then
|
|
echo "- Related coverage executed for: \`${executed_packages[*]}\`"
|
|
else
|
|
echo "- No added, copied, modified, or renamed workspace inputs required related coverage."
|
|
fi
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
- name: Record focused-tier skip
|
|
if: needs.select-tests.outputs.scope != 'focused'
|
|
env:
|
|
SELECTED_SCOPE: ${{ needs.select-tests.outputs.scope }}
|
|
SELECTION_REASON: ${{ needs.select-tests.outputs.reason }}
|
|
run: |
|
|
{
|
|
echo "### Changed Tests"
|
|
echo
|
|
echo "- Decision: skipped related coverage because scope is \`$SELECTED_SCOPE\`."
|
|
echo "- Selection reason: $SELECTION_REASON"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
# ─── Full Workspace Unit Tests ───────────────────────────────────
|
|
test-workspace:
|
|
name: Workspace Unit Tests
|
|
needs: select-tests
|
|
if: >-
|
|
always() &&
|
|
(
|
|
github.event_name != 'pull_request' ||
|
|
!contains(fromJSON('["labeled","unlabeled"]'), github.event.action) ||
|
|
github.event.label.name == 'ci:full'
|
|
)
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Require a successful scope decision
|
|
env:
|
|
SELECTOR_RESULT: ${{ needs.select-tests.result }}
|
|
SELECTED_SCOPE: ${{ needs.select-tests.outputs.scope }}
|
|
run: |
|
|
if [[ "$SELECTOR_RESULT" != "success" ]]; then
|
|
echo "::error::Select Test Scope did not complete successfully"
|
|
exit 1
|
|
fi
|
|
if [[ ! "$SELECTED_SCOPE" =~ ^(none|focused|full)$ ]]; then
|
|
echo "::error::Select Test Scope returned an invalid scope"
|
|
exit 1
|
|
fi
|
|
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
if: needs.select-tests.outputs.scope == 'full'
|
|
|
|
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
|
if: needs.select-tests.outputs.scope == 'full'
|
|
|
|
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
|
if: needs.select-tests.outputs.scope == 'full'
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
cache: pnpm
|
|
|
|
- name: Install dependencies
|
|
if: needs.select-tests.outputs.scope == 'full'
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Build shared (dependency for workspace tests)
|
|
if: needs.select-tests.outputs.scope == 'full'
|
|
run: pnpm --filter @veritas-kanban/shared build
|
|
|
|
- name: Run workspace unit tests
|
|
if: needs.select-tests.outputs.scope == 'full'
|
|
run: pnpm test:unit
|
|
|
|
- name: Run desktop readiness regression tests
|
|
if: needs.select-tests.outputs.scope == 'full'
|
|
run: pnpm desktop:test:readiness
|
|
|
|
- name: Run dual-storage parity tests
|
|
if: needs.select-tests.outputs.scope == 'full'
|
|
run: >-
|
|
pnpm --filter @veritas-kanban/server exec vitest run
|
|
src/__tests__/storage/dual-storage-parity.test.ts
|
|
|
|
- name: Record full-suite evidence
|
|
if: >-
|
|
always() &&
|
|
needs.select-tests.result == 'success' &&
|
|
needs.select-tests.outputs.scope == 'full'
|
|
env:
|
|
DIFF_RANGE: ${{ needs.select-tests.outputs.diff_range }}
|
|
SELECTION_REASON: ${{ needs.select-tests.outputs.reason }}
|
|
CURRENT_JOB_STATUS: ${{ job.status }}
|
|
run: |
|
|
{
|
|
echo "### Workspace Unit Tests"
|
|
echo
|
|
echo "- Diff range: \`${DIFF_RANGE:-not required}\`"
|
|
echo "- Selection reason: $SELECTION_REASON"
|
|
echo "- Workflow checkout SHA: \`$GITHUB_SHA\`"
|
|
echo "- Current job status: \`$CURRENT_JOB_STATUS\`"
|
|
echo "- Unit-test workspaces: \`server, web, cli, mcp\`"
|
|
echo "- Workspace workers: \`4 maximum per Vitest project\`"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
- name: Record full-tier skip
|
|
if: needs.select-tests.outputs.scope != 'full'
|
|
env:
|
|
SELECTED_SCOPE: ${{ needs.select-tests.outputs.scope }}
|
|
SELECTION_REASON: ${{ needs.select-tests.outputs.reason }}
|
|
run: |
|
|
{
|
|
echo "### Workspace Unit Tests"
|
|
echo
|
|
echo "- Decision: skipped the complete suite because scope is \`$SELECTED_SCOPE\`."
|
|
echo "- Selection reason: $SELECTION_REASON"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
# ─── Build ───────────────────────────────────────────────────────
|
|
build:
|
|
name: Build
|
|
if: >-
|
|
github.event_name != 'pull_request' ||
|
|
!contains(fromJSON('["labeled","unlabeled"]'), github.event.action) ||
|
|
github.event.label.name == 'ci:full'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
|
|
|
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
cache: pnpm
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Build shared (dependency for all builds)
|
|
run: pnpm --filter @veritas-kanban/shared build
|
|
|
|
- name: Verify native Vite config loading
|
|
run: pnpm check:vite-native-config
|
|
|
|
- name: Build all packages
|
|
run: pnpm build
|
|
|
|
- name: Verify web build output
|
|
run: |
|
|
if [ ! -d "web/dist" ]; then
|
|
echo "::error::Web build output (web/dist) not found"
|
|
exit 1
|
|
fi
|
|
echo "✅ Web build output exists"
|
|
ls -la web/dist/
|
|
|
|
- name: Verify server build output
|
|
run: |
|
|
if [ ! -d "server/dist" ]; then
|
|
echo "::error::Server build output (server/dist) not found"
|
|
exit 1
|
|
fi
|
|
echo "✅ Server build output exists"
|
|
ls -la server/dist/
|
|
|
|
- name: Verify CLI and MCP build output
|
|
run: |
|
|
for file in cli/dist/index.js mcp/dist/index.js; do
|
|
if [ ! -f "$file" ]; then
|
|
echo "::error::$file not found"
|
|
exit 1
|
|
fi
|
|
done
|
|
echo "✅ CLI and MCP build outputs exist"
|
|
|
|
# ─── Security Audit ──────────────────────────────────────────────
|
|
security-audit:
|
|
name: Security Audit
|
|
if: >-
|
|
github.event_name != 'pull_request' ||
|
|
!contains(fromJSON('["labeled","unlabeled"]'), github.event.action) ||
|
|
github.event.label.name == 'ci:full'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
|
|
|
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
cache: pnpm
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Reject tracked runtime security configuration
|
|
run: pnpm check:security-artifacts
|
|
|
|
- name: Audit production dependencies (blocks on high/critical)
|
|
run: pnpm audit --prod --audit-level=high
|
|
|
|
- name: Audit all dependencies (informational)
|
|
run: pnpm audit
|
|
continue-on-error: true
|