mirror of
https://github.com/nexu-io/open-design.git
synced 2026-07-07 23:09:34 +08:00
* 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>
66 lines
2.3 KiB
YAML
66 lines
2.3 KiB
YAML
name: tutorials-youtube-sync
|
|
|
|
on:
|
|
# Daily sweep for new community YouTube tutorials about Open Design. New
|
|
# candidates are posted to Feishu for a maintainer to review — this workflow
|
|
# does NOT generate entries or open PRs. A maintainer replies with which
|
|
# candidates to publish, and the selected videos are turned into entries via
|
|
# scripts/youtube-tutorials/generate-selected.ts (run by a maintainer, who
|
|
# opens the PR).
|
|
schedule:
|
|
- cron: '30 2 * * *'
|
|
# Manual trigger: force a sweep outside the daily schedule. Leave days empty to
|
|
# use the same gap-free watermark (since the last successful run) the schedule
|
|
# uses; set it to force a fixed N-day catch-up window.
|
|
workflow_dispatch:
|
|
inputs:
|
|
days:
|
|
description: 'Override lookback window in days (blank = since last successful run)'
|
|
required: false
|
|
default: ''
|
|
|
|
permissions:
|
|
contents: read
|
|
# Read this workflow's own run history to derive a gap-free window start.
|
|
actions: read
|
|
# Read open tutorial-submission issues and contribution PRs for the digest.
|
|
issues: read
|
|
pull-requests: read
|
|
|
|
concurrency:
|
|
group: tutorials-youtube-sync
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
notify:
|
|
name: Post tutorial candidates to Feishu
|
|
if: github.repository == 'nexu-io/open-design'
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6.0.2
|
|
|
|
- name: Setup Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '24'
|
|
|
|
- name: Discover candidates and post Feishu digest
|
|
working-directory: apps/landing-page
|
|
env:
|
|
GITHUB_TOKEN: ${{ github.token }}
|
|
YOUTUBE_API_KEY: ${{ secrets.YOUTUBE_API_KEY }}
|
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
ANTHROPIC_AUTH_TOKEN: ${{ secrets.ANTHROPIC_AUTH_TOKEN }}
|
|
ANTHROPIC_BASE_URL: ${{ secrets.ANTHROPIC_BASE_URL }}
|
|
FEISHU_TUTORIALS_WEBHOOK: ${{ secrets.FEISHU_TUTORIALS_WEBHOOK }}
|
|
FEISHU_TUTORIALS_SECRET: ${{ secrets.FEISHU_TUTORIALS_SECRET }}
|
|
DAYS_OVERRIDE: ${{ github.event.inputs.days }}
|
|
run: |
|
|
if [ -n "$DAYS_OVERRIDE" ]; then
|
|
npx --yes tsx@4.22.3 scripts/youtube-tutorials/notify-candidates.ts --days "$DAYS_OVERRIDE"
|
|
else
|
|
npx --yes tsx@4.22.3 scripts/youtube-tutorials/notify-candidates.ts
|
|
fi
|