@tanstack/cta-ui 0.10.0-alpha.26 → 0.10.0-alpha.27

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 (44) hide show
  1. package/README.md +20 -0
  2. package/dist/assets/index-DSKioOfX.css +1 -0
  3. package/dist/assets/index-DWTDdndE.js +213 -0
  4. package/dist/assets/index-DWTDdndE.js.map +1 -0
  5. package/dist/index.html +9 -3
  6. package/dist/logo-color-100w.png +0 -0
  7. package/index.html +7 -1
  8. package/lib/engine-handling/add-to-app-wrapper.ts +39 -15
  9. package/lib/engine-handling/create-app-wrapper.ts +17 -6
  10. package/lib/engine-handling/file-helpers.ts +4 -2
  11. package/lib/engine-handling/generate-initial-payload.ts +58 -51
  12. package/lib/index.ts +11 -1
  13. package/lib/types.d.ts +18 -11
  14. package/lib-dist/engine-handling/add-to-app-wrapper.d.ts +2 -0
  15. package/lib-dist/engine-handling/add-to-app-wrapper.js +14 -9
  16. package/lib-dist/engine-handling/create-app-wrapper.d.ts +2 -1
  17. package/lib-dist/engine-handling/create-app-wrapper.js +6 -4
  18. package/lib-dist/engine-handling/file-helpers.js +3 -2
  19. package/lib-dist/engine-handling/generate-initial-payload.d.ts +14 -22
  20. package/lib-dist/engine-handling/generate-initial-payload.js +44 -49
  21. package/lib-dist/index.d.ts +2 -0
  22. package/lib-dist/index.js +6 -1
  23. package/package.json +6 -4
  24. package/public/logo-color-100w.png +0 -0
  25. package/src/components/background-animation.tsx +229 -0
  26. package/src/components/cta-sidebar.tsx +28 -33
  27. package/src/components/file-navigator.tsx +72 -74
  28. package/src/components/header.tsx +31 -0
  29. package/src/components/sidebar-items/add-ons.tsx +48 -45
  30. package/src/components/sidebar-items/mode-selector.tsx +6 -4
  31. package/src/components/sidebar-items/project-name.tsx +4 -5
  32. package/src/components/sidebar-items/typescript-switch.tsx +3 -3
  33. package/src/components/startup-dialog.tsx +4 -6
  34. package/src/components/ui/switch.tsx +6 -6
  35. package/src/hooks/use-mounted.ts +9 -0
  36. package/src/hooks/use-preferred-reduced-motion.ts +27 -0
  37. package/src/index.tsx +24 -20
  38. package/src/store/project.ts +36 -20
  39. package/src/styles.css +90 -18
  40. package/src/types.d.ts +1 -1
  41. package/tailwind.config.cjs +47 -0
  42. package/dist/assets/index-D0-fpgzI.js +0 -223
  43. package/dist/assets/index-D0-fpgzI.js.map +0 -1
  44. package/dist/assets/index-D5brMzJg.css +0 -1
package/dist/index.html CHANGED
@@ -4,10 +4,16 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>TanStack CTA</title>
7
- <script type="module" crossorigin src="/assets/index-D0-fpgzI.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-D5brMzJg.css">
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="" />
9
+ <link
10
+ rel="stylesheet"
11
+ href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"
12
+ />
13
+ <script type="module" crossorigin src="/assets/index-DWTDdndE.js"></script>
14
+ <link rel="stylesheet" crossorigin href="/assets/index-DSKioOfX.css">
9
15
  </head>
10
- <body class="dark bg-black text-white dark">
16
+ <body class="dark">
11
17
  <div id="root"></div>
12
18
  </body>
13
19
  </html>
Binary file
package/index.html CHANGED
@@ -4,8 +4,14 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>TanStack CTA</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="" />
9
+ <link
10
+ rel="stylesheet"
11
+ href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"
12
+ />
7
13
  </head>
8
- <body class="dark bg-black text-white dark">
14
+ <body class="dark">
9
15
  <div id="root"></div>
10
16
  <script type="module" src="/src/main.tsx"></script>
11
17
  </body>
@@ -1,52 +1,63 @@
1
- import { readFileSync } from 'node:fs'
2
1
  import { resolve } from 'node:path'
3
2
 
