Перейти к содержимому

Обновление DWE

Новая версия DWE может изменить то, как загружается ваш проект, что делает шаг пайплайна или что печатает команда. Большинство релизов не требуют от вас ничего; некоторые — требуют. Эта страница — короткий список того, что ломается и что с этим делать, начиная с самого свежего релиза.

Это сознательно не список изменений — им является CHANGELOG.md, где записано каждое изменение. Здесь только те, ради которых вам придётся что-то править.

Как бы вы его ни ставили:

Окно терминала
brew upgrade semsemyonoff/tap/dwe # Homebrew

Иначе скачайте архив под свою платформу со страницы релизов и замените бинарник в $PATH. Способы установки перечислены в README.

DWE не обновляет сам себя. Блок update: в workspace.yml к этому отношения не имеет — он управляет git-пробой по репозиторию вашего проекта, а не бинарником DWE.

Три вещи, которые стоит сделать, прежде чем доверять новой версии в проекте:

  1. Сначала запустите dwe validate. Он ничего не деплоит и не поднимает ни один сервис, поэтому удалённый ключ конфига всплывёт именованной диагностикой, а не упавшим деплоем. Проект, который вообще перестал загружаться, отрапортует одну ошибку с именем ключа. (При этом он выполняет собственные checks.* проекта, а это могут быть команды shell или script: предполагается, что они идемпотентная инспекция, но CLI этого не гарантирует.)

  2. Перезапустите host-bridge, если проект им пользуется. Демон bridge исполняет тот бинарник, которым был запущен, поэтому после обновления на хосте контейнерный dwe продолжает работать по старой версии, пока демон не заменён:

    Окно терминала
    dwe bridge stop && dwe bridge start

    dwe bridge status показывает работающий демон; dwe version изнутри контейнера с bridge — сборку, которой он отвечает.

  3. Сделайте принудительный передеплой, если это сказано в релизных заметках. Изменение поведения, не меняющее хеш деплоя, невидимо для dwe deploy run: он отрапортует already up-to-date и пропустит ровно тот шаг, семантика которого сдвинулась. dwe deploy run --force перезапускает все шаги; условия when: при этом продолжают действовать.

В существующем проекте ничего менять не нужно: dwe init и его ai-пак по умолчанию больше не создают CLAUDE.md, а проект остаётся с тем паком, с которым был создан. Claude Code читает AGENTS.md сам, а CLAUDE.md, .claude/CLAUDE.md или CLAUDE.local.md в рабочем каталоге или выше заставляют его пропускать все AGENTS.md. Чтобы убрать ссылки из старого проекта, сделайте всё сразу:

  • Удалите корневой симлинк CLAUDE.md и запись symlinks: для CLAUDE.md из каждого workspace/templates/ai/*/manifest.yml.
  • Удалите симлинки services/*/CLAUDE.md вручную на каждой машине — рендер не удаляет вывод, который больше не объявлен.
  • Сервисный репозиторий со своим CLAUDE.md по-прежнему подавляет AGENTS.md внутри себя, включая корневой AGENTS.md проекта. Переименуйте его в AGENTS.md или выставьте в Claude Code Project instructions = claude-md-and-agents-md (/config).
  • symlink_fallback в dwe init --output json теперь всегда false и будет удалён в следующем минорном релизе.

В существующем проекте ничего менять не нужно. Одно нужно согласовать:

  • Workspace с новым корневым ключом render: требует 0.7.0 на каждой машине. Старые версии отклоняют неизвестный ключ, поэтому dwe не загружает проект — как и любой git-хук или команда, которые вызывают dwe. Обновите всех до того, как закоммитить render.workspace в репозиторий workspace.

