@tanstack/create 0.68.2 → 0.68.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 (83) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/edge-add-ons.js +106 -0
  3. package/dist/edge-config-file.js +15 -0
  4. package/dist/edge-create-app.js +438 -0
  5. package/dist/edge-environment.js +141 -0
  6. package/dist/edge-file-helpers.js +88 -0
  7. package/dist/edge-frameworks.js +33 -0
  8. package/dist/edge-package-json.js +146 -0
  9. package/dist/edge-path.js +62 -0
  10. package/dist/edge-render.js +31 -0
  11. package/dist/edge-template-file.js +141 -0
  12. package/dist/edge.js +7 -0
  13. package/dist/frameworks/react/add-ons/storybook/info.json +5 -10
  14. package/dist/generated/create-manifest.js +4683 -0
  15. package/dist/manifest-types.js +1 -0
  16. package/dist/manifest.js +1 -0
  17. package/dist/types/custom-add-ons/add-on.d.ts +5 -3
  18. package/dist/types/edge-add-ons.d.ts +5 -0
  19. package/dist/types/edge-config-file.d.ts +8 -0
  20. package/dist/types/edge-create-app.d.ts +2 -0
  21. package/dist/types/edge-environment.d.ts +19 -0
  22. package/dist/types/edge-file-helpers.d.ts +7 -0
  23. package/dist/types/edge-frameworks.d.ts +7 -0
  24. package/dist/types/edge-package-json.d.ts +3 -0
  25. package/dist/types/edge-path.d.ts +5 -0
  26. package/dist/types/edge-render.d.ts +1 -0
  27. package/dist/types/edge-template-file.d.ts +2 -0
  28. package/dist/types/edge.d.ts +9 -0
  29. package/dist/types/generated/create-manifest.d.ts +36 -0
  30. package/dist/types/manifest-types.d.ts +4 -0
  31. package/dist/types/manifest.d.ts +1 -0
  32. package/dist/types/types.d.ts +96 -56
  33. package/dist/types.js +5 -3
  34. package/package.json +25 -5
  35. package/scripts/generate-manifest.mjs +407 -0
  36. package/src/edge-add-ons.ts +138 -0
  37. package/src/edge-config-file.ts +35 -0
  38. package/src/edge-create-app.ts +594 -0
  39. package/src/edge-environment.ts +175 -0
  40. package/src/edge-file-helpers.ts +112 -0
  41. package/src/edge-frameworks.ts +54 -0
  42. package/src/edge-package-json.ts +212 -0
  43. package/src/edge-path.ts +77 -0
  44. package/src/edge-render.ts +32 -0
  45. package/src/edge-template-file.ts +204 -0
  46. package/src/edge.ts +43 -0
  47. package/src/frameworks/react/add-ons/storybook/info.json +5 -10
  48. package/src/generated/create-manifest.ts +6490 -0
  49. package/src/manifest-types.ts +8 -0
  50. package/src/manifest.ts +1 -0
  51. package/src/types.ts +5 -3
  52. package/tests/edge-import.test.ts +31 -0
  53. package/tests/edge-manifest.test.ts +168 -0
  54. package/dist/frameworks/react/add-ons/storybook/assets/_dot_storybook/main.ts +0 -17
  55. package/dist/frameworks/react/add-ons/storybook/assets/_dot_storybook/preview.ts +0 -15
  56. package/dist/frameworks/react/add-ons/storybook/assets/src/components/storybook/button.stories.ts +0 -67
  57. package/dist/frameworks/react/add-ons/storybook/assets/src/components/storybook/button.tsx +0 -47
  58. package/dist/frameworks/react/add-ons/storybook/assets/src/components/storybook/dialog.stories.tsx +0 -92
  59. package/dist/frameworks/react/add-ons/storybook/assets/src/components/storybook/dialog.tsx +0 -29
  60. package/dist/frameworks/react/add-ons/storybook/assets/src/components/storybook/index.ts +0 -14
  61. package/dist/frameworks/react/add-ons/storybook/assets/src/components/storybook/input.stories.ts +0 -43
  62. package/dist/frameworks/react/add-ons/storybook/assets/src/components/storybook/input.tsx +0 -39
  63. package/dist/frameworks/react/add-ons/storybook/assets/src/components/storybook/radio-group.stories.ts +0 -53
  64. package/dist/frameworks/react/add-ons/storybook/assets/src/components/storybook/radio-group.tsx +0 -52
  65. package/dist/frameworks/react/add-ons/storybook/assets/src/components/storybook/slider.stories.ts +0 -55
  66. package/dist/frameworks/react/add-ons/storybook/assets/src/components/storybook/slider.tsx +0 -57
  67. package/dist/frameworks/react/add-ons/storybook/assets/src/routes/demo/storybook.tsx +0 -93
  68. package/dist/frameworks/react/add-ons/storybook/package.json +0 -10
  69. package/src/frameworks/react/add-ons/storybook/assets/_dot_storybook/main.ts +0 -17
  70. package/src/frameworks/react/add-ons/storybook/assets/_dot_storybook/preview.ts +0 -15
  71. package/src/frameworks/react/add-ons/storybook/assets/src/components/storybook/button.stories.ts +0 -67
  72. package/src/frameworks/react/add-ons/storybook/assets/src/components/storybook/button.tsx +0 -47
  73. package/src/frameworks/react/add-ons/storybook/assets/src/components/storybook/dialog.stories.tsx +0 -92
  74. package/src/frameworks/react/add-ons/storybook/assets/src/components/storybook/dialog.tsx +0 -29
  75. package/src/frameworks/react/add-ons/storybook/assets/src/components/storybook/index.ts +0 -14
  76. package/src/frameworks/react/add-ons/storybook/assets/src/components/storybook/input.stories.ts +0 -43
  77. package/src/frameworks/react/add-ons/storybook/assets/src/components/storybook/input.tsx +0 -39
  78. package/src/frameworks/react/add-ons/storybook/assets/src/components/storybook/radio-group.stories.ts +0 -53
  79. package/src/frameworks/react/add-ons/storybook/assets/src/components/storybook/radio-group.tsx +0 -52
  80. package/src/frameworks/react/add-ons/storybook/assets/src/components/storybook/slider.stories.ts +0 -55
  81. package/src/frameworks/react/add-ons/storybook/assets/src/components/storybook/slider.tsx +0 -57
  82. package/src/frameworks/react/add-ons/storybook/assets/src/routes/demo/storybook.tsx +0 -93
  83. package/src/frameworks/react/add-ons/storybook/package.json +0 -10
