@tanstack/cli 0.60.1 → 0.62.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.
Files changed (66) hide show
  1. package/dist/cli.js +266 -11
  2. package/dist/command-line.js +103 -8
  3. package/dist/discovery.js +144 -0
  4. package/dist/options.js +35 -2
  5. package/dist/types/command-line.d.ts +7 -0
  6. package/dist/types/{mcp/types.d.ts → discovery.d.ts} +23 -75
  7. package/dist/types/types.d.ts +1 -2
  8. package/dist/types/ui-prompts.d.ts +5 -0
  9. package/dist/ui-prompts.js +26 -0
  10. package/package.json +6 -5
  11. package/skills/CHANGELOG.md +18 -0
  12. package/skills/add-addons-existing-app/SKILL.md +113 -0
  13. package/skills/choose-ecosystem-integrations/SKILL.md +140 -0
  14. package/skills/choose-ecosystem-integrations/references/authentication-providers.md +19 -0
  15. package/skills/choose-ecosystem-integrations/references/data-layer-providers.md +20 -0
  16. package/skills/choose-ecosystem-integrations/references/deployment-targets.md +19 -0
  17. package/skills/create-app-scaffold/SKILL.md +132 -0
  18. package/skills/create-app-scaffold/references/create-flag-compatibility-matrix.md +34 -0
  19. package/skills/create-app-scaffold/references/deployment-providers.md +19 -0
  20. package/skills/create-app-scaffold/references/framework-adapters.md +17 -0
  21. package/skills/create-app-scaffold/references/toolchains.md +17 -0
  22. package/skills/maintain-custom-addons-dev-watch/SKILL.md +118 -0
  23. package/skills/query-docs-library-metadata/SKILL.md +85 -0
  24. package/skills/query-docs-library-metadata/references/discovery-command-output-schemas.md +70 -0
  25. package/CHANGELOG.md +0 -787
  26. package/dist/mcp/api.js +0 -31
  27. package/dist/mcp/tools.js +0 -250
  28. package/dist/mcp/types.js +0 -37
  29. package/dist/mcp.js +0 -181
  30. package/dist/types/mcp/api.d.ts +0 -4
  31. package/dist/types/mcp/tools.d.ts +0 -2
  32. package/dist/types/mcp.d.ts +0 -5
  33. package/playwright-report/index.html +0 -85
  34. package/playwright.config.ts +0 -21
  35. package/src/bin.ts +0 -15
  36. package/src/cli.ts +0 -767
  37. package/src/command-line.ts +0 -473
  38. package/src/dev-watch.ts +0 -564
  39. package/src/file-syncer.ts +0 -263
  40. package/src/index.ts +0 -21
  41. package/src/mcp/api.ts +0 -42
  42. package/src/mcp/tools.ts +0 -323
  43. package/src/mcp/types.ts +0 -46
  44. package/src/mcp.ts +0 -263
  45. package/src/options.ts +0 -234
  46. package/src/types.ts +0 -28
  47. package/src/ui-environment.ts +0 -74
  48. package/src/ui-prompts.ts +0 -355
  49. package/src/utils.ts +0 -30
  50. package/test-results/.last-run.json +0 -4
  51. package/tests/command-line.test.ts +0 -622
  52. package/tests/index.test.ts +0 -9
  53. package/tests/mcp.test.ts +0 -225
  54. package/tests/options.test.ts +0 -216
  55. package/tests/setupVitest.ts +0 -6
  56. package/tests/ui-environment.test.ts +0 -97
  57. package/tests/ui-prompts.test.ts +0 -205
  58. package/tests-e2e/addons-smoke.spec.ts +0 -31
  59. package/tests-e2e/create-smoke.spec.ts +0 -39
  60. package/tests-e2e/helpers.ts +0 -526
  61. package/tests-e2e/matrix-opportunistic.spec.ts +0 -142
  62. package/tests-e2e/router-only-smoke.spec.ts +0 -68
  63. package/tests-e2e/solid-smoke.spec.ts +0 -25
  64. package/tests-e2e/templates-smoke.spec.ts +0 -52
  65. package/tsconfig.json +0 -17
  66. package/vitest.config.js +0 -8
