@retailcrm/embed-ui 0.9.22-alpha.4 → 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,12 @@
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))
4
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)
5
11
 
6
12
  ### 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
+ - 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` — дополнительно подключить или создать конфиги поддерживаемых AI-клиентов.
93
+ - `--git` — выполнить `git init` в корне проекта, если каталог еще не является Git-репозиторием.
89
94
 
90
95
  `--force` включает силовой режим для управляемых частей, но учитывает флаги `--no-*`. Например, можно обновить только
91
96
  агентские инструкции и MCP-настройки без перегенерации стартовых файлов:
@@ -111,7 +116,7 @@ npx @retailcrm/embed-ui init ./web --force --no-install --no-template
111
116
  Чтобы добавить только поддерживаемые project-level конфиги, укажите:
112
117
 
113
118
  ```bash
114
- 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
115
120
  ```
116
121
 
117
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";
@@ -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 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,20 @@ 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 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
+ };
4384
4532
  const resolveDefaultSourceRoot = (cwd, options) => {
4385
4533
  if (options.srcDir) {
4386
4534
  return options.srcDir;
@@ -4425,6 +4573,9 @@ const resolveAvailableActions = (options) => {
4425
4573
  if (!options.noMcp) {
4426
4574
  actions.push("mcp");
4427
4575
  }
4576
+ if (!options.agentsOnly && !isGitWorkTree$1(options.cwd)) {
4577
+ actions.push("git");
4578
+ }
4428
4579
  if (!options.agentsOnly && !options.noInstall) {
4429
4580
  actions.push("install");
4430
4581
  }
@@ -4436,6 +4587,7 @@ const applyPromptedActions = (options, selectedActions) => {
4436
4587
  options.noTemplate = options.noTemplate || !selectedActionSet.has("template");
4437
4588
  options.noAgents = options.noAgents || !selectedActionSet.has("agents");
4438
4589
  options.noMcp = options.noMcp || !selectedActionSet.has("mcp");
4590
+ options.initGit = options.initGit || selectedActionSet.has("git");
4439
4591
  options.noInstall = options.noInstall || !selectedActionSet.has("install");
4440
4592
  };
4441
4593
  const resolvePromptedActions = async (options) => {
@@ -4458,12 +4610,18 @@ const resolvePromptedPackageManager = async (detectedPackageManager, explicitPac
4458
4610
  return explicitPackageManager;
4459
4611
  }
4460
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
+ }
4461
4617
  return select({
4462
4618
  message: "Package manager",
4463
- default: defaultPackageManager,
4619
+ default: availablePackageManagers.includes(defaultPackageManager) ? defaultPackageManager : availablePackageManagers[0],
4464
4620
  choices: PACKAGE_MANAGERS.map((packageManager) => ({
4465
4621
  name: packageManager,
4466
- 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"
4467
4625
  }))
4468
4626
  });
4469
4627
  };
@@ -4497,6 +4655,49 @@ const resolveInteractiveInitOptions = async (cwd, options, detectedPackageManage
4497
4655
  );
4498
4656
  return nextOptions;
4499
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
+ };
4500
4701
  const DEFAULT_INIT_DIRS = ["endpoint", "pages", "widgets", "shared", "i18n"];
4501
4702
  const detectPackageManagerByLockfile = (cwd) => {
4502
4703
  const knownLockfiles = [
@@ -4508,16 +4709,6 @@ const detectPackageManagerByLockfile = (cwd) => {
4508
4709
  const lockfiles = knownLockfiles.filter(({ file }) => fs.existsSync(path.join(cwd, file)));
4509
4710
  return lockfiles.length === 1 ? lockfiles[0].packageManager : null;
4510
4711
  };
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
4712
  const resolvePackageManagerMajorVersion = (packageManager) => {
4522
4713
  const major = resolvePackageManagerVersion(packageManager)?.match(/^\d+/u)?.[0];
4523
4714
  return major ? Number(major) : null;
@@ -4565,6 +4756,16 @@ const resolveInitPackages = (tokens, extraTokens) => {
4565
4756
  }
4566
4757
  return packages;
4567
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));
4568
4769
  const resolveInitCwd = (options) => {
4569
4770
  const cwd = path.resolve(options.cwd);
4570
4771
  if (!fs.existsSync(cwd)) {
@@ -4588,6 +4789,17 @@ const resolveInitSourceRoot = (cwd, options) => {
4588
4789
  }
4589
4790
  return path.join(cwd, "web");
4590
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
+ };
4591
4803
  const runUpdate = (options) => {
4592
4804
  const version = options.version ?? resolveLatestVersion();
4593
4805
  const packageJsonPaths = collectPackageJsonPaths(options.target);
@@ -4754,7 +4966,7 @@ const applyInitTemplate = (cwd, sourceRoot, packageManager, options, changes) =>
4754
4966
  writeFileIfAllowed(path.join(cwd, "scripts/publish-extension.mjs"), createPublishScript(), options, changes);
4755
4967
  writeFileIfAllowed(path.join(cwd, "README.md"), createReadme(cwd, sourceRoot, options, packageManager), options, changes);
4756
4968
  };
4757
- const runInstall = (cwd, packageManager, options, changes, packageJsonChanged) => {
4969
+ const runInstall = async (cwd, packageManager, options, changes, packageJsonChanged) => {
4758
4970
  if (options.noInstall || options.agentsOnly) {
4759
4971
  return;
4760
4972
  }
@@ -4768,15 +4980,31 @@ const runInstall = (cwd, packageManager, options, changes, packageJsonChanged) =
4768
4980
  return;
4769
4981
  }
4770
4982
  try {
4771
- execFileSync(packageManager, args, {
4772
- cwd,
4773
- stdio: ["ignore", "pipe", "pipe"]
4774
- });
4983
+ await runCommandWithTerminalStatus(
4984
+ packageManager,
4985
+ args,
4986
+ { cwd },
4987
+ `Installing dependencies with ${packageManager}`
4988
+ );
4775
4989
  } catch (error) {
4776
4990
  printExecErrorOutput(error);
4777
4991
  throw error;
4778
4992
  }
4779
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
+ };
4780
5008
  const resolveInstallArgs = (packageManager) => {
4781
5009
  if (packageManager === "yarn" && resolvePackageManagerMajorVersion(packageManager) === 1) {
4782
5010
  return ["install", "--silent"];
@@ -4821,17 +5049,22 @@ const runInit = async (options) => {
4821
5049
  const resolvedOptions = version === "not used" ? interactiveOptions : { ...interactiveOptions, version };
4822
5050
  const packageManager = interactiveOptions.agentsOnly ? interactiveOptions.packageManager ?? detectPackageManagerByLockfile(cwd) ?? "npm" : await resolvePackageManager(cwd, interactiveOptions.packageManager);
4823
5051
  const changes = createInitChanges();
5052
+ if (shouldRequirePackageManagerBinary(selectedPackages, resolvedOptions)) {
5053
+ assertPackageManagerAvailable(packageManager);
5054
+ }
4824
5055
  applyInitPreflight(cwd, sourceRoot, packageManager, selectedPackages, version, resolvedOptions, changes);
5056
+ await applyInitGit(cwd, resolvedOptions, changes);
4825
5057
  let packageJsonPath = null;
4826
5058
  if (!resolvedOptions.agentsOnly) {
4827
5059
  packageJsonPath = applyInitPackageJson(cwd, selectedPackages, version, packageManager, resolvedOptions, changes);
5060
+ updateGitignore(cwd, resolvedOptions, changes);
4828
5061
  applyInitDirectories(sourceRoot, resolvedOptions, changes);
4829
5062
  applyInitConfigs(cwd, sourceRoot, resolvedOptions, changes);
4830
5063
  applyInitTemplate(cwd, sourceRoot, packageManager, resolvedOptions, changes);
4831
- applyInitPackageConfigHooks(cwd, selectedPackages, packageManager, resolvedOptions, changes);
4832
5064
  }
4833
- applyInitAgents(cwd, selectedPackages, packageManager, resolvedOptions, changes);
4834
- 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);
4835
5068
  printInitReport(cwd, sourceRoot, version, packageManager, changes, resolvedOptions);
4836
5069
  };
4837
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.4",
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.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",
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.4",
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",