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.
Files changed (38) hide show
  1. package/README.md +318 -0
  2. package/index.js +531 -0
  3. package/package.json +32 -0
  4. package/templates/.prettierignore.template +3 -0
  5. package/templates/.prettierrc.template +7 -0
  6. package/templates/App-query-only.jsx.template +18 -0
  7. package/templates/App-query-only.tsx.template +18 -0
  8. package/templates/App-with-query.jsx.template +17 -0
  9. package/templates/App-with-query.tsx.template +17 -0
  10. package/templates/App.jsx.template +9 -0
  11. package/templates/App.tsx.template +9 -0
  12. package/templates/ErrorBoundary.jsx.template +23 -0
  13. package/templates/ErrorBoundary.tsx.template +23 -0
  14. package/templates/ErrorPage.jsx.template +40 -0
  15. package/templates/ErrorPage.tsx.template +40 -0
  16. package/templates/ExampleForm.jsx.template +48 -0
  17. package/templates/ExampleForm.tsx.template +48 -0
  18. package/templates/HomePage.jsx.template +10 -0
  19. package/templates/HomePage.tsx.template +10 -0
  20. package/templates/eslint.config.js.template +38 -0
  21. package/templates/eslint.config.ts.template +28 -0
  22. package/templates/index.css.template +1 -0
  23. package/templates/jsconfig.json.template +23 -0
  24. package/templates/main-with-error-boundary.jsx.template +19 -0
  25. package/templates/main-with-error-boundary.tsx.template +19 -0
  26. package/templates/queryClient.js.template +10 -0
  27. package/templates/queryClient.ts.template +10 -0
  28. package/templates/router.jsx.template +11 -0
  29. package/templates/router.tsx.template +11 -0
  30. package/templates/tsconfig.json.template +24 -0
  31. package/templates/vite.config-alias-no-tailwind.js.template +14 -0
  32. package/templates/vite.config-alias-no-tailwind.ts.template +14 -0
  33. package/templates/vite.config-with-alias.js.template +16 -0
  34. package/templates/vite.config-with-alias.ts.template +16 -0
  35. package/templates/vite.config.js.template +10 -0
  36. package/templates/vite.config.ts.template +10 -0
  37. package/templates/zustand-store.js.template +7 -0
  38. 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,3 @@
1
+ dist
2
+ node_modules
3
+ .git
@@ -0,0 +1,7 @@
1
+ {
2
+ "semi": true,
3
+ "trailingComma": "es5",
4
+ "singleQuote": true,
5
+ "printWidth": 100,
6
+ "tabWidth": 2
7
+ }
@@ -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;
@@ -0,0 +1,9 @@
1
+ import { RouterProvider } from 'react-router-dom';
2
+ import { router } from './router';
3
+ import './App.css';
4
+
5
+ function App() {
6
+ return <RouterProvider router={router} />;
7
+ }
8
+
9
+ export default App;
@@ -0,0 +1,9 @@
1
+ import { RouterProvider } from 'react-router-dom';
2
+ import { router } from './router';
3
+ import './App.css';
4
+
5
+ function App() {
6
+ return <RouterProvider router={router} />;
7
+ }
8
+
9
+ export default App;