@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,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,278 +0,0 @@
1
- import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
2
- import { extname, 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
- // Binary file extensions that should be read as base64
18
- const BINARY_EXTENSIONS = new Set([
19
- '.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico', '.svg',
20
- '.woff', '.woff2', '.ttf', '.eot', '.otf',
21
- '.pdf', '.zip', '.tar', '.gz',
22
- '.mp3', '.mp4', '.wav', '.ogg', '.webm',
23
- ])
24
-
25
- // Prefix for base64-encoded binary files
26
- export const BINARY_PREFIX = 'base64:'
27
-
28
- /**
29
- * Check if a file should be treated as binary based on extension
30
- */
31
- function isBinaryFile(filePath: string): boolean {
32
- return BINARY_EXTENSIONS.has(extname(filePath).toLowerCase())
33
- }
34
-
35
- /**
36
- * Check if a path is a local directory
37
- */
38
- function isLocalPath(path: string): boolean {
39
- return path.startsWith('/') || path.startsWith('./') || path.startsWith('..')
40
- }
41
-
42
- /**
43
- * Fetch the integration manifest from GitHub or local path (with caching for remote)
44
- */
45
- export async function fetchManifest(
46
- baseUrl: string = GITHUB_RAW_BASE,
47
- ): Promise<Manifest> {
48
- if (isLocalPath(baseUrl)) {
49
- const manifestPath = join(baseUrl, 'manifest.json')
50
- if (!existsSync(manifestPath)) {
51
- throw new Error(`Manifest not found at ${manifestPath}`)
52
- }
53
- const data = JSON.parse(readFileSync(manifestPath, 'utf-8'))
54
- return ManifestSchema.parse(data)
55
- }
56
-
57
- const cacheKey = `manifest_${baseUrl.replace(/[^a-zA-Z0-9]/g, '_')}`
58
-
59
- return fetchWithCache(
60
- cacheKey,
61
- async () => {
62
- const url = `${baseUrl}/manifest.json`
63
- const response = await fetch(url)
64
-
65
- if (!response.ok) {
66
- throw new Error(`Failed to fetch manifest: ${response.statusText}`)
67
- }
68
-
69
- const data = await response.json()
70
- return ManifestSchema.parse(data)
71
- },
72
- CACHE_TTL_MS,
73
- )
74
- }
75
-
76
- /**
77
- * Fetch integration info.json from GitHub or local path (with caching for remote)
78
- */
79
- export async function fetchIntegrationInfo(
80
- integrationId: string,
81
- baseUrl: string = GITHUB_RAW_BASE,
82
- ): Promise<IntegrationInfo> {
83
- if (isLocalPath(baseUrl)) {
84
- const infoPath = join(baseUrl, integrationId, 'info.json')
85
- if (!existsSync(infoPath)) {
86
- throw new Error(`Integration info not found at ${infoPath}`)
87
- }
88
- const data = JSON.parse(readFileSync(infoPath, 'utf-8'))
89
- return IntegrationInfoSchema.parse(data)
90
- }
91
-
92
- const cacheKey = `integration_info_${integrationId}_${baseUrl.replace(/[^a-zA-Z0-9]/g, '_')}`
93
-
94
- return fetchWithCache(
95
- cacheKey,
96
- async () => {
97
- const url = `${baseUrl}/${integrationId}/info.json`
98
- const response = await fetch(url)
99
-
100
- if (!response.ok) {
101
- throw new Error(`Failed to fetch integration ${integrationId}: ${response.statusText}`)
102
- }
103
-
104
- const data = await response.json()
105
- return IntegrationInfoSchema.parse(data)
106
- },
107
- CACHE_TTL_MS,
108
- )
109
- }
110
-
111
- /**
112
- * Recursively read all files from a directory
113
- * Binary files are read as base64 with a prefix marker
114
- */
115
- function readDirRecursive(
116
- dir: string,
117
- basePath: string = '',
118
- ): Record<string, string> {
119
- const files: Record<string, string> = {}
120
-
121
- if (!existsSync(dir)) return files
122
-
123
- for (const entry of readdirSync(dir)) {
124
- const fullPath = join(dir, entry)
125
- const relativePath = basePath ? `${basePath}/${entry}` : entry
126
- const stat = statSync(fullPath)
127
-
128
- if (stat.isDirectory()) {
129
- Object.assign(files, readDirRecursive(fullPath, relativePath))
130
- } else if (isBinaryFile(relativePath)) {
131
- // Read binary files as base64 with prefix
132
- const buffer = readFileSync(fullPath)
133
- files[relativePath] = BINARY_PREFIX + buffer.toString('base64')
134
- } else {
135
- files[relativePath] = readFileSync(fullPath, 'utf-8')
136
- }
137
- }
138
-
139
- return files
140
- }
141
-
142
- /**
143
- * Fetch all files for an integration from GitHub or local path (with caching for remote)
144
- */
145
- export async function fetchIntegrationFiles(
146
- integrationId: string,
147
- baseUrl: string = GITHUB_RAW_BASE,
148
- ): Promise<Record<string, string>> {
149
- if (isLocalPath(baseUrl)) {
150
- const assetsPath = join(baseUrl, integrationId, 'assets')
151
- return readDirRecursive(assetsPath)
152
- }
153
-
154
- const cacheKey = `integration_files_${integrationId}_${baseUrl.replace(/[^a-zA-Z0-9]/g, '_')}`
155
-
156
- return fetchWithCache(
157
- cacheKey,
158
- async () => {
159
- // First fetch the file list (we'll need a files.json or similar)
160
- const filesUrl = `${baseUrl}/${integrationId}/files.json`
161
- const response = await fetch(filesUrl)
162
-
163
- if (!response.ok) {
164
- // No files.json, return empty
165
- return {}
166
- }
167
-
168
- const fileList: Array<string> = await response.json()
169
- const files: Record<string, string> = {}
170
-
171
- // Fetch each file
172
- await Promise.all(
173
- fileList.map(async (filePath) => {
174
- const fileUrl = `${baseUrl}/${integrationId}/assets/${filePath}`
175
- const fileResponse = await fetch(fileUrl)
176
-
177
- if (fileResponse.ok) {
178
- if (isBinaryFile(filePath)) {
179
- // Fetch binary files as arrayBuffer and convert to base64
180
- const buffer = await fileResponse.arrayBuffer()
181
- files[filePath] = BINARY_PREFIX + Buffer.from(buffer).toString('base64')
182
- } else {
183
- files[filePath] = await fileResponse.text()
184
- }
185
- }
186
- }),
187
- )
188
-
189
- return files
190
- },
191
- CACHE_TTL_MS,
192
- )
193
- }
194
-
195
- /**
196
- * Fetch integration package.json if it exists
197
- */
198
- async function fetchIntegrationPackageJson(
199
- integrationId: string,
200
- baseUrl: string,
201
- ): Promise<{
202
- dependencies?: Record<string, string>
203
- devDependencies?: Record<string, string>
204
- scripts?: Record<string, string>
205
- } | null> {
206
- if (isLocalPath(baseUrl)) {
207
- const pkgPath = join(baseUrl, integrationId, 'package.json')
208
- if (existsSync(pkgPath)) {
209
- return JSON.parse(readFileSync(pkgPath, 'utf-8'))
210
- }
211
- return null
212
- }
213
-
214
- const url = `${baseUrl}/${integrationId}/package.json`
215
- const response = await fetch(url)
216
-
217
- if (!response.ok) {
218
- return null
219
- }
220
-
221
- return response.json()
222
- }
223
-
224
- /**
225
- * Fetch a complete compiled integration from GitHub
226
- */
227
- export async function fetchIntegration(
228
- integrationId: string,
229
- baseUrl: string = GITHUB_RAW_BASE,
230
- ): Promise<IntegrationCompiled> {
231
- const [info, files, pkgJson] = await Promise.all([
232
- fetchIntegrationInfo(integrationId, baseUrl),
233
- fetchIntegrationFiles(integrationId, baseUrl),
234
- fetchIntegrationPackageJson(integrationId, baseUrl),
235
- ])
236
-
237
- // Merge package.json into packageAdditions if present
238
- const packageAdditions = info.packageAdditions ?? {}
239
- if (pkgJson) {
240
- if (pkgJson.dependencies) {
241
- packageAdditions.dependencies = {
242
- ...packageAdditions.dependencies,
243
- ...pkgJson.dependencies,
244
- }
245
- }
246
- if (pkgJson.devDependencies) {
247
- packageAdditions.devDependencies = {
248
- ...packageAdditions.devDependencies,
249
- ...pkgJson.devDependencies,
250
- }
251
- }
252
- if (pkgJson.scripts) {
253
- packageAdditions.scripts = {
254
- ...packageAdditions.scripts,
255
- ...pkgJson.scripts,
256
- }
257
- }
258
- }
259
-
260
- return IntegrationCompiledSchema.parse({
261
- ...info,
262
- id: integrationId,
263
- files,
264
- packageAdditions:
265
- Object.keys(packageAdditions).length > 0 ? packageAdditions : undefined,
266
- deletedFiles: [],
267
- })
268
- }
269
-
270
- /**
271
- * Fetch multiple integrations in parallel
272
- */
273
- export async function fetchIntegrations(
274
- integrationIds: Array<string>,
275
- baseUrl: string = GITHUB_RAW_BASE,
276
- ): Promise<Array<IntegrationCompiled>> {
277
- return Promise.all(integrationIds.map((id) => fetchIntegration(id, baseUrl)))
278
- }
@@ -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
- }