Перейти к основному содержанию

Обзор

Локальная разработка приложения строится вокруг синхронизации: CLI пересобирает ваш манифест, а сервер применяет только разницу между ним и метаданными, которые уже есть в вашем рабочем пространстве. На этой странице описано, какую команду выбрать, как читать, что изменила синхронизация, и что делать — по шагам — когда локальное состояние выглядит несогласованным.

Какую команду и когда использовать

Для повседневной локальной разработки вам почти всегда нужна команда twenty app dev. Развертывание и публикация предназначены для выпуска релизов, а не для локального цикла разработки.

Для локальной синхронизации не нужно повышать версию

Правило строго возрастающей version (VERSION_ALREADY_EXISTS при deploy, APP_ALREADY_INSTALLED / CANNOT_DOWNGRADE_APPLICATION при install) применяется к twenty app publish / twenty app install — пути релизов. twenty app dev синхронизирует ваш манифест на месте и никогда не требует изменения версии, поэтому вам не нужно трогать package.json, чтобы выполнять итерации. Если вы ловите себя на том, что поднимаете версию, чтобы протестировать локальное изменение, значит вы используете релизный путь, когда вам нужен цикл разработки (dev loop).

Чтение вывода синхронизации

Каждая синхронизация выводит изменения метаданных, которые она применила (или применила бы, с plan), в стиле Terraform — по одному блоку на сущность с её атрибутами, затем строку с итогами:
Это ваша первая диагностическая точка: она показывает, какие именно объекты, поля и макеты изменились, чтобы вы могли подтвердить, что синхронизация сделала то, что вы ожидали, до проверки в интерфейсе. Разрушительные изменения (to destroy) перечисляются вместе с тем, что они удаляют (например, objectMetadata "auditNote" — drops the table and all its rows), и требуют интерактивного подтверждения или --yes в скриптах. При синхронизации удаляются все сущности, принадлежащие вашему приложению, которые больше не объявлены в исходном коде. Когда план содержит удаления, он сообщает об этом и указывает на --no-prune (ранее --no-delete, который по-прежнему работает), что делает синхронизацию аддитивной: создания и обновления применяются, ничего отсутствующего в вашем источнике не удаляется, а компонент настроек и привязки хуков удаления приложения остаются без изменений, если ваш источник их не объявляет. Используйте этот параметр, когда исходный код намеренно неполный, например при внедрении приложения, созданного в пользовательском интерфейсе. Пользовательское приложение рабочей области является исключением: поскольку pull пока не может экспортировать все принадлежащие ему объекты, его синхронизация аддитивна, если не указать --prune. Серверы, выпущенные до этого изменения, по-прежнему удаляют по умолчанию, поэтому при применении пользовательского приложения к такому серверу укажите --no-prune. Когда синхронизация завершается с ошибкой на одной сущности, в сообщении указываются проблемная сущность и её universalIdentifier, например:
Используйте этот идентификатор, чтобы найти сущность в своем манифесте (и, при необходимости, в рабочем пространстве), вместо того чтобы гадать, какая из них конфликтует.

Предпросмотр изменений (plan)

twenty app plan собирает ваш манифест, запрашивает у сервера план миграции и выводит его — без применения чего-либо. Это безопасный способ ответить на вопрос «что изменит эта синхронизация?» до того, как вы на неё согласитесь.
План:
  • Ничего не записывает — ни миграции метаданных, ни обновления записи приложения, ни изменений ролей/вкладок по умолчанию, ни генерации API‑клиента.
  • Возвращает тот же diff, который применит реальная синхронизация, чтобы вы могли заранее просмотреть создаваемые/обновляемые/удаляемые сущности.
  • Полезен перед рискованным изменением, при проверке изменения, сгенерированного ИИ, или в скрипте, который должен завершаться с ошибкой, если вот-вот будет применено неожиданное изменение.
План предварительно показывает только изменения метаданных. Это также работает для приложения, которое никогда не синхронизировалось: сервер сравнивает манифест с пустым приложением, поэтому план перечисляет всё, что создал бы ваш исходный код.

Возврат установленного приложения к исходному коду

