@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,114 +0,0 @@
1
- import { resolve } from 'node:path'
2
- import { describe, expect, it } from 'vitest'
3
- import {
4
- fetchIntegration,
5
- fetchIntegrationFiles,
6
- fetchIntegrationInfo,
7
- fetchIntegrations,
8
- fetchManifest,
9
- } from './fetch.js'
10
-
11
- const INTEGRATIONS_PATH = resolve(__dirname, '../../../../integrations')
12
-
13
- describe('fetch API', () => {
14
- describe('fetchManifest', () => {
15
- it('should fetch manifest from local path', async () => {
16
- const manifest = await fetchManifest(INTEGRATIONS_PATH)
17
-
18
- expect(manifest).toBeDefined()
19
- expect(manifest.integrations).toBeInstanceOf(Array)
20
- expect(manifest.integrations.length).toBeGreaterThan(0)
21
- })
22
-
23
- it('should have integrations with required fields', async () => {
24
- const manifest = await fetchManifest(INTEGRATIONS_PATH)
25
-
26
- for (const integration of manifest.integrations) {
27
- expect(integration.id).toBeDefined()
28
- expect(integration.name).toBeDefined()
29
- expect(integration.modes).toBeInstanceOf(Array)
30
- }
31
- })
32
-
33
- it('should throw for non-existent path', async () => {
34
- await expect(fetchManifest('/non/existent/path')).rejects.toThrow()
35
- })
36
- })
37
-
38
- describe('fetchIntegrationInfo', () => {
39
- it('should fetch integration info from local path', async () => {
40
- const info = await fetchIntegrationInfo('tanstack-query', INTEGRATIONS_PATH)
41
-
42
- expect(info.name).toBe('TanStack Query')
43
- expect(info.type).toBe('integration')
44
- expect(info.modes).toContain('file-router')
45
- })
46
-
47
- it('should throw for non-existent integration', async () => {
48
- await expect(
49
- fetchIntegrationInfo('non-existent', INTEGRATIONS_PATH),
50
- ).rejects.toThrow()
51
- })
52
- })
53
-
54
- describe('fetchIntegrationFiles', () => {
55
- it('should fetch integration files from local path', async () => {
56
- const files = await fetchIntegrationFiles('tanstack-query', INTEGRATIONS_PATH)
57
-
58
- expect(Object.keys(files).length).toBeGreaterThan(0)
59
- // Files are in assets/src/integrations/query/
60
- expect(files).toHaveProperty('src/integrations/query/provider.tsx')
61
- })
62
-
63
- it('should return empty object for integration without assets', async () => {
64
- // This tests the case where assets dir doesn't exist
65
- const files = await fetchIntegrationFiles('non-existent', INTEGRATIONS_PATH)
66
- expect(files).toEqual({})
67
- })
68
- })
69
-
70
- describe('fetchIntegration', () => {
71
- it('should fetch complete integration', async () => {
72
- const integration = await fetchIntegration('tanstack-query', INTEGRATIONS_PATH)
73
-
74
- expect(integration.id).toBe('tanstack-query')
75
- expect(integration.name).toBe('TanStack Query')
76
- expect(integration.files).toBeDefined()
77
- expect(Object.keys(integration.files).length).toBeGreaterThan(0)
78
- })
79
-
80
- it('should include hooks if defined', async () => {
81
- const integration = await fetchIntegration('tanstack-query', INTEGRATIONS_PATH)
82
-
83
- expect(integration.hooks).toBeDefined()
84
- expect(integration.hooks!.length).toBeGreaterThan(0)
85
- })
86
-
87
- it('should merge package.json into packageAdditions', async () => {
88
- const integration = await fetchIntegration('tanstack-query', INTEGRATIONS_PATH)
89
-
90
- expect(integration.packageAdditions).toBeDefined()
91
- expect(integration.packageAdditions?.dependencies).toHaveProperty(
92
- '@tanstack/react-query',
93
- )
94
- })
95
- })
96
-
97
- describe('fetchIntegrations', () => {
98
- it('should fetch multiple integrations in parallel', async () => {
99
- const integrations = await fetchIntegrations(
100
- ['tanstack-query', 'tanstack-form'],
101
- INTEGRATIONS_PATH,
102
- )
103
-
104
- expect(integrations).toHaveLength(2)
105
- expect(integrations[0]?.id).toBe('tanstack-query')
106
- expect(integrations[1]?.id).toBe('tanstack-form')
107
- })
108
-
109
- it('should return empty array for empty input', async () => {
110
- const integrations = await fetchIntegrations([], INTEGRATIONS_PATH)
111
- expect(integrations).toEqual([])
112
- })
113
- })
114
- })
package/src/api/fetch.ts DELETED
@@ -1,249 +0,0 @@
1
- import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
2
- import { join } from 'node:path'
3
- import {
4
- IntegrationCompiledSchema,
5
- IntegrationInfoSchema,
6
- ManifestSchema
7
- } from '../engine/types.js'
8
- import { fetchWithCache } from '../cache/index.js'
9
- import type { IntegrationCompiled, IntegrationInfo, Manifest } from '../engine/types.js'
10
-
11
- const GITHUB_RAW_BASE =
12
- 'https://raw.githubusercontent.com/TanStack/cli/main/integrations'
13
-
14
- // 1 hour cache TTL for remote fetches
15
- const CACHE_TTL_MS = 60 * 60 * 1000
16
-
17
- /**
18
- * Check if a path is a local directory
19
- */
20
- function isLocalPath(path: string): boolean {
21
- return path.startsWith('/') || path.startsWith('./') || path.startsWith('..')
22
- }
23
-
24
- /**
25
- * Fetch the integration manifest from GitHub or local path (with caching for remote)
26
- */
27
- export async function fetchManifest(
28
- baseUrl: string = GITHUB_RAW_BASE,
29
- ): Promise<Manifest> {
30
- if (isLocalPath(baseUrl)) {
31
- const manifestPath = join(baseUrl, 'manifest.json')
32
- if (!existsSync(manifestPath)) {
33
- throw new Error(`Manifest not found at ${manifestPath}`)
34
- }
35
- const data = JSON.parse(readFileSync(manifestPath, 'utf-8'))
36
- return ManifestSchema.parse(data)
37
- }
38
-
39
- const cacheKey = `manifest_${baseUrl.replace(/[^a-zA-Z0-9]/g, '_')}`
40
-
41
- return fetchWithCache(
42
- cacheKey,
43
- async () => {
44
- const url = `${baseUrl}/manifest.json`
45
- const response = await fetch(url)
46
-
47
- if (!response.ok) {
48
- throw new Error(`Failed to fetch manifest: ${response.statusText}`)
49
- }
50
-
51
- const data = await response.json()
52
- return ManifestSchema.parse(data)
53
- },
54
- CACHE_TTL_MS,
55
- )
56
- }
57
-
58
- /**
59
- * Fetch integration info.json from GitHub or local path (with caching for remote)
60
- */
61
- export async function fetchIntegrationInfo(
62
- integrationId: string,
63
- baseUrl: string = GITHUB_RAW_BASE,
64
- ): Promise<IntegrationInfo> {
65
- if (isLocalPath(baseUrl)) {
66
- const infoPath = join(baseUrl, integrationId, 'info.json')
67
- if (!existsSync(infoPath)) {
68
- throw new Error(`Integration info not found at ${infoPath}`)
69
- }
70
- const data = JSON.parse(readFileSync(infoPath, 'utf-8'))
71
- return IntegrationInfoSchema.parse(data)
72
- }
73
-
74
- const cacheKey = `integration_info_${integrationId}_${baseUrl.replace(/[^a-zA-Z0-9]/g, '_')}`
75
-
76
- return fetchWithCache(
77
- cacheKey,
78
- async () => {
79
- const url = `${baseUrl}/${integrationId}/info.json`
80
- const response = await fetch(url)
81
-
82
- if (!response.ok) {
83
- throw new Error(`Failed to fetch integration ${integrationId}: ${response.statusText}`)
84
- }
85
-
86
- const data = await response.json()
87
- return IntegrationInfoSchema.parse(data)
88
- },
89
- CACHE_TTL_MS,
90
- )
91
- }
92
-
93
- /**
94
- * Recursively read all files from a directory
95
- */
96
- function readDirRecursive(
97
- dir: string,
98
- basePath: string = '',
99
- ): Record<string, string> {
100
- const files: Record<string, string> = {}
101
-
102
- if (!existsSync(dir)) return files
103
-
104
- for (const entry of readdirSync(dir)) {
105
- const fullPath = join(dir, entry)
106
- const relativePath = basePath ? `${basePath}/${entry}` : entry
107
- const stat = statSync(fullPath)
108
-
109
- if (stat.isDirectory()) {
110
- Object.assign(files, readDirRecursive(fullPath, relativePath))
111
- } else {
112
- files[relativePath] = readFileSync(fullPath, 'utf-8')
113
- }
114
- }
115
-
116
- return files
117
- }
118
-
119
- /**
120
- * Fetch all files for an integration from GitHub or local path (with caching for remote)
121
- */
122
- export async function fetchIntegrationFiles(
123
- integrationId: string,
124
- baseUrl: string = GITHUB_RAW_BASE,
125
- ): Promise<Record<string, string>> {
126
- if (isLocalPath(baseUrl)) {
127
- const assetsPath = join(baseUrl, integrationId, 'assets')
128
- return readDirRecursive(assetsPath)
129
- }
130
-
131
- const cacheKey = `integration_files_${integrationId}_${baseUrl.replace(/[^a-zA-Z0-9]/g, '_')}`
132
-
133
- return fetchWithCache(
134
- cacheKey,
135
- async () => {
136
- // First fetch the file list (we'll need a files.json or similar)
137
- const filesUrl = `${baseUrl}/${integrationId}/files.json`
138
- const response = await fetch(filesUrl)
139
-
140
- if (!response.ok) {
141
- // No files.json, return empty
142
- return {}
143
- }
144
-
145
- const fileList: Array<string> = await response.json()
146
- const files: Record<string, string> = {}
147
-
148
- // Fetch each file
149
- await Promise.all(
150
- fileList.map(async (filePath) => {
151
- const fileUrl = `${baseUrl}/${integrationId}/assets/${filePath}`
152
- const fileResponse = await fetch(fileUrl)
153
-
154
- if (fileResponse.ok) {
155
- files[filePath] = await fileResponse.text()
156
- }
157
- }),
158
- )
159
-
160
- return files
161
- },
162
- CACHE_TTL_MS,
163
- )
164
- }
165
-
166
- /**
167
- * Fetch integration package.json if it exists
168
- */
169
- async function fetchIntegrationPackageJson(
170
- integrationId: string,
171
- baseUrl: string,
172
- ): Promise<{
173
- dependencies?: Record<string, string>
174
- devDependencies?: Record<string, string>
175
- scripts?: Record<string, string>
176
- } | null> {
177
- if (isLocalPath(baseUrl)) {
178
- const pkgPath = join(baseUrl, integrationId, 'package.json')
179
- if (existsSync(pkgPath)) {
180
- return JSON.parse(readFileSync(pkgPath, 'utf-8'))
181
- }
182
- return null
183
- }
184
-
185
- const url = `${baseUrl}/${integrationId}/package.json`
186
- const response = await fetch(url)
187
-
188
- if (!response.ok) {
189
- return null
190
- }
191
-
192
- return response.json()
193
- }
194
-
195
- /**
196
- * Fetch a complete compiled integration from GitHub
197
- */
198
- export async function fetchIntegration(
199
- integrationId: string,
200
- baseUrl: string = GITHUB_RAW_BASE,
201
- ): Promise<IntegrationCompiled> {
202
- const [info, files, pkgJson] = await Promise.all([
203
- fetchIntegrationInfo(integrationId, baseUrl),
204
- fetchIntegrationFiles(integrationId, baseUrl),
205
- fetchIntegrationPackageJson(integrationId, baseUrl),
206
- ])
207
-
208
- // Merge package.json into packageAdditions if present
209
- const packageAdditions = info.packageAdditions ?? {}
210
- if (pkgJson) {
211
- if (pkgJson.dependencies) {
212
- packageAdditions.dependencies = {
213
- ...packageAdditions.dependencies,
214
- ...pkgJson.dependencies,
215
- }
216
- }
217
- if (pkgJson.devDependencies) {
218
- packageAdditions.devDependencies = {
219
- ...packageAdditions.devDependencies,
220
- ...pkgJson.devDependencies,
221
- }
222
- }
223
- if (pkgJson.scripts) {
224
- packageAdditions.scripts = {
225
- ...packageAdditions.scripts,
226
- ...pkgJson.scripts,
227
- }
228
- }
229
- }
230
-
231
- return IntegrationCompiledSchema.parse({
232
- ...info,
233
- id: integrationId,
234
- files,
235
- packageAdditions:
236
- Object.keys(packageAdditions).length > 0 ? packageAdditions : undefined,
237
- deletedFiles: [],
238
- })
239
- }
240
-
241
- /**
242
- * Fetch multiple integrations in parallel
243
- */
244
- export async function fetchIntegrations(
245
- integrationIds: Array<string>,
246
- baseUrl: string = GITHUB_RAW_BASE,
247
- ): Promise<Array<IntegrationCompiled>> {
248
- return Promise.all(integrationIds.map((id) => fetchIntegration(id, baseUrl)))
249
- }
@@ -1,89 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
2
- import { join } from 'node:path'
3
- import { homedir } from 'node:os'
4
-
5
- const CACHE_DIR = join(homedir(), '.tanstack', 'cache')
6
- const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000 // 24 hours
7
-
8
- interface CacheEntry<T> {
9
- data: T
10
- timestamp: number
11
- ttl: number
12
- }
13
-
14
- function ensureCacheDir() {
15
- if (!existsSync(CACHE_DIR)) {
16
- mkdirSync(CACHE_DIR, { recursive: true })
17
- }
18
- }
19
-
20
- function getCachePath(key: string): string {
21
- // Sanitize key to be filesystem-safe
22
- const safeKey = key.replace(/[^a-zA-Z0-9-_]/g, '_')
23
- return join(CACHE_DIR, `${safeKey}.json`)
24
- }
25
-
26
- export function getCached<T>(key: string): T | null {
27
- const cachePath = getCachePath(key)
28
-
29
- if (!existsSync(cachePath)) {
30
- return null
31
- }
32
-
33
- try {
34
- const raw = readFileSync(cachePath, 'utf-8')
35
- const entry: CacheEntry<T> = JSON.parse(raw)
36
-
37
- const age = Date.now() - entry.timestamp
38
- if (age > entry.ttl) {
39
- return null // Expired
40
- }
41
-
42
- return entry.data
43
- } catch {
44
- return null
45
- }
46
- }
47
-
48
- export function setCache<T>(key: string, data: T, ttlMs: number = DEFAULT_TTL_MS): void {
49
- ensureCacheDir()
50
- const cachePath = getCachePath(key)
51
-
52
- const entry: CacheEntry<T> = {
53
- data,
54
- timestamp: Date.now(),
55
- ttl: ttlMs,
56
- }
57
-
58
- writeFileSync(cachePath, JSON.stringify(entry, null, 2), 'utf-8')
59
- }
60
-
61
- export async function fetchWithCache<T>(
62
- key: string,
63
- fetcher: () => Promise<T>,
64
- ttlMs: number = DEFAULT_TTL_MS,
65
- ): Promise<T> {
66
- // Try cache first
67
- const cached = getCached<T>(key)
68
- if (cached !== null) {
69
- return cached
70
- }
71
-
72
- // Fetch fresh data
73
- const data = await fetcher()
74
-
75
- // Cache it
76
- setCache(key, data, ttlMs)
77
-
78
- return data
79
- }
80
-
81
- export function clearCache(): void {
82
- if (existsSync(CACHE_DIR)) {
83
- rmSync(CACHE_DIR, { recursive: true, force: true })
84
- }
85
- }
86
-
87
- export function getCacheDir(): string {
88
- return CACHE_DIR
89
- }