@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/start.cjs CHANGED
@@ -51,7 +51,7 @@ function timestamp() {
51
51
  }
52
52
  __name(timestamp, "timestamp");
53
53
  function info(msg) {
54
- console.log(`${timestamp()} ${import_picocolors.default.cyan("\u2139")} ${msg}`);
54
+ console.error(`${timestamp()} ${import_picocolors.default.cyan("\u2139")} ${msg}`);
55
55
  }
56
56
  __name(info, "info");
57
57
  function success(msg) {
@@ -59,7 +59,7 @@ function success(msg) {
59
59
  }
60
60
  __name(success, "success");
61
61
  function warn(msg) {
62
- console.log(`${timestamp()} ${import_picocolors.default.yellow("\u26A0")} ${msg}`);
62
+ console.error(`${timestamp()} ${import_picocolors.default.yellow("\u26A0")} ${msg}`);
63
63
  }
64
64
  __name(warn, "warn");
65
65
  function error(msg) {
@@ -163,6 +163,23 @@ function addPackage(targetDir, pm, pkg, dev) {
163
163
  }
164
164
  __name(addPackage, "addPackage");
165
165
 
166
+ // packages/cli/src/version.ts
167
+ var import_node_fs3 = require("fs");
168
+ var cached;
169
+ function cliVersion() {
170
+ if (cached !== void 0) return cached;
171
+ try {
172
+ const manifest = JSON.parse(
173
+ (0, import_node_fs3.readFileSync)(new URL("../package.json", __VOBS_CJS_FILE_URL), "utf8")
174
+ );
175
+ cached = manifest.version ?? "0.0.0";
176
+ } catch {
177
+ cached = "0.0.0";
178
+ }
179
+ return cached;
180
+ }
181
+ __name(cliVersion, "cliVersion");
182
+
166
183
  // packages/cli/src/commands/init.ts
167
184
  async function initCommand(options) {
168
185
  const projectName = options.name || await p.text({
@@ -208,7 +225,7 @@ async function initCommand(options) {
208
225
  name: projectName,
209
226
  packageName: projectName.toLowerCase().replace(/\s+/g, "-"),
210
227
  typescript: useTypescript,
211
- vobsVersion: "1.0.0"
228
+ vobsVersion: cliVersion()
212
229
  });
213
230
  logger.success("Project scaffolded");
214
231
  logger.step("Installing dependencies...");
@@ -360,7 +377,7 @@ async function addCommand(options) {
360
377
  __name(addCommand, "addCommand");
361
378
 
362
379
  // packages/cli/src/commands/dsh.ts
363
- var import_node_fs3 = require("fs");
380
+ var import_node_fs4 = require("fs");
364
381
  var import_promises = require("fs/promises");
365
382
  var import_node_child_process3 = require("child_process");
366
383
  var import_node_http = require("http");
@@ -371,7 +388,7 @@ var PLATFORM_MODULES = ["react", "react-dom"];
371
388
  var vobsVersion = /* @__PURE__ */ __name(() => {
372
389
  try {
373
390
  const manifest = JSON.parse(
374
- (0, import_node_fs3.readFileSync)(new URL("../../package.json", __VOBS_CJS_FILE_URL), "utf8")
391
+ (0, import_node_fs4.readFileSync)(new URL("../../package.json", __VOBS_CJS_FILE_URL), "utf8")
375
392
  );
376
393
  return manifest.version ?? "1.7.5";
377
394
  } catch {
@@ -413,7 +430,7 @@ async function dshInitCommand(target, options) {
413
430
  "dsh-plugin"
414
431
  );
415
432
  ensureDir(targetDir);
416
- (0, import_node_fs3.writeFileSync)(
433
+ (0, import_node_fs4.writeFileSync)(
417
434
  (0, import_node_path5.resolve)(targetDir, ".gitignore"),
418
435
  ["node_modules/", "*.log", "*.tsbuildinfo", "", "# lib/ \u662F\u6784\u5EFA\u4EA7\u7269\uFF0C\u5FC5\u987B\u63D0\u4EA4\uFF1ADSH \u5B89\u88C5\u65F6\u4E0D\u4F1A\u6784\u5EFA\u4F60\u7684\u5305\u3002", ""].join("\n"),
419
436
  "utf8"
@@ -440,7 +457,7 @@ __name(dshInitCommand, "dshInitCommand");
440
457
  async function dshBuildCommand() {
441
458
  const projectDir = process.cwd();
442
459
  const configFile = (0, import_node_path5.resolve)(projectDir, "vite.config.ts");
443
- if (!(0, import_node_fs3.existsSync)(configFile)) {
460
+ if (!(0, import_node_fs4.existsSync)(configFile)) {
444
461
  logger.error("\u5F53\u524D\u76EE\u5F55\u6CA1\u6709 vite.config.ts\uFF0C\u8BF7\u5728\u63D2\u4EF6\u9879\u76EE\u6839\u76EE\u5F55\u8FD0\u884C\u3002");
445
462
  process.exitCode = 1;
446
463
  return;
@@ -463,7 +480,7 @@ async function dshBuildCommand() {
463
480
  logger.info("\u6784\u5EFA Host \u4EA7\u7269 \u2026");
464
481
  const hostTarget = (0, import_node_path5.resolve)(projectDir, hostEntry.replace(/^\.\//u, ""));
465
482
  const hostEntrySource = (0, import_node_path5.resolve)(projectDir, "src", "host", "index.ts");
466
- if (!(0, import_node_fs3.existsSync)(hostEntrySource)) {
483
+ if (!(0, import_node_fs4.existsSync)(hostEntrySource)) {
467
484
  logger.warn(`\u6CA1\u6709\u627E\u5230 ${hostEntrySource}\uFF0C\u8DF3\u8FC7 Host \u6784\u5EFA\u3002`);
468
485
  return;
469
486
  }
@@ -576,7 +593,7 @@ async function dshDevCommand(options) {
576
593
  packageRoot = (0, import_node_path5.dirname)((0, import_node_path5.dirname)(projectRequire.resolve("@vobs/dsh")));
577
594
  }
578
595
  const candidate = (0, import_node_path5.join)(packageRoot, "dist", "preview.js");
579
- previewFile = (0, import_node_fs3.existsSync)(candidate) ? candidate : void 0;
596
+ previewFile = (0, import_node_fs4.existsSync)(candidate) ? candidate : void 0;
580
597
  } catch {
581
598
  previewFile = void 0;
582
599
  }
@@ -648,7 +665,7 @@ data: 1
648
665
  return;
649
666
  }
650
667
  if (url === "/client.js") {
651
- if (!(0, import_node_fs3.existsSync)(clientFile)) {
668
+ if (!(0, import_node_fs4.existsSync)(clientFile)) {
652
669
  response.writeHead(503, { "content-type": "text/plain; charset=utf-8" });
653
670
  response.end(`\u8FD8\u6CA1\u6709\u4EA7\u7269\uFF1A${clientEntry}
654
671
  ${buildError ?? ""}`);
@@ -669,7 +686,7 @@ ${buildError ?? ""}`);
669
686
  __name(dshDevCommand, "dshDevCommand");
670
687
  function readManifest(dir) {
671
688
  try {
672
- return JSON.parse((0, import_node_fs3.readFileSync)((0, import_node_path5.resolve)(dir, "package.json"), "utf8"));
689
+ return JSON.parse((0, import_node_fs4.readFileSync)((0, import_node_path5.resolve)(dir, "package.json"), "utf8"));
673
690
  } catch {
674
691
  return void 0;
675
692
  }
@@ -692,10 +709,10 @@ function collectIssues(dir) {
692
709
  result.errors.push("\u7F3A\u5C11 dsh.bundle.patch \u2014\u2014 DSH \u4E0D\u4F1A\u628A\u8FD9\u4E2A\u5305\u5F53\u4F5C\u7EC4\u5408\u5305\uFF08bundle\uFF09");
693
710
  } else {
694
711
  const patchPath = (0, import_node_path5.resolve)(dir, bundle.patch.replace(/^\.\//u, ""));
695
- if (!(0, import_node_fs3.existsSync)(patchPath)) {
712
+ if (!(0, import_node_fs4.existsSync)(patchPath)) {
696
713
  result.errors.push(`dsh.bundle.patch \u6307\u5411\u7684 ${bundle.patch} \u4E0D\u5B58\u5728`);
697
714
  } else {
698
- const text5 = (0, import_node_fs3.readFileSync)(patchPath, "utf8");
715
+ const text5 = (0, import_node_fs4.readFileSync)(patchPath, "utf8");
699
716
  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");
700
717
  else if (name !== "" && !text5.includes(name)) {
701
718
  result.warnings.push(`cordis.patch.yml \u91CC\u6CA1\u6709\u51FA\u73B0\u5305\u540D ${name}\uFF0C\u786E\u8BA4 insert \u7684 name \u5199\u5BF9\u4E86`);
@@ -709,7 +726,7 @@ function collectIssues(dir) {
709
726
  const hostEntry = exportsField["."];
710
727
  const clientEntry = exportsField["./client"];
711
728
  if (typeof hostEntry !== "string") result.errors.push('exports["."] \u672A\u58F0\u660E \u2014\u2014 DSH \u52A0\u8F7D\u4E0D\u5230 Host \u4FA7\u63D2\u4EF6');
712
- else if (!(0, import_node_fs3.existsSync)((0, import_node_path5.resolve)(dir, hostEntry.replace(/^\.\//u, "")))) {
729
+ else if (!(0, import_node_fs4.existsSync)((0, import_node_path5.resolve)(dir, hostEntry.replace(/^\.\//u, "")))) {
713
730
  result.errors.push(`exports["."] \u6307\u5411\u7684 ${hostEntry} \u4E0D\u5B58\u5728\uFF0C\u5148\u6784\u5EFA`);
714
731
  } else result.passed.push("Host \u5165\u53E3\u5B58\u5728");
715
732
  if (typeof clientEntry !== "string") result.errors.push('exports["./client"] \u672A\u58F0\u660E \u2014\u2014 \u9875\u9762\u62FF\u4E0D\u5230\u5BA2\u6237\u7AEF bundle');
@@ -727,10 +744,10 @@ function collectIssues(dir) {
727
744
  } else result.passed.push("\u6CA1\u6709 workspace: \u534F\u8BAE\u4F9D\u8D56");
728
745
  if (typeof clientEntry === "string") {
729
746
  const clientPath = (0, import_node_path5.resolve)(dir, clientEntry.replace(/^\.\//u, ""));
730
- if (!(0, import_node_fs3.existsSync)(clientPath)) {
747
+ if (!(0, import_node_fs4.existsSync)(clientPath)) {
731
748
  result.errors.push(`\u5BA2\u6237\u7AEF\u4EA7\u7269 ${clientEntry} \u4E0D\u5B58\u5728\uFF0C\u5148\u8FD0\u884C\u6784\u5EFA`);
732
749
  } else {
733
- const code = (0, import_node_fs3.readFileSync)(clientPath, "utf8");
750
+ const code = (0, import_node_fs4.readFileSync)(clientPath, "utf8");
734
751
  const loads = (code.match(/__ModuleLoader__\.load\(/gu) ?? []).length;
735
752
  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`);
736
753
  else result.passed.push("\u4EA7\u7269\u5F62\u6001\u662F DSH \u5BA2\u6237\u7AEF\u6A21\u5757\u534F\u8BAE");
@@ -781,7 +798,7 @@ function buildInstallSpec(options) {
781
798
  if (options.spec !== void 0) return { spec: options.spec };
782
799
  if (options.from !== void 0) {
783
800
  const target = (0, import_node_path5.resolve)(options.from);
784
- if (!(0, import_node_fs3.existsSync)(target)) return { error: `--from \u6307\u5411\u7684\u8DEF\u5F84\u4E0D\u5B58\u5728\uFF1A${target}` };
801
+ if (!(0, import_node_fs4.existsSync)(target)) return { error: `--from \u6307\u5411\u7684\u8DEF\u5F84\u4E0D\u5B58\u5728\uFF1A${target}` };
785
802
  return { spec: target };
786
803
  }
787
804
  if (options.repo !== void 0) {
@@ -878,18 +895,319 @@ async function dshCommand(action, target, options) {
878
895
  dshInstallCommand(options);
879
896
  return;
880
897
  default:
881
- logger.error(`\u672A\u77E5\u5B50\u547D\u4EE4: ${action ?? "(\u7A7A)"}`);
898
+ if (action !== void 0 && action !== "") logger.error(`\u672A\u77E5\u5B50\u547D\u4EE4: ${action}`);
882
899
  console.log("\n \u53EF\u7528\u5B50\u547D\u4EE4\uFF1A");
883
900
  for (const [name, description] of Object.entries(ACTIONS)) {
884
901
  console.log(` vobs dsh ${name.padEnd(8)} ${description}`);
885
902
  }
886
- process.exitCode = 1;
903
+ if (action !== void 0 && action !== "") process.exitCode = 1;
887
904
  }
888
905
  }
889
906
  __name(dshCommand, "dshCommand");
890
907
 
908
+ // packages/cli/src/commands/check.ts
909
+ var import_promises2 = require("fs/promises");
910
+ var import_node_path6 = __toESM(require("path"), 1);
911
+ var import_typescript = __toESM(require("typescript"), 1);
912
+ var REPORT_PATH = ".vobs/check.json";
913
+ var TEST_FILE = /(?:^|\/)(?:[^/]*\.(?:test|spec)\.tsx?|__tests__\/)/u;
914
+ var VOBS_C210 = "VOBS_C210";
915
+ var VOBS_C232 = "VOBS_C232";
916
+ var VOBS_C118 = "VOBS_C118";
917
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "lib", "build", ".git", ".vobs", "coverage"]);
918
+ async function collectSources(root) {
919
+ let rootStat;
920
+ try {
921
+ rootStat = await (0, import_promises2.stat)(root);
922
+ } catch {
923
+ throw new Error(`\u8DEF\u5F84\u4E0D\u5B58\u5728\uFF1A${root}`);
924
+ }
925
+ if (!rootStat.isDirectory()) {
926
+ throw new Error(`\u4E0D\u662F\u76EE\u5F55\uFF1A${root}\uFF08vobs check \u63A5\u53D7\u4E00\u4E2A\u76EE\u5F55\uFF09`);
927
+ }
928
+ const found = [];
929
+ const walk = /* @__PURE__ */ __name(async (dir) => {
930
+ let entries;
931
+ try {
932
+ entries = await (0, import_promises2.readdir)(dir, { withFileTypes: true });
933
+ } catch (error2) {
934
+ if (dir === root) {
935
+ throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55\uFF1A${root}\uFF08${error2 instanceof Error ? error2.message : String(error2)}\uFF09`);
936
+ }
937
+ return;
938
+ }
939
+ for (const entry of entries) {
940
+ if (entry.isDirectory()) {
941
+ if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
942
+ await walk(import_node_path6.default.join(dir, entry.name));
943
+ continue;
944
+ }
945
+ if (!entry.isFile()) continue;
946
+ if (!/\.tsx?$/u.test(entry.name) || entry.name.endsWith(".d.ts")) continue;
947
+ found.push(import_node_path6.default.join(dir, entry.name));
948
+ }
949
+ }, "walk");
950
+ await walk(root);
951
+ return found.sort();
952
+ }
953
+ __name(collectSources, "collectSources");
954
+ function returnsJsx(node) {
955
+ let seen = false;
956
+ const visit = /* @__PURE__ */ __name((child) => {
957
+ if (seen) return;
958
+ if (import_typescript.default.isJsxElement(child) || import_typescript.default.isJsxSelfClosingElement(child) || import_typescript.default.isJsxFragment(child)) {
959
+ seen = true;
960
+ return;
961
+ }
962
+ if (child !== node && import_typescript.default.isFunctionLike(child)) return;
963
+ import_typescript.default.forEachChild(child, visit);
964
+ }, "visit");
965
+ import_typescript.default.forEachChild(node, visit);
966
+ return seen;
967
+ }
968
+ __name(returnsJsx, "returnsJsx");
969
+ function signalNameOf(node) {
970
+ if (!import_typescript.default.isPropertyAccessExpression(node)) return void 0;
971
+ if (node.name.text !== "value") return void 0;
972
+ return node.expression.getText();
973
+ }
974
+ __name(signalNameOf, "signalNameOf");
975
+ function lineSnippet(source, node) {
976
+ const { line } = source.getLineAndCharacterOfPosition(node.getStart(source));
977
+ return (source.text.split(/\r?\n/u)[line] ?? "").trim();
978
+ }
979
+ __name(lineSnippet, "lineSnippet");
980
+ function diagnosticAt(source, file, node, rest) {
981
+ const { line, character } = source.getLineAndCharacterOfPosition(node.getStart(source));
982
+ return {
983
+ ...rest,
984
+ file,
985
+ line: line + 1,
986
+ column: character + 1,
987
+ snippet: lineSnippet(source, node)
988
+ };
989
+ }
990
+ __name(diagnosticAt, "diagnosticAt");
991
+ var isWriteOperator = /* @__PURE__ */ __name((kind) => kind === import_typescript.default.SyntaxKind.EqualsToken || kind === import_typescript.default.SyntaxKind.PlusEqualsToken || kind === import_typescript.default.SyntaxKind.MinusEqualsToken || kind === import_typescript.default.SyntaxKind.AsteriskEqualsToken || kind === import_typescript.default.SyntaxKind.SlashEqualsToken, "isWriteOperator");
992
+ function ruleEffectSelfSubscription(source, file) {
993
+ const found = [];
994
+ const inspectEffectBody = /* @__PURE__ */ __name((body) => {
995
+ const reads = /* @__PURE__ */ new Map();
996
+ const writes = /* @__PURE__ */ new Map();
997
+ const visit2 = /* @__PURE__ */ __name((node, insideUntrack) => {
998
+ if (node !== body && import_typescript.default.isFunctionLike(node)) return;
999
+ const nextUntracked = insideUntrack || import_typescript.default.isCallExpression(node) && import_typescript.default.isIdentifier(node.expression) && node.expression.text === "untrack";
1000
+ if (import_typescript.default.isBinaryExpression(node) && isWriteOperator(node.operatorToken.kind)) {
1001
+ const name = signalNameOf(node.left);
1002
+ if (name !== void 0 && !nextUntracked) writes.set(name, node);
1003
+ } else if ((import_typescript.default.isPrefixUnaryExpression(node) || import_typescript.default.isPostfixUnaryExpression(node)) && (node.operator === import_typescript.default.SyntaxKind.PlusPlusToken || node.operator === import_typescript.default.SyntaxKind.MinusMinusToken)) {
1004
+ const name = signalNameOf(node.operand);
1005
+ if (name !== void 0) {
1006
+ if (!nextUntracked) writes.set(name, node);
1007
+ reads.set(name, node);
1008
+ }
1009
+ } else if (import_typescript.default.isCallExpression(node) && import_typescript.default.isPropertyAccessExpression(node.expression) && node.expression.name.text === "set") {
1010
+ if (!nextUntracked) writes.set(node.expression.expression.getText(), node);
1011
+ } else {
1012
+ const name = signalNameOf(node);
1013
+ if (name !== void 0) {
1014
+ const parent = node.parent;
1015
+ const isAssignmentTarget = import_typescript.default.isBinaryExpression(parent) && isWriteOperator(parent.operatorToken.kind) && parent.left === node;
1016
+ if (!isAssignmentTarget) reads.set(name, node);
1017
+ }
1018
+ }
1019
+ import_typescript.default.forEachChild(node, (child) => visit2(child, nextUntracked));
1020
+ }, "visit");
1021
+ visit2(body, false);
1022
+ for (const [name, writeNode] of writes) {
1023
+ if (!reads.has(name)) continue;
1024
+ found.push(diagnosticAt(source, file, writeNode, {
1025
+ code: VOBS_C210,
1026
+ severity: "error",
1027
+ 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`,
1028
+ 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`
1029
+ }));
1030
+ }
1031
+ }, "inspectEffectBody");
1032
+ const visit = /* @__PURE__ */ __name((node) => {
1033
+ if (import_typescript.default.isCallExpression(node) && import_typescript.default.isIdentifier(node.expression) && (node.expression.text === "effect" || node.expression.text === "renderEffect")) {
1034
+ const callback = node.arguments[0];
1035
+ if (callback !== void 0 && (import_typescript.default.isArrowFunction(callback) || import_typescript.default.isFunctionExpression(callback))) {
1036
+ inspectEffectBody(callback.body);
1037
+ }
1038
+ }
1039
+ import_typescript.default.forEachChild(node, visit);
1040
+ }, "visit");
1041
+ visit(source);
1042
+ return found;
1043
+ }
1044
+ __name(ruleEffectSelfSubscription, "ruleEffectSelfSubscription");
1045
+ function isKeyedListCall(node) {
1046
+ if (!import_typescript.default.isCallExpression(node)) return false;
1047
+ if (!import_typescript.default.isPropertyAccessExpression(node.expression) || node.expression.name.text !== "map") return false;
1048
+ const callback = node.arguments[0];
1049
+ return callback !== void 0 && import_typescript.default.isFunctionLike(callback) && returnsJsx(callback);
1050
+ }
1051
+ __name(isKeyedListCall, "isKeyedListCall");
1052
+ function ruleListInBranch(source, file) {
1053
+ const found = [];
1054
+ 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";
1055
+ 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";
1056
+ const visit = /* @__PURE__ */ __name((node) => {
1057
+ if (import_typescript.default.isConditionalExpression(node)) {
1058
+ for (const branch of [node.whenTrue, node.whenFalse]) {
1059
+ if (isKeyedListCall(branch)) {
1060
+ found.push(diagnosticAt(source, file, branch, { code: VOBS_C232, severity: "warning", message, fix }));
1061
+ }
1062
+ }
1063
+ } else if (import_typescript.default.isBinaryExpression(node) && node.operatorToken.kind === import_typescript.default.SyntaxKind.AmpersandAmpersandToken && isKeyedListCall(node.right)) {
1064
+ found.push(diagnosticAt(source, file, node.right, { code: VOBS_C232, severity: "warning", message, fix }));
1065
+ }
1066
+ import_typescript.default.forEachChild(node, visit);
1067
+ }, "visit");
1068
+ visit(source);
1069
+ return found;
1070
+ }
1071
+ __name(ruleListInBranch, "ruleListInBranch");
1072
+ function containsSignalRead(node) {
1073
+ let seen = false;
1074
+ const visit = /* @__PURE__ */ __name((child) => {
1075
+ if (seen) return;
1076
+ if (import_typescript.default.isFunctionLike(child)) return;
1077
+ if (signalNameOf(child) !== void 0) {
1078
+ seen = true;
1079
+ return;
1080
+ }
1081
+ import_typescript.default.forEachChild(child, visit);
1082
+ }, "visit");
1083
+ visit(node);
1084
+ return seen;
1085
+ }
1086
+ __name(containsSignalRead, "containsSignalRead");
1087
+ function ruleSignalCapturedInBody(source, file) {
1088
+ const found = [];
1089
+ const inspectComponent = /* @__PURE__ */ __name((fn) => {
1090
+ const body = fn.body;
1091
+ if (body === void 0 || !import_typescript.default.isBlock(body) || !returnsJsx(fn)) return;
1092
+ const captured = [];
1093
+ for (const statement of body.statements) {
1094
+ if (!import_typescript.default.isVariableStatement(statement)) continue;
1095
+ for (const declaration of statement.declarationList.declarations) {
1096
+ if (!import_typescript.default.isIdentifier(declaration.name) || declaration.initializer === void 0) continue;
1097
+ if (containsSignalRead(declaration.initializer)) {
1098
+ captured.push({ name: declaration.name.text, node: declaration });
1099
+ }
1100
+ }
1101
+ }
1102
+ if (captured.length === 0) return;
1103
+ const usedInJsx = /* @__PURE__ */ new Set();
1104
+ const visit2 = /* @__PURE__ */ __name((node) => {
1105
+ if (import_typescript.default.isJsxExpression(node) && node.expression !== void 0) {
1106
+ const scan = /* @__PURE__ */ __name((child) => {
1107
+ if (import_typescript.default.isIdentifier(child)) usedInJsx.add(child.text);
1108
+ import_typescript.default.forEachChild(child, scan);
1109
+ }, "scan");
1110
+ scan(node.expression);
1111
+ }
1112
+ import_typescript.default.forEachChild(node, visit2);
1113
+ }, "visit");
1114
+ visit2(body);
1115
+ for (const item of captured) {
1116
+ if (!usedInJsx.has(item.name)) continue;
1117
+ found.push(diagnosticAt(source, file, item.node, {
1118
+ code: VOBS_C118,
1119
+ severity: "warning",
1120
+ 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`,
1121
+ 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`
1122
+ }));
1123
+ }
1124
+ }, "inspectComponent");
1125
+ const visit = /* @__PURE__ */ __name((node) => {
1126
+ if (import_typescript.default.isFunctionDeclaration(node) || import_typescript.default.isFunctionExpression(node) || import_typescript.default.isArrowFunction(node)) {
1127
+ inspectComponent(node);
1128
+ }
1129
+ import_typescript.default.forEachChild(node, visit);
1130
+ }, "visit");
1131
+ visit(source);
1132
+ return found;
1133
+ }
1134
+ __name(ruleSignalCapturedInBody, "ruleSignalCapturedInBody");
1135
+ var IGNORE_NEXT_LINE = /vobs-check-ignore-next-line/u;
1136
+ function suppressedLines(text5) {
1137
+ const suppressed = /* @__PURE__ */ new Set();
1138
+ const lines = text5.split(/\r?\n/u);
1139
+ for (let index = 0; index < lines.length; index++) {
1140
+ if (IGNORE_NEXT_LINE.test(lines[index])) suppressed.add(index + 2);
1141
+ }
1142
+ return suppressed;
1143
+ }
1144
+ __name(suppressedLines, "suppressedLines");
1145
+ function analyzeSource(text5, file) {
1146
+ const source = import_typescript.default.createSourceFile(file, text5, import_typescript.default.ScriptTarget.Latest, true, import_typescript.default.ScriptKind.TSX);
1147
+ const suppressed = suppressedLines(text5);
1148
+ return [
1149
+ ...ruleEffectSelfSubscription(source, file),
1150
+ ...ruleListInBranch(source, file),
1151
+ ...ruleSignalCapturedInBody(source, file)
1152
+ ].filter((item) => !suppressed.has(item.line)).sort((a, b) => a.line - b.line || a.column - b.column);
1153
+ }
1154
+ __name(analyzeSource, "analyzeSource");
1155
+ async function checkCommand(options = {}) {
1156
+ const root = import_node_path6.default.resolve(options.dir ?? process.cwd());
1157
+ let all;
1158
+ try {
1159
+ all = await collectSources(root);
1160
+ } catch (error2) {
1161
+ logger.error(error2 instanceof Error ? error2.message : String(error2));
1162
+ process.exitCode = 1;
1163
+ return;
1164
+ }
1165
+ const files = options.includeTests === true ? all : all.filter((file) => !TEST_FILE.test(import_node_path6.default.relative(root, file).split(import_node_path6.default.sep).join("/")));
1166
+ const skipped = all.length - files.length;
1167
+ const diagnostics = [];
1168
+ for (const absolute of files) {
1169
+ const text5 = await (0, import_promises2.readFile)(absolute, "utf8");
1170
+ const relative2 = import_node_path6.default.relative(root, absolute).split(import_node_path6.default.sep).join("/");
1171
+ diagnostics.push(...analyzeSource(text5, relative2));
1172
+ }
1173
+ const errors = diagnostics.filter((item) => item.severity === "error");
1174
+ const report = { root, files: files.length, skippedTests: skipped, diagnostics };
1175
+ if (options.write === true) {
1176
+ const target = import_node_path6.default.join(root, REPORT_PATH);
1177
+ await (0, import_promises2.mkdir)(import_node_path6.default.dirname(target), { recursive: true });
1178
+ await (0, import_promises2.writeFile)(target, `${JSON.stringify(report, null, 2)}
1179
+ `, "utf8");
1180
+ logger.info(`\u62A5\u544A\u5199\u5165 ${REPORT_PATH}\uFF08\u5F00\u53D1\u53F0\u9762\u677F\u8BFB\u5B83\u663E\u793A\u300C\u9879\u76EE\u300D\u9875\uFF09`);
1181
+ }
1182
+ if (options.json === true) {
1183
+ console.log(JSON.stringify(report, null, 2));
1184
+ if (errors.length > 0) process.exitCode = 1;
1185
+ return;
1186
+ }
1187
+ if (diagnostics.length === 0) {
1188
+ 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`);
1189
+ return;
1190
+ }
1191
+ console.log("");
1192
+ for (const item of diagnostics) {
1193
+ const mark = item.severity === "error" ? "\u2716" : "!";
1194
+ console.log(` ${mark} ${item.file}:${item.line}:${item.column} ${item.code}`);
1195
+ console.log(` ${item.message}`);
1196
+ if (item.snippet !== "") console.log(` ${item.snippet}`);
1197
+ console.log(` \u2192 ${item.fix}`);
1198
+ console.log("");
1199
+ }
1200
+ const warnings = diagnostics.length - errors.length;
1201
+ console.log(
1202
+ ` ${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` : ""}
1203
+ `
1204
+ );
1205
+ if (errors.length > 0) process.exitCode = 1;
1206
+ }
1207
+ __name(checkCommand, "checkCommand");
1208
+
891
1209
  // packages/cli/src/banner.ts
892
- var BANNER_LINE1 = "\u{1D5E9}\u{1D5E2}\u{1D5D5}\u{1D5E6} \u{1D5D6}\u{1D5DF}\u{1D5DC} \u{1D603}\u{1D7ED}.\u{1D7EC}";
1210
+ var BANNER_LINE1 = "\u{1D5E9}\u{1D5E2}\u{1D5D5}\u{1D5E6} \u{1D5D6}\u{1D5DF}\u{1D5DC}";
893
1211
  var BANNER_TAGLINE = "Signals First \xB7 Zero Re-renders \xB7 Ultra Lightweight";
894
1212
  var INDENT = " ";
895
1213
  var DIM = "\x1B[2m";
@@ -905,12 +1223,12 @@ function showLogo() {
905
1223
  }
906
1224
  __name(showLogo, "showLogo");
907
1225
  function showBanner() {
908
- console.log(`${INDENT}${BANNER_LINE1}`);
1226
+ console.log(`${INDENT}${BANNER_LINE1} v${cliVersion()}`);
909
1227
  }
910
1228
  __name(showBanner, "showBanner");
911
1229
  function showDevBanner() {
912
1230
  console.log();
913
- console.log(`${INDENT}${BANNER_LINE1}`);
1231
+ console.log(`${INDENT}${BANNER_LINE1} v${cliVersion()}`);
914
1232
  console.log();
915
1233
  }
916
1234
  __name(showDevBanner, "showDevBanner");
@@ -964,7 +1282,15 @@ function createCLI() {
964
1282
  dev: options.dev
965
1283
  });
966
1284
  });
967
- 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) => {
1285
+ 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) => {
1286
+ await checkCommand({
1287
+ dir,
1288
+ json: options.json === true,
1289
+ write: options.write === true,
1290
+ includeTests: options.includeTests === true
1291
+ });
1292
+ });
1293
+ 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) => {
968
1294
  await dshCommand(action, target, {
969
1295
  dir: options.dir,
970
1296
  pm: options.pm,
@@ -985,18 +1311,30 @@ function createCLI() {
985
1311
  showBanner();
986
1312
  });
987
1313
  cli.help();
988
- cli.version("1.0.0");
1314
+ cli.version(cliVersion());
989
1315
  return cli;
990
1316
  }
991
1317
  __name(createCLI, "createCLI");
1318
+ var KNOWN_SUBCOMMANDS = [
1319
+ "init",
1320
+ "create",
1321
+ "dev",
1322
+ "build",
1323
+ "generate",
1324
+ "g",
1325
+ "add",
1326
+ "check",
1327
+ "dsh",
1328
+ "logo"
1329
+ ];
992
1330
 
993
1331
  // packages/cli/src/repl.ts
994
- var import_promises2 = require("readline/promises");
1332
+ var import_promises3 = require("readline/promises");
995
1333
  var import_node_process = require("process");
996
1334
  var import_picocolors2 = __toESM(require("picocolors"), 1);
997
1335
  async function startRepl() {
998
1336
  showLogo();
999
- const rl = (0, import_promises2.createInterface)({
1337
+ const rl = (0, import_promises3.createInterface)({
1000
1338
  input: import_node_process.stdin,
1001
1339
  output: import_node_process.stdout,
1002
1340
  prompt: ` ${import_picocolors2.default.magenta("\u276F")} `,
@@ -1107,16 +1445,52 @@ function splitArgs(line) {
1107
1445
  __name(splitArgs, "splitArgs");
1108
1446
 
1109
1447
  // packages/cli/src/start.ts
1448
+ var META_FLAGS = /* @__PURE__ */ new Set(["--help", "-h", "--version", "-V"]);
1449
+ function isFlag(token) {
1450
+ return token.startsWith("-") && token !== "-";
1451
+ }
1452
+ __name(isFlag, "isFlag");
1110
1453
  async function start(args) {
1111
1454
  if (args.length === 0 || args[0] === "cli") {
1112
1455
  await startRepl();
1113
1456
  return;
1114
1457
  }
1115
- if (args.includes("--version") || args.includes("-V")) {
1458
+ const first = args[0];
1459
+ const hasSubcommand = KNOWN_SUBCOMMANDS.includes(first);
1460
+ if (!hasSubcommand && (args.includes("--version") || args.includes("-V"))) {
1116
1461
  showBanner();
1117
1462
  return;
1118
1463
  }
1119
- createCLI().parse();
1464
+ if (!hasSubcommand && !isFlag(first)) {
1465
+ process.stderr.write(`\u2716 \u672A\u77E5\u547D\u4EE4\uFF1A${first}
1466
+
1467
+ `);
1468
+ try {
1469
+ createCLI().parse(["node", "vobs", "--help"]);
1470
+ } catch {
1471
+ }
1472
+ process.exitCode = 1;
1473
+ return;
1474
+ }
1475
+ if (!hasSubcommand && isFlag(first) && !META_FLAGS.has(first)) {
1476
+ process.stderr.write(`\u2716 \u672A\u77E5\u9009\u9879\uFF1A${first}
1477
+
1478
+ `);
1479
+ try {
1480
+ createCLI().parse(["node", "vobs", "--help"]);
1481
+ } catch {
1482
+ }
1483
+ process.exitCode = 1;
1484
+ return;
1485
+ }
1486
+ try {
1487
+ createCLI().parse();
1488
+ } catch (error2) {
1489
+ const message = error2 instanceof Error ? error2.message : String(error2);
1490
+ process.stderr.write(`\u2716 ${message}
1491
+ `);
1492
+ process.exitCode = 1;
1493
+ }
1120
1494
  }
1121
1495
  __name(start, "start");
1122
1496
  // Annotate the CommonJS export names for ESM import in node: