@tanstack/cta-ui 0.10.0-alpha.19 → 0.10.0-alpha.21

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 (71) hide show
  1. package/lib/index.ts +16 -7
  2. package/lib-dist/index.d.ts +6 -1
  3. package/lib-dist/index.js +6 -3
  4. package/package.json +19 -7
  5. package/public/tailwind.svg +1 -0
  6. package/public/tanstack.png +0 -0
  7. package/public/typescript.svg +1 -0
  8. package/src/components/StatusList.tsx +22 -0
  9. package/src/components/add-on-info-dialog.tsx +39 -0
  10. package/src/components/cta-sidebar.tsx +55 -0
  11. package/src/components/custom-add-on-dialog.tsx +79 -0
  12. package/src/components/file-navigator.tsx +205 -0
  13. package/src/components/file-tree.tsx +18 -60
  14. package/src/components/file-viewer.tsx +11 -3
  15. package/src/components/sidebar-items/add-ons.tsx +91 -0
  16. package/src/components/sidebar-items/mode-selector.tsx +55 -0
  17. package/src/components/sidebar-items/project-name.tsx +29 -0
  18. package/src/components/sidebar-items/run-add-ons.tsx +71 -0
  19. package/src/components/sidebar-items/run-create-app.tsx +82 -0
  20. package/src/components/sidebar-items/starter.tsx +115 -0
  21. package/src/components/sidebar-items/typescript-switch.tsx +52 -0
  22. package/src/components/toaster.tsx +29 -0
  23. package/src/components/ui/button.tsx +21 -19
  24. package/src/components/ui/dialog.tsx +25 -20
  25. package/src/components/ui/dropdown-menu.tsx +255 -0
  26. package/src/components/ui/input.tsx +21 -0
  27. package/src/components/ui/label.tsx +22 -0
  28. package/src/components/ui/popover.tsx +46 -0
  29. package/src/components/ui/separator.tsx +28 -0
  30. package/src/components/ui/sheet.tsx +137 -0
  31. package/src/components/ui/sidebar.tsx +726 -0
  32. package/src/components/ui/skeleton.tsx +13 -0
  33. package/src/components/ui/sonner.tsx +23 -0
  34. package/src/components/ui/switch.tsx +29 -0
  35. package/src/components/ui/toggle-group.tsx +11 -11
  36. package/src/components/ui/toggle.tsx +15 -13
  37. package/src/components/ui/tooltip.tsx +61 -0
  38. package/src/components/ui/tree-view.tsx +17 -12
  39. package/src/engine-handling/add-to-app-wrapper.ts +114 -0
  40. package/src/engine-handling/create-app-wrapper.ts +107 -0
  41. package/src/engine-handling/file-helpers.ts +25 -0
  42. package/src/engine-handling/framework-registration.ts +11 -0
  43. package/src/engine-handling/generate-initial-payload.ts +93 -0
  44. package/src/engine-handling/server-environment.ts +13 -0
  45. package/src/file-classes.ts +54 -0
  46. package/src/hooks/use-mobile.ts +19 -0
  47. package/src/hooks/use-streaming-status.ts +70 -0
  48. package/src/lib/api.ts +90 -0
  49. package/src/routeTree.gen.ts +4 -27
  50. package/src/routes/__root.tsx +36 -7
  51. package/src/routes/api/add-to-app.ts +21 -0
  52. package/src/routes/api/create-app.ts +21 -0
  53. package/src/routes/api/dry-run-add-to-app.ts +16 -0
  54. package/src/routes/api/dry-run-create-app.ts +16 -0
  55. package/src/routes/api/initial-payload.ts +10 -0
  56. package/src/routes/api/load-remote-add-on.ts +42 -0
  57. package/src/routes/api/load-starter.ts +47 -0
  58. package/src/routes/api/shutdown.ts +11 -0
  59. package/src/routes/index.tsx +3 -210
  60. package/src/store/add-ons.ts +81 -0
  61. package/src/store/project.ts +268 -0
  62. package/src/styles.css +47 -0
  63. package/src/types.d.ts +87 -0
  64. package/tests/store/add-ons.test.ts +222 -0
  65. package/vitest.config.ts +6 -0
  66. package/.cursorrules +0 -7
  67. package/src/components/Header.tsx +0 -13
  68. package/src/components/applied-add-on.tsx +0 -149
  69. package/src/lib/server-fns.ts +0 -78
  70. package/src/routes/api.demo-names.ts +0 -11
  71. package/src/routes/demo.tanstack-query.tsx +0 -28
