@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.
Files changed (69) hide show
  1. package/dist/add-ons.js +5 -14
  2. package/dist/add-to-app.js +118 -74
  3. package/dist/config-file.js +9 -7
  4. package/dist/create-app.js +112 -34
  5. package/dist/custom-add-ons/add-on.js +175 -0
  6. package/dist/custom-add-ons/shared.js +117 -0
  7. package/dist/custom-add-ons/starter.js +84 -0
  8. package/dist/environment.js +59 -12
  9. package/dist/file-helpers.js +108 -2
  10. package/dist/frameworks.js +15 -1
  11. package/dist/index.js +12 -5
  12. package/dist/integrations/shadcn.js +10 -4
  13. package/dist/options.js +9 -0
  14. package/dist/package-json.js +7 -4
  15. package/dist/special-steps/index.js +24 -0
  16. package/dist/special-steps/rimraf-node-modules.js +16 -0
  17. package/dist/template-file.js +3 -13
  18. package/dist/types/add-ons.d.ts +3 -4
  19. package/dist/types/add-to-app.d.ts +16 -3
  20. package/dist/types/config-file.d.ts +4 -3
  21. package/dist/types/create-app.d.ts +1 -7
  22. package/dist/types/custom-add-ons/add-on.d.ts +69 -0
  23. package/dist/types/custom-add-ons/shared.d.ts +15 -0
  24. package/dist/types/custom-add-ons/starter.d.ts +7 -0
  25. package/dist/types/environment.d.ts +2 -1
  26. package/dist/types/file-helpers.d.ts +10 -0
  27. package/dist/types/frameworks.d.ts +2 -0
  28. package/dist/types/index.d.ts +13 -6
  29. package/dist/types/integrations/shadcn.d.ts +1 -1
  30. package/dist/types/options.d.ts +2 -0
  31. package/dist/types/package-json.d.ts +5 -0
  32. package/dist/types/package-manager.d.ts +6 -2
  33. package/dist/types/special-steps/index.d.ts +2 -0
  34. package/dist/types/special-steps/rimraf-node-modules.d.ts +2 -0
  35. package/dist/types/template-file.d.ts +1 -1
  36. package/dist/types/types.d.ts +752 -70
  37. package/dist/types.js +65 -1
  38. package/package.json +9 -3
  39. package/src/add-ons.ts +7 -19
  40. package/src/add-to-app.ts +196 -102
  41. package/src/config-file.ts +16 -13
  42. package/src/create-app.ts +129 -75
  43. package/src/custom-add-ons/add-on.ts +261 -0
  44. package/src/custom-add-ons/shared.ts +161 -0
  45. package/src/custom-add-ons/starter.ts +126 -0
  46. package/src/environment.ts +70 -11
  47. package/src/file-helpers.ts +164 -2
  48. package/src/frameworks.ts +21 -1
  49. package/src/index.ts +46 -11
  50. package/src/integrations/shadcn.ts +14 -4
  51. package/src/options.ts +11 -0
  52. package/src/package-json.ts +13 -6
  53. package/src/special-steps/index.ts +36 -0
  54. package/src/special-steps/rimraf-node-modules.ts +25 -0
  55. package/src/template-file.ts +3 -18
  56. package/src/types.ts +143 -85
  57. package/tests/add-ons.test.ts +5 -5
  58. package/tests/add-to-app.test.ts +358 -0
  59. package/tests/config-file.test.ts +15 -11
  60. package/tests/create-app.test.ts +43 -67
  61. package/tests/custom-add-ons/add-on.test.ts +12 -0
  62. package/tests/custom-add-ons/shared.test.ts +257 -0
  63. package/tests/custom-add-ons/starter.test.ts +58 -0
  64. package/tests/environment.test.ts +19 -0
  65. package/tests/integrations/shadcn.test.ts +48 -63
  66. package/tests/options.test.ts +42 -0
  67. package/tests/setupVitest.ts +6 -0
  68. package/tests/template-file.test.ts +54 -91
  69. 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
+ }
@@ -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
- await unlink(path)
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.deleteFile = async (path: string) => {
113
- await fs.unlinkSync(path)
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.exists = (path: string) => {
116
- if (isTemplatePath(path)) {
117
- return existsSync(path)
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,
@@ -1,16 +1,33 @@
1
- import { readFileSync, readdirSync, statSync } from 'node:fs'
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 (BINARY_EXTENSIONS.includes(extname(path))) {
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
- deletedFiles: [],
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,