@tanstack/cta-engine 0.10.0-alpha.26 → 0.10.0-alpha.30

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.
@@ -10,23 +10,25 @@ import { packageManagerInstall } from './package-manager.js';
10
10
  import { isBase64, recursivelyGatherFilesFromEnvironment, } from './file-helpers.js';
11
11
  import { mergePackageJSON } from './package-json.js';
12
12
  import { runSpecialSteps } from './special-steps/index.js';
13
+ import { loadStarter } from './custom-add-ons/starter.js';
13
14
  export async function hasPendingGitChanges(environment, cwd) {
14
15
  const { stdout } = await environment.execute('git', ['status', '--porcelain'], cwd);
15
16
  return stdout.length > 0;
16
17
  }
17
18
  async function createOptions(json, addOns, targetDir) {
18
19
  const framework = getFrameworkById(json.framework);
19
- // TODO: Load the starter
20
+ const starter = json.starter ? await loadStarter(json.starter) : undefined;
20
21
  return {
21
22
  ...json,
22
23
  framework,
23
24
  tailwind: true,
24
25
  addOns: true,
25
26
  chosenAddOns: await finalizeAddOns(framework, json.mode, [
26
- ...json.existingAddOns,
27
+ ...json.chosenAddOns,
27
28
  ...addOns,
28
29
  ]),
29
30
  targetDir,
31
+ starter,
30
32
  };
31
33
  }
32
34
  async function runCreateApp(options) {
@@ -165,5 +167,5 @@ export async function addToApp(environment, addOns, cwd, options) {
165
167
  });
166
168
  writeConfigFileToEnvironment(environment, newOptions);
167
169
  environment.finishStep('write-config-file', 'Config file written');
168
- environment.outro('Add-ons added successfully!');
170
+ environment.outro(`Add-ons ${addOns.join(', ')} added!`);
169
171
  }
@@ -1,5 +1,6 @@
1
1
  import { resolve } from 'node:path';
2
2
  import { CONFIG_FILE } from './constants.js';
3
+ import { createDefaultEnvironment } from './environment.js';
3
4
  function createPersistedOptions(options) {
4
5
  /* eslint-disable unused-imports/no-unused-vars */
5
6
  const { chosenAddOns, framework, targetDir, ...rest } = options;
@@ -8,7 +9,7 @@ function createPersistedOptions(options) {
8
9
  ...rest,
9
10
  version: 1,
10
11
  framework: options.framework.id,
11
- existingAddOns: options.chosenAddOns.map((addOn) => addOn.id),
12
+ chosenAddOns: options.chosenAddOns.map((addOn) => addOn.id),
12
13
  starter: options.starter?.id ?? undefined,
13
14
  };
14
15
  }
@@ -19,10 +20,27 @@ export async function readConfigFileFromEnvironment(environment, targetDir) {
19
20
  try {
20
21
  const configFile = resolve(targetDir, CONFIG_FILE);
21
22
  const config = await environment.readFile(configFile);
22
- // TODO: Look for old config files and convert them to the new format
23
- return JSON.parse(config);
23
+ const originalJSON = JSON.parse(config);
24
+ // Look for markers out outdated config files and upgrade the format on the fly (it will be written in the updated version after we add add-ons)
25
+ if (originalJSON.existingAddOns) {
26
+ if (originalJSON.framework === 'react') {
27
+ originalJSON.framework = 'react-cra';
28
+ }
29
+ originalJSON.chosenAddOns = originalJSON.existingAddOns;
30
+ delete originalJSON.existingAddOns;
31
+ delete originalJSON.addOns;
32
+ if (originalJSON.toolchain && originalJSON.toolchain !== 'none') {
33
+ originalJSON.chosenAddOns.push(originalJSON.toolchain);
34
+ }
35
+ delete originalJSON.toolchain;
36
+ delete originalJSON.variableValues;
37
+ }
38
+ return originalJSON;
24
39
  }
25
40
  catch {
26
41
  return null;
27
42
  }
28
43
  }
