ساخت و انتشار
pnpm -r build: بستهها با tsc -p tsconfig.build.json (کیت CSS را هم کپی میکند)، خط فرمان سپس با tsdown،
وب با Vite (پس از tsc ِ برنامه و Worker)، مستندات با Astro و check-links.mjs. مسیر داراییها
نسبی است (base: './')، پس برنامه هم از ریشه کار میکند، هم از زیرمسیر
(user.github.io/darzsaz/). از روی پرونده (file://) نه: کروم اسکریپتِ ماژول، CSS و قلم را از مبدأ null
رد میکند و #root خالی میماند (سنجیده ۱۴۰۵/۰۶/۲۳)؛ بستهٔ ساختهشده سرور میخواهد (scripts/serve-dist.mjs).
بودجهٔ اندازه، همه gzip («امروز»: نسل ششم، پایانِ فاز ۱۱، ۱۴۰۵/۰۶/۲۶):
| نگهبان | چه | سقف | امروز |
|---|---|---|---|
scripts/size-budget.mjs |
JavaScript ِ کاربرِ هر زبان | ۹۰۰ کیلوبایت | فارسی ۸۷۶ · انگلیسی ۸۷۵ |
| جمعِ CSS | ۲۵ | ۲۴ | |
| جمعِ قلم | ۱۳۰ | ۱۲۴ | |
| دیگر (آیکون، HTML) | ۱۵ | ۱۳ | |
| تکهٔ اولیه | ۱۶۵ | ۱۶۴ | |
| فونتِ base64 در تکهٔ js | ۰ | ۰ | |
size-limit (pnpm size) |
ورودی (index) |
۳۰ kB | ۲۶٫۸ |
کتابخانههای اولیه (vendor) |
۱۴۰ kB | ۱۳۴٫۱ | |
کیتِ تنبل (kit) |
۵۰ kB | ۴۶٫۳ | |
نمای سهبعدی (Scene3D، three) |
۲۷۵ kB | ۲۳۴٫۹ | |
| Worker ِ چیدمان | ۳۹ kB | ۳۶٫۶ | |
پنلِ مدیریت (admin) |
۸۰ kB | ۲٫۲ |
جمعِ js و css یک بار بالا رفتند (۸۵۰ ← ۹۰۰ و ۲۰ ← ۲۵، فاز ۱۱): چهار فازِ محصول از وقتی این
عددها گذاشته شد اضافه شده و همهاش در تکههای تنبل است. آنچه بالا نرفت همان چیزی است که اتصالِ
بد آن را حس میکند — تکهٔ اولیه، vendor و index — و همان روز پایین هم آمد: zod از مسیرِ
راهاندازی بیرون رفت (lib/prefs.ts دیگر واردش نمیکند)، پس vendor ۱۵۹ ← ۱۳۴ و تکهٔ اولیه
۱۸۸ ← ۱۶۴ شد. دلیل و عددها کنارِ BUDGET در scripts/size-budget.mjs.
قلمِ چاپ بیرون از js (نسل ششم، ۰.۱): تا نسل پنجم @darzsaz/report فونتِ چاپ را base64 در
جاوااسکریپتش داشت و وب همان را بسته میکرد — js ِ هر زبان ۸۷۹/۸۸۰ و بالای سقف. حالا همان ۶۷ کیلوبایت
پروندهٔ fonts/Vazirmatn-print.woff2 است که سرویسورکر پیشانباشت میکند و رابط هنگامِ ساختِ سند
جاسازیاش میکند (fonts.md)؛ سقفِ قلم ۶۵ ← ۱۳۰ برای همان بایتها، و سطرِ «فونتِ base64 در
تکهٔ js» برگشتنش را خطا میگیرد.
Worker ِ ۳۱ کیلوبایت (۰.۲): سقفِ ۱۹ از پیش از رفتنِ قاعدههای دستیار به Worker بود (موجِ ۳، P7S) و
از آن روز قرمز؛ ترکیبِ امروز با source-map-explorer روی derive.worker-*.js (بایتِ خام، کوچکشده):
validate ۲۸٫۱ + parts ۲۱٫۱ + nesting ۱۳٫۱ + hardware ۵٫۵ + machining ۵٫۵ + costing ۴٫۷ + layout ۴٫۴ +
pipeline ۳٫۴ + catalog ۲٫۶ + countertop ۱٫۶ + بقیه ۲٫۶ = ۹۲٫۶ کیلوبایتِ خام ← ۲۹٫۵ gzip. همهٔ اینها
کارِ خودِ Worker است (قطعه، چیدمان، قاعده، صورتحساب)؛ سقفِ ۳۱ یعنی ۵٪ جا برای رشدِ آگاهانه، نه بیشتر.
Worker ِ ۳۴ کیلوبایت (پایانِ فاز ۱): اصلاحِ عددهای برشکار (R1–R19) Worker را ۹۲٫۶ ← ۱۰۲٫۰ کیلوبایتِ خام
(۲۹٫۵ ← ۳۲٫۳ gzip) کرد و سقفِ ۳۱ را در pnpm verify ِ بستنِ فاز انداخت. با همان source-map-explorer، رشد
همه کدِ دامنه است و هیچ پیمانهٔ ناخواستهای نیامده: machining +۱٫۹ (چهار نوعِ اتصال R4/R5، کفتمامعرض R6)،
units +۱٫۹ (UnitModel؛ بخشی از validate/helpers و parts آمده)، parts +۱٫۵ (محفظهها R2، کنجِ ال R8)،
countertop +۱٫۴ (قطعههای کنج R10)، validate +۱٫۲ (R11–R18)، nesting +۱٫۱ (سقفِ مرحلهٔ برش و بازرسش R19).
ترکیب: validate ۲۹٫۳ + parts ۲۲٫۶ + nesting ۱۴٫۲ + machining ۷٫۴ + hardware ۵٫۶ + costing ۴٫۸ + layout ۴٫۴ +
pipeline ۳٫۵ + countertop ۳٫۰ + catalog ۲٫۶ + units ۱٫۹ + بقیه ۲٫۷. سقفِ تازه همان قاعده است: عددِ سنجیده
با ۵٪ جا. سقف فقط با جدولِ ترکیبِ تازه بالا میرود — رشدی که در جدول نمیگنجد باید برود، نه سقف.
Worker ِ ۳۹ کیلوبایت (پایانِ فاز ۲): مدلِ نمای مدرن Worker را ۱۰۲٫۰ ← ۱۱۳٫۹ کیلوبایتِ خام (۳۲٫۳ ← ۳۶٫۸
gzip) کرد و سقفِ ۳۴ را در verify ِ بستنِ فاز انداخت. باز هم همه کدِ دامنه است: validate +۵٫۶ (شش قاعدهٔ تازه —
بازشوِ فشاری، زاویهٔ لولای کنج، وزنِ جک، برخوردِ دستگیره، شیشه در قاب، لنگهٔ کشویی)، parts +۳٫۳ (پروفیلِ
دستگیرهٔ مخفی، فرزِ دستگیره، شیشه، جای دستگاه، کشویی)، nesting −۰٫۳، costing +۱٫۰ (خطوطِ شیشه)، machining +۱٫۱
(فاق و فرز)، hardware +۰٫۶، units +۰٫۲. ترکیب: validate ۳۴٫۹ + parts ۲۵٫۹ + nesting ۱۳٫۹ + machining ۸٫۵ +
hardware ۶٫۲ + costing ۵٫۸ + layout ۴٫۳ + pipeline ۳٫۴ + catalog ۳٫۱ + countertop ۲٫۹ + units ۲٫۱ + بقیه ۲٫۹.
دادهٔ کاتالوگ در Worker نیست (نخِ اصلی میفرستدش، indexCatalog) و همین سه کیلوبایت را نگه داشته.
جمعِ هر نوع رشدِ کل را میگیرد و size-limit (.size-limit.json) رشدِ یک تکه را که در جمع
پنهان میماند.
گروههای تکه (build.rolldownOptions.output.codeSplitting در apps/web/vite.config.ts): اولیه در
vendor و index، و کیتِ React Aria ِ مشترکِ دو تکهٔ تنبل یا بیشتر در kit. سرویسورکر همهٔ js را
پیشانباشت میکند، پس جمعِ gzip ِ پروندهها همان دانلودِ گوشی است و هر پرونده واژهنامهٔ فشردهسازیِ خودش را
از صفر میسازد: خودکارِ rolldown ۷۳ پرونده بود (تکهٔ اولیه ۲۰ پرونده، با پیمانههایی که خانه لازم ندارد).
موجِ ۳ ِ نسل پنجم (پایه ۸۷۱/۸۷۲): پالت فرمان از cmdk به CommandList ِ کیت (cmdk پنجرهٔ Radix و
react-remove-scroll را با خودش میآورد، ≈ ۴۷ کیلوبایتِ خام) ۸۵۸/۸۵۹، و گروهها ۸۳۹/۸۴۱؛ تکهٔ اولیه ۱۵۹ ← ۱۵۴.
js برای هر زبان (فاز ۳ ِ نسل پنجم): کاتالوگِ هر زبان تکهٔ تنبلِ خودش است (fa-IR-<hash>.js،
en-US-<hash>.js) و کاربرِ هر زبان فقط کاتالوگِ خودش را دانلود میکند؛ پس js ِ هر زبان = همهٔ js منهای
کاتالوگِ زبانهای دیگر، و سقفِ هر کدام همان ۸۵۰ است (امروز ۸۱۲ و ۸۱۱؛ جدولِ بالا). جمعِ همه هر کاتالوگ را برای کاربری میشمرد که
هرگز نمیگیردش. کنارش متنِ منبعِ پیام از بیلد برداشته میشود (apps/web/vite/messages.ts) — هر پیام
پیشتر هم در کد بود و هم در کاتالوگ. سنجیده با نقشهٔ منبع روی خطاهای هسته حدودِ یک بایت برای هر پیام
بود؛ روی قاعدهها، هزینه و برچسبِ قطعه ۲۰ تا ۲۳ بایت — شناسه هم در کد بود و هم در کاتالوگ، و متنِ
پیامی که پیشتر در کد نبود (جملهٔ کاملِ گامِ مونتاژ) تازه است. از آن پس شناسهٔ هر پیام هم در کد و کاتالوگ
به اندیسِ مبنای ۳۶ کوتاه میشود و کاتالوگ آرایهٔ بیکلید است (apps/web/vite/message-ids.ts، در Worker هم). جای آن رشد را SceneCanvas باز کرد
(scene.md): فقط کلاسهای three ِ صحنه، ۴۱ کیلوبایت کمتر برای هر زبان. پیامِ خودِ خط
فرمان (--help، خروجیِ متنی) از ۳.۶ در کاتالوگِ جدای cli.<زبان>.po است و به تکهٔ کاتالوگِ رابط
نمیرسد (i18n.md): جدا کردنِ همان ۳۲ پیامِ آن روز تکهٔ کاتالوگ را ۵۷۸ بایت
(en-US) و ۵۶۸ بایت (fa-IR) و js ِ هر زبان را ۶۰۹ و ۵۹۹ بایت کوچک کرد. pnpm size:report نقشهٔ ترکیبِ بسته را با source-map-explorer در
reports/size/ میسازد (--no-border-checks: نقشهٔ کدِ rolldown ستونِ «Infinity» دارد و
با سنجشِ مرزِ ابزار از ۵۷ تکهٔ js فقط ۳ تحلیل میشد و با این پرچم ۵۵)؛ CI آن را آرتیفکت میکند.
آزمون سرتاسری پیش از انتشار
Section titled “آزمون سرتاسری پیش از انتشار”pnpm e2e بسته را با scripts/serve-dist.mjs از /darzsaz/ بالا میآورد و در مرورگر
واقعی میسنجد: هیچ درخواست شکستخوردهای، بوم و یونیتها و نوار ابزار، افزودن یونیت
و گرفتن خروجی، دسترسپذیری با axe (نقض جدی و بحرانی صفر)، و پیشنمای رندر که دیده
شود و وسط قاب باشد. جزئیات در testing.md.
.github/workflows/ci.yml روی push ِ هر شاخه و هر pull request، و با workflow_call از
release.yml:
- کار
commits(نسل ششم، ۰.۶) شرحِ هر کامیتِ push یا pull request را با commitlint میسنجد — همان قلابِcommit-msg، ولی این یکی باLEFTHOOK=0رد نمیشود (conventions.md). فقط کامیتهایی که پس از کامیتِ قرارداد (d4ae1c4) ساخته شدهاند (scripts/commit-range.mjs، به تاریخِ کامیت): تاریخچهٔ پیش از قرارداد — نسل پنجم و کارِ بازگرداندهٔ موجِ ۶ — فارسی است و نباید این کار را قرمز کند. - کار
buildیک بار همهٔ بستهها را میسازد وdistها را آرتیفکت میکند؛ بقیهٔ کارها همان را میگیرند، نه ساختِ خودشان — فقطe2eبیلدِ شبهزبان (vite build --mode pseudo) را خودش میسازد. - کار
verifyروی Node ۲۲ و ۲۴، به همین ترتیب: typecheck (با اسکریپتها وastro check)، lint و format، knip، چرخهٔ وارد کردن، پوششِ تیپ، نگهبانها و آزمونِ خودشان (با مجوزِ وابستگیها)، آزمون با پوشش و چرخدندهٔ آن در برابرmain— همه بی هیچdist، تا تکیهٔ بیصدا به بیلد همانجا بیفتد — بعد آرتیفکتِ ساخت، نگهبانهایی که ازdistمیخوانند، بودجهٔ اندازه، بستهٔ npm و فونتِ سایت (scripts/release-check.mjs) وpnpm audit --prod. پوشش و junit ِ هر Node و گزارشِ اندازه آرتیفکت میشوند. - هر فرمانِ
pnpm verifyدر یکی از کارها اجرا میشود (آرگومانِ افزوده مثلِ--base origin/mainآزاد):scripts/test/ci-verify.test.mjsفرمانی را که درci.ymlنیست میگیرد — گامِ نگهبانهاnode scripts/i18n-glossary.mjsرا نداشت و واژهنامه فقط روی دستگاهِ توسعه میافتاد. - کار
e2eدر تصویر رسمی Playwright (نسخهاش باید با@playwright/testیکی بماند)؛ آزمون تصویری همینجا مقایسه میکند؛ junit همیشه و گزارشِ HTML در شکست آرتیفکت میشوند. - کار دستیِ
snapshots(Run workflow با گزینهٔsnapshots): عکسِ مرجعِ آزمون تصویری را در همان تصویر میسازد و آرتیفکت میکند (testing.md). - کار
storybook: Storybook ِ کیت را روی همان آرتیفکت میسازد (pnpm --filter @darzsaz/ui storybook:build)؛ story ِ شکسته همینجا میافتد. mutation.ymlهر شب جهشآزمایی را روی سه پوشهٔ هسته اجرا میکند، همزمان در ماتریس و با حافظهٔ افزایشی در cache (testing.md).
سختکاری: همهٔ گردشکارها permissions: contents: read دارند و اجازهٔ بیشتر فقط در کاری که
لازمش دارد (انتشار، Pages)؛ هر کار timeout-minutes دارد؛ هر action با SHA ِ کامیت سنجاق است
و نسخه در کامنت (برچسب جابهجاشدنی است). Dependabot (.github/dependabot.yml) هر هفته npm و
actionها و هر ماه fontTools را پیشنهاد میکند. کارهایی که ماتریسِ Node ندارند نسخه را از
.nvmrc میخوانند. گردشکارِ تغییریافته را پیش از کامیت با actionlint بسنج؛ هیچ قلابی جایش نیست.
fontTools و brotli با نسخهٔ دقیقِ scripts/requirements.txt در محیطِ مجازیِ بیرون از درخت
نصب میشوند: build-fonts.mjs --check فونتها را بایت به بایت با ساختِ تازه میسنجد و نسخهٔ
دیگر، بایتِ دیگر میسازد.
نسخهٔ pnpm از packageManager در package.json میآید؛ در workflow تکرار نمیشود
(تکرارش با نسخهٔ متفاوت، action را میانداخت).
سایتِ زنده — darzsaz.ir
Section titled “سایتِ زنده — darzsaz.ir”جایی که کاربر واقعاً برنامه را باز میکند (از ۱۴۰۵/۰۶/۲۳): https://darzsaz.ir، روی همان باکسِ تهرانِ «نایاب»، کنارِ سایتِ دیگری روی همان nginx. درزساز یک SPA ِ کاملاً استاتیک است — نه بکاند، نه دیتابیس، نه رمزی در زمانِ اجرا — پس استقرار یعنی «همین پروندهها را آنجا بگذار».
GitHub نقشی در این انتشار ندارد: نه Pages (خاموش، پایینتر)، نه Actions (صورتحساب، CI). بیلد روی همین کامپیوتر انجام میشود و سرور فقط خروجی را میگیرد (pnpm روی سرور نصب نیست).
bash <بکاپِ رمزها>/secrets-vault/projects/darzsaz/deploy.shاسکریپت و کلیدها بیرونِ مخزناند و هرگز کامیت نمیشوند. چهار متغیر جایشان را عوض میکند، پس
روی هر کامپیوترِ دیگری هم با همان بکاپ کار میکند: DARZSAZ_REPO (پیشفرض
~/AndroidStudioProjects/darzsaz)، NAYAB_KEY (کلیدِ ssh در _system/ssh/)، NAYAB_KNOWN_HOSTS
(کنارِ همان کلید) و NAYAB_HOST.
اسکریپت سه کار میکند:
pnpm --filter "@darzsaz/web..." build— برنامهٔ وب و هر بستهای که به آن میرسدrsync -az --delete --exclude='*.map'ازapps/web/dist/به ریشهٔ سایت.--deleteیعنی پروندهٔ کهنه میرود (هشِ داراییها عوض میشود و وگرنه انبار میشوند) و*.mapعمداً نمیرود: نقشهٔ سورس چند مگابایت است و سایت بی آن هم کار میکندcurlرویhttps://darzsaz.ir/با--resolve— هم HTTP 200، هم درستیِ گواهی
nginx reload نمیخواهد (فقط پروندهٔ استاتیک عوض شده). کاربرِ باز نگهداشته نسخهٔ تازه را با
همان پرسشِ «بارگذاری دوباره»ی سرویسورکر میگیرد (registerType: 'prompt'، آفلاین) —
پس یک دیپلوی هیچ کارِ نیمهتمامی را از زیرِ دستِ کسی نمیکشد.
پیش از دیپلوی
Section titled “پیش از دیپلوی”pnpm verifyسبز (دستکمpnpm -r test،pnpm typecheckوpnpm -r build) — آنچه میرود همین درختِ کاری است، نه یک برچسب و نه شاخهٔmain؛ پس کامیت و پوشِ کار پیش از دیپلوی.CHANGELOG.mdو پیشرفتِ نسل بهروز.
سرور، دامنه، گواهی
Section titled “سرور، دامنه، گواهی”| چه | کجا |
|---|---|
| ریشهٔ سایت | /var/www/darzsaz (همان چیزی که rsync پر میکند) |
| کانفیگ nginx | /etc/nginx/sites-available/darzsaz؛ رونوشتش در همان پوشهٔ بکاپ |
| SPA | try_files $uri $uri/ /index.html — هر مسیرِ درونی به index.html میرسد |
| کش | /assets/ (نامِ هشدار) یکساله و immutable؛ قلم و آیکون یکهفته؛ sw.js و *.webmanifest بیکش |
| گواهی | Let’s Encrypt برای darzsaz.ir و www؛ تمدیدِ خودکار با certbot.timer و reload ِ nginx |
| DNS | Cloudflare، رکوردِ A ِ مستقیم (grey-cloud): مسیرِ edge ↔ مبدأِ ایران گاهی ۵۲۲/۵۲۵ میدهد |
جزئیاتِ ساختِ اولیه (گامهای certbot، رکوردهای DNS، رمزها) در DEPLOY.md ِ همان پوشهٔ بکاپ است و
در مخزن نیست: مخزن هیچ توکن، کلید و نشانیِ سروری ندارد.
نمونهٔ کانفیگ nginx
Section titled “نمونهٔ کانفیگ nginx”همان شکلی که روی سرور هست، بی هیچ نشانی و رمز. <دامنه> و <ریشه> را بگذار و certbot خودش بلوکِ
۴۴۳ و گواهی را میافزاید (certbot --nginx -d <دامنه> -d www.<دامنه>):
server { listen 80; server_name <دامنه> www.<دامنه>; root <ریشه>; # چیزی که rsync پرش میکند index index.html;
# SPA: هر مسیرِ درونی به index.html میرسد، وگرنه رفرشِ روی /#/project ۴۰۴ میشد location / { try_files $uri $uri/ /index.html; }
# نامِ هشدار: یک سال و immutable. بی این، هر بازدید دوباره دانلود میکرد location /assets/ { expires 1y; add_header Cache-Control "public, immutable"; }
# سرویسورکر و مانیفست هرگز کش نمیشوند: نسخهٔ تازه باید همان بار دیده شود location = /sw.js { expires -1; add_header Cache-Control "no-cache"; } location ~* \.webmanifest$ { expires -1; add_header Cache-Control "no-cache"; }}نمونهٔ deploy.sh
Section titled “نمونهٔ deploy.sh”اسکریپتِ واقعی در بکاپِ رمزهاست؛ این کمینهٔ همان است تا اگر بکاپ نبود بشود بازش ساخت:
#!/usr/bin/env bashset -euo pipefailREPO="${DARZSAZ_REPO:-$HOME/AndroidStudioProjects/darzsaz}"KEY="${NAYAB_KEY:?کلیدِ ssh}"; HOSTS="${NAYAB_KNOWN_HOSTS:?}"; HOST="${NAYAB_HOST:?}"
pnpm --dir "$REPO" --filter "@darzsaz/web..." buildrsync -az --delete --exclude='*.map' \ -e "ssh -i $KEY -o UserKnownHostsFile=$HOSTS" \ "$REPO/apps/web/dist/" "$HOST:<ریشه>/"curl -fsS --resolve "<دامنه>:443:<نشانی>" "https://<دامنه>/" -o /dev/nullدو میزبان: سایت و برنامه (از ۱۴۰۵/۰۶/۲۶)
Section titled “دو میزبان: سایت و برنامه (از ۱۴۰۵/۰۶/۲۶)”darzsaz.ir صفحهٔ فرود و مستندات است و برنامه روی app.darzsaz.ir. جدا بودنِ مبدأ خودِ تصمیم
است: سرویسورکر، کش و IndexedDB ِ برنامه به سایت نمیرسند، و HTML ِ سایت از بودجهٔ تکهٔ اولیهٔ
برنامه خرج نمیشود.
| میزبان | ریشه | چه |
|---|---|---|
darzsaz.ir |
/var/www/darzsaz-site |
صفحهٔ فرود و مستندات (Astro، ایستا) |
app.darzsaz.ir |
/var/www/darzsaz |
خودِ برنامه (SPA و سرویسورکر) |
www.darzsaz.ir |
— | ۳۰۱ به darzsaz.ir |
گواهی یکی است و هر سه نام را دارد (certbot --expand، تمدیدِ خودکارِ همان certbot.timer).
deploy.sh ِ گاوصندوق هر دو را میسازد و دو rsync میکند.
کسی که نشانیِ قدیمی را بوکمارک کرده گم نمیشود: مسیرهای /p/… و /share از darzsaz.ir با
۳۰۱ به برنامه میروند، و نسخهای از برنامه که روی نشانیِ قدیمی باز شود یک بار میگوید «برنامه به
app.darzsaz.ir رفت — پیش از رفتن پشتیبانِ همه را بگیر» (lib/moved.ts، ۱۲.۳). این جمله تزئینی
نیست: IndexedDB به مبدأ بسته است و پروژهها با نشانی جابهجا نمیشوند، فقط با پشتیبان.
- کلیدِ میزبان از بکاپ خوانده میشود، نه از
~/.ssh/known_hostsِ این کامپیوتر (UserKnownHostsFileدر اسکریپت، ۱۴۰۵/۰۶/۲۵). روی دستگاهی که تا آن روز به سرور وصل نشده بود rsync با «Host key verification failed» میافتاد — ssh نه میتوانست بپرسد (بیssh-askpass) و نه باید کورکورانه میپذیرفت. کلیدِ ثبتشده در بکاپ با کلیدِ زندهٔ سرور سنجیده شد و یکی بود.
انتشار
Section titled “انتشار”انتشار خودکار خاموش است (۱۴۰۵/۰۶/۲۲، خواستهٔ صاحب مخزن: «فعلاً GitHub
Pages نمیخواهم»). .github/workflows/deploy.yml فقط دستی از زبانهٔ Actions
اجرا میشود: ساخت، typecheck، lint، آزمون و بودجهٔ اندازه (نه همهٔ ci.yml: بی e2e، پوشش و نگهبانها)، بعد
apps/web/dist با مستندات زیرِ docs/ روی GitHub Pages. برای روشن
کردنش در تنظیمات مخزن Pages باید روی «GitHub Actions» باشد (برای مخزن خصوصی
پلنی که Pages دارد لازم است) و — چون رابط با یکانبخ است — مجوزِ وبِ درزساز.
هیچ رمزی لازم نیست.
آفلاین
Section titled “آفلاین”سرویسورکر را vite-plugin-pwa (Workbox) در apps/web/vite.config.ts میسازد: خروجیِ ساخت
(js، css، html، svg، png، woff2، webmanifest؛ هر پرونده تا ۶ مگابایت) با hash پیشکش میشود و public/share-target-sw.js با importScripts کنارش مینشیند. registerType: 'prompt'
— نسخهٔ تازه تا کاربر «بارگذاری دوباره» را نزند فعال نمیشود (src/lib/pwa.ts، src/lib/app-update.ts).
جزئیات در storage.md.
سایت — صفحهٔ فرود و مستندات
Section titled “سایت — صفحهٔ فرود و مستندات”apps/site از فاز ۱۰ دو چیز است: صفحهٔ فرودِ ریشه (src/pages/index.astro و en/index.astro،
متنش در src/content/landing/*.yaml) و مستندات زیرِ /guide/ و /reference/. base ِ Astro دیگر
/darzsaz/docs/ نیست بلکه / است و site هم https://darzsaz.ir، چون سایت دامنهٔ خودش را دارد و
برنامه به زیردامنه میرود (بالاتر، «دو میزبان»).
عددهای صفحهٔ فرود (قطعه، ورق، قاعده) هنگامِ ساخت از خودِ کد خوانده میشوند (src/lib/numbers.ts:
derive(myKitchen(cat), cat) و RULES.length)، و عکسهایش همانهاییاند که
SHOTS=1 … --project shots از برنامهٔ ساختهشده میگیرد. قلمِ سایت با apps/site/scripts/fonts.mjs از
apps/web/public/fonts/ کپی میشود — یک منبع، و هیچ پروندهٔ قلمِ دومی در گیت.
apps/site (Astro + Starlight، فارسی راستبهچپ) با pnpm --filter @darzsaz/site build
ساخته میشود؛ پیش از ساخت، apps/site/scripts/sync-reference.mjs مرجع فنی را از docs/reference
به محتوای سایت میآورد (--check در آزمون و typecheck میسنجد که هر سند عنوان دارد و هر پیوند و لنگرش
روی سایت به جایی میرسد — اگر سندی را در docs/reference عوض کردی، همین کافی است). مرجع یک منبع
دارد و آن فارسی است (۹.۱): سایتِ انگلیسی همان صفحه را با اعلانِ «به این زبان نیست» ِ Starlight نشان
میدهد، جز rules.en.md که ساخته میشود و به en/reference/rules.md میرود. پس از
astro build، apps/site/scripts/check-links.mjs هر پیوندِ درونیِ HTML ِ ساختهشده و لنگرِ هر قاعده را در هر دو
زبان میسنجد (در pnpm -r build ِ verify)، و test/landing.test.mjs صفحهٔ فرودِ ساختهشده را:
سرآیندِ زبان، canonical، دادهٔ ساختاری، و اینکه هیچ پروندهای از مبدأِ دیگری نمیآید.
deploy.yml ِ گیتهابپیجز خروجی را زیر apps/web/dist/docs/ میگذاشت. آن مسیر با base: '/' دیگر
درست نیست و کارِ Actions هم از ۱۴۰۵/۰۶ اجرا نمیشود (صورتحساب)؛ سایت از همینجا با deploy.sh ِ
گاوصندوق منتشر میشود.
انتشار نسخه
Section titled “انتشار نسخه”برچسب vX.Y.Z روی main، release.yml را میراند:
- همان
ci.ymlِ کامل؛ تا سبز نشود چیزی منتشر نمیشود darzsaz-X.Y.Z.tgzازpnpm packدرapps/cli، وscripts/release-check.mjsروی همان tgz: برچسب با نسخهٔ بسته یکی است، هرimportِ بسته درdependenciesهست و هیچ بستهٔ ورکاسپیسی آنجا نیست (روی npm نیستند؛ درون بسته جاسازی میشوند)، و خانوادهٔ هر فونتِ درون بسته — پرونده یا base64 — وزیرمتن است- GitHub Release با بدنهٔ بخش
## [X.Y.Z]ازCHANGELOG.md(scripts/release-notes.mjs؛ اگر بخش نباشد، انتشار میافتد تا برچسبی بییادداشت بیرون نرود) - انتشار npm همان tgz با
--provenance، فقط اگر رمزNPM_TOKENدر مخزن تعریف شده باشد
زیپِ بستهٔ وب دیگر ساخته نمیشود: fonts/YekanBakh-var.woff2 را داشت و مجوزِ یکانبخ
پخشِ پرونده را نمیدهد. برنامهٔ وب روی سایت است (fonts.md). در هر CI همان نگهبان
فونتِ apps/web/dist (فقط یکانبخ و وزیرمتن) و apps/site/dist (فقط وزیرمتن) را هم از بایت
میخواند، و scripts/licenses.mjs مجوزِ هر وابستگیِ تولیدیِ بستههای پخششدنی را با فهرستِ آزاد میسنجد.
نسخه یک منبع دارد: apps/cli/package.json. npm نسخه را فقط از همان پرونده میخواند و
برچسب با همان سنجیده میشود؛ darzsaz --version آن را از define ِ tsdown میگیرد و برنامهٔ
وب (راهنما، manifest.json ِ هر .darz) از define ِ Vite. ریشه و apps/web نسخه ندارند؛
کتابخانههای درونی 0.0.0 میمانند — منتشر نمیشوند و درون بسته جاسازی میشوند.
پیش از برچسب زدن: بخش [Unreleased] را به ## [X.Y.Z] — تاریخ تبدیل کن، نسخهٔ
apps/cli/package.json را همان بگذار و pnpm install بزن — verifyDepsBeforeRun پس از تغییرِ هر
package.json تا نصبِ دوباره هیچ pnpm run ای را اجرا نمیکند.