@tanstack/cli 0.0.8 → 0.48.3

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 (88) hide show
  1. package/LICENSE +21 -0
  2. package/dist/bin.js +7 -0
  3. package/dist/cli.js +481 -0
  4. package/dist/command-line.js +174 -0
  5. package/dist/dev-watch.js +290 -0
  6. package/dist/file-syncer.js +148 -0
  7. package/dist/index.js +1 -0
  8. package/dist/mcp/api.js +31 -0
  9. package/dist/mcp/tools.js +250 -0
  10. package/dist/mcp/types.js +37 -0
  11. package/dist/mcp.js +121 -0
  12. package/dist/options.js +162 -0
  13. package/dist/types/bin.d.ts +2 -0
  14. package/dist/types/cli.d.ts +16 -0
  15. package/dist/types/command-line.d.ts +10 -0
  16. package/dist/types/dev-watch.d.ts +27 -0
  17. package/dist/types/file-syncer.d.ts +18 -0
  18. package/dist/types/index.d.ts +1 -0
  19. package/dist/types/mcp/api.d.ts +4 -0
  20. package/dist/types/mcp/tools.d.ts +2 -0
  21. package/dist/types/mcp/types.d.ts +217 -0
  22. package/dist/types/mcp.d.ts +6 -0
  23. package/dist/types/options.d.ts +8 -0
  24. package/dist/types/types.d.ts +25 -0
  25. package/dist/types/ui-environment.d.ts +2 -0
  26. package/dist/types/ui-prompts.d.ts +12 -0
  27. package/dist/types/utils.d.ts +8 -0
  28. package/dist/types.js +1 -0
  29. package/dist/ui-environment.js +52 -0
  30. package/dist/ui-prompts.js +244 -0
  31. package/dist/utils.js +30 -0
  32. package/package.json +46 -47
  33. package/src/bin.ts +6 -93
  34. package/src/cli.ts +692 -0
  35. package/src/command-line.ts +236 -0
  36. package/src/dev-watch.ts +430 -0
  37. package/src/file-syncer.ts +205 -0
  38. package/src/index.ts +1 -85
  39. package/src/mcp.ts +190 -0
  40. package/src/options.ts +260 -0
  41. package/src/types.ts +27 -0
  42. package/src/ui-environment.ts +74 -0
  43. package/src/ui-prompts.ts +322 -0
  44. package/src/utils.ts +38 -0
  45. package/tests/command-line.test.ts +304 -0
  46. package/tests/index.test.ts +9 -0
  47. package/tests/mcp.test.ts +225 -0
  48. package/tests/options.test.ts +304 -0
  49. package/tests/setupVitest.ts +6 -0
  50. package/tests/ui-environment.test.ts +97 -0
  51. package/tests/ui-prompts.test.ts +238 -0
  52. package/tsconfig.json +17 -0
  53. package/vitest.config.js +7 -0
  54. package/dist/bin.cjs +0 -769
  55. package/dist/bin.d.cts +0 -1
  56. package/dist/bin.d.mts +0 -1
  57. package/dist/bin.mjs +0 -768
  58. package/dist/fetch-CbFFGJEw.cjs +0 -3
  59. package/dist/fetch-DG5dLrsb.cjs +0 -522
  60. package/dist/fetch-DhlVXS6S.mjs +0 -390
  61. package/dist/fetch-I_OVg8JX.mjs +0 -3
  62. package/dist/index.cjs +0 -37
  63. package/dist/index.d.cts +0 -1172
  64. package/dist/index.d.mts +0 -1172
  65. package/dist/index.mjs +0 -4
  66. package/dist/template-Szi7-AZJ.mjs +0 -2202
  67. package/dist/template-lWrIZhCQ.cjs +0 -2314
  68. package/src/api/fetch.test.ts +0 -114
  69. package/src/api/fetch.ts +0 -278
  70. package/src/cache/index.ts +0 -89
  71. package/src/commands/create.ts +0 -470
  72. package/src/commands/mcp.test.ts +0 -152
  73. package/src/commands/mcp.ts +0 -211
  74. package/src/engine/compile-with-addons.test.ts +0 -302
  75. package/src/engine/compile.test.ts +0 -404
  76. package/src/engine/compile.ts +0 -569
  77. package/src/engine/config-file.test.ts +0 -118
  78. package/src/engine/config-file.ts +0 -61
  79. package/src/engine/custom-addons/integration.ts +0 -323
  80. package/src/engine/custom-addons/shared.test.ts +0 -98
  81. package/src/engine/custom-addons/shared.ts +0 -281
  82. package/src/engine/custom-addons/template.test.ts +0 -288
  83. package/src/engine/custom-addons/template.ts +0 -124
  84. package/src/engine/template.test.ts +0 -256
  85. package/src/engine/template.ts +0 -269
  86. package/src/engine/types.ts +0 -336
  87. package/src/parse-gitignore.d.ts +0 -5
  88. package/src/templates/base.ts +0 -883