4
3
  import {
4
+ CONFIG_FILE,
5
5
  addToApp,
6
+ createAppOptionsFromPersisted,
6
7
  createDefaultEnvironment,
7
8
  createMemoryEnvironment,
9
+ createSerializedOptionsFromPersisted,
10
+ readConfigFile,
8
11
  recursivelyGatherFiles,
12
+ writeConfigFileToEnvironment,
9
13
  } from '@tanstack/cta-engine'
10
14
 
11
15
  import { cleanUpFileArray, cleanUpFiles } from './file-helpers.js'
12
16
  import { getProjectPath } from './server-environment.js'
13
17
  import { createAppWrapper } from './create-app-wrapper.js'
14
18
 
19
+ import type { Environment } from '@tanstack/cta-engine'
15
20
  import type { Response } from 'express'
21
+ import type { DryRunOutput } from '../types.js'
16
22
 
17
23
  export async function addToAppWrapper(
18
24
  addOns: Array<string>,
19
25
  opts: {
20
26
  dryRun?: boolean
21
27
  response?: Response
28
+ environmentFactory?: () => Environment
22
29
  },
23
30
  ) {
24
31
  const projectPath = getProjectPath()
25
32
 
26
- const persistedOptions = JSON.parse(
27
- readFileSync(resolve(projectPath, '.cta.json')).toString(),
28
- )
33
+ const persistedOptions = await readConfigFile(projectPath)
29
34
 
30
- persistedOptions.targetDir = projectPath
35
+ if (!persistedOptions) {
36
+ throw new Error('No config file found')
37
+ }
38
+
39
+ const options = await createAppOptionsFromPersisted(persistedOptions)
40
+ options.targetDir = projectPath
31
41
 
32
42
  const newAddons: Array<string> = []
33
43
  for (const addOn of addOns) {
34
- if (!persistedOptions.existingAddOns.includes(addOn)) {
44
+ if (!options.chosenAddOns.some((a) => a.id === addOn)) {
35
45
  newAddons.push(addOn)
36
46
  }
37
47
  }
38
48
 
39
49
  if (newAddons.length === 0) {
40
- return await createAppWrapper(persistedOptions, opts)
50
+ const serializedOptions =
51
+ createSerializedOptionsFromPersisted(persistedOptions)
52
+ return await createAppWrapper(serializedOptions, opts)
41
53
  }
42
54
 
43
- async function createEnvironment() {
55
+ async function createEnvironment(): Promise<{
56
+ environment: Environment
57
+ output: DryRunOutput
58
+ }> {
44
59
  if (opts.dryRun) {
45
60
  const { environment, output } = createMemoryEnvironment(projectPath)
46
- environment.writeFile(
47
- resolve(projectPath, '.cta.json'),
48
- JSON.stringify(persistedOptions, null, 2),
49
- )
50
61
 
51
62
  const localFiles = await cleanUpFiles(
52
63
  await recursivelyGatherFiles(projectPath, false),
@@ -57,7 +68,7 @@ export async function addToAppWrapper(
57
68
  return { environment, output }
58
69
  }
59
70
  return {
60
- environment: createDefaultEnvironment(),
71
+ environment: opts.environmentFactory?.() ?? createDefaultEnvironment(),
61
72
  output: { files: {}, deletedFiles: [], commands: [] },
62
73
  }
63
74
  }
@@ -70,7 +81,15 @@ export async function addToAppWrapper(
70
81
  'Transfer-Encoding': 'chunked',
71
82
  })
72
83
 
73
- environment.startStep = ({ id, type, message }) => {
84
+ environment.startStep = ({
85
+ id,
86
+ type,
87
+ message,
88
+ }: {
89
+ id: string
90
+ type: string
91
+ message: string
92
+ }) => {
74
93
  opts.response!.write(
75
94
  JSON.stringify({
76
95
  msgType: 'start',
@@ -80,7 +99,7 @@ export async function addToAppWrapper(
80
99
  }) + '\n',
81
100
  )
82
101
  }
83
- environment.finishStep = (id, message) => {
102
+ environment.finishStep = (id: string, message: string) => {
84
103
  opts.response!.write(
85
104
  JSON.stringify({
86
105
  msgType: 'finish',
@@ -98,9 +117,14 @@ export async function addToAppWrapper(
98
117
  opts.response.end()
99
118
  } else {
100
119
  environment.startRun()
120
+ environment.writeFile(
121
+ resolve(projectPath, CONFIG_FILE),
122
+ JSON.stringify(persistedOptions, null, 2),
123
+ )
101
124
  await addToApp(environment, newAddons, projectPath, {
102
125
  forced: true,
103
126
  })
127
+ writeConfigFileToEnvironment(environment, options)
104
128
  environment.finishRun()
105
129
 
106
130
  output.files = cleanUpFiles(output.files, projectPath)
@@ -14,24 +14,35 @@ import { registerFrameworks } from './framework-registration.js'
14
14
  import { cleanUpFileArray, cleanUpFiles } from './file-helpers.js'
15
15
  import { getApplicationMode, getProjectPath } from './server-environment.js'
16
16
 
17
- import type { Options, SerializedOptions, Starter } from '@tanstack/cta-engine'
17
+ import type {
18
+ Environment,
19
+ Options,
20
+ SerializedOptions,
21
+ Starter,
22
+ } from '@tanstack/cta-engine'
18
23
 
19
24
  import type { Response } from 'express'
20
25
 
21
26
  export async function createAppWrapper(
22
27
  projectOptions: SerializedOptions,
23
- opts: { dryRun?: boolean; response?: Response },
28
+ opts: {
29
+ dryRun?: boolean
30
+ response?: Response
31
+ environmentFactory?: () => Environment
32
+ },
24
33
  ) {
25
34
  registerFrameworks()
26
35
 
27
36
  const framework = getFrameworkById(projectOptions.framework)!
28
37
 
29
38
  let starter: Starter | undefined
30
- const addOns: Array<string> = [...(projectOptions.chosenAddOns || [])]
39
+ const addOns: Array<string> = [...projectOptions.chosenAddOns]
31
40
  if (projectOptions.starter) {
32
41
  starter = await loadStarter(projectOptions.starter)
33
- for (const addOn of starter?.dependsOn ?? []) {
34
- addOns.push(addOn)
42
+ if (starter) {
43
+ for (const addOn of starter.dependsOn ?? []) {
44
+ addOns.push(addOn)
45
+ }
35
46
  }
36
47
  }
37
48
  const chosenAddOns = await finalizeAddOns(
@@ -59,7 +70,7 @@ export async function createAppWrapper(
59
70
  return createMemoryEnvironment(targetDir)
60
71
  }
61
72
  return {
62
- environment: createDefaultEnvironment(),
73
+ environment: opts.environmentFactory?.() ?? createDefaultEnvironment(),
63
74
  output: { files: {}, deletedFiles: [], commands: [] },
64
75
  }
65
76
  }
@@ -1,5 +1,7 @@
1
1
  import { basename } from 'node:path'
2
2
 
3
+ import { CONFIG_FILE } from '@tanstack/cta-engine'
4
+
3
5
  export function cleanUpFiles(
4
6
  files: Record<string, string>,
5
7
  targetDir?: string,
@@ -8,7 +10,7 @@ export function cleanUpFiles(
8
10
  const content = files[file].startsWith('base64::')
9
11
  ? '<binary file>'
10
12
  : files[file]
11
- if (basename(file) !== '.cta.json') {
13
+ if (basename(file) !== CONFIG_FILE) {
12
14
  acc[targetDir ? file.replace(targetDir, '.') : file] = content
13
15
  }
14
16
  return acc
@@ -17,7 +19,7 @@ export function cleanUpFiles(
17
19
 
18
20
  export function cleanUpFileArray(files: Array<string>, targetDir?: string) {
19
21
  return files.reduce<Array<string>>((acc, file) => {
20
- if (basename(file) !== '.cta.json') {
22
+ if (basename(file) !== CONFIG_FILE) {
21
23
  acc.push(targetDir ? file.replace(targetDir, '.') : file)
22
24
  }
23
25
  return acc
@@ -1,10 +1,12 @@
1
- import { readFileSync } from 'node:fs'
2
1
  import { basename, resolve } from 'node:path'
3
2
 
4
3
  import {
5
4
  createSerializedOptionsFromPersisted,
6
5
  getAllAddOns,
7
6
  getFrameworkById,
7
+ getRawRegistry,
8
+ getRegistryAddOns,
9
+ readConfigFile,
8
10
  recursivelyGatherFiles,
9
11
  } from '@tanstack/cta-engine'
10
12
 
@@ -17,18 +19,24 @@ import {
17
19
  getForcedRouterMode,
18
20
  getProjectOptions,
19
21
  getProjectPath,
20
- getRegistry,
22
+ getRegistry as getRegistryURL,
21
23
  } from './server-environment.js'
22
24
 
23
- import type { SerializedOptions } from '@tanstack/cta-engine'
24
- import type { Registry } from '../types.js'
25
+ import type { AddOn, SerializedOptions } from '@tanstack/cta-engine'
26
+ import type { AddOnInfo } from '../types.js'
25
27
 
26
- function absolutizeUrl(originalUrl: string, relativeUrl: string) {
27
- if (relativeUrl.startsWith('http') || relativeUrl.startsWith('https')) {
28
- return relativeUrl
28
+ function convertAddOnToAddOnInfo(addOn: AddOn): AddOnInfo {
29
+ return {
30
+ id: addOn.id,
31
+ name: addOn.name,
32
+ description: addOn.description,
33
+ modes: addOn.modes as Array<'code-router' | 'file-router'>,
34
+ type: addOn.type,
35
+ smallLogo: addOn.smallLogo,
36
+ logo: addOn.logo,
37
+ link: addOn.link!,
38
+ dependsOn: addOn.dependsOn,
29
39
  }
30
- const baseUrl = originalUrl.replace(/registry.json$/, '')
31
- return `${baseUrl}${relativeUrl.replace(/^\.\//, '')}`
32
40
  }
33
41
 
34
42
  export async function generateInitialPayload() {
@@ -44,7 +52,7 @@ export async function generateInitialPayload() {
44
52
 
45
53
  const forcedRouterMode = getForcedRouterMode()
46
54
 
47
- function getSerializedOptions() {
55
+ async function getSerializedOptions() {
48
56
  if (applicationMode === 'setup') {
49
57
  const projectOptions = getProjectOptions()
50
58
  return {
@@ -55,31 +63,23 @@ export async function generateInitialPayload() {
55
63
  typescript: projectOptions.typescript || true,
56
64
  tailwind: projectOptions.tailwind || true,
57
65
  git: projectOptions.git || true,
66
+ targetDir:
67
+ projectOptions.targetDir ||
68
+ resolve(projectPath, projectOptions.projectName),
58
69
  } as SerializedOptions
59
70
  } else {
60
- const persistedOptions = JSON.parse(
61
- readFileSync(resolve(projectPath, '.cta.json')).toString(),
62
- )
71
+ const persistedOptions = await readConfigFile(projectPath)
72
+ if (!persistedOptions) {
73
+ throw new Error('No config file found')
74
+ }
63
75
  return createSerializedOptionsFromPersisted(persistedOptions)
64
76
  }
65
77
  }
66
78
 
67
- const registryUrl = getRegistry()
68
- let registry: Registry | undefined
69
- if (registryUrl) {
70
- registry = (await fetch(registryUrl).then((res) => res.json())) as Registry
71
- for (const addOn of registry['add-ons']) {
72
- addOn.url = absolutizeUrl(registryUrl, addOn.url)
73
- }
74
- for (const starter of registry.starters) {
75
- starter.url = absolutizeUrl(registryUrl, starter.url)
76
- if (starter.banner) {
77
- starter.banner = absolutizeUrl(registryUrl, starter.banner)
78
- }
79
- }
80
- }
79
+ const rawRegistry = await getRawRegistry(getRegistryURL())
80
+ const registryAddOns = await getRegistryAddOns(getRegistryURL())
81
81
 
82
- const serializedOptions = getSerializedOptions()
82
+ const serializedOptions = await getSerializedOptions()
83
83
 
84
84
  const output = await createAppWrapper(serializedOptions, {
85
85
  dryRun: true,
@@ -87,39 +87,46 @@ export async function generateInitialPayload() {
87
87
 
88
88
  const framework = await getFrameworkById(serializedOptions.framework)
89
89
 
90
- const codeRouter = getAllAddOns(framework!, 'code-router').map((addOn) => ({
91
- id: addOn.id,
92
- name: addOn.name,
93
- description: addOn.description,
94
- type: addOn.type,
95
- smallLogo: addOn.smallLogo,
96
- logo: addOn.logo,
97
- link: addOn.link,
98
- dependsOn: addOn.dependsOn,
99
- }))
90
+ const codeRouterAddOns = getAllAddOns(framework!, 'code-router').map(
91
+ convertAddOnToAddOnInfo,
92
+ )
93
+
94
+ const fileRouterAddOns = getAllAddOns(framework!, 'file-router').map(
95
+ convertAddOnToAddOnInfo,
96
+ )
97
+
98
+ for (const addOnInfo of registryAddOns || []) {
99
+ const addOnFramework = rawRegistry?.['add-ons'].find(
100
+ (addOn) => addOn.url === addOnInfo.id,
101
+ )
102
+ if (addOnFramework?.framework === serializedOptions.framework) {
103
+ if (addOnInfo.modes.includes('code-router')) {
104
+ codeRouterAddOns.push(convertAddOnToAddOnInfo(addOnInfo))
105
+ }
106
+ if (addOnInfo.modes.includes('file-router')) {
107
+ fileRouterAddOns.push(convertAddOnToAddOnInfo(addOnInfo))
108
+ }
109
+ }
110
+ }
100
111
 
101
- const fileRouter = getAllAddOns(framework!, 'file-router').map((addOn) => ({
102
- id: addOn.id,
103
- name: addOn.name,
104
- description: addOn.description,
105
- type: addOn.type,
106
- smallLogo: addOn.smallLogo,
107
- logo: addOn.logo,
108
- link: addOn.link,
109
- dependsOn: addOn.dependsOn,
110
- }))
112
+ const serializedRegistry = {
113
+ ['add-ons']: [],
114
+ starters: (rawRegistry?.starters || []).filter(
115
+ (starter) => starter.framework === serializedOptions.framework,
116
+ ),
117
+ }
111
118
 
112
119
  return {
113
120
  applicationMode,
114
121
  localFiles,
115
122
  addOns: {
116
- 'code-router': codeRouter,
117
- 'file-router': fileRouter,
123
+ 'code-router': codeRouterAddOns,
124
+ 'file-router': fileRouterAddOns,
118
125
  },
119
126
  options: serializedOptions,
120
127
  output,
121
128
  forcedRouterMode,
122
129
  forcedAddOns: getForcedAddOns(),
123
- registry,
130
+ registry: serializedRegistry,
124
131
  }
125
132
  }
package/lib/index.ts CHANGED
@@ -2,6 +2,8 @@ import { dirname, resolve } from 'node:path'
2
2
  import { fileURLToPath } from 'node:url'
3
3
  import express from 'express'
4
4
  import cors from 'cors'
5
+ import chalk from 'chalk'
6
+
5
7
  import {
6
8
  AddOnCompiledSchema,
7
9
  StarterCompiledSchema,
@@ -13,10 +15,12 @@ import { generateInitialPayload } from './engine-handling/generate-initial-paylo
13
15
  import { setServerEnvironment } from './engine-handling/server-environment.js'
14
16
 
15
17
  import type { ServerEnvironment } from './engine-handling/server-environment.js'
18
+ import type { Environment } from '@tanstack/cta-engine'
16
19
 
17
20
  export function launchUI(
18
21
  options: Partial<ServerEnvironment> & {
19
22
  port?: number
23
+ environmentFactory?: () => Environment
20
24
  },
21
25
  ) {
22
26
  const { port: requestedPort, ...rest } = options
@@ -37,12 +41,14 @@ export function launchUI(
37
41
  app.post('/api/add-to-app', async (req, res) => {
38
42
  await addToAppWrapper(req.body.addOns, {
39
43
  response: res,
44
+ environmentFactory: options.environmentFactory,
40
45
  })
41
46
  })
42
47
 
43
48
  app.post('/api/create-app', async (req, res) => {
44
49
  await createAppWrapper(req.body.options, {
45
50
  response: res,
51
+ environmentFactory: options.environmentFactory,
46
52
  })
47
53
  })
48
54
 
@@ -50,6 +56,7 @@ export function launchUI(
50
56
  res.send(
51
57
  await addToAppWrapper(req.body.addOns, {
52
58
  dryRun: true,
59
+ environmentFactory: options.environmentFactory,
53
60
  }),
54
61
  )
55
62
  })
@@ -58,6 +65,7 @@ export function launchUI(
58
65
  res.send(
59
66
  await createAppWrapper(req.body.options, {
60
67
  dryRun: true,
68
+ environmentFactory: options.environmentFactory,
61
69
  }),
62
70
  )
63
71
  })
@@ -143,7 +151,9 @@ export function launchUI(
143
151
  const port = requestedPort || process.env.PORT || 8080
144
152
  app.listen(port, () => {
145
153
  console.log(
146
- `Create TanStack ${launchUI ? 'App' : 'API'} is running on http://localhost:${port}`,
154
+ `🔥 ${chalk.blueBright(`Create TanStack ${launchUI ? 'App' : 'API'}`)} is running on ${chalk.underline(
155
+ `http://localhost:${port}`,
156
+ )}`,
147
157
  )
148
158
  })
149
159
  }
package/lib/types.d.ts CHANGED
@@ -1,13 +1,20 @@
1
- export type Registry = {
2
- starters: Array<{
3
- name: string
4
- description: string
5
- url: string
6
- banner?: string
7
- }>
8
- 'add-ons': Array<{
9
- name: string
10
- description: string
11
- url: string
1
+ export type DryRunOutput = {
2
+ files: Record<string, string>
3
+ commands: Array<{
4
+ command: string
5
+ args: Array<string>
12
6
  }>
7
+ deletedFiles: Array<string>
8
+ }
9
+
10
+ export type AddOnInfo = {
11
+ id: string
12
+ name: string
13
+ description: string
14
+ type: 'add-on' | 'example' | 'starter' | 'toolchain'
15
+ modes: Array<'code-router' | 'file-router'>
16
+ smallLogo?: string
17
+ logo?: string
18
+ link: string
19
+ dependsOn?: Array<string>
13
20
  }
@@ -1,7 +1,9 @@
1
+ import type { Environment } from '@tanstack/cta-engine';
1
2
  import type { Response } from 'express';
2
3
  export declare function addToAppWrapper(addOns: Array<string>, opts: {
3
4
  dryRun?: boolean;
4
5
  response?: Response;
6
+ environmentFactory?: () => Environment;
5
7
  }): Promise<{
6
8
  files: Record<string, string>;
7
9
  deletedFiles: Array<string>;
@@ -1,26 +1,29 @@
1
- import { readFileSync } from 'node:fs';
2
1
  import { resolve } from 'node:path';
3
- import { addToApp, createDefaultEnvironment, createMemoryEnvironment, recursivelyGatherFiles, } from '@tanstack/cta-engine';
2
+ import { CONFIG_FILE, addToApp, createAppOptionsFromPersisted, createDefaultEnvironment, createMemoryEnvironment, createSerializedOptionsFromPersisted, readConfigFile, recursivelyGatherFiles, writeConfigFileToEnvironment, } from '@tanstack/cta-engine';
4
3
  import { cleanUpFileArray, cleanUpFiles } from './file-helpers.js';
5
4
  import { getProjectPath } from './server-environment.js';
6
5
  import { createAppWrapper } from './create-app-wrapper.js';
7
6
  export async function addToAppWrapper(addOns, opts) {
8
7
  const projectPath = getProjectPath();
9
- const persistedOptions = JSON.parse(readFileSync(resolve(projectPath, '.cta.json')).toString());
10
- persistedOptions.targetDir = projectPath;
8
+ const persistedOptions = await readConfigFile(projectPath);
9
+ if (!persistedOptions) {
10
+ throw new Error('No config file found');
11
+ }
12
+ const options = await createAppOptionsFromPersisted(persistedOptions);
13
+ options.targetDir = projectPath;
11
14
  const newAddons = [];
12
15
  for (const addOn of addOns) {
13
- if (!persistedOptions.existingAddOns.includes(addOn)) {
16
+ if (!options.chosenAddOns.some((a) => a.id === addOn)) {
14
17
  newAddons.push(addOn);
15
18
  }
16
19
  }
17
20
  if (newAddons.length === 0) {
18
- return await createAppWrapper(persistedOptions, opts);
21
+ const serializedOptions = createSerializedOptionsFromPersisted(persistedOptions);
22
+ return await createAppWrapper(serializedOptions, opts);
19
23
  }
20
24
  async function createEnvironment() {
21
25
  if (opts.dryRun) {
22
26
  const { environment, output } = createMemoryEnvironment(projectPath);
23
- environment.writeFile(resolve(projectPath, '.cta.json'), JSON.stringify(persistedOptions, null, 2));
24
27
  const localFiles = await cleanUpFiles(await recursivelyGatherFiles(projectPath, false));
25
28
  for (const file of Object.keys(localFiles)) {
26
29
  environment.writeFile(resolve(projectPath, file), localFiles[file]);
@@ -28,7 +31,7 @@ export async function addToAppWrapper(addOns, opts) {
28
31
  return { environment, output };
29
32
  }
30
33
  return {
31
- environment: createDefaultEnvironment(),
34
+ environment: opts.environmentFactory?.() ?? createDefaultEnvironment(),
32
35
  output: { files: {}, deletedFiles: [], commands: [] },
33
36
  };
34
37
  }
@@ -38,7 +41,7 @@ export async function addToAppWrapper(addOns, opts) {
38
41
  'Content-Type': 'text/plain',
39
42
  'Transfer-Encoding': 'chunked',
40
43
  });
41
- environment.startStep = ({ id, type, message }) => {
44
+ environment.startStep = ({ id, type, message, }) => {
42
45
  opts.response.write(JSON.stringify({
43
46
  msgType: 'start',
44
47
  id,
@@ -62,9 +65,11 @@ export async function addToAppWrapper(addOns, opts) {
62
65
  }
63
66
  else {
64
67
  environment.startRun();
68
+ environment.writeFile(resolve(projectPath, CONFIG_FILE), JSON.stringify(persistedOptions, null, 2));
65
69
  await addToApp(environment, newAddons, projectPath, {
66
70
  forced: true,
67
71
  });
72
+ writeConfigFileToEnvironment(environment, options);
68
73
  environment.finishRun();
69
74
  output.files = cleanUpFiles(output.files, projectPath);
70
75
  output.deletedFiles = cleanUpFileArray(output.deletedFiles, projectPath);
@@ -1,8 +1,9 @@
1
- import type { SerializedOptions } from '@tanstack/cta-engine';
1
+ import type { Environment, SerializedOptions } from '@tanstack/cta-engine';
2
2
  import type { Response } from 'express';
3
3
  export declare function createAppWrapper(projectOptions: SerializedOptions, opts: {
4
4
  dryRun?: boolean;
5
5
  response?: Response;
6
+ environmentFactory?: () => Environment;
6
7
  }): Promise<{
7
8
  files: Record<string, string>;
8
9
  deletedFiles: Array<string>;
@@ -7,11 +7,13 @@ export async function createAppWrapper(projectOptions, opts) {
7
7
  registerFrameworks();
8
8
  const framework = getFrameworkById(projectOptions.framework);
9
9
  let starter;
10
- const addOns = [...(projectOptions.chosenAddOns || [])];
10
+ const addOns = [...projectOptions.chosenAddOns];
11
11
  if (projectOptions.starter) {
12
12
  starter = await loadStarter(projectOptions.starter);
13
- for (const addOn of starter?.dependsOn ?? []) {
14
- addOns.push(addOn);
13
+ if (starter) {
14
+ for (const addOn of starter.dependsOn ?? []) {
15
+ addOns.push(addOn);
16
+ }
15
17
  }
16
18
  }
17
19
  const chosenAddOns = await finalizeAddOns(framework, projectOptions.mode, addOns);
@@ -31,7 +33,7 @@ export async function createAppWrapper(projectOptions, opts) {
31
33
  return createMemoryEnvironment(targetDir);
32
34
  }
33
35
  return {
34
- environment: createDefaultEnvironment(),
36
+ environment: opts.environmentFactory?.() ?? createDefaultEnvironment(),
35
37
  output: { files: {}, deletedFiles: [], commands: [] },
36
38
  };
37
39
  }
@@ -1,10 +1,11 @@
1
1
  import { basename } from 'node:path';
2
+ import { CONFIG_FILE } from '@tanstack/cta-engine';
2
3
  export function cleanUpFiles(files, targetDir) {
3
4
  return Object.keys(files).reduce((acc, file) => {
4
5
  const content = files[file].startsWith('base64::')
5
6
  ? '<binary file>'
6
7
  : files[file];
7
- if (basename(file) !== '.cta.json') {
8
+ if (basename(file) !== CONFIG_FILE) {
8
9
  acc[targetDir ? file.replace(targetDir, '.') : file] = content;
9
10
  }
10
11
  return acc;
@@ -12,7 +13,7 @@ export function cleanUpFiles(files, targetDir) {
12
13
  }
13
14
  export function cleanUpFileArray(files, targetDir) {
14
15
  return files.reduce((acc, file) => {
15
- if (basename(file) !== '.cta.json') {
16
+ if (basename(file) !== CONFIG_FILE) {
16
17
  acc.push(targetDir ? file.replace(targetDir, '.') : file);
17
18
  }
18
19
  return acc;