@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,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
- })