@retailcrm/embed-ui 0.9.28 → 0.9.30-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +14 -0
- package/bin/embed-ui.mjs +631 -13
- package/package.json +19 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.30-alpha.1
|
|
3
4
|
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
* **v1-components:** Added v1-sandbox ([7530f77](https://github.com/retailcrm/embed-ui/commit/7530f77f5cec41c154c2f8992cb39ab02d3c2cc4))
|
|
8
|
+
* **v1-sandbox:** Created v1-sandbox for embed-ui ([f337c9d](https://github.com/retailcrm/embed-ui/commit/f337c9dd257ae87cec23ea4edefc41a8ba099c45))
|
|
9
|
+
|
|
10
|
+
## [0.9.29](https://github.com/retailcrm/embed-ui/compare/v0.9.28...v0.9.29) (2026-06-10)
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* **v1-components:** UiButton link hover styles preserved ([3463e58](https://github.com/retailcrm/embed-ui/commit/3463e58fa78201da48262f937ac78556a989f155))
|
|
4
15
|
## [0.9.28](https://github.com/retailcrm/embed-ui/compare/v0.9.27...v0.9.28) (2026-06-05)
|
|
5
16
|
|
|
6
17
|
### Features
|
|
@@ -967,3 +978,4 @@ order.templates.number.crm из контекста settings
|
|
|
967
978
|
### Features
|
|
968
979
|
|
|
969
980
|
* Added basic types ([6bb2706](https://github.com/retailcrm/embed-ui/commit/6bb27060b3a9eca281865029e1688e44117f03f7))
|
|
981
|
+
|
package/README.md
CHANGED
|
@@ -11,6 +11,8 @@ API и компоненты для создания расширений инт
|
|
|
11
11
|
- `v1-components` — UI-компоненты и сопутствующая документация, [README.md пакета](./packages/v1-components/README.md).
|
|
12
12
|
- `v1-contexts` — реактивные контексты и доступ к данным RetailCRM JS API, [README.md пакета](./packages/v1-contexts/README.md).
|
|
13
13
|
- `v1-endpoint` — endpoint API для встраиваемых страниц и виджетов, [README.md пакета](./packages/v1-endpoint/README.md).
|
|
14
|
+
- `v1-sandbox` — песочница для запуска и тестирования JS-расширений вне CRM,
|
|
15
|
+
[README.md пакета](./packages/v1-sandbox/README.md).
|
|
14
16
|
- `v1-testing` — тестовые утилиты и вспомогательные типы для интеграций, [README.md пакета](./packages/v1-testing/README.md).
|
|
15
17
|
- `v1-types` — базовые type declarations для публичного API, [README.md пакета](./packages/v1-types/README.md).
|
|
16
18
|
|
|
@@ -83,16 +85,28 @@ npx @retailcrm/embed-ui init ./web --cwd ./my-project --package-manager npm
|
|
|
83
85
|
- `--version 0.9.21` — использовать указанную версию пакетов вместо версии запущенного CLI.
|
|
84
86
|
- `--exact` — записывать точные версии вместо диапазонов.
|
|
85
87
|
- `--packages embed-ui,components,contexts,types,endpoint` — явно выбрать пакеты для установки и настройки.
|
|
88
|
+
- `--with components,endpoint` — добавить опубликованные пакеты к стандартному набору `init`.
|
|
89
|
+
- `--package-manager yarn|npm|pnpm|bun` — явно выбрать package manager для установки.
|
|
90
|
+
- `--src-dir ./frontend` — задать frontend source root относительно корня проекта.
|
|
91
|
+
- `--dirs endpoint,pages,widgets,shared,i18n` — задать набор стартовых каталогов; дополнительно поддерживаются `src` и `tests`.
|
|
92
|
+
- `--no-dirs` — не создавать стартовые каталоги.
|
|
93
|
+
- `--template order-card` — выбрать стартовый шаблон; сейчас поддерживается `order-card`.
|
|
94
|
+
- `--page-code settings` — задать код стартовой embedded-страницы.
|
|
95
|
+
- `--widget-target order/card:common.after` — задать стартовый target виджета.
|
|
96
|
+
- `--force` — включить общий force-режим для управляемых файлов, hook-команд и install.
|
|
86
97
|
- `--force-deps` — заменить несовместимые версии зависимостей.
|
|
87
98
|
- `--fix-sections` — перенести зависимости в ожидаемые секции `dependencies`/`devDependencies`.
|
|
88
99
|
- `--force-files` — перезаписать генерируемые стартовые файлы.
|
|
89
100
|
- `--no-configs` — не создавать `tsconfig.json`, `vite.config.ts`, `eslint.config.js` и `env.d.ts`.
|
|
90
101
|
- `--no-template` — не создавать стартовые Vue-файлы и `extensionrc.json`.
|
|
91
102
|
- `--no-agents` — не создавать и не дополнять `AGENTS.md`.
|
|
103
|
+
- `--force-agents` — обновить управляемую секцию `AGENTS.md` и прокинуть `--force` в package-level agent hooks.
|
|
104
|
+
- `--agents-only` — установить только agent-инструкции, без изменения зависимостей, шаблонов, skills и MCP.
|
|
92
105
|
- `--no-skills` — не создавать и не обновлять project-level skills в `.agents/skills/*`.
|
|
93
106
|
- `--force-skills` — обновить уже существующие управляемые project-level skills.
|
|
94
107
|
- `--skills-only` — установить только project-level skills, без изменения зависимостей, шаблонов и `AGENTS.md`.
|
|
95
108
|
- `--no-mcp` — не добавлять MCP-настройки пакетов.
|
|
109
|
+
- `--force-mcp` — прокинуть `--force` в package-level MCP config hooks.
|
|
96
110
|
- `--mcp-client-configs codex,cursor,junie,vscode` — дополнительно создать project-level конфиги поддерживаемых AI-клиентов.
|
|
97
111
|
- `--git` — выполнить `git init` в корне проекта, если каталог еще не является Git-репозиторием.
|
|
98
112
|
|
package/bin/embed-ui.mjs
CHANGED
|
@@ -25,18 +25,32 @@ Options:
|
|
|
25
25
|
--dry-run Show changes without writing package.json
|
|
26
26
|
--add Add selected embed-ui packages into one package.json
|
|
27
27
|
--packages <list> Comma-separated package ids or names for --add/init
|
|
28
|
+
--with <list> Additional published package ids or names for init
|
|
28
29
|
--cwd <path> Project working directory for init
|
|
29
30
|
--package-manager Package manager for init installs
|
|
31
|
+
--dirs <list> Comma-separated starter directory presets for init
|
|
32
|
+
--src-dir <path> Frontend source root relative to cwd
|
|
33
|
+
--template <name> Starter template name
|
|
34
|
+
--page-code <code> Starter embedded page code
|
|
35
|
+
--widget-target <target> Starter widget target
|
|
30
36
|
--interactive Ask init questions with TTY selection prompts
|
|
31
37
|
--verbose Print detailed init change lists
|
|
32
38
|
--no-install Do not run package manager install in init mode
|
|
33
39
|
--no-configs Do not create root TypeScript, Vite, ESLint and env config files
|
|
40
|
+
--no-dirs Do not create starter directories in init mode
|
|
41
|
+
--no-template Do not create starter template files in init mode
|
|
42
|
+
--force Force managed init writes, hooks, and install
|
|
34
43
|
--force-deps Replace incompatible existing init dependencies
|
|
35
44
|
--fix-sections Move init dependencies to expected package.json sections
|
|
45
|
+
--force-files Overwrite generated starter files in init mode
|
|
36
46
|
--no-agents Do not create or update AGENTS.md in init mode
|
|
47
|
+
--force-agents Force AGENTS.md section and package agent hook updates
|
|
48
|
+
--agents-only Install only project-level agent instructions
|
|
37
49
|
--no-skills Do not create or update .agents/skills in init mode
|
|
50
|
+
--force-skills Force project-level skill hook updates
|
|
38
51
|
--skills-only Install only project-level skills
|
|
39
52
|
--no-mcp Do not add package MCP instructions in init mode
|
|
53
|
+
--force-mcp Force package MCP config hook updates
|
|
40
54
|
--mcp-client-configs Comma-separated project-level MCP client configs to create (codex,cursor,junie,vscode)
|
|
41
55
|
--git Initialize Git repository in init mode when cwd is not a Git work tree
|
|
42
56
|
-h, --help Show this help
|
|
@@ -251,7 +265,7 @@ const TARGET_SECTIONS = [
|
|
|
251
265
|
"optionalDependencies"
|
|
252
266
|
];
|
|
253
267
|
const ROOT_PACKAGE = "@retailcrm/embed-ui";
|
|
254
|
-
const DEFAULT_INIT_PACKAGE_IDS = ["embed-ui", "components", "contexts", "types", "endpoint"];
|
|
268
|
+
const DEFAULT_INIT_PACKAGE_IDS = ["embed-ui", "components", "contexts", "types", "endpoint", "sandbox"];
|
|
255
269
|
const INSTALLABLE_PACKAGES = [
|
|
256
270
|
{
|
|
257
271
|
id: "embed-ui",
|
|
@@ -346,6 +360,26 @@ const INSTALLABLE_PACKAGES = [
|
|
|
346
360
|
requiresMcp: true
|
|
347
361
|
}
|
|
348
362
|
]
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
id: "sandbox",
|
|
366
|
+
name: "@retailcrm/embed-ui-v1-sandbox",
|
|
367
|
+
section: "dependencies",
|
|
368
|
+
description: "Песочница и тестовый стек для проверки расширений без CRM.",
|
|
369
|
+
hooks: [
|
|
370
|
+
{
|
|
371
|
+
type: "agents",
|
|
372
|
+
binName: "embed-ui-v1-sandbox",
|
|
373
|
+
command: "init-agents",
|
|
374
|
+
failureMode: "advisory"
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
type: "config",
|
|
378
|
+
binName: "embed-ui-v1-sandbox",
|
|
379
|
+
command: "init-env",
|
|
380
|
+
failureMode: "advisory"
|
|
381
|
+
}
|
|
382
|
+
]
|
|
349
383
|
}
|
|
350
384
|
];
|
|
351
385
|
const resolveLatestVersion = () => {
|
|
@@ -529,18 +563,26 @@ const INIT_DEV_DEPENDENCIES = [
|
|
|
529
563
|
{ name: "@intlify/eslint-plugin-vue-i18n", range: "^4.4" },
|
|
530
564
|
{ name: "@intlify/unplugin-vue-i18n", range: "^11.2" },
|
|
531
565
|
{ name: "@omnicajs/eslint-plugin-dependencies", range: "^0.0.2" },
|
|
566
|
+
{ name: "@playwright/test", range: "^1.58" },
|
|
567
|
+
{ name: "@testing-library/dom", range: "^10.4" },
|
|
532
568
|
{ name: "@types/node", range: "^22.19" },
|
|
533
569
|
{ name: "@vitejs/plugin-vue", range: "^6.0" },
|
|
570
|
+
{ name: "@vitest/browser", range: "^4.1" },
|
|
571
|
+
{ name: "@vitest/browser-playwright", range: "^4.1" },
|
|
534
572
|
{ name: "@vue/language-server", range: "^3.2" },
|
|
573
|
+
{ name: "dotenv", range: "^17.4" },
|
|
535
574
|
{ name: "eslint", range: "^9.39" },
|
|
536
575
|
{ name: "eslint-plugin-vue", range: "^10.9" },
|
|
537
576
|
{ name: "globals", range: "^16.5" },
|
|
577
|
+
{ name: "jsdom", range: "^27.3" },
|
|
538
578
|
{ name: "jsonc-eslint-parser", range: "^3.1" },
|
|
539
579
|
{ name: "less", range: "^4.6" },
|
|
580
|
+
{ name: "playwright", range: "^1.58" },
|
|
540
581
|
{ name: "typescript", range: "^5.9" },
|
|
541
582
|
{ name: "typescript-eslint", range: "^8.59" },
|
|
542
583
|
{ name: "vite", range: "^7.3" },
|
|
543
584
|
{ name: "vite-svg-loader", range: "^5.1" },
|
|
585
|
+
{ name: "vitest", range: "^4.1" },
|
|
544
586
|
{ name: "vue-eslint-parser", range: "^10.4" },
|
|
545
587
|
{ name: "yaml-eslint-parser", range: "^2.0" }
|
|
546
588
|
];
|
|
@@ -982,7 +1024,7 @@ const applyInitPackageConfigHooks = async (cwd, selectedPackages, packageManager
|
|
|
982
1024
|
continue;
|
|
983
1025
|
}
|
|
984
1026
|
const args = [hook.command, cwd];
|
|
985
|
-
if (hook.requiresMcp &&
|
|
1027
|
+
if (options.force || hook.requiresMcp && options.forceMcp) {
|
|
986
1028
|
args.push("--force");
|
|
987
1029
|
}
|
|
988
1030
|
if (hook.requiresMcp && options.mcpClientConfigs?.length) {
|
|
@@ -1012,11 +1054,27 @@ const CONFIG_FILES = [
|
|
|
1012
1054
|
"vite.config.ts",
|
|
1013
1055
|
"vite.config.js",
|
|
1014
1056
|
"vite.config.mts",
|
|
1057
|
+
"vitest.config.ts",
|
|
1058
|
+
"vitest.config.browser.ts",
|
|
1059
|
+
"vitest.config.playwright.ts",
|
|
1015
1060
|
"eslint.config.js",
|
|
1016
1061
|
"eslint.config.mjs",
|
|
1017
1062
|
"env.d.ts"
|
|
1018
1063
|
];
|
|
1019
|
-
const SCRIPT_NAMES = [
|
|
1064
|
+
const SCRIPT_NAMES = [
|
|
1065
|
+
"build",
|
|
1066
|
+
"dev",
|
|
1067
|
+
"eslint",
|
|
1068
|
+
"eslint:fix",
|
|
1069
|
+
"lint",
|
|
1070
|
+
"sandbox:serve",
|
|
1071
|
+
"serve:extension",
|
|
1072
|
+
"test",
|
|
1073
|
+
"test:browser",
|
|
1074
|
+
"test:browsers:install",
|
|
1075
|
+
"test:e2e",
|
|
1076
|
+
"test:unit"
|
|
1077
|
+
];
|
|
1020
1078
|
const VITE_CONFIG_FILES = [
|
|
1021
1079
|
"vite.config.ts",
|
|
1022
1080
|
"vite.config.js",
|
|
@@ -1046,6 +1104,18 @@ const TEMPLATE_FILE_IMPACTS = [
|
|
|
1046
1104
|
{
|
|
1047
1105
|
relativePath: "i18n/locales/en-GB.json",
|
|
1048
1106
|
impact: "starter English locale messages will not be generated"
|
|
1107
|
+
},
|
|
1108
|
+
{
|
|
1109
|
+
relativePath: "sandbox/tests/unit/extensionrc.test.ts",
|
|
1110
|
+
impact: "starter unit test will not be generated"
|
|
1111
|
+
},
|
|
1112
|
+
{
|
|
1113
|
+
relativePath: "sandbox/tests/browser/starter.browser.test.ts",
|
|
1114
|
+
impact: "starter browser test will not be generated"
|
|
1115
|
+
},
|
|
1116
|
+
{
|
|
1117
|
+
relativePath: "sandbox/tests/e2e/starter.e2e.ts",
|
|
1118
|
+
impact: "starter e2e test will not be generated"
|
|
1049
1119
|
}
|
|
1050
1120
|
];
|
|
1051
1121
|
const hasEnabledMcpConfigHook = (selectedPackages, options) => !options.noMcp && selectedPackages.some(
|
|
@@ -1246,7 +1316,7 @@ const analyzeTemplateFileSkips = (cwd, sourceRoot, options, changes) => {
|
|
|
1246
1316
|
changes.warnings.push(`${path.join(getSourceRootRelative(cwd, sourceRoot), relativePath)} already exists; ${impact}`);
|
|
1247
1317
|
}
|
|
1248
1318
|
}
|
|
1249
|
-
for (const relativePath of ["extensionrc.json", "scripts/publish-extension.mjs", "README.md"]) {
|
|
1319
|
+
for (const relativePath of ["extensionrc.json", "scripts/publish-extension.mjs", "scripts/serve-extension.mjs", "README.md"]) {
|
|
1250
1320
|
if (fs.existsSync(path.join(cwd, relativePath))) {
|
|
1251
1321
|
changes.warnings.push(`${relativePath} already exists; generated project-level starter file will be skipped`);
|
|
1252
1322
|
}
|
|
@@ -1715,6 +1785,7 @@ const saveWindow = () => {
|
|
|
1715
1785
|
}
|
|
1716
1786
|
</style>
|
|
1717
1787
|
`;
|
|
1788
|
+
const playwrightConfigTemplate = "import fs from 'node:fs'\nimport path from 'node:path'\n\nimport dotenv from 'dotenv'\n\nimport { defineConfig, devices } from '@playwright/test'\n\ndotenv.config({ path: path.resolve(process.cwd(), '.env.sandbox') })\n\nconst sandboxBaseURL = process.env.SANDBOX_BASE_URL || 'http://127.0.0.1:4173'\nconst extensionBaseURL = process.env.SANDBOX_EXTENSION_URL || 'http://127.0.0.1:4175/extension/'\nconst extensionrc = JSON.parse(\n fs.readFileSync(path.resolve(process.cwd(), 'extensionrc.json'), 'utf8')\n) as { uuid: string }\nconst extensionURL = new URL(extensionrc.uuid, extensionBaseURL).href\n\nexport default defineConfig({\n testDir: './__SOURCE_ROOT__/sandbox/tests/e2e',\n testMatch: '**/*.e2e.ts',\n fullyParallel: true,\n forbidOnly: !!process.env.CI,\n retries: process.env.CI ? 2 : 0,\n workers: process.env.CI ? 1 : undefined,\n reporter: 'html',\n outputDir: 'artifacts/playwright/results',\n use: {\n baseURL: sandboxBaseURL,\n screenshot: 'only-on-failure',\n trace: 'on-first-retry',\n },\n projects: [\n {\n name: 'chromium',\n use: { ...devices['Desktop Chrome'] },\n },\n ],\n webServer: [\n {\n command: '__PACKAGE_MANAGER_RUN__ sandbox:serve',\n url: sandboxBaseURL,\n reuseExistingServer: !process.env.CI,\n },\n {\n command: '__PACKAGE_MANAGER_RUN__ build && __PACKAGE_MANAGER_RUN__ serve:extension',\n url: extensionURL,\n reuseExistingServer: !process.env.CI,\n },\n ],\n})\n";
|
|
1718
1789
|
const publishScriptTemplate = `#!/usr/bin/env node
|
|
1719
1790
|
|
|
1720
1791
|
import { fileURLToPath } from 'node:url'
|
|
@@ -2083,9 +2154,448 @@ try {
|
|
|
2083
2154
|
process.exit(1)
|
|
2084
2155
|
}
|
|
2085
2156
|
`;
|
|
2086
|
-
const readmeEnGBTemplate = "# RetailCRM extension frontend\n\nThis project was generated by `embed-ui init`.\n\n## What Was Added\n\n- `package.json` with scripts for Vite build, ESLint, and extension publishing.\n- `extensionrc.json` with the generated extension manifest source.\n- `__SOURCE_ROOT__/endpoint/endpoint.worker.ts` with `defineRunner`, one page runner, and one widget runner.\n- `__SOURCE_ROOT__/pages/SettingsPage.vue` as a starter settings page.\n- `__SOURCE_ROOT__/widgets/OrderCommonAfterWidget.vue` as a starter order widget.\n- `__SOURCE_ROOT__/i18n/` with shared JSON message files.\n- `scripts/publish-extension.mjs` for creating `dist/extension.zip` and publishing the integration module through RetailCRM API.\n- `AGENTS.md` when agent instructions were enabled during init.\n\n## Replace Generic Values\n\nReview these generated placeholders before using the project in a real integration:\n\n- Extension code in `extensionrc.json`: `retailcrm-extension-frontend`.\n- Extension name in `extensionrc.json`: `RetailCRM Extension Frontend`.\n- Page code: `__PAGE_CODE__`.\n- Page descriptor in `extensionrc.json`: keep `pages[]` as an object with `code`, `menu`, and `menuItemTitle`; string page form is not valid for RetailCRM API publishing.\n- Widget target: `__WIDGET_TARGET__`.\n- Sample controls and fake data in `__SOURCE_ROOT__/pages/SettingsPage.vue`.\n- Sample toolbar actions and fake order data in `__SOURCE_ROOT__/widgets/OrderCommonAfterWidget.vue`.\n- Shared messages in `__SOURCE_ROOT__/i18n/locales/*.json`.\n\nThe generated page and widget are intentionally generic. Keep the structure you need, but replace the sample labels, fields, and fake data with real product behavior.\n\n## Vue File Names\n\n`SettingsPage.vue` and `OrderCommonAfterWidget.vue` are generic starter names. In product code, rename Vue files after the role they play in the extension, and update imports in `__SOURCE_ROOT__/endpoint/endpoint.worker.ts`.\n\nExamples from RetailCRM extension examples:\n\n- `ReturnsPage.vue` is a full returns-management page.\n- `TasksPage.vue` is a task list/workspace page.\n- `SummaryPage.vue` is a summary dashboard page.\n- `RecordToCalendlyWidget.vue` is a focused widget for one scenario.\n\nUse the same idea for your code: `LoyaltySettingsPage.vue`, `OrderNotesWidget.vue`, `PaymentStatusSidebar.vue`, or another name that describes the real scenario.\n\n## Commands\n\n```bash\n__PACKAGE_MANAGER__ install\n__PACKAGE_MANAGER_RUN__ eslint\n__PACKAGE_MANAGER_RUN__ build\n__PACKAGE_MANAGER_RUN__ publish-extension -- --archive-only\n```\n\n## Publishing\n\nCreate `.env` in the project root when you want `publish-extension` to update RetailCRM:\n\n```dotenv\nCRM_API_HOST=https://example.retailcrm.pro\nCRM_API_KEY=your-api-key\nMODULE_URL=https://example.com\n```\n\nRun `__PACKAGE_MANAGER_RUN__ build` before publishing. The archive-only mode creates `dist/extension.zip` without sending API requests.\n\nFor local CRM checks, `MODULE_URL` must point to a dev/static server that
|
|
2087
|
-
const readmeEsESTemplate = "# Frontend de extension RetailCRM\n\nEste proyecto fue generado por `embed-ui init`.\n\n## Que Se Agrego\n\n- `package.json` con scripts para Vite build, ESLint y publicacion de la extension.\n- `extensionrc.json` con la fuente del manifiesto de la extension.\n- `__SOURCE_ROOT__/endpoint/endpoint.worker.ts` con `defineRunner`, un runner de pagina y un runner de widget.\n- `__SOURCE_ROOT__/pages/SettingsPage.vue` como pagina inicial de configuracion.\n- `__SOURCE_ROOT__/widgets/OrderCommonAfterWidget.vue` como widget inicial del pedido.\n- `__SOURCE_ROOT__/i18n/` con archivos JSON de mensajes compartidos.\n- `scripts/publish-extension.mjs` para crear `dist/extension.zip` y publicar el modulo de integracion por RetailCRM API.\n- `AGENTS.md` si las instrucciones para agentes estaban activadas durante init.\n\n## Sustituya Los Valores Genericos\n\nRevise estos valores generados antes de usar el proyecto en una integracion real:\n\n- Codigo de extension en `extensionrc.json`: `retailcrm-extension-frontend`.\n- Nombre de extension en `extensionrc.json`: `RetailCRM Extension Frontend`.\n- Codigo de pagina: `__PAGE_CODE__`.\n- Descriptor de pagina en `extensionrc.json`: mantenga `pages[]` como objeto con `code`, `menu` y `menuItemTitle`; la forma de cadena no sirve para publicar por la API RetailCRM.\n- Target del widget: `__WIDGET_TARGET__`.\n- Controles de ejemplo y datos ficticios en `__SOURCE_ROOT__/pages/SettingsPage.vue`.\n- Acciones toolbar de ejemplo y datos ficticios del pedido en `__SOURCE_ROOT__/widgets/OrderCommonAfterWidget.vue`.\n- Mensajes compartidos en `__SOURCE_ROOT__/i18n/locales/*.json`.\n\nLa pagina y el widget generados son intencionalmente genericos. Mantenga la estructura que necesite, pero sustituya etiquetas, campos y datos ficticios por comportamiento real del producto.\n\n## Nombres De Archivos Vue\n\n`SettingsPage.vue` y `OrderCommonAfterWidget.vue` son nombres iniciales genericos. En codigo de producto, renombre los archivos Vue segun la funcion que cumplen en la extension y actualice los imports en `__SOURCE_ROOT__/endpoint/endpoint.worker.ts`.\n\nEjemplos del repositorio de extensiones RetailCRM:\n\n- `ReturnsPage.vue` es una pagina completa de gestion de devoluciones.\n- `TasksPage.vue` es una pagina de lista o espacio de trabajo de tareas.\n- `SummaryPage.vue` es una pagina de resumen o dashboard.\n- `RecordToCalendlyWidget.vue` es un widget enfocado en un escenario.\n\nUse la misma idea para su codigo: `LoyaltySettingsPage.vue`, `OrderNotesWidget.vue`, `PaymentStatusSidebar.vue` u otro nombre que describa el escenario real.\n\n## Comandos\n\n```bash\n__PACKAGE_MANAGER__ install\n__PACKAGE_MANAGER_RUN__ eslint\n__PACKAGE_MANAGER_RUN__ build\n__PACKAGE_MANAGER_RUN__ publish-extension -- --archive-only\n```\n\n## Publicacion\n\nCree `.env` en la raiz del proyecto cuando quiera que `publish-extension` actualice RetailCRM:\n\n```dotenv\nCRM_API_HOST=https://example.retailcrm.pro\nCRM_API_KEY=your-api-key\nMODULE_URL=https://example.com\n```\n\nEjecute `__PACKAGE_MANAGER_RUN__ build` antes de publicar. El modo archive-only crea `dist/extension.zip` sin enviar peticiones API.\n\nPara comprobar localmente en CRM, `MODULE_URL` debe apuntar a un dev/static server
|
|
2088
|
-
const readmeRuRUTemplate = "# Фронтенд расширения RetailCRM\n\nПроект создан командой `embed-ui init`.\n\n## Что Добавлено\n\n- `package.json` со скриптами для сборки Vite, ESLint и публикации расширения.\n- `extensionrc.json` с исходным описанием манифеста расширения.\n- `__SOURCE_ROOT__/endpoint/endpoint.worker.ts` с `defineRunner`, одним runner страницы и одним runner виджета.\n- `__SOURCE_ROOT__/pages/SettingsPage.vue` как стартовая страница настроек.\n- `__SOURCE_ROOT__/widgets/OrderCommonAfterWidget.vue` как стартовый виджет заказа.\n- `__SOURCE_ROOT__/i18n/` с общими JSON-файлами переводов.\n- `scripts/publish-extension.mjs` для создания `dist/extension.zip` и публикации интеграционного модуля через RetailCRM API.\n- `AGENTS.md`, если при инициализации были включены инструкции для агентов.\n\n## Замените Generic Значения\n\nПеред использованием проекта в реальной интеграции проверьте сгенерированные общие значения:\n\n- Код расширения в `extensionrc.json`: `retailcrm-extension-frontend`.\n- Название расширения в `extensionrc.json`: `RetailCRM Extension Frontend`.\n- Код страницы: `__PAGE_CODE__`.\n- Описание страницы в `extensionrc.json`: `pages[]` должен оставаться объектом с `code`, `menu` и `menuItemTitle`; строковая форма не подходит для публикации через RetailCRM API.\n- Цель виджета: `__WIDGET_TARGET__`.\n- Демонстрационные контролы и ненастоящие данные в `__SOURCE_ROOT__/pages/SettingsPage.vue`.\n- Демонстрационные toolbar-действия и ненастоящие данные заказа в `__SOURCE_ROOT__/widgets/OrderCommonAfterWidget.vue`.\n- Общие сообщения в `__SOURCE_ROOT__/i18n/locales/*.json`.\n\nСгенерированные страница и виджет намеренно сделаны универсальными. Оставьте нужную структуру, но замените примерные подписи, поля и ненастоящие данные на реальное поведение продукта.\n\n## Имена Vue-Файлов\n\n`SettingsPage.vue` и `OrderCommonAfterWidget.vue` — универсальные стартовые имена. В продуктовом коде переименуйте Vue-файлы по роли, которую они выполняют в расширении, и обновите импорты в `__SOURCE_ROOT__/endpoint/endpoint.worker.ts`.\n\nПримеры из репозитория расширений RetailCRM:\n\n- `ReturnsPage.vue` — полноценная страница управления возвратами.\n- `TasksPage.vue` — страница списка задач или рабочей области задач.\n- `SummaryPage.vue` — страница сводки или дашборда.\n- `RecordToCalendlyWidget.vue` — сфокусированный виджет под один сценарий.\n\nИспользуйте тот же принцип: `LoyaltySettingsPage.vue`, `OrderNotesWidget.vue`, `PaymentStatusSidebar.vue` или другое имя, которое описывает реальный сценарий.\n\n## Команды\n\n```bash\n__PACKAGE_MANAGER__ install\n__PACKAGE_MANAGER_RUN__ eslint\n__PACKAGE_MANAGER_RUN__ build\n__PACKAGE_MANAGER_RUN__ publish-extension -- --archive-only\n```\n\n## Публикация\n\nСоздайте `.env` в корне проекта, когда потребуется обновлять RetailCRM через `publish-extension`:\n\n```dotenv\nCRM_API_HOST=https://example.retailcrm.pro\nCRM_API_KEY=your-api-key\nMODULE_URL=https://example.com\n```\n\nПеред публикацией выполните `__PACKAGE_MANAGER_RUN__ build`. Режим archive-only создает `dist/extension.zip` без API-запросов.\n\nДля локальной проверки в CRM `MODULE_URL` должен указывать на dev/static server
|
|
2157
|
+
const readmeEnGBTemplate = "# RetailCRM extension frontend\n\nThis project was generated by `embed-ui init`.\n\n## What Was Added\n\n- `package.json` with scripts for Vite build, ESLint, and extension publishing.\n- `extensionrc.json` with the generated extension manifest source.\n- `__SOURCE_ROOT__/endpoint/endpoint.worker.ts` with `defineRunner`, one page runner, and one widget runner.\n- `__SOURCE_ROOT__/pages/SettingsPage.vue` as a starter settings page.\n- `__SOURCE_ROOT__/widgets/OrderCommonAfterWidget.vue` as a starter order widget.\n- `__SOURCE_ROOT__/i18n/` with shared JSON message files.\n- `__SOURCE_ROOT__/sandbox/tests/` with starter unit, browser, and e2e tests.\n- `vitest.config.ts`, `vitest.config.browser.ts`, and `vitest.config.playwright.ts` for test runs.\n- `scripts/publish-extension.mjs` for creating `dist/extension.zip` and publishing the integration module through RetailCRM API.\n- `scripts/serve-extension.mjs` for serving built extension manifests, scripts, and styles during e2e tests.\n- `AGENTS.md` when agent instructions were enabled during init.\n\n## Replace Generic Values\n\nReview these generated placeholders before using the project in a real integration:\n\n- Extension code in `extensionrc.json`: `retailcrm-extension-frontend`.\n- Extension name in `extensionrc.json`: `RetailCRM Extension Frontend`.\n- Page code: `__PAGE_CODE__`.\n- Page descriptor in `extensionrc.json`: keep `pages[]` as an object with `code`, `menu`, and `menuItemTitle`; string page form is not valid for RetailCRM API publishing.\n- Widget target: `__WIDGET_TARGET__`.\n- Sample controls and fake data in `__SOURCE_ROOT__/pages/SettingsPage.vue`.\n- Sample toolbar actions and fake order data in `__SOURCE_ROOT__/widgets/OrderCommonAfterWidget.vue`.\n- Shared messages in `__SOURCE_ROOT__/i18n/locales/*.json`.\n\nThe generated page and widget are intentionally generic. Keep the structure you need, but replace the sample labels, fields, and fake data with real product behavior.\n\n## Vue File Names\n\n`SettingsPage.vue` and `OrderCommonAfterWidget.vue` are generic starter names. In product code, rename Vue files after the role they play in the extension, and update imports in `__SOURCE_ROOT__/endpoint/endpoint.worker.ts`.\n\nExamples from RetailCRM extension examples:\n\n- `ReturnsPage.vue` is a full returns-management page.\n- `TasksPage.vue` is a task list/workspace page.\n- `SummaryPage.vue` is a summary dashboard page.\n- `RecordToCalendlyWidget.vue` is a focused widget for one scenario.\n\nUse the same idea for your code: `LoyaltySettingsPage.vue`, `OrderNotesWidget.vue`, `PaymentStatusSidebar.vue`, or another name that describes the real scenario.\n\n## Commands\n\n```bash\n__PACKAGE_MANAGER__ install\n__PACKAGE_MANAGER_RUN__ eslint\n__PACKAGE_MANAGER_RUN__ build\n__PACKAGE_MANAGER_RUN__ test:unit\n__PACKAGE_MANAGER_RUN__ test:browser\n__PACKAGE_MANAGER_RUN__ test:e2e\n__PACKAGE_MANAGER_RUN__ serve:extension\n__PACKAGE_MANAGER_RUN__ publish-extension -- --archive-only\n```\n\n## Testing\n\n- `__PACKAGE_MANAGER_RUN__ test:unit` runs fast unit tests without a browser.\n- `__PACKAGE_MANAGER_RUN__ test:browser` runs the starter page and widget in Chromium through Vitest Browser.\n- `__PACKAGE_MANAGER_RUN__ test:e2e` builds the extension, starts the sandbox app and local extension server, then checks the extension through Playwright.\n- `__PACKAGE_MANAGER_RUN__ serve:extension` starts `http://127.0.0.1:4175` after a build. Tests build the concrete manifest URL as `http://127.0.0.1:4175/extension/<uuid>`.\n\nBefore the first browser/e2e run, install Chromium if needed:\n\n```bash\n__PACKAGE_MANAGER_RUN__ test:browsers:install\n```\n\n## Publishing\n\nCreate `.env` in the project root when you want `publish-extension` to update RetailCRM:\n\n```dotenv\nCRM_API_HOST=https://example.retailcrm.pro\nCRM_API_KEY=your-api-key\nMODULE_URL=https://example.com\n```\n\nRun `__PACKAGE_MANAGER_RUN__ build` before publishing. The archive-only mode creates `dist/extension.zip` without sending API requests.\n\nFor local CRM checks, `MODULE_URL` must point to a dev/static server. `__PACKAGE_MANAGER_RUN__ serve:extension`\nstarts that local server for the current `dist` build and resolves `/extension/<uuid>`,\n`/extension/<uuid>/script`, and `/extension/<uuid>/stylesheet` from `extensionrc.json`.\n`publish-extension` registers these URLs in CRM, but it does not start that server.\n";
|
|
2158
|
+
const readmeEsESTemplate = "# Frontend de extension RetailCRM\n\nEste proyecto fue generado por `embed-ui init`.\n\n## Que Se Agrego\n\n- `package.json` con scripts para Vite build, ESLint y publicacion de la extension.\n- `extensionrc.json` con la fuente del manifiesto de la extension.\n- `__SOURCE_ROOT__/endpoint/endpoint.worker.ts` con `defineRunner`, un runner de pagina y un runner de widget.\n- `__SOURCE_ROOT__/pages/SettingsPage.vue` como pagina inicial de configuracion.\n- `__SOURCE_ROOT__/widgets/OrderCommonAfterWidget.vue` como widget inicial del pedido.\n- `__SOURCE_ROOT__/i18n/` con archivos JSON de mensajes compartidos.\n- `__SOURCE_ROOT__/sandbox/tests/` con pruebas iniciales unit, browser y e2e.\n- `vitest.config.ts`, `vitest.config.browser.ts` y `vitest.config.playwright.ts` para ejecutar pruebas.\n- `scripts/publish-extension.mjs` para crear `dist/extension.zip` y publicar el modulo de integracion por RetailCRM API.\n- `scripts/serve-extension.mjs` para servir manifest, script y stylesheet compilados durante las pruebas e2e.\n- `AGENTS.md` si las instrucciones para agentes estaban activadas durante init.\n\n## Sustituya Los Valores Genericos\n\nRevise estos valores generados antes de usar el proyecto en una integracion real:\n\n- Codigo de extension en `extensionrc.json`: `retailcrm-extension-frontend`.\n- Nombre de extension en `extensionrc.json`: `RetailCRM Extension Frontend`.\n- Codigo de pagina: `__PAGE_CODE__`.\n- Descriptor de pagina en `extensionrc.json`: mantenga `pages[]` como objeto con `code`, `menu` y `menuItemTitle`; la forma de cadena no sirve para publicar por la API RetailCRM.\n- Target del widget: `__WIDGET_TARGET__`.\n- Controles de ejemplo y datos ficticios en `__SOURCE_ROOT__/pages/SettingsPage.vue`.\n- Acciones toolbar de ejemplo y datos ficticios del pedido en `__SOURCE_ROOT__/widgets/OrderCommonAfterWidget.vue`.\n- Mensajes compartidos en `__SOURCE_ROOT__/i18n/locales/*.json`.\n\nLa pagina y el widget generados son intencionalmente genericos. Mantenga la estructura que necesite, pero sustituya etiquetas, campos y datos ficticios por comportamiento real del producto.\n\n## Nombres De Archivos Vue\n\n`SettingsPage.vue` y `OrderCommonAfterWidget.vue` son nombres iniciales genericos. En codigo de producto, renombre los archivos Vue segun la funcion que cumplen en la extension y actualice los imports en `__SOURCE_ROOT__/endpoint/endpoint.worker.ts`.\n\nEjemplos del repositorio de extensiones RetailCRM:\n\n- `ReturnsPage.vue` es una pagina completa de gestion de devoluciones.\n- `TasksPage.vue` es una pagina de lista o espacio de trabajo de tareas.\n- `SummaryPage.vue` es una pagina de resumen o dashboard.\n- `RecordToCalendlyWidget.vue` es un widget enfocado en un escenario.\n\nUse la misma idea para su codigo: `LoyaltySettingsPage.vue`, `OrderNotesWidget.vue`, `PaymentStatusSidebar.vue` u otro nombre que describa el escenario real.\n\n## Comandos\n\n```bash\n__PACKAGE_MANAGER__ install\n__PACKAGE_MANAGER_RUN__ eslint\n__PACKAGE_MANAGER_RUN__ build\n__PACKAGE_MANAGER_RUN__ test:unit\n__PACKAGE_MANAGER_RUN__ test:browser\n__PACKAGE_MANAGER_RUN__ test:e2e\n__PACKAGE_MANAGER_RUN__ serve:extension\n__PACKAGE_MANAGER_RUN__ publish-extension -- --archive-only\n```\n\n## Pruebas\n\n- `__PACKAGE_MANAGER_RUN__ test:unit` ejecuta pruebas unitarias rapidas sin navegador.\n- `__PACKAGE_MANAGER_RUN__ test:browser` ejecuta la pagina y el widget iniciales en Chromium mediante Vitest Browser.\n- `__PACKAGE_MANAGER_RUN__ test:e2e` compila la extension, inicia la sandbox app y el servidor local de extension, luego comprueba la extension con Playwright.\n- `__PACKAGE_MANAGER_RUN__ serve:extension` inicia `http://127.0.0.1:4175` despues de compilar. Las pruebas construyen el manifest URL concreto como `http://127.0.0.1:4175/extension/<uuid>`.\n\nAntes del primer lanzamiento browser/e2e, instale Chromium si hace falta:\n\n```bash\n__PACKAGE_MANAGER_RUN__ test:browsers:install\n```\n\n## Publicacion\n\nCree `.env` en la raiz del proyecto cuando quiera que `publish-extension` actualice RetailCRM:\n\n```dotenv\nCRM_API_HOST=https://example.retailcrm.pro\nCRM_API_KEY=your-api-key\nMODULE_URL=https://example.com\n```\n\nEjecute `__PACKAGE_MANAGER_RUN__ build` antes de publicar. El modo archive-only crea `dist/extension.zip` sin enviar peticiones API.\n\nPara comprobar localmente en CRM, `MODULE_URL` debe apuntar a un dev/static server. `__PACKAGE_MANAGER_RUN__ serve:extension`\ninicia ese servidor local para el build actual de `dist` y resuelve `/extension/<uuid>`,\n`/extension/<uuid>/script` y `/extension/<uuid>/stylesheet` desde `extensionrc.json`.\n`publish-extension` registra estas URL en CRM, pero no arranca ese servidor.\n";
|
|
2159
|
+
const readmeRuRUTemplate = "# Фронтенд расширения RetailCRM\n\nПроект создан командой `embed-ui init`.\n\n## Что Добавлено\n\n- `package.json` со скриптами для сборки Vite, ESLint и публикации расширения.\n- `extensionrc.json` с исходным описанием манифеста расширения.\n- `__SOURCE_ROOT__/endpoint/endpoint.worker.ts` с `defineRunner`, одним runner страницы и одним runner виджета.\n- `__SOURCE_ROOT__/pages/SettingsPage.vue` как стартовая страница настроек.\n- `__SOURCE_ROOT__/widgets/OrderCommonAfterWidget.vue` как стартовый виджет заказа.\n- `__SOURCE_ROOT__/i18n/` с общими JSON-файлами переводов.\n- `__SOURCE_ROOT__/sandbox/tests/` со стартовыми unit, browser и e2e тестами.\n- `vitest.config.ts`, `vitest.config.browser.ts` и `vitest.config.playwright.ts` для запуска тестов.\n- `scripts/publish-extension.mjs` для создания `dist/extension.zip` и публикации интеграционного модуля через RetailCRM API.\n- `scripts/serve-extension.mjs` для отдачи собранных manifest, script и stylesheet расширения во время e2e-тестов.\n- `AGENTS.md`, если при инициализации были включены инструкции для агентов.\n\n## Замените Generic Значения\n\nПеред использованием проекта в реальной интеграции проверьте сгенерированные общие значения:\n\n- Код расширения в `extensionrc.json`: `retailcrm-extension-frontend`.\n- Название расширения в `extensionrc.json`: `RetailCRM Extension Frontend`.\n- Код страницы: `__PAGE_CODE__`.\n- Описание страницы в `extensionrc.json`: `pages[]` должен оставаться объектом с `code`, `menu` и `menuItemTitle`; строковая форма не подходит для публикации через RetailCRM API.\n- Цель виджета: `__WIDGET_TARGET__`.\n- Демонстрационные контролы и ненастоящие данные в `__SOURCE_ROOT__/pages/SettingsPage.vue`.\n- Демонстрационные toolbar-действия и ненастоящие данные заказа в `__SOURCE_ROOT__/widgets/OrderCommonAfterWidget.vue`.\n- Общие сообщения в `__SOURCE_ROOT__/i18n/locales/*.json`.\n\nСгенерированные страница и виджет намеренно сделаны универсальными. Оставьте нужную структуру, но замените примерные подписи, поля и ненастоящие данные на реальное поведение продукта.\n\n## Имена Vue-Файлов\n\n`SettingsPage.vue` и `OrderCommonAfterWidget.vue` — универсальные стартовые имена. В продуктовом коде переименуйте Vue-файлы по роли, которую они выполняют в расширении, и обновите импорты в `__SOURCE_ROOT__/endpoint/endpoint.worker.ts`.\n\nПримеры из репозитория расширений RetailCRM:\n\n- `ReturnsPage.vue` — полноценная страница управления возвратами.\n- `TasksPage.vue` — страница списка задач или рабочей области задач.\n- `SummaryPage.vue` — страница сводки или дашборда.\n- `RecordToCalendlyWidget.vue` — сфокусированный виджет под один сценарий.\n\nИспользуйте тот же принцип: `LoyaltySettingsPage.vue`, `OrderNotesWidget.vue`, `PaymentStatusSidebar.vue` или другое имя, которое описывает реальный сценарий.\n\n## Команды\n\n```bash\n__PACKAGE_MANAGER__ install\n__PACKAGE_MANAGER_RUN__ eslint\n__PACKAGE_MANAGER_RUN__ build\n__PACKAGE_MANAGER_RUN__ test:unit\n__PACKAGE_MANAGER_RUN__ test:browser\n__PACKAGE_MANAGER_RUN__ test:e2e\n__PACKAGE_MANAGER_RUN__ serve:extension\n__PACKAGE_MANAGER_RUN__ publish-extension -- --archive-only\n```\n\n## Тестирование\n\n- `__PACKAGE_MANAGER_RUN__ test:unit` проверяет быстрые unit-тесты без браузера.\n- `__PACKAGE_MANAGER_RUN__ test:browser` запускает стартовую страницу и виджет в Chromium через Vitest Browser.\n- `__PACKAGE_MANAGER_RUN__ test:e2e` собирает расширение, поднимает sandbox-приложение и локальный extension server, затем проверяет расширение через Playwright.\n- `__PACKAGE_MANAGER_RUN__ serve:extension` после сборки запускает `http://127.0.0.1:4175`. Тесты собирают конкретный manifest URL как `http://127.0.0.1:4175/extension/<uuid>`.\n\nПеред первым browser/e2e запуском может потребоваться установить Chromium:\n\n```bash\n__PACKAGE_MANAGER_RUN__ test:browsers:install\n```\n\n## Публикация\n\nСоздайте `.env` в корне проекта, когда потребуется обновлять RetailCRM через `publish-extension`:\n\n```dotenv\nCRM_API_HOST=https://example.retailcrm.pro\nCRM_API_KEY=your-api-key\nMODULE_URL=https://example.com\n```\n\nПеред публикацией выполните `__PACKAGE_MANAGER_RUN__ build`. Режим archive-only создает `dist/extension.zip` без API-запросов.\n\nДля локальной проверки в CRM `MODULE_URL` должен указывать на dev/static server. `__PACKAGE_MANAGER_RUN__ serve:extension`\nзапускает такой локальный сервер для текущей сборки `dist` и резолвит `/extension/<uuid>`,\n`/extension/<uuid>/script` и `/extension/<uuid>/stylesheet` из `extensionrc.json`.\n`publish-extension` регистрирует эти URL в CRM, но сам dev-server не запускает.\n";
|
|
2160
|
+
const sandboxBrowserTestTemplate = "import type { SandboxWorkerRuntime } from '@retailcrm/embed-ui-v1-sandbox/automation/browser'\n\nimport { afterEach, expect, test } from 'vitest'\nimport { fireEvent, screen } from '@testing-library/dom'\n\nimport { createExtensionSourceWorker } from '@retailcrm/embed-ui-v1-sandbox/automation/browser'\nimport { createSandboxWorkerRuntime } from '@retailcrm/embed-ui-v1-sandbox/automation/browser'\n\nlet runtime: SandboxWorkerRuntime | null = null\n\nafterEach(async () => {\n await runtime?.teardown()\n runtime = null\n document.body.innerHTML = ''\n window.history.replaceState(null, '', '/')\n})\n\ntest('opens generated settings page', async () => {\n runtime = await createRuntime()\n\n await runtime.runPage('__PAGE_CODE__')\n\n expect(await screen.findByRole('heading', { name: 'Настройки расширения' })).toBeInstanceOf(HTMLElement)\n expect(screen.getByRole('button', { name: 'Сохранить' })).toBeInstanceOf(HTMLButtonElement)\n})\n\ntest('opens generated order widget sidebar', async () => {\n runtime = await createRuntime()\n\n await runtime.runWidget('__WIDGET_TARGET__')\n\n const openSidebar = await screen.findByRole('button', { name: 'Подготовить связь' })\n\n fireEvent.click(openSidebar)\n\n expect(await screen.findByRole('dialog')).toBeInstanceOf(HTMLElement)\n expect(screen.getByText('Связь по заказу')).toBeInstanceOf(HTMLElement)\n})\n\nconst createRuntime = async (): Promise<SandboxWorkerRuntime> => {\n const sourceWorker = createExtensionSourceWorker(\n new URL('/__SOURCE_ROOT__/endpoint/endpoint.worker.ts', window.location.href)\n )\n\n return createSandboxWorkerRuntime({\n fixture: 'order-basic',\n ready: sourceWorker.ready,\n worker: sourceWorker.worker,\n })\n}\n";
|
|
2161
|
+
const sandboxE2eTestTemplate = `import fs from 'node:fs'
|
|
2162
|
+
import path from 'node:path'
|
|
2163
|
+
|
|
2164
|
+
import { expect, test } from '@playwright/test'
|
|
2165
|
+
|
|
2166
|
+
import {
|
|
2167
|
+
createSandboxPagePath,
|
|
2168
|
+
readSandboxSnapshot,
|
|
2169
|
+
} from '@retailcrm/embed-ui-v1-sandbox/automation/playwright'
|
|
2170
|
+
|
|
2171
|
+
type ExtensionConfig = {
|
|
2172
|
+
pages: Array<{ code: string }>
|
|
2173
|
+
targets: string[]
|
|
2174
|
+
uuid: string
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
const extensionBaseURL = process.env.SANDBOX_EXTENSION_URL || 'http://127.0.0.1:4175/extension/'
|
|
2178
|
+
const extensionrc = JSON.parse(
|
|
2179
|
+
fs.readFileSync(path.resolve(process.cwd(), 'extensionrc.json'), 'utf8')
|
|
2180
|
+
) as ExtensionConfig
|
|
2181
|
+
const extensionUrl = new URL(extensionrc.uuid, extensionBaseURL).href
|
|
2182
|
+
const [pageDescriptor] = extensionrc.pages
|
|
2183
|
+
const [target] = extensionrc.targets
|
|
2184
|
+
|
|
2185
|
+
test('loads generated settings page extension and saves settings', async ({ page }) => {
|
|
2186
|
+
await page.goto(createSandboxPagePath({
|
|
2187
|
+
extensionUrl,
|
|
2188
|
+
manifestUrl: extensionUrl,
|
|
2189
|
+
pageCode: pageDescriptor.code,
|
|
2190
|
+
targets: [target],
|
|
2191
|
+
}))
|
|
2192
|
+
|
|
2193
|
+
await expect(page).toHaveURL(/mode=page/u)
|
|
2194
|
+
await expect(page).toHaveURL(new RegExp(\`pageCode=\${pageDescriptor.code}\`, 'u'))
|
|
2195
|
+
await expect(page.getByRole('heading', { name: 'Настройки расширения' })).toBeVisible()
|
|
2196
|
+
await expect(page.locator('svg[class*="settings-page-addon__icon"]')).toHaveCSS('width', '24px')
|
|
2197
|
+
await expect(page.locator('svg[class*="settings-page-addon__icon"]')).toHaveCSS('height', '24px')
|
|
2198
|
+
await expect(page.getByRole('button', { name: 'Сохранить' })).toBeVisible()
|
|
2199
|
+
await expect(page.getByLabel('Название помощника')).toHaveValue('Order follow-up')
|
|
2200
|
+
|
|
2201
|
+
await page.getByLabel('Название помощника').fill('E2E assistant')
|
|
2202
|
+
await page.getByRole('button', { name: 'Сохранить' }).click()
|
|
2203
|
+
|
|
2204
|
+
await expect(page.locator('body')).toContainText('Настройки сохранены')
|
|
2205
|
+
|
|
2206
|
+
const snapshot = await readSandboxSnapshot(page)
|
|
2207
|
+
|
|
2208
|
+
expect(snapshot.host.http.every(record => record.response.status === 200)).toBe(true)
|
|
2209
|
+
})
|
|
2210
|
+
`;
|
|
2211
|
+
const sandboxUnitTestTemplate = "import { describe, expect, test } from 'vitest'\n\nimport extensionrc from '../../../../extensionrc.json'\n\ndescribe('extension manifest', () => {\n test('contains generated page and widget target', () => {\n expect(extensionrc.pages).toEqual(\n expect.arrayContaining([\n expect.objectContaining({ code: '__PAGE_CODE__' }),\n ])\n )\n\n expect(extensionrc.targets).toContain('__WIDGET_TARGET__')\n expect(extensionrc.runner).toBe('worker')\n expect(extensionrc.entrypointType).toBe('script')\n })\n})\n";
|
|
2212
|
+
const serveScriptTemplate = `#!/usr/bin/env node
|
|
2213
|
+
|
|
2214
|
+
import { fileURLToPath } from 'node:url'
|
|
2215
|
+
|
|
2216
|
+
import fs from 'node:fs'
|
|
2217
|
+
|
|
2218
|
+
import http from 'node:http'
|
|
2219
|
+
|
|
2220
|
+
import path from 'node:path'
|
|
2221
|
+
|
|
2222
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
|
2223
|
+
const projectRoot = path.resolve(scriptDir, '..')
|
|
2224
|
+
const DEFAULT_HOST = '127.0.0.1'
|
|
2225
|
+
const DEFAULT_PORT = 4175
|
|
2226
|
+
|
|
2227
|
+
const parseArgs = () => {
|
|
2228
|
+
const result = {
|
|
2229
|
+
host: DEFAULT_HOST,
|
|
2230
|
+
port: DEFAULT_PORT,
|
|
2231
|
+
}
|
|
2232
|
+
const args = process.argv.slice(2)
|
|
2233
|
+
|
|
2234
|
+
for (let index = 0; index < args.length; index++) {
|
|
2235
|
+
const arg = args[index]
|
|
2236
|
+
|
|
2237
|
+
if (arg === '--host') {
|
|
2238
|
+
result.host = args[++index] || DEFAULT_HOST
|
|
2239
|
+
continue
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
if (arg === '--port') {
|
|
2243
|
+
const port = Number(args[++index])
|
|
2244
|
+
|
|
2245
|
+
if (!Number.isInteger(port) || port <= 0) {
|
|
2246
|
+
throw new Error('Invalid --port value')
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
result.port = port
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
return result
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
const readJsonFile = (filePath) => JSON.parse(fs.readFileSync(filePath, 'utf8'))
|
|
2257
|
+
|
|
2258
|
+
const listFiles = (directoryPath, basePath = directoryPath) => {
|
|
2259
|
+
const result = []
|
|
2260
|
+
|
|
2261
|
+
for (const entry of fs.readdirSync(directoryPath, { withFileTypes: true })) {
|
|
2262
|
+
const entryPath = path.join(directoryPath, entry.name)
|
|
2263
|
+
const relativePath = path.relative(basePath, entryPath).split(path.sep).join('/')
|
|
2264
|
+
|
|
2265
|
+
if (entry.isDirectory()) {
|
|
2266
|
+
result.push(...listFiles(entryPath, basePath))
|
|
2267
|
+
continue
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
if (entry.isFile()) {
|
|
2271
|
+
result.push(relativePath)
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
return result
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
const normalizeManifestPath = (value) => typeof value === 'string' && value.startsWith('./')
|
|
2279
|
+
? value.slice(2)
|
|
2280
|
+
: value
|
|
2281
|
+
|
|
2282
|
+
const pickBuildArtifacts = (distDir) => {
|
|
2283
|
+
const files = listFiles(distDir)
|
|
2284
|
+
.filter(file => file !== 'manifest.json')
|
|
2285
|
+
.filter(file => file !== 'extension.zip')
|
|
2286
|
+
.filter(file => !file.endsWith('.map'))
|
|
2287
|
+
|
|
2288
|
+
const viteManifestPath = path.join(distDir, '.vite/manifest.json')
|
|
2289
|
+
|
|
2290
|
+
if (fs.existsSync(viteManifestPath)) {
|
|
2291
|
+
const viteManifest = readJsonFile(viteManifestPath)
|
|
2292
|
+
const entries = Object.values(viteManifest)
|
|
2293
|
+
const entry = entries.find(item => item && item.isEntry) ?? entries[0]
|
|
2294
|
+
|
|
2295
|
+
if (entry?.file) {
|
|
2296
|
+
return {
|
|
2297
|
+
scriptFile: normalizeManifestPath(entry.file),
|
|
2298
|
+
styleFile: Array.isArray(entry.css) ? normalizeManifestPath(entry.css[0]) : null,
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
return {
|
|
2304
|
+
scriptFile: files.find(file => file.endsWith('.js')) ?? null,
|
|
2305
|
+
styleFile: files.find(file => file.endsWith('.css')) ?? null,
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
const assertFileInside = (root, relativePath) => {
|
|
2310
|
+
const filePath = path.resolve(root, relativePath)
|
|
2311
|
+
|
|
2312
|
+
if (!filePath.startsWith(root + path.sep) && filePath !== root) {
|
|
2313
|
+
throw new Error('Resolved file path is outside dist: ' + relativePath)
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
return filePath
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
const resolveFileInside = (root, relativePath) => {
|
|
2320
|
+
const filePath = path.resolve(root, relativePath)
|
|
2321
|
+
|
|
2322
|
+
if (!filePath.startsWith(root + path.sep) && filePath !== root) {
|
|
2323
|
+
return null
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
return filePath
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
const getContentType = (filePath) => {
|
|
2330
|
+
if (filePath.endsWith('.js')) return 'application/javascript; charset=utf-8'
|
|
2331
|
+
if (filePath.endsWith('.css')) return 'text/css; charset=utf-8'
|
|
2332
|
+
if (filePath.endsWith('.html')) return 'text/html; charset=utf-8'
|
|
2333
|
+
if (filePath.endsWith('.json')) return 'application/json; charset=utf-8'
|
|
2334
|
+
if (filePath.endsWith('.svg')) return 'image/svg+xml'
|
|
2335
|
+
if (filePath.endsWith('.png')) return 'image/png'
|
|
2336
|
+
if (filePath.endsWith('.jpg') || filePath.endsWith('.jpeg')) return 'image/jpeg'
|
|
2337
|
+
if (filePath.endsWith('.woff2')) return 'font/woff2'
|
|
2338
|
+
|
|
2339
|
+
return 'application/octet-stream'
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
const send = (request, response, status, body, headers = {}) => {
|
|
2343
|
+
const origin = request.headers.origin || '*'
|
|
2344
|
+
const responseBody = Buffer.isBuffer(body) ? body : Buffer.from(String(body))
|
|
2345
|
+
|
|
2346
|
+
response.writeHead(status, {
|
|
2347
|
+
'access-control-allow-credentials': 'true',
|
|
2348
|
+
'access-control-allow-origin': origin,
|
|
2349
|
+
'content-length': responseBody.byteLength,
|
|
2350
|
+
'vary': 'Origin',
|
|
2351
|
+
...headers,
|
|
2352
|
+
})
|
|
2353
|
+
|
|
2354
|
+
if (request.method !== 'HEAD') {
|
|
2355
|
+
response.end(responseBody)
|
|
2356
|
+
return
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
response.end()
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
const sendFile = (request, response, filePath) => {
|
|
2363
|
+
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
|
|
2364
|
+
send(request, response, 404, 'Not found', { 'content-type': 'text/plain; charset=utf-8' })
|
|
2365
|
+
return
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
const body = fs.readFileSync(filePath)
|
|
2369
|
+
|
|
2370
|
+
send(request, response, 200, body, {
|
|
2371
|
+
'content-length': body.byteLength,
|
|
2372
|
+
'content-type': getContentType(filePath),
|
|
2373
|
+
})
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
const createHtmlManifest = (scriptUrl, stylesheetUrl) => [
|
|
2377
|
+
'<!doctype html>',
|
|
2378
|
+
'<html>',
|
|
2379
|
+
'<head>',
|
|
2380
|
+
'<meta charset="utf-8">',
|
|
2381
|
+
stylesheetUrl ? '<link rel="stylesheet" href="' + stylesheetUrl + '">' : '',
|
|
2382
|
+
'<script type="module" src="' + scriptUrl + '"><\/script>',
|
|
2383
|
+
'</head>',
|
|
2384
|
+
'<body></body>',
|
|
2385
|
+
'</html>',
|
|
2386
|
+
'',
|
|
2387
|
+
].filter(Boolean).join('\\n')
|
|
2388
|
+
|
|
2389
|
+
const normalizeExtensionUuid = (value, source) => {
|
|
2390
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
2391
|
+
throw new Error('Field "uuid" must be a non-empty string in ' + source)
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
return value
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
const createRootRegistryEntry = () => {
|
|
2398
|
+
const configPath = path.join(projectRoot, 'extensionrc.json')
|
|
2399
|
+
const distDir = path.join(projectRoot, 'dist')
|
|
2400
|
+
|
|
2401
|
+
if (!fs.existsSync(configPath)) {
|
|
2402
|
+
return null
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
if (!fs.existsSync(distDir)) {
|
|
2406
|
+
throw new Error('Build directory not found: ' + distDir + '. Run build before serve:extension.')
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
const config = readJsonFile(configPath)
|
|
2410
|
+
const uuid = normalizeExtensionUuid(config.uuid, configPath)
|
|
2411
|
+
const { scriptFile, styleFile } = pickBuildArtifacts(distDir)
|
|
2412
|
+
|
|
2413
|
+
if (!scriptFile) {
|
|
2414
|
+
throw new Error('Missing JS build artifact. Run build before serve:extension.')
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
const distRoot = path.resolve(distDir)
|
|
2418
|
+
|
|
2419
|
+
return {
|
|
2420
|
+
config,
|
|
2421
|
+
distRoot,
|
|
2422
|
+
scriptPath: assertFileInside(distRoot, scriptFile),
|
|
2423
|
+
stylePath: styleFile ? assertFileInside(distRoot, styleFile) : null,
|
|
2424
|
+
uuid,
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
const createCasesRegistryEntries = () => {
|
|
2429
|
+
const casesPath = path.join(projectRoot, 'cases.json')
|
|
2430
|
+
|
|
2431
|
+
if (!fs.existsSync(casesPath)) {
|
|
2432
|
+
return []
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
const cases = readJsonFile(casesPath)
|
|
2436
|
+
|
|
2437
|
+
if (!Array.isArray(cases.items)) {
|
|
2438
|
+
throw new Error('Field "items" must be an array in ' + casesPath)
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
return cases.items.map((record, index) => {
|
|
2442
|
+
const source = casesPath + '#items[' + index + ']'
|
|
2443
|
+
const uuid = normalizeExtensionUuid(record.uuid, source)
|
|
2444
|
+
const name = typeof record.name === 'string' && record.name.trim()
|
|
2445
|
+
? record.name
|
|
2446
|
+
: null
|
|
2447
|
+
|
|
2448
|
+
if (!name) {
|
|
2449
|
+
throw new Error('Field "name" must be a non-empty string in ' + source)
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
const distRoot = path.resolve(projectRoot, 'dist', name)
|
|
2453
|
+
const manifestPath = path.join(distRoot, 'manifest.json')
|
|
2454
|
+
|
|
2455
|
+
if (!fs.existsSync(manifestPath)) {
|
|
2456
|
+
throw new Error('Build manifest not found: ' + manifestPath)
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
const manifest = readJsonFile(manifestPath)
|
|
2460
|
+
const scriptFile = normalizeManifestPath(manifest[record.script])
|
|
2461
|
+
const styleFile = record.stylesheet ? normalizeManifestPath(manifest[record.stylesheet]) : null
|
|
2462
|
+
|
|
2463
|
+
if (!scriptFile) {
|
|
2464
|
+
throw new Error('Missing JS build artifact for ' + uuid)
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
return {
|
|
2468
|
+
config: record,
|
|
2469
|
+
distRoot,
|
|
2470
|
+
scriptPath: assertFileInside(distRoot, scriptFile),
|
|
2471
|
+
stylePath: styleFile ? assertFileInside(distRoot, styleFile) : null,
|
|
2472
|
+
uuid,
|
|
2473
|
+
}
|
|
2474
|
+
})
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
const createExtensionRegistry = () => {
|
|
2478
|
+
const registry = new Map()
|
|
2479
|
+
const entries = [
|
|
2480
|
+
createRootRegistryEntry(),
|
|
2481
|
+
...createCasesRegistryEntries(),
|
|
2482
|
+
].filter(Boolean)
|
|
2483
|
+
|
|
2484
|
+
if (entries.length === 0) {
|
|
2485
|
+
throw new Error('No extensions found. Add extensionrc.json or cases.json.')
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
for (const entry of entries) {
|
|
2489
|
+
if (registry.has(entry.uuid)) {
|
|
2490
|
+
throw new Error('Duplicate extension uuid: ' + entry.uuid)
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
registry.set(entry.uuid, entry)
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
return registry
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
const parseExtensionRequest = (pathname) => {
|
|
2500
|
+
const match = pathname.match(/^\\/extension\\/([^/]+)(?:\\/(.*))?$/u)
|
|
2501
|
+
|
|
2502
|
+
if (!match) {
|
|
2503
|
+
return null
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
return {
|
|
2507
|
+
resource: (match[2] ?? '').replace(/\\/$/u, ''),
|
|
2508
|
+
uuid: decodeURIComponent(match[1]),
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
const start = () => {
|
|
2513
|
+
const { host, port } = parseArgs()
|
|
2514
|
+
const registry = createExtensionRegistry()
|
|
2515
|
+
const server = http.createServer((request, response) => {
|
|
2516
|
+
const url = new URL(request.url || '/', 'http://extension.local')
|
|
2517
|
+
|
|
2518
|
+
if (request.method === 'OPTIONS') {
|
|
2519
|
+
send(request, response, 204, '', {
|
|
2520
|
+
'access-control-allow-headers': 'content-type',
|
|
2521
|
+
'access-control-allow-methods': 'GET,HEAD,OPTIONS,POST',
|
|
2522
|
+
})
|
|
2523
|
+
return
|
|
2524
|
+
}
|
|
2525
|
+
|
|
2526
|
+
if (request.method !== 'GET' && request.method !== 'HEAD') {
|
|
2527
|
+
send(request, response, 405, 'Method not allowed', { 'content-type': 'text/plain; charset=utf-8' })
|
|
2528
|
+
return
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
const extensionRequest = parseExtensionRequest(url.pathname)
|
|
2532
|
+
|
|
2533
|
+
if (!extensionRequest) {
|
|
2534
|
+
send(request, response, 404, 'Not found', { 'content-type': 'text/plain; charset=utf-8' })
|
|
2535
|
+
return
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
const entry = registry.get(extensionRequest.uuid)
|
|
2539
|
+
|
|
2540
|
+
if (!entry) {
|
|
2541
|
+
send(request, response, 404, 'Not found', { 'content-type': 'text/plain; charset=utf-8' })
|
|
2542
|
+
return
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
const extensionPrefix = '/extension/' + entry.uuid
|
|
2546
|
+
|
|
2547
|
+
if (!extensionRequest.resource) {
|
|
2548
|
+
send(request, response, 200, createHtmlManifest(
|
|
2549
|
+
extensionPrefix + '/script',
|
|
2550
|
+
entry.stylePath && entry.config.stylesheet !== false ? extensionPrefix + '/stylesheet' : null
|
|
2551
|
+
), {
|
|
2552
|
+
'content-type': 'text/html; charset=utf-8',
|
|
2553
|
+
})
|
|
2554
|
+
return
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
if (extensionRequest.resource === 'script') {
|
|
2558
|
+
sendFile(request, response, entry.scriptPath)
|
|
2559
|
+
return
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
if (extensionRequest.resource === 'stylesheet') {
|
|
2563
|
+
if (!entry.stylePath || entry.config.stylesheet === false) {
|
|
2564
|
+
send(request, response, 404, 'Not found', { 'content-type': 'text/plain; charset=utf-8' })
|
|
2565
|
+
return
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2568
|
+
sendFile(request, response, entry.stylePath)
|
|
2569
|
+
return
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
if (extensionRequest.resource) {
|
|
2573
|
+
const filePath = resolveFileInside(entry.distRoot, decodeURIComponent(extensionRequest.resource))
|
|
2574
|
+
|
|
2575
|
+
if (!filePath) {
|
|
2576
|
+
send(request, response, 404, 'Not found', { 'content-type': 'text/plain; charset=utf-8' })
|
|
2577
|
+
return
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
sendFile(request, response, filePath)
|
|
2581
|
+
return
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
send(request, response, 404, 'Not found', { 'content-type': 'text/plain; charset=utf-8' })
|
|
2585
|
+
})
|
|
2586
|
+
|
|
2587
|
+
server.listen(port, host, () => {
|
|
2588
|
+
console.log('Extension server: http://' + host + ':' + port)
|
|
2589
|
+
})
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
try {
|
|
2593
|
+
start()
|
|
2594
|
+
} catch (error) {
|
|
2595
|
+
console.error(error instanceof Error ? error.message : error)
|
|
2596
|
+
process.exit(1)
|
|
2597
|
+
}
|
|
2598
|
+
`;
|
|
2089
2599
|
const settingsPageTemplate = `<template>
|
|
2090
2600
|
<main :class="$style['settings-page']">
|
|
2091
2601
|
<UiPageHeader :value="t('title')">
|
|
@@ -2392,6 +2902,8 @@ const saveSettings = () => {
|
|
|
2392
2902
|
`;
|
|
2393
2903
|
const tsConfigTemplate = '{\n "compilerOptions": {\n "target": "ES2022",\n "useDefineForClassFields": true,\n "module": "ESNext",\n "moduleResolution": "Bundler",\n "strict": true,\n "jsx": "preserve",\n "resolveJsonModule": true,\n "isolatedModules": true,\n "skipLibCheck": true,\n "allowSyntheticDefaultImports": true,\n "esModuleInterop": true,\n "baseUrl": ".",\n "paths": {\n "@/*": [\n "__SOURCE_ROOT_RELATIVE__/*"\n ]\n }\n },\n "include": [\n "__SOURCE_ROOT_RELATIVE__/**/*",\n "env.d.ts"\n ],\n "vueCompilerOptions": {\n "plugins": [\n "@omnicajs/vue-remote/tooling"\n ]\n }\n}\n';
|
|
2394
2904
|
const viteConfigTemplate = "import { fileURLToPath } from 'node:url'\n\nimport path from 'node:path'\n\nimport { defineConfig } from 'vite'\n\nimport vue from '@vitejs/plugin-vue'\nimport vueRemoteVitePlugin from '@omnicajs/vue-remote/vite-plugin'\n\nimport vueI18n from '@intlify/unplugin-vue-i18n/vite'\n\nimport svgLoader from 'vite-svg-loader'\n\nconst root = path.dirname(fileURLToPath(import.meta.url))\n\nexport default defineConfig({\n plugins: [\n svgLoader({\n defaultImport: 'component',\n }),\n vueRemoteVitePlugin(),\n vue(),\n vueI18n({\n defaultSFCLang: 'json',\n include: path.resolve(root, '__LOCALE_INCLUDE_PATTERN__/**/*.{json,json5,yaml,yml}'),\n }),\n ],\n resolve: {\n alias: {\n '@': path.resolve(root, __SOURCE_ROOT_PATH__),\n },\n },\n build: {\n rollupOptions: {\n input: path.resolve(root, __ENTRY_PATH__),\n },\n },\n})\n";
|
|
2905
|
+
const vitestBrowserConfigTemplate = "import { defineConfig, mergeConfig } from 'vitest/config'\nimport { playwright } from '@vitest/browser-playwright'\n\nimport viteConfig from './vite.config'\n\nexport default mergeConfig(viteConfig, defineConfig({\n optimizeDeps: {\n include: [\n '@omnicajs/vue-remote/host',\n '@omnicajs/vue-remote/remote',\n '@remote-ui/rpc',\n '@retailcrm/embed-ui',\n '@retailcrm/embed-ui-v1-components/host',\n '@retailcrm/embed-ui-v1-components/remote',\n '@retailcrm/embed-ui-v1-contexts/remote/settings',\n '@retailcrm/embed-ui-v1-endpoint/remote',\n 'vue',\n 'vue-i18n',\n ],\n },\n server: {\n allowedHosts: true,\n },\n test: {\n include: ['__SOURCE_ROOT__/sandbox/tests/browser/**/*.browser.test.ts'],\n browser: {\n enabled: true,\n provider: playwright({\n launchOptions: {\n channel: 'chromium',\n },\n }),\n headless: true,\n screenshotFailures: true,\n screenshotDirectory: 'artifacts/browser/screenshots',\n instances: [{ browser: 'chromium' }],\n },\n },\n}))\n";
|
|
2906
|
+
const vitestConfigTemplate = "import { defineConfig, mergeConfig } from 'vitest/config'\n\nimport viteConfig from './vite.config'\n\nexport default mergeConfig(viteConfig, defineConfig({\n test: {\n environment: 'jsdom',\n include: ['__SOURCE_ROOT__/sandbox/tests/unit/**/*.test.ts'],\n },\n}))\n";
|
|
2395
2907
|
const toPosixRelative = (from, to) => {
|
|
2396
2908
|
const relativePath = path.relative(from, to) || ".";
|
|
2397
2909
|
return relativePath.split(path.sep).join("/");
|
|
@@ -2440,7 +2952,7 @@ const detectReadmeLocale = () => {
|
|
|
2440
2952
|
}
|
|
2441
2953
|
return "en-GB";
|
|
2442
2954
|
};
|
|
2443
|
-
const createPackageManagerRunCommand = (packageManager) => {
|
|
2955
|
+
const createPackageManagerRunCommand$1 = (packageManager) => {
|
|
2444
2956
|
if (packageManager === "npm") {
|
|
2445
2957
|
return "npm run";
|
|
2446
2958
|
}
|
|
@@ -2464,6 +2976,25 @@ const createViteConfig = (cwd, sourceRoot) => replaceTemplateVars(
|
|
|
2464
2976
|
SOURCE_ROOT_PATH: quoteJsString(toPosixRelative(cwd, sourceRoot))
|
|
2465
2977
|
}
|
|
2466
2978
|
);
|
|
2979
|
+
const createVitestConfig = (cwd, sourceRoot) => replaceTemplateVars(
|
|
2980
|
+
vitestConfigTemplate,
|
|
2981
|
+
{
|
|
2982
|
+
SOURCE_ROOT: toPosixRelative(cwd, sourceRoot)
|
|
2983
|
+
}
|
|
2984
|
+
);
|
|
2985
|
+
const createVitestBrowserConfig = (cwd, sourceRoot) => replaceTemplateVars(
|
|
2986
|
+
vitestBrowserConfigTemplate,
|
|
2987
|
+
{
|
|
2988
|
+
SOURCE_ROOT: toPosixRelative(cwd, sourceRoot)
|
|
2989
|
+
}
|
|
2990
|
+
);
|
|
2991
|
+
const createPlaywrightConfig = (cwd, sourceRoot, packageManager) => replaceTemplateVars(
|
|
2992
|
+
playwrightConfigTemplate,
|
|
2993
|
+
{
|
|
2994
|
+
PACKAGE_MANAGER_RUN: createPackageManagerRunCommand$1(packageManager),
|
|
2995
|
+
SOURCE_ROOT: toPosixRelative(cwd, sourceRoot)
|
|
2996
|
+
}
|
|
2997
|
+
);
|
|
2467
2998
|
const createEslintConfig = (cwd, sourceRoot) => replaceTemplateVars(
|
|
2468
2999
|
eslintConfigTemplate,
|
|
2469
3000
|
{
|
|
@@ -2480,6 +3011,27 @@ const createEndpointWorker = (options) => replaceTemplateVars(
|
|
|
2480
3011
|
const createI18nIndex = () => i18nIndexTemplate;
|
|
2481
3012
|
const createSettingsPage = () => settingsPageTemplate;
|
|
2482
3013
|
const createOrderWidget = () => orderWidgetTemplate;
|
|
3014
|
+
const createSandboxUnitTest = (options) => replaceTemplateVars(
|
|
3015
|
+
sandboxUnitTestTemplate,
|
|
3016
|
+
{
|
|
3017
|
+
PAGE_CODE: options.pageCode,
|
|
3018
|
+
WIDGET_TARGET: options.widgetTarget
|
|
3019
|
+
}
|
|
3020
|
+
);
|
|
3021
|
+
const createSandboxBrowserTest = (cwd, sourceRoot, options) => replaceTemplateVars(
|
|
3022
|
+
sandboxBrowserTestTemplate,
|
|
3023
|
+
{
|
|
3024
|
+
PAGE_CODE: options.pageCode,
|
|
3025
|
+
SOURCE_ROOT: toPosixRelative(cwd, sourceRoot),
|
|
3026
|
+
WIDGET_TARGET: options.widgetTarget
|
|
3027
|
+
}
|
|
3028
|
+
);
|
|
3029
|
+
const createSandboxE2eTest = (cwd, sourceRoot) => replaceTemplateVars(
|
|
3030
|
+
sandboxE2eTestTemplate,
|
|
3031
|
+
{
|
|
3032
|
+
SOURCE_ROOT: toPosixRelative(cwd, sourceRoot)
|
|
3033
|
+
}
|
|
3034
|
+
);
|
|
2483
3035
|
const createMessages = () => `${JSON.stringify({}, null, 2)}${DEFAULT_NEWLINE}`;
|
|
2484
3036
|
const createExtensionConfig = (options) => `${JSON.stringify({
|
|
2485
3037
|
code: "retailcrm-extension-frontend",
|
|
@@ -2505,11 +3057,12 @@ const createExtensionConfig = (options) => `${JSON.stringify({
|
|
|
2505
3057
|
}, null, 2)}${DEFAULT_NEWLINE}`;
|
|
2506
3058
|
const createExtensionIcon = () => extensionIconTemplate;
|
|
2507
3059
|
const createPublishScript = () => publishScriptTemplate;
|
|
3060
|
+
const createServeScript = () => serveScriptTemplate;
|
|
2508
3061
|
const createReadme = (cwd, sourceRoot, options, packageManager) => replaceTemplateVars(
|
|
2509
3062
|
readmeTemplates[detectReadmeLocale()],
|
|
2510
3063
|
{
|
|
2511
3064
|
PACKAGE_MANAGER: packageManager,
|
|
2512
|
-
PACKAGE_MANAGER_RUN: createPackageManagerRunCommand(packageManager),
|
|
3065
|
+
PACKAGE_MANAGER_RUN: createPackageManagerRunCommand$1(packageManager),
|
|
2513
3066
|
PAGE_CODE: options.pageCode,
|
|
2514
3067
|
SOURCE_ROOT: toPosixRelative(cwd, sourceRoot),
|
|
2515
3068
|
WIDGET_TARGET: options.widgetTarget
|
|
@@ -2526,6 +3079,7 @@ const createInitChanges = () => ({
|
|
|
2526
3079
|
git: [],
|
|
2527
3080
|
hooks: [],
|
|
2528
3081
|
install: null,
|
|
3082
|
+
browserInstall: null,
|
|
2529
3083
|
skipped: [],
|
|
2530
3084
|
warnings: []
|
|
2531
3085
|
});
|
|
@@ -2618,6 +3172,11 @@ const printInitReport = (cwd, sourceRoot, version, packageManager, changes, opti
|
|
|
2618
3172
|
console.log("install");
|
|
2619
3173
|
console.log(` ${changes.install}`);
|
|
2620
3174
|
}
|
|
3175
|
+
if (changes.browserInstall) {
|
|
3176
|
+
console.log("");
|
|
3177
|
+
console.log("browser install");
|
|
3178
|
+
console.log(` ${changes.browserInstall}`);
|
|
3179
|
+
}
|
|
2621
3180
|
if (changes.skipped.length > 0) {
|
|
2622
3181
|
console.log("");
|
|
2623
3182
|
console.log("skipped");
|
|
@@ -2651,7 +3210,8 @@ const printInitSummary = (cwd, sourceRoot, version, packageManager, changes) =>
|
|
|
2651
3210
|
changes.mcp.length ? "MCP config updated" : null,
|
|
2652
3211
|
changes.git.length ? `git: ${changes.git.join(", ")}` : null,
|
|
2653
3212
|
changes.hooks.length ? `package hooks ran: ${changes.hooks.length}` : null,
|
|
2654
|
-
changes.install ? `install: ${changes.install}` : null
|
|
3213
|
+
changes.install ? `install: ${changes.install}` : null,
|
|
3214
|
+
changes.browserInstall ? `browser install: ${changes.browserInstall}` : null
|
|
2655
3215
|
].filter((item) => typeof item === "string");
|
|
2656
3216
|
if (summary.length > 0) {
|
|
2657
3217
|
console.log("");
|
|
@@ -4984,6 +5544,15 @@ const resolvePackageManager = async (cwd, explicitPackageManager) => {
|
|
|
4984
5544
|
}
|
|
4985
5545
|
return "npm";
|
|
4986
5546
|
};
|
|
5547
|
+
const createPackageManagerRunCommand = (packageManager) => {
|
|
5548
|
+
if (packageManager === "npm") {
|
|
5549
|
+
return "npm run";
|
|
5550
|
+
}
|
|
5551
|
+
if (packageManager === "bun") {
|
|
5552
|
+
return "bun run";
|
|
5553
|
+
}
|
|
5554
|
+
return packageManager;
|
|
5555
|
+
};
|
|
4987
5556
|
const resolveInitPackages = (tokens, extraTokens) => {
|
|
4988
5557
|
const packageIds = tokens ?? [...DEFAULT_INIT_PACKAGE_IDS, ...extraTokens ?? []];
|
|
4989
5558
|
const packages = resolveInstallPackages(packageIds);
|
|
@@ -5116,6 +5685,13 @@ const applyInitPackageJson = (cwd, selectedPackages, version, packageManager, op
|
|
|
5116
5685
|
setMissingScript(packageJson, "eslint", "eslint .", changes);
|
|
5117
5686
|
setMissingScript(packageJson, "eslint:fix", "eslint --fix .", changes);
|
|
5118
5687
|
setMissingScript(packageJson, "publish-extension", "node scripts/publish-extension.mjs", changes);
|
|
5688
|
+
setMissingScript(packageJson, "test", `${createPackageManagerRunCommand(packageManager)} test:unit && ${createPackageManagerRunCommand(packageManager)} test:browser && ${createPackageManagerRunCommand(packageManager)} test:e2e`, changes);
|
|
5689
|
+
setMissingScript(packageJson, "test:unit", "vitest --run --config vitest.config.ts", changes);
|
|
5690
|
+
setMissingScript(packageJson, "test:browser", "vitest --run --config vitest.config.browser.ts", changes);
|
|
5691
|
+
setMissingScript(packageJson, "test:e2e", "playwright test -c vitest.config.playwright.ts", changes);
|
|
5692
|
+
setMissingScript(packageJson, "test:browsers:install", "playwright install chromium", changes);
|
|
5693
|
+
setMissingScript(packageJson, "serve:extension", "node scripts/serve-extension.mjs --host 127.0.0.1 --port 4175", changes);
|
|
5694
|
+
setMissingScript(packageJson, "sandbox:serve", "embed-ui-v1-sandbox serve --host 127.0.0.1 --port 4173", changes);
|
|
5119
5695
|
for (const selectedPackage of selectedPackages) {
|
|
5120
5696
|
setDependency(
|
|
5121
5697
|
packageJson,
|
|
@@ -5175,12 +5751,15 @@ const applyInitDirectories = (sourceRoot, options, changes) => {
|
|
|
5175
5751
|
ensureDirectory(path.join(sourceRoot, "i18n/locales"), options, changes);
|
|
5176
5752
|
}
|
|
5177
5753
|
};
|
|
5178
|
-
const applyInitConfigs = (cwd, sourceRoot, options, changes) => {
|
|
5754
|
+
const applyInitConfigs = (cwd, sourceRoot, packageManager, options, changes) => {
|
|
5179
5755
|
if (options.noConfigs || isGuidanceOnlyInit(options)) {
|
|
5180
5756
|
return;
|
|
5181
5757
|
}
|
|
5182
5758
|
writeFileIfAllowed(path.join(cwd, "tsconfig.json"), createTsConfig(cwd, sourceRoot), options, changes);
|
|
5183
5759
|
writeFileIfAllowed(path.join(cwd, "vite.config.ts"), createViteConfig(cwd, sourceRoot), options, changes);
|
|
5760
|
+
writeFileIfAllowed(path.join(cwd, "vitest.config.ts"), createVitestConfig(cwd, sourceRoot), options, changes);
|
|
5761
|
+
writeFileIfAllowed(path.join(cwd, "vitest.config.browser.ts"), createVitestBrowserConfig(cwd, sourceRoot), options, changes);
|
|
5762
|
+
writeFileIfAllowed(path.join(cwd, "vitest.config.playwright.ts"), createPlaywrightConfig(cwd, sourceRoot, packageManager), options, changes);
|
|
5184
5763
|
writeFileIfAllowed(path.join(cwd, "env.d.ts"), createEnvDts(), options, changes);
|
|
5185
5764
|
writeFileIfAllowed(path.join(cwd, "eslint.config.js"), createEslintConfig(cwd, sourceRoot), options, changes);
|
|
5186
5765
|
};
|
|
@@ -5204,8 +5783,12 @@ const applyInitTemplate = (cwd, sourceRoot, packageManager, options, changes) =>
|
|
|
5204
5783
|
writeFileIfAllowed(path.join(sourceRoot, "i18n/locales/ru-RU.json"), createMessages(), options, changes);
|
|
5205
5784
|
writeFileIfAllowed(path.join(sourceRoot, "pages/SettingsPage.vue"), createSettingsPage(), options, changes);
|
|
5206
5785
|
writeFileIfAllowed(path.join(sourceRoot, "widgets/OrderCommonAfterWidget.vue"), createOrderWidget(), options, changes);
|
|
5786
|
+
writeFileIfAllowed(path.join(sourceRoot, "sandbox/tests/unit/extensionrc.test.ts"), createSandboxUnitTest(options), options, changes);
|
|
5787
|
+
writeFileIfAllowed(path.join(sourceRoot, "sandbox/tests/browser/starter.browser.test.ts"), createSandboxBrowserTest(cwd, sourceRoot, options), options, changes);
|
|
5788
|
+
writeFileIfAllowed(path.join(sourceRoot, "sandbox/tests/e2e/starter.e2e.ts"), createSandboxE2eTest(cwd, sourceRoot), options, changes);
|
|
5207
5789
|
writeFileIfAllowed(path.join(cwd, "extensionrc.json"), createExtensionConfig(options), options, changes);
|
|
5208
5790
|
writeFileIfAllowed(path.join(cwd, "scripts/publish-extension.mjs"), createPublishScript(), options, changes);
|
|
5791
|
+
writeFileIfAllowed(path.join(cwd, "scripts/serve-extension.mjs"), createServeScript(), options, changes);
|
|
5209
5792
|
writeFileIfAllowed(path.join(cwd, "README.md"), createReadme(cwd, sourceRoot, options, packageManager), options, changes);
|
|
5210
5793
|
};
|
|
5211
5794
|
const runInstall = async (cwd, packageManager, options, changes, packageJsonChanged) => {
|
|
@@ -5233,6 +5816,39 @@ const runInstall = async (cwd, packageManager, options, changes, packageJsonChan
|
|
|
5233
5816
|
throw error;
|
|
5234
5817
|
}
|
|
5235
5818
|
};
|
|
5819
|
+
const resolveRunScriptArgs = (packageManager, scriptName) => {
|
|
5820
|
+
if (packageManager === "npm" || packageManager === "bun") {
|
|
5821
|
+
return ["run", scriptName];
|
|
5822
|
+
}
|
|
5823
|
+
return [scriptName];
|
|
5824
|
+
};
|
|
5825
|
+
const createRunScriptCommand = (packageManager, scriptName) => `${createPackageManagerRunCommand(packageManager)} ${scriptName}`;
|
|
5826
|
+
const runBrowserInstall = async (cwd, packageManager, options, changes, packageJsonChanged) => {
|
|
5827
|
+
if (options.noInstall || isGuidanceOnlyInit(options)) {
|
|
5828
|
+
return;
|
|
5829
|
+
}
|
|
5830
|
+
if (!packageJsonChanged && !options.force) {
|
|
5831
|
+
changes.skipped.push("browser install skipped because package.json was not changed");
|
|
5832
|
+
return;
|
|
5833
|
+
}
|
|
5834
|
+
const scriptName = "test:browsers:install";
|
|
5835
|
+
const args = resolveRunScriptArgs(packageManager, scriptName);
|
|
5836
|
+
changes.browserInstall = createRunScriptCommand(packageManager, scriptName);
|
|
5837
|
+
if (options.dryRun) {
|
|
5838
|
+
return;
|
|
5839
|
+
}
|
|
5840
|
+
try {
|
|
5841
|
+
await runCommandWithTerminalStatus(
|
|
5842
|
+
packageManager,
|
|
5843
|
+
args,
|
|
5844
|
+
{ cwd },
|
|
5845
|
+
"Installing Playwright Chromium"
|
|
5846
|
+
);
|
|
5847
|
+
} catch (error) {
|
|
5848
|
+
printExecErrorOutput(error);
|
|
5849
|
+
throw error;
|
|
5850
|
+
}
|
|
5851
|
+
};
|
|
5236
5852
|
const applyInitGit = async (cwd, options, changes) => {
|
|
5237
5853
|
if (!options.initGit) {
|
|
5238
5854
|
return;
|
|
@@ -5301,10 +5917,12 @@ const runInit = async (options) => {
|
|
|
5301
5917
|
packageJsonPath = applyInitPackageJson(cwd, selectedPackages, version, packageManager, resolvedOptions, changes);
|
|
5302
5918
|
updateGitignore(cwd, resolvedOptions, changes);
|
|
5303
5919
|
applyInitDirectories(sourceRoot, resolvedOptions, changes);
|
|
5304
|
-
applyInitConfigs(cwd, sourceRoot, resolvedOptions, changes);
|
|
5920
|
+
applyInitConfigs(cwd, sourceRoot, packageManager, resolvedOptions, changes);
|
|
5305
5921
|
applyInitTemplate(cwd, sourceRoot, packageManager, resolvedOptions, changes);
|
|
5306
5922
|
}
|
|
5307
|
-
|
|
5923
|
+
const packageJsonChanged = Boolean(packageJsonPath && changes.packageJson.length > 0);
|
|
5924
|
+
await runInstall(cwd, packageManager, resolvedOptions, changes, packageJsonChanged);
|
|
5925
|
+
await runBrowserInstall(cwd, packageManager, resolvedOptions, changes, packageJsonChanged);
|
|
5308
5926
|
await applyInitPackageConfigHooks(cwd, selectedPackages, packageManager, resolvedOptions, changes);
|
|
5309
5927
|
await applyInitSkills(cwd, selectedPackages, packageManager, resolvedOptions, changes);
|
|
5310
5928
|
await applyInitAgents(cwd, selectedPackages, packageManager, resolvedOptions, changes);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retailcrm/embed-ui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.30-alpha.1",
|
|
5
5
|
"description": "API and components for creating RetailCRM UI extensions",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -43,9 +43,19 @@
|
|
|
43
43
|
"build:bin": "vite build -c vite.config.bin.ts",
|
|
44
44
|
"build:meta": "npx tsx scripts/build.meta.ts",
|
|
45
45
|
"eslint": "eslint .",
|
|
46
|
+
"release": "conventional-release --tags",
|
|
47
|
+
"release:alpha": "conventional-release --prerelease alpha --tags",
|
|
48
|
+
"release:beta": "conventional-release --prerelease beta --tags",
|
|
49
|
+
"release:dry": "conventional-release --dry --verbose --tags",
|
|
50
|
+
"release:rc": "conventional-release --prerelease rc --tags",
|
|
46
51
|
"test": "vitest --run",
|
|
52
|
+
"test:browser": "yarn workspace @retailcrm/embed-ui-v1-sandbox run test:browser",
|
|
47
53
|
"test:coverage": "vitest --run --coverage"
|
|
48
54
|
},
|
|
55
|
+
"release": {
|
|
56
|
+
"mode": "sync",
|
|
57
|
+
"tagPrefix": "v"
|
|
58
|
+
},
|
|
49
59
|
"peerDependencies": {
|
|
50
60
|
"pinia": "^2.2",
|
|
51
61
|
"vue": "^3.5"
|
|
@@ -55,22 +65,22 @@
|
|
|
55
65
|
"@floating-ui/dom": "^1.6.13",
|
|
56
66
|
"@inquirer/prompts": "^8.4.3",
|
|
57
67
|
"@omnicajs/symfony-router": "^1.0.0",
|
|
58
|
-
"@omnicajs/vue-remote": "^0.2.
|
|
68
|
+
"@omnicajs/vue-remote": "^0.2.26",
|
|
59
69
|
"@remote-ui/rpc": "^1.4.5",
|
|
60
|
-
"@retailcrm/embed-ui-v1-components": "^0.9.
|
|
61
|
-
"@retailcrm/embed-ui-v1-contexts": "^0.9.
|
|
62
|
-
"@retailcrm/embed-ui-v1-endpoint": "^0.9.
|
|
63
|
-
"@retailcrm/embed-ui-v1-types": "^0.9.
|
|
70
|
+
"@retailcrm/embed-ui-v1-components": "^0.9.30-alpha.1",
|
|
71
|
+
"@retailcrm/embed-ui-v1-contexts": "^0.9.30-alpha.1",
|
|
72
|
+
"@retailcrm/embed-ui-v1-endpoint": "^0.9.30-alpha.1",
|
|
73
|
+
"@retailcrm/embed-ui-v1-types": "^0.9.30-alpha.1",
|
|
64
74
|
"yargs": "^17.7.2"
|
|
65
75
|
},
|
|
66
76
|
"devDependencies": {
|
|
67
77
|
"@eslint/eslintrc": "^3.3.3",
|
|
68
78
|
"@eslint/js": "^9.39.1",
|
|
69
|
-
"@modulify/git
|
|
79
|
+
"@modulify/conventional-git": "^0.1.3",
|
|
80
|
+
"@modulify/conventional-release": "^0.1.3",
|
|
70
81
|
"@modulify/pkg": "^1.0.1",
|
|
71
82
|
"@omnicajs/eslint-plugin-dependencies": "^0.0.2",
|
|
72
|
-
"@retailcrm/embed-ui-v1-testing": "^0.9.
|
|
73
|
-
"@types/git-semver-tags": "^7.0.0",
|
|
83
|
+
"@retailcrm/embed-ui-v1-testing": "^0.9.30-alpha.1",
|
|
74
84
|
"@types/node": "^22.19.2",
|
|
75
85
|
"@types/semver": "^7.7.1",
|
|
76
86
|
"@types/yargs": "^17.0.35",
|
|
@@ -79,16 +89,12 @@
|
|
|
79
89
|
"@vue/language-server": "^3.1.8",
|
|
80
90
|
"@vue/test-utils": "^2.4.6",
|
|
81
91
|
"chalk": "^5.3.0",
|
|
82
|
-
"conventional-changelog": "^6.0.0",
|
|
83
|
-
"conventional-changelog-conventionalcommits": "^8.0.0",
|
|
84
|
-
"conventional-recommended-bump": "11.2.0",
|
|
85
92
|
"eslint": "^9.39.1",
|
|
86
93
|
"eslint-plugin-import": "^2.32.0",
|
|
87
94
|
"eslint-plugin-n": "^17.23.1",
|
|
88
95
|
"eslint-plugin-promise": "^7.2.1",
|
|
89
96
|
"eslint-plugin-vue": "^10.8.0",
|
|
90
97
|
"figures": "^6.1.0",
|
|
91
|
-
"git-semver-tags": "8.0.1",
|
|
92
98
|
"glob": "^13.0.0",
|
|
93
99
|
"globals": "^16.5.0",
|
|
94
100
|
"jsdom": "^27.3.0",
|