@tanstack/cli 0.0.7 → 0.48.2
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/dist/bin.js +7 -0
- package/dist/cli.js +481 -0
- package/dist/command-line.js +174 -0
- package/dist/dev-watch.js +290 -0
- package/dist/file-syncer.js +148 -0
- package/dist/index.js +1 -0
- package/dist/mcp/api.js +31 -0
- package/dist/mcp/tools.js +250 -0
- package/dist/mcp/types.js +37 -0
- package/dist/mcp.js +121 -0
- package/dist/options.js +162 -0
- package/dist/types/bin.d.ts +2 -0
- package/dist/types/cli.d.ts +16 -0
- package/dist/types/command-line.d.ts +10 -0
- package/dist/types/dev-watch.d.ts +27 -0
- package/dist/types/file-syncer.d.ts +18 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/mcp/api.d.ts +4 -0
- package/dist/types/mcp/tools.d.ts +2 -0
- package/dist/types/mcp/types.d.ts +217 -0
- package/dist/types/mcp.d.ts +6 -0
- package/dist/types/options.d.ts +8 -0
- package/dist/types/types.d.ts +25 -0
- package/dist/types/ui-environment.d.ts +2 -0
- package/dist/types/ui-prompts.d.ts +12 -0
- package/dist/types/utils.d.ts +8 -0
- package/dist/types.js +1 -0
- package/dist/ui-environment.js +52 -0
- package/dist/ui-prompts.js +244 -0
- package/dist/utils.js +30 -0
- package/package.json +46 -46
- package/src/bin.ts +6 -93
- package/src/cli.ts +692 -0
- package/src/command-line.ts +236 -0
- package/src/dev-watch.ts +430 -0
- package/src/file-syncer.ts +205 -0
- package/src/index.ts +1 -85
- package/src/mcp.ts +190 -0
- package/src/options.ts +260 -0
- package/src/types.ts +27 -0
- package/src/ui-environment.ts +74 -0
- package/src/ui-prompts.ts +322 -0
- package/src/utils.ts +38 -0
- package/tests/command-line.test.ts +304 -0
- package/tests/index.test.ts +9 -0
- package/tests/mcp.test.ts +225 -0
- package/tests/options.test.ts +304 -0
- package/tests/setupVitest.ts +6 -0
- package/tests/ui-environment.test.ts +97 -0
- package/tests/ui-prompts.test.ts +238 -0
- package/tsconfig.json +17 -0
- package/vitest.config.js +7 -0
- package/dist/bin.cjs +0 -761
- package/dist/bin.d.cts +0 -1
- package/dist/bin.d.mts +0 -1
- package/dist/bin.mjs +0 -760
- package/dist/index.cjs +0 -36
- package/dist/index.d.cts +0 -1172
- package/dist/index.d.mts +0 -1172
- package/dist/index.mjs +0 -3
- package/dist/template-CkAkdP8n.mjs +0 -2545
- package/dist/template-Cup47s9h.cjs +0 -2783
- package/src/api/fetch.test.ts +0 -114
- package/src/api/fetch.ts +0 -249
- package/src/cache/index.ts +0 -89
- package/src/commands/create.ts +0 -463
- package/src/commands/mcp.test.ts +0 -152
- package/src/commands/mcp.ts +0 -203
- package/src/engine/compile-with-addons.test.ts +0 -302
- package/src/engine/compile.test.ts +0 -404
- package/src/engine/compile.ts +0 -551
- package/src/engine/config-file.test.ts +0 -118
- package/src/engine/config-file.ts +0 -61
- package/src/engine/custom-addons/integration.ts +0 -323
- package/src/engine/custom-addons/shared.test.ts +0 -98
- package/src/engine/custom-addons/shared.ts +0 -281
- package/src/engine/custom-addons/template.test.ts +0 -288
- package/src/engine/custom-addons/template.ts +0 -124
- package/src/engine/template.test.ts +0 -256
- package/src/engine/template.ts +0 -269
- package/src/engine/types.ts +0 -336
- package/src/parse-gitignore.d.ts +0 -5
- package/src/templates/base.ts +0 -891
package/src/commands/mcp.ts
DELETED
|
@@ -1,203 +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 { execSync } = await import('node:child_process')
|
|
98
|
-
|
|
99
|
-
// Fetch integration definitions if needed
|
|
100
|
-
let chosenIntegrations: Array<Awaited<ReturnType<typeof fetchIntegrations>>[number]> = []
|
|
101
|
-
if (integrations?.length) {
|
|
102
|
-
chosenIntegrations = await fetchIntegrations(integrations)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Compile project
|
|
106
|
-
const output = compile({
|
|
107
|
-
projectName,
|
|
108
|
-
framework: 'react',
|
|
109
|
-
mode: 'file-router' as RouterMode,
|
|
110
|
-
typescript: true,
|
|
111
|
-
tailwind: tailwind ?? true,
|
|
112
|
-
packageManager: packageManager ?? 'pnpm',
|
|
113
|
-
chosenIntegrations,
|
|
114
|
-
integrationOptions: integrationOptions ?? {},
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
// Create directory and write files
|
|
118
|
-
mkdirSync(targetDir, { recursive: true })
|
|
119
|
-
|
|
120
|
-
for (const [filePath, content] of Object.entries(output.files)) {
|
|
121
|
-
const fullPath = resolve(targetDir, filePath)
|
|
122
|
-
const dir = resolve(fullPath, '..')
|
|
123
|
-
mkdirSync(dir, { recursive: true })
|
|
124
|
-
writeFileSync(fullPath, content, 'utf-8')
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Initialize git
|
|
128
|
-
try {
|
|
129
|
-
execSync('git init', { cwd: targetDir, stdio: 'ignore' })
|
|
130
|
-
} catch {
|
|
131
|
-
// Git init failed, continue anyway
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Build response
|
|
135
|
-
const envVarList =
|
|
136
|
-
output.envVars.length > 0
|
|
137
|
-
? `\n\nRequired environment variables:\n${output.envVars.map((e) => `- ${e.name}: ${e.description}`).join('\n')}`
|
|
138
|
-
: ''
|
|
139
|
-
|
|
140
|
-
const warnings =
|
|
141
|
-
output.warnings.length > 0
|
|
142
|
-
? `\n\nWarnings:\n${output.warnings.map((w) => `- ${w}`).join('\n')}`
|
|
143
|
-
: ''
|
|
144
|
-
|
|
145
|
-
return {
|
|
146
|
-
content: [
|
|
147
|
-
{
|
|
148
|
-
type: 'text' as const,
|
|
149
|
-
text: `Project "${projectName}" created successfully at ${targetDir}
|
|
150
|
-
|
|
151
|
-
Files created: ${Object.keys(output.files).length}
|
|
152
|
-
Integrations included: ${chosenIntegrations.map((a) => a.name).join(', ') || 'none'}${envVarList}${warnings}
|
|
153
|
-
|
|
154
|
-
Next steps:
|
|
155
|
-
1. cd ${targetDir}
|
|
156
|
-
2. ${packageManager ?? 'pnpm'} install
|
|
157
|
-
3. ${packageManager ?? 'pnpm'} dev`,
|
|
158
|
-
},
|
|
159
|
-
],
|
|
160
|
-
}
|
|
161
|
-
} catch (error) {
|
|
162
|
-
return {
|
|
163
|
-
content: [
|
|
164
|
-
{
|
|
165
|
-
type: 'text' as const,
|
|
166
|
-
text: `Error creating application: ${error}`,
|
|
167
|
-
},
|
|
168
|
-
],
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
},
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
return server
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export async function runMcp(options: McpOptions): Promise<void> {
|
|
178
|
-
const server = createServer()
|
|
179
|
-
|
|
180
|
-
if (options.sse) {
|
|
181
|
-
const app = express()
|
|
182
|
-
let transport: SSEServerTransport | null = null
|
|
183
|
-
|
|
184
|
-
app.get('/sse', (_req, res) => {
|
|
185
|
-
transport = new SSEServerTransport('/messages', res)
|
|
186
|
-
server.connect(transport)
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
app.post('/messages', (req, res) => {
|
|
190
|
-
if (transport) {
|
|
191
|
-
transport.handlePostMessage(req, res)
|
|
192
|
-
}
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
const port = parseInt(options.port ?? '8080', 10)
|
|
196
|
-
app.listen(port, () => {
|
|
197
|
-
console.log(`MCP server running at http://localhost:${port}/sse`)
|
|
198
|
-
})
|
|
199
|
-
} else {
|
|
200
|
-
const transport = new StdioServerTransport()
|
|
201
|
-
await server.connect(transport)
|
|
202
|
-
}
|
|
203
|
-
}
|
|
@@ -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
|
-
})
|