Skip to content
Open
Show file tree
Hide file tree
Changes from 12 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
6 changes: 6 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ apps/
npm run test # Unit tests
```

### Docs Figma images

Files in `apps/next-docs/public/images/figma` are generated artifacts. Never edit, optimize, recompress, rename, replace, or delete these images or their manifest directly.

Update Figma references in the docs source, then regenerate the artifacts with `npm run figma-images:generate --workspace=apps/next-docs` or use the `update figma images` pull request label workflow. Commit only the output produced by the generator or workflow.

### Component development

- **Location**: `packages/react/src/ComponentName/`
Expand Down
140 changes: 140 additions & 0 deletions .github/workflows/update_figma_images.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
name: Update next-docs Figma images

on:
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-main
cancel-in-progress: true

permissions:
contents: read

jobs:
generate:
if: ${{ github.repository == 'primer/brand' }}
name: Generate image updates
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
has_changes: ${{ steps.capture.outputs.has_changes }}
source_sha: ${{ steps.source-sha.outputs.sha }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: main
fetch-depth: 0
persist-credentials: false

- name: Capture source SHA
id: source-sha
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"

- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: 24.14.1

- name: Cache dependencies
uses: actions/cache@v5
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-

- name: Install dependencies
run: npm ci

- name: Generate Figma images
run: npm run figma-images:generate --workspace=apps/next-docs
env:
FIGMA_ACCESS_TOKEN: ${{ secrets.FIGMA_ACCESS_TOKEN }}

- name: Capture generated image updates
id: capture
run: |
if [ -z "$(git status --short -- apps/next-docs/public/images/figma)" ]; then
echo 'has_changes=false' >> "$GITHUB_OUTPUT"
echo 'No next-docs Figma image changes were generated.'
exit 0
fi

artifact_directory='workflow-artifacts/next-docs-figma-images'
mkdir -p "$artifact_directory"
tar -cf "$artifact_directory/generated-images.tar" apps/next-docs/public/images/figma

echo 'has_changes=true' >> "$GITHUB_OUTPUT"
git status --short -- apps/next-docs/public/images/figma

- name: Upload generated image updates
if: steps.capture.outputs.has_changes == 'true'
uses: actions/upload-artifact@v7
with:
name: next-docs-figma-image-updates
path: workflow-artifacts/next-docs-figma-images/generated-images.tar
retention-days: 7

create-pr:
if: ${{ github.repository == 'primer/brand' && needs.generate.outputs.has_changes == 'true' }}
name: Create draft update PR
needs: generate
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: ${{ needs.generate.outputs.source_sha }}
fetch-depth: 0
persist-credentials: false

- name: Download generated image updates
uses: actions/download-artifact@v5
with:
name: next-docs-figma-image-updates
path: workflow-artifacts/next-docs-figma-images

- name: Apply generated image updates
run: |
rm -rf apps/next-docs/public/images/figma
tar -xf workflow-artifacts/next-docs-figma-images/generated-images.tar
git status --short -- apps/next-docs/public/images/figma

- name: Create GitHub App token
id: app-token
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
with:
app-id: ${{ vars.PRIMER_APP_ID_SHARED }}
private-key: ${{ secrets.PRIMER_APP_PRIVATE_KEY_SHARED }}

- name: Create draft pull request
id: create-pull-request
# Uses SHA for security hardening
uses: peter-evans/create-pull-request@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
token: ${{ steps.app-token.outputs.token }}
add-paths: |
apps/next-docs/public/images/figma
author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
branch: github-actions/update-next-docs-figma-images
base: main
delete-branch: true
commit-message: Update next-docs Figma images
title: Update next-docs Figma images
body: |
## Summary
- refresh generated Figma images for `apps/next-docs`
- apply additions, modifications, and deletions captured by the read-only generation job

## Testing
- `npm run figma-images:generate --workspace=apps/next-docs`
draft: always-true

- name: Report pull request
if: steps.create-pull-request.outputs.pull-request-url != ''
run: echo "Created or updated ${{ steps.create-pull-request.outputs.pull-request-url }}"
211 changes: 211 additions & 0 deletions .github/workflows/update_figma_images_on_label.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
name: Update Figma images on label

on:
pull_request_target:
types: [labeled]

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true

permissions:
contents: read
issues: write
pull-requests: read

jobs:
update-figma-images:
if: ${{ github.event.label.name == 'update figma images' }}
name: Update Figma images
runs-on: ubuntu-latest
steps:
- name: Verify update request is still valid
id: request-gate
uses: actions/github-script@v8
with:
script: |
const labelName = 'update figma images'
const {owner, repo} = context.repo
const pull_number = context.payload.pull_request.number

const {data: pullRequest} = await github.rest.pulls.get({
owner,
repo,
pull_number,
})

const hasLabel = pullRequest.labels.some((label) => label.name === labelName)
const isRepositoryBranch = pullRequest.head.repo.full_name === `${owner}/${repo}`
const shouldProcess = pullRequest.state === 'open' && hasLabel && isRepositoryBranch
const reason = !hasLabel ? 'label-removed' : pullRequest.state !== 'open' ? 'closed' : !isRepositoryBranch ? 'fork' : 'authorized'

core.setOutput('base_sha', pullRequest.base.sha)
core.setOutput('head_ref', pullRequest.head.ref)
core.setOutput('head_sha', pullRequest.head.sha)
core.setOutput('head_repo_full_name', pullRequest.head.repo.full_name)
core.setOutput('reason', reason)
core.setOutput('should_cleanup', hasLabel ? 'true' : 'false')
core.setOutput('should_process', shouldProcess ? 'true' : 'false')

if (shouldProcess) {
core.info(`Figma image update is authorized for PR #${pull_number}`)
return
}

if (reason === 'fork') {
core.info('Skipping because pull requests from forks cannot use repository secrets or push commits back to the branch.')
return
}

core.info(`Skipping because PR #${pull_number} is ${pullRequest.state} or missing the \"${labelName}\" label.`)

- name: Checkout trusted base revision
if: steps.request-gate.outputs.should_process == 'true'
uses: actions/checkout@v6
with:
ref: ${{ steps.request-gate.outputs.base_sha }}
fetch-depth: 0
persist-credentials: false

- name: Fetch pull request head revision
if: steps.request-gate.outputs.should_process == 'true'
env:
HEAD_SHA: ${{ steps.request-gate.outputs.head_sha }}
run: git fetch --no-tags origin "$HEAD_SHA" --depth=1

- name: Overlay pull request content as inert input
if: steps.request-gate.outputs.should_process == 'true'
env:
HEAD_SHA: ${{ steps.request-gate.outputs.head_sha }}
run: |
rm -rf apps/next-docs/content
mkdir -p apps/next-docs
git archive "$HEAD_SHA" apps/next-docs/content | tar -xf -

- name: Set up Node
if: steps.request-gate.outputs.should_process == 'true'
uses: actions/setup-node@v6
with:
node-version: 24.14.1

- name: Cache dependencies
if: steps.request-gate.outputs.should_process == 'true'
uses: actions/cache@v5
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-

- name: Install trusted dependencies
if: steps.request-gate.outputs.should_process == 'true'
run: npm ci

- name: Generate Figma images with trusted code
if: steps.request-gate.outputs.should_process == 'true'
run: npm run figma-images:generate --workspace=apps/next-docs
env:
FIGMA_ACCESS_TOKEN: ${{ secrets.FIGMA_ACCESS_TOKEN }}

- name: Archive generated Figma images
if: steps.request-gate.outputs.should_process == 'true'
run: |
artifact_directory='workflow-artifacts/update-figma-images'
mkdir -p "$artifact_directory"
tar -cf "$artifact_directory/generated-figma-images.tar" apps/next-docs/public/images/figma

- name: Checkout pull request head for commit only
if: steps.request-gate.outputs.should_process == 'true'
uses: actions/checkout@v6
with:
repository: ${{ steps.request-gate.outputs.head_repo_full_name }}
ref: ${{ steps.request-gate.outputs.head_sha }}
fetch-depth: 0
persist-credentials: false
path: pr-head

- name: Apply generated images to pull request head
if: steps.request-gate.outputs.should_process == 'true'
run: |
rm -rf pr-head/apps/next-docs/public/images/figma
mkdir -p pr-head/apps/next-docs/public/images
tar -xf workflow-artifacts/update-figma-images/generated-figma-images.tar -C pr-head