44
+ export async function readConfigFile(targetDir) {
45
+ return readConfigFileFromEnvironment(createDefaultEnvironment(), targetDir);
46
+ }
@@ -97,7 +97,7 @@ export async function readOrGenerateAddOnInfo(options) {
97
97
  dependencies: {},
98
98
  devDependencies: {},
99
99
  },
100
- dependsOn: options.existingAddOns,
100
+ dependsOn: options.chosenAddOns,
101
101
  };
102
102
  }
103
103
  export async function generateProject(persistedOptions) {
@@ -56,7 +56,7 @@ export async function createAppOptionsFromPersisted(json) {
56
56
  framework: framework,
57
57
  starter: json.starter ? await loadStarter(json.starter) : undefined,
58
58
  chosenAddOns: await finalizeAddOns(framework, json.mode, [
59
- ...json.existingAddOns,
59
+ ...json.chosenAddOns,
60
60
  ]),
61
61
  };
62
62
  }
@@ -75,7 +75,6 @@ export function createSerializedOptionsFromPersisted(json) {
75
75
  targetDir: '',
76
76
  framework: json.framework,
77
77
  starter: json.starter,
78
- chosenAddOns: json.existingAddOns,
79
78
  };
80
79
  }
81
80
  export async function runCreateApp(options) {
@@ -28,7 +28,7 @@ export async function readOrGenerateStarterInfo(options) {
28
28
  dependencies: {},
29
29
  devDependencies: {},
30
30
  },
31
- dependsOn: options.existingAddOns,
31
+ dependsOn: options.chosenAddOns,
32
32
  typescript: options.typescript,
33
33
  tailwind: options.tailwind,
34
34
  };
@@ -78,23 +78,14 @@ export function createMemoryEnvironment(returnPathsRelativeTo = '') {
78
78
  deletedFiles: [],
79
79
  };
80
80
  const { fs, vol } = memfs({});
81
- const cwd = process.cwd();
82
- const isInCwd = (path) => path.startsWith(cwd);
83
- const isTemplatePath = (path) => !isInCwd(path);
84
81
  environment.appendFile = async (path, contents) => {
85
82
  fs.mkdirSync(dirname(path), { recursive: true });
86
83
  await fs.appendFileSync(path, contents);
87
84
  };
88
85
  environment.copyFile = async (from, to) => {
89
- if (isTemplatePath(from)) {
90
- const contents = (await readFile(from)).toString();
91
- fs.mkdirSync(dirname(to), { recursive: true });
92
- fs.writeFileSync(to, contents);
93
- }
94
- else {
95
- fs.mkdirSync(dirname(to), { recursive: true });
96
- fs.copyFileSync(from, to);
97
- }
86
+ fs.mkdirSync(dirname(to), { recursive: true });
87
+ fs.copyFileSync(from, to);
88
+ return Promise.resolve();
98
89
  };
