@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/src/create-app.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { basename, resolve } from 'node:path'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { isBase64 } from './file-helpers.js'
|
|
4
4
|
import { formatCommand } from './utils.js'
|
|
5
|
-
import {
|
|
5
|
+
import { writeConfigFileToEnvironment } from './config-file.js'
|
|
6
6
|
import {
|
|
7
7
|
getPackageManagerScriptCommand,
|
|
8
8
|
packageManagerInstall,
|
|
@@ -11,93 +11,164 @@ import { createPackageJSON } from './package-json.js'
|
|
|
11
11
|
import { createTemplateFile } from './template-file.js'
|
|
12
12
|
import { installShadcnComponents } from './integrations/shadcn.js'
|
|
13
13
|
import { setupGit } from './integrations/git.js'
|
|
14
|
+
import { runSpecialSteps } from './special-steps/index.js'
|
|
14
15
|
|
|
15
16
|
import type { Environment, FileBundleHandler, Options } from './types.js'
|
|
16
17
|
|
|
17
|
-
async function writeFiles(
|
|
18
|
-
environment
|
|
19
|
-
targetDir: string,
|
|
20
|
-
options: Options,
|
|
21
|
-
) {
|
|
22
|
-
const templateFileFromContent = createTemplateFile(
|
|
23
|
-
environment,
|
|
24
|
-
options,
|
|
25
|
-
targetDir,
|
|
26
|
-
)
|
|
18
|
+
async function writeFiles(environment: Environment, options: Options) {
|
|
19
|
+
const templateFileFromContent = createTemplateFile(environment, options)
|
|
27
20
|
|
|
28
21
|
async function writeFileBundle(bundle: FileBundleHandler) {
|
|
29
22
|
const files = await bundle.getFiles()
|
|
30
23
|
for (const file of files) {
|
|
31
24
|
const contents = await bundle.getFileContents(file)
|
|
32
|
-
const
|
|
33
|
-
if (
|
|
34
|
-
await environment.
|
|
25
|
+
const isBinaryFile = isBase64(contents)
|
|
26
|
+
if (isBinaryFile) {
|
|
27
|
+
await environment.writeFileBase64(
|
|
28
|
+
resolve(options.targetDir, file),
|
|
29
|
+
contents,
|
|
30
|
+
)
|
|
35
31
|
} else {
|
|
36
32
|
await templateFileFromContent(file, contents)
|
|
37
33
|
}
|
|
38
34
|
}
|
|
35
|
+
|
|
36
|
+
const deletedFiles = await bundle.getDeletedFiles()
|
|
37
|
+
for (const file of deletedFiles) {
|
|
38
|
+
await environment.deleteFile(resolve(options.targetDir, file))
|
|
39
|
+
}
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
environment.startStep({
|
|
43
|
+
id: 'write-framework-files',
|
|
44
|
+
type: 'file',
|
|
45
|
+
message: 'Writing framework files...',
|
|
46
|
+
})
|
|
41
47
|
await writeFileBundle(options.framework)
|
|
48
|
+
environment.finishStep('write-framework-files', 'Framework files written')
|
|
42
49
|
|
|
50
|
+
let wroteAddonFiles = false
|
|
43
51
|
for (const type of ['add-on', 'example', 'toolchain']) {
|
|
44
52
|
for (const phase of ['setup', 'add-on', 'example']) {
|
|
45
53
|
for (const addOn of options.chosenAddOns.filter(
|
|
46
54
|
(addOn) => addOn.phase === phase && addOn.type === type,
|
|
47
55
|
)) {
|
|
56
|
+
environment.startStep({
|
|
57
|
+
id: 'write-addon-files',
|
|
58
|
+
type: 'file',
|
|
59
|
+
message: `Writing ${addOn.name} files...`,
|
|
60
|
+
})
|
|
48
61
|
await writeFileBundle(addOn)
|
|
62
|
+
wroteAddonFiles = true
|
|
49
63
|
}
|
|
50
64
|
}
|
|
51
65
|
}
|
|
66
|
+
if (wroteAddonFiles) {
|
|
67
|
+
environment.finishStep('write-addon-files', 'Add-on files written')
|
|
68
|
+
}
|
|
52
69
|
|
|
53
70
|
if (options.starter) {
|
|
71
|
+
environment.startStep({
|
|
72
|
+
id: 'write-starter-files',
|
|
73
|
+
type: 'file',
|
|
74
|
+
message: 'Writing starter files...',
|
|
75
|
+
})
|
|
54
76
|
await writeFileBundle(options.starter)
|
|
77
|
+
environment.finishStep('write-starter-files', 'Starter files written')
|
|
55
78
|
}
|
|
56
79
|
|
|
80
|
+
environment.startStep({
|
|
81
|
+
id: 'write-package-json',
|
|
82
|
+
type: 'file',
|
|
83
|
+
message: 'Writing package.json...',
|
|
84
|
+
})
|
|
57
85
|
await environment.writeFile(
|
|
58
|
-
resolve(targetDir, './package.json'),
|
|
86
|
+
resolve(options.targetDir, './package.json'),
|
|
59
87
|
JSON.stringify(createPackageJSON(options), null, 2),
|
|
60
88
|
)
|
|
61
|
-
|
|
62
|
-
|
|
89
|
+
environment.finishStep('write-package-json', 'Package.json written')
|
|
90
|
+
|
|
91
|
+
environment.startStep({
|
|
92
|
+
id: 'write-config-file',
|
|
93
|
+
type: 'file',
|
|
94
|
+
message: 'Writing config file...',
|
|
95
|
+
})
|
|
96
|
+
await writeConfigFileToEnvironment(environment, options)
|
|
97
|
+
environment.finishStep('write-config-file', 'Config file written')
|
|
63
98
|
}
|
|
64
99
|
|
|
65
100
|
async function runCommandsAndInstallDependencies(
|
|
66
101
|
environment: Environment,
|
|
67
|
-
targetDir: string,
|
|
68
102
|
options: Options,
|
|
69
|
-
silent: boolean,
|
|
70
103
|
) {
|
|
71
|
-
const s =
|
|
104
|
+
const s = environment.spinner()
|
|
72
105
|
|
|
73
106
|
// Setup git
|
|
74
107
|
if (options.git) {
|
|
75
|
-
s
|
|
76
|
-
|
|
77
|
-
|
|
108
|
+
s.start(`Initializing git repository...`)
|
|
109
|
+
environment.startStep({
|
|
110
|
+
id: 'initialize-git-repository',
|
|
111
|
+
type: 'command',
|
|
112
|
+
message: 'Initializing git repository...',
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
await setupGit(environment, options.targetDir)
|
|
116
|
+
|
|
117
|
+
environment.finishStep(
|
|
118
|
+
'initialize-git-repository',
|
|
119
|
+
'Initialized git repository',
|
|
120
|
+
)
|
|
121
|
+
s.stop(`Initialized git repository`)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Run any special steps for the new add-ons
|
|
125
|
+
const specialSteps = new Set<string>([])
|
|
126
|
+
for (const addOn of options.chosenAddOns) {
|
|
127
|
+
for (const step of addOn.createSpecialSteps || []) {
|
|
128
|
+
specialSteps.add(step)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (specialSteps.size) {
|
|
132
|
+
await runSpecialSteps(environment, options, Array.from(specialSteps))
|
|
78
133
|
}
|
|
79
134
|
|
|
80
135
|
// Install dependencies
|
|
81
|
-
s
|
|
136
|
+
s.start(`Installing dependencies via ${options.packageManager}...`)
|
|
137
|
+
environment.startStep({
|
|
138
|
+
id: 'install-dependencies',
|
|
139
|
+
type: 'package-manager',
|
|
140
|
+
message: `Installing dependencies via ${options.packageManager}...`,
|
|
141
|
+
})
|
|
82
142
|
await packageManagerInstall(
|
|
83
143
|
environment,
|
|
84
|
-
|
|
144
|
+
options.targetDir,
|
|
85
145
|
options.packageManager,
|
|
86
146
|
)
|
|
87
|
-
|
|
147
|
+
environment.finishStep('install-dependencies', 'Installed dependencies')
|
|
148
|
+
s.stop(`Installed dependencies`)
|
|
88
149
|
|
|
89
150
|
for (const phase of ['setup', 'add-on', 'example']) {
|
|
90
151
|
for (const addOn of options.chosenAddOns.filter(
|
|
91
152
|
(addOn) =>
|
|
92
153
|
addOn.phase === phase && addOn.command && addOn.command.command,
|
|
93
154
|
)) {
|
|
94
|
-
s
|
|
155
|
+
s.start(`Running commands for ${addOn.name}...`)
|
|
156
|
+
const cmd = formatCommand({
|
|
157
|
+
command: addOn.command!.command,
|
|
158
|
+
args: addOn.command!.args || [],
|
|
159
|
+
})
|
|
160
|
+
environment.startStep({
|
|
161
|
+
id: 'run-commands',
|
|
162
|
+
type: 'command',
|
|
163
|
+
message: cmd,
|
|
164
|
+
})
|
|
95
165
|
await environment.execute(
|
|
96
166
|
addOn.command!.command,
|
|
97
167
|
addOn.command!.args || [],
|
|
98
|
-
|
|
168
|
+
options.targetDir,
|
|
99
169
|
)
|
|
100
|
-
|
|
170
|
+
environment.finishStep('run-commands', 'Setup commands complete')
|
|
171
|
+
s.stop(`${addOn.name} commands complete`)
|
|
101
172
|
}
|
|
102
173
|
}
|
|
103
174
|
|
|
@@ -107,24 +178,31 @@ async function runCommandsAndInstallDependencies(
|
|
|
107
178
|
options.starter.command &&
|
|
108
179
|
options.starter.command.command
|
|
109
180
|
) {
|
|
110
|
-
s
|
|
181
|
+
s.start(`Setting up starter ${options.starter.name}...`)
|
|
182
|
+
const cmd = formatCommand({
|
|
183
|
+
command: options.starter.command.command,
|
|
184
|
+
args: options.starter.command.args || [],
|
|
185
|
+
})
|
|
186
|
+
environment.startStep({
|
|
187
|
+
id: 'run-starter-command',
|
|
188
|
+
type: 'command',
|
|
189
|
+
message: cmd,
|
|
190
|
+
})
|
|
191
|
+
|
|
111
192
|
await environment.execute(
|
|
112
193
|
options.starter.command.command,
|
|
113
194
|
options.starter.command.args || [],
|
|
114
|
-
|
|
195
|
+
options.targetDir,
|
|
115
196
|
)
|
|
116
|
-
|
|
197
|
+
|
|
198
|
+
environment.finishStep('run-starter-command', 'Starter command complete')
|
|
199
|
+
s.stop(`${options.starter.name} commands complete`)
|
|
117
200
|
}
|
|
118
201
|
|
|
119
|
-
await installShadcnComponents(environment, targetDir, options
|
|
202
|
+
await installShadcnComponents(environment, options.targetDir, options)
|
|
120
203
|
}
|
|
121
204
|
|
|
122
|
-
function report(
|
|
123
|
-
environment: Environment,
|
|
124
|
-
options: Options,
|
|
125
|
-
appName: string,
|
|
126
|
-
targetDir: string,
|
|
127
|
-
) {
|
|
205
|
+
function report(environment: Environment, options: Options) {
|
|
128
206
|
const warnings: Array<string> = []
|
|
129
207
|
for (const addOn of options.chosenAddOns) {
|
|
130
208
|
if (addOn.warning) {
|
|
@@ -141,53 +219,29 @@ function report(
|
|
|
141
219
|
if (environment.getErrors().length) {
|
|
142
220
|
errorStatement = `
|
|
143
221
|
|
|
144
|
-
Errors were encountered during
|
|
222
|
+
Errors were encountered during the creation of your app:
|
|
145
223
|
|
|
146
224
|
${environment.getErrors().join('\n')}`
|
|
147
225
|
}
|
|
148
226
|
|
|
149
|
-
environment.outro(
|
|
227
|
+
environment.outro(
|
|
228
|
+
`Your ${environment.appName} app is ready in '${basename(options.targetDir)}'.
|
|
150
229
|
|
|
151
230
|
Use the following commands to start your app:
|
|
152
231
|
% cd ${options.projectName}
|
|
153
232
|
% ${formatCommand(
|
|
154
|
-
|
|
155
|
-
|
|
233
|
+
getPackageManagerScriptCommand(options.packageManager, ['dev']),
|
|
234
|
+
)}
|
|
156
235
|
|
|
157
|
-
Please check the README.md for more information on testing, styling, adding routes, etc.${errorStatement}
|
|
236
|
+
Please check the README.md for more information on testing, styling, adding routes, etc.${errorStatement}`,
|
|
237
|
+
)
|
|
158
238
|
}
|
|
159
239
|
|
|
160
|
-
export async function createApp(
|
|
161
|
-
options: Options,
|
|
162
|
-
{
|
|
163
|
-
silent = false,
|
|
164
|
-
environment,
|
|
165
|
-
cwd,
|
|
166
|
-
appName = 'TanStack',
|
|
167
|
-
}: {
|
|
168
|
-
silent?: boolean
|
|
169
|
-
environment: Environment
|
|
170
|
-
cwd?: string
|
|
171
|
-
name?: string
|
|
172
|
-
appName?: string
|
|
173
|
-
},
|
|
174
|
-
) {
|
|
240
|
+
export async function createApp(environment: Environment, options: Options) {
|
|
175
241
|
environment.startRun()
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
await writeFiles(environment, targetDir, options)
|
|
180
|
-
|
|
181
|
-
await runCommandsAndInstallDependencies(
|
|
182
|
-
environment,
|
|
183
|
-
targetDir,
|
|
184
|
-
options,
|
|
185
|
-
silent,
|
|
186
|
-
)
|
|
187
|
-
|
|
242
|
+
await writeFiles(environment, options)
|
|
243
|
+
await runCommandsAndInstallDependencies(environment, options)
|
|
188
244
|
environment.finishRun()
|
|
189
245
|
|
|
190
|
-
|
|
191
|
-
report(environment, options, appName, targetDir)
|
|
192
|
-
}
|
|
246
|
+
report(environment, options)
|
|
193
247
|
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises'
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { basename, dirname, resolve } from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { AddOnCompiledSchema } from '../types.js'
|
|
6
|
+
import { createIgnore, recursivelyGatherFiles } from '../file-helpers.js'
|
|
7
|
+
import {
|
|
8
|
+
compareFilesRecursively,
|
|
9
|
+
createAppOptionsFromPersisted,
|
|
10
|
+
createPackageAdditions,
|
|
11
|
+
readCurrentProjectOptions,
|
|
12
|
+
runCreateApp,
|
|
13
|
+
} from './shared.js'
|
|
14
|
+
|
|
15
|
+
import type { PersistedOptions } from '../config-file'
|
|
16
|
+
import type {
|
|
17
|
+
AddOn,
|
|
18
|
+
AddOnCompiled,
|
|
19
|
+
AddOnInfo,
|
|
20
|
+
Environment,
|
|
21
|
+
Options,
|
|
22
|
+
} from '../types'
|
|
23
|
+
|
|
24
|
+
const ADD_ON_DIR = '.add-on'
|
|
25
|
+
|
|
26
|
+
export const ADD_ON_IGNORE_FILES: Array<string> = [
|
|
27
|
+
'main.jsx',
|
|
28
|
+
'App.jsx',
|
|
29
|
+
'main.tsx',
|
|
30
|
+
'App.tsx',
|
|
31
|
+
'routeTree.gen.ts',
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
const INFO_FILE = '.add-on/info.json'
|
|
35
|
+
const COMPILED_FILE = 'add-on.json'
|
|
36
|
+
|
|
37
|
+
const ASSETS_DIR = 'assets'
|
|
38
|
+
|
|
39
|
+
export function camelCase(str: string) {
|
|
40
|
+
return str
|
|
41
|
+
.split(/(\.|-|\/)/)
|
|
42
|
+
.filter((part) => /^[a-zA-Z]+$/.test(part))
|
|
43
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
44
|
+
.join('')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function templatize(routeCode: string, routeFile: string) {
|
|
48
|
+
let code = routeCode
|
|
49
|
+
|
|
50
|
+
// Replace the import
|
|
51
|
+
code = code.replace(
|
|
52
|
+
/import { createFileRoute } from ['"]@tanstack\/react-router['"]/g,
|
|
53
|
+
`import { <% if (fileRouter) { %>createFileRoute<% } else { %>createRoute<% } %> } from '@tanstack/react-router'`,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
// Extract route path and definition, then transform the route declaration
|
|
57
|
+
const routeMatch = code.match(
|
|
58
|
+
/export\s+const\s+Route\s*=\s*createFileRoute\(['"]([^'"]+)['"]\)\s*\(\{([^}]+)\}\)/,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
let path = ''
|
|
62
|
+
|
|
63
|
+
if (routeMatch) {
|
|
64
|
+
const fullMatch = routeMatch[0]
|
|
65
|
+
path = routeMatch[1]
|
|
66
|
+
const routeDefinition = routeMatch[2]
|
|
67
|
+
code = code.replace(
|
|
68
|
+
fullMatch,
|
|
69
|
+
`<% if (codeRouter) { %>
|
|
70
|
+
import type { RootRoute } from '@tanstack/react-router'
|
|
71
|
+
<% } else { %>
|
|
72
|
+
export const Route = createFileRoute('${path}')({${routeDefinition}})
|
|
73
|
+
<% } %>`,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
code += `
|
|
77
|
+
<% if (codeRouter) { %>
|
|
78
|
+
export default (parentRoute: RootRoute) => createRoute({
|
|
79
|
+
path: '${path}',
|
|
80
|
+
${routeDefinition}
|
|
81
|
+
getParentRoute: () => parentRoute,
|
|
82
|
+
})
|
|
83
|
+
<% } %>
|
|
84
|
+
`
|
|
85
|
+
} else {
|
|
86
|
+
console.error(`No route found in the file: ${routeFile}`)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const name = basename(path)
|
|
90
|
+
.replace('.tsx', '')
|
|
91
|
+
.replace(/^demo/, '')
|
|
92
|
+
.replace('.', ' ')
|
|
93
|
+
.trim()
|
|
94
|
+
|
|
95
|
+
const jsName = camelCase(basename(path))
|
|
96
|
+
|
|
97
|
+
return { url: path, code, name, jsName }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function validateAddOnSetup(environment: Environment) {
|
|
101
|
+
const options = await readCurrentProjectOptions(environment)
|
|
102
|
+
|
|
103
|
+
if (options.mode !== 'file-router') {
|
|
104
|
+
environment.error(
|
|
105
|
+
'This project is not using file-router mode.',
|
|
106
|
+
'To create an add-on, the project must be created with the file-router mode.',
|
|
107
|
+
)
|
|
108
|
+
process.exit(1)
|
|
109
|
+
}
|
|
110
|
+
if (!options.tailwind) {
|
|
111
|
+
environment.error(
|
|
112
|
+
'This project is not using Tailwind CSS.',
|
|
113
|
+
'To create an add-on, the project must be created with Tailwind CSS.',
|
|
114
|
+
)
|
|
115
|
+
process.exit(1)
|
|
116
|
+
}
|
|
117
|
+
if (!options.typescript) {
|
|
118
|
+
environment.error(
|
|
119
|
+
'This project is not using TypeScript.',
|
|
120
|
+
'To create an add-on, the project must be created with TypeScript.',
|
|
121
|
+
)
|
|
122
|
+
process.exit(1)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function readOrGenerateAddOnInfo(
|
|
127
|
+
options: PersistedOptions,
|
|
128
|
+
): Promise<AddOnInfo> {
|
|
129
|
+
return existsSync(INFO_FILE)
|
|
130
|
+
? JSON.parse((await readFile(INFO_FILE)).toString())
|
|
131
|
+
: ({
|
|
132
|
+
id: `${options.projectName}-add-on`,
|
|
133
|
+
name: `${options.projectName}-add-on`,
|
|
134
|
+
version: '0.0.1',
|
|
135
|
+
description: 'Add-on',
|
|
136
|
+
author: 'Jane Smith <jane.smith@example.com>',
|
|
137
|
+
license: 'MIT',
|
|
138
|
+
link: `https://github.com/jane-smith/${options.projectName}-add-on`,
|
|
139
|
+
shadcnComponents: [],
|
|
140
|
+
framework: options.framework,
|
|
141
|
+
modes: [options.mode],
|
|
142
|
+
routes: [],
|
|
143
|
+
warning: '',
|
|
144
|
+
phase: 'add-on',
|
|
145
|
+
type: 'add-on',
|
|
146
|
+
packageAdditions: {
|
|
147
|
+
scripts: {},
|
|
148
|
+
dependencies: {},
|
|
149
|
+
devDependencies: {},
|
|
150
|
+
},
|
|
151
|
+
dependsOn: options.existingAddOns,
|
|
152
|
+
} as AddOnInfo)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function generateProject(persistedOptions: PersistedOptions) {
|
|
156
|
+
const info = await readOrGenerateAddOnInfo(persistedOptions)
|
|
157
|
+
|
|
158
|
+
const output = await runCreateApp(
|
|
159
|
+
(await createAppOptionsFromPersisted(
|
|
160
|
+
persistedOptions,
|
|
161
|
+
)) as Required<Options>,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
return { info, output }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export async function buildAssetsDirectory(
|
|
168
|
+
output: {
|
|
169
|
+
files: Record<string, string>
|
|
170
|
+
},
|
|
171
|
+
info: AddOnInfo,
|
|
172
|
+
) {
|
|
173
|
+
const assetsDir = resolve(ADD_ON_DIR, ASSETS_DIR)
|
|
174
|
+
const ignore = createIgnore(process.cwd())
|
|
175
|
+
|
|
176
|
+
if (!existsSync(assetsDir)) {
|
|
177
|
+
const changedFiles: Record<string, string> = {}
|
|
178
|
+
await compareFilesRecursively('.', ignore, output.files, changedFiles)
|
|
179
|
+
|
|
180
|
+
for (const file of Object.keys(changedFiles).filter(
|
|
181
|
+
(file) => !ADD_ON_IGNORE_FILES.includes(basename(file)),
|
|
182
|
+
)) {
|
|
183
|
+
mkdirSync(dirname(resolve(assetsDir, file)), {
|
|
184
|
+
recursive: true,
|
|
185
|
+
})
|
|
186
|
+
if (file.includes('/routes/')) {
|
|
187
|
+
const { url, code, name, jsName } = templatize(changedFiles[file], file)
|
|
188
|
+
info.routes ||= []
|
|
189
|
+
if (!info.routes.find((r) => r.url === url)) {
|
|
190
|
+
info.routes.push({
|
|
191
|
+
url,
|
|
192
|
+
name,
|
|
193
|
+
jsName,
|
|
194
|
+
path: file,
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
writeFileSync(resolve(assetsDir, `${file}.ejs`), code)
|
|
198
|
+
} else {
|
|
199
|
+
writeFileSync(resolve(assetsDir, file), changedFiles[file])
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export async function updateAddOnInfo(environment: Environment) {
|
|
206
|
+
const { info, output } = await generateProject(
|
|
207
|
+
await readCurrentProjectOptions(environment),
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
info.packageAdditions = createPackageAdditions(
|
|
211
|
+
JSON.parse(output.files['./package.json']),
|
|
212
|
+
JSON.parse((await readFile('package.json')).toString()),
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
await buildAssetsDirectory(output, info)
|
|
216
|
+
|
|
217
|
+
mkdirSync(resolve(dirname(INFO_FILE)), { recursive: true })
|
|
218
|
+
writeFileSync(INFO_FILE, JSON.stringify(info, null, 2))
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export async function compileAddOn(environment: Environment) {
|
|
222
|
+
const info = await readOrGenerateAddOnInfo(
|
|
223
|
+
await readCurrentProjectOptions(environment),
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
const assetsDir = resolve(ADD_ON_DIR, ASSETS_DIR)
|
|
227
|
+
|
|
228
|
+
const compiledInfo: AddOnCompiled = {
|
|
229
|
+
...info,
|
|
230
|
+
files: await recursivelyGatherFiles(assetsDir),
|
|
231
|
+
deletedFiles: [],
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
writeFileSync(COMPILED_FILE, JSON.stringify(compiledInfo, null, 2))
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export async function initAddOn(environment: Environment) {
|
|
238
|
+
await validateAddOnSetup(environment)
|
|
239
|
+
await updateAddOnInfo(environment)
|
|
240
|
+
await compileAddOn(environment)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export async function loadRemoteAddOn(url: string): Promise<AddOn> {
|
|
244
|
+
const response = await fetch(url)
|
|
245
|
+
const jsonContent = await response.json()
|
|
246
|
+
|
|
247
|
+
const checked = AddOnCompiledSchema.safeParse(jsonContent)
|
|
248
|
+
if (!checked.success) {
|
|
249
|
+
throw new Error(`Invalid add-on: ${url}`)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const addOn = checked.data
|
|
253
|
+
addOn.id = url
|
|
254
|
+
const out = {
|
|
255
|
+
...addOn,
|
|
256
|
+
getFiles: () => Promise.resolve(Object.keys(addOn.files)),
|
|
257
|
+
getFileContents: (path: string) => Promise.resolve(addOn.files[path]),
|
|
258
|
+
getDeletedFiles: () => Promise.resolve(addOn.deletedFiles),
|
|
259
|
+
}
|
|
260
|
+
return out
|
|
261
|
+
}
|