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