twenty app pull является экспериментальной командой. Сейчас она охватывает лишь часть приложения, формат её вывода может меняться между релизами, и она перезаписывает файлы, определяющие извлекаемые ею сущности. Она восстанавливает опубликованные переводы в locales/, сохраняя записи, исходные строки которых ещё отсутствуют в вашем дереве, в locales/compiled/, чтобы отправка не привела к их потере. Зафиксируйте изменения перед её запуском и просмотрите diff.
twenty app pull является обратной операцией для apply: она считывает приложение из рабочего пространства и восстанавливает его файлы определения, переводы и сохраненные файлы зависимостей.
Внутри приложения pull считывает его локальный идентификатор defineApplication() и получает это приложение. Вне приложения команда запрашивает каталог и подтверждение, создаёт локальный проект для пользовательского приложения выбранного рабочего пространства, устанавливает зависимости с помощью Yarn, а затем получает определения. Новый проект включает файлы пакета и конфигурации без демонстрационных сущностей инициализатора. Используйте --path, чтобы выбрать существующее приложение или новый либо пустой каталог. Для неинтерактивного создания требуются --path и --yes; pull отказывается перезаписывать несвязанный непустой каталог. Создание проекта не регистрирует приложение и не изменяет рабочее пространство. Существующий проект SDK без определения приложения также получает пользовательское приложение. Параметр --universal-identifier отсутствует. Если установка зависимостей для нового проекта завершается ошибкой, каркас сохраняется. Выполните yarn install в нём, затем повторите pull. В существующих проектах зависимости не устанавливаются автоматически. Если не удаётся прочитать идентификатор их приложения, pull сообщает об этой ошибке вместо создания другого приложения. Добавьте --verbose, чтобы развернуть разделы охвата в отчёте: тогда в них будет указано до 20 идентификаторов для каждого типа метаданных, а не только итоговые значения. Что записывается:
  • конфигурация приложения;
  • сохраненные файлы зависимостей package.json и yarn.lock в корне проекта;
  • объекты со встроенными полями;
  • поля, добавленные вашим приложением к объектам, которыми оно не владеет;
  • авторские индексы;
  • флаги разрешений, объявленные вашим приложением, в виде файлов definePermissionFlag();
  • роли с разрешениями объектов, разрешениями полей, предикатами разрешений на уровне строк и группами, а также с флагами разрешений, заданными непосредственно в них. Роль по умолчанию вашего приложения записывается с помощью defineApplicationRole(). Разрешение объекта или поля, идентификатор которого определяется сборкой, записывается без него. Если роль по умолчанию не является одной из собственных ролей вашего приложения, как для приложения рабочей области Custom application, конфигурация приложения вместо этого сохраняет defaultRoleUniversalIdentifier;
  • представления с их полями, группами полей, фильтрами, группами фильтров, сортировками и встроенными группами;
  • поля представлений, добавленные вашим приложением в представления, которыми оно не владеет, в виде отдельных файлов defineViewField();
  • макеты страниц с их вкладками и виджетами в строке;
  • вкладки, которые ваше приложение добавило в макеты страниц, ему не принадлежащие, в виде отдельных файлов definePageLayoutTab();
  • элементы меню навигации, включая файлы и папки defineNavigationMenuItem();
  • виджеты, которые ваше приложение добавило на вкладки, которыми оно не владеет, в виде отдельных файлов definePageLayoutWidget();
  • рабочие процессы как файлы defineWorkflow(), когда их сохранённая версия — та, которую записала последняя синхронизация. Для пользовательского приложения рабочей области это активная версия независимо от того, была ли она опубликована синхронизацией или конструктором рабочих процессов, а apply публикует изменённое определение как новую версию, как описано в Workflows;
  • навыки в виде файлов defineSkill();
  • агенты в виде файлов defineAgent() с их триггерами и ролью, с которой их связывает ваше приложение;
  • опубликованные переводы в виде locales/\<locale>.json, чтобы strings pull мог считывать их из извлеченных метаданных и исходного текста, и locales/compiled/\<locale>.json для остальных.
