@oprt/cli 0.3.0 → 0.4.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.
package/dist/bin.js CHANGED
@@ -396,87 +396,263 @@ var init_args = __esm({
396
396
  }
397
397
  });
398
398
 
399
+ // ../../node_modules/.pnpm/@babel+parser@8.0.8/node_modules/@babel/parser/package.json
400
+ var version;
401
+ var init_package = __esm({
402
+ "../../node_modules/.pnpm/@babel+parser@8.0.8/node_modules/@babel/parser/package.json"() {
403
+ version = "8.0.8";
404
+ }
405
+ });
406
+
399
407
  // src/parser.ts
400
408
  import { createRequire } from "module";
401
- import { join } from "path";
402
409
  import { fileURLToPath as fileURLToPath2 } from "url";
403
- function usable(typescript) {
404
- const ts = typescript;
405
- if (typeof ts.createSourceFile !== "function" || ts.ScriptKind === void 0)
406
- return `TypeScript ${ts.version ?? "(unknown version)"} has no JavaScript syntax API (createSourceFile), which oprt reads code with; ${SUPPORTED_TYPESCRIPT} has it`;
410
+ function optionsFor(file) {
411
+ const typed = /\.tsx$/.test(file);
412
+ const plugins = [
413
+ "jsx",
414
+ ...typed ? ["typescript"] : [],
415
+ "decorators",
416
+ "decoratorAutoAccessors",
417
+ "deferredImportEvaluation",
418
+ "sourcePhaseImports"
419
+ ];
420
+ return {
421
+ sourceType: "module",
422
+ plugins,
423
+ // Recoverable errors are returned beside the tree, instead of the first one being thrown.
424
+ errorRecovery: true,
425
+ attachComment: false,
426
+ allowReturnOutsideFunction: true,
427
+ allowUndeclaredExports: true,
428
+ allowSuperOutsideMethod: true,
429
+ allowNewTargetOutsideFunction: true
430
+ };
431
+ }
432
+ function loadBabel() {
433
+ if (loaded !== void 0) return loaded;
434
+ const fromSource = /\.[cm]?tsx?$/.test(fileURLToPath2(import.meta.url));
407
435
  try {
408
- const probe = ts.createSourceFile(
409
- "probe.tsx",
410
- "<a>",
411
- ts.ScriptTarget?.Latest ?? 99,
412
- true,
413
- ts.ScriptKind.TSX
414
- );
415
- const diagnostics = probe.parseDiagnostics;
416
- if (!Array.isArray(diagnostics)) return "it does not report parse errors on a source file";
417
- if (diagnostics.length === 0) return "it did not report a syntax error in code that has one";
436
+ const require2 = createRequire(import.meta.url);
437
+ loaded = fromSource ? require2("@babel/parser") : require2("./babel-parser.cjs");
418
438
  } catch (error) {
419
- return `it failed on a probe parse: ${error instanceof Error ? error.message : String(error)}`;
439
+ throw new CliError(
440
+ `Code checking cannot load its parser (${FILE}): ${error instanceof Error ? error.message : String(error)} ${REINSTALL}`,
441
+ 1,
442
+ { code: "ERR_PARSER_MISSING" }
443
+ );
420
444
  }
421
- return null;
445
+ return loaded;
422
446
  }
423
- function probeTypeScript(cwd, options = {}) {
424
- const places = [
425
- { from: join(cwd, "package.json"), source: "project" },
426
- ...options.installer === false ? [] : [{ from: fileURLToPath2(import.meta.url), source: "installer" }]
427
- ];
428
- let unusable;
429
- for (const { from, source } of places) {
430
- let file;
431
- let loaded2;
432
- try {
433
- const require2 = createRequire(from);
434
- file = require2.resolve("typescript");
435
- loaded2 = require2("typescript");
436
- } catch (error) {
437
- if (error.code !== "MODULE_NOT_FOUND")
438
- unusable ??= `typescript was found but failed to load: ${error instanceof Error ? error.message : String(error)}`;
439
- continue;
440
- }
441
- const typescript = loaded2.default ?? loaded2;
442
- const problem = usable(typescript);
443
- if (problem !== null) {
444
- unusable ??= `${problem} (at ${packageFolder(file)}).`;
445
- continue;
446
- }
447
+ function probeParser(load = loadBabel) {
448
+ let babel;
449
+ try {
450
+ babel = load();
451
+ } catch (error) {
447
452
  return {
448
- ready: true,
449
- typescript,
450
- resolution: {
451
- name: "typescript",
452
- version: typescript.version,
453
- source,
454
- resolved_from: packageFolder(file)
455
- }
453
+ ready: false,
454
+ reason: "parser_missing",
455
+ detail: error instanceof Error ? error.message : String(error)
456
456
  };
457
457
  }
458
- if (unusable !== void 0)
459
- return { ready: false, reason: "typescript_unusable", detail: unusable };
460
- return {
461
- ready: false,
462
- reason: "typescript_missing",
463
- detail: `typescript could not be resolved from ${cwd}${options.installer === false ? "" : " or from beside the installer"}.`
464
- };
458
+ if (rejects(babel, "export const a = <a b={1 as number} />;"))
459
+ return {
460
+ ready: false,
461
+ reason: "parser_unusable",
462
+ detail: "it rejected valid code in a probe parse"
463
+ };
464
+ if (!rejects(babel, "export const a = <a>;"))
465
+ return {
466
+ ready: false,
467
+ reason: "parser_unusable",
468
+ detail: "it did not report a syntax error in code that has one"
469
+ };
470
+ return { ready: true, parser: PARSER };
465
471
  }
466
- var SUPPORTED_TYPESCRIPT, INSTALL_TYPESCRIPT, packageFolder;
472
+ var PARSER, FILE, REINSTALL, loaded, rejects;
467
473
  var init_parser = __esm({
468
474
  "src/parser.ts"() {
469
475
  "use strict";
470
476
  init_esm_shims();
471
- SUPPORTED_TYPESCRIPT = "TypeScript 5 or 6";
472
- INSTALL_TYPESCRIPT = "npm install -D typescript@6";
473
- packageFolder = (file) => /^(.*[\\/]node_modules[\\/]typescript)[\\/]/.exec(file)?.[1] ?? file;
477
+ init_package();
478
+ init_types();
479
+ PARSER = {
480
+ name: "@babel/parser",
481
+ version,
482
+ source: "bundled"
483
+ };
484
+ FILE = "babel-parser.cjs";
485
+ REINSTALL = "Install @oprt/cli again: the parser ships beside the executable in dist.";
486
+ rejects = (babel, code) => {
487
+ try {
488
+ return babel.parse(code, optionsFor("probe.tsx")).errors.length > 0;
489
+ } catch {
490
+ return true;
491
+ }
492
+ };
493
+ }
494
+ });
495
+
496
+ // src/syntax.ts
497
+ function* childNodes(node) {
498
+ for (const [key, value] of Object.entries(node)) {
499
+ if (NOT_NODES.has(key) || typeof value !== "object" || value === null) continue;
500
+ if (Array.isArray(value)) {
501
+ for (const item of value)
502
+ if (typeof item === "object" && item !== null && typeof item.type === "string")
503
+ yield item;
504
+ } else if (typeof value.type === "string") yield value;
505
+ }
506
+ }
507
+ function problemsOf(tree, errors, limit, consumed) {
508
+ const mismatched = /* @__PURE__ */ new Map();
509
+ walk(tree.program, [], (node, ancestors) => {
510
+ if (node.type === "JSXElement" && node.closingElement && (node.closingElement.start ?? 0) < limit && tagName(node.openingElement.name) !== tagName(node.closingElement.name))
511
+ mismatched.set(node.closingElement.start ?? 0, { element: node, ancestors: [...ancestors] });
512
+ });
513
+ const problems = [];
514
+ for (const error of errors) {
515
+ if (error.pos >= limit) continue;
516
+ const entry = error.reasonCode === "MissingClosingTagElement" && mismatched.get(error.pos);
517
+ if (entry) {
518
+ const closing = tagName(entry.element.closingElement?.name);
519
+ const outer = [...entry.ancestors].reverse().find(
520
+ (ancestor) => ancestor.type === "JSXElement" && tagName(ancestor.openingElement.name) === closing && !consumed.has(ancestor)
521
+ );
522
+ if (outer !== void 0) {
523
+ consumed.add(outer);
524
+ problems.push(unclosed(entry.element));
525
+ continue;
526
+ }
527
+ }
528
+ problems.push(problemOf(error));
529
+ }
530
+ return problems;
531
+ }
532
+ function complete(babel, prefix, file) {
533
+ let text2 = prefix;
534
+ let first = null;
535
+ for (let step = 0; step <= MAX_COMPLETIONS; step++) {
536
+ const tried = attempt(babel, text2, file);
537
+ if (tried.tree !== null) return { tree: tried.tree, errors: tried.errors, first };
538
+ const wantsMore = tried.fatal.reasonCode === "UnterminatedJsxContent" || tried.fatal.pos >= text2.length;
539
+ if (!wantsMore) return null;
540
+ const next = COMPLETIONS.find((piece) => {
541
+ const extended = attempt(babel, text2 + piece, file);
542
+ return extended.tree !== null || extended.fatal.reasonCode === "UnterminatedJsxContent" || extended.fatal.pos >= text2.length + piece.length;
543
+ });
544
+ if (next === void 0) return null;
545
+ first ??= next;
546
+ text2 += next;
547
+ }
548
+ return null;
549
+ }
550
+ function shutByCompletion(tree, limit) {
551
+ const shut = [];
552
+ walk(tree.program, [], (node) => {
553
+ if (node.type === "JSXElement" && node.closingElement && (node.closingElement.start ?? 0) >= limit)
554
+ shut.push(node);
555
+ });
556
+ return shut.sort((a, b) => (a.closingElement?.start ?? 0) - (b.closingElement?.start ?? 0));
557
+ }
558
+ function readSyntax(file, text2, babel = loadBabel()) {
559
+ const first = attempt(babel, text2, file);
560
+ if (first.tree !== null) {
561
+ const problems = problemsOf(first.tree, first.errors, Infinity, /* @__PURE__ */ new Set());
562
+ return problems.length === 0 ? { tree: first.tree, problems: [] } : { tree: null, problems: problems.sort(byPlace) };
563
+ }
564
+ let fatal = first.fatal;
565
+ if (text2.length > MAX_EXPLAINED) return { tree: null, problems: [problemOf(fatal)] };
566
+ if (/\.tsx$/.test(file) && fatal.reasonCode !== "UnterminatedJsxContent") {
567
+ const plain = text2.replace(TYPE_ARGUMENTS, blankedOut);
568
+ if (plain !== text2) {
569
+ const again = attempt(babel, plain, file);
570
+ if (again.tree === null && again.fatal.pos > fatal.pos) fatal = again.fatal;
571
+ }
572
+ }
573
+ try {
574
+ const finished = complete(babel, text2.slice(0, fatal.pos), file);
575
+ if (finished !== null) {
576
+ const limit = fatal.pos;
577
+ const consumed = /* @__PURE__ */ new Set();
578
+ const problems = problemsOf(finished.tree, finished.errors, limit, consumed);
579
+ const open = shutByCompletion(finished.tree, limit).filter(
580
+ (element) => !consumed.has(element)
581
+ );
582
+ const atEnd = fatal.reasonCode === "UnterminatedJsxContent" || fatal.pos >= text2.length;
583
+ const hints = (atEnd ? open : finished.first === CLOSING_TAG ? open.slice(0, 1) : []).map(
584
+ unclosed
585
+ );
586
+ const explained = [...problems, ...hints].some(({ code }) => code === "UnclosedJsxElement");
587
+ problems.push(...hints);
588
+ if (!(explained && fatal.reasonCode === "UnterminatedJsxContent"))
589
+ problems.push(problemOf(fatal));
590
+ return { tree: null, problems: problems.sort(byPlace) };
591
+ }
592
+ } catch {
593
+ }
594
+ return { tree: null, problems: [problemOf(fatal)] };
595
+ }
596
+ var attempt, sentence, problemOf, tagName, NOT_NODES, walk, unclosed, TYPE_ARGUMENTS, LINE_BREAKS, blankedOut, CLOSING_TAG, COMPLETIONS, MAX_COMPLETIONS, byPlace, MAX_EXPLAINED;
597
+ var init_syntax = __esm({
598
+ "src/syntax.ts"() {
599
+ "use strict";
600
+ init_esm_shims();
601
+ init_parser();
602
+ attempt = (babel, text2, file) => {
603
+ try {
604
+ const tree = babel.parse(text2, optionsFor(file));
605
+ return { tree, errors: tree.errors };
606
+ } catch (error) {
607
+ return { tree: null, fatal: error };
608
+ }
609
+ };
610
+ sentence = (message) => message.replace(/\s*\(\d+:\d+\)$/, "");
611
+ problemOf = (failure2) => ({
612
+ line: failure2.loc.line,
613
+ column: failure2.loc.column + 1,
614
+ message: sentence(failure2.message),
615
+ code: failure2.reasonCode ?? "SyntaxError",
616
+ position: failure2.pos
617
+ });
618
+ tagName = (name) => {
619
+ if (name === void 0) return "";
620
+ if (name.type === "JSXIdentifier") return name.name;
621
+ if (name.type === "JSXMemberExpression")
622
+ return `${tagName(name.object)}.${name.property.name}`;
623
+ return `${name.namespace.name}:${name.name.name}`;
624
+ };
625
+ NOT_NODES = /* @__PURE__ */ new Set(["loc", "extra", "errors", "tokens", "comments"]);
626
+ walk = (node, ancestors, visit) => {
627
+ visit(node, ancestors);
628
+ ancestors.push(node);
629
+ for (const child of childNodes(node)) walk(child, ancestors, visit);
630
+ ancestors.pop();
631
+ };
632
+ unclosed = (element) => {
633
+ const name = element.openingElement.name;
634
+ return {
635
+ line: name.loc?.start.line ?? 1,
636
+ column: (name.loc?.start.column ?? 0) + 1,
637
+ message: `JSX element '${tagName(name)}' has no corresponding closing tag.`,
638
+ code: "UnclosedJsxElement",
639
+ position: name.start ?? 0
640
+ };
641
+ };
642
+ TYPE_ARGUMENTS = /(?<=[\w$\])])<(?:[^<>()]|<(?:[^<>()]|<[^<>()]*>)*>)*>(?=\()/g;
643
+ LINE_BREAKS = new RegExp(`[^\\r\\n${String.fromCharCode(8232, 8233)}]`, "g");
644
+ blankedOut = (match) => match.replace(LINE_BREAKS, " ");
645
+ CLOSING_TAG = "</_>";
646
+ COMPLETIONS = [CLOSING_TAG, ")", "}", "]"];
647
+ MAX_COMPLETIONS = 24;
648
+ byPlace = (a, b) => a.position - b.position;
649
+ MAX_EXPLAINED = 2e5;
474
650
  }
475
651
  });
476
652
 
477
653
  // src/check.ts
478
654
  import { existsSync, readdirSync, readFileSync, statSync } from "fs";
479
- import { basename, extname, join as join2, relative, resolve, sep } from "path";
655
+ import { basename, extname, join, relative, resolve, sep } from "path";
480
656
  function allowedChildren2(rules, name) {
481
657
  for (const [parent, rule] of Object.entries(rules ?? {})) {
482
658
  if (parent === name) return rule.allowed_children;
@@ -485,52 +661,43 @@ function allowedChildren2(rules, name) {
485
661
  }
486
662
  return void 0;
487
663
  }
488
- function checkSource(typescript, contract2, file, text2) {
489
- const ts = typescript;
490
- const kind = /\.tsx$/.test(file) ? ts.ScriptKind.TSX : ts.ScriptKind.JSX;
491
- const source = ts.createSourceFile(file, text2, ts.ScriptTarget.Latest, true, kind);
492
- const parseDiagnostics = source.parseDiagnostics;
493
- if (!Array.isArray(parseDiagnostics))
494
- throw new CliError(
495
- `This TypeScript does not report syntax errors on a parsed file, so a file that does not parse could be called clean. Install TypeScript 5 or 6 in this project (${INSTALL_TYPESCRIPT}).`,
496
- 1,
497
- { code: "ERR_TYPESCRIPT_UNUSABLE" }
498
- );
499
- if (parseDiagnostics.length > 0) {
500
- const ordered = [...parseDiagnostics].sort((a, b) => (a.start ?? 0) - (b.start ?? 0));
501
- const shown = ordered.slice(0, MAX_SYNTAX_ERRORS);
664
+ function checkSource(contract2, file, text2) {
665
+ const reading = readSyntax(file, text2);
666
+ if (reading.tree === null) {
667
+ const shown = reading.problems.slice(0, MAX_SYNTAX_ERRORS);
668
+ const more = reading.problems.length - shown.length;
502
669
  return {
503
670
  file,
504
671
  status: "not_evaluated",
505
672
  limitations: [],
506
- findings: shown.map((diagnostic, index) => {
507
- const { line, character } = source.getLineAndCharacterOfPosition(diagnostic.start ?? 0);
508
- const more = ordered.length - shown.length;
509
- return {
673
+ findings: shown.map(
674
+ ({ line, column, message, code }, index) => ({
510
675
  code: "SYNTAX_ERROR",
511
676
  severity: "error",
512
677
  file,
513
- line: line + 1,
514
- column: character + 1,
515
- message: `${ts.flattenDiagnosticMessageText(diagnostic.messageText, " ")} (TS${diagnostic.code})` + (index === shown.length - 1 && more > 0 ? ` ${more} more syntax error${more === 1 ? "" : "s"} in this file not shown.` : "")
516
- };
517
- })
678
+ line,
679
+ column,
680
+ message: `${message} (${code})` + (index === shown.length - 1 && more > 0 ? ` ${more} more syntax error${more === 1 ? "" : "s"} in this file not shown.` : "")
681
+ })
682
+ )
518
683
  };
519
684
  }
520
685
  const findings = [];
521
686
  const limitations = [];
687
+ const place = (node) => ({
688
+ line: node.loc?.start.line ?? 1,
689
+ column: (node.loc?.start.column ?? 0) + 1
690
+ });
691
+ const textOf = (node) => text2.slice(node.start ?? 0, node.end ?? 0);
522
692
  const limit = (node, limitation) => {
523
- const { line, character } = source.getLineAndCharacterOfPosition(node.getStart(source));
524
- limitations.push({ ...limitation, file, line: line + 1, column: character + 1 });
693
+ limitations.push({ ...limitation, file, ...place(node) });
525
694
  };
526
695
  const report = (node, finding) => {
527
- const { line, character } = source.getLineAndCharacterOfPosition(node.getStart(source));
528
696
  findings.push({
529
697
  code: finding.code,
530
698
  severity: finding.severity,
531
699
  file,
532
- line: line + 1,
533
- column: character + 1,
700
+ ...place(node),
534
701
  ...finding.component === void 0 ? {} : { component: finding.component },
535
702
  ...finding.prop === void 0 ? {} : { prop: finding.prop },
536
703
  message: finding.message,
@@ -541,21 +708,18 @@ function checkSource(typescript, contract2, file, text2) {
541
708
  const isComponent = (name) => Object.hasOwn(contract2.components, name);
542
709
  const named = /* @__PURE__ */ new Map();
543
710
  const namespaces = /* @__PURE__ */ new Set();
544
- for (const statement of source.statements) {
545
- if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier))
546
- continue;
547
- const from = statement.moduleSpecifier.text;
548
- const clause = statement.importClause;
549
- if (!clause || clause.isTypeOnly || !clause.namedBindings) continue;
711
+ for (const statement of reading.tree.program.body) {
712
+ if (statement.type !== "ImportDeclaration" || statement.importKind === "type") continue;
713
+ const from = statement.source.value;
550
714
  const copied = from !== PACKAGE && /[./@]/.test(from[0] ?? "") && isComponent(basename(from));
551
715
  if (from !== PACKAGE && !copied) continue;
552
- if (ts.isNamespaceImport(clause.namedBindings)) {
553
- if (from === PACKAGE) namespaces.add(clause.namedBindings.name.text);
554
- continue;
555
- }
556
- for (const element2 of clause.namedBindings.elements) {
557
- if (element2.isTypeOnly) continue;
558
- const exported = (element2.propertyName ?? element2.name).text;
716
+ for (const element2 of statement.specifiers) {
717
+ if (element2.type === "ImportNamespaceSpecifier") {
718
+ if (from === PACKAGE) namespaces.add(element2.local.name);
719
+ continue;
720
+ }
721
+ if (element2.type !== "ImportSpecifier" || element2.importKind === "type") continue;
722
+ const exported = element2.imported.type === "Identifier" ? element2.imported.name : element2.imported.value;
559
723
  if (from === PACKAGE && !contract2.exports.includes(exported)) {
560
724
  let suggestions = nearest(exported, Object.keys(contract2.components));
561
725
  if (suggestions.length === 0) suggestions = nearest(exported, contract2.exports);
@@ -580,51 +744,55 @@ function checkSource(typescript, contract2, file, text2) {
580
744
  });
581
745
  continue;
582
746
  }
583
- if (isComponent(exported)) named.set(element2.name.text, exported);
747
+ if (isComponent(exported)) named.set(element2.local.name, exported);
584
748
  }
585
749
  }
750
+ const isString = (node) => node.type === "StringLiteral";
751
+ const plainTemplate = (node) => node.type === "TemplateLiteral" && node.expressions.length === 0 ? node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw : void 0;
586
752
  const literals = (expression) => {
587
753
  if (!expression) return [];
588
- if (ts.isStringLiteral(expression) || ts.isNoSubstitutionTemplateLiteral(expression))
589
- return [expression.text];
590
- if (ts.isParenthesizedExpression(expression)) return literals(expression.expression);
591
- if (ts.isConditionalExpression(expression))
592
- return [...literals(expression.whenTrue), ...literals(expression.whenFalse)];
754
+ if (isString(expression)) return [expression.value];
755
+ const template = plainTemplate(expression);
756
+ if (template !== void 0) return [template];
757
+ if (expression.type === "ConditionalExpression")
758
+ return [...literals(expression.consequent), ...literals(expression.alternate)];
593
759
  return [];
594
760
  };
595
- const valueOf = (attribute) => attribute.initializer === void 0 ? void 0 : ts.isJsxExpression(attribute.initializer) ? attribute.initializer.expression : attribute.initializer;
761
+ const valueOf = (attribute) => {
762
+ const value = attribute.value;
763
+ if (value === null || value === void 0) return void 0;
764
+ if (value.type === "JSXExpressionContainer")
765
+ return value.expression.type === "JSXEmptyExpression" ? void 0 : value.expression;
766
+ return value;
767
+ };
596
768
  const optionValue = (expression) => {
597
769
  const read = [];
598
770
  const unread = [];
599
- const walk = (node) => {
600
- if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node))
601
- read.push(node.text);
602
- else if (ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isNonNullExpression(node) || ts.isTypeAssertionExpression(node) || typeof ts.isSatisfiesExpression === "function" && ts.isSatisfiesExpression(node))
603
- walk(node.expression);
604
- else if (ts.isConditionalExpression(node)) {
605
- walk(node.whenTrue);
606
- walk(node.whenFalse);
607
- } else if (ts.isBinaryExpression(node)) {
608
- const operator = node.operatorToken.kind;
609
- if (operator === ts.SyntaxKind.QuestionQuestionToken || operator === ts.SyntaxKind.BarBarToken) {
610
- walk(node.left);
611
- walk(node.right);
612
- } else if (operator === ts.SyntaxKind.AmpersandAmpersandToken) walk(node.right);
613
- else unread.push(node);
771
+ const walk2 = (node) => {
772
+ const template = plainTemplate(node);
773
+ if (isString(node)) read.push(node.value);
774
+ else if (template !== void 0) read.push(template);
775
+ else if (node.type === "TSAsExpression" || node.type === "TSNonNullExpression" || node.type === "TSTypeAssertion" || node.type === "TSSatisfiesExpression")
776
+ walk2(node.expression);
777
+ else if (node.type === "ConditionalExpression") {
778
+ walk2(node.consequent);
779
+ walk2(node.alternate);
780
+ } else if (node.type === "LogicalExpression") {
781
+ if (node.operator === "??" || node.operator === "||") {
782
+ walk2(node.left);
783
+ walk2(node.right);
784
+ } else walk2(node.right);
614
785
  } else if (!// No value: the component's default applies.
615
- (ts.isIdentifier(node) && node.text === "undefined" || node.kind === ts.SyntaxKind.NullKeyword))
786
+ (node.type === "Identifier" && node.name === "undefined" || node.type === "NullLiteral"))
616
787
  unread.push(node);
617
788
  };
618
- if (expression) walk(expression);
789
+ if (expression) walk2(expression);
619
790
  return { literals: read, unread };
620
791
  };
621
792
  const classText = (expression) => {
622
793
  if (!expression) return "";
623
- if (ts.isTemplateExpression(expression))
624
- return [
625
- expression.head.text,
626
- ...expression.templateSpans.map((span) => span.literal.text)
627
- ].join(" ");
794
+ if (expression.type === "TemplateLiteral" && expression.expressions.length > 0)
795
+ return expression.quasis.map((span) => span.value.cooked ?? span.value.raw).join(" ");
628
796
  return literals(expression).join(" ");
629
797
  };
630
798
  const houseRules = (attribute, name) => {
@@ -650,9 +818,9 @@ function checkSource(typescript, contract2, file, text2) {
650
818
  message: "A rounded corner. The radius is 0 everywhere in OPERATOR."
651
819
  });
652
820
  }
653
- if (name === "style" && value && ts.isObjectLiteralExpression(value)) {
821
+ if (name === "style" && value && value.type === "ObjectExpression") {
654
822
  for (const property of value.properties)
655
- if (ts.isPropertyAssignment(property) && literals(property.initializer).some((literal) => RAW_COLOR_VALUE.test(literal)))
823
+ if (property.type === "ObjectProperty" && literals(property.value).some((literal) => RAW_COLOR_VALUE.test(literal)))
656
824
  report(attribute, {
657
825
  code: "RAW_COLOR",
658
826
  severity: "warning",
@@ -660,47 +828,49 @@ function checkSource(typescript, contract2, file, text2) {
660
828
  });
661
829
  }
662
830
  };
831
+ const attributeName = (attribute) => attribute.name.type === "JSXIdentifier" ? attribute.name.name : textOf(attribute.name);
663
832
  const element = (opening, ancestors, parent, hasChildren, renderedAs) => {
664
- const tag = opening.tagName;
833
+ const tag = opening.name;
665
834
  let component = null;
666
- if (ts.isIdentifier(tag)) {
667
- component = named.get(tag.text) ?? null;
668
- if (component === null && !renderedAs && Object.hasOwn(NATIVE, tag.text))
835
+ if (tag.type === "JSXIdentifier") {
836
+ component = named.get(tag.name) ?? null;
837
+ if (component === null && !renderedAs && Object.hasOwn(NATIVE, tag.name))
669
838
  report(opening, {
670
839
  code: "NATIVE_CONTROL",
671
840
  severity: "warning",
672
- message: `A native <${tag.text}>: use ${NATIVE[tag.text]}.`
841
+ message: `A native <${tag.name}>: use ${NATIVE[tag.name]}.`
673
842
  });
674
- } else if (ts.isPropertyAccessExpression(tag) && ts.isIdentifier(tag.expression)) {
675
- const owner = tag.expression.text;
676
- if (namespaces.has(owner) && isComponent(tag.name.text)) component = tag.name.text;
843
+ } else if (tag.type === "JSXMemberExpression" && tag.object.type === "JSXIdentifier") {
844
+ const owner = tag.object.name;
845
+ const member = tag.property.name;
846
+ if (namespaces.has(owner) && isComponent(member)) component = member;
677
847
  else if (named.has(owner)) {
678
- const joined = `${named.get(owner)}${tag.name.text}`;
848
+ const joined = `${named.get(owner)}${member}`;
679
849
  report(opening, {
680
850
  code: "DOT_NOTATION",
681
851
  severity: "error",
682
- message: `${owner}.${tag.name.text} is not a component.` + (isComponent(joined) ? ` Use <${joined}>.` : " OPERATOR's parts are components of their own, imported by name.")
852
+ message: `${owner}.${member} is not a component.` + (isComponent(joined) ? ` Use <${joined}>.` : " OPERATOR's parts are components of their own, imported by name.")
683
853
  });
684
854
  }
685
855
  }
686
- const attributes = opening.attributes.properties;
856
+ const attributes = opening.attributes;
687
857
  for (const attribute of attributes)
688
- if (ts.isJsxAttribute(attribute) && ts.isIdentifier(attribute.name))
689
- houseRules(attribute, attribute.name.text);
858
+ if (attribute.type === "JSXAttribute" && attribute.name.type === "JSXIdentifier")
859
+ houseRules(attribute, attribute.name.name);
690
860
  if (component === null) return null;
691
861
  const definition = contract2.components[component];
692
- const spread = attributes.some((attribute) => ts.isJsxSpreadAttribute(attribute));
862
+ const spread = attributes.some((attribute) => attribute.type === "JSXSpreadAttribute");
693
863
  for (const attribute of attributes)
694
- if (ts.isJsxSpreadAttribute(attribute))
864
+ if (attribute.type === "JSXSpreadAttribute")
695
865
  limit(attribute, {
696
866
  code: "UNRESOLVED_SPREAD",
697
867
  component,
698
- message: `Props spread into <${component}> (${quote(attribute.getText(source))}) were not read: option values and required props that arrive through them were not checked.`
868
+ message: `Props spread into <${component}> (${quote(textOf(attribute))}) were not read: option values and required props that arrive through them were not checked.`
699
869
  });
700
870
  const given = /* @__PURE__ */ new Set();
701
871
  for (const attribute of attributes) {
702
- if (!ts.isJsxAttribute(attribute)) continue;
703
- const prop = ts.isIdentifier(attribute.name) ? attribute.name.text : attribute.name.getText(source);
872
+ if (attribute.type !== "JSXAttribute") continue;
873
+ const prop = attributeName(attribute);
704
874
  given.add(prop);
705
875
  const known = Object.hasOwn(definition.props, prop) ? definition.props[prop] : void 0;
706
876
  if (known === void 0) {
@@ -722,7 +892,7 @@ function checkSource(typescript, contract2, file, text2) {
722
892
  code: "UNRESOLVED_VALUE",
723
893
  component,
724
894
  prop,
725
- message: `${component} ${prop} is set from an expression (${quote(expression.getText(source))}) whose value is not in the source, so it was not compared with the options.`
895
+ message: `${component} ${prop} is set from an expression (${quote(textOf(expression))}) whose value is not in the source, so it was not compared with the options.`
726
896
  });
727
897
  for (const literal of options) {
728
898
  if (known.values.includes(literal)) continue;
@@ -771,41 +941,40 @@ function checkSource(typescript, contract2, file, text2) {
771
941
  return component;
772
942
  };
773
943
  const visit = (node, ancestors, parent, renderedAs = false) => {
774
- if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
775
- const opening = ts.isJsxElement(node) ? node.openingElement : node;
776
- const hasChildren = ts.isJsxElement(node) && node.children.some(
777
- (child) => !(ts.isJsxText(child) && child.containsOnlyTriviaWhiteSpaces)
944
+ if (node.type === "JSXElement") {
945
+ const opening = node.openingElement;
946
+ const hasChildren = node.children.some(
947
+ (child) => !(child.type === "JSXText" && /^\s*$/.test(text2.slice(child.start ?? 0, child.end ?? 0)))
778
948
  );
779
949
  const component = element(opening, ancestors, parent, hasChildren, renderedAs);
780
- const asChild = component !== null && opening.attributes.properties.some(
781
- (attribute) => ts.isJsxAttribute(attribute) && attribute.name.getText(source) === "asChild"
950
+ const asChild = component !== null && opening.attributes.some(
951
+ (attribute) => attribute.type === "JSXAttribute" && attributeName(attribute) === "asChild"
782
952
  );
783
- for (const attribute of opening.attributes.properties) visit(attribute, [], null);
784
- if (ts.isJsxElement(node))
785
- for (const child of node.children)
786
- visit(
787
- child,
788
- component === null ? ancestors : [...ancestors, component],
789
- component,
790
- asChild
791
- );
953
+ for (const attribute of opening.attributes) visit(attribute, [], null);
954
+ for (const child of node.children)
955
+ visit(
956
+ child,
957
+ component === null ? ancestors : [...ancestors, component],
958
+ component,
959
+ asChild
960
+ );
792
961
  return;
793
962
  }
794
- ts.forEachChild(node, (child) => visit(child, ancestors, parent));
963
+ for (const child of childNodes(node)) visit(child, ancestors, parent);
795
964
  };
796
- visit(source, [], null);
797
- const byPlace = (a, b) => a.line - b.line || a.column - b.column;
965
+ visit(reading.tree.program, [], null);
966
+ const byPlace2 = (a, b) => a.line - b.line || a.column - b.column;
798
967
  return {
799
968
  file,
800
969
  status: limitations.length > 0 ? "partial" : "evaluated",
801
- findings: findings.sort(byPlace),
802
- limitations: limitations.sort(byPlace)
970
+ findings: findings.sort(byPlace2),
971
+ limitations: limitations.sort(byPlace2)
803
972
  };
804
973
  }
805
974
  function* sources(path2) {
806
975
  if (excluded(basename(path2))) return;
807
976
  if (statSync(path2).isDirectory()) {
808
- for (const entry of readdirSync(path2).sort()) yield* sources(join2(path2, entry));
977
+ for (const entry of readdirSync(path2).sort()) yield* sources(join(path2, entry));
809
978
  } else if (READ.has(extname(path2)) && !NOT_INTERFACE.test(basename(path2))) yield path2;
810
979
  }
811
980
  function whyNotRead(path2) {
@@ -815,7 +984,7 @@ function whyNotRead(path2) {
815
984
  if (!READ.has(extname(path2))) return "unsupported_extension";
816
985
  return NOT_INTERFACE.test(name) ? "test_or_story_file" : null;
817
986
  }
818
- function buildCheckReport(evaluations, context) {
987
+ function buildCheckReport(evaluations, context = {}) {
819
988
  const findings = evaluations.flatMap(({ findings: found }) => found);
820
989
  const count = (status2) => evaluations.filter((evaluation) => evaluation.status === status2).length;
821
990
  const partial = count("partial");
@@ -842,7 +1011,7 @@ function buildCheckReport(evaluations, context) {
842
1011
  limitations: evaluations.flatMap(({ limitations }) => limitations),
843
1012
  skipped_inputs: context.skipped ?? [],
844
1013
  scope: { checked: [...SCOPE.checked], not_checked: [...SCOPE.not_checked] },
845
- parser: context.parser
1014
+ parser: context.parser ?? PARSER
846
1015
  };
847
1016
  }
848
1017
  function checkExitCode(report, { strict }) {
@@ -850,24 +1019,6 @@ function checkExitCode(report, { strict }) {
850
1019
  if (report.summary.errors > 0 || report.coverage.files_not_evaluated > 0) return 1;
851
1020
  return strict && report.summary.warnings > 0 ? 1 : 0;
852
1021
  }
853
- function resolveParser(cwd, provided, who) {
854
- if (provided !== void 0 && provided !== null)
855
- return { typescript: provided, parser: describeProvided(provided) };
856
- const probe = provided === null ? null : probeTypeScript(cwd);
857
- if (probe?.ready) {
858
- const { name, version, source } = probe.resolution;
859
- return { typescript: probe.typescript, parser: { name, version, source } };
860
- }
861
- if (probe !== null && probe.reason === "typescript_unusable")
862
- throw new CliError(`${who} cannot use the TypeScript it found: ${probe.detail}`, 1, {
863
- code: "ERR_TYPESCRIPT_UNUSABLE"
864
- });
865
- throw new CliError(
866
- `${who} reads code with the project's TypeScript, and found none. Install typescript in this project (${INSTALL_TYPESCRIPT}) and run it again. It looked from ${cwd}.`,
867
- 1,
868
- { code: "ERR_TYPESCRIPT_MISSING" }
869
- );
870
- }
871
1022
  function checkPaths(paths, context) {
872
1023
  const root = resolve(context.cwd);
873
1024
  const files = [];
@@ -880,16 +1031,15 @@ function checkPaths(paths, context) {
880
1031
  if (reason !== null) skipped.push({ path: path2, reason });
881
1032
  files.push(...sources(absolute));
882
1033
  }
883
- const { typescript, parser } = resolveParser(root, context.typescript, "oprt check");
1034
+ loadBabel();
884
1035
  const evaluations = [...new Set(files)].map(
885
1036
  (file) => checkSource(
886
- typescript,
887
1037
  context.contract,
888
1038
  relative(root, file).split(sep).join("/"),
889
1039
  readFileSync(file, "utf8")
890
1040
  )
891
1041
  );
892
- return buildCheckReport(evaluations, { parser, skipped });
1042
+ return buildCheckReport(evaluations, { skipped });
893
1043
  }
894
1044
  function renderCheckText(report) {
895
1045
  const lines = report.findings.map(
@@ -926,15 +1076,15 @@ function renderCheckText(report) {
926
1076
  return `${lines.join("\n")}
927
1077
  `;
928
1078
  }
929
- var FINDING_CODES, LIMITATION_CODES, SCOPE, PACKAGE, NATIVE, NATIVE_ATTRIBUTES, nativeAttribute, RAW_COLOR_CLASS, RAW_COLOR_VALUE, ARBITRARY_SIZE, ROUNDED_CORNER, quote, MAX_SYNTAX_ERRORS, SKIPPED, READ, NOT_INTERFACE, excluded, describeProvided, plural, MAX_LIMITS_SHOWN;
1079
+ var FINDING_CODES, LIMITATION_CODES, SCOPE, PACKAGE, NATIVE, NATIVE_ATTRIBUTES, nativeAttribute, RAW_COLOR_CLASS, RAW_COLOR_VALUE, ARBITRARY_SIZE, ROUNDED_CORNER, quote, MAX_SYNTAX_ERRORS, SKIPPED, READ, NOT_INTERFACE, excluded, plural, MAX_LIMITS_SHOWN;
930
1080
  var init_check = __esm({
931
1081
  "src/check.ts"() {
932
1082
  "use strict";
933
1083
  init_esm_shims();
934
1084
  init_lookup();
935
1085
  init_parser();
1086
+ init_syntax();
936
1087
  init_types();
937
- init_parser();
938
1088
  FINDING_CODES = [
939
1089
  "INVALID_PROP_VALUE",
940
1090
  "UNKNOWN_EXPORT",
@@ -991,11 +1141,6 @@ var init_check = __esm({
991
1141
  READ = /* @__PURE__ */ new Set([".tsx", ".jsx"]);
992
1142
  NOT_INTERFACE = /\.(?:test|spec|stories)\.[jt]sx$/;
993
1143
  excluded = (name) => SKIPPED.has(name) || name.startsWith(".");
994
- describeProvided = (typescript) => ({
995
- name: "typescript",
996
- version: typescript.version ?? "unknown",
997
- source: "provided"
998
- });
999
1144
  plural = (amount, word) => `${amount} ${word}${amount === 1 ? "" : "s"}`;
1000
1145
  MAX_LIMITS_SHOWN = 20;
1001
1146
  }
@@ -1003,11 +1148,11 @@ var init_check = __esm({
1003
1148
 
1004
1149
  // package.json
1005
1150
  var package_default;
1006
- var init_package = __esm({
1151
+ var init_package2 = __esm({
1007
1152
  "package.json"() {
1008
1153
  package_default = {
1009
1154
  name: "@oprt/cli",
1010
- version: "0.3.0",
1155
+ version: "0.4.0",
1011
1156
  description: "The OPERATOR command line: copy components and recipes into your app as source, give coding agents the contract, and check their code against it. Includes an MCP server.",
1012
1157
  keywords: [
1013
1158
  "design-system",
@@ -1063,6 +1208,8 @@ var init_package = __esm({
1063
1208
  lint: "eslint src scripts"
1064
1209
  },
1065
1210
  devDependencies: {
1211
+ "@babel/parser": "8.0.8",
1212
+ "@babel/types": "8.0.8",
1066
1213
  "@modelcontextprotocol/sdk": "1.32.1",
1067
1214
  tsup: "^8.3.0",
1068
1215
  typescript: "^5.6.3",
@@ -1099,9 +1246,9 @@ var init_identity = __esm({
1099
1246
  "src/identity.ts"() {
1100
1247
  "use strict";
1101
1248
  init_esm_shims();
1102
- init_package();
1249
+ init_package2();
1103
1250
  VERSION = package_default.version;
1104
- RESULT_SCHEMA_VERSION = 1;
1251
+ RESULT_SCHEMA_VERSION = 2;
1105
1252
  digests = /* @__PURE__ */ new WeakMap();
1106
1253
  }
1107
1254
  });
@@ -1116,7 +1263,7 @@ function errorEnvelope(error, contract2) {
1116
1263
  contract_digest
1117
1264
  }))(identityOf(contract2));
1118
1265
  return {
1119
- apiVersion: 1,
1266
+ apiVersion: RESULT_SCHEMA_VERSION,
1120
1267
  error: error instanceof Error ? error.message : String(error),
1121
1268
  code: known?.code ?? "ERR_FAILED",
1122
1269
  ...known?.suggestions === void 0 ? {} : { suggestions: known.suggestions },
@@ -1237,7 +1384,7 @@ var init_results = __esm({
1237
1384
  "instructions"
1238
1385
  ];
1239
1386
  envelope = (type, data, contract2) => ({
1240
- apiVersion: 1,
1387
+ apiVersion: RESULT_SCHEMA_VERSION,
1241
1388
  type,
1242
1389
  identity: identityOf(contract2),
1243
1390
  data
@@ -1304,9 +1451,9 @@ var init_results = __esm({
1304
1451
  reason: words2("unsupported_extension", "test_or_story_file", "excluded_folder")
1305
1452
  }),
1306
1453
  parser: obj({
1307
- name: words2("typescript"),
1454
+ name: words2("@babel/parser"),
1308
1455
  version: str(),
1309
- source: words2("project", "installer", "provided")
1456
+ source: words2("bundled")
1310
1457
  }),
1311
1458
  check_data: obj({
1312
1459
  evaluation_status: words2(
@@ -1398,15 +1545,11 @@ var init_results = __esm({
1398
1545
  {
1399
1546
  status: words2("ready", "blocked"),
1400
1547
  message: str(),
1401
- reason: words2("typescript_missing", "typescript_unusable"),
1548
+ reason: words2("parser_missing", "parser_unusable"),
1402
1549
  fix: str(),
1403
- typescript: obj({
1404
- version: str(),
1405
- resolved_from: str(),
1406
- source: words2("project", "installer", "provided")
1407
- })
1550
+ parser: ref("parser")
1408
1551
  },
1409
- ["reason", "fix", "typescript"]
1552
+ ["reason", "fix", "parser"]
1410
1553
  )
1411
1554
  }),
1412
1555
  checks: arr(ref("doctor_check")),
@@ -1481,14 +1624,14 @@ var init_results = __esm({
1481
1624
  instructions: "instructions_data"
1482
1625
  };
1483
1626
  success = (type) => obj({
1484
- apiVersion: { const: 1 },
1627
+ apiVersion: { const: RESULT_SCHEMA_VERSION },
1485
1628
  type: { const: type },
1486
1629
  identity: ref("identity"),
1487
1630
  data: ref(DATA[type])
1488
1631
  });
1489
1632
  failure = () => obj(
1490
1633
  {
1491
- apiVersion: { const: 1 },
1634
+ apiVersion: { const: RESULT_SCHEMA_VERSION },
1492
1635
  error: str(),
1493
1636
  code: str({ pattern: "^ERR_[A-Z_]+$" }),
1494
1637
  suggestions: names(),
@@ -1589,11 +1732,11 @@ var init_results = __esm({
1589
1732
 
1590
1733
  // src/contract.ts
1591
1734
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
1592
- import { dirname, join as join3 } from "path";
1735
+ import { dirname, join as join2 } from "path";
1593
1736
  import { fileURLToPath as fileURLToPath3 } from "url";
1594
1737
  function loadContract() {
1595
1738
  const here = dirname(fileURLToPath3(import.meta.url));
1596
- const candidates = [join3(here, "contract.json"), join3(here, "../generated/contract.json")];
1739
+ const candidates = [join2(here, "contract.json"), join2(here, "../generated/contract.json")];
1597
1740
  const found = candidates.find((candidate) => existsSync2(candidate));
1598
1741
  if (found === void 0) {
1599
1742
  throw new Error(
@@ -1822,10 +1965,10 @@ var init_agent_block = __esm({
1822
1965
 
1823
1966
  // src/doctor.ts
1824
1967
  import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
1825
- import { dirname as dirname3, join as join4 } from "path";
1968
+ import { dirname as dirname3, join as join3 } from "path";
1826
1969
  function installedVersion(cwd, name) {
1827
1970
  for (let folder = cwd; ; folder = dirname3(folder)) {
1828
- const manifest = join4(folder, "node_modules", name, "package.json");
1971
+ const manifest = join3(folder, "node_modules", name, "package.json");
1829
1972
  if (existsSync3(manifest))
1830
1973
  return JSON.parse(readFileSync4(manifest, "utf8")).version;
1831
1974
  if (dirname3(folder) === folder) return void 0;
@@ -1834,36 +1977,36 @@ function installedVersion(cwd, name) {
1834
1977
  function doctor({
1835
1978
  cwd,
1836
1979
  contract: contract2,
1837
- parser = probeTypeScript(cwd)
1980
+ parser = probeParser()
1838
1981
  }) {
1839
1982
  const checks = [];
1840
1983
  for (const name of PACKAGES) {
1841
- const version = installedVersion(cwd, name);
1984
+ const version2 = installedVersion(cwd, name);
1842
1985
  const id = `package:${name}`;
1843
- if (version === void 0)
1986
+ if (version2 === void 0)
1844
1987
  checks.push({
1845
1988
  id,
1846
1989
  status: "warn",
1847
1990
  message: `${name} is not installed.`,
1848
1991
  fix: `Install ${name}, or copy the sources you need with oprt add.`
1849
1992
  });
1850
- else if (minor(version) !== minor(contract2.version))
1993
+ else if (minor(version2) !== minor(contract2.version))
1851
1994
  checks.push({
1852
1995
  id,
1853
1996
  status: "warn",
1854
- message: `${name} is ${version}; this installer describes ${contract2.version}.`,
1997
+ message: `${name} is ${version2}; this installer describes ${contract2.version}.`,
1855
1998
  fix: "Install matching versions of the OPERATOR packages and @oprt/cli."
1856
1999
  });
1857
- else checks.push({ id, status: "ok", message: `${name} ${version} is installed.` });
2000
+ else checks.push({ id, status: "ok", message: `${name} ${version2} is installed.` });
1858
2001
  }
1859
2002
  const written = INSTRUCTION_FILES.map((path2) => {
1860
- const file = join4(cwd, path2);
2003
+ const file = join3(cwd, path2);
1861
2004
  return {
1862
2005
  path: path2,
1863
2006
  version: existsSync3(file) ? blockVersion(readFileSync4(file, "utf8")) : void 0
1864
2007
  };
1865
- }).filter(({ version }) => version !== void 0);
1866
- const stale = written.find(({ version }) => version !== contract2.version);
2008
+ }).filter(({ version: version2 }) => version2 !== void 0);
2009
+ const stale = written.find(({ version: version2 }) => version2 !== contract2.version);
1867
2010
  if (written.length === 0)
1868
2011
  checks.push({
1869
2012
  id: "agent-instructions",
@@ -1884,39 +2027,32 @@ function doctor({
1884
2027
  status: "ok",
1885
2028
  message: `${written.map(({ path: path2 }) => path2).join(", ")} carries the OPERATOR block (${contract2.version}).`
1886
2029
  });
1887
- const install2 = `${INSTALL_TYPESCRIPT} (or your package manager's equivalent), then run oprt doctor again. Lookups work without it.`;
1888
2030
  let checkCode;
1889
2031
  if (parser.ready) {
1890
- const { version, resolved_from, source } = parser.resolution;
2032
+ const { name, version: version2 } = parser.parser;
1891
2033
  checkCode = {
1892
2034
  status: "ready",
1893
- message: `TypeScript ${version}, resolved from ${resolved_from}${source === "project" ? "" : " (beside oprt, not this project)"}.`,
1894
- typescript: { version, resolved_from, source }
2035
+ message: `${name} ${version2}, bundled with oprt: nothing to install.`,
2036
+ parser: parser.parser
1895
2037
  };
1896
- checks.push(
1897
- source === "project" ? {
1898
- id: "typescript",
1899
- status: "ok",
1900
- message: `TypeScript ${version} is installed in this project (${resolved_from}).`
1901
- } : {
1902
- id: "typescript",
1903
- status: "warn",
1904
- message: `TypeScript ${version} is not in this project; the one beside oprt is used (${resolved_from}).`,
1905
- fix: `${INSTALL_TYPESCRIPT}, so checking reads code with a version installed in the project.`
1906
- }
1907
- );
2038
+ checks.push({
2039
+ id: "parser",
2040
+ status: "ok",
2041
+ message: `Code checking reads code with ${name} ${version2}, which ships inside oprt.`
2042
+ });
1908
2043
  } else {
2044
+ const fix = "Install @oprt/cli again, then run oprt doctor again. Lookups work without it.";
1909
2045
  checkCode = {
1910
2046
  status: "blocked",
1911
2047
  message: `${parser.reason}: ${parser.detail}`,
1912
2048
  reason: parser.reason,
1913
- fix: install2
2049
+ fix
1914
2050
  };
1915
2051
  checks.push({
1916
- id: "typescript",
2052
+ id: "parser",
1917
2053
  status: "fail",
1918
2054
  message: `oprt check and the check_code tool cannot run (${parser.reason}): ${parser.detail}`,
1919
- fix: install2
2055
+ fix
1920
2056
  });
1921
2057
  }
1922
2058
  const count = (status) => checks.filter((check) => check.status === status).length;
@@ -1948,7 +2084,7 @@ var init_doctor = __esm({
1948
2084
  ".claude/CLAUDE.md",
1949
2085
  ".cursor/rules/operator.mdc"
1950
2086
  ];
1951
- minor = (version) => version.split(".").slice(0, 2).join(".");
2087
+ minor = (version2) => version2.split(".").slice(0, 2).join(".");
1952
2088
  }
1953
2089
  });
1954
2090
 
@@ -2370,15 +2506,9 @@ function runTool(name, args, context) {
2370
2506
  structured: envelope("instructions", instructionsData(contract2), contract2)
2371
2507
  };
2372
2508
  case "check_code": {
2373
- const { typescript, parser } = resolveParser(
2374
- context.cwd ?? process.cwd(),
2375
- context.typescript,
2376
- "check_code"
2377
- );
2378
- const report = buildCheckReport(
2379
- [checkSource(typescript, contract2, "snippet.tsx", args["code"])],
2380
- { parser }
2381
- );
2509
+ const report = buildCheckReport([
2510
+ checkSource(contract2, "snippet.tsx", args["code"])
2511
+ ]);
2382
2512
  return { text: renderCheckText(report), structured: envelope("check", report, contract2) };
2383
2513
  }
2384
2514
  default:
@@ -2606,11 +2736,11 @@ __export(index_exports, {
2606
2736
  serve: () => serve
2607
2737
  });
2608
2738
  import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
2609
- import { dirname as dirname5, join as join5 } from "path";
2739
+ import { dirname as dirname5, join as join4 } from "path";
2610
2740
  import { fileURLToPath as fileURLToPath4 } from "url";
2611
2741
  function loadRegistry() {
2612
2742
  const here = dirname5(fileURLToPath4(import.meta.url));
2613
- const candidates = [join5(here, "registry.json"), join5(here, "../generated/registry.json")];
2743
+ const candidates = [join4(here, "registry.json"), join4(here, "../generated/registry.json")];
2614
2744
  const found = candidates.find((candidate) => existsSync5(candidate));
2615
2745
  if (found === void 0) {
2616
2746
  throw new Error(
@@ -2625,7 +2755,7 @@ function install(options, context = {}) {
2625
2755
  registry: context.registry ?? registry
2626
2756
  });
2627
2757
  }
2628
- var registry, loaded, contract;
2758
+ var registry, loaded2, contract;
2629
2759
  var init_index = __esm({
2630
2760
  "src/index.ts"() {
2631
2761
  "use strict";
@@ -2646,7 +2776,7 @@ var init_index = __esm({
2646
2776
  init_installer();
2647
2777
  init_types();
2648
2778
  registry = loadRegistry();
2649
- contract = () => loaded ??= loadContract();
2779
+ contract = () => loaded2 ??= loadContract();
2650
2780
  }
2651
2781
  });
2652
2782
 
@@ -2655,7 +2785,7 @@ init_esm_shims();
2655
2785
  init_args();
2656
2786
  init_results();
2657
2787
  init_types();
2658
- var helpText = (version) => `oprt ${version} \u2014 OPERATOR source installer
2788
+ var helpText = (version2) => `oprt ${version2} \u2014 OPERATOR source installer
2659
2789
 
2660
2790
  Usage:
2661
2791
  oprt list [--json]
@@ -2683,7 +2813,7 @@ For coding agents (each takes --json):
2683
2813
  Exit codes: 0 success. check: 0 when every file read has no errors (no warnings either
2684
2814
  under --strict); limits it could not read are shown and do not fail the run. 1 when check
2685
2815
  finds an error, a file does not parse, or no .tsx or .jsx file was read; also for other
2686
- failures (file conflicts, TypeScript missing). 2 for a usage error, an unknown name or a
2816
+ failures (file conflicts, a parser that cannot be loaded). 2 for a usage error, an unknown name or a
2687
2817
  path that does not exist.
2688
2818
 
2689
2819
  Names accept PascalCase or kebab-case. Default output: src/components/operator.
@@ -2716,13 +2846,13 @@ try {
2716
2846
  } catch (error) {
2717
2847
  const message = error instanceof Error ? error.message : String(error);
2718
2848
  if (process.argv.includes("--json")) {
2719
- let loaded2;
2849
+ let loaded3;
2720
2850
  try {
2721
- loaded2 = (await Promise.resolve().then(() => (init_index(), index_exports))).contract();
2851
+ loaded3 = (await Promise.resolve().then(() => (init_index(), index_exports))).contract();
2722
2852
  } catch {
2723
- loaded2 = void 0;
2853
+ loaded3 = void 0;
2724
2854
  }
2725
- process.stdout.write(`${JSON.stringify(errorEnvelope(error, loaded2), null, 2)}
2855
+ process.stdout.write(`${JSON.stringify(errorEnvelope(error, loaded3), null, 2)}
2726
2856
  `);
2727
2857
  } else process.stderr.write(`oprt: ${message}
2728
2858
  `);