---
url: https://nsh.dev/ar/docs/compatibility
description: "كيف تحافظ Native Shell على عمل التطبيقات عبر الإصدارات: ما الذي يعد به الإصدار المتوافق، والسجل في nsh-compat الذي يحتفظ بكل تغيير يطرأ عليه، وآلية الإصدار، وكيفية إضافة واجهة API أو إعادة تسميتها أو تغييرها."
---

# التوافق مع الإصدارات السابقة

تنزّل التطبيقات المضيفة تطبيقات NSH على هيئة [حزم موقّعة](https://nsh.dev/translations/ar/docs/signed-packages.md) وتشغّلها كإضافات. وقد لا يُعاد بناء تطبيق سبق إطلاقه أبدًا: فقد انتقل فريقه إلى عمل آخر، ومصدره مدفون داخل عملية تجارية، والتطبيق المضيف يحدّث NSH وفق جدوله الخاص. لذلك يقدّم Native Shell وعدًا لكل تطبيق: **التطبيق المبني لإصدار متوافق يعمل على كل إصدار لاحق دون أي تعديل.** وأي تطبيق يتوقف عن العمل بعد تحديث NSH فهو خطأ في NSH.

تشرح هذه الصفحة الوعد، وكيف يُفرَض، وكيف يمكن تغيير NSH ضمن حدوده.

<a id="what-an-app-can-rely-on"></a>

## ما الذي يمكن للتطبيق الاعتماد عليه

يشمل الوعد كل ما يلمسه التطبيق المبني:

| الجزء | لماذا يهم |
| --- | --- |
| صادرات كل وحدة (`nsh` و`nsh/*` و`nsh/base` والوحدات القياسية) وأعضاؤها | تستوردها الحزمة بالاسم: إذا فُقد اسم واحد لا يبدأ التطبيق. |
| `nsh:rt`، أي الدوال التي يستدعيها JSX المُصرَّف | تحمل كل حزمة مخرجات المصرِّف الذي بناها. |
| العناصر العامة (المؤقّتات و`fetch` و`console` ...) وأعضاؤها | تستدعيها التطبيقات بالاسم. |
| خصائص العناصر وأحداثها وحمولاتها، ومفاتيح الأنماط وقيمها | تمرّرها التطبيقات بالاسم؛ والخاصية المُعاد تسميتها تُتجاهل فيعمل التطبيق بشكل خاطئ. |
| أدوات Tailwind ومتغيّراتها | تُحَلّ الأصناف وقت التشغيل من السلاسل الموجودة في الحزمة. |
| السلوك: القيم الافتراضية والوحدات والحمولات وترتيب الاستدعاءات الراجعة | يعتمد التطبيق على ما رآه وقت كتابته. |
| تعريفات TypeScript (`.nsh/types`) | يبقى مصدر التطبيق القديم يجتاز فحص الأنواع عندما يصلح فريقه خطأً ما. |
| صيغة الحزمة وتواقيعها | تبقى الحزمة الموقّعة لإصدار سابق قابلة للتحقق. |

القواعد التي تحفظ هذا الوعد:

1. **ما أُصدر في إصدار يبقى.** لا تُحذف أي وحدة أو صادرة أو عضو أو خاصية أو حدث أو مفتاح حمولة أو مفتاح نمط أو قيمة، ولا أداة Tailwind ولا عنصر عام ولا دالة `nsh:rt`.
2. **إعادة التسمية تُبقي الاسم القديم** كاسم مستعار لنفس التنفيذ، موسومًا بـ`@deprecated` مع الاسم الجديد، وإلى الأبد. ونقل صادرة إلى وحدة أخرى يُبقيها في الوحدة القديمة أيضًا.
3. **الأنواع تتسع فقط.** يكتسب الاتحاد بدائل جديدة، وتكتسب الدالة حالات تحميل زائد أو معاملات اختيارية لاحقة، ويصبح المعامل اختياريًا؛ وما تمرّره التطبيقات (الخصائص والأنماط والخيارات) لا يكتسب إلا أعضاء اختيارية. والقيم التي تقرؤها التطبيقات تنمو بالطريقة نفسها: يكتسب `platform.os` منصة جديدة، وتكتسب الحالة أو السبب حالة جديدة. وتحتفظ الحالة التي رآها التطبيق بقيمتها، ويتعامل التطبيق مع القيمة التي لا يعرفها عبر `default` الخاص بـ`switch`:

   ```tsx
   switch (platform.os) {
     case "ios":
     case "android":
       return <MobileLayout />;
     default:
       // The desktops, the web, and platforms NSH adds later.
       return <WideLayout />;
   }
   ```
4. **السلوك محفوظ.** السلوك الجديد اختياري، ويُفعَّل عبر خاصية أو خيار أو قيمة جديدة.
5. **`nsh:rt` تنمو فقط**، وتحتفظ كل دالة بتوقيعها ومعناها.
6. **تُقرأ الصيغ إلى الأبد.** الحزم والشهادات وقوائم الإلغاء و`app.json` الخاصة بالإصدارات السابقة تقرؤها الإصدارات اللاحقة؛ وتحدد الحزمة أدنى وقت تشغيل تحتاجه (`nsh publish --min-runtime`).
7. **الأمان هو الاستثناء الوحيد.** القدرة التي لا يمكن إبقاؤها آمنة تُغلق، وتُدرج مع سببها وتُذكر في ملاحظات الإصدار.

يبدأ الوعد من أول إصدار مسجَّل بوصفه متوافقًا ويسري إلى الأبد، أيًا كان رقم الإصدار. وبين الإصدارات، قد يتغير أي API لا يوجد إلا في `main`.

<a id="how-it-is-enforced"></a>

## كيف يُفرَض

ما جمّدته الإصدارات يعيش في مستودع مستقل،
[longbridge/nsh-compat](https://github.com/longbridge/nsh-compat)، منفصل عن شيفرة NSH. وكل تغيير في NSH يُختبر
مقابله ولا يستطيع تعديله: فطلب الدمج الذي يعيد تسمية API لا يستطيع أيضًا إعادة كتابة سجل ذلك API.

```
nsh                                       nsh-compat
├─ crates/          the code              ├─ releases/v<version>/   one per compatible release, never changed
└─ tests/compat/    apps that test the    │  ├─ runtime.txt         every export, global and nsh:rt function
   ├─ cases/        API being written     │  ├─ types/              the declarations apps build against
   └─ examples/                           │  ├─ tailwind.txt        every Tailwind class and what it sets
                                          │  └─ apps/               the test apps as that release built them
                                          ├─ closed.txt             security closures, each with its reason
                                          └─ src/                   the checks
```

يشغّل كل طلب دمج إلى NSH فحوص `nsh-compat` على نسخته (سير عمل **Compat**، أو
`cargo xtask compat` محليًا):

| الفحص | ما الذي ينجح |
| --- | --- |
| `checkout_apps_run` | التطبيقات في `tests/compat/`، المبنية بهذه النسخة، تفعل ما تقوله نصوصها، وكل جزء من الـAPI يدّعي نص أنه يفحصه يُفحص فعلًا. |
| `nothing_released_is_removed` | كل ما قدّمه كل إصدار مسجَّل (الصادرات والأعضاء وأنواعها والتعريفات وأصناف Tailwind وما تضبطه) ما زال مقدَّمًا، كما هو أو أوسع. |
| `released_apps_run` | التطبيقات التي بناها كل إصدار مسجَّل، كما بناها مصرِّفه، تبدأ على وقت التشغيل هذا وتفعل ما تقوله نصوصها. |

يختبر الفحص الأول الـAPI الجاري كتابته؛ أما الفحصان الآخران فيحميان كل إصدار سُجّل. وإعادة تسمية API
مُصدَر تفشل هكذا:

```
v0.2.0: runtime [nsh] Platform: removed
Keep it: a rename keeps the old name as a @deprecated alias of the new one, a type only widens.
```

`nsh-compat` يراجعه مشرفو NSH، ويشغّله CI الخاص به مقابل `main` من NSH.

<a id="the-release-flow"></a>

## مسار الإصدار

1. **التطوير.** يصل API إلى `main` مع حالة في `tests/compat/` يفحصه نصها. وإلى أن يسجّله إصدار، يمكن إعادة تسميته أو تغيير شكله؛ وتتغير الحالة معه.
2. **التسجيل.** عند الإصدار، يكتب `cargo xtask compat record <version>` العنصر `releases/v<version>/` في نسخة من
   `nsh-compat`: السطح الذي يقدمه الإصدار وكل تطبيق في `tests/compat/` مبني بمصرِّفه، مع
   نصوصها. ولا يسجّل إلا API يفحص نصٌّ كل جزء منه.
3. **التجميد.** يُدمج السجل عبر طلب دمج في `nsh-compat`. ومن ذلك الحين يخضع كل تغيير في NSH له،
   مع كل سجل سابق.

<a id="developing-within-the-promise"></a>

## التطوير ضمن الوعد

<a id="adding-an-api"></a>

### إضافة API

كل صادرة أو عنصر عام أو خاصية أو حدث أو مفتاح نمط جديد يأتي مع حالة:

1. اكتب وحدة `tests/compat/cases/<domain>/<case>.tsx` تكون صادرتها الافتراضية مكوّنًا يستخدم الـAPI
   ويعرض نتيجته. المجالات هي `base` و`core` و`events` و`layout` و`paint` و`props` و`modules` و`web`، إضافة إلى
   `android` و`macos` و`browser` لما تحدده منصة واحدة أو إعداد `nsh.json` واحد.
2. أضفها إلى `App.tsx` الخاص بالمجال، الذي يعرض حالة واحدة في كل مرة.
3. أضف الخطوات إلى `script.txt` الخاص بالمجال: `tap id:case-<case>`، ثم الخطوات التي تفحص ما يفعله الـAPI،
   وخطوة `covers <item>` لكل جزء يضيفه.
4. شغّل `cargo xtask compat` (`NSH_COMPAT_APP=<domain>` يشغّل مجالًا واحدًا).

```txt
# props-media-video
tap id:case-props-media-video
prop id:clip src https://example.com/clip.mp4
prop id:clip paused true
tap id:play
prop id:clip paused false
covers [props] src
covers [props] paused
```

خطوة `covers` يجري التحقق منها: يجب أن يستخدم التطبيق العنصر، وفي حالة الخاصية أو الحدث أو مفتاح النمط، يجب أن يؤدي حذفه من
التطبيق المبني إلى فشل خطوة في النص. تُسمّى العناصر `[<module>] <export>` و`[globals] <name>` و
`[props] <name>` و`[events] <name>` و`[styles] <name>`، وعناصر المكوّن الخاصة به `[props] <Component>.<name>`، وخصائص
المنصة `ios.<name>` و`android.<name>` و`desktop.<name>` و`web.<name>`.

<a id="renaming-or-moving-an-api"></a>

### إعادة تسمية API أو نقله

أضف الاسم الجديد وأبقِ القديم كاسم مستعار لنفس التنفيذ، معلنًا بـ`@deprecated` مع الاسم الجديد. حدّث الحالة ونصها إلى الاسم الجديد. أما API لم يسجّله أي إصدار بعد فيُعاد تسميته ببساطة.

<a id="changing-a-type"></a>

### تغيير نوع

وسّعه: مزيد من البدائل في الاتحاد، ومزيد من حالات التحميل الزائد، ومعاملات اختيارية لاحقة، وأعضاء جدد اختيارية فيما
تمرّره التطبيقات. والاتحاد الذي تقرؤه التطبيقات يكتسب أعضاء بالطريقة نفسها، كل عضو جديد لحالة جديدة. أسماء المعاملات حرة التغيير؛ أما عددها واختياريتها وأنواعها فتبقى.

<a id="changing-behavior"></a>

### تغيير السلوك

أبقِ السلوك القديم وأضف الجديد خلف خاصية أو خيار أو قيمة جديدة. وإصلاح الخطأ الذي يغيّر ما يراه تطبيق يعمل هو تغيير في السلوك أيضًا: ضعه خلف خيار اختياري، أو تأكد من أن لا تطبيق يمكنه الاعتماد على النتيجة القديمة. وأعطِ
السلوك خطوة في حالة، ليبقى كما هو.

<a id="changing-the-compiler"></a>

### تغيير المصرِّف

لا شيء إضافي: تبقى تطبيقات كل إصدار كما بناها مصرِّف ذلك الإصدار، وتُبنى الحالات من جديد
بمصرِّفك. والدوال التي يصدرها في الحزم (`nsh:rt`) تنمو فقط.

<a id="closing-an-api-for-security"></a>

### إغلاق API لأسباب أمنية

يضيف طلب دمج في `nsh-compat` الإدخال إلى `closed.txt` مع السبب (`<entry> -- <reason>`، الإدخال
كما يبلّغ عنه الفحص)، ويُدمج أولًا؛ ثم يجتاز تغيير NSH الذي يزيل الـAPI الفحص. وتذكر ملاحظات الإصدار
السبب.

<a id="scripts"></a>

## النصوص

`script.txt` هو خطوة واحدة في كل سطر. الخطوات التي تنفّذ فعلًا: `tap <selector>` و`type <selector> <text>` و
`key <selector> <key>` و`scroll <selector> <x> <y>` و`touch start|move|end|cancel` و`event <selector> <onEvent>
[<key>=<value>...]`, `wait <ms>`, `keyboard <height> <duration>`, `pixelratio <ratio>`. الخطوات التي تفحص:
`expect <text>` و`absent <text>` و`frame <selector> <x> <y> <width> <height>` و`origin` و`prop` / `noprop` و
`style` / `nostyle` و`listens` و`state` و`statusbar` و`log <text>`. يمكن للأسطر الأولى ضبط الشاشة: `viewport <w> <h>` و
`platform <os> [desktop]` و`fontscale <scale>`. المحدِّد هو `id:<testID>` أو `label:<label>` أو `text:<text>` أو
`span:<text>`؛ وتُكتب الخصائص والأنماط كما تكتبها التطبيقات (`style id:card backgroundColor #3366ffff`).
`NSH_COMPAT_DUMP=1` يطبع ما تضيفه كل خطوة إلى الشاشة وما تزيله منها، لمساعدتك على كتابة النص.

<a id="commands"></a>

## الأوامر

```sh
cargo xtask compat                      # the checks against this checkout
NSH_COMPAT_APP=props cargo xtask compat -v
cargo xtask compat record 0.2.0         # at a release: record it in nsh-compat, then open a pull request there
```

`cargo xtask compat` يحتفظ بنسخة من `nsh-compat` في `target/nsh-compat/` ويحدّثها في كل تشغيل؛
والنسخة التي فيها تغييرات خاصة بها تُستخدم كما هي، فيمكن تجربة تغيير في الفحوص مقابل NSH قبل طلب
الدمج الخاص به.
