@retailcrm/embed-ui 0.9.22-alpha.4 → 0.9.22-alpha.7

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,27 @@
1
1
  # Changelog
2
2
 
3
3
 
4
+ ## [0.9.22-alpha.7](https://github.com/retailcrm/embed-ui/compare/v0.9.22-alpha.6...v0.9.22-alpha.7) (2026-05-20)
5
+
6
+ ### Bug Fixes
7
+
8
+ * Package manifests normalized for npm publish ([c612b50](https://github.com/retailcrm/embed-ui/commit/c612b5040ba79bab6c88360554b8f156b48e3721))
9
+ ## [0.9.22-alpha.6](https://github.com/retailcrm/embed-ui/compare/v0.9.22-alpha.5...v0.9.22-alpha.6) (2026-05-20)
10
+
11
+ ### Features
12
+
13
+ * MCP client config selection added ([e46fccd](https://github.com/retailcrm/embed-ui/commit/e46fccde382e6d0af2550932e00d41f8a3bcc1af))
14
+
15
+ ### Bug Fixes
16
+
17
+ * **v1-endpoint:** MCP startup probes handled ([9695b77](https://github.com/retailcrm/embed-ui/commit/9695b779de22fdd73c977546e592e327db4f5f1a))
18
+ * **v1-endpoint:** Project-level MCP config generated ([16ff7a2](https://github.com/retailcrm/embed-ui/commit/16ff7a2a0321ba01c186da0f6ad3664930bd1de4))
19
+ ## [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)
20
+
21
+ ### Features
22
+
23
+ * Embed UI init flow improved ([50357d6](https://github.com/retailcrm/embed-ui/commit/50357d647d5288c9794e46ef91a36d2da6620101))
24
+ * Embed UI init MCP setup completed ([749c87a](https://github.com/retailcrm/embed-ui/commit/749c87a64bab67b6ed72aed7391ccf24403943ff))
4
25
  ## [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)
5
26
 
6
27
  ### Bug Fixes
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
+ - project-level client configs для Codex CLI, Cursor, Junie и VS Code — не создаются без `--mcp-client-configs`;
65
+ - Git — в неинтерактивном режиме не инициализируется без `--git`, а в интерактивном режиме предлагается, если корень проекта не является Git-репозиторием;
62
66
  - существующие файлы не перезаписываются, а зависимости с потенциальным конфликтом не заменяются без явных force-флагов.
63
67
 
64
68
  По умолчанию команда использует текущий рабочий каталог как корень проекта. Его можно задать явно:
@@ -85,7 +89,8 @@ npx @retailcrm/embed-ui init ./web --cwd ./my-project --package-manager npm
85
89
  - `--no-template` — не создавать стартовые Vue-файлы и `extensionrc.json`.
86
90
  - `--no-agents` — не создавать и не дополнять `AGENTS.md`.
87
91
  - `--no-mcp` — не добавлять MCP-настройки пакетов.
88
- - `--mcp-client-configs cursor,junie,vscode` — дополнительно создать project-level конфиги поддерживаемых AI-клиентов.
92
+ - `--mcp-client-configs codex,cursor,junie,vscode` — дополнительно создать project-level конфиги поддерживаемых AI-клиентов.
93
+ - `--git` — выполнить `git init` в корне проекта, если каталог еще не является Git-репозиторием.
89
94
 
90
95
  `--force` включает силовой режим для управляемых частей, но учитывает флаги `--no-*`. Например, можно обновить только
91
96
  агентские инструкции и MCP-настройки без перегенерации стартовых файлов:
@@ -101,8 +106,9 @@ npx @retailcrm/embed-ui init ./web --force --no-install --no-template
101
106
  - `@retailcrm/embed-ui-v1-components` — добавляет порядок чтения README, AI-документации и YAML-профилей компонентов.
102
107
  - `@retailcrm/embed-ui-v1-endpoint` — добавляет инструкции по целям виджетов и MCP-ресурсам.
103
108
 
104
- Для `v1-endpoint` также создается `.mcp.json` с сервером `retailcrm-embed-ui-v1-endpoint`. Он отдает AI-friendly
105
- описания целей виджетов через ресурсы:
109
+ Для `v1-endpoint` также создается `.mcp.json` с сервером `retailcrm-embed-ui-v1-endpoint`. Этот файл рассчитан на
110
+ Claude Code project scope и использует `${CLAUDE_PROJECT_DIR:-.}/node_modules/.bin/embed-ui-v1-endpoint-mcp`, чтобы
111
+ сервер резолвился относительно открытого проекта. MCP отдает AI-friendly описания целей виджетов через ресурсы:
106
112
 
107
113
  - `embed-ui-v1-endpoint://targets`;
108
114
  - `embed-ui-v1-endpoint://targets/<encoded-target>`.
@@ -111,9 +117,16 @@ npx @retailcrm/embed-ui init ./web --force --no-install --no-template
111
117
  Чтобы добавить только поддерживаемые project-level конфиги, укажите:
112
118
 
113
119
  ```bash
114
- npx @retailcrm/embed-ui init ./web --no-install --mcp-client-configs cursor,junie,vscode
120
+ npx @retailcrm/embed-ui init ./web --no-install --mcp-client-configs codex,cursor,junie,vscode
115
121
  ```
116
122
 
123
+ Для Codex CLI создается `.codex/config.toml`. Codex подхватывает его только в trusted project, поэтому после генерации
124
+ проект нужно открыть/перезапустить в Codex и доверить ему проект, если клиент спросит. User-level подключение через
125
+ `codex mcp add` намеренно не выполняется автоматически: на одной машине могут быть проекты с разными версиями
126
+ `@retailcrm/embed-ui-v1-endpoint`, а user-level серверы с одинаковыми MCP resource URI могут конфликтовать между собой.
127
+ Для Cursor и VS Code CLI генерирует project-level конфиги с `${workspaceFolder}/node_modules/.bin/...`; для Junie
128
+ остается относительный `./node_modules/.bin/...`.
129
+
117
130
  Повторный запуск без `--force` не дублирует управляемые секции и записи MCP. При `--force` обновляются только записи
118
131
  этого пакета, ручные серверы и соседние разделы не удаляются.
119
132
 
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";
@@ -34,7 +34,8 @@ Options:
34
34
  --fix-sections Move init dependencies to expected package.json sections
35
35
  --no-agents Do not create or update AGENTS.md in init mode
36
36
  --no-mcp Do not add package MCP instructions in init mode
37
- --mcp-client-configs Comma-separated MCP client configs to create (cursor,junie,vscode)
37
+ --mcp-client-configs Comma-separated project-level 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
38
39
  -h, --help Show this help
39
40
 
40
41
  Examples:
@@ -114,6 +115,10 @@ const parseInitArgs = (argv) => {
114
115
  type: "string",
115
116
  coerce: parsePackageList,
116
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"
117
122
  }).parseSync();
118
123
  if (parsed.help || parsed.h) {
119
124
  console.log(HELP_TEXT);
@@ -156,7 +161,8 @@ const parseInitArgs = (argv) => {
156
161
  agentsOnly: parsed.agentsOnly,
157
162
  noMcp: !parsed.mcp,
158
163
  forceMcp: parsed.forceMcp,
159
- mcpClientConfigs: parsed.mcpClientConfigs ?? null
164
+ mcpClientConfigs: parsed.mcpClientConfigs ?? null,
165
+ initGit: parsed.git
160
166
  };
161
167
  };
162
168
  const parseArgs = (argv) => {
@@ -610,13 +616,7 @@ const setDependency = (packageJson, section, name, range, changes, options = {})
610
616
  nextRange
611
617
  });
612
618
  };
613
- const resolveLocalBinPath = (cwd, binName) => {
614
- const binPath = path.join(cwd, "node_modules", ".bin", process.platform === "win32" ? `${binName}.cmd` : binName);
615
- return fs.existsSync(binPath) ? binPath : null;
616
- };
617
- const hasLocalPackage = (cwd, packageName) => fs.existsSync(path.join(cwd, "node_modules", packageName, "package.json"));
618
- const createPackageSpec = (packageName, version) => version && version !== "not used" ? `${packageName}@${version}` : packageName;
619
- const resolvePackageManagerVersion$1 = (packageManager) => {
619
+ const resolvePackageManagerVersion = (packageManager) => {
620
620
  try {
621
621
  return execFileSync(packageManager, ["--version"], {
622
622
  encoding: "utf8",
@@ -626,6 +626,90 @@ const resolvePackageManagerVersion$1 = (packageManager) => {
626
626
  return null;
627
627
  }
628
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;
629
713
  const resolveMajorVersion = (version) => {
630
714
  const major = version?.match(/^\d+/u)?.[0];
631
715
  return major ? Number(major) : null;
@@ -677,7 +761,7 @@ const resolveDownloadCommand = (packageName, binName, packageManager, args, pack
677
761
  source: "transient"
678
762
  };
679
763
  };
680
- const resolvePackageHookCommand = (cwd, packageName, binName, packageManager, args, packageVersion = null, versionResolver = resolvePackageManagerVersion$1) => {
764
+ const resolvePackageHookCommand = (cwd, packageName, binName, packageManager, args, packageVersion = null, versionResolver = resolvePackageManagerVersion) => {
681
765
  const localBinPath = resolveLocalBinPath(cwd, binName);
682
766
  if (localBinPath) {
683
767
  return {
@@ -709,7 +793,15 @@ const getExecErrorMessage = (error) => {
709
793
  }
710
794
  return String(error);
711
795
  };
712
- 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) => {
713
805
  const command = resolvePackageHookCommand(
714
806
  cwd,
715
807
  packageName,
@@ -723,10 +815,13 @@ const runPackageHookCommand = (cwd, packageName, binName, packageManager, args,
723
815
  return;
724
816
  }
725
817
  try {
726
- execFileSync(command.command, command.args, {
727
- cwd,
728
- stdio: ["ignore", "pipe", "pipe"]
729
- });
818
+ const result = await runCommandWithTerminalStatus(
819
+ command.command,
820
+ command.args,
821
+ { cwd },
822
+ `Running ${packageName} ${args[0]}`
823
+ );
824
+ appendMcpHookNotices(result.stdout, changes);
730
825
  } catch (error) {
731
826
  if (command.source === "transient" && failureMode === "advisory") {
732
827
  changes.warnings.push(`Package hook ${command.display} was skipped: ${getExecErrorMessage(error)}`);
@@ -771,12 +866,12 @@ const updateRootAgents = (cwd, options, changes) => {
771
866
  }
772
867
  changes.agents.push(`update ${agentsPath}`);
773
868
  };
774
- const runInitAgentsHook = (packageName, binName, cwd, packageManager, failureMode, options, changes) => {
869
+ const runInitAgentsHook = async (packageName, binName, cwd, packageManager, failureMode, options, changes) => {
775
870
  const args = ["init-agents", cwd];
776
871
  if (options.force || options.forceAgents) {
777
872
  args.push("--force");
778
873
  }
779
- runPackageHookCommand(
874
+ await runPackageHookCommand(
780
875
  cwd,
781
876
  packageName,
782
877
  binName,
@@ -787,7 +882,7 @@ const runInitAgentsHook = (packageName, binName, cwd, packageManager, failureMod
787
882
  changes
788
883
  );
789
884
  };
790
- const applyInitAgents = (cwd, selectedPackages, packageManager, options, changes) => {
885
+ const applyInitAgents = async (cwd, selectedPackages, packageManager, options, changes) => {
791
886
  if (options.noAgents) {
792
887
  return;
793
888
  }
@@ -801,7 +896,7 @@ const applyInitAgents = (cwd, selectedPackages, packageManager, options, changes
801
896
  changes.warnings.push(`Skipping ${selectedPackage.id} ${hook.command} because it currently includes MCP instructions`);
802
897
  continue;
803
898
  }
804
- runInitAgentsHook(
899
+ await runInitAgentsHook(
805
900
  selectedPackage.name,
806
901
  hook.binName,
807
902
  cwd,
@@ -813,7 +908,7 @@ const applyInitAgents = (cwd, selectedPackages, packageManager, options, changes
813
908
  }
814
909
  }
815
910
  };
816
- const applyInitPackageConfigHooks = (cwd, selectedPackages, packageManager, options, changes) => {
911
+ const applyInitPackageConfigHooks = async (cwd, selectedPackages, packageManager, options, changes) => {
817
912
  for (const selectedPackage of selectedPackages) {
818
913
  for (const hook of selectedPackage.hooks ?? []) {
819
914
  if (hook.type !== "config") {
@@ -829,7 +924,7 @@ const applyInitPackageConfigHooks = (cwd, selectedPackages, packageManager, opti
829
924
  if (hook.requiresMcp && options.mcpClientConfigs?.length) {
830
925
  args.push("--mcp-client-configs", options.mcpClientConfigs.join(","));
831
926
  }
832
- runPackageHookCommand(
927
+ await runPackageHookCommand(
833
928
  cwd,
834
929
  selectedPackage.name,
835
930
  hook.binName,
@@ -912,6 +1007,35 @@ const describePathState = (cwd, relativePath) => {
912
1007
  }
913
1008
  return `${relativePath}: found`;
914
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
+ };
915
1039
  const readExistingPackageJson = (packageJsonPath, changes) => {
916
1040
  if (!fs.existsSync(packageJsonPath)) {
917
1041
  changes.preflight.push("package.json: missing; it will be created");
@@ -1082,6 +1206,7 @@ const applyInitPreflight = (cwd, sourceRoot, packageManager, selectedPackages, v
1082
1206
  if (!options.target && !options.srcDir && fs.existsSync(path.join(cwd, "src")) && path.basename(sourceRoot) === "web") {
1083
1207
  changes.warnings.push("src/ already exists; generated frontend source root resolved to web/");
1084
1208
  }
1209
+ analyzeGitDirectory(cwd, options, changes);
1085
1210
  changes.preflight.push(describePathState(cwd, "src"));
1086
1211
  changes.preflight.push(describePathState(cwd, "web"));
1087
1212
  if (options.noConfigs) {
@@ -2202,6 +2327,7 @@ const createInitChanges = () => ({
2202
2327
  files: [],
2203
2328
  agents: [],
2204
2329
  mcp: [],
2330
+ git: [],
2205
2331
  hooks: [],
2206
2332
  install: null,
2207
2333
  skipped: [],
@@ -2252,7 +2378,8 @@ const printInitReport = (cwd, sourceRoot, version, packageManager, changes, opti
2252
2378
  console.log("");
2253
2379
  console.log("files");
2254
2380
  for (const filePath of changes.files) {
2255
- console.log(` create ${filePath}`);
2381
+ const hasAction = /^(create|update)\s/u.test(filePath);
2382
+ console.log(` ${hasAction ? filePath : `create ${filePath}`}`);
2256
2383
  }
2257
2384
  }
2258
2385
  if (changes.agents.length > 0) {
@@ -2276,6 +2403,13 @@ const printInitReport = (cwd, sourceRoot, version, packageManager, changes, opti
2276
2403
  console.log(` ${options.dryRun ? "would run" : "ran"} ${hook}`);
2277
2404
  }
2278
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
+ }
2279
2413
  if (changes.install) {
2280
2414
  console.log("");
2281
2415
  console.log("install");
@@ -2308,9 +2442,10 @@ const printInitSummary = (cwd, sourceRoot, version, packageManager, changes) =>
2308
2442
  const summary = [
2309
2443
  changes.packageJson.length ? `package.json updated (${changes.packageJson.length} change(s))` : null,
2310
2444
  changes.directories.length ? `directories created: ${changes.directories.length}` : null,
2311
- changes.files.length ? `files created: ${changes.files.length}` : null,
2445
+ changes.files.length ? `files changed: ${changes.files.length}` : null,
2312
2446
  changes.agents.length ? "AGENTS.md updated" : null,
2313
2447
  changes.mcp.length ? "MCP config updated" : null,
2448
+ changes.git.length ? `git: ${changes.git.join(", ")}` : null,
2314
2449
  changes.hooks.length ? `package hooks ran: ${changes.hooks.length}` : null,
2315
2450
  changes.install ? `install: ${changes.install}` : null
2316
2451
  ].filter((item) => typeof item === "string");
@@ -4372,6 +4507,7 @@ const INIT_ACTION_LABELS = {
4372
4507
  template: "Создать стартовый шаблон",
4373
4508
  agents: "Обновить AGENTS.md",
4374
4509
  mcp: "Добавить MCP-настройки",
4510
+ git: "Инициализировать Git",
4375
4511
  install: "Запустить установку зависимостей"
4376
4512
  };
4377
4513
  const INIT_ACTION_DESCRIPTIONS = {
@@ -4379,8 +4515,33 @@ const INIT_ACTION_DESCRIPTIONS = {
4379
4515
  template: "Vue-точка входа, страница настроек, виджет заказа, i18n и publish script",
4380
4516
  agents: "Общие и пакетные инструкции для AI-агентов",
4381
4517
  mcp: ".mcp.json и MCP-инструкции пакетов",
4518
+ git: "git init в каталоге проекта, если Git еще не настроен",
4382
4519
  install: "Запуск выбранного package manager после изменения package.json"
4383
4520
  };
4521
+ const MCP_CLIENT_CONFIG_LABELS = {
4522
+ codex: "Codex CLI",
4523
+ cursor: "Cursor",
4524
+ junie: "Junie",
4525
+ vscode: "VS Code"
4526
+ };
4527
+ const MCP_CLIENT_CONFIG_DESCRIPTIONS = {
4528
+ codex: ".codex/config.toml for trusted Codex projects",
4529
+ cursor: ".cursor/mcp.json with ${workspaceFolder}",
4530
+ junie: ".junie/mcp/mcp.json",
4531
+ vscode: ".vscode/mcp.json with ${workspaceFolder}"
4532
+ };
4533
+ const MCP_CLIENT_CONFIGS = Object.keys(MCP_CLIENT_CONFIG_LABELS);
4534
+ const isGitWorkTree$1 = (cwd) => {
4535
+ try {
4536
+ execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
4537
+ cwd,
4538
+ stdio: ["ignore", "pipe", "ignore"]
4539
+ });
4540
+ return true;
4541
+ } catch {
4542
+ return false;
4543
+ }
4544
+ };
4384
4545
  const resolveDefaultSourceRoot = (cwd, options) => {
4385
4546
  if (options.srcDir) {
4386
4547
  return options.srcDir;
@@ -4425,6 +4586,9 @@ const resolveAvailableActions = (options) => {
4425
4586
  if (!options.noMcp) {
4426
4587
  actions.push("mcp");
4427
4588
  }
4589
+ if (!options.agentsOnly && !isGitWorkTree$1(options.cwd)) {
4590
+ actions.push("git");
4591
+ }
4428
4592
  if (!options.agentsOnly && !options.noInstall) {
4429
4593
  actions.push("install");
4430
4594
  }
@@ -4436,6 +4600,7 @@ const applyPromptedActions = (options, selectedActions) => {
4436
4600
  options.noTemplate = options.noTemplate || !selectedActionSet.has("template");
4437
4601
  options.noAgents = options.noAgents || !selectedActionSet.has("agents");
4438
4602
  options.noMcp = options.noMcp || !selectedActionSet.has("mcp");
4603
+ options.initGit = options.initGit || selectedActionSet.has("git");
4439
4604
  options.noInstall = options.noInstall || !selectedActionSet.has("install");
4440
4605
  };
4441
4606
  const resolvePromptedActions = async (options) => {
@@ -4458,12 +4623,32 @@ const resolvePromptedPackageManager = async (detectedPackageManager, explicitPac
4458
4623
  return explicitPackageManager;
4459
4624
  }
4460
4625
  const defaultPackageManager = detectedPackageManager ?? "npm";
4626
+ const availablePackageManagers = PACKAGE_MANAGERS.filter(isPackageManagerAvailable);
4627
+ if (availablePackageManagers.length === 0) {
4628
+ throw new Error("No supported package manager binary was found in PATH. Install npm, yarn, pnpm, or bun and rerun init.");
4629
+ }
4461
4630
  return select({
4462
4631
  message: "Package manager",
4463
- default: defaultPackageManager,
4632
+ default: availablePackageManagers.includes(defaultPackageManager) ? defaultPackageManager : availablePackageManagers[0],
4464
4633
  choices: PACKAGE_MANAGERS.map((packageManager) => ({
4465
4634
  name: packageManager,
4466
- value: packageManager
4635
+ value: packageManager,
4636
+ description: isPackageManagerAvailable(packageManager) ? "found in PATH" : "not found in PATH",
4637
+ disabled: isPackageManagerAvailable(packageManager) ? false : "not found in PATH"
4638
+ }))
4639
+ });
4640
+ };
4641
+ const resolvePromptedMcpClientConfigs = async (options) => {
4642
+ if (options.noMcp || options.mcpClientConfigs) {
4643
+ return options.mcpClientConfigs;
4644
+ }
4645
+ return checkbox({
4646
+ message: "MCP client configs",
4647
+ choices: MCP_CLIENT_CONFIGS.map((clientConfig) => ({
4648
+ name: MCP_CLIENT_CONFIG_LABELS[clientConfig],
4649
+ value: clientConfig,
4650
+ checked: false,
4651
+ description: MCP_CLIENT_CONFIG_DESCRIPTIONS[clientConfig]
4467
4652
  }))
4468
4653
  });
4469
4654
  };
@@ -4491,12 +4676,56 @@ const resolveInteractiveInitOptions = async (cwd, options, detectedPackageManage
4491
4676
  }
4492
4677
  const selectedActions = await resolvePromptedActions(nextOptions);
4493
4678
  applyPromptedActions(nextOptions, selectedActions);
4679
+ nextOptions.mcpClientConfigs = await resolvePromptedMcpClientConfigs(nextOptions);
4494
4680
  nextOptions.packageManager = await resolvePromptedPackageManager(
4495
4681
  detectedPackageManager,
4496
4682
  nextOptions.packageManager
4497
4683
  );
4498
4684
  return nextOptions;
4499
4685
  };
4686
+ const GITIGNORE_SECTION_HEADER = "# RetailCRM embed-ui init";
4687
+ const REQUIRED_GITIGNORE_ENTRIES = [
4688
+ "node_modules/",
4689
+ "dist/",
4690
+ "coverage/",
4691
+ ".env",
4692
+ ".env.*",
4693
+ "!.env.example",
4694
+ "*.log",
4695
+ "npm-debug.log*",
4696
+ "yarn-debug.log*",
4697
+ "yarn-error.log*",
4698
+ "pnpm-debug.log*",
4699
+ ".DS_Store"
4700
+ ];
4701
+ const normalizeGitignoreLine = (line) => line.trim().replace(/\/$/u, "");
4702
+ const hasGitignoreEntry = (lines, entry) => {
4703
+ const normalizedEntry = normalizeGitignoreLine(entry);
4704
+ return lines.some((line) => normalizeGitignoreLine(line) === normalizedEntry);
4705
+ };
4706
+ const updateGitignore = (cwd, options, changes) => {
4707
+ if (options.agentsOnly) {
4708
+ return;
4709
+ }
4710
+ const gitignorePath = path.join(cwd, ".gitignore");
4711
+ const fileExists = fs.existsSync(gitignorePath);
4712
+ const currentContent = fileExists ? fs.readFileSync(gitignorePath, "utf8") : "";
4713
+ const lines = currentContent.split(/\r?\n/u);
4714
+ const missingEntries = REQUIRED_GITIGNORE_ENTRIES.filter((entry) => !hasGitignoreEntry(lines, entry));
4715
+ if (missingEntries.length === 0) {
4716
+ changes.skipped.push(`${gitignorePath} already contains required init entries`);
4717
+ return;
4718
+ }
4719
+ const section = [
4720
+ GITIGNORE_SECTION_HEADER,
4721
+ ...missingEntries
4722
+ ].join(DEFAULT_NEWLINE);
4723
+ const nextContent = currentContent.trimEnd() ? `${currentContent.trimEnd()}${DEFAULT_NEWLINE}${DEFAULT_NEWLINE}${section}${DEFAULT_NEWLINE}` : `${section}${DEFAULT_NEWLINE}`;
4724
+ if (!options.dryRun) {
4725
+ fs.writeFileSync(gitignorePath, nextContent, "utf8");
4726
+ }
4727
+ changes.files.push(`${fileExists ? "update" : "create"} ${gitignorePath}`);
4728
+ };
4500
4729
  const DEFAULT_INIT_DIRS = ["endpoint", "pages", "widgets", "shared", "i18n"];
4501
4730
  const detectPackageManagerByLockfile = (cwd) => {
4502
4731
  const knownLockfiles = [
@@ -4508,16 +4737,6 @@ const detectPackageManagerByLockfile = (cwd) => {
4508
4737
  const lockfiles = knownLockfiles.filter(({ file }) => fs.existsSync(path.join(cwd, file)));
4509
4738
  return lockfiles.length === 1 ? lockfiles[0].packageManager : null;
4510
4739
  };
4511
- const resolvePackageManagerVersion = (packageManager) => {
4512
- try {
4513
- return execFileSync(packageManager, ["--version"], {
4514
- encoding: "utf8",
4515
- stdio: ["ignore", "pipe", "ignore"]
4516
- }).trim();
4517
- } catch {
4518
- return null;
4519
- }
4520
- };
4521
4740
  const resolvePackageManagerMajorVersion = (packageManager) => {
4522
4741
  const major = resolvePackageManagerVersion(packageManager)?.match(/^\d+/u)?.[0];
4523
4742
  return major ? Number(major) : null;
@@ -4565,6 +4784,16 @@ const resolveInitPackages = (tokens, extraTokens) => {
4565
4784
  }
4566
4785
  return packages;
4567
4786
  };
4787
+ const hasEnabledPackageHook = (selectedPackages, options) => selectedPackages.some((selectedPackage) => selectedPackage.hooks?.some((hook) => {
4788
+ if (hook.type === "agents") {
4789
+ return !options.noAgents && (!hook.requiresMcp || !options.noMcp);
4790
+ }
4791
+ if (hook.type === "config") {
4792
+ return !options.agentsOnly && (!hook.requiresMcp || !options.noMcp);
4793
+ }
4794
+ return false;
4795
+ }) ?? false);
4796
+ const shouldRequirePackageManagerBinary = (selectedPackages, options) => !options.dryRun && (!options.noInstall || hasEnabledPackageHook(selectedPackages, options));
4568
4797
  const resolveInitCwd = (options) => {
4569
4798
  const cwd = path.resolve(options.cwd);
4570
4799
  if (!fs.existsSync(cwd)) {
@@ -4588,6 +4817,17 @@ const resolveInitSourceRoot = (cwd, options) => {
4588
4817
  }
4589
4818
  return path.join(cwd, "web");
4590
4819
  };
4820
+ const isGitWorkTree = (cwd) => {
4821
+ try {
4822
+ execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
4823
+ cwd,
4824
+ stdio: ["ignore", "pipe", "ignore"]
4825
+ });
4826
+ return true;
4827
+ } catch {
4828
+ return false;
4829
+ }
4830
+ };
4591
4831
  const runUpdate = (options) => {
4592
4832
  const version = options.version ?? resolveLatestVersion();
4593
4833
  const packageJsonPaths = collectPackageJsonPaths(options.target);
@@ -4754,7 +4994,7 @@ const applyInitTemplate = (cwd, sourceRoot, packageManager, options, changes) =>
4754
4994
  writeFileIfAllowed(path.join(cwd, "scripts/publish-extension.mjs"), createPublishScript(), options, changes);
4755
4995
  writeFileIfAllowed(path.join(cwd, "README.md"), createReadme(cwd, sourceRoot, options, packageManager), options, changes);
4756
4996
  };
4757
- const runInstall = (cwd, packageManager, options, changes, packageJsonChanged) => {
4997
+ const runInstall = async (cwd, packageManager, options, changes, packageJsonChanged) => {
4758
4998
  if (options.noInstall || options.agentsOnly) {
4759
4999
  return;
4760
5000
  }
@@ -4768,15 +5008,31 @@ const runInstall = (cwd, packageManager, options, changes, packageJsonChanged) =
4768
5008
  return;
4769
5009
  }
4770
5010
  try {
4771
- execFileSync(packageManager, args, {
4772
- cwd,
4773
- stdio: ["ignore", "pipe", "pipe"]
4774
- });
5011
+ await runCommandWithTerminalStatus(
5012
+ packageManager,
5013
+ args,
5014
+ { cwd },
5015
+ `Installing dependencies with ${packageManager}`
5016
+ );
4775
5017
  } catch (error) {
4776
5018
  printExecErrorOutput(error);
4777
5019
  throw error;
4778
5020
  }
4779
5021
  };
5022
+ const applyInitGit = async (cwd, options, changes) => {
5023
+ if (!options.initGit) {
5024
+ return;
5025
+ }
5026
+ if (isGitWorkTree(cwd)) {
5027
+ changes.skipped.push("git init skipped because cwd is already inside a Git work tree");
5028
+ return;
5029
+ }
5030
+ changes.git.push("git init");
5031
+ if (options.dryRun) {
5032
+ return;
5033
+ }
5034
+ await runCommandWithTerminalStatus("git", ["init"], { cwd }, "Initializing Git repository");
5035
+ };
4780
5036
  const resolveInstallArgs = (packageManager) => {
4781
5037
  if (packageManager === "yarn" && resolvePackageManagerMajorVersion(packageManager) === 1) {
4782
5038
  return ["install", "--silent"];
@@ -4821,17 +5077,22 @@ const runInit = async (options) => {
4821
5077
  const resolvedOptions = version === "not used" ? interactiveOptions : { ...interactiveOptions, version };
4822
5078
  const packageManager = interactiveOptions.agentsOnly ? interactiveOptions.packageManager ?? detectPackageManagerByLockfile(cwd) ?? "npm" : await resolvePackageManager(cwd, interactiveOptions.packageManager);
4823
5079
  const changes = createInitChanges();
5080
+ if (shouldRequirePackageManagerBinary(selectedPackages, resolvedOptions)) {
5081
+ assertPackageManagerAvailable(packageManager);
5082
+ }
4824
5083
  applyInitPreflight(cwd, sourceRoot, packageManager, selectedPackages, version, resolvedOptions, changes);
5084
+ await applyInitGit(cwd, resolvedOptions, changes);
4825
5085
  let packageJsonPath = null;
4826
5086
  if (!resolvedOptions.agentsOnly) {
4827
5087
  packageJsonPath = applyInitPackageJson(cwd, selectedPackages, version, packageManager, resolvedOptions, changes);
5088
+ updateGitignore(cwd, resolvedOptions, changes);
4828
5089
  applyInitDirectories(sourceRoot, resolvedOptions, changes);
4829
5090
  applyInitConfigs(cwd, sourceRoot, resolvedOptions, changes);
4830
5091
  applyInitTemplate(cwd, sourceRoot, packageManager, resolvedOptions, changes);
4831
- applyInitPackageConfigHooks(cwd, selectedPackages, packageManager, resolvedOptions, changes);
4832
5092
  }
4833
- applyInitAgents(cwd, selectedPackages, packageManager, resolvedOptions, changes);
4834
- runInstall(cwd, packageManager, resolvedOptions, changes, Boolean(packageJsonPath && changes.packageJson.length > 0));
5093
+ await runInstall(cwd, packageManager, resolvedOptions, changes, Boolean(packageJsonPath && changes.packageJson.length > 0));
5094
+ await applyInitPackageConfigHooks(cwd, selectedPackages, packageManager, resolvedOptions, changes);
5095
+ await applyInitAgents(cwd, selectedPackages, packageManager, resolvedOptions, changes);
4835
5096
  printInitReport(cwd, sourceRoot, version, packageManager, changes, resolvedOptions);
4836
5097
  };
4837
5098
  const main = async (argv = process$2.argv.slice(2)) => {
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@retailcrm/embed-ui",
3
3
  "type": "module",
4
- "version": "0.9.22-alpha.4",
4
+ "version": "0.9.22-alpha.7",
5
5
  "description": "API and components for creating RetailCRM UI extensions",
6
- "repository": "git@github.com:retailcrm/embed-ui.git",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+ssh://git@github.com/retailcrm/embed-ui.git"
9
+ },
7
10
  "author": "RetailDriverLLC <integration@retailcrm.ru>",
8
11
  "license": "MIT",
9
12
  "contributors": [
@@ -19,7 +22,7 @@
19
22
  "./dist/*": "./dist/*",
20
23
  "./types/*": "./types/*"
21
24
  },
22
- "bin": "./bin/embed-ui.mjs",
25
+ "bin": "bin/embed-ui.mjs",
23
26
  "files": [
24
27
  "bin",
25
28
  "dist",
@@ -54,10 +57,10 @@
54
57
  "@omnicajs/symfony-router": "^1.0.0",
55
58
  "@omnicajs/vue-remote": "^0.2.23",
56
59
  "@remote-ui/rpc": "^1.4.5",
57
- "@retailcrm/embed-ui-v1-components": "^0.9.22-alpha.4",
58
- "@retailcrm/embed-ui-v1-contexts": "^0.9.22-alpha.4",
59
- "@retailcrm/embed-ui-v1-endpoint": "^0.9.22-alpha.4",
60
- "@retailcrm/embed-ui-v1-types": "^0.9.22-alpha.4",
60
+ "@retailcrm/embed-ui-v1-components": "^0.9.22-alpha.7",
61
+ "@retailcrm/embed-ui-v1-contexts": "^0.9.22-alpha.7",
62
+ "@retailcrm/embed-ui-v1-endpoint": "^0.9.22-alpha.7",
63
+ "@retailcrm/embed-ui-v1-types": "^0.9.22-alpha.7",
61
64
  "yargs": "^17.7.2"
62
65
  },
63
66
  "devDependencies": {
@@ -66,7 +69,7 @@
66
69
  "@modulify/git-toolkit": "^0.0.2",
67
70
  "@modulify/pkg": "^1.0.1",
68
71
  "@omnicajs/eslint-plugin-dependencies": "^0.0.2",
69
- "@retailcrm/embed-ui-v1-testing": "^0.9.22-alpha.4",
72
+ "@retailcrm/embed-ui-v1-testing": "^0.9.22-alpha.7",
70
73
  "@types/git-semver-tags": "^7.0.0",
71
74
  "@types/node": "^22.19.2",
72
75
  "@types/semver": "^7.7.1",