Написание плагинов
Плагины расширяют Mockarty новым контентом, логикой и интерфейсом — пак готовых
моков, свой генератор $.fake.*, панель в сайдбаре. Этот гайд проведёт от пустой
папки до установленного рабочего плагина за несколько минут.
Если вам нужно только установить чужой плагин — смотрите
Плагины. Эта страница — для авторов.
Быстрый старт — создать, упаковать и включить плагин
Для комплекта моков CLI создаёт исходные файлы и упаковывает их. Работающий
экземпляр Mockarty и токен администратора нужны для установки и включения
готового файла.
mockarty-cli plugin create my-plugin # scaffold (--template для других типов)
mockarty-cli plugin pack my-plugin # → my-plugin-0.1.0.zip
mockarty-cli plugin install my-plugin-0.1.0.zip && mockarty-cli plugin enable my-plugin
Так вы включите стартовый комплект моков. Перед публикацией измените его
содержимое. Для WASM-фейкера дополнительно запустите ./build.sh
(нужен TinyGo). При итерациях
mockarty-cli plugin dev my-plugin --enable переустанавливает плагин после
каждого сохранения. Для другого типа плагина укажите --template при создании.
Что такое плагин
Плагин — это небольшой .zip-бандл с одним ключевым файлом plugin.json
(манифест) плюс ассеты (WebAssembly-модуль, HTML-панель). Манифест
декларирует, что плагин добавляет; Mockarty сам это рисует и применяет.
Механизмы:
| Механизм | Что вы пишете | Где исполняется |
|---|---|---|
| Mock-kit | только JSON | — (декларативный контент) |
| WASM-фейкер | крошечную функцию, скомпилированную в WebAssembly | в изолированной песочнице |
| WASM-трансформер ответа | крошечную функцию, скомпилированную в WebAssembly | в изолированной песочнице, inline на пути ответа |
| WASM-матчер запроса | крошечную функцию, скомпилированную в WebAssembly | в изолированной песочнице, на пути матчинга |
| Кодек протокола | кодировщик/декодировщик строковых фреймов в WebAssembly | в песочнице за listener’ом, которым владеет хост |
| UI-панель (sidebar / page-slot / command / entity-tab / settings) | HTML-страницу | в sandboxed iframe |
| Вики-макрос | JSON + text/template | раскрывается на вики-страницах, виден в /-меню редактора |
| Коннектор | только JSON | переиспользует встроенный адаптер интеграции |
| Контент-пак | только JSON | наполняет другой модуль (wiki / дашборды / задачи / коллекции / тест-кейсы / контракты) по требованию |
Плюс опциональные настройки (форма из JSON-Schema) на любом из них.
Бандл можно загрузить из файла без подключения к реестру.
Цикл авторинга
Всё ниже — через mockarty-cli. Scaffold, упаковка и dev-релоад работают
полностью офлайн; только install/enable требуют запущенного сервера и
админ-токена.
mockarty-cli plugin create my-plugin # scaffold (шаблон mock-kit)
mockarty-cli plugin pack my-plugin # → my-plugin-0.1.0.zip
mockarty-cli plugin install my-plugin-0.1.0.zip # загрузка на сервер (админ)
mockarty-cli plugin enable my-plugin # включить
Выбор шаблона
create создаёт пакет, который уже проходит валидацию и проверку соответствия,
поэтому первым делом вы правите содержимое, а не боретесь со схемой:
--template |
Что создаётся |
|---|---|
mock-kit (по умолчанию) |
набор готовых моков |
wasm-faker |
генератор $.fake.* и build.sh (нужен TinyGo) |
protocol-codec |
client-first строковый протокол, WASM-кодек и build.sh |
ui-panel |
панель в боковом меню — HTML-страница в изолированном iframe |
connector |
тип подключения поверх встроенного адаптера интеграций |
content-pack |
стартовое содержимое для другого модуля (вики, дашборды, задачи, …) |
link-type |
тип связи между сущностями с шаблоном URL для перехода |
event-type |
событие, на которое могут подписаться другие модули |
event-listener |
подписка на события хоста и собственное событие |
task-type |
тип задания, которое умеет выполнять раннер |
wiki-macro |
макрос, раскрывающийся на страницах вики |
agent |
персона-специалист, которой агентская сеть может поручить работу |
skill |
многоразовый рецепт, который хост выдаёт агенту по требованию |
mcp-tool |
ваше задание раннера как полноценный инструмент MCP для агента |
workflow-component |
ваше задание раннера как шаг миссии |
aqc-surface |
своя поверхность качества и обработчик её свидетельства |
Шаблон есть для каждого семейства вкладов, которое понимает сервер, кроме
одного: worker поставляет настоящие исполняемые файлы, и манифест
закрепляет sha256 каждого артефакта — выдумать двоичный файл каркас не может.
Начните worker с шаблона task-type и допишите блок workers руками.
Проверьте пакет раньше всех остальных
plugin test выполняет всё, что сервер проверил бы при установке, и вдобавок
то, что остаётся неверным даже после успешной установки: вклад без названия,
который никто не найдёт в списке; разрешение, запрошенное в манифесте, но
никому не нужное; поле настроек с именем api_token, которое хранило бы токен
открытым текстом. Ни сервер, ни сеть не нужны:
mockarty-cli plugin test ./my-plugin # каталог или собранный .zip
mockarty-cli plugin test ./my-plugin --strict # считать ошибкой и предупреждения (для CI)
mockarty-cli plugin test ./my-plugin --report r.json # переносимый отчёт для ревьюера
mockarty-cli plugin test ./my-plugin --report r.json --sign-key key.b64 # с подписью
Каждая находка сопровождается способом исправления на следующей строке. Ошибки
завершают команду с ненулевым кодом, поэтому задание CI с этой командой не
опубликует пакет, который сервер откажется принимать.
Переменная MOCKARTY_CONFORMANCE_HOST_VERSION дополнительно сверяет объявленный
в манифесте диапазон версий с конкретной версией сервера; без неё эта проверка
пропускается, а не угадывается.
Автодополнение в редакторе и проверка в CI
plugin schema печатает JSON Schema манифеста — она генерируется из тех же
словарей, которые проверяет сервер, поэтому разойтись с ними не может:
mockarty-cli plugin schema --out plugin.schema.json
Укажите её редактору ("$schema": "./plugin.schema.json" в plugin.json) — и
получите автодополнение и подсветку ошибок прямо во время набора.
Пока итерируете — не гоняйте pack/install вручную, пусть dev следит и
переустанавливает:
mockarty-cli plugin dev my-plugin --enable # переустановка на каждое сохранение
Проверить бандл можно в любой момент, без сервера:
mockarty-cli plugin inspect my-plugin-0.1.0.zip
Манифест
{
"id": "acme.ru-fakers",
"name": "Russian fakers",
"version": "1.0.0",
"description": "Что добавляет плагин.",
"author": { "name": "Acme", "url": "https://acme.example" },
"license": "MIT",
"min_mockarty_version": "2.0.0",
"contributes": { }
}
- id — строчный slug с точками-неймспейсами (
acme.ru-fakers). Это
постоянная идентичность плагина; выбирается один раз. - version — semver. Установка старшей версии поверх младшей — апгрейд
на месте с сохранением состояния «включён». - min_mockarty_version — опционально; не даёт установиться на старый сервер.
- contributes — полезная нагрузка. Один или несколько разделов ниже.
Валидация строгая, а ошибки называют точное поле — опечатка падает на
pack/install, никогда молча.
Mock-kit (без кода)
Простейший плагин: готовый контур моков. После включения kit появляется в
каталоге mock-kit’ов и готов к развёртыванию в любой namespace.
{
"contributes": {
"mock_kits": [
{
"key": "demo_users_api",
"name": "Users API (demo)",
"description": "Стартовый контур /users.",
"mocks": [
{ "route": "/users", "method": "GET", "status_code": 200,
"body": { "users": [ { "id": "$.fake.UUID", "name": "$.fake.Name" } ] } },
{ "route": "/users", "method": "POST", "status_code": 201,
"body": { "id": "$.fake.UUID", "created": true } }
]
}
]
}
}
Тела поддерживают те же динамические хелперы, что и любой мок ($.fake.*,
JSONPath). Scaffold: mockarty-cli plugin create my-kit --template mock-kit.
Мок кита умеет всё то же, что мок, сделанный руками
Кит имеет смысл выкладывать, только если контур получается полным — иначе тот,
кто его развернёт, будет достраивать руками самую интересную половину. Поэтому
мок кита принимает те же поля, что и обычный мок, с теми же именами:
| Поле | Что даёт |
|---|---|
conditions |
Несколько моков на одном маршруте, различаемых телом запроса. Так кит выкладывает на одном эндпоинте и обычный ответ, и ошибку, и стриминг. |
header_conditions, query_conditions |
То же, но по заголовкам или параметрам запроса. Условия по query — только для HTTP: вызов инструмента несёт аргументы, а не строку запроса. |
headers |
Заголовки ответа — Retry-After при 429, свой трассировочный идентификатор. Если объявили хоть один заголовок, вы владеете всеми: application/json по умолчанию уже не добавляется. |
delay |
Миллисекунды до ответа (до 120000). Медленный апстрим; работает и для HTTP, и для инструментов MCP. |
priority |
Выбирает из моков со сработавшими условиями: побеждает большее значение. Мок со сработавшими условиями всё равно выше общего мока без условий. |
llm |
Ответ в формате конкретного LLM-провайдера — см. ниже. |
sse |
Явная цепочка server-sent events, для не-LLM потоков. Мок отдаётся клиентам event-stream по своему маршруту. |
protocol + mcp |
Отдавать мок как инструмент MCP, а не как URL. |
Среди моков со сработавшими условиями побеждает самый высокий приоритет; при равном
приоритете выбирается более точное совпадение. Мок без условий — это общий случай: он используется,
только если не подошёл ни один мок с условиями. Пишите общий случай без условий, каждый особый — с условиями, а
пересекающиеся особые случаи упорядочивайте приоритетом.
{
"mocks": [
{ "route": "/v1/chat/completions", "method": "POST", "status_code": 429,
"priority": 90,
"conditions": [ { "path": "$.model", "assertAction": "equals", "value": "mock/rate-limit" } ],
"headers": { "Content-Type": ["application/json"], "Retry-After": ["2"] },
"body": { "error": { "message": "Rate limit reached.", "code": "rate_limit_exceeded" } } },
{ "route": "/v1/chat/completions", "method": "POST", "status_code": 200,
"llm": { "provider": "openai", "model": "gpt-4o-mini",
"content": "Вы сказали: $.req.lastUserMessage",
"finishReason": "stop", "chunkChars": 6, "chunkDelayMs": 25 } }
]
}
Ответы LLM (llm)
provider задаёт формат на проводе: openai (подходит и для DeepSeek, Qwen,
Mistral, Ollama и прочих OpenAI-совместимых API) или anthropic. Для стриминга
второй мок не нужен: если запрос содержит "stream": true, тот же самый мок
отдаётся потоком по токенам, а темп задают chunkChars и chunkDelayMs.
toolCalls возвращает вызов функции вместо текста, а finishReason должен
использовать словарь самого провайдера (stop/tool_calls/length у OpenAI,
end_turn/tool_use/max_tokens у Anthropic) — клиенты сравнивают строку
буквально.
Инструменты MCP (protocol: "mcp")
Поставьте protocol в "mcp", и мок будет отвечать на вызов инструмента, а не на
URL. mcp.tool — имя, которое видит клиент, mcp.description — то, что читает
модель, решая, применять ли инструмент, а mcp.input_schema — JSON Schema его
аргументов; объявляйте её. Без неё сервер сможет только вывести схему из ваших
условий, а выведенная схема лишена required и срезает вложенные аргументы —
то есть ровно того, что нужно модели для корректного вызова. Признак
mcp_is_error: true возвращает ошибку инструмента.
{
"protocol": "mcp",
"mcp": {
"tool": "browser_navigate",
"description": "Перейти по URL и вернуть заголовок страницы.",
"input_schema": {
"type": "object",
"properties": { "url": { "type": "string", "description": "Абсолютный URL" } },
"required": ["url"]
}
},
"body": { "content": [ { "type": "text", "text": "Переход на $.req.url" } ] }
}
Клиенты обращаются к мокнутому MCP-серверу по POST /stubs/<пространство> и
находят инструменты через tools/list. Готовые примеры обоих видов лежат в
examples/plugins/ — llm-openai, llm-anthropic и mcp-playwright,
mcp-github и другие.
WASM-фейкер (свой $.fake.*)
Фейкер-плагин добавляет новые генераторы динамических ответов — например валидные
национальные идентификаторы. Логика исполняется как WebAssembly-модуль в
запертой песочнице: читать и писать он может только свою память — ни диска, ни
сети, ни часов. В этом вся модель безопасности: код-плагин не может тронуть
ничего без выданного гранта.
Scaffold:
mockarty-cli plugin create my-fakers --template wasm-faker
Получите манифест, исходник TinyGo и build-скрипт:
{
"contributes": {
"wasm": [
{ "point": "faker-provider", "module": "faker.wasm",
"fn": "faker", "exports": ["my_id"] }
]
}
}
- module — скомпилированный
.wasm-ассет в бандле. - fn — экспортируемая функция, которую вызывает хост.
- exports — имена фейкеров, которые обслуживает функция. Указав
my_id, вы
делаете$.fake.my_idдоступным в любом теле мока.
Контракт гостя
Mockarty передаёт запрошенное имя фейкера и ждёт значение обратно — маленькими
JSON-строками. Модуль экспортирует три вещи:
memory ваша линейная память
mk_alloc(size i32) -> i32 bump-аллокатор, возвращает указатель
<fn>(ptr i32, len i32) -> i64 пакует (outPtr << 32 | outLen) результата
Для точки faker вход — {"name":"<фейкер>"}, выход — {"value":"<строка>"}.
Одна функция обслуживает все имена из exports; ветвитесь по полю name.
Scaffold реализует всё это — вы правите только возвращаемое значение.
Сборка
Продукту для запуска плагина тулчейн не нужен — в нём собственный
WebAssembly-рантайм. TinyGo нужен только чтобы
скомпилировать модуль:
cd my-fakers && ./build.sh # tinygo build -target=wasm-unknown -o faker.wasm .
mockarty-cli plugin pack .
Полноценный пример с валидными контрольными суммами (ИНН/СНИЛС/ОГРН/КПП) есть в
examples/plugins/ru-fakers в поставке продукта — хорошая отправная точка для
настоящих генераторов.
Случайность: у песочницы нет источника энтропии, поэтому засейте небольшой
PRNG, продвигающийся на каждый вызов. Тогда значения меняются от вызова к
вызову (никогда не кэшируются), но детерминированы в пределах запуска сервера —
идеально для воспроизводимых тестовых данных.
WASM-трансформер ответа (переписать тело мока)
Плагин-трансформер пост-обрабатывает отрендеренное тело мока прямо перед
отправкой — маскирует поле, оборачивает в конверт, вырезает токен. Исполняется как
WebAssembly-модуль в той же изолированной песочнице, что и фейкер, inline на
пути ответа, и только для тех моков, которые вы включили тегом.
{
"contributes": {
"wasm": [
{ "point": "response-transformer", "module": "transform.wasm",
"fn": "transform", "exports": ["envelope"] }
]
}
}
- point —
response-transformer. - fn — экспортируемая функция, которую хост вызывает с байтами тела.
- exports — ключи трансформера, которые обслуживает эта функция. Указав
envelope, вы разрешаете моку подключиться тегомtransform:envelope.
Включение на уровне мока. Добавьте моку тег transform:<key>. Его
отрендеренное тело пропускается через трансформер, привязанный к <key>; моки без
тега не платят ничего. Несколько тегов transform: применяются по порядку.
Контракт гостя
Те же три экспорта, что у фейкера, но полезная нагрузка — сырое тело, не JSON:
memory ваша линейная память
mk_alloc(size i32) -> i32 bump-аллокатор, возвращающий указатель
transform(ptr i32, len i32) -> i64 пакует (outPtr << 32 | outLen) нового тела
transform получает байты тела в (ptr,len) и возвращает изменённое тело. Хост
ограничивает передаваемый вход (по умолчанию 1 МиБ) — тело больше отдаётся без
изменений, а не обрезается, поэтому рассчитывайте арену входа под этот лимит.
Трансформер, который ошибся или упал, пропускается, и отдаётся исходное тело — он
никогда не сломает ответ.
Рабочий пример, оборачивающий любое тело в
{"data":<body>,"transformedBy":"plugin"}, есть в
examples/plugins/response-transformer.
WASM-матчер запроса (кастомный оператор условия)
Плагин-матчер добавляет новый оператор условия — проверку, которой нет среди
встроенных (equals, contains, matches, gt/lt, one_of, …): например контрольная
сумма Луна или проверка контрольного разряда национального идентификатора.
Исполняется как WebAssembly-модуль в той же изолированной песочнице и только
для условий, которые его называют — каждый встроенный оператор решается без вызова
вашего модуля.
{
"contributes": {
"wasm": [
{ "point": "condition-matcher", "module": "match.wasm",
"fn": "match", "exports": ["luhn", "divisible_by"] }
]
}
}
- point —
condition-matcher. - exports — ключи операторов, которые обслуживает функция. Указав
luhn, вы
разрешаете условию использоватьassertAction: "plugin:luhn".
Применение на моке. Задайте условию assertAction = plugin:<key>. Его
path выбирает проверяемое значение, value — операнд (для операторов, которым
он нужен, как divisible_by). Условие, чей матчер не установлен или выключен для
пространства, не сработает. Такой запрос не получит ответ от этого мока.
Контракт гостя
Те же три экспорта, что у фейкера. Хост передаёт вызванный ключ плюс значение и
операнд как JSON и ждёт булево:
memory ваша линейная память
mk_alloc(size i32) -> i32 bump-аллокатор, возвращающий указатель
match(ptr i32, len i32) -> i64 пакует (outPtr << 32 | outLen) результата
Вход — {"key":"<key>","actual":"<значение>","expected":"<операнд>"}; выход —
{"match":true|false}. Одна функция обслуживает все ключи из exports;
ветвитесь по полю key. Матчер, который ошибся или упал, считается несовпадением —
он никогда не сломает матчинг запроса.
Рабочий пример (plugin:luhn, plugin:divisible_by) есть в
examples/plugins/condition-matcher.
Кодек протокола (client-first TCP-строки)
Протокольный плагин добавляет безопасный кодек, а не сетевой сервер. Mockarty
владеет listener’ом, маршрутизацией namespace, лимитом фрейма 1 МиБ, разрешением
Socket-мока, журналом запросов, захватом неопределённых запросов и завершением
соединений. Гость только переводит фрейм до перевода строки в JSON-сообщение и
ответ выбранного мока обратно в один фрейм.
mockarty-cli plugin create my-codec --template protocol-codec
./my-codec/build.sh # нужен TinyGo
mockarty-cli plugin test ./my-codec
Манифест связывает один дескриптор ровно с одним WASM-экспортом с тем же ключом:
{
"contributes": {
"protocols": [{
"key": "acme_line", "name": "ACME line", "transport": "tcp-line",
"magic": "ACME "
}],
"wasm": [{
"point": "protocol-codec", "module": "codec.wasm",
"fn": "codec", "exports": ["acme_line"]
}]
}
}
Хост вызывает codec с
{"operation":"decode|encode","protocol":"acme_line","frameBase64":"..."}.
Для decode верните {"message":{...}}, для encode —
{"frameBase64":"..."}. Хост перезаписывает serverName и
pluginProtocol, поэтому гостевой код не может направить запрос в чужой протокол
или стереть атрибуцию. Дедлайн вызова — 100 мс; доступа к диску, сети,
переменным окружения и часам нет.
Запустите Mockarty с MOCKARTY_UNIFIED_PORT=1, включите плагин и выполните
mockarty-cli plugin protocols. Создайте Socket-мок с возвращённым
serverName и событием из decode; в разделе Socket Конструктора этот routing
заполняется из списка активных плагинов. Успешные вызовы попадают в обычный
журнал запросов, а несовпавшие декодированные сообщения — в Undefined Requests,
откуда их можно превратить в мок.
Первый транспорт намеренно ограничен: client-first, печатный ASCII magic длиной
2–16 байт, JSON-сообщение, фрейминг по переводу строки, поток request/reply.
Бинарный фрейминг, server-first handshake и произвольное состояние сокета
требуют нового транспорта хоста; прятать их в гостевом WASM нельзя.
UI-панель (страница в сайдбаре)
UI-плагин добавляет пункт сайдбара, открывающий вашу HTML-страницу. Страница
рендерится в sandboxed iframe из вашего бандла: строгий CSP, без same-origin,
поэтому она никогда не прочитает сессию или страницу хоста. С Mockarty она
общается только через postMessage.
mockarty-cli plugin create my-panel --template ui-panel
{
"contributes": {
"ui": [
{ "point": "sidebar", "id": "my_panel", "title": "My panel",
"icon": "wrench-screwdriver", "panel": "panel.html" }
]
}
}
- icon — имя heroicon (без префикса
hi-). Возьмите существующее, иначе
слот отрисуется пустым. - panel — HTML-файл относительно корня бандла.
Mockarty добавляет текущую тему как ?theme=dark|light, чтобы панель совпадала
со светлой и тёмной. Scaffold panel.html уже учитывает тему и самодостаточен —
превратите его в свой интерфейс. Он следует за переключением темы вживую.
Встраивание панели inline (page-slot)
Кроме сайдбара, ui-вклад может отрисовать панель inline на странице хоста —
point: "page-slot" с target, называющим слот хоста:
{ "point": "page-slot", "id": "ops_slot", "title": "Ops", "target": "dashboard", "panel": "panel.html" }
Слоты есть на 12 экранах, включая dashboard, mocks, tasks (задачи),
test-cases (TCM), api-tester, security, chaos, contract-testing,
wiki; панель появляется там в том же sandboxed iframe.
Помимо сайдбара и page-slot доступны ещё четыре UI-точки, все на том же
sandboxed-iframe механизме:
command— действие в палитре Ctrl-K (категория «Плагины»), открывает
панель плагина с любого экрана;entity-tab— вкладка в карточке сущности:target= тип сущности
(issue— рядом с «Комментарии/История»,mock— рядом с
«Конфигурация/Логи/Версии/Инфо»,case— в билдере тест-кейса рядом с
«Шаги…История»). Плагинные вкладки помечены маленьким значком-пазлом, чтобы
отличаться от встроенных;settings-section— sandboxed-панель плагина внутри модалки
Настроить, под формой, которую хост генерирует изsettings_schema
(JSON Schema);board-import— свой формат диаграмм в пикере импорта на досках
(описан ниже).
Плагин может дать несколько точек из одного panel.html
(см. examples/plugins/ops-panel).
Свой формат диаграмм на досках (board-import)
Вклад board-import добавляет ваш формат в список импорта на доске — рядом с
Mermaid, draw.io и сценой доски, с бейджем Плагин. target — слаг формата:
{ "point": "board-import", "id": "plantuml", "title": "PlantUML",
"target": "plantuml", "icon": "document-text", "panel": "import.html" }
Когда пользователь выбирает ваш пункт, хост загружает panel.html в обычной
песочнице — невидимо, в роли конвертера — и присылает туда введённые данные:
window.addEventListener('message', (e) => {
if (e.data && e.data.type === 'mockarty:board-import-request') {
const mermaid = convert(e.data.text); // ваша конвертация
parent.postMessage({ type: 'mockarty:board-import',
format: 'mermaid', payload: mermaid }, '*');
}
});
Отвечайте format = mermaid, drawio или scene и payload — текстом в
этом формате; в фигуры его превратит сам хост. Чтобы сообщить о проблеме,
ответьте { type: 'mockarty:board-import', error: 'причина' } — пользователь
увидит её текстом. Панель, не ответившая за минуту, снимается.
Внешняя панель (panel_url) — подключите свой сервис
Вместо файла panel из бандла, ui-вклад может указывать на страницу вашего
собственного сервиса — задайте panel_url абсолютным https-URL (требуется
ровно одно из panel / panel_url, не оба сразу):
{ "point": "sidebar", "id": "status_board", "title": "Статус-борд",
"panel_url": "https://plugin.example.com/board" }
Страница грузится в том же sandbox-iframe, что и панель из бандла: получает
подсказку ?theme=, но не имеет ни сессии хоста, ни same-origin доступа.
Используйте это, когда панель — живой продукт со своим бэкендом (дашборды,
SaaS-консоли): ничего не нужно пересобирать при каждом релизе, URL всегда
отдаёт вашу свежую версию. Пользователь видит origin панели в манифесте до
установки.
Вики-макрос (в стиле Confluence, без кода)
Макрос-плагин добавляет новый блок !plugin-<имя>:… на все вики-страницы и в
/-меню редактора — жест макросов Confluence, декларативно:
"contributes": { "wiki_macros": [
{ "name": "plugin-status", "title": "Статус-плашка", "icon": "tag",
"params": [ { "name": "colour", "title": "Цвет", "default": "grey" } ],
"template": "<span data-macro=\"plugin-status\" data-macro-args=\"{{.colour}}\">{{.text}}</span>" } ] }
Использование на странице: !plugin-status:colour=green SHIPPED. Аргументы —
пары key=value (значения с пробелами — в кавычках: key="a b"); остальные
слова приходят как {{.text}}. Префикс plugin- в имени обязателен — макрос
плагина никогда не затенит встроенный.
- template — Go
text/template. Его вывод снова проходит обычный
markdown-конвейер — рендерер Markdown, затем санитайзер — поэтому макрос не может
произвести ничего, что автор не набрал бы руками; скрипты и inline-стили
вырезаются. Вывод ограничен 64 KiB. Непарсящийся шаблон падает при
УСТАНОВКЕ, а не при просмотре страницы. - Закреплённая пара атрибутов
data-macro/data-macro-args(на
div/span) пропускается санитайзером, чтобы хост мог стилизовать вывод —
статус-плашки получают цвета из коробки (grey/green/red/yellow/blue по
первому токену args). - Действует включение per-namespace: там, где плагин выключен, строка макроса
остаётся авторским текстом — видимая деградация, никогда тихая потеря. - Включённые макросы автоматически появляются в
/-меню редактора с меткой 🧩.
Рабочий пример: examples/plugins/wiki-status-macros (статус-плашка и панель
DECISION).
Коннектор (плагин для внешнего инструмента)
Коннектор-плагин расширяет Mockarty к внешнему инструменту — Jira, GitHub,
GitLab, TestRail — без кода. Он поставляет предконфигурированную привязку к
адаптеру интеграции, который у Mockarty уже есть, поэтому установка сразу даёт
готовый коннектор с разумными дефолтами.
mockarty-cli plugin create my-jira --template connector
{
"contributes": {
"connectors": [
{ "key": "jira_cloud", "name": "Jira Cloud", "kind": "jira",
"icon": "bug-ant",
"config_template": { "base_url": "https://your-org.atlassian.net", "project_key": "QA" } }
]
}
}
- kind — встроенный адаптер:
jira,github,gitlab,testrailи другие.
Проверяется при установке; неизвестный kind отклоняется — плагин
переиспользует готовый адаптер, а не поставляет свой. - config_template — дефолты, которыми предзаполняется интеграция при её
создании из коннектора. Никаких секретов здесь — учётные данные вводятся
при настройке.
Включённые коннекторы отдаются в GET /api/v1/plugin-connectors и в UI
«Плагины»; создание интеграции из коннектора использует его kind и
config_template как дефолты, а вызовы идут через встроенный адаптер. Это
паттерн для любого трекера/CI/чата: переиспользуй готовый адаптер, оформи
коннектор, распространяй как плагин. Пример examples/plugins/jira-connector —
полная рабочая отправная точка.
Настройки (форма конфигурации)
Плагин может объявить схему настроек — JSON Schema своих конфигурируемых
опций. При её наличии страница «Плагины» показывает форму Настроить,
сгенерированную из схемы, а сохраняемые значения валидируются против неё.
{
"id": "acme.ops",
"name": "Ops",
"version": "1.0.0",
"settings_schema": {
"type": "object",
"properties": {
"heading": { "type": "string", "title": "Заголовок панели" },
"refresh_seconds": { "type": "integer", "minimum": 1, "maximum": 60 },
"show_clock": { "type": "boolean", "title": "Показывать часы" }
}
},
"contributes": { "ui": [
{ "point": "sidebar", "id": "ops", "title": "Ops", "panel": "panel.html" },
{ "point": "settings-section", "id": "ops_settings", "title": "Превью Ops",
"icon": "wrench-screwdriver", "panel": "panel.html" }
] }
}
Форма поддерживает string, number/integer (с minimum/maximum), boolean и
enum (выпадающий список), плюс title и description. Админская форма
сохраняет дефолт плагина для инстанса; если у пространства есть
переопределение, в нём действует оно. Оба слоя сохраняются при апгрейде.
Для settings-section обязательны settings_schema и ровно один из
panel / HTTPS panel_url; target не нужен. Секция монтируется только пока
плагин активен в текущем пространстве, помечается видимым бейджем Плагин и
работает в той же строгой песочнице, что остальные plugin-панели. Это
дополнительный интерфейс: значения по-прежнему сохраняет форма хоста.
Не храните секреты в настройках. Настройки читаемы любым пользователем,
который может открыть плагин (хост передаёт их панели), поэтому считайте их
публичной конфигурацией — URL, метки, переключатели. Учётные данные — в
secret store, не здесь.
Чтение настроек в панели. Sandboxed-панель не может сама запросить свои
настройки (у неё нет аутентифицированного origin), поэтому хост читает их и
присылает через postMessage при загрузке панели. Bundled-панель получает
значения, действующие в текущем пространстве. Внешний panel_url обязан
объявить permission ui:external-panel; без этого проверенного разрешения
настройки внешнему origin не передаются. Подпишитесь:
window.addEventListener('message', function (ev) {
if (ev.source !== window.parent) return; // доверяем только хосту
if (!ev.data || ev.data.type !== 'mockarty:settings') return;
var s = ev.data.settings || {};
if (s.heading) document.querySelector('h1').textContent = s.heading;
});
Пример examples/plugins/ops-panel делает ровно это.
Подпись (опционально, для регулируемых сред)
Сервер может требовать, чтобы плагины были подписаны ключом, которому доверяют
его операторы. Сгенерируйте пару ключей, подпишите бандл и передайте публичный
ключ оператору сервера:
mockarty-cli plugin keygen --out mykey # mykey (приватный), mykey.pub
mockarty-cli plugin pack my-plugin --sign mykey # подписанный бандл
Оператор настраивает, каким ключам доверять (и обязательна ли подпись).
Неподписанный или недоверенный бандл на таком сервере будет отклонён.
mockarty-cli plugin inspect показывает статус подписи бандла.
Контент-пак — наполнить другой модуль (без кода)
Контент-пак шипит готовый контент для модуля за пределами мокинга и позволяет
юзеру инстанцировать его по требованию в namespace. Сейчас подключены модули Wiki (Confluence-style стартер), Dashboards (виджет-дашборд)
и Issue Tracker (Jira-style стартовый проект с issues); collections — той же формы. Ничего не создаётся, пока пак не инстанцируют, поэтому enable плагина не
трогает данные namespace.
"contributes": { "content_packs": [
{ "key": "team_wiki", "name": "Team wiki starter", "kind": "wiki",
"description": "Runbook, ADR и PRD.",
"items": [
{"title": "Incident runbook", "content": "# Incident runbook\n…",
"children": [{"title": "Postmortem", "content": "# Postmortem\n…"}]},
{"title": "ADR", "content": "# ADR-000\n"}
] } ] }
- kind — какой модуль наполняет пак:
wiki(страницы, вложенность черезchildren)
илиdashboard(виджет-дашборд:items=[{name, description, widgets:[{widgetType, dataSourceId, x, y, w, h, params?}]}]), илиissue(стартовый проект + issues:items=[{name, keyPrefix, description?, issues:[{title, type, description?, priority?}]}], type ∈ task/story/bug/epic), илиcollection(коллекции API-Tester:items=[{name, protocol?, requests:[{name, method?, url, body?}]}]), илиtcm(тест-кейсы с ручными шагами:items=[{name, priority?, expectedResult?, tags?, steps:[{action, expectedResult?}]}]), илиcontract(стартовые API-контракты, публикуемые в реестр:items=[{serviceName, specContent, specType?, version?, description?, tags?}]— тип спеки авто-определяется, если не указан), илиboard(шаблоны досок:items=[{name, description?, tags?, scene?}], гдеscene— обычный Excalidraw JSON; см.examples/plugins/board-templates). Паки, нацеленные на лицензируемый модуль (API Tester, TCM, контрактное тестирование, доски), требуют эту фичу на неймспейсе. - items — kind-специфичный контент (для
wiki— дерево{title, content, children}).
После install + enable пак появляется в каталоге контент-паков
(GET /api/v1/content-packs, или MCP-тул content_pack_list) для namespace, где
плагин активен; инстанцируйте через POST /api/v1/content-packs/<key>/instantiate
(или content_pack_instantiate). Пример: examples/plugins/team-wiki-pack.
Типы связей — отношения сущностей с внешними системами (без кода)
Link-вклад объявляет типизированное отношение между сущностью Mockarty и
другой системой — «у этого issue есть Jira-тикет», «этот мок пришёл из GitHub
issue». URL-шаблон превращает сохранённый ключ в кликабельный deep link (каждый
{key} заменяется), так что связанная сущность — в одном клике от своего
двойника.
"contributes": { "links": [
{ "key": "jira_ticket", "name": "Jira ticket", "icon": "bug-ant",
"from_entity": "issue", "to_entity": "external",
"url_template": "https://your-org.atlassian.net/browse/{key}" } ] }
- from_entity / to_entity — одно из
mock,issue,case,wiki,
collection,dashboard,contract,scan,external. - url_template — опц. абсолютный http(s) URL;
{key}= сохранённое значение.
После включения типы видны в GET /api/v1/entity-link-types (фильтр
?entity=issue) для namespace, где плагин активен, а карточки сущностей
(напр. issue) показывают секцию Внешние связи для добавления/удаления
хранимых связей (POST/GET/DELETE /api/v1/entity-links; MCP entity_link_*).
Пример: examples/plugins/tracker-links.
Типы событий — расширить каталог нотификаций (без кода)
Event-вклад добавляет события, на которые пользователи подписываются
per-channel ровно как на встроенные (email, Telegram, Slack, in-app …). Они
появляются в каталоге нотификаций в категории plugin.
"contributes": { "event_types": [
{ "type": "plugin.deploy.started", "title": "Deploy started", "severity": "info" },
{ "type": "plugin.deploy.failed", "title": "Deploy failed", "severity": "error",
"description": "Деплой упал и требует внимания." } ] }
- type — обязан быть namespaced
plugin.<name>— никогда не затенит
встроенное событие. - severity —
info(дефолт),success,warning,errorилиcritical. - default_subscribed — подписать пользователей по умолчанию (редко).
Пример: examples/plugins/deploy-events.
Отправка событий во время работы
Поверхность плагина с API-токеном — managed worker, внешний процесс, панель
через страницу хоста — публикует объявленное событие так:
curl -X POST "https://your-mockarty/api/v1/plugin-events/<plugin-id>/emit" \
-H "Authorization: Bearer <api-token>" -H "Content-Type: application/json" \
-d '{"eventType":"plugin.deploy.failed","correlationId":"deploy-42",
"payload":{"stage":"migrate"}}'
Принимаются только типы, объявленные в манифесте, только пока плагин
установлен и активен в пространстве имён; payload — JSON-объект до 256 КиБ.
Хост сам проставляет версию конверта, id плагина и необязательную связку
correlationId/causationId — подписчики получают их под этими же ключами.
Типы задач — диспетчеризуемые джобы для внешних раннеров (без кода)
Task-вклад именует новый вид runner-джоба. В паре с внешним раннером,
объявляющим соответствующую capability (стандартный протокол раннеров),
отправленные задачи этого типа диспетчеризуются ему — так вендор поставляет
тяжёлую, внепроцессную функциональность.
"contributes": { "task_types": [
{ "type": "plugin-dbt-run", "name": "dbt run",
"description": "Выполнить dbt-прогон на внешнем раннере." } ] }
- type — строчный slug с namespace
plugin-<name>, максимум 19 символов —
никогда не затенит встроенный тип. Plugin-типы всегда licence-free —
достижимость управляется enable-состоянием самого плагина.
Пример: examples/plugins/custom-runner-tasks.
Импорт из другого инструмента (WireMock / Postman / Mockoon / Bruno / Insomnia / Connect / n8n)
Уже есть библиотека моков в другом инструменте? Превратите её в Mockarty-плагин
одной командой — общая форма «маршрут + метод + статус + JSON-тело» конвертируется
прямо в mock-kit:
mockarty-cli plugin import ./mappings --from wiremock --id acme.legacy-stubs
mockarty-cli plugin import ./collection.json --from postman --id acme.api
mockarty-cli plugin import ./env.json --from mockoon --id acme.env
mockarty-cli plugin import ./my-collection --from bruno --id acme.api
mockarty-cli plugin import ./export.json --from insomnia --id acme.api
- WireMock — один файл-маппинг, файл
{"mappings":[…]}или папка с
.json-файлами по маппингу. ОбрабатываютсяjsonBody/bodyиurlPath/url. - Postman — коллекция v2.1; вложенные папки обходятся, первый сохранённый
пример-ответ каждого запроса становится моком. Переменные пути{{var}}→:var. - Mockoon — экспорт окружения; берётся default- (или первый) ответ маршрута.
- Bruno — папка коллекции с
.bru-файлами (передайте папку). Каждый
запрос становится 200-моком; блокbody:json— телом мока,{{var}}в пути
превращается в:var. Bruno не хранит примеры ответов, так что ответы
начинаются с тела запроса или пустыми — отредактируйте после импорта. - Insomnia — файл экспорта v4, JSON или YAML (Application → Export).
Запросы становятся 200-моками (JSON-тела переносятся); префиксы
{{ _.base_url }}отрезаются, шаблоны в пути становятся:params.
Команда пишет готовый к проверке каталог плагина; дальше pack и install как
обычно. Тело-JSON-объект проходит как есть; массив или скаляр верхнего уровня
оборачивается в {"response": …} (тело kit — объект).
Приложения Atlassian Connect (--from connect)
Connect-приложение — это уже «дескриптор + ваш hosted-сервис + iframe-страницы +
вебхуки», та же форма, что плагин Mockarty с внешними панелями. Дескриптор
конвертируется как есть:
mockarty-cli plugin import ./atlassian-connect.json --from connect
generalPages/adminPages/webSectionsстановятся панелями в сайдбаре,
webPanels— панелями page-slot на дашборде; все открывают ваш существующий
https-сервис черезpanel_url(бэкенд менять не нужно;--idнеобязателен —
выводится из Connectkey).webhooksпревращаются в подписываемые типы событий плагина;scopes
записываются как информационные permissions, видимые при установке.- Не переносится: JWT-хендшейк, workflow-модули и query-плейсхолдеры
{context.param}(query отбрасывается из URL панелей) — панель работает в
sandbox Mockarty, а не в контекст-протоколе Jira.baseUrlдескриптора должен
быть реальным https-URL (шаблонные dev-значения вроде{{localBaseUrl}}
отклоняются с понятной ошибкой).
Просмотрите сгенерированный plugin.json, затем pack и install как обычно.
Ноды сообщества n8n (--from n8n)
Библиотека нод n8n — огромный каталог описаний интеграций. Направьте конвертер
на папку нод (community-пакет или подкаталоги n8n-nodes-base/nodes) — и он
выдаст ОДИН connector-плагин, где каждая нода становится пресетом интеграции:
mockarty-cli plugin import ./nodes --from n8n
- Ноды, совпадающие со встроенным адаптером Mockarty (
github,gitlab,
jira,jenkins,linear), переиспользуют его; остальные становятся
пресетомwebhook_generic, заполненным базовым API-URL ноды, если исходник
его объявляет. - Имя сервиса, описание, категория и ссылка на документацию переносятся;
trigger-варианты складываются в базовую ноду. - Логика исполнения нод и credential-флоу НЕ переносятся — пресет даёт
идентичность сервиса и базовый API, чтобы интеграция создавалась в два
клика; это не runtime воркфлоу.
--id необязателен (по умолчанию n8n.connectors). Просмотрите, pack,
install.
Публикация в реестр
Нет центрального магазина, который бы вас фильтровал. Реестр — это просто
Git-репозиторий с index.json, перечисляющим плагины и указывающим на их
скачиваемые zip’ы — та же модель, что Homebrew tap. Подготовьте запись:
mockarty-cli plugin publish my-plugin-1.0.0.zip \
--download-url https://github.com/you/registry/releases/download/my-plugin-v1.0.0/my-plugin-1.0.0.zip
Добавьте напечатанный объект в index.json реестра, загрузите zip как
релиз-ассет — и плагин станет устанавливаемым по id на любом сервере,
направленном на этот реестр. Стартовый index.json и примеры-плагины
есть в папке examples/plugins/ в поставке продукта.
Сервер vs десктоп
Плагин ведёт себя одинаково на полном сервере и в десктоп-сборке — бандл хранится
и применяется одинаково. WASM-фейкеры и UI-панели работают в обоих. В кластере
включение плагина на одной ноде автоматически распространяется на остальные.
Ничего десктоп- или сервер-специфичного в плагине нет.
Чек-лист
plugin.jsonвалиден (mockarty-cli plugin inspect)- Один понятный раздел
contributesна каждую добавляемую вещь - WASM:
.wasmсобран и упакован; фейкеры перечислены вexports - UI:
iconсуществует; панель учитывает тему; нет предположения о доступе к DOM хоста - Semver поднят на каждое изменение, которое вы выпускаете