@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.
Files changed (83) hide show
  1. package/dist/bin.js +7 -0
  2. package/dist/cli.js +481 -0
  3. package/dist/command-line.js +174 -0
  4. package/dist/dev-watch.js +290 -0
  5. package/dist/file-syncer.js +148 -0
  6. package/dist/index.js +1 -0
  7. package/dist/mcp/api.js +31 -0
  8. package/dist/mcp/tools.js +250 -0
  9. package/dist/mcp/types.js +37 -0
  10. package/dist/mcp.js +121 -0
  11. package/dist/options.js +162 -0
  12. package/dist/types/bin.d.ts +2 -0
  13. package/dist/types/cli.d.ts +16 -0
  14. package/dist/types/command-line.d.ts +10 -0
  15. package/dist/types/dev-watch.d.ts +27 -0
  16. package/dist/types/file-syncer.d.ts +18 -0
  17. package/dist/types/index.d.ts +1 -0
  18. package/dist/types/mcp/api.d.ts +4 -0
  19. package/dist/types/mcp/tools.d.ts +2 -0
  20. package/dist/types/mcp/types.d.ts +217 -0
  21. package/dist/types/mcp.d.ts +6 -0
  22. package/dist/types/options.d.ts +8 -0
  23. package/dist/types/types.d.ts +25 -0
  24. package/dist/types/ui-environment.d.ts +2 -0
  25. package/dist/types/ui-prompts.d.ts +12 -0
  26. package/dist/types/utils.d.ts +8 -0
  27. package/dist/types.js +1 -0
  28. package/dist/ui-environment.js +52 -0
  29. package/dist/ui-prompts.js +244 -0
  30. package/dist/utils.js +30 -0
  31. package/package.json +46 -46
  32. package/src/bin.ts +6 -93
  33. package/src/cli.ts +692 -0
  34. package/src/command-line.ts +236 -0
  35. package/src/dev-watch.ts +430 -0
  36. package/src/file-syncer.ts +205 -0
  37. package/src/index.ts +1 -85
  38. package/src/mcp.ts +190 -0
  39. package/src/options.ts +260 -0
  40. package/src/types.ts +27 -0
  41. package/src/ui-environment.ts +74 -0
  42. package/src/ui-prompts.ts +322 -0
  43. package/src/utils.ts +38 -0
  44. package/tests/command-line.test.ts +304 -0
  45. package/tests/index.test.ts +9 -0
  46. package/tests/mcp.test.ts +225 -0
  47. package/tests/options.test.ts +304 -0
  48. package/tests/setupVitest.ts +6 -0
  49. package/tests/ui-environment.test.ts +97 -0
  50. package/tests/ui-prompts.test.ts +238 -0
  51. package/tsconfig.json +17 -0
  52. package/vitest.config.js +7 -0
  53. package/dist/bin.cjs +0 -761
  54. package/dist/bin.d.cts +0 -1
  55. package/dist/bin.d.mts +0 -1
  56. package/dist/bin.mjs +0 -760
  57. package/dist/index.cjs +0 -36
  58. package/dist/index.d.cts +0 -1172
  59. package/dist/index.d.mts +0 -1172
  60. package/dist/index.mjs +0 -3
  61. package/dist/template-CkAkdP8n.mjs +0 -2545
  62. package/dist/template-Cup47s9h.cjs +0 -2783
  63. package/src/api/fetch.test.ts +0 -114
  64. package/src/api/fetch.ts +0 -249
  65. package/src/cache/index.ts +0 -89
  66. package/src/commands/create.ts +0 -463
  67. package/src/commands/mcp.test.ts +0 -152
  68. package/src/commands/mcp.ts +0 -203
  69. package/src/engine/compile-with-addons.test.ts +0 -302
  70. package/src/engine/compile.test.ts +0 -404
  71. package/src/engine/compile.ts +0 -551
  72. package/src/engine/config-file.test.ts +0 -118
  73. package/src/engine/config-file.ts +0 -61
  74. package/src/engine/custom-addons/integration.ts +0 -323
  75. package/src/engine/custom-addons/shared.test.ts +0 -98
  76. package/src/engine/custom-addons/shared.ts +0 -281
  77. package/src/engine/custom-addons/template.test.ts +0 -288
  78. package/src/engine/custom-addons/template.ts +0 -124
  79. package/src/engine/template.test.ts +0 -256
  80. package/src/engine/template.ts +0 -269
  81. package/src/engine/types.ts +0 -336
  82. package/src/parse-gitignore.d.ts +0 -5
  83. package/src/templates/base.ts +0 -891