@@ -1,263 +0,0 @@
1
- import fs from 'node:fs'
2
- import path from 'node:path'
3
- import crypto from 'node:crypto'
4
- import * as diff from 'diff'
5
-
6
- export interface FileUpdate {
7
- path: string
8
- diff?: string
9
- }
10
-
11
- export interface SyncResult {
12
- updated: Array<FileUpdate>
13
- skipped: Array<string>
14
- created: Array<string>
15
- deleted: Array<string>
16
- sourceFiles: Array<string>
17
- errors: Array<string>
18
- }
19
-
20
- export interface SyncOptions {
21
- deleteRemoved?: boolean
22
- previousSourceFiles?: Set<string>
23
- }
24
-
25
- export class FileSyncer {
26
- async sync(
27
- sourceDir: string,
28
- targetDir: string,
29
- options?: SyncOptions,
30
- ): Promise<SyncResult> {
31
- const result: SyncResult = {
32
- updated: [],
33
- skipped: [],
34
- created: [],
35
- deleted: [],
36
- sourceFiles: [],
37
- errors: [],
38
- }
39
-
40
- // Ensure directories exist
41
- if (!fs.existsSync(sourceDir)) {
42
- throw new Error(`Source directory does not exist: ${sourceDir}`)
43
- }
44
- if (!fs.existsSync(targetDir)) {
45
- throw new Error(`Target directory does not exist: ${targetDir}`)
46
- }
47
-
48
- // Walk through source directory and sync files
49
- await this.syncDirectory(sourceDir, targetDir, sourceDir, result)
50
-
51
- if (options?.deleteRemoved && options.previousSourceFiles) {
52
- const currentSourceFileSet = new Set(result.sourceFiles)
53
- await this.deleteRemovedFiles(
54
- targetDir,
55
- options.previousSourceFiles,
56
- currentSourceFileSet,
57
- result,
58
- )
59
- }
60
-
61
- return result
62
- }
63
-
64
- private async syncDirectory(
65
- currentPath: string,
66
- targetBase: string,
67
- sourceBase: string,
68
- result: SyncResult,
69
- ): Promise<void> {
70
- const entries = await fs.promises.readdir(currentPath, {
71
- withFileTypes: true,
72
- })
73
-
74
- for (const entry of entries) {
75
- const sourcePath = path.join(currentPath, entry.name)
76
- const relativePath = path.relative(sourceBase, sourcePath)
77
- const targetPath = path.join(targetBase, relativePath)
78
-
79
- // Skip certain directories
80
- if (entry.isDirectory()) {
81
- if (this.shouldSkipDirectory(entry.name)) {
82
- continue
83
- }
84
-
85
- // Ensure target directory exists
86
- if (!fs.existsSync(targetPath)) {
87
- await fs.promises.mkdir(targetPath, { recursive: true })
88
- }
89
-
90
- // Recursively sync subdirectory
91
- await this.syncDirectory(sourcePath, targetBase, sourceBase, result)
92
- } else if (entry.isFile()) {
93
- // Skip certain files
94
- if (this.shouldSkipFile(entry.name)) {
95
- continue
96
- }
97
-
98
- result.sourceFiles.push(relativePath)
99
-
100
- try {
101
- const shouldUpdate = await this.shouldUpdateFile(
102
- sourcePath,
103
- targetPath,
104
- )
105
-
106
- if (shouldUpdate) {
107
- // Check if file exists to generate diff
108
- let fileDiff: string | undefined
109
- const targetExists = fs.existsSync(targetPath)
110
-
111
- if (targetExists) {
112
- // Generate diff for existing files
113
- const oldContent = await fs.promises.readFile(targetPath, 'utf-8')
114
- const newContent = await fs.promises.readFile(sourcePath, 'utf-8')
115
-
116
- const changes = diff.createPatch(
117
- relativePath,
118
- oldContent,
119
- newContent,
120
- 'Previous',
121
- 'Current',
122
- )
123
-
124
- // Only include diff if there are actual changes
125
- if (changes && changes.split('\n').length > 5) {
126
- fileDiff = changes
127
- }
128
- }
129
-
130
- // Copy file
131
- await fs.promises.copyFile(sourcePath, targetPath)
132
-
133
- // Touch file to trigger dev server reload
134
- const now = new Date()
135
- await fs.promises.utimes(targetPath, now, now)
136
-
137
- if (!targetExists) {
138
- result.created.push(relativePath)
139
- } else {
140
- result.updated.push({
141
- path: relativePath,
142
- diff: fileDiff,
143
- })
144
- }
145
- } else {
146
- result.skipped.push(relativePath)
147
- }
148
- } catch (error) {
149
- result.errors.push(
150
- `${relativePath}: ${error instanceof Error ? error.message : String(error)}`,
151
- )
152
- }
153
- }
154
- }
155
- }
156
-
157
- private async shouldUpdateFile(
158
- sourcePath: string,
159
- targetPath: string,
160
- ): Promise<boolean> {
161
- // If target doesn't exist, definitely update
162
- if (!fs.existsSync(targetPath)) {
163
- return true
164
- }
165
-
166
- // Compare file sizes first (quick check)
167
- const [sourceStats, targetStats] = await Promise.all([
168
- fs.promises.stat(sourcePath),
169
- fs.promises.stat(targetPath),
170
- ])
171
-
172
- if (sourceStats.size !== targetStats.size) {
173
- return true
174
- }
175
-
176
- // Compare MD5 hashes for content
177
- const [sourceHash, targetHash] = await Promise.all([
178
- this.calculateHash(sourcePath),
179
- this.calculateHash(targetPath),
180
- ])
181
-
182
- return sourceHash !== targetHash
183
- }
184
-
185
- private async calculateHash(filePath: string): Promise<string> {
186
- return new Promise((resolve, reject) => {
187
- const hash = crypto.createHash('md5')
188
- const stream = fs.createReadStream(filePath)
189
-
190
- stream.on('data', (data) => hash.update(data))
191
- stream.on('end', () => resolve(hash.digest('hex')))
192
- stream.on('error', reject)
193
- })
194
- }
195
-
196
- private shouldSkipDirectory(name: string): boolean {
197
- const skipDirs = [
198
- 'node_modules',
199
- '.git',
200
- 'dist',
201
- 'build',
202
- '.next',
203
- '.nuxt',
204
- '.cache',
205
- '.tmp-dev',
206
- 'coverage',
207
- '.turbo',
208
- ]
209
-
210
- return skipDirs.includes(name) || name.startsWith('.')
211
- }
212
-
213
- private shouldSkipFile(name: string): boolean {
214
- const skipFiles = [
215
- '.DS_Store',
216
- 'Thumbs.db',
217
- 'desktop.ini',
218
- '.cta.json', // Skip .cta.json as it contains framework ID that changes each build
219
- ]
220
-
221
- const skipExtensions = ['.log', '.lock', '.pid', '.seed', '.sqlite']
222
-
223
- if (skipFiles.includes(name)) {
224
- return true
225
- }
226
-
227
- const ext = path.extname(name).toLowerCase()
228
- return skipExtensions.includes(ext)
229
- }
230
-
231
- private async deleteRemovedFiles(
232
- targetDir: string,
233
- previousSourceFiles: Set<string>,
234
- currentSourceFiles: Set<string>,
235
- result: SyncResult,
236
- ): Promise<void> {
237
- for (const relativePath of previousSourceFiles) {
238
- if (currentSourceFiles.has(relativePath)) {
239
- continue
240
- }
241
-
242
- const targetPath = path.join(targetDir, relativePath)
243
-
244
- try {
245
- if (!fs.existsSync(targetPath)) {
246
- continue
247
- }
248
-
249
- const stats = await fs.promises.stat(targetPath)
250
- if (!stats.isFile()) {
251
- continue
252
- }
253
-
254
- await fs.promises.unlink(targetPath)
255
- result.deleted.push(relativePath)
256
- } catch (error) {
257
- result.errors.push(
258
- `${relativePath}: ${error instanceof Error ? error.message : String(error)}`,
259
- )
260
- }
261
- }
262
- }
263
- }
package/src/index.ts DELETED
@@ -1,21 +0,0 @@
1
- import { pathToFileURL } from 'node:url'
2
- import {
3
- createReactFrameworkDefinition,
4
- createSolidFrameworkDefinition,
5
- } from '@tanstack/create'
6
-
7
- import { cli } from './cli.js'
8
-
9
- export { cli }
10
-
11
- const entryPath = process.argv[1]
12
- if (entryPath && import.meta.url === pathToFileURL(entryPath).href) {
13
- cli({
14
- name: 'tanstack',
15
- appName: 'TanStack',
16
- frameworkDefinitionInitializers: [
17
- createReactFrameworkDefinition,
18
- createSolidFrameworkDefinition,
19
- ],
20
- })
21
- }
package/src/mcp/api.ts DELETED
@@ -1,42 +0,0 @@
1
- import { LibrariesResponseSchema, PartnersResponseSchema } from './types.js'
2
- import type { LibrariesResponse, PartnersResponse } from './types.js'
3
-
4
- const TANSTACK_API_BASE = 'https://tanstack.com/api/data'
5
-
6
- export async function fetchLibraries(): Promise<LibrariesResponse> {
7
- const response = await fetch(`${TANSTACK_API_BASE}/libraries`)
8
- if (!response.ok) {
9
- throw new Error(`Failed to fetch libraries: ${response.statusText}`)
10
- }
11
- const data = await response.json()
12
- return LibrariesResponseSchema.parse(data)
13
- }
14
-
15
- export async function fetchPartners(): Promise<PartnersResponse> {
16
- const response = await fetch(`${TANSTACK_API_BASE}/partners`)
17
- if (!response.ok) {
18
- throw new Error(`Failed to fetch partners: ${response.statusText}`)
19
- }
20
- const data = await response.json()
21
- return PartnersResponseSchema.parse(data)
22
- }
23
-
24
- export async function fetchDocContent(
25
- repo: string,
26
- branch: string,
27
- filePath: string,
28
- ): Promise<string | null> {
29
- const url = `https://raw.githubusercontent.com/${repo}/${branch}/${filePath}`
30
- const response = await fetch(url, {
31
- headers: { 'User-Agent': 'tanstack-cli' },
32
- })
33
-
34
- if (!response.ok) {
35
- if (response.status === 404) {
36
- return null
37
- }
38
- throw new Error(`Failed to fetch doc: ${response.statusText}`)
39
- }
40
-
41
- return response.text()
42
- }
package/src/mcp/tools.ts DELETED
@@ -1,323 +0,0 @@
1
- import { z } from 'zod'
2
- import { fetchDocContent, fetchLibraries, fetchPartners } from './api.js'
3
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
4
-
5
- // Algolia config (public read-only keys)
6
- const ALGOLIA_APP_ID = 'FQ0DQ6MA3C'
7
- const ALGOLIA_API_KEY = '10c34d6a5c89f6048cf644d601e65172'
8
- const ALGOLIA_INDEX = 'tanstack-test'
9
-
10
- const GROUP_KEYS = ['state', 'headlessUI', 'performance', 'tooling'] as const
11
-
12
- function jsonResult(data: unknown) {
13
- return { content: [{ type: 'text' as const, text: JSON.stringify(data, null, 2) }] }
14
- }
15
-
16
- function errorResult(error: string) {
17
- return { content: [{ type: 'text' as const, text: `Error: ${error}` }], isError: true }
18
- }
19
-
20
- export function registerDocTools(server: McpServer) {
21
- // Tool: tanstack_list_libraries
22
- server.tool(
23
- 'tanstack_list_libraries',
24
- 'List TanStack libraries with metadata, frameworks, and docs URLs.',
25
- {
26
- group: z
27
- .enum(GROUP_KEYS)
28
- .optional()
29
- .describe('Filter libraries by group. Options: state, headlessUI, performance, tooling'),
30
- },
31
- async ({ group }) => {
32
- try {
33
- const data = await fetchLibraries()
34
- let libraries = data.libraries
35
-
36
- if (group && data.groups[group]) {
37
- const groupIds = data.groups[group]
38
- libraries = libraries.filter((lib) => groupIds.includes(lib.id))
39
- }
40
-
41
- const groupName = group ? data.groupNames[group] || group : 'All Libraries'
42
-
43
- return jsonResult({
44
- group: groupName,
45
- count: libraries.length,
46
- libraries: libraries.map((lib) => ({
47
- id: lib.id,
48
- name: lib.name,
49
- tagline: lib.tagline,
50
- description: lib.description,
51
- frameworks: lib.frameworks,
52
- latestVersion: lib.latestVersion,
53
- docsUrl: lib.docsUrl,
54
- githubUrl: lib.githubUrl,
55
- })),
56
- })
57
- } catch (error) {
58
- return errorResult(String(error))
59
- }
60
- },
61
- )
62
-
63
- // Tool: tanstack_doc
64
- server.tool(
65
- 'tanstack_doc',
66
- 'Fetch a TanStack documentation page by library and path.',
67
- {
68
- library: z.string().describe('Library ID (e.g., query, router, table, form)'),
69
- path: z.string().describe('Documentation path (e.g., framework/react/overview)'),
70
- version: z.string().optional().describe('Version (e.g., v5, v1). Defaults to latest'),
71
- },
72
- async ({ library: libraryId, path, version = 'latest' }) => {
73
- try {
74
- const data = await fetchLibraries()
75
- const library = data.libraries.find((l) => l.id === libraryId)
76
-
77
- if (!library) {
78
- return errorResult(
79
- `Library "${libraryId}" not found. Use tanstack_list_libraries to see available libraries.`,
80
- )
81
- }
82
-
83
- if (version !== 'latest' && !library.availableVersions.includes(version)) {
84
- return errorResult(
85
- `Version "${version}" not found for ${library.name}. Available: ${library.availableVersions.join(', ')}`,
86
- )
87
- }
88
-
89
- // Resolve branch
90
- const branch =
91
- version === 'latest' || version === library.latestVersion
92
- ? library.latestBranch || 'main'
93
- : version
94
-
95
- const docsRoot = library.docsRoot || 'docs'
96
- const filePath = `${docsRoot}/${path}.md`
97
- const content = await fetchDocContent(library.repo, branch, filePath)
98
-
99
- if (!content) {
100
- return errorResult(
101
- `Document not found: ${library.name} / ${path} (version: ${version})`,
102
- )
103
- }
104
-
105
- // Extract frontmatter title if present
106
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/)
107
- let title = path.split('/').pop() || 'Untitled'
108
- let docContent = content
109
-
110
- if (frontmatterMatch && frontmatterMatch[1]) {
111
- const frontmatter = frontmatterMatch[1]
112
- const titleMatch = frontmatter.match(/title:\s*['"]?([^'"\n]+)['"]?/)
113
- if (titleMatch && titleMatch[1]) {
114
- title = titleMatch[1]
115
- }
116
- docContent = content.slice(frontmatterMatch[0].length).trim()
117
- }
118
-
119
- return jsonResult({
120
- title,
121
- content: docContent,
122
- url: `https://tanstack.com/${libraryId}/${version}/docs/${path}`,
123
- library: library.name,
124
- version: version === 'latest' ? library.latestVersion : version,
125
- })
126
- } catch (error) {
127
- return errorResult(String(error))
128
- }
129
- },
130
- )
131
-
132
- // Tool: tanstack_search_docs
133
- server.tool(
134
- 'tanstack_search_docs',
135
- 'Search TanStack documentation. Returns matching pages with snippets.',
136
- {
137
- query: z.string().describe('Search query'),
138
- library: z.string().optional().describe('Filter to specific library (e.g., query, router)'),
139
- framework: z
140
- .string()
141
- .optional()
142
- .describe('Filter to specific framework (e.g., react, vue, solid)'),
143
- limit: z
144
- .number()
145
- .min(1)
146
- .max(50)
147
- .optional()
148
- .describe('Maximum number of results (default: 10, max: 50)'),
149
- },
150
- async ({ query, library, framework, limit = 10 }) => {
151
- try {
152
- const ALL_LIBRARIES = [
153
- 'config', 'form', 'optimistic', 'pacer', 'query', 'ranger',
154
- 'react-charts', 'router', 'start', 'store', 'table', 'virtual', 'db', 'devtools',
155
- ]
156
- const ALL_FRAMEWORKS = ['react', 'vue', 'solid', 'svelte', 'angular']
157
-
158
- // Build filters
159
- const filterParts: Array<string> = ['version:latest']
160
-
161
- if (library) {
162
- const otherLibraries = ALL_LIBRARIES.filter((l) => l !== library)
163
- const exclusions = otherLibraries.map((l) => `NOT library:${l}`).join(' AND ')
164
- if (exclusions) filterParts.push(`(${exclusions})`)
165
- }
166
-
167
- if (framework) {
168
- const otherFrameworks = ALL_FRAMEWORKS.filter((f) => f !== framework)
169
- const exclusions = otherFrameworks.map((f) => `NOT framework:${f}`).join(' AND ')
170
- if (exclusions) filterParts.push(`(${exclusions})`)
171
- }
172
-
173
- // Call Algolia REST API directly
174
- const searchParams = {
175
- requests: [
176
- {
177
- indexName: ALGOLIA_INDEX,
178
- query,
179
- hitsPerPage: Math.min(limit, 50),
180
- filters: filterParts.join(' AND '),
181
- attributesToRetrieve: ['hierarchy', 'url', 'content', 'library'],
182
- attributesToSnippet: ['content:80'],
183
- },
184
- ],
185
- }
186
-
187
- const response = await fetch(
188
- `https://${ALGOLIA_APP_ID}-dsn.algolia.net/1/indexes/*/queries`,
189
- {
190
- method: 'POST',
191
- headers: {
192
- 'Content-Type': 'application/json',
193
- 'X-Algolia-Application-Id': ALGOLIA_APP_ID,
194
- 'X-Algolia-API-Key': ALGOLIA_API_KEY,
195
- },
196
- body: JSON.stringify(searchParams),
197
- },
198
- )
199
-
200
- if (!response.ok) {
201
- return errorResult(`Algolia search failed: ${response.statusText}`)
202
- }
203
-
204
- const searchResponse = await response.json() as {
205
- results: Array<{
206
- hits: Array<{
207
- objectID: string
208
- url: string
209
- library?: string
210
- hierarchy: Record<string, string | undefined>
211
- content?: string
212
- _snippetResult?: { content?: { value?: string } }
213
- }>
214
- nbHits?: number
215
- }>
216
- }
217
-
218
- const searchResult = searchResponse.results[0]
219
- if (!searchResult) {
220
- return jsonResult({ query, totalHits: 0, results: [] })
221
- }
222
-
223
- const results = searchResult.hits.map((hit) => {
224
- const breadcrumb = Object.values(hit.hierarchy).filter((v): v is string => Boolean(v))
225
- return {
226
- title: hit.hierarchy.lvl1 || hit.hierarchy.lvl0 || 'Untitled',
227
- url: hit.url,
228
- snippet: hit._snippetResult?.content?.value || hit.content || '',
229
- library: hit.library || 'unknown',
230
- breadcrumb,
231
- }
232
- })
233
-
234
- return jsonResult({
235
- query,
236
- totalHits: searchResult.nbHits || results.length,
237
- results,
238
- })
239
- } catch (error) {
240
- return errorResult(String(error))
241
- }
242
- },
243
- )
244
-
245
- // Tool: tanstack_ecosystem
246
- server.tool(
247
- 'tanstack_ecosystem',
248
- 'Ecosystem partner recommendations. Filter by category (database, auth, deployment, monitoring, cms, api, data-grid) or library.',
249
- {
250
- category: z
251
- .string()
252
- .optional()
253
- .describe(
254
- 'Filter by category: database, auth, deployment, monitoring, cms, api, data-grid, code-review, learning',
255
- ),
256
- library: z
257
- .string()
258
- .optional()
259
- .describe('Filter by TanStack library (e.g., start, router, query, table)'),
260
- },
261
- async ({ category, library }) => {
262
- try {
263
- const data = await fetchPartners()
264
-
265
- // Category aliases
266
- const categoryAliases: Record<string, string> = {
267
- db: 'database',
268
- postgres: 'database',
269
- sql: 'database',
270
- login: 'auth',
271
- authentication: 'auth',
272
- hosting: 'deployment',
273
- deploy: 'deployment',
274
- serverless: 'deployment',
275
- errors: 'monitoring',
276
- logging: 'monitoring',
277
- content: 'cms',
278
- 'api-keys': 'api',
279
- grid: 'data-grid',
280
- review: 'code-review',
281
- courses: 'learning',
282
- }
283
-
284
- let resolvedCategory: string | undefined
285
- if (category) {
286
- const normalized = category.toLowerCase().trim()
287
- resolvedCategory = categoryAliases[normalized] || normalized
288
- if (!data.categories.includes(resolvedCategory)) {
289
- resolvedCategory = undefined
290
- }
291
- }
292
-
293
- const lib = library?.toLowerCase().trim()
294
-
295
- const partners = data.partners
296
- .filter((p) => !resolvedCategory || p.category === resolvedCategory)
297
- .filter((p) => !lib || p.libraries.some((l) => l === lib))
298
- .map((p) => ({
299
- id: p.id,
300
- name: p.name,
301
- tagline: p.tagline,
302
- description: p.description,
303
- category: p.category,
304
- categoryLabel: p.categoryLabel,
305
- url: p.url,
306
- libraries: p.libraries,
307
- }))
308
-
309
- return jsonResult({
310
- query: {
311
- category,
312
- categoryResolved: resolvedCategory,
313
- library,
314
- },
315
- count: partners.length,
316
- partners,
317
- })
318
- } catch (error) {
319
- return errorResult(String(error))
320
- }
321
- },
322
- )
323
- }
package/src/mcp/types.ts DELETED
@@ -1,46 +0,0 @@
1
- import { z } from 'zod'
2
-
3
- // API response types from tanstack.com
4
- export const LibrarySchema = z.object({
5
- id: z.string(),
6
- name: z.string(),
7
- tagline: z.string(),
8
- description: z.string().optional(),
9
- frameworks: z.array(z.string()),
10
- latestVersion: z.string(),
11
- latestBranch: z.string().optional(),
12
- availableVersions: z.array(z.string()),
13
- repo: z.string(),
14
- docsRoot: z.string().optional(),
15
- defaultDocs: z.string().optional(),
16
- docsUrl: z.string().optional(),
17
- githubUrl: z.string().optional(),
18
- })
19
-
20
- export const LibrariesResponseSchema = z.object({
21
- libraries: z.array(LibrarySchema),
22
- groups: z.record(z.array(z.string())),
23
- groupNames: z.record(z.string()),
24
- })
25
-
26
- export const PartnerSchema = z.object({
27
- id: z.string(),
28
- name: z.string(),
29
- tagline: z.string().optional(),
30
- description: z.string(),
31
- category: z.string(),
32
- categoryLabel: z.string(),
33
- libraries: z.array(z.string()),
34
- url: z.string(),
35
- })
36
-
37
- export const PartnersResponseSchema = z.object({
38
- partners: z.array(PartnerSchema),
39
- categories: z.array(z.string()),
40
- categoryLabels: z.record(z.string()),
41
- })
42
-
43
- export type Library = z.infer<typeof LibrarySchema>
44
- export type LibrariesResponse = z.infer<typeof LibrariesResponseSchema>
45
- export type Partner = z.infer<typeof PartnerSchema>
46
- export type PartnersResponse = z.infer<typeof PartnersResponseSchema>