@retailcrm/embed-ui 0.9.21 → 0.9.22-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 +21 -0
- package/README.md +102 -2
- package/bin/embed-ui.mjs +2681 -0
- package/dist/meta.json +151 -136
- package/package.json +21 -9
- package/types/widget.d.ts +7 -139
- package/bin/embed-ui-update.mjs +0 -617
package/bin/embed-ui.mjs
ADDED
|
@@ -0,0 +1,2681 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createInterface } from "node:readline/promises";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { pathToFileURL } from "node:url";
|
|
7
|
+
import process$1 from "node:process";
|
|
8
|
+
import yargs from "yargs";
|
|
9
|
+
import { randomUUID } from "node:crypto";
|
|
10
|
+
const PACKAGE_MANAGERS = ["yarn", "npm", "pnpm", "bun"];
|
|
11
|
+
const HELP_TEXT = `Usage:
|
|
12
|
+
npx @retailcrm/embed-ui [target] [version] [options]
|
|
13
|
+
npx @retailcrm/embed-ui init [target] [options]
|
|
14
|
+
|
|
15
|
+
Options:
|
|
16
|
+
-t, --target <path> Target path (default: current directory)
|
|
17
|
+
-v, --version <ver> Target version. If omitted, latest npm version is used
|
|
18
|
+
--exact Use exact version instead of range
|
|
19
|
+
--dry-run Show changes without writing package.json
|
|
20
|
+
--add Add selected embed-ui packages into one package.json
|
|
21
|
+
--packages <list> Comma-separated package ids or names for --add/init
|
|
22
|
+
--cwd <path> Project working directory for init
|
|
23
|
+
--package-manager Package manager for init installs
|
|
24
|
+
--interactive Ask init questions in TTY instead of using every default
|
|
25
|
+
--no-install Do not run package manager install in init mode
|
|
26
|
+
--no-configs Do not create root TypeScript, Vite, ESLint and env config files
|
|
27
|
+
--force-deps Replace incompatible existing init dependencies
|
|
28
|
+
--fix-sections Move init dependencies to expected package.json sections
|
|
29
|
+
--no-agents Do not create or update AGENTS.md in init mode
|
|
30
|
+
--no-mcp Do not add package MCP instructions in init mode
|
|
31
|
+
--mcp-client-configs Comma-separated MCP client configs to create (cursor,junie,vscode)
|
|
32
|
+
-h, --help Show this help
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
npx @retailcrm/embed-ui
|
|
36
|
+
npx @retailcrm/embed-ui --version 0.9.11
|
|
37
|
+
npx @retailcrm/embed-ui ./my-project 0.9.11
|
|
38
|
+
npx @retailcrm/embed-ui --target ./my-project --dry-run
|
|
39
|
+
npx @retailcrm/embed-ui --add
|
|
40
|
+
npx @retailcrm/embed-ui --add --packages components,contexts
|
|
41
|
+
npx @retailcrm/embed-ui init ./web --package-manager yarn
|
|
42
|
+
npx @retailcrm/embed-ui init --interactive
|
|
43
|
+
`;
|
|
44
|
+
const isSemverLike = (value) => /^v?\d+\.\d+\.\d+/.test(value);
|
|
45
|
+
const stripLeadingV = (value) => value.replace(/^v/, "");
|
|
46
|
+
const parsePackageList = (value) => value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
47
|
+
const parseInitArgs = (argv) => {
|
|
48
|
+
const normalizedArgv = argv.map((argument) => {
|
|
49
|
+
if (argument === "--no-dirs") {
|
|
50
|
+
return "--no-dirs-enabled";
|
|
51
|
+
}
|
|
52
|
+
if (argument === "--no-template") {
|
|
53
|
+
return "--no-template-enabled";
|
|
54
|
+
}
|
|
55
|
+
return argument;
|
|
56
|
+
});
|
|
57
|
+
const parsed = yargs(normalizedArgv).scriptName("embed-ui").usage("Usage: $0 init [target] [options]").help(false).version(false).exitProcess(false).strictOptions().parserConfiguration({
|
|
58
|
+
"camel-case-expansion": true,
|
|
59
|
+
"boolean-negation": true
|
|
60
|
+
}).option("cwd", {
|
|
61
|
+
type: "string",
|
|
62
|
+
default: process$1.cwd(),
|
|
63
|
+
describe: "Project working directory"
|
|
64
|
+
}).option("help", {
|
|
65
|
+
alias: "h",
|
|
66
|
+
type: "boolean"
|
|
67
|
+
}).option("target", {
|
|
68
|
+
alias: "t",
|
|
69
|
+
type: "string",
|
|
70
|
+
describe: "Frontend target directory relative to cwd"
|
|
71
|
+
}).option("version", {
|
|
72
|
+
alias: "v",
|
|
73
|
+
type: "string",
|
|
74
|
+
coerce: stripLeadingV,
|
|
75
|
+
describe: "Target package version"
|
|
76
|
+
}).option("packages", {
|
|
77
|
+
type: "string",
|
|
78
|
+
coerce: parsePackageList,
|
|
79
|
+
describe: "Comma-separated init package ids or names"
|
|
80
|
+
}).option("with", {
|
|
81
|
+
type: "string",
|
|
82
|
+
coerce: parsePackageList,
|
|
83
|
+
describe: "Additional published package ids or names"
|
|
84
|
+
}).option("package-manager", {
|
|
85
|
+
type: "string",
|
|
86
|
+
choices: PACKAGE_MANAGERS,
|
|
87
|
+
describe: "Package manager used for install"
|
|
88
|
+
}).option("dirs", {
|
|
89
|
+
type: "string",
|
|
90
|
+
coerce: parsePackageList,
|
|
91
|
+
describe: "Comma-separated directory presets"
|
|
92
|
+
}).option("src-dir", {
|
|
93
|
+
type: "string",
|
|
94
|
+
describe: "Frontend source root relative to cwd"
|
|
95
|
+
}).option("template", {
|
|
96
|
+
type: "string",
|
|
97
|
+
default: "order-card",
|
|
98
|
+
describe: "Starter template name"
|
|
99
|
+
}).option("page-code", {
|
|
100
|
+
type: "string",
|
|
101
|
+
default: "settings",
|
|
102
|
+
describe: "Starter embedded page code"
|
|
103
|
+
}).option("widget-target", {
|
|
104
|
+
type: "string",
|
|
105
|
+
default: "order/card:common.after",
|
|
106
|
+
describe: "Starter widget target"
|
|
107
|
+
}).option("dry-run", { type: "boolean", default: false }).option("exact", { type: "boolean", default: false }).option("interactive", { type: "boolean", default: false }).option("install", { type: "boolean", default: true }).option("force", { type: "boolean", default: false }).option("force-deps", { type: "boolean", default: false }).option("fix-sections", { type: "boolean", default: false }).option("force-files", { type: "boolean", default: false }).option("configs", { type: "boolean", default: true }).option("dirs-enabled", { type: "boolean", default: true }).option("template-enabled", { type: "boolean", default: true }).option("agents", { type: "boolean", default: true }).option("force-agents", { type: "boolean", default: false }).option("agents-only", { type: "boolean", default: false }).option("mcp", { type: "boolean", default: true }).option("force-mcp", { type: "boolean", default: false }).option("mcp-client-configs", {
|
|
108
|
+
type: "string",
|
|
109
|
+
coerce: parsePackageList,
|
|
110
|
+
describe: "Comma-separated MCP client config ids"
|
|
111
|
+
}).parseSync();
|
|
112
|
+
if (parsed.help || parsed.h) {
|
|
113
|
+
console.log(HELP_TEXT);
|
|
114
|
+
process$1.exit(0);
|
|
115
|
+
}
|
|
116
|
+
const positionals = parsed._.map(String);
|
|
117
|
+
if (positionals.length > 1) {
|
|
118
|
+
throw new Error("Too many positional arguments");
|
|
119
|
+
}
|
|
120
|
+
if (!parsed.mcp && parsed.mcpClientConfigs?.length) {
|
|
121
|
+
throw new Error("Option --mcp-client-configs cannot be used together with --no-mcp");
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
command: "init",
|
|
125
|
+
cwd: path.resolve(process$1.cwd(), parsed.cwd),
|
|
126
|
+
target: parsed.target ?? positionals[0] ?? null,
|
|
127
|
+
version: parsed.version ?? null,
|
|
128
|
+
dryRun: parsed.dryRun,
|
|
129
|
+
exact: parsed.exact,
|
|
130
|
+
packages: parsed.packages ?? null,
|
|
131
|
+
with: parsed.with ?? null,
|
|
132
|
+
packageManager: parsed.packageManager ?? null,
|
|
133
|
+
interactive: parsed.interactive,
|
|
134
|
+
noInstall: !parsed.install,
|
|
135
|
+
force: parsed.force,
|
|
136
|
+
forceDeps: parsed.forceDeps,
|
|
137
|
+
fixSections: parsed.fixSections,
|
|
138
|
+
forceFiles: parsed.forceFiles,
|
|
139
|
+
noConfigs: !parsed.configs,
|
|
140
|
+
noDirs: !parsed.dirsEnabled,
|
|
141
|
+
dirs: parsed.dirs ?? null,
|
|
142
|
+
srcDir: parsed.srcDir ?? null,
|
|
143
|
+
noTemplate: !parsed.templateEnabled,
|
|
144
|
+
template: parsed.template,
|
|
145
|
+
pageCode: parsed.pageCode,
|
|
146
|
+
widgetTarget: parsed.widgetTarget,
|
|
147
|
+
noAgents: !parsed.agents,
|
|
148
|
+
forceAgents: parsed.forceAgents,
|
|
149
|
+
agentsOnly: parsed.agentsOnly,
|
|
150
|
+
noMcp: !parsed.mcp,
|
|
151
|
+
forceMcp: parsed.forceMcp,
|
|
152
|
+
mcpClientConfigs: parsed.mcpClientConfigs ?? null
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
const parseArgs = (argv) => {
|
|
156
|
+
if (argv[0] === "init") {
|
|
157
|
+
return parseInitArgs(argv.slice(1));
|
|
158
|
+
}
|
|
159
|
+
const parsed = yargs(argv).scriptName("embed-ui").usage("Usage: $0 [target] [version] [options]").help(false).version(false).exitProcess(false).strictOptions().option("target", {
|
|
160
|
+
alias: "t",
|
|
161
|
+
type: "string",
|
|
162
|
+
default: process$1.cwd()
|
|
163
|
+
}).option("help", {
|
|
164
|
+
alias: "h",
|
|
165
|
+
type: "boolean"
|
|
166
|
+
}).option("version", {
|
|
167
|
+
alias: "v",
|
|
168
|
+
type: "string",
|
|
169
|
+
coerce: stripLeadingV
|
|
170
|
+
}).option("dry-run", { type: "boolean", default: false }).option("exact", { type: "boolean", default: false }).option("add", { type: "boolean", default: false }).option("packages", {
|
|
171
|
+
type: "string",
|
|
172
|
+
coerce: parsePackageList
|
|
173
|
+
}).parseSync();
|
|
174
|
+
if (parsed.help || parsed.h) {
|
|
175
|
+
console.log(HELP_TEXT);
|
|
176
|
+
process$1.exit(0);
|
|
177
|
+
}
|
|
178
|
+
const positionals = parsed._.map(String);
|
|
179
|
+
if (positionals.length > 2) {
|
|
180
|
+
throw new Error("Too many positional arguments");
|
|
181
|
+
}
|
|
182
|
+
const options = {
|
|
183
|
+
command: "update",
|
|
184
|
+
target: path.resolve(process$1.cwd(), parsed.target),
|
|
185
|
+
version: parsed.version ?? null,
|
|
186
|
+
dryRun: parsed.dryRun,
|
|
187
|
+
exact: parsed.exact,
|
|
188
|
+
add: parsed.add,
|
|
189
|
+
packages: parsed.packages ?? null
|
|
190
|
+
};
|
|
191
|
+
if (positionals.length >= 1) {
|
|
192
|
+
const first = positionals[0];
|
|
193
|
+
if (!options.version && isSemverLike(first)) {
|
|
194
|
+
options.version = stripLeadingV(first);
|
|
195
|
+
} else {
|
|
196
|
+
options.target = path.resolve(process$1.cwd(), first);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (positionals.length === 2) {
|
|
200
|
+
if (options.version) {
|
|
201
|
+
throw new Error("Version is already specified");
|
|
202
|
+
}
|
|
203
|
+
options.version = stripLeadingV(positionals[1]);
|
|
204
|
+
}
|
|
205
|
+
if (options.packages && !options.add) {
|
|
206
|
+
throw new Error("Option --packages can only be used together with --add");
|
|
207
|
+
}
|
|
208
|
+
return options;
|
|
209
|
+
};
|
|
210
|
+
const TARGET_SECTIONS = [
|
|
211
|
+
"dependencies",
|
|
212
|
+
"devDependencies",
|
|
213
|
+
"peerDependencies",
|
|
214
|
+
"optionalDependencies"
|
|
215
|
+
];
|
|
216
|
+
const ROOT_PACKAGE = "@retailcrm/embed-ui";
|
|
217
|
+
const DEFAULT_INIT_PACKAGE_IDS = ["embed-ui", "components", "contexts", "types", "endpoint"];
|
|
218
|
+
const INSTALLABLE_PACKAGES = [
|
|
219
|
+
{
|
|
220
|
+
id: "embed-ui",
|
|
221
|
+
name: ROOT_PACKAGE,
|
|
222
|
+
section: "dependencies",
|
|
223
|
+
description: "Базовый пакет с общим API и согласованными v1-зависимостями."
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
id: "components",
|
|
227
|
+
name: "@retailcrm/embed-ui-v1-components",
|
|
228
|
+
section: "dependencies",
|
|
229
|
+
description: "UI-компоненты для host/remote приложений.",
|
|
230
|
+
hooks: [
|
|
231
|
+
{
|
|
232
|
+
type: "agents",
|
|
233
|
+
binName: "embed-ui-v1-components",
|
|
234
|
+
command: "init-agents",
|
|
235
|
+
failureMode: "advisory"
|
|
236
|
+
}
|
|
237
|
+
]
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
id: "contexts",
|
|
241
|
+
name: "@retailcrm/embed-ui-v1-contexts",
|
|
242
|
+
section: "dependencies",
|
|
243
|
+
description: "Реактивные контексты RetailCRM JS API."
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
id: "types",
|
|
247
|
+
name: "@retailcrm/embed-ui-v1-types",
|
|
248
|
+
section: "dependencies",
|
|
249
|
+
description: "Базовые type declarations для RetailCRM JS API."
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
id: "testing",
|
|
253
|
+
name: "@retailcrm/embed-ui-v1-testing",
|
|
254
|
+
section: "devDependencies",
|
|
255
|
+
description: "Вспомогательные утилиты и типы для тестов интеграций."
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
id: "endpoint",
|
|
259
|
+
name: "@retailcrm/embed-ui-v1-endpoint",
|
|
260
|
+
section: "dependencies",
|
|
261
|
+
description: "Endpoint API для интеграций в RetailCRM.",
|
|
262
|
+
hooks: [
|
|
263
|
+
{
|
|
264
|
+
type: "agents",
|
|
265
|
+
binName: "embed-ui-v1-endpoint",
|
|
266
|
+
command: "init-agents",
|
|
267
|
+
failureMode: "advisory",
|
|
268
|
+
requiresMcp: true
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
type: "config",
|
|
272
|
+
binName: "embed-ui-v1-endpoint",
|
|
273
|
+
command: "init-config",
|
|
274
|
+
failureMode: "advisory",
|
|
275
|
+
requiresMcp: true
|
|
276
|
+
}
|
|
277
|
+
]
|
|
278
|
+
}
|
|
279
|
+
];
|
|
280
|
+
const resolveLatestVersion = () => {
|
|
281
|
+
const output = execFileSync(
|
|
282
|
+
"npm",
|
|
283
|
+
["view", ROOT_PACKAGE, "version"],
|
|
284
|
+
{
|
|
285
|
+
encoding: "utf8",
|
|
286
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
287
|
+
}
|
|
288
|
+
).trim();
|
|
289
|
+
if (!output) {
|
|
290
|
+
throw new Error(`Cannot resolve latest version for ${ROOT_PACKAGE}`);
|
|
291
|
+
}
|
|
292
|
+
return output;
|
|
293
|
+
};
|
|
294
|
+
const isTargetPackage = (name) => name === ROOT_PACKAGE || name.startsWith(`${ROOT_PACKAGE}-`);
|
|
295
|
+
const createRange = (version, exact) => exact ? version : `^${version}`;
|
|
296
|
+
const formatRange = (currentRange, nextVersion, exact) => {
|
|
297
|
+
if (exact) {
|
|
298
|
+
return nextVersion;
|
|
299
|
+
}
|
|
300
|
+
if (currentRange.startsWith("workspace:")) {
|
|
301
|
+
return currentRange;
|
|
302
|
+
}
|
|
303
|
+
if (currentRange.startsWith("~")) {
|
|
304
|
+
return `~${nextVersion}`;
|
|
305
|
+
}
|
|
306
|
+
if (currentRange.startsWith("^")) {
|
|
307
|
+
return `^${nextVersion}`;
|
|
308
|
+
}
|
|
309
|
+
return `^${nextVersion}`;
|
|
310
|
+
};
|
|
311
|
+
const findDependencySection = (packageJson, packageName) => {
|
|
312
|
+
for (const section of TARGET_SECTIONS) {
|
|
313
|
+
const dependencyMap = packageJson[section];
|
|
314
|
+
if (dependencyMap && typeof dependencyMap === "object" && packageName in dependencyMap) {
|
|
315
|
+
return section;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return null;
|
|
319
|
+
};
|
|
320
|
+
const updatePackageJson = (packageJson, version, exact) => {
|
|
321
|
+
const updates = [];
|
|
322
|
+
for (const section of TARGET_SECTIONS) {
|
|
323
|
+
const dependencyMap = packageJson[section];
|
|
324
|
+
if (!dependencyMap || typeof dependencyMap !== "object") {
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
for (const [name, currentRange] of Object.entries(dependencyMap)) {
|
|
328
|
+
if (!isTargetPackage(name) || typeof currentRange !== "string") {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
const nextRange = formatRange(currentRange, version, exact);
|
|
332
|
+
if (nextRange === currentRange) {
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
dependencyMap[name] = nextRange;
|
|
336
|
+
updates.push({
|
|
337
|
+
type: "update",
|
|
338
|
+
section,
|
|
339
|
+
name,
|
|
340
|
+
currentRange,
|
|
341
|
+
nextRange
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return updates;
|
|
346
|
+
};
|
|
347
|
+
const resolveInstallPackages = (tokens) => {
|
|
348
|
+
const selectedPackages = [];
|
|
349
|
+
const seen = /* @__PURE__ */ new Set();
|
|
350
|
+
for (const token of tokens) {
|
|
351
|
+
const normalized = token.trim();
|
|
352
|
+
if (!normalized) {
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
const numericIndex = Number(normalized);
|
|
356
|
+
const selectedPackage = Number.isInteger(numericIndex) && numericIndex >= 1 && numericIndex <= INSTALLABLE_PACKAGES.length ? INSTALLABLE_PACKAGES[numericIndex - 1] : INSTALLABLE_PACKAGES.find((entry) => entry.id === normalized || entry.name === normalized);
|
|
357
|
+
if (!selectedPackage) {
|
|
358
|
+
const supported = INSTALLABLE_PACKAGES.map((entry, index) => `${index + 1}/${entry.id}/${entry.name}`).join(", ");
|
|
359
|
+
throw new Error(`Unknown add target "${normalized}". Supported values: ${supported}`);
|
|
360
|
+
}
|
|
361
|
+
if (seen.has(selectedPackage.name)) {
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
seen.add(selectedPackage.name);
|
|
365
|
+
selectedPackages.push(selectedPackage);
|
|
366
|
+
}
|
|
367
|
+
return selectedPackages;
|
|
368
|
+
};
|
|
369
|
+
const installPackages = (packageJson, packages, version, exact) => {
|
|
370
|
+
const updates = [];
|
|
371
|
+
for (const selectedPackage of packages) {
|
|
372
|
+
const section = findDependencySection(packageJson, selectedPackage.name) ?? selectedPackage.section;
|
|
373
|
+
const dependencyMap = packageJson[section] ?? {};
|
|
374
|
+
if (!(section in packageJson)) {
|
|
375
|
+
packageJson[section] = dependencyMap;
|
|
376
|
+
}
|
|
377
|
+
const currentRange = dependencyMap[selectedPackage.name];
|
|
378
|
+
const nextRange = typeof currentRange === "string" ? formatRange(currentRange, version, exact) : createRange(version, exact);
|
|
379
|
+
if (currentRange === nextRange) {
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
dependencyMap[selectedPackage.name] = nextRange;
|
|
383
|
+
updates.push({
|
|
384
|
+
type: typeof currentRange === "string" ? "update" : "install",
|
|
385
|
+
section,
|
|
386
|
+
name: selectedPackage.name,
|
|
387
|
+
currentRange: typeof currentRange === "string" ? currentRange : null,
|
|
388
|
+
nextRange
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
return updates;
|
|
392
|
+
};
|
|
393
|
+
const promptForInstallSelection = async (packageJson) => {
|
|
394
|
+
if (!process$1.stdin.isTTY || !process$1.stdout.isTTY) {
|
|
395
|
+
throw new Error("Interactive add mode requires a TTY. Use --packages to select packages explicitly.");
|
|
396
|
+
}
|
|
397
|
+
console.log("Выберите пакеты для установки в текущий package.json:");
|
|
398
|
+
for (const [index, selectedPackage] of INSTALLABLE_PACKAGES.entries()) {
|
|
399
|
+
const currentSection = findDependencySection(packageJson, selectedPackage.name);
|
|
400
|
+
const installedHint = currentSection ? ` Уже есть в ${currentSection}.` : "";
|
|
401
|
+
console.log(` ${index + 1}. ${selectedPackage.name} (${selectedPackage.id})`);
|
|
402
|
+
console.log(` ${selectedPackage.description} Раздел по умолчанию: ${selectedPackage.section}.${installedHint}`);
|
|
403
|
+
}
|
|
404
|
+
const readline = createInterface({
|
|
405
|
+
input: process$1.stdin,
|
|
406
|
+
output: process$1.stdout
|
|
407
|
+
});
|
|
408
|
+
try {
|
|
409
|
+
while (true) {
|
|
410
|
+
const answer = await readline.question(
|
|
411
|
+
"Введите номера, ids или имена пакетов через запятую (например: 1,3 или components,types): "
|
|
412
|
+
);
|
|
413
|
+
const tokens = parsePackageList(answer);
|
|
414
|
+
if (tokens.length === 0) {
|
|
415
|
+
return [];
|
|
416
|
+
}
|
|
417
|
+
try {
|
|
418
|
+
return resolveInstallPackages(tokens);
|
|
419
|
+
} catch (error) {
|
|
420
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
421
|
+
console.error(message);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
} finally {
|
|
425
|
+
readline.close();
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
const DEFAULT_INDENT = " ";
|
|
429
|
+
const DEFAULT_NEWLINE = "\n";
|
|
430
|
+
const INIT_RUNTIME_DEPENDENCIES = [
|
|
431
|
+
{ name: "@omnicajs/vue-remote", range: "^0.2.23" },
|
|
432
|
+
{ name: "pinia", range: "^2.2" },
|
|
433
|
+
{ name: "vue", range: "^3.5" },
|
|
434
|
+
{ name: "vue-i18n", range: "^11" }
|
|
435
|
+
];
|
|
436
|
+
const INIT_DEV_DEPENDENCIES = [
|
|
437
|
+
{ name: "@eslint/js", range: "^9.39" },
|
|
438
|
+
{ name: "@intlify/eslint-plugin-vue-i18n", range: "~4.3.0" },
|
|
439
|
+
{ name: "@intlify/unplugin-vue-i18n", range: "^11.1" },
|
|
440
|
+
{ name: "@omnicajs/eslint-plugin-dependencies", range: "^0.0.2" },
|
|
441
|
+
{ name: "@types/node", range: "^22.19" },
|
|
442
|
+
{ name: "@vitejs/plugin-vue", range: "^6.0" },
|
|
443
|
+
{ name: "@vue/language-server", range: "^3.2" },
|
|
444
|
+
{ name: "eslint", range: "^9.39" },
|
|
445
|
+
{ name: "eslint-plugin-vue", range: "^10.9" },
|
|
446
|
+
{ name: "globals", range: "^16.5" },
|
|
447
|
+
{ name: "less", range: "^4.6" },
|
|
448
|
+
{ name: "typescript", range: "^5.9" },
|
|
449
|
+
{ name: "typescript-eslint", range: "^8.59" },
|
|
450
|
+
{ name: "vite", range: "^7.3" },
|
|
451
|
+
{ name: "vite-svg-loader", range: "^5.1" },
|
|
452
|
+
{ name: "vue-eslint-parser", range: "^10.4" }
|
|
453
|
+
];
|
|
454
|
+
const I18N_RUNTIME_DEPENDENCY = "vue-i18n";
|
|
455
|
+
const hasExistingDependency = (packageJson, name) => findDependencySection(packageJson, name) !== null;
|
|
456
|
+
const detectFormatting = (source) => {
|
|
457
|
+
const newline = source.includes("\r\n") ? "\r\n" : DEFAULT_NEWLINE;
|
|
458
|
+
const indentMatch = source.match(/\n([ \t]+)"/);
|
|
459
|
+
return {
|
|
460
|
+
indent: indentMatch?.[1] ?? DEFAULT_INDENT,
|
|
461
|
+
newline,
|
|
462
|
+
trailingNewline: source.endsWith("\n") || source.endsWith("\r\n")
|
|
463
|
+
};
|
|
464
|
+
};
|
|
465
|
+
const serializePackageJson = (packageJson, formatting) => {
|
|
466
|
+
const serialized = JSON.stringify(packageJson, null, formatting.indent).replace(/\n/g, formatting.newline);
|
|
467
|
+
return formatting.trailingNewline ? `${serialized}${formatting.newline}` : serialized;
|
|
468
|
+
};
|
|
469
|
+
const readPackageJson = (packageJsonPath) => {
|
|
470
|
+
const source = fs.readFileSync(packageJsonPath, "utf8");
|
|
471
|
+
return {
|
|
472
|
+
formatting: detectFormatting(source),
|
|
473
|
+
packageJson: JSON.parse(source)
|
|
474
|
+
};
|
|
475
|
+
};
|
|
476
|
+
const writePackageJson = (packageJsonPath, packageJson, formatting) => {
|
|
477
|
+
fs.writeFileSync(packageJsonPath, serializePackageJson(packageJson, formatting), "utf8");
|
|
478
|
+
};
|
|
479
|
+
const readOrCreatePackageJson = (packageJsonPath) => {
|
|
480
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
481
|
+
return {
|
|
482
|
+
created: false,
|
|
483
|
+
...readPackageJson(packageJsonPath)
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
return {
|
|
487
|
+
created: true,
|
|
488
|
+
formatting: {
|
|
489
|
+
indent: DEFAULT_INDENT,
|
|
490
|
+
newline: DEFAULT_NEWLINE,
|
|
491
|
+
trailingNewline: true
|
|
492
|
+
},
|
|
493
|
+
packageJson: {
|
|
494
|
+
name: "retailcrm-extension-frontend",
|
|
495
|
+
private: true,
|
|
496
|
+
type: "module",
|
|
497
|
+
scripts: {
|
|
498
|
+
build: "vite build",
|
|
499
|
+
eslint: "eslint .",
|
|
500
|
+
"eslint:fix": "eslint --fix ."
|
|
501
|
+
},
|
|
502
|
+
dependencies: {},
|
|
503
|
+
devDependencies: {}
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
};
|
|
507
|
+
const ensureObjectField = (object, field) => {
|
|
508
|
+
if (!object[field] || typeof object[field] !== "object" || Array.isArray(object[field])) {
|
|
509
|
+
object[field] = {};
|
|
510
|
+
}
|
|
511
|
+
return object[field];
|
|
512
|
+
};
|
|
513
|
+
const resolveRangeMajor$1 = (range) => {
|
|
514
|
+
const match = range.match(/\d+/u);
|
|
515
|
+
return match ? Number(match[0]) : null;
|
|
516
|
+
};
|
|
517
|
+
const isCompatibleRange = (currentRange, expectedRange) => {
|
|
518
|
+
const currentMajor = resolveRangeMajor$1(currentRange);
|
|
519
|
+
const expectedMajor = resolveRangeMajor$1(expectedRange);
|
|
520
|
+
return currentMajor !== null && expectedMajor !== null && currentMajor === expectedMajor;
|
|
521
|
+
};
|
|
522
|
+
const setMissingScript = (packageJson, name, command, changes) => {
|
|
523
|
+
const scripts = ensureObjectField(packageJson, "scripts");
|
|
524
|
+
if (scripts[name] === command) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
if (typeof scripts[name] === "string") {
|
|
528
|
+
changes.warnings.push(`script "${name}" already exists and will not be overwritten`);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
scripts[name] = command;
|
|
532
|
+
changes.packageJson.push({
|
|
533
|
+
type: "script",
|
|
534
|
+
name,
|
|
535
|
+
nextRange: command
|
|
536
|
+
});
|
|
537
|
+
};
|
|
538
|
+
const setDependency = (packageJson, section, name, range, changes, options = {}) => {
|
|
539
|
+
let currentSection = findDependencySection(packageJson, name);
|
|
540
|
+
if (currentSection && currentSection !== section) {
|
|
541
|
+
if (options.fixSections) {
|
|
542
|
+
const previousSection = currentSection;
|
|
543
|
+
const currentDependencyMap = packageJson[currentSection];
|
|
544
|
+
const nextDependencyMap = ensureObjectField(packageJson, section);
|
|
545
|
+
nextDependencyMap[name] = currentDependencyMap[name];
|
|
546
|
+
delete currentDependencyMap[name];
|
|
547
|
+
currentSection = section;
|
|
548
|
+
changes.packageJson.push({
|
|
549
|
+
type: "update",
|
|
550
|
+
section,
|
|
551
|
+
name,
|
|
552
|
+
currentRange: `in ${previousSection}`,
|
|
553
|
+
nextRange: `move to ${section}`
|
|
554
|
+
});
|
|
555
|
+
} else {
|
|
556
|
+
changes.warnings.push(`${name} already exists in ${currentSection}; expected ${section}. Use --fix-sections to move it.`);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
const dependencyMap = ensureObjectField(packageJson, section);
|
|
561
|
+
const currentRange = dependencyMap[name];
|
|
562
|
+
if (currentRange === range) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (typeof currentRange === "string" && !isTargetPackage(name)) {
|
|
566
|
+
if (isCompatibleRange(currentRange, range) && !options.forceDeps) {
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
if (!options.forceDeps) {
|
|
570
|
+
changes.warnings.push(`${name} has range ${currentRange}; expected compatible ${range}. Use --force-deps to replace it.`);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
dependencyMap[name] = typeof currentRange === "string" ? formatRange(currentRange, range.replace(/^[~^]/u, ""), range === range.replace(/^[~^]/u, "")) : range;
|
|
575
|
+
const nextRange = String(dependencyMap[name]);
|
|
576
|
+
changes.packageJson.push({
|
|
577
|
+
type: typeof currentRange === "string" ? "update" : "install",
|
|
578
|
+
section,
|
|
579
|
+
name,
|
|
580
|
+
currentRange: typeof currentRange === "string" ? currentRange : null,
|
|
581
|
+
nextRange
|
|
582
|
+
});
|
|
583
|
+
};
|
|
584
|
+
const resolveLocalBinPath = (cwd, binName) => {
|
|
585
|
+
const binPath = path.join(cwd, "node_modules", ".bin", process.platform === "win32" ? `${binName}.cmd` : binName);
|
|
586
|
+
return fs.existsSync(binPath) ? binPath : null;
|
|
587
|
+
};
|
|
588
|
+
const hasLocalPackage = (cwd, packageName) => fs.existsSync(path.join(cwd, "node_modules", packageName, "package.json"));
|
|
589
|
+
const resolveDownloadCommand = (packageName, binName, packageManager, args) => {
|
|
590
|
+
if (packageManager === "yarn") {
|
|
591
|
+
const commandArgs2 = ["dlx", "-p", packageName, binName, ...args];
|
|
592
|
+
return {
|
|
593
|
+
command: "yarn",
|
|
594
|
+
args: commandArgs2,
|
|
595
|
+
display: `yarn ${commandArgs2.join(" ")}`,
|
|
596
|
+
source: "transient"
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
if (packageManager === "pnpm") {
|
|
600
|
+
const commandArgs2 = ["dlx", "--package", packageName, binName, ...args];
|
|
601
|
+
return {
|
|
602
|
+
command: "pnpm",
|
|
603
|
+
args: commandArgs2,
|
|
604
|
+
display: `pnpm ${commandArgs2.join(" ")}`,
|
|
605
|
+
source: "transient"
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
if (packageManager === "bun") {
|
|
609
|
+
const commandArgs2 = ["x", "--package", packageName, binName, ...args];
|
|
610
|
+
return {
|
|
611
|
+
command: "bun",
|
|
612
|
+
args: commandArgs2,
|
|
613
|
+
display: `bun ${commandArgs2.join(" ")}`,
|
|
614
|
+
source: "transient"
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
const commandArgs = ["exec", "--yes", "--package", packageName, "--", binName, ...args];
|
|
618
|
+
return {
|
|
619
|
+
command: "npm",
|
|
620
|
+
args: commandArgs,
|
|
621
|
+
display: `npm ${commandArgs.join(" ")}`,
|
|
622
|
+
source: "transient"
|
|
623
|
+
};
|
|
624
|
+
};
|
|
625
|
+
const resolvePackageHookCommand = (cwd, packageName, binName, packageManager, args) => {
|
|
626
|
+
const localBinPath = resolveLocalBinPath(cwd, binName);
|
|
627
|
+
if (localBinPath) {
|
|
628
|
+
return {
|
|
629
|
+
command: localBinPath,
|
|
630
|
+
args,
|
|
631
|
+
display: `${localBinPath} ${args.join(" ")}`,
|
|
632
|
+
source: "local"
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
if (hasLocalPackage(cwd, packageName)) {
|
|
636
|
+
throw new Error(
|
|
637
|
+
`${packageName} is installed, but ${binName} was not found in node_modules/.bin. Reinstall dependencies or check the package bin metadata.`
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
return resolveDownloadCommand(packageName, binName, packageManager, args);
|
|
641
|
+
};
|
|
642
|
+
const getExecErrorMessage = (error) => {
|
|
643
|
+
if (error instanceof Error && error.message) {
|
|
644
|
+
return error.message;
|
|
645
|
+
}
|
|
646
|
+
return String(error);
|
|
647
|
+
};
|
|
648
|
+
const runPackageHookCommand = (cwd, packageName, binName, packageManager, args, failureMode, options, changes) => {
|
|
649
|
+
const command = resolvePackageHookCommand(cwd, packageName, binName, packageManager, args);
|
|
650
|
+
changes.hooks.push(command.display);
|
|
651
|
+
if (options.dryRun) {
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
try {
|
|
655
|
+
execFileSync(command.command, command.args, {
|
|
656
|
+
cwd,
|
|
657
|
+
stdio: "inherit"
|
|
658
|
+
});
|
|
659
|
+
} catch (error) {
|
|
660
|
+
if (command.source === "transient" && failureMode === "advisory") {
|
|
661
|
+
changes.warnings.push(`Package hook ${command.display} was skipped: ${getExecErrorMessage(error)}`);
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
throw error;
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
const ROOT_AGENTS_SECTION_HEADER = "## @retailcrm/embed-ui";
|
|
668
|
+
const createRootAgentsSection = () => `${ROOT_AGENTS_SECTION_HEADER}
|
|
669
|
+
|
|
670
|
+
When working with RetailCRM embedded UI in this project:
|
|
671
|
+
|
|
672
|
+
1. Use documented public package entrypoints instead of package internals.
|
|
673
|
+
2. Read package README files from \`./node_modules/@retailcrm/embed-ui*\` before changing integration code.
|
|
674
|
+
3. Prefer package-provided \`AGENTS.md\`, MCP resources, YAML profiles, and docs over guessing from source names.
|
|
675
|
+
4. Keep widget targets, page codes, contexts, and host API contracts aligned with package documentation.
|
|
676
|
+
`;
|
|
677
|
+
const appendOrReplaceSection = (content, header, section, force) => {
|
|
678
|
+
const escapedHeader = header.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
679
|
+
const sectionPattern = new RegExp(`${escapedHeader}[\\s\\S]*?(?=\\n##\\s|$)`, "u");
|
|
680
|
+
if (sectionPattern.test(content)) {
|
|
681
|
+
if (!force) {
|
|
682
|
+
return null;
|
|
683
|
+
}
|
|
684
|
+
return content.replace(sectionPattern, section.trimEnd()).replace(/\s+$/u, "") + DEFAULT_NEWLINE;
|
|
685
|
+
}
|
|
686
|
+
const trimmed = content.replace(/\s+$/u, "");
|
|
687
|
+
return `${trimmed}${trimmed ? `${DEFAULT_NEWLINE}${DEFAULT_NEWLINE}` : ""}${section}${DEFAULT_NEWLINE}`;
|
|
688
|
+
};
|
|
689
|
+
const updateRootAgents = (cwd, options, changes) => {
|
|
690
|
+
const agentsPath = path.join(cwd, "AGENTS.md");
|
|
691
|
+
const section = createRootAgentsSection();
|
|
692
|
+
const content = fs.existsSync(agentsPath) ? fs.readFileSync(agentsPath, "utf8") : "# AGENTS.md\n";
|
|
693
|
+
const nextContent = appendOrReplaceSection(content, ROOT_AGENTS_SECTION_HEADER, section, options.force || options.forceAgents);
|
|
694
|
+
if (nextContent === null) {
|
|
695
|
+
changes.skipped.push(`${agentsPath} already contains ${ROOT_AGENTS_SECTION_HEADER}`);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
if (!options.dryRun) {
|
|
699
|
+
fs.writeFileSync(agentsPath, nextContent, "utf8");
|
|
700
|
+
}
|
|
701
|
+
changes.agents.push(`update ${agentsPath}`);
|
|
702
|
+
};
|
|
703
|
+
const runInitAgentsHook = (packageName, binName, cwd, packageManager, failureMode, options, changes) => {
|
|
704
|
+
const args = ["init-agents", cwd];
|
|
705
|
+
if (options.force || options.forceAgents) {
|
|
706
|
+
args.push("--force");
|
|
707
|
+
}
|
|
708
|
+
runPackageHookCommand(
|
|
709
|
+
cwd,
|
|
710
|
+
packageName,
|
|
711
|
+
binName,
|
|
712
|
+
packageManager,
|
|
713
|
+
args,
|
|
714
|
+
failureMode,
|
|
715
|
+
options,
|
|
716
|
+
changes
|
|
717
|
+
);
|
|
718
|
+
};
|
|
719
|
+
const applyInitAgents = (cwd, selectedPackages, packageManager, options, changes) => {
|
|
720
|
+
if (options.noAgents) {
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
updateRootAgents(cwd, options, changes);
|
|
724
|
+
for (const selectedPackage of selectedPackages) {
|
|
725
|
+
for (const hook of selectedPackage.hooks ?? []) {
|
|
726
|
+
if (hook.type !== "agents") {
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
if (hook.requiresMcp && options.noMcp) {
|
|
730
|
+
changes.warnings.push(`Skipping ${selectedPackage.id} ${hook.command} because it currently includes MCP instructions`);
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
733
|
+
runInitAgentsHook(
|
|
734
|
+
selectedPackage.name,
|
|
735
|
+
hook.binName,
|
|
736
|
+
cwd,
|
|
737
|
+
packageManager,
|
|
738
|
+
hook.failureMode,
|
|
739
|
+
options,
|
|
740
|
+
changes
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
const applyInitPackageConfigHooks = (cwd, selectedPackages, packageManager, options, changes) => {
|
|
746
|
+
for (const selectedPackage of selectedPackages) {
|
|
747
|
+
for (const hook of selectedPackage.hooks ?? []) {
|
|
748
|
+
if (hook.type !== "config") {
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
if (hook.requiresMcp && options.noMcp) {
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
const args = [hook.command, cwd];
|
|
755
|
+
if (hook.requiresMcp && (options.force || options.forceMcp)) {
|
|
756
|
+
args.push("--force");
|
|
757
|
+
}
|
|
758
|
+
if (hook.requiresMcp && options.mcpClientConfigs?.length) {
|
|
759
|
+
args.push("--mcp-client-configs", options.mcpClientConfigs.join(","));
|
|
760
|
+
}
|
|
761
|
+
runPackageHookCommand(
|
|
762
|
+
cwd,
|
|
763
|
+
selectedPackage.name,
|
|
764
|
+
hook.binName,
|
|
765
|
+
packageManager,
|
|
766
|
+
args,
|
|
767
|
+
hook.failureMode,
|
|
768
|
+
options,
|
|
769
|
+
changes
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
const LOCKFILES = [
|
|
775
|
+
{ file: "yarn.lock", packageManager: "yarn" },
|
|
776
|
+
{ file: "package-lock.json", packageManager: "npm" },
|
|
777
|
+
{ file: "pnpm-lock.yaml", packageManager: "pnpm" },
|
|
778
|
+
{ file: "bun.lockb", packageManager: "bun" }
|
|
779
|
+
];
|
|
780
|
+
const CONFIG_FILES = [
|
|
781
|
+
"tsconfig.json",
|
|
782
|
+
"vite.config.ts",
|
|
783
|
+
"vite.config.js",
|
|
784
|
+
"vite.config.mts",
|
|
785
|
+
"eslint.config.js",
|
|
786
|
+
"eslint.config.mjs",
|
|
787
|
+
"env.d.ts"
|
|
788
|
+
];
|
|
789
|
+
const SCRIPT_NAMES = ["build", "dev", "eslint", "eslint:fix", "lint", "test"];
|
|
790
|
+
const VITE_CONFIG_FILES = [
|
|
791
|
+
"vite.config.ts",
|
|
792
|
+
"vite.config.js",
|
|
793
|
+
"vite.config.mts"
|
|
794
|
+
];
|
|
795
|
+
const ESLINT_CONFIG_FILES = [
|
|
796
|
+
"eslint.config.js",
|
|
797
|
+
"eslint.config.mjs"
|
|
798
|
+
];
|
|
799
|
+
const TEMPLATE_FILE_IMPACTS = [
|
|
800
|
+
{
|
|
801
|
+
relativePath: "endpoint/endpoint.worker.ts",
|
|
802
|
+
impact: "endpoint runner entry will not be generated"
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
relativePath: "pages/SettingsPage.vue",
|
|
806
|
+
impact: "starter settings page will not be generated"
|
|
807
|
+
},
|
|
808
|
+
{
|
|
809
|
+
relativePath: "widgets/OrderCommonAfterWidget.vue",
|
|
810
|
+
impact: "starter order widget will not be generated"
|
|
811
|
+
},
|
|
812
|
+
{
|
|
813
|
+
relativePath: "i18n/index.ts",
|
|
814
|
+
impact: "starter i18n bootstrap will not be generated"
|
|
815
|
+
},
|
|
816
|
+
{
|
|
817
|
+
relativePath: "i18n/locales/en-GB.json",
|
|
818
|
+
impact: "starter English locale messages will not be generated"
|
|
819
|
+
}
|
|
820
|
+
];
|
|
821
|
+
const hasEnabledMcpConfigHook = (selectedPackages, options) => !options.noMcp && selectedPackages.some(
|
|
822
|
+
(selectedPackage) => selectedPackage.hooks?.some((hook) => hook.type === "config" && hook.requiresMcp) ?? false
|
|
823
|
+
);
|
|
824
|
+
const readTextIfExists = (filePath) => {
|
|
825
|
+
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
|
|
826
|
+
return null;
|
|
827
|
+
}
|
|
828
|
+
return fs.readFileSync(filePath, "utf8");
|
|
829
|
+
};
|
|
830
|
+
const hasTextMarker = (content, markers) => markers.some((marker) => content.includes(marker));
|
|
831
|
+
const normalizeRelativePath = (relativePath) => relativePath.split(path.sep).join("/");
|
|
832
|
+
const getSourceRootRelative = (cwd, sourceRoot) => normalizeRelativePath(path.relative(cwd, sourceRoot) || ".");
|
|
833
|
+
const resolveRangeMajor = (range) => {
|
|
834
|
+
const match = range.match(/\d+/u);
|
|
835
|
+
return match ? Number(match[0]) : null;
|
|
836
|
+
};
|
|
837
|
+
const describePathState = (cwd, relativePath) => {
|
|
838
|
+
const targetPath = path.join(cwd, relativePath);
|
|
839
|
+
if (!fs.existsSync(targetPath)) {
|
|
840
|
+
return `${relativePath}: missing`;
|
|
841
|
+
}
|
|
842
|
+
return `${relativePath}: found`;
|
|
843
|
+
};
|
|
844
|
+
const readExistingPackageJson = (packageJsonPath, changes) => {
|
|
845
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
846
|
+
changes.preflight.push("package.json: missing; it will be created");
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
const { packageJson } = readPackageJson(packageJsonPath);
|
|
850
|
+
changes.preflight.push("package.json: found");
|
|
851
|
+
return packageJson;
|
|
852
|
+
};
|
|
853
|
+
const analyzeDependency = (packageJson, name, expectedRange, expectedSection, options, changes) => {
|
|
854
|
+
const section = findDependencySection(packageJson, name);
|
|
855
|
+
if (!section) {
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
const dependencyMap = packageJson[section];
|
|
859
|
+
const currentRange = typeof dependencyMap === "object" && dependencyMap ? dependencyMap[name] : null;
|
|
860
|
+
if (section !== expectedSection) {
|
|
861
|
+
if (options.fixSections) {
|
|
862
|
+
changes.preflight.push(`${name}: will move from ${section} to ${expectedSection}`);
|
|
863
|
+
} else {
|
|
864
|
+
changes.warnings.push(`${name} already exists in ${section}; expected ${expectedSection}. Use --fix-sections to move it.`);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
if (typeof currentRange !== "string") {
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
const currentMajor = resolveRangeMajor(currentRange);
|
|
871
|
+
const expectedMajor = resolveRangeMajor(expectedRange);
|
|
872
|
+
if (currentMajor !== null && expectedMajor !== null && currentMajor !== expectedMajor) {
|
|
873
|
+
if (options.forceDeps) {
|
|
874
|
+
changes.preflight.push(`${name}: will replace ${currentRange} with ${expectedRange}`);
|
|
875
|
+
} else {
|
|
876
|
+
changes.warnings.push(`${name} has range ${currentRange}; expected compatible ${expectedRange}. Use --force-deps to replace it.`);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
const analyzePackageJson = (packageJson, selectedPackages, version, options, changes) => {
|
|
881
|
+
if (packageJson.type === "module") {
|
|
882
|
+
changes.preflight.push("package.json type: module");
|
|
883
|
+
} else if (packageJson.type) {
|
|
884
|
+
changes.warnings.push(`package.json already has type "${String(packageJson.type)}"; expected "module"`);
|
|
885
|
+
} else {
|
|
886
|
+
changes.preflight.push('package.json type: missing; "module" will be added');
|
|
887
|
+
}
|
|
888
|
+
const scripts = packageJson.scripts;
|
|
889
|
+
if (scripts && typeof scripts === "object" && !Array.isArray(scripts)) {
|
|
890
|
+
for (const scriptName of SCRIPT_NAMES) {
|
|
891
|
+
if (scriptName in scripts) {
|
|
892
|
+
changes.preflight.push(`script ${scriptName}: found`);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
const selectedRange = options.exact ? version : `^${version}`;
|
|
897
|
+
for (const selectedPackage of selectedPackages) {
|
|
898
|
+
analyzeDependency(packageJson, selectedPackage.name, selectedRange, selectedPackage.section, options, changes);
|
|
899
|
+
}
|
|
900
|
+
for (const dependency of INIT_RUNTIME_DEPENDENCIES) {
|
|
901
|
+
if (dependency.name === I18N_RUNTIME_DEPENDENCY && hasExistingDependency(packageJson, dependency.name)) {
|
|
902
|
+
changes.preflight.push(`${dependency.name}: found; i18n dependency setup will be skipped to avoid conflicts with existing project configuration`);
|
|
903
|
+
continue;
|
|
904
|
+
}
|
|
905
|
+
analyzeDependency(packageJson, dependency.name, dependency.range, "dependencies", options, changes);
|
|
906
|
+
}
|
|
907
|
+
for (const dependency of INIT_DEV_DEPENDENCIES) {
|
|
908
|
+
analyzeDependency(packageJson, dependency.name, dependency.range, "devDependencies", options, changes);
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
const analyzeTsConfig = (cwd, sourceRoot, changes) => {
|
|
912
|
+
const tsconfigPath = path.join(cwd, "tsconfig.json");
|
|
913
|
+
const content = readTextIfExists(tsconfigPath);
|
|
914
|
+
if (!content) {
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
let parsed;
|
|
918
|
+
try {
|
|
919
|
+
parsed = JSON.parse(content);
|
|
920
|
+
} catch {
|
|
921
|
+
changes.warnings.push("tsconfig.json: cannot parse JSON; verify @ alias, JSON imports, and vue-remote tooling manually");
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
const compilerOptions = parsed.compilerOptions && typeof parsed.compilerOptions === "object" ? parsed.compilerOptions : {};
|
|
925
|
+
const vueCompilerOptions = parsed.vueCompilerOptions && typeof parsed.vueCompilerOptions === "object" ? parsed.vueCompilerOptions : {};
|
|
926
|
+
const sourceRootRelative = getSourceRootRelative(cwd, sourceRoot);
|
|
927
|
+
if (compilerOptions.moduleResolution !== "Bundler") {
|
|
928
|
+
changes.warnings.push('tsconfig.json: moduleResolution is not "Bundler"; Vite-style imports may resolve differently');
|
|
929
|
+
}
|
|
930
|
+
if (compilerOptions.resolveJsonModule !== true) {
|
|
931
|
+
changes.warnings.push("tsconfig.json: resolveJsonModule is not enabled; JSON locale imports may not type-check");
|
|
932
|
+
}
|
|
933
|
+
const paths = compilerOptions.paths && typeof compilerOptions.paths === "object" ? compilerOptions.paths : null;
|
|
934
|
+
const aliasTarget = paths?.["@/*"];
|
|
935
|
+
if (!Array.isArray(aliasTarget)) {
|
|
936
|
+
changes.warnings.push("tsconfig.json: @/* path alias is missing; generated imports use @/");
|
|
937
|
+
} else if (!aliasTarget.includes(`${sourceRootRelative}/*`)) {
|
|
938
|
+
changes.warnings.push(`tsconfig.json: @/* path alias does not point to ${sourceRootRelative}/*`);
|
|
939
|
+
}
|
|
940
|
+
const plugins = Array.isArray(vueCompilerOptions.plugins) ? vueCompilerOptions.plugins : [];
|
|
941
|
+
if (!plugins.includes("@omnicajs/vue-remote/tooling")) {
|
|
942
|
+
changes.warnings.push("tsconfig.json: @omnicajs/vue-remote/tooling plugin is missing; remote script modifiers may not be typed");
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
const analyzeViteConfig = (cwd, sourceRoot, changes) => {
|
|
946
|
+
const viteConfig = VITE_CONFIG_FILES.map((file) => ({ file, content: readTextIfExists(path.join(cwd, file)) })).find(({ content }) => content !== null);
|
|
947
|
+
if (!viteConfig?.content) {
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
const sourceRootRelative = getSourceRootRelative(cwd, sourceRoot);
|
|
951
|
+
if (!hasTextMarker(viteConfig.content, ["@omnicajs/vue-remote/vite-plugin", "vueRemoteVitePlugin"])) {
|
|
952
|
+
changes.warnings.push(`${viteConfig.file}: @omnicajs/vue-remote/vite-plugin is missing; remote event modifiers will not be transformed`);
|
|
953
|
+
}
|
|
954
|
+
if (!hasTextMarker(viteConfig.content, ["@intlify/unplugin-vue-i18n/vite", "vueI18n"])) {
|
|
955
|
+
changes.warnings.push(`${viteConfig.file}: vue-i18n Vite plugin is missing; JSON and SFC locale blocks may not be bundled`);
|
|
956
|
+
}
|
|
957
|
+
if (!hasTextMarker(viteConfig.content, ["vite-svg-loader", "defaultImport: 'component'", 'defaultImport: "component"'])) {
|
|
958
|
+
changes.warnings.push(`${viteConfig.file}: SVG component loader is missing; generated SVG component imports may fail`);
|
|
959
|
+
}
|
|
960
|
+
if (!hasTextMarker(viteConfig.content, ["'@'", '"@"'])) {
|
|
961
|
+
changes.warnings.push(`${viteConfig.file}: @ alias is missing; generated imports use @/`);
|
|
962
|
+
} else if (!viteConfig.content.includes(sourceRootRelative) && !viteConfig.content.includes(path.basename(sourceRoot))) {
|
|
963
|
+
changes.warnings.push(`${viteConfig.file}: @ alias may not point to ${sourceRootRelative}`);
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
const analyzeEslintConfig = (cwd, changes) => {
|
|
967
|
+
const eslintConfig = ESLINT_CONFIG_FILES.map((file) => ({ file, content: readTextIfExists(path.join(cwd, file)) })).find(({ content }) => content !== null);
|
|
968
|
+
if (!eslintConfig?.content) {
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
if (!eslintConfig.content.includes("@intlify/eslint-plugin-vue-i18n")) {
|
|
972
|
+
changes.warnings.push(`${eslintConfig.file}: @intlify/eslint-plugin-vue-i18n is missing; dynamic or missing translation keys will not be guarded`);
|
|
973
|
+
}
|
|
974
|
+
if (!eslintConfig.content.includes("@omnicajs/eslint-plugin-dependencies")) {
|
|
975
|
+
changes.warnings.push(`${eslintConfig.file}: @omnicajs/eslint-plugin-dependencies is missing; generated import ordering may not be enforced`);
|
|
976
|
+
}
|
|
977
|
+
if (!eslintConfig.content.includes("vue/block-order")) {
|
|
978
|
+
changes.warnings.push(`${eslintConfig.file}: vue/block-order is missing; generated Vue block order may drift from the template`);
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
const analyzeTemplateFileSkips = (cwd, sourceRoot, options, changes) => {
|
|
982
|
+
if (options.noTemplate || options.force || options.forceFiles) {
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
for (const { relativePath, impact } of TEMPLATE_FILE_IMPACTS) {
|
|
986
|
+
if (fs.existsSync(path.join(sourceRoot, relativePath))) {
|
|
987
|
+
changes.warnings.push(`${path.join(getSourceRootRelative(cwd, sourceRoot), relativePath)} already exists; ${impact}`);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
for (const relativePath of ["extensionrc.json", "scripts/publish-extension.mjs", "README.md"]) {
|
|
991
|
+
if (fs.existsSync(path.join(cwd, relativePath))) {
|
|
992
|
+
changes.warnings.push(`${relativePath} already exists; generated project-level starter file will be skipped`);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
};
|
|
996
|
+
const applyInitPreflight = (cwd, sourceRoot, packageManager, selectedPackages, version, options, changes) => {
|
|
997
|
+
if (options.agentsOnly) {
|
|
998
|
+
changes.preflight.push("agents-only mode: package.json, configs, and template files are skipped");
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
changes.preflight.push(`source root: ${path.relative(cwd, sourceRoot) || "."}`);
|
|
1002
|
+
const existingLockfiles = LOCKFILES.filter(({ file }) => fs.existsSync(path.join(cwd, file)));
|
|
1003
|
+
if (existingLockfiles.length === 0) {
|
|
1004
|
+
changes.preflight.push(`lockfile: none; using ${packageManager}`);
|
|
1005
|
+
} else {
|
|
1006
|
+
changes.preflight.push(`lockfile: ${existingLockfiles.map(({ file }) => file).join(", ")}; using ${packageManager}`);
|
|
1007
|
+
}
|
|
1008
|
+
if (existingLockfiles.length > 1) {
|
|
1009
|
+
changes.warnings.push(`Multiple lockfiles found: ${existingLockfiles.map(({ file }) => file).join(", ")}`);
|
|
1010
|
+
}
|
|
1011
|
+
if (!options.target && !options.srcDir && fs.existsSync(path.join(cwd, "src")) && path.basename(sourceRoot) === "web") {
|
|
1012
|
+
changes.warnings.push("src/ already exists; generated frontend source root resolved to web/");
|
|
1013
|
+
}
|
|
1014
|
+
changes.preflight.push(describePathState(cwd, "src"));
|
|
1015
|
+
changes.preflight.push(describePathState(cwd, "web"));
|
|
1016
|
+
if (options.noConfigs) {
|
|
1017
|
+
changes.preflight.push("configs: disabled");
|
|
1018
|
+
} else {
|
|
1019
|
+
for (const configFile of CONFIG_FILES) {
|
|
1020
|
+
if (fs.existsSync(path.join(cwd, configFile))) {
|
|
1021
|
+
changes.preflight.push(`${configFile}: found; generated config will be skipped unless --force-files is used`);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
analyzeTsConfig(cwd, sourceRoot, changes);
|
|
1025
|
+
analyzeViteConfig(cwd, sourceRoot, changes);
|
|
1026
|
+
analyzeEslintConfig(cwd, changes);
|
|
1027
|
+
}
|
|
1028
|
+
analyzeTemplateFileSkips(cwd, sourceRoot, options, changes);
|
|
1029
|
+
if (hasEnabledMcpConfigHook(selectedPackages, options)) {
|
|
1030
|
+
changes.preflight.push("v1-endpoint init-config: enabled");
|
|
1031
|
+
if (options.mcpClientConfigs?.length) {
|
|
1032
|
+
changes.preflight.push(`MCP client configs requested: ${options.mcpClientConfigs.join(", ")}`);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
const packageJson = readExistingPackageJson(path.join(cwd, "package.json"), changes);
|
|
1036
|
+
if (packageJson) {
|
|
1037
|
+
analyzePackageJson(packageJson, selectedPackages, version, options, changes);
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
const SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
1041
|
+
".git",
|
|
1042
|
+
".hg",
|
|
1043
|
+
".svn",
|
|
1044
|
+
".yarn",
|
|
1045
|
+
"node_modules",
|
|
1046
|
+
"dist",
|
|
1047
|
+
"build",
|
|
1048
|
+
"coverage"
|
|
1049
|
+
]);
|
|
1050
|
+
const ensureDirectoryExists = (targetPath) => {
|
|
1051
|
+
if (!fs.existsSync(targetPath)) {
|
|
1052
|
+
throw new Error(`Path not found: ${targetPath}`);
|
|
1053
|
+
}
|
|
1054
|
+
const stat = fs.statSync(targetPath);
|
|
1055
|
+
if (!stat.isDirectory()) {
|
|
1056
|
+
throw new Error(`Target is not a directory: ${targetPath}`);
|
|
1057
|
+
}
|
|
1058
|
+
};
|
|
1059
|
+
const resolvePackageJsonPath = (targetPath) => {
|
|
1060
|
+
if (path.basename(targetPath) === "package.json") {
|
|
1061
|
+
if (!fs.existsSync(targetPath)) {
|
|
1062
|
+
throw new Error(`package.json not found: ${targetPath}`);
|
|
1063
|
+
}
|
|
1064
|
+
return targetPath;
|
|
1065
|
+
}
|
|
1066
|
+
const packageJsonPath = path.resolve(targetPath, "package.json");
|
|
1067
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
1068
|
+
throw new Error(`package.json not found: ${packageJsonPath}`);
|
|
1069
|
+
}
|
|
1070
|
+
return packageJsonPath;
|
|
1071
|
+
};
|
|
1072
|
+
const collectPackageJsonPaths = (targetPath) => {
|
|
1073
|
+
const resolvedTarget = path.resolve(targetPath);
|
|
1074
|
+
if (!fs.existsSync(resolvedTarget)) {
|
|
1075
|
+
throw new Error(`Path not found: ${resolvedTarget}`);
|
|
1076
|
+
}
|
|
1077
|
+
if (path.basename(resolvedTarget) === "package.json") {
|
|
1078
|
+
return [resolvedTarget];
|
|
1079
|
+
}
|
|
1080
|
+
ensureDirectoryExists(resolvedTarget);
|
|
1081
|
+
const packageJsonPaths = [];
|
|
1082
|
+
const visit = (directoryPath) => {
|
|
1083
|
+
const packageJsonPath = path.join(directoryPath, "package.json");
|
|
1084
|
+
if (fs.existsSync(packageJsonPath) && fs.statSync(packageJsonPath).isFile()) {
|
|
1085
|
+
packageJsonPaths.push(packageJsonPath);
|
|
1086
|
+
}
|
|
1087
|
+
for (const entry of fs.readdirSync(directoryPath, { withFileTypes: true })) {
|
|
1088
|
+
if (!entry.isDirectory() || entry.isSymbolicLink()) {
|
|
1089
|
+
continue;
|
|
1090
|
+
}
|
|
1091
|
+
if (SKIP_DIRECTORIES.has(entry.name)) {
|
|
1092
|
+
continue;
|
|
1093
|
+
}
|
|
1094
|
+
visit(path.join(directoryPath, entry.name));
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
visit(resolvedTarget);
|
|
1098
|
+
return packageJsonPaths.sort();
|
|
1099
|
+
};
|
|
1100
|
+
const writeFileIfAllowed = (filePath, content, options, changes) => {
|
|
1101
|
+
if (fs.existsSync(filePath) && !options.forceFiles && !options.force) {
|
|
1102
|
+
changes.warnings.push(`${filePath} already exists and will not be overwritten`);
|
|
1103
|
+
return false;
|
|
1104
|
+
}
|
|
1105
|
+
if (!options.dryRun) {
|
|
1106
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
1107
|
+
fs.writeFileSync(filePath, content, "utf8");
|
|
1108
|
+
}
|
|
1109
|
+
changes.files.push(filePath);
|
|
1110
|
+
return true;
|
|
1111
|
+
};
|
|
1112
|
+
const ensureDirectory = (directoryPath, options, changes) => {
|
|
1113
|
+
if (fs.existsSync(directoryPath)) {
|
|
1114
|
+
if (!fs.statSync(directoryPath).isDirectory()) {
|
|
1115
|
+
throw new Error(`Target path is not a directory: ${directoryPath}`);
|
|
1116
|
+
}
|
|
1117
|
+
changes.skipped.push(`${directoryPath} already exists`);
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
if (!options.dryRun) {
|
|
1121
|
+
fs.mkdirSync(directoryPath, { recursive: true });
|
|
1122
|
+
}
|
|
1123
|
+
changes.directories.push(directoryPath);
|
|
1124
|
+
};
|
|
1125
|
+
const endpointWorkerTemplate = "import type { App } from 'vue'\n\nimport { watch } from 'vue'\n\nimport { useField } from '@retailcrm/embed-ui'\n\nimport {\n definePageRunner,\n defineRunner,\n defineWidgetRunner,\n runEndpoint,\n} from '@retailcrm/embed-ui-v1-endpoint/remote'\nimport {\n useContext as useSettingsContext,\n} from '@retailcrm/embed-ui-v1-contexts/remote/settings'\n\nimport OrderCommonAfterWidget from '../widgets/OrderCommonAfterWidget.vue'\n\nimport SettingsPage from '../pages/SettingsPage.vue'\n\nimport { i18n } from '../i18n'\n\nconst setupApp = async (app: App) => {\n app.use(i18n)\n\n const settings = useSettingsContext()\n await settings.initialize()\n\n const locale = useField(settings, 'system.locale')\n\n i18n.global.locale.value = locale.value\n\n watch(locale, value => {\n i18n.global.locale.value = value\n })\n}\n\nconst runner = defineRunner({\n pages: [{\n __PAGE_CODE__: definePageRunner(SettingsPage, setupApp),\n }],\n widgets: [{\n __WIDGET_TARGET__: defineWidgetRunner(OrderCommonAfterWidget, setupApp),\n }],\n})\n\nrunEndpoint(runner)\n";
|
|
1126
|
+
const envDtsTemplate = `/// <reference types="vite/client" />
|
|
1127
|
+
|
|
1128
|
+
declare module '*.svg' {
|
|
1129
|
+
import type { DefineComponent } from 'vue'
|
|
1130
|
+
|
|
1131
|
+
const component: DefineComponent<Record<string, never>, Record<string, never>, unknown>
|
|
1132
|
+
export default component
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
declare module '*.vue' {
|
|
1136
|
+
import type { DefineComponent } from 'vue'
|
|
1137
|
+
|
|
1138
|
+
const component: DefineComponent<Record<string, never>, Record<string, never>, unknown>
|
|
1139
|
+
export default component
|
|
1140
|
+
}
|
|
1141
|
+
`;
|
|
1142
|
+
const eslintConfigTemplate = "import { defineConfig } from 'eslint/config'\n\nimport globals from 'globals'\n\nimport pluginDependencies from '@omnicajs/eslint-plugin-dependencies'\nimport pluginJs from '@eslint/js'\nimport pluginTs from 'typescript-eslint'\nimport pluginVue from 'eslint-plugin-vue'\nimport pluginVueI18n from '@intlify/eslint-plugin-vue-i18n'\n\nexport default defineConfig([\n { files: ['**/*.{js,mjs,cjs,ts,vue}'] },\n {\n settings: {\n 'vue-i18n': {\n localeDir: {\n pattern: '__LOCALE_DIR_PATTERN__',\n localeKey: 'file',\n },\n messageSyntaxVersion: '^11.0.0',\n },\n },\n },\n pluginJs.configs.recommended,\n ...pluginTs.configs.recommended,\n ...pluginVue.configs['flat/essential'],\n ...pluginVueI18n.configs.recommended,\n {\n files: ['**/*.{js,mjs,cjs,ts,vue}'],\n plugins: {\n '@intlify/vue-i18n': pluginVueI18n,\n dependencies: pluginDependencies,\n },\n languageOptions: {\n globals: {\n ...globals.browser,\n ...globals.node,\n },\n },\n rules: {\n 'comma-dangle': ['error', 'always-multiline'],\n 'eqeqeq': ['error', 'always'],\n 'indent': ['error', 2, { SwitchCase: 1 }],\n 'no-debugger': 'error',\n 'no-multiple-empty-lines': ['error', { max: 1, maxBOF: 0, maxEOF: 0 }],\n 'no-trailing-spaces': 'error',\n 'object-curly-spacing': ['error', 'always'],\n 'quotes': ['error', 'single'],\n 'semi': ['error', 'never'],\n\n '@typescript-eslint/consistent-type-imports': ['error', {\n prefer: 'type-imports',\n fixStyle: 'separate-type-imports',\n }],\n\n '@intlify/vue-i18n/key-format-style': ['error', 'camelCase', {\n allowArray: true,\n }],\n '@intlify/vue-i18n/no-duplicate-keys-in-locale': 'error',\n '@intlify/vue-i18n/no-dynamic-keys': 'error',\n '@intlify/vue-i18n/no-missing-keys': 'error',\n '@intlify/vue-i18n/no-missing-keys-in-other-locales': 'error',\n '@intlify/vue-i18n/no-raw-text': ['warn', {\n ignorePattern: '^[-–—~+#:()&=×%/\\\\d\\\\s\\\\u00A0\\\\n,.<>•]+$',\n ignoreText: ['API', 'CRM', ''],\n }],\n '@intlify/vue-i18n/no-unknown-locale': 'error',\n '@intlify/vue-i18n/no-unused-keys': 'error',\n '@intlify/vue-i18n/sfc-locale-attr': 'error',\n\n 'dependencies/import-style': ['error', {\n maxSingleLineLength: 90,\n maxSingleLineSpecifiers: 3,\n }],\n 'dependencies/separate-type-imports': 'error',\n 'dependencies/separate-type-partitions': 'error',\n 'dependencies/sort-named-imports': ['error', {\n type: 'alphabetical',\n ignoreAlias: true,\n }],\n 'dependencies/sort-imports': ['error', {\n type: 'alphabetical',\n imports: {\n orderBy: 'alias',\n splitDeclarations: true,\n },\n groups: [\n 'side-effect-style',\n 'side-effect',\n [\n 'type-import',\n 'type-external',\n 'type-vue-components',\n 'type-internal',\n 'type-parent',\n 'type-sibling',\n 'type-index',\n ],\n 'builtin',\n 'value-external',\n 'value-vue-components',\n 'value-internal',\n ['value-parent', 'value-sibling'],\n 'index',\n 'ts-equals-import',\n 'unknown',\n ],\n customGroups: [{\n groupName: 'type-vue-components',\n selector: 'type',\n elementNamePattern: ['\\\\.(svg|vue)$'],\n }, {\n groupName: 'value-vue-components',\n elementNamePattern: ['\\\\.(svg|vue)$'],\n }],\n newlinesInside: 1,\n partitions: {\n orderBy: 'type-first',\n splitBy: {\n comments: false,\n newlines: true,\n },\n },\n }],\n },\n },\n {\n files: ['**/*.vue'],\n languageOptions: {\n parserOptions: { parser: pluginTs.parser },\n },\n rules: {\n 'vue/block-order': ['error', {\n order: ['template', 'script', 'i18n', 'style'],\n }],\n 'vue/attributes-order': 'error',\n 'vue/component-definition-name-casing': ['error', 'PascalCase'],\n 'vue/component-name-in-template-casing': ['error', 'PascalCase'],\n 'vue/html-indent': ['error', 4, {\n attribute: 1,\n baseIndent: 1,\n closeBracket: 0,\n switchCase: 1,\n }],\n 'vue/html-self-closing': ['error', {\n html: {\n component: 'always',\n normal: 'always',\n void: 'always',\n },\n math: 'always',\n svg: 'always',\n }],\n 'vue/max-attributes-per-line': ['error', {\n singleline: 4,\n multiline: { max: 1 },\n }],\n 'vue/script-indent': ['error', 2, {\n baseIndent: 0,\n switchCase: 1,\n }],\n\n 'indent': 'off',\n },\n },\n { ignores: ['dist/**', 'coverage/**'] },\n])\n";
|
|
1143
|
+
const extensionIconTemplate = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">\n <path d="M12 3 4 7v10l8 4 8-4V7l-8-4Z" stroke="currentColor" stroke-width="1.5" />\n <path d="m8 10 4 2 4-2M12 12v5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />\n</svg>\n';
|
|
1144
|
+
const i18nIndexTemplate = "import { createI18n } from 'vue-i18n'\n\nimport enGB from './locales/en-GB.json'\nimport esES from './locales/es-ES.json'\nimport ruRU from './locales/ru-RU.json'\n\nconst messages = {\n 'en-GB': enGB,\n 'es-ES': esES,\n 'ru-RU': ruRU,\n} as const\n\nexport type Locale = keyof typeof messages\nexport type MessageSchema = typeof enGB\n\nexport const i18n = createI18n<[MessageSchema], Locale>({\n legacy: false,\n locale: 'ru-RU',\n fallbackLocale: 'en-GB',\n messages,\n})\n";
|
|
1145
|
+
const orderWidgetTemplate = `<template>
|
|
1146
|
+
<UiToolbarButton @click="openSidebar">
|
|
1147
|
+
{{ t('openSidebar') }}
|
|
1148
|
+
</UiToolbarButton>
|
|
1149
|
+
|
|
1150
|
+
<UiToolbarLink
|
|
1151
|
+
dotted
|
|
1152
|
+
href="javascript:void(0);"
|
|
1153
|
+
@click="openWindow"
|
|
1154
|
+
>
|
|
1155
|
+
{{ t('openWindow') }}
|
|
1156
|
+
</UiToolbarLink>
|
|
1157
|
+
|
|
1158
|
+
<UiModalSidebar v-model:opened="sidebarOpened">
|
|
1159
|
+
<template #title>
|
|
1160
|
+
{{ t('sidebarTitle') }}
|
|
1161
|
+
</template>
|
|
1162
|
+
|
|
1163
|
+
<div :class="$style['order-widget-sidebar']">
|
|
1164
|
+
<p :class="$style['order-widget-sidebar__lead']">{{ t('sidebarLead') }}</p>
|
|
1165
|
+
|
|
1166
|
+
<div :class="$style['order-widget-summary']">
|
|
1167
|
+
<span :class="$style['order-widget-summary__label']">{{ t('sampleTarget') }}</span>
|
|
1168
|
+
<strong :class="$style['order-widget-summary__value']">{{ props.target }}</strong>
|
|
1169
|
+
</div>
|
|
1170
|
+
|
|
1171
|
+
<div :class="$style['order-widget-summary']">
|
|
1172
|
+
<span :class="$style['order-widget-summary__label']">{{ t('sampleCustomer') }}</span>
|
|
1173
|
+
<strong :class="$style['order-widget-summary__value']">{{ t('sampleCustomerValue') }}</strong>
|
|
1174
|
+
</div>
|
|
1175
|
+
|
|
1176
|
+
<UiField
|
|
1177
|
+
:id="fieldIds.followUpNote"
|
|
1178
|
+
v-slot="{ id, ariaInvalid, ariaLabelledby }"
|
|
1179
|
+
:label="t('followUpNote')"
|
|
1180
|
+
>
|
|
1181
|
+
<UiTextbox
|
|
1182
|
+
:id="id"
|
|
1183
|
+
v-model:value="followUpNote"
|
|
1184
|
+
:input-attributes="{
|
|
1185
|
+
'aria-invalid': ariaInvalid,
|
|
1186
|
+
'aria-labelledby': ariaLabelledby,
|
|
1187
|
+
}"
|
|
1188
|
+
multiline
|
|
1189
|
+
rows="4"
|
|
1190
|
+
width="100%"
|
|
1191
|
+
/>
|
|
1192
|
+
</UiField>
|
|
1193
|
+
|
|
1194
|
+
<UiField
|
|
1195
|
+
:id="fieldIds.createTask"
|
|
1196
|
+
v-slot="{ id, ariaInvalid, ariaLabelledby }"
|
|
1197
|
+
:label="t('createTask')"
|
|
1198
|
+
>
|
|
1199
|
+
<UiSwitch
|
|
1200
|
+
:id="id"
|
|
1201
|
+
v-model:value="createTask"
|
|
1202
|
+
:aria-invalid="ariaInvalid"
|
|
1203
|
+
:aria-labelledby="ariaLabelledby"
|
|
1204
|
+
/>
|
|
1205
|
+
</UiField>
|
|
1206
|
+
</div>
|
|
1207
|
+
|
|
1208
|
+
<template #footer>
|
|
1209
|
+
<div :class="$style['order-widget-footer']">
|
|
1210
|
+
<UiButton @click="saveDraft">
|
|
1211
|
+
{{ t('saveDraft') }}
|
|
1212
|
+
</UiButton>
|
|
1213
|
+
|
|
1214
|
+
<UiButton
|
|
1215
|
+
appearance="secondary"
|
|
1216
|
+
@click="closeSidebar"
|
|
1217
|
+
>
|
|
1218
|
+
{{ t('close') }}
|
|
1219
|
+
</UiButton>
|
|
1220
|
+
</div>
|
|
1221
|
+
</template>
|
|
1222
|
+
</UiModalSidebar>
|
|
1223
|
+
|
|
1224
|
+
<UiModalWindow v-model:opened="windowOpened">
|
|
1225
|
+
<template #title>
|
|
1226
|
+
{{ t('windowTitle') }}
|
|
1227
|
+
</template>
|
|
1228
|
+
|
|
1229
|
+
<div :class="$style['order-widget-window']">
|
|
1230
|
+
<p :class="$style['order-widget-window__lead']">{{ t('windowLead') }}</p>
|
|
1231
|
+
</div>
|
|
1232
|
+
|
|
1233
|
+
<template #footer>
|
|
1234
|
+
<div :class="$style['order-widget-footer']">
|
|
1235
|
+
<UiButton @click="saveWindow">
|
|
1236
|
+
{{ t('save') }}
|
|
1237
|
+
</UiButton>
|
|
1238
|
+
|
|
1239
|
+
<UiButton
|
|
1240
|
+
appearance="secondary"
|
|
1241
|
+
@click="windowOpened = false"
|
|
1242
|
+
>
|
|
1243
|
+
{{ t('cancel') }}
|
|
1244
|
+
</UiButton>
|
|
1245
|
+
</div>
|
|
1246
|
+
</template>
|
|
1247
|
+
</UiModalWindow>
|
|
1248
|
+
</template>
|
|
1249
|
+
|
|
1250
|
+
<script lang="ts" remote setup>
|
|
1251
|
+
import { ref } from 'vue'
|
|
1252
|
+
import { useI18n } from 'vue-i18n'
|
|
1253
|
+
|
|
1254
|
+
import {
|
|
1255
|
+
UiButton,
|
|
1256
|
+
UiField,
|
|
1257
|
+
UiModalSidebar,
|
|
1258
|
+
UiModalWindow,
|
|
1259
|
+
UiSwitch,
|
|
1260
|
+
UiTextbox,
|
|
1261
|
+
UiToolbarButton,
|
|
1262
|
+
UiToolbarLink,
|
|
1263
|
+
} from '@retailcrm/embed-ui-v1-components/remote'
|
|
1264
|
+
|
|
1265
|
+
const props = defineProps<{
|
|
1266
|
+
target: string
|
|
1267
|
+
}>()
|
|
1268
|
+
|
|
1269
|
+
const { t } = useI18n({ useScope: 'local' })
|
|
1270
|
+
|
|
1271
|
+
const sidebarOpened = ref(false)
|
|
1272
|
+
const windowOpened = ref(false)
|
|
1273
|
+
const followUpNote = ref('Call after payment confirmation.')
|
|
1274
|
+
const createTask = ref(true)
|
|
1275
|
+
|
|
1276
|
+
const fieldIds = {
|
|
1277
|
+
followUpNote: 'order-follow-up-note',
|
|
1278
|
+
createTask: 'order-create-task',
|
|
1279
|
+
} as const
|
|
1280
|
+
|
|
1281
|
+
const openSidebar = () => {
|
|
1282
|
+
sidebarOpened.value = true
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
const openWindow = () => {
|
|
1286
|
+
windowOpened.value = true
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
const closeSidebar = () => {
|
|
1290
|
+
sidebarOpened.value = false
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
const saveDraft = () => {
|
|
1294
|
+
sidebarOpened.value = false
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
const saveWindow = () => {
|
|
1298
|
+
windowOpened.value = false
|
|
1299
|
+
}
|
|
1300
|
+
<\/script>
|
|
1301
|
+
|
|
1302
|
+
<i18n locale="en-GB" lang="json">
|
|
1303
|
+
{
|
|
1304
|
+
"cancel": "Cancel",
|
|
1305
|
+
"close": "Close",
|
|
1306
|
+
"createTask": "Create task after save",
|
|
1307
|
+
"followUpNote": "Follow-up note",
|
|
1308
|
+
"openSidebar": "Prepare follow-up",
|
|
1309
|
+
"openWindow": "Quick action",
|
|
1310
|
+
"sampleCustomer": "Sample customer",
|
|
1311
|
+
"sampleCustomerValue": "Maria Garcia, paid order",
|
|
1312
|
+
"sampleTarget": "Current target",
|
|
1313
|
+
"save": "Save",
|
|
1314
|
+
"saveDraft": "Save draft",
|
|
1315
|
+
"sidebarLead": "Use this sidebar as a compact place for real order controls.",
|
|
1316
|
+
"sidebarTitle": "Order follow-up",
|
|
1317
|
+
"windowLead": "Use a modal window for a focused confirmation or a short blocking action.",
|
|
1318
|
+
"windowTitle": "Quick order action"
|
|
1319
|
+
}
|
|
1320
|
+
</i18n>
|
|
1321
|
+
|
|
1322
|
+
<i18n locale="es-ES" lang="json">
|
|
1323
|
+
{
|
|
1324
|
+
"cancel": "Cancelar",
|
|
1325
|
+
"close": "Cerrar",
|
|
1326
|
+
"createTask": "Crear tarea al guardar",
|
|
1327
|
+
"followUpNote": "Nota de seguimiento",
|
|
1328
|
+
"openSidebar": "Preparar seguimiento",
|
|
1329
|
+
"openWindow": "Accion rapida",
|
|
1330
|
+
"sampleCustomer": "Cliente de ejemplo",
|
|
1331
|
+
"sampleCustomerValue": "Maria Garcia, pedido pagado",
|
|
1332
|
+
"sampleTarget": "Target actual",
|
|
1333
|
+
"save": "Guardar",
|
|
1334
|
+
"saveDraft": "Guardar borrador",
|
|
1335
|
+
"sidebarLead": "Use esta barra lateral como lugar compacto para controles reales del pedido.",
|
|
1336
|
+
"sidebarTitle": "Seguimiento del pedido",
|
|
1337
|
+
"windowLead": "Use una ventana modal para una confirmacion enfocada o una accion bloqueante breve.",
|
|
1338
|
+
"windowTitle": "Accion rapida del pedido"
|
|
1339
|
+
}
|
|
1340
|
+
</i18n>
|
|
1341
|
+
|
|
1342
|
+
<i18n locale="ru-RU" lang="json">
|
|
1343
|
+
{
|
|
1344
|
+
"cancel": "Отменить",
|
|
1345
|
+
"close": "Закрыть",
|
|
1346
|
+
"createTask": "Создать задачу после сохранения",
|
|
1347
|
+
"followUpNote": "Заметка для связи",
|
|
1348
|
+
"openSidebar": "Подготовить связь",
|
|
1349
|
+
"openWindow": "Быстрое действие",
|
|
1350
|
+
"sampleCustomer": "Демонстрационный клиент",
|
|
1351
|
+
"sampleCustomerValue": "Мария Гарсия, оплаченный заказ",
|
|
1352
|
+
"sampleTarget": "Текущая цель",
|
|
1353
|
+
"save": "Сохранить",
|
|
1354
|
+
"saveDraft": "Сохранить черновик",
|
|
1355
|
+
"sidebarLead": "Используйте эту боковую панель как компактное место для реальных контролов заказа.",
|
|
1356
|
+
"sidebarTitle": "Связь по заказу",
|
|
1357
|
+
"windowLead": "Используйте модальное окно для точечного подтверждения или короткого блокирующего действия.",
|
|
1358
|
+
"windowTitle": "Быстрое действие с заказом"
|
|
1359
|
+
}
|
|
1360
|
+
</i18n>
|
|
1361
|
+
|
|
1362
|
+
<style lang="less" module>
|
|
1363
|
+
@import (reference) '@retailcrm/embed-ui-v1-components/assets/stylesheets/palette.less';
|
|
1364
|
+
@import (reference) '@retailcrm/embed-ui-v1-components/assets/stylesheets/typography.less';
|
|
1365
|
+
|
|
1366
|
+
.order-widget-sidebar {
|
|
1367
|
+
display: grid;
|
|
1368
|
+
gap: 18px;
|
|
1369
|
+
|
|
1370
|
+
&__lead {
|
|
1371
|
+
.text-small();
|
|
1372
|
+
|
|
1373
|
+
margin: 0;
|
|
1374
|
+
color: @grey-900;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
.order-widget-window {
|
|
1379
|
+
display: grid;
|
|
1380
|
+
gap: 18px;
|
|
1381
|
+
|
|
1382
|
+
&__lead {
|
|
1383
|
+
.text-small();
|
|
1384
|
+
|
|
1385
|
+
margin: 0;
|
|
1386
|
+
color: @grey-900;
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
.order-widget-summary {
|
|
1391
|
+
display: grid;
|
|
1392
|
+
gap: 4px;
|
|
1393
|
+
padding: 12px;
|
|
1394
|
+
border: 1px solid @grey-500;
|
|
1395
|
+
border-radius: 6px;
|
|
1396
|
+
background: @grey-100;
|
|
1397
|
+
|
|
1398
|
+
&__label {
|
|
1399
|
+
.text-tiny();
|
|
1400
|
+
|
|
1401
|
+
color: @grey-800;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
&__value {
|
|
1405
|
+
.text-small-accent();
|
|
1406
|
+
|
|
1407
|
+
color: @black-500;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
.order-widget-footer {
|
|
1412
|
+
display: flex;
|
|
1413
|
+
align-items: center;
|
|
1414
|
+
gap: 12px;
|
|
1415
|
+
}
|
|
1416
|
+
</style>
|
|
1417
|
+
`;
|
|
1418
|
+
const publishScriptTemplate = `#!/usr/bin/env node
|
|
1419
|
+
|
|
1420
|
+
import { fileURLToPath } from 'node:url'
|
|
1421
|
+
|
|
1422
|
+
import fs from 'node:fs'
|
|
1423
|
+
|
|
1424
|
+
import path from 'node:path'
|
|
1425
|
+
|
|
1426
|
+
import { spawnSync } from 'node:child_process'
|
|
1427
|
+
|
|
1428
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
|
1429
|
+
const projectRoot = path.resolve(scriptDir, '..')
|
|
1430
|
+
const args = new Set(process.argv.slice(2))
|
|
1431
|
+
const archiveOnly = args.has('--archive-only')
|
|
1432
|
+
|
|
1433
|
+
const readJsonFile = (filePath) => JSON.parse(fs.readFileSync(filePath, 'utf8'))
|
|
1434
|
+
|
|
1435
|
+
const loadEnvFile = (filePath) => {
|
|
1436
|
+
if (!fs.existsSync(filePath)) {
|
|
1437
|
+
return
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
for (const line of fs.readFileSync(filePath, 'utf8').split(/\\r?\\n/u)) {
|
|
1441
|
+
const trimmed = line.trim()
|
|
1442
|
+
|
|
1443
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
1444
|
+
continue
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
const separatorIndex = trimmed.indexOf('=')
|
|
1448
|
+
|
|
1449
|
+
if (separatorIndex === -1) {
|
|
1450
|
+
continue
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
const key = trimmed.slice(0, separatorIndex).trim()
|
|
1454
|
+
let value = trimmed.slice(separatorIndex + 1).trim()
|
|
1455
|
+
|
|
1456
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith('\\'') && value.endsWith('\\''))) {
|
|
1457
|
+
value = value.slice(1, -1)
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
process.env[key] ??= value
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
const assertNonEmptyString = (value, field) => {
|
|
1465
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
1466
|
+
throw new Error('Field "' + field + '" must be a non-empty string')
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
return value
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
const assertStringArray = (value, field) => {
|
|
1473
|
+
if (value === undefined) {
|
|
1474
|
+
return []
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
if (!Array.isArray(value) || value.some(item => typeof item !== 'string' || item.trim() === '')) {
|
|
1478
|
+
throw new Error('Field "' + field + '" must be an array of non-empty strings')
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
return value
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
const listFiles = (directoryPath, basePath = directoryPath) => {
|
|
1485
|
+
const result = []
|
|
1486
|
+
|
|
1487
|
+
for (const entry of fs.readdirSync(directoryPath, { withFileTypes: true })) {
|
|
1488
|
+
const entryPath = path.join(directoryPath, entry.name)
|
|
1489
|
+
const relativePath = path.relative(basePath, entryPath).split(path.sep).join('/')
|
|
1490
|
+
|
|
1491
|
+
if (entry.isDirectory()) {
|
|
1492
|
+
result.push(...listFiles(entryPath, basePath))
|
|
1493
|
+
continue
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
if (entry.isFile()) {
|
|
1497
|
+
result.push(relativePath)
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
return result
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
const normalizeManifestPath = (value) => typeof value === 'string' && value.startsWith('./')
|
|
1505
|
+
? value.slice(2)
|
|
1506
|
+
: value
|
|
1507
|
+
|
|
1508
|
+
const pickBuildArtifacts = (distDir, code) => {
|
|
1509
|
+
const files = listFiles(distDir)
|
|
1510
|
+
.filter(file => file !== 'manifest.json')
|
|
1511
|
+
.filter(file => file !== code + '.zip')
|
|
1512
|
+
.filter(file => !file.endsWith('.map'))
|
|
1513
|
+
|
|
1514
|
+
const viteManifestPath = path.join(distDir, '.vite/manifest.json')
|
|
1515
|
+
|
|
1516
|
+
if (fs.existsSync(viteManifestPath)) {
|
|
1517
|
+
const viteManifest = readJsonFile(viteManifestPath)
|
|
1518
|
+
const entries = Object.values(viteManifest)
|
|
1519
|
+
const entry = entries.find(item => item && item.isEntry) ?? entries[0]
|
|
1520
|
+
|
|
1521
|
+
if (entry?.file) {
|
|
1522
|
+
return {
|
|
1523
|
+
files,
|
|
1524
|
+
scriptFile: normalizeManifestPath(entry.file),
|
|
1525
|
+
styleFile: Array.isArray(entry.css) ? normalizeManifestPath(entry.css[0]) : null,
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
return {
|
|
1531
|
+
files,
|
|
1532
|
+
scriptFile: files.find(file => file.endsWith('.js')) ?? null,
|
|
1533
|
+
styleFile: files.find(file => file.endsWith('.css')) ?? null,
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
const zipExtension = (distDir, code, extensionManifest, files) => {
|
|
1538
|
+
const archivePath = path.join(distDir, 'extension.zip')
|
|
1539
|
+
const manifestPath = path.join(distDir, 'manifest.json')
|
|
1540
|
+
const previousManifest = fs.existsSync(manifestPath) ? fs.readFileSync(manifestPath) : null
|
|
1541
|
+
|
|
1542
|
+
fs.writeFileSync(manifestPath, JSON.stringify(extensionManifest), 'utf8')
|
|
1543
|
+
|
|
1544
|
+
try {
|
|
1545
|
+
const zipResult = spawnSync('zip', ['-rFS', archivePath, ...files, 'manifest.json'], {
|
|
1546
|
+
cwd: distDir,
|
|
1547
|
+
stdio: 'inherit',
|
|
1548
|
+
})
|
|
1549
|
+
|
|
1550
|
+
if (zipResult.error) {
|
|
1551
|
+
throw new Error('Zip command failed: ' + zipResult.error.message)
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
if (zipResult.status !== 0) {
|
|
1555
|
+
throw new Error('Zip archive creation failed')
|
|
1556
|
+
}
|
|
1557
|
+
} finally {
|
|
1558
|
+
if (previousManifest) {
|
|
1559
|
+
fs.writeFileSync(manifestPath, previousManifest)
|
|
1560
|
+
} else {
|
|
1561
|
+
fs.rmSync(manifestPath, { force: true })
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
return archivePath
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
loadEnvFile(path.join(projectRoot, '.env'))
|
|
1569
|
+
|
|
1570
|
+
const configPath = path.join(projectRoot, 'extensionrc.json')
|
|
1571
|
+
|
|
1572
|
+
if (!fs.existsSync(configPath)) {
|
|
1573
|
+
console.error('Config not found: ' + configPath)
|
|
1574
|
+
process.exit(1)
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
let config
|
|
1578
|
+
|
|
1579
|
+
try {
|
|
1580
|
+
config = readJsonFile(configPath)
|
|
1581
|
+
} catch (error) {
|
|
1582
|
+
console.error('Cannot read extensionrc.json:', error)
|
|
1583
|
+
process.exit(1)
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
try {
|
|
1587
|
+
const code = config.code ?? 'retailcrm-extension-frontend'
|
|
1588
|
+
const uuid = assertNonEmptyString(config.uuid, 'uuid')
|
|
1589
|
+
const version = assertNonEmptyString(config.version, 'version')
|
|
1590
|
+
const targets = assertStringArray(config.targets, 'targets')
|
|
1591
|
+
const pages = assertStringArray(config.pages, 'pages')
|
|
1592
|
+
const runner = config.runner ?? 'worker'
|
|
1593
|
+
|
|
1594
|
+
if (targets.length === 0 && pages.length === 0) {
|
|
1595
|
+
throw new Error('Specify at least one target or page in extensionrc.json')
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
const distDir = path.join(projectRoot, 'dist')
|
|
1599
|
+
|
|
1600
|
+
if (!fs.existsSync(distDir)) {
|
|
1601
|
+
throw new Error('Build directory not found: ' + distDir)
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
const { files, scriptFile, styleFile } = pickBuildArtifacts(distDir, code)
|
|
1605
|
+
|
|
1606
|
+
if (!scriptFile) {
|
|
1607
|
+
throw new Error('Missing JS build artifact. Run npm run build before publishing.')
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
const extensionManifest = {
|
|
1611
|
+
code,
|
|
1612
|
+
version,
|
|
1613
|
+
entrypoint: scriptFile,
|
|
1614
|
+
scripts: [scriptFile],
|
|
1615
|
+
runner,
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
if (targets.length > 0) {
|
|
1619
|
+
extensionManifest.targets = targets
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
if (pages.length > 0) {
|
|
1623
|
+
extensionManifest.pages = pages
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
if (styleFile) {
|
|
1627
|
+
extensionManifest.stylesheet = styleFile
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
const archivePath = zipExtension(distDir, code, extensionManifest, files)
|
|
1631
|
+
|
|
1632
|
+
console.log('Archive created: ' + archivePath)
|
|
1633
|
+
|
|
1634
|
+
if (archiveOnly) {
|
|
1635
|
+
process.exit(0)
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
const crmHost = process.env.CRM_API_HOST
|
|
1639
|
+
const crmKey = process.env.CRM_API_KEY
|
|
1640
|
+
const baseUrl = config.baseUrl || process.env.MODULE_URL || process.env.EXTENSION_BASE_URL
|
|
1641
|
+
|
|
1642
|
+
if (!crmHost || !crmKey) {
|
|
1643
|
+
throw new Error('Missing CRM_API_HOST or CRM_API_KEY in .env')
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
if (!baseUrl) {
|
|
1647
|
+
throw new Error('Missing MODULE_URL or EXTENSION_BASE_URL in .env or baseUrl in extensionrc.json')
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
const embedJs = {
|
|
1651
|
+
entrypoint: config.entrypoint || '/extension/' + uuid + '/script',
|
|
1652
|
+
runner,
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
if (targets.length > 0) {
|
|
1656
|
+
embedJs.targets = targets
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
if (pages.length > 0) {
|
|
1660
|
+
embedJs.pages = pages
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
if (styleFile && config.stylesheet !== false) {
|
|
1664
|
+
embedJs.stylesheet = typeof config.stylesheet === 'string'
|
|
1665
|
+
? config.stylesheet
|
|
1666
|
+
: '/extension/' + uuid + '/stylesheet'
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
const integrationModule = {
|
|
1670
|
+
code,
|
|
1671
|
+
integrationCode: code,
|
|
1672
|
+
active: true,
|
|
1673
|
+
name: config.name || code,
|
|
1674
|
+
clientId: config.clientId || 'client-id-xxx',
|
|
1675
|
+
baseUrl,
|
|
1676
|
+
integrations: {
|
|
1677
|
+
embedJs,
|
|
1678
|
+
},
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
const form = new FormData()
|
|
1682
|
+
form.append('integrationModule', JSON.stringify(integrationModule))
|
|
1683
|
+
|
|
1684
|
+
const response = await fetch(new URL('/api/v5/integration-modules/' + code + '/edit', crmHost), {
|
|
1685
|
+
method: 'POST',
|
|
1686
|
+
headers: {
|
|
1687
|
+
'X-Api-Key': crmKey,
|
|
1688
|
+
},
|
|
1689
|
+
body: form,
|
|
1690
|
+
})
|
|
1691
|
+
|
|
1692
|
+
const text = await response.text()
|
|
1693
|
+
|
|
1694
|
+
if (!response.ok) {
|
|
1695
|
+
console.error('Request failed: ' + response.status + ' ' + response.statusText)
|
|
1696
|
+
console.error(text)
|
|
1697
|
+
process.exit(1)
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
console.log(text)
|
|
1701
|
+
} catch (error) {
|
|
1702
|
+
console.error(error instanceof Error ? error.message : error)
|
|
1703
|
+
process.exit(1)
|
|
1704
|
+
}
|
|
1705
|
+
`;
|
|
1706
|
+
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- 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";
|
|
1707
|
+
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- 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";
|
|
1708
|
+
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- Цель виджета: `__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";
|
|
1709
|
+
const settingsPageTemplate = `<template>
|
|
1710
|
+
<main :class="$style['settings-page']">
|
|
1711
|
+
<UiPageHeader :value="t('title')">
|
|
1712
|
+
<template #addon>
|
|
1713
|
+
<div :class="$style['settings-page-addon']">
|
|
1714
|
+
<ExtensionIcon
|
|
1715
|
+
:class="$style['settings-page-addon__icon']"
|
|
1716
|
+
aria-hidden="true"
|
|
1717
|
+
/>
|
|
1718
|
+
<span :class="$style['settings-page-addon__subtitle']">{{ t('subtitle') }}</span>
|
|
1719
|
+
</div>
|
|
1720
|
+
</template>
|
|
1721
|
+
|
|
1722
|
+
<template #actions>
|
|
1723
|
+
<UiButton @click="saveSettings">
|
|
1724
|
+
{{ t('save') }}
|
|
1725
|
+
</UiButton>
|
|
1726
|
+
</template>
|
|
1727
|
+
</UiPageHeader>
|
|
1728
|
+
|
|
1729
|
+
<section :class="$style['settings-form']">
|
|
1730
|
+
<div :class="$style['settings-form__intro']">
|
|
1731
|
+
<h2 :class="$style['settings-form__heading']">{{ t('workspaceTitle') }}</h2>
|
|
1732
|
+
<p :class="$style['settings-form__description']">{{ t('workspaceDescription') }}</p>
|
|
1733
|
+
</div>
|
|
1734
|
+
|
|
1735
|
+
<div :class="$style['settings-form__grid']">
|
|
1736
|
+
<UiField
|
|
1737
|
+
:id="fieldIds.assistantName"
|
|
1738
|
+
v-slot="{ id, ariaInvalid, ariaLabelledby }"
|
|
1739
|
+
:hint="t('assistantNameHint')"
|
|
1740
|
+
:hint-aria-label="t('hint')"
|
|
1741
|
+
:label="t('assistantName')"
|
|
1742
|
+
>
|
|
1743
|
+
<UiTextbox
|
|
1744
|
+
:id="id"
|
|
1745
|
+
v-model:value="assistantName"
|
|
1746
|
+
:input-attributes="{
|
|
1747
|
+
'aria-invalid': ariaInvalid,
|
|
1748
|
+
'aria-labelledby': ariaLabelledby,
|
|
1749
|
+
}"
|
|
1750
|
+
width="100%"
|
|
1751
|
+
@update:value="resetSaved"
|
|
1752
|
+
/>
|
|
1753
|
+
</UiField>
|
|
1754
|
+
|
|
1755
|
+
<UiField
|
|
1756
|
+
:id="fieldIds.owner"
|
|
1757
|
+
v-slot="{ id, ariaInvalid, ariaLabelledby }"
|
|
1758
|
+
:label="t('owner')"
|
|
1759
|
+
>
|
|
1760
|
+
<UiSelect
|
|
1761
|
+
:id="id"
|
|
1762
|
+
v-model:value="owner"
|
|
1763
|
+
:aria-invalid="ariaInvalid"
|
|
1764
|
+
:aria-labelledby="ariaLabelledby"
|
|
1765
|
+
width="100%"
|
|
1766
|
+
@update:value="resetSaved"
|
|
1767
|
+
>
|
|
1768
|
+
<UiSelectOption
|
|
1769
|
+
value="sales"
|
|
1770
|
+
:label="t('ownerSales')"
|
|
1771
|
+
/>
|
|
1772
|
+
<UiSelectOption
|
|
1773
|
+
value="delivery"
|
|
1774
|
+
:label="t('ownerDelivery')"
|
|
1775
|
+
/>
|
|
1776
|
+
</UiSelect>
|
|
1777
|
+
</UiField>
|
|
1778
|
+
|
|
1779
|
+
<UiField
|
|
1780
|
+
:id="fieldIds.note"
|
|
1781
|
+
v-slot="{ id, ariaInvalid, ariaLabelledby }"
|
|
1782
|
+
:class="$style['settings-form__note']"
|
|
1783
|
+
:label="t('note')"
|
|
1784
|
+
>
|
|
1785
|
+
<UiTextbox
|
|
1786
|
+
:id="id"
|
|
1787
|
+
v-model:value="note"
|
|
1788
|
+
:input-attributes="{
|
|
1789
|
+
'aria-invalid': ariaInvalid,
|
|
1790
|
+
'aria-labelledby': ariaLabelledby,
|
|
1791
|
+
}"
|
|
1792
|
+
multiline
|
|
1793
|
+
rows="4"
|
|
1794
|
+
width="100%"
|
|
1795
|
+
@update:value="resetSaved"
|
|
1796
|
+
/>
|
|
1797
|
+
</UiField>
|
|
1798
|
+
|
|
1799
|
+
<UiField
|
|
1800
|
+
:id="fieldIds.enabled"
|
|
1801
|
+
v-slot="{ id, ariaInvalid, ariaLabelledby }"
|
|
1802
|
+
:label="t('enabled')"
|
|
1803
|
+
>
|
|
1804
|
+
<UiSwitch
|
|
1805
|
+
:id="id"
|
|
1806
|
+
v-model:value="enabled"
|
|
1807
|
+
:aria-invalid="ariaInvalid"
|
|
1808
|
+
:aria-labelledby="ariaLabelledby"
|
|
1809
|
+
@update:value="resetSaved"
|
|
1810
|
+
/>
|
|
1811
|
+
</UiField>
|
|
1812
|
+
</div>
|
|
1813
|
+
|
|
1814
|
+
<p
|
|
1815
|
+
v-if="saved"
|
|
1816
|
+
:class="$style['settings-form__status']"
|
|
1817
|
+
>
|
|
1818
|
+
{{ t('saved') }}
|
|
1819
|
+
</p>
|
|
1820
|
+
</section>
|
|
1821
|
+
</main>
|
|
1822
|
+
</template>
|
|
1823
|
+
|
|
1824
|
+
<script lang="ts" remote setup>
|
|
1825
|
+
import { ref } from 'vue'
|
|
1826
|
+
import { useI18n } from 'vue-i18n'
|
|
1827
|
+
|
|
1828
|
+
import {
|
|
1829
|
+
UiButton,
|
|
1830
|
+
UiField,
|
|
1831
|
+
UiPageHeader,
|
|
1832
|
+
UiSelect,
|
|
1833
|
+
UiSelectOption,
|
|
1834
|
+
UiSwitch,
|
|
1835
|
+
UiTextbox,
|
|
1836
|
+
} from '@retailcrm/embed-ui-v1-components/remote'
|
|
1837
|
+
|
|
1838
|
+
import ExtensionIcon from '@/shared/assets/extension.svg'
|
|
1839
|
+
|
|
1840
|
+
const { t } = useI18n({ useScope: 'local' })
|
|
1841
|
+
|
|
1842
|
+
const assistantName = ref('Order follow-up')
|
|
1843
|
+
const owner = ref('sales')
|
|
1844
|
+
const enabled = ref(true)
|
|
1845
|
+
const note = ref('Check payment status and delivery date before calling the customer.')
|
|
1846
|
+
const saved = ref(false)
|
|
1847
|
+
|
|
1848
|
+
const fieldIds = {
|
|
1849
|
+
assistantName: 'extension-assistant-name',
|
|
1850
|
+
note: 'extension-note',
|
|
1851
|
+
owner: 'extension-owner',
|
|
1852
|
+
enabled: 'extension-enabled',
|
|
1853
|
+
} as const
|
|
1854
|
+
|
|
1855
|
+
const resetSaved = () => {
|
|
1856
|
+
saved.value = false
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
const saveSettings = () => {
|
|
1860
|
+
saved.value = true
|
|
1861
|
+
}
|
|
1862
|
+
<\/script>
|
|
1863
|
+
|
|
1864
|
+
<i18n locale="en-GB" lang="json">
|
|
1865
|
+
{
|
|
1866
|
+
"assistantName": "Assistant name",
|
|
1867
|
+
"assistantNameHint": "Use this value as a starter label for your real setting.",
|
|
1868
|
+
"enabled": "Enable order prompts",
|
|
1869
|
+
"hint": "Hint",
|
|
1870
|
+
"note": "Default note",
|
|
1871
|
+
"owner": "Responsible team",
|
|
1872
|
+
"ownerDelivery": "Delivery team",
|
|
1873
|
+
"ownerSales": "Sales team",
|
|
1874
|
+
"save": "Save",
|
|
1875
|
+
"saved": "Settings saved",
|
|
1876
|
+
"subtitle": "Starter configuration page",
|
|
1877
|
+
"title": "Extension settings",
|
|
1878
|
+
"workspaceDescription": "Replace these sample controls with settings that drive your embedded page and order widget.",
|
|
1879
|
+
"workspaceTitle": "Order workflow defaults"
|
|
1880
|
+
}
|
|
1881
|
+
</i18n>
|
|
1882
|
+
|
|
1883
|
+
<i18n locale="es-ES" lang="json">
|
|
1884
|
+
{
|
|
1885
|
+
"assistantName": "Nombre del asistente",
|
|
1886
|
+
"assistantNameHint": "Use este valor como etiqueta inicial para su ajuste real.",
|
|
1887
|
+
"enabled": "Activar sugerencias del pedido",
|
|
1888
|
+
"hint": "Ayuda",
|
|
1889
|
+
"note": "Nota predeterminada",
|
|
1890
|
+
"owner": "Equipo responsable",
|
|
1891
|
+
"ownerDelivery": "Equipo de entrega",
|
|
1892
|
+
"ownerSales": "Equipo comercial",
|
|
1893
|
+
"save": "Guardar",
|
|
1894
|
+
"saved": "Configuracion guardada",
|
|
1895
|
+
"subtitle": "Pagina inicial de configuracion",
|
|
1896
|
+
"title": "Configuracion de la extension",
|
|
1897
|
+
"workspaceDescription": "Sustituya estos controles de ejemplo por ajustes que controlen su pagina integrada y el widget del pedido.",
|
|
1898
|
+
"workspaceTitle": "Valores iniciales del proceso de pedidos"
|
|
1899
|
+
}
|
|
1900
|
+
</i18n>
|
|
1901
|
+
|
|
1902
|
+
<i18n locale="ru-RU" lang="json">
|
|
1903
|
+
{
|
|
1904
|
+
"assistantName": "Название помощника",
|
|
1905
|
+
"assistantNameHint": "Используйте это значение как стартовую подпись для реальной настройки.",
|
|
1906
|
+
"enabled": "Включить подсказки в заказе",
|
|
1907
|
+
"hint": "Подсказка",
|
|
1908
|
+
"note": "Заметка по умолчанию",
|
|
1909
|
+
"owner": "Ответственная команда",
|
|
1910
|
+
"ownerDelivery": "Команда доставки",
|
|
1911
|
+
"ownerSales": "Отдел продаж",
|
|
1912
|
+
"save": "Сохранить",
|
|
1913
|
+
"saved": "Настройки сохранены",
|
|
1914
|
+
"subtitle": "Стартовая страница настроек",
|
|
1915
|
+
"title": "Настройки расширения",
|
|
1916
|
+
"workspaceDescription": "Замените эти демонстрационные контролы настройками, которые управляют встроенной страницей и виджетом заказа.",
|
|
1917
|
+
"workspaceTitle": "Настройки сценария заказа"
|
|
1918
|
+
}
|
|
1919
|
+
</i18n>
|
|
1920
|
+
|
|
1921
|
+
<style lang="less" module>
|
|
1922
|
+
@import (reference) '@retailcrm/embed-ui-v1-components/assets/stylesheets/geometry.less';
|
|
1923
|
+
@import (reference) '@retailcrm/embed-ui-v1-components/assets/stylesheets/layout.less';
|
|
1924
|
+
@import (reference) '@retailcrm/embed-ui-v1-components/assets/stylesheets/palette.less';
|
|
1925
|
+
@import (reference) '@retailcrm/embed-ui-v1-components/assets/stylesheets/typography.less';
|
|
1926
|
+
|
|
1927
|
+
.settings-page {
|
|
1928
|
+
display: grid;
|
|
1929
|
+
gap: @spacing-md;
|
|
1930
|
+
max-width: 960px;
|
|
1931
|
+
|
|
1932
|
+
@media (max-width: 720px) {
|
|
1933
|
+
gap: @spacing-sm;
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
.settings-page-addon {
|
|
1938
|
+
display: flex;
|
|
1939
|
+
align-items: center;
|
|
1940
|
+
gap: @spacing-xs;
|
|
1941
|
+
min-width: 0;
|
|
1942
|
+
|
|
1943
|
+
&__icon {
|
|
1944
|
+
.square(@line-height-regular);
|
|
1945
|
+
|
|
1946
|
+
flex: none;
|
|
1947
|
+
color: @blue-600;
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
&__subtitle {
|
|
1951
|
+
.ellipsis();
|
|
1952
|
+
.text-tiny(18px);
|
|
1953
|
+
|
|
1954
|
+
color: @grey-900;
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
.settings-form {
|
|
1959
|
+
display: grid;
|
|
1960
|
+
gap: @spacing-sm + @spacing-xxs;
|
|
1961
|
+
padding: @spacing-md;
|
|
1962
|
+
border: 1px solid @grey-500;
|
|
1963
|
+
border-radius: @border-radius-lg;
|
|
1964
|
+
background: white;
|
|
1965
|
+
|
|
1966
|
+
@media (max-width: 720px) {
|
|
1967
|
+
padding: @spacing-sm;
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
&__intro {
|
|
1971
|
+
display: grid;
|
|
1972
|
+
gap: @spacing-xxs + 2px;
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
&__heading {
|
|
1976
|
+
.h4-accent(24px);
|
|
1977
|
+
|
|
1978
|
+
margin: 0;
|
|
1979
|
+
color: @black-500;
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
&__description {
|
|
1983
|
+
.text-small();
|
|
1984
|
+
|
|
1985
|
+
max-width: 640px;
|
|
1986
|
+
margin: 0;
|
|
1987
|
+
color: @grey-900;
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
&__grid {
|
|
1991
|
+
display: grid;
|
|
1992
|
+
grid-template-columns: repeat(2, minmax(240px, 1fr));
|
|
1993
|
+
gap: (@spacing-sm + 2px) @spacing-md;
|
|
1994
|
+
|
|
1995
|
+
@media (max-width: 720px) {
|
|
1996
|
+
grid-template-columns: 1fr;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
&__note {
|
|
2001
|
+
grid-column: 1 / -1;
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
&__status {
|
|
2005
|
+
.text-tiny(18px);
|
|
2006
|
+
|
|
2007
|
+
margin: 0;
|
|
2008
|
+
color: @green-800;
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
</style>
|
|
2012
|
+
`;
|
|
2013
|
+
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';
|
|
2014
|
+
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";
|
|
2015
|
+
const toPosixRelative = (from, to) => {
|
|
2016
|
+
const relativePath = path.relative(from, to) || ".";
|
|
2017
|
+
return relativePath.split(path.sep).join("/");
|
|
2018
|
+
};
|
|
2019
|
+
const quoteJsString = (value) => `'${value.replace(/\\/gu, "\\\\").replace(/'/gu, "\\'")}'`;
|
|
2020
|
+
const replaceTemplateVars = (template, vars) => {
|
|
2021
|
+
let content = template;
|
|
2022
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
2023
|
+
content = content.replaceAll(`__${key}__`, value);
|
|
2024
|
+
}
|
|
2025
|
+
return content;
|
|
2026
|
+
};
|
|
2027
|
+
const readmeTemplates = {
|
|
2028
|
+
"en-GB": readmeEnGBTemplate,
|
|
2029
|
+
"es-ES": readmeEsESTemplate,
|
|
2030
|
+
"ru-RU": readmeRuRUTemplate
|
|
2031
|
+
};
|
|
2032
|
+
const normalizeLocale = (value) => {
|
|
2033
|
+
const normalized = value?.split(".")[0].replace("_", "-").toLowerCase();
|
|
2034
|
+
if (!normalized) {
|
|
2035
|
+
return null;
|
|
2036
|
+
}
|
|
2037
|
+
if (normalized.startsWith("ru")) {
|
|
2038
|
+
return "ru-RU";
|
|
2039
|
+
}
|
|
2040
|
+
if (normalized.startsWith("es")) {
|
|
2041
|
+
return "es-ES";
|
|
2042
|
+
}
|
|
2043
|
+
if (normalized.startsWith("en")) {
|
|
2044
|
+
return "en-GB";
|
|
2045
|
+
}
|
|
2046
|
+
return null;
|
|
2047
|
+
};
|
|
2048
|
+
const detectReadmeLocale = () => {
|
|
2049
|
+
const envCandidates = [
|
|
2050
|
+
process.env.LANGUAGE?.split(":")[0],
|
|
2051
|
+
process.env.LC_ALL,
|
|
2052
|
+
process.env.LC_MESSAGES,
|
|
2053
|
+
process.env.LANG
|
|
2054
|
+
];
|
|
2055
|
+
for (const candidate of envCandidates) {
|
|
2056
|
+
const locale = normalizeLocale(candidate);
|
|
2057
|
+
if (locale) {
|
|
2058
|
+
return locale;
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
return "en-GB";
|
|
2062
|
+
};
|
|
2063
|
+
const createPackageManagerRunCommand = (packageManager) => {
|
|
2064
|
+
if (packageManager === "npm") {
|
|
2065
|
+
return "npm run";
|
|
2066
|
+
}
|
|
2067
|
+
if (packageManager === "bun") {
|
|
2068
|
+
return "bun run";
|
|
2069
|
+
}
|
|
2070
|
+
return packageManager;
|
|
2071
|
+
};
|
|
2072
|
+
const createEnvDts = () => envDtsTemplate;
|
|
2073
|
+
const createTsConfig = (cwd, sourceRoot) => replaceTemplateVars(
|
|
2074
|
+
tsConfigTemplate,
|
|
2075
|
+
{
|
|
2076
|
+
SOURCE_ROOT_RELATIVE: toPosixRelative(cwd, sourceRoot)
|
|
2077
|
+
}
|
|
2078
|
+
);
|
|
2079
|
+
const createViteConfig = (cwd, sourceRoot) => replaceTemplateVars(
|
|
2080
|
+
viteConfigTemplate,
|
|
2081
|
+
{
|
|
2082
|
+
ENTRY_PATH: quoteJsString(toPosixRelative(cwd, path.join(sourceRoot, "endpoint/endpoint.worker.ts"))),
|
|
2083
|
+
LOCALE_INCLUDE_PATTERN: toPosixRelative(cwd, path.join(sourceRoot, "i18n/locales")),
|
|
2084
|
+
SOURCE_ROOT_PATH: quoteJsString(toPosixRelative(cwd, sourceRoot))
|
|
2085
|
+
}
|
|
2086
|
+
);
|
|
2087
|
+
const createEslintConfig = (cwd, sourceRoot) => replaceTemplateVars(
|
|
2088
|
+
eslintConfigTemplate,
|
|
2089
|
+
{
|
|
2090
|
+
LOCALE_DIR_PATTERN: `./${toPosixRelative(cwd, path.join(sourceRoot, "i18n/locales"))}/*.{json,json5,yaml,yml}`
|
|
2091
|
+
}
|
|
2092
|
+
);
|
|
2093
|
+
const createEndpointWorker = (options) => replaceTemplateVars(
|
|
2094
|
+
endpointWorkerTemplate,
|
|
2095
|
+
{
|
|
2096
|
+
PAGE_CODE: quoteJsString(options.pageCode),
|
|
2097
|
+
WIDGET_TARGET: quoteJsString(options.widgetTarget)
|
|
2098
|
+
}
|
|
2099
|
+
);
|
|
2100
|
+
const createI18nIndex = () => i18nIndexTemplate;
|
|
2101
|
+
const createSettingsPage = () => settingsPageTemplate;
|
|
2102
|
+
const createOrderWidget = () => orderWidgetTemplate;
|
|
2103
|
+
const createMessages = () => `${JSON.stringify({}, null, 2)}${DEFAULT_NEWLINE}`;
|
|
2104
|
+
const createExtensionConfig = (options) => `${JSON.stringify({
|
|
2105
|
+
code: "retailcrm-extension-frontend",
|
|
2106
|
+
name: "RetailCRM Extension Frontend",
|
|
2107
|
+
uuid: randomUUID(),
|
|
2108
|
+
version: "1.0.0",
|
|
2109
|
+
targets: [options.widgetTarget],
|
|
2110
|
+
pages: [options.pageCode],
|
|
2111
|
+
stylesheet: true,
|
|
2112
|
+
entrypointType: "script",
|
|
2113
|
+
runner: "worker"
|
|
2114
|
+
}, null, 2)}${DEFAULT_NEWLINE}`;
|
|
2115
|
+
const createExtensionIcon = () => extensionIconTemplate;
|
|
2116
|
+
const createPublishScript = () => publishScriptTemplate;
|
|
2117
|
+
const createReadme = (cwd, sourceRoot, options, packageManager) => replaceTemplateVars(
|
|
2118
|
+
readmeTemplates[detectReadmeLocale()],
|
|
2119
|
+
{
|
|
2120
|
+
PACKAGE_MANAGER: packageManager,
|
|
2121
|
+
PACKAGE_MANAGER_RUN: createPackageManagerRunCommand(packageManager),
|
|
2122
|
+
PAGE_CODE: options.pageCode,
|
|
2123
|
+
SOURCE_ROOT: toPosixRelative(cwd, sourceRoot),
|
|
2124
|
+
WIDGET_TARGET: options.widgetTarget
|
|
2125
|
+
}
|
|
2126
|
+
);
|
|
2127
|
+
const createInitChanges = () => ({
|
|
2128
|
+
preflight: [],
|
|
2129
|
+
packageJson: [],
|
|
2130
|
+
directories: [],
|
|
2131
|
+
files: [],
|
|
2132
|
+
agents: [],
|
|
2133
|
+
mcp: [],
|
|
2134
|
+
hooks: [],
|
|
2135
|
+
install: null,
|
|
2136
|
+
skipped: [],
|
|
2137
|
+
warnings: []
|
|
2138
|
+
});
|
|
2139
|
+
const printChanges = (changes) => {
|
|
2140
|
+
for (const change of changes) {
|
|
2141
|
+
let prefix;
|
|
2142
|
+
if (change.type === "script") {
|
|
2143
|
+
prefix = `scripts: ${change.name} -> ${change.nextRange}`;
|
|
2144
|
+
} else if (change.type === "field") {
|
|
2145
|
+
prefix = `${change.name} -> ${change.nextRange}`;
|
|
2146
|
+
} else {
|
|
2147
|
+
prefix = change.type === "install" ? `${change.section}: ${change.name} -> ${change.nextRange}` : `${change.section}: ${change.name} ${change.currentRange} -> ${change.nextRange}`;
|
|
2148
|
+
}
|
|
2149
|
+
console.log(` ${prefix}`);
|
|
2150
|
+
}
|
|
2151
|
+
};
|
|
2152
|
+
const printInitReport = (cwd, sourceRoot, version, packageManager, changes, options) => {
|
|
2153
|
+
console.log(`CWD: ${cwd}`);
|
|
2154
|
+
console.log(`Target: ${sourceRoot}`);
|
|
2155
|
+
console.log(`Resolved version: ${version}`);
|
|
2156
|
+
console.log(`Package manager: ${packageManager}`);
|
|
2157
|
+
if (changes.preflight.length > 0) {
|
|
2158
|
+
console.log("");
|
|
2159
|
+
console.log("preflight");
|
|
2160
|
+
for (const item of changes.preflight) {
|
|
2161
|
+
console.log(` ${item}`);
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
if (changes.packageJson.length > 0) {
|
|
2165
|
+
console.log("");
|
|
2166
|
+
console.log("package.json");
|
|
2167
|
+
printChanges(changes.packageJson);
|
|
2168
|
+
}
|
|
2169
|
+
if (changes.directories.length > 0) {
|
|
2170
|
+
console.log("");
|
|
2171
|
+
console.log("directories");
|
|
2172
|
+
for (const directoryPath of changes.directories) {
|
|
2173
|
+
console.log(` create ${directoryPath}`);
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
if (changes.files.length > 0) {
|
|
2177
|
+
console.log("");
|
|
2178
|
+
console.log("files");
|
|
2179
|
+
for (const filePath of changes.files) {
|
|
2180
|
+
console.log(` create ${filePath}`);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
if (changes.agents.length > 0) {
|
|
2184
|
+
console.log("");
|
|
2185
|
+
console.log("AGENTS.md");
|
|
2186
|
+
for (const agentChange of changes.agents) {
|
|
2187
|
+
console.log(` ${agentChange}`);
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
if (changes.mcp.length > 0) {
|
|
2191
|
+
console.log("");
|
|
2192
|
+
console.log("MCP");
|
|
2193
|
+
for (const mcpChange of changes.mcp) {
|
|
2194
|
+
console.log(` ${mcpChange}`);
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
if (changes.hooks.length > 0) {
|
|
2198
|
+
console.log("");
|
|
2199
|
+
console.log("package hooks");
|
|
2200
|
+
for (const hook of changes.hooks) {
|
|
2201
|
+
console.log(` ${options.dryRun ? "would run" : "ran"} ${hook}`);
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
if (changes.install) {
|
|
2205
|
+
console.log("");
|
|
2206
|
+
console.log("install");
|
|
2207
|
+
console.log(` ${changes.install}`);
|
|
2208
|
+
}
|
|
2209
|
+
if (changes.skipped.length > 0) {
|
|
2210
|
+
console.log("");
|
|
2211
|
+
console.log("skipped");
|
|
2212
|
+
for (const skipped of changes.skipped) {
|
|
2213
|
+
console.log(` ${skipped}`);
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
if (changes.warnings.length > 0) {
|
|
2217
|
+
console.log("");
|
|
2218
|
+
console.log("warnings");
|
|
2219
|
+
for (const warning of new Set(changes.warnings)) {
|
|
2220
|
+
console.log(` ${warning}`);
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
if (options.dryRun) {
|
|
2224
|
+
console.log("");
|
|
2225
|
+
console.log("Dry run enabled, no files were modified.");
|
|
2226
|
+
}
|
|
2227
|
+
};
|
|
2228
|
+
const INIT_PACKAGE_IDS = new Set(DEFAULT_INIT_PACKAGE_IDS);
|
|
2229
|
+
const resolveDefaultSourceRoot = (cwd, options) => {
|
|
2230
|
+
if (options.srcDir) {
|
|
2231
|
+
return options.srcDir;
|
|
2232
|
+
}
|
|
2233
|
+
if (options.target) {
|
|
2234
|
+
return options.target;
|
|
2235
|
+
}
|
|
2236
|
+
return fs.existsSync(path.join(cwd, "src")) ? "./web" : "./src";
|
|
2237
|
+
};
|
|
2238
|
+
const normalizeOptionalAnswer = (value) => {
|
|
2239
|
+
const trimmed = value.trim();
|
|
2240
|
+
return trimmed.length ? trimmed : null;
|
|
2241
|
+
};
|
|
2242
|
+
const askString = async (readline, question, defaultValue) => {
|
|
2243
|
+
const answer = normalizeOptionalAnswer(await readline.question(`${question} [${defaultValue}]: `));
|
|
2244
|
+
return answer ?? defaultValue;
|
|
2245
|
+
};
|
|
2246
|
+
const askBoolean = async (readline, question, defaultValue) => {
|
|
2247
|
+
const suffix = "Y/n";
|
|
2248
|
+
while (true) {
|
|
2249
|
+
const answer = normalizeOptionalAnswer(await readline.question(`${question} [${suffix}]: `));
|
|
2250
|
+
if (answer === null) {
|
|
2251
|
+
return defaultValue;
|
|
2252
|
+
}
|
|
2253
|
+
if (/^(y|yes|д|да)$/iu.test(answer)) {
|
|
2254
|
+
return true;
|
|
2255
|
+
}
|
|
2256
|
+
if (/^(n|no|н|нет)$/iu.test(answer)) {
|
|
2257
|
+
return false;
|
|
2258
|
+
}
|
|
2259
|
+
console.error("Введите yes/no или нажмите Enter для значения по умолчанию.");
|
|
2260
|
+
}
|
|
2261
|
+
};
|
|
2262
|
+
const askPackageManager = async (readline, detectedPackageManager, explicitPackageManager) => {
|
|
2263
|
+
if (explicitPackageManager) {
|
|
2264
|
+
return explicitPackageManager;
|
|
2265
|
+
}
|
|
2266
|
+
const defaultPackageManager = detectedPackageManager ?? "npm";
|
|
2267
|
+
while (true) {
|
|
2268
|
+
const answer = await askString(
|
|
2269
|
+
readline,
|
|
2270
|
+
`Package manager (${PACKAGE_MANAGERS.join("/")})`,
|
|
2271
|
+
defaultPackageManager
|
|
2272
|
+
);
|
|
2273
|
+
if (PACKAGE_MANAGERS.includes(answer)) {
|
|
2274
|
+
return answer;
|
|
2275
|
+
}
|
|
2276
|
+
console.error(`Unknown package manager: ${answer}`);
|
|
2277
|
+
}
|
|
2278
|
+
};
|
|
2279
|
+
const askPackages = async (readline, options) => {
|
|
2280
|
+
if (options.packages) {
|
|
2281
|
+
return options.packages;
|
|
2282
|
+
}
|
|
2283
|
+
const defaultPackageIds = [...DEFAULT_INIT_PACKAGE_IDS, ...options.with ?? []];
|
|
2284
|
+
console.log("Пакеты для init:");
|
|
2285
|
+
for (const selectedPackage of INSTALLABLE_PACKAGES.filter((entry) => INIT_PACKAGE_IDS.has(entry.id))) {
|
|
2286
|
+
console.log(` - ${selectedPackage.id}: ${selectedPackage.name}`);
|
|
2287
|
+
console.log(` ${selectedPackage.description}`);
|
|
2288
|
+
}
|
|
2289
|
+
while (true) {
|
|
2290
|
+
const answer = await askString(
|
|
2291
|
+
readline,
|
|
2292
|
+
"Пакеты через запятую",
|
|
2293
|
+
defaultPackageIds.join(",")
|
|
2294
|
+
);
|
|
2295
|
+
const tokens = parsePackageList(answer);
|
|
2296
|
+
try {
|
|
2297
|
+
const selectedPackages = resolveInstallPackages(tokens);
|
|
2298
|
+
if (selectedPackages.some((selectedPackage) => selectedPackage.id === "testing")) {
|
|
2299
|
+
throw new Error("@retailcrm/embed-ui-v1-testing is not published for public init yet");
|
|
2300
|
+
}
|
|
2301
|
+
return tokens;
|
|
2302
|
+
} catch (error) {
|
|
2303
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
};
|
|
2307
|
+
const resolveInteractiveInitOptions = async (cwd, options, detectedPackageManager) => {
|
|
2308
|
+
if (!options.interactive) {
|
|
2309
|
+
return options;
|
|
2310
|
+
}
|
|
2311
|
+
if (!process$1.stdin.isTTY || !process$1.stdout.isTTY) {
|
|
2312
|
+
throw new Error("Interactive init mode requires a TTY. Use explicit flags or omit --interactive.");
|
|
2313
|
+
}
|
|
2314
|
+
const readline = createInterface({
|
|
2315
|
+
input: process$1.stdin,
|
|
2316
|
+
output: process$1.stdout
|
|
2317
|
+
});
|
|
2318
|
+
try {
|
|
2319
|
+
const nextOptions = { ...options };
|
|
2320
|
+
if (!nextOptions.agentsOnly) {
|
|
2321
|
+
const defaultSourceRoot = resolveDefaultSourceRoot(cwd, nextOptions);
|
|
2322
|
+
const sourceRoot = await askString(readline, "Frontend source root", defaultSourceRoot);
|
|
2323
|
+
if (nextOptions.srcDir) {
|
|
2324
|
+
nextOptions.srcDir = sourceRoot;
|
|
2325
|
+
} else {
|
|
2326
|
+
nextOptions.target = sourceRoot;
|
|
2327
|
+
}
|
|
2328
|
+
nextOptions.packages = await askPackages(readline, nextOptions);
|
|
2329
|
+
nextOptions.noConfigs = nextOptions.noConfigs || !await askBoolean(readline, "Создать базовые конфиги", true);
|
|
2330
|
+
nextOptions.noTemplate = nextOptions.noTemplate || !await askBoolean(readline, "Создать стартовый шаблон", true);
|
|
2331
|
+
}
|
|
2332
|
+
nextOptions.packageManager = await askPackageManager(
|
|
2333
|
+
readline,
|
|
2334
|
+
detectedPackageManager,
|
|
2335
|
+
nextOptions.packageManager
|
|
2336
|
+
);
|
|
2337
|
+
nextOptions.noAgents = nextOptions.noAgents || !await askBoolean(readline, "Обновить AGENTS.md", true);
|
|
2338
|
+
nextOptions.noMcp = nextOptions.noMcp || !await askBoolean(readline, "Добавить MCP-настройки", true);
|
|
2339
|
+
if (!nextOptions.agentsOnly) {
|
|
2340
|
+
nextOptions.noInstall = nextOptions.noInstall || !await askBoolean(readline, "Запустить установку зависимостей", true);
|
|
2341
|
+
}
|
|
2342
|
+
return nextOptions;
|
|
2343
|
+
} finally {
|
|
2344
|
+
readline.close();
|
|
2345
|
+
}
|
|
2346
|
+
};
|
|
2347
|
+
const DEFAULT_INIT_DIRS = ["endpoint", "pages", "widgets", "shared", "i18n"];
|
|
2348
|
+
const detectPackageManagerByLockfile = (cwd) => {
|
|
2349
|
+
const knownLockfiles = [
|
|
2350
|
+
{ packageManager: "yarn", file: "yarn.lock" },
|
|
2351
|
+
{ packageManager: "npm", file: "package-lock.json" },
|
|
2352
|
+
{ packageManager: "pnpm", file: "pnpm-lock.yaml" },
|
|
2353
|
+
{ packageManager: "bun", file: "bun.lockb" }
|
|
2354
|
+
];
|
|
2355
|
+
const lockfiles = knownLockfiles.filter(({ file }) => fs.existsSync(path.join(cwd, file)));
|
|
2356
|
+
return lockfiles.length === 1 ? lockfiles[0].packageManager : null;
|
|
2357
|
+
};
|
|
2358
|
+
const resolvePackageManagerVersion = (packageManager) => {
|
|
2359
|
+
try {
|
|
2360
|
+
return execFileSync(packageManager, ["--version"], {
|
|
2361
|
+
encoding: "utf8",
|
|
2362
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2363
|
+
}).trim();
|
|
2364
|
+
} catch {
|
|
2365
|
+
return null;
|
|
2366
|
+
}
|
|
2367
|
+
};
|
|
2368
|
+
const promptForPackageManager = async () => {
|
|
2369
|
+
const readline = createInterface({
|
|
2370
|
+
input: process$1.stdin,
|
|
2371
|
+
output: process$1.stdout
|
|
2372
|
+
});
|
|
2373
|
+
try {
|
|
2374
|
+
while (true) {
|
|
2375
|
+
const answer = await readline.question("Choose package manager (yarn/npm/pnpm/bun): ");
|
|
2376
|
+
if (PACKAGE_MANAGERS.includes(answer)) {
|
|
2377
|
+
return answer;
|
|
2378
|
+
}
|
|
2379
|
+
console.error(`Unknown package manager: ${answer}`);
|
|
2380
|
+
}
|
|
2381
|
+
} finally {
|
|
2382
|
+
readline.close();
|
|
2383
|
+
}
|
|
2384
|
+
};
|
|
2385
|
+
const resolvePackageManager = async (cwd, explicitPackageManager) => {
|
|
2386
|
+
if (explicitPackageManager) {
|
|
2387
|
+
if (!PACKAGE_MANAGERS.includes(explicitPackageManager)) {
|
|
2388
|
+
throw new Error(`Unknown package manager: ${explicitPackageManager}`);
|
|
2389
|
+
}
|
|
2390
|
+
return explicitPackageManager;
|
|
2391
|
+
}
|
|
2392
|
+
const packageManager = detectPackageManagerByLockfile(cwd);
|
|
2393
|
+
if (packageManager) {
|
|
2394
|
+
return packageManager;
|
|
2395
|
+
}
|
|
2396
|
+
if (process$1.stdin.isTTY && process$1.stdout.isTTY) {
|
|
2397
|
+
return promptForPackageManager();
|
|
2398
|
+
}
|
|
2399
|
+
return "npm";
|
|
2400
|
+
};
|
|
2401
|
+
const resolveInitPackages = (tokens, extraTokens) => {
|
|
2402
|
+
const packageIds = tokens ?? [...DEFAULT_INIT_PACKAGE_IDS, ...extraTokens ?? []];
|
|
2403
|
+
const packages = resolveInstallPackages(packageIds);
|
|
2404
|
+
for (const selectedPackage of packages) {
|
|
2405
|
+
if (selectedPackage.id === "testing") {
|
|
2406
|
+
throw new Error("@retailcrm/embed-ui-v1-testing is not published for public init yet");
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
return packages;
|
|
2410
|
+
};
|
|
2411
|
+
const resolveInitCwd = (options) => {
|
|
2412
|
+
const cwd = path.resolve(options.cwd);
|
|
2413
|
+
if (!fs.existsSync(cwd)) {
|
|
2414
|
+
throw new Error(`cwd does not exist: ${cwd}`);
|
|
2415
|
+
}
|
|
2416
|
+
if (!fs.statSync(cwd).isDirectory()) {
|
|
2417
|
+
throw new Error(`cwd is not a directory: ${cwd}`);
|
|
2418
|
+
}
|
|
2419
|
+
return cwd;
|
|
2420
|
+
};
|
|
2421
|
+
const resolveInitSourceRoot = (cwd, options) => {
|
|
2422
|
+
if (options.srcDir) {
|
|
2423
|
+
return path.resolve(cwd, options.srcDir);
|
|
2424
|
+
}
|
|
2425
|
+
if (options.target) {
|
|
2426
|
+
return path.resolve(cwd, options.target);
|
|
2427
|
+
}
|
|
2428
|
+
const srcPath = path.join(cwd, "src");
|
|
2429
|
+
if (!fs.existsSync(srcPath)) {
|
|
2430
|
+
return srcPath;
|
|
2431
|
+
}
|
|
2432
|
+
return path.join(cwd, "web");
|
|
2433
|
+
};
|
|
2434
|
+
const runUpdate = (options) => {
|
|
2435
|
+
const version = options.version ?? resolveLatestVersion();
|
|
2436
|
+
const packageJsonPaths = collectPackageJsonPaths(options.target);
|
|
2437
|
+
const reports = [];
|
|
2438
|
+
for (const packageJsonPath of packageJsonPaths) {
|
|
2439
|
+
const { formatting, packageJson } = readPackageJson(packageJsonPath);
|
|
2440
|
+
const updates = updatePackageJson(packageJson, version, options.exact);
|
|
2441
|
+
if (updates.length === 0) {
|
|
2442
|
+
continue;
|
|
2443
|
+
}
|
|
2444
|
+
if (!options.dryRun) {
|
|
2445
|
+
writePackageJson(packageJsonPath, packageJson, formatting);
|
|
2446
|
+
}
|
|
2447
|
+
reports.push({ packageJsonPath, updates });
|
|
2448
|
+
}
|
|
2449
|
+
if (reports.length === 0) {
|
|
2450
|
+
console.log(`No ${ROOT_PACKAGE}* dependencies found or changed under ${path.resolve(options.target)}`);
|
|
2451
|
+
return;
|
|
2452
|
+
}
|
|
2453
|
+
const totalUpdates = reports.reduce((sum, report) => sum + report.updates.length, 0);
|
|
2454
|
+
console.log(`Resolved version: ${version}`);
|
|
2455
|
+
for (const report of reports) {
|
|
2456
|
+
console.log(report.packageJsonPath);
|
|
2457
|
+
printChanges(report.updates);
|
|
2458
|
+
}
|
|
2459
|
+
if (options.dryRun) {
|
|
2460
|
+
console.log("Dry run enabled, package.json files were not modified");
|
|
2461
|
+
return;
|
|
2462
|
+
}
|
|
2463
|
+
console.log(
|
|
2464
|
+
`Updated ${totalUpdates} dependency entries in ${reports.length} package.json file(s) under ${path.resolve(options.target)}`
|
|
2465
|
+
);
|
|
2466
|
+
};
|
|
2467
|
+
const runAdd = async (options) => {
|
|
2468
|
+
const version = options.version ?? resolveLatestVersion();
|
|
2469
|
+
const packageJsonPath = resolvePackageJsonPath(path.resolve(options.target));
|
|
2470
|
+
const { formatting, packageJson } = readPackageJson(packageJsonPath);
|
|
2471
|
+
const selectedPackages = options.packages ? resolveInstallPackages(options.packages) : await promptForInstallSelection(packageJson);
|
|
2472
|
+
if (selectedPackages.length === 0) {
|
|
2473
|
+
console.log("Nothing selected, package.json was not modified");
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
const updates = installPackages(packageJson, selectedPackages, version, options.exact);
|
|
2477
|
+
if (updates.length === 0) {
|
|
2478
|
+
console.log(`Selected packages are already installed with matching ranges in ${packageJsonPath}`);
|
|
2479
|
+
return;
|
|
2480
|
+
}
|
|
2481
|
+
console.log(`Resolved version: ${version}`);
|
|
2482
|
+
console.log(packageJsonPath);
|
|
2483
|
+
printChanges(updates);
|
|
2484
|
+
if (options.dryRun) {
|
|
2485
|
+
console.log("Dry run enabled, package.json was not modified");
|
|
2486
|
+
return;
|
|
2487
|
+
}
|
|
2488
|
+
writePackageJson(packageJsonPath, packageJson, formatting);
|
|
2489
|
+
console.log(`Installed ${updates.length} package entries in ${packageJsonPath}`);
|
|
2490
|
+
};
|
|
2491
|
+
const applyInitPackageJson = (cwd, selectedPackages, version, packageManager, options, changes) => {
|
|
2492
|
+
const packageJsonPath = path.join(cwd, "package.json");
|
|
2493
|
+
const {
|
|
2494
|
+
created,
|
|
2495
|
+
formatting,
|
|
2496
|
+
packageJson
|
|
2497
|
+
} = readOrCreatePackageJson(packageJsonPath);
|
|
2498
|
+
if (!packageJson.type) {
|
|
2499
|
+
packageJson.type = "module";
|
|
2500
|
+
changes.packageJson.push({ type: "field", name: "type", nextRange: "module" });
|
|
2501
|
+
} else if (packageJson.type !== "module") {
|
|
2502
|
+
changes.warnings.push('package.json already has type field and it is not "module"');
|
|
2503
|
+
}
|
|
2504
|
+
setMissingScript(packageJson, "build", "vite build", changes);
|
|
2505
|
+
setMissingScript(packageJson, "eslint", "eslint .", changes);
|
|
2506
|
+
setMissingScript(packageJson, "eslint:fix", "eslint --fix .", changes);
|
|
2507
|
+
setMissingScript(packageJson, "publish-extension", "node scripts/publish-extension.mjs", changes);
|
|
2508
|
+
for (const selectedPackage of selectedPackages) {
|
|
2509
|
+
setDependency(
|
|
2510
|
+
packageJson,
|
|
2511
|
+
selectedPackage.section,
|
|
2512
|
+
selectedPackage.name,
|
|
2513
|
+
createRange(version, options.exact),
|
|
2514
|
+
changes,
|
|
2515
|
+
options
|
|
2516
|
+
);
|
|
2517
|
+
}
|
|
2518
|
+
for (const dependency of INIT_RUNTIME_DEPENDENCIES) {
|
|
2519
|
+
if (dependency.name === I18N_RUNTIME_DEPENDENCY && hasExistingDependency(packageJson, dependency.name)) {
|
|
2520
|
+
changes.skipped.push(`${dependency.name} already exists; i18n dependency setup skipped to avoid conflicts with existing project configuration`);
|
|
2521
|
+
continue;
|
|
2522
|
+
}
|
|
2523
|
+
setDependency(packageJson, "dependencies", dependency.name, dependency.range, changes, options);
|
|
2524
|
+
}
|
|
2525
|
+
for (const dependency of INIT_DEV_DEPENDENCIES) {
|
|
2526
|
+
setDependency(packageJson, "devDependencies", dependency.name, dependency.range, changes, options);
|
|
2527
|
+
}
|
|
2528
|
+
if (!packageJson.packageManager && options.packageManager) {
|
|
2529
|
+
const packageManagerVersion = resolvePackageManagerVersion(packageManager);
|
|
2530
|
+
if (packageManagerVersion) {
|
|
2531
|
+
const nextPackageManager = `${packageManager}@${packageManagerVersion}`;
|
|
2532
|
+
packageJson.packageManager = nextPackageManager;
|
|
2533
|
+
changes.packageJson.push({
|
|
2534
|
+
type: "field",
|
|
2535
|
+
name: "packageManager",
|
|
2536
|
+
nextRange: nextPackageManager
|
|
2537
|
+
});
|
|
2538
|
+
} else {
|
|
2539
|
+
changes.warnings.push(`Cannot resolve ${packageManager} version; packageManager field was not written`);
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
if (!options.dryRun && (created || changes.packageJson.length > 0)) {
|
|
2543
|
+
writePackageJson(packageJsonPath, packageJson, formatting);
|
|
2544
|
+
}
|
|
2545
|
+
return packageJsonPath;
|
|
2546
|
+
};
|
|
2547
|
+
const applyInitDirectories = (sourceRoot, options, changes) => {
|
|
2548
|
+
if (options.noDirs || options.agentsOnly) {
|
|
2549
|
+
return;
|
|
2550
|
+
}
|
|
2551
|
+
const dirs = options.dirs ?? DEFAULT_INIT_DIRS;
|
|
2552
|
+
const unknownDir = dirs.find((dir) => ![...DEFAULT_INIT_DIRS, "src", "tests"].includes(dir));
|
|
2553
|
+
if (unknownDir) {
|
|
2554
|
+
throw new Error(`Unknown directory preset: ${unknownDir}`);
|
|
2555
|
+
}
|
|
2556
|
+
ensureDirectory(sourceRoot, options, changes);
|
|
2557
|
+
for (const dir of dirs) {
|
|
2558
|
+
if (dir === "src") {
|
|
2559
|
+
continue;
|
|
2560
|
+
}
|
|
2561
|
+
ensureDirectory(path.join(sourceRoot, dir), options, changes);
|
|
2562
|
+
}
|
|
2563
|
+
if (dirs.includes("i18n")) {
|
|
2564
|
+
ensureDirectory(path.join(sourceRoot, "i18n/locales"), options, changes);
|
|
2565
|
+
}
|
|
2566
|
+
};
|
|
2567
|
+
const applyInitConfigs = (cwd, sourceRoot, options, changes) => {
|
|
2568
|
+
if (options.noConfigs || options.agentsOnly) {
|
|
2569
|
+
return;
|
|
2570
|
+
}
|
|
2571
|
+
writeFileIfAllowed(path.join(cwd, "tsconfig.json"), createTsConfig(cwd, sourceRoot), options, changes);
|
|
2572
|
+
writeFileIfAllowed(path.join(cwd, "vite.config.ts"), createViteConfig(cwd, sourceRoot), options, changes);
|
|
2573
|
+
writeFileIfAllowed(path.join(cwd, "env.d.ts"), createEnvDts(), options, changes);
|
|
2574
|
+
writeFileIfAllowed(path.join(cwd, "eslint.config.js"), createEslintConfig(cwd, sourceRoot), options, changes);
|
|
2575
|
+
};
|
|
2576
|
+
const applyInitTemplate = (cwd, sourceRoot, packageManager, options, changes) => {
|
|
2577
|
+
if (options.noTemplate || options.agentsOnly) {
|
|
2578
|
+
return;
|
|
2579
|
+
}
|
|
2580
|
+
if (options.template !== "order-card") {
|
|
2581
|
+
throw new Error(`Unknown template: ${options.template}`);
|
|
2582
|
+
}
|
|
2583
|
+
writeFileIfAllowed(
|
|
2584
|
+
path.join(sourceRoot, "endpoint/endpoint.worker.ts"),
|
|
2585
|
+
createEndpointWorker(options),
|
|
2586
|
+
options,
|
|
2587
|
+
changes
|
|
2588
|
+
);
|
|
2589
|
+
writeFileIfAllowed(path.join(sourceRoot, "shared/assets/extension.svg"), createExtensionIcon(), options, changes);
|
|
2590
|
+
writeFileIfAllowed(path.join(sourceRoot, "i18n/index.ts"), createI18nIndex(), options, changes);
|
|
2591
|
+
writeFileIfAllowed(path.join(sourceRoot, "i18n/locales/en-GB.json"), createMessages(), options, changes);
|
|
2592
|
+
writeFileIfAllowed(path.join(sourceRoot, "i18n/locales/es-ES.json"), createMessages(), options, changes);
|
|
2593
|
+
writeFileIfAllowed(path.join(sourceRoot, "i18n/locales/ru-RU.json"), createMessages(), options, changes);
|
|
2594
|
+
writeFileIfAllowed(path.join(sourceRoot, "pages/SettingsPage.vue"), createSettingsPage(), options, changes);
|
|
2595
|
+
writeFileIfAllowed(path.join(sourceRoot, "widgets/OrderCommonAfterWidget.vue"), createOrderWidget(), options, changes);
|
|
2596
|
+
writeFileIfAllowed(path.join(cwd, "extensionrc.json"), createExtensionConfig(options), options, changes);
|
|
2597
|
+
writeFileIfAllowed(path.join(cwd, "scripts/publish-extension.mjs"), createPublishScript(), options, changes);
|
|
2598
|
+
writeFileIfAllowed(path.join(cwd, "README.md"), createReadme(cwd, sourceRoot, options, packageManager), options, changes);
|
|
2599
|
+
};
|
|
2600
|
+
const runInstall = (cwd, packageManager, options, changes, packageJsonChanged) => {
|
|
2601
|
+
if (options.noInstall || options.agentsOnly) {
|
|
2602
|
+
return;
|
|
2603
|
+
}
|
|
2604
|
+
if (!packageJsonChanged && !options.force) {
|
|
2605
|
+
changes.skipped.push("install skipped because package.json was not changed");
|
|
2606
|
+
return;
|
|
2607
|
+
}
|
|
2608
|
+
const args = ["install"];
|
|
2609
|
+
changes.install = `${packageManager} ${args.join(" ")}`;
|
|
2610
|
+
if (options.dryRun) {
|
|
2611
|
+
return;
|
|
2612
|
+
}
|
|
2613
|
+
execFileSync(packageManager, args, {
|
|
2614
|
+
cwd,
|
|
2615
|
+
stdio: "inherit"
|
|
2616
|
+
});
|
|
2617
|
+
};
|
|
2618
|
+
const runInit = async (options) => {
|
|
2619
|
+
const cwd = resolveInitCwd(options);
|
|
2620
|
+
const interactiveOptions = await resolveInteractiveInitOptions(
|
|
2621
|
+
cwd,
|
|
2622
|
+
options,
|
|
2623
|
+
detectPackageManagerByLockfile(cwd)
|
|
2624
|
+
);
|
|
2625
|
+
const sourceRoot = resolveInitSourceRoot(cwd, interactiveOptions);
|
|
2626
|
+
if (fs.existsSync(sourceRoot) && !fs.statSync(sourceRoot).isDirectory()) {
|
|
2627
|
+
throw new Error(`Target path is not a directory: ${sourceRoot}`);
|
|
2628
|
+
}
|
|
2629
|
+
const selectedPackages = resolveInitPackages(interactiveOptions.packages, interactiveOptions.with);
|
|
2630
|
+
const version = interactiveOptions.agentsOnly ? interactiveOptions.version ?? "not used" : interactiveOptions.version ?? resolveLatestVersion();
|
|
2631
|
+
const packageManager = interactiveOptions.agentsOnly ? interactiveOptions.packageManager ?? detectPackageManagerByLockfile(cwd) ?? "npm" : await resolvePackageManager(cwd, interactiveOptions.packageManager);
|
|
2632
|
+
const changes = createInitChanges();
|
|
2633
|
+
applyInitPreflight(cwd, sourceRoot, packageManager, selectedPackages, version, interactiveOptions, changes);
|
|
2634
|
+
let packageJsonPath = null;
|
|
2635
|
+
if (!interactiveOptions.agentsOnly) {
|
|
2636
|
+
packageJsonPath = applyInitPackageJson(cwd, selectedPackages, version, packageManager, interactiveOptions, changes);
|
|
2637
|
+
applyInitDirectories(sourceRoot, interactiveOptions, changes);
|
|
2638
|
+
applyInitConfigs(cwd, sourceRoot, interactiveOptions, changes);
|
|
2639
|
+
applyInitTemplate(cwd, sourceRoot, packageManager, interactiveOptions, changes);
|
|
2640
|
+
applyInitPackageConfigHooks(cwd, selectedPackages, packageManager, interactiveOptions, changes);
|
|
2641
|
+
}
|
|
2642
|
+
applyInitAgents(cwd, selectedPackages, packageManager, interactiveOptions, changes);
|
|
2643
|
+
runInstall(cwd, packageManager, interactiveOptions, changes, Boolean(packageJsonPath && changes.packageJson.length > 0));
|
|
2644
|
+
printInitReport(cwd, sourceRoot, version, packageManager, changes, interactiveOptions);
|
|
2645
|
+
};
|
|
2646
|
+
const main = async (argv = process$1.argv.slice(2)) => {
|
|
2647
|
+
const options = parseArgs(argv);
|
|
2648
|
+
if (options.command === "init") {
|
|
2649
|
+
await runInit(options);
|
|
2650
|
+
return;
|
|
2651
|
+
}
|
|
2652
|
+
if (options.add) {
|
|
2653
|
+
await runAdd(options);
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
runUpdate(options);
|
|
2657
|
+
};
|
|
2658
|
+
const isExecutedDirectly = () => {
|
|
2659
|
+
const entryPath = process$1.argv[1];
|
|
2660
|
+
if (!entryPath) {
|
|
2661
|
+
return false;
|
|
2662
|
+
}
|
|
2663
|
+
return pathToFileURL(path.resolve(entryPath)).href === import.meta.url;
|
|
2664
|
+
};
|
|
2665
|
+
if (isExecutedDirectly()) {
|
|
2666
|
+
try {
|
|
2667
|
+
await main();
|
|
2668
|
+
} catch (error) {
|
|
2669
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2670
|
+
console.error(message);
|
|
2671
|
+
process$1.exit(1);
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
export {
|
|
2675
|
+
main,
|
|
2676
|
+
parseArgs,
|
|
2677
|
+
parseInitArgs,
|
|
2678
|
+
runAdd,
|
|
2679
|
+
runInit,
|
|
2680
|
+
runUpdate
|
|
2681
|
+
};
|