Files
lucide/.github/workflows/ci.yml
T
Eric FennisandClaude Opus 5 caa622b4ea ci(security): improve security with adding permissions (#4619)
* ci(security): add scoped GITHUB_TOKEN permissions to all workflows

Every workflow now declares its token permissions explicitly instead of
inheriting the repository default, which satisfies OpenSSF Scorecard's
token-permissions check.

Multi-job and write-scoped workflows use `permissions: {}` as a
deny-by-default floor at the top level, with the actual grants declared
per job. Consolidating those grants at the workflow level would leak
scopes into jobs that don't need them — a `permissions` block replaces
rather than merges, so the empty top-level block also makes a forgotten
job block fail closed rather than inherit the default.

Single-job read-only package workflows keep a plain top-level
`contents: read`; there is no write scope to leak, so a second block per
file would only add noise.

Scopes were matched to what each job's steps actually do:

- linting-icons: `contents: read` per job. No `pull-requests: read` —
  tj-actions/changed-files only needs that with `use_rest_api: true`,
  and here it reads the local .git.
- lint-pr-title: `pull-requests: read`, which
  action-semantic-pull-request needs to read the PR title.
- close-stale-prs: `pull-requests: write` only; the job never checks out.
- comment-icon-preview: `actions: read` for the cross-run artifact
  download, plus `pull-requests: write` to post the comment.
- release: `contents: read` for the checkouts, `id-token: write` only on
  the publishing jobs that use npm provenance, and `contents: write` only
  on post-release, which uploads the release assets. The prepare job
  neither checks out nor calls the API, so its empty scope is explicit.
- ci: `contents: write` only on create-release; the reusable-workflow
  calls pass `id-token: write` and `contents: write` down to release.yml,
  since a called workflow can only reduce what the caller grants.

Verified with actionlint (no new findings) and zizmor (no
excessive-permissions findings).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(ci): correct pull_request path filter in the lucide-vue workflow

The Vue package lives in packages/vue, not packages/lucide-vue, so the
filter never matched and the workflow did not run for changes to the
package itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Remove empty scope in prepare

* Patch WeaknessCWE-506

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 09:57:00 +02:00

132 lines
3.9 KiB
YAML

name: Continuous integration icons
on:
push:
branches:
- main
paths:
- icons/**/*.svg
workflow_dispatch:
inputs:
version:
description: Version
required: true
tag:
required: true
description: NPM tag
default: 'latest'
type: choice
options:
- latest
- next
- beta
- alpha
permissions: {}
jobs:
check-dispatch-gate:
if: github.repository == 'lucide-icons/lucide' &&
startsWith(github.event.head_commit.message, 'feat(icons)') &&
github.event_name != 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
RELEASE_ENVIRONMENT: ${{ steps.environment.outputs.RELEASE_ENVIRONMENT }}
steps:
- uses: actions/checkout@v6
- name: Fetch tags
run: git fetch --all --tags
- name: Check if latest tag was created today
id: environment
run: |
LATEST_TAG=$(git tag --sort=-v:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1)
TAG_DATE=$(git log -1 --format=%cs "$LATEST_TAG")
TODAY=$(date +%Y-%m-%d)
if [ "$TAG_DATE" == "$TODAY" ]; then
echo "::warning::A release tag already exists for today ($TODAY): $LATEST_TAG. Manual approval will be required to proceed."
echo "RELEASE_ENVIRONMENT=Icon Release Gated" >> $GITHUB_OUTPUT
else
echo "RELEASE_ENVIRONMENT=Icon Release" >> $GITHUB_OUTPUT
fi
create-release:
if: github.repository == 'lucide-icons/lucide'
needs: check-dispatch-gate
environment: ${{ needs.check-dispatch-gate.outputs.RELEASE_ENVIRONMENT }}
runs-on: ubuntu-latest
permissions:
contents: write # Required to create the release and tag
outputs:
VERSION: ${{ steps.new-version.outputs.NEW_VERSION }}
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Fetch tags
run: git fetch --all --tags
- name: Get latest tag
id: latest-tag
run: echo "LATEST_TAG=$(git tag --sort=-v:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1)" >> $GITHUB_OUTPUT
- name: Log latest tag
run: echo '${{ steps.latest-tag.outputs.LATEST_TAG }}'
- name: Check if we can patch
run: pnpm semver $LATEST_TAG -i minor
env:
LATEST_TAG: ${{ steps.latest-tag.outputs.LATEST_TAG }}
- name: Create new version
id: new-version
run: echo "NEW_VERSION=$(pnpm semver $LATEST_TAG -i minor)" >> $GITHUB_OUTPUT
env:
LATEST_TAG: ${{ steps.latest-tag.outputs.LATEST_TAG }}
- name: Check output
run: |
echo '${{ steps.new-version.outputs.NEW_VERSION }}'
echo '${{ steps.change-log.outputs.CHANGE_LOG }}'
- name: Create Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ steps.new-version.outputs.NEW_VERSION }}
name: Version ${{ steps.new-version.outputs.NEW_VERSION }}
generate_release_notes: true
start-release:
if: github.repository == 'lucide-icons/lucide'
needs: create-release
uses: './.github/workflows/release.yml'
secrets: inherit
permissions:
id-token: write # Required for OIDC (npm provenance)
contents: write # Required to upload release assets
with:
version: ${{ needs.create-release.outputs.VERSION }}
dispatch-release:
if: github.event_name == 'workflow_dispatch'
uses: './.github/workflows/release.yml'
secrets: inherit
permissions:
id-token: write # Required for OIDC (npm provenance)
contents: write # Required to upload release assets
with:
version: ${{ inputs.version }}
tag: ${{ inputs.tag }}