@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.cjs
CHANGED
|
@@ -65,7 +65,7 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
|
65
65
|
|
|
66
66
|
// src/index.ts
|
|
67
67
|
var import_node_fs6 = require("fs");
|
|
68
|
-
var
|
|
68
|
+
var import_node_path6 = require("path");
|
|
69
69
|
var import_node_url3 = require("url");
|
|
70
70
|
|
|
71
71
|
// src/contract.ts
|
|
@@ -560,7 +560,7 @@ var import_node_crypto = require("crypto");
|
|
|
560
560
|
// package.json
|
|
561
561
|
var package_default = {
|
|
562
562
|
name: "@oprt/cli",
|
|
563
|
-
version: "0.
|
|
563
|
+
version: "0.4.0",
|
|
564
564
|
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.",
|
|
565
565
|
keywords: [
|
|
566
566
|
"design-system",
|
|
@@ -616,6 +616,8 @@ var package_default = {
|
|
|
616
616
|
lint: "eslint src scripts"
|
|
617
617
|
},
|
|
618
618
|
devDependencies: {
|
|
619
|
+
"@babel/parser": "8.0.8",
|
|
620
|
+
"@babel/types": "8.0.8",
|
|
619
621
|
"@modelcontextprotocol/sdk": "1.32.1",
|
|
620
622
|
tsup: "^8.3.0",
|
|
621
623
|
typescript: "^5.6.3",
|
|
@@ -629,7 +631,7 @@ var package_default = {
|
|
|
629
631
|
|
|
630
632
|
// src/identity.ts
|
|
631
633
|
var VERSION = package_default.version;
|
|
632
|
-
var RESULT_SCHEMA_VERSION =
|
|
634
|
+
var RESULT_SCHEMA_VERSION = 2;
|
|
633
635
|
var digests = /* @__PURE__ */ new WeakMap();
|
|
634
636
|
function contractDigest(contract2) {
|
|
635
637
|
let digest = digests.get(contract2);
|
|
@@ -650,77 +652,241 @@ function identityOf(contract2) {
|
|
|
650
652
|
|
|
651
653
|
// src/check.ts
|
|
652
654
|
var import_node_fs3 = require("fs");
|
|
653
|
-
var
|
|
655
|
+
var import_node_path3 = require("path");
|
|
654
656
|
|
|
655
657
|
// src/parser.ts
|
|
656
658
|
var import_node_module = require("module");
|
|
657
|
-
var import_node_path3 = require("path");
|
|
658
659
|
var import_node_url2 = require("url");
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
660
|
+
|
|
661
|
+
// ../../node_modules/.pnpm/@babel+parser@8.0.8/node_modules/@babel/parser/package.json
|
|
662
|
+
var version = "8.0.8";
|
|
663
|
+
|
|
664
|
+
// src/parser.ts
|
|
665
|
+
var PARSER = {
|
|
666
|
+
name: "@babel/parser",
|
|
667
|
+
version,
|
|
668
|
+
source: "bundled"
|
|
669
|
+
};
|
|
670
|
+
function optionsFor(file) {
|
|
671
|
+
const typed = /\.tsx$/.test(file);
|
|
672
|
+
const plugins = [
|
|
673
|
+
"jsx",
|
|
674
|
+
...typed ? ["typescript"] : [],
|
|
675
|
+
"decorators",
|
|
676
|
+
"decoratorAutoAccessors",
|
|
677
|
+
"deferredImportEvaluation",
|
|
678
|
+
"sourcePhaseImports"
|
|
679
|
+
];
|
|
680
|
+
return {
|
|
681
|
+
sourceType: "module",
|
|
682
|
+
plugins,
|
|
683
|
+
// Recoverable errors are returned beside the tree, instead of the first one being thrown.
|
|
684
|
+
errorRecovery: true,
|
|
685
|
+
attachComment: false,
|
|
686
|
+
allowReturnOutsideFunction: true,
|
|
687
|
+
allowUndeclaredExports: true,
|
|
688
|
+
allowSuperOutsideMethod: true,
|
|
689
|
+
allowNewTargetOutsideFunction: true
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
var FILE = "babel-parser.cjs";
|
|
693
|
+
var REINSTALL = "Install @oprt/cli again: the parser ships beside the executable in dist.";
|
|
694
|
+
var loaded;
|
|
695
|
+
function loadBabel() {
|
|
696
|
+
if (loaded !== void 0) return loaded;
|
|
697
|
+
const fromSource = /\.[cm]?tsx?$/.test((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
665
698
|
try {
|
|
666
|
-
const
|
|
667
|
-
|
|
668
|
-
"<a>",
|
|
669
|
-
ts.ScriptTarget?.Latest ?? 99,
|
|
670
|
-
true,
|
|
671
|
-
ts.ScriptKind.TSX
|
|
672
|
-
);
|
|
673
|
-
const diagnostics = probe.parseDiagnostics;
|
|
674
|
-
if (!Array.isArray(diagnostics)) return "it does not report parse errors on a source file";
|
|
675
|
-
if (diagnostics.length === 0) return "it did not report a syntax error in code that has one";
|
|
699
|
+
const require2 = (0, import_node_module.createRequire)(importMetaUrl);
|
|
700
|
+
loaded = fromSource ? require2("@babel/parser") : require2("./babel-parser.cjs");
|
|
676
701
|
} catch (error) {
|
|
677
|
-
|
|
702
|
+
throw new CliError(
|
|
703
|
+
`Code checking cannot load its parser (${FILE}): ${error instanceof Error ? error.message : String(error)} ${REINSTALL}`,
|
|
704
|
+
1,
|
|
705
|
+
{ code: "ERR_PARSER_MISSING" }
|
|
706
|
+
);
|
|
678
707
|
}
|
|
679
|
-
return
|
|
708
|
+
return loaded;
|
|
680
709
|
}
|
|
681
|
-
var
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
file = require2.resolve("typescript");
|
|
694
|
-
loaded2 = require2("typescript");
|
|
695
|
-
} catch (error) {
|
|
696
|
-
if (error.code !== "MODULE_NOT_FOUND")
|
|
697
|
-
unusable ??= `typescript was found but failed to load: ${error instanceof Error ? error.message : String(error)}`;
|
|
698
|
-
continue;
|
|
699
|
-
}
|
|
700
|
-
const typescript = loaded2.default ?? loaded2;
|
|
701
|
-
const problem = usable(typescript);
|
|
702
|
-
if (problem !== null) {
|
|
703
|
-
unusable ??= `${problem} (at ${packageFolder(file)}).`;
|
|
704
|
-
continue;
|
|
705
|
-
}
|
|
710
|
+
var rejects = (babel, code) => {
|
|
711
|
+
try {
|
|
712
|
+
return babel.parse(code, optionsFor("probe.tsx")).errors.length > 0;
|
|
713
|
+
} catch {
|
|
714
|
+
return true;
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
function probeParser(load = loadBabel) {
|
|
718
|
+
let babel;
|
|
719
|
+
try {
|
|
720
|
+
babel = load();
|
|
721
|
+
} catch (error) {
|
|
706
722
|
return {
|
|
707
|
-
ready:
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
723
|
+
ready: false,
|
|
724
|
+
reason: "parser_missing",
|
|
725
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
if (rejects(babel, "export const a = <a b={1 as number} />;"))
|
|
729
|
+
return {
|
|
730
|
+
ready: false,
|
|
731
|
+
reason: "parser_unusable",
|
|
732
|
+
detail: "it rejected valid code in a probe parse"
|
|
715
733
|
};
|
|
734
|
+
if (!rejects(babel, "export const a = <a>;"))
|
|
735
|
+
return {
|
|
736
|
+
ready: false,
|
|
737
|
+
reason: "parser_unusable",
|
|
738
|
+
detail: "it did not report a syntax error in code that has one"
|
|
739
|
+
};
|
|
740
|
+
return { ready: true, parser: PARSER };
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// src/syntax.ts
|
|
744
|
+
var attempt = (babel, text2, file) => {
|
|
745
|
+
try {
|
|
746
|
+
const tree = babel.parse(text2, optionsFor(file));
|
|
747
|
+
return { tree, errors: tree.errors };
|
|
748
|
+
} catch (error) {
|
|
749
|
+
return { tree: null, fatal: error };
|
|
716
750
|
}
|
|
717
|
-
|
|
718
|
-
|
|
751
|
+
};
|
|
752
|
+
var sentence = (message) => message.replace(/\s*\(\d+:\d+\)$/, "");
|
|
753
|
+
var problemOf = (failure2) => ({
|
|
754
|
+
line: failure2.loc.line,
|
|
755
|
+
column: failure2.loc.column + 1,
|
|
756
|
+
message: sentence(failure2.message),
|
|
757
|
+
code: failure2.reasonCode ?? "SyntaxError",
|
|
758
|
+
position: failure2.pos
|
|
759
|
+
});
|
|
760
|
+
var tagName = (name) => {
|
|
761
|
+
if (name === void 0) return "";
|
|
762
|
+
if (name.type === "JSXIdentifier") return name.name;
|
|
763
|
+
if (name.type === "JSXMemberExpression")
|
|
764
|
+
return `${tagName(name.object)}.${name.property.name}`;
|
|
765
|
+
return `${name.namespace.name}:${name.name.name}`;
|
|
766
|
+
};
|
|
767
|
+
var NOT_NODES = /* @__PURE__ */ new Set(["loc", "extra", "errors", "tokens", "comments"]);
|
|
768
|
+
function* childNodes(node) {
|
|
769
|
+
for (const [key, value] of Object.entries(node)) {
|
|
770
|
+
if (NOT_NODES.has(key) || typeof value !== "object" || value === null) continue;
|
|
771
|
+
if (Array.isArray(value)) {
|
|
772
|
+
for (const item of value)
|
|
773
|
+
if (typeof item === "object" && item !== null && typeof item.type === "string")
|
|
774
|
+
yield item;
|
|
775
|
+
} else if (typeof value.type === "string") yield value;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
var walk = (node, ancestors, visit) => {
|
|
779
|
+
visit(node, ancestors);
|
|
780
|
+
ancestors.push(node);
|
|
781
|
+
for (const child of childNodes(node)) walk(child, ancestors, visit);
|
|
782
|
+
ancestors.pop();
|
|
783
|
+
};
|
|
784
|
+
var unclosed = (element) => {
|
|
785
|
+
const name = element.openingElement.name;
|
|
719
786
|
return {
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
787
|
+
line: name.loc?.start.line ?? 1,
|
|
788
|
+
column: (name.loc?.start.column ?? 0) + 1,
|
|
789
|
+
message: `JSX element '${tagName(name)}' has no corresponding closing tag.`,
|
|
790
|
+
code: "UnclosedJsxElement",
|
|
791
|
+
position: name.start ?? 0
|
|
723
792
|
};
|
|
793
|
+
};
|
|
794
|
+
function problemsOf(tree, errors, limit, consumed) {
|
|
795
|
+
const mismatched = /* @__PURE__ */ new Map();
|
|
796
|
+
walk(tree.program, [], (node, ancestors) => {
|
|
797
|
+
if (node.type === "JSXElement" && node.closingElement && (node.closingElement.start ?? 0) < limit && tagName(node.openingElement.name) !== tagName(node.closingElement.name))
|
|
798
|
+
mismatched.set(node.closingElement.start ?? 0, { element: node, ancestors: [...ancestors] });
|
|
799
|
+
});
|
|
800
|
+
const problems = [];
|
|
801
|
+
for (const error of errors) {
|
|
802
|
+
if (error.pos >= limit) continue;
|
|
803
|
+
const entry = error.reasonCode === "MissingClosingTagElement" && mismatched.get(error.pos);
|
|
804
|
+
if (entry) {
|
|
805
|
+
const closing = tagName(entry.element.closingElement?.name);
|
|
806
|
+
const outer = [...entry.ancestors].reverse().find(
|
|
807
|
+
(ancestor) => ancestor.type === "JSXElement" && tagName(ancestor.openingElement.name) === closing && !consumed.has(ancestor)
|
|
808
|
+
);
|
|
809
|
+
if (outer !== void 0) {
|
|
810
|
+
consumed.add(outer);
|
|
811
|
+
problems.push(unclosed(entry.element));
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
problems.push(problemOf(error));
|
|
816
|
+
}
|
|
817
|
+
return problems;
|
|
818
|
+
}
|
|
819
|
+
var TYPE_ARGUMENTS = /(?<=[\w$\])])<(?:[^<>()]|<(?:[^<>()]|<[^<>()]*>)*>)*>(?=\()/g;
|
|
820
|
+
var LINE_BREAKS = new RegExp(`[^\\r\\n${String.fromCharCode(8232, 8233)}]`, "g");
|
|
821
|
+
var blankedOut = (match) => match.replace(LINE_BREAKS, " ");
|
|
822
|
+
var CLOSING_TAG = "</_>";
|
|
823
|
+
var COMPLETIONS = [CLOSING_TAG, ")", "}", "]"];
|
|
824
|
+
var MAX_COMPLETIONS = 24;
|
|
825
|
+
function complete(babel, prefix, file) {
|
|
826
|
+
let text2 = prefix;
|
|
827
|
+
let first = null;
|
|
828
|
+
for (let step = 0; step <= MAX_COMPLETIONS; step++) {
|
|
829
|
+
const tried = attempt(babel, text2, file);
|
|
830
|
+
if (tried.tree !== null) return { tree: tried.tree, errors: tried.errors, first };
|
|
831
|
+
const wantsMore = tried.fatal.reasonCode === "UnterminatedJsxContent" || tried.fatal.pos >= text2.length;
|
|
832
|
+
if (!wantsMore) return null;
|
|
833
|
+
const next = COMPLETIONS.find((piece) => {
|
|
834
|
+
const extended = attempt(babel, text2 + piece, file);
|
|
835
|
+
return extended.tree !== null || extended.fatal.reasonCode === "UnterminatedJsxContent" || extended.fatal.pos >= text2.length + piece.length;
|
|
836
|
+
});
|
|
837
|
+
if (next === void 0) return null;
|
|
838
|
+
first ??= next;
|
|
839
|
+
text2 += next;
|
|
840
|
+
}
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
function shutByCompletion(tree, limit) {
|
|
844
|
+
const shut = [];
|
|
845
|
+
walk(tree.program, [], (node) => {
|
|
846
|
+
if (node.type === "JSXElement" && node.closingElement && (node.closingElement.start ?? 0) >= limit)
|
|
847
|
+
shut.push(node);
|
|
848
|
+
});
|
|
849
|
+
return shut.sort((a, b) => (a.closingElement?.start ?? 0) - (b.closingElement?.start ?? 0));
|
|
850
|
+
}
|
|
851
|
+
var byPlace = (a, b) => a.position - b.position;
|
|
852
|
+
var MAX_EXPLAINED = 2e5;
|
|
853
|
+
function readSyntax(file, text2, babel = loadBabel()) {
|
|
854
|
+
const first = attempt(babel, text2, file);
|
|
855
|
+
if (first.tree !== null) {
|
|
856
|
+
const problems = problemsOf(first.tree, first.errors, Infinity, /* @__PURE__ */ new Set());
|
|
857
|
+
return problems.length === 0 ? { tree: first.tree, problems: [] } : { tree: null, problems: problems.sort(byPlace) };
|
|
858
|
+
}
|
|
859
|
+
let fatal = first.fatal;
|
|
860
|
+
if (text2.length > MAX_EXPLAINED) return { tree: null, problems: [problemOf(fatal)] };
|
|
861
|
+
if (/\.tsx$/.test(file) && fatal.reasonCode !== "UnterminatedJsxContent") {
|
|
862
|
+
const plain = text2.replace(TYPE_ARGUMENTS, blankedOut);
|
|
863
|
+
if (plain !== text2) {
|
|
864
|
+
const again = attempt(babel, plain, file);
|
|
865
|
+
if (again.tree === null && again.fatal.pos > fatal.pos) fatal = again.fatal;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
try {
|
|
869
|
+
const finished = complete(babel, text2.slice(0, fatal.pos), file);
|
|
870
|
+
if (finished !== null) {
|
|
871
|
+
const limit = fatal.pos;
|
|
872
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
873
|
+
const problems = problemsOf(finished.tree, finished.errors, limit, consumed);
|
|
874
|
+
const open = shutByCompletion(finished.tree, limit).filter(
|
|
875
|
+
(element) => !consumed.has(element)
|
|
876
|
+
);
|
|
877
|
+
const atEnd = fatal.reasonCode === "UnterminatedJsxContent" || fatal.pos >= text2.length;
|
|
878
|
+
const hints = (atEnd ? open : finished.first === CLOSING_TAG ? open.slice(0, 1) : []).map(
|
|
879
|
+
unclosed
|
|
880
|
+
);
|
|
881
|
+
const explained = [...problems, ...hints].some(({ code }) => code === "UnclosedJsxElement");
|
|
882
|
+
problems.push(...hints);
|
|
883
|
+
if (!(explained && fatal.reasonCode === "UnterminatedJsxContent"))
|
|
884
|
+
problems.push(problemOf(fatal));
|
|
885
|
+
return { tree: null, problems: problems.sort(byPlace) };
|
|
886
|
+
}
|
|
887
|
+
} catch {
|
|
888
|
+
}
|
|
889
|
+
return { tree: null, problems: [problemOf(fatal)] };
|
|
724
890
|
}
|
|
725
891
|
|
|
726
892
|
// src/check.ts
|
|
@@ -784,52 +950,43 @@ var quote = (text2) => {
|
|
|
784
950
|
return flat.length > 48 ? `${flat.slice(0, 45)}...` : flat;
|
|
785
951
|
};
|
|
786
952
|
var MAX_SYNTAX_ERRORS = 10;
|
|
787
|
-
function checkSource(
|
|
788
|
-
const
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
if (!Array.isArray(parseDiagnostics))
|
|
793
|
-
throw new CliError(
|
|
794
|
-
`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}).`,
|
|
795
|
-
1,
|
|
796
|
-
{ code: "ERR_TYPESCRIPT_UNUSABLE" }
|
|
797
|
-
);
|
|
798
|
-
if (parseDiagnostics.length > 0) {
|
|
799
|
-
const ordered = [...parseDiagnostics].sort((a, b) => (a.start ?? 0) - (b.start ?? 0));
|
|
800
|
-
const shown = ordered.slice(0, MAX_SYNTAX_ERRORS);
|
|
953
|
+
function checkSource(contract2, file, text2) {
|
|
954
|
+
const reading = readSyntax(file, text2);
|
|
955
|
+
if (reading.tree === null) {
|
|
956
|
+
const shown = reading.problems.slice(0, MAX_SYNTAX_ERRORS);
|
|
957
|
+
const more = reading.problems.length - shown.length;
|
|
801
958
|
return {
|
|
802
959
|
file,
|
|
803
960
|
status: "not_evaluated",
|
|
804
961
|
limitations: [],
|
|
805
|
-
findings: shown.map(
|
|
806
|
-
|
|
807
|
-
const more = ordered.length - shown.length;
|
|
808
|
-
return {
|
|
962
|
+
findings: shown.map(
|
|
963
|
+
({ line, column, message, code }, index) => ({
|
|
809
964
|
code: "SYNTAX_ERROR",
|
|
810
965
|
severity: "error",
|
|
811
966
|
file,
|
|
812
|
-
line
|
|
813
|
-
column
|
|
814
|
-
message: `${
|
|
815
|
-
}
|
|
816
|
-
|
|
967
|
+
line,
|
|
968
|
+
column,
|
|
969
|
+
message: `${message} (${code})` + (index === shown.length - 1 && more > 0 ? ` ${more} more syntax error${more === 1 ? "" : "s"} in this file not shown.` : "")
|
|
970
|
+
})
|
|
971
|
+
)
|
|
817
972
|
};
|
|
818
973
|
}
|
|
819
974
|
const findings = [];
|
|
820
975
|
const limitations = [];
|
|
976
|
+
const place = (node) => ({
|
|
977
|
+
line: node.loc?.start.line ?? 1,
|
|
978
|
+
column: (node.loc?.start.column ?? 0) + 1
|
|
979
|
+
});
|
|
980
|
+
const textOf = (node) => text2.slice(node.start ?? 0, node.end ?? 0);
|
|
821
981
|
const limit = (node, limitation) => {
|
|
822
|
-
|
|
823
|
-
limitations.push({ ...limitation, file, line: line + 1, column: character + 1 });
|
|
982
|
+
limitations.push({ ...limitation, file, ...place(node) });
|
|
824
983
|
};
|
|
825
984
|
const report = (node, finding) => {
|
|
826
|
-
const { line, character } = source.getLineAndCharacterOfPosition(node.getStart(source));
|
|
827
985
|
findings.push({
|
|
828
986
|
code: finding.code,
|
|
829
987
|
severity: finding.severity,
|
|
830
988
|
file,
|
|
831
|
-
|
|
832
|
-
column: character + 1,
|
|
989
|
+
...place(node),
|
|
833
990
|
...finding.component === void 0 ? {} : { component: finding.component },
|
|
834
991
|
...finding.prop === void 0 ? {} : { prop: finding.prop },
|
|
835
992
|
message: finding.message,
|
|
@@ -840,21 +997,18 @@ function checkSource(typescript, contract2, file, text2) {
|
|
|
840
997
|
const isComponent = (name) => Object.hasOwn(contract2.components, name);
|
|
841
998
|
const named = /* @__PURE__ */ new Map();
|
|
842
999
|
const namespaces = /* @__PURE__ */ new Set();
|
|
843
|
-
for (const statement of
|
|
844
|
-
if (
|
|
845
|
-
|
|
846
|
-
const
|
|
847
|
-
const clause = statement.importClause;
|
|
848
|
-
if (!clause || clause.isTypeOnly || !clause.namedBindings) continue;
|
|
849
|
-
const copied = from !== PACKAGE && /[./@]/.test(from[0] ?? "") && isComponent((0, import_node_path4.basename)(from));
|
|
1000
|
+
for (const statement of reading.tree.program.body) {
|
|
1001
|
+
if (statement.type !== "ImportDeclaration" || statement.importKind === "type") continue;
|
|
1002
|
+
const from = statement.source.value;
|
|
1003
|
+
const copied = from !== PACKAGE && /[./@]/.test(from[0] ?? "") && isComponent((0, import_node_path3.basename)(from));
|
|
850
1004
|
if (from !== PACKAGE && !copied) continue;
|
|
851
|
-
|
|
852
|
-
if (
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
if (element2.
|
|
857
|
-
const exported =
|
|
1005
|
+
for (const element2 of statement.specifiers) {
|
|
1006
|
+
if (element2.type === "ImportNamespaceSpecifier") {
|
|
1007
|
+
if (from === PACKAGE) namespaces.add(element2.local.name);
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
if (element2.type !== "ImportSpecifier" || element2.importKind === "type") continue;
|
|
1011
|
+
const exported = element2.imported.type === "Identifier" ? element2.imported.name : element2.imported.value;
|
|
858
1012
|
if (from === PACKAGE && !contract2.exports.includes(exported)) {
|
|
859
1013
|
let suggestions = nearest(exported, Object.keys(contract2.components));
|
|
860
1014
|
if (suggestions.length === 0) suggestions = nearest(exported, contract2.exports);
|
|
@@ -879,51 +1033,55 @@ function checkSource(typescript, contract2, file, text2) {
|
|
|
879
1033
|
});
|
|
880
1034
|
continue;
|
|
881
1035
|
}
|
|
882
|
-
if (isComponent(exported)) named.set(element2.name
|
|
1036
|
+
if (isComponent(exported)) named.set(element2.local.name, exported);
|
|
883
1037
|
}
|
|
884
1038
|
}
|
|
1039
|
+
const isString = (node) => node.type === "StringLiteral";
|
|
1040
|
+
const plainTemplate = (node) => node.type === "TemplateLiteral" && node.expressions.length === 0 ? node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw : void 0;
|
|
885
1041
|
const literals = (expression) => {
|
|
886
1042
|
if (!expression) return [];
|
|
887
|
-
if (
|
|
888
|
-
|
|
889
|
-
if (
|
|
890
|
-
if (
|
|
891
|
-
return [...literals(expression.
|
|
1043
|
+
if (isString(expression)) return [expression.value];
|
|
1044
|
+
const template = plainTemplate(expression);
|
|
1045
|
+
if (template !== void 0) return [template];
|
|
1046
|
+
if (expression.type === "ConditionalExpression")
|
|
1047
|
+
return [...literals(expression.consequent), ...literals(expression.alternate)];
|
|
892
1048
|
return [];
|
|
893
1049
|
};
|
|
894
|
-
const valueOf = (attribute) =>
|
|
1050
|
+
const valueOf = (attribute) => {
|
|
1051
|
+
const value = attribute.value;
|
|
1052
|
+
if (value === null || value === void 0) return void 0;
|
|
1053
|
+
if (value.type === "JSXExpressionContainer")
|
|
1054
|
+
return value.expression.type === "JSXEmptyExpression" ? void 0 : value.expression;
|
|
1055
|
+
return value;
|
|
1056
|
+
};
|
|
895
1057
|
const optionValue = (expression) => {
|
|
896
1058
|
const read = [];
|
|
897
1059
|
const unread = [];
|
|
898
|
-
const
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
else if (
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
if (operator ===
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
} else
|
|
912
|
-
else unread.push(node);
|
|
1060
|
+
const walk2 = (node) => {
|
|
1061
|
+
const template = plainTemplate(node);
|
|
1062
|
+
if (isString(node)) read.push(node.value);
|
|
1063
|
+
else if (template !== void 0) read.push(template);
|
|
1064
|
+
else if (node.type === "TSAsExpression" || node.type === "TSNonNullExpression" || node.type === "TSTypeAssertion" || node.type === "TSSatisfiesExpression")
|
|
1065
|
+
walk2(node.expression);
|
|
1066
|
+
else if (node.type === "ConditionalExpression") {
|
|
1067
|
+
walk2(node.consequent);
|
|
1068
|
+
walk2(node.alternate);
|
|
1069
|
+
} else if (node.type === "LogicalExpression") {
|
|
1070
|
+
if (node.operator === "??" || node.operator === "||") {
|
|
1071
|
+
walk2(node.left);
|
|
1072
|
+
walk2(node.right);
|
|
1073
|
+
} else walk2(node.right);
|
|
913
1074
|
} else if (!// No value: the component's default applies.
|
|
914
|
-
(
|
|
1075
|
+
(node.type === "Identifier" && node.name === "undefined" || node.type === "NullLiteral"))
|
|
915
1076
|
unread.push(node);
|
|
916
1077
|
};
|
|
917
|
-
if (expression)
|
|
1078
|
+
if (expression) walk2(expression);
|
|
918
1079
|
return { literals: read, unread };
|
|
919
1080
|
};
|
|
920
1081
|
const classText = (expression) => {
|
|
921
1082
|
if (!expression) return "";
|
|
922
|
-
if (
|
|
923
|
-
return
|
|
924
|
-
expression.head.text,
|
|
925
|
-
...expression.templateSpans.map((span) => span.literal.text)
|
|
926
|
-
].join(" ");
|
|
1083
|
+
if (expression.type === "TemplateLiteral" && expression.expressions.length > 0)
|
|
1084
|
+
return expression.quasis.map((span) => span.value.cooked ?? span.value.raw).join(" ");
|
|
927
1085
|
return literals(expression).join(" ");
|
|
928
1086
|
};
|
|
929
1087
|
const houseRules = (attribute, name) => {
|
|
@@ -949,9 +1107,9 @@ function checkSource(typescript, contract2, file, text2) {
|
|
|
949
1107
|
message: "A rounded corner. The radius is 0 everywhere in OPERATOR."
|
|
950
1108
|
});
|
|
951
1109
|
}
|
|
952
|
-
if (name === "style" && value &&
|
|
1110
|
+
if (name === "style" && value && value.type === "ObjectExpression") {
|
|
953
1111
|
for (const property of value.properties)
|
|
954
|
-
if (
|
|
1112
|
+
if (property.type === "ObjectProperty" && literals(property.value).some((literal) => RAW_COLOR_VALUE.test(literal)))
|
|
955
1113
|
report(attribute, {
|
|
956
1114
|
code: "RAW_COLOR",
|
|
957
1115
|
severity: "warning",
|
|
@@ -959,47 +1117,49 @@ function checkSource(typescript, contract2, file, text2) {
|
|
|
959
1117
|
});
|
|
960
1118
|
}
|
|
961
1119
|
};
|
|
1120
|
+
const attributeName = (attribute) => attribute.name.type === "JSXIdentifier" ? attribute.name.name : textOf(attribute.name);
|
|
962
1121
|
const element = (opening, ancestors, parent, hasChildren, renderedAs) => {
|
|
963
|
-
const tag = opening.
|
|
1122
|
+
const tag = opening.name;
|
|
964
1123
|
let component = null;
|
|
965
|
-
if (
|
|
966
|
-
component = named.get(tag.
|
|
967
|
-
if (component === null && !renderedAs && Object.hasOwn(NATIVE, tag.
|
|
1124
|
+
if (tag.type === "JSXIdentifier") {
|
|
1125
|
+
component = named.get(tag.name) ?? null;
|
|
1126
|
+
if (component === null && !renderedAs && Object.hasOwn(NATIVE, tag.name))
|
|
968
1127
|
report(opening, {
|
|
969
1128
|
code: "NATIVE_CONTROL",
|
|
970
1129
|
severity: "warning",
|
|
971
|
-
message: `A native <${tag.
|
|
1130
|
+
message: `A native <${tag.name}>: use ${NATIVE[tag.name]}.`
|
|
972
1131
|
});
|
|
973
|
-
} else if (
|
|
974
|
-
const owner = tag.
|
|
975
|
-
|
|
1132
|
+
} else if (tag.type === "JSXMemberExpression" && tag.object.type === "JSXIdentifier") {
|
|
1133
|
+
const owner = tag.object.name;
|
|
1134
|
+
const member = tag.property.name;
|
|
1135
|
+
if (namespaces.has(owner) && isComponent(member)) component = member;
|
|
976
1136
|
else if (named.has(owner)) {
|
|
977
|
-
const joined = `${named.get(owner)}${
|
|
1137
|
+
const joined = `${named.get(owner)}${member}`;
|
|
978
1138
|
report(opening, {
|
|
979
1139
|
code: "DOT_NOTATION",
|
|
980
1140
|
severity: "error",
|
|
981
|
-
message: `${owner}.${
|
|
1141
|
+
message: `${owner}.${member} is not a component.` + (isComponent(joined) ? ` Use <${joined}>.` : " OPERATOR's parts are components of their own, imported by name.")
|
|
982
1142
|
});
|
|
983
1143
|
}
|
|
984
1144
|
}
|
|
985
|
-
const attributes = opening.attributes
|
|
1145
|
+
const attributes = opening.attributes;
|
|
986
1146
|
for (const attribute of attributes)
|
|
987
|
-
if (
|
|
988
|
-
houseRules(attribute, attribute.name.
|
|
1147
|
+
if (attribute.type === "JSXAttribute" && attribute.name.type === "JSXIdentifier")
|
|
1148
|
+
houseRules(attribute, attribute.name.name);
|
|
989
1149
|
if (component === null) return null;
|
|
990
1150
|
const definition = contract2.components[component];
|
|
991
|
-
const spread = attributes.some((attribute) =>
|
|
1151
|
+
const spread = attributes.some((attribute) => attribute.type === "JSXSpreadAttribute");
|
|
992
1152
|
for (const attribute of attributes)
|
|
993
|
-
if (
|
|
1153
|
+
if (attribute.type === "JSXSpreadAttribute")
|
|
994
1154
|
limit(attribute, {
|
|
995
1155
|
code: "UNRESOLVED_SPREAD",
|
|
996
1156
|
component,
|
|
997
|
-
message: `Props spread into <${component}> (${quote(attribute
|
|
1157
|
+
message: `Props spread into <${component}> (${quote(textOf(attribute))}) were not read: option values and required props that arrive through them were not checked.`
|
|
998
1158
|
});
|
|
999
1159
|
const given = /* @__PURE__ */ new Set();
|
|
1000
1160
|
for (const attribute of attributes) {
|
|
1001
|
-
if (
|
|
1002
|
-
const prop =
|
|
1161
|
+
if (attribute.type !== "JSXAttribute") continue;
|
|
1162
|
+
const prop = attributeName(attribute);
|
|
1003
1163
|
given.add(prop);
|
|
1004
1164
|
const known = Object.hasOwn(definition.props, prop) ? definition.props[prop] : void 0;
|
|
1005
1165
|
if (known === void 0) {
|
|
@@ -1021,7 +1181,7 @@ function checkSource(typescript, contract2, file, text2) {
|
|
|
1021
1181
|
code: "UNRESOLVED_VALUE",
|
|
1022
1182
|
component,
|
|
1023
1183
|
prop,
|
|
1024
|
-
message: `${component} ${prop} is set from an expression (${quote(expression
|
|
1184
|
+
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.`
|
|
1025
1185
|
});
|
|
1026
1186
|
for (const literal of options) {
|
|
1027
1187
|
if (known.values.includes(literal)) continue;
|
|
@@ -1070,35 +1230,34 @@ function checkSource(typescript, contract2, file, text2) {
|
|
|
1070
1230
|
return component;
|
|
1071
1231
|
};
|
|
1072
1232
|
const visit = (node, ancestors, parent, renderedAs = false) => {
|
|
1073
|
-
if (
|
|
1074
|
-
const opening =
|
|
1075
|
-
const hasChildren =
|
|
1076
|
-
(child) => !(
|
|
1233
|
+
if (node.type === "JSXElement") {
|
|
1234
|
+
const opening = node.openingElement;
|
|
1235
|
+
const hasChildren = node.children.some(
|
|
1236
|
+
(child) => !(child.type === "JSXText" && /^\s*$/.test(text2.slice(child.start ?? 0, child.end ?? 0)))
|
|
1077
1237
|
);
|
|
1078
1238
|
const component = element(opening, ancestors, parent, hasChildren, renderedAs);
|
|
1079
|
-
const asChild = component !== null && opening.attributes.
|
|
1080
|
-
(attribute) =>
|
|
1239
|
+
const asChild = component !== null && opening.attributes.some(
|
|
1240
|
+
(attribute) => attribute.type === "JSXAttribute" && attributeName(attribute) === "asChild"
|
|
1081
1241
|
);
|
|
1082
|
-
for (const attribute of opening.attributes
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
);
|
|
1242
|
+
for (const attribute of opening.attributes) visit(attribute, [], null);
|
|
1243
|
+
for (const child of node.children)
|
|
1244
|
+
visit(
|
|
1245
|
+
child,
|
|
1246
|
+
component === null ? ancestors : [...ancestors, component],
|
|
1247
|
+
component,
|
|
1248
|
+
asChild
|
|
1249
|
+
);
|
|
1091
1250
|
return;
|
|
1092
1251
|
}
|
|
1093
|
-
|
|
1252
|
+
for (const child of childNodes(node)) visit(child, ancestors, parent);
|
|
1094
1253
|
};
|
|
1095
|
-
visit(
|
|
1096
|
-
const
|
|
1254
|
+
visit(reading.tree.program, [], null);
|
|
1255
|
+
const byPlace2 = (a, b) => a.line - b.line || a.column - b.column;
|
|
1097
1256
|
return {
|
|
1098
1257
|
file,
|
|
1099
1258
|
status: limitations.length > 0 ? "partial" : "evaluated",
|
|
1100
|
-
findings: findings.sort(
|
|
1101
|
-
limitations: limitations.sort(
|
|
1259
|
+
findings: findings.sort(byPlace2),
|
|
1260
|
+
limitations: limitations.sort(byPlace2)
|
|
1102
1261
|
};
|
|
1103
1262
|
}
|
|
1104
1263
|
var SKIPPED = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", "coverage"]);
|
|
@@ -1106,19 +1265,19 @@ var READ = /* @__PURE__ */ new Set([".tsx", ".jsx"]);
|
|
|
1106
1265
|
var NOT_INTERFACE = /\.(?:test|spec|stories)\.[jt]sx$/;
|
|
1107
1266
|
var excluded = (name) => SKIPPED.has(name) || name.startsWith(".");
|
|
1108
1267
|
function* sources(path) {
|
|
1109
|
-
if (excluded((0,
|
|
1268
|
+
if (excluded((0, import_node_path3.basename)(path))) return;
|
|
1110
1269
|
if ((0, import_node_fs3.statSync)(path).isDirectory()) {
|
|
1111
|
-
for (const entry of (0, import_node_fs3.readdirSync)(path).sort()) yield* sources((0,
|
|
1112
|
-
} else if (READ.has((0,
|
|
1270
|
+
for (const entry of (0, import_node_fs3.readdirSync)(path).sort()) yield* sources((0, import_node_path3.join)(path, entry));
|
|
1271
|
+
} else if (READ.has((0, import_node_path3.extname)(path)) && !NOT_INTERFACE.test((0, import_node_path3.basename)(path))) yield path;
|
|
1113
1272
|
}
|
|
1114
1273
|
function whyNotRead(path) {
|
|
1115
|
-
const name = (0,
|
|
1274
|
+
const name = (0, import_node_path3.basename)(path);
|
|
1116
1275
|
if (excluded(name)) return "excluded_folder";
|
|
1117
1276
|
if ((0, import_node_fs3.statSync)(path).isDirectory()) return null;
|
|
1118
|
-
if (!READ.has((0,
|
|
1277
|
+
if (!READ.has((0, import_node_path3.extname)(path))) return "unsupported_extension";
|
|
1119
1278
|
return NOT_INTERFACE.test(name) ? "test_or_story_file" : null;
|
|
1120
1279
|
}
|
|
1121
|
-
function buildCheckReport(evaluations, context) {
|
|
1280
|
+
function buildCheckReport(evaluations, context = {}) {
|
|
1122
1281
|
const findings = evaluations.flatMap(({ findings: found }) => found);
|
|
1123
1282
|
const count = (status2) => evaluations.filter((evaluation) => evaluation.status === status2).length;
|
|
1124
1283
|
const partial = count("partial");
|
|
@@ -1145,7 +1304,7 @@ function buildCheckReport(evaluations, context) {
|
|
|
1145
1304
|
limitations: evaluations.flatMap(({ limitations }) => limitations),
|
|
1146
1305
|
skipped_inputs: context.skipped ?? [],
|
|
1147
1306
|
scope: { checked: [...SCOPE.checked], not_checked: [...SCOPE.not_checked] },
|
|
1148
|
-
parser: context.parser
|
|
1307
|
+
parser: context.parser ?? PARSER
|
|
1149
1308
|
};
|
|
1150
1309
|
}
|
|
1151
1310
|
function checkExitCode(report, { strict }) {
|
|
@@ -1153,51 +1312,27 @@ function checkExitCode(report, { strict }) {
|
|
|
1153
1312
|
if (report.summary.errors > 0 || report.coverage.files_not_evaluated > 0) return 1;
|
|
1154
1313
|
return strict && report.summary.warnings > 0 ? 1 : 0;
|
|
1155
1314
|
}
|
|
1156
|
-
var describeProvided = (typescript) => ({
|
|
1157
|
-
name: "typescript",
|
|
1158
|
-
version: typescript.version ?? "unknown",
|
|
1159
|
-
source: "provided"
|
|
1160
|
-
});
|
|
1161
|
-
function resolveParser(cwd, provided, who) {
|
|
1162
|
-
if (provided !== void 0 && provided !== null)
|
|
1163
|
-
return { typescript: provided, parser: describeProvided(provided) };
|
|
1164
|
-
const probe = provided === null ? null : probeTypeScript(cwd);
|
|
1165
|
-
if (probe?.ready) {
|
|
1166
|
-
const { name, version, source } = probe.resolution;
|
|
1167
|
-
return { typescript: probe.typescript, parser: { name, version, source } };
|
|
1168
|
-
}
|
|
1169
|
-
if (probe !== null && probe.reason === "typescript_unusable")
|
|
1170
|
-
throw new CliError(`${who} cannot use the TypeScript it found: ${probe.detail}`, 1, {
|
|
1171
|
-
code: "ERR_TYPESCRIPT_UNUSABLE"
|
|
1172
|
-
});
|
|
1173
|
-
throw new CliError(
|
|
1174
|
-
`${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}.`,
|
|
1175
|
-
1,
|
|
1176
|
-
{ code: "ERR_TYPESCRIPT_MISSING" }
|
|
1177
|
-
);
|
|
1178
|
-
}
|
|
1179
1315
|
function checkPaths(paths, context) {
|
|
1180
|
-
const root = (0,
|
|
1316
|
+
const root = (0, import_node_path3.resolve)(context.cwd);
|
|
1181
1317
|
const files = [];
|
|
1182
1318
|
const skipped = [];
|
|
1183
1319
|
for (const path of paths) {
|
|
1184
|
-
const absolute = (0,
|
|
1320
|
+
const absolute = (0, import_node_path3.resolve)(root, path);
|
|
1185
1321
|
if (!(0, import_node_fs3.existsSync)(absolute))
|
|
1186
1322
|
throw new CliError(`No such file or folder: ${path}`, 2, { code: "ERR_PATH_NOT_FOUND" });
|
|
1187
1323
|
const reason = whyNotRead(absolute);
|
|
1188
1324
|
if (reason !== null) skipped.push({ path, reason });
|
|
1189
1325
|
files.push(...sources(absolute));
|
|
1190
1326
|
}
|
|
1191
|
-
|
|
1327
|
+
loadBabel();
|
|
1192
1328
|
const evaluations = [...new Set(files)].map(
|
|
1193
1329
|
(file) => checkSource(
|
|
1194
|
-
typescript,
|
|
1195
1330
|
context.contract,
|
|
1196
|
-
(0,
|
|
1331
|
+
(0, import_node_path3.relative)(root, file).split(import_node_path3.sep).join("/"),
|
|
1197
1332
|
(0, import_node_fs3.readFileSync)(file, "utf8")
|
|
1198
1333
|
)
|
|
1199
1334
|
);
|
|
1200
|
-
return buildCheckReport(evaluations, {
|
|
1335
|
+
return buildCheckReport(evaluations, { skipped });
|
|
1201
1336
|
}
|
|
1202
1337
|
var plural = (amount, word) => `${amount} ${word}${amount === 1 ? "" : "s"}`;
|
|
1203
1338
|
var MAX_LIMITS_SHOWN = 20;
|
|
@@ -1239,7 +1374,7 @@ function renderCheckText(report) {
|
|
|
1239
1374
|
|
|
1240
1375
|
// src/doctor.ts
|
|
1241
1376
|
var import_node_fs4 = require("fs");
|
|
1242
|
-
var
|
|
1377
|
+
var import_node_path4 = require("path");
|
|
1243
1378
|
|
|
1244
1379
|
// src/agent-block.ts
|
|
1245
1380
|
var START = "<!-- OPERATOR:START -->";
|
|
@@ -1321,48 +1456,48 @@ var INSTRUCTION_FILES = [
|
|
|
1321
1456
|
".claude/CLAUDE.md",
|
|
1322
1457
|
".cursor/rules/operator.mdc"
|
|
1323
1458
|
];
|
|
1324
|
-
var minor = (
|
|
1459
|
+
var minor = (version2) => version2.split(".").slice(0, 2).join(".");
|
|
1325
1460
|
function installedVersion(cwd, name) {
|
|
1326
|
-
for (let folder = cwd; ; folder = (0,
|
|
1327
|
-
const manifest = (0,
|
|
1461
|
+
for (let folder = cwd; ; folder = (0, import_node_path4.dirname)(folder)) {
|
|
1462
|
+
const manifest = (0, import_node_path4.join)(folder, "node_modules", name, "package.json");
|
|
1328
1463
|
if ((0, import_node_fs4.existsSync)(manifest))
|
|
1329
1464
|
return JSON.parse((0, import_node_fs4.readFileSync)(manifest, "utf8")).version;
|
|
1330
|
-
if ((0,
|
|
1465
|
+
if ((0, import_node_path4.dirname)(folder) === folder) return void 0;
|
|
1331
1466
|
}
|
|
1332
1467
|
}
|
|
1333
1468
|
function doctor({
|
|
1334
1469
|
cwd,
|
|
1335
1470
|
contract: contract2,
|
|
1336
|
-
parser =
|
|
1471
|
+
parser = probeParser()
|
|
1337
1472
|
}) {
|
|
1338
1473
|
const checks = [];
|
|
1339
1474
|
for (const name of PACKAGES) {
|
|
1340
|
-
const
|
|
1475
|
+
const version2 = installedVersion(cwd, name);
|
|
1341
1476
|
const id = `package:${name}`;
|
|
1342
|
-
if (
|
|
1477
|
+
if (version2 === void 0)
|
|
1343
1478
|
checks.push({
|
|
1344
1479
|
id,
|
|
1345
1480
|
status: "warn",
|
|
1346
1481
|
message: `${name} is not installed.`,
|
|
1347
1482
|
fix: `Install ${name}, or copy the sources you need with oprt add.`
|
|
1348
1483
|
});
|
|
1349
|
-
else if (minor(
|
|
1484
|
+
else if (minor(version2) !== minor(contract2.version))
|
|
1350
1485
|
checks.push({
|
|
1351
1486
|
id,
|
|
1352
1487
|
status: "warn",
|
|
1353
|
-
message: `${name} is ${
|
|
1488
|
+
message: `${name} is ${version2}; this installer describes ${contract2.version}.`,
|
|
1354
1489
|
fix: "Install matching versions of the OPERATOR packages and @oprt/cli."
|
|
1355
1490
|
});
|
|
1356
|
-
else checks.push({ id, status: "ok", message: `${name} ${
|
|
1491
|
+
else checks.push({ id, status: "ok", message: `${name} ${version2} is installed.` });
|
|
1357
1492
|
}
|
|
1358
1493
|
const written = INSTRUCTION_FILES.map((path) => {
|
|
1359
|
-
const file = (0,
|
|
1494
|
+
const file = (0, import_node_path4.join)(cwd, path);
|
|
1360
1495
|
return {
|
|
1361
1496
|
path,
|
|
1362
1497
|
version: (0, import_node_fs4.existsSync)(file) ? blockVersion((0, import_node_fs4.readFileSync)(file, "utf8")) : void 0
|
|
1363
1498
|
};
|
|
1364
|
-
}).filter(({ version }) =>
|
|
1365
|
-
const stale = written.find(({ version }) =>
|
|
1499
|
+
}).filter(({ version: version2 }) => version2 !== void 0);
|
|
1500
|
+
const stale = written.find(({ version: version2 }) => version2 !== contract2.version);
|
|
1366
1501
|
if (written.length === 0)
|
|
1367
1502
|
checks.push({
|
|
1368
1503
|
id: "agent-instructions",
|
|
@@ -1383,39 +1518,32 @@ function doctor({
|
|
|
1383
1518
|
status: "ok",
|
|
1384
1519
|
message: `${written.map(({ path }) => path).join(", ")} carries the OPERATOR block (${contract2.version}).`
|
|
1385
1520
|
});
|
|
1386
|
-
const install2 = `${INSTALL_TYPESCRIPT} (or your package manager's equivalent), then run oprt doctor again. Lookups work without it.`;
|
|
1387
1521
|
let checkCode;
|
|
1388
1522
|
if (parser.ready) {
|
|
1389
|
-
const {
|
|
1523
|
+
const { name, version: version2 } = parser.parser;
|
|
1390
1524
|
checkCode = {
|
|
1391
1525
|
status: "ready",
|
|
1392
|
-
message:
|
|
1393
|
-
|
|
1526
|
+
message: `${name} ${version2}, bundled with oprt: nothing to install.`,
|
|
1527
|
+
parser: parser.parser
|
|
1394
1528
|
};
|
|
1395
|
-
checks.push(
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
} : {
|
|
1401
|
-
id: "typescript",
|
|
1402
|
-
status: "warn",
|
|
1403
|
-
message: `TypeScript ${version} is not in this project; the one beside oprt is used (${resolved_from}).`,
|
|
1404
|
-
fix: `${INSTALL_TYPESCRIPT}, so checking reads code with a version installed in the project.`
|
|
1405
|
-
}
|
|
1406
|
-
);
|
|
1529
|
+
checks.push({
|
|
1530
|
+
id: "parser",
|
|
1531
|
+
status: "ok",
|
|
1532
|
+
message: `Code checking reads code with ${name} ${version2}, which ships inside oprt.`
|
|
1533
|
+
});
|
|
1407
1534
|
} else {
|
|
1535
|
+
const fix = "Install @oprt/cli again, then run oprt doctor again. Lookups work without it.";
|
|
1408
1536
|
checkCode = {
|
|
1409
1537
|
status: "blocked",
|
|
1410
1538
|
message: `${parser.reason}: ${parser.detail}`,
|
|
1411
1539
|
reason: parser.reason,
|
|
1412
|
-
fix
|
|
1540
|
+
fix
|
|
1413
1541
|
};
|
|
1414
1542
|
checks.push({
|
|
1415
|
-
id: "
|
|
1543
|
+
id: "parser",
|
|
1416
1544
|
status: "fail",
|
|
1417
1545
|
message: `oprt check and the check_code tool cannot run (${parser.reason}): ${parser.detail}`,
|
|
1418
|
-
fix
|
|
1546
|
+
fix
|
|
1419
1547
|
});
|
|
1420
1548
|
}
|
|
1421
1549
|
const count = (status) => checks.filter((check) => check.status === status).length;
|
|
@@ -1436,7 +1564,7 @@ function doctor({
|
|
|
1436
1564
|
|
|
1437
1565
|
// src/init.ts
|
|
1438
1566
|
var import_node_fs5 = require("fs");
|
|
1439
|
-
var
|
|
1567
|
+
var import_node_path5 = require("path");
|
|
1440
1568
|
var AGENT_FILES = {
|
|
1441
1569
|
claude: ["CLAUDE.md"],
|
|
1442
1570
|
cursor: [".cursor/rules/operator.mdc"],
|
|
@@ -1464,7 +1592,7 @@ function init(options, context) {
|
|
|
1464
1592
|
const dryRun = options.dryRun ?? false;
|
|
1465
1593
|
const files = targets(options).map((path) => {
|
|
1466
1594
|
validateRelative(path);
|
|
1467
|
-
const destination = (0,
|
|
1595
|
+
const destination = (0, import_node_path5.resolve)(root, path);
|
|
1468
1596
|
inspectPath(root, destination);
|
|
1469
1597
|
const existing = (0, import_node_fs5.existsSync)(destination) ? (0, import_node_fs5.readFileSync)(destination, "utf8") : null;
|
|
1470
1598
|
if (options.remove) {
|
|
@@ -1481,7 +1609,7 @@ function init(options, context) {
|
|
|
1481
1609
|
const fresh = existing === null && path.endsWith(".mdc") ? CURSOR_FRONT_MATTER : existing;
|
|
1482
1610
|
const { text: text2, action } = applyBlock(fresh, block);
|
|
1483
1611
|
if (action !== "unchanged" && !dryRun) {
|
|
1484
|
-
(0, import_node_fs5.mkdirSync)((0,
|
|
1612
|
+
(0, import_node_fs5.mkdirSync)((0, import_node_path5.dirname)(destination), { recursive: true });
|
|
1485
1613
|
(0, import_node_fs5.writeFileSync)(destination, text2);
|
|
1486
1614
|
}
|
|
1487
1615
|
return { path, action: existing === null ? "create" : action };
|
|
@@ -1503,7 +1631,7 @@ var RESULT_TYPES = [
|
|
|
1503
1631
|
"instructions"
|
|
1504
1632
|
];
|
|
1505
1633
|
var envelope = (type, data, contract2) => ({
|
|
1506
|
-
apiVersion:
|
|
1634
|
+
apiVersion: RESULT_SCHEMA_VERSION,
|
|
1507
1635
|
type,
|
|
1508
1636
|
identity: identityOf(contract2),
|
|
1509
1637
|
data
|
|
@@ -1517,7 +1645,7 @@ function errorEnvelope(error, contract2) {
|
|
|
1517
1645
|
contract_digest
|
|
1518
1646
|
}))(identityOf(contract2));
|
|
1519
1647
|
return {
|
|
1520
|
-
apiVersion:
|
|
1648
|
+
apiVersion: RESULT_SCHEMA_VERSION,
|
|
1521
1649
|
error: error instanceof Error ? error.message : String(error),
|
|
1522
1650
|
code: known?.code ?? "ERR_FAILED",
|
|
1523
1651
|
...known?.suggestions === void 0 ? {} : { suggestions: known.suggestions },
|
|
@@ -1613,9 +1741,9 @@ var DEFS = {
|
|
|
1613
1741
|
reason: words2("unsupported_extension", "test_or_story_file", "excluded_folder")
|
|
1614
1742
|
}),
|
|
1615
1743
|
parser: obj({
|
|
1616
|
-
name: words2("
|
|
1744
|
+
name: words2("@babel/parser"),
|
|
1617
1745
|
version: str(),
|
|
1618
|
-
source: words2("
|
|
1746
|
+
source: words2("bundled")
|
|
1619
1747
|
}),
|
|
1620
1748
|
check_data: obj({
|
|
1621
1749
|
evaluation_status: words2(
|
|
@@ -1707,15 +1835,11 @@ var DEFS = {
|
|
|
1707
1835
|
{
|
|
1708
1836
|
status: words2("ready", "blocked"),
|
|
1709
1837
|
message: str(),
|
|
1710
|
-
reason: words2("
|
|
1838
|
+
reason: words2("parser_missing", "parser_unusable"),
|
|
1711
1839
|
fix: str(),
|
|
1712
|
-
|
|
1713
|
-
version: str(),
|
|
1714
|
-
resolved_from: str(),
|
|
1715
|
-
source: words2("project", "installer", "provided")
|
|
1716
|
-
})
|
|
1840
|
+
parser: ref("parser")
|
|
1717
1841
|
},
|
|
1718
|
-
["reason", "fix", "
|
|
1842
|
+
["reason", "fix", "parser"]
|
|
1719
1843
|
)
|
|
1720
1844
|
}),
|
|
1721
1845
|
checks: arr(ref("doctor_check")),
|
|
@@ -1790,14 +1914,14 @@ var DATA = {
|
|
|
1790
1914
|
instructions: "instructions_data"
|
|
1791
1915
|
};
|
|
1792
1916
|
var success = (type) => obj({
|
|
1793
|
-
apiVersion: { const:
|
|
1917
|
+
apiVersion: { const: RESULT_SCHEMA_VERSION },
|
|
1794
1918
|
type: { const: type },
|
|
1795
1919
|
identity: ref("identity"),
|
|
1796
1920
|
data: ref(DATA[type])
|
|
1797
1921
|
});
|
|
1798
1922
|
var failure = () => obj(
|
|
1799
1923
|
{
|
|
1800
|
-
apiVersion: { const:
|
|
1924
|
+
apiVersion: { const: RESULT_SCHEMA_VERSION },
|
|
1801
1925
|
error: str(),
|
|
1802
1926
|
code: str({ pattern: "^ERR_[A-Z_]+$" }),
|
|
1803
1927
|
suggestions: names(),
|
|
@@ -2378,15 +2502,9 @@ function runTool(name, args, context) {
|
|
|
2378
2502
|
structured: envelope("instructions", instructionsData(contract2), contract2)
|
|
2379
2503
|
};
|
|
2380
2504
|
case "check_code": {
|
|
2381
|
-
const
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
"check_code"
|
|
2385
|
-
);
|
|
2386
|
-
const report = buildCheckReport(
|
|
2387
|
-
[checkSource(typescript, contract2, "snippet.tsx", args["code"])],
|
|
2388
|
-
{ parser }
|
|
2389
|
-
);
|
|
2505
|
+
const report = buildCheckReport([
|
|
2506
|
+
checkSource(contract2, "snippet.tsx", args["code"])
|
|
2507
|
+
]);
|
|
2390
2508
|
return { text: renderCheckText(report), structured: envelope("check", report, contract2) };
|
|
2391
2509
|
}
|
|
2392
2510
|
default:
|
|
@@ -2470,8 +2588,8 @@ function serve(input, output, context) {
|
|
|
2470
2588
|
|
|
2471
2589
|
// src/index.ts
|
|
2472
2590
|
function loadRegistry() {
|
|
2473
|
-
const here = (0,
|
|
2474
|
-
const candidates = [(0,
|
|
2591
|
+
const here = (0, import_node_path6.dirname)((0, import_node_url3.fileURLToPath)(importMetaUrl));
|
|
2592
|
+
const candidates = [(0, import_node_path6.join)(here, "registry.json"), (0, import_node_path6.join)(here, "../generated/registry.json")];
|
|
2475
2593
|
const found = candidates.find((candidate) => (0, import_node_fs6.existsSync)(candidate));
|
|
2476
2594
|
if (found === void 0) {
|
|
2477
2595
|
throw new Error(
|
|
@@ -2481,8 +2599,8 @@ function loadRegistry() {
|
|
|
2481
2599
|
return JSON.parse((0, import_node_fs6.readFileSync)(found, "utf8"));
|
|
2482
2600
|
}
|
|
2483
2601
|
var registry = loadRegistry();
|
|
2484
|
-
var
|
|
2485
|
-
var contract = () =>
|
|
2602
|
+
var loaded2;
|
|
2603
|
+
var contract = () => loaded2 ??= loadContract();
|
|
2486
2604
|
function install(options, context = {}) {
|
|
2487
2605
|
return installSources(options, {
|
|
2488
2606
|
cwd: context.cwd ?? process.cwd(),
|