Каждая сущность помещается в файл, который уже её определяет, поэтому повторный pull перезаписывает только то, что изменилось на сервере. Файл также перезаписывается, если в нем используется другая функция define, чем та, которую записала бы команда pull, например роль, которая стала или перестала быть ролью по умолчанию. Новые сущности размещаются рядом с существующими файлами того же типа, а сгенерированное имя, которое совпало бы с несвязанным файлом, уточняется вместо его перезаписи. Файлы зависимостей. Когда сохранённый package.json или yarn.lock изменяется, pull заменяет локальный файл и сообщает о перезаписанных локальных изменениях. Если удалённый файл не изменился с момента предыдущего pull, локальные изменения сохраняются. Отсутствующий удалённый файл не удаляет соответствующий локальный файл. Когда экспортированный пакет не включает twenty-sdk, как это делает пользовательское приложение рабочего пространства, pull сохраняет вашу локальную зависимость SDK, чтобы команды CLI по-прежнему могли загрузить проект. Файлы зависимостей, метаданные и базовая версия записываются вместе и вместе откатываются при ошибке записи. После получения изменений зависимостей выполните yarn install. Pull не устанавливает пакеты, не запускает скрипты пакетов и не повторно генерирует типизированный клиент. Что не записывается:
  • Еще не экспортировано — элементы меню команд и меню настроек, функции логики и фронтенд-компоненты, поставщики подключений, переменные приложения, общедоступные ресурсы, типы активности временной шкалы и сохраненный исходный код ваших функций.
  • Определяется движком — системные поля, отношения по умолчанию, представления по умолчанию и поддерживающие их индексы, а также макеты страниц по умолчанию каждого объекта. Сервер пересоздаёт их при apply. Объект всё ещё может указывать свой идентификатор метки на одно из них, как это делают объекты-связки, созданные в пользовательском интерфейсе; указатель записывается, а сборка разрешает его без добавления поля.
  • Размещено в представлении по умолчанию или внешнем представлении — фильтры, группы фильтров, сортировки, группы и группы полей, добавленные в представление движка по умолчанию или в представление, принадлежащее другому приложению, пока не имеют исходной формы и фиксируются в отчёте. Добавленные туда поля представления записываются как отдельные файлы, поскольку идентификатор представления известен.
  • Размещённые в макете страницы по умолчанию или чужом макете страницы — вкладки и виджеты, добавленные в макет страницы по умолчанию механизма или в макет, принадлежащий другому приложению, записываются как отдельные файлы definePageLayoutTab() и definePageLayoutWidget(), поскольку идентификатор, к которому они привязываются, известен.
  • Виджеты, которые экспорт пока не может включить — в любом макете, включая ваш собственный, виджет устаревшего типа VIEW, виджет, отображающий один из собственных компонентов фронтенда или элементов меню команд вашего приложения, либо виджет без позиции для режима макета его вкладки отображается в отчёте, а макет или вкладка записывается без него.
  • Рабочие процессы, которые экспорт пока не может перенести — рабочий процесс, изменённый в конструкторе рабочих процессов с момента последней синхронизации, системный или частный рабочий процесс, рабочий процесс с шагом Code или сообщением чата, которые рабочие процессы приложения не поддерживают, а также рабочий процесс, вызывающий действие рабочего процесса другого приложения, отмечаются в отчёте и остаются на месте. Пользовательское приложение рабочей области является исключением для первого и последнего случаев: конструктор также публикует его рабочие процессы, и они могут вызывать действия любого приложения, поэтому они извлекаются. Вместо этого в отчёте отмечается один из его рабочих процессов, если у него нет активной версии или если его активная версия использует параметры, которые невозможно выразить в определении. Шаги электронной почты и календаря переносятся: учётная запись, предоставленная рабочему пространству, записывается как её connectedAccountHandle, тогда как личная учётная запись участника и переменные сохраняются без изменений. Вместо этого рабочий процесс отмечается в отчёте, если учётная запись шага архивирована, больше не может выполнять шаг или использует тот же адрес, что и более старая учётная запись, поскольку применение дескриптора выбрало бы другую учётную запись.
  • Агенты, которых экспорт пока не может перенести — агент с несколькими ролями или привязанный к извлечению роли, в которое он не записывает, отмечается и остается на месте.
  • Состояние среды выполнения рабочего пространства, вложения записей, вложения электронных писем, назначения ролей участникам и API-ключам, вебхуки, пользовательская навигация и элементы навигации, закреплённые за записью, — всё это намеренно исключено, в том числе для пользовательского приложения рабочего пространства.
  • Принадлежит другому приложению — указывается в отчёте, но не извлекается.
В отчёте по завершении операции извлечения всё это перечислено, сгруппированное по типу и причине. Все, что оно обозначает как незаписанное, по-прежнему находится в вашей рабочей области, поэтому отправляйте извлеченное дерево обратно с помощью twenty app apply --no-prune, пока не будут поддерживаться отсутствующие типы. Пользовательское приложение рабочей области уже применяется таким образом по умолчанию. Применение с очисткой (по умолчанию для других приложений, --prune для пользовательского приложения) удалило бы их, а twenty app plan точно указывает, какие именно. Локальное для рабочей области состояние. Сущность, деактивированная в рабочей области, записывается как активное определение, а переопределения, применённые рабочей областью к сущности, например переименованная метка, не записываются. В отчёте эти строки перечислены в разделе «Записано без локального состояния рабочей области». Повторное применение дерева к той же рабочей области не изменяет это состояние; в другой рабочей области сущность будет активной и без переопределений. Базовый файл. Pull записывает полученные данные в .twenty/cli/pull-base.json, который игнорируется Git и является отдельным для каждой рабочей копии. Именно с этим файлом последующий pull сравнивает данные, чтобы не изменять файлы сущностей, которые не изменились, и удалить файл сущности, исчезнувшей из рабочего пространства. Рабочая копия без него просто записывает всё и ничего не удаляет.

