@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,257 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { fs, vol } from 'memfs'
3
+
4
+ import {
5
+ compareFilesRecursively,
6
+ createAppOptionsFromPersisted,
7
+ createPackageAdditions,
8
+ createSerializedOptionsFromPersisted,
9
+ readCurrentProjectOptions,
10
+ } from '../../src/custom-add-ons/shared.js'
11
+ import { createMemoryEnvironment } from '../../src/environment.js'
12
+ import {
13
+ __testClearFrameworks,
14
+ __testRegisterFramework,
15
+ } from '../../src/frameworks.js'
16
+
17
+ import type { PersistedOptions } from '../../src/config-file.js'
18
+
19
+ vi.mock('node:fs', () => fs)
20
+ vi.mock('node:fs/promises', () => fs.promises)
21
+
22
+ beforeEach(() => {
23
+ vol.reset()
24
+
25
+ const fakeFiles = {
26
+ './package.json': JSON.stringify({
27
+ name: 'test',
28
+ version: '1.0.0',
29
+ dependencies: {},
30
+ }),
31
+ }
32
+
33
+ __testClearFrameworks()
34
+ __testRegisterFramework({
35
+ id: 'test',
36
+ name: 'Test',
37
+ description: 'Test',
38
+ version: '1.0.0',
39
+ baseDirectory: '/foo',
40
+ addOnsDirectories: [],
41
+ examplesDirectory: '',
42
+ basePackageJSON: {},
43
+ optionalPackages: {},
44
+ getAddOns: () => [
45
+ {
46
+ id: 'test',
47
+ name: 'Test',
48
+ description: 'Test',
49
+ version: '1.0.0',
50
+ type: 'add-on',
51
+ phase: 'add-on',
52
+ modes: ['code-router', 'file-router'],
53
+ command: {
54
+ command: 'echo',
55
+ args: ['baz'],
56
+ },
57
+ packageAdditions: {
58
+ dependencies: {
59
+ 'test-package': '1.0.0',
60
+ },
61
+ },
62
+ dependsOn: [],
63
+ getFiles: () => Promise.resolve(['./jack.txt']),
64
+ getFileContents: () => Promise.resolve('foo'),
65
+ getDeletedFiles: () => Promise.resolve([]),
66
+ },
67
+ ],
68
+ getFiles: () => Promise.resolve(Object.keys(fakeFiles)),
69
+ getFileContents: (path) => Promise.resolve(fakeFiles[path]),
70
+ getDeletedFiles: () => Promise.resolve([]),
71
+ })
72
+ })
73
+
74
+ describe('createAppOptionsFromPersisted', () => {
75
+ it('should create live options from persisted options', async () => {
76
+ const persistedOptions = {
77
+ projectName: 'test-project',
78
+ framework: 'test',
79
+ mode: 'code-router',
80
+ typescript: true,
81
+ tailwind: true,
82
+ git: true,
83
+ packageManager: 'npm',
84
+ targetDir: '',
85
+ starter: undefined,
86
+ chosenAddOns: [],
87
+ existingAddOns: [],
88
+ version: 1,
89
+ } as PersistedOptions
90
+ const appOptions = await createAppOptionsFromPersisted(persistedOptions)
91
+ expect(appOptions.framework.id).toEqual('test')
92
+ expect(appOptions.mode).toEqual('code-router')
93
+ expect(appOptions.typescript).toEqual(true)
94
+ expect(appOptions.tailwind).toEqual(true)
95
+ expect(appOptions.git).toEqual(true)
96
+ expect(appOptions.packageManager).toEqual('npm')
97
+ expect(appOptions.targetDir).toEqual('')
98
+ expect(appOptions.starter).toEqual(undefined)
99
+ expect(appOptions.chosenAddOns).toEqual([])
100
+ expect(appOptions.chosenAddOns).toEqual([])
101
+ })
102
+ })
103
+
104
+ describe('createSerializedOptionsFromPersisted', () => {
105
+ it('should create serialized options from persisted options', async () => {
106
+ const persistedOptions = {
107
+ projectName: 'test-project',
108
+ framework: 'test',
109
+ mode: 'code-router',
110
+ typescript: true,
111
+ tailwind: true,
112
+ git: true,
113
+ packageManager: 'npm',
114
+ targetDir: '',
115
+ starter: undefined,
116
+ chosenAddOns: [],
117
+ existingAddOns: [],
118
+ version: 1,
119
+ } as PersistedOptions
120
+ const appOptions =
121
+ await createSerializedOptionsFromPersisted(persistedOptions)
122
+ expect(appOptions.framework).toEqual('test')
123
+ expect(appOptions.mode).toEqual('code-router')
124
+ expect(appOptions.typescript).toEqual(true)
125
+ expect(appOptions.tailwind).toEqual(true)
126
+ expect(appOptions.git).toEqual(true)
127
+ expect(appOptions.packageManager).toEqual('npm')
128
+ expect(appOptions.targetDir).toEqual('')
129
+ expect(appOptions.starter).toEqual(undefined)
130
+ expect(appOptions.chosenAddOns).toEqual([])
131
+ expect(appOptions.chosenAddOns).toEqual([])
132
+ })
133
+ })
134
+
135
+ describe('createPackageAdditions', () => {
136
+ it('should handles scripts', () => {
137
+ const packageAdditions = createPackageAdditions(
138
+ {
139
+ scripts: {
140
+ dev: 'vinxi dev',
141
+ },
142
+ },
143
+ {
144
+ scripts: {
145
+ dev: 'vinxi dev',
146
+ foo: 'bar',
147
+ },
148
+ },
149
+ )
150
+ expect(packageAdditions).toEqual({
151
+ scripts: {
152
+ foo: 'bar',
153
+ },
154
+ })
155
+ })
156
+
157
+ it('should handles dependencies', () => {
158
+ const packageAdditions = createPackageAdditions(
159
+ {
160
+ dependencies: {
161
+ react: '^18.0.0',
162
+ },
163
+ devDependencies: {
164
+ 'foo-dev-dependency': '^18.0.0',
165
+ 'updated-dev-dependency': '^18.0.0',
166
+ },
167
+ },
168
+ {
169
+ dependencies: {
170
+ react: '^18.0.0',
171
+ 'react-dom': '^20.0.0',
172
+ },
173
+ devDependencies: {
174
+ 'foo-dev-dependency': '^18.0.0',
175
+ 'bar-dev-dependency': '^18.0.0',
176
+ 'updated-dev-dependency': '^20.0.0',
177
+ },
178
+ },
179
+ )
180
+ expect(packageAdditions).toEqual({
181
+ dependencies: {
182
+ 'react-dom': '^20.0.0',
183
+ },
184
+ devDependencies: {
185
+ 'bar-dev-dependency': '^18.0.0',
186
+ 'updated-dev-dependency': '^20.0.0',
187
+ },
188
+ })
189
+ })
190
+ })
191
+
192
+ describe('readCurrentProjectOptions', () => {
193
+ it('should read the current project options', async () => {
194
+ const { environment } = createMemoryEnvironment()
195
+ environment.writeFile(
196
+ '.cta.json',
197
+ JSON.stringify({
198
+ projectName: 'test-project',
199
+ framework: 'test',
200
+ mode: 'code-router',
201
+ typescript: true,
202
+ tailwind: true,
203
+ git: true,
204
+ packageManager: 'npm',
205
+ targetDir: '',
206
+ starter: undefined,
207
+ chosenAddOns: [],
208
+ existingAddOns: [],
209
+ version: 1,
210
+ }),
211
+ )
212
+ const options = await readCurrentProjectOptions(environment)
213
+ expect(options).toEqual({
214
+ chosenAddOns: [],
215
+ existingAddOns: [],
216
+ framework: 'test',
217
+ git: true,
218
+ mode: 'code-router',
219
+ packageManager: 'npm',
220
+ projectName: 'test-project',
221
+ tailwind: true,
222
+ targetDir: '',
223
+ typescript: true,
224
+ version: 1,
225
+ })
226
+ })
227
+ })
228
+
229
+ describe('compareFilesRecursively', () => {
230
+ it('should compare files recursively', async () => {
231
+ vol.mkdirSync('/foo')
232
+ vol.writeFileSync('/foo/.gitignore', 'foo')
233
+ vol.writeFileSync('/foo/bar.txt', 'bar')
234
+ vol.writeFileSync('/foo/baz.txt', 'baz')
235
+ vol.writeFileSync('/foo/qux.txt', 'qux')
236
+ vol.mkdirSync('/foo/bar')
237
+ vol.writeFileSync('/foo/bar/baz.txt', 'baz')
238
+ vol.writeFileSync('/foo/bar/qux.txt', 'qux')
239
+ const changedFiles = {}
240
+ await compareFilesRecursively(
241
+ '/foo',
242
+ () => false,
243
+ {
244
+ '/foo/.gitignore': '.gitignore',
245
+ '/foo/bar.txt': 'bar',
246
+ },
247
+ changedFiles,
248
+ )
249
+ expect(changedFiles).toEqual({
250
+ '/foo/.gitignore': 'foo',
251
+ '/foo/bar/baz.txt': 'baz',
252
+ '/foo/bar/qux.txt': 'qux',
253
+ '/foo/baz.txt': 'baz',
254
+ '/foo/qux.txt': 'qux',
255
+ })
256
+ })
257
+ })
@@ -0,0 +1,58 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { fs, vol } from 'memfs'
3
+
4
+ import { readOrGenerateStarterInfo } from '../../src/custom-add-ons/starter.js'
5
+
6
+ vi.mock('node:fs', () => fs)
7
+ vi.mock('node:fs/promises', () => fs.promises)
8
+
9
+ beforeEach(() => {
10
+ vol.reset()
11
+ })
12
+
13
+ describe('readOrGenerateStarterInfo', () => {
14
+ it('should read the starter info', async () => {
15
+ const starterInfo = await readOrGenerateStarterInfo({
16
+ framework: 'test',
17
+ version: 1,
18
+ existingAddOns: [],
19
+ starter: undefined,
20
+ projectName: 'test',
21
+ mode: 'code-router',
22
+ typescript: true,
23
+ tailwind: true,
24
+ git: true,
25
+ })
26
+ expect(starterInfo.id).toEqual('test-starter')
27
+ })
28
+
29
+ it('should read the starter info', async () => {
30
+ fs.mkdirSync(process.cwd(), { recursive: true })
31
+ fs.writeFileSync(
32
+ 'starter-info.json',
33
+ JSON.stringify({
34
+ framework: 'test',
35
+ version: 1,
36
+ existingAddOns: [],
37
+ starter: undefined,
38
+ name: 'test-starter',
39
+ mode: 'code-router',
40
+ typescript: true,
41
+ tailwind: true,
42
+ git: true,
43
+ }),
44
+ )
45
+ const starterInfo = await readOrGenerateStarterInfo({
46
+ framework: 'test',
47
+ version: 1,
48
+ existingAddOns: [],
49
+ starter: undefined,
50
+ projectName: 'test',
51
+ mode: 'code-router',
52
+ typescript: true,
53
+ tailwind: true,
54
+ git: true,
55
+ })
56
+ expect(starterInfo.name).toEqual('test-starter')
57
+ })
58
+ })
@@ -31,6 +31,25 @@ describe('createMemoryEnvironment', () => {
31
31
  expect(output.files['/test.txt']).toEqual('test')
32
32
  })
