elcrm 1.0.0 → 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 +10 -0
- package/dist/index.js +2828 -425
- package/dist/vite/discover-lib.js +4 -4
- package/dist/vite/plugin-css-scoped.js +2 -2
- package/dist/vite/postbuild.d.ts +5 -0
- package/dist/vite/postbuild.js +59 -0
- package/package.json +10 -4
- 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/README.md +2 -26
- package/templates/panel/api/package.json +2 -5
- package/templates/panel/api/src/index.ts +11 -20
- package/templates/panel/api/src/lib/cron.ts +10 -7
- package/templates/panel/api/src/lib/db.ts +3 -5
- package/templates/panel/api/src/lib/jwt.ts +1 -1
- package/templates/panel/api/src/routes/auth.ts +20 -8
- package/templates/panel/web/package.json +4 -4
- package/templates/panel/web/src/modules/Account/sections/Profile.tsx +0 -1
- package/templates/panel/web/src/modules/Settings/sections/General.tsx +0 -2
- package/templates/panel/web/src/modules/Settings/sections/Profile.tsx +0 -1
- package/templates/panel/web/src/pages/Auth.tsx +0 -1
- package/templates/panel/web/src/style/elcrm.css +4 -1
- package/templates/panel/web/src/style/theme-dark.css +65 -3
- package/templates/panel/web/src/style/theme-light.css +65 -3
- package/templates/panel/web/src/style/theme.css +73 -3
package/dist/index.js
CHANGED
|
@@ -1,5 +1,742 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __returnValue = (v) => v;
|
|
5
|
+
function __exportSetter(name, newValue) {
|
|
6
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
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);
|
|
18
|
+
|
|
19
|
+
// src/lib/latest.ts
|
|
20
|
+
async function fetchLatestVersion(name) {
|
|
21
|
+
if (CACHE.has(name))
|
|
22
|
+
return CACHE.get(name);
|
|
23
|
+
const url = `https://registry.npmjs.org/${encodeURIComponent(name)}/latest`;
|
|
24
|
+
try {
|
|
25
|
+
const res = await fetch(url, {
|
|
26
|
+
headers: { Accept: "application/json" }
|
|
27
|
+
});
|
|
28
|
+
if (!res.ok)
|
|
29
|
+
return null;
|
|
30
|
+
const data = await res.json();
|
|
31
|
+
if (!data.version)
|
|
32
|
+
return null;
|
|
33
|
+
CACHE.set(name, data.version);
|
|
34
|
+
return data.version;
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function fetchLatestMap(names) {
|
|
40
|
+
const map = new Map;
|
|
41
|
+
const results = await Promise.all(names.map(async (name) => {
|
|
42
|
+
const v = await fetchLatestVersion(name);
|
|
43
|
+
return [name, v];
|
|
44
|
+
}));
|
|
45
|
+
for (const [name, v] of results) {
|
|
46
|
+
if (v)
|
|
47
|
+
map.set(name, v);
|
|
48
|
+
}
|
|
49
|
+
return map;
|
|
50
|
+
}
|
|
51
|
+
async function fetchCorePinnedVersions() {
|
|
52
|
+
const url = "https://registry.npmjs.org/@elcrm%2Fcore/latest";
|
|
53
|
+
const map = new Map;
|
|
54
|
+
try {
|
|
55
|
+
const res = await fetch(url, {
|
|
56
|
+
headers: { Accept: "application/json" }
|
|
57
|
+
});
|
|
58
|
+
if (!res.ok)
|
|
59
|
+
return map;
|
|
60
|
+
const data = await res.json();
|
|
61
|
+
if (data.version)
|
|
62
|
+
map.set("@elcrm/core", data.version);
|
|
63
|
+
for (const [name, ver] of Object.entries(data.dependencies ?? {})) {
|
|
64
|
+
if (name.startsWith("@elcrm/")) {
|
|
65
|
+
map.set(name, ver.replace(/^[\^~]/, ""));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} catch {}
|
|
69
|
+
return map;
|
|
70
|
+
}
|
|
71
|
+
var CACHE;
|
|
72
|
+
var init_latest = __esm(() => {
|
|
73
|
+
CACHE = new Map;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// src/lib/pkg.ts
|
|
77
|
+
import { existsSync as existsSync2, readFileSync, writeFileSync } from "fs";
|
|
78
|
+
import { join as join2 } from "path";
|
|
79
|
+
function packageJsonPath(cwd = process.cwd()) {
|
|
80
|
+
return join2(cwd, "package.json");
|
|
81
|
+
}
|
|
82
|
+
function readPackageJson(cwd = process.cwd()) {
|
|
83
|
+
const path = packageJsonPath(cwd);
|
|
84
|
+
if (!existsSync2(path))
|
|
85
|
+
return null;
|
|
86
|
+
try {
|
|
87
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function writePackageJson(pkg, cwd = process.cwd()) {
|
|
93
|
+
writeFileSync(packageJsonPath(cwd), JSON.stringify(pkg, null, 2) + `
|
|
94
|
+
`, "utf8");
|
|
95
|
+
}
|
|
96
|
+
var init_pkg = () => {};
|
|
97
|
+
|
|
98
|
+
// src/lib/migrate/socket-server.ts
|
|
99
|
+
import {
|
|
100
|
+
existsSync as existsSync3,
|
|
101
|
+
readdirSync,
|
|
102
|
+
readFileSync as readFileSync2,
|
|
103
|
+
statSync,
|
|
104
|
+
writeFileSync as writeFileSync2
|
|
105
|
+
} from "fs";
|
|
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);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
function rewriteImports(source) {
|
|
126
|
+
let hits = 0;
|
|
127
|
+
let next = source;
|
|
128
|
+
const patterns = [
|
|
129
|
+
[
|
|
130
|
+
/from\s+["']@elcrm\/socket\/server["']/g,
|
|
131
|
+
'from "@elcrm/server"'
|
|
132
|
+
],
|
|
133
|
+
[
|
|
134
|
+
/from\s+["']@elcrm\/socket-server["']/g,
|
|
135
|
+
'from "@elcrm/server"'
|
|
136
|
+
],
|
|
137
|
+
[
|
|
138
|
+
/require\s*\(\s*["']@elcrm\/socket\/server["']\s*\)/g,
|
|
139
|
+
'require("@elcrm/server")'
|
|
140
|
+
],
|
|
141
|
+
[
|
|
142
|
+
/require\s*\(\s*["']@elcrm\/socket-server["']\s*\)/g,
|
|
143
|
+
'require("@elcrm/server")'
|
|
144
|
+
]
|
|
145
|
+
];
|
|
146
|
+
for (const [re, repl] of patterns) {
|
|
147
|
+
const before = next;
|
|
148
|
+
next = next.replace(re, () => {
|
|
149
|
+
hits++;
|
|
150
|
+
return repl;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return { next, hits };
|
|
154
|
+
}
|
|
155
|
+
async function migrateSocketServer(cwd, dry) {
|
|
156
|
+
const changes = [];
|
|
157
|
+
const pkg = readPackageJson(cwd);
|
|
158
|
+
let removedSocketServer = false;
|
|
159
|
+
if (pkg) {
|
|
160
|
+
for (const section of [
|
|
161
|
+
"dependencies",
|
|
162
|
+
"devDependencies",
|
|
163
|
+
"peerDependencies"
|
|
164
|
+
]) {
|
|
165
|
+
const map = pkg[section];
|
|
166
|
+
if (!map)
|
|
167
|
+
continue;
|
|
168
|
+
if (map["@elcrm/socket-server"]) {
|
|
169
|
+
removedSocketServer = true;
|
|
170
|
+
changes.push({
|
|
171
|
+
file: "package.json",
|
|
172
|
+
detail: `\u0443\u0434\u0430\u043B\u0438\u0442\u044C ${section}["@elcrm/socket-server"]`
|
|
173
|
+
});
|
|
174
|
+
if (!dry)
|
|
175
|
+
delete map["@elcrm/socket-server"];
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const hasServer = Boolean(pkg.dependencies?.["@elcrm/server"]) || Boolean(pkg.devDependencies?.["@elcrm/server"]);
|
|
179
|
+
if (removedSocketServer && !hasServer) {
|
|
180
|
+
const latest = dry ? null : await fetchLatestVersion("@elcrm/server");
|
|
181
|
+
const ver = latest ? `^${latest}` : "^0.5.3";
|
|
182
|
+
changes.push({
|
|
183
|
+
file: "package.json",
|
|
184
|
+
detail: `\u0434\u043E\u0431\u0430\u0432\u0438\u0442\u044C dependencies["@elcrm/server"] = ${ver}`
|
|
185
|
+
});
|
|
186
|
+
if (!dry) {
|
|
187
|
+
if (!pkg.dependencies)
|
|
188
|
+
pkg.dependencies = {};
|
|
189
|
+
pkg.dependencies["@elcrm/server"] = ver;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (!dry && changes.some((c2) => c2.file === "package.json")) {
|
|
193
|
+
writePackageJson(pkg, cwd);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const roots = ["src", "app", "server", "lib"].map((d) => join3(cwd, d));
|
|
197
|
+
const files = roots.flatMap((r) => walkFiles(r));
|
|
198
|
+
for (const file of files) {
|
|
199
|
+
const raw = readFileSync2(file, "utf8");
|
|
200
|
+
const { next, hits } = rewriteImports(raw);
|
|
201
|
+
if (hits > 0) {
|
|
202
|
+
changes.push({
|
|
203
|
+
file: file.replace(cwd + "/", ""),
|
|
204
|
+
detail: `${hits} \u0437\u0430\u043C\u0435\u043D(\u044B) \u0438\u043C\u043F\u043E\u0440\u0442\u043E\u0432 \u2192 @elcrm/server`
|
|
205
|
+
});
|
|
206
|
+
if (!dry)
|
|
207
|
+
writeFileSync2(file, next, "utf8");
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return changes;
|
|
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
|
+
});
|
|
234
|
+
|
|
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))
|
|
249
|
+
continue;
|
|
250
|
+
const full = join4(dir, name);
|
|
251
|
+
let st;
|
|
252
|
+
try {
|
|
253
|
+
st = statSync2(full);
|
|
254
|
+
} catch {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (st.isDirectory())
|
|
258
|
+
walkCodeFiles(full, out);
|
|
259
|
+
else if (/\.(tsx?|jsx?|mjs|cjs|css)$/.test(name))
|
|
260
|
+
out.push(full);
|
|
261
|
+
}
|
|
262
|
+
return out;
|
|
263
|
+
}
|
|
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;
|
|
300
|
+
});
|
|
301
|
+
}
|
|
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`
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
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}`;
|
|
361
|
+
});
|
|
362
|
+
}
|
|
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`
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
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\(\)\}/;
|
|
399
|
+
for (const file of files) {
|
|
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;
|
|
411
|
+
}
|
|
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}`;
|
|
427
|
+
});
|
|
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)"
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
continue;
|
|
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)`
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
return changes;
|
|
445
|
+
}
|
|
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);
|
|
460
|
+
}
|
|
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;
|
|
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
|
+
});
|
|
487
|
+
|
|
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}`);
|
|
501
|
+
}
|
|
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
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return changes;
|
|
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
|
+
});
|
|
535
|
+
|
|
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
|
+
`);
|
|
565
|
+
}
|
|
566
|
+
return { next, n };
|
|
567
|
+
}
|
|
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 };
|
|
583
|
+
}
|
|
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
|
+
});
|
|
592
|
+
}
|
|
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)
|
|
602
|
+
continue;
|
|
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)
|
|
619
|
+
continue;
|
|
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
|
+
});
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return changes;
|
|
628
|
+
}
|
|
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";
|
|
3
740
|
|
|
4
741
|
// src/parseArgs.ts
|
|
5
742
|
function parseArgs(argv = process.argv.slice(2)) {
|
|
@@ -38,156 +775,1362 @@ function parseArgs(argv = process.argv.slice(2)) {
|
|
|
38
775
|
continue;
|
|
39
776
|
}
|
|
40
777
|
}
|
|
41
|
-
if (body && !flags.includes(body))
|
|
42
|
-
flags.push(body);
|
|
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
|
+
}
|
|
43
1083
|
continue;
|
|
44
1084
|
}
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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);
|
|
51
1101
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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};`);
|
|
55
1136
|
}
|
|
56
|
-
positionals.push(arg);
|
|
57
1137
|
}
|
|
58
|
-
|
|
59
|
-
return
|
|
60
|
-
|
|
61
|
-
positionals: rest,
|
|
62
|
-
options,
|
|
63
|
-
flags
|
|
64
|
-
};
|
|
1138
|
+
lines.push("");
|
|
1139
|
+
return lines.join(`
|
|
1140
|
+
`);
|
|
65
1141
|
}
|
|
66
|
-
function
|
|
67
|
-
|
|
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 };
|
|
68
1281
|
}
|
|
69
1282
|
|
|
70
|
-
// src/
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
function
|
|
77
|
-
const
|
|
78
|
-
|
|
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
|
+
]) {
|
|
79
1295
|
const map = pkg[section];
|
|
80
|
-
if (
|
|
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);
|
|
81
1322
|
continue;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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);
|
|
85
1332
|
}
|
|
86
1333
|
}
|
|
87
1334
|
}
|
|
88
1335
|
return out;
|
|
89
1336
|
}
|
|
90
|
-
function
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
// src/lib/latest.ts
|
|
95
|
-
var CACHE = new Map;
|
|
96
|
-
async function fetchLatestVersion(name) {
|
|
97
|
-
if (CACHE.has(name))
|
|
98
|
-
return CACHE.get(name);
|
|
99
|
-
const url = `https://registry.npmjs.org/${encodeURIComponent(name)}/latest`;
|
|
1337
|
+
function readPkgAt(dir) {
|
|
1338
|
+
const path = join7(dir, "package.json");
|
|
1339
|
+
if (!existsSync7(path))
|
|
1340
|
+
return null;
|
|
100
1341
|
try {
|
|
101
|
-
|
|
102
|
-
headers: { Accept: "application/json" }
|
|
103
|
-
});
|
|
104
|
-
if (!res.ok)
|
|
105
|
-
return null;
|
|
106
|
-
const data = await res.json();
|
|
107
|
-
if (!data.version)
|
|
108
|
-
return null;
|
|
109
|
-
CACHE.set(name, data.version);
|
|
110
|
-
return data.version;
|
|
1342
|
+
return JSON.parse(readFileSync11(path, "utf8"));
|
|
111
1343
|
} catch {
|
|
112
1344
|
return null;
|
|
113
1345
|
}
|
|
114
1346
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
+
|
|
1626
|
+
// src/lib/test/serverScan.ts
|
|
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";
|
|
1629
|
+
|
|
1630
|
+
// src/lib/test/serverRules.ts
|
|
1631
|
+
var CODE_RULES = [
|
|
1632
|
+
{
|
|
1633
|
+
id: "import-elcrm-db",
|
|
1634
|
+
severity: "error",
|
|
1635
|
+
test: /from\s+["']@elcrm\/db["']/,
|
|
1636
|
+
message: "\u041F\u0430\u043A\u0435\u0442 @elcrm/db \u0432\u043B\u0438\u0442 \u0432 @elcrm/server/db",
|
|
1637
|
+
fix: `import { \u2026 } from "@elcrm/server/db"`,
|
|
1638
|
+
auto: true,
|
|
1639
|
+
replace: [
|
|
1640
|
+
{
|
|
1641
|
+
from: /from\s+["']@elcrm\/db["']/g,
|
|
1642
|
+
to: 'from "@elcrm/server/db"'
|
|
1643
|
+
}
|
|
1644
|
+
]
|
|
1645
|
+
},
|
|
1646
|
+
{
|
|
1647
|
+
id: "import-elcrm-jwt",
|
|
1648
|
+
severity: "error",
|
|
1649
|
+
test: /from\s+["']@elcrm\/jwt["']/,
|
|
1650
|
+
message: "\u041F\u0430\u043A\u0435\u0442 @elcrm/jwt \u0432\u043B\u0438\u0442 \u0432 @elcrm/server/jwt",
|
|
1651
|
+
fix: `import { \u2026 } from "@elcrm/server/jwt"`,
|
|
1652
|
+
auto: true,
|
|
1653
|
+
replace: [
|
|
1654
|
+
{
|
|
1655
|
+
from: /from\s+["']@elcrm\/jwt["']/g,
|
|
1656
|
+
to: 'from "@elcrm/server/jwt"'
|
|
1657
|
+
}
|
|
1658
|
+
]
|
|
1659
|
+
},
|
|
1660
|
+
{
|
|
1661
|
+
id: "import-elcrm-cron",
|
|
1662
|
+
severity: "error",
|
|
1663
|
+
test: /from\s+["']@elcrm\/cron["']/,
|
|
1664
|
+
message: "\u041F\u0430\u043A\u0435\u0442 @elcrm/cron \u0432\u043B\u0438\u0442 \u0432 @elcrm/server/cron",
|
|
1665
|
+
fix: `import { cron, createCron } from "@elcrm/server/cron"`,
|
|
1666
|
+
auto: true,
|
|
1667
|
+
replace: [
|
|
1668
|
+
{
|
|
1669
|
+
from: /from\s+["']@elcrm\/cron["']/g,
|
|
1670
|
+
to: 'from "@elcrm/server/cron"'
|
|
1671
|
+
}
|
|
1672
|
+
]
|
|
1673
|
+
},
|
|
1674
|
+
{
|
|
1675
|
+
id: "import-elcrm-tb",
|
|
1676
|
+
severity: "error",
|
|
1677
|
+
test: /from\s+["']@elcrm\/tb["']/,
|
|
1678
|
+
message: "\u041F\u0430\u043A\u0435\u0442 @elcrm/tb \u0432\u043B\u0438\u0442 \u0432 @elcrm/server/tb",
|
|
1679
|
+
fix: `import { tb, createTb } from "@elcrm/server/tb"`,
|
|
1680
|
+
auto: true,
|
|
1681
|
+
replace: [
|
|
1682
|
+
{
|
|
1683
|
+
from: /from\s+["']@elcrm\/tb["']/g,
|
|
1684
|
+
to: 'from "@elcrm/server/tb"'
|
|
1685
|
+
}
|
|
1686
|
+
]
|
|
1687
|
+
},
|
|
1688
|
+
{
|
|
1689
|
+
id: "createWsHub",
|
|
1690
|
+
severity: "error",
|
|
1691
|
+
test: /\bcreateWsHub\b/,
|
|
1692
|
+
message: "createWsHub \u0443\u0434\u0430\u043B\u0451\u043D",
|
|
1693
|
+
fix: 'createSocket("ws").use(\u2026) + sendSocket \u2014 \u0441\u043C. MPC.md',
|
|
1694
|
+
auto: false
|
|
1695
|
+
},
|
|
1696
|
+
{
|
|
1697
|
+
id: "serverConnect",
|
|
1698
|
+
severity: "error",
|
|
1699
|
+
test: /\bserverConnect\b/,
|
|
1700
|
+
message: "serverConnect \u0443\u0434\u0430\u043B\u0451\u043D",
|
|
1701
|
+
fix: 'createSocket("ws").use("auth"|name, handler) \u2014 \u0441\u043C. MPC.md',
|
|
1702
|
+
auto: false
|
|
1703
|
+
},
|
|
1704
|
+
{
|
|
1705
|
+
id: "sendListen",
|
|
1706
|
+
severity: "error",
|
|
1707
|
+
test: /\.sendListen\b/,
|
|
1708
|
+
message: "hub.sendListen \u2192 sendSocket",
|
|
1709
|
+
fix: `import { sendSocket } from "@elcrm/server/ws"; sendSocket(uid, listen, data)`,
|
|
1710
|
+
auto: true,
|
|
1711
|
+
replace: [
|
|
1712
|
+
{
|
|
1713
|
+
from: /(\w+)\.sendListen\(/g,
|
|
1714
|
+
to: "sendSocket("
|
|
1715
|
+
},
|
|
1716
|
+
{
|
|
1717
|
+
from: /(\w+)\.sendListenAll\(/g,
|
|
1718
|
+
to: "sendSocketAll("
|
|
1719
|
+
}
|
|
1720
|
+
]
|
|
1721
|
+
},
|
|
1722
|
+
{
|
|
1723
|
+
id: "sendListenAll",
|
|
1724
|
+
severity: "error",
|
|
1725
|
+
test: /\.sendListenAll\b/,
|
|
1726
|
+
message: "hub.sendListenAll \u2192 sendSocketAll",
|
|
1727
|
+
fix: `import { sendSocketAll } from "@elcrm/server/ws"`,
|
|
1728
|
+
auto: true,
|
|
1729
|
+
replace: [
|
|
1730
|
+
{
|
|
1731
|
+
from: /(\w+)\.sendListenAll\(/g,
|
|
1732
|
+
to: "sendSocketAll("
|
|
1733
|
+
}
|
|
1734
|
+
]
|
|
1735
|
+
},
|
|
1736
|
+
{
|
|
1737
|
+
id: "createServerFromEnv",
|
|
1738
|
+
severity: "error",
|
|
1739
|
+
test: /\bcreateServerFromEnv\b/,
|
|
1740
|
+
message: "createServerFromEnv \u0443\u0434\u0430\u043B\u0451\u043D",
|
|
1741
|
+
fix: "createServer() \u2014 env \u0432\u043D\u0443\u0442\u0440\u0438",
|
|
1742
|
+
auto: true,
|
|
1743
|
+
replace: [
|
|
1744
|
+
{
|
|
1745
|
+
from: /\bcreateServerFromEnv\b/g,
|
|
1746
|
+
to: "createServer"
|
|
1747
|
+
}
|
|
1748
|
+
]
|
|
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
|
+
},
|
|
1758
|
+
{
|
|
1759
|
+
id: "createJwtFromEnv",
|
|
1760
|
+
severity: "warn",
|
|
1761
|
+
test: /\bcreateJwtFromEnv\b/,
|
|
1762
|
+
message: "createJwtFromEnv \u0443\u0434\u0430\u043B\u0451\u043D",
|
|
1763
|
+
fix: "createJWT()",
|
|
1764
|
+
auto: true,
|
|
1765
|
+
replace: [{ from: /\bcreateJwtFromEnv\b/g, to: "createJWT" }]
|
|
1766
|
+
},
|
|
1767
|
+
{
|
|
1768
|
+
id: "createCronFromEnv",
|
|
1769
|
+
severity: "warn",
|
|
1770
|
+
test: /\bcreateCronFromEnv\b/,
|
|
1771
|
+
message: "createCronFromEnv \u0443\u0434\u0430\u043B\u0451\u043D",
|
|
1772
|
+
fix: "createCron() \u0438\u043B\u0438 cron",
|
|
1773
|
+
auto: true,
|
|
1774
|
+
replace: [{ from: /\bcreateCronFromEnv\b/g, to: "createCron" }]
|
|
1775
|
+
},
|
|
1776
|
+
{
|
|
1777
|
+
id: "createTbFromEnv",
|
|
1778
|
+
severity: "warn",
|
|
1779
|
+
test: /\bcreateTbFromEnv\b/,
|
|
1780
|
+
message: "createTbFromEnv \u0443\u0434\u0430\u043B\u0451\u043D",
|
|
1781
|
+
fix: "createTb() \u0438\u043B\u0438 TELEGRAM= + createServer()",
|
|
1782
|
+
auto: true,
|
|
1783
|
+
replace: [{ from: /\bcreateTbFromEnv\b/g, to: "createTb" }]
|
|
1784
|
+
},
|
|
1785
|
+
{
|
|
1786
|
+
id: "registerOpsRouters",
|
|
1787
|
+
severity: "warn",
|
|
1788
|
+
test: /\bregisterOpsRouters\b/,
|
|
1789
|
+
message: "registerOpsRouters \u0443\u0434\u0430\u043B\u0451\u043D",
|
|
1790
|
+
fix: 'createRouter("health"|"system"|"logs").use(\u2026)',
|
|
1791
|
+
auto: false
|
|
1792
|
+
},
|
|
1793
|
+
{
|
|
1794
|
+
id: "Cron-useJob",
|
|
1795
|
+
severity: "info",
|
|
1796
|
+
test: /\.useJob\s*\(/,
|
|
1797
|
+
message: "useJob \u0443\u0441\u0442\u0430\u0440\u0435\u043B",
|
|
1798
|
+
fix: "cron.use(\u2026)",
|
|
1799
|
+
auto: true,
|
|
1800
|
+
replace: [{ from: /\.useJob\s*\(/g, to: ".use(" }]
|
|
1801
|
+
},
|
|
1802
|
+
{
|
|
1803
|
+
id: "WsHubApi-type",
|
|
1804
|
+
severity: "warn",
|
|
1805
|
+
test: /\bWsHubApi\b/,
|
|
1806
|
+
message: "\u0442\u0438\u043F WsHubApi \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u0443\u0435\u0442\u0441\u044F",
|
|
1807
|
+
fix: "sendSocket / getSocket(); \u0442\u0438\u043F \u043D\u0435 \u043D\u0443\u0436\u0435\u043D",
|
|
1808
|
+
auto: false
|
|
1809
|
+
},
|
|
1810
|
+
{
|
|
1811
|
+
id: "homemade-smtp-auth-login",
|
|
1812
|
+
severity: "error",
|
|
1813
|
+
test: /\bAUTH LOGIN\b/,
|
|
1814
|
+
message: "\u0421\u0430\u043C\u043E\u043F\u0438\u0441\u043D\u044B\u0439 SMTP (AUTH LOGIN) \u2014 \u043B\u0435\u0433\u0430\u0441\u0438",
|
|
1815
|
+
fix: 'createMailer / sendMail \u0438\u0437 "@elcrm/server/mail" \u2014 \u0441\u043C. MPC.md',
|
|
1816
|
+
auto: false
|
|
1817
|
+
},
|
|
1818
|
+
{
|
|
1819
|
+
id: "import-nodemailer",
|
|
1820
|
+
severity: "error",
|
|
1821
|
+
test: /from\s+["']nodemailer["']|require\s*\(\s*["']nodemailer["']\s*\)/,
|
|
1822
|
+
message: "nodemailer \u2192 @elcrm/server/mail",
|
|
1823
|
+
fix: 'import { createMailer, sendMail } from "@elcrm/server/mail"',
|
|
1824
|
+
auto: false
|
|
1825
|
+
},
|
|
1826
|
+
{
|
|
1827
|
+
id: "import-web-push",
|
|
1828
|
+
severity: "error",
|
|
1829
|
+
test: /from\s+["']web-push["']|require\s*\(\s*["']web-push["']\s*\)/,
|
|
1830
|
+
message: "web-push \u2192 @elcrm/server/notifications",
|
|
1831
|
+
fix: 'import { \u2026 } from "@elcrm/server/notifications"',
|
|
1832
|
+
auto: false
|
|
1833
|
+
},
|
|
1834
|
+
{
|
|
1835
|
+
id: "cors-origin-singular",
|
|
1836
|
+
severity: "warn",
|
|
1837
|
+
test: /process\.env\.CORS_ORIGIN\b(?!S)/,
|
|
1838
|
+
message: "process.env.CORS_ORIGIN \u2192 CORS_ORIGINS (\u043F\u0430\u043A\u0435\u0442 createServer)",
|
|
1839
|
+
fix: "CORS_ORIGINS=https://a.example,https://b.example \u2014 \u0431\u0435\u0437 path; \u0441\u0432\u043E\u0439 cors({ origin }) \u2014 \u043E\u043A",
|
|
1840
|
+
auto: false
|
|
1841
|
+
},
|
|
1842
|
+
{
|
|
1843
|
+
id: "rest-path-equals-api",
|
|
1844
|
+
severity: "error",
|
|
1845
|
+
test: /path\s*===\s*["']\/api\//,
|
|
1846
|
+
message: 'REST if (path === "/api/\u2026") \u2014 \u043B\u0435\u0433\u0430\u0441\u0438',
|
|
1847
|
+
fix: 'createRouter("name").use("method", handler) + rpcOk/rpcErr \u2014 \u0441\u043C. MPC.md',
|
|
1848
|
+
auto: false
|
|
1849
|
+
},
|
|
1850
|
+
{
|
|
1851
|
+
id: "rest-handle-export",
|
|
1852
|
+
severity: "error",
|
|
1853
|
+
test: /export\s+async\s+function\s+handle\w+\s*\([^)]*\)\s*:\s*Promise<\s*Response\b/,
|
|
1854
|
+
message: "export handle*(ctx): Promise<Response> \u2014 REST-\u043B\u0435\u0433\u0430\u0441\u0438",
|
|
1855
|
+
fix: "\u043B\u043E\u0433\u0438\u043A\u0430 \u0432 createRouter(\u2026).use(\u2026); Response \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F git/hooks/static",
|
|
1856
|
+
auto: false
|
|
1857
|
+
},
|
|
1858
|
+
{
|
|
1859
|
+
id: "rpc-bridge-invoke-handle",
|
|
1860
|
+
severity: "warn",
|
|
1861
|
+
test: /invoke(?:Authed|Public|Repo)\s*\(\s*handle\w+/,
|
|
1862
|
+
message: "RPC-\u043C\u043E\u0441\u0442 invoke*(handle*) \u043F\u043E\u0432\u0435\u0440\u0445 REST \u2014 \u043F\u0440\u043E\u043C\u0435\u0436\u0443\u0442\u043E\u0447\u043D\u044B\u0439 \u043B\u0435\u0433\u0430\u0441\u0438",
|
|
1863
|
+
fix: "\u043F\u0435\u0440\u0435\u043D\u0435\u0441\u0442\u0438 \u0442\u0435\u043B\u043E handler \u0432 createRouter().use(); \u0443\u0431\u0440\u0430\u0442\u044C routes/api",
|
|
1864
|
+
auto: false
|
|
1865
|
+
},
|
|
1866
|
+
{
|
|
1867
|
+
id: "routes-api-import",
|
|
1868
|
+
severity: "warn",
|
|
1869
|
+
test: /from\s+["'][^"']*\/routes\/api\/[^"']+["']/,
|
|
1870
|
+
message: "\u0438\u043C\u043F\u043E\u0440\u0442 \u0438\u0437 routes/api \u2014 REST-\u043B\u0435\u0433\u0430\u0441\u0438",
|
|
1871
|
+
fix: "createRouter \u0432 src/rpc (\u0438\u043B\u0438 \u0440\u044F\u0434\u043E\u043C \u0441 createServer); \u0431\u0435\u0437 handle* \u043C\u043E\u0441\u0442\u0430",
|
|
1872
|
+
auto: false
|
|
1873
|
+
},
|
|
1874
|
+
{
|
|
1875
|
+
id: "cron-useJob",
|
|
1876
|
+
severity: "warn",
|
|
1877
|
+
test: /\b(?:Cron\.)?useJob\s*\(/,
|
|
1878
|
+
message: "Cron.useJob deprecated",
|
|
1879
|
+
fix: 'import { cron } from "@elcrm/server/cron"; cron.use(fn, interval?, name?)',
|
|
1880
|
+
auto: false
|
|
1881
|
+
},
|
|
1882
|
+
{
|
|
1883
|
+
id: "cron-namespace",
|
|
1884
|
+
severity: "info",
|
|
1885
|
+
test: /import\s*\{[^}]*\bCron\b[^}]*\}\s*from\s*["']@elcrm\/server\/cron["']/,
|
|
1886
|
+
message: "namespace Cron deprecated \u2014 singleton cron",
|
|
1887
|
+
fix: 'import { cron } from "@elcrm/server/cron"',
|
|
1888
|
+
auto: false
|
|
1889
|
+
},
|
|
1890
|
+
{
|
|
1891
|
+
id: "manual-jwt-init-handler",
|
|
1892
|
+
severity: "warn",
|
|
1893
|
+
test: /\bjwt\.init\s*\(/,
|
|
1894
|
+
message: "jwt.init \u0432 handler \u2014 createServer \u0443\u0436\u0435 \u043A\u043B\u0430\u0434\u0451\u0442 user; \u043D\u0435 \u043F\u0430\u0440\u0441\u0438\u0442\u044C ssid \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u043E",
|
|
1895
|
+
fix: "\u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435 params.user; \u0434\u043B\u044F \u0433\u0435\u0439\u0442\u0430 \u2014 middleware (if (!user?.id) return rpcLogout())",
|
|
1896
|
+
auto: false
|
|
1897
|
+
},
|
|
1898
|
+
{
|
|
1899
|
+
id: "createSessionMiddleware-reparse",
|
|
1900
|
+
severity: "info",
|
|
1901
|
+
test: /createSessionMiddleware\s*\(\s*\{[^}]*resolve\s*:\s*async/,
|
|
1902
|
+
message: "createSessionMiddleware \u0441 resolve \u0447\u0430\u0441\u0442\u043E \u0441\u043D\u043E\u0432\u0430 \u0437\u043E\u0432\u0451\u0442 jwt.init \u2014 \u0434\u0443\u0431\u043B\u044C",
|
|
1903
|
+
fix: "\u0435\u0441\u043B\u0438 createServer({ jwt }) \u0443\u0436\u0435 \u0435\u0441\u0442\u044C: middleware \u0442\u043E\u043B\u044C\u043A\u043E if (!user?.id) return rpcLogout()",
|
|
1904
|
+
auto: false
|
|
1905
|
+
},
|
|
1906
|
+
{
|
|
1907
|
+
id: "node-fs-import",
|
|
1908
|
+
severity: "warn",
|
|
1909
|
+
test: /from\s+["'](?:node:)?fs(?:\/promises)?["']/,
|
|
1910
|
+
message: "import node:fs / fs \u2014 \u0432 Bun-\u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u043D\u0435 \u043D\u0443\u0436\u0435\u043D \u0434\u043B\u044F \u0442\u0438\u043F\u043E\u0432\u044B\u0445 \u0437\u0430\u0434\u0430\u0447",
|
|
1911
|
+
fix: "SQLite: initDB \u0441\u0430\u043C \u0441\u043E\u0437\u0434\u0430\u0451\u0442 \u043A\u0430\u0442\u0430\u043B\u043E\u0433; \u0444\u0430\u0439\u043B\u044B \u2014 Bun.file / Bun.write; \u043F\u0443\u0442\u0438 \u2014 \u0431\u0435\u0437 node:path",
|
|
1912
|
+
auto: false
|
|
1913
|
+
},
|
|
1914
|
+
{
|
|
1915
|
+
id: "node-path-import",
|
|
1916
|
+
severity: "warn",
|
|
1917
|
+
test: /from\s+["'](?:node:)?path["']/,
|
|
1918
|
+
message: "import node:path / path \u2014 \u0438\u0437\u0431\u0435\u0433\u0430\u0439\u0442\u0435 \u0432 app \u043D\u0430 @elcrm/server",
|
|
1919
|
+
fix: "\u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0435 \u0441\u0442\u0440\u043E\u043A\u0438 / URL; mkdir \u0434\u043B\u044F sqlite \u0434\u0435\u043B\u0430\u0435\u0442 @elcrm/server/db",
|
|
1920
|
+
auto: false
|
|
1921
|
+
}
|
|
1922
|
+
];
|
|
1923
|
+
var PKG_LEGACY = [
|
|
1924
|
+
"@elcrm/db",
|
|
1925
|
+
"@elcrm/jwt",
|
|
1926
|
+
"@elcrm/cron",
|
|
1927
|
+
"@elcrm/tb",
|
|
1928
|
+
"nodemailer",
|
|
1929
|
+
"web-push"
|
|
1930
|
+
];
|
|
1931
|
+
|
|
1932
|
+
// src/lib/test/serverScan.ts
|
|
1933
|
+
var SKIP2 = new Set([
|
|
1934
|
+
"node_modules",
|
|
1935
|
+
"dist",
|
|
1936
|
+
".git",
|
|
1937
|
+
"coverage",
|
|
1938
|
+
".bun",
|
|
1939
|
+
"vendor"
|
|
1940
|
+
]);
|
|
1941
|
+
function walkFiles2(root, dir, out) {
|
|
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
|
+
});
|
|
124
1983
|
}
|
|
125
|
-
return
|
|
1984
|
+
return findings;
|
|
126
1985
|
}
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
const
|
|
1986
|
+
function scanPackageJson(root, file, text) {
|
|
1987
|
+
const findings = [];
|
|
1988
|
+
const rel2 = relative3(root, file) || file;
|
|
1989
|
+
let pkg;
|
|
130
1990
|
try {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
+
});
|
|
143
2008
|
}
|
|
144
|
-
}
|
|
145
|
-
|
|
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;
|
|
146
2023
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
+
}
|
|
155
2035
|
}
|
|
156
|
-
|
|
157
|
-
if (bun.status === 0)
|
|
158
|
-
return "bun";
|
|
159
|
-
return "npm";
|
|
2036
|
+
return { root, findings, filesScanned: files.length };
|
|
160
2037
|
}
|
|
161
|
-
function
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
});
|
|
169
|
-
|
|
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;
|
|
170
2074
|
}
|
|
171
2075
|
|
|
172
|
-
// src/
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
function readPackageJson(cwd = process.cwd()) {
|
|
179
|
-
const path = packageJsonPath(cwd);
|
|
180
|
-
if (!existsSync2(path))
|
|
181
|
-
return null;
|
|
182
|
-
try {
|
|
183
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
184
|
-
} catch {
|
|
185
|
-
return null;
|
|
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;
|
|
186
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;
|
|
187
2103
|
}
|
|
188
|
-
function
|
|
189
|
-
|
|
190
|
-
|
|
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`));
|
|
191
2134
|
}
|
|
192
2135
|
|
|
193
2136
|
// src/commands/update.ts
|
|
@@ -206,6 +2149,8 @@ async function cmdUpdate(parsed) {
|
|
|
206
2149
|
const dry = hasFlag(parsed, "dry");
|
|
207
2150
|
const useCore = hasFlag(parsed, "core");
|
|
208
2151
|
const noInstall = hasFlag(parsed, "no-install");
|
|
2152
|
+
const doFix = hasFlag(parsed, "fix");
|
|
2153
|
+
const doTest = hasFlag(parsed, "test");
|
|
209
2154
|
const names = uniqueElcrmNames(deps);
|
|
210
2155
|
console.log(`\u041D\u0430\u0439\u0434\u0435\u043D\u043E @elcrm/*: ${names.length}`);
|
|
211
2156
|
let versions;
|
|
@@ -227,17 +2172,17 @@ async function cmdUpdate(parsed) {
|
|
|
227
2172
|
for (const dep of deps) {
|
|
228
2173
|
const latest = versions.get(dep.name);
|
|
229
2174
|
if (!latest) {
|
|
230
|
-
rows.push(` ? ${dep.name}: ${dep.version} \u2192 (\u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E \u043D\u0430 npm)`);
|
|
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)")}`);
|
|
231
2176
|
continue;
|
|
232
2177
|
}
|
|
233
2178
|
const current = dep.version.replace(/^[\^~]/, "");
|
|
234
2179
|
if (current === latest) {
|
|
235
|
-
rows.push(` = ${dep.name}: ${dep.version}`);
|
|
2180
|
+
rows.push(` ${c.gray("=")} ${dep.name}: ${c.gray(dep.version)}`);
|
|
236
2181
|
continue;
|
|
237
2182
|
}
|
|
238
2183
|
const prefix = dep.version.startsWith("^") ? "^" : dep.version.startsWith("~") ? "~" : "";
|
|
239
2184
|
const next = `${prefix}${latest}`;
|
|
240
|
-
rows.push(` \u2191 ${dep.name}: ${dep.version} \u2192 ${next}`);
|
|
2185
|
+
rows.push(` ${c.blue("\u2191")} ${dep.name}: ${c.gray(dep.version)} \u2192 ${c.green(next)}`);
|
|
241
2186
|
if (!dry) {
|
|
242
2187
|
const section = pkg[dep.section];
|
|
243
2188
|
section[dep.name] = next;
|
|
@@ -251,100 +2196,172 @@ async function cmdUpdate(parsed) {
|
|
|
251
2196
|
if (dry) {
|
|
252
2197
|
console.log(`
|
|
253
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
|
+
}
|
|
254
2225
|
return;
|
|
255
2226
|
}
|
|
256
2227
|
if (changed === 0) {
|
|
257
2228
|
console.log(`
|
|
258
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.`);
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
console.log(`
|
|
2230
|
+
} else {
|
|
2231
|
+
writePackageJson(pkg, cwd);
|
|
2232
|
+
console.log(`
|
|
263
2233
|
package.json \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D (${changed}).`);
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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.");
|
|
269
2242
|
}
|
|
270
|
-
}
|
|
271
|
-
|
|
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`);
|
|
2257
|
+
try {
|
|
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}`);
|
|
2276
|
+
}
|
|
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)");
|
|
272
2289
|
}
|
|
273
2290
|
}
|
|
274
2291
|
|
|
275
2292
|
// src/lib/copyTemplate.ts
|
|
276
2293
|
import {
|
|
277
2294
|
cpSync,
|
|
278
|
-
existsSync as
|
|
2295
|
+
existsSync as existsSync11,
|
|
279
2296
|
mkdirSync,
|
|
280
|
-
readdirSync,
|
|
281
|
-
readFileSync as
|
|
282
|
-
statSync,
|
|
283
|
-
writeFileSync as
|
|
2297
|
+
readdirSync as readdirSync7,
|
|
2298
|
+
readFileSync as readFileSync14,
|
|
2299
|
+
statSync as statSync7,
|
|
2300
|
+
writeFileSync as writeFileSync5
|
|
284
2301
|
} from "fs";
|
|
285
|
-
import { join as
|
|
2302
|
+
import { join as join11, resolve as resolve2 } from "path";
|
|
286
2303
|
|
|
287
2304
|
// src/lib/paths.ts
|
|
288
|
-
import { existsSync as
|
|
289
|
-
import { dirname, join as
|
|
2305
|
+
import { existsSync as existsSync10 } from "fs";
|
|
2306
|
+
import { dirname as dirname2, join as join10 } from "path";
|
|
290
2307
|
import { fileURLToPath } from "url";
|
|
291
2308
|
function packageRoot() {
|
|
292
|
-
const here =
|
|
293
|
-
const candidates = [here,
|
|
2309
|
+
const here = dirname2(fileURLToPath(import.meta.url));
|
|
2310
|
+
const candidates = [here, join10(here, ".."), join10(here, "..", "..")];
|
|
294
2311
|
for (const dir of candidates) {
|
|
295
|
-
if (
|
|
2312
|
+
if (existsSync10(join10(dir, "package.json")) && existsSync10(join10(dir, "templates"))) {
|
|
296
2313
|
return dir;
|
|
297
2314
|
}
|
|
298
2315
|
}
|
|
299
2316
|
for (const dir of candidates) {
|
|
300
|
-
if (
|
|
2317
|
+
if (existsSync10(join10(dir, "package.json")))
|
|
301
2318
|
return dir;
|
|
302
2319
|
}
|
|
303
|
-
return
|
|
2320
|
+
return join10(here, "..");
|
|
304
2321
|
}
|
|
305
2322
|
function templatesDir() {
|
|
306
|
-
return
|
|
2323
|
+
return join10(packageRoot(), "templates");
|
|
307
2324
|
}
|
|
308
2325
|
|
|
309
2326
|
// src/lib/copyTemplate.ts
|
|
310
|
-
var
|
|
2327
|
+
var SKIP3 = new Set(["node_modules", "dist", ".git"]);
|
|
311
2328
|
function folderToPackageName(projectPath) {
|
|
312
2329
|
const base = projectPath.replace(/\\/g, "/").split("/").filter(Boolean).pop() ?? "app";
|
|
313
2330
|
const slug = base.toLowerCase().replace(/[^a-z0-9-._~/]/g, "-").replace(/^[-_.]+|[-_.]+$/g, "");
|
|
314
2331
|
return slug || "app";
|
|
315
2332
|
}
|
|
316
2333
|
function isEmptyDir(dir) {
|
|
317
|
-
if (!
|
|
2334
|
+
if (!existsSync11(dir))
|
|
318
2335
|
return true;
|
|
319
|
-
const entries =
|
|
2336
|
+
const entries = readdirSync7(dir).filter((e) => e !== ".git" && e !== ".DS_Store");
|
|
320
2337
|
return entries.length === 0;
|
|
321
2338
|
}
|
|
322
2339
|
function writePkgName(pkgPath, name) {
|
|
323
|
-
if (!
|
|
2340
|
+
if (!existsSync11(pkgPath))
|
|
324
2341
|
return;
|
|
325
|
-
const pkg = JSON.parse(
|
|
2342
|
+
const pkg = JSON.parse(readFileSync14(pkgPath, "utf8"));
|
|
326
2343
|
pkg.name = name;
|
|
327
2344
|
pkg.version = pkg.version ?? "0.0.0";
|
|
328
|
-
|
|
2345
|
+
writeFileSync5(pkgPath, JSON.stringify(pkg, null, 2) + `
|
|
329
2346
|
`, "utf8");
|
|
330
2347
|
}
|
|
331
2348
|
function finalizePanelNames(targetDir, baseName) {
|
|
332
|
-
writePkgName(
|
|
333
|
-
writePkgName(
|
|
334
|
-
writePkgName(
|
|
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`);
|
|
335
2352
|
}
|
|
336
2353
|
function copyTemplate(templateName, targetDir, packageName) {
|
|
337
|
-
const src =
|
|
338
|
-
if (!
|
|
2354
|
+
const src = join11(templatesDir(), templateName);
|
|
2355
|
+
if (!existsSync11(src) || !statSync7(src).isDirectory()) {
|
|
339
2356
|
throw new Error(`\u0428\u0430\u0431\u043B\u043E\u043D \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${templateName}
|
|
340
2357
|
\u041E\u0436\u0438\u0434\u0430\u043B\u0441\u044F \u043A\u0430\u0442\u0430\u043B\u043E\u0433: ${src}`);
|
|
341
2358
|
}
|
|
342
2359
|
mkdirSync(targetDir, { recursive: true });
|
|
343
|
-
for (const entry of
|
|
344
|
-
if (
|
|
2360
|
+
for (const entry of readdirSync7(src)) {
|
|
2361
|
+
if (SKIP3.has(entry))
|
|
345
2362
|
continue;
|
|
346
|
-
const from =
|
|
347
|
-
const to =
|
|
2363
|
+
const from = join11(src, entry);
|
|
2364
|
+
const to = join11(targetDir, entry);
|
|
348
2365
|
cpSync(from, to, { recursive: true });
|
|
349
2366
|
}
|
|
350
2367
|
const name = packageName ?? folderToPackageName(targetDir);
|
|
@@ -352,19 +2369,19 @@ function copyTemplate(templateName, targetDir, packageName) {
|
|
|
352
2369
|
finalizePanelNames(targetDir, name);
|
|
353
2370
|
return;
|
|
354
2371
|
}
|
|
355
|
-
writePkgName(
|
|
2372
|
+
writePkgName(join11(targetDir, "package.json"), name);
|
|
356
2373
|
}
|
|
357
2374
|
function listTemplates() {
|
|
358
2375
|
const dir = templatesDir();
|
|
359
|
-
if (!
|
|
2376
|
+
if (!existsSync11(dir))
|
|
360
2377
|
return [];
|
|
361
|
-
return
|
|
362
|
-
const p =
|
|
363
|
-
return
|
|
2378
|
+
return readdirSync7(dir).filter((name) => {
|
|
2379
|
+
const p = join11(dir, name);
|
|
2380
|
+
return statSync7(p).isDirectory() && !SKIP3.has(name);
|
|
364
2381
|
});
|
|
365
2382
|
}
|
|
366
2383
|
function resolveTarget(pathOrName, cwd = process.cwd()) {
|
|
367
|
-
return
|
|
2384
|
+
return resolve2(cwd, pathOrName);
|
|
368
2385
|
}
|
|
369
2386
|
|
|
370
2387
|
// src/commands/create.ts
|
|
@@ -426,165 +2443,40 @@ async function cmdInit(parsed) {
|
|
|
426
2443
|
const cwd = process.cwd();
|
|
427
2444
|
const force = hasFlag(parsed, "force");
|
|
428
2445
|
const noInstall = hasFlag(parsed, "no-install");
|
|
429
|
-
if (!force && !isEmptyDir(cwd)) {
|
|
430
|
-
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>.");
|
|
431
|
-
process.exit(1);
|
|
432
|
-
}
|
|
433
|
-
const pkgName = template === "lib" ? libPackageName(cwd) : folderToPackageName(cwd);
|
|
434
|
-
console.log(`\u0428\u0430\u0431\u043B\u043E\u043D: ${template}`);
|
|
435
|
-
console.log(`\u041A\u0430\u0442\u0430\u043B\u043E\u0433: ${cwd}`);
|
|
436
|
-
copyTemplate(template, cwd, pkgName);
|
|
437
|
-
console.log("\u041F\u0440\u043E\u0435\u043A\u0442 \u0438\u043D\u0438\u0446\u0438\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D.");
|
|
438
|
-
if (!noInstall) {
|
|
439
|
-
const ok = installDeps(cwd);
|
|
440
|
-
if (!ok)
|
|
441
|
-
process.exit(1);
|
|
442
|
-
}
|
|
443
|
-
doneHint(template);
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// src/lib/migrate/socket-server.ts
|
|
447
|
-
import {
|
|
448
|
-
existsSync as existsSync5,
|
|
449
|
-
readdirSync as readdirSync2,
|
|
450
|
-
readFileSync as readFileSync3,
|
|
451
|
-
statSync as statSync2,
|
|
452
|
-
writeFileSync as writeFileSync3
|
|
453
|
-
} from "fs";
|
|
454
|
-
import { join as join5 } from "path";
|
|
455
|
-
var SKIP_DIRS = new Set([
|
|
456
|
-
"node_modules",
|
|
457
|
-
"dist",
|
|
458
|
-
".git",
|
|
459
|
-
"coverage",
|
|
460
|
-
".turbo"
|
|
461
|
-
]);
|
|
462
|
-
var TEXT_EXT = new Set([
|
|
463
|
-
".ts",
|
|
464
|
-
".tsx",
|
|
465
|
-
".js",
|
|
466
|
-
".jsx",
|
|
467
|
-
".mjs",
|
|
468
|
-
".cjs",
|
|
469
|
-
".mts",
|
|
470
|
-
".cts"
|
|
471
|
-
]);
|
|
472
|
-
function walkFiles(dir, out = []) {
|
|
473
|
-
if (!existsSync5(dir))
|
|
474
|
-
return out;
|
|
475
|
-
for (const name of readdirSync2(dir)) {
|
|
476
|
-
if (SKIP_DIRS.has(name))
|
|
477
|
-
continue;
|
|
478
|
-
const full = join5(dir, name);
|
|
479
|
-
const st = statSync2(full);
|
|
480
|
-
if (st.isDirectory())
|
|
481
|
-
walkFiles(full, out);
|
|
482
|
-
else {
|
|
483
|
-
const ext = name.includes(".") ? `.${name.split(".").pop()}` : "";
|
|
484
|
-
if (TEXT_EXT.has(ext))
|
|
485
|
-
out.push(full);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
return out;
|
|
489
|
-
}
|
|
490
|
-
function rewriteImports(source) {
|
|
491
|
-
let hits = 0;
|
|
492
|
-
let next = source;
|
|
493
|
-
const patterns = [
|
|
494
|
-
[
|
|
495
|
-
/from\s+["']@elcrm\/socket\/server["']/g,
|
|
496
|
-
'from "@elcrm/server"'
|
|
497
|
-
],
|
|
498
|
-
[
|
|
499
|
-
/from\s+["']@elcrm\/socket-server["']/g,
|
|
500
|
-
'from "@elcrm/server"'
|
|
501
|
-
],
|
|
502
|
-
[
|
|
503
|
-
/require\s*\(\s*["']@elcrm\/socket\/server["']\s*\)/g,
|
|
504
|
-
'require("@elcrm/server")'
|
|
505
|
-
],
|
|
506
|
-
[
|
|
507
|
-
/require\s*\(\s*["']@elcrm\/socket-server["']\s*\)/g,
|
|
508
|
-
'require("@elcrm/server")'
|
|
509
|
-
]
|
|
510
|
-
];
|
|
511
|
-
for (const [re, repl] of patterns) {
|
|
512
|
-
const before = next;
|
|
513
|
-
next = next.replace(re, () => {
|
|
514
|
-
hits++;
|
|
515
|
-
return repl;
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
return { next, hits };
|
|
519
|
-
}
|
|
520
|
-
async function migrateSocketServer(cwd, dry) {
|
|
521
|
-
const changes = [];
|
|
522
|
-
const pkg = readPackageJson(cwd);
|
|
523
|
-
let removedSocketServer = false;
|
|
524
|
-
if (pkg) {
|
|
525
|
-
for (const section of [
|
|
526
|
-
"dependencies",
|
|
527
|
-
"devDependencies",
|
|
528
|
-
"peerDependencies"
|
|
529
|
-
]) {
|
|
530
|
-
const map = pkg[section];
|
|
531
|
-
if (!map)
|
|
532
|
-
continue;
|
|
533
|
-
if (map["@elcrm/socket-server"]) {
|
|
534
|
-
removedSocketServer = true;
|
|
535
|
-
changes.push({
|
|
536
|
-
file: "package.json",
|
|
537
|
-
detail: `\u0443\u0434\u0430\u043B\u0438\u0442\u044C ${section}["@elcrm/socket-server"]`
|
|
538
|
-
});
|
|
539
|
-
if (!dry)
|
|
540
|
-
delete map["@elcrm/socket-server"];
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
const hasServer = Boolean(pkg.dependencies?.["@elcrm/server"]) || Boolean(pkg.devDependencies?.["@elcrm/server"]);
|
|
544
|
-
if (removedSocketServer && !hasServer) {
|
|
545
|
-
const latest = dry ? null : await fetchLatestVersion("@elcrm/server");
|
|
546
|
-
const ver = latest ? `^${latest}` : "^0.5.3";
|
|
547
|
-
changes.push({
|
|
548
|
-
file: "package.json",
|
|
549
|
-
detail: `\u0434\u043E\u0431\u0430\u0432\u0438\u0442\u044C dependencies["@elcrm/server"] = ${ver}`
|
|
550
|
-
});
|
|
551
|
-
if (!dry) {
|
|
552
|
-
if (!pkg.dependencies)
|
|
553
|
-
pkg.dependencies = {};
|
|
554
|
-
pkg.dependencies["@elcrm/server"] = ver;
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
if (!dry && changes.some((c) => c.file === "package.json")) {
|
|
558
|
-
writePackageJson(pkg, cwd);
|
|
559
|
-
}
|
|
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);
|
|
560
2449
|
}
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
});
|
|
571
|
-
if (!dry)
|
|
572
|
-
writeFileSync3(file, next, "utf8");
|
|
573
|
-
}
|
|
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);
|
|
574
2459
|
}
|
|
575
|
-
|
|
2460
|
+
doneHint(template);
|
|
576
2461
|
}
|
|
577
2462
|
|
|
578
2463
|
// src/commands/migrate.ts
|
|
2464
|
+
init_socket_server();
|
|
2465
|
+
init_front();
|
|
579
2466
|
var MIGRATIONS = {
|
|
580
|
-
"socket-server": migrateSocketServer
|
|
2467
|
+
"socket-server": migrateSocketServer,
|
|
2468
|
+
...FRONT_MIGRATIONS,
|
|
2469
|
+
front: migrateFrontAll,
|
|
2470
|
+
ui: migrateFrontAll
|
|
581
2471
|
};
|
|
582
2472
|
async function cmdMigrate(parsed) {
|
|
583
2473
|
if (hasFlag(parsed, "list") || parsed.positionals[0] === "list") {
|
|
584
2474
|
console.log("\u0414\u043E\u0441\u0442\u0443\u043F\u043D\u044B\u0435 \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438:");
|
|
585
|
-
|
|
586
|
-
|
|
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}`);
|
|
587
2478
|
}
|
|
2479
|
+
console.log(" socket-server (\u0442\u0430\u043A\u0436\u0435 \u0432 front)");
|
|
588
2480
|
return;
|
|
589
2481
|
}
|
|
590
2482
|
const name = parsed.positionals[0];
|
|
@@ -608,127 +2500,51 @@ async function cmdMigrate(parsed) {
|
|
|
608
2500
|
console.log("\u0418\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439 \u043D\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F.");
|
|
609
2501
|
return;
|
|
610
2502
|
}
|
|
611
|
-
for (const
|
|
612
|
-
console.log(` ${
|
|
2503
|
+
for (const c2 of changes) {
|
|
2504
|
+
console.log(` ${c2.file}: ${c2.detail}`);
|
|
613
2505
|
}
|
|
614
2506
|
console.log(dry ? `
|
|
615
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.` : `
|
|
616
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");
|
|
2511
|
+
}
|
|
617
2512
|
}
|
|
618
2513
|
|
|
619
2514
|
// src/commands/doctor.ts
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
var DEPRECATED_IMPORT_RE = /(?:from\s+|require\s*\(\s*)["']@elcrm\/(?:socket\/server|socket-server)["']/;
|
|
623
|
-
function scanDeprecatedImports(cwd) {
|
|
624
|
-
const hits = [];
|
|
625
|
-
const SKIP2 = new Set(["node_modules", "dist", ".git"]);
|
|
626
|
-
const walk = (dir) => {
|
|
627
|
-
if (!existsSync6(dir))
|
|
628
|
-
return;
|
|
629
|
-
for (const name of readdirSync3(dir)) {
|
|
630
|
-
if (SKIP2.has(name))
|
|
631
|
-
continue;
|
|
632
|
-
const full = join6(dir, name);
|
|
633
|
-
const st = statSync3(full);
|
|
634
|
-
if (st.isDirectory())
|
|
635
|
-
walk(full);
|
|
636
|
-
else if (/\.(tsx?|jsx?|mjs|cjs)$/.test(name)) {
|
|
637
|
-
const text = readFileSync4(full, "utf8");
|
|
638
|
-
if (DEPRECATED_IMPORT_RE.test(text)) {
|
|
639
|
-
hits.push(full.replace(cwd + "/", ""));
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
};
|
|
644
|
-
walk(join6(cwd, "src"));
|
|
645
|
-
return hits;
|
|
646
|
-
}
|
|
2515
|
+
init_socket_server();
|
|
2516
|
+
init_pkg();
|
|
647
2517
|
async function cmdDoctor(parsed) {
|
|
648
2518
|
const cwd = process.cwd();
|
|
649
2519
|
const fix = hasFlag(parsed, "fix");
|
|
650
|
-
const issues = [];
|
|
651
2520
|
const pkg = readPackageJson(cwd);
|
|
652
2521
|
if (!pkg) {
|
|
653
2522
|
console.error("package.json \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D.");
|
|
654
2523
|
process.exit(1);
|
|
655
2524
|
}
|
|
656
|
-
if (pkg
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
code: "engines",
|
|
668
|
-
message: "\u041D\u0435\u0442 engines.node (\u043E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F >=18)",
|
|
669
|
-
fixable: true
|
|
670
|
-
});
|
|
671
|
-
}
|
|
672
|
-
const scripts = pkg.scripts ?? {};
|
|
673
|
-
const isLib = Boolean(pkg.name?.startsWith("@elcrm/"));
|
|
674
|
-
if (isLib) {
|
|
675
|
-
if (!scripts.build && !scripts["pack:check"]) {
|
|
676
|
-
issues.push({
|
|
677
|
-
level: "warn",
|
|
678
|
-
code: "scripts",
|
|
679
|
-
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"
|
|
680
|
-
});
|
|
681
|
-
}
|
|
682
|
-
if (!scripts["pack:check"]) {
|
|
683
|
-
issues.push({
|
|
684
|
-
level: "info",
|
|
685
|
-
code: "pack-check",
|
|
686
|
-
message: "\u041D\u0435\u0442 scripts.pack:check",
|
|
687
|
-
fixable: true
|
|
688
|
-
});
|
|
2525
|
+
if (hasElcrmServer(pkg)) {
|
|
2526
|
+
const result = scanServerProject(cwd);
|
|
2527
|
+
const errors = printServerFindings(result);
|
|
2528
|
+
if (!fix) {
|
|
2529
|
+
if (result.findings.some((f) => f.auto)) {
|
|
2530
|
+
console.log(`
|
|
2531
|
+
\u0410\u0432\u0442\u043E\u0437\u0430\u043C\u0435\u043D\u044B: bunx elcrm-server update (\u0438\u043B\u0438 elcrm migrate \u2026)`);
|
|
2532
|
+
}
|
|
2533
|
+
if (errors)
|
|
2534
|
+
process.exit(1);
|
|
2535
|
+
return;
|
|
689
2536
|
}
|
|
2537
|
+
console.log("doctor --fix \u043D\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435: \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435 bunx elcrm-server update");
|
|
2538
|
+
if (errors)
|
|
2539
|
+
process.exit(1);
|
|
2540
|
+
return;
|
|
690
2541
|
}
|
|
691
|
-
const
|
|
692
|
-
if (deps.some((d) => d.name === "@elcrm/socket-server")) {
|
|
693
|
-
issues.push({
|
|
694
|
-
level: "error",
|
|
695
|
-
code: "deprecated-socket-server",
|
|
696
|
-
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",
|
|
697
|
-
fixable: true
|
|
698
|
-
});
|
|
699
|
-
}
|
|
700
|
-
const umbrella = pkg.dependencies?.elcrm || pkg.devDependencies?.elcrm;
|
|
701
|
-
if (umbrella && /^[\^~]?0\.9\./.test(umbrella)) {
|
|
702
|
-
issues.push({
|
|
703
|
-
level: "warn",
|
|
704
|
-
code: "legacy-umbrella",
|
|
705
|
-
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`
|
|
706
|
-
});
|
|
707
|
-
}
|
|
708
|
-
const importHits = scanDeprecatedImports(cwd);
|
|
709
|
-
for (const file of importHits) {
|
|
710
|
-
issues.push({
|
|
711
|
-
level: "error",
|
|
712
|
-
code: "deprecated-import",
|
|
713
|
-
message: `${file}: \u0438\u043C\u043F\u043E\u0440\u0442 @elcrm/socket/server \u0438\u043B\u0438 socket-server`,
|
|
714
|
-
fixable: true
|
|
715
|
-
});
|
|
716
|
-
}
|
|
717
|
-
if (deps.length === 0 && !isLib) {
|
|
718
|
-
issues.push({
|
|
719
|
-
level: "info",
|
|
720
|
-
code: "no-elcrm",
|
|
721
|
-
message: "\u041D\u0435\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0435\u0439 @elcrm/*"
|
|
722
|
-
});
|
|
723
|
-
}
|
|
2542
|
+
const issues = collectFrontIssues(cwd, pkg);
|
|
724
2543
|
if (issues.length === 0) {
|
|
725
2544
|
console.log("doctor: \u0437\u0430\u043C\u0435\u0447\u0430\u043D\u0438\u0439 \u043D\u0435\u0442.");
|
|
726
2545
|
return;
|
|
727
2546
|
}
|
|
728
|
-
|
|
729
|
-
const mark = issue.level === "error" ? "E" : issue.level === "warn" ? "W" : "I";
|
|
730
|
-
console.log(`[${mark}] ${issue.code}: ${issue.message}`);
|
|
731
|
-
}
|
|
2547
|
+
printIssues(issues);
|
|
732
2548
|
if (!fix) {
|
|
733
2549
|
const fixable = issues.filter((i) => i.fixable).length;
|
|
734
2550
|
if (fixable) {
|
|
@@ -746,10 +2562,11 @@ async function cmdDoctor(parsed) {
|
|
|
746
2562
|
}
|
|
747
2563
|
if (!pkg.engines)
|
|
748
2564
|
pkg.engines = {};
|
|
749
|
-
if (!pkg.engines.node) {
|
|
2565
|
+
if (!pkg.engines.node && !pkg.engines.bun) {
|
|
750
2566
|
pkg.engines.node = ">=18";
|
|
751
2567
|
pkgDirty = true;
|
|
752
2568
|
}
|
|
2569
|
+
const isLib = Boolean(pkg.name?.startsWith("@elcrm/"));
|
|
753
2570
|
if (isLib) {
|
|
754
2571
|
if (!pkg.scripts)
|
|
755
2572
|
pkg.scripts = {};
|
|
@@ -764,14 +2581,20 @@ async function cmdDoctor(parsed) {
|
|
|
764
2581
|
if (mig.length) {
|
|
765
2582
|
console.log(`\u041C\u0438\u0433\u0440\u0430\u0446\u0438\u044F socket-server: ${mig.length} \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439.`);
|
|
766
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
|
+
}
|
|
767
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.");
|
|
768
|
-
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");
|
|
769
2591
|
}
|
|
770
2592
|
|
|
771
2593
|
// src/commands/build.ts
|
|
772
|
-
import { existsSync as
|
|
2594
|
+
import { existsSync as existsSync12 } from "fs";
|
|
773
2595
|
|
|
774
2596
|
// src/lib/nextVersion.ts
|
|
2597
|
+
init_pkg();
|
|
775
2598
|
async function nextVersion() {
|
|
776
2599
|
const pkg = readPackageJson();
|
|
777
2600
|
if (!pkg?.version) {
|
|
@@ -792,9 +2615,9 @@ async function nextVersion() {
|
|
|
792
2615
|
|
|
793
2616
|
// src/commands/build.ts
|
|
794
2617
|
function resolveEntrypoint() {
|
|
795
|
-
if (
|
|
2618
|
+
if (existsSync12("./src/index.js"))
|
|
796
2619
|
return "./src/index.js";
|
|
797
|
-
if (
|
|
2620
|
+
if (existsSync12("./src/index.ts"))
|
|
798
2621
|
return "./src/index.ts";
|
|
799
2622
|
return "./src/index.js";
|
|
800
2623
|
}
|
|
@@ -871,7 +2694,521 @@ async function cmdUuid() {
|
|
|
871
2694
|
console.log(crypto.randomUUID());
|
|
872
2695
|
}
|
|
873
2696
|
|
|
2697
|
+
// src/commands/postbuild.ts
|
|
2698
|
+
import { resolve as resolve3 } from "path";
|
|
2699
|
+
|
|
2700
|
+
// src/vite/postbuild.ts
|
|
2701
|
+
import {
|
|
2702
|
+
existsSync as existsSync13,
|
|
2703
|
+
readdirSync as readdirSync9,
|
|
2704
|
+
readFileSync as readFileSync16,
|
|
2705
|
+
rmSync,
|
|
2706
|
+
statSync as statSync8,
|
|
2707
|
+
writeFileSync as writeFileSync7
|
|
2708
|
+
} from "fs";
|
|
2709
|
+
import { join as join13 } from "path";
|
|
2710
|
+
|
|
2711
|
+
// src/vite/concat-css.ts
|
|
2712
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync15, writeFileSync as writeFileSync6 } from "fs";
|
|
2713
|
+
import { join as join12 } from "path";
|
|
2714
|
+
function concatDistCss(options) {
|
|
2715
|
+
const outFile = options.outFile ?? "index.css";
|
|
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(`
|
|
2718
|
+
`);
|
|
2719
|
+
const outPath = join12(options.distDir, outFile);
|
|
2720
|
+
writeFileSync6(outPath, css);
|
|
2721
|
+
console.log(`[concat-css] ${files.length} \u0444\u0430\u0439\u043B\u043E\u0432 \u2192 ${outFile}`);
|
|
2722
|
+
return outPath;
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
// src/vite/postbuild.ts
|
|
2726
|
+
function postbuildLib(options) {
|
|
2727
|
+
const dist = options.distDir;
|
|
2728
|
+
const keep = new Set([
|
|
2729
|
+
"chunks",
|
|
2730
|
+
"assets",
|
|
2731
|
+
...options.keepDirs ?? []
|
|
2732
|
+
]);
|
|
2733
|
+
concatDistCss({
|
|
2734
|
+
distDir: dist,
|
|
2735
|
+
outFile: options.cssOutFile ?? "index.css"
|
|
2736
|
+
});
|
|
2737
|
+
for (const name of readdirSync9(dist)) {
|
|
2738
|
+
const dir = join13(dist, name);
|
|
2739
|
+
if (!statSync8(dir).isDirectory())
|
|
2740
|
+
continue;
|
|
2741
|
+
if (keep.has(name))
|
|
2742
|
+
continue;
|
|
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;
|
|
2747
|
+
if (src) {
|
|
2748
|
+
const text = readFileSync16(src, "utf8").replace(/from ['"]\.\.\/([^'"]+)['"]/g, `from './$1'`);
|
|
2749
|
+
writeFileSync7(flat, text);
|
|
2750
|
+
console.log(`[postbuild] types \u2192 ${name}.d.ts`);
|
|
2751
|
+
}
|
|
2752
|
+
rmSync(dir, { recursive: true, force: true });
|
|
2753
|
+
console.log(`[postbuild] removed nested dir: ${name}/`);
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
|
|
2757
|
+
// src/commands/postbuild.ts
|
|
2758
|
+
async function cmdPostbuild(parsed) {
|
|
2759
|
+
const dir = parsed.options.dir || "./dist";
|
|
2760
|
+
const distDir = resolve3(process.cwd(), dir);
|
|
2761
|
+
postbuildLib({ distDir });
|
|
2762
|
+
console.log(`[postbuild] \u0433\u043E\u0442\u043E\u0432\u043E: ${distDir}`);
|
|
2763
|
+
}
|
|
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
|
+
|
|
874
3194
|
// src/index.ts
|
|
3195
|
+
function cliVersion() {
|
|
3196
|
+
const here = dirname4(fileURLToPath2(import.meta.url));
|
|
3197
|
+
for (const p of [
|
|
3198
|
+
join18(here, "..", "package.json"),
|
|
3199
|
+
join18(here, "package.json")
|
|
3200
|
+
]) {
|
|
3201
|
+
try {
|
|
3202
|
+
const v = JSON.parse(readFileSync19(p, "utf8")).version;
|
|
3203
|
+
if (typeof v === "string" && v)
|
|
3204
|
+
return v;
|
|
3205
|
+
} catch {}
|
|
3206
|
+
}
|
|
3207
|
+
return "0.0.0";
|
|
3208
|
+
}
|
|
3209
|
+
function showVersion() {
|
|
3210
|
+
console.log(`elcrm ${cliVersion()}`);
|
|
3211
|
+
}
|
|
875
3212
|
function showHelp() {
|
|
876
3213
|
console.log(`elcrm \u2014 CLI \u0434\u043B\u044F \u044D\u043A\u043E\u0441\u0438\u0441\u0442\u0435\u043C\u044B @elcrm/*
|
|
877
3214
|
|
|
@@ -879,8 +3216,9 @@ function showHelp() {
|
|
|
879
3216
|
elcrm <\u043A\u043E\u043C\u0430\u043D\u0434\u0430> [\u0430\u0440\u0433\u0443\u043C\u0435\u043D\u0442\u044B] [\u0444\u043B\u0430\u0433\u0438]
|
|
880
3217
|
|
|
881
3218
|
\u041A\u043E\u043C\u0430\u043D\u0434\u044B:
|
|
882
|
-
update [--dry] [--core] [--no-install]
|
|
883
|
-
\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
|
|
884
3222
|
|
|
885
3223
|
create <name> [--template app|lib|panel] [--no-install]
|
|
886
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)
|
|
@@ -891,33 +3229,80 @@ function showHelp() {
|
|
|
891
3229
|
|
|
892
3230
|
migrate <name> [--dry]
|
|
893
3231
|
migrate --list
|
|
894
|
-
\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
|
|
895
3237
|
|
|
896
3238
|
doctor [--fix]
|
|
897
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
|
|
898
3240
|
|
|
3241
|
+
test [dir] [--front|--server] [--no-scripts]
|
|
3242
|
+
\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0438: @elcrm/server \u0432 package.json \u2192 server, \u0438\u043D\u0430\u0447\u0435 \u2192 front
|
|
3243
|
+
(workspaces \u2014 \u043A\u0430\u0436\u0434\u044B\u0439 \u043F\u0430\u043A\u0435\u0442 \u0441\u0432\u043E\u0438\u043C \u0440\u0435\u0436\u0438\u043C\u043E\u043C)
|
|
3244
|
+
|
|
899
3245
|
build [--client] [--url=\u2026] [--name=\u2026] [--dir=\u2026] [--folder=\u2026]
|
|
900
3246
|
\u0421\u0431\u043E\u0440\u043A\u0430 \u0438 \u0434\u0435\u043F\u043B\u043E\u0439 (\u0438\u0437 @elcrm/deploy)
|
|
901
3247
|
|
|
3248
|
+
postbuild [--dir=./dist]
|
|
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
|
|
3250
|
+
(package.json: "build": "vite build && elcrm postbuild")
|
|
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
|
+
|
|
902
3264
|
uuid
|
|
903
3265
|
\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C UUID
|
|
904
3266
|
|
|
905
3267
|
help
|
|
906
3268
|
\u042D\u0442\u0430 \u0441\u043F\u0440\u0430\u0432\u043A\u0430
|
|
907
3269
|
|
|
3270
|
+
\u0424\u043B\u0430\u0433\u0438:
|
|
3271
|
+
-v, --version \u0412\u0435\u0440\u0441\u0438\u044F CLI
|
|
3272
|
+
-h, --help \u0421\u043F\u0440\u0430\u0432\u043A\u0430
|
|
3273
|
+
|
|
908
3274
|
\u041F\u0440\u0438\u043C\u0435\u0440\u044B:
|
|
3275
|
+
elcrm -v
|
|
909
3276
|
elcrm update
|
|
910
3277
|
elcrm update --dry
|
|
3278
|
+
elcrm update --fix --test
|
|
3279
|
+
elcrm migrate form-aliases --dry
|
|
3280
|
+
elcrm migrate form-tokens --dry
|
|
911
3281
|
elcrm create my-app --template app
|
|
912
3282
|
elcrm init --template panel
|
|
913
3283
|
elcrm init --template lib
|
|
914
|
-
elcrm migrate
|
|
3284
|
+
elcrm migrate front --dry
|
|
3285
|
+
elcrm migrate size-sml
|
|
3286
|
+
elcrm audit
|
|
3287
|
+
elcrm audit --fix
|
|
915
3288
|
elcrm doctor --fix
|
|
3289
|
+
elcrm test
|
|
3290
|
+
elcrm test --server
|
|
916
3291
|
elcrm build --client --url=https://example.com/deploy
|
|
3292
|
+
elcrm postbuild
|
|
3293
|
+
elcrm css
|
|
3294
|
+
elcrm css --dry
|
|
3295
|
+
elcrm docs
|
|
3296
|
+
elcrm cursor
|
|
3297
|
+
elcrm cursor --lib
|
|
917
3298
|
`);
|
|
918
3299
|
}
|
|
919
3300
|
async function main() {
|
|
920
3301
|
const parsed = parseArgs();
|
|
3302
|
+
if (hasFlag(parsed, "version", "v") || parsed.command === "version") {
|
|
3303
|
+
showVersion();
|
|
3304
|
+
return;
|
|
3305
|
+
}
|
|
921
3306
|
if (hasFlag(parsed, "help", "h") || parsed.command === "help" || !parsed.command) {
|
|
922
3307
|
showHelp();
|
|
923
3308
|
return;
|
|
@@ -941,9 +3326,27 @@ async function main() {
|
|
|
941
3326
|
case "doctor":
|
|
942
3327
|
await cmdDoctor(parsed);
|
|
943
3328
|
break;
|
|
3329
|
+
case "test":
|
|
3330
|
+
await cmdTest(parsed);
|
|
3331
|
+
break;
|
|
944
3332
|
case "build":
|
|
945
3333
|
await cmdBuild(parsed);
|
|
946
3334
|
break;
|
|
3335
|
+
case "postbuild":
|
|
3336
|
+
await cmdPostbuild(parsed);
|
|
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;
|
|
947
3350
|
case "uuid":
|
|
948
3351
|
await cmdUuid();
|
|
949
3352
|
break;
|