99
90
  environment.execute = async (command, args) => {
100
91
  output.commands.push({
package/dist/index.js CHANGED
@@ -7,11 +7,12 @@ export { createMemoryEnvironment, createDefaultEnvironment, } from './environmen
7
7
  export { CODE_ROUTER, CONFIG_FILE, FILE_ROUTER } from './constants.js';
8
8
  export { DEFAULT_PACKAGE_MANAGER, SUPPORTED_PACKAGE_MANAGERS, getPackageManager, } from './package-manager.js';
9
9
  export { registerFramework, getFrameworkById, getFrameworkByName, getFrameworks, } from './frameworks.js';
10
- export { writeConfigFileToEnvironment, readConfigFileFromEnvironment, } from './config-file.js';
10
+ export { writeConfigFileToEnvironment, readConfigFileFromEnvironment, readConfigFile, } from './config-file.js';
11
11
  export { cleanUpFiles, cleanUpFileArray, readFileHelper, getBinaryFile, recursivelyGatherFiles, relativePath, } from './file-helpers.js';
12
12
  export { formatCommand } from './utils.js';
13
13
  export { initStarter, compileStarter } from './custom-add-ons/starter.js';
14
14
  export { initAddOn, compileAddOn } from './custom-add-ons/add-on.js';
15
15
  export { createAppOptionsFromPersisted, createSerializedOptionsFromPersisted, } from './custom-add-ons/shared.js';
16
16
  export { createSerializedOptions } from './options.js';
17
+ export { getRawRegistry, getRegistry, getRegistryAddOns, getRegistryStarters, } from './registry.js';
17
18
  export { StarterCompiledSchema, AddOnCompiledSchema, AddOnInfoSchema, IntegrationSchema, } from './types.js';
@@ -0,0 +1,56 @@
1
+ import { loadRemoteAddOn } from './custom-add-ons/add-on.js';
2
+ import { loadStarter } from './custom-add-ons/starter.js';
3
+ function absolutizeUrl(originalUrl, relativeUrl) {
4
+ if (relativeUrl.startsWith('http') || relativeUrl.startsWith('https')) {
5
+ return relativeUrl;
6
+ }
7
+ const baseUrl = originalUrl.replace(/registry.json$/, '');
8
+ return `${baseUrl}${relativeUrl.replace(/^\.\//, '')}`;
9
+ }
10
+ export async function getRawRegistry(registryUrl) {
11
+ const regUrl = registryUrl || process.env.CTA_REGISTRY;
12
+ if (regUrl) {
13
+ const registry = (await fetch(regUrl).then((res) => res.json()));
14
+ for (const addOn of registry['add-ons']) {
15
+ addOn.url = absolutizeUrl(regUrl, addOn.url);
16
+ }
17
+ for (const starter of registry.starters) {
18
+ starter.url = absolutizeUrl(regUrl, starter.url);
19
+ if (starter.banner) {
20
+ starter.banner = absolutizeUrl(regUrl, starter.banner);
21
+ }
22
+ }
23
+ return registry;
24
+ }
25
+ }
26
+ async function getAddOns(registry) {
27
+ const addOns = [];
28
+ for (const addOnInfo of registry['add-ons']) {
29
+ const addOn = await loadRemoteAddOn(addOnInfo.url);
30
+ addOns.push(addOn);
31
+ }
32
+ return addOns;
33
+ }
34
+ export async function getRegistryAddOns(registryUrl) {
35
+ const registry = await getRawRegistry(registryUrl);
36
+ return registry ? await getAddOns(registry) : [];
37
+ }
38
+ async function getStarters(registry) {
39
+ const starters = [];
40
+ for (const starterInfo of registry.starters) {
41
+ const starter = await loadStarter(starterInfo.url);
42
+ starters.push(starter);
43
+ }
44
+ return starters;
45
+ }
46
+ export async function getRegistryStarters(registryUrl) {
47
+ const registry = await getRawRegistry(registryUrl);
48
+ return registry ? await getStarters(registry) : [];
49
+ }
50
+ export async function getRegistry(registryUrl) {
51
+ const registry = await getRawRegistry(registryUrl);
52
+ return {
53
+ addOns: registry ? await getAddOns(registry) : [],
54
+ starters: registry ? await getStarters(registry) : [],
55
+ };
56
+ }
@@ -2,8 +2,9 @@ import type { Environment, Options } from './types.js';
2
2
  export type PersistedOptions = Omit<Partial<Options>, 'addOns' | 'chosenAddOns' | 'framework' | 'starter' | 'targetDir'> & {
3
3
  framework: string;
4
4
  version: number;
5
- existingAddOns: Array<string>;
5
+ chosenAddOns: Array<string>;
6
6
  starter?: string;
7
7
  };
8
8
  export declare function writeConfigFileToEnvironment(environment: Environment, options: Options): Promise<void>;
9
9
  export declare function readConfigFileFromEnvironment(environment: Environment, targetDir: string): Promise<PersistedOptions | null>;
10
+ export declare function readConfigFile(targetDir: string): Promise<PersistedOptions | null>;
@@ -7,13 +7,14 @@ export { createMemoryEnvironment, createDefaultEnvironment, } from './environmen
7
7
  export { CODE_ROUTER, CONFIG_FILE, FILE_ROUTER } from './constants.js';
8
8
  export { DEFAULT_PACKAGE_MANAGER, SUPPORTED_PACKAGE_MANAGERS, getPackageManager, } from './package-manager.js';
9
9
  export { registerFramework, getFrameworkById, getFrameworkByName, getFrameworks, } from './frameworks.js';
10
- export { writeConfigFileToEnvironment, readConfigFileFromEnvironment, } from './config-file.js';
10
+ export { writeConfigFileToEnvironment, readConfigFileFromEnvironment, readConfigFile, } from './config-file.js';
11
11
  export { cleanUpFiles, cleanUpFileArray, readFileHelper, getBinaryFile, recursivelyGatherFiles, relativePath, } from './file-helpers.js';
12
12
  export { formatCommand } from './utils.js';
13
13
  export { initStarter, compileStarter } from './custom-add-ons/starter.js';
14
14
  export { initAddOn, compileAddOn } from './custom-add-ons/add-on.js';
15
15
  export { createAppOptionsFromPersisted, createSerializedOptionsFromPersisted, } from './custom-add-ons/shared.js';
16
16
  export { createSerializedOptions } from './options.js';
17
+ export { getRawRegistry, getRegistry, getRegistryAddOns, getRegistryStarters, } from './registry.js';
17
18
  export { StarterCompiledSchema, StatusEvent, StatusStepType, StopEvent, AddOnCompiledSchema, AddOnInfoSchema, IntegrationSchema, } from './types.js';
18
19
  export type { AddOn, Environment, FileBundleHandler, Framework, FrameworkDefinition, Mode, Options, SerializedOptions, Starter, StarterCompiled, } from './types.js';
19
20
  export type { PersistedOptions } from './config-file.js';
@@ -0,0 +1,25 @@
1
+ import type { AddOn, Mode, Starter } from './types';
2
+ export type Registry = {
3
+ starters: Array<{
4
+ name: string;
5
+ description: string;
6
+ url: string;
7
+ banner?: string;
8
+ mode: Mode;
9
+ framework: string;
10
+ }>;
11
+ 'add-ons': Array<{
12
+ name: string;
13
+ description: string;
14
+ url: string;
15
+ modes: Array<Mode>;
16
+ framework: string;
17
+ }>;
18
+ };
19
+ export declare function getRawRegistry(registryUrl?: string): Promise<Registry | undefined>;
20
+ export declare function getRegistryAddOns(registryUrl?: string): Promise<Array<AddOn>>;
21
+ export declare function getRegistryStarters(registryUrl?: string): Promise<Array<Starter>>;
22
+ export declare function getRegistry(registryUrl?: string): Promise<{
23
+ addOns: Array<AddOn>;
24
+ starters: Array<Starter>;
25
+ }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/cta-engine",
3
- "version": "0.10.0-alpha.26",
3
+ "version": "0.10.0-alpha.30",
4
4
  "description": "Tanstack Application Builder Engine",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/add-to-app.ts CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  } from './file-helpers.js'
18
18
  import { mergePackageJSON } from './package-json.js'
19
19
  import { runSpecialSteps } from './special-steps/index.js'
20
+ import { loadStarter } from './custom-add-ons/starter.js'
20
21
 
21
22
  import type { Environment, Mode, Options } from './types.js'
22
23
  import type { PersistedOptions } from './config-file.js'
@@ -40,7 +41,7 @@ async function createOptions(
40
41
  ): Promise<Options> {
41
42
  const framework = getFrameworkById(json.framework)
42
43
 
43
- // TODO: Load the starter
44
+ const starter = json.starter ? await loadStarter(json.starter) : undefined
44
45
 
45
46
  return {
46
47
  ...json,
@@ -48,10 +49,11 @@ async function createOptions(
48
49
  tailwind: true,
49
50
  addOns: true,
50
51
  chosenAddOns: await finalizeAddOns(framework!, json.mode as Mode, [
51
- ...json.existingAddOns,
52
+ ...json.chosenAddOns,
52
53
  ...addOns,
53
54
  ]),
54
55
  targetDir,
56
+ starter,
55
57
  } as Options
56
58
  }
57
59
 
@@ -270,5 +272,5 @@ export async function addToApp(
270
272
  writeConfigFileToEnvironment(environment, newOptions)
271
273
  environment.finishStep('write-config-file', 'Config file written')
272
274
 
273
- environment.outro('Add-ons added successfully!')
275
+ environment.outro(`Add-ons ${addOns.join(', ')} added!`)
274
276
  }
@@ -1,6 +1,7 @@
1
1
  import { resolve } from 'node:path'
2
2
 
3
3
  import { CONFIG_FILE } from './constants.js'
4
+ import { createDefaultEnvironment } from './environment.js'
4
5
 
5
6
  import type { Environment, Options } from './types.js'
6
7
 
@@ -10,7 +11,7 @@ export type PersistedOptions = Omit<
10
11
  > & {
11
12
  framework: string
12
13
  version: number
13
- existingAddOns: Array<string>
14
+ chosenAddOns: Array<string>
14
15
  starter?: string
15
16
  }
16
17
 
@@ -22,7 +23,7 @@ function createPersistedOptions(options: Options): PersistedOptions {
22
23
  ...rest,
23
24
  version: 1,
24
25
  framework: options.framework.id,
25
- existingAddOns: options.chosenAddOns.map((addOn) => addOn.id),
26
+ chosenAddOns: options.chosenAddOns.map((addOn) => addOn.id),
26
27
  starter: options.starter?.id ?? undefined,
27
28
  }
28
29
  }
@@ -45,10 +46,31 @@ export async function readConfigFileFromEnvironment(
45
46
  const configFile = resolve(targetDir, CONFIG_FILE)
46
47
  const config = await environment.readFile(configFile)
47
48
 
48
- // TODO: Look for old config files and convert them to the new format
49
+ const originalJSON = JSON.parse(config)
49
50
 
50
- return JSON.parse(config)
51
+ // Look for markers out outdated config files and upgrade the format on the fly (it will be written in the updated version after we add add-ons)
52
+ if (originalJSON.existingAddOns) {
53
+ if (originalJSON.framework === 'react') {
54
+ originalJSON.framework = 'react-cra'
55
+ }
56
+ originalJSON.chosenAddOns = originalJSON.existingAddOns
57
+ delete originalJSON.existingAddOns
58
+ delete originalJSON.addOns
59
+ if (originalJSON.toolchain && originalJSON.toolchain !== 'none') {
60
+ originalJSON.chosenAddOns.push(originalJSON.toolchain)
61
+ }
62
+ delete originalJSON.toolchain
63
+ delete originalJSON.variableValues
64
+ }
65
+
66
+ return originalJSON
51
67
  } catch {
52
68
  return null
53
69
  }
54
70
  }
71
+
72
+ export async function readConfigFile(
73
+ targetDir: string,
74
+ ): Promise<PersistedOptions | null> {
75
+ return readConfigFileFromEnvironment(createDefaultEnvironment(), targetDir)
76
+ }
@@ -148,7 +148,7 @@ export async function readOrGenerateAddOnInfo(
148
148
  dependencies: {},
149
149
  devDependencies: {},
150
150
  },
151
- dependsOn: options.existingAddOns,
151
+ dependsOn: options.chosenAddOns,
152
152
  } as AddOnInfo)
