@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/README.md +9 -8
- package/dist/babel-parser.cjs +14339 -0
- package/dist/bin.js +402 -272
- package/dist/contract.json +1 -1
- package/dist/index.cjs +389 -271
- package/dist/index.d.cts +25 -41
- package/dist/index.d.ts +25 -41
- package/dist/index.js +375 -257
- package/dist/registry.json +1 -1
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
3
|
-
import { dirname as dirname5, join as
|
|
3
|
+
import { dirname as dirname5, join as join4 } from "path";
|
|
4
4
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
5
5
|
|
|
6
6
|
// src/contract.ts
|
|
@@ -504,7 +504,7 @@ import { createHash } from "crypto";
|
|
|
504
504
|
// package.json
|
|
505
505
|
var package_default = {
|
|
506
506
|
name: "@oprt/cli",
|
|
507
|
-
version: "0.
|
|
507
|
+
version: "0.4.0",
|
|
508
508
|
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.",
|
|
509
509
|
keywords: [
|
|
510
510
|
"design-system",
|
|
@@ -560,6 +560,8 @@ var package_default = {
|
|
|
560
560
|
lint: "eslint src scripts"
|
|
561
561
|
},
|
|
562
562
|
devDependencies: {
|
|
563
|
+
"@babel/parser": "8.0.8",
|
|
564
|
+
"@babel/types": "8.0.8",
|
|
563
565
|
"@modelcontextprotocol/sdk": "1.32.1",
|
|
564
566
|
tsup: "^8.3.0",
|
|
565
567
|
typescript: "^5.6.3",
|
|
@@ -573,7 +575,7 @@ var package_default = {
|
|
|
573
575
|
|
|
574
576
|
// src/identity.ts
|
|
575
577
|
var VERSION = package_default.version;
|
|
576
|
-
var RESULT_SCHEMA_VERSION =
|
|
578
|
+
var RESULT_SCHEMA_VERSION = 2;
|
|
577
579
|
var digests = /* @__PURE__ */ new WeakMap();
|
|
578
580
|
function contractDigest(contract2) {
|
|
579
581
|
let digest = digests.get(contract2);
|
|
@@ -594,77 +596,241 @@ function identityOf(contract2) {
|
|
|
594
596
|
|
|
595
597
|
// src/check.ts
|
|
596
598
|
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3, statSync } from "fs";
|
|
597
|
-
import { basename, extname, join as
|
|
599
|
+
import { basename, extname, join as join2, relative as relative2, resolve as resolve2, sep as sep2 } from "path";
|
|
598
600
|
|
|
599
601
|
// src/parser.ts
|
|
600
602
|
import { createRequire } from "module";
|
|
601
|
-
import { join as join2 } from "path";
|
|
602
603
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
604
|
+
|
|
605
|
+
// ../../node_modules/.pnpm/@babel+parser@8.0.8/node_modules/@babel/parser/package.json
|
|
606
|
+
var version = "8.0.8";
|
|
607
|
+
|
|
608
|
+
// src/parser.ts
|
|
609
|
+
var PARSER = {
|
|
610
|
+
name: "@babel/parser",
|
|
611
|
+
version,
|
|
612
|
+
source: "bundled"
|
|
613
|
+
};
|
|
614
|
+
function optionsFor(file) {
|
|
615
|
+
const typed = /\.tsx$/.test(file);
|
|
616
|
+
const plugins = [
|
|
617
|
+
"jsx",
|
|
618
|
+
...typed ? ["typescript"] : [],
|
|
619
|
+
"decorators",
|
|
620
|
+
"decoratorAutoAccessors",
|
|
621
|
+
"deferredImportEvaluation",
|
|
622
|
+
"sourcePhaseImports"
|
|
623
|
+
];
|
|
624
|
+
return {
|
|
625
|
+
sourceType: "module",
|
|
626
|
+
plugins,
|
|
627
|
+
// Recoverable errors are returned beside the tree, instead of the first one being thrown.
|
|
628
|
+
errorRecovery: true,
|
|
629
|
+
attachComment: false,
|
|
630
|
+
allowReturnOutsideFunction: true,
|
|
631
|
+
allowUndeclaredExports: true,
|
|
632
|
+
allowSuperOutsideMethod: true,
|
|
633
|
+
allowNewTargetOutsideFunction: true
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
var FILE = "babel-parser.cjs";
|
|
637
|
+
var REINSTALL = "Install @oprt/cli again: the parser ships beside the executable in dist.";
|
|
638
|
+
var loaded;
|
|
639
|
+
function loadBabel() {
|
|
640
|
+
if (loaded !== void 0) return loaded;
|
|
641
|
+
const fromSource = /\.[cm]?tsx?$/.test(fileURLToPath2(import.meta.url));
|
|
609
642
|
try {
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
"<a>",
|
|
613
|
-
ts.ScriptTarget?.Latest ?? 99,
|
|
614
|
-
true,
|
|
615
|
-
ts.ScriptKind.TSX
|
|
616
|
-
);
|
|
617
|
-
const diagnostics = probe.parseDiagnostics;
|
|
618
|
-
if (!Array.isArray(diagnostics)) return "it does not report parse errors on a source file";
|
|
619
|
-
if (diagnostics.length === 0) return "it did not report a syntax error in code that has one";
|
|
643
|
+
const require2 = createRequire(import.meta.url);
|
|
644
|
+
loaded = fromSource ? require2("@babel/parser") : require2("./babel-parser.cjs");
|
|
620
645
|
} catch (error) {
|
|
621
|
-
|
|
646
|
+
throw new CliError(
|
|
647
|
+
`Code checking cannot load its parser (${FILE}): ${error instanceof Error ? error.message : String(error)} ${REINSTALL}`,
|
|
648
|
+
1,
|
|
649
|
+
{ code: "ERR_PARSER_MISSING" }
|
|
650
|
+
);
|
|
622
651
|
}
|
|
623
|
-
return
|
|
652
|
+
return loaded;
|
|
624
653
|
}
|
|
625
|
-
var
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
file = require2.resolve("typescript");
|
|
638
|
-
loaded2 = require2("typescript");
|
|
639
|
-
} catch (error) {
|
|
640
|
-
if (error.code !== "MODULE_NOT_FOUND")
|
|
641
|
-
unusable ??= `typescript was found but failed to load: ${error instanceof Error ? error.message : String(error)}`;
|
|
642
|
-
continue;
|
|
643
|
-
}
|
|
644
|
-
const typescript = loaded2.default ?? loaded2;
|
|
645
|
-
const problem = usable(typescript);
|
|
646
|
-
if (problem !== null) {
|
|
647
|
-
unusable ??= `${problem} (at ${packageFolder(file)}).`;
|
|
648
|
-
continue;
|
|
649
|
-
}
|
|
654
|
+
var rejects = (babel, code) => {
|
|
655
|
+
try {
|
|
656
|
+
return babel.parse(code, optionsFor("probe.tsx")).errors.length > 0;
|
|
657
|
+
} catch {
|
|
658
|
+
return true;
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
function probeParser(load = loadBabel) {
|
|
662
|
+
let babel;
|
|
663
|
+
try {
|
|
664
|
+
babel = load();
|
|
665
|
+
} catch (error) {
|
|
650
666
|
return {
|
|
651
|
-
ready:
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
667
|
+
ready: false,
|
|
668
|
+
reason: "parser_missing",
|
|
669
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
if (rejects(babel, "export const a = <a b={1 as number} />;"))
|
|
673
|
+
return {
|
|
674
|
+
ready: false,
|
|
675
|
+
reason: "parser_unusable",
|
|
676
|
+
detail: "it rejected valid code in a probe parse"
|
|
677
|
+
};
|
|
678
|
+
if (!rejects(babel, "export const a = <a>;"))
|
|
679
|
+
return {
|
|
680
|
+
ready: false,
|
|
681
|
+
reason: "parser_unusable",
|
|
682
|
+
detail: "it did not report a syntax error in code that has one"
|
|
659
683
|
};
|
|
684
|
+
return { ready: true, parser: PARSER };
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// src/syntax.ts
|
|
688
|
+
var attempt = (babel, text2, file) => {
|
|
689
|
+
try {
|
|
690
|
+
const tree = babel.parse(text2, optionsFor(file));
|
|
691
|
+
return { tree, errors: tree.errors };
|
|
692
|
+
} catch (error) {
|
|
693
|
+
return { tree: null, fatal: error };
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
var sentence = (message) => message.replace(/\s*\(\d+:\d+\)$/, "");
|
|
697
|
+
var problemOf = (failure2) => ({
|
|
698
|
+
line: failure2.loc.line,
|
|
699
|
+
column: failure2.loc.column + 1,
|
|
700
|
+
message: sentence(failure2.message),
|
|
701
|
+
code: failure2.reasonCode ?? "SyntaxError",
|
|
702
|
+
position: failure2.pos
|
|
703
|
+
});
|
|
704
|
+
var tagName = (name) => {
|
|
705
|
+
if (name === void 0) return "";
|
|
706
|
+
if (name.type === "JSXIdentifier") return name.name;
|
|
707
|
+
if (name.type === "JSXMemberExpression")
|
|
708
|
+
return `${tagName(name.object)}.${name.property.name}`;
|
|
709
|
+
return `${name.namespace.name}:${name.name.name}`;
|
|
710
|
+
};
|
|
711
|
+
var NOT_NODES = /* @__PURE__ */ new Set(["loc", "extra", "errors", "tokens", "comments"]);
|
|
712
|
+
function* childNodes(node) {
|
|
713
|
+
for (const [key, value] of Object.entries(node)) {
|
|
714
|
+
if (NOT_NODES.has(key) || typeof value !== "object" || value === null) continue;
|
|
715
|
+
if (Array.isArray(value)) {
|
|
716
|
+
for (const item of value)
|
|
717
|
+
if (typeof item === "object" && item !== null && typeof item.type === "string")
|
|
718
|
+
yield item;
|
|
719
|
+
} else if (typeof value.type === "string") yield value;
|
|
660
720
|
}
|
|
661
|
-
|
|
662
|
-
|
|
721
|
+
}
|
|
722
|
+
var walk = (node, ancestors, visit) => {
|
|
723
|
+
visit(node, ancestors);
|
|
724
|
+
ancestors.push(node);
|
|
725
|
+
for (const child of childNodes(node)) walk(child, ancestors, visit);
|
|
726
|
+
ancestors.pop();
|
|
727
|
+
};
|
|
728
|
+
var unclosed = (element) => {
|
|
729
|
+
const name = element.openingElement.name;
|
|
663
730
|
return {
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
731
|
+
line: name.loc?.start.line ?? 1,
|
|
732
|
+
column: (name.loc?.start.column ?? 0) + 1,
|
|
733
|
+
message: `JSX element '${tagName(name)}' has no corresponding closing tag.`,
|
|
734
|
+
code: "UnclosedJsxElement",
|
|
735
|
+
position: name.start ?? 0
|
|
667
736
|
};
|
|
737
|
+
};
|
|
738
|
+
function problemsOf(tree, errors, limit, consumed) {
|
|
739
|
+
const mismatched = /* @__PURE__ */ new Map();
|
|
740
|
+
walk(tree.program, [], (node, ancestors) => {
|
|
741
|
+
if (node.type === "JSXElement" && node.closingElement && (node.closingElement.start ?? 0) < limit && tagName(node.openingElement.name) !== tagName(node.closingElement.name))
|
|
742
|
+
mismatched.set(node.closingElement.start ?? 0, { element: node, ancestors: [...ancestors] });
|
|
743
|
+
});
|
|
744
|
+
const problems = [];
|
|
745
|
+
for (const error of errors) {
|
|
746
|
+
if (error.pos >= limit) continue;
|
|
747
|
+
const entry = error.reasonCode === "MissingClosingTagElement" && mismatched.get(error.pos);
|
|
748
|
+
if (entry) {
|
|
749
|
+
const closing = tagName(entry.element.closingElement?.name);
|
|
750
|
+
const outer = [...entry.ancestors].reverse().find(
|
|
751
|
+
(ancestor) => ancestor.type === "JSXElement" && tagName(ancestor.openingElement.name) === closing && !consumed.has(ancestor)
|
|
752
|
+
);
|
|
753
|
+
if (outer !== void 0) {
|
|
754
|
+
consumed.add(outer);
|
|
755
|
+
problems.push(unclosed(entry.element));
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
problems.push(problemOf(error));
|
|
760
|
+
}
|
|
761
|
+
return problems;
|
|
762
|
+
}
|
|
763
|
+
var TYPE_ARGUMENTS = /(?<=[\w$\])])<(?:[^<>()]|<(?:[^<>()]|<[^<>()]*>)*>)*>(?=\()/g;
|
|
764
|
+
var LINE_BREAKS = new RegExp(`[^\\r\\n${String.fromCharCode(8232, 8233)}]`, "g");
|
|
765
|
+
var blankedOut = (match) => match.replace(LINE_BREAKS, " ");
|
|
766
|
+
var CLOSING_TAG = "</_>";
|
|
767
|
+
var COMPLETIONS = [CLOSING_TAG, ")", "}", "]"];
|
|
768
|
+
var MAX_COMPLETIONS = 24;
|
|
769
|
+
function complete(babel, prefix, file) {
|
|
770
|
+
let text2 = prefix;
|
|
771
|
+
let first = null;
|
|
772
|
+
for (let step = 0; step <= MAX_COMPLETIONS; step++) {
|
|
773
|
+
const tried = attempt(babel, text2, file);
|
|
774
|
+
if (tried.tree !== null) return { tree: tried.tree, errors: tried.errors, first };
|
|
775
|
+
const wantsMore = tried.fatal.reasonCode === "UnterminatedJsxContent" || tried.fatal.pos >= text2.length;
|
|
776
|
+
if (!wantsMore) return null;
|
|
777
|
+
const next = COMPLETIONS.find((piece) => {
|
|
778
|
+
const extended = attempt(babel, text2 + piece, file);
|
|
779
|
+
return extended.tree !== null || extended.fatal.reasonCode === "UnterminatedJsxContent" || extended.fatal.pos >= text2.length + piece.length;
|
|
780
|
+
});
|
|
781
|
+
if (next === void 0) return null;
|
|
782
|
+
first ??= next;
|
|
783
|
+
text2 += next;
|
|
784
|
+
}
|
|
785
|
+
return null;
|
|
786
|
+
}
|
|
787
|
+
function shutByCompletion(tree, limit) {
|
|
788
|
+
const shut = [];
|
|
789
|
+
walk(tree.program, [], (node) => {
|
|
790
|
+
if (node.type === "JSXElement" && node.closingElement && (node.closingElement.start ?? 0) >= limit)
|
|
791
|
+
shut.push(node);
|
|
792
|
+
});
|
|
793
|
+
return shut.sort((a, b) => (a.closingElement?.start ?? 0) - (b.closingElement?.start ?? 0));
|
|
794
|
+
}
|
|
795
|
+
var byPlace = (a, b) => a.position - b.position;
|
|
796
|
+
var MAX_EXPLAINED = 2e5;
|
|
797
|
+
function readSyntax(file, text2, babel = loadBabel()) {
|
|
798
|
+
const first = attempt(babel, text2, file);
|
|
799
|
+
if (first.tree !== null) {
|
|
800
|
+
const problems = problemsOf(first.tree, first.errors, Infinity, /* @__PURE__ */ new Set());
|
|
801
|
+
return problems.length === 0 ? { tree: first.tree, problems: [] } : { tree: null, problems: problems.sort(byPlace) };
|
|
802
|
+
}
|
|
803
|
+
let fatal = first.fatal;
|
|
804
|
+
if (text2.length > MAX_EXPLAINED) return { tree: null, problems: [problemOf(fatal)] };
|
|
805
|
+
if (/\.tsx$/.test(file) && fatal.reasonCode !== "UnterminatedJsxContent") {
|
|
806
|
+
const plain = text2.replace(TYPE_ARGUMENTS, blankedOut);
|
|
807
|
+
if (plain !== text2) {
|
|
808
|
+
const again = attempt(babel, plain, file);
|
|
809
|
+
if (again.tree === null && again.fatal.pos > fatal.pos) fatal = again.fatal;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
try {
|
|
813
|
+
const finished = complete(babel, text2.slice(0, fatal.pos), file);
|
|
814
|
+
if (finished !== null) {
|
|
815
|
+
const limit = fatal.pos;
|
|
816
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
817
|
+
const problems = problemsOf(finished.tree, finished.errors, limit, consumed);
|
|
818
|
+
const open = shutByCompletion(finished.tree, limit).filter(
|
|
819
|
+
(element) => !consumed.has(element)
|
|
820
|
+
);
|
|
821
|
+
const atEnd = fatal.reasonCode === "UnterminatedJsxContent" || fatal.pos >= text2.length;
|
|
822
|
+
const hints = (atEnd ? open : finished.first === CLOSING_TAG ? open.slice(0, 1) : []).map(
|
|
823
|
+
unclosed
|
|
824
|
+
);
|
|
825
|
+
const explained = [...problems, ...hints].some(({ code }) => code === "UnclosedJsxElement");
|
|
826
|
+
problems.push(...hints);
|
|
827
|
+
if (!(explained && fatal.reasonCode === "UnterminatedJsxContent"))
|
|
828
|
+
problems.push(problemOf(fatal));
|
|
829
|
+
return { tree: null, problems: problems.sort(byPlace) };
|
|
830
|
+
}
|
|
831
|
+
} catch {
|
|
832
|
+
}
|
|
833
|
+
return { tree: null, problems: [problemOf(fatal)] };
|
|
668
834
|
}
|
|
669
835
|
|
|
670
836
|
// src/check.ts
|
|
@@ -728,52 +894,43 @@ var quote = (text2) => {
|
|
|
728
894
|
return flat.length > 48 ? `${flat.slice(0, 45)}...` : flat;
|
|
729
895
|
};
|
|
730
896
|
var MAX_SYNTAX_ERRORS = 10;
|
|
731
|
-
function checkSource(
|
|
732
|
-
const
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
if (!Array.isArray(parseDiagnostics))
|
|
737
|
-
throw new CliError(
|
|
738
|
-
`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}).`,
|
|
739
|
-
1,
|
|
740
|
-
{ code: "ERR_TYPESCRIPT_UNUSABLE" }
|
|
741
|
-
);
|
|
742
|
-
if (parseDiagnostics.length > 0) {
|
|
743
|
-
const ordered = [...parseDiagnostics].sort((a, b) => (a.start ?? 0) - (b.start ?? 0));
|
|
744
|
-
const shown = ordered.slice(0, MAX_SYNTAX_ERRORS);
|
|
897
|
+
function checkSource(contract2, file, text2) {
|
|
898
|
+
const reading = readSyntax(file, text2);
|
|
899
|
+
if (reading.tree === null) {
|
|
900
|
+
const shown = reading.problems.slice(0, MAX_SYNTAX_ERRORS);
|
|
901
|
+
const more = reading.problems.length - shown.length;
|
|
745
902
|
return {
|
|
746
903
|
file,
|
|
747
904
|
status: "not_evaluated",
|
|
748
905
|
limitations: [],
|
|
749
|
-
findings: shown.map(
|
|
750
|
-
|
|
751
|
-
const more = ordered.length - shown.length;
|
|
752
|
-
return {
|
|
906
|
+
findings: shown.map(
|
|
907
|
+
({ line, column, message, code }, index) => ({
|
|
753
908
|
code: "SYNTAX_ERROR",
|
|
754
909
|
severity: "error",
|
|
755
910
|
file,
|
|
756
|
-
line
|
|
757
|
-
column
|
|
758
|
-
message: `${
|
|
759
|
-
}
|
|
760
|
-
|
|
911
|
+
line,
|
|
912
|
+
column,
|
|
913
|
+
message: `${message} (${code})` + (index === shown.length - 1 && more > 0 ? ` ${more} more syntax error${more === 1 ? "" : "s"} in this file not shown.` : "")
|
|
914
|
+
})
|
|
915
|
+
)
|
|
761
916
|
};
|
|
762
917
|
}
|
|
763
918
|
const findings = [];
|
|
764
919
|
const limitations = [];
|
|
920
|
+
const place = (node) => ({
|
|
921
|
+
line: node.loc?.start.line ?? 1,
|
|
922
|
+
column: (node.loc?.start.column ?? 0) + 1
|
|
923
|
+
});
|
|
924
|
+
const textOf = (node) => text2.slice(node.start ?? 0, node.end ?? 0);
|
|
765
925
|
const limit = (node, limitation) => {
|
|
766
|
-
|
|
767
|
-
limitations.push({ ...limitation, file, line: line + 1, column: character + 1 });
|
|
926
|
+
limitations.push({ ...limitation, file, ...place(node) });
|
|
768
927
|
};
|
|
769
928
|
const report = (node, finding) => {
|
|
770
|
-
const { line, character } = source.getLineAndCharacterOfPosition(node.getStart(source));
|
|
771
929
|
findings.push({
|
|
772
930
|
code: finding.code,
|
|
773
931
|
severity: finding.severity,
|
|
774
932
|
file,
|
|
775
|
-
|
|
776
|
-
column: character + 1,
|
|
933
|
+
...place(node),
|
|
777
934
|
...finding.component === void 0 ? {} : { component: finding.component },
|
|
778
935
|
...finding.prop === void 0 ? {} : { prop: finding.prop },
|
|
779
936
|
message: finding.message,
|
|
@@ -784,21 +941,18 @@ function checkSource(typescript, contract2, file, text2) {
|
|
|
784
941
|
const isComponent = (name) => Object.hasOwn(contract2.components, name);
|
|
785
942
|
const named = /* @__PURE__ */ new Map();
|
|
786
943
|
const namespaces = /* @__PURE__ */ new Set();
|
|
787
|
-
for (const statement of
|
|
788
|
-
if (
|
|
789
|
-
|
|
790
|
-
const from = statement.moduleSpecifier.text;
|
|
791
|
-
const clause = statement.importClause;
|
|
792
|
-
if (!clause || clause.isTypeOnly || !clause.namedBindings) continue;
|
|
944
|
+
for (const statement of reading.tree.program.body) {
|
|
945
|
+
if (statement.type !== "ImportDeclaration" || statement.importKind === "type") continue;
|
|
946
|
+
const from = statement.source.value;
|
|
793
947
|
const copied = from !== PACKAGE && /[./@]/.test(from[0] ?? "") && isComponent(basename(from));
|
|
794
948
|
if (from !== PACKAGE && !copied) continue;
|
|
795
|
-
|
|
796
|
-
if (
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
if (element2.
|
|
801
|
-
const exported =
|
|
949
|
+
for (const element2 of statement.specifiers) {
|
|
950
|
+
if (element2.type === "ImportNamespaceSpecifier") {
|
|
951
|
+
if (from === PACKAGE) namespaces.add(element2.local.name);
|
|
952
|
+
continue;
|
|
953
|
+
}
|
|
954
|
+
if (element2.type !== "ImportSpecifier" || element2.importKind === "type") continue;
|
|
955
|
+
const exported = element2.imported.type === "Identifier" ? element2.imported.name : element2.imported.value;
|
|
802
956
|
if (from === PACKAGE && !contract2.exports.includes(exported)) {
|
|
803
957
|
let suggestions = nearest(exported, Object.keys(contract2.components));
|
|
804
958
|
if (suggestions.length === 0) suggestions = nearest(exported, contract2.exports);
|
|
@@ -823,51 +977,55 @@ function checkSource(typescript, contract2, file, text2) {
|
|
|
823
977
|
});
|
|
824
978
|
continue;
|
|
825
979
|
}
|
|
826
|
-
if (isComponent(exported)) named.set(element2.name
|
|
980
|
+
if (isComponent(exported)) named.set(element2.local.name, exported);
|
|
827
981
|
}
|
|
828
982
|
}
|
|
983
|
+
const isString = (node) => node.type === "StringLiteral";
|
|
984
|
+
const plainTemplate = (node) => node.type === "TemplateLiteral" && node.expressions.length === 0 ? node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw : void 0;
|
|
829
985
|
const literals = (expression) => {
|
|
830
986
|
if (!expression) return [];
|
|
831
|
-
if (
|
|
832
|
-
|
|
833
|
-
if (
|
|
834
|
-
if (
|
|
835
|
-
return [...literals(expression.
|
|
987
|
+
if (isString(expression)) return [expression.value];
|
|
988
|
+
const template = plainTemplate(expression);
|
|
989
|
+
if (template !== void 0) return [template];
|
|
990
|
+
if (expression.type === "ConditionalExpression")
|
|
991
|
+
return [...literals(expression.consequent), ...literals(expression.alternate)];
|
|
836
992
|
return [];
|
|
837
993
|
};
|
|
838
|
-
const valueOf = (attribute) =>
|
|
994
|
+
const valueOf = (attribute) => {
|
|
995
|
+
const value = attribute.value;
|
|
996
|
+
if (value === null || value === void 0) return void 0;
|
|
997
|
+
if (value.type === "JSXExpressionContainer")
|
|
998
|
+
return value.expression.type === "JSXEmptyExpression" ? void 0 : value.expression;
|
|
999
|
+
return value;
|
|
1000
|
+
};
|
|
839
1001
|
const optionValue = (expression) => {
|
|
840
1002
|
const read = [];
|
|
841
1003
|
const unread = [];
|
|
842
|
-
const
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
else if (
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
if (operator ===
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
} else
|
|
856
|
-
else unread.push(node);
|
|
1004
|
+
const walk2 = (node) => {
|
|
1005
|
+
const template = plainTemplate(node);
|
|
1006
|
+
if (isString(node)) read.push(node.value);
|
|
1007
|
+
else if (template !== void 0) read.push(template);
|
|
1008
|
+
else if (node.type === "TSAsExpression" || node.type === "TSNonNullExpression" || node.type === "TSTypeAssertion" || node.type === "TSSatisfiesExpression")
|
|
1009
|
+
walk2(node.expression);
|
|
1010
|
+
else if (node.type === "ConditionalExpression") {
|
|
1011
|
+
walk2(node.consequent);
|
|
1012
|
+
walk2(node.alternate);
|
|
1013
|
+
} else if (node.type === "LogicalExpression") {
|
|
1014
|
+
if (node.operator === "??" || node.operator === "||") {
|
|
1015
|
+
walk2(node.left);
|
|
1016
|
+
walk2(node.right);
|
|
1017
|
+
} else walk2(node.right);
|
|
857
1018
|
} else if (!// No value: the component's default applies.
|
|
858
|
-
(
|
|
1019
|
+
(node.type === "Identifier" && node.name === "undefined" || node.type === "NullLiteral"))
|
|
859
1020
|
unread.push(node);
|
|
860
1021
|
};
|
|
861
|
-
if (expression)
|
|
1022
|
+
if (expression) walk2(expression);
|
|
862
1023
|
return { literals: read, unread };
|
|
863
1024
|
};
|
|
864
1025
|
const classText = (expression) => {
|
|
865
1026
|
if (!expression) return "";
|
|
866
|
-
if (
|
|
867
|
-
return
|
|
868
|
-
expression.head.text,
|
|
869
|
-
...expression.templateSpans.map((span) => span.literal.text)
|
|
870
|
-
].join(" ");
|
|
1027
|
+
if (expression.type === "TemplateLiteral" && expression.expressions.length > 0)
|
|
1028
|
+
return expression.quasis.map((span) => span.value.cooked ?? span.value.raw).join(" ");
|
|
871
1029
|
return literals(expression).join(" ");
|
|
872
1030
|
};
|
|
873
1031
|
const houseRules = (attribute, name) => {
|
|
@@ -893,9 +1051,9 @@ function checkSource(typescript, contract2, file, text2) {
|
|
|
893
1051
|
message: "A rounded corner. The radius is 0 everywhere in OPERATOR."
|
|
894
1052
|
});
|
|
895
1053
|
}
|
|
896
|
-
if (name === "style" && value &&
|
|
1054
|
+
if (name === "style" && value && value.type === "ObjectExpression") {
|
|
897
1055
|
for (const property of value.properties)
|
|
898
|
-
if (
|
|
1056
|
+
if (property.type === "ObjectProperty" && literals(property.value).some((literal) => RAW_COLOR_VALUE.test(literal)))
|
|
899
1057
|
report(attribute, {
|
|
900
1058
|
code: "RAW_COLOR",
|
|
901
1059
|
severity: "warning",
|
|
@@ -903,47 +1061,49 @@ function checkSource(typescript, contract2, file, text2) {
|
|
|
903
1061
|
});
|
|
904
1062
|
}
|
|
905
1063
|
};
|
|
1064
|
+
const attributeName = (attribute) => attribute.name.type === "JSXIdentifier" ? attribute.name.name : textOf(attribute.name);
|
|
906
1065
|
const element = (opening, ancestors, parent, hasChildren, renderedAs) => {
|
|
907
|
-
const tag = opening.
|
|
1066
|
+
const tag = opening.name;
|
|
908
1067
|
let component = null;
|
|
909
|
-
if (
|
|
910
|
-
component = named.get(tag.
|
|
911
|
-
if (component === null && !renderedAs && Object.hasOwn(NATIVE, tag.
|
|
1068
|
+
if (tag.type === "JSXIdentifier") {
|
|
1069
|
+
component = named.get(tag.name) ?? null;
|
|
1070
|
+
if (component === null && !renderedAs && Object.hasOwn(NATIVE, tag.name))
|
|
912
1071
|
report(opening, {
|
|
913
1072
|
code: "NATIVE_CONTROL",
|
|
914
1073
|
severity: "warning",
|
|
915
|
-
message: `A native <${tag.
|
|
1074
|
+
message: `A native <${tag.name}>: use ${NATIVE[tag.name]}.`
|
|
916
1075
|
});
|
|
917
|
-
} else if (
|
|
918
|
-
const owner = tag.
|
|
919
|
-
|
|
1076
|
+
} else if (tag.type === "JSXMemberExpression" && tag.object.type === "JSXIdentifier") {
|
|
1077
|
+
const owner = tag.object.name;
|
|
1078
|
+
const member = tag.property.name;
|
|
1079
|
+
if (namespaces.has(owner) && isComponent(member)) component = member;
|
|
920
1080
|
else if (named.has(owner)) {
|
|
921
|
-
const joined = `${named.get(owner)}${
|
|
1081
|
+
const joined = `${named.get(owner)}${member}`;
|
|
922
1082
|
report(opening, {
|
|
923
1083
|
code: "DOT_NOTATION",
|
|
924
1084
|
severity: "error",
|
|
925
|
-
message: `${owner}.${
|
|
1085
|
+
message: `${owner}.${member} is not a component.` + (isComponent(joined) ? ` Use <${joined}>.` : " OPERATOR's parts are components of their own, imported by name.")
|
|
926
1086
|
});
|
|
927
1087
|
}
|
|
928
1088
|
}
|
|
929
|
-
const attributes = opening.attributes
|
|
1089
|
+
const attributes = opening.attributes;
|
|
930
1090
|
for (const attribute of attributes)
|
|
931
|
-
if (
|
|
932
|
-
houseRules(attribute, attribute.name.
|
|
1091
|
+
if (attribute.type === "JSXAttribute" && attribute.name.type === "JSXIdentifier")
|
|
1092
|
+
houseRules(attribute, attribute.name.name);
|
|
933
1093
|
if (component === null) return null;
|
|
934
1094
|
const definition = contract2.components[component];
|
|
935
|
-
const spread = attributes.some((attribute) =>
|
|
1095
|
+
const spread = attributes.some((attribute) => attribute.type === "JSXSpreadAttribute");
|
|
936
1096
|
for (const attribute of attributes)
|
|
937
|
-
if (
|
|
1097
|
+
if (attribute.type === "JSXSpreadAttribute")
|
|
938
1098
|
limit(attribute, {
|
|
939
1099
|
code: "UNRESOLVED_SPREAD",
|
|
940
1100
|
component,
|
|
941
|
-
message: `Props spread into <${component}> (${quote(attribute
|
|
1101
|
+
message: `Props spread into <${component}> (${quote(textOf(attribute))}) were not read: option values and required props that arrive through them were not checked.`
|
|
942
1102
|
});
|
|
943
1103
|
const given = /* @__PURE__ */ new Set();
|
|
944
1104
|
for (const attribute of attributes) {
|
|
945
|
-
if (
|
|
946
|
-
const prop =
|
|
1105
|
+
if (attribute.type !== "JSXAttribute") continue;
|
|
1106
|
+
const prop = attributeName(attribute);
|
|
947
1107
|
given.add(prop);
|
|
948
1108
|
const known = Object.hasOwn(definition.props, prop) ? definition.props[prop] : void 0;
|
|
949
1109
|
if (known === void 0) {
|
|
@@ -965,7 +1125,7 @@ function checkSource(typescript, contract2, file, text2) {
|
|
|
965
1125
|
code: "UNRESOLVED_VALUE",
|
|
966
1126
|
component,
|
|
967
1127
|
prop,
|
|
968
|
-
message: `${component} ${prop} is set from an expression (${quote(expression
|
|
1128
|
+
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.`
|
|
969
1129
|
});
|
|
970
1130
|
for (const literal of options) {
|
|
971
1131
|
if (known.values.includes(literal)) continue;
|
|
@@ -1014,35 +1174,34 @@ function checkSource(typescript, contract2, file, text2) {
|
|
|
1014
1174
|
return component;
|
|
1015
1175
|
};
|
|
1016
1176
|
const visit = (node, ancestors, parent, renderedAs = false) => {
|
|
1017
|
-
if (
|
|
1018
|
-
const opening =
|
|
1019
|
-
const hasChildren =
|
|
1020
|
-
(child) => !(
|
|
1177
|
+
if (node.type === "JSXElement") {
|
|
1178
|
+
const opening = node.openingElement;
|
|
1179
|
+
const hasChildren = node.children.some(
|
|
1180
|
+
(child) => !(child.type === "JSXText" && /^\s*$/.test(text2.slice(child.start ?? 0, child.end ?? 0)))
|
|
1021
1181
|
);
|
|
1022
1182
|
const component = element(opening, ancestors, parent, hasChildren, renderedAs);
|
|
1023
|
-
const asChild = component !== null && opening.attributes.
|
|
1024
|
-
(attribute) =>
|
|
1183
|
+
const asChild = component !== null && opening.attributes.some(
|
|
1184
|
+
(attribute) => attribute.type === "JSXAttribute" && attributeName(attribute) === "asChild"
|
|
1025
1185
|
);
|
|
1026
|
-
for (const attribute of opening.attributes
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
);
|
|
1186
|
+
for (const attribute of opening.attributes) visit(attribute, [], null);
|
|
1187
|
+
for (const child of node.children)
|
|
1188
|
+
visit(
|
|
1189
|
+
child,
|
|
1190
|
+
component === null ? ancestors : [...ancestors, component],
|
|
1191
|
+
component,
|
|
1192
|
+
asChild
|
|
1193
|
+
);
|
|
1035
1194
|
return;
|
|
1036
1195
|
}
|
|
1037
|
-
|
|
1196
|
+
for (const child of childNodes(node)) visit(child, ancestors, parent);
|
|
1038
1197
|
};
|
|
1039
|
-
visit(
|
|
1040
|
-
const
|
|
1198
|
+
visit(reading.tree.program, [], null);
|
|
1199
|
+
const byPlace2 = (a, b) => a.line - b.line || a.column - b.column;
|
|
1041
1200
|
return {
|
|
1042
1201
|
file,
|
|
1043
1202
|
status: limitations.length > 0 ? "partial" : "evaluated",
|
|
1044
|
-
findings: findings.sort(
|
|
1045
|
-
limitations: limitations.sort(
|
|
1203
|
+
findings: findings.sort(byPlace2),
|
|
1204
|
+
limitations: limitations.sort(byPlace2)
|
|
1046
1205
|
};
|
|
1047
1206
|
}
|
|
1048
1207
|
var SKIPPED = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", "coverage"]);
|
|
@@ -1052,7 +1211,7 @@ var excluded = (name) => SKIPPED.has(name) || name.startsWith(".");
|
|
|
1052
1211
|
function* sources(path) {
|
|
1053
1212
|
if (excluded(basename(path))) return;
|
|
1054
1213
|
if (statSync(path).isDirectory()) {
|
|
1055
|
-
for (const entry of readdirSync(path).sort()) yield* sources(
|
|
1214
|
+
for (const entry of readdirSync(path).sort()) yield* sources(join2(path, entry));
|
|
1056
1215
|
} else if (READ.has(extname(path)) && !NOT_INTERFACE.test(basename(path))) yield path;
|
|
1057
1216
|
}
|
|
1058
1217
|
function whyNotRead(path) {
|
|
@@ -1062,7 +1221,7 @@ function whyNotRead(path) {
|
|
|
1062
1221
|
if (!READ.has(extname(path))) return "unsupported_extension";
|
|
1063
1222
|
return NOT_INTERFACE.test(name) ? "test_or_story_file" : null;
|
|
1064
1223
|
}
|
|
1065
|
-
function buildCheckReport(evaluations, context) {
|
|
1224
|
+
function buildCheckReport(evaluations, context = {}) {
|
|
1066
1225
|
const findings = evaluations.flatMap(({ findings: found }) => found);
|
|
1067
1226
|
const count = (status2) => evaluations.filter((evaluation) => evaluation.status === status2).length;
|
|
1068
1227
|
const partial = count("partial");
|
|
@@ -1089,7 +1248,7 @@ function buildCheckReport(evaluations, context) {
|
|
|
1089
1248
|
limitations: evaluations.flatMap(({ limitations }) => limitations),
|
|
1090
1249
|
skipped_inputs: context.skipped ?? [],
|
|
1091
1250
|
scope: { checked: [...SCOPE.checked], not_checked: [...SCOPE.not_checked] },
|
|
1092
|
-
parser: context.parser
|
|
1251
|
+
parser: context.parser ?? PARSER
|
|
1093
1252
|
};
|
|
1094
1253
|
}
|
|
1095
1254
|
function checkExitCode(report, { strict }) {
|
|
@@ -1097,29 +1256,6 @@ function checkExitCode(report, { strict }) {
|
|
|
1097
1256
|
if (report.summary.errors > 0 || report.coverage.files_not_evaluated > 0) return 1;
|
|
1098
1257
|
return strict && report.summary.warnings > 0 ? 1 : 0;
|
|
1099
1258
|
}
|
|
1100
|
-
var describeProvided = (typescript) => ({
|
|
1101
|
-
name: "typescript",
|
|
1102
|
-
version: typescript.version ?? "unknown",
|
|
1103
|
-
source: "provided"
|
|
1104
|
-
});
|
|
1105
|
-
function resolveParser(cwd, provided, who) {
|
|
1106
|
-
if (provided !== void 0 && provided !== null)
|
|
1107
|
-
return { typescript: provided, parser: describeProvided(provided) };
|
|
1108
|
-
const probe = provided === null ? null : probeTypeScript(cwd);
|
|
1109
|
-
if (probe?.ready) {
|
|
1110
|
-
const { name, version, source } = probe.resolution;
|
|
1111
|
-
return { typescript: probe.typescript, parser: { name, version, source } };
|
|
1112
|
-
}
|
|
1113
|
-
if (probe !== null && probe.reason === "typescript_unusable")
|
|
1114
|
-
throw new CliError(`${who} cannot use the TypeScript it found: ${probe.detail}`, 1, {
|
|
1115
|
-
code: "ERR_TYPESCRIPT_UNUSABLE"
|
|
1116
|
-
});
|
|
1117
|
-
throw new CliError(
|
|
1118
|
-
`${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}.`,
|
|
1119
|
-
1,
|
|
1120
|
-
{ code: "ERR_TYPESCRIPT_MISSING" }
|
|
1121
|
-
);
|
|
1122
|
-
}
|
|
1123
1259
|
function checkPaths(paths, context) {
|
|
1124
1260
|
const root = resolve2(context.cwd);
|
|
1125
1261
|
const files = [];
|
|
@@ -1132,16 +1268,15 @@ function checkPaths(paths, context) {
|
|
|
1132
1268
|
if (reason !== null) skipped.push({ path, reason });
|
|
1133
1269
|
files.push(...sources(absolute));
|
|
1134
1270
|
}
|
|
1135
|
-
|
|
1271
|
+
loadBabel();
|
|
1136
1272
|
const evaluations = [...new Set(files)].map(
|
|
1137
1273
|
(file) => checkSource(
|
|
1138
|
-
typescript,
|
|
1139
1274
|
context.contract,
|
|
1140
1275
|
relative2(root, file).split(sep2).join("/"),
|
|
1141
1276
|
readFileSync3(file, "utf8")
|
|
1142
1277
|
)
|
|
1143
1278
|
);
|
|
1144
|
-
return buildCheckReport(evaluations, {
|
|
1279
|
+
return buildCheckReport(evaluations, { skipped });
|
|
1145
1280
|
}
|
|
1146
1281
|
var plural = (amount, word) => `${amount} ${word}${amount === 1 ? "" : "s"}`;
|
|
1147
1282
|
var MAX_LIMITS_SHOWN = 20;
|
|
@@ -1183,7 +1318,7 @@ function renderCheckText(report) {
|
|
|
1183
1318
|
|
|
1184
1319
|
// src/doctor.ts
|
|
1185
1320
|
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
1186
|
-
import { dirname as dirname3, join as
|
|
1321
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
1187
1322
|
|
|
1188
1323
|
// src/agent-block.ts
|
|
1189
1324
|
var START = "<!-- OPERATOR:START -->";
|
|
@@ -1265,10 +1400,10 @@ var INSTRUCTION_FILES = [
|
|
|
1265
1400
|
".claude/CLAUDE.md",
|
|
1266
1401
|
".cursor/rules/operator.mdc"
|
|
1267
1402
|
];
|
|
1268
|
-
var minor = (
|
|
1403
|
+
var minor = (version2) => version2.split(".").slice(0, 2).join(".");
|
|
1269
1404
|
function installedVersion(cwd, name) {
|
|
1270
1405
|
for (let folder = cwd; ; folder = dirname3(folder)) {
|
|
1271
|
-
const manifest =
|
|
1406
|
+
const manifest = join3(folder, "node_modules", name, "package.json");
|
|
1272
1407
|
if (existsSync3(manifest))
|
|
1273
1408
|
return JSON.parse(readFileSync4(manifest, "utf8")).version;
|
|
1274
1409
|
if (dirname3(folder) === folder) return void 0;
|
|
@@ -1277,36 +1412,36 @@ function installedVersion(cwd, name) {
|
|
|
1277
1412
|
function doctor({
|
|
1278
1413
|
cwd,
|
|
1279
1414
|
contract: contract2,
|
|
1280
|
-
parser =
|
|
1415
|
+
parser = probeParser()
|
|
1281
1416
|
}) {
|
|
1282
1417
|
const checks = [];
|
|
1283
1418
|
for (const name of PACKAGES) {
|
|
1284
|
-
const
|
|
1419
|
+
const version2 = installedVersion(cwd, name);
|
|
1285
1420
|
const id = `package:${name}`;
|
|
1286
|
-
if (
|
|
1421
|
+
if (version2 === void 0)
|
|
1287
1422
|
checks.push({
|
|
1288
1423
|
id,
|
|
1289
1424
|
status: "warn",
|
|
1290
1425
|
message: `${name} is not installed.`,
|
|
1291
1426
|
fix: `Install ${name}, or copy the sources you need with oprt add.`
|
|
1292
1427
|
});
|
|
1293
|
-
else if (minor(
|
|
1428
|
+
else if (minor(version2) !== minor(contract2.version))
|
|
1294
1429
|
checks.push({
|
|
1295
1430
|
id,
|
|
1296
1431
|
status: "warn",
|
|
1297
|
-
message: `${name} is ${
|
|
1432
|
+
message: `${name} is ${version2}; this installer describes ${contract2.version}.`,
|
|
1298
1433
|
fix: "Install matching versions of the OPERATOR packages and @oprt/cli."
|
|
1299
1434
|
});
|
|
1300
|
-
else checks.push({ id, status: "ok", message: `${name} ${
|
|
1435
|
+
else checks.push({ id, status: "ok", message: `${name} ${version2} is installed.` });
|
|
1301
1436
|
}
|
|
1302
1437
|
const written = INSTRUCTION_FILES.map((path) => {
|
|
1303
|
-
const file =
|
|
1438
|
+
const file = join3(cwd, path);
|
|
1304
1439
|
return {
|
|
1305
1440
|
path,
|
|
1306
1441
|
version: existsSync3(file) ? blockVersion(readFileSync4(file, "utf8")) : void 0
|
|
1307
1442
|
};
|
|
1308
|
-
}).filter(({ version }) =>
|
|
1309
|
-
const stale = written.find(({ version }) =>
|
|
1443
|
+
}).filter(({ version: version2 }) => version2 !== void 0);
|
|
1444
|
+
const stale = written.find(({ version: version2 }) => version2 !== contract2.version);
|
|
1310
1445
|
if (written.length === 0)
|
|
1311
1446
|
checks.push({
|
|
1312
1447
|
id: "agent-instructions",
|
|
@@ -1327,39 +1462,32 @@ function doctor({
|
|
|
1327
1462
|
status: "ok",
|
|
1328
1463
|
message: `${written.map(({ path }) => path).join(", ")} carries the OPERATOR block (${contract2.version}).`
|
|
1329
1464
|
});
|
|
1330
|
-
const install2 = `${INSTALL_TYPESCRIPT} (or your package manager's equivalent), then run oprt doctor again. Lookups work without it.`;
|
|
1331
1465
|
let checkCode;
|
|
1332
1466
|
if (parser.ready) {
|
|
1333
|
-
const {
|
|
1467
|
+
const { name, version: version2 } = parser.parser;
|
|
1334
1468
|
checkCode = {
|
|
1335
1469
|
status: "ready",
|
|
1336
|
-
message:
|
|
1337
|
-
|
|
1470
|
+
message: `${name} ${version2}, bundled with oprt: nothing to install.`,
|
|
1471
|
+
parser: parser.parser
|
|
1338
1472
|
};
|
|
1339
|
-
checks.push(
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
} : {
|
|
1345
|
-
id: "typescript",
|
|
1346
|
-
status: "warn",
|
|
1347
|
-
message: `TypeScript ${version} is not in this project; the one beside oprt is used (${resolved_from}).`,
|
|
1348
|
-
fix: `${INSTALL_TYPESCRIPT}, so checking reads code with a version installed in the project.`
|
|
1349
|
-
}
|
|
1350
|
-
);
|
|
1473
|
+
checks.push({
|
|
1474
|
+
id: "parser",
|
|
1475
|
+
status: "ok",
|
|
1476
|
+
message: `Code checking reads code with ${name} ${version2}, which ships inside oprt.`
|
|
1477
|
+
});
|
|
1351
1478
|
} else {
|
|
1479
|
+
const fix = "Install @oprt/cli again, then run oprt doctor again. Lookups work without it.";
|
|
1352
1480
|
checkCode = {
|
|
1353
1481
|
status: "blocked",
|
|
1354
1482
|
message: `${parser.reason}: ${parser.detail}`,
|
|
1355
1483
|
reason: parser.reason,
|
|
1356
|
-
fix
|
|
1484
|
+
fix
|
|
1357
1485
|
};
|
|
1358
1486
|
checks.push({
|
|
1359
|
-
id: "
|
|
1487
|
+
id: "parser",
|
|
1360
1488
|
status: "fail",
|
|
1361
1489
|
message: `oprt check and the check_code tool cannot run (${parser.reason}): ${parser.detail}`,
|
|
1362
|
-
fix
|
|
1490
|
+
fix
|
|
1363
1491
|
});
|
|
1364
1492
|
}
|
|
1365
1493
|
const count = (status) => checks.filter((check) => check.status === status).length;
|
|
@@ -1447,7 +1575,7 @@ var RESULT_TYPES = [
|
|
|
1447
1575
|
"instructions"
|
|
1448
1576
|
];
|
|
1449
1577
|
var envelope = (type, data, contract2) => ({
|
|
1450
|
-
apiVersion:
|
|
1578
|
+
apiVersion: RESULT_SCHEMA_VERSION,
|
|
1451
1579
|
type,
|
|
1452
1580
|
identity: identityOf(contract2),
|
|
1453
1581
|
data
|
|
@@ -1461,7 +1589,7 @@ function errorEnvelope(error, contract2) {
|
|
|
1461
1589
|
contract_digest
|
|
1462
1590
|
}))(identityOf(contract2));
|
|
1463
1591
|
return {
|
|
1464
|
-
apiVersion:
|
|
1592
|
+
apiVersion: RESULT_SCHEMA_VERSION,
|
|
1465
1593
|
error: error instanceof Error ? error.message : String(error),
|
|
1466
1594
|
code: known?.code ?? "ERR_FAILED",
|
|
1467
1595
|
...known?.suggestions === void 0 ? {} : { suggestions: known.suggestions },
|
|
@@ -1557,9 +1685,9 @@ var DEFS = {
|
|
|
1557
1685
|
reason: words2("unsupported_extension", "test_or_story_file", "excluded_folder")
|
|
1558
1686
|
}),
|
|
1559
1687
|
parser: obj({
|
|
1560
|
-
name: words2("
|
|
1688
|
+
name: words2("@babel/parser"),
|
|
1561
1689
|
version: str(),
|
|
1562
|
-
source: words2("
|
|
1690
|
+
source: words2("bundled")
|
|
1563
1691
|
}),
|
|
1564
1692
|
check_data: obj({
|
|
1565
1693
|
evaluation_status: words2(
|
|
@@ -1651,15 +1779,11 @@ var DEFS = {
|
|
|
1651
1779
|
{
|
|
1652
1780
|
status: words2("ready", "blocked"),
|
|
1653
1781
|
message: str(),
|
|
1654
|
-
reason: words2("
|
|
1782
|
+
reason: words2("parser_missing", "parser_unusable"),
|
|
1655
1783
|
fix: str(),
|
|
1656
|
-
|
|
1657
|
-
version: str(),
|
|
1658
|
-
resolved_from: str(),
|
|
1659
|
-
source: words2("project", "installer", "provided")
|
|
1660
|
-
})
|
|
1784
|
+
parser: ref("parser")
|
|
1661
1785
|
},
|
|
1662
|
-
["reason", "fix", "
|
|
1786
|
+
["reason", "fix", "parser"]
|
|
1663
1787
|
)
|
|
1664
1788
|
}),
|
|
1665
1789
|
checks: arr(ref("doctor_check")),
|
|
@@ -1734,14 +1858,14 @@ var DATA = {
|
|
|
1734
1858
|
instructions: "instructions_data"
|
|
1735
1859
|
};
|
|
1736
1860
|
var success = (type) => obj({
|
|
1737
|
-
apiVersion: { const:
|
|
1861
|
+
apiVersion: { const: RESULT_SCHEMA_VERSION },
|
|
1738
1862
|
type: { const: type },
|
|
1739
1863
|
identity: ref("identity"),
|
|
1740
1864
|
data: ref(DATA[type])
|
|
1741
1865
|
});
|
|
1742
1866
|
var failure = () => obj(
|
|
1743
1867
|
{
|
|
1744
|
-
apiVersion: { const:
|
|
1868
|
+
apiVersion: { const: RESULT_SCHEMA_VERSION },
|
|
1745
1869
|
error: str(),
|
|
1746
1870
|
code: str({ pattern: "^ERR_[A-Z_]+$" }),
|
|
1747
1871
|
suggestions: names(),
|
|
@@ -2322,15 +2446,9 @@ function runTool(name, args, context) {
|
|
|
2322
2446
|
structured: envelope("instructions", instructionsData(contract2), contract2)
|
|
2323
2447
|
};
|
|
2324
2448
|
case "check_code": {
|
|
2325
|
-
const
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
"check_code"
|
|
2329
|
-
);
|
|
2330
|
-
const report = buildCheckReport(
|
|
2331
|
-
[checkSource(typescript, contract2, "snippet.tsx", args["code"])],
|
|
2332
|
-
{ parser }
|
|
2333
|
-
);
|
|
2449
|
+
const report = buildCheckReport([
|
|
2450
|
+
checkSource(contract2, "snippet.tsx", args["code"])
|
|
2451
|
+
]);
|
|
2334
2452
|
return { text: renderCheckText(report), structured: envelope("check", report, contract2) };
|
|
2335
2453
|
}
|
|
2336
2454
|
default:
|
|
@@ -2415,7 +2533,7 @@ function serve(input, output, context) {
|
|
|
2415
2533
|
// src/index.ts
|
|
2416
2534
|
function loadRegistry() {
|
|
2417
2535
|
const here = dirname5(fileURLToPath3(import.meta.url));
|
|
2418
|
-
const candidates = [
|
|
2536
|
+
const candidates = [join4(here, "registry.json"), join4(here, "../generated/registry.json")];
|
|
2419
2537
|
const found = candidates.find((candidate) => existsSync5(candidate));
|
|
2420
2538
|
if (found === void 0) {
|
|
2421
2539
|
throw new Error(
|
|
@@ -2425,8 +2543,8 @@ function loadRegistry() {
|
|
|
2425
2543
|
return JSON.parse(readFileSync6(found, "utf8"));
|
|
2426
2544
|
}
|
|
2427
2545
|
var registry = loadRegistry();
|
|
2428
|
-
var
|
|
2429
|
-
var contract = () =>
|
|
2546
|
+
var loaded2;
|
|
2547
|
+
var contract = () => loaded2 ??= loadContract();
|
|
2430
2548
|
function install(options, context = {}) {
|
|
2431
2549
|
return installSources(options, {
|
|
2432
2550
|
cwd: context.cwd ?? process.cwd(),
|