@vobs/cli 1.7.8 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/banner.cjs +22 -3
- package/dist/banner.cjs.map +1 -1
- package/dist/banner.d.cts +6 -2
- package/dist/banner.d.ts +6 -2
- package/dist/banner.js +20 -3
- package/dist/banner.js.map +1 -1
- package/dist/cli.cjs +361 -21
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +10 -1
- package/dist/cli.d.ts +10 -1
- package/dist/cli.js +351 -12
- package/dist/cli.js.map +1 -1
- package/dist/commands/add.cjs +2 -2
- package/dist/commands/add.cjs.map +1 -1
- package/dist/commands/add.js +2 -2
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/build.cjs +2 -2
- package/dist/commands/build.cjs.map +1 -1
- package/dist/commands/build.js +2 -2
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/check.cjs +382 -0
- package/dist/commands/check.cjs.map +1 -0
- package/dist/commands/check.d.cts +34 -0
- package/dist/commands/check.d.ts +34 -0
- package/dist/commands/check.js +343 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/dev.cjs +2 -2
- package/dist/commands/dev.cjs.map +1 -1
- package/dist/commands/dev.js +2 -2
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/dsh.cjs +2 -2
- package/dist/commands/dsh.cjs.map +1 -1
- package/dist/commands/dsh.js +2 -2
- package/dist/commands/dsh.js.map +1 -1
- package/dist/commands/generate.cjs +2 -2
- package/dist/commands/generate.cjs.map +1 -1
- package/dist/commands/generate.js +2 -2
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/index.cjs +34 -17
- package/dist/commands/index.cjs.map +1 -1
- package/dist/commands/index.js +25 -8
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/init.cjs +20 -3
- package/dist/commands/init.cjs.map +1 -1
- package/dist/commands/init.js +20 -3
- package/dist/commands/init.js.map +1 -1
- package/dist/index.cjs +399 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +388 -14
- package/dist/index.js.map +1 -1
- package/dist/repl.cjs +34 -17
- package/dist/repl.cjs.map +1 -1
- package/dist/repl.js +25 -8
- package/dist/repl.js.map +1 -1
- package/dist/start.cjs +399 -25
- package/dist/start.cjs.map +1 -1
- package/dist/start.d.cts +8 -1
- package/dist/start.d.ts +8 -1
- package/dist/start.js +388 -14
- package/dist/start.js.map +1 -1
- package/dist/utils/logger.cjs +2 -2
- package/dist/utils/logger.cjs.map +1 -1
- package/dist/utils/logger.js +2 -2
- package/dist/utils/logger.js.map +1 -1
- package/dist/version.cjs +47 -0
- package/dist/version.cjs.map +1 -0
- package/dist/version.d.cts +3 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.js +23 -0
- package/dist/version.js.map +1 -0
- package/package.json +3 -2
- package/src/banner.ts +11 -5
- package/src/cli-process.test.ts +139 -0
- package/src/cli.ts +30 -2
- package/src/commands/check.test.ts +238 -0
- package/src/commands/check.ts +461 -0
- package/src/commands/init.ts +2 -1
- package/src/start.ts +66 -5
- package/src/utils/logger.ts +14 -2
- package/src/version.ts +27 -0
package/dist/cli.js
CHANGED
|
@@ -16,7 +16,7 @@ function timestamp() {
|
|
|
16
16
|
}
|
|
17
17
|
__name(timestamp, "timestamp");
|
|
18
18
|
function info(msg) {
|
|
19
|
-
console.
|
|
19
|
+
console.error(`${timestamp()} ${pc.cyan("\u2139")} ${msg}`);
|
|
20
20
|
}
|
|
21
21
|
__name(info, "info");
|
|
22
22
|
function success(msg) {
|
|
@@ -24,7 +24,7 @@ function success(msg) {
|
|
|
24
24
|
}
|
|
25
25
|
__name(success, "success");
|
|
26
26
|
function warn(msg) {
|
|
27
|
-
console.
|
|
27
|
+
console.error(`${timestamp()} ${pc.yellow("\u26A0")} ${msg}`);
|
|
28
28
|
}
|
|
29
29
|
__name(warn, "warn");
|
|
30
30
|
function error(msg) {
|
|
@@ -128,6 +128,23 @@ function addPackage(targetDir, pm, pkg, dev) {
|
|
|
128
128
|
}
|
|
129
129
|
__name(addPackage, "addPackage");
|
|
130
130
|
|
|
131
|
+
// packages/cli/src/version.ts
|
|
132
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
133
|
+
var cached;
|
|
134
|
+
function cliVersion() {
|
|
135
|
+
if (cached !== void 0) return cached;
|
|
136
|
+
try {
|
|
137
|
+
const manifest = JSON.parse(
|
|
138
|
+
readFileSync2(new URL("../package.json", import.meta.url), "utf8")
|
|
139
|
+
);
|
|
140
|
+
cached = manifest.version ?? "0.0.0";
|
|
141
|
+
} catch {
|
|
142
|
+
cached = "0.0.0";
|
|
143
|
+
}
|
|
144
|
+
return cached;
|
|
145
|
+
}
|
|
146
|
+
__name(cliVersion, "cliVersion");
|
|
147
|
+
|
|
131
148
|
// packages/cli/src/commands/init.ts
|
|
132
149
|
async function initCommand(options) {
|
|
133
150
|
const projectName = options.name || await p.text({
|
|
@@ -173,7 +190,7 @@ async function initCommand(options) {
|
|
|
173
190
|
name: projectName,
|
|
174
191
|
packageName: projectName.toLowerCase().replace(/\s+/g, "-"),
|
|
175
192
|
typescript: useTypescript,
|
|
176
|
-
vobsVersion:
|
|
193
|
+
vobsVersion: cliVersion()
|
|
177
194
|
});
|
|
178
195
|
logger.success("Project scaffolded");
|
|
179
196
|
logger.step("Installing dependencies...");
|
|
@@ -325,7 +342,7 @@ async function addCommand(options) {
|
|
|
325
342
|
__name(addCommand, "addCommand");
|
|
326
343
|
|
|
327
344
|
// packages/cli/src/commands/dsh.ts
|
|
328
|
-
import { existsSync as existsSync3, readFileSync as
|
|
345
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
329
346
|
import { readFile } from "fs/promises";
|
|
330
347
|
import { execSync as execSync3, spawnSync } from "child_process";
|
|
331
348
|
import { createServer as createServer2 } from "http";
|
|
@@ -336,7 +353,7 @@ var PLATFORM_MODULES = ["react", "react-dom"];
|
|
|
336
353
|
var vobsVersion = /* @__PURE__ */ __name(() => {
|
|
337
354
|
try {
|
|
338
355
|
const manifest = JSON.parse(
|
|
339
|
-
|
|
356
|
+
readFileSync3(new URL("../../package.json", import.meta.url), "utf8")
|
|
340
357
|
);
|
|
341
358
|
return manifest.version ?? "1.7.5";
|
|
342
359
|
} catch {
|
|
@@ -634,7 +651,7 @@ ${buildError ?? ""}`);
|
|
|
634
651
|
__name(dshDevCommand, "dshDevCommand");
|
|
635
652
|
function readManifest(dir) {
|
|
636
653
|
try {
|
|
637
|
-
return JSON.parse(
|
|
654
|
+
return JSON.parse(readFileSync3(resolve5(dir, "package.json"), "utf8"));
|
|
638
655
|
} catch {
|
|
639
656
|
return void 0;
|
|
640
657
|
}
|
|
@@ -660,7 +677,7 @@ function collectIssues(dir) {
|
|
|
660
677
|
if (!existsSync3(patchPath)) {
|
|
661
678
|
result.errors.push(`dsh.bundle.patch \u6307\u5411\u7684 ${bundle.patch} \u4E0D\u5B58\u5728`);
|
|
662
679
|
} else {
|
|
663
|
-
const text5 =
|
|
680
|
+
const text5 = readFileSync3(patchPath, "utf8");
|
|
664
681
|
if (!text5.includes("insert:")) result.errors.push("cordis.patch.yml \u91CC\u6CA1\u6709 insert: \u6BB5\u843D\uFF0C\u63D2\u4EF6\u884C\u4E0D\u4F1A\u8FDB\u7EC4\u5408\u6811");
|
|
665
682
|
else if (name !== "" && !text5.includes(name)) {
|
|
666
683
|
result.warnings.push(`cordis.patch.yml \u91CC\u6CA1\u6709\u51FA\u73B0\u5305\u540D ${name}\uFF0C\u786E\u8BA4 insert \u7684 name \u5199\u5BF9\u4E86`);
|
|
@@ -695,7 +712,7 @@ function collectIssues(dir) {
|
|
|
695
712
|
if (!existsSync3(clientPath)) {
|
|
696
713
|
result.errors.push(`\u5BA2\u6237\u7AEF\u4EA7\u7269 ${clientEntry} \u4E0D\u5B58\u5728\uFF0C\u5148\u8FD0\u884C\u6784\u5EFA`);
|
|
697
714
|
} else {
|
|
698
|
-
const code =
|
|
715
|
+
const code = readFileSync3(clientPath, "utf8");
|
|
699
716
|
const loads = (code.match(/__ModuleLoader__\.load\(/gu) ?? []).length;
|
|
700
717
|
if (loads !== 1) result.errors.push(`\u5BA2\u6237\u7AEF\u4EA7\u7269\u91CC __ModuleLoader__.load \u51FA\u73B0 ${loads} \u6B21\uFF0C\u5E94\u4E3A 1 \u6B21\uFF08\u5148\u6784\u5EFA\uFF09`);
|
|
701
718
|
else result.passed.push("\u4EA7\u7269\u5F62\u6001\u662F DSH \u5BA2\u6237\u7AEF\u6A21\u5757\u534F\u8BAE");
|
|
@@ -853,8 +870,309 @@ async function dshCommand(action, target, options) {
|
|
|
853
870
|
}
|
|
854
871
|
__name(dshCommand, "dshCommand");
|
|
855
872
|
|
|
873
|
+
// packages/cli/src/commands/check.ts
|
|
874
|
+
import { mkdir, readdir, readFile as readFile2, stat, writeFile as writeFile2 } from "fs/promises";
|
|
875
|
+
import path from "path";
|
|
876
|
+
import ts from "typescript";
|
|
877
|
+
var REPORT_PATH = ".vobs/check.json";
|
|
878
|
+
var TEST_FILE = /(?:^|\/)(?:[^/]*\.(?:test|spec)\.tsx?|__tests__\/)/u;
|
|
879
|
+
var VOBS_C210 = "VOBS_C210";
|
|
880
|
+
var VOBS_C232 = "VOBS_C232";
|
|
881
|
+
var VOBS_C118 = "VOBS_C118";
|
|
882
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "lib", "build", ".git", ".vobs", "coverage"]);
|
|
883
|
+
async function collectSources(root) {
|
|
884
|
+
let rootStat;
|
|
885
|
+
try {
|
|
886
|
+
rootStat = await stat(root);
|
|
887
|
+
} catch {
|
|
888
|
+
throw new Error(`\u8DEF\u5F84\u4E0D\u5B58\u5728\uFF1A${root}`);
|
|
889
|
+
}
|
|
890
|
+
if (!rootStat.isDirectory()) {
|
|
891
|
+
throw new Error(`\u4E0D\u662F\u76EE\u5F55\uFF1A${root}\uFF08vobs check \u63A5\u53D7\u4E00\u4E2A\u76EE\u5F55\uFF09`);
|
|
892
|
+
}
|
|
893
|
+
const found = [];
|
|
894
|
+
const walk = /* @__PURE__ */ __name(async (dir) => {
|
|
895
|
+
let entries;
|
|
896
|
+
try {
|
|
897
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
898
|
+
} catch (error2) {
|
|
899
|
+
if (dir === root) {
|
|
900
|
+
throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55\uFF1A${root}\uFF08${error2 instanceof Error ? error2.message : String(error2)}\uFF09`);
|
|
901
|
+
}
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
for (const entry of entries) {
|
|
905
|
+
if (entry.isDirectory()) {
|
|
906
|
+
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
907
|
+
await walk(path.join(dir, entry.name));
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
if (!entry.isFile()) continue;
|
|
911
|
+
if (!/\.tsx?$/u.test(entry.name) || entry.name.endsWith(".d.ts")) continue;
|
|
912
|
+
found.push(path.join(dir, entry.name));
|
|
913
|
+
}
|
|
914
|
+
}, "walk");
|
|
915
|
+
await walk(root);
|
|
916
|
+
return found.sort();
|
|
917
|
+
}
|
|
918
|
+
__name(collectSources, "collectSources");
|
|
919
|
+
function returnsJsx(node) {
|
|
920
|
+
let seen = false;
|
|
921
|
+
const visit = /* @__PURE__ */ __name((child) => {
|
|
922
|
+
if (seen) return;
|
|
923
|
+
if (ts.isJsxElement(child) || ts.isJsxSelfClosingElement(child) || ts.isJsxFragment(child)) {
|
|
924
|
+
seen = true;
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
if (child !== node && ts.isFunctionLike(child)) return;
|
|
928
|
+
ts.forEachChild(child, visit);
|
|
929
|
+
}, "visit");
|
|
930
|
+
ts.forEachChild(node, visit);
|
|
931
|
+
return seen;
|
|
932
|
+
}
|
|
933
|
+
__name(returnsJsx, "returnsJsx");
|
|
934
|
+
function signalNameOf(node) {
|
|
935
|
+
if (!ts.isPropertyAccessExpression(node)) return void 0;
|
|
936
|
+
if (node.name.text !== "value") return void 0;
|
|
937
|
+
return node.expression.getText();
|
|
938
|
+
}
|
|
939
|
+
__name(signalNameOf, "signalNameOf");
|
|
940
|
+
function lineSnippet(source, node) {
|
|
941
|
+
const { line } = source.getLineAndCharacterOfPosition(node.getStart(source));
|
|
942
|
+
return (source.text.split(/\r?\n/u)[line] ?? "").trim();
|
|
943
|
+
}
|
|
944
|
+
__name(lineSnippet, "lineSnippet");
|
|
945
|
+
function diagnosticAt(source, file, node, rest) {
|
|
946
|
+
const { line, character } = source.getLineAndCharacterOfPosition(node.getStart(source));
|
|
947
|
+
return {
|
|
948
|
+
...rest,
|
|
949
|
+
file,
|
|
950
|
+
line: line + 1,
|
|
951
|
+
column: character + 1,
|
|
952
|
+
snippet: lineSnippet(source, node)
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
__name(diagnosticAt, "diagnosticAt");
|
|
956
|
+
var isWriteOperator = /* @__PURE__ */ __name((kind) => kind === ts.SyntaxKind.EqualsToken || kind === ts.SyntaxKind.PlusEqualsToken || kind === ts.SyntaxKind.MinusEqualsToken || kind === ts.SyntaxKind.AsteriskEqualsToken || kind === ts.SyntaxKind.SlashEqualsToken, "isWriteOperator");
|
|
957
|
+
function ruleEffectSelfSubscription(source, file) {
|
|
958
|
+
const found = [];
|
|
959
|
+
const inspectEffectBody = /* @__PURE__ */ __name((body) => {
|
|
960
|
+
const reads = /* @__PURE__ */ new Map();
|
|
961
|
+
const writes = /* @__PURE__ */ new Map();
|
|
962
|
+
const visit2 = /* @__PURE__ */ __name((node, insideUntrack) => {
|
|
963
|
+
if (node !== body && ts.isFunctionLike(node)) return;
|
|
964
|
+
const nextUntracked = insideUntrack || ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "untrack";
|
|
965
|
+
if (ts.isBinaryExpression(node) && isWriteOperator(node.operatorToken.kind)) {
|
|
966
|
+
const name = signalNameOf(node.left);
|
|
967
|
+
if (name !== void 0 && !nextUntracked) writes.set(name, node);
|
|
968
|
+
} else if ((ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) && (node.operator === ts.SyntaxKind.PlusPlusToken || node.operator === ts.SyntaxKind.MinusMinusToken)) {
|
|
969
|
+
const name = signalNameOf(node.operand);
|
|
970
|
+
if (name !== void 0) {
|
|
971
|
+
if (!nextUntracked) writes.set(name, node);
|
|
972
|
+
reads.set(name, node);
|
|
973
|
+
}
|
|
974
|
+
} else if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "set") {
|
|
975
|
+
if (!nextUntracked) writes.set(node.expression.expression.getText(), node);
|
|
976
|
+
} else {
|
|
977
|
+
const name = signalNameOf(node);
|
|
978
|
+
if (name !== void 0) {
|
|
979
|
+
const parent = node.parent;
|
|
980
|
+
const isAssignmentTarget = ts.isBinaryExpression(parent) && isWriteOperator(parent.operatorToken.kind) && parent.left === node;
|
|
981
|
+
if (!isAssignmentTarget) reads.set(name, node);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
ts.forEachChild(node, (child) => visit2(child, nextUntracked));
|
|
985
|
+
}, "visit");
|
|
986
|
+
visit2(body, false);
|
|
987
|
+
for (const [name, writeNode] of writes) {
|
|
988
|
+
if (!reads.has(name)) continue;
|
|
989
|
+
found.push(diagnosticAt(source, file, writeNode, {
|
|
990
|
+
code: VOBS_C210,
|
|
991
|
+
severity: "error",
|
|
992
|
+
message: `effect \u5199\u5165\u4E86\u5B83\u81EA\u5DF1\u4F9D\u8D56\u7684\u4FE1\u53F7 "${name}" \u2014\u2014 \u8FD9\u6B21\u5199\u5165\u4F1A\u628A\u5B83\u91CD\u65B0\u8C03\u5EA6\uFF0C\u5F62\u6210\u81EA\u8BA2\u9605\u5FAA\u73AF`,
|
|
993
|
+
fix: `\u628A\u8FD9\u6B21\u5199\u5165\u5305\u8FDB untrack\uFF1Auntrack(() => { ${name}.value = next })\uFF1B\u5982\u679C\u8FD9\u4E2A effect \u672C\u6765\u5C31\u53EA\u8BE5\u505A\u526F\u4F5C\u7528\uFF0C\u68C0\u67E5\u662F\u4E0D\u662F\u8BEF\u8BFB\u4E86\u4E0D\u8BE5\u8BFB\u7684\u4FE1\u53F7\u3002`
|
|
994
|
+
}));
|
|
995
|
+
}
|
|
996
|
+
}, "inspectEffectBody");
|
|
997
|
+
const visit = /* @__PURE__ */ __name((node) => {
|
|
998
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && (node.expression.text === "effect" || node.expression.text === "renderEffect")) {
|
|
999
|
+
const callback = node.arguments[0];
|
|
1000
|
+
if (callback !== void 0 && (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback))) {
|
|
1001
|
+
inspectEffectBody(callback.body);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
ts.forEachChild(node, visit);
|
|
1005
|
+
}, "visit");
|
|
1006
|
+
visit(source);
|
|
1007
|
+
return found;
|
|
1008
|
+
}
|
|
1009
|
+
__name(ruleEffectSelfSubscription, "ruleEffectSelfSubscription");
|
|
1010
|
+
function isKeyedListCall(node) {
|
|
1011
|
+
if (!ts.isCallExpression(node)) return false;
|
|
1012
|
+
if (!ts.isPropertyAccessExpression(node.expression) || node.expression.name.text !== "map") return false;
|
|
1013
|
+
const callback = node.arguments[0];
|
|
1014
|
+
return callback !== void 0 && ts.isFunctionLike(callback) && returnsJsx(callback);
|
|
1015
|
+
}
|
|
1016
|
+
__name(isKeyedListCall, "isKeyedListCall");
|
|
1017
|
+
function ruleListInBranch(source, file) {
|
|
1018
|
+
const found = [];
|
|
1019
|
+
const message = "list \u5199\u5728\u4E09\u5143/&& \u7684\u5206\u652F\u91CC \u2014\u2014 \u4F1A\u8D70\u591A\u6001\u63D2\u5165\uFF0C\u5931\u53BB keyed \u590D\u7528";
|
|
1020
|
+
const fix = "\u628A list \u63D0\u6210**\u76F4\u63A5\u7684**\u5B50\u8868\u8FBE\u5F0F\uFF1A\u5148\u5199\u6761\u4EF6\u5206\u652F\uFF0C\u518D\u5355\u72EC\u5199 {items.map(...)}\u3002\u4E09\u5143\u91CC\u540C\u65F6\u6709\u8282\u70B9\u4E0E list \u65F6\uFF0Clist \u90A3\u4E00\u652F\u4E0D\u4F1A\u7F16\u8BD1\u6210 insertList\u3002";
|
|
1021
|
+
const visit = /* @__PURE__ */ __name((node) => {
|
|
1022
|
+
if (ts.isConditionalExpression(node)) {
|
|
1023
|
+
for (const branch of [node.whenTrue, node.whenFalse]) {
|
|
1024
|
+
if (isKeyedListCall(branch)) {
|
|
1025
|
+
found.push(diagnosticAt(source, file, branch, { code: VOBS_C232, severity: "warning", message, fix }));
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
} else if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken && isKeyedListCall(node.right)) {
|
|
1029
|
+
found.push(diagnosticAt(source, file, node.right, { code: VOBS_C232, severity: "warning", message, fix }));
|
|
1030
|
+
}
|
|
1031
|
+
ts.forEachChild(node, visit);
|
|
1032
|
+
}, "visit");
|
|
1033
|
+
visit(source);
|
|
1034
|
+
return found;
|
|
1035
|
+
}
|
|
1036
|
+
__name(ruleListInBranch, "ruleListInBranch");
|
|
1037
|
+
function containsSignalRead(node) {
|
|
1038
|
+
let seen = false;
|
|
1039
|
+
const visit = /* @__PURE__ */ __name((child) => {
|
|
1040
|
+
if (seen) return;
|
|
1041
|
+
if (ts.isFunctionLike(child)) return;
|
|
1042
|
+
if (signalNameOf(child) !== void 0) {
|
|
1043
|
+
seen = true;
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
ts.forEachChild(child, visit);
|
|
1047
|
+
}, "visit");
|
|
1048
|
+
visit(node);
|
|
1049
|
+
return seen;
|
|
1050
|
+
}
|
|
1051
|
+
__name(containsSignalRead, "containsSignalRead");
|
|
1052
|
+
function ruleSignalCapturedInBody(source, file) {
|
|
1053
|
+
const found = [];
|
|
1054
|
+
const inspectComponent = /* @__PURE__ */ __name((fn) => {
|
|
1055
|
+
const body = fn.body;
|
|
1056
|
+
if (body === void 0 || !ts.isBlock(body) || !returnsJsx(fn)) return;
|
|
1057
|
+
const captured = [];
|
|
1058
|
+
for (const statement of body.statements) {
|
|
1059
|
+
if (!ts.isVariableStatement(statement)) continue;
|
|
1060
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
1061
|
+
if (!ts.isIdentifier(declaration.name) || declaration.initializer === void 0) continue;
|
|
1062
|
+
if (containsSignalRead(declaration.initializer)) {
|
|
1063
|
+
captured.push({ name: declaration.name.text, node: declaration });
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
if (captured.length === 0) return;
|
|
1068
|
+
const usedInJsx = /* @__PURE__ */ new Set();
|
|
1069
|
+
const visit2 = /* @__PURE__ */ __name((node) => {
|
|
1070
|
+
if (ts.isJsxExpression(node) && node.expression !== void 0) {
|
|
1071
|
+
const scan = /* @__PURE__ */ __name((child) => {
|
|
1072
|
+
if (ts.isIdentifier(child)) usedInJsx.add(child.text);
|
|
1073
|
+
ts.forEachChild(child, scan);
|
|
1074
|
+
}, "scan");
|
|
1075
|
+
scan(node.expression);
|
|
1076
|
+
}
|
|
1077
|
+
ts.forEachChild(node, visit2);
|
|
1078
|
+
}, "visit");
|
|
1079
|
+
visit2(body);
|
|
1080
|
+
for (const item of captured) {
|
|
1081
|
+
if (!usedInJsx.has(item.name)) continue;
|
|
1082
|
+
found.push(diagnosticAt(source, file, item.node, {
|
|
1083
|
+
code: VOBS_C118,
|
|
1084
|
+
severity: "warning",
|
|
1085
|
+
message: `\u7EC4\u4EF6\u4F53\u91CC\u8BFB\u4E86\u4FE1\u53F7\u5E76\u5B58\u8FDB "${item.name}" \u2014\u2014 \u7EC4\u4EF6\u4F53\u53EA\u6267\u884C\u4E00\u6B21\uFF0C\u8FD9\u4E2A\u503C\u4E4B\u540E\u6C38\u8FDC\u4E0D\u4F1A\u66F4\u65B0`,
|
|
1086
|
+
fix: `\u628A\u8BFB\u53D6\u653E\u8FDB\u52A8\u6001\u8868\u8FBE\u5F0F\uFF08\u76F4\u63A5\u5728 JSX \u91CC\u7528 .value\uFF09\uFF0C\u6216\u6539\u6210 memo \u6D3E\u751F\u503C\uFF1Aconst ${item.name} = memo(() => ...)\u3002`
|
|
1087
|
+
}));
|
|
1088
|
+
}
|
|
1089
|
+
}, "inspectComponent");
|
|
1090
|
+
const visit = /* @__PURE__ */ __name((node) => {
|
|
1091
|
+
if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node)) {
|
|
1092
|
+
inspectComponent(node);
|
|
1093
|
+
}
|
|
1094
|
+
ts.forEachChild(node, visit);
|
|
1095
|
+
}, "visit");
|
|
1096
|
+
visit(source);
|
|
1097
|
+
return found;
|
|
1098
|
+
}
|
|
1099
|
+
__name(ruleSignalCapturedInBody, "ruleSignalCapturedInBody");
|
|
1100
|
+
var IGNORE_NEXT_LINE = /vobs-check-ignore-next-line/u;
|
|
1101
|
+
function suppressedLines(text5) {
|
|
1102
|
+
const suppressed = /* @__PURE__ */ new Set();
|
|
1103
|
+
const lines = text5.split(/\r?\n/u);
|
|
1104
|
+
for (let index = 0; index < lines.length; index++) {
|
|
1105
|
+
if (IGNORE_NEXT_LINE.test(lines[index])) suppressed.add(index + 2);
|
|
1106
|
+
}
|
|
1107
|
+
return suppressed;
|
|
1108
|
+
}
|
|
1109
|
+
__name(suppressedLines, "suppressedLines");
|
|
1110
|
+
function analyzeSource(text5, file) {
|
|
1111
|
+
const source = ts.createSourceFile(file, text5, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
|
|
1112
|
+
const suppressed = suppressedLines(text5);
|
|
1113
|
+
return [
|
|
1114
|
+
...ruleEffectSelfSubscription(source, file),
|
|
1115
|
+
...ruleListInBranch(source, file),
|
|
1116
|
+
...ruleSignalCapturedInBody(source, file)
|
|
1117
|
+
].filter((item) => !suppressed.has(item.line)).sort((a, b) => a.line - b.line || a.column - b.column);
|
|
1118
|
+
}
|
|
1119
|
+
__name(analyzeSource, "analyzeSource");
|
|
1120
|
+
async function checkCommand(options = {}) {
|
|
1121
|
+
const root = path.resolve(options.dir ?? process.cwd());
|
|
1122
|
+
let all;
|
|
1123
|
+
try {
|
|
1124
|
+
all = await collectSources(root);
|
|
1125
|
+
} catch (error2) {
|
|
1126
|
+
logger.error(error2 instanceof Error ? error2.message : String(error2));
|
|
1127
|
+
process.exitCode = 1;
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
const files = options.includeTests === true ? all : all.filter((file) => !TEST_FILE.test(path.relative(root, file).split(path.sep).join("/")));
|
|
1131
|
+
const skipped = all.length - files.length;
|
|
1132
|
+
const diagnostics = [];
|
|
1133
|
+
for (const absolute of files) {
|
|
1134
|
+
const text5 = await readFile2(absolute, "utf8");
|
|
1135
|
+
const relative2 = path.relative(root, absolute).split(path.sep).join("/");
|
|
1136
|
+
diagnostics.push(...analyzeSource(text5, relative2));
|
|
1137
|
+
}
|
|
1138
|
+
const errors = diagnostics.filter((item) => item.severity === "error");
|
|
1139
|
+
const report = { root, files: files.length, skippedTests: skipped, diagnostics };
|
|
1140
|
+
if (options.write === true) {
|
|
1141
|
+
const target = path.join(root, REPORT_PATH);
|
|
1142
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
1143
|
+
await writeFile2(target, `${JSON.stringify(report, null, 2)}
|
|
1144
|
+
`, "utf8");
|
|
1145
|
+
logger.info(`\u62A5\u544A\u5199\u5165 ${REPORT_PATH}\uFF08\u5F00\u53D1\u53F0\u9762\u677F\u8BFB\u5B83\u663E\u793A\u300C\u9879\u76EE\u300D\u9875\uFF09`);
|
|
1146
|
+
}
|
|
1147
|
+
if (options.json === true) {
|
|
1148
|
+
console.log(JSON.stringify(report, null, 2));
|
|
1149
|
+
if (errors.length > 0) process.exitCode = 1;
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
if (diagnostics.length === 0) {
|
|
1153
|
+
logger.success(`\u68C0\u67E5\u901A\u8FC7 \u2014\u2014 ${files.length} \u4E2A\u6587\u4EF6${skipped > 0 ? `\uFF08\u8DF3\u8FC7 ${skipped} \u4E2A\u6D4B\u8BD5\u6587\u4EF6\uFF09` : ""}\uFF0C\u6CA1\u6709\u53D1\u73B0\u95EE\u9898`);
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
console.log("");
|
|
1157
|
+
for (const item of diagnostics) {
|
|
1158
|
+
const mark = item.severity === "error" ? "\u2716" : "!";
|
|
1159
|
+
console.log(` ${mark} ${item.file}:${item.line}:${item.column} ${item.code}`);
|
|
1160
|
+
console.log(` ${item.message}`);
|
|
1161
|
+
if (item.snippet !== "") console.log(` ${item.snippet}`);
|
|
1162
|
+
console.log(` \u2192 ${item.fix}`);
|
|
1163
|
+
console.log("");
|
|
1164
|
+
}
|
|
1165
|
+
const warnings = diagnostics.length - errors.length;
|
|
1166
|
+
console.log(
|
|
1167
|
+
` ${errors.length} \u4E2A\u9519\u8BEF \xB7 ${warnings} \u4E2A\u8B66\u544A \xB7 \u5171\u68C0\u67E5 ${files.length} \u4E2A\u6587\u4EF6${skipped > 0 ? `\uFF08\u8DF3\u8FC7 ${skipped} \u4E2A\u6D4B\u8BD5\u6587\u4EF6\uFF0C\u7528 --include-tests \u7EB3\u5165\uFF09` : ""}
|
|
1168
|
+
`
|
|
1169
|
+
);
|
|
1170
|
+
if (errors.length > 0) process.exitCode = 1;
|
|
1171
|
+
}
|
|
1172
|
+
__name(checkCommand, "checkCommand");
|
|
1173
|
+
|
|
856
1174
|
// packages/cli/src/banner.ts
|
|
857
|
-
var BANNER_LINE1 = "\u{1D5E9}\u{1D5E2}\u{1D5D5}\u{1D5E6} \u{1D5D6}\u{1D5DF}\u{1D5DC}
|
|
1175
|
+
var BANNER_LINE1 = "\u{1D5E9}\u{1D5E2}\u{1D5D5}\u{1D5E6} \u{1D5D6}\u{1D5DF}\u{1D5DC}";
|
|
858
1176
|
var BANNER_TAGLINE = "Signals First \xB7 Zero Re-renders \xB7 Ultra Lightweight";
|
|
859
1177
|
var INDENT = " ";
|
|
860
1178
|
var DIM = "\x1B[2m";
|
|
@@ -870,12 +1188,12 @@ function showLogo() {
|
|
|
870
1188
|
}
|
|
871
1189
|
__name(showLogo, "showLogo");
|
|
872
1190
|
function showBanner() {
|
|
873
|
-
console.log(`${INDENT}${BANNER_LINE1}`);
|
|
1191
|
+
console.log(`${INDENT}${BANNER_LINE1} v${cliVersion()}`);
|
|
874
1192
|
}
|
|
875
1193
|
__name(showBanner, "showBanner");
|
|
876
1194
|
function showDevBanner() {
|
|
877
1195
|
console.log();
|
|
878
|
-
console.log(`${INDENT}${BANNER_LINE1}`);
|
|
1196
|
+
console.log(`${INDENT}${BANNER_LINE1} v${cliVersion()}`);
|
|
879
1197
|
console.log();
|
|
880
1198
|
}
|
|
881
1199
|
__name(showDevBanner, "showDevBanner");
|
|
@@ -929,6 +1247,14 @@ function createCLI() {
|
|
|
929
1247
|
dev: options.dev
|
|
930
1248
|
});
|
|
931
1249
|
});
|
|
1250
|
+
cli.command("check [dir]", "\u9759\u6001\u68C0\u67E5\u6E90\u7801\uFF1Aeffect \u81EA\u8BA2\u9605 / \u5217\u8868\u5199\u8FDB\u5206\u652F / \u7EC4\u4EF6\u4F53\u91CC\u8BFB\u4FE1\u53F7\uFF08\u884C\u5185\u6291\u5236\uFF1A\u4E0A\u4E00\u884C\u5199 // vobs-check-ignore-next-line\uFF09").option("--json", "\u4EE5 JSON \u8F93\u51FA\uFF08\u7ED9 AI \u4E0E\u5DE5\u5177\u6D88\u8D39\uFF09").option("--write", `\u628A\u7ED3\u679C\u5199\u5230 ${".vobs/check.json"}\uFF08\u5F00\u53D1\u53F0\u9762\u677F\u8BFB\u5B83\uFF09`).option("--include-tests", "\u628A\u6D4B\u8BD5\u6587\u4EF6\u4E5F\u7EB3\u5165\u68C0\u67E5\uFF08\u9ED8\u8BA4\u8DF3\u8FC7\uFF09").action(async (dir, options) => {
|
|
1251
|
+
await checkCommand({
|
|
1252
|
+
dir,
|
|
1253
|
+
json: options.json === true,
|
|
1254
|
+
write: options.write === true,
|
|
1255
|
+
includeTests: options.includeTests === true
|
|
1256
|
+
});
|
|
1257
|
+
});
|
|
932
1258
|
cli.command("dsh [action] [target]", "DSH \u63D2\u4EF6\u5DE5\u5177\u94FE\uFF08init / dev / build / check / install\uFF09").option("--dir <dir>", "init\uFF1A\u76EE\u6807\u76EE\u5F55").option("--pm <pm>", "init\uFF1A\u5305\u7BA1\u7406\u5668\uFF08pnpm/npm\uFF09").option("--port <port>", "dev\uFF1A\u9884\u89C8\u7AEF\u53E3", { default: 5199 }).option("--profile <name>", "install\uFF1ADSH profile \u540D", { default: "desktop" }).option("--spec <spec>", "install\uFF1A\u5B8C\u6574\u5B89\u88C5 spec").option("--repo <owner/name>", "install\uFF1AGitHub owner/name").option("--tag <ref>", "install\uFF1AGit tag / \u5206\u652F / commit").option("--subpath <path>", "install\uFF1A\u4ED3\u5E93\u5185\u5B50\u76EE\u5F55").option("--from <path>", "install\uFF1A\u4ECE\u672C\u5730\u76EE\u5F55\u5B89\u88C5").option("--dry", "install\uFF1A\u53EA\u6253\u5370\u547D\u4EE4\uFF0C\u4E0D\u6267\u884C").action(async (action, target, options) => {
|
|
933
1259
|
await dshCommand(action, target, {
|
|
934
1260
|
dir: options.dir,
|
|
@@ -950,11 +1276,24 @@ function createCLI() {
|
|
|
950
1276
|
showBanner();
|
|
951
1277
|
});
|
|
952
1278
|
cli.help();
|
|
953
|
-
cli.version(
|
|
1279
|
+
cli.version(cliVersion());
|
|
954
1280
|
return cli;
|
|
955
1281
|
}
|
|
956
1282
|
__name(createCLI, "createCLI");
|
|
1283
|
+
var KNOWN_SUBCOMMANDS = [
|
|
1284
|
+
"init",
|
|
1285
|
+
"create",
|
|
1286
|
+
"dev",
|
|
1287
|
+
"build",
|
|
1288
|
+
"generate",
|
|
1289
|
+
"g",
|
|
1290
|
+
"add",
|
|
1291
|
+
"check",
|
|
1292
|
+
"dsh",
|
|
1293
|
+
"logo"
|
|
1294
|
+
];
|
|
957
1295
|
export {
|
|
1296
|
+
KNOWN_SUBCOMMANDS,
|
|
958
1297
|
createCLI
|
|
959
1298
|
};
|
|
960
1299
|
//# sourceMappingURL=cli.js.map
|