Skip to content
13 changes: 13 additions & 0 deletions .github/actions/audit-npm/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# audit-npm action Changelog

All notable changes to the **audit-npm** action are documented in this file.

## v1.0.0

### Added

- Initial release of audit-npm composite action
- Runs `npm audit`
- Parses and summarizes vulnerabilities by severity
- Outputs a markdown summary and a boolean audit gate
- Audit gate fails if a critical or high production vulnerability exists
79 changes: 79 additions & 0 deletions .github/actions/audit-npm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# NPM Audit Action

## 🧭 Summary

Runs `npm audit`, parses the results, and outputs a markdown summary and a pass/fail gate for use in CI workflows. Designed for Node.js projects to automate dependency vulnerability checks.

## Scope/Limitations

- Only supports projects with a `package.json` in the working directory.
- Requires `jq` (preinstalled on GitHub-hosted runners).
- Only checks for vulnerabilities reported by `npm audit`.

## 🔒 Permissions

The following GHA permissions are required to use this step:

```yaml
permissions:
contents: read
```

## Dependencies

- `jq` — JSON processor (preinstalled on GitHub-hosted Ubuntu runners)
- `npm` — Node.js package manager

## 📤 Outputs

| Name | Description |
| -------------- | ------------------------------------------------------------------------------------------------ |
| `gate_passed` | true/false if audit gate passed (no critical or high vulnerabilities in production dependencies) |
| `gate_summary` | Markdown summary of audit results |

## 🚀 Usage

Basic usage example:

```yaml
- name: Audit NPM dependencies
id: audit
uses: ./.github/actions/audit-npm
continue-on-error: true
```

Example outputs:

```yaml
steps.audit.outputs.gate_passed
steps.audit.outputs.gate_summary
```

Example usage of outputs in later steps:

```yaml
- name: Show audit summary
run: echo "${{ steps.audit.outputs.gate_summary }}"

- name: Check audit gate
if: steps.audit.outputs.gate_passed == 'false'
run: |
echo "Audit gate failed"
exit 1
```

## 🧠 Notes

- The audit gate only checks production dependencies for critical or high vulnerabilities.
- The summary table includes both production and all dependencies.
- This action does not auto-fix vulnerabilities; it only reports them.

## Versioning

This action uses namespaced tags for versioning and is tracked in the CHANGELOG.

```text
action/audit-npm/vX.Y.Z
```

See the repository's versioning documentation for details on how tags are validated and created.
75 changes: 75 additions & 0 deletions .github/actions/audit-npm/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Audit NPM Dependencies
description: Run npm audit, parse results, and output a summary and audit gate

outputs:
gate_passed:
description: 'true/false if audit gate passed (no critical or high vulnerabilities in production dependencies)'
value: ${{ steps.evaluate-gate.outputs.gate_passed }}
gate_summary:
description: 'Markdown summary of audit results'
value: ${{ steps.generate-summary.outputs.gate_summary }}

runs:
using: 'composite'
steps:
- name: Audit All
id: audit-all
shell: bash
run: npm audit --json > audit-all.json || true

- name: Audit Production
id: audit-prod
shell: bash
run: npm audit --json --omit=dev > audit-prod.json || true