33
33
 
34
+ it('should remove empty directories', async () => {
35
+ const { environment, output } = createMemoryEnvironment()
36
+
37
+ environment.startRun()
38
+
39
+ await environment.writeFile('/test.txt', 'test')
40
+
41
+ await environment.writeFile('/foo/test1.txt', 'test')
42
+ await environment.writeFile('/foo/test2.txt', 'test')
43
+ environment.deleteFile('/foo/test1.txt')
44
+ environment.deleteFile('/foo/test2.txt')
45
+
46
+ await environment.writeFile('/bar/test1.txt', 'test')
47
+
48
+ environment.finishRun()
49
+
50
+ expect(Object.keys(output.files)).toEqual(['/test.txt', '/bar/test1.txt'])
51
+ })
52
+
34
53
  it('should track command execution', async () => {
35
54
  const { environment, output } = createMemoryEnvironment()
36
55
 
@@ -9,22 +9,17 @@ describe('shadcn', () => {
9
9
  it('should skip if no components are selected', async () => {
10
10
  const { environment, output } = createMemoryEnvironment()
11
11
  environment.startRun()
12
- await installShadcnComponents(
13
- environment,
14
- '/test',
15
- {
16
- packageManager: 'pnpm',
17
- chosenAddOns: [],
18
- projectName: 'test',
19
- typescript: true,
20
- spinner: () => ({
21
- start: () => {},
22
- succeed: () => {},
23
- fail: () => {},
24
- }),
25
- } as unknown as Options,
26
- true,
27
- )
12
+ await installShadcnComponents(environment, '/test', {
13
+ packageManager: 'pnpm',
14
+ chosenAddOns: [],
15
+ projectName: 'test',
16
+ typescript: true,
17
+ spinner: () => ({
18
+ start: () => {},
19
+ succeed: () => {},
20
+ fail: () => {},
21
+ }),
22
+ } as unknown as Options)
28
23
  environment.finishRun()
29
24
 
30
25
  expect(output.commands).toEqual([])
@@ -33,31 +28,26 @@ describe('shadcn', () => {
33
28
  it('should add shadcn components for add-ons', async () => {
34
29
  const { environment, output } = createMemoryEnvironment()
35
30
  environment.startRun()
36
- await installShadcnComponents(
37
- environment,
38
- '/test',
39
- {
40
- packageManager: 'pnpm',
41
- chosenAddOns: [
42
- {
43
- id: 'shadcn',
44
- shadcnComponents: ['button'],
45
- },
46
- {
47
- id: 'test-1',
48
- shadcnComponents: ['button', 'card'],
49
- },
50
- ],
51
- projectName: 'test',
52
- typescript: true,
53
- spinner: () => ({
54
- start: () => {},
55
- succeed: () => {},
56
- fail: () => {},
57
- }),
58
- } as unknown as Options,
59
- false,
60
- )
31
+ await installShadcnComponents(environment, '/test', {
32
+ packageManager: 'pnpm',
33
+ chosenAddOns: [
34
+ {
35
+ id: 'shadcn',
36
+ shadcnComponents: ['button'],
37
+ },
38
+ {
39
+ id: 'test-1',
40
+ shadcnComponents: ['button', 'card'],
41
+ },
42
+ ],
43
+ projectName: 'test',
44
+ typescript: true,
45
+ spinner: () => ({
46
+ start: () => {},
47
+ succeed: () => {},
48
+ fail: () => {},
49
+ }),
50
+ } as unknown as Options)
61
51
  environment.finishRun()
62
52
 
63
53
  expect(output.commands).toEqual([
@@ -71,29 +61,24 @@ describe('shadcn', () => {
71
61
  it('should add shadcn components in the starter', async () => {
72
62
  const { environment, output } = createMemoryEnvironment()
73
63
  environment.startRun()
74
- await installShadcnComponents(
75
- environment,
76
- '/test',
77
- {
78
- packageManager: 'pnpm',
79
- chosenAddOns: [
80
- {
81
- id: 'shadcn',
82
- },
83
- ],
84
- projectName: 'test',
85
- typescript: true,
86
- starter: {
87
- shadcnComponents: ['button', 'card'],
64
+ await installShadcnComponents(environment, '/test', {
65
+ packageManager: 'pnpm',
66
+ chosenAddOns: [
67
+ {
68
+ id: 'shadcn',
88
69
  },
89
- spinner: () => ({
90
- start: () => {},
91
- succeed: () => {},
92
- fail: () => {},
93
- }),
94
- } as unknown as Options,
95
- false,
96
- )
70
+ ],
71
+ projectName: 'test',
72
+ typescript: true,
73
+ starter: {
74
+ shadcnComponents: ['button', 'card'],
75
+ },
76
+ spinner: () => ({
77
+ start: () => {},
78
+ succeed: () => {},
79
+ fail: () => {},
80
+ }),
81
+ } as unknown as Options)
97
82
  environment.finishRun()
98
83
 
99
84
  expect(output.commands).toEqual([
@@ -0,0 +1,42 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { createSerializedOptions } from '../src/options.js'
4
+ import { AddOn, Framework, Options, Starter } from '../src/types.js'
5
+
6
+ describe('createSerializedOptions', () => {
7
+ it('handle no add-ons', () => {
8
+ const options = createSerializedOptions({
9
+ framework: {
10
+ id: 'react-cra',
11
+ } as Framework,
12
+ chosenAddOns: [],
13
+ } as unknown as Options)
14
+ expect(options).toEqual({
15
+ framework: 'react-cra',
16
+ chosenAddOns: [],
17
+ })
18
+ })
19
+
20
+ it('handle add-ons and a starter', () => {
21
+ const options = createSerializedOptions({
22
+ framework: {
23
+ id: 'react-cra',
24
+ } as Framework,
25
+ chosenAddOns: [
26
+ {
27
+ id: 'add-on-1',
28
+ description: 'Add-on 1',
29
+ modes: ['file-router'],
30
+ } as AddOn,
31
+ ],
32
+ starter: {
33
+ id: 'starter-1',
34
+ } as unknown as Starter,
35
+ } as unknown as Options)
36
+ expect(options).toEqual({
37
+ framework: 'react-cra',
38
+ chosenAddOns: ['add-on-1'],
39
+ starter: 'starter-1',
40
+ })
41
+ })
42
+ })
@@ -0,0 +1,6 @@
1
+ import createFetchMock from 'vitest-fetch-mock'
2
+ import { vi } from 'vitest'
3
+
4
+ const fetchMocker = createFetchMock(vi)
5
+
6
+ fetchMocker.enableMocks()