@coraltravelcenter/b2c-landing-builder 2.14.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,224 +1,174 @@
1
1
  # @coraltravelcenter/b2c-landing-builder
2
2
 
3
- Глобальный CLI для разработки B2C-лендингов через Tampermonkey и сборки
4
- CMS-готовых HTML-файлов.
3
+ CLI для разработки B2C-лендингов, локального предпросмотра через Tampermonkey,
4
+ сборки CMS-готовых HTML-блоков и управляемой публикации ассетов и виджетов в
5
+ Backoffice.
5
6
 
6
- ## Установка
7
+ Builder устанавливается глобально и запускается из каталога проекта. Исходники
8
+ builder, `vite.config.js` и собственные build-скрипты проекту не нужны.
7
9
 
8
- Требуется Node.js 20.19 или новее.
10
+ ## Требования и установка
11
+
12
+ - Node.js 20.19 или новее;
13
+ - Tampermonkey для локального предпросмотра;
14
+ - офисная сеть или VPN для работы с Backoffice.
9
15
 
10
16
  ```bash
11
17
  npm install --global @coraltravelcenter/b2c-landing-builder
18
+ b2c-landing-vite --help
12
19
  ```
13
20
 
14
- Builder устанавливается один раз и не копируется в проекты лендингов.
15
-
16
- ## Команды
17
-
18
- Команды выполняются из корня проекта:
21
+ ## Создание проекта
19
22
 
20
23
  ```bash
21
- b2c-landing-vite check
24
+ npm create @coraltravelcenter/b2c-landing-vite
25
+ cd <project-directory>
26
+ npm install
27
+ b2c-landing-vite check --strict
22
28
  b2c-landing-vite dev
23
- b2c-landing-vite build
24
- b2c-landing-vite deploy
25
- b2c-landing-vite deploy --section hero
26
- b2c-landing-vite deploy assets
27
- b2c-landing-vite block:add hero
28
- b2c-landing-vite block:rename hero main-hero
29
- b2c-landing-vite update
30
29
  ```
31
30
 
