@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/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
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
|
|
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
|
|
409
|
-
|
|
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
|
-
|
|
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
|
|
445
|
+
return loaded;
|
|
422
446
|
}
|
|
423
|
-
function
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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:
|
|
449
|
-
|
|
450
|
-
|
|
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 (
|
|
459
|
-
return {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
|
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
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
|
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(
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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(
|
|
507
|
-
|
|
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
|
|
514
|
-
column
|
|
515
|
-
message: `${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
545
|
-
if (
|
|
546
|
-
|
|
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
|
-
|
|
553
|
-
if (
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
if (element2.
|
|
558
|
-
const exported =
|
|
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
|
|
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 (
|
|
589
|
-
|
|
590
|
-
if (
|
|
591
|
-
if (
|
|
592
|
-
return [...literals(expression.
|
|
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) =>
|
|
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
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
else if (
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
if (operator ===
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
} else
|
|
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
|
-
(
|
|
786
|
+
(node.type === "Identifier" && node.name === "undefined" || node.type === "NullLiteral"))
|
|
616
787
|
unread.push(node);
|
|
617
788
|
};
|
|
618
|
-
if (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 (
|
|
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 &&
|
|
821
|
+
if (name === "style" && value && value.type === "ObjectExpression") {
|
|
654
822
|
for (const property of value.properties)
|
|
655
|
-
if (
|
|
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.
|
|
833
|
+
const tag = opening.name;
|
|
665
834
|
let component = null;
|
|
666
|
-
if (
|
|
667
|
-
component = named.get(tag.
|
|
668
|
-
if (component === null && !renderedAs && Object.hasOwn(NATIVE, tag.
|
|
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.
|
|
841
|
+
message: `A native <${tag.name}>: use ${NATIVE[tag.name]}.`
|
|
673
842
|
});
|
|
674
|
-
} else if (
|
|
675
|
-
const owner = tag.
|
|
676
|
-
|
|
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)}${
|
|
848
|
+
const joined = `${named.get(owner)}${member}`;
|
|
679
849
|
report(opening, {
|
|
680
850
|
code: "DOT_NOTATION",
|
|
681
851
|
severity: "error",
|
|
682
|
-
message: `${owner}.${
|
|
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
|
|
856
|
+
const attributes = opening.attributes;
|
|
687
857
|
for (const attribute of attributes)
|
|
688
|
-
if (
|
|
689
|
-
houseRules(attribute, attribute.name.
|
|
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) =>
|
|
862
|
+
const spread = attributes.some((attribute) => attribute.type === "JSXSpreadAttribute");
|
|
693
863
|
for (const attribute of attributes)
|
|
694
|
-
if (
|
|
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
|
|
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 (
|
|
703
|
-
const prop =
|
|
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
|
|
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 (
|
|
775
|
-
const opening =
|
|
776
|
-
const hasChildren =
|
|
777
|
-
(child) => !(
|
|
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.
|
|
781
|
-
(attribute) =>
|
|
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
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
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
|
-
|
|
963
|
+
for (const child of childNodes(node)) visit(child, ancestors, parent);
|
|
795
964
|
};
|
|
796
|
-
visit(
|
|
797
|
-
const
|
|
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(
|
|
802
|
-
limitations: limitations.sort(
|
|
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(
|
|
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
|
-
|
|
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, {
|
|
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,
|
|
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
|
|
1151
|
+
var init_package2 = __esm({
|
|
1007
1152
|
"package.json"() {
|
|
1008
1153
|
package_default = {
|
|
1009
1154
|
name: "@oprt/cli",
|
|
1010
|
-
version: "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
|
-
|
|
1249
|
+
init_package2();
|
|
1103
1250
|
VERSION = package_default.version;
|
|
1104
|
-
RESULT_SCHEMA_VERSION =
|
|
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:
|
|
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:
|
|
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("
|
|
1454
|
+
name: words2("@babel/parser"),
|
|
1308
1455
|
version: str(),
|
|
1309
|
-
source: words2("
|
|
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("
|
|
1548
|
+
reason: words2("parser_missing", "parser_unusable"),
|
|
1402
1549
|
fix: str(),
|
|
1403
|
-
|
|
1404
|
-
version: str(),
|
|
1405
|
-
resolved_from: str(),
|
|
1406
|
-
source: words2("project", "installer", "provided")
|
|
1407
|
-
})
|
|
1550
|
+
parser: ref("parser")
|
|
1408
1551
|
},
|
|
1409
|
-
["reason", "fix", "
|
|
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:
|
|
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:
|
|
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
|
|
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 = [
|
|
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
|
|
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 =
|
|
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 =
|
|
1980
|
+
parser = probeParser()
|
|
1838
1981
|
}) {
|
|
1839
1982
|
const checks = [];
|
|
1840
1983
|
for (const name of PACKAGES) {
|
|
1841
|
-
const
|
|
1984
|
+
const version2 = installedVersion(cwd, name);
|
|
1842
1985
|
const id = `package:${name}`;
|
|
1843
|
-
if (
|
|
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(
|
|
1993
|
+
else if (minor(version2) !== minor(contract2.version))
|
|
1851
1994
|
checks.push({
|
|
1852
1995
|
id,
|
|
1853
1996
|
status: "warn",
|
|
1854
|
-
message: `${name} is ${
|
|
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} ${
|
|
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 =
|
|
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 }) =>
|
|
1866
|
-
const stale = written.find(({ 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 {
|
|
2032
|
+
const { name, version: version2 } = parser.parser;
|
|
1891
2033
|
checkCode = {
|
|
1892
2034
|
status: "ready",
|
|
1893
|
-
message:
|
|
1894
|
-
|
|
2035
|
+
message: `${name} ${version2}, bundled with oprt: nothing to install.`,
|
|
2036
|
+
parser: parser.parser
|
|
1895
2037
|
};
|
|
1896
|
-
checks.push(
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
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
|
|
2049
|
+
fix
|
|
1914
2050
|
};
|
|
1915
2051
|
checks.push({
|
|
1916
|
-
id: "
|
|
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
|
|
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 = (
|
|
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
|
|
2374
|
-
|
|
2375
|
-
|
|
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
|
|
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 = [
|
|
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,
|
|
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 = () =>
|
|
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 = (
|
|
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,
|
|
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
|
|
2849
|
+
let loaded3;
|
|
2720
2850
|
try {
|
|
2721
|
-
|
|
2851
|
+
loaded3 = (await Promise.resolve().then(() => (init_index(), index_exports))).contract();
|
|
2722
2852
|
} catch {
|
|
2723
|
-
|
|
2853
|
+
loaded3 = void 0;
|
|
2724
2854
|
}
|
|
2725
|
-
process.stdout.write(`${JSON.stringify(errorEnvelope(error,
|
|
2855
|
+
process.stdout.write(`${JSON.stringify(errorEnvelope(error, loaded3), null, 2)}
|
|
2726
2856
|
`);
|
|
2727
2857
|
} else process.stderr.write(`oprt: ${message}
|
|
2728
2858
|
`);
|