Files
nexu-io__open-design/.github/workflows/labeler.yml
xne998808-ai 88d1eab743 feat(landing): surface user-submitted tutorial issues and PRs in the daily digest (#4095)
* feat(landing): surface user-submitted tutorial issues and PRs in the digest

User submissions lived only on GitHub and never entered the daily review flow.
The tutorials-youtube-sync digest now also lists them so they're reviewed
alongside auto-discovered YouTube candidates:

- Submission issues: open issues from the 'Submit a tutorial' form (label
  tutorials). The card shows each with the video link extracted from the body;
  on approval a maintainer generates the entry and opens a PR (Closes #issue).
- Contribution PRs: open PRs touching app/content/tutorials/** are auto-labeled
  'tutorials' by a new labeler workflow, and listed in the card for review/merge
  on GitHub.

Adds github-submissions.ts (search-API fetch + video-URL extraction) with unit
tests, two new Feishu card sections, issues:read + pull-requests:read on the
workflow, and the labeler workflow/config. Submission lookup is non-fatal — a
failure skips those sections without affecting the YouTube candidates.

* fix(landing): fail on submission lookup error; broaden YouTube URL parsing

Per review:
- A failed submission search no longer silently posts a digest without the
  issue/PR sections. It now aborts before posting (exit non-zero, observable);
  submissions aren't windowed so the next run re-queries them, and YouTube
  candidates are re-covered via the unchanged watermark — nothing is lost.
- Extract a shared extractYouTubeId() (watch incl. m.youtube, youtu.be, embed,
  shorts, live) used by both the submission parser and generate-selected, so the
  digest no longer mislabels shorts/mobile links as 'no video link'. videoUrl is
  canonicalized to youtu.be/<id>.
- Tests: shorts + mobile-watch extraction, and that a failed search rejects
  (is not swallowed).

* fix(landing): host-anchor extractYouTubeId to real YouTube hosts

Per review: the broadened matcher accepted ?v=<id> on any host, so a non-YouTube
link like https://example.com/watch?v=... was canonicalized to a youtu.be URL
and hid invalid submissions. Require a YouTube host (youtube.com / m. / music. /
youtu.be) before accepting v=/shorts/embed/live. Add a regression test that a
non-YouTube ?v= URL leaves videoUrl undefined.

* fix(landing): validate YouTube host via URL hostname, not substring

Per review: the substring host check matched lookalikes (notyoutube.com,
evil-youtube.com). extractYouTubeId now parses candidate URLs with new URL() and
checks hostname against an allowlist (youtu.be, youtube.com, www/m/music
.youtube.com) before extracting the id. Regression fixtures for non-YouTube and
lookalike hosts.

* fix(ci): grant issues:write to labeler so it can create the tutorials label

Per review: actions/labeler needs issue-label write to create the tutorials
label when missing. The label is also pre-created in the repo so the
issue-submission form can apply it; this makes first-run deterministic.

* fix(landing): sync tutorials label to live diff; paginate submission search

Per review:
- labeler: sync-labels: true so the tutorials label is removed when a PR no
  longer touches the tutorials content dir, keeping the digest's
  is:pr label:tutorials query free of stale/non-tutorial PRs.
- searchIssues paginates (per_page=100, loops until a short page, 10-page cap)
  so a backlog over one page is not silently truncated. Test stubs two pages.

---------

Co-authored-by: Joey <276262049+xne998808-ai@users.noreply.github.com>
2026-06-10 14:18:29 +00:00

33 lines
1.1 KiB
YAML

name: labeler
# Auto-applies the `tutorials` label to PRs that touch the tutorials content
# directory, so the scheduled tutorials-youtube-sync digest can list community
# tutorial contributions for review. Config: .github/labeler.yml.
on:
pull_request_target:
types: [opened, synchronize, reopened]
permissions:
contents: read
pull-requests: write
# actions/labeler needs issue-label write to create the `tutorials` label if
# it does not yet exist before applying it (the label is also pre-created in
# the repo so the issue-submission form can apply it too).
issues: write
concurrency:
group: labeler-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
label:
if: github.repository == 'nexu-io/open-design'
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v5
with:
# Keep the `tutorials` label in sync with the live diff: remove it if a
# PR no longer touches the tutorials content dir, so the digest's
# `is:pr label:tutorials` query never lists stale, non-tutorial PRs.
sync-labels: true