@tanstack/cta-engine 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/dist/add-ons.js +5 -14
- package/dist/add-to-app.js +118 -74
- package/dist/config-file.js +9 -7
- package/dist/create-app.js +112 -34
- package/dist/custom-add-ons/add-on.js +175 -0
- package/dist/custom-add-ons/shared.js +117 -0
- package/dist/custom-add-ons/starter.js +84 -0
- package/dist/environment.js +59 -12
- package/dist/file-helpers.js +108 -2
- package/dist/frameworks.js +15 -1
- package/dist/index.js +12 -5
- package/dist/integrations/shadcn.js +10 -4
- package/dist/options.js +9 -0
- package/dist/package-json.js +7 -4
- package/dist/special-steps/index.js +24 -0
- package/dist/special-steps/rimraf-node-modules.js +16 -0
- package/dist/template-file.js +3 -13
- package/dist/types/add-ons.d.ts +3 -4
- package/dist/types/add-to-app.d.ts +16 -3
- package/dist/types/config-file.d.ts +4 -3
- package/dist/types/create-app.d.ts +1 -7
- package/dist/types/custom-add-ons/add-on.d.ts +69 -0
- package/dist/types/custom-add-ons/shared.d.ts +15 -0
- package/dist/types/custom-add-ons/starter.d.ts +7 -0
- package/dist/types/environment.d.ts +2 -1
- package/dist/types/file-helpers.d.ts +10 -0
- package/dist/types/frameworks.d.ts +2 -0
- package/dist/types/index.d.ts +13 -6
- package/dist/types/integrations/shadcn.d.ts +1 -1
- package/dist/types/options.d.ts +2 -0
- package/dist/types/package-json.d.ts +5 -0
- package/dist/types/package-manager.d.ts +6 -2
- package/dist/types/special-steps/index.d.ts +2 -0
- package/dist/types/special-steps/rimraf-node-modules.d.ts +2 -0
- package/dist/types/template-file.d.ts +1 -1
- package/dist/types/types.d.ts +752 -70
- package/dist/types.js +65 -1
- package/package.json +9 -3
- package/src/add-ons.ts +7 -19
- package/src/add-to-app.ts +196 -102
- package/src/config-file.ts +16 -13
- package/src/create-app.ts +129 -75
- package/src/custom-add-ons/add-on.ts +261 -0
- package/src/custom-add-ons/shared.ts +161 -0
- package/src/custom-add-ons/starter.ts +126 -0
- package/src/environment.ts +70 -11
- package/src/file-helpers.ts +164 -2
- package/src/frameworks.ts +21 -1
- package/src/index.ts +46 -11
- package/src/integrations/shadcn.ts +14 -4
- package/src/options.ts +11 -0
- package/src/package-json.ts +13 -6
- package/src/special-steps/index.ts +36 -0
- package/src/special-steps/rimraf-node-modules.ts +25 -0
- package/src/template-file.ts +3 -18
- package/src/types.ts +143 -85
- package/tests/add-ons.test.ts +5 -5
- package/tests/add-to-app.test.ts +358 -0
- package/tests/config-file.test.ts +15 -11
- package/tests/create-app.test.ts +43 -67
- package/tests/custom-add-ons/add-on.test.ts +12 -0
- package/tests/custom-add-ons/shared.test.ts +257 -0
- package/tests/custom-add-ons/starter.test.ts +58 -0
- package/tests/environment.test.ts +19 -0
- package/tests/integrations/shadcn.test.ts +48 -63
- package/tests/options.test.ts +42 -0
- package/tests/setupVitest.ts +6 -0
- package/tests/template-file.test.ts +54 -91
- package/vitest.config.ts +2 -0
package/dist/types.js
CHANGED
|
@@ -1 +1,65 @@
|
|
|
1
|
-
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
export const AddOnBaseSchema = z.object({
|
|
3
|
+
id: z.string(),
|
|
4
|
+
name: z.string(),
|
|
5
|
+
description: z.string(),
|
|
6
|
+
author: z.string().optional(),
|
|
7
|
+
version: z.string().optional(),
|
|
8
|
+
link: z.string().optional(),
|
|
9
|
+
license: z.string().optional(),
|
|
10
|
+
warning: z.string().optional(),
|
|
11
|
+
type: z.enum(['add-on', 'example', 'starter', 'toolchain']),
|
|
12
|
+
command: z
|
|
13
|
+
.object({
|
|
14
|
+
command: z.string(),
|
|
15
|
+
args: z.array(z.string()).optional(),
|
|
16
|
+
})
|
|
17
|
+
.optional(),
|
|
18
|
+
routes: z
|
|
19
|
+
.array(z.object({
|
|
20
|
+
url: z.string().optional(),
|
|
21
|
+
name: z.string().optional(),
|
|
22
|
+
path: z.string(),
|
|
23
|
+
jsName: z.string(),
|
|
24
|
+
}))
|
|
25
|
+
.optional(),
|
|
26
|
+
packageAdditions: z
|
|
27
|
+
.object({
|
|
28
|
+
dependencies: z.record(z.string(), z.string()).optional(),
|
|
29
|
+
devDependencies: z.record(z.string(), z.string()).optional(),
|
|
30
|
+
scripts: z.record(z.string(), z.string()).optional(),
|
|
31
|
+
})
|
|
32
|
+
.optional(),
|
|
33
|
+
shadcnComponents: z.array(z.string()).optional(),
|
|
34
|
+
dependsOn: z.array(z.string()).optional(),
|
|
35
|
+
smallLogo: z.string().optional(),
|
|
36
|
+
logo: z.string().optional(),
|
|
37
|
+
addOnSpecialSteps: z.array(z.string()).optional(),
|
|
38
|
+
createSpecialSteps: z.array(z.string()).optional(),
|
|
39
|
+
});
|
|
40
|
+
export const StarterSchema = AddOnBaseSchema.extend({
|
|
41
|
+
framework: z.string(),
|
|
42
|
+
mode: z.string(),
|
|
43
|
+
typescript: z.boolean(),
|
|
44
|
+
tailwind: z.boolean(),
|
|
45
|
+
banner: z.string().optional(),
|
|
46
|
+
});
|
|
47
|
+
export const StarterCompiledSchema = StarterSchema.extend({
|
|
48
|
+
files: z.record(z.string(), z.string()),
|
|
49
|
+
deletedFiles: z.array(z.string()),
|
|
50
|
+
});
|
|
51
|
+
export const IntegrationSchema = z.object({
|
|
52
|
+
type: z.string(),
|
|
53
|
+
path: z.string(),
|
|
54
|
+
jsName: z.string(),
|
|
55
|
+
});
|
|
56
|
+
export const AddOnInfoSchema = AddOnBaseSchema.extend({
|
|
57
|
+
modes: z.array(z.string()),
|
|
58
|
+
integrations: z.array(IntegrationSchema).optional(),
|
|
59
|
+
phase: z.enum(['setup', 'add-on']),
|
|
60
|
+
readme: z.string().optional(),
|
|
61
|
+
});
|
|
62
|
+
export const AddOnCompiledSchema = AddOnInfoSchema.extend({
|
|
63
|
+
files: z.record(z.string(), z.string()),
|
|
64
|
+
deletedFiles: z.array(z.string()),
|
|
65
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/cta-engine",
|
|
3
|
-
"version": "0.10.0-alpha.
|
|
3
|
+
"version": "0.10.0-alpha.21",
|
|
4
4
|
"description": "Tanstack Application Builder Engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -24,17 +24,23 @@
|
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"ejs": "^3.1.10",
|
|
26
26
|
"execa": "^9.5.2",
|
|
27
|
+
"ignore": "^7.0.3",
|
|
27
28
|
"memfs": "^4.17.0",
|
|
28
|
-
"
|
|
29
|
+
"parse-gitignore": "^2.0.0",
|
|
30
|
+
"prettier": "^3.5.0",
|
|
31
|
+
"rimraf": "^6.0.1",
|
|
32
|
+
"zod": "^3.24.2"
|
|
29
33
|
},
|
|
30
34
|
"devDependencies": {
|
|
31
35
|
"@tanstack/config": "^0.16.2",
|
|
32
36
|
"@types/ejs": "^3.1.5",
|
|
33
37
|
"@types/node": "^22.13.4",
|
|
38
|
+
"@types/parse-gitignore": "^1.0.2",
|
|
34
39
|
"@vitest/coverage-v8": "3.1.1",
|
|
35
40
|
"eslint": "^9.20.0",
|
|
36
41
|
"typescript": "^5.6.3",
|
|
37
|
-
"vitest": "^3.0.8"
|
|
42
|
+
"vitest": "^3.0.8",
|
|
43
|
+
"vitest-fetch-mock": "^0.4.5"
|
|
38
44
|
},
|
|
39
45
|
"scripts": {}
|
|
40
46
|
}
|
package/src/add-ons.ts
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { loadRemoteAddOn } from './custom-add-ons/add-on.js'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
)
|
|
7
|
-
return framework.getAddOns().filter((a) => a.templates.includes(template))
|
|
3
|
+
import type { AddOn, Framework, Mode } from './types.js'
|
|
4
|
+
|
|
5
|
+
export function getAllAddOns(framework: Framework, mode: Mode): Array<AddOn> {
|
|
6
|
+
return framework.getAddOns().filter((a) => a.modes.includes(mode))
|
|
8
7
|
}
|
|
9
8
|
|
|
10
9
|
// Turn the list of chosen add-on IDs into a final list of add-ons by resolving dependencies
|
|
11
10
|
export async function finalizeAddOns(
|
|
12
11
|
framework: Framework,
|
|
13
|
-
|
|
12
|
+
mode: Mode,
|
|
14
13
|
chosenAddOnIDs: Array<string>,
|
|
15
14
|
): Promise<Array<AddOn>> {
|
|
16
15
|
const finalAddOnIDs = new Set(chosenAddOnIDs)
|
|
17
16
|
|
|
18
|
-
const addOns = getAllAddOns(framework,
|
|
17
|
+
const addOns = getAllAddOns(framework, mode)
|
|
19
18
|
|
|
20
19
|
for (const addOnID of finalAddOnIDs) {
|
|
21
20
|
let addOn: AddOn | undefined
|
|
@@ -48,14 +47,3 @@ export async function finalizeAddOns(
|
|
|
48
47
|
function loadAddOn(addOn: AddOn): AddOn {
|
|
49
48
|
return addOn
|
|
50
49
|
}
|
|
51
|
-
|
|
52
|
-
export async function loadRemoteAddOn(url: string): Promise<AddOn> {
|
|
53
|
-
const response = await fetch(url)
|
|
54
|
-
const fileContent = await response.json()
|
|
55
|
-
fileContent.id = url
|
|
56
|
-
return {
|
|
57
|
-
...fileContent,
|
|
58
|
-
getFiles: () => Promise.resolve(Object.keys(fileContent.files)),
|
|
59
|
-
getFileContents: (path: string) => Promise.resolve(fileContent.files[path]),
|
|
60
|
-
}
|
|
61
|
-
}
|
package/src/add-to-app.ts
CHANGED
|
@@ -1,180 +1,274 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { existsSync, statSync } from 'node:fs'
|
|
3
|
-
import { basename, dirname, resolve } from 'node:path'
|
|
4
|
-
import { execa, execaSync } from 'execa'
|
|
1
|
+
import { basename, resolve } from 'node:path'
|
|
5
2
|
|
|
6
3
|
import { CONFIG_FILE } from './constants.js'
|
|
7
4
|
import { finalizeAddOns } from './add-ons.js'
|
|
8
5
|
import { getFrameworkById } from './frameworks.js'
|
|
9
|
-
import {
|
|
10
|
-
createDefaultEnvironment,
|
|
11
|
-
createMemoryEnvironment,
|
|
12
|
-
} from './environment.js'
|
|
6
|
+
import { createMemoryEnvironment } from './environment.js'
|
|
13
7
|
import { createApp } from './create-app.js'
|
|
14
|
-
import {
|
|
15
|
-
|
|
8
|
+
import {
|
|
9
|
+
readConfigFileFromEnvironment,
|
|
10
|
+
writeConfigFileToEnvironment,
|
|
11
|
+
} from './config-file.js'
|
|
12
|
+
import { formatCommand } from './utils.js'
|
|
13
|
+
import { packageManagerInstall } from './package-manager.js'
|
|
14
|
+
import {
|
|
15
|
+
isBase64,
|
|
16
|
+
recursivelyGatherFilesFromEnvironment,
|
|
17
|
+
} from './file-helpers.js'
|
|
18
|
+
import { mergePackageJSON } from './package-json.js'
|
|
19
|
+
import { runSpecialSteps } from './special-steps/index.js'
|
|
16
20
|
|
|
17
|
-
import type { Environment, Options } from './types.js'
|
|
21
|
+
import type { Environment, Mode, Options } from './types.js'
|
|
18
22
|
import type { PersistedOptions } from './config-file.js'
|
|
19
23
|
|
|
20
|
-
function
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
export async function hasPendingGitChanges(
|
|
25
|
+
environment: Environment,
|
|
26
|
+
cwd: string,
|
|
27
|
+
) {
|
|
28
|
+
const { stdout } = await environment.execute(
|
|
29
|
+
'git',
|
|
30
|
+
['status', '--porcelain'],
|
|
31
|
+
cwd,
|
|
32
|
+
)
|
|
33
|
+
return stdout.length > 0
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
async function createOptions(
|
|
30
37
|
json: PersistedOptions,
|
|
31
38
|
addOns: Array<string>,
|
|
32
|
-
|
|
39
|
+
targetDir: string,
|
|
40
|
+
): Promise<Options> {
|
|
33
41
|
const framework = getFrameworkById(json.framework)
|
|
34
42
|
|
|
43
|
+
// TODO: Load the starter
|
|
44
|
+
|
|
35
45
|
return {
|
|
36
46
|
...json,
|
|
37
47
|
framework,
|
|
38
48
|
tailwind: true,
|
|
39
49
|
addOns: true,
|
|
40
|
-
chosenAddOns: await finalizeAddOns(framework!, json.mode as
|
|
50
|
+
chosenAddOns: await finalizeAddOns(framework!, json.mode as Mode, [
|
|
41
51
|
...json.existingAddOns,
|
|
42
52
|
...addOns,
|
|
43
53
|
]),
|
|
44
|
-
|
|
54
|
+
targetDir,
|
|
55
|
+
} as Options
|
|
45
56
|
}
|
|
46
57
|
|
|
47
58
|
async function runCreateApp(options: Required<Options>) {
|
|
48
|
-
const { environment, output } = createMemoryEnvironment()
|
|
49
|
-
await createApp(
|
|
50
|
-
silent: true,
|
|
51
|
-
environment,
|
|
52
|
-
cwd: process.cwd(),
|
|
53
|
-
name: 'create-tsrouter-app',
|
|
54
|
-
})
|
|
59
|
+
const { environment, output } = createMemoryEnvironment(options.targetDir)
|
|
60
|
+
await createApp(environment, options)
|
|
55
61
|
return output
|
|
56
62
|
}
|
|
57
63
|
|
|
58
|
-
export async function
|
|
59
|
-
addOns: Array<string>,
|
|
60
|
-
{
|
|
61
|
-
silent = false,
|
|
62
|
-
}: {
|
|
63
|
-
silent?: boolean
|
|
64
|
-
} = {},
|
|
64
|
+
export async function getCurrentConfiguration(
|
|
65
65
|
environment: Environment,
|
|
66
|
+
cwd: string,
|
|
66
67
|
) {
|
|
67
|
-
const persistedOptions = await
|
|
68
|
+
const persistedOptions = await readConfigFileFromEnvironment(environment, cwd)
|
|
68
69
|
if (!persistedOptions) {
|
|
69
70
|
environment.error(
|
|
70
71
|
'There is no .cta.json file in your project.',
|
|
71
72
|
'This is probably because this was created with an older version of create-tsrouter-app.',
|
|
72
73
|
)
|
|
73
|
-
return
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (!silent) {
|
|
77
|
-
environment.intro(`Adding ${addOns.join(', ')} to the project...`)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (await hasPendingGitChanges()) {
|
|
81
|
-
environment.error(
|
|
82
|
-
'You have pending git changes.',
|
|
83
|
-
'Please commit or stash them before adding add-ons.',
|
|
84
|
-
)
|
|
85
|
-
return
|
|
74
|
+
return undefined
|
|
86
75
|
}
|
|
87
76
|
|
|
88
|
-
|
|
77
|
+
return persistedOptions
|
|
78
|
+
}
|
|
89
79
|
|
|
90
|
-
|
|
80
|
+
export async function writeFiles(
|
|
81
|
+
environment: Environment,
|
|
82
|
+
cwd: string,
|
|
83
|
+
output: {
|
|
84
|
+
files: Record<string, string>
|
|
85
|
+
deletedFiles: Array<string>
|
|
86
|
+
},
|
|
87
|
+
forced: boolean,
|
|
88
|
+
) {
|
|
89
|
+
const currentFiles = await recursivelyGatherFilesFromEnvironment(
|
|
90
|
+
environment,
|
|
91
|
+
cwd,
|
|
92
|
+
false,
|
|
93
|
+
)
|
|
91
94
|
|
|
92
95
|
const overwrittenFiles: Array<string> = []
|
|
93
96
|
const changedFiles: Array<string> = []
|
|
94
|
-
const contentMap = new Map<string, string>()
|
|
95
97
|
for (const file of Object.keys(output.files)) {
|
|
96
|
-
const relativeFile = file.replace(
|
|
97
|
-
if (
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
if (
|
|
101
|
-
['package.json', CONFIG_FILE].includes(basename(file)) ||
|
|
102
|
-
contents !== output.files[file]
|
|
103
|
-
) {
|
|
104
|
-
overwrittenFiles.push(relativeFile)
|
|
105
|
-
contentMap.set(relativeFile, output.files[file])
|
|
106
|
-
}
|
|
98
|
+
const relativeFile = file.replace(cwd, '')
|
|
99
|
+
if (currentFiles[relativeFile]) {
|
|
100
|
+
if (currentFiles[relativeFile] !== output.files[file]) {
|
|
101
|
+
overwrittenFiles.push(relativeFile)
|
|
107
102
|
}
|
|
108
103
|
} else {
|
|
109
104
|
changedFiles.push(relativeFile)
|
|
110
|
-
contentMap.set(relativeFile, output.files[file])
|
|
111
105
|
}
|
|
112
106
|
}
|
|
113
107
|
|
|
114
|
-
if (overwrittenFiles.length
|
|
108
|
+
if (!forced && overwrittenFiles.length) {
|
|
115
109
|
environment.warn(
|
|
116
|
-
'The following will be overwritten
|
|
117
|
-
overwrittenFiles.join('\n'),
|
|
110
|
+
'The following will be overwritten',
|
|
111
|
+
[...overwrittenFiles, ...output.deletedFiles].join('\n'),
|
|
118
112
|
)
|
|
119
113
|
const shouldContinue = await environment.confirm('Do you want to continue?')
|
|
120
114
|
if (!shouldContinue) {
|
|
121
|
-
|
|
115
|
+
throw new Error('User cancelled')
|
|
122
116
|
}
|
|
123
117
|
}
|
|
124
118
|
|
|
119
|
+
for (const file of output.deletedFiles) {
|
|
120
|
+
if (environment.exists(resolve(cwd, file))) {
|
|
121
|
+
await environment.deleteFile(resolve(cwd, file))
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
environment.startStep({
|
|
126
|
+
id: 'write-files',
|
|
127
|
+
type: 'file',
|
|
128
|
+
message: 'Writing add-on files...',
|
|
129
|
+
})
|
|
130
|
+
|
|
125
131
|
for (const file of [...changedFiles, ...overwrittenFiles]) {
|
|
126
|
-
const targetFile = `.${file}`
|
|
127
132
|
const fName = basename(file)
|
|
128
|
-
const contents =
|
|
133
|
+
const contents = output.files[file]
|
|
129
134
|
if (fName === 'package.json') {
|
|
130
135
|
const currentJson = JSON.parse(
|
|
131
|
-
|
|
136
|
+
await environment.readFile(resolve(cwd, file)),
|
|
137
|
+
)
|
|
138
|
+
const newJSON = mergePackageJSON(currentJson, JSON.parse(contents))
|
|
139
|
+
environment.writeFile(
|
|
140
|
+
resolve(cwd, file),
|
|
141
|
+
JSON.stringify(newJSON, null, 2),
|
|
132
142
|
)
|
|
133
|
-
const newJson = JSON.parse(contents)
|
|
134
|
-
|
|
135
|
-
currentJson.scripts = newJson.scripts
|
|
136
|
-
currentJson.dependencies = sortObject({
|
|
137
|
-
...currentJson.dependencies,
|
|
138
|
-
...newJson.dependencies,
|
|
139
|
-
})
|
|
140
|
-
currentJson.devDependencies = sortObject({
|
|
141
|
-
...currentJson.devDependencies,
|
|
142
|
-
...newJson.devDependencies,
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
await writeFile(targetFile, JSON.stringify(currentJson, null, 2))
|
|
146
143
|
} else if (fName !== CONFIG_FILE) {
|
|
147
|
-
|
|
148
|
-
|
|
144
|
+
if (isBase64(contents)) {
|
|
145
|
+
await environment.writeFileBase64(resolve(cwd, file), contents)
|
|
146
|
+
} else {
|
|
147
|
+
await environment.writeFile(resolve(cwd, file), contents)
|
|
148
|
+
}
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
152
|
+
environment.finishStep('write-files', 'Add-on files written')
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function runNewCommands(
|
|
156
|
+
environment: Environment,
|
|
157
|
+
originalOptions: PersistedOptions,
|
|
158
|
+
cwd: string,
|
|
159
|
+
output: {
|
|
160
|
+
commands: Array<{
|
|
161
|
+
command: string
|
|
162
|
+
args: Array<string>
|
|
163
|
+
}>
|
|
164
|
+
},
|
|
165
|
+
) {
|
|
166
|
+
const originalOutput = await runCreateApp({
|
|
167
|
+
...(await createOptions(originalOptions, [], cwd)),
|
|
168
|
+
} as Required<Options>)
|
|
169
|
+
|
|
156
170
|
const originalCommands = new Set(
|
|
157
171
|
originalOutput.commands.map((c) => [c.command, ...c.args].join(' ')),
|
|
158
172
|
)
|
|
173
|
+
|
|
159
174
|
for (const command of output.commands) {
|
|
160
175
|
const commandString = [command.command, ...command.args].join(' ')
|
|
161
176
|
if (!originalCommands.has(commandString)) {
|
|
162
|
-
|
|
177
|
+
environment.startStep({
|
|
178
|
+
id: 'run-commands',
|
|
179
|
+
type: 'command',
|
|
180
|
+
message: `Running ${formatCommand({ command: command.command, args: command.args })}...`,
|
|
181
|
+
})
|
|
182
|
+
await environment.execute(command.command, command.args, cwd)
|
|
183
|
+
environment.finishStep('run-commands', 'Setup commands complete')
|
|
163
184
|
}
|
|
164
185
|
}
|
|
165
|
-
|
|
166
|
-
writeConfigFile(realEnvironment, process.cwd(), newOptions)
|
|
186
|
+
}
|
|
167
187
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
188
|
+
export async function addToApp(
|
|
189
|
+
environment: Environment,
|
|
190
|
+
addOns: Array<string>,
|
|
191
|
+
cwd: string,
|
|
192
|
+
options?: {
|
|
193
|
+
forced?: boolean
|
|
194
|
+
},
|
|
195
|
+
) {
|
|
196
|
+
const persistedOptions = await getCurrentConfiguration(environment, cwd)
|
|
197
|
+
if (!persistedOptions) {
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (!options?.forced && (await hasPendingGitChanges(environment, cwd))) {
|
|
202
|
+
environment.error(
|
|
203
|
+
'You have pending git changes.',
|
|
204
|
+
'Please commit or stash them before adding add-ons.',
|
|
205
|
+
)
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
environment.intro(`Adding ${addOns.join(', ')} to the project...`)
|
|
210
|
+
environment.startStep({
|
|
211
|
+
id: 'processing-new-app-setup',
|
|
212
|
+
type: 'file',
|
|
213
|
+
message: 'Processing new app setup...',
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
const newOptions = await createOptions(persistedOptions, addOns, cwd)
|
|
217
|
+
|
|
218
|
+
const output = await runCreateApp({
|
|
219
|
+
...newOptions,
|
|
220
|
+
targetDir: cwd,
|
|
221
|
+
} as Required<Options>)
|
|
222
|
+
|
|
223
|
+
await writeFiles(environment, cwd, output, !!options?.forced)
|
|
224
|
+
|
|
225
|
+
environment.finishStep(
|
|
226
|
+
'processing-new-app-setup',
|
|
227
|
+
'Application files written',
|
|
174
228
|
)
|
|
175
|
-
s?.stop(`Installed dependencies`)
|
|
176
229
|
|
|
177
|
-
|
|
178
|
-
|
|
230
|
+
// Run any special steps for the new add-ons
|
|
231
|
+
|
|
232
|
+
const specialSteps = new Set<string>([])
|
|
233
|
+
for (const addOn of newOptions.chosenAddOns) {
|
|
234
|
+
for (const step of addOn.addOnSpecialSteps || []) {
|
|
235
|
+
if (addOns.includes(addOn.id)) {
|
|
236
|
+
specialSteps.add(step)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
179
239
|
}
|
|
240
|
+
if (specialSteps.size) {
|
|
241
|
+
await runSpecialSteps(environment, newOptions, Array.from(specialSteps))
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Install dependencies
|
|
245
|
+
|
|
246
|
+
environment.startStep({
|
|
247
|
+
id: 'install-dependencies',
|
|
248
|
+
type: 'package-manager',
|
|
249
|
+
message: `Installing dependencies via ${newOptions.packageManager}...`,
|
|
250
|
+
})
|
|
251
|
+
const s = environment.spinner()
|
|
252
|
+
s.start(`Installing dependencies via ${newOptions.packageManager}...`)
|
|
253
|
+
await packageManagerInstall(
|
|
254
|
+
environment,
|
|
255
|
+
newOptions.targetDir,
|
|
256
|
+
newOptions.packageManager,
|
|
257
|
+
)
|
|
258
|
+
s.stop(`Installed dependencies`)
|
|
259
|
+
environment.finishStep('install-dependencies', 'Dependencies installed')
|
|
260
|
+
|
|
261
|
+
// Handle new commands
|
|
262
|
+
|
|
263
|
+
await runNewCommands(environment, persistedOptions, cwd, output)
|
|
264
|
+
|
|
265
|
+
environment.startStep({
|
|
266
|
+
id: 'write-config-file',
|
|
267
|
+
type: 'file',
|
|
268
|
+
message: 'Writing config file...',
|
|
269
|
+
})
|
|
270
|
+
writeConfigFileToEnvironment(environment, newOptions)
|
|
271
|
+
environment.finishStep('write-config-file', 'Config file written')
|
|
272
|
+
|
|
273
|
+
environment.outro('Add-ons added successfully!')
|
|
180
274
|
}
|
package/src/config-file.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises'
|
|
2
1
|
import { resolve } from 'node:path'
|
|
3
2
|
|
|
4
3
|
import { CONFIG_FILE } from './constants.js'
|
|
@@ -7,40 +6,44 @@ import type { Environment, Options } from './types.js'
|
|
|
7
6
|
|
|
8
7
|
export type PersistedOptions = Omit<
|
|
9
8
|
Partial<Options>,
|
|
10
|
-
'addOns' | 'chosenAddOns' | 'framework'
|
|
9
|
+
'addOns' | 'chosenAddOns' | 'framework' | 'starter' | 'targetDir'
|
|
11
10
|
> & {
|
|
12
11
|
framework: string
|
|
13
12
|
version: number
|
|
14
13
|
existingAddOns: Array<string>
|
|
14
|
+
starter?: string
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
environment: Environment,
|
|
19
|
-
targetDir: string,
|
|
20
|
-
options: Options,
|
|
21
|
-
) {
|
|
17
|
+
function createPersistedOptions(options: Options): PersistedOptions {
|
|
22
18
|
/* eslint-disable unused-imports/no-unused-vars */
|
|
23
|
-
const {
|
|
19
|
+
const { chosenAddOns, framework, targetDir, ...rest } = options
|
|
24
20
|
/* eslint-enable unused-imports/no-unused-vars */
|
|
25
|
-
|
|
21
|
+
return {
|
|
26
22
|
...rest,
|
|
27
23
|
version: 1,
|
|
28
24
|
framework: options.framework.id,
|
|
29
25
|
existingAddOns: options.chosenAddOns.map((addOn) => addOn.id),
|
|
26
|
+
starter: options.starter?.id ?? undefined,
|
|
30
27
|
}
|
|
28
|
+
}
|
|
31
29
|
|
|
30
|
+
export async function writeConfigFileToEnvironment(
|
|
31
|
+
environment: Environment,
|
|
32
|
+
options: Options,
|
|
33
|
+
) {
|
|
32
34
|
await environment.writeFile(
|
|
33
|
-
resolve(targetDir, CONFIG_FILE),
|
|
34
|
-
JSON.stringify(
|
|
35
|
+
resolve(options.targetDir, CONFIG_FILE),
|
|
36
|
+
JSON.stringify(createPersistedOptions(options), null, 2),
|
|
35
37
|
)
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
export async function
|
|
40
|
+
export async function readConfigFileFromEnvironment(
|
|
41
|
+
environment: Environment,
|
|
39
42
|
targetDir: string,
|
|
40
43
|
): Promise<PersistedOptions | null> {
|
|
41
44
|
try {
|
|
42
45
|
const configFile = resolve(targetDir, CONFIG_FILE)
|
|
43
|
-
const config = await readFile(configFile
|
|
46
|
+
const config = await environment.readFile(configFile)
|
|
44
47
|
|
|
45
48
|
// TODO: Look for old config files and convert them to the new format
|
|
46
49
|
|