@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
package/src/types.d.ts ADDED
@@ -0,0 +1,87 @@
1
+ import type { Mode, StatusStepType } from '@tanstack/cta-engine'
2
+
3
+ export type ApplicationMode = 'add' | 'setup' | 'none'
4
+
5
+ export type StarterInfo = {
6
+ url: string
7
+ id: string
8
+ name: string
9
+ description: string
10
+ version: string
11
+ author: string
12
+ license: string
13
+ mode: Mode
14
+ typescript: boolean
15
+ tailwind: boolean
16
+ banner?: string
17
+ dependsOn?: Array<string>
18
+ }
19
+
20
+ // Files
21
+
22
+ export type DryRunOutput = {
23
+ files: Record<string, string>
24
+ commands: Array<{
25
+ command: string
26
+ args: Array<string>
27
+ }>
28
+ deletedFiles: Array<string>
29
+ }
30
+
31
+ export type AddOnInfo = {
32
+ id: string
33
+ name: string
34
+ description: string
35
+ type: 'add-on' | 'example' | 'starter' | 'toolchain'
36
+ modes: Array<'code-router' | 'file-router'>
37
+ smallLogo?: string
38
+ logo?: string
39
+ link: string
40
+ dependsOn?: Array<string>
41
+ }
42
+
43
+ export type FileClass =
44
+ | 'unchanged'
45
+ | 'added'
46
+ | 'modified'
47
+ | 'deleted'
48
+ | 'overwritten'
49
+
50
+ export type FileTreeItem = TreeDataItem & {
51
+ contents: string
52
+ fullPath: string
53
+ fileClass: FileClass | undefined
54
+ originalFile?: string
55
+ modifiedFile?: string
56
+ }
57
+
58
+ export type InitialData = {
59
+ options: SerializedOptions
60
+ output: GeneratorOutput
61
+ localFiles: Record<string, string>
62
+ addOns: {
63
+ 'code-router': Array<AddOnInfo>
64
+ 'file-router': Array<AddOnInfo>
65
+ }
66
+ applicationMode: ApplicationMode
67
+ }
68
+
69
+ export type EventItem = {
70
+ msgType: 'start'
71
+ id: string
72
+ type: StatusStepType
73
+ message: string
74
+ }
75
+ export type EventFinish = {
76
+ msgType: 'finish'
77
+ id: string
78
+ message: string
79
+ }
80
+
81
+ export type StreamEvent = EventItem | EventFinish
82
+
83
+ export type StreamItem = {
84
+ id: string
85
+ icon: typeof FileIcon
86
+ message: string
87
+ }
@@ -0,0 +1,222 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import type { AddOnInfo } from '@/types'
4
+
5
+ import { getAddOnStatus } from '@/store/add-ons'
6
+
7
+ describe('getAddOnStatus', () => {
8
+ it('everything should be enabled if nothing is selected', () => {
9
+ const addOnStatus = getAddOnStatus(
10
+ [
11
+ {
12
+ id: 'add-on-1',
13
+ dependsOn: ['add-on-2'],
14
+ },
15
+ {
16
+ id: 'add-on-2',
17
+ },
18
+ ] as unknown as Array<AddOnInfo>,
19
+ [],
20
+ [],
21
+ )
22
+ expect(addOnStatus).toEqual({
23
+ 'add-on-1': {
24
+ selected: false,
25
+ enabled: true,
26
+ },
27
+ 'add-on-2': {
28
+ selected: false,
29
+ enabled: true,
30
+ },
31
+ })
32
+ })
33
+
34
+ it('should handle a single add-on', () => {
35
+ const addOnStatus = getAddOnStatus(
36
+ [
37
+ {
38
+ id: 'add-on-1',
39
+ dependsOn: [],
40
+ },
41
+ ] as unknown as Array<AddOnInfo>,
42
+ ['add-on-1'],
43
+ [],
44
+ )
45
+ expect(addOnStatus).toEqual({
46
+ 'add-on-1': {
47
+ selected: true,
48
+ enabled: true,
49
+ },
50
+ })
51
+ })
52
+
53
+ it('should handle a depended-on add-on', () => {
54
+ const addOnStatus = getAddOnStatus(
55
+ [
56
+ {
57
+ id: 'add-on-1',
58
+ dependsOn: ['add-on-2'],
59
+ },
60
+ {
61
+ id: 'add-on-2',
62
+ dependsOn: [],
63
+ },
64
+ ] as unknown as Array<AddOnInfo>,
65
+ ['add-on-1'],
66
+ [],
67
+ )
68
+ expect(addOnStatus).toEqual({
69
+ 'add-on-1': {
70
+ selected: true,
71
+ enabled: true,
72
+ },
73
+ 'add-on-2': {
74
+ selected: true,
75
+ enabled: false,
76
+ },
77
+ })
78
+ })
79
+
80
+ it('should handle a selected depended-on add-on', () => {
81
+ const addOnStatus = getAddOnStatus(
82
+ [
83
+ {
84
+ id: 'add-on-1',
85
+ dependsOn: ['add-on-2'],
86
+ },
87
+ {
88
+ id: 'add-on-2',
89
+ dependsOn: [],
90
+ },
91
+ ] as unknown as Array<AddOnInfo>,
92
+ ['add-on-1', 'add-on-2'],
93
+ [],
94
+ )
95
+ expect(addOnStatus).toEqual({
96
+ 'add-on-1': {
97
+ selected: true,
98
+ enabled: true,
99
+ },
100
+ 'add-on-2': {
101
+ selected: true,
102
+ enabled: false,
103
+ },
104
+ })
105
+ })
106
+
107
+ it('should handle a selected depended-on add-on', () => {
108
+ const addOnStatus = getAddOnStatus(
109
+ [
110
+ {
111
+ id: 'add-on-1',
112
+ dependsOn: ['add-on-2'],
113
+ },
114
+ {
115
+ id: 'add-on-2',
116
+ dependsOn: [],
117
+ },
118
+ ] as unknown as Array<AddOnInfo>,
119
+ ['add-on-2'],
120
+ [],
121
+ )
122
+ expect(addOnStatus).toEqual({
123
+ 'add-on-1': {
124
+ selected: false,
125
+ enabled: true,
126
+ },
127
+ 'add-on-2': {
128
+ selected: true,
129
+ enabled: true,
130
+ },
131
+ })
132
+ })
133
+
134
+ it('wont cycle', () => {
135
+ const addOnStatus = getAddOnStatus(
136
+ [
137
+ {
138
+ id: 'add-on-1',
139
+ dependsOn: ['add-on-2'],
140
+ },
141
+ {
142
+ id: 'add-on-2',
143
+ dependsOn: ['add-on-1'],
144
+ },
145
+ ] as unknown as Array<AddOnInfo>,
146
+ ['add-on-1'],
147
+ [],
148
+ )
149
+ expect(addOnStatus).toEqual({
150
+ 'add-on-1': {
151
+ selected: true,
152
+ enabled: false,
153
+ },
154
+ 'add-on-2': {
155
+ selected: true,
156
+ enabled: false,
157
+ },
158
+ })
159
+ })
160
+
161
+ it('should handle original add-ons', () => {
162
+ const addOnStatus = getAddOnStatus(
163
+ [
164
+ {
165
+ id: 'add-on-1',
166
+ dependsOn: ['add-on-2'],
167
+ },
168
+ {
169
+ id: 'add-on-2',
170
+ dependsOn: [],
171
+ },
172
+ ] as unknown as Array<AddOnInfo>,
173
+ ['add-on-1'],
174
+ ['add-on-2'],
175
+ )
176
+ expect(addOnStatus).toEqual({
177
+ 'add-on-1': {
178
+ selected: true,
179
+ enabled: true,
180
+ },
181
+ 'add-on-2': {
182
+ selected: true,
183
+ enabled: false,
184
+ },
185
+ })
186
+ })
187
+
188
+ it('should handle original add-ons with dependencies', () => {
189
+ const addOnStatus = getAddOnStatus(
190
+ [
191
+ {
192
+ id: 'add-on-1',
193
+ dependsOn: ['add-on-2'],
194
+ },
195
+ {
196
+ id: 'add-on-2',
197
+ dependsOn: ['add-on-3'],
198
+ },
199
+ {
200
+ id: 'add-on-3',
201
+ dependsOn: [],
202
+ },
203
+ ] as unknown as Array<AddOnInfo>,
204
+ ['add-on-1'],
205
+ ['add-on-2'],
206
+ )
207
+ expect(addOnStatus).toEqual({
208
+ 'add-on-1': {
209
+ selected: true,
210
+ enabled: true,
211
+ },
212
+ 'add-on-2': {
213
+ selected: true,
214
+ enabled: false,
215
+ },
216
+ 'add-on-3': {
217
+ selected: true,
218
+ enabled: false,
219
+ },
220
+ })
221
+ })
222
+ })
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from 'vitest/config'
2
+ import tsconfigPaths from 'vite-tsconfig-paths'
3
+
4
+ export default defineConfig({
5
+ plugins: [tsconfigPaths()],
6
+ })
package/.cursorrules DELETED
@@ -1,7 +0,0 @@
1
- # shadcn instructions
2
-
3
- Use the latest version of Shadcn to install new components, like this command to add a button component:
4
-
5
- ```bash
6
- pnpx shadcn@latest add button
7
- ```
@@ -1,13 +0,0 @@
1
- import { Link } from "@tanstack/react-router";
2
-
3
- export default function Header() {
4
- return (
5
- <header className="p-2 flex gap-2 bg-white text-black justify-between">
6
- <nav className="flex flex-row">
7
- <div className="px-2 font-bold">
8
- <Link to="/">Home</Link>
9
- </div>
10
- </nav>
11
- </header>
12
- );
13
- }
@@ -1,149 +0,0 @@
1
- import { useState } from 'react'
2
-
3
- import { Checkbox } from '@/components/ui/checkbox'
4
- import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
5
-
6
- import { runCreateApp } from '@/lib/server-fns'
7
- import FileViewer from './file-viewer'
8
- import FileTree from './file-tree'
9
-
10
- import type { Mode } from '@tanstack/cta-engine'
11
-
12
- export default function AppliedAddOn({
13
- projectPath,
14
- output: originalOutput,
15
- addOnInfo,
16
- outputWithoutAddon: originalOutputWithoutAddon,
17
- originalOptions,
18
- addOns,
19
- }: {
20
- projectPath: string
21
- output: {
22
- files: Record<string, string>
23
- }
24
- outputWithoutAddon: {
25
- files: Record<string, string>
26
- }
27
- addOnInfo: {
28
- templates: Array<string>
29
- }
30
- originalOptions: {
31
- existingAddOns: Array<string>
32
- mode: Mode
33
- }
34
- addOns: Record<string, Array<any>>
35
- }) {
36
- const [selectedFile, setSelectedFile] = useState<string | null>(null)
37
-
38
- const [options, setOptions] = useState(originalOptions)
39
- const [output, setOutput] = useState(originalOutput)
40
- const [outputWithoutAddon, setOutputWithoutAddon] = useState(
41
- originalOutputWithoutAddon,
42
- )
43
- const [selectedAddOns, setSelectedAddOns] = useState<Array<string>>([])
44
-
45
- async function updateOptions(
46
- updatedOptions: Partial<typeof options>,
47
- updatedAddOns: Array<string> = [],
48
- ) {
49
- const newMode = updatedOptions.mode || options.mode
50
- const existingAddOns = [
51
- ...(originalOptions.existingAddOns || []),
52
- ...(updatedAddOns || []),
53
- ].filter((id) => addOns[newMode as Mode].some((addOn) => addOn.id === id))
54
-
55
- const newOptions = {
56
- ...options,
57
- ...updatedOptions,
58
- existingAddOns,
59
- }
60
- setOptions(newOptions)
61
- const [newOutput, newOutputWithoutAddon] = await Promise.all([
62
- runCreateApp({
63
- data: { withAddOn: true, options: newOptions },
64
- }),
65
- runCreateApp({
66
- data: { withAddOn: false, options: newOptions },
67
- }),
68
- ])
69
- setOutput(newOutput)
70
- setOutputWithoutAddon(newOutputWithoutAddon)
71
- }
72
-
73
- return (
74
- <div>
75
- <div className="flex flex-row items-center mb-5">
76
- <ToggleGroup
77
- type="single"
78
- value={options.mode}
79
- onValueChange={(v: string) => {
80
- if (v) {
81
- updateOptions(
82
- {
83
- mode: v as Mode,
84
- },
85
- selectedAddOns,
86
- )
87
- }
88
- }}
89
- >
90
- <ToggleGroupItem
91
- value="code-router"
92
- disabled={!addOnInfo.templates.includes('code-router')}
93
- >
94
- Code Router
95
- </ToggleGroupItem>
96
- <ToggleGroupItem
97
- value="file-router"
98
- disabled={!addOnInfo.templates.includes('file-router')}
99
- >
100
- File Router
101
- </ToggleGroupItem>
102
- </ToggleGroup>
103
- <div className="flex flex-row ml-5 flex-wrap">
104
- {addOns[options.mode as Mode].map((addOn) => (
105
- <div key={addOn.name} className="mr-2 flex items-center">
106
- <Checkbox
107
- id={addOn.id}
108
- checked={
109
- originalOptions.existingAddOns.includes(addOn.id) ||
110
- selectedAddOns.includes(addOn.id)
111
- }
112
- disabled={originalOptions.existingAddOns.includes(addOn.id)}
113
- onClick={() => {
114
- let updatedAddOns = selectedAddOns.includes(addOn.id)
115
- ? selectedAddOns.filter((id) => id !== addOn.id)
116
- : [...selectedAddOns, addOn.id]
117
- setSelectedAddOns(updatedAddOns)
118
- updateOptions({}, updatedAddOns)
119
- }}
120
- />
121
- <label htmlFor={addOn.id} className="ml-2">
122
- {addOn.name}
123
- </label>
124
- </div>
125
- ))}
126
- </div>
127
- </div>
128
- <div className="flex flex-row">
129
- <FileTree
130
- prefix={projectPath}
131
- tree={output.files}
132
- originalTree={outputWithoutAddon.files}
133
- onFileSelected={(file) => {
134
- setSelectedFile(file)
135
- }}
136
- />
137
- <div className="max-w-3/4 w-3/4 pl-2">
138
- {selectedFile ? (
139
- <FileViewer
140
- filePath={selectedFile}
141
- originalFile={outputWithoutAddon.files[selectedFile]}
142
- modifiedFile={output.files[selectedFile]}
143
- />
144
- ) : null}
145
- </div>
146
- </div>
147
- </div>
148
- )
149
- }
@@ -1,78 +0,0 @@
1
- import { createServerFn } from '@tanstack/react-start'
2
- import { readFileSync } from 'node:fs'
3
- import { basename, resolve } from 'node:path'
4
-
5
- import { getAllAddOns, createMemoryEnvironment } from '@tanstack/cta-engine'
6
- import { createAppOptionsFromPersisted } from '@tanstack/cta-custom-add-on'
7
-
8
- import { createApp } from '@tanstack/cta-engine'
9
-
10
- import type { AddOn, Mode, PersistedOptions } from '@tanstack/cta-engine'
11
-
12
- export const getAddons = createServerFn({
13
- method: 'GET',
14
- })
15
- .validator((data: unknown) => {
16
- return data as { platform: string; mode: Mode }
17
- })
18
- .handler(({ data: { platform, mode } }) => {
19
- return getAllAddOns(platform, mode)
20
- })
21
-
22
- export const getAddonInfo = createServerFn({
23
- method: 'GET',
24
- }).handler(async () => {
25
- const addOnInfo = readFileSync(
26
- resolve(process.env.PROJECT_PATH, 'add-on.json'),
27
- )
28
- return JSON.parse(addOnInfo.toString())
29
- })
30
-
31
- export const getOriginalOptions = createServerFn({
32
- method: 'GET',
33
- }).handler(async () => {
34
- const addOnInfo = readFileSync(resolve(process.env.PROJECT_PATH, '.cta.json'))
35
- return JSON.parse(addOnInfo.toString()) as PersistedOptions
36
- })
37
-
38
- export const runCreateApp = createServerFn({
39
- method: 'POST',
40
- })
41
- .validator((data: unknown) => {
42
- return data as { withAddOn: boolean; options: PersistedOptions }
43
- })
44
- .handler(
45
- async ({
46
- data: { withAddOn, options: persistedOptions },
47
- }: {
48
- data: { withAddOn: boolean; options: PersistedOptions }
49
- }) => {
50
- const { output, environment } = createMemoryEnvironment()
51
- const options = await createAppOptionsFromPersisted(persistedOptions)
52
- options.chosenAddOns = withAddOn
53
- ? [...options.chosenAddOns, (await getAddonInfo()) as AddOn]
54
- : [...options.chosenAddOns]
55
- await createApp(
56
- {
57
- ...options,
58
- },
59
- {
60
- silent: true,
61
- environment,
62
- cwd: process.env.PROJECT_PATH,
63
- },
64
- )
65
-
66
- output.files = Object.keys(output.files).reduce<Record<string, string>>(
67
- (acc, file) => {
68
- if (basename(file) !== '.cta.json') {
69
- acc[file] = output.files[file]
70
- }
71
- return acc
72
- },
73
- {},
74
- )
75
-
76
- return output
77
- },
78
- )
@@ -1,11 +0,0 @@
1
- import { createAPIFileRoute } from '@tanstack/react-start/api'
2
-
3
- export const APIRoute = createAPIFileRoute('/api/demo-names')({
4
- GET: async ({ request }) => {
5
- return new Response(JSON.stringify(['Alice', 'Bob', 'Charlie']), {
6
- headers: {
7
- 'Content-Type': 'application/json',
8
- },
9
- })
10
- },
11
- })
@@ -1,28 +0,0 @@
1
- import { createFileRoute } from '@tanstack/react-router'
2
- import { useQuery } from '@tanstack/react-query'
3
-
4
- export const Route = createFileRoute('/demo/tanstack-query')({
5
- component: TanStackQueryDemo,
6
- })
7
-
8
- function TanStackQueryDemo() {
9
- const { data } = useQuery({
10
- queryKey: ['people'],
11
- queryFn: () =>
12
- fetch('https://swapi.dev/api/people')
13
- .then((res) => res.json())
14
- .then((d) => d.results as { name: string }[]),
15
- initialData: [],
16
- })
17
-
18
- return (
19
- <div className="p-4">
20
- <h1 className="text-2xl mb-4">People list from Swapi</h1>
21
- <ul>
22
- {data.map((person) => (
23
- <li key={person.name}>{person.name}</li>
24
- ))}
25
- </ul>
26
- </div>
27
- )
28
- }