@tanstack/cli 0.0.6 → 0.0.8

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.
package/src/api/fetch.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
2
- import { join } from 'node:path'
2
+ import { extname, join } from 'node:path'
3
3
  import {
4
4
  IntegrationCompiledSchema,
5
5
  IntegrationInfoSchema,
@@ -14,6 +14,24 @@ const GITHUB_RAW_BASE =
14
14
  // 1 hour cache TTL for remote fetches
15
15
  const CACHE_TTL_MS = 60 * 60 * 1000
16
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
+
17
35
  /**
18
36
  * Check if a path is a local directory
19
37
  */
@@ -92,6 +110,7 @@ export async function fetchIntegrationInfo(
92
110
 
93
111
  /**
94
112
  * Recursively read all files from a directory
113
+ * Binary files are read as base64 with a prefix marker
95
114
  */
96
115
  function readDirRecursive(
97
116
  dir: string,
@@ -108,6 +127,10 @@ function readDirRecursive(
108
127
 
109
128
  if (stat.isDirectory()) {
110
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')
111
134
  } else {
112
135
  files[relativePath] = readFileSync(fullPath, 'utf-8')
113
136
  }
@@ -152,7 +175,13 @@ export async function fetchIntegrationFiles(
152
175
  const fileResponse = await fetch(fileUrl)
153
176
 
154
177
  if (fileResponse.ok) {
155
- files[filePath] = await fileResponse.text()
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
+ }
156
185
  }
157
186
  }),
158
187
  )
@@ -14,7 +14,7 @@ import {
14
14
  text,
15
15
  } from '@clack/prompts'
16
16
  import chalk from 'chalk'
17
- import { fetchIntegrations, fetchManifest } from '../api/fetch.js'
17
+ import { BINARY_PREFIX, fetchIntegrations, fetchManifest } from '../api/fetch.js'
18
18
  import { compile } from '../engine/compile.js'
19
19
  import { writeConfigFile } from '../engine/config-file.js'
20
20
  import { loadTemplate } from '../engine/custom-addons/template.js'
@@ -383,7 +383,14 @@ export async function runCreate(
383
383
  const fullPath = resolve(targetDir, filePath)
384
384
  const dir = resolve(fullPath, '..')
385
385
  mkdirSync(dir, { recursive: true })
386
- writeFileSync(fullPath, content, 'utf-8')
386
+
387
+ // Handle binary files (base64 encoded with prefix)
388
+ if (content.startsWith(BINARY_PREFIX)) {
389
+ const base64Data = content.slice(BINARY_PREFIX.length)
390
+ writeFileSync(fullPath, Buffer.from(base64Data, 'base64'))
391
+ } else {
392
+ writeFileSync(fullPath, content, 'utf-8')
393
+ }
387
394
  }
388
395
 
389
396
  // Write config file for integration/template creation
@@ -94,6 +94,7 @@ function createServer() {
94
94
  try {
95
95
  const { mkdirSync, writeFileSync } = await import('node:fs')
96
96
  const { resolve } = await import('node:path')
97
+ const { BINARY_PREFIX } = await import('../api/fetch.js')
97
98
  const { execSync } = await import('node:child_process')
98
99
 
99
100
  // Fetch integration definitions if needed
@@ -121,7 +122,14 @@ function createServer() {
121
122
  const fullPath = resolve(targetDir, filePath)
122
123
  const dir = resolve(fullPath, '..')
123
124
  mkdirSync(dir, { recursive: true })
124
- writeFileSync(fullPath, content, 'utf-8')
125
+
126
+ // Handle binary files (base64 encoded with prefix)
127
+ if (content.startsWith(BINARY_PREFIX)) {
128
+ const base64Data = content.slice(BINARY_PREFIX.length)
129
+ writeFileSync(fullPath, Buffer.from(base64Data, 'base64'))
130
+ } else {
131
+ writeFileSync(fullPath, content, 'utf-8')
132
+ }
125
133
  }
126
134
 
127
135
  // Initialize git
@@ -129,6 +129,7 @@ function buildPackageJson(
129
129
  dependencies: {
130
130
  '@tanstack/react-router': '^1.132.0',
131
131
  '@tanstack/react-router-devtools': '^1.132.0',
132
+ '@tanstack/react-devtools': '^0.9.2',
132
133
  '@tanstack/react-start': '^1.132.0',
133
134
  react: '^19.2.0',
134
135
  'react-dom': '^19.2.0',
@@ -246,6 +247,23 @@ export function compile(options: CompileOptions): CompileOutput {
246
247
  return true
247
248
  })
248
249
 
250
+ // Generate .env.example with integration env vars
251
+ if (uniqueEnvVars.length > 0) {
252
+ const envLines: Array<string> = [
253
+ '# Environment Variables',
254
+ '# Copy this file to .env.local and fill in your values',
255
+ '',
256
+ ]
257
+
258
+ for (const v of uniqueEnvVars) {
259
+ envLines.push(`# ${v.description}${v.required ? ' (required)' : ''}`)
260
+ envLines.push(`${v.name}=${v.example || ''}`)
261
+ envLines.push('')
262
+ }
263
+
264
+ outputFiles['.env.example'] = envLines.join('\n')
265
+ }
266
+
249
267
  return {
250
268
  files: outputFiles,
251
269
  packages,
@@ -163,11 +163,9 @@ export function generateRootRoute(
163
163
  const lines: Array<{ text: string; integrationId: string }> = []
164
164
  const hasHeader = options.chosenIntegrations.length > 0
165
165
 
166
- if (devtoolsPlugins.length > 0) {
167
- lines.push({ text: `import React from 'react'`, integrationId: 'base' })
168
- }
169
166
  lines.push({ text: `import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router'`, integrationId: 'base' })
170
- lines.push({ text: `import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'`, integrationId: 'base' })
167
+ lines.push({ text: `import { TanStackDevtools } from '@tanstack/react-devtools'`, integrationId: 'base' })
168
+ lines.push({ text: `import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'`, integrationId: 'base' })
171
169
 
172
170
  lines.push({ text: `import appCss from '../styles.css?url'`, integrationId: 'base' })
173
171
  if (hasHeader) {
@@ -194,16 +192,6 @@ export function generateRootRoute(
194
192
  lines.push({ text: `import { ${devtools.jsName} } from '${importPath}'`, integrationId: devtools.integrationId })
195
193
  }
196
194
 
197
- // Devtools array
198
- if (devtoolsPlugins.length > 0) {
199
- lines.push({ text: '', integrationId: 'base' })
200
- lines.push({ text: `const devtoolsPlugins = [`, integrationId: 'base' })
201
- for (const devtools of devtoolsPlugins) {
202
- lines.push({ text: ` ${devtools.jsName},`, integrationId: devtools.integrationId })
203
- }
204
- lines.push({ text: `]`, integrationId: 'base' })
205
- }
206
-
207
195
  lines.push({ text: '', integrationId: 'base' })
208
196
  lines.push({ text: `export const Route = createRootRoute({`, integrationId: 'base' })
209
197
  lines.push({ text: ` head: () => ({`, integrationId: 'base' })
@@ -237,13 +225,17 @@ export function generateRootRoute(
237
225
  lines.push({ text: `${currentIndent}<Header />`, integrationId: 'base' })
238
226
  }
239
227
  lines.push({ text: `${currentIndent}{children}`, integrationId: 'base' })
240
- lines.push({ text: `${currentIndent}<TanStackRouterDevtools />`, integrationId: 'base' })
241
228
 
242
- if (devtoolsPlugins.length > 0) {
243
- lines.push({ text: `${currentIndent}{devtoolsPlugins.map((plugin, i) => (`, integrationId: 'base' })
244
- lines.push({ text: `${currentIndent} <React.Fragment key={i}>{plugin.render}</React.Fragment>`, integrationId: 'base' })
245
- lines.push({ text: `${currentIndent}))}`, integrationId: 'base' })
229
+ // TanStack unified devtools with plugins
230
+ lines.push({ text: `${currentIndent}<TanStackDevtools`, integrationId: 'base' })
231
+ lines.push({ text: `${currentIndent} config={{ position: 'bottom-right' }}`, integrationId: 'base' })
232
+ lines.push({ text: `${currentIndent} plugins={[`, integrationId: 'base' })
233
+ lines.push({ text: `${currentIndent} { name: 'TanStack Router', render: <TanStackRouterDevtoolsPanel /> },`, integrationId: 'base' })
234
+ for (const devtools of devtoolsPlugins) {
235
+ lines.push({ text: `${currentIndent} ${devtools.jsName},`, integrationId: devtools.integrationId })
246
236
  }
237
+ lines.push({ text: `${currentIndent} ]}`, integrationId: 'base' })
238
+ lines.push({ text: `${currentIndent}/>`, integrationId: 'base' })
247
239
 
248
240
  for (const provider of [...rootProviders].reverse()) {
249
241
  currentIndent = currentIndent.slice(0, -2)