@@ -0,0 +1,13 @@
1
+ import { cn } from "@/lib/utils"
2
+
3
+ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
4
+ return (
5
+ <div
6
+ data-slot="skeleton"
7
+ className={cn("bg-accent animate-pulse rounded-md", className)}
8
+ {...props}
9
+ />
10
+ )
11
+ }
12
+
13
+ export { Skeleton }
@@ -0,0 +1,23 @@
1
+ import { useTheme } from "next-themes"
2
+ import { Toaster as Sonner, ToasterProps } from "sonner"
3
+
4
+ const Toaster = ({ ...props }: ToasterProps) => {
5
+ const { theme = "system" } = useTheme()
6
+
7
+ return (
8
+ <Sonner
9
+ theme={theme as ToasterProps["theme"]}
10
+ className="toaster group"
11
+ style={
12
+ {
13
+ "--normal-bg": "var(--popover)",
14
+ "--normal-text": "var(--popover-foreground)",
15
+ "--normal-border": "var(--border)",
16
+ } as React.CSSProperties
17
+ }
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+
23
+ export { Toaster }
@@ -0,0 +1,29 @@
1
+ import * as React from "react"
2
+ import * as SwitchPrimitive from "@radix-ui/react-switch"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ function Switch({
7
+ className,
8
+ ...props
9
+ }: React.ComponentProps<typeof SwitchPrimitive.Root>) {
10
+ return (
11
+ <SwitchPrimitive.Root
12
+ data-slot="switch"
13
+ className={cn(
14
+ "peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
15
+ className
16
+ )}
17
+ {...props}
18
+ >
19
+ <SwitchPrimitive.Thumb
20
+ data-slot="switch-thumb"
21
+ className={cn(
22
+ "bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
23
+ )}
24
+ />
25
+ </SwitchPrimitive.Root>
26
+ )
27
+ }
28
+
29
+ export { Switch }
@@ -1,15 +1,15 @@
1
- import * as React from "react"
2
- import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
3
- import { type VariantProps } from "class-variance-authority"
1
+ import * as React from 'react'
2
+ import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group'
3
+ import type { VariantProps } from 'class-variance-authority'
4
4
 
5
- import { cn } from "@/lib/utils"
6
- import { toggleVariants } from "@/components/ui/toggle"
5
+ import { cn } from '@/lib/utils'
6
+ import { toggleVariants } from '@/components/ui/toggle'
7
7
 
8
8
  const ToggleGroupContext = React.createContext<
9
9
  VariantProps<typeof toggleVariants>
10
10
  >({
11
- size: "default",
12
- variant: "default",
11
+ size: 'default',
12
+ variant: 'default',
13
13
  })
14
14
 
15
15
  function ToggleGroup({
@@ -26,8 +26,8 @@ function ToggleGroup({
26
26
  data-variant={variant}
27
27
  data-size={size}
28
28
  className={cn(
29
- "group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs",
30
- className
29
+ 'group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs',
30
+ className,
31
31
  )}
32
32
  {...props}
33
33
  >
@@ -58,8 +58,8 @@ function ToggleGroupItem({
58
58
  variant: context.variant || variant,
59
59
  size: context.size || size,
60
60
  }),
61
- "min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l",
62
- className
61
+ 'min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l',
62
+ className,
63
63
  )}
64
64
  {...props}
65
65
  >
@@ -1,31 +1,33 @@
1
- "use client"
1
+ 'use client'
2
2
 
3
- import * as React from "react"
4
- import * as TogglePrimitive from "@radix-ui/react-toggle"
5
- import { cva, type VariantProps } from "class-variance-authority"
3
+ import * as React from 'react'
4
+ import * as TogglePrimitive from '@radix-ui/react-toggle'
5
+ import { cva } from 'class-variance-authority'
6
6
 
7
- import { cn } from "@/lib/utils"
7
+ import type { VariantProps } from 'class-variance-authority'
8
+
9
+ import { cn } from '@/lib/utils'
8
10
 
9
11
  const toggleVariants = cva(
10
12
  "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",
11
13
  {
12
14
  variants: {
13
15
  variant: {
14
- default: "bg-transparent",
16
+ default: 'bg-transparent',
15
17
  outline:
16
- "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground",
18
+ 'border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground',
17
19
  },
18
20
  size: {
19
- default: "h-9 px-2 min-w-9",
20
- sm: "h-8 px-1.5 min-w-8",
21
- lg: "h-10 px-2.5 min-w-10",
21
+ default: 'h-9 px-2 min-w-9',
22
+ sm: 'h-8 px-1.5 min-w-8',
23
+ lg: 'h-10 px-2.5 min-w-10',
22
24
  },
23
25
  },
24
26
  defaultVariants: {
25
- variant: "default",
26
- size: "default",
27
+ variant: 'default',
28
+ size: 'default',
27
29
  },
28
- }
30
+ },
29
31
  )
30
32
 
31
33
  function Toggle({
@@ -0,0 +1,61 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ function TooltipProvider({
9
+ delayDuration = 0,
10
+ ...props
11
+ }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
12
+ return (
13
+ <TooltipPrimitive.Provider
14
+ data-slot="tooltip-provider"
15
+ delayDuration={delayDuration}
16
+ {...props}
17
+ />
18
+ )
19
+ }
20
+
21
+ function Tooltip({
22
+ ...props
23
+ }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
24
+ return (
25
+ <TooltipProvider>
26
+ <TooltipPrimitive.Root data-slot="tooltip" {...props} />
27
+ </TooltipProvider>
28
+ )
29
+ }
30
+
31
+ function TooltipTrigger({
32
+ ...props
33
+ }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
34
+ return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
35
+ }
36
+
37
+ function TooltipContent({
38
+ className,
39
+ sideOffset = 0,
40
+ children,
41
+ ...props
42
+ }: React.ComponentProps<typeof TooltipPrimitive.Content>) {
43
+ return (
44
+ <TooltipPrimitive.Portal>
45
+ <TooltipPrimitive.Content
46
+ data-slot="tooltip-content"
47
+ sideOffset={sideOffset}
48
+ className={cn(
49
+ "bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
50
+ className
51
+ )}
52
+ {...props}
53
+ >
54
+ {children}
55
+ <TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
56
+ </TooltipPrimitive.Content>
57
+ </TooltipPrimitive.Portal>
58
+ )
59
+ }
60
+
61
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
@@ -22,7 +22,7 @@ interface TreeDataItem {
22
22
  icon?: any
23
23
  selectedIcon?: any
24
24
  openIcon?: any
25
- children?: TreeDataItem[]
25
+ children?: Array<TreeDataItem>
26
26
  actions?: React.ReactNode
27
27
  onClick?: () => void
28
28
  draggable?: boolean
@@ -31,10 +31,11 @@ interface TreeDataItem {
31
31
  }
32
32
 
33
33
  type TreeProps = React.HTMLAttributes<HTMLDivElement> & {
34
- data: TreeDataItem[] | TreeDataItem
34
+ data: Array<TreeDataItem> | TreeDataItem
35
35
  initialSelectedItemId?: string
36
36
  onSelectChange?: (item: TreeDataItem | undefined) => void
37
37
  expandAll?: boolean
38
+ initialExpandedItemIds?: Array<string>
38
39
  defaultNodeIcon?: any
39
40
  defaultLeafIcon?: any
40
41
  onDocumentDrag?: (sourceItem: TreeDataItem, targetItem: TreeDataItem) => void
@@ -47,6 +48,7 @@ const TreeView = React.forwardRef<HTMLDivElement, TreeProps>(
47
48
  initialSelectedItemId,
48
49
  onSelectChange,
49
50
  expandAll,
51
+ initialExpandedItemIds,
50
52
  defaultLeafIcon,
51
53
  defaultNodeIcon,
52
54
  className,
@@ -88,14 +90,17 @@ const TreeView = React.forwardRef<HTMLDivElement, TreeProps>(
88
90
  )
89
91
 
90
92
  const expandedItemIds = React.useMemo(() => {
91
- if (!initialSelectedItemId) {
92
- return [] as string[]
93
+ if (initialExpandedItemIds) {
94
+ return initialExpandedItemIds
95
+ }
96
+ if (!initialSelectedItemId && !expandAll) {
97
+ return [] as Array<string>
93
98
  }
94
99
 
95
- const ids: string[] = []
100
+ const ids: Array<string> = []
96
101
 
97
102
  function walkTreeItems(
98
- items: TreeDataItem[] | TreeDataItem,
103
+ items: Array<TreeDataItem> | TreeDataItem,
99
104
  targetId: string,
100
105
  ) {
101
106
  if (items instanceof Array) {
@@ -115,7 +120,7 @@ const TreeView = React.forwardRef<HTMLDivElement, TreeProps>(
115
120
 
116
121
  walkTreeItems(data, initialSelectedItemId)
117
122
  return ids
118
- }, [data, expandAll, initialSelectedItemId])
123
+ }, [data, expandAll, initialSelectedItemId, initialExpandedItemIds])
119
124
 
120
125
  return (
121
126
  <div className={cn('overflow-hidden relative p-2', className)}>
@@ -147,7 +152,7 @@ TreeView.displayName = 'TreeView'
147
152
  type TreeItemProps = TreeProps & {
148
153
  selectedItemId?: string
149
154
  handleSelectChange: (item: TreeDataItem | undefined) => void
150
- expandedItemIds: string[]
155
+ expandedItemIds: Array<string>
151
156
  defaultNodeIcon?: any
152
157
  defaultLeafIcon?: any
153
158
  handleDragStart?: (item: TreeDataItem) => void
@@ -226,7 +231,7 @@ const TreeNode = ({
226
231
  }: {
227
232
  item: TreeDataItem
228
233
  handleSelectChange: (item: TreeDataItem | undefined) => void
229
- expandedItemIds: string[]
234
+ expandedItemIds: Array<string>
230
235
  selectedItemId?: string
231
236
  defaultNodeIcon?: any
232
237
  defaultLeafIcon?: any
@@ -294,7 +299,7 @@ const TreeNode = ({
294
299
  isOpen={value.includes(item.id)}
295
300
  default={defaultNodeIcon}
296
301
  />
297
- <span className="text-sm truncate">{item.name}</span>
302
+ <span className="text-md truncate">{item.name}</span>
298
303
  <TreeActions isSelected={selectedItemId === item.id}>
299
304
  {item.actions}
300
305
  </TreeActions>
@@ -401,7 +406,7 @@ const TreeLeaf = React.forwardRef<
401
406
  isSelected={selectedItemId === item.id}
402
407
  default={defaultLeafIcon}
403
408
  />
404
- <span className="flex-grow text-sm truncate">{item.name}</span>
409
+ <span className="flex-grow text-md truncate">{item.name}</span>
405
410
  <TreeActions isSelected={selectedItemId === item.id}>
406
411
  {item.actions}
407
412
  </TreeActions>
@@ -438,7 +443,7 @@ const AccordionContent = React.forwardRef<
438
443
  <AccordionPrimitive.Content
439
444
  ref={ref}
440
445
  className={cn(
441
- 'overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down',
446
+ 'overflow-hidden text-md transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down',
442
447
  className,
443
448
  )}
444
449
  {...props}
@@ -0,0 +1,114 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { resolve } from 'node:path'
3
+
4
+ import {
5
+ addToApp,
6
+ createDefaultEnvironment,
7
+ createMemoryEnvironment,
8
+ recursivelyGatherFiles,
9
+ } from '@tanstack/cta-engine'
10
+
11
+ import { createAppWrapper } from './create-app-wrapper.js'
12
+
13
+ import { getProjectPath } from '@/engine-handling/server-environment.js'
14
+ import {
15
+ cleanUpFileArray,
16
+ cleanUpFiles,
17
+ } from '@/engine-handling/file-helpers.js'
18
+
19
+ export async function addToAppWrapper(
20
+ addOns: Array<string>,
21
+ opts: {
22
+ dryRun?: boolean
23
+ stream?: boolean
24
+ },
25
+ ) {
26
+ const projectPath = getProjectPath()
27
+
28
+ const persistedOptions = JSON.parse(
29
+ readFileSync(resolve(projectPath, '.cta.json')),
30
+ )
31
+
32
+ const newAddons: Array<string> = []
33
+ for (const addOn of addOns) {
34
+ if (!persistedOptions.existingAddOns.includes(addOn)) {
35
+ newAddons.push(addOn)
36
+ }
37
+ }
38
+
39
+ if (newAddons.length === 0) {
40
+ return await createAppWrapper(persistedOptions, opts)
41
+ }
42
+
43
+ async function createEnvironment() {
44
+ if (opts.dryRun) {
45
+ const { environment, output } = createMemoryEnvironment(projectPath)
46
+ environment.writeFile(
47
+ resolve(projectPath, '.cta.json'),
48
+ JSON.stringify(persistedOptions, null, 2),
49
+ )
50
+
51
+ const localFiles = await cleanUpFiles(
52
+ await recursivelyGatherFiles(projectPath, false),
53
+ )
54
+ for (const file of Object.keys(localFiles)) {
55
+ environment.writeFile(resolve(projectPath, file), localFiles[file])
56
+ }
57
+ return { environment, output }
58
+ }
59
+ return {
60
+ environment: createDefaultEnvironment(),
61
+ output: { files: {}, deletedFiles: [], commands: [] },
62
+ }
63
+ }
64
+
65
+ const { environment, output } = await createEnvironment()
66
+
67
+ if (opts.stream) {
68
+ return new ReadableStream({
69
+ start(controller) {
70
+ environment.startStep = ({ id, type, message }) => {
71
+ controller.enqueue(
72
+ new TextEncoder().encode(
73
+ JSON.stringify({
74
+ msgType: 'start',
75
+ id,
76
+ type,
77
+ message,
78
+ }) + '\n',
79
+ ),
80
+ )
81
+ }
82
+ environment.finishStep = (id, message) => {
83
+ controller.enqueue(
84
+ new TextEncoder().encode(
85
+ JSON.stringify({
86
+ msgType: 'finish',
87
+ id,
88
+ message,
89
+ }) + '\n',
90
+ ),
91
+ )
92
+ }
93
+
94
+ environment.startRun()
95
+ addToApp(environment, newAddons, projectPath, {
96
+ forced: true,
97
+ }).then(() => {
98
+ environment.finishRun()
99
+ controller.close()
100
+ })
101
+ },
102
+ })
103
+ } else {
104
+ environment.startRun()
105
+ await addToApp(environment, newAddons, projectPath, {
106
+ forced: true,
107
+ })
108
+ environment.finishRun()
109
+
110
+ output.files = cleanUpFiles(output.files, projectPath)
111
+ output.deletedFiles = cleanUpFileArray(output.deletedFiles, projectPath)
112
+ return output
113
+ }
114
+ }
@@ -0,0 +1,107 @@
1
+ import { resolve } from 'node:path'
2
+
3
+ import {
4
+ createApp,
5
+ createDefaultEnvironment,
6
+ createMemoryEnvironment,
7
+ finalizeAddOns,
8
+ getFrameworkById,
9
+ loadStarter,
10
+ } from '@tanstack/cta-engine'
11
+
12
+ import { registerFrameworks } from './framework-registration'
13
+
14
+ import { cleanUpFileArray, cleanUpFiles } from './file-helpers'
15
+ import { getApplicationMode, getProjectPath } from './server-environment'
16
+
17
+ import type { Options, SerializedOptions, Starter } from '@tanstack/cta-engine'
18
+
19
+ export async function createAppWrapper(
20
+ projectOptions: SerializedOptions,
21
+ opts: { dryRun?: boolean; stream?: boolean },
22
+ ) {
23
+ registerFrameworks()
24
+
25
+ const framework = getFrameworkById(projectOptions.framework)!
26
+
27
+ let starter: Starter | undefined
28
+ const addOns: Array<string> = [...projectOptions.chosenAddOns]
29
+ if (projectOptions.starter) {
30
+ starter = await loadStarter(projectOptions.starter)
31
+ for (const addOn of starter.dependsOn ?? []) {
32
+ addOns.push(addOn)
33
+ }
34
+ }
35
+ const chosenAddOns = await finalizeAddOns(
36
+ framework,
37
+ projectOptions.mode,
38
+ addOns,
39
+ )
40
+
41
+ const projectPath = getProjectPath()
42
+ const targetDir =
43
+ getApplicationMode() === 'add'
44
+ ? projectOptions.targetDir
45
+ : resolve(projectPath, projectOptions.projectName)
46
+
47
+ const options: Options = {
48
+ ...projectOptions,
49
+ targetDir,
50
+ starter,
51
+ framework,
52
+ chosenAddOns,
53
+ }
54
+
55
+ function createEnvironment() {
56
+ if (opts.dryRun) {
57
+ return createMemoryEnvironment(targetDir)
58
+ }
59
+ return {
60
+ environment: createDefaultEnvironment(),
61
+ output: { files: {}, deletedFiles: [], commands: [] },
62
+ }
63
+ }
64
+
65
+ const { environment, output } = createEnvironment()
66
+
67
+ if (opts.stream) {
68
+ return new ReadableStream({
69
+ start(controller) {
70
+ environment.startStep = ({ id, type, message }) => {
71
+ controller.enqueue(
72
+ new TextEncoder().encode(
73
+ JSON.stringify({
74
+ msgType: 'start',
75
+ id,
76
+ type,
77
+ message,
78
+ }) + '\n',
79
+ ),
80
+ )
81
+ }
82
+ environment.finishStep = (id, message) => {
83
+ controller.enqueue(
84
+ new TextEncoder().encode(
85
+ JSON.stringify({
86
+ msgType: 'finish',
87
+ id,
88
+ message,
89
+ }) + '\n',
90
+ ),
91
+ )
92
+ }
93
+
94
+ createApp(environment, options).then(() => {
95
+ controller.close()
96
+ })
97
+ },
98
+ })
99
+ } else {
100
+ await createApp(environment, options)
101
+
102
+ output.files = cleanUpFiles(output.files, targetDir)
103
+ output.deletedFiles = cleanUpFileArray(output.deletedFiles, targetDir)
104
+
105
+ return output
106
+ }
107
+ }
@@ -0,0 +1,25 @@
1
+ import { basename } from 'node:path'
2
+
3
+ export function cleanUpFiles(
4
+ files: Record<string, string>,
5
+ targetDir?: string,
6
+ ) {
7
+ return Object.keys(files).reduce<Record<string, string>>((acc, file) => {
8
+ const content = files[file].startsWith('base64::')
9
+ ? '<binary file>'
10
+ : files[file]
11
+ if (basename(file) !== '.cta.json') {
12
+ acc[targetDir ? file.replace(targetDir, '.') : file] = content
13
+ }
14
+ return acc
15
+ }, {})
16
+ }
17
+
18
+ export function cleanUpFileArray(files: Array<string>, targetDir?: string) {
19
+ return files.reduce<Array<string>>((acc, file) => {
20
+ if (basename(file) !== '.cta.json') {
21
+ acc.push(targetDir ? file.replace(targetDir, '.') : file)
22
+ }
23
+ return acc
24
+ }, [])
25
+ }
@@ -0,0 +1,11 @@
1
+ import { register as registerReactCra } from '@tanstack/cta-framework-react-cra'
2
+ import { register as registerSolid } from '@tanstack/cta-framework-solid'
3
+
4
+ let registered = false
5
+
6
+ export function registerFrameworks() {
7
+ if (registered) return
8
+ registerReactCra()
9
+ registerSolid()
10
+ registered = true
11
+ }
@@ -0,0 +1,93 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { basename, resolve } from 'node:path'
3
+
4
+ import {
5
+ createSerializedOptionsFromPersisted,
6
+ getAllAddOns,
7
+ getFrameworkById,
8
+ recursivelyGatherFiles,
9
+ } from '@tanstack/cta-engine'
10
+
11
+ import { cleanUpFiles } from './file-helpers.js'
12
+ import { createAppWrapper } from './create-app-wrapper.js'
13
+ import { registerFrameworks } from './framework-registration.js'
14
+ import {
15
+ getApplicationMode,
16
+ getProjectOptions,
17
+ getProjectPath,
18
+ } from './server-environment.js'
19
+
20
+ import type { SerializedOptions } from '@tanstack/cta-engine'
21
+
22
+ export async function generateInitialPayload() {
23
+ registerFrameworks()
24
+
25
+ const projectPath = getProjectPath()
26
+ const applicationMode = getApplicationMode()
27
+
28
+ const localFiles =
29
+ applicationMode === 'add'
30
+ ? await cleanUpFiles(await recursivelyGatherFiles(projectPath, false))
31
+ : {}
32
+
33
+ function getSerializedOptions() {
34
+ if (applicationMode === 'setup') {
35
+ const projectOptions = getProjectOptions()
36
+ return {
37
+ ...projectOptions,
38
+ framework: projectOptions.framework || 'react-cra',
39
+ projectName: projectOptions.projectName || basename(projectPath),
40
+ mode: projectOptions.mode || 'file-router',
41
+ typescript: projectOptions.typescript || true,
42
+ tailwind: projectOptions.tailwind || true,
43
+ git: projectOptions.git || true,
44
+ } as SerializedOptions
45
+ } else {
46
+ const persistedOptions = JSON.parse(
47
+ readFileSync(resolve(projectPath, '.cta.json')),
48
+ )
49
+ return createSerializedOptionsFromPersisted(persistedOptions)
50
+ }
51
+ }
52
+
53
+ const serializedOptions = getSerializedOptions()
54
+
55
+ const output = await createAppWrapper(serializedOptions, {
56
+ dryRun: true,
57
+ })
58
+
59
+ const framework = await getFrameworkById(serializedOptions.framework)
60
+
61
+ const codeRouter = getAllAddOns(framework!, 'code-router').map((addOn) => ({
62
+ id: addOn.id,
63
+ name: addOn.name,
64
+ description: addOn.description,
65
+ type: addOn.type,
66
+ smallLogo: addOn.smallLogo,
67
+ logo: addOn.logo,
68
+ link: addOn.link,
69
+ dependsOn: addOn.dependsOn,
70
+ }))
71
+
72
+ const fileRouter = getAllAddOns(framework!, 'file-router').map((addOn) => ({
73
+ id: addOn.id,
74
+ name: addOn.name,
75
+ description: addOn.description,
76
+ type: addOn.type,
77
+ smallLogo: addOn.smallLogo,
78
+ logo: addOn.logo,
79
+ link: addOn.link,
80
+ dependsOn: addOn.dependsOn,
81
+ }))
82
+
83
+ return {
84
+ applicationMode,
85
+ localFiles,
86
+ addOns: {
87
+ 'code-router': codeRouter,
88
+ 'file-router': fileRouter,
89
+ },
90
+ options: serializedOptions,
91
+ output,
92
+ }
93
+ }