مسئله
نگهداری رزومه معمولاً یک کار خستهکننده و تکراری است. یک فایل Word یا یک صفحه در سایتی آنلاین داریم و هر بار که چیزی عوض میشود، باید همهی مراحل را از اول تکرار کنیم: ویرایش دستی، خروجیگرفتن PDF، آپلود در چند جای مختلف و همه اینها در صورتیِ که حس و حالش رو هم داشته باشیم.
به نظرم این روش سهتا مشکل اصلی داشت:
۱. نسخههای متعدد و گمشده. بعد از چند بار ویرایش، معلوم نیست کدام فایل آخرین نسخه است.
۲. بههمریختن ظاهری. یک خط اضافه میکنید و ناگهان کل صفحهبندی جابهجا میشود.
۳. کار دستی تکراری و حوصله سربر. هر تغییر کوچک، چند دقیقه کار دستی میطلبد.
از طرفی، ما بهعنوان مهندس نرمافزار سالهاست این مسائل را در کد حل کردهایم:
نسخهبندی با Git، خودکارسازی با CI، انتشار با Release.
پس چرا رزومه را هم همینطور مدیریت نکنیم!؟
چرا Typst؟
«تایپست» یک زبان متنباز و باحال هست که با راست نوشته شده و برای ساخت انواع فایلها و سندهای متنی و تصویری است.
اگر با LaTeX کار کرده باشید، ایدهی کلی آشناست؛ بهجای اینکه ظاهر سند را با ماوس درست کنید، آن را مینویسید.
به اضافه اینکه دردسرهای «لَتِک» را به هیچ عنوان نداره و خیلی سادهتر و سرراستترعه.
اما Typst در مقایسه با LaTeX دو تا مزیت مهم هم داره:
- نحو (syntax) بسیار سادهتری دارد. برای شروع لازم نیست چیز زیادی یاد بگیرید.
- سرعت کامپایل بسیار بالاست. ساخت یک رزومهی سهصفحهای در حد چند ثانیه طول میکشد، برخلاف «لَتِک» که چندصدتا کتابخانه و وابستگی لازم داره تا یک صفحه خروجی بده.
مهمتر از همه، خروجیاش قابل پیشبینیعه. تا زمانی که فایل را تغییر ندهید، PDF خروجی دقیقاً همان چیزی میماند که بود.
ساختار پروژه
اول از همه بخشهای رزومه رو به فایلهای کوچک و مستقل تقسیم کردم:

نکتهی کلیدی، فایل utils.typ است. در آن یک تابع تعریف شده که قالب نمایش هر تجربهی کاری را مشخص میکند.
// Function to create a work experience entry #let experience( company: none, title: none, duration: none, location: none, responsibilities: (), ) = { grid( columns: (1fr, 1fr), gutter: 10pt, align(left)[ #text(size: 12pt, weight: "bold", font: "DejaVu Sans Mono")[#company] \ #text(size: 10pt, weight: "bold", font: "CodeNewRoman Nerd Font")[#title] \ #text(size: 6pt, font: "0xProto Nerd Font")[#duration] \ #text(size: 7pt, fill: gray, font: "Hurmit Nerd Font")[#location] \ ], ) text(size: 10pt)[ #for res in responsibilities [ - #res ] ] } // Formatted date #let today = datetime.today() #let month_index = datetime.today().month() #let months = ( "", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", ) #let formatted_date = [ #months.at(month_index) #today.year() ] #let page_number = context [ #set align(right) #here().page() ] #let update_context = context [ #set align(left) #text( size: 3pt, weight: "regular", [ LastUpdated: #formatted_date \ Made by #link("https://typst.app/")[Typst] ], ) ]
البته که شما میتونید بهترش کنید و اگر هم دوست داشتید مرجرکوئست (که معادلِ گیتهابش میشه پولرکوئست) بفرستید.
این یعنی جدایی داده از ظاهر. اگر روزی تصمیم بگیرم اندازهی قلم عنوان شرکتها را تغییر دهم، فقط یک خط در utils.typ عوض میشود و همهی بخشها هماهنگ بهروز میشوند.
خودکارسازی با GitHub Actions
تا اینجا هنوز باید دستی typst compile میزدم. مرحلهی بعد این بود که این کار رو هم خودکار کنم.
یک workflow توو گیتهاب نوشتم که با هر push روی برنچ اصلی اجرا میشه و این کارها را انجام میدهد:
۱. Typst را با نسخهی مشخص و ثابت نصب میکنه.
۲. فونتها رو دانلود میکنه.
۳. رزومه رو build میکنه.
۴. بررسی میکند که همهی فونتها درست پیدا شده باشند.
۵. فایل PDF را بهعنوان یک Release منتشر میکند.
نکتهای دربارهی فونتها
این بخش چندتا وارنینگ خوردم که بد نیست دربارهاش بنویسم.
روی کامپیوتر من همهی فونتها نصب بودند، پس همهچیز درست به نظر میرسید. اما کانتینر رانر CI یک محیط تمیز و خالی است و هیچ فونتی روی آن نصب نیست.
من از بیسایمیج آلپاین استفاده کردم، شما هرچی دلتون خواست استفاده کنید، محدودیتی نداره ولی رو اوبنتو زمان بیلد و خصوصا نصب فونتها خیلی بیشتر از آلپاین زمان برد.
البته Typst وقتی فونتی رو پیدا نکنه، خطا نمیده؛ فقط یک warning چاپ میکند و از یک فونت جایگزین استفاده میکند.
نتیجه این بود که یک بار PDF منتشرشده، آیکونهای بخش تماس (تلفن، ایمیل، LinkedIn) را نداشت و من متوجه نشدم، چون build سبز بود.
راهحل، اضافهکردن یک مرحلهی کوچک اما مهم به workflow بود: خروجی build را نگه دار و اگر حتی یک warning از نوع «فونت پیدا نشد» در آن بود، کل build را fail کن.
- name: Verify every requested font resolved run: | missing=$(grep -i "unknown font family" build.log || true) if [ -n "$missing" ]; then echo "::error::Fonts failed to resolve:" echo "$missing" exit 1 fi echo "All fonts resolved."
یه جورایی یک اخطار مهم رو به خطا تبدیل کردیم. (سرمون درد میکنه دیگه)
نکتهی دوم دربارهی فونتها، بحث لایسنس بود. فونت Times New Roman قابل توزیع آزاد نیست و نمیشه اون رو در رانر CI گیتهاب نصب کرد. بهجاش از TeX Gyre Termes استفاده کردم؛ یک فونت آزاد که عملاً معادل Times است.
اگر هم رزومهتون بخشهای فارسی داره یا اینکه کلا فارسی هست هم هیچ مشکلی نداره، میتونید فونتهای فارسی نصب کنید که پیشنهاد خودِ من خانواده «وزیر» هست که «صابر راستیکردار» عزیز خالقش هست که روحش در آرامش و رحمت باشه.
همچنین تایپست «راست-به-چپ» رو به خوبی پشتیبانی میکنه.
انتشار فایل پیدیاف با لینک ثابت
مهمترین بخش برای من این بود که یک لینک ثابت داشته باشم که همیشه به آخرین نسخهی رزومه اشاره کنه.
معادل این لینک:
https://github.com/<user>/<repo>/releases/latest/download/<file>.pdf
این آدرس هیچوقت تغییر نمیکنه. workflow در هر اجرا، فایل PDF جدید را روی همان Release جایگزین میکند، نمونه آدرس رزومه خودم.
# Publishes to a stable URL: # https://github.com/mhrdq8i/cv-builder/releases/latest/download/MehrdadQasemkhani-CV.pdf - name: Publish PDF to release if: github.event_name != 'pull_request' env: GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.repository }} run: | if gh release view "${RELEASE_TAG}" >/dev/null 2>&1; then gh release upload "${RELEASE_TAG}" "${PDF_NAME}" --clobber else gh release create "${RELEASE_TAG}" "${PDF_NAME}" \ --title "Latest CV" \ --notes "Automatically built from ${GITHUB_SHA}. Always the newest build." fi
مزیتش این است که هر جایی این لینک را گذاشته باشم، خودبهخود بهروز میشود.
صفحهی آنلاین با GitHub Pages
قدم آخر، صفحهی رزومهی آنلاین بود روی GitHub Pages بود که قبلا با قالبهای آماده سادهی HTML ساخته بودم، که دکمهی «دانلود رزومه» اون رو مستقیم به همون لینکِ ثابتِ Release وصل کردم.
<a href="https://github.com/mhrdq8i/cv-builder/releases/latest/download/MehrdadQasemkhani-CV.pdf"> Download Resume </a>
به این ترتیب دو مخزن، دو مسئولیت جدا دارند و هیچکدام لازم نیست از تغییرات دیگری خبر داشته باشد:
- ریپو cv-builder: سورس رزومه و ساخت PDF
- ریپو صفحهی شخصی: نمایش آنلاین؛ که یک ریپو خاص داخل گیتلب به حساب میاد که دقیقا همنام به یوزرنیم گیتهابتون هست. اگر مایل بودید بگید مقاله اون رو هم میزارم.
نتیجه
جریان کار نهایی الان این است:
ویرایش یک فایل .typ → git push → ☕ → رزومهی بهروز، همهجا
کل فرایند بعد از اینکه دیتای جدید رو وارد کردید، حدود ۴۰ ثانیه طول میکشه و هیچ دخالت دستی هم نداره. مهمتر از همه صرفهجویی در زمانِ و اینکه دیگه «نسخهی آخر» گم نمیشه؛ چون همیشه یک منبع درست وجود داره:
مخزن گیت.
اگر میخواهید شروع کنید، پیشنهاد من این است که ساده این کار رو بکنید؛
اول رزومه را با Typst بنویسید و بهصورت دستی build بگیرید. وقتی از خروجی راضی بودید، تازه سراغ خودکارسازی بروید.
همینطور اگر خواستید که رزومه آنلاین هم داشته باشید باید همون ریپوی صفحه شخصی، همون که چندخط بالاتر گفتم رو داشته باشید و داخلش گیتهاب پیجز رو فعال کرده باشید.
امیدوارم که به کارتون بیاد این مقاله و از شرِ آپدیت رزومه خلاص بشید :)
لینکها رو هم در انتهای صفحه میزارم که کمک بگیرید ازشون.
- رزومهی آنلاین
- سورس پروژه رزومه آنلاین
- سورس typst پروژه رزومهساز