В проекте ничего менять не нужно, повторный деплой не требуется. Изменились три вещи:

  • Баннер dwe cmd выводится в stderr. Строка ▶ <id> [<type>] <description> раньше открывала stdout команды; скрипт, который пропускал или разбирал эту первую строку, теперь получает только вывод самой команды, а с --output json баннер не печатается вовсе. Кроме того, баннер показывает только первую строку многострочного description:.
  • dwe validate может сообщить о новых находках в существующем проекте: hide:, который не вычисляется; отсутствующий файл из compose: / compose_after: / compose.base; команда, чей service: не называет ни одного compose-сервиса; info-заметка о блоке healthcheck: в активной compose-цепочке без start_period (собственный HEALTHCHECK образа не проверяется). Они указывают на ошибки, которые были и раньше. Ни одна из этих проверок не входит в preflight, поэтому lifecycle-команды они не блокируют.
  • dwe compose argv больше не читает флаги dwe после compose-команды. dwe compose argv exec app ls --all теперь печатает --all как часть команды; собственные флаги dwe ставьте первыми (dwe compose argv --all ps, dwe -v compose argv ps).

Host-порт compose, проведённый через vars:, теперь переназначается в каждой тестовой копии. Если compose-файл публикует порт одной переменной ("${VALKEY_PORT:-6379}:6379") и активное правило exports.env экспортирует эту переменную from: vars.<path>, dwe test выделяет свободный host-порт для <path> и записывает его в local.yml копии — без записи env.vars в сценарии. Раньше такой порт биндил число живого стека, а dwe validate предупреждал об этом.

Что проверить в своих сценариях:

  • Шаг, в котором исходный host-порт был захардкожен, теперь обращается не туда. Читайте порт из переменной — ${vars.<path>} — или зафиксируйте порт копии строкой env.vars: { <path>: 6380 }, которая отключает автоматическое переназначение для этого пути.
  • Записи env.vars: { <path>: auto } для такого порта стали избыточными. Они продолжают работать и ничего не меняют; удалите их, когда будет удобно. auto по-прежнему нужен для var, который не читает ни один порт compose.
  • Неотслеженная переменная не изменилась — нет правила exports.env, when: правила ложен, или правило читает не vars.<path> и не объявленный порт сервиса. Она по-прежнему биндит исходный порт и по-прежнему предупреждает, но теперь предупреждение указывает на два способа провести порт (запись services.<name>.ports или правило exports.env from: vars.<path>) вместо совета написать env.vars: { …: auto }.

Сценарий, заблокированный сканером изоляции compose, ничего не создаёт. Скан теперь выполняется до создания копии, поэтому блокирующая находка (container_name:, буквальный host-порт) проваливает сценарий без каталога копии и без compose-проекта, и отчёт об ошибке для этого запуска не собирается — отчёт, оставшийся от более раннего провала того же сценария, остаётся на месте, так что ориентируйтесь на предупреждения самого запуска, а не на отметку времени каталога. Код выхода прежний (1). --keep в этом случае сохранять нечего.

dwe test list --output json игнорирует персональные compose-overlay’и. compose.extra / services.<name>.compose.extra из вашего workspace/local.yml ссылаются на gitignored-файлы, которых копия не получает, поэтому команда теперь вычисляет сценарии без них — так же, как их выполняет копия: числа стоимости (build_services, external_images) эти сервисы больше не учитывают, как и cost_profile.isolation_findings. dwe validate tests строит то же представление для каждого сценария, поэтому находка interpolated_host_port, существующая только в таком overlay, больше не сообщается, — а остальные виды находок (container_name, литеральный хост-порт, виды про тома и сети) по-прежнему берутся из сканирования всего проекта и предупреждают независимо от overlay’ев.

DWE переходит на go-sprout 1.1, который больше не принимает порядок аргументов Sprig. Это касается всех мест, где вычисляются шаблоны {{ … }}, — см. Шаблоны.

Десять функций теперь падают на порядке Sprig. get, set, unset, hasKey, pick, omit, append, prepend, slice и without принимают map или список последним. Раньше старый порядок молча переставлялся с предупреждением; теперь это ошибка рендера, так что она всплывёт при первом же вычислении шаблона. Перепишите вызов через пайп:

{{ hasKey $m "a" }} → {{ $m | hasKey "a" }}
{{ append $list "x" }} → {{ $list | append "x" }}
{{ slice $list 1 2 }} → {{ $list | slice 1 2 }}

Четыре regex-функции поменяли порядок аргументов и не падают. Строка, с которой они работают, переехала на последнее место. Шаблон под старый порядок продолжает рендериться — в неверный результат:

функциябылостало
regexFindAllregexFindAll <re> <value> <n>regexFindAll <re> <n> <value>
regexSplitregexSplit <re> <value> <n>regexSplit <re> <n> <value>
regexReplaceAllregexReplaceAll <re> <value> <replacement>regexReplaceAll <re> <replacement> <value>
regexReplaceAllLiteralregexReplaceAllLiteral <re> <value> <replacement>regexReplaceAllLiteral <re> <replacement> <value>

Об этом ничто не сообщит, поэтому найдите их поиском:

Окно терминала
grep -rnE 'regex(FindAll|Split|ReplaceAll)' workspace/

Поиск заодно зацепит regexFindAllGroups и regexFindAllNamed, которые не менялись, — их пропустите. Остальные regex-функции — regexMatch, regexFind, regexQuoteMeta и варианты *Groups / *Named — не изменились.

Алиасы mustRegex* удалены. mustRegexFind, mustRegexFindAll, mustRegexMatch, mustRegexSplit, mustRegexReplaceAll и mustRegexReplaceAllLiteral были deprecated; шаблон, который их вызывает, теперь не парсится. Уберите префикс must — и там, где это касается, примените новый порядок аргументов из таблицы выше.

div на ноль теперь ошибка рендера, а не произвольное число.

Релизным бинарникам нужна macOS 13 Ventura или новее. Они собираются Go 1.27, который прекратил поддержку macOS 12; на более старом Mac оставайтесь на 0.6.0. Сборка из исходников требует Go 1.27.

dwe validate предупреждает о host-портах compose, подставленных из переменной, которую dwe test не переназначает. Порт вроде "${VALKEY_PORT:-6379}:6379", где VALKEY_PORT экспортируется from: vars.ports.valkey, в каждом тестовом запуске биндит тот же host-порт, что и живой стек. Так было всегда; теперь об этом говорят dwe validate и dwe test run. Предупреждение появляется только в проектах с каталогом workspace/tests/ и проваливает dwe validate --strict. Исправьте каждый сценарий, который называет предупреждение:

env:
vars:
ports.valkey: auto

Порт, экспортируемый from: services.<name>.ports.<x>, уже переназначается, пока этот сервис включён в сценарии; предупреждение появится только для сценария, который этот сервис выключает, но оставляет его compose-файл в стеке, — например, объявленного в корневом compose-файле. Сервис с required: true выключить нельзя, поэтому его порты переназначаются всегда и предупреждения он не даёт. См. Интерполированные host-порты.

Предупреждение без списка сценариев значит, что ни одно активное правило exports.env не прослеживает переменную — она приходит из написанного вручную .env, окружения хоста или из правила, чей when: ложен, — поэтому никакая настройка сценария её не исправит. Экспортируйте переменную from: vars.<path> и добавьте env.vars: { <path>: auto } в каждый сценарий, либо экспортируйте её from: services.<name>.ports.<x>.

Переходите сразу на 0.6.2? Шаг с env.vars: { <path>: auto } выше там уже не нужен — переменная, экспортируемая from: vars.<path>, переназначается автоматически (см. Обновление до 0.6.2).

Литеральный host-порт за интерполированным адресом привязки теперь блокирует dwe test run. "${BIND:-127.0.0.1}:8080:80" публикует литеральный host-порт 8080, который конфликтует с живым стеком; раньше сканер его пропускал. Опишите порт под services.<name>.ports и интерполируйте его, как любой литеральный host-порт, или передайте --skip-isolation-check.

dwe validate предупреждает, когда host-скрипт сам собирает имя compose-проекта. Внутри dwe test shell-шаги и скрипты получают имя копии в COMPOSE_PROJECT_NAME; скрипт, который передаёт docker compose -p "${PROJECT_PREFIX:-dwe}-${PROJECT_NAME:-myproj}", его игнорирует и обращается к живому стеку. Новое предупреждение tests.host_project_name называет файл и строку. Как и предупреждение о портах, оно появляется только в проектах с каталогом workspace/tests/ и проваливает dwe validate --strict. Оставьте старое выражение как fallback:

Окно терминала
PROJECT="${COMPOSE_PROJECT_NAME:-${PROJECT_PREFIX:-dwe}-${PROJECT_NAME:-myproj}}"

Что проверка читает и что пропускает — см. Host-скрипты, которые сами собирают имя compose-проекта.

Встроенный пайплайн деплоя теперь снова поднимает остановленный стек. Его шаг up несёт check: {type: builtin, cmd: containers_running}, поэтому выполняется на каждом деплое, а не пропускается журналом после первого успеха. Поэтому встроенный пайплайн больше никогда не сообщает already up-to-date: деплой неизменённого запущенного проекта — это быстрый docker up --wait плюс проверка.

Первый деплой после обновления один раз видит изменение конфига. check: входит в хеш конфига проекта, поэтому проект, задеплоенный встроенным пайплайном, больше не совпадает со своим записанным хешем. Интерактивный dwe deploy run один раз покажет селектор Deployed config changed. Choose action: — выберите Apply changes. Неинтерактивный деплой применит изменение без вопросов. В обоих случаях шаги уровня проекта (up, info, сообщение об успехе) выполнятся один раз; шаги сервисов это не затрагивает.

Извлечённый через eject или написанный вручную workspace/deploy.yml сохраняет старое поведение. Он целиком заменяет встроенный пайплайн, поэтому его шаг docker up по-прежнему без check: и по-прежнему пропускается после записи в журнал. Чтобы получить исправление, добавьте тот же check — это вызовет то же разовое изменение конфига:

- name: up
type: dwe
cmd: "docker up --wait"
check:
type: builtin
cmd: containers_running

Что принимает проверка — см. containers_running.

Три группы, в том порядке, в котором вы на них наткнётесь.

Проект не загружается вообще, пока это не исправлено, и все команды сообщают одну и ту же ошибку. Первые две называют файл; третья вместо этого называет индекс правила в exports.env, потому что правило экспорта не несёт в себе исходного слоя.

Верхнеуровневый ключ state: удалён. Удалите его. Это была свободная строка с одним потребителем и без замены; свободные значения живут под vars:.

# workspace/defaults.yml — удалить
state: ""

Если что-то его читало — правило exports.env с from: state, ссылка ${state} — перенесите значение под vars: и поправьте путь либо уберите правило.

Верхнеуровневый блок ui: удалён. Удалите его. Три его настройки браузера команд замены не имеют, а сам браузер теперь работает так, как всегда разрешался отсутствующий блок: верхнеуровневые группы развёрнуты, пустые поддеревья схлопываются при фильтрации, бейджи типов включены. Хоткеи, форма параметров, лестница фолбэков и поведение мыши, которые были описаны на старой странице ui, теперь живут в разделе Интерактивный браузер в ../reference/config/commands/index.md.

COMPOSE_PROJECT_NAME стал зарезервированной переменной .env, и переобъявлять его нельзя. Правило exports.env с таким именем валит загрузку:

exports.env[3]: "COMPOSE_PROJECT_NAME" is a reserved system variable and cannot be
redeclared as an export rule (reserved: PROJECT, UID, GID, COMPOSE_PROJECT_NAME)

Удалите правило — DWE пишет эту строку сам. Но сначала сверьте значение, не считайте его тем же самым. Встроенное значение — это имя compose-проекта, которое DWE передаёт в -p: project_name из workspace/docker.yml, иначе <project.prefix>-<project.name>, всегда в нижнем регистре. Самодельное правило, собранное по другому выражению, давало другую строку, и всё созданное под старым именем — контейнеры, сети, тома — становится невидимым для нового. Сравните их перед удалением:

Окно терминала
grep COMPOSE_PROJECT_NAME .env # что давало ваше правило
dwe render env | grep COMPOSE_PROJECT_NAME # что напишет DWE

Если значения разошлись, а стек поднят, либо пропишите старое значение в project_name в workspace/docker.yml, либо примите новое имя и передеплойте один раз с нуля.

Ничего не падает. Поведение стало другим.

Контейнерные команды теперь сами определяют три рантайм-дефолта. Каждый действует на свой набор типов команд:

дефолтдействует нановое поведение
workdir и userservice_exec, service_run, daemonБез workdir: берётся cli.workdir сервиса → work_dir_internal → dir_internal — та же цепочка, что у dwe shell; workdir: internal — отказ от неё. Демон без user: наследует cli.user сервиса вместо USER из образа.
TTYservice_exec, service_runТерминал в контейнере выдаётся, только если команду запустили вы сами и потоки DWE — терминалы, либо запуск идёт через bridge. Всё остальное получает -T с принудительным цветом.
режим execservice_execmode: по умолчанию exec-or-run вместо exec-or-fail: при остановленном сервисе выполняется разовый docker compose run --rm вместо отказа.

Что проверить: любой демон, у чьего сервиса задан cli.user — меняется владелец всего, что демон пишет, поэтому пропишите user: явно, если старый uid был важен. Любую команду, которая не должна создавать контейнер, — объявите mode: exec-or-fail. Любой check: с type: command, указывающий на service_exec-команду без mode: — теперь он создаёт контейнер, а разовый запуск идёт с --no-deps, так что такая проба может отрапортовать успех против лежащего стека.

Ни одно из трёх изменений не меняет хеш деплоя. Сделайте один dwe deploy run --force после обновления, иначе шаг со сдвинувшейся семантикой отрапортует already up-to-date и будет пропущен.

Голый docker compose из корня проекта теперь работает в проекте DWE. Сгенерированный .env несёт COMPOSE_PROJECT_NAME, а .env лежит в каталоге compose-проекта — Compose подхватывает его сам, поверх любого верхнеуровневого name: в вашем compose-файле. Это и было целью: ручной docker compose ps наконец показывает те же контейнеры, что и dwe status. Но compose-файл с расходящимся name: — то, что dwe validate сообщает как config.compose_project_name — стал мёртвой конфигурацией, и ресурсы, созданные под ним, голому compose больше не видны. Либо приведите оба к одному значению, либо впишите в compose-файл name: ${COMPOSE_PROJECT_NAME} — теперь оно резолвится.

Логи пайплайна больше не пишут каждый кадр перерисовки. Кадр, оканчивающийся на \r, придерживается и перекрывается следующим, поэтому .dwe/logs/<pipeline>.log пишет одну строку на одну завершённую строку. Следствие: tail -f .dwe/logs/deploy.log больше не показывает живой прогресс клона или скачивания, только завершённые строки. Живой вывод в терминале не изменился.

dwe docs llms-txt --output PATH стал --out PATH. Алиаса нет: старый локальный --output затенял корневой флаг формата с тем же именем, из-за чего -o на этой команде не работал. -o json теперь принимается и игнорируется — сам документ и есть полезная нагрузка.

Изменился текст ошибки строгих загрузчиков. Неизвестное поле в пайплайне, service.yml, файле команд, манифесте, сценарии, setup.yml или бандле переводов теперь читается так:

workspace/deploy.yml:12: unknown field "defaults" — allowed here: log, phases

Раньше всплывало сообщение YAML-библиотеки field defaults not found in type config.DeployConfig. Скрипт или CI-джоба, которые грепают not found in type, нуждаются в правке.

У dwe validate появились предупреждения, способные завалить --strict. Теперь сообщается о dot-пути, который не резолвится в собранном конфиге: from: или when: у правила exports.env, а также params.<name>.default_from, params.<name>.options.from и context.<name>.from у команды. Это предупреждения — from: с default: является законным опциональным путём, а путь может жить в local.yml, которого нет на этой машине. Но dwe validate --strict считает предупреждения ошибками, поэтому CI-джоба на нём может начать падать на осознанно опциональном пути. Дайте такому правилу default: либо поправьте путь.

dwe validate заодно стал честно сообщать о встроенном пайплайне по умолчанию: пустой или полностью закомментированный deploy.yml теперь идёт как info — has no active content (all comments or empty) — built-in default pipeline is active — там, где раньше рапортовался OK. Ничего не сломано: этот файл никогда и не выполнялся. Если хотите начать править со встроенного пайплайна, dwe deploy eject --out workspace/deploy.yml --force запишет его туда закомментированным, готовым к редактированию документом.

Релизы до v0.5.0 включительно появились раньше этой страницы. Их заметки — на странице релизов.