Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion danger/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ jobs:
* required: false
* default: `${{ github.token }}`

* `extra-dangerfile`: Path to an additional dangerfile to run custom checks.
* type: string
* required: false
* default: ""

* `extra-install-packages`: Additional packages that are required by the extra-dangerfile, you can find a list of packages here: https://packages.debian.org/search?suite=bookworm&keywords=curl.
* type: string
* required: false
* default: ""

## Outputs

* `outcome`: Whether the Danger run finished successfully. Possible values are `success`, `failure`, `cancelled`, or `skipped`.
Expand All @@ -52,4 +62,18 @@ The Danger action runs the following checks:
- **Conventional commits**: Validates commit message format and PR title conventions
- **Cross-repo links**: Checks for proper formatting of links in changelog entries

For detailed rule implementations, see [dangerfile.js](dangerfile.js).
For detailed rule implementations, see [dangerfile.js](dangerfile.js).

## Extra Danger File

When using an extra dangerfile, the file must be inside the repository and written in CommonJS syntax. You can use the following snippet to export your dangerfile:

```JavaScript
module.exports = async function ({ fail, warn, message, markdown, danger }) {
...
const gitUrl = danger.github.pr.head.repo.git_url;
...
warn('...');
}

```
57 changes: 51 additions & 6 deletions danger/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ inputs:
description: 'Token for the repo. Can be passed in using {{ secrets.GITHUB_TOKEN }}'
required: false
default: ${{ github.token }}
extra-dangerfile:
description: 'Path to additional dangerfile to run after the main checks'
type: string
required: false
extra-install-packages:
description: 'Additional apt packages to install in the DangerJS container (space-separated package names)'
type: string
required: false

outputs:
outcome:
Expand All @@ -18,8 +26,10 @@ runs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
env:
API_TOKEN: ${{ inputs.api-token }}
with:
token: ${{ inputs.api-token }}
token: ${{ env.API_TOKEN }}
fetch-depth: 0

# Read the Danger version from the properties file
Expand All @@ -28,19 +38,54 @@ runs:
shell: pwsh
run: Get-Content '${{ github.action_path }}/danger.properties' | Tee-Object $env:GITHUB_OUTPUT -Append

# Validate extra-install-packages to prevent code injection
- name: Validate package names
if: ${{ inputs.extra-install-packages }}
shell: bash
env:
EXTRA_INSTALL_PACKAGES: ${{ inputs.extra-install-packages }}
run: |
packages="$EXTRA_INSTALL_PACKAGES"
# Only allow alphanumeric characters, hyphens, periods, plus signs, underscores, and spaces
if ! echo "$packages" | grep -E '^[a-zA-Z0-9._+-]+( [a-zA-Z0-9._+-]+)*$' > /dev/null; then
echo "::error::Invalid package names in extra-install-packages. Only alphanumeric characters, hyphens, periods, plus signs, underscores, and spaces are allowed."
exit 1
fi

# Using a pre-built docker image in GitHub container registry instead of NPM to reduce possible attack vectors.
- name: Run DangerJS
id: danger
- name: Setup container
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.api-token }}
EXTRA_DANGERFILE_INPUT: ${{ inputs.extra-dangerfile }}
run: |
docker run \
# Start a detached container with all necessary volumes and environment variables
docker run -td --name danger \
--entrypoint /bin/bash \
--volume ${{ github.workspace }}:/github/workspace \
--volume ${{ github.action_path }}:${{ github.action_path }} \
--volume ${{ github.event_path }}:${{ github.event_path }} \
--workdir /github/workspace \
--user $(id -u) \
-e "INPUT_ARGS" -e "GITHUB_JOB" -e "GITHUB_REF" -e "GITHUB_SHA" -e "GITHUB_REPOSITORY" -e "GITHUB_REPOSITORY_OWNER" -e "GITHUB_RUN_ID" -e "GITHUB_RUN_NUMBER" -e "GITHUB_RETENTION_DAYS" -e "GITHUB_RUN_ATTEMPT" -e "GITHUB_ACTOR" -e "GITHUB_TRIGGERING_ACTOR" -e "GITHUB_WORKFLOW" -e "GITHUB_HEAD_REF" -e "GITHUB_BASE_REF" -e "GITHUB_EVENT_NAME" -e "GITHUB_SERVER_URL" -e "GITHUB_API_URL" -e "GITHUB_GRAPHQL_URL" -e "GITHUB_REF_NAME" -e "GITHUB_REF_PROTECTED" -e "GITHUB_REF_TYPE" -e "GITHUB_WORKSPACE" -e "GITHUB_ACTION" -e "GITHUB_EVENT_PATH" -e "GITHUB_ACTION_REPOSITORY" -e "GITHUB_ACTION_REF" -e "GITHUB_PATH" -e "GITHUB_ENV" -e "GITHUB_STEP_SUMMARY" -e "RUNNER_OS" -e "RUNNER_ARCH" -e "RUNNER_NAME" -e "RUNNER_TOOL_CACHE" -e "RUNNER_TEMP" -e "RUNNER_WORKSPACE" -e "ACTIONS_RUNTIME_URL" -e "ACTIONS_RUNTIME_TOKEN" -e "ACTIONS_CACHE_URL" -e GITHUB_ACTIONS=true -e CI=true \
-e GITHUB_TOKEN="${{ inputs.api-token }}" \
-e GITHUB_TOKEN="$GITHUB_TOKEN" \
-e DANGER_DISABLE_TRANSPILATION="true" \
-e "EXTRA_DANGERFILE_INPUT=$EXTRA_DANGERFILE_INPUT" \
ghcr.io/danger/danger-js:${{ steps.config.outputs.version }} \
--failOnErrors --dangerfile ${{ github.action_path }}/dangerfile.js
-c "sleep infinity"

- name: Setup additional packages
if: ${{ inputs.extra-install-packages }}
shell: bash
env:
EXTRA_INSTALL_PACKAGES: ${{ inputs.extra-install-packages }}
run: |
echo "Installing packages: $EXTRA_INSTALL_PACKAGES"
docker exec --user root danger sh -c "set -e && apt-get update && apt-get install -y --no-install-recommends $EXTRA_INSTALL_PACKAGES"
echo "All additional packages installed successfully."

- name: Run DangerJS
id: danger
shell: bash
run: |
docker exec --user $(id -u) danger danger ci --fail-on-errors --dangerfile ${{ github.action_path }}/dangerfile.js
42 changes: 42 additions & 0 deletions danger/dangerfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,52 @@ async function checkActionsArePinned() {
}
}

async function checkFromExternalChecks() {
// Get the external dangerfile path from environment variable (passed via workflow input)
// Priority: EXTRA_DANGERFILE (absolute path) -> EXTRA_DANGERFILE_INPUT (relative path)
const extraDangerFilePath = process.env.EXTRA_DANGERFILE || process.env.EXTRA_DANGERFILE_INPUT;
console.log(`::debug:: Checking from external checks: ${extraDangerFilePath}`);
if (extraDangerFilePath) {
try {
const workspaceDir = '/github/workspace';

const path = require('path');
const fs = require('fs');
const customPath = path.join(workspaceDir, extraDangerFilePath);
// Ensure the resolved path is within workspace
const resolvedPath = fs.realpathSync(customPath);
if (!resolvedPath.startsWith(workspaceDir)) {
fail(`Invalid dangerfile path: ${extraDangerFilePath}. Must be within workspace.`);
return;
}

const extraModule = require(customPath);
if (typeof extraModule !== 'function') {
warn(`EXTRA_DANGERFILE must export a function at ${customPath}`);
return;
}
await extraModule({
fail: fail,
warn: warn,
message: message,
markdown: markdown,
danger: danger,
});
} catch (err) {
if (err.message && err.message.includes('Cannot use import statement outside a module')) {
warn(`External dangerfile uses ES6 imports. Please convert to CommonJS syntax (require/module.exports) or use .mjs extension with proper module configuration.\nFile: ${extraDangerFilePath}`);
} else {
warn(`Could not load custom Dangerfile: ${extraDangerFilePath}\n${err}`);
}
}
}
}

async function checkAll() {
await checkDocs();
await checkChangelog();
await checkActionsArePinned();
await checkFromExternalChecks();
}

schedule(checkAll);
Loading