@retailcrm/embed-ui 0.9.22-alpha.3 → 0.9.22-alpha.5

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/CHANGELOG.md CHANGED
@@ -1,6 +1,19 @@
1
1
  # Changelog
2
2
 
3
3
 
4
+ ## [0.9.22-alpha.5](https://github.com/retailcrm/embed-ui/compare/v0.9.22-alpha.4...v0.9.22-alpha.5) (2026-05-19)
5
+
6
+ ### Features
7
+
8
+ * Embed UI init flow improved ([50357d6](https://github.com/retailcrm/embed-ui/commit/50357d647d5288c9794e46ef91a36d2da6620101))
9
+ * Embed UI init MCP setup completed ([749c87a](https://github.com/retailcrm/embed-ui/commit/749c87a64bab67b6ed72aed7391ccf24403943ff))
10
+ ## [0.9.22-alpha.4](https://github.com/retailcrm/embed-ui/compare/v0.9.22-alpha.3...v0.9.22-alpha.4) (2026-05-19)
11
+
12
+ ### Bug Fixes
13
+
14
+ * Deprecated deep equality dependency removed ([32abfbc](https://github.com/retailcrm/embed-ui/commit/32abfbc540609b4b3e611ca7dd5331d92be56d83))
15
+ * Embed UI init output noise reduced ([d8d8c7b](https://github.com/retailcrm/embed-ui/commit/d8d8c7b99e1e0201fc4a4a8753931c799fdc005a))
16
+ * Release job hang prevented ([17ca586](https://github.com/retailcrm/embed-ui/commit/17ca5861c2227936bf8ea2b24e2670a17a1e7295))
4
17
  ## [0.9.22-alpha.3](https://github.com/retailcrm/embed-ui/compare/v0.9.22-alpha.2...v0.9.22-alpha.3) (2026-05-18)
5
18
 
6
19
  ### Features
package/README.md CHANGED
@@ -38,9 +38,11 @@ npx @retailcrm/embed-ui --help
38
38
  - добавляет `package.json`, если его еще нет;
39
39
  - добавляет зависимости `@retailcrm/embed-ui*`, `vue`, `pinia`, `vue-i18n` и dev-зависимости для Vite, TypeScript и ESLint;
40
40
  - создает `tsconfig.json`, `vite.config.ts`, `eslint.config.js`, `env.d.ts`;
41
+ - создает или дополняет `.gitignore` типовыми правилами для `node_modules`, `dist`, `coverage`, `.env` и логов;
41
42
  - создает стартовые файлы во frontend-каталоге: endpoint worker, страницу настроек, виджет заказа, i18n JSON-сообщения;
42
43
  - добавляет `extensionrc.json` и `scripts/publish-extension.mjs`;
43
- - добавляет `AGENTS.md` и MCP-настройки пакетов, если они не отключены флагами.
44
+ - добавляет `AGENTS.md` и MCP-настройки пакетов, если они не отключены флагами;
45
+ - может инициализировать Git-репозиторий, если включить `--git` или выбрать это действие в интерактивном режиме.
44
46
 
45
47
  ```bash
46
48
  npx @retailcrm/embed-ui init ./web --package-manager yarn
@@ -52,13 +54,15 @@ npx @retailcrm/embed-ui init ./web --package-manager yarn
52
54
  - frontend-каталог — позиционный аргумент команды, а если его нет, то `./src` для нового проекта или `./web`, если `./src` уже существует;
53
55
  - версия пакетов — версия запущенного CLI;
54
56
  - package manager — определяется по единственному lock-файлу в корне проекта, в интерактивном терминале запрашивается у пользователя, в неинтерактивном режиме используется `npm`;
57
+ - интерактивный выбор package manager показывает только найденные в `PATH` варианты, а явно выбранный недоступный manager останавливает `init` до записи файлов;
55
58
  - пакеты — `@retailcrm/embed-ui`, `v1-components`, `v1-contexts`, `v1-types` и `v1-endpoint`;
56
59
  - установка зависимостей — запускается автоматически после изменения `package.json`;
57
60
  - шаблон — создается стартовая конфигурация для страницы настроек и виджета `order/card:common.after`;
58
61
  - каталоги — создаются `endpoint`, `pages`, `widgets`, `shared`, `i18n` и `i18n/locales` внутри frontend-каталога;
59
62
  - `AGENTS.md` — создается или дополняется инструкциями корневого пакета и выбранных пакетов;
60
63
  - MCP — добавляется `.mcp.json` для `v1-endpoint`;
61
- - client configs для Cursor, Junie и VS Code — не создаются без `--mcp-client-configs`;
64
+ - client configs для Codex CLI, Cursor, Junie и VS Code — не создаются без `--mcp-client-configs`;
65
+ - Git — в неинтерактивном режиме не инициализируется без `--git`, а в интерактивном режиме предлагается, если корень проекта не является Git-репозиторием;
62
66
  - существующие файлы не перезаписываются, а зависимости с потенциальным конфликтом не заменяются без явных force-флагов.
63
67
 
64
68
  По умолчанию команда использует текущий рабочий каталог как корень проекта. Его можно задать явно:
@@ -74,6 +78,7 @@ npx @retailcrm/embed-ui init ./web --cwd ./my-project --package-manager npm
74
78
 
75
79
  - `--no-install` — не запускать установку зависимостей после изменения `package.json`.
76
80
  - `--interactive` — задать основные параметры через TTY-интерфейс со списками выбора, сохранив флаги как явные ограничения.
81
+ - `--verbose` — вывести подробный список изменений после `init`; без него CLI печатает краткую сводку.
77
82
  - `--version 0.9.21` — использовать указанную версию пакетов вместо версии запущенного CLI.
78
83
  - `--exact` — записывать точные версии вместо диапазонов.
79
84
  - `--packages embed-ui,components,contexts,types,endpoint` — явно выбрать пакеты для установки и настройки.
@@ -84,7 +89,8 @@ npx @retailcrm/embed-ui init ./web --cwd ./my-project --package-manager npm
84
89
  - `--no-template` — не создавать стартовые Vue-файлы и `extensionrc.json`.
85
90
  - `--no-agents` — не создавать и не дополнять `AGENTS.md`.
86
91
  - `--no-mcp` — не добавлять MCP-настройки пакетов.
87
- - `--mcp-client-configs cursor,junie,vscode` — дополнительно создать project-level конфиги поддерживаемых AI-клиентов.
92
+ - `--mcp-client-configs codex,cursor,junie,vscode` — дополнительно подключить или создать конфиги поддерживаемых AI-клиентов.
93
+ - `--git` — выполнить `git init` в корне проекта, если каталог еще не является Git-репозиторием.
88
94
 
89
95
  `--force` включает силовой режим для управляемых частей, но учитывает флаги `--no-*`. Например, можно обновить только
90
96
  агентские инструкции и MCP-настройки без перегенерации стартовых файлов:
@@ -110,7 +116,7 @@ npx @retailcrm/embed-ui init ./web --force --no-install --no-template
110
116
  Чтобы добавить только поддерживаемые project-level конфиги, укажите:
111
117
 
112
118
  ```bash
113
- npx @retailcrm/embed-ui init ./web --no-install --mcp-client-configs cursor,junie,vscode
119
+ npx @retailcrm/embed-ui init ./web --no-install --mcp-client-configs codex,cursor,junie,vscode
114
120
  ```
115
121
 
116
122
  Повторный запуск без `--force` не дублирует управляемые секции и записи MCP. При `--force` обновляются только записи
package/bin/embed-ui.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createInterface } from "node:readline/promises";
3
- import { execFileSync } from "node:child_process";
3
+ import { execFileSync, spawn } from "node:child_process";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import fs from "node:fs";
6
6
  import path from "node:path";
@@ -27,13 +27,15 @@ Options:
27
27
  --cwd <path> Project working directory for init
28
28
  --package-manager Package manager for init installs
29
29
  --interactive Ask init questions with TTY selection prompts
30
+ --verbose Print detailed init change lists
30
31
  --no-install Do not run package manager install in init mode
31
32
  --no-configs Do not create root TypeScript, Vite, ESLint and env config files
32
33
  --force-deps Replace incompatible existing init dependencies
33
34
  --fix-sections Move init dependencies to expected package.json sections
34
35
  --no-agents Do not create or update AGENTS.md in init mode
35
36
  --no-mcp Do not add package MCP instructions in init mode
36
- --mcp-client-configs Comma-separated MCP client configs to create (cursor,junie,vscode)
37
+ --mcp-client-configs Comma-separated MCP client configs to create (codex,cursor,junie,vscode)
38
+ --git Initialize Git repository in init mode when cwd is not a Git work tree
37
39
  -h, --help Show this help
38
40
 
39
41
  Examples:
@@ -109,10 +111,14 @@ const parseInitArgs = (argv) => {
109
111
  type: "string",
110
112
  default: "order/card:common.after",
111
113
  describe: "Starter widget target"
112
- }).option("dry-run", { type: "boolean", default: false }).option("exact", { type: "boolean", default: false }).option("interactive", { type: "boolean", default: false }).option("install", { type: "boolean", default: true }).option("force", { type: "boolean", default: false }).option("force-deps", { type: "boolean", default: false }).option("fix-sections", { type: "boolean", default: false }).option("force-files", { type: "boolean", default: false }).option("configs", { type: "boolean", default: true }).option("dirs-enabled", { type: "boolean", default: true }).option("template-enabled", { type: "boolean", default: true }).option("agents", { type: "boolean", default: true }).option("force-agents", { type: "boolean", default: false }).option("agents-only", { type: "boolean", default: false }).option("mcp", { type: "boolean", default: true }).option("force-mcp", { type: "boolean", default: false }).option("mcp-client-configs", {
114
+ }).option("dry-run", { type: "boolean", default: false }).option("exact", { type: "boolean", default: false }).option("interactive", { type: "boolean", default: false }).option("verbose", { type: "boolean", default: false }).option("install", { type: "boolean", default: true }).option("force", { type: "boolean", default: false }).option("force-deps", { type: "boolean", default: false }).option("fix-sections", { type: "boolean", default: false }).option("force-files", { type: "boolean", default: false }).option("configs", { type: "boolean", default: true }).option("dirs-enabled", { type: "boolean", default: true }).option("template-enabled", { type: "boolean", default: true }).option("agents", { type: "boolean", default: true }).option("force-agents", { type: "boolean", default: false }).option("agents-only", { type: "boolean", default: false }).option("mcp", { type: "boolean", default: true }).option("force-mcp", { type: "boolean", default: false }).option("mcp-client-configs", {
113
115
  type: "string",
114
116
  coerce: parsePackageList,
115
117
  describe: "Comma-separated MCP client config ids"
118
+ }).option("git", {
119
+ type: "boolean",
120
+ default: false,
121
+ describe: "Initialize Git repository when cwd is not a Git work tree"
116
122
  }).parseSync();
117
123
  if (parsed.help || parsed.h) {
118
124
  console.log(HELP_TEXT);
@@ -136,6 +142,7 @@ const parseInitArgs = (argv) => {
136
142
  with: parsed.with ?? null,
137
143
  packageManager: parsed.packageManager ?? null,
138
144
  interactive: parsed.interactive,
145
+ verbose: parsed.verbose,
139
146
  noInstall: !parsed.install,
140
147
  force: parsed.force,
141
148
  forceDeps: parsed.forceDeps,
@@ -154,7 +161,8 @@ const parseInitArgs = (argv) => {
154
161
  agentsOnly: parsed.agentsOnly,
155
162
  noMcp: !parsed.mcp,
156
163
  forceMcp: parsed.forceMcp,
157
- mcpClientConfigs: parsed.mcpClientConfigs ?? null
164
+ mcpClientConfigs: parsed.mcpClientConfigs ?? null,
165
+ initGit: parsed.git
158
166
  };
159
167
  };
160
168
  const parseArgs = (argv) => {
@@ -455,12 +463,13 @@ const INIT_RUNTIME_DEPENDENCIES = [
455
463
  { name: "@remote-ui/rpc", range: "^1.4.7" },
456
464
  { name: "pinia", range: "^2.2" },
457
465
  { name: "vue", range: "^3.5" },
458
- { name: "vue-i18n", range: "^11" }
466
+ { name: "vue-i18n", range: "^11" },
467
+ { name: "zod", range: "^4.4" }
459
468
  ];
460
469
  const INIT_DEV_DEPENDENCIES = [
461
470
  { name: "@eslint/js", range: "^9.39" },
462
- { name: "@intlify/eslint-plugin-vue-i18n", range: "~4.3.0" },
463
- { name: "@intlify/unplugin-vue-i18n", range: "^11.1" },
471
+ { name: "@intlify/eslint-plugin-vue-i18n", range: "^4.4" },
472
+ { name: "@intlify/unplugin-vue-i18n", range: "^11.2" },
464
473
  { name: "@omnicajs/eslint-plugin-dependencies", range: "^0.0.2" },
465
474
  { name: "@types/node", range: "^22.19" },
466
475
  { name: "@vitejs/plugin-vue", range: "^6.0" },
@@ -607,13 +616,7 @@ const setDependency = (packageJson, section, name, range, changes, options = {})
607
616
  nextRange
608
617
  });
609
618
  };
610
- const resolveLocalBinPath = (cwd, binName) => {
611
- const binPath = path.join(cwd, "node_modules", ".bin", process.platform === "win32" ? `${binName}.cmd` : binName);
612
- return fs.existsSync(binPath) ? binPath : null;
613
- };
614
- const hasLocalPackage = (cwd, packageName) => fs.existsSync(path.join(cwd, "node_modules", packageName, "package.json"));
615
- const createPackageSpec = (packageName, version) => version && version !== "not used" ? `${packageName}@${version}` : packageName;
616
- const resolvePackageManagerVersion$1 = (packageManager) => {
619
+ const resolvePackageManagerVersion = (packageManager) => {
617
620
  try {
618
621
  return execFileSync(packageManager, ["--version"], {
619
622
  encoding: "utf8",
@@ -623,6 +626,90 @@ const resolvePackageManagerVersion$1 = (packageManager) => {
623
626
  return null;
624
627
  }
625
628
  };
629
+ const isPackageManagerAvailable = (packageManager) => resolvePackageManagerVersion(packageManager) !== null;
630
+ const assertPackageManagerAvailable = (packageManager) => {
631
+ if (!isPackageManagerAvailable(packageManager)) {
632
+ throw new Error(
633
+ `Package manager "${packageManager}" was selected, but its binary was not found in PATH. Install ${packageManager} or choose another package manager.`
634
+ );
635
+ }
636
+ };
637
+ const SPINNER_FRAMES = ["-", "\\", "|", "/"];
638
+ class CommandError extends Error {
639
+ stdout;
640
+ stderr;
641
+ constructor(command, exitCode, stdout, stderr) {
642
+ super(`Command failed${exitCode === null ? "" : ` with exit code ${exitCode}`}: ${command}`);
643
+ this.stdout = stdout;
644
+ this.stderr = stderr;
645
+ }
646
+ }
647
+ const createSpinner = (message) => {
648
+ if (!process.stderr.isTTY) {
649
+ return () => void 0;
650
+ }
651
+ let frameIndex = 0;
652
+ process.stderr.write(`${SPINNER_FRAMES[frameIndex]} ${message}`);
653
+ const timer = setInterval(() => {
654
+ frameIndex = (frameIndex + 1) % SPINNER_FRAMES.length;
655
+ process.stderr.write(`\r${SPINNER_FRAMES[frameIndex]} ${message}`);
656
+ }, 100);
657
+ return () => {
658
+ clearInterval(timer);
659
+ };
660
+ };
661
+ const finishSpinner = (message, status) => {
662
+ if (process.stderr.isTTY) {
663
+ process.stderr.write(`\r${status} ${message}
664
+ `);
665
+ }
666
+ };
667
+ const runCommandWithTerminalStatus = async (command, args, options, message) => {
668
+ const stopSpinner = createSpinner(message);
669
+ const displayCommand = [command, ...args].join(" ");
670
+ let stdoutResult = "";
671
+ let stderrResult = "";
672
+ try {
673
+ await new Promise((resolve, reject) => {
674
+ const child = spawn(command, args, {
675
+ ...options,
676
+ stdio: ["ignore", "pipe", "pipe"]
677
+ });
678
+ const stdout = [];
679
+ const stderr = [];
680
+ child.stdout.on("data", (chunk) => stdout.push(chunk));
681
+ child.stderr.on("data", (chunk) => stderr.push(chunk));
682
+ child.on("error", reject);
683
+ child.on("close", (exitCode) => {
684
+ const stdoutBuffer = Buffer.concat(stdout);
685
+ const stderrBuffer = Buffer.concat(stderr);
686
+ stdoutResult = stdoutBuffer.toString("utf8");
687
+ stderrResult = stderrBuffer.toString("utf8");
688
+ if (exitCode === 0) {
689
+ resolve();
690
+ return;
691
+ }
692
+ reject(new CommandError(displayCommand, exitCode, stdoutBuffer, stderrBuffer));
693
+ });
694
+ });
695
+ finishSpinner(message, "OK");
696
+ return {
697
+ stdout: stdoutResult,
698
+ stderr: stderrResult
699
+ };
700
+ } catch (error) {
701
+ finishSpinner(message, "FAIL");
702
+ throw error;
703
+ } finally {
704
+ stopSpinner();
705
+ }
706
+ };
707
+ const resolveLocalBinPath = (cwd, binName) => {
708
+ const binPath = path.join(cwd, "node_modules", ".bin", process.platform === "win32" ? `${binName}.cmd` : binName);
709
+ return fs.existsSync(binPath) ? binPath : null;
710
+ };
711
+ const hasLocalPackage = (cwd, packageName) => fs.existsSync(path.join(cwd, "node_modules", packageName, "package.json"));
712
+ const createPackageSpec = (packageName, version) => version && version !== "not used" ? `${packageName}@${version}` : packageName;
626
713
  const resolveMajorVersion = (version) => {
627
714
  const major = version?.match(/^\d+/u)?.[0];
628
715
  return major ? Number(major) : null;
@@ -640,7 +727,7 @@ const resolveDownloadCommand = (packageName, binName, packageManager, args, pack
640
727
  source: "transient"
641
728
  };
642
729
  }
643
- const commandArgs2 = ["-y", "-p", packageSpec, binName, ...args];
730
+ const commandArgs2 = ["-y", "--loglevel=error", "-p", packageSpec, binName, ...args];
644
731
  return {
645
732
  command: "npx",
646
733
  args: commandArgs2,
@@ -666,7 +753,7 @@ const resolveDownloadCommand = (packageName, binName, packageManager, args, pack
666
753
  source: "transient"
667
754
  };
668
755
  }
669
- const commandArgs = ["exec", "--yes", "--package", packageSpec, "--", binName, ...args];
756
+ const commandArgs = ["exec", "--yes", "--loglevel=error", "--package", packageSpec, "--", binName, ...args];
670
757
  return {
671
758
  command: "npm",
672
759
  args: commandArgs,
@@ -674,7 +761,7 @@ const resolveDownloadCommand = (packageName, binName, packageManager, args, pack
674
761
  source: "transient"
675
762
  };
676
763
  };
677
- const resolvePackageHookCommand = (cwd, packageName, binName, packageManager, args, packageVersion = null, versionResolver = resolvePackageManagerVersion$1) => {
764
+ const resolvePackageHookCommand = (cwd, packageName, binName, packageManager, args, packageVersion = null, versionResolver = resolvePackageManagerVersion) => {
678
765
  const localBinPath = resolveLocalBinPath(cwd, binName);
679
766
  if (localBinPath) {
680
767
  return {
@@ -692,12 +779,29 @@ const resolvePackageHookCommand = (cwd, packageName, binName, packageManager, ar
692
779
  return resolveDownloadCommand(packageName, binName, packageManager, args, packageVersion, versionResolver);
693
780
  };
694
781
  const getExecErrorMessage = (error) => {
782
+ if (error && typeof error === "object") {
783
+ const output = [
784
+ "stderr" in error ? error.stderr : null,
785
+ "stdout" in error ? error.stdout : null
786
+ ].map((value) => value instanceof Buffer ? value.toString("utf8") : value).filter((value) => typeof value === "string" && value.trim().length > 0).map((value) => value.trim()).join("\n");
787
+ if (output) {
788
+ return output;
789
+ }
790
+ }
695
791
  if (error instanceof Error && error.message) {
696
792
  return error.message;
697
793
  }
698
794
  return String(error);
699
795
  };
700
- const runPackageHookCommand = (cwd, packageName, binName, packageManager, args, failureMode, options, changes) => {
796
+ const appendMcpHookNotices = (output, changes) => {
797
+ for (const line of output.split(/\r?\n/u)) {
798
+ const trimmedLine = line.trim();
799
+ if (trimmedLine.startsWith("MCP: ")) {
800
+ changes.mcp.push(trimmedLine.slice("MCP: ".length));
801
+ }
802
+ }
803
+ };
804
+ const runPackageHookCommand = async (cwd, packageName, binName, packageManager, args, failureMode, options, changes) => {
701
805
  const command = resolvePackageHookCommand(
702
806
  cwd,
703
807
  packageName,
@@ -711,10 +815,13 @@ const runPackageHookCommand = (cwd, packageName, binName, packageManager, args,
711
815
  return;
712
816
  }
713
817
  try {
714
- execFileSync(command.command, command.args, {
715
- cwd,
716
- stdio: "inherit"
717
- });
818
+ const result = await runCommandWithTerminalStatus(
819
+ command.command,
820
+ command.args,
821
+ { cwd },
822
+ `Running ${packageName} ${args[0]}`
823
+ );
824
+ appendMcpHookNotices(result.stdout, changes);
718
825
  } catch (error) {
719
826
  if (command.source === "transient" && failureMode === "advisory") {
720
827
  changes.warnings.push(`Package hook ${command.display} was skipped: ${getExecErrorMessage(error)}`);
@@ -759,12 +866,12 @@ const updateRootAgents = (cwd, options, changes) => {
759
866
  }
760
867
  changes.agents.push(`update ${agentsPath}`);
761
868
  };
762
- const runInitAgentsHook = (packageName, binName, cwd, packageManager, failureMode, options, changes) => {
869
+ const runInitAgentsHook = async (packageName, binName, cwd, packageManager, failureMode, options, changes) => {
763
870
  const args = ["init-agents", cwd];
764
871
  if (options.force || options.forceAgents) {
765
872
  args.push("--force");
766
873
  }
767
- runPackageHookCommand(
874
+ await runPackageHookCommand(
768
875
  cwd,
769
876
  packageName,
770
877
  binName,
@@ -775,7 +882,7 @@ const runInitAgentsHook = (packageName, binName, cwd, packageManager, failureMod
775
882
  changes
776
883
  );
777
884
  };
778
- const applyInitAgents = (cwd, selectedPackages, packageManager, options, changes) => {
885
+ const applyInitAgents = async (cwd, selectedPackages, packageManager, options, changes) => {
779
886
  if (options.noAgents) {
780
887
  return;
781
888
  }
@@ -789,7 +896,7 @@ const applyInitAgents = (cwd, selectedPackages, packageManager, options, changes
789
896
  changes.warnings.push(`Skipping ${selectedPackage.id} ${hook.command} because it currently includes MCP instructions`);
790
897
  continue;
791
898
  }
792
- runInitAgentsHook(
899
+ await runInitAgentsHook(
793
900
  selectedPackage.name,
794
901
  hook.binName,
795
902
  cwd,
@@ -801,7 +908,7 @@ const applyInitAgents = (cwd, selectedPackages, packageManager, options, changes
801
908
  }
802
909
  }
803
910
  };
804
- const applyInitPackageConfigHooks = (cwd, selectedPackages, packageManager, options, changes) => {
911
+ const applyInitPackageConfigHooks = async (cwd, selectedPackages, packageManager, options, changes) => {
805
912
  for (const selectedPackage of selectedPackages) {
806
913
  for (const hook of selectedPackage.hooks ?? []) {
807
914
  if (hook.type !== "config") {
@@ -817,7 +924,7 @@ const applyInitPackageConfigHooks = (cwd, selectedPackages, packageManager, opti
817
924
  if (hook.requiresMcp && options.mcpClientConfigs?.length) {
818
925
  args.push("--mcp-client-configs", options.mcpClientConfigs.join(","));
819
926
  }
820
- runPackageHookCommand(
927
+ await runPackageHookCommand(
821
928
  cwd,
822
929
  selectedPackage.name,
823
930
  hook.binName,
@@ -900,6 +1007,35 @@ const describePathState = (cwd, relativePath) => {
900
1007
  }
901
1008
  return `${relativePath}: found`;
902
1009
  };
1010
+ const isGitWorkTree$2 = (cwd) => {
1011
+ try {
1012
+ execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
1013
+ cwd,
1014
+ encoding: "utf8",
1015
+ stdio: ["ignore", "pipe", "ignore"]
1016
+ });
1017
+ return true;
1018
+ } catch {
1019
+ return false;
1020
+ }
1021
+ };
1022
+ const analyzeGitDirectory = (cwd, options, changes) => {
1023
+ const gitPath = path.join(cwd, ".git");
1024
+ if (!fs.existsSync(gitPath)) {
1025
+ if (options.initGit) {
1026
+ changes.preflight.push("git: missing; git init enabled");
1027
+ }
1028
+ return;
1029
+ }
1030
+ if (isGitWorkTree$2(cwd)) {
1031
+ return;
1032
+ }
1033
+ if (options.initGit) {
1034
+ changes.preflight.push("git: invalid .git metadata; git init enabled");
1035
+ } else {
1036
+ changes.warnings.push(".git exists, but Git does not recognize this directory as a repository; initialize or repair Git metadata before relying on git context");
1037
+ }
1038
+ };
903
1039
  const readExistingPackageJson = (packageJsonPath, changes) => {
904
1040
  if (!fs.existsSync(packageJsonPath)) {
905
1041
  changes.preflight.push("package.json: missing; it will be created");
@@ -1070,6 +1206,7 @@ const applyInitPreflight = (cwd, sourceRoot, packageManager, selectedPackages, v
1070
1206
  if (!options.target && !options.srcDir && fs.existsSync(path.join(cwd, "src")) && path.basename(sourceRoot) === "web") {
1071
1207
  changes.warnings.push("src/ already exists; generated frontend source root resolved to web/");
1072
1208
  }
1209
+ analyzeGitDirectory(cwd, options, changes);
1073
1210
  changes.preflight.push(describePathState(cwd, "src"));
1074
1211
  changes.preflight.push(describePathState(cwd, "web"));
1075
1212
  if (options.noConfigs) {
@@ -2190,6 +2327,7 @@ const createInitChanges = () => ({
2190
2327
  files: [],
2191
2328
  agents: [],
2192
2329
  mcp: [],
2330
+ git: [],
2193
2331
  hooks: [],
2194
2332
  install: null,
2195
2333
  skipped: [],
@@ -2209,6 +2347,10 @@ const printChanges = (changes) => {
2209
2347
  }
2210
2348
  };
2211
2349
  const printInitReport = (cwd, sourceRoot, version, packageManager, changes, options) => {
2350
+ if (!options.verbose && !options.dryRun) {
2351
+ printInitSummary(cwd, sourceRoot, version, packageManager, changes);
2352
+ return;
2353
+ }
2212
2354
  console.log(`CWD: ${cwd}`);
2213
2355
  console.log(`Target: ${sourceRoot}`);
2214
2356
  console.log(`Resolved version: ${version}`);
@@ -2236,7 +2378,8 @@ const printInitReport = (cwd, sourceRoot, version, packageManager, changes, opti
2236
2378
  console.log("");
2237
2379
  console.log("files");
2238
2380
  for (const filePath of changes.files) {
2239
- console.log(` create ${filePath}`);
2381
+ const hasAction = /^(create|update)\s/u.test(filePath);
2382
+ console.log(` ${hasAction ? filePath : `create ${filePath}`}`);
2240
2383
  }
2241
2384
  }
2242
2385
  if (changes.agents.length > 0) {
@@ -2260,6 +2403,13 @@ const printInitReport = (cwd, sourceRoot, version, packageManager, changes, opti
2260
2403
  console.log(` ${options.dryRun ? "would run" : "ran"} ${hook}`);
2261
2404
  }
2262
2405
  }
2406
+ if (changes.git.length > 0) {
2407
+ console.log("");
2408
+ console.log("git");
2409
+ for (const gitChange of changes.git) {
2410
+ console.log(` ${gitChange}`);
2411
+ }
2412
+ }
2263
2413
  if (changes.install) {
2264
2414
  console.log("");
2265
2415
  console.log("install");
@@ -2284,6 +2434,43 @@ const printInitReport = (cwd, sourceRoot, version, packageManager, changes, opti
2284
2434
  console.log("Dry run enabled, no files were modified.");
2285
2435
  }
2286
2436
  };
2437
+ const printInitSummary = (cwd, sourceRoot, version, packageManager, changes) => {
2438
+ console.log(`Initialized ${cwd}`);
2439
+ console.log(` source root: ${sourceRoot}`);
2440
+ console.log(` version: ${version}`);
2441
+ console.log(` package manager: ${packageManager}`);
2442
+ const summary = [
2443
+ changes.packageJson.length ? `package.json updated (${changes.packageJson.length} change(s))` : null,
2444
+ changes.directories.length ? `directories created: ${changes.directories.length}` : null,
2445
+ changes.files.length ? `files changed: ${changes.files.length}` : null,
2446
+ changes.agents.length ? "AGENTS.md updated" : null,
2447
+ changes.mcp.length ? "MCP config updated" : null,
2448
+ changes.git.length ? `git: ${changes.git.join(", ")}` : null,
2449
+ changes.hooks.length ? `package hooks ran: ${changes.hooks.length}` : null,
2450
+ changes.install ? `install: ${changes.install}` : null
2451
+ ].filter((item) => typeof item === "string");
2452
+ if (summary.length > 0) {
2453
+ console.log("");
2454
+ console.log("changes");
2455
+ for (const item of summary) {
2456
+ console.log(` ${item}`);
2457
+ }
2458
+ }
2459
+ if (changes.skipped.length > 0) {
2460
+ console.log("");
2461
+ console.log("skipped");
2462
+ for (const skipped of changes.skipped) {
2463
+ console.log(` ${skipped}`);
2464
+ }
2465
+ }
2466
+ if (changes.warnings.length > 0) {
2467
+ console.log("");
2468
+ console.log("warnings");
2469
+ for (const warning of new Set(changes.warnings)) {
2470
+ console.log(` ${warning}`);
2471
+ }
2472
+ }
2473
+ };
2287
2474
  const isUpKey = (key, keybindings = []) => (
2288
2475
  // The up key
2289
2476
  key.name === "up" || // Vim keybinding: hjkl keys map to left/down/up/right
@@ -4320,6 +4507,7 @@ const INIT_ACTION_LABELS = {
4320
4507
  template: "Создать стартовый шаблон",
4321
4508
  agents: "Обновить AGENTS.md",
4322
4509
  mcp: "Добавить MCP-настройки",
4510
+ git: "Инициализировать Git",
4323
4511
  install: "Запустить установку зависимостей"
4324
4512
  };
4325
4513
  const INIT_ACTION_DESCRIPTIONS = {
@@ -4327,8 +4515,20 @@ const INIT_ACTION_DESCRIPTIONS = {
4327
4515
  template: "Vue-точка входа, страница настроек, виджет заказа, i18n и publish script",
4328
4516
  agents: "Общие и пакетные инструкции для AI-агентов",
4329
4517
  mcp: ".mcp.json и MCP-инструкции пакетов",
4518
+ git: "git init в каталоге проекта, если Git еще не настроен",
4330
4519
  install: "Запуск выбранного package manager после изменения package.json"
4331
4520
  };
4521
+ const isGitWorkTree$1 = (cwd) => {
4522
+ try {
4523
+ execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
4524
+ cwd,
4525
+ stdio: ["ignore", "pipe", "ignore"]
4526
+ });
4527
+ return true;
4528
+ } catch {
4529
+ return false;
4530
+ }
4531
+ };
4332
4532
  const resolveDefaultSourceRoot = (cwd, options) => {
4333
4533
  if (options.srcDir) {
4334
4534
  return options.srcDir;
@@ -4373,6 +4573,9 @@ const resolveAvailableActions = (options) => {
4373
4573
  if (!options.noMcp) {
4374
4574
  actions.push("mcp");
4375
4575
  }
4576
+ if (!options.agentsOnly && !isGitWorkTree$1(options.cwd)) {
4577
+ actions.push("git");
4578
+ }
4376
4579
  if (!options.agentsOnly && !options.noInstall) {
4377
4580
  actions.push("install");
4378
4581
  }
@@ -4384,6 +4587,7 @@ const applyPromptedActions = (options, selectedActions) => {
4384
4587
  options.noTemplate = options.noTemplate || !selectedActionSet.has("template");
4385
4588
  options.noAgents = options.noAgents || !selectedActionSet.has("agents");
4386
4589
  options.noMcp = options.noMcp || !selectedActionSet.has("mcp");
4590
+ options.initGit = options.initGit || selectedActionSet.has("git");
4387
4591
  options.noInstall = options.noInstall || !selectedActionSet.has("install");
4388
4592
  };
4389
4593
  const resolvePromptedActions = async (options) => {
@@ -4406,12 +4610,18 @@ const resolvePromptedPackageManager = async (detectedPackageManager, explicitPac
4406
4610
  return explicitPackageManager;
4407
4611
  }
4408
4612
  const defaultPackageManager = detectedPackageManager ?? "npm";
4613
+ const availablePackageManagers = PACKAGE_MANAGERS.filter(isPackageManagerAvailable);
4614
+ if (availablePackageManagers.length === 0) {
4615
+ throw new Error("No supported package manager binary was found in PATH. Install npm, yarn, pnpm, or bun and rerun init.");
4616
+ }
4409
4617
  return select({
4410
4618
  message: "Package manager",
4411
- default: defaultPackageManager,
4619
+ default: availablePackageManagers.includes(defaultPackageManager) ? defaultPackageManager : availablePackageManagers[0],
4412
4620
  choices: PACKAGE_MANAGERS.map((packageManager) => ({
4413
4621
  name: packageManager,
4414
- value: packageManager
4622
+ value: packageManager,
4623
+ description: isPackageManagerAvailable(packageManager) ? "found in PATH" : "not found in PATH",
4624
+ disabled: isPackageManagerAvailable(packageManager) ? false : "not found in PATH"
4415
4625
  }))
4416
4626
  });
4417
4627
  };
@@ -4445,6 +4655,49 @@ const resolveInteractiveInitOptions = async (cwd, options, detectedPackageManage
4445
4655
  );
4446
4656
  return nextOptions;
4447
4657
  };
4658
+ const GITIGNORE_SECTION_HEADER = "# RetailCRM embed-ui init";
4659
+ const REQUIRED_GITIGNORE_ENTRIES = [
4660
+ "node_modules/",
4661
+ "dist/",
4662
+ "coverage/",
4663
+ ".env",
4664
+ ".env.*",
4665
+ "!.env.example",
4666
+ "*.log",
4667
+ "npm-debug.log*",
4668
+ "yarn-debug.log*",
4669
+ "yarn-error.log*",
4670
+ "pnpm-debug.log*",
4671
+ ".DS_Store"
4672
+ ];
4673
+ const normalizeGitignoreLine = (line) => line.trim().replace(/\/$/u, "");
4674
+ const hasGitignoreEntry = (lines, entry) => {
4675
+ const normalizedEntry = normalizeGitignoreLine(entry);
4676
+ return lines.some((line) => normalizeGitignoreLine(line) === normalizedEntry);
4677
+ };
4678
+ const updateGitignore = (cwd, options, changes) => {
4679
+ if (options.agentsOnly) {
4680
+ return;
4681
+ }
4682
+ const gitignorePath = path.join(cwd, ".gitignore");
4683
+ const fileExists = fs.existsSync(gitignorePath);
4684
+ const currentContent = fileExists ? fs.readFileSync(gitignorePath, "utf8") : "";
4685
+ const lines = currentContent.split(/\r?\n/u);
4686
+ const missingEntries = REQUIRED_GITIGNORE_ENTRIES.filter((entry) => !hasGitignoreEntry(lines, entry));
4687
+ if (missingEntries.length === 0) {
4688
+ changes.skipped.push(`${gitignorePath} already contains required init entries`);
4689
+ return;
4690
+ }
4691
+ const section = [
4692
+ GITIGNORE_SECTION_HEADER,
4693
+ ...missingEntries
4694
+ ].join(DEFAULT_NEWLINE);
4695
+ const nextContent = currentContent.trimEnd() ? `${currentContent.trimEnd()}${DEFAULT_NEWLINE}${DEFAULT_NEWLINE}${section}${DEFAULT_NEWLINE}` : `${section}${DEFAULT_NEWLINE}`;
4696
+ if (!options.dryRun) {
4697
+ fs.writeFileSync(gitignorePath, nextContent, "utf8");
4698
+ }
4699
+ changes.files.push(`${fileExists ? "update" : "create"} ${gitignorePath}`);
4700
+ };
4448
4701
  const DEFAULT_INIT_DIRS = ["endpoint", "pages", "widgets", "shared", "i18n"];
4449
4702
  const detectPackageManagerByLockfile = (cwd) => {
4450
4703
  const knownLockfiles = [
@@ -4456,15 +4709,9 @@ const detectPackageManagerByLockfile = (cwd) => {
4456
4709
  const lockfiles = knownLockfiles.filter(({ file }) => fs.existsSync(path.join(cwd, file)));
4457
4710
  return lockfiles.length === 1 ? lockfiles[0].packageManager : null;
4458
4711
  };
4459
- const resolvePackageManagerVersion = (packageManager) => {
4460
- try {
4461
- return execFileSync(packageManager, ["--version"], {
4462
- encoding: "utf8",
4463
- stdio: ["ignore", "pipe", "ignore"]
4464
- }).trim();
4465
- } catch {
4466
- return null;
4467
- }
4712
+ const resolvePackageManagerMajorVersion = (packageManager) => {
4713
+ const major = resolvePackageManagerVersion(packageManager)?.match(/^\d+/u)?.[0];
4714
+ return major ? Number(major) : null;
4468
4715
  };
4469
4716
  const promptForPackageManager = async () => {
4470
4717
  const readline2 = createInterface({
@@ -4509,6 +4756,16 @@ const resolveInitPackages = (tokens, extraTokens) => {
4509
4756
  }
4510
4757
  return packages;
4511
4758
  };
4759
+ const hasEnabledPackageHook = (selectedPackages, options) => selectedPackages.some((selectedPackage) => selectedPackage.hooks?.some((hook) => {
4760
+ if (hook.type === "agents") {
4761
+ return !options.noAgents && (!hook.requiresMcp || !options.noMcp);
4762
+ }
4763
+ if (hook.type === "config") {
4764
+ return !options.agentsOnly && (!hook.requiresMcp || !options.noMcp);
4765
+ }
4766
+ return false;
4767
+ }) ?? false);
4768
+ const shouldRequirePackageManagerBinary = (selectedPackages, options) => !options.dryRun && (!options.noInstall || hasEnabledPackageHook(selectedPackages, options));
4512
4769
  const resolveInitCwd = (options) => {
4513
4770
  const cwd = path.resolve(options.cwd);
4514
4771
  if (!fs.existsSync(cwd)) {
@@ -4532,6 +4789,17 @@ const resolveInitSourceRoot = (cwd, options) => {
4532
4789
  }
4533
4790
  return path.join(cwd, "web");
4534
4791
  };
4792
+ const isGitWorkTree = (cwd) => {
4793
+ try {
4794
+ execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
4795
+ cwd,
4796
+ stdio: ["ignore", "pipe", "ignore"]
4797
+ });
4798
+ return true;
4799
+ } catch {
4800
+ return false;
4801
+ }
4802
+ };
4535
4803
  const runUpdate = (options) => {
4536
4804
  const version = options.version ?? resolveLatestVersion();
4537
4805
  const packageJsonPaths = collectPackageJsonPaths(options.target);
@@ -4698,7 +4966,7 @@ const applyInitTemplate = (cwd, sourceRoot, packageManager, options, changes) =>
4698
4966
  writeFileIfAllowed(path.join(cwd, "scripts/publish-extension.mjs"), createPublishScript(), options, changes);
4699
4967
  writeFileIfAllowed(path.join(cwd, "README.md"), createReadme(cwd, sourceRoot, options, packageManager), options, changes);
4700
4968
  };
4701
- const runInstall = (cwd, packageManager, options, changes, packageJsonChanged) => {
4969
+ const runInstall = async (cwd, packageManager, options, changes, packageJsonChanged) => {
4702
4970
  if (options.noInstall || options.agentsOnly) {
4703
4971
  return;
4704
4972
  }
@@ -4706,15 +4974,64 @@ const runInstall = (cwd, packageManager, options, changes, packageJsonChanged) =
4706
4974
  changes.skipped.push("install skipped because package.json was not changed");
4707
4975
  return;
4708
4976
  }
4709
- const args = ["install"];
4977
+ const args = resolveInstallArgs(packageManager);
4710
4978
  changes.install = `${packageManager} ${args.join(" ")}`;
4711
4979
  if (options.dryRun) {
4712
4980
  return;
4713
4981
  }
4714
- execFileSync(packageManager, args, {
4715
- cwd,
4716
- stdio: "inherit"
4717
- });
4982
+ try {
4983
+ await runCommandWithTerminalStatus(
4984
+ packageManager,
4985
+ args,
4986
+ { cwd },
4987
+ `Installing dependencies with ${packageManager}`
4988
+ );
4989
+ } catch (error) {
4990
+ printExecErrorOutput(error);
4991
+ throw error;
4992
+ }
4993
+ };
4994
+ const applyInitGit = async (cwd, options, changes) => {
4995
+ if (!options.initGit) {
4996
+ return;
4997
+ }
4998
+ if (isGitWorkTree(cwd)) {
4999
+ changes.skipped.push("git init skipped because cwd is already inside a Git work tree");
5000
+ return;
5001
+ }
5002
+ changes.git.push("git init");
5003
+ if (options.dryRun) {
5004
+ return;
5005
+ }
5006
+ await runCommandWithTerminalStatus("git", ["init"], { cwd }, "Initializing Git repository");
5007
+ };
5008
+ const resolveInstallArgs = (packageManager) => {
5009
+ if (packageManager === "yarn" && resolvePackageManagerMajorVersion(packageManager) === 1) {
5010
+ return ["install", "--silent"];
5011
+ }
5012
+ if (packageManager === "npm") {
5013
+ return ["install", "--loglevel=error"];
5014
+ }
5015
+ if (packageManager === "pnpm") {
5016
+ return ["install", "--loglevel=error"];
5017
+ }
5018
+ if (packageManager === "bun") {
5019
+ return ["install", "--silent"];
5020
+ }
5021
+ return ["install"];
5022
+ };
5023
+ const printExecErrorOutput = (error) => {
5024
+ if (!error || typeof error !== "object") {
5025
+ return;
5026
+ }
5027
+ const execError = error;
5028
+ for (const field of ["stdout", "stderr"]) {
5029
+ const value = execError[field];
5030
+ const output = value instanceof Buffer ? value.toString("utf8") : value;
5031
+ if (typeof output === "string" && output.trim().length > 0) {
5032
+ console.error(output.trim());
5033
+ }
5034
+ }
4718
5035
  };
4719
5036
  const runInit = async (options) => {
4720
5037
  const cwd = resolveInitCwd(options);
@@ -4732,17 +5049,22 @@ const runInit = async (options) => {
4732
5049
  const resolvedOptions = version === "not used" ? interactiveOptions : { ...interactiveOptions, version };
4733
5050
  const packageManager = interactiveOptions.agentsOnly ? interactiveOptions.packageManager ?? detectPackageManagerByLockfile(cwd) ?? "npm" : await resolvePackageManager(cwd, interactiveOptions.packageManager);
4734
5051
  const changes = createInitChanges();
5052
+ if (shouldRequirePackageManagerBinary(selectedPackages, resolvedOptions)) {
5053
+ assertPackageManagerAvailable(packageManager);
5054
+ }
4735
5055
  applyInitPreflight(cwd, sourceRoot, packageManager, selectedPackages, version, resolvedOptions, changes);
5056
+ await applyInitGit(cwd, resolvedOptions, changes);
4736
5057
  let packageJsonPath = null;
4737
5058
  if (!resolvedOptions.agentsOnly) {
4738
5059
  packageJsonPath = applyInitPackageJson(cwd, selectedPackages, version, packageManager, resolvedOptions, changes);
5060
+ updateGitignore(cwd, resolvedOptions, changes);
4739
5061
  applyInitDirectories(sourceRoot, resolvedOptions, changes);
4740
5062
  applyInitConfigs(cwd, sourceRoot, resolvedOptions, changes);
4741
5063
  applyInitTemplate(cwd, sourceRoot, packageManager, resolvedOptions, changes);
4742
- applyInitPackageConfigHooks(cwd, selectedPackages, packageManager, resolvedOptions, changes);
4743
5064
  }
4744
- applyInitAgents(cwd, selectedPackages, packageManager, resolvedOptions, changes);
4745
- runInstall(cwd, packageManager, resolvedOptions, changes, Boolean(packageJsonPath && changes.packageJson.length > 0));
5065
+ await runInstall(cwd, packageManager, resolvedOptions, changes, Boolean(packageJsonPath && changes.packageJson.length > 0));
5066
+ await applyInitPackageConfigHooks(cwd, selectedPackages, packageManager, resolvedOptions, changes);
5067
+ await applyInitAgents(cwd, selectedPackages, packageManager, resolvedOptions, changes);
4746
5068
  printInitReport(cwd, sourceRoot, version, packageManager, changes, resolvedOptions);
4747
5069
  };
4748
5070
  const main = async (argv = process$2.argv.slice(2)) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@retailcrm/embed-ui",
3
3
  "type": "module",
4
- "version": "0.9.22-alpha.3",
4
+ "version": "0.9.22-alpha.5",
5
5
  "description": "API and components for creating RetailCRM UI extensions",
6
6
  "repository": "git@github.com:retailcrm/embed-ui.git",
7
7
  "author": "RetailDriverLLC <integration@retailcrm.ru>",
@@ -54,10 +54,10 @@
54
54
  "@omnicajs/symfony-router": "^1.0.0",
55
55
  "@omnicajs/vue-remote": "^0.2.23",
56
56
  "@remote-ui/rpc": "^1.4.5",
57
- "@retailcrm/embed-ui-v1-components": "^0.9.22-alpha.3",
58
- "@retailcrm/embed-ui-v1-contexts": "^0.9.22-alpha.3",
59
- "@retailcrm/embed-ui-v1-endpoint": "^0.9.22-alpha.3",
60
- "@retailcrm/embed-ui-v1-types": "^0.9.22-alpha.3",
57
+ "@retailcrm/embed-ui-v1-components": "^0.9.22-alpha.5",
58
+ "@retailcrm/embed-ui-v1-contexts": "^0.9.22-alpha.5",
59
+ "@retailcrm/embed-ui-v1-endpoint": "^0.9.22-alpha.5",
60
+ "@retailcrm/embed-ui-v1-types": "^0.9.22-alpha.5",
61
61
  "yargs": "^17.7.2"
62
62
  },
63
63
  "devDependencies": {
@@ -66,7 +66,7 @@
66
66
  "@modulify/git-toolkit": "^0.0.2",
67
67
  "@modulify/pkg": "^1.0.1",
68
68
  "@omnicajs/eslint-plugin-dependencies": "^0.0.2",
69
- "@retailcrm/embed-ui-v1-testing": "^0.9.22-alpha.3",
69
+ "@retailcrm/embed-ui-v1-testing": "^0.9.22-alpha.5",
70
70
  "@types/git-semver-tags": "^7.0.0",
71
71
  "@types/node": "^22.19.2",
72
72
  "@types/semver": "^7.7.1",