@@ -0,0 +1,594 @@
1
+ import { createPackageJSON } from './edge-package-json.js'
2
+ import { createTemplateFile } from './edge-template-file.js'
3
+ import { formatCommand } from './utils.js'
4
+ import { isBase64, isDemoFilePath } from './edge-file-helpers.js'
5
+ import { basenamePath, joinPaths, normalizePath } from './edge-path.js'
6
+ import { resolvePackageJSONLatest } from './npm-resolver.js'
7
+ import { writeConfigFileToEnvironment } from './edge-config-file.js'
8
+ import {
9
+ getPackageManagerExecuteCommand,
10
+ getPackageManagerScriptCommand,
11
+ packageManagerInstall,
12
+ translateExecuteCommand,
13
+ } from './package-manager.js'
14
+
15
+ import type { Environment, FileBundleHandler, Options } from './types.js'
16
+
17
+ function stripExamplesFromOptions(options: Options): Options {
18
+ if (options.includeExamples !== false) {
19
+ return options
20
+ }
21
+
22
+ const chosenAddOns = options.chosenAddOns
23
+ .filter((addOn) => addOn.type !== 'example')
24
+ .map((addOn) => {
25
+ const filteredRoutes = (addOn.routes || []).filter(
26
+ (route) =>
27
+ !isDemoFilePath(route.path) &&
28
+ !(route.url && route.url.startsWith('/demo')),
29
+ )
30
+
31
+ const filteredIntegrations = (addOn.integrations || []).filter(
32
+ (integration) => !isDemoFilePath(integration.path),
33
+ )
34
+
35
+ return {
36
+ ...addOn,
37
+ routes: filteredRoutes,
38
+ integrations: filteredIntegrations,
39
+ getFiles: async () => {
40
+ const files = await addOn.getFiles()
41
+ return files.filter((file) => !isDemoFilePath(file))
42
+ },
43
+ getDeletedFiles: async () => {
44
+ const deletedFiles = await addOn.getDeletedFiles()
45
+ return deletedFiles.filter((file) => !isDemoFilePath(file))
46
+ },
47
+ }
48
+ })
49
+
50
+ return {
51
+ ...options,
52
+ chosenAddOns,
53
+ }
54
+ }
55
+
56
+ async function writeFiles(environment: Environment, options: Options) {
57
+ const templateFileFromContent = createTemplateFile(environment, options)
58
+
59
+ async function writeFileBundle(bundle: FileBundleHandler) {
60
+ const files = await bundle.getFiles()
61
+
62
+ for (const file of files) {
63
+ const contents = await bundle.getFileContents(file)
64
+
65
+ if (isBase64(contents)) {
66
+ await environment.writeFileBase64(
67
+ joinPaths(options.targetDir, file),
68
+ contents,
69
+ )
70
+ } else {
71
+ await templateFileFromContent(file, contents)
72
+ }
73
+ }
74
+
75
+ const deletedFiles = await bundle.getDeletedFiles()
76
+ for (const file of deletedFiles) {
77
+ await environment.deleteFile(joinPaths(options.targetDir, file))
78
+ }
79
+ }
80
+
81
+ environment.startStep({
82
+ id: 'write-framework-files',
83
+ type: 'file',
84
+ message: 'Writing framework files...',
85
+ })
86
+ await writeFileBundle(options.framework)
87
+ environment.finishStep('write-framework-files', 'Framework files written')
88
+
89
+ let wroteAddonFiles = false
90
+ for (const type of ['add-on', 'example', 'toolchain', 'deployment']) {
91
+ for (const phase of ['setup', 'add-on', 'example']) {
92
+ for (const addOn of options.chosenAddOns.filter(
93
+ (addOn) => addOn.phase === phase && addOn.type === type,
94
+ )) {
95
+ environment.startStep({
96
+ id: 'write-addon-files',
97
+ type: 'file',
98
+ message: `Writing ${addOn.name} files...`,
99
+ })
100
+ await writeFileBundle(addOn)
101
+ wroteAddonFiles = true
102
+ }
103
+ }
104
+ }
105
+ if (wroteAddonFiles) {
106
+ environment.finishStep('write-addon-files', 'Add-on files written')
107
+ }
108
+
109
+ if (options.starter) {
110
+ environment.startStep({
111
+ id: 'write-starter-files',
112
+ type: 'file',
113
+ message: 'Writing starter files...',
114
+ })
115
+ await writeFileBundle(options.starter)
116
+ environment.finishStep('write-starter-files', 'Starter files written')
117
+ }
118
+
119
+ environment.startStep({
120
+ id: 'write-package-json',
121
+ type: 'file',
122
+ message: 'Writing package.json...',
123
+ })
124
+ const packageJSON = await resolvePackageJSONLatest(createPackageJSON(options))
125
+ await environment.writeFile(
126
+ joinPaths(options.targetDir, './package.json'),
127
+ JSON.stringify(packageJSON, null, 2),
128
+ )
129
+ environment.finishStep('write-package-json', 'Package.json written')
130
+
131
+ environment.startStep({
132
+ id: 'write-config-file',
133
+ type: 'file',
134
+ message: 'Writing config file...',
135
+ })
136
+ await writeConfigFileToEnvironment(environment, options)
137
+ environment.finishStep('write-config-file', 'Config file written')
138
+ }
139
+
140
+ async function runSpecialSteps(
141
+ environment: Environment,
142
+ options: Options,
143
+ specialSteps: Array<string>,
144
+ ) {
145
+ if (!specialSteps.length) {
146
+ return
147
+ }
148
+
149
+ environment.startStep({
150
+ id: 'special-steps',
151
+ type: 'command',
152
+ message: 'Running special steps...',
153
+ })
154
+
155
+ for (const step of specialSteps) {
156
+ if (step === 'rimraf-node-modules') {
157
+ await environment.rimraf(joinPaths(options.targetDir, 'node_modules'))
158
+ for (const lockFile of [
159
+ 'package-lock.json',
160
+ 'yarn.lock',
161
+ 'pnpm-lock.yaml',
162
+ ]) {
163
+ const lockFilePath = joinPaths(options.targetDir, lockFile)
164
+ if (environment.exists(lockFilePath)) {
165
+ await environment.deleteFile(lockFilePath)
166
+ }
167
+ }
168
+ continue
169
+ }
170
+
171
+ if (step === 'post-init-script') {
172
+ const packageJsonPath = joinPaths(options.targetDir, 'package.json')
173
+ if (!environment.exists(packageJsonPath)) {
174
+ environment.warn(
175
+ 'Warning',
176
+ 'No package.json found, skipping post-create-init script',
177
+ )
178
+ continue
179
+ }
180
+
181
+ const packageJson = JSON.parse(await environment.readFile(packageJsonPath))
182
+ const postCreateInit = packageJson.scripts?.['post-create-init']
183
+ if (postCreateInit) {
184
+ const { command, args } = getPackageManagerScriptCommand(
185
+ options.packageManager,
186
+ ['post-create-init'],
187
+ )
188
+ await environment.execute(command, args, options.targetDir, {
189
+ inherit: true,
190
+ })
191
+ }
192
+ continue
193
+ }
194
+
195
+ environment.error(`Special step ${step} not found`)
196
+ }
197
+
198
+ environment.finishStep('special-steps', 'Special steps complete')
199
+ }
200
+
201
+ async function installShadcnComponents(
202
+ environment: Environment,
203
+ targetDir: string,
204
+ options: Options,
205
+ ) {
206
+ if (!options.chosenAddOns.find((a) => a.id === 'shadcn')) {
207
+ return
208
+ }
209
+
210
+ const shadcnComponents = new Set<string>()
211
+ for (const addOn of options.chosenAddOns) {
212
+ if (addOn.shadcnComponents) {
213
+ for (const component of addOn.shadcnComponents) {
214
+ shadcnComponents.add(component)
215
+ }
216
+ }
217
+ }
218
+ if (options.starter?.shadcnComponents) {
219
+ for (const component of options.starter.shadcnComponents) {
220
+ shadcnComponents.add(component)
221
+ }
222
+ }
223
+
224
+ if (shadcnComponents.size > 0) {
225
+ environment.startStep({
226
+ id: 'install-shadcn-components',
227
+ type: 'command',
228
+ message: `Installing shadcn components (${Array.from(shadcnComponents).join(', ')})...`,
229
+ })
230
+
231
+ const { command, args } = getPackageManagerExecuteCommand(
232
+ options.packageManager,
233
+ 'shadcn@latest',
234
+ ['add', '--silent', '--yes', ...Array.from(shadcnComponents)],
235
+ )
236
+ await environment.execute(command, args, normalizePath(targetDir))
237
+
238
+ environment.finishStep(
239
+ 'install-shadcn-components',
240
+ 'Shadcn components installed',
241
+ )
242
+ }
243
+ }
244
+
245
+ async function setupIntent(
246
+ environment: Environment,
247
+ targetDir: string,
248
+ options: Options,
249
+ ) {
250
+ if (!options.intent) {
251
+ return
252
+ }
253
+
254
+ environment.startStep({
255
+ id: 'setup-intent',
256
+ type: 'command',
257
+ message: 'Setting up TanStack Intent skill mappings...',
258
+ })
259
+
260
+ const { command, args } = getPackageManagerExecuteCommand(
261
+ options.packageManager,
262
+ '@tanstack/intent',
263
+ ['install', '--map'],
264
+ )
265
+ await environment.execute(command, args, normalizePath(targetDir))
266
+ environment.finishStep('setup-intent', 'TanStack Intent configured')
267
+ }
268
+
269
+ async function runCommandsAndInstallDependencies(
270
+ environment: Environment,
271
+ options: Options,
272
+ ) {
273
+ const s = environment.spinner()
274
+
275
+ if (options.git) {
276
+ s.start('Initializing git repository...')
277
+ environment.startStep({
278
+ id: 'initialize-git-repository',
279
+ type: 'command',
280
+ message: 'Initializing git repository...',
281
+ })
282
+
283
+ await environment.execute('git', ['init'], normalizePath(options.targetDir))
284
+
285
+ environment.finishStep(
286
+ 'initialize-git-repository',
287
+ 'Initialized git repository',
288
+ )
289
+ s.stop('Initialized git repository')
290
+ }
291
+
292
+ const specialSteps = new Set<string>()
293
+ for (const addOn of options.chosenAddOns) {
294
+ for (const step of addOn.createSpecialSteps || []) {
295
+ specialSteps.add(step)
296
+ }
297
+ }
298
+ if (specialSteps.size) {
299
+ await runSpecialSteps(environment, options, Array.from(specialSteps))
300
+ }
301
+
302
+ if (options.install !== false) {
303
+ s.start(`Installing dependencies via ${options.packageManager}...`)
304
+ environment.startStep({
305
+ id: 'install-dependencies',
306
+ type: 'package-manager',
307
+ message: `Installing dependencies via ${options.packageManager}...`,
308
+ })
309
+ await packageManagerInstall(
310
+ environment,
311
+ options.targetDir,
312
+ options.packageManager,
313
+ )
314
+ environment.finishStep('install-dependencies', 'Installed dependencies')
315
+ s.stop('Installed dependencies')
316
+ } else {
317
+ s.start('Skipping dependency installation...')
318
+ environment.startStep({
319
+ id: 'skip-dependencies',
320
+ type: 'info',
321
+ message: 'Skipping dependency installation...',
322
+ })
323
+ environment.finishStep('skip-dependencies', 'Dependency installation skipped')
324
+ s.stop('Dependency installation skipped')
325
+ }
326
+
327
+ const postInitSpecialSteps = new Set<string>()
328
+ for (const addOn of options.chosenAddOns) {
329
+ for (const step of addOn.postInitSpecialSteps || []) {
330
+ postInitSpecialSteps.add(step)
331
+ }
332
+ }
333
+ if (postInitSpecialSteps.size) {
334
+ await runSpecialSteps(
335
+ environment,
336
+ options,
337
+ Array.from(postInitSpecialSteps),
338
+ )
339
+ }
340
+
341
+ for (const phase of ['setup', 'add-on', 'example']) {
342
+ for (const addOn of options.chosenAddOns.filter(
343
+ (addOn) =>
344
+ addOn.phase === phase && addOn.command && addOn.command.command,
345
+ )) {
346
+ s.start(`Running commands for ${addOn.name}...`)
347
+ const translated = translateExecuteCommand(options.packageManager, {
348
+ command: addOn.command!.command,
349
+ args: addOn.command!.args || [],
350
+ })
351
+ const cmd = formatCommand(translated)
352
+ environment.startStep({
353
+ id: 'run-commands',
354
+ type: 'command',
355
+ message: cmd,
356
+ })
357
+ await environment.execute(
358
+ translated.command,
359
+ translated.args,
360
+ options.targetDir,
361
+ { inherit: true },
362
+ )
363
+ environment.finishStep('run-commands', 'Setup commands complete')
364
+ s.stop(`${addOn.name} commands complete`)
365
+ }
366
+ }
367
+
368
+ if (
369
+ options.starter &&
370
+ options.starter.command &&
371
+ options.starter.command.command
372
+ ) {
373
+ s.start(`Setting up starter ${options.starter.name}...`)
374
+ const starterTranslated = translateExecuteCommand(options.packageManager, {
375
+ command: options.starter.command.command,
376
+ args: options.starter.command.args || [],
377
+ })
378
+ const cmd = formatCommand(starterTranslated)
379
+ environment.startStep({
380
+ id: 'run-starter-command',
381
+ type: 'command',
382
+ message: cmd,
383
+ })
384
+
385
+ await environment.execute(
386
+ starterTranslated.command,
387
+ starterTranslated.args,
388
+ options.targetDir,
389
+ { inherit: true },
390
+ )
391
+
392
+ environment.finishStep('run-starter-command', 'Starter command complete')
393
+ s.stop(`${options.starter.name} commands complete`)
394
+ }
395
+
396
+ await installShadcnComponents(environment, options.targetDir, options)
397
+ await setupIntent(environment, options.targetDir, options)
398
+
399
+ if (shouldGenerateRoutes(options)) {
400
+ s.start('Generating route tree...')
401
+ const command = getPackageManagerScriptCommand(options.packageManager, [
402
+ 'generate-routes',
403
+ ])
404
+ const cmd = formatCommand(command)
405
+ environment.startStep({
406
+ id: 'generate-routes',
407
+ type: 'command',
408
+ message: cmd,
409
+ })
410
+ await environment.execute(
411
+ command.command,
412
+ command.args,
413
+ options.targetDir,
414
+ {
415
+ inherit: true,
416
+ },
417
+ )
418
+ environment.finishStep('generate-routes', 'Route tree generated')
419
+ s.stop('Route tree generated')
420
+ }
421
+ }
422
+
423
+ function shouldGenerateRoutes(options: Options) {
424
+ return (
425
+ options.install !== false &&
426
+ options.mode === 'file-router' &&
427
+ (options.framework.id === 'react' || options.framework.id === 'solid')
428
+ )
429
+ }
430
+
431
+ async function seedEnvValues(environment: Environment, options: Options) {
432
+ const envVarValues = options.envVarValues || {}
433
+ const entries = Object.entries(envVarValues)
434
+ if (entries.length === 0) return
435
+
436
+ const envLocalPath = joinPaths(options.targetDir, '.env.local')
437
+ if (!environment.exists(envLocalPath)) {
438
+ return
439
+ }
440
+
441
+ let envContents = await environment.readFile(envLocalPath)
442
+ for (const [key, value] of entries) {
443
+ const escapedValue = value.replace(/\n/g, '\\n')
444
+ const nextLine = `${key}=${escapedValue}`
445
+ const pattern = new RegExp(`^${key}=.*$`, 'm')
446
+
447
+ if (pattern.test(envContents)) {
448
+ envContents = envContents.replace(pattern, nextLine)
449
+ } else {
450
+ envContents += `${envContents.endsWith('\n') ? '' : '\n'}${nextLine}\n`
451
+ }
452
+ }
453
+
454
+ await environment.writeFile(envLocalPath, envContents)
455
+ }
456
+
457
+ async function writeEnvExample(environment: Environment, options: Options) {
458
+ const envExamplePath = joinPaths(options.targetDir, '.env.example')
459
+ const existing = environment.exists(envExamplePath)
460
+ ? await environment.readFile(envExamplePath)
461
+ : ''
462
+
463
+ const declared = new Set<string>()
464
+ for (const match of existing.matchAll(/^([A-Z_][A-Z0-9_]*)=/gm)) {
465
+ declared.add(match[1])
466
+ }
467
+
468
+ const sections: Array<string> = []
469
+ for (const addOn of options.chosenAddOns) {
470
+ const lines: Array<string> = []
471
+ for (const envVar of addOn.envVars || []) {
472
+ if (declared.has(envVar.name)) continue
473
+ declared.add(envVar.name)
474
+ if (envVar.description) {
475
+ const required = envVar.required ? ' (required)' : ''
476
+ lines.push(`# ${envVar.description}${required}`)
477
+ }
478
+ lines.push(`${envVar.name}=`)
479
+ }
480
+ if (lines.length > 0) {
481
+ sections.push(`# ${addOn.name}\n${lines.join('\n')}`)
482
+ }
483
+ }
484
+
485
+ if (sections.length === 0) return
486
+
487
+ const additions = sections.join('\n\n')
488
+ const newContent = existing
489
+ ? `${existing.trimEnd()}\n\n${additions}\n`
490
+ : `${additions}\n`
491
+
492
+ await environment.writeFile(envExamplePath, newContent)
493
+ }
494
+
495
+ const SHIPPING_CATEGORIES = new Set(['auth', 'database', 'orm', 'deploy'])
496
+
497
+ function buildNextSteps(options: Options): string {
498
+ const collectedEnv = new Set(Object.keys(options.envVarValues || {}))
499
+ const listedEnvVars = new Set<string>()
500
+ const envVarLines: Array<string> = []
501
+ const docLines: Array<string> = []
502
+
503
+ for (const addOn of options.chosenAddOns) {
504
+ if (addOn.link && addOn.category && SHIPPING_CATEGORIES.has(addOn.category)) {
505
+ docLines.push(` - ${addOn.name} (${addOn.category}): ${addOn.link}`)
506
+ }
507
+
508
+ for (const envVar of addOn.envVars || []) {
509
+ if (listedEnvVars.has(envVar.name)) continue
510
+ listedEnvVars.add(envVar.name)
511
+ const required = envVar.required ? ' (required)' : ''
512
+ const status = collectedEnv.has(envVar.name)
513
+ ? ' - already set from your input'
514
+ : ' - needs a value'
515
+ const desc = envVar.description ? ` - ${envVar.description}` : ''
516
+ envVarLines.push(` - ${envVar.name}${required}${desc}${status}`)
517
+ }
518
+ }
519
+
520
+ const sections: Array<string> = []
521
+ if (envVarLines.length > 0) {
522
+ sections.push(
523
+ `Environment variables (review/fill in .env.local before deploying):\n${envVarLines.join('\n')}`,
524
+ )
525
+ }
526
+ if (docLines.length > 0) {
527
+ sections.push(`Docs for the integrations you picked:\n${docLines.join('\n')}`)
528
+ }
529
+ if (options.intent) {
530
+ sections.push(
531
+ `Working with an AI agent? Your agent config (AGENTS.md / CLAUDE.md) was wired up by TanStack Intent\nwith explicit skill mappings for the libraries you installed. Try asking your agent:\n - "migrate this Next.js page to TanStack Start"\n - "add a protected /dashboard route"\n - "show me how to use TanStack Router search params"`,
532
+ )
533
+ }
534
+
535
+ return sections.length > 0 ? `\nNext steps:\n\n${sections.join('\n\n')}\n` : ''
536
+ }
537
+
538
+ function report(environment: Environment, options: Options) {
539
+ const warnings: Array<string> = []
540
+ for (const addOn of options.chosenAddOns) {
541
+ if (addOn.warning) {
542
+ warnings.push(addOn.warning)
543
+ }
544
+ }
545
+
546
+ if (warnings.length > 0) {
547
+ environment.warn('Warnings', warnings.join('\n'))
548
+ }
549
+
550
+ let errorStatement = ''
551
+ if (environment.getErrors().length) {
552
+ errorStatement = `
553
+
554
+ Errors were encountered during the creation of your app:
555
+
556
+ ${environment.getErrors().join('\n')}`
557
+ }
558
+
559
+ const targetDir = normalizePath(options.targetDir)
560
+ const isCurrentDirectory = targetDir === '.'
561
+ const locationMessage = isCurrentDirectory
562
+ ? `Your ${environment.appName} app is ready.`
563
+ : `Your ${environment.appName} app is ready in '${basenamePath(targetDir)}'.`
564
+ const cdInstruction = isCurrentDirectory
565
+ ? ''
566
+ : `% cd ${options.projectName}
567
+ `
568
+
569
+ const nextSteps = buildNextSteps(options)
570
+
571
+ environment.outro(
572
+ `${locationMessage}
573
+
574
+ Use the following commands to start your app:
575
+ ${cdInstruction}% ${formatCommand(
576
+ getPackageManagerScriptCommand(options.packageManager, ['dev']),
577
+ )}
578
+ ${nextSteps}
579
+ Please read the README.md file for information on testing, styling, adding routes, etc.${errorStatement}`,
580
+ )
581
+ }
582
+
583
+ export async function createApp(environment: Environment, options: Options) {
584
+ const effectiveOptions = stripExamplesFromOptions(options)
585
+
586
+ environment.startRun()
587
+ await writeFiles(environment, effectiveOptions)
588
+ await seedEnvValues(environment, effectiveOptions)
589
+ await writeEnvExample(environment, effectiveOptions)
590
+ await runCommandsAndInstallDependencies(environment, effectiveOptions)
591
+ environment.finishRun()
592
+
593
+ report(environment, effectiveOptions)
594
+ }