@tsrx/mcp 0.0.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/src/target.js ADDED
@@ -0,0 +1,216 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ export const TARGET_CANDIDATES = [
5
+ {
6
+ target: 'ripple',
7
+ compilerPackage: '@tsrx/ripple',
8
+ signals: ['@tsrx/ripple', 'ripple', '@ripple-ts/vite-plugin', '@ripple-ts/compat-react'],
9
+ },
10
+ {
11
+ target: 'react',
12
+ compilerPackage: '@tsrx/react',
13
+ signals: [
14
+ '@tsrx/react',
15
+ '@tsrx/vite-plugin-react',
16
+ '@tsrx/rspack-plugin-react',
17
+ '@tsrx/turbopack-plugin-react',
18
+ '@tsrx/bun-plugin-react',
19
+ ],
20
+ },
21
+ {
22
+ target: 'preact',
23
+ compilerPackage: '@tsrx/preact',
24
+ signals: [
25
+ '@tsrx/preact',
26
+ '@tsrx/vite-plugin-preact',
27
+ '@tsrx/rspack-plugin-preact',
28
+ '@tsrx/bun-plugin-preact',
29
+ ],
30
+ },
31
+ {
32
+ target: 'solid',
33
+ compilerPackage: '@tsrx/solid',
34
+ signals: ['@tsrx/solid', '@tsrx/vite-plugin-solid', '@tsrx/rspack-plugin-solid'],
35
+ },
36
+ {
37
+ target: 'vue',
38
+ compilerPackage: '@tsrx/vue',
39
+ signals: ['@tsrx/vue', '@tsrx/vite-plugin-vue', '@tsrx/rspack-plugin-vue'],
40
+ },
41
+ ];
42
+
43
+ export const CONFIG_FILES = [
44
+ 'vite.config.js',
45
+ 'vite.config.ts',
46
+ 'vite.config.mjs',
47
+ 'vite.config.mts',
48
+ 'rspack.config.js',
49
+ 'rspack.config.ts',
50
+ 'next.config.js',
51
+ 'next.config.mjs',
52
+ 'next.config.ts',
53
+ 'tsconfig.json',
54
+ 'prettier.config.js',
55
+ 'prettier.config.mjs',
56
+ 'prettier.config.cjs',
57
+ '.prettierrc',
58
+ 'eslint.config.js',
59
+ 'eslint.config.mjs',
60
+ 'eslint.config.cjs',
61
+ ];
62
+
63
+ /**
64
+ * @param {string | undefined} [cwd]
65
+ */
66
+ export function resolve_cwd(cwd) {
67
+ return path.resolve(cwd ?? process.cwd());
68
+ }
69
+
70
+ /**
71
+ * @param {string | undefined} [cwd]
72
+ */
73
+ export function resolve_cwd_context(cwd) {
74
+ const cwd_specified = cwd !== undefined;
75
+ const resolved_cwd = resolve_cwd(cwd);
76
+ return {
77
+ cwd: resolved_cwd,
78
+ hint: cwd_hint(resolved_cwd, cwd_specified) || null,
79
+ };
80
+ }
81
+
82
+ /**
83
+ * @param {string} start
84
+ */
85
+ export function find_package_json(start) {
86
+ let current = path.resolve(start);
87
+ for (;;) {
88
+ const candidate = path.join(current, 'package.json');
89
+ if (fs.existsSync(candidate)) return candidate;
90
+ const parent = path.dirname(current);
91
+ if (parent === current) return null;
92
+ current = parent;
93
+ }
94
+ }
95
+
96
+ /**
97
+ * @param {Record<string, unknown>} package_json
98
+ */
99
+ function get_dependency_names(package_json) {
100
+ const names = new Set();
101
+ for (const field of [
102
+ 'dependencies',
103
+ 'devDependencies',
104
+ 'peerDependencies',
105
+ 'optionalDependencies',
106
+ ]) {
107
+ const deps = package_json[field];
108
+ if (deps && typeof deps === 'object') {
109
+ for (const name of Object.keys(deps)) names.add(name);
110
+ }
111
+ }
112
+ return names;
113
+ }
114
+
115
+ /**
116
+ * @param {string} root
117
+ */
118
+ function read_config_text(root) {
119
+ let text = '';
120
+ for (const file of CONFIG_FILES) {
121
+ const filename = path.join(root, file);
122
+ if (fs.existsSync(filename)) {
123
+ try {
124
+ text += `\n${fs.readFileSync(filename, 'utf8')}`;
125
+ } catch {
126
+ // Best-effort context only.
127
+ }
128
+ }
129
+ }
130
+ return text;
131
+ }
132
+
133
+ /**
134
+ * @param {string} resolved_cwd
135
+ * @param {boolean} cwd_specified
136
+ */
137
+ function cwd_hint(resolved_cwd, cwd_specified) {
138
+ if (cwd_specified) return '';
139
+ return ` Hint: cwd was not supplied, so this defaulted to ${resolved_cwd}. Pass cwd pointing at your project root for accurate target detection.`;
140
+ }
141
+
142
+ /**
143
+ * @param {string | undefined} [cwd]
144
+ */
145
+ export function detect_target(cwd) {
146
+ const cwd_context = resolve_cwd_context(cwd);
147
+ const package_json_path = find_package_json(cwd_context.cwd);
148
+ if (!package_json_path) {
149
+ return {
150
+ cwd: cwd_context.cwd,
151
+ packageJsonPath: null,
152
+ detectedTarget: null,
153
+ confidence: /** @type {'none'} */ ('none'),
154
+ matches: [],
155
+ message:
156
+ 'No package.json found from the supplied cwd or its ancestors.' + (cwd_context.hint ?? ''),
157
+ };
158
+ }
159
+
160
+ const root = path.dirname(package_json_path);
161
+ /** @type {Record<string, unknown>} */
162
+ let package_json;
163
+ try {
164
+ package_json = JSON.parse(fs.readFileSync(package_json_path, 'utf8'));
165
+ } catch (error) {
166
+ return {
167
+ cwd: cwd_context.cwd,
168
+ packageJsonPath: package_json_path,
169
+ detectedTarget: null,
170
+ confidence: 'none',
171
+ matches: [],
172
+ message: `Could not parse package.json: ${error instanceof Error ? error.message : String(error)}`,
173
+ };
174
+ }
175
+
176
+ const dependency_names = get_dependency_names(package_json);
177
+ const config_text = read_config_text(root);
178
+ const matches = [];
179
+
180
+ for (const candidate of TARGET_CANDIDATES) {
181
+ const matched_signals = candidate.signals.filter(
182
+ (signal) => dependency_names.has(signal) || config_text.includes(signal),
183
+ );
184
+ if (matched_signals.length > 0) {
185
+ matches.push({
186
+ target: candidate.target,
187
+ compilerPackage: candidate.compilerPackage,
188
+ signals: matched_signals,
189
+ score: matched_signals.length,
190
+ });
191
+ }
192
+ }
193
+
194
+ matches.sort((a, b) => b.score - a.score || a.target.localeCompare(b.target));
195
+ const detected = matches[0] ?? null;
196
+ const tied = detected ? matches.filter((match) => match.score === detected.score) : [];
197
+ const detected_target = tied.length === 1 ? detected.target : null;
198
+
199
+ const base_message =
200
+ tied.length > 1
201
+ ? `Multiple TSRX targets matched equally: ${tied.map((match) => match.target).join(', ')}.`
202
+ : detected
203
+ ? `Detected TSRX target "${detected.target}" from ${detected.signals.join(', ')}.`
204
+ : 'No TSRX target packages were found in package.json or common bundler configs.';
205
+
206
+ return {
207
+ cwd: cwd_context.cwd,
208
+ packageJsonPath: package_json_path,
209
+ detectedTarget: detected_target,
210
+ confidence: /** @type {'high' | 'ambiguous' | 'none'} */ (
211
+ detected ? (tied.length === 1 ? 'high' : 'ambiguous') : 'none'
212
+ ),
213
+ matches,
214
+ message: base_message + (cwd_context.hint ?? ''),
215
+ };
216
+ }
@@ -0,0 +1,112 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { analyze_tsrx_result } from './analyze.js';
4
+ import { compile_tsrx } from './compile.js';
5
+ import { format_tsrx } from './format.js';
6
+ import { resolve_cwd_context } from './target.js';
7
+
8
+ /**
9
+ * @param {unknown} error
10
+ */
11
+ function normalize_read_error(error) {
12
+ if (error && typeof error === 'object') {
13
+ const candidate = /** @type {Record<string, unknown>} */ (error);
14
+ return {
15
+ message: candidate.message ? String(candidate.message) : String(error),
16
+ code: candidate.code ? String(candidate.code) : null,
17
+ };
18
+ }
19
+ return {
20
+ message: String(error),
21
+ code: null,
22
+ };
23
+ }
24
+
25
+ /**
26
+ * @param {string} filePath
27
+ * @param {string} cwd
28
+ */
29
+ function resolve_file_path(filePath, cwd) {
30
+ return path.isAbsolute(filePath) ? path.resolve(filePath) : path.resolve(cwd, filePath);
31
+ }
32
+
33
+ /**
34
+ * @param {{
35
+ * filePath: string,
36
+ * cwd?: string,
37
+ * target?: string,
38
+ * collect?: boolean,
39
+ * loose?: boolean,
40
+ * mode?: 'client' | 'server',
41
+ * printWidth?: number,
42
+ * tabWidth?: number,
43
+ * useTabs?: boolean,
44
+ * singleQuote?: boolean,
45
+ * }} input
46
+ */
47
+ export async function validate_tsrx_file(input) {
48
+ const cwd_context = resolve_cwd_context(input.cwd);
49
+ const cwd = cwd_context.cwd;
50
+ const filePath = resolve_file_path(input.filePath, cwd);
51
+ const filename = filePath;
52
+
53
+ let code;
54
+ try {
55
+ code = await fs.readFile(filePath, 'utf8');
56
+ } catch (error) {
57
+ return {
58
+ ok: false,
59
+ cwd,
60
+ filePath,
61
+ filename,
62
+ message: cwd_context.hint,
63
+ read: {
64
+ ok: false,
65
+ error: normalize_read_error(error),
66
+ },
67
+ format: null,
68
+ compile: null,
69
+ analysis: null,
70
+ };
71
+ }
72
+
73
+ const formatResult = await format_tsrx({
74
+ code,
75
+ filename,
76
+ cwd,
77
+ printWidth: input.printWidth,
78
+ tabWidth: input.tabWidth,
79
+ useTabs: input.useTabs,
80
+ singleQuote: input.singleQuote,
81
+ check: true,
82
+ });
83
+ const compileResult = await compile_tsrx({
84
+ code,
85
+ filename,
86
+ cwd,
87
+ target: input.target,
88
+ collect: input.collect,
89
+ loose: input.loose,
90
+ mode: input.mode,
91
+ includeCode: false,
92
+ });
93
+ const analysisResult = analyze_tsrx_result({
94
+ code,
95
+ compileResult,
96
+ });
97
+
98
+ return {
99
+ ok: formatResult.ok && formatResult.check === true && compileResult.ok,
100
+ cwd,
101
+ filePath,
102
+ filename,
103
+ message: cwd_context.hint,
104
+ read: {
105
+ ok: true,
106
+ error: null,
107
+ },
108
+ format: formatResult,
109
+ compile: compileResult,
110
+ analysis: analysisResult,
111
+ };
112
+ }