- name: Parse audit reports
id: parse-audit
shell: bash
run: |
jq_counts='
(.metadata.vulnerabilities // {}) as $v |
[
($v.critical // 0),
($v.high // 0),
($v.moderate // 0),
($v.low // 0)
] | @tsv
'
read -r ALL_CRIT ALL_HIGH ALL_MOD ALL_LOW < <(jq -r "$jq_counts" audit-all.json)
read -r PRD_CRIT PRD_HIGH PRD_MOD PRD_LOW < <(jq -r "$jq_counts" audit-prod.json)
echo "ALL_CRIT=$ALL_CRIT" >> $GITHUB_ENV
echo "ALL_HIGH=$ALL_HIGH" >> $GITHUB_ENV
echo "ALL_MOD=$ALL_MOD" >> $GITHUB_ENV
echo "ALL_LOW=$ALL_LOW" >> $GITHUB_ENV
echo "PRD_CRIT=$PRD_CRIT" >> $GITHUB_ENV
echo "PRD_HIGH=$PRD_HIGH" >> $GITHUB_ENV
echo "PRD_MOD=$PRD_MOD" >> $GITHUB_ENV
echo "PRD_LOW=$PRD_LOW" >> $GITHUB_ENV

- name: Evaluate audit gate
id: evaluate-gate
shell: bash
run: |
if [ "$PRD_CRIT" -gt 0 ] || [ "$PRD_HIGH" -gt 0 ]; then
gate_passed="false"
else
gate_passed="true"
fi
echo "gate_passed=${gate_passed}" >> "$GITHUB_OUTPUT"

- name: Generate audit summary
id: generate-summary
shell: bash
run: |
{
echo "gate_summary<<EOF"
echo "### 🛡️ Dependency Audit"
echo ""
echo "| | ⛔ Critical | ❌ High | ⚠️ Moderate | 🔵 Low |"
echo "|:---------:|:--------:|:----:|:--------:|:---:|"
echo "| All | $ALL_CRIT | $ALL_HIGH | $ALL_MOD | $ALL_LOW |"
echo "| Production| $PRD_CRIT | $PRD_HIGH | $PRD_MOD | $PRD_LOW |"
echo ""
echo "<sub>Gate checks production dependencies only (fails on any Critical or High)</sub>"
echo "EOF"
} >> "$GITHUB_OUTPUT"
12 changes: 12 additions & 0 deletions .github/actions/run-npm-script/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# run-npm-script action Changelog

All notable changes to the **run-npm-script** action are documented in this file.

## v1.0.0

### Added

- Initial release of run-npm-script composite action
- Runs the specified npm script from the input, if present in package.json
- Outputs status: success, failure, or notpresent
- Supports custom working directory
80 changes: 80 additions & 0 deletions .github/actions/run-npm-script/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Run NPM Script Action

## 🧭 Summary

Runs a specified npm script (e.g., build, lint:check, test) if present in package.json, and outputs the result as success, failure, or notpresent. Useful for DRY, reusable npm script checks in CI workflows.

## Scope/Limitations

- Only works with projects that have a package.json in the specified working directory.
- Requires jq (preinstalled on GitHub-hosted runners).
- Only checks for the existence of the script key, not its content.

## 🔒 Permissions

The following GHA permissions are required to use this step:

```yaml
permissions:
contents: read
```

## Dependencies

- `jq` — JSON processor (preinstalled on GitHub-hosted Ubuntu runners)
- `npm` — Node.js package manager

## ⚙️ Inputs

| Name | Required | Description |
| ------------------- | -------- | -------------------------------------------------------------------------------- |
| `script` | ✅ | The npm script to run (e.g., build, lint:check, test) |
| `working_directory` | ❌ | Directory containing package.json, pass in '.' if you want the current directory |

## 📤 Outputs

| Name | Description |
| -------- | ------------------------------- |
| `status` | success, failure, or notpresent |

## 🚀 Usage

Basic usage example:

```yaml
- name: Run build script
id: build
uses: ./.github/actions/run-npm-script
with:
working-directory: '.'
script: build
```

Example outputs:

```yaml
steps.build.outputs.status
```

Example usage of outputs in later steps:

```yaml
if: steps.build.outputs.status == 'success'
run: echo "Build passed!"
```

## 🧠 Notes

- The action will output notpresent if the script is not found in package.json or if package.json is missing.
- The action will output failure if the script exists but fails.
- The action will output success if the script runs and exits with code 0.

## Versioning

This action uses namespaced tags for versioning and is tracked in the CHANGELOG.

```text
action/run-npm-script/vX.Y.Z
```

See the repository's versioning documentation for details on how tags are validated and created.
44 changes: 44 additions & 0 deletions .github/actions/run-npm-script/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: 'Run NPM Script'
description: 'Run an npm script if present, and output status'

inputs:
script:
description: 'The npm script to run (e.g., build, lint:check, test)'
required: true
default: ''
working_directory:
description: 'Directory containing package.json (optional)'
required: false
default: '.'

outputs:
status:
description: 'success, failure, or notpresent'
value: ${{ steps.run-script.outputs.status }}

runs:
using: 'composite'
steps:
- name: Run npm script
id: run-script
shell: bash
env:
WORKING_DIRECTORY: ${{ inputs.working_directory }}
SCRIPT: ${{ inputs.script }}
run: |
cd "$WORKING_DIRECTORY"
if ! [ -f package.json ]; then
echo "No package.json found. Skipping $SCRIPT."
echo "status=notpresent" >> "$GITHUB_OUTPUT"
exit 0
fi
if ! jq -e --arg s "$SCRIPT" '.scripts | has($s)' package.json > /dev/null; then
echo "script $SCRIPT not found in package.json. Skipping."
echo "status=notpresent" >> "$GITHUB_OUTPUT"
exit 0
fi
if npm run "$SCRIPT"; then
echo "status=success" >> "$GITHUB_OUTPUT"
else
echo "status=failure" >> "$GITHUB_OUTPUT"
fi
30 changes: 5 additions & 25 deletions .github/workflows/internal_on_push_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,11 @@ permissions:
jobs:
internal-ci:
name: Internal CI
runs-on: ubuntu-latest

steps:
- name: Checkout repo
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc

- name: Install dependencies
run: npm ci

- name: Dependency Audit
run: npm audit

- name: Test
run: npm test

- name: Lint Check
run: npm run lint:check

- name: Format Check
run: npm run format:check
uses: ./.github/workflows/run_npm_ci_scripts.yml
secrets: inherit
with:
working_directory: '.'
commit_identifier: ${{ github.sha }}

semgrep:
uses: ./.github/workflows/run_semgrep_scan.yml
Expand Down
Loading
Loading