@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/format.js ADDED
@@ -0,0 +1,138 @@
1
+ import path from 'node:path';
2
+ import { format, resolveConfig, resolveConfigFile } from 'prettier';
3
+ import * as tsrx_prettier_plugin from '@tsrx/prettier-plugin';
4
+ import { resolve_cwd_context } from './target.js';
5
+
6
+ const BUILTIN_DEFAULTS = {
7
+ printWidth: 100,
8
+ tabWidth: 2,
9
+ useTabs: true,
10
+ singleQuote: true,
11
+ };
12
+
13
+ /**
14
+ * @typedef {{
15
+ * message: string,
16
+ * name: string | null,
17
+ * loc: unknown,
18
+ * }} NormalizedFormatError
19
+ *
20
+ * @typedef {{
21
+ * ok: boolean,
22
+ * filename: string,
23
+ * cwd: string,
24
+ * message: string | null,
25
+ * configPath: string | null,
26
+ * formatted: string | null,
27
+ * changed: boolean,
28
+ * errors: NormalizedFormatError[],
29
+ * check: boolean | null,
30
+ * }} FormatResult
31
+ */
32
+
33
+ /**
34
+ * @param {unknown} error
35
+ * @returns {NormalizedFormatError}
36
+ */
37
+ function normalize_error(error) {
38
+ if (error && typeof error === 'object') {
39
+ const candidate = /** @type {Record<string, unknown>} */ (error);
40
+ return {
41
+ message: candidate.message ? String(candidate.message) : String(error),
42
+ name: candidate.name ? String(candidate.name) : null,
43
+ loc: candidate.loc ?? null,
44
+ };
45
+ }
46
+ return {
47
+ message: String(error),
48
+ name: null,
49
+ loc: null,
50
+ };
51
+ }
52
+
53
+ /**
54
+ * @param {{
55
+ * printWidth?: number,
56
+ * tabWidth?: number,
57
+ * useTabs?: boolean,
58
+ * singleQuote?: boolean,
59
+ * }} input
60
+ */
61
+ function pick_user_overrides(input) {
62
+ /** @type {Record<string, unknown>} */
63
+ const overrides = {};
64
+ if (input.printWidth !== undefined) overrides.printWidth = input.printWidth;
65
+ if (input.tabWidth !== undefined) overrides.tabWidth = input.tabWidth;
66
+ if (input.useTabs !== undefined) overrides.useTabs = input.useTabs;
67
+ if (input.singleQuote !== undefined) overrides.singleQuote = input.singleQuote;
68
+ return overrides;
69
+ }
70
+
71
+ /**
72
+ * @param {{
73
+ * code: string,
74
+ * filename?: string,
75
+ * cwd?: string,
76
+ * printWidth?: number,
77
+ * tabWidth?: number,
78
+ * useTabs?: boolean,
79
+ * singleQuote?: boolean,
80
+ * check?: boolean,
81
+ * }} input
82
+ * @returns {Promise<FormatResult>}
83
+ */
84
+ export async function format_tsrx(input) {
85
+ const filename = input.filename ?? 'Component.tsrx';
86
+ const cwd_context = resolve_cwd_context(input.cwd);
87
+ const cwd = cwd_context.cwd;
88
+ const resolved_filename = path.isAbsolute(filename)
89
+ ? path.resolve(filename)
90
+ : path.resolve(cwd, filename);
91
+
92
+ let project_config = /** @type {Record<string, unknown> | null} */ (null);
93
+ let config_path = /** @type {string | null} */ (null);
94
+ try {
95
+ project_config = await resolveConfig(resolved_filename, { editorconfig: true });
96
+ config_path = await resolveConfigFile(resolved_filename);
97
+ } catch {
98
+ // Treat unreadable Prettier config as "no project config" rather than failing.
99
+ }
100
+
101
+ const user_overrides = pick_user_overrides(input);
102
+ const options = {
103
+ ...BUILTIN_DEFAULTS,
104
+ ...(project_config ?? {}),
105
+ ...user_overrides,
106
+ filepath: resolved_filename,
107
+ parser: 'ripple',
108
+ plugins: [tsrx_prettier_plugin],
109
+ };
110
+
111
+ try {
112
+ const formatted = await format(input.code, options);
113
+
114
+ return {
115
+ ok: true,
116
+ filename,
117
+ cwd,
118
+ message: cwd_context.hint,
119
+ configPath: config_path,
120
+ formatted,
121
+ changed: formatted !== input.code,
122
+ errors: [],
123
+ check: input.check ? formatted === input.code : null,
124
+ };
125
+ } catch (error) {
126
+ return {
127
+ ok: false,
128
+ filename,
129
+ cwd,
130
+ message: cwd_context.hint,
131
+ configPath: config_path,
132
+ formatted: null,
133
+ changed: false,
134
+ errors: [normalize_error(error)],
135
+ check: input.check ? false : null,
136
+ };
137
+ }
138
+ }
@@ -0,0 +1,79 @@
1
+ // This file is generated by packages/tsrx-mcp/scripts/generate-docs-index.js.
2
+ // Do not edit it directly.
3
+
4
+ /** @typedef {{ slug: string, title: string, use_cases: string, content: string }} DocumentationSection */
5
+
6
+ /** @type {DocumentationSection[]} */
7
+ export const documentation_sections = [
8
+ {
9
+ slug: 'overview',
10
+ title: 'TSRX Overview',
11
+ use_cases:
12
+ 'always, introduction, explain tsrx, compare jsx, language model context, runtime targets',
13
+ content:
14
+ '# TSRX Overview\n\nTSRX is a TypeScript language extension for authoring declarative UI in .tsrx files. It adds a small set of syntax forms on top of TypeScript, while letting each target compiler define the runtime semantics.\n\nCore ideas:\n- component declarations use the component keyword.\n- JSX-like elements can be written as statements inside component bodies.\n- control-flow statements can contain template output.\n- expression-position JSX must use <>...</> or <tsx>...</tsx>.\n- lazy destructuring uses &[] and &{} for by-reference bindings.\n\nThe core language docs should stay target-neutral. After identifying the active runtime target, use target-specific docs, prompts, or skills for runtime imports, bundler setup, and semantics that are not defined by TSRX itself.\n\nSource: website-tsrx/src/pages/specification.tsrx',
15
+ },
16
+ {
17
+ slug: 'components',
18
+ title: 'Component Declarations',
19
+ use_cases:
20
+ 'components, component keyword, props, authoring .tsrx files, no jsx return syntax',
21
+ content:
22
+ '# Component Declarations\n\nComponents are declared with the component keyword, not as functions returning JSX.\n\n```tsx\ncomponent Button(props: { label: string }) {\n <button>{props.label}</button>\n}\n```\n\nInside a component body, template elements are statements. Do not generate `return <div />` from a component body. Use a bare `return;` only as a guard exit after emitting fallback template statements.\n\nSpecification grammar:\n\n```text\nComponentDeclaration :\n component BindingIdentifier ( FormalParametersopt ) { ComponentBody }\n\nComponentExpression :\n component BindingIdentifieropt ( FormalParametersopt ) { ComponentBody }\n```\n\nSource: website-tsrx/src/pages/specification.tsrx#components',
23
+ },
24
+ {
25
+ slug: 'text-and-template-expressions',
26
+ title: 'Text and Template Expressions',
27
+ use_cases:
28
+ 'text children, quoted text, raw text errors, html, text directive, string literals',
29
+ content:
30
+ '# Text and Template Expressions\n\nRaw unquoted text children are not valid TSRX. Static text should be written as a direct double-quoted child, and dynamic values should be wrapped in braces.\n\n```tsx\ncomponent Greeting({ name }: { name: string }) {\n <h1>"Hello"</h1>\n <p>{name}</p>\n}\n```\n\nSingle-quoted strings and template literals remain JavaScript expressions, so they must be inside braces. Use `{text expression}` for explicit text and `{html expression}` only for trusted HTML.\n\nSpecification grammar:\n\n```text\nDoubleQuotedTextChild :\n " JSXStringCharactersopt "\n\nTemplateExpression :\n { AssignmentExpression }\n { text AssignmentExpression }\n { html AssignmentExpression }\n```\n\nSource: website-tsrx/src/pages/specification.tsrx#templates',
31
+ },
32
+ {
33
+ slug: 'tsx-expression-values',
34
+ title: 'TSX Expression Values',
35
+ use_cases:
36
+ 'fragments, tsx tag, pass jsx as prop, return jsx from helper, expression position jsx',
37
+ content:
38
+ '# TSX Expression Values\n\nRegular template elements in component bodies are statements and have no value. When JSX must be used in expression position, wrap it in `<>...</>` or `<tsx>...</tsx>`.\n\n```tsx\ncomponent App() {\n const title = <><span>"Settings"</span></>;\n\n <Card title={title} />\n}\n```\n\nUse this for assigning JSX to variables, returning JSX from helper functions, or passing JSX as props. Do not write `const el = <div />` directly in TSRX component code.\n\nSpecification grammar:\n\n```text\nTsxElement :\n <tsx> TsxChildrenopt </tsx>\n\nTsxCompatElement :\n <tsx: IdentifierName> TsxChildrenopt </tsx: IdentifierName>\n\nTsxFragmentShorthand :\n <> TsxChildrenopt </>\n```\n\nSource: website-tsrx/src/pages/specification.tsrx#tsx-islands',
39
+ },
40
+ {
41
+ slug: 'control-flow',
42
+ title: 'Control Flow',
43
+ use_cases:
44
+ 'if else, for loops, switch, try catch, conditional rendering, lists, guard returns',
45
+ content:
46
+ '# Control Flow\n\nStandard JavaScript control flow can contain template statements inside component bodies and nested element children.\n\n```tsx\ncomponent List({ items }: { items: string[] }) {\n if (items.length === 0) {\n <p>"No items"</p>\n return;\n }\n\n <ul>\n for (const item of items) {\n <li>{item}</li>\n }\n </ul>\n}\n```\n\nA bare `return;` exits the current render path. A return with a value is invalid inside a TSRX component body.\n\nSource: website-tsrx/src/pages/features.tsrx#if',
47
+ },
48
+ {
49
+ slug: 'lazy-destructuring',
50
+ title: 'Lazy Destructuring',
51
+ use_cases: 'reactivity, lazy binding, ampersand destructuring, &[], &{}',
52
+ content:
53
+ '# Lazy Destructuring\n\nTSRX supports lazy binding patterns prefixed with `&`. They bind by reference rather than by value. The target compiler provides the runtime semantics.\n\n```tsx\nlet &[count] = source;\nlet &{ name, age } = props;\n```\n\nThe language defines the syntax and AST shape. Target-specific docs should explain what source values are valid and how reads and writes are lowered for the active runtime.\n\nSpecification grammar:\n\n```text\nLazyAssignmentStatement :\n LazyObjectBindingPattern = AssignmentExpression ;\n LazyArrayBindingPattern = AssignmentExpression ;\n```\n\nSource: website-tsrx/src/pages/specification.tsrx#lazy',
54
+ },
55
+ {
56
+ slug: 'style-and-server',
57
+ title: 'Style and Server Extensions',
58
+ use_cases:
59
+ '#style, scoped css, #server, server blocks, compile-time identifiers',
60
+ content:
61
+ '# Style and Server Extensions\n\n`#style` is a compile-time identifier for scoped CSS class names declared in the current module.\n\n```tsx\n<div class={#style.card} />\n```\n\n`#server { ... }` marks a lexical region intended for server compile targets. TSRX parses the block; target compilers decide how to emit or strip it.\n\nSpecification grammar:\n\n```text\nStyleIdentifier :\n #style\n\nStyleAccess :\n #style . IdentifierName\n #style [ StringLiteral ]\n\nServerIdentifier :\n #server\n\nServerBlock :\n #server { StatementListopt }\n\nServerMemberAccess :\n #server . IdentifierName\n```\n\nSource: website-tsrx/src/pages/specification.tsrx#style',
62
+ },
63
+ {
64
+ slug: 'target-integration',
65
+ title: 'Target Integration',
66
+ use_cases:
67
+ 'runtime target, compiler package, target-specific setup, skills, runtime semantics',
68
+ content:
69
+ '# Target Integration\n\nTSRX authoring syntax is shared, but output and runtime semantics are target-defined.\n\nThe core MCP server should detect the target, then hand off runtime-specific questions to a target-specific skill, prompt, resource set, or compiler-backed tool.\n\nTarget-specific layers should own:\n- package installation and bundler setup\n- runtime imports and helper APIs\n- compatibility blocks and escape hatches\n- compiler warnings and semantic restrictions\n- examples that depend on a specific rendering runtime\n\nWhen helping in an existing project, detect the target before generating code. If no target-specific layer is available, stay within target-neutral TSRX syntax and ask for confirmation before assuming runtime APIs.\n\nSource: website-tsrx/src/pages/getting-started.tsrx',
70
+ },
71
+ {
72
+ slug: 'tooling',
73
+ title: 'Tooling',
74
+ use_cases:
75
+ 'typescript plugin, typecheck, prettier, eslint, vscode, editor setup, diagnostics',
76
+ content:
77
+ "# Tooling\n\nCommon TSRX tooling packages:\n\n- `@tsrx/typescript-plugin` for TypeScript integration and `tsrx-tsc`.\n- `@tsrx/prettier-plugin` for formatting .tsrx files.\n- `@tsrx/eslint-plugin` for linting.\n- language server and editor integration packages for diagnostics, hover, completion, and definitions.\n\nUse the project package manager and match the active target runtime's compiler and bundler integration.\n\nSource: website-tsrx/src/pages/getting-started.tsrx#tooling-install",
78
+ },
79
+ ];
package/src/http.js ADDED
@@ -0,0 +1,99 @@
1
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
2
+ import { createTSRXMcpServer } from './server.js';
3
+
4
+ const DEFAULT_ALLOWED_METHODS = 'GET, POST, DELETE, OPTIONS';
5
+ const DEFAULT_ALLOWED_HEADERS = [
6
+ 'accept',
7
+ 'authorization',
8
+ 'content-type',
9
+ 'last-event-id',
10
+ 'mcp-protocol-version',
11
+ 'mcp-session-id',
12
+ ].join(', ');
13
+ const DEFAULT_EXPOSED_HEADERS = ['mcp-session-id', 'mcp-protocol-version'].join(', ');
14
+
15
+ /**
16
+ * @param {import('node:http').ServerResponse} res
17
+ * @param {{ origin?: string }} [options]
18
+ */
19
+ function apply_cors_headers(res, options = {}) {
20
+ res.setHeader('Access-Control-Allow-Origin', options.origin ?? '*');
21
+ res.setHeader('Access-Control-Allow-Methods', DEFAULT_ALLOWED_METHODS);
22
+ res.setHeader('Access-Control-Allow-Headers', DEFAULT_ALLOWED_HEADERS);
23
+ res.setHeader('Access-Control-Expose-Headers', DEFAULT_EXPOSED_HEADERS);
24
+ res.setHeader('Vary', 'Origin');
25
+ }
26
+
27
+ /**
28
+ * @param {import('node:http').ServerResponse} res
29
+ * @param {number} status
30
+ * @param {unknown} body
31
+ */
32
+ function send_json(res, status, body) {
33
+ res.statusCode = status;
34
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
35
+ res.end(JSON.stringify(body, null, 2));
36
+ }
37
+
38
+ /**
39
+ * @param {import('node:http').IncomingMessage} req
40
+ * @param {import('node:http').ServerResponse} res
41
+ * @param {{
42
+ * cors?: boolean,
43
+ * corsOrigin?: string,
44
+ * bearerToken?: string,
45
+ * enableJsonResponse?: boolean,
46
+ * }} [options]
47
+ */
48
+ export async function handleTSRXMcpNodeRequest(req, res, options = {}) {
49
+ if (options.cors !== false) {
50
+ apply_cors_headers(res, { origin: options.corsOrigin });
51
+ }
52
+
53
+ if (req.method === 'OPTIONS') {
54
+ res.statusCode = 204;
55
+ res.end();
56
+ return;
57
+ }
58
+
59
+ if (options.bearerToken) {
60
+ const authorization = req.headers.authorization;
61
+ if (authorization !== `Bearer ${options.bearerToken}`) {
62
+ send_json(res, 401, {
63
+ error: 'unauthorized',
64
+ message: 'Missing or invalid bearer token.',
65
+ });
66
+ return;
67
+ }
68
+ }
69
+
70
+ if (req.method !== 'GET' && req.method !== 'POST' && req.method !== 'DELETE') {
71
+ send_json(res, 405, {
72
+ error: 'method_not_allowed',
73
+ message: 'TSRX MCP only accepts GET, POST, DELETE, and OPTIONS requests.',
74
+ });
75
+ return;
76
+ }
77
+
78
+ const server = createTSRXMcpServer({ remote: true });
79
+ const transport = new StreamableHTTPServerTransport({
80
+ sessionIdGenerator: undefined,
81
+ enableJsonResponse: options.enableJsonResponse ?? true,
82
+ });
83
+
84
+ try {
85
+ await server.connect(transport);
86
+ await transport.handleRequest(req, res);
87
+ } catch (error) {
88
+ if (!res.headersSent) {
89
+ send_json(res, 500, {
90
+ error: 'mcp_request_failed',
91
+ message: error instanceof Error ? error.message : String(error),
92
+ });
93
+ } else {
94
+ res.end();
95
+ }
96
+ } finally {
97
+ await server.close().catch(() => {});
98
+ }
99
+ }
package/src/index.js ADDED
@@ -0,0 +1,25 @@
1
+ export {
2
+ createTSRXMcpServer,
3
+ analyze_tsrx_handler,
4
+ compile_tsrx_handler,
5
+ detect_target_handler,
6
+ format_tsrx_handler,
7
+ get_documentation_handler,
8
+ inspect_project_handler,
9
+ list_sections_handler,
10
+ validate_tsrx_file_handler,
11
+ } from './server.js';
12
+
13
+ export { handleTSRXMcpNodeRequest } from './http.js';
14
+ export { analyze_tsrx } from './analyze.js';
15
+ export { compile_tsrx } from './compile.js';
16
+ export { format_tsrx } from './format.js';
17
+ export { inspect_project } from './inspect.js';
18
+ export { validate_tsrx_file } from './validate.js';
19
+ export { detect_target, TARGET_CANDIDATES } from './target.js';
20
+ export {
21
+ documentation_sections,
22
+ find_documentation_section,
23
+ find_similar_documentation_sections,
24
+ list_documentation_sections,
25
+ } from './docs.js';
package/src/inspect.js ADDED
@@ -0,0 +1,235 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { CONFIG_FILES, detect_target, TARGET_CANDIDATES } from './target.js';
4
+
5
+ const DEPENDENCY_FIELDS = [
6
+ 'dependencies',
7
+ 'devDependencies',
8
+ 'peerDependencies',
9
+ 'optionalDependencies',
10
+ ];
11
+
12
+ const TOOLING_PACKAGES = [
13
+ '@tsrx/prettier-plugin',
14
+ '@tsrx/eslint-plugin',
15
+ '@tsrx/typescript-plugin',
16
+ '@tsrx/language-server',
17
+ '@tsrx/vscode-plugin',
18
+ ];
19
+
20
+ const PACKAGE_MANAGER_LOCKFILES = [
21
+ { file: 'pnpm-lock.yaml', packageManager: 'pnpm' },
22
+ { file: 'package-lock.json', packageManager: 'npm' },
23
+ { file: 'yarn.lock', packageManager: 'yarn' },
24
+ { file: 'bun.lock', packageManager: 'bun' },
25
+ { file: 'bun.lockb', packageManager: 'bun' },
26
+ ];
27
+
28
+ /**
29
+ * @param {Record<string, unknown>} package_json
30
+ */
31
+ function get_dependencies(package_json) {
32
+ const dependencies = [];
33
+ for (const field of DEPENDENCY_FIELDS) {
34
+ const values = package_json[field];
35
+ if (!values || typeof values !== 'object') continue;
36
+ for (const [name, version] of Object.entries(values)) {
37
+ dependencies.push({
38
+ name,
39
+ version: typeof version === 'string' ? version : String(version),
40
+ field,
41
+ });
42
+ }
43
+ }
44
+ return dependencies.sort((a, b) => a.name.localeCompare(b.name));
45
+ }
46
+
47
+ /**
48
+ * @param {string} root
49
+ */
50
+ function find_config_files(root) {
51
+ return CONFIG_FILES.filter((file) => fs.existsSync(path.join(root, file)));
52
+ }
53
+
54
+ /**
55
+ * @param {string} root
56
+ * @param {Record<string, unknown>} package_json
57
+ */
58
+ function detect_package_manager(root, package_json) {
59
+ if (typeof package_json.packageManager === 'string') {
60
+ return package_json.packageManager.split('@')[0] || package_json.packageManager;
61
+ }
62
+ const match = PACKAGE_MANAGER_LOCKFILES.find(({ file }) => fs.existsSync(path.join(root, file)));
63
+ return match?.packageManager ?? null;
64
+ }
65
+
66
+ /**
67
+ * @param {string | null} package_manager
68
+ */
69
+ function runner(package_manager) {
70
+ if (package_manager === 'npm') return 'npm run';
71
+ if (package_manager === 'yarn') return 'yarn';
72
+ if (package_manager === 'bun') return 'bun run';
73
+ return 'pnpm';
74
+ }
75
+
76
+ /**
77
+ * @param {Record<string, unknown>} package_json
78
+ */
79
+ function get_scripts(package_json) {
80
+ const scripts = package_json.scripts;
81
+ if (!scripts || typeof scripts !== 'object') return {};
82
+ /** @type {Record<string, string>} */
83
+ const normalized = {};
84
+ for (const [name, command] of Object.entries(scripts)) {
85
+ if (typeof command === 'string') normalized[name] = command;
86
+ }
87
+ return normalized;
88
+ }
89
+
90
+ /**
91
+ * @param {Record<string, string>} scripts
92
+ * @param {string | null} package_manager
93
+ */
94
+ function get_likely_commands(scripts, package_manager) {
95
+ const run = runner(package_manager);
96
+ return {
97
+ format: scripts.format ? `${run} format` : null,
98
+ formatCheck: scripts['format:check'] ? `${run} format:check` : null,
99
+ typecheck: scripts.typecheck ? `${run} typecheck` : null,
100
+ test: scripts.test ? `${run} test` : null,
101
+ };
102
+ }
103
+
104
+ /**
105
+ * @param {Array<{ name: string, version: string, field: string }>} dependencies
106
+ */
107
+ function get_tsrx_packages(dependencies) {
108
+ return dependencies.filter(
109
+ ({ name }) =>
110
+ name === 'ripple' ||
111
+ name.startsWith('@tsrx/') ||
112
+ name.startsWith('@ripple-ts/') ||
113
+ name.includes('tsrx'),
114
+ );
115
+ }
116
+
117
+ /**
118
+ * @param {Array<{ name: string, version: string, field: string }>} dependencies
119
+ */
120
+ function get_tooling(dependencies) {
121
+ return TOOLING_PACKAGES.map((name) => {
122
+ const dependency = dependencies.find((item) => item.name === name);
123
+ return {
124
+ name,
125
+ present: Boolean(dependency),
126
+ version: dependency?.version ?? null,
127
+ field: dependency?.field ?? null,
128
+ };
129
+ });
130
+ }
131
+
132
+ /**
133
+ * @param {Array<{ name: string, version: string, field: string }>} dependencies
134
+ */
135
+ function get_target_package_status(dependencies) {
136
+ return TARGET_CANDIDATES.map((candidate) => {
137
+ const packages = [];
138
+ for (const signal of candidate.signals) {
139
+ const dependency = dependencies.find((item) => item.name === signal);
140
+ if (dependency) packages.push(dependency);
141
+ }
142
+ return {
143
+ target: candidate.target,
144
+ compilerPackage: candidate.compilerPackage,
145
+ present: packages.length > 0,
146
+ packages: packages.map((dependency) => ({
147
+ name: dependency.name,
148
+ version: dependency.version,
149
+ field: dependency.field,
150
+ })),
151
+ };
152
+ });
153
+ }
154
+
155
+ /**
156
+ * @param {{ cwd?: string }} input
157
+ */
158
+ export function inspect_project(input = {}) {
159
+ const detection = detect_target(input.cwd);
160
+ const cwd = detection.cwd;
161
+ const package_json_path = detection.packageJsonPath;
162
+
163
+ if (!package_json_path) {
164
+ return {
165
+ cwd,
166
+ root: null,
167
+ packageJsonPath: null,
168
+ packageName: null,
169
+ packageManager: null,
170
+ target: detection,
171
+ configFiles: [],
172
+ tsrxPackages: [],
173
+ targetPackages: [],
174
+ tooling: [],
175
+ scripts: {},
176
+ commands: {
177
+ format: null,
178
+ formatCheck: null,
179
+ typecheck: null,
180
+ test: null,
181
+ },
182
+ message: detection.message,
183
+ };
184
+ }
185
+
186
+ const root = path.dirname(package_json_path);
187
+ /** @type {Record<string, unknown>} */
188
+ let package_json;
189
+ try {
190
+ package_json = JSON.parse(fs.readFileSync(package_json_path, 'utf8'));
191
+ } catch (error) {
192
+ return {
193
+ cwd,
194
+ root,
195
+ packageJsonPath: package_json_path,
196
+ packageName: null,
197
+ packageManager: null,
198
+ target: detection,
199
+ configFiles: find_config_files(root),
200
+ tsrxPackages: [],
201
+ targetPackages: [],
202
+ tooling: [],
203
+ scripts: {},
204
+ commands: {
205
+ format: null,
206
+ formatCheck: null,
207
+ typecheck: null,
208
+ test: null,
209
+ },
210
+ message: `Could not parse package.json: ${error instanceof Error ? error.message : String(error)}`,
211
+ };
212
+ }
213
+
214
+ const dependencies = get_dependencies(package_json);
215
+ const scripts = get_scripts(package_json);
216
+ const package_manager = detect_package_manager(root, package_json);
217
+ const tsrx_packages = get_tsrx_packages(dependencies);
218
+ const target_packages = get_target_package_status(dependencies);
219
+
220
+ return {
221
+ cwd,
222
+ root,
223
+ packageJsonPath: package_json_path,
224
+ packageName: typeof package_json.name === 'string' ? package_json.name : null,
225
+ packageManager: package_manager,
226
+ target: detection,
227
+ configFiles: find_config_files(root),
228
+ tsrxPackages: tsrx_packages,
229
+ targetPackages: target_packages,
230
+ tooling: get_tooling(dependencies),
231
+ scripts,
232
+ commands: get_likely_commands(scripts, package_manager),
233
+ message: `${detection.message} Found ${tsrx_packages.length} TSRX-related package${tsrx_packages.length === 1 ? '' : 's'}.`,
234
+ };
235
+ }