@@ -1,211 +0,0 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
3
- import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'
4
- import express from 'express'
5
- import { z } from 'zod'
6
- import { fetchIntegrations, fetchManifest } from '../api/fetch.js'
7
- import { compile } from '../engine/compile.js'
8
- import { registerDocTools } from '../mcp/tools.js'
9
- import type { RouterMode } from '../engine/types.js'
10
-
11
- interface McpOptions {
12
- sse?: boolean
13
- port?: string
14
- }
15
-
16
- function createServer() {
17
- const server = new McpServer({
18
- name: 'TanStack CLI',
19
- version: '0.0.1',
20
- })
21
-
22
- server.tool(
23
- 'listTanStackIntegrations',
24
- 'List available integrations for creating TanStack applications',
25
- {},
26
- async () => {
27
- try {
28
- const manifest = await fetchManifest()
29
- const integrations = manifest.integrations
30
- .filter((a) => a.modes.includes('file-router'))
31
- .map((integration) => ({
32
- id: integration.id,
33
- name: integration.name,
34
- description: integration.description,
35
- category: integration.category,
36
- dependsOn: integration.dependsOn,
37
- exclusive: integration.exclusive,
38
- hasOptions: integration.hasOptions,
39
- }))
40
-
41
- return {
42
- content: [
43
- {
44
- type: 'text' as const,
45
- text: JSON.stringify(integrations, null, 2),
46
- },
47
- ],
48
- }
49
- } catch (error) {
50
- return {
51
- content: [
52
- {
53
- type: 'text' as const,
54
- text: `Error fetching integrations: ${error}`,
55
- },
56
- ],
57
- }
58
- }
59
- },
60
- )
61
-
62
- // Register documentation/ecosystem tools
63
- registerDocTools(server)
64
-
65
- server.tool(
66
- 'createTanStackApplication',
67
- 'Create a new TanStack Start application',
68
- {
69
- projectName: z
70
- .string()
71
- .describe('The name of the project (will be the directory name)'),
72
- targetDir: z
73
- .string()
74
- .describe('Absolute path where the project should be created'),
75
- integrations: z
76
- .array(z.string())
77
- .optional()
78
- .describe(
79
- 'Array of integration IDs to include. Use listTanStackIntegrations to see available options.',
80
- ),
81
- integrationOptions: z
82
- .record(z.record(z.unknown()))
83
- .optional()
84
- .describe(
85
- 'Configuration for integrations. Format: {"integrationId": {"optionName": "value"}}',
86
- ),
87
- tailwind: z.boolean().optional().describe('Include Tailwind CSS (default: true)'),
88
- packageManager: z
89
- .enum(['npm', 'pnpm', 'yarn', 'bun', 'deno'])
90
- .optional()
91
- .describe('Package manager to use (default: pnpm)'),
92
- },
93
- async ({ projectName, targetDir, integrations, integrationOptions, tailwind, packageManager }) => {
94
- try {
95
- const { mkdirSync, writeFileSync } = await import('node:fs')
96
- const { resolve } = await import('node:path')
97
- const { BINARY_PREFIX } = await import('../api/fetch.js')
98
- const { execSync } = await import('node:child_process')
99
-
100
- // Fetch integration definitions if needed
101
- let chosenIntegrations: Array<Awaited<ReturnType<typeof fetchIntegrations>>[number]> = []
102
- if (integrations?.length) {
103
- chosenIntegrations = await fetchIntegrations(integrations)
104
- }
105
-
106
- // Compile project
107
- const output = compile({
108
- projectName,
109
- framework: 'react',
110
- mode: 'file-router' as RouterMode,
111
- typescript: true,
112
- tailwind: tailwind ?? true,
113
- packageManager: packageManager ?? 'pnpm',
114
- chosenIntegrations,
115
- integrationOptions: integrationOptions ?? {},
116
- })
117
-
118
- // Create directory and write files
119
- mkdirSync(targetDir, { recursive: true })
120
-
121
- for (const [filePath, content] of Object.entries(output.files)) {
122
- const fullPath = resolve(targetDir, filePath)
123
- const dir = resolve(fullPath, '..')
124
- mkdirSync(dir, { recursive: true })
125
-
126
- // Handle binary files (base64 encoded with prefix)
127
- if (content.startsWith(BINARY_PREFIX)) {
128
- const base64Data = content.slice(BINARY_PREFIX.length)
129
- writeFileSync(fullPath, Buffer.from(base64Data, 'base64'))
130
- } else {
131
- writeFileSync(fullPath, content, 'utf-8')
132
- }
133
- }
134
-
135
- // Initialize git
136
- try {
137
- execSync('git init', { cwd: targetDir, stdio: 'ignore' })
138
- } catch {
139
- // Git init failed, continue anyway
140
- }
141
-
142
- // Build response
143
- const envVarList =
144
- output.envVars.length > 0
145
- ? `\n\nRequired environment variables:\n${output.envVars.map((e) => `- ${e.name}: ${e.description}`).join('\n')}`
146
- : ''
147
-
148
- const warnings =
149
- output.warnings.length > 0
150
- ? `\n\nWarnings:\n${output.warnings.map((w) => `- ${w}`).join('\n')}`
151
- : ''
152
-
153
- return {
154
- content: [
155
- {
156
- type: 'text' as const,
157
- text: `Project "${projectName}" created successfully at ${targetDir}
158
-
159
- Files created: ${Object.keys(output.files).length}
160
- Integrations included: ${chosenIntegrations.map((a) => a.name).join(', ') || 'none'}${envVarList}${warnings}
161
-
162
- Next steps:
163
- 1. cd ${targetDir}
164
- 2. ${packageManager ?? 'pnpm'} install
165
- 3. ${packageManager ?? 'pnpm'} dev`,
166
- },
167
- ],
168
- }
169
- } catch (error) {
170
- return {
171
- content: [
172
- {
173
- type: 'text' as const,
174
- text: `Error creating application: ${error}`,
175
- },
176
- ],
177
- }
178
- }
179
- },
180
- )
181
-
182
- return server
183
- }
184
-
185
- export async function runMcp(options: McpOptions): Promise<void> {
186
- const server = createServer()
187
-
188
- if (options.sse) {
189
- const app = express()
190
- let transport: SSEServerTransport | null = null
191
-
192
- app.get('/sse', (_req, res) => {
193
- transport = new SSEServerTransport('/messages', res)
194
- server.connect(transport)
195
- })
196
-
197
- app.post('/messages', (req, res) => {
198
- if (transport) {
199
- transport.handlePostMessage(req, res)
200
- }
201
- })
202
-
203
- const port = parseInt(options.port ?? '8080', 10)
204
- app.listen(port, () => {
205
- console.log(`MCP server running at http://localhost:${port}/sse`)
206
- })
207
- } else {
208
- const transport = new StdioServerTransport()
209
- await server.connect(transport)
210
- }
211
- }
@@ -1,302 +0,0 @@
1
- import { resolve } from 'node:path'
2
- import { beforeAll, describe, expect, it } from 'vitest'
3
- import { fetchIntegration, fetchIntegrations } from '../api/fetch.js'
4
- import { compile } from './compile.js'
5
- import type { CompileOptions, IntegrationCompiled } from './types.js'
6
-
7
- const INTEGRATIONS_PATH = resolve(__dirname, '../../../../integrations')
8
-
9
- const baseOptions: CompileOptions = {
10
- projectName: 'test-project',
11
- framework: 'react',
12
- mode: 'file-router',
13
- typescript: true,
14
- tailwind: true,
15
- packageManager: 'pnpm',
16
- chosenIntegrations: [],
17
- integrationOptions: {},
18
- }
19
-
20
- describe('compile with real integrations', () => {
21
- describe('TanStack Query integration', () => {
22
- let queryIntegration: IntegrationCompiled
23
-
24
- beforeAll(async () => {
25
- queryIntegration = await fetchIntegration('tanstack-query', INTEGRATIONS_PATH)
26
- })
27
-
28
- it('should load integration from local path', () => {
29
- expect(queryIntegration.id).toBe('tanstack-query')
30
- expect(queryIntegration.name).toBe('TanStack Query')
31
- expect(queryIntegration.hooks).toHaveLength(2)
32
- })
33
-
34
- it('should have provider hook', () => {
35
- const provider = queryIntegration.hooks?.find(
36
- (i) => i.type === 'root-provider',
37
- )
38
- expect(provider).toBeDefined()
39
- expect(provider?.jsName).toBe('QueryProvider')
40
- })
41
-
42
- it('should have devtools hook', () => {
43
- const devtools = queryIntegration.hooks?.find(
44
- (i) => i.type === 'devtools',
45
- )
46
- expect(devtools).toBeDefined()
47
- expect(devtools?.jsName).toBe('queryDevtoolsPlugin')
48
- })
49
-
50
- it('should include integration files in compile output', () => {
51
- const output = compile({
52
- ...baseOptions,
53
- chosenIntegrations: [queryIntegration],
54
- })
55
-
56
- expect(output.files).toHaveProperty(
57
- 'src/integrations/query/provider.tsx',
58
- )
59
- expect(output.files).toHaveProperty(
60
- 'src/integrations/query/devtools.tsx',
61
- )
62
- expect(output.files).toHaveProperty('src/routes/demo/query.tsx')
63
- })
64
-
65
- it('should inject provider into __root.tsx', () => {
66
- const output = compile({
67
- ...baseOptions,
68
- chosenIntegrations: [queryIntegration],
69
- })
70
-
71
- const rootRoute = output.files['src/routes/__root.tsx']!
72
- expect(rootRoute).toContain('QueryProvider')
73
- })
74
-
75
- it('should inject devtools into __root.tsx', () => {
76
- const output = compile({
77
- ...baseOptions,
78
- chosenIntegrations: [queryIntegration],
79
- })
80
-
81
- const rootRoute = output.files['src/routes/__root.tsx']!
82
- expect(rootRoute).toContain('queryDevtoolsPlugin')
83
- })
84
- })
85
-
86
- describe('Shadcn integration', () => {
87
- let shadcnIntegration: IntegrationCompiled
88
-
89
- beforeAll(async () => {
90
- shadcnIntegration = await fetchIntegration('shadcn', INTEGRATIONS_PATH)
91
- })
92
-
93
- it('should load shadcn integration', () => {
94
- expect(shadcnIntegration.id).toBe('shadcn')
95
- expect(shadcnIntegration.name).toBe('shadcn/ui')
96
- })
97
-
98
- it('should include shadcn files', () => {
99
- const output = compile({
100
- ...baseOptions,
101
- chosenIntegrations: [shadcnIntegration],
102
- })
103
-
104
- // Shadcn should have components.json or similar config files
105
- expect(Object.keys(output.files).some((f) => f.includes('components'))).toBe(true)
106
- })
107
- })
108
-
109
- describe('Multiple integrations', () => {
110
- let integrations: Array<IntegrationCompiled>
111
-
112
- beforeAll(async () => {
113
- integrations = await fetchIntegrations(
114
- ['tanstack-query', 'tanstack-form'],
115
- INTEGRATIONS_PATH,
116
- )
117
- })
118
-
119
- it('should load multiple integrations', () => {
120
- expect(integrations).toHaveLength(2)
121
- expect(integrations.map((a) => a.id)).toEqual(['tanstack-query', 'tanstack-form'])
122
- })
123
-
124
- it('should compile with multiple integrations', () => {
125
- const output = compile({
126
- ...baseOptions,
127
- chosenIntegrations: integrations,
128
- })
129
-
130
- // Both integration files should be present
131
- expect(output.files).toHaveProperty(
132
- 'src/integrations/query/provider.tsx',
133
- )
134
- // TanStack Form doesn't have a provider, just a demo route
135
- expect(output.files).toHaveProperty(
136
- 'src/routes/demo/form.tsx',
137
- )
138
- })
139
-
140
- it('should merge all integration dependencies', () => {
141
- const output = compile({
142
- ...baseOptions,
143
- chosenIntegrations: integrations,
144
- })
145
-
146
- // Check that dependencies from both integrations are merged
147
- expect(output.packages.dependencies).toHaveProperty('@tanstack/react-query')
148
- expect(output.packages.dependencies).toHaveProperty('@tanstack/react-form')
149
- })
150
- })
151
-
152
- describe('Vite plugin integration (Sentry)', () => {
153
- let sentryIntegration: IntegrationCompiled
154
-
155
- beforeAll(async () => {
156
- sentryIntegration = await fetchIntegration('sentry', INTEGRATIONS_PATH)
157
- })
158
-
159
- it('should load sentry integration', () => {
160
- expect(sentryIntegration.id).toBe('sentry')
161
- })
162
-
163
- it('should inject vite plugin if sentry has one', () => {
164
- const vitePlugin = sentryIntegration.hooks?.find(
165
- (i) => i.type === 'vite-plugin',
166
- )
167
-
168
- if (vitePlugin) {
169
- const output = compile({
170
- ...baseOptions,
171
- chosenIntegrations: [sentryIntegration],
172
- })
173
-
174
- const viteConfig = output.files['vite.config.ts']!
175
- expect(viteConfig).toContain(vitePlugin.jsName || vitePlugin.code)
176
- }
177
- })
178
- })
179
-
180
- describe('Toolchain integration (ESLint)', () => {
181
- let eslintIntegration: IntegrationCompiled
182
-
183
- beforeAll(async () => {
184
- eslintIntegration = await fetchIntegration('eslint', INTEGRATIONS_PATH)
185
- })
186
-
187
- it('should load eslint integration', () => {
188
- expect(eslintIntegration.id).toBe('eslint')
189
- expect(eslintIntegration.type).toBe('toolchain')
190
- expect(eslintIntegration.phase).toBe('setup')
191
- })
192
-
193
- it('should add eslint config files', () => {
194
- const output = compile({
195
- ...baseOptions,
196
- chosenIntegrations: [eslintIntegration],
197
- })
198
-
199
- // ESLint should add config file
200
- const hasEslintConfig = Object.keys(output.files).some(
201
- (f) => f.includes('eslint') || f.includes('.eslintrc'),
202
- )
203
- expect(hasEslintConfig).toBe(true)
204
- })
205
-
206
- it('should add eslint scripts to package.json', () => {
207
- const output = compile({
208
- ...baseOptions,
209
- chosenIntegrations: [eslintIntegration],
210
- })
211
-
212
- const pkg = JSON.parse(output.files['package.json']!)
213
- expect(pkg.scripts.lint).toBeDefined()
214
- })
215
- })
216
-
217
- describe('Deployment integration (Railway)', () => {
218
- let railwayIntegration: IntegrationCompiled
219
-
220
- beforeAll(async () => {
221
- railwayIntegration = await fetchIntegration('railway', INTEGRATIONS_PATH)
222
- })
223
-
224
- it('should load railway integration', () => {
225
- expect(railwayIntegration.id).toBe('railway')
226
- expect(railwayIntegration.type).toBe('deployment')
227
- })
228
- })
229
- })
230
-
231
- describe('compile output validation', () => {
232
- it('should generate valid vite.config.ts syntax', async () => {
233
- const queryIntegration = await fetchIntegration('tanstack-query', INTEGRATIONS_PATH)
234
- const output = compile({
235
- ...baseOptions,
236
- chosenIntegrations: [queryIntegration],
237
- })
238
-
239
- const viteConfig = output.files['vite.config.ts']!
240
-
241
- // Basic syntax checks
242
- expect(viteConfig).toContain('export default defineConfig')
243
- expect(viteConfig).toContain('plugins:')
244
-
245
- // Should have balanced braces
246
- const openBraces = (viteConfig.match(/\{/g) || []).length
247
- const closeBraces = (viteConfig.match(/\}/g) || []).length
248
- expect(openBraces).toBe(closeBraces)
249
-
250
- // Should have balanced brackets
251
- const openBrackets = (viteConfig.match(/\[/g) || []).length
252
- const closeBrackets = (viteConfig.match(/\]/g) || []).length
253
- expect(openBrackets).toBe(closeBrackets)
254
- })
255
-
256
- it('should generate valid __root.tsx syntax', async () => {
257
- const queryIntegration = await fetchIntegration('tanstack-query', INTEGRATIONS_PATH)
258
- const output = compile({
259
- ...baseOptions,
260
- chosenIntegrations: [queryIntegration],
261
- })
262
-
263
- const rootRoute = output.files['src/routes/__root.tsx']!
264
-
265
- // Basic syntax checks
266
- expect(rootRoute).toContain('createRootRoute')
267
- expect(rootRoute).toContain('export const Route')
268
-
269
- // Should have balanced JSX tags (roughly - this is a simple check)
270
- // Note: We only count React component tags (uppercase), not HTML tags (lowercase like html, head, body)
271
- const openTags = (rootRoute.match(/<[A-Z][^/>]*>/g) || []).length
272
- const closeTags = (rootRoute.match(/<\/[A-Z][^>]*>/g) || []).length
273
- const selfClosingTags = (rootRoute.match(/<[A-Z][^>]*\/>/g) || []).length
274
- // Open tags should equal close tags + self-closing tags (approximately)
275
- // Allow tolerance of 4 due to complex provider nesting patterns
276
- expect(Math.abs(openTags - closeTags - selfClosingTags)).toBeLessThanOrEqual(4)
277
- })
278
-
279
- it('should generate valid package.json', async () => {
280
- const integrations = await fetchIntegrations(
281
- ['tanstack-query', 'tanstack-form'],
282
- INTEGRATIONS_PATH,
283
- )
284
- const output = compile({
285
- ...baseOptions,
286
- chosenIntegrations: integrations,
287
- })
288
-
289
- const pkg = JSON.parse(output.files['package.json']!)
290
-
291
- // Required fields
292
- expect(pkg.name).toBe('test-project')
293
- expect(pkg.type).toBe('module')
294
- expect(pkg.scripts).toBeDefined()
295
- expect(pkg.dependencies).toBeDefined()
296
- expect(pkg.devDependencies).toBeDefined()
297
-
298
- // No duplicate keys (JSON.parse would fail otherwise, but let's be explicit)
299
- expect(typeof pkg.dependencies).toBe('object')
300
- expect(typeof pkg.devDependencies).toBe('object')
301
- })
302
- })