create-alistt69-kit 0.3.6 → 0.3.7
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 +189 -189
- 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 -170
- package/src/core/copy-base-template.js +11 -11
- package/src/core/create-project.js +99 -99
- 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 +278 -278
- package/src/core/replace-tokens.js +45 -45
- package/src/core/restore-special-files.js +18 -18
- package/src/features/agents-md/files/AGENTS.md +36 -36
- package/src/features/agents-md/index.js +13 -13
- package/src/features/autoprefixer/files/postcss.config.cjs +4 -4
- package/src/features/autoprefixer/index.js +31 -31
- package/src/features/define-feature.js +32 -32
- package/src/features/eslint/files/eslint.config.mjs +135 -135
- package/src/features/eslint/index.js +43 -43
- package/src/features/index.js +28 -28
- package/src/features/prerender/files/prerender.routes.mjs +5 -5
- package/src/features/prerender/files/scripts/prerender.mjs +114 -114
- package/src/features/prerender/index.js +40 -40
- package/src/features/react-router/files/scripts/generate/page.mjs +366 -366
- package/src/features/react-router/files/src/app/App.tsx +18 -18
- package/src/features/react-router/files/src/app/providers/error-boundary/lib/provider/index.tsx +44 -44
- package/src/features/react-router/files/src/app/providers/error-boundary/ui/error-screen/index.tsx +151 -151
- package/src/features/react-router/files/src/app/providers/index.ts +17 -17
- package/src/features/react-router/files/src/app/providers/router/lib/provider/index.tsx +21 -21
- package/src/features/react-router/files/src/app/providers/router/model/router/index.tsx +24 -24
- package/src/features/react-router/files/src/app/providers/router/types/index.ts +10 -10
- package/src/features/react-router/files/src/app/providers/router/ui/app/index.tsx +36 -36
- package/src/features/react-router/files/src/index.tsx +23 -23
- 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 +36 -36
- package/src/features/stylelint/files/stylelint.config.mjs +13 -13
- package/src/features/stylelint/index.js +37 -37
- 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 +12 -12
- package/src/templates/base/src/app/providers/error-boundary/lib/provider/index.tsx +44 -44
- package/src/templates/base/src/app/providers/error-boundary/ui/error-screen/index.tsx +150 -150
- package/src/templates/base/src/app/providers/index.ts +5 -5
- package/src/templates/base/src/index.tsx +19 -19
- package/src/templates/base/src/styles/index.scss +13 -13
- package/src/templates/base/src/styles/normalize.scss +36 -36
- package/src/templates/base/tsconfig.json +25 -25
- package/src/utils/agents-md.js +52 -52
- package/src/utils/console-format.js +11 -11
- package/src/utils/package-json.js +96 -96
- package/src/utils/package-manager.js +22 -22
|
@@ -1,279 +1,279 @@
|
|
|
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
|
-
'generate:page': 'Generate a new page and auto-register it in the router when available',
|
|
53
|
-
'build:assets': 'Build production assets without prerender',
|
|
54
|
-
prerender: 'Prerender configured routes into static HTML',
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
function formatMarkdownList(items, fallback = '- None') {
|
|
58
|
-
if (items.length === 0) {
|
|
59
|
-
return fallback;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return items.join('\n');
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function formatDefaultTooling() {
|
|
66
|
-
return formatMarkdownList(
|
|
67
|
-
defaultTooling.map(({ title, docs, description }) => (
|
|
68
|
-
`- [${title}](${docs}) — ${description}`
|
|
69
|
-
)),
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function formatFeatureList(selectedFeatureIds) {
|
|
74
|
-
if (selectedFeatureIds.length === 0) {
|
|
75
|
-
return '- None';
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return selectedFeatureIds
|
|
79
|
-
.map((featureId) => {
|
|
80
|
-
const feature = featuresById.get(featureId);
|
|
81
|
-
|
|
82
|
-
if (!feature) {
|
|
83
|
-
return `- ${featureId}`;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return feature.hint
|
|
87
|
-
? `- ${feature.title} — ${feature.hint}`
|
|
88
|
-
: `- ${feature.title}`;
|
|
89
|
-
})
|
|
90
|
-
.join('\n');
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function formatScripts(packageManager, scripts) {
|
|
94
|
-
const entries = Object.entries(scripts);
|
|
95
|
-
|
|
96
|
-
if (entries.length === 0) {
|
|
97
|
-
return '_No scripts available._';
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return entries
|
|
101
|
-
.map(([scriptName]) => {
|
|
102
|
-
const command = getRunScriptCommand(packageManager, scriptName);
|
|
103
|
-
const description = scriptDescriptions[scriptName] ?? 'Project script';
|
|
104
|
-
|
|
105
|
-
return `- \`${command}\` — ${description}`;
|
|
106
|
-
})
|
|
107
|
-
.join('\n');
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function formatQuickStart({
|
|
111
|
-
projectName,
|
|
112
|
-
packageManager,
|
|
113
|
-
shouldInstallDependencies,
|
|
114
|
-
}) {
|
|
115
|
-
const steps = [`cd ${projectName}`];
|
|
116
|
-
|
|
117
|
-
if (!shouldInstallDependencies) {
|
|
118
|
-
steps.push(packageManager === 'yarn' ? 'yarn' : `${packageManager} install`);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
steps.push(getRunScriptCommand(packageManager, 'start'));
|
|
122
|
-
|
|
123
|
-
return [
|
|
124
|
-
shouldInstallDependencies
|
|
125
|
-
? 'Dependencies are already installed.'
|
|
126
|
-
: 'Install dependencies first, then start the development server.',
|
|
127
|
-
'',
|
|
128
|
-
'```bash',
|
|
129
|
-
...steps,
|
|
130
|
-
'```',
|
|
131
|
-
].join('\n');
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function formatProjectStructure(selectedFeatureIds) {
|
|
135
|
-
const items = [
|
|
136
|
-
'- `public/` — static assets and HTML template',
|
|
137
|
-
'- `src/` — application source code',
|
|
138
|
-
'- `src/app/` — app bootstrap, providers, entry-level setup',
|
|
139
|
-
'- `src/styles/` — global styles and shared styling layer',
|
|
140
|
-
'- `src/widgets/` — app widgets',
|
|
141
|
-
'- `config/build/` — split webpack configuration',
|
|
142
|
-
];
|
|
143
|
-
|
|
144
|
-
if (selectedFeatureIds.includes('react-router')) {
|
|
145
|
-
items.splice(3, 0, '- `src/pages/` — route-level pages');
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return items.join('\n');
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function formatFeatureSpecificSections({ selectedFeatureIds, packageManager }) {
|
|
152
|
-
const sections = [];
|
|
153
|
-
|
|
154
|
-
if (selectedFeatureIds.includes('agents-md')) {
|
|
155
|
-
sections.push([
|
|
156
|
-
'## AGENTS.md',
|
|
157
|
-
'',
|
|
158
|
-
'When `AGENTS.md` is enabled, the project includes a root instruction file for AI coding agents.',
|
|
159
|
-
'',
|
|
160
|
-
'- Base file: `AGENTS.md`',
|
|
161
|
-
'- Other selected features may append their own sections into this file automatically during scaffolding.',
|
|
162
|
-
].join('\n'));
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (selectedFeatureIds.includes('react-router')) {
|
|
166
|
-
sections.push([
|
|
167
|
-
'## Page generator',
|
|
168
|
-
'',
|
|
169
|
-
'When `React Router DOM` is enabled, you can scaffold a new page with one command.',
|
|
170
|
-
'',
|
|
171
|
-
'```bash',
|
|
172
|
-
`${getRunScriptCommand(packageManager, 'generate:page')} about`,
|
|
173
|
-
'```',
|
|
174
|
-
'',
|
|
175
|
-
'This creates:',
|
|
176
|
-
'',
|
|
177
|
-
'- `src/pages/about/index.ts`',
|
|
178
|
-
'- `src/pages/about/lazy.ts`',
|
|
179
|
-
'- `src/pages/about/page.tsx`',
|
|
180
|
-
'',
|
|
181
|
-
'If the standard router files are still present, the generator also updates:',
|
|
182
|
-
'',
|
|
183
|
-
'- `src/app/providers/router/types/index.ts`',
|
|
184
|
-
'- `src/app/providers/router/model/config/index.ts`',
|
|
185
|
-
'- `src/app/providers/router/model/router/index.tsx`',
|
|
186
|
-
].join('\n'));
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (selectedFeatureIds.includes('prerender')) {
|
|
190
|
-
sections.push([
|
|
191
|
-
'## Prerender',
|
|
192
|
-
'',
|
|
193
|
-
'When `Prerender` is enabled, production build runs a postbuild Puppeteer prerender step.',
|
|
194
|
-
'',
|
|
195
|
-
'Configured routes live in:',
|
|
196
|
-
'',
|
|
197
|
-
'- `prerender.routes.mjs`',
|
|
198
|
-
'- `scripts/prerender.mjs`',
|
|
199
|
-
'',
|
|
200
|
-
'Example route config:',
|
|
201
|
-
'',
|
|
202
|
-
'```js',
|
|
203
|
-
'export default async function getPrerenderRoutes() {',
|
|
204
|
-
' return [',
|
|
205
|
-
" '/',",
|
|
206
|
-
" '/about',",
|
|
207
|
-
' ];',
|
|
208
|
-
'}',
|
|
209
|
-
'```',
|
|
210
|
-
'',
|
|
211
|
-
'Useful commands:',
|
|
212
|
-
'',
|
|
213
|
-
'```bash',
|
|
214
|
-
`${getRunScriptCommand(packageManager, 'build:assets')}`,
|
|
215
|
-
`${getRunScriptCommand(packageManager, 'prerender')}`,
|
|
216
|
-
`${getRunScriptCommand(packageManager, 'build')}`,
|
|
217
|
-
'```',
|
|
218
|
-
'',
|
|
219
|
-
'If `React Router DOM` page generator is also enabled, new pages are additionally appended to `prerender.routes.mjs` automatically when that file exists.',
|
|
220
|
-
].join('\n'));
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return sections.join('\n\n');
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
export async function renderProjectReadme({
|
|
227
|
-
projectPath,
|
|
228
|
-
projectName,
|
|
229
|
-
selectedFeatureIds,
|
|
230
|
-
packageManager,
|
|
231
|
-
shouldInstallDependencies,
|
|
232
|
-
}) {
|
|
233
|
-
const packageJson = await readPackageJson(projectPath);
|
|
234
|
-
const featureSpecificSections = formatFeatureSpecificSections({
|
|
235
|
-
selectedFeatureIds,
|
|
236
|
-
packageManager,
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
const readmeContent = [
|
|
240
|
-
`# ${projectName}`,
|
|
241
|
-
'',
|
|
242
|
-
'Created with `create-alistt69-kit`.',
|
|
243
|
-
'',
|
|
244
|
-
'## Overview',
|
|
245
|
-
'',
|
|
246
|
-
'Starter project based on React + TypeScript + Webpack with optional tooling selected during scaffolding.',
|
|
247
|
-
'',
|
|
248
|
-
'## Included by default',
|
|
249
|
-
'',
|
|
250
|
-
formatDefaultTooling(),
|
|
251
|
-
'',
|
|
252
|
-
'## Selected optional features',
|
|
253
|
-
'',
|
|
254
|
-
formatFeatureList(selectedFeatureIds),
|
|
255
|
-
'',
|
|
256
|
-
'## Quick start',
|
|
257
|
-
'',
|
|
258
|
-
formatQuickStart({
|
|
259
|
-
projectName,
|
|
260
|
-
packageManager,
|
|
261
|
-
shouldInstallDependencies,
|
|
262
|
-
}),
|
|
263
|
-
'',
|
|
264
|
-
'## Project structure',
|
|
265
|
-
'',
|
|
266
|
-
formatProjectStructure(selectedFeatureIds),
|
|
267
|
-
'',
|
|
268
|
-
'## Available scripts',
|
|
269
|
-
'',
|
|
270
|
-
formatScripts(packageManager, packageJson.scripts ?? {}),
|
|
271
|
-
...(featureSpecificSections ? ['', featureSpecificSections] : []),
|
|
272
|
-
].join('\n');
|
|
273
|
-
|
|
274
|
-
await writeFile(
|
|
275
|
-
resolve(projectPath, 'README.md'),
|
|
276
|
-
`${readmeContent}\n`,
|
|
277
|
-
'utf8',
|
|
278
|
-
);
|
|
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
|
+
'generate:page': 'Generate a new page and auto-register it in the router when available',
|
|
53
|
+
'build:assets': 'Build production assets without prerender',
|
|
54
|
+
prerender: 'Prerender configured routes into static HTML',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function formatMarkdownList(items, fallback = '- None') {
|
|
58
|
+
if (items.length === 0) {
|
|
59
|
+
return fallback;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return items.join('\n');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function formatDefaultTooling() {
|
|
66
|
+
return formatMarkdownList(
|
|
67
|
+
defaultTooling.map(({ title, docs, description }) => (
|
|
68
|
+
`- [${title}](${docs}) — ${description}`
|
|
69
|
+
)),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function formatFeatureList(selectedFeatureIds) {
|
|
74
|
+
if (selectedFeatureIds.length === 0) {
|
|
75
|
+
return '- None';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return selectedFeatureIds
|
|
79
|
+
.map((featureId) => {
|
|
80
|
+
const feature = featuresById.get(featureId);
|
|
81
|
+
|
|
82
|
+
if (!feature) {
|
|
83
|
+
return `- ${featureId}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return feature.hint
|
|
87
|
+
? `- ${feature.title} — ${feature.hint}`
|
|
88
|
+
: `- ${feature.title}`;
|
|
89
|
+
})
|
|
90
|
+
.join('\n');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function formatScripts(packageManager, scripts) {
|
|
94
|
+
const entries = Object.entries(scripts);
|
|
95
|
+
|
|
96
|
+
if (entries.length === 0) {
|
|
97
|
+
return '_No scripts available._';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return entries
|
|
101
|
+
.map(([scriptName]) => {
|
|
102
|
+
const command = getRunScriptCommand(packageManager, scriptName);
|
|
103
|
+
const description = scriptDescriptions[scriptName] ?? 'Project script';
|
|
104
|
+
|
|
105
|
+
return `- \`${command}\` — ${description}`;
|
|
106
|
+
})
|
|
107
|
+
.join('\n');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function formatQuickStart({
|
|
111
|
+
projectName,
|
|
112
|
+
packageManager,
|
|
113
|
+
shouldInstallDependencies,
|
|
114
|
+
}) {
|
|
115
|
+
const steps = [`cd ${projectName}`];
|
|
116
|
+
|
|
117
|
+
if (!shouldInstallDependencies) {
|
|
118
|
+
steps.push(packageManager === 'yarn' ? 'yarn' : `${packageManager} install`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
steps.push(getRunScriptCommand(packageManager, 'start'));
|
|
122
|
+
|
|
123
|
+
return [
|
|
124
|
+
shouldInstallDependencies
|
|
125
|
+
? 'Dependencies are already installed.'
|
|
126
|
+
: 'Install dependencies first, then start the development server.',
|
|
127
|
+
'',
|
|
128
|
+
'```bash',
|
|
129
|
+
...steps,
|
|
130
|
+
'```',
|
|
131
|
+
].join('\n');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function formatProjectStructure(selectedFeatureIds) {
|
|
135
|
+
const items = [
|
|
136
|
+
'- `public/` — static assets and HTML template',
|
|
137
|
+
'- `src/` — application source code',
|
|
138
|
+
'- `src/app/` — app bootstrap, providers, entry-level setup',
|
|
139
|
+
'- `src/styles/` — global styles and shared styling layer',
|
|
140
|
+
'- `src/widgets/` — app widgets',
|
|
141
|
+
'- `config/build/` — split webpack configuration',
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
if (selectedFeatureIds.includes('react-router')) {
|
|
145
|
+
items.splice(3, 0, '- `src/pages/` — route-level pages');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return items.join('\n');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function formatFeatureSpecificSections({ selectedFeatureIds, packageManager }) {
|
|
152
|
+
const sections = [];
|
|
153
|
+
|
|
154
|
+
if (selectedFeatureIds.includes('agents-md')) {
|
|
155
|
+
sections.push([
|
|
156
|
+
'## AGENTS.md',
|
|
157
|
+
'',
|
|
158
|
+
'When `AGENTS.md` is enabled, the project includes a root instruction file for AI coding agents.',
|
|
159
|
+
'',
|
|
160
|
+
'- Base file: `AGENTS.md`',
|
|
161
|
+
'- Other selected features may append their own sections into this file automatically during scaffolding.',
|
|
162
|
+
].join('\n'));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (selectedFeatureIds.includes('react-router')) {
|
|
166
|
+
sections.push([
|
|
167
|
+
'## Page generator',
|
|
168
|
+
'',
|
|
169
|
+
'When `React Router DOM` is enabled, you can scaffold a new page with one command.',
|
|
170
|
+
'',
|
|
171
|
+
'```bash',
|
|
172
|
+
`${getRunScriptCommand(packageManager, 'generate:page')} about`,
|
|
173
|
+
'```',
|
|
174
|
+
'',
|
|
175
|
+
'This creates:',
|
|
176
|
+
'',
|
|
177
|
+
'- `src/pages/about/index.ts`',
|
|
178
|
+
'- `src/pages/about/lazy.ts`',
|
|
179
|
+
'- `src/pages/about/page.tsx`',
|
|
180
|
+
'',
|
|
181
|
+
'If the standard router files are still present, the generator also updates:',
|
|
182
|
+
'',
|
|
183
|
+
'- `src/app/providers/router/types/index.ts`',
|
|
184
|
+
'- `src/app/providers/router/model/config/index.ts`',
|
|
185
|
+
'- `src/app/providers/router/model/router/index.tsx`',
|
|
186
|
+
].join('\n'));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (selectedFeatureIds.includes('prerender')) {
|
|
190
|
+
sections.push([
|
|
191
|
+
'## Prerender',
|
|
192
|
+
'',
|
|
193
|
+
'When `Prerender` is enabled, production build runs a postbuild Puppeteer prerender step.',
|
|
194
|
+
'',
|
|
195
|
+
'Configured routes live in:',
|
|
196
|
+
'',
|
|
197
|
+
'- `prerender.routes.mjs`',
|
|
198
|
+
'- `scripts/prerender.mjs`',
|
|
199
|
+
'',
|
|
200
|
+
'Example route config:',
|
|
201
|
+
'',
|
|
202
|
+
'```js',
|
|
203
|
+
'export default async function getPrerenderRoutes() {',
|
|
204
|
+
' return [',
|
|
205
|
+
" '/',",
|
|
206
|
+
" '/about',",
|
|
207
|
+
' ];',
|
|
208
|
+
'}',
|
|
209
|
+
'```',
|
|
210
|
+
'',
|
|
211
|
+
'Useful commands:',
|
|
212
|
+
'',
|
|
213
|
+
'```bash',
|
|
214
|
+
`${getRunScriptCommand(packageManager, 'build:assets')}`,
|
|
215
|
+
`${getRunScriptCommand(packageManager, 'prerender')}`,
|
|
216
|
+
`${getRunScriptCommand(packageManager, 'build')}`,
|
|
217
|
+
'```',
|
|
218
|
+
'',
|
|
219
|
+
'If `React Router DOM` page generator is also enabled, new pages are additionally appended to `prerender.routes.mjs` automatically when that file exists.',
|
|
220
|
+
].join('\n'));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return sections.join('\n\n');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export async function renderProjectReadme({
|
|
227
|
+
projectPath,
|
|
228
|
+
projectName,
|
|
229
|
+
selectedFeatureIds,
|
|
230
|
+
packageManager,
|
|
231
|
+
shouldInstallDependencies,
|
|
232
|
+
}) {
|
|
233
|
+
const packageJson = await readPackageJson(projectPath);
|
|
234
|
+
const featureSpecificSections = formatFeatureSpecificSections({
|
|
235
|
+
selectedFeatureIds,
|
|
236
|
+
packageManager,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const readmeContent = [
|
|
240
|
+
`# ${projectName}`,
|
|
241
|
+
'',
|
|
242
|
+
'Created with `create-alistt69-kit`.',
|
|
243
|
+
'',
|
|
244
|
+
'## Overview',
|
|
245
|
+
'',
|
|
246
|
+
'Starter project based on React + TypeScript + Webpack with optional tooling selected during scaffolding.',
|
|
247
|
+
'',
|
|
248
|
+
'## Included by default',
|
|
249
|
+
'',
|
|
250
|
+
formatDefaultTooling(),
|
|
251
|
+
'',
|
|
252
|
+
'## Selected optional features',
|
|
253
|
+
'',
|
|
254
|
+
formatFeatureList(selectedFeatureIds),
|
|
255
|
+
'',
|
|
256
|
+
'## Quick start',
|
|
257
|
+
'',
|
|
258
|
+
formatQuickStart({
|
|
259
|
+
projectName,
|
|
260
|
+
packageManager,
|
|
261
|
+
shouldInstallDependencies,
|
|
262
|
+
}),
|
|
263
|
+
'',
|
|
264
|
+
'## Project structure',
|
|
265
|
+
'',
|
|
266
|
+
formatProjectStructure(selectedFeatureIds),
|
|
267
|
+
'',
|
|
268
|
+
'## Available scripts',
|
|
269
|
+
'',
|
|
270
|
+
formatScripts(packageManager, packageJson.scripts ?? {}),
|
|
271
|
+
...(featureSpecificSections ? ['', featureSpecificSections] : []),
|
|
272
|
+
].join('\n');
|
|
273
|
+
|
|
274
|
+
await writeFile(
|
|
275
|
+
resolve(projectPath, 'README.md'),
|
|
276
|
+
`${readmeContent}\n`,
|
|
277
|
+
'utf8',
|
|
278
|
+
);
|
|
279
279
|
}
|
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
import { readFile, readdir, stat, writeFile } from 'node:fs/promises';
|
|
2
|
-
import { resolve } from 'node:path';
|
|
3
|
-
|
|
4
|
-
const replaceTokensInFile = async (filePath, tokens) => {
|
|
5
|
-
const fileContent = await readFile(filePath, 'utf8');
|
|
6
|
-
|
|
7
|
-
let updatedContent = fileContent;
|
|
8
|
-
|
|
9
|
-
for (const [token, value] of Object.entries(tokens)) {
|
|
10
|
-
updatedContent = updatedContent.replaceAll(token, value);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
await writeFile(filePath, updatedContent, 'utf8');
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const replaceTokensRecursively = async (targetPath, tokens) => {
|
|
17
|
-
const targetStat = await stat(targetPath);
|
|
18
|
-
|
|
19
|
-
if (targetStat.isFile()) {
|
|
20
|
-
await replaceTokensInFile(targetPath, tokens);
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (!targetStat.isDirectory()) {
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const entries = await readdir(targetPath, { withFileTypes: true });
|
|
29
|
-
|
|
30
|
-
for (const entry of entries) {
|
|
31
|
-
const entryPath = resolve(targetPath, entry.name);
|
|
32
|
-
|
|
33
|
-
if (entry.isDirectory()) {
|
|
34
|
-
await replaceTokensRecursively(entryPath, tokens);
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (entry.isFile()) {
|
|
39
|
-
await replaceTokensInFile(entryPath, tokens);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
export async function replaceTokens(targetDirPath, tokens) {
|
|
45
|
-
await replaceTokensRecursively(targetDirPath, tokens);
|
|
1
|
+
import { readFile, readdir, stat, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
const replaceTokensInFile = async (filePath, tokens) => {
|
|
5
|
+
const fileContent = await readFile(filePath, 'utf8');
|
|
6
|
+
|
|
7
|
+
let updatedContent = fileContent;
|
|
8
|
+
|
|
9
|
+
for (const [token, value] of Object.entries(tokens)) {
|
|
10
|
+
updatedContent = updatedContent.replaceAll(token, value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
await writeFile(filePath, updatedContent, 'utf8');
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const replaceTokensRecursively = async (targetPath, tokens) => {
|
|
17
|
+
const targetStat = await stat(targetPath);
|
|
18
|
+
|
|
19
|
+
if (targetStat.isFile()) {
|
|
20
|
+
await replaceTokensInFile(targetPath, tokens);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!targetStat.isDirectory()) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const entries = await readdir(targetPath, { withFileTypes: true });
|
|
29
|
+
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
const entryPath = resolve(targetPath, entry.name);
|
|
32
|
+
|
|
33
|
+
if (entry.isDirectory()) {
|
|
34
|
+
await replaceTokensRecursively(entryPath, tokens);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (entry.isFile()) {
|
|
39
|
+
await replaceTokensInFile(entryPath, tokens);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export async function replaceTokens(targetDirPath, tokens) {
|
|
45
|
+
await replaceTokensRecursively(targetDirPath, tokens);
|
|
46
46
|
}
|