name: github-metrics on: schedule: - cron: '15 0 * * *' workflow_dispatch: permissions: contents: read jobs: metrics: name: Generate issue and PR activity SVG if: github.repository == 'nexu-io/open-design' runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6.0.2 - name: Generate OpenDesign bot token id: open-design-bot-token uses: actions/create-github-app-token@v3.2.0 with: client-id: ${{ secrets.BOT_APP_CLIENT_ID }} private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }} owner: nexu-io repositories: open-design permission-administration: read permission-contents: read permission-issues: read permission-pull-requests: read - name: Generate issue and PR activity uses: lowlighter/metrics@latest with: # Render into the action workspace only. The upload step publishes the # SVG to repository-assets R2 so generated metrics never write back to # the git history. filename: github-metrics.svg # Auth: use the OpenDesign GitHub App for follow-up metrics # that need private repository API access, but do not grant write # permissions because this workflow publishes to R2 instead of git. token: ${{ steps.open-design-bot-token.outputs.token }} output_action: none # Use the repository template (per-repo metrics, not user metrics). # Organization-owned repositories must be targeted explicitly, otherwise # lowlighter/metrics infers the token owner and treats the target as an org. template: repository base: '' user: nexu-io repo: open-design # Keep this README image focused on the maintenance signal that is not # covered elsewhere: issue and PR follow-up status. Contributors and # star history have dedicated README sections, and languages are # already summarized by GitHub. plugin_followup: yes plugin_followup_sections: pr, issue config_timezone: Asia/Shanghai config_display: large - name: Normalize activity SVG layout shell: bash run: | set -euo pipefail raw_svg="/metrics_renders/github-metrics.svg" svg="$RUNNER_TEMP/github-metrics.svg" if [ ! -s "$raw_svg" ]; then echo "Expected metrics SVG not found at $raw_svg" >&2 exit 1 fi cp "$raw_svg" "$svg" SVG_PATH="$svg" node <<'NODE' const fs = require('node:fs'); const svg = process.env.SVG_PATH; if (!svg) throw new Error('SVG_PATH is required'); let content = fs.readFileSync(svg, 'utf8'); content = content.replace( /]*)>/, '' ); content = content.replace( 'Overall issues and pull requests status', 'Issue and pull request status' ); const layoutCss = [ 'foreignObject>div{width:100%;display:flex;flex-direction:column;align-items:center;transform:scale(1.1);transform-origin:top center}', '.field{justify-content:center}', '.followup-title{text-align:center}', 'svg.large .largeable{width:520px}', 'svg.large .chart.largeable{width:504px}' ].join(''); content = content.replace('', `${layoutCss}`); fs.writeFileSync(svg, content); NODE - name: Publish metrics SVG to repository assets env: AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_REPOSITORY_ASSETS_AK }} AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_REPOSITORY_ASSETS_SK }} AWS_DEFAULT_REGION: auto AWS_EC2_METADATA_DISABLED: "true" CLOUDFLARE_R2_REPOSITORY_ASSETS_BUCKET: ${{ secrets.CLOUDFLARE_R2_REPOSITORY_ASSETS_BUCKET }} CLOUDFLARE_R2_REPOSITORY_ASSETS_PUBLIC_ORIGIN: ${{ vars.CLOUDFLARE_R2_REPOSITORY_ASSETS_PUBLIC_ORIGIN }} CLOUDFLARE_R2_REPOSITORY_ASSETS_URL: ${{ secrets.CLOUDFLARE_R2_REPOSITORY_ASSETS_URL }} shell: bash run: | set -euo pipefail svg="$RUNNER_TEMP/github-metrics.svg" if [ ! -s "$svg" ]; then echo "Expected metrics SVG not found at $svg" >&2 exit 1 fi key="resources/images/github-metrics.svg" node --experimental-strip-types .github/scripts/r2.ts put-object \ --endpoint "$CLOUDFLARE_R2_REPOSITORY_ASSETS_URL" \ --bucket "$CLOUDFLARE_R2_REPOSITORY_ASSETS_BUCKET" \ --key "$key" \ --file "$svg" \ --content-type "image/svg+xml" \ --cache-control "public, max-age=300" \ --public-origin "$CLOUDFLARE_R2_REPOSITORY_ASSETS_PUBLIC_ORIGIN"