Локальная разработка и справочник репозитория

Dee Wan CMS (ดีหวาน 🇹🇭) — многоязычная headless CMS с поддержкой нескольких сайтов. Она целиком работает на Cloudflare: Hono Worker поверх D1 для API и админка на SvelteKit в Pages.
Прежде всего это CMS: определяйте структуру контента, создавайте, публикуйте и редактируйте его. Её отличие от других headless CMS — возможность получить независимый бэкенд. Генерация автономного бэкенда создаёт для сайта ваш собственный репозиторий с нормализованной схемой Prisma, проверяемой историей миграций, которая только пополняется, сгенерированными маршрутами с привычной семантикой запросов Prisma, отдельной базой данных, развёртыванием и учётными данными. Во время выполнения зависимость от Dee Wan отсутствует. Ваш код сохраняется при повторной генерации. Полный переход к работе без Dee Wan тоже поддерживается.
Контент доступен двумя способами. Публичный API CMS отражает изменения сразу после публикации. Автономный бэкенд — независимый релиз схемы и контента: чтобы перенести в него последующие изменения из CMS, нужно явно повторить генерацию и развёртывание.
Предварительная версия: Dee Wan 1.0 — ещё не выпущенный продукт. Приёмка на реальном Cloudflare, финальные браузерные проверки и миграция реальных сайтов ещё не завершены. Что покрыто, а что нет, см. в границах продукта 1.0.
С чего начать:
- Попробуйте локально — аккаунт Cloudflare не нужен.
- Установка Dee Wan — полное руководство для вашего собственного аккаунта Cloudflare.
- Публичный API чтения — как веб-сайт читает опубликованный контент.
- Автономный бэкенд: обзор — что такое сгенерированный бэкенд и как его сгенерировать.
- Расширение вашего бэкенда — работа в сгенерированном автономном бэкенде.
- Миграции базы данных — изменение и применение схемы.
Возможности
Заголовок раздела «Возможности»- Конструктор моделей — определяйте модели контента и поля (текст, форматированный текст, число, логическое значение, выбор, дата, медиа, JSON, связь, компонент) в UI. Модели хранятся как данные, поэтому добавление типа «Recipe» не требует ни кода, ни миграции. Перед применением показываются различия, а изменения, из-за которых существующий контент перестал бы проходить проверку, отмечаются заранее.
- Несколько сайтов — одна установка обслуживает много сайтов. Каждый запрос админки явно указывает сайт, а слой данных ограничивает им все запросы к базе. Если сайт не указан, API отклоняет запрос, чтобы исключить чтение данных другого сайта.
- Многоязычность — настраивайте языки в UI. Контент хранится отдельно для каждого языка.
- Версионирование и публикация — ревизии контента с публикацией и снятием с публикации. Редакционная проверка — необязательный режим для каждого сайта, по умолчанию выключенный: новый сайт публикует прямо из черновика, и никакие элементы управления проверкой не появляются, пока кто-нибудь не включит проверку в разделе Настройки → Процесс.
- Помощь ИИ — генерация и перевод текстов, а также генерация изображений через Worker. Ключ провайдера никогда не попадает в браузер.
- Медиа — загрузка в R2 или Cloudflare Images с автоматическим преобразованием в WebP/AVIF.
- Вход — собственные аккаунты и сессии установки Dee Wan. Первый администратор создаёт аккаунт по одноразовой ссылке активации: с паролем, по magic link, через Google или GitHub. Вход по паролю, magic link, через Google и GitHub никогда не создаёт аккаунты. Cloudflare Access необязателен и по умолчанию выключен. Если администратор сайта добавит правило Access, пользователь с подтверждённой идентификацией Access, соответствующей этому правилу, при входе получит аккаунт и роль на сайте.
- Удалённые плагины — плагин развёртывается как отдельный сервис. Его код не выполняется на
бэкенде Dee Wan и в основном документе админки; собственный интерфейс плагина работает в изолированном
фрейме. Прямого доступа к базе данных нет. Версионируемый HTTP-протокол позволяет работать с контентом,
медиа и процессом согласования в пределах доступа, заданного для сайта в разделе Настройки → Плагины,
и текущих делегированных прав пользователя. Публикацию по расписанию обеспечивает плагин Scheduling
(
plugins/scheduling/), а не ядро. Разработчикам плагинов: написание плагина · протокол v1 · безопасность · тестирование · схема манифеста · схема протокола.
Роль пользователя назначается отдельно для каждого сайта. Встроенные роли:
| Роль | Возможности |
|---|---|
admin |
Всё на сайте: контент, публикация, модели, настройки, люди, роли, плагины, импорт и экспорт. |
editor |
Создавать, редактировать, публиковать, снимать с публикации и одобрять любой контент. Экспорт. Не может менять модели или настройки. |
author |
Создавать контент и читать весь контент, но редактировать только созданный им самим. Не может публиковать, снимать с публикации, одобрять или восстанавливать. |
viewer |
Только чтение. |
Как работает правило для автора:
- Владельцем записи считается создавший её аккаунт. Если создатель не указан, пользователь с ролью автора не может редактировать запись.
- Переводы одной записи образуют группу с основной записью, которая тоже обновляется при каждом сохранении. Поэтому автор может редактировать перевод, только если он создал и сам перевод, и основную запись группы.
- Если автор пытается выполнить действие с чужой записью — отредактировать её или, например,
отправить на проверку, — запрос отклоняется с
not_owner. Редактор скрывает недоступные автору элементы управления.
На суперадминистратора эти ограничения не распространяются.
Архитектура
Заголовок раздела «Архитектура»| Компонент | Стек |
|---|---|
| UI админки | SvelteKit (одностраничное приложение), shadcn-svelte, TipTap — в Cloudflare Pages |
| API админки | Hono на Cloudflare Worker |
| Публичный API чтения | Второй Worker, читающий только опубликованный контент |
| База данных | D1, через Prisma (@prisma/adapter-d1) |
| Медиа | R2 или Cloudflare Images |
| Аутентификация | Сессии Better Auth. Cloudflare Access — необязательно |
| Инференс | DeepInfra |
Репозиторий — это npm workspace. Большинство общих библиотек в packages/ — это git submodules,
поэтому клонируйте с --recurse-submodules. page-contract и plugin-conformance находятся в этом репозитории.
backend/ Admin and public Workers: API, auth, site-scoped data access, AI proxy, Prisma schemafrontend/ SvelteKit admin UIpackages/ submodules: schema-parser, prisma-guard, prisma-rbac, prisma-generator-express, eav-to-prisma, cache, prisma-query-builder-ui. In this repository: page-contract, plugin-conformanceplugins/ remote plugins built against protocol v1 (Scheduling)scripts/ install, deploy, migrate, health, teardownПопробуйте локально, без аккаунта Cloudflare
Заголовок раздела «Попробуйте локально, без аккаунта Cloudflare»Так можно запустить весь продукт на своей машине с локальным файлом D1: без аккаунта, API-токена и DNS. Вам нужны Node и npm, как указано в разделе Требования.
git clone --recurse-submodules https://github.com/dee-wan-cms/dee-wan.gitcd dee-wannode scripts/install-dependencies.mjs # root and backend dependenciesnode scripts/install-dependencies.mjs frontend # admin UI dependenciesnpm run local:migrate # apply the schema to the local databasenpm run local:api # admin Worker on http://localhost:8787 (leave running)npm run local:seed # sample sites and content (in a second terminal)npm run local:admin # admin UI on http://localhost:5173 (leave running)local:seed выводит созданные данные для входа: admin@localhost.test, пароль
local-dev-password-1234. Повторный запуск сообщает, что уже существует, а не создаёт копии.
Команда создаёт два набора данных. Каждый из них можно создать отдельно:
| Команда | Что создаёт |
|---|---|
npm run local:seed:fixture |
Local Dev Site: первый администратор, модель Article, три черновика и одна демонстрационная страница. |
npm run local:seed:corpus |
Два реалистичных вымышленных сайта — Meridian Review, журнал с архивом за восемнадцать месяцев, и Fern & Ash, небольшой сайт студии. Они используют определения тестового блога и содержат около пятидесяти записей в состояниях «опубликовано», «черновик», «на проверке», «одобрено» и «запланировано», со связями, изображениями с альтернативным текстом и переводами на немецкий. Сначала нужно создать администратора из фикстуры. |
Даты в демонстрационном наборе привязаны к моменту его первого создания, поэтому повторные
запуски не меняют даты существующего контента. Изображения берутся из backend/scripts/fixtures/media/, если в этом каталоге
что-то есть. Иначе для набора генерируются простые изображения-заглушки.
Затем в UI админки откройте Контент, отредактируйте одну из созданных статей и опубликуйте её.
Чтобы прочитать опубликованное так, как это сделал бы веб-сайт:
npm run local:public # public read Worker on http://127.0.0.1:8788 (leave running)curl -H "Host: localhost" http://127.0.0.1:8788/v1/articleПубличный Worker определяет сайт по заголовку Host и ни по чему другому, поэтому запрос без него
получает 404. В развёрнутой установке действует то же правило.
Импорт и экспорт контента доступны в разделе Настройки → Перенос данных. Ссылка Отдельный бэкенд на главной панели открывает раздел Настройки → Код и публикация, где подключают репозиторий и управляют развёртыванием. Генерация проекта выполняется отдельно; аккаунт Cloudflare нужен при развёртывании.
Эта локальная среда использует backend/wrangler.dev.jsonc, который отслеживается в репозитории,
не содержит ни учётных данных, ни идентификатора аккаунта и никогда не обращается к Cloudflare.
Установка на Cloudflare
Заголовок раздела «Установка на Cloudflare»Полное руководство, включая автоматические установки, обновление и удаление, — Установка Dee Wan. Ниже краткая версия.
Требования
Заголовок раздела «Требования»- Аккаунт Cloudflare с Workers, D1, Pages и R2 или Cloudflare Images.
- Node 22 (≥22.14) или 24 (≥24.10).
.nvmrcрепозитория закрепляет 24.18.1, так чтоnvm useвыберет её. - npm ровно той версии, которую объявляет
package.json#packageManager(11.16.0), — это npm, поставляемый с Node из.nvmrc. Всегда устанавливайте зависимости командойnode scripts/install-dependencies.mjs, не запускайтеnpm installилиnpm ciнапрямую: она отклоняет запуск с другой версией npm, устанавливает зафиксированное дерево с отключёнными install-скриптами, сверяет каждый скрипт зависимостей сpackage.json#allowScriptsи только затем пересобирает одобренные. - При желании — API-ключ DeepInfra для функций ИИ.
Cloudflare Access (Zero Trust) не требуется. Установка спрашивает, включать ли его. По умолчанию — нет, и тогда вход работает через собственные сессии установки Dee Wan.
Быстрый старт
Заголовок раздела «Быстрый старт»git clone --recurse-submodules https://github.com/dee-wan-cms/dee-wan.gitcd dee-wannode scripts/install-dependencies.mjsnode scripts/install-dependencies.mjs frontend
npm run setup # provisions everything, builds and deploys the admin UI and both Workersnpm run health # checks the deployment answerssetup создаёт базу данных D1 и хранилище медиа, применяет схему, собирает и разворачивает UI
админки, админский Worker и публичный Worker чтения, сохраняет их секреты и предлагает DNS и
необязательный Access. В конце он выводит ссылку активации: откройте её и выберите любой
настроенный способ — пароль, magic link, Google или GitHub. Каждый способ должен подтвердить точный
нормализованный BOOTSTRAP_ADMIN_EMAIL. Ссылка одноразовая и истекает через 15 минут. Если вы её упустили,
снова запустите npm run setup — неиспользованная истёкшая ссылка считается незавершённым шагом,
поэтому запуск выпустит новую, пока инсталляцию никто не активировал.
Последующие изменения кода или конфигурации поставляются командой npm run deploy (см.
Развёртывание).
После клонирования отдельная ручная сборка не нужна: packages/*/dist и клиент Prisma — результаты сборки, и
setup, deploy и migrate:d1 собирают их, когда их нет. npm run doctor — проверка только для
чтения. На свежем клоне она сообщает об отсутствии этих результатов, поэтому запускайте её после
setup или после npm run build:packages && npm run prisma:generate.
Публикация контента (публичный Worker чтения)
Заголовок раздела «Публикация контента (публичный Worker чтения)»Опубликованный контент отдаёт отдельный публичный Worker чтения, настроенный через
backend/wrangler.public.jsonc.
npm run setup записывает этот файл за вас, как только определены аккаунт, зона и
идентификатор базы данных, и никогда не перезаписывает существующий. Если какое-либо из этих
значений невозможно определить, setup сообщает, какого именно. В этом случае подготовьте файл
вручную:
cp backend/wrangler.public.example.jsonc backend/wrangler.public.jsonc# edit backend/wrangler.public.jsonc — account id, D1 id, routenpm run deploydeploy не запускается без этого файла: без публичного Worker опубликованный контент
недоступен для чтения. Если нужна только админка, используйте npm run deploy -- --admin-only.
Такой запуск помечается как admin-only, а не как полное развёртывание.
Нестандартному админскому конфигу (--config) нужно также указать его публичный конфиг, например
npm run deploy -- --config wrangler.v3.jsonc --public-config wrangler.v3.public.jsonc. Оба
конфига читаются, и их значения account_id сравниваются до того, как что-либо будет развёрнуто.
Публичный кеш выключен, пока вы его не включите. Инсталляция по умолчанию задаёт
PUBLIC_CACHE_MAX_AGE=0, и публичный Worker отвечает Cache-Control: no-store, поэтому каждая
публикация, обновление, снятие с публикации или удаление видны при следующем чтении. Чтобы
кешировать, админский Worker должен уметь очищать кеш при публикации, для чего нужны
CF_PURGE_ZONE_ID и CF_PURGE_API_TOKEN с Zone → Cache Purge (см. Секреты). Увеличение PUBLIC_CACHE_MAX_AGE также делает вас зависимым от
Browser Cache TTL зоны: эта настройка увеличивает время хранения в браузере,
если сервер задаёт меньшее значение. При этом очистка кеша Cloudflare не очищает кеш браузера. Прежде чем полагаться на кеш,
установите Browser Cache TTL в Respect Existing Headers.
Учётные данные
Заголовок раздела «Учётные данные»setup нужен API-токен Cloudflare. Он запрашивает его, если токен не задан, и выводит точный
список прав доступа с причиной для каждого. В Установке Dee Wan
отмечено, какие права зависят от выбранных настроек, например от собственного домена или Access.
npm run doctor выводит тот же список.
export CLOUDFLARE_API_TOKEN=… # optional — skips the promptexport CLOUDFLARE_ACCOUNT_ID=… # optional — skips the account pickerСамому setup больше не нужна никакая переменная окружения. Секреты приложения задаются через
wrangler secret put (ниже), никогда не в файле. Необязательные переменные, которые используют
другие команды:
| Переменная | Кто использует |
|---|---|
DEEWAN_RESOURCE_PREFIX |
setup — помещает каждое имя ресурса в пространство имён. См. общий аккаунт. |
DEEWAN_YES=1 или CI=true |
setup, install:cms — никогда не задавать вопросов. Брать значения по умолчанию или завершаться ошибкой. |
D1_PLAN (free или paid) |
Автоматические установки — отвечает на вопрос о тарифе D1. |
DEEWAN_CF_OAUTH_CLIENT_ID, DEEWAN_CF_OAUTH_REDIRECT |
install:web — вход в Cloudflare. Без идентификатора клиента веб-установщик не может подключиться к Cloudflare. |
DEEWAN_INSTALL_PORT |
install:web, install:web:dry-run — порт на 127.0.0.1. |
CF_ACCESS_CLIENT_ID, CF_ACCESS_CLIENT_SECRET |
health — сервисный токен Cloudflare Access для проверки периметра Access, когда Access включён. |
DEE_WAN_ADMIN_COOKIE |
provision:site, acceptance — cookie админской сессии для проверок, которым она нужна. |
DEEWAN_GITHUB_CLIENT_ID |
wire:github, backend:deliver — OAuth-клиент GitHub, используемый для входа через устройство. |
VERIFY_ADMIN_URL, VERIFY_PUBLIC_URL, VERIFY_EMAIL, VERIFY_PASSWORD |
verify (пароль читается только из окружения). |
DEEWAN_ACCEPTANCE_ADMIN_EMAIL, DEEWAN_ACCEPTANCE_SITE_ID, DEEWAN_PUBLIC_HOST, DEEWAN_PUBLIC_STRANGER_HOST |
acceptance. |
DEEWAN_SMOKE_PAGES, DEEWAN_SMOKE_HOST |
Smoke-тест развёртывания. |
PUBLIC_BACKEND_URL |
local:admin — API админки, с которым общается локальный UI (по умолчанию http://localhost:8787). |
Секреты
Заголовок раздела «Секреты»Секреты никогда не хранятся в wrangler.jsonc или в базе данных. Задавайте их из каталога
backend/:
npx wrangler secret put DEEPINFRA_API_KEY # AI text and image generationОбычные настройки (не секреты) задаются в vars в backend/wrangler.jsonc и вступают в силу при
следующем npm run deploy. Всё ниже необязательно. Функция, настройки которой отсутствуют,
выключена.
| Имя | Вид | Что включает |
|---|---|---|
DEEPINFRA_API_KEY |
secret | Подключение DeepInfra по умолчанию для текста, изображений и структурированной генерации. |
AI_RATE_LIMIT_PER_HOUR |
var | Число вызовов ИИ, разрешённых одному сайту в час. По умолчанию 250. |
CF_IMAGES_API_TOKEN |
secret | Загрузки, когда STORAGE_PROVIDER равен cf-images. |
R2_PUBLIC_DOMAIN |
var | Публичный адрес, с которого отдаются медиа из R2. Его записывает setup. Медиа в R2 без него не работают. |
CF_PURGE_ZONE_ID |
var | Очистка edge-кеша при публикации, вместе с CF_PURGE_API_TOKEN. Только если вы увеличиваете PUBLIC_CACHE_MAX_AGE. |
CF_PURGE_API_TOKEN |
secret | Токен с Zone → Cache Purge для этой зоны. |
RESEND_API_KEY |
secret | Вход через magic link, письма и коды приглашений, а также доставка через Resend ссылок для установки пароля, выпущенных администратором. Нужен MAIL_FROM. Если хотя бы одна из двух настроек отсутствует, magic link недоступен, а ссылки приглашений и пароля показываются администратору для передачи. |
MAIL_FROM |
var | Адрес отправителя, например Dee Wan <cms@example.com>. Должен быть подтверждённым отправителем в Resend или в вашем почтовом транспорте. |
MAIL_TRANSPORT_URL |
var | Адрес https:// вашего почтового сервиса. Если задан, заменяет Resend для каждого сообщения. Нужны MAIL_TRANSPORT_TOKEN и MAIL_FROM; если хотя бы одна из двух настроек отсутствует, почта помечается как ненастроенная, а ссылки показываются для передачи. Адрес не https или не публичный отклоняется. См. Транспорт почты. |
MAIL_TRANSPORT_TOKEN |
secret | Bearer-токен, который Dee Wan отправляет на MAIL_TRANSPORT_URL. |
AUTH_INITIAL_METHODS |
var | Доступные до создания первого администратора способы входа, через запятую: password, magic_link, google, github. По умолчанию password. Способ входа остаётся недоступным без обязательных привязок. |
GOOGLE_CLIENT_ID |
var | OAuth-клиент Google для входа в CMS. Callback: BACKEND_URL/api/auth/callback/google. |
GOOGLE_CLIENT_SECRET |
secret | Секрет OAuth-клиента Google для входа. |
GITHUB_CLIENT_ID |
var | OAuth App GitHub для входа в CMS. Callback: BACKEND_URL/api/auth/callback/github. Не относится к передаче исходников и входу через устройство в CLI. |
GITHUB_CLIENT_SECRET |
secret | Секрет OAuth-приложения GitHub для входа. |
GITHUB_APP_ID, GITHUB_APP_SLUG, GITHUB_APP_CLIENT_ID |
var | GitHub App для Настройки → Код и публикация (подключение репозитория и управление развёртыванием). Без всех пяти значений GITHUB_APP_* этот экран сообщает github_app_not_configured. |
GITHUB_APP_CLIENT_SECRET, GITHUB_APP_PRIVATE_KEY |
secret | Остальные настройки этого GitHub App. |
PLUGIN_CATALOGUE_URLS |
var | Блок «Каталог плагинов» в разделе Настройки → Плагины. URL каталогов плагинов через запятую, только https://, читаются не больше 4. По умолчанию нет ни одного. Запись в каталоге ничего не разрешает: установка всё равно проверяет манифест. См. docs/plugins/security.md. |
OPERATOR_SENTRY_DSN |
secret | Отправляет очищенные отчёты об ошибках в ваш собственный проект Sentry. |
DEEWAN_ERROR_REPORTING |
var | off прекращает отправку отчётов в проект Sentry Dee Wan. |
TEMPLATE_ARTIFACTS |
binding | Шаблоны сайта: приватный бакет R2, привязанный в r2_buckets, где хранятся исходники, скомпилированный результат и отчёт о проверках каждого кандидата шаблона. Для принятия шаблона нужен только он. См. docs/MANAGED-TEMPLATES.md. |
TEMPLATE_BUILD_SERVICE_URL |
var | Загрузка шаблона сайта, а с DEEPINFRA_API_KEY — и его генерация. Origin хоста, на котором запущен npm run template-build-service: голый origin https:// или http:// на localhost, 127.0.0.1 или [::1]. Также нужны TEMPLATE_BUILD_SERVICE_KEY, TEMPLATE_SANDBOX_IMAGE и TEMPLATE_ARTIFACTS. |
TEMPLATE_BUILD_SERVICE_KEY |
secret | Подписывает каждый запрос на сборку. Хост сборки хранит то же значение. Ключ короче 32 символов считается незаданным. |
TEMPLATE_PREVIEW_SERVICE_URL |
var | Предпросмотр кандидата шаблона сайта. Origin хоста предпросмотра, по тем же правилам. Один хост может обслуживать оба. Также нужны TEMPLATE_PREVIEW_SERVICE_KEY, TEMPLATE_SANDBOX_IMAGE и TEMPLATE_ARTIFACTS. |
TEMPLATE_PREVIEW_SERVICE_KEY |
secret | Подписывает каждый запрос на предпросмотр. Хост предпросмотра хранит то же значение. Ключ короче 32 символов считается незаданным. |
TEMPLATE_SANDBOX_IMAGE |
var | Образ контейнера, в котором хосты сборки и предпросмотра запускают сгенерированный код, закреплённый по дайджесту (<repository>@sha256:<64 hex>). Тег считается незаданным значением. Нужен для загрузки, генерации и предпросмотра. |
MANAGED_RENDER_HMAC_KEY |
secret | Подписывает точный рендер дизайна сайта в редакторе страниц. Ключ короче 32 символов считается незаданным. Этому рендеру нужен ещё активный релиз управляемого рендерера; без него сайт видит Structure preview. Он нужен и для подготовки, активации и отката релиза: админка отправляет сервису развёртывания только ключ каждого сайта, выведенный из него. Никогда не передавайте это значение рендереру сайта или сервису развёртывания. |
MANAGED_RENDER_DEPLOY_SERVICE_URL |
var | Подготовка, активация и откат релиза управляемого рендерера. Origin хоста, на котором запущен npm run managed-deploy-service: голый origin https:// или http:// на localhost, 127.0.0.1 или [::1]. Также нужны MANAGED_RENDER_DEPLOY_SERVICE_KEY, MANAGED_RENDER_INSTALLATION_ID, MANAGED_RENDER_HMAC_KEY, TEMPLATE_ARTIFACTS и активная привязка сайта. |
MANAGED_RENDER_DEPLOY_SERVICE_KEY |
secret | Подписывает каждый запрос на развёртывание и проверяет каждую квитанцию. Хост развёртывания хранит то же значение. Не менее 32 символов. |
MANAGED_RENDER_INSTALLATION_ID |
var | Стабильный несекретный идентификатор этой инсталляции: строчные латинские буквы, цифры и дефисы, от 3 до 63 символов. Из него и идентификатора сайта выводится имя Worker рендерера каждого сайта. Никогда не меняйте его после первого релиза. |
Если для генерации, загрузки, предпросмотра или принятия шаблона сайта не хватает настроек,
операция возвращает 503 template_operation_unavailable с названием операции и именами
недостающих настроек, но без их значений. Настройки сборки и предпросмотра не запускают
развёртывание, а принятие шаблона не меняет работающий сайт. Релизы разворачивает отдельный
сервис, который хранит API-токен Cloudflare; админский Worker никогда его не хранит.
Если развёртывание не настроено, в настройках перечислены недостающие настройки. См. docs/MANAGED-TEMPLATES.md.
API админки обращается к провайдерам ИИ только через /api/ai/* на Worker, который хранит ключ и
применяет почасовую квоту.
Отчёты об ошибках. О необработанных ошибках можно сообщать с типом ошибки, очищенным
сообщением, кадрами стека и маршрутом — никогда с контентом, значениями форм, адресами email или
учётными данными. Если задан OPERATOR_SENTRY_DSN, они отправляются в ваш проект Sentry. Эта сборка
не содержит адреса Sentry Dee Wan, поэтому в Dee Wan ничего не отправляется, если не задан
DEEWAN_SENTRY_DSN. Тогда Установка → Отчёты об ошибках (только для
суперадминистраторов) показывает «В этой сборке нет адреса Dee Wan. Ничего не отправляется.»
Переключатель на этом экране и DEEWAN_ERROR_REPORTING=off влияют только на отчёты в Dee Wan, но не
на ваш собственный DSN. Секрет принадлежит одному Worker: чтобы отправлялись и ошибки публичного
Worker чтения, задайте DEEWAN_SENTRY_DSN или OPERATOR_SENTRY_DSN и на нём
(npx wrangler secret put <NAME> --config wrangler.public.jsonc).
Развёртывание
Заголовок раздела «Развёртывание»npm run deploy # admin UI (Pages), admin Worker, and the public read Workernpm run health # post-deploy checks; with Access on, also checks the perimeterdeploy собирает UI админки с PUBLIC_BACKEND_URL, заданным в окружении сборки равным
FRONTEND_URL из backend/wrangler.jsonc. frontend/.env ему не нужен. Он не разворачивает
админский Worker, если есть неприменённые миграции или если проверить их наличие не удалось, —
сначала примените их через npm run migrate:d1 или передайте --skip-migration-check, если вы
знаете, что новому коду они не нужны.
Обновление до более нового релиза (pull, переустановка, миграция, развёртывание) пошагово описано в Установке Dee Wan.
Миграции
Заголовок раздела «Миграции»Свежей установке ничего из этого не нужно — setup применяет схему. В дальнейшем:
| Команда | К чему применяется |
|---|---|
npm run migrate:d1 |
К развёрнутой управляющей базе данных. |
npm run tenant:upgrade |
К базам данных отдельных сайтов, созданным provision:site. |
npm run local:migrate |
К базе данных локальной среды (backend/wrangler.dev.jsonc). Работает на любом клоне. |
npm run migrate:d1:local |
К локальному зеркалу установленной конфигурации. Читает backend/wrangler.jsonc, поэтому завершается ошибкой на клоне, который никогда не устанавливался. |
Миграции — это миграции Prisma в backend/prisma/migrations/, зеркалируемые для D1 в
backend/migrations-wrangler/ командой npm run migrate:sync (npm run migrate:check проверяет
зеркало). schema.prisma собирается из файлов-фрагментов. Редактируйте фрагменты. Применение
не создаёт миграций. Создание миграции — отдельный явный шаг. Полный процесс см. в
Миграциях базы данных.
Поставщик ИИ
Заголовок раздела «Поставщик ИИ»По умолчанию используется DeepInfra. Чтобы подключить другого поставщика с API, совместимым с
OpenAI, задайте переменные установки ниже и сохраните ключ командой
wrangler secret put AI_API_KEY из каталога backend/:
| Переменная | Значение |
|---|---|
AI_PROVIDER |
openai-compatible |
AI_BASE_URL |
Базовый адрес API, например https://inference.example/v1; без учётных данных, параметров запроса и фрагмента |
AI_TEXT_MODEL |
Модель чата по умолчанию; используется также для перевода и генерации структурированных данных |
AI_VISION_MODEL |
Необязательная модель с поддержкой изображений для описания медиафайлов |
AI_IMAGE_MODEL |
Необязательная модель генерации изображений; без неё эта функция недоступна |
AI_TEXT_MODELS, AI_VISION_MODELS, AI_IMAGE_MODELS |
Дополнительные идентификаторы моделей через запятую для каждого списка; не более 100 на тип задачи, включая модель по умолчанию |
Для совместимого поставщика списки берутся из этой проверенной конфигурации: Dee Wan не пытается
угадать возможности моделей по произвольному каталогу. Неизвестные цены не показываются. Указывайте
точные идентификаторы доступных у поставщика моделей. Генерация изображений запрашивает встроенные
данные b64_json; поставщики, возвращающие только адреса изображений, этим подключением не
поддерживаются. Нужен HTTPS; исключение — localhost при локальной разработке. Клиентский запрос
не может изменить адрес поставщика или ключ.
В Настройки → Генерация выбирают одну из настроенных моделей. Ключи остаются секретами Worker и не попадают в настройки сайта, запросы браузера или генерируемый код. Оба подключения используют одну квоту сайта, проверку результата перед применением и обработку ошибок. Смена поставщика не перезаписывает сохранённые модели: выберите идентификаторы, доступные у нового поставщика.
Импорт и экспорт контента
Заголовок раздела «Импорт и экспорт контента»Обе функции находятся в админке в разделе Настройки → Перенос данных — без терминала и без правки JSON.
- Импорт: архив экспорта Strapi (
.tar/.tar.gz, проверено на Strapi 4.25.9 и 5.51.1) или копия сайта Dee Wan (.json), экспортированная из другой инсталляции. Мастер изучает архив, показывает найденное, сопоставляет типы и языки, проверяет план, затем выполняет его. Ничего не перезаписывается: импорт создаёт контент или пропускает его, и всё задание можно отменить. - Экспорт: Скачать копию сайта записывает один файл с моделями, полями, языками, процессами, настройками и текущим контентом сайта, опубликованной версией там, где она отличается, и точными байтами каждого изображения. Если какое-либо изображение невозможно точно прочитать из R2 или Cloudflare Images, скачивание отклоняется с указанием изображения, которое не удалось прочитать; неполная копия не создаётся.
- Электронные таблицы позволяют обновлять существующие записи из списка контента одной модели.
Импорту архивов нужен приватный IMPORT_BUCKET. npm run setup предлагает создать его, а
npm run doctor сообщает, включён ли он. Всё остальное работает без него. Импорт реализован, но
для 1.0 ещё не завершён. См. границы продукта 1.0.
Вход и восстановление доступа
Заголовок раздела «Вход и восстановление доступа»Публичная регистрация закрыта. Учётные записи создаются только при наличии соответствующих полномочий. Если отправка почты настроена, пользователь с существующей учётной записью может запросить ссылку для сброса пароля на экране входа. Пароль задаёт владелец учётной записи через одноразовую ссылку, а не тот, кто её отправил.
Каждая ссылка ниже одноразовая, истекает через 15 минут и показывается ровно один раз. Хранится только её хеш, поэтому повторно показать её невозможно. Выпуск новой аннулирует предыдущую. Пароль должен содержать не менее 12 символов.
Активация новой установки
Заголовок раздела «Активация новой установки»Установщик выводит ссылку активации в конце свежей установки. Откройте её и выберите любой из настроенных
способов входа: пароль, magic link, Google или GitHub. Каждый способ должен подтвердить точный нормализованный адрес
из BOOTSTRAP_ADMIN_EMAIL. На странице активации заменить этот адрес нельзя. После входа владелец может изменить email
учётной записи в разделе Безопасность аккаунта, подтвердив новый адрес.
Если ссылка истекла неиспользованной, снова запустите установщик (npm run setup или
npm run install:cms -- resume). Он выпустит новую, пока инсталляцию никто не активировал.
Для ещё не активированной установки явно замените потерянную или истёкшую ссылку:
npm run claim:link -- --remote --bootstrap --email owner@example.comБез --bootstrap команда восстанавливает владельцу доступ к существующему аккаунту
единственного активного суперадминистратора. Если такого аккаунта нет, команда отклоняется. См.
Создание и замена первого администратора.
Кто-то забыл пароль
Заголовок раздела «Кто-то забыл пароль»Суперадминистратор создаёт ссылку из списка Люди. Если почта настроена (RESEND_API_KEY и
MAIL_FROM), ссылка также отправляется по email. В любом случае она один раз показывается
суперадминистратору, чтобы он передал её любым удобным способом.
Последний суперадминистратор потерял доступ
Заголовок раздела «Последний суперадминистратор потерял доступ»Это единственный случай, который никто другой не может исправить, поэтому нужны учётные данные развёртывания:
npm run claim:link -- --remote --email locked-out@example.comКоманда отклоняется, если адрес не принадлежит единственному активному суперадминистратору. Если их двое, второй должен вместо этого выпустить ссылку из списка Люди.
Ваша учётная запись
Заголовок раздела «Ваша учётная запись»Каждый вошедший пользователь может открыть Безопасность аккаунта через меню учётной записи, чтобы сменить язык админки, отвязать собственное подключение Google или GitHub и управлять своими ключами доступа. См. Безопасность учётной записи.
Разработка
Заголовок раздела «Разработка»cd backend && npm test # backend unit tests (Vitest, local SQLite)cd frontend && npm test -- --run # admin UI unit tests (Vitest, single run)cd frontend && npm run check # admin UI type checknpm run lint # lint the backend and scriptsnpm run typecheck:scripts # type check scripts/npm run submodules:check # fail if a submodule pin or worktree has driftedЧтобы запустить локальный админский Worker из установленного backend/wrangler.jsonc вместо
отслеживаемого dev-конфига, используйте npm run dev:install --workspace backend.
Команды
Заголовок раздела «Команды»Установка и эксплуатация:
| Команда | Что делает |
|---|---|
npm run doctor |
Предварительная проверка только для чтения: называет, чего не хватает и как это исправить. |
npm run plan |
Только чтение: каждый шаг установки и его состояние по файлам на диске. -- --json — для программной обработки. |
npm run setup |
Интерактивная установка: создаёт ресурсы, применяет схему, собирает и разворачивает всё, выводит ссылку активации. |
npm run install:cms -- <verb> |
Установщик с подкомандами: plan, apply, resume, doctor, verify, teardown, manifest. |
npm run install:web |
Веб-установщик на 127.0.0.1. Не является поддерживаемым путём установки: ему нужно ваше собственное приложение Cloudflare OAuth (DEEWAN_CF_OAUTH_CLIENT_ID). См. Предпросмотр веб-установщика. |
npm run install:web:dry-run |
Веб-установщик, в котором все обращения к внешним системам имитируются, — для проверки. Не изменяет внешние ресурсы. |
npm run deploy |
Собирает и разворачивает UI админки и оба Worker. --config, --public-config, --admin-only, --skip-migration-check. |
npm run health |
Проверки после развёртывания по развёрнутым адресам. |
npm run probe |
Анонимная проверка доступности только для чтения. |
npm run verify -- --read-only --public <url> |
Проверка после развёртывания, которая ничего не записывает. --model <slug> читает одну модель. |
npm run verify -- --admin <url> --public <url> --email <addr> --disposable |
Полная проверка одноразового развёртывания: создаёт сайт, модель и контент. Нужен VERIFY_PASSWORD. |
npm run migrate:d1 |
Применяет ожидающие миграции к развёрнутой управляющей базе данных. |
npm run migrate:d1:local |
То же для локального зеркала установленной конфигурации. |
npm run provision:site -- --site <id> |
Даёт одному сайту собственную базу данных D1 и повторно разворачивает оба Worker. --plan — для предварительного просмотра, --repair — для перепроверки. Платно. |
npm run tenant:upgrade |
Применяет ожидающие изменения схемы отдельных сайтов к каждой активной базе данных сайта. --plan, --site <id>. |
npm run claim:link |
Выпускает одноразовую ссылку пароля для единственного активного суперадминистратора. --bootstrap заменяет ссылку первого администратора в ещё не активированной установке. |
npm run revoke:sessions |
Аварийная мера: завершает сессии всех пользователей. |
npm run teardown |
Удаляет то, что setup записал как созданное инсталляцией; по умолчанию backend/wrangler.jsonc и его конфигурации публичного Worker и приёма форм. Пробный прогон, если не указан --apply. Сохраняет подключённые существующие ресурсы, а также изменённые ресурсы и те, состояние которых не удалось прочитать. См. Начать заново или удалить инсталляцию. |
npm run backend:generate -- --site <id> --source <cms.sqlite> --dest <dir> --db <scratch.sqlite> |
Локально генерирует автономный бэкенд из SQLite-копии базы данных CMS. Ничего не разворачивает. Сайту с активным управляемым релизом рендерера нужен ещё --template-artifacts <dir> — локальная копия артефактов шаблона этого релиза; релиз экспортируется в managed-renderer/. |
npm run backend:generate:all |
То же для каждого сайта. Ничего не разворачивает. Не может передать --template-artifacts, поэтому генерация для сайта с активным релизом управляемого рендерера отклоняется. Запустите для этого сайта отдельно npm run backend:generate с --template-artifacts <dir>. |
npm run template-build-service |
Запускает сервис сборки и предпросмотра управляемых шаблонов на хосте сборки. Каждая сборка и каждый предпросмотр идут в новом контейнере образа песочницы, закреплённого по дайджесту. Сервис не запускается без ключа сборки или предпросмотра длиной не менее 32 символов, origin в TEMPLATE_SERVICE_AUDIENCE и допустимого значения каждой настройки TEMPLATE_SANDBOX_*, и отказывается работать от root. Он обслуживает запросы по обычному HTTP на TEMPLATE_SERVICE_HOST:TEMPLATE_SERVICE_PORT (по умолчанию 127.0.0.1:8790) и выполняет одну операцию за раз. Админка обращается только к origin https:// (или http:// на loopback), поэтому удалённому хосту нужен TLS перед ним. Не хранит учётных данных развёртывания. Ничего не разворачивает. См. docs/MANAGED-TEMPLATES.md. |
npm run template-sandbox:context -- --out <dir> --node-image <node@sha256:…> |
Записывает контекст сборки образа песочницы шаблонов. --out должен быть новым или пустым каталогом, а --node-image должен указывать образ по дайджесту. Версии npm, Wrangler, playwright-core и axe-core читаются из этого checkout, поэтому сначала установите зависимости. Ничего не собирает, не скачивает и не публикует. |
npm run managed-deploy-service |
Запускает сервис развёртывания управляемого рендерера на доверенном хосте. Он хранит API-токен Cloudflare, которым загружает, разворачивает и маршрутизирует Worker рендерера каждого сайта; админский Worker такого токена никогда не хранит. Сервис также получает идентификационные данные каждого рендерера для проверки в админке. Сервис не запускается, если обязательная настройка отсутствует или недопустима. См. docs/MANAGED-TEMPLATES.md. |
npm run wire:github -- --repo <owner/name> |
Отправляет сгенерированный проект в GitHub и сохраняет в зашифрованном виде секреты для его workflow сборки. |
npm run backend:deliver -- --site <id> --repo <owner/name> --source <cms.db> --dest <dir> --db <target.db> |
Передаёт сайт в виде вашего собственного репозитория и разворачивает его там. --media также переносит опубликованные медиа в ваш R2. Принимает --template-artifacts <dir>, как backend:generate. Управляемый рендерер не разворачивает. |
curl -H "X-Site-Id: <id>" -b "<session>" <admin>/api/models/public-client.ts |
Скачивает типизированный TypeScript-клиент без зависимостей для публичного API чтения сайта. |
Локальная среда:
| Команда | Что делает |
|---|---|
npm run local:migrate |
Применяет схему к локальной базе данных. Без учётных данных. |
npm run local:api |
Админский Worker на http://localhost:8787. |
npm run local:public |
Публичный Worker чтения на http://127.0.0.1:8788. |
npm run local:seed |
Сайт-фикстура плюс реалистичный корпус. Идемпотентна. |
npm run local:seed:fixture |
Только сайт-фикстура. |
npm run local:seed:corpus |
Только корпус. |
npm run local:admin |
UI админки на http://localhost:5173 поверх локального Worker. |
Контрибьюторам:
| Команда | Что делает |
|---|---|
npm run test:boot |
Запускает оба Worker под зафиксированной средой выполнения и завершается ошибкой, если любой из них не запускается. |
npm run test:e2e |
Браузерный end-to-end набор (Cypress) на dev-сервере, который он сам запускает. E2E_REAL_BACKEND=1 добавляет настоящие Workers. Тогда спецификация полноэкранного редактора получает и сгенерированный Astro-рендерер на E2E_EDITOR_PORT (по умолчанию 43189). Первой сборке нужен npm registry. Спецификация релизов Scheduling также получает плагин Scheduling на E2E_PLUGIN_PORT (по умолчанию 43190). |
npm run migrate:sync |
Перегенерирует backend/migrations-wrangler/ из миграций Prisma. |
npm run migrate:check |
Завершается ошибкой, если это зеркало устарело. |
npm run prisma:generate |
Пересобирает schema.prisma из фрагментов и перегенерирует клиент Prisma. |
npm run prisma:drift |
Завершается ошибкой, если schema.prisma больше не описывает то, что строят миграции. |
npm run tenant:schema |
Перегенерирует схему, с которой начинают новые базы данных сайтов. |
npm run tenant:schema:check |
Завершается ошибкой, если эта схема устарела. |
npm run docs:api |
Перегенерирует docs/PUBLIC-API.md. |
npm run docs:plugins |
Перегенерирует документ протокола плагинов и JSON Schema. |
npm run contract:embed |
Перегенерирует встроенный контракт страницы после правки backend/src/lib/page-contract/. |
npm run submodules:sync |
Инициализирует или обновляет submodules в packages/. |
npm run submodules:check |
Завершается ошибкой, если закреплённая версия или рабочее дерево подмодуля отличается от ожидаемого. |
npm run lint |
Линтит бэкенд и скрипты. |
npm run typecheck:scripts |
Проверяет типы в scripts/. |
npm run test:scripts-offline |
Запускает офлайн-проверки сгенерированного кода развёртывания и аутентификации через удаление типов в Node. |
npm run guard-editor:check |
Проверяет типы пакета редактора guard и его контракт с CMS. |
npm run guard-editor:test |
Запускает тесты пакета редактора guard и проверку его контракта. |
npm run test:guard-editor-contract |
Собирает ядро редактора guard и запускает только проверку его контракта. Это запускает CI. |
npm run acceptance -- plan |
Выводит путь релизной приёмки. Ничего не запускает. |
npm run acceptance -- run --account <id> --confirm-disposable |
Проходит этот путь на одноразовом аккаунте Cloudflare и записывает журнал (ledger). Создаёт платные ресурсы и оставляет их. |
npm run acceptance -- report --account <id> |
Выводит этот журнал. |
npm run public:query-probe -- --confirm-disposable … |
Проверяет фильтры публичного API на одноразовом развёртывании, затем восстанавливает то, что изменила. Никогда не направляйте её на продакшн. |
Участие в разработке
Заголовок раздела «Участие в разработке»PR приветствуются. Два соглашения:
packages/— это submodules: сначала коммитьте и отправляйте изменения там, затем обновляйте указатель.- Маршруты, привязанные к сайту, должны проходить через слой данных с привязкой к сайту, который добавляет сайт в каждый запрос. Обход этого слоя — именно так данные одного сайта утекают в другой.
Запуск установщика в общем аккаунте
Заголовок раздела «Запуск установщика в общем аккаунте»Имена ресурсов по умолчанию (dee-wan-cms-backend, dee-wan-cms-db и так далее) фиксированы,
поэтому две установки в одном аккаунте Cloudflare конфликтуют по именам.
Задайте DEEWAN_RESOURCE_PREFIX, чтобы поместить всё, что создаёт запуск, в отдельное пространство
имён:
DEEWAN_RESOURCE_PREFIX=test-a1b2 npm run setup # creates test-a1b2-dee-wan-cms-db, …Префикс должен состоять из строчных латинских букв, цифр и дефисов, не длиннее 24 символов. При удалении установки ресурсы не выбираются по имени: удаляются только ресурсы, которые, согласно записи об установке, были созданы именно ею. См. Начать заново или удалить инсталляцию.
Задавайте префикс при новой установке, а не для существующей. Wrangler находит базу данных D1 по
имени в backend/wrangler.jsonc, поэтому префикс работает только после того, как setup записал в
этот файл имена с префиксом. Если включить его для инсталляции, конфиг которой содержит имена без
префикса, каждая команда будет завершаться ошибкой
Couldn't find a D1 DB with the name or binding '<prefix>-…'. Чтобы перевести существующую
инсталляцию под префикс, перезапустите setup.
Smoke-тест развёртывания
Заголовок раздела «Smoke-тест развёртывания»Короткая проверка работающей установки после каждого развёртывания:
DEEWAN_SMOKE_PAGES=https://admin.example.com \DEEWAN_SMOKE_HOST=https://cms.example.com \npm run smoke --workspace=dee-wan-cms-backendЗамените оба адреса origin-адресами вашего UI админки и API админки. Сессия не нужна, проверяются две вещи:
- UI админки отдаёт приложение для глубоких ссылок, а не только для
/— неправильный_routes.jsonломает их первыми, и никто этого не замечает, переходя с главной страницы. - при включённом Access
/api/*на публичном хосте находится за Access. При выключенном Access (по умолчанию) API защищён входом, и эта проверка неприменима.
Если адреса не указаны, тест пропускается, а не падает.
Лицензия
Заголовок раздела «Лицензия»MIT. Copyright (c) 2026. Полный текст — в LICENSE.
Сделано в Таиланде 🇹🇭 для глобальной edge-сети
Стабильная версия ещё не выпущена (pre-MVP)
Коммит исходного кода: fe23db481c422b40e317b92fc98116b4c0c26a87 (с локальными изменениями)
Дата сборки (UTC): 2026-10-18T09:22:34.630Z
Коммит для обновления: не определён: в рабочей копии есть изменения. Примечания к выпуску