- name: Commit generated images
if: steps.request-gate.outputs.should_process == 'true'
id: commit-generated-images
run: |
git -C pr-head add -A apps/next-docs/public/images/figma

if git -C pr-head diff --cached --quiet; then
echo 'created_commit=false' >> "$GITHUB_OUTPUT"
echo 'No generated Figma image changes to commit.'
exit 0
fi

git -C pr-head config user.name 'github-actions[bot]'
git -C pr-head config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git -C pr-head commit -m 'github-actions[bot] Update Figma images'
echo 'created_commit=true' >> "$GITHUB_OUTPUT"

- name: Create GitHub App token
if: steps.commit-generated-images.outputs.created_commit == 'true'
id: app-token
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
with:
app-id: ${{ vars.PRIMER_APP_ID_SHARED }}
private-key: ${{ secrets.PRIMER_APP_PRIVATE_KEY_SHARED }}

- name: Push generated images
if: steps.commit-generated-images.outputs.created_commit == 'true'
env:
APP_TOKEN: ${{ steps.app-token.outputs.token }}
HEAD_REF: ${{ steps.request-gate.outputs.head_ref }}
HEAD_REPOSITORY: ${{ steps.request-gate.outputs.head_repo_full_name }}
run: |
git -C pr-head remote set-url origin "https://x-access-token:${APP_TOKEN}@github.com/${HEAD_REPOSITORY}.git"
git -C pr-head push origin "HEAD:${HEAD_REF}"

- name: Skip unsupported request
if: steps.request-gate.outputs.should_process != 'true'
run: |
case '${{ steps.request-gate.outputs.reason }}' in
fork)
echo 'Skipping Figma image update because pull requests from forks cannot use FIGMA_ACCESS_TOKEN or receive generated commits. Recreate the branch in this repository and re-apply the label.'
;;
closed)
echo 'Skipping Figma image update because the pull request is closed.'
;;
label-removed)
echo 'Skipping Figma image update because the request label is no longer present.'
;;
*)
echo 'Skipping Figma image update.'
;;
esac

- name: Remove request label
if: always() && steps.request-gate.outputs.should_cleanup == 'true' && (steps.request-gate.outputs.should_process != 'true' || success())
uses: actions/github-script@v8
with:
script: |
const labelName = 'update figma images'
const {owner, repo} = context.repo
const issue_number = context.payload.pull_request.number

try {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number,
name: labelName,
})
core.info(`Removed label \"${labelName}\" from PR #${issue_number}`)
} catch (error) {
if (error.status === 404) {
core.info(`Label \"${labelName}\" was already removed from PR #${issue_number}`)
return
}

throw error
}
25 changes: 25 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,31 @@ Here are a few things you can do that will increase the likelihood of your pull
- Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
- Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).

### Updating docs Figma images

Docs can use frames from the [Brand Interface Guidelines Figma file](https://www.figma.com/design/kc69gOteR1MsL0aQtLdxLW/-Brand--Interface-guidelines) with the `FigmaImage` component:

```mdx
<FigmaImage
src="https://www.figma.com/design/kc69gOteR1MsL0aQtLdxLW/Brand-Interface-Guidelines?node-id=1804-8382"
darkModeSrc="https://www.figma.com/design/kc69gOteR1MsL0aQtLdxLW/Brand-Interface-Guidelines?node-id=1804-8383"
alt="Describe the image"
/>
```

Figma URLs can also be used for the `thumbnail` and `thumbnail_darkMode` frontmatter fields.

To update the generated images locally:

1. Add `FIGMA_ACCESS_TOKEN=...` to `apps/next-docs/.env.local`.
1. Run `npm run figma-images:generate --workspace=apps/next-docs`.

Files in `apps/next-docs/public/images/figma` are generated artifacts. Do not edit, optimize, recompress, rename, replace, or delete the downloaded images or their manifest directly. Update the Figma references in the docs source and regenerate the files instead.

Commit only the changes produced by the generator. To update the images through GitHub Actions instead, add the `update figma images` label to your pull request.

CI runs `npm run figma-images:validate --workspace=apps/next-docs` to check the Figma URLs and generated files. This command does not require a Figma access token.

## Releasing a new Primer Brand version

See [RELEASING.md](RELEASING.md) for our release process.
Expand Down
Loading
Loading