moving action files, adding publishing, adjusted usage

This commit is contained in:
Luke Hagar
2025-09-11 16:09:36 +00:00
parent c753d4031a
commit cd45e4ccdb
8 changed files with 257 additions and 4 deletions

View File

@@ -45,7 +45,7 @@ jobs:
- name: Build action image
run: |
docker build -t slinky-action -f .github/actions/slinky/Dockerfile .
docker build -t slinky-action -f Dockerfile .
- name: Run action container (expect nonzero if failures)
id: run_action

View File

@@ -16,7 +16,7 @@ jobs:
uses: actions/checkout@v4
- name: Run Slinky link checker
uses: ./\.github/actions/slinky
uses: ./
with:
path: .
patterns: "**/*"

74
.github/workflows/publish.yml vendored Normal file
View File

@@ -0,0 +1,74 @@
name: Publish Release
on:
push:
branches:
- main
workflow_dispatch: {}
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Compute next v1 tag
id: compute
uses: actions/github-script@v7
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const tags = await github.paginate(github.rest.repos.listTags, { owner, repo, per_page: 100 });
const v1tags = tags
.map(t => t.name)
.filter(name => /^v1\.\d+\.\d+$/.test(name));
let nextPatch = 0;
let minor = 0;
if (v1tags.length > 0) {
let maxPatch = 0;
for (const name of v1tags) {
const [, mi, pa] = name.match(/^v1\.(\d+)\.(\d+)$/);
const m = parseInt(mi, 10);
const p = parseInt(pa, 10);
if (m > minor) { minor = m; maxPatch = p; }
else if (m === minor && p > maxPatch) { maxPatch = p; }
}
nextPatch = maxPatch + 1;
}
const newTag = `v1.${minor}.${nextPatch}`;
core.setOutput('tag', newTag);
- name: Create GitHub Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.compute.outputs.tag }}
release_name: ${{ steps.compute.outputs.tag }}
draft: false
prerelease: false
- name: Update floating v1 tag to latest
uses: actions/github-script@v7
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const sha = context.sha;
const ref = 'tags/v1';
try {
await github.rest.git.getRef({ owner, repo, ref });
await github.rest.git.updateRef({ owner, repo, ref, sha, force: true });
} catch (e) {
await github.rest.git.createRef({ owner, repo, ref: `refs/${ref}`, sha });
}

18
Dockerfile Normal file
View File

@@ -0,0 +1,18 @@
FROM golang:1.24 as build
WORKDIR /app
# Expect the repository root as build context when building this image
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /usr/local/bin/slinky ./
FROM alpine:3.20
RUN adduser -D -u 10001 appuser \
&& apk add --no-cache curl jq ca-certificates
COPY --from=build /usr/local/bin/slinky /usr/local/bin/slinky
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
USER appuser
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -21,7 +21,7 @@ check: build
# Build the Docker-based GitHub Action locally
action-image:
docker build -t slinky-action -f .github/actions/slinky/Dockerfile .
docker build -t slinky-action -f Dockerfile .
# Run the Action container against the current repo
action-run: action-image

View File

@@ -20,7 +20,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Run Slinky
uses: ./.github/actions/slinky
uses: owner/repo@v1
with:
path: .
patterns: "**/*"

72
action.yml Normal file
View File

@@ -0,0 +1,72 @@
name: "Slinky Link Checker"
description: "Slink through your repository looking for dead links"
author: "LukeHagar"
branding:
icon: "link"
color: "blue"
inputs:
path:
description: "Root path to scan"
required: false
default: "."
patterns:
description: "Comma-separated doublestar patterns. Ex: docs/**/*.md,**/*.go; default **/*"
required: false
default: "**/*"
concurrency:
description: "Maximum concurrent requests"
required: false
default: "16"
timeout:
description: "HTTP timeout seconds"
required: false
default: "10"
json-out:
description: "Optional path to write JSON results"
required: false
default: "results.json"
md-out:
description: "Optional path to write Markdown report for PR comment"
required: false
default: "results.md"
repo-blob-base:
description: "Override GitHub blob base URL (https://github.com/<owner>/<repo>/blob/<sha>)"
required: false
default: ""
fail-on-failures:
description: "Fail the job if any links fail"
required: false
default: "true"
comment-pr:
description: "If running on a PR, post a comment with the report"
required: false
default: "true"
step-summary:
description: "Append the report to the GitHub Step Summary"
required: false
default: "true"
runs:
using: "docker"
image: "Dockerfile"
args: []
env:
INPUT_PATH: ${{ inputs.path }}
INPUT_PATTERNS: ${{ inputs.patterns }}
INPUT_CONCURRENCY: ${{ inputs.concurrency }}
INPUT_TIMEOUT: ${{ inputs.timeout }}
INPUT_JSON_OUT: ${{ inputs["json-out"] }}
INPUT_MD_OUT: ${{ inputs["md-out"] }}
INPUT_REPO_BLOB_BASE: ${{ inputs["repo-blob-base"] }}
INPUT_FAIL_ON_FAILURES: ${{ inputs["fail-on-failures"] }}
INPUT_COMMENT_PR: ${{ inputs["comment-pr"] }}
INPUT_STEP_SUMMARY: ${{ inputs["step-summary"] }}
outputs:
json-path:
description: "Path to JSON results file"
md-path:
description: "Path to Markdown report file"

