Extension API: версия и совместимость
Отдельно от версии пакета расширения (manifest.version, semver для каталога и автообновления) и от engines.rynt (совместимость с версией лаунчера).
Два разных «версии»
| Поле | Формат | Назначение |
|---|---|---|
manifest.version | semver (0.0.1, 1.2.0) | Релиз пакета, каталог, автообновление с маркетплейса |
manifest.extensionApi | целое число как строка ("1") | Контракт API платформы (реестры, setup, expose, host APIs) |
engines.rynt | semver range | Минимальные возможности лаунчера (NW, пути, модели) |
Путать extensionApi с semver пакета или с ^1.0.0 не нужно — в v1 везде "1".
Политика Extension API
- Сейчас одна версия API:
1. В SDK:EXTENSION_API_VERSION = 1(число) или экспорт строки"1"— в манифесте только"1". - Обратная совместимость обязательна: новые версии API (когда появится
2) только добавляют возможности; старые расширения сextensionApi: "1"продолжают работать на хосте с API2. - Изменений ломающих контракт не планируется — эволюция через новые реестры, опциональные поля манифеста, новые методы host API.
- Лаунчер автообновляемый — пользователь получает новый хост; расширения с диска/каталога тоже должны мягко подтягивать новые релизы пакета (см. ниже).
Манифест
{
"extensionApi": "1"
}Рекомендуется указывать явно. Если поле опущено в тестовых пакетах — хост трактует как "1" с предупреждением в dev.
Не использовать в v1:
"extensionApi": "^1.0.0"При валидации манифеста (сборка / discovery / parseRyntManifest) — нормализация: если значение похоже на semver range, в dev warning и приведение к major 1 (или ошибка только в strict-режиме CI).
Проверка в лаунчере (мягкая)
При старте лаунчер вызывает checkExtensionApi (major API, не semver):
required = parseExtensionApiMajor(manifest.extensionApi) // 1
host = EXTENSION_API_VERSION // 1
if required === host:
OK
if required < host:
OK // старое расширение на новом лаунчере — ожидаемый случай
if required > host:
SOFT: предупреждение + запись в errors[id] с понятным текстом,
по умолчанию всё равно попытка load (флаг strictExtensions=false)| Ситуация | Поведение по умолчанию |
|---|---|
extensionApi: "1", host 1 | загрузка |
| поле отсутствует | загрузка, warn «считаем API 1» |
extensionApi: "2", host 1 | warn + в UI «нужен более новый лаунчер»; не падать всем графом |
legacy ^1.0.0 | warn + трактовать как 1 |
Жёсткий отказ (strictExtensionApi: true в dev/CI) — опционально, не для продакшн-пользователя.
Нужна ли проверка вообще?
Да, но мягкая: единственный случай, когда она полезна — required > host (расширение собрано под будущий лаунчер). Всё остальное совместимо по политике. Semver-диапазоны для extensionApi не дают этой ясности — от них отказываемся.
Сервис маркетплейса
При публикации:
extensionApiобязателен, значение —"1"(или целое ≤MAX_PUBLISHED_EXTENSION_API).- Расширение с
extensionApi: "2"приMAX = 1→VALIDATION_ERROR(каталог не принимает пакет для несуществующего API). - Это жёстче, чем лаунчер: в каталог попадает только то, что осмысленно раздавать.
Автообновление расширений (лаунчер)
Отдельно от проверки API. Используется manifest.version (semver пакета), не extensionApi.
Правила (мягкие)
| Правило | Описание |
|---|---|
| Источник | каталог (is_latest, downloadUrl) — 05-marketplace-service.md |
| Сравнение | semver: semver.gt(remote, local) |
| Когда | при старте лаунчера |
| Отключение | settings.extensionsAutoUpdate === false (переключатель в настройках расширений) |
| Сбой сети | не блокировать запуск; использовать локальные копии |
extensionApi | при автообновлении не меняется логика: новый zip того же id должен оставаться на "1" до выхода API 2 |
| Зависимости | после обновления пакета A перезапуск резолва графа; если сломались deps — warn в настройках |
UI
- Настройки → Расширения: переключатель «Автообновление с маркетплейса».
- Управление расширениями: блок «Доступны обновления» + «Обновить» / «Обновить все».
- После автообновления при старте: Callout в настройках расширений (
extensionAutoUpdateNotice).
Не в v1 (можно позже)
- Дельта-патчи; подпись артефактов; откат версии; периодический фоновый poll.
Реализация (чеклист)
- [x]
EXTENSION_API_VERSION = 1в SDK - [x]
parseExtensionApiMajor()+normalizeExtensionApiInManifest()+ мягкая проверка при загрузке - [x] Тестовые
vite.config.ts:extensionApi: '1' - [x] Документировать в 02-manifest-contract.md
- [x]
extension-marketplace-service: валидацияextensionApiпри publish - [x] Клиент:
listExtensionUpdatesAvailable/applyExtensionUpdates/runExtensionAutoUpdateOnStartup - [x] UI: переключатель автообновления, список pending updates, ручное обновление
- [x]
extension-build: нормализацияextensionApiпри emitdist/manifest.json
Когда появится API 2
- Поднять
EXTENSION_API_VERSIONдо2в SDK. - Хост принимает
extensionApi: "1"и"2". - В каталоге разрешить публикацию с
"2"для пакетов, которым нужны новые возможности. - Расширения на
"1"не трогать, пока не понадобится явно новый контракт.
Это согласовано с автообновлением лаунчера: пользователь обновил лаунчер → старые .ryntextension с "1" работают; обновили расширение с каталога → получили новый функционал того же API 1 через новый version пакета.