Skip to content

feat(ci): ephemeral PR preview среди с уникални по репозиторий имена - #1

Open
ydimitrof wants to merge 6 commits into
mainfrom
feat/ephemeral-pr-previews
Open

feat(ci): ephemeral PR preview среди с уникални по репозиторий имена#1
ydimitrof wants to merge 6 commits into
mainfrom
feat/ephemeral-pr-previews

Conversation

@ydimitrof

@ydimitrof ydimitrof commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Какво и защо

Добавя ephemeral preview среда за всеки PR — по един Cloudflare Worker на PR,
трит при затваряне и reap-ван след 5 дни без деплой. Портва pipeline-а от
lyubomir-bozhinov/sigma, но сменя схемата на имената.

Причината: този репозиторий, lyubomir-bozhinov/sigma и midt-bg/sigma
деплойват preview-тата си в един и същ Cloudflare акаунт. При схема
sigma-pr-<номер> PR midt-bg#12 тук и PR midt-bg#12 в другия форк дават едно и също име —
вторият деплой мълчаливо презаписва първия, а reaper-ът на всеки репозиторий
трие worker-ите на другия.

Затова името носи притежателя на репозиторя и се извежда, а не се
конфигурира:

ydimitrof/sigma          PR #12 → sigma-ydimitrof-pr-12
lyubomir-bozhinov/sigma  PR #12 → sigma-lyubomir-bozhinov-pr-12
midt-bg/sigma            PR #12 → sigma-midt-bg-pr-12

PREVIEW_WORKER_PREFIX в оригинала е опционална променлива с fallback към общ
sigma-pr — тоест връща същия сблъсък при първия, който забрави да я зададе.
Тук остава само като изричен override за по-къс URL; незададена вече е
безопасното състояние.

scripts/preview-name.mjs е единственият източник на името. Deploy, teardown и
reaper го внасят оттам — три копия на един regex е начинът, по който
преименувано preview остава завинаги неизтрито.

Поправен бъг от оригинала

Стъпката за коментар в preview-reap.yml извежда номера на PR-а с hardcode-нат
/^sigma-pr-(\d+)$/. При какъвто и да е друг префикс тя мълчаливо спира да
съвпада: worker-ите се трият, а PR-ът никога не разбира. Reaper-ът тук връща
(worker, PR) двойки от същия модул, който изгражда името.

Какво НЕ изолират уникалните имена

  • Rate-limit namespace_id-тата (10011005) са account-scoped целочислени
    id-та — квотите са общи за всяко preview на всеки форк в акаунта.
  • dev D1 / R2 / Vectorize са наистина споделени (read-only от preview worker-а).
  • *.workers.dev preview URL-ите не са зад Cloudflare Access.

И трите са описани в docs/dev-environments.md.

Обхват

  • Само web. ETL worker-ът пише в D1 и е cron-only — няма per-PR копие.
  • Без миграции. Preview-тата само четат споделената dev база.
  • Само същият репозиторий. Fork PR-ите нямат достъп до secrets и се
    пропускат (пак минават обикновеното CI).

Допълнителен обхват: undici

Пет advisory-та за undici 7.28.0, публикувани на 2026-08-03, чупят стъпката
Dependency audit на всеки PR в репозиторя — не само този. main мина последно
на 2026-07-30, преди да съществуват. undici е dev-only transitive
(jsdom→vitest, wrangler→miniflare) и не влиза в бъндъла на Worker-а.

pnpm-workspace.yaml вече има undici override заради три по-ранни advisory-та по
същия път, така че поправката е вдигане на пина до ^7.29.0 вместо нов suppression.

Това не е свързано с preview средите и по-редното място е отделен PR — включено
е тук съзнателно, за да не остане CI червено. Ако предпочитате, ще го изнеса.

Същият проблем ще удари и midt-bg/sigma при следващия run на main.

Свързан issue

Няма.

Вид промяна

  • feat — нова функционалност
  • docs — документация
  • ci / build — поддръжка

Как е тествано

pnpm test:scripts   → 90 tests, 90 pass, 0 fail
pnpm typecheck      → 7 successful, 7 total
pnpm test           → 6 successful, 6 total
pnpm lint           → All matched files use Prettier code style!
pnpm check:docs     → all docs refs resolve and every doc is indexed
osv-scanner v2.4.0  → No issues found          (същата команда като в CI)

Изведеното име:

$ GITHUB_REPOSITORY_OWNER=ydimitrof node scripts/preview-name.mjs --pr 12
PREVIEW_WORKER_PREFIX=sigma-ydimitrof-pr
SIGMA_WEB_NAME=sigma-ydimitrof-pr-12

$ GITHUB_REPOSITORY_OWNER=MidtBG node scripts/preview-name.mjs --pr 12   # case folding
PREVIEW_WORKER_PREFIX=sigma-midtbg-pr

$ node scripts/preview-name.mjs --pr 12                                  # без owner/override
preview-name: cannot derive a preview prefix ...   (exit 2 — без мълчалив default)

Защитните бариери при триене (--dry-run, GITHUB_REPOSITORY_OWNER=ydimitrof):

worker резултат
sigma-pr-12 ❌ отказан — чужд префикс
sigma-lyubomir-bozhinov-pr-12 ❌ отказан — чужд префикс
sigma, sigma-etl, sigma-dev ❌ отказан — защитено дълготрайно име
sigma-ydimitrof-pr-12 ✅ приет

Render-ът end-to-end (wrangler-render.mjs върху apps/web/wrangler.jsonc):

