diff --git a/.cursor/rules/agent-workflow.mdc b/.cursor/rules/agent-workflow.mdc new file mode 100644 index 0000000..4b2c4a2 --- /dev/null +++ b/.cursor/rules/agent-workflow.mdc @@ -0,0 +1,13 @@ +--- +description: Правила работы агента в репозитории OPM +alwaysApply: true +--- + +# Правила работы агента в этом репо + +- Меняй только то, что нужно для задачи; без «заодно»-рефакторинга и лишних markdown-файлов. +- Сохраняй русские имена и стиль соседнего кода. +- После существенных правок ядра/CLI — запускай релевантные тесты (`tasks/test.os` или узкий сценарий). +- Коммиты и push — только по явной просьбе пользователя. +- Не коммить секреты (`GITHUB_OAUTH_TOKEN`, `opm.cfg` с токенами/прокси). +- Пользовательский `README.md` обновляй, если меняется публичное поведение CLI. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..14814ce --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,199 @@ +# AGENTS.md — руководство для AI-агентов (OPM) + +Документ для агентов, работающих с этим репозиторием. Цель — быстро понять архитектуру, где править код и как проверять изменения. + +## Что это за проект + +**OPM (OneScript Package Manager)** — менеджер пакетов для [OneScript](https://oscript.io): сборка `.ospx`, установка из хаба/файла/URL, разрешение зависимостей, публикация, scaffold и запуск задач. + +- Репозиторий: https://github.com/oscript-library/opm +- Лицензия: Apache-2.0 +- Версия продукта: `КонстантыOpm.ВерсияПродукта` (сейчас `1.1.2`) +- Требуемая среда: OneScript ≥ **1.8.3** (`packagedef`) +- Хабы: `http://hub.oscript.io`, запасной `http://hub.oscript.ru` +- Packaging docs: https://hub.oscript.io/packaging + +## Стек + +| Слой | Технология | +|------|------------| +| Язык | OneScript / BSL (`.os`), **русские** идентификаторы | +| CLI | пакет `cli` | +| Логи | `logos` (`oscript.app.opm`) | +| Unit | `1testrunner` | +| BDD | `1bdd` (Gherkin на русском) | +| Coverage | `coverage` + `oscript -codestat=` | +| CI | GitHub Actions + SonarQube (`sonar.openbsl.ru`) | + +Runtime-зависимости — в корневом `packagedef`: `fs`, `asserts`, `fluent`, `logos`, `cli`, `tempfiles`, `gitrunner`, `reflector`. + +## Структура репозитория + +``` +opm/ +├── packagedef # манифест пакета OPM +├── src/ +│ ├── cmd/ +│ │ ├── opm.os # точка входа CLI +│ │ ├── Классы/ # КомандаOpm_*.os, ИсполнительЗадач +│ │ └── Модули/ # ПараметрыПриложенияOpm +│ └── core/ +│ ├── Классы/ # менеджеры, сборщик, установка... +│ └── Модули/ # КонстантыOpm, РаботаС*, НастройкиOpm +├── tasks/ # opm run / opm test +│ ├── test.os # unit + bdd +│ ├── coverage.os # как в CI +│ └── oscript.cfg +├── tests/ # unit-тесты (1testrunner) +├── features/ # BDD (1bdd) + step_definitions/ +├── .github/workflows/ # CI / release / rebase +└── oscript_modules/ # локальные зависимости (vendor, в .gitignore) +``` + +Отдельного каталога `docs/` нет — ориентир: `README.md` и этот файл. + +## Архитектура (слои) + +``` +src/cmd/opm.os (cli.КонсольноеПриложение) + → КомандаOpm_* (ОписаниеКоманды / ВыполнитьКоманду) + → РаботаСПакетами / СборщикПакета / ИсполнительЗадач / ... + → МенеджерУстановкиПакетов / МенеджерПолученияПакетов / УстановкаПакета / ... +``` + +### Карта «хочу изменить X» + +| Задача | Куда смотреть | +|--------|----------------| +| CLI-команда / флаги | `src/cmd/opm.os`, `src/cmd/Классы/КомандаOpm_*.os` | +| Install / зависимости | `РаботаСПакетами`, `МенеджерУстановкиПакетов`, `УстановкаПакета`, `КэшУстановленныхПакетов` | +| Скачивание с хаба | `МенеджерПолученияПакетов`, `СерверПакетов`, `КонстантыOpm` | +| Сборка `.ospx` | `СборщикПакета`, `ОписаниеПакета`, `СериализацияМетаданныхПакета` | +| Publish | `КомандаOpm_Push` | +| Версии `Имя@Версия` | `РаботаСВерсиями` | +| Чтение `packagedef` | `РаботаСОписаниемПакета`, `ОписаниеПакета` | +| `opm.cfg` / прокси / зеркала | `ПараметрыПриложенияOpm`, `НастройкиOpm` | +| Версия продукта | `src/core/Модули/КонстантыOpm.os` (+ fallback в `packagedef`) | + +### Команды CLI + +| Команда | Класс | +|---------|-------| +| `a app` | `КомандаOpm_App` | +| `b build` | `КомандаOpm_Build` | +| `c config` | `КомандаOpm_Config` | +| `i install` | `КомандаOpm_Install` | +| `ls list` | `КомандаOpm_List` | +| `pre prepare` | `КомандаOpm_Prepare` | +| `p push` | `КомандаOpm_Push` | +| `r run` | `КомандаOpm_Run` | +| `test` | `КомандаOpm_Test` | +| `u update` | `КомандаOpm_Update` | +| `version` | `КомандаOpm_Version` | + +### Потоки данных (кратко) + +**Install:** CLI → `РаботаСПакетами` → download (`МенеджерПолученияПакетов`) → `УстановкаПакета` (unzip `.ospx`) → рекурсивные зависимости → кэш установленных. + +**Build:** `СборщикПакета` читает `packagedef` (контекст `Описание` = fluent `ОписаниеПакета`) → hooks → `{Имя}-{Версия}.ospx` = ZIP(`opm-metadata.xml` + `content.zip`). + +**Режимы установки:** локально → `./oscript_modules`; глобально → системный `lib` OneScript (`РежимУстановкиПакетов`). + +## Окружение и команды + +### Подготовка + +```powershell +# Нужен OneScript ≥ 1.8.3 (stable или 1.8.4 как в CI) +opm install opm +opm install 1testrunner +opm install 1bdd +opm install coverage +opm install -l --dev +``` + +### Запуск из исходников + +```powershell +oscript src\cmd\opm.os --help +oscript src\cmd\opm.os version +oscript src\cmd\opm.os install --local +oscript src\cmd\opm.os build --mf .\packagedef . +``` + +Отладка: `.vscode/launch.json`, `LOGOS_CONFIG=logger.oscript.app.opm=DEBUG`. + +### Тесты + +```powershell +oscript tasks\test.os # unit + bdd +opm test # через CLI +``` + +Отчёты: каталог `out/`. + +### Типовые CLI-вызовы + +```powershell +opm install asserts +opm install --local # зависимости packagedef → ./oscript_modules +opm install --local --dev +opm install -f my.ospx --local +opm install Package@1.2.0 +opm build --mf .\packagedef . +opm list +opm list --remote +opm prepare my-package +opm update opm +``` + +Полезные переменные: `OSCRIPTBIN`, `OPM_HUB_MIRROR`, `OPM_HUB_CHANNEL`, `GITHUB_OAUTH_TOKEN`, `LOGOS_CONFIG`. + +## Соглашения по коду + +1. **Русский BSL** — имена процедур, переменных, каталогов `Классы/` / `Модули/`. +2. Пользовательские строки — не использовать `НСтр("ru='...';en='...')`. +3. Модули = статический API (`РаботаС*`, `КонстантыOpm`); классы = состояние (`Менеджер*`, `УстановкаПакета`). +4. Подключения: `#Использовать logos`, `#Использовать "../core"`, `#Использовать cli`. +5. Комментарии — на русском. Не рефакторить стиль «заодно», если задача этого не требует. + +### Добавление CLI-команды + +1. Создать `src/cmd/Классы/КомандаOpm_Foo.os` по образцу `КомандаOpm_Build.os`: + - `ОписаниеКоманды(КомандаПриложения)` — опции и аргументы; + - `ВыполнитьКоманду(КомандаПриложения)` — логика. +2. Зарегистрировать в `src/cmd/opm.os`: + `Приложение.ДобавитьКоманду("f foo", НСтр(...), Новый КомандаOpm_Foo);` +3. При необходимости — unit/BDD и строка в `README.md`. + +**Не копировать** `ШаблонКоманды.os-template` — там устаревший cmdline API. Актуальный паттерн — пакет `cli`, как в `КомандаOpm_Build.os`. + +### Зависимости самого OPM + +В корневом `packagedef`: + +```bsl +.ЗависитОт("имя", "min.version") +.РазработкаЗависитОт("имя", "min.version") +``` + +Затем `opm install -l` / `opm install -l --dev`. + +## Тесты: контракт + +**Unit** (`tests/*.os`, 1testrunner): + +- `ПолучитьСписокТестов(Тестирование)` +- `ПередЗапускомТеста` / `ПослеЗапускаТеста` +- методы `ТестДолжен_*` +- asserts: `#Использовать asserts`, `Ожидаем` + +Файлы: `packagedef-test.os`, `versions-test.os`, `mft-serializer-test.os`, `pkg-cache.os`, `packagelist.os`, `download.os`, `build-install-test.os`. + +**BDD** (`features/*.feature` + `features/step_definitions/*.os`, `# language: ru`): + +- `opm-build.feature`, `install-file.feature`, `Настройки.feature` + +При правках логики — добавляй/обновляй ближайший тест в той же области (см. карту выше). + +Перед PR желательно прогнать `oscript tasks\test.os`. \ No newline at end of file diff --git "a/src/cmd/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260Opm_Push.os" "b/src/cmd/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260Opm_Push.os" index feda711..6856466 100644 --- "a/src/cmd/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260Opm_Push.os" +++ "b/src/cmd/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260Opm_Push.os" @@ -25,7 +25,7 @@ .Перечисление("stable", "stable", "Канал содержащий стабильные версии пакетов") .Перечисление("dev", "dev" , "Канал содержащий разработческие версии пакетов") .ВОкружении("OPM_HUB_CHANNEL"); - ОпцияЗеркала = КомандаПриложения.Опция("m mirror", "СерверУдаленногоХранилища", "Имя сервера для публикации. + ОпцияЗеркала = КомандаПриложения.Опция("m mirror", "ОсновнойСерверПакетов", "Имя сервера для публикации. | Доступные сервера прописываются в конфигурационном файле opm.cfg, параметр 'СервераПакетов'.") .ВОкружении("OPM_HUB_MIRROR") .ТПеречисление(); @@ -48,10 +48,7 @@ ИмяСервераПакетов = КомандаПриложения.ЗначениеОпции("mirror"); Если ПустаяСтрока(ТокенАвторизации) Тогда - НастройкиИзФайла = ПараметрыПриложенияOpm.СобратьНастройкиИзФайлов(); - Если НастройкиИзФайла.Свойство("ТокенПубликацииПакетов") Тогда - ТокенАвторизации = НастройкиИзФайла.ТокенПубликацииПакетов; - КонецЕсли; + ТокенАвторизации = НастройкиOpm.ПолучитьНастройки().ТокенПубликацииПакетов; КонецЕсли; МаскаФайлаПакета = КомандаПриложения.ЗначениеАргумента("FILE"); @@ -135,26 +132,58 @@ КонецФункции -Процедура ОтправитьПакетВХаб(Знач ТокенАвторизации, Знач ФайлПакета, Знач Канал, Знач ИмяСервераПакетов) +Функция ПортПоУмолчанию(Знач Сервер) - ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ФайлПакета.ПолноеИмя); - ДвоичныеДанныеФайлаВBase64 = Base64Строка(ДвоичныеДанныеФайла); + Возврат ?(СтрНачинаетсяС(НРег(Сервер), "https://"), 443, 80); +КонецФункции + +Функция ПолучитьПараметрыСоединенияПубликации(Знач ИмяСервераПакетов) Экспорт + ДоступныеСервераПакетов = НастройкиOpm.ПолучитьНастройки().СервераПакетов; - // Для настроек по умолчанию Сервер = КонстантыOpm.СерверУдаленногоХранилища; Ресурс = КонстантыOpm.РесурсПубликацииПакетов; + Порт = Неопределено; Для Каждого НастройкаСервера Из ДоступныеСервераПакетов Цикл Если СтрСравнить(НастройкаСервера.Имя, ИмяСервераПакетов) = 0 Тогда Сервер = НастройкаСервера.Сервер; Ресурс = НастройкаСервера.РесурсПубликацииПакетов; + Порт = НастройкаСервера.Порт; Прервать; КонецЕсли; КонецЦикла; + + Порт = ?(Порт = Неопределено, ПортПоУмолчанию(Сервер), Порт); + Настройки = НастройкиOpm.ПолучитьНастройки(); + НастройкиПрокси = Неопределено; + Если Настройки.ИспользоватьПрокси Тогда + НастройкиПрокси = НастройкиOpm.ПолучитьИнтернетПрокси(); + КонецЕсли; + + Результат = Новый Структура; + Результат.Вставить("Сервер", Сервер); + Результат.Вставить("Ресурс", Ресурс); + Результат.Вставить("Порт", Порт); + Результат.Вставить("ИспользоватьПрокси", Настройки.ИспользоватьПрокси); + Результат.Вставить("НастройкиПрокси", НастройкиПрокси); + + Возврат Результат; + +КонецФункции + +Процедура ОтправитьПакетВХаб(Знач ТокенАвторизации, Знач ФайлПакета, Знач Канал, Знач ИмяСервераПакетов) + + ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ФайлПакета.ПолноеИмя); + ДвоичныеДанныеФайлаВBase64 = Base64Строка(ДвоичныеДанныеФайла); + + ПараметрыСоединения = ПолучитьПараметрыСоединенияПубликации(ИмяСервераПакетов); + Сервер = ПараметрыСоединения.Сервер; + Ресурс = ПараметрыСоединения.Ресурс; + Порт = ПараметрыСоединения.Порт; Лог.Отладка("Сервер = %1", Сервер); Лог.Отладка("Ресурс = %1", Ресурс); @@ -164,7 +193,11 @@ Заголовки.Вставить("FILE-NAME", ФайлПакета.Имя); Заголовки.Вставить("CHANNEL", Канал); - Соединение = Новый HTTPСоединение(Сервер); + Если ПараметрыСоединения.ИспользоватьПрокси Тогда + Соединение = Новый HTTPСоединение(Сервер, Порт, , , ПараметрыСоединения.НастройкиПрокси); + Иначе + Соединение = Новый HTTPСоединение(Сервер, Порт); + КонецЕсли; Запрос = Новый HTTPЗапрос(Ресурс, Заголовки); Запрос.УстановитьТелоИзДвоичныхДанных(ДвоичныеДанныеФайла); diff --git "a/src/cmd/\320\234\320\276\320\264\321\203\320\273\320\270/\320\237\320\260\321\200\320\260\320\274\320\265\321\202\321\200\321\213\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\321\217Opm.os" "b/src/cmd/\320\234\320\276\320\264\321\203\320\273\320\270/\320\237\320\260\321\200\320\260\320\274\320\265\321\202\321\200\321\213\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\321\217Opm.os" index 1a441ad..96df0e6 100644 --- "a/src/cmd/\320\234\320\276\320\264\321\203\320\273\320\270/\320\237\320\260\321\200\320\260\320\274\320\265\321\202\321\200\321\213\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\321\217Opm.os" +++ "b/src/cmd/\320\234\320\276\320\264\321\203\320\273\320\270/\320\237\320\260\321\200\320\260\320\274\320\265\321\202\321\200\321\213\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\321\217Opm.os" @@ -7,93 +7,110 @@ Процедура НастроитьOpm() Экспорт - НастройкиOpmИзФайлов = СобратьНастройкиИзФайлов(); + Документ = ПрочитатьДействующийДокументНастроек(); - Если НастройкиOpmИзФайлов.Количество() = 0 Тогда + Если Документ = Неопределено Тогда Лог.Отладка("Настройки opm не найдены"); Возврат; КонецЕсли; - НастройкиПроксиЕсть = ПолучитьЗначение(НастройкиOpmИзФайлов,"Прокси", Неопределено); - - Если Не НастройкиПроксиЕсть = Неопределено Тогда + ПредупредитьОНеизвестныхКлючах(Документ); + НастройкиOpm.ЗагрузитьИзДокумента(Документ); - НастройкиПрокси = НастройкиOpmИзФайлов.Прокси; +КонецПроцедуры - Сервер = ПолучитьЗначение(НастройкиПрокси, "Сервер", ""); - Порт = Число(ПолучитьЗначение(НастройкиПрокси, "Порт", 80)); - Пользователь = ПолучитьЗначение(НастройкиПрокси, "Пользователь", ""); - Пароль = ПолучитьЗначение(НастройкиПрокси, "Пароль", ""); +Процедура ПредупредитьОНеизвестныхКлючах(Знач Документ) - ПроксиПоУмолчанию = ПолучитьЗначение(НастройкиПрокси,"ПроксиПоУмолчанию", Неопределено); - ИспользованиеПрокси = ПолучитьЗначение(НастройкиПрокси, "ИспользоватьПрокси", Неопределено); + ИзвестныеКорневыеКлючи = Новый Соответствие; + ИзвестныеКорневыеКлючи.Вставить("СоздаватьShСкриптЗапуска", Истина); + ИзвестныеКорневыеКлючи.Вставить("Прокси", Истина); + ИзвестныеКорневыеКлючи.Вставить("СервераПакетов", Истина); + ИзвестныеКорневыеКлючи.Вставить("ТокенПубликацииПакетов", Истина); - Если ИспользованиеПрокси = Истина Тогда - НастройкиOpm.УстановитьИспользованиеПрокси(ИспользованиеПрокси); + Для каждого КлючЗначение Из Документ Цикл + Если ИзвестныеКорневыеКлючи[КлючЗначение.Ключ] = Неопределено Тогда + Лог.Предупреждение(НСтр("ru='Неизвестный ключ настроек opm.cfg: %1';en='Unknown opm.cfg setting key: %1'"), КлючЗначение.Ключ); + КонецЕсли; + КонецЦикла; - Если Не ПроксиПоУмолчанию = Неопределено Тогда - НастройкиOpm.УстановитьСистемныеНастройкиПроксиСервера(ПроксиПоУмолчанию); - КонецЕсли; + Прокси = Неопределено; + Если Документ.Свойство("Прокси", Прокси) И ТипЗнч(Прокси) = Тип("Структура") Тогда + ПредупредитьОНеизвестныхКлючахСекции(Прокси, КлючиСекцииПрокси(), "Прокси"); + КонецЕсли; - Если ПроксиПоУмолчанию = Неопределено Или ПроксиПоУмолчанию = Ложь Тогда - НастройкиOpm.УстановитьНастройкиПроксиСервера(Сервер, Порт, Пользователь, Пароль); + СервераПакетов = Неопределено; + Если Документ.Свойство("СервераПакетов", СервераПакетов) И ТипЗнч(СервераПакетов) = Тип("Массив") Тогда + Индекс = 0; + Для каждого ЭлементСервера Из СервераПакетов Цикл + Индекс = Индекс + 1; + Если ТипЗнч(ЭлементСервера) = Тип("Структура") Тогда + ПредупредитьОНеизвестныхКлючахСекции(ЭлементСервера, КлючиСекцииСервераПакетов(), + СтрШаблон("СервераПакетов[%1]", Индекс)); КонецЕсли; + КонецЦикла; + КонецЕсли; +КонецПроцедуры + +Процедура ПредупредитьОНеизвестныхКлючахСекции(Знач Секция, Знач ИзвестныеКлючи, Знач ИмяСекции) + + Для каждого КлючЗначение Из Секция Цикл + Если ИзвестныеКлючи[КлючЗначение.Ключ] = Неопределено Тогда + Лог.Предупреждение(НСтр("ru='Неизвестный ключ настроек opm.cfg: %1.%2';en='Unknown opm.cfg setting key: %1.%2'"), + ИмяСекции, КлючЗначение.Ключ); КонецЕсли; + КонецЦикла; - КонецЕсли; +КонецПроцедуры - СоздаватьShСкриптЗапуска = ПолучитьЗначение(НастройкиOpmИзФайлов,"СоздаватьShСкриптЗапуска", Неопределено); - - Если Не СоздаватьShСкриптЗапуска = Неопределено Тогда +Функция КлючиСекцииПрокси() - НастройкиOpm.УстановитьСозданиеShСкриптЗапуска(СоздаватьShСкриптЗапуска); + Результат = Новый Соответствие; + Результат.Вставить("Сервер", Истина); + Результат.Вставить("Порт", Истина); + Результат.Вставить("Пользователь", Истина); + Результат.Вставить("Пароль", Истина); + Результат.Вставить("ИспользоватьПрокси", Истина); + Результат.Вставить("ПроксиПоУмолчанию", Истина); + Результат.Вставить("ИспользоватьАутентификациюОС", Истина); + Возврат Результат; - КонецЕсли; +КонецФункции - СервераПакетов = ПолучитьЗначение(НастройкиOpmИзФайлов,"СервераПакетов", Неопределено); - - Если Не СервераПакетов = Неопределено Тогда - Индекс = 1; - - Для каждого ТекущийСерверПакетов Из СервераПакетов Цикл - - Сервер = ПолучитьЗначение(ТекущийСерверПакетов, "Сервер", ""); - Порт = Число(ПолучитьЗначение(ТекущийСерверПакетов, "Порт", 80)); - ПутьНаСервере = ПолучитьЗначение(ТекущийСерверПакетов, "ПутьНаСервере", "/"); - Имя = ПолучитьЗначение(ТекущийСерверПакетов, "Имя", СтрШаблон("ДопСервер_%1", Индекс)); - РесурсПубликацииПакетов = ПолучитьЗначение(ТекущийСерверПакетов, "РесурсПубликацииПакетов", "/"); - Приоритет = Число(ПолучитьЗначение(ТекущийСерверПакетов, "Приоритет", 0)); - - Если ПустаяСтрока(Сервер) Тогда - Лог.Отладка("Для сервера <%1> не задан адрес", Индекс); - Продолжить; - КонецЕсли; - - НастройкиOpm.ДобавитьСерверПакетов(Имя, Сервер, ПутьНаСервере, РесурсПубликацииПакетов, Порт, Приоритет); - Индекс = Индекс +1; +Функция КлючиСекцииСервераПакетов() - КонецЦикла; + Результат = Новый Соответствие; + Результат.Вставить("Имя", Истина); + Результат.Вставить("Сервер", Истина); + Результат.Вставить("Порт", Истина); + Результат.Вставить("ПутьНаСервере", Истина); + Результат.Вставить("РесурсПубликацииПакетов", Истина); + Результат.Вставить("Приоритет", Истина); + Возврат Результат; - КонецЕсли; +КонецФункции -КонецПроцедуры +Функция ПрочитатьДействующийДокументНастроек() -Функция ПолучитьЗначение(ВходящаяСтруктура, Ключ, ЗначениеПоУмолчанию) + МассивПутейНастроек = СписокФайловНастроек(); - Перем ЗначениеКлюча; + Для каждого Элемент из МассивПутейНастроек Цикл - Если Не ВходящаяСтруктура.Свойство(Ключ, ЗначениеКлюча) Тогда - - Возврат ЗначениеПоУмолчанию; - - КонецЕсли; + Лог.Отладка("Чтение файла настроек %1", Элемент); - Если НЕ ЗначениеЗаполнено(ЗначениеКлюча) Тогда - Возврат ЗначениеПоУмолчанию; - КонецЕсли; + Если НЕ Новый Файл(Элемент).Существует() Тогда + Продолжить; + КонецЕсли; + + Попытка + Возврат ПрочитатьФайлНастроек(Элемент); + Исключение + Лог.Отладка("Чтение файла настроек %1. Ошибка: %2", Элемент, ОписаниеОшибки()); + КонецПопытки; + + КонецЦикла; - Возврат ЗначениеКлюча; + Возврат Неопределено; КонецФункции @@ -184,34 +201,6 @@ КонецФункции -Функция СобратьНастройкиИзФайлов() Экспорт - - НастройкиФайла = Новый Структура; - - МассивПутейНастроек = СписокФайловНастроек(); - - Для каждого Элемент из МассивПутейНастроек Цикл - - Лог.Отладка("Чтение файла настроек %1", Элемент); - - Если НЕ Новый Файл(Элемент).Существует() Тогда - Продолжить; - КонецЕсли; - - Попытка - НастройкиФайла = ПрочитатьФайлНастроек(Элемент); - Прервать; - Исключение - Лог.Отладка("Чтение файла настроек %1. Ошибка: %2", Элемент, ОписаниеОшибки()); - - КонецПопытки; - - КонецЦикла; - - Возврат НастройкиФайла; - -КонецФункции - Функция ПрочитатьФайлНастроек(Знач ПутьФайлаНастроек) Если НЕ Новый Файл(ПутьФайлаНастроек).Существует() Тогда diff --git "a/src/core/\320\234\320\276\320\264\321\203\320\273\320\270/\320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\270Opm.os" "b/src/core/\320\234\320\276\320\264\321\203\320\273\320\270/\320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\270Opm.os" index 40f745c..8c494ba 100644 --- "a/src/core/\320\234\320\276\320\264\321\203\320\273\320\270/\320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\270Opm.os" +++ "b/src/core/\320\234\320\276\320\264\321\203\320\273\320\270/\320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\270Opm.os" @@ -78,6 +78,108 @@ КонецПроцедуры +Процедура УстановитьТокенПубликацииПакетов(Знач ЗначениеНастройки) Экспорт + + мНастройки.ТокенПубликацииПакетов = ЗначениеНастройки; + +КонецПроцедуры + +// Заполняет модель из прочитанного JSON-документа opm.cfg. +// Неизвестные ключи не загружаются; вызывающий код логирует предупреждения. +// +// Параметры: +// Документ - Структура - корневой объект opm.cfg +// +Процедура ЗагрузитьИзДокумента(Знач Документ) Экспорт + + мНастройки.СервераПакетов = Новый Массив; + + НастройкиПроксиЕсть = ПолучитьЗначение(Документ, "Прокси", Неопределено); + + Если Не НастройкиПроксиЕсть = Неопределено Тогда + + НастройкиПрокси = Документ.Прокси; + + Сервер = ПолучитьЗначение(НастройкиПрокси, "Сервер", ""); + Порт = Число(ПолучитьЗначение(НастройкиПрокси, "Порт", 80)); + Пользователь = ПолучитьЗначение(НастройкиПрокси, "Пользователь", ""); + Пароль = ПолучитьЗначение(НастройкиПрокси, "Пароль", ""); + ИспользоватьАутентификациюОС = Булево(ПолучитьЗначение(НастройкиПрокси, "ИспользоватьАутентификациюОС", Ложь)); + + ПроксиПоУмолчанию = ПолучитьЗначение(НастройкиПрокси, "ПроксиПоУмолчанию", Неопределено); + ИспользованиеПрокси = ПолучитьЗначение(НастройкиПрокси, "ИспользоватьПрокси", Неопределено); + + УстановитьНастройкиПроксиСервера(Сервер, Порт, Пользователь, Пароль, ИспользоватьАутентификациюОС); + + Если Не ИспользованиеПрокси = Неопределено Тогда + УстановитьИспользованиеПрокси(ИспользованиеПрокси); + КонецЕсли; + + Если Не ПроксиПоУмолчанию = Неопределено Тогда + УстановитьСистемныеНастройкиПроксиСервера(ПроксиПоУмолчанию); + КонецЕсли; + + КонецЕсли; + + СоздаватьShСкриптЗапуска = ПолучитьЗначение(Документ, "СоздаватьShСкриптЗапуска", Неопределено); + + Если Не СоздаватьShСкриптЗапуска = Неопределено Тогда + УстановитьСозданиеShСкриптЗапуска(СоздаватьShСкриптЗапуска); + КонецЕсли; + + ТокенПубликацииПакетов = ПолучитьЗначение(Документ, "ТокенПубликацииПакетов", Неопределено); + + Если Не ТокенПубликацииПакетов = Неопределено Тогда + УстановитьТокенПубликацииПакетов(ТокенПубликацииПакетов); + КонецЕсли; + + СервераПакетов = ПолучитьЗначение(Документ, "СервераПакетов", Неопределено); + + Если Не СервераПакетов = Неопределено Тогда + Индекс = 1; + + Для каждого ТекущийСерверПакетов Из СервераПакетов Цикл + + Сервер = ПолучитьЗначение(ТекущийСерверПакетов, "Сервер", ""); + Порт = ПолучитьЗначение(ТекущийСерверПакетов, "Порт", Неопределено); + Если Не Порт = Неопределено Тогда + Порт = Число(Порт); + КонецЕсли; + ПутьНаСервере = ПолучитьЗначение(ТекущийСерверПакетов, "ПутьНаСервере", "/"); + Имя = ПолучитьЗначение(ТекущийСерверПакетов, "Имя", СтрШаблон("ДопСервер_%1", Индекс)); + РесурсПубликацииПакетов = ПолучитьЗначение(ТекущийСерверПакетов, "РесурсПубликацииПакетов", "/"); + Приоритет = Число(ПолучитьЗначение(ТекущийСерверПакетов, "Приоритет", 0)); + + Если ПустаяСтрока(Сервер) Тогда + Лог.Отладка("Для сервера <%1> не задан адрес", Индекс); + Продолжить; + КонецЕсли; + + ДобавитьСерверПакетов(Имя, Сервер, ПутьНаСервере, РесурсПубликацииПакетов, Порт, Приоритет); + Индекс = Индекс + 1; + + КонецЦикла; + + КонецЕсли; + +КонецПроцедуры + +Функция ПолучитьЗначение(ВходящаяСтруктура, Ключ, ЗначениеПоУмолчанию) + + Перем ЗначениеКлюча; + + Если Не ВходящаяСтруктура.Свойство(Ключ, ЗначениеКлюча) Тогда + Возврат ЗначениеПоУмолчанию; + КонецЕсли; + + Если НЕ ЗначениеЗаполнено(ЗначениеКлюча) Тогда + Возврат ЗначениеПоУмолчанию; + КонецЕсли; + + Возврат ЗначениеКлюча; + +КонецФункции + Процедура УстановитьСистемныеНастройкиПроксиСервера(Знач ЗначениеНастройки) Экспорт мНастройки.ИспользоватьПрокси = ЗначениеНастройки; @@ -103,7 +205,7 @@ Знач Сервер, Знач ПутьНаСервере = "", Знач РесурсПубликацииПакетов = "", - Знач Порт = 80, + Знач Порт = Неопределено, Знач Приоритет = Неопределено) Экспорт мНастройки.СервераПакетов.Добавить(НастройкиСервераПакетов(Имя, Сервер, ПутьНаСервере, РесурсПубликацииПакетов, Порт, Приоритет)); @@ -121,8 +223,8 @@ Процедура ИнициализацияСерверовПакетов() // Сервера пакетов по умолчанию - ДобавитьСерверПакетов("ОсновнойСерверПакетов", КонстантыOpm.СерверУдаленногоХранилища, КонстантыOpm.ПутьВХранилище, КонстантыOpm.РесурсПубликацииПакетов, 80, 0); - ДобавитьСерверПакетов("ЗапаснойСерверПакетов", КонстантыOpm.СерверЗапасногоХранилища, КонстантыOpm.ПутьВЗапасномХранилище, Неопределено, 80, 1); + ДобавитьСерверПакетов("ОсновнойСерверПакетов", КонстантыOpm.СерверУдаленногоХранилища, КонстантыOpm.ПутьВХранилище, КонстантыOpm.РесурсПубликацииПакетов, Неопределено, 0); + ДобавитьСерверПакетов("ЗапаснойСерверПакетов", КонстантыOpm.СерверЗапасногоХранилища, КонстантыOpm.ПутьВЗапасномХранилище, Неопределено, Неопределено, 1); КонецПроцедуры Процедура Инициализация() @@ -133,6 +235,7 @@ мНастройки.Вставить("НастройкиПрокси", Новый Структура("Сервер, Порт, Пользователь, Пароль, ИспользоватьАутентификациюОС", "","","","", Ложь)); мНастройки.Вставить("СоздаватьShСкриптЗапуска", Ложь); + мНастройки.Вставить("ТокенПубликацииПакетов", ""); мНастройки.Вставить("СервераПакетов", Новый Массив); КонецПроцедуры diff --git a/tests/proxy-settings-load-test.os b/tests/proxy-settings-load-test.os new file mode 100644 index 0000000..e55652f --- /dev/null +++ b/tests/proxy-settings-load-test.os @@ -0,0 +1,76 @@ +#Использовать asserts +#Использовать tempfiles +#Использовать "../src/core" +#Использовать "../src/cmd" + +Перем юТест; +Перем мВременныеФайлы; +Перем мИсходныйКаталог; + +Функция ПолучитьСписокТестов(Знач Тестирование) Экспорт + + юТест = Тестирование; + + ИменаТестов = Новый Массив; + ИменаТестов.Добавить("ТестДолжен_ЗагрузитьПоляПроксиПриВыключенномИспользовании"); + + Возврат ИменаТестов; + +КонецФункции + +Процедура ПередЗапускомТеста() Экспорт + мИсходныйКаталог = ТекущийКаталог(); +КонецПроцедуры + +Процедура ПослеЗапускаТеста() Экспорт + + УстановитьТекущийКаталог(мИсходныйКаталог); + + Если мВременныеФайлы <> Неопределено Тогда + мВременныеФайлы.Удалить(); + мВременныеФайлы = Неопределено; + КонецЕсли; + + НастройкиOpm.СброситьНастройки(); + ПараметрыПриложенияOpm.НастроитьOpm(); + +КонецПроцедуры + +Процедура ТестДолжен_ЗагрузитьПоляПроксиПриВыключенномИспользовании() Экспорт + + мВременныеФайлы = Новый МенеджерВременныхФайлов; + КаталогНастроек = мВременныеФайлы.СоздатьКаталог(); + + НастройкиДокумента = Новый Структура; + НастройкиПрокси = Новый Структура; + НастройкиПрокси.Вставить("ИспользоватьПрокси", Ложь); + НастройкиПрокси.Вставить("Сервер", "proxy.example.com"); + НастройкиПрокси.Вставить("Порт", 3128); + НастройкиПрокси.Вставить("Пользователь", "proxyuser"); + НастройкиПрокси.Вставить("Пароль", "proxypass"); + НастройкиДокумента.Вставить("Прокси", НастройкиПрокси); + + ПутьФайла = ОбъединитьПути(КаталогНастроек, КонстантыOpm.ИмяФайлаНастроек); + ПараметрыПриложенияOpm.СохранитьНастройки(НастройкиДокумента, ПутьФайла); + + УстановитьТекущийКаталог(КаталогНастроек); + НастройкиOpm.СброситьНастройки(); + ПараметрыПриложенияOpm.НастроитьOpm(); + + ТекущиеНастройки = НастройкиOpm.ПолучитьНастройки(); + НастройкиПроксиМодели = ТекущиеНастройки.НастройкиПрокси; + + Ожидаем.Что(НастройкиПроксиМодели.Сервер, "Сервер прокси должен загрузиться из файла") + .Равно("proxy.example.com"); + Ожидаем.Что(НастройкиПроксиМодели.Порт, "Порт прокси должен загрузиться из файла") + .Равно(3128); + Ожидаем.Что(НастройкиПроксиМодели.Пользователь, "Пользователь прокси должен загрузиться из файла") + .Равно("proxyuser"); + Ожидаем.Что(НастройкиПроксиМодели.Пароль, "Пароль прокси должен загрузиться из файла") + .Равно("proxypass"); + Ожидаем.Что(ТекущиеНастройки.ИспользоватьПрокси, "ИспользоватьПрокси должен остаться Ложь") + .Равно(Ложь); + Ожидаем.Что(ТекущиеНастройки.ИспользоватьСистемныйПрокси, "ИспользоватьСистемныйПрокси должен быть Ложь") + .Равно(Ложь); + +КонецПроцедуры diff --git a/tests/push-test.os b/tests/push-test.os new file mode 100644 index 0000000..bc91003 --- /dev/null +++ b/tests/push-test.os @@ -0,0 +1,86 @@ +#Использовать asserts +#Использовать "../src/core" +#Использовать "../src/cmd" + +Перем юТест; + +Функция ПолучитьСписокТестов(Знач Тестирование) Экспорт + + юТест = Тестирование; + + ИменаТестов = Новый Массив; + ИменаТестов.Добавить("ТестДолжен_ИспользоватьЯвныйПортСервераПакетовПриПубликации"); + ИменаТестов.Добавить("ТестДолжен_ИспользоватьПорт80ДляHttpПубликацииБезЯвногоПорта"); + ИменаТестов.Добавить("ТестДолжен_ИспользоватьПорт443ДляHttpsПубликацииБезЯвногоПорта"); + ИменаТестов.Добавить("ТестДолжен_ПрименитьПроксиПриПубликации"); + + Возврат ИменаТестов; + +КонецФункции + +Процедура ПередЗапускомТеста() Экспорт + + НастройкиOpm.СброситьНастройки(); + +КонецПроцедуры + +Процедура ПослеЗапускаТеста() Экспорт + + НастройкиOpm.СброситьНастройки(); + ПараметрыПриложенияOpm.НастроитьOpm(); + +КонецПроцедуры + +Функция ПараметрыСоединенияПубликации(Знач ИмяСервераПакетов) + + КомандаPush = Новый КомандаOpm_Push; + Возврат КомандаPush.ПолучитьПараметрыСоединенияПубликации(ИмяСервераПакетов); + +КонецФункции + +Процедура ТестДолжен_ИспользоватьЯвныйПортСервераПакетовПриПубликации() Экспорт + + НастройкиOpm.ДобавитьСерверПакетов("ТестовыйСервер", "http://packages.example.com", "/download/", "/push", 8080); + + ПараметрыСоединения = ПараметрыСоединенияПубликации("ТестовыйСервер"); + + Ожидаем.Что(ПараметрыСоединения.Порт, "Публикация должна использовать явный порт из настроек сервера") + .Равно(8080); + +КонецПроцедуры + +Процедура ТестДолжен_ИспользоватьПорт80ДляHttpПубликацииБезЯвногоПорта() Экспорт + + НастройкиOpm.ДобавитьСерверПакетов("HttpСервер", "http://packages.example.com", "/download/", "/push"); + + ПараметрыСоединения = ПараметрыСоединенияПубликации("HttpСервер"); + + Ожидаем.Что(ПараметрыСоединения.Порт, "HTTP-публикация без явного порта должна использовать 80") + .Равно(80); + +КонецПроцедуры + +Процедура ТестДолжен_ИспользоватьПорт443ДляHttpsПубликацииБезЯвногоПорта() Экспорт + + НастройкиOpm.ДобавитьСерверПакетов("HttpsСервер", "https://packages.example.com", "/download/", "/push"); + + ПараметрыСоединения = ПараметрыСоединенияПубликации("HttpsСервер"); + + Ожидаем.Что(ПараметрыСоединения.Порт, "HTTPS-публикация без явного порта должна использовать 443") + .Равно(443); + +КонецПроцедуры + +Процедура ТестДолжен_ПрименитьПроксиПриПубликации() Экспорт + + НастройкиOpm.ДобавитьСерверПакетов("ТестовыйСервер", "http://packages.example.com", "/download/", "/push"); + НастройкиOpm.УстановитьНастройкиПроксиСервера("proxy.example.com", 3128); + + ПараметрыСоединения = ПараметрыСоединенияПубликации("ТестовыйСервер"); + + Ожидаем.Что(ПараметрыСоединения.ИспользоватьПрокси, "Публикация должна учитывать включенный прокси") + .Равно(Истина); + Ожидаем.Что(ПараметрыСоединения.НастройкиПрокси = Неопределено, "Для публикации должны быть подготовлены настройки прокси") + .Равно(Ложь); + +КонецПроцедуры diff --git a/tests/settings-model-test.os b/tests/settings-model-test.os new file mode 100644 index 0000000..d03fead --- /dev/null +++ b/tests/settings-model-test.os @@ -0,0 +1,152 @@ +#Использовать asserts +#Использовать tempfiles +#Использовать "../src/core" +#Использовать "../src/cmd" + +Перем юТест; +Перем мВременныеФайлы; +Перем мИсходныйКаталог; + +Функция ПолучитьСписокТестов(Знач Тестирование) Экспорт + + юТест = Тестирование; + + ИменаТестов = Новый Массив; + ИменаТестов.Добавить("ТестДолжен_ЗагрузитьТокенПубликацииВМодель"); + ИменаТестов.Добавить("ТестДолжен_ИгнорироватьНеизвестныйКорневойКлюч"); + ИменаТестов.Добавить("ТестДолжен_ЗагрузитьИспользоватьАутентификациюОС"); + ИменаТестов.Добавить("ТестДолжен_ЗагрузитьПортПроксиПоУмолчанию"); + ИменаТестов.Добавить("ТестДолжен_ОставитьПортСервераПакетовНеопределеннымЕслиПолеОтсутствует"); + ИменаТестов.Добавить("ТестДолжен_СоздатьВстроенныеСервераПакетовБезЯвногоПорта"); + + Возврат ИменаТестов; + +КонецФункции + +Процедура ПередЗапускомТеста() Экспорт + мИсходныйКаталог = ТекущийКаталог(); +КонецПроцедуры + +Процедура ПослеЗапускаТеста() Экспорт + + УстановитьТекущийКаталог(мИсходныйКаталог); + + Если мВременныеФайлы <> Неопределено Тогда + мВременныеФайлы.Удалить(); + мВременныеФайлы = Неопределено; + КонецЕсли; + + НастройкиOpm.СброситьНастройки(); + ПараметрыПриложенияOpm.НастроитьOpm(); + +КонецПроцедуры + +Процедура ЗагрузитьНастройкиИзВременногоФайла(Знач НастройкиДокумента) + + мВременныеФайлы = Новый МенеджерВременныхФайлов; + КаталогНастроек = мВременныеФайлы.СоздатьКаталог(); + + ПутьФайла = ОбъединитьПути(КаталогНастроек, КонстантыOpm.ИмяФайлаНастроек); + ПараметрыПриложенияOpm.СохранитьНастройки(НастройкиДокумента, ПутьФайла); + + УстановитьТекущийКаталог(КаталогНастроек); + НастройкиOpm.СброситьНастройки(); + ПараметрыПриложенияOpm.НастроитьOpm(); + +КонецПроцедуры + +Процедура ТестДолжен_ЗагрузитьТокенПубликацииВМодель() Экспорт + + НастройкиДокумента = Новый Структура; + НастройкиДокумента.Вставить("ТокенПубликацииПакетов", "ghp_test_token"); + + ЗагрузитьНастройкиИзВременногоФайла(НастройкиДокумента); + + ТекущиеНастройки = НастройкиOpm.ПолучитьНастройки(); + + Ожидаем.Что(ТекущиеНастройки.ТокенПубликацииПакетов, "Токен публикации должен загрузиться в модель") + .Равно("ghp_test_token"); + +КонецПроцедуры + +Процедура ТестДолжен_ИгнорироватьНеизвестныйКорневойКлюч() Экспорт + + НастройкиДокумента = Новый Структура; + НастройкиДокумента.Вставить("FutureSetting", 1); + НастройкиДокумента.Вставить("СоздаватьShСкриптЗапуска", Истина); + + ЗагрузитьНастройкиИзВременногоФайла(НастройкиДокумента); + + ТекущиеНастройки = НастройкиOpm.ПолучитьНастройки(); + + Ожидаем.Что(ТекущиеНастройки.СоздаватьShСкриптЗапуска, "Известный ключ должен загрузиться") + .Равно(Истина); + Ожидаем.Что(ТекущиеНастройки.Свойство("FutureSetting"), "Неизвестный ключ не должен попасть в модель") + .Равно(Ложь); + +КонецПроцедуры + +Процедура ТестДолжен_ЗагрузитьИспользоватьАутентификациюОС() Экспорт + + НастройкиДокумента = Новый Структура; + НастройкиПрокси = Новый Структура; + НастройкиПрокси.Вставить("ИспользоватьПрокси", Ложь); + НастройкиПрокси.Вставить("ИспользоватьАутентификациюОС", Истина); + НастройкиДокумента.Вставить("Прокси", НастройкиПрокси); + + ЗагрузитьНастройкиИзВременногоФайла(НастройкиДокумента); + + ТекущиеНастройки = НастройкиOpm.ПолучитьНастройки(); + + Ожидаем.Что(ТекущиеНастройки.НастройкиПрокси.ИспользоватьАутентификациюОС, + "ИспользоватьАутентификациюОС должен загрузиться из файла") + .Равно(Истина); + +КонецПроцедуры + +Процедура ТестДолжен_ЗагрузитьПортПроксиПоУмолчанию() Экспорт + + НастройкиДокумента = Новый Структура; + НастройкиПрокси = Новый Структура; + НастройкиПрокси.Вставить("Сервер", "proxy.example.com"); + НастройкиДокумента.Вставить("Прокси", НастройкиПрокси); + + ЗагрузитьНастройкиИзВременногоФайла(НастройкиДокумента); + + ТекущиеНастройки = НастройкиOpm.ПолучитьНастройки(); + + Ожидаем.Что(ТекущиеНастройки.НастройкиПрокси.Порт, "Порт прокси по умолчанию должен быть 80") + .Равно(80); + +КонецПроцедуры + +Процедура ТестДолжен_ОставитьПортСервераПакетовНеопределеннымЕслиПолеОтсутствует() Экспорт + + НастройкиДокумента = Новый Структура; + СервераПакетов = Новый Массив; + СервераПакетов.Добавить(Новый Структура("Имя, Сервер, ПутьНаСервере, РесурсПубликацииПакетов", + "ТестовыйСервер", "http://packages.example.com", "/download/", "/push")); + НастройкиДокумента.Вставить("СервераПакетов", СервераПакетов); + + ЗагрузитьНастройкиИзВременногоФайла(НастройкиДокумента); + + ТекущиеНастройки = НастройкиOpm.ПолучитьНастройки(); + НастройкаСервера = ТекущиеНастройки.СервераПакетов[0]; + + Ожидаем.Что(НастройкаСервера.Порт, "Отсутствующий порт сервера пакетов должен остаться Неопределено") + .Равно(Неопределено); + +КонецПроцедуры + +Процедура ТестДолжен_СоздатьВстроенныеСервераПакетовБезЯвногоПорта() Экспорт + + НастройкиOpm.СброситьНастройки(); + + ТекущиеНастройки = НастройкиOpm.ПолучитьНастройки(); + + Ожидаем.Что(ТекущиеНастройки.СервераПакетов[0].Порт, "Основной сервер по умолчанию не должен хранить явный порт") + .Равно(Неопределено); + Ожидаем.Что(ТекущиеНастройки.СервераПакетов[1].Порт, "Запасной сервер по умолчанию не должен хранить явный порт") + .Равно(Неопределено); + +КонецПроцедуры