@tanstack/cta-engine 0.10.0-alpha.16 → 0.10.0-alpha.18
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/package-json.js +3 -4
- package/dist/template-file.js +21 -7
- package/dist/types/types.d.ts +6 -5
- package/package.json +2 -1
- package/src/package-json.ts +5 -5
- package/src/template-file.ts +29 -10
- package/src/types.ts +7 -5
- package/tests/add-ons.test.ts +67 -0
- package/tests/config-file.test.ts +39 -0
- package/tests/create-app.test.ts +145 -0
- package/tests/environment.test.ts +27 -0
- package/tests/integrations/git.test.ts +20 -0
- package/tests/integrations/shadcn.test.ts +106 -0
- package/tests/package-json.test.ts +63 -0
- package/tests/template-file.test.ts +217 -0
- package/tests/utils.test.ts +23 -0
package/dist/package-json.js
CHANGED
|
@@ -18,7 +18,6 @@ function mergePackageJSON(packageJSON, overlayPackageJSON) {
|
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
export function createPackageJSON(options) {
|
|
21
|
-
const addOns = options.chosenAddOns.map((addOn) => addOn.packageAdditions);
|
|
22
21
|
let packageJSON = {
|
|
23
22
|
...JSON.parse(JSON.stringify(options.framework.basePackageJSON)),
|
|
24
23
|
name: options.projectName,
|
|
@@ -37,10 +36,10 @@ export function createPackageJSON(options) {
|
|
|
37
36
|
for (const addition of additions.filter(Boolean)) {
|
|
38
37
|
packageJSON = mergePackageJSON(packageJSON, addition);
|
|
39
38
|
}
|
|
40
|
-
for (const addOn of
|
|
39
|
+
for (const addOn of options.chosenAddOns.map((addOn) => addOn.packageAdditions)) {
|
|
41
40
|
packageJSON = mergePackageJSON(packageJSON, addOn);
|
|
42
41
|
}
|
|
43
|
-
packageJSON.dependencies = sortObject(packageJSON.dependencies);
|
|
44
|
-
packageJSON.devDependencies = sortObject(packageJSON.devDependencies);
|
|
42
|
+
packageJSON.dependencies = sortObject((packageJSON.dependencies ?? {}));
|
|
43
|
+
packageJSON.devDependencies = sortObject((packageJSON.devDependencies ?? {}));
|
|
45
44
|
return packageJSON;
|
|
46
45
|
}
|
package/dist/template-file.js
CHANGED
|
@@ -3,7 +3,7 @@ import { render } from 'ejs';
|
|
|
3
3
|
import { format } from 'prettier';
|
|
4
4
|
import { CODE_ROUTER, FILE_ROUTER } from './constants.js';
|
|
5
5
|
import { formatCommand } from './utils.js';
|
|
6
|
-
import {
|
|
6
|
+
import { getPackageManagerInstallCommand, getPackageManagerScriptCommand, } from './package-manager.js';
|
|
7
7
|
import { relativePath } from './file-helpers.js';
|
|
8
8
|
function convertDotFilesAndPaths(path) {
|
|
9
9
|
return path
|
|
@@ -15,8 +15,11 @@ export function createTemplateFile(environment, options, targetDir) {
|
|
|
15
15
|
function getPackageManagerAddScript(packageName, isDev = false) {
|
|
16
16
|
return formatCommand(getPackageManagerInstallCommand(options.packageManager, packageName, isDev));
|
|
17
17
|
}
|
|
18
|
-
function getPackageManagerRunScript(scriptName) {
|
|
19
|
-
return formatCommand(
|
|
18
|
+
function getPackageManagerRunScript(scriptName, args = []) {
|
|
19
|
+
return formatCommand(getPackageManagerScriptCommand(options.packageManager, [
|
|
20
|
+
scriptName,
|
|
21
|
+
...args,
|
|
22
|
+
]));
|
|
20
23
|
}
|
|
21
24
|
class IgnoreFileError extends Error {
|
|
22
25
|
constructor() {
|
|
@@ -38,6 +41,19 @@ export function createTemplateFile(environment, options, targetDir) {
|
|
|
38
41
|
routes.push(...addOn.routes);
|
|
39
42
|
}
|
|
40
43
|
}
|
|
44
|
+
const variables = {
|
|
45
|
+
...options.variableValues,
|
|
46
|
+
...options.chosenAddOns.reduce((acc, addOn) => {
|
|
47
|
+
return {
|
|
48
|
+
...acc,
|
|
49
|
+
...addOn.variables,
|
|
50
|
+
};
|
|
51
|
+
}, {}),
|
|
52
|
+
};
|
|
53
|
+
const addOnEnabled = options.chosenAddOns.reduce((acc, addOn) => {
|
|
54
|
+
acc[addOn.id] = true;
|
|
55
|
+
return acc;
|
|
56
|
+
}, {});
|
|
41
57
|
return async function templateFile(file, content) {
|
|
42
58
|
const templateValues = {
|
|
43
59
|
packageManager: options.packageManager,
|
|
@@ -48,13 +64,11 @@ export function createTemplateFile(environment, options, targetDir) {
|
|
|
48
64
|
jsx: options.typescript ? 'tsx' : 'jsx',
|
|
49
65
|
fileRouter: options.mode === FILE_ROUTER,
|
|
50
66
|
codeRouter: options.mode === CODE_ROUTER,
|
|
51
|
-
addOnEnabled
|
|
52
|
-
acc[addOn.id] = true;
|
|
53
|
-
return acc;
|
|
54
|
-
}, {}),
|
|
67
|
+
addOnEnabled,
|
|
55
68
|
addOns: options.chosenAddOns,
|
|
56
69
|
integrations,
|
|
57
70
|
routes,
|
|
71
|
+
variables,
|
|
58
72
|
getPackageManagerAddScript,
|
|
59
73
|
getPackageManagerRunScript,
|
|
60
74
|
relativePath: (path) => relativePath(file, path),
|
package/dist/types/types.d.ts
CHANGED
|
@@ -6,6 +6,11 @@ export type FileBundleHandler = {
|
|
|
6
6
|
getFiles: () => Promise<Array<string>>;
|
|
7
7
|
getFileContents: (path: string) => Promise<string>;
|
|
8
8
|
};
|
|
9
|
+
export type Integration = {
|
|
10
|
+
type: 'provider' | 'root-provider' | 'layout' | 'header-user';
|
|
11
|
+
path: string;
|
|
12
|
+
jsName: string;
|
|
13
|
+
};
|
|
9
14
|
export type AddOnDefinition = {
|
|
10
15
|
id: string;
|
|
11
16
|
name: string;
|
|
@@ -33,11 +38,7 @@ export type AddOnDefinition = {
|
|
|
33
38
|
shadcnComponents?: Array<string>;
|
|
34
39
|
warning?: string;
|
|
35
40
|
dependsOn?: Array<string>;
|
|
36
|
-
integrations?: Array<
|
|
37
|
-
type: 'provider' | 'root-provider' | 'layout' | 'header-user';
|
|
38
|
-
path: string;
|
|
39
|
-
jsName: string;
|
|
40
|
-
}>;
|
|
41
|
+
integrations?: Array<Integration>;
|
|
41
42
|
variables?: Array<Variable>;
|
|
42
43
|
files?: Record<string, string>;
|
|
43
44
|
deletedFiles?: Array<string>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/cta-engine",
|
|
3
|
-
"version": "0.10.0-alpha.
|
|
3
|
+
"version": "0.10.0-alpha.18",
|
|
4
4
|
"description": "Tanstack Application Builder Engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"@tanstack/config": "^0.16.2",
|
|
32
32
|
"@types/ejs": "^3.1.5",
|
|
33
33
|
"@types/node": "^22.13.4",
|
|
34
|
+
"@vitest/coverage-v8": "3.1.1",
|
|
34
35
|
"eslint": "^9.20.0",
|
|
35
36
|
"typescript": "^5.6.3",
|
|
36
37
|
"vitest": "^3.0.8"
|
package/src/package-json.ts
CHANGED
|
@@ -25,8 +25,6 @@ function mergePackageJSON(
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export function createPackageJSON(options: Options) {
|
|
28
|
-
const addOns = options.chosenAddOns.map((addOn) => addOn.packageAdditions)
|
|
29
|
-
|
|
30
28
|
let packageJSON = {
|
|
31
29
|
...JSON.parse(JSON.stringify(options.framework.basePackageJSON)),
|
|
32
30
|
name: options.projectName,
|
|
@@ -47,15 +45,17 @@ export function createPackageJSON(options: Options) {
|
|
|
47
45
|
packageJSON = mergePackageJSON(packageJSON, addition!)
|
|
48
46
|
}
|
|
49
47
|
|
|
50
|
-
for (const addOn of
|
|
48
|
+
for (const addOn of options.chosenAddOns.map(
|
|
49
|
+
(addOn) => addOn.packageAdditions,
|
|
50
|
+
)) {
|
|
51
51
|
packageJSON = mergePackageJSON(packageJSON, addOn)
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
packageJSON.dependencies = sortObject(
|
|
55
|
-
packageJSON.dependencies as Record<string, string>,
|
|
55
|
+
(packageJSON.dependencies ?? {}) as Record<string, string>,
|
|
56
56
|
)
|
|
57
57
|
packageJSON.devDependencies = sortObject(
|
|
58
|
-
packageJSON.devDependencies as Record<string, string>,
|
|
58
|
+
(packageJSON.devDependencies ?? {}) as Record<string, string>,
|
|
59
59
|
)
|
|
60
60
|
|
|
61
61
|
return packageJSON
|
package/src/template-file.ts
CHANGED
|
@@ -5,8 +5,8 @@ import { format } from 'prettier'
|
|
|
5
5
|
import { CODE_ROUTER, FILE_ROUTER } from './constants.js'
|
|
6
6
|
import { formatCommand } from './utils.js'
|
|
7
7
|
import {
|
|
8
|
-
getPackageManagerExecuteCommand,
|
|
9
8
|
getPackageManagerInstallCommand,
|
|
9
|
+
getPackageManagerScriptCommand,
|
|
10
10
|
} from './package-manager.js'
|
|
11
11
|
import { relativePath } from './file-helpers.js'
|
|
12
12
|
|
|
@@ -36,9 +36,15 @@ export function createTemplateFile(
|
|
|
36
36
|
),
|
|
37
37
|
)
|
|
38
38
|
}
|
|
39
|
-
function getPackageManagerRunScript(
|
|
39
|
+
function getPackageManagerRunScript(
|
|
40
|
+
scriptName: string,
|
|
41
|
+
args: Array<string> = [],
|
|
42
|
+
) {
|
|
40
43
|
return formatCommand(
|
|
41
|
-
|
|
44
|
+
getPackageManagerScriptCommand(options.packageManager, [
|
|
45
|
+
scriptName,
|
|
46
|
+
...args,
|
|
47
|
+
]),
|
|
42
48
|
)
|
|
43
49
|
}
|
|
44
50
|
|
|
@@ -65,6 +71,24 @@ export function createTemplateFile(
|
|
|
65
71
|
}
|
|
66
72
|
}
|
|
67
73
|
|
|
74
|
+
const variables = {
|
|
75
|
+
...options.variableValues,
|
|
76
|
+
...options.chosenAddOns.reduce<Record<string, any>>((acc, addOn) => {
|
|
77
|
+
return {
|
|
78
|
+
...acc,
|
|
79
|
+
...addOn.variables,
|
|
80
|
+
}
|
|
81
|
+
}, {}),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const addOnEnabled = options.chosenAddOns.reduce<Record<string, boolean>>(
|
|
85
|
+
(acc, addOn) => {
|
|
86
|
+
acc[addOn.id] = true
|
|
87
|
+
return acc
|
|
88
|
+
},
|
|
89
|
+
{},
|
|
90
|
+
)
|
|
91
|
+
|
|
68
92
|
return async function templateFile(file: string, content: string) {
|
|
69
93
|
const templateValues = {
|
|
70
94
|
packageManager: options.packageManager,
|
|
@@ -75,16 +99,11 @@ export function createTemplateFile(
|
|
|
75
99
|
jsx: options.typescript ? 'tsx' : 'jsx',
|
|
76
100
|
fileRouter: options.mode === FILE_ROUTER,
|
|
77
101
|
codeRouter: options.mode === CODE_ROUTER,
|
|
78
|
-
addOnEnabled
|
|
79
|
-
(acc, addOn) => {
|
|
80
|
-
acc[addOn.id] = true
|
|
81
|
-
return acc
|
|
82
|
-
},
|
|
83
|
-
{},
|
|
84
|
-
),
|
|
102
|
+
addOnEnabled,
|
|
85
103
|
addOns: options.chosenAddOns,
|
|
86
104
|
integrations,
|
|
87
105
|
routes,
|
|
106
|
+
variables,
|
|
88
107
|
|
|
89
108
|
getPackageManagerAddScript,
|
|
90
109
|
getPackageManagerRunScript,
|
package/src/types.ts
CHANGED
|
@@ -10,6 +10,12 @@ export type FileBundleHandler = {
|
|
|
10
10
|
getFileContents: (path: string) => Promise<string>
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
export type Integration = {
|
|
14
|
+
type: 'provider' | 'root-provider' | 'layout' | 'header-user'
|
|
15
|
+
path: string
|
|
16
|
+
jsName: string
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
export type AddOnDefinition = {
|
|
14
20
|
id: string
|
|
15
21
|
name: string
|
|
@@ -37,11 +43,7 @@ export type AddOnDefinition = {
|
|
|
37
43
|
shadcnComponents?: Array<string>
|
|
38
44
|
warning?: string
|
|
39
45
|
dependsOn?: Array<string>
|
|
40
|
-
integrations?: Array<
|
|
41
|
-
type: 'provider' | 'root-provider' | 'layout' | 'header-user'
|
|
42
|
-
path: string
|
|
43
|
-
jsName: string
|
|
44
|
-
}>
|
|
46
|
+
integrations?: Array<Integration>
|
|
45
47
|
variables?: Array<Variable>
|
|
46
48
|
|
|
47
49
|
files?: Record<string, string>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { finalizeAddOns, getAllAddOns } from '../src/add-ons.js'
|
|
4
|
+
|
|
5
|
+
import type { AddOn, Framework } from '../src/types.js'
|
|
6
|
+
|
|
7
|
+
describe('getAllAddOns', () => {
|
|
8
|
+
it('filter add-ons', () => {
|
|
9
|
+
const addOns = getAllAddOns(
|
|
10
|
+
{
|
|
11
|
+
id: 'react-cra',
|
|
12
|
+
getAddOns: () => [
|
|
13
|
+
{
|
|
14
|
+
id: 'add-on-1',
|
|
15
|
+
description: 'Add-on 1',
|
|
16
|
+
templates: ['file-router'],
|
|
17
|
+
} as AddOn,
|
|
18
|
+
{
|
|
19
|
+
id: 'add-on-2',
|
|
20
|
+
description: 'Add-on 2',
|
|
21
|
+
templates: ['code-router'],
|
|
22
|
+
} as AddOn,
|
|
23
|
+
],
|
|
24
|
+
} as Framework,
|
|
25
|
+
'file-router',
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
expect(addOns.length).toEqual(1)
|
|
29
|
+
expect(addOns[0].id).toEqual('add-on-1')
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe('finalizeAddOns', () => {
|
|
34
|
+
it('should finalize add-ons', async () => {
|
|
35
|
+
const addOns = await finalizeAddOns(
|
|
36
|
+
{
|
|
37
|
+
id: 'react-cra',
|
|
38
|
+
getAddOns: () => [
|
|
39
|
+
{
|
|
40
|
+
id: 'add-on-1',
|
|
41
|
+
description: 'Add-on 1',
|
|
42
|
+
templates: ['file-router'],
|
|
43
|
+
dependsOn: ['add-on-2'],
|
|
44
|
+
} as AddOn,
|
|
45
|
+
{
|
|
46
|
+
id: 'add-on-2',
|
|
47
|
+
description: 'Add-on 2',
|
|
48
|
+
templates: ['file-router'],
|
|
49
|
+
} as AddOn,
|
|
50
|
+
{
|
|
51
|
+
id: 'add-on-3',
|
|
52
|
+
description: 'Add-on 3',
|
|
53
|
+
templates: ['file-router'],
|
|
54
|
+
} as AddOn,
|
|
55
|
+
],
|
|
56
|
+
} as Framework,
|
|
57
|
+
'file-router',
|
|
58
|
+
['add-on-1'],
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
expect(addOns.length).toEqual(2)
|
|
62
|
+
const addOnIds = addOns.map((a) => a.id)
|
|
63
|
+
expect(addOnIds.includes('add-on-1')).toEqual(true)
|
|
64
|
+
expect(addOnIds.includes('add-on-2')).toEqual(true)
|
|
65
|
+
expect(addOnIds.includes('add-on-3')).toEqual(false)
|
|
66
|
+
})
|
|
67
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import { writeConfigFile } from '../src/config-file.js'
|
|
5
|
+
import { CONFIG_FILE } from '../src/constants.js'
|
|
6
|
+
|
|
7
|
+
import type { AddOn, Environment, Framework, Options } from '../src/types.js'
|
|
8
|
+
|
|
9
|
+
describe('writeConfigFile', () => {
|
|
10
|
+
it('should write the config file', async () => {
|
|
11
|
+
const options = {
|
|
12
|
+
framework: {
|
|
13
|
+
id: 'react-cra',
|
|
14
|
+
getAddOns: () => [],
|
|
15
|
+
} as unknown as Framework,
|
|
16
|
+
chosenAddOns: [
|
|
17
|
+
{
|
|
18
|
+
id: 'add-on-1',
|
|
19
|
+
description: 'Add-on 1',
|
|
20
|
+
templates: ['file-router'],
|
|
21
|
+
} as AddOn,
|
|
22
|
+
],
|
|
23
|
+
addOns: [],
|
|
24
|
+
} as unknown as Options
|
|
25
|
+
const targetDir = 'test-dir'
|
|
26
|
+
const persistedOptions = {
|
|
27
|
+
version: 1,
|
|
28
|
+
framework: options.framework.id,
|
|
29
|
+
existingAddOns: options.chosenAddOns.map((addOn) => addOn.id),
|
|
30
|
+
}
|
|
31
|
+
const env = {
|
|
32
|
+
writeFile: (path, optionsString) => {
|
|
33
|
+
expect(path).toEqual(resolve(targetDir, CONFIG_FILE))
|
|
34
|
+
expect(optionsString).toEqual(JSON.stringify(persistedOptions, null, 2))
|
|
35
|
+
},
|
|
36
|
+
} as Environment
|
|
37
|
+
await writeConfigFile(env, targetDir, options)
|
|
38
|
+
})
|
|
39
|
+
})
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from 'vitest'
|
|
4
|
+
|
|
5
|
+
import { createApp } from '../src/create-app.js'
|
|
6
|
+
|
|
7
|
+
import { createMemoryEnvironment } from '../src/environment.js'
|
|
8
|
+
import { FILE_ROUTER } from '../src/constants.js'
|
|
9
|
+
import { AddOn, Options } from '../src/types.js'
|
|
10
|
+
|
|
11
|
+
const simpleOptions = {
|
|
12
|
+
projectName: 'test',
|
|
13
|
+
framework: {
|
|
14
|
+
id: 'test',
|
|
15
|
+
name: 'Test',
|
|
16
|
+
basePackageJSON: {
|
|
17
|
+
scripts: {
|
|
18
|
+
dev: 'react-scripts start',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
optionalPackages: {
|
|
22
|
+
typescript: {
|
|
23
|
+
devDependencies: {
|
|
24
|
+
typescript: '^5.0.0',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
tailwindcss: {
|
|
28
|
+
dependencies: {
|
|
29
|
+
tailwindcss: '^3.0.0',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
'file-router': {
|
|
33
|
+
dependencies: {
|
|
34
|
+
'file-router': '^1.0.0',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
getFiles: () => ['./src/test.txt'],
|
|
39
|
+
getFileContents: () => 'Hello',
|
|
40
|
+
},
|
|
41
|
+
chosenAddOns: [],
|
|
42
|
+
packageManager: 'pnpm',
|
|
43
|
+
typescript: true,
|
|
44
|
+
tailwind: true,
|
|
45
|
+
mode: FILE_ROUTER,
|
|
46
|
+
variableValues: {},
|
|
47
|
+
} as unknown as Options
|
|
48
|
+
|
|
49
|
+
describe('createApp', () => {
|
|
50
|
+
it('should create an app', async () => {
|
|
51
|
+
const { environment, output } = createMemoryEnvironment()
|
|
52
|
+
await createApp(simpleOptions, {
|
|
53
|
+
silent: true,
|
|
54
|
+
environment,
|
|
55
|
+
name: 'Test',
|
|
56
|
+
cwd: '/',
|
|
57
|
+
appName: 'TanStack App',
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
expect(output.files['/src/test.txt']).toEqual('Hello')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('should create an app - not silent', async () => {
|
|
64
|
+
const { environment, output } = createMemoryEnvironment()
|
|
65
|
+
await createApp(simpleOptions, {
|
|
66
|
+
silent: false,
|
|
67
|
+
environment,
|
|
68
|
+
name: 'Test',
|
|
69
|
+
cwd: '/foo/bar/baz',
|
|
70
|
+
appName: 'TanStack App',
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const cwd = process.cwd()
|
|
74
|
+
|
|
75
|
+
expect(output.files[resolve(cwd, '/foo/bar/baz/src/test.txt')]).toEqual(
|
|
76
|
+
'Hello',
|
|
77
|
+
)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('should create an app - with a starter', async () => {
|
|
81
|
+
const { environment, output } = createMemoryEnvironment()
|
|
82
|
+
await createApp(
|
|
83
|
+
{
|
|
84
|
+
...simpleOptions,
|
|
85
|
+
starter: {
|
|
86
|
+
command: {
|
|
87
|
+
command: 'echo',
|
|
88
|
+
args: ['Hello'],
|
|
89
|
+
},
|
|
90
|
+
getFiles: () => ['./src/test2.txt'],
|
|
91
|
+
getFileContents: () => 'Hello-2',
|
|
92
|
+
} as unknown as AddOn,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
silent: false,
|
|
96
|
+
environment,
|
|
97
|
+
name: 'Test',
|
|
98
|
+
cwd: '/',
|
|
99
|
+
appName: 'TanStack App',
|
|
100
|
+
},
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
expect(output.files['/src/test2.txt']).toEqual('Hello-2')
|
|
104
|
+
expect(output.commands.some(({ command }) => command === 'echo')).toBe(true)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('should create an app - with a add-on', async () => {
|
|
108
|
+
const { environment, output } = createMemoryEnvironment()
|
|
109
|
+
await createApp(
|
|
110
|
+
{
|
|
111
|
+
...simpleOptions,
|
|
112
|
+
git: true,
|
|
113
|
+
addOns: true,
|
|
114
|
+
chosenAddOns: [
|
|
115
|
+
{
|
|
116
|
+
type: 'add-on',
|
|
117
|
+
phase: 'add-on',
|
|
118
|
+
warning: 'This is a warning',
|
|
119
|
+
command: {
|
|
120
|
+
command: 'echo',
|
|
121
|
+
args: ['Hello'],
|
|
122
|
+
},
|
|
123
|
+
packageAdditions: {
|
|
124
|
+
dependencies: {},
|
|
125
|
+
devDependencies: {},
|
|
126
|
+
},
|
|
127
|
+
getFiles: () => ['./src/test2.txt', './public/foo.jpg'],
|
|
128
|
+
getFileContents: () => 'base64::aGVsbG8=',
|
|
129
|
+
} as unknown as AddOn,
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
silent: false,
|
|
134
|
+
environment,
|
|
135
|
+
name: 'Test',
|
|
136
|
+
cwd: '/',
|
|
137
|
+
appName: 'TanStack App',
|
|
138
|
+
},
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
console.log(output.commands)
|
|
142
|
+
expect(output.files['/src/test2.txt']).toEqual('hello')
|
|
143
|
+
expect(output.commands.some(({ command }) => command === 'echo')).toBe(true)
|
|
144
|
+
})
|
|
145
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { createMemoryEnvironment } from '../src/environment.js'
|
|
4
|
+
|
|
5
|
+
describe('createMemoryEnvironment', () => {
|
|
6
|
+
it('should handle basic file operations', async () => {
|
|
7
|
+
const { environment, output } = createMemoryEnvironment()
|
|
8
|
+
|
|
9
|
+
environment.startRun()
|
|
10
|
+
await environment.writeFile('/test.txt', 'test')
|
|
11
|
+
environment.finishRun()
|
|
12
|
+
|
|
13
|
+
expect(output.files['/test.txt']).toEqual('test')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should track command execution', async () => {
|
|
17
|
+
const { environment, output } = createMemoryEnvironment()
|
|
18
|
+
|
|
19
|
+
environment.startRun()
|
|
20
|
+
await environment.execute('echo', ['test'], '')
|
|
21
|
+
environment.finishRun()
|
|
22
|
+
|
|
23
|
+
expect(output.commands.length).toEqual(1)
|
|
24
|
+
expect(output.commands[0].command).toEqual('echo')
|
|
25
|
+
expect(output.commands[0].args).toEqual(['test'])
|
|
26
|
+
})
|
|
27
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { createMemoryEnvironment } from '../../src/environment.js'
|
|
4
|
+
import { setupGit } from '../../src/integrations/git.js'
|
|
5
|
+
|
|
6
|
+
describe('git', () => {
|
|
7
|
+
it('should create a git repository', async () => {
|
|
8
|
+
const { environment, output } = createMemoryEnvironment()
|
|
9
|
+
environment.startRun()
|
|
10
|
+
await setupGit(environment, '/test')
|
|
11
|
+
environment.finishRun()
|
|
12
|
+
|
|
13
|
+
expect(output.commands).toEqual([
|
|
14
|
+
{
|
|
15
|
+
command: 'git',
|
|
16
|
+
args: ['init'],
|
|
17
|
+
},
|
|
18
|
+
])
|
|
19
|
+
})
|
|
20
|
+
})
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { createMemoryEnvironment } from '../../src/environment.js'
|
|
4
|
+
import { installShadcnComponents } from '../../src/integrations/shadcn.js'
|
|
5
|
+
|
|
6
|
+
import type { Options } from '../../src/types.js'
|
|
7
|
+
|
|
8
|
+
describe('shadcn', () => {
|
|
9
|
+
it('should skip if no components are selected', async () => {
|
|
10
|
+
const { environment, output } = createMemoryEnvironment()
|
|
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
|
+
)
|
|
28
|
+
environment.finishRun()
|
|
29
|
+
|
|
30
|
+
expect(output.commands).toEqual([])
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('should add shadcn components for add-ons', async () => {
|
|
34
|
+
const { environment, output } = createMemoryEnvironment()
|
|
35
|
+
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
|
+
)
|
|
61
|
+
environment.finishRun()
|
|
62
|
+
|
|
63
|
+
expect(output.commands).toEqual([
|
|
64
|
+
{
|
|
65
|
+
command: 'pnpx',
|
|
66
|
+
args: ['shadcn@latest', 'add', '--silent', '--yes', 'button', 'card'],
|
|
67
|
+
},
|
|
68
|
+
])
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('should add shadcn components in the starter', async () => {
|
|
72
|
+
const { environment, output } = createMemoryEnvironment()
|
|
73
|
+
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'],
|
|
88
|
+
},
|
|
89
|
+
spinner: () => ({
|
|
90
|
+
start: () => {},
|
|
91
|
+
succeed: () => {},
|
|
92
|
+
fail: () => {},
|
|
93
|
+
}),
|
|
94
|
+
} as unknown as Options,
|
|
95
|
+
false,
|
|
96
|
+
)
|
|
97
|
+
environment.finishRun()
|
|
98
|
+
|
|
99
|
+
expect(output.commands).toEqual([
|
|
100
|
+
{
|
|
101
|
+
command: 'pnpx',
|
|
102
|
+
args: ['shadcn@latest', 'add', '--silent', '--yes', 'button', 'card'],
|
|
103
|
+
},
|
|
104
|
+
])
|
|
105
|
+
})
|
|
106
|
+
})
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { createPackageJSON } from '../src/package-json.js'
|
|
4
|
+
import { FILE_ROUTER } from '../src/constants.js'
|
|
5
|
+
|
|
6
|
+
import type { Options, Framework } from '../src/types.js'
|
|
7
|
+
|
|
8
|
+
describe('createPackageJSON', () => {
|
|
9
|
+
it('should create a package.json', () => {
|
|
10
|
+
const packageJSON = createPackageJSON({
|
|
11
|
+
chosenAddOns: [
|
|
12
|
+
{
|
|
13
|
+
packageAdditions: {
|
|
14
|
+
scripts: {
|
|
15
|
+
dev: 'file-router dev',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
mode: FILE_ROUTER,
|
|
21
|
+
typescript: true,
|
|
22
|
+
tailwind: true,
|
|
23
|
+
projectName: 'test',
|
|
24
|
+
framework: {
|
|
25
|
+
basePackageJSON: {},
|
|
26
|
+
optionalPackages: {
|
|
27
|
+
typescript: {
|
|
28
|
+
devDependencies: {
|
|
29
|
+
typescript: '^5.0.0',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
tailwindcss: {
|
|
33
|
+
dependencies: {
|
|
34
|
+
tailwindcss: '^3.0.0',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
'file-router': {
|
|
38
|
+
dependencies: {
|
|
39
|
+
'file-router': '^1.0.0',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
} as unknown as Framework,
|
|
44
|
+
} as unknown as Options)
|
|
45
|
+
|
|
46
|
+
const expected = {
|
|
47
|
+
name: 'test',
|
|
48
|
+
dependencies: {
|
|
49
|
+
'file-router': '^1.0.0',
|
|
50
|
+
tailwindcss: '^3.0.0',
|
|
51
|
+
},
|
|
52
|
+
devDependencies: {
|
|
53
|
+
typescript: '^5.0.0',
|
|
54
|
+
},
|
|
55
|
+
scripts: {
|
|
56
|
+
dev: 'file-router dev',
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Use JSON.stringify to test sorting order of dependencies
|
|
61
|
+
expect(JSON.stringify(packageJSON)).toEqual(JSON.stringify(expected))
|
|
62
|
+
})
|
|
63
|
+
})
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { createMemoryEnvironment } from '../src/environment.js'
|
|
4
|
+
import { createTemplateFile } from '../src/template-file.js'
|
|
5
|
+
import { FILE_ROUTER } from '../src/constants.js'
|
|
6
|
+
|
|
7
|
+
import type { AddOn, Integration, Options } from '../src/types.js'
|
|
8
|
+
|
|
9
|
+
const simpleOptions = {
|
|
10
|
+
projectName: 'test',
|
|
11
|
+
framework: {
|
|
12
|
+
id: 'test',
|
|
13
|
+
name: 'Test',
|
|
14
|
+
},
|
|
15
|
+
chosenAddOns: [],
|
|
16
|
+
packageManager: 'pnpm',
|
|
17
|
+
typescript: true,
|
|
18
|
+
tailwind: true,
|
|
19
|
+
mode: FILE_ROUTER,
|
|
20
|
+
variableValues: {
|
|
21
|
+
a: 'foo',
|
|
22
|
+
},
|
|
23
|
+
} as unknown as Options
|
|
24
|
+
|
|
25
|
+
describe('createTemplateFile', () => {
|
|
26
|
+
it('should template a simple file', async () => {
|
|
27
|
+
const { environment, output } = createMemoryEnvironment()
|
|
28
|
+
const templateFile = createTemplateFile(environment, simpleOptions, '/test')
|
|
29
|
+
environment.startRun()
|
|
30
|
+
await templateFile('./test.ts', 'let a = 1')
|
|
31
|
+
environment.finishRun()
|
|
32
|
+
|
|
33
|
+
expect(output.files['/test/test.ts'].trim()).toEqual('let a = 1')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('should template a simple file with ejs', async () => {
|
|
37
|
+
const { environment, output } = createMemoryEnvironment()
|
|
38
|
+
const templateFile = createTemplateFile(
|
|
39
|
+
environment,
|
|
40
|
+
{
|
|
41
|
+
...simpleOptions,
|
|
42
|
+
variableValues: {
|
|
43
|
+
a: 'foo',
|
|
44
|
+
},
|
|
45
|
+
} as unknown as Options,
|
|
46
|
+
'/test',
|
|
47
|
+
)
|
|
48
|
+
environment.startRun()
|
|
49
|
+
await templateFile('./test.ts.ejs', "let a = '<%= variables.a %>'")
|
|
50
|
+
environment.finishRun()
|
|
51
|
+
|
|
52
|
+
expect(output.files['/test/test.ts'].trim()).toEqual("let a = 'foo'")
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('should handle ignore files', async () => {
|
|
56
|
+
const { environment, output } = createMemoryEnvironment()
|
|
57
|
+
const templateFile = createTemplateFile(
|
|
58
|
+
environment,
|
|
59
|
+
{
|
|
60
|
+
...simpleOptions,
|
|
61
|
+
} as unknown as Options,
|
|
62
|
+
'/test',
|
|
63
|
+
)
|
|
64
|
+
environment.startRun()
|
|
65
|
+
await templateFile('./test.ts.ejs', '<% ignoreFile() %>let a = 1')
|
|
66
|
+
environment.finishRun()
|
|
67
|
+
|
|
68
|
+
expect(output.files['/test/test.ts']).toBeUndefined()
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('should handle append files', async () => {
|
|
72
|
+
const { environment, output } = createMemoryEnvironment()
|
|
73
|
+
const templateFile = createTemplateFile(environment, simpleOptions, '/test')
|
|
74
|
+
environment.startRun()
|
|
75
|
+
await templateFile('./test.txt.ejs', 'Line 1\n')
|
|
76
|
+
await templateFile('./test.txt.append', 'Line 2\n')
|
|
77
|
+
environment.finishRun()
|
|
78
|
+
|
|
79
|
+
expect(output.files['/test/test.txt']).toEqual('Line 1\nLine 2\n')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('should handle enabled add-ons', async () => {
|
|
83
|
+
const { environment, output } = createMemoryEnvironment()
|
|
84
|
+
const templateFile = createTemplateFile(
|
|
85
|
+
environment,
|
|
86
|
+
{
|
|
87
|
+
...simpleOptions,
|
|
88
|
+
chosenAddOns: [
|
|
89
|
+
{
|
|
90
|
+
id: 'test1',
|
|
91
|
+
name: 'Test 1',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'test2',
|
|
95
|
+
name: 'Test 2',
|
|
96
|
+
},
|
|
97
|
+
] as Array<AddOn>,
|
|
98
|
+
},
|
|
99
|
+
'/test',
|
|
100
|
+
)
|
|
101
|
+
environment.startRun()
|
|
102
|
+
await templateFile(
|
|
103
|
+
'./test.txt.ejs',
|
|
104
|
+
"Addons: <%= Object.keys(addOnEnabled).join(', ') %>",
|
|
105
|
+
)
|
|
106
|
+
environment.finishRun()
|
|
107
|
+
|
|
108
|
+
expect(output.files['/test/test.txt']).toEqual('Addons: test1, test2')
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('should handle relative paths', async () => {
|
|
112
|
+
const { environment, output } = createMemoryEnvironment()
|
|
113
|
+
const templateFile = createTemplateFile(environment, simpleOptions, '/test')
|
|
114
|
+
environment.startRun()
|
|
115
|
+
await templateFile(
|
|
116
|
+
'./src/test/test.txt.ejs',
|
|
117
|
+
"import { foo } from '<%= relativePath('./foo.ts') %>'",
|
|
118
|
+
)
|
|
119
|
+
environment.finishRun()
|
|
120
|
+
|
|
121
|
+
expect(output.files['/test/src/test/test.txt']).toEqual(
|
|
122
|
+
"import { foo } from '../../foo.ts'",
|
|
123
|
+
)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('should handle routes', async () => {
|
|
127
|
+
const { environment, output } = createMemoryEnvironment()
|
|
128
|
+
const templateFile = createTemplateFile(
|
|
129
|
+
environment,
|
|
130
|
+
{
|
|
131
|
+
...simpleOptions,
|
|
132
|
+
chosenAddOns: [
|
|
133
|
+
{
|
|
134
|
+
id: 'test',
|
|
135
|
+
name: 'Test',
|
|
136
|
+
routes: [
|
|
137
|
+
{
|
|
138
|
+
path: '/test',
|
|
139
|
+
name: 'Test',
|
|
140
|
+
url: '/test',
|
|
141
|
+
jsName: 'test',
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
} as AddOn,
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
'/test',
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
environment.startRun()
|
|
151
|
+
await templateFile(
|
|
152
|
+
'./test.txt.ejs',
|
|
153
|
+
"<%= routes.map((route) => route.url).join(', ') %>",
|
|
154
|
+
)
|
|
155
|
+
environment.finishRun()
|
|
156
|
+
|
|
157
|
+
console.log(output.files['/test/test.txt'])
|
|
158
|
+
|
|
159
|
+
expect(output.files['/test/test.txt']).toEqual('/test')
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('should handle integrations', async () => {
|
|
163
|
+
const { environment, output } = createMemoryEnvironment()
|
|
164
|
+
const templateFile = createTemplateFile(
|
|
165
|
+
environment,
|
|
166
|
+
{
|
|
167
|
+
...simpleOptions,
|
|
168
|
+
chosenAddOns: [
|
|
169
|
+
{
|
|
170
|
+
id: 'test',
|
|
171
|
+
name: 'Test',
|
|
172
|
+
integrations: [
|
|
173
|
+
{
|
|
174
|
+
type: 'header-user',
|
|
175
|
+
path: '/test',
|
|
176
|
+
jsName: 'test',
|
|
177
|
+
} as Integration,
|
|
178
|
+
],
|
|
179
|
+
} as AddOn,
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
'/test',
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
environment.startRun()
|
|
186
|
+
await templateFile(
|
|
187
|
+
'./test.txt.ejs',
|
|
188
|
+
"<%= integrations.map((integration) => integration.path).join(', ') %>",
|
|
189
|
+
)
|
|
190
|
+
environment.finishRun()
|
|
191
|
+
|
|
192
|
+
expect(output.files['/test/test.txt']).toEqual('/test')
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
it('should handle package manager', async () => {
|
|
196
|
+
const { environment, output } = createMemoryEnvironment()
|
|
197
|
+
const templateFile = createTemplateFile(environment, simpleOptions, '/test')
|
|
198
|
+
environment.startRun()
|
|
199
|
+
await templateFile(
|
|
200
|
+
'./foo.txt.ejs',
|
|
201
|
+
"<%= getPackageManagerAddScript('foo') %>",
|
|
202
|
+
)
|
|
203
|
+
await templateFile(
|
|
204
|
+
'./foo-dev.txt.ejs',
|
|
205
|
+
"<%= getPackageManagerAddScript('foo', true) %>",
|
|
206
|
+
)
|
|
207
|
+
await templateFile(
|
|
208
|
+
'./run-dev.txt.ejs',
|
|
209
|
+
"<%= getPackageManagerRunScript('dev') %>",
|
|
210
|
+
)
|
|
211
|
+
environment.finishRun()
|
|
212
|
+
|
|
213
|
+
expect(output.files['/test/foo.txt']).toEqual('pnpm add foo')
|
|
214
|
+
expect(output.files['/test/foo-dev.txt']).toEqual('pnpm add foo --dev')
|
|
215
|
+
expect(output.files['/test/run-dev.txt']).toEqual('pnpm dev')
|
|
216
|
+
})
|
|
217
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { formatCommand, jsSafeName, sortObject } from '../src/utils.js'
|
|
4
|
+
|
|
5
|
+
describe('formatCommand', () => {
|
|
6
|
+
it('should format a command', () => {
|
|
7
|
+
expect(formatCommand({ command: 'echo', args: ['test'] })).toEqual(
|
|
8
|
+
'echo test',
|
|
9
|
+
)
|
|
10
|
+
})
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
describe('jsSafeName', () => {
|
|
14
|
+
it('should convert a string to a safe JS name', () => {
|
|
15
|
+
expect(jsSafeName('test.foo')).toEqual('TestFoo')
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
describe('sortObject', () => {
|
|
20
|
+
it('should sort an object', () => {
|
|
21
|
+
expect(sortObject({ b: 'b', a: 'a' })).toEqual({ a: 'a', b: 'b' })
|
|
22
|
+
})
|
|
23
|
+
})
|