@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.
- package/lib/index.ts +16 -7
- package/lib-dist/index.d.ts +6 -1
- package/lib-dist/index.js +6 -3
- package/package.json +19 -7
- package/public/tailwind.svg +1 -0
- package/public/tanstack.png +0 -0
- package/public/typescript.svg +1 -0
- package/src/components/StatusList.tsx +22 -0
- package/src/components/add-on-info-dialog.tsx +39 -0
- package/src/components/cta-sidebar.tsx +55 -0
- package/src/components/custom-add-on-dialog.tsx +79 -0
- package/src/components/file-navigator.tsx +205 -0
- package/src/components/file-tree.tsx +18 -60
- package/src/components/file-viewer.tsx +11 -3
- package/src/components/sidebar-items/add-ons.tsx +91 -0
- package/src/components/sidebar-items/mode-selector.tsx +55 -0
- package/src/components/sidebar-items/project-name.tsx +29 -0
- package/src/components/sidebar-items/run-add-ons.tsx +71 -0
- package/src/components/sidebar-items/run-create-app.tsx +82 -0
- package/src/components/sidebar-items/starter.tsx +115 -0
- package/src/components/sidebar-items/typescript-switch.tsx +52 -0
- package/src/components/toaster.tsx +29 -0
- package/src/components/ui/button.tsx +21 -19
- package/src/components/ui/dialog.tsx +25 -20
- package/src/components/ui/dropdown-menu.tsx +255 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/label.tsx +22 -0
- package/src/components/ui/popover.tsx +46 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +137 -0
- package/src/components/ui/sidebar.tsx +726 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/sonner.tsx +23 -0
- package/src/components/ui/switch.tsx +29 -0
- package/src/components/ui/toggle-group.tsx +11 -11
- package/src/components/ui/toggle.tsx +15 -13
- package/src/components/ui/tooltip.tsx +61 -0
- package/src/components/ui/tree-view.tsx +17 -12
- package/src/engine-handling/add-to-app-wrapper.ts +114 -0
- package/src/engine-handling/create-app-wrapper.ts +107 -0
- package/src/engine-handling/file-helpers.ts +25 -0
- package/src/engine-handling/framework-registration.ts +11 -0
- package/src/engine-handling/generate-initial-payload.ts +93 -0
- package/src/engine-handling/server-environment.ts +13 -0
- package/src/file-classes.ts +54 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/hooks/use-streaming-status.ts +70 -0
- package/src/lib/api.ts +90 -0
- package/src/routeTree.gen.ts +4 -27
- package/src/routes/__root.tsx +36 -7
- package/src/routes/api/add-to-app.ts +21 -0
- package/src/routes/api/create-app.ts +21 -0
- package/src/routes/api/dry-run-add-to-app.ts +16 -0
- package/src/routes/api/dry-run-create-app.ts +16 -0
- package/src/routes/api/initial-payload.ts +10 -0
- package/src/routes/api/load-remote-add-on.ts +42 -0
- package/src/routes/api/load-starter.ts +47 -0
- package/src/routes/api/shutdown.ts +11 -0
- package/src/routes/index.tsx +3 -210
- package/src/store/add-ons.ts +81 -0
- package/src/store/project.ts +268 -0
- package/src/styles.css +47 -0
- package/src/types.d.ts +87 -0
- package/tests/store/add-ons.test.ts +222 -0
- package/vitest.config.ts +6 -0
- package/.cursorrules +0 -7
- package/src/components/Header.tsx +0 -13
- package/src/components/applied-add-on.tsx +0 -149
- package/src/lib/server-fns.ts +0 -78
- package/src/routes/api.demo-names.ts +0 -11
- package/src/routes/demo.tanstack-query.tsx +0 -28
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SerializedOptions } from '@tanstack/cta-engine'
|
|
2
|
+
|
|
3
|
+
export function getProjectPath(): string {
|
|
4
|
+
return process.env.CTA_PROJECT_PATH!
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function getApplicationMode(): 'add' | 'setup' {
|
|
8
|
+
return process.env.CTA_MODE as 'add' | 'setup'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getProjectOptions(): SerializedOptions {
|
|
12
|
+
return JSON.parse(process.env.CTA_OPTIONS!)
|
|
13
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { FileClass } from '@/types.js'
|
|
2
|
+
|
|
3
|
+
export const twClasses: Record<FileClass, string> = {
|
|
4
|
+
unchanged: 'text-gray-500',
|
|
5
|
+
added: 'text-green-500 font-bold',
|
|
6
|
+
modified: 'text-blue-500 italic',
|
|
7
|
+
deleted: 'text-red-500 line-through',
|
|
8
|
+
overwritten: 'text-red-700 underline',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type FileClassAndInfo = {
|
|
12
|
+
fileClass: FileClass
|
|
13
|
+
originalFile?: string
|
|
14
|
+
modifiedFile?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const getFileClass = (
|
|
18
|
+
file: string,
|
|
19
|
+
tree: Record<string, string>,
|
|
20
|
+
originalTree: Record<string, string>,
|
|
21
|
+
localTree: Record<string, string>,
|
|
22
|
+
deletedFiles: Array<string>,
|
|
23
|
+
): FileClassAndInfo => {
|
|
24
|
+
if (localTree[file]) {
|
|
25
|
+
if (deletedFiles.includes(file)) {
|
|
26
|
+
return { fileClass: 'deleted', originalFile: localTree[file] }
|
|
27
|
+
}
|
|
28
|
+
// We have a local file and it's in the new tree
|
|
29
|
+
if (tree[file]) {
|
|
30
|
+
// Our new tree has changed this file
|
|
31
|
+
if (localTree[file] !== tree[file]) {
|
|
32
|
+
// Was the local tree different from the original?
|
|
33
|
+
if (originalTree[file] && localTree[file] !== originalTree[file]) {
|
|
34
|
+
// Yes, it was overwritten
|
|
35
|
+
return {
|
|
36
|
+
fileClass: 'overwritten',
|
|
37
|
+
originalFile: localTree[file],
|
|
38
|
+
modifiedFile: tree[file],
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
// No, it just being modified
|
|
42
|
+
return {
|
|
43
|
+
fileClass: 'modified',
|
|
44
|
+
originalFile: localTree[file],
|
|
45
|
+
modifiedFile: tree[file],
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { fileClass: 'unchanged', modifiedFile: localTree[file] }
|
|
51
|
+
} else {
|
|
52
|
+
return { fileClass: 'added', modifiedFile: tree[file] }
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
const MOBILE_BREAKPOINT = 768
|
|
4
|
+
|
|
5
|
+
export function useIsMobile() {
|
|
6
|
+
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
|
7
|
+
|
|
8
|
+
React.useEffect(() => {
|
|
9
|
+
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
|
10
|
+
const onChange = () => {
|
|
11
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
12
|
+
}
|
|
13
|
+
mql.addEventListener("change", onChange)
|
|
14
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
15
|
+
return () => mql.removeEventListener("change", onChange)
|
|
16
|
+
}, [])
|
|
17
|
+
|
|
18
|
+
return !!isMobile
|
|
19
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
InfoIcon,
|
|
5
|
+
MessageCircleCodeIcon,
|
|
6
|
+
PackageIcon,
|
|
7
|
+
SquarePenIcon,
|
|
8
|
+
TerminalIcon,
|
|
9
|
+
} from 'lucide-react'
|
|
10
|
+
|
|
11
|
+
import type { StatusStepType } from '@tanstack/cta-engine'
|
|
12
|
+
|
|
13
|
+
import type { StreamEvent, StreamItem } from '@/types'
|
|
14
|
+
|
|
15
|
+
const iconMap: Record<StatusStepType, typeof InfoIcon> = {
|
|
16
|
+
file: SquarePenIcon,
|
|
17
|
+
command: TerminalIcon,
|
|
18
|
+
'package-manager': PackageIcon,
|
|
19
|
+
info: InfoIcon,
|
|
20
|
+
other: MessageCircleCodeIcon,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default function useStreamingStatus() {
|
|
24
|
+
const [streamItems, setStreamItems] = useState<Array<StreamItem>>([])
|
|
25
|
+
const [finished, setFinished] = useState(false)
|
|
26
|
+
|
|
27
|
+
const monitorStream = useCallback(async (res: Response) => {
|
|
28
|
+
setFinished(false)
|
|
29
|
+
const reader = res.body?.getReader()
|
|
30
|
+
const decoder = new TextDecoder()
|
|
31
|
+
|
|
32
|
+
let rawStream = ''
|
|
33
|
+
while (true) {
|
|
34
|
+
const result = await reader?.read()
|
|
35
|
+
if (result?.done) break
|
|
36
|
+
|
|
37
|
+
rawStream += decoder.decode(result?.value)
|
|
38
|
+
|
|
39
|
+
let currentId: string | undefined
|
|
40
|
+
const newStreamItems: Array<StreamItem> = []
|
|
41
|
+
for (const line of rawStream.split('\n')) {
|
|
42
|
+
if (line.startsWith('{') && line.endsWith('}')) {
|
|
43
|
+
const item = JSON.parse(line) as StreamEvent
|
|
44
|
+
if (item.msgType === 'start') {
|
|
45
|
+
if (currentId === item.id) {
|
|
46
|
+
newStreamItems[newStreamItems.length - 1].message = item.message
|
|
47
|
+
} else {
|
|
48
|
+
currentId = item.id
|
|
49
|
+
newStreamItems.push({
|
|
50
|
+
id: currentId,
|
|
51
|
+
icon: iconMap[item.type],
|
|
52
|
+
message: item.message,
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
if (newStreamItems.length > 0) {
|
|
57
|
+
newStreamItems[newStreamItems.length - 1].message = item.message
|
|
58
|
+
}
|
|
59
|
+
currentId = undefined
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
setStreamItems(newStreamItems)
|
|
64
|
+
}
|
|
65
|
+
setFinished(true)
|
|
66
|
+
return rawStream
|
|
67
|
+
}, [])
|
|
68
|
+
|
|
69
|
+
return { finished, streamItems, monitorStream }
|
|
70
|
+
}
|
package/src/lib/api.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { SerializedOptions } from '@tanstack/cta-engine'
|
|
2
|
+
|
|
3
|
+
import type { AddOnInfo, DryRunOutput, InitialData, StarterInfo } from '@/types'
|
|
4
|
+
|
|
5
|
+
export async function createAppStreaming(
|
|
6
|
+
options: SerializedOptions,
|
|
7
|
+
chosenAddOns: Array<string>,
|
|
8
|
+
projectStarter?: StarterInfo,
|
|
9
|
+
) {
|
|
10
|
+
return await fetch('/api/create-app', {
|
|
11
|
+
method: 'POST',
|
|
12
|
+
body: JSON.stringify({
|
|
13
|
+
options: {
|
|
14
|
+
...options,
|
|
15
|
+
chosenAddOns,
|
|
16
|
+
starter: projectStarter?.url || undefined,
|
|
17
|
+
},
|
|
18
|
+
}),
|
|
19
|
+
headers: {
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function addToAppStreaming(chosenAddOns: Array<string>) {
|
|
26
|
+
return await fetch('/api/add-to-app', {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
body: JSON.stringify({
|
|
29
|
+
addOns: chosenAddOns,
|
|
30
|
+
}),
|
|
31
|
+
headers: {
|
|
32
|
+
'Content-Type': 'application/json',
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function shutdown() {
|
|
38
|
+
return fetch('/api/shutdown', {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function loadRemoteAddOn(url: string) {
|
|
44
|
+
const response = await fetch(`/api/load-remote-add-on?url=${url}`)
|
|
45
|
+
return (await response.json()) as AddOnInfo | { error: string }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function loadRemoteStarter(url: string) {
|
|
49
|
+
const response = await fetch(`/api/load-starter?url=${url}`)
|
|
50
|
+
return (await response.json()) as StarterInfo | { error: string }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function loadInitialData() {
|
|
54
|
+
const payloadReq = await fetch('/api/initial-payload')
|
|
55
|
+
return (await payloadReq.json()) as InitialData
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function dryRunCreateApp(
|
|
59
|
+
options: SerializedOptions,
|
|
60
|
+
chosenAddOns: Array<string>,
|
|
61
|
+
projectStarter?: StarterInfo,
|
|
62
|
+
) {
|
|
63
|
+
const outputReq = await fetch('/api/dry-run-create-app', {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers: {
|
|
66
|
+
'Content-Type': 'application/json',
|
|
67
|
+
},
|
|
68
|
+
body: JSON.stringify({
|
|
69
|
+
options: {
|
|
70
|
+
...options,
|
|
71
|
+
chosenAddOns: chosenAddOns,
|
|
72
|
+
starter: projectStarter?.url,
|
|
73
|
+
},
|
|
74
|
+
}),
|
|
75
|
+
})
|
|
76
|
+
return outputReq.json() as Promise<DryRunOutput>
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function dryRunAddToApp(addOns: Array<string>) {
|
|
80
|
+
const outputReq = await fetch('/api/dry-run-add-to-app', {
|
|
81
|
+
method: 'POST',
|
|
82
|
+
headers: {
|
|
83
|
+
'Content-Type': 'application/json',
|
|
84
|
+
},
|
|
85
|
+
body: JSON.stringify({
|
|
86
|
+
addOns,
|
|
87
|
+
}),
|
|
88
|
+
})
|
|
89
|
+
return outputReq.json() as Promise<DryRunOutput>
|
|
90
|
+
}
|
package/src/routeTree.gen.ts
CHANGED
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
|
|
13
13
|
import { Route as rootRoute } from './routes/__root'
|
|
14
14
|
import { Route as IndexImport } from './routes/index'
|
|
15
|
-
import { Route as DemoTanstackQueryImport } from './routes/demo.tanstack-query'
|
|
16
15
|
|
|
17
16
|
// Create/Update Routes
|
|
18
17
|
|
|
@@ -22,12 +21,6 @@ const IndexRoute = IndexImport.update({
|
|
|
22
21
|
getParentRoute: () => rootRoute,
|
|
23
22
|
} as any)
|
|
24
23
|
|
|
25
|
-
const DemoTanstackQueryRoute = DemoTanstackQueryImport.update({
|
|
26
|
-
id: '/demo/tanstack-query',
|
|
27
|
-
path: '/demo/tanstack-query',
|
|
28
|
-
getParentRoute: () => rootRoute,
|
|
29
|
-
} as any)
|
|
30
|
-
|
|
31
24
|
// Populate the FileRoutesByPath interface
|
|
32
25
|
|
|
33
26
|
declare module '@tanstack/react-router' {
|
|
@@ -39,13 +32,6 @@ declare module '@tanstack/react-router' {
|
|
|
39
32
|
preLoaderRoute: typeof IndexImport
|
|
40
33
|
parentRoute: typeof rootRoute
|
|
41
34
|
}
|
|
42
|
-
'/demo/tanstack-query': {
|
|
43
|
-
id: '/demo/tanstack-query'
|
|
44
|
-
path: '/demo/tanstack-query'
|
|
45
|
-
fullPath: '/demo/tanstack-query'
|
|
46
|
-
preLoaderRoute: typeof DemoTanstackQueryImport
|
|
47
|
-
parentRoute: typeof rootRoute
|
|
48
|
-
}
|
|
49
35
|
}
|
|
50
36
|
}
|
|
51
37
|
|
|
@@ -53,37 +39,32 @@ declare module '@tanstack/react-router' {
|
|
|
53
39
|
|
|
54
40
|
export interface FileRoutesByFullPath {
|
|
55
41
|
'/': typeof IndexRoute
|
|
56
|
-
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
|
|
57
42
|
}
|
|
58
43
|
|
|
59
44
|
export interface FileRoutesByTo {
|
|
60
45
|
'/': typeof IndexRoute
|
|
61
|
-
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
|
|
62
46
|
}
|
|
63
47
|
|
|
64
48
|
export interface FileRoutesById {
|
|
65
49
|
__root__: typeof rootRoute
|
|
66
50
|
'/': typeof IndexRoute
|
|
67
|
-
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
|
|
68
51
|
}
|
|
69
52
|
|
|
70
53
|
export interface FileRouteTypes {
|
|
71
54
|
fileRoutesByFullPath: FileRoutesByFullPath
|
|
72
|
-
fullPaths: '/'
|
|
55
|
+
fullPaths: '/'
|
|
73
56
|
fileRoutesByTo: FileRoutesByTo
|
|
74
|
-
to: '/'
|
|
75
|
-
id: '__root__' | '/'
|
|
57
|
+
to: '/'
|
|
58
|
+
id: '__root__' | '/'
|
|
76
59
|
fileRoutesById: FileRoutesById
|
|
77
60
|
}
|
|
78
61
|
|
|
79
62
|
export interface RootRouteChildren {
|
|
80
63
|
IndexRoute: typeof IndexRoute
|
|
81
|
-
DemoTanstackQueryRoute: typeof DemoTanstackQueryRoute
|
|
82
64
|
}
|
|
83
65
|
|
|
84
66
|
const rootRouteChildren: RootRouteChildren = {
|
|
85
67
|
IndexRoute: IndexRoute,
|
|
86
|
-
DemoTanstackQueryRoute: DemoTanstackQueryRoute,
|
|
87
68
|
}
|
|
88
69
|
|
|
89
70
|
export const routeTree = rootRoute
|
|
@@ -96,15 +77,11 @@ export const routeTree = rootRoute
|
|
|
96
77
|
"__root__": {
|
|
97
78
|
"filePath": "__root.tsx",
|
|
98
79
|
"children": [
|
|
99
|
-
"/"
|
|
100
|
-
"/demo/tanstack-query"
|
|
80
|
+
"/"
|
|
101
81
|
]
|
|
102
82
|
},
|
|
103
83
|
"/": {
|
|
104
84
|
"filePath": "index.tsx"
|
|
105
|
-
},
|
|
106
|
-
"/demo/tanstack-query": {
|
|
107
|
-
"filePath": "demo.tanstack-query.tsx"
|
|
108
85
|
}
|
|
109
86
|
}
|
|
110
87
|
}
|
package/src/routes/__root.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
Outlet,
|
|
3
2
|
HeadContent,
|
|
3
|
+
Outlet,
|
|
4
4
|
Scripts,
|
|
5
5
|
createRootRouteWithContext,
|
|
6
6
|
} from '@tanstack/react-router'
|
|
@@ -9,10 +9,33 @@ import appCss from '../styles.css?url'
|
|
|
9
9
|
|
|
10
10
|
import type { QueryClient } from '@tanstack/react-query'
|
|
11
11
|
|
|
12
|
+
import {
|
|
13
|
+
SidebarProvider,
|
|
14
|
+
SidebarTrigger,
|
|
15
|
+
useSidebar,
|
|
16
|
+
} from '@/components/ui/sidebar'
|
|
17
|
+
import { Toaster } from '@/components/toaster'
|
|
18
|
+
|
|
19
|
+
import { AppSidebar } from '@/components/cta-sidebar'
|
|
20
|
+
|
|
12
21
|
interface MyRouterContext {
|
|
13
22
|
queryClient: QueryClient
|
|
14
23
|
}
|
|
15
24
|
|
|
25
|
+
function Content() {
|
|
26
|
+
const { open } = useSidebar()
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<main
|
|
30
|
+
className={
|
|
31
|
+
open ? 'w-full max-w-[calc(100%-370px)]' : 'w-full max-w-[100%]'
|
|
32
|
+
}
|
|
33
|
+
>
|
|
34
|
+
<SidebarTrigger className="m-2" />
|
|
35
|
+
<Outlet />
|
|
36
|
+
</main>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
16
39
|
export const Route = createRootRouteWithContext<MyRouterContext>()({
|
|
17
40
|
head: () => ({
|
|
18
41
|
meta: [
|
|
@@ -24,7 +47,7 @@ export const Route = createRootRouteWithContext<MyRouterContext>()({
|
|
|
24
47
|
content: 'width=device-width, initial-scale=1',
|
|
25
48
|
},
|
|
26
49
|
{
|
|
27
|
-
title: '
|
|
50
|
+
title: 'TanStack CTA',
|
|
28
51
|
},
|
|
29
52
|
],
|
|
30
53
|
links: [
|
|
@@ -35,11 +58,17 @@ export const Route = createRootRouteWithContext<MyRouterContext>()({
|
|
|
35
58
|
],
|
|
36
59
|
}),
|
|
37
60
|
|
|
38
|
-
component: () =>
|
|
39
|
-
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
61
|
+
component: () => {
|
|
62
|
+
return (
|
|
63
|
+
<RootDocument>
|
|
64
|
+
<SidebarProvider>
|
|
65
|
+
<AppSidebar />
|
|
66
|
+
<Content />
|
|
67
|
+
<Toaster />
|
|
68
|
+
</SidebarProvider>
|
|
69
|
+
</RootDocument>
|
|
70
|
+
)
|
|
71
|
+
},
|
|
43
72
|
})
|
|
44
73
|
|
|
45
74
|
function RootDocument({ children }: { children: React.ReactNode }) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createAPIFileRoute } from '@tanstack/react-start/api'
|
|
2
|
+
|
|
3
|
+
import { addToAppWrapper } from '@/engine-handling/add-to-app-wrapper'
|
|
4
|
+
|
|
5
|
+
export const APIRoute = createAPIFileRoute('/api/add-to-app')({
|
|
6
|
+
POST: async ({ request }) => {
|
|
7
|
+
const { addOns } = await request.json()
|
|
8
|
+
|
|
9
|
+
const stream = await addToAppWrapper(addOns, {
|
|
10
|
+
stream: true,
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
return new Response(stream as ReadableStream, {
|
|
14
|
+
headers: {
|
|
15
|
+
'Content-Type': 'text/event-stream',
|
|
16
|
+
'Cache-Control': 'no-cache',
|
|
17
|
+
Connection: 'keep-alive',
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
},
|
|
21
|
+
})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createAPIFileRoute } from '@tanstack/react-start/api'
|
|
2
|
+
|
|
3
|
+
import { createAppWrapper } from '@/engine-handling/create-app-wrapper'
|
|
4
|
+
|
|
5
|
+
export const APIRoute = createAPIFileRoute('/api/create-app')({
|
|
6
|
+
POST: async ({ request }) => {
|
|
7
|
+
const { options: serializedOptions } = await request.json()
|
|
8
|
+
|
|
9
|
+
const stream = await createAppWrapper(serializedOptions, {
|
|
10
|
+
stream: true,
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
return new Response(stream as ReadableStream, {
|
|
14
|
+
headers: {
|
|
15
|
+
'Content-Type': 'text/event-stream',
|
|
16
|
+
'Cache-Control': 'no-cache',
|
|
17
|
+
Connection: 'keep-alive',
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
},
|
|
21
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { json } from '@tanstack/react-start'
|
|
2
|
+
import { createAPIFileRoute } from '@tanstack/react-start/api'
|
|
3
|
+
|
|
4
|
+
import { addToAppWrapper } from '@/engine-handling/add-to-app-wrapper'
|
|
5
|
+
|
|
6
|
+
export const APIRoute = createAPIFileRoute('/api/dry-run-add-to-app')({
|
|
7
|
+
POST: async ({ request }) => {
|
|
8
|
+
const { addOns } = await request.json()
|
|
9
|
+
|
|
10
|
+
return json(
|
|
11
|
+
await addToAppWrapper(addOns, {
|
|
12
|
+
dryRun: true,
|
|
13
|
+
}),
|
|
14
|
+
)
|
|
15
|
+
},
|
|
16
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { json } from '@tanstack/react-start'
|
|
2
|
+
import { createAPIFileRoute } from '@tanstack/react-start/api'
|
|
3
|
+
|
|
4
|
+
import { createAppWrapper } from '@/engine-handling/create-app-wrapper'
|
|
5
|
+
|
|
6
|
+
export const APIRoute = createAPIFileRoute('/api/dry-run-create-app')({
|
|
7
|
+
POST: async ({ request }) => {
|
|
8
|
+
const { options: serializedOptions } = await request.json()
|
|
9
|
+
|
|
10
|
+
return json(
|
|
11
|
+
await createAppWrapper(serializedOptions, {
|
|
12
|
+
dryRun: true,
|
|
13
|
+
}),
|
|
14
|
+
)
|
|
15
|
+
},
|
|
16
|
+
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { json } from '@tanstack/react-start'
|
|
2
|
+
import { createAPIFileRoute } from '@tanstack/react-start/api'
|
|
3
|
+
|
|
4
|
+
import { generateInitialPayload } from '@/engine-handling/generate-initial-payload'
|
|
5
|
+
|
|
6
|
+
export const APIRoute = createAPIFileRoute('/api/initial-payload')({
|
|
7
|
+
GET: async () => {
|
|
8
|
+
return json(await generateInitialPayload())
|
|
9
|
+
},
|
|
10
|
+
})
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { json } from '@tanstack/react-start'
|
|
2
|
+
import { createAPIFileRoute } from '@tanstack/react-start/api'
|
|
3
|
+
|
|
4
|
+
import { AddOnCompiledSchema } from '@tanstack/cta-engine'
|
|
5
|
+
|
|
6
|
+
export const APIRoute = createAPIFileRoute('/api/load-remote-add-on')({
|
|
7
|
+
GET: async ({ request }) => {
|
|
8
|
+
const incomingUrl = new URL(request.url)
|
|
9
|
+
const url = incomingUrl.searchParams.get('url')
|
|
10
|
+
|
|
11
|
+
if (!url) {
|
|
12
|
+
return json({ error: 'url is required' }, { status: 400 })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const response = await fetch(url)
|
|
17
|
+
const data = await response.json()
|
|
18
|
+
|
|
19
|
+
const parsed = AddOnCompiledSchema.safeParse(data)
|
|
20
|
+
|
|
21
|
+
if (!parsed.success) {
|
|
22
|
+
return json({ error: 'Invalid add-on data' }, { status: 400 })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return json({
|
|
26
|
+
id: url,
|
|
27
|
+
name: parsed.data.name,
|
|
28
|
+
description: parsed.data.description,
|
|
29
|
+
version: parsed.data.version,
|
|
30
|
+
author: parsed.data.author,
|
|
31
|
+
license: parsed.data.license,
|
|
32
|
+
link: parsed.data.link,
|
|
33
|
+
smallLogo: parsed.data.smallLogo,
|
|
34
|
+
logo: parsed.data.logo,
|
|
35
|
+
type: parsed.data.type,
|
|
36
|
+
modes: parsed.data.modes,
|
|
37
|
+
})
|
|
38
|
+
} catch {
|
|
39
|
+
return json({ error: 'Failed to load add-on' }, { status: 500 })
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
})
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { json } from '@tanstack/react-start'
|
|
2
|
+
import { createAPIFileRoute } from '@tanstack/react-start/api'
|
|
3
|
+
|
|
4
|
+
import { StarterCompiledSchema } from '@tanstack/cta-engine'
|
|
5
|
+
|
|
6
|
+
export const APIRoute = createAPIFileRoute('/api/load-starter')({
|
|
7
|
+
GET: async ({ request }) => {
|
|
8
|
+
const incomingUrl = new URL(request.url)
|
|
9
|
+
const url = incomingUrl.searchParams.get('url')
|
|
10
|
+
|
|
11
|
+
if (!url) {
|
|
12
|
+
return json({ error: 'url is required' }, { status: 400 })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const response = await fetch(url)
|
|
17
|
+
const data = await response.json()
|
|
18
|
+
|
|
19
|
+
const parsed = StarterCompiledSchema.safeParse(data)
|
|
20
|
+
|
|
21
|
+
if (!parsed.success) {
|
|
22
|
+
return json({ error: 'Invalid starter data' }, { status: 400 })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return json({
|
|
26
|
+
url,
|
|
27
|
+
|
|
28
|
+
id: parsed.data.id,
|
|
29
|
+
name: parsed.data.name,
|
|
30
|
+
description: parsed.data.description,
|
|
31
|
+
version: parsed.data.version,
|
|
32
|
+
author: parsed.data.author,
|
|
33
|
+
license: parsed.data.license,
|
|
34
|
+
dependsOn: parsed.data.dependsOn,
|
|
35
|
+
|
|
36
|
+
mode: parsed.data.mode,
|
|
37
|
+
typescript: parsed.data.typescript,
|
|
38
|
+
tailwind: parsed.data.tailwind,
|
|
39
|
+
banner: parsed.data.banner
|
|
40
|
+
? url.replace('starter.json', parsed.data.banner)
|
|
41
|
+
: undefined,
|
|
42
|
+
})
|
|
43
|
+
} catch {
|
|
44
|
+
return json({ error: 'Failed to load starter' }, { status: 500 })
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { json } from '@tanstack/react-start'
|
|
2
|
+
import { createAPIFileRoute } from '@tanstack/react-start/api'
|
|
3
|
+
|
|
4
|
+
export const APIRoute = createAPIFileRoute('/api/shutdown')({
|
|
5
|
+
POST: () => {
|
|
6
|
+
setTimeout(() => {
|
|
7
|
+
process.exit(0)
|
|
8
|
+
}, 50)
|
|
9
|
+
return json({ shutdown: true })
|
|
10
|
+
},
|
|
11
|
+
})
|