32
- - `check` проверяет конфигурацию, `order.json` и контракт JS-блоков.
33
- - `dev` запускает Vite и отдельный userscript с названием проекта для
34
- Tampermonkey.
35
- - `build` создаёт CMS-файлы в `@CMS/` и переписывает пути ассетов на CDN.
36
- - `deploy` собирает проект, синхронизирует ассеты с CDN и после успешной
37
- синхронизации разворачивает CMS-виджеты.
38
- - `deploy --section <key>` разворачивает только выбранную CMS-секцию, не изменяя
39
- и не удаляя соседние секции.
40
- - `deploy assets` собирает проект и синхронизирует ассеты с B2C CDN.
41
- - При первом deploy builder запрашивает backoffice-логин и скрытый пароль через
42
- ADFS. На диск сохраняется только токен. Для CI можно передать
43
- `B2C_BACKOFFICE_TOKEN`.
44
- - Backoffice-запросы ограничены 30 секундами. Безопасные GET-запросы повторяются
45
- до двух раз при сетевом сбое, timeout, HTTP 429, 502, 503 или 504. Изменяющие
46
- CMS запросы автоматически не повторяются, чтобы не создать дубликаты.
47
- - После успешного deploy builder сохраняет снимок секций в `.b2c/deploy/` как
48
- достоверный `base`. Следующий deploy структурно сравнивает секции `local`,
49
- актуальный `remote` и `base`. Изменения разных секций объединяются; изменение
50
- одной секции с обеих сторон считается конфликтом.
51
- - Если `base` ещё отсутствует, отличие local от remote считается потенциальным
52
- конфликтом. Обычный deploy не выполняет silent overwrite и предлагает явно
53
- сохранить CMS-версию, применить local или отменить операцию.
54
- - Стабильные `pageId`, `layoutAreaId` и позиция сохраняются отдельно в
55
- `.b2c/deploy/<site>--<folder>.binding.json`. Этот файл можно хранить в Git,
56
- чтобы команда находила ту же CMS-страницу после нового клонирования проекта.
57
- - Перед deploy builder получает список версий сохранённой страницы. Published
58
- выбирается по умолчанию; из Published или Unpublished создаётся checkout,
59
- существующий Checkout обновляется напрямую. Потерянную привязку можно
60
- восстановить интерактивным выбором существующей страницы.
61
- - Одновременные deploy одного проекта на компьютере блокируются lock-файлом в
62
- `.b2c/deploy/`; аварийно оставленный lock автоматически признаётся устаревшим.
63
- - Непосредственно перед первой записью CMS-состояние читается повторно. Если оно
64
- изменилось во время проверки/выбора стратегии, deploy останавливается.
65
- - `block:add` создаёт файлы в выбранных форматах разметки и стилей.
66
- - `block:rename` атомарно переименовывает все файлы блока и обновляет порядок.
67
- - `update` проверяет npm registry и показывает команду ручного обновления.
68
-
69
- После успешного выполнения рабочих команд builder коротко и независимо
70
- проверяет в npm registry версии самого builder и шаблона проекта. Для проекта
71
- без `template.version` шаблон помечается как `legacy`. Ошибки сети не влияют на
72
- работу CLI; ничего не обновляется автоматически:
31
+ Генератор: [CoralTravelCenter/create-b2c-landing-vite](https://github.com/CoralTravelCenter/create-b2c-landing-vite).
73
32
 
74
- ```bash
75
- b2c-landing-vite update
76
- # Update manually: npm install --global @coraltravelcenter/b2c-landing-builder@latest
77
- # Update project template: npx @coraltravelcenter/create-b2c-landing-vite@latest migrate
78
- ```
33
+ ## Основной рабочий процесс
79
34
 
80
- ## Создание проекта
35
+ 1. Добавьте или измените блоки в `src/`.
36
+ 2. Запустите `b2c-landing-vite check --strict`.
37
+ 3. Проверьте результат через `b2c-landing-vite dev` и Tampermonkey.
38
+ 4. Перед публикацией выполните `b2c-landing-vite deploy --dry-run`.
39
+ 5. Примените проверенный план командой `b2c-landing-vite deploy`.
81
40
 
82
- Новые проекты создаются отдельным пакетом:
41
+ Для предварительной загрузки ассетов без изменения CMS используйте
42
+ `b2c-landing-vite deploy assets`.
83
43
 
84
- ```bash
85
- npm create @coraltravelcenter/b2c-landing-vite
44
+ ### Терминальный интерфейс
45
+
46
+ Каждая команда показывает единый структурированный сценарий: название команды и
47
+ проекта, текущие стадии, важные параметры, подтверждения и итог. Терминальные
48
+ подписи остаются на английском и одинаково используются в `check`, `dev`,
49
+ `build`, block-командах и всех вариантах deploy.
50
+
51
+ Полный deploy открывает одну terminal-сессию. Вложенные build, CDN и CMS-этапы
52
+ отображаются внутри неё, без повторных заголовков и ложных промежуточных
53
+ `complete`. Ошибка сохраняет исходную диагностику и ненулевой exit code.
54
+
55
+ В интерактивном терминале прогресс может отображаться spinner-анимацией. При
56
+ перенаправлении вывода содержательные подписи, пути и URL остаются текстовыми.
57
+
58
+ ## Структура проекта
59
+
60
+ ```text
61
+ src/
62
+ order.json
63
+ markup/
64
+ welcome.html | welcome.pug
65
+ styles/
66
+ welcome.css | welcome.scss | welcome.less
67
+ scripts/
68
+ welcome.js | welcome.ts
69
+ utils/ # общие JS/TS-утилиты
70
+ components/ # компоненты проекта для Vue-стека
71
+ public/ # исходные статические файлы
72
+ pug.rc # настройки Pug, если выбран Pug
73
+ landing.config.mjs
74
+ package.json
86
75
  ```
87
76
 
88
- Репозиторий генератора:
89
- [CoralTravelCenter/create-b2c-landing-vite](https://github.com/CoralTravelCenter/create-b2c-landing-vite).
77
+ Каталог `@CMS/` создаётся во время сборки. Это производный артефакт: его не нужно
78
+ редактировать вручную или хранить в Git.
90
79
 
91
80
  ## Конфигурация
92
81
 
93
- В корне каждого проекта находится `landing.config.mjs`:
82
+ Проект настраивается в `landing.config.mjs`:
94
83
 
95
84
  ```js
96
85
  export default {
97
86
  schemaVersion: 2,
98
- builder: {
99
- minVersion: "2.10.0",
100
- },
101
- template: {
102
- version: "2.13.0",
103
- },
104
- project: {
105
- name: "uae-promo",
106
- },
107
- site: {
108
- preset: "coral",
109
- },
87
+ builder: {minVersion: "2.10.0"},
88
+ template: {version: "2.13.0"},
89
+ project: {name: "uae-promo"},
90
+ site: {preset: "coral"},
110
91
  stack: {
111
92
  script: "vue",
112
93
  markup: "pug",
113
94
  styles: "less",
114
95
  },
115
- blocks: {
116
- static: ["html-only"],
117
- },
96
+ blocks: {static: ["html-only"]},
118
97
  };
119
98
  ```
120
99
 
121
- `template.version` записывается генератором и показывает, какой версией шаблона
122
- создан проект. Для старых проектов поле необязательно; его отсутствие позволяет
123
- builder распознать legacy-шаблон и предложить миграцию в будущих версиях.
124
-
125
- Имя проекта должно соответствовать `^[a-z0-9][a-z0-9-]*$`.
100
+ ### Версии
126
101
 
127
- Поддерживаются:
102
+ - `schemaVersion` — версия формата конфигурации; поддерживаются `1` и `2`.
103
+ - Для схемы `2` обязателен `builder.minVersion`. Слишком старая версия builder
104
+ блокирует выполнение команд.
105
+ - `template.version` записывает генератор. В legacy-проектах поле может
106
+ отсутствовать; проверка обновлений тогда предложит миграцию шаблона.
128
107
 
129
- - JavaScript: `js`, `ts`, `vue`, `vue-ts` (`vanilla` поддерживается для старых проектов);
130
- - разметка: `html`, `pug`;
131
- - стили: `css`, `scss`, `less`;
132
- - сайты: `coral`, `sunmar`.
108
+ ### Проект, сайт и стек
133
109
 
134
- Зависимости выбранного стека устанавливаются локально в проект: `typescript`
135
- для TS, `vue` и `@vitejs/plugin-vue` для Vue, `pug` для Pug, `less` для Less и
136
- `sass` для SCSS.
137
- Builder загружает Vue-плагин из проекта, поэтому Vue-зависимости не
138
- устанавливаются вместе с глобальным builder.
110
+ - `project.name` должен соответствовать `^[a-z0-9][a-z0-9-]*$`.
111
+ - `site.preset` принимает `coral` или `sunmar` и определяет домен и CDN.
139
112
 
140
- `blocks.static` явно перечисляет блоки без JavaScript initializer. Команда
141
- `b2c-landing-vite check --strict` требует initializer или такую декларацию.
142
- HTML-разметка поддерживает вложенные `<include src="./partial.html"></include>`.
113
+ | Поле | Поддерживаемые значения |
114
+ | --- | --- |
115
+ | `stack.script` | `js`, `ts`, `vue`, `vue-ts`; `vanilla` — legacy-алиас |
116
+ | `stack.markup` | `html`, `pug` |
117
+ | `stack.styles` | `css`, `scss`, `less` |
143
118
 
144
- При первом запуске dev-сервер сохраняет выбранный свободный порт в
145
- `.b2c/dev-port`; следующие запуски используют его строго. Новый порт можно
146
- закрепить командой `B2C_PORT=5175 npm run dev`.
119
+ Зависимости стека устанавливаются локально в проект: `typescript` для TS,
120
+ `vue` и `@vitejs/plugin-vue` для Vue, `pug` для Pug, `sass` для SCSS и `less`
121
+ для Less. Глобальный builder не заменяет эти зависимости.
147
122
 
148
- Перед подготовкой deploy-артефактов builder рекурсивно проверяет `public/`.
149
- Изображения JPG, JPEG и PNG конвертируются в WebP с качеством 80, а ссылки в
150
- CMS-файлах автоматически переключаются на новые имена. Исходные файлы в
151
- `public/` не изменяются. Если WebP с таким именем уже существует, используется
152
- он. SVG, GIF, шрифты и остальные файлы копируются без преобразования.
123
+ ### Статические блоки
153
124
 
154
- Monkey URL, контейнер и CDN prefix не задаются вручную:
125
+ `blocks.static` перечисляет блоки без JavaScript/TypeScript initializer.
126
+ `check --strict` требует, чтобы каждый блок либо имел initializer, либо был явно
127
+ объявлен статическим. Статический блок не может одновременно иметь script entry.
155
128
 
156
- - URL: `https://<домен-пресета>/monkey/*`;
157
- - контейнер: `#monkey-app`;
158
- - CDN prefix: `landing-pages/<project.name>`.
129
+ ## Блоки и порядок
159
130
 
160
- Dev-сервер начинает с `127.0.0.1:5173` и автоматически выбирает следующий
161
- свободный порт, поэтому несколько проектов можно запускать одновременно. Если
162
- задан `B2C_PORT`, порт считается обязательным и занятое значение завершает
163
- команду ошибкой.
131
+ `src/order.json` единственный источник порядка блоков и CDN-папки:
164
132
 
165
- После запуска dev-сервера builder выводит рядом с локальным адресом Target URL
166
- и открывает его отдельной вкладкой: `https://www.coral.ru/monkey/` или
167
- `https://www.sunmar.ru/monkey/`. Существующая вкладка установки Tampermonkey
168
- userscript открывается как раньше. `B2C_NO_OPEN=1` отключает автоматическое
169
- открытие обеих вкладок, но Target URL остаётся в терминале.
170
-
171
- Userscript ограничен доменом выбранного бренда и дополнительно проверяет hostname
172
- перед монтированием. Namespace также содержит бренд, поэтому Coral и Sunmar не
173
- конфликтуют даже при одинаковом названии проекта.
133
+ ```json
134
+ {
135
+ "folder": "uae-promo",
136
+ "blocks": ["hero", "welcome"]
137
+ }
138
+ ```
174
139
 
175
- Для Coral и Sunmar настроены отдельные production CDN. Команда `build`
176
- автоматически выбирает CDN по `site.preset`.
140
+ - `folder` должен соответствовать `^[a-z0-9][a-z0-9-]*$`;
141
+ - CDN prefix — `<assetsBase>/landing-pages/<folder>/`;
142
+ - ключи блоков должны быть уникальными;
143
+ - каждому ключу нужны разметка и стили выбранных форматов.
177
144
 
178
- ## Структура проекта
145
+ Создание и переименование:
179
146
 
180
- ```text
181
- src/
182
- order.json
183
- markup/
184
- welcome.html | welcome.pug
185
- styles/
186
- welcome.css | welcome.scss | welcome.less
187
- scripts/
188
- welcome.js | welcome.ts
189
- utils/ # общие JavaScript-утилиты проекта
190
- components/ # только для Vue
191
- public/
192
- pug.rc # только для Pug
193
- landing.config.mjs
194
- package.json
147
+ ```bash
148
+ b2c-landing-vite block:add hero
149
+ b2c-landing-vite block:rename hero main-hero
195
150
  ```
196
151
 
197
- В проекте не нужны `vite.config.js`, исходники builder или локальные build-скрипты.
152
+ `block:add` создаёт файлы выбранного стека и добавляет ключ в конец order.
153
+ `block:rename` сначала проверяет все пути, затем переименовывает имеющиеся
154
+ варианты разметки, стилей и скрипта и обновляет order. Конфликт целевого файла
155
+ останавливает команду до частичных изменений.
198
156
 
199
- ## Порядок блоков
157
+ ### HTML include
200
158
 
201
- `src/order.json` является единственным источником порядка:
159
+ HTML поддерживает относительные вложенные include:
202
160
 
203
- ```json
204
- {
205
- "folder": "uae-promo",
206
- "blocks": ["hero", "welcome"]
207
- }
161
+ ```html
162
+ <include src="./partials/card.html"></include>
208
163
  ```
209
164
 
210
- `folder` задаёт единственную папку проекта на CDN. Она должна быть безопасным
211
- именем без вложенных путей. Итоговый префикс ассетов:
212
- `<assetsBase>/landing-pages/<folder>/`.
213
-
214
- Для каждого ключа обязательны разметка и стиль с тем же именем. JS/TS-файл
215
- необязателен. Форматы определяются `landing.config.mjs`; отсутствие обязательного
216
- файла блокирует `check`, `dev` и `build` с указанием ожидаемого пути.
165
+ Путь разрешается относительно исходного файла. Отсутствующий файл или
166
+ рекурсивная цепочка блокируют проверку и сборку.
217
167
 
218
168
  ## Контракт JavaScript и TypeScript
219
169
 
220
- Если у блока есть `src/scripts/<key>.js` или `src/scripts/<key>.ts`, файл обязан
221
- экспортировать функцию инициализации по умолчанию:
170
+ Script entry `src/scripts/<key>.js` или `.ts` экспортирует initializer по
171
+ умолчанию:
222
172
 
223
173
  ```js
224
174
  export default function hero() {
@@ -229,79 +179,313 @@ export default function hero() {
229
179
  }
230
180
  ```
231
181
 
232
- Builder вставляет разметку, затем вызывает функцию без аргументов. Скрипт блока
233
- сам находит свою разметку в `document`. Этот контракт одинаков для dev и CMS
234
- build; дополнительные контейнеры builder не создаёт. Блок без JS допустим.
182
+ Builder вставляет разметку, затем вызывает функцию без аргументов. Функция сама
183
+ находит блок в `document`. Дополнительные контейнеры builder не создаёт.
184
+
185
+ Расширение entry определяется `stack.script`: например, при `ts` файл `hero.js`
186
+ считается конфликтующим, а не альтернативным entry.
187
+
188
+ ## Проверка проекта
189
+
190
+ ```bash
191
+ b2c-landing-vite check
192
+ b2c-landing-vite check --strict
193
+ ```
194
+
195
+ Проверяются конфигурация и версии, локальные зависимости стека, `src/order.json`,
196
+ файлы блоков, HTML include, расширение script entry и default initializer.
197
+ Strict-режим дополнительно проверяет контракт статических блоков. Команда не
198
+ собирает проект и не обращается к CDN или CMS.
235
199
 
236
- ## Изображения
200
+ ## Локальная разработка
237
201
 
238
- Изображения хранятся в `public/` и используются от корня:
202
+ ```bash
203
+ b2c-landing-vite dev
204
+ ```
205
+
206
+ Builder запускает Vite на `127.0.0.1`, создаёт Tampermonkey userscript с
207
+ названием проекта и открывает его установку и Target URL:
208
+
209
+ - `https://www.coral.ru/monkey/`;
210
+ - `https://www.sunmar.ru/monkey/`.
211
+
212
+ Userscript ограничен доменом выбранного бренда, проверяет hostname и использует
213
+ namespace с site preset. Проекты Coral и Sunmar с одинаковым названием поэтому
214
+ не конфликтуют.
215
+
216
+ На Target URL userscript ждёт контейнер `#monkey-app`, последовательно вставляет
217
+ в него блоки из `src/order.json`, а затем вызывает их initializer. Повторное
218
+ монтирование в тот же контейнер блокируется.
219
+
220
+ ### Порт и вкладки
221
+
222
+ Первый запуск начинается с `5173` и при необходимости выбирает следующий
223
+ свободный порт. Он сохраняется в `.b2c/dev-port`; следующие запуски используют
224
+ его строго.
225
+
226
+ ```bash
227
+ B2C_PORT=5175 b2c-landing-vite dev
228
+ B2C_NO_OPEN=1 b2c-landing-vite dev
229
+ ```
230
+
231
+ `B2C_PORT` назначает обязательный порт от `1` до `65535`.
232
+ `B2C_NO_OPEN=1` отключает автоматическое открытие вкладок.
233
+
234
+ ### Обновление страницы
235
+
236
+ - Стили и Vue-компоненты используют штатный HMR Vite.
237
+ - Разметка, script entry, `src/order.json` и `pug.rc` вызывают полную перезагрузку.
238
+ - Добавление и удаление разметки или script entry также перезагружает страницу.
239
+
240
+ Root-relative URL из `public/` в dev направляются на origin текущего Vite-сервера.
241
+
242
+ ## Production build
243
+
244
+ ```bash
245
+ b2c-landing-vite build
246
+ ```
247
+
248
+ Сборка:
249
+
250
+ 1. Проверяет проект и зависимости стека.
251
+ 2. Создаёт отдельный `@CMS/<key>.html` для каждого блока.
252
+ 3. Переписывает root-relative media URL на production CDN site preset.
253
+ 4. Готовит копию ассетов в `@CMS/assets/`.
254
+ 5. Конвертирует JPG, JPEG и PNG в WebP с качеством `80`.
255
+ 6. Обновляет CMS HTML после конвертации.
256
+ 7. Создаёт `@CMS/manifest.json` с путями, размерами и SHA-256.
257
+
258
+ Исходники в `public/` не меняются. Существующий WebP с нужным именем имеет
259
+ приоритет. Коллизия нескольких исходников в один итоговый путь блокирует build.
260
+
261
+ Builder переписывает root-relative ссылки на `jpg`, `jpeg`, `png`, `webp`,
262
+ `avif`, `gif`, `svg`, `mov`, `webm` и `mp4`. Внешние URL, шрифты и прочие
263
+ root-relative ресурсы не изменяются.
264
+
265
+ Файлы подключаются от корня:
239
266
 
240
267
  ```html
241
- <img src="/hero.webp" alt="">
268
+ <img src="/images/hero.webp" alt="">
242
269
  ```
243
270
 
244
271
  ```css
245
- .hero {
246
- background-image: url("/hero.webp");
247
- }
272
+ .hero { background-image: url("/images/hero.webp"); }
248
273
  ```
249
274
 
250
- В dev пути направляются на фактический origin текущего Vite-сервера. При
251
- production build они
252
- преобразуются в `<assetsBase>/landing-pages/<folder>/...`. Builder
253
- переписывает изображения `jpg`, `jpeg`, `png`, `webp`, `avif`, `gif`, `svg` и
254
- видео `mov`, `webm`, `mp4`; остальные root-relative ресурсы не изменяются.
275
+ Вложенная структура сохраняется: `public/hotels/room.webp` публикуется как
276
+ `landing-pages/<folder>/hotels/room.webp`.
255
277
 
256
- ### Deploy assets
278
+ ## Deploy ассетов
257
279
 
258
280
  ```bash
259
281
  b2c-landing-vite deploy assets
260
282
  ```
261
283
 
262
- Команда выполняет свежую сборку, сравнивает build manifest с deploy-state и CDN,
263
- а затем показывает план загрузки. Перед отправкой файлов требуется явное
264
- подтверждение; по умолчанию выбран отказ.
284
+ Команда выполняет свежий build, сравнивает manifest с deploy-state и CDN,
285
+ показывает план и запрашивает подтверждение новых загрузок. По умолчанию выбран
286
+ ответ `No`. Deploy-state обновляется атомарно только после успешной загрузки
287
+ всего плана.
288
+
289
+ ### Immutable CDN
290
+
291
+ Существующие CDN-файлы не перезаписываются и не удаляются. Новое содержимое нужно
292
+ публиковать под новым именем и обновлять ссылку.
293
+
294
+ Если локальный файл изменился, а тот же путь уже занят на CDN, команда сообщает
295
+ immutable conflict и не обновляет state. При частичном сетевом сбое она выводит
296
+ уже загруженные пути, но также не принимает новый state целиком.
297
+
298
+ ## Dry-run
299
+
300
+ ```bash
301
+ b2c-landing-vite deploy --dry-run
302
+ b2c-landing-vite deploy --dry-run --section hero
303
+ ```
304
+
305
+ Dry-run выполняет свежий локальный build, читает CDN и CMS и показывает
306
+ фактический план ассетов, страницу, placement и операции над виджетами. Он не
307
+ загружает ассеты, не создаёт checkout, не изменяет CMS и не записывает
308
+ deploy-state, binding или pending-журнал.
309
+
310
+ Каталог `@CMS/` при этом обновляется, поскольку dry-run начинается с build.
311
+
312
+ ## Полный deploy
313
+
314
+ ```bash
315
+ b2c-landing-vite deploy
316
+ b2c-landing-vite deploy --section hero
317
+ ```
318
+
319
+ Deploy последовательно выполняет свежий build, синхронизацию ассетов и CMS
320
+ operation plan. CMS не меняется, если обязательная загрузка ассетов отменена или
321
+ завершилась ошибкой. Перед CMS builder проверяет наличие всех manifest-ассетов
322
+ на CDN.
323
+
324
+ ### Страница и checkout
325
+
326
+ При первом запуске можно создать страницу или выбрать существующую. Builder
327
+ сохраняет `pageId`, `layoutAreaId` и позицию в binding-файле. Затем:
328
+
329
+ - Published выбирается по умолчанию;
330
+ - из Published или Unpublished создаётся Checkout;
331
+ - существующий Checkout обновляется напрямую;
332
+ - потерянную привязку можно восстановить интерактивным выбором.
333
+
334
+ При пустом CMS-плане checkout не создаётся.
335
+
336
+ ### Согласование local, remote и base
337
+
338
+ Builder сравнивает:
339
+
340
+ - `local` — текущие файлы `@CMS/`;
341
+ - `remote` — управляемые виджеты выбранной версии;
342
+ - `base` — подтверждённый снимок последнего deploy.
343
+
344
+ Независимые изменения разных секций объединяются. Конкурирующее изменение одной
345
+ секции не перезаписывается молча: нужно сохранить remote, явно применить local
346
+ или отменить deploy. Без `base` различие local и remote также считается
347
+ потенциальным конфликтом.
348
+
349
+ ### Operation plan и публикация
350
+
351
+ Перед записью показывается исполнимый план добавления, обновления, перестановки и
352
+ удаления управляемых виджетов. Он строится по фактическому checkout и повторно
353
+ проверяется после подготовки версии. Подтверждение по умолчанию — `No`.
354
+
355
+ Изменяющие Backoffice-запросы автоматически не повторяются. После синхронизации
356
+ builder предлагает публикацию. Для неё должны быть заполнены title, URL, SEO
357
+ title и SEO description. В конце выводятся Preview, Backoffice URL и, после
358
+ публикации, live URL.
359
+
360
+ ### Deploy одной секции
361
+
362
+ `deploy --section <key>` ограничивает reconciliation и CMS-план указанной
363
+ секцией. Соседние секции не обновляются и не удаляются. Asset-стадия остаётся
364
+ полной: builder собирает и проверяет весь manifest.
365
+
366
+ Pending полного deploy нельзя продолжить через `--section`, и наоборот: resume
367
+ требует исходный scope.
368
+
369
+ ### Частичная ошибка и resume
370
+
371
+ Перед первой CMS-операцией создаётся pending-журнал. После каждой подтверждённой
372
+ операции в него атомарно записываются deployment id, scope, checkout placement,
373
+ binding, завершённые операции и ожидаемая ревизия.
265
374
 
266
- Вложенная структура `public/` сохраняется на CDN. Например,
267
- `public/hotels/gallery/room.webp` загружается как
268
- `landing-pages/<folder>/hotels/gallery/room.webp`.
375
+ После ошибки следующий deploy того же scope открывает тот же checkout,
376
+ перечитывает его и продолжает по свежему фактическому плану. Новый checkout не
377
+ создаётся. После полного успеха pending удаляется.
269
378
 
270
- Существующие CDN-файлы не перезаписываются и не удаляются. Если содержимое
271
- изменилось, файл нужно переименовать, чтобы опубликовать его по новому URL.
272
- Deploy-state обновляется атомарно только после успешной загрузки всех файлов.
273
- В итоговом блоке команда также выводит корневой URL `CDN folder`, например
274
- `https://b2ccdn.coral.ru/content/landing-pages/uae-promo/`.
379
+ ### Revision guard и ограничения
275
380
 
276
- Обычный `deploy` выполняет этот же план синхронизации ассетов перед изменением
277
- CMS. Если нужны новые файлы, он запрашивает подтверждение загрузки и продолжает
278
- CMS-deploy только после её успешного завершения. При отказе CMS не изменяется.
279
- Отдельная команда `deploy assets` полезна, когда нужно загрузить файлы заранее,
280
- не меняя CMS.
381
+ Перед каждой операцией, перед resume и после последней операции builder повторно
382
+ читает checkout и сравнивает ревизию. При несовпадении следующая операция не
383
+ запускается, а pending сохраняется для проверки.
281
384
 
282
- Перед изменением CMS команда показывает план добавления, обновления, удаления и
283
- перестановки управляемых виджетов. Подтверждение и последующее предложение
284
- публикации по умолчанию имеют значение `No`. После deploy выводятся Preview и
285
- прямая ссылка на созданную checkout-версию в BackOffice.
385
+ Это best-effort защита, не транзакционная блокировка. Без используемого ETag или
386
+ server-side lock остаются:
286
387
 
287
- ## Совместимость
388
+ - TOCTOU-окно между контрольным GET и изменяющим запросом;
389
+ - окно между успешной CMS-операцией и записью pending-журнала.
390
+
391
+ После такого сбоя проверьте checkout в Backoffice перед повторным deploy.
392
+
393
+ ### Локальная блокировка
394
+
395
+ Lock не допускает параллельные deploy одного проекта на одном компьютере, но не
396
+ защищает от другого клона или ручных действий в Backoffice. Lock старше двух
397
+ часов или от завершившегося процесса считается устаревшим.
398
+
399
+ ## Служебные файлы
400
+
401
+ | Путь | Назначение | Git |
402
+ | --- | --- | --- |
403
+ | `.b2c/dev-port` | Локальный dev-порт | Не хранить |
404
+ | `.b2c/deploy/<site>--<folder>.state.json` | Хэши ассетов и CMS base | Хранить для общего state команды |
405
+ | `.b2c/deploy/<site>--<folder>.binding.json` | Привязка к странице и layout area | Хранить, если привязка общая |
406
+ | `.b2c/deploy/<site>--<folder>.pending.json` | Незавершённый deploy | Не хранить |
407
+ | `.b2c/deploy/<site>--<folder>.lock` | Локальная блокировка | Не хранить; удаляется автоматически |
408
+
409
+ Не редактируйте pending и lock во время работающего deploy.
410
+
411
+ ## Аутентификация и окружение
412
+
413
+ При первом обращении к Backoffice builder запрашивает ADFS login и скрытый
414
+ password. Сохраняется только access token, отдельно для каждого бренда, максимум
415
+ на 23 часа. Путь по умолчанию: `~/.b2c-landing-vite/auth.json`.
416
+
417
+ | Переменная | Назначение |
418
+ | --- | --- |
419
+ | `B2C_BACKOFFICE_TOKEN` | Готовый token без ADFS login |
420
+ | `B2C_BACKOFFICE_LOGIN` | Login для неинтерактивного входа |
421
+ | `B2C_BACKOFFICE_PASSWORD` | Password для неинтерактивного входа |
422
+ | `B2C_AUTH_CACHE` | Другой путь к token cache |
423
+ | `B2C_PORT` | Обязательный dev-порт |
424
+ | `B2C_NO_OPEN=1` | Не открывать вкладки автоматически |
425
+
426
+ Не добавляйте cache, пароли и токены в Git. Backoffice GET-запросы имеют timeout
427
+ 30 секунд и до двух повторов при сетевой ошибке, timeout, HTTP 429, 502, 503 или
428
+ 504. Изменяющие запросы не повторяются.
429
+
430
+ ## Обновления
431
+
432
+ ```bash
433
+ b2c-landing-vite update
434
+ ```
435
+
436
+ Команда проверяет npm registry и печатает инструкции, но ничего не устанавливает.
437
+ Короткая проверка также выполняется после рабочих команд; ошибка registry не
438
+ меняет их результат.
439
+
440
+ ```bash
441
+ npm install --global @coraltravelcenter/b2c-landing-builder@latest
442
+ npx @coraltravelcenter/create-b2c-landing-vite@latest migrate
443
+ ```
288
444
 
289
- Builder проверяет `schemaVersion` до запуска команд. Несовместимая версия схемы
290
- блокирует работу. Для аварийной сборки старого проекта можно использовать:
445
+ Аварийная сборка конкретной версией:
291
446
 
292
447
  ```bash
293
448
  npx @coraltravelcenter/b2c-landing-builder@<version> build
294
449
  ```
295
450
 
296
- ## Разработка пакета
451
+ ## Диагностика
452
+
453
+ - **Missing local dependency:** установите зависимость выбранного стека в проект.
454
+ - **Port already in use:** остановите процесс или задайте свободный `B2C_PORT`.
455
+ - **Backoffice is unavailable:** подключитесь к офисной сети или VPN.
456
+ - **Immutable asset conflict:** переименуйте файл и обновите ссылки.
457
+ - **Remote CMS changes:** сравните local и remote и явно выберите стратегию.
458
+ - **Incomplete deploy:** повторите deploy с тем же scope; при revision conflict
459
+ сначала проверьте checkout и не удаляйте pending ради обхода защиты.
460
+
461
+ ## Справочник команд
462
+
463
+ Все команды выполняются из корня проекта.
464
+
465
+ | Команда | Результат |
466
+ | --- | --- |
467
+ | `b2c-landing-vite check` | Проверить проект |
468
+ | `b2c-landing-vite check --strict` | Проверить static/initializer-контракт |
469
+ | `b2c-landing-vite dev` | Запустить Vite и userscript |
470
+ | `b2c-landing-vite build` | Собрать `@CMS/`, ассеты и manifest |
471
+ | `b2c-landing-vite deploy assets` | Синхронизировать только CDN-ассеты |
472
+ | `b2c-landing-vite deploy --dry-run` | Показать полный CDN/CMS-план |
473
+ | `b2c-landing-vite deploy --dry-run --section <key>` | Показать план секции |
474
+ | `b2c-landing-vite deploy` | Синхронизировать ассеты и CMS |
475
+ | `b2c-landing-vite deploy --section <key>` | Синхронизировать ассеты и одну секцию |
476
+ | `b2c-landing-vite block:add <key>` | Создать блок |
477
+ | `b2c-landing-vite block:rename <old> <new>` | Переименовать блок |
478
+ | `b2c-landing-vite update` | Показать ручные обновления |
479
+
480
+ ## Разработка builder
297
481
 
298
482
  ```bash
299
483
  npm ci
300
484
  npm test
301
485
  npm run check
302
486
  npm run build
487
+ npm run test:dev
303
488
  npm pack --dry-run
304
489
  ```
305
490
 
306
- Pull request проверяется на Node.js 20.19 и 22. Prerelease публикуется с npm
307
- dist-tag `next`, стабильная версия — с `latest`.
491
+ Инструкции по публикации: [RELEASING.md](./RELEASING.md).