153
153
  }
154
154
 
@@ -78,7 +78,7 @@ export async function createAppOptionsFromPersisted(
78
78
  framework: framework!,
79
79
  starter: json.starter ? await loadStarter(json.starter) : undefined,
80
80
  chosenAddOns: await finalizeAddOns(framework!, json.mode as Mode, [
81
- ...json.existingAddOns,
81
+ ...json.chosenAddOns,
82
82
  ]),
83
83
  }
84
84
  }
@@ -100,7 +100,6 @@ export function createSerializedOptionsFromPersisted(
100
100
  targetDir: '',
101
101
  framework: json.framework,
102
102
  starter: json.starter,
103
- chosenAddOns: json.existingAddOns,
104
103
  }
105
104
  }
106
105
 
@@ -48,7 +48,7 @@ export async function readOrGenerateStarterInfo(
48
48
  dependencies: {},
49
49
  devDependencies: {},
50
50
  },
51
- dependsOn: options.existingAddOns,
51
+ dependsOn: options.chosenAddOns,
52
52
  typescript: options.typescript!,
53
53
  tailwind: options.tailwind!,
54
54
  }
@@ -111,23 +111,14 @@ export function createMemoryEnvironment(returnPathsRelativeTo: string = '') {
111
111
 
112
112
  const { fs, vol } = memfs({})
113
113
 
114
- const cwd = process.cwd()
115
- const isInCwd = (path: string) => path.startsWith(cwd)
116
- const isTemplatePath = (path: string) => !isInCwd(path)
117
-
118
114
  environment.appendFile = async (path: string, contents: string) => {
119
115
  fs.mkdirSync(dirname(path), { recursive: true })
120
116
  await fs.appendFileSync(path, contents)
121
117
  }
122
118
  environment.copyFile = async (from: string, to: string) => {
123
- if (isTemplatePath(from)) {
124
- const contents = (await readFile(from)).toString()
125
- fs.mkdirSync(dirname(to), { recursive: true })
126
- fs.writeFileSync(to, contents)
127
- } else {
128
- fs.mkdirSync(dirname(to), { recursive: true })
129
- fs.copyFileSync(from, to)
130
- }
119
+ fs.mkdirSync(dirname(to), { recursive: true })
120
+ fs.copyFileSync(from, to)
121
+ return Promise.resolve()
131
122
  }
132
123
  environment.execute = async (command: string, args: Array<string>) => {
133
124
  output.commands.push({
package/src/index.ts CHANGED
@@ -29,6 +29,7 @@ export {
29
29
  export {
30
30
  writeConfigFileToEnvironment,
31
31
  readConfigFileFromEnvironment,
32
+ readConfigFile,
32
33
  } from './config-file.js'
33
34
 
34
35
  export {
@@ -51,6 +52,13 @@ export {
51
52
 
52
53
  export { createSerializedOptions } from './options.js'
53
54
 
55
+ export {
56
+ getRawRegistry,
57
+ getRegistry,
58
+ getRegistryAddOns,
59
+ getRegistryStarters,
60
+ } from './registry.js'
61
+
54
62
  export {
55
63
  StarterCompiledSchema,
56
64
  StatusEvent,
@@ -0,0 +1,92 @@
1
+ import { loadRemoteAddOn } from './custom-add-ons/add-on.js'
2
+ import { loadStarter } from './custom-add-ons/starter.js'
3
+
4
+ import type { AddOn, Mode, Starter } from './types'
5
+
6
+ export type Registry = {
7
+ starters: Array<{
8
+ name: string
9
+ description: string
10
+ url: string
11
+ banner?: string
12
+ mode: Mode
13
+ framework: string
14
+ }>
15
+ 'add-ons': Array<{
16
+ name: string
17
+ description: string
18
+ url: string
19
+ modes: Array<Mode>
20
+ framework: string
21
+ }>
22
+ }
23
+
24
+ function absolutizeUrl(originalUrl: string, relativeUrl: string) {
25
+ if (relativeUrl.startsWith('http') || relativeUrl.startsWith('https')) {
26
+ return relativeUrl
27
+ }
28
+ const baseUrl = originalUrl.replace(/registry.json$/, '')
29
+ return `${baseUrl}${relativeUrl.replace(/^\.\//, '')}`
30
+ }
31
+
32
+ export async function getRawRegistry(
33
+ registryUrl?: string,
34
+ ): Promise<Registry | undefined> {
35
+ const regUrl = registryUrl || process.env.CTA_REGISTRY
36
+ if (regUrl) {
37
+ const registry = (await fetch(regUrl).then((res) => res.json())) as Registry
38
+ for (const addOn of registry['add-ons']) {
39
+ addOn.url = absolutizeUrl(regUrl, addOn.url)
40
+ }
41
+ for (const starter of registry.starters) {
42
+ starter.url = absolutizeUrl(regUrl, starter.url)
43
+ if (starter.banner) {
44
+ starter.banner = absolutizeUrl(regUrl, starter.banner)
45
+ }
46
+ }
47
+ return registry
48
+ }
49
+ }
50
+
51
+ async function getAddOns(registry: Registry): Promise<Array<AddOn>> {
52
+ const addOns: Array<AddOn> = []
53
+ for (const addOnInfo of registry['add-ons']) {
54
+ const addOn = await loadRemoteAddOn(addOnInfo.url)
55
+ addOns.push(addOn)
56
+ }
57
+ return addOns
58
+ }
59
+
60
+ export async function getRegistryAddOns(
61
+ registryUrl?: string,
62
+ ): Promise<Array<AddOn>> {
63
+ const registry = await getRawRegistry(registryUrl)
64
+ return registry ? await getAddOns(registry) : []
65
+ }
66
+
67
+ async function getStarters(registry: Registry): Promise<Array<Starter>> {
68
+ const starters: Array<Starter> = []
69
+ for (const starterInfo of registry.starters) {
70
+ const starter = await loadStarter(starterInfo.url)
71
+ starters.push(starter)
72
+ }
73
+ return starters
74
+ }
75
+
76
+ export async function getRegistryStarters(
77
+ registryUrl?: string,
78
+ ): Promise<Array<Starter>> {
79
+ const registry = await getRawRegistry(registryUrl)
80
+ return registry ? await getStarters(registry) : []
81
+ }
82
+
83
+ export async function getRegistry(registryUrl?: string): Promise<{
84
+ addOns: Array<AddOn>
85
+ starters: Array<Starter>
86
+ }> {
87
+ const registry = await getRawRegistry(registryUrl)
88
+ return {
89
+ addOns: registry ? await getAddOns(registry) : [],
90
+ starters: registry ? await getStarters(registry) : [],
91
+ }
92
+ }
@@ -24,7 +24,7 @@ const fakeCTAJSON: PersistedOptions = {
24
24
  targetDir: '/foo',
25
25
  framework: 'test',
26
26
  mode: 'code-router',
27
- existingAddOns: [],
27
+ chosenAddOns: [],
28
28
  version: 1,
29
29
  typescript: true,
30
30
  tailwind: true,
@@ -88,7 +88,7 @@ const configFile: PersistedOptions = {
88
88
  targetDir: '/foo',
89
89
  framework: 'test',
90
90
  mode: 'code-router',
91
- existingAddOns: [],
91
+ chosenAddOns: [],
92
92
  version: 1,
93
93
  typescript: true,
94
94
  tailwind: true,
@@ -37,7 +37,7 @@ describe('writeConfigFile', () => {
37
37
  const persistedOptions = {
38
38
  version: 1,
39
39
  framework: options.framework.id,
40
- existingAddOns: options.chosenAddOns.map((addOn) => addOn.id),
40
+ chosenAddOns: options.chosenAddOns.map((addOn) => addOn.id),
41
41
  }
42
42
  const env = {
43
43
  writeFile: (path, optionsString) => {
@@ -55,7 +55,7 @@ describe('readConfigFileFromEnvironment', () => {
55
55
  const persistedOptions = {
56
56
  version: 1,
57
57
  framework: 'react-cra',
58
- existingAddOns: ['add-on-1'],
58
+ chosenAddOns: ['add-on-1'],
59
59
  }
60
60
  const { environment } = createMemoryEnvironment()
61
61
  environment.writeFile(
@@ -65,4 +65,41 @@ describe('readConfigFileFromEnvironment', () => {
65
65
  const config = await readConfigFileFromEnvironment(environment, targetDir)
66
66
  expect(config).toEqual(persistedOptions)
67
67
  })
68
+
69
+ it('should upgrade old config files', async () => {
70
+ const targetDir = 'test-dir'
71
+ const persistedOptions = {
72
+ framework: 'react',
73
+ projectName: 'foo',
74
+ typescript: false,
75
+ tailwind: false,
76
+ packageManager: 'pnpm',
77
+ toolchain: 'none',
78
+ mode: 'code-router',
79
+ git: true,
80
+ variableValues: {},
81
+ version: 1,
82
+ existingAddOns: [],
83
+ }
84
+ const { output, environment } = createMemoryEnvironment()
85
+ environment.writeFile(
86
+ resolve(targetDir, CONFIG_FILE),
87
+ JSON.stringify(persistedOptions, null, 2),
88
+ )
89
+ const config = await readConfigFileFromEnvironment(environment, targetDir)
90
+
91
+ environment.finishRun()
92
+
93
+ expect(config).toEqual({
94
+ framework: 'react-cra',
95
+ projectName: 'foo',
96
+ typescript: false,
97
+ tailwind: false,
98
+ packageManager: 'pnpm',
99
+ chosenAddOns: [],
100
+ git: true,
101
+ mode: 'code-router',
102
+ version: 1,
103
+ })
104
+ })
68
105
  })
@@ -84,7 +84,6 @@ describe('createAppOptionsFromPersisted', () => {
84
84
  targetDir: '',
85
85
  starter: undefined,
86
86
  chosenAddOns: [],
87
- existingAddOns: [],
88
87
  version: 1,
89
88
  } as PersistedOptions
90
89
  const appOptions = await createAppOptionsFromPersisted(persistedOptions)
@@ -114,7 +113,6 @@ describe('createSerializedOptionsFromPersisted', () => {
114
113
  targetDir: '',
115
114
  starter: undefined,
116
115
  chosenAddOns: [],
117
- existingAddOns: [],
118
116
  version: 1,
119
117
  } as PersistedOptions
120
118
  const appOptions =
@@ -205,14 +203,12 @@ describe('readCurrentProjectOptions', () => {
205
203
  targetDir: '',
206
204
  starter: undefined,
207
205
  chosenAddOns: [],
208
- existingAddOns: [],
209
206
  version: 1,
210
207
  }),
211
208
  )
212
209
  const options = await readCurrentProjectOptions(environment)
213
210
  expect(options).toEqual({
214
211
  chosenAddOns: [],
215
- existingAddOns: [],
216
212
  framework: 'test',
217
213
  git: true,
218
214
  mode: 'code-router',
@@ -15,13 +15,13 @@ describe('readOrGenerateStarterInfo', () => {
15
15
  const starterInfo = await readOrGenerateStarterInfo({
16
16
  framework: 'test',
17
17
  version: 1,
18
- existingAddOns: [],
19
18
  starter: undefined,
20
19
  projectName: 'test',
21
20
  mode: 'code-router',
22
21
  typescript: true,
23
22
  tailwind: true,
24
23
  git: true,
24
+ chosenAddOns: [],
25
25
  })
26
26
  expect(starterInfo.id).toEqual('test-starter')
27
27
  })
@@ -33,7 +33,7 @@ describe('readOrGenerateStarterInfo', () => {
33
33
  JSON.stringify({
34
34
  framework: 'test',
35
35
  version: 1,
36
- existingAddOns: [],
36
+ chosenAddOns: [],
37
37
  starter: undefined,
38
38
  name: 'test-starter',
39
39
  mode: 'code-router',
@@ -45,7 +45,7 @@ describe('readOrGenerateStarterInfo', () => {
45
45
  const starterInfo = await readOrGenerateStarterInfo({
46
46
  framework: 'test',
47
47
  version: 1,
48
- existingAddOns: [],
48
+ chosenAddOns: [],
49
49
  starter: undefined,
50
50
  projectName: 'test',
51
51
  mode: 'code-router',