@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,256 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import {
3
- createTemplateContext,
4
- processTemplateFile,
5
- relativePath,
6
- } from './template.js'
7
- import type { CompileOptions } from './types.js'
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('relativePath', () => {
21
- it('should calculate relative path between files in same directory', () => {
22
- expect(relativePath('src/routes/index.tsx', 'src/routes/about.tsx')).toBe(
23
- './about.tsx',
24
- )
25
- })
26
-
27
- it('should calculate relative path going up directories', () => {
28
- expect(
29
- relativePath('src/routes/demo/test.tsx', 'src/integrations/query/client.ts'),
30
- ).toBe('../../integrations/query/client.ts')
31
- })
32
-
33
- it('should calculate relative path going down directories', () => {
34
- expect(relativePath('src/index.tsx', 'src/routes/demo/test.tsx')).toBe(
35
- './routes/demo/test.tsx',
36
- )
37
- })
38
-
39
- it('should strip extension when requested', () => {
40
- expect(
41
- relativePath('src/routes/index.tsx', 'src/integrations/query/client.ts', true),
42
- ).toBe('../integrations/query/client')
43
- })
44
- })
45
-
46
- describe('createTemplateContext', () => {
47
- it('should create context with correct project config', () => {
48
- const ctx = createTemplateContext(baseOptions, 'src/index.tsx')
49
-
50
- expect(ctx.projectName).toBe('test-project')
51
- expect(ctx.packageManager).toBe('pnpm')
52
- expect(ctx.typescript).toBe(true)
53
- expect(ctx.tailwind).toBe(true)
54
- expect(ctx.js).toBe('ts')
55
- expect(ctx.jsx).toBe('tsx')
56
- })
57
-
58
- it('should set router mode flags correctly for file-router', () => {
59
- const ctx = createTemplateContext(baseOptions, 'src/index.tsx')
60
-
61
- expect(ctx.fileRouter).toBe(true)
62
- expect(ctx.codeRouter).toBe(false)
63
- })
64
-
65
- it('should set router mode flags correctly for code-router', () => {
66
- const ctx = createTemplateContext(
67
- { ...baseOptions, mode: 'code-router' },
68
- 'src/index.tsx',
69
- )
70
-
71
- expect(ctx.fileRouter).toBe(false)
72
- expect(ctx.codeRouter).toBe(true)
73
- })
74
-
75
- it('should generate correct package manager scripts for pnpm', () => {
76
- const ctx = createTemplateContext(baseOptions, 'src/index.tsx')
77
-
78
- expect(ctx.getPackageManagerAddScript('react')).toBe('pnpm add react')
79
- expect(ctx.getPackageManagerAddScript('vitest', true)).toBe('pnpm add -D vitest')
80
- expect(ctx.getPackageManagerRunScript('dev')).toBe('pnpm dev')
81
- })
82
-
83
- it('should generate correct package manager scripts for npm', () => {
84
- const ctx = createTemplateContext(
85
- { ...baseOptions, packageManager: 'npm' },
86
- 'src/index.tsx',
87
- )
88
-
89
- expect(ctx.getPackageManagerAddScript('react')).toBe('npm install react')
90
- expect(ctx.getPackageManagerAddScript('vitest', true)).toBe('npm install -D vitest')
91
- expect(ctx.getPackageManagerRunScript('dev')).toBe('npm run dev')
92
- })
93
-
94
- it('should collect hooks from integrations', () => {
95
- const ctx = createTemplateContext(
96
- {
97
- ...baseOptions,
98
- chosenIntegrations: [
99
- {
100
- id: 'test',
101
- name: 'Test',
102
- description: 'Test integration',
103
- type: 'integration',
104
- phase: 'integration',
105
- modes: ['file-router'],
106
- files: {},
107
- deletedFiles: [],
108
- hooks: [
109
- { type: 'root-provider', jsName: 'TestProvider', path: 'src/test.tsx' },
110
- ],
111
- },
112
- ],
113
- },
114
- 'src/index.tsx',
115
- )
116
-
117
- expect(ctx.hooks).toHaveLength(1)
118
- expect(ctx.hooks[0]?.jsName).toBe('TestProvider')
119
- })
120
-
121
- it('should build integrationEnabled map', () => {
122
- const ctx = createTemplateContext(
123
- {
124
- ...baseOptions,
125
- chosenIntegrations: [
126
- {
127
- id: 'tanstack-query',
128
- name: 'TanStack Query',
129
- description: 'Data fetching',
130
- type: 'integration',
131
- phase: 'integration',
132
- modes: ['file-router'],
133
- files: {},
134
- deletedFiles: [],
135
- },
136
- ],
137
- },
138
- 'src/index.tsx',
139
- )
140
-
141
- expect(ctx.integrationEnabled['tanstack-query']).toBe(true)
142
- expect(ctx.integrationEnabled['clerk']).toBeUndefined()
143
- })
144
- })
145
-
146
- describe('processTemplateFile', () => {
147
- it('should pass through non-EJS files unchanged', () => {
148
- const result = processTemplateFile(
149
- 'src/utils.ts',
150
- 'export const foo = 1',
151
- baseOptions,
152
- )
153
-
154
- expect(result).not.toBeNull()
155
- expect(result!.path).toBe('src/utils.ts')
156
- expect(result!.content).toBe('export const foo = 1')
157
- expect(result!.append).toBe(false)
158
- })
159
-
160
- it('should process EJS templates', () => {
161
- const result = processTemplateFile(
162
- 'src/config.ts.ejs',
163
- 'export const name = "<%= projectName %>"',
164
- baseOptions,
165
- )
166
-
167
- expect(result).not.toBeNull()
168
- expect(result!.path).toBe('src/config.ts')
169
- expect(result!.content).toBe('export const name = "test-project"')
170
- })
171
-
172
- it('should convert _dot_ prefix to dot', () => {
173
- const result = processTemplateFile(
174
- '_dot_gitignore',
175
- 'node_modules',
176
- baseOptions,
177
- )
178
-
179
- expect(result).not.toBeNull()
180
- expect(result!.path).toBe('.gitignore')
181
- })
182
-
183
- it('should handle .append suffix', () => {
184
- const result = processTemplateFile(
185
- 'src/styles.css.append',
186
- '.new-class { }',
187
- baseOptions,
188
- )
189
-
190
- expect(result).not.toBeNull()
191
- expect(result!.path).toBe('src/styles.css')
192
- expect(result!.append).toBe(true)
193
- })
194
-
195
- it('should convert .ts to .js when typescript is disabled', () => {
196
- const result = processTemplateFile('src/utils.ts', 'const x = 1', {
197
- ...baseOptions,
198
- typescript: false,
199
- })
200
-
201
- expect(result).not.toBeNull()
202
- expect(result!.path).toBe('src/utils.js')
203
- })
204
-
205
- it('should convert .tsx to .jsx when typescript is disabled', () => {
206
- const result = processTemplateFile(
207
- 'src/App.tsx',
208
- 'export default () => <div />',
209
- { ...baseOptions, typescript: false },
210
- )
211
-
212
- expect(result).not.toBeNull()
213
- expect(result!.path).toBe('src/App.jsx')
214
- })
215
-
216
- it('should return null when ignoreFile() is called', () => {
217
- const result = processTemplateFile(
218
- 'src/conditional.ts.ejs',
219
- '<% ignoreFile() %>',
220
- baseOptions,
221
- )
222
-
223
- expect(result).toBeNull()
224
- })
225
-
226
- it('should process conditional EJS based on integrations', () => {
227
- const withQuery = processTemplateFile(
228
- 'src/test.ts.ejs',
229
- '<% if (integrationEnabled["tanstack-query"]) { %>query enabled<% } else { %>no query<% } %>',
230
- {
231
- ...baseOptions,
232
- chosenIntegrations: [
233
- {
234
- id: 'tanstack-query',
235
- name: 'TanStack Query',
236
- description: 'Data fetching',
237
- type: 'integration',
238
- phase: 'integration',
239
- modes: ['file-router'],
240
- files: {},
241
- deletedFiles: [],
242
- },
243
- ],
244
- },
245
- )
246
-
247
- const withoutQuery = processTemplateFile(
248
- 'src/test.ts.ejs',
249
- '<% if (integrationEnabled["tanstack-query"]) { %>query enabled<% } else { %>no query<% } %>',
250
- baseOptions,
251
- )
252
-
253
- expect(withQuery!.content).toBe('query enabled')
254
- expect(withoutQuery!.content).toBe('no query')
255
- })
256
- })
@@ -1,269 +0,0 @@
1
- import { render } from 'ejs'
2
- import type {
3
- CompileOptions,
4
- Hook,
5
- IntegrationCompiled,
6
- Route,
7
- } from './types.js'
8
-
9
- /**
10
- * Convert _dot_ prefixes to actual dots (for dotfiles)
11
- */
12
- function convertDotFiles(path: string): string {
13
- return path
14
- .split('/')
15
- .map((segment) => segment.replace(/^_dot_/, '.'))
16
- .join('/')
17
- }
18
-
19
- /**
20
- * Strip option prefixes from filename
21
- * e.g., __postgres__schema.prisma -> schema.prisma
22
- */
23
- function stripOptionPrefix(path: string): string {
24
- const match = path.match(/^(.+\/)?__([^_]+)__(.+)$/)
25
- if (match) {
26
- const [, directory, , filename] = match
27
- return (directory || '') + filename
28
- }
29
- return path
30
- }
31
-
32
- /**
33
- * Calculate relative path between two file paths
34
- */
35
- export function relativePath(
36
- from: string,
37
- to: string,
38
- stripExtension: boolean = false,
39
- ): string {
40
- const fromParts = from.split('/').slice(0, -1) // Remove filename
41
- const toParts = to.split('/')
42
-
43
- // Find common prefix length
44
- let commonLength = 0
45
- while (
46
- commonLength < fromParts.length &&
47
- commonLength < toParts.length &&
48
- fromParts[commonLength] === toParts[commonLength]
49
- ) {
50
- commonLength++
51
- }
52
-
53
- // Build relative path
54
- const upCount = fromParts.length - commonLength
55
- const ups = Array(upCount).fill('..')
56
- const remainder = toParts.slice(commonLength)
57
-
58
- let result = [...ups, ...remainder].join('/')
59
- if (!result.startsWith('.')) {
60
- result = './' + result
61
- }
62
-
63
- if (stripExtension) {
64
- result = result.replace(/\.[^/.]+$/, '')
65
- }
66
-
67
- return result
68
- }
69
-
70
- /**
71
- * Error thrown when ignoreFile() is called in a template
72
- */
73
- class IgnoreFileError extends Error {
74
- constructor() {
75
- super('ignoreFile')
76
- this.name = 'IgnoreFileError'
77
- }
78
- }
79
-
80
- export interface TemplateContext {
81
- // Project config
82
- packageManager: string
83
- projectName: string
84
- typescript: boolean
85
- tailwind: boolean
86
- js: 'ts' | 'js'
87
- jsx: 'tsx' | 'jsx'
88
-
89
- // Router mode
90
- fileRouter: boolean
91
- codeRouter: boolean
92
-
93
- // Integration state
94
- integrationEnabled: Record<string, boolean>
95
- integrationOption: Record<string, Record<string, unknown>>
96
- integrations: Array<IntegrationCompiled>
97
- hooks: Array<Hook>
98
- routes: Array<Route>
99
-
100
- // Helper functions
101
- getPackageManagerAddScript: (pkg: string, isDev?: boolean) => string
102
- getPackageManagerRunScript: (script: string, args?: Array<string>) => string
103
- relativePath: (to: string, stripExt?: boolean) => string
104
- hookImportContent: (hook: Hook) => string
105
- hookImportCode: (hook: Hook) => string
106
- ignoreFile: () => never
107
- }
108
-
109
- /**
110
- * Create template context from compile options
111
- */
112
- export function createTemplateContext(
113
- options: CompileOptions,
114
- currentFile: string,
115
- ): TemplateContext {
116
- // Collect all hooks from chosen integrations
117
- const hooks: Array<Hook> = []
118
- for (const integration of options.chosenIntegrations) {
119
- if (integration.hooks) {
120
- hooks.push(...integration.hooks)
121
- }
122
- }
123
-
124
- // Collect all routes from chosen integrations
125
- const routes: Array<Route> = []
126
- for (const integration of options.chosenIntegrations) {
127
- if (integration.routes) {
128
- routes.push(...integration.routes)
129
- }
130
- }
131
-
132
- // Build integration enabled map
133
- const integrationEnabled: Record<string, boolean> = {}
134
- for (const integration of options.chosenIntegrations) {
135
- integrationEnabled[integration.id] = true
136
- }
137
-
138
- // Helper to calculate relative path from current file
139
- const localRelativePath = (to: string, stripExt: boolean = false) =>
140
- relativePath(currentFile, to, stripExt)
141
-
142
- // Helper to generate import statement for hook
143
- const hookImportContent = (hook: Hook) =>
144
- hook.import ||
145
- `import ${hook.jsName} from '${localRelativePath(hook.path || '')}'`
146
-
147
- // Helper to get the code/value for a hook
148
- const hookImportCode = (hook: Hook) =>
149
- hook.code || hook.jsName || ''
150
-
151
- return {
152
- packageManager: options.packageManager,
153
- projectName: options.projectName,
154
- typescript: options.typescript,
155
- tailwind: options.tailwind,
156
- js: options.typescript ? 'ts' : 'js',
157
- jsx: options.typescript ? 'tsx' : 'jsx',
158
-
159
- fileRouter: options.mode === 'file-router',
160
- codeRouter: options.mode === 'code-router',
161
-
162
- integrationEnabled,
163
- integrationOption: options.integrationOptions,
164
- integrations: options.chosenIntegrations,
165
- hooks,
166
- routes,
167
-
168
- getPackageManagerAddScript: (pkg: string, isDev: boolean = false) => {
169
- const pm = options.packageManager
170
- if (pm === 'npm') return `npm install ${isDev ? '-D ' : ''}${pkg}`
171
- if (pm === 'yarn') return `yarn add ${isDev ? '-D ' : ''}${pkg}`
172
- if (pm === 'pnpm') return `pnpm add ${isDev ? '-D ' : ''}${pkg}`
173
- if (pm === 'bun') return `bun add ${isDev ? '-d ' : ''}${pkg}`
174
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
175
- if (pm === 'deno') return `deno add ${pkg}`
176
- return `npm install ${isDev ? '-D ' : ''}${pkg}`
177
- },
178
-
179
- getPackageManagerRunScript: (
180
- script: string,
181
- args: Array<string> = [],
182
- ) => {
183
- const pm = options.packageManager
184
- const argsStr = args.length ? ' ' + args.join(' ') : ''
185
- if (pm === 'npm') return `npm run ${script}${argsStr}`
186
- if (pm === 'yarn') return `yarn ${script}${argsStr}`
187
- if (pm === 'pnpm') return `pnpm ${script}${argsStr}`
188
- if (pm === 'bun') return `bun run ${script}${argsStr}`
189
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
190
- if (pm === 'deno') return `deno task ${script}${argsStr}`
191
- return `npm run ${script}${argsStr}`
192
- },
193
-
194
- relativePath: localRelativePath,
195
- hookImportContent,
196
- hookImportCode,
197
-
198
- ignoreFile: () => {
199
- throw new IgnoreFileError()
200
- },
201
- }
202
- }
203
-
204
- export interface ProcessedFile {
205
- path: string
206
- content: string
207
- append: boolean
208
- }
209
-
210
- /**
211
- * Process a template file with EJS
212
- */
213
- export function processTemplateFile(
214
- filePath: string,
215
- content: string,
216
- options: CompileOptions,
217
- ): ProcessedFile | null {
218
- const context = createTemplateContext(options, filePath)
219
-
220
- let processedContent = content
221
- let shouldIgnore = false
222
-
223
- // Process EJS if file ends with .ejs
224
- if (filePath.endsWith('.ejs')) {
225
- try {
226
- processedContent = render(content, context)
227
- } catch (error) {
228
- if (error instanceof IgnoreFileError) {
229
- shouldIgnore = true
230
- } else {
231
- throw new Error(`EJS error in file ${filePath}: ${error}`)
232
- }
233
- }
234
- }
235
-
236
- if (shouldIgnore) {
237
- return null
238
- }
239
-
240
- // Process path transformations
241
- let outputPath = filePath
242
-
243
- // Remove .ejs extension
244
- outputPath = outputPath.replace(/\.ejs$/, '')
245
-
246
- // Convert _dot_ to .
247
- outputPath = convertDotFiles(outputPath)
248
-
249
- // Strip option prefixes
250
- outputPath = stripOptionPrefix(outputPath)
251
-
252
- // Check for .append suffix
253
- let append = false
254
- if (outputPath.endsWith('.append')) {
255
- append = true
256
- outputPath = outputPath.replace(/\.append$/, '')
257
- }
258
-
259
- // Convert .ts/.tsx to .js/.jsx if not TypeScript
260
- if (!options.typescript) {
261
- outputPath = outputPath.replace(/\.tsx$/, '.jsx').replace(/\.ts$/, '.js')
262
- }
263
-
264
- return {
265
- path: outputPath,
266
- content: processedContent,
267
- append,
268
- }
269
- }