feat(forge): pluggable forge abstraction + GitLab and Gitea/Forgejo/Codeberg (#1913)

* refactor(forge): forge-neutral foundation (GitHub-only)

Decouple git-hosting from GitHub behind a neutral abstraction (issue #1616), GitHub-only for now; existing GitHub behaviour is unchanged.

- Forge manifest, neutral ForgeService contract, forge registry + resolver, and a client forge-module registry.
- GitHub code renamed to the neutral shape; PR/Issue attachment wording preserved.
- forge.search.response enums parse tolerantly (unknown kind/auth state degrade instead of breaking the client).
- createPullRequest reports typed CLI/auth errors instead of a generic message.
- forge-resolver host/remote caches are LRU-bounded.
- Forge host trust is explicit: only a known cloud host or a CLI-authenticated host is ever talked to; an unauthenticated GitHub Enterprise host fails resolution instead of routing to github.com.
- Docs: forge-providers guide, glossary and i18n forge-copy conventions, architecture and rpc-namespacing terminology.
- Vitest React Native mocks (unistyles, svg, linking, lucide) consolidated into shared aliased test-stubs.

* feat(forge): GitLab adapter, forge-aware UI, pipelines and approvals

GitLab adapter over the glab CLI on the neutral contracts: MR status, forge-aware UI, pipeline tree, and N-of-M approvals.

- threadIsResolved is part of the neutral timeline item.
- Pipeline load failures show an error instead of an empty section.
- Manual pipeline jobs render as pending.
- Fork/detached MR head pipelines are fetched by MR iid (glab ci get --merge-request).

* feat(forge): Gitea family adapter (Gitea, Forgejo, Codeberg)

One adapter over the tea CLI serving Gitea, Forgejo, and Codeberg on the neutral contracts.

- CI status aggregates commit statuses and Actions runs together.
- Gitea's terminal "warning" state maps to failure on server and client.
- Gitea Actions check details are reachable from the PR pane by workflowRunId.

* refactor(forge): localize compatibility handling

* test(forge): expect normalized GitLab facts

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
This commit is contained in:
nllptrx
2026-07-17 15:03:26 +08:00
committed by GitHub
co-authored by Mohamed Boudra
parent 04d1ebdce0
commit a8ebd390fa
187 changed files with 20988 additions and 2907 deletions
+45 -10
View File
@@ -102,6 +102,7 @@ export const ar: TranslationResources = {
addImage: "أضف صورة",
addFile: "Upload file",
addIssueOrPr: "أضف مشكلة أو PR",
addIssueOrPr_mr: "أضف مشكلة أو MR",
dropImagesHere: "إسقاط الصور هنا",
dropFilesHere: "Drop files here",
editQueuedMessage: "تحرير الرسالة في قائمة الانتظار",
@@ -109,8 +110,8 @@ export const ar: TranslationResources = {
openImage: "فتح مرفق الصورة",
removeImage: "إزالة مرفق الصورة",
removeFile: "Remove file attachment",
openGithub: "افتح{{kind}}#{{number}}",
removeGithub: "إزالة{{kind}}#{{number}}",
openGithub: "افتح {{kind}} {{number}}",
removeGithub: "إزالة {{kind}} {{number}}",
element: "عنصر",
openBrowserElement: "افتح مرفق عنصر المتصفح",
removeBrowserElement: "إزالة مرفق عنصر المتصفح",
@@ -134,7 +135,9 @@ export const ar: TranslationResources = {
searching: "جارٍ البحث...",
noResults: "لم يتم العثور على نتائج.",
searchPlaceholder: "بحث القضايا والعلاقات العامة...",
searchPlaceholder_mr: "بحث القضايا و MR...",
title: "إرفاق المشكلة أو PR",
title_mr: "إرفاق المشكلة أو MR",
},
},
agentControls: {
@@ -618,6 +621,9 @@ export const ar: TranslationResources = {
label: "إنشاء PR",
pending: "إنشاء PR...",
success: "تم إنشاء PR",
label_mr: "إنشاء MR",
pending_mr: "إنشاء MR...",
success_mr: "تم إنشاء MR",
},
mergeBranch: {
label: "دمج محليا",
@@ -640,7 +646,13 @@ export const ar: TranslationResources = {
rebase: "دمج PR (rebase)",
pending: "دمج PR...",
success: "تم دمج PR",
squash_mr: "دمج MR (squash)",
merge_mr: "دمج MR (merge)",
rebase_mr: "دمج MR (rebase)",
pending_mr: "دمج MR...",
success_mr: "تم دمج MR",
},
viewPr_mr: "عرض MR",
autoMerge: {
enableSquash: "دمج تلقائي (squash)",
enableMerge: "دمج تلقائي (merge)",
@@ -651,7 +663,7 @@ export const ar: TranslationResources = {
disabled: "تم تعطيل الدمج التلقائي",
},
unavailable: {
viewPrNoGithub: "عرض PR غير متاح الآن لأن GitHub غير متصل",
viewPrNoForge: "عرض {{noun}} غير متاح الآن لأن {{brand}} غير متصل",
pullNoRemote: "السحب غير متاح هنا لأن هذا الفرع غير متصل بجهاز التحكم عن بعد بعد",
pullDirty: "السحب غير متاح أثناء وجود تغييرات محلية، لذا قم بتنفيذها أو تخزينها أولاً",
pullUpToDate: "السحب غير متاح لأن هذا الفرع محدث بالفعل",
@@ -662,8 +674,10 @@ export const ar: TranslationResources = {
"السحب والدفع غير متاح هنا لأن هذا الفرع غير متصل بجهاز التحكم عن بعد بعد",
pullAndPushDirty:
"لا يتوفر السحب والدفع أثناء وجود تغييرات محلية، لذا قم بتنفيذها أو تخزينها أولاً",
pullAndPushNoIncoming: "السحب والدفع غير متاحين لأنه لا توجد تغييرات واردة للسحب أولاً",
pullAndPushInSync: "السحب والدفع غير متاح لأن هذا الفرع متزامن بالفعل",
createPrNoGithub: "إنشاء PR غير متاح حاليًا لأن GitHub غير متصل",
pullAndPushNothingToPush: "السحب والدفع غير متاحين لأنه لا يوجد شيء جديد لإرساله بعد السحب",
createPrNoForge: "إنشاء {{noun}} غير متاح حاليًا لأن {{brand}} غير متصل",
createPrNoCommits: "إنشاء PR غير متاح لأن هذا الفرع ليس لديه أي التزامات جديدة حتى الآن",
mergeNoBase: "الدمج غير متاح لأننا لم نتمكن من تحديد الفرع الأساسي",
mergeDirty:
@@ -673,13 +687,16 @@ export const ar: TranslationResources = {
updateDirty: "التحديث غير متاح أثناء وجود تغييرات محلية، لذا قم بتنفيذها أو تخزينها أولاً",
updateCurrent: "التحديث غير متاح لأن هذا الفرع محدث بالفعل باستخدام{{baseRef}}",
mergePrNoGithub: "دمج PR غير متاح الآن لأن GitHub غير متصل",
archiveNotWorktree:
"الأرشيف غير متاح هنا لأنه لم يتم إنشاء مساحة العمل هذه كشجرة عمل Paseo",
mergePrNoForge: "دمج {{noun}} غير متاح الآن لأن {{brand}} غير متصل",
mergePrMissing: "دمج PR غير متاح لأنه لا يوجد طلب سحب حتى الآن",
mergePrDraft: "دمج PR غير متاح لأن طلب السحب لا يزال مسودة",
mergePrMerged: "دمج PR غير متاح لأن طلب السحب مدمج بالفعل",
mergePrClosed: "دمج PR غير متاح لأن طلب السحب مغلق",
mergePrConflicts: "دمج PR غير متاح لأن طلب السحب به تعارضات",
mergePrQueue: "دمج PR غير متاح هنا لأن هذا المستودع يستخدم قائمة انتظار دمج",
mergePrNotReady: "دمج PR غير متاح حتى يبلغ GitHub أن طلب السحب جاهز للدمج",
mergePrNotReady: "دمج {{noun}} غير متاح حتى يبلغ {{brand}} أن {{noun}} جاهز للدمج",
autoMergeCannotDisable: "تم تمكين الدمج التلقائي، ولكن لا يمكن لهذا الحساب تعطيله",
},
toasts: {
@@ -729,7 +746,7 @@ export const ar: TranslationResources = {
expandAllFolders: "توسيع كافة المجلدات",
refreshing: "منعش",
refresh: "ينعش",
refreshState: "تحديث بوابة وحالة GitHub",
refreshState: "تحديث حالة git و{{brand}}",
failedRefresh: "فشل تحديث حالة git.",
emptyHiddenWhitespace: "لا توجد تغييرات مرئية بعد إخفاء المسافة البيضاء",
emptyUncommitted: "لا توجد تغييرات غير ملتزم بها",
@@ -765,13 +782,23 @@ export const ar: TranslationResources = {
pr: {
actions: {
viewPullRequest: "عرض",
openOn: "فتح على {{brand}}",
},
sections: {
checks: "الشيكات",
pipeline: "خط المعالجة",
reviews: "التعليقات",
},
empty: {
noJobs: "لا توجد مهام",
loadingPipeline: "جارٍ تحميل خط المعالجة...",
pipelineJobsLoadFailed: "تعذر تحميل مهام خط المعالجة",
allowedToFail: "مسموح بالفشل",
},
approvals: "{{given}} من {{required}} موافقات",
accessibility: {
pullRequest: "سحب الطلب #{{number}}",
pullRequest_mr: "طلب دمج !{{number}}",
},
states: {
draft: "مسودة",
@@ -788,11 +815,19 @@ export const ar: TranslationResources = {
time: {
justNow: "الآن",
},
thread: {
discussion: "سلسلة المناقشة",
},
errors: {
statusLoadFailed: "غير قادر على تحميل حالة طلب السحب",
activityLoadFailed: "غير قادر على تحميل نشاط طلب السحب",
},
},
forgeSetup: {
installCli: "ثبّت واجهة سطر الأوامر {{cli}} لاستخدام ميزات {{brand}}.",
signIn: "نفّذ {{command}} لاستخدام ميزات {{brand}}.",
generic: "قم بإعداد {{brand}} على هذا المضيف لاستخدام ميزاته.",
},
},
},
sidebar: {
@@ -924,10 +959,10 @@ export const ar: TranslationResources = {
refPicker: {
startingRef: "بدء المرجع",
chooseStart: "اختر من أين تبدأ",
checkoutHint: "تحقق من PR#{{number}}؟",
checkoutPr: "تحقق من PR#{{number}}",
dismissCheckoutHint: "تجاهل تلميح الخروج PR#{{number}}",
intoBase: "إلى{{baseRef}}",
checkoutHint: "تحقق من {{noun}} {{numberPrefix}}{{number}}؟",
checkoutPr: "تحقق من {{noun}} {{numberPrefix}}{{number}}",
dismissCheckoutHint: "تجاهل تلميح الخروج {{noun}} {{numberPrefix}}{{number}}",
intoBase: "إلى {{baseRef}}",
searching: "جارٍ البحث...",
noMatchingRefs: "لا توجد مراجع مطابقة.",
searchPlaceholder: "بحث الفروع والعلاقات العامة",