Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
2489b8b
Ошибки связи: адрес сервера и причина вместо «Нет соединения»
Segate-ekb Oct 16, 2026
cf0028c
Ошибки сети: имена локальных переменных без тени функции Суть
Segate-ekb Oct 16, 2026
5f937f3
Ошибки связи по коду ответа: 404 — файл не найден, 401 и 403 — отказ …
Segate-ekb Oct 16, 2026
5cb5e84
Спецификация после @: точная версия, тег или выражение версии уходят …
Segate-ekb Oct 16, 2026
4fa8404
Параметр engine: к запросу файла пакета добавляется версия установлен…
Segate-ekb Oct 16, 2026
36e96df
Тест параметра engine: версия движка в ожидаемом адресе закодирована,…
Segate-ekb Oct 16, 2026
0a26c7a
Контрольная сумма скачанного пакета: файл сверяется с sha256 из загол…
Segate-ekb Oct 16, 2026
f28e707
Адрес хаба в команде: opm install хост[:порт]/[пул/]пакет[@спецификац…
Segate-ekb Oct 16, 2026
d0aa2a5
Признак адреса хаба: условие хоста вынесено в переменную
Segate-ekb Oct 16, 2026
d59fd68
Хаб и пул опциями: --hub принимает только [схема://]хост[:порт], путь…
Segate-ekb Oct 16, 2026
9202b91
Пул без хаба: --pool без --hub публикует и ставит из пула хаба по умо…
Segate-ekb Oct 16, 2026
494d09b
Экспорт ради тестов снят: сервер публикации — СерверыПакетов.ДляПубли…
Segate-ekb Oct 16, 2026
11f9987
push --force: ресурс публикации получает параметр force=1 (СерверыПак…
Segate-ekb Oct 16, 2026
c01df0a
push --oidc: публикация id-token конвейера вместо токена — в GitHub A…
Segate-ekb Oct 16, 2026
d5fff26
push: опции --oidc, --oidc-audience, --oidc-token-var переименованы в…
Segate-ekb Oct 16, 2026
9f57e40
push --trusted: опция --trusted-token-var снята — id-token GitLab CI …
Segate-ekb Oct 16, 2026
56442a4
install --all-pools: версию выбирает хаб по всем доступным пулам — се…
Segate-ekb Oct 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,120 @@ OneScript Package Manager

Справка из википедии: [Система управления пакетами](https://ru.wikipedia.org/wiki/%D0%A1%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%B0_%D1%83%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F_%D0%BF%D0%B0%D0%BA%D0%B5%D1%82%D0%B0%D0%BC%D0%B8)

# Адрес хаба прямо в команде

Установка принимает адрес пакета в хабе вместо имени либо хаб опцией `--hub` и пул опцией `--pool`
(переменные окружения `OPM_HUB` и `OPM_POOL`), публикация — только опциями; без `--hub` — хаб по умолчанию,
без `--pool` — пул хаба по умолчанию; запись в `opm.cfg` не нужна:

```sh
opm install localhost:3380/corp/my-package@stable # хост, порт, пул, пакет, тег
opm install hub.example.com/asserts # пакет в пуле хаба по умолчанию
opm install https://hub.example.com/public/asserts # схема указана явно
opm install --hub localhost:3380 --pool corp my-package@stable
opm install --hub localhost:3380 --all-pools my-package@stable # версию выбирает хаб по всем доступным пулам
opm push --hub localhost:3380 --pool corp my-package-1.0.0.ospx
opm push --hub https://hub.example.com --token <токен> my-package-1.0.0.ospx
```

Адрес пакета: `[схема://]хост[:порт]/[пул/]пакет[@спецификация]`; в `--hub` — `[схема://]хост[:порт]` без пути.

* В адресе пакета последний сегмент пути — пакет, сегмент перед ним — пул; без пула — пул хаба по умолчанию.
Зависимости пакета ставятся с того же сервера, у `install --hub` — с хаба из опций.
* `install --all-pools` просит хаб выбрать версию по всем пулам, открытым токену (без токена — по публичным):
адреса `/api/v1/download/<пакет>/<файл>` с теми же `spec` и `engine`; с `--pool` не сочетается.
* Отказ: путь в `--hub`, адрес пакета вместе с `--hub`, `install --hub` вместе с `-a`, `-f`, `-u`, `-m`.
* Строка считается адресом, если в ней есть схема либо путь после хоста с портом, с точкой в имени
или `localhost`. Остальное — имя пакета.
* Без схемы берётся `http` для `localhost`, `127.*` и адреса с явным портом, иначе `https`.
* Авторизация берётся у сервера из `opm.cfg` с тем же адресом и портом.
* `opm push --hub` отправляет токен из опции `--token` (переменная окружения `GITHUB_OAUTH_TOKEN`)
или из настройки `ТокенПубликацииПакетов`. Канал при публикации в такой хаб необязателен.
* Опция `-m|--mirror` у `push` устарела и печатает предупреждение: вместо неё — `--hub`.
* `opm push --force` просит хаб опубликовать поверх уже занятого номера версии (параметр `force=1`
в запросе публикации); перезапишет ли он версию, решает хаб.

# Версия пакета после @

`install`, `update` и `download` принимают после `@` точную версию, имя тега или выражение версии:

```sh
opm install vrunner@1.0.0 # точная версия
opm install vrunner@stable # версия под тегом
opm install vrunner@^1.4 # старшая версия, подходящая под выражение
opm install vrunner # версию выбирает хаб
```

Спецификация уходит хабу параметром `?spec=`, имя файла в адресе прежнее — `<пакет>-<спецификация>.ospx`.
Тег и выражение версии разрешает только хаб, принимающий `spec`; хаб без него отвечает 404, и opm так и говорит.
Точная версия ставится с любым хабом. Файловый сервер (`file://`) отдаёт только файл названной версии
и не подставляет вместо неё старшую.

К запросу файла пакета opm добавляет параметр `engine` — версию установленного OneScript. Хаб, который его понимает,
выбирает версию под этот движок там, где версию выбирает он (без `@`, тег, выражение); точная версия отдаётся как есть.
Если хаб отказал и объяснил причину в ответе, opm показывает её.

# Ошибки связи

Если хаб ответил, но файла не отдал, причина называется по коду ответа: 404 — файл не найден,
401 и 403 — отказ в доступе, иначе — код ответа и имя файла.

```console
$ opm install my-package
Ошибка установки пакета my-package: файл my-package.ospx в хабе не найден
```

Если ответа нет, opm называет адрес сервера: `Не удалось подключиться к http://hub.example.com:3380`.
Исходное описание ошибки уходит в отладочный журнал `oscript.app.opm`. При переборе нескольких
серверов из `opm.cfg` недоступный сервер остаётся предупреждением, и поиск идёт дальше.

# Проверка контрольной суммы

Хаб присылает sha256 файла пакета в заголовке `X-Checksum-Sha256`. opm сверяет с ним скачанный `.ospx`
и при расхождении отказывает в установке, не записывая файл. Ответ без заголовка (хаб без его поддержки,
файловый сервер) не сверяется.

# Доверенная публикация из CI

Конвейер GitHub Actions или GitLab CI публикует пакет без токена: `opm push --trusted` берёт у конвейера
OIDC id-token и отправляет его хабу вместо токена, а хаб сверяет его с записью доверия пакета.
Запись доверия (провайдер, репозиторий, при необходимости конвейер и ref) заводится на странице пакета в хабе.

GitHub Actions выдаёт id-token конвейеру с разрешением `id-token: write`:

```yaml
permissions:
contents: read
id-token: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: otymko/setup-onescript@v1.5.1
- run: opm install -l && opm build .
- run: opm push --hub https://hub.example.com/corp --trusted my-package-1.0.0.ospx
```

GitLab CI кладёт id-token в переменную блока `id_tokens`; opm читает её под именем `OPENHUB_TOKEN`:

```yaml
publish:
id_tokens:
OPENHUB_TOKEN:
aud: https://hub.example.com
script:
- opm install -l && opm build .
- opm push --hub https://hub.example.com/corp --trusted my-package-1.0.0.ospx
```

* В GitHub Actions id-token запрашивается с аудиторией, равной адресу хаба (`https://hub.example.com`);
другую аудиторию задаёт `--trusted-audience`. В GitLab CI аудиторию задаёт `aud` в блоке `id_tokens`.
* С `--trusted` токен из `--token` и настройки `ТокенПубликацииПакетов` не отправляется.
* Конвейер не выдал id-token — opm отказывает и называет, чего не хватает: `permissions: id-token: write`
в GitHub Actions, переменной `OPENHUB_TOKEN` в блоке `id_tokens` в GitLab CI.
* Подпись id-token и запись доверия проверяет хаб; отказ хаба opm показывает его сообщением.

# Сервера
В настоящее время пакеты хранятся на двух серверах:
- [hub.oscript.io](http://hub.oscript.io/download) - основной хаб пакетов
Expand Down
32 changes: 32 additions & 0 deletions features/install-hub.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# language: ru

Функциональность: Установка с хаба из опций

Как разработчик
Я хочу ставить пакеты с хаба, названного опцией --hub
Чтобы не путать адрес хаба в опции с адресом пакета в аргументе

Контекст: Тестовый контекст
Допустим Я очищаю параметры команды "opm" в контексте
И Я устанавливаю путь выполнения команды "opm" к текущей библиотеке

Сценарий: Адрес пакета вместе с опцией --hub
Допустим Я добавляю параметр "install" для команды "opm"
И Я добавляю параметр "--hub" для команды "opm"
И Я добавляю параметр "hub.example" для команды "opm"
И Я добавляю параметр "hub.example/corp/pkg" для команды "opm"
Когда Я выполняю команду "opm"
Тогда Вывод команды "opm" содержит "задан адресом вместе с опцией <--hub>: укажите одно из двух"
И Код возврата команды "opm" равен 1

Сценарий: Все пулы вместе с одним пулом
Допустим Я добавляю параметр "install" для команды "opm"
И Я добавляю параметр "--hub" для команды "opm"
И Я добавляю параметр "hub.example" для команды "opm"
И Я добавляю параметр "--pool" для команды "opm"
И Я добавляю параметр "corp" для команды "opm"
И Я добавляю параметр "--all-pools" для команды "opm"
И Я добавляю параметр "pkg" для команды "opm"
Когда Я выполняю команду "opm"
Тогда Вывод команды "opm" содержит "Опция <--all-pools> не сочетается с опцией <--pool>"
И Код возврата команды "opm" равен 1
2 changes: 2 additions & 0 deletions packagedef
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,14 @@
.ОпределяетКласс("СерверПакетов", "src/core/Классы/СерверПакетов.os")
.ОпределяетКласс("СериализацияМетаданныхПакета", "src/core/Классы/СериализацияМетаданныхПакета.os")
.ОпределяетКласс("УстановкаПакета", "src/core/Классы/УстановкаПакета.os")
.ОпределяетМодуль("АдресаХаба", "src/core/Модули/АдресаХаба.os")
.ОпределяетМодуль("КонстантыOpm", "src/core/Модули/КонстантыOpm.os")
.ОпределяетМодуль("НастройкиOpm", "src/core/Модули/НастройкиOpm.os")
.ОпределяетМодуль("РаботаСВерсиями", "src/core/Модули/РаботаСВерсиями.os")
.ОпределяетМодуль("РаботаСОписаниемПакета", "src/core/Модули/РаботаСОписаниемПакета.os")
.ОпределяетМодуль("РаботаСПакетами", "src/core/Модули/РаботаСПакетами.os")
.ОпределяетМодуль("РежимУстановкиПакетов", "src/core/Модули/РежимУстановкиПакетов.os")
.ОпределяетМодуль("СерверыПакетов", "src/core/Модули/СерверыПакетов.os")
.ОпределяетМодуль("ТокенКонвейераCI", "src/core/Модули/ТокенКонвейераCI.os")
.ОпределяетМодуль("ОбщегоНазначенияOpm", "src/core/Модули/ОбщегоНазначенияOpm.os")
.ИсполняемыйФайл("src/cmd/opm.os");
2 changes: 1 addition & 1 deletion src/cmd/Классы/КомандаOpm_Download.os
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
ОпцияЗеркала.Перечисление(ДоступноеЗеркало, ДоступноеЗеркало, "Сервер '" + ДоступноеЗеркало + "'");
КонецЦикла;

КомандаПриложения.Аргумент("PACKAGE", "", "Имя пакета в хабе. Чтобы установить конкретную версию, используйте ИмяПакета@ВерсияПакета")
КомандаПриложения.Аргумент("PACKAGE", "", ПараметрыПриложенияOpm.ОписаниеАргументаПакет())
.ТМассивСтрок()
.Обязательный(Ложь);

Expand Down
47 changes: 46 additions & 1 deletion src/cmd/Классы/КомандаOpm_Install.os
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@
ОпцияЗеркала.Перечисление(ДоступноеЗеркало, ДоступноеЗеркало, "Сервер '" + ДоступноеЗеркало + "'");
КонецЦикла;

КомандаПриложения.Аргумент("PACKAGE", "", "Имя пакета в хабе. Чтобы установить конкретную версию, используйте ИмяПакета@ВерсияПакета")
КомандаПриложения.Опция("hub", "", "Адрес хаба, с которого ставятся пакеты и их зависимости: [схема://]хост[:порт]; без адреса - хаб по умолчанию.")
.ВОкружении("OPM_HUB");
КомандаПриложения.Опция("pool", "", "Пул хаба; без пула - пул хаба по умолчанию.")
.ВОкружении("OPM_POOL");
КомандаПриложения.Опция("all-pools", Ложь, "Ставить из всех доступных пулов хаба: версию выбирает хаб среди пулов, открытых токену.");

КомандаПриложения.Аргумент("PACKAGE", "", ПараметрыПриложенияOpm.ОписаниеАргументаПакет())
.ТМассивСтрок()
.Обязательный(Ложь);

Expand All @@ -44,6 +50,9 @@
НеобходимоУстановитьЗависимости = Не КомандаПриложения.ЗначениеОпции("skip-install-deps");
СоздаватьФайлыЗапуска = НЕ КомандаПриложения.ЗначениеОпции("skip-create-app");
ИмяСервера = КомандаПриложения.ЗначениеОпции("mirror");
Хаб = КомандаПриложения.ЗначениеОпции("hub");
Пул = КомандаПриложения.ЗначениеОпции("pool");
ПоВсемПулам = КомандаПриложения.ЗначениеОпции("all-pools");

РежимУстановки = РежимУстановкиПакетов.Глобально;
Если УстановкаВЛокальныйКаталог Тогда
Expand Down Expand Up @@ -78,6 +87,14 @@
НастройкаУстановки.СоздаватьФайлыЗапуска = СоздаватьФайлыЗапуска;
НастройкаУстановки.ИмяСервера = ИмяСервера;

Если ЗначениеЗаполнено(Хаб) Или ЗначениеЗаполнено(Пул) Или ПоВсемПулам Тогда
Если УстановкаВсехПакетов Или ЗначениеЗаполнено(ФайлПакетаУстановки) Или ЗначениеЗаполнено(ИнтернетСсылкаНаПакет)
Или ЗначениеЗаполнено(ИмяСервера) Тогда
ВызватьИсключение "Опция <--hub> не сочетается с опциями <-a>, <-f>, <-u> и <-m>";
КонецЕсли;
НастройкаУстановки.ИмяСервера = ПодключитьХаб(Хаб, Пул, ПоВсемПулам, МассивПакетовКУстановке);
КонецЕсли;

Если УстановкаВсехПакетов Тогда
РаботаСПакетами.УстановитьВсеПакетыИзОблака(РежимУстановки, ЦелевойКаталог, НастройкаУстановки);
ИначеЕсли ПустаяСтрока(ФайлПакетаУстановки) И ПустаяСтрока(ИнтернетСсылкаНаПакет) И МассивПакетовКУстановке.Количество() = 0 Тогда
Expand Down Expand Up @@ -144,6 +161,34 @@

КонецПроцедуры

// Подключает хаб из опций --hub, --pool и --all-pools: пакеты и их зависимости ставятся с него.
//
// Параметры:
// Хаб - Строка - [схема://]хост[:порт].
// Пул - Строка - пул хаба; пустой - пул хаба по умолчанию.
// ПоВсемПулам - Булево - выдача по всем доступным пулам хаба; с пулом не сочетается.
// ИменаПакетов - Массив - имена пакетов из аргумента PACKAGE.
//
// Возвращаемое значение:
// Строка - имя сервера пула в настройках.
//
Функция ПодключитьХаб(Знач Хаб, Знач Пул, Знач ПоВсемПулам, Знач ИменаПакетов)

Если ПоВсемПулам И ЗначениеЗаполнено(Пул) Тогда
ВызватьИсключение "Опция <--all-pools> не сочетается с опцией <--pool>: укажите одно из двух";
КонецЕсли;

Адрес = АдресаХаба.Адрес(Хаб, Пул);
Для Каждого ИмяПакета Из ИменаПакетов Цикл
Если АдресаХаба.ЭтоАдресХаба(ИмяПакета) Тогда
ВызватьИсключение СтрШаблон("Пакет <%1> задан адресом вместе с опцией <--hub>: укажите одно из двух", ИмяПакета);
КонецЕсли;
КонецЦикла;

Возврат РаботаСПакетами.ПодключитьСерверХаба(Адрес, ПоВсемПулам);

КонецФункции

Функция ЭтоФайлПакета(Знач ИмяПакета)

Возврат СтрЗаканчиваетсяНа(НРег(ИмяПакета), ".ospx");
Expand Down
Loading
Loading