@octanejs/mcp-server 0.2.23 → 0.2.24
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 +1 -1
- package/package.json +1 -1
- package/src/bridge.js +34 -6
- package/src/bridge.test.js +17 -0
- package/src/index.js +1 -0
package/README.md
CHANGED
|
@@ -167,7 +167,7 @@ one manifest suite by name (`js-framework`, `todomvc`, `weather-app`,
|
|
|
167
167
|
`floating-tree-navigation`, `manifest-cache-invalidation`, `vite-client-assets`, `activity`,
|
|
168
168
|
`streaming-ssr`, `streaming-backpressure`,
|
|
169
169
|
`compiler-throughput`, `tsrx-component-graph`, `codegen-size`, `hook-memo`,
|
|
170
|
-
`template-call-memo`, `tsrx-renderer-selection`, `bundle-size`, `bundle-reachability`, `three-renderer`,
|
|
170
|
+
`transition-hooks`, `template-call-memo`, `tsrx-renderer-selection`, `bundle-size`, `bundle-reachability`, `three-renderer`,
|
|
171
171
|
`three-bundle-size`, …)
|
|
172
172
|
or every suite with `all`; `quick` selects the reduced-iteration smoke pass. The
|
|
173
173
|
suite list mirrors the runner manifest and `node benchmarks/bench.mjs --list`.
|
package/package.json
CHANGED
package/src/bridge.js
CHANGED
|
@@ -6,6 +6,7 @@ import { join, resolve } from 'node:path';
|
|
|
6
6
|
// expected union from the workspace manifests, so publishing a new binding
|
|
7
7
|
// without registering it in either catalog fails the mcp-server tests.
|
|
8
8
|
export const KNOWN_BINDINGS = {
|
|
9
|
+
'react-is': '@octanejs/octane-is',
|
|
9
10
|
'@gsap/react': '@octanejs/gsap',
|
|
10
11
|
animejs: '@octanejs/animejs',
|
|
11
12
|
'usehooks-ts': '@octanejs/usehooks-ts',
|
|
@@ -474,6 +475,20 @@ export async function collectSourceFiles(root, out = [], depth = 0) {
|
|
|
474
475
|
|
|
475
476
|
export function scanSource(source) {
|
|
476
477
|
const apis = new Map();
|
|
478
|
+
const symbolExports = new Map();
|
|
479
|
+
// Introspection libraries export element-kind symbols, not components.
|
|
480
|
+
// Require the right-hand identifier to resolve to an actual Symbol.for
|
|
481
|
+
// declaration; ordinary component exports/render calls remain API uses.
|
|
482
|
+
const symbols = new Set(
|
|
483
|
+
[...source.matchAll(/\b([A-Za-z_$][\w$]*)\s*=\s*Symbol\.for\(\s*['"][^'"]+['"]\s*\)/g)].map(
|
|
484
|
+
(match) => match[1],
|
|
485
|
+
),
|
|
486
|
+
);
|
|
487
|
+
for (const match of source.matchAll(
|
|
488
|
+
/\bexports\.([A-Za-z_$][\w$]*)\s*=\s*([A-Za-z_$][\w$]*)\s*;/g,
|
|
489
|
+
)) {
|
|
490
|
+
if (symbols.has(match[2])) symbolExports.set(match[1], (symbolExports.get(match[1]) ?? 0) + 1);
|
|
491
|
+
}
|
|
477
492
|
for (const name of Object.keys(REACT_API_MAP)) {
|
|
478
493
|
if (name === 'onChange') continue;
|
|
479
494
|
const matches = source.match(new RegExp(`\\b${name}\\b`, 'g'));
|
|
@@ -493,13 +508,14 @@ export function scanSource(source) {
|
|
|
493
508
|
}
|
|
494
509
|
}
|
|
495
510
|
const classComponent = /\bextends\s+(React\.)?(Pure)?Component\b/.test(source);
|
|
496
|
-
return { apis, imports, classComponent };
|
|
511
|
+
return { apis, imports, classComponent, symbolExports };
|
|
497
512
|
}
|
|
498
513
|
|
|
499
514
|
export async function scanPath(root) {
|
|
500
515
|
const files = await collectSourceFiles(resolve(root));
|
|
501
516
|
const totals = new Map();
|
|
502
517
|
const imports = new Set();
|
|
518
|
+
const symbolExports = new Map();
|
|
503
519
|
let classComponents = false;
|
|
504
520
|
for (const file of files) {
|
|
505
521
|
let source;
|
|
@@ -513,14 +529,26 @@ export async function scanPath(root) {
|
|
|
513
529
|
totals.set(name, (totals.get(name) ?? 0) + count);
|
|
514
530
|
}
|
|
515
531
|
for (const spec of result.imports) imports.add(spec);
|
|
532
|
+
for (const [name, count] of result.symbolExports) {
|
|
533
|
+
symbolExports.set(name, (symbolExports.get(name) ?? 0) + count);
|
|
534
|
+
}
|
|
516
535
|
classComponents ||= result.classComponent;
|
|
517
536
|
}
|
|
518
|
-
return { filesScanned: files.length, totals, imports, classComponents };
|
|
537
|
+
return { filesScanned: files.length, totals, imports, classComponents, symbolExports };
|
|
519
538
|
}
|
|
520
539
|
|
|
521
|
-
function apiRows(totals) {
|
|
540
|
+
function apiRows(totals, symbolExports) {
|
|
522
541
|
return [...totals.entries()]
|
|
523
|
-
.map(([name, count]) =>
|
|
542
|
+
.map(([name, count]) =>
|
|
543
|
+
symbolExports.get(name) === count
|
|
544
|
+
? {
|
|
545
|
+
name,
|
|
546
|
+
count,
|
|
547
|
+
status: 'rewrite',
|
|
548
|
+
note: 'Exported element-kind marker: map to the Octane kind; predicates for unsupported kinds remain false. This does not require rendering that component.',
|
|
549
|
+
}
|
|
550
|
+
: { name, count, ...REACT_API_MAP[name] },
|
|
551
|
+
)
|
|
524
552
|
.sort((a, b) => b.count - a.count);
|
|
525
553
|
}
|
|
526
554
|
|
|
@@ -581,7 +609,7 @@ export async function bridgeReport({ packageName, path, projectRoot }) {
|
|
|
581
609
|
}
|
|
582
610
|
|
|
583
611
|
const scan = await scanPath(scanRoot);
|
|
584
|
-
const rows = apiRows(scan.totals);
|
|
612
|
+
const rows = apiRows(scan.totals, scan.symbolExports);
|
|
585
613
|
report.filesScanned = scan.filesScanned;
|
|
586
614
|
report.reactImports = [...scan.imports];
|
|
587
615
|
report.classComponents = scan.classComponents;
|
|
@@ -604,7 +632,7 @@ export function bridgeReportFromSource(source, { packageName } = {}) {
|
|
|
604
632
|
report.vanillaCore = detectVanillaCore(packageName, null);
|
|
605
633
|
}
|
|
606
634
|
const scan = scanSource(source);
|
|
607
|
-
const rows = apiRows(scan.apis);
|
|
635
|
+
const rows = apiRows(scan.apis, scan.symbolExports);
|
|
608
636
|
report.reactImports = [...scan.imports];
|
|
609
637
|
report.classComponents = scan.classComponent;
|
|
610
638
|
report.apis = rows;
|
package/src/bridge.test.js
CHANGED
|
@@ -41,6 +41,23 @@ describe('scanSource', () => {
|
|
|
41
41
|
expect(scanSource('class Memoish extends PureComponent {}').classComponent).toBe(true);
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
+
it('classifies symbol-kind exports without requiring the corresponding renderer', () => {
|
|
45
|
+
const report = bridgeReportFromSource(`
|
|
46
|
+
var REACT_PROFILER_TYPE = Symbol.for('react.profiler');
|
|
47
|
+
exports.Profiler = REACT_PROFILER_TYPE;
|
|
48
|
+
exports.isProfiler = value => value.type === REACT_PROFILER_TYPE;
|
|
49
|
+
`);
|
|
50
|
+
expect(report.apis.find((row) => row.name === 'Profiler').status).toBe('rewrite');
|
|
51
|
+
expect(report.verdict).toBe('bridgeable-with-rewrites');
|
|
52
|
+
const renderer = bridgeReportFromSource(`
|
|
53
|
+
import { Profiler } from 'react';
|
|
54
|
+
var REACT_PROFILER_TYPE = Symbol.for('react.profiler');
|
|
55
|
+
exports.Profiler = REACT_PROFILER_TYPE;
|
|
56
|
+
export const View = () => <Profiler />;
|
|
57
|
+
`);
|
|
58
|
+
expect(renderer.verdict).toBe('needs-rework');
|
|
59
|
+
});
|
|
60
|
+
|
|
44
61
|
it('targets only React-style text-host onChange wiring', () => {
|
|
45
62
|
const source = `
|
|
46
63
|
function Demo(props) {
|