89
entrypoint.sh Normal file
View File

@@ -0,0 +1,89 @@
#!/bin/sh
set -eu
PATH_ARG="${INPUT_PATH:-.}"
PATTERNS_ARG="${INPUT_PATTERNS:-**/*}"
CONCURRENCY_ARG="${INPUT_CONCURRENCY:-16}"
TIMEOUT_ARG="${INPUT_TIMEOUT:-10}"
JSON_OUT_ARG="${INPUT_JSON_OUT:-results.json}"
MD_OUT_ARG="${INPUT_MD_OUT:-results.md}"
REPO_BLOB_BASE_ARG="${INPUT_REPO_BLOB_BASE:-}"
FAIL_ON_FAILURES_ARG="${INPUT_FAIL_ON_FAILURES:-true}"
COMMENT_PR_ARG="${INPUT_COMMENT_PR:-true}"
STEP_SUMMARY_ARG="${INPUT_STEP_SUMMARY:-true}"
ARGS="check \"${PATH_ARG}\" --concurrency ${CONCURRENCY_ARG} --timeout ${TIMEOUT_ARG}"
if [ "${FAIL_ON_FAILURES_ARG}" = "true" ]; then
ARGS="$ARGS --fail-on-failures true"
else
ARGS="$ARGS --fail-on-failures false"
fi
if [ -n "${PATTERNS_ARG}" ]; then
NORM_PATTERNS=$(printf "%s" "${PATTERNS_ARG}" | sed 's/,\s*/,/g')
IFS=','
set -- $NORM_PATTERNS
unset IFS
for pat in "$@"; do
ARGS="$ARGS --patterns \"$pat\""
done
fi
if [ -n "${JSON_OUT_ARG}" ]; then
ARGS="$ARGS --json-out \"${JSON_OUT_ARG}\""
fi
if [ -n "${MD_OUT_ARG}" ]; then
ARGS="$ARGS --md-out \"${MD_OUT_ARG}\""
fi
# Compute GitHub blob base URL for file links used in the Markdown report
if [ -n "${REPO_BLOB_BASE_ARG}" ]; then
export SLINKY_REPO_BLOB_BASE_URL="${REPO_BLOB_BASE_ARG}"
elif [ -n "${GITHUB_REPOSITORY:-}" ]; then
COMMIT_SHA="${GITHUB_SHA:-}"
if [ -n "${GITHUB_EVENT_PATH:-}" ] && command -v jq >/dev/null 2>&1; then
PR_HEAD_SHA="$(jq -r '.pull_request.head.sha // empty' "$GITHUB_EVENT_PATH" || true)"
if [ -n "$PR_HEAD_SHA" ]; then
COMMIT_SHA="$PR_HEAD_SHA"
fi
fi
if [ -n "$COMMIT_SHA" ]; then
export SLINKY_REPO_BLOB_BASE_URL="https://github.com/${GITHUB_REPOSITORY}/blob/${COMMIT_SHA}"
fi
fi
eval slinky ${ARGS}
# Expose outputs
if [ -n "${GITHUB_OUTPUT:-}" ]; then
if [ -n "${JSON_OUT_ARG}" ]; then
echo "json-path=${JSON_OUT_ARG}" >> "$GITHUB_OUTPUT"
fi
if [ -n "${MD_OUT_ARG}" ]; then
echo "md-path=${MD_OUT_ARG}" >> "$GITHUB_OUTPUT"
fi
fi
# Append report to job summary if requested
if [ "${STEP_SUMMARY_ARG}" = "true" ] && [ -n "${GITHUB_STEP_SUMMARY:-}" ] && [ -n "${MD_OUT_ARG}" ] && [ -f "${MD_OUT_ARG}" ]; then
cat "${MD_OUT_ARG}" >> "$GITHUB_STEP_SUMMARY"
fi
# Post PR comment if this is a PR and requested
if [ "${COMMENT_PR_ARG}" = "true" ] && [ -n "${MD_OUT_ARG}" ] && [ -f "${MD_OUT_ARG}" ]; then
PR_NUMBER=""
if [ -n "${GITHUB_EVENT_PATH:-}" ] && command -v jq >/dev/null 2>&1; then
PR_NUMBER="$(jq -r '.pull_request.number // empty' "$GITHUB_EVENT_PATH" || true)"
fi
if [ -n "${PR_NUMBER}" ] && [ -n "${GITHUB_REPOSITORY:-}" ] && [ -n "${GITHUB_TOKEN:-}" ]; then
BODY_CONTENT="$(cat "${MD_OUT_ARG}")"
curl -sS -H "Authorization: Bearer ${GITHUB_TOKEN}" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
-X POST "https://api.github.com/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \
-d "$(printf '{"body": %s}' "$(jq -Rs . <<EOF
${BODY_CONTENT}
EOF
)" )" >/dev/null || true
fi
fi