@nudojs/cli 2.0.1 → 3.0.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 (2) hide show
  1. package/dist/index.js +102 -115
  2. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -21497,20 +21497,17 @@ function any() {
21497
21497
  return makeBuilder(void 0, []);
21498
21498
  }
21499
21499
  function array(item) {
21500
- if (!isConstraint(item))
21501
- throw new Error("nudo: array(item) \u671F\u671B\u7EA6\u675F\u503C\uFF08number()/string()/\u2026\u6216\u5176\u7EC4\u5408\u5B50\uFF09");
21502
- return makeBuilder(void 0, [], { element: item });
21500
+ return makeBuilder(void 0, [], {
21501
+ element: asNestedConstraint(item, "array(item)")
21502
+ });
21503
21503
  }
21504
21504
  function shape(fields) {
21505
21505
  const mapped = {};
21506
21506
  for (const [k, v2] of Object.entries(fields)) {
21507
- if (!isConstraint(v2))
21508
- throw new Error(
21509
- `nudo: shape \u5B57\u6BB5 '${k}' \u671F\u671B\u7EA6\u675F\u503C\uFF08number()/string()/\u2026\u6216\u5176\u7EC4\u5408\u5B50\uFF09\uFF0C\u6536\u5230\u975E\u7EA6\u675F`
21510
- );
21507
+ const constraint = asNestedConstraint(v2, `shape \u5B57\u6BB5 '${k}'`);
21511
21508
  mapped[k] = {
21512
- constraint: v2,
21513
- ...v2.isOptional ? { optional: true } : {}
21509
+ constraint,
21510
+ ...constraint.isOptional ? { optional: true } : {}
21514
21511
  };
21515
21512
  }
21516
21513
  return makeBuilder(void 0, [], { fields: mapped });
@@ -21535,19 +21532,32 @@ function lit3(v2) {
21535
21532
  const prim2 = typeof v2 === "number" ? "number" : typeof v2 === "string" ? "string" : typeof v2 === "boolean" ? "boolean" : void 0;
21536
21533
  return makeBuilder(prim2, [eq(selfTerm(), lit(v2))]);
21537
21534
  }
21535
+ function asNestedConstraint(x, ctx) {
21536
+ if (isConstraint(x)) return toPlainConstraint(x);
21537
+ if (x === null || x === void 0 || typeof x === "number" || typeof x === "string" || typeof x === "boolean") {
21538
+ return toPlainConstraint(lit3(x));
21539
+ }
21540
+ throw new Error(
21541
+ `nudo: ${ctx} \u671F\u671B\u7EA6\u675F\u503C\uFF08number()/string()/\u2026\uFF09\u6216\u5177\u4F53\u5B57\u9762\u91CF\uFF0C\u6536\u5230\u975E\u7EA6\u675F`
21542
+ );
21543
+ }
21538
21544
  function union(...cs) {
21539
21545
  if (cs.length === 0)
21540
21546
  throw new Error("nudo union(): \u81F3\u5C11\u9700\u8981\u4E00\u4E2A\u6210\u5458\u7EA6\u675F");
21541
- return makeBuilder(void 0, [], { members: cs.map(toPlainConstraint) });
21547
+ return makeBuilder(void 0, [], {
21548
+ members: cs.map((c) => asNestedConstraint(c, "union \u6210\u5458"))
21549
+ });
21542
21550
  }
21543
21551
  function fn(params, returns, opts) {
21544
21552
  const normalized = {};
21545
- for (const [k, v2] of Object.entries(params)) normalized[k] = toPlainConstraint(v2);
21553
+ for (const [k, v2] of Object.entries(params)) {
21554
+ normalized[k] = asNestedConstraint(v2, `fn \u53C2\u6570 '${k}'`);
21555
+ }
21546
21556
  return makeBuilder(void 0, [], {
21547
21557
  fn: {
21548
21558
  params: normalized,
21549
- ...returns !== void 0 ? { returns: toPlainConstraint(returns) } : {},
21550
- ...opts?.throws !== void 0 ? { throws: toPlainConstraint(opts.throws) } : {}
21559
+ ...returns !== void 0 ? { returns: asNestedConstraint(returns, "fn \u8FD4\u56DE\u503C") } : {},
21560
+ ...opts?.throws !== void 0 ? { throws: asNestedConstraint(opts.throws, "fn throws") } : {}
21551
21561
  }
21552
21562
  });
21553
21563
  }
@@ -60675,10 +60685,6 @@ function absLit(v2) {
60675
60685
  if (v2 === null) return absNullLit();
60676
60686
  return absUndefLit();
60677
60687
  }
60678
- function absUnion(members) {
60679
- if (members.length === 0) return absExact({ k: "never" });
60680
- return members.reduce((acc, m) => joinAbs(acc, m));
60681
- }
60682
60688
  function tryParseConstraint(expr) {
60683
60689
  const s = expr.trim();
60684
60690
  if (!CONSTRAINT_EXPR_RE.test(s)) return void 0;
@@ -60707,15 +60713,15 @@ function flattenPreds(preds) {
60707
60713
  function constraintSelfEqLit(c, selfIds) {
60708
60714
  const leaves = flattenPreds(c.preds);
60709
60715
  const eqs = leaves.filter((p2) => p2.op === "eq");
60710
- if (eqs.length !== 1) return void 0;
60716
+ if (eqs.length !== 1) return { found: false };
60711
60717
  const p = eqs[0];
60712
- if (p.op !== "eq") return void 0;
60718
+ if (p.op !== "eq") return { found: false };
60713
60719
  const isSelf = (t) => t.op === "var" && typeof t.id === "string" && selfIds.includes(t.id);
60714
60720
  const a = p.a;
60715
60721
  const b = p.b;
60716
- if (isSelf(a) && b.op === "lit") return b.value;
60717
- if (isSelf(b) && a.op === "lit") return a.value;
60718
- return void 0;
60722
+ if (isSelf(a) && b.op === "lit") return { found: true, value: b.value };
60723
+ if (isSelf(b) && a.op === "lit") return { found: true, value: a.value };
60724
+ return { found: false };
60719
60725
  }
60720
60726
  function constraintToCaseArgAbs(c) {
60721
60727
  if (c.members && c.members.length > 0) {
@@ -60724,10 +60730,32 @@ function constraintToCaseArgAbs(c) {
60724
60730
  return { shape: { k: "sum", members: parts }, conf: "path" };
60725
60731
  }
60726
60732
  const selfIds = [SELF, "__arg", "__nudo_self__"];
60727
- const lv = constraintSelfEqLit(c, selfIds);
60728
- if (typeof lv === "number" && !Number.isNaN(lv)) return numLit(lv);
60729
- if (typeof lv === "string") return strLit(lv);
60730
- if (typeof lv === "boolean") return boolLit(lv);
60733
+ const litRes = constraintSelfEqLit(c, selfIds);
60734
+ if (litRes.found) {
60735
+ const lv = litRes.value;
60736
+ if (typeof lv === "number" && !Number.isNaN(lv)) return numLit(lv);
60737
+ if (typeof lv === "string") return strLit(lv);
60738
+ if (typeof lv === "boolean") return boolLit(lv);
60739
+ if (lv === null) return absNullLit();
60740
+ return absUndefLit();
60741
+ }
60742
+ if (c.element) {
60743
+ return absExact({ k: "arr", element: constraintToCaseArgAbs(c.element) });
60744
+ }
60745
+ if (c.fields) {
60746
+ const slots = {};
60747
+ for (const [key, field] of Object.entries(c.fields)) {
60748
+ slots[key] = { value: constraintToCaseArgAbs(field.constraint) };
60749
+ }
60750
+ return absExact({ k: "obj", slots });
60751
+ }
60752
+ const bareScalar = !c.fields && !c.element && !c.members && !c.fn && c.int !== true && c.preds.length === 0;
60753
+ if (bareScalar) {
60754
+ if (c.prim === "number" || c.prim === "string" || c.prim === "boolean") {
60755
+ return absExact({ k: "prim", type: c.prim });
60756
+ }
60757
+ if (!c.prim) return absUnknown2();
60758
+ }
60731
60759
  return constraintToEntryAbs(c, "__arg");
60732
60760
  }
60733
60761
  function parseCaseArgExpr(expr) {
@@ -60739,49 +60767,16 @@ function parseCaseArgExpr(expr) {
60739
60767
  return absUnknown2();
60740
60768
  }
60741
60769
  }
60742
- return parseLegacyTypeExpr(expr);
60770
+ return parseLiteralOrStructure(expr);
60743
60771
  }
60744
- function parseLegacyTypeExpr(expr) {
60772
+ function parseLiteralOrStructure(expr) {
60745
60773
  const s = expr.trim();
60746
- if (s === "T.number") return num();
60747
- if (s === "T.string") return str();
60748
- if (s === "T.boolean") return bool();
60749
- if (s === "T.unknown") return absUnknown2();
60750
- if (s === "T.never") return absExact({ k: "never" });
60751
- if (s === "T.null") return absNullLit();
60752
- if (s === "T.undefined") return absUndefLit();
60753
60774
  if (s === "true") return absLit(true);
60754
60775
  if (s === "false") return absLit(false);
60755
60776
  if (s === "null") return absLit(null);
60756
60777
  if (s === "undefined") return absLit(void 0);
60757
- const literalMatch = s.match(/^T\.literal\((.+)\)$/);
60758
- if (literalMatch) {
60759
- return absLit(parsePrimitiveValue(literalMatch[1].trim()));
60760
- }
60761
- if (s.startsWith("T.union(") && s.endsWith(")")) {
60762
- const inner = s.slice("T.union(".length, -1);
60763
- const args = splitTopLevelArgs(inner);
60764
- return absUnion(args.map(parseLegacyTypeExpr));
60765
- }
60766
- if (s.startsWith("T.array(") && s.endsWith(")")) {
60767
- const inner = s.slice("T.array(".length, -1);
60768
- return absExact({ k: "arr", element: parseLegacyTypeExpr(inner) });
60769
- }
60770
- if (s.startsWith("T.tuple(") && s.endsWith(")")) {
60771
- const inner = s.slice("T.tuple(".length, -1).trim();
60772
- if (inner.startsWith("[") && inner.endsWith("]")) {
60773
- const elements = splitTopLevelArgs(inner.slice(1, -1));
60774
- return absExact({ k: "tuple", elements: elements.map(parseLegacyTypeExpr) });
60775
- }
60776
- return absExact({ k: "tuple", elements: [] });
60777
- }
60778
- if (s.startsWith("T.object(") && s.endsWith(")")) {
60779
- const inner = s.slice("T.object(".length, -1).trim();
60780
- if (inner.startsWith("{") && inner.endsWith("}")) {
60781
- return parseObjectLiteral(inner.slice(1, -1).trim());
60782
- }
60783
- return absExact({ k: "obj", slots: {} });
60784
- }
60778
+ if (s === "unknown" || s === "any") return absUnknown2();
60779
+ if (s === "never") return absExact({ k: "never" });
60785
60780
  if (findTopLevelArrow(s) !== -1 || /^function\s*[\w$]*\s*\(/.test(s)) {
60786
60781
  const fnExpr = parseArrowFunctionExpr(s);
60787
60782
  if (fnExpr) {
@@ -60799,7 +60794,7 @@ function parseLegacyTypeExpr(expr) {
60799
60794
  const content = s.slice(1, -1).trim();
60800
60795
  if (!content) return absExact({ k: "tuple", elements: [] });
60801
60796
  const elements = splitTopLevelArgs(content);
60802
- return absExact({ k: "tuple", elements: elements.map(parseLegacyTypeExpr) });
60797
+ return absExact({ k: "tuple", elements: elements.map((e) => parseCaseArgExpr(e)) });
60803
60798
  }
60804
60799
  return absUnknown2();
60805
60800
  }
@@ -60812,21 +60807,10 @@ function parseObjectLiteral(content) {
60812
60807
  if (colonIdx === -1) continue;
60813
60808
  const key = entry.slice(0, colonIdx).trim().replace(/^["']|["']$/g, "");
60814
60809
  const val = entry.slice(colonIdx + 1).trim();
60815
- slots[key] = { value: parseLegacyTypeExpr(val) };
60810
+ slots[key] = { value: parseCaseArgExpr(val) };
60816
60811
  }
60817
60812
  return absExact({ k: "obj", slots });
60818
60813
  }
60819
- function parsePrimitiveValue(s) {
60820
- if (s === "true") return true;
60821
- if (s === "false") return false;
60822
- if (s === "null") return null;
60823
- if (s === "undefined") return void 0;
60824
- if (/^-?\d+(\.\d+)?$/.test(s)) return Number(s);
60825
- if (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'")) {
60826
- return s.slice(1, -1);
60827
- }
60828
- return s;
60829
- }
60830
60814
  function splitTopLevelArgs(s) {
60831
60815
  const result = [];
60832
60816
  let depth = 0;
@@ -61239,7 +61223,7 @@ function findReplaceSeparator(raw) {
61239
61223
  }
61240
61224
  if (ch === " " && depth === 0) {
61241
61225
  const rest = raw.slice(i + 1).trimStart();
61242
- if (rest.startsWith("T.") || rest.startsWith("{") || rest.startsWith("[") || rest.startsWith('"') || rest.startsWith("'") || /^-?\d/.test(rest) || rest === "true" || rest === "false" || rest === "null" || rest === "undefined") {
61226
+ if (CONSTRAINT_EXPR_RE.test(rest) || rest.startsWith("{") || rest.startsWith("[") || rest.startsWith('"') || rest.startsWith("'") || rest === "true" || rest === "false" || rest === "null" || rest === "undefined" || rest === "unknown" || rest === "any" || rest === "never" || /^-?\d+(\.\d+)?$/.test(rest) || rest.includes("=>") || /^function\s*[\w$]*\s*\(/.test(rest)) {
61243
61227
  return i;
61244
61228
  }
61245
61229
  }
@@ -61293,7 +61277,7 @@ var init_directives = __esm({
61293
61277
  PURE_REGEX = /@nudo:pure\b/g;
61294
61278
  SKIP_REGEX = /@nudo:skip(?:\s+(.+))?/g;
61295
61279
  SAMPLE_REGEX = /@nudo:sample\s+(\d+)/g;
61296
- CONSTRAINT_EXPR_RE = /^(number|string|boolean|array|shape|lit|union|fn|partial|pick|omit|record|required|readonly|nonNullable|and)\s*\(/;
61280
+ CONSTRAINT_EXPR_RE = /^(number|string|boolean|any|array|shape|lit|union|fn|partial|pick|omit|record|required|readonly|nonNullable|and)\s*\(/;
61297
61281
  AS_REGEX = /^\s*@nudo:as\s+(.+)/;
61298
61282
  REPLACE_REGEX = /^\s*@nudo:replace\s+(.+)/;
61299
61283
  ENV_REGEX = /^\/\s*@nudo:env\s+(.+)/;
@@ -275512,7 +275496,7 @@ function absFnSig(paramTypes, returnType) {
275512
275496
  returnType
275513
275497
  });
275514
275498
  }
275515
- function absUnion2(members) {
275499
+ function absUnion(members) {
275516
275500
  if (members.length === 0) return absNever();
275517
275501
  return members.reduce((acc, m) => joinAbs(acc, m));
275518
275502
  }
@@ -275827,7 +275811,7 @@ function materialize(ctx) {
275827
275811
  const ret = mapType(ctx, decl.type, 0);
275828
275812
  if (!isPlainUnknown(ret)) returns.push(ret);
275829
275813
  }
275830
- sig2.returnType = returns.length > 0 ? absUnion2(returns) : absUnknown3();
275814
+ sig2.returnType = returns.length > 0 ? absUnion(returns) : absUnknown3();
275831
275815
  }
275832
275816
  rec[p.name] = value;
275833
275817
  continue;
@@ -275851,7 +275835,7 @@ function materialize(ctx) {
275851
275835
  if (ctor) {
275852
275836
  for (const param of ctor.parameters) {
275853
275837
  let type = param.type ? mapType(ctx, param.type, 0) : absUnknown3();
275854
- if (param.questionToken) type = absUnion2([type, absUndefLit2()]);
275838
+ if (param.questionToken) type = absUnion([type, absUndefLit2()]);
275855
275839
  params.push(type);
275856
275840
  }
275857
275841
  }
@@ -275901,7 +275885,7 @@ function instanceFor(ctx, key) {
275901
275885
  const name = memberName(member);
275902
275886
  if (name === void 0) continue;
275903
275887
  let type = member.type ? mapType(ctx, member.type, 0) : absUnknown3();
275904
- if (member.questionToken) type = absUnion2([type, absUndefLit2()]);
275888
+ if (member.questionToken) type = absUnion([type, absUndefLit2()]);
275905
275889
  properties[name] = type;
275906
275890
  } else if (import_typescript.default.isMethodSignature(member) || import_typescript.default.isMethodDeclaration(member)) {
275907
275891
  const name = memberName(member);
@@ -275929,7 +275913,7 @@ function signatureToFnSig(ctx, sig2) {
275929
275913
  } else {
275930
275914
  type = param.type ? mapType(ctx, param.type, 0) : absUnknown3();
275931
275915
  }
275932
- if (param.questionToken && !param.dotDotDotToken) type = absUnion2([type, absUndefLit2()]);
275916
+ if (param.questionToken && !param.dotDotDotToken) type = absUnion([type, absUndefLit2()]);
275933
275917
  return type;
275934
275918
  });
275935
275919
  const returnType = sig2.type ? mapType(ctx, sig2.type, 0) : absUnknown3();
@@ -275942,7 +275926,7 @@ function mapTypeMembers(ctx, members, depth) {
275942
275926
  const name = memberName(member);
275943
275927
  if (name === void 0) continue;
275944
275928
  let type = member.type ? mapType(ctx, member.type, depth) : absUnknown3();
275945
- if (member.questionToken) type = absUnion2([type, absUndefLit2()]);
275929
+ if (member.questionToken) type = absUnion([type, absUndefLit2()]);
275946
275930
  properties[name] = type;
275947
275931
  } else if (import_typescript.default.isMethodSignature(member)) {
275948
275932
  const name = memberName(member);
@@ -275984,7 +275968,7 @@ function mapType(ctx, node, depth) {
275984
275968
  return absUnknown3();
275985
275969
  }
275986
275970
  if (import_typescript.default.isUnionTypeNode(node)) {
275987
- return absUnion2(node.types.map((t) => mapType(ctx, t, depth)));
275971
+ return absUnion(node.types.map((t) => mapType(ctx, t, depth)));
275988
275972
  }
275989
275973
  if (import_typescript.default.isIntersectionTypeNode(node)) {
275990
275974
  const members = node.types.map((t) => mapType(ctx, t, depth));
@@ -276018,7 +276002,7 @@ function mapType(ctx, node, depth) {
276018
276002
  const params = [];
276019
276003
  for (const param of ctor.parameters) {
276020
276004
  let type = param.type ? mapType(ctx, param.type, depth) : absUnknown3();
276021
- if (param.questionToken) type = absUnion2([type, absUndefLit2()]);
276005
+ if (param.questionToken) type = absUnion([type, absUndefLit2()]);
276022
276006
  params.push(type);
276023
276007
  }
276024
276008
  return absFnSig(params, ctor.type ? mapType(ctx, ctor.type, depth) : absUnknown3());
@@ -277701,7 +277685,8 @@ function validateMockDirectives(directives, diagnostics) {
277701
277685
  for (const d of directives) {
277702
277686
  if (d.kind !== "mock" || !d.expression) continue;
277703
277687
  const expr = d.expression.trim();
277704
- if (expr.includes("(") && expr.includes(")") && !expr.startsWith("T.")) {
277688
+ const isTypeExpr = /^(number|string|boolean|any|array|shape|lit|union|fn|partial|pick|omit|record|required|readonly|nonNullable|and)\s*\(/.test(expr) || expr === "unknown" || expr === "any" || expr === "never" || expr === "true" || expr === "false" || expr === "null" || expr === "undefined" || /^-?\d+(\.\d+)?$/.test(expr) || (expr.startsWith('"') && expr.endsWith('"') || expr.startsWith("'") && expr.endsWith("'")) || expr.startsWith("{") || expr.startsWith("[");
277689
+ if (expr.includes("(") && expr.includes(")") && !isTypeExpr && !expr.includes("=>")) {
277705
277690
  diagnostics.push({
277706
277691
  range: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
277707
277692
  severity: "warning",
@@ -277709,7 +277694,8 @@ function validateMockDirectives(directives, diagnostics) {
277709
277694
  code: "nudo:mock-invalid",
277710
277695
  suggestions: [
277711
277696
  "Supported formats: stub(), stub().returns(value), spy(), mock()",
277712
- "Arrow functions: (args) => expression or (args) => { statements; return value; }"
277697
+ "Arrow functions: (args) => expression or (args) => { statements; return value; }",
277698
+ "Type expressions: number(), string(), shape({...}), union(...), or concrete literals"
277713
277699
  ]
277714
277700
  });
277715
277701
  }
@@ -278685,7 +278671,7 @@ function analyzeFileUncachedInner(filePath, source, activeCases, externalCallRec
278685
278671
  diagnostics.push({
278686
278672
  range: { start: { line: directive.commentLine, column: 0 }, end: { line: directive.commentLine, column: 999 } },
278687
278673
  severity: "error",
278688
- message: `Case "${directive.name}": expected ${formatShape(directive.expected)}, got ${formatShape(caseAbs)}. The inferred return type does not match the expected type declared in the @nudo:case directive`,
278674
+ message: `debug "${directive.name}": expected ${formatShape(directive.expected)}, got ${formatShape(caseAbs)}. The inferred return type does not match the expected type declared in the @nudo:case witness`,
278689
278675
  code: "nudo:case-expected"
278690
278676
  });
278691
278677
  }
@@ -281177,15 +281163,15 @@ function serializeCaseArg(a) {
281177
281163
  }
281178
281164
  switch (s.k) {
281179
281165
  case "prim":
281180
- if (s.type === "number" || s.type === "string" || s.type === "boolean") {
281181
- return `T.${s.type}`;
281182
- }
281166
+ if (s.type === "number") return "number()";
281167
+ if (s.type === "string") return "string()";
281168
+ if (s.type === "boolean") return "boolean()";
281183
281169
  return null;
281184
281170
  case "unknown":
281185
281171
  case "any":
281186
- return "T.unknown";
281172
+ return "any()";
281187
281173
  case "never":
281188
- return "T.never";
281174
+ return "never";
281189
281175
  case "sum": {
281190
281176
  const parts = [];
281191
281177
  for (const member of s.members) {
@@ -281193,11 +281179,11 @@ function serializeCaseArg(a) {
281193
281179
  if (ser === null) return null;
281194
281180
  parts.push(ser);
281195
281181
  }
281196
- return `T.union(${parts.join(", ")})`;
281182
+ return `union(${parts.join(", ")})`;
281197
281183
  }
281198
281184
  case "arr": {
281199
281185
  const el = serializeCaseArg(s.element);
281200
- return el === null ? null : `T.array(${el})`;
281186
+ return el === null ? null : `array(${el})`;
281201
281187
  }
281202
281188
  case "tuple": {
281203
281189
  const parts = [];
@@ -283843,7 +283829,7 @@ async function runInfer(file, options = {}) {
283843
283829
  }
283844
283830
  }
283845
283831
  if (result.functions.length === 0 && !result.externalFunctions?.length) {
283846
- console.log("No functions with @nudo:case directives found.");
283832
+ console.log("No functions found to analyze.");
283847
283833
  return;
283848
283834
  }
283849
283835
  const dtsLines = [];
@@ -283867,7 +283853,8 @@ async function runInfer(file, options = {}) {
283867
283853
  }
283868
283854
  for (const c of fn2.cases) {
283869
283855
  const argsStr = c.argAbs.map(formatShape).join(", ");
283870
- let line = `Case "${c.name}": (${argsStr}) => ${formatShape(c.abs)}`;
283856
+ const label = c.name.startsWith("call@") || c.name.startsWith("entry@") ? c.name : `debug "${c.name}"`;
283857
+ let line = `${label}: (${argsStr}) => ${formatShape(c.abs)}`;
283871
283858
  if (c.throwsAbs.shape.k !== "never") line += ` throws ${formatShape(c.throwsAbs)}`;
283872
283859
  console.log(line);
283873
283860
  if (c.intension?.display) {
@@ -283888,7 +283875,7 @@ async function runInfer(file, options = {}) {
283888
283875
  }
283889
283876
  if (fn2.cases.length > 1 && fn2.combinedAbs) {
283890
283877
  console.log(`
283891
- Combined: ${formatShape(fn2.combinedAbs)}`);
283878
+ Observed: ${formatShape(fn2.combinedAbs)}`);
283892
283879
  }
283893
283880
  console.log();
283894
283881
  }
@@ -283909,13 +283896,14 @@ Combined: ${formatShape(fn2.combinedAbs)}`);
283909
283896
  `);
283910
283897
  for (const c of fn2.cases) {
283911
283898
  const argsStr = c.argAbs.map(formatShape).join(", ");
283912
- let line = `Case "${c.name}": (${argsStr}) => ${formatShape(c.abs)}`;
283899
+ const label = c.name.startsWith("call@") || c.name.startsWith("entry@") ? c.name : `debug "${c.name}"`;
283900
+ let line = `${label}: (${argsStr}) => ${formatShape(c.abs)}`;
283913
283901
  if (c.throwsAbs.shape.k !== "never") line += ` throws ${formatShape(c.throwsAbs)}`;
283914
283902
  console.log(line);
283915
283903
  }
283916
283904
  if (fn2.cases.length > 1 && fn2.combinedAbs) {
283917
283905
  console.log(`
283918
- Combined: ${formatShape(fn2.combinedAbs)}`);
283906
+ Observed: ${formatShape(fn2.combinedAbs)}`);
283919
283907
  }
283920
283908
  console.log();
283921
283909
  }
@@ -283971,7 +283959,7 @@ async function runInferJson(file, externalRecords) {
283971
283959
  const { serializeInferJson: serializeInferJson2 } = await Promise.resolve().then(() => (init_src4(), src_exports2));
283972
283960
  console.log(JSON.stringify(serializeInferJson2(result, filePath), null, 2));
283973
283961
  }
283974
- program.command("infer").description("Infer types from a JS/TS file (or a directory of them) \u2014 functions with @nudo:case directives use them; all other functions are inferred from call sites (whole-program analysis)").argument("<file>", "Path to the JS/TS file (or directory)").option("--dts", "Generate .d.ts file").option("--loc", "Show source locations in output").option("--json", "Output as JSON").option("--callsites <paths...>", "Usage-site files (tests/apps) to harvest real call shapes from; their calls to this file's exports become synthesized cases").option("--emit-cases [mode]", "Write synthesized call-site cases back into the source as @nudo:case directives (call@ prefix); mode: update (default: add)").option("--dry-run", "With --emit-cases: print a unified diff instead of writing to disk").option("--exit-on-diff", "With --dry-run: exit with code 1 when the diff is non-empty").action(
283962
+ program.command("infer").description("Infer types from a JS/TS file (or a directory) by executing observed call sites; optional @nudo:case witnesses are for debug / nudo test only").argument("<file>", "Path to the JS/TS file (or directory)").option("--dts", "Generate .d.ts file").option("--loc", "Show source locations in output").option("--json", "Output as JSON").option("--callsites <paths...>", "Usage-site files (tests/apps) to harvest real call shapes from; their calls to this file's exports become synthesized cases").option("--emit-cases [mode]", "Debug solidification: write synthesized call-site witnesses back as @nudo:case directives (call@ prefix); not the interface product path. mode: update (default: add)").option("--dry-run", "With --emit-cases: print a unified diff instead of writing to disk").option("--exit-on-diff", "With --dry-run: exit with code 1 when the diff is non-empty").action(
283975
283963
  async (file, opts) => {
283976
283964
  let emit;
283977
283965
  if (opts.emitCases !== void 0) {
@@ -284513,7 +284501,7 @@ program.command("interface").alias("refine").description(
284513
284501
  }
284514
284502
  }
284515
284503
  );
284516
- program.command("test").description("Run @nudo:case directives as assertions (case-as-test); exit 1 on failure").argument("<file>", "Path to the JS/TS file or a directory of them").action(async (file) => {
284504
+ program.command("test").description("Run @nudo:case directives as debug assertions (case-as-test); exit 1 on failure").argument("<file>", "Path to the JS/TS file or a directory of them").action(async (file) => {
284517
284505
  const targets = resolveTargets(file);
284518
284506
  if (targets.length === 0) return;
284519
284507
  for (const filePath of targets) {
@@ -284657,12 +284645,12 @@ async function runDoctor(paths, opts) {
284657
284645
  const entryInfo = r.entryOnly > 0 ? `, ${r.entryOnly} entry-only` : "";
284658
284646
  console.log(` \xB7 ${r.functions} function(s)${entryInfo}`);
284659
284647
  if (r.uncovered.length > 0) {
284660
- console.log(` \u26A0 uncovered (no cases): ${r.uncovered.join(", ")}`);
284648
+ console.log(` \u26A0 uncovered (no call-site evidence): ${r.uncovered.join(", ")}`);
284661
284649
  }
284662
284650
  if (r.drift) {
284663
284651
  const refresh = `nudo infer ${r.file} --callsites ${(opts.callsites ?? []).join(" ")} --emit-cases=update`;
284664
284652
  console.log(
284665
- ` \u2717 drift: ${r.drift.added + r.drift.removed} directive(s) changed (+${r.drift.added} new, -${r.drift.removed} removed) \u2014 refresh with: ${refresh.trim()}`
284653
+ ` \u2717 drift: ${r.drift.added + r.drift.removed} debug witness directive(s) changed (+${r.drift.added} new, -${r.drift.removed} removed) \u2014 optional refresh: ${refresh.trim()}`
284666
284654
  );
284667
284655
  }
284668
284656
  if ((r.interfaceDrift ?? 0) > 0) {
@@ -284809,18 +284797,16 @@ async function runGenerate(file, format, output) {
284809
284797
  const filePath = resolve12(file);
284810
284798
  const source = readFileSync15(filePath, "utf-8");
284811
284799
  const result = await analyzeFileAsync(filePath, source);
284812
- const functions = result.functions.filter(
284813
- (f) => f.cases.some((c) => c.source === "directive")
284814
- );
284800
+ const functions = result.functions.filter((f) => f.cases.length > 0);
284815
284801
  if (functions.length === 0) {
284816
- console.log("No functions with @nudo:case directives found.");
284802
+ console.log("No analyzed functions found.");
284817
284803
  return;
284818
284804
  }
284819
284805
  const zodChunks = [];
284820
284806
  const guardChunks = [];
284821
284807
  const dtsChunks = [];
284822
284808
  for (const fn2 of functions) {
284823
- const caseResults = fn2.cases.filter((c) => c.source === "directive");
284809
+ const caseResults = fn2.cases;
284824
284810
  const baseName = fn2.name;
284825
284811
  if (format === "zod" || format === "all") {
284826
284812
  const lines = [`
@@ -284828,7 +284814,8 @@ async function runGenerate(file, format, output) {
284828
284814
  for (const c of caseResults) {
284829
284815
  const inputSchemas = c.argAbs.map((a, i) => `arg${i}: ${absToZodSchema(a)}`).join(", ");
284830
284816
  const outputSchema = absToZodSchema(c.abs);
284831
- lines.push(`// Case "${c.name}":`);
284817
+ const label = c.name.startsWith("call@") || c.name.startsWith("entry@") ? c.name : `debug "${c.name}"`;
284818
+ lines.push(`// ${label}:`);
284832
284819
  lines.push(`// Input: { ${inputSchemas} }`);
284833
284820
  lines.push(`// Output: ${outputSchema}`);
284834
284821
  }
@@ -284837,9 +284824,9 @@ async function runGenerate(file, format, output) {
284837
284824
  if (format === "guard" || format === "all") {
284838
284825
  const lines = [`
284839
284826
  // === ${baseName} Type Guards ===`];
284840
- for (const c of caseResults) {
284841
- const guardName = `is${baseName}${c.name.charAt(0).toUpperCase() + c.name.slice(1)}Output`;
284842
- lines.push(generateGuardFunctionFromAbs(guardName, c.abs));
284827
+ const absForGuard = fn2.combinedAbs ?? caseResults[0]?.abs;
284828
+ if (absForGuard) {
284829
+ lines.push(generateGuardFunctionFromAbs(`is${baseName}Output`, absForGuard));
284843
284830
  }
284844
284831
  guardChunks.push(lines.join("\n"));
284845
284832
  }
@@ -284938,7 +284925,7 @@ Usage \u2014 add this directive at the top of your JS file:`);
284938
284925
  const hintPath = resolve12(process.cwd()) === outDir ? basename5(out) : out;
284939
284926
  console.log(` /// @nudo:env ${hintPath}`);
284940
284927
  }
284941
- program.command("harvest").description("Convert @types/<pkg> .d.ts declarations into a Nudo env file (TS source using T.* constructors)").argument("[pkg]", "Package name under @types (e.g. node)").option("--out <file>", "Output .ts env file (default: ./nudo-harvest-<pkg>.ts)").option("--auto [dir]", "Scan directory (default .) for bare imports and report auto-harvestable @types packages").action(async (pkg, opts) => {
284928
+ program.command("harvest").description("Convert @types/<pkg> .d.ts declarations into a Nudo env file (constraint builders, not T.*)").argument("[pkg]", "Package name under @types (e.g. node)").option("--out <file>", "Output .ts env file (default: ./nudo-harvest-<pkg>.ts)").option("--auto [dir]", "Scan directory (default .) for bare imports and report auto-harvestable @types packages").action(async (pkg, opts) => {
284942
284929
  if (opts.auto !== void 0) {
284943
284930
  const dir = resolve12(typeof opts.auto === "string" ? opts.auto : ".");
284944
284931
  const files = existsSync16(dir) && statSync9(dir).isDirectory() ? collectNudoFiles(dir) : existsSync16(dir) ? [dir] : [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nudojs/cli",
3
- "version": "2.0.1",
3
+ "version": "3.0.0",
4
4
  "engines": {
5
5
  "node": ">=20"
6
6
  },
@@ -43,11 +43,11 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "commander": "^13.0.0",
46
- "@nudojs/parser": "0.5.1",
47
- "@nudojs/core": "2.0.1",
48
- "@nudojs/service": "3.0.0",
49
- "@nudojs/env": "0.4.0",
50
- "@nudojs/harvester": "0.2.6"
46
+ "@nudojs/core": "2.1.0",
47
+ "@nudojs/parser": "1.0.0",
48
+ "@nudojs/service": "4.0.0",
49
+ "@nudojs/env": "0.4.1",
50
+ "@nudojs/harvester": "0.2.7"
51
51
  },
52
52
  "scripts": {
53
53
  "build": "tsup"