Перейти к содержимому
Версия 1.0 ещё не выпущена; проект находится на стадии pre-MVP. См. возможности и ограничения версии 1.0.

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

Dee Wan CMS — буквенный знак Dee Wan на баннере с тайским орнаментом

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.

С чего начать:

  • Конструктор моделей — определяйте модели контента и поля (текст, форматированный текст, число, логическое значение, выбор, дата, медиа, 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 schema
frontend/ SvelteKit admin UI
packages/ submodules: schema-parser, prisma-guard, prisma-rbac, prisma-generator-express, eav-to-prisma,
cache, prisma-query-builder-ui. In this repository: page-contract, plugin-conformance
plugins/ remote plugins built against protocol v1 (Scheduling)
scripts/ install, deploy, migrate, health, teardown

Так можно запустить весь продукт на своей машине с локальным файлом D1: без аккаунта, API-токена и DNS. Вам нужны Node и npm, как указано в разделе Требования.

Окно терминала
git clone --recurse-submodules https://github.com/dee-wan-cms/dee-wan.git
cd dee-wan
node scripts/install-dependencies.mjs # root and backend dependencies
node scripts/install-dependencies.mjs frontend # admin UI dependencies
npm run local:migrate # apply the schema to the local database
npm 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.

Полное руководство, включая автоматические установки, обновление и удаление, — Установка 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.git
cd dee-wan
node scripts/install-dependencies.mjs
node scripts/install-dependencies.mjs frontend
npm run setup # provisions everything, builds and deploys the admin UI and both Workers
npm run health # checks the deployment answers

setup создаёт базу данных 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, route
npm run deploy

deploy не запускается без этого файла: без публичного 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 prompt
export 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 Worker
npm run health # post-deploy checks; with Access on, also checks the perimeter

deploy собирает 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 check
npm run lint # lint the backend and scripts
npm 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.

Короткая проверка работающей установки после каждого развёртывания:

Окно терминала
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
Коммит для обновления: не определён: в рабочей копии есть изменения. Примечания к выпуску