fs-starter 1.0.0
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/README.md +318 -0
- package/index.js +531 -0
- package/package.json +32 -0
- package/templates/.prettierignore.template +3 -0
- package/templates/.prettierrc.template +7 -0
- package/templates/App-query-only.jsx.template +18 -0
- package/templates/App-query-only.tsx.template +18 -0
- package/templates/App-with-query.jsx.template +17 -0
- package/templates/App-with-query.tsx.template +17 -0
- package/templates/App.jsx.template +9 -0
- package/templates/App.tsx.template +9 -0
- package/templates/ErrorBoundary.jsx.template +23 -0
- package/templates/ErrorBoundary.tsx.template +23 -0
- package/templates/ErrorPage.jsx.template +40 -0
- package/templates/ErrorPage.tsx.template +40 -0
- package/templates/ExampleForm.jsx.template +48 -0
- package/templates/ExampleForm.tsx.template +48 -0
- package/templates/HomePage.jsx.template +10 -0
- package/templates/HomePage.tsx.template +10 -0
- package/templates/eslint.config.js.template +38 -0
- package/templates/eslint.config.ts.template +28 -0
- package/templates/index.css.template +1 -0
- package/templates/jsconfig.json.template +23 -0
- package/templates/main-with-error-boundary.jsx.template +19 -0
- package/templates/main-with-error-boundary.tsx.template +19 -0
- package/templates/queryClient.js.template +10 -0
- package/templates/queryClient.ts.template +10 -0
- package/templates/router.jsx.template +11 -0
- package/templates/router.tsx.template +11 -0
- package/templates/tsconfig.json.template +24 -0
- package/templates/vite.config-alias-no-tailwind.js.template +14 -0
- package/templates/vite.config-alias-no-tailwind.ts.template +14 -0
- package/templates/vite.config-with-alias.js.template +16 -0
- package/templates/vite.config-with-alias.ts.template +16 -0
- package/templates/vite.config.js.template +10 -0
- package/templates/vite.config.ts.template +10 -0
- package/templates/zustand-store.js.template +7 -0
- package/templates/zustand-store.ts.template +13 -0
package/index.js
ADDED
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { input, select, confirm } from '@inquirer/prompts';
|
|
4
|
+
import { execa } from 'execa';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import fs from 'fs-extra';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
|
|
13
|
+
// Check Node.js version (requires >= 18 for ESM and top-level await)
|
|
14
|
+
const NODE_VERSION_REQUIRED = '18.0.0';
|
|
15
|
+
const currentVersion = process.version.slice(1); // Remove 'v' prefix
|
|
16
|
+
|
|
17
|
+
function compareVersions(current, required) {
|
|
18
|
+
const currentParts = current.split('.').map(Number);
|
|
19
|
+
const requiredParts = required.split('.').map(Number);
|
|
20
|
+
|
|
21
|
+
for (let i = 0; i < requiredParts.length; i++) {
|
|
22
|
+
if (currentParts[i] > requiredParts[i]) return 1;
|
|
23
|
+
if (currentParts[i] < requiredParts[i]) return -1;
|
|
24
|
+
}
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (compareVersions(currentVersion, NODE_VERSION_REQUIRED) < 0) {
|
|
29
|
+
console.error(chalk.red(`\n❌ Error: Node.js ${NODE_VERSION_REQUIRED} or higher is required.`));
|
|
30
|
+
console.error(chalk.yellow(`Current version: ${process.version}`));
|
|
31
|
+
console.error(chalk.cyan(`Please upgrade Node.js: https://nodejs.org/\n`));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Helper to read templates
|
|
36
|
+
async function readTemplate(templateName) {
|
|
37
|
+
const templatePath = path.join(__dirname, 'templates', templateName);
|
|
38
|
+
return await fs.readFile(templatePath, 'utf-8');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function setup() {
|
|
42
|
+
console.log(chalk.bold.cyan(`
|
|
43
|
+
______ _ _ _ _
|
|
44
|
+
| ___| | | | | | | | |
|
|
45
|
+
| |_ _ __ ___ _ __ | |_ ___ _ __ __| | ___| |_ __ _ _ __| |_ ___ _ __
|
|
46
|
+
| _| '__/ _ \\| '_ \\| __/ _ \\ '_ \\ / _\` | / __| __/ _\` | '__| __/ _ \\ '__|
|
|
47
|
+
| | | | | (_) | | | | || __/ | | | (_| | \\__ \\ || (_| | | | || __/ |
|
|
48
|
+
\\_| |_| \\___/|_| |_|\\__\\___|_| |_|\\__,_| |___/\\__\\__,_|_| \\__\\___|_|
|
|
49
|
+
|
|
50
|
+
`));
|
|
51
|
+
|
|
52
|
+
// --- 1. INTERACTIVE QUESTIONS ---
|
|
53
|
+
const projectName = await input({
|
|
54
|
+
message: 'Project name:',
|
|
55
|
+
default: 'my-react-app',
|
|
56
|
+
validate: (value) => {
|
|
57
|
+
if (!value || value.trim() === '') {
|
|
58
|
+
return 'Project name is required';
|
|
59
|
+
}
|
|
60
|
+
if (!/^[a-z0-9-_]+$/.test(value)) {
|
|
61
|
+
return 'Use only lowercase letters, numbers, hyphens and underscores';
|
|
62
|
+
}
|
|
63
|
+
if (fs.existsSync(path.join(process.cwd(), value))) {
|
|
64
|
+
return 'A folder with this name already exists';
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const packageManager = await select({
|
|
71
|
+
message: 'Choose package manager:',
|
|
72
|
+
choices: [
|
|
73
|
+
{ name: chalk.blue('yarn'), value: 'yarn' },
|
|
74
|
+
{ name: chalk.red('npm'), value: 'npm' },
|
|
75
|
+
{ name: chalk.yellow('pnpm'), value: 'pnpm' },
|
|
76
|
+
],
|
|
77
|
+
default: 'yarn'
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const lang = await select({
|
|
81
|
+
message: 'Choose language:',
|
|
82
|
+
choices: [
|
|
83
|
+
{ name: chalk.blue('TypeScript'), value: 'ts' },
|
|
84
|
+
{ name: chalk.yellow('JavaScript'), value: 'js' },
|
|
85
|
+
],
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const useTailwind = await confirm({
|
|
89
|
+
message: 'Install Tailwind CSS v4?',
|
|
90
|
+
default: true
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const useZustand = await confirm({
|
|
94
|
+
message: 'Install Zustand?',
|
|
95
|
+
default: false
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const useTanstackQuery = await confirm({
|
|
99
|
+
message: 'Install TanStack Query (React Query)?',
|
|
100
|
+
default: false
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const useRouter = await confirm({
|
|
104
|
+
message: 'Install React Router Dom?',
|
|
105
|
+
default: true
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const useAtomic = await confirm({
|
|
109
|
+
message: 'Use Atomic Design folder structure?',
|
|
110
|
+
default: true
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const initGit = await confirm({
|
|
114
|
+
message: 'Initialize Git repository?',
|
|
115
|
+
default: true
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const useEslint = await confirm({
|
|
119
|
+
message: 'Install ESLint?',
|
|
120
|
+
default: true
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const usePrettier = await confirm({
|
|
124
|
+
message: 'Install Prettier?',
|
|
125
|
+
default: true
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const useFormik = await confirm({
|
|
129
|
+
message: 'Install Formik + Yup for form handling?',
|
|
130
|
+
default: false
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Ask about Error Boundary only if Router is not installed
|
|
134
|
+
// (Router has its own errorElement system)
|
|
135
|
+
let useErrorBoundary = false;
|
|
136
|
+
if (!useRouter) {
|
|
137
|
+
useErrorBoundary = await confirm({
|
|
138
|
+
message: 'Add Error Boundary component (react-error-boundary)?',
|
|
139
|
+
default: true
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Path aliases are always configured
|
|
144
|
+
const usePathAliases = true;
|
|
145
|
+
|
|
146
|
+
// --- PATH CONFIGURATION ---
|
|
147
|
+
const projectPath = path.join(process.cwd(), projectName);
|
|
148
|
+
const template = lang === 'ts' ? 'react-ts' : 'react';
|
|
149
|
+
const ext = lang === 'ts' ? 'ts' : 'js';
|
|
150
|
+
const jsxExt = lang === 'ts' ? 'tsx' : 'jsx';
|
|
151
|
+
|
|
152
|
+
// --- CLEANUP FUNCTION ---
|
|
153
|
+
const cleanup = async () => {
|
|
154
|
+
if (fs.existsSync(projectPath)) {
|
|
155
|
+
await fs.remove(projectPath);
|
|
156
|
+
console.log(chalk.yellow('Project folder removed due to error.'));
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// --- 2. BASE PROJECT CREATION ---
|
|
161
|
+
const spinner = ora('Creating base project with Vite...').start();
|
|
162
|
+
try {
|
|
163
|
+
const createCmd = packageManager === 'npm' ? 'npm' : packageManager;
|
|
164
|
+
const createArgs = packageManager === 'npm'
|
|
165
|
+
? ['create', 'vite@latest', projectName, '--', '--template', template]
|
|
166
|
+
: ['create', 'vite', projectName, '--template', template];
|
|
167
|
+
|
|
168
|
+
await execa(createCmd, createArgs);
|
|
169
|
+
spinner.succeed('Base project created.');
|
|
170
|
+
} catch (err) {
|
|
171
|
+
spinner.fail('Error creating project.');
|
|
172
|
+
console.error(chalk.red(err.message));
|
|
173
|
+
await cleanup();
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// --- 3. FOLDER STRUCTURE CREATION ---
|
|
178
|
+
const structureSpinner = ora("Creating folder structure...").start();
|
|
179
|
+
try {
|
|
180
|
+
const folders = useAtomic
|
|
181
|
+
? ['Atoms', 'Molecules', 'Organisms', 'Pages', 'Stores', 'Utils']
|
|
182
|
+
: ['Stores', 'Utils', 'Pages'];
|
|
183
|
+
|
|
184
|
+
for (const folder of folders) {
|
|
185
|
+
await fs.ensureDir(path.join(projectPath, 'src', folder));
|
|
186
|
+
}
|
|
187
|
+
structureSpinner.succeed(`Folders created: ${folders.join(', ')}`);
|
|
188
|
+
} catch (err) {
|
|
189
|
+
structureSpinner.fail(`Error creating folders: ${err.message}`);
|
|
190
|
+
console.error(chalk.red(err));
|
|
191
|
+
await cleanup();
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// --- 4. TAILWIND V4 CONFIGURATION ---
|
|
196
|
+
if (useTailwind) {
|
|
197
|
+
const twSpinner = ora('Configuring Tailwind v4...').start();
|
|
198
|
+
try {
|
|
199
|
+
// Development dependencies only (-D flag)
|
|
200
|
+
await execa(packageManager, ['add', '-D', 'tailwindcss', '@tailwindcss/vite'], { cwd: projectPath });
|
|
201
|
+
|
|
202
|
+
const viteConfigFileName = lang === 'ts' ? 'vite.config.ts' : 'vite.config.js';
|
|
203
|
+
const viteConfigPath = path.join(projectPath, viteConfigFileName);
|
|
204
|
+
|
|
205
|
+
// Use template with or without path aliases
|
|
206
|
+
const templateName = usePathAliases
|
|
207
|
+
? `vite.config-with-alias.${lang === 'ts' ? 'ts' : 'js'}.template`
|
|
208
|
+
: `vite.config.${lang === 'ts' ? 'ts' : 'js'}.template`;
|
|
209
|
+
const viteConfigContent = await readTemplate(templateName);
|
|
210
|
+
await fs.writeFile(viteConfigPath, viteConfigContent);
|
|
211
|
+
|
|
212
|
+
const cssPath = path.join(projectPath, 'src/index.css');
|
|
213
|
+
const cssContent = await readTemplate('index.css.template');
|
|
214
|
+
await fs.writeFile(cssPath, cssContent);
|
|
215
|
+
|
|
216
|
+
twSpinner.succeed('Tailwind v4 configured.');
|
|
217
|
+
} catch (err) {
|
|
218
|
+
twSpinner.fail(`Error configuring Tailwind: ${err.message}`);
|
|
219
|
+
console.error(chalk.red(err));
|
|
220
|
+
await cleanup();
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// --- 5. ZUSTAND CONFIGURATION ---
|
|
226
|
+
if (useZustand) {
|
|
227
|
+
const zSpinner = ora('Installing Zustand...').start();
|
|
228
|
+
try {
|
|
229
|
+
// Runtime dependency
|
|
230
|
+
await execa(packageManager, ['add', 'zustand'], { cwd: projectPath });
|
|
231
|
+
|
|
232
|
+
const storeContent = await readTemplate(`zustand-store.${lang === 'ts' ? 'ts' : 'js'}.template`);
|
|
233
|
+
await fs.writeFile(path.join(projectPath, 'src/Stores', `useStore.${ext}`), storeContent);
|
|
234
|
+
zSpinner.succeed('Zustand installed in src/Stores/.');
|
|
235
|
+
} catch (err) {
|
|
236
|
+
zSpinner.fail(`Error installing Zustand: ${err.message}`);
|
|
237
|
+
console.error(chalk.red(err));
|
|
238
|
+
await cleanup();
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// --- 6. REACT ROUTER CONFIGURATION ---
|
|
244
|
+
if (useRouter) {
|
|
245
|
+
const rSpinner = ora('Installing React Router...').start();
|
|
246
|
+
try {
|
|
247
|
+
// Runtime dependency
|
|
248
|
+
await execa(packageManager, ['add', 'react-router-dom'], { cwd: projectPath });
|
|
249
|
+
|
|
250
|
+
// Create HomePage component
|
|
251
|
+
const homePageContent = await readTemplate(`HomePage.${jsxExt}.template`);
|
|
252
|
+
await fs.writeFile(path.join(projectPath, 'src/Pages', `HomePage.${jsxExt}`), homePageContent);
|
|
253
|
+
|
|
254
|
+
// Create ErrorPage component for router error handling
|
|
255
|
+
const errorPageContent = await readTemplate(`ErrorPage.${jsxExt}.template`);
|
|
256
|
+
await fs.writeFile(path.join(projectPath, 'src', `ErrorPage.${jsxExt}`), errorPageContent);
|
|
257
|
+
|
|
258
|
+
// Create router file
|
|
259
|
+
const routerContent = await readTemplate(`router.${jsxExt}.template`);
|
|
260
|
+
await fs.writeFile(path.join(projectPath, `src/router.${jsxExt}`), routerContent);
|
|
261
|
+
|
|
262
|
+
// App.tsx will be created later based on Router + Query combination
|
|
263
|
+
rSpinner.succeed('React Router configured with error handling.');
|
|
264
|
+
} catch (err) {
|
|
265
|
+
rSpinner.fail(`Error installing React Router: ${err.message}`);
|
|
266
|
+
console.error(chalk.red(err));
|
|
267
|
+
await cleanup();
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// --- 7. TANSTACK QUERY CONFIGURATION ---
|
|
273
|
+
if (useTanstackQuery) {
|
|
274
|
+
const tqSpinner = ora('Installing TanStack Query...').start();
|
|
275
|
+
try {
|
|
276
|
+
// Runtime dependencies
|
|
277
|
+
await execa(packageManager, ['add', '@tanstack/react-query', '@tanstack/react-query-devtools'], { cwd: projectPath });
|
|
278
|
+
|
|
279
|
+
// Create queryClient file
|
|
280
|
+
const queryClientContent = await readTemplate(`queryClient.${ext}.template`);
|
|
281
|
+
await fs.writeFile(path.join(projectPath, 'src', `queryClient.${ext}`), queryClientContent);
|
|
282
|
+
|
|
283
|
+
tqSpinner.succeed('TanStack Query installed and configured.');
|
|
284
|
+
} catch (err) {
|
|
285
|
+
tqSpinner.fail(`Error installing TanStack Query: ${err.message}`);
|
|
286
|
+
console.error(chalk.red(err));
|
|
287
|
+
await cleanup();
|
|
288
|
+
process.exit(1);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// --- 7.5. APP.TSX/JSX GENERATION (after Router and Query setup) ---
|
|
293
|
+
// This ensures the correct template is used based on Router + Query combination
|
|
294
|
+
const appSpinner = ora('Generating App component...').start();
|
|
295
|
+
try {
|
|
296
|
+
let appTemplate;
|
|
297
|
+
|
|
298
|
+
if (useRouter && useTanstackQuery) {
|
|
299
|
+
// Router + Query: includes RouterProvider and QueryClientProvider
|
|
300
|
+
appTemplate = `App-with-query.${jsxExt}.template`;
|
|
301
|
+
} else if (useRouter && !useTanstackQuery) {
|
|
302
|
+
// Router only: includes RouterProvider
|
|
303
|
+
appTemplate = `App.${jsxExt}.template`;
|
|
304
|
+
} else if (!useRouter && useTanstackQuery) {
|
|
305
|
+
// Query only: includes QueryClientProvider
|
|
306
|
+
appTemplate = `App-query-only.${jsxExt}.template`;
|
|
307
|
+
} else {
|
|
308
|
+
// Neither Router nor Query: basic App
|
|
309
|
+
appTemplate = `App.${jsxExt}.template`;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const appContent = await readTemplate(appTemplate);
|
|
313
|
+
await fs.writeFile(path.join(projectPath, `src/App.${jsxExt}`), appContent);
|
|
314
|
+
|
|
315
|
+
appSpinner.succeed('App component generated.');
|
|
316
|
+
} catch (err) {
|
|
317
|
+
appSpinner.fail(`Error generating App component: ${err.message}`);
|
|
318
|
+
console.error(chalk.red(err));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// --- 8. PATH ALIASES CONFIGURATION ---
|
|
322
|
+
if (usePathAliases && !useTailwind) {
|
|
323
|
+
const aliasSpinner = ora('Configuring path aliases...').start();
|
|
324
|
+
try {
|
|
325
|
+
// Update vite.config with aliases if Tailwind is not installed
|
|
326
|
+
const viteConfigFileName = lang === 'ts' ? 'vite.config.ts' : 'vite.config.js';
|
|
327
|
+
const viteConfigPath = path.join(projectPath, viteConfigFileName);
|
|
328
|
+
|
|
329
|
+
const templateName = `vite.config-alias-no-tailwind.${lang === 'ts' ? 'ts' : 'js'}.template`;
|
|
330
|
+
const viteConfigContent = await readTemplate(templateName);
|
|
331
|
+
await fs.writeFile(viteConfigPath, viteConfigContent);
|
|
332
|
+
|
|
333
|
+
aliasSpinner.succeed('Path aliases configured.');
|
|
334
|
+
} catch (err) {
|
|
335
|
+
aliasSpinner.fail(`Error configuring path aliases: ${err.message}`);
|
|
336
|
+
console.error(chalk.red(err));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Configure tsconfig/jsconfig for path aliases
|
|
341
|
+
if (usePathAliases) {
|
|
342
|
+
try {
|
|
343
|
+
const configFileName = lang === 'ts' ? 'tsconfig.json' : 'jsconfig.json';
|
|
344
|
+
const configPath = path.join(projectPath, configFileName);
|
|
345
|
+
|
|
346
|
+
const configContent = await readTemplate(`${configFileName}.template`);
|
|
347
|
+
await fs.writeFile(configPath, configContent);
|
|
348
|
+
} catch (err) {
|
|
349
|
+
console.error(chalk.red(`Error creating ${lang === 'ts' ? 'tsconfig' : 'jsconfig'}: ${err.message}`));
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// --- 9. FORMIK + YUP CONFIGURATION ---
|
|
354
|
+
if (useFormik) {
|
|
355
|
+
const formikSpinner = ora('Installing Formik + Yup...').start();
|
|
356
|
+
try {
|
|
357
|
+
// Runtime dependencies
|
|
358
|
+
await execa(packageManager, ['add', 'formik', 'yup'], { cwd: projectPath });
|
|
359
|
+
|
|
360
|
+
// Create example form in Utils folder
|
|
361
|
+
const formContent = await readTemplate(`ExampleForm.${jsxExt}.template`);
|
|
362
|
+
await fs.writeFile(path.join(projectPath, 'src/Utils', `ExampleForm.${jsxExt}`), formContent);
|
|
363
|
+
|
|
364
|
+
formikSpinner.succeed('Formik + Yup installed with example form in src/Utils/.');
|
|
365
|
+
} catch (err) {
|
|
366
|
+
formikSpinner.fail(`Error installing Formik + Yup: ${err.message}`);
|
|
367
|
+
console.error(chalk.red(err));
|
|
368
|
+
await cleanup();
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// --- 10. ERROR BOUNDARY CONFIGURATION ---
|
|
374
|
+
if (useErrorBoundary) {
|
|
375
|
+
const ebSpinner = ora('Installing Error Boundary...').start();
|
|
376
|
+
try {
|
|
377
|
+
// Runtime dependency
|
|
378
|
+
await execa(packageManager, ['add', 'react-error-boundary'], { cwd: projectPath });
|
|
379
|
+
|
|
380
|
+
// Create ErrorFallback component
|
|
381
|
+
const errorFallbackContent = await readTemplate(`ErrorBoundary.${jsxExt}.template`);
|
|
382
|
+
await fs.writeFile(path.join(projectPath, 'src', `ErrorFallback.${jsxExt}`), errorFallbackContent);
|
|
383
|
+
|
|
384
|
+
// Update main.tsx/jsx to wrap App with ErrorBoundary
|
|
385
|
+
const mainContent = await readTemplate(`main-with-error-boundary.${jsxExt}.template`);
|
|
386
|
+
const mainFileName = lang === 'ts' ? 'main.tsx' : 'main.jsx';
|
|
387
|
+
await fs.writeFile(path.join(projectPath, 'src', mainFileName), mainContent);
|
|
388
|
+
|
|
389
|
+
ebSpinner.succeed('Error Boundary configured with react-error-boundary.');
|
|
390
|
+
} catch (err) {
|
|
391
|
+
ebSpinner.fail(`Error configuring Error Boundary: ${err.message}`);
|
|
392
|
+
console.error(chalk.red(err));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// --- 11. ESLINT CONFIGURATION ---
|
|
397
|
+
if (useEslint) {
|
|
398
|
+
const eslintSpinner = ora('Installing ESLint...').start();
|
|
399
|
+
try {
|
|
400
|
+
// Development dependencies only (-D flag)
|
|
401
|
+
const eslintPkgs = lang === 'ts'
|
|
402
|
+
? ['eslint', '@eslint/js', 'typescript-eslint', 'eslint-plugin-react', 'eslint-plugin-react-hooks', 'eslint-plugin-react-refresh', 'globals']
|
|
403
|
+
: ['eslint', '@eslint/js', 'eslint-plugin-react', 'eslint-plugin-react-hooks', 'eslint-plugin-react-refresh', 'globals'];
|
|
404
|
+
|
|
405
|
+
await execa(packageManager, ['add', '-D', ...eslintPkgs], { cwd: projectPath });
|
|
406
|
+
|
|
407
|
+
const eslintConfig = await readTemplate(`eslint.config.${lang === 'ts' ? 'ts' : 'js'}.template`);
|
|
408
|
+
await fs.writeFile(path.join(projectPath, 'eslint.config.js'), eslintConfig);
|
|
409
|
+
eslintSpinner.succeed('ESLint configured.');
|
|
410
|
+
} catch (err) {
|
|
411
|
+
eslintSpinner.fail(`Error installing ESLint: ${err.message}`);
|
|
412
|
+
console.error(chalk.red(err));
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// --- 12. PRETTIER CONFIGURATION ---
|
|
417
|
+
if (usePrettier) {
|
|
418
|
+
const prettierSpinner = ora('Installing Prettier...').start();
|
|
419
|
+
try {
|
|
420
|
+
// Development dependency only (-D flag)
|
|
421
|
+
await execa(packageManager, ['add', '-D', 'prettier'], { cwd: projectPath });
|
|
422
|
+
|
|
423
|
+
const prettierConfig = await readTemplate('.prettierrc.template');
|
|
424
|
+
await fs.writeFile(
|
|
425
|
+
path.join(projectPath, '.prettierrc'),
|
|
426
|
+
prettierConfig
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
const prettierIgnore = await readTemplate('.prettierignore.template');
|
|
430
|
+
await fs.writeFile(
|
|
431
|
+
path.join(projectPath, '.prettierignore'),
|
|
432
|
+
prettierIgnore
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
prettierSpinner.succeed('Prettier configured.');
|
|
436
|
+
} catch (err) {
|
|
437
|
+
prettierSpinner.fail(`Error installing Prettier: ${err.message}`);
|
|
438
|
+
console.error(chalk.red(err));
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// --- 13. GIT INITIALIZATION ---
|
|
443
|
+
if (initGit) {
|
|
444
|
+
const gitSpinner = ora('Initializing Git...').start();
|
|
445
|
+
try {
|
|
446
|
+
await execa('git', ['init'], { cwd: projectPath });
|
|
447
|
+
await execa('git', ['add', '.'], { cwd: projectPath });
|
|
448
|
+
await execa('git', ['commit', '-m', 'Initial commit'], { cwd: projectPath });
|
|
449
|
+
gitSpinner.succeed('Git repository initialized.');
|
|
450
|
+
} catch (err) {
|
|
451
|
+
gitSpinner.fail(`Error initializing Git: ${err.message}`);
|
|
452
|
+
console.error(chalk.yellow('Git is not required, continuing...'));
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// --- 14. README GENERATION ---
|
|
457
|
+
const readmeSpinner = ora('Generating README.md...').start();
|
|
458
|
+
try {
|
|
459
|
+
const features = [];
|
|
460
|
+
if (useTailwind) features.push('Tailwind CSS v4');
|
|
461
|
+
if (useZustand) features.push('Zustand');
|
|
462
|
+
if (useTanstackQuery) features.push('TanStack Query (React Query)');
|
|
463
|
+
if (useRouter) features.push('React Router with Error Handling');
|
|
464
|
+
if (usePathAliases) features.push('Path Aliases (@/ imports)');
|
|
465
|
+
if (useFormik) features.push('Formik + Yup');
|
|
466
|
+
if (useErrorBoundary) features.push('Error Boundary (react-error-boundary)');
|
|
467
|
+
if (useEslint) features.push('ESLint');
|
|
468
|
+
if (usePrettier) features.push('Prettier');
|
|
469
|
+
|
|
470
|
+
const readmeContent = `# ${projectName}
|
|
471
|
+
|
|
472
|
+
React project generated with Vite.
|
|
473
|
+
|
|
474
|
+
## 🚀 Features
|
|
475
|
+
|
|
476
|
+
- ⚡ Vite
|
|
477
|
+
- ⚛️ React 18
|
|
478
|
+
- ${lang === 'ts' ? '📘 TypeScript' : '📗 JavaScript'}
|
|
479
|
+
${features.map(f => `- ✨ ${f}`).join('\n')}
|
|
480
|
+
${useAtomic ? '- 📁 Atomic Design Structure' : ''}
|
|
481
|
+
|
|
482
|
+
## 📦 Installation
|
|
483
|
+
|
|
484
|
+
\`\`\`bash
|
|
485
|
+
${packageManager} install
|
|
486
|
+
\`\`\`
|
|
487
|
+
|
|
488
|
+
## 🏃 Start
|
|
489
|
+
|
|
490
|
+
\`\`\`bash
|
|
491
|
+
${packageManager} ${packageManager === 'npm' ? 'run ' : ''}dev
|
|
492
|
+
\`\`\`
|
|
493
|
+
|
|
494
|
+
## 🏗️ Build
|
|
495
|
+
|
|
496
|
+
\`\`\`bash
|
|
497
|
+
${packageManager} ${packageManager === 'npm' ? 'run ' : ''}build
|
|
498
|
+
\`\`\`
|
|
499
|
+
|
|
500
|
+
## 📂 Project Structure
|
|
501
|
+
|
|
502
|
+
\`\`\`
|
|
503
|
+
src/
|
|
504
|
+
${useAtomic ? '├── Atoms/ # Atomic components\n├── Molecules/ # Molecular components\n├── Organisms/ # Complex components\n' : ''}├── Pages/ # Application pages
|
|
505
|
+
├── Stores/ # State management
|
|
506
|
+
└── Utils/ # Utility functions
|
|
507
|
+
\`\`\`
|
|
508
|
+
|
|
509
|
+
---
|
|
510
|
+
|
|
511
|
+
Generated with ❤️ using frontend-starter
|
|
512
|
+
`;
|
|
513
|
+
|
|
514
|
+
await fs.writeFile(path.join(projectPath, 'README.md'), readmeContent);
|
|
515
|
+
readmeSpinner.succeed('README.md generated.');
|
|
516
|
+
} catch (err) {
|
|
517
|
+
readmeSpinner.fail(`Error generating README: ${err.message}`);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// --- CONCLUSION ---
|
|
521
|
+
console.log(chalk.green(`\n✅ Project "${projectName}" successfully generated!`));
|
|
522
|
+
console.log(chalk.white(`\n📋 To get started:`));
|
|
523
|
+
console.log(chalk.cyan(` 1. cd ${projectName}`));
|
|
524
|
+
console.log(chalk.cyan(` 2. ${packageManager} install`));
|
|
525
|
+
console.log(chalk.cyan(` 3. ${packageManager} ${packageManager === 'npm' ? 'run ' : ''}dev\n`));
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
setup().catch((err) => {
|
|
529
|
+
console.error(chalk.red('\n❌ Fatal error:'), err);
|
|
530
|
+
process.exit(1);
|
|
531
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fs-starter",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"fs-starter": "./index.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "index.js",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [],
|
|
13
|
+
"author": "",
|
|
14
|
+
"license": "ISC",
|
|
15
|
+
"description": "",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@inquirer/prompts": "^8.2.0",
|
|
18
|
+
"chalk": "^5.6.2",
|
|
19
|
+
"execa": "^9.6.1",
|
|
20
|
+
"fs-extra": "^11.3.3",
|
|
21
|
+
"inquirer": "^13.2.2",
|
|
22
|
+
"ora": "^9.2.0"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"index.js",
|
|
26
|
+
"templates/",
|
|
27
|
+
"utils/"
|
|
28
|
+
],
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18.0.0"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { QueryClientProvider } from '@tanstack/react-query';
|
|
2
|
+
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
|
3
|
+
import { queryClient } from './queryClient';
|
|
4
|
+
import './App.css';
|
|
5
|
+
|
|
6
|
+
function App() {
|
|
7
|
+
return (
|
|
8
|
+
<QueryClientProvider client={queryClient}>
|
|
9
|
+
<div>
|
|
10
|
+
<h1>Welcome to React + Vite</h1>
|
|
11
|
+
<p>Start building your app!</p>
|
|
12
|
+
</div>
|
|
13
|
+
<ReactQueryDevtools initialIsOpen={false} />
|
|
14
|
+
</QueryClientProvider>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default App;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { QueryClientProvider } from '@tanstack/react-query';
|
|
2
|
+
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
|
3
|
+
import { queryClient } from './queryClient';
|
|
4
|
+
import './App.css';
|
|
5
|
+
|
|
6
|
+
function App() {
|
|
7
|
+
return (
|
|
8
|
+
<QueryClientProvider client={queryClient}>
|
|
9
|
+
<div>
|
|
10
|
+
<h1>Welcome to React + Vite</h1>
|
|
11
|
+
<p>Start building your app!</p>
|
|
12
|
+
</div>
|
|
13
|
+
<ReactQueryDevtools initialIsOpen={false} />
|
|
14
|
+
</QueryClientProvider>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default App;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { RouterProvider } from 'react-router-dom';
|
|
2
|
+
import { QueryClientProvider } from '@tanstack/react-query';
|
|
3
|
+
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
|
4
|
+
import { router } from './router';
|
|
5
|
+
import { queryClient } from './queryClient';
|
|
6
|
+
import './App.css';
|
|
7
|
+
|
|
8
|
+
function App() {
|
|
9
|
+
return (
|
|
10
|
+
<QueryClientProvider client={queryClient}>
|
|
11
|
+
<RouterProvider router={router} />
|
|
12
|
+
<ReactQueryDevtools initialIsOpen={false} />
|
|
13
|
+
</QueryClientProvider>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default App;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { RouterProvider } from 'react-router-dom';
|
|
2
|
+
import { QueryClientProvider } from '@tanstack/react-query';
|
|
3
|
+
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
|
4
|
+
import { router } from './router';
|
|
5
|
+
import { queryClient } from './queryClient';
|
|
6
|
+
import './App.css';
|
|
7
|
+
|
|
8
|
+
function App() {
|
|
9
|
+
return (
|
|
10
|
+
<QueryClientProvider client={queryClient}>
|
|
11
|
+
<RouterProvider router={router} />
|
|
12
|
+
<ReactQueryDevtools initialIsOpen={false} />
|
|
13
|
+
</QueryClientProvider>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default App;
|