@tanstack/cli 0.61.1 → 0.62.0
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/package.json +5 -1
- package/skills/CHANGELOG.md +18 -0
- package/skills/add-addons-existing-app/SKILL.md +113 -0
- package/skills/choose-ecosystem-integrations/SKILL.md +140 -0
- package/skills/choose-ecosystem-integrations/references/authentication-providers.md +19 -0
- package/skills/choose-ecosystem-integrations/references/data-layer-providers.md +20 -0
- package/skills/choose-ecosystem-integrations/references/deployment-targets.md +19 -0
- package/skills/create-app-scaffold/SKILL.md +132 -0
- package/skills/create-app-scaffold/references/create-flag-compatibility-matrix.md +34 -0
- package/skills/create-app-scaffold/references/deployment-providers.md +19 -0
- package/skills/create-app-scaffold/references/framework-adapters.md +17 -0
- package/skills/create-app-scaffold/references/toolchains.md +17 -0
- package/skills/maintain-custom-addons-dev-watch/SKILL.md +118 -0
- package/skills/query-docs-library-metadata/SKILL.md +85 -0
- package/skills/query-docs-library-metadata/references/discovery-command-output-schemas.md +70 -0
- package/CHANGELOG.md +0 -815
- package/playwright-report/index.html +0 -85
- package/playwright.config.ts +0 -21
- package/src/bin.ts +0 -15
- package/src/cli.ts +0 -1099
- package/src/command-line.ts +0 -612
- package/src/dev-watch.ts +0 -564
- package/src/discovery.ts +0 -209
- package/src/file-syncer.ts +0 -263
- package/src/index.ts +0 -21
- package/src/options.ts +0 -280
- package/src/types.ts +0 -27
- package/src/ui-environment.ts +0 -74
- package/src/ui-prompts.ts +0 -387
- package/src/utils.ts +0 -30
- package/test-results/.last-run.json +0 -4
- package/tests/command-line.test.ts +0 -703
- package/tests/index.test.ts +0 -9
- package/tests/options.test.ts +0 -281
- package/tests/setupVitest.ts +0 -6
- package/tests/ui-environment.test.ts +0 -97
- package/tests/ui-prompts.test.ts +0 -233
- package/tests-e2e/addons-smoke.spec.ts +0 -31
- package/tests-e2e/create-smoke.spec.ts +0 -39
- package/tests-e2e/helpers.ts +0 -526
- package/tests-e2e/matrix-opportunistic.spec.ts +0 -142
- package/tests-e2e/router-only-smoke.spec.ts +0 -54
- package/tests-e2e/solid-smoke.spec.ts +0 -26
- package/tests-e2e/templates-smoke.spec.ts +0 -52
- package/tsconfig.json +0 -17
- package/vitest.config.js +0 -8
package/src/cli.ts
DELETED
|
@@ -1,1099 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs'
|
|
2
|
-
import { resolve } from 'node:path'
|
|
3
|
-
import { Command, InvalidArgumentError } from 'commander'
|
|
4
|
-
import { cancel, confirm, intro, isCancel, log } from '@clack/prompts'
|
|
5
|
-
import chalk from 'chalk'
|
|
6
|
-
import semver from 'semver'
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
SUPPORTED_PACKAGE_MANAGERS,
|
|
10
|
-
addToApp,
|
|
11
|
-
compileAddOn,
|
|
12
|
-
compileStarter,
|
|
13
|
-
createApp,
|
|
14
|
-
devAddOn,
|
|
15
|
-
getAllAddOns,
|
|
16
|
-
getFrameworkByName,
|
|
17
|
-
getFrameworks,
|
|
18
|
-
initAddOn,
|
|
19
|
-
initStarter,
|
|
20
|
-
} from '@tanstack/create'
|
|
21
|
-
import {
|
|
22
|
-
LIBRARY_GROUPS,
|
|
23
|
-
fetchDocContent,
|
|
24
|
-
fetchLibraries,
|
|
25
|
-
fetchPartners,
|
|
26
|
-
searchTanStackDocs,
|
|
27
|
-
} from './discovery.js'
|
|
28
|
-
|
|
29
|
-
import { promptForAddOns, promptForCreateOptions } from './options.js'
|
|
30
|
-
import {
|
|
31
|
-
normalizeOptions,
|
|
32
|
-
validateDevWatchOptions,
|
|
33
|
-
validateLegacyCreateFlags,
|
|
34
|
-
} from './command-line.js'
|
|
35
|
-
|
|
36
|
-
import { createUIEnvironment } from './ui-environment.js'
|
|
37
|
-
import { DevWatchManager } from './dev-watch.js'
|
|
38
|
-
|
|
39
|
-
import type { CliOptions } from './types.js'
|
|
40
|
-
import type {
|
|
41
|
-
FrameworkDefinition,
|
|
42
|
-
Options,
|
|
43
|
-
PackageManager,
|
|
44
|
-
} from '@tanstack/create'
|
|
45
|
-
|
|
46
|
-
// Read version from package.json
|
|
47
|
-
const packageJsonPath = new URL('../package.json', import.meta.url)
|
|
48
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
|
49
|
-
const VERSION = packageJson.version
|
|
50
|
-
|
|
51
|
-
export function cli({
|
|
52
|
-
name,
|
|
53
|
-
appName,
|
|
54
|
-
forcedAddOns = [],
|
|
55
|
-
forcedDeployment,
|
|
56
|
-
defaultFramework,
|
|
57
|
-
frameworkDefinitionInitializers,
|
|
58
|
-
showDeploymentOptions = false,
|
|
59
|
-
legacyAutoCreate = false,
|
|
60
|
-
defaultRouterOnly = false,
|
|
61
|
-
}: {
|
|
62
|
-
name: string
|
|
63
|
-
appName: string
|
|
64
|
-
forcedAddOns?: Array<string>
|
|
65
|
-
forcedDeployment?: string
|
|
66
|
-
defaultFramework?: string
|
|
67
|
-
frameworkDefinitionInitializers?: Array<() => FrameworkDefinition>
|
|
68
|
-
showDeploymentOptions?: boolean
|
|
69
|
-
legacyAutoCreate?: boolean
|
|
70
|
-
defaultRouterOnly?: boolean
|
|
71
|
-
}) {
|
|
72
|
-
const environment = createUIEnvironment(appName, false)
|
|
73
|
-
|
|
74
|
-
const program = new Command()
|
|
75
|
-
|
|
76
|
-
async function confirmTargetDirectorySafety(
|
|
77
|
-
targetDir: string,
|
|
78
|
-
forced?: boolean,
|
|
79
|
-
) {
|
|
80
|
-
if (forced) {
|
|
81
|
-
return
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (!fs.existsSync(targetDir)) {
|
|
85
|
-
return
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (!fs.statSync(targetDir).isDirectory()) {
|
|
89
|
-
throw new Error(`Target path exists and is not a directory: ${targetDir}`)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (fs.readdirSync(targetDir).length === 0) {
|
|
93
|
-
return
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const shouldContinue = await confirm({
|
|
97
|
-
message: `Target directory "${targetDir}" already exists and is not empty. Continue anyway?`,
|
|
98
|
-
initialValue: false,
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
if (isCancel(shouldContinue) || !shouldContinue) {
|
|
102
|
-
cancel('Operation cancelled.')
|
|
103
|
-
process.exit(0)
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const availableFrameworks = getFrameworks().map((f) => f.name)
|
|
108
|
-
|
|
109
|
-
function resolveBuiltInDevWatchPath(frameworkId: string): string {
|
|
110
|
-
const candidates = [
|
|
111
|
-
resolve(process.cwd(), 'packages/create/src/frameworks', frameworkId),
|
|
112
|
-
resolve(process.cwd(), '../create/src/frameworks', frameworkId),
|
|
113
|
-
]
|
|
114
|
-
|
|
115
|
-
for (const candidate of candidates) {
|
|
116
|
-
if (fs.existsSync(candidate)) {
|
|
117
|
-
return candidate
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return candidates[0]
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async function startDevWatchMode(projectName: string, options: CliOptions) {
|
|
125
|
-
// Validate dev watch options
|
|
126
|
-
const validation = validateDevWatchOptions({ ...options, projectName })
|
|
127
|
-
if (!validation.valid) {
|
|
128
|
-
console.error(validation.error)
|
|
129
|
-
process.exit(1)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Enter dev watch mode
|
|
133
|
-
if (!projectName && !options.targetDir) {
|
|
134
|
-
console.error('Project name/target directory is required for dev watch mode')
|
|
135
|
-
process.exit(1)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (!options.framework) {
|
|
139
|
-
console.error('Failed to detect framework')
|
|
140
|
-
process.exit(1)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const framework = getFrameworkByName(options.framework)
|
|
144
|
-
if (!framework) {
|
|
145
|
-
console.error('Failed to detect framework')
|
|
146
|
-
process.exit(1)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// First, create the app normally using the standard flow
|
|
150
|
-
const normalizedOpts = await normalizeOptions(
|
|
151
|
-
{
|
|
152
|
-
...options,
|
|
153
|
-
projectName,
|
|
154
|
-
framework: framework.id,
|
|
155
|
-
},
|
|
156
|
-
forcedAddOns,
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
if (!normalizedOpts) {
|
|
160
|
-
throw new Error('Failed to normalize options')
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
normalizedOpts.targetDir =
|
|
164
|
-
options.targetDir || resolve(process.cwd(), projectName)
|
|
165
|
-
|
|
166
|
-
// Create the initial app with minimal output for dev watch mode
|
|
167
|
-
console.log(chalk.bold('\ndev-watch'))
|
|
168
|
-
console.log(chalk.gray('├─') + ' ' + `creating initial ${appName} app...`)
|
|
169
|
-
if (normalizedOpts.install !== false) {
|
|
170
|
-
console.log(
|
|
171
|
-
chalk.gray('├─') + ' ' + chalk.yellow('⟳') + ' installing packages...',
|
|
172
|
-
)
|
|
173
|
-
}
|
|
174
|
-
const silentEnvironment = createUIEnvironment(appName, true)
|
|
175
|
-
await confirmTargetDirectorySafety(normalizedOpts.targetDir, options.force)
|
|
176
|
-
await createApp(silentEnvironment, normalizedOpts)
|
|
177
|
-
console.log(chalk.gray('└─') + ' ' + chalk.green('✓') + ` app created`)
|
|
178
|
-
|
|
179
|
-
// Now start the dev watch mode
|
|
180
|
-
const manager = new DevWatchManager({
|
|
181
|
-
watchPath: options.devWatch!,
|
|
182
|
-
targetDir: normalizedOpts.targetDir,
|
|
183
|
-
framework,
|
|
184
|
-
cliOptions: normalizedOpts,
|
|
185
|
-
packageManager: normalizedOpts.packageManager,
|
|
186
|
-
runDevCommand: options.runDev,
|
|
187
|
-
environment,
|
|
188
|
-
frameworkDefinitionInitializers,
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
await manager.start()
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const toolchains = new Set<string>()
|
|
195
|
-
for (const framework of getFrameworks()) {
|
|
196
|
-
for (const addOn of framework.getAddOns()) {
|
|
197
|
-
if (addOn.type === 'toolchain') {
|
|
198
|
-
toolchains.add(addOn.id)
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const deployments = new Set<string>()
|
|
204
|
-
for (const framework of getFrameworks()) {
|
|
205
|
-
for (const addOn of framework.getAddOns()) {
|
|
206
|
-
if (addOn.type === 'deployment') {
|
|
207
|
-
deployments.add(addOn.id)
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Mode is always file-router (TanStack Start)
|
|
213
|
-
const defaultMode = 'file-router'
|
|
214
|
-
const categoryAliases: Record<string, string> = {
|
|
215
|
-
db: 'database',
|
|
216
|
-
postgres: 'database',
|
|
217
|
-
sql: 'database',
|
|
218
|
-
login: 'auth',
|
|
219
|
-
authentication: 'auth',
|
|
220
|
-
hosting: 'deployment',
|
|
221
|
-
deploy: 'deployment',
|
|
222
|
-
serverless: 'deployment',
|
|
223
|
-
errors: 'monitoring',
|
|
224
|
-
logging: 'monitoring',
|
|
225
|
-
content: 'cms',
|
|
226
|
-
'api-keys': 'api',
|
|
227
|
-
grid: 'data-grid',
|
|
228
|
-
review: 'code-review',
|
|
229
|
-
courses: 'learning',
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function printJson(data: unknown) {
|
|
233
|
-
console.log(JSON.stringify(data, null, 2))
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function parsePositiveInteger(value: string) {
|
|
237
|
-
const parsed = Number(value)
|
|
238
|
-
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
239
|
-
throw new InvalidArgumentError('Value must be a positive integer')
|
|
240
|
-
}
|
|
241
|
-
return parsed
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
program
|
|
245
|
-
.name(name)
|
|
246
|
-
.description(`${appName} CLI`)
|
|
247
|
-
.version(VERSION, '-v, --version', 'output the current version')
|
|
248
|
-
|
|
249
|
-
// Helper to create the create command action handler
|
|
250
|
-
async function handleCreate(projectName: string, options: CliOptions) {
|
|
251
|
-
const legacyCreateFlags = validateLegacyCreateFlags(options)
|
|
252
|
-
if (legacyCreateFlags.error) {
|
|
253
|
-
log.error(legacyCreateFlags.error)
|
|
254
|
-
process.exit(1)
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
for (const warning of legacyCreateFlags.warnings) {
|
|
258
|
-
log.warn(warning)
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
if (options.listAddOns) {
|
|
262
|
-
const addOns = await getAllAddOns(
|
|
263
|
-
getFrameworkByName(options.framework || defaultFramework || 'React')!,
|
|
264
|
-
defaultMode,
|
|
265
|
-
)
|
|
266
|
-
const visibleAddOns = addOns.filter((a) => !forcedAddOns.includes(a.id))
|
|
267
|
-
if (options.json) {
|
|
268
|
-
printJson(
|
|
269
|
-
visibleAddOns.map((addOn) => ({
|
|
270
|
-
id: addOn.id,
|
|
271
|
-
name: addOn.name,
|
|
272
|
-
description: addOn.description,
|
|
273
|
-
type: addOn.type,
|
|
274
|
-
category: addOn.category,
|
|
275
|
-
phase: addOn.phase,
|
|
276
|
-
modes: addOn.modes,
|
|
277
|
-
link: addOn.link,
|
|
278
|
-
warning: addOn.warning,
|
|
279
|
-
exclusive: addOn.exclusive,
|
|
280
|
-
dependsOn: addOn.dependsOn,
|
|
281
|
-
options: addOn.options,
|
|
282
|
-
})),
|
|
283
|
-
)
|
|
284
|
-
return
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
let hasConfigurableAddOns = false
|
|
288
|
-
for (const addOn of visibleAddOns) {
|
|
289
|
-
const hasOptions =
|
|
290
|
-
addOn.options && Object.keys(addOn.options).length > 0
|
|
291
|
-
const optionMarker = hasOptions ? '*' : ' '
|
|
292
|
-
if (hasOptions) hasConfigurableAddOns = true
|
|
293
|
-
console.log(
|
|
294
|
-
`${optionMarker} ${chalk.bold(addOn.id)}: ${addOn.description}`,
|
|
295
|
-
)
|
|
296
|
-
}
|
|
297
|
-
if (hasConfigurableAddOns) {
|
|
298
|
-
console.log('\n* = has configuration options')
|
|
299
|
-
}
|
|
300
|
-
return
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (options.addonDetails) {
|
|
304
|
-
const addOns = await getAllAddOns(
|
|
305
|
-
getFrameworkByName(options.framework || defaultFramework || 'React')!,
|
|
306
|
-
defaultMode,
|
|
307
|
-
)
|
|
308
|
-
const addOn =
|
|
309
|
-
addOns.find((a) => a.id === options.addonDetails) ??
|
|
310
|
-
addOns.find(
|
|
311
|
-
(a) =>
|
|
312
|
-
a.id.toLowerCase() === options.addonDetails!.toLowerCase(),
|
|
313
|
-
)
|
|
314
|
-
if (!addOn) {
|
|
315
|
-
console.error(`Add-on '${options.addonDetails}' not found`)
|
|
316
|
-
process.exit(1)
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
if (options.json) {
|
|
320
|
-
const files = await addOn.getFiles()
|
|
321
|
-
printJson({
|
|
322
|
-
id: addOn.id,
|
|
323
|
-
name: addOn.name,
|
|
324
|
-
description: addOn.description,
|
|
325
|
-
type: addOn.type,
|
|
326
|
-
category: addOn.category,
|
|
327
|
-
phase: addOn.phase,
|
|
328
|
-
modes: addOn.modes,
|
|
329
|
-
link: addOn.link,
|
|
330
|
-
warning: addOn.warning,
|
|
331
|
-
exclusive: addOn.exclusive,
|
|
332
|
-
dependsOn: addOn.dependsOn,
|
|
333
|
-
options: addOn.options,
|
|
334
|
-
routes: addOn.routes,
|
|
335
|
-
packageAdditions: addOn.packageAdditions,
|
|
336
|
-
shadcnComponents: addOn.shadcnComponents,
|
|
337
|
-
integrations: addOn.integrations,
|
|
338
|
-
readme: addOn.readme,
|
|
339
|
-
files,
|
|
340
|
-
author: addOn.author,
|
|
341
|
-
version: addOn.version,
|
|
342
|
-
license: addOn.license,
|
|
343
|
-
})
|
|
344
|
-
return
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
console.log(
|
|
348
|
-
`${chalk.bold.cyan('Add-on Details:')} ${chalk.bold(addOn.name)}`,
|
|
349
|
-
)
|
|
350
|
-
console.log(`${chalk.bold('ID:')} ${addOn.id}`)
|
|
351
|
-
console.log(`${chalk.bold('Description:')} ${addOn.description}`)
|
|
352
|
-
console.log(`${chalk.bold('Type:')} ${addOn.type}`)
|
|
353
|
-
console.log(`${chalk.bold('Phase:')} ${addOn.phase}`)
|
|
354
|
-
console.log(`${chalk.bold('Supported Modes:')} ${addOn.modes.join(', ')}`)
|
|
355
|
-
|
|
356
|
-
if (addOn.link) {
|
|
357
|
-
console.log(`${chalk.bold('Link:')} ${chalk.blue(addOn.link)}`)
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
if (addOn.dependsOn && addOn.dependsOn.length > 0) {
|
|
361
|
-
console.log(
|
|
362
|
-
`${chalk.bold('Dependencies:')} ${addOn.dependsOn.join(', ')}`,
|
|
363
|
-
)
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
if (addOn.options && Object.keys(addOn.options).length > 0) {
|
|
367
|
-
console.log(`\n${chalk.bold.yellow('Configuration Options:')}`)
|
|
368
|
-
for (const [optionName, option] of Object.entries(addOn.options)) {
|
|
369
|
-
if ('type' in option) {
|
|
370
|
-
const opt = option as any
|
|
371
|
-
console.log(` ${chalk.bold(optionName)}:`)
|
|
372
|
-
console.log(` Label: ${opt.label}`)
|
|
373
|
-
if (opt.description) {
|
|
374
|
-
console.log(` Description: ${opt.description}`)
|
|
375
|
-
}
|
|
376
|
-
console.log(` Type: ${opt.type}`)
|
|
377
|
-
console.log(` Default: ${opt.default}`)
|
|
378
|
-
if (opt.type === 'select' && opt.options) {
|
|
379
|
-
console.log(` Available values:`)
|
|
380
|
-
for (const choice of opt.options) {
|
|
381
|
-
console.log(` - ${choice.value}: ${choice.label}`)
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
} else {
|
|
387
|
-
console.log(`\n${chalk.gray('No configuration options available')}`)
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
if (addOn.routes && addOn.routes.length > 0) {
|
|
391
|
-
console.log(`\n${chalk.bold.green('Routes:')}`)
|
|
392
|
-
for (const route of addOn.routes) {
|
|
393
|
-
console.log(` ${chalk.bold(route.url)} (${route.name})`)
|
|
394
|
-
console.log(` File: ${route.path}`)
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
return
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
if (options.devWatch) {
|
|
401
|
-
await startDevWatchMode(projectName, options)
|
|
402
|
-
return
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
try {
|
|
406
|
-
const cliOptions = {
|
|
407
|
-
projectName,
|
|
408
|
-
...options,
|
|
409
|
-
} as CliOptions
|
|
410
|
-
|
|
411
|
-
if (defaultRouterOnly && cliOptions.routerOnly === undefined) {
|
|
412
|
-
cliOptions.routerOnly = true
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
if (
|
|
416
|
-
cliOptions.routerOnly !== true &&
|
|
417
|
-
cliOptions.template &&
|
|
418
|
-
['file-router', 'typescript', 'tsx', 'javascript', 'js', 'jsx'].includes(
|
|
419
|
-
cliOptions.template.toLowerCase(),
|
|
420
|
-
) &&
|
|
421
|
-
cliOptions.template.toLowerCase() !== 'file-router'
|
|
422
|
-
) {
|
|
423
|
-
cliOptions.routerOnly = true
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
cliOptions.framework = getFrameworkByName(
|
|
427
|
-
options.framework || defaultFramework || 'React',
|
|
428
|
-
)!.id
|
|
429
|
-
|
|
430
|
-
let finalOptions: Options | undefined
|
|
431
|
-
if (cliOptions.interactive || cliOptions.addOns === true) {
|
|
432
|
-
cliOptions.addOns = true
|
|
433
|
-
} else {
|
|
434
|
-
finalOptions = await normalizeOptions(
|
|
435
|
-
cliOptions,
|
|
436
|
-
forcedAddOns,
|
|
437
|
-
{ forcedDeployment },
|
|
438
|
-
)
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
if (finalOptions) {
|
|
442
|
-
intro(`Creating a new ${appName} app in ${projectName}...`)
|
|
443
|
-
} else {
|
|
444
|
-
intro(`Let's configure your ${appName} application`)
|
|
445
|
-
finalOptions = await promptForCreateOptions(cliOptions, {
|
|
446
|
-
forcedAddOns,
|
|
447
|
-
showDeploymentOptions,
|
|
448
|
-
})
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
if (!finalOptions) {
|
|
452
|
-
throw new Error('No options were provided')
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
;(finalOptions as Options & { routerOnly?: boolean }).routerOnly =
|
|
456
|
-
!!cliOptions.routerOnly
|
|
457
|
-
|
|
458
|
-
// Determine target directory:
|
|
459
|
-
// 1. Use --target-dir if provided
|
|
460
|
-
// 2. Use targetDir from normalizeOptions if set (handles "." case)
|
|
461
|
-
// 3. If original projectName was ".", use current directory
|
|
462
|
-
// 4. Otherwise, use project name as subdirectory
|
|
463
|
-
if (options.targetDir) {
|
|
464
|
-
finalOptions.targetDir = options.targetDir
|
|
465
|
-
} else if (finalOptions.targetDir) {
|
|
466
|
-
// Keep the targetDir from normalizeOptions (handles "." case)
|
|
467
|
-
} else if (projectName === '.') {
|
|
468
|
-
finalOptions.targetDir = resolve(process.cwd())
|
|
469
|
-
} else {
|
|
470
|
-
finalOptions.targetDir = resolve(process.cwd(), finalOptions.projectName)
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
await confirmTargetDirectorySafety(finalOptions.targetDir, options.force)
|
|
474
|
-
await createApp(environment, finalOptions)
|
|
475
|
-
} catch (error) {
|
|
476
|
-
log.error(
|
|
477
|
-
error instanceof Error ? error.message : 'An unknown error occurred',
|
|
478
|
-
)
|
|
479
|
-
process.exit(1)
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// Helper to configure create command options
|
|
484
|
-
function configureCreateCommand(cmd: Command) {
|
|
485
|
-
cmd.argument('[project-name]', 'name of the project')
|
|
486
|
-
|
|
487
|
-
if (!defaultFramework) {
|
|
488
|
-
cmd.option<string>(
|
|
489
|
-
'--framework <type>',
|
|
490
|
-
`project framework (${availableFrameworks.join(', ')})`,
|
|
491
|
-
(value) => {
|
|
492
|
-
if (value.toLowerCase() === 'react-cra') {
|
|
493
|
-
return 'react'
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
if (
|
|
497
|
-
!availableFrameworks.some(
|
|
498
|
-
(f) => f.toLowerCase() === value.toLowerCase(),
|
|
499
|
-
)
|
|
500
|
-
) {
|
|
501
|
-
throw new InvalidArgumentError(
|
|
502
|
-
`Invalid framework: ${value}. Only the following are allowed: ${availableFrameworks.join(', ')}`,
|
|
503
|
-
)
|
|
504
|
-
}
|
|
505
|
-
return value
|
|
506
|
-
},
|
|
507
|
-
defaultFramework || 'React',
|
|
508
|
-
)
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
cmd
|
|
512
|
-
.option(
|
|
513
|
-
'--starter [url-or-id]',
|
|
514
|
-
'DEPRECATED: use --template. Initializes from a template URL or built-in id',
|
|
515
|
-
false,
|
|
516
|
-
)
|
|
517
|
-
.option('--template-id <id>', 'initialize using a built-in template id')
|
|
518
|
-
.option(
|
|
519
|
-
'--template [url-or-id]',
|
|
520
|
-
'initialize this project from a template URL or built-in template id',
|
|
521
|
-
)
|
|
522
|
-
.option('--no-install', 'skip installing dependencies')
|
|
523
|
-
.option<PackageManager>(
|
|
524
|
-
`--package-manager <${SUPPORTED_PACKAGE_MANAGERS.join('|')}>`,
|
|
525
|
-
`Explicitly tell the CLI to use this package manager`,
|
|
526
|
-
(value) => {
|
|
527
|
-
if (!SUPPORTED_PACKAGE_MANAGERS.includes(value as PackageManager)) {
|
|
528
|
-
throw new InvalidArgumentError(
|
|
529
|
-
`Invalid package manager: ${value}. The following are allowed: ${SUPPORTED_PACKAGE_MANAGERS.join(
|
|
530
|
-
', ',
|
|
531
|
-
)}`,
|
|
532
|
-
)
|
|
533
|
-
}
|
|
534
|
-
return value as PackageManager
|
|
535
|
-
},
|
|
536
|
-
)
|
|
537
|
-
.option(
|
|
538
|
-
'--dev-watch <path>',
|
|
539
|
-
'Watch a framework directory for changes and auto-rebuild',
|
|
540
|
-
)
|
|
541
|
-
.option('--run-dev', 'Run the app dev server alongside dev-watch', false)
|
|
542
|
-
.option(
|
|
543
|
-
'--router-only',
|
|
544
|
-
'Use router-only compatibility mode (file-based routing without TanStack Start)',
|
|
545
|
-
)
|
|
546
|
-
.option(
|
|
547
|
-
'--tailwind',
|
|
548
|
-
'Deprecated: compatibility flag; Tailwind is always enabled',
|
|
549
|
-
)
|
|
550
|
-
.option(
|
|
551
|
-
'--no-tailwind',
|
|
552
|
-
'Deprecated: compatibility flag; Tailwind opt-out is ignored',
|
|
553
|
-
)
|
|
554
|
-
.option('--examples', 'include demo/example pages')
|
|
555
|
-
.option('--no-examples', 'exclude demo/example pages')
|
|
556
|
-
|
|
557
|
-
if (deployments.size > 0) {
|
|
558
|
-
cmd.option<string>(
|
|
559
|
-
`--deployment <${Array.from(deployments).join('|')}>`,
|
|
560
|
-
`Explicitly tell the CLI to use this deployment adapter`,
|
|
561
|
-
(value) => {
|
|
562
|
-
if (!deployments.has(value)) {
|
|
563
|
-
throw new InvalidArgumentError(
|
|
564
|
-
`Invalid adapter: ${value}. The following are allowed: ${Array.from(
|
|
565
|
-
deployments,
|
|
566
|
-
).join(', ')}`,
|
|
567
|
-
)
|
|
568
|
-
}
|
|
569
|
-
return value
|
|
570
|
-
},
|
|
571
|
-
)
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
if (toolchains.size > 0) {
|
|
575
|
-
cmd
|
|
576
|
-
.option<string>(
|
|
577
|
-
`--toolchain <${Array.from(toolchains).join('|')}>`,
|
|
578
|
-
`Explicitly tell the CLI to use this toolchain`,
|
|
579
|
-
(value) => {
|
|
580
|
-
if (!toolchains.has(value)) {
|
|
581
|
-
throw new InvalidArgumentError(
|
|
582
|
-
`Invalid toolchain: ${value}. The following are allowed: ${Array.from(
|
|
583
|
-
toolchains,
|
|
584
|
-
).join(', ')}`,
|
|
585
|
-
)
|
|
586
|
-
}
|
|
587
|
-
return value
|
|
588
|
-
},
|
|
589
|
-
)
|
|
590
|
-
.option('--no-toolchain', 'skip toolchain selection')
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
cmd
|
|
594
|
-
.option('--interactive', 'interactive mode', false)
|
|
595
|
-
.option<Array<string> | boolean>(
|
|
596
|
-
'--add-ons [...add-ons]',
|
|
597
|
-
'pick from a list of available add-ons (comma separated list)',
|
|
598
|
-
(value: string) => {
|
|
599
|
-
let addOns: Array<string> | boolean = !!value
|
|
600
|
-
if (typeof value === 'string') {
|
|
601
|
-
addOns = value.split(',').map((addon) => addon.trim())
|
|
602
|
-
}
|
|
603
|
-
return addOns
|
|
604
|
-
},
|
|
605
|
-
)
|
|
606
|
-
.option('--list-add-ons', 'list all available add-ons', false)
|
|
607
|
-
.option(
|
|
608
|
-
'--addon-details <addon-id>',
|
|
609
|
-
'show detailed information about a specific add-on',
|
|
610
|
-
)
|
|
611
|
-
.option('--json', 'output JSON for automation', false)
|
|
612
|
-
.option('--git', 'create a git repository')
|
|
613
|
-
.option('--no-git', 'do not create a git repository')
|
|
614
|
-
.option(
|
|
615
|
-
'--target-dir <path>',
|
|
616
|
-
'the target directory for the application root',
|
|
617
|
-
)
|
|
618
|
-
.option(
|
|
619
|
-
'--add-on-config <config>',
|
|
620
|
-
'JSON string with add-on configuration options',
|
|
621
|
-
)
|
|
622
|
-
.option(
|
|
623
|
-
'-f, --force',
|
|
624
|
-
'force project creation even if the target directory is not empty',
|
|
625
|
-
false,
|
|
626
|
-
)
|
|
627
|
-
|
|
628
|
-
return cmd
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
// === CREATE SUBCOMMAND ===
|
|
632
|
-
// Creates a TanStack Start app (file-router mode).
|
|
633
|
-
const createCommand = program
|
|
634
|
-
.command('create')
|
|
635
|
-
.description(`Create a new TanStack Start application`)
|
|
636
|
-
|
|
637
|
-
configureCreateCommand(createCommand)
|
|
638
|
-
createCommand.action(handleCreate)
|
|
639
|
-
|
|
640
|
-
// === DEV SUBCOMMAND ===
|
|
641
|
-
const devCommand = program
|
|
642
|
-
.command('dev')
|
|
643
|
-
.description(
|
|
644
|
-
'Create a sandbox app and watch built-in framework templates/add-ons',
|
|
645
|
-
)
|
|
646
|
-
|
|
647
|
-
configureCreateCommand(devCommand)
|
|
648
|
-
devCommand.action(async (projectName: string, options: CliOptions) => {
|
|
649
|
-
const frameworkName = options.framework || defaultFramework || 'React'
|
|
650
|
-
const framework = getFrameworkByName(frameworkName)
|
|
651
|
-
if (!framework) {
|
|
652
|
-
console.error(`Unknown framework: ${frameworkName}`)
|
|
653
|
-
process.exit(1)
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
const watchPath = resolveBuiltInDevWatchPath(framework.id)
|
|
657
|
-
const devOptions: CliOptions = {
|
|
658
|
-
...options,
|
|
659
|
-
framework: framework.name,
|
|
660
|
-
devWatch: watchPath,
|
|
661
|
-
runDev: true,
|
|
662
|
-
install: options.install ?? true,
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
await startDevWatchMode(projectName, devOptions)
|
|
666
|
-
})
|
|
667
|
-
|
|
668
|
-
// === LIBRARIES SUBCOMMAND ===
|
|
669
|
-
program
|
|
670
|
-
.command('libraries')
|
|
671
|
-
.description('List TanStack libraries')
|
|
672
|
-
.option(
|
|
673
|
-
'--group <group>',
|
|
674
|
-
`filter by group (${LIBRARY_GROUPS.join(', ')})`,
|
|
675
|
-
)
|
|
676
|
-
.option('--json', 'output JSON for automation', false)
|
|
677
|
-
.action(async (options: { group?: string; json: boolean }) => {
|
|
678
|
-
try {
|
|
679
|
-
const data = await fetchLibraries()
|
|
680
|
-
let libraries = data.libraries
|
|
681
|
-
|
|
682
|
-
if (
|
|
683
|
-
options.group &&
|
|
684
|
-
Object.prototype.hasOwnProperty.call(data.groups, options.group)
|
|
685
|
-
) {
|
|
686
|
-
const groupIds = data.groups[options.group]
|
|
687
|
-
libraries = libraries.filter((lib) => groupIds.includes(lib.id))
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
const groupName = options.group
|
|
691
|
-
? data.groupNames[options.group] || options.group
|
|
692
|
-
: 'All Libraries'
|
|
693
|
-
|
|
694
|
-
const payload = {
|
|
695
|
-
group: groupName,
|
|
696
|
-
count: libraries.length,
|
|
697
|
-
libraries: libraries.map((lib) => ({
|
|
698
|
-
id: lib.id,
|
|
699
|
-
name: lib.name,
|
|
700
|
-
tagline: lib.tagline,
|
|
701
|
-
description: lib.description,
|
|
702
|
-
frameworks: lib.frameworks,
|
|
703
|
-
latestVersion: lib.latestVersion,
|
|
704
|
-
docsUrl: lib.docsUrl,
|
|
705
|
-
githubUrl: lib.githubUrl,
|
|
706
|
-
})),
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
if (options.json) {
|
|
710
|
-
printJson(payload)
|
|
711
|
-
return
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
console.log(chalk.bold(groupName))
|
|
715
|
-
for (const lib of payload.libraries) {
|
|
716
|
-
console.log(
|
|
717
|
-
`${chalk.bold(lib.id)} (${lib.latestVersion}) - ${lib.tagline}`,
|
|
718
|
-
)
|
|
719
|
-
}
|
|
720
|
-
} catch (error) {
|
|
721
|
-
log.error(error instanceof Error ? error.message : String(error))
|
|
722
|
-
process.exit(1)
|
|
723
|
-
}
|
|
724
|
-
})
|
|
725
|
-
|
|
726
|
-
// === DOC SUBCOMMAND ===
|
|
727
|
-
program
|
|
728
|
-
.command('doc')
|
|
729
|
-
.description('Fetch a TanStack documentation page')
|
|
730
|
-
.argument('<library>', 'library ID (eg. query, router, table)')
|
|
731
|
-
.argument('<path>', 'documentation path (eg. framework/react/overview)')
|
|
732
|
-
.option('--docs-version <version>', 'docs version (default: latest)', 'latest')
|
|
733
|
-
.option('--json', 'output JSON for automation', false)
|
|
734
|
-
.action(
|
|
735
|
-
async (
|
|
736
|
-
libraryId: string,
|
|
737
|
-
path: string,
|
|
738
|
-
options: { docsVersion: string; json: boolean },
|
|
739
|
-
) => {
|
|
740
|
-
try {
|
|
741
|
-
const data = await fetchLibraries()
|
|
742
|
-
const library = data.libraries.find((l) => l.id === libraryId)
|
|
743
|
-
|
|
744
|
-
if (!library) {
|
|
745
|
-
throw new Error(
|
|
746
|
-
`Library "${libraryId}" not found. Use \`tanstack libraries\` to see available libraries.`,
|
|
747
|
-
)
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
if (
|
|
751
|
-
options.docsVersion !== 'latest' &&
|
|
752
|
-
!library.availableVersions.includes(options.docsVersion)
|
|
753
|
-
) {
|
|
754
|
-
throw new Error(
|
|
755
|
-
`Version "${options.docsVersion}" not found for ${library.name}. Available: ${library.availableVersions.join(', ')}`,
|
|
756
|
-
)
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
const branch =
|
|
760
|
-
options.docsVersion === 'latest' ||
|
|
761
|
-
options.docsVersion === library.latestVersion
|
|
762
|
-
? library.latestBranch || 'main'
|
|
763
|
-
: options.docsVersion
|
|
764
|
-
|
|
765
|
-
const docsRoot = library.docsRoot || 'docs'
|
|
766
|
-
const filePath = `${docsRoot}/${path}.md`
|
|
767
|
-
const content = await fetchDocContent(library.repo, branch, filePath)
|
|
768
|
-
|
|
769
|
-
if (!content) {
|
|
770
|
-
throw new Error(
|
|
771
|
-
`Document not found: ${library.name} / ${path} (version: ${options.docsVersion})`,
|
|
772
|
-
)
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/)
|
|
776
|
-
let title = path.split('/').pop() || 'Untitled'
|
|
777
|
-
let docContent = content
|
|
778
|
-
|
|
779
|
-
if (frontmatterMatch && frontmatterMatch[1]) {
|
|
780
|
-
const frontmatter = frontmatterMatch[1]
|
|
781
|
-
const titleMatch = frontmatter.match(
|
|
782
|
-
/title:\s*['"]?([^'"\n]+)['"]?/,
|
|
783
|
-
)
|
|
784
|
-
if (titleMatch && titleMatch[1]) {
|
|
785
|
-
title = titleMatch[1]
|
|
786
|
-
}
|
|
787
|
-
docContent = content.slice(frontmatterMatch[0].length).trim()
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
const payload = {
|
|
791
|
-
title,
|
|
792
|
-
content: docContent,
|
|
793
|
-
url: `https://tanstack.com/${libraryId}/${options.docsVersion}/docs/${path}`,
|
|
794
|
-
library: library.name,
|
|
795
|
-
version:
|
|
796
|
-
options.docsVersion === 'latest'
|
|
797
|
-
? library.latestVersion
|
|
798
|
-
: options.docsVersion,
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
if (options.json) {
|
|
802
|
-
printJson(payload)
|
|
803
|
-
return
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
console.log(chalk.bold(payload.title))
|
|
807
|
-
console.log(chalk.blue(payload.url))
|
|
808
|
-
console.log('')
|
|
809
|
-
console.log(payload.content)
|
|
810
|
-
} catch (error) {
|
|
811
|
-
log.error(error instanceof Error ? error.message : String(error))
|
|
812
|
-
process.exit(1)
|
|
813
|
-
}
|
|
814
|
-
},
|
|
815
|
-
)
|
|
816
|
-
|
|
817
|
-
// === SEARCH-DOCS SUBCOMMAND ===
|
|
818
|
-
program
|
|
819
|
-
.command('search-docs')
|
|
820
|
-
.description('Search TanStack documentation')
|
|
821
|
-
.argument('<query>', 'search query')
|
|
822
|
-
.option('--library <id>', 'filter to specific library')
|
|
823
|
-
.option('--framework <name>', 'filter to specific framework')
|
|
824
|
-
.option('--limit <n>', 'max results (default: 10, max: 50)', parsePositiveInteger, 10)
|
|
825
|
-
.option('--json', 'output JSON for automation', false)
|
|
826
|
-
.action(
|
|
827
|
-
async (
|
|
828
|
-
query: string,
|
|
829
|
-
options: {
|
|
830
|
-
library?: string
|
|
831
|
-
framework?: string
|
|
832
|
-
limit: number
|
|
833
|
-
json: boolean
|
|
834
|
-
},
|
|
835
|
-
) => {
|
|
836
|
-
try {
|
|
837
|
-
const payload = await searchTanStackDocs({
|
|
838
|
-
query,
|
|
839
|
-
library: options.library,
|
|
840
|
-
framework: options.framework,
|
|
841
|
-
limit: options.limit,
|
|
842
|
-
})
|
|
843
|
-
|
|
844
|
-
if (options.json) {
|
|
845
|
-
printJson(payload)
|
|
846
|
-
return
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
for (const result of payload.results) {
|
|
850
|
-
console.log(
|
|
851
|
-
`${chalk.bold(result.title)} [${result.library}]\n${chalk.blue(result.url)}\n${result.snippet}\n`,
|
|
852
|
-
)
|
|
853
|
-
}
|
|
854
|
-
} catch (error) {
|
|
855
|
-
log.error(error instanceof Error ? error.message : String(error))
|
|
856
|
-
process.exit(1)
|
|
857
|
-
}
|
|
858
|
-
},
|
|
859
|
-
)
|
|
860
|
-
|
|
861
|
-
// === ECOSYSTEM SUBCOMMAND ===
|
|
862
|
-
program
|
|
863
|
-
.command('ecosystem')
|
|
864
|
-
.description('List TanStack ecosystem partners')
|
|
865
|
-
.option('--category <category>', 'filter by category')
|
|
866
|
-
.option('--library <id>', 'filter by TanStack library')
|
|
867
|
-
.option('--json', 'output JSON for automation', false)
|
|
868
|
-
.action(
|
|
869
|
-
async (options: { category?: string; library?: string; json: boolean }) => {
|
|
870
|
-
try {
|
|
871
|
-
const data = await fetchPartners()
|
|
872
|
-
|
|
873
|
-
let resolvedCategory: string | undefined
|
|
874
|
-
if (options.category) {
|
|
875
|
-
const normalized = options.category.toLowerCase().trim()
|
|
876
|
-
resolvedCategory = categoryAliases[normalized] || normalized
|
|
877
|
-
if (!data.categories.includes(resolvedCategory)) {
|
|
878
|
-
resolvedCategory = undefined
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
const library = options.library?.toLowerCase().trim()
|
|
883
|
-
const partners = data.partners
|
|
884
|
-
.filter((partner) =>
|
|
885
|
-
resolvedCategory ? partner.category === resolvedCategory : true,
|
|
886
|
-
)
|
|
887
|
-
.filter((partner) =>
|
|
888
|
-
library ? partner.libraries.some((l) => l === library) : true,
|
|
889
|
-
)
|
|
890
|
-
.map((partner) => ({
|
|
891
|
-
id: partner.id,
|
|
892
|
-
name: partner.name,
|
|
893
|
-
tagline: partner.tagline,
|
|
894
|
-
description: partner.description,
|
|
895
|
-
category: partner.category,
|
|
896
|
-
categoryLabel: partner.categoryLabel,
|
|
897
|
-
url: partner.url,
|
|
898
|
-
libraries: partner.libraries,
|
|
899
|
-
}))
|
|
900
|
-
|
|
901
|
-
const payload = {
|
|
902
|
-
query: {
|
|
903
|
-
category: options.category,
|
|
904
|
-
categoryResolved: resolvedCategory,
|
|
905
|
-
library: options.library,
|
|
906
|
-
},
|
|
907
|
-
count: partners.length,
|
|
908
|
-
partners,
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
if (options.json) {
|
|
912
|
-
printJson(payload)
|
|
913
|
-
return
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
for (const partner of partners) {
|
|
917
|
-
console.log(
|
|
918
|
-
`${chalk.bold(partner.name)} [${partner.category}] - ${partner.description}\n${chalk.blue(partner.url)}`,
|
|
919
|
-
)
|
|
920
|
-
}
|
|
921
|
-
} catch (error) {
|
|
922
|
-
log.error(error instanceof Error ? error.message : String(error))
|
|
923
|
-
process.exit(1)
|
|
924
|
-
}
|
|
925
|
-
},
|
|
926
|
-
)
|
|
927
|
-
|
|
928
|
-
// === PIN-VERSIONS SUBCOMMAND ===
|
|
929
|
-
program
|
|
930
|
-
.command('pin-versions')
|
|
931
|
-
.description('Pin versions of the TanStack libraries')
|
|
932
|
-
.action(async () => {
|
|
933
|
-
if (!fs.existsSync('package.json')) {
|
|
934
|
-
console.error('package.json not found')
|
|
935
|
-
return
|
|
936
|
-
}
|
|
937
|
-
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'))
|
|
938
|
-
|
|
939
|
-
const packages: Record<string, string> = {
|
|
940
|
-
'@tanstack/react-router': '',
|
|
941
|
-
'@tanstack/router-generator': '',
|
|
942
|
-
'@tanstack/react-router-devtools': '',
|
|
943
|
-
'@tanstack/react-start': '',
|
|
944
|
-
'@tanstack/react-start-config': '',
|
|
945
|
-
'@tanstack/router-plugin': '',
|
|
946
|
-
'@tanstack/react-start-client': '',
|
|
947
|
-
'@tanstack/react-start-plugin': '1.115.0',
|
|
948
|
-
'@tanstack/react-start-server': '',
|
|
949
|
-
'@tanstack/start-server-core': '1.115.0',
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
function sortObject(obj: Record<string, string>): Record<string, string> {
|
|
953
|
-
return Object.keys(obj)
|
|
954
|
-
.sort()
|
|
955
|
-
.reduce<Record<string, string>>((acc, key) => {
|
|
956
|
-
acc[key] = obj[key]
|
|
957
|
-
return acc
|
|
958
|
-
}, {})
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
if (!packageJson.dependencies['@tanstack/react-start']) {
|
|
962
|
-
console.error('@tanstack/react-start not found in dependencies')
|
|
963
|
-
return
|
|
964
|
-
}
|
|
965
|
-
let changed = 0
|
|
966
|
-
const startVersion = packageJson.dependencies[
|
|
967
|
-
'@tanstack/react-start'
|
|
968
|
-
].replace(/^\^/, '')
|
|
969
|
-
for (const pkg of Object.keys(packages)) {
|
|
970
|
-
if (!packageJson.dependencies[pkg]) {
|
|
971
|
-
packageJson.dependencies[pkg] = packages[pkg].length
|
|
972
|
-
? semver.maxSatisfying(
|
|
973
|
-
[startVersion, packages[pkg]],
|
|
974
|
-
`^${packages[pkg]}`,
|
|
975
|
-
)!
|
|
976
|
-
: startVersion
|
|
977
|
-
changed++
|
|
978
|
-
} else {
|
|
979
|
-
if (packageJson.dependencies[pkg].startsWith('^')) {
|
|
980
|
-
packageJson.dependencies[pkg] = packageJson.dependencies[
|
|
981
|
-
pkg
|
|
982
|
-
].replace(/^\^/, '')
|
|
983
|
-
changed++
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
packageJson.dependencies = sortObject(packageJson.dependencies)
|
|
988
|
-
if (changed > 0) {
|
|
989
|
-
fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2))
|
|
990
|
-
console.log(
|
|
991
|
-
`${changed} packages updated.
|
|
992
|
-
|
|
993
|
-
Remove your node_modules directory and package lock file and re-install.`,
|
|
994
|
-
)
|
|
995
|
-
} else {
|
|
996
|
-
console.log(
|
|
997
|
-
'No changes needed. The relevant TanStack packages are already pinned.',
|
|
998
|
-
)
|
|
999
|
-
}
|
|
1000
|
-
})
|
|
1001
|
-
|
|
1002
|
-
// === ADD SUBCOMMAND ===
|
|
1003
|
-
program
|
|
1004
|
-
.command('add')
|
|
1005
|
-
.argument(
|
|
1006
|
-
'[add-on...]',
|
|
1007
|
-
'Name of the add-ons (or add-ons separated by spaces or commas)',
|
|
1008
|
-
)
|
|
1009
|
-
.option('--forced', 'Force the add-on to be added', false)
|
|
1010
|
-
.action(async (addOns: Array<string>, options: { forced: boolean }) => {
|
|
1011
|
-
const parsedAddOns: Array<string> = []
|
|
1012
|
-
for (const addOn of addOns) {
|
|
1013
|
-
if (addOn.includes(',') || addOn.includes(' ')) {
|
|
1014
|
-
parsedAddOns.push(
|
|
1015
|
-
...addOn.split(/[\s,]+/).map((addon) => addon.trim()),
|
|
1016
|
-
)
|
|
1017
|
-
} else {
|
|
1018
|
-
parsedAddOns.push(addOn.trim())
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
if (parsedAddOns.length < 1) {
|
|
1022
|
-
const selectedAddOns = await promptForAddOns()
|
|
1023
|
-
if (selectedAddOns.length) {
|
|
1024
|
-
await addToApp(environment, selectedAddOns, resolve(process.cwd()), {
|
|
1025
|
-
forced: options.forced,
|
|
1026
|
-
})
|
|
1027
|
-
}
|
|
1028
|
-
} else {
|
|
1029
|
-
await addToApp(environment, parsedAddOns, resolve(process.cwd()), {
|
|
1030
|
-
forced: options.forced,
|
|
1031
|
-
})
|
|
1032
|
-
}
|
|
1033
|
-
})
|
|
1034
|
-
|
|
1035
|
-
// === ADD-ON SUBCOMMAND ===
|
|
1036
|
-
const addOnCommand = program.command('add-on')
|
|
1037
|
-
addOnCommand
|
|
1038
|
-
.command('init')
|
|
1039
|
-
.description('Initialize an add-on from the current project')
|
|
1040
|
-
.action(async () => {
|
|
1041
|
-
await initAddOn(environment)
|
|
1042
|
-
})
|
|
1043
|
-
addOnCommand
|
|
1044
|
-
.command('compile')
|
|
1045
|
-
.description('Update add-on from the current project')
|
|
1046
|
-
.action(async () => {
|
|
1047
|
-
await compileAddOn(environment)
|
|
1048
|
-
})
|
|
1049
|
-
addOnCommand
|
|
1050
|
-
.command('dev')
|
|
1051
|
-
.description(
|
|
1052
|
-
'Watch project files and continuously refresh .add-on and add-on.json',
|
|
1053
|
-
)
|
|
1054
|
-
.action(async () => {
|
|
1055
|
-
await devAddOn(environment)
|
|
1056
|
-
})
|
|
1057
|
-
|
|
1058
|
-
// === TEMPLATE SUBCOMMAND ===
|
|
1059
|
-
const templateCommand = program.command('template')
|
|
1060
|
-
templateCommand
|
|
1061
|
-
.command('init')
|
|
1062
|
-
.description('Initialize a project template from the current project')
|
|
1063
|
-
.action(async () => {
|
|
1064
|
-
await initStarter(environment)
|
|
1065
|
-
})
|
|
1066
|
-
templateCommand
|
|
1067
|
-
.command('compile')
|
|
1068
|
-
.description('Compile the template JSON file for the current project')
|
|
1069
|
-
.action(async () => {
|
|
1070
|
-
await compileStarter(environment)
|
|
1071
|
-
})
|
|
1072
|
-
|
|
1073
|
-
// Legacy alias for template command
|
|
1074
|
-
const starterCommand = program.command('starter')
|
|
1075
|
-
starterCommand
|
|
1076
|
-
.command('init')
|
|
1077
|
-
.description('Deprecated alias: initialize a project template')
|
|
1078
|
-
.action(async () => {
|
|
1079
|
-
await initStarter(environment)
|
|
1080
|
-
})
|
|
1081
|
-
starterCommand
|
|
1082
|
-
.command('compile')
|
|
1083
|
-
.description('Deprecated alias: compile the template JSON file')
|
|
1084
|
-
.action(async () => {
|
|
1085
|
-
await compileStarter(environment)
|
|
1086
|
-
})
|
|
1087
|
-
|
|
1088
|
-
// === LEGACY AUTO-CREATE MODE ===
|
|
1089
|
-
// For backward compatibility with cli-aliases (create-tsrouter-app, etc.)
|
|
1090
|
-
// If legacyAutoCreate is true and no subcommand is provided, treat the first
|
|
1091
|
-
// argument as a project name and auto-invoke create behavior
|
|
1092
|
-
if (legacyAutoCreate) {
|
|
1093
|
-
// Configure the main program with create options for legacy mode
|
|
1094
|
-
configureCreateCommand(program)
|
|
1095
|
-
program.action(handleCreate)
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
program.parse()
|
|
1099
|
-
}
|