create-alistt69-kit 0.1.9 → 0.1.12
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/LICENSE +20 -20
- package/README.md +123 -122
- package/bin/index.js +24 -24
- package/package.json +44 -44
- package/src/core/apply-features.js +14 -14
- package/src/core/collect-project-info.js +170 -194
- package/src/core/copy-base-template.js +11 -11
- package/src/core/create-project.js +99 -98
- package/src/core/install-dependencies.js +27 -27
- package/src/core/parse-cli-args.js +122 -122
- package/src/core/prepare-target-directory.js +69 -69
- package/src/core/render-project-readme.js +188 -75
- package/src/core/replace-tokens.js +45 -45
- package/src/core/restore-special-files.js +18 -18
- package/src/features/autoprefixer/files/postcss.config.cjs +4 -4
- package/src/features/autoprefixer/index.js +19 -23
- package/src/features/define-feature.js +33 -0
- package/src/features/eslint/files/eslint.config.mjs +133 -133
- package/src/features/eslint/index.js +30 -35
- package/src/features/index.js +25 -15
- package/src/features/react-router/files/src/app/App.tsx +20 -20
- package/src/features/react-router/files/src/app/layouts/app/index.tsx +36 -36
- package/src/features/react-router/files/src/app/providers/router/config/router.tsx +13 -13
- package/src/features/react-router/files/src/pages/error/index.ts +1 -1
- package/src/features/react-router/files/src/pages/error/lazy.ts +3 -3
- package/src/features/react-router/files/src/pages/error/page.tsx +7 -7
- package/src/features/react-router/files/src/pages/main/index.ts +1 -1
- package/src/features/react-router/files/src/pages/main/lazy.ts +3 -3
- package/src/features/react-router/files/src/pages/main/page.tsx +7 -7
- package/src/features/react-router/index.js +19 -23
- package/src/features/stylelint/files/stylelint.config.mjs +13 -13
- package/src/features/stylelint/index.js +24 -29
- package/src/templates/base/.editorconfig +11 -11
- package/src/templates/base/README.md +2 -2
- package/src/templates/base/babel.config.json +12 -12
- package/src/templates/base/gitignore +27 -27
- package/src/templates/base/package.json +48 -48
- package/src/templates/base/public/index.html +12 -12
- package/src/templates/base/src/app/App.tsx +17 -17
- package/src/templates/base/src/index.tsx +16 -16
- package/src/templates/base/src/styles/index.scss +13 -13
- package/src/templates/base/tsconfig.json +25 -25
- package/src/utils/console-format.js +11 -11
- package/src/utils/package-json.js +96 -72
- package/src/utils/package-manager.js +22 -22
|
@@ -1,123 +1,123 @@
|
|
|
1
|
-
import { allowedPackageManagers } from '../utils/package-manager.js';
|
|
2
|
-
|
|
3
|
-
function readOptionValue(args, currentIndex, optionName) {
|
|
4
|
-
const currentArg = args[currentIndex];
|
|
5
|
-
|
|
6
|
-
if (currentArg.includes('=')) {
|
|
7
|
-
return {
|
|
8
|
-
value: currentArg.slice(currentArg.indexOf('=') + 1),
|
|
9
|
-
nextIndex: currentIndex,
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const nextArg = args[currentIndex + 1];
|
|
14
|
-
|
|
15
|
-
if (!nextArg || nextArg.startsWith('-')) {
|
|
16
|
-
throw new Error(`Option ${optionName} requires a value`);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return {
|
|
20
|
-
value: nextArg,
|
|
21
|
-
nextIndex: currentIndex + 1,
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function formatHelpMessage() {
|
|
26
|
-
return [
|
|
27
|
-
'Usage:',
|
|
28
|
-
' create-alistt69-kit <project-name> [options]',
|
|
29
|
-
'',
|
|
30
|
-
'Options:',
|
|
31
|
-
' -def, --defaults Skip prompts and use defaults',
|
|
32
|
-
' --overwrite Overwrite target directory if it exists',
|
|
33
|
-
' --no-install Do not install dependencies',
|
|
34
|
-
' --features <comma-list> Example: eslint,stylelint,react-router',
|
|
35
|
-
' --features all Enable all features',
|
|
36
|
-
' --pm <npm|pnpm|yarn> Package manager',
|
|
37
|
-
' -h, --help Show help',
|
|
38
|
-
'',
|
|
39
|
-
'Defaults:',
|
|
40
|
-
' All features are enabled by default',
|
|
41
|
-
' Package manager: npm',
|
|
42
|
-
' Install dependencies: yes',
|
|
43
|
-
'',
|
|
44
|
-
'Examples:',
|
|
45
|
-
' create-alistt69-kit my-app',
|
|
46
|
-
' create-alistt69-kit my-app --features=all',
|
|
47
|
-
' create-alistt69-kit my-app --pm pnpm --no-install',
|
|
48
|
-
' create-alistt69-kit my-app --defaults',
|
|
49
|
-
' create-alistt69-kit my-app --defaults --overwrite',
|
|
50
|
-
].join('\n');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function parseCliArgs(argv) {
|
|
54
|
-
const result = {
|
|
55
|
-
projectName: undefined,
|
|
56
|
-
selectedFeatureIds: undefined,
|
|
57
|
-
shouldInstallDependencies: undefined,
|
|
58
|
-
packageManager: undefined,
|
|
59
|
-
defaults: false,
|
|
60
|
-
overwrite: false,
|
|
61
|
-
showHelp: false,
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
65
|
-
const arg = argv[index];
|
|
66
|
-
|
|
67
|
-
if (arg === '-h' || arg === '--help') {
|
|
68
|
-
result.showHelp = true;
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (arg === '-def' || arg === '--defaults') {
|
|
73
|
-
result.defaults = true;
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (arg === '--overwrite') {
|
|
78
|
-
result.overwrite = true;
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (arg === '--no-install') {
|
|
83
|
-
result.shouldInstallDependencies = false;
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (arg.startsWith('--features')) {
|
|
88
|
-
const { value, nextIndex } = readOptionValue(argv, index, '--features');
|
|
89
|
-
index = nextIndex;
|
|
90
|
-
|
|
91
|
-
result.selectedFeatureIds = value
|
|
92
|
-
? value.split(',').map((item) => item.trim()).filter(Boolean)
|
|
93
|
-
: [];
|
|
94
|
-
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (arg.startsWith('--pm')) {
|
|
99
|
-
const { value, nextIndex } = readOptionValue(argv, index, '--pm');
|
|
100
|
-
index = nextIndex;
|
|
101
|
-
|
|
102
|
-
if (!allowedPackageManagers.includes(value)) {
|
|
103
|
-
throw new Error(`Unknown package manager: ${value}`);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
result.packageManager = value;
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (arg.startsWith('-')) {
|
|
111
|
-
throw new Error(`Unknown option: ${arg}`);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (!result.projectName) {
|
|
115
|
-
result.projectName = arg;
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
throw new Error(`Unexpected argument: ${arg}`);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return result;
|
|
1
|
+
import { allowedPackageManagers } from '../utils/package-manager.js';
|
|
2
|
+
|
|
3
|
+
function readOptionValue(args, currentIndex, optionName) {
|
|
4
|
+
const currentArg = args[currentIndex];
|
|
5
|
+
|
|
6
|
+
if (currentArg.includes('=')) {
|
|
7
|
+
return {
|
|
8
|
+
value: currentArg.slice(currentArg.indexOf('=') + 1),
|
|
9
|
+
nextIndex: currentIndex,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const nextArg = args[currentIndex + 1];
|
|
14
|
+
|
|
15
|
+
if (!nextArg || nextArg.startsWith('-')) {
|
|
16
|
+
throw new Error(`Option ${optionName} requires a value`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
value: nextArg,
|
|
21
|
+
nextIndex: currentIndex + 1,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function formatHelpMessage() {
|
|
26
|
+
return [
|
|
27
|
+
'Usage:',
|
|
28
|
+
' create-alistt69-kit <project-name> [options]',
|
|
29
|
+
'',
|
|
30
|
+
'Options:',
|
|
31
|
+
' -def, --defaults Skip prompts and use defaults',
|
|
32
|
+
' --overwrite Overwrite target directory if it exists',
|
|
33
|
+
' --no-install Do not install dependencies',
|
|
34
|
+
' --features <comma-list> Example: eslint,stylelint,react-router',
|
|
35
|
+
' --features all Enable all features',
|
|
36
|
+
' --pm <npm|pnpm|yarn> Package manager',
|
|
37
|
+
' -h, --help Show help',
|
|
38
|
+
'',
|
|
39
|
+
'Defaults:',
|
|
40
|
+
' All features are enabled by default',
|
|
41
|
+
' Package manager: npm',
|
|
42
|
+
' Install dependencies: yes',
|
|
43
|
+
'',
|
|
44
|
+
'Examples:',
|
|
45
|
+
' create-alistt69-kit my-app',
|
|
46
|
+
' create-alistt69-kit my-app --features=all',
|
|
47
|
+
' create-alistt69-kit my-app --pm pnpm --no-install',
|
|
48
|
+
' create-alistt69-kit my-app --defaults',
|
|
49
|
+
' create-alistt69-kit my-app --defaults --overwrite',
|
|
50
|
+
].join('\n');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function parseCliArgs(argv) {
|
|
54
|
+
const result = {
|
|
55
|
+
projectName: undefined,
|
|
56
|
+
selectedFeatureIds: undefined,
|
|
57
|
+
shouldInstallDependencies: undefined,
|
|
58
|
+
packageManager: undefined,
|
|
59
|
+
defaults: false,
|
|
60
|
+
overwrite: false,
|
|
61
|
+
showHelp: false,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
65
|
+
const arg = argv[index];
|
|
66
|
+
|
|
67
|
+
if (arg === '-h' || arg === '--help') {
|
|
68
|
+
result.showHelp = true;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (arg === '-def' || arg === '--defaults') {
|
|
73
|
+
result.defaults = true;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (arg === '--overwrite') {
|
|
78
|
+
result.overwrite = true;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (arg === '--no-install') {
|
|
83
|
+
result.shouldInstallDependencies = false;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (arg.startsWith('--features')) {
|
|
88
|
+
const { value, nextIndex } = readOptionValue(argv, index, '--features');
|
|
89
|
+
index = nextIndex;
|
|
90
|
+
|
|
91
|
+
result.selectedFeatureIds = value
|
|
92
|
+
? value.split(',').map((item) => item.trim()).filter(Boolean)
|
|
93
|
+
: [];
|
|
94
|
+
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (arg.startsWith('--pm')) {
|
|
99
|
+
const { value, nextIndex } = readOptionValue(argv, index, '--pm');
|
|
100
|
+
index = nextIndex;
|
|
101
|
+
|
|
102
|
+
if (!allowedPackageManagers.includes(value)) {
|
|
103
|
+
throw new Error(`Unknown package manager: ${value}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
result.packageManager = value;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (arg.startsWith('-')) {
|
|
111
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!result.projectName) {
|
|
115
|
+
result.projectName = arg;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
throw new Error(`Unexpected argument: ${arg}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return result;
|
|
123
123
|
}
|
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
import { confirm, isCancel } from '@clack/prompts';
|
|
2
|
-
import { access, readdir, rm } from 'node:fs/promises';
|
|
3
|
-
import process from 'node:process';
|
|
4
|
-
import { resolve } from 'node:path';
|
|
5
|
-
|
|
6
|
-
async function pathExists(targetDirPath) {
|
|
7
|
-
try {
|
|
8
|
-
await access(targetDirPath);
|
|
9
|
-
return true;
|
|
10
|
-
} catch {
|
|
11
|
-
return false;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export async function prepareTargetDirectory({
|
|
16
|
-
projectName,
|
|
17
|
-
overwrite = false,
|
|
18
|
-
defaults = false,
|
|
19
|
-
}) {
|
|
20
|
-
const targetDirPath = resolve(process.cwd(), projectName);
|
|
21
|
-
const exists = await pathExists(targetDirPath);
|
|
22
|
-
|
|
23
|
-
if (!exists) {
|
|
24
|
-
return {
|
|
25
|
-
targetDirPath,
|
|
26
|
-
wasOverwritten: false,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const directoryEntries = await readdir(targetDirPath);
|
|
31
|
-
const isEmpty = directoryEntries.length === 0;
|
|
32
|
-
|
|
33
|
-
if (isEmpty) {
|
|
34
|
-
return {
|
|
35
|
-
targetDirPath,
|
|
36
|
-
wasOverwritten: false,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (overwrite) {
|
|
41
|
-
await rm(targetDirPath, { recursive: true, overwrite: true });
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
targetDirPath,
|
|
45
|
-
wasOverwritten: true,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (defaults) {
|
|
50
|
-
throw new Error(
|
|
51
|
-
`Directory already exists and is not empty: ${targetDirPath}. Use --overwrite to overwrite it.`,
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const overwriteAnswer = await confirm({
|
|
56
|
-
message: `Directory "${projectName}" already exists and will be overwritten. Continue?`,
|
|
57
|
-
initialValue: false,
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
if (isCancel(overwriteAnswer) || !overwriteAnswer) {
|
|
61
|
-
process.exit(0);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
await rm(targetDirPath, { recursive: true, overwrite: true });
|
|
65
|
-
|
|
66
|
-
return {
|
|
67
|
-
targetDirPath,
|
|
68
|
-
wasOverwritten: true,
|
|
69
|
-
};
|
|
1
|
+
import { confirm, isCancel } from '@clack/prompts';
|
|
2
|
+
import { access, readdir, rm } from 'node:fs/promises';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { resolve } from 'node:path';
|
|
5
|
+
|
|
6
|
+
async function pathExists(targetDirPath) {
|
|
7
|
+
try {
|
|
8
|
+
await access(targetDirPath);
|
|
9
|
+
return true;
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function prepareTargetDirectory({
|
|
16
|
+
projectName,
|
|
17
|
+
overwrite = false,
|
|
18
|
+
defaults = false,
|
|
19
|
+
}) {
|
|
20
|
+
const targetDirPath = resolve(process.cwd(), projectName);
|
|
21
|
+
const exists = await pathExists(targetDirPath);
|
|
22
|
+
|
|
23
|
+
if (!exists) {
|
|
24
|
+
return {
|
|
25
|
+
targetDirPath,
|
|
26
|
+
wasOverwritten: false,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const directoryEntries = await readdir(targetDirPath);
|
|
31
|
+
const isEmpty = directoryEntries.length === 0;
|
|
32
|
+
|
|
33
|
+
if (isEmpty) {
|
|
34
|
+
return {
|
|
35
|
+
targetDirPath,
|
|
36
|
+
wasOverwritten: false,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (overwrite) {
|
|
41
|
+
await rm(targetDirPath, { recursive: true, overwrite: true });
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
targetDirPath,
|
|
45
|
+
wasOverwritten: true,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (defaults) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`Directory already exists and is not empty: ${targetDirPath}. Use --overwrite to overwrite it.`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const overwriteAnswer = await confirm({
|
|
56
|
+
message: `Directory "${projectName}" already exists and will be overwritten. Continue?`,
|
|
57
|
+
initialValue: false,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (isCancel(overwriteAnswer) || !overwriteAnswer) {
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
await rm(targetDirPath, { recursive: true, overwrite: true });
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
targetDirPath,
|
|
68
|
+
wasOverwritten: true,
|
|
69
|
+
};
|
|
70
70
|
}
|
|
@@ -1,76 +1,189 @@
|
|
|
1
|
-
import { writeFile } from 'node:fs/promises';
|
|
2
|
-
import { resolve } from 'node:path';
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
1
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { featuresById } from '../features/index.js';
|
|
5
|
+
import { readPackageJson } from '../utils/package-json.js';
|
|
6
|
+
import { getRunScriptCommand } from '../utils/package-manager.js';
|
|
7
|
+
|
|
8
|
+
const defaultTooling = [
|
|
9
|
+
{
|
|
10
|
+
title: 'React',
|
|
11
|
+
docs: 'https://react.dev/',
|
|
12
|
+
description: 'UI library',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
title: 'TypeScript',
|
|
16
|
+
docs: 'https://www.typescriptlang.org/',
|
|
17
|
+
description: 'Static typing',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
title: 'Webpack',
|
|
21
|
+
docs: 'https://webpack.js.org/',
|
|
22
|
+
description: 'Bundling and build pipeline',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
title: 'SCSS Modules',
|
|
26
|
+
docs: 'https://github.com/css-modules/css-modules',
|
|
27
|
+
description: 'Scoped styling',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
title: 'SVGR',
|
|
31
|
+
docs: 'https://react-svgr.com/',
|
|
32
|
+
description: 'SVGs as React components',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
title: 'Webpack Bundle Analyzer',
|
|
36
|
+
docs: 'https://github.com/webpack-contrib/webpack-bundle-analyzer',
|
|
37
|
+
description: 'Bundle size inspection',
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const scriptDescriptions = {
|
|
42
|
+
dev: 'Start development server',
|
|
43
|
+
start: 'Start development server',
|
|
44
|
+
build: 'Build for production',
|
|
45
|
+
'build:dev': 'Build in development mode',
|
|
46
|
+
'build:prod': 'Build in production mode',
|
|
47
|
+
typecheck: 'Run TypeScript type check',
|
|
48
|
+
lint: 'Run ESLint',
|
|
49
|
+
'lint:fix': 'Run ESLint with autofix',
|
|
50
|
+
'lint:styles': 'Run Stylelint',
|
|
51
|
+
'lint:styles:fix': 'Run Stylelint with autofix',
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
function formatMarkdownList(items, fallback = '- None') {
|
|
55
|
+
if (items.length === 0) {
|
|
56
|
+
return fallback;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return items.join('\n');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function formatDefaultTooling() {
|
|
63
|
+
return formatMarkdownList(
|
|
64
|
+
defaultTooling.map(({ title, docs, description }) => (
|
|
65
|
+
`- [${title}](${docs}) — ${description}`
|
|
66
|
+
)),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function formatFeatureList(selectedFeatureIds) {
|
|
71
|
+
if (selectedFeatureIds.length === 0) {
|
|
72
|
+
return '- None';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return selectedFeatureIds
|
|
76
|
+
.map((featureId) => {
|
|
77
|
+
const feature = featuresById.get(featureId);
|
|
78
|
+
|
|
79
|
+
if (!feature) {
|
|
80
|
+
return `- ${featureId}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return feature.hint
|
|
84
|
+
? `- ${feature.title} — ${feature.hint}`
|
|
85
|
+
: `- ${feature.title}`;
|
|
86
|
+
})
|
|
87
|
+
.join('\n');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function formatScripts(packageManager, scripts) {
|
|
91
|
+
const entries = Object.entries(scripts);
|
|
92
|
+
|
|
93
|
+
if (entries.length === 0) {
|
|
94
|
+
return '_No scripts available._';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return entries
|
|
98
|
+
.map(([scriptName]) => {
|
|
99
|
+
const command = getRunScriptCommand(packageManager, scriptName);
|
|
100
|
+
const description = scriptDescriptions[scriptName] ?? 'Project script';
|
|
101
|
+
|
|
102
|
+
return `- \`${command}\` — ${description}`;
|
|
103
|
+
})
|
|
104
|
+
.join('\n');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function formatQuickStart({
|
|
108
|
+
projectName,
|
|
109
|
+
packageManager,
|
|
110
|
+
shouldInstallDependencies,
|
|
111
|
+
}) {
|
|
112
|
+
const steps = [`cd ${projectName}`];
|
|
113
|
+
|
|
114
|
+
if (!shouldInstallDependencies) {
|
|
115
|
+
steps.push(packageManager === 'yarn' ? 'yarn' : `${packageManager} install`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
steps.push(getRunScriptCommand(packageManager, 'start'));
|
|
119
|
+
|
|
120
|
+
return [
|
|
121
|
+
shouldInstallDependencies
|
|
122
|
+
? 'Dependencies are already installed.'
|
|
123
|
+
: 'Install dependencies first, then start the development server.',
|
|
124
|
+
'',
|
|
125
|
+
'```bash',
|
|
126
|
+
...steps,
|
|
127
|
+
'```',
|
|
128
|
+
].join('\n');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function formatProjectStructure() {
|
|
132
|
+
return [
|
|
133
|
+
'- `public/` — static assets and HTML template',
|
|
134
|
+
'- `src/` — application source code',
|
|
135
|
+
'- `src/app/` — app bootstrap, providers, entry-level setup',
|
|
136
|
+
'- `src/styles/` — global styles and shared styling layer',
|
|
137
|
+
'- `config/build/` — split webpack configuration',
|
|
138
|
+
].join('\n');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function renderProjectReadme({
|
|
142
|
+
projectPath,
|
|
143
|
+
projectName,
|
|
144
|
+
selectedFeatureIds,
|
|
145
|
+
packageManager,
|
|
146
|
+
shouldInstallDependencies,
|
|
147
|
+
}) {
|
|
148
|
+
const packageJson = await readPackageJson(projectPath);
|
|
149
|
+
|
|
150
|
+
const readmeContent = [
|
|
151
|
+
`# ${projectName}`,
|
|
152
|
+
'',
|
|
153
|
+
'Created with `create-alistt69-kit`.',
|
|
154
|
+
'',
|
|
155
|
+
'## Overview',
|
|
156
|
+
'',
|
|
157
|
+
'Starter project based on React + TypeScript + Webpack with optional tooling selected during scaffolding.',
|
|
158
|
+
'',
|
|
159
|
+
'## Included by default',
|
|
160
|
+
'',
|
|
161
|
+
formatDefaultTooling(),
|
|
162
|
+
'',
|
|
163
|
+
'## Selected optional features',
|
|
164
|
+
'',
|
|
165
|
+
formatFeatureList(selectedFeatureIds),
|
|
166
|
+
'',
|
|
167
|
+
'## Quick start',
|
|
168
|
+
'',
|
|
169
|
+
formatQuickStart({
|
|
170
|
+
projectName,
|
|
171
|
+
packageManager,
|
|
172
|
+
shouldInstallDependencies,
|
|
173
|
+
}),
|
|
174
|
+
'',
|
|
175
|
+
'## Project structure',
|
|
176
|
+
'',
|
|
177
|
+
formatProjectStructure(),
|
|
178
|
+
'',
|
|
179
|
+
'## Available scripts',
|
|
180
|
+
'',
|
|
181
|
+
formatScripts(packageManager, packageJson.scripts ?? {}),
|
|
182
|
+
].join('\n');
|
|
183
|
+
|
|
184
|
+
await writeFile(
|
|
185
|
+
resolve(projectPath, 'README.md'),
|
|
186
|
+
`${readmeContent}\n`,
|
|
187
|
+
'utf8',
|
|
188
|
+
);
|
|
76
189
|
}
|