elcrm 1.1.1 → 1.1.2
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/README.md +1 -0
- package/dist/index.js +2477 -922
- package/dist/vite/discover-lib.js +4 -4
- package/dist/vite/plugin-css-scoped.js +2 -2
- package/package.json +2 -2
- package/templates/elcrm-cursor/AGENTS.md +13 -0
- package/templates/elcrm-cursor/rules/elcrm-lib.mdc +101 -0
- package/templates/elcrm-cursor/rules/elcrm-module.mdc +25 -0
- package/templates/elcrm-cursor/rules/elcrm-packages.mdc +43 -0
- package/templates/elcrm-cursor/rules/elcrm-rpc.mdc +23 -0
- package/templates/elcrm-cursor/rules/elcrm-server.mdc +30 -0
- package/templates/elcrm-cursor/rules/elcrm-theme.mdc +13 -0
- package/templates/elcrm-cursor/rules/elcrm-ui.mdc +41 -0
- package/templates/elcrm-cursor/rules/elcrm.mdc +43 -0
- package/templates/elcrm-docs/ALERT.elCRM.md +26 -0
- package/templates/elcrm-docs/API.elCRM.md +38 -0
- package/templates/elcrm-docs/BUTTON.elCRM.md +36 -0
- package/templates/elcrm-docs/CLI.elCRM.md +27 -0
- package/templates/elcrm-docs/COMPONENTS.elCRM.md +68 -0
- package/templates/elcrm-docs/FORM.elCRM.md +39 -0
- package/templates/elcrm-docs/ICONS.elCRM.md +40 -0
- package/templates/elcrm-docs/MODAL.elCRM.md +41 -0
- package/templates/elcrm-docs/NOTICE.elCRM.md +31 -0
- package/templates/elcrm-docs/NOTIFICATIONS.elCRM.md +39 -0
- package/templates/elcrm-docs/PASSKEY.elCRM.md +26 -0
- package/templates/elcrm-docs/ROUTER.elCRM.md +34 -0
- package/templates/elcrm-docs/SERVER.elCRM.md +53 -0
- package/templates/elcrm-docs/SOCKET.elCRM.md +34 -0
- package/templates/elcrm-docs/TOOLTIP.elCRM.md +25 -0
- package/templates/panel/web/src/style/theme-dark.css +39 -3
- package/templates/panel/web/src/style/theme-light.css +39 -3
- package/templates/panel/web/src/style/theme.css +51 -3
package/dist/index.js
CHANGED
|
@@ -1,103 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8
|
-
|
|
9
|
-
// src/parseArgs.ts
|
|
10
|
-
function parseArgs(argv = process.argv.slice(2)) {
|
|
11
|
-
const options = {};
|
|
12
|
-
const flags = [];
|
|
13
|
-
const positionals = [];
|
|
14
|
-
for (let i = 0;i < argv.length; i++) {
|
|
15
|
-
const arg = argv[i];
|
|
16
|
-
if (arg === "--") {
|
|
17
|
-
positionals.push(...argv.slice(i + 1));
|
|
18
|
-
break;
|
|
19
|
-
}
|
|
20
|
-
if (arg.startsWith("--")) {
|
|
21
|
-
const body = arg.slice(2);
|
|
22
|
-
if (body.includes("=")) {
|
|
23
|
-
const eq = body.indexOf("=");
|
|
24
|
-
const key = body.slice(0, eq);
|
|
25
|
-
const value = body.slice(eq + 1);
|
|
26
|
-
if (key)
|
|
27
|
-
options[key] = value;
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
const next = argv[i + 1];
|
|
31
|
-
if (next && !next.startsWith("-")) {
|
|
32
|
-
const valueOpts = new Set([
|
|
33
|
-
"template",
|
|
34
|
-
"url",
|
|
35
|
-
"name",
|
|
36
|
-
"dir",
|
|
37
|
-
"folder",
|
|
38
|
-
"out"
|
|
39
|
-
]);
|
|
40
|
-
if (valueOpts.has(body)) {
|
|
41
|
-
options[body] = next;
|
|
42
|
-
i++;
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
if (body && !flags.includes(body))
|
|
47
|
-
flags.push(body);
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
if (arg.startsWith("-") && arg.length > 1) {
|
|
51
|
-
const short = arg.slice(1);
|
|
52
|
-
if (short === "t" && argv[i + 1] && !argv[i + 1].startsWith("-")) {
|
|
53
|
-
options.template = argv[i + 1];
|
|
54
|
-
i++;
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
if (short && !flags.includes(short))
|
|
58
|
-
flags.push(short);
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
positionals.push(arg);
|
|
62
|
-
}
|
|
63
|
-
const [command = "", ...rest] = positionals;
|
|
64
|
-
return {
|
|
65
|
-
command,
|
|
66
|
-
positionals: rest,
|
|
67
|
-
options,
|
|
68
|
-
flags
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
function hasFlag(parsed, ...names) {
|
|
72
|
-
return names.some((n) => parsed.flags.includes(n));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// src/lib/elcrmDeps.ts
|
|
76
|
-
var SECTIONS = [
|
|
77
|
-
"dependencies",
|
|
78
|
-
"devDependencies",
|
|
79
|
-
"peerDependencies"
|
|
80
|
-
];
|
|
81
|
-
function listElcrmDeps(pkg) {
|
|
82
|
-
const out = [];
|
|
83
|
-
for (const section of SECTIONS) {
|
|
84
|
-
const map = pkg[section];
|
|
85
|
-
if (!map || typeof map !== "object")
|
|
86
|
-
continue;
|
|
87
|
-
for (const [name, version] of Object.entries(map)) {
|
|
88
|
-
if (name.startsWith("@elcrm/") && typeof version === "string") {
|
|
89
|
-
out.push({ name, version, section });
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return out;
|
|
94
|
-
}
|
|
95
|
-
function uniqueElcrmNames(deps) {
|
|
96
|
-
return [...new Set(deps.map((d) => d.name))].sort();
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __returnValue = (v) => v;
|
|
5
|
+
function __exportSetter(name, newValue) {
|
|
6
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
97
7
|
}
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, {
|
|
11
|
+
get: all[name],
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
set: __exportSetter.bind(all, name)
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
98
18
|
|
|
99
19
|
// src/lib/latest.ts
|
|
100
|
-
var CACHE = new Map;
|
|
101
20
|
async function fetchLatestVersion(name) {
|
|
102
21
|
if (CACHE.has(name))
|
|
103
22
|
return CACHE.get(name);
|
|
@@ -149,30 +68,10 @@ async function fetchCorePinnedVersions() {
|
|
|
149
68
|
} catch {}
|
|
150
69
|
return map;
|
|
151
70
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
import { spawnSync } from "child_process";
|
|
157
|
-
function detectPackageManager(cwd = process.cwd()) {
|
|
158
|
-
if (existsSync(join(cwd, "bun.lockb")) || existsSync(join(cwd, "bun.lock"))) {
|
|
159
|
-
return "bun";
|
|
160
|
-
}
|
|
161
|
-
const bun = spawnSync("bun", ["--version"], { encoding: "utf8" });
|
|
162
|
-
if (bun.status === 0)
|
|
163
|
-
return "bun";
|
|
164
|
-
return "npm";
|
|
165
|
-
}
|
|
166
|
-
function installDeps(cwd = process.cwd()) {
|
|
167
|
-
const pm = detectPackageManager(cwd);
|
|
168
|
-
console.log(`\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0435\u0439 (${pm} install)\u2026`);
|
|
169
|
-
const r = spawnSync(pm, ["install"], {
|
|
170
|
-
cwd,
|
|
171
|
-
stdio: "inherit",
|
|
172
|
-
env: process.env
|
|
173
|
-
});
|
|
174
|
-
return r.status === 0;
|
|
175
|
-
}
|
|
71
|
+
var CACHE;
|
|
72
|
+
var init_latest = __esm(() => {
|
|
73
|
+
CACHE = new Map;
|
|
74
|
+
});
|
|
176
75
|
|
|
177
76
|
// src/lib/pkg.ts
|
|
178
77
|
import { existsSync as existsSync2, readFileSync, writeFileSync } from "fs";
|
|
@@ -194,318 +93,34 @@ function writePackageJson(pkg, cwd = process.cwd()) {
|
|
|
194
93
|
writeFileSync(packageJsonPath(cwd), JSON.stringify(pkg, null, 2) + `
|
|
195
94
|
`, "utf8");
|
|
196
95
|
}
|
|
96
|
+
var init_pkg = () => {};
|
|
197
97
|
|
|
198
|
-
// src/lib/
|
|
199
|
-
var enabled = process.env.NO_COLOR == null && process.env.FORCE_COLOR !== "0" && Boolean(process.stdout.isTTY);
|
|
200
|
-
function wrap(code, text) {
|
|
201
|
-
if (!enabled)
|
|
202
|
-
return text;
|
|
203
|
-
return `\x1B[${code}m${text}\x1B[0m`;
|
|
204
|
-
}
|
|
205
|
-
var c = {
|
|
206
|
-
gray: (t) => wrap("90", t),
|
|
207
|
-
green: (t) => wrap("32", t),
|
|
208
|
-
blue: (t) => wrap("34", t),
|
|
209
|
-
yellow: (t) => wrap("33", t),
|
|
210
|
-
dim: (t) => wrap("2", t)
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
// src/commands/update.ts
|
|
214
|
-
async function cmdUpdate(parsed) {
|
|
215
|
-
const cwd = process.cwd();
|
|
216
|
-
const pkg = readPackageJson(cwd);
|
|
217
|
-
if (!pkg) {
|
|
218
|
-
console.error("package.json \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u0432 \u0442\u0435\u043A\u0443\u0449\u0435\u043C \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0435.");
|
|
219
|
-
process.exit(1);
|
|
220
|
-
}
|
|
221
|
-
const deps = listElcrmDeps(pkg);
|
|
222
|
-
if (deps.length === 0) {
|
|
223
|
-
console.log("\u0417\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0435\u0439 @elcrm/* \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E.");
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
const dry = hasFlag(parsed, "dry");
|
|
227
|
-
const useCore = hasFlag(parsed, "core");
|
|
228
|
-
const noInstall = hasFlag(parsed, "no-install");
|
|
229
|
-
const names = uniqueElcrmNames(deps);
|
|
230
|
-
console.log(`\u041D\u0430\u0439\u0434\u0435\u043D\u043E @elcrm/*: ${names.length}`);
|
|
231
|
-
let versions;
|
|
232
|
-
if (useCore) {
|
|
233
|
-
console.log("\u0420\u0435\u0436\u0438\u043C --core: \u0432\u0435\u0440\u0441\u0438\u0438 \u0438\u0437 \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u043D\u043E\u0433\u043E @elcrm/core\u2026");
|
|
234
|
-
versions = await fetchCorePinnedVersions();
|
|
235
|
-
const missing = names.filter((n) => !versions.has(n));
|
|
236
|
-
if (missing.length) {
|
|
237
|
-
const extra = await fetchLatestMap(missing);
|
|
238
|
-
for (const [k, v] of extra)
|
|
239
|
-
versions.set(k, v);
|
|
240
|
-
}
|
|
241
|
-
} else {
|
|
242
|
-
console.log("\u0417\u0430\u043F\u0440\u043E\u0441 latest \u0441 npm registry\u2026");
|
|
243
|
-
versions = await fetchLatestMap(names);
|
|
244
|
-
}
|
|
245
|
-
let changed = 0;
|
|
246
|
-
const rows = [];
|
|
247
|
-
for (const dep of deps) {
|
|
248
|
-
const latest = versions.get(dep.name);
|
|
249
|
-
if (!latest) {
|
|
250
|
-
rows.push(` ${c.yellow("?")} ${dep.name}: ${c.gray(dep.version)} \u2192 ${c.yellow("(\u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E \u043D\u0430 npm)")}`);
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
253
|
-
const current = dep.version.replace(/^[\^~]/, "");
|
|
254
|
-
if (current === latest) {
|
|
255
|
-
rows.push(` ${c.gray("=")} ${dep.name}: ${c.gray(dep.version)}`);
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
const prefix = dep.version.startsWith("^") ? "^" : dep.version.startsWith("~") ? "~" : "";
|
|
259
|
-
const next = `${prefix}${latest}`;
|
|
260
|
-
rows.push(` ${c.blue("\u2191")} ${dep.name}: ${c.gray(dep.version)} \u2192 ${c.green(next)}`);
|
|
261
|
-
if (!dry) {
|
|
262
|
-
const section = pkg[dep.section];
|
|
263
|
-
section[dep.name] = next;
|
|
264
|
-
changed++;
|
|
265
|
-
} else {
|
|
266
|
-
changed++;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
console.log(rows.join(`
|
|
270
|
-
`));
|
|
271
|
-
if (dry) {
|
|
272
|
-
console.log(`
|
|
273
|
-
--dry: \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u043E (\u043A \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u044E: ${changed}).`);
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
if (changed === 0) {
|
|
277
|
-
console.log(`
|
|
278
|
-
\u0412\u0441\u0435 @elcrm/* \u0443\u0436\u0435 \u043D\u0430 \u0430\u043A\u0442\u0443\u0430\u043B\u044C\u043D\u044B\u0445 \u0432\u0435\u0440\u0441\u0438\u044F\u0445.`);
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
writePackageJson(pkg, cwd);
|
|
282
|
-
console.log(`
|
|
283
|
-
package.json \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D (${changed}).`);
|
|
284
|
-
if (!noInstall) {
|
|
285
|
-
const ok = installDeps(cwd);
|
|
286
|
-
if (!ok) {
|
|
287
|
-
console.error("\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0435\u0439 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C \u0441 \u043E\u0448\u0438\u0431\u043A\u043E\u0439.");
|
|
288
|
-
process.exit(1);
|
|
289
|
-
}
|
|
290
|
-
} else {
|
|
291
|
-
console.log("\u041F\u0440\u043E\u043F\u0443\u0441\u043A install (--no-install). \u0417\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u0435 bun/npm install \u0432\u0440\u0443\u0447\u043D\u0443\u044E.");
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// src/lib/copyTemplate.ts
|
|
98
|
+
// src/lib/migrate/socket-server.ts
|
|
296
99
|
import {
|
|
297
|
-
|
|
298
|
-
existsSync as existsSync4,
|
|
299
|
-
mkdirSync,
|
|
100
|
+
existsSync as existsSync3,
|
|
300
101
|
readdirSync,
|
|
301
102
|
readFileSync as readFileSync2,
|
|
302
103
|
statSync,
|
|
303
104
|
writeFileSync as writeFileSync2
|
|
304
105
|
} from "fs";
|
|
305
|
-
import { join as
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
106
|
+
import { join as join3 } from "path";
|
|
107
|
+
function walkFiles(dir, out = []) {
|
|
108
|
+
if (!existsSync3(dir))
|
|
109
|
+
return out;
|
|
110
|
+
for (const name of readdirSync(dir)) {
|
|
111
|
+
if (SKIP_DIRS.has(name))
|
|
112
|
+
continue;
|
|
113
|
+
const full = join3(dir, name);
|
|
114
|
+
const st = statSync(full);
|
|
115
|
+
if (st.isDirectory())
|
|
116
|
+
walkFiles(full, out);
|
|
117
|
+
else {
|
|
118
|
+
const ext = name.includes(".") ? `.${name.split(".").pop()}` : "";
|
|
119
|
+
if (TEXT_EXT.has(ext))
|
|
120
|
+
out.push(full);
|
|
317
121
|
}
|
|
318
122
|
}
|
|
319
|
-
|
|
320
|
-
if (existsSync3(join3(dir, "package.json")))
|
|
321
|
-
return dir;
|
|
322
|
-
}
|
|
323
|
-
return join3(here, "..");
|
|
324
|
-
}
|
|
325
|
-
function templatesDir() {
|
|
326
|
-
return join3(packageRoot(), "templates");
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// src/lib/copyTemplate.ts
|
|
330
|
-
var SKIP = new Set(["node_modules", "dist", ".git"]);
|
|
331
|
-
function folderToPackageName(projectPath) {
|
|
332
|
-
const base = projectPath.replace(/\\/g, "/").split("/").filter(Boolean).pop() ?? "app";
|
|
333
|
-
const slug = base.toLowerCase().replace(/[^a-z0-9-._~/]/g, "-").replace(/^[-_.]+|[-_.]+$/g, "");
|
|
334
|
-
return slug || "app";
|
|
335
|
-
}
|
|
336
|
-
function isEmptyDir(dir) {
|
|
337
|
-
if (!existsSync4(dir))
|
|
338
|
-
return true;
|
|
339
|
-
const entries = readdirSync(dir).filter((e) => e !== ".git" && e !== ".DS_Store");
|
|
340
|
-
return entries.length === 0;
|
|
341
|
-
}
|
|
342
|
-
function writePkgName(pkgPath, name) {
|
|
343
|
-
if (!existsSync4(pkgPath))
|
|
344
|
-
return;
|
|
345
|
-
const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
|
|
346
|
-
pkg.name = name;
|
|
347
|
-
pkg.version = pkg.version ?? "0.0.0";
|
|
348
|
-
writeFileSync2(pkgPath, JSON.stringify(pkg, null, 2) + `
|
|
349
|
-
`, "utf8");
|
|
350
|
-
}
|
|
351
|
-
function finalizePanelNames(targetDir, baseName) {
|
|
352
|
-
writePkgName(join4(targetDir, "package.json"), baseName);
|
|
353
|
-
writePkgName(join4(targetDir, "web", "package.json"), `${baseName}-web`);
|
|
354
|
-
writePkgName(join4(targetDir, "api", "package.json"), `${baseName}-api`);
|
|
355
|
-
}
|
|
356
|
-
function copyTemplate(templateName, targetDir, packageName) {
|
|
357
|
-
const src = join4(templatesDir(), templateName);
|
|
358
|
-
if (!existsSync4(src) || !statSync(src).isDirectory()) {
|
|
359
|
-
throw new Error(`\u0428\u0430\u0431\u043B\u043E\u043D \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${templateName}
|
|
360
|
-
\u041E\u0436\u0438\u0434\u0430\u043B\u0441\u044F \u043A\u0430\u0442\u0430\u043B\u043E\u0433: ${src}`);
|
|
361
|
-
}
|
|
362
|
-
mkdirSync(targetDir, { recursive: true });
|
|
363
|
-
for (const entry of readdirSync(src)) {
|
|
364
|
-
if (SKIP.has(entry))
|
|
365
|
-
continue;
|
|
366
|
-
const from = join4(src, entry);
|
|
367
|
-
const to = join4(targetDir, entry);
|
|
368
|
-
cpSync(from, to, { recursive: true });
|
|
369
|
-
}
|
|
370
|
-
const name = packageName ?? folderToPackageName(targetDir);
|
|
371
|
-
if (templateName === "panel") {
|
|
372
|
-
finalizePanelNames(targetDir, name);
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
writePkgName(join4(targetDir, "package.json"), name);
|
|
376
|
-
}
|
|
377
|
-
function listTemplates() {
|
|
378
|
-
const dir = templatesDir();
|
|
379
|
-
if (!existsSync4(dir))
|
|
380
|
-
return [];
|
|
381
|
-
return readdirSync(dir).filter((name) => {
|
|
382
|
-
const p = join4(dir, name);
|
|
383
|
-
return statSync(p).isDirectory() && !SKIP.has(name);
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
function resolveTarget(pathOrName, cwd = process.cwd()) {
|
|
387
|
-
return resolve(cwd, pathOrName);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// src/commands/create.ts
|
|
391
|
-
function libPackageName(pathOrName) {
|
|
392
|
-
let slug = folderToPackageName(pathOrName);
|
|
393
|
-
slug = slug.replace(/^@elcrm\//, "").replace(/^elcrm[._-]/, "");
|
|
394
|
-
return `@elcrm/${slug || "lib"}`;
|
|
395
|
-
}
|
|
396
|
-
function doneHint(template, cdPath) {
|
|
397
|
-
const prefix = cdPath ? ` cd ${cdPath}
|
|
398
|
-
` : "";
|
|
399
|
-
if (template === "panel") {
|
|
400
|
-
console.log(`
|
|
401
|
-
\u0413\u043E\u0442\u043E\u0432\u043E (panel: web + api).
|
|
402
|
-
|
|
403
|
-
${prefix} bun run dev
|
|
404
|
-
|
|
405
|
-
Web \u2192 http://localhost:5173
|
|
406
|
-
API \u2192 http://localhost:4545
|
|
407
|
-
\u0412\u0445\u043E\u0434: admin / admin
|
|
408
|
-
`);
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
console.log(`
|
|
412
|
-
\u0413\u043E\u0442\u043E\u0432\u043E.
|
|
413
|
-
|
|
414
|
-
${prefix} bun run dev
|
|
415
|
-
`);
|
|
416
|
-
}
|
|
417
|
-
async function cmdCreate(parsed) {
|
|
418
|
-
const name = parsed.positionals[0];
|
|
419
|
-
if (!name) {
|
|
420
|
-
console.error("\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0438\u043C\u044F \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0430: elcrm create <name>");
|
|
421
|
-
console.error(`\u0428\u0430\u0431\u043B\u043E\u043D\u044B: ${listTemplates().join(", ") || "(\u043D\u0435\u0442)"}`);
|
|
422
|
-
process.exit(1);
|
|
423
|
-
}
|
|
424
|
-
const template = parsed.options.template ?? "app";
|
|
425
|
-
const target = resolveTarget(name);
|
|
426
|
-
const noInstall = hasFlag(parsed, "no-install");
|
|
427
|
-
if (!isEmptyDir(target)) {
|
|
428
|
-
console.error(`\u041A\u0430\u0442\u0430\u043B\u043E\u0433 \u043D\u0435 \u043F\u0443\u0441\u0442: ${target}`);
|
|
429
|
-
process.exit(1);
|
|
430
|
-
}
|
|
431
|
-
const pkgName = template === "lib" && !name.startsWith("@") ? libPackageName(name) : folderToPackageName(name);
|
|
432
|
-
console.log(`\u0428\u0430\u0431\u043B\u043E\u043D: ${template}`);
|
|
433
|
-
console.log(`\u041A\u0430\u0442\u0430\u043B\u043E\u0433: ${target}`);
|
|
434
|
-
console.log(`package name: ${pkgName}`);
|
|
435
|
-
copyTemplate(template, target, pkgName);
|
|
436
|
-
console.log("\u0424\u0430\u0439\u043B\u044B \u0441\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u043D\u044B.");
|
|
437
|
-
if (!noInstall) {
|
|
438
|
-
const ok = installDeps(target);
|
|
439
|
-
if (!ok)
|
|
440
|
-
process.exit(1);
|
|
441
|
-
}
|
|
442
|
-
doneHint(template, name);
|
|
443
|
-
}
|
|
444
|
-
async function cmdInit(parsed) {
|
|
445
|
-
const template = parsed.options.template ?? "app";
|
|
446
|
-
const cwd = process.cwd();
|
|
447
|
-
const force = hasFlag(parsed, "force");
|
|
448
|
-
const noInstall = hasFlag(parsed, "no-install");
|
|
449
|
-
if (!force && !isEmptyDir(cwd)) {
|
|
450
|
-
console.error("\u0422\u0435\u043A\u0443\u0449\u0438\u0439 \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u043D\u0435 \u043F\u0443\u0441\u0442. \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435 --force \u0438\u043B\u0438 elcrm create <name>.");
|
|
451
|
-
process.exit(1);
|
|
452
|
-
}
|
|
453
|
-
const pkgName = template === "lib" ? libPackageName(cwd) : folderToPackageName(cwd);
|
|
454
|
-
console.log(`\u0428\u0430\u0431\u043B\u043E\u043D: ${template}`);
|
|
455
|
-
console.log(`\u041A\u0430\u0442\u0430\u043B\u043E\u0433: ${cwd}`);
|
|
456
|
-
copyTemplate(template, cwd, pkgName);
|
|
457
|
-
console.log("\u041F\u0440\u043E\u0435\u043A\u0442 \u0438\u043D\u0438\u0446\u0438\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D.");
|
|
458
|
-
if (!noInstall) {
|
|
459
|
-
const ok = installDeps(cwd);
|
|
460
|
-
if (!ok)
|
|
461
|
-
process.exit(1);
|
|
462
|
-
}
|
|
463
|
-
doneHint(template);
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// src/lib/migrate/socket-server.ts
|
|
467
|
-
import {
|
|
468
|
-
existsSync as existsSync5,
|
|
469
|
-
readdirSync as readdirSync2,
|
|
470
|
-
readFileSync as readFileSync3,
|
|
471
|
-
statSync as statSync2,
|
|
472
|
-
writeFileSync as writeFileSync3
|
|
473
|
-
} from "fs";
|
|
474
|
-
import { join as join5 } from "path";
|
|
475
|
-
var SKIP_DIRS = new Set([
|
|
476
|
-
"node_modules",
|
|
477
|
-
"dist",
|
|
478
|
-
".git",
|
|
479
|
-
"coverage",
|
|
480
|
-
".turbo"
|
|
481
|
-
]);
|
|
482
|
-
var TEXT_EXT = new Set([
|
|
483
|
-
".ts",
|
|
484
|
-
".tsx",
|
|
485
|
-
".js",
|
|
486
|
-
".jsx",
|
|
487
|
-
".mjs",
|
|
488
|
-
".cjs",
|
|
489
|
-
".mts",
|
|
490
|
-
".cts"
|
|
491
|
-
]);
|
|
492
|
-
function walkFiles(dir, out = []) {
|
|
493
|
-
if (!existsSync5(dir))
|
|
494
|
-
return out;
|
|
495
|
-
for (const name of readdirSync2(dir)) {
|
|
496
|
-
if (SKIP_DIRS.has(name))
|
|
497
|
-
continue;
|
|
498
|
-
const full = join5(dir, name);
|
|
499
|
-
const st = statSync2(full);
|
|
500
|
-
if (st.isDirectory())
|
|
501
|
-
walkFiles(full, out);
|
|
502
|
-
else {
|
|
503
|
-
const ext = name.includes(".") ? `.${name.split(".").pop()}` : "";
|
|
504
|
-
if (TEXT_EXT.has(ext))
|
|
505
|
-
out.push(full);
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
return out;
|
|
123
|
+
return out;
|
|
509
124
|
}
|
|
510
125
|
function rewriteImports(source) {
|
|
511
126
|
let hits = 0;
|
|
@@ -578,10 +193,10 @@ async function migrateSocketServer(cwd, dry) {
|
|
|
578
193
|
writePackageJson(pkg, cwd);
|
|
579
194
|
}
|
|
580
195
|
}
|
|
581
|
-
const roots = ["src", "app", "server", "lib"].map((d) =>
|
|
196
|
+
const roots = ["src", "app", "server", "lib"].map((d) => join3(cwd, d));
|
|
582
197
|
const files = roots.flatMap((r) => walkFiles(r));
|
|
583
198
|
for (const file of files) {
|
|
584
|
-
const raw =
|
|
199
|
+
const raw = readFileSync2(file, "utf8");
|
|
585
200
|
const { next, hits } = rewriteImports(raw);
|
|
586
201
|
if (hits > 0) {
|
|
587
202
|
changes.push({
|
|
@@ -589,350 +204,1428 @@ async function migrateSocketServer(cwd, dry) {
|
|
|
589
204
|
detail: `${hits} \u0437\u0430\u043C\u0435\u043D(\u044B) \u0438\u043C\u043F\u043E\u0440\u0442\u043E\u0432 \u2192 @elcrm/server`
|
|
590
205
|
});
|
|
591
206
|
if (!dry)
|
|
592
|
-
|
|
207
|
+
writeFileSync2(file, next, "utf8");
|
|
593
208
|
}
|
|
594
209
|
}
|
|
595
210
|
return changes;
|
|
596
211
|
}
|
|
212
|
+
var SKIP_DIRS, TEXT_EXT;
|
|
213
|
+
var init_socket_server = __esm(() => {
|
|
214
|
+
init_latest();
|
|
215
|
+
init_pkg();
|
|
216
|
+
SKIP_DIRS = new Set([
|
|
217
|
+
"node_modules",
|
|
218
|
+
"dist",
|
|
219
|
+
".git",
|
|
220
|
+
"coverage",
|
|
221
|
+
".turbo"
|
|
222
|
+
]);
|
|
223
|
+
TEXT_EXT = new Set([
|
|
224
|
+
".ts",
|
|
225
|
+
".tsx",
|
|
226
|
+
".js",
|
|
227
|
+
".jsx",
|
|
228
|
+
".mjs",
|
|
229
|
+
".cjs",
|
|
230
|
+
".mts",
|
|
231
|
+
".cts"
|
|
232
|
+
]);
|
|
233
|
+
});
|
|
597
234
|
|
|
598
|
-
// src/
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
const name
|
|
611
|
-
|
|
612
|
-
console.error("\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u044E: elcrm migrate <name>");
|
|
613
|
-
console.error(`\u0414\u043E\u0441\u0442\u0443\u043F\u043D\u043E: ${Object.keys(MIGRATIONS).join(", ")}`);
|
|
614
|
-
console.error("\u0421\u043F\u0438\u0441\u043E\u043A: elcrm migrate --list");
|
|
615
|
-
process.exit(1);
|
|
616
|
-
}
|
|
617
|
-
const fn = MIGRATIONS[name];
|
|
618
|
-
if (!fn) {
|
|
619
|
-
console.error(`\u041D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u044F: ${name}`);
|
|
620
|
-
console.error(`\u0414\u043E\u0441\u0442\u0443\u043F\u043D\u043E: ${Object.keys(MIGRATIONS).join(", ")}`);
|
|
621
|
-
process.exit(1);
|
|
622
|
-
}
|
|
623
|
-
const dry = hasFlag(parsed, "dry");
|
|
624
|
-
const cwd = process.cwd();
|
|
625
|
-
console.log(`\u041C\u0438\u0433\u0440\u0430\u0446\u0438\u044F: ${name}${dry ? " (--dry)" : ""}`);
|
|
626
|
-
const changes = await fn(cwd, dry);
|
|
627
|
-
if (changes.length === 0) {
|
|
628
|
-
console.log("\u0418\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439 \u043D\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F.");
|
|
629
|
-
return;
|
|
630
|
-
}
|
|
631
|
-
for (const c2 of changes) {
|
|
632
|
-
console.log(` ${c2.file}: ${c2.detail}`);
|
|
633
|
-
}
|
|
634
|
-
console.log(dry ? `
|
|
635
|
-
--dry: ${changes.length} \u043F\u043E\u0442\u0435\u043D\u0446\u0438\u0430\u043B\u044C\u043D\u044B\u0445 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439.` : `
|
|
636
|
-
\u041F\u0440\u0438\u043C\u0435\u043D\u0435\u043D\u043E: ${changes.length}.`);
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
// src/lib/test/front.ts
|
|
640
|
-
import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync4, statSync as statSync3 } from "fs";
|
|
641
|
-
import { join as join6, relative } from "path";
|
|
642
|
-
var SKIP2 = new Set(["node_modules", "dist", ".git", "coverage", ".bun"]);
|
|
643
|
-
var DEPRECATED_SOCKET_IMPORT_RE = /(?:from\s+|require\s*\(\s*)["']@elcrm\/(?:socket\/server|socket-server)["']/;
|
|
644
|
-
var THEMES_CSS_IMPORT_RE = /(?:from\s+|import\s+)["']@elcrm\/[^"']+\/themes\.css["']|import\s+["']@elcrm\/[^"']+\/themes\.css["']/;
|
|
645
|
-
var LEGACY_PASSKEY_IMPORT_RE = /(?:from\s+|require\s*\(\s*)["']@elcrm\/passkey["']/;
|
|
646
|
-
var LEGACY_PASSKEY_SYMBOL_RE = /\b(?:createPasskey|authenticateWithPasskey)\b/;
|
|
647
|
-
function walkCode(dir, out) {
|
|
648
|
-
if (!existsSync6(dir))
|
|
649
|
-
return;
|
|
650
|
-
for (const name of readdirSync3(dir)) {
|
|
651
|
-
if (SKIP2.has(name))
|
|
235
|
+
// src/lib/migrate/walk.ts
|
|
236
|
+
import {
|
|
237
|
+
existsSync as existsSync4,
|
|
238
|
+
readdirSync as readdirSync2,
|
|
239
|
+
readFileSync as readFileSync3,
|
|
240
|
+
statSync as statSync2,
|
|
241
|
+
writeFileSync as writeFileSync3
|
|
242
|
+
} from "fs";
|
|
243
|
+
import { join as join4, relative } from "path";
|
|
244
|
+
function walkCodeFiles(dir, out = []) {
|
|
245
|
+
if (!existsSync4(dir))
|
|
246
|
+
return out;
|
|
247
|
+
for (const name of readdirSync2(dir)) {
|
|
248
|
+
if (SKIP_DIRS2.has(name))
|
|
652
249
|
continue;
|
|
653
|
-
const full =
|
|
250
|
+
const full = join4(dir, name);
|
|
654
251
|
let st;
|
|
655
252
|
try {
|
|
656
|
-
st =
|
|
253
|
+
st = statSync2(full);
|
|
657
254
|
} catch {
|
|
658
255
|
continue;
|
|
659
256
|
}
|
|
660
257
|
if (st.isDirectory())
|
|
661
|
-
|
|
662
|
-
else if (/\.(tsx?|jsx?|mjs|cjs)$/.test(name))
|
|
258
|
+
walkCodeFiles(full, out);
|
|
259
|
+
else if (/\.(tsx?|jsx?|mjs|cjs|css)$/.test(name))
|
|
663
260
|
out.push(full);
|
|
664
261
|
}
|
|
262
|
+
return out;
|
|
665
263
|
}
|
|
666
|
-
function
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
264
|
+
function projectCodeRoots(cwd) {
|
|
265
|
+
return ["src", "app", "web/src", "lib", "modules"].map((d) => join4(cwd, d)).filter((p) => existsSync4(p));
|
|
266
|
+
}
|
|
267
|
+
function rel(cwd, file) {
|
|
268
|
+
return relative(cwd, file) || file;
|
|
269
|
+
}
|
|
270
|
+
function writeIfChanged(file, next, dry) {
|
|
271
|
+
const prev = readFileSync3(file, "utf8");
|
|
272
|
+
if (prev === next)
|
|
273
|
+
return false;
|
|
274
|
+
if (!dry)
|
|
275
|
+
writeFileSync3(file, next, "utf8");
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
var SKIP_DIRS2;
|
|
279
|
+
var init_walk = __esm(() => {
|
|
280
|
+
SKIP_DIRS2 = new Set([
|
|
281
|
+
"node_modules",
|
|
282
|
+
"dist",
|
|
283
|
+
".git",
|
|
284
|
+
"coverage",
|
|
285
|
+
".turbo",
|
|
286
|
+
".next",
|
|
287
|
+
"build"
|
|
288
|
+
]);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// src/lib/migrate/size-sml.ts
|
|
292
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
293
|
+
function rewrite(source) {
|
|
294
|
+
let next = source;
|
|
295
|
+
let hits = 0;
|
|
296
|
+
for (const [re, repl] of PATTERNS) {
|
|
297
|
+
next = next.replace(re, () => {
|
|
298
|
+
hits++;
|
|
299
|
+
return repl;
|
|
684
300
|
});
|
|
685
301
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
if (existsSync6(join6(cwd, "src/lib")) && !scripts["check:css"]) {
|
|
703
|
-
issues.push({
|
|
704
|
-
level: "info",
|
|
705
|
-
code: "check-css",
|
|
706
|
-
message: "\u041D\u0435\u0442 scripts.check:css (css-scoped) \u2014 \u0440\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0443\u0435\u0442\u0441\u044F \u0434\u043B\u044F UI-lib"
|
|
707
|
-
});
|
|
708
|
-
}
|
|
709
|
-
if (existsSync6(join6(cwd, "src/lib")) && !scripts["check:tokens"]) {
|
|
710
|
-
issues.push({
|
|
711
|
-
level: "info",
|
|
712
|
-
code: "check-tokens",
|
|
713
|
-
message: "\u041D\u0435\u0442 scripts.check:tokens \u2014 \u0440\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0443\u0435\u0442\u0441\u044F \u0434\u043B\u044F UI-lib \u0441 CSS Modules"
|
|
302
|
+
return { next, hits };
|
|
303
|
+
}
|
|
304
|
+
async function migrateSizeSml(cwd, dry) {
|
|
305
|
+
const changes = [];
|
|
306
|
+
const files = projectCodeRoots(cwd).flatMap((r) => walkCodeFiles(r));
|
|
307
|
+
for (const file of files) {
|
|
308
|
+
if (file.endsWith(".css"))
|
|
309
|
+
continue;
|
|
310
|
+
const raw = readFileSync4(file, "utf8");
|
|
311
|
+
const { next, hits } = rewrite(raw);
|
|
312
|
+
if (hits === 0)
|
|
313
|
+
continue;
|
|
314
|
+
if (writeIfChanged(file, next, dry) || dry) {
|
|
315
|
+
changes.push({
|
|
316
|
+
file: rel(cwd, file),
|
|
317
|
+
detail: `${hits}\xD7 size sm/md \u2192 s/m`
|
|
714
318
|
});
|
|
715
319
|
}
|
|
716
320
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
321
|
+
return changes;
|
|
322
|
+
}
|
|
323
|
+
var PATTERNS;
|
|
324
|
+
var init_size_sml = __esm(() => {
|
|
325
|
+
init_walk();
|
|
326
|
+
PATTERNS = [
|
|
327
|
+
[/size\s*=\s*"sm"/g, 'size="s"'],
|
|
328
|
+
[/size\s*=\s*'sm'/g, "size='s'"],
|
|
329
|
+
[/size\s*=\s*\{\s*"sm"\s*\}/g, 'size={"s"}'],
|
|
330
|
+
[/size\s*=\s*\{\s*'sm'\s*\}/g, "size={'s'}"],
|
|
331
|
+
[/size\s*=\s*"md"/g, 'size="m"'],
|
|
332
|
+
[/size\s*=\s*'md'/g, "size='m'"],
|
|
333
|
+
[/size\s*=\s*\{\s*"md"\s*\}/g, 'size={"m"}'],
|
|
334
|
+
[/size\s*=\s*\{\s*'md'\s*\}/g, "size={'m'}"]
|
|
335
|
+
];
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// src/lib/migrate/field-border.ts
|
|
339
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
340
|
+
function extractColor(value) {
|
|
341
|
+
const v = value.trim().replace(/;$/, "");
|
|
342
|
+
const m = v.match(/^(?:\d+(?:\.\d+)?(?:px|em|rem)?\s+)?(?:solid|dashed|dotted|double)\s+(.+)$/i);
|
|
343
|
+
if (m)
|
|
344
|
+
return m[1].trim();
|
|
345
|
+
if (/^(#|rgb|hsl|var\(|color-mix|transparent|currentColor)/i.test(v)) {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
function rewriteCss(source) {
|
|
351
|
+
let hits = 0;
|
|
352
|
+
let next = source;
|
|
353
|
+
for (const name of VARS) {
|
|
354
|
+
const re = new RegExp(`(${name.replace(/-/g, "\\-")}\\s*:\\s*)([^;\\n}]+)`, "g");
|
|
355
|
+
next = next.replace(re, (full, prefix, rawVal) => {
|
|
356
|
+
const color = extractColor(rawVal);
|
|
357
|
+
if (!color)
|
|
358
|
+
return full;
|
|
359
|
+
hits++;
|
|
360
|
+
return `${prefix}${color}`;
|
|
724
361
|
});
|
|
725
362
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
363
|
+
return { next, hits };
|
|
364
|
+
}
|
|
365
|
+
async function migrateFieldBorder(cwd, dry) {
|
|
366
|
+
const changes = [];
|
|
367
|
+
const files = projectCodeRoots(cwd).flatMap((r) => walkCodeFiles(r)).filter((f) => f.endsWith(".css"));
|
|
368
|
+
for (const file of files) {
|
|
369
|
+
const raw = readFileSync5(file, "utf8");
|
|
370
|
+
const { next, hits } = rewriteCss(raw);
|
|
371
|
+
if (hits === 0)
|
|
372
|
+
continue;
|
|
373
|
+
if (writeIfChanged(file, next, dry) || dry) {
|
|
374
|
+
changes.push({
|
|
375
|
+
file: rel(cwd, file),
|
|
376
|
+
detail: `${hits}\xD7 border hover/focus \u2192 \u0442\u043E\u043B\u044C\u043A\u043E \u0446\u0432\u0435\u0442`
|
|
732
377
|
});
|
|
733
378
|
}
|
|
734
379
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
if (existsSync6(full))
|
|
755
|
-
files.push(full);
|
|
756
|
-
const inSrc = join6(cwd, "src", name);
|
|
757
|
-
if (existsSync6(inSrc) && !files.includes(inSrc))
|
|
758
|
-
files.push(inSrc);
|
|
759
|
-
}
|
|
380
|
+
return changes;
|
|
381
|
+
}
|
|
382
|
+
var VARS;
|
|
383
|
+
var init_field_border = __esm(() => {
|
|
384
|
+
init_walk();
|
|
385
|
+
VARS = [
|
|
386
|
+
"--field-border-focus",
|
|
387
|
+
"--field-border-hover",
|
|
388
|
+
"--field-border-error",
|
|
389
|
+
"--field-border-disabled"
|
|
390
|
+
];
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// src/lib/migrate/modal-create.ts
|
|
394
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
395
|
+
async function migrateModalCreate(cwd, dry) {
|
|
396
|
+
const changes = [];
|
|
397
|
+
const files = projectCodeRoots(cwd).flatMap((r) => walkCodeFiles(r)).filter((f) => /\.(tsx|jsx)$/.test(f));
|
|
398
|
+
const addRe = /Modal\.Add\s*\(\s*<([A-Za-z_][\w.]*)\s+key=\{Date\.now\(\)\}/;
|
|
760
399
|
for (const file of files) {
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
}
|
|
400
|
+
const raw = readFileSync6(file, "utf8");
|
|
401
|
+
if (!addRe.test(raw) && !/Modal\.Add\s*\(/.test(raw))
|
|
402
|
+
continue;
|
|
403
|
+
if (/export\s+default\s+Modal\.Create\s*\(/.test(raw)) {
|
|
404
|
+
if (/Modal\.Add\s*\(/.test(raw)) {
|
|
405
|
+
changes.push({
|
|
406
|
+
file: rel(cwd, file),
|
|
407
|
+
detail: "\u043E\u0441\u0442\u0430\u043B\u0441\u044F Modal.Add \u0440\u044F\u0434\u043E\u043C \u0441 Create \u2014 \u0443\u0431\u0435\u0440\u0438\u0442\u0435 Add \u0432\u0440\u0443\u0447\u043D\u0443\u044E"
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
continue;
|
|
771
411
|
}
|
|
772
|
-
const
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
412
|
+
const simple = /function\s+Screen\b/.test(raw) || /const\s+Screen\s*=/.test(raw) || /function\s+\w+\s*\([^)]*\)\s*\{[\s\S]*Modal\.Main/.test(raw);
|
|
413
|
+
const openWrapper = /(?:const|function)\s+Open\s*=\s*(?:\([^)]*\)|[A-Za-z_]\w*)\s*=>\s*\{?\s*Modal\.Add/;
|
|
414
|
+
if (simple && openWrapper.test(raw) && /export\s+default/.test(raw)) {
|
|
415
|
+
let next = raw;
|
|
416
|
+
next = next.replace(/\n(?:const|function)\s+Open\s*=\s*[\s\S]*?(?=\n(?:const|function|export)\s)/, `
|
|
417
|
+
`);
|
|
418
|
+
next = next.replace(/\n(?:const|function)\s+Icon\s*=\s*[\s\S]*?(?=\n(?:const|function|export)\s)/, `
|
|
419
|
+
`);
|
|
420
|
+
next = next.replace(/\nconst\s+Add\s*=\s*\{[\s\S]*?\};\s*\nexport\s+default\s+Add\s*;?/, `
|
|
421
|
+
export default Modal.Create(Screen);
|
|
422
|
+
`);
|
|
423
|
+
next = next.replace(/export\s+default\s+\{\s*Open\s*,?\s*Icon?\s*,?\s*\}\s*;?/, "export default Modal.Create(Screen);");
|
|
424
|
+
next = next.replace(/export\s+function\s+open\w+\s*\([^)]*\)\s*\{[\s\S]*?Modal\.Add\([\s\S]*?\);\s*\}/, (block) => {
|
|
425
|
+
return `/* TODO: \u0437\u0430\u043C\u0435\u043D\u0438\u0442\u0435 \u0432\u044B\u0437\u043E\u0432\u044B \u043D\u0430 Modal.Open("Module","Name", props) \u0438\u043B\u0438 Create.Open */
|
|
426
|
+
${block}`;
|
|
782
427
|
});
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
if (pkg.name === "@elcrm/passkey")
|
|
789
|
-
return;
|
|
790
|
-
issues.push({
|
|
791
|
-
level: "error",
|
|
792
|
-
code: "legacy-passkey-api",
|
|
793
|
-
message: "createPasskey / authenticateWithPasskey \u043D\u0435 \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u0443\u044E\u0442\u0441\u044F \u2014 createPasskeyClient",
|
|
794
|
-
file: rel,
|
|
795
|
-
line: i + 1
|
|
428
|
+
if (next !== raw && !/Modal\.Add/.test(next)) {
|
|
429
|
+
if (writeIfChanged(file, next, dry) || dry) {
|
|
430
|
+
changes.push({
|
|
431
|
+
file: rel(cwd, file),
|
|
432
|
+
detail: "Modal.Add \u2192 Modal.Create(Screen)"
|
|
796
433
|
});
|
|
797
434
|
}
|
|
798
|
-
|
|
799
|
-
}
|
|
800
|
-
const lines = text.split(/\r?\n/);
|
|
801
|
-
lines.forEach((line, i) => {
|
|
802
|
-
const trimmed = line.trim();
|
|
803
|
-
if (trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*")) {
|
|
804
|
-
return;
|
|
805
|
-
}
|
|
806
|
-
if (/<(?:input|textarea|select)\b/.test(line)) {
|
|
807
|
-
issues.push({
|
|
808
|
-
level: "error",
|
|
809
|
-
code: "native-html-input",
|
|
810
|
-
message: "\u043D\u0430\u0442\u0438\u0432\u043D\u044B\u0439 <input|textarea|select> \u2014 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435 @elcrm/form (StringField, Select, \u2026)",
|
|
811
|
-
file: rel,
|
|
812
|
-
line: i + 1
|
|
813
|
-
});
|
|
814
|
-
}
|
|
815
|
-
if (/(?:^|\s)native(?:\s|$|=|\/|>)/.test(line) && !/\bnavigator\b|\bnativeEvent\b|\bcreateNative/.test(line)) {
|
|
816
|
-
issues.push({
|
|
817
|
-
level: "warn",
|
|
818
|
-
code: "form-native-prop",
|
|
819
|
-
message: "prop native \u0443 \u043F\u043E\u043B\u044F \u0444\u043E\u0440\u043C\u044B \u2014 \u0443\u0431\u0435\u0440\u0438\u0442\u0435; \u0441\u0442\u0438\u043B\u0438 \u0447\u0435\u0440\u0435\u0437 \u0442\u043E\u043A\u0435\u043D\u044B @elcrm/form",
|
|
820
|
-
file: rel,
|
|
821
|
-
line: i + 1
|
|
822
|
-
});
|
|
435
|
+
continue;
|
|
823
436
|
}
|
|
437
|
+
}
|
|
438
|
+
const count = (raw.match(/Modal\.Add\s*\(/g) || []).length;
|
|
439
|
+
changes.push({
|
|
440
|
+
file: rel(cwd, file),
|
|
441
|
+
detail: `${count}\xD7 Modal.Add \u2014 \u043F\u0435\u0440\u0435\u0432\u0435\u0434\u0438\u0442\u0435 \u043D\u0430 Modal.Create + Open (\u0441\u043C. Projects/modal/Create)`
|
|
824
442
|
});
|
|
825
443
|
}
|
|
826
|
-
return
|
|
827
|
-
}
|
|
828
|
-
async function runPkgScript(cwd, script) {
|
|
829
|
-
const pkg = JSON.parse(readFileSync4(join6(cwd, "package.json"), "utf8"));
|
|
830
|
-
if (!pkg.scripts?.[script])
|
|
831
|
-
return { ran: false, ok: true };
|
|
832
|
-
console.log(`\u2192 bun run ${script}`);
|
|
833
|
-
const proc = Bun.spawn(["bun", "run", script], {
|
|
834
|
-
cwd,
|
|
835
|
-
stdout: "inherit",
|
|
836
|
-
stderr: "inherit"
|
|
837
|
-
});
|
|
838
|
-
const code = await proc.exited;
|
|
839
|
-
return { ran: true, ok: code === 0 };
|
|
444
|
+
return changes;
|
|
840
445
|
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
446
|
+
var init_modal_create = __esm(() => {
|
|
447
|
+
init_walk();
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
// src/lib/migrate/legacy-hacks.ts
|
|
451
|
+
import { existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
|
|
452
|
+
import { join as join5 } from "path";
|
|
453
|
+
async function migrateLegacyReactHack(cwd, dry) {
|
|
454
|
+
const changes = [];
|
|
455
|
+
const files = new Set(projectCodeRoots(cwd).flatMap((r) => walkCodeFiles(r)).filter((f) => /(^|\/)(main|bootstrap)\.(tsx?|jsx?)$/.test(f)));
|
|
456
|
+
for (const name of ["main.tsx", "main.ts", "src/main.tsx", "src/main.ts"]) {
|
|
457
|
+
const full = join5(cwd, name);
|
|
458
|
+
if (existsSync5(full))
|
|
459
|
+
files.add(full);
|
|
847
460
|
}
|
|
848
|
-
|
|
461
|
+
for (const file of files) {
|
|
462
|
+
const raw = readFileSync7(file, "utf8");
|
|
463
|
+
if (!HACK_RE.test(raw))
|
|
464
|
+
continue;
|
|
465
|
+
const next = raw.replace(/\/\*\s*@elcrm\/modal[\s\S]*?\*\/\s*/m, "").replace(/\(globalThis\s+as\s+typeof\s+globalThis\s+&\s+\{\s*React:\s*typeof\s+React\s*\}\)\.React\s*=\s*React\s*;\s*/g, "").replace(/globalThis\.React\s*=\s*React\s*;\s*/g, "");
|
|
466
|
+
if (next === raw) {
|
|
467
|
+
changes.push({
|
|
468
|
+
file: rel(cwd, file),
|
|
469
|
+
detail: "globalThis.React \u2014 \u0443\u0434\u0430\u043B\u0438\u0442\u0435 \u0432\u0440\u0443\u0447\u043D\u0443\u044E \u043F\u043E\u0441\u043B\u0435 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u044F @elcrm/modal"
|
|
470
|
+
});
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
if (writeIfChanged(file, next, dry) || dry) {
|
|
474
|
+
changes.push({
|
|
475
|
+
file: rel(cwd, file),
|
|
476
|
+
detail: "\u0443\u0434\u0430\u043B\u0451\u043D globalThis.React \u0445\u0430\u043A"
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return changes;
|
|
849
481
|
}
|
|
482
|
+
var HACK_RE;
|
|
483
|
+
var init_legacy_hacks = __esm(() => {
|
|
484
|
+
init_walk();
|
|
485
|
+
HACK_RE = /\(globalThis\s+as\s+[^)]+\)\.React\s*=\s*React\s*;?|globalThis\.React\s*=\s*React\s*;?/;
|
|
486
|
+
});
|
|
850
487
|
|
|
851
|
-
// src/lib/
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
488
|
+
// src/lib/migrate/form-aliases.ts
|
|
489
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
490
|
+
function rewrite2(source) {
|
|
491
|
+
let next = source;
|
|
492
|
+
const labels = [];
|
|
493
|
+
for (const [re, repl, label] of REPLACEMENTS) {
|
|
494
|
+
let n = 0;
|
|
495
|
+
next = next.replace(re, () => {
|
|
496
|
+
n++;
|
|
497
|
+
return repl;
|
|
498
|
+
});
|
|
499
|
+
if (n > 0)
|
|
500
|
+
labels.push(`${n}\xD7 ${label}`);
|
|
858
501
|
}
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
502
|
+
return { next, labels };
|
|
503
|
+
}
|
|
504
|
+
async function migrateFormAliases(cwd, dry) {
|
|
505
|
+
const changes = [];
|
|
506
|
+
const files = projectCodeRoots(cwd).flatMap((r) => walkCodeFiles(r));
|
|
507
|
+
for (const file of files) {
|
|
508
|
+
if (file.endsWith(".css"))
|
|
509
|
+
continue;
|
|
510
|
+
const raw = readFileSync8(file, "utf8");
|
|
511
|
+
const { next, labels } = rewrite2(raw);
|
|
512
|
+
if (labels.length === 0)
|
|
513
|
+
continue;
|
|
514
|
+
if (writeIfChanged(file, next, dry) || dry) {
|
|
515
|
+
changes.push({
|
|
516
|
+
file: rel(cwd, file),
|
|
517
|
+
detail: labels.join(", ")
|
|
518
|
+
});
|
|
519
|
+
}
|
|
867
520
|
}
|
|
868
|
-
return
|
|
521
|
+
return changes;
|
|
869
522
|
}
|
|
523
|
+
var REPLACEMENTS;
|
|
524
|
+
var init_form_aliases = __esm(() => {
|
|
525
|
+
init_walk();
|
|
526
|
+
REPLACEMENTS = [
|
|
527
|
+
[/\bProgressField\b/g, "RangeField", "ProgressField\u2192RangeField"],
|
|
528
|
+
[/\bReadonlyField\b/g, "DisplayField", "ReadonlyField\u2192DisplayField"],
|
|
529
|
+
[/\bOtpField\b/g, "CodeField", "OtpField\u2192CodeField"],
|
|
530
|
+
[/\bSegmentField\b/g, "TabsField", "SegmentField\u2192TabsField"],
|
|
531
|
+
[/\bDragonDropField\b/g, "DragDropField", "DragonDropField\u2192DragDropField"],
|
|
532
|
+
[/\bTProgress\b/g, "TRange", "TProgress\u2192TRange"]
|
|
533
|
+
];
|
|
534
|
+
});
|
|
870
535
|
|
|
871
|
-
// src/lib/
|
|
872
|
-
import {
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
"
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
536
|
+
// src/lib/migrate/form-tokens.ts
|
|
537
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
538
|
+
function removeDecl(css, name) {
|
|
539
|
+
const re = new RegExp(`\\n?[ \\t]*${name.replace(/-/g, "\\-")}\\s*:[^;\\n}]+;?[ \\t]*`, "g");
|
|
540
|
+
let n = 0;
|
|
541
|
+
const next = css.replace(re, () => {
|
|
542
|
+
n++;
|
|
543
|
+
return "";
|
|
544
|
+
});
|
|
545
|
+
return { next: next.replace(/\n{3,}/g, `
|
|
546
|
+
|
|
547
|
+
`), n };
|
|
548
|
+
}
|
|
549
|
+
function migrateDatePopupShadowDecl(css) {
|
|
550
|
+
let n = 0;
|
|
551
|
+
let next = css;
|
|
552
|
+
const hasPopup = /--popup-shadow\s*:/.test(next);
|
|
553
|
+
const re = /(--date-popup-shadow\s*:\s*)(?!var\(\s*--popup-shadow)([^;\n}]+)/gi;
|
|
554
|
+
next = next.replace(re, (full, prefix, value) => {
|
|
555
|
+
n++;
|
|
556
|
+
const v = value.trim();
|
|
557
|
+
if (hasPopup) {
|
|
558
|
+
return `${prefix}var(--popup-shadow)`;
|
|
559
|
+
}
|
|
560
|
+
return `/*__POPUP_SHADOW__*/${v}/*__END__*/${prefix}var(--popup-shadow)`;
|
|
561
|
+
});
|
|
562
|
+
if (next.includes("/*__POPUP_SHADOW__*/")) {
|
|
563
|
+
next = next.replace(/\/\*__POPUP_SHADOW__\*\/([\s\S]*?)\/\*__END__\*\//, (_m, v) => `--popup-shadow: ${v.trim()};
|
|
564
|
+
`);
|
|
883
565
|
}
|
|
884
|
-
return
|
|
566
|
+
return { next, n };
|
|
885
567
|
}
|
|
886
|
-
function
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
568
|
+
function expandFieldPaddingShorthand(css) {
|
|
569
|
+
let n = 0;
|
|
570
|
+
if (/--field-padding-block\s*:/.test(css)) {
|
|
571
|
+
return { next: css, n: 0 };
|
|
572
|
+
}
|
|
573
|
+
const re = /(--field-padding\s*:\s*)(?!var\(\s*--field-padding-block)(\S+)\s+(\S+)\s*;/g;
|
|
574
|
+
const next = css.replace(re, (full, prefix, block, inline) => {
|
|
575
|
+
if (block.includes("(") || inline.includes("("))
|
|
576
|
+
return full;
|
|
577
|
+
n++;
|
|
578
|
+
return `--field-padding-block: ${block};
|
|
579
|
+
` + ` --field-padding-inline: ${inline};
|
|
580
|
+
` + ` ${prefix}var(--field-padding-block) var(--field-padding-inline);`;
|
|
581
|
+
});
|
|
582
|
+
return { next, n };
|
|
890
583
|
}
|
|
891
|
-
function
|
|
892
|
-
|
|
893
|
-
let
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
584
|
+
function rewriteFormTokens(source) {
|
|
585
|
+
let next = source;
|
|
586
|
+
let hits = 0;
|
|
587
|
+
for (const [re, to] of VAR_REPLACEMENTS) {
|
|
588
|
+
next = next.replace(re, () => {
|
|
589
|
+
hits++;
|
|
590
|
+
return to;
|
|
591
|
+
});
|
|
898
592
|
}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
593
|
+
const shadow = migrateDatePopupShadowDecl(next);
|
|
594
|
+
next = shadow.next;
|
|
595
|
+
hits += shadow.n;
|
|
596
|
+
const pad = expandFieldPaddingShorthand(next);
|
|
597
|
+
next = pad.next;
|
|
598
|
+
hits += pad.n;
|
|
599
|
+
for (const name of DECL_REMOVE) {
|
|
600
|
+
const stillUsed = new RegExp(`var\\(\\s*${name.replace(/-/g, "\\-")}\\s*`, "i").test(next);
|
|
601
|
+
if (stillUsed)
|
|
907
602
|
continue;
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
603
|
+
const rem = removeDecl(next, name);
|
|
604
|
+
next = rem.next;
|
|
605
|
+
hits += rem.n;
|
|
606
|
+
}
|
|
607
|
+
return { next, hits };
|
|
608
|
+
}
|
|
609
|
+
async function migrateFormTokens(cwd, dry) {
|
|
610
|
+
const pkg = readPackageJson(cwd);
|
|
611
|
+
if (pkg?.name === "@elcrm/form")
|
|
612
|
+
return [];
|
|
613
|
+
const changes = [];
|
|
614
|
+
const files = projectCodeRoots(cwd).flatMap((r) => walkCodeFiles(r)).filter((f) => f.endsWith(".css"));
|
|
615
|
+
for (const file of files) {
|
|
616
|
+
const raw = readFileSync9(file, "utf8");
|
|
617
|
+
const { next, hits } = rewriteFormTokens(raw);
|
|
618
|
+
if (hits === 0 || next === raw)
|
|
912
619
|
continue;
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
}
|
|
620
|
+
if (writeIfChanged(file, next, dry) || dry) {
|
|
621
|
+
changes.push({
|
|
622
|
+
file: rel(cwd, file),
|
|
623
|
+
detail: `${hits}\xD7 form-tokens (padding / popup-shadow / cleanup)`
|
|
624
|
+
});
|
|
918
625
|
}
|
|
919
626
|
}
|
|
920
|
-
return
|
|
627
|
+
return changes;
|
|
921
628
|
}
|
|
922
|
-
function
|
|
923
|
-
const
|
|
924
|
-
if (!
|
|
925
|
-
return
|
|
926
|
-
|
|
927
|
-
return
|
|
629
|
+
function lineHasDeprecatedFormToken(line) {
|
|
630
|
+
const t = line.trim();
|
|
631
|
+
if (!t || t.startsWith("/*") || t.startsWith("*"))
|
|
632
|
+
return false;
|
|
633
|
+
if (DEPRECATED_VAR_RE.test(t))
|
|
634
|
+
return true;
|
|
635
|
+
if (!DEPRECATED_DECL_RE.test(t))
|
|
636
|
+
return false;
|
|
637
|
+
if (/:\s*var\(\s*--(?:field-padding|popup-shadow)/.test(t))
|
|
638
|
+
return false;
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
var VAR_REPLACEMENTS, DECL_REMOVE, FORM_TOKEN_DEPRECATED, DEPRECATED_VAR_RE, DEPRECATED_DECL_RE;
|
|
642
|
+
var init_form_tokens = __esm(() => {
|
|
643
|
+
init_pkg();
|
|
644
|
+
init_walk();
|
|
645
|
+
VAR_REPLACEMENTS = [
|
|
646
|
+
[
|
|
647
|
+
/var\(\s*--field-note-padding-top\s*(?:,[^)]+)?\)/gi,
|
|
648
|
+
"var(--field-padding-block)"
|
|
649
|
+
],
|
|
650
|
+
[
|
|
651
|
+
/var\(\s*--field-note-padding\s*(?:,[^)]+)?\)/gi,
|
|
652
|
+
"var(--field-padding-block)"
|
|
653
|
+
],
|
|
654
|
+
[
|
|
655
|
+
/var\(\s*--field-side-padding\s*(?:,[^)]+)?\)/gi,
|
|
656
|
+
"var(--field-padding-inline)"
|
|
657
|
+
],
|
|
658
|
+
[
|
|
659
|
+
/var\(\s*--control-side-padding\s*(?:,[^)]+)?\)/gi,
|
|
660
|
+
"var(--field-padding-inline)"
|
|
661
|
+
],
|
|
662
|
+
[
|
|
663
|
+
/var\(\s*--control-inner-padding\s*(?:,[^)]+)?\)/gi,
|
|
664
|
+
"var(--field-padding)"
|
|
665
|
+
],
|
|
666
|
+
[
|
|
667
|
+
/var\(\s*--date-popup-shadow\s*(?:,[^)]+)?\)/gi,
|
|
668
|
+
"var(--popup-shadow)"
|
|
669
|
+
]
|
|
670
|
+
];
|
|
671
|
+
DECL_REMOVE = [
|
|
672
|
+
"--field-note-padding-top",
|
|
673
|
+
"--field-note-padding",
|
|
674
|
+
"--control-inner-padding",
|
|
675
|
+
"--control-side-padding",
|
|
676
|
+
"--field-side-padding",
|
|
677
|
+
"--date-popup-shadow"
|
|
678
|
+
];
|
|
679
|
+
FORM_TOKEN_DEPRECATED = [
|
|
680
|
+
"--field-note-padding",
|
|
681
|
+
"--field-note-padding-top",
|
|
682
|
+
"--control-inner-padding",
|
|
683
|
+
"--control-side-padding",
|
|
684
|
+
"--field-side-padding",
|
|
685
|
+
"--date-popup-shadow"
|
|
686
|
+
];
|
|
687
|
+
DEPRECATED_VAR_RE = new RegExp(`var\\(\\s*(?:${FORM_TOKEN_DEPRECATED.map((n) => n.replace(/-/g, "\\-")).join("|")})\\s*`, "i");
|
|
688
|
+
DEPRECATED_DECL_RE = new RegExp(`(?:${FORM_TOKEN_DEPRECATED.map((n) => n.replace(/-/g, "\\-")).join("|")})\\s*:`);
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
// src/lib/migrate/front.ts
|
|
692
|
+
var exports_front = {};
|
|
693
|
+
__export(exports_front, {
|
|
694
|
+
migrateFrontAll: () => migrateFrontAll,
|
|
695
|
+
FRONT_MIGRATIONS: () => FRONT_MIGRATIONS,
|
|
696
|
+
FRONT_MIGRATE_ORDER: () => FRONT_MIGRATE_ORDER
|
|
697
|
+
});
|
|
698
|
+
async function migrateFrontAll(cwd, dry) {
|
|
699
|
+
const all = [];
|
|
700
|
+
for (const name of FRONT_MIGRATE_ORDER) {
|
|
701
|
+
const part = await FRONT_MIGRATIONS[name](cwd, dry);
|
|
702
|
+
for (const c2 of part) {
|
|
703
|
+
all.push({ file: c2.file, detail: `[${name}] ${c2.detail}` });
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
return all;
|
|
707
|
+
}
|
|
708
|
+
var FRONT_MIGRATIONS, FRONT_MIGRATE_ORDER;
|
|
709
|
+
var init_front = __esm(() => {
|
|
710
|
+
init_socket_server();
|
|
711
|
+
init_size_sml();
|
|
712
|
+
init_field_border();
|
|
713
|
+
init_modal_create();
|
|
714
|
+
init_legacy_hacks();
|
|
715
|
+
init_form_aliases();
|
|
716
|
+
init_form_tokens();
|
|
717
|
+
FRONT_MIGRATIONS = {
|
|
718
|
+
"size-sml": migrateSizeSml,
|
|
719
|
+
"field-border": migrateFieldBorder,
|
|
720
|
+
"modal-create": migrateModalCreate,
|
|
721
|
+
"form-aliases": migrateFormAliases,
|
|
722
|
+
"form-tokens": migrateFormTokens,
|
|
723
|
+
"legacy-hacks": migrateLegacyReactHack,
|
|
724
|
+
"socket-server": migrateSocketServer
|
|
725
|
+
};
|
|
726
|
+
FRONT_MIGRATE_ORDER = [
|
|
727
|
+
"form-aliases",
|
|
728
|
+
"form-tokens",
|
|
729
|
+
"size-sml",
|
|
730
|
+
"field-border",
|
|
731
|
+
"modal-create",
|
|
732
|
+
"socket-server"
|
|
733
|
+
];
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
// src/index.ts
|
|
737
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
738
|
+
import { dirname as dirname4, join as join18 } from "path";
|
|
739
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
740
|
+
|
|
741
|
+
// src/parseArgs.ts
|
|
742
|
+
function parseArgs(argv = process.argv.slice(2)) {
|
|
743
|
+
const options = {};
|
|
744
|
+
const flags = [];
|
|
745
|
+
const positionals = [];
|
|
746
|
+
for (let i = 0;i < argv.length; i++) {
|
|
747
|
+
const arg = argv[i];
|
|
748
|
+
if (arg === "--") {
|
|
749
|
+
positionals.push(...argv.slice(i + 1));
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
if (arg.startsWith("--")) {
|
|
753
|
+
const body = arg.slice(2);
|
|
754
|
+
if (body.includes("=")) {
|
|
755
|
+
const eq = body.indexOf("=");
|
|
756
|
+
const key = body.slice(0, eq);
|
|
757
|
+
const value = body.slice(eq + 1);
|
|
758
|
+
if (key)
|
|
759
|
+
options[key] = value;
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
const next = argv[i + 1];
|
|
763
|
+
if (next && !next.startsWith("-")) {
|
|
764
|
+
const valueOpts = new Set([
|
|
765
|
+
"template",
|
|
766
|
+
"url",
|
|
767
|
+
"name",
|
|
768
|
+
"dir",
|
|
769
|
+
"folder",
|
|
770
|
+
"out"
|
|
771
|
+
]);
|
|
772
|
+
if (valueOpts.has(body)) {
|
|
773
|
+
options[body] = next;
|
|
774
|
+
i++;
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
if (body && !flags.includes(body))
|
|
779
|
+
flags.push(body);
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
if (arg.startsWith("-") && arg.length > 1) {
|
|
783
|
+
const short = arg.slice(1);
|
|
784
|
+
if (short === "t" && argv[i + 1] && !argv[i + 1].startsWith("-")) {
|
|
785
|
+
options.template = argv[i + 1];
|
|
786
|
+
i++;
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
if (short && !flags.includes(short))
|
|
790
|
+
flags.push(short);
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
positionals.push(arg);
|
|
794
|
+
}
|
|
795
|
+
const [command = "", ...rest] = positionals;
|
|
796
|
+
return {
|
|
797
|
+
command,
|
|
798
|
+
positionals: rest,
|
|
799
|
+
options,
|
|
800
|
+
flags
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
function hasFlag(parsed, ...names) {
|
|
804
|
+
return names.some((n) => parsed.flags.includes(n));
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// src/lib/elcrmDeps.ts
|
|
808
|
+
var SECTIONS = [
|
|
809
|
+
"dependencies",
|
|
810
|
+
"devDependencies",
|
|
811
|
+
"peerDependencies"
|
|
812
|
+
];
|
|
813
|
+
function listElcrmDeps(pkg) {
|
|
814
|
+
const out = [];
|
|
815
|
+
for (const section of SECTIONS) {
|
|
816
|
+
const map = pkg[section];
|
|
817
|
+
if (!map || typeof map !== "object")
|
|
818
|
+
continue;
|
|
819
|
+
for (const [name, version] of Object.entries(map)) {
|
|
820
|
+
if (name.startsWith("@elcrm/") && typeof version === "string") {
|
|
821
|
+
out.push({ name, version, section });
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
return out;
|
|
826
|
+
}
|
|
827
|
+
function uniqueElcrmNames(deps) {
|
|
828
|
+
return [...new Set(deps.map((d) => d.name))].sort();
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// src/commands/update.ts
|
|
832
|
+
init_latest();
|
|
833
|
+
|
|
834
|
+
// src/lib/install.ts
|
|
835
|
+
import { existsSync } from "fs";
|
|
836
|
+
import { join } from "path";
|
|
837
|
+
import { spawnSync } from "child_process";
|
|
838
|
+
function detectPackageManager(cwd = process.cwd()) {
|
|
839
|
+
if (existsSync(join(cwd, "bun.lockb")) || existsSync(join(cwd, "bun.lock"))) {
|
|
840
|
+
return "bun";
|
|
841
|
+
}
|
|
842
|
+
const bun = spawnSync("bun", ["--version"], { encoding: "utf8" });
|
|
843
|
+
if (bun.status === 0)
|
|
844
|
+
return "bun";
|
|
845
|
+
return "npm";
|
|
846
|
+
}
|
|
847
|
+
function installDeps(cwd = process.cwd()) {
|
|
848
|
+
const pm = detectPackageManager(cwd);
|
|
849
|
+
console.log(`\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0435\u0439 (${pm} install)\u2026`);
|
|
850
|
+
const r = spawnSync(pm, ["install"], {
|
|
851
|
+
cwd,
|
|
852
|
+
stdio: "inherit",
|
|
853
|
+
env: process.env
|
|
854
|
+
});
|
|
855
|
+
return r.status === 0;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// src/commands/update.ts
|
|
859
|
+
init_pkg();
|
|
860
|
+
|
|
861
|
+
// src/lib/ansi.ts
|
|
862
|
+
var enabled = process.env.NO_COLOR == null && process.env.FORCE_COLOR !== "0" && Boolean(process.stdout.isTTY);
|
|
863
|
+
function wrap(code, text) {
|
|
864
|
+
if (!enabled)
|
|
865
|
+
return text;
|
|
866
|
+
return `\x1B[${code}m${text}\x1B[0m`;
|
|
867
|
+
}
|
|
868
|
+
var c = {
|
|
869
|
+
gray: (t) => wrap("90", t),
|
|
870
|
+
green: (t) => wrap("32", t),
|
|
871
|
+
blue: (t) => wrap("34", t),
|
|
872
|
+
yellow: (t) => wrap("33", t),
|
|
873
|
+
red: (t) => wrap("31", t),
|
|
874
|
+
cyan: (t) => wrap("36", t),
|
|
875
|
+
dim: (t) => wrap("2", t)
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
// src/commands/update.ts
|
|
879
|
+
init_front();
|
|
880
|
+
|
|
881
|
+
// src/lib/cssSync.ts
|
|
882
|
+
init_form_tokens();
|
|
883
|
+
import {
|
|
884
|
+
existsSync as existsSync6,
|
|
885
|
+
readdirSync as readdirSync3,
|
|
886
|
+
readFileSync as readFileSync10,
|
|
887
|
+
statSync as statSync3,
|
|
888
|
+
writeFileSync as writeFileSync4
|
|
889
|
+
} from "fs";
|
|
890
|
+
import { createRequire } from "module";
|
|
891
|
+
import { dirname, join as join6 } from "path";
|
|
892
|
+
var COLOR_VALUE = /#|rgba?\(|hsla?\(|oklch|oklab|color-mix|lab\(|lch\(|hwb\(/i;
|
|
893
|
+
var COLOR_NAME = /(?:^|-)(bg|fg|color|fill|track|thumb|overlay|shadow|border|ring|background|placeholder)(?:-|$)/i;
|
|
894
|
+
var GEOM_VALUE = /(?:^|\s)(\d*\.?\d+)(px|em|rem|%|vh|vw|dvh|ch|ex|s|ms|deg|fr|pt)\b|^[0-9.]+$|ease|linear|none/i;
|
|
895
|
+
function stripComments(css) {
|
|
896
|
+
return css.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
897
|
+
}
|
|
898
|
+
function parseCustomProps(body) {
|
|
899
|
+
const out = new Map;
|
|
900
|
+
const src = stripComments(body);
|
|
901
|
+
const re = /(--[a-zA-Z0-9-]+)\s*:/g;
|
|
902
|
+
let m;
|
|
903
|
+
while (m = re.exec(src)) {
|
|
904
|
+
let depth = 0;
|
|
905
|
+
let end = re.lastIndex;
|
|
906
|
+
while (end < src.length) {
|
|
907
|
+
const ch = src[end];
|
|
908
|
+
if (ch === "(")
|
|
909
|
+
depth++;
|
|
910
|
+
else if (ch === ")")
|
|
911
|
+
depth--;
|
|
912
|
+
else if ((ch === ";" || ch === "}") && depth <= 0)
|
|
913
|
+
break;
|
|
914
|
+
end++;
|
|
915
|
+
}
|
|
916
|
+
const value = src.slice(re.lastIndex, end).trim().replace(/\s+/g, " ");
|
|
917
|
+
if (value)
|
|
918
|
+
out.set(m[1], value);
|
|
919
|
+
re.lastIndex = end;
|
|
920
|
+
}
|
|
921
|
+
return out;
|
|
922
|
+
}
|
|
923
|
+
function walkRules(css, fn) {
|
|
924
|
+
const s = stripComments(css);
|
|
925
|
+
let i = 0;
|
|
926
|
+
while (i < s.length) {
|
|
927
|
+
const open = s.indexOf("{", i);
|
|
928
|
+
if (open < 0)
|
|
929
|
+
break;
|
|
930
|
+
const sel = s.slice(i, open).trim();
|
|
931
|
+
let depth = 1;
|
|
932
|
+
let j = open + 1;
|
|
933
|
+
while (j < s.length && depth > 0) {
|
|
934
|
+
if (s[j] === "{")
|
|
935
|
+
depth++;
|
|
936
|
+
else if (s[j] === "}")
|
|
937
|
+
depth--;
|
|
938
|
+
j++;
|
|
939
|
+
}
|
|
940
|
+
const body = s.slice(open + 1, j - 1);
|
|
941
|
+
if (sel.startsWith("@"))
|
|
942
|
+
walkRules(body, fn);
|
|
943
|
+
else
|
|
944
|
+
fn(sel, body);
|
|
945
|
+
i = j;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
function fileKind(file) {
|
|
949
|
+
const base = file.replace(/\\/g, "/").toLowerCase();
|
|
950
|
+
if (base.endsWith("light.css") || /\/light\.css$/.test(base))
|
|
951
|
+
return "light";
|
|
952
|
+
if (base.endsWith("dark.css") || /\/dark\.css$/.test(base))
|
|
953
|
+
return "dark";
|
|
954
|
+
if (base.endsWith("tokens.css") || base.endsWith("theme.css") || /\/tokens\.css$/.test(base)) {
|
|
955
|
+
return "geometry";
|
|
956
|
+
}
|
|
957
|
+
return "mixed";
|
|
958
|
+
}
|
|
959
|
+
function selectorBucket(sel) {
|
|
960
|
+
const s = sel.toLowerCase();
|
|
961
|
+
if (/data-theme\s*=\s*["']?light/.test(s))
|
|
962
|
+
return "light";
|
|
963
|
+
if (/data-theme\s*=\s*["']?dark/.test(s))
|
|
964
|
+
return "dark";
|
|
965
|
+
if (/(^|,)\s*:root\b/.test(s) || /(^|,)\s*html\b/.test(s))
|
|
966
|
+
return "geometry";
|
|
967
|
+
return null;
|
|
968
|
+
}
|
|
969
|
+
function isColorToken(name, value) {
|
|
970
|
+
if (COLOR_VALUE.test(value))
|
|
971
|
+
return true;
|
|
972
|
+
if (COLOR_NAME.test(name) && /transparent|currentcolor|var\(|\bnone\b/i.test(value)) {
|
|
973
|
+
return true;
|
|
974
|
+
}
|
|
975
|
+
if (COLOR_NAME.test(name) && !GEOM_VALUE.test(value))
|
|
976
|
+
return true;
|
|
977
|
+
return false;
|
|
978
|
+
}
|
|
979
|
+
function listCssFiles(dir, acc = []) {
|
|
980
|
+
if (!existsSync6(dir))
|
|
981
|
+
return acc;
|
|
982
|
+
for (const name of readdirSync3(dir)) {
|
|
983
|
+
if (name === "node_modules")
|
|
984
|
+
continue;
|
|
985
|
+
const full = join6(dir, name);
|
|
986
|
+
let st;
|
|
987
|
+
try {
|
|
988
|
+
st = statSync3(full);
|
|
989
|
+
} catch {
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
if (st.isDirectory())
|
|
993
|
+
listCssFiles(full, acc);
|
|
994
|
+
else if (name.endsWith(".css"))
|
|
995
|
+
acc.push(full);
|
|
996
|
+
}
|
|
997
|
+
return acc;
|
|
998
|
+
}
|
|
999
|
+
function resolvePkgRoot(pkgName, cwd) {
|
|
1000
|
+
try {
|
|
1001
|
+
const req = createRequire(join6(cwd, "package.json"));
|
|
1002
|
+
const pkgJson = req.resolve(`${pkgName}/package.json`);
|
|
1003
|
+
return dirname(pkgJson);
|
|
1004
|
+
} catch {
|
|
1005
|
+
const fallback = join6(cwd, "node_modules", ...pkgName.split("/"));
|
|
1006
|
+
return existsSync6(join6(fallback, "package.json")) ? fallback : null;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
function readPkgJson(dir) {
|
|
1010
|
+
try {
|
|
1011
|
+
return JSON.parse(readFileSync10(join6(dir, "package.json"), "utf8"));
|
|
1012
|
+
} catch {
|
|
1013
|
+
return null;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
function exportPath(exportsMap, key) {
|
|
1017
|
+
if (!exportsMap || typeof exportsMap !== "object")
|
|
1018
|
+
return;
|
|
1019
|
+
const ent = exportsMap[key];
|
|
1020
|
+
if (typeof ent === "string")
|
|
1021
|
+
return ent;
|
|
1022
|
+
if (ent && typeof ent === "object") {
|
|
1023
|
+
const o = ent;
|
|
1024
|
+
const v = o.import ?? o.default ?? o.require;
|
|
1025
|
+
return typeof v === "string" ? v : undefined;
|
|
1026
|
+
}
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
function inspectPkgCss(pkgName, cwd) {
|
|
1030
|
+
const root = resolvePkgRoot(pkgName, cwd);
|
|
1031
|
+
if (!root)
|
|
1032
|
+
return null;
|
|
1033
|
+
const pkg = readPkgJson(root);
|
|
1034
|
+
const exp = pkg?.exports;
|
|
1035
|
+
const styleRel = exportPath(exp, "./style.css");
|
|
1036
|
+
const lightRel = exportPath(exp, "./light.css");
|
|
1037
|
+
const darkRel = exportPath(exp, "./dark.css");
|
|
1038
|
+
const abs = (rel2) => rel2 ? join6(root, rel2.replace(/^\.\//, "")) : undefined;
|
|
1039
|
+
return {
|
|
1040
|
+
name: pkgName,
|
|
1041
|
+
root,
|
|
1042
|
+
style: abs(styleRel) && existsSync6(abs(styleRel)) ? abs(styleRel) : undefined,
|
|
1043
|
+
light: abs(lightRel) && existsSync6(abs(lightRel)) ? abs(lightRel) : undefined,
|
|
1044
|
+
dark: abs(darkRel) && existsSync6(abs(darkRel)) ? abs(darkRel) : undefined
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
function addDef(acc, seen, pkg, bucket, name, value) {
|
|
1048
|
+
const key = `${bucket}:${name}`;
|
|
1049
|
+
if (seen.has(key))
|
|
1050
|
+
return;
|
|
1051
|
+
seen.add(key);
|
|
1052
|
+
acc.push({ name, value, bucket, pkg });
|
|
1053
|
+
}
|
|
1054
|
+
function collectPkgTokens(pkgName, cwd) {
|
|
1055
|
+
const root = resolvePkgRoot(pkgName, cwd);
|
|
1056
|
+
if (!root)
|
|
1057
|
+
return [];
|
|
1058
|
+
const files = [
|
|
1059
|
+
...listCssFiles(join6(root, "dist")),
|
|
1060
|
+
...listCssFiles(join6(root, "src/lib/styles")),
|
|
1061
|
+
...listCssFiles(join6(root, "src/style")),
|
|
1062
|
+
join6(root, "test/main.css")
|
|
1063
|
+
].filter((f) => existsSync6(f));
|
|
1064
|
+
const acc = [];
|
|
1065
|
+
const seen = new Set;
|
|
1066
|
+
for (const file of files) {
|
|
1067
|
+
const css = readFileSync10(file, "utf8");
|
|
1068
|
+
const kind = fileKind(file);
|
|
1069
|
+
if (kind !== "mixed") {
|
|
1070
|
+
for (const [name, value] of parseCustomProps(css)) {
|
|
1071
|
+
const color = isColorToken(name, value);
|
|
1072
|
+
if (kind === "geometry") {
|
|
1073
|
+
if (color) {
|
|
1074
|
+
addDef(acc, seen, pkgName, "light", name, value);
|
|
1075
|
+
addDef(acc, seen, pkgName, "dark", name, value);
|
|
1076
|
+
} else {
|
|
1077
|
+
addDef(acc, seen, pkgName, "geometry", name, value);
|
|
1078
|
+
}
|
|
1079
|
+
continue;
|
|
1080
|
+
}
|
|
1081
|
+
addDef(acc, seen, pkgName, color ? kind : "geometry", name, value);
|
|
1082
|
+
}
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
walkRules(css, (sel, body) => {
|
|
1086
|
+
const bucket = selectorBucket(sel);
|
|
1087
|
+
if (!bucket)
|
|
1088
|
+
return;
|
|
1089
|
+
for (const [name, value] of parseCustomProps(body)) {
|
|
1090
|
+
const color = isColorToken(name, value);
|
|
1091
|
+
if (bucket === "geometry") {
|
|
1092
|
+
if (color) {
|
|
1093
|
+
addDef(acc, seen, pkgName, "light", name, value);
|
|
1094
|
+
addDef(acc, seen, pkgName, "dark", name, value);
|
|
1095
|
+
} else {
|
|
1096
|
+
addDef(acc, seen, pkgName, "geometry", name, value);
|
|
1097
|
+
}
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
addDef(acc, seen, pkgName, color ? bucket : "geometry", name, value);
|
|
1101
|
+
}
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
return acc;
|
|
1105
|
+
}
|
|
1106
|
+
function existingTokenNames(css) {
|
|
1107
|
+
return new Set(parseCustomProps(css).keys());
|
|
1108
|
+
}
|
|
1109
|
+
function detectIndent(css) {
|
|
1110
|
+
const m = css.match(/\n([ \t]+)--[a-zA-Z]/);
|
|
1111
|
+
return m?.[1] ?? " ";
|
|
1112
|
+
}
|
|
1113
|
+
function prefixOf(name) {
|
|
1114
|
+
const parts = name.slice(2).split("-");
|
|
1115
|
+
return parts[0] || "misc";
|
|
1116
|
+
}
|
|
1117
|
+
function formatTokenBlock(defs, indent) {
|
|
1118
|
+
const byPrefix = new Map;
|
|
1119
|
+
for (const d of defs) {
|
|
1120
|
+
const p = prefixOf(d.name);
|
|
1121
|
+
const list = byPrefix.get(p) ?? [];
|
|
1122
|
+
list.push(d);
|
|
1123
|
+
byPrefix.set(p, list);
|
|
1124
|
+
}
|
|
1125
|
+
const prefixes = [...byPrefix.keys()].sort();
|
|
1126
|
+
const lines = [];
|
|
1127
|
+
for (const p of prefixes) {
|
|
1128
|
+
const list = byPrefix.get(p);
|
|
1129
|
+
const pkgs = [...new Set(list.map((x) => x.pkg))].sort();
|
|
1130
|
+
const label = pkgs.map((n) => n.replace(/^@elcrm\//, "")).join(", ");
|
|
1131
|
+
lines.push("");
|
|
1132
|
+
lines.push(`${indent}/* \u2014\u2014 ${p} (${label}) \u2014\u2014 */`);
|
|
1133
|
+
list.sort((a, b) => a.name.localeCompare(b.name));
|
|
1134
|
+
for (const d of list) {
|
|
1135
|
+
lines.push(`${indent}${d.name}: ${d.value};`);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
lines.push("");
|
|
1139
|
+
return lines.join(`
|
|
1140
|
+
`);
|
|
1141
|
+
}
|
|
1142
|
+
function insertIntoRule(css, selectorTest, snippet) {
|
|
1143
|
+
const s = css;
|
|
1144
|
+
let i = 0;
|
|
1145
|
+
while (i < s.length) {
|
|
1146
|
+
const open = s.indexOf("{", i);
|
|
1147
|
+
if (open < 0)
|
|
1148
|
+
break;
|
|
1149
|
+
const sel = stripComments(s.slice(i, open)).trim();
|
|
1150
|
+
let depth = 1;
|
|
1151
|
+
let j = open + 1;
|
|
1152
|
+
while (j < s.length && depth > 0) {
|
|
1153
|
+
if (s[j] === "{")
|
|
1154
|
+
depth++;
|
|
1155
|
+
else if (s[j] === "}")
|
|
1156
|
+
depth--;
|
|
1157
|
+
j++;
|
|
1158
|
+
}
|
|
1159
|
+
if (selectorTest(sel)) {
|
|
1160
|
+
const before = s.slice(0, j - 1).replace(/\s*$/, "");
|
|
1161
|
+
const after = s.slice(j - 1);
|
|
1162
|
+
return `${before}
|
|
1163
|
+
${snippet}${after}`;
|
|
1164
|
+
}
|
|
1165
|
+
i = j;
|
|
1166
|
+
}
|
|
1167
|
+
return null;
|
|
1168
|
+
}
|
|
1169
|
+
function findStyleDir(cwd) {
|
|
1170
|
+
for (const d of [join6(cwd, "src/style"), join6(cwd, "web/src/style")]) {
|
|
1171
|
+
if (existsSync6(join6(d, "theme-light.css")) || existsSync6(join6(d, "elcrm.css")) || existsSync6(join6(d, "theme.css"))) {
|
|
1172
|
+
return d;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
return null;
|
|
1176
|
+
}
|
|
1177
|
+
function ensureImport(css, spec) {
|
|
1178
|
+
const needle = `@import "${spec}"`;
|
|
1179
|
+
const alt = `@import '${spec}'`;
|
|
1180
|
+
if (css.includes(needle) || css.includes(alt)) {
|
|
1181
|
+
return { css, added: false };
|
|
1182
|
+
}
|
|
1183
|
+
const line = `${needle};`;
|
|
1184
|
+
const imports = [...css.matchAll(/@import[^;]+;/g)];
|
|
1185
|
+
if (imports.length) {
|
|
1186
|
+
const themeIdx = imports.findIndex((m) => /theme\.css/.test(m[0]));
|
|
1187
|
+
const anchor = themeIdx >= 0 ? imports[themeIdx] : imports[imports.length - 1];
|
|
1188
|
+
const idx = themeIdx >= 0 ? anchor.index : anchor.index + anchor[0].length;
|
|
1189
|
+
const pad = themeIdx >= 0 ? line + `
|
|
1190
|
+
` : `
|
|
1191
|
+
` + line;
|
|
1192
|
+
return {
|
|
1193
|
+
css: css.slice(0, idx) + pad + css.slice(idx),
|
|
1194
|
+
added: true
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
return { css: line + `
|
|
1198
|
+
` + css, added: true };
|
|
1199
|
+
}
|
|
1200
|
+
function syncElcrmCss(options) {
|
|
1201
|
+
const styleDir = findStyleDir(options.cwd);
|
|
1202
|
+
if (!styleDir) {
|
|
1203
|
+
throw new Error("\u041D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D src/style (theme.css / theme-light.css / elcrm.css).");
|
|
1204
|
+
}
|
|
1205
|
+
const paths = {
|
|
1206
|
+
geometry: join6(styleDir, "theme.css"),
|
|
1207
|
+
light: join6(styleDir, "theme-light.css"),
|
|
1208
|
+
dark: join6(styleDir, "theme-dark.css"),
|
|
1209
|
+
elcrm: join6(styleDir, "elcrm.css")
|
|
1210
|
+
};
|
|
1211
|
+
const allDefs = [];
|
|
1212
|
+
const skippedPkgs = [];
|
|
1213
|
+
const cssExports = [];
|
|
1214
|
+
for (const name of options.pkgNames) {
|
|
1215
|
+
const exp = inspectPkgCss(name, options.cwd);
|
|
1216
|
+
if (exp)
|
|
1217
|
+
cssExports.push(exp);
|
|
1218
|
+
const defs = collectPkgTokens(name, options.cwd);
|
|
1219
|
+
if (defs.length === 0)
|
|
1220
|
+
skippedPkgs.push(name);
|
|
1221
|
+
allDefs.push(...defs);
|
|
1222
|
+
}
|
|
1223
|
+
const added = {
|
|
1224
|
+
"theme.css": [],
|
|
1225
|
+
"theme-light.css": [],
|
|
1226
|
+
"theme-dark.css": []
|
|
1227
|
+
};
|
|
1228
|
+
const imports = [];
|
|
1229
|
+
const fileFor = (bucket) => bucket === "geometry" ? paths.geometry : bucket === "light" ? paths.light : paths.dark;
|
|
1230
|
+
const labelFor = (bucket) => bucket === "geometry" ? "theme.css" : bucket === "light" ? "theme-light.css" : "theme-dark.css";
|
|
1231
|
+
for (const bucket of ["geometry", "light", "dark"]) {
|
|
1232
|
+
const file = fileFor(bucket);
|
|
1233
|
+
if (!existsSync6(file))
|
|
1234
|
+
continue;
|
|
1235
|
+
let css = readFileSync10(file, "utf8");
|
|
1236
|
+
const have = existingTokenNames(css);
|
|
1237
|
+
const missing = allDefs.filter((d) => d.bucket === bucket && !have.has(d.name) && !FORM_TOKEN_DEPRECATED.includes(d.name));
|
|
1238
|
+
const uniq = [];
|
|
1239
|
+
const seen = new Set;
|
|
1240
|
+
for (const d of missing) {
|
|
1241
|
+
if (seen.has(d.name))
|
|
1242
|
+
continue;
|
|
1243
|
+
seen.add(d.name);
|
|
1244
|
+
uniq.push(d);
|
|
1245
|
+
}
|
|
1246
|
+
if (!uniq.length)
|
|
1247
|
+
continue;
|
|
1248
|
+
const indent = detectIndent(css);
|
|
1249
|
+
const snippet = formatTokenBlock(uniq, indent);
|
|
1250
|
+
const test = bucket === "geometry" ? (sel) => /(^|,)\s*:root\b/.test(sel) : bucket === "light" ? (sel) => /data-theme\s*=\s*["']?light/.test(sel.toLowerCase()) : (sel) => /data-theme\s*=\s*["']?dark/.test(sel.toLowerCase());
|
|
1251
|
+
const next = insertIntoRule(css, test, snippet);
|
|
1252
|
+
if (!next) {
|
|
1253
|
+
throw new Error(`\u041D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u0431\u043B\u043E\u043A \u0441\u0435\u043B\u0435\u043A\u0442\u043E\u0440\u0430 \u0432 ${labelFor(bucket)}`);
|
|
1254
|
+
}
|
|
1255
|
+
added[labelFor(bucket)] = uniq.map((d) => d.name);
|
|
1256
|
+
if (!options.dry)
|
|
1257
|
+
writeFileSync4(file, next, "utf8");
|
|
1258
|
+
}
|
|
1259
|
+
if (existsSync6(paths.elcrm)) {
|
|
1260
|
+
let css = readFileSync10(paths.elcrm, "utf8");
|
|
1261
|
+
for (const exp of cssExports) {
|
|
1262
|
+
const specs = [];
|
|
1263
|
+
if (exp.light)
|
|
1264
|
+
specs.push(`${exp.name}/light.css`);
|
|
1265
|
+
if (exp.dark)
|
|
1266
|
+
specs.push(`${exp.name}/dark.css`);
|
|
1267
|
+
const tokens = join6(exp.root, "dist/tokens.css");
|
|
1268
|
+
if (existsSync6(tokens))
|
|
1269
|
+
specs.push(`${exp.name}/tokens.css`);
|
|
1270
|
+
for (const spec of specs) {
|
|
1271
|
+
const r = ensureImport(css, spec);
|
|
1272
|
+
css = r.css;
|
|
1273
|
+
if (r.added)
|
|
1274
|
+
imports.push(spec);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
if (imports.length && !options.dry)
|
|
1278
|
+
writeFileSync4(paths.elcrm, css, "utf8");
|
|
1279
|
+
}
|
|
1280
|
+
return { styleDir, added, imports, skippedPkgs };
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// src/commands/test.ts
|
|
1284
|
+
import { resolve } from "path";
|
|
1285
|
+
|
|
1286
|
+
// src/lib/test/detect.ts
|
|
1287
|
+
import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync11, statSync as statSync4 } from "fs";
|
|
1288
|
+
import { join as join7 } from "path";
|
|
1289
|
+
function hasElcrmServer(pkg) {
|
|
1290
|
+
for (const section of [
|
|
1291
|
+
"dependencies",
|
|
1292
|
+
"devDependencies",
|
|
1293
|
+
"peerDependencies"
|
|
1294
|
+
]) {
|
|
1295
|
+
const map = pkg[section];
|
|
1296
|
+
if (map && typeof map === "object" && "@elcrm/server" in map)
|
|
1297
|
+
return true;
|
|
1298
|
+
}
|
|
1299
|
+
return false;
|
|
1300
|
+
}
|
|
1301
|
+
function detectTestMode(pkg, force) {
|
|
1302
|
+
if (force === "server" || force === "front")
|
|
1303
|
+
return force;
|
|
1304
|
+
return hasElcrmServer(pkg) ? "server" : "front";
|
|
1305
|
+
}
|
|
1306
|
+
function listWorkspaceDirs(cwd, pkg) {
|
|
1307
|
+
const raw = pkg.workspaces;
|
|
1308
|
+
let patterns = [];
|
|
1309
|
+
if (Array.isArray(raw)) {
|
|
1310
|
+
patterns = raw.filter((p) => typeof p === "string");
|
|
1311
|
+
} else if (raw && typeof raw === "object" && Array.isArray(raw.packages)) {
|
|
1312
|
+
patterns = raw.packages.filter((p) => typeof p === "string");
|
|
1313
|
+
}
|
|
1314
|
+
if (!patterns.length)
|
|
1315
|
+
return [];
|
|
1316
|
+
const out = [];
|
|
1317
|
+
for (const pat of patterns) {
|
|
1318
|
+
if (!pat.includes("*")) {
|
|
1319
|
+
const dir = join7(cwd, pat);
|
|
1320
|
+
if (existsSync7(join7(dir, "package.json")))
|
|
1321
|
+
out.push(dir);
|
|
1322
|
+
continue;
|
|
1323
|
+
}
|
|
1324
|
+
const base = pat.replace(/\/\*$/, "").replace(/\*$/, "");
|
|
1325
|
+
const parent = join7(cwd, base);
|
|
1326
|
+
if (!existsSync7(parent) || !statSync4(parent).isDirectory())
|
|
1327
|
+
continue;
|
|
1328
|
+
for (const name of readdirSync4(parent)) {
|
|
1329
|
+
const dir = join7(parent, name);
|
|
1330
|
+
if (statSync4(dir).isDirectory() && existsSync7(join7(dir, "package.json"))) {
|
|
1331
|
+
out.push(dir);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
return out;
|
|
1336
|
+
}
|
|
1337
|
+
function readPkgAt(dir) {
|
|
1338
|
+
const path = join7(dir, "package.json");
|
|
1339
|
+
if (!existsSync7(path))
|
|
1340
|
+
return null;
|
|
1341
|
+
try {
|
|
1342
|
+
return JSON.parse(readFileSync11(path, "utf8"));
|
|
928
1343
|
} catch {
|
|
929
1344
|
return null;
|
|
930
1345
|
}
|
|
931
1346
|
}
|
|
932
1347
|
|
|
1348
|
+
// src/lib/test/front.ts
|
|
1349
|
+
import { existsSync as existsSync8, readdirSync as readdirSync5, readFileSync as readFileSync12, statSync as statSync5 } from "fs";
|
|
1350
|
+
import { join as join8, relative as relative2 } from "path";
|
|
1351
|
+
init_form_tokens();
|
|
1352
|
+
var SKIP = new Set(["node_modules", "dist", ".git", "coverage", ".bun"]);
|
|
1353
|
+
var DEPRECATED_SOCKET_IMPORT_RE = /(?:from\s+|require\s*\(\s*)["']@elcrm\/(?:socket\/server|socket-server)["']/;
|
|
1354
|
+
var THEMES_CSS_IMPORT_RE = /(?:from\s+|import\s+)["']@elcrm\/[^"']+\/themes\.css["']|import\s+["']@elcrm\/[^"']+\/themes\.css["']/;
|
|
1355
|
+
var LEGACY_PASSKEY_IMPORT_RE = /(?:from\s+|require\s*\(\s*)["']@elcrm\/passkey["']/;
|
|
1356
|
+
var LEGACY_PASSKEY_SYMBOL_RE = /\b(?:createPasskey|authenticateWithPasskey)\b/;
|
|
1357
|
+
function walkCode(dir, out) {
|
|
1358
|
+
if (!existsSync8(dir))
|
|
1359
|
+
return;
|
|
1360
|
+
for (const name of readdirSync5(dir)) {
|
|
1361
|
+
if (SKIP.has(name))
|
|
1362
|
+
continue;
|
|
1363
|
+
const full = join8(dir, name);
|
|
1364
|
+
let st;
|
|
1365
|
+
try {
|
|
1366
|
+
st = statSync5(full);
|
|
1367
|
+
} catch {
|
|
1368
|
+
continue;
|
|
1369
|
+
}
|
|
1370
|
+
if (st.isDirectory())
|
|
1371
|
+
walkCode(full, out);
|
|
1372
|
+
else if (/\.(tsx?|jsx?|mjs|cjs|css)$/.test(name))
|
|
1373
|
+
out.push(full);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
function collectFrontIssues(cwd, pkg) {
|
|
1377
|
+
const issues = [];
|
|
1378
|
+
const isLib = Boolean(pkg.name?.startsWith("@elcrm/"));
|
|
1379
|
+
const scripts = pkg.scripts ?? {};
|
|
1380
|
+
if (pkg.type !== "module") {
|
|
1381
|
+
issues.push({
|
|
1382
|
+
level: "warn",
|
|
1383
|
+
code: "type-module",
|
|
1384
|
+
message: '\u041D\u0435\u0442 "type": "module"',
|
|
1385
|
+
fixable: true
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
if (!pkg.engines?.node && !pkg.engines?.bun) {
|
|
1389
|
+
issues.push({
|
|
1390
|
+
level: "warn",
|
|
1391
|
+
code: "engines",
|
|
1392
|
+
message: "\u041D\u0435\u0442 engines.node (\u043E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F >=18) \u0438\u043B\u0438 engines.bun",
|
|
1393
|
+
fixable: true
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
if (isLib) {
|
|
1397
|
+
if (!scripts.build && !scripts["pack:check"]) {
|
|
1398
|
+
issues.push({
|
|
1399
|
+
level: "warn",
|
|
1400
|
+
code: "scripts",
|
|
1401
|
+
message: "\u0414\u043B\u044F \u0431\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0438 \u0436\u0435\u043B\u0430\u0442\u0435\u043B\u044C\u043D\u044B scripts.build \u0438/\u0438\u043B\u0438 pack:check"
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
if (!scripts["pack:check"]) {
|
|
1405
|
+
issues.push({
|
|
1406
|
+
level: "info",
|
|
1407
|
+
code: "pack-check",
|
|
1408
|
+
message: "\u041D\u0435\u0442 scripts.pack:check",
|
|
1409
|
+
fixable: true
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
if (existsSync8(join8(cwd, "src/lib")) && !scripts["check:css"]) {
|
|
1413
|
+
issues.push({
|
|
1414
|
+
level: "info",
|
|
1415
|
+
code: "check-css",
|
|
1416
|
+
message: "\u041D\u0435\u0442 scripts.check:css (css-scoped) \u2014 \u0440\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0443\u0435\u0442\u0441\u044F \u0434\u043B\u044F UI-lib"
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
if (existsSync8(join8(cwd, "src/lib")) && !scripts["check:tokens"]) {
|
|
1420
|
+
issues.push({
|
|
1421
|
+
level: "info",
|
|
1422
|
+
code: "check-tokens",
|
|
1423
|
+
message: "\u041D\u0435\u0442 scripts.check:tokens \u2014 \u0440\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0443\u0435\u0442\u0441\u044F \u0434\u043B\u044F UI-lib \u0441 CSS Modules"
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
const deps = listElcrmDeps(pkg);
|
|
1428
|
+
if (deps.some((d) => d.name === "@elcrm/socket-server")) {
|
|
1429
|
+
issues.push({
|
|
1430
|
+
level: "error",
|
|
1431
|
+
code: "deprecated-socket-server",
|
|
1432
|
+
message: "\u0417\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C @elcrm/socket-server \u0443\u0441\u0442\u0430\u0440\u0435\u043B\u0430 \u2014 elcrm migrate socket-server",
|
|
1433
|
+
fixable: true
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
for (const name of ["@elcrm/db", "@elcrm/jwt", "@elcrm/cron", "@elcrm/tb"]) {
|
|
1437
|
+
if (deps.some((d) => d.name === name)) {
|
|
1438
|
+
issues.push({
|
|
1439
|
+
level: "error",
|
|
1440
|
+
code: `pkg-${name}`,
|
|
1441
|
+
message: `${name} \u2014 \u0441\u0435\u0440\u0432\u0435\u0440\u043D\u044B\u0439 \u043F\u0430\u043A\u0435\u0442; \u043D\u0430 \u0444\u0440\u043E\u043D\u0442\u0435 \u043D\u0435 \u043D\u0443\u0436\u0435\u043D (server: @elcrm/server/\u2026)`
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
const umbrella = pkg.dependencies?.elcrm || pkg.devDependencies?.elcrm;
|
|
1446
|
+
if (umbrella && /^[\^~]?0\.9\./.test(umbrella)) {
|
|
1447
|
+
issues.push({
|
|
1448
|
+
level: "warn",
|
|
1449
|
+
code: "legacy-umbrella",
|
|
1450
|
+
message: `\u0423\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0438\u0439 \u043F\u0430\u043A\u0435\u0442 elcrm@${umbrella} \u2014 \u043F\u0435\u0440\u0435\u0445\u043E\u0434\u0438\u0442\u0435 \u043D\u0430 @elcrm/* / @elcrm/core`
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
if (deps.length === 0 && !isLib) {
|
|
1454
|
+
issues.push({
|
|
1455
|
+
level: "info",
|
|
1456
|
+
code: "no-elcrm",
|
|
1457
|
+
message: "\u041D\u0435\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0435\u0439 @elcrm/*"
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
const files = [];
|
|
1461
|
+
walkCode(join8(cwd, "src"), files);
|
|
1462
|
+
walkCode(join8(cwd, "style"), files);
|
|
1463
|
+
for (const name of ["bootstrap.tsx", "bootstrap.ts", "main.tsx", "main.ts", "index.tsx", "index.ts", "App.tsx"]) {
|
|
1464
|
+
const full = join8(cwd, name);
|
|
1465
|
+
if (existsSync8(full))
|
|
1466
|
+
files.push(full);
|
|
1467
|
+
const inSrc = join8(cwd, "src", name);
|
|
1468
|
+
if (existsSync8(inSrc) && !files.includes(inSrc))
|
|
1469
|
+
files.push(inSrc);
|
|
1470
|
+
}
|
|
1471
|
+
for (const file of files) {
|
|
1472
|
+
const text = readFileSync12(file, "utf8");
|
|
1473
|
+
const rel2 = relative2(cwd, file);
|
|
1474
|
+
if (DEPRECATED_SOCKET_IMPORT_RE.test(text)) {
|
|
1475
|
+
issues.push({
|
|
1476
|
+
level: "error",
|
|
1477
|
+
code: "deprecated-import",
|
|
1478
|
+
message: "\u0438\u043C\u043F\u043E\u0440\u0442 @elcrm/socket/server \u0438\u043B\u0438 socket-server",
|
|
1479
|
+
file: rel2,
|
|
1480
|
+
fixable: true
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
const themesRe = new RegExp(THEMES_CSS_IMPORT_RE.source, "g");
|
|
1484
|
+
let tm;
|
|
1485
|
+
while (tm = themesRe.exec(text)) {
|
|
1486
|
+
const line = text.slice(0, tm.index).split(/\r?\n/).length;
|
|
1487
|
+
issues.push({
|
|
1488
|
+
level: "error",
|
|
1489
|
+
code: "themes-css",
|
|
1490
|
+
message: "import \u2026/themes.css \u0443\u0441\u0442\u0430\u0440\u0435\u043B \u2014 \u0442\u043E\u043A\u0435\u043D\u044B \u0437\u0430\u0434\u0430\u0451\u0442 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 (elcrm-theme / theme.css)",
|
|
1491
|
+
file: rel2,
|
|
1492
|
+
line
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
if (LEGACY_PASSKEY_IMPORT_RE.test(text) && LEGACY_PASSKEY_SYMBOL_RE.test(text)) {
|
|
1496
|
+
const lines2 = text.split(/\r?\n/);
|
|
1497
|
+
lines2.forEach((line, i) => {
|
|
1498
|
+
if (/\bcreatePasskey\b/.test(line) || /\bauthenticateWithPasskey\b/.test(line)) {
|
|
1499
|
+
if (pkg.name === "@elcrm/passkey")
|
|
1500
|
+
return;
|
|
1501
|
+
issues.push({
|
|
1502
|
+
level: "error",
|
|
1503
|
+
code: "legacy-passkey-api",
|
|
1504
|
+
message: "createPasskey / authenticateWithPasskey \u043D\u0435 \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u0443\u044E\u0442\u0441\u044F \u2014 createPasskeyClient",
|
|
1505
|
+
file: rel2,
|
|
1506
|
+
line: i + 1
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
const lines = text.split(/\r?\n/);
|
|
1512
|
+
lines.forEach((line, i) => {
|
|
1513
|
+
const trimmed = line.trim();
|
|
1514
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*")) {
|
|
1515
|
+
return;
|
|
1516
|
+
}
|
|
1517
|
+
if (/<(?:input|textarea|select)\b/.test(line)) {
|
|
1518
|
+
if (pkg.name === "@elcrm/form")
|
|
1519
|
+
return;
|
|
1520
|
+
issues.push({
|
|
1521
|
+
level: "error",
|
|
1522
|
+
code: "native-html-input",
|
|
1523
|
+
message: "\u043D\u0430\u0442\u0438\u0432\u043D\u044B\u0439 <input|textarea|select> \u2014 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435 @elcrm/form (StringField, Select, \u2026)",
|
|
1524
|
+
file: rel2,
|
|
1525
|
+
line: i + 1
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
if (/(?:^|\s)native(?:\s|$|=|\/|>)/.test(line) && !/\bnavigator\b|\bnativeEvent\b|\bcreateNative/.test(line)) {
|
|
1529
|
+
if (pkg.name === "@elcrm/form")
|
|
1530
|
+
return;
|
|
1531
|
+
issues.push({
|
|
1532
|
+
level: "warn",
|
|
1533
|
+
code: "form-native-prop",
|
|
1534
|
+
message: "prop native \u0443 \u043F\u043E\u043B\u044F \u0444\u043E\u0440\u043C\u044B \u2014 \u0443\u0431\u0435\u0440\u0438\u0442\u0435; \u0441\u0442\u0438\u043B\u0438 \u0447\u0435\u0440\u0435\u0437 \u0442\u043E\u043A\u0435\u043D\u044B @elcrm/form",
|
|
1535
|
+
file: rel2,
|
|
1536
|
+
line: i + 1
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
if (/size\s*=\s*["'](?:sm|md)["']/.test(line) && !/titleSize/.test(line)) {
|
|
1540
|
+
issues.push({
|
|
1541
|
+
level: "warn",
|
|
1542
|
+
code: "size-legacy",
|
|
1543
|
+
message: 'size="sm"|"md" \u0443\u0441\u0442\u0430\u0440\u0435\u043B\u043E \u2014 "s"|"m" (elcrm migrate size-sml)',
|
|
1544
|
+
file: rel2,
|
|
1545
|
+
line: i + 1,
|
|
1546
|
+
fixable: true
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
if (/Modal\.Add\s*\(/.test(line)) {
|
|
1550
|
+
issues.push({
|
|
1551
|
+
level: "warn",
|
|
1552
|
+
code: "modal-add",
|
|
1553
|
+
message: "Modal.Add \u2014 Modal.Create + Open (elcrm migrate modal-create)",
|
|
1554
|
+
file: rel2,
|
|
1555
|
+
line: i + 1,
|
|
1556
|
+
fixable: true
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
if (/--field-border-(?:focus|hover|error|disabled)\s*:\s*\d/.test(line) && /solid|dashed|dotted/i.test(line)) {
|
|
1560
|
+
issues.push({
|
|
1561
|
+
level: "error",
|
|
1562
|
+
code: "field-border-shorthand",
|
|
1563
|
+
message: "field-border hover/focus/disabled \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0446\u0432\u0435\u0442 (elcrm migrate field-border)",
|
|
1564
|
+
file: rel2,
|
|
1565
|
+
line: i + 1,
|
|
1566
|
+
fixable: true
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
if (lineHasDeprecatedFormToken(line)) {
|
|
1570
|
+
issues.push({
|
|
1571
|
+
level: "warn",
|
|
1572
|
+
code: "form-token-legacy",
|
|
1573
|
+
message: "legacy padding/shadow-\u0442\u043E\u043A\u0435\u043D \u2192 --field-padding* / --popup-shadow (elcrm migrate form-tokens)",
|
|
1574
|
+
file: rel2,
|
|
1575
|
+
line: i + 1,
|
|
1576
|
+
fixable: true
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
return issues;
|
|
1582
|
+
}
|
|
1583
|
+
async function runPkgScript(cwd, script) {
|
|
1584
|
+
const pkg = JSON.parse(readFileSync12(join8(cwd, "package.json"), "utf8"));
|
|
1585
|
+
if (!pkg.scripts?.[script])
|
|
1586
|
+
return { ran: false, ok: true };
|
|
1587
|
+
console.log(`\u2192 bun run ${script}`);
|
|
1588
|
+
const proc = Bun.spawn(["bun", "run", script], {
|
|
1589
|
+
cwd,
|
|
1590
|
+
stdout: "inherit",
|
|
1591
|
+
stderr: "inherit"
|
|
1592
|
+
});
|
|
1593
|
+
const code = await proc.exited;
|
|
1594
|
+
return { ran: true, ok: code === 0 };
|
|
1595
|
+
}
|
|
1596
|
+
async function runFrontScripts(cwd) {
|
|
1597
|
+
let failed = 0;
|
|
1598
|
+
for (const name of ["check:css", "check:tokens", "test"]) {
|
|
1599
|
+
const r = await runPkgScript(cwd, name);
|
|
1600
|
+
if (r.ran && !r.ok)
|
|
1601
|
+
failed++;
|
|
1602
|
+
}
|
|
1603
|
+
return failed;
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
// src/lib/test/print.ts
|
|
1607
|
+
function printIssues(issues, title) {
|
|
1608
|
+
if (title)
|
|
1609
|
+
console.log(title);
|
|
1610
|
+
if (issues.length === 0) {
|
|
1611
|
+
console.log(c.green("ok \u2014 \u0437\u0430\u043C\u0435\u0447\u0430\u043D\u0438\u0439 \u043D\u0435\u0442"));
|
|
1612
|
+
return 0;
|
|
1613
|
+
}
|
|
1614
|
+
let errors = 0;
|
|
1615
|
+
for (const issue of issues) {
|
|
1616
|
+
const mark = issue.level === "error" ? "E" : issue.level === "warn" ? "W" : "I";
|
|
1617
|
+
if (issue.level === "error")
|
|
1618
|
+
errors++;
|
|
1619
|
+
const loc = issue.file != null ? ` ${issue.file}${issue.line != null ? `:${issue.line}` : ""}` : "";
|
|
1620
|
+
const color = issue.level === "error" ? c.yellow : issue.level === "warn" ? c.yellow : c.gray;
|
|
1621
|
+
console.log(color(`[${mark}] ${issue.code}${loc}: ${issue.message}`));
|
|
1622
|
+
}
|
|
1623
|
+
return errors;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
933
1626
|
// src/lib/test/serverScan.ts
|
|
934
|
-
import { existsSync as
|
|
935
|
-
import { join as
|
|
1627
|
+
import { existsSync as existsSync9, readdirSync as readdirSync6, readFileSync as readFileSync13, statSync as statSync6 } from "fs";
|
|
1628
|
+
import { join as join9, relative as relative3 } from "path";
|
|
936
1629
|
|
|
937
1630
|
// src/lib/test/serverRules.ts
|
|
938
1631
|
var CODE_RULES = [
|
|
@@ -1054,6 +1747,14 @@ var CODE_RULES = [
|
|
|
1054
1747
|
}
|
|
1055
1748
|
]
|
|
1056
1749
|
},
|
|
1750
|
+
{
|
|
1751
|
+
id: "manual-loadEnvAtStartup",
|
|
1752
|
+
severity: "error",
|
|
1753
|
+
test: /\bloadEnvAtStartup\s*\(/,
|
|
1754
|
+
message: "\u0440\u0443\u0447\u043D\u043E\u0439 loadEnvAtStartup() \u2014 \u043B\u0435\u0433\u0430\u0441\u0438; \u0434\u043E createServer \u2014 ensureEnvAutoLoaded()",
|
|
1755
|
+
fix: "await ensureEnvAutoLoaded() (\u043E\u0434\u0438\u043D \u0440\u0430\u0437 \u0432 start); createServer/mail \u0441\u0430\u043C\u0438 \u0434\u043E\u0433\u0440\u0443\u0436\u0430\u044E\u0442. \u041D\u0435 top-level await \u0432 \u043F\u0430\u043A\u0435\u0442\u0435 (bun build \u043B\u043E\u043C\u0430\u0435\u0442)",
|
|
1756
|
+
auto: false
|
|
1757
|
+
},
|
|
1057
1758
|
{
|
|
1058
1759
|
id: "createJwtFromEnv",
|
|
1059
1760
|
severity: "warn",
|
|
@@ -1229,7 +1930,7 @@ var PKG_LEGACY = [
|
|
|
1229
1930
|
];
|
|
1230
1931
|
|
|
1231
1932
|
// src/lib/test/serverScan.ts
|
|
1232
|
-
var
|
|
1933
|
+
var SKIP2 = new Set([
|
|
1233
1934
|
"node_modules",
|
|
1234
1935
|
"dist",
|
|
1235
1936
|
".git",
|
|
@@ -1238,141 +1939,581 @@ var SKIP3 = new Set([
|
|
|
1238
1939
|
"vendor"
|
|
1239
1940
|
]);
|
|
1240
1941
|
function walkFiles2(root, dir, out) {
|
|
1241
|
-
if (!
|
|
1942
|
+
if (!existsSync9(dir))
|
|
1943
|
+
return;
|
|
1944
|
+
for (const name of readdirSync6(dir)) {
|
|
1945
|
+
if (SKIP2.has(name))
|
|
1946
|
+
continue;
|
|
1947
|
+
const full = join9(dir, name);
|
|
1948
|
+
let st;
|
|
1949
|
+
try {
|
|
1950
|
+
st = statSync6(full);
|
|
1951
|
+
} catch {
|
|
1952
|
+
continue;
|
|
1953
|
+
}
|
|
1954
|
+
if (st.isDirectory())
|
|
1955
|
+
walkFiles2(root, full, out);
|
|
1956
|
+
else if (/\.(tsx?|jsx?|mjs|cjs)$/.test(name) || name === "package.json") {
|
|
1957
|
+
out.push(full);
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
function scanCodeFile(root, file, text) {
|
|
1962
|
+
const findings = [];
|
|
1963
|
+
const lines = text.split(/\r?\n/);
|
|
1964
|
+
const rel2 = relative3(root, file) || file;
|
|
1965
|
+
for (const rule of CODE_RULES) {
|
|
1966
|
+
lines.forEach((line, i) => {
|
|
1967
|
+
rule.test.lastIndex = 0;
|
|
1968
|
+
if (!rule.test.test(line))
|
|
1969
|
+
return;
|
|
1970
|
+
rule.test.lastIndex = 0;
|
|
1971
|
+
const m = line.match(rule.test);
|
|
1972
|
+
findings.push({
|
|
1973
|
+
ruleId: rule.id,
|
|
1974
|
+
severity: rule.severity,
|
|
1975
|
+
file: rel2,
|
|
1976
|
+
line: i + 1,
|
|
1977
|
+
match: (m?.[0] ?? line).trim().slice(0, 120),
|
|
1978
|
+
message: rule.message,
|
|
1979
|
+
fix: rule.fix,
|
|
1980
|
+
auto: rule.auto
|
|
1981
|
+
});
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1984
|
+
return findings;
|
|
1985
|
+
}
|
|
1986
|
+
function scanPackageJson(root, file, text) {
|
|
1987
|
+
const findings = [];
|
|
1988
|
+
const rel2 = relative3(root, file) || file;
|
|
1989
|
+
let pkg;
|
|
1990
|
+
try {
|
|
1991
|
+
pkg = JSON.parse(text);
|
|
1992
|
+
} catch {
|
|
1993
|
+
return findings;
|
|
1994
|
+
}
|
|
1995
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1996
|
+
for (const name of PKG_LEGACY) {
|
|
1997
|
+
if (deps[name]) {
|
|
1998
|
+
findings.push({
|
|
1999
|
+
ruleId: `pkg-${name}`,
|
|
2000
|
+
severity: "error",
|
|
2001
|
+
file: rel2,
|
|
2002
|
+
line: 1,
|
|
2003
|
+
match: `"${name}": "${deps[name]}"`,
|
|
2004
|
+
message: `\u0417\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C ${name} \u2192 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435 @elcrm/server (+ subpath)`,
|
|
2005
|
+
fix: `\u0423\u0434\u0430\u043B\u0438\u0442\u044C ${name}, \u0434\u043E\u0431\u0430\u0432\u0438\u0442\u044C/\u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C "@elcrm/server": ">=0.6.0"`,
|
|
2006
|
+
auto: true
|
|
2007
|
+
});
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
if ((deps["@elcrm/db"] || deps["@elcrm/jwt"] || deps["@elcrm/cron"] || deps["@elcrm/tb"]) && !deps["@elcrm/server"]) {
|
|
2011
|
+
findings.push({
|
|
2012
|
+
ruleId: "pkg-need-server",
|
|
2013
|
+
severity: "error",
|
|
2014
|
+
file: rel2,
|
|
2015
|
+
line: 1,
|
|
2016
|
+
match: "missing @elcrm/server",
|
|
2017
|
+
message: "\u041D\u0443\u0436\u043D\u0430 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C @elcrm/server",
|
|
2018
|
+
fix: `"@elcrm/server": ">=0.6.0"`,
|
|
2019
|
+
auto: true
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
2022
|
+
return findings;
|
|
2023
|
+
}
|
|
2024
|
+
function scanServerProject(root) {
|
|
2025
|
+
const files = [];
|
|
2026
|
+
walkFiles2(root, root, files);
|
|
2027
|
+
const findings = [];
|
|
2028
|
+
for (const file of files) {
|
|
2029
|
+
const text = readFileSync13(file, "utf8");
|
|
2030
|
+
if (file.endsWith("package.json")) {
|
|
2031
|
+
findings.push(...scanPackageJson(root, file, text));
|
|
2032
|
+
} else {
|
|
2033
|
+
findings.push(...scanCodeFile(root, file, text));
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
return { root, findings, filesScanned: files.length };
|
|
2037
|
+
}
|
|
2038
|
+
function printServerFindings(result) {
|
|
2039
|
+
const { findings, filesScanned, root } = result;
|
|
2040
|
+
const errors = findings.filter((f) => f.severity === "error");
|
|
2041
|
+
const warns = findings.filter((f) => f.severity === "warn");
|
|
2042
|
+
const infos = findings.filter((f) => f.severity === "info");
|
|
2043
|
+
console.log(`
|
|
2044
|
+
elcrm test (server) \u2014 ${root}`);
|
|
2045
|
+
console.log(`\u0444\u0430\u0439\u043B\u043E\u0432: ${filesScanned}, \u043F\u0440\u043E\u0431\u043B\u0435\u043C: ${findings.length}`);
|
|
2046
|
+
console.log(` error: ${errors.length} warn: ${warns.length} info: ${infos.length}
|
|
2047
|
+
`);
|
|
2048
|
+
const byFile = new Map;
|
|
2049
|
+
for (const f of findings) {
|
|
2050
|
+
const list = byFile.get(f.file) ?? [];
|
|
2051
|
+
list.push(f);
|
|
2052
|
+
byFile.set(f.file, list);
|
|
2053
|
+
}
|
|
2054
|
+
for (const [file, list] of [...byFile.entries()].sort()) {
|
|
2055
|
+
console.log(`${file}`);
|
|
2056
|
+
for (const f of list) {
|
|
2057
|
+
const tag = f.severity === "error" ? "ERR " : f.severity === "warn" ? "WARN" : "INFO";
|
|
2058
|
+
console.log(` ${tag} L${f.line} [${f.ruleId}] ${f.message}`);
|
|
2059
|
+
console.log(` ${f.match}`);
|
|
2060
|
+
if (f.fix)
|
|
2061
|
+
console.log(` \u2192 ${f.fix}`);
|
|
2062
|
+
if (f.auto)
|
|
2063
|
+
console.log(` (auto: bunx elcrm-server update)`);
|
|
2064
|
+
else
|
|
2065
|
+
console.log(` (\u0432\u0440\u0443\u0447\u043D\u0443\u044E \u2014 MPC.md \u0432 @elcrm/server)`);
|
|
2066
|
+
}
|
|
2067
|
+
console.log("");
|
|
2068
|
+
}
|
|
2069
|
+
if (findings.length === 0) {
|
|
2070
|
+
console.log(`ok \u2014 \u0441\u0435\u0440\u0432\u0435\u0440\u043D\u043E\u0435 \u043B\u0435\u0433\u0430\u0441\u0438 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E
|
|
2071
|
+
`);
|
|
2072
|
+
}
|
|
2073
|
+
return errors.length;
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
// src/commands/test.ts
|
|
2077
|
+
async function testOneDir(cwd, force, runScripts) {
|
|
2078
|
+
const pkg = readPkgAt(cwd);
|
|
2079
|
+
if (!pkg) {
|
|
2080
|
+
console.error(`package.json \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${cwd}`);
|
|
2081
|
+
return 1;
|
|
2082
|
+
}
|
|
2083
|
+
const mode = detectTestMode(pkg, force);
|
|
2084
|
+
console.log(c.blue(`
|
|
2085
|
+
elcrm test \u2014 ${cwd}
|
|
2086
|
+
\u0440\u0435\u0436\u0438\u043C: ${mode}${force ? " (\u0444\u043B\u0430\u0433)" : " (\u043F\u043E package.json)"}`));
|
|
2087
|
+
if (mode === "server") {
|
|
2088
|
+
const result = scanServerProject(cwd);
|
|
2089
|
+
return printServerFindings(result);
|
|
2090
|
+
}
|
|
2091
|
+
const issues = collectFrontIssues(cwd, pkg);
|
|
2092
|
+
let errors = printIssues(issues, `
|
|
2093
|
+
\u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438\u0439 \u0430\u043D\u0430\u043B\u0438\u0437:`);
|
|
2094
|
+
if (runScripts) {
|
|
2095
|
+
console.log("");
|
|
2096
|
+
const scriptFails = await runFrontScripts(cwd);
|
|
2097
|
+
errors += scriptFails;
|
|
2098
|
+
} else {
|
|
2099
|
+
console.log(c.gray(`
|
|
2100
|
+
(--no-scripts: check:css / check:tokens / test \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D\u044B)`));
|
|
2101
|
+
}
|
|
2102
|
+
return errors;
|
|
2103
|
+
}
|
|
2104
|
+
async function cmdTest(parsed) {
|
|
2105
|
+
const target = resolve(parsed.positionals[0] ?? process.cwd());
|
|
2106
|
+
const force = hasFlag(parsed, "server") ? "server" : hasFlag(parsed, "front") ? "front" : null;
|
|
2107
|
+
const runScripts = !hasFlag(parsed, "no-scripts");
|
|
2108
|
+
const rootPkg = readPkgAt(target);
|
|
2109
|
+
if (!rootPkg) {
|
|
2110
|
+
console.error("package.json \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D.");
|
|
2111
|
+
process.exit(1);
|
|
2112
|
+
}
|
|
2113
|
+
const workspaces = listWorkspaceDirs(target, rootPkg);
|
|
2114
|
+
let totalErrors = 0;
|
|
2115
|
+
if (workspaces.length > 0 && !force) {
|
|
2116
|
+
console.log(c.dim(`workspaces: ${workspaces.map((d) => d.replace(target + "/", "")).join(", ")}`));
|
|
2117
|
+
const rootHasElcrm = Boolean(rootPkg.dependencies && Object.keys(rootPkg.dependencies).some((k) => k.startsWith("@elcrm/"))) || Boolean(rootPkg.devDependencies && Object.keys(rootPkg.devDependencies).some((k) => k.startsWith("@elcrm/")));
|
|
2118
|
+
if (rootHasElcrm || hasFlag(parsed, "root")) {
|
|
2119
|
+
totalErrors += await testOneDir(target, force, runScripts);
|
|
2120
|
+
}
|
|
2121
|
+
for (const dir of workspaces) {
|
|
2122
|
+
totalErrors += await testOneDir(dir, force, runScripts);
|
|
2123
|
+
}
|
|
2124
|
+
} else {
|
|
2125
|
+
totalErrors += await testOneDir(target, force, runScripts);
|
|
2126
|
+
}
|
|
2127
|
+
if (totalErrors > 0) {
|
|
2128
|
+
console.log(c.yellow(`
|
|
2129
|
+
elcrm test: ${totalErrors} error(s)`));
|
|
2130
|
+
process.exit(1);
|
|
2131
|
+
}
|
|
2132
|
+
console.log(c.green(`
|
|
2133
|
+
elcrm test: ok`));
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
// src/commands/update.ts
|
|
2137
|
+
async function cmdUpdate(parsed) {
|
|
2138
|
+
const cwd = process.cwd();
|
|
2139
|
+
const pkg = readPackageJson(cwd);
|
|
2140
|
+
if (!pkg) {
|
|
2141
|
+
console.error("package.json \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u0432 \u0442\u0435\u043A\u0443\u0449\u0435\u043C \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0435.");
|
|
2142
|
+
process.exit(1);
|
|
2143
|
+
}
|
|
2144
|
+
const deps = listElcrmDeps(pkg);
|
|
2145
|
+
if (deps.length === 0) {
|
|
2146
|
+
console.log("\u0417\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0435\u0439 @elcrm/* \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E.");
|
|
2147
|
+
return;
|
|
2148
|
+
}
|
|
2149
|
+
const dry = hasFlag(parsed, "dry");
|
|
2150
|
+
const useCore = hasFlag(parsed, "core");
|
|
2151
|
+
const noInstall = hasFlag(parsed, "no-install");
|
|
2152
|
+
const doFix = hasFlag(parsed, "fix");
|
|
2153
|
+
const doTest = hasFlag(parsed, "test");
|
|
2154
|
+
const names = uniqueElcrmNames(deps);
|
|
2155
|
+
console.log(`\u041D\u0430\u0439\u0434\u0435\u043D\u043E @elcrm/*: ${names.length}`);
|
|
2156
|
+
let versions;
|
|
2157
|
+
if (useCore) {
|
|
2158
|
+
console.log("\u0420\u0435\u0436\u0438\u043C --core: \u0432\u0435\u0440\u0441\u0438\u0438 \u0438\u0437 \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u043D\u043E\u0433\u043E @elcrm/core\u2026");
|
|
2159
|
+
versions = await fetchCorePinnedVersions();
|
|
2160
|
+
const missing = names.filter((n) => !versions.has(n));
|
|
2161
|
+
if (missing.length) {
|
|
2162
|
+
const extra = await fetchLatestMap(missing);
|
|
2163
|
+
for (const [k, v] of extra)
|
|
2164
|
+
versions.set(k, v);
|
|
2165
|
+
}
|
|
2166
|
+
} else {
|
|
2167
|
+
console.log("\u0417\u0430\u043F\u0440\u043E\u0441 latest \u0441 npm registry\u2026");
|
|
2168
|
+
versions = await fetchLatestMap(names);
|
|
2169
|
+
}
|
|
2170
|
+
let changed = 0;
|
|
2171
|
+
const rows = [];
|
|
2172
|
+
for (const dep of deps) {
|
|
2173
|
+
const latest = versions.get(dep.name);
|
|
2174
|
+
if (!latest) {
|
|
2175
|
+
rows.push(` ${c.yellow("?")} ${dep.name}: ${c.gray(dep.version)} \u2192 ${c.yellow("(\u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E \u043D\u0430 npm)")}`);
|
|
2176
|
+
continue;
|
|
2177
|
+
}
|
|
2178
|
+
const current = dep.version.replace(/^[\^~]/, "");
|
|
2179
|
+
if (current === latest) {
|
|
2180
|
+
rows.push(` ${c.gray("=")} ${dep.name}: ${c.gray(dep.version)}`);
|
|
2181
|
+
continue;
|
|
2182
|
+
}
|
|
2183
|
+
const prefix = dep.version.startsWith("^") ? "^" : dep.version.startsWith("~") ? "~" : "";
|
|
2184
|
+
const next = `${prefix}${latest}`;
|
|
2185
|
+
rows.push(` ${c.blue("\u2191")} ${dep.name}: ${c.gray(dep.version)} \u2192 ${c.green(next)}`);
|
|
2186
|
+
if (!dry) {
|
|
2187
|
+
const section = pkg[dep.section];
|
|
2188
|
+
section[dep.name] = next;
|
|
2189
|
+
changed++;
|
|
2190
|
+
} else {
|
|
2191
|
+
changed++;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
console.log(rows.join(`
|
|
2195
|
+
`));
|
|
2196
|
+
if (dry) {
|
|
2197
|
+
console.log(`
|
|
2198
|
+
--dry: \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u043E (\u043A \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u044E: ${changed}).`);
|
|
2199
|
+
if (doFix) {
|
|
2200
|
+
console.log(`
|
|
2201
|
+
--fix + --dry: \u043F\u0440\u0435\u0432\u044C\u044E migrate front\u2026`);
|
|
2202
|
+
const front = await migrateFrontAll(cwd, true);
|
|
2203
|
+
if (front.length === 0)
|
|
2204
|
+
console.log(" \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0439 \u043A\u043E\u0434\u0430 \u043D\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F.");
|
|
2205
|
+
else
|
|
2206
|
+
for (const c2 of front)
|
|
2207
|
+
console.log(` ${c2.file}: ${c2.detail}`);
|
|
2208
|
+
console.log(`
|
|
2209
|
+
--fix + --dry: \u043F\u0440\u0435\u0432\u044C\u044E CSS-\u0442\u043E\u043A\u0435\u043D\u043E\u0432\u2026`);
|
|
2210
|
+
try {
|
|
2211
|
+
const css = syncElcrmCss({ cwd, pkgNames: names, dry: true });
|
|
2212
|
+
let n = 0;
|
|
2213
|
+
for (const [file, tokens] of Object.entries(css.added)) {
|
|
2214
|
+
if (!tokens.length)
|
|
2215
|
+
continue;
|
|
2216
|
+
n += tokens.length;
|
|
2217
|
+
console.log(` ${file}: ${tokens.join(", ")}`);
|
|
2218
|
+
}
|
|
2219
|
+
if (n === 0)
|
|
2220
|
+
console.log(" \u041D\u043E\u0432\u044B\u0445 \u0442\u043E\u043A\u0435\u043D\u043E\u0432 \u043D\u0435\u0442.");
|
|
2221
|
+
} catch (e) {
|
|
2222
|
+
console.log(` ${c.gray("css:")} ${e instanceof Error ? e.message : e}`);
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
1242
2225
|
return;
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
2226
|
+
}
|
|
2227
|
+
if (changed === 0) {
|
|
2228
|
+
console.log(`
|
|
2229
|
+
\u0412\u0441\u0435 @elcrm/* \u0443\u0436\u0435 \u043D\u0430 \u0430\u043A\u0442\u0443\u0430\u043B\u044C\u043D\u044B\u0445 \u0432\u0435\u0440\u0441\u0438\u044F\u0445.`);
|
|
2230
|
+
} else {
|
|
2231
|
+
writePackageJson(pkg, cwd);
|
|
2232
|
+
console.log(`
|
|
2233
|
+
package.json \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D (${changed}).`);
|
|
2234
|
+
if (!noInstall) {
|
|
2235
|
+
const ok = installDeps(cwd);
|
|
2236
|
+
if (!ok) {
|
|
2237
|
+
console.error("\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0435\u0439 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C \u0441 \u043E\u0448\u0438\u0431\u043A\u043E\u0439.");
|
|
2238
|
+
process.exit(1);
|
|
2239
|
+
}
|
|
2240
|
+
} else {
|
|
2241
|
+
console.log("\u041F\u0440\u043E\u043F\u0443\u0441\u043A install (--no-install). \u0417\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u0435 bun/npm install \u0432\u0440\u0443\u0447\u043D\u0443\u044E.");
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
if (doFix) {
|
|
2245
|
+
console.log(`
|
|
2246
|
+
--fix: migrate front (form-aliases, size, \u2026)\u2026`);
|
|
2247
|
+
const front = await migrateFrontAll(cwd, false);
|
|
2248
|
+
if (front.length === 0) {
|
|
2249
|
+
console.log(" \u0418\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439 \u043A\u043E\u0434\u0430 \u043D\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F.");
|
|
2250
|
+
} else {
|
|
2251
|
+
for (const ch of front)
|
|
2252
|
+
console.log(` ${ch.file}: ${ch.detail}`);
|
|
2253
|
+
console.log(` \u041F\u0440\u0438\u043C\u0435\u043D\u0435\u043D\u043E: ${front.length}.`);
|
|
2254
|
+
}
|
|
2255
|
+
console.log(`
|
|
2256
|
+
--fix: sync CSS-\u0442\u043E\u043A\u0435\u043D\u043E\u0432 (elcrm css)\u2026`);
|
|
1248
2257
|
try {
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
2258
|
+
const pkgAfter = readPackageJson(cwd) ?? pkg;
|
|
2259
|
+
const cssNames = uniqueElcrmNames(listElcrmDeps(pkgAfter));
|
|
2260
|
+
const css = syncElcrmCss({ cwd, pkgNames: cssNames, dry: false });
|
|
2261
|
+
let added = 0;
|
|
2262
|
+
for (const [file, tokens] of Object.entries(css.added)) {
|
|
2263
|
+
if (!tokens.length)
|
|
2264
|
+
continue;
|
|
2265
|
+
added += tokens.length;
|
|
2266
|
+
console.log(` ${c.green("+")} ${file}: ${tokens.join(", ")}`);
|
|
2267
|
+
}
|
|
2268
|
+
if (css.imports.length) {
|
|
2269
|
+
console.log(` ${c.green("+")} elcrm.css @import: ${css.imports.join(", ")}`);
|
|
2270
|
+
}
|
|
2271
|
+
if (added === 0 && css.imports.length === 0) {
|
|
2272
|
+
console.log(" \u0422\u043E\u043A\u0435\u043D\u044B \u0443\u0436\u0435 \u043D\u0430 \u043C\u0435\u0441\u0442\u0435.");
|
|
2273
|
+
}
|
|
2274
|
+
} catch (e) {
|
|
2275
|
+
console.log(` ${c.gray("css sync \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D:")} ${e instanceof Error ? e.message : e}`);
|
|
1252
2276
|
}
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
2277
|
+
}
|
|
2278
|
+
if (doTest) {
|
|
2279
|
+
console.log(`
|
|
2280
|
+
--test: elcrm test\u2026`);
|
|
2281
|
+
await cmdTest(parseArgs(["test"]));
|
|
2282
|
+
} else if (doFix) {
|
|
2283
|
+
console.log(`
|
|
2284
|
+
\u0414\u0430\u043B\u044C\u0448\u0435: elcrm test (\u0438\u043B\u0438 elcrm update --fix --test)`);
|
|
2285
|
+
} else if (changed > 0) {
|
|
2286
|
+
console.log(`
|
|
2287
|
+
\u041F\u043E\u0441\u043B\u0435 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u044F @elcrm/form: elcrm update --fix --test`);
|
|
2288
|
+
console.log(" (\u0430\u043B\u0438\u0430\u0441\u044B \u043F\u043E\u043B\u0435\u0439 + form-tokens + \u043D\u0435\u0434\u043E\u0441\u0442\u0430\u044E\u0449\u0438\u0435 CSS-\u0442\u043E\u043A\u0435\u043D\u044B)");
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
// src/lib/copyTemplate.ts
|
|
2293
|
+
import {
|
|
2294
|
+
cpSync,
|
|
2295
|
+
existsSync as existsSync11,
|
|
2296
|
+
mkdirSync,
|
|
2297
|
+
readdirSync as readdirSync7,
|
|
2298
|
+
readFileSync as readFileSync14,
|
|
2299
|
+
statSync as statSync7,
|
|
2300
|
+
writeFileSync as writeFileSync5
|
|
2301
|
+
} from "fs";
|
|
2302
|
+
import { join as join11, resolve as resolve2 } from "path";
|
|
2303
|
+
|
|
2304
|
+
// src/lib/paths.ts
|
|
2305
|
+
import { existsSync as existsSync10 } from "fs";
|
|
2306
|
+
import { dirname as dirname2, join as join10 } from "path";
|
|
2307
|
+
import { fileURLToPath } from "url";
|
|
2308
|
+
function packageRoot() {
|
|
2309
|
+
const here = dirname2(fileURLToPath(import.meta.url));
|
|
2310
|
+
const candidates = [here, join10(here, ".."), join10(here, "..", "..")];
|
|
2311
|
+
for (const dir of candidates) {
|
|
2312
|
+
if (existsSync10(join10(dir, "package.json")) && existsSync10(join10(dir, "templates"))) {
|
|
2313
|
+
return dir;
|
|
1257
2314
|
}
|
|
1258
2315
|
}
|
|
2316
|
+
for (const dir of candidates) {
|
|
2317
|
+
if (existsSync10(join10(dir, "package.json")))
|
|
2318
|
+
return dir;
|
|
2319
|
+
}
|
|
2320
|
+
return join10(here, "..");
|
|
1259
2321
|
}
|
|
1260
|
-
function
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
2322
|
+
function templatesDir() {
|
|
2323
|
+
return join10(packageRoot(), "templates");
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
// src/lib/copyTemplate.ts
|
|
2327
|
+
var SKIP3 = new Set(["node_modules", "dist", ".git"]);
|
|
2328
|
+
function folderToPackageName(projectPath) {
|
|
2329
|
+
const base = projectPath.replace(/\\/g, "/").split("/").filter(Boolean).pop() ?? "app";
|
|
2330
|
+
const slug = base.toLowerCase().replace(/[^a-z0-9-._~/]/g, "-").replace(/^[-_.]+|[-_.]+$/g, "");
|
|
2331
|
+
return slug || "app";
|
|
2332
|
+
}
|
|
2333
|
+
function isEmptyDir(dir) {
|
|
2334
|
+
if (!existsSync11(dir))
|
|
2335
|
+
return true;
|
|
2336
|
+
const entries = readdirSync7(dir).filter((e) => e !== ".git" && e !== ".DS_Store");
|
|
2337
|
+
return entries.length === 0;
|
|
2338
|
+
}
|
|
2339
|
+
function writePkgName(pkgPath, name) {
|
|
2340
|
+
if (!existsSync11(pkgPath))
|
|
2341
|
+
return;
|
|
2342
|
+
const pkg = JSON.parse(readFileSync14(pkgPath, "utf8"));
|
|
2343
|
+
pkg.name = name;
|
|
2344
|
+
pkg.version = pkg.version ?? "0.0.0";
|
|
2345
|
+
writeFileSync5(pkgPath, JSON.stringify(pkg, null, 2) + `
|
|
2346
|
+
`, "utf8");
|
|
2347
|
+
}
|
|
2348
|
+
function finalizePanelNames(targetDir, baseName) {
|
|
2349
|
+
writePkgName(join11(targetDir, "package.json"), baseName);
|
|
2350
|
+
writePkgName(join11(targetDir, "web", "package.json"), `${baseName}-web`);
|
|
2351
|
+
writePkgName(join11(targetDir, "api", "package.json"), `${baseName}-api`);
|
|
2352
|
+
}
|
|
2353
|
+
function copyTemplate(templateName, targetDir, packageName) {
|
|
2354
|
+
const src = join11(templatesDir(), templateName);
|
|
2355
|
+
if (!existsSync11(src) || !statSync7(src).isDirectory()) {
|
|
2356
|
+
throw new Error(`\u0428\u0430\u0431\u043B\u043E\u043D \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${templateName}
|
|
2357
|
+
\u041E\u0436\u0438\u0434\u0430\u043B\u0441\u044F \u043A\u0430\u0442\u0430\u043B\u043E\u0433: ${src}`);
|
|
1282
2358
|
}
|
|
1283
|
-
|
|
2359
|
+
mkdirSync(targetDir, { recursive: true });
|
|
2360
|
+
for (const entry of readdirSync7(src)) {
|
|
2361
|
+
if (SKIP3.has(entry))
|
|
2362
|
+
continue;
|
|
2363
|
+
const from = join11(src, entry);
|
|
2364
|
+
const to = join11(targetDir, entry);
|
|
2365
|
+
cpSync(from, to, { recursive: true });
|
|
2366
|
+
}
|
|
2367
|
+
const name = packageName ?? folderToPackageName(targetDir);
|
|
2368
|
+
if (templateName === "panel") {
|
|
2369
|
+
finalizePanelNames(targetDir, name);
|
|
2370
|
+
return;
|
|
2371
|
+
}
|
|
2372
|
+
writePkgName(join11(targetDir, "package.json"), name);
|
|
1284
2373
|
}
|
|
1285
|
-
function
|
|
1286
|
-
const
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
2374
|
+
function listTemplates() {
|
|
2375
|
+
const dir = templatesDir();
|
|
2376
|
+
if (!existsSync11(dir))
|
|
2377
|
+
return [];
|
|
2378
|
+
return readdirSync7(dir).filter((name) => {
|
|
2379
|
+
const p = join11(dir, name);
|
|
2380
|
+
return statSync7(p).isDirectory() && !SKIP3.has(name);
|
|
2381
|
+
});
|
|
2382
|
+
}
|
|
2383
|
+
function resolveTarget(pathOrName, cwd = process.cwd()) {
|
|
2384
|
+
return resolve2(cwd, pathOrName);
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
// src/commands/create.ts
|
|
2388
|
+
function libPackageName(pathOrName) {
|
|
2389
|
+
let slug = folderToPackageName(pathOrName);
|
|
2390
|
+
slug = slug.replace(/^@elcrm\//, "").replace(/^elcrm[._-]/, "");
|
|
2391
|
+
return `@elcrm/${slug || "lib"}`;
|
|
2392
|
+
}
|
|
2393
|
+
function doneHint(template, cdPath) {
|
|
2394
|
+
const prefix = cdPath ? ` cd ${cdPath}
|
|
2395
|
+
` : "";
|
|
2396
|
+
if (template === "panel") {
|
|
2397
|
+
console.log(`
|
|
2398
|
+
\u0413\u043E\u0442\u043E\u0432\u043E (panel: web + api).
|
|
2399
|
+
|
|
2400
|
+
${prefix} bun run dev
|
|
2401
|
+
|
|
2402
|
+
Web \u2192 http://localhost:5173
|
|
2403
|
+
API \u2192 http://localhost:4545
|
|
2404
|
+
\u0412\u0445\u043E\u0434: admin / admin
|
|
2405
|
+
`);
|
|
2406
|
+
return;
|
|
1293
2407
|
}
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
});
|
|
1307
|
-
}
|
|
2408
|
+
console.log(`
|
|
2409
|
+
\u0413\u043E\u0442\u043E\u0432\u043E.
|
|
2410
|
+
|
|
2411
|
+
${prefix} bun run dev
|
|
2412
|
+
`);
|
|
2413
|
+
}
|
|
2414
|
+
async function cmdCreate(parsed) {
|
|
2415
|
+
const name = parsed.positionals[0];
|
|
2416
|
+
if (!name) {
|
|
2417
|
+
console.error("\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0438\u043C\u044F \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0430: elcrm create <name>");
|
|
2418
|
+
console.error(`\u0428\u0430\u0431\u043B\u043E\u043D\u044B: ${listTemplates().join(", ") || "(\u043D\u0435\u0442)"}`);
|
|
2419
|
+
process.exit(1);
|
|
1308
2420
|
}
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
match: "missing @elcrm/server",
|
|
1316
|
-
message: "\u041D\u0443\u0436\u043D\u0430 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C @elcrm/server",
|
|
1317
|
-
fix: `"@elcrm/server": ">=0.6.0"`,
|
|
1318
|
-
auto: true
|
|
1319
|
-
});
|
|
2421
|
+
const template = parsed.options.template ?? "app";
|
|
2422
|
+
const target = resolveTarget(name);
|
|
2423
|
+
const noInstall = hasFlag(parsed, "no-install");
|
|
2424
|
+
if (!isEmptyDir(target)) {
|
|
2425
|
+
console.error(`\u041A\u0430\u0442\u0430\u043B\u043E\u0433 \u043D\u0435 \u043F\u0443\u0441\u0442: ${target}`);
|
|
2426
|
+
process.exit(1);
|
|
1320
2427
|
}
|
|
1321
|
-
|
|
2428
|
+
const pkgName = template === "lib" && !name.startsWith("@") ? libPackageName(name) : folderToPackageName(name);
|
|
2429
|
+
console.log(`\u0428\u0430\u0431\u043B\u043E\u043D: ${template}`);
|
|
2430
|
+
console.log(`\u041A\u0430\u0442\u0430\u043B\u043E\u0433: ${target}`);
|
|
2431
|
+
console.log(`package name: ${pkgName}`);
|
|
2432
|
+
copyTemplate(template, target, pkgName);
|
|
2433
|
+
console.log("\u0424\u0430\u0439\u043B\u044B \u0441\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u043D\u044B.");
|
|
2434
|
+
if (!noInstall) {
|
|
2435
|
+
const ok = installDeps(target);
|
|
2436
|
+
if (!ok)
|
|
2437
|
+
process.exit(1);
|
|
2438
|
+
}
|
|
2439
|
+
doneHint(template, name);
|
|
1322
2440
|
}
|
|
1323
|
-
function
|
|
1324
|
-
const
|
|
1325
|
-
|
|
1326
|
-
const
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
2441
|
+
async function cmdInit(parsed) {
|
|
2442
|
+
const template = parsed.options.template ?? "app";
|
|
2443
|
+
const cwd = process.cwd();
|
|
2444
|
+
const force = hasFlag(parsed, "force");
|
|
2445
|
+
const noInstall = hasFlag(parsed, "no-install");
|
|
2446
|
+
if (!force && !isEmptyDir(cwd)) {
|
|
2447
|
+
console.error("\u0422\u0435\u043A\u0443\u0449\u0438\u0439 \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u043D\u0435 \u043F\u0443\u0441\u0442. \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435 --force \u0438\u043B\u0438 elcrm create <name>.");
|
|
2448
|
+
process.exit(1);
|
|
2449
|
+
}
|
|
2450
|
+
const pkgName = template === "lib" ? libPackageName(cwd) : folderToPackageName(cwd);
|
|
2451
|
+
console.log(`\u0428\u0430\u0431\u043B\u043E\u043D: ${template}`);
|
|
2452
|
+
console.log(`\u041A\u0430\u0442\u0430\u043B\u043E\u0433: ${cwd}`);
|
|
2453
|
+
copyTemplate(template, cwd, pkgName);
|
|
2454
|
+
console.log("\u041F\u0440\u043E\u0435\u043A\u0442 \u0438\u043D\u0438\u0446\u0438\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D.");
|
|
2455
|
+
if (!noInstall) {
|
|
2456
|
+
const ok = installDeps(cwd);
|
|
2457
|
+
if (!ok)
|
|
2458
|
+
process.exit(1);
|
|
2459
|
+
}
|
|
2460
|
+
doneHint(template);
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
// src/commands/migrate.ts
|
|
2464
|
+
init_socket_server();
|
|
2465
|
+
init_front();
|
|
2466
|
+
var MIGRATIONS = {
|
|
2467
|
+
"socket-server": migrateSocketServer,
|
|
2468
|
+
...FRONT_MIGRATIONS,
|
|
2469
|
+
front: migrateFrontAll,
|
|
2470
|
+
ui: migrateFrontAll
|
|
2471
|
+
};
|
|
2472
|
+
async function cmdMigrate(parsed) {
|
|
2473
|
+
if (hasFlag(parsed, "list") || parsed.positionals[0] === "list") {
|
|
2474
|
+
console.log("\u0414\u043E\u0441\u0442\u0443\u043F\u043D\u044B\u0435 \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438:");
|
|
2475
|
+
console.log(" front | ui \u043F\u0430\u043A\u0435\u0442 \u0444\u0440\u043E\u043D\u0442\u043E\u0432\u044B\u0445 \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0439:");
|
|
2476
|
+
for (const name2 of FRONT_MIGRATE_ORDER) {
|
|
2477
|
+
console.log(` - ${name2}`);
|
|
1333
2478
|
}
|
|
2479
|
+
console.log(" socket-server (\u0442\u0430\u043A\u0436\u0435 \u0432 front)");
|
|
2480
|
+
return;
|
|
2481
|
+
}
|
|
2482
|
+
const name = parsed.positionals[0];
|
|
2483
|
+
if (!name) {
|
|
2484
|
+
console.error("\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u044E: elcrm migrate <name>");
|
|
2485
|
+
console.error(`\u0414\u043E\u0441\u0442\u0443\u043F\u043D\u043E: ${Object.keys(MIGRATIONS).join(", ")}`);
|
|
2486
|
+
console.error("\u0421\u043F\u0438\u0441\u043E\u043A: elcrm migrate --list");
|
|
2487
|
+
process.exit(1);
|
|
2488
|
+
}
|
|
2489
|
+
const fn = MIGRATIONS[name];
|
|
2490
|
+
if (!fn) {
|
|
2491
|
+
console.error(`\u041D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u044F: ${name}`);
|
|
2492
|
+
console.error(`\u0414\u043E\u0441\u0442\u0443\u043F\u043D\u043E: ${Object.keys(MIGRATIONS).join(", ")}`);
|
|
2493
|
+
process.exit(1);
|
|
1334
2494
|
}
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
const
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
console.log(`
|
|
1343
|
-
elcrm test (server) \u2014 ${root}`);
|
|
1344
|
-
console.log(`\u0444\u0430\u0439\u043B\u043E\u0432: ${filesScanned}, \u043F\u0440\u043E\u0431\u043B\u0435\u043C: ${findings.length}`);
|
|
1345
|
-
console.log(` error: ${errors.length} warn: ${warns.length} info: ${infos.length}
|
|
1346
|
-
`);
|
|
1347
|
-
const byFile = new Map;
|
|
1348
|
-
for (const f of findings) {
|
|
1349
|
-
const list = byFile.get(f.file) ?? [];
|
|
1350
|
-
list.push(f);
|
|
1351
|
-
byFile.set(f.file, list);
|
|
2495
|
+
const dry = hasFlag(parsed, "dry");
|
|
2496
|
+
const cwd = process.cwd();
|
|
2497
|
+
console.log(`\u041C\u0438\u0433\u0440\u0430\u0446\u0438\u044F: ${name}${dry ? " (--dry)" : ""}`);
|
|
2498
|
+
const changes = await fn(cwd, dry);
|
|
2499
|
+
if (changes.length === 0) {
|
|
2500
|
+
console.log("\u0418\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439 \u043D\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F.");
|
|
2501
|
+
return;
|
|
1352
2502
|
}
|
|
1353
|
-
for (const
|
|
1354
|
-
console.log(
|
|
1355
|
-
for (const f of list) {
|
|
1356
|
-
const tag = f.severity === "error" ? "ERR " : f.severity === "warn" ? "WARN" : "INFO";
|
|
1357
|
-
console.log(` ${tag} L${f.line} [${f.ruleId}] ${f.message}`);
|
|
1358
|
-
console.log(` ${f.match}`);
|
|
1359
|
-
if (f.fix)
|
|
1360
|
-
console.log(` \u2192 ${f.fix}`);
|
|
1361
|
-
if (f.auto)
|
|
1362
|
-
console.log(` (auto: bunx elcrm-server update)`);
|
|
1363
|
-
else
|
|
1364
|
-
console.log(` (\u0432\u0440\u0443\u0447\u043D\u0443\u044E \u2014 MPC.md \u0432 @elcrm/server)`);
|
|
1365
|
-
}
|
|
1366
|
-
console.log("");
|
|
2503
|
+
for (const c2 of changes) {
|
|
2504
|
+
console.log(` ${c2.file}: ${c2.detail}`);
|
|
1367
2505
|
}
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
2506
|
+
console.log(dry ? `
|
|
2507
|
+
--dry: ${changes.length} \u043F\u043E\u0442\u0435\u043D\u0446\u0438\u0430\u043B\u044C\u043D\u044B\u0445 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439.` : `
|
|
2508
|
+
\u041F\u0440\u0438\u043C\u0435\u043D\u0435\u043D\u043E: ${changes.length}.`);
|
|
2509
|
+
if (name === "front" || name === "ui" || name === "size-sml" || name === "form-tokens") {
|
|
2510
|
+
console.log("\u0414\u0430\u043B\u044C\u0448\u0435: elcrm css && elcrm audit");
|
|
1371
2511
|
}
|
|
1372
|
-
return errors.length;
|
|
1373
2512
|
}
|
|
1374
2513
|
|
|
1375
2514
|
// src/commands/doctor.ts
|
|
2515
|
+
init_socket_server();
|
|
2516
|
+
init_pkg();
|
|
1376
2517
|
async function cmdDoctor(parsed) {
|
|
1377
2518
|
const cwd = process.cwd();
|
|
1378
2519
|
const fix = hasFlag(parsed, "fix");
|
|
@@ -1440,14 +2581,20 @@ async function cmdDoctor(parsed) {
|
|
|
1440
2581
|
if (mig.length) {
|
|
1441
2582
|
console.log(`\u041C\u0438\u0433\u0440\u0430\u0446\u0438\u044F socket-server: ${mig.length} \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439.`);
|
|
1442
2583
|
}
|
|
2584
|
+
const { migrateFrontAll: migrateFrontAll2 } = await Promise.resolve().then(() => (init_front(), exports_front));
|
|
2585
|
+
const front = await migrateFrontAll2(cwd, false);
|
|
2586
|
+
if (front.length) {
|
|
2587
|
+
console.log(`migrate front: ${front.length} \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439.`);
|
|
2588
|
+
}
|
|
1443
2589
|
console.log("doctor --fix: \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u044B\u0435 \u043F\u0440\u0430\u0432\u043A\u0438 \u043F\u0440\u0438\u043C\u0435\u043D\u0435\u043D\u044B.");
|
|
1444
|
-
console.log("\u0420\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0443\u0435\u0442\u0441\u044F: elcrm update");
|
|
2590
|
+
console.log("\u0420\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0443\u0435\u0442\u0441\u044F: elcrm update --fix --test");
|
|
1445
2591
|
}
|
|
1446
2592
|
|
|
1447
2593
|
// src/commands/build.ts
|
|
1448
|
-
import { existsSync as
|
|
2594
|
+
import { existsSync as existsSync12 } from "fs";
|
|
1449
2595
|
|
|
1450
2596
|
// src/lib/nextVersion.ts
|
|
2597
|
+
init_pkg();
|
|
1451
2598
|
async function nextVersion() {
|
|
1452
2599
|
const pkg = readPackageJson();
|
|
1453
2600
|
if (!pkg?.version) {
|
|
@@ -1468,9 +2615,9 @@ async function nextVersion() {
|
|
|
1468
2615
|
|
|
1469
2616
|
// src/commands/build.ts
|
|
1470
2617
|
function resolveEntrypoint() {
|
|
1471
|
-
if (
|
|
2618
|
+
if (existsSync12("./src/index.js"))
|
|
1472
2619
|
return "./src/index.js";
|
|
1473
|
-
if (
|
|
2620
|
+
if (existsSync12("./src/index.ts"))
|
|
1474
2621
|
return "./src/index.ts";
|
|
1475
2622
|
return "./src/index.js";
|
|
1476
2623
|
}
|
|
@@ -1547,91 +2694,30 @@ async function cmdUuid() {
|
|
|
1547
2694
|
console.log(crypto.randomUUID());
|
|
1548
2695
|
}
|
|
1549
2696
|
|
|
1550
|
-
// src/commands/test.ts
|
|
1551
|
-
import { resolve as resolve2 } from "path";
|
|
1552
|
-
async function testOneDir(cwd, force, runScripts) {
|
|
1553
|
-
const pkg = readPkgAt(cwd);
|
|
1554
|
-
if (!pkg) {
|
|
1555
|
-
console.error(`package.json \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${cwd}`);
|
|
1556
|
-
return 1;
|
|
1557
|
-
}
|
|
1558
|
-
const mode = detectTestMode(pkg, force);
|
|
1559
|
-
console.log(c.blue(`
|
|
1560
|
-
elcrm test \u2014 ${cwd}
|
|
1561
|
-
\u0440\u0435\u0436\u0438\u043C: ${mode}${force ? " (\u0444\u043B\u0430\u0433)" : " (\u043F\u043E package.json)"}`));
|
|
1562
|
-
if (mode === "server") {
|
|
1563
|
-
const result = scanServerProject(cwd);
|
|
1564
|
-
return printServerFindings(result);
|
|
1565
|
-
}
|
|
1566
|
-
const issues = collectFrontIssues(cwd, pkg);
|
|
1567
|
-
let errors = printIssues(issues, `
|
|
1568
|
-
\u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438\u0439 \u0430\u043D\u0430\u043B\u0438\u0437:`);
|
|
1569
|
-
if (runScripts) {
|
|
1570
|
-
console.log("");
|
|
1571
|
-
const scriptFails = await runFrontScripts(cwd);
|
|
1572
|
-
errors += scriptFails;
|
|
1573
|
-
} else {
|
|
1574
|
-
console.log(c.gray(`
|
|
1575
|
-
(--no-scripts: check:css / check:tokens / test \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D\u044B)`));
|
|
1576
|
-
}
|
|
1577
|
-
return errors;
|
|
1578
|
-
}
|
|
1579
|
-
async function cmdTest(parsed) {
|
|
1580
|
-
const target = resolve2(parsed.positionals[0] ?? process.cwd());
|
|
1581
|
-
const force = hasFlag(parsed, "server") ? "server" : hasFlag(parsed, "front") ? "front" : null;
|
|
1582
|
-
const runScripts = !hasFlag(parsed, "no-scripts");
|
|
1583
|
-
const rootPkg = readPkgAt(target);
|
|
1584
|
-
if (!rootPkg) {
|
|
1585
|
-
console.error("package.json \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D.");
|
|
1586
|
-
process.exit(1);
|
|
1587
|
-
}
|
|
1588
|
-
const workspaces = listWorkspaceDirs(target, rootPkg);
|
|
1589
|
-
let totalErrors = 0;
|
|
1590
|
-
if (workspaces.length > 0 && !force) {
|
|
1591
|
-
console.log(c.dim(`workspaces: ${workspaces.map((d) => d.replace(target + "/", "")).join(", ")}`));
|
|
1592
|
-
const rootHasElcrm = Boolean(rootPkg.dependencies && Object.keys(rootPkg.dependencies).some((k) => k.startsWith("@elcrm/"))) || Boolean(rootPkg.devDependencies && Object.keys(rootPkg.devDependencies).some((k) => k.startsWith("@elcrm/")));
|
|
1593
|
-
if (rootHasElcrm || hasFlag(parsed, "root")) {
|
|
1594
|
-
totalErrors += await testOneDir(target, force, runScripts);
|
|
1595
|
-
}
|
|
1596
|
-
for (const dir of workspaces) {
|
|
1597
|
-
totalErrors += await testOneDir(dir, force, runScripts);
|
|
1598
|
-
}
|
|
1599
|
-
} else {
|
|
1600
|
-
totalErrors += await testOneDir(target, force, runScripts);
|
|
1601
|
-
}
|
|
1602
|
-
if (totalErrors > 0) {
|
|
1603
|
-
console.log(c.yellow(`
|
|
1604
|
-
elcrm test: ${totalErrors} error(s)`));
|
|
1605
|
-
process.exit(1);
|
|
1606
|
-
}
|
|
1607
|
-
console.log(c.green(`
|
|
1608
|
-
elcrm test: ok`));
|
|
1609
|
-
}
|
|
1610
|
-
|
|
1611
2697
|
// src/commands/postbuild.ts
|
|
1612
2698
|
import { resolve as resolve3 } from "path";
|
|
1613
2699
|
|
|
1614
2700
|
// src/vite/postbuild.ts
|
|
1615
2701
|
import {
|
|
1616
|
-
existsSync as
|
|
1617
|
-
readdirSync as
|
|
1618
|
-
readFileSync as
|
|
2702
|
+
existsSync as existsSync13,
|
|
2703
|
+
readdirSync as readdirSync9,
|
|
2704
|
+
readFileSync as readFileSync16,
|
|
1619
2705
|
rmSync,
|
|
1620
|
-
statSync as
|
|
1621
|
-
writeFileSync as
|
|
2706
|
+
statSync as statSync8,
|
|
2707
|
+
writeFileSync as writeFileSync7
|
|
1622
2708
|
} from "fs";
|
|
1623
|
-
import { join as
|
|
2709
|
+
import { join as join13 } from "path";
|
|
1624
2710
|
|
|
1625
2711
|
// src/vite/concat-css.ts
|
|
1626
|
-
import { readdirSync as
|
|
1627
|
-
import { join as
|
|
2712
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync15, writeFileSync as writeFileSync6 } from "fs";
|
|
2713
|
+
import { join as join12 } from "path";
|
|
1628
2714
|
function concatDistCss(options) {
|
|
1629
2715
|
const outFile = options.outFile ?? "index.css";
|
|
1630
|
-
const files =
|
|
1631
|
-
const css = files.map((f) =>
|
|
2716
|
+
const files = readdirSync8(options.distDir).filter((f) => f.endsWith(".css") && f !== outFile).sort();
|
|
2717
|
+
const css = files.map((f) => readFileSync15(join12(options.distDir, f), "utf8")).join(`
|
|
1632
2718
|
`);
|
|
1633
|
-
const outPath =
|
|
1634
|
-
|
|
2719
|
+
const outPath = join12(options.distDir, outFile);
|
|
2720
|
+
writeFileSync6(outPath, css);
|
|
1635
2721
|
console.log(`[concat-css] ${files.length} \u0444\u0430\u0439\u043B\u043E\u0432 \u2192 ${outFile}`);
|
|
1636
2722
|
return outPath;
|
|
1637
2723
|
}
|
|
@@ -1648,19 +2734,19 @@ function postbuildLib(options) {
|
|
|
1648
2734
|
distDir: dist,
|
|
1649
2735
|
outFile: options.cssOutFile ?? "index.css"
|
|
1650
2736
|
});
|
|
1651
|
-
for (const name of
|
|
1652
|
-
const dir =
|
|
1653
|
-
if (!
|
|
2737
|
+
for (const name of readdirSync9(dist)) {
|
|
2738
|
+
const dir = join13(dist, name);
|
|
2739
|
+
if (!statSync8(dir).isDirectory())
|
|
1654
2740
|
continue;
|
|
1655
2741
|
if (keep.has(name))
|
|
1656
2742
|
continue;
|
|
1657
|
-
const impl =
|
|
1658
|
-
const nestedIndex =
|
|
1659
|
-
const flat =
|
|
1660
|
-
const src =
|
|
2743
|
+
const impl = join13(dir, `${name}.d.ts`);
|
|
2744
|
+
const nestedIndex = join13(dir, "index.d.ts");
|
|
2745
|
+
const flat = join13(dist, `${name}.d.ts`);
|
|
2746
|
+
const src = existsSync13(impl) ? impl : existsSync13(nestedIndex) ? nestedIndex : null;
|
|
1661
2747
|
if (src) {
|
|
1662
|
-
const text =
|
|
1663
|
-
|
|
2748
|
+
const text = readFileSync16(src, "utf8").replace(/from ['"]\.\.\/([^'"]+)['"]/g, `from './$1'`);
|
|
2749
|
+
writeFileSync7(flat, text);
|
|
1664
2750
|
console.log(`[postbuild] types \u2192 ${name}.d.ts`);
|
|
1665
2751
|
}
|
|
1666
2752
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -1676,15 +2762,444 @@ async function cmdPostbuild(parsed) {
|
|
|
1676
2762
|
console.log(`[postbuild] \u0433\u043E\u0442\u043E\u0432\u043E: ${distDir}`);
|
|
1677
2763
|
}
|
|
1678
2764
|
|
|
2765
|
+
// src/commands/css.ts
|
|
2766
|
+
init_pkg();
|
|
2767
|
+
async function cmdCss(parsed) {
|
|
2768
|
+
const cwd = parsed.options.dir || process.cwd();
|
|
2769
|
+
const pkg = readPackageJson(cwd);
|
|
2770
|
+
if (!pkg) {
|
|
2771
|
+
console.error("package.json \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D.");
|
|
2772
|
+
process.exit(1);
|
|
2773
|
+
}
|
|
2774
|
+
const names = uniqueElcrmNames(listElcrmDeps(pkg));
|
|
2775
|
+
if (!names.length) {
|
|
2776
|
+
console.log("\u0417\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0435\u0439 @elcrm/* \u043D\u0435\u0442.");
|
|
2777
|
+
return;
|
|
2778
|
+
}
|
|
2779
|
+
const dry = hasFlag(parsed, "dry");
|
|
2780
|
+
console.log(`${dry ? "[dry] " : ""}\u041F\u0430\u043A\u0435\u0442\u044B: ${names.join(", ")}`);
|
|
2781
|
+
try {
|
|
2782
|
+
const result = syncElcrmCss({ cwd, pkgNames: names, dry });
|
|
2783
|
+
console.log(`\u041A\u0430\u0442\u0430\u043B\u043E\u0433 \u0441\u0442\u0438\u043B\u0435\u0439: ${result.styleDir}`);
|
|
2784
|
+
let total = 0;
|
|
2785
|
+
for (const [file, tokens] of Object.entries(result.added)) {
|
|
2786
|
+
if (!tokens.length) {
|
|
2787
|
+
console.log(` ${c.gray(file)}: \u0431\u0435\u0437 \u043D\u043E\u0432\u044B\u0445 \u0442\u043E\u043A\u0435\u043D\u043E\u0432`);
|
|
2788
|
+
continue;
|
|
2789
|
+
}
|
|
2790
|
+
total += tokens.length;
|
|
2791
|
+
console.log(` ${c.green("+")} ${file}: ${tokens.length}`);
|
|
2792
|
+
for (const t of tokens)
|
|
2793
|
+
console.log(` ${c.dim(t)}`);
|
|
2794
|
+
}
|
|
2795
|
+
if (result.imports.length) {
|
|
2796
|
+
console.log(` ${c.green("+")} elcrm.css @import:`);
|
|
2797
|
+
for (const spec of result.imports)
|
|
2798
|
+
console.log(` ${c.dim(spec)}`);
|
|
2799
|
+
}
|
|
2800
|
+
if (result.skippedPkgs.length) {
|
|
2801
|
+
console.log(` ${c.gray("\u0431\u0435\u0437 CSS-\u0442\u043E\u043A\u0435\u043D\u043E\u0432:")} ${result.skippedPkgs.join(", ")}`);
|
|
2802
|
+
}
|
|
2803
|
+
if (total === 0 && result.imports.length === 0) {
|
|
2804
|
+
console.log("\u0412\u0441\u0451 \u0443\u0436\u0435 \u043D\u0430 \u043C\u0435\u0441\u0442\u0435.");
|
|
2805
|
+
} else if (dry) {
|
|
2806
|
+
console.log("\u0417\u0430\u043F\u0438\u0441\u044C \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u043B\u0430\u0441\u044C (--dry).");
|
|
2807
|
+
}
|
|
2808
|
+
} catch (e) {
|
|
2809
|
+
console.error(e instanceof Error ? e.message : e);
|
|
2810
|
+
process.exit(1);
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
// src/commands/audit.ts
|
|
2815
|
+
import { existsSync as existsSync14, readFileSync as readFileSync17 } from "fs";
|
|
2816
|
+
import { join as join14, relative as relative4 } from "path";
|
|
2817
|
+
init_pkg();
|
|
2818
|
+
init_walk();
|
|
2819
|
+
init_front();
|
|
2820
|
+
init_form_tokens();
|
|
2821
|
+
function scan(cwd) {
|
|
2822
|
+
const out = [];
|
|
2823
|
+
const pkg = readPackageJson(cwd);
|
|
2824
|
+
if (!pkg) {
|
|
2825
|
+
out.push({
|
|
2826
|
+
level: "error",
|
|
2827
|
+
code: "no-pkg",
|
|
2828
|
+
message: "package.json \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D"
|
|
2829
|
+
});
|
|
2830
|
+
return out;
|
|
2831
|
+
}
|
|
2832
|
+
for (const section of ["dependencies", "devDependencies"]) {
|
|
2833
|
+
const map = pkg[section];
|
|
2834
|
+
if (!map)
|
|
2835
|
+
continue;
|
|
2836
|
+
for (const [name, ver] of Object.entries(map)) {
|
|
2837
|
+
if (!name.startsWith("@elcrm/"))
|
|
2838
|
+
continue;
|
|
2839
|
+
if (typeof ver === "string" && ver.startsWith("file:")) {
|
|
2840
|
+
out.push({
|
|
2841
|
+
level: "warn",
|
|
2842
|
+
code: "file-dep",
|
|
2843
|
+
message: `${section}["${name}"] = ${ver} \u2014 \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u0443\u0439\u0442\u0435 \u0438\u043B\u0438 \u0437\u0430\u0444\u0438\u043A\u0441\u0438\u0440\u0443\u0439\u0442\u0435 sibling \u0432 README`,
|
|
2844
|
+
fix: "elcrm update / publish \u043F\u0430\u043A\u0435\u0442\u0430"
|
|
2845
|
+
});
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
const files = projectCodeRoots(cwd).flatMap((r) => walkCodeFiles(r));
|
|
2850
|
+
for (const file of files) {
|
|
2851
|
+
const text = readFileSync17(file, "utf8");
|
|
2852
|
+
const rel2 = relative4(cwd, file);
|
|
2853
|
+
const lines = text.split(/\r?\n/);
|
|
2854
|
+
lines.forEach((line, i) => {
|
|
2855
|
+
const n = i + 1;
|
|
2856
|
+
if (/size\s*=\s*["']sm["']/.test(line) || /size\s*=\s*["']md["']/.test(line)) {
|
|
2857
|
+
if (/titleSize/.test(line))
|
|
2858
|
+
return;
|
|
2859
|
+
out.push({
|
|
2860
|
+
level: "warn",
|
|
2861
|
+
code: "size-legacy",
|
|
2862
|
+
message: 'size="sm"|"md" \u2192 "s"|"m"',
|
|
2863
|
+
file: rel2,
|
|
2864
|
+
line: n,
|
|
2865
|
+
fix: "elcrm migrate size-sml"
|
|
2866
|
+
});
|
|
2867
|
+
}
|
|
2868
|
+
if (/Modal\.Add\s*\(/.test(line)) {
|
|
2869
|
+
out.push({
|
|
2870
|
+
level: "warn",
|
|
2871
|
+
code: "modal-add",
|
|
2872
|
+
message: "Modal.Add \u2014 \u043F\u0440\u0435\u0434\u043F\u043E\u0447\u0442\u0438\u0442\u0435\u043B\u0435\u043D Modal.Create + Open",
|
|
2873
|
+
file: rel2,
|
|
2874
|
+
line: n,
|
|
2875
|
+
fix: "elcrm migrate modal-create"
|
|
2876
|
+
});
|
|
2877
|
+
}
|
|
2878
|
+
if (/key=\{Date\.now\(\)\}/.test(line) && /Modal/.test(text)) {
|
|
2879
|
+
out.push({
|
|
2880
|
+
level: "info",
|
|
2881
|
+
code: "modal-date-key",
|
|
2882
|
+
message: "key={Date.now()} \u0443 \u043C\u043E\u0434\u0430\u043B\u043A\u0438 \u2014 Create \u0437\u0430\u0434\u0430\u0451\u0442 \u0441\u0442\u0430\u0431\u0438\u043B\u044C\u043D\u044B\u0439 name",
|
|
2883
|
+
file: rel2,
|
|
2884
|
+
line: n
|
|
2885
|
+
});
|
|
2886
|
+
}
|
|
2887
|
+
if (/--field-border-(?:focus|hover|error|disabled)\s*:\s*\d/.test(line) && /solid|dashed|dotted/i.test(line)) {
|
|
2888
|
+
out.push({
|
|
2889
|
+
level: "error",
|
|
2890
|
+
code: "field-border-shorthand",
|
|
2891
|
+
message: "hover/focus border \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0446\u0432\u0435\u0442, \u043D\u0435 shorthand",
|
|
2892
|
+
file: rel2,
|
|
2893
|
+
line: n,
|
|
2894
|
+
fix: "elcrm migrate field-border"
|
|
2895
|
+
});
|
|
2896
|
+
}
|
|
2897
|
+
if (/!important/.test(line) && /drag|file-upload|DragDrop/i.test(rel2 + line)) {
|
|
2898
|
+
out.push({
|
|
2899
|
+
level: "info",
|
|
2900
|
+
code: "dragdrop-important",
|
|
2901
|
+
message: '!important \u0443 DragDrop \u2014 layout="fill"|"overlay" \u0432 @elcrm/form',
|
|
2902
|
+
file: rel2,
|
|
2903
|
+
line: n
|
|
2904
|
+
});
|
|
2905
|
+
}
|
|
2906
|
+
if (/globalThis\.React\s*=/.test(line)) {
|
|
2907
|
+
out.push({
|
|
2908
|
+
level: "warn",
|
|
2909
|
+
code: "react-global",
|
|
2910
|
+
message: "globalThis.React \u0445\u0430\u043A \u2014 migrate legacy-hacks / \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u0435 modal",
|
|
2911
|
+
file: rel2,
|
|
2912
|
+
line: n,
|
|
2913
|
+
fix: "elcrm migrate legacy-hacks"
|
|
2914
|
+
});
|
|
2915
|
+
}
|
|
2916
|
+
if (/\b(ProgressField|ReadonlyField|OtpField|SegmentField|DragonDropField|TProgress)\b/.test(line)) {
|
|
2917
|
+
out.push({
|
|
2918
|
+
level: "error",
|
|
2919
|
+
code: "form-alias",
|
|
2920
|
+
message: "\u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0438\u0439 \u0430\u043B\u0438\u0430\u0441 @elcrm/form (ProgressField/OtpField/\u2026) \u2192 \u043A\u0430\u043D\u043E\u043D",
|
|
2921
|
+
file: rel2,
|
|
2922
|
+
line: n,
|
|
2923
|
+
fix: "elcrm migrate form-aliases"
|
|
2924
|
+
});
|
|
2925
|
+
}
|
|
2926
|
+
});
|
|
2927
|
+
}
|
|
2928
|
+
const toolbarMarkers = files.filter((f) => {
|
|
2929
|
+
if (!f.endsWith(".css"))
|
|
2930
|
+
return false;
|
|
2931
|
+
if (/theme(-light|-dark)?\.css$/.test(f))
|
|
2932
|
+
return false;
|
|
2933
|
+
const t = readFileSync17(f, "utf8");
|
|
2934
|
+
return t.includes("--search-border:") && t.includes("--button-secondary-bg:") && t.includes("transparent");
|
|
2935
|
+
});
|
|
2936
|
+
if (toolbarMarkers.length > 1) {
|
|
2937
|
+
out.push({
|
|
2938
|
+
level: "info",
|
|
2939
|
+
code: "toolbar-dup",
|
|
2940
|
+
message: `\u043E\u0434\u0438\u043D\u0430\u043A\u043E\u0432\u044B\u0435 toolbar-\u0442\u043E\u043A\u0435\u043D\u044B \u0432 ${toolbarMarkers.length} \u0444\u0430\u0439\u043B\u0430\u0445 \u2014 \u043A\u043B\u0430\u0441\u0441 .el-toolbar-controls`,
|
|
2941
|
+
fix: "\u043E\u0431\u0449\u0438\u0439 \u043A\u043B\u0430\u0441\u0441 + theme tokens"
|
|
2942
|
+
});
|
|
2943
|
+
}
|
|
2944
|
+
const styleDir = ["src/style", "src/styles", "style"].map((d) => join14(cwd, d)).find(existsSync14);
|
|
2945
|
+
if (styleDir && !existsSync14(join14(styleDir, "elcrm.css"))) {
|
|
2946
|
+
out.push({
|
|
2947
|
+
level: "info",
|
|
2948
|
+
code: "elcrm-css",
|
|
2949
|
+
message: "\u043D\u0435\u0442 elcrm.css \u2014 elcrm css \u0441\u043E\u0437\u0434\u0430\u0441\u0442 imports + \u0442\u043E\u043A\u0435\u043D\u044B",
|
|
2950
|
+
fix: "elcrm css"
|
|
2951
|
+
});
|
|
2952
|
+
}
|
|
2953
|
+
const depsForm = pkg.dependencies?.["@elcrm/form"] || pkg.devDependencies?.["@elcrm/form"];
|
|
2954
|
+
if (depsForm && styleDir) {
|
|
2955
|
+
const themeFiles = [
|
|
2956
|
+
"theme-light.css",
|
|
2957
|
+
"theme-dark.css",
|
|
2958
|
+
"theme.css"
|
|
2959
|
+
].map((f) => join14(styleDir, f));
|
|
2960
|
+
const themeText = themeFiles.filter(existsSync14).map((f) => readFileSync17(f, "utf8")).join(`
|
|
2961
|
+
`);
|
|
2962
|
+
if (themeText && !/--popup-shadow\s*:/.test(themeText) && /--date-popup-shadow\s*:|--select-background\s*:/.test(themeText)) {
|
|
2963
|
+
out.push({
|
|
2964
|
+
level: "info",
|
|
2965
|
+
code: "popup-shadow",
|
|
2966
|
+
message: "\u043D\u0435\u0442 --popup-shadow \u0432 theme-* (\u043E\u0431\u0449\u0430\u044F \u0442\u0435\u043D\u044C \u043F\u043E\u043F\u0430\u043F\u043E\u0432 Date/Select/Color/Time)",
|
|
2967
|
+
fix: "elcrm css \u0438\u043B\u0438 elcrm update --fix"
|
|
2968
|
+
});
|
|
2969
|
+
}
|
|
2970
|
+
if (themeText && !/--field-padding-block\s*:/.test(themeText) && /--field-note-padding\s*:|--control-inner-padding\s*:/.test(themeText)) {
|
|
2971
|
+
out.push({
|
|
2972
|
+
level: "warn",
|
|
2973
|
+
code: "field-padding",
|
|
2974
|
+
message: "\u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0438\u0435 padding-\u0442\u043E\u043A\u0435\u043D\u044B \u2014 \u0435\u0434\u0438\u043D\u044B\u0439 --field-padding / --field-padding-block",
|
|
2975
|
+
fix: "elcrm migrate form-tokens"
|
|
2976
|
+
});
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
for (const file of files) {
|
|
2980
|
+
if (!file.endsWith(".css"))
|
|
2981
|
+
continue;
|
|
2982
|
+
const text = readFileSync17(file, "utf8");
|
|
2983
|
+
const relPath = relative4(cwd, file);
|
|
2984
|
+
const lines = text.split(/\r?\n/);
|
|
2985
|
+
lines.forEach((line, i) => {
|
|
2986
|
+
if (/:\s*var\(\s*--field-padding/.test(line))
|
|
2987
|
+
return;
|
|
2988
|
+
if (lineHasDeprecatedFormToken(line)) {
|
|
2989
|
+
out.push({
|
|
2990
|
+
level: "warn",
|
|
2991
|
+
code: "form-token-legacy",
|
|
2992
|
+
message: "legacy form-\u0442\u043E\u043A\u0435\u043D \u2192 --field-padding* / --popup-shadow",
|
|
2993
|
+
file: relPath,
|
|
2994
|
+
line: i + 1,
|
|
2995
|
+
fix: "elcrm migrate form-tokens"
|
|
2996
|
+
});
|
|
2997
|
+
}
|
|
2998
|
+
});
|
|
2999
|
+
}
|
|
3000
|
+
return out;
|
|
3001
|
+
}
|
|
3002
|
+
function printFindings(findings) {
|
|
3003
|
+
if (!findings.length) {
|
|
3004
|
+
console.log(`${c.green("audit")}: \u0437\u0430\u043C\u0435\u0447\u0430\u043D\u0438\u0439 \u043D\u0435\u0442.`);
|
|
3005
|
+
return;
|
|
3006
|
+
}
|
|
3007
|
+
const icon = { error: c.red("\u2715"), warn: c.yellow("!"), info: c.cyan("i") };
|
|
3008
|
+
for (const f of findings) {
|
|
3009
|
+
const loc = f.file ? f.line ? `${f.file}:${f.line}` : f.file : "";
|
|
3010
|
+
console.log(`${icon[f.level]} [${f.code}] ${f.message}${loc ? ` ${c.dim(loc)}` : ""}`);
|
|
3011
|
+
if (f.fix)
|
|
3012
|
+
console.log(` ${c.dim("\u2192 " + f.fix)}`);
|
|
3013
|
+
}
|
|
3014
|
+
const err = findings.filter((f) => f.level === "error").length;
|
|
3015
|
+
const warn = findings.filter((f) => f.level === "warn").length;
|
|
3016
|
+
console.log(`
|
|
3017
|
+
\u0418\u0442\u043E\u0433\u043E: ${findings.length} (${err} error, ${warn} warn). \u0418\u0441\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435: elcrm audit --fix / elcrm migrate front`);
|
|
3018
|
+
}
|
|
3019
|
+
async function cmdAudit(parsed) {
|
|
3020
|
+
const cwd = parsed.options.dir || process.cwd();
|
|
3021
|
+
const fix = hasFlag(parsed, "fix");
|
|
3022
|
+
const findings = scan(cwd);
|
|
3023
|
+
printFindings(findings);
|
|
3024
|
+
if (!fix) {
|
|
3025
|
+
if (findings.some((f) => f.level === "error"))
|
|
3026
|
+
process.exit(1);
|
|
3027
|
+
return;
|
|
3028
|
+
}
|
|
3029
|
+
console.log(`
|
|
3030
|
+
${c.cyan("\u2192")} elcrm migrate front${hasFlag(parsed, "dry") ? " --dry" : ""}`);
|
|
3031
|
+
const changes = await migrateFrontAll(cwd, hasFlag(parsed, "dry"));
|
|
3032
|
+
if (!changes.length) {
|
|
3033
|
+
console.log("\u0410\u0432\u0442\u043E\u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0439 \u043D\u0435 \u043F\u043E\u0442\u0440\u0435\u0431\u043E\u0432\u0430\u043B\u043E\u0441\u044C.");
|
|
3034
|
+
} else {
|
|
3035
|
+
for (const ch of changes) {
|
|
3036
|
+
console.log(` ${ch.file}: ${ch.detail}`);
|
|
3037
|
+
}
|
|
3038
|
+
console.log(hasFlag(parsed, "dry") ? `
|
|
3039
|
+
--dry: ${changes.length} \u043F\u043E\u0442\u0435\u043D\u0446\u0438\u0430\u043B\u044C\u043D\u044B\u0445 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439.` : `
|
|
3040
|
+
\u041F\u0440\u0438\u043C\u0435\u043D\u0435\u043D\u043E: ${changes.length}.`);
|
|
3041
|
+
}
|
|
3042
|
+
console.log("\u0420\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0443\u0435\u0442\u0441\u044F: elcrm css && elcrm doctor");
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
// src/commands/docs.ts
|
|
3046
|
+
import { join as join16, resolve as resolve4 } from "path";
|
|
3047
|
+
|
|
3048
|
+
// src/lib/projectKind.ts
|
|
3049
|
+
function detectProjectKind(cwd) {
|
|
3050
|
+
const pkg = readPkgAt(cwd);
|
|
3051
|
+
if (!pkg)
|
|
3052
|
+
return "front";
|
|
3053
|
+
const workspaces = listWorkspaceDirs(cwd, pkg);
|
|
3054
|
+
if (workspaces.length) {
|
|
3055
|
+
const modes = new Set;
|
|
3056
|
+
for (const dir of workspaces) {
|
|
3057
|
+
const w = readPkgAt(dir);
|
|
3058
|
+
if (w)
|
|
3059
|
+
modes.add(detectTestMode(w));
|
|
3060
|
+
}
|
|
3061
|
+
if (modes.has("front") && modes.has("server"))
|
|
3062
|
+
return "mixed";
|
|
3063
|
+
if (modes.has("server") && !modes.has("front"))
|
|
3064
|
+
return "server";
|
|
3065
|
+
if (modes.has("front"))
|
|
3066
|
+
return "front";
|
|
3067
|
+
}
|
|
3068
|
+
return hasElcrmServer(pkg) ? "server" : "front";
|
|
3069
|
+
}
|
|
3070
|
+
function isElcrmLibPackage(cwd) {
|
|
3071
|
+
const pkg = readPkgAt(cwd);
|
|
3072
|
+
const name = pkg?.name;
|
|
3073
|
+
return typeof name === "string" && name.startsWith("@elcrm/");
|
|
3074
|
+
}
|
|
3075
|
+
|
|
3076
|
+
// src/lib/elcrmDocsCatalog.ts
|
|
3077
|
+
var ALL_ELCRM_DOCS = [
|
|
3078
|
+
"CLI.elCRM.md",
|
|
3079
|
+
"API.elCRM.md",
|
|
3080
|
+
"ALERT.elCRM.md",
|
|
3081
|
+
"BUTTON.elCRM.md",
|
|
3082
|
+
"MODAL.elCRM.md",
|
|
3083
|
+
"NOTICE.elCRM.md",
|
|
3084
|
+
"NOTIFICATIONS.elCRM.md",
|
|
3085
|
+
"PASSKEY.elCRM.md",
|
|
3086
|
+
"ROUTER.elCRM.md",
|
|
3087
|
+
"SOCKET.elCRM.md",
|
|
3088
|
+
"TOOLTIP.elCRM.md",
|
|
3089
|
+
"ICONS.elCRM.md",
|
|
3090
|
+
"FORM.elCRM.md",
|
|
3091
|
+
"COMPONENTS.elCRM.md",
|
|
3092
|
+
"SERVER.elCRM.md"
|
|
3093
|
+
];
|
|
3094
|
+
var SERVER_DOCS = [
|
|
3095
|
+
"SERVER.elCRM.md",
|
|
3096
|
+
"CLI.elCRM.md",
|
|
3097
|
+
"API.elCRM.md",
|
|
3098
|
+
"SOCKET.elCRM.md"
|
|
3099
|
+
];
|
|
3100
|
+
function docsForProject(kind, lib) {
|
|
3101
|
+
if (lib || kind === "front" || kind === "mixed")
|
|
3102
|
+
return ALL_ELCRM_DOCS;
|
|
3103
|
+
return SERVER_DOCS;
|
|
3104
|
+
}
|
|
3105
|
+
|
|
3106
|
+
// src/lib/scaffoldFiles.ts
|
|
3107
|
+
import {
|
|
3108
|
+
existsSync as existsSync15,
|
|
3109
|
+
mkdirSync as mkdirSync2,
|
|
3110
|
+
readdirSync as readdirSync10,
|
|
3111
|
+
readFileSync as readFileSync18,
|
|
3112
|
+
writeFileSync as writeFileSync8
|
|
3113
|
+
} from "fs";
|
|
3114
|
+
import { dirname as dirname3, join as join15 } from "path";
|
|
3115
|
+
function writeScaffoldFile(dest, body, opts) {
|
|
3116
|
+
if (opts.dry)
|
|
3117
|
+
return "dry";
|
|
3118
|
+
if (!opts.force && existsSync15(dest))
|
|
3119
|
+
return "skip";
|
|
3120
|
+
mkdirSync2(dirname3(dest), { recursive: true });
|
|
3121
|
+
writeFileSync8(dest, body, "utf8");
|
|
3122
|
+
return "write";
|
|
3123
|
+
}
|
|
3124
|
+
function readBundled(relPath) {
|
|
3125
|
+
const full = join15(packageRoot(), "templates", relPath);
|
|
3126
|
+
if (!existsSync15(full)) {
|
|
3127
|
+
throw new Error(`\u0428\u0430\u0431\u043B\u043E\u043D \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${relPath}`);
|
|
3128
|
+
}
|
|
3129
|
+
return readFileSync18(full, "utf8");
|
|
3130
|
+
}
|
|
3131
|
+
|
|
3132
|
+
// src/commands/docs.ts
|
|
3133
|
+
async function cmdDocs(parsed) {
|
|
3134
|
+
const cwd = resolve4(parsed.options.dir || process.cwd());
|
|
3135
|
+
const dry = hasFlag(parsed, "dry");
|
|
3136
|
+
const force = hasFlag(parsed, "force") || !hasFlag(parsed, "keep");
|
|
3137
|
+
const lib = hasFlag(parsed, "lib") || isElcrmLibPackage(cwd);
|
|
3138
|
+
const kind = detectProjectKind(cwd);
|
|
3139
|
+
const names = docsForProject(kind, lib);
|
|
3140
|
+
console.log(`${dry ? "[dry] " : ""}elcrm docs \u2192 ${cwd}/docs (${kind}${lib ? ", lib" : ""})`);
|
|
3141
|
+
for (const name of names) {
|
|
3142
|
+
const body = readBundled(join16("elcrm-docs", name));
|
|
3143
|
+
const dest = join16(cwd, "docs", name);
|
|
3144
|
+
const action = writeScaffoldFile(dest, body, { dry, force });
|
|
3145
|
+
const mark = action === "write" ? c.green("+") : action === "skip" ? c.gray("=") : c.cyan("~");
|
|
3146
|
+
console.log(` ${mark} docs/${name}`);
|
|
3147
|
+
}
|
|
3148
|
+
if (kind === "server" && !lib) {
|
|
3149
|
+
console.log(c.gray("\u0441\u0435\u0440\u0432\u0435\u0440: SERVER, CLI, API, SOCKET"));
|
|
3150
|
+
}
|
|
3151
|
+
console.log("\u0414\u0430\u043B\u044C\u0448\u0435: elcrm cursor \u2014 \u043F\u0440\u0430\u0432\u0438\u043B\u0430 \u0432 .cursor/rules (+ .cursor/docs)");
|
|
3152
|
+
}
|
|
3153
|
+
|
|
3154
|
+
// src/commands/cursor.ts
|
|
3155
|
+
import { join as join17, resolve as resolve5 } from "path";
|
|
3156
|
+
var ALWAYS = ["elcrm.mdc", "elcrm-packages.mdc"];
|
|
3157
|
+
var UI = ["elcrm-ui.mdc", "elcrm-module.mdc", "elcrm-theme.mdc"];
|
|
3158
|
+
var SERVER = ["elcrm-server.mdc", "elcrm-rpc.mdc"];
|
|
3159
|
+
var LIB = ["elcrm-lib.mdc"];
|
|
3160
|
+
function writeOne(dest, body, dry, force, label) {
|
|
3161
|
+
const action = writeScaffoldFile(dest, body, { dry, force });
|
|
3162
|
+
const mark = action === "write" ? c.green("+") : action === "skip" ? c.gray("=") : c.cyan("~");
|
|
3163
|
+
console.log(` ${mark} ${label}`);
|
|
3164
|
+
}
|
|
3165
|
+
async function cmdCursor(parsed) {
|
|
3166
|
+
const cwd = resolve5(parsed.options.dir || process.cwd());
|
|
3167
|
+
const dry = hasFlag(parsed, "dry");
|
|
3168
|
+
const force = hasFlag(parsed, "force") || !hasFlag(parsed, "keep");
|
|
3169
|
+
const lib = hasFlag(parsed, "lib") || isElcrmLibPackage(cwd);
|
|
3170
|
+
const kind = detectProjectKind(cwd);
|
|
3171
|
+
const ruleNames = [
|
|
3172
|
+
...ALWAYS,
|
|
3173
|
+
...kind === "server" ? [] : UI,
|
|
3174
|
+
...kind === "front" ? [] : SERVER,
|
|
3175
|
+
...lib ? LIB : []
|
|
3176
|
+
];
|
|
3177
|
+
console.log(`${dry ? "[dry] " : ""}elcrm cursor \u2192 ${cwd}/.cursor (${kind}${lib ? ", lib" : ""})`);
|
|
3178
|
+
for (const name of ruleNames) {
|
|
3179
|
+
let body = readBundled(join17("elcrm-cursor", "rules", name));
|
|
3180
|
+
if (kind === "server" && (name === "elcrm-server.mdc" || name === "elcrm-rpc.mdc")) {
|
|
3181
|
+
body = body.replace("alwaysApply: false", "alwaysApply: true").replace(/^globs:.*\n/m, "");
|
|
3182
|
+
}
|
|
3183
|
+
writeOne(join17(cwd, ".cursor", "rules", name), body, dry, force, `.cursor/rules/${name}`);
|
|
3184
|
+
}
|
|
3185
|
+
const docs = docsForProject(kind, lib);
|
|
3186
|
+
for (const name of docs) {
|
|
3187
|
+
const body = readBundled(join17("elcrm-docs", name));
|
|
3188
|
+
writeOne(join17(cwd, ".cursor", "docs", name), body, dry, force, `.cursor/docs/${name}`);
|
|
3189
|
+
}
|
|
3190
|
+
writeOne(join17(cwd, "AGENTS.md"), readBundled(join17("elcrm-cursor", "AGENTS.md")), dry, force, "AGENTS.md");
|
|
3191
|
+
console.log(c.gray("\u0410\u0433\u0435\u043D\u0442: AGENTS.md + rules (\u0443\u0437\u043A\u0438\u0435 glob) + .cursor/docs. \u0421\u0434\u0430\u0447\u0430: elcrm test."));
|
|
3192
|
+
}
|
|
3193
|
+
|
|
1679
3194
|
// src/index.ts
|
|
1680
3195
|
function cliVersion() {
|
|
1681
|
-
const here =
|
|
3196
|
+
const here = dirname4(fileURLToPath2(import.meta.url));
|
|
1682
3197
|
for (const p of [
|
|
1683
|
-
|
|
1684
|
-
|
|
3198
|
+
join18(here, "..", "package.json"),
|
|
3199
|
+
join18(here, "package.json")
|
|
1685
3200
|
]) {
|
|
1686
3201
|
try {
|
|
1687
|
-
const v = JSON.parse(
|
|
3202
|
+
const v = JSON.parse(readFileSync19(p, "utf8")).version;
|
|
1688
3203
|
if (typeof v === "string" && v)
|
|
1689
3204
|
return v;
|
|
1690
3205
|
} catch {}
|
|
@@ -1701,8 +3216,9 @@ function showHelp() {
|
|
|
1701
3216
|
elcrm <\u043A\u043E\u043C\u0430\u043D\u0434\u0430> [\u0430\u0440\u0433\u0443\u043C\u0435\u043D\u0442\u044B] [\u0444\u043B\u0430\u0433\u0438]
|
|
1702
3217
|
|
|
1703
3218
|
\u041A\u043E\u043C\u0430\u043D\u0434\u044B:
|
|
1704
|
-
update [--dry] [--core] [--no-install]
|
|
1705
|
-
\u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0432\u0441\u0435 @elcrm/* \
|
|
3219
|
+
update [--dry] [--core] [--no-install] [--fix] [--test]
|
|
3220
|
+
\u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0432\u0441\u0435 @elcrm/* \u0434\u043E latest; --fix = migrate front; --test = elcrm test
|
|
3221
|
+
\u0420\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0443\u0435\u0442\u0441\u044F: elcrm update --fix --test
|
|
1706
3222
|
|
|
1707
3223
|
create <name> [--template app|lib|panel] [--no-install]
|
|
1708
3224
|
\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u043F\u0440\u043E\u0435\u043A\u0442 \u0438\u0437 \u0448\u0430\u0431\u043B\u043E\u043D\u0430 (\u043A\u0430\u043A create-react-app)
|
|
@@ -1713,7 +3229,11 @@ function showHelp() {
|
|
|
1713
3229
|
|
|
1714
3230
|
migrate <name> [--dry]
|
|
1715
3231
|
migrate --list
|
|
1716
|
-
\u041C\u0438\u0433\u0440\u0430\u0446\u0438\u0438
|
|
3232
|
+
\u041C\u0438\u0433\u0440\u0430\u0446\u0438\u0438: front|ui, form-aliases, form-tokens, size-sml, field-border, modal-create, legacy-hacks, socket-server
|
|
3233
|
+
|
|
3234
|
+
audit [--fix] [--dry] [--dir=\u2026]
|
|
3235
|
+
\u0423\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0438\u0439 API + CSS-\u0442\u043E\u043A\u0435\u043D\u044B form (padding-\u0430\u043B\u0438\u0430\u0441\u044B, popup-shadow, border shorthand)
|
|
3236
|
+
--fix \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0435\u0442 migrate front
|
|
1717
3237
|
|
|
1718
3238
|
doctor [--fix]
|
|
1719
3239
|
\u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043F\u0440\u043E\u0435\u043A\u0442 \u0438 \u043F\u0440\u0438 \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E\u0441\u0442\u0438 \u043F\u0440\u0438\u0432\u0435\u0441\u0442\u0438 \u043A \u0441\u0442\u0430\u043D\u0434\u0430\u0440\u0442\u0443
|
|
@@ -1729,6 +3249,18 @@ function showHelp() {
|
|
|
1729
3249
|
\u041F\u043E\u0441\u043B\u0435 vite build lib-\u043F\u0430\u043A\u0435\u0442\u0430: \u0441\u043A\u043B\u0435\u0438\u0442\u044C CSS + \u0441\u043F\u043B\u044E\u0449\u0438\u0442\u044C .d.ts
|
|
1730
3250
|
(package.json: "build": "vite build && elcrm postbuild")
|
|
1731
3251
|
|
|
3252
|
+
css [--dry] [--dir=\u2026]
|
|
3253
|
+
\u0414\u0435\u0444\u043E\u043B\u0442\u043D\u044B\u0435 \u0442\u043E\u043A\u0435\u043D\u044B @elcrm/* \u2192 src/style/theme.css, theme-light/dark, elcrm.css
|
|
3254
|
+
\u0423\u0436\u0435 \u0437\u0430\u0434\u0430\u043D\u043D\u044B\u0435 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0435 \u043D\u0435 \u0437\u0430\u0442\u0438\u0440\u0430\u044E\u0442\u0441\u044F, \u0442\u043E\u043B\u044C\u043A\u043E \u043D\u0435\u0434\u043E\u0441\u0442\u0430\u044E\u0449\u0438\u0435 (\u0433\u0440\u0443\u043F\u043F\u044B \u0447\u0435\u0440\u0435\u0437 \u043F\u0443\u0441\u0442\u0443\u044E \u0441\u0442\u0440\u043E\u043A\u0443)
|
|
3255
|
+
|
|
3256
|
+
docs [--dry] [--keep] [--dir=\u2026] [--lib]
|
|
3257
|
+
docs/*.elCRM.md \u043F\u043E \u043F\u0430\u043A\u0435\u0442\u0430\u043C (cli, api, alert, button, modal, notice, \u2026)
|
|
3258
|
+
\u0441\u0435\u0440\u0432\u0435\u0440-only \u2192 SERVER, CLI, API, SOCKET; --lib \u0438\u043B\u0438 \u043F\u0430\u043A\u0435\u0442 @elcrm/* \u2192 \u0432\u0441\u0435 \u0444\u0430\u0439\u043B\u044B
|
|
3259
|
+
|
|
3260
|
+
cursor [--dry] [--keep] [--dir=\u2026] [--lib|-lib]
|
|
3261
|
+
.cursor/rules + .cursor/docs + AGENTS.md
|
|
3262
|
+
--lib: \u0447\u0435\u043A\u043B\u0438\u0441\u0442 \u0430\u0432\u0442\u043E\u0440\u0430 \u0431\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0438 (\u043F\u043E\u0441\u043B\u0435 \u043F\u0440\u0430\u0432\u043E\u043A src \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C CLI-\u0448\u043F\u0430\u0440\u0433\u0430\u043B\u043A\u0438)
|
|
3263
|
+
|
|
1732
3264
|
uuid
|
|
1733
3265
|
\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C UUID
|
|
1734
3266
|
|
|
@@ -1743,15 +3275,26 @@ function showHelp() {
|
|
|
1743
3275
|
elcrm -v
|
|
1744
3276
|
elcrm update
|
|
1745
3277
|
elcrm update --dry
|
|
3278
|
+
elcrm update --fix --test
|
|
3279
|
+
elcrm migrate form-aliases --dry
|
|
3280
|
+
elcrm migrate form-tokens --dry
|
|
1746
3281
|
elcrm create my-app --template app
|
|
1747
3282
|
elcrm init --template panel
|
|
1748
3283
|
elcrm init --template lib
|
|
1749
|
-
elcrm migrate
|
|
3284
|
+
elcrm migrate front --dry
|
|
3285
|
+
elcrm migrate size-sml
|
|
3286
|
+
elcrm audit
|
|
3287
|
+
elcrm audit --fix
|
|
1750
3288
|
elcrm doctor --fix
|
|
1751
3289
|
elcrm test
|
|
1752
3290
|
elcrm test --server
|
|
1753
3291
|
elcrm build --client --url=https://example.com/deploy
|
|
1754
3292
|
elcrm postbuild
|
|
3293
|
+
elcrm css
|
|
3294
|
+
elcrm css --dry
|
|
3295
|
+
elcrm docs
|
|
3296
|
+
elcrm cursor
|
|
3297
|
+
elcrm cursor --lib
|
|
1755
3298
|
`);
|
|
1756
3299
|
}
|
|
1757
3300
|
async function main() {
|
|
@@ -1792,6 +3335,18 @@ async function main() {
|
|
|
1792
3335
|
case "postbuild":
|
|
1793
3336
|
await cmdPostbuild(parsed);
|
|
1794
3337
|
break;
|
|
3338
|
+
case "css":
|
|
3339
|
+
await cmdCss(parsed);
|
|
3340
|
+
break;
|
|
3341
|
+
case "audit":
|
|
3342
|
+
await cmdAudit(parsed);
|
|
3343
|
+
break;
|
|
3344
|
+
case "docs":
|
|
3345
|
+
await cmdDocs(parsed);
|
|
3346
|
+
break;
|
|
3347
|
+
case "cursor":
|
|
3348
|
+
await cmdCursor(parsed);
|
|
3349
|
+
break;
|
|
1795
3350
|
case "uuid":
|
|
1796
3351
|
await cmdUuid();
|
|
1797
3352
|
break;
|