Лестница восстановления

Когда локальные метаданные выглядят неверно, действуйте поэтапно в следующем порядке и останавливайтесь, как только проблема решена. Каждый следующий шаг более разрушителен, чем предыдущий.
  1. Повторная синхронизация. Снова выполните twenty app apply. Синхронизации идемпотентны — повторный запуск корректного манифеста безопасен и часто устраняет временный сбой.
  2. Предварительный просмотр плана. Выполните twenty app plan, чтобы увидеть, что именно намеревается изменить следующая синхронизация, не применяя эти изменения.
  3. Прочитайте сообщение об ошибке. Если синхронизация завершается с ошибкой, обратите внимание на тип метаданных и universalIdentifier в сообщении (см. выше) и найдите эту сущность в своем манифесте. Конфликт обычно указывает на дублированный или повторно используемый идентификатор.
  4. Удаление и повторная установка. Выполните twenty app uninstall, затем снова синхронизируйте (twenty app dev). Это пересобирает метаданные приложения с нуля, сохраняя остальную часть вашего рабочего пространства нетронутой.
  5. Полный сброс (крайняя мера). Сбросьте базу данных разработки Docker или репозитория, затем заново выполните начальное наполнение данными и синхронизацию.
сброс базы данных разработки Docker или репозитория удаляет все данные в вашем локальном экземпляре — все рабочие пространства, записи и приложения. Используйте это только после того, как предыдущие шаги не помогли.
Столкнулись с ошибкой метаданных? Пожалуйста, создайте issue и приложите сообщение о сбое миграции (с типом метаданных и universalIdentifier), вывод Metadata changes из синхронизации и команды, которые вы запускали.

Избегайте одновременных синхронизаций в одном рабочем пространстве

Синхронизация применяет миграции метаданных. Запуск нескольких операций sync, deploy или install по отношению к одному и тому же рабочему пространству одновременно — например, из нескольких терминалов или при параллельных итерациях агентов ИИ — может перемешать эти миграции и оставить метаданные в частично примененном состоянии. Сервер последовательно обрабатывает синхронизации для каждого рабочего пространства, чтобы предотвратить это, но вам всё равно следует пропускать чувствительные операции с метаданными через один процесс, а не запускать их параллельно. Если вы организуете разработку с несколькими агентами, направляйте их вызовы sync/deploy/install через одну очередь, чтобы в каждый момент времени выполнялась только одна операция.

Как различать типы сбоев

Когда что‑то идёт не так, diff метаданных и именованные ошибки помогают определить, на каком этапе произошел сбой:
  • Ошибка сборки манифеста — CLI завершается с ошибкой до синхронизации (MANIFEST_BUILD_FAILED, TYPECHECK_FAILED); исправьте исходный код приложения.
  • Ошибка принадлежности регистрации — синхронизация отклоняется, поскольку universalIdentifier приложения принадлежит другой рабочей области или вообще не принадлежит ни одной рабочей области; см. Принадлежность регистрации.
  • Ошибка синхронизации / миграции — сборка проходит успешно, но применение diff завершается сбоем с указанием сущности и universalIdentifier; исправьте конфликтующие метаданные.
  • Ошибка размера зависимостей — синхронизация или установка завершается сбоем, потому что продакшен‑зависимости приложения (dependencies) слишком велики, чтобы их установить в качестве слоя среды выполнения (LOGIC_FUNCTION_DEPENDENCIES_SIZE_EXCEEDED); перенесите пакеты, которые ваши логические функции не импортируют во время выполнения (UI-библиотеки, инструменты разработки), в devDependencies.
  • Выполняется отложенная миграция — синхронизация отклонена, поскольку фоновая миграция модели данных из предыдущей синхронизации всё ещё выполняется (DEFERRED_WORKSPACE_MIGRATION_ACTIONS_IN_PROGRESS); дождитесь её завершения, затем повторите синхронизацию.
  • Ошибка выполнения кода приложения — синхронизация проходит успешно, но ваши логические функции или компоненты ведут себя неправильно во время выполнения; проверьте журналы функций.
  • Локальное состояние экземпляра — ни один из вышеперечисленных пунктов не подходит, и рабочее пространство всё ещё выглядит неправильно; двигайтесь вниз по лестнице восстановления.