@coraltravelcenter/b2c-landing-builder 2.11.0 → 2.12.1

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
@@ -30,9 +30,11 @@ b2c-landing-vite update
30
30
  ```
31
31
 
32
32
  - `check` проверяет конфигурацию, `order.json` и контракт JS-блоков.
33
- - `dev` запускает Vite и userscript для Tampermonkey.
33
+ - `dev` запускает Vite и отдельный userscript с названием проекта для
34
+ Tampermonkey.
34
35
  - `build` создаёт CMS-файлы в `@CMS/` и переписывает пути ассетов на CDN.
35
- - `deploy` собирает проект, проверяет ассеты на CDN и разворачивает CMS-виджеты.
36
+ - `deploy` собирает проект, синхронизирует ассеты с CDN и после успешной
37
+ синхронизации разворачивает CMS-виджеты.
36
38
  - `deploy --section <key>` разворачивает только выбранную CMS-секцию, не изменяя
37
39
  и не удаляя соседние секции.
38
40
  - `deploy assets` собирает проект и синхронизирует ассеты с B2C CDN.
@@ -144,6 +146,15 @@ Monkey URL, контейнер и CDN prefix не задаются вручну
144
146
  - контейнер: `#monkey-app`;
145
147
  - CDN prefix: `landing-pages/<project.name>`.
146
148
 
149
+ Dev-сервер начинает с `127.0.0.1:5173` и автоматически выбирает следующий
150
+ свободный порт, поэтому несколько проектов можно запускать одновременно. Если
151
+ задан `B2C_PORT`, порт считается обязательным и занятое значение завершает
152
+ команду ошибкой.
153
+
154
+ Userscript ограничен доменом выбранного бренда и дополнительно проверяет hostname
155
+ перед монтированием. Namespace также содержит бренд, поэтому Coral и Sunmar не
156
+ конфликтуют даже при одинаковом названии проекта.
157
+
147
158
  Для Coral и Sunmar настроены отдельные production CDN. Команда `build`
148
159
  автоматически выбирает CDN по `site.preset`.
149
160
 
@@ -219,7 +230,8 @@ build; дополнительные контейнеры builder не созда
219
230
  }
220
231
  ```
221
232
 
222
- В dev пути направляются на `http://127.0.0.1:5173`. При production build они
233
+ В dev пути направляются на фактический origin текущего Vite-сервера. При
234
+ production build они
223
235
  преобразуются в `<assetsBase>/landing-pages/<folder>/...`. Builder
224
236
  переписывает только изображения `jpg`, `jpeg`, `png`, `webp`, `avif`, `gif` и
225
237
  `svg`; остальные root-relative ресурсы не изменяются.
@@ -244,9 +256,11 @@ Deploy-state обновляется атомарно только после у
244
256
  В итоговом блоке команда также выводит корневой URL `CDN folder`, например
245
257
  `https://b2ccdn.coral.ru/content/landing-pages/uae-promo/`.
246
258
 
247
- Обычный `deploy` не загружает ассеты. Он проверяет наличие всех файлов build
248
- manifest на CDN и останавливается до изменения CMS, если сначала требуется
249
- выполнить `deploy assets`.
259
+ Обычный `deploy` выполняет этот же план синхронизации ассетов перед изменением
260
+ CMS. Если нужны новые файлы, он запрашивает подтверждение загрузки и продолжает
261
+ CMS-deploy только после её успешного завершения. При отказе CMS не изменяется.
262
+ Отдельная команда `deploy assets` полезна, когда нужно загрузить файлы заранее,
263
+ не меняя CMS.
250
264
 
251
265
  Перед изменением CMS команда показывает план добавления, обновления, удаления и
252
266
  перестановки управляемых виджетов. Подтверждение и последующее предложение
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@coraltravelcenter/b2c-landing-builder",
3
- "version": "2.11.0",
3
+ "version": "2.12.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@coraltravelcenter/b2c-landing-builder",
9
- "version": "2.11.0",
9
+ "version": "2.12.1",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@babel/parser": "7.28.6",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coraltravelcenter/b2c-landing-builder",
3
- "version": "2.11.0",
3
+ "version": "2.12.1",
4
4
  "description": "CLI and build toolkit for B2C landing projects",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli/index.mjs CHANGED
@@ -100,14 +100,24 @@ export async function runCli(args) {
100
100
  },
101
101
  });
102
102
  }
103
- await runCli(["build"]);
104
103
  const {getBackofficeToken} = await import("../deploy/auth.mjs");
105
104
  const token = await getBackofficeToken({brand: preset.id});
105
+ const {BackofficeClient} = await import("../deploy/backoffice-client.mjs");
106
+ const client = new BackofficeClient({brand: preset.id, token});
107
+ const {deployAssetsCommand} = await import("../deploy/assets-command.mjs");
108
+ const assets = await deployAssetsCommand({
109
+ config,
110
+ project,
111
+ preset,
112
+ build: () => runCli(["build"]),
113
+ client,
114
+ });
115
+ if (assets.cancelled) return assets;
106
116
  const {deployProject} = await import("../deploy/deploy.mjs");
