@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
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { readdir } from 'node:fs/promises'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import { createApp } from '../create-app.js'
|
|
4
|
+
import { createMemoryEnvironment } from '../environment.js'
|
|
5
|
+
import { finalizeAddOns } from '../add-ons.js'
|
|
6
|
+
import { getFrameworkById } from '../frameworks.js'
|
|
7
|
+
import { readConfigFileFromEnvironment } from '../config-file.js'
|
|
8
|
+
import { readFileHelper } from '../file-helpers.js'
|
|
9
|
+
import { loadStarter } from '../custom-add-ons/starter.js'
|
|
10
|
+
|
|
11
|
+
import type { Environment, Mode, Options, SerializedOptions } from '../types.js'
|
|
12
|
+
import type { PersistedOptions } from '../config-file.js'
|
|
13
|
+
|
|
14
|
+
export function createPackageAdditions(
|
|
15
|
+
originalPackageJson: Record<string, any>,
|
|
16
|
+
currentPackageJson: Record<string, any>,
|
|
17
|
+
) {
|
|
18
|
+
const packageAdditions: Record<string, any> = {}
|
|
19
|
+
|
|
20
|
+
const scripts: Record<string, any> = {}
|
|
21
|
+
for (const script of Object.keys(currentPackageJson.scripts || {})) {
|
|
22
|
+
if (
|
|
23
|
+
originalPackageJson.scripts[script] !== currentPackageJson.scripts[script]
|
|
24
|
+
) {
|
|
25
|
+
scripts[script] = currentPackageJson.scripts[script]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
packageAdditions.scripts = Object.keys(scripts).length ? scripts : undefined
|
|
29
|
+
|
|
30
|
+
const dependencies: Record<string, string> = {}
|
|
31
|
+
for (const dependency of Object.keys(currentPackageJson.dependencies || {})) {
|
|
32
|
+
if (
|
|
33
|
+
originalPackageJson.dependencies[dependency] !==
|
|
34
|
+
currentPackageJson.dependencies[dependency]
|
|
35
|
+
) {
|
|
36
|
+
dependencies[dependency] = currentPackageJson.dependencies[dependency]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
packageAdditions.dependencies = Object.keys(dependencies).length
|
|
40
|
+
? dependencies
|
|
41
|
+
: undefined
|
|
42
|
+
|
|
43
|
+
const devDependencies: Record<string, string> = {}
|
|
44
|
+
for (const dependency of Object.keys(
|
|
45
|
+
currentPackageJson.devDependencies || {},
|
|
46
|
+
)) {
|
|
47
|
+
if (
|
|
48
|
+
originalPackageJson.devDependencies[dependency] !==
|
|
49
|
+
currentPackageJson.devDependencies[dependency]
|
|
50
|
+
) {
|
|
51
|
+
devDependencies[dependency] =
|
|
52
|
+
currentPackageJson.devDependencies[dependency]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
packageAdditions.devDependencies = Object.keys(devDependencies).length
|
|
56
|
+
? devDependencies
|
|
57
|
+
: undefined
|
|
58
|
+
|
|
59
|
+
return packageAdditions
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function createAppOptionsFromPersisted(
|
|
63
|
+
json: PersistedOptions,
|
|
64
|
+
): Promise<Options> {
|
|
65
|
+
/* eslint-disable unused-imports/no-unused-vars */
|
|
66
|
+
const { version, ...rest } = json
|
|
67
|
+
/* eslint-enable unused-imports/no-unused-vars */
|
|
68
|
+
const framework = getFrameworkById(rest.framework)
|
|
69
|
+
return {
|
|
70
|
+
...rest,
|
|
71
|
+
mode: json.mode as Mode,
|
|
72
|
+
projectName: json.projectName!,
|
|
73
|
+
typescript: json.typescript!,
|
|
74
|
+
tailwind: json.tailwind!,
|
|
75
|
+
git: json.git!,
|
|
76
|
+
packageManager: json.packageManager!,
|
|
77
|
+
targetDir: '',
|
|
78
|
+
framework: framework!,
|
|
79
|
+
starter: json.starter ? await loadStarter(json.starter) : undefined,
|
|
80
|
+
chosenAddOns: await finalizeAddOns(framework!, json.mode as Mode, [
|
|
81
|
+
...json.existingAddOns,
|
|
82
|
+
]),
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function createSerializedOptionsFromPersisted(
|
|
87
|
+
json: PersistedOptions,
|
|
88
|
+
): SerializedOptions {
|
|
89
|
+
/* eslint-disable unused-imports/no-unused-vars */
|
|
90
|
+
const { version, ...rest } = json
|
|
91
|
+
/* eslint-enable unused-imports/no-unused-vars */
|
|
92
|
+
return {
|
|
93
|
+
...rest,
|
|
94
|
+
mode: json.mode as Mode,
|
|
95
|
+
projectName: json.projectName!,
|
|
96
|
+
typescript: json.typescript!,
|
|
97
|
+
tailwind: json.tailwind!,
|
|
98
|
+
git: json.git!,
|
|
99
|
+
packageManager: json.packageManager!,
|
|
100
|
+
targetDir: '',
|
|
101
|
+
framework: json.framework,
|
|
102
|
+
starter: json.starter,
|
|
103
|
+
chosenAddOns: json.existingAddOns,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function runCreateApp(options: Required<Options>) {
|
|
108
|
+
const { environment, output } = createMemoryEnvironment()
|
|
109
|
+
|
|
110
|
+
const targetDir = resolve(process.cwd())
|
|
111
|
+
|
|
112
|
+
await createApp(environment, {
|
|
113
|
+
...options,
|
|
114
|
+
targetDir,
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
output.files = Object.fromEntries(
|
|
118
|
+
Object.entries(output.files).map(([key, value]) => {
|
|
119
|
+
return [key.replace(targetDir, '.'), value]
|
|
120
|
+
}),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
return output
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function compareFilesRecursively(
|
|
127
|
+
path: string,
|
|
128
|
+
ignore: (filePath: string) => boolean,
|
|
129
|
+
original: Record<string, string>,
|
|
130
|
+
changedFiles: Record<string, string>,
|
|
131
|
+
) {
|
|
132
|
+
const files = await readdir(path, { withFileTypes: true })
|
|
133
|
+
for (const file of files) {
|
|
134
|
+
const filePath = `${path}/${file.name}`
|
|
135
|
+
if (!ignore(file.name)) {
|
|
136
|
+
if (file.isDirectory()) {
|
|
137
|
+
await compareFilesRecursively(filePath, ignore, original, changedFiles)
|
|
138
|
+
} else {
|
|
139
|
+
const contents = await readFileHelper(filePath)
|
|
140
|
+
if (!original[filePath] || original[filePath] !== contents) {
|
|
141
|
+
changedFiles[filePath] = contents
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export async function readCurrentProjectOptions(environment: Environment) {
|
|
149
|
+
const persistedOptions = await readConfigFileFromEnvironment(
|
|
150
|
+
environment,
|
|
151
|
+
process.cwd(),
|
|
152
|
+
)
|
|
153
|
+
if (!persistedOptions) {
|
|
154
|
+
environment.error(
|
|
155
|
+
'There is no .cta.json file in your project.',
|
|
156
|
+
`This is probably because this was created with an older version of create-tsrouter-app.`,
|
|
157
|
+
)
|
|
158
|
+
process.exit(1)
|
|
159
|
+
}
|
|
160
|
+
return persistedOptions
|
|
161
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises'
|
|
2
|
+
import { existsSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { resolve } from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { StarterCompiledSchema } from '../types.js'
|
|
6
|
+
import { createIgnore } 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
|
+
Environment,
|
|
18
|
+
Options,
|
|
19
|
+
Starter,
|
|
20
|
+
StarterCompiled,
|
|
21
|
+
StarterInfo,
|
|
22
|
+
} from '../types'
|
|
23
|
+
|
|
24
|
+
const INFO_FILE = 'starter-info.json'
|
|
25
|
+
const COMPILED_FILE = 'starter.json'
|
|
26
|
+
|
|
27
|
+
export async function readOrGenerateStarterInfo(
|
|
28
|
+
options: PersistedOptions,
|
|
29
|
+
): Promise<StarterInfo> {
|
|
30
|
+
return existsSync(INFO_FILE)
|
|
31
|
+
? JSON.parse((await readFile(INFO_FILE)).toString())
|
|
32
|
+
: {
|
|
33
|
+
id: `${options.projectName}-starter`,
|
|
34
|
+
name: `${options.projectName}-starter`,
|
|
35
|
+
version: '0.0.1',
|
|
36
|
+
description: 'Project starter',
|
|
37
|
+
author: 'Jane Smith <jane.smith@example.com>',
|
|
38
|
+
license: 'MIT',
|
|
39
|
+
link: `https://github.com/jane-smith/${options.projectName}-starter`,
|
|
40
|
+
shadcnComponents: [],
|
|
41
|
+
framework: options.framework,
|
|
42
|
+
mode: options.mode!,
|
|
43
|
+
routes: [],
|
|
44
|
+
warning: '',
|
|
45
|
+
type: 'starter',
|
|
46
|
+
packageAdditions: {
|
|
47
|
+
scripts: {},
|
|
48
|
+
dependencies: {},
|
|
49
|
+
devDependencies: {},
|
|
50
|
+
},
|
|
51
|
+
dependsOn: options.existingAddOns,
|
|
52
|
+
typescript: options.typescript!,
|
|
53
|
+
tailwind: options.tailwind!,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function loadCurrentStarterInfo(environment: Environment) {
|
|
58
|
+
const persistedOptions = await readCurrentProjectOptions(environment)
|
|
59
|
+
const info = await readOrGenerateStarterInfo(persistedOptions)
|
|
60
|
+
|
|
61
|
+
const output = await runCreateApp(
|
|
62
|
+
(await createAppOptionsFromPersisted(
|
|
63
|
+
persistedOptions,
|
|
64
|
+
)) as Required<Options>,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return { info, output }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function updateStarterInfo(environment: Environment) {
|
|
71
|
+
const { info, output } = await loadCurrentStarterInfo(environment)
|
|
72
|
+
|
|
73
|
+
info.packageAdditions = createPackageAdditions(
|
|
74
|
+
JSON.parse(output.files['./package.json']),
|
|
75
|
+
JSON.parse((await readFile('package.json')).toString()),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
writeFileSync(INFO_FILE, JSON.stringify(info, null, 2))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function compileStarter(environment: Environment) {
|
|
82
|
+
const { info, output } = await loadCurrentStarterInfo(environment)
|
|
83
|
+
|
|
84
|
+
const ignore = createIgnore(process.cwd())
|
|
85
|
+
const changedFiles: Record<string, string> = {}
|
|
86
|
+
await compareFilesRecursively('.', ignore, output.files, changedFiles)
|
|
87
|
+
|
|
88
|
+
const deletedFiles: Array<string> = []
|
|
89
|
+
for (const file of Object.keys(output.files)) {
|
|
90
|
+
if (!existsSync(resolve(process.cwd(), file))) {
|
|
91
|
+
deletedFiles.push(file)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const compiledInfo: StarterCompiled = {
|
|
96
|
+
...info,
|
|
97
|
+
files: changedFiles,
|
|
98
|
+
deletedFiles,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
writeFileSync(COMPILED_FILE, JSON.stringify(compiledInfo, null, 2))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function initStarter(environment: Environment) {
|
|
105
|
+
await updateStarterInfo(environment)
|
|
106
|
+
await compileStarter(environment)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function loadStarter(url: string): Promise<Starter> {
|
|
110
|
+
const response = await fetch(url)
|
|
111
|
+
const jsonContent = await response.json()
|
|
112
|
+
|
|
113
|
+
const checked = StarterCompiledSchema.safeParse(jsonContent)
|
|
114
|
+
if (!checked.success) {
|
|
115
|
+
throw new Error(`Invalid starter: ${url}`)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const starter = checked.data
|
|
119
|
+
starter.id = url
|
|
120
|
+
return {
|
|
121
|
+
...starter,
|
|
122
|
+
getFiles: () => Promise.resolve(Object.keys(starter.files)),
|
|
123
|
+
getFileContents: (path: string) => Promise.resolve(starter.files[path]),
|
|
124
|
+
getDeletedFiles: () => Promise.resolve(starter.deletedFiles),
|
|
125
|
+
}
|
|
126
|
+
}
|
package/src/environment.ts
CHANGED
|
@@ -3,13 +3,21 @@ import {
|
|
|
3
3
|
copyFile,
|
|
4
4
|
mkdir,
|
|
5
5
|
readFile,
|
|
6
|
+
readdir,
|
|
6
7
|
unlink,
|
|
7
8
|
writeFile,
|
|
8
9
|
} from 'node:fs/promises'
|
|
9
|
-
import { existsSync } from 'node:fs'
|
|
10
|
+
import { existsSync, statSync } from 'node:fs'
|
|
10
11
|
import { dirname } from 'node:path'
|
|
11
12
|
import { execa } from 'execa'
|
|
12
13
|
import { memfs } from 'memfs'
|
|
14
|
+
import { rimraf } from 'rimraf'
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
cleanUpFileArray,
|
|
18
|
+
cleanUpFiles,
|
|
19
|
+
getBinaryFile,
|
|
20
|
+
} from './file-helpers.js'
|
|
13
21
|
|
|
14
22
|
import type { Environment } from './types.js'
|
|
15
23
|
|
|
@@ -34,22 +42,43 @@ export function createDefaultEnvironment(): Environment {
|
|
|
34
42
|
await mkdir(dirname(path), { recursive: true })
|
|
35
43
|
return writeFile(path, contents)
|
|
36
44
|
},
|
|
45
|
+
writeFileBase64: async (path: string, base64Contents: string) => {
|
|
46
|
+
await mkdir(dirname(path), { recursive: true })
|
|
47
|
+
return writeFile(path, getBinaryFile(base64Contents) as string)
|
|
48
|
+
},
|
|
37
49
|
execute: async (command: string, args: Array<string>, cwd: string) => {
|
|
38
50
|
try {
|
|
39
|
-
await execa(command, args, {
|
|
51
|
+
const result = await execa(command, args, {
|
|
40
52
|
cwd,
|
|
41
53
|
})
|
|
54
|
+
return { stdout: result.stdout }
|
|
42
55
|
} catch {
|
|
43
56
|
errors.push(
|
|
44
57
|
`Command "${command} ${args.join(' ')}" did not run successfully. Please run this manually in your project.`,
|
|
45
58
|
)
|
|
59
|
+
return { stdout: '' }
|
|
46
60
|
}
|
|
47
61
|
},
|
|
48
62
|
deleteFile: async (path: string) => {
|
|
49
|
-
|
|
63
|
+
if (existsSync(path)) {
|
|
64
|
+
await unlink(path)
|
|
65
|
+
}
|
|
50
66
|
},
|
|
51
67
|
|
|
68
|
+
readFile: async (path: string) => {
|
|
69
|
+
return (await readFile(path)).toString()
|
|
70
|
+
},
|
|
52
71
|
exists: (path: string) => existsSync(path),
|
|
72
|
+
isDirectory: (path: string) => statSync(path).isDirectory(),
|
|
73
|
+
readdir: async (path: string) => readdir(path),
|
|
74
|
+
rimraf: async (path: string) => {
|
|
75
|
+
await rimraf(path)
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
appName: 'TanStack',
|
|
79
|
+
|
|
80
|
+
startStep: () => {},
|
|
81
|
+
finishStep: () => {},
|
|
53
82
|
|
|
54
83
|
intro: () => {},
|
|
55
84
|
outro: () => {},
|
|
@@ -64,11 +93,12 @@ export function createDefaultEnvironment(): Environment {
|
|
|
64
93
|
}
|
|
65
94
|
}
|
|
66
95
|
|
|
67
|
-
export function createMemoryEnvironment() {
|
|
96
|
+
export function createMemoryEnvironment(returnPathsRelativeTo: string = '') {
|
|
68
97
|
const environment = createDefaultEnvironment()
|
|
69
98
|
|
|
70
99
|
const output: {
|
|
71
100
|
files: Record<string, string>
|
|
101
|
+
deletedFiles: Array<string>
|
|
72
102
|
commands: Array<{
|
|
73
103
|
command: string
|
|
74
104
|
args: Array<string>
|
|
@@ -76,6 +106,7 @@ export function createMemoryEnvironment() {
|
|
|
76
106
|
} = {
|
|
77
107
|
files: {},
|
|
78
108
|
commands: [],
|
|
109
|
+
deletedFiles: [],
|
|
79
110
|
}
|
|
80
111
|
|
|
81
112
|
const { fs, vol } = memfs({})
|
|
@@ -103,24 +134,52 @@ export function createMemoryEnvironment() {
|
|
|
103
134
|
command,
|
|
104
135
|
args,
|
|
105
136
|
})
|
|
106
|
-
return Promise.resolve()
|
|
137
|
+
return Promise.resolve({ stdout: '' })
|
|
138
|
+
}
|
|
139
|
+
environment.readFile = async (path: string) => {
|
|
140
|
+
return Promise.resolve(fs.readFileSync(path, 'utf-8').toString())
|
|
107
141
|
}
|
|
108
142
|
environment.writeFile = async (path: string, contents: string) => {
|
|
109
143
|
fs.mkdirSync(dirname(path), { recursive: true })
|
|
110
144
|
await fs.writeFileSync(path, contents)
|
|
111
145
|
}
|
|
112
|
-
environment.
|
|
113
|
-
|
|
146
|
+
environment.writeFileBase64 = async (path: string, contents: string) => {
|
|
147
|
+
// For the in-memory file system, we are not converting the base64 to binary
|
|
148
|
+
// because it's not needed.
|
|
149
|
+
fs.mkdirSync(dirname(path), { recursive: true })
|
|
150
|
+
await fs.writeFileSync(path, contents)
|
|
114
151
|
}
|
|
115
|
-
environment.
|
|
116
|
-
|
|
117
|
-
|
|
152
|
+
environment.deleteFile = async (path: string) => {
|
|
153
|
+
output.deletedFiles.push(path)
|
|
154
|
+
if (fs.existsSync(path)) {
|
|
155
|
+
await fs.unlinkSync(path)
|
|
118
156
|
}
|
|
119
|
-
return fs.existsSync(path)
|
|
120
157
|
}
|
|
121
158
|
environment.finishRun = () => {
|
|
122
159
|
output.files = vol.toJSON() as Record<string, string>
|
|
160
|
+
for (const file of Object.keys(output.files)) {
|
|
161
|
+
if (fs.statSync(file).isDirectory()) {
|
|
162
|
+
delete output.files[file]
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (returnPathsRelativeTo.length) {
|
|
166
|
+
output.files = cleanUpFiles(output.files, returnPathsRelativeTo)
|
|
167
|
+
output.deletedFiles = cleanUpFileArray(
|
|
168
|
+
output.deletedFiles,
|
|
169
|
+
returnPathsRelativeTo,
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
environment.exists = (path: string) => {
|
|
174
|
+
return fs.existsSync(path)
|
|
175
|
+
}
|
|
176
|
+
environment.isDirectory = (path: string) => {
|
|
177
|
+
return fs.statSync(path).isDirectory()
|
|
178
|
+
}
|
|
179
|
+
environment.readdir = async (path: string) => {
|
|
180
|
+
return Promise.resolve(fs.readdirSync(path).map((d) => d.toString()))
|
|
123
181
|
}
|
|
182
|
+
environment.rimraf = async () => {}
|
|
124
183
|
|
|
125
184
|
return {
|
|
126
185
|
environment,
|
package/src/file-helpers.ts
CHANGED
|
@@ -1,16 +1,33 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readdir } from 'node:fs/promises'
|
|
2
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
|
|
2
3
|
import { basename, extname, resolve } from 'node:path'
|
|
4
|
+
import parseGitignore from 'parse-gitignore'
|
|
5
|
+
import ignore from 'ignore'
|
|
6
|
+
|
|
7
|
+
import type { Environment } from './types'
|
|
3
8
|
|
|
4
9
|
const BINARY_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico']
|
|
5
10
|
|
|
6
11
|
export function readFileHelper(path: string): string {
|
|
7
|
-
if (
|
|
12
|
+
if (isBinaryFile(path)) {
|
|
8
13
|
return `base64::${readFileSync(path).toString('base64')}`
|
|
9
14
|
} else {
|
|
10
15
|
return readFileSync(path, 'utf-8').toString()
|
|
11
16
|
}
|
|
12
17
|
}
|
|
13
18
|
|
|
19
|
+
export function isBinaryFile(path: string): boolean {
|
|
20
|
+
return BINARY_EXTENSIONS.includes(extname(path))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function convertBinaryContentsToBase64(contents: any): string {
|
|
24
|
+
return `base64::${Buffer.from(contents).toString('base64')}`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isBase64(content: string): boolean {
|
|
28
|
+
return content.startsWith('base64::')
|
|
29
|
+
}
|
|
30
|
+
|
|
14
31
|
export function getBinaryFile(content: string): string | null {
|
|
15
32
|
if (content.startsWith('base64::')) {
|
|
16
33
|
const binaryContent = Buffer.from(content.replace('base64::', ''), 'base64')
|
|
@@ -71,3 +88,148 @@ export function findFilesRecursively(
|
|
|
71
88
|
}
|
|
72
89
|
}
|
|
73
90
|
}
|
|
91
|
+
|
|
92
|
+
async function recursivelyGatherFilesHelper(
|
|
93
|
+
basePath: string,
|
|
94
|
+
path: string,
|
|
95
|
+
files: Record<string, string>,
|
|
96
|
+
ignore: (filePath: string) => boolean,
|
|
97
|
+
) {
|
|
98
|
+
const dirFiles = await readdir(path, { withFileTypes: true })
|
|
99
|
+
for (const file of dirFiles) {
|
|
100
|
+
if (ignore(file.name)) {
|
|
101
|
+
continue
|
|
102
|
+
}
|
|
103
|
+
if (file.isDirectory()) {
|
|
104
|
+
await recursivelyGatherFilesHelper(
|
|
105
|
+
basePath,
|
|
106
|
+
resolve(path, file.name),
|
|
107
|
+
files,
|
|
108
|
+
ignore,
|
|
109
|
+
)
|
|
110
|
+
} else {
|
|
111
|
+
const filePath = resolve(path, file.name)
|
|
112
|
+
files[filePath.replace(basePath, '.')] = await readFileHelper(filePath)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function recursivelyGatherFiles(
|
|
118
|
+
path: string,
|
|
119
|
+
includeProjectFiles = true,
|
|
120
|
+
) {
|
|
121
|
+
const ignore = createIgnore(path, includeProjectFiles)
|
|
122
|
+
const files: Record<string, string> = {}
|
|
123
|
+
await recursivelyGatherFilesHelper(path, path, files, ignore)
|
|
124
|
+
return files
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function recursivelyGatherFilesFromEnvironmentHelper(
|
|
128
|
+
environment: Environment,
|
|
129
|
+
basePath: string,
|
|
130
|
+
path: string,
|
|
131
|
+
files: Record<string, string>,
|
|
132
|
+
ignore: (filePath: string) => boolean,
|
|
133
|
+
) {
|
|
134
|
+
const dirFiles = await environment.readdir(path)
|
|
135
|
+
for (const file of dirFiles) {
|
|
136
|
+
if (ignore(file)) {
|
|
137
|
+
continue
|
|
138
|
+
}
|
|
139
|
+
if (environment.isDirectory(resolve(path, file))) {
|
|
140
|
+
await recursivelyGatherFilesFromEnvironmentHelper(
|
|
141
|
+
environment,
|
|
142
|
+
basePath,
|
|
143
|
+
resolve(path, file),
|
|
144
|
+
files,
|
|
145
|
+
ignore,
|
|
146
|
+
)
|
|
147
|
+
} else {
|
|
148
|
+
const filePath = resolve(path, file)
|
|
149
|
+
files[filePath.replace(basePath, '.')] =
|
|
150
|
+
await environment.readFile(filePath)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function recursivelyGatherFilesFromEnvironment(
|
|
156
|
+
environment: Environment,
|
|
157
|
+
path: string,
|
|
158
|
+
includeProjectFiles = true,
|
|
159
|
+
) {
|
|
160
|
+
const ignore = createIgnore(path, includeProjectFiles)
|
|
161
|
+
const files: Record<string, string> = {}
|
|
162
|
+
await recursivelyGatherFilesFromEnvironmentHelper(
|
|
163
|
+
environment,
|
|
164
|
+
path,
|
|
165
|
+
path,
|
|
166
|
+
files,
|
|
167
|
+
ignore,
|
|
168
|
+
)
|
|
169
|
+
return files
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export const IGNORE_FILES = [
|
|
173
|
+
'.starter',
|
|
174
|
+
'.add-on',
|
|
175
|
+
'.cta.json',
|
|
176
|
+
'.git',
|
|
177
|
+
'add-on-info.json',
|
|
178
|
+
'add-on.json',
|
|
179
|
+
'build',
|
|
180
|
+
'bun.lock',
|
|
181
|
+
'bun.lockb',
|
|
182
|
+
'deno.lock',
|
|
183
|
+
'dist',
|
|
184
|
+
'node_modules',
|
|
185
|
+
'package-lock.json',
|
|
186
|
+
'pnpm-lock.yaml',
|
|
187
|
+
'starter.json',
|
|
188
|
+
'starter-info.json',
|
|
189
|
+
'yarn.lock',
|
|
190
|
+
]
|
|
191
|
+
|
|
192
|
+
const PROJECT_FILES = ['package.json']
|
|
193
|
+
|
|
194
|
+
export function createIgnore(path: string, includeProjectFiles = true) {
|
|
195
|
+
const ignoreList = existsSync(resolve(path, '.gitignore'))
|
|
196
|
+
? (
|
|
197
|
+
parseGitignore(
|
|
198
|
+
readFileSync(resolve(path, '.gitignore')),
|
|
199
|
+
) as unknown as { patterns: Array<string> }
|
|
200
|
+
).patterns
|
|
201
|
+
: []
|
|
202
|
+
const ig = ignore().add(ignoreList)
|
|
203
|
+
return (filePath: string) => {
|
|
204
|
+
const fileName = basename(filePath)
|
|
205
|
+
if (
|
|
206
|
+
IGNORE_FILES.includes(fileName) ||
|
|
207
|
+
(includeProjectFiles && PROJECT_FILES.includes(fileName))
|
|
208
|
+
) {
|
|
209
|
+
return true
|
|
210
|
+
}
|
|
211
|
+
const nameWithoutDotSlash = fileName.replace(/^\.\//, '')
|
|
212
|
+
return ig.ignores(nameWithoutDotSlash)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function cleanUpFiles(
|
|
217
|
+
files: Record<string, string>,
|
|
218
|
+
targetDir?: string,
|
|
219
|
+
) {
|
|
220
|
+
return Object.keys(files).reduce<Record<string, string>>((acc, file) => {
|
|
221
|
+
if (basename(file) !== '.cta.json') {
|
|
222
|
+
acc[targetDir ? file.replace(targetDir, '.') : file] = files[file]
|
|
223
|
+
}
|
|
224
|
+
return acc
|
|
225
|
+
}, {})
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function cleanUpFileArray(files: Array<string>, targetDir?: string) {
|
|
229
|
+
return files.reduce<Array<string>>((acc, file) => {
|
|
230
|
+
if (basename(file) !== '.cta.json') {
|
|
231
|
+
acc.push(targetDir ? file.replace(targetDir, '.') : file)
|
|
232
|
+
}
|
|
233
|
+
return acc
|
|
234
|
+
}, [])
|
|
235
|
+
}
|
package/src/frameworks.ts
CHANGED
|
@@ -34,6 +34,14 @@ function getAddOns(framework: FrameworkDefinition) {
|
|
|
34
34
|
readme = readFileSync(resolve(addOnsBase, dir, 'README.md'), 'utf-8')
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
let smallLogo: string | undefined
|
|
38
|
+
if (existsSync(resolve(addOnsBase, dir, 'small-logo.svg'))) {
|
|
39
|
+
smallLogo = readFileSync(
|
|
40
|
+
resolve(addOnsBase, dir, 'small-logo.svg'),
|
|
41
|
+
'utf-8',
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
37
45
|
const absoluteFiles: Record<string, string> = {}
|
|
38
46
|
const assetsDir = resolve(addOnsBase, dir, 'assets')
|
|
39
47
|
if (existsSync(assetsDir)) {
|
|
@@ -57,9 +65,10 @@ function getAddOns(framework: FrameworkDefinition) {
|
|
|
57
65
|
packageAdditions,
|
|
58
66
|
readme,
|
|
59
67
|
files,
|
|
60
|
-
|
|
68
|
+
smallLogo,
|
|
61
69
|
getFiles,
|
|
62
70
|
getFileContents,
|
|
71
|
+
getDeletedFiles: () => Promise.resolve(info.deletedFiles ?? []),
|
|
63
72
|
})
|
|
64
73
|
}
|
|
65
74
|
}
|
|
@@ -67,6 +76,14 @@ function getAddOns(framework: FrameworkDefinition) {
|
|
|
67
76
|
return addOns
|
|
68
77
|
}
|
|
69
78
|
|
|
79
|
+
export function __testRegisterFramework(framework: Framework) {
|
|
80
|
+
frameworks.push(framework)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function __testClearFrameworks() {
|
|
84
|
+
frameworks.length = 0
|
|
85
|
+
}
|
|
86
|
+
|
|
70
87
|
export function registerFramework(framework: FrameworkDefinition) {
|
|
71
88
|
const baseAssetsDirectory = resolve(framework.baseDirectory, 'base')
|
|
72
89
|
|
|
@@ -93,6 +110,9 @@ export function registerFramework(framework: FrameworkDefinition) {
|
|
|
93
110
|
getFileContents: (path: string) => {
|
|
94
111
|
return Promise.resolve(readFileHelper(resolve(baseAssetsDirectory, path)))
|
|
95
112
|
},
|
|
113
|
+
getDeletedFiles: () => {
|
|
114
|
+
return Promise.resolve([])
|
|
115
|
+
},
|
|
96
116
|
basePackageJSON,
|
|
97
117
|
optionalPackages,
|
|
98
118
|
getAddOns: () => addOns,
|