@@ -1,61 +0,0 @@
1
- import { readFile, writeFile } from 'node:fs/promises'
2
- import { existsSync } from 'node:fs'
3
- import { resolve } from 'node:path'
4
-
5
- import type { CompileOptions, PackageManager, RouterMode } from './types.js'
6
-
7
- export const CONFIG_FILE = '.tanstack.json'
8
-
9
- export interface PersistedOptions {
10
- version: number
11
- projectName: string
12
- framework: string
13
- mode: RouterMode
14
- typescript: boolean
15
- tailwind: boolean
16
- packageManager: PackageManager
17
- chosenIntegrations: Array<string>
18
- customTemplate?: string
19
- }
20
-
21
- function createPersistedOptions(options: CompileOptions): PersistedOptions {
22
- return {
23
- version: 1,
24
- projectName: options.projectName,
25
- framework: options.framework,
26
- mode: options.mode,
27
- typescript: options.typescript,
28
- tailwind: options.tailwind,
29
- packageManager: options.packageManager,
30
- chosenIntegrations: options.chosenIntegrations.map((integration) => integration.id),
31
- customTemplate: options.customTemplate?.id,
32
- }
33
- }
34
-
35
- export async function writeConfigFile(
36
- targetDir: string,
37
- options: CompileOptions,
38
- ): Promise<void> {
39
- const configPath = resolve(targetDir, CONFIG_FILE)
40
- await writeFile(
41
- configPath,
42
- JSON.stringify(createPersistedOptions(options), null, 2),
43
- )
44
- }
45
-
46
- export async function readConfigFile(
47
- targetDir: string,
48
- ): Promise<PersistedOptions | null> {
49
- const configPath = resolve(targetDir, CONFIG_FILE)
50
-
51
- if (!existsSync(configPath)) {
52
- return null
53
- }
54
-
55
- try {
56
- const content = await readFile(configPath, 'utf-8')
57
- return JSON.parse(content)
58
- } catch {
59
- return null
60
- }
61
- }
@@ -1,323 +0,0 @@
1
- import { mkdir, readFile, writeFile } from 'node:fs/promises'
2
- import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
3
- import { basename, dirname, resolve } from 'node:path'
4
-
5
- import { IntegrationCompiledSchema } from '../types.js'
6
- import {
7
- compareFilesRecursively,
8
- createCompileOptionsFromPersisted,
9
- createIgnore,
10
- createPackageAdditions,
11
- readCurrentProjectOptions,
12
- recursivelyGatherFiles,
13
- runCompile,
14
- } from './shared.js'
15
-
16
- import type { PersistedOptions } from '../config-file.js'
17
- import type { IntegrationCompiled, IntegrationInfo, Route } from '../types.js'
18
-
19
- const INTEGRATION_DIR = '.integration'
20
- const INFO_FILE = '.integration/info.json'
21
- const COMPILED_FILE = 'integration.json'
22
- const ASSETS_DIR = 'assets'
23
-
24
- // Files to ignore when building integration assets (these are generated or template-specific)
25
- const INTEGRATION_IGNORE_FILES = [
26
- 'main.jsx',
27
- 'App.jsx',
28
- 'main.tsx',
29
- 'App.tsx',
30
- 'routeTree.gen.ts',
31
- ]
32
-
33
- function camelCase(str: string): string {
34
- return str
35
- .split(/(\.|-|\/)/)
36
- .filter((part) => /^[a-zA-Z]+$/.test(part))
37
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
38
- .join('')
39
- }
40
-
41
- function templatize(routeCode: string, routeFile: string): {
42
- url: string
43
- code: string
44
- name: string
45
- jsName: string
46
- } {
47
- let code = routeCode
48
-
49
- // Replace the import
50
- code = code.replace(
51
- /import { createFileRoute } from ['"]@tanstack\/react-router['"]/g,
52
- `import { <% if (fileRouter) { %>createFileRoute<% } else { %>createRoute<% } %> } from '@tanstack/react-router'`,
53
- )
54
-
55
- // Extract route path and definition, then transform the route declaration
56
- const routeMatch = code.match(
57
- /export\s+const\s+Route\s*=\s*createFileRoute\(['"]([^'"]+)['"]\)\s*\(\{([^}]+)\}\)/,
58
- )
59
-
60
- let path = ''
61
-
62
- if (routeMatch) {
63
- const fullMatch = routeMatch[0]
64
- path = routeMatch[1]!
65
- const routeDefinition = routeMatch[2]
66
- code = code.replace(
67
- fullMatch,
68
- `<% if (codeRouter) { %>
69
- import type { RootRoute } from '@tanstack/react-router'
70
- <% } else { %>
71
- export const Route = createFileRoute('${path}')({${routeDefinition}})
72
- <% } %>`,
73
- )
74
-
75
- code += `
76
- <% if (codeRouter) { %>
77
- export default (parentRoute: RootRoute) => createRoute({
78
- path: '${path}',
79
- ${routeDefinition}
80
- getParentRoute: () => parentRoute,
81
- })
82
- <% } %>
83
- `
84
- } else {
85
- console.warn(`No route found in the file: ${routeFile}`)
86
- }
87
-
88
- const name = basename(path)
89
- .replace('.tsx', '')
90
- .replace(/^demo/, '')
91
- .replace('.', ' ')
92
- .trim()
93
-
94
- const jsName = camelCase(basename(path))
95
-
96
- return { url: path, code, name, jsName }
97
- }
98
-
99
- async function validateIntegrationSetup(targetDir: string): Promise<void> {
100
- const options = await readCurrentProjectOptions(targetDir)
101
-
102
- if (options.mode === 'code-router') {
103
- throw new Error(
104
- 'This project is using code-router mode.\n' +
105
- 'To create an integration, the project must use file-router mode.',
106
- )
107
- }
108
- if (!options.tailwind) {
109
- throw new Error(
110
- 'This project is not using Tailwind CSS.\n' +
111
- 'To create an integration, the project must be created with Tailwind CSS.',
112
- )
113
- }
114
- if (!options.typescript) {
115
- throw new Error(
116
- 'This project is not using TypeScript.\n' +
117
- 'To create an integration, the project must be created with TypeScript.',
118
- )
119
- }
120
- }
121
-
122
- async function readOrGenerateIntegrationInfo(
123
- options: PersistedOptions,
124
- targetDir: string,
125
- ): Promise<IntegrationInfo> {
126
- const infoPath = resolve(targetDir, INFO_FILE)
127
-
128
- if (existsSync(infoPath)) {
129
- const content = await readFile(infoPath, 'utf-8')
130
- return JSON.parse(content)
131
- }
132
-
133
- return {
134
- id: `${options.projectName}-integration`,
135
- name: `${options.projectName} Integration`,
136
- description: 'Custom integration',
137
- author: 'Author <author@example.com>',
138
- version: '0.0.1',
139
- license: 'MIT',
140
- link: `https://github.com/example/${options.projectName}-integration`,
141
-
142
- type: 'integration',
143
- phase: 'integration',
144
- modes: [options.mode],
145
-
146
- requiresTailwind: options.tailwind || undefined,
147
-
148
- dependsOn: options.chosenIntegrations.length > 0 ? options.chosenIntegrations : undefined,
149
-
150
- routes: [],
151
- packageAdditions: {
152
- scripts: {},
153
- dependencies: {},
154
- devDependencies: {},
155
- },
156
- }
157
- }
158
-
159
- async function generateBaseProject(
160
- persistedOptions: PersistedOptions,
161
- targetDir: string,
162
- integrationsPath?: string,
163
- ): Promise<{ info: IntegrationInfo; output: { files: Record<string, string> } }> {
164
- const info = await readOrGenerateIntegrationInfo(persistedOptions, targetDir)
165
-
166
- const compileOptions = await createCompileOptionsFromPersisted(
167
- persistedOptions,
168
- integrationsPath,
169
- )
170
-
171
- const output = runCompile(compileOptions)
172
-
173
- return { info, output }
174
- }
175
-
176
- async function buildAssetsDirectory(
177
- targetDir: string,
178
- output: { files: Record<string, string> },
179
- info: IntegrationInfo,
180
- ): Promise<void> {
181
- const assetsDir = resolve(targetDir, INTEGRATION_DIR, ASSETS_DIR)
182
-
183
- // Only build if assets directory doesn't exist yet
184
- if (existsSync(assetsDir)) {
185
- return
186
- }
187
-
188
- const ignoreFn = createIgnore(targetDir)
189
- const changedFiles: Record<string, string> = {}
190
- await compareFilesRecursively(targetDir, ignoreFn, output.files, changedFiles)
191
-
192
- for (const file of Object.keys(changedFiles)) {
193
- // Skip ignored files
194
- if (INTEGRATION_IGNORE_FILES.includes(basename(file))) {
195
- continue
196
- }
197
-
198
- const targetPath = resolve(assetsDir, file)
199
- mkdirSync(dirname(targetPath), { recursive: true })
200
-
201
- // Templatize route files
202
- const fileContent = changedFiles[file]!
203
- if (file.includes('/routes/')) {
204
- const { url, code, name, jsName } = templatize(fileContent, file)
205
-
206
- info.routes ||= []
207
- const existingRoute = info.routes.find((r: Route) => r.url === url)
208
- if (!existingRoute) {
209
- info.routes.push({
210
- url,
211
- name,
212
- jsName,
213
- path: file,
214
- })
215
- }
216
-
217
- writeFileSync(`${targetPath}.ejs`, code)
218
- } else {
219
- writeFileSync(targetPath, fileContent)
220
- }
221
- }
222
- }
223
-
224
- async function updateIntegrationInfo(
225
- targetDir: string,
226
- integrationsPath?: string,
227
- ): Promise<void> {
228
- const persistedOptions = await readCurrentProjectOptions(targetDir)
229
- const { info, output } = await generateBaseProject(persistedOptions, targetDir, integrationsPath)
230
-
231
- // Calculate package.json differences
232
- const originalPackageJson = JSON.parse(output.files['package.json']!)
233
- const currentPackageJson = JSON.parse(
234
- await readFile(resolve(targetDir, 'package.json'), 'utf-8'),
235
- )
236
-
237
- info.packageAdditions = createPackageAdditions(
238
- originalPackageJson,
239
- currentPackageJson,
240
- )
241
-
242
- await buildAssetsDirectory(targetDir, output, info)
243
-
244
- // Write info file
245
- const infoDir = resolve(targetDir, dirname(INFO_FILE))
246
- await mkdir(infoDir, { recursive: true })
247
- await writeFile(
248
- resolve(targetDir, INFO_FILE),
249
- JSON.stringify(info, null, 2),
250
- )
251
- }
252
-
253
- export async function compileIntegration(
254
- targetDir: string,
255
- _integrationsPath?: string,
256
- ): Promise<void> {
257
- const persistedOptions = await readCurrentProjectOptions(targetDir)
258
- const info = await readOrGenerateIntegrationInfo(persistedOptions, targetDir)
259
-
260
- const assetsDir = resolve(targetDir, INTEGRATION_DIR, ASSETS_DIR)
261
-
262
- const compiledInfo: IntegrationCompiled = {
263
- ...info,
264
- id: info.id || `${persistedOptions.projectName}-integration`,
265
- files: await recursivelyGatherFiles(assetsDir),
266
- deletedFiles: [],
267
- }
268
-
269
- await writeFile(
270
- resolve(targetDir, COMPILED_FILE),
271
- JSON.stringify(compiledInfo, null, 2),
272
- )
273
-
274
- console.log(`Compiled integration written to ${COMPILED_FILE}`)
275
- }
276
-
277
- export async function initIntegration(
278
- targetDir: string,
279
- integrationsPath?: string,
280
- ): Promise<void> {
281
- await validateIntegrationSetup(targetDir)
282
- await updateIntegrationInfo(targetDir, integrationsPath)
283
- await compileIntegration(targetDir, integrationsPath)
284
-
285
- console.log(`
286
- Integration initialized successfully!
287
-
288
- Files created:
289
- ${INFO_FILE} - Integration metadata (edit this to customize)
290
- ${INTEGRATION_DIR}/${ASSETS_DIR}/ - Integration asset files
291
- ${COMPILED_FILE} - Compiled integration (distribute this)
292
-
293
- Next steps:
294
- 1. Edit ${INFO_FILE} to customize your integration metadata
295
- 2. Run 'tanstack integration compile' to rebuild after changes
296
- 3. Share ${COMPILED_FILE} or host it publicly
297
- `)
298
- }
299
-
300
- /**
301
- * Load a remote integration from a URL
302
- */
303
- export async function loadRemoteIntegration(url: string): Promise<IntegrationCompiled> {
304
- const response = await fetch(url)
305
- if (!response.ok) {
306
- throw new Error(`Failed to fetch integration from ${url}: ${response.statusText}`)
307
- }
308
-
309
- const jsonContent = await response.json()
310
-
311
- const result = IntegrationCompiledSchema.safeParse(jsonContent)
312
- if (!result.success) {
313
- throw new Error(`Invalid integration at ${url}: ${result.error.message}`)
314
- }
315
-
316
- const integration = result.data
317
- // Use the URL as the ID if not set
318
- if (!integration.id) {
319
- integration.id = url
320
- }
321
-
322
- return integration
323
- }
@@ -1,98 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { createPackageAdditions } from './shared.js'
3
-
4
- describe('createPackageAdditions', () => {
5
- it('should return empty object when packages are identical', () => {
6
- const original = {
7
- dependencies: { react: '^18.0.0' },
8
- devDependencies: { typescript: '^5.0.0' },
9
- }
10
- const current = {
11
- dependencies: { react: '^18.0.0' },
12
- devDependencies: { typescript: '^5.0.0' },
13
- }
14
- expect(createPackageAdditions(original, current)).toEqual({})
15
- })
16
-
17
- it('should detect new dependencies', () => {
18
- const original = {
19
- dependencies: { react: '^18.0.0' },
20
- }
21
- const current = {
22
- dependencies: { react: '^18.0.0', 'react-dom': '^18.0.0' },
23
- }
24
- expect(createPackageAdditions(original, current)).toEqual({
25
- dependencies: { 'react-dom': '^18.0.0' },
26
- })
27
- })
28
-
29
- it('should detect updated dependency versions', () => {
30
- const original = {
31
- dependencies: { react: '^18.0.0' },
32
- }
33
- const current = {
34
- dependencies: { react: '^19.0.0' },
35
- }
36
- expect(createPackageAdditions(original, current)).toEqual({
37
- dependencies: { react: '^19.0.0' },
38
- })
39
- })
40
-
41
- it('should detect new devDependencies', () => {
42
- const original = {
43
- devDependencies: { typescript: '^5.0.0' },
44
- }
45
- const current = {
46
- devDependencies: { typescript: '^5.0.0', vitest: '^1.0.0' },
47
- }
48
- expect(createPackageAdditions(original, current)).toEqual({
49
- devDependencies: { vitest: '^1.0.0' },
50
- })
51
- })
52
-
53
- it('should detect new scripts', () => {
54
- const original = {
55
- scripts: { dev: 'vinxi dev' },
56
- }
57
- const current = {
58
- scripts: { dev: 'vinxi dev', test: 'vitest' },
59
- }
60
- expect(createPackageAdditions(original, current)).toEqual({
61
- scripts: { test: 'vitest' },
62
- })
63
- })
64
-
65
- it('should detect updated scripts', () => {
66
- const original = {
67
- scripts: { dev: 'vinxi dev' },
68
- }
69
- const current = {
70
- scripts: { dev: 'vite dev' },
71
- }
72
- expect(createPackageAdditions(original, current)).toEqual({
73
- scripts: { dev: 'vite dev' },
74
- })
75
- })
76
-
77
- it('should handle missing sections in original', () => {
78
- const original = {}
79
- const current = {
80
- dependencies: { react: '^18.0.0' },
81
- devDependencies: { typescript: '^5.0.0' },
82
- scripts: { dev: 'vite' },
83
- }
84
- expect(createPackageAdditions(original, current)).toEqual({
85
- dependencies: { react: '^18.0.0' },
86
- devDependencies: { typescript: '^5.0.0' },
87
- scripts: { dev: 'vite' },
88
- })
89
- })
90
-
91
- it('should handle missing sections in current', () => {
92
- const original = {
93
- dependencies: { react: '^18.0.0' },
94
- }
95
- const current = {}
96
- expect(createPackageAdditions(original, current)).toEqual({})
97
- })
98
- })