107
117
  return deployProject({
108
118
  config,
109
119
  preset,
110
- token,
120
+ client,
111
121
  section: sectionOnly ? rest[1] : null,
112
122
  });
113
123
  });
@@ -36,16 +36,24 @@ export async function planAssetChanges({manifest, state, listFiles, remoteRoot =
36
36
  };
37
37
  const upload = [];
38
38
  const skip = [];
39
- await Promise.all(pending.map(async (asset) => {
39
+ let unchanged = 0;
40
+ const pendingPaths = new Set(pending.map((asset) => asset.path));
41
+ await Promise.all(manifest.assets.map(async (asset) => {
40
42
  const remotePath = path.posix.join("/", remoteRoot, manifest.assetsPrefix, asset.path);
41
43
  const names = await namesInDirectory(path.posix.dirname(remotePath));
42
- (names.has(path.posix.basename(remotePath)) ? skip : upload).push(asset);
44
+ const exists = names.has(path.posix.basename(remotePath));
45
+ if (!pendingPaths.has(asset.path)) {
46
+ if (exists) unchanged += 1;
47
+ else upload.push(asset);
48
+ return;
49
+ }
50
+ (exists ? skip : upload).push(asset);
43
51
  }));
44
52
  const byPath = (left, right) => left.path.localeCompare(right.path);
45
53
  return {
46
54
  upload: upload.sort(byPath),
47
55
  skip: skip.sort(byPath),
48
- unchanged: manifest.assets.length - pending.length,
56
+ unchanged,
49
57
  remove: prune ? obsoleteAssets(manifest, state) : [],
50
58
  };
51
59
  }
@@ -30,6 +30,9 @@ function waitForContainer() {
30
30
  });
31
31
  }
32
32
 
33
- const container = await waitForContainer();
34
- mount(container, parts);
35
- setupLocalCdnAssetRewrite({root: container, cdnBase: "http://127.0.0.1:5173"});
33
+ if (window.location.hostname === __B2C_TARGET_HOST__) {
34
+ const container = await waitForContainer();
35
+ mount(container, parts);
36
+ const devOrigin = new URL("/", import.meta.url).origin;
37
+ setupLocalCdnAssetRewrite({root: container, cdnBase: devOrigin});
38
+ }
@@ -18,6 +18,18 @@ export function toViteFsUrl(filePath) {
18
18
  const clientEntryUrl = toViteFsUrl(clientEntry);
19
19
  let monkeyPlugin;
20
20
 
21
+ export function resolveDevServerPort(env = process.env) {
22
+ const explicitPort = env.B2C_PORT;
23
+ if (explicitPort === undefined || explicitPort === "") {
24
+ return {port: 5173, strictPort: false};
25
+ }
26
+ const port = Number(explicitPort);
27
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
28
+ throw new Error("B2C_PORT must be an integer between 1 and 65535");
29
+ }
30
+ return {port, strictPort: true};
31
+ }
32
+
21
33
  async function loadMonkeyPlugin() {
22
34
  if (monkeyPlugin) return monkeyPlugin;
23
35
 
@@ -36,6 +48,7 @@ export async function createViteConfig(root = process.cwd()) {
36
48
  const preset = resolvePreset(config.site.preset);
37
49
  const monkey = await loadMonkeyPlugin();
38
50
  const shouldOpen = process.env.B2C_NO_OPEN !== "1";
51
+ const devPort = resolveDevServerPort();
39
52
  const vue = resolveScriptStack(config.stack.script).vue
40
53
  ? await loadProjectVuePlugin(root)
41
54
  : null;
@@ -45,8 +58,7 @@ export async function createViteConfig(root = process.cwd()) {
45
58
  configFile: false,
46
59
  server: {
47
60
  host: "127.0.0.1",
48
- port: Number(process.env.B2C_PORT || 5173),
49
- strictPort: true,
61
+ ...devPort,
50
62
  cors: true,
51
63
  open: shouldOpen ? "/__vite-plugin-monkey.install.user.js" : false,
52
64
  fs: {allow: [root, builderRoot]},
@@ -59,13 +71,16 @@ export async function createViteConfig(root = process.cwd()) {
59
71
  server: {open: shouldOpen, prefix: false},
60
72
  userscript: {
61
73
  name: config.project.name,
62
- namespace: "b2c-landing-builder",
74
+ namespace: `b2c-landing-builder:${preset.id}`,
63
75
  match: [`https://${preset.domain}/monkey/*`],
64
76
  grant: "none",
65
77
  "run-at": "document-idle",
66
78
  },
67
79
  }),
68
80
  ].filter(Boolean),
81
+ define: {
82
+ __B2C_TARGET_HOST__: JSON.stringify(preset.domain),
83
+ },
69
84
  };
70
85
  }
71
86