name      = sigma-ydimitrof-pr-12
d1        = sigma-dev/<SIGMA_D1_ID>
r2        = CSV_CACHE=sigma-csv-cache-dev, REPORTS=sigma-reports-dev
vectorize = sigma-assistant-dev

Непроверено: dry-run на reaper-а срещу живия акаунт (няма токен локално) —
той е проверката, че вижда само sigma-ydimitrof-pr-* и нула от
sigma-pr-*. Струва си да е първото след настройката на средата.

Чеклист

  • Комитите следват conventional commits и нямат Co-Authored-By: trailer
  • PR-ът е с един логически обхват — не: съдържа и несвързаното вдигане на
    undici (виж „Допълнителен обхват" по-горе), защото то чупи CI на всеки PR
  • pnpm typecheck минава
  • pnpm test (поне за засегнатите пакети) минава
  • pnpm lint е чисто
  • Няма комитнати тайни, .env* или .dev.vars
  • Документацията в docs/ е обновена

Преди да заработи

Нужен е GitHub Environment preview (без required reviewers) с Cloudflare
токен за акаунта, SIGMA_D1_ID за sigma-dev и четирите SIGMA_*_NAME
променливи — gh командите са в docs/dev-environments-setup.md.

Описва preview средата за всеки PR: жизнен цикъл, защитни бариери при
триене и настройката на `preview` GitHub Environment-а.

Ключовото решение е в имената. Този репозиторий, lyubomir-bozhinov/sigma и
midt-bg/sigma деплойват preview-тата си в един и същ Cloudflare акаунт, така
че схема `sigma-pr-<номер>` се сблъсква при еднакъв номер на PR — вторият
деплой презаписва първия, а reaper-ът на всеки репозиторий трие worker-ите
на другия. Затова името носи притежателя на репозиторя и се извежда
автоматично, вместо да се конфигурира.

.env.example допълва и трите липсващи имена на ресурси, които deploy.yml
вече изисква за всеки non-prod таргет.
scripts/preview-name.mjs е единственият източник на името на ephemeral
preview worker: `sigma-<owner>-pr-<номер на PR>`, изведено от
GITHUB_REPOSITORY_OWNER. Deploy, teardown и reaper го внасят оттам —
три копия на един regex е начинът, по който преименувано preview остава
завинаги неизтрито.

Изведено, а не конфигурирано: променлива с fallback към общ default връща
същия сблъсък при първия, който забрави да я зададе. PREVIEW_WORKER_PREFIX
остава като изричен override и се валидира, вместо да се пренаписва
мълчаливо.

Дължината се проверява срещу 63-символния DNS label лимит на
<име>.<subdomain>.workers.dev — при login от 39 символа изведеното име
стига до 54.
preview.yml деплойва web worker-а за всеки отворен PR от същия репозиторий
и го трие при затваряне; preview-reap.yml е TTL backstop-ът (03:17 UTC),
който хваща idle preview-та и orphan-и от пропаднал teardown.

Само web: ETL worker-ът пише в D1 и е cron-only, затова няма per-PR копие.
Без миграции: preview-тата споделят dev D1 read-only.

Триенето минава през три бариери — allowlist `<префикс>-<цифри>`, denylist
от дълготрайните имена, и отказ при липсващ префикс вместо предположение за
общ default. Reaper-ът изброява всички worker-и в споделения акаунт, но
обработва само тези с префикса на този репозиторий, така че не може да
изтрие живо preview на друг fork.

Reaper-ът връща (worker, PR) двойки от същия модул, който изгражда името,
вместо стъпката за коментар да ги извежда наново — hardcode-нат шаблон там
мълчаливо спира да съвпада при друг префикс и PR-ът никога не разбира, че
preview-то му е изтрито.

scripts/ живеят извън workspace-ите, затова `pnpm test:scripts` (node:test)
ги пуска отделно в CI.
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Test coverage

Workspace Lines Δ Branches Δ Functions Statements
apps/etl 74.05% +0.05pp 58.22% +0.02pp 70.21% 71.42%
apps/web 89.78% +0.08pp 81.82% +0.02pp 90.43% 88.73%
packages/config 92.85% +0.05pp 72.22% +0.02pp 92.85% 89.18%
packages/db 94.24% +0.04pp 79.01% +0.01pp 86.34% 91.13%
packages/ingest 85.86% +0.06pp 80.00% +0.00pp 79.27% 83.97%
packages/shared 95.40% +0.00pp 80.00% +0.00pp 91.66% 88.49%
Total (informational) 90.30% 79.58% 85.76% 88.01%

✅ No workspace dropped below its baseline (tolerance 0.5pp).

Пет нови advisory-та, публикувани на 2026-08-03, чупят „Dependency audit"
на всеки PR в репозиторя: cross-user disclosure + request smuggling
(GHSA-4cwx-7wf7-3272, HIGH), downstream response desync
(GHSA-8xcm-r25x-g524), cross-user disclosure по WebSocket
(GHSA-jr45-8vmc-qm54), CRLF injection през blob-like body type
(GHSA-m8rv-5g2x-5cg5) и cookie-attribute injection (GHSA-v3r7-h72x-cjcm).

undici е dev-only transitive (jsdom→vitest, wrangler→miniflare) и не влиза
в бъндъла на Worker-а, но 7.29.0 е поправката и override-ът вече съществува
за три по-ранни advisory-та по същия път — затова се вдига, вместо да се
добавя suppression.

Не е свързано с preview средите в този PR; включено тук, за да не остане
CI червено.
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

🌐 Preview deployedhttps://sigma-ydimitrof-pr-1.midt-platforms.workers.dev

Worker sigma-ydimitrof-pr-1 · shares the dev D1 (read-only). Updates on each push; removed when this PR closes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant