@vobs/cli 1.7.7 → 1.8.0

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.
Files changed (81) hide show
  1. package/dist/banner.cjs +22 -3
  2. package/dist/banner.cjs.map +1 -1
  3. package/dist/banner.d.cts +6 -2
  4. package/dist/banner.d.ts +6 -2
  5. package/dist/banner.js +20 -3
  6. package/dist/banner.js.map +1 -1
  7. package/dist/cli.cjs +364 -24
  8. package/dist/cli.cjs.map +1 -1
  9. package/dist/cli.d.cts +10 -1
  10. package/dist/cli.d.ts +10 -1
  11. package/dist/cli.js +354 -15
  12. package/dist/cli.js.map +1 -1
  13. package/dist/commands/add.cjs +2 -2
  14. package/dist/commands/add.cjs.map +1 -1
  15. package/dist/commands/add.js +2 -2
  16. package/dist/commands/add.js.map +1 -1
  17. package/dist/commands/build.cjs +2 -2
  18. package/dist/commands/build.cjs.map +1 -1
  19. package/dist/commands/build.js +2 -2
  20. package/dist/commands/build.js.map +1 -1
  21. package/dist/commands/check.cjs +382 -0
  22. package/dist/commands/check.cjs.map +1 -0
  23. package/dist/commands/check.d.cts +34 -0
  24. package/dist/commands/check.d.ts +34 -0
  25. package/dist/commands/check.js +343 -0
  26. package/dist/commands/check.js.map +1 -0
  27. package/dist/commands/dev.cjs +2 -2
  28. package/dist/commands/dev.cjs.map +1 -1
  29. package/dist/commands/dev.js +2 -2
  30. package/dist/commands/dev.js.map +1 -1
  31. package/dist/commands/dsh.cjs +4 -4
  32. package/dist/commands/dsh.cjs.map +1 -1
  33. package/dist/commands/dsh.js +4 -4
  34. package/dist/commands/dsh.js.map +1 -1
  35. package/dist/commands/generate.cjs +2 -2
  36. package/dist/commands/generate.cjs.map +1 -1
  37. package/dist/commands/generate.js +2 -2
  38. package/dist/commands/generate.js.map +1 -1
  39. package/dist/commands/index.cjs +36 -19
  40. package/dist/commands/index.cjs.map +1 -1
  41. package/dist/commands/index.js +27 -10
  42. package/dist/commands/index.js.map +1 -1
  43. package/dist/commands/init.cjs +20 -3
  44. package/dist/commands/init.cjs.map +1 -1
  45. package/dist/commands/init.js +20 -3
  46. package/dist/commands/init.js.map +1 -1
  47. package/dist/index.cjs +402 -28
  48. package/dist/index.cjs.map +1 -1
  49. package/dist/index.js +391 -17
  50. package/dist/index.js.map +1 -1
  51. package/dist/repl.cjs +34 -17
  52. package/dist/repl.cjs.map +1 -1
  53. package/dist/repl.js +25 -8
  54. package/dist/repl.js.map +1 -1
  55. package/dist/start.cjs +402 -28
  56. package/dist/start.cjs.map +1 -1
  57. package/dist/start.d.cts +8 -1
  58. package/dist/start.d.ts +8 -1
  59. package/dist/start.js +391 -17
  60. package/dist/start.js.map +1 -1
  61. package/dist/utils/logger.cjs +2 -2
  62. package/dist/utils/logger.cjs.map +1 -1
  63. package/dist/utils/logger.js +2 -2
  64. package/dist/utils/logger.js.map +1 -1
  65. package/dist/version.cjs +47 -0
  66. package/dist/version.cjs.map +1 -0
  67. package/dist/version.d.cts +3 -0
  68. package/dist/version.d.ts +3 -0
  69. package/dist/version.js +23 -0
  70. package/dist/version.js.map +1 -0
  71. package/package.json +3 -2
  72. package/src/banner.ts +11 -5
  73. package/src/cli-process.test.ts +139 -0
  74. package/src/cli.ts +31 -3
  75. package/src/commands/check.test.ts +238 -0
  76. package/src/commands/check.ts +461 -0
  77. package/src/commands/dsh.ts +4 -2
  78. package/src/commands/init.ts +2 -1
  79. package/src/start.ts +66 -5
  80. package/src/utils/logger.ts +14 -2
  81. package/src/version.ts +27 -0
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ function timestamp() {
16
16
  }
17
17
  __name(timestamp, "timestamp");
18
18
  function info(msg) {
19
- console.log(`${timestamp()} ${pc.cyan("\u2139")} ${msg}`);
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.log(`${timestamp()} ${pc.yellow("\u26A0")} ${msg}`);
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: "1.0.0"
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 readFileSync2, writeFileSync as writeFileSync2 } from "fs";
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
- readFileSync2(new URL("../../package.json", import.meta.url), "utf8")
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(readFileSync2(resolve5(dir, "package.json"), "utf8"));
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 = readFileSync2(patchPath, "utf8");
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 = readFileSync2(clientPath, "utf8");
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");
@@ -843,18 +860,319 @@ async function dshCommand(action, target, options) {
843
860
  dshInstallCommand(options);
844
861
  return;
845
862
  default:
846
- logger.error(`\u672A\u77E5\u5B50\u547D\u4EE4: ${action ?? "(\u7A7A)"}`);
863
+ if (action !== void 0 && action !== "") logger.error(`\u672A\u77E5\u5B50\u547D\u4EE4: ${action}`);
847
864
  console.log("\n \u53EF\u7528\u5B50\u547D\u4EE4\uFF1A");
848
865
  for (const [name, description] of Object.entries(ACTIONS)) {
849
866
  console.log(` vobs dsh ${name.padEnd(8)} ${description}`);
850
867
  }
851
- process.exitCode = 1;
868
+ if (action !== void 0 && action !== "") process.exitCode = 1;
852
869
  }
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} \u{1D603}\u{1D7ED}.\u{1D7EC}";
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,7 +1247,15 @@ function createCLI() {
929
1247
  dev: options.dev
930
1248
  });
931
1249
  });
932
- 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) => {
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
+ });
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,
935
1261
  pm: options.pm,
@@ -950,10 +1276,22 @@ function createCLI() {
950
1276
  showBanner();
951
1277
  });
952
1278
  cli.help();
953
- cli.version("1.0.0");
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
 
958
1296
  // packages/cli/src/repl.ts
959
1297
  import { createInterface } from "readline/promises";
@@ -1072,16 +1410,52 @@ function splitArgs(line) {
1072
1410
  __name(splitArgs, "splitArgs");
1073
1411
 
1074
1412
  // packages/cli/src/start.ts
1413
+ var META_FLAGS = /* @__PURE__ */ new Set(["--help", "-h", "--version", "-V"]);
1414
+ function isFlag(token) {
1415
+ return token.startsWith("-") && token !== "-";
1416
+ }
1417
+ __name(isFlag, "isFlag");
1075
1418
  async function start(args) {
1076
1419
  if (args.length === 0 || args[0] === "cli") {
1077
1420
  await startRepl();
1078
1421
  return;
1079
1422
  }
1080
- if (args.includes("--version") || args.includes("-V")) {
1423
+ const first = args[0];
1424
+ const hasSubcommand = KNOWN_SUBCOMMANDS.includes(first);
1425
+ if (!hasSubcommand && (args.includes("--version") || args.includes("-V"))) {
1081
1426
  showBanner();
1082
1427
  return;
1083
1428
  }
1084
- createCLI().parse();
1429
+ if (!hasSubcommand && !isFlag(first)) {
1430
+ process.stderr.write(`\u2716 \u672A\u77E5\u547D\u4EE4\uFF1A${first}
1431
+
1432
+ `);
1433
+ try {
1434
+ createCLI().parse(["node", "vobs", "--help"]);
1435
+ } catch {
1436
+ }
1437
+ process.exitCode = 1;
1438
+ return;
1439
+ }
1440
+ if (!hasSubcommand && isFlag(first) && !META_FLAGS.has(first)) {
1441
+ process.stderr.write(`\u2716 \u672A\u77E5\u9009\u9879\uFF1A${first}
1442
+
1443
+ `);
1444
+ try {
1445
+ createCLI().parse(["node", "vobs", "--help"]);
1446
+ } catch {
1447
+ }
1448
+ process.exitCode = 1;
1449
+ return;
1450
+ }
1451
+ try {
1452
+ createCLI().parse();
1453
+ } catch (error2) {
1454
+ const message = error2 instanceof Error ? error2.message : String(error2);
1455
+ process.stderr.write(`\u2716 ${message}
1456
+ `);
1457
+ process.exitCode = 1;
1458
+ }
1085
1459
  }
1086
1460
  __name(start, "start");
1087
1461
  export {