Skip to content
Open
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
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changelog uses ## v1.0.0, but the repo’s version validation expects headings to be exactly ## X.Y.Z (no v prefix). Using v1.0.0 will cause automated version/changelog validation to fail.

Suggestion: change the heading to ## 1.0.0 and ensure the PR label matches that version format.

Suggested change
## v1.0.0
## 1.0.0

Copilot uses AI. Check for mistakes.

### 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
Comment on lines +72 to +76
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The versioning section uses a tag format of action/.../vX.Y.Z, but this repo’s policy uses namespaced tags like actions/<component-name>/X.Y.Z and changelog headings must be ## X.Y.Z.

Suggestion: update this section to match VERSIONING.md so consumers use the correct tag format.

Copilot uses AI. Check for mistakes.
```

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
Comment on lines +15 to +18
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This composite action always runs npm audit in the current directory. Since the reusable workflow is parameterized by working_directory, audit needs a way to run from that directory too.

Suggestion: add a working_directory input and cd into it before running the audit commands (for both the all-deps and prod-only audits).

Copilot uses AI. Check for mistakes.

- 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
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changelog uses ## v1.0.0, but the repo’s version validation expects headings to be exactly ## X.Y.Z (no v prefix). Using v1.0.0 will cause automated version/changelog validation to fail.

Suggestion: change the heading to ## 1.0.0 and ensure the PR label matches that version format.

Suggested change
## v1.0.0
## 1.0.0

Copilot uses AI. Check for mistakes.

### 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: '.'
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The usage example sets working-directory, but the action input is working_directory (underscore) in action.yml. As written, the example won’t apply the input.

Suggestion: update the example to use working_directory.

Suggested change
working-directory: '.'
working_directory: '.'

Copilot uses AI. Check for mistakes.
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
Comment on lines +74 to +77
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The versioning section uses a tag format of action/.../vX.Y.Z, but this repo’s policy uses namespaced tags like actions/<component-name>/X.Y.Z (and changelog headings must be ## X.Y.Z).

Suggestion: update this section to match VERSIONING.md so consumers use the correct tag format.

Suggested change
This action uses namespaced tags for versioning and is tracked in the CHANGELOG.
```text
action/run-npm-script/vX.Y.Z
This action uses namespaced tags for versioning and is tracked in the CHANGELOG under headings of the form `## X.Y.Z`.
```text
actions/run-npm-script/X.Y.Z

Copilot uses AI. Check for mistakes.
```

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
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jq -e --arg s "$SCRIPT" '.scripts | has($s)' will error if package.json has no scripts key (because has() can’t be applied to null). That can produce noisy logs or unintended behavior for minimal package.json files.

Suggestion: make the jq expression null-safe (e.g., treat missing .scripts as {}) so “script not present” is handled cleanly.

Suggested change
if ! jq -e --arg s "$SCRIPT" '.scripts | has($s)' package.json > /dev/null; then
if ! jq -e --arg s "$SCRIPT" '.scripts // {} | has($s)' package.json > /dev/null; then

Copilot uses AI. Check for mistakes.
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 @@ -14,31 +14,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